As many of you know, I’ve recently started at SSW, and of course that means I get to go File->New project much more frequently than when I was 100% product development.
I have a few rules when it comes to project layout which should be par for the course
- All domain logic is in its own project
- All database logic is in its own project
- All UI logic is in its own project
Unfortunately, people doing the scaffolding for MVC and EF6 seem to have a different set of priorities
- All views should be modelled straight off an entity
- All entities should contain all the validation required for the UI
Of course, this works fine for CRUD cases, but the moment you need some business logic, you’re in the deep end with no handrail.
Now, this is a project where the business logic is not so crazy that it warrants going full DDD - but it still deserves to have the domain easily tested and separated from the view, for maintainability.
I went for a design that gave View Models, Command Handlers, Repositories and Entities, but was limited to Transactional Consistency. It also allowed fast read-models and flexible Unit of Work usage. I thought I’d share the layout here. It is in EF6 code-first on ASP.NET MVC5, and is available on GitHub
1. Domain
The sample domain is an application for police to record speeding tickets. They enter in the license plate, the speed limit and the vehicles speed. They may optionally enter the vehicle’s make and model.
If a vehicle in the system already has the license plate, then the ticket is linked to the corresponding vehicle. Otherwise, a new vehicle is created and will eventually be synced with an external system.
Further, if the vehicle was not in the system, and the make and model was provided, it is used as the initial values for the vehicle. Otherwise, it is ignored.
At a later date, the officer may mark the speed ticket as paid.
Entities
There are two entities here - a speed ticket and a vehicle.
These will exist as POCOs in the domain project.
Commands
All commands are in the domain project, and are simply serialized method calls.
Command Handlers
Note this is not CQS. DB-generated ids are prevelant, and I thought it an ok tradeoff for the nicer URL that it offers over using guids. So commands that create entities will return the entity that was created.
This also lives in the domain project.
These methods are trivially testable, and do not make any assumptions about persistence - in fact, they’re quite close to the usual DDD repository pattern, with a repository per aggregate root.
However, I’ve been loose with transactions here - there are none in the command handlers, so it’s up to the application to set up the TransactionScope
or UnitOfWork
or whatever is cool.
The sample application will use a per-request UnitOfWork that’s committed in the controller.
Of course, in a perfect DDD world, the vehicle would likely be eventually consistent, and not related via a primary key, but I feel that’s complicating this simple project.
2. Data Access
I have purposely not extracted a generic EntityFrameworkRepository as base classes for these, for brevity.
The design here is that the repositories don’t do persistence themselves, but rely on the contexts to be the same instance, and be commited externally via the unit of work implementation.
To be honest, the unit of work implementation here is pretty close to pure laziness, but it works well enough for our use case.
Important: Search methods in this pattern should never return tracked entities Save methods need to ensure that they mark the model as modified after attaching, or it will never be saved.
3. UI
The views are trivial given the right models, so here’s the controller
We’ve made a critical decoupling here that will save your views from polluting the domain - view models are simply projections from the db sets - with .AsNoTracking()
. Of course, this could be abstracted again.
This is pretty powerful - the projections will be done in the database query, so you’ll only be getting what you need for the view model, not everything from every entitiy. Filtering is also trivially done from expressions - so again, in the query itself.
4. Setup injection
Autofac will be our container of choice - specifically the Autofac MVC QuickStart.
Beyond this though, let’s look at the additional setups - command handlers, repositories, and our data layers all need to be set up
Everything except the context and unit of work are transient dependencies by default, and we’ve made our per request.
5. Win!
That’s it! Run your project and be happy that you’ve isolated your domain, used entity framework and MVC, got dependency injection and everything is super squeaky clean.
Be sure to check out the sample project on GitHub!
Please if you have any suggestions for improvement - especially a more reliable (but still simple) transactional consistency, let me know on the twitters!