Mert Özen Further With Every Line
Software Architecture Introduction to Software Architecture — #01

What Is Software Architecture? How It Differs from Design and What an Architect Does

Mert Özen Nov 7, 2026 12 min 5 views
What Is Software Architecture? How It Differs from Design and What an Architect Does

Software architecture is the set of decisions that shape a system's important properties and are expensive to undo; between it and design there is no hard line but a spectrum from strategic to tactical. The SEI, ISO/IEC/IEEE 42010 and Fowler/Johnson definitions, FoSA's three laws, the architect's role and the Bookstore example used across the category.

Version note: The code in this post was built and run with C# 14 and .NET 10.

Guess First

Today the ordering side of Bookstore works roughly like this: an order is saved, the customer gets a link to track it, and the payment provider receives a reference number it can match the order with. The essence of the code:

Order order = new(0, "Ada", 44.47m);
OrderStore store = new();
int id = store.Place(order);

Console.WriteLine(OrderLinks.ForCustomer(id));
Console.WriteLine(PaymentReference.For(id));

public record Order(int Id, string CustomerName, decimal Total);

public sealed class OrderStore
{
    private readonly List<Order> _orders = [];
    private int _lastId = 1041;

    public int Place(Order order)
    {
        int id = ++_lastId;
        _orders.Add(order with { Id = id });
        return id;
    }

    public Order? Find(int id) => _orders.FirstOrDefault(o => o.Id == id);
}

public static class OrderLinks
{
    public static string ForCustomer(int orderId) => $"https://bookstore.example/orders/{orderId}";
}

public static class PaymentReference
{
    public static string For(int orderId) => $"BKS-{orderId:D8}";
}
https://bookstore.example/orders/1042
BKS-00001042

Two tasks arrive in the same week. The first: the CustomerName property becomes BuyerName, because the product team now separates the buyer from the payer on gift orders. The second: the order number becomes a Guid instead of an int. A sequential number is guessable from outside and tells competitors how many orders you take per day; the IDOR (Insecure Direct Object Reference) post in the OWASP (Open Worldwide Application Security Project) Top 10 series and the "Exposing Ids" post in the Solution Patterns series covered exactly this.

Two questions: which of these tasks is an architecture decision? And how many errors does the compiler report for the second change; once they are gone, is the job done?

What the Compiler Shows and What It Doesn't

The first task is a rename. You press rename on the property in the IDE (Integrated Development Environment), and since every usage lives in the same compilation, they all change together. Both the risk and the impact fit in one commit. For the second task, let's switch Order and OrderStore to Guid and leave the rest alone:

Order order = new(Guid.Empty, "Ada", 44.47m);
OrderStore store = new();
Guid id = store.Place(order);

Console.WriteLine(OrderLinks.ForCustomer(id));
Console.WriteLine(PaymentReference.For(id));

public record Order(Guid Id, string CustomerName, decimal Total);

public sealed class OrderStore
{
    private readonly List<Order> _orders = [];

    public Guid Place(Order order)
    {
        Guid id = Guid.CreateVersion7();
        _orders.Add(order with { Id = id });
        return id;
    }

    public Order? Find(Guid id) => _orders.FirstOrDefault(o => o.Id == id);
}
Program.cs(5,42): error CS1503: Argument 1: cannot convert from 'System.Guid' to 'int'
Program.cs(6,40): error CS1503: Argument 1: cannot convert from 'System.Guid' to 'int'

Two errors. Change the parameters of OrderLinks.ForCustomer and PaymentReference.For to Guid and the project compiles. Let's run it:

https://bookstore.example/orders/01a1b7aa-2de1-7748-9b39-fc2bf5c1f263
Unhandled exception. System.FormatException: Format string can be only "D", "d", "N", "n", "P", "p", "B", "b", "X" or "x".
   at System.Guid.ThrowBadGuidFormatSpecification()
   ...
   at PaymentReference.For(Guid orderId) in p01b/Program.cs:line 31

