Part 1 - Commands and Handlers
Part 2 - Domain Events and the updating the read model
Ok, so in the last blog, I showed how I used commands to communicate intent of the user, and had the handler actually "do stuff" wit the domain. So far, we've got a pretty perfectly useless system. Huh? Whaaa?? Well, yeah, I created a MartialArtist object. Great, but it doesn't even have a single property that I could display on a screen. So while I'm tracking that people registered, we don't know anything about them, except that we created an Id for them. Take a look at the code in the Referrer object.
21: public void BroughtUser(string emailAddress)
22: {
23: var user = new MartialArtist(emailAddress);
24: UsersBrought.Add(user);
25: DomainEvents.Raise(new UserRegisterd(user.Id, emailAddress));
26: }
The interesting part is line 25. We raise a DomainEvent to record the fact that a User registered on our site. The code for UserRegistered is pretty simple.
1: using System;
2: using myDojo.Infrastructure.CQRS;
3:
4: namespace myDojo.Domain.Events.MartialArtists
5: {
6: public class UserRegisterd : IDomainEvent
7: {
8: public Guid Id { get; set; }
9: public string EmailAddress { get; set; }
10:
11: public UserRegisterd(Guid id, string emailAddress)
12: {
13: Id = id;
14: EmailAddress = emailAddress;
15:
16: }
17: }
18: }
It's just a small *almost Poco object that stores some data. Simple enough. The neat stuff is in DomainEvents.Raise. Let's go look
31: public static void Raise<T>(T args) where T : IDomainEvent
32: {
33: if (Container != null)
34: foreach (var handler in Container.GetAllInstances<Handles<T>>())
35: handler.Handle(args);
36:
37: if (actions != null)
38: foreach (var action in actions)
39: if (action is Action<T>)
40: ((Action<T>) action)(args);
41: }
Line 33 - 34 uses our IOC container to find every instance of Handles<T> in the system and calls Handle on each of them. This gives us a nice seam where we can create objects for the Read side to display things to render on screens. To display a martial artist details I made an object like this.
1: using System;
2: using System.Collections.Generic;
3: using myDojo.Infrastructure;
4:
5: namespace MyDojo.Query.ViewModels
6: {
7: public class MartialArtistDetails : ObjectWithIdentity
8: {
9: public MartialArtistDetails(Guid id):base(id){}
10: public MartialArtistDetails(){}
11: public string EmailAddress { get; set; }
12: public string Name { get; set; }
13: public string Biography { get; set; }
14: public string Belt { get; set; }
15: public int Stripes { get; set; }
16: public List<PromotionViewModel> Promotions { get; set; }
17: }
18: }
Nice and simple... Just a little DTO. We'll CRUD these things based on events. I typically write "Writer" objects that do this. One per ReadModel. Let's look at my MartialArtistDetailsWriter.
1: using myDojo.Domain.Events;
2: using myDojo.Domain.Events.MartialArtists;
3: using myDojo.Infrastructure;
4: using myDojo.Infrastructure.CQRS;
5: using MyDojo.Query.Infrastructure;
6:
7: namespace MyDojo.Query.ViewModels
8: {
9: public class MartialArtistDetailsWriter :
10: Handles<UserRegisterd>,
14: {
15: private readonly IReadModelRepository<MartialArtistDetails> _detailsReadModelRepository;
16:
17: public MartialArtistDetailsWriter(IReadModelRepository<MartialArtistDetails> detailsReadModelRepository)
18: {
19: _detailsReadModelRepository = detailsReadModelRepository;
20: }
21:
22: public void Handle(UserRegisterd @event)
23: {
24: var details = _detailsReadModelRepository.GetSingle(d => d.EmailAddress == @event.EmailAddress) ??
25: new MartialArtistDetails(@event.Id);
26: details.EmailAddress = @event.EmailAddress;
27: details.Id = @event.Id;
28: _detailsReadModelRepository.Store(details);
29: }
30:
57: }
58: }
I ripped out a bunch of stuff that's there if you git this code, cause I only wanna discuss the one handler for now. So on line 24, get a object if it already exists. If one doesn't I just create a new one. I'll probably create a new method on my read model repos that'll just create one if it doesn't exist to get rid of the ugly ?? new stuff. But let's ignore that for now. So I simply get the object from the repo, and set the email address and Id and save it. Really, it couldn't be much simpler. could it?
In fact, it's so simple, lets go ahead and look at some other code.
30: public virtual MartialArtist ChangeNameTo(string name)
31: {
32: DomainEvents.Raise(new MartialArtistChangedName(Id,name));
33: return this;
34: }
35: public virtual MartialArtist ChangeBioTo(string bio)
36: {
37: DomainEvents.Raise(new MartialArtistChangedBio(Id,bio));
38: return this;
39: }
This is code in MartialArtist that lets him change some info about himself. Not that this actually does nothing to the domain object itself. There's currently no reason to. We have no properties on MartialArtist for that stuff, since the fact that a MartialArtist has a certain name or bio would ever change anything about the behavior of the MartialArtist object right? We just raise domain events that let other systems (our Read Model) know that it happened so they can respond if needed. Of course the MartialArtistDetailsWriter DOES care about these events, because we do want to display his name and bio, right? Let's look at the complete DetailsWriter now.
1: using System;
2: using myDojo.Domain.Events;
3: using myDojo.Domain.Events.MartialArtists;
4: using myDojo.Infrastructure;
5: using myDojo.Infrastructure.CQRS;
6: using MyDojo.Query.Infrastructure;
7:
8: namespace MyDojo.Query.ViewModels
9: {
10: public class MartialArtistDetailsWriter :
11: Handles<UserRegisterd>,
12: Handles<MartialArtistChangedName>,
13: Handles<MartialArtistChangedBio>,
14: Handles<StudentPromoted>
15: {
16: private readonly IReadModelRepository<MartialArtistDetails> _detailsReadModelRepository;
17:
18: public MartialArtistDetailsWriter(IReadModelRepository<MartialArtistDetails> detailsReadModelRepository)
19: {
20: _detailsReadModelRepository = detailsReadModelRepository;
21: }
22:
23: public void Handle(UserRegisterd @event)
24: {
25: var details = _detailsReadModelRepository.GetSingle(d => d.EmailAddress == @event.EmailAddress) ??
26: new MartialArtistDetails(@event.Id);
27: details.EmailAddress = @event.EmailAddress;
28: details.Id = @event.Id;
29: _detailsReadModelRepository.Store(details);
30: }
31:
32: public void Handle(MartialArtistChangedName @event)
33: {
34: var details = TheDetailsForMartialArtistWithIdOf(@event.Id);
35: details.Name = @event.Name;
36: _detailsReadModelRepository.Store(details);
37: }
38:
39: private MartialArtistDetails TheDetailsForMartialArtistWithIdOf(Guid id)
40: {
41: return _detailsReadModelRepository.GetById(id) ?? new MartialArtistDetails(id);
42: }
43:
44: public void Handle(MartialArtistChangedBio @event)
45: {
46: var details = TheDetailsForMartialArtistWithIdOf(@event.Id);
47: details.Biography = @event.Bio;
48: _detailsReadModelRepository.Store(details);
49: }
50:
51: public void Handle(StudentPromoted @event)
52: {
53: var details = TheDetailsForMartialArtistWithIdOf(@event.StudentId);
54: details.Belt = @event.Rank.Belt;
55: details.Stripes = @event.Rank.Stripes;
56:
57: }
58: }
59: }
Well there ya go! We're now creating and updating objects for the UI to display based on events from the domain. The nice thing is, the domain knows NOTHING at all about the read model. In fact, all these Read models sit in a different assembly that Domain can't even reference. This assembly (myDojo.Queries) is what the website uses to display stuff on the screen. To execute commands - well I discussed that in my last blog, but the website NEVER actually uses the Domain assembly, only the Command and Query assemblies. This makes the controllers much simpler. Let's look at my UserController.
1: using System.Linq;
2: using System.Web.Mvc;
3: using myDojo.Commands.Users;
4: using myDojo.Domain;
5: using myDojo.Domain.Users;
6: using myDojo.Infrastructure;
7: using myDojo.Infrastructure.Web;
8: using MyDojo.Query.ViewModels;
9: using myDojo.Web.Models;
10:
11: namespace myDojo.Web.Controllers
12: {
13: public class UserController : DefaultController
14: {
15: private readonly IReadModelRepository<MartialArtistDetails> _detailsReadModelRepository;
16:
17: public UserController(IReadModelRepository<MartialArtistDetails> detailsReadModelRepository)
18: {
19: _detailsReadModelRepository = detailsReadModelRepository;
20: }
21:
22: public ActionResult Register()
23: {
24: return View();
25: }
26: [HttpPost]
27: public CommandActionResult<RegisterUser> Register(string email)
28: {
29: return Command(new RegisterUser(email, null), () => RedirectToAction("Edit", new {email}));
30: }
31: public ActionResult Edit(string email)
32: {
33: var readModel = _detailsReadModelRepository.GetSingle(d=>d.EmailAddress == email);
34: var viewModel = new EditMartialArtistForm(readModel);
35: return View(viewModel);
36:
37: }
38: [HttpPost]
39: public CommandActionResult<EditMartialArtistInfo> Edit(EditMartialArtistForm model)
40: {
41: return Command(new EditMartialArtistInfo(model.Id, model.Name, model.Biography), () => RedirectToAction("List"));
42: }
43: public ActionResult List()
44: {
45: var users = _detailsReadModelRepository.GetAll().AsEnumerable();
46: return View(users);
47: }
48:
49: }
50:
51:
52: }
Lemme show you that EditMartialArtistInfo command
1: using System;
2: using myDojo.Infrastructure.CQRS.Commands;
3:
4: namespace myDojo.Commands.Users
5: {
6: public class EditMartialArtistInfo : ICommand
7: {
8: public Guid UserId { get; set; }
9: public string Name { get; set; }
10: public string Biography { get; set; }
11:
12: public EditMartialArtistInfo(Guid userId, string name, string biography)
13: {
14: UserId = userId;
15: Name = name;
16: Biography = biography;
17: }
18: }
19: }
and handler
1: using System;
2: using myDojo.Commands.Users;
3: using myDojo.Domain.Users;
4: using myDojo.Infrastructure;
5: using myDojo.Infrastructure.CQRS.Commands;
6:
7: namespace myDojo.CommandHandlers.Users
8: {
9: public class EditMartialArtistInfoHandler : SimpleCommandHandler<EditMartialArtistInfo>
10: {
11: private readonly IAggrigateRootRepository<MartialArtist> _martialArtistRepository;
12:
13: public EditMartialArtistInfoHandler(IAggrigateRootRepository<MartialArtist> martialArtistRepository )
14: {
15: _martialArtistRepository = martialArtistRepository;
16: }
17:
18: protected override void DoHandle(EditMartialArtistInfo command)
19: {
20: var ma = _martialArtistRepository.GetById(command.UserId);
21: if (!String.IsNullOrEmpty(command.Name))
22: ma.ChangeNameTo(command.Name);
23: if (!String.IsNullOrEmpty(command.Biography))
24: ma.ChangeBioTo(command.Biography);
25: _martialArtistRepository.Store(ma);
26: }
27: }
28: }
There ya go... Hope this makes sense. Next up, we'll switch gears and discuss my Db4o repositories.
Good job, man! 2 parts is very cool. I will read it more and more. Thanks for your sharing!
ReplyDeleteThanks @thang. If you're a CQRS noobie, I'd strongly recommend checking out Udi and Greg Young's blogs and the DDD / CQRS users group. They're listing in the links section, right under "About Me".
ReplyDeleteThanks for the posts! I pulled myDojo from Github, but it doesn't build. The infrastructure project references a couple of files that don't exist: IQuery.cs and QueryActionResult.cs. Help?
ReplyDeleteI'm a git rookie! :) Sorry about that John. Gimme a minute and I'll have that pushed up.
ReplyDeleteIt's actually code for the next blog. I'll probably publish that tonight.
There ya go John... Git that a whirl.
ReplyDeleteInteresting. How do you avoid having multiple database writes when a user edits multiple fields in a form? In this model, wouldn't you get both a read and a write each time one of the MartialArtist properties is set?
ReplyDeleteedirne
ReplyDeletetrabzon
adana
yozgat
H5H5