Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

It's these kind of rules that mean I'm here wading through 5 layers of exquisitely decoupled nonsense that could be done in a few lines


Convert function between API and DB model does not sound complicated.

Storing the API model in your DB is really a bad idea.


I just listened to the DHH/Kent Beck/Martin Fowler discussion about TDD "damage" and both sides still seemed unconvinced by the end of it, but this exact example came up. It seems like SOA (whether it's DDD or Hexagonal or Clean or w/e) and TDD really push you towards this kind of layer bloat for one reason or another.

I'm (maybe obviously) on the SOA-skeptic side, my arguments generally are:

- Most apps aren't that big and don't need multiple layers of abstraction (i.e. the ORM and its models are totally fine). If the app starts getting too big for its britches, probably the best thing to do is make it 2 apps (too big: 2 apps is a good slogan here).

- Dependency injection and mocks are pretty bad ideas that are only occasionally useful (DHH uses the example of a payments gateway), but mostly push IoC through your whole app and make control flow confusingly backwards. Mocks are always in disrepair, and almost never accurately reflect what they're trying to mock, and thus ironically are big vectors for bugs that make it through testing.

- Having tons of unit tests tends to slow eng velocity to a crawl, because they test the parts of the application that aren't the requirements (were these functions called, what's the call signature of this function, was this class instantiated, etc.). Unit tests create a super-fine-grained shadow spec about the lowest level details of your application, and mostly I think they shouldn't ever be committed to a repo. They help during individual development, but then the whole team is stuck maintaining them forever whenever they make changes. They also tend to slow down CI because they're slow and always flaky.

- You almost certainly will never need to switch databases, let alone abstract across a database, a message queue, and a web api. It's not worth doing a "repo" abstraction and encapsulating those details.

- There are (now) really good libraries for almost anything you want to do. ORMs literally map database entities to domain entities--they just abstract the persistence for you. Sounds like a repo to me! We also have good validation, logging, monitoring, auth/auth etc. built into frameworks and 3rd party services. A lot of the things you might put into other layers or even other services are now neatly packaged into libraries/frameworks you can just use and SaaS things you can just buy, leaving you free to just implement your business logic.


I generally agree with the position that unit tests should be used with discretion, and that full coverage via unit tests often leads to thousands of low-ulitility or redundant tests, and so on. However I cannot agree with this:

>They also tend to slow down CI because they're slow and always flaky.

In my experience, unit tests are the most stable, the least flaky, because they touch the least code and often have very simple setup. An integration test might rely on four database tables being just-so, and go on to connect with two external services (and whether mocked, replayed, or live, flakiness may arise). That integration test is twenty times more valuable, but it is equally more likely to break for reasons tangential to its core assertions.


Oh, yeah I have experience with super flaky integration/UI tests too. I think a couple rules mostly keep things from getting out of hand in unit tests (never import `random`, use `freezegun`, etc.), but in my experience even this fails to corral the flakiness of 1000s of unit tests. I'm hopeful that property testing frameworks make a dent here, but I haven't had enough experience with them yet.

> An integration test might rely on four database tables being just-so, and go on to connect with two external services (and whether mocked, replayed, or live, flakiness may arise).

I actually feel most comfortable when my integration tests are essentially just API calls--or you could think of them as unit tests of the API. That way, if it's flaky in CI, it's flaky in prod too, and you know to fix it.

This is where mocks generally lead you astray, either they act like everything is fine, or they have some randomness/etc. built in and it causes flakiness. Any time you're testing a mock and not "real" code is a huge failure IMO; it debases the entire scientific process.

---

I guess I would summarize my testing position as "test all the API calls you support, pretty exhaustively". That's your spec. If you need some unit tests while you're developing something, definitely add them, but once you get things up to spec, just toss 'em. Otherwise you're binding future engineers to your implementation, which generally isn't helpful.


Agree on the points that you should never need to abstract over your database, orm, message queue, etc.

Disagree on dependency injection. I came from the globals/patch everything school of python, to the Fastapi/Pytest DI flavor, and it's a breath of fresh air. It's just so much easier to abstract the IO providers and swap them out with objects tailored to the test suite - eg for database, I create db objects which roll back any transactions between tests.

