Architecture

Architectural Thinking: Boundaries, Responsibilities and Trade-offs

How to reason about a system's boundaries, responsibilities, sources of truth and trade-offs, compare architectural styles by real need and cost, and…

Chapter 12Architecture

Earlier in this course we moved from a user's need to product requirements, clarified requirements, acceptance criteria and quality characteristics. Now we can talk about the structure of the solution: which parts exist, who is responsible for what, across which boundaries they interact, and what happens when one of them changes or fails.

Software architecture is the set of significant structural decisions that noticeably shape later change, release, maintenance and risk. The name of a technology is not, on its own, architecture. Choosing a particular library may remain a local detail. Choosing which part of the system owns payments, and whether another component may change payment state, affects boundaries, data, permissions and many decisions that follow.

After working through this unit you should be able to explain the difference between a logical boundary and a separate server; see coupling, responsibility and the source of truth; compare a monolith, a modular monolith, services and event-driven interaction by need and by cost; name the assumptions that still have to be checked; and record a decision so that the team can understand it later.

Architectural thinking does not require drawing the whole system in advance or appointing a "chief architect" for every feature. It requires seeing the consequences of a significant choice, naming its context and being ready to revisit the decision when the facts change.

Separate architecture, module design, code and deployment

Four layers of software system design: system architecture, module design, implementation and deployment topology

In conversation, the word "architecture" is often used for four different things. This unit keeps them apart, so that we do not argue about different levels at once:

Level Question Example
System architecture Which structural decisions define boundaries, responsibilities, data flows and expensive consequences for several parts? Confirming a booking and taking the slot happen in one transaction (an operation that applies all related changes together or none of them), and the bookings module owns that transaction.
Module design How does one part of the code organise its responsibility and the interaction it exposes to others? The bookings module offers a rescheduleBooking operation, while the calculation of available time slots stays hidden inside its boundary.
Implementation Which code, library and local algorithm carry out a specific behaviour? The free-time check uses a database index and a transactional constraint.
Deployment topology In which processes, machines, regions or containers do the parts run? The web application and the background worker start as separate processes from the same build of the software.

These levels are related but not identical. A separate module does not have to run on a separate server. Two processes can run exactly the same code but with different modes and at different scale. A system with a single deployment can be modular or tangled; the number of processes proves neither.

The word monolith is used inconsistently: it can mean one deployed application, one large codebase, or responsibilities that have not been separated. So always say which level you mean. In this unit, a modular monolith is one application that can be released as a whole, but whose internal modules have distinct duties and controlled dependencies. A microservice is a separately deployed service in its own process that interacts through a network contract and often carries its own operational responsibility. Real teams may choose intermediate forms.

Shopify showed why a large system may need stronger internal boundaries without moving to microservices. In 2019 the company wrote that more than a thousand developers had worked on its Rails codebase over more than ten years. A small team started a modularity project in early 2017, chose boundaries inside the monolith and described difficulties with brittle dependencies, testing and passing context on to new developers. When files were moved, part of the Git history was lost, because GitHub treated the moves as deletions and creations rather than renames. In its 2020 self-assessment Shopify gave a scale of more than 2.8 million lines of Ruby and 500,000 commits, and noted that the work was far from finished, although the team already saw intermediate benefits and was spreading clearer boundaries between components. This is a useful primary case about context, team, scale and the cost of change. But Shopify's self-assessment is not an independent comparison with microservices, and it does not prove that other teams need the same tools or rules (Shopify, "Deconstructing the Monolith", 21 February 2019; Shopify, "Under Deconstruction: The State of Shopify's Monolith", 16 September 2020).

A part of the system is a responsibility, not just a folder

Boundaries and responsibilities of the parts of a system

The words component, module, service, interface and responsibility carry useful shades of meaning, but not every organisation defines them the same way. In our working model:

  • a responsibility is the set of decisions, rules and state that a particular part of the system must answer for;
  • a module is a logically grouped part of the program with clear duties and a permitted way of interacting with others;
  • a component is a part that can be considered and changed on its own through described boundaries; it may consist of several modules;
  • an interface is the promise a part of the system makes that its consumers can see: which operations or data are available and under what conditions;
  • a service is a part that provides behaviour across a runtime boundary, often as a separate process or network address.

