36 drawings, one hand, one pen. Each links back to the sheet it belongs to. They are drawn to be read left to right, and each has a note in the margin that points at the part worth arguing about.
Fig. 1. The only legal dependency directions in a modular monolith. The host may call a module's public facade, a module may call the kernel, and nothing may call sideways into a sibling's internals. B-01 · The modular monolith is a legitimate destinationFig. 1. A context map for a small multi-tenant web application. U is upstream, D is downstream; the arrow points the way influence travels. Only the hatched system, which we did not write, sits behind an anti-corruption layer. B-02 · Bounded contexts before microservices: drawing the seamsFig. 1. The dual write. Two systems, two writes, no transaction across them. The hatched band is the window in which the process can die with the invoice saved and nobody told. B-03 · The transactional outbox with EF Core and Azure Service BusFig. 2. The relay carries the row to the bus after the commit. The hatched gap is the reason delivery is at-least-once: the send has happened, the row has not yet been marked. B-03 · The transactional outbox with EF Core and Azure Service BusFig. 1. The cheapest useful form of CQRS. The two paths share a database and share nothing else; the read side never loads an entity and the write side never returns a screen shape. B-04 · CQRS without the ceremonyFig. 1. Both deliveries reach the consumer and both attempt the insert. The primary key on (consumer, message id) is the only thing that distinguishes them, and it does so inside the same transaction as the payment. B-05 · Idempotent consumers and the duplicate message you will receiveFig. 1. Two versions, two adapters, one domain model. The hatched adapter is the frozen one: it fabricates a field the domain no longer has, and that fabrication is the entire cost of keeping v1 alive. B-06 · Versioning a public API for a thousand tenantsFig. 1. The hosting ladder as I draw it today. The hatched area is the gap that stood open from 2018 to 2022; a small team either stayed at the bottom or climbed too high. C-01 · App Service, Container Apps or AKS: hosting a .NET web application then and nowFig. 1. One Front Door profile routes each tenant's subdomain to the origin group of its stamp. The stamps share nothing but the map that says who lives where. C-02 · Deployment stamps: cloning the whole cellFig. 1. The tenant id is set once in the API, travels as baggage, is written onto the message by hand, and is restored in the worker before its first span starts. The processor turns it into a tag on all five spans. C-03 · OpenTelemetry in .NET: traces that carry the tenant idFig. 1. The evaluator sees the tenant context and the flag definition and nothing else. Entitlements sit beside it, consulted by the code that needs them, and are not something a flag decides. C-04 · Feature flags with Azure App Configuration: ship dark, reveal per tenantFig. 1. Three buckets, three rules. Shared cost is split equally, the floor is split by design capacity, and the variable lines follow measured drivers down to each tenant. C-05 · The cost sheet: what a tenant actually costs youFig. 1. Four rungs, one module. The parameter file is the only thing that changes between them, and the two upper gates are where a human reads the what-if before anything runs. C-06 · Bicep and the environments ladderFig. 1. The two front doors. Behind Door A every box is yours to run; behind Door B you get an ID token and an invoice. I-01 · ASP.NET Core Identity or Azure AD B2C: choosing the front doorFig. 1. The redirect dance for tenant acme. The host names the tenant before the challenge, so the policy scheme can forward to oidc-acme, and the callback lands on the same host so the same options validate the token. I-02 · OpenID Connect for a multi-tenant web application: one issuer, many tenantsFig. 1. One authorization decision. The handler asks for the user's roles in the current tenant, never the user's roles in general, and the cache key carries both halves. I-03 · Authorization policies, requirements and handlers: roles that belong to a tenantFig. 1. Two machine paths, one principal. The key path looks up a hash in your table; the token path validates a signature against the issuer's keys. Everything after the principal is shared. I-04 · API keys, client credentials and machine accessFig. 1. The admin consent dance. The tenant value that comes back on the redirect is a hint; the tid claim in a validated id_token is the fact, and it only means something once it is in your link table. I-05 · Entra ID multi-tenant applications and the admin consent danceFig. 1. One ADR page as a drawn sheet. Four sections and a status; the last line is the one that gets read when the numbers in the context stop being true. K-01 · Architecture decision records: a page per decisionFig. 1. Every component that existed, every connection that existed. Accurate, and useless: no line says what travels along it or why. K-02 · How I draw a system before I build itFig. 2. Same system, one question. Six boxes, six arrows, each with a verb, and the failure path drawn while the pen was still out. K-02 · How I draw a system before I build itFig. 1. Four layers, four questions. The top two carry opinion and date slowly; the bottom two carry behaviour and are the only ones to trust about what a service does today. K-03 · Reading Microsoft docs like an architectFig. 1. The report and the shared login, drawn as what they are. Both cross the hatched strip, and the hatched strip is the only thing that keeps company 1's rows out of company 2's screens. K-04 · Saying no to the feature that breaks the tenancy modelFig. 2. The alternatives. Left: each tenant publishes agreed totals outward into a read model the organisation owns. Right: one person, several memberships, one tenant per token. K-04 · Saying no to the feature that breaks the tenancy modelFig. 1. One line from 2010 to 2026, sampled every two years. Above it, what arrived on schedule; below it, what I learned late and at my own expense. The vermilion box is 2016, the only year in which the two rows had to agree with each other. K-05 · Fourteen years of .NET web applications: what changed, what did notFig. 1. The pipeline reads outward. The innermost timeout bounds one attempt, the retry decides how many attempts, and the outer timeout bounds the wait for the caller regardless of what happens inside. R-01 · Retries, timeouts and the circuit breaker: Polly then, Microsoft.Extensions.Resilience nowFig. 1. Both layers are addressed by the same string. The hatched prefix is the only thing that stops instance 2 handing tenant 0042 data to tenant 0051. R-02 · Caching layers for a web application: in-process, Redis, and HybridCacheFig. 1. Each tenant gets its own bucket, sized by plan. Tenant 0051 has drained theirs and is being told to come back in eight seconds, while the other two carry on untouched. R-03 · Rate limiting the noisy neighbourFig. 1. Both instances wake at 02:00 and both try to acquire the same blob lease. One gets 201 and does the work, the other gets 409 and goes back to sleep. R-04 · Background work: hosted services, queues and the job you must not run twiceFig. 1. The generator on the left, the dashboard on the right. Every metric on the right is read per tenant, because an overall p95 that looks fine can hide one customer at nine seconds. R-05 · Load testing a tenant-shaped systemFig. 1. Three ways to slice a tenant. From left to right you trade isolation for density; the hatched column on the right is the only thing keeping tenant A out of tenant B's invoices. T-01 · Three ways to slice a tenantFig. 1. The tenant is resolved once at the edge and then travels as a scoped service. A background job has no edge, so it creates its own scope and sets the same service before calling the same handler. T-02 · Tenant resolution in the middlewareFig. 1. The filter lives in the query pipeline, so anything that skips the pipeline skips the filter. The dashed line on the right is the second fence: row-level security in the database, which does not care how the SQL was produced. T-03 · EF Core global query filters as a tenant fence, and where they leakFig. 1. The application asks the catalog once, then connects straight to the shard. Pools are budgets around groups of shards; moving a database between pools changes its budget, moving a tenant between shards changes its address. T-04 · Sharding Azure SQL with elastic pools and a catalogFig. 1. A setting resolves override, then plan, then default, once per version of the tenant's settings. The version is part of the cache key, so a change shows up without anyone having to evict anything. T-05 · Per-tenant configuration and entitlementsFig. 1. Every step left of the dashed line has a compensation that runs in reverse order when a later step fails. The last step has none; once the tenant is Online and the mail is out, the only correct move is forward. T-06 · Onboarding a tenant is a saga