Sequence Diagram 1: Lifeline, Mesaj Türleri, Activation ve Bir API İsteğinin Yolculuğu
Sequence diagram, bir senaryoda participant'lar arasındaki mesajları zaman sırasıyla gösterir: lifeline'lar yan yana, zaman yukarıdan aşağı, synchronous call dolu ok ucuyla, reply kesikli çizgiyle çizilir. Yazıda İK Portalı'nın POST /leave-requests isteği Angular'dan SQL Server'a kadar gerçek koddan ve EF Core log'undan çizilip Mermaid ile kod olarak da veriliyor.
Sürüm notu: Bu yazıdaki diyagramlar UML (Unified Modeling Language) 2.5.1 notasyonuyla çizilmiştir; C# örnekleri .NET 10 ve Entity Framework Core (EF Core) 10 ile, SQL (Structured Query Language) komutları SQL Server 2025 üzerinde, Mermaid kodu Mermaid 12.3.0 ile denenmiştir.
Önce Tahmin Et: Bir İstek Veritabanına Kaç Kez Gider?
İK (insan kaynakları) Portalı'nda Ada, 21–25 Aralık için yıllık izin formunu doldurup "Gönder"e bastı. Angular tarafındaki form, isteği POST /leave-requests olarak API'ye (Application Programming Interface) gönderiyor. Sunucudaki endpoint ve service şöyle:
app.MapPost("/leave-requests", async (CreateLeaveRequestDto dto, ILeaveRequestService service, CancellationToken ct) =>
{
try
{
LeaveRequest request = await service.CreateAsync(
new CreateLeaveRequest(dto.EmployeeId, dto.LeaveTypeCode, dto.Start, dto.End, dto.Reason, dto.Submit), ct);
return Results.Created($"/leave-requests/{request.Id}",
new { request.Id, Status = request.Status.ToString(), request.WorkingDays });
}
catch (LeaveRuleException ex)
{
return Results.Problem(ex.Message, statusCode: StatusCodes.Status409Conflict);
}
});
public async Task<LeaveRequest> CreateAsync(CreateLeaveRequest command, CancellationToken ct)
{
Employee employee = await requests.FindEmployeeAsync(command.EmployeeId, ct)
?? throw new LeaveRuleException($"Employee {command.EmployeeId} not found.");
LeaveType leaveType = await requests.FindLeaveTypeAsync(command.LeaveTypeCode, ct)
?? throw new LeaveRuleException($"Leave type {command.LeaveTypeCode} not found.");
DateRange period = new(command.Start, command.End);
LeaveRequest request = new(employee, leaveType, period, command.Reason, clock.GetUtcNow());
if (command.Submit)
await SubmitAsync(request, ct);
requests.Add(request);
await requests.SaveChangesAsync(ct);
return request;
}
private async Task SubmitAsync(LeaveRequest request, CancellationToken ct)
{
int days = await workingDays.CountAsync(request.Period, ct);
if (request.LeaveType.DeductsFromBalance &&
!await balances.HasEnoughBalanceAsync(request.EmployeeId, request.LeaveType, request.Period.Start.Year, days, ct))
throw new LeaveRuleException($"Not enough annual leave balance for {days} working days.");
if (await requests.HasOverlapAsync(request.EmployeeId, request.Period, ct))
throw new LeaveRuleException("An active leave request already overlaps these dates.");
request.Submit(days, clock.GetUtcNow());
}
Bu tek istek için SQL Server'a kaç komut gider ve hangi sırayla? LeaveRequest object'i ne zaman doğuyor, kimden mesaj alıyor? Kodu baştan sona okuyarak cevaplayabilirsin ama dört farklı dosyaya (endpoint, service, repository, iki ayrı service implementation'ı) atlamak gerekiyor. Bu yazının sorusu, bunu tek bir resimde görmenin yolu.
Cevap: Altı Komut, Tek Senaryo
İsteği gerçekten gönderip EF Core'un log'una bakınca:
curl -i -X POST http://localhost:5281/leave-requests -H 'Content-Type: application/json' \
-d '{"employeeId":4,"leaveTypeCode":"ANNUAL","start":"2026-12-21","end":"2026-12-25","reason":"Year-end trip","submit":true}'
HTTP/1.1 201 Created
Location: /leave-requests/42
{"id":42,"status":"PendingManagerApproval","workingDays":5}
Executed DbCommand (19ms) SELECT TOP(2) ... FROM [Employees] AS [e] WHERE [e].[Id] = @id
Executed DbCommand (5ms) SELECT TOP(2) ... FROM [LeaveTypes] AS [l] WHERE [l].[Code] = @code
Executed DbCommand (5ms) SELECT [p].[Date] FROM [PublicHolidays] AS [p] WHERE ...
Executed DbCommand (8ms) SELECT TOP(2) ... FROM [LeaveBalances] AS [l] WHERE ...
Executed DbCommand (3ms) SELECT CASE WHEN EXISTS (SELECT 1 FROM [LeaveRequests] AS [l] WHERE ...)
Executed DbCommand (8ms) INSERT INTO [LeaveRequests] (...) OUTPUT INSERTED.[Id] VALUES (...)
Beş SELECT ve bir INSERT (log satırlarını okunsun diye kısalttım). Sıralama kodun sıralamasıyla aynı, ama koddan bunu çıkarmak için FindEmployeeAsync'in, CountAsync'in ve HasEnoughBalanceAsync'in implementation'larını ayrı ayrı açmak gerekiyor. Sequence diagram tam bu bilgiyi, yani kimin kime hangi sırayla mesaj gönderdiğini, tek bir sayfada gösteren diyagram.
Sequence Diagram Neyi Cevaplar?
UML 2.5.1'de sequence diagram, Interactions bölümünün dört diyagramından biri (diğerleri communication, interaction overview ve timing). Bir interaction, belli bir senaryoda katılımcılar arasında gidip gelen mesajların sıralı kaydı. Diyagramda katılımcılar yan yana dizilir, zaman yukarıdan aşağı akar. Class diagram "hangi class'lar var ve birbirlerini tanıyorlar mı?" sorusunu cevaplıyordu; sequence diagram ise "bu senaryoda kim kimi, hangi sırayla çağırıyor?" sorusunu.
Anahtar kelime senaryo. Bir sequence diagram genellikle tek bir yolu çizer: burada Ada'nın bakiyesi yeterli, tarihleri çakışmıyor ve formda "Gönder"e basıyor. Koddaki if (command.Submit) ve iki throw bu diyagramda yok. Alternatif yolları (alt, opt, break gibi combined fragment'lerle) serinin bir sonraki yazısında çizeceğiz.
Lifeline ve Execution Specification
Lifeline bir interaction'a katılan tek bir participant: başlık kutusu ve altından inen kesikli çizgi. Başlıktaki söz dizimi ad : Tip. Ad önemli değilse yalnız :LeaveRequestService yazılır; belli bir object'i kastediyorsan req42 : LeaveRequest. Tip bir interface de olabilir: :IBalanceService lifeline'ı, runtime'da DI (dependency injection) container'ın hangi implementation'ı verdiğinden bağımsız olarak "bakiye sorusunu cevaplayan şey" demek. Bu, diyagramı implementation değiştiğinde de doğru tutar.
Lifeline bir class değil, bir instance. Bu ayrım önemli: aynı class'tan iki object bir senaryoya katılıyorsa iki ayrı lifeline çizilir (örneğin bir sonraki yazıda onaycı Alan ve Grace aynı Employee class'ının iki lifeline'ı olabilir).
Lifeline üzerindeki ince dikdörtgen execution specification (pratikte "activation bar"): o participant'ın bir davranışı yürüttüğü, ya da yürüttüğü davranışın içinden başka birini beklediği zaman aralığı. C#'ta bir method'un başından return'üne kadar geçen süre. async method'larda thread await sırasında serbest kalır, ama mantıksal olarak method henüz bitmemiştir; activation bar thread'i değil method çağrısının ömrünü gösterir.
Mesaj Türleri

