Kiss架构:Springboot + Angular - Pasquale Paola

19-01-23 banq
                   

就像维基百科建议的那样,KISS是一个缩写

保持简单,愚蠢

作为美国海军在1960年提出的设计原则.KISS原则指出,如果保持简单而不是复杂化,大多数系统都能发挥最佳作用; 因此,简单性应该是设计中的关键目标,并且应该避免不必要的复杂性。

根据我的经验,我研究了许多类型的技术,有机会检查源代码,一般来说,从客户端(可以是SPA或本机应用程序)和服务器端开发应用程序。 。依靠这样的经历,我想开发一个简单的架构,包括尊重基本的格局在CRUD上下文。我将说明这个架构的基础,作为我所有项目的起点。

在设计中我记住了KISS概念,从这个角度来看,我将提供5层,称为层,具有特定的职责任务:

  • 前端
  • API
  • 商业逻辑
  • 集成层
  • DAO

我使用多层架构模型。列表中的层顺序与信息流严格关联,从前端一直到达DAO!

本文引用的项目可通过以下链接下载:https://github.com/paspao/springboot-kiss-architecture

git clone https://github.com/paspao/springboot-kiss-architecture

该项目使用maven(即父类和子类)进行管理; 即使在Angular中开发的前端被包含在maven构建阶段中以创建单个工件(下面的FRONTEND段中提供了更多详细信息)。

为了深入了解每一层,我更喜欢使用BottomUp方法,所以让我们从数据开始。

DAO

数据访问对象。我在谈论CRUD应用程序,即要收集和处理的数据。这个模块代表了最深刻的一点。该层执行数据,它描述实体和访问逻辑。注意:插入,修改,删除和显示数据的简单数据访问逻辑,不与任何其他层绑定; 它是最深层的,并且它与它的任何兄弟都没有依赖关系,它不处理应用程序的特定方面,例如授权,事务或其他:也只是访问数据。在Springboot上下文中,我正在执行实体和存储库范例。

@Configuration
@ComponentScan("org.ska.dao")
@EntityScan(basePackages = {"org.ska.dao.entity"})
@EnableJpaRepositories(basePackages = {"org.ska.dao.repository"})
@EnableAutoConfiguration
@EnableTransactionManagement
public class KissDaoConfiguration {
}

所以我定义了组件,实体和存储库的位置。此外,我启用了事务,因此使用DAO模块的任何人都不必担心配置DAO模块的方面。

集成

简单的CRUD数据管理是不够的:我们可能需要与不依赖于我们数据的其他系统(例如JAX-WS或JAX-RS服务)或具有不同协议的特定打印系统等进行互操作。此组件包括所有这些交互/集成都没有对应用程序上下文的特定绑定,以保证您具有非常高的可重用性(如DAO模块,此层也是叶类型)。

@Configuration
@ComponentScan(basePackages = {"org.ska.integration.beans"})
public class KissIntegrationConfiguration {

    @Bean
    public GeoApiContext geocoder(){
        GeoApiContext context = new GeoApiContext.Builder()
                .apiKey("Your apikey")
                .build();
        return context;
    }
}

这里我展示了模块配置的中心点:只有一个对服务定义的引用和第三方服务的实例(GeoApiContext)。

业务逻辑

稍后识别要处理的数据时,每个应用程序必须处理定义到DAO层中的实体之间的交互逻辑。您必须将用户需求与应用程序逻辑相结合,将它们分解,然后向上层公开简单且可读的签名。因此,这一层允许开发人员进行一些处理,而无需详细说明数据库的结构或下面的集成。

我们在这里发现DTO的定义和用法有助于掩盖存储在数据库系统或各种集成bean中的数据:为什么?

有一些原因:首先,它是一种隐藏敏感数据的信息形式(例如密码,时间戳或数据一致性所需的其他信息,但对最终用户没有)。如果需要详细说明返回的数据(如从多个源组合的数据),DTO有助于以合适的方式构建这些数据。

另一个方面是数据的序列化:在某些上下文中,您必须转换数据库中存在的信息,以使其可用于人类。因此,开发人员必须“污染” 实体使用序列化逻辑,其目的应该只是表示数据,例如:数据库系统上的DATE字段可能是一个数字,但我们以可打印的方式表示它,所以我们可能会使用格式化注释; 这是一个解决方案!但在这种情况下,我们将序列化的各个方面链接到一个实体,从长远来看,这个解决方案将导致无法使用的代码。哈罗德·阿贝尔森说:

必须编写程序供人们阅读,并且只有程序才能执行。

DTO允许面对列出的问题,创建类似“缓冲区”的东西,因此更松散耦合和更多可重用性。

总而言之,BUSINESS LOGIC与DAO层和INTEGRATION层进行通信,创建协同作用并在它们之间进行交互。此外,它将逻辑和数据转换带入DTO,可供其他层使用。警告:业务逻辑层使用定义的DTO,同样适用于返回的数据。它们绝不是其他层中定义的对象,因此要确保我们上面所说的内容并且能够处理返回的数据。

该层的另一个特征是事务管理:由于它实现了业务逻辑,因此能够确定对数据的操作是否成功,因此定义了操作的“ 事务性 ”。

以下是业务逻辑层的@Configuration:

@Configuration
@ComponentScan(basePackages = {"org.ska.business"})
@Import({KissDaoConfiguration.class, KissIntegrationConfiguration.class})
public class KissBusinessConfiguration {
    
