>> To unsubscribe from this group and stop receiving emails from it, send an email to clean-code-discussion+unsub...@googlegroups.com.
> clean-code-discussion+unsub...@googlegroups.com <javascript:>.
> >> To post to this group, send email to
> clean-code...@googlegroups.com <javascript:>.
> >> Visit this group at
> http://groups.google.com/group/clean-code-discussion
> <http://groups.google.com/group/clean-code-discussion>.
> >
>
> --
>
> ----------------------------------------------------------------------
> * George Dinwiddie * http://blog.gdinwiddie.com
> Software Development http://www.idiacomputing.com
> Consultant and Coach http://www.agilemaryland.org
>
> ----------------------------------------------------------------------
>
> --
> The only way to go fast is to go well.
> ---
> You received this message because you are subscribed to the Google
> Groups "Clean Code Discussion" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to clean-code-discussion+unsub...@googlegroups.com
> <mailto:clean-code-discussion+unsub...@googlegroups.com>.
Since this is also something that I encounter frequently and have a strong opinion about, i thought I’d share it.
I am addressing an anemic domain model here. Not a persistence model, a model just there for the orm and that can only be accessed from inside the persistence layer.
Which is what i think a anemic domain model boils down to in most applications. But i'll get to that.
I had a lot of time on the train so this got to be a rather long post.
So let’s get started.
Is it OO?
-----------
To me it is most definitely not OO since the inner state of an object is exposed but no behavior is exposed. This violates the basic idea of OO and the Tell-don't ask principle. ( I do follow the OO definition from Alan Kay, which is explained here ) To me an anemic domain model is just using mutable data structures with the logic on how to change the data elsewhere. Worst case: spread out over the system.
See Chapter 6 of Clean Code : "Why do so many developers automatically add getters and setters to their objects?"
There is a difference between Objects and data structures. Use what is needed when appropriate. When i talk about a domain model if talk about Objects with behavior. Data structures i prefer to be immutable by the way.
Is it bad?
-----------
I do consider it a bad practice. Not because it is not OO (although why do it in an OO language?). It is bad because it violates encapsulation. Even in procedural programming in C we have encapsulation.
Instead of having the domain objects having expose behavior, they expose their inner state. Which means that everything that has access to the domain object can modify it state. Regardless of any logic or invariants that may exist. So the Service may mutate the state of the "domain" objects, hopefully correctly. Unfortunately everything else can also change the state of the objects since it are "domain" objects and they are central to the application. Because everyone can access it, there is no encapsulation. So it is also very easy for bugs to sneak in. Or to do a quick fix. The larger the system gets, the more developers that are on the team, the bigger a problem this gets.
It also violates the single responsibility principle. Since the domain object is not responsible for the data it contains. That logic is spread out over the system. In an anemic domain model i assume that we can agree that ideally there should only be one service object that contains the logic and controls the state of the domain object for a given use case. But the problem is that it is very easy to have other services modify the "domain" object. Which eventually always happens. This results in really fragile code.
Why do people do it?
----------------------------------
I think because it is just procedural programming which is more natural at first than OO. Most developers i encounter who do this, don't do it out of conviction. They do it simply because that is how they have always done it. They often don't know the alternative and haven't really thought about it.
Another reason is that it is easier to do at first. Simply code your logic and mutate some state.
It think is also easy to go ‘fast’ if you can access anything from anywhere. You can change whatever you want wherever you want it. Which at first might seem less complicated than being constrained by objects not wanting to expose their state.
In practice the "domain" model often just serves as an input for the orm. So it is more of a persistence model than a domain model. But unfortunately a model that everyone can change. Something not everyone has a problem with.
Response to earlier posts
------------------------------
In response to the blog post from Mathias Verraes, i completely agree with it. ;-)
In response to the remarks from Caio.
The problem is not that there exist service objects. Gateways and application services are indeed service objects. But that the domain object hold no logic and are only there to sore the state. State that is mutable by anyone. A transaction script is focussed highly on the database. No layers, no model. Just simple logic and data changes. A domain model should not care or know about the database.
In response to George
The encapsulation is not just in your mind in C. As uncle bob likes to emphasise: "In C we had perfect encapsulation" :-)
My standard example
-----------------------------
In order to explain my point i usually use the classic example of buying a paper from a paper boy. Though it is probably overkill, i thought i include it here as well. Although it will make this post very long, it may help someone who stumbles upon this thread to clarify what i'm talking about :) Of course there is also the risk that people start to over analyse this example code. Please don't :-) The code is just some pseudo code with a minimum design. I’ve left out the constructors, getters and setters etc... But hopefully it does the job of explaining what i mean.
The anemic model way:
......................................
/********************
//The model
class Wallet {
private Money money;
public Money take(double price) {
//With logic if money is available
}
}
class Customer {
private Wallet wallet;
private Paper paper;
//With getters and setters
}
class PaperBoy {
private Wallet wallet;
private Stack<Paper> papers;
private double unitPriceOfPaper;
//With getters and setters
}
/********************
//The gateway
class ApplicationService {
private BuyPaperService service;
private Dao dao;
public void buyPaper(Long customerId, Long paperBoyId) {
Customer customer = dao.getCustomer(customerId);
PaperBoy paperBoy = dao.getPaperBoy(paperBoyId);
service.buyPaper(customer, paperBoy);
}
}
/********************
//The services
//The service with the logic
//Note the law of demeter violations/ train wrecks
class BuyPaperService {
void buyPaper(Customer customer, PaperBoy paperBoy) {
Paper paper = paperBoy.getPapers().pop();
double unitPriceOfPaper = paperBoy.getUnitPriceOfPaper();
//Seriously? anyone can just take my wallet and take what they want?
Money moneyForPaper = customer.getWallet().getMoney().remove(unitPriceOfPaper);
customer.setPaper(paper);
paperBoy.getWallet().getMoney().add(moneyForPaper);
}
}
//Some other Service that hides between all the other services in the application
class BadService {
void losePapers( PaperBoy paperBoy) {
paperBoy.setPapers(null);
//Nullpointers bound to happen.
}
void takeTheirWallet(Customer customer, PaperBoy paperBoy) {
final Wallet walletCustomer = customer.getWallet();
final Wallet fakeWallet = new Wallet();
//They just replaced my wallet!
customer.setWallet(fakeWallet);
final Wallet walletPaperBoy = paperBoy.getWallet();
paperBoy.setWallet(fakeWallet);
//They both have lost their wallets
//And the now both have the same wallet!!
partyTimeForMe(walletCustomer,walletPaperBoy);
}
void buyPaperFromEvilPaperBoy(Customer customer, PaperBoy paperBoy) {
customer.getWallet().getMoney().remove(1000000);
customer.setPaper(null);
}
}
Of course the bad service should not exist. The problem is that it is easy for stuff like this to do exist. Working with the code above you would need to look to everything that accesses the data members of the model and check if it is correct. You would also always need to know the state the object is currently in. The logic can easily be spread out over multiple services. So you need to know in which sequence which state changes were made and if the object is still in the correct state. If a bad Service made the paperboy lose all its papers and the BuyPaperService then buys a paper...
Or if the paperboy and customer have the same wallet...
Why make it so difficult? It should just not be possible to do these things. Fight cognitive overload.
The only one who may handle the papers is the paper boy. The only one who may handle a wallet is the owner of the wallet.
My preferred way
.............................
/********************
//The model
class Customer {
private Wallet wallet;
private Paper paper;
void buyPaper(PaperBoy paperBoy){
double price = paperBoy.getUnitPriceOfPaper();
if(priceToHigh(price)) throw new RuntimeException("i don't want to pay that much");
Money amount = wallet.take(price);
this.paper = paperBoy.buyPaper(amount);
}
}
class PaperBoy {
private Wallet wallet;
private Stack<Paper> papers;
private double unitPriceOfPaper;
Paper buyPaper(Money money){
if(money.isEqualTo(unitPriceOfPaper))
return papers.pop();
else
if(money.isLargersThan(unitPriceOfPaper))
throw new RuntimeException("You paid To Much. No change available");
else
throw new RuntimeException("You paid To little. More money is required");
}
}
/********************
//The gateway
class ApplicationService {
private BuyPaperService service;
private Dao dao;
public void buyPaper(Long customerId, Long paperBoyId) {
Customer customer = dao.getCustomer(customerId);
PaperBoy paperBoy = dao.getPaperBoy(paperBoyId);
customer.buyPaper(paperBoy);
}
}
//There are no service with the logic.
The scenarios from the bas service are no longer possible.
You can’t lose your papers outside of the PaperBoy class because only the paperboy has acces to its papers.
You can’t just take and replace someones wallet. Only the owner has acces to it.
You can’t just take whatever money you want. They will take their own money and give it to you when they want to.
If there is a bug in buying a paper, you know exactly where to look. It can’t be anywhere else. By encapsulating the data you have control over who modifies it.
To summarize
------------------
To me this is all about encapsulation. Not encapsulating your state makes it easier to modify state and just do whatever you want. Which is one of the reasons why this is so popular I think. But it has the big side effect that you lose control over who changes what and what state your “objects” are in. You need to chase down the logic to make sure it is not misused. Thats how big balls of mud are created IMO.
Fight cognitive overload! Keep it simple. Encapsulate data so you it doesn't change behind your back :-)
And if you are using an OO language, use OO :D
There.
I feel a lot better now i got this of my chest :-)
Regards,
Guido
----
Dit bericht (inclusief de bijlagen) kan vertrouwelijk zijn. Als u dit bericht ten onrechte hebt ontvangen, wordt u verzocht de afzender te informeren en het bericht te wissen. Het is niet toegestaan om dit bericht, geheel of gedeeltelijk, zonder toestemming te gebruiken of te verspreiden. Continuum Consulting NV sluit elke aansprakelijkheid uit wanneer informatie in deze e-mail niet correct, onvolledig of het niet tijdig overkomt, evenals indien er schade ontstaat ten gevolge van deze e-mail. Continuum Consulting NV garandeert niet dat het bericht vrij kan zijn van onderschepping of manipulatie daarvan door derden of computerprogramma’s die worden gebruikt voor elektronische berichten en het overbrengen van virussen.
"The vast majority of Java EE applications are built in a procedural way without a reason. The business logic is decomposed into tasks and resources, which are mapped into Controls and anemic, persistent entities. Such a procedural approach works surprisingly well until type-specific behavior for domain objects must be realized."..."All the shortcomings mentioned here are caused by the lack of object orientation in the persistence layer of most J2EE and Java EE applications."
"The solution is surprisingly simple: Model your application with real objects and don't care about the persistence at the beginning. Real objects imply cohesive classes with encapsulated state, related behaviour, and inheritance. Just put business logic into the domain objects and use inheritance where appropriate. JPA turns out to be really flexible for mapping rich domain objects into relational tables. The more complex is the logic you have to realize, the less viable is the development of applications in a procedural way with anemic domain objects."
"The outdated J2EE CMP persistence didn't supported inheritance or polymorphic queries, so the anemic domain object approach was the only viable solution. The J2EE patterns were designed to mitigate such restrictions. Although alternative solutions, such as JDO or Hibernate, support inheritance, the situation did not improve. Due to the implementation of outdated patterns, most of the domain objects in production are still anemic -- without any reasonable explanation.
With the ubiquity of JPA, it's time to rethink the way complex logic is developed. Persistence is a cross-cutting concern and should not have any impact on design or, in particular, on the amount of logic residing in domain objects. Only the remaining cross-cutting and, often, procedural logic should be implemented in a Control. You should have a good reason to split the state and behaviour into separate layers.
Rich domain objects have become a best practice and the anemic domain model (http://www.martinfowler.com/bliki/AnemicDomainModel.html) is an anti-pattern, at least in more ambitious projects."
> To unsubscribe from this group and stop receiving emails from it, send an email to clean-code-discussion+unsub...@googlegroups.com.
...Parnas' paper examined two conceptual abstractions for decomposing complex systems for the purposes of developing software. One was top-down functional decomposition, the approach that was gaining widespread acceptance under the label "structured design". Functional decomposition is based on an attempt to model the performance of the computer and software and to translate the requirements of the domain problem into those computer-based constructs. |
> To unsubscribe from this group and stop receiving emails from it, send an email to clean-code-discussion+unsub...@googlegroups.com.
I love Fowler's pseudoscientific graphs. This is from Patterns of Enterprise Application Architecture --> Part 1: The Narratives --> Chapter 1. Layering --> Chapter 2. Organizing Domain Logic --> Making a Choice
> To unsubscribe from this group and stop receiving emails from it, send an email to clean-code-discussion+unsub...@googlegroups.com.
> To unsubscribe from this group and stop receiving emails from it, send an email to clean-code-discussion+unsub...@googlegroups.com.
> To unsubscribe from this group and stop receiving emails from it, send an email to clean-code-discussion+unsub...@googlegroups.com.
To unsubscribe from this group and stop receiving emails from it, send an email to clean-code-discu...@googlegroups.com.
Hi, I am a newbie and seriously there a lot of things going over my head. Can anyone please summarise the discussion for someone like me to get some take home advice.
Thanks!
To unsubscribe from this group and stop receiving emails from it, send an email to clean-code-discu...@googlegroups.com.
Note to self:
--
The only way to go fast is to go well.
---
You received this message because you are subscribed to the Google Groups "Clean Code Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clean-code-discu...@googlegroups.com.
To post to this group, send email to clean-code...@googlegroups.com.
Visit this group at http://groups.google.com/group/clean-code-discussion.