Architecture Principles


Answers the Question(s)Brief DescriptionedX Example(s)References (Reading List)
Domain-Driven Design (DDD)

Where should this code live?

Which service/app should be the source of the truth of this data?

What should be the boundary (or single responsibility) of this service/app?

What should we name "it"?

How should we structure our code?

How should we keep this service/app isolated from others?

What are edX's main business domains and bounded contexts?

DDD provides principles for modeling our system and our code. Eric Evan's original book is hefty and contains many finer points, but the biggest takeaways for edX are:

  1. Critical to bridge common understanding between Engineering & Business on:
    1. Aligned edX DDD Domain Vision Statement
    2. Ubiquitous language of our domain concepts
    3. Labeling our domains as Core, Supporting, and Generic to prioritize our resources, SLOs, etc.
  2. Decompose our architecture by "jobs" or "responsibilities" and NOT by data. That is, NOT Entity-based Services, but Lifecycle-based Services.
  3. Since domain concepts are local to its Bounded Context, each Context owns the semantics of its own ubiquitous naming and the management of its own data as optimized for its own needs.
  • Boundaries within the monolith
  • Separate Enterprise IDA
  • Location of Microbachelors logic
  • Size of micro-frontends
  • Name of "Course" as a ubiquitous language within each context (see edX DDD Ubiquitous Language)
    • LMS: course = course-run
    • Discovery: course = catalog-course

Non-Examples:

  • Considering discovery service as a central entity-based service to all other services
  • Lack of abstractions within edx-platform
Domain-Driven Design
Reactive Manifesto (RM) & Self-Contained Systems (SCS)

How can we achieve low latency when responding to end-user requests?

How can we scale the communications between services in our architecture?

What should be the communication relationship between backend services?

What should be the separation of concerns between backend and frontend?

The Reactive Manifesto recommends asynchronous (non-blocking) communication channels between services wherever possible in order to create a scalable loosely coupled architecture. This then results in a system that is responsive, resilient, and elastic.

The principles of Self-Contained Systems take the Reactive Manifesto one step further and advocate for highly decoupled verticals in the system, with each vertical owning its own frontends and data. Each SCS is highly cohesive and completely independent of other SCS's. This may lead to data duplication across SCS's, but results in a highly loosely coupled architecture. This is consistent with DDD's recommendation for data ownership within each context.

  • Credentials service uses async non-blocking communications with LMS
  • Micro-frontend relationship with their backends
  • Micro-frontend coordinates responses across multiple backends (i.e., async integration at the UI)

Non-Examples:

  • Multiple endpoints making blocking connections to the discovery service.
  • Connecting to other services within a web-worker response to the end-user.

Reactive Manifesto

Self-contained Systems

SOLID

Where should this code live?

How do we scale development so volatile changes don't require modifications to a stable core?

What should be the design of this API?

Should we break up this component/API into multiple parts?

How do we create an API suitable for a plugin architecture?

Following SOLID design principles lead to creating a scalable and maintainable codebase where:

  1. the responsibility of each component is clearly defined and abstracted (S)
  2. volatile and ongoing changes do not require modifications to a stable core (O, D)
  3. pluggable components can be easily substitutable (L)
  4. dependency management is maintainable (I, D)
  • xBlock API
  • Django App Plugin API
  • API for pluggable Course Tabs
Clean Architecture