1. Introduction and Goals

1.1. Requirements Overview

Conference Example is a conference management system used by speakers (hundreds), organizers (dozens), and attendees (thousands) to organize and manage conferences in the German-speaking region.

The key features are:

  • Conference program — attendees can view the conference program including times and rooms online

  • Talk management — speakers can manage their talks (submit, edit)

  • Talk rating — attendees can rate the popularity of talks (thumbs up, thumbs down)

  • Real-time notifications — organizers can notify attendees in real time about changes to the conference program (opt-in required)

  • Conference branding — each conference can have its own branding

  • Slide sharing — presentation slides are made available to attendees online

  • Mobile app — a mobile conference app for attendees is envisioned for the future

Beyond the functional scope, the primary purpose of this project is to serve as a comprehensive example system for presentations and workshops covering topics such as Domain-Driven Design, Clean Architecture, software architecture, architecture documentation, architecture tests, and testing.

1.1.1. Additional Context

  • Conferences take place in the German-speaking region.

  • There are only a few support staff members.

  • Very high traffic is expected while a conference is running.

1.2. Quality Goals

Priority Quality Goal Motivation

1

Understandability

The codebase is used in talks and workshops. Concepts must be immediately graspable by an audience encountering the code for the first time.

2

Testability

The system demonstrates various testing strategies (unit, acceptance, architecture tests). Every layer must be independently testable with minimal setup.

3

Maintainability

The project evolves alongside new talk topics. Adding new features or demonstrating new patterns must be straightforward without breaking existing examples.

1.3. Stakeholders

Role Name Expectations

Conference Organizer

(fictional)

Can create and publish conferences, manage rooms and schedules, and review submitted talk proposals.

Speaker

(fictional)

Can register, browse published conferences, and submit talk proposals including title, abstract, and tags.

Conference Attendee

(fictional)

Can view the conference program including times and rooms, rate talks (thumbs up / thumbs down), receive real-time notifications about program changes (opt-in), and access presentation slides.

Presenter / Author

Andreas Lausen

Needs a realistic, well-structured example project that can be used across different talks and extended incrementally to demonstrate new architectural concepts.

Talk Audience

(various)

Expects the code to be clean, readable, and representative of the concepts being presented — not oversimplified, but not overwhelming.

2. Architecture Constraints

Constraint Background / Motivation

Backend implemented in .NET / C# (latest version)

The author’s primary platform and language. Using the current version ensures the project stays relevant and showcases modern language features.

Runnable locally without external infrastructure

Anyone cloning the repository must be able to start the full system — including the databases — on their own machine using Docker. No cloud account, hosted service, or manual setup beyond Docker is required.

English for all code and documentation

The project is publicly available and used in talks with an international audience. English as a shared language maximises accessibility.

Architecture documentation using arc42 and AsciiDoc

Documentation is treated as code: stored in the repository, version-controlled, and generated reproducibly via ./build-docs.sh. The arc42 template provides a proven, widely recognised structure for software architecture documentation.

Freely accessible source code

The project is open source and publicly hosted. Anyone can clone, run, and extend it — a prerequisite for its use as a teaching and reference example.

3. System Scope and Context

3.1. Business Context

The Conference Management System interacts with three primary user roles and one external system.

business context
Actor / System Interaction

Organizer

Creates and configures conferences (rooms, time slots). Assigns accepted talks to the program. Triggers notifications to attendees about program changes.

Speaker

Submits and edits talk proposals (title, abstract, tags). Uploads presentation slides for accepted talks.

Attendee

Views the conference program (times, rooms). Rates talks (thumbs up / thumbs down). Opts in to receive real-time notifications about program changes. Accesses presentation slides.

Identity Provider

External system providing authentication and role-based authorization. The Conference Management System delegates all identity concerns to this provider and consumes tokens/claims via an anti-corruption layer.

3.2. Technical Context

The system is deployed as a modular monolith: a single ASP.NET Core application that hosts all bounded contexts as in-process modules. Each module owns its own database schema within a shared database instance.

technical context

4. Solution Strategy

4.1. Domain-Driven Design

The system follows a Domain-Driven Design approach. The problem space is decomposed into subdomains, each of which is mapped to a solution-space bounded context.

4.1.1. Subdomains

Subdomain Type Description

Conference

Core

Managing conferences, rooms, time slots, and the conference program (scheduling).

Session

Core

Managing talks, speakers, abstracts, and tags. Talks are submitted and curated independently before being assigned to the conference program.

Voting

Supporting

Attendees rate the popularity of talks (thumbs up / thumbs down). High-traffic subdomain during a running conference — requires independent scalability.

Notification

Supporting

Real-time notifications to attendees about changes to the conference program. Attendees must opt in to receive notifications.

Attendee

Supporting

Attendee registration, profiles, and preferences (e.g. notification opt-in).

Content

Supporting

Upload and distribution of presentation slides. Speakers upload slides for their talks; attendees can access them online.

Identity

Generic

Authentication, authorization, and user/role management (speaker, organizer, attendee). This subdomain is best served by a standard identity provider (e.g. Keycloak, Auth0, Entra ID) rather than custom development.

4.1.2. Mapping Subdomains to Bounded Contexts

Not every subdomain requires a custom-built bounded context. The following table shows how subdomains map to bounded contexts in the solution space.

Subdomain Bounded Context Rationale

Conference

Conference

Custom implementation — core domain with conference-specific business rules.

Session

Session

Custom implementation — core domain with its own aggregate lifecycle (submit → review → accept).

Voting

Voting

Custom implementation — isolated due to fundamentally different traffic and scaling characteristics (thousands of concurrent votes).

Notification

Notification

Custom implementation — event-driven, asynchronous delivery, own infrastructure concerns (WebSocket, push).

Attendee

Attendee

Custom implementation — manages attendee-specific domain logic (registration, preferences).

Content

Content

Custom implementation — handles file storage and distribution, potentially backed by a CDN or object store.

Identity

(no custom BC)

Addressed by a standard identity provider. An anti-corruption layer in the API translates external identity tokens into domain-specific role concepts.

4.2. Modular Monolith

The system is built as a modular monolith: all bounded contexts are deployed within a single ASP.NET Core application, but are strictly isolated as modules with clear boundaries.

  • Single API — one ASP.NET Core host exposes all endpoints. Each module registers its own controllers/endpoints, but externally there is one unified API.

  • Schema per module — all modules share one physical database, but each module owns a dedicated database schema. No module may read from or write to another module’s schema directly.

  • In-process communication — modules communicate via defined interfaces (domain events, mediator, or explicit contracts), not via network calls.

  • Extractability — the strict module boundaries are designed so that individual bounded contexts can be extracted into separate services later if scaling or organisational needs require it.

4.3. Clean Architecture

Each custom bounded context follows a Clean Architecture layered structure:

API  ──►  Application  ──►  Domain
 │                            ▲
 └──────►  Persistence  ──────┘
  • Domain — aggregates, entities, value objects, domain events. No dependencies on other layers.

  • Application — use cases (commands and queries). Depends on the domain only; does not know Persistence directly.

  • Persistence — shared data models and the IDatabaseContext interface. Depends on the domain.

  • API — ASP.NET Core entry point, dependency injection, HTTP endpoints. Depends on all other layers.

5. Building Block View

5.1. Whitebox Overall System — Bounded Context Map

The following diagram shows the bounded contexts of the system and their relationships.

bounded context map

5.1.1. Contained Building Blocks

Bounded Context Type Responsibility

Conference

Core

Manages conferences, rooms, time slots, and the conference program. Owns the scheduling of talks to slots. References talks by ID only.

Session

Core

Manages talks, speakers, abstracts, and tags. Talks have their own lifecycle (submit → review → accept) independent of the conference program.

Voting

Supporting

Handles attendee ratings of talks (thumbs up / thumbs down). Designed for high throughput and independent scalability.

Notification

Supporting

Delivers real-time notifications to attendees about conference program changes. Event-driven, asynchronous. Respects attendee opt-in preferences.

Attendee

Supporting

Manages attendee registration, profiles, and preferences (e.g. notification opt-in).

Content

Supporting

Handles upload and distribution of presentation slides. Speakers upload slides linked to their talks.

