Add Turkish (tr) docs and update README (#744)

* Add Turkish (tr) docs and update README

Add a full set of Turkish documentation under docs/tr (agents, changelog, CLAUDE guide, contributing, code of conduct, and many agents/commands/skills/rules files). Update README to include a link to the Turkish docs and increment the supported language count from 5 to 6. This commit adds localized guidance and references to help Turkish-speaking contributors and users.

* Update docs/tr/TROUBLESHOOTING.md

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Update docs/tr/README.md

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* docs(tr): fix license link and update readmes

Update Turkish docs: change license badge link to point to repository root (../../LICENSE), increment displayed language count from 5 to 6, and remove two outdated related links from docs/tr/examples/README.md to keep references accurate.

* Update docs/tr/commands/instinct-import.md

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Update docs/tr/commands/checkpoint.md

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
Berkcan Gümüşışık
2026-03-23 01:37:04 +03:00
committed by GitHub
parent bb1efad7c7
commit fd2a8edb53
139 changed files with 26670 additions and 2 deletions

View File

@@ -0,0 +1,523 @@
---
name: api-design
description: REST API tasarım kalıpları; kaynak isimlendirme, durum kodları, sayfalama, filtreleme, hata yanıtları, versiyonlama ve üretim API'leri için hız sınırlama içerir.
origin: ECC
---
# API Tasarım Kalıpları
Tutarlı, geliştirici dostu REST API'leri tasarlamak için konvansiyonlar ve en iyi uygulamalar.
## Ne Zaman Aktifleştirmeli
- Yeni API endpoint'leri tasarlarken
- Mevcut API sözleşmelerini incelerken
- Sayfalama, filtreleme veya sıralama eklerken
- API'ler için hata işleme uygularken
- API versiyonlama stratejisi planlarken
- Halka açık veya iş ortağı odaklı API'ler oluştururken
## Kaynak Tasarımı
### URL Yapısı
```
# Kaynaklar isim, çoğul, küçük harf, kebab-case
GET /api/v1/users
GET /api/v1/users/:id
POST /api/v1/users
PUT /api/v1/users/:id
PATCH /api/v1/users/:id
DELETE /api/v1/users/:id
# İlişkiler için alt kaynaklar
GET /api/v1/users/:id/orders
POST /api/v1/users/:id/orders
# CRUD'a uymayan aksiyonlar (fiilleri dikkatli kullanın)
POST /api/v1/orders/:id/cancel
POST /api/v1/auth/login
POST /api/v1/auth/refresh
```
### İsimlendirme Kuralları
```
# İYİ
/api/v1/team-members # çok sözcüklü kaynaklar için kebab-case
/api/v1/orders?status=active # filtreleme için query parametreleri
/api/v1/users/123/orders # sahiplik için iç içe kaynaklar
# KÖTÜ
/api/v1/getUsers # URL'de fiil
/api/v1/user # tekil (çoğul kullanın)
/api/v1/team_members # URL'lerde snake_case
/api/v1/users/123/getOrders # iç içe kaynaklarda fiil
```
## HTTP Metodları ve Durum Kodları
### Metod Semantiği
| Metod | Idempotent | Güvenli | Kullanım Amacı |
|--------|-----------|------|---------|
| GET | Evet | Evet | Kaynakları getir |
| POST | Hayır | Hayır | Kaynak oluştur, aksiyonları tetikle |
| PUT | Evet | Hayır | Kaynağın tam değişimi |
| PATCH | Hayır* | Hayır | Kaynağın kısmi güncellemesi |
| DELETE | Evet | Hayır | Kaynağı kaldır |
*PATCH uygun implementasyonla idempotent yapılabilir
### Durum Kodu Referansı
```
# Başarı
200 OK — GET, PUT, PATCH (yanıt body'si ile)
201 Created — POST (Location header ekleyin)
204 No Content — DELETE, PUT (yanıt body'si yok)
# İstemci Hataları
400 Bad Request — Validasyon hatası, hatalı JSON
401 Unauthorized — Eksik veya geçersiz kimlik doğrulama
403 Forbidden — Kimlik doğrulandı ama yetkilendirilmedi
404 Not Found — Kaynak mevcut değil
409 Conflict — Tekrar kayıt, durum çakışması
422 Unprocessable Entity — Semantik olarak geçersiz (geçerli JSON, kötü veri)
429 Too Many Requests — Hız limiti aşıldı
# Sunucu Hataları
500 Internal Server Error — Beklenmeyen hata (detaylarıığa çıkarmayın)
502 Bad Gateway — Upstream servis başarısız
503 Service Unavailable — Geçici aşırı yük, Retry-After ekleyin
```
### Yaygın Hatalar
```
# KÖTÜ: Her şey için 200
{ "status": 200, "success": false, "error": "Not found" }
# İYİ: HTTP durum kodlarını semantik olarak kullanın
HTTP/1.1 404 Not Found
{ "error": { "code": "not_found", "message": "User not found" } }
# KÖTÜ: Validasyon hataları için 500
# İYİ: Alan düzeyinde detaylarla 400 veya 422
# KÖTÜ: Oluşturulan kaynaklar için 200
# İYİ: Location header ile 201
HTTP/1.1 201 Created
Location: /api/v1/users/abc-123
```
## Yanıt Formatı
### Başarı Yanıtı
```json
{
"data": {
"id": "abc-123",
"email": "alice@example.com",
"name": "Alice",
"created_at": "2025-01-15T10:30:00Z"
}
}
```
### Koleksiyon Yanıtı (Sayfalama ile)
```json
{
"data": [
{ "id": "abc-123", "name": "Alice" },
{ "id": "def-456", "name": "Bob" }
],
"meta": {
"total": 142,
"page": 1,
"per_page": 20,
"total_pages": 8
},
"links": {
"self": "/api/v1/users?page=1&per_page=20",
"next": "/api/v1/users?page=2&per_page=20",
"last": "/api/v1/users?page=8&per_page=20"
}
}
```
### Hata Yanıtı
```json
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Must be a valid email address",
"code": "invalid_format"
},
{
"field": "age",
"message": "Must be between 0 and 150",
"code": "out_of_range"
}
]
}
}
```
### Yanıt Zarfı Varyantları
```typescript
// Seçenek A: Data sarmalayıcılı zarf (halka açık API'ler için önerilir)
interface ApiResponse<T> {
data: T;
meta?: PaginationMeta;
links?: PaginationLinks;
}
interface ApiError {
error: {
code: string;
message: string;
details?: FieldError[];
};
}
// Seçenek B: Düz yanıt (daha basit, dahili API'ler için yaygın)
// Başarı: kaynağı doğrudan döndür
// Hata: hata nesnesini döndür
// HTTP durum koduyla ayırt et
```
## Sayfalama
### Offset-Tabanlı (Basit)
```
GET /api/v1/users?page=2&per_page=20
# Implementasyon
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 20;
```
**Artıları:** Uygulaması kolay, "N sayfasına git" destekler
**Eksileri:** Büyük offset'lerde yavaş (OFFSET 100000), eş zamanlı eklemelerde tutarsız
### Cursor-Tabanlı (Ölçeklenebilir)
```
GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20
# Implementasyon
SELECT * FROM users
WHERE id > :cursor_id
ORDER BY id ASC
LIMIT 21; -- has_next belirlemek için bir fazla getir
```
```json
{
"data": [...],
"meta": {
"has_next": true,
"next_cursor": "eyJpZCI6MTQzfQ"
}
}
```
**Artıları:** Pozisyondan bağımsız tutarlı performans, eş zamanlı eklemelerde kararlı
**Eksileri:** Rastgele sayfaya atlayamaz, cursor opak
### Hangisi Ne Zaman Kullanılmalı
| Kullanım Senaryosu | Sayfalama Tipi |
|----------|----------------|
| Admin panelleri, küçük veri setleri (<10K) | Offset |
| Sonsuz kaydırma, akışlar, büyük veri setleri | Cursor |
| Halka açık API'ler | Cursor (varsayılan) ile offset (opsiyonel) |
| Arama sonuçları | Offset (kullanıcılar sayfa numarası bekler) |
## Filtreleme, Sıralama ve Arama
### Filtreleme
```
# Basit eşitlik
GET /api/v1/orders?status=active&customer_id=abc-123
# Karşılaştırma operatörleri (köşeli parantez notasyonu kullanın)
GET /api/v1/products?price[gte]=10&price[lte]=100
GET /api/v1/orders?created_at[after]=2025-01-01
# Çoklu değerler (virgülle ayrılmış)
GET /api/v1/products?category=electronics,clothing
# İç içe alanlar (nokta notasyonu)
GET /api/v1/orders?customer.country=US
```
### Sıralama
```
# Tek alan (azalan için - öneki)
GET /api/v1/products?sort=-created_at
# Çoklu alanlar (virgülle ayrılmış)
GET /api/v1/products?sort=-featured,price,-created_at
```
### Tam Metin Arama
```
# Arama query parametresi
GET /api/v1/products?q=wireless+headphones
# Alana özel arama
GET /api/v1/users?email=alice
```
### Seyrek Fieldset'ler
```
# Sadece belirtilen alanları döndür (payload'ı azaltır)
GET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status&include=customer.name
```
## Kimlik Doğrulama ve Yetkilendirme
### Token-Tabanlı Auth
```
# Authorization header'da Bearer token
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# API key (sunucudan sunucuya)
GET /api/v1/data
X-API-Key: sk_live_abc123
```
### Yetkilendirme Kalıpları
```typescript
// Kaynak seviyesi: sahipliği kontrol et
app.get("/api/v1/orders/:id", async (req, res) => {
const order = await Order.findById(req.params.id);
if (!order) return res.status(404).json({ error: { code: "not_found" } });
if (order.userId !== req.user.id) return res.status(403).json({ error: { code: "forbidden" } });
return res.json({ data: order });
});
// Rol-tabanlı: yetkileri kontrol et
app.delete("/api/v1/users/:id", requireRole("admin"), async (req, res) => {
await User.delete(req.params.id);
return res.status(204).send();
});
```
## Hız Sınırlama
### Header'lar
```
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000
# Aşıldığında
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again in 60 seconds."
}
}
```
### Hız Limit Katmanları
| Katman | Limit | Pencere | Kullanım Senaryosu |
|------|-------|--------|----------|
| Anonim | 30/dk | IP Başına | Halka açık endpoint'ler |
| Kimlik Doğrulanmış | 100/dk | Kullanıcı Başına | Standart API erişimi |
| Premium | 1000/dk | API key Başına | Ücretli API planları |
| Dahili | 10000/dk | Servis Başına | Servisten servise |
## Versiyonlama
### URL Yolu Versiyonlama (Önerilen)
```
/api/v1/users
/api/v2/users
```
**Artıları:**ık, yönlendirmesi kolay, cache'lenebilir
**Eksileri:** Versiyonlar arası URL değişir
### Header Versiyonlama
```
GET /api/users
Accept: application/vnd.myapp.v2+json
```
**Artıları:** Temiz URL'ler
**Eksileri:** Test etmesi zor, unutulması kolay
### Versiyonlama Stratejisi
```
1. /api/v1/ ile başlayın — ihtiyaç duyana kadar versiyonlamayın
2. En fazla 2 aktif versiyon koruyun (mevcut + önceki)
3. Kullanımdan kaldırma zaman çizelgesi:
- Kullanımdan kaldırmayı duyurun (halka açık API'ler için 6 ay önceden)
- Sunset header ekleyin: Sunset: Sat, 01 Jan 2026 00:00:00 GMT
- Sunset tarihinden sonra 410 Gone döndürün
4. Breaking olmayan değişiklikler yeni versiyon gerektirmez:
- Yanıtlara yeni alanlar eklemek
- Yeni opsiyonel query parametreleri eklemek
- Yeni endpoint'ler eklemek
5. Breaking değişiklikler yeni versiyon gerektirir:
- Alanları kaldırmak veya yeniden adlandırmak
- Alan tiplerini değiştirmek
- URL yapısını değiştirmek
- Kimlik doğrulama metodunu değiştirmek
```
## Implementasyon Kalıpları
### TypeScript (Next.js API Route)
```typescript
import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
export async function POST(req: NextRequest) {
const body = await req.json();
const parsed = createUserSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({
error: {
code: "validation_error",
message: "Request validation failed",
details: parsed.error.issues.map(i => ({
field: i.path.join("."),
message: i.message,
code: i.code,
})),
},
}, { status: 422 });
}
const user = await createUser(parsed.data);
return NextResponse.json(
{ data: user },
{
status: 201,
headers: { Location: `/api/v1/users/${user.id}` },
},
);
}
```
### Python (Django REST Framework)
```python
from rest_framework import serializers, viewsets, status
from rest_framework.response import Response
class CreateUserSerializer(serializers.Serializer):
email = serializers.EmailField()
name = serializers.CharField(max_length=100)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "email", "name", "created_at"]
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if self.action == "create":
return CreateUserSerializer
return UserSerializer
def create(self, request):
serializer = CreateUserSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = UserService.create(**serializer.validated_data)
return Response(
{"data": UserSerializer(user).data},
status=status.HTTP_201_CREATED,
headers={"Location": f"/api/v1/users/{user.id}"},
)
```
### Go (net/http)
```go
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
return
}
if err := req.Validate(); err != nil {
writeError(w, http.StatusUnprocessableEntity, "validation_error", err.Error())
return
}
user, err := h.service.Create(r.Context(), req)
if err != nil {
switch {
case errors.Is(err, domain.ErrEmailTaken):
writeError(w, http.StatusConflict, "email_taken", "Email already registered")
default:
writeError(w, http.StatusInternalServerError, "internal_error", "Internal error")
}
return
}
w.Header().Set("Location", fmt.Sprintf("/api/v1/users/%s", user.ID))
writeJSON(w, http.StatusCreated, map[string]any{"data": user})
}
```
## API Tasarım Kontrol Listesi
Yeni bir endpoint yayınlamadan önce:
- [ ] Kaynak URL isimlendirme konvansiyonlarını takip ediyor (çoğul, kebab-case, fiil yok)
- [ ] Doğru HTTP metodu kullanılıyor (okumalar için GET, oluşturmalar için POST, vb.)
- [ ] Uygun durum kodları döndürülüyor (her şey için 200 değil)
- [ ] Girdi şema ile validasyona tabi tutuluyor (Zod, Pydantic, Bean Validation)
- [ ] Hata yanıtları kodlar ve mesajlarla standart formatı takip ediyor
- [ ] Liste endpoint'leri için sayfalama uygulanmış (cursor veya offset)
- [ ] Kimlik doğrulama gerekli (veya açıkça halka açık işaretlenmiş)
- [ ] Yetkilendirme kontrol ediliyor (kullanıcı sadece kendi kaynaklarına erişebilir)
- [ ] Hız sınırlama yapılandırılmış
- [ ] Yanıt dahili detayları sızdırmıyor (stack trace'ler, SQL hataları)
- [ ] Mevcut endpoint'lerle tutarlı isimlendirme (camelCase vs snake_case)
- [ ] Dokümante edilmiş (OpenAPI/Swagger spec güncellenmiş)

View File

@@ -0,0 +1,598 @@
---
name: backend-patterns
description: Node.js, Express ve Next.js API routes için backend mimari kalıpları, API tasarımı, veritabanı optimizasyonu ve sunucu tarafı en iyi uygulamalar.
origin: ECC
---
# Backend Geliştirme Kalıpları
Ölçeklenebilir sunucu tarafı uygulamalar için backend mimari kalıpları ve en iyi uygulamalar.
## Ne Zaman Aktifleştirmelisiniz
- REST veya GraphQL API endpoint'leri tasarlarken
- Repository, service veya controller katmanları uygularken
- Veritabanı sorgularını optimize ederken (N+1, indeksleme, bağlantı havuzu)
- Önbellekleme eklerken (Redis, in-memory, HTTP cache başlıkları)
- Arka plan işleri veya async işleme ayarlarken
- API'ler için hata yönetimi ve doğrulama yapılandırırken
- Middleware oluştururken (auth, logging, rate limiting)
## API Tasarım Kalıpları
### RESTful API Yapısı
```typescript
// ✅ Kaynak tabanlı URL'ler
GET /api/markets # Kaynakları listele
GET /api/markets/:id # Tek kaynak getir
POST /api/markets # Kaynak oluştur
PUT /api/markets/:id # Kaynağı değiştir (tam)
PATCH /api/markets/:id # Kaynağı güncelle (kısmi)
DELETE /api/markets/:id # Kaynağı sil
// ✅ Filtreleme, sıralama, sayfalama için query parametreleri
GET /api/markets?status=active&sort=volume&limit=20&offset=0
```
### Repository Kalıbı
```typescript
// Veri erişim mantığını soyutla
interface MarketRepository {
findAll(filters?: MarketFilters): Promise<Market[]>
findById(id: string): Promise<Market | null>
create(data: CreateMarketDto): Promise<Market>
update(id: string, data: UpdateMarketDto): Promise<Market>
delete(id: string): Promise<void>
}
class SupabaseMarketRepository implements MarketRepository {
async findAll(filters?: MarketFilters): Promise<Market[]> {
let query = supabase.from('markets').select('*')
if (filters?.status) {
query = query.eq('status', filters.status)
}
if (filters?.limit) {
query = query.limit(filters.limit)
}
const { data, error } = await query
if (error) throw new Error(error.message)
return data
}
// Diğer metodlar...
}
```
### Service Katmanı Kalıbı
```typescript
// İş mantığı veri erişiminden ayrılmış
class MarketService {
constructor(private marketRepo: MarketRepository) {}
async searchMarkets(query: string, limit: number = 10): Promise<Market[]> {
// İş mantığı
const embedding = await generateEmbedding(query)
const results = await this.vectorSearch(embedding, limit)
// Tam veriyi getir
const markets = await this.marketRepo.findByIds(results.map(r => r.id))
// Benzerliğe göre sırala
return markets.sort((a, b) => {
const scoreA = results.find(r => r.id === a.id)?.score || 0
const scoreB = results.find(r => r.id === b.id)?.score || 0
return scoreA - scoreB
})
}
private async vectorSearch(embedding: number[], limit: number) {
// Vector arama implementasyonu
}
}
```
### Middleware Kalıbı
```typescript
// Request/response işleme hattı
export function withAuth(handler: NextApiHandler): NextApiHandler {
return async (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) {
return res.status(401).json({ error: 'Unauthorized' })
}
try {
const user = await verifyToken(token)
req.user = user
return handler(req, res)
} catch (error) {
return res.status(401).json({ error: 'Invalid token' })
}
}
}
// Kullanım
export default withAuth(async (req, res) => {
// Handler req.user'a erişebilir
})
```
## Veritabanı Kalıpları
### Sorgu Optimizasyonu
```typescript
// ✅ İYİ: Sadece gerekli sütunları seç
const { data } = await supabase
.from('markets')
.select('id, name, status, volume')
.eq('status', 'active')
.order('volume', { ascending: false })
.limit(10)
// ❌ KÖTÜ: Her şeyi seç
const { data } = await supabase
.from('markets')
.select('*')
```
### N+1 Sorgu Önleme
```typescript
// ❌ KÖTÜ: N+1 sorgu problemi
const markets = await getMarkets()
for (const market of markets) {
market.creator = await getUser(market.creator_id) // N sorgu
}
// ✅ İYİ: Toplu getirme
const markets = await getMarkets()
const creatorIds = markets.map(m => m.creator_id)
const creators = await getUsers(creatorIds) // 1 sorgu
const creatorMap = new Map(creators.map(c => [c.id, c]))
markets.forEach(market => {
market.creator = creatorMap.get(market.creator_id)
})
```
### Transaction Kalıbı
```typescript
async function createMarketWithPosition(
marketData: CreateMarketDto,
positionData: CreatePositionDto
) {
// Supabase transaction kullan
const { data, error } = await supabase.rpc('create_market_with_position', {
market_data: marketData,
position_data: positionData
})
if (error) throw new Error('Transaction failed')
return data
}
// Supabase'de SQL fonksiyonu
CREATE OR REPLACE FUNCTION create_market_with_position(
market_data jsonb,
position_data jsonb
)
RETURNS jsonb
LANGUAGE plpgsql
AS $$
BEGIN
-- Transaction otomatik başlar
INSERT INTO markets VALUES (market_data);
INSERT INTO positions VALUES (position_data);
RETURN jsonb_build_object('success', true);
EXCEPTION
WHEN OTHERS THEN
-- Rollback otomatik olur
RETURN jsonb_build_object('success', false, 'error', SQLERRM);
END;
$$;
```
## Önbellekleme Stratejileri
### Redis Önbellekleme Katmanı
```typescript
class CachedMarketRepository implements MarketRepository {
constructor(
private baseRepo: MarketRepository,
private redis: RedisClient
) {}
async findById(id: string): Promise<Market | null> {
// Önce önbelleği kontrol et
const cached = await this.redis.get(`market:${id}`)
if (cached) {
return JSON.parse(cached)
}
// Cache miss - veritabanından getir
const market = await this.baseRepo.findById(id)
if (market) {
// 5 dakika önbellekle
await this.redis.setex(`market:${id}`, 300, JSON.stringify(market))
}
return market
}
async invalidateCache(id: string): Promise<void> {
await this.redis.del(`market:${id}`)
}
}
```
### Cache-Aside Kalıbı
```typescript
async function getMarketWithCache(id: string): Promise<Market> {
const cacheKey = `market:${id}`
// Önbelleği dene
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)
// Cache miss - DB'den getir
const market = await db.markets.findUnique({ where: { id } })
if (!market) throw new Error('Market not found')
// Önbelleği güncelle
await redis.setex(cacheKey, 300, JSON.stringify(market))
return market
}
```
## Hata Yönetimi Kalıpları
### Merkezi Hata Yöneticisi
```typescript
class ApiError extends Error {
constructor(
public statusCode: number,
public message: string,
public isOperational = true
) {
super(message)
Object.setPrototypeOf(this, ApiError.prototype)
}
}
export function errorHandler(error: unknown, req: Request): Response {
if (error instanceof ApiError) {
return NextResponse.json({
success: false,
error: error.message
}, { status: error.statusCode })
}
if (error instanceof z.ZodError) {
return NextResponse.json({
success: false,
error: 'Validation failed',
details: error.errors
}, { status: 400 })
}
// Beklenmeyen hataları logla
console.error('Unexpected error:', error)
return NextResponse.json({
success: false,
error: 'Internal server error'
}, { status: 500 })
}
// Kullanım
export async function GET(request: Request) {
try {
const data = await fetchData()
return NextResponse.json({ success: true, data })
} catch (error) {
return errorHandler(error, request)
}
}
```
### Exponential Backoff ile Tekrar Deneme
```typescript
async function fetchWithRetry<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
let lastError: Error
for (let i = 0; i < maxRetries; i++) {
try {
return await fn()
} catch (error) {
lastError = error as Error
if (i < maxRetries - 1) {
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, i) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
}
}
}
throw lastError!
}
// Kullanım
const data = await fetchWithRetry(() => fetchFromAPI())
```
## Kimlik Doğrulama ve Yetkilendirme
### JWT Token Doğrulama
```typescript
import jwt from 'jsonwebtoken'
interface JWTPayload {
userId: string
email: string
role: 'admin' | 'user'
}
export function verifyToken(token: string): JWTPayload {
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload
return payload
} catch (error) {
throw new ApiError(401, 'Invalid token')
}
}
export async function requireAuth(request: Request) {
const token = request.headers.get('authorization')?.replace('Bearer ', '')
if (!token) {
throw new ApiError(401, 'Missing authorization token')
}
return verifyToken(token)
}
// API route'unda kullanım
export async function GET(request: Request) {
const user = await requireAuth(request)
const data = await getDataForUser(user.userId)
return NextResponse.json({ success: true, data })
}
```
### Rol Tabanlı Erişim Kontrolü
```typescript
type Permission = 'read' | 'write' | 'delete' | 'admin'
interface User {
id: string
role: 'admin' | 'moderator' | 'user'
}
const rolePermissions: Record<User['role'], Permission[]> = {
admin: ['read', 'write', 'delete', 'admin'],
moderator: ['read', 'write', 'delete'],
user: ['read', 'write']
}
export function hasPermission(user: User, permission: Permission): boolean {
return rolePermissions[user.role].includes(permission)
}
export function requirePermission(permission: Permission) {
return (handler: (request: Request, user: User) => Promise<Response>) => {
return async (request: Request) => {
const user = await requireAuth(request)
if (!hasPermission(user, permission)) {
throw new ApiError(403, 'Insufficient permissions')
}
return handler(request, user)
}
}
}
// Kullanım - HOF handler'ı sarar
export const DELETE = requirePermission('delete')(
async (request: Request, user: User) => {
// Handler doğrulanmış yetki ile kullanıcı alır
return new Response('Deleted', { status: 200 })
}
)
```
## Rate Limiting
### Basit In-Memory Rate Limiter
```typescript
class RateLimiter {
private requests = new Map<string, number[]>()
async checkLimit(
identifier: string,
maxRequests: number,
windowMs: number
): Promise<boolean> {
const now = Date.now()
const requests = this.requests.get(identifier) || []
// Pencere dışındaki eski istekleri kaldır
const recentRequests = requests.filter(time => now - time < windowMs)
if (recentRequests.length >= maxRequests) {
return false // Rate limit aşıldı
}
// Mevcut isteği ekle
recentRequests.push(now)
this.requests.set(identifier, recentRequests)
return true
}
}
const limiter = new RateLimiter()
export async function GET(request: Request) {
const ip = request.headers.get('x-forwarded-for') || 'unknown'
const allowed = await limiter.checkLimit(ip, 100, 60000) // 100 req/dak
if (!allowed) {
return NextResponse.json({
error: 'Rate limit exceeded'
}, { status: 429 })
}
// İstekle devam et
}
```
## Arka Plan İşleri ve Kuyruklar
### Basit Kuyruk Kalıbı
```typescript
class JobQueue<T> {
private queue: T[] = []
private processing = false
async add(job: T): Promise<void> {
this.queue.push(job)
if (!this.processing) {
this.process()
}
}
private async process(): Promise<void> {
this.processing = true
while (this.queue.length > 0) {
const job = this.queue.shift()!
try {
await this.execute(job)
} catch (error) {
console.error('Job failed:', error)
}
}
this.processing = false
}
private async execute(job: T): Promise<void> {
// İş yürütme mantığı
}
}
// Market indeksleme için kullanım
interface IndexJob {
marketId: string
}
const indexQueue = new JobQueue<IndexJob>()
export async function POST(request: Request) {
const { marketId } = await request.json()
// Bloke etmek yerine kuyruğa ekle
await indexQueue.add({ marketId })
return NextResponse.json({ success: true, message: 'Job queued' })
}
```
## Loglama ve İzleme
### Yapılandırılmış Loglama
```typescript
interface LogContext {
userId?: string
requestId?: string
method?: string
path?: string
[key: string]: unknown
}
class Logger {
log(level: 'info' | 'warn' | 'error', message: string, context?: LogContext) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
...context
}
console.log(JSON.stringify(entry))
}
info(message: string, context?: LogContext) {
this.log('info', message, context)
}
warn(message: string, context?: LogContext) {
this.log('warn', message, context)
}
error(message: string, error: Error, context?: LogContext) {
this.log('error', message, {
...context,
error: error.message,
stack: error.stack
})
}
}
const logger = new Logger()
// Kullanım
export async function GET(request: Request) {
const requestId = crypto.randomUUID()
logger.info('Fetching markets', {
requestId,
method: 'GET',
path: '/api/markets'
})
try {
const markets = await fetchMarkets()
return NextResponse.json({ success: true, data: markets })
} catch (error) {
logger.error('Failed to fetch markets', error as Error, { requestId })
return NextResponse.json({ error: 'Internal error' }, { status: 500 })
}
}
```
**Unutmayın**: Backend kalıpları ölçeklenebilir, sürdürülebilir sunucu tarafı uygulamalar sağlar. Karmaşıklık seviyenize uyan kalıpları seçin.

View File

@@ -0,0 +1,530 @@
---
name: coding-standards
description: TypeScript, JavaScript, React ve Node.js geliştirme için evrensel kodlama standartları, en iyi uygulamalar ve kalıplar.
origin: ECC
---
# Kodlama Standartları ve En İyi Uygulamalar
Tüm projelerde uygulanabilir evrensel kodlama standartları.
## Ne Zaman Aktifleştirmelisiniz
- Yeni bir proje veya modül başlatırken
- Kod kalitesi ve sürdürülebilirlik için kod incelerken
- Mevcut kodu kurallara uygun hale getirmek için refactor ederken
- İsimlendirme, biçimlendirme veya yapısal tutarlılığı zorunlu kılarken
- Linting, biçimlendirme veya tür kontrolü kuralları ayarlarken
- Yeni katkıda bulunanları kodlama kurallarına alıştırırken
## Kod Kalitesi İlkeleri
### 1. Önce Okunabilirlik
- Kod yazılmaktan çok okunur
- Net değişken ve fonksiyon isimleri
- Yorumlardan çok kendi kendini belgeleyen kod tercih edilir
- Tutarlı biçimlendirme
### 2. KISS (Keep It Simple, Stupid - Basit Tut)
- Çalışan en basit çözüm
-ırı mühendislikten kaçının
- Erken optimizasyon yapmayın
- Anlaşılır kod > akıllıca kod
### 3. DRY (Don't Repeat Yourself - Kendini Tekrar Etme)
- Ortak mantığı fonksiyonlara çıkarın
- Yeniden kullanılabilir bileşenler oluşturun
- Yardımcı araçları modüller arasında paylaşın
- Kopyala-yapıştır programlamadan kaçının
### 4. YAGNI (You Aren't Gonna Need It - İhtiyacın Olmayacak)
- İhtiyaç duyulmadan özellikler oluşturmayın
- Spekülatif genellemeden kaçının
- Karmaşıklığı sadece gerektiğinde ekleyin
- Basit başlayın, gerektiğinde refactor edin
## TypeScript/JavaScript Standartları
### Değişken İsimlendirme
```typescript
// ✅ İYİ: Açıklayıcı isimler
const marketSearchQuery = 'election'
const isUserAuthenticated = true
const totalRevenue = 1000
// ❌ KÖTÜ: Belirsiz isimler
const q = 'election'
const flag = true
const x = 1000
```
### Fonksiyon İsimlendirme
```typescript
// ✅ İYİ: Fiil-isim kalıbı
async function fetchMarketData(marketId: string) { }
function calculateSimilarity(a: number[], b: number[]) { }
function isValidEmail(email: string): boolean { }
// ❌ KÖTÜ: Belirsiz veya sadece isim
async function market(id: string) { }
function similarity(a, b) { }
function email(e) { }
```
### Değişmezlik Kalıbı (KRİTİK)
```typescript
// ✅ HER ZAMAN spread operatörü kullanın
const updatedUser = {
...user,
name: 'New Name'
}
const updatedArray = [...items, newItem]
// ❌ ASLA doğrudan mutasyon yapmayın
user.name = 'New Name' // KÖTÜ
items.push(newItem) // KÖTÜ
```
### Hata Yönetimi
```typescript
// ✅ İYİ: Kapsamlı hata yönetimi
async function fetchData(url: string) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return await response.json()
} catch (error) {
console.error('Fetch failed:', error)
throw new Error('Failed to fetch data')
}
}
// ❌ KÖTÜ: Hata yönetimi yok
async function fetchData(url) {
const response = await fetch(url)
return response.json()
}
```
### Async/Await En İyi Uygulamaları
```typescript
// ✅ İYİ: Mümkün olduğunda paralel yürütme
const [users, markets, stats] = await Promise.all([
fetchUsers(),
fetchMarkets(),
fetchStats()
])
// ❌ KÖTÜ: Gereksiz yere sıralı
const users = await fetchUsers()
const markets = await fetchMarkets()
const stats = await fetchStats()
```
### Tür Güvenliği
```typescript
// ✅ İYİ: Doğru tipler
interface Market {
id: string
name: string
status: 'active' | 'resolved' | 'closed'
created_at: Date
}
function getMarket(id: string): Promise<Market> {
// Implementation
}
// ❌ KÖTÜ: 'any' kullanımı
function getMarket(id: any): Promise<any> {
// Implementation
}
```
## React En İyi Uygulamaları
### Bileşen Yapısı
```typescript
// ✅ İYİ: Tiplerle fonksiyonel bileşen
interface ButtonProps {
children: React.ReactNode
onClick: () => void
disabled?: boolean
variant?: 'primary' | 'secondary'
}
export function Button({
children,
onClick,
disabled = false,
variant = 'primary'
}: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant}`}
>
{children}
</button>
)
}
// ❌ KÖTÜ: Tip yok, belirsiz yapı
export function Button(props) {
return <button onClick={props.onClick}>{props.children}</button>
}
```
### Özel Hook'lar
```typescript
// ✅ İYİ: Yeniden kullanılabilir özel hook
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => clearTimeout(handler)
}, [value, delay])
return debouncedValue
}
// Kullanım
const debouncedQuery = useDebounce(searchQuery, 500)
```
### State Yönetimi
```typescript
// ✅ İYİ: Doğru state güncellemeleri
const [count, setCount] = useState(0)
// Önceki state'e dayalı fonksiyonel güncelleme
setCount(prev => prev + 1)
// ❌ KÖTÜ: Doğrudan state referansı
setCount(count + 1) // Async senaryolarda eski olabilir
```
### Koşullu Render
```typescript
// ✅ İYİ: Açık koşullu render
{isLoading && <Spinner />}
{error && <ErrorMessage error={error} />}
{data && <DataDisplay data={data} />}
// ❌ KÖTÜ: Ternary cehennemi
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
```
## API Tasarım Standartları
### REST API Kuralları
```
GET /api/markets # Tüm marketleri listele
GET /api/markets/:id # Belirli marketi getir
POST /api/markets # Yeni market oluştur
PUT /api/markets/:id # Marketi güncelle (tam)
PATCH /api/markets/:id # Marketi güncelle (kısmi)
DELETE /api/markets/:id # Marketi sil
# Filtreleme için query parametreleri
GET /api/markets?status=active&limit=10&offset=0
```
### Response Formatı
```typescript
// ✅ İYİ: Tutarlı response yapısı
interface ApiResponse<T> {
success: boolean
data?: T
error?: string
meta?: {
total: number
page: number
limit: number
}
}
// Başarılı response
return NextResponse.json({
success: true,
data: markets,
meta: { total: 100, page: 1, limit: 10 }
})
// Hata response
return NextResponse.json({
success: false,
error: 'Invalid request'
}, { status: 400 })
```
### Input Doğrulama
```typescript
import { z } from 'zod'
// ✅ İYİ: Schema doğrulama
const CreateMarketSchema = z.object({
name: z.string().min(1).max(200),
description: z.string().min(1).max(2000),
endDate: z.string().datetime(),
categories: z.array(z.string()).min(1)
})
export async function POST(request: Request) {
const body = await request.json()
try {
const validated = CreateMarketSchema.parse(body)
// Doğrulanmış veriyle devam et
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({
success: false,
error: 'Validation failed',
details: error.errors
}, { status: 400 })
}
}
}
```
## Dosya Organizasyonu
### Proje Yapısı
```
src/
├── app/ # Next.js App Router
│ ├── api/ # API routes
│ ├── markets/ # Market sayfaları
│ └── (auth)/ # Auth sayfaları (route groups)
├── components/ # React bileşenleri
│ ├── ui/ # Genel UI bileşenleri
│ ├── forms/ # Form bileşenleri
│ └── layouts/ # Layout bileşenleri
├── hooks/ # Özel React hooks
├── lib/ # Yardımcı araçlar ve konfigürasyonlar
│ ├── api/ # API istemcileri
│ ├── utils/ # Yardımcı fonksiyonlar
│ └── constants/ # Sabitler
├── types/ # TypeScript tipleri
└── styles/ # Global stiller
```
### Dosya İsimlendirme
```
components/Button.tsx # Bileşenler için PascalCase
hooks/useAuth.ts # 'use' öneki ile camelCase
lib/formatDate.ts # Yardımcı araçlar için camelCase
types/market.types.ts # .types soneki ile camelCase
```
## Yorumlar ve Dokümantasyon
### Ne Zaman Yorum Yapmalı
```typescript
// ✅ İYİ: NİÇİN'i açıklayın, NE'yi değil
// Kesintiler sırasında API'yi aşırı yüklemekten kaçınmak için exponential backoff kullan
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
// Büyük dizilerle performans için burada kasıtlı olarak mutasyon kullanılıyor
items.push(newItem)
// ❌ KÖTÜ: Açık olanı belirtmek
// Sayacı 1 artır
count++
// İsmi kullanıcının ismine ayarla
name = user.name
```
### Public API'ler için JSDoc
```typescript
/**
* Semantik benzerlik kullanarak market arar.
*
* @param query - Doğal dil arama sorgusu
* @param limit - Maksimum sonuç sayısı (varsayılan: 10)
* @returns Benzerlik skoruna göre sıralanmış market dizisi
* @throws {Error} OpenAI API başarısız olursa veya Redis kullanılamazsa
*
* @example
* ```typescript
* const results = await searchMarkets('election', 5)
* console.log(results[0].name) // "Trump vs Biden"
* ```
*/
export async function searchMarkets(
query: string,
limit: number = 10
): Promise<Market[]> {
// Implementation
}
```
## Performans En İyi Uygulamaları
### Memoization
```typescript
import { useMemo, useCallback } from 'react'
// ✅ İYİ: Pahalı hesaplamaları memoize et
const sortedMarkets = useMemo(() => {
return markets.sort((a, b) => b.volume - a.volume)
}, [markets])
// ✅ İYİ: Callback'leri memoize et
const handleSearch = useCallback((query: string) => {
setSearchQuery(query)
}, [])
```
### Lazy Loading
```typescript
import { lazy, Suspense } from 'react'
// ✅ İYİ: Ağır bileşenleri lazy yükle
const HeavyChart = lazy(() => import('./HeavyChart'))
export function Dashboard() {
return (
<Suspense fallback={<Spinner />}>
<HeavyChart />
</Suspense>
)
}
```
### Veritabanı Sorguları
```typescript
// ✅ İYİ: Sadece gerekli sütunları seç
const { data } = await supabase
.from('markets')
.select('id, name, status')
.limit(10)
// ❌ KÖTÜ: Her şeyi seç
const { data } = await supabase
.from('markets')
.select('*')
```
## Test Standartları
### Test Yapısı (AAA Kalıbı)
```typescript
test('benzerliği doğru hesaplar', () => {
// Arrange (Hazırla)
const vector1 = [1, 0, 0]
const vector2 = [0, 1, 0]
// Act (İşle)
const similarity = calculateCosineSimilarity(vector1, vector2)
// Assert (Doğrula)
expect(similarity).toBe(0)
})
```
### Test İsimlendirme
```typescript
// ✅ İYİ: Açıklayıcı test isimleri
test('sorguya uygun market bulunamadığında boş dizi döndürür', () => { })
test('OpenAI API anahtarı eksikse hata fırlatır', () => { })
test('Redis kullanılamazsa substring aramaya geri döner', () => { })
// ❌ KÖTÜ: Belirsiz test isimleri
test('çalışır', () => { })
test('arama testi', () => { })
```
## Kod Kokusu Tespiti
Bu anti-kalıplara dikkat edin:
### 1. Uzun Fonksiyonlar
```typescript
// ❌ KÖTÜ: 50 satırdan uzun fonksiyon
function processMarketData() {
// 100 satır kod
}
// ✅ İYİ: Küçük fonksiyonlara böl
function processMarketData() {
const validated = validateData()
const transformed = transformData(validated)
return saveData(transformed)
}
```
### 2. Derin İç İçe Geçme
```typescript
// ❌ KÖTÜ: 5+ seviye iç içe geçme
if (user) {
if (user.isAdmin) {
if (market) {
if (market.isActive) {
if (hasPermission) {
// Bir şeyler yap
}
}
}
}
}
// ✅ İYİ: Erken dönüşler
if (!user) return
if (!user.isAdmin) return
if (!market) return
if (!market.isActive) return
if (!hasPermission) return
// Bir şeyler yap
```
### 3. Sihirli Sayılar
```typescript
// ❌ KÖTÜ: Açıklanmamış sayılar
if (retryCount > 3) { }
setTimeout(callback, 500)
// ✅ İYİ: İsimlendirilmiş sabitler
const MAX_RETRIES = 3
const DEBOUNCE_DELAY_MS = 500
if (retryCount > MAX_RETRIES) { }
setTimeout(callback, DEBOUNCE_DELAY_MS)
```
**Unutmayın**: Kod kalitesi pazarlık konusu değildir. Açık, sürdürülebilir kod hızlı geliştirme ve güvenli refactoring sağlar.

View File

@@ -0,0 +1,364 @@
---
name: continuous-learning-v2
description: Hook'lar aracılığıyla oturumları gözlemleyen, güven skorlaması ile atomik instinct'ler oluşturan ve bunları skill/command/agent'lara evriltiren instinct tabanlı öğrenme sistemi. v2.1 çapraz proje kontaminasyonunu önlemek için proje kapsamlı instinct'ler ekler.
origin: ECC
version: 2.1.0
---
# Sürekli Öğrenme v2.1 - Instinct Tabanlı Mimari
Claude Code oturumlarınızı güven skorlaması ile atomik "instinct'ler" - küçük öğrenilmiş davranışlar - aracılığıyla yeniden kullanılabilir bilgiye dönüştüren gelişmiş bir öğrenme sistemi.
**v2.1** **proje kapsamlı instinct'ler** ekler — React kalıpları React projenizde kalır, Python kuralları Python projenizde kalır ve evrensel kalıplar (örneğin "her zaman input'u doğrula") global olarak paylaşılır.
## Ne Zaman Aktifleştirmelisiniz
- Claude Code oturumlarından otomatik öğrenme ayarlarken
- Hook'lar aracılığıyla instinct tabanlı davranış çıkarmayı yapılandırırken
- Öğrenilmiş davranışlar için güven eşiklerini ayarlarken
- Instinct kütüphanelerini incelerken, dışa veya içe aktarırken
- Instinct'leri tam skill'lere, command'lara veya agent'lara evriltirken
- Proje kapsamlı vs global instinct'leri yönetirken
- Instinct'leri projeden global kapsamına yükseltirken
## v2.1'deki Yenilikler
| Özellik | v2.0 | v2.1 |
|---------|------|------|
| Depolama | Global (~/.claude/homunculus/) | Proje kapsamlı (projects/<hash>/) |
| Kapsam | Tüm instinct'ler her yerde geçerli | Proje kapsamlı + global |
| Tespit | Yok | git remote URL / repo path |
| Yükseltme | Yok | Proje → 2+ projede görülünce global |
| Komutlar | 4 (status/evolve/export/import) | 6 (+promote/projects) |
| Çapraz proje | Kontaminasyon riski | Varsayılan olarak izole |
## v2'deki Yenilikler (vs v1)
| Özellik | v1 | v2 |
|---------|----|----|
| Gözlem | Stop hook (oturum sonu) | PreToolUse/PostToolUse (%100 güvenilir) |
| Analiz | Ana bağlam | Arka plan agent'ı (Haiku) |
| Granülerlik | Tam skill'ler | Atomik "instinct'ler" |
| Güven | Yok | 0.3-0.9 ağırlıklı |
| Evrim | Doğrudan skill'e | Instinct'ler -> kümeleme -> skill/command/agent |
| Paylaşım | Yok | Instinct'leri dışa/içe aktar |
## Instinct Modeli
Instinct küçük öğrenilmiş bir davranıştır:
```yaml
---
id: prefer-functional-style
trigger: "yeni fonksiyonlar yazarken"
confidence: 0.7
domain: "code-style"
source: "session-observation"
scope: project
project_id: "a1b2c3d4e5f6"
project_name: "my-react-app"
---
# Fonksiyonel Stili Tercih Et
## Aksiyon
Uygun olduğunda sınıflar yerine fonksiyonel kalıpları kullan.
## Kanıt
- 5 fonksiyonel kalıp tercihinin gözlemlenmesi
- Kullanıcı 2025-01-15'te sınıf tabanlı yaklaşımı fonksiyonele düzeltti
```
**Özellikler:**
- **Atomik** -- bir tetikleyici, bir aksiyon
- **Güven ağırlıklı** -- 0.3 = geçici, 0.9 = neredeyse kesin
- **Alan etiketli** -- code-style, testing, git, debugging, workflow, vb.
- **Kanıt destekli** -- hangi gözlemlerin oluşturduğunu takip eder
- **Kapsam farkında** -- `project` (varsayılan) veya `global`
## Nasıl Çalışır
```
Oturum Aktivitesi (bir git repo'sunda)
|
| Hook'lar prompt'ları + tool kullanımını yakalar (%100 güvenilir)
| + proje bağlamını tespit eder (git remote / repo path)
v
+---------------------------------------------+
| projects/<project-hash>/observations.jsonl |
| (prompt'lar, tool çağrıları, sonuçlar, proje) |
+---------------------------------------------+
|
| Gözlemci agent okur (arka plan, Haiku)
v
+---------------------------------------------+
| KALIP TESPİTİ |
| * Kullanıcı düzeltmeleri -> instinct |
| * Hata çözümleri -> instinct |
| * Tekrarlanan iş akışları -> instinct |
| * Kapsam kararı: project mi global mi? |
+---------------------------------------------+
|
| Oluşturur/günceller
v
+---------------------------------------------+
| projects/<project-hash>/instincts/personal/ |
| * prefer-functional.yaml (0.7) [project] |
| * use-react-hooks.yaml (0.9) [project] |
+---------------------------------------------+
| instincts/personal/ (GLOBAL) |
| * always-validate-input.yaml (0.85) [global]|
| * grep-before-edit.yaml (0.6) [global] |
+---------------------------------------------+
|
| /evolve kümeleme + /promote
v
+---------------------------------------------+
| projects/<hash>/evolved/ (proje kapsamlı) |
| evolved/ (global) |
| * commands/new-feature.md |
| * skills/testing-workflow.md |
| * agents/refactor-specialist.md |
+---------------------------------------------+
```
## Proje Tespiti
Sistem mevcut projenizi otomatik olarak tespit eder:
1. **`CLAUDE_PROJECT_DIR` env var** (en yüksek öncelik)
2. **`git remote get-url origin`** -- taşınabilir proje ID'si oluşturmak için hash'lenir (farklı makinelerde aynı repo aynı ID'yi alır)
3. **`git rev-parse --show-toplevel`** -- repo path kullanan yedek (makineye özgü)
4. **Global yedek** -- proje tespit edilemezse, instinct'ler global kapsamına gider
Her proje 12 karakterlik bir hash ID alır (örn. `a1b2c3d4e5f6`). `~/.claude/homunculus/projects.json` dosyasındaki kayıt dosyası ID'leri insanların okuyabileceği isimlerle eşler.
## Hızlı Başlangıç
### 1. Gözlem Hook'larını Aktifleştirin
`~/.claude/settings.json` dosyanıza ekleyin.
**Plugin olarak kuruluysa** (önerilen):
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/hooks/observe.sh"
}]
}],
"PostToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/hooks/observe.sh"
}]
}]
}
}
```
**`~/.claude/skills` dizinine manuel kuruluysa**:
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh"
}]
}],
"PostToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh"
}]
}]
}
}
```
### 2. Dizin Yapısını Başlatın
Sistem ilk kullanımda dizinleri otomatik oluşturur, ancak manuel olarak da oluşturabilirsiniz:
```bash
# Global dizinler
mkdir -p ~/.claude/homunculus/{instincts/{personal,inherited},evolved/{agents,skills,commands},projects}
# Proje dizinleri hook bir git repo'sunda ilk çalıştığında otomatik oluşturulur
```
### 3. Instinct Komutlarını Kullanın
```bash
/instinct-status # Öğrenilmiş instinct'leri göster (proje + global)
/evolve # İlgili instinct'leri skill/command'lara kümele
/instinct-export # Instinct'leri dosyaya aktar
/instinct-import # Başkalarından instinct'leri içe aktar
/promote # Proje instinct'lerini global kapsamına yükselt
/projects # Tüm bilinen projeleri ve instinct sayılarını listele
```
## Komutlar
| Komut | Açıklama |
|---------|-------------|
| `/instinct-status` | Tüm instinct'leri göster (proje kapsamlı + global) güvenle |
| `/evolve` | İlgili instinct'leri skill/command'lara kümele, yükseltme öner |
| `/instinct-export` | Instinct'leri dışa aktar (kapsam/alana göre filtrelenebilir) |
| `/instinct-import <file>` | Kapsam kontrolü ile instinct'leri içe aktar |
| `/promote [id]` | Proje instinct'lerini global kapsamına yükselt |
| `/projects` | Tüm bilinen projeleri ve instinct sayılarını listele |
## Konfigürasyon
Arka plan gözlemcisini kontrol etmek için `config.json` dosyasını düzenleyin:
```json
{
"version": "2.1",
"observer": {
"enabled": false,
"run_interval_minutes": 5,
"min_observations_to_analyze": 20
}
}
```
| Anahtar | Varsayılan | Açıklama |
|-----|---------|-------------|
| `observer.enabled` | `false` | Arka plan gözlemci agent'ını aktifleştir |
| `observer.run_interval_minutes` | `5` | Gözlemcinin gözlemleri ne sıklıkla analiz ettiği |
| `observer.min_observations_to_analyze` | `20` | Analiz çalışmadan önce minimum gözlem |
Diğer davranışlar (gözlem yakalama, instinct eşikleri, proje kapsamı, yükseltme kriterleri) `instinct-cli.py` ve `observe.sh` içindeki kod varsayılanları aracılığıyla yapılandırılır.
## Dosya Yapısı
```
~/.claude/homunculus/
+-- identity.json # Profiliniz, teknik seviye
+-- projects.json # Kayıt: proje hash -> isim/path/remote
+-- observations.jsonl # Global gözlemler (yedek)
+-- instincts/
| +-- personal/ # Global otomatik öğrenilmiş instinct'ler
| +-- inherited/ # Global içe aktarılan instinct'ler
+-- evolved/
| +-- agents/ # Global oluşturulan agent'lar
| +-- skills/ # Global oluşturulan skill'ler
| +-- commands/ # Global oluşturulan komutlar
+-- projects/
+-- a1b2c3d4e5f6/ # Proje hash (git remote URL'den)
| +-- project.json # Proje başına metadata yansıması (id/name/root/remote)
| +-- observations.jsonl
| +-- observations.archive/
| +-- instincts/
| | +-- personal/ # Projeye özgü otomatik öğrenilmiş
| | +-- inherited/ # Projeye özgü içe aktarılan
| +-- evolved/
| +-- skills/
| +-- commands/
| +-- agents/
+-- f6e5d4c3b2a1/ # Başka bir proje
+-- ...
```
## Kapsam Karar Kılavuzu
| Kalıp Tipi | Kapsam | Örnekler |
|-------------|-------|---------|
| Dil/framework kuralları | **project** | "React hook'ları kullan", "Django REST kalıplarını takip et" |
| Dosya yapısı tercihleri | **project** | "Testler `__tests__`/ içinde", "Bileşenler src/components/ içinde" |
| Kod stili | **project** | "Fonksiyonel stil kullan", "Dataclass'ları tercih et" |
| Hata işleme stratejileri | **project** | "Hatalar için Result tipi kullan" |
| Güvenlik uygulamaları | **global** | "Kullanıcı input'unu doğrula", "SQL'i sanitize et" |
| Genel en iyi uygulamalar | **global** | "Önce testleri yaz", "Her zaman hataları işle" |
| Tool iş akışı tercihleri | **global** | "Edit'ten önce Grep", "Write'tan önce Read" |
| Git uygulamaları | **global** | "Conventional commit'ler", "Küçük odaklı commit'ler" |
## Instinct Yükseltme (Project -> Global)
Aynı instinct birden fazla projede yüksek güvenle göründüğünde, global kapsamına yükseltme adayıdır.
**Otomatik yükseltme kriterleri:**
- 2+ projede aynı instinct ID
- Ortalama güven >= 0.8
**Nasıl yükseltilir:**
```bash
# Belirli bir instinct'i yükselt
python3 instinct-cli.py promote prefer-explicit-errors
# Tüm uygun instinct'leri otomatik yükselt
python3 instinct-cli.py promote
# Değişiklik yapmadan önizle
python3 instinct-cli.py promote --dry-run
```
`/evolve` komutu ayrıca yükseltme adaylarını önerir.
## Güven Skorlaması
Güven zamanla evrimleşir:
| Skor | Anlamı | Davranış |
|-------|---------|----------|
| 0.3 | Geçici | Önerilir ama zorunlu değil |
| 0.5 | Orta | İlgili olduğunda uygulanır |
| 0.7 | Güçlü | Uygulama için otomatik onaylanır |
| 0.9 | Neredeyse kesin | Temel davranış |
**Güven artar** şu durumlarda:
- Kalıp tekrar tekrar gözlemlenir
- Kullanıcı önerilen davranışı düzeltmez
- Diğer kaynaklardan benzer instinct'ler hemfikirdir
**Güven azalır** şu durumlarda:
- Kullanıcı davranışııkça düzeltir
- Kalıp uzun süre gözlemlenmez
- Çelişkili kanıt ortaya çıkar
## Neden Gözlem için Skill'ler Yerine Hook'lar?
> "v1 gözlem için skill'lere güveniyordu. Skill'ler olasılıksaldır -- Claude'un yargısına göre zamanın ~%50-80'inde tetiklenirler."
Hook'lar **%100** deterministik olarak tetiklenir. Bu şu anlama gelir:
- Her tool çağrısı gözlemlenir
- Hiçbir kalıp kaçırılmaz
- Öğrenme kapsamlıdır
## Geriye Dönük Uyumluluk
v2.1, v2.0 ve v1 ile tamamen uyumludur:
- `~/.claude/homunculus/instincts/` içindeki mevcut global instinct'ler hala global instinct olarak çalışır
- v1'den `~/.claude/skills/learned/` skill'leri hala çalışır
- Stop hook hala çalışır (ama şimdi v2'ye de beslenir)
- Kademeli geçiş: her ikisini de paralel çalıştırın
## Gizlilik
- Gözlemler makinenizde **yerel** kalır
- Proje kapsamlı instinct'ler proje başına izoledir
- Sadece **instinct'ler** (kalıplar) dışa aktarılabilir — ham gözlemler değil
- Gerçek kod veya konuşma içeriği paylaşılmaz
- Neyin dışa aktarılacağını ve yükseltileceğini siz kontrol edersiniz
## İlgili
- [ECC-Tools GitHub App](https://github.com/apps/ecc-tools) - Repo geçmişinden instinct'ler oluştur
- Homunculus - v2 instinct tabanlı mimariye ilham veren topluluk projesi (atomik gözlemler, güven skorlaması, instinct evrim hattı)
- [The Longform Guide](https://x.com/affaanmustafa/status/2014040193557471352) - Sürekli öğrenme bölümü
---
*Instinct tabanlı öğrenme: Claude'a kalıplarınızı öğretmek, her seferinde bir proje.*

View File

@@ -0,0 +1,119 @@
---
name: continuous-learning
description: Claude Code oturumlarından yeniden kullanılabilir kalıpları otomatik olarak çıkarın ve gelecekte kullanmak üzere öğrenilmiş skill'ler olarak kaydedin.
origin: ECC
---
# Sürekli Öğrenme Skill'i
Claude Code oturumlarını sonunda otomatik olarak değerlendirir ve öğrenilmiş skill'ler olarak kaydedilebilecek yeniden kullanılabilir kalıpları çıkarır.
## Ne Zaman Aktifleştirmelisiniz
- Claude Code oturumlarından otomatik kalıp çıkarma ayarlarken
- Oturum değerlendirmesi için Stop hook'u yapılandırırken
- `~/.claude/skills/learned/` içindeki öğrenilmiş skill'leri incelerken veya düzenlerken
- Çıkarma eşiklerini veya kalıp kategorilerini ayarlarken
- v1 (bu) ile v2 (instinct tabanlı) yaklaşımlarını karşılaştırırken
## Nasıl Çalışır
Bu skill her oturumun sonunda **Stop hook** olarak çalışır:
1. **Oturum Değerlendirmesi**: Oturumun yeterli mesaja sahip olup olmadığını kontrol eder (varsayılan: 10+)
2. **Kalıp Tespiti**: Oturumdan çıkarılabilir kalıpları tanımlar
3. **Skill Çıkarma**: Yararlı kalıpları `~/.claude/skills/learned/` dizinine kaydeder
## Konfigürasyon
Özelleştirmek için `config.json` dosyasını düzenleyin:
```json
{
"min_session_length": 10,
"extraction_threshold": "medium",
"auto_approve": false,
"learned_skills_path": "~/.claude/skills/learned/",
"patterns_to_detect": [
"error_resolution",
"user_corrections",
"workarounds",
"debugging_techniques",
"project_specific"
],
"ignore_patterns": [
"simple_typos",
"one_time_fixes",
"external_api_issues"
]
}
```
## Kalıp Tipleri
| Kalıp | Açıklama |
|---------|-------------|
| `error_resolution` | Belirli hataların nasıl çözüldüğü |
| `user_corrections` | Kullanıcı düzeltmelerinden kalıplar |
| `workarounds` | Framework/kütüphane tuhaflıklarına çözümler |
| `debugging_techniques` | Etkili hata ayıklama yaklaşımları |
| `project_specific` | Projeye özgü kurallar |
## Hook Kurulumu
`~/.claude/settings.json` dosyanıza ekleyin:
```json
{
"hooks": {
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/continuous-learning/evaluate-session.sh"
}]
}]
}
}
```
## Neden Stop Hook?
- **Hafif**: Oturum sonunda bir kez çalışır
- **Bloke Etmeyen**: Her mesaja gecikme eklemez
- **Tam Bağlam**: Tam oturum kaydına erişimi vardır
## İlgili
- [The Longform Guide](https://x.com/affaanmustafa/status/2014040193557471352) - Sürekli öğrenme bölümü
- `/learn` komutu - Oturum ortasında manuel kalıp çıkarma
---
## Karşılaştırma Notları (Araştırma: Ocak 2025)
### vs Homunculus
Homunculus v2 daha sofistike bir yaklaşım benimsiyor:
| Özellik | Bizim Yaklaşım | Homunculus v2 |
|---------|--------------|---------------|
| Gözlem | Stop hook (oturum sonu) | PreToolUse/PostToolUse hooks (%100 güvenilir) |
| Analiz | Ana bağlam | Arka plan agent'ı (Haiku) |
| Granülerlik | Tam skill'ler | Atomik "instinct'ler" |
| Güven | Yok | 0.3-0.9 ağırlıklı |
| Evrim | Doğrudan skill'e | Instinct'ler → kümeleme → skill/command/agent |
| Paylaşım | Yok | Instinct'leri dışa/içe aktar |
**Homunculus'tan temel içgörü:**
> "v1 gözlem için skill'lere güveniyordu. Skill'ler olasılıksaldır—zamanın ~%50-80'inde tetiklenirler. v2 gözlem için hook'ları kullanır (%100 güvenilir) ve öğrenilmiş davranışın atomik birimi olarak instinct'leri kullanır."
### Potansiyel v2 İyileştirmeleri
1. **Instinct tabanlı öğrenme** - Güven skorlaması ile daha küçük, atomik davranışlar
2. **Arka plan gözlemcisi** - Paralel analiz yapan Haiku agent'ı
3. **Güven azalması** - Çelişkiye uğrarsa instinct'ler güven kaybeder
4. **Alan etiketleme** - code-style, testing, git, debugging, vb.
5. **Evrim yolu** - İlgili instinct'leri skill/command'lara kümeleme
Bkz: Tam spec için `docs/continuous-learning-v2-spec.md`.

View File

@@ -0,0 +1,319 @@
---
name: database-migrations
description: Şema değişiklikleri, veri migration'ları, rollback'ler ve PostgreSQL, MySQL ve yaygın ORM'ler (Prisma, Drizzle, Django, TypeORM, golang-migrate) arasında sıfır kesinti deployment'ları için veritabanı migration en iyi uygulamaları.
origin: ECC
---
# Veritabanı Migration Kalıpları
Üretim sistemleri için güvenli, geri alınabilir veritabanı şema değişiklikleri.
## Ne Zaman Aktifleştirmeli
- Veritabanı tabloları oluştururken veya değiştirirken
- Sütun veya indeks eklerken/kaldırırken
- Veri migration'ları çalıştırırken (backfill, dönüştürme)
- Sıfır kesinti şema değişiklikleri planlarken
- Yeni bir proje için migration araçları kurarken
## Temel İlkeler
1. **Her değişiklik bir migration'dır** — üretim veritabanlarını asla manuel olarak değiştirmeyin
2. **Migration'lar üretimde sadece ileri** — rollback'ler yeni forward migration'lar kullanır
3. **Şema ve veri migration'ları ayrıdır** — tek migration'da DDL ve DML'yi asla karıştırmayın
4. **Migration'ları üretim boyutundaki veriye karşı test edin** — 100 satırda çalışan migration 10M'de kilitlenebilir
5. **Migration'lar üretimde çalıştıktan sonra değişmezdir** — üretimde çalışan migration'ı asla düzenlemeyin
## Migration Güvenlik Kontrol Listesi
Herhangi bir migration uygulamadan önce:
- [ ] Migration UP ve DOWN'a sahip (veya açıkça geri alınamaz olarak işaretlenmiş)
- [ ] Büyük tablolarda tam tablo kilitleri yok (concurrent operasyonlar kullan)
- [ ] Yeni sütunlar varsayılanlara sahip veya nullable (varsayılan olmadan NOT NULL asla ekleme)
- [ ] İndeksler concurrent oluşturuluyor (mevcut tablolar için CREATE TABLE ile inline değil)
- [ ] Veri backfill şema değişikliğinden ayrı bir migration
- [ ] Üretim verisinin kopyasına karşı test edilmiş
- [ ] Rollback planı dokümante edilmiş
## PostgreSQL Kalıpları
### Güvenli Sütun Ekleme
```sql
-- İYİ: Nullable sütun, kilit yok
ALTER TABLE users ADD COLUMN avatar_url TEXT;
-- İYİ: Varsayılanlı sütun (Postgres 11+ anlık, yeniden yazma yok)
ALTER TABLE users ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT true;
-- KÖTÜ: Mevcut tabloda varsayılansız NOT NULL (tam yeniden yazma gerektirir)
ALTER TABLE users ADD COLUMN role TEXT NOT NULL;
-- Bu tabloyu kilitler ve her satırı yeniden yazar
```
### Kesinti Olmadan İndeks Ekleme
```sql
-- KÖTÜ: Büyük tablolarda yazmaları engeller
CREATE INDEX idx_users_email ON users (email);
-- İYİ: Engellemez, concurrent yazmalara izin verir
CREATE INDEX CONCURRENTLY idx_users_email ON users (email);
-- Not: CONCURRENTLY transaction bloğu içinde çalıştırılamaz
-- Çoğu migration aracı bunun için özel işleme ihtiyaç duyar
```
### Sütun Yeniden Adlandırma (Sıfır Kesinti)
Üretimde asla doğrudan yeniden adlandırmayın. Expand-contract kalıbını kullanın:
```sql
-- Adım 1: Yeni sütun ekle (migration 001)
ALTER TABLE users ADD COLUMN display_name TEXT;
-- Adım 2: Veriyi backfill et (migration 002, veri migration'ı)
UPDATE users SET display_name = username WHERE display_name IS NULL;
-- Adım 3: Uygulama kodunu her iki sütunu okuma/yazma için güncelle
-- Uygulama değişikliklerini deploy et
-- Adım 4: Eski sütuna yazmayı durdur, kaldır (migration 003)
ALTER TABLE users DROP COLUMN username;
```
### Güvenli Sütun Kaldırma
```sql
-- Adım 1: Sütuna tüm uygulama referanslarını kaldır
-- Adım 2: Sütun referansı olmadan uygulamayı deploy et
-- Adım 3: Sonraki migration'da sütunu kaldır
ALTER TABLE orders DROP COLUMN legacy_status;
-- Django için: SeparateDatabaseAndState kullanarak modelden kaldır
-- DROP COLUMN oluşturmadan (sonra sonraki migration'da kaldır)
```
### Büyük Veri Migration'ları
```sql
-- KÖTÜ: Tüm satırları tek transaction'da günceller (tabloyu kilitler)
UPDATE users SET normalized_email = LOWER(email);
-- İYİ: İlerleme ile batch güncelleme
DO $$
DECLARE
batch_size INT := 10000;
rows_updated INT;
BEGIN
LOOP
UPDATE users
SET normalized_email = LOWER(email)
WHERE id IN (
SELECT id FROM users
WHERE normalized_email IS NULL
LIMIT batch_size
FOR UPDATE SKIP LOCKED
);
GET DIAGNOSTICS rows_updated = ROW_COUNT;
RAISE NOTICE 'Updated % rows', rows_updated;
EXIT WHEN rows_updated = 0;
COMMIT;
END LOOP;
END $$;
```
## Prisma (TypeScript/Node.js)
### İş Akışı
```bash
# Şema değişikliklerinden migration oluştur
npx prisma migrate dev --name add_user_avatar
# Üretimde bekleyen migration'ları uygula
npx prisma migrate deploy
# Veritabanını sıfırla (sadece dev)
npx prisma migrate reset
# Şema değişikliklerinden sonra client oluştur
npx prisma generate
```
### Şema Örneği
```prisma
model User {
id String @id @default(cuid())
email String @unique
name String?
avatarUrl String? @map("avatar_url")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
orders Order[]
@@map("users")
@@index([email])
}
```
### Özel SQL Migration
Prisma'nın ifade edemediği operasyonlar için (concurrent indeksler, veri backfill'leri):
```bash
# Boş migration oluştur, sonra SQL'i manuel düzenle
npx prisma migrate dev --create-only --name add_email_index
```
```sql
-- migrations/20240115_add_email_index/migration.sql
-- Prisma CONCURRENTLY oluşturamaz, bu yüzden manuel yazıyoruz
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email ON users (email);
```
## Drizzle (TypeScript/Node.js)
### İş Akışı
```bash
# Şema değişikliklerinden migration oluştur
npx drizzle-kit generate
# Migration'ları uygula
npx drizzle-kit migrate
# Şemayı doğrudan push et (sadece dev, migration dosyası yok)
npx drizzle-kit push
```
### Şema Örneği
```typescript
import { pgTable, text, timestamp, uuid, boolean } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
email: text("email").notNull().unique(),
name: text("name"),
isActive: boolean("is_active").notNull().default(true),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
});
```
## Django (Python)
### İş Akışı
```bash
# Model değişikliklerinden migration oluştur
python manage.py makemigrations
# Migration'ları uygula
python manage.py migrate
# Migration durumunu göster
python manage.py showmigrations
# Özel SQL için boş migration oluştur
python manage.py makemigrations --empty app_name -n description
```
### Veri Migration
```python
from django.db import migrations
def backfill_display_names(apps, schema_editor):
User = apps.get_model("accounts", "User")
batch_size = 5000
users = User.objects.filter(display_name="")
while users.exists():
batch = list(users[:batch_size])
for user in batch:
user.display_name = user.username
User.objects.bulk_update(batch, ["display_name"], batch_size=batch_size)
def reverse_backfill(apps, schema_editor):
pass # Veri migration'ı, geri alma gerekmez
class Migration(migrations.Migration):
dependencies = [("accounts", "0015_add_display_name")]
operations = [
migrations.RunPython(backfill_display_names, reverse_backfill),
]
```
## golang-migrate (Go)
### İş Akışı
```bash
# Migration çifti oluştur
migrate create -ext sql -dir migrations -seq add_user_avatar
# Tüm bekleyen migration'ları uygula
migrate -path migrations -database "$DATABASE_URL" up
# Son migration'ı rollback et
migrate -path migrations -database "$DATABASE_URL" down 1
# Versiyonu zorla (dirty durumu düzelt)
migrate -path migrations -database "$DATABASE_URL" force VERSION
```
### Migration Dosyaları
```sql
-- migrations/000003_add_user_avatar.up.sql
ALTER TABLE users ADD COLUMN avatar_url TEXT;
CREATE INDEX CONCURRENTLY idx_users_avatar ON users (avatar_url) WHERE avatar_url IS NOT NULL;
-- migrations/000003_add_user_avatar.down.sql
DROP INDEX IF EXISTS idx_users_avatar;
ALTER TABLE users DROP COLUMN IF EXISTS avatar_url;
```
## Sıfır Kesinti Migration Stratejisi
Kritik üretim değişiklikleri için expand-contract kalıbını takip edin:
```
Faz 1: EXPAND
- Yeni sütun/tablo ekle (nullable veya varsayılanlı)
- Deploy: uygulama hem ESKİ hem YENİ'ye yazar
- Mevcut veriyi backfill et
Faz 2: MIGRATE
- Deploy: uygulama YENİ'den okur, her İKİSİNE yazar
- Veri tutarlılığını doğrula
Faz 3: CONTRACT
- Deploy: uygulama sadece YENİ'yi kullanır
- Eski sütun/tabloyu ayrı migration'da kaldır
```
### Zaman Çizelgesi Örneği
```
Gün 1: Migration new_status sütunu ekler (nullable)
Gün 1: App v2 deploy et — hem status hem new_status'a yaz
Gün 2: Mevcut satırlar için backfill migration'ı çalıştır
Gün 3: App v3 deploy et — sadece new_status'tan okur
Gün 7: Migration eski status sütununu kaldırır
```
## Anti-Kalıplar
| Anti-Kalıp | Neden Başarısız Olur | Daha İyi Yaklaşım |
|-------------|-------------|-----------------|
| Üretimde manuel SQL | Denetim izi yok, tekrarlanamaz | Her zaman migration dosyaları kullan |
| Deploy edilmiş migration'ları düzenleme | Ortamlar arası sapma yaratır | Bunun yerine yeni migration oluştur |
| Varsayılansız NOT NULL | Tabloyu kilitler, tüm satırları yeniden yazar | Nullable ekle, backfill et, sonra kısıt ekle |
| Büyük tabloda inline indeks | Build sırasında yazmaları engeller | CREATE INDEX CONCURRENTLY |
| Tek migration'da şema + veri | Rollback zor, uzun transaction'lar | Ayrı migration'lar |
| Kodu kaldırmadan önce sütun kaldırma | Eksik sütunda uygulama hataları | Önce kodu kaldır, sonra sütunu sonraki deploy'da kaldır |

View File

@@ -0,0 +1,427 @@
---
name: deployment-patterns
description: Deployment iş akışları, CI/CD pipeline kalıpları, Docker konteynerizasyonu, sağlık kontrolleri, rollback stratejileri ve web uygulamaları için üretim hazırlığı kontrol listeleri.
origin: ECC
---
# Deployment Kalıpları
Üretim deployment iş akışları ve CI/CD en iyi uygulamaları.
## Ne Zaman Aktifleştirmeli
- CI/CD pipeline'ları kurarken
- Bir uygulamayı Docker'ize ederken
- Deployment stratejisi planlarken (blue-green, canary, rolling)
- Sağlık kontrolleri ve hazırlık probe'ları uygularken
- Üretim yayınına hazırlanırken
- Ortama özgü ayarları yapılandırırken
## Deployment Stratejileri
### Rolling Deployment (Varsayılan)
Instance'ları kademeli olarak değiştir — rollout sırasında eski ve yeni versiyonlar birlikte çalışır.
```
Instance 1: v1 → v2 (önce güncelle)
Instance 2: v1 (hala v1 çalışıyor)
Instance 3: v1 (hala v1 çalışıyor)
Instance 1: v2
Instance 2: v1 → v2 (ikinci olarak güncelle)
Instance 3: v1
Instance 1: v2
Instance 2: v2
Instance 3: v1 → v2 (son olarak güncelle)
```
**Artıları:** Sıfır kesinti, kademeli rollout
**Eksileri:** İki versiyon aynı anda çalışır — geriye uyumlu değişiklikler gerektirir
**Ne zaman kullanılır:** Standart deployment'lar, geriye uyumlu değişiklikler
### Blue-Green Deployment
İki özdeş ortam çalıştır. Trafiği atomik olarak değiştir.
```
Blue (v1) ← trafik
Green (v2) boşta, yeni versiyon çalışıyor
# Doğrulamadan sonra:
Blue (v1) boşta (yedek haline gelir)
Green (v2) ← trafik
```
**Artıları:** Anında rollback (blue'ya geri dön), temiz geçiş
**Eksileri:** Deployment sırasında 2x altyapı gerektirir
**Ne zaman kullanılır:** Kritik servisler, sorunlara sıfır tolerans
### Canary Deployment
Önce trafiğin küçük bir yüzdesini yeni versiyona yönlendir.
```
v1: %95 trafik
v2: %5 trafik (canary)
# Metrikler iyi görünüyorsa:
v1: %50 trafik
v2: %50 trafik
# Final:
v2: %100 trafik
```
**Artıları:** Tam rollout'tan önce gerçek trafikle sorunları yakalar
**Eksileri:** Trafik bölme altyapısı, izleme gerektirir
**Ne zaman kullanılır:** Yüksek trafikli servisler, riskli değişiklikler, feature flag'ler
## Docker
### Multi-Stage Dockerfile (Node.js)
```dockerfile
# Stage 1: Bağımlılıkları yükle
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production=false
# Stage 2: Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
RUN npm prune --production
# Stage 3: Production image
FROM node:22-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && adduser -S appuser -u 1001
USER appuser
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
ENV NODE_ENV=production
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
```
### Multi-Stage Dockerfile (Go)
```dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /server ./cmd/server
FROM alpine:3.19 AS runner
RUN apk --no-cache add ca-certificates
RUN adduser -D -u 1001 appuser
USER appuser
COPY --from=builder /server /server
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:8080/health || exit 1
CMD ["/server"]
```
### Multi-Stage Dockerfile (Python/Django)
```dockerfile
FROM python:3.12-slim AS builder
WORKDIR /app
RUN pip install --no-cache-dir uv
COPY requirements.txt .
RUN uv pip install --system --no-cache -r requirements.txt
FROM python:3.12-slim AS runner
WORKDIR /app
RUN useradd -r -u 1001 appuser
USER appuser
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY . .
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health/')" || exit 1
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
```
### Docker En İyi Uygulamaları
```
# İYİ uygulamalar
- Belirli versiyon tag'leri kullanın (node:22-alpine, node:latest değil)
- Image boyutunu minimize etmek için multi-stage build'ler
- Root olmayan kullanıcı olarak çalıştır
- Önce bağımlılık dosyalarını kopyalayın (layer caching)
- node_modules, .git, test'leri hariç tutmak için .dockerignore kullanın
- HEALTHCHECK talimatı ekleyin
- docker-compose veya k8s'te kaynak limitleri ayarlayın
# KÖTÜ uygulamalar
- Root olarak çalıştırmak
- :latest tag'lerini kullanmak
- Tüm repo'yu tek COPY layer'da kopyalamak
- Production image'de dev bağımlılıklarını yüklemek
- Image'de secret'ları saklamak (env var veya secrets manager kullanın)
```
## CI/CD Pipeline
### GitHub Actions (Standart Pipeline)
```yaml
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test -- --coverage
- uses: actions/upload-artifact@v4
if: always()
with:
name: coverage
path: coverage/
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy to production
run: |
# Platforma özgü deployment komutu
# Railway: railway up
# Vercel: vercel --prod
# K8s: kubectl set image deployment/app app=ghcr.io/${{ github.repository }}:${{ github.sha }}
echo "Deploying ${{ github.sha }}"
```
### Pipeline Aşamaları
```
PR açıldığında:
lint → typecheck → unit tests → integration tests → preview deploy
Main'e merge edildiğinde:
lint → typecheck → unit tests → integration tests → build image → deploy staging → smoke tests → deploy production
```
## Sağlık Kontrolleri
### Sağlık Kontrolü Endpoint'i
```typescript
// Basit sağlık kontrolü
app.get("/health", (req, res) => {
res.status(200).json({ status: "ok" });
});
// Detaylı sağlık kontrolü (dahili izleme için)
app.get("/health/detailed", async (req, res) => {
const checks = {
database: await checkDatabase(),
redis: await checkRedis(),
externalApi: await checkExternalApi(),
};
const allHealthy = Object.values(checks).every(c => c.status === "ok");
res.status(allHealthy ? 200 : 503).json({
status: allHealthy ? "ok" : "degraded",
timestamp: new Date().toISOString(),
version: process.env.APP_VERSION || "unknown",
uptime: process.uptime(),
checks,
});
});
async function checkDatabase(): Promise<HealthCheck> {
try {
await db.query("SELECT 1");
return { status: "ok", latency_ms: 2 };
} catch (err) {
return { status: "error", message: "Database unreachable" };
}
}
```
### Kubernetes Probe'ları
```yaml
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 2
startupProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 30 # 30 * 5s = 150s max başlatma süresi
```
## Ortam Yapılandırması
### Twelve-Factor App Kalıbı
```bash
# Tüm yapılandırma ortam değişkenleri ile — asla kodda değil
DATABASE_URL=postgres://user:pass@host:5432/db
REDIS_URL=redis://host:6379/0
API_KEY=${API_KEY} # secrets manager tarafından enjekte edilir
LOG_LEVEL=info
PORT=3000
# Ortama özgü davranış
NODE_ENV=production # veya staging, development
APP_ENV=production # açık uygulama ortamı
```
### Yapılandırma Validasyonu
```typescript
import { z } from "zod";
const envSchema = z.object({
NODE_ENV: z.enum(["development", "staging", "production"]),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
});
// Başlangıçta validasyon yap — yapılandırma yanlışsa hızlı başarısız ol
export const env = envSchema.parse(process.env);
```
## Rollback Stratejisi
### Anında Rollback
```bash
# Docker/Kubernetes: önceki image'a işaret et
kubectl rollout undo deployment/app
# Vercel: önceki deployment'ı yükselt
vercel rollback
# Railway: önceki commit'i tekrar deploy et
railway up --commit <previous-sha>
# Veritabanı: migration'ı rollback et (geri alınabilirse)
npx prisma migrate resolve --rolled-back <migration-name>
```
### Rollback Kontrol Listesi
- [ ] Önceki image/artifact mevcut ve tag'lenmiş
- [ ] Veritabanı migration'ları geriye uyumlu (yıkıcı değişiklik yok)
- [ ] Feature flag'ler deploy olmadan yeni özellikleri devre dışı bırakabilir
- [ ] Hata oranı artışları için izleme alarmları yapılandırılmış
- [ ] Rollback üretim yayınından önce staging'de test edilmiş
## Üretim Hazırlığı Kontrol Listesi
Herhangi bir üretim deployment'ından önce:
### Uygulama
- [ ] Tüm testler geçiyor (unit, integration, E2E)
- [ ] Kodda veya yapılandırma dosyalarında hardcode edilmiş secret yok
- [ ] Hata işleme tüm edge case'leri kapsıyor
- [ ] Loglama yapılandırılmış (JSON) ve PII içermiyor
- [ ] Sağlık kontrolü endpoint'i anlamlı durum döndürüyor
### Altyapı
- [ ] Docker image yeniden üretilebilir şekilde build oluyor (sabitlenmiş versiyonlar)
- [ ] Ortam değişkenleri dokümante edilmiş ve başlangıçta validate ediliyor
- [ ] Kaynak limitleri ayarlanmış (CPU, bellek)
- [ ] Horizontal scaling yapılandırılmış (min/max instance'lar)
- [ ] Tüm endpoint'lerde SSL/TLS etkin
### İzleme
- [ ] Uygulama metrikleri export ediliyor (istek oranı, gecikme, hatalar)
- [ ] Hata oranı > eşik için alarmlar yapılandırılmış
- [ ] Log toplama kurulmuş (yapılandırılmış loglar, aranabilir)
- [ ] Sağlık endpoint'inde uptime izleme
### Güvenlik
- [ ] Bağımlılıklar CVE'ler için taranmış
- [ ] CORS sadece izin verilen origin'ler için yapılandırılmış
- [ ] Halka açık endpoint'lerde hız sınırlama etkin
- [ ] Kimlik doğrulama ve yetkilendirme doğrulanmış
- [ ] Güvenlik header'ları ayarlanmış (CSP, HSTS, X-Frame-Options)
### Operasyonlar
- [ ] Rollback planı dokümante edilmiş ve test edilmiş
- [ ] Veritabanı migration'ı üretim boyutundaki veriye karşı test edilmiş
- [ ] Yaygın hata senaryoları için runbook
- [ ] Nöbet rotasyonu ve yükseltme yolu tanımlanmış

View File

@@ -0,0 +1,734 @@
---
name: django-patterns
description: DRF ile Django mimari desenleri, REST API tasarımı, ORM en iyi uygulamaları, caching, signal'ler, middleware ve production-grade Django uygulamaları.
origin: ECC
---
# Django Geliştirme Desenleri
Ölçeklenebilir, bakımı kolay uygulamalar için production-grade Django mimari desenleri.
## Ne Zaman Etkinleştirmeli
- Django web uygulamaları oluştururken
- Django REST Framework API'leri tasarlarken
- Django ORM ve modeller ile çalışırken
- Django proje yapısını kurarken
- Caching, signal'ler, middleware implement ederken
## Proje Yapısı
### Önerilen Düzen
```
myproject/
├── config/
│ ├── __init__.py
│ ├── settings/
│ │ ├── __init__.py
│ │ ├── base.py # Base ayarlar
│ │ ├── development.py # Dev ayarları
│ │ ├── production.py # Production ayarları
│ │ └── test.py # Test ayarları
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
├── manage.py
└── apps/
├── __init__.py
├── users/
│ ├── __init__.py
│ ├── models.py
│ ├── views.py
│ ├── serializers.py
│ ├── urls.py
│ ├── permissions.py
│ ├── filters.py
│ ├── services.py
│ └── tests/
└── products/
└── ...
```
### Split Settings Deseni
```python
# config/settings/base.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = env('DJANGO_SECRET_KEY')
DEBUG = False
ALLOWED_HOSTS = []
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'corsheaders',
# Local apps
'apps.users',
'apps.products',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'config.urls'
WSGI_APPLICATION = 'config.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env('DB_NAME'),
'USER': env('DB_USER'),
'PASSWORD': env('DB_PASSWORD'),
'HOST': env('DB_HOST'),
'PORT': env('DB_PORT', default='5432'),
}
}
# config/settings/development.py
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
DATABASES['default']['NAME'] = 'myproject_dev'
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# config/settings/production.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'WARNING',
'class': 'logging.FileHandler',
'filename': '/var/log/django/django.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'WARNING',
'propagate': True,
},
},
}
```
## Model Tasarım Desenleri
### Model En İyi Uygulamaları
```python
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.core.validators import MinValueValidator, MaxValueValidator
class User(AbstractUser):
"""AbstractUser'ı extend eden özel kullanıcı modeli."""
email = models.EmailField(unique=True)
phone = models.CharField(max_length=20, blank=True)
birth_date = models.DateField(null=True, blank=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
class Meta:
db_table = 'users'
verbose_name = 'user'
verbose_name_plural = 'users'
ordering = ['-date_joined']
def __str__(self):
return self.email
def get_full_name(self):
return f"{self.first_name} {self.last_name}".strip()
class Product(models.Model):
"""Uygun alan yapılandırması ile Product modeli."""
name = models.CharField(max_length=200)
slug = models.SlugField(unique=True, max_length=250)
description = models.TextField(blank=True)
price = models.DecimalField(
max_digits=10,
decimal_places=2,
validators=[MinValueValidator(0)]
)
stock = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True)
category = models.ForeignKey(
'Category',
on_delete=models.CASCADE,
related_name='products'
)
tags = models.ManyToManyField('Tag', blank=True, related_name='products')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'products'
ordering = ['-created_at']
indexes = [
models.Index(fields=['slug']),
models.Index(fields=['-created_at']),
models.Index(fields=['category', 'is_active']),
]
constraints = [
models.CheckConstraint(
check=models.Q(price__gte=0),
name='price_non_negative'
)
]
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
```
### QuerySet En İyi Uygulamaları
```python
from django.db import models
class ProductQuerySet(models.QuerySet):
"""Product modeli için özel QuerySet."""
def active(self):
"""Sadece aktif ürünleri döndür."""
return self.filter(is_active=True)
def with_category(self):
"""N+1 sorgularını önlemek için ilişkili kategoriyi seç."""
return self.select_related('category')
def with_tags(self):
"""Many-to-many ilişkisi için tag'leri prefetch et."""
return self.prefetch_related('tags')
def in_stock(self):
"""Stok > 0 olan ürünleri döndür."""
return self.filter(stock__gt=0)
def search(self, query):
"""İsim veya açıklamaya göre ürünleri ara."""
return self.filter(
models.Q(name__icontains=query) |
models.Q(description__icontains=query)
)
class Product(models.Model):
# ... alanlar ...
objects = ProductQuerySet.as_manager() # Özel QuerySet kullan
# Kullanım
Product.objects.active().with_category().in_stock()
```
### Manager Metodları
```python
class ProductManager(models.Manager):
"""Karmaşık sorgular için özel manager."""
def get_or_none(self, **kwargs):
"""DoesNotExist yerine nesne veya None döndür."""
try:
return self.get(**kwargs)
except self.model.DoesNotExist:
return None
def create_with_tags(self, name, price, tag_names):
"""İlişkili tag'lerle ürün oluştur."""
product = self.create(name=name, price=price)
tags = [Tag.objects.get_or_create(name=name)[0] for name in tag_names]
product.tags.set(tags)
return product
def bulk_update_stock(self, product_ids, quantity):
"""Birden fazla ürün için toplu stok güncellemesi."""
return self.filter(id__in=product_ids).update(stock=quantity)
# Model'de
class Product(models.Model):
# ... alanlar ...
custom = ProductManager()
```
## Django REST Framework Desenleri
### Serializer Desenleri
```python
from rest_framework import serializers
from django.contrib.auth.password_validation import validate_password
from .models import Product, User
class ProductSerializer(serializers.ModelSerializer):
"""Product modeli için serializer."""
category_name = serializers.CharField(source='category.name', read_only=True)
average_rating = serializers.FloatField(read_only=True)
discount_price = serializers.SerializerMethodField()
class Meta:
model = Product
fields = [
'id', 'name', 'slug', 'description', 'price',
'discount_price', 'stock', 'category_name',
'average_rating', 'created_at'
]
read_only_fields = ['id', 'slug', 'created_at']
def get_discount_price(self, obj):
"""Uygulanabilirse indirimli fiyatı hesapla."""
if hasattr(obj, 'discount') and obj.discount:
return obj.price * (1 - obj.discount.percent / 100)
return obj.price
def validate_price(self, value):
"""Fiyatın negatif olmadığından emin ol."""
if value < 0:
raise serializers.ValidationError("Price cannot be negative.")
return value
class ProductCreateSerializer(serializers.ModelSerializer):
"""Ürün oluşturmak için serializer."""
class Meta:
model = Product
fields = ['name', 'description', 'price', 'stock', 'category']
def validate(self, data):
"""Birden fazla alan için özel validation."""
if data['price'] > 10000 and data['stock'] > 100:
raise serializers.ValidationError(
"Cannot have high-value products with large stock."
)
return data
class UserRegistrationSerializer(serializers.ModelSerializer):
"""Kullanıcı kaydı için serializer."""
password = serializers.CharField(
write_only=True,
required=True,
validators=[validate_password],
style={'input_type': 'password'}
)
password_confirm = serializers.CharField(write_only=True, style={'input_type': 'password'})
class Meta:
model = User
fields = ['email', 'username', 'password', 'password_confirm']
def validate(self, data):
"""Şifrelerin eşleştiğini doğrula."""
if data['password'] != data['password_confirm']:
raise serializers.ValidationError({
"password_confirm": "Password fields didn't match."
})
return data
def create(self, validated_data):
"""Hash'lenmiş şifre ile kullanıcı oluştur."""
validated_data.pop('password_confirm')
password = validated_data.pop('password')
user = User.objects.create(**validated_data)
user.set_password(password)
user.save()
return user
```
### ViewSet Desenleri
```python
from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from django_filters.rest_framework import DjangoFilterBackend
from .models import Product
from .serializers import ProductSerializer, ProductCreateSerializer
from .permissions import IsOwnerOrReadOnly
from .filters import ProductFilter
from .services import ProductService
class ProductViewSet(viewsets.ModelViewSet):
"""Product modeli için ViewSet."""
queryset = Product.objects.select_related('category').prefetch_related('tags')
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_class = ProductFilter
search_fields = ['name', 'description']
ordering_fields = ['price', 'created_at', 'name']
ordering = ['-created_at']
def get_serializer_class(self):
"""Action'a göre uygun serializer döndür."""
if self.action == 'create':
return ProductCreateSerializer
return ProductSerializer
def perform_create(self, serializer):
"""Kullanıcı bağlamı ile kaydet."""
serializer.save(created_by=self.request.user)
@action(detail=False, methods=['get'])
def featured(self, request):
"""Öne çıkan ürünleri döndür."""
featured = self.queryset.filter(is_featured=True)[:10]
serializer = self.get_serializer(featured, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def purchase(self, request, pk=None):
"""Bir ürün satın al."""
product = self.get_object()
service = ProductService()
result = service.purchase(product, request.user)
return Response(result, status=status.HTTP_201_CREATED)
@action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
def my_products(self, request):
"""Mevcut kullanıcı tarafından oluşturulan ürünleri döndür."""
products = self.queryset.filter(created_by=request.user)
page = self.paginate_queryset(products)
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
```
### Özel Action'lar
```python
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def add_to_cart(request):
"""Kullanıcı sepetine ürün ekle."""
product_id = request.data.get('product_id')
quantity = request.data.get('quantity', 1)
try:
product = Product.objects.get(id=product_id)
except Product.DoesNotExist:
return Response(
{'error': 'Product not found'},
status=status.HTTP_404_NOT_FOUND
)
cart, _ = Cart.objects.get_or_create(user=request.user)
CartItem.objects.create(
cart=cart,
product=product,
quantity=quantity
)
return Response({'message': 'Added to cart'}, status=status.HTTP_201_CREATED)
```
## Service Layer Deseni
```python
# apps/orders/services.py
from typing import Optional
from django.db import transaction
from .models import Order, OrderItem
class OrderService:
"""Sipariş ilgili iş mantığı için service layer."""
@staticmethod
@transaction.atomic
def create_order(user, cart: Cart) -> Order:
"""Sepetten sipariş oluştur."""
order = Order.objects.create(
user=user,
total_price=cart.total_price
)
for item in cart.items.all():
OrderItem.objects.create(
order=order,
product=item.product,
quantity=item.quantity,
price=item.product.price
)
# Sepeti temizle
cart.items.all().delete()
return order
@staticmethod
def process_payment(order: Order, payment_data: dict) -> bool:
"""Sipariş için ödemeyi işle."""
# Ödeme gateway entegrasyonu
payment = PaymentGateway.charge(
amount=order.total_price,
token=payment_data['token']
)
if payment.success:
order.status = Order.Status.PAID
order.save()
# Onay email'i gönder
OrderService.send_confirmation_email(order)
return True
return False
@staticmethod
def send_confirmation_email(order: Order):
"""Sipariş onay email'i gönder."""
# Email gönderme mantığı
pass
```
## Caching Stratejileri
### View Seviyesi Caching
```python
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
@method_decorator(cache_page(60 * 15), name='dispatch') # 15 dakika
class ProductListView(generic.ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
```
### Template Fragment Caching
```django
{% load cache %}
{% cache 500 sidebar %}
... pahalı sidebar içeriği ...
{% endcache %}
```
### Düşük Seviye Caching
```python
from django.core.cache import cache
def get_featured_products():
"""Caching ile öne çıkan ürünleri getir."""
cache_key = 'featured_products'
products = cache.get(cache_key)
if products is None:
products = list(Product.objects.filter(is_featured=True))
cache.set(cache_key, products, timeout=60 * 15) # 15 dakika
return products
```
### QuerySet Caching
```python
from django.core.cache import cache
def get_popular_categories():
cache_key = 'popular_categories'
categories = cache.get(cache_key)
if categories is None:
categories = list(Category.objects.annotate(
product_count=Count('products')
).filter(product_count__gt=10).order_by('-product_count')[:20])
cache.set(cache_key, categories, timeout=60 * 60) # 1 saat
return categories
```
## Signal'ler
### Signal Desenleri
```python
# apps/users/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from .models import Profile
User = get_user_model()
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""Kullanıcı oluşturulduğunda profil oluştur."""
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
"""Kullanıcı kaydedildiğinde profili kaydet."""
instance.profile.save()
# apps/users/apps.py
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.users'
def ready(self):
"""Uygulama hazır olduğunda signal'leri import et."""
import apps.users.signals
```
## Middleware
### Özel Middleware
```python
# middleware/active_user_middleware.py
import time
from django.utils.deprecation import MiddlewareMixin
class ActiveUserMiddleware(MiddlewareMixin):
"""Aktif kullanıcıları takip etmek için middleware."""
def process_request(self, request):
"""Gelen request'i işle."""
if request.user.is_authenticated:
# Son aktif zamanı güncelle
request.user.last_active = timezone.now()
request.user.save(update_fields=['last_active'])
class RequestLoggingMiddleware(MiddlewareMixin):
"""Request'leri loglamak için middleware."""
def process_request(self, request):
"""Request başlangıç zamanını logla."""
request.start_time = time.time()
def process_response(self, request, response):
"""Request süresini logla."""
if hasattr(request, 'start_time'):
duration = time.time() - request.start_time
logger.info(f'{request.method} {request.path} - {response.status_code} - {duration:.3f}s')
return response
```
## Performans Optimizasyonu
### N+1 Sorgu Önleme
```python
# Kötü - N+1 sorguları
products = Product.objects.all()
for product in products:
print(product.category.name) # Her ürün için ayrı sorgu
# İyi - select_related ile tek sorgu
products = Product.objects.select_related('category').all()
for product in products:
print(product.category.name)
# İyi - Many-to-many için prefetch
products = Product.objects.prefetch_related('tags').all()
for product in products:
for tag in product.tags.all():
print(tag.name)
```
### Veritabanı İndeksleme
```python
class Product(models.Model):
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(unique=True)
category = models.ForeignKey('Category', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [
models.Index(fields=['name']),
models.Index(fields=['-created_at']),
models.Index(fields=['category', 'created_at']),
]
```
### Toplu Operasyonlar
```python
# Toplu oluşturma
Product.objects.bulk_create([
Product(name=f'Product {i}', price=10.00)
for i in range(1000)
])
# Toplu güncelleme
products = Product.objects.all()[:100]
for product in products:
product.is_active = True
Product.objects.bulk_update(products, ['is_active'])
# Toplu silme
Product.objects.filter(stock=0).delete()
```
## Hızlı Referans
| Desen | Açıklama |
|-------|----------|
| Split settings | Ayrı dev/prod/test ayarları |
| Özel QuerySet | Yeniden kullanılabilir sorgu metodları |
| Service Layer | İş mantığı ayrımı |
| ViewSet | REST API endpoint'leri |
| Serializer validation | Request/response dönüşümü |
| select_related | Foreign key optimizasyonu |
| prefetch_related | Many-to-many optimizasyonu |
| Cache first | Pahalı operasyonları cache'le |
| Signal'ler | Olay güdümlü aksiyonlar |
| Middleware | Request/response işleme |
Unutmayın: Django birçok kısayol sağlar, ancak production uygulamaları için yapı ve organizasyon kısa koddan daha önemlidir. Bakımı kolay olacak şekilde oluşturun.

View File

@@ -0,0 +1,364 @@
---
name: docker-patterns
description: Yerel geliştirme, konteyner güvenliği, ağ, volume stratejileri ve multi-servis orkestrasyon için Docker ve Docker Compose kalıpları.
origin: ECC
---
# Docker Kalıpları
Konteynerize edilmiş geliştirme için Docker ve Docker Compose en iyi uygulamaları.
## Ne Zaman Aktifleştirmeli
- Yerel geliştirme için Docker Compose kurarken
- Çok konteynerli mimariler tasarlarken
- Konteyner ağ veya volume sorunlarını giderirken
- Dockerfile'ları güvenlik ve boyut için incelerken
- Yerel geliştirmeden konteynerize iş akışına geçerken
## Yerel Geliştirme için Docker Compose
### Standart Web Uygulaması Stack'i
```yaml
# docker-compose.yml
services:
app:
build:
context: .
target: dev # Multi-stage Dockerfile'ın dev aşamasını kullan
ports:
- "3000:3000"
volumes:
- .:/app # Hot reload için bind mount
- /app/node_modules # Anonim volume -- konteyner bağımlılıklarını korur
environment:
- DATABASE_URL=postgres://postgres:postgres@db:5432/app_dev
- REDIS_URL=redis://redis:6379/0
- NODE_ENV=development
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
command: npm run dev
db:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app_dev
volumes:
- pgdata:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redisdata:/data
mailpit: # Yerel email testi
image: axllent/mailpit
ports:
- "8025:8025" # Web UI
- "1025:1025" # SMTP
volumes:
pgdata:
redisdata:
```
### Geliştirme vs Üretim Dockerfile
```dockerfile
# Aşama: bağımlılıklar
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Aşama: dev (hot reload, debug araçları)
FROM node:22-alpine AS dev
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
# Aşama: build
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build && npm prune --production
# Aşama: production (minimal image)
FROM node:22-alpine AS production
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && adduser -S appuser -u 1001
USER appuser
COPY --from=build --chown=appuser:appgroup /app/dist ./dist
COPY --from=build --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=build --chown=appuser:appgroup /app/package.json ./
ENV NODE_ENV=production
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
```
### Override Dosyaları
```yaml
# docker-compose.override.yml (otomatik yüklenir, sadece dev ayarları)
services:
app:
environment:
- DEBUG=app:*
- LOG_LEVEL=debug
ports:
- "9229:9229" # Node.js debugger
# docker-compose.prod.yml (üretim için açıkça)
services:
app:
build:
target: production
restart: always
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
```
```bash
# Geliştirme (override'ı otomatik yükler)
docker compose up
# Üretim
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
```
## Ağ (Networking)
### Servis Keşfi
Aynı Compose ağındaki servisler servis adıyla çözümlenir:
```
# "app" konteynerinden:
postgres://postgres:postgres@db:5432/app_dev # "db" db konteynerine çözümlenir
redis://redis:6379/0 # "redis" redis konteynerine çözümlenir
```
### Özel Ağlar
```yaml
services:
frontend:
networks:
- frontend-net
api:
networks:
- frontend-net
- backend-net
db:
networks:
- backend-net # Sadece api'den erişilebilir, frontend'den değil
networks:
frontend-net:
backend-net:
```
### Sadece Gereklileri Açığa Çıkarma
```yaml
services:
db:
ports:
- "127.0.0.1:5432:5432" # Sadece host'tan erişilebilir, ağdan değil
# Üretimde port'ları tamamen çıkar -- sadece Docker ağı içinden erişilebilir
```
## Volume Stratejileri
```yaml
volumes:
# İsimli volume: konteyner yeniden başlatmalarında kalıcı, Docker tarafından yönetilir
pgdata:
# Bind mount: host dizinini konteynere eşler (geliştirme için)
# - ./src:/app/src
# Anonim volume: bind mount override'ından konteyner tarafından oluşturulan içeriği korur
# - /app/node_modules
```
### Yaygın Kalıplar
```yaml
services:
app:
volumes:
- .:/app # Kaynak kodu (hot reload için bind mount)
- /app/node_modules # Konteyner'ın node_modules'ünü host'tan koru
- /app/.next # Build cache'ini koru
db:
volumes:
- pgdata:/var/lib/postgresql/data # Kalıcı veri
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql # Init scriptleri
```
## Konteyner Güvenliği
### Dockerfile Sıkılaştırma
```dockerfile
# 1. Belirli tag'ler kullanın (:latest asla)
FROM node:22.12-alpine3.20
# 2. Root olmayan kullanıcı olarak çalıştır
RUN addgroup -g 1001 -S app && adduser -S app -u 1001
USER app
# 3. Capability'leri düşür (compose'da)
# 4. Mümkün olduğunda salt okunur kök dosya sistemi
# 5. Image layer'larında secret yok
```
### Compose Güvenliği
```yaml
services:
app:
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
- /app/.cache
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Sadece < 1024 port'lara bind için
```
### Secret Yönetimi
```yaml
# İYİ: Ortam değişkenleri kullanın (runtime'da enjekte edilir)
services:
app:
env_file:
- .env # .env'i asla git'e commit etmeyin
environment:
- API_KEY # Host ortamından miras alır
# İYİ: Docker secrets (Swarm modu)
secrets:
db_password:
file: ./secrets/db_password.txt
services:
db:
secrets:
- db_password
# KÖTÜ: Image'de hardcode
# ENV API_KEY=sk-proj-xxxxx # ASLA BUNU YAPMAYIN
```
## .dockerignore
```
node_modules
.git
.env
.env.*
dist
coverage
*.log
.next
.cache
docker-compose*.yml
Dockerfile*
README.md
tests/
```
## Hata Ayıklama
### Yaygın Komutlar
```bash
# Logları görüntüle
docker compose logs -f app # App loglarını takip et
docker compose logs --tail=50 db # db'den son 50 satır
# Çalışan konteynerde komut çalıştır
docker compose exec app sh # app'e shell ile gir
docker compose exec db psql -U postgres # postgres'e bağlan
# İncele
docker compose ps # Çalışan servisler
docker compose top # Her konteynerdeki işlemler
docker stats # Kaynak kullanımı
# Yeniden build et
docker compose up --build # Image'leri yeniden build et
docker compose build --no-cache app # Tam rebuild'i zorla
# Temizle
docker compose down # Konteynerleri durdur ve kaldır
docker compose down -v # Volume'leri de kaldır (YIKıCı)
docker system prune # Kullanılmayan image/konteynerleri kaldır
```
### Ağ Sorunlarını Hata Ayıklama
```bash
# Konteyner içinde DNS çözümlemesini kontrol et
docker compose exec app nslookup db
# Bağlantıyı kontrol et
docker compose exec app wget -qO- http://api:3000/health
# Ağı incele
docker network ls
docker network inspect <project>_default
```
## Anti-Kalıplar
```
# KÖTÜ: Üretimde orkestrasyon olmadan docker compose kullanma
# Üretim çok konteynerli iş yükleri için Kubernetes, ECS veya Docker Swarm kullanın
# KÖTÜ: Volume olmadan konteynerlerde veri depolama
# Konteynerler geçicidir -- volume olmadan yeniden başlatmada tüm veri kaybolur
# KÖTÜ: Root olarak çalıştırma
# Daima root olmayan bir kullanıcı oluşturun ve kullanın
# KÖTÜ: :latest tag kullanma
# Yeniden üretilebilir build'ler için belirli versiyonlara sabitle
# KÖTÜ: Tüm servisleri içeren tek dev konteyner
# Endişeleri ayırın: konteyner başına bir işlem
# KÖTÜ: Secret'ları docker-compose.yml'e koymak
# .env dosyaları (gitignore'lanmış) veya Docker secrets kullanın
```

View File

@@ -0,0 +1,326 @@
---
name: e2e-testing
description: Playwright E2E test kalıpları, Page Object Model, yapılandırma, CI/CD entegrasyonu, artifact yönetimi ve kararsız test stratejileri.
origin: ECC
---
# E2E Test Kalıpları
Kararlı, hızlı ve sürdürülebilir E2E test paketleri oluşturmak için kapsamlı Playwright kalıpları.
## Test Dosyası Organizasyonu
```
tests/
├── e2e/
│ ├── auth/
│ │ ├── login.spec.ts
│ │ ├── logout.spec.ts
│ │ └── register.spec.ts
│ ├── features/
│ │ ├── browse.spec.ts
│ │ ├── search.spec.ts
│ │ └── create.spec.ts
│ └── api/
│ └── endpoints.spec.ts
├── fixtures/
│ ├── auth.ts
│ └── data.ts
└── playwright.config.ts
```
## Page Object Model (POM)
```typescript
import { Page, Locator } from '@playwright/test'
export class ItemsPage {
readonly page: Page
readonly searchInput: Locator
readonly itemCards: Locator
readonly createButton: Locator
constructor(page: Page) {
this.page = page
this.searchInput = page.locator('[data-testid="search-input"]')
this.itemCards = page.locator('[data-testid="item-card"]')
this.createButton = page.locator('[data-testid="create-btn"]')
}
async goto() {
await this.page.goto('/items')
await this.page.waitForLoadState('networkidle')
}
async search(query: string) {
await this.searchInput.fill(query)
await this.page.waitForResponse(resp => resp.url().includes('/api/search'))
await this.page.waitForLoadState('networkidle')
}
async getItemCount() {
return await this.itemCards.count()
}
}
```
## Test Yapısı
```typescript
import { test, expect } from '@playwright/test'
import { ItemsPage } from '../../pages/ItemsPage'
test.describe('Item Search', () => {
let itemsPage: ItemsPage
test.beforeEach(async ({ page }) => {
itemsPage = new ItemsPage(page)
await itemsPage.goto()
})
test('should search by keyword', async ({ page }) => {
await itemsPage.search('test')
const count = await itemsPage.getItemCount()
expect(count).toBeGreaterThan(0)
await expect(itemsPage.itemCards.first()).toContainText(/test/i)
await page.screenshot({ path: 'artifacts/search-results.png' })
})
test('should handle no results', async ({ page }) => {
await itemsPage.search('xyznonexistent123')
await expect(page.locator('[data-testid="no-results"]')).toBeVisible()
expect(await itemsPage.getItemCount()).toBe(0)
})
})
```
## Playwright Yapılandırması
```typescript
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html', { outputFolder: 'playwright-report' }],
['junit', { outputFile: 'playwright-results.xml' }],
['json', { outputFile: 'playwright-results.json' }]
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 10000,
navigationTimeout: 30000,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
})
```
## Kararsız Test Kalıpları
### Karantina
```typescript
test('flaky: complex search', async ({ page }) => {
test.fixme(true, 'Flaky - Issue #123')
// test kodu...
})
test('conditional skip', async ({ page }) => {
test.skip(process.env.CI, 'Flaky in CI - Issue #123')
// test kodu...
})
```
### Kararsızlığı Belirleme
```bash
npx playwright test tests/search.spec.ts --repeat-each=10
npx playwright test tests/search.spec.ts --retries=3
```
### Yaygın Nedenler ve Çözümler
**Yarış koşulları:**
```typescript
// Kötü: element'in hazır olduğunu varsayar
await page.click('[data-testid="button"]')
// İyi: otomatik bekleme locator
await page.locator('[data-testid="button"]').click()
```
**Ağ zamanlaması:**
```typescript
// Kötü: keyfi timeout
await page.waitForTimeout(5000)
// İyi: belirli koşulu bekle
await page.waitForResponse(resp => resp.url().includes('/api/data'))
```
**Animasyon zamanlaması:**
```typescript
// Kötü: animasyon sırasında tıkla
await page.click('[data-testid="menu-item"]')
// İyi: kararlılığı bekle
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
await page.waitForLoadState('networkidle')
await page.locator('[data-testid="menu-item"]').click()
```
## Artifact Yönetimi
### Ekran Görüntüleri
```typescript
await page.screenshot({ path: 'artifacts/after-login.png' })
await page.screenshot({ path: 'artifacts/full-page.png', fullPage: true })
await page.locator('[data-testid="chart"]').screenshot({ path: 'artifacts/chart.png' })
```
### Trace'ler
```typescript
await browser.startTracing(page, {
path: 'artifacts/trace.json',
screenshots: true,
snapshots: true,
})
// ... test aksiyonları ...
await browser.stopTracing()
```
### Video
```typescript
// playwright.config.ts'de
use: {
video: 'retain-on-failure',
videosPath: 'artifacts/videos/'
}
```
## CI/CD Entegrasyonu
```yaml
# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
env:
BASE_URL: ${{ vars.STAGING_URL }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
```
## Test Raporu Şablonu
```markdown
# E2E Test Raporu
**Tarih:** YYYY-MM-DD HH:MM
**Süre:** Xd Ys
**Durum:** GEÇTİ / BAŞARISIZ
## Özet
- Toplam: X | Geçti: Y (Z%) | Başarısız: A | Kararsız: B | Atlandı: C
## Başarısız Testler
### test-adı
**Dosya:** `tests/e2e/feature.spec.ts:45`
**Hata:** Element'in görünür olması bekleniyordu
**Ekran Görüntüsü:** artifacts/failed.png
**Önerilen Çözüm:** [açıklama]
## Artifact'lar
- HTML Raporu: playwright-report/index.html
- Ekran Görüntüleri: artifacts/*.png
- Videolar: artifacts/videos/*.webm
- Trace'ler: artifacts/*.zip
```
## Wallet / Web3 Testi
```typescript
test('wallet connection', async ({ page, context }) => {
// Wallet provider'ı mock'la
await context.addInitScript(() => {
window.ethereum = {
isMetaMask: true,
request: async ({ method }) => {
if (method === 'eth_requestAccounts')
return ['0x1234567890123456789012345678901234567890']
if (method === 'eth_chainId') return '0x1'
}
}
})
await page.goto('/')
await page.locator('[data-testid="connect-wallet"]').click()
await expect(page.locator('[data-testid="wallet-address"]')).toContainText('0x1234')
})
```
## Finansal / Kritik Akış Testi
```typescript
test('trade execution', async ({ page }) => {
// Üretimde atla — gerçek para
test.skip(process.env.NODE_ENV === 'production', 'Skip on production')
await page.goto('/markets/test-market')
await page.locator('[data-testid="position-yes"]').click()
await page.locator('[data-testid="trade-amount"]').fill('1.0')
// Önizlemeyi doğrula
const preview = page.locator('[data-testid="trade-preview"]')
await expect(preview).toContainText('1.0')
// Onayla ve blockchain'i bekle
await page.locator('[data-testid="confirm-trade"]').click()
await page.waitForResponse(
resp => resp.url().includes('/api/trade') && resp.status() === 200,
{ timeout: 30000 }
)
await expect(page.locator('[data-testid="trade-success"]')).toBeVisible()
})
```

View File

@@ -0,0 +1,270 @@
---
name: eval-harness
description: Eval-driven development (EDD) ilkelerini uygulayan Claude Code oturumları için formal değerlendirme çerçevesi
origin: ECC
tools: Read, Write, Edit, Bash, Grep, Glob
---
# Eval Harness Skill
Claude Code oturumları için eval-driven development (EDD) ilkelerini uygulayan formal değerlendirme çerçevesi.
## Ne Zaman Aktifleştirmeli
- AI destekli iş akışları için eval-driven development (EDD) kurarken
- Claude Code görev tamamlama için geçti/kaldı kriterleri tanımlarken
- pass@k metrikleriyle agent güvenilirliğini ölçerken
- Prompt veya agent değişiklikleri için regresyon test paketleri oluştururken
- Model versiyonları arasında agent performansını benchmark ederken
## Felsefe
Eval-Driven Development, eval'ları "AI geliştirmenin birim testleri" olarak ele alır:
- İmplementasyondan ÖNCE beklenen davranışı tanımla
- Geliştirme sırasında eval'ları sürekli çalıştır
- Her değişiklikle regresyonları izle
- Güvenilirlik ölçümü için pass@k metriklerini kullan
## Eval Tipleri
### Capability Eval'ları
Claude'un daha önce yapamadığı bir şeyi yapıp yapamadığını test et:
```markdown
[CAPABILITY EVAL: feature-name]
Görev: Claude'un başarması gereken şeyin açıklaması
Başarı Kriterleri:
- [ ] Kriter 1
- [ ] Kriter 2
- [ ] Kriter 3
Beklenen Çıktı: Beklenen sonucun açıklaması
```
### Regression Eval'ları
Değişikliklerin mevcut fonksiyonaliteyi bozmadığından emin ol:
```markdown
[REGRESSION EVAL: feature-name]
Baseline: SHA veya checkpoint adı
Testler:
- existing-test-1: PASS/FAIL
- existing-test-2: PASS/FAIL
- existing-test-3: PASS/FAIL
Sonuç: X/Y geçti (önceden Y/Y)
```
## Grader Tipleri
### 1. Code-Based Grader
Kod kullanarak deterministik kontroller:
```bash
# Dosyanın beklenen pattern içerip içermediğini kontrol et
grep -q "export function handleAuth" src/auth.ts && echo "PASS" || echo "FAIL"
# Testlerin geçip geçmediğini kontrol et
npm test -- --testPathPattern="auth" && echo "PASS" || echo "FAIL"
# Build'in başarılı olup olmadığını kontrol et
npm run build && echo "PASS" || echo "FAIL"
```
### 2. Model-Based Grader
ık uçlu çıktıları değerlendirmek için Claude kullan:
```markdown
[MODEL GRADER PROMPT]
Aşağıdaki kod değişikliğini değerlendir:
1. Belirtilen sorunu çözüyor mu?
2. İyi yapılandırılmış mı?
3. Edge case'ler işleniyor mu?
4. Hata işleme uygun mu?
Puan: 1-5 (1=kötü, 5=mükemmel)
Gerekçe: [açıklama]
```
### 3. Human Grader
Manuel inceleme için işaretle:
```markdown
[HUMAN REVIEW REQUIRED]
Değişiklik: Neyin değiştiğinin açıklaması
Sebep: Neden insan incelemesi gerekli
Risk Seviyesi: DÜŞÜK/ORTA/YÜKSEK
```
## Metrikler
### pass@k
"k denemede en az bir başarı"
- pass@1: İlk deneme başarı oranı
- pass@3: 3 denemede başarı
- Tipik hedef: pass@3 > %90
### pass^k
"Tüm k denemeler başarılı"
- Güvenilirlik için daha yüksek çıta
- pass^3: Ardışık 3 başarı
- Kritik yollar için kullan
## Eval İş Akışı
### 1. Tanımla (Kodlamadan Önce)
```markdown
## EVAL DEFINITION: feature-xyz
### Capability Eval'ları
1. Yeni kullanıcı hesabı oluşturabilir
2. Email formatını doğrulayabilir
3. Şifreyi güvenli şekilde hash'leyebilir
### Regression Eval'ları
1. Mevcut login hala çalışıyor
2. Oturum yönetimi değişmedi
3. Logout akışı sağlam
### Başarı Metrikleri
- capability eval'lar için pass@3 > %90
- regression eval'lar için pass^3 = %100
```
### 2. Uygula
Tanımlanan eval'ları geçmek için kod yaz.
### 3. Değerlendir
```bash
# Capability eval'ları çalıştır
[Her capability eval'ı çalıştır, PASS/FAIL kaydet]
# Regression eval'ları çalıştır
npm test -- --testPathPattern="existing"
# Rapor oluştur
```
### 4. Rapor
```markdown
EVAL REPORT: feature-xyz
========================
Capability Eval'ları:
create-user: PASS (pass@1)
validate-email: PASS (pass@2)
hash-password: PASS (pass@1)
Genel: 3/3 geçti
Regression Eval'ları:
login-flow: PASS
session-mgmt: PASS
logout-flow: PASS
Genel: 3/3 geçti
Metrikler:
pass@1: %67 (2/3)
pass@3: %100 (3/3)
Durum: İNCELEMEYE HAZIR
```
## Entegrasyon Kalıpları
### İmplementasyondan Önce
```
/eval define feature-name
```
`.claude/evals/feature-name.md` konumunda eval tanım dosyası oluşturur
### İmplementasyon Sırasında
```
/eval check feature-name
```
Mevcut eval'ları çalıştırır ve durumu raporlar
### İmplementasyondan Sonra
```
/eval report feature-name
```
Tam eval raporu oluşturur
## Eval Depolama
Eval'ları projede sakla:
```
.claude/
evals/
feature-xyz.md # Eval tanımı
feature-xyz.log # Eval çalıştırma geçmişi
baseline.json # Regression baseline'ları
```
## En İyi Uygulamalar
1. **Kodlamadan ÖNCE eval'ları tanımla** - Başarı kriterleri hakkında net düşünmeyi zorlar
2. **Eval'ları sık çalıştır** - Regresyonları erken yakala
3. **pass@k'yı zaman içinde izle** - Güvenilirlik trendlerini gözle
4. **Mümkün olduğunda code grader kullan** - Deterministik > olasılıksal
5. **Güvenlik için insan incelemesi** - Güvenlik kontrollerini asla tam otomatikleştirme
6. **Eval'ları hızlı tut** - Yavaş eval'lar çalıştırılmaz
7. **Eval'ları kodla versiyonla** - Eval'lar birinci sınıf artifact'lardır
## Örnek: Kimlik Doğrulama Ekleme
```markdown
## EVAL: add-authentication
### Faz 1: Tanımla (10 dk)
Capability Eval'ları:
- [ ] Kullanıcı email/şifre ile kayıt olabilir
- [ ] Kullanıcı geçerli kimlik bilgileriyle giriş yapabilir
- [ ] Geçersiz kimlik bilgileri uygun hatayla reddedilir
- [ ] Oturumlar sayfa yeniden yüklemelerinde kalıcıdır
- [ ] Logout oturumu temizler
Regression Eval'ları:
- [ ] Halka açık rotalar hala erişilebilir
- [ ] API yanıtları değişmedi
- [ ] Veritabanı şeması uyumlu
### Faz 2: Uygula (değişir)
[Kod yaz]
### Faz 3: Değerlendir
Çalıştır: /eval check add-authentication
### Faz 4: Raporla
EVAL REPORT: add-authentication
==============================
Capability: 5/5 geçti (pass@3: %100)
Regression: 3/3 geçti (pass^3: %100)
Durum: YAYINLA
```
## Product Eval'ları (v1.8)
Davranış kalitesi sadece birim testlerle yakalanamadığında product eval'ları kullan.
### Grader Tipleri
1. Code grader (deterministik assertion'lar)
2. Rule grader (regex/şema kısıtlamaları)
3. Model grader (LLM-as-judge rubric)
4. Human grader (belirsiz çıktılar için manuel karar)
### pass@k Kılavuzu
- `pass@1`: doğrudan güvenilirlik
- `pass@3`: kontrollü yeniden denemeler altında pratik güvenilirlik
- `pass^3`: kararlılık testi (3 çalıştırmanın tümü geçmeli)
Önerilen eşikler:
- Capability eval'ları: pass@3 >= 0.90
- Regression eval'ları: yayın-kritik yollar için pass^3 = 1.00
### Eval Anti-Kalıpları
- Prompt'ları bilinen eval örneklerine overfitting yapmak
- Sadece mutlu-yol çıktılarını ölçmek
- Geçme oranlarını kovalamken maliyet ve gecikme kaymasını görmezden gelmek
- Yayın kapılarında kararsız grader'lara izin vermek
### Minimal Eval Artifact Düzeni
- `.claude/evals/<feature>.md` tanımı
- `.claude/evals/<feature>.log` çalıştırma geçmişi
- `docs/releases/<version>/eval-summary.md` yayın snapshot'ı

View File

@@ -0,0 +1,642 @@
---
name: frontend-patterns
description: React, Next.js, state yönetimi, performans optimizasyonu ve UI en iyi uygulamaları için frontend geliştirme kalıpları.
origin: ECC
---
# Frontend Geliştirme Kalıpları
React, Next.js ve performanslı kullanıcı arayüzleri için modern frontend kalıpları.
## Ne Zaman Aktifleştirmelisiniz
- React bileşenleri oluştururken (composition, props, rendering)
- State yönetirken (useState, useReducer, Zustand, Context)
- Veri çekme implementasyonu (SWR, React Query, server components)
- Performans optimize ederken (memoization, virtualization, code splitting)
- Formlarla çalışırken (validation, controlled inputs, Zod schemas)
- Client-side routing ve navigasyon işlerken
- Erişilebilir, responsive UI kalıpları oluştururken
## Bileşen Kalıpları
### Kalıtım Yerine Composition
```typescript
// ✅ İYİ: Bileşen composition
interface CardProps {
children: React.ReactNode
variant?: 'default' | 'outlined'
}
export function Card({ children, variant = 'default' }: CardProps) {
return <div className={`card card-${variant}`}>{children}</div>
}
export function CardHeader({ children }: { children: React.ReactNode }) {
return <div className="card-header">{children}</div>
}
export function CardBody({ children }: { children: React.ReactNode }) {
return <div className="card-body">{children}</div>
}
// Kullanım
<Card>
<CardHeader>Başlık</CardHeader>
<CardBody>İçerik</CardBody>
</Card>
```
### Compound Components
```typescript
interface TabsContextValue {
activeTab: string
setActiveTab: (tab: string) => void
}
const TabsContext = createContext<TabsContextValue | undefined>(undefined)
export function Tabs({ children, defaultTab }: {
children: React.ReactNode
defaultTab: string
}) {
const [activeTab, setActiveTab] = useState(defaultTab)
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
{children}
</TabsContext.Provider>
)
}
export function TabList({ children }: { children: React.ReactNode }) {
return <div className="tab-list">{children}</div>
}
export function Tab({ id, children }: { id: string, children: React.ReactNode }) {
const context = useContext(TabsContext)
if (!context) throw new Error('Tab must be used within Tabs')
return (
<button
className={context.activeTab === id ? 'active' : ''}
onClick={() => context.setActiveTab(id)}
>
{children}
</button>
)
}
// Kullanım
<Tabs defaultTab="overview">
<TabList>
<Tab id="overview">Genel Bakış</Tab>
<Tab id="details">Detaylar</Tab>
</TabList>
</Tabs>
```
### Render Props Kalıbı
```typescript
interface DataLoaderProps<T> {
url: string
children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode
}
export function DataLoader<T>({ url, children }: DataLoaderProps<T>) {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [url])
return <>{children(data, loading, error)}</>
}
// Kullanım
<DataLoader<Market[]> url="/api/markets">
{(markets, loading, error) => {
if (loading) return <Spinner />
if (error) return <Error error={error} />
return <MarketList markets={markets!} />
}}
</DataLoader>
```
## Özel Hook Kalıpları
### State Yönetimi Hook'u
```typescript
export function useToggle(initialValue = false): [boolean, () => void] {
const [value, setValue] = useState(initialValue)
const toggle = useCallback(() => {
setValue(v => !v)
}, [])
return [value, toggle]
}
// Kullanım
const [isOpen, toggleOpen] = useToggle()
```
### Async Veri Çekme Hook'u
```typescript
interface UseQueryOptions<T> {
onSuccess?: (data: T) => void
onError?: (error: Error) => void
enabled?: boolean
}
export function useQuery<T>(
key: string,
fetcher: () => Promise<T>,
options?: UseQueryOptions<T>
) {
const [data, setData] = useState<T | null>(null)
const [error, setError] = useState<Error | null>(null)
const [loading, setLoading] = useState(false)
const refetch = useCallback(async () => {
setLoading(true)
setError(null)
try {
const result = await fetcher()
setData(result)
options?.onSuccess?.(result)
} catch (err) {
const error = err as Error
setError(error)
options?.onError?.(error)
} finally {
setLoading(false)
}
}, [fetcher, options])
useEffect(() => {
if (options?.enabled !== false) {
refetch()
}
}, [key, refetch, options?.enabled])
return { data, error, loading, refetch }
}
// Kullanım
const { data: markets, loading, error, refetch } = useQuery(
'markets',
() => fetch('/api/markets').then(r => r.json()),
{
onSuccess: data => console.log('Getirilen', data.length, 'market'),
onError: err => console.error('Başarısız:', err)
}
)
```
### Debounce Hook'u
```typescript
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => clearTimeout(handler)
}, [value, delay])
return debouncedValue
}
// Kullanım
const [searchQuery, setSearchQuery] = useState('')
const debouncedQuery = useDebounce(searchQuery, 500)
useEffect(() => {
if (debouncedQuery) {
performSearch(debouncedQuery)
}
}, [debouncedQuery])
```
## State Yönetimi Kalıpları
### Context + Reducer Kalıbı
```typescript
interface State {
markets: Market[]
selectedMarket: Market | null
loading: boolean
}
type Action =
| { type: 'SET_MARKETS'; payload: Market[] }
| { type: 'SELECT_MARKET'; payload: Market }
| { type: 'SET_LOADING'; payload: boolean }
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'SET_MARKETS':
return { ...state, markets: action.payload }
case 'SELECT_MARKET':
return { ...state, selectedMarket: action.payload }
case 'SET_LOADING':
return { ...state, loading: action.payload }
default:
return state
}
}
const MarketContext = createContext<{
state: State
dispatch: Dispatch<Action>
} | undefined>(undefined)
export function MarketProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, {
markets: [],
selectedMarket: null,
loading: false
})
return (
<MarketContext.Provider value={{ state, dispatch }}>
{children}
</MarketContext.Provider>
)
}
export function useMarkets() {
const context = useContext(MarketContext)
if (!context) throw new Error('useMarkets must be used within MarketProvider')
return context
}
```
## Performans Optimizasyonu
### Memoization
```typescript
// ✅ Pahalı hesaplamalar için useMemo
const sortedMarkets = useMemo(() => {
return markets.sort((a, b) => b.volume - a.volume)
}, [markets])
// ✅ Alt bileşenlere geçirilen fonksiyonlar için useCallback
const handleSearch = useCallback((query: string) => {
setSearchQuery(query)
}, [])
// ✅ Pure bileşenler için React.memo
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
return (
<div className="market-card">
<h3>{market.name}</h3>
<p>{market.description}</p>
</div>
)
})
```
### Code Splitting ve Lazy Loading
```typescript
import { lazy, Suspense } from 'react'
// ✅ Ağır bileşenleri lazy yükle
const HeavyChart = lazy(() => import('./HeavyChart'))
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
export function Dashboard() {
return (
<div>
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart data={data} />
</Suspense>
<Suspense fallback={null}>
<ThreeJsBackground />
</Suspense>
</div>
)
}
```
### Uzun Listeler için Virtualization
```typescript
import { useVirtualizer } from '@tanstack/react-virtual'
export function VirtualMarketList({ markets }: { markets: Market[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: markets.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 100, // Tahmini satır yüksekliği
overscan: 5 // Ekstra render edilecek öğeler
})
return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative'
}}
>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
>
<MarketCard market={markets[virtualRow.index]} />
</div>
))}
</div>
</div>
)
}
```
## Form İşleme Kalıpları
### Doğrulamalı Controlled Form
```typescript
interface FormData {
name: string
description: string
endDate: string
}
interface FormErrors {
name?: string
description?: string
endDate?: string
}
export function CreateMarketForm() {
const [formData, setFormData] = useState<FormData>({
name: '',
description: '',
endDate: ''
})
const [errors, setErrors] = useState<FormErrors>({})
const validate = (): boolean => {
const newErrors: FormErrors = {}
if (!formData.name.trim()) {
newErrors.name = 'İsim gereklidir'
} else if (formData.name.length > 200) {
newErrors.name = 'İsim 200 karakterden az olmalıdır'
}
if (!formData.description.trim()) {
newErrors.description = 'Açıklama gereklidir'
}
if (!formData.endDate) {
newErrors.endDate = 'Bitiş tarihi gereklidir'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!validate()) return
try {
await createMarket(formData)
// Başarı işleme
} catch (error) {
// Hata işleme
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={formData.name}
onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}
placeholder="Market ismi"
/>
{errors.name && <span className="error">{errors.name}</span>}
{/* Diğer alanlar */}
<button type="submit">Market Oluştur</button>
</form>
)
}
```
## Error Boundary Kalıbı
```typescript
interface ErrorBoundaryState {
hasError: boolean
error: Error | null
}
export class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
ErrorBoundaryState
> {
state: ErrorBoundaryState = {
hasError: false,
error: null
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error boundary caught:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2>Bir şeyler yanlış gitti</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false })}>
Tekrar dene
</button>
</div>
)
}
return this.props.children
}
}
// Kullanım
<ErrorBoundary>
<App />
</ErrorBoundary>
```
## Animasyon Kalıpları
### Framer Motion Animasyonları
```typescript
import { motion, AnimatePresence } from 'framer-motion'
// ✅ Liste animasyonları
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
return (
<AnimatePresence>
{markets.map(market => (
<motion.div
key={market.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
<MarketCard market={market} />
</motion.div>
))}
</AnimatePresence>
)
}
// ✅ Modal animasyonları
export function Modal({ isOpen, onClose, children }: ModalProps) {
return (
<AnimatePresence>
{isOpen && (
<>
<motion.div
className="modal-overlay"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
/>
<motion.div
className="modal-content"
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
>
{children}
</motion.div>
</>
)}
</AnimatePresence>
)
}
```
## Erişilebilirlik Kalıpları
### Klavye Navigasyonu
```typescript
export function Dropdown({ options, onSelect }: DropdownProps) {
const [isOpen, setIsOpen] = useState(false)
const [activeIndex, setActiveIndex] = useState(0)
const handleKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault()
setActiveIndex(i => Math.min(i + 1, options.length - 1))
break
case 'ArrowUp':
e.preventDefault()
setActiveIndex(i => Math.max(i - 1, 0))
break
case 'Enter':
e.preventDefault()
onSelect(options[activeIndex])
setIsOpen(false)
break
case 'Escape':
setIsOpen(false)
break
}
}
return (
<div
role="combobox"
aria-expanded={isOpen}
aria-haspopup="listbox"
onKeyDown={handleKeyDown}
>
{/* Dropdown implementasyonu */}
</div>
)
}
```
### Focus Yönetimi
```typescript
export function Modal({ isOpen, onClose, children }: ModalProps) {
const modalRef = useRef<HTMLDivElement>(null)
const previousFocusRef = useRef<HTMLElement | null>(null)
useEffect(() => {
if (isOpen) {
// Şu anki focus'lanmış elementi kaydet
previousFocusRef.current = document.activeElement as HTMLElement
// Modal'a focus yap
modalRef.current?.focus()
} else {
// Kapatırken focus'u geri yükle
previousFocusRef.current?.focus()
}
}, [isOpen])
return isOpen ? (
<div
ref={modalRef}
role="dialog"
aria-modal="true"
tabIndex={-1}
onKeyDown={e => e.key === 'Escape' && onClose()}
>
{children}
</div>
) : null
}
```
**Unutmayın**: Modern frontend kalıpları sürdürülebilir, performanslı kullanıcı arayüzleri sağlar. Proje karmaşıklığınıza uyan kalıpları seçin.

View File

@@ -0,0 +1,674 @@
---
name: golang-patterns
description: İdiomatic Go desenler, en iyi uygulamalar ve sağlam, verimli ve bakımı kolay Go uygulamaları oluşturmak için konvansiyonlar.
origin: ECC
---
# Go Geliştirme Desenleri
Sağlam, verimli ve bakımı kolay uygulamalar oluşturmak için idiomatic Go desenleri ve en iyi uygulamalar.
## Ne Zaman Etkinleştirmeli
- Yeni Go kodu yazarken
- Go kodunu gözden geçirirken
- Mevcut Go kodunu refactor ederken
- Go paketleri/modülleri tasarlarken
## Temel Prensipler
### 1. Basitlik ve Açıklık
Go, zekiceden ziyade basitliği tercih eder. Kod açık ve okunması kolay olmalıdır.
```go
// İyi: Açık ve doğrudan
func GetUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
// Kötü: Aşırı zeki
func GetUser(id string) (*User, error) {
return func() (*User, error) {
if u, e := db.FindUser(id); e == nil {
return u, nil
} else {
return nil, e
}
}()
}
```
### 2. Sıfır Değeri Kullanışlı Yapın
Türleri, sıfır değerinin başlatma olmadan hemen kullanılabilir olacağı şekilde tasarlayın.
```go
// İyi: Sıfır değer kullanışlıdır
type Counter struct {
mu sync.Mutex
count int // sıfır değer 0'dır, kullanıma hazırdır
}
func (c *Counter) Inc() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// İyi: bytes.Buffer sıfır değerle çalışır
var buf bytes.Buffer
buf.WriteString("hello")
// Kötü: Başlatma gerektirir
type BadCounter struct {
counts map[string]int // nil map panic verir
}
```
### 3. Interface Kabul Et, Struct Döndür
Fonksiyonlar interface parametreleri kabul etmeli ve somut tipler döndürmelidir.
```go
// İyi: Interface kabul eder, somut tip döndürür
func ProcessData(r io.Reader) (*Result, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return &Result{Data: data}, nil
}
// Kötü: Interface döndürür (implementasyon detaylarını gereksiz yere gizler)
func ProcessData(r io.Reader) (io.Reader, error) {
// ...
}
```
## Hata İşleme Desenleri
### Bağlam ile Hata Sarmalama
```go
// İyi: Hataları bağlamla sarmalayın
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load config %s: %w", path, err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
return &cfg, nil
}
```
### Özel Hata Tipleri
```go
// Domain'e özgü hataları tanımlayın
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Yaygın durumlar için sentinel hatalar
var (
ErrNotFound = errors.New("resource not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
```
### errors.Is ve errors.As ile Hata Kontrolü
```go
func HandleError(err error) {
// Belirli bir hatayı kontrol et
if errors.Is(err, sql.ErrNoRows) {
log.Println("No records found")
return
}
// Hata tipini kontrol et
var validationErr *ValidationError
if errors.As(err, &validationErr) {
log.Printf("Validation error on field %s: %s",
validationErr.Field, validationErr.Message)
return
}
// Bilinmeyen hata
log.Printf("Unexpected error: %v", err)
}
```
### Hataları Asla Göz Ardı Etmeyin
```go
// Kötü: Boş tanımlayıcı ile hatayı göz ardı etmek
result, _ := doSomething()
// İyi: Hatayı işleyin veya neden göz ardı edildiğini açıkça belgelendirin
result, err := doSomething()
if err != nil {
return err
}
// Kabul edilebilir: Hata gerçekten önemli olmadığında (nadir)
_ = writer.Close() // En iyi çaba temizliği, hata başka yerde loglanır
```
## Eşzamanlılık Desenleri
### Worker Pool
```go
func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}()
}
wg.Wait()
close(results)
}
```
### İptal ve Zaman Aşımları için Context
```go
func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
```
### Zarif Kapatma
```go
func GracefulShutdown(server *http.Server) {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited")
}
```
### Koordineli Goroutine'ler için errgroup
```go
import "golang.org/x/sync/errgroup"
func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([][]byte, len(urls))
for i, url := range urls {
i, url := i, url // Loop değişkenlerini yakala
g.Go(func() error {
data, err := FetchWithTimeout(ctx, url)
if err != nil {
return err
}
results[i] = data
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
```
### Goroutine Sızıntılarından Kaçınma
```go
// Kötü: Context iptal edilirse goroutine sızıntısı
func leakyFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte)
go func() {
data, _ := fetch(url)
ch <- data // Alıcı yoksa sonsuza kadar bloklar
}()
return ch
}
// İyi: İptali düzgün bir şekilde işler
func safeFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte, 1) // Tamponlu kanal
go func() {
data, err := fetch(url)
if err != nil {
return
}
select {
case ch <- data:
case <-ctx.Done():
}
}()
return ch
}
```
## Interface Tasarımı
### Küçük, Odaklanmış Interface'ler
```go
// İyi: Tek metodlu interface'ler
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Interface'leri gerektiği gibi birleştirin
type ReadWriteCloser interface {
Reader
Writer
Closer
}
```
### Interface'leri Kullanıldıkları Yerde Tanımlayın
```go
// Sağlayıcı pakette değil, tüketici pakette
package service
// UserStore bu servisin neye ihtiyacı olduğunu tanımlar
type UserStore interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
type Service struct {
store UserStore
}
// Somut implementasyon başka bir pakette olabilir
// Bu interface'i bilmesine gerek yoktur
```
### Type Assertion ile Opsiyonel Davranış
```go
type Flusher interface {
Flush() error
}
func WriteAndFlush(w io.Writer, data []byte) error {
if _, err := w.Write(data); err != nil {
return err
}
// Destekleniyorsa flush et
if f, ok := w.(Flusher); ok {
return f.Flush()
}
return nil
}
```
## Paket Organizasyonu
### Standart Proje Düzeni
```text
myproject/
├── cmd/
│ └── myapp/
│ └── main.go # Giriş noktası
├── internal/
│ ├── handler/ # HTTP handler'lar
│ ├── service/ # İş mantığı
│ ├── repository/ # Veri erişimi
│ └── config/ # Yapılandırma
├── pkg/
│ └── client/ # Public API client
├── api/
│ └── v1/ # API tanımları (proto, OpenAPI)
├── testdata/ # Test fixture'ları
├── go.mod
├── go.sum
└── Makefile
```
### Paket İsimlendirme
```go
// İyi: Kısa, küçük harf, alt çizgi yok
package http
package json
package user
// Kötü: Verbose, karışık büyük/küçük harf veya gereksiz
package httpHandler
package json_parser
package userService // Gereksiz 'Service' eki
```
### Paket Seviyesi State'ten Kaçının
```go
// Kötü: Global değişken state
var db *sql.DB
func init() {
db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}
// İyi: Dependency injection
type Server struct {
db *sql.DB
}
func NewServer(db *sql.DB) *Server {
return &Server{db: db}
}
```
## Struct Tasarımı
### Functional Options Deseni
```go
type Server struct {
addr string
timeout time.Duration
logger *log.Logger
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) {
s.timeout = d
}
}
func WithLogger(l *log.Logger) Option {
return func(s *Server) {
s.logger = l
}
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{
addr: addr,
timeout: 30 * time.Second, // varsayılan
logger: log.Default(), // varsayılan
}
for _, opt := range opts {
opt(s)
}
return s
}
// Kullanım
server := NewServer(":8080",
WithTimeout(60*time.Second),
WithLogger(customLogger),
)
```
### Kompozisyon için Embedding
```go
type Logger struct {
prefix string
}
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.prefix, msg)
}
type Server struct {
*Logger // Embedding - Server Log metodunu alır
addr string
}
func NewServer(addr string) *Server {
return &Server{
Logger: &Logger{prefix: "SERVER"},
addr: addr,
}
}
// Kullanım
s := NewServer(":8080")
s.Log("Starting...") // Gömülü Logger.Log'u çağırır
```
## Bellek ve Performans
### Boyut Bilindiğinde Slice'ları Önceden Tahsis Edin
```go
// Kötü: Slice'ı birden çok kez büyütür
func processItems(items []Item) []Result {
var results []Result
for _, item := range items {
results = append(results, process(item))
}
return results
}
// İyi: Tek tahsis
func processItems(items []Item) []Result {
results := make([]Result, 0, len(items))
for _, item := range items {
results = append(results, process(item))
}
return results
}
```
### Sık Tahsisler için sync.Pool Kullanın
```go
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func ProcessRequest(data []byte) []byte {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
buf.Write(data)
// İşle...
return buf.Bytes()
}
```
### Döngülerde String Birleştirmekten Kaçının
```go
// Kötü: Birçok string tahsisi oluşturur
func join(parts []string) string {
var result string
for _, p := range parts {
result += p + ","
}
return result
}
// İyi: strings.Builder ile tek tahsis
func join(parts []string) string {
var sb strings.Builder
for i, p := range parts {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString(p)
}
return sb.String()
}
// En iyi: Standart kütüphaneyi kullanın
func join(parts []string) string {
return strings.Join(parts, ",")
}
```
## Go Tooling Entegrasyonu
### Temel Komutlar
```bash
# Build ve çalıştır
go build ./...
go run ./cmd/myapp
# Test
go test ./...
go test -race ./...
go test -cover ./...
# Statik analiz
go vet ./...
staticcheck ./...
golangci-lint run
# Modül yönetimi
go mod tidy
go mod verify
# Formatlama
gofmt -w .
goimports -w .
```
### Önerilen Linter Yapılandırması (.golangci.yml)
```yaml
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gofmt
- goimports
- misspell
- unconvert
- unparam
linters-settings:
errcheck:
check-type-assertions: true
govet:
check-shadowing: true
issues:
exclude-use-default: false
```
## Hızlı Referans: Go İfadeleri
| İfade | Açıklama |
|-------|----------|
| Interface kabul et, struct döndür | Fonksiyonlar interface parametreleri kabul eder, somut tipler döndürür |
| Hatalar değerdir | Hataları exception değil birinci sınıf değerler olarak ele alın |
| Belleği paylaşarak iletişim kurmayın | Goroutine'ler arası koordinasyon için kanalları kullanın |
| Sıfır değeri kullanışlı yapın | Tipler açık başlatma olmadan çalışmalıdır |
| Biraz kopyalama biraz bağımlılıktan iyidir | Gereksiz dış bağımlılıklardan kaçının |
| Açık zekiden iyidir | Okunabilirliği zekiceden öncelikli kılın |
| gofmt kimsenin favorisi değil ama herkesin arkadaşı | Her zaman gofmt/goimports ile formatlayın |
| Erken dönün | Hataları önce işleyin, mutlu yolu girintilendirilmemiş tutun |
## Kaçınılması Gereken Anti-Desenler
```go
// Kötü: Uzun fonksiyonlarda naked return'ler
func process() (result int, err error) {
// ... 50 satır ...
return // Ne döndürülüyor?
}
// Kötü: Kontrol akışı için panic kullanmak
func GetUser(id string) *User {
user, err := db.Find(id)
if err != nil {
panic(err) // Bunu yapmayın
}
return user
}
// Kötü: Struct içinde context geçmek
type Request struct {
ctx context.Context // Context ilk parametre olmalı
ID string
}
// İyi: Context ilk parametre olarak
func ProcessRequest(ctx context.Context, id string) error {
// ...
}
// Kötü: Value ve pointer receiver'ları karıştırmak
type Counter struct{ n int }
func (c Counter) Value() int { return c.n } // Value receiver
func (c *Counter) Increment() { c.n++ } // Pointer receiver
// Bir stil seçin ve tutarlı olun
```
**Unutmayın**: Go kodu en iyi anlamda sıkıcı olmalıdır - öngörülebilir, tutarlı ve anlaşılması kolay. Şüphe duyduğunuzda, basit tutun.

View File

@@ -0,0 +1,720 @@
---
name: golang-testing
description: Table-driven testler, subtestler, benchmark'lar, fuzzing ve test coverage içeren Go test desenleri. TDD metodolojisi ile idiomatic Go uygulamalarını takip eder.
origin: ECC
---
# Go Test Desenleri
TDD metodolojisini takip eden güvenilir, bakımı kolay testler yazmak için kapsamlı Go test desenleri.
## Ne Zaman Etkinleştirmeli
- Yeni Go fonksiyonları veya metodları yazarken
- Mevcut koda test coverage eklerken
- Performans-kritik kod için benchmark'lar oluştururken
- Input validation için fuzz testler implement ederken
- Go projelerinde TDD workflow'u takip ederken
## Go için TDD Workflow'u
### RED-GREEN-REFACTOR Döngüsü
```
RED → Önce başarısız bir test yaz
GREEN → Testi geçirmek için minimal kod yaz
REFACTOR → Testleri yeşil tutarken kodu iyileştir
REPEAT → Sonraki gereksinimle devam et
```
### Go'da Adım Adım TDD
```go
// Adım 1: Interface/signature'ı tanımla
// calculator.go
package calculator
func Add(a, b int) int {
panic("not implemented") // Placeholder
}
// Adım 2: Başarısız test yaz (RED)
// calculator_test.go
package calculator
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2, 3) = %d; want %d", got, want)
}
}
// Adım 3: Testi çalıştır - FAIL'i doğrula
// $ go test
// --- FAIL: TestAdd (0.00s)
// panic: not implemented
// Adım 4: Minimal kodu implement et (GREEN)
func Add(a, b int) int {
return a + b
}
// Adım 5: Testi çalıştır - PASS'i doğrula
// $ go test
// PASS
// Adım 6: Gerekirse refactor et, testlerin hala geçtiğini doğrula
```
## Table-Driven Testler
Go testleri için standart desen. Minimal kodla kapsamlı coverage sağlar.
```go
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -1, -2, -3},
{"zero values", 0, 0, 0},
{"mixed signs", -1, 1, 0},
{"large numbers", 1000000, 2000000, 3000000},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, got, tt.expected)
}
})
}
}
```
### Hata Durumları ile Table-Driven Testler
```go
func TestParseConfig(t *testing.T) {
tests := []struct {
name string
input string
want *Config
wantErr bool
}{
{
name: "valid config",
input: `{"host": "localhost", "port": 8080}`,
want: &Config{Host: "localhost", Port: 8080},
},
{
name: "invalid JSON",
input: `{invalid}`,
wantErr: true,
},
{
name: "empty input",
input: "",
wantErr: true,
},
{
name: "minimal config",
input: `{}`,
want: &Config{}, // Sıfır değer config
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseConfig(tt.input)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("got %+v; want %+v", got, tt.want)
}
})
}
}
```
## Subtestler ve Sub-benchmark'lar
### İlgili Testleri Organize Etme
```go
func TestUser(t *testing.T) {
// Tüm subtestler tarafından paylaşılan setup
db := setupTestDB(t)
t.Run("Create", func(t *testing.T) {
user := &User{Name: "Alice"}
err := db.CreateUser(user)
if err != nil {
t.Fatalf("CreateUser failed: %v", err)
}
if user.ID == "" {
t.Error("expected user ID to be set")
}
})
t.Run("Get", func(t *testing.T) {
user, err := db.GetUser("alice-id")
if err != nil {
t.Fatalf("GetUser failed: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got name %q; want %q", user.Name, "Alice")
}
})
t.Run("Update", func(t *testing.T) {
// ...
})
t.Run("Delete", func(t *testing.T) {
// ...
})
}
```
### Paralel Subtestler
```go
func TestParallel(t *testing.T) {
tests := []struct {
name string
input string
}{
{"case1", "input1"},
{"case2", "input2"},
{"case3", "input3"},
}
for _, tt := range tests {
tt := tt // Range değişkenini yakala
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // Subtestleri paralel çalıştır
result := Process(tt.input)
// assertion'lar...
_ = result
})
}
}
```
## Test Helper'ları
### Helper Fonksiyonlar
```go
func setupTestDB(t *testing.T) *sql.DB {
t.Helper() // Bunu helper fonksiyon olarak işaretle
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("failed to open database: %v", err)
}
// Test bittiğinde temizlik
t.Cleanup(func() {
db.Close()
})
// Migration'ları çalıştır
if _, err := db.Exec(schema); err != nil {
t.Fatalf("failed to create schema: %v", err)
}
return db
}
func assertNoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func assertEqual[T comparable](t *testing.T, got, want T) {
t.Helper()
if got != want {
t.Errorf("got %v; want %v", got, want)
}
}
```
### Geçici Dosyalar ve Dizinler
```go
func TestFileProcessing(t *testing.T) {
// Geçici dizin oluştur - otomatik olarak temizlenir
tmpDir := t.TempDir()
// Test dosyası oluştur
testFile := filepath.Join(tmpDir, "test.txt")
err := os.WriteFile(testFile, []byte("test content"), 0644)
if err != nil {
t.Fatalf("failed to create test file: %v", err)
}
// Testi çalıştır
result, err := ProcessFile(testFile)
if err != nil {
t.Fatalf("ProcessFile failed: %v", err)
}
// Assert...
_ = result
}
```
## Golden File'lar
`testdata/` içinde saklanan beklenen çıktı dosyalarına karşı test etme.
```go
var update = flag.Bool("update", false, "update golden files")
func TestRender(t *testing.T) {
tests := []struct {
name string
input Template
}{
{"simple", Template{Name: "test"}},
{"complex", Template{Name: "test", Items: []string{"a", "b"}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Render(tt.input)
golden := filepath.Join("testdata", tt.name+".golden")
if *update {
// Golden dosyayı güncelle: go test -update
err := os.WriteFile(golden, got, 0644)
if err != nil {
t.Fatalf("failed to update golden file: %v", err)
}
}
want, err := os.ReadFile(golden)
if err != nil {
t.Fatalf("failed to read golden file: %v", err)
}
if !bytes.Equal(got, want) {
t.Errorf("output mismatch:\ngot:\n%s\nwant:\n%s", got, want)
}
})
}
}
```
## Interface'ler ile Mocking
### Interface Tabanlı Mocking
```go
// Bağımlılıklar için interface tanımlayın
type UserRepository interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
// Production implementasyonu
type PostgresUserRepository struct {
db *sql.DB
}
func (r *PostgresUserRepository) GetUser(id string) (*User, error) {
// Gerçek veritabanı sorgusu
}
// Testler için mock implementasyon
type MockUserRepository struct {
GetUserFunc func(id string) (*User, error)
SaveUserFunc func(user *User) error
}
func (m *MockUserRepository) GetUser(id string) (*User, error) {
return m.GetUserFunc(id)
}
func (m *MockUserRepository) SaveUser(user *User) error {
return m.SaveUserFunc(user)
}
// Mock kullanarak test
func TestUserService(t *testing.T) {
mock := &MockUserRepository{
GetUserFunc: func(id string) (*User, error) {
if id == "123" {
return &User{ID: "123", Name: "Alice"}, nil
}
return nil, ErrNotFound
},
}
service := NewUserService(mock)
user, err := service.GetUserProfile("123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got name %q; want %q", user.Name, "Alice")
}
}
```
## Benchmark'lar
### Temel Benchmark'lar
```go
func BenchmarkProcess(b *testing.B) {
data := generateTestData(1000)
b.ResetTimer() // Setup süresini sayma
for i := 0; i < b.N; i++ {
Process(data)
}
}
// Çalıştır: go test -bench=BenchmarkProcess -benchmem
// Çıktı: BenchmarkProcess-8 10000 105234 ns/op 4096 B/op 10 allocs/op
```
### Farklı Boyutlarla Benchmark
```go
func BenchmarkSort(b *testing.B) {
sizes := []int{100, 1000, 10000, 100000}
for _, size := range sizes {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
data := generateRandomSlice(size)
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Zaten sıralanmış veriyi sıralamaktan kaçınmak için kopya oluştur
tmp := make([]int, len(data))
copy(tmp, data)
sort.Ints(tmp)
}
})
}
}
```
### Bellek Tahsis Benchmark'ları
```go
func BenchmarkStringConcat(b *testing.B) {
parts := []string{"hello", "world", "foo", "bar", "baz"}
b.Run("plus", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var s string
for _, p := range parts {
s += p
}
_ = s
}
})
b.Run("builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
for _, p := range parts {
sb.WriteString(p)
}
_ = sb.String()
}
})
b.Run("join", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = strings.Join(parts, "")
}
})
}
```
## Fuzzing (Go 1.18+)
### Temel Fuzz Testi
```go
func FuzzParseJSON(f *testing.F) {
// Seed corpus ekle
f.Add(`{"name": "test"}`)
f.Add(`{"count": 123}`)
f.Add(`[]`)
f.Add(`""`)
f.Fuzz(func(t *testing.T, input string) {
var result map[string]interface{}
err := json.Unmarshal([]byte(input), &result)
if err != nil {
// Rastgele input için geçersiz JSON beklenebilir
return
}
// Parsing başarılıysa, yeniden encoding çalışmalı
_, err = json.Marshal(result)
if err != nil {
t.Errorf("Marshal failed after successful Unmarshal: %v", err)
}
})
}
// Çalıştır: go test -fuzz=FuzzParseJSON -fuzztime=30s
```
### Birden Çok Input ile Fuzz Testi
```go
func FuzzCompare(f *testing.F) {
f.Add("hello", "world")
f.Add("", "")
f.Add("abc", "abc")
f.Fuzz(func(t *testing.T, a, b string) {
result := Compare(a, b)
// Özellik: Compare(a, a) her zaman 0'a eşit olmalı
if a == b && result != 0 {
t.Errorf("Compare(%q, %q) = %d; want 0", a, b, result)
}
// Özellik: Compare(a, b) ve Compare(b, a) zıt işarete sahip olmalı
reverse := Compare(b, a)
if (result > 0 && reverse >= 0) || (result < 0 && reverse <= 0) {
if result != 0 || reverse != 0 {
t.Errorf("Compare(%q, %q) = %d, Compare(%q, %q) = %d; inconsistent",
a, b, result, b, a, reverse)
}
}
})
}
```
## Test Coverage
### Coverage Çalıştırma
```bash
# Temel coverage
go test -cover ./...
# Coverage profili oluştur
go test -coverprofile=coverage.out ./...
# Coverage'ı tarayıcıda görüntüle
go tool cover -html=coverage.out
# Fonksiyona göre coverage görüntüle
go tool cover -func=coverage.out
# Race detection ile coverage
go test -race -coverprofile=coverage.out ./...
```
### Coverage Hedefleri
| Kod Tipi | Hedef |
|----------|-------|
| Kritik iş mantığı | 100% |
| Public API'ler | 90%+ |
| Genel kod | 80%+ |
| Oluşturulan kod | Hariç tut |
### Oluşturulan Kodu Coverage'dan Hariç Tutma
```go
//go:generate mockgen -source=interface.go -destination=mock_interface.go
// Coverage profile'ında, build tag'leri ile hariç tut:
// go test -cover -tags=!generate ./...
```
## HTTP Handler Testleri
```go
func TestHealthHandler(t *testing.T) {
// Request oluştur
req := httptest.NewRequest(http.MethodGet, "/health", nil)
w := httptest.NewRecorder()
// Handler'ı çağır
HealthHandler(w, req)
// Response'u kontrol et
resp := w.Result()
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("got status %d; want %d", resp.StatusCode, http.StatusOK)
}
body, _ := io.ReadAll(resp.Body)
if string(body) != "OK" {
t.Errorf("got body %q; want %q", body, "OK")
}
}
func TestAPIHandler(t *testing.T) {
tests := []struct {
name string
method string
path string
body string
wantStatus int
wantBody string
}{
{
name: "get user",
method: http.MethodGet,
path: "/users/123",
wantStatus: http.StatusOK,
wantBody: `{"id":"123","name":"Alice"}`,
},
{
name: "not found",
method: http.MethodGet,
path: "/users/999",
wantStatus: http.StatusNotFound,
},
{
name: "create user",
method: http.MethodPost,
path: "/users",
body: `{"name":"Bob"}`,
wantStatus: http.StatusCreated,
},
}
handler := NewAPIHandler()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var body io.Reader
if tt.body != "" {
body = strings.NewReader(tt.body)
}
req := httptest.NewRequest(tt.method, tt.path, body)
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != tt.wantStatus {
t.Errorf("got status %d; want %d", w.Code, tt.wantStatus)
}
if tt.wantBody != "" && w.Body.String() != tt.wantBody {
t.Errorf("got body %q; want %q", w.Body.String(), tt.wantBody)
}
})
}
}
```
## Test Komutları
```bash
# Tüm testleri çalıştır
go test ./...
# Verbose çıktı ile testleri çalıştır
go test -v ./...
# Belirli bir testi çalıştır
go test -run TestAdd ./...
# Pattern ile eşleşen testleri çalıştır
go test -run "TestUser/Create" ./...
# Race detector ile testleri çalıştır
go test -race ./...
# Coverage ile testleri çalıştır
go test -cover -coverprofile=coverage.out ./...
# Sadece kısa testleri çalıştır
go test -short ./...
# Timeout ile testleri çalıştır
go test -timeout 30s ./...
# Benchmark'ları çalıştır
go test -bench=. -benchmem ./...
# Fuzzing çalıştır
go test -fuzz=FuzzParse -fuzztime=30s ./...
# Test çalışma sayısı (flaky test tespiti için)
go test -count=10 ./...
```
## En İyi Uygulamalar
**YAPIN:**
- Testleri ÖNCE yazın (TDD)
- Kapsamlı coverage için table-driven testler kullanın
- İmplementasyon değil davranış test edin
- Helper fonksiyonlarda `t.Helper()` kullanın
- Bağımsız testler için `t.Parallel()` kullanın
- Kaynakları `t.Cleanup()` ile temizleyin
- Senaryoyu açıklayan anlamlı test isimleri kullanın
**YAPMAYIN:**
- Private fonksiyonları doğrudan test etmeyin (public API üzerinden test edin)
- Testlerde `time.Sleep()` kullanmayın (channel'lar veya condition'lar kullanın)
- Flaky testleri göz ardı etmeyin (düzeltin veya kaldırın)
- Her şeyi mocklamayın (mümkün olduğunda integration testlerini tercih edin)
- Hata yolu testini atlamayın
## CI/CD ile Entegrasyon
```yaml
# GitHub Actions örneği
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Run tests
run: go test -race -coverprofile=coverage.out ./...
- name: Check coverage
run: |
go tool cover -func=coverage.out | grep total | awk '{print $3}' | \
awk -F'%' '{if ($1 < 80) exit 1}'
```
**Unutmayın**: Testler dokümantasyondur. Kodunuzun nasıl kullanılması gerektiğini gösterirler. Testleri açık yazın ve güncel tutun.

View File

@@ -0,0 +1,151 @@
---
name: jpa-patterns
description: Spring Boot'ta entity tasarımı, ilişkiler, sorgu optimizasyonu, transaction'lar, auditing, indeksleme, sayfalama ve pooling için JPA/Hibernate kalıpları.
origin: ECC
---
# JPA/Hibernate Kalıpları
Spring Boot'ta veri modelleme, repository'ler ve performans ayarlaması için kullanın.
## Ne Zaman Aktifleştirmeli
- JPA entity'leri ve tablo eşlemelerini tasarlarken
- İlişkileri tanımlarken (@OneToMany, @ManyToOne, @ManyToMany)
- Sorguları optimize ederken (N+1 önleme, fetch stratejileri, projections)
- Transaction'ları, auditing'i veya soft delete'leri yapılandırırken
- Sayfalama, sıralama veya özel repository metodları kurarken
- Connection pooling (HikariCP) veya second-level caching ayarlarken
## Entity Tasarımı
```java
@Entity
@Table(name = "markets", indexes = {
@Index(name = "idx_markets_slug", columnList = "slug", unique = true)
})
@EntityListeners(AuditingEntityListener.class)
public class MarketEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String name;
@Column(nullable = false, unique = true, length = 120)
private String slug;
@Enumerated(EnumType.STRING)
private MarketStatus status = MarketStatus.ACTIVE;
@CreatedDate private Instant createdAt;
@LastModifiedDate private Instant updatedAt;
}
```
Auditing'i etkinleştir:
```java
@Configuration
@EnableJpaAuditing
class JpaConfig {}
```
## İlişkiler ve N+1 Önleme
```java
@OneToMany(mappedBy = "market", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PositionEntity> positions = new ArrayList<>();
```
- Varsayılan olarak lazy loading; gerektiğinde sorgularda `JOIN FETCH` kullan
- Koleksiyonlarda `EAGER` kullanmaktan kaçın; okuma yolları için DTO projections kullan
```java
@Query("select m from MarketEntity m left join fetch m.positions where m.id = :id")
Optional<MarketEntity> findWithPositions(@Param("id") Long id);
```
## Repository Kalıpları
```java
public interface MarketRepository extends JpaRepository<MarketEntity, Long> {
Optional<MarketEntity> findBySlug(String slug);
@Query("select m from MarketEntity m where m.status = :status")
Page<MarketEntity> findByStatus(@Param("status") MarketStatus status, Pageable pageable);
}
```
- Hafif sorgular için projections kullan:
```java
public interface MarketSummary {
Long getId();
String getName();
MarketStatus getStatus();
}
Page<MarketSummary> findAllBy(Pageable pageable);
```
## Transaction'lar
- Servis metodlarını `@Transactional` ile işaretle
- Okuma yollarını optimize etmek için `@Transactional(readOnly = true)` kullan
- Propagation'ı dikkatle seç; uzun süreli transaction'lardan kaçın
```java
@Transactional
public Market updateStatus(Long id, MarketStatus status) {
MarketEntity entity = repo.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Market"));
entity.setStatus(status);
return Market.from(entity);
}
```
## Sayfalama
```java
PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());
Page<MarketEntity> markets = repo.findByStatus(MarketStatus.ACTIVE, page);
```
Cursor benzeri sayfalama için, sıralama ile birlikte JPQL'de `id > :lastId` ekle.
## İndeksleme ve Performans
- Yaygın filtreler için indeksler ekle (`status`, `slug`, foreign key'ler)
- Sorgu kalıplarına uyan composite indeksler kullan (`status, created_at`)
- `select *` kullanmaktan kaçın; sadece gerekli sütunları project et
- `saveAll` ve `hibernate.jdbc.batch_size` ile yazmaları batch'le
## Connection Pooling (HikariCP)
Önerilen özellikler:
```
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.validation-timeout=5000
```
PostgreSQL LOB işleme için ekle:
```
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
```
## Caching
- 1st-level cache EntityManager başına; transaction'lar arası entity'leri tutmaktan kaçın
- Okuma ağırlıklı entity'ler için second-level cache'i dikkatle düşün; eviction stratejisini doğrula
## Migration'lar
- Flyway veya Liquibase kullan; üretimde Hibernate auto DDL'ye asla güvenme
- Migration'ları idempotent ve ekleyici tut; plan olmadan sütun kaldırmaktan kaçın
## Veri Erişimi Testi
- Üretimi yansıtmak için Testcontainers ile `@DataJpaTest` tercih et
- Logları kullanarak SQL verimliliğini assert et: parametre değerleri için `logging.level.org.hibernate.SQL=DEBUG` ve `logging.level.org.hibernate.orm.jdbc.bind=TRACE` ayarla
**Hatırla**: Entity'leri yalın, sorguları kasıtlı ve transaction'ları kısa tut. Fetch stratejileri ve projections ile N+1'i önle, ve okuma/yazma yolların için indeksle.

View File

@@ -0,0 +1,535 @@
---
name: kotlin-patterns
description: Coroutine'ler, null safety ve DSL builder'lar ile sağlam, verimli ve sürdürülebilir Kotlin uygulamaları oluşturmak için idiomatic Kotlin kalıpları, en iyi uygulamalar ve konvansiyonlar.
origin: ECC
---
# Kotlin Geliştirme Kalıpları
Sağlam, verimli ve sürdürülebilir uygulamalar oluşturmak için idiomatic Kotlin kalıpları ve en iyi uygulamalar.
## Ne Zaman Kullanılır
- Yeni Kotlin kodu yazarken
- Kotlin kodunu incelerken
- Mevcut Kotlin kodunu refactor ederken
- Kotlin modülleri veya kütüphaneleri tasarlarken
- Gradle Kotlin DSL build'lerini yapılandırırken
## Nasıl Çalışır
Bu skill yedi temel alanda idiomatic Kotlin konvansiyonlarını uygular: tip sistemi ve safe-call operatörleri kullanarak null safety, `val` ve data class'larda `copy()` ile immutability, exhaustive tip hiyerarşileri için sealed class'lar ve interface'ler, coroutine'ler ve `Flow` ile yapılandırılmış eşzamanlılık, inheritance olmadan davranış eklemek için extension fonksiyonlar, `@DslMarker` ve lambda receiver'lar kullanarak tip güvenli DSL builder'lar, ve build yapılandırması için Gradle Kotlin DSL.
## Örnekler
**Elvis operatörü ile null safety:**
```kotlin
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user?.email ?: "unknown@example.com"
}
```
**Exhaustive sonuçlar için sealed class:**
```kotlin
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Failure(val error: AppError) : Result<Nothing>()
data object Loading : Result<Nothing>()
}
```
**async/await ile yapılandırılmış eşzamanlılık:**
```kotlin
suspend fun fetchUserWithPosts(userId: String): UserProfile =
coroutineScope {
val user = async { userService.getUser(userId) }
val posts = async { postService.getUserPosts(userId) }
UserProfile(user = user.await(), posts = posts.await())
}
```
## Temel İlkeler
### 1. Null Safety
Kotlin'in tip sistemi nullable ve non-nullable tipleri ayırır. Tam olarak kullanın.
```kotlin
// İyi: Varsayılan olarak non-nullable tipler kullan
fun getUser(id: String): User {
return userRepository.findById(id)
?: throw UserNotFoundException("User $id not found")
}
// İyi: Safe call'lar ve Elvis operatörü
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user?.email ?: "unknown@example.com"
}
// Kötü: Nullable tipleri zorla açma
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user!!.email // null ise NPE fırlatır
}
```
### 2. Varsayılan Olarak Immutability
`var` yerine `val` tercih edin, mutable koleksiyonlar yerine immutable olanları.
```kotlin
// İyi: Immutable veri
data class User(
val id: String,
val name: String,
val email: String,
)
// İyi: copy() ile dönüştürme
fun updateEmail(user: User, newEmail: String): User =
user.copy(email = newEmail)
// İyi: Immutable koleksiyonlar
val users: List<User> = listOf(user1, user2)
val filtered = users.filter { it.email.isNotBlank() }
// Kötü: Mutable state
var currentUser: User? = null // Mutable global state'ten kaçın
val mutableUsers = mutableListOf<User>() // Gerçekten gerekmedikçe kaçın
```
### 3. Expression Body'ler ve Tek İfadeli Fonksiyonlar
Kısa, okunabilir fonksiyonlar için expression body'ler kullanın.
```kotlin
// İyi: Expression body
fun isAdult(age: Int): Boolean = age >= 18
fun formatFullName(first: String, last: String): String =
"$first $last".trim()
fun User.displayName(): String =
name.ifBlank { email.substringBefore('@') }
// İyi: Expression olarak when
fun statusMessage(code: Int): String = when (code) {
200 -> "OK"
404 -> "Not Found"
500 -> "Internal Server Error"
else -> "Unknown status: $code"
}
// Kötü: Gereksiz block body
fun isAdult(age: Int): Boolean {
return age >= 18
}
```
### 4. Value Objeler İçin Data Class'lar
Öncelikle veri tutan tipler için data class'lar kullanın.
```kotlin
// İyi: copy, equals, hashCode, toString ile data class
data class CreateUserRequest(
val name: String,
val email: String,
val role: Role = Role.USER,
)
// İyi: Tip güvenliği için value class (runtime'da sıfır maliyet)
@JvmInline
value class UserId(val value: String) {
init {
require(value.isNotBlank()) { "UserId cannot be blank" }
}
}
@JvmInline
value class Email(val value: String) {
init {
require('@' in value) { "Invalid email: $value" }
}
}
fun getUser(id: UserId): User = userRepository.findById(id)
```
## Sealed Class'lar ve Interface'ler
### Kısıtlı Hiyerarşileri Modelleme
```kotlin
// İyi: Exhaustive when için sealed class
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Failure(val error: AppError) : Result<Nothing>()
data object Loading : Result<Nothing>()
}
fun <T> Result<T>.getOrNull(): T? = when (this) {
is Result.Success -> data
is Result.Failure -> null
is Result.Loading -> null
}
fun <T> Result<T>.getOrThrow(): T = when (this) {
is Result.Success -> data
is Result.Failure -> throw error.toException()
is Result.Loading -> throw IllegalStateException("Still loading")
}
```
### API Yanıtları İçin Sealed Interface'ler
```kotlin
sealed interface ApiError {
val message: String
data class NotFound(override val message: String) : ApiError
data class Unauthorized(override val message: String) : ApiError
data class Validation(
override val message: String,
val field: String,
) : ApiError
data class Internal(
override val message: String,
val cause: Throwable? = null,
) : ApiError
}
fun ApiError.toStatusCode(): Int = when (this) {
is ApiError.NotFound -> 404
is ApiError.Unauthorized -> 401
is ApiError.Validation -> 422
is ApiError.Internal -> 500
}
```
## Scope Fonksiyonlar
### Her Birini Ne Zaman Kullanmalı
```kotlin
// let: Nullable'ı veya scope edilmiş sonucu dönüştür
val length: Int? = name?.let { it.trim().length }
// apply: Bir nesneyi yapılandır (nesneyi döndürür)
val user = User().apply {
name = "Alice"
email = "alice@example.com"
}
// also: Yan etkiler (nesneyi döndürür)
val user = createUser(request).also { logger.info("Created user: ${it.id}") }
// run: Receiver ile block çalıştır (sonucu döndürür)
val result = connection.run {
prepareStatement(sql)
executeQuery()
}
// with: run'ın extension olmayan formu
val csv = with(StringBuilder()) {
appendLine("name,email")
users.forEach { appendLine("${it.name},${it.email}") }
toString()
}
```
## Extension Fonksiyonlar
### Inheritance Olmadan Fonksiyonalite Ekleme
```kotlin
// İyi: Domain'e özgü extension'lar
fun String.toSlug(): String =
lowercase()
.replace(Regex("[^a-z0-9\\s-]"), "")
.replace(Regex("\\s+"), "-")
.trim('-')
fun Instant.toLocalDate(zone: ZoneId = ZoneId.systemDefault()): LocalDate =
atZone(zone).toLocalDate()
// İyi: Koleksiyon extension'ları
fun <T> List<T>.second(): T = this[1]
fun <T> List<T>.secondOrNull(): T? = getOrNull(1)
// İyi: Scope edilmiş extension'lar (global namespace'i kirletmez)
class UserService {
private fun User.isActive(): Boolean =
status == Status.ACTIVE && lastLogin.isAfter(Instant.now().minus(30, ChronoUnit.DAYS))
fun getActiveUsers(): List<User> = userRepository.findAll().filter { it.isActive() }
}
```
## Coroutine'ler
### Yapılandırılmış Eşzamanlılık
```kotlin
// İyi: coroutineScope ile yapılandırılmış eşzamanlılık
suspend fun fetchUserWithPosts(userId: String): UserProfile =
coroutineScope {
val userDeferred = async { userService.getUser(userId) }
val postsDeferred = async { postService.getUserPosts(userId) }
UserProfile(
user = userDeferred.await(),
posts = postsDeferred.await(),
)
}
// İyi: child'lar bağımsız başarısız olabildiğinde supervisorScope
suspend fun fetchDashboard(userId: String): Dashboard =
supervisorScope {
val user = async { userService.getUser(userId) }
val notifications = async { notificationService.getRecent(userId) }
val recommendations = async { recommendationService.getFor(userId) }
Dashboard(
user = user.await(),
notifications = try {
notifications.await()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
emptyList()
},
recommendations = try {
recommendations.await()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
emptyList()
},
)
}
```
### Reactive Stream'ler İçin Flow
```kotlin
// İyi: Uygun hata işleme ile cold flow
fun observeUsers(): Flow<List<User>> = flow {
while (currentCoroutineContext().isActive) {
val users = userRepository.findAll()
emit(users)
delay(5.seconds)
}
}.catch { e ->
logger.error("Error observing users", e)
emit(emptyList())
}
// İyi: Flow operatörleri
fun searchUsers(query: Flow<String>): Flow<List<User>> =
query
.debounce(300.milliseconds)
.distinctUntilChanged()
.filter { it.length >= 2 }
.mapLatest { q -> userRepository.search(q) }
.catch { emit(emptyList()) }
```
## DSL Builder'lar
### Tip Güvenli Builder'lar
```kotlin
// İyi: @DslMarker ile DSL
@DslMarker
annotation class HtmlDsl
@HtmlDsl
class HTML {
private val children = mutableListOf<Element>()
fun head(init: Head.() -> Unit) {
children += Head().apply(init)
}
fun body(init: Body.() -> Unit) {
children += Body().apply(init)
}
override fun toString(): String = children.joinToString("\n")
}
fun html(init: HTML.() -> Unit): HTML = HTML().apply(init)
// Kullanım
val page = html {
head { title("My Page") }
body {
h1("Welcome")
p("Hello, World!")
}
}
```
## Gradle Kotlin DSL
### build.gradle.kts Yapılandırması
```kotlin
// En son versiyonları kontrol et: https://kotlinlang.org/docs/releases.html
plugins {
kotlin("jvm") version "2.3.10"
kotlin("plugin.serialization") version "2.3.10"
id("io.ktor.plugin") version "3.4.0"
id("org.jetbrains.kotlinx.kover") version "0.9.7"
id("io.gitlab.arturbosch.detekt") version "1.23.8"
}
group = "com.example"
version = "1.0.0"
kotlin {
jvmToolchain(21)
}
dependencies {
// Ktor
implementation("io.ktor:ktor-server-core:3.4.0")
implementation("io.ktor:ktor-server-netty:3.4.0")
implementation("io.ktor:ktor-server-content-negotiation:3.4.0")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.4.0")
// Exposed
implementation("org.jetbrains.exposed:exposed-core:1.0.0")
implementation("org.jetbrains.exposed:exposed-dao:1.0.0")
implementation("org.jetbrains.exposed:exposed-jdbc:1.0.0")
implementation("org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0")
// Koin
implementation("io.insert-koin:koin-ktor:4.2.0")
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
// Test
testImplementation("io.kotest:kotest-runner-junit5:6.1.4")
testImplementation("io.kotest:kotest-assertions-core:6.1.4")
testImplementation("io.kotest:kotest-property:6.1.4")
testImplementation("io.mockk:mockk:1.14.9")
testImplementation("io.ktor:ktor-server-test-host:3.4.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
}
tasks.withType<Test> {
useJUnitPlatform()
}
detekt {
config.setFrom(files("config/detekt/detekt.yml"))
buildUponDefaultConfig = true
}
```
## Hata İşleme Kalıpları
### Domain Operasyonları İçin Result Tipi
```kotlin
// İyi: Kotlin'in Result'ını veya özel sealed class kullan
suspend fun createUser(request: CreateUserRequest): Result<User> = runCatching {
require(request.name.isNotBlank()) { "Name cannot be blank" }
require('@' in request.email) { "Invalid email format" }
val user = User(
id = UserId(UUID.randomUUID().toString()),
name = request.name,
email = Email(request.email),
)
userRepository.save(user)
user
}
// İyi: Result'ları zincirle
val displayName = createUser(request)
.map { it.name }
.getOrElse { "Unknown" }
```
### require, check, error
```kotlin
// İyi: Net mesajlarla ön koşullar
fun withdraw(account: Account, amount: Money): Account {
require(amount.value > 0) { "Amount must be positive: $amount" }
check(account.balance >= amount) { "Insufficient balance: ${account.balance} < $amount" }
return account.copy(balance = account.balance - amount)
}
```
## Hızlı Referans: Kotlin İdiyomları
| İdiyom | Açıklama |
|-------|-------------|
| `val` over `var` | Immutable değişkenleri tercih et |
| `data class` | equals/hashCode/copy ile value objeler için |
| `sealed class/interface` | Kısıtlı tip hiyerarşileri için |
| `value class` | Sıfır maliyetli tip güvenli sarmalayıcılar için |
| Expression `when` | Exhaustive pattern matching |
| Safe call `?.` | Null-safe member erişimi |
| Elvis `?:` | Nullable'lar için varsayılan değer |
| `let`/`apply`/`also`/`run`/`with` | Temiz kod için scope fonksiyonlar |
| Extension fonksiyonlar | Inheritance olmadan davranış ekle |
| `copy()` | Data class'larda immutable güncellemeler |
| `require`/`check` | Ön koşul assertion'ları |
| Coroutine `async`/`await` | Yapılandırılmış concurrent execution |
| `Flow` | Cold reactive stream'ler |
| `sequence` | Lazy evaluation |
| Delegation `by` | Inheritance olmadan implementasyonu yeniden kullan |
## Kaçınılması Gereken Anti-Kalıplar
```kotlin
// Kötü: Nullable tipleri zorla açma
val name = user!!.name
// Kötü: Java'dan platform tipi sızıntısı
fun getLength(s: String) = s.length // Güvenli
fun getLength(s: String?) = s?.length ?: 0 // Java'dan null'ları işle
// Kötü: Mutable data class'lar
data class MutableUser(var name: String, var email: String)
// Kötü: Kontrol akışı için exception kullanma
try {
val user = findUser(id)
} catch (e: NotFoundException) {
// Beklenen durumlar için exception kullanma
}
// İyi: Nullable dönüş veya Result kullan
val user: User? = findUserOrNull(id)
// Kötü: Coroutine scope'u görmezden gelme
GlobalScope.launch { /* GlobalScope'tan kaçın */ }
// İyi: Yapılandırılmış eşzamanlılık kullan
coroutineScope {
launch { /* Uygun şekilde scope edilmiş */ }
}
// Kötü: Derin iç içe scope fonksiyonlar
user?.let { u ->
u.address?.let { a ->
a.city?.let { c -> process(c) }
}
}
// İyi: Doğrudan null-safe zincir
user?.address?.city?.let { process(it) }
```
**Hatırla**: Kotlin kodu kısa ama okunabilir olmalı. Güvenlik için tip sisteminden yararlanın, immutability tercih edin ve eşzamanlılık için coroutine'ler kullanın. Şüpheye düştüğünüzde, derleyicinin size yardım etmesine izin verin.

View File

@@ -0,0 +1,578 @@
---
name: kotlin-testing
description: Kotest, MockK, coroutine testi, property-based testing ve Kover coverage ile Kotlin test kalıpları. İdiomatic Kotlin uygulamalarıyla TDD metodolojisini takip eder.
origin: ECC
---
# Kotlin Test Kalıpları
Kotest ve MockK ile TDD metodolojisini takip ederek güvenilir, sürdürülebilir testler yazmak için kapsamlı Kotlin test kalıpları.
## Ne Zaman Kullanılır
- Yeni Kotlin fonksiyonları veya class'lar yazarken
- Mevcut Kotlin koduna test coverage eklerken
- Property-based testler uygularken
- Kotlin projelerinde TDD iş akışını takip ederken
- Kod coverage için Kover yapılandırırken
## Nasıl Çalışır
1. **Hedef kodu belirle** — Test edilecek fonksiyon, class veya modülü bul
2. **Kotest spec yaz** — Test scope'una uygun bir spec stili seç (StringSpec, FunSpec, BehaviorSpec)
3. **Bağımlılıkları mock'la** — Test edilen birimi izole etmek için MockK kullan
4. **Testleri çalıştır (RED)** — Testin beklenen hatayla başarısız olduğunu doğrula
5. **Kodu uygula (GREEN)** — Testi geçmek için minimal kod yaz
6. **Refactor** — Testleri yeşil tutarken implementasyonu iyileştir
7. **Coverage'ı kontrol et**`./gradlew koverHtmlReport` çalıştır ve %80+ coverage'ı doğrula
## TDD İş Akışı for Kotlin
### RED-GREEN-REFACTOR Döngüsü
```
RED -> Önce başarısız bir test yaz
GREEN -> Testi geçmek için minimal kod yaz
REFACTOR -> Testleri yeşil tutarken kodu iyileştir
REPEAT -> Sonraki gereksinimle devam et
```
### Kotlin'de Adım Adım TDD
```kotlin
// Adım 1: Interface/signature tanımla
// EmailValidator.kt
package com.example.validator
fun validateEmail(email: String): Result<String> {
TODO("not implemented")
}
// Adım 2: Başarısız test yaz (RED)
// EmailValidatorTest.kt
package com.example.validator
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.result.shouldBeFailure
import io.kotest.matchers.result.shouldBeSuccess
class EmailValidatorTest : StringSpec({
"valid email returns success" {
validateEmail("user@example.com").shouldBeSuccess("user@example.com")
}
"empty email returns failure" {
validateEmail("").shouldBeFailure()
}
"email without @ returns failure" {
validateEmail("userexample.com").shouldBeFailure()
}
})
// Adım 3: Testleri çalıştır - FAIL doğrula
// $ ./gradlew test
// EmailValidatorTest > valid email returns success FAILED
// kotlin.NotImplementedError: An operation is not implemented
// Adım 4: Minimal kodu uygula (GREEN)
fun validateEmail(email: String): Result<String> {
if (email.isBlank()) return Result.failure(IllegalArgumentException("Email cannot be blank"))
if ('@' !in email) return Result.failure(IllegalArgumentException("Email must contain @"))
val regex = Regex("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
if (!regex.matches(email)) return Result.failure(IllegalArgumentException("Invalid email format"))
return Result.success(email)
}
// Adım 5: Testleri çalıştır - PASS doğrula
// $ ./gradlew test
// EmailValidatorTest > valid email returns success PASSED
// EmailValidatorTest > empty email returns failure PASSED
// EmailValidatorTest > email without @ returns failure PASSED
// Adım 6: Gerekirse refactor et, testlerin hala geçtiğini doğrula
```
## Kotest Spec Stilleri
### StringSpec (En Basit)
```kotlin
class CalculatorTest : StringSpec({
"add two positive numbers" {
Calculator.add(2, 3) shouldBe 5
}
"add negative numbers" {
Calculator.add(-1, -2) shouldBe -3
}
"add zero" {
Calculator.add(0, 5) shouldBe 5
}
})
```
### FunSpec (JUnit benzeri)
```kotlin
class UserServiceTest : FunSpec({
val repository = mockk<UserRepository>()
val service = UserService(repository)
test("getUser returns user when found") {
val expected = User(id = "1", name = "Alice")
coEvery { repository.findById("1") } returns expected
val result = service.getUser("1")
result shouldBe expected
}
test("getUser throws when not found") {
coEvery { repository.findById("999") } returns null
shouldThrow<UserNotFoundException> {
service.getUser("999")
}
}
})
```
### BehaviorSpec (BDD Stili)
```kotlin
class OrderServiceTest : BehaviorSpec({
val repository = mockk<OrderRepository>()
val paymentService = mockk<PaymentService>()
val service = OrderService(repository, paymentService)
Given("a valid order request") {
val request = CreateOrderRequest(
userId = "user-1",
items = listOf(OrderItem("product-1", quantity = 2)),
)
When("the order is placed") {
coEvery { paymentService.charge(any()) } returns PaymentResult.Success
coEvery { repository.save(any()) } answers { firstArg() }
val result = service.placeOrder(request)
Then("it should return a confirmed order") {
result.status shouldBe OrderStatus.CONFIRMED
}
Then("it should charge payment") {
coVerify(exactly = 1) { paymentService.charge(any()) }
}
}
When("payment fails") {
coEvery { paymentService.charge(any()) } returns PaymentResult.Declined
Then("it should throw PaymentException") {
shouldThrow<PaymentException> {
service.placeOrder(request)
}
}
}
}
})
```
## Kotest Matcher'lar
### Temel Matcher'lar
```kotlin
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.*
import io.kotest.matchers.collections.*
import io.kotest.matchers.nulls.*
// Eşitlik
result shouldBe expected
result shouldNotBe unexpected
// String'ler
name shouldStartWith "Al"
name shouldEndWith "ice"
name shouldContain "lic"
name shouldMatch Regex("[A-Z][a-z]+")
name.shouldBeBlank()
// Koleksiyonlar
list shouldContain "item"
list shouldHaveSize 3
list.shouldBeSorted()
list.shouldContainAll("a", "b", "c")
list.shouldBeEmpty()
// Null'lar
result.shouldNotBeNull()
result.shouldBeNull()
// Tipler
result.shouldBeInstanceOf<User>()
// Sayılar
count shouldBeGreaterThan 0
price shouldBeInRange 1.0..100.0
// Exception'lar
shouldThrow<IllegalArgumentException> {
validateAge(-1)
}.message shouldBe "Age must be positive"
shouldNotThrow<Exception> {
validateAge(25)
}
```
## MockK
### Temel Mocking
```kotlin
class UserServiceTest : FunSpec({
val repository = mockk<UserRepository>()
val logger = mockk<Logger>(relaxed = true) // Relaxed: varsayılanları döndürür
val service = UserService(repository, logger)
beforeTest {
clearMocks(repository, logger)
}
test("findUser delegates to repository") {
val expected = User(id = "1", name = "Alice")
every { repository.findById("1") } returns expected
val result = service.findUser("1")
result shouldBe expected
verify(exactly = 1) { repository.findById("1") }
}
test("findUser returns null for unknown id") {
every { repository.findById(any()) } returns null
val result = service.findUser("unknown")
result.shouldBeNull()
}
})
```
### Coroutine Mocking
```kotlin
class AsyncUserServiceTest : FunSpec({
val repository = mockk<UserRepository>()
val service = UserService(repository)
test("getUser suspending function") {
coEvery { repository.findById("1") } returns User(id = "1", name = "Alice")
val result = service.getUser("1")
result.name shouldBe "Alice"
coVerify { repository.findById("1") }
}
test("getUser with delay") {
coEvery { repository.findById("1") } coAnswers {
delay(100) // Async çalışmayı simüle et
User(id = "1", name = "Alice")
}
val result = service.getUser("1")
result.name shouldBe "Alice"
}
})
```
## Coroutine Testi
### Suspend Fonksiyonlar İçin runTest
```kotlin
import kotlinx.coroutines.test.runTest
class CoroutineServiceTest : FunSpec({
test("concurrent fetches complete together") {
runTest {
val service = DataService(testScope = this)
val result = service.fetchAllData()
result.users.shouldNotBeEmpty()
result.products.shouldNotBeEmpty()
}
}
test("timeout after delay") {
runTest {
val service = SlowService()
shouldThrow<TimeoutCancellationException> {
withTimeout(100) {
service.slowOperation() // > 100ms sürer
}
}
}
}
})
```
### Flow Testi
```kotlin
import io.kotest.matchers.collections.shouldContainInOrder
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
class FlowServiceTest : FunSpec({
test("observeUsers emits updates") {
runTest {
val service = UserFlowService()
val emissions = service.observeUsers()
.take(3)
.toList()
emissions shouldHaveSize 3
emissions.last().shouldNotBeEmpty()
}
}
test("searchUsers debounces input") {
runTest {
val service = SearchService()
val queries = MutableSharedFlow<String>()
val results = mutableListOf<List<User>>()
val job = launch {
service.searchUsers(queries).collect { results.add(it) }
}
queries.emit("a")
queries.emit("ab")
queries.emit("abc") // Sadece bu aramayı tetiklemeli
advanceTimeBy(500)
results shouldHaveSize 1
job.cancel()
}
}
})
```
## Property-Based Testing
### Kotest Property Testing
```kotlin
import io.kotest.core.spec.style.FunSpec
import io.kotest.property.Arb
import io.kotest.property.arbitrary.*
import io.kotest.property.forAll
import io.kotest.property.checkAll
class PropertyTest : FunSpec({
test("string reverse is involutory") {
forAll<String> { s ->
s.reversed().reversed() == s
}
}
test("list sort is idempotent") {
forAll(Arb.list(Arb.int())) { list ->
list.sorted() == list.sorted().sorted()
}
}
test("serialization roundtrip preserves data") {
checkAll(Arb.bind(Arb.string(1..50), Arb.string(5..100)) { name, email ->
User(name = name, email = "$email@test.com")
}) { user ->
val json = Json.encodeToString(user)
val decoded = Json.decodeFromString<User>(json)
decoded shouldBe user
}
}
})
```
## Kover Coverage
### Gradle Yapılandırması
```kotlin
// build.gradle.kts
plugins {
id("org.jetbrains.kotlinx.kover") version "0.9.7"
}
kover {
reports {
total {
html { onCheck = true }
xml { onCheck = true }
}
filters {
excludes {
classes("*.generated.*", "*.config.*")
}
}
verify {
rule {
minBound(80) // %80 coverage'ın altında build başarısız
}
}
}
}
```
### Coverage Komutları
```bash
# Testleri coverage ile çalıştır
./gradlew koverHtmlReport
# Coverage eşiklerini doğrula
./gradlew koverVerify
# CI için XML raporu
./gradlew koverXmlReport
# HTML raporunu görüntüle (OS'nize göre komutu kullanın)
# macOS: open build/reports/kover/html/index.html
# Linux: xdg-open build/reports/kover/html/index.html
# Windows: start build/reports/kover/html/index.html
```
### Coverage Hedefleri
| Kod Tipi | Hedef |
|-----------|--------|
| Kritik business mantığı | %100 |
| Public API'ler | %90+ |
| Genel kod | %80+ |
| Generated / config kodu | Hariç tut |
## Ktor testApplication Testi
```kotlin
class ApiRoutesTest : FunSpec({
test("GET /users returns list") {
testApplication {
application {
configureRouting()
configureSerialization()
}
val response = client.get("/users")
response.status shouldBe HttpStatusCode.OK
val users = response.body<List<UserResponse>>()
users.shouldNotBeEmpty()
}
}
test("POST /users creates user") {
testApplication {
application {
configureRouting()
configureSerialization()
}
val response = client.post("/users") {
contentType(ContentType.Application.Json)
setBody(CreateUserRequest("Alice", "alice@example.com"))
}
response.status shouldBe HttpStatusCode.Created
}
}
})
```
## Test Komutları
```bash
# Tüm testleri çalıştır
./gradlew test
# Belirli test class'ını çalıştır
./gradlew test --tests "com.example.UserServiceTest"
# Belirli testi çalıştır
./gradlew test --tests "com.example.UserServiceTest.getUser returns user when found"
# Verbose çıktı ile çalıştır
./gradlew test --info
# Coverage ile çalıştır
./gradlew koverHtmlReport
# Detekt çalıştır (statik analiz)
./gradlew detekt
# Ktlint çalıştır (formatlama kontrolü)
./gradlew ktlintCheck
# Sürekli test
./gradlew test --continuous
```
## En İyi Uygulamalar
**YAPILMASI GEREKENLER:**
- ÖNCE testleri yaz (TDD)
- Proje genelinde Kotest'in spec stillerini tutarlı kullan
- Suspend fonksiyonlar için MockK'nın `coEvery`/`coVerify`'ını kullan
- Coroutine testi için `runTest` kullan
- İmplementasyon değil davranışı test et
- Pure fonksiyonlar için property-based testing kullan
- Netlik için `data class` test fixture'ları kullan
**YAPILMAMASI GEREKENLER:**
- Test framework'lerini karıştırma (Kotest seç ve ona sadık kal)
- Data class'ları mock'lama (gerçek instance'lar kullan)
- Coroutine testlerinde `Thread.sleep()` kullanma (`advanceTimeBy` kullan)
- TDD'de RED fazını atlama
- Private fonksiyonları doğrudan test etme
- Kararsız testleri görmezden gelme
## CI/CD ile Entegrasyon
```yaml
# GitHub Actions örneği
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Run tests with coverage
run: ./gradlew test koverXmlReport
- name: Verify coverage
run: ./gradlew koverVerify
- name: Upload coverage
uses: codecov/codecov-action@v5
with:
files: build/reports/kover/report.xml
token: ${{ secrets.CODECOV_TOKEN }}
```
**Hatırla**: Testler dokümantasyondur. Kotlin kodunuzun nasıl kullanılması gerektiğini gösterirler. Testleri okunabilir yapmak için Kotest'in açıklayıcı matcher'larını ve bağımlılıkları temiz mock'lamak için MockK kullanın.

View File

@@ -0,0 +1,415 @@
---
name: laravel-patterns
description: Laravel architecture patterns, routing/controllers, Eloquent ORM, service layers, queues, events, caching, and API resources for production apps.
origin: ECC
---
# Laravel Geliştirme Desenleri
Ölçeklenebilir, bakım yapılabilir uygulamalar için üretim seviyesi Laravel mimari desenleri.
## Ne Zaman Kullanılır
- Laravel web uygulamaları veya API'ler oluşturma
- Controller'lar, servisler ve domain mantığını yapılandırma
- Eloquent model'ler ve ilişkiler ile çalışma
- Resource'lar ve sayfalama ile API tasarlama
- Kuyruklar, event'ler, caching ve arka plan işleri ekleme
## Nasıl Çalışır
- Uygulamayı net sınırlar etrafında yapılandırın (controller'lar -> servisler/action'lar -> model'ler).
- Routing'i öngörülebilir tutmak için açık binding'ler ve scoped binding'ler kullanın; erişim kontrolü için yetkilendirmeyi yine de uygulayın.
- Domain mantığını tutarlı tutmak için typed model'leri, cast'leri ve scope'ları tercih edin.
- IO-ağır işleri kuyruklarda tutun ve pahalı okumaları önbelleğe alın.
- Config'i `config/*` içinde merkezileştirin ve ortamlarıık tutun.
## Örnekler
### Proje Yapısı
Net katman sınırları (HTTP, servisler/action'lar, model'ler) ile geleneksel bir Laravel düzeni kullanın.
### Önerilen Düzen
```
app/
├── Actions/ # Tek amaçlı kullanım durumları
├── Console/
├── Events/
├── Exceptions/
├── Http/
│ ├── Controllers/
│ ├── Middleware/
│ ├── Requests/ # Form request validation
│ └── Resources/ # API resources
├── Jobs/
├── Models/
├── Policies/
├── Providers/
├── Services/ # Domain servislerini koordine etme
└── Support/
config/
database/
├── factories/
├── migrations/
└── seeders/
resources/
├── views/
└── lang/
routes/
├── api.php
├── web.php
└── console.php
```
### Controllers -> Services -> Actions
Controller'ları ince tutun. Orkestrasyon'u servislere ve tek amaçlı mantığı action'lara koyun.
```php
final class CreateOrderAction
{
public function __construct(private OrderRepository $orders) {}
public function handle(CreateOrderData $data): Order
{
return $this->orders->create($data);
}
}
final class OrdersController extends Controller
{
public function __construct(private CreateOrderAction $createOrder) {}
public function store(StoreOrderRequest $request): JsonResponse
{
$order = $this->createOrder->handle($request->toDto());
return response()->json([
'success' => true,
'data' => OrderResource::make($order),
'error' => null,
'meta' => null,
], 201);
}
}
```
### Routing ve Controllers
Netlik için route-model binding ve resource controller'ları tercih edin.
```php
use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('projects', ProjectController::class);
});
```
### Route Model Binding (Scoped)
Çapraz kiracı erişimini önlemek için scoped binding'leri kullanın.
```php
Route::scopeBindings()->group(function () {
Route::get('/accounts/{account}/projects/{project}', [ProjectController::class, 'show']);
});
```
### İç İçe Route'lar ve Binding İsimleri
- Çift iç içe geçmeyi önlemek için prefix'leri ve path'leri tutarlı tutun (örn. `conversation` vs `conversations`).
- Bound model'e uyan tek bir parametre ismi kullanın (örn. `Conversation` için `{conversation}`).
- İç içe geçirirken üst-alt ilişkilerini zorlamak için scoped binding'leri tercih edin.
```php
use App\Http\Controllers\Api\ConversationController;
use App\Http\Controllers\Api\MessageController;
use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->prefix('conversations')->group(function () {
Route::post('/', [ConversationController::class, 'store'])->name('conversations.store');
Route::scopeBindings()->group(function () {
Route::get('/{conversation}', [ConversationController::class, 'show'])
->name('conversations.show');
Route::post('/{conversation}/messages', [MessageController::class, 'store'])
->name('conversation-messages.store');
Route::get('/{conversation}/messages/{message}', [MessageController::class, 'show'])
->name('conversation-messages.show');
});
});
```
Bir parametrenin farklı bir model sınıfına çözümlenmesini istiyorsanız, açık binding tanımlayın. Özel binding mantığı için `Route::bind()` kullanın veya model'de `resolveRouteBinding()` uygulayın.
```php
use App\Models\AiConversation;
use Illuminate\Support\Facades\Route;
Route::model('conversation', AiConversation::class);
```
### Service Container Binding'leri
Net bağımlılık bağlantısı için bir service provider'da interface'leri implementasyonlara bağlayın.
```php
use App\Repositories\EloquentOrderRepository;
use App\Repositories\OrderRepository;
use Illuminate\Support\ServiceProvider;
final class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(OrderRepository::class, EloquentOrderRepository::class);
}
}
```
### Eloquent Model Desenleri
### Model Yapılandırması
```php
final class Project extends Model
{
use HasFactory;
protected $fillable = ['name', 'owner_id', 'status'];
protected $casts = [
'status' => ProjectStatus::class,
'archived_at' => 'datetime',
];
public function owner(): BelongsTo
{
return $this->belongsTo(User::class, 'owner_id');
}
public function scopeActive(Builder $query): Builder
{
return $query->whereNull('archived_at');
}
}
```
### Özel Cast'ler ve Value Object'ler
Sıkı tiplemeler için enum'lar veya value object'leri kullanın.
```php
use Illuminate\Database\Eloquent\Casts\Attribute;
protected $casts = [
'status' => ProjectStatus::class,
];
```
```php
protected function budgetCents(): Attribute
{
return Attribute::make(
get: fn (int $value) => Money::fromCents($value),
set: fn (Money $money) => $money->toCents(),
);
}
```
### N+1'i Önlemek için Eager Loading
```php
$orders = Order::query()
->with(['customer', 'items.product'])
->latest()
->paginate(25);
```
### Karmaşık Filtreler için Query Object'leri
```php
final class ProjectQuery
{
public function __construct(private Builder $query) {}
public function ownedBy(int $userId): self
{
$query = clone $this->query;
return new self($query->where('owner_id', $userId));
}
public function active(): self
{
$query = clone $this->query;
return new self($query->whereNull('archived_at'));
}
public function builder(): Builder
{
return $this->query;
}
}
```
### Global Scope'lar ve Soft Delete'ler
Varsayılan filtreleme için global scope'ları ve geri kurtarılabilir kayıtlar için `SoftDeletes` kullanın.
Katmanlı davranış istemediğiniz sürece, aynı filtre için global scope veya named scope kullanın, ikisini birden değil.
```php
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Builder;
final class Project extends Model
{
use SoftDeletes;
protected static function booted(): void
{
static::addGlobalScope('active', function (Builder $builder): void {
$builder->whereNull('archived_at');
});
}
}
```
### Yeniden Kullanılabilir Filtreler için Query Scope'ları
```php
use Illuminate\Database\Eloquent\Builder;
final class Project extends Model
{
public function scopeOwnedBy(Builder $query, int $userId): Builder
{
return $query->where('owner_id', $userId);
}
}
// Servis, repository vb. içinde
$projects = Project::ownedBy($user->id)->get();
```
### Çok Adımlı Güncellemeler için Transaction'lar
```php
use Illuminate\Support\Facades\DB;
DB::transaction(function (): void {
$order->update(['status' => 'paid']);
$order->items()->update(['paid_at' => now()]);
});
```
### Migration'lar
### İsimlendirme Kuralı
- Dosya isimleri zaman damgası kullanır: `YYYY_MM_DD_HHMMSS_create_users_table.php`
- Migration'lar anonim sınıflar kullanır (isimlendirilmiş sınıf yok); dosya ismi amacı iletir
- Tablo isimleri varsayılan olarak `snake_case` ve çoğuldur
### Örnek Migration
```php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('orders', function (Blueprint $table): void {
$table->id();
$table->foreignId('customer_id')->constrained()->cascadeOnDelete();
$table->string('status', 32)->index();
$table->unsignedInteger('total_cents');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('orders');
}
};
```
### Form Request'ler ve Validation
Validation'ı form request'lerde tutun ve input'ları DTO'lara dönüştürün.
```php
use App\Models\Order;
final class StoreOrderRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()?->can('create', Order::class) ?? false;
}
public function rules(): array
{
return [
'customer_id' => ['required', 'integer', 'exists:customers,id'],
'items' => ['required', 'array', 'min:1'],
'items.*.sku' => ['required', 'string'],
'items.*.quantity' => ['required', 'integer', 'min:1'],
];
}
public function toDto(): CreateOrderData
{
return new CreateOrderData(
customerId: (int) $this->validated('customer_id'),
items: $this->validated('items'),
);
}
}
```
### API Resource'ları
Resource'lar ve sayfalama ile API yanıtlarını tutarlı tutun.
```php
$projects = Project::query()->active()->paginate(25);
return response()->json([
'success' => true,
'data' => ProjectResource::collection($projects->items()),
'error' => null,
'meta' => [
'page' => $projects->currentPage(),
'per_page' => $projects->perPage(),
'total' => $projects->total(),
],
]);
```
### Event'ler, Job'lar ve Kuyruklar
- Yan etkiler için domain event'leri yayınlayın (email'ler, analytics)
- Yavaş işler için kuyruğa alınmış job'ları kullanın (raporlar, export'lar, webhook'lar)
- Yeniden deneme ve backoff ile idempotent handler'ları tercih edin
### Caching
- Okuma-ağırlıklı endpoint'leri ve pahalı sorguları önbelleğe alın
- Model event'lerinde (created/updated/deleted) önbellekleri geçersiz kılın
- Kolay geçersiz kılma için ilgili verileri önbelleğe alırken tag'leri kullanın
### Yapılandırma ve Ortamlar
- Gizli bilgileri `.env`'de ve yapılandırmayı `config/*.php`'de tutun
- Ortama özel yapılandırma geçersiz kılmaları kullanın ve production'da `config:cache` kullanın

View File

@@ -0,0 +1,285 @@
---
name: laravel-security
description: Laravel security best practices for authn/authz, validation, CSRF, mass assignment, file uploads, secrets, rate limiting, and secure deployment.
origin: ECC
---
# Laravel Güvenlik En İyi Uygulamaları
Laravel uygulamalarını yaygın güvenlik açıklarına karşı korumak için kapsamlı güvenlik rehberi.
## Ne Zaman Aktif Edilir
- Kimlik doğrulama veya yetkilendirme ekleme
- Kullanıcı girişi ve dosya yüklemelerini işleme
- Yeni API endpoint'leri oluşturma
- Gizli bilgileri ve ortam ayarlarını yönetme
- Production deployment'ları sertleştirme
## Nasıl Çalışır
- Middleware temel korumalar sağlar (CSRF için `VerifyCsrfToken`, güvenlik başlıkları için `SecurityHeaders`).
- Guard'lar ve policy'ler erişim kontrolünü zorlar (`auth:sanctum`, `$this->authorize`, policy middleware).
- Form Request'ler servislere ulaşmadan önce girişi doğrular ve şekillendirir (`UploadInvoiceRequest`).
- Rate limiting, auth kontrolleri ile birlikte kötüye kullanım koruması ekler (`RateLimiter::for('login')`).
- Veri güvenliği encrypted cast'lerden, mass-assignment korumalarından ve signed route'lardan gelir (`URL::temporarySignedRoute` + `signed` middleware).
## Temel Güvenlik Ayarları
- Production'da `APP_DEBUG=false`
- `APP_KEY` ayarlanmalı ve tehlikeye girdiğinde döndürülmelidir
- `SESSION_SECURE_COOKIE=true` ve `SESSION_SAME_SITE=lax` ayarlayın (veya hassas uygulamalar için `strict`)
- Doğru HTTPS algılama için güvenilir proxy'leri yapılandırın
## Session ve Cookie Sertleştirme
- JavaScript erişimini önlemek için `SESSION_HTTP_ONLY=true` ayarlayın
- Yüksek riskli akışlar için `SESSION_SAME_SITE=strict` kullanın
- Login ve ayrıcalık değişikliklerinde session'ları yeniden oluşturun
## Kimlik Doğrulama ve Token'lar
- API kimlik doğrulama için Laravel Sanctum veya Passport kullanın
- Hassas veriler için yenileme akışları ile kısa ömürlü token'ları tercih edin
- Logout ve tehlikeye girmiş hesaplarda token'ları iptal edin
Örnek route koruması:
```php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->get('/me', function (Request $request) {
return $request->user();
});
```
## Parola Güvenliği
- `Hash::make()` ile parolaları hash'leyin ve asla düz metin saklamayın
- Sıfırlama akışları için Laravel'in password broker'ını kullanın
```php
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
$validated = $request->validate([
'password' => ['required', 'string', Password::min(12)->letters()->mixedCase()->numbers()->symbols()],
]);
$user->update(['password' => Hash::make($validated['password'])]);
```
## Yetkilendirme: Policy'ler ve Gate'ler
- Model seviyesi yetkilendirme için policy'leri kullanın
- Controller'larda ve servislerde yetkilendirmeyi zorlayın
```php
$this->authorize('update', $project);
```
Route seviyesi zorlama için policy middleware kullanın:
```php
use Illuminate\Support\Facades\Route;
Route::put('/projects/{project}', [ProjectController::class, 'update'])
->middleware(['auth:sanctum', 'can:update,project']);
```
## Validation ve Veri Temizleme
- Her zaman Form Request'ler ile girişleri doğrulayın
- Sıkı validation kuralları ve tip kontrolleri kullanın
- Türetilmiş alanlar için request payload'larına asla güvenmeyin
## Mass Assignment Koruması
- `$fillable` veya `$guarded` kullanın ve `Model::unguard()` kullanmaktan kaçının
- DTO'ları veya açık attribute mapping'i tercih edin
## SQL Injection Önleme
- Eloquent veya query builder parametre binding kullanın
- Kesinlikle gerekli olmadıkça raw SQL kullanmaktan kaçının
```php
DB::select('select * from users where email = ?', [$email]);
```
## XSS Önleme
- Blade varsayılan olarak çıktıyı escape eder (`{{ }}`)
- `{!! !!}` sadece güvenilir, temizlenmiş HTML için kullanın
- Zengin metni özel bir kütüphane ile temizleyin
## CSRF Koruması
- `VerifyCsrfToken` middleware'ini etkin tutun
- Formlara `@csrf` ekleyin ve SPA istekleri için XSRF token'ları gönderin
Sanctum ile SPA kimlik doğrulaması için, stateful isteklerin yapılandırıldığından emin olun:
```php
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost')),
```
## Dosya Yükleme Güvenliği
- Dosya boyutunu, MIME tipini ve uzantısını doğrulayın
- Mümkün olduğunda yüklemeleri public path dışında saklayın
- Gerekirse dosyaları malware için tarayın
```php
final class UploadInvoiceRequest extends FormRequest
{
public function authorize(): bool
{
return (bool) $this->user()?->can('upload-invoice');
}
public function rules(): array
{
return [
'invoice' => ['required', 'file', 'mimes:pdf', 'max:5120'],
];
}
}
```
```php
$path = $request->file('invoice')->store(
'invoices',
config('filesystems.private_disk', 'local') // bunu public olmayan bir disk'e ayarlayın
);
```
## Rate Limiting
- Auth ve yazma endpoint'lerinde `throttle` middleware'i uygulayın
- Login, password reset ve OTP için daha sıkı limitler kullanın
```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('login', function (Request $request) {
return [
Limit::perMinute(5)->by($request->ip()),
Limit::perMinute(5)->by(strtolower((string) $request->input('email'))),
];
});
```
## Gizli Bilgiler ve Kimlik Bilgileri
- Gizli bilgileri asla kaynak kontrolüne commit etmeyin
- Ortam değişkenlerini ve gizli yöneticileri kullanın
- Maruz kalma sonrası anahtarları döndürün ve session'ları geçersiz kılın
## Şifreli Attribute'lar
Bekleyen hassas sütunlar için encrypted cast'leri kullanın.
```php
protected $casts = [
'api_token' => 'encrypted',
];
```
## Güvenlik Başlıkları
- Uygun yerlerde CSP, HSTS ve frame koruması ekleyin
- HTTPS yönlendirmelerini zorlamak için güvenilir proxy yapılandırması kullanın
Başlıkları ayarlamak için örnek middleware:
```php
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
final class SecurityHeaders
{
public function handle(Request $request, \Closure $next): Response
{
$response = $next($request);
$response->headers->add([
'Content-Security-Policy' => "default-src 'self'",
'Strict-Transport-Security' => 'max-age=31536000', // tüm subdomain'ler HTTPS olduğunda includeSubDomains/preload ekleyin
'X-Frame-Options' => 'DENY',
'X-Content-Type-Options' => 'nosniff',
'Referrer-Policy' => 'no-referrer',
]);
return $response;
}
}
```
## CORS ve API Erişimi
- `config/cors.php`'de origin'leri kısıtlayın
- Kimlik doğrulamalı route'lar için wildcard origin'lerden kaçının
```php
// config/cors.php
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
'allowed_origins' => ['https://app.example.com'],
'allowed_headers' => [
'Content-Type',
'Authorization',
'X-Requested-With',
'X-XSRF-TOKEN',
'X-CSRF-TOKEN',
],
'supports_credentials' => true,
];
```
## Loglama ve PII
- Parolaları, token'ları veya tam kart verilerini asla loglamayın
- Yapılandırılmış loglarda hassas alanları redakte edin
```php
use Illuminate\Support\Facades\Log;
Log::info('User updated profile', [
'user_id' => $user->id,
'email' => '[REDACTED]',
'token' => '[REDACTED]',
]);
```
## Bağımlılık Güvenliği
- Düzenli olarak `composer audit` çalıştırın
- Bağımlılıkları dikkatle sabitleyin ve CVE'lerde hızlıca güncelleyin
## Signed URL'ler
Geçici, kurcalamaya dayanıklı bağlantılar için signed route'ları kullanın.
```php
use Illuminate\Support\Facades\URL;
$url = URL::temporarySignedRoute(
'downloads.invoice',
now()->addMinutes(15),
['invoice' => $invoice->id]
);
```
```php
use Illuminate\Support\Facades\Route;
Route::get('/invoices/{invoice}/download', [InvoiceController::class, 'download'])
->name('downloads.invoice')
->middleware('signed');
```

View File

@@ -0,0 +1,283 @@
---
name: laravel-tdd
description: Test-driven development for Laravel with PHPUnit and Pest, factories, database testing, fakes, and coverage targets.
origin: ECC
---
# Laravel TDD İş Akışı
80%+ kapsam (unit + feature) ile Laravel uygulamaları için test-driven development.
## Ne Zaman Kullanılır
- Laravel'de yeni özellikler veya endpoint'ler
- Bug düzeltmeleri veya refactoring'ler
- Eloquent model'leri, policy'leri, job'ları ve notification'ları test etme
- Proje zaten PHPUnit'te standartlaşmamışsa yeni testler için Pest'i tercih edin
## Nasıl Çalışır
### Red-Green-Refactor Döngüsü
1) Başarısız bir test yazın
2) Geçmek için minimal değişiklik uygulayın
3) Testleri yeşil tutarken refactor edin
### Test Katmanları
- **Unit**: saf PHP sınıfları, value object'leri, servisler
- **Feature**: HTTP endpoint'leri, auth, validation, policy'ler
- **Integration**: database + kuyruk + harici sınırlar
Kapsama göre katmanları seçin:
- Saf iş mantığı ve servisler için **Unit** testleri kullanın.
- HTTP, auth, validation ve yanıt şekli için **Feature** testleri kullanın.
- DB/kuyruklar/harici servisleri birlikte doğrularken **Integration** testleri kullanın.
### Database Stratejisi
- Çoğu feature/integration testi için `RefreshDatabase` (test run'ı başına bir kez migration'ları çalıştırır, ardından desteklendiğinde her testi bir transaction'a sarar; in-memory veritabanları test başına yeniden migrate edebilir)
- Şema zaten migrate edilmişse ve sadece test başına rollback'e ihtiyacınız varsa `DatabaseTransactions`
- Her test için tam bir migrate/fresh'e ihtiyacınız varsa ve maliyetini karşılayabiliyorsanız `DatabaseMigrations`
Veritabanına dokunan testler için varsayılan olarak `RefreshDatabase` kullanın: transaction desteği olan veritabanları için, test run'ı başına bir kez (static bir bayrak aracılığıyla) migration'ları çalıştırır ve her testi bir transaction'a sarar; `:memory:` SQLite veya transaction'sız bağlantılar için her testten önce migrate eder. Şema zaten migrate edilmişse ve sadece test başına rollback'lere ihtiyacınız varsa `DatabaseTransactions` kullanın.
### Test Framework Seçimi
- Mevcut olduğunda yeni testler için varsayılan olarak **Pest** kullanın.
- Proje zaten PHPUnit'te standartlaşmışsa veya PHPUnit'e özgü araçlar gerektiriyorsa sadece **PHPUnit** kullanın.
## Örnekler
### PHPUnit Örneği
```php
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
final class ProjectControllerTest extends TestCase
{
use RefreshDatabase;
public function test_owner_can_create_project(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->postJson('/api/projects', [
'name' => 'New Project',
]);
$response->assertCreated();
$this->assertDatabaseHas('projects', ['name' => 'New Project']);
}
}
```
### Feature Test Örneği (HTTP Katmanı)
```php
use App\Models\Project;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
final class ProjectIndexTest extends TestCase
{
use RefreshDatabase;
public function test_projects_index_returns_paginated_results(): void
{
$user = User::factory()->create();
Project::factory()->count(3)->for($user)->create();
$response = $this->actingAs($user)->getJson('/api/projects');
$response->assertOk();
$response->assertJsonStructure(['success', 'data', 'error', 'meta']);
}
}
```
### Pest Örneği
```php
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\actingAs;
use function Pest\Laravel\assertDatabaseHas;
uses(RefreshDatabase::class);
test('owner can create project', function () {
$user = User::factory()->create();
$response = actingAs($user)->postJson('/api/projects', [
'name' => 'New Project',
]);
$response->assertCreated();
assertDatabaseHas('projects', ['name' => 'New Project']);
});
```
### Feature Test Pest Örneği (HTTP Katmanı)
```php
use App\Models\Project;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\actingAs;
uses(RefreshDatabase::class);
test('projects index returns paginated results', function () {
$user = User::factory()->create();
Project::factory()->count(3)->for($user)->create();
$response = actingAs($user)->getJson('/api/projects');
$response->assertOk();
$response->assertJsonStructure(['success', 'data', 'error', 'meta']);
});
```
### Factory'ler ve State'ler
- Test verileri için factory'leri kullanın
- Uç durumlar için state'leri tanımlayın (archived, admin, trial)
```php
$user = User::factory()->state(['role' => 'admin'])->create();
```
### Database Testi
- Temiz durum için `RefreshDatabase` kullanın
- Testleri izole ve deterministik tutun
- Manuel sorgular yerine `assertDatabaseHas` tercih edin
### Persistence Test Örneği
```php
use App\Models\Project;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
final class ProjectRepositoryTest extends TestCase
{
use RefreshDatabase;
public function test_project_can_be_retrieved_by_slug(): void
{
$project = Project::factory()->create(['slug' => 'alpha']);
$found = Project::query()->where('slug', 'alpha')->firstOrFail();
$this->assertSame($project->id, $found->id);
}
}
```
### Yan Etkiler için Fake'ler
- Job'lar için `Bus::fake()`
- Kuyruğa alınmış işler için `Queue::fake()`
- Bildirimler için `Mail::fake()` ve `Notification::fake()`
- Domain event'leri için `Event::fake()`
```php
use Illuminate\Support\Facades\Queue;
Queue::fake();
dispatch(new SendOrderConfirmation($order->id));
Queue::assertPushed(SendOrderConfirmation::class);
```
```php
use Illuminate\Support\Facades\Notification;
Notification::fake();
$user->notify(new InvoiceReady($invoice));
Notification::assertSentTo($user, InvoiceReady::class);
```
### Auth Testi (Sanctum)
```php
use Laravel\Sanctum\Sanctum;
Sanctum::actingAs($user);
$response = $this->getJson('/api/projects');
$response->assertOk();
```
### HTTP ve Harici Servisler
- Harici API'leri izole etmek için `Http::fake()` kullanın
- Giden payload'ları `Http::assertSent()` ile doğrulayın
### Kapsam Hedefleri
- Unit + feature testleri için 80%+ kapsam zorlayın
- CI'da `pcov` veya `XDEBUG_MODE=coverage` kullanın
### Test Komutları
- `php artisan test`
- `vendor/bin/phpunit`
- `vendor/bin/pest`
### Test Yapılandırması
- Hızlı testler için `phpunit.xml`'de `DB_CONNECTION=sqlite` ve `DB_DATABASE=:memory:` ayarlayın
- Dev/prod verilerine dokunmaktan kaçınmak için testler için ayrı env tutun
### Yetkilendirme Testleri
```php
use Illuminate\Support\Facades\Gate;
$this->assertTrue(Gate::forUser($user)->allows('update', $project));
$this->assertFalse(Gate::forUser($otherUser)->allows('update', $project));
```
### Inertia Feature Testleri
Inertia.js kullanırken, Inertia test yardımcıları ile component ismi ve prop'ları doğrulayın.
```php
use App\Models\User;
use Inertia\Testing\AssertableInertia;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
final class DashboardInertiaTest extends TestCase
{
use RefreshDatabase;
public function test_dashboard_inertia_props(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->get('/dashboard');
$response->assertOk();
$response->assertInertia(fn (AssertableInertia $page) => $page
->component('Dashboard')
->where('user.id', $user->id)
->has('projects')
);
}
}
```
Testleri Inertia yanıtlarıyla uyumlu tutmak için ham JSON assertion'ları yerine `assertInertia` tercih edin.

View File

@@ -0,0 +1,179 @@
---
name: laravel-verification
description: Verification loop for Laravel projects: env checks, linting, static analysis, tests with coverage, security scans, and deployment readiness.
origin: ECC
---
# Laravel Doğrulama Döngüsü
PR'lardan önce, büyük değişikliklerden sonra ve deployment öncesi çalıştırın.
## Ne Zaman Kullanılır
- Laravel projesi için pull request açmadan önce
- Büyük refactoring'ler veya bağımlılık yükseltmelerinden sonra
- Staging veya production için deployment öncesi doğrulama
- Tam lint -> test -> güvenlik -> deployment hazırlık pipeline'ı çalıştırma
## Nasıl Çalışır
- Her katmanın bir öncekinin üzerine inşa edilmesi için fazları sırayla ortam kontrollerinden deployment hazırlığına kadar çalıştırın.
- Ortam ve Composer kontrolleri her şeyi kapsar; başarısız olurlarsa hemen durun.
- Tam testleri ve kapsamı çalıştırmadan önce linting/static analiz temiz olmalıdır.
- Güvenlik ve migration incelemeleri testlerden sonra olur, böylece veri veya yayın adımlarından önce davranışı doğrularsınız.
- Build/deployment hazırlığı ve kuyruk/zamanlayıcı kontrolleri son kapılardır; herhangi bir başarısızlık yayını engeller.
## Faz 1: Ortam Kontrolleri
```bash
php -v
composer --version
php artisan --version
```
- `.env`'nin mevcut olduğunu ve gerekli anahtarların var olduğunu doğrulayın
- Production ortamları için `APP_DEBUG=false` onaylayın
- `APP_ENV`'in hedef deployment'la eşleştiğini onaylayın (`production`, `staging`)
Yerel olarak Laravel Sail kullanıyorsanız:
```bash
./vendor/bin/sail php -v
./vendor/bin/sail artisan --version
```
## Faz 1.5: Composer ve Autoload
```bash
composer validate
composer dump-autoload -o
```
## Faz 2: Linting ve Static Analiz
```bash
vendor/bin/pint --test
vendor/bin/phpstan analyse
```
Projeniz PHPStan yerine Psalm kullanıyorsa:
```bash
vendor/bin/psalm
```
## Faz 3: Testler ve Kapsam
```bash
php artisan test
```
Kapsam (CI):
```bash
XDEBUG_MODE=coverage php artisan test --coverage
```
CI örneği (format -> static analiz -> testler):
```bash
vendor/bin/pint --test
vendor/bin/phpstan analyse
XDEBUG_MODE=coverage php artisan test --coverage
```
## Faz 4: Güvenlik ve Bağımlılık Kontrolleri
```bash
composer audit
```
## Faz 5: Database ve Migration'lar
```bash
php artisan migrate --pretend
php artisan migrate:status
```
- Yıkıcı migration'ları dikkatle inceleyin
- Migration dosya isimlerinin `Y_m_d_His_*` formatını takip ettiğinden emin olun (örn. `2025_03_14_154210_create_orders_table.php`) ve değişikliği net bir şekilde açıklasın
- Rollback'lerin mümkün olduğundan emin olun
- `down()` metotlarını doğrulayın ve açık yedeklemeler olmadan geri alınamaz veri kaybından kaçının
## Faz 6: Build ve Deployment Hazırlığı
```bash
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
```
- Cache warmup'larının production yapılandırmasında başarılı olduğundan emin olun
- Kuyruk worker'larının ve zamanlayıcının yapılandırıldığını doğrulayın
- Hedef ortamda `storage/` ve `bootstrap/cache/`'in yazılabilir olduğunu onaylayın
## Faz 7: Kuyruk ve Zamanlayıcı Kontrolleri
```bash
php artisan schedule:list
php artisan queue:failed
```
Horizon kullanılıyorsa:
```bash
php artisan horizon:status
```
`queue:monitor` mevcutsa, job'ları işlemeden biriktirmeyi kontrol etmek için kullanın:
```bash
php artisan queue:monitor default --max=100
```
Aktif doğrulama (sadece staging): özel bir kuyruğa no-op job dispatch edin ve işlemek için tek bir worker çalıştırın (non-`sync` kuyruk bağlantısının yapılandırıldığından emin olun).
```bash
php artisan tinker --execute="dispatch((new App\\Jobs\\QueueHealthcheck())->onQueue('healthcheck'))"
php artisan queue:work --once --queue=healthcheck
```
Job'un beklenen yan etkiyi ürettiğini doğrulayın (log girişi, healthcheck tablo satırı veya metrik).
Bunu sadece test job'u işlemenin güvenli olduğu non-production ortamlarında çalıştırın.
## Örnekler
Minimal akış:
```bash
php -v
composer --version
php artisan --version
composer validate
vendor/bin/pint --test
vendor/bin/phpstan analyse
php artisan test
composer audit
php artisan migrate --pretend
php artisan config:cache
php artisan queue:failed
```
CI tarzı pipeline:
```bash
composer validate
composer dump-autoload -o
vendor/bin/pint --test
vendor/bin/phpstan analyse
XDEBUG_MODE=coverage php artisan test --coverage
composer audit
php artisan migrate --pretend
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan schedule:list
```

View File

@@ -0,0 +1,44 @@
---
name: nextjs-turbopack
description: Next.js 16+ and Turbopack — incremental bundling, FS caching, dev speed, and when to use Turbopack vs webpack.
origin: ECC
---
# Next.js ve Turbopack
Next.js 16+ yerel geliştirme için varsayılan olarak Turbopack kullanır: geliştirme başlatma ve hot update'leri önemli ölçüde hızlandıran Rust ile yazılmış artımlı bir bundler.
## Ne Zaman Kullanılır
- **Turbopack (varsayılan dev)**: Günlük geliştirme için kullanın. Özellikle büyük uygulamalarda daha hızlı soğuk başlatma ve HMR.
- **Webpack (legacy dev)**: Sadece bir Turbopack bug'ına denk gelirseniz veya dev'de webpack'e özgü bir plugin'e güveniyorsanız kullanın. `--webpack` ile devre dışı bırakın (veya Next.js sürümünüze bağlı olarak `--no-turbopack`; sürümünüz için dokümanlara bakın).
- **Production**: Production build davranışı (`next build`) Next.js sürümüne bağlı olarak Turbopack veya webpack kullanabilir; sürümünüz için resmi Next.js dokümantasyonunu kontrol edin.
Şu durumlarda kullanın: Next.js 16+ uygulamalarını geliştirme veya debug etme, yavaş dev başlatma veya HMR'yi teşhis etme veya production bundle'larını optimize etme.
## Nasıl Çalışır
- **Turbopack**: Next.js dev için artımlı bundler. Dosya sistemi önbelleği kullanır, böylece yeniden başlatmalar çok daha hızlıdır (örn. büyük projelerde 5-14x).
- **Dev'de varsayılan**: Next.js 16'dan itibaren, `next dev` devre dışı bırakılmadıkça Turbopack ile çalışır.
- **Dosya sistemi önbelleği**: Yeniden başlatmalar önceki çalışmayı yeniden kullanır; önbellek genellikle `.next` altındadır; temel kullanım için ekstra yapılandırma gerekmez.
- **Bundle Analyzer (Next.js 16.1+)**: Çıktıyı incelemek ve ağır bağımlılıkları bulmak için deneysel Bundle Analyzer; config veya deneysel bayrak ile etkinleştirin (sürümünüz için Next.js dokümantasyonuna bakın).
## Örnekler
### Komutlar
```bash
next dev
next build
next start
```
### Kullanım
Turbopack ile yerel geliştirme için `next dev` çalıştırın. Code-splitting'i optimize etmek ve büyük bağımlılıkları kırpmak için Bundle Analyzer'ı kullanın (Next.js dokümantasyonuna bakın). Mümkün olduğunda App Router ve server component'leri tercih edin.
## En İyi Uygulamalar
- Kararlı Turbopack ve önbellekleme davranışı için güncel bir Next.js 16.x sürümünde kalın.
- Dev yavaşsa, Turbopack'te (varsayılan) olduğunuzdan ve önbelleğin gereksiz yere temizlenmediğinden emin olun.
- Production bundle boyutu sorunları için, sürümünüz için resmi Next.js bundle analiz araçlarını kullanın.

View File

@@ -0,0 +1,147 @@
---
name: postgres-patterns
description: Sorgu optimizasyonu, şema tasarımı, indeksleme ve güvenlik için PostgreSQL veritabanı kalıpları. Supabase en iyi uygulamalarına dayanır.
origin: ECC
---
# PostgreSQL Kalıpları
PostgreSQL en iyi uygulamaları için hızlı referans. Detaylı kılavuz için `database-reviewer` agent'ını kullanın.
## Ne Zaman Aktifleştirmeli
- SQL sorguları veya migration'lar yazarken
- Veritabanı şemaları tasarlarken
- Yavaş sorguları troubleshoot ederken
- Row Level Security uygularken
- Connection pooling kurarken
## Hızlı Referans
### İndeks Hile Sayfası
| Sorgu Kalıbı | İndeks Tipi | Örnek |
|--------------|------------|---------|
| `WHERE col = value` | B-tree (varsayılan) | `CREATE INDEX idx ON t (col)` |
| `WHERE col > value` | B-tree | `CREATE INDEX idx ON t (col)` |
| `WHERE a = x AND b > y` | Composite | `CREATE INDEX idx ON t (a, b)` |
| `WHERE jsonb @> '{}'` | GIN | `CREATE INDEX idx ON t USING gin (col)` |
| `WHERE tsv @@ query` | GIN | `CREATE INDEX idx ON t USING gin (col)` |
| Zaman serisi aralıkları | BRIN | `CREATE INDEX idx ON t USING brin (col)` |
### Veri Tipi Hızlı Referans
| Kullanım Senaryosu | Doğru Tip | Kaçın |
|----------|-------------|-------|
| ID'ler | `bigint` | `int`, rastgele UUID |
| String'ler | `text` | `varchar(255)` |
| Timestamp'ler | `timestamptz` | `timestamp` |
| Para | `numeric(10,2)` | `float` |
| Flag'ler | `boolean` | `varchar`, `int` |
### Yaygın Kalıplar
**Composite İndeks Sırası:**
```sql
-- Önce eşitlik sütunları, sonra aralık sütunları
CREATE INDEX idx ON orders (status, created_at);
-- Şunlar için çalışır: WHERE status = 'pending' AND created_at > '2024-01-01'
```
**Covering İndeks:**
```sql
CREATE INDEX idx ON users (email) INCLUDE (name, created_at);
-- SELECT email, name, created_at için tablo aramasını önler
```
**Partial İndeks:**
```sql
CREATE INDEX idx ON users (email) WHERE deleted_at IS NULL;
-- Daha küçük indeks, sadece aktif kullanıcıları içerir
```
**RLS Policy (Optimize Edilmiş):**
```sql
CREATE POLICY policy ON orders
USING ((SELECT auth.uid()) = user_id); -- SELECT'e sar!
```
**UPSERT:**
```sql
INSERT INTO settings (user_id, key, value)
VALUES (123, 'theme', 'dark')
ON CONFLICT (user_id, key)
DO UPDATE SET value = EXCLUDED.value;
```
**Cursor Sayfalama:**
```sql
SELECT * FROM products WHERE id > $last_id ORDER BY id LIMIT 20;
-- O(1) vs O(n) olan OFFSET
```
**Kuyruk İşleme:**
```sql
UPDATE jobs SET status = 'processing'
WHERE id = (
SELECT id FROM jobs WHERE status = 'pending'
ORDER BY created_at LIMIT 1
FOR UPDATE SKIP LOCKED
) RETURNING *;
```
### Anti-Kalıp Tespiti
```sql
-- İndekslenmemiş foreign key'leri bul
SELECT conrelid::regclass, a.attname
FROM pg_constraint c
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)
WHERE c.contype = 'f'
AND NOT EXISTS (
SELECT 1 FROM pg_index i
WHERE i.indrelid = c.conrelid AND a.attnum = ANY(i.indkey)
);
-- Yavaş sorguları bul
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
WHERE mean_exec_time > 100
ORDER BY mean_exec_time DESC;
-- Tablo bloat'ını kontrol et
SELECT relname, n_dead_tup, last_vacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
```
### Yapılandırma Şablonu
```sql
-- Bağlantı limitleri (RAM için ayarla)
ALTER SYSTEM SET max_connections = 100;
ALTER SYSTEM SET work_mem = '8MB';
-- Timeout'lar
ALTER SYSTEM SET idle_in_transaction_session_timeout = '30s';
ALTER SYSTEM SET statement_timeout = '30s';
-- İzleme
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Güvenlik varsayılanları
REVOKE ALL ON SCHEMA public FROM public;
SELECT pg_reload_conf();
```
## İlgili
- Agent: `database-reviewer` - Tam veritabanı inceleme iş akışı
- Skill: `clickhouse-io` - ClickHouse analytics kalıpları
- Skill: `backend-patterns` - API ve backend kalıpları
---
*Supabase Agent Skills'e dayanır (kredi: Supabase ekibi) (MIT License)*

View File

@@ -0,0 +1,750 @@
---
name: python-patterns
description: Pythonic idiomlar, PEP 8 standartları, type hint'ler ve sağlam, verimli ve bakımı kolay Python uygulamaları oluşturmak için en iyi uygulamalar.
origin: ECC
---
# Python Geliştirme Desenleri
Sağlam, verimli ve bakımı kolay uygulamalar oluşturmak için idiomatic Python desenleri ve en iyi uygulamalar.
## Ne Zaman Etkinleştirmeli
- Yeni Python kodu yazarken
- Python kodunu gözden geçirirken
- Mevcut Python kodunu refactor ederken
- Python paketleri/modülleri tasarlarken
## Temel Prensipler
### 1. Okunabilirlik Önemlidir
Python okunabilirliği önceliklendirir. Kod açık ve anlaşılması kolay olmalıdır.
```python
# İyi: Açık ve okunabilir
def get_active_users(users: list[User]) -> list[User]:
"""Sağlanan listeden sadece aktif kullanıcıları döndür."""
return [user for user in users if user.is_active]
# Kötü: Zeki ama kafa karıştırıcı
def get_active_users(u):
return [x for x in u if x.a]
```
### 2. Açık, Örtük Olandan Daha İyidir
Sihirden kaçının; kodunuzun ne yaptığı konusunda açık olun.
```python
# İyi: Açık yapılandırma
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Kötü: Gizli yan etkiler
import some_module
some_module.setup() # Bu ne yapıyor?
```
### 3. EAFP - Affederek Sormaktansa İzin İstemek Daha Kolaydır
Python, koşulları kontrol etmek yerine exception handling'i tercih eder.
```python
# İyi: EAFP stili
def get_value(dictionary: dict, key: str) -> Any:
try:
return dictionary[key]
except KeyError:
return default_value
# Kötü: LBYL (Atlamadan Önce Bak) stili
def get_value(dictionary: dict, key: str) -> Any:
if key in dictionary:
return dictionary[key]
else:
return default_value
```
## Type Hint'ler
### Temel Type Annotation'lar
```python
from typing import Optional, List, Dict, Any
def process_user(
user_id: str,
data: Dict[str, Any],
active: bool = True
) -> Optional[User]:
"""Bir kullanıcıyı işle ve güncellenmiş User'ı veya None döndür."""
if not active:
return None
return User(user_id, data)
```
### Modern Type Hint'ler (Python 3.9+)
```python
# Python 3.9+ - Built-in tipleri kullan
def process_items(items: list[str]) -> dict[str, int]:
return {item: len(item) for item in items}
# Python 3.8 ve öncesi - typing modülünü kullan
from typing import List, Dict
def process_items(items: List[str]) -> Dict[str, int]:
return {item: len(item) for item in items}
```
### Type Alias'ları ve TypeVar
```python
from typing import TypeVar, Union
# Karmaşık tipler için type alias
JSON = Union[dict[str, Any], list[Any], str, int, float, bool, None]
def parse_json(data: str) -> JSON:
return json.loads(data)
# Generic tipler
T = TypeVar('T')
def first(items: list[T]) -> T | None:
"""İlk öğeyi döndür veya liste boşsa None döndür."""
return items[0] if items else None
```
### Protocol Tabanlı Duck Typing
```python
from typing import Protocol
class Renderable(Protocol):
def render(self) -> str:
"""Nesneyi string'e render et."""
def render_all(items: list[Renderable]) -> str:
"""Renderable protocol'ünü implement eden tüm öğeleri render et."""
return "\n".join(item.render() for item in items)
```
## Hata İşleme Desenleri
### Spesifik Exception Handling
```python
# İyi: Spesifik exception'ları yakala
def load_config(path: str) -> Config:
try:
with open(path) as f:
return Config.from_json(f.read())
except FileNotFoundError as e:
raise ConfigError(f"Config file not found: {path}") from e
except json.JSONDecodeError as e:
raise ConfigError(f"Invalid JSON in config: {path}") from e
# Kötü: Bare except
def load_config(path: str) -> Config:
try:
with open(path) as f:
return Config.from_json(f.read())
except:
return None # Sessiz hata!
```
### Exception Chaining
```python
def process_data(data: str) -> Result:
try:
parsed = json.loads(data)
except json.JSONDecodeError as e:
# Traceback'i korumak için exception'ları zincirleme
raise ValueError(f"Failed to parse data: {data}") from e
```
### Özel Exception Hiyerarşisi
```python
class AppError(Exception):
"""Tüm uygulama hataları için base exception."""
pass
class ValidationError(AppError):
"""Input validation başarısız olduğunda raise edilir."""
pass
class NotFoundError(AppError):
"""İstenen kaynak bulunamadığında raise edilir."""
pass
# Kullanım
def get_user(user_id: str) -> User:
user = db.find_user(user_id)
if not user:
raise NotFoundError(f"User not found: {user_id}")
return user
```
## Context Manager'lar
### Kaynak Yönetimi
```python
# İyi: Context manager'ları kullanma
def process_file(path: str) -> str:
with open(path, 'r') as f:
return f.read()
# Kötü: Manuel kaynak yönetimi
def process_file(path: str) -> str:
f = open(path, 'r')
try:
return f.read()
finally:
f.close()
```
### Özel Context Manager'lar
```python
from contextlib import contextmanager
@contextmanager
def timer(name: str):
"""Bir kod bloğunu zamanlamak için context manager."""
start = time.perf_counter()
yield
elapsed = time.perf_counter() - start
print(f"{name} took {elapsed:.4f} seconds")
# Kullanım
with timer("data processing"):
process_large_dataset()
```
### Context Manager Class'ları
```python
class DatabaseTransaction:
def __init__(self, connection):
self.connection = connection
def __enter__(self):
self.connection.begin_transaction()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.connection.commit()
else:
self.connection.rollback()
return False # Exception'ları suppress etme
# Kullanım
with DatabaseTransaction(conn):
user = conn.create_user(user_data)
conn.create_profile(user.id, profile_data)
```
## Comprehension'lar ve Generator'lar
### List Comprehension'ları
```python
# İyi: Basit dönüşümler için list comprehension
names = [user.name for user in users if user.is_active]
# Kötü: Manuel döngü
names = []
for user in users:
if user.is_active:
names.append(user.name)
# Karmaşık comprehension'lar genişletilmelidir
# Kötü: Çok karmaşık
result = [x * 2 for x in items if x > 0 if x % 2 == 0]
# İyi: Bir generator fonksiyonu kullan
def filter_and_transform(items: Iterable[int]) -> list[int]:
result = []
for x in items:
if x > 0 and x % 2 == 0:
result.append(x * 2)
return result
```
### Generator Expression'ları
```python
# İyi: Lazy evaluation için generator
total = sum(x * x for x in range(1_000_000))
# Kötü: Büyük ara liste oluşturur
total = sum([x * x for x in range(1_000_000)])
```
### Generator Fonksiyonları
```python
def read_large_file(path: str) -> Iterator[str]:
"""Büyük bir dosyayı satır satır oku."""
with open(path) as f:
for line in f:
yield line.strip()
# Kullanım
for line in read_large_file("huge.txt"):
process(line)
```
## Data Class'lar ve Named Tuple'lar
### Data Class'lar
```python
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class User:
"""Otomatik __init__, __repr__ ve __eq__ ile User entity."""
id: str
name: str
email: str
created_at: datetime = field(default_factory=datetime.now)
is_active: bool = True
# Kullanım
user = User(
id="123",
name="Alice",
email="alice@example.com"
)
```
### Validation ile Data Class'lar
```python
@dataclass
class User:
email: str
age: int
def __post_init__(self):
# Email formatını validate et
if "@" not in self.email:
raise ValueError(f"Invalid email: {self.email}")
# Yaş aralığını validate et
if self.age < 0 or self.age > 150:
raise ValueError(f"Invalid age: {self.age}")
```
### Named Tuple'lar
```python
from typing import NamedTuple
class Point(NamedTuple):
"""Immutable 2D nokta."""
x: float
y: float
def distance(self, other: 'Point') -> float:
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
# Kullanım
p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance(p2)) # 5.0
```
## Decorator'lar
### Fonksiyon Decorator'ları
```python
import functools
import time
def timer(func: Callable) -> Callable:
"""Fonksiyon yürütmesini zamanlamak için decorator."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
# slow_function() yazdırır: slow_function took 1.0012s
```
### Parametreli Decorator'lar
```python
def repeat(times: int):
"""Bir fonksiyonu birden çok kez tekrarlamak için decorator."""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
@repeat(times=3)
def greet(name: str) -> str:
return f"Hello, {name}!"
# greet("Alice") döndürür ["Hello, Alice!", "Hello, Alice!", "Hello, Alice!"]
```
### Class Tabanlı Decorator'lar
```python
class CountCalls:
"""Bir fonksiyonun kaç kez çağrıldığını sayan decorator."""
def __init__(self, func: Callable):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} has been called {self.count} times")
return self.func(*args, **kwargs)
@CountCalls
def process():
pass
# Her process() çağrısı çağrı sayısını yazdırır
```
## Eşzamanlılık Desenleri
### I/O-Bound Görevler için Threading
```python
import concurrent.futures
import threading
def fetch_url(url: str) -> str:
"""Bir URL fetch et (I/O-bound operasyon)."""
import urllib.request
with urllib.request.urlopen(url) as response:
return response.read().decode()
def fetch_all_urls(urls: list[str]) -> dict[str, str]:
"""Thread'ler kullanarak birden fazla URL'yi eşzamanlı fetch et."""
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
future_to_url = {executor.submit(fetch_url, url): url for url in urls}
results = {}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
results[url] = future.result()
except Exception as e:
results[url] = f"Error: {e}"
return results
```
### CPU-Bound Görevler için Multiprocessing
```python
def process_data(data: list[int]) -> int:
"""CPU-yoğun hesaplama."""
return sum(x ** 2 for x in data)
def process_all(datasets: list[list[int]]) -> list[int]:
"""Birden fazla process kullanarak birden fazla dataset işle."""
with concurrent.futures.ProcessPoolExecutor() as executor:
results = list(executor.map(process_data, datasets))
return results
```
### Eşzamanlı I/O için Async/Await
```python
import asyncio
async def fetch_async(url: str) -> str:
"""Asenkron olarak bir URL fetch et."""
import aiohttp
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def fetch_all(urls: list[str]) -> dict[str, str]:
"""Birden fazla URL'yi eşzamanlı fetch et."""
tasks = [fetch_async(url) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
return dict(zip(urls, results))
```
## Paket Organizasyonu
### Standart Proje Düzeni
```
myproject/
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── main.py
│ ├── api/
│ │ ├── __init__.py
│ │ └── routes.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── user.py
│ └── utils/
│ ├── __init__.py
│ └── helpers.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_api.py
│ └── test_models.py
├── pyproject.toml
├── README.md
└── .gitignore
```
### Import Konvansiyonları
```python
# İyi: Import sırası - stdlib, third-party, local
import os
import sys
from pathlib import Path
import requests
from fastapi import FastAPI
from mypackage.models import User
from mypackage.utils import format_name
# İyi: Otomatik import sıralama için isort kullanın
# pip install isort
```
### Paket Export'ları için __init__.py
```python
# mypackage/__init__.py
"""mypackage - Örnek bir Python paketi."""
__version__ = "1.0.0"
# Ana class/fonksiyonları paket seviyesinde export et
from mypackage.models import User, Post
from mypackage.utils import format_name
__all__ = ["User", "Post", "format_name"]
```
## Bellek ve Performans
### Bellek Verimliliği için __slots__ Kullanma
```python
# Kötü: Normal class __dict__ kullanır (daha fazla bellek)
class Point:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
# İyi: __slots__ bellek kullanımını azaltır
class Point:
__slots__ = ['x', 'y']
def __init__(self, x: float, y: float):
self.x = x
self.y = y
```
### Büyük Veri için Generator
```python
# Kötü: Bellekte tam liste döndürür
def read_lines(path: str) -> list[str]:
with open(path) as f:
return [line.strip() for line in f]
# İyi: Satırları birer birer yield eder
def read_lines(path: str) -> Iterator[str]:
with open(path) as f:
for line in f:
yield line.strip()
```
### Döngülerde String Birleştirmekten Kaçının
```python
# Kötü: String immutability nedeniyle O(n²)
result = ""
for item in items:
result += str(item)
# İyi: join kullanarak O(n)
result = "".join(str(item) for item in items)
# İyi: Oluşturma için StringIO kullanma
from io import StringIO
buffer = StringIO()
for item in items:
buffer.write(str(item))
result = buffer.getvalue()
```
## Python Tooling Entegrasyonu
### Temel Komutlar
```bash
# Kod formatlama
black .
isort .
# Linting
ruff check .
pylint mypackage/
# Type checking
mypy .
# Test
pytest --cov=mypackage --cov-report=html
# Güvenlik taraması
bandit -r .
# Dependency yönetimi
pip-audit
safety check
```
### pyproject.toml Yapılandırması
```toml
[project]
name = "mypackage"
version = "1.0.0"
requires-python = ">=3.9"
dependencies = [
"requests>=2.31.0",
"pydantic>=2.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"black>=23.0.0",
"ruff>=0.1.0",
"mypy>=1.5.0",
]
[tool.black]
line-length = 88
target-version = ['py39']
[tool.ruff]
line-length = 88
select = ["E", "F", "I", "N", "W"]
[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--cov=mypackage --cov-report=term-missing"
```
## Hızlı Referans: Python İfadeleri
| İfade | Açıklama |
|-------|----------|
| EAFP | Affederek Sormaktansa İzin İstemek Daha Kolay |
| Context manager'lar | Kaynak yönetimi için `with` kullan |
| List comprehension'lar | Basit dönüşümler için |
| Generator'lar | Lazy evaluation ve büyük dataset'ler için |
| Type hint'ler | Fonksiyon signature'larını annotate et |
| Dataclass'lar | Auto-generated metodlarla veri container'ları için |
| `__slots__` | Bellek optimizasyonu için |
| f-string'ler | String formatlama için (Python 3.6+) |
| `pathlib.Path` | Path operasyonları için (Python 3.4+) |
| `enumerate` | Döngülerde index-element çiftleri için |
## Kaçınılması Gereken Anti-Desenler
```python
# Kötü: Mutable default argümanlar
def append_to(item, items=[]):
items.append(item)
return items
# İyi: None kullan ve yeni liste oluştur
def append_to(item, items=None):
if items is None:
items = []
items.append(item)
return items
# Kötü: type() ile tip kontrolü
if type(obj) == list:
process(obj)
# İyi: isinstance kullan
if isinstance(obj, list):
process(obj)
# Kötü: None ile == ile karşılaştırma
if value == None:
process()
# İyi: is kullan
if value is None:
process()
# Kötü: from module import *
from os.path import *
# İyi: Açık import'lar
from os.path import join, exists
# Kötü: Bare except
try:
risky_operation()
except:
pass
# İyi: Spesifik exception
try:
risky_operation()
except SpecificError as e:
logger.error(f"Operation failed: {e}")
```
__Unutmayın__: Python kodu okunabilir, açık ve en az sürpriz ilkesine uygun olmalıdır. Şüphe duyduğunuzda, açıklığı zekiceden öncelikli kılın.

View File

@@ -0,0 +1,816 @@
---
name: python-testing
description: pytest, TDD metodolojisi, fixture'lar, mocking, parametrizasyon ve coverage gereksinimleri kullanarak Python test stratejileri.
origin: ECC
---
# Python Test Desenleri
pytest, TDD metodolojisi ve en iyi uygulamalar kullanarak Python uygulamaları için kapsamlı test stratejileri.
## Ne Zaman Etkinleştirmeli
- Yeni Python kodu yazarken (TDD'yi takip et: red, green, refactor)
- Python projeleri için test suite'leri tasarlarken
- Python test coverage'ını gözden geçirirken
- Test altyapısını kurarken
## Temel Test Felsefesi
### Test-Driven Development (TDD)
Her zaman TDD döngüsünü takip edin:
1. **RED**: İstenen davranış için başarısız bir test yaz
2. **GREEN**: Testi geçirmek için minimal kod yaz
3. **REFACTOR**: Testleri yeşil tutarken kodu iyileştir
```python
# Adım 1: Başarısız test yaz (RED)
def test_add_numbers():
result = add(2, 3)
assert result == 5
# Adım 2: Minimal implementasyon yaz (GREEN)
def add(a, b):
return a + b
# Adım 3: Gerekirse refactor et (REFACTOR)
```
### Coverage Gereksinimleri
- **Hedef**: 80%+ kod coverage'ı
- **Kritik yollar**: 100% coverage gereklidir
- Coverage'ı ölçmek için `pytest --cov` kullanın
```bash
pytest --cov=mypackage --cov-report=term-missing --cov-report=html
```
## pytest Temelleri
### Temel Test Yapısı
```python
import pytest
def test_addition():
"""Temel toplama testi."""
assert 2 + 2 == 4
def test_string_uppercase():
"""String büyük harf yapma testi."""
text = "hello"
assert text.upper() == "HELLO"
def test_list_append():
"""Liste append testi."""
items = [1, 2, 3]
items.append(4)
assert 4 in items
assert len(items) == 4
```
### Assertion'lar
```python
# Eşitlik
assert result == expected
# Eşitsizlik
assert result != unexpected
# Doğruluk değeri
assert result # Truthy
assert not result # Falsy
assert result is True # Tam olarak True
assert result is False # Tam olarak False
assert result is None # Tam olarak None
# Üyelik
assert item in collection
assert item not in collection
# Karşılaştırmalar
assert result > 0
assert 0 <= result <= 100
# Tip kontrolü
assert isinstance(result, str)
# Exception testi (tercih edilen yaklaşım)
with pytest.raises(ValueError):
raise ValueError("error message")
# Exception mesajını kontrol et
with pytest.raises(ValueError, match="invalid input"):
raise ValueError("invalid input provided")
# Exception niteliklerini kontrol et
with pytest.raises(ValueError) as exc_info:
raise ValueError("error message")
assert str(exc_info.value) == "error message"
```
## Fixture'lar
### Temel Fixture Kullanımı
```python
import pytest
@pytest.fixture
def sample_data():
"""Örnek veri sağlayan fixture."""
return {"name": "Alice", "age": 30}
def test_sample_data(sample_data):
"""Fixture kullanan test."""
assert sample_data["name"] == "Alice"
assert sample_data["age"] == 30
```
### Setup/Teardown ile Fixture
```python
@pytest.fixture
def database():
"""Setup ve teardown ile fixture."""
# Setup
db = Database(":memory:")
db.create_tables()
db.insert_test_data()
yield db # Teste sağla
# Teardown
db.close()
def test_database_query(database):
"""Veritabanı operasyonlarını test et."""
result = database.query("SELECT * FROM users")
assert len(result) > 0
```
### Fixture Scope'ları
```python
# Function scope (varsayılan) - her test için çalışır
@pytest.fixture
def temp_file():
with open("temp.txt", "w") as f:
yield f
os.remove("temp.txt")
# Module scope - modül başına bir kez çalışır
@pytest.fixture(scope="module")
def module_db():
db = Database(":memory:")
db.create_tables()
yield db
db.close()
# Session scope - test oturumu başına bir kez çalışır
@pytest.fixture(scope="session")
def shared_resource():
resource = ExpensiveResource()
yield resource
resource.cleanup()
```
### Parametreli Fixture
```python
@pytest.fixture(params=[1, 2, 3])
def number(request):
"""Parametreli fixture."""
return request.param
def test_numbers(number):
"""Test her parametre için 3 kez çalışır."""
assert number > 0
```
### Birden Fazla Fixture Kullanma
```python
@pytest.fixture
def user():
return User(id=1, name="Alice")
@pytest.fixture
def admin():
return User(id=2, name="Admin", role="admin")
def test_user_admin_interaction(user, admin):
"""Birden fazla fixture kullanan test."""
assert admin.can_manage(user)
```
### Autouse Fixture'ları
```python
@pytest.fixture(autouse=True)
def reset_config():
"""Her testten önce otomatik olarak çalışır."""
Config.reset()
yield
Config.cleanup()
def test_without_fixture_call():
# reset_config otomatik olarak çalışır
assert Config.get_setting("debug") is False
```
### Paylaşılan Fixture'lar için Conftest.py
```python
# tests/conftest.py
import pytest
@pytest.fixture
def client():
"""Tüm testler için paylaşılan fixture."""
app = create_app(testing=True)
with app.test_client() as client:
yield client
@pytest.fixture
def auth_headers(client):
"""API testi için auth header'ları oluştur."""
response = client.post("/api/login", json={
"username": "test",
"password": "test"
})
token = response.json["token"]
return {"Authorization": f"Bearer {token}"}
```
## Parametrizasyon
### Temel Parametrizasyon
```python
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
("PyThOn", "PYTHON"),
])
def test_uppercase(input, expected):
"""Test farklı input'larla 3 kez çalışır."""
assert input.upper() == expected
```
### Birden Fazla Parametre
```python
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300),
])
def test_add(a, b, expected):
"""Birden fazla input ile toplama testi."""
assert add(a, b) == expected
```
### ID'li Parametrizasyon
```python
@pytest.mark.parametrize("input,expected", [
("valid@email.com", True),
("invalid", False),
("@no-domain.com", False),
], ids=["valid-email", "missing-at", "missing-domain"])
def test_email_validation(input, expected):
"""Okunabilir test ID'leri ile email validation testi."""
assert is_valid_email(input) is expected
```
### Parametreli Fixture'lar
```python
@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
def db(request):
"""Birden fazla veritabanı backend'ine karşı test."""
if request.param == "sqlite":
return Database(":memory:")
elif request.param == "postgresql":
return Database("postgresql://localhost/test")
elif request.param == "mysql":
return Database("mysql://localhost/test")
def test_database_operations(db):
"""Test her veritabanı için 3 kez çalışır."""
result = db.query("SELECT 1")
assert result is not None
```
## Marker'lar ve Test Seçimi
### Özel Marker'lar
```python
# Yavaş testleri işaretle
@pytest.mark.slow
def test_slow_operation():
time.sleep(5)
# Entegrasyon testlerini işaretle
@pytest.mark.integration
def test_api_integration():
response = requests.get("https://api.example.com")
assert response.status_code == 200
# Unit testleri işaretle
@pytest.mark.unit
def test_unit_logic():
assert calculate(2, 3) == 5
```
### Belirli Testleri Çalıştırma
```bash
# Sadece hızlı testleri çalıştır
pytest -m "not slow"
# Sadece entegrasyon testlerini çalıştır
pytest -m integration
# Entegrasyon veya yavaş testleri çalıştır
pytest -m "integration or slow"
# Unit olarak işaretlenmiş ama yavaş olmayan testleri çalıştır
pytest -m "unit and not slow"
```
### pytest.ini'de Marker'ları Yapılandırma
```ini
[pytest]
markers =
slow: marks tests as slow
integration: marks tests as integration tests
unit: marks tests as unit tests
django: marks tests as requiring Django
```
## Mocking ve Patching
### Fonksiyonları Mocking
```python
from unittest.mock import patch, Mock
@patch("mypackage.external_api_call")
def test_with_mock(api_call_mock):
"""Mock'lanmış harici API ile test."""
api_call_mock.return_value = {"status": "success"}
result = my_function()
api_call_mock.assert_called_once()
assert result["status"] == "success"
```
### Dönüş Değerlerini Mocking
```python
@patch("mypackage.Database.connect")
def test_database_connection(connect_mock):
"""Mock'lanmış veritabanı bağlantısı ile test."""
connect_mock.return_value = MockConnection()
db = Database()
db.connect()
connect_mock.assert_called_once_with("localhost")
```
### Exception'ları Mocking
```python
@patch("mypackage.api_call")
def test_api_error_handling(api_call_mock):
"""Mock'lanmış exception ile hata işleme testi."""
api_call_mock.side_effect = ConnectionError("Network error")
with pytest.raises(ConnectionError):
api_call()
api_call_mock.assert_called_once()
```
### Context Manager'ları Mocking
```python
@patch("builtins.open", new_callable=mock_open)
def test_file_reading(mock_file):
"""Mock'lanmış open ile dosya okuma testi."""
mock_file.return_value.read.return_value = "file content"
result = read_file("test.txt")
mock_file.assert_called_once_with("test.txt", "r")
assert result == "file content"
```
### Autospec Kullanma
```python
@patch("mypackage.DBConnection", autospec=True)
def test_autospec(db_mock):
"""API yanlış kullanımını yakalamak için autospec ile test."""
db = db_mock.return_value
db.query("SELECT * FROM users")
# DBConnection query metodu yoksa bu başarısız olur
db_mock.assert_called_once()
```
### Mock Class Instance'ları
```python
class TestUserService:
@patch("mypackage.UserRepository")
def test_create_user(self, repo_mock):
"""Mock'lanmış repository ile kullanıcı oluşturma testi."""
repo_mock.return_value.save.return_value = User(id=1, name="Alice")
service = UserService(repo_mock.return_value)
user = service.create_user(name="Alice")
assert user.name == "Alice"
repo_mock.return_value.save.assert_called_once()
```
### Mock Property
```python
@pytest.fixture
def mock_config():
"""Property'li bir mock oluştur."""
config = Mock()
type(config).debug = PropertyMock(return_value=True)
type(config).api_key = PropertyMock(return_value="test-key")
return config
def test_with_mock_config(mock_config):
"""Mock'lanmış config property'leri ile test."""
assert mock_config.debug is True
assert mock_config.api_key == "test-key"
```
## Asenkron Kodu Test Etme
### pytest-asyncio ile Asenkron Testler
```python
import pytest
@pytest.mark.asyncio
async def test_async_function():
"""Asenkron fonksiyon testi."""
result = await async_add(2, 3)
assert result == 5
@pytest.mark.asyncio
async def test_async_with_fixture(async_client):
"""Asenkron fixture ile asenkron test."""
response = await async_client.get("/api/users")
assert response.status_code == 200
```
### Asenkron Fixture
```python
@pytest.fixture
async def async_client():
"""Asenkron test client sağlayan asenkron fixture."""
app = create_app()
async with app.test_client() as client:
yield client
@pytest.mark.asyncio
async def test_api_endpoint(async_client):
"""Asenkron fixture kullanan test."""
response = await async_client.get("/api/data")
assert response.status_code == 200
```
### Asenkron Fonksiyonları Mocking
```python
@pytest.mark.asyncio
@patch("mypackage.async_api_call")
async def test_async_mock(api_call_mock):
"""Mock ile asenkron fonksiyon testi."""
api_call_mock.return_value = {"status": "ok"}
result = await my_async_function()
api_call_mock.assert_awaited_once()
assert result["status"] == "ok"
```
## Exception'ları Test Etme
### Beklenen Exception'ları Test Etme
```python
def test_divide_by_zero():
"""Sıfıra bölmenin ZeroDivisionError raise ettiğini test et."""
with pytest.raises(ZeroDivisionError):
divide(10, 0)
def test_custom_exception():
"""Mesaj ile özel exception testi."""
with pytest.raises(ValueError, match="invalid input"):
validate_input("invalid")
```
### Exception Niteliklerini Test Etme
```python
def test_exception_with_details():
"""Özel niteliklerle exception testi."""
with pytest.raises(CustomError) as exc_info:
raise CustomError("error", code=400)
assert exc_info.value.code == 400
assert "error" in str(exc_info.value)
```
## Yan Etkileri Test Etme
### Dosya Operasyonlarını Test Etme
```python
import tempfile
import os
def test_file_processing():
"""Geçici dosya ile dosya işleme testi."""
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
f.write("test content")
temp_path = f.name
try:
result = process_file(temp_path)
assert result == "processed: test content"
finally:
os.unlink(temp_path)
```
### pytest'in tmp_path Fixture'ı ile Test Etme
```python
def test_with_tmp_path(tmp_path):
"""pytest'in built-in geçici yol fixture'ını kullanarak test."""
test_file = tmp_path / "test.txt"
test_file.write_text("hello world")
result = process_file(str(test_file))
assert result == "hello world"
# tmp_path otomatik olarak temizlenir
```
### tmpdir Fixture ile Test Etme
```python
def test_with_tmpdir(tmpdir):
"""pytest'in tmpdir fixture'ını kullanarak test."""
test_file = tmpdir.join("test.txt")
test_file.write("data")
result = process_file(str(test_file))
assert result == "data"
```
## Test Organizasyonu
### Dizin Yapısı
```
tests/
├── conftest.py # Paylaşılan fixture'lar
├── __init__.py
├── unit/ # Unit testler
│ ├── __init__.py
│ ├── test_models.py
│ ├── test_utils.py
│ └── test_services.py
├── integration/ # Entegrasyon testleri
│ ├── __init__.py
│ ├── test_api.py
│ └── test_database.py
└── e2e/ # End-to-end testler
├── __init__.py
└── test_user_flow.py
```
### Test Class'ları
```python
class TestUserService:
"""İlgili testleri bir class'ta grupla."""
@pytest.fixture(autouse=True)
def setup(self):
"""Bu class'taki her testten önce çalışan setup."""
self.service = UserService()
def test_create_user(self):
"""Kullanıcı oluşturma testi."""
user = self.service.create_user("Alice")
assert user.name == "Alice"
def test_delete_user(self):
"""Kullanıcı silme testi."""
user = User(id=1, name="Bob")
self.service.delete_user(user)
assert not self.service.user_exists(1)
```
## En İyi Uygulamalar
### YAPIN
- **TDD'yi takip edin**: Koddan önce testleri yazın (red-green-refactor)
- **Bir şeyi test edin**: Her test tek bir davranışı doğrulamalı
- **Açıklayıcı isimler kullanın**: `test_user_login_with_invalid_credentials_fails`
- **Fixture'ları kullanın**: Tekrarı fixture'larla ortadan kaldırın
- **Harici bağımlılıkları mock'layın**: Harici servislere bağımlı olmayın
- **Kenar durumları test edin**: Boş input'lar, None değerleri, sınır koşulları
- **%80+ coverage hedefleyin**: Kritik yollara odaklanın
- **Testleri hızlı tutun**: Yavaş testleri ayırmak için marker'lar kullanın
### YAPMAYIN
- **İmplementasyonu test etmeyin**: Davranışı test edin, iç yapıyı değil
- **Testlerde karmaşık koşullar kullanmayın**: Testleri basit tutun
- **Test hatalarını göz ardı etmeyin**: Tüm testler geçmeli
- **Third-party kodu test etmeyin**: Kütüphanelerin çalıştığına güvenin
- **Testler arası state paylaşmayın**: Testler bağımsız olmalı
- **Testlerde exception yakalamayın**: `pytest.raises` kullanın
- **Print statement'ları kullanmayın**: Assertion'ları ve pytest çıktısını kullanın
- **Çok kırılgan testler yazmayın**: Aşırı spesifik mock'lardan kaçının
## Yaygın Desenler
### API Endpoint'lerini Test Etme (FastAPI/Flask)
```python
@pytest.fixture
def client():
app = create_app(testing=True)
return app.test_client()
def test_get_user(client):
response = client.get("/api/users/1")
assert response.status_code == 200
assert response.json["id"] == 1
def test_create_user(client):
response = client.post("/api/users", json={
"name": "Alice",
"email": "alice@example.com"
})
assert response.status_code == 201
assert response.json["name"] == "Alice"
```
### Veritabanı Operasyonlarını Test Etme
```python
@pytest.fixture
def db_session():
"""Test veritabanı oturumu oluştur."""
session = Session(bind=engine)
session.begin_nested()
yield session
session.rollback()
session.close()
def test_create_user(db_session):
user = User(name="Alice", email="alice@example.com")
db_session.add(user)
db_session.commit()
retrieved = db_session.query(User).filter_by(name="Alice").first()
assert retrieved.email == "alice@example.com"
```
### Class Metodlarını Test Etme
```python
class TestCalculator:
@pytest.fixture
def calculator(self):
return Calculator()
def test_add(self, calculator):
assert calculator.add(2, 3) == 5
def test_divide_by_zero(self, calculator):
with pytest.raises(ZeroDivisionError):
calculator.divide(10, 0)
```
## pytest Yapılandırması
### pytest.ini
```ini
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
--strict-markers
--disable-warnings
--cov=mypackage
--cov-report=term-missing
--cov-report=html
markers =
slow: marks tests as slow
integration: marks tests as integration tests
unit: marks tests as unit tests
```
### pyproject.toml
```toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--cov=mypackage",
"--cov-report=term-missing",
"--cov-report=html",
]
markers = [
"slow: marks tests as slow",
"integration: marks tests as integration tests",
"unit: marks tests as unit tests",
]
```
## Testleri Çalıştırma
```bash
# Tüm testleri çalıştır
pytest
# Belirli dosyayı çalıştır
pytest tests/test_utils.py
# Belirli testi çalıştır
pytest tests/test_utils.py::test_function
# Verbose çıktı ile çalıştır
pytest -v
# Coverage ile çalıştır
pytest --cov=mypackage --cov-report=html
# Sadece hızlı testleri çalıştır
pytest -m "not slow"
# İlk hataya kadar çalıştır
pytest -x
# N hataya kadar çalıştır
pytest --maxfail=3
# Son başarısız testleri çalıştır
pytest --lf
# Pattern ile testleri çalıştır
pytest -k "test_user"
# Hatada debugger ile çalıştır
pytest --pdb
```
## Hızlı Referans
| Desen | Kullanım |
|-------|----------|
| `pytest.raises()` | Beklenen exception'ları test et |
| `@pytest.fixture()` | Yeniden kullanılabilir test fixture'ları oluştur |
| `@pytest.mark.parametrize()` | Birden fazla input ile testleri çalıştır |
| `@pytest.mark.slow` | Yavaş testleri işaretle |
| `pytest -m "not slow"` | Yavaş testleri atla |
| `@patch()` | Fonksiyonları ve class'ları mock'la |
| `tmp_path` fixture | Otomatik geçici dizin |
| `pytest --cov` | Coverage raporu oluştur |
| `assert` | Basit ve okunabilir assertion'lar |
**Unutmayın**: Testler de koddur. Temiz, okunabilir ve bakımı kolay tutun. İyi testler hata yakalar; harika testler hataları önler.

View File

@@ -0,0 +1,499 @@
---
name: rust-patterns
description: Idiomatic Rust patterns, ownership, error handling, traits, concurrency, and best practices for building safe, performant applications.
origin: ECC
---
# Rust Geliştirme Desenleri
Güvenli, performanslı ve bakım yapılabilir uygulamalar oluşturmak için idiomatic Rust desenleri ve en iyi uygulamalar.
## Ne Zaman Kullanılır
- Yeni Rust kodu yazma
- Rust kodunu inceleme
- Mevcut Rust kodunu refactor etme
- Crate yapısı ve modül düzenini tasarlama
## Nasıl Çalışır
Bu skill altı ana alanda idiomatic Rust kurallarını zorlar: derleme zamanında veri yarışlarını önlemek için ownership ve borrowing, kütüphaneler için `thiserror` ve uygulamalar için `anyhow` ile `Result`/`?` hata yayılımı, yasadışı durumları temsil edilemez yapmak için enum'lar ve kapsamlı desen eşleştirme, sıfır maliyetli soyutlama için trait'ler ve generic'ler, `Arc<Mutex<T>>`, channel'lar ve async/await ile güvenli eşzamanlılık ve domain'e göre düzenlenmiş minimal `pub` yüzeyleri.
## Temel İlkeler
### 1. Ownership ve Borrowing
Rust'ın ownership sistemi derleme zamanında veri yarışlarını ve bellek hatalarını önler.
```rust
// İyi: Ownership'e ihtiyacınız olmadığında referansları geçirin
fn process(data: &[u8]) -> usize {
data.len()
}
// İyi: Saklamak veya tüketmek için ownership alın
fn store(data: Vec<u8>) -> Record {
Record { payload: data }
}
// Kötü: Borrow checker'dan kaçınmak için gereksiz clone
fn process_bad(data: &Vec<u8>) -> usize {
let cloned = data.clone(); // İsraf — sadece borrow alın
cloned.len()
}
```
### Esnek Ownership için `Cow` Kullanın
```rust
use std::borrow::Cow;
fn normalize(input: &str) -> Cow<'_, str> {
if input.contains(' ') {
Cow::Owned(input.replace(' ', "_"))
} else {
Cow::Borrowed(input) // Mutasyon gerekmediğinde sıfır maliyet
}
}
```
## Hata İşleme
### `Result` ve `?` Kullanın — Production'da Asla `unwrap()` Kullanmayın
```rust
// İyi: Hataları context ile yayın
use anyhow::{Context, Result};
fn load_config(path: &str) -> Result<Config> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("failed to read config from {path}"))?;
let config: Config = toml::from_str(&content)
.with_context(|| format!("failed to parse config from {path}"))?;
Ok(config)
}
// Kötü: Hata durumunda panic
fn load_config_bad(path: &str) -> Config {
let content = std::fs::read_to_string(path).unwrap(); // Panic!
toml::from_str(&content).unwrap()
}
```
### Kütüphane Hataları için `thiserror`, Uygulama Hataları için `anyhow`
```rust
// Kütüphane kodu: yapılandırılmış, tiplendirilmiş hatalar
use thiserror::Error;
#[derive(Debug, Error)]
pub enum StorageError {
#[error("record not found: {id}")]
NotFound { id: String },
#[error("connection failed")]
Connection(#[from] std::io::Error),
#[error("invalid data: {0}")]
InvalidData(String),
}
// Uygulama kodu: esnek hata işleme
use anyhow::{bail, Result};
fn run() -> Result<()> {
let config = load_config("app.toml")?;
if config.workers == 0 {
bail!("worker count must be > 0");
}
Ok(())
}
```
### İç İçe Eşleştirme Yerine `Option` Combinator'ları
```rust
// İyi: Combinator zinciri
fn find_user_email(users: &[User], id: u64) -> Option<String> {
users.iter()
.find(|u| u.id == id)
.map(|u| u.email.clone())
}
// Kötü: Derinlemesine iç içe eşleştirme
fn find_user_email_bad(users: &[User], id: u64) -> Option<String> {
match users.iter().find(|u| u.id == id) {
Some(user) => match &user.email {
email => Some(email.clone()),
},
None => None,
}
}
```
## Enum'lar ve Desen Eşleştirme
### Durumları Enum'lar Olarak Modelleyin
```rust
// İyi: İmkansız durumlar temsil edilemez
enum ConnectionState {
Disconnected,
Connecting { attempt: u32 },
Connected { session_id: String },
Failed { reason: String, retries: u32 },
}
fn handle(state: &ConnectionState) {
match state {
ConnectionState::Disconnected => connect(),
ConnectionState::Connecting { attempt } if *attempt > 3 => abort(),
ConnectionState::Connecting { .. } => wait(),
ConnectionState::Connected { session_id } => use_session(session_id),
ConnectionState::Failed { retries, .. } if *retries < 5 => retry(),
ConnectionState::Failed { reason, .. } => log_failure(reason),
}
}
```
### Kapsamlı Eşleştirme — İş Mantığı için Catch-All Yok
```rust
// İyi: Her varyantııkça işle
match command {
Command::Start => start_service(),
Command::Stop => stop_service(),
Command::Restart => restart_service(),
// Yeni bir varyant eklemek burada işlemeyi zorlar
}
// Kötü: Wildcard yeni varyantları gizler
match command {
Command::Start => start_service(),
_ => {} // Stop, Restart ve gelecek varyantları sessizce yok sayar
}
```
## Trait'ler ve Generic'ler
### Generic Girişleri Kabul Et, Somut Türleri Döndür
```rust
// İyi: Generic girdi, somut çıktı
fn read_all(reader: &mut impl Read) -> std::io::Result<Vec<u8>> {
let mut buf = Vec::new();
reader.read_to_end(&mut buf)?;
Ok(buf)
}
// İyi: Birden fazla kısıtlama için trait bound'ları
fn process<T: Display + Send + 'static>(item: T) -> String {
format!("processed: {item}")
}
```
### Dinamik Dispatch için Trait Object'leri
```rust
// Heterojen koleksiyonlara veya plugin sistemlerine ihtiyacınız olduğunda kullanın
trait Handler: Send + Sync {
fn handle(&self, request: &Request) -> Response;
}
struct Router {
handlers: Vec<Box<dyn Handler>>,
}
// Performansa ihtiyacınız olduğunda generic'leri kullanın (monomorfizasyon)
fn fast_process<H: Handler>(handler: &H, request: &Request) -> Response {
handler.handle(request)
}
```
### Tip Güvenliği için Newtype Deseni
```rust
// İyi: Farklı tipler argümanları karıştırmayı önler
struct UserId(u64);
struct OrderId(u64);
fn get_order(user: UserId, order: OrderId) -> Result<Order> {
// User ve order ID'lerini yanlışlıkla değiştiremezsiniz
todo!()
}
// Kötü: Argümanları değiştirmek kolay
fn get_order_bad(user_id: u64, order_id: u64) -> Result<Order> {
todo!()
}
```
## Struct'lar ve Veri Modelleme
### Karmaşık Yapılandırma için Builder Deseni
```rust
struct ServerConfig {
host: String,
port: u16,
max_connections: usize,
}
impl ServerConfig {
fn builder(host: impl Into<String>, port: u16) -> ServerConfigBuilder {
ServerConfigBuilder { host: host.into(), port, max_connections: 100 }
}
}
struct ServerConfigBuilder { host: String, port: u16, max_connections: usize }
impl ServerConfigBuilder {
fn max_connections(mut self, n: usize) -> Self { self.max_connections = n; self }
fn build(self) -> ServerConfig {
ServerConfig { host: self.host, port: self.port, max_connections: self.max_connections }
}
}
// Kullanım: ServerConfig::builder("localhost", 8080).max_connections(200).build()
```
## Iterator'lar ve Closure'lar
### Manuel Döngüler Yerine Iterator Zincirlerini Tercih Edin
```rust
// İyi: Deklaratif, lazy, birleştirilebilir
let active_emails: Vec<String> = users.iter()
.filter(|u| u.is_active)
.map(|u| u.email.clone())
.collect();
// Kötü: İmperatif biriktirme
let mut active_emails = Vec::new();
for user in &users {
if user.is_active {
active_emails.push(user.email.clone());
}
}
```
### Tip Annotation ile `collect()` Kullanın
```rust
// Farklı tiplere collect et
let names: Vec<_> = items.iter().map(|i| &i.name).collect();
let lookup: HashMap<_, _> = items.iter().map(|i| (i.id, i)).collect();
let combined: String = parts.iter().copied().collect();
// Result'ları collect et — ilk hatada kısa devre yapar
let parsed: Result<Vec<i32>, _> = strings.iter().map(|s| s.parse()).collect();
```
## Eşzamanlılık
### Paylaşılan Mutable State için `Arc<Mutex<T>>`
```rust
use std::sync::{Arc, Mutex};
let counter = Arc::new(Mutex::new(0));
let handles: Vec<_> = (0..10).map(|_| {
let counter = Arc::clone(&counter);
std::thread::spawn(move || {
let mut num = counter.lock().expect("mutex poisoned");
*num += 1;
})
}).collect();
for handle in handles {
handle.join().expect("worker thread panicked");
}
```
### Mesaj Geçişi için Channel'lar
```rust
use std::sync::mpsc;
let (tx, rx) = mpsc::sync_channel(16); // Backpressure ile bounded channel
for i in 0..5 {
let tx = tx.clone();
std::thread::spawn(move || {
tx.send(format!("message {i}")).expect("receiver disconnected");
});
}
drop(tx); // Sender'ı kapat böylece rx iterator sonlanır
for msg in rx {
println!("{msg}");
}
```
### Tokio ile Async
```rust
use tokio::time::Duration;
async fn fetch_with_timeout(url: &str) -> Result<String> {
let response = tokio::time::timeout(
Duration::from_secs(5),
reqwest::get(url),
)
.await
.context("request timed out")?
.context("request failed")?;
response.text().await.context("failed to read body")
}
// Eşzamanlı görevler spawn et
async fn fetch_all(urls: Vec<String>) -> Vec<Result<String>> {
let handles: Vec<_> = urls.into_iter()
.map(|url| tokio::spawn(async move {
fetch_with_timeout(&url).await
}))
.collect();
let mut results = Vec::with_capacity(handles.len());
for handle in handles {
results.push(handle.await.unwrap_or_else(|e| panic!("spawned task panicked: {e}")));
}
results
}
```
## Unsafe Kod
### Unsafe Ne Zaman Kabul Edilebilir
```rust
// Kabul edilebilir: Belgelenmiş değişmezlerle FFI sınırı (Rust 2024+)
/// # Safety
/// `ptr` başlatılmış bir `Widget`'a geçerli, hizalı bir pointer olmalıdır.
unsafe fn widget_from_raw<'a>(ptr: *const Widget) -> &'a Widget {
// SAFETY: çağıran ptr'nin geçerli ve hizalı olduğunu garanti eder
unsafe { &*ptr }
}
// Kabul edilebilir: Doğruluk kanıtı ile performans-kritik yol
// SAFETY: döngü sınırı nedeniyle index her zaman < len
unsafe { slice.get_unchecked(index) }
```
### Unsafe Ne Zaman Kabul EDİLEMEZ
```rust
// Kötü: Borrow checker'ı atlamak için unsafe kullanma
// Kötü: Kolaylık için unsafe kullanma
// Kötü: Safety yorumu olmadan unsafe kullanma
// Kötü: İlgisiz tipler arasında transmute etme
```
## Modül Sistemi ve Crate Yapısı
### Tipe Göre Değil, Domain'e Göre Düzenle
```text
my_app/
├── src/
│ ├── main.rs
│ ├── lib.rs
│ ├── auth/ # Domain modülü
│ │ ├── mod.rs
│ │ ├── token.rs
│ │ └── middleware.rs
│ ├── orders/ # Domain modülü
│ │ ├── mod.rs
│ │ ├── model.rs
│ │ └── service.rs
│ └── db/ # Altyapı
│ ├── mod.rs
│ └── pool.rs
├── tests/ # Entegrasyon testleri
├── benches/ # Benchmark'lar
└── Cargo.toml
```
### Görünürlük — Minimal Şekilde Açığa Çıkarın
```rust
// İyi: Dahili paylaşım için pub(crate)
pub(crate) fn validate_input(input: &str) -> bool {
!input.is_empty()
}
// İyi: lib.rs'den public API'yi yeniden export et
pub mod auth;
pub use auth::AuthMiddleware;
// Kötü: Her şeyi pub yapmak
pub fn internal_helper() {} // pub(crate) veya private olmalı
```
## Araç Entegrasyonu
### Temel Komutlar
```bash
# Build ve kontrol
cargo build
cargo check # Codegen olmadan hızlı tip kontrolü
cargo clippy # Lint'ler ve öneriler
cargo fmt # Kodu formatla
# Test etme
cargo test
cargo test -- --nocapture # println çıktısını göster
cargo test --lib # Sadece unit testler
cargo test --test integration # Sadece entegrasyon testleri
# Bağımlılıklar
cargo audit # Güvenlik denetimi
cargo tree # Bağımlılık ağacı
cargo update # Bağımlılıkları güncelle
# Performans
cargo bench # Benchmark'ları çalıştır
```
## Hızlı Referans: Rust Deyimleri
| Deyim | Açıklama |
|-------|----------|
| Clone etme, borrow al | Ownership gerekmedikçe clone yerine `&T` geçir |
| Yasadışı durumları temsil edilemez yap | Sadece geçerli durumları modellemek için enum'ları kullan |
| `unwrap()` yerine `?` | Hataları yay, kütüphane/production kodunda asla panic |
| Validate etme, parse et | Sınırda yapılandırılmamış veriyi tiplendirilmiş struct'lara dönüştür |
| Tip güvenliği için newtype | Argüman değişimlerini önlemek için primitive'leri newtype'lara sar |
| Döngüler yerine iterator'ları tercih et | Deklaratif zincirler daha net ve genellikle daha hızlı |
| Result'larda `#[must_use]` | Çağıranların dönüş değerlerini işlemesini garanti et |
| Esnek ownership için `Cow` | Borrow yeterli olduğunda allocation'lardan kaçın |
| Kapsamlı eşleştirme | İş-kritik enum'lar için wildcard `_` yok |
| Minimal `pub` yüzeyi | Dahili API'ler için `pub(crate)` kullan |
## Kaçınılacak Anti-Desenler
```rust
// Kötü: Production kodunda .unwrap()
let value = map.get("key").unwrap();
// Kötü: Nedenini anlamadan borrow checker'ı tatmin etmek için .clone()
let data = expensive_data.clone();
process(&original, &data);
// Kötü: &str yeterken String kullanma
fn greet(name: String) { /* &str olmalı */ }
// Kötü: Kütüphanelerde Box<dyn Error> (yerine thiserror kullanın)
fn parse(input: &str) -> Result<Data, Box<dyn std::error::Error>> { todo!() }
// Kötü: must_use uyarılarını yok sayma
let _ = validate(input); // Bir Result'ı sessizce atma
// Kötü: Async context'te bloke etme
async fn bad_async() {
std::thread::sleep(Duration::from_secs(1)); // Executor'ı bloke eder!
// Kullanın: tokio::time::sleep(Duration::from_secs(1)).await;
}
```
**Unutmayın**: Derlenir ise muhtemelen doğrudur — ama sadece `unwrap()` kullanmaktan kaçınır, `unsafe`'i minimize eder ve tip sisteminin sizin için çalışmasına izin verirseniz.

View File

@@ -0,0 +1,500 @@
---
name: rust-testing
description: Rust testing patterns including unit tests, integration tests, async testing, property-based testing, mocking, and coverage. Follows TDD methodology.
origin: ECC
---
# Rust Test Desenleri
TDD metodolojisini takip ederek güvenilir, bakım yapılabilir testler yazmak için kapsamlı Rust test desenleri.
## Ne Zaman Kullanılır
- Yeni Rust fonksiyonları, metotları veya trait'leri yazma
- Mevcut koda test kapsamı ekleme
- Performans-kritik kod için benchmark'lar oluşturma
- Girdi doğrulama için property-based testler uygulama
- Rust projelerinde TDD iş akışını takip etme
## Nasıl Çalışır
1. **Hedef kodu tanımla** — Test edilecek fonksiyon, trait veya modülü bul
2. **Bir test yaz**`#[cfg(test)]` modülünde `#[test]` kullan, parametreli testler için rstest veya property-based testler için proptest
3. **Bağımlılıkları mock'la** — Test altındaki birimi izole etmek için mockall kullan
4. **Testleri çalıştır (RED)** — Testin beklenen hata ile başarısız olduğunu doğrula
5. **Uygula (GREEN)** — Geçmek için minimal kod yaz
6. **Refactor** — Testleri yeşil tutarken iyileştir
7. **Kapsamı kontrol et** — cargo-llvm-cov kullan, 80%+ hedefle
## Rust için TDD İş Akışı
### RED-GREEN-REFACTOR Döngüsü
```
RED → Önce başarısız bir test yaz
GREEN → Testi geçmek için minimal kod yaz
REFACTOR → Testleri yeşil tutarken kodu iyileştir
REPEAT → Bir sonraki gereksinimle devam et
```
### Rust'ta Adım-Adım TDD
```rust
// RED: Önce testi yaz, yer tutucu olarak todo!() kullan
pub fn add(a: i32, b: i32) -> i32 { todo!() }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() { assert_eq!(add(2, 3), 5); }
}
// cargo test → 'not yet implemented'da panic
```
```rust
// GREEN: todo!()'yu minimal implementasyonla değiştir
pub fn add(a: i32, b: i32) -> i32 { a + b }
// cargo test → GEÇTİ, sonra testleri yeşil tutarken REFACTOR
```
## Unit Testler
### Modül Seviyesi Test Organizasyonu
```rust
// src/user.rs
pub struct User {
pub name: String,
pub email: String,
}
impl User {
pub fn new(name: impl Into<String>, email: impl Into<String>) -> Result<Self, String> {
let email = email.into();
if !email.contains('@') {
return Err(format!("invalid email: {email}"));
}
Ok(Self { name: name.into(), email })
}
pub fn display_name(&self) -> &str {
&self.name
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creates_user_with_valid_email() {
let user = User::new("Alice", "alice@example.com").unwrap();
assert_eq!(user.display_name(), "Alice");
assert_eq!(user.email, "alice@example.com");
}
#[test]
fn rejects_invalid_email() {
let result = User::new("Bob", "not-an-email");
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid email"));
}
}
```
### Assertion Makroları
```rust
assert_eq!(2 + 2, 4); // Eşitlik
assert_ne!(2 + 2, 5); // Eşitsizlik
assert!(vec![1, 2, 3].contains(&2)); // Boolean
assert_eq!(value, 42, "expected 42 but got {value}"); // Özel mesaj
assert!((0.1_f64 + 0.2 - 0.3).abs() < f64::EPSILON); // Float karşılaştırma
```
## Hata ve Panic Testi
### `Result` Dönüşlerini Test Etme
```rust
#[test]
fn parse_returns_error_for_invalid_input() {
let result = parse_config("}{invalid");
assert!(result.is_err());
// Spesifik hata varyantını doğrula
let err = result.unwrap_err();
assert!(matches!(err, ConfigError::ParseError(_)));
}
#[test]
fn parse_succeeds_for_valid_input() -> Result<(), Box<dyn std::error::Error>> {
let config = parse_config(r#"{"port": 8080}"#)?;
assert_eq!(config.port, 8080);
Ok(()) // Herhangi bir ? Err döndürürse test başarısız olur
}
```
### Panic'leri Test Etme
```rust
#[test]
#[should_panic]
fn panics_on_empty_input() {
process(&[]);
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn panics_with_specific_message() {
let v: Vec<i32> = vec![];
let _ = v[0];
}
```
## Entegrasyon Testleri
### Dosya Yapısı
```text
my_crate/
├── src/
│ └── lib.rs
├── tests/ # Entegrasyon testleri
│ ├── api_test.rs # Her dosya ayrı bir test binary'si
│ ├── db_test.rs
│ └── common/ # Paylaşılan test yardımcıları
│ └── mod.rs
```
### Entegrasyon Testleri Yazma
```rust
// tests/api_test.rs
use my_crate::{App, Config};
#[test]
fn full_request_lifecycle() {
let config = Config::test_default();
let app = App::new(config);
let response = app.handle_request("/health");
assert_eq!(response.status, 200);
assert_eq!(response.body, "OK");
}
```
## Async Testler
### Tokio ile
```rust
#[tokio::test]
async fn fetches_data_successfully() {
let client = TestClient::new().await;
let result = client.get("/data").await;
assert!(result.is_ok());
assert_eq!(result.unwrap().items.len(), 3);
}
#[tokio::test]
async fn handles_timeout() {
use std::time::Duration;
let result = tokio::time::timeout(
Duration::from_millis(100),
slow_operation(),
).await;
assert!(result.is_err(), "should have timed out");
}
```
## Test Organizasyon Desenleri
### `rstest` ile Parametreli Testler
```rust
use rstest::{rstest, fixture};
#[rstest]
#[case("hello", 5)]
#[case("", 0)]
#[case("rust", 4)]
fn test_string_length(#[case] input: &str, #[case] expected: usize) {
assert_eq!(input.len(), expected);
}
// Fixture'lar
#[fixture]
fn test_db() -> TestDb {
TestDb::new_in_memory()
}
#[rstest]
fn test_insert(test_db: TestDb) {
test_db.insert("key", "value");
assert_eq!(test_db.get("key"), Some("value".into()));
}
```
### Test Yardımcıları
```rust
#[cfg(test)]
mod tests {
use super::*;
/// Mantıklı varsayılanlarla test kullanıcısı oluşturur.
fn make_user(name: &str) -> User {
User::new(name, &format!("{name}@test.com")).unwrap()
}
#[test]
fn user_display() {
let user = make_user("alice");
assert_eq!(user.display_name(), "alice");
}
}
```
## `proptest` ile Property-Based Testing
### Temel Property Testleri
```rust
use proptest::prelude::*;
proptest! {
#[test]
fn encode_decode_roundtrip(input in ".*") {
let encoded = encode(&input);
let decoded = decode(&encoded).unwrap();
assert_eq!(input, decoded);
}
#[test]
fn sort_preserves_length(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
let original_len = vec.len();
vec.sort();
assert_eq!(vec.len(), original_len);
}
#[test]
fn sort_produces_ordered_output(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
vec.sort();
for window in vec.windows(2) {
assert!(window[0] <= window[1]);
}
}
}
```
### Özel Stratejiler
```rust
use proptest::prelude::*;
fn valid_email() -> impl Strategy<Value = String> {
("[a-z]{1,10}", "[a-z]{1,5}")
.prop_map(|(user, domain)| format!("{user}@{domain}.com"))
}
proptest! {
#[test]
fn accepts_valid_emails(email in valid_email()) {
assert!(User::new("Test", &email).is_ok());
}
}
```
## `mockall` ile Mock'lama
### Trait-Tabanlı Mock'lama
```rust
use mockall::{automock, predicate::eq};
#[automock]
trait UserRepository {
fn find_by_id(&self, id: u64) -> Option<User>;
fn save(&self, user: &User) -> Result<(), StorageError>;
}
#[test]
fn service_returns_user_when_found() {
let mut mock = MockUserRepository::new();
mock.expect_find_by_id()
.with(eq(42))
.times(1)
.returning(|_| Some(User { id: 42, name: "Alice".into() }));
let service = UserService::new(Box::new(mock));
let user = service.get_user(42).unwrap();
assert_eq!(user.name, "Alice");
}
#[test]
fn service_returns_none_when_not_found() {
let mut mock = MockUserRepository::new();
mock.expect_find_by_id()
.returning(|_| None);
let service = UserService::new(Box::new(mock));
assert!(service.get_user(99).is_none());
}
```
## Doc Testleri
### Çalıştırılabilir Dokümantasyon
```rust
/// İki sayıyı toplar.
///
/// # Examples
///
/// ```
/// use my_crate::add;
///
/// assert_eq!(add(2, 3), 5);
/// assert_eq!(add(-1, 1), 0);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// Bir config string'i parse eder.
///
/// # Errors
///
/// Girdi geçerli TOML değilse `Err` döner.
///
/// ```no_run
/// use my_crate::parse_config;
///
/// let config = parse_config(r#"port = 8080"#).unwrap();
/// assert_eq!(config.port, 8080);
/// ```
///
/// ```no_run
/// use my_crate::parse_config;
///
/// assert!(parse_config("}{invalid").is_err());
/// ```
pub fn parse_config(input: &str) -> Result<Config, ParseError> {
todo!()
}
```
## Criterion ile Benchmark'lama
```toml
# Cargo.toml
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "benchmark"
harness = false
```
```rust
// benches/benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci(n: u64) -> u64 {
match n {
0 | 1 => n,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn bench_fibonacci(c: &mut Criterion) {
c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}
criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);
```
## Test Kapsamı
### Kapsamı Çalıştırma
```bash
# Kurulum: cargo install cargo-llvm-cov (veya CI'da taiki-e/install-action kullan)
cargo llvm-cov # Özet
cargo llvm-cov --html # HTML raporu
cargo llvm-cov --lcov > lcov.info # CI için LCOV formatı
cargo llvm-cov --fail-under-lines 80 # Eşiğin altındaysa başarısız yap
```
### Kapsam Hedefleri
| Kod Tipi | Hedef |
|----------|-------|
| Kritik iş mantığı | 100% |
| Public API | 90%+ |
| Genel kod | 80%+ |
| Oluşturulmuş / FFI binding'leri | Hariç tut |
## Test Komutları
```bash
cargo test # Tüm testleri çalıştır
cargo test -- --nocapture # println çıktısını göster
cargo test test_name # Desene uyan testleri çalıştır
cargo test --lib # Sadece unit testler
cargo test --test api_test # Sadece entegrasyon testleri
cargo test --doc # Sadece doc testleri
cargo test --no-fail-fast # İlk başarısızlıkta durma
cargo test -- --ignored # Yok sayılan testleri çalıştır
```
## En İyi Uygulamalar
**YAPIN:**
- ÖNCE testleri yazın (TDD)
- Unit testler için `#[cfg(test)]` modülleri kullanın
- Implementasyon değil, davranışı test edin
- Senaryoyu açıklayan açıklayıcı test isimleri kullanın
- Daha iyi hata mesajları için `assert!` yerine `assert_eq!` tercih edin
- Daha temiz hata çıktısı için `Result` döndüren testlerde `?` kullanın
- Testleri bağımsız tutun — paylaşılan mutable state yok
**YAPMAYIN:**
- `Result::is_err()` test edebiliyorsanız `#[should_panic]` kullanmayın
- Her şeyi mock'lamayın — mümkün olduğunda entegrasyon testlerini tercih edin
- Kararsız testleri yok saymayın — düzeltin veya karantinaya alın
- Testlerde `sleep()` kullanmayın — channel'lar, barrier'lar veya `tokio::time::pause()` kullanın
- Hata yolu testini atlamayın
## CI Entegrasyonu
```yaml
# GitHub Actions
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- name: Check formatting
run: cargo fmt --check
- name: Clippy
run: cargo clippy -- -D warnings
- name: Run tests
run: cargo test
- uses: taiki-e/install-action@cargo-llvm-cov
- name: Coverage
run: cargo llvm-cov --fail-under-lines 80
```
**Unutmayın**: Testler dokümantasyondur. Kodunuzun nasıl kullanılması gerektiğini gösterirler. Onları net yazın ve güncel tutun.

View File

@@ -0,0 +1,495 @@
---
name: security-review
description: Kimlik doğrulama eklerken, kullanıcı girdisi işlerken, secret'larla çalışırken, API endpoint'leri oluştururken veya ödeme/hassas özellikler uygularken bu skill'i kullanın. Kapsamlı güvenlik kontrol listesi ve kalıplar sağlar.
origin: ECC
---
# Güvenlik İnceleme Skill'i
Bu skill tüm kodun güvenlik en iyi uygulamalarını takip etmesini sağlar ve potansiyel güvenlik açıklarını tanımlar.
## Ne Zaman Aktifleştirmelisiniz
- Kimlik doğrulama veya yetkilendirme uygularken
- Kullanıcı girdisi veya dosya yüklemeleri işlerken
- Yeni API endpoint'leri oluştururken
- Secret'lar veya kimlik bilgileriyle çalışırken
- Ödeme özellikleri uygularken
- Hassas veri saklarken veya iletirken
- Üçüncü taraf API'leri entegre ederken
## Güvenlik Kontrol Listesi
### 1. Secret Yönetimi
#### ❌ ASLA Bunu Yapmayın
```typescript
const apiKey = "sk-proj-xxxxx" // Hardcoded secret
const dbPassword = "password123" // Kaynak kodda
```
#### ✅ HER ZAMAN Bunu Yapın
```typescript
const apiKey = process.env.OPENAI_API_KEY
const dbUrl = process.env.DATABASE_URL
// Secret'ların var olduğunu doğrula
if (!apiKey) {
throw new Error('OPENAI_API_KEY not configured')
}
```
#### Doğrulama Adımları
- [ ] Hardcoded API key, token veya şifre yok
- [ ] Tüm secret'lar environment variable'larda
- [ ] `.env.local` .gitignore'da
- [ ] Git history'de secret yok
- [ ] Production secret'ları hosting platformunda (Vercel, Railway)
### 2. Input Doğrulama
#### Her Zaman Kullanıcı Girdisini Doğrulayın
```typescript
import { z } from 'zod'
// Doğrulama şeması tanımla
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150)
})
// İşlemeden önce doğrula
export async function createUser(input: unknown) {
try {
const validated = CreateUserSchema.parse(input)
return await db.users.create(validated)
} catch (error) {
if (error instanceof z.ZodError) {
return { success: false, errors: error.errors }
}
throw error
}
}
```
#### Dosya Yükleme Doğrulama
```typescript
function validateFileUpload(file: File) {
// Boyut kontrolü (5MB max)
const maxSize = 5 * 1024 * 1024
if (file.size > maxSize) {
throw new Error('Dosya çok büyük (max 5MB)')
}
// Tip kontrolü
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
if (!allowedTypes.includes(file.type)) {
throw new Error('Geçersiz dosya tipi')
}
// Uzantı kontrolü
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif']
const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0]
if (!extension || !allowedExtensions.includes(extension)) {
throw new Error('Geçersiz dosya uzantısı')
}
return true
}
```
#### Doğrulama Adımları
- [ ] Tüm kullanıcı girdileri şema ile doğrulanmış
- [ ] Dosya yüklemeleri kısıtlanmış (boyut, tip, uzantı)
- [ ] Kullanıcı girdisi doğrudan sorgularda kullanılmıyor
- [ ] Whitelist doğrulama (blacklist değil)
- [ ] Hata mesajları hassas bilgi sızdırmıyor
### 3. SQL Injection Önleme
#### ❌ ASLA SQL Concatenation Yapmayın
```typescript
// TEHLİKELİ - SQL Injection açığı
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
await db.query(query)
```
#### ✅ HER ZAMAN Parametreli Sorgular Kullanın
```typescript
// Güvenli - parametreli sorgu
const { data } = await supabase
.from('users')
.select('*')
.eq('email', userEmail)
// Veya raw SQL ile
await db.query(
'SELECT * FROM users WHERE email = $1',
[userEmail]
)
```
#### Doğrulama Adımları
- [ ] Tüm veritabanı sorguları parametreli
- [ ] SQL'de string concatenation yok
- [ ] ORM/query builder doğru kullanılıyor
- [ ] Supabase sorguları düzgün sanitize edilmiş
### 4. Kimlik Doğrulama ve Yetkilendirme
#### JWT Token İşleme
```typescript
// ❌ YANLIŞ: localStorage (XSS'e karşı savunmasız)
localStorage.setItem('token', token)
// ✅ DOĞRU: httpOnly cookies
res.setHeader('Set-Cookie',
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
```
#### Yetkilendirme Kontrolleri
```typescript
export async function deleteUser(userId: string, requesterId: string) {
// HER ZAMAN önce yetkilendirmeyi doğrula
const requester = await db.users.findUnique({
where: { id: requesterId }
})
if (requester.role !== 'admin') {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 403 }
)
}
// Silme işlemine devam et
await db.users.delete({ where: { id: userId } })
}
```
#### Row Level Security (Supabase)
```sql
-- Tüm tablolarda RLS'yi aktifleştir
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- Kullanıcılar sadece kendi verilerini görebilir
CREATE POLICY "Users view own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- Kullanıcılar sadece kendi verilerini güncelleyebilir
CREATE POLICY "Users update own data"
ON users FOR UPDATE
USING (auth.uid() = id);
```
#### Doğrulama Adımları
- [ ] Token'lar httpOnly cookie'lerde (localStorage'da değil)
- [ ] Hassas operasyonlardan önce yetkilendirme kontrolleri
- [ ] Supabase'de Row Level Security aktif
- [ ] Rol tabanlı erişim kontrolü uygulanmış
- [ ] Session yönetimi güvenli
### 5. XSS Önleme
#### HTML'i Sanitize Et
```typescript
import DOMPurify from 'isomorphic-dompurify'
// HER ZAMAN kullanıcı tarafından sağlanan HTML'i sanitize et
function renderUserContent(html: string) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'],
ALLOWED_ATTR: []
})
return <div dangerouslySetInnerHTML={{ __html: clean }} />
}
```
#### Content Security Policy
```typescript
// next.config.js
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
`.replace(/\s{2,}/g, ' ').trim()
}
]
```
#### Doğrulama Adımları
- [ ] Kullanıcı tarafından sağlanan HTML sanitize edilmiş
- [ ] CSP başlıkları yapılandırılmış
- [ ] Doğrulanmamış dinamik içerik render'ı yok
- [ ] React'in yerleşik XSS koruması kullanılıyor
### 6. CSRF Koruması
#### CSRF Token'ları
```typescript
import { csrf } from '@/lib/csrf'
export async function POST(request: Request) {
const token = request.headers.get('X-CSRF-Token')
if (!csrf.verify(token)) {
return NextResponse.json(
{ error: 'Invalid CSRF token' },
{ status: 403 }
)
}
// İsteği işle
}
```
#### SameSite Cookie'ler
```typescript
res.setHeader('Set-Cookie',
`session=${sessionId}; HttpOnly; Secure; SameSite=Strict`)
```
#### Doğrulama Adımları
- [ ] State değiştiren operasyonlarda CSRF token'ları
- [ ] Tüm cookie'lerde SameSite=Strict
- [ ] Double-submit cookie pattern uygulanmış
### 7. Rate Limiting
#### API Rate Limiting
```typescript
import rateLimit from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 dakika
max: 100, // Pencere başına 100 istek
message: 'Çok fazla istek'
})
// Route'lara uygula
app.use('/api/', limiter)
```
#### Pahalı Operasyonlar
```typescript
// Aramalar için agresif rate limiting
const searchLimiter = rateLimit({
windowMs: 60 * 1000, // 1 dakika
max: 10, // Dakikada 10 istek
message: 'Çok fazla arama isteği'
})
app.use('/api/search', searchLimiter)
```
#### Doğrulama Adımları
- [ ] Tüm API endpoint'lerinde rate limiting
- [ ] Pahalı operasyonlarda daha sıkı limitler
- [ ] IP tabanlı rate limiting
- [ ] Kullanıcı tabanlı rate limiting (authenticated)
### 8. Hassas Veri İfşası
#### Loglama
```typescript
// ❌ YANLIŞ: Hassas veri loglama
console.log('User login:', { email, password })
console.log('Payment:', { cardNumber, cvv })
// ✅ DOĞRU: Hassas veriyi gizle
console.log('User login:', { email, userId })
console.log('Payment:', { last4: card.last4, userId })
```
#### Hata Mesajları
```typescript
// ❌ YANLIŞ: İç detaylarıığa çıkarma
catch (error) {
return NextResponse.json(
{ error: error.message, stack: error.stack },
{ status: 500 }
)
}
// ✅ DOĞRU: Genel hata mesajları
catch (error) {
console.error('Internal error:', error)
return NextResponse.json(
{ error: 'Bir hata oluştu. Lütfen tekrar deneyin.' },
{ status: 500 }
)
}
```
#### Doğrulama Adımları
- [ ] Loglarda şifre, token veya secret yok
- [ ] Kullanıcılar için genel hata mesajları
- [ ] Detaylı hatalar sadece sunucu loglarında
- [ ] Kullanıcılara stack trace gösterilmiyor
### 9. Blockchain Güvenliği (Solana)
#### Wallet Doğrulama
```typescript
import { verify } from '@solana/web3.js'
async function verifyWalletOwnership(
publicKey: string,
signature: string,
message: string
) {
try {
const isValid = verify(
Buffer.from(message),
Buffer.from(signature, 'base64'),
Buffer.from(publicKey, 'base64')
)
return isValid
} catch (error) {
return false
}
}
```
#### Transaction Doğrulama
```typescript
async function verifyTransaction(transaction: Transaction) {
// Alıcıyı doğrula
if (transaction.to !== expectedRecipient) {
throw new Error('Geçersiz alıcı')
}
// Miktarı doğrula
if (transaction.amount > maxAmount) {
throw new Error('Miktar limiti aşıyor')
}
// Kullanıcının yeterli bakiyesi olduğunu doğrula
const balance = await getBalance(transaction.from)
if (balance < transaction.amount) {
throw new Error('Yetersiz bakiye')
}
return true
}
```
#### Doğrulama Adımları
- [ ] Wallet imzaları doğrulanmış
- [ ] Transaction detayları validate edilmiş
- [ ] Transaction'lardan önce bakiye kontrolleri
- [ ] Kör transaction imzalama yok
### 10. Bağımlılık Güvenliği
#### Düzenli Güncellemeler
```bash
# Güvenlik açıklarını kontrol et
npm audit
# Otomatik düzeltilebilir sorunları düzelt
npm audit fix
# Bağımlılıkları güncelle
npm update
# Eski paketleri kontrol et
npm outdated
```
#### Lock Dosyaları
```bash
# HER ZAMAN lock dosyalarını commit et
git add package-lock.json
# CI/CD'de tekrarlanabilir build'ler için kullan
npm ci # npm install yerine
```
#### Doğrulama Adımları
- [ ] Bağımlılıklar güncel
- [ ] Bilinen güvenlik açığı yok (npm audit clean)
- [ ] Lock dosyaları commit edilmiş
- [ ] GitHub'da Dependabot aktif
- [ ] Düzenli güvenlik güncellemeleri
## Güvenlik Testi
### Otomatik Güvenlik Testleri
```typescript
// Kimlik doğrulama testi
test('kimlik doğrulama gerektirir', async () => {
const response = await fetch('/api/protected')
expect(response.status).toBe(401)
})
// Yetkilendirme testi
test('admin rolü gerektirir', async () => {
const response = await fetch('/api/admin', {
headers: { Authorization: `Bearer ${userToken}` }
})
expect(response.status).toBe(403)
})
// Input doğrulama testi
test('geçersiz input'u reddeder', async () => {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify({ email: 'not-an-email' })
})
expect(response.status).toBe(400)
})
// Rate limiting testi
test('rate limit'leri zorlar', async () => {
const requests = Array(101).fill(null).map(() =>
fetch('/api/endpoint')
)
const responses = await Promise.all(requests)
const tooManyRequests = responses.filter(r => r.status === 429)
expect(tooManyRequests.length).toBeGreaterThan(0)
})
```
## Deployment Öncesi Güvenlik Kontrol Listesi
HERHANGİ bir production deployment'ından önce:
- [ ] **Secret'lar**: Hardcoded secret yok, hepsi env var'larda
- [ ] **Input Doğrulama**: Tüm kullanıcı girdileri validate edilmiş
- [ ] **SQL Injection**: Tüm sorgular parametreli
- [ ] **XSS**: Kullanıcı içeriği sanitize edilmiş
- [ ] **CSRF**: Koruma aktif
- [ ] **Kimlik Doğrulama**: Doğru token işleme
- [ ] **Yetkilendirme**: Rol kontrolleri yerinde
- [ ] **Rate Limiting**: Tüm endpoint'lerde aktif
- [ ] **HTTPS**: Production'da zorunlu
- [ ] **Güvenlik Başlıkları**: CSP, X-Frame-Options yapılandırılmış
- [ ] **Hata İşleme**: Hatalarda hassas veri yok
- [ ] **Loglama**: Hassas veri loglanmıyor
- [ ] **Bağımlılıklar**: Güncel, güvenlik açığı yok
- [ ] **Row Level Security**: Supabase'de aktif
- [ ] **CORS**: Düzgün yapılandırılmış
- [ ] **Dosya Yüklemeleri**: Validate edilmiş (boyut, tip)
- [ ] **Wallet İmzaları**: Doğrulanmış (blockchain varsa)
## Kaynaklar
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [Next.js Security](https://nextjs.org/docs/security)
- [Supabase Security](https://supabase.com/docs/guides/auth)
- [Web Security Academy](https://portswigger.net/web-security)
---
**Unutmayın**: Güvenlik opsiyonel değildir. Bir güvenlik açığı tüm platformu tehlikeye atabilir. Şüphe duyduğunuzda ihtiyatlı olun.

View File

@@ -0,0 +1,312 @@
---
name: springboot-patterns
description: Spring Boot architecture patterns, REST API design, layered services, data access, caching, async processing, and logging. Use for Java Spring Boot backend work.
origin: ECC
---
# Spring Boot Geliştirme Desenleri
Ölçeklenebilir, üretim seviyesi servisler için Spring Boot mimari ve API desenleri.
## Ne Zaman Aktif Edilir
- Spring MVC veya WebFlux ile REST API'leri oluşturma
- Controller → service → repository katmanlarını yapılandırma
- Spring Data JPA, caching veya async processing'i yapılandırma
- Validation, exception handling veya sayfalama ekleme
- Dev/staging/production ortamları için profiller kurma
- Spring Events veya Kafka ile event-driven desenler uygulama
## REST API Yapısı
```java
@RestController
@RequestMapping("/api/markets")
@Validated
class MarketController {
private final MarketService marketService;
MarketController(MarketService marketService) {
this.marketService = marketService;
}
@GetMapping
ResponseEntity<Page<MarketResponse>> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Page<Market> markets = marketService.list(PageRequest.of(page, size));
return ResponseEntity.ok(markets.map(MarketResponse::from));
}
@PostMapping
ResponseEntity<MarketResponse> create(@Valid @RequestBody CreateMarketRequest request) {
Market market = marketService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(MarketResponse::from(market));
}
}
```
## Repository Deseni (Spring Data JPA)
```java
public interface MarketRepository extends JpaRepository<MarketEntity, Long> {
@Query("select m from MarketEntity m where m.status = :status order by m.volume desc")
List<MarketEntity> findActive(@Param("status") MarketStatus status, Pageable pageable);
}
```
## Transaction'lı Service Katmanı
```java
@Service
public class MarketService {
private final MarketRepository repo;
public MarketService(MarketRepository repo) {
this.repo = repo;
}
@Transactional
public Market create(CreateMarketRequest request) {
MarketEntity entity = MarketEntity.from(request);
MarketEntity saved = repo.save(entity);
return Market.from(saved);
}
}
```
## DTO'lar ve Validation
```java
public record CreateMarketRequest(
@NotBlank @Size(max = 200) String name,
@NotBlank @Size(max = 2000) String description,
@NotNull @FutureOrPresent Instant endDate,
@NotEmpty List<@NotBlank String> categories) {}
public record MarketResponse(Long id, String name, MarketStatus status) {
static MarketResponse from(Market market) {
return new MarketResponse(market.id(), market.name(), market.status());
}
}
```
## Exception Handling
```java
@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<ApiError> handleValidation(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.joining(", "));
return ResponseEntity.badRequest().body(ApiError.validation(message));
}
@ExceptionHandler(AccessDeniedException.class)
ResponseEntity<ApiError> handleAccessDenied() {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiError.of("Forbidden"));
}
@ExceptionHandler(Exception.class)
ResponseEntity<ApiError> handleGeneric(Exception ex) {
// Beklenmeyen hataları stack trace'ler ile loglayın
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiError.of("Internal server error"));
}
}
```
## Caching
Bir configuration sınıfında `@EnableCaching` gerektirir.
```java
@Service
public class MarketCacheService {
private final MarketRepository repo;
public MarketCacheService(MarketRepository repo) {
this.repo = repo;
}
@Cacheable(value = "market", key = "#id")
public Market getById(Long id) {
return repo.findById(id)
.map(Market::from)
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
}
@CacheEvict(value = "market", key = "#id")
public void evict(Long id) {}
}
```
## Async Processing
Bir configuration sınıfında `@EnableAsync` gerektirir.
```java
@Service
public class NotificationService {
@Async
public CompletableFuture<Void> sendAsync(Notification notification) {
// email/SMS gönder
return CompletableFuture.completedFuture(null);
}
}
```
## Loglama (SLF4J)
```java
@Service
public class ReportService {
private static final Logger log = LoggerFactory.getLogger(ReportService.class);
public Report generate(Long marketId) {
log.info("generate_report marketId={}", marketId);
try {
// mantık
} catch (Exception ex) {
log.error("generate_report_failed marketId={}", marketId, ex);
throw ex;
}
return new Report();
}
}
```
## Middleware / Filter'lar
```java
@Component
public class RequestLoggingFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
long start = System.currentTimeMillis();
try {
filterChain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - start;
log.info("req method={} uri={} status={} durationMs={}",
request.getMethod(), request.getRequestURI(), response.getStatus(), duration);
}
}
}
```
## Sayfalama ve Sıralama
```java
PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());
Page<Market> results = marketService.list(page);
```
## Hata-Dayanıklı Harici Çağrılar
```java
public <T> T withRetry(Supplier<T> supplier, int maxRetries) {
int attempts = 0;
while (true) {
try {
return supplier.get();
} catch (Exception ex) {
attempts++;
if (attempts >= maxRetries) {
throw ex;
}
try {
Thread.sleep((long) Math.pow(2, attempts) * 100L);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw ex;
}
}
}
}
```
## Rate Limiting (Filter + Bucket4j)
**Güvenlik Notu**: `X-Forwarded-For` başlığı varsayılan olarak güvenilmezdir çünkü istemciler onu taklit edebilir.
Forwarded başlıkları sadece şu durumlarda kullanın:
1. Uygulamanız güvenilir bir reverse proxy'nin arkasında (nginx, AWS ALB, vb.)
2. `ForwardedHeaderFilter`'ı bean olarak kaydetmişsiniz
3. application properties'de `server.forward-headers-strategy=NATIVE` veya `FRAMEWORK` yapılandırmışsınız
4. Proxy'niz `X-Forwarded-For` başlığını üzerine yazmak için yapılandırılmış (eklememek için değil)
`ForwardedHeaderFilter` düzgün yapılandırıldığında, `request.getRemoteAddr()` otomatik olarak
forwarded başlıklardan doğru istemci IP'sini döndürür. Bu yapılandırma olmadan, `request.getRemoteAddr()` doğrudan kullanın—anlık bağlantı IP'sini döndürür, bu güvenilir tek değerdir.
```java
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
/*
* GÜVENLİK: Bu filtre rate limiting için istemcileri tanımlamak üzere request.getRemoteAddr() kullanır.
*
* Uygulamanız bir reverse proxy'nin (nginx, AWS ALB, vb.) arkasındaysa, doğru istemci IP tespiti için
* Spring'i forwarded başlıkları düzgün işleyecek şekilde yapılandırmalısınız:
*
* 1. application.properties/yaml'da server.forward-headers-strategy=NATIVE (cloud platformlar için)
* veya FRAMEWORK ayarlayın
* 2. FRAMEWORK stratejisi kullanıyorsanız, ForwardedHeaderFilter'ı kaydedin:
*
* @Bean
* ForwardedHeaderFilter forwardedHeaderFilter() {
* return new ForwardedHeaderFilter();
* }
*
* 3. Proxy'nizin sahteciliği önlemek için X-Forwarded-For başlığını üzerine yazdığından emin olun (eklemediğinden)
* 4. Container'ınız için server.tomcat.remoteip.trusted-proxies veya eşdeğerini yapılandırın
*
* Bu yapılandırma olmadan, request.getRemoteAddr() istemci IP'si değil proxy IP'si döndürür.
* X-Forwarded-For'u doğrudan okumayın—güvenilir proxy işleme olmadan kolayca taklit edilebilir.
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// ForwardedHeaderFilter yapılandırıldığında doğru istemci IP'sini döndüren
// veya aksi halde doğrudan bağlantı IP'sini döndüren getRemoteAddr() kullanın. X-Forwarded-For
// başlıklarına doğrudan güvenmeyin, düzgun proxy yapılandırması olmadan.
String clientIp = request.getRemoteAddr();
Bucket bucket = buckets.computeIfAbsent(clientIp,
k -> Bucket.builder()
.addLimit(Bandwidth.classic(100, Refill.greedy(100, Duration.ofMinutes(1))))
.build());
if (bucket.tryConsume(1)) {
filterChain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
}
}
}
```
## Arka Plan Job'ları
Spring'in `@Scheduled`'ını kullanın veya kuyruklar ile entegre olun (örn. Kafka, SQS, RabbitMQ). Handler'ları idempotent ve gözlemlenebilir tutun.
## Gözlemlenebilirlik
- Logback encoder ile yapılandırılmış loglama (JSON)
- Metrikler: Micrometer + Prometheus/OTel
- Tracing: OpenTelemetry veya Brave backend ile Micrometer Tracing
## Production Varsayılanları
- Constructor injection'ı tercih edin, field injection'dan kaçının
- RFC 7807 hataları için `spring.mvc.problemdetails.enabled=true` etkinleştirin (Spring Boot 3+)
- İş yükü için HikariCP pool boyutlarını yapılandırın, timeout'ları ayarlayın
- Sorgular için `@Transactional(readOnly = true)` kullanın
- `@NonNull` ve uygun yerlerde `Optional` ile null-safety zorlayın
**Unutmayın**: Controller'ları ince, servisleri odaklı, repository'leri basit ve hataları merkezi olarak işlenmiş tutun. Bakım yapılabilirlik ve test edilebilirlik için optimize edin.

View File

@@ -0,0 +1,272 @@
---
name: springboot-security
description: Spring Security best practices for authn/authz, validation, CSRF, secrets, headers, rate limiting, and dependency security in Java Spring Boot services.
origin: ECC
---
# Spring Boot Güvenlik İncelemesi
Auth ekleme, girişi işleme, endpoint oluşturma veya gizli bilgilerle uğraşırken kullanın.
## Ne Zaman Aktif Edilir
- Kimlik doğrulama ekleme (JWT, OAuth2, session-based)
- Yetkilendirme uygulama (@PreAuthorize, role-based erişim)
- Kullanıcı girişini doğrulama (Bean Validation, custom validator'lar)
- CORS, CSRF veya güvenlik başlıklarını yapılandırma
- Gizli bilgileri yönetme (Vault, ortam değişkenleri)
- Rate limiting veya brute-force koruması ekleme
- Bağımlılıkları CVE için tarama
## Kimlik Doğrulama
- İptal listesi ile stateless JWT veya opaque token'ları tercih edin
- Session'lar için `httpOnly`, `Secure`, `SameSite=Strict` cookie'leri kullanın
- Token'ları `OncePerRequestFilter` veya resource server ile doğrulayın
```java
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtService jwtService;
public JwtAuthFilter(JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
Authentication auth = jwtService.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
```
## Yetkilendirme
- Method güvenliğini etkinleştirin: `@EnableMethodSecurity`
- `@PreAuthorize("hasRole('ADMIN')")` veya `@PreAuthorize("@authz.canEdit(#id)")` kullanın
- Varsayılan olarak reddedin; sadece gerekli scope'larıığa çıkarın
```java
@RestController
@RequestMapping("/api/admin")
public class AdminController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/users")
public List<UserDto> listUsers() {
return userService.findAll();
}
@PreAuthorize("@authz.isOwner(#id, authentication)")
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
```
## Girdi Doğrulama
- Controller'larda `@Valid` ile Bean Validation kullanın
- DTO'lara kısıtlamalar uygulayın: `@NotBlank`, `@Email`, `@Size`, custom validator'lar
- Render etmeden önce herhangi bir HTML'i whitelist ile temizleyin
```java
// KÖTÜ: Validation yok
@PostMapping("/users")
public User createUser(@RequestBody UserDto dto) {
return userService.create(dto);
}
// İYİ: Doğrulanmış DTO
public record CreateUserDto(
@NotBlank @Size(max = 100) String name,
@NotBlank @Email String email,
@NotNull @Min(0) @Max(150) Integer age
) {}
@PostMapping("/users")
public ResponseEntity<UserDto> createUser(@Valid @RequestBody CreateUserDto dto) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(userService.create(dto));
}
```
## SQL Injection Önleme
- Spring Data repository'leri veya parametreli sorgular kullanın
- Native sorgular için `:param` binding'leri kullanın; string'leri asla birleştirmeyin
```java
// KÖTÜ: Native sorguda string birleştirme
@Query(value = "SELECT * FROM users WHERE name = '" + name + "'", nativeQuery = true)
// İYİ: Parametreli native sorgu
@Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true)
List<User> findByName(@Param("name") String name);
// İYİ: Spring Data türetilmiş sorgu (otomatik parametreli)
List<User> findByEmailAndActiveTrue(String email);
```
## Parola Kodlama
- Parolaları her zaman BCrypt veya Argon2 ile hash'leyin — asla düz metin saklamayın
- Manuel hash'leme değil `PasswordEncoder` bean'i kullanın
```java
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // cost faktörü 12
}
// Servis içinde
public User register(CreateUserDto dto) {
String hashedPassword = passwordEncoder.encode(dto.password());
return userRepository.save(new User(dto.email(), hashedPassword));
}
```
## CSRF Koruması
- Tarayıcı session uygulamaları için CSRF'i etkin tutun; formlara/başlıklara token ekleyin
- Bearer token'lı saf API'ler için CSRF'i devre dışı bırakın ve stateless auth'a güvenin
```java
http
.csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
```
## Gizli Bilgi Yönetimi
- Kaynak kodda gizli bilgi yok; env veya vault'tan yükleyin
- `application.yml`'i kimlik bilgilerinden arınmış tutun; yer tutucular kullanın
- Token'ları ve DB kimlik bilgilerini düzenli olarak döndürün
```yaml
# KÖTÜ: application.yml'de sabit kodlanmış
spring:
datasource:
password: mySecretPassword123
# İYİ: Ortam değişkeni yer tutucu
spring:
datasource:
password: ${DB_PASSWORD}
# İYİ: Spring Cloud Vault entegrasyonu
spring:
cloud:
vault:
uri: https://vault.example.com
token: ${VAULT_TOKEN}
```
## Güvenlik Başlıkları
```java
http
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'"))
.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
.xssProtection(Customizer.withDefaults())
.referrerPolicy(rp -> rp.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER)));
```
## CORS Yapılandırması
- CORS'u controller başına değil, güvenlik filtre seviyesinde yapılandırın
- İzin verilen origin'leri kısıtlayın — production'da asla `*` kullanmayın
```java
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://app.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
// SecurityFilterChain içinde:
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
```
## Rate Limiting
- Pahalı endpoint'lerde Bucket4j veya gateway seviyesi limitler uygulayın
- Patlamalarda logla ve uyar; yeniden deneme ipuçları ile 429 döndür
```java
// Endpoint başına rate limiting için Bucket4j kullanma
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
private Bucket createBucket() {
return Bucket.builder()
.addLimit(Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1))))
.build();
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String clientIp = request.getRemoteAddr();
Bucket bucket = buckets.computeIfAbsent(clientIp, k -> createBucket());
if (bucket.tryConsume(1)) {
chain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("{\"error\": \"Rate limit exceeded\"}");
}
}
}
```
## Bağımlılık Güvenliği
- CI'da OWASP Dependency Check / Snyk çalıştırın
- Spring Boot ve Spring Security'yi desteklenen sürümlerde tutun
- Bilinen CVE'lerde build'leri başarısız yapın
## Loglama ve PII
- Gizli bilgileri, token'ları, parolaları veya tam PAN verilerini asla loglamayın
- Hassas alanları redakte edin; yapılandırılmış JSON loglama kullanın
## Dosya Yüklemeleri
- Boyutu, content type'ı ve uzantıyı doğrulayın
- Web root dışında saklayın; gerekirse tarayın
## Yayın Öncesi Kontrol Listesi
- [ ] Auth token'ları doğru şekilde doğrulanmış ve süresi dolmuş
- [ ] Her hassas path'te yetkilendirme korumaları
- [ ] Tüm girişler doğrulanmış ve temizlenmiş
- [ ] String-birleştirilmiş SQL yok
- [ ] Uygulama türü için doğru CSRF duruşu
- [ ] Gizli bilgiler harici; hiçbiri commit edilmemiş
- [ ] Güvenlik başlıkları yapılandırılmış
- [ ] API'lerde rate limiting
- [ ] Bağımlılıklar taranmış ve güncel
- [ ] Loglar hassas verilerden arınmış
**Unutmayın**: Varsayılan olarak reddet, girişleri doğrula, en az ayrıcalık ve önce yapılandırma ile güvenli.

View File

@@ -0,0 +1,158 @@
---
name: springboot-tdd
description: Test-driven development for Spring Boot using JUnit 5, Mockito, MockMvc, Testcontainers, and JaCoCo. Use when adding features, fixing bugs, or refactoring.
origin: ECC
---
# Spring Boot TDD İş Akışı
80%+ kapsam (unit + integration) ile Spring Boot servisleri için TDD rehberi.
## Ne Zaman Kullanılır
- Yeni özellikler veya endpoint'ler
- Bug düzeltmeleri veya refactoring'ler
- Veri erişim mantığı veya güvenlik kuralları ekleme
## İş Akışı
1) Önce testleri yazın (başarısız olmalılar)
2) Geçmek için minimal kod uygulayın
3) Testleri yeşil tutarken refactor edin
4) Kapsamı zorlayın (JaCoCo)
## Unit Testler (JUnit 5 + Mockito)
```java
@ExtendWith(MockitoExtension.class)
class MarketServiceTest {
@Mock MarketRepository repo;
@InjectMocks MarketService service;
@Test
void createsMarket() {
CreateMarketRequest req = new CreateMarketRequest("name", "desc", Instant.now(), List.of("cat"));
when(repo.save(any())).thenAnswer(inv -> inv.getArgument(0));
Market result = service.create(req);
assertThat(result.name()).isEqualTo("name");
verify(repo).save(any());
}
}
```
Desenler:
- Arrange-Act-Assert
- Kısmi mock'lardan kaçının; açık stubbing tercih edin
- Varyantlar için `@ParameterizedTest` kullanın
## Web Katmanı Testleri (MockMvc)
```java
@WebMvcTest(MarketController.class)
class MarketControllerTest {
@Autowired MockMvc mockMvc;
@MockBean MarketService marketService;
@Test
void returnsMarkets() throws Exception {
when(marketService.list(any())).thenReturn(Page.empty());
mockMvc.perform(get("/api/markets"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").isArray());
}
}
```
## Entegrasyon Testleri (SpringBootTest)
```java
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class MarketIntegrationTest {
@Autowired MockMvc mockMvc;
@Test
void createsMarket() throws Exception {
mockMvc.perform(post("/api/markets")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name":"Test","description":"Desc","endDate":"2030-01-01T00:00:00Z","categories":["general"]}
"""))
.andExpect(status().isCreated());
}
}
```
## Persistence Testleri (DataJpaTest)
```java
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(TestContainersConfig.class)
class MarketRepositoryTest {
@Autowired MarketRepository repo;
@Test
void savesAndFinds() {
MarketEntity entity = new MarketEntity();
entity.setName("Test");
repo.save(entity);
Optional<MarketEntity> found = repo.findByName("Test");
assertThat(found).isPresent();
}
}
```
## Testcontainers
- Production'ı yansıtmak için Postgres/Redis için yeniden kullanılabilir container'lar kullanın
- JDBC URL'lerini Spring context'e enjekte etmek için `@DynamicPropertySource` ile bağlayın
## Kapsam (JaCoCo)
Maven snippet:
```xml
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.14</version>
<executions>
<execution>
<goals><goal>prepare-agent</goal></goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals><goal>report</goal></goals>
</execution>
</executions>
</plugin>
```
## Assertion'lar
- Okunabilirlik için AssertJ'yi (`assertThat`) tercih edin
- JSON yanıtları için `jsonPath` kullanın
- Exception'lar için: `assertThatThrownBy(...)`
## Test Veri Builder'ları
```java
class MarketBuilder {
private String name = "Test";
MarketBuilder withName(String name) { this.name = name; return this; }
Market build() { return new Market(null, name, MarketStatus.ACTIVE); }
}
```
## CI Komutları
- Maven: `mvn -T 4 test` veya `mvn verify`
- Gradle: `./gradlew test jacocoTestReport`
**Unutmayın**: Testleri hızlı, izole ve deterministik tutun. Uygulama detaylarını değil, davranışı test edin.

View File

@@ -0,0 +1,231 @@
---
name: springboot-verification
description: "Verification loop for Spring Boot projects: build, static analysis, tests with coverage, security scans, and diff review before release or PR."
origin: ECC
---
# Spring Boot Doğrulama Döngüsü
PR'lardan önce, büyük değişikliklerden sonra ve deployment öncesi çalıştırın.
## Ne Zaman Aktif Edilir
- Spring Boot servisi için pull request açmadan önce
- Büyük refactoring veya bağımlılık yükseltmelerinden sonra
- Staging veya production için deployment öncesi doğrulama
- Tam build → lint → test → güvenlik taraması pipeline'ı çalıştırma
- Test kapsamının eşikleri karşıladığını doğrulama
## Faz 1: Build
```bash
mvn -T 4 clean verify -DskipTests
# veya
./gradlew clean assemble -x test
```
Build başarısız olursa, durdurun ve düzeltin.
## Faz 2: Static Analiz
Maven (yaygın plugin'ler):
```bash
mvn -T 4 spotbugs:check pmd:check checkstyle:check
```
Gradle (yapılandırılmışsa):
```bash
./gradlew checkstyleMain pmdMain spotbugsMain
```
## Faz 3: Testler + Kapsam
```bash
mvn -T 4 test
mvn jacoco:report # 80%+ kapsam doğrula
# veya
./gradlew test jacocoTestReport
```
Rapor:
- Toplam testler, geçen/başarısız
- Kapsam % (satırlar/dallar)
### Unit Testler
Mock bağımlılıklarla izole olarak servis mantığını test edin:
```java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock private UserRepository userRepository;
@InjectMocks private UserService userService;
@Test
void createUser_validInput_returnsUser() {
var dto = new CreateUserDto("Alice", "alice@example.com");
var expected = new User(1L, "Alice", "alice@example.com");
when(userRepository.save(any(User.class))).thenReturn(expected);
var result = userService.create(dto);
assertThat(result.name()).isEqualTo("Alice");
verify(userRepository).save(any(User.class));
}
@Test
void createUser_duplicateEmail_throwsException() {
var dto = new CreateUserDto("Alice", "existing@example.com");
when(userRepository.existsByEmail(dto.email())).thenReturn(true);
assertThatThrownBy(() -> userService.create(dto))
.isInstanceOf(DuplicateEmailException.class);
}
}
```
### Testcontainers ile Entegrasyon Testleri
H2 yerine gerçek bir veritabanına karşı test edin:
```java
@SpringBootTest
@Testcontainers
class UserRepositoryIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired private UserRepository userRepository;
@Test
void findByEmail_existingUser_returnsUser() {
userRepository.save(new User("Alice", "alice@example.com"));
var found = userRepository.findByEmail("alice@example.com");
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("Alice");
}
}
```
### MockMvc ile API Testleri
Tam Spring context ile controller katmanını test edin:
```java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private UserService userService;
@Test
void createUser_validInput_returns201() throws Exception {
var user = new UserDto(1L, "Alice", "alice@example.com");
when(userService.create(any())).thenReturn(user);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name": "Alice", "email": "alice@example.com"}
"""))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Alice"));
}
@Test
void createUser_invalidEmail_returns400() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name": "Alice", "email": "not-an-email"}
"""))
.andExpect(status().isBadRequest());
}
}
```
## Faz 4: Güvenlik Taraması
```bash
# Bağımlılık CVE'leri
mvn org.owasp:dependency-check-maven:check
# veya
./gradlew dependencyCheckAnalyze
# Kaynakta gizli bilgiler
grep -rn "password\s*=\s*\"" src/ --include="*.java" --include="*.yml" --include="*.properties"
grep -rn "sk-\|api_key\|secret" src/ --include="*.java" --include="*.yml"
# Gizli bilgiler (git geçmişi)
git secrets --scan # yapılandırılmışsa
```
### Yaygın Güvenlik Bulguları
```
# System.out.println kontrolü (yerine logger kullan)
grep -rn "System\.out\.print" src/main/ --include="*.java"
# Yanıtlarda ham exception mesajları kontrolü
grep -rn "e\.getMessage()" src/main/ --include="*.java"
# Wildcard CORS kontrolü
grep -rn "allowedOrigins.*\*" src/main/ --include="*.java"
```
## Faz 5: Lint/Format (opsiyonel kapı)
```bash
mvn spotless:apply # Spotless plugin kullanıyorsanız
./gradlew spotlessApply
```
## Faz 6: Diff İncelemesi
```bash
git diff --stat
git diff
```
Kontrol listesi:
- Debug logları kalmamış (`System.out`, koruma olmadan `log.debug`)
- Anlamlı hatalar ve HTTP durumları
- Gerekli yerlerde transaction'lar ve validation mevcut
- Config değişiklikleri belgelenmiş
## Çıktı Şablonu
```
DOĞRULAMA RAPORU
===================
Build: [GEÇTİ/BAŞARISIZ]
Static: [GEÇTİ/BAŞARISIZ] (spotbugs/pmd/checkstyle)
Testler: [GEÇTİ/BAŞARISIZ] (X/Y geçti, Z% kapsam)
Güvenlik: [GEÇTİ/BAŞARISIZ] (CVE bulguları: N)
Diff: [X dosya değişti]
Genel: [HAZIR / HAZIR DEĞİL]
Düzeltilecek Sorunlar:
1. ...
2. ...
```
## Sürekli Mod
- Önemli değişikliklerde veya uzun oturumlarda her 30-60 dakikada bir fazları yeniden çalıştırın
- Kısa döngü tutun: hızlı geri bildirim için `mvn -T 4 test` + spotbugs
**Unutmayın**: Hızlı geri bildirim geç sürprizleri yener. Kapıyı sıkı tutun—production sistemlerinde uyarıları kusur olarak değerlendirin.

View File

@@ -0,0 +1,410 @@
---
name: tdd-workflow
description: Yeni özellikler yazarken, hata düzeltirken veya kod refactor ederken bu skill'i kullanın. Unit, integration ve E2E testlerini içeren %80+ kapsam ile test güdümlü geliştirmeyi zorlar.
origin: ECC
---
# Test Güdümlü Geliştirme İş Akışı
Bu skill tüm kod geliştirmenin kapsamlı test kapsamı ile TDD ilkelerini takip etmesini sağlar.
## Ne Zaman Aktifleştirmelisiniz
- Yeni özellikler veya fonksiyonellik yazarken
- Hataları veya sorunları düzeltirken
- Mevcut kodu refactor ederken
- API endpoint'leri eklerken
- Yeni bileşenler oluştururken
## Temel İlkeler
### 1. Koddan ÖNCE Testler
HER ZAMAN önce testleri yazın, sonra testleri geçmesi için kod uygulayın.
### 2. Kapsam Gereksinimleri
- Minimum %80 kapsam (unit + integration + E2E)
- Tüm uç durumlar kapsanmış
- Hata senaryoları test edilmiş
- Sınır koşulları doğrulanmış
### 3. Test Tipleri
#### Unit Testler
- Bireysel fonksiyonlar ve yardımcı araçlar
- Bileşen mantığı
- Pure fonksiyonlar
- Yardımcılar ve utilities
#### Integration Testler
- API endpoint'leri
- Veritabanı operasyonları
- Service etkileşimleri
- Harici API çağrıları
#### E2E Testler (Playwright)
- Kritik kullanıcı akışları
- Tam iş akışları
- Tarayıcı otomasyonu
- UI etkileşimleri
## TDD İş Akışı Adımları
### Adım 1: Kullanıcı Hikayeleri Yazın
```
[Rol] olarak, [eylem] yapmak istiyorum, böylece [fayda] elde ederim
Örnek:
Kullanıcı olarak, marketleri semantik olarak aramak istiyorum,
böylece tam anahtar kelimeler olmasa bile ilgili marketleri bulabilirim.
```
### Adım 2: Test Senaryoları Oluşturun
Her kullanıcı hikayesi için kapsamlı test senaryoları oluşturun:
```typescript
describe('Semantik Arama', () => {
it('sorgu için ilgili marketleri döndürür', async () => {
// Test implementasyonu
})
it('boş sorguyu zarif şekilde işler', async () => {
// Uç durumu test et
})
it('Redis kullanılamazsa substring aramaya geri döner', async () => {
// Fallback davranışını test et
})
it('sonuçları benzerlik skoruna göre sıralar', async () => {
// Sıralama mantığını test et
})
})
```
### Adım 3: Testleri Çalıştırın (Başarısız Olmalı)
```bash
npm test
# Testler başarısız olmalı - henüz implement etmedik
```
### Adım 4: Kod Uygulayın
Testleri geçmesi için minimal kod yazın:
```typescript
// Testler tarafından yönlendirilen implementasyon
export async function searchMarkets(query: string) {
// Implementasyon buraya
}
```
### Adım 5: Testleri Tekrar Çalıştırın
```bash
npm test
# Testler artık geçmeli
```
### Adım 6: Refactor Edin
Testleri yeşil tutarken kod kalitesini iyileştirin:
- Tekrarı kaldırın
- İsimlendirmeyi iyileştirin
- Performansı optimize edin
- Okunabilirliği artırın
### Adım 7: Kapsamı Doğrulayın
```bash
npm run test:coverage
# %80+ kapsam sağlandığını doğrula
```
## Test Kalıpları
### Unit Test Kalıbı (Jest/Vitest)
```typescript
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from './Button'
describe('Button Bileşeni', () => {
it('doğru metinle render eder', () => {
render(<Button>Tıkla</Button>)
expect(screen.getByText('Tıkla')).toBeInTheDocument()
})
it('tıklandığında onClick\'i çağırır', () => {
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Tıkla</Button>)
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
it('disabled prop true olduğunda devre dışı kalır', () => {
render(<Button disabled>Tıkla</Button>)
expect(screen.getByRole('button')).toBeDisabled()
})
})
```
### API Integration Test Kalıbı
```typescript
import { NextRequest } from 'next/server'
import { GET } from './route'
describe('GET /api/markets', () => {
it('marketleri başarıyla döndürür', async () => {
const request = new NextRequest('http://localhost/api/markets')
const response = await GET(request)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.success).toBe(true)
expect(Array.isArray(data.data)).toBe(true)
})
it('query parametrelerini validate eder', async () => {
const request = new NextRequest('http://localhost/api/markets?limit=invalid')
const response = await GET(request)
expect(response.status).toBe(400)
})
it('veritabanı hatalarını zarif şekilde işler', async () => {
// Veritabanı başarısızlığını mock'la
const request = new NextRequest('http://localhost/api/markets')
// Hata işlemeyi test et
})
})
```
### E2E Test Kalıbı (Playwright)
```typescript
import { test, expect } from '@playwright/test'
test('kullanıcı marketleri arayabilir ve filtreleyebilir', async ({ page }) => {
// Markets sayfasına git
await page.goto('/')
await page.click('a[href="/markets"]')
// Sayfanın yüklendiğini doğrula
await expect(page.locator('h1')).toContainText('Markets')
// Marketleri ara
await page.fill('input[placeholder="Marketleri ara"]', 'election')
// Debounce ve sonuçları bekle
await page.waitForTimeout(600)
// Arama sonuçlarının gösterildiğini doğrula
const results = page.locator('[data-testid="market-card"]')
await expect(results).toHaveCount(5, { timeout: 5000 })
// Sonuçların arama terimini içerdiğini doğrula
const firstResult = results.first()
await expect(firstResult).toContainText('election', { ignoreCase: true })
// Duruma göre filtrele
await page.click('button:has-text("Aktif")')
// Filtrelenmiş sonuçları doğrula
await expect(results).toHaveCount(3)
})
test('kullanıcı yeni market oluşturabilir', async ({ page }) => {
// Önce login ol
await page.goto('/creator-dashboard')
// Market oluşturma formunu doldur
await page.fill('input[name="name"]', 'Test Market')
await page.fill('textarea[name="description"]', 'Test açıklama')
await page.fill('input[name="endDate"]', '2025-12-31')
// Formu gönder
await page.click('button[type="submit"]')
// Başarı mesajını doğrula
await expect(page.locator('text=Market başarıyla oluşturuldu')).toBeVisible()
// Market sayfasına yönlendirmeyi doğrula
await expect(page).toHaveURL(/\/markets\/test-market/)
})
```
## Test Dosya Organizasyonu
```
src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.test.tsx # Unit testler
│ │ └── Button.stories.tsx # Storybook
│ └── MarketCard/
│ ├── MarketCard.tsx
│ └── MarketCard.test.tsx
├── app/
│ └── api/
│ └── markets/
│ ├── route.ts
│ └── route.test.ts # Integration testler
└── e2e/
├── markets.spec.ts # E2E testler
├── trading.spec.ts
└── auth.spec.ts
```
## Harici Servisleri Mock'lama
### Supabase Mock
```typescript
jest.mock('@/lib/supabase', () => ({
supabase: {
from: jest.fn(() => ({
select: jest.fn(() => ({
eq: jest.fn(() => Promise.resolve({
data: [{ id: 1, name: 'Test Market' }],
error: null
}))
}))
}))
}
}))
```
### Redis Mock
```typescript
jest.mock('@/lib/redis', () => ({
searchMarketsByVector: jest.fn(() => Promise.resolve([
{ slug: 'test-market', similarity_score: 0.95 }
])),
checkRedisHealth: jest.fn(() => Promise.resolve({ connected: true }))
}))
```
### OpenAI Mock
```typescript
jest.mock('@/lib/openai', () => ({
generateEmbedding: jest.fn(() => Promise.resolve(
new Array(1536).fill(0.1) // Mock 1536-boyutlu embedding
))
}))
```
## Test Kapsamı Doğrulama
### Kapsam Raporu Çalıştır
```bash
npm run test:coverage
```
### Kapsam Eşikleri
```json
{
"jest": {
"coverageThresholds": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
```
## Kaçınılması Gereken Yaygın Test Hataları
### ❌ YANLIŞ: Implementasyon Detaylarını Test Etme
```typescript
// İç state'i test etme
expect(component.state.count).toBe(5)
```
### ✅ DOĞRU: Kullanıcı Tarafından Görünen Davranışı Test Et
```typescript
// Kullanıcıların gördüğünü test et
expect(screen.getByText('Sayı: 5')).toBeInTheDocument()
```
### ❌ YANLIŞ: Kırılgan Selector'lar
```typescript
// Kolayca bozulur
await page.click('.css-class-xyz')
```
### ✅ DOĞRU: Semantik Selector'lar
```typescript
// Değişikliklere karşı dayanıklı
await page.click('button:has-text("Gönder")')
await page.click('[data-testid="submit-button"]')
```
### ❌ YANLIŞ: Test İzolasyonu Yok
```typescript
// Testler birbirine bağımlı
test('kullanıcı oluşturur', () => { /* ... */ })
test('aynı kullanıcıyı günceller', () => { /* önceki teste bağımlı */ })
```
### ✅ DOĞRU: Bağımsız Testler
```typescript
// Her test kendi verisini hazırlar
test('kullanıcı oluşturur', () => {
const user = createTestUser()
// Test mantığı
})
test('kullanıcı günceller', () => {
const user = createTestUser()
// Güncelleme mantığı
})
```
## Sürekli Test
### Geliştirme Sırasında Watch Modu
```bash
npm test -- --watch
# Dosya değişikliklerinde testler otomatik çalışır
```
### Pre-Commit Hook
```bash
# Her commit öncesi çalışır
npm test && npm run lint
```
### CI/CD Entegrasyonu
```yaml
# GitHub Actions
- name: Run Tests
run: npm test -- --coverage
- name: Upload Coverage
uses: codecov/codecov-action@v3
```
## En İyi Uygulamalar
1. **Önce Testleri Yaz** - Her zaman TDD
2. **Test Başına Bir Assert** - Tek davranışa odaklan
3. **Açıklayıcı Test İsimleri** - Neyin test edildiğini açıkla
4. **Arrange-Act-Assert** - Net test yapısı
5. **Harici Bağımlılıkları Mock'la** - Unit testleri izole et
6. **Uç Durumları Test Et** - Null, undefined, boş, büyük
7. **Hata Yollarını Test Et** - Sadece happy path değil
8. **Testleri Hızlı Tut** - Unit testler < 50ms her biri
9. **Testlerden Sonra Temizle** - Yan etki yok
10. **Kapsam Raporlarını İncele** - Boşlukları tespit et
## Başarı Metrikleri
- %80+ kod kapsamı sağlanmış
- Tüm testler geçiyor (yeşil)
- Atlanmış veya devre dışı test yok
- Hızlı test yürütme (< 30s unit testler için)
- E2E testler kritik kullanıcı akışlarını kapsıyor
- Testler production'dan önce hataları yakalar
---
**Unutmayın**: Testler opsiyonel değildir. Güvenli refactoring, hızlı geliştirme ve production güvenilirliği sağlayan güvenlik ağıdırlar.

View File

@@ -0,0 +1,126 @@
---
name: verification-loop
description: "Claude Code oturumları için kapsamlı doğrulama sistemi."
origin: ECC
---
# Verification Loop Skill
Claude Code oturumları için kapsamlı doğrulama sistemi.
## Ne Zaman Kullanılır
Bu skill'i şu durumlarda çağır:
- Bir özellik veya önemli kod değişikliği tamamladıktan sonra
- PR oluşturmadan önce
- Kalite kapılarının geçtiğinden emin olmak istediğinde
- Refactoring sonrasında
## Doğrulama Fazları
### Faz 1: Build Doğrulaması
```bash
# Projenin build olup olmadığını kontrol et
npm run build 2>&1 | tail -20
# VEYA
pnpm build 2>&1 | tail -20
```
Build başarısız olursa, devam etmeden önce DUR ve düzelt.
### Faz 2: Tip Kontrolü
```bash
# TypeScript projeleri
npx tsc --noEmit 2>&1 | head -30
# Python projeleri
pyright . 2>&1 | head -30
```
Tüm tip hatalarını raporla. Devam etmeden önce kritik olanları düzelt.
### Faz 3: Lint Kontrolü
```bash
# JavaScript/TypeScript
npm run lint 2>&1 | head -30
# Python
ruff check . 2>&1 | head -30
```
### Faz 4: Test Paketi
```bash
# Testleri coverage ile çalıştır
npm run test -- --coverage 2>&1 | tail -50
# Coverage eşiğini kontrol et
# Hedef: minimum %80
```
Rapor:
- Toplam testler: X
- Geçti: X
- Başarısız: X
- Coverage: %X
### Faz 5: Güvenlik Taraması
```bash
# Secret'ları kontrol et
grep -rn "sk-" --include="*.ts" --include="*.js" . 2>/dev/null | head -10
grep -rn "api_key" --include="*.ts" --include="*.js" . 2>/dev/null | head -10
# console.log kontrol et
grep -rn "console.log" --include="*.ts" --include="*.tsx" src/ 2>/dev/null | head -10
```
### Faz 6: Diff İncelemesi
```bash
# Neyin değiştiğini göster
git diff --stat
git diff HEAD~1 --name-only
```
Her değişen dosyayı şunlar için incele:
- İstenmeyen değişiklikler
- Eksik hata işleme
- Potansiyel edge case'ler
## Çıktı Formatı
Tüm fazları çalıştırdıktan sonra, bir doğrulama raporu üret:
```
DOĞRULAMA RAPORU
==================
Build: [PASS/FAIL]
Tipler: [PASS/FAIL] (X hata)
Lint: [PASS/FAIL] (X uyarı)
Testler: [PASS/FAIL] (X/Y geçti, %Z coverage)
Güvenlik: [PASS/FAIL] (X sorun)
Diff: [X dosya değişti]
Genel: PR için [HAZIR/HAZIR DEĞİL]
Düzeltilmesi Gereken Sorunlar:
1. ...
2. ...
```
## Sürekli Mod
Uzun oturumlar için, her 15 dakikada bir veya major değişikliklerden sonra doğrulama çalıştır:
```markdown
Mental kontrol noktası belirle:
- Her fonksiyonu tamamladıktan sonra
- Bir component'i bitirdikten sonra
- Sonraki göreve geçmeden önce
Çalıştır: /verify
```
## Hook'larla Entegrasyon
Bu skill PostToolUse hook'larını tamamlar ancak daha derin doğrulama sağlar.
Hook'lar sorunları anında yakalar; bu skill kapsamlı inceleme sağlar.