Context Mapping: Anti-Corruption Layer, Open Host Service ve Bounded Context'ler Arası İlişkiler
Context map, bounded context'ler arasındaki ilişkileri hem teknik hem organizasyonel olarak adlandırır: upstream/downstream, Partnership, Shared Kernel, Customer/Supplier, Conformist, Anti-Corruption Layer, Open Host Service, Published Language, Separate Ways ve Big Ball of Mud. Bookstore'un context map'i, eski kargo sisteminin SOAP/XML modelini Shipping'e çeviren test'li bir ACL, versiyonlu bir Published Language ve Shared Kernel'in Common.dll'e dönüşme tuzağı.
Sürüm notu: Bu yazıdaki kodlar C# 14 ve .NET 10 ile derlenip denenmiştir.
Önce Tahmin Et
Bookstore'un çalıştığı kargo firmasının gönderi sorgulama servisi yıllar önce yazılmış bir SOAP (Simple Object Access Protocol) servisi. Bir siparişin durumunu sorduğunda cevap şöyle geliyor:
<ShipmentRecord>
<RefNo>ORD-1001</RefNo>
<TrkNo>0007700123456</TrkNo>
<StatCd>05</StatCd>
<StatDt>20261107143000</StatDt>
<BranchCd>IST-034</BranchCd>
<PcsCnt>1</PcsCnt>
</ShipmentRecord>
05 "dağıtımda" demek, tarih Türkiye saatiyle ve offset'siz, takip numarasının başında sıfırlar var. Bookstore'un kodu bugün bu cevabı doğrudan kullanıyor: sipariş takip sayfası, kargo e-postası ve bir rapor StatCd == "05" kontrolünü kendi içinde yapıyor, toplam 14 yerde. Kargo firması bir duyuru yayımlıyor: aktarma merkezleri için yeni bir 04 kodu geliyor. Kaç dosya değişir? Bir de: kargo firmasıyla konuşup servisini Bookstore'un ihtiyacına göre değiştirmesini istemek mümkün mü? Tahmin et.
Context Map: Var Olan Araziyi Çizmek
Dördüncü yazıda her bounded context'e kendi modelini verdik. Ama context'ler birbirine dokunuyor ve bu temas noktaları kendi başına bir tasarım konusu. Evans'ın DDD (Domain-Driven Design) Reference'ındaki Context Map pattern'i şöyle der: projede kullanılan her modeli belirle ve bounded context'ini tanımla; nesne yönelimli olmayan alt sistemlerin örtük modelleri de dahil. Her context'e bir isim ver ve bu isimleri ubiquitous language'in parçası yap. Modeller arasındaki temas noktalarını, her iletişim için açık çeviriyi, paylaşımı, yalıtım mekanizmalarını ve etki düzeyini tarif et.
Pattern'deki en önemli cümle şu: var olan araziyi çiz, dönüşümleri sonraya bırak. Context map bir hedef mimari çizimi değil, bugünün dürüst bir fotoğrafı. Bookstore'un kargo entegrasyonu bugün eski servisin XML (Extensible Markup Language) alanlarını kodun içine taşıyorsa, harita bunu göstermeli; nasıl düzelteceğimize ancak ondan sonra karar verebiliriz. Reference ayrıca bu ilişkilerin teknik olmayan kanallarla, yani ekipler arası güç dengesi ve iletişimle şekillendiğini vurgular. Context mapping'in yarısı kod, yarısı organizasyon.
Ekipler Arası Üç İlişki
DDD Crew topluluğunun context mapping kaynağı, pattern'lere geçmeden önce ekipler arasındaki ilişkiyi üç türe ayırır:
- Mutually dependent: iki ekibin yazılımı ancak birlikte teslim edildiğinde çalışır. Sürekli koordinasyon gerekir.
- Upstream/downstream: upstream ekibin yaptıkları downstream'i etkiler, tersi geçerli değildir. Upstream, downstream'in akıbetinden bağımsız başarılı olabilir.
- Free: iki ekip arasında organizasyonel ya da teknik bir bağ yoktur; birinin değişikliği diğerinin başarısını etkilemez.
Upstream ve downstream nehir benzetmesinden geliyor: nehrin yukarısına atılan bir şey aşağıya ulaşır, aşağıya atılan yukarıya çıkmaz. Kargo firması Bookstore'un upstream'i; servisini değiştirdiğinde Bookstore etkilenir, Bookstore'un ihtiyaçları ise kargo firmasının yol haritasını pek etkilemez. Tahmin sorusundaki ikinci sorunun cevabı da bu: büyük ihtimalle hayır.
Context Mapping Pattern'leri
Evans'ın Reference'ında dokuz ilişki pattern'i var; Partnership ve Big Ball of Mud kitaptan sonra eklenenler. Her biri, iki context arasındaki ilişkinin hem teknik hem organizasyonel yanını adlandırır:
| Pattern | Ne zaman | Ne yapılır |
|---|---|---|
| Partnership | İki context birlikte başarılı ya da başarısız oluyor | Ekipler planlamayı ve entegrasyonu birlikte yönetir, bağımlı özellikler aynı sürümde çıkar |
| Shared Kernel | İki ekip modelin küçük bir parçasını paylaşmaktan fayda görüyor | Paylaşılan alt küme açıkça sınırlanır, küçük tutulur, diğer ekibe danışmadan değiştirilmez |
| Customer/Supplier | Upstream/downstream ilişki, ama upstream downstream'i önemsiyor | Downstream'in öncelikleri upstream'in planına girer, ortak kabul test'leri yazılır |
| Conformist | Upstream'in downstream'in ihtiyacını karşılamak için motivasyonu yok | Downstream upstream'in modelini olduğu gibi kullanır, çeviri maliyetinden kurtulur |
| Anti-Corruption Layer | Upstream'in modeli zayıf ya da uygunsuz, ama entegrasyon gerekli | Downstream, upstream'i kendi modelinin terimleriyle sunan yalıtıcı bir çeviri katmanı kurar |
| Open Host Service | Bir context'e birçok context entegre oluyor | Herkesin kullanabileceği açık bir protokol tanımlanır, özel ihtiyaçlar tek seferlik çevirmenlerle karşılanır |
| Published Language | Modeller arası çeviri için ortak bir dil gerekiyor | İyi dokümante edilmiş, paylaşılan bir veri değişim dili kullanılır; çoğu zaman Open Host Service ile birlikte |
| Separate Ways | İki işlev arasında anlamlı bir ilişki yok | Hiç entegre olunmaz; her taraf kendi basit çözümünü kurar |
| Big Ball of Mud | Modellerin karıştığı, sınırların tutarsız olduğu bir bölge | Tamamının etrafına sınır çizilir, içinde sofistike modelleme denenmez, dışarı yayılması engellenir |
Khononov bu pattern'leri üç grupta toplar: cooperation (Partnership, Shared Kernel), customer-supplier (Conformist, Anti-Corruption Layer, Open Host Service) ve Separate Ways. Gruplama, seçimin önce bir organizasyon sorusu olduğunu hatırlatıyor: iki ekip birlikte mi kazanıp kaybediyor, yoksa biri diğerinden güçlü mü?
Bookstore'un Context Map'i
Mimariye Giriş serisinin Conway's Law yazısındaki hedef ekip yapısını, yani Catalog & Search, Ordering & Payment, Fulfillment (Inventory ve Shipping) ve Customer Experience (Notification ve hesap) ekiplerini varsayarak Bookstore'un haritası:

Catalog ile Ordering arasında Customer/Supplier ilişkisi var: Ordering, Catalog'un müşterisi ve Catalog ekibi planını yaparken Ordering'in ihtiyaçlarını hesaba katıyor. Catalog kitap bilgisini bir Open Host Service olarak, fiyat değişikliklerini de BookPriceChanged event'iyle bir Published Language üzerinden sunuyor; Inventory ve ileride mobil uygulama da aynı protokolü kullanacak. Ordering, siparişin verildiğini OrderPlaced event'iyle yayınlıyor ve Inventory ile Shipping bunu dinliyor. Shipping gönderiyi Payment'tan gelen PaymentCaptured sonrasında oluşturuyor; haritayı okunur tutmak için bu oku çizmedik.
Ordering ile Payments aynı ekipte ve birlikte başarılı ya da başarısız oluyorlar: ödeme alınamayan bir sipariş akışı işe yaramaz. Bu bir Partnership ve ikisi küçük bir Shared Kernel paylaşıyor: yalnız Money value object'i. Payments'ın ödeme sağlayıcısıyla, Shipping'in eski kargo sistemiyle ilişkisi ise anti-corruption layer: ikisinde de upstream bizim ihtiyacımıza göre değişmeyecek ve modelleri bizimkine uymuyor.
Notification ise bilinçli olarak Conformist: Ordering'in ve Shipping'in event'lerini olduğu gibi kullanıyor ve e-posta şablonlarını bu event'lerin alanlarıyla dolduruyor. Kendi modeline çeviri yapmak, değeri olmayan bir maliyet olurdu; Notification'ın tek işi bildirmek. Haritada olmayan kimlik sağlayıcısı da benzer: kullanıcı ve oturum kavramlarını hazır çözümün modelinden aynen alıyoruz. Ve unutmamak gerekir: bugünkü layered monolith'teki Bookstore.Business project'inin bir kısmı hâlâ bu haritaya göre ayrılmadı. Dürüst bir harita o bölgeyi Big Ball of Mud olarak işaretler ve yeni kodun oraya bağlanmasını engeller.
Anti-Corruption Layer'ı Kodla: Eski Kargo Sistemi
Tahmin sorusundaki 14 yerin sorunu, eski sistemin modelinin Bookstore'un içine sızmış olması. Kargo firmasının StatCd'si sipariş takip sayfasında, e-postada ve raporda ayrı ayrı yorumlanıyor; yeni bir kod geldiğinde hepsi değişiyor. Anti-corruption layer (ACL) bu sızıntıyı tek bir yere toplar. Önce Shipping'in kendi modeli ve dış dünyaya sorduğu soru:
namespace Bookstore.Shipping;
public enum ShipmentStatus { Accepted, InTransit, OutForDelivery, Delivered, DeliveryFailed, ReturnedToSender }
public readonly record struct TrackingNumber(string Value)
{
public override string ToString() => Value;
}
public sealed record ShipmentTracking(string OrderReference, TrackingNumber TrackingNumber, ShipmentStatus Status, DateTimeOffset UpdatedAtUtc);
// Port: Shipping modeli kendi kelimeleriyle soruyor
public interface IShipmentTracker
{
Task<ShipmentTracking> TrackAsync(string orderReference, CancellationToken cancellationToken);
}
Shipping "durum kodu" bilmiyor; ShipmentStatus ve TrackingNumber biliyor ve zamanı UTC (Coordinated Universal Time) olarak tutuyor. Çeviriyi bir translator yapıyor:
using System.Globalization;
using System.Xml.Linq;
namespace Bookstore.Shipping.LegacyCarrier;
public sealed class LegacyCarrierContractException(string message) : Exception(message);
// Translator: eski SOAP/XML modeli -> Shipping modeli; eski modelden dışarı hiçbir şey sızmaz
public static class LegacyShipmentTranslator
{
private static readonly XNamespace Ns = "urn:legacy-carrier:v1";
private static readonly TimeSpan CarrierClockOffset = TimeSpan.FromHours(3);
private static readonly Dictionary<string, ShipmentStatus> StatusCodes = new()
{
["01"] = ShipmentStatus.Accepted,
["03"] = ShipmentStatus.InTransit,
["04"] = ShipmentStatus.InTransit,
["05"] = ShipmentStatus.OutForDelivery,
["07"] = ShipmentStatus.Delivered,
["09"] = ShipmentStatus.ReturnedToSender,
["11"] = ShipmentStatus.DeliveryFailed,
};
public static ShipmentTracking ToTracking(XDocument soapResponse)
{
XElement record = soapResponse.Descendants(Ns + "ShipmentRecord").SingleOrDefault()
?? throw new LegacyCarrierContractException("ShipmentRecord is missing.");
string code = Required(record, "StatCd");
if (!StatusCodes.TryGetValue(code, out ShipmentStatus status))
throw new LegacyCarrierContractException($"Unknown status code '{code}'.");
// Kargo firması yerel saati (UTC+3) offset'siz gönderiyor
DateTime local = DateTime.ParseExact(Required(record, "StatDt"), "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
DateTimeOffset updatedAtUtc = new DateTimeOffset(local, CarrierClockOffset).ToUniversalTime();
return new ShipmentTracking(
Required(record, "RefNo"),
new TrackingNumber(Required(record, "TrkNo").TrimStart('0')),
status,
updatedAtUtc);
}
private static string Required(XElement record, string name) =>
record.Element(Ns + name)?.Value.Trim() is { Length: > 0 } value
? value
: throw new LegacyCarrierContractException($"{name} is missing.");
}
SOAP konuşmayı ise bir adapter üstleniyor ve dışarıya yalnız Shipping'in kendi modelini veriyor:
using System.Text;
using System.Xml.Linq;
namespace Bookstore.Shipping.LegacyCarrier;
// Adapter: eski kargo sistemiyle SOAP konuşur, Shipping modelini döner
public sealed class LegacyCarrierAdapter(HttpClient http) : IShipmentTracker
{
public async Task<ShipmentTracking> TrackAsync(string orderReference, CancellationToken cancellationToken)
{
string envelope = $"""
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<QueryShipment xmlns="urn:legacy-carrier:v1"><RefNo>{orderReference}</RefNo></QueryShipment>
</soap:Body>
</soap:Envelope>
""";
using StringContent content = new(envelope, Encoding.UTF8, "text/xml");
content.Headers.Add("SOAPAction", "urn:legacy-carrier:v1/QueryShipment");
using HttpResponseMessage response = await http.PostAsync("ShipmentService.asmx", content, cancellationToken);
response.EnsureSuccessStatusCode();
await using Stream body = await response.Content.ReadAsStreamAsync(cancellationToken);
XDocument document = await XDocument.LoadAsync(body, LoadOptions.None, cancellationToken);
return LegacyShipmentTranslator.ToTracking(document);
}
}
Kargo firmasının servisi yerine aynı XML'i dönen bir stub ile çalıştıralım:
using System.Globalization;
using System.Net;
using System.Text;
using Bookstore.Catalog.Contracts.V1;
using Bookstore.Shipping;
using Bookstore.Shipping.LegacyCarrier;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
// Kargo firmasının SOAP servisi gibi cevap veren bir stub
const string soapResponse = """
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<QueryShipmentResponse xmlns="urn:legacy-carrier:v1">
<ShipmentRecord>
<RefNo>ORD-1001</RefNo>
<TrkNo>0007700123456</TrkNo>
<StatCd>05</StatCd>
<StatDt>20261107143000</StatDt>
<BranchCd>IST-034</BranchCd>
<PcsCnt>1</PcsCnt>
</ShipmentRecord>
</QueryShipmentResponse>
</soap:Body>
</soap:Envelope>
""";
HttpClient http = new(new StubHandler(soapResponse)) { BaseAddress = new Uri("https://carrier.example/") };
IShipmentTracker tracker = new LegacyCarrierAdapter(http);
ShipmentTracking tracking = await tracker.TrackAsync("ORD-1001", CancellationToken.None);
Console.WriteLine(tracking);
BookPriceChanged priceChanged = new(Guid.Parse("0199a3e0-0000-7000-8000-000000000001"), 20.00m, 18.00m, "EUR", new DateTimeOffset(2026, 11, 7, 9, 0, 0, TimeSpan.Zero));
Console.WriteLine($"{BookPriceChanged.EventType}: {System.Text.Json.JsonSerializer.Serialize(priceChanged)}");
sealed class StubHandler(string xml) : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(xml, Encoding.UTF8, "text/xml") });
}
ShipmentTracking { OrderReference = ORD-1001, TrackingNumber = 7700123456, Status = OutForDelivery, UpdatedAtUtc = 11/07/2026 11:30:00 +00:00 }
bookstore.catalog.book-price-changed.v1: {"BookId":"0199a3e0-0000-7000-8000-000000000001","OldPrice":20.00,"NewPrice":18.00,"Currency":"EUR","ChangedAtUtc":"2026-11-07T09:00:00+00:00"}
İlk satır ACL'in çıktısı: 05 artık OutForDelivery, Türkiye saatiyle 14:30 artık UTC ile 11:30, takip numarası baştaki sıfırlardan arınmış. İkinci satıra Open Host Service bölümünde döneceğiz. Kargo firması yeni bir 04 kodu eklediğinde değişen yer tek bir dictionary satırı; sipariş takip sayfası, e-posta ve rapor ShipmentStatus ile çalıştığı için hiçbirine dokunulmuyor. Tahmin sorusunun cevabı 14 değil, 1.
Evans kitabında ACL'in içini genellikle facade, adapter ve translator'larla kurmayı anlatır; yukarıdaki kod da aynı ayrımı yapıyor. Bu, Design Patterns serisindeki Adapter Pattern yazısının bir bounded context sınırındaki karşılığı; fark, adapter'ın yalnız interface'i değil, kavramları da çevirmesi. Microsoft'un Azure Architecture Center'ındaki Anti-corruption Layer pattern sayfası da aynı fikri anlatır ve bedelini not eder: katman ek gecikme ve yönetilecek bir parça daha getirir. Clean, Onion ve Hexagonal Architecture serisindeki infrastructure adapter'ları ile karşılaştırırsan, IShipmentTracker bir port, LegacyCarrierAdapter onun adapter'ı; ACL bu adapter'ın içine eklenen çeviri sorumluluğu.
ACL'i Test Etmek
ACL'in en değerli parçası translator ve o saf bir fonksiyon: XML alır, Shipping modeli döner. HTTP (Hypertext Transfer Protocol) ya da ağ olmadan test edilebilir:
using System.Xml.Linq;
using Bookstore.Shipping;
using Bookstore.Shipping.LegacyCarrier;
namespace Bookstore.Shipping.Tests;
public class LegacyShipmentTranslatorTests
{
private static XDocument Response(string statusCode, string statusDate = "20261107143000") => XDocument.Parse($"""
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<QueryShipmentResponse xmlns="urn:legacy-carrier:v1">
<ShipmentRecord>
<RefNo>ORD-1001</RefNo>
<TrkNo>0007700123456</TrkNo>
<StatCd>{statusCode}</StatCd>
<StatDt>{statusDate}</StatDt>
</ShipmentRecord>
</QueryShipmentResponse>
</soap:Body>
</soap:Envelope>
""");
[Theory]
[InlineData("01", ShipmentStatus.Accepted)]
[InlineData("03", ShipmentStatus.InTransit)]
[InlineData("04", ShipmentStatus.InTransit)]
[InlineData("05", ShipmentStatus.OutForDelivery)]
[InlineData("07", ShipmentStatus.Delivered)]
[InlineData("09", ShipmentStatus.ReturnedToSender)]
[InlineData("11", ShipmentStatus.DeliveryFailed)]
public void Maps_legacy_status_codes(string code, ShipmentStatus expected) =>
Assert.Equal(expected, LegacyShipmentTranslator.ToTracking(Response(code)).Status);
[Fact]
public void Converts_carrier_local_time_to_utc() =>
Assert.Equal(new DateTimeOffset(2026, 11, 7, 11, 30, 0, TimeSpan.Zero),
LegacyShipmentTranslator.ToTracking(Response("05")).UpdatedAtUtc);
[Fact]
public void Drops_leading_zeros_from_tracking_number() =>
Assert.Equal(new TrackingNumber("7700123456"), LegacyShipmentTranslator.ToTracking(Response("05")).TrackingNumber);
[Fact]
public void Unknown_status_code_fails_loudly()
{
LegacyCarrierContractException ex = Assert.Throws<LegacyCarrierContractException>(
() => LegacyShipmentTranslator.ToTracking(Response("99")));
Assert.Equal("Unknown status code '99'.", ex.Message);
}
}
dotnet test Bookstore.Shipping.Tests
Passed! - Failed: 0, Passed: 10, Skipped: 0, Total: 10, Duration: 15 ms - Bookstore.Shipping.Tests.dll (net10.0)
Bu test'ler aynı zamanda kargo firmasıyla aramızdaki sözleşmenin yazılı hâli. Kod tablosu, saat dilimi ve takip numarası biçimi burada; kargo firması bir şeyi değiştirdiğinde önce bu test'lere bir örnek eklenir. Bilinmeyen bir kodun sessizce varsayılan bir değere dönmek yerine exception fırlatması da bilinçli bir seçim: yanlış durum göstermek, hata vermekten pahalı. Production'da bu exception'ı log'layıp gönderiyi "bilinmeyen durum" kuyruğuna almak, ACL'in dışındaki bir karar.
Open Host Service ve Published Language
ACL downstream'in savunmasıysa, Open Host Service upstream'in nezaketidir. Catalog'a Ordering, Inventory, mobil uygulama ve ileride başka context'ler bağlanacak. Her biri için ayrı bir çeviri yazmak yerine Catalog, iç modelinden bağımsız ve versiyonlu bir sözleşme yayınlıyor:
namespace Bookstore.Catalog.Contracts.V1;
// Published Language: dokümante, versiyonlu, Catalog'un içindeki Book modelinden ayrı
public sealed record BookSummary(Guid BookId, string Isbn, string Title, decimal ListPrice, string Currency);
public sealed record BookPriceChanged(Guid BookId, decimal OldPrice, decimal NewPrice, string Currency, DateTimeOffset ChangedAtUtc)
{
public const string EventType = "bookstore.catalog.book-price-changed.v1";
}
Demo'nun son iki satırı bu event'i JSON (JavaScript Object Notation) olarak yazdırıyor; çıktının ikinci satırı Catalog'un dış dünyaya söylediği cümle. Catalog'un içindeki Book yarın on yeni property alabilir; V1.BookSummary ve V1.BookPriceChanged değişmez. Kırıcı bir değişiklik gerekirse V2 yanına eklenir ve eski tüketiciler bir süre V1 ile yaşamaya devam eder. Event tipindeki .v1 son eki de aynı sözü mesaj düzeyinde veriyor.
.NET'te Published Language'in gündelik bir örneği ASP.NET Core'un ProblemDetails tipi. Hata cevaplarını, RFC (Request for Comments) 7807 ile tanımlanan ve bugün RFC 9457 ile güncellenen application/problem+json formatında döndürür. Bu format ne senin modelin ne de client'ınki; iki tarafın da çevirdiği, herkesin okuyabildiği ortak bir dil. Endpoint'lerini OpenAPI ile belgelemek de aynı işi görür: sözleşme koddan ayrı, okunabilir ve versiyonlanabilir hâle gelir. ASP.NET Core Web API (Application Programming Interface) serisinin ProblemDetails ile global hata yönetimi ve Swagger ve OpenAPI yazılarında bu araçları ayrıntılı gördük; context mapping açısından önemli olan, onların bir Published Language rolü oynaması.
Shared Kernel'in Sınırı
Shared Kernel kâğıt üzerinde masum: iki ekip modelin küçük bir parçasını ortak kullanıyor. Reference'ın uyarısı da net: bu çok yakın bir bağımlılık, tasarımı güçlendirebilir de baltalayabilir de; çekirdeği küçük tut ve diğer ekibe danışmadan değiştirme. Bookstore'da Ordering ve Payments, Bookstore.SharedKernel içinde yalnız Money'yi paylaşıyor. Para birimi kuralları ve yuvarlama iki context'te farklılaşırsa bunun bedeli gerçek para olur; ortak tek bir tanım burada hakikaten değerli.
Sorun, Shared Kernel'in zamanla bir "Common" project'ine dönüşmesi. Önce Money, sonra bir Address, sonra bir DateHelper, sonra herkesin kullandığı bir BaseEntity, sonra bir Customer. Bir gün Common.dll'deki bir değişiklik altı context'i birden kırar ve dördüncü yazıda parçaladığımız dev class başka bir isimle geri döner. Bir kural koymak işe yarıyor: Shared Kernel'e eklenen her tip, onu paylaşan iki ekibin de onayıyla girer ve gerçekten aynı anlamı taşımayan hiçbir şey oraya konmaz.
Sık Düşülen Tuzaklar
Fark etmeden Conformist olmak. Ödeme sağlayıcısının durum enum'ını, kargo firmasının StatCd'sini doğrudan kendi entity'lerinde kullanmak bir karar gibi görünmez, ama bir karardır: upstream'in modelini benimsemişsin. Conformist bazen doğru seçimdir; Notification'da olduğu gibi bilinçli verildiğinde.
Her yere ACL koymak. Aynı ekipteki iki context arasında, modelleri zaten uyumluyken çeviri katmanı yazmak bakım maliyeti üretir. ACL'i upstream'in modeli gerçekten uymadığında ve upstream'i etkileyemediğinde kur.
Shared Kernel'i Common.dll çöplüğüne çevirmek. Paylaşılan kod, paylaşılan karar demektir. Küçük tutulmayan bir çekirdek, sınırları sessizce siler.
Haritayı ideal hâlinden çizmek. Context map bugünün fotoğrafı olmalı. Big Ball of Mud olan bölgeyi temiz kutular olarak çizmek, ekibe yanlış bir güven verir ve yeni kodun o çamura bağlanmasını kolaylaştırır.
Sözleşmeyi iç modelle aynı tutmak. Catalog'un Book entity'sini olduğu gibi JSON'a çevirip yayınlamak Open Host Service değildir; iç modeldeki her değişiklik bütün tüketicileri kırar. Sözleşme ayrı ve versiyonlu olmalı.
Küçük Bir Deneme
★☆☆ Kendi projenin dış sistemlerini listele: ödeme, kargo, e-posta, muhasebe, kimlik. Her biri için ilişkiyi adlandır: Conformist mi, ACL mi? Bu bilinçli bir karar mıydı?
★★☆ Kargo firması yeni bir 13 kodu ekliyor: "alıcı adreste yok, şubede bekliyor". Bunu ShipmentStatus'a ekle, translator'ı ve test'leri güncelle. Kaç dosyaya dokundun? Shipping dışında bir dosya değişti mi?
★★★ Kendi sistemin için bir context map çiz: context'ler, dış sistemler, upstream ve downstream okları, her okta bir pattern adı. Sonra iki soruyu cevapla: Hangi ilişki bugün olduğundan farklı olmalı? Haritanın hangi bölgesi Big Ball of Mud?
Bu yazıyla Bookstore'un strategic design resmini tamamladık: dil, subdomain'ler, bounded context'ler ve aralarındaki ilişkiler. Ama bu resmi masa başında, kod okuyarak çizdik. Gerçek bir projede bu bilgi domain uzmanlarının kafasında dağınık durur ve onu bir araya getirmenin hızlı bir yolu var. Bir sonraki yazıda EventStorming ile Bookstore'un sipariş, ödeme, kargo ve iade akışını bir duvarın üzerinde yeniden keşfediyoruz.