要想打通整个微服务架构中的所有通道,就需要在标准 request contract 定义 mockParameter ,这是这一切的前提。
服务与服务之间调用走标准微服务 request contract,服务与外部系统的依赖可以选择走 HTTP Header,也可以选择走标准 request ,就要看我们的整个服务框架是否已经覆盖所有的产线及一些遗留系统的问题。
public abstract class BaseRequest implements Serializable { public MockParameter mockParameter; } |
BaseRequest 是所有 request 的基类,这样才能保证所有的请求能够正常的传递。
使用 AOP + RestEasy HttpClientRequest SPI 初步实现 Mock
整个系统的开发架构分层依赖是:facade->biz->service,基本的所有核心逻辑都是在 service 中,请求的 request dto 最多不能越界到 service 层,按照规范讲 request dto 顶多滞留在 biz 层,但是在互联网的世界中一些都是可以快速迭代的,并不是多么硬性规定,及时重构是偿还技术债务的主要方法。
前面我们已经讲过,我们采用的 RPC 框架是 RestEasy + RestEasy client ,我们先来看下入口的地方。
@Component @Path("v1/calculator/") public class RuleCalculatorFacadeImpl extends BaseFacade implements RuleCalculatorFacade { @MockFacade(Setting = MockFacade.SETTING_REQUEST_MOCK_PARAMETER) public RuleCalculateResponse ruleCalculate(RuleCalculateRequest request) { ... } } |
请在文本框输入文字 |
再看下 service 对象。
@Component public class MarketingServiceImpl extends MarketingBaseService implements MarketingService { @MockFacade(Setting = MockFacade.SETTING_FACADE_MOCK_BEAN) public MarketingResult onlyExtendMarketingActivity(Marketing..Parameter tagsParameter) { ... } |
我们重点看下 @MockFacade annotation 声明。
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MockFacade { String SETTING_REQUEST_MOCK_PARAMETER = "setting_request_mock_parameter"; String SETTING_FACADE_MOCK_BEAN = "setting_facade_mock_bean"; String Setting(); } |
通过这个 annotation 我们的主要目的就是将 mockParameter 放到 ThreadLocal 中去和请求处理完时的清理工作。还有一个功能就是 service 层的 mock bean 处理。
@Aspect @Component @Slf4j public class MockMarketingFacadeInterceptor { @Before("@annotation(mockFacade)") public void beforeMethod(JoinPoint joinPoint, MockFacade mockFacade) { String settingName = mockFacade.Setting(); if (MockFacade.SETTING_REQUEST_MOCK_PARAMETER.equals(settingName)) { Object[] args = joinPoint.getArgs(); if (args == null) return; List<Object> argList = Arrays.asList(args); argList.forEach(item -> { if (item instanceof BaseRequest) { BaseRequest request = (BaseRequest) item; if (request.getMockParameter() != null) { MarketingBaseService.mockParameterThreadLocal.set(request.getMockParameter()); log.info("----setting mock parameter:{}", JSON.toJSONString(request.getMockParameter())); } } }); } else if (MockFacade.SETTING_FACADE_MOCK_BEAN.equals(settingName)) { MarketingBaseService marketingBaseService = (MarketingBaseService) joinPoint.getThis(); marketingBaseService.mockBean(); log.info("----setting mock bean."); } } @After("@annotation(mockFacade)") public void afterMethod(JoinPoint joinpoint, MockFacade mockFacade) { if (MockFacade.SETTING_FACADE_MOCK_BEAN.equals(mockFacade.Setting())) { MarketingBaseService marketingBaseService = (MarketingBaseService) joinpoint.getThis(); marketingBaseService.mockRemove(); log.info("----remove mock bean."); } if (MockFacade.SETTING_REQUEST_MOCK_PARAMETER.equals(mockFacade.Setting())) { MarketingBaseService.mockParameterThreadLocal.remove(); log.info("----remove ThreadLocal. ThreadLocal get {}", MarketingBaseService.mockParameterThreadLocal.get()); } } } |
这些逻辑完全基于一个约定,就是 MarketingBaseService,不具有通用型,只是在逐步的重构和提取中,最终会是一个 plugin 框架。
public abstract class MarketingBaseService extends BaseService { protected ClassMarketingCentralFacade classMarketingCentralFacade; protected CCMarketingCentralFacade ccMarketingCentralFacade; public static ThreadLocal<MockParameter> mockParameterThreadLocal = new ThreadLocal<>(); public void mockBean() { MockParameter mockParameter = mockParameterThreadLocal.get(); if (mockParameter != null && mockParameter.mockClassMarketingInterface) { if (mockParameter.useAutoTestingMock) { this.setClassMarketingCentralFacade(SpringContextHolder.getBean("ClassMarketingCentralFacadeTestMock", ClassMarketingCentralFacade.class)); } else { this.setClassMarketingCentralFacade(SpringContextHolder.getBean("ClassMarketingCentralFacadeMocker", ClassMarketingCentralFacadeMocker.class)); } } else { this.setClassMarketingCentralFacade(SpringContextHolder.getBean("ClassMarketingCentralFacade", ClassMarketingCentralFacade.class)); } if (mockParameter != null && mockParameter.mockCCMarketingInterface) { if (mockParameter.useAutoTestingMock) { this.setCcMarketingCentralFacade(SpringContextHolder.getBean("CCMarketingCentralFacadeTestMock", CCMarketingCentralFacade.class)); } else { this.setCcMarketingCentralFacade(SpringContextHolder.getBean("CCMarketingCentralFacadeMocker", CCMarketingCentralFacadeMocker.class)); } } else { this.setCcMarketingCentralFacade(SpringContextHolder.getBean("CCMarketingCentralFacade", CCMarketingCentralFacade.class)); } } public void mockRemove() { mockParameterThreadLocal.remove(); } } |
我们可以顺利的将 request 中的 mockParameter 放到 ThreadLocal 中,可以动态的通过 AOP 的方式来注入相应的 mockerBean。
现在我们还要处理的就是对 mockGateway 的调用将 __mockParameter_ 中的 autoContext 中的标示字符串放到 HTTP Header 中去。
@Component public class MockHttpHeadSetting implements ClientRequestFilter { @Override public void filter(ClientRequestContext requestContext) throws IOException { MultivaluedMap<String, Object> header = requestContext.getHeaders(); MockParameter mockParameter = MarketingBaseService.mockParameterThreadLocal.get(); if (mockParameter != null && StringUtils.isNotBlank(mockParameter.getTestingMockParam())) { header.add("Mock-parameter", mockParameter.getTestingMockParam()); } } } |
接着在 SPI(javax.ws.rs.ext.Providers ) 文件中配置即可
com.hujiang.marketingcloud.ruleengine.service.MockHttpHeadSetting |
总结
在整个微服务架构的实践中,工程界一直缺少探讨的就是在微服务架构的测试这块,离我们比较近的是自动化测试,因为自动化测试基本上是所有系统都需要的。
但是有一块我们一直没有重视的就是 全链路压力测试 这块,在生产上进行全链路的真实的压力测试需要解决很多问题,比较重要的就是 DB 这块,压测的时候产生的所有交易数据不能够参与结算、财务流程,这就需要借助 影子表 来解决,所有的数据都不会写入最终的真实的交易数据中去。当然还有其他地方都需要解决,一旦打开全链路压测开关,应该需要处理所有产生数据的地方,这是一个庞大的工程,但是也会非常有意思。
本篇文章只是我们在这块的一个初步尝试,我们会继续扩展下去,在下次产线全链路压测的时候我们就可以借助现在的实践架构扩展起来。
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。