A simple scenario
To demonstrate the approach explained in the previous parts, I’m going to take a simple example to focus on the architecture and structure of the solution. We’ll provide functionality to manage blog entries and comments.
We will create a BlogService that has the following commands: AddBlogEntry, PublishBlogEntry, UnpublishBlogEntry, ModifyBlogEntry, AddCommentToBlogEntry, RemoveCommentFromBlogEntry; and the following queries: FindAllBlogEntries, FindBlogEntryById.
The implementation of the layers will be simplistic, but representative enough to demonstrate the basics.
Visual Studio solution and projects
First create the physical folder structure needed for our libraries, solution and projects:
Create a new visual studio solution in the \Trunk\Solutions folder and call it ArchitectureExample.Master.sln:
We are going to structure our solution using solution folders, to make a clear distinction between the layers, so create the following folders:
The domain layer
To start the implementation, it seems logical to start with the layer that has the least dependencies, which is, of course, the domain layer. This layer holds the domain entities, domain services and repository interfaces.
Add a class library project called ArchitectureExample.Domain.Entities to the Domain Layer solution folder, and put it physically in the \Trunk\Sources\ folder:
In this project we are going to add the domain entities, which will contain data and domain logic. So add two classes to the ArchitectureExample.Domain.Entities project: BlogEntry and Comment, and implement them like this:
The domain logic is simple, we have no dependencies whatsoever; instead we focus on the domain logic itself. A couple of notes about domain entities:
- They don’t have a public default constructor; instead they are created with a constructor that accepts parameters needed to create a valid entity. This is to avoid incomplete and invalid entities.
- They don’t have public setters. Methods should be used so that the intend is expressed.
- They don’t have references to repositories. If an entity needs a reference to another entity, it is passed with its constructor.
- Domain entities are not registered by a dependency injection framework: they are created or loaded manually by the application layer because that’s their responsibility.
To hold the domain services, add a new class library project called ArchitectureExample.Domain.Services to the Domain Layer solution folder, and put it physically in the \Trunk\Sources\ folder:
In this project we define the domain services and repository interfaces that are needed to actually do something with the domain entities.
We should put logic as much as possible in the domain entities themselves, if it involves the entities and their children. However, if operations become more complex and they involve multiple entities (business logic that doesn’t naturally fit within a domain object), the concept of a domain service comes into play. Domain services are like facades that orchestrate domain entities to work together and use repositories facilitate the insertion, creation, deletion, and retrieval of data. In our case however, for now, we don’t have such complex operations, so we leave this project empty.
The last project we’re going to create in the domain layer is the one holding the repository interfaces, so add a new class library project called ArchitectureExample.Domain.Repositories to the Domain Layer solution folder, and put it physically in the \Trunk\Sources\ folder:
In DDD terminology, BlogEntry and Comment are both entities (because they both have an identity), but they also form an aggregate where BlogEntry is the root entity. After all, a specific comment belongs to a specific blog entry, without a blog entry a comment would not make any sense; and so this aggregate forms a logical grouping of blog entries and comments.
The rule is: only aggregate roots can be obtained directly with queries; so this means that we should have a repository per aggregate root, and in our case this would be a IBlogEntryRepository that takes care of managing operations for blog entries and comments.
Add the interface IBlogEntryRepository to the ArchitectureExample.Domain.Repositories project:
We will be implementing the repository pattern, which means we define a generic repository interface to which all repository interfaces must adhere to: IRepository<>.
By exposing entity collections as IQueryable, it is possible for the users of the repository to use LINQ statements to query the collection.
Add a new class library project called ArchitectureExample.Domain.Shared to the Domain Layer solution folder, and put it physically in the \Trunk\Sources\ folder:
And then add the following interface to this project:
That’s it for the domain layer, by now you should have the following structure:
Build the solution to make sure everything is ok.
Next time we will continue with the application layer.