Your DI framework is killing your code
blog.activelylazy.co.uk
blog.activelylazy.co.uk
Consider the "classical OOP" method signature:
email.send()
vs "the anemic way"
emailSender.send(message: Email)
If there is only one way to send email (SMTP), things works fine. But let's assume we have to implement another way (e.g. Mailgun API). In the "anemic" case everything is simple: extract interface from EmailSender, add new implementation, all other method signatures remain unchanged. Refactorings are simple and can be done easily in any modern IDE. However, in "classical OOP" way, we have to do something like:
email.send(sender: EmailSender)
Which changes Email::send signature, which leads to breaking API changes and extensive manual refactoring of the entire codebase.
The "anemic way" had won because it is much more reusable and maintains API stability. Incidentally, this approach have smoothed the transition to functional languages — "NounVerber" objects became modules (collections of functions), and value objects became immutable data types.
Only if you decide to change the signature, there are other ways to achieve the same result.
What if two threads need to send mail via SMTP and Mailgun...
Is this approach is so dangerous (as we're constantly told) why has it never caused me any problems? Maybe I'm missing something but I've never read an article that explains what it is I'm missing in a way that clicks.
Adding an optional parameter to an already existing method is not always a backwards-compatible change. For example, in C# this will effectively change method signature and break API.
http://haacked.com/archive/2010/08/10/versioning-issues-with...
http://haacked.com/archive/2010/08/12/more-optional-versioni...
...yet I've used this technique for years in VB.Net without issue....weird.
(let ((*emailer* mailgun-client))
(send email))
Or, using Go contexts (which are just a way of hacking in dynamic variables): ctx = email.NewContext(ctx, mailgunClient)
email.Send(ctx)
And in email.Send: emailer, ok := EmailerFromContext(ctx)
if !ok {
emailer = defaultSMTPEmailer
}With currying, you do away with OO and parameter initialization altogether.
@curry
def SMTPSender(host, message):
...
emailSender = SMTPSender(host='localhost')
# partially evaluated fn with `host` fixed
emailSender(message=message) emailSender = SMTPSender(host='localhost') # in container
emailSender.send(message) # in implementation
The consumer of the SMTPSender only interacts with the send() method.An email doesn't send itself nor does it interact with the transport. The transport (MUA/MTA for example) is responsible for delivering it via whatever method it happens to support. The container delivers the transport to the code. The consuming code doesn't care what the transport and method does, merely introduce the two and let them make sweet email love.
The article stinks of circa 2002 purist babble.
It's hard to swim the trends.
I agree. By the way, you can do that even in imperative languages. I do it all the time in Python or Go.
Says who?
I say the point of good OO design is to have very clear focused responsibilities for classes, very clear layers of abstraction, and concretely declared dependencies.
This way I know my business logic doesn't sprawl, is discoverable, can be replaced modularly, and tested extensively and throughly.
This author sounds like he has no idea what it takes to actually build a maintainable code base of domain-driven business logic.
The fact that he actually thinks "Order.SubmitForPicking(), UserAccount.UpdatePostalAddress(), and Basket.CalculatePriceIncludingTaxes()" are examples of good OO design is LAUGHABLE. This approach couples your data layer with your business logic, negates the ability to use mocks, and makes refactoring WAY harder than it needs to be.
On the other hand, I'm not sure the perspective is quite as important as it used to be. Modern dynamic languages make it harder to do bad things in memory, and passing value objects around is pretty much the arterial life force of the entire Internet. In any case one thing is sure: the more pieces of code that know the internal structure of a bit of data the more dependencies there are, and the harder the program is to work on. So to the extent you can have that knowledge in one place - the implementation of an interface - you're better off.
Perhaps more importantly, OO purism just seems a little anachronistic to me today. I think the key things that OO had to teach us have been mostly absorbed, and we've moved on. It's still there, but now it's part of the fabric of the art and not as obtrusive as it was in the 90's.
"See, what you are suggesting is actually a strong violation of the single responsibility principle as well as separation of concerns. This is exactly like the code I used to write. While it really is easier to manage when it's small, it's a naive approach that doesn't scale well.
In a real life example of what you are suggesting, our User and Building classes for instance ended up over 10,000 lines long, doing things even just tangentially related to their parents. The real kickers were the cross business object ones who required more than one. Is it on Class X or Class Y? Sometimes it would end up on both accidentally causing the angels to cry.
About two years ago we started a massive rewrite, and now we have lots of small consice objects that perform very specific tasks. They're logical, organized and dependency injected. I would never wish the reverse on anyone."
The author briefly mentions the Single Responsibility Principle and then tramples all over it, suggesting you put your 'code close to your data', shoving your verbs onto your models? Good luck ever refactoring. You'll have orphaned properties and methods that people are afraid to remove because they may anger the gods. You'll have flags, flags everywhere to control how your model does its work. Give me small, swappable, testable noun-verb classes any day of the week.
I guess the main flaw of both the article arguments and yours is assuming anybody knows a set of universal rules that apply to all software development.
TL;DR: rich domain model causes an explosion of coupling (your User class is now coupled to your database, your screen, your rendering engine, your printer, etc, when all it really is is some user data). And the rich domain model simply breaks down when you need functionality on two objects (should it be user.render(engine) or engine.render(user); either way one of your domain objects needs to know some "private internals" of another). With the anemic domain model these problems don't exist.
The real tl;dr is that if you can't run and test your domain model without a database, UI, external services etc, it likely is not a domain model in the first place.
Your misunderstanding is common when ignoring the provenance of the pattern: the place for it is "when you have complex and ever-changing business rules" (page 116 of Patterns of Enterprise Architecture). If "user.render(engine)" or "engine.render(user)" are operations in your domain model (and are, as I assume from your post, UI related operations) the domain model is being used as an architectural pattern rather than a business logic organization pattern, in which case none of the stated benefits hold because of misuse.
I won't even go into things like "Does your customer know what an OrderPriceStrategyFactory is for? No, then it’s not a real thing. Its some bullshit you made up.", but statements like "If we change how we contact customers then only the customer needs to change, not also the ReportBuilder" sound overly simplistic to me. The whole point would be that nothing needs to be changed. You just write a new ReportBuilder that implements the same contract/interface and switch the wiring. No changes in the existing ReportBuilder or Customer classes.
That, or maybe I'm just completely misunderstanding what I'm reading. Which isn't entirely impossible :-)
I like my objects as a unit of code organization and my value objects as simply that - an immutable representation of a value.
If you're trying to model the inner workings of a business, you probably don't want to be starting with the customer's view of it...
Until you need to generate a different type of report for the customer, render a different format email, or send the email via another process. At that point, you have 3 options:
1. inherit and override. So you end up with
* SpecialReportCustomer : Customer,
* NewEmailStyleReport : Report,
* SendViaProxyEmail : Email
It should be pretty obvious that this won't scale... at all.
2. Inject the desired sender or report generator into your object, depending on what implementation of the report builder or email sender you want. But why would the object have to know how to print itself, or send itself? Separation of concerns, people!
3. Break the interface of your class, make the methods accept a special reportBuilder or EmailSender... but why do that, when you can just do literally the opposite of what the author says, and have option 4:
4. Use an CustomerReportBuilder.Create(customer), or EmailSender.Send(email).
...and then you see why option #4 is your best choice, and why this article is objectively wrong in every way.
A report is something that belongs to business, but the fact that it could be sent via email is totally accidental and depending on the technology we use (as it is the persistence layer, or the protocol which exposes the app).
Domain concerns live on domain object (which knows only other domain objects), while application concerns live in services. And here is where you have interfaces, concrete implementations and, where necessary, factories to switch or add the implementation at the flip of a switch. Eg: a ReportMailSender which implements a ReportSender, that someday get replaced with a SlackSender implementing the same interface. You want both? Wrap them on a AggregatedSender.
Now your DI framework is getting really useful.
An example is comparing Objective C's string class, to .Net's. In objective C, path manipulation methods are on the string object itself; but in .Net, path manipulation methods are static methods in the Path class.
The .Net approach is better. Why? Do all strings represent paths? No. There's an unlimited number of ways and reasons for manipulating strings; and making them all methods of the string class is unsustainable.
If the code is readable, understandable, testable, and it does the job - who are you to say it's rubbish?
The world has moved away from strict OO because it's not very maintainable.
1) The problem that I've noticed with the traditional OO designs that I've worked with is that bits of similar functionality tend to get scattered about the code base. For example, the author creates a CreateReport() method in the Customer class. But if later, we have, say, an Account class (not equivalent to a Customer) that also needs a report, we find ourselves with two likely similar looking methods. I've found this to become problematic as the codebase grows, and ultimately your code exhibits a massive DRY violation.
2) His complaint about singletons is interesting but I think irrelevant. In Spring, for example, everything tends to be a singleton (the default), and stateless, so indeed, your code could be replaced by static methods. Not that this would be a good idea, although in some cases it might make sense. (The Play framework does this: http://stackoverflow.com/questions/5192904/play-framework-us...)
3) The author needs to read Yegge's http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom....
What's so terrible about static methods? If you need to perform some sort of calculation that doesn't mutate any state, isn't a static method the correct choice, at least when you're working in a language with no functions?
A procedure with no side effects is really just a function, is it not?
His proposals for how OO should work are half the reason I started shifting toward functional languages years ago. OO learning from Functional is a good not bad thing.
Order.SubmitForPicking() sounds great on paper, that’s until you realize in order to submit an order for picking, you’ll need other value objects such as Customer, Account, Warehouse, Picker, Robot, Supervisor, Audit information …. the list goes on and on. How are you going to inject these info into an Order? And do they really belong in an order? I certainly don’t want to see all of these info in my JSON when I retrieve a single order on the client side.
The moment you move behaviors close to a value object is also the moment a plain object is no longer a plain object. Furthermore, a behavior laden value object becomes hard to evolve, extend and modify. There’s no clear boundary of where data ends and behavior begins, you certainly cannot package them up as a library and give it to your clients, you’ll have to create a separate set of value objects for that and start a maintenance nightmare.
It's kinda sad that Java Code Geek rejected my comments (and many others') and we have to come to Hacker News to express our views.
Say you have a class CustomerHandler (stupid example, pseudo-C#) with a constructor like this:
public CustomerHandler(Guid customerId, ILogger logger, ISomeExtraDependency whatever) { ... }
Now, let's say another class needs to create CustomerHandlers for specific customers, but doesn't want to care how they are created. Autofac, the .NET DI framework I'm most familiar with, let's you inject factory delegates: public RequestProcessor(Func<Guid, CustomerHandler) customerHandlerFactory) { ... }
(Every type not mentioned in the Func<> is auto-injected.) This is very convenient because you can change the dependencies of CustomerHandler without messing up the rest of the code.Of course, even in the absence of such an auto-factory feature, you can still create a dedicated CustomerHandlerFactory class and inject that. That's not quite as convenient, but it's still a lot better to have only one extra place that has to know CustomerHandler's dependencies than having them all over your codebase.
My biggest problem with the enterprise Java way of doing things is the fact that you have to open 18 files just to figure out how the hell one small bit of functionality works.
For me, one of the clearest ways of analyzing a system in domain terms and working with it with business users to is with technology neutral specs in something like a Yourdon-style DFD, with a supporting data model and process descriptions.
It then becomes clear what the relevant nouns are in the domain: they fall in two basic groups:
(1) entities that correspond to named data items shown in flows or stores on the DFD (which will also, of course, be represented in the data model), and
(2) entities that correspond to named boxes and bubbles on the DFD, particularly processes, stores, and external interfaces.
A system whose implementation maps closely to this kind of requirements specification is a lot like an anemic data model system, with most of the classes being either immutable data objects or process objects (the latter of which may have "NounVerber" names if that convention is used), but that's because the processor is a real noun in the domain -- in a non-automated implementation of the system, its a role that a human actor would take on in executing the business process.
You do end up with state stored in (or stored externally and managed by) objects with methods which mutate state, but these tend to be the processor objects and the stores, not the data objects that are transferred along flows.
It seems to me that the "rich domain model" approach is mostly a failure of domain modeling that apply ideas from OO's origin in simulation modeling without recognizing that, in most other domains, the analogy to simulation modeling only works if you recognize that applications tend to be "simulations" of an idealized process executed by idealized technology-neutral actors acting on abstract data constructs, and that those idealized actors -- the processors -- are key elements of the domain model. Rich domain modeling ignores those and attributes their behavior somewhat arbitrarily to data objects, which end up giving data objects multiple, tangentially-related responsibilities (and provides lots of rooms for debating where certain responsibilities belong.)
on the other hand: IoC containers try so solve a let-down of oo...we have to come up with something better- functional programming?