    @Bean
    public Mapper dozerMapper(){
        Mapper dozerBeanMapper =  DozerBeanMapperBuilder.buildDefault();
        return dozerBeanMapper;
    }

}

它是唯一一个直接链接到DAO和INTEGRATION层的模块,因此必须导入配置才能使用它们。此外,为了加快实体和DTO之间的映射,使用为此而生成的框架是一种很好的做法,避免了长期和不可读的setter和getter代码块; 在我的例子中,我使用了一个名为Dozer的映射框架。

API

在这层中,呈现逻辑,它代表了我们应用程序的入口点,至少从服务器的角度来看。JAX-RS或JAX-WS等服务的定义主要是以XML,JSON或其他方式呈现数据。它仅与BUSINESS LOGIC层进行对话:服务只调用Business层提供的一个或多个服务,它永远不会直接使用INTEGRATION或DAO层,也不会使用其中定义的对象,以避免紧密耦合和意大利面条代码。

它涉及身份验证和授权的管理:这里可以确定谁可以执行操作:在下面的层中要复杂得多,或者确定哪些角色尚不可知的信息。

API总是需要一些文档:缺乏REST世界之一是缺少这些服务的通用描述符。保证这方面的技术是Swagger(现在是OpenAPI):它允许记录API,但是生成的文档也可以重用来生成客户端部分,因此不仅仅是描述性的部分。例如,在我的情况下,带有REST服务的通信层完全由服务的Swagger描述生成:在FRONTEND模块中,有一个remote-services文件夹,其中包含工具:https://editor.swagger.io.

在api层的配置类中,我导入业务层并设置Swagger文档的生成。

@Configuration
@Import(KissBusinessConfiguration.class)
@EnableSwagger2
public class KissApiConfiguration {
    
        @Autowired
        private Environment env;

        @Bean
        public Docket api() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .select()
                    //.apis(RequestHandlerSelectors.any())
                    .apis(RequestHandlerSelectors.basePackage("org.ska.api.web"))
                    .paths(PathSelectors.any())
                    .build()
                    .apiInfo(apiInfo());
        }

        private ApiInfo apiInfo() {
            return new ApiInfo(
                    "Contact management REST API",
                    "API",
                    env.getProperty("info.version"),
                    null,
                    new Contact("Pasquale Paola", "https://www.linkedin.com/in/pasqualepaola/", "pasquale.paola@gmail.com"),
                    null, null, Collections.emptyList());
        }

}

前端

它代表单页应用程序:这种类型的应用程序必须与应用程序完全分离,并且Rest技术的使用已经保证了这一方面,但有必要注意与远程服务的通信是如何实现的。我经常陷入非常糟糕的组织和管理用于调用远程服务的各种HTTP客户端,我的意思是在整个应用程序中找到的引用。

为了解决这个问题并使SPA与代表与远程服务通信的所有内容严格分开,如前所述,我使用Swagger技术生成一个允许与Rest API通信的存根。所以开发人员将使用Swagger生成的东西,主要是因为它提供了大量现成的代码,具有不同的使用选项,而你不再需要重写它。此外,逻辑将在其他地方实现,因为远程通信部分(Stub)将不断重新生成,并且开发人员不会梦想在可能被覆盖的源中实现自己的逻辑(我希望)。

为了确保用Angular编写的应用程序可以包含在Maven项目的构建周期中,我确保通过添加pom.xml文件即使FRONTEND也成为Maven模块。这个模块不会产生任何工件,所以包装将是pom类型,但是这样我就可以将它插入到maven构建中并与其兄弟姐妹创建依赖关系。为了能够在Maven上下文中集成Angular构建,我使用了一个名为frontend-maven-plugin的插件:它允许安装Node和Npm实例

 <execution>
            <id>install node and npm</id>
            <goals>
              <goal>install-node-and-npm</goal>
            </goals>
            <phase>generate-resources</phase>
          </execution>
          <execution>
            <id>npm install</id>
            <goals>
              <goal>npm</goal>
            </goals>
            <configuration>
              <arguments>install</arguments>
            </configuration>
          </execution>

以及随后调用Angular CLI来管理依赖关系并构建。

 <execution>
            <id>npm build</id>
            <goals>
              <goal>npm</goal>
            </goals>
            <phase>generate-resources</phase>
            <configuration>
              <arguments>run build</arguments>
            </configuration>
          </execution>

当调用Npm构建任务时,控件将从Angular CLI中获取,如package.json中所述:

...
"build": "ng build --prod --progress --build-optimizer --delete-output-path --base-href /kiss/ui/ --output-path dist/resources/static/ui"
...

输出路径设置为dist / resources / static / ui,路径dist / resources也配置为前端模块的资源。结合下面的层API配置,它允许在Springboot应用程序中注入Angular构建的结果。在输出路径(package.json 中的build命令)中有一个特殊目录  ... / static / ...,其中一个是Springboot允许定义静态内容的目录。

<resources>
    <resource>
        <directory>../frontend/dist/resources</directory>
    </resource>
    <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
    </resource>
</resources>
...

Build

mvn clean install

Run​​​​​​​

java -jar api/target/api-0.0.1-SNAPSHOT.jar

访问:http://localhost:8080/kiss/

​​​​​​​点击标题见原文!