当前位置:Java -> 实施最佳实践:使用Spring Boot构建微服务API

实施最佳实践:使用Spring Boot构建微服务API

技术架构

首先,让我们详细解释一下架构。让我们详细了解每个层次。

Technical Architecture

让我详细解释一下架构。这些组件通常与遵循领域驱动设计(DDD)和模型-视图-控制器(MVC)或类似架构模式的应用架构相关联。我逐一介绍:

实体

实体代表应用程序中的核心业务对象或概念。它们封装与业务领域相关的数据。例如,在员工管理系统中,员工实体可能具有与员工相关的名称、电子邮件和薪资等属性。

存储库

存储库负责处理数据访问逻辑。它们提供对数据存储的抽象,允许应用程序与数据交互,而无需担心底层存储细节。例如,EmployeeRepository 将处理数据库中员工记录的存储、检索、更新和删除操作。

服务

服务包含在实体方法内部无法自然适应的业务逻辑。它们协调实体和存储库之间的交互,以执行更高级别的用例。例如,EmployeeService 可能具有用于计算奖金、处理员工调动或处理涉及多个实体的复杂业务规则的方法。

映射器

映射器负责在应用程序的不同层之间转换数据。它们将数据从数据库实体转换为域对象,反之亦然。例如,EmployeeMapper 可能将员工实体转换为可以通过网络发送或由表示层使用的数据传输对象(EmployeeRequest)。

控制器

控制器处理来自用户界面或外部系统的输入请求。它们解释用户输入,调用必要的服务或业务逻辑,并准备要发送回去的响应。在 web 应用程序中,控制器接收 HTTP 请求,提取必要的数据,并将请求委托给适当的服务。然后格式化服务响应并将其发送回客户端。

前端:您可以选择构建原生应用,如 Android 和 iOS。可以使用 React 或 Angular 框架构建桌面浏览器应用程序或移动浏览器应用程序。

架构实施的最佳实践

实体:有用的提示

  • 将包命名为“entities”,放在功能名称下面
  • 将 ID 设置为 Long 类型,生成类型为 identity
  • 将类和表名设置为复数形式,如 users
  • 对于每个 String 字段,设置长度
  • 将可为空性设为 true/false
  • 使用 Lombok 生成构造函数和 getter/setter 代码
  • 如有需要,使用 @ManyToOne 引用其他表。请记住,表的创建是自动的,实体中的写入内容很重要。
  • 如有需要,使用 @OneToMany 双向关联,以便在一次调用中保存多个表中的值。
  • 使用 @ManyToMany 连接表。如果加入表除了加入 id 列以外还有其他字段,则创建一个单独的 Join 类。
  • 确定适合 is-a 关系的正确继承类型。根据每个类中的字段数量,在单个表、类表和具体表继承之间进行选择。

示例

package org.project.feature.entities;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class Users {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 100, nullable = false)
private String firstName;
@Column(length = 100, nullable = false)
private String lastName;
@Column(length = 100, nullable = false, unique = true)
private String email;
 }


存储库:有用的提示

  • 将包命名为“repositories”,放在功能名称下面
  • JpaRepository 扩展为实体名称和 ID 为 Long
  • 尽可能使用方法风格查询实体,如 findByEmail
  • 对于向数据库进行多条目批处理操作,使用 saveAll
  • 尽可能使用 Optional 作为返回类型

示例

package org.project.feature.repositories;
 
 import org.project.feature.entities.Users;
 import org.springframework.data.jpa.repository.JpaRepository;
 
 public interface UsersRepository extends JpaRepository<Users, Long> {
 
 }


服务:有用的提示

  • 将包命名为“services”,放在功能名称下面
  • 为服务内所有操作创建一个接口,并创建一个实现类
  • 使用 @AllArgsConstructor 替代 @Autowired 注解
  • 接受 Request 对象并从模型包返回 Response 对象。
  • 如果需要调用多个存储库,它应该在一个事务中进行调用,除非您希望启动一个新事务。
  • 如果需要调用多个服务,特定服务必须被命名为聚合服务,并在一个事务中进行调用。
  • 不要从服务返回 ResponseEntity;这是控制器层的工作。

示例

package org.project.feature.services;
 
 import org.project.feature.models.UsersRequest;
 import org.project.feature.models.UsersResponse;
 
 import java.util.List;
 
 public interface UsersService {
     UsersResponse createUser(UsersRequest usersRequest);
 
     UsersResponse getUserById(Long userId);
 
     List<UsersResponse> getAllUsers();
 
     UsersResponse updateUser(Long id, UsersRequest users);
 
     void deleteUser(Long userId);
 }

 

package org.project.feature.services.impl;
 
 import lombok.AllArgsConstructor;
 import org.project.feature.entities.Users;
 import org.project.feature.mappers.UsersMapper;
 import org.project.feature.models.UsersRequest;
 import org.project.feature.models.UsersResponse;
 import org.project.feature.repositories.UsersRepository;
 import org.project.feature.services.UsersService;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 
 @Service
 @AllArgsConstructor
 public class UsersServiceImpl implements UsersService {
 
     private final UsersRepository usersRepository;
 
     private final UsersMapper usersMapper;
     @Override
     public UsersResponse createUser(UsersRequest usersRequest) {
         Users users = usersMapper.convertRequestToEntity(usersRequest);
         Users saved = usersRepository.save(users);
         return usersMapper.convertEntityToResponse(saved);
     }
 
     @Override
     public UsersResponse getUserById(Long userId) {
         Optional<Users> optionalUser = usersRepository.findById(userId);
         return usersMapper.convertEntityToResponse(optionalUser.get());
     }
 
     @Override
     public List<UsersResponse> getAllUsers() {
         List<Users> users = usersRepository.findAll();
         List<UsersResponse> usersResponses = new ArrayList<>();
         for (Users user : users)
             usersResponses.add(usersMapper.convertEntityToResponse(user));
         return usersResponses;
     }
 
     @Override
     public UsersResponse updateUser(Long id, UsersRequest usersRequest) {
         Optional<Users> user = usersRepository.findById(id);
         Users existingUsers = user.orElse(null);
         existingUsers.setFirstName(usersRequest.getFirstName());
         existingUsers.setLastName(usersRequest.getLastName());
         existingUsers.setEmail(usersRequest.getEmail());
         Users updatedUsers = usersRepository.save(existingUsers);
         return usersMapper.convertEntityToResponse(updatedUsers);
     }
 
     @Override
     public void deleteUser(Long userId) {
         usersRepository.deleteById(userId);
     }
 }


