Project structure, naming, modules in DDD/CQRS/ES

1,116 views
Skip to first unread message

urbanhusky

unread,
Aug 2, 2016, 8:20:05 AM8/2/16
to DDD/CQRS
Hi,

I'm trying to get a better understanding of DDD, CQRS etc. We would like to apply DDD (or "DDD-light" because we don't have direct access to domain experts and everything runs through several layers of management - but we want to model more closely to business processes, guard invariants etc.) and CQRS/ES for new parts of an application.
For this reason we're currently working on a prototype to demonstrate that we have the technical capabilities to handle this - at least from the standpoint of technical competence (actual design, modelling etc. is something we'll have to improve, or rather actually use).

We don't want to risk anyone taking our prototype and just expanding on it, as if it were anywhere production-ready code (let alone well/correctly modelled), which is why we're using a different domain for this prototype: car rental. That's something we can convey easily and can come up with several processes to demonstrate various concepts.

I'm now trying to set up the project structure (C# application). I've started with identifying and separating the bounded contexts, such as:
  • Reservations: customers can look up available cars and reserve them (our core domain, we can add risk-assessment, offering upgrades etc.). Process driven by customer
  • Rental: customer picks up car he reserved prior (or possibly even needs a car right now).
  • Returns: customer returns car, calculation of fees (if any)
  • Fleet management: supporting subdomain, most likely just CRUD management of available cars unless we have processes here

I definitely want to have the domain model and events in separate projects with limited references, so that they can be as focussed on the domain (and persistence-ignorant) as possible. Having the events in a separate assembly also allows other contexts to reference them (conformist relationship) for integration across contexts (all contexts share the same event store + messaging).


This initially leaves me with these modules:

  • CarRental.Reservations.Domain.Model (Domain models, domain services, interfaces for domain model repositories?)
  • CarRental.Reservations.Domain.Messaging (Events)
  • CarRental.Rental.Domain.Model
  • CarRental.Rental.Domain.Messaging
  • CarRental.Returns.Domain.Model
  • CarRental.Returns.Domain.Messaging
  • CarRental.FleetManagement.Domain.Model
  • CarRental.FleetManagement.Domain.Messaging

...or just CarRental.{Context}.Domain.Model and CarRental.{Context}.Domain.Messaging (or would Messages be more appropriate? or perhaps Events?)


We'll certainly have some infrastructure. Domain model repositories (implementation) would be infrastructure per context: CarRental.{Context}.Infrastructure.Repositories

The interfaces, base classes etc. for our event-sourced aggregate roots, event store, events, projections etc. would fit best in a shared context. Possibly CarRental.Infrastructure.


We'll need an event store too (or have to embed an existing one). I'd like to separate the actual service from the implementation so that we can host it in a console application while debugging and in a windows service in production:

CarRental.Infrastructure.EventStore.Service for the assembly which gets referenced by CarRental.Infrastructure.EventStore.Service.ConsoleServer or CarRental.Infrastructure.EventStore.Service.WindowsService.

Does Infrastructure make sense for such a service? It doesn't fulfil any specific application purposes and certainly isn't a domain service.


Since we're using CQRS, we'll need a place for the commands - which I would think are application-specific. So we'll need modules for that as well:

CarRental.{Context}.Application.Commands. For commands and command handlers - we'll be using a command bus for decoupling instead of commands as per command-pattern.


So far my initial impression is that everything has its proper place - but we're still missing one thing before we can look at the presentation: the read side.


We need a module which handles projections into read models (which are stored in the read store best suited the types of queries that the view runs). I.e. we'll most likely have some sort of document database and an SQL database in which we'll each save a partition of the read models. This will cause some leakage of persistence into our application, because we don't want to abstract away all the benefits we get. We won't even be implementing any repositories here if the underlying storage already provides everything we need. For example: we might use entity framework. We'll use the EF context directly instead of writing some needless abstraction around it. If we use RavenDb, we will use its session directly as well.


