[Java] Dependency management in layered architecture

4 minute read

What is this page?

This page organizes the management method of business logic introduced in the new project.
How to structure and use related classes in the situation that “you don’t need a huge DI container for Web API like Spring”? Is the focus point.

Premise

This project was a simple common library that returned the result when an argument was passed to a single method that became the entry point.
From the entry point, business logic associated with multiple entities is defined (a so-called MVC-like architecture).

When changes are made to the business logic, we considered this time on the assumption that the number of services that have business logic instead of the entry point will increase or change.

It’s kind of unpleasant for the caller to instantiate and execute the service directly

The service layer class referenced from the entry point is 4 or 5 at most, so you can instantiate it directly …

  1. Whether it is necessary to create an instance from the class. Is it generated even though it has no state? I even think it’s okay to push it into the ultimate static context.
  2. The need for an interface. Is it only necessary to implement it? On the other hand, it is safe to make an IF as a promise.
  3. There are roughly three ways to call the caller. Instance generation, DI, static method (utilization).
    And so on, I had to face the method of dependency management.

Fear, assumed risk, perspective

I considered it from four perspectives:

  1. I get confused when the business logic is updated in the future development of additional functions.
  2. Rely on fat libraries for DI containers.
  3. I don’t want to bother to install the Spring / DI library … There are at most 5 services. Tight coupling occurs.
  4. Can it be read simply when others read it?
    In the past, I have reflected on the fact that I failed to configure the class and developed complicated functions that I could not see, so I chose carefully.

solution

I decided to prepare a simple DI container.
It’s an image of having a class with such a structure between a factory method, a singleton, and a service locator mediate.
The factory class itself should be a single instance, and services should be retrieved from this single instance.

Also, make sure that business logic updates are only affected through the factory.
When business logic was added, I thought it might be possible to add an instantiation method to the factory.

Then, the upper layer class that depends on the service generates the service from the factory and uses it.
Annotation-based DI like Spring and Java EE seems to be a little overkill in this project, so I did not adopt it in this project.
As a result of various investigations, I adopted a pattern called DYDI (Do it Yourself Dependency Injection), which is easy to prepare by myself and seems to be suitable for a compact project like this one.

Implementation image

It consists of three classes: a class that generates logic, an interface / implementation of logic, and a caller.

First, prepare a factory class that returns an instance with logic.

ServiceFactory.java


public class ServiceFactory {

    /**
     *Factory-specific single instance.
     */
    private static final ServiceFactory serviceFactory = new ServiceFactory();

    private ServiceFactory() {}

    public static ServiceFactory getInstance() { return serviceFactory; }

    /**
     *Returns the substance of the logic.
     */
    public TargetLogic getTargetLogic() {
        return new TargetLogicImpl();
    }

Define the interface and logic returned from this factory.
This is a quick definition as it can be flexibly defined according to the use case.

TargetLogic.java


public interface TargetLogic {

    void consume();

}

TargetLogicImpl.java


public interface TargetLogicImpl implements TargetLogic {

    void consume() { 
        //Something processing
    }

}

Finally, we will call the logic.

Application.java


public class Application {
    private final TargetLogic targetLogic = ServiceFactory.getInstance().getTargetLogic();
}

You can now get the logic through the factory in the ʻApplication` class.

How was it?

As I read at the beginning, the application was small and the project structure was simple, so the dependency management method was just right.
Even if there is a logic change in the future, it seems that it can be organized simply because it is enough to add a method to the factory.
When should you create an instance? Shouldn’t it be made? It was a side effect that deepened my thinking about.

On the other hand, if your project is a little bigger or has more layers, it’s more straightforward to rely on libraries like Spring and Guice.
To be honest, there may be more projects that are hard to work with.

reference