There are two distinct issues:
1. Code duplication
2. Conceptual duplication and/or leaky models.
(1) is fairly simple to solve. You don't need to reimplement the code,
just reference the library (either the write library directly or a
library containing the rule that is referenced by both write and read
side). Code duplication is a smell and quite irky but not actually as
bad as (2).
I usually think about the read side as providing relations, classes or
categories of information. So If we have to provide a
GetAllowedCustomer I would have a view/table (if we're using sql,
something else otherwise) containing this information and just read
directly from it. The read model is not properly closed if we have to
go somewhere else to use it, so the "precise business rule" must
become an atom in the read side and live in the write side. Usually we
add a bit to the write side and every command changing this bit update
the read side accordingly (either using event-sourcing or not). But we
need to be sure that it is actually a business rule/logic and not just
some syntactical or structural requirement. For example, let's say
we're building a project management application for an outsourcing
company. They have a rule stating "A customer is only allowed to view
the projects that are part of contracts signed by their companies", so
we can have a data model like this:
customer N--1 company N--M contract N--M project
Assuming we are using SQL and have a write side normalized like that
we can have a view (I'm using * for natural joins):
projects_x_customer = customer * company * contract * project
And just use the view. This business rule is actually a
structural/association/navigation requirement, a byproduct of the
write side's structure. We don't leak nothing from the write model to
the read model because the bits of information in the model are built
in the structure of the model.
Sometimes the rule can be much more complex, let's look Fowler's
cuillen example for a bit:
http://martinfowler.com/articles/dblogic.html
This problem is hairier because the information is not dependent on
just the structure of our model but in many other pieces. It's
scattered and not atomic in the read side's POV, the read side would
have to inspect many bits in the write side and compose them in a way
that is too similar to the way the write side uses the same
information. So going DRY we need to make an atom out of it, I would
add a bit the write side and modify it accordingly (again using a data
model as the write side persistence):
customer 1--N order 1--N line item
customer 1--N cuillen month
In the command handler:
void Handle(PlaceOrderCommand cmd) {
var order = Repo.get<Order>(cmd.OrderId);
order.Place();
var customer = Repo.get<Customer>(order.CustomerId);
customer.AddCuillen(order);
}
This is a bad command handler because we are making the aggregates
(i.e. order and customer) dependent on each other's invariants (in
this case AddCuillen can't fail so we never rollback the changes to
order, but it isn't so in the general case) and thus aren't properly
isolated. I would be pragmatic in this case, mark down the technical
debt and move on to other issues. OTOH if the AddCuillen was fallible,
I would go to the business and talk a little about eventual
consistency and would probably add domain events (not necessarily
event sourcing) to solve this.
Now our read model only depends on the already existing bits of the
write model, without having to compose them to obtain an atomic bit of
information.
tl;dr; If the read model can't access the information as an atom from
the write side (either via the event's fields in event sourcing
solutions or by just traversing the write side in direct ways) we are
missing an atom in the write model. If the write model persistent
solution is a normalized relational store we have to denormalize it or
move some logic from the code to the database (e.g. adding views to
the write side).
Actually it only gets updated on commands, the client calls
GetAllowedCustomers which should map to plain tables and/or simple
views (if you're using SQL, something else otherwise).
On Oct 4, 2011 12:57 PM, "Riana" <ryana...@gmail.com> wrote:
>
> Hi guys,
>
> Yep, I was talking about shared business logic only on the server,
> those that are used on both read and write side.
>
> Michael # Do you mean CQ(R)S do not suit for those kind of situations
> and there are no solution ?
>
> Daniel # Sure, in general, the read model (the read tables) will be
> updated by commands.
>
> Now, the point of my question is about those business rules that rely
> on kind of "dynamic datas" such as current date for example.
> Consider a query on the read side called "GetAdultCustomers", where a
> customer is considered an adult if it is older than a given age.
> In this kind of situation, it seems to me that I have to duplicate the
> business rule (the minimum age to be considered adult) on both read
> and write side.
I would a "adult at date" in the read model. It's update on birth date changes and modifications in the adulthood law.
> I have to put it on the write side because it is a rule that I need to
> check to perform certain actions.
> I have to put it on the read side because I have to retrieve only
> adult customers for my scenario ...
>
> What is your opinions about this ?
Some dynamic checks may be harder to denormalize properly and in some situations duplication may be preferred, but usually you can just add a relation. With temporal data precomputation is useful and (sometimes) cheap.
Hi Daniel, thanks for your answer again !
So, I guess, in conclusion, we can consider that as one of the
situations, where CQ(R)S is not recommended.
Riana
On 4 oct, 21:22, Daniel Yokomizo <daniel.yokom...@gmail.com> wrote:
The questions that are particularly nasty and often need a domain model for reads not just a view model etc generally start with 'what if'