领域驱动设计DDD之值对象 有更新!

值对象是什么?

之前我们讲到实体,它最主要的特征在于概念上的标识。而领域中存在一些不需要进行标识的对象,它们主要是对事物的描述。如何理解这段话呢?可以通俗的理解,之前我们认为实体是数据库中的一条记录,这条记录会发生更改,例如学生的信息表。而值对象对应到数据库中的什么呢?它们不会单独地映射一张表而是可能对应表中的几个字段。比如学生信息表中拥有省、市、区、地址四个字段构成学生的住址,这四个字段构成的是一个整体用来描述学生的详细地址。

为什么我们将学生详细地址建模成值对象呢?

因为我们并不需要追踪学生详细地址的变化,我们并不关系它的连续性。


在很多DDD的博客文章中讲解到值对象都会用到了地址这一概念,可能有的人会误解地址天然就是值对象。并非如此,它有时可能作为值对象,有时可能作为实体。这需要看我们的业务场景。

值对象的情形:

  1. 当你和你的舍友在淘宝上购买商品,你们在订单上填的地址是一样的,并不需要严格区分这两个地址,快递直接送到这个地址即可。(地址映射到订单表中的几个字段上,如:省市区地址)

实体的情形:
2. 当你们宿舍的宽带坏了,需要报修,这时候你和你的舍友都打了电话报修。但是宽带人员并不会派两个人来抢修宽带。因为宽带人员知道你们是同一个地址。(宿舍地址映射到宽带公司系统中的一条地址用户表,以地址来区别用户)


阅读全文 »

枚举类的业务实践

业务场景

在进行业务开发时经常会有状态值的业务需要,例如一场考试有未开考、考试中、考试结束等状态或者一年四季有春天、夏天、秋天、冬天等状态。从前台传参到我们的业务模型再到数据库,其实这些状态是贯穿整个开发流程的。如果仅仅使用1、2、3、4来代表春夏秋冬的话,那代码的自解释能力就太差了。那如何利用好枚举类来更优雅的编码呢?

枚举类需要满足的功能

  1. 消除代码中的魔术数

  2. 根据数值获取状态的名称

  3. 根据数值获取状态

  4. 获取数值和状态名称为键值对的Map作为前端筛选项。

第一种枚举类实现

public enum SeasonEnum {

    /**/
    SPRING(1,"春天"),
    SUMMER(2,"夏天"),
    AUTUMN(3,"秋天"),
    WINTER(4,"冬天");

    public final int id;
    public final String desc;

    SeasonEnum(int id, String desc) {
        this.id = id;
        this.desc = desc;
    }


    /**
     * 根据数值获取对应的枚举
     * @param id
     * @return
     */
    public static SeasonEnum getEnumById(int id) {
        for (SeasonEnum seasonEnum : SeasonEnum.values()) {
            if (seasonEnum.id == id) {
                return seasonEnum;
            }
        }
        return null;
    }


    /**
     * 根据数值获取对应的枚举描述
     * @param id
     * @return
     */
    public static String getEnumDescById(int id) {
        final String unknown = "未知";
        for (SeasonEnum seasonEnum : SeasonEnum.values()) {
            if (seasonEnum.id == id) {
                return seasonEnum.desc;
            }
        }
        return unknown;
    }


    /**
     * 获取数值-描述键值对map
     * @return
     */
    public static Map getEnumMap() {
        Map<Object, Object> map = new HashMap<>();
        for (SeasonEnum seasonEnum : SeasonEnum.values()) {
            map.put(seasonEnum.id, seasonEnum.desc);
        }
        return map;
    }

}

阅读全文 »

Spring AOP 之Jdk动态代理剖析 有更新!

代理模式

一说到代理,很多人都会立马想到设计模型中的代理模式,通过持有被代理对象并继承被代理对象的类便可以实现代理。假设我们要给ServiceA代理日志功能,就需要声明并实现日志代理类。如果要给ServiceA代理事务功能,就又需要声明并实现事务代理类。这时如何整合日志和事务代理功能就是一个问题了。其次,假设ServiceA当中有100个方法,都需要手工加上100次日志代码。再其次,假设ServiceA、ServiceB、ServiceC都需要代理日志的话,还得针对这三个Service生成不同的代理类。
我们可以看到静态代理的局限性:

  • 难以整合不同的代理类去代理同一个对象。
  • 难以在类内部的各个方法复用代理逻辑。
  • 难以在不同的类之间复用代理逻辑。
  • 需要大量的代理类才能满足我们的庞大的业务需求。

