0

I'm working on a Node project (in typescript) that features a variation of clean architecture. I have a pretty clear understanding of the different layers of an application and how they depend on each other. What is not clear to me is where definitions for interfaces and models should exist (say for a repository, a domain service, an entity, etc.).

I can imagine arguments for defining some interfaces locally (say for a repo) and for naming them in at a higher scope (say for some base class), but I can't decide on if it really even matters. Where the definition is imported from has no bearing on the instances that are based on them, right? So is this just a matter of pedantic?

3
  • 1
    Interfaces are always defined as close to the place of their use as possible. Architecture does not affect this. For example, if your Controller needs to fetch a state of UI it defines an interface that UI implements and injects/provides.
    – Basilevs
    Dec 7, 2021 at 4:08
  • 2
    The main thing about the clean architecture is the dependency rule, so it matters very much where the interfaces at the layer boundaries are defined and where they are imported from. Dependencies need to go from a low-level layer to a high level layer; if a class that uses an interface would have to break that rule, the interface needs to be moved to that class (to a higher level layer), and it's implementation needs to stay where it is (in a lower level layer). That's called dependency inversion. Otherwise, you don't really have clearly separated (or easily separable) layers. Dec 7, 2021 at 7:50
  • 1
    Now, again, this is for interfaces that have interactions that cross layer boundaries; for interfaces internal to the layers, organize them how you see fit. So to figure out what to do, you need to think about how your layers are going to interact and communicate to each other. Dec 7, 2021 at 7:55

1 Answer 1

1

I opted to refer to "modules" instead of "layers" here, because it fits a more general approach for many different architectures and I wanted to keep the name consistent.

There are different approaches here, depending on how your architecture is set up.

  • You can go for a modular design, where each bounded context exposes its own interface.
  • You can centralize your interfaces, and consider your bounded contexts as the implementation of the interface that was defined in that central location. Whether you bundle all your interfaces in one centralized location, or create a separate interface bundle per bounded context, is up to you to decide.
  • When working with inverted dependencies, it is the consumer who defines the interfaces, and the module only acts as the implementer of said interface.

Note that the last two bullet point focus on public interfaces (i.e. between modules), not internal interfaces (i.e. only used inside the module itself).

As you're working in node.js, it's very easy to start putting interfaces where you please, as node.js is quite unconstrained about its dependency graph. I'm a .NET dev, and .NET enforces a rigid project dependency graph, which in turn very much dictates where you can and can't put your interfaces so that the implementations can access them.
In your case, you have ample freedom to choose how you structure things. The good thing is that you get choice, the bad thing is that it requires self discipline to stick to your choice and remain consistent.

Which of these you use is up to you to decide, based on what makes the most sense in your architecture and development approach.

Possible considerations:

  • If you're dealing with a central domain module which is the meat of your application and which calls the shots, inverted dependencies make a lot of sense as the domain will be the one demanding what the outer layers must provide for the domain. Render unto Caesar the things that are Caesar's.
  • When there are multiple consumers, inverted dependencies make little sense because these consumers couldn't easily coordinate on using the same interface.
  • If your modules independently decide what they provide (which often happends when separate teams develop them), it makes more sense to have the moduels themselves define their public interface. My house, my rules.
  • If you might end up with variant implementations and you choose to separate them into individual modules (e.g. a MongoDb DAL module, a file storage module, an in-memory storage module, ...) as opposed to a single module with (internally) multiple possible implementations; it makes more sense to define the DAL interfaces separately, so that all of these modules have access to it without one specific module claiming ownership over them.
    • If you intend to distribute your application as packages, it makes a lot of sense to create such individual modules so consumers only download the modules that they need.

These are just some of the possible considerations that can influence which approach best fits your use case.

In general, the rule is that interfaces belong with the party that decides the interface's structure. Different development methodologies put that responsibility in different locations.

1
  • Thanks for the thorough answer. Your point about me using NodeJs is precisely why I'm contemplating this so heavily. I love JS, but it gives way to a lot of 'do as you please' software. Given my implementation, I will opt for local interfaces defined within the layer and public ones exposed to the higher layers.
    – GHOST-34
    Dec 7, 2021 at 19:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.