技术架构
首先,让我们转向架构,这将详细解释。让我们详细看一下每一层。
我来详细解释一下架构。这些组件通常与遵循领域驱动设计原则的应用程序架构相关联(DDD)和模型-视图-控制器(手动音量调节)或类似的架构模式。让我逐一介绍:
实体
实体代表应用程序中的核心业务对象或概念。它们封装了与业务领域相关的数据。例如,在雇员管理系统中,雇员实体可能具有与雇员相关的姓名、电子邮件和工资等属性。
仓库
存储库负责处理数据访问逻辑。它们提供了对数据存储的抽象,允许应用程序与数据进行交互,而无需担心底层的存储细节。例如,EmployeeRepository将处理像存储、检索、更新和删除数据库中的员工记录这样的操作。
服务
服务包含不适合实体方法的业务逻辑。它们编排实体和存储库之间的交互以满足更高层次的用例。例如,EmployeeService可能具有计算奖金、处理员工调动或处理涉及多个实体的复杂业务规则的方法。
地图绘制者
映射器负责在应用程序的不同层之间转换数据。它们将数据从数据库实体转换为域对象,反之亦然。例如,EmployeeMapper可能将Employee实体转换为可以通过网络发送或由表示层使用的数据传输对象(EmployeeRequest)。
控制器
控制器处理来自用户界面或外部系统的请求。它们解释用户输入,调用必要的服务或业务逻辑,并准备发回的响应。在web应用程序中,控制器接收HTTP请求,提取必要的数据,并将请求委托给适当的服务。然后它格式化服务响应并将其发送回客户端。
前端:您可以选择构建Android和iOS等原生应用程序。桌面浏览器应用程序或移动浏览器应用程序可以使用React或Angular框架构建。
架构实施的最佳实践
实体:有用的提示
在功能名称下将包命名为“实体”
将id设置为Long并将生成类型设置为identity
用复数形式命名类和表,如users
对构造函数和getter/setter代码使用Lombok
每个字符串字段都有长度
将nullable设置为true/false
使用对其他表的引用,如@ManyToOne。请记住,创建的表是自动的,您在实体中写的内容很重要。
使用@OneToMany如果希望在一次调用中保存多个表中的值,请选择双向。
使用@ManyToMany连接表。如果连接表中存在除连接id列之外的字段,则创建一个单独的连接类。
确定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;
}
知识库:有用的提示
在特性名称下将包命名为“存储库”
扩展JpaRepository实体名称和id一样长
尽可能将样式用作查询实体的方法,如findByEmail
对数据库的多个条目使用批处理操作,例如saveAll
尽可能使用可选的返回类型
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> {
}
服务:有用的提示
在特性名称下将包命名为“服务”
为服务中的所有操作创建一个接口,并创建一个实现类
使用@AllArgsConstructor为@Autowired注释
接受请求对象并从模型的包中返回响应对象。
如果需要调用多个存储库,除非您希望启动一个新的事务,否则应该在一个事务中调用。
如果您希望调用多个服务,那么特定的服务必须被命名为聚合服务,并且在一个事务中。
不要从服务返回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”
创建一个名为制图人使用泛型将实体转换为模型,反之亦然
不要在控制器层中将实体用作返回对象
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;
}
控制器:有用的提示
在特性名称下将包命名为“控制器”
尝试在有限的上下文中为每个资源创建一个API
使用复数形式的资源名称,如/API/users
对于积垢操作:
使用HTTP POST创建以请求为主体的操作
使用HTTP PUT进行更新操作
使用HTTP GET检索所有记录
使用HTTP GET with /{id}进行带有标识符的检索
使用带有/{id}的HTTP DELETE删除记录
对于操作other,CRUD尽量避免使用动词
在控制器层实现错误处理
用@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模式取出一些服务,并将其作为单独的微服务进行部署。这种编码结构会有所帮助。如果你从一开始就掌握了基本知识,那么以后的旅程将会很顺利。