IfWUvQqsh.md
A Dependency Is Not Just a Relationship Where One Piece of Code Uses Another
Summary
A deep dive into what dependencies really represent in real-world services, going beyond the simple 'one function calls another' definition. Using a TypeScript coupon service example, it explores how dependencies carry implicit contracts (input/output, failure handling, performance, side effects, repeatability, lifecycle), how hidden dependencies like current time, environment variables, or global state can silently affect behavior, and how tightly coupling business logic to a specific technology like Prisma spreads change into unrelated code. It also covers dependency direction, why interfaces don't automatically reduce coupling unless expressed in the service's own language, when interfaces are unnecessary overhead, how to decide whether failures (like email delivery) should propagate synchronously or be decoupled via events, risks introduced by external service dependencies, signs that a long dependency list reveals an overly broad responsibility, lifecycle mismatches between shared and request-scoped state, and the importance of test doubles preserving real contract behavior (e.g., duplicate ID handling). Closes with a checklist of questions to ask before adding any dependency and a list of common mistakes with fixes.
Full article
daily.dev links to this article rather than hosting it. Read it at the original source: https://vxdeveloper.medium.com/a-dependency-is-not-just-a-relationship-where-one-piece-of-code-uses-another-bf81b90977e0
Questions this post answers
Why doesn't wrapping Prisma types in an interface actually reduce coupling in my repository pattern?
An interface only reduces coupling when its methods are expressed in the language of the consuming service, not the underlying technology. If a repository interface still uses types like Prisma.CouponCreateInput or PrismaCoupon in its signatures, the use case is still forced to understand Prisma's types, meaning the interface wraps the dependency without actually removing it.
Should I make coupon issuance fail if the confirmation email fails to send?
It depends on whether email delivery is an essential result of issuance or just follow-up work. If issuance is only valid when the email succeeds, both operations' failures must be handled together; if persistence is the core result and email can be retried later, decouple them using an event (e.g., publish a CouponIssued event) handled by a separate async handler.
Why might an in-memory repository test double give me false confidence in my tests?
An in-memory test double gives false confidence when it doesn't replicate real contract behavior, such as rejecting duplicate IDs. A real database may throw an error on a duplicate primary key while a naive in-memory Map implementation silently overwrites the existing entry, hiding a bug that only surfaces against the production database.