Solution Structure for a DDD Application

Description Article
Table of Contents
Summary

A robust .NET application solution following a multilayered software architecture can be obtained combining DDD concepts, service oriented architecture  (SOA) concepts and object-oriented design (OOD).

This article describes how the solution structure presented for a DDD aplication can be implemented in practice.

The diagram below gives a detailed approach of the multilayered architecture model to apply when developing an application of small, medium or large complexity. On each module or component it´s indicated what custom or Microsoft .NET component should be used.


Operations
Presentation Layer

Refer to MSDN articles related to WPF and/or MVC technology.

Distributed Interfaces Layer
How to do it

The Distributed Services layer can be built through WCF services or old ASPX web-services.

The proceeding to implement these services in the context of the presented solution is:

Step 1:

Implement the classes used to transport information of business entities between the Distributed Interfaces layer and the Application layer according the Data Transfer Objects (DTO) design pattern . Consider only relevant information and use the “DDDFramework.ProjectName.ServiceModel“ namespace;

Step 2:

Consider the universe of application services elegible to to be exposed to external systems and implement a WCF service or  ASPX web-service for each according the following:

  • In case of query services parameterize it with a business entity identifier (single result) or a query object (list of results), invoke the corrrespondent application service and encapsulate returned business entity object(s) into DTO object(s);
  • In case of create/update services parameterize it with a DTO object, decapsulate it into a business entity object, and invoke the corrrespondent application service;
  • Include pre-validation steps or required actions as invokation to specific or common methods defined on the “DDDFramework.ProjectName.Infrastructure.Common” and “DDReamework.Infrastructure.Base.Common” namespaces

Step 3:

If non-IIS hosting is required, in case of WCF services, then implement custom host as a console application or a Windows service.

Services Implementation

If WCF services are preferred then refer to this article for service implementation description. Services contracts and services implementation can be kept together within the same namespace.

If SOAP ASPX web-services are preferred then refer to this article for service implementation description.

If ReST web-services using custom API are preferred then refer to this article for implementation description.

Application layer
How to do it

The Application layer can be built using:

  • Common XXXManager named classes for each application service: Each manager class will handle and coordinate the workflow or complex tasks for a given set of entities;
  • Windows Worflow Foundation definition and host classes for implementation of complex tasks and activity algorithms for which the application services are responsible for.

The application services classes makes extensive use of the repository classes for persistence purposes. On the other hand these repository classes executes query definitions, defined at Domain layer through the use of the Specification pattern, or those defined on the Infrastructure layer through the Query Object pattern.

Alternatively and for query purposes, the queries implemented in the “DDDFramework.ProjectName.QueryModel.Queries” namespace can be used directly for querying.

Domain Model Layer
How to do it

The following table describes the namespaces/assemblies that must exist in the Domain Model layer of a generic framework. After group and build a solution for compilation, with projects as mentioned next, it will become available to all applications:

Namespace Classes Description Remarks
DDDFramework.Business.
Base.Model.Entities
Entity<TEntity,TID>
ValueObject<T>
IEntity<TEntity,TID>
IValueObject<T>
Include interfaces and generic abstract base classes for entities objects and value objects responsible for encapsulating business functionality The code for these classes is shown below
DDDFramework.Business.
Base.Model.Repositories
IReadOnlyRepository
IPersistentRepository
IEntityKey
IPersistentMethodRepository
IReadOnlyMethodRepository
Include generic interfaces for repository objects responsible for reading and storing domain entities The code for these classes is described on this article
DDDFramework.Business.
Base.Model.Specifications
Specification<TEntity>
ISpecification<TEntitiy>
Include generic interfaces and abstract base classes for query objects responsible for query implementation based on the Specification pattern and Linq The code for these classes is described on this article

 

For each specific application the following namespaces must be implemented (as a single assemby):

Namespace Classes Description Remarks
DDDFramework.ProjectName.
Business.Model
Derivated classes from Entity<TEntity,TID> and
ValueObject<T>;
Base service classes
Each of these classes represent specific business entitiesdomain services, and specific repository classes
DDDFramework.ProjectName.
Business.Model.Specifications
Derivated classes from Specification<TEntity> Each of these classes represents a query definition Sample code of how to define queries is described on this article
DDDFramework.ProjectName.
Business.Model.Repositories
Interfaces for specific repositories Each of these classes represents a repository to be used with a single aggregate root

 