The compiler does not check the format string in {orderId:D8}; "pad to eight digits with zeros" makes sense for an int, is invalid for a Guid, and blows up at runtime on the first order. But that is still the easy part. The hard parts are not in the code at all: the /orders/1042 links sitting in customers' mailboxes, tens of thousands of transactions stored as BKS-00001042 at the payment provider, the accounting report written on the assumption that an order number is an eight-digit number, the support process where an agent asks a caller to read out their order number. Try having someone read a thirty-six-character Guid over the phone.

So the decision "the order number is a sequential int and is exposed as is", which looked like a type choice in a record, was an architecture decision. Not because of where it sits in the code, but because of the cost of changing it: its effect has crossed the application boundary and spread to customers, the payment provider and other teams. The rename is a design-level decision. The rest of this post is about making that distinction clear.

What Is Software Architecture? Three Definitions

There is no single accepted definition, but three of them side by side give a clear picture.

The SEI (Software Engineering Institute, Carnegie Mellon University) definition, from Bass, Clements and Kazman's Software Architecture in Practice: the architecture of a system is the set of structures needed to reason about it, each made of software elements, the relations among them and the properties of both. Two things make this definition useful. First, it is plural: there is no single architecture diagram; the module structure (which code lives where), the runtime structure (which process calls which) and the deployment structure (what is installed where) are separate, and all of them are part of the architecture. Second, the emphasis on reasoning: if a structure does not help you say something about the system's performance, security or modifiability, it is not architecture.

ISO/IEC/IEEE 42010, the joint architecture description standard of ISO (International Organization for Standardization), IEC (International Electrotechnical Commission) and IEEE (Institute of Electrical and Electronics Engineers), is more abstract: the fundamental concepts or properties of a system in its environment and the principles that govern its realization and evolution. The key word is evolution: architecture is not drawn once and left alone, it also governs how the system changes.

The third comes from Martin Fowler's "Who Needs an Architect?", published in IEEE Software in 2003. Fowler relays an objection Ralph Johnson (one of the GoF (Gang of Four: Gamma, Helm, Johnson and Vlissides, authors of the 1994 Design Patterns book)) posted to a mailing list. For Johnson there is no "highest level concept of a system"; the customer's concept differs from the developer's. Architecture is the shared understanding that the expert developers on a project have of the system's design, and what makes a component significant is that the experts consider it significant. Johnson offers a second definition too: the decisions you wish you could get right early in a project. Fowler takes it one step further: why do people want to get those decisions right early? Because they perceive them as hard to change. Architecture thus becomes "things that people perceive as hard to change". Johnson's summary has been quoted for years: "Architecture is about the important stuff. Whatever that is."

Where the three definitions overlap gives a practical test: a decision is architectural if it determines an important property of the system (performance, security, modifiability) and is expensive to undo. The order number at the start was exactly that.

Architecture and Design: A Spectrum

"Is this architecture or design?" usually has no yes/no answer. Mark Richards and Neal Ford's Fundamentals of Software Architecture (2nd edition, O'Reilly, 2025; FoSA from here on) describes the two as ends of a spectrum and suggests three questions to place a decision on it: is it strategic or tactical (how much thought and planning it needs, how many people it concerns), how much effort it takes to undo, and how significant its trade-offs are.

01-architecture-design-spectrum-en.png

The method name on the left is purely tactical: one person decides and undoing it takes seconds. Whether to use a repository sits near the middle; as we saw in the ASP.NET Core Web API (Application Programming Interface) series post "Layered Structure: The Service Layer and the Repository Debate" and the Entity Framework Core (EF Core) series post "EF Core and Architecture", once it becomes a team convention it gets harder to change. Whether the order email is sent synchronously or through a queue changes how the system reacts to failure; the fourth post of this series is devoted to that decision. On the far right are the database engine and the question of one deployment versus several services: undoing those takes months.

