Here's a recommended minimalistic and minimum layers for a modern day line of business applications, especially the SPA ones
- App
- RichDomainModel - emphasis on rich
- RichDomainModel.Test
- RichDomainModelMapping
- Dto
- UnitTestFriendlyDal - Domain Access Layer, emphasis on unit-test friendly
1. App
- Hosts the UI
- Serves DTOs as JSON to browser using ASP.NET Web API. ASP.NET Web API gets and pushes DTOs to rich domain models
- Uses DTOs for data bag between UI and domain models
- For wiring dependencies, LightInject is highly recommended, it's a very capable IoC/DI container. For AOP concerns, LightInject has interception capability
- For JSON serializer/deserializer, NetJSON is highly recommended
- References RichDomainModel, Dto, LightInject, NHibernate
2. RichDomainModel
- Not only contains domain model's nouns, this also contains the domain model's business actions, verbs. See Martin Fowler's post regarding rich domain model & anemic domain model: http://www.martinfowler.com/bliki/AnemicDomainModel.html
- Receives and returns DTOs to App project
- Sample implementation:
using Dto; using System.Collections.Generic; using System.Linq; using UnitTestFriendlyDal; namespace Domain { public static partial class ProductionDomain { public class ProductCategory { public int ProductCategoryId { get; set; } public string ProductCategoryName { get; set; } public static IEnumerable<ProductionDto.ProductCategory> GetAll(IDomainAccess ds) { // http://www.ienablemuch.com/2014/10/proper-way-to-query-reference-entity.html return ds.Query<ProductCategory>().MakeCacheable().ToList() .Select(x => new ProductionDto.ProductCategory { Id = x.ProductCategoryId, Name = x.ProductCategoryName }); } } } }
- Has no virtual keyword on domain models' members even though NHibernate need domain models' members to be virtual. Uses Virtuosity.Fody to automatically make the domain models' members virtual
- Uses static classes to enforce schema-like partitioning of domain models. static partial classes are better than namespace, namespace could be bypassed, read here: http://www.ienablemuch.com/2014/01/i-love-the-word-the.html
- Though I mentioned above I love the word The, it's better to use an apt naming convention for the domain models. For AdventureWorks example, the tables HumanResources.Department, HumanResources.Employee, Person.Address, Person.Person and Production.Product tables domain models counterparts are: HumanResourcesDomain.Department, HumanResourcesDomain.Employee, PersonDomain.Address, PersonDomain.Person, ProductionDomain.Product.
- Do note that the names with Domain suffix are static partial classes, not namespaces, to see why static partial classes are better than namespace, see the link above. As for the need to use suffix/prefix, we cannot nest a class inside another class if it has the same name as the outer class
- References Dto and UnitTestFriendlyDal only
3. RichDomainModel.Test
- Tests the business logic / behavior of the rich domain models
- References RichDomainModel, RichDomainModelMapping, NHibernate, UnitTestFriendlyDal
4. RichDomainModelMapping
- Maps relational entities to RichDomainModel
- Sample implementation: https://github.com/MichaelBuen/DemoSpaArchitectureMvp/blob/master/DomainMapping/Mapper.cs
- References RichDomainModel and NHibernate only
5. Dto - data bag between UI and rich domain model
6. UnitTestFriendlyDal
- DAL is not data access layer nor repository. This is just a domain access layer that needed its Linq be mockable. The data access layer / repository is NHibernate itself, no need to abstract NHibernate. Don't pile abstractions after abstractions on top of NHibernate, especially if the ORM has a repository, unit-of-work and data access layer built in
- This is just a thin layer for ORM. NHibernate's .Query (Linq) is an extension method, extension methods can't be mocked properly, hence this interface is created. Abstracting the ORM is not the goal, it's the testability that we are after. Had NHibernate's .Query is not an extesion method, it can be mocked properly, this layer will not be needed. There's no need to add unnecessary abstractions on top of a capable ORM
- Sample implementation: https://github.com/MichaelBuen/DemoSpaArchitectureMvp/blob/master/UnitTestFriendlyDal/DomainAccess.cs
- References NHibernate only
Here's a sample of an SPA stack that applies the layers above
Sample Code: https://github.com/MichaelBuen/DemoSpaArchitectureMvp
Multi-tenancy concern
For multi-tenancy, it's better not to use schema on NHibernate. NHibernate doesn't have the capability yet to do proper multi-tenancy, Hibernate has
Shoehorning schema on NHibernate's ISessionFactory as a mechanism to do multi-tenancy would entail each tenant to have their own session factory. Disadvantage being, as the second level cache for common reference tables amongst tenants have a copy on each tenant's session factory, the second-level cache can't be shared effectively, or can't be shared at all. So changes on common reference tables on one tenant will not appear on other tenants' second level cache. On the other hand, if we isolate the common reference table to one session factory only, the drawback is we cannot navigate nor join tenant entities to those common reference tables as those entities live in their own session factory
I'll expound and make a simulation of this on another post
Database schema is a bit fancy as a multi-tenancy mechanism, especially if the ORM is not yet capable of mapping entities to schema on-the-fly or elegantly
So for now on NHibernate, it's better to use filters for multi-tenancy concerns
Naming guideline
http://www.ienablemuch.com/2013/01/when-your-codes-names-are-running-afoul.html
Happy Coding!