浅谈微服务架构设计


“架构”一个抽象而看似高端的词汇,我们难以给它一个明确的定义。人们发明了很多描述架构的概念,单体架构、SOA架构、六边形架构、洋葱圈架构、微服务架构等。这些不同概念的定义和思想相互渗透影响,没有清晰的边界。在实践中一个系统往往是参考多种思想形成的综合性产物,因此我们很难判定一个系统究竟属于哪一种架构风格。熵增理论为什么让好多人一下子就领悟了? 无意中看到这样一篇文章,似乎能给我们一些启发,软件架构的存在是为了保持系统要素之间的有序性,无论是软件系统还是现实世界广义上的系统,在没有外力介入的情况下,其熵增是不可避免的,万物总是从有序变得无序。一个房间,只要我们不去整理,随着时间的推移,它迟早会变得杂乱无章,而且这个过程发生得简单而自然,因为它符合熵增规律。而架构的本质是要描述一种规则,这种规则限定了在一个房间内,什么东西该放在哪里,当然即使有规则的存在,房间依然会变乱,但是我们会发现房间变乱的速度变慢了。因此我们可以这么说,架构可以有效地降低系统熵增的速度。我们知道将一个杂乱的房间打扫干净需要耗费巨大的心力,因为逆熵增规律而动总是艰难的。在软件世界中,重构这种行为的本质是使用外力作用于孤立系统,使之完成熵减过程。虽然重构的代价很大,但确是不可避免的,例如在一个系统不同的发展阶段,适合它的架构风格是不同的,将一个系统从单体架构演化为微服务架构必然意味着重构。当然很多情况下重构是对前期错误设计付出的代价,一个早期的微小的错误设计很可能引发蝴蝶效应,造成系统后期巨大的缺陷,当系统设计者意识到这个缺陷已经导致系统无法维护时,重构就被提上了议程,良好的架构设计就是为了减少这种不必要代价的产生。COLA 4.0:应用架构的最佳实践 这篇博客中作者对于架构的思考可以给我们一些启发,一个良好的架构必须要达到的目的是关注点分离,所谓的分离指的是业务复杂度技术复杂度的分离,例如在开发创建订单的业务时,不应该考虑分布式锁是基于 Redis 还是 Zookeeper 来实现,这是两个维度的问题,技术复杂度始终不应该侵入业务复杂度,评价一个技术框架实现是否优秀的重要指标就是侵入性,技术组件向上暴露的 API 应该是抽象简洁的,对于调用者来说无需关心其实现细节。无论是业务逻辑还是技术细节,控制其复杂度的根本能力是抽象能力,抽象就是寻找事物的共性,并针对这种共性建立通用的解决方案。业务问题往往更为多变,因此共性更加难以寻找,而不同业务面临的技术问题往往是类似的,这也是为什么软件领域内大量的开源框架总是为了解决某一类技术问题,而没有面向业务的万能良药。但是在业务的设计中,依然可以遵循某一种指导思想,例如领域驱动设计,简称 DDD(Domain Driven Design)就是这样一种思想。因此对微服务架构的探讨也应该基于这两个维度,业务逻辑和技术细节。

从业务设计层面来谈


要设计良好的微服务架构,根本在于业务逻辑的划分。因为在技术层面上,大量的开源组织和云服务厂商已经提供了较为成熟的解决方案,虽然将这些技术方案整合起来也需要对微服务技术体系有一定程度的认知,但整体来讲由于技术细节本身具有良好的抽象性,容易在行业内达成一致共识,所以构建微服务技术层面的基础设施反而不是风险最大的。而业务的划分则是仁者见仁,智者见智,难以形成统一的标准。但是无论业务本身有多大的差异性,在设计过程中我们依然要遵循一些基本原则,也可以利用一些设计思想尽可能降低业务复杂度。

怎么理解 “微”

微服务中的 “微” 告诉我们服务的粒度应该尽可能小,这有利于服务功能具备高度的复用性。那么所谓的服务大小是基于什么维度去判断呢?是代码量还是服务所需的硬件资源?事实上,微服务划分最重要的依据就是业务职责的独立性和单一性,我们知道设计模式中有一个原则是单一责任原则,软件系统的一切设计都是为了达到高内聚,低耦合。一个服务承担某一类业务模块的功能,意味着该模块的逻辑可以内聚在服务内部,具有独立的数据库和数据结构,甚至可以采用不同的持久化方案,对外只需提供尽可能简洁的 API,避免复杂度外露。追求服务粒度小并不意味着业务请求的调用链会被拉得很长,如果业务边界划分得当,服务间交互应该不是那么频繁的,而且在微服务架构中,应该尽可能使用异步或响应式的编程模型,避免服务消费者强依赖于服务提供者。

基于 DDD(领域驱动设计)的分层架构

DDD 告诉我们一个系统应该划分为展现层,应用层,领域层以及基础设施层,业务复杂度集中于领域层,而技术复杂度集中于基础设施层,实现关注点分离。

分层架构

  • 展现层:又被称为 User Interface 用户接口层,在前后端分离的架构下该层一般由前端或移动端实现,通过调用应用层 API 获取 VO(View Object)并将业务数据以某种形式展现给用户或另一个计算机系统。

  • 应用层:需要从粗粒度上定义业务 API,但应用层本身应该是很薄的一层,尽量不承担业务逻辑,不包含业务规则及业务知识,仅仅作为业务任务的编排器,类似 JavaCompletableFuture 对异步任务的编排,协调多个领域聚合共同完成一项业务任务。对于系统中的查询类操作,应用层相当于一个 API 组合器,往往需要聚合多个领域模型中的数据交付给展现层,此时针对领域层的多个查询操作大部分是可以并行的,因此在应用层中非常适合实践异步或响应式编程模型。一般来讲应用层的数据对象被定义为 DTO(Data Transfer Object),与展现层通过 VO 交互,与领域层通过 DO(Domain Object)交互,因此往往需要大量的 Converter 类型转换器。

  • 领域层:所谓的“领域驱动设计”,领域层是整个系统的核心层,负责表达大部分的业务概念,是业务复杂度的集中地,并且系统的设计应该是从建立领域模型开始的,同时在微服务架构中,领域模型应该作为服务划分的重要依据。

  • 基础设施层:作为技术复杂度的集中地,为应用层和领域层提供技术能力,例如持久化方案,缓存机制,消息 API,分布式事务支持等。需要注意的是基础设施层应该遵循依赖导致原则,避免暴露内部的实现细节,对外应该提供抽象的 API,避免业务代码直接依赖于外部技术组件,以免在未来切换技术实现方案时造成巨大的升级成本。

依赖倒置与防腐层

在谈论设计模式时离不开一个重要的原则“依赖导致原则(Dependency Inversion)”,对于一个软件模块或组件而言,无论本身是具体还是抽象,都应该尽可能依赖于抽象,因此当我们需要在两个具体模块之间建立依赖关系时,应该建立一个抽象层将依赖关系隔开,在微服务架构中也把这种抽象层称为防腐层。

服务注册发现

为什么在 Java 开发过程中需要 SpringBeanFactory 这样的 IOC 容器?为什么在微服务架构中需要服务注册中心?因为无论是 Java Bean 之间的强依赖,还是 microservices 之间的强依赖,最终的结果是大幅增加系统的复杂度,造成熵增。因此我们需要一个中心化的容器,维护服务提供者的信息,并向服务消费者提供这样的信息,避免两者直接交互。从 Java Bean 的角度来看,BeanFactory 就是这样的容器,从微服务的角度来看,服务注册中心就是这样的容器。

抽象层

我们在前文说到优秀架构的目标是关注点分离,从基于 DDD 的分层架构来看,技术复杂度被集中于基础设施层,那就意味着基础设施层必然需要依然大量的第三方依赖,技术中间件等。但是这些技术细节不应该暴露到业务层面上,例如系统原本依赖于 RabbitMQ 实现分布式消息机制,未来由于某种原因需要切换为 RocketMQ 实现,此时如果业务代码直接依赖于 RabbitMQ 的消息 API,那么牵一发而动全身,切换工作量是较大的。因此较好的实现策略是在业务代码与技术实现之间建立抽象层,从设计模式的角度来讲比较符合 Adapter Pattern(适配器模式)Facade Pattern(门面模式) 的思想。适配器模式用于适配不同的技术方案,门面模式用于向上层暴露抽象 API。所谓的“防腐层”就是防止外部依赖逐渐腐化系统内部的代码,因为外部依赖随着版本的升级,其代码改动是不可控的,需要抽象层与之解耦。

事件驱动 - 业务解耦的利器