This view has two practical consequences. First, it cures the "architecture = folder structure" misconception. Controllers, Services and Repositories folders are not an architecture, at most the trace of an architecture decision; the real question is how those folders depend on each other and what those dependencies make easy or hard. Second, a decision's position is not fixed. In the same article Fowler describes how migration tooling that made schema changes easy took the database schema "out of the architecture". One of an architect's most valuable jobs is pulling decisions to the left of the spectrum by reducing irreversibility. In the opening example that would have meant separating the customer-facing order number from the database key from day one.

An Example from .NET: Why Is string Still UTF-16?

.NET itself is a live example of this distinction. In 2002 System.String was designed as an immutable type made of UTF-16 (Unicode Transformation Format, 16-bit) code units. Today most of the web speaks UTF-8, and for most text UTF-16 means twice the memory:

using System.Text;

string title = "Les Misérables";
ReadOnlySpan<byte> utf8Title = "Les Misérables"u8;

Console.WriteLine($"string: {title.Length} UTF-16 code units, {title.Length * sizeof(char)} bytes");
Console.WriteLine($"u8 literal: {utf8Title.Length} bytes");
Console.WriteLine($"Encoding.UTF8: {Encoding.UTF8.GetByteCount(title)} bytes");
string: 14 UTF-16 code units, 28 bytes
u8 literal: 15 bytes
Encoding.UTF8: 15 bytes

Even so, the internal representation of string has not changed and will not. Millions of lines of code rely on string.Length returning the number of UTF-16 code units, on char being 16 bits, on direct memory access through fixed (char* p = s). The decision is architectural because it has spread through the entire ecosystem. Instead of changing the representation, the .NET team added new paths next to it: Span<byte>-based UTF-8 APIs, System.Text.Json built on UTF-8 from the start, and the u8 literals that came with C# 11. That is the typical way of living with architecture decisions: if you can't undo a decision, you open a new path that limits its impact.

The Laws of Architecture

FoSA opens with three observations it calls the "laws" of architecture. The first: everything in software architecture is a trade-off. The corollary the book adds matters too: if you think you have found something that isn't a trade-off, you most likely just haven't identified the trade-off yet. Moving the order email to a queue speeds up checkout but raises the chance of losing the email and the complexity of the system. We will see that with real measurements in the fourth post.

The second: why is more important than how. You can always work out how a system is built by reading the code; you can't work out why it was built that way. When someone joins the project three years later and asks "why is the order number an int?", the answer is not in the code. ADRs (Architecture Decision Records), the standard way to write down the reasoning behind decisions, are covered in detail in the upcoming Architecture Decisions series.

The third was added in the second edition: most architecture decisions are not binary; they sit on a spectrum between two extremes. People ask "monolith or microservices?", but in between are the modular monolith, service-based architecture, splitting out just one piece and many other points. The sixth and seventh posts of this series map those points.

What Does an Architect Do?

FoSA expects eight things from an architect: make architecture decisions, continually analyze the architecture and the technology environment, keep up with current trends, ensure decisions are followed, have exposure to diverse technologies, know the business domain, have strong interpersonal skills, and navigate the organization's politics. Note that half the list is not technical.

In the same article Fowler compares two kinds of architect. Architectus Reloadus (named after the Architect in the film Matrix Reloaded) makes all the important decisions alone, either to protect the system's conceptual integrity or because they don't trust the team. Architectus Oryzus is inside the project: in the morning they pair with a developer to extract some shared locking code, in the afternoon they explain the technical cost of an idea to the product team in a requirements session. For Fowler the most important job of this second type is growing the team, and an architect's value is inversely proportional to the number of decisions they make. The more decisions can safely be left to the team, the better the architect has done.

The industry's name for the first type is the ivory tower architect anti-pattern: someone detached from the code, drawing diagrams but not living with the consequences. The symptoms are familiar: decisions arrive without reasons, the team quietly works around them, and the architect's diagram and the system in production drift apart. The antidote is simple: the architect keeps writing code, but not on the critical path. FoSA's advice is to pick work that won't block the team: proofs of concept, technical debt and bug fix work, small automation tools and fitness functions (tests that check the architecture automatically), code reviews. We will see fitness functions in the last post of this series.

