DDD, CQRS and testing with ASP.Net MVC

49
DDD, CQRS & Testing with ASP.Net MVC 26 Septembe r 2015

Transcript of DDD, CQRS and testing with ASP.Net MVC

Page 1: DDD, CQRS and testing with ASP.Net MVC

DDD, CQRS & Testingwith ASP.Net

MVC26

September

2015

Page 2: DDD, CQRS and testing with ASP.Net MVC

Just to introduce myself…• I’m Andy Butland• Blog (sporadically) at

http://web-matters.blogspot.it/• Find here a copy of slides and links to various

resources• Contact: [email protected] / @andybutland• I work for Zone – www.thisiszone.com - a digital

agency where I’m head of .NET development• We develop web sites and applications using

ASP.Net MVC and CMS platforms such as Umbraco and EPiServer

• We’re primarily UK based, in London and Bristol• But I’m lucky enough to live…

Page 3: DDD, CQRS and testing with ASP.Net MVC

… here, in Bassano del Grappa, Italy

Page 4: DDD, CQRS and testing with ASP.Net MVC

And what are we talking about…• We’ll be discussing some practices I follow when

building web applications using ASP.Net MVC• Digested and adapted from various opinions• Adopting best practices but in a practical way• For me at least… leads to a nicely organised,

testable and maintainable code base

Page 5: DDD, CQRS and testing with ASP.Net MVC

Standing on the shoulders…• Much of what follows comes from various

speakers, bloggers and writers in the .Net community

• Finally read “Domain Driven Design” by Eric Evans• Rob Conery questioned some of practices around use of

repositories• Blog series on DDD, CQRS and other MVC practices from

Jimmy Bogard and Gabriel Schenker at lostechies.com • Julie Lerman’s articles on DDD with Entity Framework at

MSDN• Adam Tibi’s implementation of a CQRS pattern using the

IoC container Ninject was heavily borrowed for my own use

Page 6: DDD, CQRS and testing with ASP.Net MVC

Contents

1

2

3

4

DDD with ASP.Net MVC and Entity Framework

CQRS using a mediator pattern Wrap up and Q&A

Unit testing our model, queries and commands

Page 7: DDD, CQRS and testing with ASP.Net MVC

1. DDD with ASP.Net MVC and Entity Framework

Page 8: DDD, CQRS and testing with ASP.Net MVC

Domain driven design (DDD)• Close replication of the real-world, business

problem• Focussed on an appropriate area of application

responsibility – the “bounded context”• Sharing of domain knowledge and vocabulary

between business experts, developers and, importantly, the software itself – the “ubiquitous language”

Page 9: DDD, CQRS and testing with ASP.Net MVC

A rich domain model• In essence, a DDD approach looks to push more

logic and behaviour to the application’s core model classes

• Contrasted with an “anaemic” domain model• Consider the domain as an “API” that you provide to the rest

of the application – providing appropriate access points and retaining control

• Lean more on code and less on SQL/data access• More expressive, easier to write and maintain – and test• But careful balance needed as can’t forget performance

Page 10: DDD, CQRS and testing with ASP.Net MVC

Control via the constructor• Avoid a public, parameterless, default constructor

• Prevents the instantiation of invalid objects by application code via the {} syntax, e.g.var product = new Product { Name = “Test product” };

Page 11: DDD, CQRS and testing with ASP.Net MVC

private Question(){ Options = new List<QuestionOption>();}

public Question(string code, string text, int orderInSection, QuestionSection section, QuestionType type) : this(){ Code = code; Text = text; OrderInSection = orderInSection; Section = section; Type = type;}

EF requires a parameter-less

constructor, but it can be private.

By requiring the rest of the application to only call this constructor, we can

ensure we have a valid initialisation of an instance.

Page 12: DDD, CQRS and testing with ASP.Net MVC

Control of property access• Use private property setters to prevent direct

access to properties• Prevents the modification of an instance into an invalid

state• Instead provide validated methods to allow related property

values to be set together, ensuring a consistent state is maintained

Page 13: DDD, CQRS and testing with ASP.Net MVC

public int? NumericalMin { get; private set; }

public int? NumericalMax { get; private set; }

public void SetNumericalRange(int min, int max){ if (min > max) { throw new ArgumentException( “Max parameter must be greater than the min parameter."); }

NumericalMin = min; NumericalMax = max;}

Properties can’t be set directly

Instead a method must be called, which can validate and ensure related

properties are populated together.

Page 14: DDD, CQRS and testing with ASP.Net MVC

Move behaviour into the model• Where possible implement business logic in the

domain model objects• Object graph must be populated sufficiently to support the

behaviour• Being POCO classes, there are no dependencies that

complicate unit testing

Page 15: DDD, CQRS and testing with ASP.Net MVC

public int GetMaximumAvailableScore(){ if (Type.Id == (int)QuestionTypeId.MultipleSelect) { return Options.Sum(x => x.Score); } else if (Type.Id == (int)QuestionTypeId.SingleSelect) { return Options.Max(x => x.Score); } else { return 0; }}

Logic can be encapsulated in

methods…

Page 16: DDD, CQRS and testing with ASP.Net MVC

[TestMethod]public void Question_GetMaximumScoreForMultiSelect_ReturnsCorrectValue(){ // Arrange var question = CreateMultiSelectQuestion();

// Act var maxScore = question.GetMaximumAvailableScore();

// Assert Assert.AreEqual(8, maxScore);} … which can be

easily unit tested.

Page 17: DDD, CQRS and testing with ASP.Net MVC

2. CQRS using a mediator pattern

Page 18: DDD, CQRS and testing with ASP.Net MVC

Command Query Responsibility Segregation• In essence, CQRS involves a separation between

read and write operations in an application• At scale we may have separate models – one

highly cached and de-normalised for reads, and a strict, validated model for writes

• Related concepts – “event sourcing”, “eventual consistency” – may be appropriate in certain situations

• Even for small to medium scale applications though, CQRS has benefits over a more typical CRUD style

Page 19: DDD, CQRS and testing with ASP.Net MVC

Benefits of CQRS• “Slices over layers” – breaking down application

by features rather than technical tiers• More meaningful data operations over CRUD

using the “ubiquitous language”• e.g. “ShipOrderCommand” versus

“SaveOrder(Order order)”• Single, discrete transactions with the ORM• More… but smaller, more focussed and single

responsibility principle adhering classes

Page 20: DDD, CQRS and testing with ASP.Net MVC

MVC CQRS Pattern: Reads

CONTROLLER VIEW

VIEWMODEL

VIEW MODELQUERY

HANDLER

VIEW MODELQUERY

DATABASE

Retrieve domain model objectsvia the Entity Framework context

Map to view model using AutoMapper

View model passed tostrongly typed view

Query passed as a GET parameterto the controller action method:• Might be a simple Id• Or something more

complex for a search feature

QUERYDISPATCHER

Controller calls query dispatcherpassing query.Appropriate query handler is foundfrom view model and query types(using Ninject)

Page 21: DDD, CQRS and testing with ASP.Net MVC

MVC CQRS Pattern: Writes

CONTROLLER

COMMANDRESULT

COMMANDHANDLER

COMMAND

DATABASE

Retrieve and update domain model objectsand persist via the EF context

Simple command result is returned (success flag, error message and - sometimes - return data).

Command may be POSTed as a parameter to the controller action method or created within the method

COMMANDDISPATCHER

Controller calls command dispatcherpassing command.Appropriate command handler is foundfrom command type(using Ninject)

Page 22: DDD, CQRS and testing with ASP.Net MVC

MVC CQRS Pattern: Validated Writes

CONTROLLER

COMMANDRESULT

COMMANDHANDLER

COMMAND

DATABASE

Map to domain model objectsand persist via the Entity Framework context

Simple command result is returned

Validated view model is mapped to command

COMMANDDISPATCHER

Controller calls command dispatcherpassing command.Appropriate command handler is foundfrom command type(using Ninject)

VIEWMODEL VIEW

View model is model bound to controller action method from form POST and validated.

In case of validation error, view model is re-populated and returned to view.

Page 23: DDD, CQRS and testing with ASP.Net MVC

public abstract class BaseController : Controller{

public BaseController(IQueryDispatcher queryDispatcher, ICommandDispatcher

commandDispatcher){ QueryDispatcher = queryDispatcher; CommandDispatcher = commandDispatcher;}

}

Base controller has injected dependencies for dispatching

queries and commands.

All controllers inherit from this.

Page 24: DDD, CQRS and testing with ASP.Net MVC

private static void RegisterServices(IKernel kernel){

kernel.Bind<IQueryDispatcher>().To<QueryDispatcher>();kernel.Bind<ICommandDispatcher>().To<CommandDispatcher>();

kernel.Bind(x => x .FromAssembliesMatching(“MyApplication.dll")

.SelectAllClasses().InheritedFrom(typeof(IQueryHandler<,>)) .BindAllInterfaces());

kernel.Bind(x => x .FromAssembliesMatching(“MyApplication.dll")

.SelectAllClasses().InheritedFrom(typeof(ICommandHandler<>)) .BindAllInterfaces());

}

Resolved using Ninject IoC container

Which also handles the convention based matching of

the appropriate handler to each query and command

Page 25: DDD, CQRS and testing with ASP.Net MVC

public interface IQueryDispatcher{ Task<TResult> Dispatch<TParameter, TResult>(TParameter query) where TParameter : IQuery where TResult : IQueryResult;}

The query dispatcher has a single method that takes two type parameters: the query definition and the query

result.

It takes the query definition as the argument and

asynchronously returns the result.

Page 26: DDD, CQRS and testing with ASP.Net MVC

public class QueryDispatcher : IQueryDispatcher{ private readonly IKernel _kernel;

public QueryDispatcher(IKernel kernel) { _kernel = kernel; }

public async Task<TResult> Dispatch<TParameter, TResult>(TParameter query)

where TParameter : IQuery where TResult : IQueryResult { var handler = _kernel.Get<IQueryHandler<TParameter, TResult>>(); return await handler.Retrieve(query); }}

The implementation retrieves the appropriate handler based on the type parameters from the services registered with the Ninject IoC container.

Page 27: DDD, CQRS and testing with ASP.Net MVC

public interface ICommandDispatcher{ Task<CommandResult> Dispatch<TParameter>(TParameter command)

where TParameter : ICommand;}

Similarly the command dispatcher has a single method

that takes a single type parameters: the command

definition.

It takes the command definition as the argument,

asynchronously process it and return a status result.

Page 28: DDD, CQRS and testing with ASP.Net MVC

public class CommandDispatcher : ICommandDispatcher{ private readonly IKernel _kernel;

public CommandDispatcher(IKernel kernel) { _kernel = kernel; }

public async Task<CommandResult> Dispatch<TParameter>(TParameter command)

where TParameter : ICommand { var handler = _kernel.Get<ICommandHandler<TParameter>>(); return await handler.Execute(command); }}

The implementation retrieves the appropriate

handler based on the type parameter from the

services registered with the Ninject IoC container.

Page 29: DDD, CQRS and testing with ASP.Net MVC

public class CommandResult{ public bool Success { get; set; }

public string Message { get; set; }

public object Data { get; set; }}

The simple command result usually just returns the status of

the command.

On occasion it’s useful to return some data, most often the Id of

a newly generated record.

Page 30: DDD, CQRS and testing with ASP.Net MVC

public async Task<ViewResult> Details(DetailsViewModelQuery query){ var vm = await QueryDispatcher.Dispatch<DetailsViewModelQuery,

DetailsViewModel>(query);

return View("Details", vm);}

The controller action method is very thin, delegating

immediately to the query dispatcher to create the view

model.

The query definition may be as simple as the

Id of the record to retrieve.

Page 31: DDD, CQRS and testing with ASP.Net MVC

public class DetailsViewModelQueryHandler : IQueryHandler<DetailsViewModelQuery, DetailsViewModel>{

public async Task<DetailsViewModel> Retrieve(DetailsViewModelQuery query)

{ValidateArguments();Context = Context ?? new ApplicationDbContext();

var result = new DetailsViewModel(); var question = await Context.Questions .SingleOrDefaultAsync(x => x.Id == query.Id); Mapper.Map(question, result);

return result;} Query handler queries the EF

context and maps the domain model object to the view model.

EF context is instantiated or

passed in via the constructor for

testing.

Page 32: DDD, CQRS and testing with ASP.Net MVC

[HttpPost][ValidateAntiForgeryToken]public async Task<RedirectToRouteResult> SignUpForActivity

(SignUpParticipantCommand command){ command.ParticipantId =

User.Identity.GetUserId(); command.AddedOn = DateTime.Now; var commandResult = await CommandDispatcher.Dispatch(command); if (commandResult.Success) { TempData["SignUpMessage"] = "Thank you for signing up. "; } else { // Handle failure of operation

} return RedirectToAction("Details", new {id = command.ActivityId, });}

Command is model bound from form post,

with additional details set in code.

Again controller action method delegates to the

command dispatcher execute the command.

Page 33: DDD, CQRS and testing with ASP.Net MVC

public class SignUpParticipantCommandHandler : ICommandHandler<SignUpParticipantCommand>{ public async Task<CommandResult> Execute

(SignUpParticipantCommand command) { ValidateArguments(command);

Context = Context ?? new ApplicationDbContext();

var result = new CommandResult(); var activity = await Context.Activities .SingleOrDefaultAsync(x => x.Id == command.ActivityId); var participant = await Context.Participants .SingleOrDefaultAsync(x => x.Id == command.ParticipantId);

if (activity != null && participant != null) {

...

Related entity details are retrieved.

Page 34: DDD, CQRS and testing with ASP.Net MVC

if (Context.ActivityParticipants .SingleOrDefault(x => x.Activity.Id == command.ActivityId && x.Participant.Id == command.ParticipantId) == null) { if (activity.NumberOfPlaces > Context.ActivityParticipants .Count(x => x.Activity.Id == command.ActivityId)) { var activityParticipant = new ActivityParticipant(activity, participant, command.AddedOn); Context.ActivityParticipants.Add(activityParticipant); await Context.SaveChangesAsync(); result.Success = true; } else { result.Success = false; result.Message = "There are not enough places remaining"; } ...

Further checks are made on the validity of the

command before execution.

Page 35: DDD, CQRS and testing with ASP.Net MVC

} else { result.Success = false; result.Message = "Participant is already signed up"; } } else { result.Success = false; result.Message = "Participant and/or activity not found"; }

return result; }}

Appropriate results with status and error details

are provided to the calling code.

Page 36: DDD, CQRS and testing with ASP.Net MVC

[HttpPost][ValidateAntiForgeryToken]public async Task<ActionResult> Edit(EditViewModel vm){ if (ModelState.IsValid) { var command = new AddOrEditCommand(); Mapper.Map(vm, command); var commandResult = await CommandDispatcher.Dispatch(command); if (commandResult.Success) { var newId = (int)commandResult.Data; // Do stuff with the generated Id of if we need to... return RedirectToAction("Index"); } }

return View("Edit", vm);}

View model is model bound and

validated. If something fails, return to view.

If validation passes, map the view model to

a command object.

Page 37: DDD, CQRS and testing with ASP.Net MVC

3. Unit testing our model, queries and commands

Page 38: DDD, CQRS and testing with ASP.Net MVC

Applying unit testing• By moving logic into our domain model we’ve

already made that easier to test• These have no dependencies so can simply be

instantiated with a known state before performing the operations under test

• With the use of the CQRS mediator pattern, our thin controllers mean there’s little value in testing them

• But we still have a logic that we should put under test in our query and command handlers

• These handlers are closely tied to data access code, specifically the use of Entity Framework

Page 39: DDD, CQRS and testing with ASP.Net MVC

Unit testing Entity Framework• Testing with a database

• Slow• Brittle - as hard to maintain a known, isolated data

set for tests• Testing with in-memory objects

• Of limited value as LINQ to Objects != LINQ to Entities

• Using Effort – an in-memory database generated on the fly - written and maintained by Tamas Flamich

• http://effort.codeplex.com/ • Fast• Mimics true behaviour of EF very closely

Page 40: DDD, CQRS and testing with ASP.Net MVC

Unit testing with Effort• Install via NuGet: PM> Install-Package Effort• Instantiate an empty, memory backed EF context• Seed the context with a known set of data

• Can use EF API for this• Or for faster tests and with less code, load from CSV

files• Create an instance of the query or command

handler, passing in the in-memory context• Execute the query or command• Assert the query result is as expected or the

command operations have persisted

Page 41: DDD, CQRS and testing with ASP.Net MVC

[TestClass]public class RoomViewModelQueryHandlerTests : BaseDataTest{ [TestMethod] public void SampleTest() { // Arrange SetUpContextAndTestData(); var handler = new RoomViewModelQueryHandler(Context); var query = new RoomViewModelQuery { Id = 1 };

// Act var result = handler.Retrieve(query).Result;

// Assert Assert.AreEqual("Kitchen", result.RoomDescription); }}

Instantiate handler passing in in-memory,

seeded context

Retrieve the result (the view

model).

Assert the view model properties

Page 42: DDD, CQRS and testing with ASP.Net MVC

public abstract class BaseDataTest{ protected ProductEntities Context { get; private set; }

protected void SetUpContextAndTestData() { InitContext(); SeedData(); }

private void InitContext() { var connection = Effort.EntityConnectionFactory .CreateTransient("name=ProductEntities"); Context = new ProductEntities(connection); }

...

Connection to in-memory representation of EDMX

meta-data created.

An alternative method supports the code-first

approach

Page 43: DDD, CQRS and testing with ASP.Net MVC

private void SeedData() { var categories = new List<Category> { new Category { Id = 1, Description = "Kitchens", }, new Category { Id = 2, Description = "Bedrooms", }, };

Context.Categories.AddRange(categories);

Context.SaveChanges(); }}

Data seeded using EF API (or CSV files can be used)

Page 44: DDD, CQRS and testing with ASP.Net MVC

4. Wrap-up and Q&A

Page 45: DDD, CQRS and testing with ASP.Net MVC

In summary• Even with a “light-touch” of architectural patterns

we have an application that provides• Rich domain model or core• Data access separated into discrete read and write

operations, adhering to the single responsibility principle

• Testable logic and data access code• Whilst these patterns can be taken a lot further

where warranted, even for small-medium scale applications there’s value in their use

Page 46: DDD, CQRS and testing with ASP.Net MVC

Lastly, some thanks…• To my colleagues at Zone

• Numerous discussions, questions and advice as we’ve evolved techniques and technologies over the years

• To everyone sharing knowledge, opinions and techniques

• Blogs, forum threads, talks and other community contributions that have influenced the thinking behind our work and this presentation

• Looking forward to more discussions this afternoon!

Page 47: DDD, CQRS and testing with ASP.Net MVC

Provided as Creative Commons 3.0 Share Alike:

http://creativecommons.org/licenses/by-nc-sa/3.0/

Page 48: DDD, CQRS and testing with ASP.Net MVC
Page 49: DDD, CQRS and testing with ASP.Net MVC

webnextconf.eu