An interface can be an ordinary function call inside one process, or a network contract between two systems. A boundary is of no use if it has been drawn but anyone can bypass its rules and change someone else's state directly.

Responsibility helps test a boundary. If the "booking" module simultaneously decides a button's colour, pays supplier invoices, sends marketing campaigns and stores the password policy, its name does not explain its role. If, on the other hand, every small action requires calls into five different modules, the boundaries may be cut too finely or the duties placed badly.

One business scenario may pass through several modules. That is not automatically a mistake. For example, "reschedule a consultation" might check availability in the calendar, record the change in the bookings module and schedule a notification. The question is not whether a change touched more than one part, but whether those transitions are explicit, consistent and safe to verify.

Coupling and cohesion

Coupling is the dependence of one part on another. Coupling can never be zero: parts have to cooperate to create a product. The risk grows when changing one responsibility forces unrelated modules to know each other's internal details, to coordinate a simultaneous deployment, or to copy the same rules.

Cohesion is how far the duties inside a part share a common reason to change. All the functions of a module may be related to schedules, but if some store working hours, others run a mailing and still others change the public website, a shared name does not guarantee cohesion.

For diagnosis, this unit looks at four forms of coupling. These are teaching categories, not a universal official classification of every architectural problem.

Form of coupling How it shows up Question to check
Structural One module imports another's internal class or relies on its hidden structure Can we change the internal way data is stored without changing every consumer?
Temporal For one operation to work, another must run earlier, later or within a certain window Do we know what to do if the first action is delayed or arrives twice?
Data Several parts directly change the same tables, fields or business rules Who owns the record, and who has the right to change it?
Operational Parts have to be released, scaled, monitored or recovered together Do they really need to be operated and changed together?

A database shared between modules is not necessarily bad. Early on it can preserve the atomicity of an important write and reduce the amount of infrastructure. But when many modules change the same tables at will, physical closeness hides a logical dependency: nobody can say who is responsible for a rule or whether it is safe to change the schema. What follows from this is the need for a clear data owner, not automatically a separate database for every folder.

Modularity should match the way the system actually changes. If the team keeps changing two boundaries together, that is a sign they need to be re-examined: do they really have different owners, different rates of change or different rules for storing data? That is a reason for analysis, not an unconditional order to merge the modules. Likewise, if a component can be replaced independently without changing its consumers, that independence may be valuable, but only if the product and the team actually need it. In their joint article "Microservices", James Lewis and Martin Fowler discuss evolutionary decomposition of a system; the authors published it on 25 March 2014 (Lewis and Fowler, "Microservices").

State, source of truth, commands and events

The source of truth, commands and events in a system

State is the stored facts on which later behaviour depends. In a booking system these might be a time slot, a client, the status of a booking and the time it was last updated. The architectural question is not simply "which table holds the value?" but "who has the right to decide what the value is?"

The source of truth is the place or component responsible for confirmed state. Other copies can speed up reads or keep things working during a failure, but they must have a clear relationship with the source: when they are refreshed, how they mark themselves as stale, and what to do when they disagree. If two independent services can, without coordination, both confirm that the same single slot is free, the system has to explain how it will prevent a double booking.

When thinking about interaction, it helps to distinguish three kinds of message:

  • A command asks the owner to perform an action: "move the booking to 14:00". It can be accepted, rejected or postponed; success should not be treated as fact until it is confirmed.
  • A query asks for information: "which time slots are free tomorrow?". A query on its own should not change state.
  • An event reports a fact that has already happened: "the booking was moved to 14:00". Consumers may use it to trigger other actions, but they must not rewrite the fact as if the event were merely proposing a command.

This does not mean every system must have separate queues, brokers or data models for commands, queries and events. The distinction helps pin down the owner and the expectations.

Command, query and event describe what a message means. Synchronous versus asynchronous interaction describes whether the initiator waits for an answer. These are different dimensions, even though in a real system they combine in a single flow.

