> If you know from day one that you need to support two databases rather than one, that would be enough to cause design choices that you wouldn't otherwise make.
I disagree (strongly in favour of of DI / ports-and-adapters / hexagonal).
I don't want my tax-calculation logic to know about one database, let alone two!
Bad design:
class TaxCalculator {
PGConnection conn;
TaxResult calculate(UserId userId) {..}
}
Hypothetical straw-man "future-proof" design:
class TaxCalculator {
MagicalAbstractAnyDatabaseInterface conn;
TaxResult calculate(UserId userId) {..}
}
I think a lot of commenters are looking at this interface stuff as writing more code paths to support more possible databases, per the middle example above. But I do the work to keep the database out of the TaxCalculator.
class TaxCalculator {
UserPurchasesFetcher userPurchasesFetcher;
TaxResult calculate(UserId userId) {..}
}
which is backed by JOINS inside. And I can do this refactoring in two mutually-independent steps. I can make my Postgres class implement UserPurchasesFetcher without thinking about TaxCalculator, and vice versa.
And if it's about the data integrity that JOINs could notionally provide, I no longer believe in doing things that way. The universe doesn't begin and end within my Postgres instance. I need to be transacting across boundaries, using event sourcing, idempotency, eventual consistency and so forth.
I disagree (strongly in favour of of DI / ports-and-adapters / hexagonal).
I don't want my tax-calculation logic to know about one database, let alone two!
Bad design:
Hypothetical straw-man "future-proof" design: Actual better design: I think a lot of commenters are looking at this interface stuff as writing more code paths to support more possible databases, per the middle example above. But I do the work to keep the database out of the TaxCalculator.