Java 单体服务开发指南
Posted zuozewei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 单体服务开发指南相关的知识,希望对你有一定的参考价值。
文章目录
一、代码组织模式
1、多仓库
-
优点
- 每一个服务都有一个独立的仓库,职责单一
- 代码量和复杂性受控,服务由不同的团队独立维护、边界清晰。
- 单个服务也易于自治开发测试部署和扩展,不需要集中管理集中协调。
-
缺点
- 项目代码不容易规范。每个团队容易各自为政,随意引入依赖,code review 无法集中开展,代码风格各不相同。
- 项目集成和部署会比较麻烦。虽然每个项目服务易于集成和部署,但是整个应用集成和部署的时候由于仓库分散就需要集中的管理和协调。
- 开发人员缺乏对整个项目的整体认知。开发人员一般只关心自己的服务代码,看不到项目整体,造成缺乏对项目整体架构和业务目标整体性的理解。
- 项目间冗余代码多。每个服务一个服务一个仓库,势必造成团队在开发的时候走捷径,不断地重复造轮子而不是去优先重用其他团队开发的代码。
2、单体仓库
-
知名企业案例
- B 站
-
优点
- 易于规范代码。所有的代码在一个仓库当中就可以标准化依赖管理,集中开展 code review,规范化代码的风格。
- 于集成和部署。所有的代码在一个仓库里面,配合自动化构建工具,可以做到一键构建、一键部署,一般不需要特别的集中管理和协调。
- 易于理解项目整体。开发人员可以把整个项目加载到本地的 IDE 当中,进行 code review,也可以直接在本地部署调试,方便开发人员把握整体的技术架构和业务目标。
- 易于重用。所有的代码都在一个仓库中,开发人员开发的时候比较容易发现和重用已有的代码,而不是去重复造轮子,开发人员(通过 IDE 的支持)容易对现有代码进行重构,可以抽取出一些公共的功能进一步提升代码的质量和复用度。
-
缺点
- 随着公司业务团队规模的变大,单一的代码库会变得越来越庞大复杂性也呈极度的上升
- 一般都有独立的代码管理和集成团队进行支持
- 有配套的自动化构建工具来支持
二、编程规约(参考《阿里 Java 开发手册》)
1、命名风格
2、常量定义
3、代码格式
4、OOP 规约
5、日期时间
6、集合处理
7、并发处理
8、控制语句
9、注释规约
10、前后端规约
11、其它
三、异常日志(参考《阿里 Java 开发手册》)
1、错误码
2、异常处理
3、日志规约
四、单元测试(参考《阿里 Java 开发手册》)
五、安全规约(参考《阿里 Java 开发手册》)
六、mysql数据库(参考《阿里 Java 开发手册》)
1、建表规约
2、SQL语句
3、ORM 映射
4、索引规约
七、工程结构(参考《阿里 Java 开发手册》)
1、应用分层
2、二方库依赖
3、服务器
八、设计规约(参考《阿里 Java 开发手册》)
九、服务开发框架
1、接口参数校验
- 技术方案
- Hibernate Validator
- 自定义注解
- 全局异常处理
Hibernate Validator 是 SpringBoot 内置的校验框架,只要集成了 SpringBoot 就自动集成了它,我们可以通过在对象上面使用它提供的注解来完成参数校验。
常用注解:
- @Null:被注释的属性必须为null;
- @NotNull:被注释的属性不能为null;
- @AssertTrue:被注释的属性必须为true;
- @AssertFalse:被注释的属性必须为false;
- @Min:被注释的属性必须大于等于其value值;
- @Max:被注释的属性必须小于等于其value值;
- @Size:被注释的属性必须在其min和max值之间;
- @Pattern:被注释的属性必须符合其regexp所定义的正则表达式;
- @NotBlank:被注释的字符串不能为空字符串;
- @NotEmpty:被注释的属性不能为空;
- @Email:被注释的属性必须符合邮箱格式。
有时候框架提供的校验注解并不能满足我们的需要,此时我们就需要自定义校验注解。比如还是上面的添加品牌,此时有个参数 showStatus,我们希望它只能是 0 或者 1,不能是其他数字,此时可以使用自定义注解来实现该功能。
- 控制器接口参数校验
@GetMapping(path = "/list")
ListAccountResponse listAccounts(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestParam int offset, @RequestParam @Min(0) int limit);
// GetOrCreate is for internal use by other APIs to match a user based on their phonenumber or email.
@PostMapping(path= "/get_or_create")
GenericAccountResponse getOrCreateAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid GetOrCreateRequest request);
@GetMapping(path = "/get")
GenericAccountResponse getAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestParam @NotBlank String userId);
@PutMapping(path = "/update")
GenericAccountResponse updateAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid AccountDto newAccount);
@GetMapping(path = "/get_account_by_phonenumber")
GenericAccountResponse getAccountByPhonenumber(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestParam @PhoneNumber String phoneNumber);
- DTO 参数校验
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AccountDto {
@NotBlank
private String id;
private String name;
@Email(message = "Invalid email")
private String email;
private boolean confirmedAndActive;
@NotNull
private Instant memberSince;
private boolean support;
@PhoneNumber
private String phoneNumber;
@NotEmpty
private String photoUrl;
}
- 自定义注解
@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {
String message() default "Invalid phone number";
Class[] groups() default {};
Class[] payload() default {};
}
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
@Override
public boolean isValid(String phoneField, ConstraintValidatorContext context) {
if (phoneField == null) return true; // can be null
return phoneField != null && phoneField.matches("[0-9]+")
&& (phoneField.length() > 8) && (phoneField.length() < 14);
}
}
2、统一异常处理
使用全局异常处理来处理校验逻辑的思路很简单,首先我们需要通过 @ControllerAdvice 注解定义一个全局异常的处理类,然后自定义一个校验异常,当我们在 Controller 中校验失败时,直接抛出该异常,这样就可以达到校验失败返回错误信息的目的了
使用到的注解:
- @ControllerAdvice:类似于@Component 注解,可以指定一个组件,这个组件主要用于增强@Controller注解修饰的类的功能,比如说进行全局异常处理。
- @ExceptionHandler:用来修饰全局异常处理的方法,可以指定异常的类型。
- RestControllerAdvice
@RestControllerAdvice
public class GlobalExceptionTranslator {
static final ILogger logger = SLoggerFactory.getLogger(GlobalExceptionTranslator.class);
@ExceptionHandler(MissingServletRequestParameterException.class)
public BaseResponse handleError(MissingServletRequestParameterException e) {
logger.warn("Missing Request Parameter", e);
String message = String.format("Missing Request Parameter: %s", e.getParameterName());
return BaseResponse
.builder()
.code(ResultCode.PARAM_MISS)
.message(message)
.build();
}
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public BaseResponse handleError(MethodArgumentTypeMismatchException e) {
logger.warn("Method Argument Type Mismatch", e);
String message = String.format("Method Argument Type Mismatch: %s", e.getName());
return BaseResponse
.builder()
.code(ResultCode.PARAM_TYPE_ERROR)
.message(message)
.build();
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public BaseResponse handleError(MethodArgumentNotValidException e) {
logger.warn("Method Argument Not Valid", e);
BindingResult result = e.getBindingResult();
FieldError error = result.getFieldError();
String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
return BaseResponse
.builder()
.code(ResultCode.PARAM_VALID_ERROR)
.message(message)
.build();
}
@ExceptionHandler(BindException.class)
public BaseResponse handleError(BindException e) {
logger.warn("Bind Exception", e);
FieldError error = e.getFieldError();
String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
return BaseResponse
.builder()
.code(ResultCode.PARAM_BIND_ERROR)
.message(message)
.build();
}
@ExceptionHandler(ConstraintViolationException.class)
public BaseResponse handleError(ConstraintViolationException e) {
logger.warn("Constraint Violation", e);
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
ConstraintViolation<?> violation = violations.iterator().next();
String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();
String message = String.format("%s:%s", path, violation.getMessage());
return BaseResponse
.builder()
.code(ResultCode.PARAM_VALID_ERROR)
.message(message)
.build();
}
@ExceptionHandler(NoHandlerFoundException.class)
public BaseResponse handleError(NoHandlerFoundException e) {
logger.error("404 Not Found", e);
return BaseResponse
.builder()
.code(ResultCode.NOT_FOUND)
.message(e.getMessage())
.build();
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public BaseResponse handleError(HttpMessageNotReadableException e) {
logger.error("Message Not Readable", e);
return BaseResponse
.builder()
.code(ResultCode.MSG_NOT_READABLE)
.message(e.getMessage())
.build();
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public BaseResponse handleError(HttpRequestMethodNotSupportedException e) {
logger.error("Request Method Not Supported", e);
return BaseResponse
.builder()
.code(ResultCode.METHOD_NOT_SUPPORTED)
.message(e.getMessage())
.build();
}
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public BaseResponse handleError(HttpMediaTypeNotSupportedException e) {
logger.error("Media Type Not Supported", e);
return BaseResponse
.builder()
.code(ResultCode.MEDIA_TYPE_NOT_SUPPORTED)
.message(e.getMessage())
.build();
}
@ExceptionHandler(ServiceException.class)
public BaseResponse handleError(ServiceException e) {
logger.error("Service Exception", e);
return BaseResponse
.builder()
.code(e.getResultCode())
.message(e.getMessage())
.build();
}
@ExceptionHandler(PermissionDeniedException.class)
public BaseResponse handleError(PermissionDeniedException e) {
logger.error("Permission Denied", e);
return BaseResponse
.builder()
.code(e.getResultCode())
.message(e.getMessage())
.build();
}
@ExceptionHandler(Throwable.class)
public BaseResponse handleError(Throwable e) {
logger.error("Internal Server Error", e);
return BaseResponse
.builder()
.code(ResultCode.INTERNAL_SERVER_ERROR)
.message(e.getMessage())
.build();
}
}
- 统一异常捕获
@ExceptionHandler(ServiceException.class)
public BaseResponse handleError(ServiceException e) {
logger.error("Service Exception", e);
return BaseResponse
.builder()
.code(e.getResultCode())
.message(e.getMessage())
.build();
}
@ExceptionHandler(Throwable.class)
public BaseResponse handleError(Throwable e) {
logger.error("Internal Server Error", e);
return BaseResponse
.builder()
.code(ResultCode.INTERNAL_SERVER_ERROR)
.message(e.getMessage())
.build();
}
- BaseResponse
- 继承
- 封装消息+捎带
- 正常响应
- 异常响应
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BaseResponse {
private String message;
@Builder.Default
private ResultCode code = ResultCode.SUCCESS;
public boolean isSuccess() {
return code == ResultCode.SUCCESS;
}
}
- Web MVC ErrorController
@Controller
@SuppressWarnings(value = "Duplicates")
public class GlobalErrorController implements ErrorController {
static final ILogger logger = SLoggerFactory.getLogger(GlobalErrorController.class);
@Autowired
ErrorPageFactory errorPageFactory;
@Auto以上是关于Java 单体服务开发指南的主要内容,如果未能解决你的问题,请参考以下文章