In synchronous interaction, the initiator waits for a response before continuing the current scenario. A local function call within one process usually shares memory and passes errors back more simply. A network call adds latency, a timeout, the possibility of losing the connection after the work has actually been done, retries and partial failure: the client may not know whether the server saved the record if its response was lost. That is why turning a local call into a network call is an architectural decision, not a change of address.

Asynchronous interaction lets the initiator carry on before all the subsequent steps have finished. This is useful when an action can be done later or retried, such as sending an email after a booking has been confirmed. But it adds a queue, a pending status, the possibility of redelivery, duplicates, monitoring of backlog and responsibility for a failed job. The user must be shown honestly what has already been confirmed and what is still waiting to be processed.

Event-driven architecture is a style of interaction in which parts announce events and react to them. "BookingRescheduled happened" might trigger a notification and update a report. Events can be passed inside one process or between systems over a network; the name alone does not imply a central broker. The real need may be for a single message, a timer or a background job rather than an event platform.

More important than the pattern is establishing who owns a fact, who can change it, whether the user expects an immediate answer, and how the system recovers from a late, repeated or lost message. Transactions, consistency and protection against data loss are covered in more depth in Data Architecture and Database Migrations.

Monolith, modular monolith, services and event-driven systems

Architectural styles and their trade-offs