One more note: "architect" doesn't have to be a title. In a team of three, architecture decisions are made by the most senior person, sometimes by everyone together. This series is for everyone who has to pick the type of the order number without "architect" on their business card.

The Series Example: Bookstore Grows

Throughout this category we follow one system: the Bookstore you know from the site's C#, EF Core, Dapper, OWASP and Software Design series. It is an online shop that sells books; Book, Author, Customer, Order and OrderItem are the same types, and the namespaces are still Bookstore.*. What changes is the point of view: so far we looked at a single class or a single query; in this series we look at the system as a whole.

Where it stands today: one ASP.NET Core application and one SQL Server database, three developers who deploy once a week and a part-time ops colleague. Users fall into three groups. Customers search for books, place orders and track them on the web site. Internal users are the catalog editor, the warehouse clerk and customer support. External systems are the payment provider, the email provider and the courier's API. The numbers are known as well: about 40,000 books, 15,000 unique visitors and 800 orders a day; during the back-to-school and year-end campaigns traffic grows tenfold and peaks at 120 orders per minute. The eighteen-month goal: triple the order volume, open in a second country and ship a mobile app that uses the same API.

Functionally, the system splits into six business areas. We will use these names throughout the category:

AreaNamespaceResponsibility
CatalogBookstore.CatalogBooks, authors, categories, search and price display
OrderingBookstore.OrderingCart to order, checkout and order status
InventoryBookstore.InventoryStock, reservations and warehouse receipts
PaymentBookstore.PaymentsAuthorize, capture and refund with the payment provider
ShippingBookstore.ShippingShipping labels and tracking numbers
NotificationBookstore.NotificationsOrder confirmation and shipping emails, SMS (Short Message Service)

This table is not an architecture, it is a starting point. Whether these areas live in the same process, talk to each other synchronously or share the same database are decisions we will make in the rest of the series. The next step is to pin down what we mean by "a good system": quality attributes, the subject of the next post.

Common Pitfalls

Mistaking architecture for a folder structure or a project template. Copying a Clean Architecture template gives you a folder layout, but it doesn't tell you which characteristics you prioritize and why. Choosing the template is a decision too, and its reasoning should be written down.

Thinking only big decisions are architectural. The type of the order number, the name of a JSON (JavaScript Object Notation) field, the shape of a public URL (Uniform Resource Locator) look small; but the moment they cross the system boundary they become expensive to undo. Treat everything that crosses a boundary like an architecture decision.

Treating every decision as architectural. The opposite is a trap too: teams that hold a meeting for every method name stop delivering. Separate architectural decisions by the cost of undoing them; if it's cheap, leave it to the team.

The architect stops writing code. An architect detached from the code doesn't see the cost of their decisions, and the team starts quietly working around them. Stay close to the code with work that is off the critical path but keeps you familiar with the system.

Not writing down the "why" of a decision. Today everyone knows; six months later nobody remembers. At least a one-paragraph note: what was chosen, what the alternatives were, which trade-off was accepted.

A Small Experiment

★☆☆ List five decisions made in your project in the last month and place each on the spectrum: who decided, how many days it would take to undo, which trade-off was accepted? Is the reasoning behind the rightmost decision written down anywhere?

★★☆ Redesign the opening example so the customer-facing order number is separate from the database key: add a separate OrderNumber to Order and make the link and the payment reference use only that. Then change the type of Id and see how many errors the compiler reports this time, and that nothing exposed to the outside changes.

★★★ Find a decision in a .NET API you use that could not be changed because of backward compatibility and got a new path next to it instead (hint: DateTime and DateTimeOffset, HttpWebRequest and HttpClient, BinaryFormatter). Why was the old decision architectural, and how does the new path limit its impact?

We said architecture is about "the important stuff". In the next post we make "important" concrete: using the quality attributes of ISO/IEC 25010:2023, we decide which architecture characteristics really matter for Bookstore.