飞道的博客

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

393人阅读  评论(0)

目录

一、依赖管理

二、服务模块管理 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<ConstraintViolation<?>> 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<String, EnvConfig> map;
  17. static {
  18. map = new HashMap<String, EnvConfig>();
  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. <dependency>
  2. <groupId>io.springfox</groupId>
  3. <artifactId>springfox-swagger2</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>io.springfox</groupId>
  7. <artifactId>springfox-swagger-ui</artifactId>
  8. </dependency>

  
  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. }

 十三、常用框架和比对

 


转载:https://blog.csdn.net/kk_lina/article/details/127703463
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场