Hard disagree on unit tests. Maybe in other languages, but in Python, trying to develop even a moderately complex app without unit tests is a nightmare. I know, I've lived it. Even in an app with >85% unit test coverage, there was still a ton of friction around development on any of the interfaces which had low coverage.

Any gains in velocity of development almost always cost far more in debugging down the road.

I love python, but it is really prone to dumb footguns at runtime, NoneType errors in particular. You need to impose a lot of discipline to make large python apps enjoyable to develop on.


> Disagree on dependency injection.

I think DI makes a lot more sense in languages that are statically typed. In Python, the implementations I've seen use a lot of ABCs, "_in_mem" repos (for mocking/testing). I've been assailing mocks elsewhere, but my criticism of ABCs is that you write a bunch of boilerplate code, only to still just get a runtime error that your tests should catch anyway.

DI also pushes IoC... everywhere. There's not really anything inherently wrong with it, but the fact that it's backwards from typical control flow makes it confusing because the two always coexist. As a result, when trying to trace the behavior of code, you have to dig through lots of layers of configuration and/or implicit magic to discover what implementation is actually being called. Or, you're lucky and there's only ever a single implementation (this is most cases), but then why are you using DI in the first place?

FastAPIs handlers kind of blur the lines of DI. The way they've implemented route handlers conflates DI with callbacks. They could easily have done something like what Django does, and generated API docs by the type signatures of the handlers, then it wouldn't look so much like DI, just mapping URLs to handlers.

> eg for database, I create db objects which roll back any transactions between tests

I've experienced the pain of implementing this myself in Go, so I know it's not super trivial to set this up yourself, but that said, I get irritated when I see testing influencing design decisions in ways like this. Like, this is a big architecture decision made solely to support an additional database configuration. My opinion is that this conditional belongs in some kind of `if TESTING:...` block during app initialization, not literally injected into every route handler, etc.

> Hard disagree on unit tests. Maybe in other languages, but in Python, trying to develop even a moderately complex app without unit tests is a nightmare.

Oh I've had that pain too, but types have pretty much solved those issues for me. Or, weirdly I've only encountered:

- apps with (effectively) no tests

- apps with only unit tests

- apps with both unit and integration (and maybe UI) tests

I've never encountered an app with only integration or UI tests, but in my personal/contracting projects, I only ever write integration tests, and it's worked great. Coverage stats help a lot here too, you can see which code paths aren't taken and either fix the bug or delete the cruft.

No tests is clearly very bad, but I think integration tests are far, far better than unit tests.


> leaving you free to just implement your business logic.

Often, engineers (and HN) forget that code is a means to an end - not an artistic expression that provides value by its pure existence.


I mostly agree with you and DHH on that topic, however in my experience reasonably applied SOA/DDD actually shields me from this layering nonsense.

When your apps live as a service on the network or as a nicely isolated module in your repo, you no longer have a reason to over-engineer them. You don't need a grandiose architecture that solves every problem, instead you can make local decisions that are good enough in the specific context. Though, admittedly, I found it hard to sell such "inconsistencies" to other tech leaders, most folks aspire to those grandiosities.

> If the app starts getting too big for its britches, probably the best thing to do is make it 2 apps

That's the argument in favor of SOA, isn't it?


> When your apps live as a service on the network or as a nicely isolated module in your repo, you no longer have a reason to over-engineer them. You don't need a grandiose architecture that solves every problem, instead you can make local decisions that are good enough in the specific context.

Totally agree yeah, it's a huge boon to engineers to focus on their tickets rather than to have to constantly consider application architecture. I think as long as a framework exists (either an off the shelf one like Django or even--shudder--an in-house one) you get this benefit.

My problem (and maybe we agree here too) comes from the dynamic where the team decides on SOA, and embarks on this saga of implementing the "framework" themselves. That's a big loss in productivity as now you have 2 software projects: your framework and your business app.

> That's the argument in favor of SOA, isn't it?

Eh, not really. I don't think anyone disputes that big apps should be multiple services. SOA/DDD/Hex/etc. aren't novel for suggesting that, their novel claim is that you can tame the complexity of an enormous service by rigidly adhering to their principles when structuring and implementing it. My counterargument is that it's simpler and easier to split services before they become enormous.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: