professional-programming/antipatterns/mvcs-antipatterns.md

116 lines
5.7 KiB
Markdown
Raw Normal View History

2020-07-21 09:02:44 +02:00
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [MVCS Antipatterns](#mvcs-antipatterns)
- [Creating entities for association tables](#creating-entities-for-association-tables)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
MVCS Antipatterns
=================
In simple terms, Model-View-Controller-Services add a few more layers to the
MVC pattern. The main one is the service, which owns all the core business
logic and manipulate the repository layer.
Creating entities for association tables
----------------------------------------
You'll often need association tables, for instance to set up a many to many
relationships between users and their toasters. Let's assume that a toaster can
be owned by multiple users.
It might be tempting to create a `UserToaster` entity for this relationship,
especially if this relationship has some complex attributes associated with
(for instance, since when the toaster is owned by the user).
Let me pull a few quotes from the [Domain Driven
Design](http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) by Eric Evans:
> Design a portion of the software system to reflect the domain model in a very
> literal way, so that mapping is obvious.
> Object-oriented programming is powerful because it is based on a modeling
> paradigm, and it provides implementations of the model constructs. As far as
> the programmer is concerned, objects really exist in memory, they have
> associations with other objects, they are organized into classes, and they
> provide behavior available by messaging.
> Does an object represent something with continuity and identity— something
> that is tracked through different states or even across different
> implementations? Or is it an attribute that describes the state of something
> else? This is the basic distinction between an ENTITY and a VALUE OBJECT.
> Defining objects that clearly follow one pattern or the other makes the
> objects less ambiguous and lays out the path toward specific choices for
> robust design.
Evans, Eric (2003-08-22). Domain-Driven Design: Tackling Complexity in the
Heart of Software. Pearson Education. Kindle Edition.
Entities should model business processes, not persistence details
([source](http://blog.sapiensworks.com/post/2013/05/13/7-Biggest-Pitfalls-When-Doing-Domain-Driven-Design.aspx/)).
* In that case, `UserToaster` does not map to anything the business is using.
Using plain English, somebody might ask about "what toasters does user
A owns?" or "who owns toaster B and since when?" Nobody would ask "give me
the UserToaster for user A".
* The association table can be considered an implementation detail that should
not (in most cases) leak in the domain layer. All the code should be dealing
with the simpler logic of "user having toasters", not UserToaster objects
being an association between a user and a toaster. This makes the code more
intuitive and natural.
* It will be easier to handle serializing a "user having toasters" than
serializing UserToaster association.
* This will make it very easy to force the calling site to take care of some
business logic. For instance, you might be able to get all `UserToaster`, and
then filter on whether they were bought. You might be tempted to do that by
going through the `UserToaster` object and filtering those that have
`were_bought` to be True. At some point, you might be doing the same thing in
multiple places, which will decrease maintainability. If you were hiding that
logic in the repository, you wouldn't have that issue `find_bought_toasters`.
So in that case, I would recommend doing the following:
* Create a `User` and `Toaster` entity.
* Put the association properties on the entity that makes sense, for instance
`owned_since` would be on `Toaster`, even though in the database it's stored
on the association table.
* If filtering on association properties is required, put this logic in
repositories. In plain English, you would for instance ask "give all the
toasters user A owned in December?", you wouldn't ask "give be all the
UserToaster for owned by user A in December".
Note that Domain Driver Design recommends avoiding many-to-many relationships
altogether:
> In real life, there are lots of many-to-many associations, and a great number
> are naturally bidirectional. The same tends to be true of early forms of
> a model as we brainstorm and explore the domain. But these general
> associations complicate implementation and maintenance. Furthermore, they
> communicate very little about the nature of the relationship.
> There are at least three ways of making associations more tractable.
> 1. Imposing a traversal direction
> 2. Adding a qualifier, effectively reducing multiplicity
> 3. Eliminating nonessential associations
Evans, Eric (2003-08-22). Domain-Driven Design: Tackling Complexity in the
Heart of Software (Kindle Locations 1642-1647). Pearson Education. Kindle
Edition.
Imposing a traversal direction is probably the best solution. In our example,
it's not immediately evident which direction is the right one (a toaster being
owned by a user, or a user owning a toasters), because that depends on what
this application is doing. If we're working on an app that lets a connected
user see their toasters, then we would force the relationship to be
unidirectional user->toasters.
Sources:
* [7 Biggest Pitfalls When Doing Domain Driven
Design](http://blog.sapiensworks.com/post/2013/05/13/7-Biggest-Pitfalls-When-Doing-Domain-Driven-Design.aspx/)
* [Domain-Driven Design: Tackling Complexity in the Heart of
Software](http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215)