分布式 BASE 理论认为要构建一个具备弹性的分布式系统,需要具备三个要素:

  • Basically Available(基本可用)

  • Soft-state(软状态)

  • Eventually Consistent(最终一致性)

由于分布式系统中网络环境的复杂性,我们无法利用单体服务基于关系型数据库 ACID 事务的特性达成数据强一致性。而如果使用分布式事务的方案追求数据的强一致性,那么意味着系统中所有的调用都必须是同步的,想象一下如果在一个长调用链中,任何一个服务的崩溃都会导致该业务流程不可用,此时系统的可用性肯定是不高的,因此我们需要在系统的可用性和一致性之间取得一种平衡。BASE 理论建议我们追求数据的最终一致性,所以我们需要在服务间建立一种异步通信机制,事件驱动就是这样一种思想。

在系统中充满了这样的场景,当行为 A 发生时需要触发行为 B,此时如果行为 A 必须关注行为 B 执行的结果,那么我们不得不采取同步的方式在行为 A 的代码中调用行为 B,如果两者位于不同的服务中,那么我们会采用 RPC 调用的方式,此时的代码具有侵入性,行为 B 的代码侵入了行为 A,意味着修改 B 模块就必然影响 A 模块。而事实上系统的大部分行为在执行时无需相互关注执行结果,我们应该尽可能采用异步编程的模型去实现。在行为 A 执行完成后,我们可以发布一个特定的 Event 事件,B 模块注册为该事件的监听器,监听到事件发生后,触发行为 B 即可。随着系统业务的发展,会有新的模块需要关注该事件,只需注册新的监听器即可,这些行为对于模块 A 以及模块 B 都是不可见的,就达到了关注点分离的目的。事件监听机制是观察者模式的一种延伸,多个 Observer 观察者关注同个主题,观察者之间是不可见的。

从技术细节层面来谈


十年再出发,Dubbo 3.0 Preview 即将在 3 月发布 随着 Dubbo 新版本的发布,阿里官方在这篇文章中探讨了 DubboSpring CloudgRPC 之间的关系。在几年之前,当一家企业决定实践微服务架构时,时常会面临技术选型的困惑,是应该选择以 Dubbo 为核心阿里开源的一系列技术组件作为方案还是应该拥抱 Spring Cloud体系?事实上站在今天的角度来看,这个问题已经没有多大意义,因为这两者不是互斥的,完全可以组合使用。Dubbo 已经成为 Spring Cloud Alibaba 中的 RPC 调用实现方案,而 Spring Cloud Alibaba 本身由作为 Spring Cloud 抽象标准的一种实现。Spring Cloud 2020.0.0 正式发布,对开发者来说意味着什么? 随着近些年 Spring Cloud 体系的快速发展,Spring Cloud 已经成为 Java 领域内微服务框架的既定标准,Spring 官方团队结合大量业界经验逐渐抽象出一套微服务通用架构模式标准。Spring 的主要目的在于建立标准,达成共识,而各类开源或商业组织会建立自己的设配方案,作为 Spring Cloud 的标准的一种实现。开发者完全可以根据需求灵活组合,选取最符合应用场景的实现方案。

Spring Cloud 如何建立抽象

我们知道早期的 Spring Cloud 框架一直把 Netflix 套件作为默认实现,两者几乎可以划上等号,EurekaRibbonHystrixZuul 这些 Netflix 的开源组件共用组成了早期的 Spring Cloud 框架。而在 Spring 的规划中,Netflix 套件是要被逐渐废弃的,因为 Spring 需要建立更高级别的抽象来制定业界规范,终于在官方最新发布的 Spring Cloud 2020 版本中,除了 Eureka 服务注册中心以外,其余的 Netflix 组件依赖已经被移除,同时官方提供了替代方案。

根据 Spring Cloud 描绘的微服务架构图我们可以知道一个由微服务组成的系统总要解决几类技术问题,事实上在 Spring Cloud Commons 模块中已经抽象出了这些概念:

  • Service registration and discovery(服务注册与发现)

  • Distributed/versioned configuration(分布式并基于版本的外部化配置)

  • Routing(网关路由)

  • Service-to-service calls(服务间 RPC 调用)

  • Load balancing(负载均衡)

  • Circuit Breakers(服务熔断与降级)

  • Global locks(全局分布式锁)

  • Leadership election and cluster state(主节点选举算法以及分布式状态同步)

  • Distributed messaging(分布式消息)

