英文:
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?
答案1
得分: 1
我们可以创建测试来满足需求。我已经在其中一个项目中创建了相同的测试,该测试验证了*服务和存储库不应依赖于Web层*。您可以根据需求修改软件包。
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("服务和存储库不应依赖于Web层")
.check(importedClasses);
}
}
英文:
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);
}
}
答案2
得分: 1
以下是翻译好的内容:
这里有一个我认为有趣的方法,尽管(免责声明)我还没有看到有人真正实现这个方法:
创建以下Maven模块:
- 控制器(Controllers)
- 服务接口(Services-api)
- 服务实现(Service-impl)
- 存储接口(Repos-api)
- 存储实现(Repos-impl)
- Spring Boot 应用(Spring-boot-application)
除了最后一个模块,所有模块都将是“普通”的JAR包,最后一个模块将使用Spring Boot Maven插件构建成JAR/WAR包。
现在,定义依赖关系如下:
- 控制器依赖于服务接口(services-api)。
- 服务接口(services-api)仅包含服务接口,没有依赖关系。
- 服务实现(services-impl)依赖于服务接口(services-api)和存储接口(repos-api)。
- 存储接口(repos-api)同样只包含接口,没有依赖关系。
- 存储实现(repos-impl)依赖于存储接口(repos-api)和持久化驱动程序(如JDBC驱动程序或其他您使用的驱动程序)。
- Spring Boot 应用(spring-boot-application)包含对控制器模块、服务实现模块(会传递依赖服务接口)和存储实现模块(会传递依赖存储接口)的依赖。这是一种在运行时将所有内容粘合在一起的封装。
因此,现在如果您正在编写控制器(Controllers模块),您只能注入服务接口,否则它无法编译通过。您不能注入存储库(无论是接口还是实现),同样无法通过编译。
当您创建服务接口时,您不需要任何依赖关系,服务之间的依赖关系将在可能依赖其他服务的实现级别提供。
在编写服务实现时,可以注入存储接口,但不能注入实现(同样无法通过编译)。
如果您使用的是Java 9+,您可能可以使用它们的模块系统来创建相同级别的分离,我不熟悉这个系统,但核心思想是简单的:
Spring是一个运行时框架,我的答案是试图在编译级别“捕捉”这些“不良”用法。
我理解所呈现的分离方法“过于严格”,可能很多团队不会费心去做这样的事情,我个人更加“依赖”于项目上工作的程序员“知道自己在做什么”,但我也理解有时您真的不能够完全依赖于这一点,因此这就是我的回答
英文:
Here is one interesting method IMO, although (disclaimer) I haven’t seen that someone really implements this:
Create the following maven modules:
- Controllers
- Services-api
- Service-impl
- Repos-api
- Repos-impl
- 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:
- Controller depends on services-api
- Services-api includes only interfaces for services, it does not have dependencies
- Services-impl depends on services-api and repos-api
- Repos-Api are again only interfaces, no dependencies there
- Repos-Impl depends on Repos-api and persistence drivers (like JDBC drivers or whatever you use)
- 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
答案3
得分: 0
如果在定义存储库(Repository)和服务类(Service Class)时未使用public关键字,并将它们放在同一个包中。当然,你需要从控制器(Controller)通信到服务(Service)。最好的方法是创建一个接口,它是public的(在同一个包中),比如:
public interface UserFacade
该接口由实现类来实现(在同一个包中,不带public关键字):
@Component
class UserFacadeImpl implements UserFacade {
private YourService service;
void someMethod() {
service.doSomethingWithRepository();
}
}
然后,你可以在这个门面(facade)中定义使用你的服务或在这个包中定义的任何其他服务的方法。最终,从包外部访问你的存储库的唯一方式是通过在UserFacade中定义的方法间接访问,因此只有同一包中的服务可以直接使用这个存储库。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论