Adding Activity is a certain part of behavior, the fact that it can be
done to other things does not mean that you should inherit some the
other thing. That just gets you tighter coupling and make things more
difficult to change. Udi has a very great blog on this
-http://www.udidahan.com/2009/06/07/the-fallacy-of-reuse/.
Why couple up Adding Activity to the Customer Project object? What
does that buy you?
The only thing it buys you is read concerns, and in CQRS you've got
that separate :)
Explain the domain a little more to us, betcha you'll get some great
input if you give us a few more examples.
/E
IActivityContainer (or something)
AddActivity(whatever)
I then mark whatever AR implements it.
AddActivity command handler just depends on a IRepository<IActivityContainer>
void Handle(ActivityAddedToProject @event)
{
_activityContainerRepository(@event.ProjectId).AddProject(@event.SomeProperty);
}
Works great for me.
Certain (for now only one) types of projects have activities. The
system doesn't doesn't need to care that it's a "customer" project,
just that it's a project that you can add activities too, right?
Think about the commands you'll need to add activities, you'd never be
doing anything besides just adding the activity, so just build a
Repository<IActivityProject>
For now, it's totally fine if it just returns a Customer project, but
consuming code won't even know it's a Customer project, it'll just
know it's an IActivityProject
I'm not an ES guru, but I do this thing in NHibernate all the time.
_session.Query<IActivityProject>().First(p => p.Id == theId) would get
you ANY object that implemented IActivityProject.
I'd be very surprised if the ES tools didn't support the same thing.
Once again, the process of getting your thinking "behavior centric"
can be daunting. We've had this "data first" stuff shoved in our head
for years.... But I make it a point now to avoid writing persistence
code until the last minute, it's very liberating :)
Does that make sense?
>> My domain is about reporting time, and time is reported by a person on
>> a project. There a different kinds of projects; customer project,
>> built-in projects and non-attendance projects (the last one is also
>> considered to be built-in projects).
There are several ways to get around inheritance. Composition is one of them.
You can also use the State Pattern: Have a look at: http://en.wikipedia.org/wiki/State_pattern
Class Project {
ProjectType _pType;
.....
Project(..., ProjectType pType) -> ....
F1(...) -> _pType.F1(this, ....);
Fn(...) -> _pType.Fn(this, ....);
}
You can also inject behaviour based on intent.
Class Project<Scenario> {
Scenario _pScene;
.....
Project(..., Scenario pType) -> ....
F1(...) -> _pScene(this, ....);
Fn(...) -> _pScene(this, ....);
}
Project< Scenario> p = Projects.Get<Scenario>(x, SomeScenario);
This is a variant of the state pattern where the behavioral state is defines by the use case.
Hope it helps,
Cheers,
Nuno
Cheers,
Nuno
The project is the aggregate root, so
even if not using inheritance, I don’t know what type a specific Id
is.
The samples I've seen (and I think this is also true for the built-in
support in nCQRS) only have simple object creation strategies (aka new
T()) which won't work with an interface. I can see this beeing solved
with a IOC framework of choice though, so it will be able to get
support for a system where roles are made explicit.
The Time entity is of course an aggregate root.
What I don't understand is why Activity would be better as an
aggregate root of its own. For me that is like saying that all
entities should be realised as Aggregates. In this particular case I
have several issues:
* The activities and projects share them same life cycle. When a
project is deleted (if that would have been supported) the activities
would go too). The activities is of no intereset outside of the
project.
* When getting a project from the aggregate, I always want the
activities to be loaded since a lot of the behavior is depending on
them. (I know this could be solved with NHibernate, but when using
event sourcing I would have to do a call to GetById for each activity
in the project)
* Who should be responsible to load the activities to the project (so
it can perform its behaviors correctly)? I'm not aware of any lazy
loading support in any event store (I haven't been looking hard either
though).
> What I don't understand is why Activity would be better as an Aggregate
Neither it is :)
You are quite right, if your axis of behavioral change is to the Project Type. That is the same axis of change as the traditional OO class inheritance.
The problem of OO class inheritance is that it only encapsulates behavioral distinction downwards. We need to keep in mind that inheritance is about abstractions, not so much adding new features to existing ones.
So class inheritance is limited. Even more limited in OO languages that don't support multiple inheritance. But if it fits the bill for your particular problem I would use it.
What people have found is that are other more fine grained Axis of behavioral change as for instance the Role of an object in a particular activity. They find it more useful then go for class inheritance.
Extention Objects are not so much about abstraction as they are with context/feature/activity. This is not a replacement of OO class inheritance, but one that people find more useful.
To this you need to see a Class has a collection of features. Now you have your multiple project types correct? What kind of features they have in common and what kind of features each type of project has different?
So say F1 is common to all, and F2 is specific to one.
So when you do:
F1 f = AllProjects.get<F1>(projectId);
f.execute(...);
It does not really matter if is a project of type A, B, or C that implements its, as long as it implements it. In this case they all do because it is common to all of them.
F2 f = AllProjects.get<F2>(projectId);
f.execute(...);
Would result in an exception if the project does not implement that feature.
If the project whose id is given does not support the given feature, the query should return an exception.
So far so good right? Simple?
Now let's focus on the event stream for a moment. An event stream is a Bag of facts. Let's say we have a Bag of facts about all projects. Each fact as encoded the type of its subjects (Say the type of project)
Now we know that we can correlate the type of project with a Class. StandardProject, CustomerProject etc etc. If can have a mechanism that based on the type of project and a class correlation rule is able to rehydratate any of object of these classes we have a clever way to to get the objects.
Now here is the drill. We are not interested in the object themselves so much as we are interested in what they represent (Project) and their features (F1, F2, ....);
So the client never deals with ProjectA, ProjectB ... etc. It deals with F1, F2, ..., FN of a specific entity (project id); Remember as long as specific project implements a feature there is no exception.
For this to work, you would to think that one single Entity can have multiple implementations, as much as the features.
So you would need to think that we have an abstract concept called Project, one entity, one aggregate to which we add a variable set of features.
Have a look at http://www.ccs.neu.edu/research/demeter/adaptive-patterns/visitor-usage/papers/plop96/extension-objects-gamma.ps;
When we call: F1 f = AllProjects.get<F1>(projectId);
We are getting an Object Extention (F1) of generic concept we call Project whose specific implementation is unknown to the client (ProjectA, ProjectB, ProjectC ....);
The downside, is that the client can longer is casting because what we don't know its type of Class neither does it car. Every Time we want a different feature we need to go to AllProjects. (We call this a Repository in DDD, but in OO terms used to be called a Object Extent, the collection of all objects of a given type).
F1 f1 = AllProjects.get<F1>(projectId);
f1.execute(...);
F2 f2 = AllProjects.Cast<F2>(f);
f2.execute(...);
Powerfull no?
As usual different tools for different trades.
Software is an every evolved activity right? Our concepts change often. For instance, features will be added to ProjectA, other features will be changed etc etc.
Quite often when we change a Class we produce a new dll and deploy. We need to change the data schema of the say your relational database, some transformations etc. The same goes with the event stream? ... not quite :)
Event if you change an type of event, you don't need to change all records of given event. Indeed you should not.
Remember I wrote:
> Now let's focus on the event stream for a moment. An event stream is a Bag of facts. Let's say we have a Bag of facts about all projects. Each fact as encoded the type of its subjects (Say the type of project)
The type of project is important for rehydration purposes but not only. This type can be the type of the implementation class to be rehydrated not some random description. If you do that ...
Suppose that we change a given say feature F1 (each feature results in an event). We may even change the feature so much that we probably need to add or remove subjects from the event (FIELDS).
Now say that instead of changing implementation class ProjectA we create a new one ProjectAChanged.
Now say that when we now create a project of type A instead of using ProjectA implementation we use ProjectAChanged implementation from then on.
Voila instant system upgrade with no down time. New projects will use ProjectAChanged implementation while old ones will proceed as usual :). An they all share the same event stream ... Project.
Cheers,
Nuno