A reference architecture (part 3)

…continued from part 2

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:

p3_1

Create a new visual studio solution in the \Trunk\Solutions folder and call it ArchitectureExample.Master.sln:

p3_2

We are going to structure our solution using solution folders, to make a clear distinction between the layers, so create the following folders:

p3_3

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:

p3_4

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:

public class BlogEntry 
{
private BlogEntry() {}
public BlogEntry(string title, string body)
{
LastModifiedDate = DateTime.Now;
Title = title;
Body = body;
}

public int Id { get; private set; }
public string Title { get; private set; }
public string Body { get; private set; }
public DateTime? LastPublishDate { get; private set; }
public DateTime LastModifiedDate { get; private set; }
public virtual ICollection<Comment> Comments { get; private set; }

public void Publish()
{
LastPublishDate = DateTime.Now;
}

public void Unpublish()
{
LastPublishDate = null;
}

public void Modify(string title, string body)
{
Title = title;
Body = body;
LastModifiedDate = DateTime.Now;
}

public Comment AddComment(string commentText, string emailAddress, string name)
{
var comment = new Comment(this, commentText, emailAddress, name);
if (Comments == null) Comments = new List<Comment>();
Comments.Add(comment);
return comment;
}

public void RemoveComment(Comment comment)
{
Comments.Remove(comment);
}
}

public class Comment
{
private Comment() {}
public Comment(BlogEntry blogEntry, string name, string emailAddress, string commentText)
{
BlogEntry = blogEntry;
Name = name;
EmailAddress = emailAddress;
CommentText = commentText;
DateWritten = DateTime.Now;
}

public int Id { get; private set; }
public string Name { get; private set; }
public string EmailAddress { get; private set; }
public string CommentText { get; private set; }
public DateTime DateWritten { get; private set; }
public BlogEntry BlogEntry { get; private set; }
}

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:

p3_5

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:

p3_6

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:

public interface IBlogEntryRepository : IRepository<BlogEntry>, IDisposable
{
IQueryable<BlogEntry> BlogEntries { get; }
}

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:

p3_7

And then add the following interface to this project:

public interface IRepository<T> where T : class
{
void Add(T entity);
void Update(T entity);
void Delete(T entity);
void Delete(Expression<Func<T, bool>> where);
IEnumerable<T> Query(Expression<Func<T, bool>> filter);
IEnumerable<T> QueryObjectGraph(Expression<Func<T, bool>> filter, string children);
}

That’s it for the domain layer, by now you should have the following structure:

p3_8

Build the solution to make sure everything is ok.

Next time we will continue with the application layer.

Advertisements

4 thoughts on “A reference architecture (part 3)

  1. Alexey Aza says:

    Thank you, Ludwig, for writting this blog post series. It’s very intresting for me. During reading this one I’ve got one small question about creating entities. Imagine the situation when entity need much more parameters for creation, let say 5-10. I guess for this need we should create one more class just for passing data into entity constructor, am I right? If so, where do you locate this kind of classes according to your project structure?

    • Ludwig Stuyck says:

      Interesting question. If I understand your problem correctly, it means you have domain entities that have a lot of properties. First question: are they all needed in order to create a valid object? You could for example limit your constructor to only accept parameters that are mandatory to create a valid domain entity, and use other public business-oriented operations to set the other ones. Another option is (as you say) to create ‘container classes’, but I think then they are also part of your domain and belong next to the domain entities; but because they don’t have identity as a concept, they would be value objects. Now, the example I give in the series are simplistic, because I didn’t want to focus on DDD, but an example would be address – instead of having street, number and other properties in your domain entity you can put them in an Address value object, and use Address in your domain entity. You can organize this in the solution the way you want, this kind of depends on the size of your domain: put everything in one folder, create a folder per subdomain concept, etc…

      • Alexey Aza says:

        Thank you for very informative answer. In my case I deal with language learning domain, application that helps to learn new words. So I have a class Word with properties related exactly to it (like translation, transcription, examples of word usage), but I also have a WordStatistics, that has a lot of properties, like date of adding, count of repetition, date of last repetition, e.g. All of that properties are needed to create valid object, so I think it very simulair to your Address example, so I’ll split it in logically valid value objects.

  2. Teresa says:

    Hi Ludwig,
    I’ve a question. In this example, when you remove an item, the item is deleted from the database. In my project, I don’t need to remove the item – just need to set a flag (eg: IsDeleted) to true. Since the setters are private, how do I achieve this?
    Thanks
    Teresa

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s