Identity Provider

Generic (external)

Authentication and role management. Not a custom bounded context — addressed by a standard identity product. Other contexts integrate via an anti-corruption layer that translates tokens/claims into domain-specific roles.

5.1.2. Important Interfaces

Bounded contexts communicate through well-defined interfaces. The following integration patterns are used:

  • ID references — contexts reference entities from other contexts by ID only (e.g. SessionId, AttendeeId). No shared domain models.

  • Domain events — the Notification context subscribes to domain events from the Conference context (e.g. ProgramChanged) to trigger attendee notifications.

  • Anti-corruption layer (ACL) — contexts that interact with the external identity provider translate external claims into internal role/permission concepts.

6. Runtime View

7. Deployment View

7.1. Infrastructure Level 1

deployment level1

8. Cross-cutting Concepts

9. Architecture Decisions

9.1. Architecture Decision Records

9.1.1. ADR-1: Modular Monolith as Deployment Architecture

Attribute Value

Status

Accepted

Context

The system consists of multiple bounded contexts (Conference, Session, Voting, Notification, Attendee, Content). A deployment strategy must be chosen that balances modularity, operational simplicity, and the ability to evolve the architecture over time.

Decision

Deploy all bounded contexts as modules within a single ASP.NET Core application (modular monolith). Each module owns its own database schema within a shared database instance. Modules communicate in-process via defined interfaces (domain events, mediator, or explicit contracts) — not via network calls.

Rationale

  • Operational simplicity — one deployable unit, one database instance. No distributed-systems overhead (service discovery, network resilience, distributed transactions).

  • Developer experience — straightforward local development and debugging; all modules run in a single process.

  • Strict boundaries — despite sharing a process, modules are isolated via schema-per-module and enforced dependency rules (architecture tests). This preserves the option to extract modules into separate services if scaling or organisational needs require it.

  • Demonstration value — shows how DDD bounded contexts can be implemented with strong isolation without prematurely adopting microservices.

Consequences

  • Positive: Low operational complexity, fast in-process communication, simple transactions within a module, easy local development.

  • Negative / trade-offs: All modules must be deployed together; a bug in one module can affect the entire application. Independent scaling of individual modules is not possible without extraction. Discipline is required to maintain module boundaries over time.

9.1.2. ADR-2: Event Sourcing and CQRS

Attribute Value

Status

Accepted

Context

The system serves as a demonstration project for talks and workshops, as well as a real-world example of modern architectural patterns. For conference and talk management, traceability of changes and efficient queries for different views are relevant concerns.

Decision

Adopt Event Sourcing combined with CQRS (Command Query Responsibility Segregation). Aggregate state is not stored as a current snapshot but as an immutable sequence of domain events. The write and read sides are explicitly separated: commands mutate state via the event store, while queries are served by optimised read models (projections).

Rationale

  • Demonstration value — the pattern is well-suited for illustrating DDD, domain events, and Clean Architecture in talks and workshops.

  • Read-side performance — read models can be tailored exactly to each use case (e.g. conference overview, talk list by track) and are pre-computed, so no joins or aggregations are required at query time.

  • Complete audit trail — all state changes are persisted as events; no information is ever lost through overwriting.

  • Temporal queries — the state at any point in the past can be reconstructed by replaying events up to a given position in the stream.

  • Debugging and traceability — bugs can be reproduced and analysed by inspecting the exact sequence of events that led to a given state.

  • Natural expressiveness in the domain model — domain events (SessionProposed, SpeakerConfirmed, …) are first-class citizens and directly reflect the ubiquitous language.

  • Independent scaling — the read and write sides can be scaled separately; read models can be materialised in a query-optimised store (e.g. Redis, Elasticsearch).

  • Integration capability — the event stream provides a natural foundation for integrating with other systems or bounded contexts via asynchronous message processing.

Consequences

  • Positive: High traceability, flexible read models, good testability of aggregates via event sequences.

  • Negative / trade-offs: Higher initial complexity compared to a classic CRUD approach; eventual consistency between the write and read sides must be consciously accounted for; event schema evolution requires care (upcasting).

9.2. Open Decisions

