You already have the boundaries
Service-oriented architecture is usually presented as a destination. It's more useful as a question about the boundaries you already have, so the first step is to measure the ones in this application.
Ten contexts
Admin: 60 files Enrollment: 54 files
Partners: 42 files Catalog: 21 files
Identity: 19 files Auth: 18 files
Execution: 14 files Public: 8 files
Community: 6 files Media: 1 file
Ten bounded contexts and 243 Ruby files, counted on 2026-09-28 as the .rb files under app/<layer>/<context>/. Admin comes first because the course authoring screens live in Admin::Catalog (33 files). Count those as Catalog and the ranking changes, so ask how a context's size was counted before you compare two of them.
MEMORY.md states the rule that produced them:
"Every piece of code belongs to a named domain (bounded context). There are no exceptions — do not add controllers, models, services, or helpers at the root level."
This is service-oriented architecture without the network. The boundaries exist and are enforced by convention, and a call across one is a plain method call.
The coupling matrix
For each context, count how many of its files reference another context by namespace. Catalog::Course counts as an edge to Catalog; Admin::Catalog:: stays inside Admin.
| From | To | Files |
|---|---|---|
| Enrollment | Catalog | 21 |
| Admin | Catalog | 13 |
| Enrollment | Identity | 11 |
| Auth | Identity | 10 |
| Admin | Enrollment | 8 |
| Public | Catalog | 5 |
| Partners | Enrollment | 5 |
| Catalog | Enrollment | 5 |
| Admin | Partners | 5 |
| Execution | Identity | 5 |
| Execution | Catalog | 4 |
| Execution | Enrollment | 3 |
flowchart TB
E["Enrollment 54 files"] -->|21| C["Catalog 21 files"]
C -->|5| E
AD["Admin 60"] -->|13| C
AD -->|8| E
AD -->|5| PA["Partners 42"]
PA -->|5| E
E -->|11| I["Identity 19 files"]
AU["Auth 18"] -->|10| I
P["Public 8"] -->|5| C
X["Execution 14"] -->|5| I
X -->|4| C
X -->|3| E
M["Media 1"]
AD -->|2| M
What the matrix says
Three contexts take the traffic
Catalog, Enrollment and Identity receive eleven of the twelve largest edges: four go to Catalog, four to Enrollment and three to Identity. Catalog and Identity are the hubs that most contexts depend on. Enrollment is both: a heavy user of Catalog and Identity, and a dependency of Admin, Partners and Catalog.
One pair is bidirectional
Enrollment references Catalog in twenty-one files, and Catalog references Enrollment in five. A boundary crossed in both directions is a seam that has been sewn shut. It's the most important fact on this page for everything that follows.
Media is a leaf
One file, referenced by two Admin files, referencing nothing. In graph terms it's the easiest thing here to extract, and it's one file, which is lesson 4's point: the candidate you can extract isn't the one worth extracting.
Execution is nearly a leaf
Fourteen files. Its outgoing edges are small (five files to Identity, four to Catalog, three to Enrollment), and no context references it from more than two files. It sits at the edge of the system, and its workload is unlike any other context's: it grades submissions on the server, in a sandbox, from a background job (Execution::GradeSubmissionJob).
The question SOA asks
Given boundaries like these, service-oriented architecture asks one question:
Should any of these boundaries become a network boundary?
It's narrower than "should we build microservices" or "is the monolith bad": a local question about one edge on that diagram, with a measurable answer, and the rest of this course is the method for answering it.
The default answer is no, and the default is right far more often than the literature suggests. A modular monolith with ten enforced contexts already has most of what service orientation is for: independent reasoning, clear ownership, replaceable parts. What it lacks is independent deployment and independent failure, and lesson 3 is about what those cost.
The honest starting position
This application has one human contributor and over two thousand commits, and it deploys as a single unit.
Nothing here should be a separate service, and saying so up front is what makes the rest of the course usable. The method matters anyway, for two reasons: it's the same method that tells you when the answer changes, and it produces a diagnosis of the current design that's worth having whether or not anything gets extracted.
Lesson 2 is that diagnosis, and it doesn't agree with this page.
Key takeaways
- Ten bounded contexts, 243 Ruby files, with boundaries enforced by convention and no network in between.
- Catalog and Identity are hubs; with Enrollment, they receive eleven of the twelve largest edges.
- Enrollment and Catalog reference each other: twenty-one files one way, five the other.
- Media is the graph's leaf and is one file; Execution is nearly a leaf and grades code on the server.
- SOA asks one local question: should a specific boundary become a network boundary? The default answer is no.
Keep going with lesson 2
Where the boundaries are not
Maps the twenty-two foreign keys that cross contexts and shows Catalog and Identity acting as a shared kernel.
A free account also gives you the quizzes, your progress and notes, and the discussion.