Skip to content

Software Architecture & Design

Architecture is the set of decisions that are expensive to change later. Get them roughly right and your system stays aligned with your goals, absorbs new requirements gracefully, and survives the inevitable churn of technology and team. Get them wrong and every feature fights the structure. You don’t need to be the one drawing the diagrams as a manager, but you do need the vocabulary to follow the conversation and ask good questions. Here are the foundations I lean on.

Architecture styles

Architecture styles are the foundational blueprints for building software systems. Each one makes different trade-offs, and the right choice depends on the needs, constraints, and context of what you’re building — not on what’s fashionable. The most common styles:

  • Monolithic — The entire application is built and served as a single unit; all functionality lives in one place.
  • Service-Oriented (SOA) — The system is divided into individual services, each owning specific functionality and communicating with the others, which promotes reuse and lets each service be managed independently.
  • Component-Based — The software is assembled from modular components, each providing a specific capability, that can be replaced, updated, or modified without disturbing the rest of the system.
  • Distributed Systems — Components are spread across multiple machines or networks but present a unified service, improving scalability and reliability.
  • Event-Driven — Components react to events or messages, acting when they receive specific notifications. This makes the system reactive and well-suited to asynchronous work.
  • Interpreter — High-level code is translated to machine instructions line by line and executed directly rather than compiled ahead of time, trading performance for flexibility.
  • Data-Centric — The system is organized around managing and using data, optimizing integrity, storage, and retrieval, with functionality built atop efficient data processing.

There’s no “best” style — only the one that fits. Most real systems blend several, and a distributed, microservice-based one usually lands on the cloud-native infrastructure the next page covers.

The 12-Factor App

The 12-Factor App is a concise methodology for building modern, portable, cloud-native applications. It’s old enough now to be assumed knowledge, and it still holds up.

#FactorWhat it meansTL;DR
1CodebaseOne codebase tracked in version control, many deploysOne repo per app
2DependenciesDeclare and isolate dependencies explicitlyNo global installs
3ConfigStore config in environment variablesNo secrets in code
4Backing ServicesTreat services (DBs, queues, etc.) as attached resourcesSwap without code change
5Build, Release, RunSeparate the build, release, and run stagesImmutable releases
6ProcessesExecute the app as one or more stateless processesNo sticky sessions
7Port BindingSelf-host via a port; don’t rely on an external serverApp runs standalone
8ConcurrencyScale out via the process modelRun more instances
9DisposabilityFast startup and graceful shutdownResilient to crashes
10Dev/Prod ParityKeep environments as similar as possibleAvoid “works on my machine”
11LogsTreat logs as event streamsWrite to stdout/stderr
12Admin ProcessesRun admin tasks as one-off processesOne-off commands for migrations, fixes

Together these are a checklist for portability, scalability, and maintainability across environments.

SOLID principles

SOLID is a set of five design principles for writing maintainable, scalable, and robust object-oriented code. They’re not rules to recite — they’re a shared diagnosis language for why a class is painful to change.

PrincipleWhat it saysTL;DR
SSingle ResponsibilityA class should have one reason to change — it should do one thing.One job per class
OOpen/ClosedEntities should be open for extension but closed for modification.Add features without editing existing code
LLiskov SubstitutionSubclasses should be substitutable for their base classes without breaking behavior.Child classes behave like their parents
IInterface SegregationClients shouldn’t be forced to depend on interfaces they don’t use.Small, focused interfaces
DDependency InversionDepend on abstractions, not concretions; high-level modules shouldn’t depend on low-level ones.Use interfaces and dependency injection

Followed together, SOLID leads to cleaner, more testable, more flexible codebases.

💡
I treat SOLID as a vocabulary for code review, not a gate. When a reviewer says “this violates the Single Responsibility Principle,” what they usually mean is “this class will be a nightmare to change six months from now.” Naming the principle turns a vague unease into a specific, actionable conversation — and that’s the whole point of having shared language.

Design patterns

Design patterns are proven, reusable solutions to common problems in software design. They aren’t copy-paste blueprints; they’re templates for structuring code to solve a recurring type of problem in a maintainable, flexible way.

The classic set comes from the Gang of Four (GoF) — four authors who cataloged 23 object-oriented design patterns in their 1994 book, Design Patterns. (More on the Gang of Four.)

Why they’re worth knowing:

  • Consistency — Teams share a common pattern language.
  • Maintainability — They encapsulate varying behavior and simplify future change.
  • Scalability — They encourage loosely-coupled, well-organized code.
  • Best practices — They package decades of collective experience.

The patterns fall into three categories: Creational (object creation), Structural (object composition), and Behavioral (object interaction).

Creational

PatternWhat it doesExample
SingletonEnsure only one instance exists and is globally accessible.A single Logger instance.
Factory MethodLet subclasses decide which class to instantiate.Creating MySQL or PostgreSQL connections based on config.
Abstract FactoryCreate families of related objects without naming concrete classes.A UIFactory producing Windows or Mac controls.
BuilderConstruct a complex object step by step.Building a House with optional features.
PrototypeCreate new objects by cloning a prototype instance.Duplicating a document template.

Structural

PatternWhat it doesExample
AdapterConvert one interface into another clients expect.Wrapping a legacy payment API to match your interface.
CompositeTreat individual objects and compositions uniformly.File-system directories and files in a tree.
ProxyControl access to an object or defer its initialization.Lazy-loading a high-resolution image.
DecoratorAdd responsibilities to objects dynamically.Adding encryption to a data stream.
BridgeDecouple an abstraction from its implementation.Separating UI code from platform-specific drawing APIs.

Behavioral

PatternWhat it doesExample
StrategyEncapsulate interchangeable algorithms.Selecting quicksort or mergesort at runtime.
ObserverNotify dependent objects when state changes.UI components updating when data models change.
CommandEncapsulate a request as an object to queue it or support undo.Undo/redo in a text editor.
StateAlter behavior when an object’s internal state changes.A vending machine switching between “Waiting” and “Dispensing.”
Template MethodDefine the skeleton of an algorithm, deferring steps to subclasses.A test framework’s setup/teardown sequence.

For a complete, illustrated catalog of the classic patterns and more, see Refactoring.Guru — Design Patterns Catalog.

📚 Go Deeper

Books

Tools

Last updated on