lots of repetition in Aggregate root code

Hi, I’m starting my first spike into the ddd/cqrs/es world, I’m using the commondomain ARbase and the repository recomended by GES and GES. I think I’ll be using this in my tag line for a little bit.
My concern is that i’m seeing a lot of repetition in my code. I take a command, largely just a bunch of strings from a client, and handle that in my AR by calling a method that specifically handles that command. That handling generally amounts to applying those values to the state of the AR and/or children. Then I raise an event where I specify the properties that have changed in the state of my AR. Then I write an Apply method that takes that event and applies those property changes to the AR. Just to be specific, the processes is translate the command to change the state of the AR -> emit and store an event with those new values -> repopulate the AR using those events by passing them to Apply methods which map the state back to the AR.

If I have that right, Then there’s an awful lot of mapping back and forth. Sure the command is not a one to one with the event, there is processing and casting and implications there of, but the mapping of the AR state to the event and then the Event back to the AR state really is one to one.

Should I look at doing this with Automapper?

Thanks for your thoughts,

R

Should I look at doing this with Automapper?

no

Can you post some code examples?

Yea but it ain’t gonna be pretty.

I just posted another question where I went ahead and described my domain. I’ll give a brief description of the AR and then the process of what I’m doing here.

Basically and Email system

Converstaion -AR

– properites

– ConversationSender

– ConversationRecipient

– Message

– -- properties

– -- MessageSender

– -- MessageRecipient

public class Conversation : AggregateBase

{

public Conversation(CreateConversationCommand command)

{

Subject = command.Subject;

ConversationTypeCode = ConversationTypeCode.Inquiry;

RaiseEvent(new ConversationCreatedEvent(command.Subject, ConversationTypeCode));

AddConversationSender(command.CreateConversationBrideCommand);

AddConversationRecipient(command.CreateConversationProfileCommand);

AddInitialMessage(command.CreateInitialMessageCommand);

}

private void AddConversationSender(CreateConversationBrideCommand command)

{

if (_conversationParticipants.Any(x => x.ParticipantCode == ParticipantCode.From))

{

throw new Exception(“A Conversation can only have one Sender”);

}

var conversationBride = new ConversationBride(command);

_conversationParticipants.Add(conversationBride);

RaiseEvent(new ConversationSenderBrideCreatedEvent(conversationBride.City,

ConversationStateCode.Sent,

ConversationTypeStatusCode.New,

conversationBride.DisplayName,

conversationBride.Email,

conversationBride.EventBudget,

conversationBride.EventDate,

conversationBride.ExpectedGuestsMin,

conversationBride.FirstName,

conversationBride.LastName,

conversationBride.MemberId,

ParticipantCode.From,

ParticipantTypeCode.Bride,

conversationBride.Phone));

}

private void Apply(ConversationCreatedEvent vent)

{

Subject = vent.Subject;

ConversationTypeCode = Enumeration.FromValue(vent.ConversationType);

}

private void Appy(ConversationSenderBrideCreatedEvent vent)

{

_conversationParticipants.Add(new ConversationBride

{

City = vent.City,

ConversationStateCode = Enumeration.FromValue(vent.ConversationStateCode),

ConversationTypeStatusCode = Enumeration.FromValue(vent.ConversationTypeStatusCode),

DisplayName = vent.FirstName + " " + vent.LastName,

Email = vent.Email,

EventBudget = vent.EventBudget,

EventDate = vent.EventDate,

ExpectedGuestsMax = vent.Guests,

ExpectedGuestsMin = vent.Guests,

FirstName = vent.FirstName,

LastName = vent.LastName,

MemberId = vent.MemberId,

ParticipantCode = Enumeration.FromValue(vent.ParticipantCode),

ParticipantTypeCode = Enumeration.FromValue(vent.ParticipantTypeCode),

Phone = vent.Phone

});

}

Then in ConversationBride I have

public class ConversationBride : ConversationParticipant

{

public ConversationBride(){}

public ConversationBride(CreateConversationBrideCommand command)

{

City = command.City;

ConversationStateCode = ConversationStateCode.Sent;

ConversationTypeStatusCode = ConversationTypeStatusCode.New;

DisplayName = command.FirstName + " " + command.LastName;

Email = command.Email;

EventBudget = command.WeddingBudget;

EventDate = command.EventDate;

ExpectedGuestsMax = command.ExpectedGuests;

ExpectedGuestsMin = command.ExpectedGuests;

FirstName = command.FirstName;

LastName = command.LastName;

MemberId = command.MemberId;

ParticipantCode = ParticipantCode.From;

ParticipantTypeCode = ParticipantTypeCode.Bride;

Phone = command.PhoneNumber;

}

public virtual string MemberId { get; set; }

public virtual string Phone { get; set; }

public virtual string City { get; set; }

public virtual State State { get; set; }

public virtual DateTime? EventDate { get; set; }

public virtual int ExpectedGuestsMin { get; set; }

public virtual int ExpectedGuestsMax { get; set; }

public virtual long EventBudget { get; set; }

}

Now as I said this is an initial spike derived from the code we currently have. I know there are problems and that’s kind of why i’m posting this

For one I either have to have a paramerterless constructor and a bunch of get;set; s or perhaps I should have constructor that takes all the properties, and then not use the command. but that just introduces even more repetition.

Thanks for your thoughts.

R

Couple of things:

  1. you are not going to avoid the duplication of copying over information from command -> event

  2. the aggregate only needs to hold enough state to check invariants and apply new behaviors. In your example, you are only checking one invariant:

if (_conversationParticipants.Any(x => x.ParticipantCode == ParticipantCode.From))

{

throw new Exception(“A Conversation can only have one Sender”);

}

where _conversationParticipants is a collection of ConversationParticipant. But you are only checking of a participant code has already been added. So you can get away with a HashSet and then check _codes.Contains(…)

couple things,
a) you say don’t use automapper, and I have some pain that has been caused by miss-use of automapper, but wouldn’t this be just the place to use it?

b) you say I wont be able to avoid command -> event duplication but what about event -> aggregate depopulation duplication. just seems excessive.
Thanks

r

    public Conversation(CreateConversationCommand command)

    {

        Subject = command.Subject;

        ConversationTypeCode = ConversationTypeCode.Inquiry;

        

        RaiseEvent(new ConversationCreatedEvent(command.Subject, ConversationTypeCode));

        

        AddConversationSender(command.CreateConversationBrideCommand);

        AddConversationRecipient(command.CreateConversationProfileCommand);

        AddInitialMessage(command.CreateInitialMessageCommand);

    }

Why are you passing a command to the constructor?

Why are you maintaining so much state in the aggregate?

I’m very confused about a command carrying other commands? Eg command.createconversationbridecommand?

Ug. I mean it doesn’t surprise me that it’s all wrong.
so, here’s how I got to what is undoubtedly a ridiculous design.

My understanding is that it’s good form, when creating a new AR, to pass in the data it needs, through the constructor, to be in a good state. I could pass all the properties in individually but I figured I’d reuse the Command object that has all those properties on it. Seemed more concise.

You ask why the AR has so much state. By this I presume you mean why does it have so many children and grandchildren. I have my model and it feels like the AR may be bigger then necessary, but every time I try to break it down, I keep coming up with the some thing. Is it really that bad? Even if I break it down so that the messages are their own ARs the Conversation would still need to have the two ConversationParticipants.

Now for the commands containing sub commands. Here’s my thinking. A client hits the applicationService and says here’s some data about this guy and that guy and they want to have a conversation and here’s the body of the first message. The AppService now says Ok let’s create a Conversation AR. We need to pass it the data it needs to be in a consistent state. I can pass a bunch of individual parameters or I can wrap all the parameters in a nice organized manner and hand them off to the conversation and let the data pass it’s self along down stream.

When trying to figure out what’s making your eyes roll, here’s what I come up with.

  1. perhaps the AR doesn’t need all the information when it’s created. Perhaps I can create it with just the Subject and type and then call methods on the AR to add the child entities. Thus avoiding commands with subcommands

  2. perhaps when you talk about a command what is meant is what the client passes to the application service. In this case the data payload, if you will, will most likely be a bag of a bunch of properties. The name of the command with express what’s to be done with them. So that’s received by the AppService who’s job it is to create an AR and it just passes the properties loose to the constructor or whatever we discern from number 1 above.

2.1) is it just the case that the dtos that I’m feeding into the AR in order to populate it should be called something other than a command? Or is it the idea of passing a dto into the constructor that’s the issue?

Maybe I’m way off. And I hope I’m not wasting your time. I appreciate any direction you can give me.

thanks,

r

Hmmmm. the client is creating two “guys” and a conversation all in one post?

What is a “conversation” other than a series of messages between two or more parties about a particular subject?

Is a conversation really just a ui construct built off of the messages?

[bah. I hate domains where the UL contains the words “messages”, “services”, “domain”]

I’m sorry if I’m not going about this the correct way, I was being causal.
The client says here’s a recipient email address and here’s what we know about the logged in user now create an email thread.
This is just how gmail works. You create a message with a recipient, it creates a thread ( or conversation ) with some meta data about who’s involved and such.

Anyway, I get the sense that I need to investigate things in a more bite size manner.

Thanks to all who have helped.

R