Mesajlar ok gövdesi ve ok ucuyla birbirinden ayrılır. Synchronous call düz çizgi ve dolu ok ucu: gönderen, cevap gelene kadar devam etmez. Asynchronous message düz çizgi ve açık ok ucu: gönderen mesajı bırakır ve yoluna devam eder. Reply kesikli çizgi ve açık ok ucu; üstüne dönen değer yazılabilir. Create message kesikli çizgiyle yeni lifeline'ın başlık kutusuna gider; lifeline o noktadan önce yoktur. Destruction occurrence lifeline'ın sonuna konan bir X'tir. Kaynağı diyagramın dışında kalan mesaj found message (dolu bir daireden çıkar), hedefi bilinmeyen mesaj lost message (dolu bir dairede biter).
C# geliştiricisini en çok şaşırtan nokta şu: await ile çağrılan bir async method UML'de synchronous call'dır. Sync ile async arasındaki fark thread'in bloklanması değil, gönderenin sonucu bekleyip beklemediği. await service.CreateAsync(...) satırından sonraki kod, cevap gelmeden çalışmaz; yani çağıran bekliyor. Asynchronous message ise beklenmeyen gönderimdir: bir message queue'ya event yazmak, Channel<T>'a bir item bırakmak ya da bilerek await edilmeyen bir Task. Bunları bir sonraki yazıda onay akışında göreceğiz.
Mesaj etiketi de bir söz dizimine sahip: FindEmployeeAsync(4) gibi operation adı ve argümanlar. Reply etiketi döneni söyler (ada, true, Id = 42). Spesifikasyon reply'ı result = Operation(...): value biçiminde de yazmana izin veriyor; pratikte yalnız değer yeterli oluyor.
İzin Talebinin Yolculuğu

