Practical

The Technical Toolkit

The bridge from theory to practice. You understand the architecture - here's what you need to implement it.

A note on examples. The articles in this section use C# and Unity for all code samples - that’s the stack our team works with daily. The architectural principles are engine-agnostic, but the practical patterns (DI containers, async primitives, test harnesses) are inevitably shown in a specific language. If you’re working in Unreal, Godot, or another engine, the concepts should translate directly with equivalent mechanisms.

The Gap Between Theory and Practice

The Architecture Model describes a clean, layered system where dependencies point inward and features interact through well-defined interfaces. The Game Architecture Difficulties topic describes what happens without it.

But knowing the architecture isn’t enough. There’s a practical question: how do you actually write code that follows these rules?

The answer comes down to one fundamental capability: loose coupling through dependency inversion.

Why Dependency Inversion Is the Prerequisite

The architecture model demands that inner layers never reference outer layers. But inner layers need things that outer layers provide - persistence, network access, UI rendering. This creates an apparent contradiction: how can the Core use persistence without depending on the persistence implementation?

The answer is the Dependency Inversion Principle: instead of the Core depending on a concrete persistence class, the Core defines an interface describing what it needs, and the outer layer provides a concrete class that satisfies that interface. The dependency is inverted - the outer layer depends on the inner layer’s interface, not the other way around.

WITHOUT dependency inversion:
  Core - depends on -> FileSaveService (in Services layer)
  ✗ Inner layer depends on outer layer. Architecture violated.

WITH dependency inversion:
  Core - defines -> IPersistence (interface, lives in Core)
  Services - implements -> FileSaveService : IPersistence
  ✓ Outer layer depends on inner layer's contract. Architecture preserved.

This single principle makes the entire layered architecture possible. Without it, the dependency rule is unenforceable. With it, every boundary in the system becomes clean and testable.

The Technical Requirements

To implement dependency inversion in practice, you need a small set of technical capabilities:

1. A Way to Wire Dependencies at Runtime

If the Core only knows about IPersistence (the interface), something has to provide the actual FileSaveService (the implementation) at runtime. This is the job of a dependency injection system - a mechanism that connects interfaces to implementations so that code can ask for what it needs without knowing where it comes from.

This is covered in depth in Dependency Injection.

2. A Way to Communicate Without Direct References

Not all communication should flow through injected dependencies. When the Core’s state changes, multiple outer layers might need to react - UI updates, analytics events, sound effects. The Core shouldn’t need to know about any of them.

Event systems solve this: the Core emits a signal (“inventory changed”), and any number of listeners can respond without the Core knowing they exist.

This is covered in depth in Event Systems.

3. A Way to Verify the Architecture Is Being Followed

Dependency rules are useless if they’re only enforced by discipline. Under pressure, developers take shortcuts. Over time, shortcuts accumulate. One day the architecture exists only in a diagram that no longer matches the code.

Architectural tests automate enforcement. They analyze the codebase and fail the build if any layer references something it shouldn’t. The architecture becomes a living, verified property of the code - not a document gathering dust.

This is covered in depth in Architectural Tests.

4. A Way to Test the Logic in Isolation

The whole point of clean layers is that inner layers are independent. The ultimate proof of independence is testability - can you run your game logic in a plain test harness, without the engine, without a database, without a network?

If the answer is yes, your architecture is working. If not, your layers have leaks.

This is covered in depth in Unit Tests.

A Warning About Tools

It’s tempting to treat these tools as solutions in themselves. They are not.

Dependency injection is a mechanism, not an architecture. You can have a DI container and still build a tangled mess - just wire everything to everything through the container instead of through static references. The coupling is the same; only the syntax changes. (See: Dependency Injection - The Singleton in Disguise)

Event systems can be abused to create invisible coupling - where nothing references anything directly, but everything reacts to everything through a global event bus, and debugging becomes impossible.

Architectural tests can be written too loosely to catch real violations, or so strictly that they create busywork without value.

The architecture is the solution. These are tools that serve the architecture. They are necessary but not sufficient. Each of the linked topics discusses not just how to use the tool, but how to use it correctly - in service of clean, layered design.

Where to Go From Here

If you want to…Read
Understand how to wire layers together at runtimeDependency Injection
Learn how inner layers communicate outwardEvent Systems
Manage time and lifecycle safetyAsynchronous Flows
See how clean layers enable testingUnit Tests
Enforce architectural rules automaticallyArchitectural Tests
Understand why singletons undermine all of thisSingleton Anti-Pattern
Understand how performance and DOD fit inThe Performance Dilemma
Revisit the layer model itselfArchitecture Model