映射器:有用的提示

  • 将包命名为特征名称下的“mappers”
  • 使用泛型创建一个名为Mapper的接口,并使用这个映射器将实体转换为模型,反之亦然
  • 不要在控制器层将实体作为返回对象

示例

package org.project.feature.mappers;
 
 import org.project.feature.entities.Users;
 import org.project.feature.models.UsersRequest;
 import org.project.feature.models.UsersResponse;
 import org.springframework.stereotype.Component;
 
 @Component
 public class UsersMapper {
 
     public UsersResponse convertEntityToResponse(Users users) {
         UsersResponse usersResponse = new UsersResponse();
         usersResponse.setId(users.getId());
         usersResponse.setFirstName(users.getFirstName());
         usersResponse.setLastName(users.getLastName());
         usersResponse.setEmail(users.getEmail());
         return usersResponse;
     }
 
     public Users convertRequestToEntity(UsersRequest usersRequest) {
         Users users = new Users();
         users.setFirstName(usersRequest.getFirstName());
         users.setLastName(usersRequest.getLastName());
         users.setEmail(usersRequest.getEmail());
         return users;
     }
 
 }


模型:有用的提示

  • 将包命名为特征名称下的“models”
  • 所有的请求和响应对象都将存储在这里
  • 为模型类使用@Data注解
  • 模型应该作为API的前端,服务应该将模型转换为实体,以便与存储库通信。

示例

package org.project.feature.models;
 
 import jakarta.persistence.Column;
 import lombok.Data;
 
 import java.io.Serializable;
 
 @Data
 public class UsersRequest implements Serializable {
     private String firstName;
     private String lastName;
     private String email;
 }


package org.project.feature.models;
 
 import jakarta.persistence.Column;
 import lombok.Data;
 
 import java.io.Serializable;
 
 @Data
 public class UsersResponse implements Serializable {
     private Long id;
     private String firstName;
     private String lastName;
     private String email;
 }


控制器:有用的提示

  • 将包命名为特征名称下的“controllers”
  • 尝试为有界上下文下的每个资源创建一个API
  • 将资源名称使用复数形式,如/API/users
  • 对于CRUD操作:
    1. 使用HTTP POST进行创建操作,请求为主体
    2. 使用HTTP PUT进行更新操作
    3. 使用HTTP GET检索所有记录
    4. 使用带/{id}的HTTP GET检索标识符
    5. 使用带/{id}的HTTP DELETE来删除记录
  • 对于其他操作,尽量避免使用动词
  • 在控制器层实现错误处理
  • 使用@Valid在控制器层进行验证
  • 理解API思维和RPC思维之间的差异非常重要。这是理解API的关键。

示例

package org.project.feature.controllers;
 
 import lombok.AllArgsConstructor;
 import org.project.feature.models.UsersRequest;
 import org.project.feature.models.UsersResponse;
 import org.project.feature.services.UsersService;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
 @RestController
 @AllArgsConstructor
 @RequestMapping("/api/users")
 public class UsersController {
 
     private final UsersService usersService;
 
     @PostMapping
     public ResponseEntity<UsersResponse> createUser(@RequestBody UsersRequest usersRequest){
         UsersResponse savedUsers = usersService.createUser(usersRequest);
         return new ResponseEntity<>(savedUsers, HttpStatus.CREATED);
     }
 
     @GetMapping("{id}")
     public ResponseEntity<UsersResponse> getUserById(@PathVariable("id") Long userId){
         UsersResponse users = usersService.getUserById(userId);
         return new ResponseEntity<>(users, HttpStatus.OK);
     }
 
     @GetMapping
     public ResponseEntity<List<UsersResponse>> getAllUsers(){
         List<UsersResponse> users = usersService.getAllUsers();
         return new ResponseEntity<>(users, HttpStatus.OK);
     }
 
     @PutMapping("{id}")
     public ResponseEntity<UsersResponse> updateUser(@PathVariable("id") Long userId,
                                             @RequestBody UsersRequest usersRequest){
         UsersResponse updatedUsers = usersService.updateUser(userId, usersRequest);
         return new ResponseEntity<>(updatedUsers, HttpStatus.OK);
     }
 
     @DeleteMapping("{id}")
     public ResponseEntity<String> deleteUser(@PathVariable("id") Long userId){
         usersService.deleteUser(userId);
         return new ResponseEntity<>("User successfully deleted!", HttpStatus.OK);
     }
 }


结论

从长远的角度来看,随着代码基础变得庞大,您可能需要使用Strangler模式将一些服务独立出来,并将其部署为单独的微服务。这种编码结构将有所帮助。如果您从一开始就把基础打好,那么以后的道路将会更加顺畅。 

推荐阅读: 5.了解ARP协议吗?

本文链接: 实施最佳实践:使用Spring Boot构建微服务API