服务注册与发现

我们可以看下 Spring 官方对于 Service discovery 的描述:

service-discovery

在微服务架构中,服务消费者(Consumer)不能总是知道服务提供者(Provider)的位置(主要指 IP 和端口号),因此我们需要一些中间件来帮助我们维护服务提供者的位置列表,并且基于服务弹性伸缩的需求,这个列表需要根据情况的变化动态更新。这样的中间件有很多,例如 EurekaConsulZookeeperNacos 等,而我们在前文提到过业务程序本身不应该直接依赖于技术中间件,因此在 Spring Cloud Commons 模块中为我们提供了一个抽象接口 DiscovertClient 用以描述该服务具有服务发现的能力,并且我们可以集成 Spring Cloud Load Balancer 模块是服务发现客户端具备客户端负载均衡的能力,从服务提供者列表中选取最恰当的服务实例。

public interface DiscoveryClient extends Ordered {

	int DEFAULT_ORDER = 0;

	String description();

	List<ServiceInstance> getInstances(String serviceId);

	List<String> getServices();

	@Override
	default int getOrder() {
		return DEFAULT_ORDER;
	}

}

DiscoveryClient 是对于服务发现客户端的抽象,通过它我们可以 getServices 获取所有 Provider 列表,同时根据某个 ProviderserivceId 获取其实例列表。任何一个服务想要获得服务发现的能力,都应该在其 Spring 容器中注册 DiscoveryClient 的实例,当然其具体实现取决于我们的技术选型。

public interface ServiceRegistry<R extends Registration> {

	void register(R registration);

	void deregister(R registration);

	void close();

	void setStatus(R registration, String status);

	<T> T getStatus(R registration);

}

服务发现的前提是服务注册,系统中的服务提供者都应该是一个服务注册客户端,而 ServiceRegistry 就是对这一种能力的抽象,Registration 是对注册信息的抽象描述。一般来讲微服务架构中的大部分服务即是服务消费者,同时也是服务提供者,所以大部分情况下一个服务应该同时实现 DiscoveryClientServiceRegistry,同时具备服务发现和服务注册的能力。

public abstract class AbstractAutoServiceRegistration<R extends Registration>
		implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> {

	private final ServiceRegistry<R> serviceRegistry;

	private AtomicInteger port = new AtomicInteger(0);

	private AtomicBoolean running = new AtomicBoolean(false);

	private ApplicationContext context;

	@Override
	@SuppressWarnings("deprecation")
	public void onApplicationEvent(WebServerInitializedEvent event) {
		bind(event);
	}

	@Deprecated
	public void bind(WebServerInitializedEvent event) {
		ApplicationContext context = event.getApplicationContext();
		if (context instanceof ConfigurableWebServerApplicationContext) {
			if ("management".equals(((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
				return;
			}
		}
		this.port.compareAndSet(0, event.getWebServer().getPort());
		this.start();
	}

	public void start() {
		if (!isEnabled()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Discovery Lifecycle disabled. Not starting");
			}
			return;
		}

		// only initialize if nonSecurePort is greater than 0 and it isn't already running
		// because of containerPortInitializer below
		if (!this.running.get()) {
			this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
			register();
			if (shouldRegisterManagement()) {
				registerManagement();
			}
			this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));
			this.running.compareAndSet(false, true);
		}

	}

	protected void registerManagement() {
		R registration = getManagementRegistration();
		if (registration != null) {
			this.serviceRegistry.register(registration);
		}
	}
	
}

前文我们提到过微服务架构应该具备服务自动弹性伸缩的机制,当一个新的服务实例被启动时,服务注册的行为应该是被自动触发。从 AbstractAutoServiceRegistration 我们可以看到 Spring Cloud 已经提供了这样的抽象模板。当 WebServer 启动时,WebServerInitializedEvent 事件被发布,基于 Spring 的事件驱动机制,AbstractAutoServiceRegistration 作为该事件的监听器,bind 方法被调用,触发服务注册的相关逻辑。AtomicBoolean 作为锁确保注册行为只会发生一次,而具体的注册行为由 ServiceRegistry 的实现类决定,Spring Cloud 作为标准的制定者,抽象的定义者,不会强制捆绑某一种实现,具体的实现逻辑取决于我们集成的外部依赖。

Spring Cloud LoadBalancer