9.2.1. OD-1: Frontend Technology

Attribute Value

Status

Open

Question

Which frontend technology should be used for the user interface?

Options Considered

  • React

  • Angular

  • Blazor (WebAssembly or Server)

  • Vue.js

Decision Criteria

Developer experience, alignment with the .NET backend ecosystem, suitability as a demonstration technology for talks and workshops.

9.2.2. OD-2: Frontend Boundary — Shared vs. Per Bounded Context

Attribute Value

Status

Open

Question

Should there be a single shared frontend application, or a separate frontend application per bounded context?

Options Considered

  • Shared frontend — one application consumes APIs from all bounded contexts. Simpler deployment, but introduces coupling between bounded contexts at the UI layer.

  • Frontend per bounded context — each bounded context owns its own frontend. Stronger isolation and autonomy per context, but requires a composition strategy (e.g. micro frontends) to present a unified user experience.

Decision Criteria

Degree of desired autonomy between bounded contexts, operational complexity, and value as a demonstration of DDD and Clean Architecture principles.

9.2.3. OD-3: Event Store Persistence Technology

Attribute Value

Status

Open

Question

Which persistence technology should be used as the event store?

Options Considered

  • EventStoreDB — purpose-built event store with native support for event streams, subscriptions, and projections. Ideal fit for Event Sourcing, but introduces an additional infrastructure component.

  • PostgreSQL — relational database used as an event store via an append-only events table. Familiar technology, easy to operate, but requires more custom infrastructure code.

  • SQL Server — same approach as PostgreSQL; already common in .NET ecosystems.

  • MongoDB — document-oriented store that can serve as an event store via an append-only collection. Designed to handle large volumes of data efficiently, which is relevant given that events are never deleted and the store grows continuously over time. Good .NET support via the official MongoDB driver.

  • Marten — .NET library that turns PostgreSQL into a document/event store with first-class Event Sourcing support and projections built in.

Decision Criteria

Operational simplicity, .NET integration, suitability as a demonstration technology, and alignment with the existing infrastructure.

9.2.4. OD-4: Read Model Persistence Technology

Attribute Value

Status

Open

Question

Which persistence technology should be used to store and serve read models (projections)?

Options Considered

  • In-memory store — projections are held in memory and rebuilt on startup by replaying all events. Zero operational overhead, but not suitable for production deployments with large event streams.

  • PostgreSQL — projections are materialised into relational tables. Flexible querying via SQL, easy to operate alongside the event store if PostgreSQL is already chosen there.

  • Redis — in-memory key/value store for very fast reads. Well-suited for simple, flat projections; less ideal for complex query patterns.

  • Elasticsearch / OpenSearch — full-text search and complex aggregations out of the box. High operational overhead; most valuable when rich search capabilities are required.

  • MongoDB — document-oriented store well-suited for read models, as projections map naturally to documents. Good .NET support via the official MongoDB driver; flexible schema makes it easy to evolve projection shapes without migrations.

  • Marten (with PostgreSQL) — if Marten is chosen for the event store, it also provides built-in projection support writing into PostgreSQL, avoiding a separate read-side store entirely.

Decision Criteria

Query complexity, read performance requirements, operational simplicity, and consistency with the chosen event store technology.

9.2.5. OD-5: Identity Provider

Attribute Value

Status

Open

Question

Which identity provider should be used for authentication and role management?

Options Considered

  • Keycloak — open-source, self-hosted, feature-rich (SSO, social login, fine-grained roles). Requires operational effort for hosting and maintenance.

  • Auth0 — managed SaaS identity platform. Low operational effort, but introduces a vendor dependency and recurring costs.

  • Microsoft Entra ID (formerly Azure AD) — managed identity service, strong integration with the Microsoft/.NET ecosystem. Ties the system to the Azure platform.

Decision Criteria

Operational simplicity (self-hosted vs. managed), cost, alignment with the .NET ecosystem, ease of local development (Docker-based setup), and suitability as a demonstration technology.

10. Quality Requirements

10.1. Quality Tree

10.2. Quality Scenarios

11. Risks and Technical Debts

Risk / Technical Debt Description Mitigation

12. Glossary

Term Definition