professional-programming/antipatterns/mvcs-antipatterns.md

114 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 -->
2020-07-21 09:37:15 +02:00
## Table of Contents
2020-07-21 09:02:44 +02:00
- [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 -->
2020-07-21 09:14:24 +02:00
# MVCS Antipatterns
2020-07-21 09:02:44 +02:00
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.
2020-07-21 09:14:24 +02:00
## Creating entities for association tables
2020-07-21 09:02:44 +02:00
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/)).
2020-07-21 09:14:24 +02:00
- In that case, `UserToaster` does not map to anything the business is using.
2020-07-21 09:02:44 +02:00
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".
2020-07-21 09:14:24 +02:00
- The association table can be considered an implementation detail that should
2020-07-21 09:02:44 +02:00
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.
2020-07-21 09:14:24 +02:00
- It will be easier to handle serializing a "user having toasters" than
2020-07-21 09:02:44 +02:00
serializing UserToaster association.
2020-07-21 09:14:24 +02:00
- This will make it very easy to force the calling site to take care of some
2020-07-21 09:02:44 +02:00
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:
2020-07-21 09:14:24 +02:00
- Create a `User` and `Toaster` entity.
- Put the association properties on the entity that makes sense, for instance
2020-07-21 09:02:44 +02:00
`owned_since` would be on `Toaster`, even though in the database it's stored
on the association table.
2020-07-21 09:14:24 +02:00
- If filtering on association properties is required, put this logic in
2020-07-21 09:02:44 +02:00
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:
2020-07-21 09:14:24 +02:00
- [7 Biggest Pitfalls When Doing Domain Driven
2020-07-21 09:02:44 +02:00
Design](http://blog.sapiensworks.com/post/2013/05/13/7-Biggest-Pitfalls-When-Doing-Domain-Driven-Design.aspx/)
2020-07-21 09:14:24 +02:00
- [Domain-Driven Design: Tackling Complexity in the Heart of
2020-07-21 09:02:44 +02:00
Software](http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215)