作为 Netflix Ribbon 的替代方案,Spring Cloud LoadBalancer 是一个非常新的模块,它一度只是 Spring Cloud 孵化器里的一个小项目,并且一度搁浅。后再经过重启,发展,现行使其伟大使命,正式用于完全替换 Ribbon,成为 Spring Cloud 负载均衡器唯一实现。

public interface LoadBalancerClient extends ServiceInstanceChooser {

	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

	<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

	URI reconstructURI(ServiceInstance instance, URI original);

}

LoadBalancerClient 是其核心接口,描述了基于正向代理的客户端负载均衡的能力,它是 ServiceInstanceChooser 的扩展,ServiceInstanceChooser 代表服务实例的选择器,显然负载均衡客户端应该具备选择服务实例的能力,因此这个接口层级设计是非常合理的。在目前的版本中 BlockingLoadBalancerClientLoadBalancerClient 的唯一实现。

负载均衡客户端

负载均衡客户端的根本目的是要从某服务提供者的实例列表中选取最恰当的 ServiceInatance完成 RPC 调用,这个过程对于我们在业务层面上使用 RPC 客户端来说应该是无感知的,因此 Spring Cloud 需要提供不同的适配器将 LoadBalancerClient 集成到不同的客户端技术中,例如将 LoadBalancerClient 封装为请求拦截器集成到 RestTemplate 这样的 Rest 客户端中。

Spring Cloud Circuit Breaker

在微服务架构中由于服务被拆分为较细的粒度,请求调用链被拉长,系统的整体可用性会被降低。我们需要引入 Circuit Breaker 断路器这样的概念,通过在短时间内牺牲系统一部分非核心功能来保证核心功能的可用性。因为当一个请求在某一环节上发生异常时,异常未必会被及时抛出,在同步的调用的情况下,意味着此时请求线程被阻塞等待结果返回,而线程属于服务端宝贵的资源,这种情况必然导致系统硬件利用率的降低。Circuit Breaker 的作用是在服务满足一定条件(例如响应时间或异常比例达到一定阈值)的情况下,将针对该服务的所有请求熔断,或返回兜底结果,降低该服务的异常对整个系统的影响,直到该服务的性能指标恢复正常后熔断状态解除。

public interface CircuitBreaker {

	default <T> T run(Supplier<T> toRun) {
		return run(toRun, throwable -> {
			throw new NoFallbackAvailableException("No fallback available.", throwable);
		});
	};

	<T> T run(Supplier<T> toRun, Function<Throwable, T> fallback);

}

Spring Cloud Circuit Breaker 针对断路器概念建立了抽象,通过 run 方法可以将某业务请求委托给断路器执行,toRun 参数泛指一切行为请求行为,fallback 代表请求发生异常时的降低操作,我们可以指定一个兜底返回。

断路器抽象

Spring Cloud Circuit Breaker 利用抽象工厂模式将断路器的生产过程抽象化,这有利于适配不同的断路器实现,当前 Spring Cloud 支持四种不同的实现方案,分别是 Netfix HystrixResilience4JSentinelSpring Retry,开发者可以根据自身的需求选用。

@Slf4j
@RestController
@RequestMapping("user")
public class UserController {

	@Resource
    private CircuitBreakerFactory<?, ?> circuitBreakerFactory;

	@GetMapping("getUserInfo")
    public UserInfo getUserInfo(@RequestParam("id") long id) {
        return circuitBreakerFactory.create("getUserInfo").run(() -> userClient.getUserInfo(id), throwable -> {
            log.error("getUserInfo timeout!", throwable);
            return new UserInfo();
        });
    }

}

在实现层面上,我们只需依赖注入 CircuitBreakerFactory 类型的实例即可,无法感知到底层的具体实现,这符合针对技术组件建立防腐层的要求。

Spring Cloud Gateway

前文我们提到 Netflix Zuul 组件已经被 Spring 官方废弃,即使在旧版本中也不再推荐使用。同时 Spring Cloud 推出了全新的替代方案 Spring Cloud Gateway,这是 Spring Cloud 项目中一个非常具有代表性的模块,为什么这么说呢? 因为 Spring 长期以来都希望推广基于响应式编程模型的技术栈,在 Spring Framework 5.0 之后的版本中引入了 Project Reactor 响应式编程库,提供了完善的响应式编程 API,并且推出了 Spring Webflux 作为 Spring MVC 的替代方案,Spring 认为异步非阻塞式的编程模型会成为未来的主流。在 Spring Cloud 2020 版本这一倾向变得更为明显,Spring Cloud Gateway 正是基于 Reactor 技术栈实现的,这体现了 Spring 强推 Reactive 异步体系的决心。

