Sayi
Sayi
Hi there! Do you have a plans to support regex validator? ``` @Parameter(names = "-v", description = "version:1.0 or 2.0", regex = "(2\\.0|1\\.0)") ``` if you are interested in this...
 ## 前言 随着年底考核的结束,团队也走了不少人,我没有做过多的挽留,因为我无法在他们年轻的时候给予他们更多的价值和成长,祝他们更好。接下来陆陆续续地面试了几个新人,我非常小心翼翼,我唯一能告诉他们的是我们的产品梦和技术梦,在90后组成的团队里面,我不希望琐碎的管理与他们的技术梦相冲撞,极力反对没有实践意义的日常工作,最近这三个月,因为莫名奇妙管理方式的改变,团队的创新似乎丢失了,同样也在这三个月,我努力忘记自己的情绪,带领团队回到正轨,领域驱动设计在这样的背景下应运而生了。 这篇文章假设你已经初步了解过领域驱动设计(DDD)的基本概念(聚合根、实体、值对象、领域服务、领域事件、资源库、限界上下文等)以及CQRS的设计,本文会将重点放在如何落地DDD和CQRS上。 ## DDD分层架构 Evans在它的《领域驱动设计:软件核心复杂性应对之道》书中推荐采用分层架构去实现领域驱动设计:  其实这种分层架构我们早已驾轻就熟,MVC模式就是我们所熟知的一种分层架构,我们尽可能去设计每一层,使其保持高度内聚性,让它们只对下层进行依赖,体现了高内聚低耦合的思想。 分层架构的落地就简单明了了,用户界面层我们可以理解成web层的Controller,应用层和业务无关,它负责协调领域层进行工作,领域层是领域驱动设计的业务核心,包含领域模型和领域服务,领域层的重点放在如何表达领域模型上,无需考虑显示和存储问题,基础实施层是最底层,提供基础的接口和实现,领域层和应用服务层通过基础实施层提供的接口实现类如持久化、发送消息等功能。阿里巴巴开源的整洁面向对向分层架构COLA就采取了这样的分层架构来实现领域驱动。 ## 改进DDD分层架构和DIP依赖倒置原则 DDD分层架构是一种可落地的架构,但是我们依然可以进行改进,Vernon在它的《实现领域驱动设计》一书中提到了采用依赖倒置原则改进的方案。 > 所谓的依赖倒置原则指的是:高层模块不应该依赖于低层模块,两者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象。  从图中可以看到,基础实施层位于其他所有层的上方,接口定义在其它层,基础实施实现这些接口。依赖原则的定义在DDD设计中可以改述为:领域层等其他层不应该依赖于基础实施层,两者都应该依赖于抽象,具体落地的时候,这些抽象的接口定义放在了领域层等下方层中。这也就是意味着一个重要的落地指导原则: **所有依赖基础实施实现的抽象接口,都应该定义在领域层或应用层中**。 采用依赖倒置原则改进DDD分层架构除了上面说的DIP的好处外,还有什么好处吗?其实这种分层结构更加地高内聚低耦合。每一层只依赖于抽象,因为具体的实现在基础实施层,无需关心。只要抽象不变,就无需改动那一层,实现如果需要改变,只需要修改基础实施层就可以了。 采用依赖倒置原则的代码落地中,资源库Repository的抽象接口定义就会放在领域层了,下文会再阐述如何落地Repository。 ## 六边形架构、洋葱架构、整洁架构 《实现领域驱动设计》一书中提到了DDD架构更深层次的变化,Vernon放弃了分层架构,采用了对称性架构:六边形架构,作者认为这是一种具有持久生命力的架构。当你真正理解这种架构的时候,相信你也不得不佩服这种角度不同的设计。  如上图,在这种架构风格中,外部客户和内部系统的交互都会通过端口和适配器完成转换,这些外部客户之间是平等的,比如用户web界面和数据库持久化,当你需要一个新的外部客户时,只需要增加相应的适配器,比如当我们增加外部一个RPC的服务时,只需要编写对应的适配器即可。 好吧,当将web界面和持久化统称在一起,没有前端和数据库后端之分的时候,这种观察架构的角度已经打动到了我。 那么适配器在各种外部客户的场景下时什么呢?如果外部客户时HTTP请求,那么SpringMVC的注解和Controller构成了适配器,如果外部客户时MQ消息,那么适配器就是MQConsumer监听器,如果外部客户时数据库,那么适配器可能就是Mybatis的Mapper。 随着架构的演化,后来又提出了洋葱架构和整洁架构,这些架构大同小异,它们都只允许外层依赖内层,不允许内层知道外层的细节,下图是整洁架构图,详细介绍这里就不作赘述,可以参考这篇文章:[The Clean...
 Apache Dubbo™ 是一款高性能Java RPC框架,这是一句来自官方的介绍。关于RPC框架的底层原理可以参见以前我写过的一篇文章:[《写一个极简的RPC》](https://github.com/Sayi/sayi.github.com/issues/26)。 关于dubbo的知识点官方文档已经讲得非常透彻了,做的好的做的不好的文档都有提及,本文将从源码层面理解dubbo的几个设计核心点。 ## 基本格式:无处不在的URL URL作为dubbo配置信息的统一格式,这是整个框架设计的基本原则,无论是注册中心,还是提供者、消费者都以URL形式存在。 ``` dubbo://172.18.35.230:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&compiler=jdk&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=91615®ister=true&release=&side=provider×tamp=1568021066716 ``` 上面这段URL就是一个典型的服务提供者,通过协议(dubbo)、服务地址(172.18.35.230:20880)和若干参数表达了Provider的信息。 ``` zookeeper://127.0.0.1:7181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-api-provider&compiler=jdk&dubbo=2.0.2&interface=org.apache.dubbo.registry.RegistryService&pid=91615×tamp=1568021061661 ``` 上面这个URL是注册中心。 ## 核心机制:Service Provider Interface dubbo是一个采取了Microkernel + Plugin模式的框架,提供了很多组件的扩展点,做到这种模式我们就需要设计一套加载插件的方式,这个首要的机制就是SPI。 ## 扩展点 @SPI dubbo的SPI借鉴了JDK的SPI,但又有些许不同: 1. 按需加载扩展特性...
> GitHub项目地址:[https://github.com/Sayi/swagger-dubbo](https://github.com/Sayi/swagger-dubbo) > > Dubbo是一种透明化的RPC调用方案和服务治理方案,对外暴露服务接口Provider。Swagger构建了符合Open Api规范的API文档,通过SwaggerUI提供了模拟HTTP请求的工具。 本文将探讨的是Dubbo服务接口文档化,以及如何通过HTTP请求访问服务接口,便于应用在单机接口测试、服务快速验证、扩展服务方式等场景。 #### Dubbo服务接口和Http请求映射 服务接口与HTTP的映射,涉及到请求URL、HTTP方法、参数。 ##### 请求URL 一个请求URL需要唯一确定一个接口和一个方法,我们先看看一个接口的定义: ```java package com.deepoove.swagger.dubbo.example.api.service; import java.util.List; import com.deepoove.swagger.dubbo.example.api.pojo.User; public interface UserService { List query(String phone); List query(int areaCode);...
 ## 前言 事物具有一系列状态,并且随着状态的变更某些行为会有差异,这样的代码中就会充斥中状态的if判断,解耦这些不同状态下的行为将是一件非常重要的设计。 我们试想下如何解决这种问题,首先想到的是**策略模式**:每种状态可以对应一个策略,策略中包含着对应状态的行为,然后我们还可以考虑采用**命令模式**,具体的行为放到命令处理器中执行,本文将探讨与状态这个概念紧密相连的设计模式:**状态模式**,利用它来编写更优雅的代码。 ## 状态模式-State Design Pattern 状态模式是一种行为模式,它将状态和行为解耦,当一个类的状态改变时,它的行为也随之改变。  从类图中可以看到,状态模式是多态特性和面向接口的完美体现,`State`是一个接口,表示状态的抽象,`ConcreteStateA`和`ConcreteStateB`是具体的状态实现类,表示两种状态的行为,`Context`的`request()`方法将会根据状态的变更从而调用不同`State`接口实现类的具体行为方法,具体用代码实现状态模式时,可以参考这篇文章:[https://github.com/iluwatar/java-design-patterns/tree/master/state](https://github.com/iluwatar/java-design-patterns/tree/master/state)。 有一个很关键的点就是`State`的实现类到底需不需要依赖`Context`?典型状态模式的代码实现中是依赖了`Context`,目的是在每个状态的行为内部,调用`Context`的状态变更方法。个人认为状态实现类可以不依赖`Context`,我们可以将状态改变的触发动作从状态实现类解耦出去,状态机(StateMachine)通过一种类似事件的机制实现这种解耦。 ## 有限状态机-Finite State Machine  有限状态机是一种数学计算模型,在任何时刻,状态机都处于有限状态中的某一个状态,它可以响应输入事件,从而执行一个动作或者转换到另一个状态,这个过程称为过渡(transition),我们先来认识几个重要的术语: ### 1. State 状态 有限状态机的状态是一个固定的集合,状态的变迁可以通过状态迁移图来表示,它可以拥有一个开始状态和终止状态,也可以设置其初始化的状态。 当一个事件满足时,就有可能会触发状态的迁移。 ### 2. Event 事件 事件是用来触发状态的迁移或者执行一个动作而不迁移状态。...
如何度量一段代码的性能,换种实现方式会有更佳的性能表现吗?你或许想知道fastjson是否正如它自己所说的那样至今性能未遇对手?Fork/Join框架真的有提高性能吗? 一句话:**Measure, Don’t Guess!** JMH(Java Microbenchmark Harness)是由OpenJDK Developer提供的基准测试工具(基准可以理解为比较的基础,我们将这一次性能测试结果作为基准结果,下一次的测试结果将与基准数据进行比较),它是一种常用的性能测试工具,解决了基准测试中常见的一些问题,本文将针对这些问题介绍如何正确的使用JMH,以及可视化测试结果。 > 可视化JMH Visual chart GitHub地址:[https://github.com/Sayi/jmh-visual-chart](https://github.com/Sayi/jmh-visual-chart) ## 字符串拼接性能比较 我们通过基准测试来比较使用"+"号和使用Stringbuilder进行字符串拼接的性能。 ### 1. 创建基准测试项目 我们可以在一个已有项目中运行基准测试,但是为了获得更加准确的度量结果,官方推荐使用Maven archetype来创建独立的JMH项目: ```bash mvn archetype:generate \ -DinteractiveMode=false \ -DarchetypeGroupId=org.openjdk.jmh \ -DarchetypeArtifactId=jmh-java-benchmark-archetype...
前面已经讨论过通用池设计、线程池的设计,本文探讨池化机制在数据库连接复用上的应用:数据库连接池。 JDBC API是一个高度抽象的API,每个厂商自己有着自己的实现。大多数针对JDBC层的框架都是对JDBC API的包装,包装成一些高级功能,比如可以对DataSource和Connection进行包装支持连接池功能,可以对Statement进行包装,支持多数据源的分库分表功能。关于JDBC可以参考我之前的文章[ JDBC和DBUtils的架构设计](https://github.com/Sayi/sayi.github.com/issues/63)。 > 开发者接触的数据库连接池配置大多数都是在配置数据源,因为数据库连接池通常是在DataSource抽象基础上实现的。 ## DataSource 我们先来看看数据源`javax.sql.DataSource`接口的定义: ```java public interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; Connection getConnection(String username, String password) throws SQLException; }...
本系列文章探讨对象池化技术。 ## 池化机制的好处 说到池我们可以联想到很多概念,比如线程池、数据库连接池、常量池等等。在多线程设计中,可以通过池化机制复用对象以达到提高性能的目的,池的核心优势是对 **对象的复用**,这样就免去了“对象重复创建”的时间,尤其当创建对象本身是一个耗时的事情,比如IO操作。 拿我们最常见的线程池为例,线程这个对象是可以被复用的,程序要执行的是任务,这些任务可以交给复用的线程来处理,而线程的创建恰恰又是一个比较耗时的操作,我们通过线程对象的池化技术达到复用线程的目的。 > 与多线程+池的设计还有一种类似的设计:单线程+IO多路复用,主要用来解决IO密集型的问题。 ## 实现一个池要考虑哪些因素? 设计一个池的核心要素无非分成两个方面:池子+对象。 **池子** 要考虑大小,池子过大可能会占用过多的系统性能,池子过小可能由于无法获取对象而阻塞线程。当我们很确定自己需要多大的池来执行,可以使用fixed大小的池子,我们也可以设计一个对象个数动态变化的池子:池子有一个最大值maxTotal和最小值minIdle,最大值是对象个数的上限,当池子一段时间没有使用后,就去回收超过最小值个数的对象,这样在系统繁忙时,就可以充分复用对象,在系统空闲时,又可以释放不必要的对象。 **对象** 必须是可以被复用的,且创建对象应该是耗时的才值得被复用。可以被复用是一个特别重要的点,当你的对象做了一些不可复用的操作后,必须在放回池中的时候重置这些设置,或者说从池子中取出对象时,都要重新进行初始化。 ## 池的配置 本节我们来看看池子的一些通用配置,可能命名和你见过的一些池子的配置名称不一样,但是它们的含义是一致的。 ### maxTotal、maxIdle、minIdle 我们先来看看关于池大小的几个配置。 | 配置 | 作用 | | --- | ---...
JDBC(Java Database Connectivity)定义了一套访问和操作数据源(通常是数据库)的接口和抽象,本文将对JDBC的架构进行探讨,然后通过DBUtils学习如何封装一个好用的JDBC框架,阅读完本文后,当你再使用Mybatis等持久层框架时,想必或多或少有种感觉:"它其实是一个认识很久的老朋友"。 ## JDBC 架构  上图是一个典型的JDBC三层模型,在客户端和数据库中间加入中间层,在中间层中将业务逻辑与JDBC驱动分开,通过一个抽象层掩盖不同厂商JDBC驱动的区别,并且提供诸如连接池、事务管理和分库分表等功能,这个模型相对于应用直连数据库驱动增加了更好的扩展性。 JDBC驱动是每个数据库厂商针对JDBC API的实现,我们可以通过一套JDBC API无缝隙切换不同厂商的数据源,这些API在java.sql和javax.sql包下。 ### 经典JDBC代码 我们先来看看经典的通过JDBC连接数据库的代码,类路径下包含最新的mysql jdbc驱动: ```gradle compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6' ``` app_config是数据库中一张表,拥有name和value两列数据,我们遍历并且打印表中所有数据,具体代码如下: ```java String url = "jdbc:mysql://127.0.0.1:3306/sayi?useUnicode=true&characterEncoding=UTF-8";...
本文将探讨类加载器是如何寻找且加载Class文件,它的应用场景以及我们如何自定义自己的类加载器。 ## 类加载器ClassLoader 类加载器是将字节码文件加载到JVM,它具有 **延迟加载** 的特性,需要某个类时才会去加载。如果 **同一份字节码文件** 被不同类加载器加载后的Class对象,它们是不相等的:即它们不是同一个类。 回到《JVM(三)类和对象的生命周期》文章开头写的代码,打印出了每个类型的加载器,主要分为三种类型:Bootstrap classes、Extension classes和User classes。 ```java Apple.class: sun.misc.Launcher$AppClassLoader@2a139a55 class [Lcom.deepoove.java8.def.Apple;: sun.misc.Launcher$AppClassLoader@2a139a55 Gson.class classloader: sun.misc.Launcher$AppClassLoader@2a139a55 sun.misc.Launcher$ExtClassLoader@4aa298b7 String.class: null ``` ### Bootstrap Class Loader Bootstrap启动类加载器负责加载实现了Java平台的类,包括JDK的lib目录、rt.jar等。启动类的路径是由`sun.boot.class.path`属性决定的,这个属性只能被引用无法被修改。...