Live data from Hacker News

Your DI framework is killing your code

blog.activelylazy.co.uk

41–50 of 59 posts

Re: Your DI framework is killing your code

#41
post #8
post #6

Yet another time it comes up. But now, after functional programming being commonplace, we at least know the answer to the "why" question. 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 i…

> Which changes Email::send signature, which leads to breaking API changes and extensive manual refactoring of the entire codebase. Only if you decide to change the signature, there are other ways to achieve the same result.

Exactly! This is why Lisp has dynamic variables:

    (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
    }

Re: Your DI framework is killing your code

#42
A couple of miscellaneous thoughts:

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....

Re: Your DI framework is killing your code

#43
> "A Customer can CreateReport(), a Report can RenderAsEmail(), and an Email can Send()"

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.

Re: Your DI framework is killing your code

#44
post #4

This is just the anemic vs rich domain model debate, the standard retort being https://blog.inf.ed.ac.uk/sapm/2014/02/04/the-anaemic-domain... . 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 functionalit…

This is a gross misunderstanding of a rich domain model. A domain model should depend on nothing externally, except via interfaces which are a part of the domain model.

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.

Re: Your DI framework is killing your code

#45
post #6

Yet another time it comes up. But now, after functional programming being commonplace, we at least know the answer to the "why" question. 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 i…

I remember being torn reading articles about anemic [considered harmful] around 2004 because being paranoid, immutable, functional, data first fits my mind a lot more.

It's hard to swim the trends.

Re: Your DI framework is killing your code

#46
I wonder if the author has ever worked on any relatively large projects. This has to be the worst way you can write your code. I know I wouldn't like working on anything written in that way. That's how you end up with 10,000 line files.

Re: Your DI framework is killing your code

#47

> "A Customer can CreateReport(), a Report can RenderAsEmail(), and an Email can Send()" 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…

Agree, I also find useful to split between domain concerns and application concerns.

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.

Re: Your DI framework is killing your code

#48
The other design smell with these noun-verbers is they’re almost always singletons. Oh you might not realise they’re singletons, because you’ve cleverly hidden that behind your dependency injection framework: but it’s still a singleton. If there’s no state on it, it might as well be a singleton. It’s a static method in all but name. Oh sure its more testable than if you’d actually used the word “static”. But it’s still a static method. If you’d not lied to yourself with your DI framework and written this as a static method, you’d recoil in horror. But because you’ve tarted it up and called it a “dependency”, you think it’s ok. Well it isn’t. It’s still crap code. What you’ve got is procedures, arbitrarily grouped into classes you laughably call “dependencies”. It sure as hell isn’t OO.

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?

Re: Your DI framework is killing your code

#49

Earlier quoted context omitted.

Couldn't you just add a new SendMethod optional parameter defaulted to the method that was used prior to the refactoring? No breaking API changes, no extensive manual refactoring of the entire codebase, as far as I can tell. This is how I always did refactoring for years, it's immediately obvious to support developers wtf is going on, and there's no requirement to have any "complicated" architecture on every single o…

> Couldn't you just add a new SendMethod optional parameter 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.

Holy cow it seems it seems you're right:

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.

Re: Your DI framework is killing your code

#50
post #21

My comments from the page, awaiting authorization: "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 in…

You know, most code just shouldn't scale on this manner. But yes, there's some code that must.

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.

Post reply on HN