而动态代理是如何灵活的解决这些问题的呢?

JDK动态代理

首先我们先使用动态代理最基础的用法,不涉及Spring框架的最原始方法。

先声明一个HelloService接口:

public interface HelloService {
    void hello();
}

HelloService的实现类:

public class HelloServiceImpl implements HelloService {
    @Override
    public void hello() {
        System.out.println("hello!");
    }
}

阅读全文 »

Spring AOP 之cglib动态代理剖析

Cglib动态代理

在之前的文章中我们介绍了JDK动态代理的解析,今天我们来剖析一下Cglib的动态代理解析。

Cglib代理例子

按照惯例我们先用一个简单的例子来说明

HelloService被代理类:

public class HelloService {
    public void hello() {
        System.out.println("hello!");
    }
}

事务增强类MethodInterceptor:

public class TransactionIntercepter implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("开启事务!");
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("结束事务!");
        return object;
    }
    
}
阅读全文 »

随机洗牌的算法 有更新!

今天偶然看到群里的群友说道,面试被问如何将扑克牌随机洗牌输出。笔者觉得这道题挺有意思而且挺开放性,有多种不同的实现方式。然后我就随手写了一个算法,仔细一想这个算法的优化空间挺大,于是又写出三种算法。

第一种

我们通过JDK的随机算法获取一个随机下标,再通过Set集合来判断牌是否有被抽到过,如果抽到过的话,继续进行循环,直到抽到原牌数量为止。

public class ShuffleCard1 {

    public static int[] getShuffleCards(int[] cards) {
        // 获取随机数种子
        Random rand = new Random(System.currentTimeMillis());
        // 用Set集合存储已抽过的牌
        Set<Integer> isExisted = new HashSet();
        // 声明洗牌后数组
        int[] shuffleCards = new int[cards.length];
        // 已抽到的牌数量
        int drawCount = 0;
        // 当抽到的牌数量没达到原牌数组的大小时循环
        while (drawCount < cards.length) {
            // 获取一个随机下标
            int index = rand.nextInt(cards.length);
            // 判断该下标对应的牌是否已被抽过,没有的话,抽出
            if (!isExisted.contains(cards[index])) {
                shuffleCards[drawCount++] = cards[index];
                isExisted.add(cards[index]);
            }
        }
        return shuffleCards;
    }
}
阅读全文 »

Spring AOP详解 有更新!

情景案例

小明辛苦忙了一整年终于完成了包含300个接口的业务系统项目。项目圆满上线并稳定运行了一段时间了。突然有一天总监说,对于会造成数据变化的所有接口,我们必须记录用户的操作日志。然后小明就吭哧吭哧给其中150个接口,挨个加上日志代码,累得真够呛。

过了一阵子总监又说,所有变化很少的数据全部都加上缓存,缓存涉及到刷新缓存、获取缓存、删除缓存的问题。于是乎,小明就又吭哧吭哧地给其中的100个接口加上缓存相关的代码。

又过了一阵子总监说,所有涉及充值退款费用相关的接口,需要生成发票单存入数据库。这时候小明又需要吭哧吭哧给涉及到的50个接口,挨个加上发票存储操作。

小明天天加班也没在工期内完成任务,并且原本的业务代码已经变得臃肿不堪了。

原本的代码:

/**
 * 业务方法
 */
public static void method() {

    // 业务操作
    doBusiness();

}

经过硬编码添加各种非业务性代码后的业务代码:

/**
 * 业务方法
 */
public static void method() {
    // 日志操作
    doLog();
    // 业务操作
    doBusiness();
    // 缓存操作
    doLog();
    // 发票操作
    doReceipt();

}

读者应该能明显感受到在没有AOP代理的情况下的缺点

  1. 业务代码和非业务代码混杂在一起,原本清晰的业务流程淹没在与业务不相关的代码中。
  2. 增加非业务性的功能时,都需要手工硬编码去实现,费时费力。
  3. 代码变得不好维护,一是代码耦合度高,二是需要通过硬编码的方式去拓展或者修改功能。
阅读全文 »

如何理解BIO、NIO、AIO的区别? 有更新!

很多文章在谈论到BIO、NIO、AIO的时候仅仅是抛出一堆定义,以及一些生动的例子。看似很好理解。但是并没有将最基础的本质原理显现出来,如果没有没有从IO的原理出发的话是很难理解这三者之间的区别的。所以本篇文章从Java是如何进行IO操作为开头进行分析。

