AWS CDK Kod Organizasyonu: Service-Based ve Domain-Based Mimari Patternleri
AWS CDK projelerinde service-based, domain-based, feature-based veya layer-based organizasyon patternlerini ne zaman kullanacağını öğren. Karar çerçeveleri, çalışan örnekler ve sürdürülebilir infrastructure code için migration stratejileri.
Özet
AWS CDK projeleri genellikle belirsiz organizasyon stratejileriyle başlar ve ölçeklendikçe tight coupling, circular dependency'ler ve deployment darboğazlarıyla karşılaşır. Bu rehber, beş organizasyon patternini - service-based, domain-based, feature-based, layer-based ve hybrid - çalışan TypeScript örnekleri ve karar çerçeveleriyle inceliyor. Ekiplerin business ihtiyaçlarına, team yapısına ve deployment gereksinimlerine uygun CDK projeleri oluşturmasına yardımcı oluyor.
Problem Bağlamı
Farklı ekiplerle CDK projeleriyle çalışırken tekrar eden bir pattern fark ettim: projeler iyi niyetlerle başlıyor ama zamanla maintain edilmesi zorlaşıyor. Infrastructure code organik olarak büyüyor, dosyalar uygun olan yere konuluyor ve kısa sürede ekipler merge conflict'ler, belirsiz ownership ve çözemedikleri deployment dependency'lerle uğraşıyor.
Teknik sorunlar birkaç şekilde ortaya çıkıyor. AWS service'lerine göre organize olan ekipler (ayrı lambda/, dynamodb/, api-gateway/ klasörleri oluşturma) tek bir business özelliğindeki değişikliğin birden fazla directory'ye dokunmasını görüyor. Cross-stack referanslar, bir şeyler bozulana kadar belli olmayan deployment dependency'ler yaratıyor. Birden fazla ekip shared infrastructure üzerinde çalıştığında, belirsiz domain sınırları merge conflict'lere ve koordinasyon yüküne yol açıyor.
Temel soru sadece dosya organizasyonu değil - business mimarisini, deployment gereksinimlerini ve team yapısını yansıtan, aynı zamanda sistemleri kırılgan yapan coupling problemlerinden kaçınan infrastructure'ı nasıl modelliyorsun.
Teknik Gereksinimler
İyi organize edilmiş bir CDK projesinin çözmesi gereken birkaç teknik gereksinim var:
Deployment Bağımsızlığı: Ekipler, infrastructure değişikliklerini diğer ekiplerle koordine etmeden veya ilgisiz sistemleri kırma endişesi olmadan deploy edebilmeli.
Açık Ownership: Her stack ve construct'ın belirgin bir sahibi olmalı, sorun çıktığında veya değişiklik gerektiğinde kime soracağın açık olmalı.
Sürdürülebilirlik: Infrastructure code'u gezinmek kolay olmalı, ilişkili kaynaklar mantıksal olarak gruplanmalı ki developer'lar ihtiyaç duydukları şeyi hızlıca bulabilsin.
Ölçeklenebilirlik: Organizasyon pattern'i, proje 10 kaynaktan 500'e, 1 ekipten 10 ekibe büyüdükçe büyük bir yeniden yapılandırma gerektirmeden çalışmalı.
Cross-Cutting Concern'ler: VPC'ler, security policy'ler ve monitoring gibi shared infrastructure, tekrarlama veya garip dependency'ler olmadan ele alınmalı.
Organizasyon Patternleri
Service-Based Organizasyon
Service-based pattern, code'u AWS service türüne göre organize eder - tüm Lambda fonksiyonları bir arada, tüm DynamoDB tabloları bir arada, tüm API Gateway'ler bir arada.
Bu yaklaşım başta mantıklı görünüyor - tüm Lambda fonksiyonları bir yerde, tutarlı konfigürasyonlar uygulamak kolay. Ancak problemler hızla belirgin hale geliyor:
- User fonksiyonalitesinde değişiklik yapmak birden fazla directory ve stack'e dokunmayı gerektiriyor
- Cross-stack referanslar deployment dependency'leri yaratıyor (DynamoDB stack, Lambda stack'ten önce deploy edilmeli)
- Team ownership belirsiz - "Lambda stack"in sahibi kim?
- Infrastructure'ın bir alt kümesini (sadece user ile ilgili kaynaklar) deploy etmek zor
Service-based ne zaman işe yarar: 10'dan az kaynağa sahip küçük projeler, öğrenme/deney aşaması veya organizasyon pattern'inin henüz çok önemli olmadığı single-service uygulamalar.
Domain-Based Organizasyon
Domain-based pattern, code'u business domain'ine veya bounded context'e göre organize eder, bir domain için tüm infrastructure'ı bir arada gruplar.
Bu organizasyon, infrastructure'ı business yetenekleriyle hizalıyor. User fonksiyonalitesindeki değişiklikler sadece user/ directory'sine dokunuyor. Team ownership açık - user ekibi user stack'e sahip. Domain'ler (shared infrastructure'dan sonra) bağımsız deploy edilebiliyor ve gerekirse bir domain'i ayrı bir repository'ye çıkarmak basit.
Single-Table Design ile uyum: Domain-based organizasyon, DynamoDB Single-Table Design pattern'i ile mükemmel uyumludur. Single table birden fazla entity type'ı (User, Order, Payment) içerdiğinde, her domain kendi access pattern'lerini ve repository logic'ini kendi klasöründe tutabilir. Örneğin user/ klasöründe user-repository.ts dosyası User entity'sine özel query'leri içerir, order/ klasöründe order-repository.ts Order access pattern'lerini yönetir - hepsi aynı physical table'ı kullanırken. Tablo definition'ı shared/ klasöründe olur, domain'ler sadece access pattern'lerini own eder.
Domain-based ne zaman işe yarar: Microservices mimarileri, multi-team organizasyonlar, business'a hizalanmış infrastructure, Single-Table Design kullanımı veya açık bounded context'lerin olduğu durumlar.
Feature-Based Organizasyon
Product odaklı ekipler için, user'a yönelik özelliklere göre organize olmak teknik domain'lerden daha mantıklı.
Feature-based organizasyon, özellikler birden fazla AWS service'ine ve domain'e yayıldığında, ekipler teknik katmanlardan ziyade product özellikleri etrafında organize olduğunda ve product hızlı özellik geliştirme ve deployment gerektirdiğinde iyi çalışır.
Feature-based ne zaman işe yarar: Feature ekipleri olan product şirketleri, hızlı geliştirme ortamları, bağımsız deploy edilebilecek özellikler.
Layer-Based Organizasyon
Layer-based organizasyon, infrastructure'ı teknik katmana göre ayırır, kaynakları lifecycle ve değişim sıklığına göre gruplar.
Layer-based organizasyon stateful ve stateless kaynakların net ayrımını, farklı güncelleme sıklıklarının ayrı ele alınmasını (foundation nadiren değişir, API'ler sık değişir), data katmanı nadiren değiştiği için azalan deployment riskini ve katman başına farklı removal policy'lerin daha kolay uygulanmasını sağlar.
Dezavantajlar ise business logic'in katmanlara dağılması, team ownership'in daha az net olması ve complete özellikleri deploy etmenin birden fazla katman arasında koordinasyon gerektirmesi.
Layer-based ne zaman işe yarar: Net infrastructure katmanları, ayrı infrastructure ve application ekipleri, stateful kaynaklardan stateless kaynakları ayırma ihtiyacı.
Hybrid Yaklaşım
Gerçek dünya projeleri genellikle pattern'leri birleştirmekten fayda görür - cross-cutting concern'ler için katmanlar kullanırken business logic'i domain'e göre organize etme.
Hybrid yaklaşım her iki dünyanın en iyisini veriyor: foundation katmanı cross-cutting concern'lerle ilgileniyor, domain stack'leri business logic bütünlüğünü koruyor ve net dependency graph'leri foundation kurulduktan sonra bağımsız deployment'ı mümkün kılıyor.
Karar Çerçevesi
Stack'leri Ne Zaman Ayırmalı vs Construct Kullanmalı
Karşılaştığım en yaygın sorulardan biri: "Ne zaman yeni bir stack oluşturmalıyım, ne zaman sadece construct kullanmalıyım?" Cevap deployment bağımsızlığına dayanıyor.
Birden fazla stack kullan:
- Farklı ekipler farklı parçaları maintain edecekse
- Farklı deployment schedule'ları varsa (data layer ayda bir deploy edilir, compute layer günlük)
- Farklı environment'lar veya account'lar varsa (dev bir account'ta, prod başka bir account'ta)
- CI/CD için bağımsız deployment kritikse
- CloudFormation'ın stack başına 500 kaynak limitine yaklaşılıyorsa
- Açık sınırları olan farklı business domain'leri varsa
- Stateful kaynaklar stateless kaynaklardan farklı removal policy'lere ihtiyaç duyuyorsa
Construct kullan (stack değil):
- Sadece aynı ekip içinde code ownership ayırıyorsan
- Kaynaklar birlikte değişiyor ve birlikte deploy edilmeliyse
- Bağımsız deployment'a ihtiyaç yoksa
- Kaynaklar tight coupling ve çok sayıda referansa sahipse
- Sadece code sürdürülebilirliği için organize ediyorsan
İşte bir karar ağacı örneği:
Monorepo vs Multi-Repo Stratejisi
Monorepo versus multi-repo kararı, ekiplerin nasıl işbirliği yaptığını ve infrastructure'ı nasıl deploy ettiğini etkiler.
Monorepo pattern:
Monorepo faydaları: tek source of truth, service'ler arasında atomic değişiklikler, boundary'ler arasında daha kolay refactoring, npm'e publish etmeden shared construct'lar, basitleştirilmiş dependency yönetimi ve tek CI/CD pipeline konfigürasyonu.
Dezavantajlar: daha büyük repository clone süresi, service'ler arasında tight coupling riski, hangi service'lerin değiştiğini algılayan CI/CD karmaşıklığı, daha az ayrıntılı team permission'ları ve potansiyel olarak daha büyük deployment blast radius.
Multi-repo pattern her service için ayrı repository'ler kullanır, shared construct'lar private npm registry'ye publish edilir.
Multi-repo faydaları: net service boundary'leri, bağımsız versioning ve deployment, açık team ownership ve permission'ları, daha küçük repository boyutu, language/framework çeşitliliği ve azalmış merge conflict'ler.
Dezavantajlar: cross-service değişikliklerin birden fazla PR gerektirmesi, shared construct'ların publish ve versioning'e ihtiyaç duyması, dependency version yönetimi karmaşıklığı, consistency'i garanti etmenin zorluğu ve daha fazla CI/CD pipeline overhead'i.
Tavsiyem: 10'dan az kişilik ekipler için monorepo ile başla. Atomic değişikliklerin ve shared construct'ların basitliği, küçük ölçekte dezavantajlardan daha ağır basıyor. Team boyutu veya deployment bağımsızlığı gerektirdiğinde multi-repo'ya geç - genellikle 50+ mühendis civarında veya farklı lifecycle'lara sahip gerçekten bağımsız service'lerin olduğu durumda.
Cross-Cutting Concern'leri Ele Alma
Networking, security, monitoring ve diğer cross-cutting concern'ler domain boundary'lerine temiz bir şekilde uymaz. İyi çalışan üç pattern var.
Foundation Stack Pattern
Cross-Cutting Policy'ler için CDK Aspect'ler
CDK Aspect'ler, application'daki tüm kaynaklara otomatik olarak policy uygulamanı sağlar.
Shared Construct Library
Best practice'leri kapsayan tekrar kullanılabilir L3 construct'lar oluştur. Bu construct'ları factory pattern'leri ve functional programming yaklaşımlarıyla daha da güçlendirebilirsin - detaylar için AWS CDK Functional Patterns yazısına göz at.
Multi-Environment Yönetimi
Birden fazla environment'ı (dev, staging, prod) yönetmek dikkatli configuration stratejisi gerektiriyor.
Static Stack Creation (Tavsiye Edilen)
Consistency'i garantilemek için synthesis sırasında tüm environment'ları oluştur.
Bu yaklaşım production configuration'ın geliştirme sırasında validate edilmesini garantiler. Production config'te bir yazım hatası, production deployment sırasında değil, local'de cdk synth çalıştırıldığında hemen yakalanır.
Yaygın Hatalar
Erken Stack Bölme
Çok fazla küçük stack oluşturmak aşırı cross-stack referanslarına ve deployment karmaşıklığına yol açar.
Büyük stack'lerle başla ve sadece net bir sebep olduğunda böl: farklı ekipler, farklı lifecycle'lar veya kaynak limitine yaklaşma.
Circular Dependency'ler
Stack'ler arasındaki circular dependency'ler kaynak ilişkilerini tasarlarken yaygın bir sorun.
Tek yönlü dependency akışı tasarla. Döngüleri kırmak için shared resource stack'leri veya event-driven mimari kullan.
Cross-Account/Region Referansları
Farklı AWS hesapları veya bölgeleri arasında kaynak referansı açık işleme gerektirir. SSM Parameter Store üzerinden export/import veya manuel ARN geçişi kullan. Doğrudan cross-account referansı çalışmaz; çözüm olarak StringParameter.valueFromLookup ile ARN alıp Table.fromTableArn ile import et.
CDK Refactor Tool Kullanmama
Logical ID'leri korumadan refactor yapmak kaynak değiştirme ve veri kaybına yol açar. Kod reorganizasyonunda her zaman logical ID'leri koru; overrideLogicalId veya construct ismini sabit tut.
Sonuçlar
Bir projede foundation katmanı ile domain-based organizasyon uyguladıktan sonra, ekip ölçülebilir iyileştirmeler gördü. Bireysel service'ler için deployment süresi 15 dakikadan (tüm service'leri deploy etme) 3-5 dakikaya (sadece değişen service'i deploy etme) düştü. Merge conflict'ler önemli ölçüde azaldı - birden fazla ekibin shared stack'lere dokunmasından kaynaklanan sprint başına 3-4 conflict yerine, her ekip kendi domain stack'ine sahip olduğu için conflict'ler nadir hale geldi.
Organizasyon pattern'i onboarding'i de iyileştirdi. Yeni developer'lar user service'i service-type klasörlerine dağılmış infrastructure'ı bir araya getirmek yerine sadece domains/user/ directory'sini okuyarak anlayabiliyordu. Code review süresi iyileşti çünkü reviewer'lar cross-stack dependency'leri anlamadan tek bir domain'e odaklanabiliyordu.
Önemli Çıkarımlar
Organizasyon pattern'i business yapısıyla eşleşir: Business'a hizalanmış ekipler için domain-based, infrastructure ekipleri için layer-based, product ekipleri için feature-based seç. Pattern, organizasyonun sistemi nasıl düşündüğünü yansıtmalı.
Stack'lerden önce construct'lar: Çoğu code organizasyon problemi birden fazla stack değil, construct'larla çözülür. Stack'leri sadece deployment bağımsızlığı, team boundary'leri veya kaynak limitine ulaşma için böl.
Foundation pattern esastır: Shared infrastructure'ı (VPC, networking, security) domain-specific kaynaklardan ayır, böylece circular dependency'lerden kaçın ve domain bağımsızlığını sağla.
Küçük ekipler için monorepo, büyükler için multi-repo: Monorepo'lar ~50 developer'a kadar iyi çalışır. Sonrasında, net service boundary'leri ve team özerkliği için multi-repo'yu düşün.
Static stack creation consistency garantiler: Synthesis sırasında tüm environment'ları (dev, staging, prod) oluşturmak dev'de test edilen kodun tam olarak prod'a deploy edileceğini garantiler.
Basit başla, yavaşça geliştir: Büyük stack'ler ve construct'larla başla, sadece net sebepler ortaya çıktığında ayrı stack'lere böl (farklı ekipler, farklı lifecycle'lar, kaynak limitleri).
Doğru organizasyon pattern'i senin spesifik bağlamına bağlı - ekip boyutu, deployment gereksinimleri ve business yapısı. Basit başla ve gerçek ihtiyaçlar netleştikçe organizasyonun ortaya çıkmasına izin ver.