Saturday, 16 November 2013

Applied Domain-Driven Design (DDD), Part 4 - Infrastructure

If you come from database centric development (where database is the heart of the application) then this is going to be hard for you. In domain-driven design database and general data sources are not important, your application is persistence ignorant.

Put your infrastructure interfaces in to Domain Model Layer. Your domain will use them to get data, it doesn't need to care how, it just knows there is an interface exposed and it will use it. This simplifies things and allows you to focus on your actual Domain rather worrying about what database you will be using, where data is coming from, etc.

Infrastructure Contracts:
    public interface IEmailDispatcher
    {
        void Dispatch(MailMessage mailMessage);
    }

    public interface INewsletterSubscriber
    {
        void Subscribe(Customer customer);
    }
    //this lives in the core library and you can inherit from it and extend it e.g. ICustomerRepository : IRepository<Customer> then you can add some custom methods to your new interface. This can be useful if you want to uselize some of rich features of the ORM that you are using (should be a very rare case)
    public interface IRepository<TEntity> 
        where TEntity : IDomainEntity
    {
        TEntity FindById(Guid id);
        TEntity FindOne(ISpecification<TEntity> spec);
        IEnumerable<TEntity> Find(ISpecification<TEntity> spec);
        void Add(TEntity entity);
        void Remove(TEntity entity);
    } 



Infrastructure Implementation (lives in the Infrastructure Layer):
 

public class NHibernateRepository<TEntity> : IRepository<TEntity>
        where TEntity : IDomainEntity
    {
        public TEntity FindById(Guid id)
        {
            throw new NotImplementedException();
        }

        public TEntity FindOne(ISpecification<TEntity> spec)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<TEntity> Find(ISpecification<TEntity> spec)
        {
            throw new NotImplementedException();
        }

        public void Add(TEntity entity)
        {
            throw new NotImplementedException();
        }

        public void Remove(TEntity entity)
        {
            throw new NotImplementedException();
        }
    }

    public class SmtpEmailDispatcher : IEmailDispatcher
    {
        public void Dispatch(MailMessage mailMessage)
        {
            throw new NotImplementedException();
        }
    }

    public class WSNewsletterSubscriber : INewsletterSubscriber
    {
        public void Subscribe(Customer customer)
        {
            throw new NotImplementedException();
        }
    }


Example usage:
            

    public class CustomerCreatedHandle : Handles<CustomerCreated>
    {
        readonly INewsletterSubscriber newsletterSubscriber;
        readonly IEmailDispatcher emailDispatcher;

        public CustomerCreatedHandle(INewsletterSubscriber newsletterSubscriber, IEmailDispatcher emailDispatcher)
        {
            this.newsletterSubscriber = newsletterSubscriber;
            this.emailDispatcher = emailDispatcher;
        }

        public void Handle(CustomerCreated args)
        {
            //example #1 calling an interface email dispatcher this can have differnet kind of implementation depending on context, e.g
            // smtp = SmtpEmailDispatcher (current), exchange = ExchangeEmailDispatcher, msmq = MsmqEmailDispatcher, etc... let infrastructure worry about it
            this.emailDispatcher.Dispatch(new MailMessage());

            //example #2 calling an interface newsletter subscriber  this can differnet kind of implementation e.g
            // web service = WSNewsletterSubscriber (current), msmq = MsmqNewsletterSubscriber, Sql = SqlNewsletterSubscriber, etc... let infrastructure worry about it

            this.newsletterSubscriber.Subscribe(args.Customer);
        }
    }


Summary:
  • Infrastructure contains implementation classes that actually talks to the infrastructure IO, Sql, Msmq, etc.
  • Domain is the heart of the application not the Infrastructure (this can be hard to grasp if you come from DBA background).
  • Infrastructure is not important in Domain-design design, it facilitates the application development doesn't lead it.
  • Infrastructure should not contain any domain logic, all domain logic should be in the domain. (i guarantee that when you first start out, you will put logic in there without knowing it)

