Hacker News new | ask | show | jobs
by cassiogo 1550 days ago
> replacing the PostgresProjectionsRepository with an MemoryProjectionsRepository

Not as simple if you are using any "advanced" PG feature. If you testing simple select statements thats probably fine.

2 comments

> Not as simple if you are using any "advanced" PG feature.

If those "advanced" features leak into your domain, you have tight coupling, dependencies and poor testability (the latter is screaming that you have the first problems above all).

If anything, such advanced features are best tucked away behind abstractions.

  SuperMarketProjection {
    CompanyList: GetAllWithinTravelTime(Time)
    Company: GetOne(id)
    Boolean: Write(Company)
  }
This "projection" can use postgis, may store data in json columns, might have advanced materialized views adjoined on read, and so on. For the adapter, (edit: for the user of the adapter) it matters not.

Which has only benefits. And one tradeoff: it requires carefull design and thought, which requires information that you often lack at time of designing.

If you're writing software for an audience of "one" (one organization or such) Is it really that bad to have it be tightly coupled? You're likely writing something so specific that no one else will be using it anyways
If you write software that no-one uses I guess its fine to write it using brainfuck on a atari, I guess.

But I consider myself a user too. I consider myself-in-ten-years a user too. I'm sometimes still maintaining some crap that I wrote 20 years ago and boy I wished I didn't cut that many corners back then.

Tight coupling brings a large range of problems, loosely coupling a large range of benefits. The only two major downsides of loosely coupled code that I have encountered, are when the coupling is done at the wrong place: poorly abstracted code is worse than not-abstracted code, often.

The second downside that in order to properly (un)couple code, you need knowledge that you often lack at the point of writing (often resulting in downside #1) and that gaining the knowledge can significantly stall a project (aka: we cannot start building because we haven't found the perfect model yet).

Other than that: loosely coupled code is much more fun to work with, for one, which makes that I like working on some projects, bu really dread working on others. Having fun, alone, is enough benefit for me. Even in my private one-off-tools (which then turn out to not be one-off but are dragged along for literally decades, sigh).

From a purist's point a of view maybe. But as soon as it's a non-trivial DB with constraints/referential integrity/triggers/etc. it quickly become impossible to test those without an actual DB.
> But as soon as it's a non-trivial DB with constraints/referential integrity/triggers/etc

Which is probably a reason not to use them? For me it clearly is. Everything is a trade-off, and "not being able to test it" such a big downside that I'd rather not use it at all. Amongst all the other downsides that tight coupling comes with, for me, it almost always balances towards the "just avoid it".

For example: I've worked on projects that were highly coupled to Oracle and MySQL databases, where large pieces of businesslogic (arbitrarily) were scattered over the database. So when Oracle went directions (and for prices) that my customer could not warrant, and when MySQL simply no-longer could handle the performance, we had to rewrite a lot (in postgres, mostly). Often pieces from scratch entirely. One downside of tight coupling is "lock-in". There are many more. So in that project we avoided the same mistake that brought us there: we avoided most fancy features and kept all business logic in the business-logic-layer and out of the DB. We tucked away fancy PG features behind simple and clean "facade patterns".

Sure you can, you just need to write mocks that handle constraints, integrity, etc.....
I don't follow this. My assumption for the repository interface is something like (language, error and domain agnostic):

   interface Repository<T> {
      FetchAll() T[]
      Fetch(id) T
      Persist(T) id
   }
Why would the SQL statements be reflected in the behaviour that the interface is providing?