简体   繁体   中英

Spring boot: enforce best practices

I am setting up a project and wondering if there any way to enforce repository instance access from service layer only?

We may create the Test to achieve the requirement. I have created the same in one of the projects which validate that Services and repositories should not depend on the web layer . You can modify the packages according to the requirements.

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import org.junit.jupiter.api.Test;

class ArchTest {

    @Test
    void servicesAndRepositoriesShouldNotDependOnWebLayer() {
        JavaClasses importedClasses = new ClassFileImporter()
            .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
            .importPackages("com.learning.springboot");

        noClasses()
            .that()
            .resideInAnyPackage("com.learning.springboot.service..")
            .or()
            .resideInAnyPackage("com.learning.springboot.repository..")
            .should()
            .dependOnClassesThat()
            .resideInAnyPackage("..com.learning.springboot.web..")
            .because("Services and repositories should not depend on web layer")
            .check(importedClasses);
    }
}

If you do not use public keyword when defining a repository and service class and put them together in one package. Of course you need to communicate from Controller to Service. The best way to do that is to create interface which is public (in the same package), let's say:

public interface UserFacade

which is implemented by the (same package, without public keyword):

@Component
class UserFacadeImpl implements UserFacade {

   private YourService service;

   void someMethod() {
       service.doSomethingWithRepository();
   }
}

Then you can define methods in this facade which use your service or any other services which are defined in this package. In the end, the only way to access your repository from outside the package is indirectly by the methods defined in UserFacade, so directly only services in the same package can use this repo.

Here is one interesting method IMO, although (disclaimer) I haven't seen that someone really implements this:

Create the following maven modules:

  1. Controllers
  2. Services-api
  3. Service-impl
  4. Repos-api
  5. Repos-impl
  6. Spring-boot-application

All the modules but the last one will be “ordinary” jars, the last one will be JAR/WAR build with spring boot maven plugin.

Now, define the dependencies as follows:

  1. Controller depends on services-api
  2. Services-api includes only interfaces for services, it does not have dependencies
  3. Services-impl depends on services-api and repos-api
  4. Repos-Api are again only interfaces, no dependencies there
  5. Repos-Impl depends on Repos-api and persistence drivers (like JDBC drivers or whatever you use)
  6. Spring-boot-application contains dependencies on the Controllers module, Service-impl (that brings transitively Service-Api) and Repos-impl (that brings repo api transitively). This is a kind of wrapper that glues everything together in runtime.

So now, if you're writing the controller (module Controllers), then you can inject only interfaces of the services, otherwise it won't compile. You can't inject the repositories (neither interface, nor implementations), again it won't compile.

When you create the service (interface) - you don't need any dependencies, dependencies between services will be supplied at the level of implementation that may depend on other services.

When you're writing the implementation of the service - its ok to inject repository api, but not an implementation (again, it won't compile).

If you're using Java 9+, you might probably create the same level of separation by using their module system, with which I'm not familiar, however the core idea is simple:

Spring is a runtime framework, and my answer is an attempt to “catch” these “bad” usages at the level of compilation.

I understand that the presented separation is “too strict” and probably many teams won't bother doing such a thing, I by myself rather “rely” on the programmers that work on the project that they “know what they do”, but I also understand that sometimes you can't really rely on that and hence my answer:)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM