InterviewKata
Software architecture · Level 2: Structure · Module 07a of 46

Inside a service: SOLID, patterns, hexagonal

Architecture styles decide how services fit together; this module is about what happens inside one. A senior engineer becomes an architect partly by seeing that the same few ideas work at both scales: depend on abstractions, keep business rules away from frameworks, and group code by what changes together. Get these right and a service can swap its database, its payment provider or its web framework without touching a single business rule.

See it builtThe Architecture styles track builds these styles for one bike-share app, with code you can run: 03 Hexagonal (ports and adapters) · 04 Clean and onion.

1. The idea

A kitchen has sockets on the wall. The kettle, the toaster and the blender each have a plug that fits the socket. You can replace the toaster with a better one, or plug in a tester to check the wiring, without calling an electrician. Now imagine a toaster wired straight into the wall: replacing it means opening the wall. Business rules wired straight into a database driver or a payment SDK are that toaster.

Three groups of ideas do most of the work inside a service.

SOLID: five rules for code that changes safely

PrincipleIn one sentenceSymptom when it's broken
Single responsibilityA class has one reason to change: it serves one actor (one part of the business).A tax-rule change and a report-format change both edit the same class.
Open–closedAdd behaviour by adding code, not by editing working code.Every new payment method adds another case to a switch in five places.
Liskov substitutionA subtype must work wherever its parent is expected.Callers check if (x instanceof ReadOnlyList) before calling add.
Interface segregationDon't force a client to depend on methods it doesn't use.A reporting job depends on a 40-method OrderService to call one getter.
Dependency inversionHigh-level policy and low-level detail both depend on an abstraction that the policy owns.The checkout rules import the Stripe SDK; tests need a network.

The design patterns architects actually use

Of the 23 patterns in the 1994 "Gang of Four" book, a handful come up constantly, and each has a twin at architecture scale. Knowing both helps you explain a big design with a small, familiar idea.

PatternProblem it solves in codeThe same idea between services
StrategySwap an algorithm at runtime (pricing rules per country)Plug-ins in a microkernel (Module 12)
AdapterMake an awkward interface look like the one you wantAn anticorruption layer in front of a legacy system (Module 08a)
FacadeOne simple entry point to a messy subsystemAn API gateway or aggregator (Module 21)
DecoratorAdd logging, caching or retries around an object without changing itMiddleware and sidecars (Module 16)
ObserverNotify others when something happens, without knowing whoPublish/subscribe and events (Module 14)

Hexagonal, onion and clean: one rule, three drawings

Hexagonal architecture, or ports and adapters (Alistair Cockburn, 2005), puts the application in the middle. It defines ports: interfaces in its own terms, such as PaymentPort.charge(order) or OrderRepository.save(order). Adapters outside implement them for a real technology (Stripe, PostgreSQL) or drive the application (a web controller, a test, a batch job). Onion (Jeffrey Palermo, 2008) and clean architecture (Robert C. Martin, 2012) draw the same thing as rings. They share one rule, which Martin calls the Dependency Rule: source-code dependencies only point inwards. The domain knows nothing about the web, the database or any vendor.

Vertical slices: group by feature, not by layer

Classic layering puts all controllers in one folder, all services in another and all repositories in a third. But a feature change usually touches one of each. Vertical slice architecture (Jimmy Bogard, 2018) groups the code for one request together, from endpoint to query. His rule: minimise coupling between slices and maximise coupling inside a slice. It's domain partitioning (Module 08) applied inside a single service.

2. How it works

Swapping the payment provider

A checkout service charges cards through a port it owns. Watch what changes when the business moves from Stripe to Adyen, and what a test plugs into the same port.

One feature, two code layouts

The product team asks for discount codes at checkout. Count the places that change.

3. Try it yourself

Which way do the arrows point?

An order service with a controller, a use case (OrderService), a domain entity (Order), two ports and two adapters. Choose how the code is wired. Red arrows point outwards and break the Dependency Rule; yellow ones are allowed but skip the business rules.

4. Trade-offs

Abstraction vs indirection

A port lets you swap and test a dependency. It also adds a file and a jump for every reader. Put ports where change or testing is likely: payment, messaging, storage, other teams' services. Not around your own utility functions.

Hexagonal vs plain layers

For a small CRUD service, controller → service → repository is fine and easy to read. Hexagonal earns its keep when business rules are rich, long-lived, or must run against several channels and providers.

Patterns vs simplicity

A strategy for one algorithm is ceremony. Add the pattern when the second variant arrives, not in case it might. Patterns are names for solutions, not goals.

Slices vs shared code

Slices keep a feature's code together and let each use the simplest approach. They also duplicate small things between slices. Share the domain model; let slices duplicate glue code.

5. In real projects

Framework-first projects drift into the toaster problem. Generators create controllers/, services/ and models/, and ORM annotations sit on the domain classes. That's fine early. When unit tests start needing a database container, or a vendor change touches business code, introduce ports at those two places first.

The composition root. In hexagonal code, one place (application start-up, or a dependency-injection container) decides which adapter plugs into which port. Swapping Stripe for Adyen, or production for a test fake, is a change there only.

Architecture checks keep the rule true. Tools such as ArchUnit (Java), NetArchTest (.NET), import-linter (Python) or dependency-cruiser (JavaScript) fail the build when domain code imports an adapter: a fitness function (Module 26).

Say it in the interview

Two-sentence answers
  • "How would you structure this service?" Business rules in the middle with no framework imports, ports for the database and the payment provider, adapters outside. The Dependency Rule keeps arrows pointing inwards, so rules are testable in milliseconds and vendors are swappable.
  • "What does dependency inversion buy you?" The business code owns the interface and the vendor code implements it, so a vendor change or a test fake touches the edge, not the rules.
  • "Layers or vertical slices?" Slices when most changes are per feature, which is most of the time; shared layers only for what really is shared, like the domain model.
Red flags interviewers notice
  • Business rules that import a database driver, an HTTP framework or a vendor SDK.
  • An interface for every class "for testability", with one implementation each.
  • Naming patterns instead of explaining the problem they solve.

6. Remember it

Memory hook: "Plugs point in"Business rules own the sockets; every database, vendor and test is a plug that points inwards.
How this hook works: what "plugs point in" means

What the slogan means, phrase by phrase

"Plugs"
Adapters: the code that talks to PostgreSQL, Stripe, HTTP or a test.
"point in"
The Dependency Rule: source dependencies point towards the domain, never away from it.

Why this picture fits: the socket is shaped by the house, not by the toaster; the port is shaped by the business, not by the vendor.

The story → the concept

The kitchenThe code
Socket in the wallPort (an interface the domain owns)
Toaster's plugAdapter (Stripe, PostgreSQL)
Socket testerTest fake
Toaster wired into the wallBusiness code importing a vendor SDK

The trap it saves you from

Letting the framework or a vendor shape your business code, so every outside change becomes an inside change.

7. Common mistakes

Ports shaped like the vendorPaymentPort.createStripeCharge(customerId, source) is still Stripe's model. Name ports in business terms: charge(order, amount).
Interfaces everywhereAn interface with one implementation and no test fake is indirection without benefit. Abstract at boundaries.
The anemic domainEntities with only getters and setters, and every rule in "service" classes. The domain ring is then empty, and the rules end up in controllers.
Pattern huntingAdding a factory, a builder and a visitor to a 200-line service. Use a pattern when its problem appears.

8. Design exercise (30 minutes)

  1. Draw the rings (10 min). Take a service you know. Put each package or folder in a ring: domain, use cases, adapters. Draw its imports.
  2. Find the outward arrows (10 min). Mark every import from an inner ring to an outer one. Which would hurt most if the vendor or framework changed?
  3. Design one port (10 min). For the worst one, write the port's interface in business words, the adapter that implements it, and the fake a test would use.

9. Self-check quiz

What is the Dependency Rule in clean and hexagonal architecture?

Inner rings know nothing about outer ones; adapters depend on ports, never the reverse.

In ports and adapters, who owns the shape of the PaymentPort interface?

Dependency inversion: the policy owns the abstraction; the vendor adapter implements it.

Every new shipping carrier means editing a switch statement in five classes. Which principle is broken?

New behaviour should arrive as new code (a strategy per carrier), not as edits to working code.

Which code-level pattern is an anticorruption layer the architecture-scale version of?

Both translate someone else's interface into the one your code wants.

What does vertical slice architecture do?

Bogard: minimise coupling between slices, maximise coupling within a slice.

10. Go deeper

ResourceTimeWhy
Hexagonal architecture (Alistair Cockburn)20 minThe original ports-and-adapters article.
The Clean Architecture (Robert C. Martin)10 minThe four rings and the Dependency Rule.
Vertical Slice Architecture (Jimmy Bogard)10 minGrouping by request instead of by layer.
Design patterns catalogue (Refactoring.Guru)referenceEvery Gang of Four pattern with diagrams and code in several languages.