• 2、云原生微服务实践-服务开发框架设计和实践


    目录

    一、依赖管理

    二、服务模块管理 api、svc

    三、其他文件管理

    1、私密配置文件

    2、前端页面单页文件

    四、单体仓库 mono-repo

    1、单体仓库和多仓库的对比: 

    2、单体仓库优点

    五、接口参数校验

    六、统一异常处理

    七、DTO(数据传输对象)和DMO(数据模型对象)

    八、强类型接口设计

    1、特点:接口规划、编译器自动类型检查、自动代码生成。但是客户端和服务端耦合性大

    2、Spring Feign


    一、依赖管理

    JavaSpring项目使用Maven管理所有微服务先继承实现父模块依赖,根据自己模块业务再附加依赖。common-lib公共共享模块,服务开发封装类。

    二、服务模块管理 api、svc

    每个服务由两个模块组成,接口模块和服务模块,例如account-aip、account-svc,一般api接口模块上传到maven仓库进行管理,api为强类型客户端,其他服务调用相关服务的api即可。

    三、其他文件管理

    1、私密配置文件

    除了服务外还有config目录进行对私密文件的存储,使用spring的本地私密配置方式,config里面的私密控制不会被监听到版本控制。

    2、前端页面单页文件

    四、单体仓库 mono-repo

    1、单体仓库和多仓库的对比: 

    2、单体仓库优点

    • 易于规范代码风格
    • 易于集成和部署,配合构建工具可以一键部署
    • 易于理解,方便开发人员把握整体建构
    • 易于复用,可以抽取公共功能,进行重构

    五、接口参数校验

    spring有成套且成熟的校验注解,开发成员只需要填写注释。开发人员也可以根据自己的业务校验规则定义注解,实现ConstraintValidator,重写校验规则即可。

    例如:

    六、统一异常处理

    1. package xyz.staffjoy.common.error;
    2. import com.github.structlog4j.ILogger;
    3. import com.github.structlog4j.SLoggerFactory;
    4. import org.hibernate.validator.internal.engine.path.PathImpl;
    5. import org.springframework.http.converter.HttpMessageNotReadableException;
    6. import org.springframework.validation.BindException;
    7. import org.springframework.validation.BindingResult;
    8. import org.springframework.validation.FieldError;
    9. import org.springframework.web.HttpMediaTypeNotSupportedException;
    10. import org.springframework.web.HttpRequestMethodNotSupportedException;
    11. import org.springframework.web.bind.MethodArgumentNotValidException;
    12. import org.springframework.web.bind.MissingServletRequestParameterException;
    13. import org.springframework.web.bind.annotation.*;
    14. import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
    15. import org.springframework.web.servlet.NoHandlerFoundException;
    16. import xyz.staffjoy.common.api.BaseResponse;
    17. import xyz.staffjoy.common.api.ResultCode;
    18. import xyz.staffjoy.common.auth.PermissionDeniedException;
    19. import javax.validation.ConstraintViolation;
    20. import javax.validation.ConstraintViolationException;
    21. import java.util.Set;
    22. @RestControllerAdvice
    23. public class GlobalExceptionTranslator {
    24. static final ILogger logger = SLoggerFactory.getLogger(GlobalExceptionTranslator.class);
    25. @ExceptionHandler(MissingServletRequestParameterException.class)
    26. public BaseResponse handleError(MissingServletRequestParameterException e) {
    27. logger.warn("Missing Request Parameter", e);
    28. String message = String.format("Missing Request Parameter: %s", e.getParameterName());
    29. return BaseResponse
    30. .builder()
    31. .code(ResultCode.PARAM_MISS)
    32. .message(message)
    33. .build();
    34. }
    35. @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    36. public BaseResponse handleError(MethodArgumentTypeMismatchException e) {
    37. logger.warn("Method Argument Type Mismatch", e);
    38. String message = String.format("Method Argument Type Mismatch: %s", e.getName());
    39. return BaseResponse
    40. .builder()
    41. .code(ResultCode.PARAM_TYPE_ERROR)
    42. .message(message)
    43. .build();
    44. }
    45. @ExceptionHandler(MethodArgumentNotValidException.class)
    46. public BaseResponse handleError(MethodArgumentNotValidException e) {
    47. logger.warn("Method Argument Not Valid", e);
    48. BindingResult result = e.getBindingResult();
    49. FieldError error = result.getFieldError();
    50. String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
    51. return BaseResponse
    52. .builder()
    53. .code(ResultCode.PARAM_VALID_ERROR)
    54. .message(message)
    55. .build();
    56. }
    57. @ExceptionHandler(BindException.class)
    58. public BaseResponse handleError(BindException e) {
    59. logger.warn("Bind Exception", e);
    60. FieldError error = e.getFieldError();
    61. String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
    62. return BaseResponse
    63. .builder()
    64. .code(ResultCode.PARAM_BIND_ERROR)
    65. .message(message)
    66. .build();
    67. }
    68. @ExceptionHandler(ConstraintViolationException.class)
    69. public BaseResponse handleError(ConstraintViolationException e) {
    70. logger.warn("Constraint Violation", e);
    71. Set> violations = e.getConstraintViolations();
    72. ConstraintViolation violation = violations.iterator().next();
    73. String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();
    74. String message = String.format("%s:%s", path, violation.getMessage());
    75. return BaseResponse
    76. .builder()
    77. .code(ResultCode.PARAM_VALID_ERROR)
    78. .message(message)
    79. .build();
    80. }
    81. @ExceptionHandler(NoHandlerFoundException.class)
    82. public BaseResponse handleError(NoHandlerFoundException e) {
    83. logger.error("404 Not Found", e);
    84. return BaseResponse
    85. .builder()
    86. .code(ResultCode.NOT_FOUND)
    87. .message(e.getMessage())
    88. .build();
    89. }
    90. @ExceptionHandler(HttpMessageNotReadableException.class)
    91. public BaseResponse handleError(HttpMessageNotReadableException e) {
    92. logger.error("Message Not Readable", e);
    93. return BaseResponse
    94. .builder()
    95. .code(ResultCode.MSG_NOT_READABLE)
    96. .message(e.getMessage())
    97. .build();
    98. }
    99. @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    100. public BaseResponse handleError(HttpRequestMethodNotSupportedException e) {
    101. logger.error("Request Method Not Supported", e);
    102. return BaseResponse
    103. .builder()
    104. .code(ResultCode.METHOD_NOT_SUPPORTED)
    105. .message(e.getMessage())
    106. .build();
    107. }
    108. @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    109. public BaseResponse handleError(HttpMediaTypeNotSupportedException e) {
    110. logger.error("Media Type Not Supported", e);
    111. return BaseResponse
    112. .builder()
    113. .code(ResultCode.MEDIA_TYPE_NOT_SUPPORTED)
    114. .message(e.getMessage())
    115. .build();
    116. }
    117. @ExceptionHandler(ServiceException.class)
    118. public BaseResponse handleError(ServiceException e) {
    119. logger.error("Service Exception", e);
    120. return BaseResponse
    121. .builder()
    122. .code(e.getResultCode())
    123. .message(e.getMessage())
    124. .build();
    125. }
    126. @ExceptionHandler(PermissionDeniedException.class)
    127. public BaseResponse handleError(PermissionDeniedException e) {
    128. logger.error("Permission Denied", e);
    129. return BaseResponse
    130. .builder()
    131. .code(e.getResultCode())
    132. .message(e.getMessage())
    133. .build();
    134. }
    135. @ExceptionHandler(Throwable.class)
    136. public BaseResponse handleError(Throwable e) {
    137. logger.error("Internal Server Error", e);
    138. return BaseResponse
    139. .builder()
    140. .code(ResultCode.INTERNAL_SERVER_ERROR)
    141. .message(e.getMessage())
    142. .build();
    143. }
    144. }

    1. package xyz.staffjoy.common.error;
    2. import lombok.Getter;
    3. import xyz.staffjoy.common.api.ResultCode;
    4. /**
    5. * Business Service Exception
    6. *
    7. * @author william
    8. */
    9. public class ServiceException extends RuntimeException {
    10. private static final long serialVersionUID = 2359767895161832954L;
    11. @Getter
    12. private final ResultCode resultCode;
    13. public ServiceException(String message) {
    14. super(message);
    15. this.resultCode = ResultCode.FAILURE;
    16. }
    17. public ServiceException(ResultCode resultCode) {
    18. super(resultCode.getMsg());
    19. this.resultCode = resultCode;
    20. }
    21. public ServiceException(ResultCode resultCode, String msg) {
    22. super(msg);
    23. this.resultCode = resultCode;
    24. }
    25. public ServiceException(ResultCode resultCode, Throwable cause) {
    26. super(cause);
    27. this.resultCode = resultCode;
    28. }
    29. public ServiceException(String msg, Throwable cause) {
    30. super(msg, cause);
    31. this.resultCode = ResultCode.FAILURE;
    32. }
    33. /**
    34. * for better performance
    35. *
    36. * @return Throwable
    37. */
    38. @Override
    39. public Throwable fillInStackTrace() {
    40. return this;
    41. }
    42. public Throwable doFillInStackTrace() {
    43. return super.fillInStackTrace();
    44. }
    45. }

    七、DTO(数据传输对象)和DMO(数据模型对象)

    dto与dmo互转:

    转换工具地址:https://github.com/modelmapper/modelmapper

    modelmap使用详情:https://blog.csdn.net/m0_54797663/article/details/115277264

    八、强类型接口设计

    1、特点:接口规划、编译器自动类型检查、自动代码生成。但是客户端和服务端耦合性大

    通过Spring Feign结合强、若类型的结合,既能达到强类型接口的规范性又能达到弱类型接口的解耦性。

    2、Spring Feign

    注意:返回类型如果根据泛型进行包装的,springfeign是无法解析的,因为编译之后的泛型会被擦除!  

    九、环境隔离

    1. public class EnvConstant {
    2. public static final String ENV_DEV = "dev";
    3. public static final String ENV_TEST = "test";
    4. public static final String ENV_UAT = "uat"; // similar to staging
    5. public static final String ENV_PROD = "prod";
    6. }
    1. package xyz.staffjoy.common.env;
    2. import lombok.*;
    3. import java.util.HashMap;
    4. import java.util.Map;
    5. // environment related configuration
    6. @Data
    7. @Builder
    8. public class EnvConfig {
    9. private String name;
    10. private boolean debug;
    11. private String externalApex;
    12. private String internalApex;
    13. private String scheme;
    14. @Getter(AccessLevel.NONE)
    15. @Setter(AccessLevel.NONE)
    16. private static Map map;
    17. static {
    18. map = new HashMap();
    19. EnvConfig envConfig = EnvConfig.builder().name(EnvConstant.ENV_DEV)
    20. .debug(true)
    21. .externalApex("staffjoy-v2.local")
    22. .internalApex(EnvConstant.ENV_DEV)
    23. .scheme("http")
    24. .build();
    25. map.put(EnvConstant.ENV_DEV, envConfig);
    26. envConfig = EnvConfig.builder().name(EnvConstant.ENV_TEST)
    27. .debug(true)
    28. .externalApex("staffjoy-v2.local")
    29. .internalApex(EnvConstant.ENV_DEV)
    30. .scheme("http")
    31. .build();
    32. map.put(EnvConstant.ENV_TEST, envConfig);
    33. // for aliyun k8s demo, enable debug and use http and staffjoy-uat.local
    34. // in real world, disable debug and use http and staffjoy-uat.xyz in UAT environment
    35. envConfig = EnvConfig.builder().name(EnvConstant.ENV_UAT)
    36. .debug(true)
    37. .externalApex("staffjoy-uat.local")
    38. .internalApex(EnvConstant.ENV_UAT)
    39. .scheme("http")
    40. .build();
    41. map.put(EnvConstant.ENV_UAT, envConfig);
    42. // envConfig = EnvConfig.builder().name(EnvConstant.ENV_UAT)
    43. // .debug(false)
    44. // .externalApex("staffjoy-uat.xyz")
    45. // .internalApex(EnvConstant.ENV_UAT)
    46. // .scheme("https")
    47. // .build();
    48. // map.put(EnvConstant.ENV_UAT, envConfig);
    49. envConfig = EnvConfig.builder().name(EnvConstant.ENV_PROD)
    50. .debug(false)
    51. .externalApex("staffjoy.com")
    52. .internalApex(EnvConstant.ENV_PROD)
    53. .scheme("https")
    54. .build();
    55. map.put(EnvConstant.ENV_PROD, envConfig);
    56. }
    57. public static EnvConfig getEnvConfg(String env) {
    58. EnvConfig envConfig = map.get(env);
    59. if (envConfig == null) {
    60. envConfig = map.get(EnvConstant.ENV_DEV);
    61. }
    62. return envConfig;
    63. }
    64. }

    十、异步调用处理

    配置异步线程池:

    1. package xyz.staffjoy.account.config;
    2. import org.springframework.context.annotation.Bean;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.context.annotation.Import;
    5. import org.springframework.scheduling.annotation.EnableAsync;
    6. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    7. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    8. import org.springframework.security.crypto.password.PasswordEncoder;
    9. import xyz.staffjoy.common.async.ContextCopyingDecorator;
    10. import xyz.staffjoy.common.config.StaffjoyRestConfig;
    11. import java.util.concurrent.Executor;
    12. @Configuration
    13. @EnableAsync
    14. @Import(value = {StaffjoyRestConfig.class})
    15. @SuppressWarnings(value = "Duplicates")
    16. public class AppConfig {
    17. public static final String ASYNC_EXECUTOR_NAME = "asyncExecutor";
    18. @Bean(name=ASYNC_EXECUTOR_NAME)
    19. public Executor asyncExecutor() {
    20. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    21. // for passing in request scope context
    22. executor.setTaskDecorator(new ContextCopyingDecorator());
    23. executor.setCorePoolSize(3);
    24. executor.setMaxPoolSize(5);
    25. executor.setQueueCapacity(100);
    26. executor.setWaitForTasksToCompleteOnShutdown(true);
    27. executor.setThreadNamePrefix("AsyncThread-");
    28. executor.initialize();
    29. return executor;
    30. }
    31. @Bean
    32. public PasswordEncoder passwordEncoder() {
    33. return new BCryptPasswordEncoder();
    34. }
    35. }

    注意:调用方与被调用方不能在同一个bean中。

    十二、集成Swagger文档

    SpringBoot引入swagger依赖

    1. io.springfox
    2. springfox-swagger2
    3. io.springfox
    4. springfox-swagger-ui
    1. package xyz.staffjoy.account.config;
    2. import org.springframework.context.annotation.Bean;
    3. import org.springframework.context.annotation.Configuration;
    4. import springfox.documentation.builders.ApiInfoBuilder;
    5. import springfox.documentation.builders.PathSelectors;
    6. import springfox.documentation.builders.RequestHandlerSelectors;
    7. import springfox.documentation.service.ApiInfo;
    8. import springfox.documentation.service.Contact;
    9. import springfox.documentation.spi.DocumentationType;
    10. import springfox.documentation.spring.web.plugins.Docket;
    11. import springfox.documentation.swagger2.annotations.EnableSwagger2;
    12. @Configuration
    13. @EnableSwagger2
    14. public class SwaggerConfig {
    15. @Bean
    16. public Docket api() {
    17. return new Docket(DocumentationType.SWAGGER_2)
    18. .select()
    19. .apis(RequestHandlerSelectors.basePackage("xyz.staffjoy.account.controller"))
    20. .paths(PathSelectors.any())
    21. .build()
    22. .apiInfo(apiEndPointsInfo())
    23. .useDefaultResponseMessages(false);
    24. }
    25. private ApiInfo apiEndPointsInfo() {
    26. return new ApiInfoBuilder().title("Account REST API")
    27. .description("Staffjoy Account REST API")
    28. .contact(new Contact("bobo", "https://github.com/jskillcloud", "bobo@jskillcloud.com"))
    29. .license("The MIT License")
    30. .licenseUrl("https://opensource.org/licenses/MIT")
    31. .version("V2")
    32. .build();
    33. }
    34. }

     十三、常用框架和比对

     

  • 相关阅读:
    Overleaf能使用自己系统中的字体嘛?
    夯基提质|正雅膜片及附件定位系统双升级
    Redis的事务管理
    Codeforces Round 888 (Div. 3)
    安卓实训作孽之Linux命令手册
    Delete `␍` 最简单最有效的解决方法和解释(VScode)
    Certbot免费的HTTPS证书
    【内网安全】横向移动-IPC
    网络安全(黑客)自学
    深入理解 Java 对象的内存布局
  • 原文地址:https://blog.csdn.net/kk_lina/article/details/127703463