Şekil, yukarıdaki gerçek koşunun sequence diagram'ı. Her lifeline'ın koddaki karşılığı:
:LeaveRequestApi: Angular SPA (Single Page Application) tarafındakiLeaveRequestApiservice'i. Form,HttpClientile isteği gönderip cevabı bekliyor; sonucu ekranda gösterdiği için mesaj synchronous çizildi.:HrPortal.Api:MapPost'a verilen lambda. HTTP (Hypertext Transfer Protocol) isteğini domain diline çeviriyor ve201 Createdya da409 Conflictdöndürüyor.:LeaveRequestService: akışı yöneten application service. Diyagramda en çok ok çıkan lifeline bu.req42 : LeaveRequest: service'in ortasındanewile doğan entity; create message'ı bu yüzden başlık kutusuna gidiyor.:ILeaveRequestRepositoryve:IBalanceService: interface tipli lifeline'lar; implementation'larıHrPortal.Leave.Infrastructure'da EF Core kullanıyor.:SQL Server: sistemin dışındaki veritabanı. Mesaj etiketleri log'daki gerçek tablo adları.
Diyagramın altındaki not da diyagramın bir parçası: IWorkingDayCalculator lifeline'ını ve bazı SELECT'leri bilerek çizmedim. Bu bir detay seviyesi kararı; diyagram "tüm çağrılar" değil "bu soruyu cevaplamak için gerekenler". Neyi çıkardığını söylemek, okuyanın eksik bir resmi tam sanmasını engeller.
Diyagramdan Sorumluluk Okumak
Bir sequence diagram çizilince kodun okurken fark edilmeyen özellikleri görünür olur. Bu diyagramda dört tanesi göze çarpıyor.
Okların çoğu tek bir lifeline'dan çıkıyor. LeaveRequestService bir orkestra şefi gibi: her participant'a o soruyor, cevapları o topluyor. Application service için bu doğal; ama aynı şekil bir entity'de görünseydi, o entity'nin fazla şey bildiğinin işareti olurdu.
req42 yalnız iki mesaj alıyor: create ve Submit(5, now). İş kuralı (taslak mı, gün sayısı pozitif mi, yeni durum ne) entity'nin içinde; service yalnız dışarıdan gerekenleri (iş günü sayısı, bakiye, çakışma) topluyor. Sorumluluk dağılımı diyagramda okunabiliyor.
Veritabanına giden oklar sayılabiliyor. Altı komut bu istek için sorun değil, ama aynı diyagramı bir listeleme ekranı için çizip her satır için bir ok gördüğünde N+1 problemini koddan önce diyagramda yakalarsın. EF Core serisindeki EF Core'da Performans Temelleri: N+1, Projection, AsNoTracking ve Compiled Query yazısı bu sorunun çözümlerini anlatıyor.
Kontrol ile kayıt arasında zaman var. HasEnoughBalanceAsync cevap verdikten sonra INSERT'e kadar dört mesaj daha geçiyor ve arada başka bir isteğin bakiyeyi değiştirmesini engelleyen bir şey yok. Diyagram bunu açıkça gösteriyor; requirement'taki R8 maddesinin (onay anında bakiyenin yeniden kontrolü) var olma sebebi de bu.
Aynı Diyagram Kod Olarak: Mermaid
Sequence diagram, diagrams-as-code araçlarının en iyi desteklediği diyagram türü. Aynı senaryonun Mermaid karşılığı (GitHub'daki Markdown dosyalarında doğrudan render olur):
sequenceDiagram
autonumber
participant SPA as :LeaveRequestApi (Angular)
participant Api as :HrPortal.Api
participant Svc as :LeaveRequestService
participant Repo as :ILeaveRequestRepository
participant Bal as :IBalanceService
participant Db as :SQL Server
SPA->>+Api: POST /leave-requests
Api->>+Svc: CreateAsync(command)
Svc->>+Repo: FindEmployeeAsync(4)
Repo->>+Db: SELECT ... FROM Employees
Db-->>-Repo: 1 row
Repo-->>-Svc: ada
Svc->>+Repo: FindLeaveTypeAsync("ANNUAL")
Repo-->>-Svc: annual
create participant Req as req42:LeaveRequest
Svc->>Req: new(ada, annual, period, reason, now)
Svc->>+Bal: HasEnoughBalanceAsync(4, annual, 2026, 5)
Bal->>+Db: SELECT ... FROM LeaveBalances
Db-->>-Bal: EntitledDays 14, UsedDays 6
Bal-->>-Svc: true
Svc->>+Repo: HasOverlapAsync(4, period)
Repo-->>-Svc: false
Svc->>Req: Submit(5, now)
Svc->>Repo: Add(req42)
Svc->>+Repo: SaveChangesAsync()
Repo->>+Db: INSERT INTO LeaveRequests ...
Db-->>-Repo: Id = 42
Repo-->>-Svc:
Svc-->>-Api: req42
Api-->>-SPA: 201 Created
Bu dosya mermaid-cli ile (Mermaid 12.3.0) hatasız render oldu. ->> synchronous call, -->> reply, ok sonundaki + ve - activation'ı açıp kapatıyor; autonumber mesajları numaralıyor. create participant satırı lifeline'ı ilk mesajın hizasında başlatıyor, ama Mermaid create message'ı kesikli değil düz çizgiyle çiziyor; UML'e sadık bir çizim istiyorsan bunu bilmen gerekiyor. Mermaid'de asynchronous message için -) var; bir sonraki yazıda kullanacağız. Söz diziminin geri kalanı serinin Mermaid ve Structurizr DSL (Domain-Specific Language) yazısında.
Sık Düşülen Tuzaklar
Her method çağrısını çizmek. Include, SingleOrDefaultAsync, GetUtcNow, logging çağrıları... Hepsini çizersen diyagram kodun okunması zor bir kopyasına dönüşür. Kural: senaryonun sorusunu cevaplamaya yetecek kadar mesaj, ve çıkardığını not olarak söyle.
Lifeline'ları class ya da katman adıyla çizmek. "Business Layer" ya da "Data Access" bir participant değil, bir klasör. Mesajı alan şey bir object; katman adı yazınca kimin neyi bildiği kaybolur.
await'i asynchronous message sanmak. Açık ok ucu "gönderen beklemiyor" demek; await edilen her çağrı dolu ok ucuyla çizilir. Yanlış ok ucu, okuyanı fire-and-forget bir tasarım olduğuna inandırır.
Reply'ları tamamen atlamak ya da hepsini çizmek. Dönen değer akışın devamını etkiliyorsa (true, Id = 42) reply'ı çiz; void bir çağrının boş reply'ı genellikle gürültü.
Birden fazla senaryoyu tek diyagrama sıkıştırmak. "Bakiye yetmezse şuraya, çakışma varsa buraya" diye ok eklemeye başladığında ya combined fragment'e (sonraki yazı) ya da ayrı bir diyagrama ihtiyacın var.
Ne Zaman Kullanma
Tek bir service'in tek bir repository'yi çağırdığı CRUD (Create, Read, Update, Delete) endpoint'leri için sequence diagram çizme; kod zaten bir bakışta okunuyor. Sequence diagram, akış birden fazla sınırı (process, network, dış sistem) geçtiğinde, sıranın önemli olduğu yerde (önce kontrol, sonra kayıt) ya da bir performans veya tutarlılık sorununu konuşurken değer üretir. Bir kod review'unda "bu istek kaç kez veritabanına gidiyor?" sorusu geldiyse, çizmenin tam zamanı.
Küçük Bir Deneme
★☆☆ Şekle :IWorkingDayCalculator lifeline'ını ve onun PublicHolidays SELECT'ini ekle. Hangi iki mesajın arasına giriyor ve neden?
★★☆ Ada aynı formu "Taslak olarak kaydet" ile gönderdi ("submit": false). Bu senaryonun sequence diagram'ını çiz ve gerçek koşuyla doğrula: kaç DbCommand çalışıyor, hangi lifeline'lar diyagramdan tamamen çıkıyor?
★★★ FindEmployeeAsync ve FindLeaveTypeAsync ayrı ayrı veritabanına gidiyor. Bu iki SELECT'i ve HasOverlapAsync'i birleştiren bir tasarım öner, yeni sequence diagram'ını çiz ve önce/sonra log'larını karşılaştır. Hangi sorumluluğu hangi lifeline'a taşıdın?
Bir sonraki yazıda bu diyagrama koşulları ve paralelliği ekliyoruz: alt, opt, loop, par, break ve ref ile yönetici ve direktör onayını, yetersiz bakiye hatasını ve onaydan sonra message queue üzerinden giden e-posta ve takvim kaydını çizeceğiz.