Tips:
  • When it comes to repositories try and just use a generic repository and stay away from custom implementations as much as possible i.e. IRepository<Customer> = good, ICustomerRepository = bad (it's never that simple, but a good general rule to work with).
  • When you first start out with infrastructure implementations, force your self not to put any if statements in to it. This will help your mind adjust to leaving all logic out of the Infrastructure Layer.
  • Take your time and try and understand what persistence ignorance really means, also try and research polyglot persistence this will expand your understanding.

Useful links:

*Note: Code in this article is not production ready and is used for prototyping purposes only. If you have suggestions or feedback please do comment. 

6 comments:

  1. How do I do if it take some time to get answer from the infrastructure?
    I have to get some data from a FTP server and it take some seconds up to some minutes.

    ReplyDelete
    Replies
    1. Hi, thanks for the comment!

      FTP is just a way of getting data, I would treat it the same as a web service. As it’s infrastructure I would also abstract it away.

      If your application needs to read this large dataset then you have 3 options (off the top of my head):
      1. Make data smaller
      2. Reduce latency time
      3. Accept the time it takes

      1. Make data smaller
      To do this you can do 3 things, extract less data (get just what you need), page it (get a small chunk of it at the time) or compress it.

      public interface ISomeData
      {
      IEnumerable GetSomeData();

      IEnumerable GetSomeData(int pageNumber, int numberOfResults);
      }

      public class FTPSomeData : ISomeData
      {
      public IEnumerable GetSomeData()
      {
      //de-compress data here
      //de-serialize here
      }

      public IEnumerable GetSomeData(int pageNumber, int numberOfResults)
      {
      //de-compress data here
      //de-serialize here
      //page
      }
      }

      To make read processes faster you could also pre-process this data and make it read friendly by pre-paging it, reformatting it so that there is less data to get or compress it and let the CPU do more work.

      2. Reduce latency time
      You could take all of the data from the FTP server and cache it on the local application server so that there is less latency. This might be a big one if the FTP server is on the other side of the world.

      3. Accept the time it takes
      If the dataset is very large, then it’s large. Increase the timeout, put a nice loading bar on to the screen and make users wait.

      I recommend options 1 and 2, however it depends on your situation.

      The key things is that your domain layer should not know how FTP does it’s magic. Hence FTPSomeData should be inside the infrastructure layer.

      I hope this helps and sorry for the late reply.
      Let me know if this has answered your question and what you have decided to do.

      Delete
    2. Thanks for your reply.

      Unfortunately, the FTP server is an old system that I'm not able to change. So option 1 is unfortunately no alternative.

      I try to cache the data I download to accelerate repeated downloads. It is not a big deal if the data presented is little outdated, but when the data is changed little all the time, I must download new data when requested.


      One idea I've been thinking about is that when a user request a particular data, it is returned directly from the cached data (if any). At the same time, a download from the FTP server is initialized, and when the data has been retrieved;the user interface is updated. I think it is acceptably to the user under the conditions given that it works so. But the question is how to implement it.

      Delete
  2. CustomerCreated class has a clean Handle method code with used interfaces. But MailMessage will have multiple parameters like From, To[], mail body, mail title. I will have email rules that send email to system admins. And these stuations will be dirty Handle() code. How can we fix this?

    ReplyDelete
    Replies
    1. Hi, thanks for the comment!

      You need to keep your Handle clean i.e. it should not care about To, From, etc. It needs to care about your domain objects only. So you would pass Customer, Invoice and other objects in to it and then inside the handle you would actually extract the required data from the objects and prepare your MailMessage, so it would look something like this:

      public void Handle(CustomerCreated args)
      {
      MailMessage msg = new MailMessage();
      msg.To = args.Customer.Email;
      msg.From = //my company from address config
      msg.Body = String.Format("Hello {0} {1}, to active your email click here...",
      args.Customer.FirstName, args.Customer.LastName);

      this.emailDispatcher.Dispatch(msg);
      }


      Let me know if this makes sense, if it doesn't I will update my domain-driven design Github repository (https://github.com/zkavtaskin/Domain-Driven-Design-Example) with an example.

      Delete
  3. For those using EF, here's a generic Entity Framework Repository example (and general writeup on repositories): http://deviq.com/repository-pattern/

    Also for caching, keep caching responsibility separate from your persistence logic by using the CachedRepository pattern: http://ardalis.com/introducing-the-cachedrepository-pattern

    ReplyDelete