Your technical library
Microservices & Service Architecture
9 modules · 61 topics
About this book
Microservices & Service Architecture
This is the library's microservices course. It teaches what the word covers — where a service boundary goes, how services call each other, what a contract between them promises, who owns each piece of data, how an operation that spans several services survives a crash, and how a monolith is split while it keeps running — and it teaches it honestly, so the answer is often "a module, not a service". Each slogan the subject is usually learned as — "just use gRPC", "put GraphQL in front", "use a saga" — is replaced with the mechanism, its failure mode and the quantity that decides whether to use it. It assumes no experience of building services: every term is explained where it first appears or linked to the lesson that teaches it, and the depth aims past what a senior staff engineer is expected to know. It builds on System Design, PostgreSQL, DBMS and Computer Networking.
Modularity and coupling gives criteria for where any boundary belongs. Service communication follows the boundary onto the network: what a remote call hides and what it cannot, gRPC on HTTP/2, REST, GraphQL, finding service instances, the service mesh, and services that talk through events instead of calls. Service boundaries: monolith or microservices then decides which boundaries deserve a network at all: what a microservice is, bounded contexts, data ownership and read models, and the modular monolith. Contract evolution and API semantics for distributed clients keep a contract stable while everything behind it changes — compatibility, versioning, errors, pagination, idempotency and webhooks.
Long-running workflows runs multi-step business processes across services with outboxes, sagas and durable execution, and migrating live systems changes schemas, data stores and whole systems without stopping them. Multi-tenancy serves many customers from one fleet, and architectural decisions closes with how decisions are recorded, sequenced and paid for — ending with one service extracted from a monolith, end to end.
61 topics across 9 modules, in dependency order.
61 of 61 topics are authored. Read in dependency order. Each topic opens with the problem it inherits and closes with what it leaves open. Planned lessons are labelled and excluded from progress; references cover authored material.
Read at your own pace. Each topic is one continuous page; mark it done as you finish.
Start with 01.1 · Information hiding: decompose by what is likely to changeReview what you have finished↷ 0 skipped topics- 02.1Remote procedure calls: making a network call look like a function call, and where it leaks
- 02.2gRPC: a remote call as an HTTP/2 stream
- 02.3REST: resources, a uniform interface, and what intermediaries can do with it
- 02.4GraphQL: a client-chosen query over a graph of resolvers, and the N+1 cost
- 02.5One backend per client, or one graph over many services: BFFs and federation
- 02.6Finding instances and spreading calls: discovery, connection reuse and subsetting
- 02.7The service mesh: moving network code out of every service
- 02.8Events between services: commands, notifications and carried state
- 02.9Choosing how services talk: REST, gRPC, GraphQL or messages, per boundary
- 03.1What a microservice is: independent deployability, and where the idea came from
- 03.2Bounded contexts: one model per boundary, not one model per company
- 03.3Context maps: who translates, and who pays for change
- 03.4Conway's law: systems mirror the communication structure that built them
- 03.5Data ownership: why a shared database is a shared interface
- 03.6CQRS and read models: one model for changing data, others for asking
- 03.7The distributed monolith: services that must change together
- 03.8The modular monolith: boundaries without a network
- 03.9Monolith or microservices: deciding per boundary
- 04.1Compatibility classes: wire, source and behaviour
- 04.2Hyrum's law: every observable behaviour becomes a dependency
- 04.3Detecting breaking changes: schema diffs and consumer-driven contracts
- 04.4Event contracts: compatibility over a retained history
- 04.5Versioning strategies: what each one makes the provider pay
- 05.1Error contracts: telling the caller what to do next
- 05.2Pagination under concurrent writes: offset, keyset and snapshot
- 05.3Optimistic concurrency over an API: etags, preconditions and field masks
- 05.4Idempotency keys for multi-step requests: atomic phases and recovery points
- 05.5Long-running operations: the operation as a resource
- 05.6Webhook contracts: at-least-once, unordered, and thin
- 06.1Outbox and inbox: making a state change and its message one fact
- 06.2Saga structure: compensatable, pivot and retriable steps
- 06.3Saga isolation: anomalies without a lock manager and their countermeasures
- 06.4Orchestration and choreography: where a process's state lives
- 06.5Durable execution: a workflow as code that survives crashes by replay
- 06.6Determinism under replay: what workflow code may not do
- 06.7Changing workflow code under running executions
- 06.8Choosing a workflow mechanism
- 07.1Expand and contract: changing a schema across a rolling deploy
- 07.2Online schema change: shadow tables, change capture and cut-over
- 07.3Moving data to a new store: dual writes against change data capture
- 07.4Backfills against a live change stream
- 07.5Verifying a migration: checksums, sampling and reconciliation
- 07.6Shadow traffic: comparing a new implementation on real requests
- 07.7Strangler fig: replacing a system one route at a time
- 07.8Cut-over and rollback: where the point of no return is
- 08.1Silo, pool and bridge: the cost and isolation of tenancy
- 08.2Tenant data partitioning: database, schema or row per tenant
- 08.3Per-tenant customisation: extensible schemas in a shared store
- 08.4Fairness between tenants: quotas, fair queuing and dominant resources
- 08.5Tenant placement: packing skewed tenants and moving the big ones
- 08.6Tenant tiering: one product, several tenancy models
- 09.1Decision records and fitness functions: keeping the why and checking the what
- 09.2Reversibility: one-way doors, two-way doors and the value of waiting
- 09.3Cost of delay: sequencing architectural work by value per unit time
- 09.4Technical debt as principal and interest
- 09.5Extracting a service from a monolith, end to end
01.2Deep modules: interface cost against hidden functionality
01.3Connascence: a graded measure of coupling
01.4Dependency direction: which side of a boundary may know the other
01.5Modularity as option value: when a boundary pays for itself
02.1Remote procedure calls: making a network call look like a function call, and where it leaks
02.2gRPC: a remote call as an HTTP/2 stream
02.3REST: resources, a uniform interface, and what intermediaries can do with it
02.4GraphQL: a client-chosen query over a graph of resolvers, and the N+1 cost
02.5One backend per client, or one graph over many services: BFFs and federation
02.6Finding instances and spreading calls: discovery, connection reuse and subsetting
02.7The service mesh: moving network code out of every service
02.8Events between services: commands, notifications and carried state
02.9Choosing how services talk: REST, gRPC, GraphQL or messages, per boundary
03.1What a microservice is: independent deployability, and where the idea came from
03.2Bounded contexts: one model per boundary, not one model per company
03.3Context maps: who translates, and who pays for change
03.4Conway's law: systems mirror the communication structure that built them
03.5Data ownership: why a shared database is a shared interface
03.6CQRS and read models: one model for changing data, others for asking
03.7The distributed monolith: services that must change together
03.8The modular monolith: boundaries without a network
03.9Monolith or microservices: deciding per boundary
04.1Compatibility classes: wire, source and behaviour
04.2Hyrum's law: every observable behaviour becomes a dependency
04.3Detecting breaking changes: schema diffs and consumer-driven contracts
04.4Event contracts: compatibility over a retained history
04.5Versioning strategies: what each one makes the provider pay
05.1Error contracts: telling the caller what to do next
05.2Pagination under concurrent writes: offset, keyset and snapshot
05.3Optimistic concurrency over an API: etags, preconditions and field masks
05.4Idempotency keys for multi-step requests: atomic phases and recovery points
05.5Long-running operations: the operation as a resource
05.6Webhook contracts: at-least-once, unordered, and thin
06.1Outbox and inbox: making a state change and its message one fact
06.2Saga structure: compensatable, pivot and retriable steps
06.3Saga isolation: anomalies without a lock manager and their countermeasures
06.4Orchestration and choreography: where a process's state lives
06.5Durable execution: a workflow as code that survives crashes by replay
06.6Determinism under replay: what workflow code may not do
06.7Changing workflow code under running executions
06.8Choosing a workflow mechanism
07.1Expand and contract: changing a schema across a rolling deploy
07.2Online schema change: shadow tables, change capture and cut-over
07.3Moving data to a new store: dual writes against change data capture
07.4Backfills against a live change stream
07.5Verifying a migration: checksums, sampling and reconciliation
07.6Shadow traffic: comparing a new implementation on real requests
07.7Strangler fig: replacing a system one route at a time
07.8Cut-over and rollback: where the point of no return is
08.1Silo, pool and bridge: the cost and isolation of tenancy
08.2Tenant data partitioning: database, schema or row per tenant
08.3Per-tenant customisation: extensible schemas in a shared store
08.4Fairness between tenants: quotas, fair queuing and dominant resources
08.5Tenant placement: packing skewed tenants and moving the big ones
08.6Tenant tiering: one product, several tenancy models
09.1Decision records and fitness functions: keeping the why and checking the what
09.2Reversibility: one-way doors, two-way doors and the value of waiting
09.3Cost of delay: sequencing architectural work by value per unit time
09.4Technical debt as principal and interest
09.5Extracting a service from a monolith, end to end
Skipped topics
Moved past without finishing. A skipped topic still closes its module, and Continue goes on without it — open one to read it or mark it understood.
Glossary
Terms as the lessons define them, each linked to the lesson that defines it.
KWIC index
Parnas's program makes a KWIC index, a Key Word In Context index.
Go to Information hiding: decompose by what is likely to change →circular shift
Take each line and circularly shift it: remove the first word and append it at the end, over and over until the line has been through every starting word.
Go to Information hiding: decompose by what is likely to change →Information hiding
Information hiding is the decomposition criterion of making each module the one place that knows a design decision, and giving the module an interface that reveals as little about that decision as possible.
Go to Information hiding: decompose by what is likely to change →secret
The decision a module hides is called its secret, the word Parnas's later papers use.
Go to Information hiding: decompose by what is likely to change →interface
This is the module's interface: everything a caller needs to know in order to use the module correctly, whether or not it is written down.
Go to Deep modules: interface cost against hidden functionality →deep module
A deep module provides a lot of functionality behind a small interface: what a caller must know is small next to what the module does for it.
Go to Deep modules: interface cost against hidden functionality →shallow module
A shallow module has an interface that is complicated compared with the functionality behind it, so it saves its callers little for what it costs them to learn.
Go to Deep modules: interface cost against hidden functionality →pass-through method
A pass-through method is a method that does almost nothing except call another method with the same or a very similar signature.
Go to Deep modules: interface cost against hidden functionality →Temporal decomposition
Temporal decomposition means splitting a program into modules by the order things happen in: a module that reads the file, then one that parses it, then one that writes the result.
Go to Deep modules: interface cost against hidden functionality →Information leakage
Information leakage is when a design decision shows up in more than one module, so that changing it means changing them all.
Go to Deep modules: interface cost against hidden functionality →Connascence
Connascence between two software elements (functions, classes, modules, services) exists when a change to one requires a change to the other for the whole to stay correct.
Go to Connascence: a graded measure of coupling →Strength
Strength: how hard the coupling is to find and to refactor away.
Go to Connascence: a graded measure of coupling →Degree
Degree: how many elements are connascent with each other.
Go to Connascence: a graded measure of coupling →Locality
Locality: how far apart the elements are.
Go to Connascence: a graded measure of coupling →Connascence of name
Connascence of name: both sides must agree on the name of something.
Go to Connascence: a graded measure of coupling →Connascence of type
Connascence of type: both sides must agree on the type of a value.
Go to Connascence: a graded measure of coupling →Connascence of meaning
Connascence of meaning (also called of convention): both sides must agree on what a particular value stands for.
Go to Connascence: a graded measure of coupling →Connascence of position
Connascence of position: both sides must agree on the order of values.
Go to Connascence: a graded measure of coupling →Connascence of algorithm
Connascence of algorithm: both sides must run the same procedure.
Go to Connascence: a graded measure of coupling →Connascence of execution
Connascence of execution: the order in which things run matters.
Go to Connascence: a graded measure of coupling →Connascence of timing
Connascence of timing: when things happen matters.
Go to Connascence: a graded measure of coupling →Connascence of value
Connascence of value: several values must change together.
Go to Connascence: a graded measure of coupling →Connascence of identity
Connascence of identity: both sides must refer to the same entity.
Go to Connascence: a graded measure of coupling →Dependency inversion
Dependency inversion, which Martin's 1994 paper shows on the keyboard-to-printer program, is the practice of having a higher-level piece of code define an interface for what it needs, and having the lower-level code depend on that interface by implementing it, so that the source dependency points opposite to the control flow.
Go to Dependency direction: which side of a boundary may know the other →port
A port is an interface, owned by the application, that names one purposeful conversation with the outside world, such as "charge this order" or "load and save orders".
Go to Dependency direction: which side of a boundary may know the other →adapter
An adapter is code, outside the application, that translates between a port and one particular technology.
Go to Dependency direction: which side of a boundary may know the other →afferent coupling
For a component, afferent coupling is the number of other components that depend on it: the arrows coming in.
Go to Dependency direction: which side of a boundary may know the other →Efferent coupling
Efferent coupling is the number of other components it depends on: the arrows going out.
Go to Dependency direction: which side of a boundary may know the other →Instability
Instability is , a number between 0 and 1.
Go to Dependency direction: which side of a boundary may know the other →option
In finance, an option is the right, but not the obligation, to do something later.
Go to Modularity as option value: when a boundary pays for itself →option value
The option value of a module is the expected gain from trying alternative implementations of it and keeping the best, given that every other module is untouched.
Go to Modularity as option value: when a boundary pays for itself →technical potential
It is positive only because the module has an interface that lets one implementation replace another; it grows with the technical potential , the spread (standard deviation) of how good the alternative implementations might be.
Go to Modularity as option value: when a boundary pays for itself →visibility cost
Here is the module's complexity, its number of design parameters; is its technical potential per parameter; is its number of experiments; is the cost of one experiment, taken as below; and is its visibility cost: the cost of redoing every other module , of size and redesign cost per parameter, that depends on (sees) module .
Go to Modularity as option value: when a boundary pays for itself →design structure matrix
They described each design with a design structure matrix (DSM): a square table with one row and one column per design decision, where a mark in row , column says that depends on .
Go to Modularity as option value: when a boundary pays for itself →marshalling
The step that turns an in-memory value into bytes is called marshalling, and the reverse, rebuilding the value from bytes, is unmarshalling.
Go to Remote procedure calls: making a network call look like a function call, and where it leaks →interface definition
Both are generated from an interface definition: a short file, in a small language of its own, that lists each remote procedure with the types of its arguments and results.
Go to Remote procedure calls: making a network call look like a function call, and where it leaks →binding
Finding it is called binding.
Go to Remote procedure calls: making a network call look like a function call, and where it leaks →remote procedure call
A remote procedure call (RPC) transfers control and data from a caller to a procedure in another address space, usually on another machine, through generated stubs: the caller invokes a local stub with the procedure's own signature, the stub marshals the arguments and sends them, and a server stub unmarshals them, invokes the procedure and returns its result the same way.
Go to Remote procedure calls: making a network call look like a function call, and where it leaks →chatty
An interface that needs many calls to do one job is called chatty; one that does the job in a single call is coarse-grained.
Go to Remote procedure calls: making a network call look like a function call, and where it leaks →Coarse-grained interface
An interface that needs many calls to do one job is called chatty; one that does the job in a single call is coarse-grained.
Go to Remote procedure calls: making a network call look like a function call, and where it leaks →call semantics
The promise is the call's call semantics.
Go to Remote procedure calls: making a network call look like a function call, and where it leaks →call identifier
Each call carries a call identifier, and the server remembers which identifiers it has already executed, so a retry is answered without running the procedure again.
Go to Remote procedure calls: making a network call look like a function call, and where it leaks →trailers
These are called trailers, and few systems used them until gRPC. gRPC puts the call's outcome there:
Go to gRPC: a remote call as an HTTP/2 stream →resource
A resource is anything that can be named: one order, the list of a customer's orders, today's prices.
Go to REST: resources, a uniform interface, and what intermediaries can do with it →representation
Resources are manipulated through representations.
Go to REST: resources, a uniform interface, and what intermediaries can do with it →uniform interface
The uniform interface is REST's requirement that every component interact through one generic interface: resources identified by URLs, manipulated by transferring representations, through self-descriptive messages whose methods have meanings every party knows, with hypermedia links telling the client what it can do next.
Go to REST: resources, a uniform interface, and what intermediaries can do with it →GraphQL schema
A GraphQL server publishes a schema: the types it knows, each with its fields and their types.
Go to GraphQL: a client-chosen query over a graph of resolvers, and the N+1 cost →resolver
Call the field's resolver: a function the server's authors wrote for that field of that type, given the parent object and the field's arguments.
Go to GraphQL: a client-chosen query over a graph of resolvers, and the N+1 cost →GraphQL
GraphQL is a query language and execution model in which a client sends a query selecting fields from a typed schema, and the server executes it by walking the selection: for each field it calls that field's resolver with the parent value and arguments, completes the result against the field's type, and recurses into the field's own selection, so that the response has exactly the shape of the query.
Go to GraphQL: a client-chosen query over a graph of resolvers, and the N+1 cost →N+1 problem
In general, a list of items whose child field each needs another call makes calls on top of the one that fetched the list, the N+1 problem.
Go to GraphQL: a client-chosen query over a graph of resolvers, and the N+1 cost →backend for frontend
Calçado called it the backend for frontend (BFF).
Go to One backend per client, or one graph over many services: BFFs and federation →GraphQL federation
In early 2019 Apollo, a company that builds GraphQL tooling, published a specification for exactly that, GraphQL federation, and Netflix built its next architecture on it.
Go to One backend per client, or one graph over many services: BFFs and federation →subgraph
Each team runs its own GraphQL service, a subgraph, declaring the types and fields it owns.
Go to One backend per client, or one graph over many services: BFFs and federation →entity
A type that several subgraphs contribute fields to is an entity, marked with a key, the fields that identify one instance: @key(fields: "sku").
Go to One backend per client, or one graph over many services: BFFs and federation →supergraph
A router and a supergraph.
Go to One backend per client, or one graph over many services: BFFs and federation →Graph router
A router and a supergraph.
Go to One backend per client, or one graph over many services: BFFs and federation →query plan
The router answers by building a query plan, a sequence of fetches where each fetch may need results from an earlier one:
Go to One backend per client, or one graph over many services: BFFs and federation →service discovery
The problem of keeping callers' lists of a service's live addresses up to date is called service discovery.
Go to Finding instances and spreading calls: discovery, connection reuse and subsetting →sidecar
A proxy placed next to every instance this way is called a sidecar, after the passenger car bolted to the side of a motorcycle.
Go to The service mesh: moving network code out of every service →service mesh
A service mesh is infrastructure that handles service-to-service communication outside the services: a data plane of proxies that carry every request between services and apply discovery, balancing, timeouts, retries, encryption and telemetry to it, and a control plane that computes each proxy's configuration and pushes it to them.
Go to The service mesh: moving network code out of every service →data plane
A service mesh is infrastructure that handles service-to-service communication outside the services: a data plane of proxies that carry every request between services and apply discovery, balancing, timeouts, retries, encryption and telemetry to it, and a control plane that computes each proxy's configuration and pushes it to them.
Go to The service mesh: moving network code out of every service →control plane
A service mesh is infrastructure that handles service-to-service communication outside the services: a data plane of proxies that carry every request between services and apply discovery, balancing, timeouts, retries, encryption and telemetry to it, and a control plane that computes each proxy's configuration and pushes it to them.
Go to The service mesh: moving network code out of every service →xDS
Envoy defines a family of configuration APIs, together called xDS: each "x" is a kind of configuration, such as the listeners a proxy opens, the routes it applies, the clusters of upstream services and the endpoints in each cluster, and "DS" stands for discovery service.
Go to The service mesh: moving network code out of every service →command
A command says "do this": ReserveStock(order 8841, sku-17, 2).
Go to Events between services: commands, notifications and carried state →event notification
An event notification says "this happened": OrderPlaced(8841), and little else, perhaps a link back to the source.
Go to Events between services: commands, notifications and carried state →Event-carried state transfer
Event-carried state transfer says "this happened, and here is the new state": OrderPlaced(8841, customer 77, items [...], address {...}).
Go to Events between services: commands, notifications and carried state →Transcoding
A translating proxy or library, a transcoder, maps each HTTP request onto the gRPC method: path variables and the body become fields of the request message.
Go to Choosing how services talk: REST, gRPC, GraphQL or messages, per boundary →Monolith
Monolith: an application built and deployed as one unit.
Go to What a microservice is: independent deployability, and where the idea came from →Service-oriented architecture
Service-oriented architecture, and what it became.
Go to What a microservice is: independent deployability, and where the idea came from →enterprise service bus
An enterprise service bus (ESB) is a central piece of middleware that every application talks to instead of talking to each other.
Go to What a microservice is: independent deployability, and where the idea came from →independent deployability
A service encapsulates functionality and makes it accessible to other services via networks." Its first chapter makes independent deployability the key idea: service boundaries, interfaces and team ownership exist so that a team can ship its change without coordinating with others.
Go to What a microservice is: independent deployability, and where the idea came from →microservice
A microservice is a service, meaning a process reached over a network, that is modelled around a business capability and is independently deployable: any change to it can go to production on its own, at any moment, with the rest of the system left alone, the system staying correct, and no one outside the owning team needing to act.
Go to What a microservice is: independent deployability, and where the idea came from →independently deployable
A microservice is a service, meaning a process reached over a network, that is modelled around a business capability and is independently deployable: any change to it can go to production on its own, at any moment, with the rest of the system left alone, the system staying correct, and no one outside the owning team needing to act.
Go to What a microservice is: independent deployability, and where the idea came from →microservice architecture
A microservice architecture is an application built as a suite of such services.
Go to What a microservice is: independent deployability, and where the idea came from →Domain-driven design
Domain-driven design (DDD) is an approach to building software that was named by Eric Evans in the 2003 book Domain-Driven Design: Tackling Complexity in the Heart of Software.
Go to Bounded contexts: one model per boundary, not one model per company →domain
The domain is the area of business the software is about (selling goods online, for the shop).
Go to Bounded contexts: one model per boundary, not one model per company →domain expert
A domain expert is a person who knows that business well, such as the person who runs the warehouse, without needing to be a programmer.
Go to Bounded contexts: one model per boundary, not one model per company →domain model
DDD asks developers and domain experts to build a domain model together: a simplified description of the parts of the business that matter, with their rules, which the code then mirrors.
Go to Bounded contexts: one model per boundary, not one model per company →ubiquitous language
The people who build and use a piece of software speak about the business in words, and the words they settle on form its ubiquitous language: one vocabulary, used in conversation, in diagrams and in the code itself, so that a class is named the way the expert says the thing.
Go to Bounded contexts: one model per boundary, not one model per company →bounded context
A bounded context is a boundary, typically a subsystem or the work of one team, within which one particular model is defined and applicable: inside it every term has exactly one meaning, and the team speaks one ubiquitous language.
Go to Bounded contexts: one model per boundary, not one model per company →subdomain
A subdomain is a part of the business, seen from the problem's side.
Go to Bounded contexts: one model per boundary, not one model per company →upstream-downstream
Two contexts are in an upstream-downstream relationship when the upstream team's actions affect the success of the downstream team's project, but the downstream team's actions do not significantly affect the upstream's.
Go to Context maps: who translates, and who pays for change →context map
A context map names every model in play, and for each pair of contexts that communicate it records the points of contact: how information is translated, what is shared, how the contexts are isolated, and who has influence over whom.
Go to Context maps: who translates, and who pays for change →surface
Call the number of those places its surface, : this is the degree of the connascence, counted in code sites.
Go to Context maps: who translates, and who pays for change →conformist
The downstream is a conformist.
Go to Context maps: who translates, and who pays for change →anticorruption layer
An anticorruption layer is an isolating layer that the downstream builds to get the upstream system's functionality in terms of the downstream's own model.
Go to Context maps: who translates, and who pays for change →open host service
That is an open host service.
Go to Context maps: who translates, and who pays for change →published language
A published language is a well-documented shared language that can express the domain information needed, used as a common medium so that each side translates into and out of that language rather than to and from the other's private model.
Go to Context maps: who translates, and who pays for change →separate ways
In separate ways two contexts are declared to have no connection at all, and each solves its problem alone, even if that duplicates a little data.
Go to Context maps: who translates, and who pays for change →partnership
In a partnership two teams succeed or fail together, because a missing feature in one makes the other undeliverable.
Go to Context maps: who translates, and who pays for change →Conway's law
Conway's law: organisations which design systems are constrained to produce designs which are copies of the communication structures of those organisations.
Go to Conway's law: systems mirror the communication structure that built them →propagation cost
The share of its cells that hold a 1 is the system's propagation cost: on average, the fraction of the system that a change to one randomly chosen element can reach.
Go to Conway's law: systems mirror the communication structure that built them →inverse Conway manoeuvre
Thoughtworks, a software consultancy, put this on its Technology Radar in July 2014 as the inverse Conway manoeuvre: evolve the team and organisational structure to promote the architecture you want.
Go to Conway's law: systems mirror the communication structure that built them →cognitive load
Team Topologies answers with cognitive load, an idea from educational psychology (John Sweller's cognitive load theory): the amount of mental effort a person or group can spend before they stop learning and start making mistakes.
Go to Conway's law: systems mirror the communication structure that built them →Data ownership
Data ownership means that exactly one service is the system of record for a piece of data, and that service alone reads and writes the store holding it.
Go to Data ownership: why a shared database is a shared interface →denormalised
It wants the facts of one screen in one place, even if that repeats a customer's name on every row (denormalised), with indexes in the order each screen sorts by.
Go to CQRS and read models: one model for changing data, others for asking →command-query separation
Bertrand Meyer, designer of the Eiffel language, put a rule into the book Object-Oriented Software Construction (first edition 1988, second 1997) called command-query separation: every method either changes state and returns nothing (a command), or returns an answer and changes nothing (a query), never both. "Asking a question should not change the answer." Calling balance() twice must give the same number; withdr…
Go to CQRS and read models: one model for changing data, others for asking →Command Query Responsibility Segregation
Command Query Responsibility Segregation (CQRS) uses one model to validate and apply changes (the write model) and one or more separate models, built only to answer questions (the read models), each kept up to date from the stream of changes the write model makes.
Go to CQRS and read models: one model for changing data, others for asking →write model
Command Query Responsibility Segregation (CQRS) uses one model to validate and apply changes (the write model) and one or more separate models, built only to answer questions (the read models), each kept up to date from the stream of changes the write model makes.
Go to CQRS and read models: one model for changing data, others for asking →Read model
Command Query Responsibility Segregation (CQRS) uses one model to validate and apply changes (the write model) and one or more separate models, built only to answer questions (the read models), each kept up to date from the stream of changes the write model makes.
Go to CQRS and read models: one model for changing data, others for asking →projector
The projector is the small program in the middle.
Go to CQRS and read models: one model for changing data, others for asking →distributed monolith
A distributed monolith is a system split into separately running services in which a change, a release or a failure in one service usually forces a change, a release or a failure in others.
Go to The distributed monolith: services that must change together →lockstep
Sam Newman describes the same symptom in Newman's books as a system that has to be released in lockstep: several services deployed together, in a coordinated order, because none can safely go alone.
Go to The distributed monolith: services that must change together →Pass-through coupling
Pass-through coupling.
Go to The distributed monolith: services that must change together →temporal coupling
This is temporal coupling: caller and callee must both be working at the same instant.
Go to The distributed monolith: services that must change together →hard dependency
A hard dependency is a service whose answer the request cannot do without.
Go to The distributed monolith: services that must change together →modular monolith
A modular monolith is one application, built and deployed as one unit, in which the code is divided into modules along business domains with strictly enforced boundaries between them.
Go to The modular monolith: boundaries without a network →service granularity
The word for the size of the choice is service granularity: how much of a system's behaviour one separately deployed unit holds.
Go to Monolith or microservices: deciding per boundary →microservice premium
Fowler named this the microservice premium (2015): the extra cost and risk that managing a suite of services adds to a project, which only a system complex enough to be hard to manage as a monolith can recoup.
Go to Monolith or microservices: deciding per boundary →Wire compatibility
An API change is wire compatible when code written against the previous version can still communicate correctly with the new server: the bytes still parse, and the old and new sides still expect the same encoding.
Go to Compatibility classes: wire, source and behaviour →Source compatibility
It is source compatible when code written against the previous version still compiles, and runs, after the client library is regenerated from the new contract.
Go to Compatibility classes: wire, source and behaviour →Semantic compatibility
It is semantically compatible when that code keeps receiving what most reasonable developers would expect: the same meaning, not just the same bytes.
Go to Compatibility classes: wire, source and behaviour →exhaustive match
An exhaustive match is a branch over a value that is required to name every possible case, so that the compiler can say when one is missing.
Go to Compatibility classes: wire, source and behaviour →robustness principle
This became known as the robustness principle, or Postel's law, after Jon Postel, one of the authors of the early internet protocol specifications.
Go to Compatibility classes: wire, source and behaviour →Hyrum's law
Hyrum's law: with a sufficient number of users of an API, it does not matter what you promise in the contract, because all observable behaviours of your system will be depended on by somebody.
Go to Hyrum's law: every observable behaviour becomes a dependency →implicit interface
The behaviours that consumers depend on, whether or not anyone documented them, form the implicit interface of the service, as opposed to the documented, explicit one.
Go to Hyrum's law: every observable behaviour becomes a dependency →schema diff
This is a schema diff, and it needs no consumer's cooperation and no running service.
Go to Detecting breaking changes: schema diffs and consumer-driven contracts →consumer contract
A consumer contract is the set of expectations one consumer places on a provider: a part of the provider's full contract, written by that consumer.
Go to Detecting breaking changes: schema diffs and consumer-driven contracts →consumer-driven contract
A consumer-driven contract is what the provider obtains by treating the union of all its consumers' contracts as an obligation: the provider is free to change anything outside that union, and must not change anything inside it without the consumers' agreement.
Go to Detecting breaking changes: schema diffs and consumer-driven contracts →compatibility relation
It is a relation between an ordered pair of schemas.
Go to Event contracts: compatibility over a retained history →schema registry
A schema registry is a small service that stores every version of every schema and refuses a new version that breaks a rule.
Go to Event contracts: compatibility over a retained history →upcaster
An upcaster is a function that takes an event written under an old version and returns it in a newer version's shape, applied as the event is read, so every consumer sees only the latest shape.
Go to Event contracts: compatibility over a retained history →API version
The provider publishes the break as a new API version: a name a client can ask for, here /v1/orders and /v2/orders, or a version in the protobuf package (orders.v1, orders.v2).
Go to Versioning strategies: what each one makes the provider pay →version change module
The post's name for this is a version change; here we call it a version change module: a piece of code holding the change's description, a transformation that undoes it, and the resource types it applies to.
Go to Versioning strategies: what each one makes the provider pay →API version pinning
Each consumer account is pinned to a version, set automatically at its first request, so an integration never receives a break it did not choose.
Go to Versioning strategies: what each one makes the provider pay →Deprecation
Deprecation means "stop using this, and do not start".
Go to Versioning strategies: what each one makes the provider pay →Sunset
Sunset means "this will stop responding at this time".
Go to Versioning strategies: what each one makes the provider pay →unknown outcome
This lesson calls that case an unknown outcome.
Go to Error contracts: telling the caller what to do next →retry scope
Call that amount the retry scope: the call (send the same request again, perhaps after a delay), the transaction (restart the sequence that led to this call), or none (do not repeat until something changes that the call cannot change).
Go to Error contracts: telling the caller what to do next →error contract
An error contract is the part of an API's promise that covers failure.
Go to Error contracts: telling the caller what to do next →safe-to-repeat
A safe-to-repeat operation is one where sending it twice has the same intended effect as sending it once; RFC 9110 calls a method with that property idempotent, and lists PUT, DELETE and the read-only methods as such.
Go to Error contracts: telling the caller what to do next →error reason
The reason is a short UPPER_SNAKE_CASE name of the cause, at most 63 characters (for example NO_STOCK).
Go to Error contracts: telling the caller what to do next →page
So list calls return a page: at most k items (the page size), plus a way to ask for the next page.
Go to Pagination under concurrent writes: offset, keyset and snapshot →page size
So list calls return a page: at most k items (the page size), plus a way to ask for the next page.
Go to Pagination under concurrent writes: offset, keyset and snapshot →offset
The offset o is the number of rows to step over before collecting the page.
Go to Pagination under concurrent writes: offset, keyset and snapshot →seek method
Winand calls this the seek method, and it is usually called keyset pagination or cursor pagination.
Go to Pagination under concurrent writes: offset, keyset and snapshot →keyset pagination
Winand calls this the seek method, and it is usually called keyset pagination or cursor pagination.
Go to Pagination under concurrent writes: offset, keyset and snapshot →sort key
It holds the sort key of the last row: the value or values the list is ordered by.
Go to Pagination under concurrent writes: offset, keyset and snapshot →tiebreaker
The remedy is to add a column that is unique, usually the primary key, as a tiebreaker, and to compare the two columns together.
Go to Pagination under concurrent writes: offset, keyset and snapshot →Snapshot pagination
Snapshot pagination reads every page of one listing as of a single moment: the first page fixes a snapshot time, the page token carries it, and each later page is a keyset read evaluated at that time.
Go to Pagination under concurrent writes: offset, keyset and snapshot →Optimistic concurrency control
Optimistic concurrency control lets every client read and compute without locks, and makes the write carry proof of what the client read.
Go to Optimistic concurrency over an API: etags, preconditions and field masks →entity tag
HTTP has one built in: the entity tag, written ETag.
Go to Optimistic concurrency over an API: etags, preconditions and field masks →validator
It is a validator: a value that changes when the resource changes, so a later comparison can tell whether anything has happened.
Go to Optimistic concurrency over an API: etags, preconditions and field masks →field mask
The fix is a field mask: the update names the fields it changes, and the server leaves all others untouched.
Go to Optimistic concurrency over an API: etags, preconditions and field masks →foreign state mutation
Step 2 is a foreign state mutation: a change made in a system outside our own database.
Go to Idempotency keys for multi-step requests: atomic phases and recovery points →atomic phase
An atomic phase is a run of local writes, inside one database transaction, between two foreign calls.
Go to Idempotency keys for multi-step requests: atomic phases and recovery points →recovery point
A recovery point is a name, stored on the idempotency key's row, for the last step a request is known to have completed.
Go to Idempotency keys for multi-step requests: atomic phases and recovery points →request fingerprint
A request fingerprint is a hash of the parts of the request that express the caller's intent: the same input always gives the same hash, and two different inputs give different hashes in practice. "Normalised" means the body is put in a fixed form first (for JSON, keys sorted), so that two ways of writing the same body hash the same.
Go to Idempotency keys for multi-step requests: atomic phases and recovery points →completer
The completer scans for keys that are not finished, whose lock is free or expired and whose last attempt is older than some threshold, and drives each one to the end by running the same loop.
Go to Idempotency keys for multi-step requests: atomic phases and recovery points →reaper
The reaper deletes keys older than the retention window.
Go to Idempotency keys for multi-step requests: atomic phases and recovery points →long-running operation
A long-running operation is a server-side resource that represents one piece of asynchronous work.
Go to Long-running operations: the operation as a resource →poll
To poll is to ask again and again until the answer changes.
Go to Long-running operations: the operation as a resource →exponential back-off
The usual answer is exponential back-off: start with a short interval and make each wait a constant factor longer than the one before.
Go to Long-running operations: the operation as a resource →jitter
One more rule, because back-off is used by thousands of clients: add jitter, a small random amount, to each wait.
Go to Long-running operations: the operation as a resource →long polling
In long polling the client sends the same GET, but the server does not answer until the operation is done or a timeout passes, whichever is first.
Go to Long-running operations: the operation as a resource →callback
A callback turns the direction around: the client gives a URL when it starts the operation, and the server sends the finished operation to it.
Go to Long-running operations: the operation as a resource →lease
A worker therefore claims an operation with a lease, a claim that expires unless the worker keeps renewing it, and saves its progress as it goes, so that another worker can resume.
Go to Long-running operations: the operation as a resource →webhook
A webhook is an HTTP request that a service (the provider) sends to a URL its customer (the receiver) registered in advance, to say that an event happened.
Go to Webhook contracts: at-least-once, unordered, and thin →thin
For webhooks the same choice is called thin versus full payloads: the Standard Webhooks specification describes a thin payload as holding only identifiers of the affected entities and perhaps some information about the change itself, and a full payload as holding the entities' whole state.
Go to Webhook contracts: at-least-once, unordered, and thin →acknowledgement
A 2xx answer is an acknowledgement that the receiver has the event safely, and not that it has done the work.
Go to Webhook contracts: at-least-once, unordered, and thin →exponential backoff
Spacing the attempts by exponential backoff means each wait is a fixed multiple of the one before: quick attempts at first, in case the failure was a blip, then ever longer waits, so a receiver that is down is not hammered.
Go to Webhook contracts: at-least-once, unordered, and thin →dual write
Doing one after the other is a dual write, and any order loses something.
Go to Outbox and inbox: making a state change and its message one fact →message relay
A separate process, the message relay, reads committed outbox rows and publishes them.
Go to Outbox and inbox: making a state change and its message one fact →inbox
An inbox is a table in the receiving service's own database that records the id of each message already processed, written in the same local transaction as the message's effect.
Go to Outbox and inbox: making a state change and its message one fact →idempotent consumer
The pattern is called the idempotent consumer in Richardson's catalogue, which suggests tracking the ids of messages already processed.
Go to Outbox and inbox: making a state change and its message one fact →exactly-once effect
An exactly-once effect means each message changes the receiver's state once, however many times it is delivered.
Go to Outbox and inbox: making a state change and its message one fact →semantic undo
We will call this a semantic undo: a fresh forward transaction whose business meaning cancels the earlier one.
Go to Saga structure: compensatable, pivot and retriable steps →compensatable transaction
A compensatable transaction is a step that has a compensation which can always be run to completion.
Go to Saga structure: compensatable, pivot and retriable steps →retriable transaction
A retriable transaction is a step that, if retried enough times, is guaranteed to succeed.
Go to Saga structure: compensatable, pivot and retriable steps →pivot transaction
A pivot transaction is the go or no-go step: if it commits, the saga will run to the end; if it fails, the saga undoes everything before it.
Go to Saga structure: compensatable, pivot and retriable steps →tombstone
The fix is that a compensation which finds nothing to undo still records that it ran, a tombstone keyed by saga id, and every forward step checks for the tombstone before acting.
Go to Saga structure: compensatable, pivot and retriable steps →Backward recovery
Backward recovery compensates for the steps already executed.
Go to Saga structure: compensatable, pivot and retriable steps →Forward recovery
Forward recovery executes the steps that are missing.
Go to Saga structure: compensatable, pivot and retriable steps →ACD
A saga is ACD: it has atomicity (after a while, either every step has run or every completed step has been compensated), consistency (each step keeps its own service's rules, and the rules that span services are kept by the application's design) and durability (each step is committed in its own database), but no isolation: the effects of a step are visible to everyone the moment its local transaction commits, long…
Go to Saga isolation: anomalies without a lock manager and their countermeasures →fuzzy read
A fuzzy read (Richardson's name for a non-repeatable read, a cousin of what System Design calls read skew) is one saga reading the same data twice and getting different answers, because another saga committed in between.
Go to Saga isolation: anomalies without a lock manager and their countermeasures →countermeasures
Nobody owns such a component across the shop's services, so each defence is a countermeasure: a design technique, written into the application's data and step order, that does the job of isolation for one kind of anomaly.
Go to Saga isolation: anomalies without a lock manager and their countermeasures →semantic lock
A semantic lock is a flag in the application's own data, usually a status such as PENDING, set by a compensatable step of a saga to say "this record is mid-saga".
Go to Saga isolation: anomalies without a lock manager and their countermeasures →Pessimistic view
Pessimistic view is this reordering: move the step whose visible effect others must not act on until after the pivot, so that it is never compensated.
Go to Saga isolation: anomalies without a lock manager and their countermeasures →reread value
The reread value countermeasure stops a lost update by making the second writer look first.
Go to Saga isolation: anomalies without a lock manager and their countermeasures →commutative
Two updates commute when applying them in either order leaves the same result.
Go to Saga isolation: anomalies without a lock manager and their countermeasures →version file
A version file keeps, for a record, the log of operations applied to it, keyed by the saga that sent them, so an operation that arrives in the wrong order can be recognised and combined correctly.
Go to Saga isolation: anomalies without a lock manager and their countermeasures →choreography
In choreography, there is no central controller.
Go to Orchestration and choreography: where a process's state lives →orchestration
In orchestration, an orchestrator tells the participants which local transaction to run.
Go to Orchestration and choreography: where a process's state lives →orchestrator
In orchestration, an orchestrator tells the participants which local transaction to run.
Go to Orchestration and choreography: where a process's state lives →process manager
A process manager is a central processing unit that maintains the state of a sequence of steps and determines the next step based on intermediate results.
Go to Orchestration and choreography: where a process's state lives →routing slip
The same catalogue has a lighter relative, the routing slip: a list of steps attached to the message itself.
Go to Orchestration and choreography: where a process's state lives →Durable execution
Durable execution runs a program so that its progress survives the loss of any process running it.
Go to Durable execution: a workflow as code that survives crashes by replay →event history
An engine records every request the program makes to the outside world, and every answer, in an append-only event history kept in durable storage.
Go to Durable execution: a workflow as code that survives crashes by replay →replay
This re-running is called replay.
Go to Durable execution: a workflow as code that survives crashes by replay →continue-as-new
The restart is called continue-as-new in Temporal.
Go to Durable execution: a workflow as code that survives crashes by replay →deterministic
Workflow code is deterministic when, run again with the same input and the same recorded results, it issues the same commands in the same order.
Go to Determinism under replay: what workflow code may not do →non-determinism error
A mismatch is a non-determinism error: the engine cannot reproduce the old run with the new code.
Go to Changing workflow code under running executions →version marker
A version marker is a named record the workflow code writes into its own history when it first reaches a changed point.
Go to Changing workflow code under running executions →live set
That collection is the live set.
Go to Expand and contract: changing a schema across a rolling deploy →migration
A migration is a script that changes the schema; this lesson uses it for steps 1 and 6 only, and says "release" for a deploy of code.
Go to Expand and contract: changing a schema across a rolling deploy →feature flag
Switch it behind a feature flag, a switch the running code checks in configuration so it can be turned back without a deploy.
Go to Expand and contract: changing a schema across a rolling deploy →Online schema change
Online schema change is changing a table's structure while it keeps serving reads and writes.
Go to Online schema change: shadow tables, change capture and cut-over →shadow table
The common construction creates a shadow table (also called a ghost table): an empty table with the new structure, which only the migration tool uses.
Go to Online schema change: shadow tables, change capture and cut-over →trigger
A trigger is a routine stored in the database, run automatically whenever a row is inserted, updated or deleted.
Go to Online schema change: shadow tables, change capture and cut-over →binary log
Every engine already writes each change to a log for replicas and recovery: PostgreSQL's write-ahead log, which logical replication decodes into row changes, and MySQL's binary log (binlog), the file of changes that MySQL replicas read.
Go to Online schema change: shadow tables, change capture and cut-over →throttle
This is a throttle, and it is a feedback loop of the ordinary kind: measure, compare with a limit, act, repeat.
Go to Online schema change: shadow tables, change capture and cut-over →Convergence
A copy B converges to its source A when, once A stops changing and B has applied every change A emitted, B holds for every key exactly the value A holds.
Go to Moving data to a new store: dual writes against change data capture →version guard
Call that a version guard.
Go to Moving data to a new store: dual writes against change data capture →reconciler
So a dual-write migration also needs a reconciler: a background job that walks the keys in ranges, compares each key's version in A with its version in B, and repairs any key where B is behind by re-sending A's row, with its version, through the same guarded write.
Go to Moving data to a new store: dual writes against change data capture →backfill
A backfill is the copying of data that already exists into a new place (a new store, a new table or a new column) while the system that owns the data keeps running and changing it.
Go to Backfills against a live change stream →stale overwrite
Call this a stale overwrite: a write that carries a value read earlier lands after, and replaces, a value written later.
Go to Backfills against a live change stream →upsert
The stream applier applies each event as an upsert: write the row, creating it if it is missing.
Go to Backfills against a live change stream →version
Give every row a version, a number that grows with each change to that row, and make every write to the target conditional: apply it only if its version is newer than what the target holds.
Go to Backfills against a live change stream →snapshot
Take a consistent snapshot of the table, a frozen view as of one instant, and learn the position in the change log that instant corresponds to.
Go to Backfills against a live change stream →watermark method
In the watermark method, a copier takes the next chunk of a table by (1) writing a low watermark, a uniquely identified marker update, to a small table in the source database; (2) selecting the chunk by primary-key range; (3) writing a high watermark the same way.
Go to Backfills against a live change stream →chunk
A chunk is a run of consecutive primary keys, read with WHERE id > last_id ORDER BY id LIMIT size so that each read starts at an index seek.
Go to Backfills against a live change stream →checkpoint
The last key of a finished chunk is also the checkpoint: the one value a restarted job needs to continue.
Go to Backfills against a live change stream →defect fraction
The defect fraction is the number of defective keys divided by the number of keys, a number between 0 and 1.
Go to Verifying a migration: checksums, sampling and reconciliation →miss probability
A verification method is judged by its miss probability : the chance that it reports "no defects" although a fraction of the keys are defective.
Go to Verifying a migration: checksums, sampling and reconciliation →comparison function
This comparison function is part of the migration's specification, as much as the transform that copies the data.
Go to Verifying a migration: checksums, sampling and reconciliation →checksum
The way out is a checksum, a short fingerprint made by a hash function: a function that squeezes any input into a fixed-size value, always giving the same value for the same input and, for a good one, a value that looks unrelated for any other input (see cryptographic hash functions).
Go to Verifying a migration: checksums, sampling and reconciliation →reconciliation
A pass that was clean on Tuesday says nothing about Thursday's writes, so the check becomes a loop, a reconciliation process that continuously compares the two sides and repairs what differs.
Go to Verifying a migration: checksums, sampling and reconciliation →incumbent
Call the old one the incumbent, the system customers rely on now, and the new one the candidate, the system hoping to replace it.
Go to Shadow traffic: comparing a new implementation on real requests →candidate
Call the old one the incumbent, the system customers rely on now, and the new one the candidate, the system hoping to replace it.
Go to Shadow traffic: comparing a new implementation on real requests →Shadowing
Shadowing (also called traffic mirroring) is running a candidate implementation on copies of real requests while only the incumbent's response is returned to the caller, then comparing the two responses.
Go to Shadow traffic: comparing a new implementation on real requests →strangler fig application
A strangler fig application is a way to replace a system gradually: new functionality is built beside the old system, behind a common front that sends each request to whichever of the two handles it, and behaviour is moved from the old system to the new one piece by piece until the old one handles nothing and can be removed.
Go to Strangler fig: replacing a system one route at a time →reverse proxy
A reverse proxy is a server that sits in front of other servers: clients talk to it, and it forwards each request to a server behind it and returns the reply.
Go to Strangler fig: replacing a system one route at a time →routing facade
The routing facade of a strangler fig is a reverse proxy whose forwarding rules are the migration plan.
Go to Strangler fig: replacing a system one route at a time →seams
Fowler speaks of finding seams in a legacy system, to split it along.
Go to Strangler fig: replacing a system one route at a time →Branch by abstraction
Branch by abstraction is a way to replace a module, library or framework that other code depends on, gradually and without a long-lived version-control branch: put an abstraction layer in front of the thing being replaced, move the clients onto it, build the new implementation behind the same layer, switch clients over to it, then delete the old implementation and, if no longer needed, the layer.
Go to Strangler fig: replacing a system one route at a time →cut-over
A cut-over is the moment the router stops sending a capability's traffic to the old system and starts sending it to the new one.
Go to Cut-over and rollback: where the point of no return is →rollback
A rollback is the opposite switch.
Go to Cut-over and rollback: where the point of no return is →reversible
A step in a migration is reversible if, after it, the system can return to the state before it with no acknowledged write lost and within a time the business accepts.
Go to Cut-over and rollback: where the point of no return is →write freeze
That moment is the write freeze: the router holds new writes (or the old store is made read-only) for a short window while the last changes flow across.
Go to Cut-over and rollback: where the point of no return is →Reverse replication
Reverse replication is a change stream from the new system of record back to the old store, started at the cut-over position so that the old store keeps receiving every write the new store accepts.
Go to Cut-over and rollback: where the point of no return is →point of no return
The point of no return is the first step after which a rollback would lose acknowledged writes or need data that no longer exists.
Go to Cut-over and rollback: where the point of no return is →bake period
So the point of no return is a decision the team takes, and the skill is in taking it as late as is reasonable and after a bake period: the time the new system runs as the only writer, with the old one still caught up, before the old one is retired.
Go to Cut-over and rollback: where the point of no return is →tenant
A tenant is a customer of a shared software product: a group of users who see one set of data, one configuration and one bill, and who must never see another group's data.
Go to Silo, pool and bridge: the cost and isolation of tenancy →multi-tenant
A system that serves several tenants from the same running software is multi-tenant.
Go to Silo, pool and bridge: the cost and isolation of tenancy →silo
A silo gives a tenant dedicated resources: a database of their own, say, or a whole independent stack of services.
Go to Silo, pool and bridge: the cost and isolation of tenancy →pool
A pool shares resources between tenants.
Go to Silo, pool and bridge: the cost and isolation of tenancy →bridge
A bridge mixes the two, which is the next section.
Go to Silo, pool and bridge: the cost and isolation of tenancy →noisy neighbour
A noisy neighbour is a tenant whose heavy use of a shared resource (CPU, database connections, a queue) slows the tenants sharing it.
Go to Silo, pool and bridge: the cost and isolation of tenancy →tenant directory
That is a small table, the tenant directory, mapping a tenant id to a placement, and a router in front of the services that consults it.
Go to Silo, pool and bridge: the cost and isolation of tenancy →tenant key
A tenant key is a column, present in every table that holds tenant data, that names the tenant owning each row (here tenant_id).
Go to Tenant data partitioning: database, schema or row per tenant →Entity–attribute–value
Entity–attribute–value (EAV) stores each fact about a record as its own row of three parts: which record (the entity), which field (the attribute) and its content (the value).
Go to Per-tenant customisation: extensible schemas in a shared store →document column
A document column holds a record's custom fields as one structured value, for example {"channel": "wholesale", "region": "EMEA", "po_number": "PO-7731"}.
Go to Per-tenant customisation: extensible schemas in a shared store →dominant share
A tenant's dominant share is the largest, over all resources, of the fraction of that resource allocated to it; the resource that gives the maximum is its dominant resource.
Go to Fairness between tenants: quotas, fair queuing and dominant resources →Dominant resource fairness
Dominant resource fairness (DRF) is max-min fairness applied to the dominant shares: it makes the smallest dominant share as large as possible, then the next smallest, and so on.
Go to Fairness between tenants: quotas, fair queuing and dominant resources →placement directory
A placement directory is a table that maps each tenant to the shard (or cell, or whole stack) that currently serves it.
Go to Tenant placement: packing skewed tenants and moving the big ones →Bin packing
Bin packing: given items with sizes , each at most , and any number of identical bins of capacity , assign every item to a bin so that no bin's total exceeds , using as few bins as possible.
Go to Tenant placement: packing skewed tenants and moving the big ones →first fit
The simplest method is first fit: take tenants in the order they arrive and put each on the first shard that still has room, opening a new shard when none does.
Go to Tenant placement: packing skewed tenants and moving the big ones →tenant tier
A tenant tier is a named bundle of commitments that a tenant buys: how isolated it is, what limits apply to it, what latency and availability it is promised, and what it pays.
Go to Tenant tiering: one product, several tenancy models →architecturally significant decision
An architecturally significant decision is one that shapes the structure, the dependencies, the interfaces, or a quality such as latency or isolation, and that is expensive to change once other code and other teams have built on it.
Go to Decision records and fitness functions: keeping the why and checking the what →architecture decision record
An architecture decision record, or ADR, is a short text file that describes a set of forces and a single decision in response to them.
Go to Decision records and fitness functions: keeping the why and checking the what →ADR
An architecture decision record, or ADR, is a short text file that describes a set of forces and a single decision in response to them.
Go to Decision records and fitness functions: keeping the why and checking the what →fitness function
A fitness function is, in the words of the book's second edition (Ford, Parsons, Kua and Pramod Sadalage, 2022), "any mechanism that provides an objective integrity assessment of some architectural characteristic(s)".
Go to Decision records and fitness functions: keeping the why and checking the what →one-way door
A one-way door is a decision that cannot be undone, or can be undone only at a cost so high that nobody would pay it.
Go to Reversibility: one-way doors, two-way doors and the value of waiting →two-way door
A two-way door is a decision that can be undone at a cost small next to what it was meant to gain.
Go to Reversibility: one-way doors, two-way doors and the value of waiting →reversal cost
The reversal cost of a decision is everything it takes to get back to roughly where you were: the engineering work to undo it, the time the system spends in the wrong state while you do, the coordination with everyone who relied on it, and anything you cannot recover (lost data, a broken promise, a missed window).
Go to Reversibility: one-way doors, two-way doors and the value of waiting →expected value of information
The expected value of information, , of a signal is how much better the best decision made after seeing it is, on average, than the best decision made without it.
Go to Reversibility: one-way doors, two-way doors and the value of waiting →expected value of perfect information
The value of that perfect information is the expected value of perfect information, , where the first maximum is taken inside the expectation, state by state.
Go to Reversibility: one-way doors, two-way doors and the value of waiting →carrying cost
the carrying cost : what it costs to keep the door open, paid whether or not you ever use it (an extra interface, a second path to maintain, an extra running system);
Go to Reversibility: one-way doors, two-way doors and the value of waiting →cost of delay
The cost of delay of a piece of work is the value lost for each unit of time before it is finished, written and measured in money (or any one consistent value unit) per week.
Go to Cost of delay: sequencing architectural work by value per unit time →duration
Its duration is the time the team needs to do it, in weeks.
Go to Cost of delay: sequencing architectural work by value per unit time →Technical debt
Technical debt is the gap between a design as it stands and the design the team now understands it should have, which was shipped knowingly to learn sooner.
Go to Technical debt as principal and interest →principal
Its principal is the one-off cost of closing the gap.
Go to Technical debt as principal and interest →interest
Its interest is the extra cost that gap adds to each later change that touches the affected code.
Go to Technical debt as principal and interest →discount rate
We capture that with a discount rate per period: a cost arriving periods from now is counted as today.
Go to Technical debt as principal and interest →hotspots
Adam Tornhill, in Your Code as a Crime Scene (Pragmatic Bookshelf; 2nd edition 2024), builds a technique on it: find the hotspots, the files that are both changed often and complex, using nothing but commit history and a measure of size or complexity.
Go to Technical debt as principal and interest →