2、云原生微服务实践-服务开发框架设计和实践
创始人
2024-01-21 02:06:24

目录

一、依赖管理

二、服务模块管理 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,重写校验规则即可。

例如:

六、统一异常处理

package xyz.staffjoy.common.error;import com.github.structlog4j.ILogger;
import com.github.structlog4j.SLoggerFactory;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import xyz.staffjoy.common.api.BaseResponse;
import xyz.staffjoy.common.api.ResultCode;
import xyz.staffjoy.common.auth.PermissionDeniedException;import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;@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> 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();}
}

package xyz.staffjoy.common.error;import lombok.Getter;
import xyz.staffjoy.common.api.ResultCode;/*** Business Service Exception** @author william*/
public class ServiceException extends RuntimeException {private static final long serialVersionUID = 2359767895161832954L;@Getterprivate final ResultCode resultCode;public ServiceException(String message) {super(message);this.resultCode = ResultCode.FAILURE;}public ServiceException(ResultCode resultCode) {super(resultCode.getMsg());this.resultCode = resultCode;}public ServiceException(ResultCode resultCode, String msg) {super(msg);this.resultCode = resultCode;}public ServiceException(ResultCode resultCode, Throwable cause) {super(cause);this.resultCode = resultCode;}public ServiceException(String msg, Throwable cause) {super(msg, cause);this.resultCode = ResultCode.FAILURE;}/*** for better performance** @return Throwable*/@Overridepublic Throwable fillInStackTrace() {return this;}public Throwable doFillInStackTrace() {return super.fillInStackTrace();}
}

七、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是无法解析的,因为编译之后的泛型会被擦除!  

九、环境隔离

public class EnvConstant {public static final String ENV_DEV = "dev";public static final String ENV_TEST = "test";public static final String ENV_UAT = "uat"; // similar to stagingpublic static final String ENV_PROD = "prod";
}
package xyz.staffjoy.common.env;import lombok.*;import java.util.HashMap;
import java.util.Map;// environment related configuration
@Data
@Builder
public class EnvConfig {private String name;private boolean debug;private String externalApex;private String internalApex;private String scheme;@Getter(AccessLevel.NONE)@Setter(AccessLevel.NONE)private static Map map;static {map = new HashMap();EnvConfig envConfig = EnvConfig.builder().name(EnvConstant.ENV_DEV).debug(true).externalApex("staffjoy-v2.local").internalApex(EnvConstant.ENV_DEV).scheme("http").build();map.put(EnvConstant.ENV_DEV, envConfig);envConfig = EnvConfig.builder().name(EnvConstant.ENV_TEST).debug(true).externalApex("staffjoy-v2.local").internalApex(EnvConstant.ENV_DEV).scheme("http").build();map.put(EnvConstant.ENV_TEST, envConfig);// for aliyun k8s demo, enable debug and use http and staffjoy-uat.local// in real world, disable debug and use http and staffjoy-uat.xyz in UAT environmentenvConfig = EnvConfig.builder().name(EnvConstant.ENV_UAT).debug(true).externalApex("staffjoy-uat.local").internalApex(EnvConstant.ENV_UAT).scheme("http").build();map.put(EnvConstant.ENV_UAT, envConfig);//        envConfig = EnvConfig.builder().name(EnvConstant.ENV_UAT)
//                .debug(false)
//                .externalApex("staffjoy-uat.xyz")
//                .internalApex(EnvConstant.ENV_UAT)
//                .scheme("https")
//                .build();
//        map.put(EnvConstant.ENV_UAT, envConfig);envConfig = EnvConfig.builder().name(EnvConstant.ENV_PROD).debug(false).externalApex("staffjoy.com").internalApex(EnvConstant.ENV_PROD).scheme("https").build();map.put(EnvConstant.ENV_PROD, envConfig);}public static EnvConfig getEnvConfg(String env) {EnvConfig envConfig = map.get(env);if (envConfig == null) {envConfig = map.get(EnvConstant.ENV_DEV);}return envConfig;}
}

十、异步调用处理

配置异步线程池:

package xyz.staffjoy.account.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import xyz.staffjoy.common.async.ContextCopyingDecorator;
import xyz.staffjoy.common.config.StaffjoyRestConfig;import java.util.concurrent.Executor;@Configuration
@EnableAsync
@Import(value = {StaffjoyRestConfig.class})
@SuppressWarnings(value = "Duplicates")
public class AppConfig {public static final String ASYNC_EXECUTOR_NAME = "asyncExecutor";@Bean(name=ASYNC_EXECUTOR_NAME)public Executor asyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// for passing in request scope contextexecutor.setTaskDecorator(new ContextCopyingDecorator());executor.setCorePoolSize(3);executor.setMaxPoolSize(5);executor.setQueueCapacity(100);executor.setWaitForTasksToCompleteOnShutdown(true);executor.setThreadNamePrefix("AsyncThread-");executor.initialize();return executor;}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}

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

十二、集成Swagger文档

SpringBoot引入swagger依赖

io.springfoxspringfox-swagger2io.springfoxspringfox-swagger-ui
package xyz.staffjoy.account.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration
@EnableSwagger2
public class SwaggerConfig {@Beanpublic Docket api() {return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.basePackage("xyz.staffjoy.account.controller")).paths(PathSelectors.any()).build().apiInfo(apiEndPointsInfo()).useDefaultResponseMessages(false);}private ApiInfo apiEndPointsInfo() {return new ApiInfoBuilder().title("Account REST API").description("Staffjoy Account REST API").contact(new Contact("bobo", "https://github.com/jskillcloud", "bobo@jskillcloud.com")).license("The MIT License").licenseUrl("https://opensource.org/licenses/MIT").version("V2").build();}
}

 十三、常用框架和比对

 

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
进口奶粉的排名 荷兰奶粉十大排... 越来越乱的国内奶粉市场让很多宝妈宝爸都不知道该如何选择奶粉品牌,宝宝的安全是第一位,奶粉的质量以及口...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
不用扬鞭自奋蹄 乘势而上千帆竞... 思明区正全力向“两个进位”目标发起冲刺,努力为建设高素质高颜值现代化国际化的厦门作出“思明贡献”。(...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...