Java中的IO原理

首先Java中的IO都是依赖操作系统内核进行的,我们程序中的IO读写其实调用的是操作系统内核中的read&write两大系统调用。

那内核是如何进行IO交互的呢?

  1. 网卡收到经过网线传来的网络数据,并将网络数据写到内存中。
  2. 当网卡把数据写入到内存后,网卡向cpu发出一个中断信号,操作系统便能得知有新数据到来,再通过网卡中断程序去处理数据。
  3. 将内存中的网络数据写入到对应socket的接收缓冲区中。
  4. 当接收缓冲区的数据写好之后,应用程序开始进行数据处理。

对应抽象到java的socket代码简单示例如下:

public class SocketServer {
  public static void main(String[] args) throws Exception {
    // 监听指定的端口
    int port = 8080;
    ServerSocket server = new ServerSocket(port);
    // server将一直等待连接的到来
    Socket socket = server.accept();
    // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
    InputStream inputStream = socket.getInputStream();
    byte[] bytes = new byte[1024];
    int len;
    while ((len = inputStream.read(bytes)) != -1) {
      //获取数据进行处理
      String message = new String(bytes, 0, len,"UTF-8");
    }
    // socket、server,流关闭操作,省略不表
  }
}

阅读全文 »

Mysql主从复制原理及同步延迟问题

主从复制解决的问题

  • 数据分布:通过复制将数据分布到不同地理位置
  • 负载均衡:读写分离以及将读负载到多台从库
  • 备份:可作为实时备份
  • 高可用性:利用主主复制实现高可用

复制原理

复制的原理其实很简单,仅分为以下三步:

  1. 在主库上把数据更改记录到二进制日志binary log中,具体是在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中去,Mysql会按照事务提交的顺序来记录二进制日志的。日志记录好之后,主库通知存储引擎提交事务。

  2. 从库会启动一个IO线程,该线程会连接到主库。而主库上的binlog dump线程会去读取主库本地的binlog日志文件中的更新事件。发往从库,从库接收到日志之后会将其记录到本地的中继日志relay-log当中。

  3. 从库中的SQL线程读取中继日志relay-log中的事件,将其重放到从库中。(在5.6版本之前SQL线程是单线程的,使得主从之间延迟更大)

两种复制方式

日志文件中记录的到底是什么呢?
mysql支持了两种日志格式,这两种日志格式也体现了各自的复制方式

阅读全文 »

mysql主从搭建小白教程

先准备两台Mysql数据库

  • 主数据库:192.168.0.106
  • 从数据库:192.168.0.107

如果还没创建好数据库的话可以参考我之前的mysql搭建过程。

docker安装mysql小白教程

1.在主库创建同步账户Slave

CREATE USER 'slave'@'%'  IDENTIFIED BY 'slave123456';

如果出现以下异常的话。

ERROR 3009 (HY000): Column count of mysql.user is wrong. 
Expected 45, found 43. Created with MySQL 50645, now running 50727. 
Please use mysql_upgrade to fix this error.

阅读全文 »

docker安装mysql小白教程 有更新!

如果还没有安装好docker的话可以参考我之前的docker安装教程

Docker小白安装教程

1.先搜索一下mysql镜像列表

docker search mysql

出现以下mysql相关的镜像列表,列表中有Star数、Official(是否官方)等参数,默认按Star数排列。

NAME                              DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
mysql                             MySQL is a widely used, open-source relation…   8621                [OK]                
mariadb                           MariaDB is a community-developed fork of MyS…   2997                [OK]                
mysql/mysql-server                Optimized MySQL Server Docker images. Create…   637                                     [OK]
centos/mysql-57-centos7           MySQL 5.7 SQL database server                   63                                      
centurylink/mysql                 Image containing mysql. Optimized to be link…   61                                      [OK]
mysql/mysql-cluster               Experimental MySQL Cluster Docker images. Cr…   51                                      
deitch/mysql-backup               REPLACED! Please use http://hub.docker.com/r…   41                                      [OK]
tutum/mysql                       Base docker image to run a MySQL database se…   34                                      
bitnami/mysql                     Bitnami MySQL Docker Image                      33                                      [OK]
schickling/mysql-backup-s3        Backup MySQL to S3 (supports periodic backup…   28                                      [OK]
prom/mysqld-exporter                                                              23                                      [OK]

阅读全文 »