Thoughts on Application Design

I have been working on some new applications and prototypes recently.  I historically have seen such moments as an opportunity to think about applications development.  I have been primarily involved in web development for many years now.  I have used a number of frameworks, mostly open source.  I have used Csla on a number of projects.  I also worked with many others, such as Prism, various home-made MVVP implementations, Angular, jQuery, ASP.NET MVC, and many others.  Over the years I came to a few ideas how to structure applications.  I am 100% certain my ideas here will be disputed by many people, including many that I know.  However, my conclusions come from a place of peace and experience.  I will likely rethink many of these ideas in the years to come, but I am pretty sure I like where I am going at this point.  I have been living in .NET World, but I think the approach will translate well to other languages.  I am also only going to document server side, I will try to talk about client side in a different post.  So, here we go.

On many projects I created smart objects that contain data and business logic. I have come to a conclusion that in modern applications where objects travel over the wire, this is not the best idea.  I think better design today is to separate data from logic.  In modern applications, sometime we want to use functional programming approaches, and combining functions with data seems wrong.  This approach also makes it easier to share models between multiple apps, such as mobile apps.  Simply put we have data transfer objects, literally, and another object that contains code that operates on DTOs.  Following the same logic, we would want to separate validation logic from data as well.  One could pick a framework, such as FluentValidation to achieve this goal.  What do we call classes that contain business logic that operates on DTOs?  I call them “services”.  Pick your name if you do not like this one.  This approach cleanly solves the issues of separation of concerns.  So, here is what we have so far, if we were to build a rolodex application. 

  • Person class, with properties only such as FirstName and LastName, and PersonId as unique identifier.
  • PersonValidator class with a Validate method that accepts a person and returns validation result.  This class will define standard rules, such as FirstaName being required.  It may also have a method that validates that no other person has the same first and last name
  • PersonService with methods such as create new person, and update or insert a person.  It should not have any persistence code.  This will be implemented in another class.

The persistence layer should be separate.  I have always been fond of repository pattern.  If you prefer others, such as active record, that will work as well.  The reason I like repository is because in my mind that follows proper relationship between DTO, validation and persistence, which is no relationship at all ideally.  Repository will accept a DTO and preform a persistence operation, such as update or insert.  So, now we are adding

  • PersonRepository with a method such as insert that accepts a person DTO.

Now we need another class that needs to coordinate between out moving parts so far.  In case of Asp.NET Core it is a controller.  I feel that even outside of web apps, controller is a good term.  It controls the flow of data through the system.  It will accept a DTO, validates it, performs any business logic calls via calls to PersonService, such as raise new person event, and other business logic functions.  Hence, we are adding

  • PesonController

What if we want to build more complex object graph?  We could add for example a collection property, such as List of Phones, to the person.  In order to maintain single responsibility principle, we would want to add PhoneValidator and PhoneRepository.  If we do not interact with phones directly, but only through a person, we would probably not create PhoneService.  In this case we would probably have person repository call phone repository to persist phones.

We also want to have interfaces on all classes that contain functional code, such Person Service and Person Repository.

We probably will have a number of other supporting classes, utility classes, etc…  Just group those by purpose, following single responsibility principle.  Now if we group our solution into projects, we will have the following

  • Business.Interfaces, no references
    • IPersonService
  • Data.Interfaces, references business
    • IPersonRepository
  • Business, references Business.Interfaces
    • Person
    • PersonValidator
    • PersonService
  • Data, references Data.Interfaces and Business
    • PersonRepository
  • Web, references all other projects, wires up interfaces to implementations via dependency injection
    • PersonController
  • Unit tests
    • Test using mocks as necessary or test implementations of interfaces that do persistence.
  • Integration tests
    • Repository tests

A slight variation would be as follows

  • Business.Interfaces, references Data.Interfaces and Business.DTO
    • IPersonService, will have a persistence call to IPersonRepository
  • Data.Interfaces, references business
    • IPersonRepository
  • Business.DTO no references
    • Person
  • Business, references Business.Interfaces, Business.DTO and Data.Interfaces
    • PersonValidator
    • PersonService
  • Data, references Data.Interfaces and Business.DTO
    • PersonRepository
  • Web, references all other projects, wires up interfaces to implementations
    via dependency injection
    • PersonController delegates all the calls to PersonService and contains virtually no code
  • Unit tests
    • Test using mocks as necessary or test implementations of interfaces that do
      persistence.
  • Integration tests
    • Repository tests

To summarize, crucial points are:

  • Separate data from everything else
  • Separate business logic from data
  • Separate persistence from logic and data.  Make sure that business logic does not know anything about data persistence implementation, but may know about persistence interfaces.
  • Have a coordinator class, controller or service that puts it all together
  • Rely on interfaces everywhere except DTOs.
  • You may type more, but you will have a very clean separation between layers.

Let me what you think.