2

How do you solve a situation when you have multiple representations of same object, depending on a view?

For example, lets say you have a book store. Within a book store, you have 2 main representations of Books:

  1. In Lists (search results, browse by category, author, etc...): This is a compact representation that might have some aggregates like for example NumberOfAuthors and NumberOfRwviews. Each Author and Review are entities themselves saved in db.
  2. DetailsView: here you wouldn't have aggregates but real values for each Author, as Book has a property AuthorsList.

Case 2 is clear, you get all from DB and show it. But how to solve case 1. if you want to reduce number of connections and payload to/from DB? So, if you don't want to get all actual Authors and Reviews from DB but just 2 ints for count for each of them.

Full normalized solution would be 2, but 1 seems to require either some denormalization or create 2 different entities: BookDetails and BookCompact within Business Layer.

Important: I am not talking about View DTOs, but actually getting data from DB which doesn't fit into Business Layer Book class.

dee zg
  • 13,793
  • 10
  • 42
  • 82
  • 2
    It's as simple as not using the domain model for queries. Go directly to the DB. Read and writes are two different things. Aggregates are meant to be used for writes, not reads. – plalx Nov 17 '16 at 08:39
  • I am not sure if i read your comment correctly, but there might be missunderstanding. I do have separate DTO/DAO for reads and writes but at the moment I am not talking about writes at all, talking about reads only. So, its about situation when you have 2 different reads; different in terms that Read1 is a subset of Read2 (because it needs just part of data). In that case, should each read have its own Business Model representation or should collection of Reviews actually be a method GetReviews(int bookId) in Service Layer instead of being a property on Business Model entity? – dee zg Nov 17 '16 at 08:54
  • "each read have its own Business Model representation" This is the part I do not get. Reads shouldn't be concerned about business models (domain model) at all IMHO. You issue a query to the DB to gather exactly what is needed and then construct a DTO from the result. That's it. You may compose multiple DTOs together though. – plalx Nov 17 '16 at 18:35
  • huh...but what if you need to apply some business rules / operations on entities before giving them to view? are you suggesting to apply that logic in DTOs? – dee zg Nov 17 '16 at 19:32
  • What kind of business logic? Dou you have an example? Perhaps you confuse business logic with view logic. Again, business logic is executed during writes, not reads. – plalx Nov 17 '16 at 20:03
  • its very possible i confuse it. for example, let say you have a book and you keep record of its place on top ten lists (day by day) of books sold in a shop. When you present Book details view, you want to show longest streak the book was on number 1 position. ALso, you might want to calculate the price of your book based on its popularity so first you have to calculate PopularityIndex and then include it in some pricing formula. That's just 2 examples from the top of my head. So, you don't store those values in DB, as they are moving targets, and i don't think its view logic. – dee zg Nov 17 '16 at 20:31
  • 2
    Ok, I get what you mean now. Well as I guideline I'd tell you that any computed data that doesn't participate in business invariants checks does not have to be part of the domain model (but it can). Basically, computing the longest streak can be done very easily on the query side of things without involving the domain at all. However, if the logic is too complex then it may be worth to keep that logic within the domain model and persist the result of the computation while keeping it up to date by listening to domain events. – plalx Nov 17 '16 at 21:30
  • 1
    The price example could be different though because it is very likely that the price will be used in a business process (e.g. buying the book). You may want to be able to ensure that any data involved in it's calculation did not change between the time the price was computed and the time the product was bought (or perhaps eventual consistency is just fine or is the only way given the amount of data involved). – plalx Nov 17 '16 at 21:37
  • well, thats where the trick is. in this example price can be a result of calculation based on popularity index of all books in the store. that is exactly why i cant draw that strict line about fitting business rules into writea only because updating popularity indexes and top ten streaks for each book, theoretically, depends on all other books in a store and updating all on write to one seems like a huge overkill compared to calculating one when needed. or am i missing something? – dee zg Nov 17 '16 at 21:48
  • further on, popularity index might not be just number. it can also be a criteria for grantig authors some credit, reward, whatever. point beeing, it can also be, in my view, core part of business rules. – dee zg Nov 17 '16 at 21:51

2 Answers2

0

For me it sounds like multiple Query Models (QM). I used DDD with CQRS/ES style, so aggregate roots are producing events based on commands being passed in. To those events multiple QMs are subscribed. So I create multiple "views" based on requirements.
The ES (event-sourcing) has huge power - I can introduce another QMs later by replaying stored events.
Sounds like managing a lot of similar, or even duplicate data, but it has sense for me.
QMs can and are optimized to contain just enough data/structure/indexes for given purpose. This is the way out of "shared data model". I see the huge evil in "RDMS" one for all approach. You will always get lost in complexity of managing shared model - like you do.