Each domain service can be implemented as a common POCO class with or without an associated service interface.

Implementation of  an Entity object (base class):

    public interface IEntity<TEntity, TID> where TEntity : class
    {
        TID ID { get; set; }
        bool IsTransient();
        bool Equals(object obj);
        bool Equals(Entity<TEntity, TID> other);
        int GetHashCode();
        string ToString();
    }

    [Serializable]
    public abstract class Entity<TEntity, TID> : IEntity<TEntity, TID> where TEntity : class
    {
        public virtual TID ID { get; set; }
        public virtual bool IsTransient()
        {
            return ID.Equals(default(TID));
        }

        public virtual bool Equals(Entity<TEntity, TID> other)
        {
            if (ReferenceEquals(this, other))
                return true;
            if (other == null || !(other is TEntity))
                return false;
            if (!IsTransient() && !other.IsTransient() && ID.Equals(other.ID))
                return true;
            return IsTransient() && other.IsTransient();
        }

        public override bool Equals(object obj)
        {
            var compareTo = obj as TEntity;
            return Equals(compareTo);
        }

        public override int GetHashCode()
        {
            var thisIsTransient = Equals(ID, default(TID));

            // When this instance is transient, we use the base GetHashCode()
            return thisIsTransient ? base.GetHashCode() : ID.GetHashCode();
        }

        public override string ToString()
        {
            return GetType().FullName + "#" + ID;
        }

        public static bool operator ==(Entity<TEntity, TID> left, Entity<TEntity, TID> right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(Entity<TEntity, TID> left, Entity<TEntity, TID> right)
        {
            return !Equals(left, right);
        }
    }

Implementation of a Value object (base class):

   public interface IValueObject<T> where T : class
    {
        bool Equals(ValueObject<T> other);
        bool Equals(object obj);
        int GetHashCode();
        string ToString();
    }

    [Serializable]
    public abstract class ValueObject<T> : IEquatable<T>, IValueObject<T> where T : class
    {

        public virtual bool Equals(T other)
        {
            var compareTo = other as ValueObject<T>;

            return Equals(compareTo);
        }

        public virtual bool Equals(ValueObject<T> other)
        {
            if (ReferenceEquals(this, other))
                return true;

            if (ReferenceEquals(null, other) || !GetType().Equals(other.GetType()))
                return false;

            // Compare all public properties
            var publicProperties = GetType().GetProperties();

            if (publicProperties != null && publicProperties.Any())
                return publicProperties
                    .All(item => item.GetValue(this, null)
                    .Equals(item.GetValue(other, null)));

            return true;
        }

        public override bool Equals(object obj)
        {
            // If both are null, or both are same instance, return true
            if (ReferenceEquals(obj, this))
                return true;

            if (obj == null)
                return false;

            var item = obj as ValueObject<T>;

            if (item == null)
                return false;

            return Equals(item);
        }

        public override int GetHashCode()
        {
            var hashCode = 31;
            var changeMultiplier = false;
            const int index = 1;

            // Compare all public properties
            var publicProperties = GetType().GetProperties();

            if (publicProperties != null && publicProperties.Any())
            {
                foreach (var value in publicProperties
                    .Select(item => item.GetValue(this, null)))
                {
                    if (value != null)
                    {
                        hashCode = hashCode * ((changeMultiplier) ? 59 : 114)
                            + value.GetHashCode();
                        changeMultiplier = !changeMultiplier;
                    }
                    else
                        hashCode = hashCode ^ (index * 13);
                }
            }

            return hashCode;
        }

        public override string ToString()
        {
            return GetType().FullName;
        }

        public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
        {
            return !Equals(left, right);
        }

    }
Infrastructure Layer (Persistence)
How to do it

The following table describes the namespaces/assemblies that must exist in the Infrastructure layer of a generic framework. After group and build a solution for compilation, with projects as mentioned next, it will become available to all applications:

Namespace Classes Description Remarks
DDDFramework.Infrastructure.
Base.Persistence
IUnitOfWorkFactory
IUnitOfWork
Include generic interfaces for unit of work objects responsible for transaction control
DDDFramework.Infrastructure
.Persistence
UnitOfWorkFactory
UnitOfWork
NHGenericRepository
NHGenericMethodRepository
Include implementation of repository interfaces as well as implementation of unit of work interfaces when using NHibernate ORM framework
DDDFramework.Infrastructure
.Persistence (*)
UnitOfWorkFactory
UnitOfWork
NHGenericRepository
NHGenericMethodRepository
Include implementation of repository interfaces as well as implementation of unit of work interfaces when using NHibernate ORM framework
CriteriaResult
QueryOverResult
Implement generic query object interfaces suitable to be used on the query object when using NHibernate ORM framework
DDDFramework.Infrastructure
.Persistence (*)
UnitOfWorkFactory
UnitOfWork
L2SQLGenericRepository
L2SQLGenericMethodRepository
Include implementation of repository interfaces as well as implementation of unit of work interfaces using Linq To SQL ORM framework
(*) dependent of the persistent data store used

For each specific application and according the ORM choosed to work with, the following namespaces must be implemented (as a single assembly) according the application domain model, database schema, etc:

Namespace Classes Description Remarks
DDDFramework.ProjectName.
Infrastructure.Persistence.Repositories
Derivated classes from NHGenericRepository class or L2SQLGenericRepository (or other) Each of the classes represent specific repositories required to persist some business entities Refer to the Structure for a DDD Application: Overview article for details
DDDFramework.ProjectName.
Infrastructure..Persistence.Entities
POCO classes with no business behavior Each of the classes represent the data entities objects required to persist business data on the database according the ORM framework used Refer to the Structure for a DDD Application: Overview article for details

 

Infrastructure Layer (Cross-cutting services)

Common and generic services (in pratice implemented as methods or funcions) that can be used in the infrastructure layer as well as in all the other layers must be organized on the namespace Company.Infrastructure.Base.Common: An example of these type of services is the LogManager. The code for this service is shown in the several articles mentioned in the previous section.

Common services (in pratice implemented as methods or funcions) that were implemented for a specific application, to be used on all the upper layers, must be organized on the namespace.Company.ProjectName.Infrastructure.Base.Common

Distributed Interfaces Layer (integration services)

Web-service clients and components used to communicate with external systems and specific to the application must be organized on the namespace Company.ProjectName.ServiceGateway.

How to do it

Web-service clients can be consumed as described on the following articles:

How to do it: Consume an ASP.NET Web Service through an HTTP web request

WCF Services: Client Development

WCF ReST Services: Client Development

Query Model Layer
How to do it

The Query Model layer is built according the Query Object design pattern and CQRS principles. Therefore it requires:

  • The data entities representing the business entities at low lever and defined on the “DDDFramework.ProjectName.Business.Model.Entities namespace”. These objects will be used to build the each of the queries;
  • Set of data transfer objects to encapsulate results to return to upper layer from where the querying request was sent; and
  • A query handler mechanism to execute the queries and obtain results.

The following table describes the namespaces/assemblies that must exist in the Query Model layer of a generic framework. After group and build a solution for compilation, with projects as mentioned next, it will become available to all applications:

Namespace Classes Description Remarks
DDDFramework.Base.QueryModel IQuery
IQueryResult
Page
QueryableResult
Interfaces for query definition, execution and page results, when implemented When using Linq providers the QueryableResult class is used and no other implementation of the IQueryResult interface is required.The implementation of the IQuery  interface is dependent of the ORM plataform used

For each specific application the following namespaces must be implemented:

Namespace Classes Description Remarks
DDDFramework.ProjectName.QueryModel.DVO Data transfer objects Simple classes to encapsulate result s and transfer to upper layer(s)
DDDFramework.ProjectName.QueryModel.Queries Implementation of interface IQuery Each of the classes represents a query built using the data entities and according the ORM framework used.Refer to thisarticle for an example of how the queries should be defined.The implementation of the IQuery interface is directly dependent of the ORM plataform used: The “::Execute()” method expects SELECT-like code to build the query that will be executed when fetching results through the IQueryResult interface implementation.

 

SOURCE CODE
How to do it

Download source code package here.