This is the 16th day of my participation in the August More Text Challenge. For details, see: August More Text Challenge

Today’s question is: Does the Service layer need an interface?

Now combined with the project I participated in and read some project source code. If “your project uses a dependency injection framework like Spring, you don’t need an interface” **!

First, let’s talk about why you don’t need interfaces when you use a dependency injection framework!

Why you don’t need an interface

I’ve sorted out the reasons why interfaces should be added to the Service layer and Dao layer, and I’ve come down to three:

  • You can write the upper-level code without implementing the specific Service logic, such as a Controller call to a Service

  • By default, Spring implements AOP based on dynamic proxies, which require interfaces

  • You can have multiple implementations of a Service

In fact, none of these three reasons hold water!

Let’s start with the first reason: “The upper layer can code without the underlying logic being implemented”! Typical programming for interfaces, decoupling layers from layer to layer, seems to be fine.

This development method is suitable for different modules are developed by different people or project teams, because the cost of communication is relatively large. At the same time to avoid the differences between the project team development schedule and mutual influence.

But let’s think back, in the general project development, how many project teams are divided into layers of development tasks? In fact, most projects are divided by function. Even with the current separation of the front and back ends, pure back end development is divided by functional modules, with one person responsible for the complete logical processing from the Controller layer to the DAO layer. In this case, each layer defines an interface and then implements the logic, in addition to increasing the workload of the developer (of course, if the amount of code counts toward the workload, the developer should not be so disinterested in the interface!). “Is actually of no use.

If a developer wants to develop the upper-level logic before the lower-level logic is finished, they can write empty methods on the lower-level classes to complete the upper-level logic first.

Here is my favorite development process, the top-down coding process:

  • Write the logic at the Controller layer first, and write the calling code whenever you need to delegate a Service call. Prioritize the process at the Controller layer

  • Then add TODO to the class and method generated from the code below the call using the IDE’s autocompletion

  • After all the classes and methods are completed, we can complete the logic one by one according to the above process based on TODO.

This approach will give you a better understanding of the business process.

For the second reason, it doesn’t work at all. Spring is based on dynamic proxies by default, but it is configurable to implement AOP using CGLib. CGLib does not require interfaces.

The final reason is that “you can have multiple implementations of a Service.” This reason is not sufficient, or does not consider the scenario. In fact, in most cases there is no need for multiple implementations, or there are alternatives to multiple implementations based on interfaces.

In addition, for many projects that use interfaces, the project structure is also questionable! Next, let’s look at the project structure.

Project structure and interface implementation

The general project structure is divided into layers, as follows:

  • Controller

  • Service

  • Dao

In cases where no more implementations are required, interfaces are not required. The above project structure will suffice.

For situations where multiple implementations are needed, either now or later. In this case, it looks as if interfaces are needed. The project structure at this point would look like this:

  • Controller

  • Service

    • —- The interface is in a package

    • Impl – implemented in another package

  • Dao

With the above structure, let’s consider the multi-implementation case. What should we do?

The first is to add a package to the Service, write new logic in it, and then modify the configuration file to make the new implementation an injection object.

  • Controller

  • Service

    • —- The interface is in a package

    • Impl – implemented in another package

    • Impl2 – the new implementation is in another package

  • Dao

The second way is to add a new Service module and write new logic in it (note that the package here cannot be the same as the original Service package, or the same package but with a different class name, otherwise the class cannot be created. Since both Service modules need to be loaded at the same time, if the package name and class name are the same, the fully qualified class name of both modules will be the same!) Then modify the configuration file to make the new logic an injected object.

  • Controller

  • Service

    • —- The interface is in a package

    • Impl – implemented in another package

  • Service2

    • Impl2 – the new implementation is in another package
  • Dao

In fact, the first approach is relatively simple, focusing only on the package level. The second approach focuses on both the module and package layers. In addition, both approaches actually result in projects containing unwanted logical code. Because the old logic is in the bag.

However, from a structural point of view, the actual structure of mode 2 is clearer than that of Mode 1 because the logic can be distinguished from the modules.

Is there a way to combine the best of both worlds? The answer is yes, and the operation is not complicated!

First, the interface and implementation are separated as a separate module:

  • Controller

  • Service: indicates an interface module

  • ServiceImpl

    • Impl – implemented in another package
  • ServiceImpl2

    • Impl2 – the new implementation is in another package
  • Dao

Second, adjust the packaging configuration to either ServiceImpl or ServiceImpl2. Since ServiceImpl and ServiceImpl2 are optional, the package structure of ServiceImpl and ServiceImpl2 can be the same. With the same package structure, dependency inject-related configurations need not be adjusted once the dependencies are adjusted. After the adjustment, the project structure looks like this:

  • Controller

  • Service: indicates an interface module

  • ServiceImpl

    • Impl – implemented in another package
  • ServiceImpl2

    • Impl – the new implementation and honesty are now in the same package
  • Dao

The package structure and class names in the ServiceImpl and ServiceImpl2 modules are now the same. Do we still need interface modules?

Suppose we remove the Service interface module and the structure looks like this:

  • Controller

  • Service1 — old implementation

  • Service2 — New implementation

  • Dao

Is it possible to have multiple implementations of a Service simply by adjusting module dependencies? Obvious answer, right?

Disadvantages of not using interfaces

The reasons for not using interfaces are given above. Not using interfaces is not without its drawbacks, however, the main problem being that there is no strong interface specification for multiple implementations. That is, you can’t quickly generate framework code with an IDE by implementing interfaces. The IDE can also give error alerts for interfaces that are not implemented.

A less elegant solution is to copy the code from the original module into the new module and implement the new logic based on the old code.

Therefore, interfaces are recommended if a project requires multiple implementations and a large number of implementations (although most projects do not have multiple implementations). Otherwise, you do not need to use the interface.

conclusion

This article points out the reason why the Service layer needs an interface. As well as personal views on this issue, I hope you have some help.