I think that the projections are an application-responsibility, with parts of infrastructure due to the tighter coupling to the underlying database. We most likely won't have to switch stores for our read models anyway - and if we do, we'll just write them for the new store and rebuild from the event store (i.e. they're new views/read models). There is no migration scenario here, no need for abstraction. Optimising reads and selecting the best suited database is CQRS.


But where to put this? CarRental.{Context}.Application.Read? with Read.Projections containing all the projections and read models?

I'm not sure if "read" is a good fit (considering UL), or if it isn't too technical.


For the presentation, we'll have an asp.net mvc/webapi application on top. One application over all contexts:

CarRental.Presentation.Web

This might house some minimal application logic, but most should be contained to the modules (i.e. application services, commands etc. are defined per context). So I think that presentation fits this best.


We'll need a service that hosts and runs our projections as well.

CarRental.Application.Read.Projections.Service? (with facades/adapters/wrappers for console and windows service)

...however, this also needs to be available as a RESTful web api because the root web application must consume this. We can't host it in the same application as the web application for technical reasons (we need asp.net mvc and that isn't available for self-hosting, i.e. we can only host it in IIS, which is not a good choice for services that should run and process 24/7 - and aspnet core is no alternative either because it is unfinished)

...so perhaps CarRental.Read.Presentation.Web would be better? But we don't have any UI, only services...

This service must handle the projections and web api to the read models because we will need notifications about updates to the read models (ajax push/signalR). The projections part will be handled by the context-specific projections in CarRental.{Context}.Application.Read.Projections, which is hosted in this service.


Once the asp.net tooling no longer sucks (currently it is not possible to split web applications into multiple projects/modules), we'll have web-specific parts per context:

CarRental.{Context}.Presentation.Web (because asp.net: this will contain views, controllers, js, css etc.).


This leaves me with this structure:

Namespaces will always start with CarRental. but I think that it is much more readable if the projects and corresponding folders don't. The folder hierarchy provides context.


\CarRental (root folder)
 
\ReservationsContext (virtual/solution folder)
    \Reservations.Application (project/assembly)
      \Commands
        ...commands, handlers
    \Reservations.Application.Read.Projections (project/assembly)
      ...projections and read models
   
\Reservations.Domain.Model (project/assembly)
      ...domain models, repository interfaces
, domain services
    \Reservations.Domain.Messaging (project/assembly)   
      ...domain events
    \Reservations.Infrastructure (project/assembly)
      \Repositories
        ...implementation of domain repositories
 
\RentalContext (virtual/solution folder)
    .Application, .Read.Projections, .Domain.Model, .Domain.Messaging, .Infrastructure
 
\ReturnsContext (virtual/solution folder)
    .Application, .Read.Projections, .Domain.Model, .Domain.Messaging, .Infrastructure 
  \
FleetManagementContext (virtual/solution folder)
    .Application, .Read.Projections, .Domain.Model, .Domain.Messaging, .Infrastructure
  \CarRental.Application.Read.Service (project/assembly)
    ...classes to host the web api and projections service...
  \CarRental.Application.Read.Service.ConsoleServer (project/assembly)
    ...console application that hosts the projections service and web api...
  \CarRental.Application.Read.Service.WindowsService (project/assembly)
    ...windows service that hosts
the projections service and web api...
  \CarRental.Infrastructure (project/assembly)
    ...general infrastructure (interfaces, base classes etc.)
  \CarRental.Infrastructure.EventStore.Service (project/assembly)
    ...classes to host an event store
  \CarRental.Infrastructure.EventStore.Service.ConsoleServer (project/assembly)
    ...console application host of an event store
  \CarRental.Infrastructure.EventStore.WindowsService
    ...windows service host of an event store
 
\CarRental.Presentation.Web (project/assembly)
   
...asp.net mvc/webapi aplication...

That seems to be somewhat complex already.


Did I do anything wrong? Would there be other approaches to this? How could this be improved?

Reply all
Reply to author
Forward
0 new messages