CQS and shared Business Rules

已查看 716 次
跳至第一个未读帖子

Riana

未读,
2011年10月1日 09:49:072011/10/1
收件人 DDD/CQRS
Hi,

I'm facing a problem that some of you that apply CQS (Command Query
Separation)
pattern, may have already encountered.

In my implementation of CQS, I completely separate the read "channel"
from the
write "channel", that is, I use only the Domain Model for write side
and I
retrieve data directly from the persistence store (without using the
Domain
Model) for the read side. It is a kind of light CQRS if you will
except I use one database and same tables for Read and Write.

Now, I'm facing a scenario where, I need to use a business rule for
both write
scenario and read scenario, thus a shared business rule.

Concretely, for the read side, I have a method called :
"GetAllowedCustomer"
that returns a list of customers that is filtered according to a
precise
business rule.

The problem is that these business rules actually lives in the DM,
cause it is
used also for some write scenarios.

How do you usually handle this problem ? Do you duplicate the business
rule in
both sides read and write or do you try to put the shared business
rule
somewhere ? Or do you use another solution ?

Thanks a lot !

Riana

Daniel Yokomizo

未读,
2011年10月1日 10:58:032011/10/1
收件人 ddd...@googlegroups.com

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).

Riana

未读,
2011年10月2日 14:39:152011/10/2
收件人 DDD/CQRS
Hi Daniel, and thanks a lot for your clear answer, it makes sense !
Thank you !

Riana

On 1 oct, 16:58, Daniel Yokomizo <daniel.yokom...@gmail.com> wrote:

Tom Janssens

未读,
2011年10月3日 03:44:382011/10/3
收件人 ddd...@googlegroups.com
When I have problems populating my read side using CQS I start launching (only the necessary) events which allow me to build a proper viewmodel.

Riana

未读,
2011年10月3日 15:22:352011/10/3
收件人 DDD/CQRS
Hi Tom, I did not implement the Event Sourcing part of CQRS so I can't
replay them once they were triggered. I implement a simple version of
CQS.

I have one extra question Daniel. In fact I just need a confirmation.

According to the solution that you recommended, every time the client
calls "GetAllowedCustomers", the view tables are updated by the DM. My
question is: Is that really ideal in term of performance ? Do the
separation between Read and Write worth it in this precise situation ?

Thanks guys !

Riana

Daniel Yokomizo

未读,
2011年10月3日 19:29:522011/10/3
收件人 ddd...@googlegroups.com
On Mon, Oct 3, 2011 at 4:22 PM, Riana <ryana...@gmail.com> wrote:
> Hi Tom, I did not implement the Event Sourcing part of CQRS so I can't
> replay them once they were triggered. I implement a simple version of
> CQS.
>
> I have one extra question Daniel. In fact I just need a confirmation.
>
> According to the solution that you recommended, every time the client
> calls "GetAllowedCustomers", the view tables are updated by the DM. My
> question is: Is that really ideal in term of performance ? Do the
> separation between Read and Write worth it in this precise situation ?

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).

Michael Devenijn

未读,
2011年10月4日 07:34:262011/10/4
收件人 ddd...@googlegroups.com
Hello,

I had this problem too a while a go too. And came to the quite simple conclusion "It's not CQ(R)S if you share business logic"

After some modeling and re-analysis with the business we used the events to build a new readmodel (in your case a new table or new columns ??)  specifically for this read/filtering scenario. I think you are stuck in a RDBMS scenario (I was a lot in the beginning too)

regards

Michael

Rinat Abdullin

未读,
2011年10月4日 07:51:042011/10/4
收件人 ddd...@googlegroups.com
Clarification: it is possible to have business (domain) logic shared between server and clients and still have a valid CQRS/DDD/ES architecture.

Consider, for example, scenario with occasionally connected systems. Client generates events locally and then merges them with server's latest changes.

Best,
Rinat

Michael Devenijn

未读,
2011年10月4日 08:06:422011/10/4
收件人 ddd...@googlegroups.com
Maybe I understood it wrong but he/she is talking about sharing business logic between domainmodel/readmodel for querying purposes ?

I suppose you mean in your scenario (occasionally connected systems) the same B.L. is used on the server as on the client but still split between domain and readmodel ?

M.

Rinat Abdullin

未读,
2011年10月4日 08:36:502011/10/4
收件人 ddd...@googlegroups.com
Michael, you are correct. It's my fault that I didn't read the thread carefully.

Rinat

Riana

未读,
2011年10月4日 11:57:442011/10/4
收件人 DDD/CQRS
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 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 ?

Thanks guys !

Riana



On 4 oct, 14:36, Rinat Abdullin <rinat.abdul...@gmail.com> wrote:
> Michael, you are correct. It's my fault that I didn't read the thread
> carefully.
>
> Rinat
>
> On Tue, Oct 4, 2011 at 6:06 PM, Michael Devenijn
> <michael.deven...@dkma.be>wrote:

Daniel Yokomizo

未读,
2011年10月4日 15:22:472011/10/4
收件人 ddd...@googlegroups.com


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.

已删除帖子

Giacomo Tesio

未读,
2011年10月7日 05:51:262011/10/7
收件人 ddd...@googlegroups.com
Riana do not confuse CQS and CQRS.

I use CQRS/Event Sourcing in just two occasion! It's an implementation detail!



Giacomo



On Wed, Oct 5, 2011 at 1:47 PM, Riana <ryana...@gmail.com> wrote:
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:

Greg Young

未读,
2011年10月7日 10:47:362011/10/7
收件人 ddd...@googlegroups.com

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'

Sharas

未读,
2011年10月7日 11:11:502011/10/7
收件人 ddd...@googlegroups.com
Nasty questions might sometimes start with "WTF"? (I just couldn't hold it)

Riana

未读,
2011年10月8日 17:47:562011/10/8
收件人 DDD/CQRS
Hi !

Giacomo, you are right ! I think I confused CQS and CQRS here !
I have to review my architecture :-/

Thanks a lot guys

Riana

Teimuraz Kantaria

未读,
2018年2月27日 05:39:002018/2/27
收件人 DDD/CQRS
Hi Riana! I'm facing exactly same issue. Did you solve it?
回复全部
回复作者
转发
0 个新帖子