No architectural style removes complexity entirely; what differs is the complexity it reduces and the new cost it adds. Software Architecture: The Hard Parts proposes comparing the consequences of boundaries, flows, data ownership and deployment, rather than choosing the number of services by popularity (Neal Ford, Mark Richards, Pramod Sadalage, Zhamak Dehghani, Software Architecture: The Hard Parts, O'Reilly, 2021).

Option What it simplifies What complexity it adds or leaves
Monolithic deployment without clear internal boundaries One build and one deployment can be simple at the start Changing one detail can affect distant parts; it is hard to know the owner and to change internal dependencies safely
Modular monolith Keeps a shared process and deployment, but makes boundaries and permitted interaction explicit Internal rules have to be maintained; a release may still require compatibility across the whole system
Separately deployed services Independent parts can have their own release pace, scale and owner Network, timeouts, partial failures, contract versions, separate logs, alerts, access, deployment and on-call
Event-driven interaction The sender need not know every recipient; work can be deferred and retried State becomes distributed in time; rules are needed for retries, ordering, stale reads, errors and tracing causes

An event-driven system is not mutually exclusive with a monolith or with services. An application can have modules and events inside a single process. Two services can exchange synchronous requests. Do not infer the whole way a system is built from a single tool.

Six services do not mean six useful boundaries

An architecture sketch with "users", "schedule", "booking", "notifications", "billing" and "analytics" services does not explain who determines the state of a booking or who can confirm a slot. If one action needs three services to change state in a single atomic step, the visible network boundaries have not made the requirement any simpler. And if a notification failed to go out, that should not cancel a booking the product has already confirmed.

Separate services make sense when a specific kind of independence is valuable: a separate team can safely release the component on its own schedule; one component has a different load; a failure of its dependency can be contained; it needs to be replaced or scaled independently; and the team is able to diagnose and operate the distributed path. If, however, the parts change together, are owned by one team and have to be released as one unit, a service contract can become an unnecessary operational boundary. Fowler stresses both sides of the trade-off and describes a case where a growing team made decomposition more useful for pace, yet it still cost more person-hours than the monolithic option (Martin Fowler, "Microservice Trade-Offs"). This is an example for reasoning, not a controlled comparative measurement whose result can be carried over to any product.

Ideas from the books: conceptual integrity and the limit of human complexity

Software Architecture: The Hard Parts examines the trade-offs of component granularity, process coordination, contracts, data and operational complexity. Here these dimensions help us compare options for a small system; the book does not give a universal recipe for the number of services (Neal Ford, Mark Richards, Pramod Sadalage, Zhamak Dehghani, Software Architecture: The Hard Parts, O'Reilly, 2021).

The second edition of Team Topologies treats cognitive load as a factor in organisational and technical design. When defining a boundary, a team can ask whether it matches what its members are able to understand and change safely, and whether it supports the interaction needed between teams. At the same time, a team name or a box on an org chart does not by itself define the architecture; owners, the flow of work and communication all change. So the structure has to be tested against real changes and revisited when its cost outweighs its benefit. Working models for teams are discussed in more detail in Human Teams, Expertise and Specialist Agents (Team Topologies, 2nd ed., 23 September 2025).

Brooks called conceptual integrity an important property of a system: its parts should add up to a coherent design with rules the user can understand. He linked this to unity of plan and to separating design from implementation. For booking, this means, for example, that every part must interpret the moment a slot becomes taken in the same way, and that an email failure must not quietly cancel a booking that has already been confirmed (Frederick P. Brooks Jr., The Mythical Man-Month, essay "Aristocracy, Democracy, and System Design").

In another essay Brooks distinguished the essential complexity of a problem from the accidental complexity of its implementation. Essential complexity comes from the problem itself: booking rules, time, concurrent requests and the need not to lose state. Accidental complexity arises from tools, languages and the way something is implemented; better tools can reduce it, but they do not abolish a product rule. His 1980s argument was that no single tool would remove an order of magnitude of the complexity of software development; it is not a claim about modern language models (Frederick P. Brooks Jr., The Mythical Man-Month: Essays on Software Engineering, Anniversary Edition, 2nd ed., 1995, essay "No Silver Bullet—Essence and Accident in Software Engineering").

Applying these ideas to artificial intelligence (AI) agents is our own adaptation, not a conclusion Brooks drew about AI. An agent can help implement a coherent design, but it does not define that design on the product owner's behalf, and it does not prove that a transaction is protected against duplication. If the rules, the source of truth and the consequences of failure are still unknown, a new tool can only implement a wrong assumption faster.

Worked example 1: a booking service built by two people

Consider a teaching scenario: two developers are building a web service in which a client chooses a consultation time and can reschedule an upcoming booking. The first release has no payments and no independent teams. The product owner has agreed one hard condition: a single slot must never be confirmed for two clients. An administrator must be able to see bookings and change availability; a notification after a booking is created may go out with a delay of up to a few minutes. These limits are assumptions of the example, not facts from any real product.

First define ownership and the critical rules

  • The Schedule module describes working hours and the available consultation windows.
  • The Bookings module owns the confirmed booking and changes to its state.
  • The Notifications module is responsible for preparing the confirmation and retrying its delivery.
  • A booking record contains the chosen slot and the client; the system must not confirm two active bookings for the same slot.
  • Before a reschedule, the new time is checked and reserved before the old one is released. If another request has taken the new slot, the client's previous booking remains valid.

These modules can be built as a modular monolith with one relational database. The Bookings module checks and writes the booking in a single transaction: an operation that commits all related changes together or none of them. The exact mechanism for preventing duplicates depends on the chosen database and must be verified against its documentation. In the same transaction, the system saves a job for the Notifications module. A failure before the commit does not leave a booking without its job; after the commit, a background handler can retry delivery.

If the job is stored in a different store, atomicity is no longer guaranteed, and a separate decision is needed for reliable hand-off or compensation. An outage of the external email service does not cancel a confirmed booking: the client sees "Booking confirmed; notification is still being sent", not a false "booking was not created".

Compare two versions of the structure

Option A, a modular monolith: one repository, one main release and internal boundaries; Bookings owns the confirmed booking. Option B, six separately deployed services: accounts, schedule, bookings, payments, notifications and analytics. This option is deliberately excessive for the sake of comparison: the scenario's requirements contain no payments or analytics, so separate services for those areas solve no stated need here. The tables below assess the cost of such a split; they do not propose adding those capabilities.

First, let us mark the requirement "never confirm a double-booked slot" as a pass/fail condition, not a score that convenient deployment can make up for. Both options must show how they enforce and verify it. The rest we compare against the current conditions: a two-person team does not need independent releases, and an email failure must not cancel a confirmed booking.

Question Modular monolith Six separate services
How does a two-person team maintain the system? One main release and a shared database; the internal boundaries must be protected Separate releases are possible, but each service adds deployment and diagnostics
Who protects a booking against a double-booked slot? A single owner can check and write the slot within one transaction An owner and a coordination mechanism must be defined across the schedule and booking boundaries
What happens if email is down? The job is saved together with the booking and can be retried The failure can be isolated, but a network contract, retries and delivery monitoring are needed

The same condition, "never confirm a double-booked slot", remains a mandatory pass, not a score that a more convenient release can offset. To make the basis of the choice explicit, let us score the remaining criteria on a scale from 1 to 5. The weights come only from the limits of the teaching scenario: a two-person team needs maintainable simplicity and has no dedicated operator; there are no independent teams or release schedules yet; an email failure must not break booking.

Criterion Weight and rationale Modular monolith, 1–5 Six services, 1–5
Clear maintenance by two developers 4: the boundary affects everyday change and recovery; there is no separate platform 5 2
Independent release of individual domains 2: there are no separate teams or different schedules today 2 5
Isolation of a notification service failure 3: a notification may be delayed, but the booking is already confirmed 4 4
Transparency of the core booking transaction 5: an error creates a double booking or loses a valid one 5 3
Weighted total, for orientation Weight × score; the double-slot condition is checked separately 61 45

These are not measured properties of platforms and not a mathematical proof of a winner. They are ordered judgements under teaching assumptions, made so that it is visible why the current need favours the modular monolith. If, a year from now, separate schedule and booking teams work to different release schedules, the weight of independence will change; if request volumes and data reveal a distinct bottleneck, the scaling of that specific boundary will need to be examined. For now, the team records the decision "modular monolith with a single owner of booking state", its reasons and the signals for revisiting it. It also keeps the services alternative on file in case the context changes.

The explanatory table and the weighted scoring lead to the same provisional choice: the modular monolith keeps the critical write inside one boundary and does not add a platform this team cannot yet justify. The scores are conditional judgements for this example; the numbers themselves are not a measurement of platforms. If separate teams with different release schedules appear, or measurements reveal a bottleneck, the team should check whether the value of a service boundary has changed and revisit the architecture decision record (ADR).

Worked example 2: a catalogue and an agent's advice to use Kafka

Every hour a supplier sends a full product catalogue file. The public page can either ask the supplier synchronously for the price on every view, or read a local copy that a background job refreshes on a schedule. For now, only this website reads the catalogue.

A synchronous read can return the freshest price, but every page view depends on the supplier's latency and availability. A local copy shields page views from a slow external system, but it can go stale. The team must show the time of the last successful import and must not present old data as fresh.

In this hypothetical scenario, the seller agrees that data shown for browsing may lag by up to 70 minutes. The import must start no later than 10 minutes after the scheduled hour, and the current price is checked before an order is placed. This makes it possible to show the local catalogue and refresh it in the background. If an import fails, the system makes a limited number of retries. Once the limit is exhausted, it keeps the last complete copy, shows its age and marks products as unverified once the freshness limit is passed. Before payment it fetches the current price and shows any change to the customer for approval.

There are three different sources of facts here: the supplier owns the current price, the shop owns the last fully accepted snapshot, and the order owns its own confirmed state. They must not be merged into a single "current price".

Having read a short description, an agent suggests: "To scale, add Kafka and six microservices." This is a recommendation, not proof of need. It does not explain what exactly is growing, where the bottleneck is, or which guarantees are required during a failure. Ask the agent to answer specific questions:

  1. Outcome: when must the import start, how fresh does the catalogue need to be, and may an incomplete file replace the last verified copy?
  2. Facts: how many suppliers and consumers are there; what file size, import time, error rate and longest runs have been measured? If there is no data, say so rather than inventing a load.
  3. Options: a scheduled job inside the application, a separate background worker, or a broker for several independent consumers and replaying history.
  4. Cost: how many dependencies, how much operational work, which retry rules, history, diagnostics and skills does each option add for the team?
  5. Verification: a prototype must handle a typical file and the largest file, a retry after a temporary failure, a schema mismatch, and preservation of the previous complete copy.

Under the stated conditions (one supplier, one consumer and up to 70 minutes of acceptable lag), a scheduled background job with a retry limit is enough; there is no data about millions of products, so that capacity has not been demonstrated. A broker is worth re-evaluating if independent consumers appear, if there is a need to replay history, or if a bottleneck is measured.

This is an example of working with an agent, not an assessment of a specific product or supplier. OpenAI advises increasing complexity gradually and defining quality requirements in advance; Anthropic reminds us that automatically checking a function does not replace human evaluation of system-level requirements (OpenAI, "A practical guide to building agents", reviewed 23 September 2026; Anthropic, "Building effective agents", 19 December 2024). An agent can compare options and check facts, but the decision about product policy remains with the policy's owner.

When to record a decision and when it can wait

Not every library name needs to be kept as an architectural record. A record is needed when a decision defines a boundary of responsibility, a single source of state, the way an important quality is achieved, the risk of an expensive migration, or the reason for a future incompatibility. If a decision is local and easy to change without consequences for other parts, an extra document may cost more than it is worth.

Compare two decisions:

  • "We will use library X to format dates": often a local implementation detail, if it is easy to replace.
  • "Historical bookings store the local consultation time and the time zone identifier, not just the offset from UTC (Coordinated Universal Time, the shared reference scale for comparing moments regardless of local time zone)": this affects storage, rescheduling, display and migrations; changing it later may be expensive.

For an unknown that can be checked quickly, it is sometimes better to build a small prototype: limited, temporary code or an experiment that answers a specific question without pretending to be a finished system. For example, measure the import of a real anonymised file, or check whether a particular database provides the slot constraint you need. Record what the prototype did not test: it may prove that the format can be read, but not that the production refresh schedule is reliable.

Technical debt is not a universal licence to postpone safety. If a team temporarily chooses a simpler option, it records the risk, the owner and the condition for revisiting. For example: "While we have up to 100,000 items and a single background import, we use one table with a timestamp; if the largest import exceeds the agreed window twice in one week, we measure the bottleneck and separately evaluate bulk loading." Every number in such a record must have a source; if it is an agreed assumption, it is labelled as one.

The architecture record: what was decided, why, and what could change it

An ADR (Architectural Decision Record) is a short text about one significant decision. The format is not a magic standard and may vary between teams. Michael Nygard popularised a lightweight record with status, context, decision and consequences; he also stresses that later decisions should reference the one they superseded, so that the history of reasons is kept (Michael Nygard, "Documenting Architecture Decisions", 15 November 2011). The format itself is covered in detail in Architecture Decision Records.

A practical record might contain:

ADR-001: Keep booking confirmation in a single module

Status: accepted

Context:
Two developers maintain the first version. One slot must not be confirmed
for two clients. There are no separate teams with independent release
schedules. A notification may be late, but the booking itself must remain valid.

Decision:
The Bookings module owns the confirmed booking and reserves the slot within
a single transaction. In the same transaction it saves a job for the
Notifications module in the shared database; after confirmation, a background
handler can retry delivery. A separate store would require its own mechanism
for reliable hand-off or compensation.

Alternatives:
Split booking and scheduling across separately deployed services;
keep a monolith without clear internal boundaries.

Consequences:
Positive: one clear owner of state; fewer network hops in the
critical operation.
Negative: the application is released as a single unit; the internal boundary
has to be maintained through code rules and change review.

Evidence and assumptions:
The one-active-booking-per-slot condition comes from the agreed requirements.
Load and a future split of teams have not been measured.

Review conditions:
Revisit the decision if independent teams with different release schedules
appear, or if measurements reveal a distinct, well-founded bottleneck.

This is a teaching example, not a real company's architecture. An ADR does not replace a user guide, an application programming interface (API) contract, a work plan, a requirement or full architecture documentation. Its purpose is to preserve the basis for a decision and to open the door to deliberate revision. Martin Fowler describes an ADR as a short record of a single decision, with context, consequences and status, that helps a future team understand why the system turned out the way it did (Martin Fowler, "Architecture Decision Record", 24 March 2026).

Working order: from a need to a decision you can verify

How to compare architectural options

Before a significant architectural choice, work through this sequence together with the owners of the requirements and the people who will maintain the system:

  1. Name the need and the boundaries. What behaviour and which quality characteristics must the system provide? What is definitely outside the decision for now?
  2. Record the sources and the unknowns. Which fact is confirmed? Which number is an assumption? Can a small investigation replace an argument about guesses?
  3. Find the owner of the data and the actions. Who can confirm or cancel the state? Who only reads a derived copy?
  4. Build several real options. The list should include the simplest sufficient option and, where needed, an option with more isolation. "We'll put in a modern platform" is not a description of an alternative.
  5. Compare consequences. How will release, latency, failure, recovery, consistency, cost, ownership and team load change? Which conclusion rests on numbers, and which on agreed assumptions?
  6. Test the riskiest unknown. Find the official documentation, check the existing code, run a prototype or ask the relevant owner for a decision.
  7. Choose and record the decision. Write down the status, context, choice, alternatives, positive and negative consequences, sources, unresolved conditions and the signal for revisiting.
  8. Verify the result. During implementation, measure or test precisely the properties that were the reason for the decision. If the data contradicts the assumption, do not defend the choice merely because the code has already been written.

If an agent claims "everyone does it this way", ask it to name the need, the source of the claim, at least two realistic alternatives, the consequences and evidence for this particular environment. An answer of "it's best practice" without context is a signal to keep analysing, not an order to accept the decision.

Summary

Architecture helps explain boundaries and consequences; it is not a decoration for a list of technologies. A logical module is not necessarily a service; a single release can contain strong internal boundaries; an event bus is not a synonym for scalability. Identify the source of truth and who is responsible for state. Distinguish ordinary calls, network requests and messages announcing facts. Compare options by confirmed need, the cost of a mistake and the team's ability to maintain the structure.

Start with the simplest sufficient architecture, but not the one that merely looks simplest: if it hides state, duplicates permissions or does not explain recovery, its simplicity is deceptive. Record the reasons for significant decisions, mark the unknowns, test the riskiest assumptions and name the conditions for revisiting.

Sources and further reading

All online sources were checked on 10 October 2026. For books, the edition used is given; the unit restates ideas and describes its own teaching examples rather than reproducing the books' text.

  • Neal Ford, Mark Richards, Pramod Sadalage, Zhamak Dehghani. Software Architecture: The Hard Parts, O'Reilly Media, 2021. Publisher's page and Neal Ford's author page. Used as the basis for analysing trade-offs around granularity, contracts, data and operational complexity; the author page confirms the book and its authorship but does not replace reading it.
  • Matthew Skelton, Manuel Pais. Team Topologies: Organizing Business and Technology Teams for Fast Flow, 2nd ed., IT Revolution, 23 September 2025. Authors' page. Used for the idea of cognitive load as one factor in team and system boundaries; team types are not turned into mandatory modules.
  • Frederick P. Brooks Jr. The Mythical Man-Month: Essays on Software Engineering, Anniversary Edition, 2nd ed., Addison-Wesley, 1995. Essay "Aristocracy, Democracy, and System Design", for conceptual integrity; essay "No Silver Bullet—Essence and Accident in Software Engineering", for essential and accidental complexity. The modern application to agents is marked as our own adaptation.
  • Shopify Engineering. "Deconstructing the Monolith: Designing Software that Maximizes Developer Productivity", 21 February 2019; "Under Deconstruction: The State of Shopify's Monolith", 16 September 2020. The company's primary report on the context, goals, intermediate results and costs of a modular monolith; a self-description by the company, not an independent comparative study.
  • James Lewis and Martin Fowler. "Microservices", 25 March 2014. Joint article on service boundaries and the evolution of architecture.
  • Martin Fowler. "Microservice Trade-Offs", 1 July 2015. The author's analysis of the benefits and costs of a distributed system; some of the examples cited are practitioners' reports to the author.
  • Michael Nygard. "Documenting Architecture Decisions", 15 November 2011. Source of the short record of context, decision, status and consequences.
  • Martin Fowler. "Architecture Decision Record", 24 March 2026. Describes the purpose, form and use of short architecture decision records.
  • OpenAI. A practical guide to building agents, official page checked on 10 October 2026. Used as a general source on growing agent complexity gradually, quality requirements and controlled oversight; not as a specification of any particular Codex client.
  • Anthropic. "Building effective agents", 19 December 2024. The authors' material on simpler composable approaches, evaluation criteria, feedback and the need for human review of system-level requirements; the authors themselves warn that part of the tooling landscape has changed since publication.