Service per Team means every service boundary has exactly one cross-functional team that owns it end to end: code, database, pipeline, production operation, and on-call.
Intro
Service per Team means every service boundary has exactly one cross-functional team that owns it end to end: code, database, pipeline, production operation, and on-call. Nobody else commits to that repository, and nobody else can block its releases. In an insurance claims system this means, for example, a “Claims Intake” team that owns the intake service, its database, its Angular micro-UI slice, its dashboards and its pager, instead of five teams sharing one ClaimsPlatform repository and taking turns releasing it. The pattern is as much about people and ownership as it is about code. It exists to remove merge conflicts, release queues and the “who owns this outage?” argument.
Why we need this
- Conway’s Law is unavoidable. A system’s structure mirrors the communication structure of the organisation that builds it. If five teams share one codebase, the codebase becomes one coordinated release. If each team owns a service, the architecture and the org chart line up, and independent deployment becomes possible.
- Speed is limited by coordination. DORA research consistently links high delivery performance (deployment frequency, lead time, change failure rate, recovery time) to loosely coupled teams that can ship without asking permission from other teams.
- Accountability needs a name. “You build it, you run it” only works if “you” is one identifiable team. Shared ownership becomes no ownership: nobody fixes the flaky test, upgrades the library, or answers the 2 a.m. page.
- Cognitive load is finite. A team can hold roughly one service domain in its head (Team Topologies calls this the team’s cognitive load limit). Owning too much leads to shallow knowledge of everything.
- Microservices without team ownership gain the cost and lose the benefit. You pay for network calls, distributed tracing and eventual consistency, but if three teams still have to coordinate every release, you have a distributed monolith.
What problem it solves
Problem: Multiple teams committing to shared repositories cause merge conflicts and release friction.
What goes wrong without it (insurance claims example):
- Four teams (Intake, Assessment, Payments, Fraud) all commit to one
claims-platformrepo. A long-liveddevelopbranch is broken weekly by someone else’s half-finished change. - Release day is a meeting. The Payments hot-fix waits behind a Fraud feature that failed QA. A one-line fix takes eleven days to reach production.
- A production incident at 3 a.m.: the pager goes to a shared “platform” rota. The on-call engineer has never seen the settlement code and pings four people.
- Shared libraries and the shared database schema become a commons. Everyone edits
Common.Models, nobody upgrades it, and a rename in one team’s DTO breaks another team’s build. - Priorities collide. The same backlog serves four product owners, so architecture and tech-debt work never wins.
How the pattern resolves it: each service (repo, pipeline, database, runtime, dashboards, alerts, runbook) has one owning team with the authority and accountability to change and operate it. Other teams interact through published contracts (API, events), not through the code.
When it is needed (and when it is NOT)
It fits when:
- More than about 8 to 10 engineers work on the same system and their changes regularly collide.
- You already decomposed by business capability or subdomain (Days 1 and 2) and now need to assign ownership to those boundaries.
- Different parts of the system change at different speeds (Fraud rules weekly, Policy admin quarterly).
- You are moving to DevOps/SRE practices and want a clear on-call boundary.
- You can staff teams cross-functionally: backend, frontend, QA and operations skills inside each team.
It is overkill or wrong when:
- You have one team of 5 to 8 people. A single well-structured modular monolith with one owner is cheaper. Splitting services to make “team boundaries” adds cost with no benefit.
- Services are so tiny that one team would own 15 of them. That is a cognitive-load problem in the other direction. Group related services into one team-owned domain.
- You cannot staff a service with the skills it needs (for example, one DBA shared by nine teams). Fix the staffing or use an enabling/platform team first.
- The domain boundary itself is unclear. Ownership assigned to a wrong boundary just makes the wrong boundary permanent. Do Days 1 and 2 first.
- Anti-pattern: one team per technical layer (UI team, API team, DB team). That guarantees every feature crosses three teams, which is the opposite of the goal.
How to identify the problem (key signals)
- Merge conflicts and broken main. More than a couple of conflicting PRs per week in one repo,
mainred for hours, “don’t merge now, X is testing” messages in chat. - Release trains and freeze windows. Deployment frequency is measured in weeks; a small fix waits for a scheduled release of other teams’ work. Lead time for changes is dominated by waiting, not coding.
- Ownership ambiguity in incidents. Incident channels start with “whose service is this?”; mean time to acknowledge is long; the pager rota contains people who did not write the code.
- CODEOWNERS is
*or absent. Or every folder lists three teams. PR review bounces between teams for days. - Change failure rate correlates with cross-team merges. Post-mortems repeatedly cite “another team’s change” as the trigger.
- Shared library or shared schema edited by everyone.
Common,Shared,Utilsprojects with dozens of committers and no maintainer. - Team complaints. “I can’t get my change out,” “I don’t know who to ask,” “We spend more time in coordination meetings than coding.” Also, dependency tickets on other teams’ boards make up a large share of your backlog.
Flow Diagram
One cross-functional team owns a service end to end; other teams interact only through contracts.
flowchart TB subgraph Team["team-claims-intake"] REPO["Repo + CODEOWNERS + service.yaml"] --> PIPE["Own CI/CD pipeline"] PIPE --> APP["claims-intake service"] APP --> DB[("ClaimsIntakeDb")] APP --> MON["Dashboards + alerts"] MON --> ONCALL["Own on-call rota"] end OTHER["Other teams"] -- "OpenAPI / events only" --> APP OTHER -. "Pull request proposal" .-> REPO PLAT["Platform team"] -- "Templates + golden paths" --> PIPELevel 1: Beginner
Analogy: A restaurant with one kitchen where every chef cooks every dish, versus food stalls in a food court where each stall has its own team, stove, recipe and opening hours. If the noodle stall’s stove breaks, only the noodle stall is affected, and everyone knows who to call.
Core rules of the pattern:
- One service → one owning team (a team may own several related services, but a service never has two owners).
- The owning team does everything: build, test, deploy, monitor, fix.
- Others may ask for a change (open an issue, propose a PR), but the owner decides.
- The ownership is written down and machine-readable, not tribal knowledge.
Minimal working example. Ownership as a file in the repository, enforced by GitHub CODEOWNERS (Azure DevOps offers the equivalent through branch policies with required reviewers per path):
# .github/CODEOWNERS (repo: claims-intake-service)# Every path is owned by the Intake team. PRs need their approval.* @contoso/team-claims-intake/docs/ @contoso/team-claims-intake/.github/workflows/ @contoso/team-claims-intake @contoso/platform-enablementAnd a small ownership manifest that dashboards, alerts and Slack bots can read:
# service.yaml (in the root of claims-intake-service)name: claims-intake-serviceowner-team: claims-intakeon-call: claims-intake-oncall # rota name in your paging toolslack: "#team-claims-intake"tier: 1 # business criticalityrunbook: docs/runbook.mddepends-on: - policy-service # consumed via REST API - claim-events (topic) # published via Service BusBeginner check: open any repository. Within 30 seconds you should be able to say which team owns it, how to contact them, and who gets paged.
Level 2: Intermediate
In a real .NET + Angular + SQL Server/PostgreSQL system, “service per team” shows up in five concrete places: repo layout, runtime identity, database, pipeline and UI.
Target versions: .NET 10 (LTS, supported until November 2028; .NET 8 LTS ends 10 November 2026) and Angular 22 (released June 2026; Angular 21 is in security-only support).
Repo and runtime, per team. Each team has its own repo, its own CI/CD pipeline, its own database and its own identity. Nothing in the pipeline references another team’s repo.
claims-intake-service/ (owned by team-claims-intake)├── src/│ ├── Claims.Intake.Api/ # ASP.NET Core minimal API│ ├── Claims.Intake.Domain/│ └── Claims.Intake.Infrastructure/ # EF Core, own SQL Server DB "ClaimsIntakeDb"├── web/intake-ui/ # Angular 22 app or web-component, owned by same team├── tests/├── azure-pipelines.yml # own pipeline, own deploy cadence├── service.yaml└── .github/CODEOWNERSMake ownership visible at runtime. Tag telemetry with the owning team so alerts route automatically to the right rota:
// Program.cs (.NET 10, ASP.NET Core minimal API)using OpenTelemetry.Resources;using OpenTelemetry.Trace;using OpenTelemetry.Metrics;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry() .ConfigureResource(r => r .AddService("claims-intake-service") .AddAttributes(new Dictionary<string, object> { ["team.owner"] = "claims-intake", ["team.oncall"] = "claims-intake-oncall", ["service.tier"] = 1 })) .WithTracing(t => t.AddAspNetCoreInstrumentation().AddHttpClientInstrumentation()) .WithMetrics(m => m.AddAspNetCoreInstrumentation());
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapHealthChecks("/health/live");
app.MapPost("/claims", (SubmitClaim cmd) =>{ // Intake owns the Claim aggregate creation. Other teams never write to ClaimsIntakeDb. var id = Guid.NewGuid(); return Results.Created($"/claims/{id}", new { id, status = "Received" });});
app.Run();
public record SubmitClaim(string PolicyNumber, decimal EstimatedAmount, string Description);Packages: OpenTelemetry.Extensions.Hosting, OpenTelemetry.Instrumentation.AspNetCore, OpenTelemetry.Instrumentation.Http. Add an exporter (for Azure, Azure.Monitor.OpenTelemetry.Exporter or the Azure Monitor distro) to send the data.
Database ownership. Team Intake is the only writer to ClaimsIntakeDb. Other teams read via API or events (see Day 5, Database per Service). Give each team its own connection identity and deny cross-team logins:
-- Only the Intake service identity can touch the Intake databaseCREATE USER [claims-intake-svc] FROM EXTERNAL PROVIDER; -- Entra ID managed identityALTER ROLE db_datareader ADD MEMBER [claims-intake-svc];ALTER ROLE db_datawriter ADD MEMBER [claims-intake-svc];-- No grants for payments-svc or fraud-svc on this database.UI ownership (Angular). The team owns its slice of the UI too, otherwise a frontend team becomes the bottleneck. A pragmatic approach is a standalone, lazily loaded route module the shell app loads (a full micro-frontend split is covered in Day 53):
// app.routes.ts in the shell (Angular 22, standalone routes)import { Routes } from '@angular/router';
export const routes: Routes = [ { path: 'intake', // The Intake team owns and versions this bundle loadChildren: () => import('@contoso/claims-intake-ui').then(m => m.INTAKE_ROUTES), },];Pipeline. Each team’s pipeline deploys only its own service, on its own schedule:
# azure-pipelines.yml (trimmed)trigger: branches: { include: [ main ] } paths: { include: [ src/*, web/* ] }pool: { vmImage: ubuntu-latest }steps: - script: dotnet test --configuration Release - script: dotnet publish src/Claims.Intake.Api -c Release -o $(Build.ArtifactStagingDirectory)/api # deploy step targets only the claims-intake Container App / App ServiceTeam API between teams. The contract is the boundary. Intake publishes an OpenAPI document and events; consumers use those and nothing else (Day 51 covers contract tests).
Level 3: Advanced
Team sizing and shape. Use the “two-pizza” heuristic (about 5 to 9 people). A team needs backend, frontend, QA, and someone with operations/DevOps skill. A team that must wait for a separate QA or ops department to release is not really independent.
Flow-of-change metrics. Measure whether the pattern is working with the four DORA metrics per team, not per department: deployment frequency, lead time for changes, change failure rate, and time to restore service. Expect deployment frequency to rise and lead time to fall once the shared-repo queue is gone.
Scalability. Team independence is a scaling mechanism for people, not for CPU. Adding the 10th team should not slow the first nine. Cross-team dependencies grow quadratically, so keep team-to-team interactions as few and as explicit as possible (Team Topologies interaction modes: collaboration, X-as-a-Service, facilitating).
Security and access. Ownership maps to least privilege:
- Entra ID groups per team; RBAC on each team’s resource group or subscription.
- Managed identities per service; no shared connection strings or shared service principals.
- Branch protection and required reviewers from the owning team.
- Secrets in the team’s own Key Vault (or a shared vault with per-team access policies/RBAC).
Failure modes and common mistakes:
| Mistake | Consequence | Fix |
|---|---|---|
| Splitting teams by layer (UI/API/DB) | Every feature needs three teams, handoffs return | Split by business capability; make teams cross-functional |
| Team owns too many services | Cognitive overload, shallow ownership | Cap services per team, merge tiny services or share ownership at the domain level |
Shared Common library edited by all | Hidden coupling, forced lock-step upgrades | Keep shared code tiny, versioned as NuGet with a named maintainer team, or duplicate small DTOs on purpose |
| Shared database “just for reporting” | Back door coupling, schema lockouts | Publish events or a read model; give reporting its own store |
| Ownership only on a wiki | Goes stale within weeks | Keep service.yaml and CODEOWNERS in the repo; validate in CI |
| No platform support | Every team builds its own pipeline and monitoring badly | Create a platform/enabling team offering golden paths (Days 40 and 41) |
| Orphaned services after reorgs | Nobody on call | Quarterly ownership audit; CI fails when owner-team no longer exists |
| Ownership without authority | Team is blamed but cannot decide (needs approval from architecture board for everything) | Give the team decision rights inside the service boundary; architects set guardrails, not gates |
Enforcing ownership in CI (example): a small check that every repo has a valid service.yaml:
// OwnershipCheck/Program.cs (console app run in CI, .NET 10)using System.Text.RegularExpressions;
var path = args.Length > 0 ? args[0] : "service.yaml";if (!File.Exists(path)){ Console.Error.WriteLine("service.yaml is missing. Every service must declare an owner."); return 1;}
var text = File.ReadAllText(path);var owner = Regex.Match(text, @"^owner-team:\s*(\S+)", RegexOptions.Multiline);var oncall = Regex.Match(text, @"^on-call:\s*(\S+)", RegexOptions.Multiline);
if (!owner.Success || !oncall.Success){ Console.Error.WriteLine("service.yaml must define owner-team and on-call."); return 1;}
Console.WriteLine($"OK: owned by {owner.Groups[1].Value}, on-call {oncall.Groups[1].Value}");return 0;Level 4: Expert and Architect view
Design trade-offs: how to assign ownership
| Model | Strengths | Weaknesses | Choose when |
|---|---|---|---|
| Service per Team (one team, one or few services) | Clear accountability, fast independent delivery, low merge friction | Needs cross-functional staffing; risk of duplicated effort; bus factor within small teams | Default for domains with stable boundaries and 5+ engineers |
| Team per layer (UI/API/DB teams) | Deep specialist knowledge | Every feature crosses teams, long lead times | Rarely; only for a truly specialised platform component |
| Shared ownership / collective code ownership | Flexible staffing, no silos | Diffusion of responsibility, conflicts, unclear on-call | Very small orgs (one team) |
| Feature teams over a shared monolith | Product-aligned, simple deployment | Merge conflicts, shared release cadence | Early product stage, modular monolith |
| Stream-aligned + platform + enabling teams (Team Topologies) | Reduces cognitive load, scales to many teams | Needs organisational maturity and a real platform investment | 5+ teams with shared infrastructure needs |
| Inner-source (owner + external contributors via PR) | Cross-team improvements without ownership dilution | Requires good docs, review capacity | Shared libraries and platform tools |
Combines with:
- Decompose by Business Capability / Subdomain (Days 1 and 2): these choose the boundaries; Service per Team assigns owners to them.
- Database per Service (Day 5): team ownership of code without ownership of data is fake independence.
- Microservice Chassis and Service Template (Days 40 and 41): reduce the per-team cost of standing up pipelines, logging and auth.
- Consumer-Driven Contract Tests (Day 51): make cross-team API changes safe without meetings.
- Health Check API, Observability (Days 31 to 37): give each team the tooling to run what they own.
- Backends for Frontends and Micro-Frontends (Days 20 and 53): extend team ownership up to the UI.
ADR (architecture review ready)
ADR-004: Adopt Service per Team for the Claims Platform
Status: Proposed
Context: Four teams (Intake, Assessment, Payments, Fraud), 34 engineers total, commit to a single
claims-platformrepository. Median lead time for change is 11 days; 40% of production incidents cite a cross-team change; the on-call rota is shared and MTTA exceeds 25 minutes because responders do not know the code. (Figures are illustrative for the scenario.)Decision: Each bounded context identified in ADR-001/002 will be owned by exactly one cross-functional team. Each team gets its own repository, pipeline, database, Entra ID group, managed identity, dashboards and on-call rota. Ownership is recorded in
service.yamlandCODEOWNERS, validated in CI. A small platform team provides templates and shared tooling. Cross-team interaction happens only via versioned APIs and events.Consequences (positive): shorter lead time, clearer incident response, independent release cadence, better cognitive load fit.
Consequences (negative): some duplicated code and tooling, need for a platform team, higher demand for DevOps skills in every team, requirement to invest in API contracts and observability up front.
Alternatives considered: a shared repo with feature teams (rejected: does not remove release coupling); layered teams (rejected: increases hand-offs); a single team owning everything (rejected: does not scale beyond ~10 engineers).
Review trigger: revisit if a team owns more than 4 services or DORA lead time does not improve after two quarters.
Azure implementation
Service per Team is an organisational pattern, so Azure supports it through isolation of ownership, not a single feature.
Azure services that support it:
| Need | Azure service | How it helps |
|---|---|---|
| Source, boards, pipelines per team | Azure DevOps (or GitHub Enterprise) | One project or repo per team/service; branch policies with required reviewers; per-team boards and pipelines |
| Identity and access | Microsoft Entra ID groups + Azure RBAC | Team group gets Contributor on its own resource group only |
| Resource isolation | Resource groups / subscriptions, Management Groups | One resource group per service (or per team per environment); subscription per environment for larger orgs |
| Runtime | Azure Container Apps, AKS, App Service | One app per service; each team deploys only its own |
| Data | Azure SQL Database, Azure Database for PostgreSQL flexible server | One database per service, access via managed identity |
| Secrets | Azure Key Vault | Vault per team or per service, RBAC-based |
| Monitoring and routing | Azure Monitor, Application Insights, Log Analytics, Action Groups | Alert rules route to the owning team’s Action Group / paging tool |
| Governance | Azure Policy | Enforce mandatory tags (owner-team, on-call, cost-center); deny untagged resources |
| Cost accountability | Microsoft Cost Management | Per-team cost views by tag or resource group |
| API surface | Azure API Management | Per-team API products and subscriptions; publishes each team’s contract |
How to configure (key steps):
- Create an Entra ID security group per team (
team-claims-intake). Add members; nominate owners. - Create a resource group per service and environment (for example
rg-claims-intake-prod). Assign the team group the Contributor role there and Reader on shared networking. - Apply an Azure Policy initiative requiring the tags
owner-team,on-call,service-tier. Use the built-in Require a tag on resources policy, or a custom definition withdenyeffect. - Give the service a system-assigned or user-assigned managed identity, and grant it access to only its own database (
CREATE USER ... FROM EXTERNAL PROVIDER, as shown in Level 2) and Key Vault (Key Vault Secrets User role). - In Azure Monitor, create an Action Group per team (email, SMS, webhook to the paging tool) and attach alert rules from that team’s Application Insights resource.
- In Azure DevOps or GitHub, add branch protection plus CODEOWNERS/required reviewers. Use environments with approvals owned by the same team.
- Turn on Cost Management budgets scoped to each team’s resource groups, with alerts to the team.
Pricing and tier considerations (verify on the Azure pricing pages and calculator before committing; prices change and vary by region):
- Azure DevOps Services: the first 5 Basic users are free, additional Basic users are a per-user monthly fee (around USD 6 per user per month at the time of writing). Extra Microsoft-hosted parallel jobs and Artifacts storage are billed separately. GitHub licensing is per seat under Team or Enterprise plans.
- Entra ID: the Free tier covers groups and basic RBAC. Conditional Access, PIM (just-in-time elevation for on-call engineers) and access reviews (useful for quarterly ownership audits) need Entra ID P1/P2 licences.
- Azure Policy, Resource Groups, RBAC, Cost Management: no additional charge for the core features.
- Runtime: Container Apps (Consumption plan with a monthly free grant, and Dedicated workload profiles), App Service (Basic, Standard, Premium v3/v4 tiers), AKS (free control-plane tier, paid Standard tier with SLA, plus node VM costs). Choose per team based on load; teams may pick different tiers for their services.
- Azure SQL Database: serverless or provisioned vCore, or DTU-based tiers. A database per team increases the number of billed databases, so use elastic pools for many small, spiky databases to control cost.
- Application Insights / Log Analytics: billed per GB ingested, so per-team sampling and daily caps prevent one noisy team creating a surprise bill.
- Azure API Management: tiers include Consumption, Developer, Basic, Standard and Premium, plus the v2 tiers (Basic v2, Standard v2, Premium v2). Choose the tier by SLA, VNet and scale needs, not per team; use products and workspaces to separate teams’ APIs.
Reference architecture (text):
An enterprise has one Azure tenant with management groups for Production and Non-Production. A shared platform subscription hosts the hub network, Azure Container Registry, the Log Analytics workspace and Azure API Management. Each stream-aligned team (Claims Intake, Assessment, Payments, Fraud) has its own resource group per environment holding: a Container App (or App Service), an Azure SQL database (from an elastic pool), a Key Vault, an Application Insights resource, and an Azure Service Bus namespace or topics it owns. The team’s Entra ID group has Contributor rights only in those resource groups. Azure Policy enforces tags and denies public network access to databases. Azure DevOps has one project (or one repository set) per team with its own pipeline deploying only to that team’s resource group; environments have approval checks owned by the team. Azure Monitor alert rules notify each team’s Action Group, which pages the team’s own rota. APIM exposes each team’s API as a separate product with its own subscription keys and versioning. A platform team owns the hub network, the shared templates (Bicep modules) and the golden-path pipelines that teams consume as a service.
Teaching guide for my team
Explain to a beginner in 2 minutes: “Imagine a food court. Each stall has its own cooks, its own stove and its own opening hours. If the noodle stall is closed, the pizza stall still sells pizza. In our software, each service is a stall and each team owns exactly one stall from cooking to customer complaints. That way nobody waits for someone else to release, and when something breaks at night, there is one team to call. We write the owner down in a file in the repo so anyone can find it in seconds.”
Explain to an intermediate developer in 5 minutes: Start with the pain: shared repo, merge conflicts, release queue, unclear on-call. Then state the rule: one service, one team, end-to-end (code, database, pipeline, alerts, on-call). Show service.yaml, CODEOWNERS, and one team’s pipeline deploying only its own resource group. Explain that data and UI are part of ownership, so a shared database or a shared UI project brings back the coupling. Explain that the boundary between teams is a contract (OpenAPI, events), and contract tests protect it. Close with the caveats: it needs cross-functional staffing, a platform team to reduce duplicated effort, and correct domain boundaries (Days 1 and 2).
Hands-on exercise: “Ownership audit and fix” (60 to 90 minutes)
- Take a real or sample repo (or the
claims-platformsample). List every top-level folder and who committed to it in the last 90 days (git shortlog -sn -- <folder>). - Group folders into candidate services and map each to one team.
- Write a
CODEOWNERSfile and aservice.yamlfor each candidate service. - Add the ownership check program (Level 3) to a CI pipeline; make it fail if
owner-teamoron-callis missing. - Create an Azure Action Group and an alert rule for one service and confirm it targets that team.
Expected outcome: every folder has exactly one owning team; the CI check fails on a service without service.yaml; an alert on the sample service reaches only the owning team; the group can name at least two places where two teams still share something (database, library, pipeline) and propose how to remove that coupling.
Interview-style questions:
- What does Conway’s Law have to do with Service per Team? The architecture ends up mirroring team communication paths. If teams are aligned with service boundaries (and vice versa), independent deployment becomes natural. Otherwise the code structure drifts toward the org chart anyway, usually in an unhelpful way.
- Can one team own several services? Can one service have two owning teams? One team can own several related services as long as cognitive load stays manageable. A service should never have two owning teams; shared ownership leads to diffusion of responsibility. Contributions from other teams come through PRs to the owner (inner source).
- Why is “one team per layer” (UI, API, DB) a bad application of this pattern? Because every feature then needs all three teams, adding hand-offs and queues, which is exactly the coordination cost the pattern tries to remove. Teams should be cross-functional and aligned to a business capability.
Mastery checklist
- I can explain Conway’s Law and the inverse Conway manoeuvre with an example from our own system.
- I can list at least five signals (from Section 4) that indicate shared ownership is hurting us, and where to measure them (Git history, DORA metrics, incident data).
- I can write a
CODEOWNERSfile, aservice.yaml, and a CI check that enforces them. - I can show how ownership extends to data (own database and identity) and UI (own Angular slice).
- I can design the Azure setup: Entra group, resource group, RBAC, Policy tags, Action Group and cost budget per team.
- I can explain when NOT to use the pattern (small team, unclear boundaries, layered teams, too many tiny services).
- I can write an ADR for adopting the pattern with measurable review triggers.
- I can describe the role of platform and enabling teams and why they are needed alongside stream-aligned teams.
Key takeaway
Give every service exactly one cross-functional team that builds, runs and answers for it, and write that ownership down where tools can enforce it. Independent delivery follows from clear ownership, not from the number of services.
