Home › Micro Degree Best Practices › Software Architecture and Engineering Best Practices
Best Practice ManualSoftware Architecture and Engineering Best Practices
A practical reference for software architecture work, covering decisions, documentation, quality attributes, reviews, and measures that keep systems changeable over time.
01Purpose and scope
- This manual supports the course Software Architecture and Engineering Best Practices. It describes how architecture work is actually carried out on software teams, from framing the problem and choosing a structure, through recording decisions, to checking after release whether the running system still matches what was decided.
- The scope covers system decomposition, interface and data ownership, deployment topology, and the engineering practices that shape architecture, such as code review, test strategy, and release management. It does not teach a specific programming language, a particular cloud product, or formal verification methods, although those tools appear in examples where they help.
- Practice changes a lot by context. A five-person product team running one deployable service needs a short decision log and a few diagrams. A payments platform with auditors and regulators needs traceable decisions, formal risk reviews, and sign-off paths. An embedded controller team may treat timing and memory limits as the primary architectural driver. The practices here are general, and each section notes where the trade-offs shift and where the guidance runs out.
02Core principles
- Architecture is the set of decisions that are expensive to reverse later. Most day-to-day coding choices can be changed cheaply, so the architecture role is to identify the few choices that cannot, such as how data is partitioned, which protocols cross team boundaries, and where trust boundaries sit. Spending attention on those choices pays off more than debating naming or folder layout.
- Quality attributes drive structure. Latency targets, availability goals, security requirements, cost ceilings, and the speed at which changes must reach production each push the design in a different direction. Name the attributes that matter for the system, rank them, and state them as testable scenarios, for example: under a peak load of a stated request rate, the checkout response must complete within a stated time for most requests. A design that does not respond to ranked attributes tends to be a collection of preferences.
- Keep designs as simple as the stated requirements allow. A distributed system adds network failure modes, versioning problems, observability work, and deployment coordination. Those costs are real even when the component diagram looks clean. Splitting a system into many services is a decision with an operating cost, not a free gain in modularity. Conway's law, which observes that system structure tends to mirror the communication structure of the organization that builds it, is a useful check. If two teams must coordinate every release, the boundary between their components is probably not where the architecture says it is.
03Planning and preparation
- Before proposing structure, collect the inputs that will constrain it. These include functional requirements, expected load and growth, data retention rules, integration points with existing systems, team size and skills, budget, and any mandated platforms. Interview the people who will live with the result, including product owners, operations staff, security reviewers, data owners, and the teams that own upstream or downstream systems. Each group usually holds one constraint that no one else knows about.
- Inventory what already exists. Draw the current system context, list the containers or deployables, record the datastores and their owners, and note known pain points from incident reports and support tickets. A new design that ignores the installed base usually fails at the migration step, not in the diagram.
- Agree on how much design to do up front. A reversible feature inside a stable service may need only a short design note. A new data store, a new external integration, or a change to authentication needs a written decision with alternatives considered. Set the level of detail with the people who will review it, and write that expectation down.
- Typical deliverables at this stage are a context diagram, a short list of quality attribute scenarios with priorities, a constraints list, a risk register with owners, and an empty decision record template. Tools vary by team. Diagramming tools, a shared repository for decision records, and a simple issue tracker are usually enough. The tool matters less than keeping the artifacts in a place people actually read.
04Recommended workflow
-
1
: Frame the problem in one page. State the business outcome, the users affected, the failure that matters most, and what is explicitly out of scope. If the framing cannot fit on one page, the problem is probably two problems.
-
2
: Write the quality attribute scenarios and rank them. Ask the product owner and operations lead to confirm the ranking. Where two attributes conflict, such as strong consistency and low write latency across regions, record which one wins and why.
-
3
: Generate two or three candidate structures. Include at least one option that is deliberately simpler than the others, such as a modular monolith or a single service with a clear internal module boundary. Comparing options against the same scenarios makes the trade-offs visible.
-
4
: Evaluate each option against the scenarios. Use rough estimates where needed, such as request counts per component, data volume per year of operation, or the number of teams that would touch a change. Mark where an estimate is a guess, and decide which guesses need a spike or prototype to check.
-
5
: Run short spikes for the riskiest unknowns. A spike might load-test a single query path, check whether a managed queue supports the required ordering guarantees, or confirm that a vendor's authentication flow works with the existing identity provider. Time-box each spike and write down what it did not answer.
-
6
: Record the decision. Write a decision record that states the context, the options, the chosen option, the consequences, and the conditions under which the decision should be revisited. Link it from the relevant component diagrams and from the pull requests that implement it.
-
7
: Review with the affected teams. Walk through the diagrams and decision record with the owners of interfaces the design touches. Capture objections in writing, resolve them or record them as accepted risks, and get an explicit approval from whoever holds that authority in your organization.
-
8
: Plan the implementation in slices. Break the work into increments that can be deployed and observed, and put the riskiest integration first. Define the migration path from the current state, including how old and new components coexist, how data moves, and how rollback works if a slice fails.
-
9
: Verify in production-like conditions and after release. Confirm that the implemented structure matches the decision record, that the measured behavior meets the scenarios, and that operations can see what they need to see. Schedule a follow-up review at the date or trigger named in the decision record.
05Quality checks, risk controls and compliance
- Architecture reviews are most useful when they check specific things. Look at whether each component has one clear owner, whether each datastore has one service that writes to it, whether interfaces are versioned, and whether failure modes such as timeouts, retries, and partial outages have been designed rather than assumed. A review that asks only whether the diagram looks reasonable will rarely catch a problem.
- Fitness functions are automated checks tied to architectural goals. Examples include a test that fails when a module imports from a package it should not depend on, a performance test that runs against a fixed scenario on every release candidate, or a dependency report that flags libraries with known vulnerabilities. Fitness functions turn architecture rules into something a build can enforce, and they are a common way to keep structure from drifting between reviews.
- Threat modeling should happen during design, not after release. A common approach uses the STRIDE categories of spoofing, tampering, repudiation, information disclosure, denial of service, and elevation of privilege to walk through each data flow and trust boundary. The output is a list of threats, the controls chosen for each, and the residual risk that someone accepts. Security teams in most organizations have their own templates, and those should be used where they exist.
- Regulated and high-risk fields need extra care. Systems that handle health records, financial transactions, tax calculations, or safety-critical control are subject to rules that depend on jurisdiction, sector, and contract terms. Data residency, retention, audit logging, encryption requirements, accessibility obligations such as those described in WCAG, and change approval processes vary by where and for whom the system operates. Check the current requirements with the organization's compliance office and the authoritative regulator guidance for each market. This manual does not replace qualified professional judgment, approved internal procedures, or required authorization from the people who hold it.
- Risk controls should be proportionate and visible. Record each significant risk with an owner, a likelihood and impact estimate in the team's own scale, and a mitigation or an explicit acceptance. Revisit the register when the architecture changes, not only on a fixed calendar.
06Collaboration and documentation
- Decision records are the most durable architecture artifact most teams can produce. A good record is short, dated in the repository history rather than in the body text, and linked to the code it affects. It explains why a choice was made, including the options that were rejected, so that a future engineer does not reopen the same debate without new information. Keep the number of records manageable by recording only decisions that are hard to reverse or that affect several teams.
- Diagrams should show the system at a few useful levels of detail. The C4 model, developed by Simon Brown, describes context, containers, components, and code as separate views. Most teams only need the first two or three levels. Diagrams that try to show everything at once become unreadable and go stale quickly, so attach a date of last review and an owner to each one.
- Ownership maps help teams know whom to call. For each component, record the owning team, the on-call contact, the upstream and downstream dependencies, and the datastores it writes to. Keep this near the code, because a separate wiki page goes out of date faster.
- Architecture work needs regular, small conversations rather than a single large review. A standing design forum with a fixed agenda, a short written proposal template, and a clear rule for who can approve changes to shared interfaces usually works better than ad hoc meetings. Make sure that the people who will operate the system are in the room for decisions about deployment, logging, and failure handling.
07Measures and continuous improvement
- Measure the outcomes architecture is supposed to affect rather than the number of diagrams produced. Delivery measures such as deployment frequency, lead time for changes, change failure rate, and mean time to restore service, as described in the DORA research work, indicate whether the structure supports safe change. Use them as trends within one team's history, not as targets to compare across unrelated systems.
- Structural measures help catch drift. Examples include the number of components that change together in most commits, the count of synchronous calls crossing a given boundary, the share of decisions with a recorded record, the number of shared datastores with multiple writers, and the time it takes a new engineer to make a first change in a component. Each measure needs a stated purpose and a named owner, or it becomes noise.
- Schedule explicit reviews of old decisions. A decision made when traffic was low may no longer hold after a growth period, and the decision record should say which conditions would trigger a revisit. Keep an architecture debt list with the same discipline used for product backlogs, including estimated cost of carrying each item and a plan or explicit decision to leave it alone.
- Retrospectives on significant changes are worth the time. After a migration or a major incident, ask which decisions helped, which were missing information, and which assumptions turned out to be false. Feed the answers back into templates, checklists, and fitness functions, so the next team starts with better defaults.
08Common mistakes and how to prevent them
- Splitting into services before the team and domain boundaries are clear is a frequent error. The result is a distributed monolith, where components still change together but now require network calls and separate deployments. Prevent it by building a modular structure first, measuring which boundaries hold under change, and extracting services only where the benefit is clear.
- Choosing technology because it is fashionable rather than because it fits the scenarios leads to expensive and fragile systems. Prevent it by requiring every technology choice to name the quality attribute it serves and the operational cost it adds. If no scenario benefits, the choice needs a stronger justification.
- Leaving data ownership ambiguous causes most long-running integration problems. When two services write to the same table, nobody can change the schema safely. Prevent it by assigning one writer per dataset and exposing reads through a defined interface or a replicated copy with a stated freshness guarantee.
- Ignoring operations until release produces systems nobody can run. Missing logs, unclear health checks, and no runbooks turn small failures into long incidents. Prevent it by including the operations lead in design reviews and making observability and rollback part of the definition of done.
- Writing decisions after the fact yields records that describe the chosen answer without the reasoning. Prevent it by drafting the record before implementation starts and updating it when the decision changes.
- Attempting a full rewrite as one event is high risk and rarely finishes on schedule. Prevent it by planning an incremental path with a working system at each step, and by keeping the old and new components running together until the new one has proven itself.
09Practical checklist
- Confirm the business outcome, users, and out-of-scope items are written on one page before proposing a structure.
- Rank the quality attributes and state each one as a testable scenario agreed with product and operations.
- Compare at least two candidate structures, including one simpler option, against the same scenarios.
- Identify the riskiest unknowns and time-box a spike for each before committing.
- Record the decision with context, options, consequences, and a revisit trigger, and link it from the affected components.
- Review the design with owners of every interface it touches and capture objections in writing.
- Confirm each datastore has one writer and each component has one owning team.
- Run threat modeling on new data flows and trust boundaries, using the security team's template where one exists.
- Check regulatory obligations for the jurisdiction and sector with the compliance office and current authoritative sources.
- Add fitness functions or automated checks for the rules the architecture depends on.
- Track delivery and structural measures as trends, and schedule the follow-up review named in the decision record.
★Learning sources
- Micro Degree: Software Architecture and Engineering Best Practices
- Micro Degree: Data Analytics Applications
- Micro Degree: Azure Cloud Fundamentals
- Micro Degree: Google Cloud Platform Basics
?Frequently asked questions
How much architecture documentation is enough for a small team?
A small team usually needs a context diagram, a short list of quality attribute scenarios, and a decision log with one entry for each hard-to-reverse choice. Keep the records in the same repository as the code so they are updated with the changes they describe. Expand the documentation only when a new person or another team regularly asks questions the existing records cannot answer.
When should a team split a system into separate services?
Split when the boundary is already stable in the code, when different parts need to scale or be released at different rates, or when team ownership makes independent deployment clearly worth the operating cost. If components still change together and require coordinated releases, a modular structure inside one deployable is usually the better choice. Make the split decision with the operations lead present and record the expected cost and the conditions for reconsidering it.
How do I handle architecture decisions in a regulated industry?
Record decisions with the same structure used elsewhere, and add the compliance and security requirements that constrained each option. Requirements such as data residency, retention, audit logging, and approval for changes depend on jurisdiction and sector, so confirm them with the compliance office and the current authoritative guidance for each market. This guidance does not replace qualified professional judgment, approved internal procedures, or required authorization.
What if leadership wants a fast decision and I do not have enough information?
Explain which unknowns matter most and propose a short spike that answers the most decision-relevant one within a stated time limit. Present the options with their trade-offs and say which decision could be made now and which could be deferred without blocking the work. Record the decision as provisional with the conditions under which it will be revisited, so the speed does not become a permanent commitment.
This manual explains practice in general terms. It does not replace qualified professional judgment, approved procedures or required authorization.
Technical Best Practices
China, India, United States, Indonesia, Pakistan, Nigeria, Brazil, Bangladesh, Russia, Japan, Mexico, Philippines, Vietnam, Egypt, Turkey, Germany, United Kingdom, France, Italy, Canada, South Korea, Australia, Spain