12

Considering a medium-big software with an n-layer architecture and dependency injection, I am comfortable to say that an object belonging to a layer can depend on objects from lower layers but never on objects from higher layers.

But I'm not sure what to think about objects that depend on other objects of the same layer.

As an example, let's assume an application with three layers and several objects like the one in the image. Obviously top-down dependencies (green arrows) are ok, bottom-up (red arrow) are not ok, but what about a dependency inside the same layer (yellow arrow)?

enter image description here

Excluding circular dependency, I'm curious about any other issue that could arise and how much the layered architecture is being violated in this case.

  • If you don't have cycles, in a layer where you have the horysontal links there exists splitting into sub-layers which has only dependencies between layers – max630 Mar 20 at 13:51
10

Yes, objects in one layer can have direct dependencies among each other, sometimes even cyclic ones - that is actually what makes the core difference to the allowed dependencies between objects in different layers, where either no direct dependencies are allowed, or just a strict dependency direction .

However, that does not mean they should have such dependencies in an arbitrary manner. It depends actually on what your layers represent, how large the system is and and what the responsibility of the parts should be. Note that "layered architecture" is a vague term, there is a huge variation of what that actually means in different kind of systems.

For example, lets say you have a "horizontally layered system", with a database layer, a business layer and a user interface (UI) layer. Lets says the UI layer contains at least several dozens different dialog classes.

One may choose a design where none of the dialog classes depend on another dialog class directly. One may choose a design where "main dialogs" and "sub dialogs" exists and there are only direct dependencies from "main" to "sub" dialogs. Or one may prefer a design where any existing UI class can use/reuse any other UI class from the same layer.

These are all possible design choices, maybe more or less sensible depending on the type of system you are building, but none of them makes the "layering" of your system invalid.

  • Continuing on the UI example, what are the pro and the cons of having the inner dependencies? I can see that it makes easier reuse and introduce cyclic dependencies, which might be a problem depending on the DI method used. Anything else? – bracco23 Mar 22 at 16:06
  • @bracco23: the pros and cons of having "inner" dependencies are the same as the pros of cons of having arbitrary dependencies. "Cons" are they make components harder to reuse and harder to test, especially in isolation from other components. Pros are, explicit dependencies make things which require cohesion easier to use, understand, manage, and test. – Doc Brown Mar 23 at 7:57
14

I am comfortable to say that an object belonging to a layer can depend on objects from lower layers

To be honest, I don't think you should be comfortable with that. When dealing with anything but a trivial system, I'd aim to ensure all layers only ever depend on abstractions from other layers; both lower and higher.

So for example, Obj 1 should not depend on Obj 3. It should have a dependency on eg IObj 3 and should be told which implementation of that abstraction it is to work with at runtime. The thing doing the telling should be unrelated to any of the levels as it's job is to map those dependencies. That might be an IoC container, custom code called eg by main that uses pure DI. Or at a push it could even be a service locator. Regardless, the dependencies do not exist between the layers until that thing provides the mapping.

But I'm not sure what to think about objects that depend on other objects of the same layer.

I'd argue that this is the only time you should have direct dependencies. It's part of the inner workings of that layer and can be changed without affecting other layers. So it's not a harmful coupling.

  • Thanks for the answer. Yes, layer should not expose objects but interfaces, I know that but I left it out for the sake of simplicity. – bracco23 Mar 20 at 13:54
4

Lets look at this practically

enter image description here

Obj 3 now knows Obj 4 exists. So what? Why do we care?

DIP says

"High-level modules should not depend on low-level modules. Both should depend on abstractions."

OK but, aren't all objects abstractions?

DIP also says

"Abstractions should not depend on details. Details should depend on abstractions."

OK but, if my object is properly encapsulated doesn't that hide any details?

Some people like to blindly insist that every object needs a keyword interface. I'm not one of them. I do like to blindly insist that if you're not going to use them now you need a plan to deal with needing something like them later.

If your code is fully refactor-able on every release you can just extract interfaces later if you need them. If you have published code that you don't want to recompile and find yourself wishing you were talking through an interface you'll need a plan.

Obj 3 knows Obj 4 exists. But does Obj 3 know if Obj 4 is concrete?

This right here is why it's so nice to NOT spread new everywhere. If Obj 3 doesn't know if Obj 4 is concrete, likely because it didn't create it, then if you snuck in later and turned Obj 4 into an abstract class Obj 3 wouldn't care.

If you can do that then Obj 4 has been fully abstract all along. The only thing making an interface between them from the start gets you is the assurance that someone wont accidentally add code that gives away that Obj 4 is concrete right now. Protected constructors can mitigate that risk but that leads to another question:

Are Obj 3 and Obj 4 in the same package?

Objects are often grouped in some way (package, namespace, etc). When grouped wisely change more likely impacts within a group rather than across groups.

I like to group by feature. If Obj 3 and Obj 4 are in the same group and layer it's very unlikely that you'll have published one and not want to refactor it while needing to change only the other one. That means these objects are less likely to benefit from having an abstraction put between them before it has a clear need.

If you are crossing a group boundary though it's really a good idea to let objects on either side vary independently.

It should be that simple but unfortunately both Java and C# have made unfortunate choices that complicate this.

In C# it's tradition to name every keyword interface with an I prefix. That forces clients to KNOW they are talking to a keyword interface. That messes with the refactoring plan.

In Java it's tradition to use a better naming pattern: FooImple implements Foo However, this only helps at the source code level since Java compiles keyword interfaces to a different binary. That means when you refactor Foo from concrete to abstract clients that don't need a single character of code changed still have to be recompiled.

It's these BUGS in these particular languages that keeps people from being able put off formal abstracting until they really need it. You didn't say what language you're using but understand there are some languages that simply don't have these problems.

You didn't say what language you're using so I'll just urge you to analyze your language and situation carefully before you decide it's going to be keyword interfaces everywhere.

The YAGNI principle plays a key role here. But so does "Please make it hard to shoot myself in the foot".

0

In addition to the answers above, I think It might help you out to look at it from different points of view.

For example, from the Dependency Rule standpoint. DR is a rule suggested by Robert C. Martin for his famous Clean Architecture.

It says

Source code dependencies must point only inward, toward higher-level policies.

By higher-level policies, he means higher-level of abstractions. Components that leaks on implementation details, as for instance interfaces or abstract classes in contrast with concrete classes or data structures.

The thing is, the rule is not restricted to the inter-layer dependency. It only points out to the dependency between pieces of code, regardless the location or the layer they belong to.

So no, there is nothing inherently wrong with having dependencies between elements of the same layer. However, the dependency still can be implemented to convey with the stable dependency principle.

Another standpoint is SRP.

Decoupling is our way to break harmful dependencies and convey with some best practices like dependency inversion (IoC). However, those elements that share the reasons to change they don't give reasons for decoupling because elements with the same reason to change will change at the same time (very likely) and they will be deployed at the same time too. If that's the case between Obj3 and Obj4 then, once more, there's nothing inherently wrong.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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