Spring Cloud Gateway 的本质是通过一系列的全局过滤器针对系统外部外部请求做统一过滤处理,例如用户身份认证,用户权限校验,限流等操作,避免在业务服务中产生重复代码,破坏服务的单一责任性。我曾经在 微服务网关 Spring Cloud Gateway 之限流 这篇文章中较为详细地论述过 Spring Cloud Gateway 对限流功能的支持,有兴趣可以阅读。

Spring Cloud Stream

Spring Cloud 体系中,我个人会把 Spring Cloud Steam 模块放在一个极为重要的位置。因为在微服务架构中,如果我们遵循 BASE 理论,那么构建一个事件驱动风格的异步模型尤为重要。而在服务间实现异步通信的常规实现就是依赖于某一消息中间件,例如 Apache KafkaRabbitMQRocketMQAzure Event Hub 等云服务或开源技术。但是我们会发现这些消息中间件的实现细节各不相同,虽然它们大多实现了发布/订阅模型,生产者组,消费者组,事务性消息等概念,但是前文我们提到过,让业务代码直接依赖于消息 API 不是一个良好的选择。我们希望的是在业务层面上将消息结构抽象化,编写业务代码时无需关注诸如消息序列化,消息负载均衡,推/拉模型等技术细节。Spring Cloud Stream 通过建立三种抽象模型为我们提供了这样的可能:

  • Destination Binders:绑定器,负责集成外部消息中间件。

  • Destination Bindings:绑定协议,作为应用代码和消息中间件之间的媒介,屏蔽消息 API 的复杂度,统一消息生产者消息者客户端代码的风格。

  • Message:抽象消息模型,被消息生产者和消费者所引用,分别与 Binder 交互,Message 作为业务消息的包装,将消息抽象为消息头和消息体,统一消息风格。

Spring Cloud Stream

消息生产者示例代码:

@Slf4j
@RestController
@RequestMapping("user")
public class UserController {

	@Resource
    private Source source;

	@PostMapping("addUserInfoAsync")
    public void addUserInfoAsync(@RequestBody UserInfo userInfo) {
        source.output().send(MessageBuilder.withPayload(userInfo).setHeader(RocketMQHeaders.TAGS, "add-user-tag").build());
    }
	
}

消息消费者示例代码:

@Slf4j
@RestController
@RequestMapping("user")
public class UserController implements UserClient {

	@StreamListener(value = Processor.INPUT)
    public void subscribe(Message<UserInfo> message) {
        UserInfo userInfo = message.getPayload();
        log.info("consume message {}", JSON.toJSONString(userInfo));
        addUserInfo(userInfo);
    }

}

Spring Cloud Stream 通过在消息中间件和应用服务之间建立抽象层,使系统具备更高的扩展性,对于消息生产者来说,只需关注消息的目的地,对于消息消费者来说,只需关注消息的来源,而无需关注底层消息中间件的技术概念。甚至在特殊情况下,生产者与消费者可以完全使用不同的分布式消息技术,通过不同的 Binder 层适配依然可以把它们组合到一起。总而言之,合理地建立抽象是整个 Spring 生态家族的根本指导思想,也是 Spring 建立业界技术标准的根本途径。


文章作者: Ethan Zhang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Ethan Zhang !
评论
 上一篇
微服务网关与安全 微服务网关与安全
API Gateway 网关服务作为微服务架构中系统外部与内部的网络屏障,安全策略的应用是必不可少的。我们知道在微服务架构设计中有一个重要原则,即服务的单一责任性,尤其是对于某一业务服务而言,理想状态下我们希望其仅承担某一项业务职责。而系
2021-03-31 Ethan Zhang
下一篇 
Spring 事务抽象带来的思考 Spring 事务抽象带来的思考
一般来说,在一个复杂系统中,我们会集成很多的 data access frameworks,也就是所谓的数据持久层框架,例如通过 Hibernate,MyBatis 操作 Mysql 这样的关系型数据库,亦或是通过 Jedis,Redis
2020-12-31 Ethan Zhang