I wrote an answer previously on Open-Closed Principle (OCP) vs Liskov's Substitution Principle (LSP) and both those principles relate to each other a lot, but are still conceptually different with some contrived examples of having one but not the other. Because of this answer I'll only touch on OCP briefly and dwelve deeper into DIP and what makes that tick.
Lets try discussing how OCP relates and differs with Dependency Inversion Principle (DIP) by first explaining the different principles first.
Dependency Inversion Principle
Reading Uncle Bob's Principles of OOD you'll find that DIP states the following:
Depend on abstractions, not on concretions.
An abstraction in Java is simply achieved with interface
and abstract
keywords, meaning you have a "contract" for some software entity that the code has to follow. Some programming languages do not have a facility to explicitly set behaviors for the code to follow so abstractions have to be followed in a more manual fashion rather than having the compiler help you enforce the contract. E.g. in C++ you have classes with virtual methods, and dynamic programming languages such as Javascript you have to make sure you use objects the same way (though in the case of Javascript this has been extended in TypeScript that adds a type system to help you out with writing contracts that is verified by the compiler).
The name includes the term "inversion" because traditionally (y'know in the old dark ages of programming) you wrote software structures that had higher level modules depending on low level modules. E.g. it made sense to have a ButtonAtKitchen
handling inputs for a KitchenLamp1
and KitchenLamp2
. Unfortunately that made software a lot more specific than it needed to be and the object graph would look like this:
So when you make the software more general, by adding "contracts". Notice how the arrows in the object graph "inverts" the direction. That kitchen lamps are now dependent on a Button
. In other words the details are now dependent on abstractions instead of the other way around.
Thus we have a more general definition of DIP, also detailed in the original article of DIP by Uncle Bob.
A. High level modules should not depend on low level modules. Both should depend on abstraction.
B. Abstractions should not depend upon details. Details should depend on abstractions.
Open-Closed Principle
Continued from Uncle Bob's principles you'll find that OCP states the following:
You should be able to extend a classes behavior, without modifying it.
One example of achieving this is to employ the Strategy pattern where a Context
class is closed for modifications (i.e. you can't change it's internal code at all) but is also open for extension through it's collaborating dependencies (i.e. the strategy classes).
In a more general sense any module is built to be extensible through it's extension points.
OCP is similar to DIP, right?
No, not really.
Although they both are discussing abstractions they're conceptually different. Both principles are looking at different contexts, OCP on one particular module and DIP on several modules. You can achieve both at the same time as with most Gang of Four design patterns, but you can still steer away from the path.
In the DIP example mentioned above, with the button and kitchen lamps, none of the kitchen lamps are extensible (nor currently has any requirement explaining they need to be). The design is breaking OCP but follows DIP.
A reverse (and a contrived) example would be a kitchen lamp be extensible (with the extension point being something like a LampShade
) but the button is still dependent on the lamps. It is breaking DIP but follows OCP.
Don't worry, it happens
This is actually something you'll see happen often in production code, that some part of it may break a principle. In larger software systems (i.e. anything larger than the examples above), you might break one principle but keeping the other usually because you need to keep the code simple. This is, in my mind, okay for small and self-contained modules, as they are in the context related to Single Responsibility Principle (SRP).
Once some module becomes complicated though you most likely need to look at it with all principles in mind and redesign or refactor it to some well-known pattern.
The main reason you would have the interfaces for your repositories in your BLL is to avoid having hard references to the separate DAL but instead have your changing DAL reference the stable BLL.
To be able to swap out implementations without changing the stable BLL
On the internet this might not be the main reason in general, but this would be a compelling reason for me to prefer option 2 in your situation.
Let's assume that you are using a DI (dependency injection) container. If you configure this using a configuration file, for example, you would tell the container that the implementations for the repository-interfaces in the BLL can be found in the DAL.Sql project. Once you switch over to a NoSQL solution you would create the DAL.NoSQL project, deploy it and change the DI container configuration to resolve the implementations of the repository from the new project. Your new DAL depends on the stable BLL that does not need to change.
However, if you go with option 1, you might not be able to hot-swap the DAL project. If you use .NET for example you could run into issues where the BLL project depends on a certain version of the DAL dll and you would not be able to swap out the DAL without changing the BLL as well.
Because the repository interface is part of the BLL
A repository is merely a gateway that defines how your application will retrieve data. In that sense it is as much a part of the business logic as your domain objects. The concrete implementations can change, but the interfaces themselves are part of your business logic.
Because it isolates the BLL
Having the interfaces and implementations in the DAL means bringing all of the DAL into the BLL. There is nothing preventing developers from using objects from the DAL project in ways that they shouldn't be used. Having the DAL depend on the BLL means the BLL can only contain the interfaces that it needs.
To avoid a separate project with the domain objects
Your DAL and BLL both depend on your domain objects. If your BLL has a class that uses a repository and a domain object, you cannot put your repository interfaces into the DAL as that creates a circular reference (BLL needs interface for repository in DAL, DAL needs domain objects in BLL). So you would have to split the BLL and the domain objects into two projects so your BLL can reference the domain and the DAL and the DAL can also reference the domain.
Best Answer
The central idea of the DIP is to eliminate any direct dependencies between higher-level modules and lower-level modules. To achieve this, an abstract, stable interface is placed between them, upon which both layers depend instead.
In the repository pattern, the interface usually references the entity for which the repository is responsible. It therefore makes the lower level implementation (
CarModelRepository
) depend on a higher-level module entity (CarModel
).Replacing the concrete
CarModel
with anICarModel
interface does not really solve this problem. After all, theCarModel
instance that we obtain through the repository implements various business rules and should therefore live in the business layer.We could increase the separation between the business layer and the repository implementation. For example, we could provide a
CarModelFactory
(implemented in the business layer) that implementsICarModelFactory
(part of the shared interface) to the repository. Alternatively, we might make the repository deal withCarData
value objects rather than actualCarModel
entities - and make the business layer instantiate the entities itself.In most cases, this doesn't seem fruitful. Attempting to find an abstract interface between the two modules that is more stable than
CarModel
itself is almost always going to be futile. That's because, fundamentally,CarModelRepository
is not intended to be a generic utility class. It's purpose is essentially to glue your specific domain model to the database.As a result, trying to separate it from the domain model (or the database) doesn't really produce anything useful. In other words, the repository can be seen as an adapter between the domain model and the database driver - and therefore must be aware of both.
I should note that Martin's primary focus when introducing the DIP was to separate the higher level module from lower level modules, which the repository pattern achieves.