hellxcz
  • 76
  • 4
  • so if i read you correctly, what you are saying is to have one query model for view in list and other for detailed view? if that is the case then i have 2 questions: 1) where do you keep those 2 QMs? are they part of the domain or? 2) if there is a business logic to be applied on both of those models, where do you imolement that? is it 2 aggregates inheriting same abstract aggregate? or something in your service layer? or some other way? thanks – dee zg Nov 18 '16 at 14:35
  • 1) QM they are Query part of CQRS. – hellxcz Nov 18 '16 at 15:12
  • 2) business logic should be executed at command processing. this leads to emission of event. event is consumed by QMs – hellxcz Nov 18 '16 at 15:13
  • nice framework is Axon (http://www.axonframework.org/). it implements CQRS/ES with aggregate roots, unit of work, command distribution/handling, events distribution/handling – hellxcz Nov 18 '16 at 15:15
  • seems like we went the same full circle as in comments to question: what happens if some business rule is many orders of magnitude more expensive to do on write than it is on read? (please look at comments beneath initial question) – dee zg Nov 18 '16 at 17:31
  • You wrote "business rule". It means that it should be solved at Command time. At this moment, you have to have enough data to do the decission and then produce event(s). Event is something, what happened - in a past. Query model is view based on produced events in structure, which is optimal for given purpose. You can have multiple QMs, consuming events from multiple command models. – hellxcz Nov 20 '16 at 15:20
  • so, if i understand you correctly, what you are saying that even if executing some business rule at Command time cost orders of magnitudes more expensive operations then executing it at Query time it should still be done that way. If that is the case, could you please elaborate why is it so? – dee zg Nov 21 '16 at 09:55
  • Well, I still can't find situation, where at command time the complexity would be higher than in event consumption. When I'm designing command model to support some computation heavy feature, I'll design it in the way to support it. Thats the responsibility of command model, to DO features. – hellxcz Nov 21 '16 at 15:16
  • Could you please refer to it in the context of calculating Book PopularityIndex (example in comments here under Question)? You can't do that kind of calculation at command/event time as its calculation depends directly on other books in the store that could be changed since last command (for example, it can be time dependant, also). So, you have an option: either you calculate for all records in DB at command time (when changing one) or you calculate only at query time. I have really hard time understanding why is it better at command time for that example? Thanks – dee zg Nov 21 '16 at 17:11
  • Consider the eager computation vs lazy computation. Eager one will store the value. Lazy one will do the computation during each read. Now lets think a little about PopularityIndex itself. I supposen you have Book aggregate root, which contains command handlers. One of those is changePopularity or rankHigher or whatEver. Those would produce popularityIndexChanged, beside others. Now the magic can happen. To this event you can subscribe listener, which can produce change in ranking query model (which contains information about all rankings, do that in ACID, or atomic increase,...) – hellxcz Nov 21 '16 at 18:51
  • The event subscriber can also emit a command, to some ranking aggregate root, which can do some business validation, or send emails, and at the end produce event, which already contains the computed rating order based on its internal state. Yes the state can be huge, but highly optimized just contains the rank related data. Such event, when will be event sourced, is much more valuable, because contains more accurate data. Such event can be later used for graph about ranking changes in time, prediction of books ordering, etc. – hellxcz Nov 21 '16 at 18:56
  • So if i understand you correctly, in a way, you are talking about redundant persistance where you keep things saved in form of events + in some form of last/current state (snapshots) on the query side? I worked on one PBX app where we stored all incoming calls events (ring, answer, cancel, etc...) which were stored as they came in but also we had a state machine that was saving current state of each call base on incoming events. Is that a setup you're describing? – dee zg Nov 22 '16 at 07:54
  • 1
    Basically yes. Its Event Sourcing style. Check that axon, it contains a lot of implemented infrastructure features to support CQRS/ES style of DDD. The persistency is not redundant, its not shared rdms storage. It is respecting Single Responsibility Principle - events are stored as they are, multiple QMs contains data which are important to each one not more, command model contains just enough to do business decissions. And EventStorage contains everything, whatchappened in system, so holds the truth, and can serve to reconstruct the system from scratch, to do business analitics – hellxcz Nov 26 '16 at 09:50
  • 1
    There is huge power in such system. You dont have just latest snapshot of your data in shared relation database, you have complete system behavior from day one. Another scenario of using ES is, that you can add another QM if neede (highly optimized for your read requiremers) and recompute it from day one till today, with no history miss. Its more storage occupied, but not 10 times more. Storage is cheap, but business data are expensive. – hellxcz Nov 26 '16 at 09:54
0

I had a very good result with the following design:

  • domain package contains @Entity classes which contain all necessary data which are stored in database

  • dto package which contains view/views of entity which will be returned from service

Dto should have constructor which takes entity as parameter. To copy data easier you can use BeanUtils.copyProperties(domainClass, dtoClass);

By doing this you are sharing only minimal amount of information and it is returned in object which does not have any functionality.

Marcin Szymczak
  • 11,199
  • 5
  • 55
  • 63