You already have the boundaries

Free preview 5 min to read

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.
Lesson 1 of 7 · free

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.

Type to start searching...