A4.领域驱动设计之领域层序列化与Jackson融合实践.adoc

前言

这是一个基于Java 21 的 六边形架构与领域驱动设计的一个通用项目,并且结合现有的最新版本技术架构实现了 领域驱动设计模式和六边形架构模式组件定义. 并且结合微服务,介绍了领域层,领域事件,资源库,分布式锁,序列化,安全认证,日志等,并提供了实现功能. 并且我会以日常发布文章和更新代码的形式来完善它.

简介

在系列文章,我们介绍了领域驱动设计中的领域层,其中关于业务的ID我们抽象出了一个基础类,例如我们定义了一个客户ID,因为没有提供get方法,会导致不能显示这个值。或者使用属性直接序列化,又不是我们想要的结构。

package com.iokays.sample.customer.domain;

import com.iokays.common.domain.jpa.AbstractId;
import jakarta.persistence.Embeddable;
import org.apache.commons.lang3.Validate;

import java.util.UUID;

@Embeddable
public class CustomerId extends AbstractId {

    private static final String UUID_REGEX = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";

    private CustomerId() {
        super();
    }

    public CustomerId(String id) {
        super(id);
    }

    public static CustomerId makeCustomerId() {
        return new CustomerId(UUID.randomUUID().toString());
    }

    /**
     * 验证是否是UUID字符串
     *
     * @param anId
     */
    @Override
    protected void validateId(String anId) {
        Validate.notNull(anId);
        Validate.isTrue(anId.matches(UUID_REGEX));
    }
}

我们需要得到的序列化的结果希望是这样的:

{
  "customerId": "f7236822-b6e4-47b4-94c4-61fd05ab7001"
}

而不是

{
  "customerId":
  {
      "id": "f7236822-b6e4-47b4-94c4-61fd05ab7001"
   }
}

本用例将介绍如何通过Jackson的序列化扩展,实现我们想要的结果。

Jackson

Jackson是一个用于处理JSON数据的Java库,有丰富的特性去自定义序列化格式,并且可以灵活[注解,全局]的使用。我们只需要了解下面的类,就可以简单的使用它。

jackson class

objectMapper

Jackson的入口类,我们通过这个类可以完成序列化与反序列化。我提供了一些测试类,测试类中已经展示了如何使用。

package com.iokays.common.domain.serialization;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
 * ObjectMapper 测试类
 */
class ObjectMapperTest {

    @Test
    void testSample() throws JsonProcessingException {
        final var objectMapper = new ObjectMapper();
        final var person = new Person("Andy", 30);

        //序列化
        final var json = objectMapper.writeValueAsString(person);
        Assertions.assertEquals("{\"name\":\"Andy\",\"age\":30}", json);

        //反序列化
        final var person2 = objectMapper.readValue(json, Person.class);
        Assertions.assertEquals(person, person2);
    }

    /**
     * ALWAYS:
     * 默认值。所有属性都会被包含在 JSON 中,无论它们的值是什么。
     * NON_NULL:
     * 只有非 null 的属性会被包含在 JSON 中。如果某个属性的值为 null,则该属性不会出现在生成的 JSON 字符串中。
     * NON_ABSENT:
     * 只有非 null 且非 Optional.empty() 的属性会被包含在 JSON 中。适用于 Java 8 的 Optional 类型。
     * NON_DEFAULT:
     * 只有非默认值的属性会被包含在 JSON 中。对于基本类型,这意味着只有非零值或非默认值的属性会被包含;对于对象类型,这意味着只有非 null 的属性会被包含。
     * NON_EMPTY:
     * 只有非 null 且非“空”的属性会被包含在 JSON 中。对于集合、数组和 Map,“空”意味着长度为 0;对于字符串,“空”意味着长度为 0 或仅包含空白字符。
     * CUSTOM:
     * 允许自定义包含逻辑。可以通过实现 JsonInclude.Value 接口来自定义包含规则
     *
     * @throws JsonProcessingException
     */
    @Test
    void testJsonInclude() throws JsonProcessingException {
        final var objectMapper = new ObjectMapper();

        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //不含值null的字段
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); //不含空字符串的字段

        final var person = new Person("", null);
        //序列化
        final var json = objectMapper.writeValueAsString(person);
        Assertions.assertEquals("{}", json);
    }

    /**
     * 测试对象
     */
    record Person(String name, Integer age) {
    }


}

自定义序列化&反序列化

Jackson提供了两种自定义序列化与反序列化的方式,一种是通过实现`JsonSerializer`和`JsonDeserializer`接口, 或者继承已经实现的子类。因为业务ID的(反)序列化,使用的是这个方法。我这边就一并介绍。

前面我们定义了一个CustomerId类,来标识客户ID, 接着我们来定义(反)序列化的方式。

package com.iokays.common.domain.serialization;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.iokays.common.domain.jpa.AbstractId;

import java.io.IOException;

/**
 * 将默认的 abstractId.id() 序列化为Id字符串
 * 当对象是 AbstractId, 将值序列化为: abstractId.id。而不是默认的嵌套结构。
 */
public class AbstractIdSerializer<T extends AbstractId> extends JsonSerializer<T> {

    @Override
    public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        // 自定义序列化逻辑
        if (null != value) {
            gen.writeString(value.id());
        }
    }
}

因为继承AbstractId的子类结构都是一样的,所以可以一个通用的序列化工具。但反序列化需要得到一个具体的子类,使用基类的方式, 很难创建到子类实例。所以这里使用了一个模板方法模式,来处理反序列化。

package com.iokays.common.domain.serialization;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.iokays.common.domain.jpa.AbstractId;

import java.io.IOException;

/**
 * 反序列化,因为需要反序列化的子类,所以将该类设置为抽象类,让子类自己实现
 *
 * @param <T>
 */
public abstract class AbstractIdDeserializer<T extends AbstractId> extends JsonDeserializer<T> {

    @Override
    public final T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        return create(jsonParser.getText());
    }

    protected abstract T create(String text);

}

所以每个子类都需要继承AbstractIdDeserializer,来实现对象的创建过程。例如我们就提供了CustomerIdDeserializer.

package com.iokays.common.domain.serialization;

/**
 * CustomerId 反序列化子类。
 */
public class CustomerIdDeserializer extends AbstractIdDeserializer<CustomerId> {

    @Override
    protected CustomerId create(String text) {
        return new CustomerId(text);
    }

}

接着我们定义一个类,含有多个属性,并且都是CustomerId类型, 来测试基于属性注解和全局配置的使用方法。

package com.iokays.common.domain.serialization;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

/**
 * 这个对象有多个 CustomerId 属性,
 */
public class ManyCustomerId {

    /**
     * 使用注解指定了 Jackson 序列化与反序列化 CustomerId
     */
    @JsonSerialize(using = AbstractIdSerializer.class)
    @JsonDeserialize(using = CustomerIdDeserializer.class)
    protected CustomerId customerId1;

    /**
     * 使用注解 仅仅指定了 Jackson 反序列化 CustomerId
     */
    @JsonDeserialize(using = CustomerIdDeserializer.class)
    protected CustomerId customerId2;

    public CustomerId getCustomerId1() {
        return customerId1;
    }

    public CustomerId getCustomerId2() {
        return customerId2;
    }

}

测试方法如下:

package com.iokays.common.domain.serialization;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.iokays.common.domain.jpa.AbstractId;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.UUID;

public class CustomerTest {

    private static final Logger log = LoggerFactory.getLogger(CustomerTest.class);

    /**
     * 使用默认注解形式,对指定的属性字段 进行测试
     *
     * @throws JsonProcessingException
     */
    @Test
    public void testAnnotation() throws JsonProcessingException {
        final var objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        final var test1 = new ManyCustomerId();
        test1.customerId1 = new CustomerId(UUID.randomUUID().toString());
        test1.customerId2 = new CustomerId(UUID.randomUUID().toString());

        final var json = objectMapper.writeValueAsString(test1);
        log.info("test1_json: {}", json);
        Assertions.assertTrue(json.contains("\"customerId2\":{}")); //customerId2为空

        final var test2 = objectMapper.readValue(json, CustomerTest.class);
        log.info("test2_json: {}", objectMapper.writeValueAsString(test2));

    }

    /**
     * 序列化、反序列化都使用全局模式, 进行测试
     *
     * @throws JsonProcessingException
     */
    @Test
    public void testSimpleModule() throws JsonProcessingException {
        final var objectMapper = new ObjectMapper();

        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        final SimpleModule simpleModule = new SimpleModule();
        //序列化
        simpleModule.addSerializer(AbstractId.class, new AbstractIdSerializer<>());

        //反序列化,需要指定为具体的实现类,不建议使用反射机制
        simpleModule.addDeserializer(CustomerId.class, new CustomerIdDeserializer());

        objectMapper.registerModule(simpleModule);

        final var test1 = new ManyCustomerId();
        log.info("test1_empty_json: {}", objectMapper.writeValueAsString(test1));

        test1.customerId1 = new CustomerId(UUID.randomUUID().toString());
        test1.customerId2 = new CustomerId(UUID.randomUUID().toString());

        final var json = objectMapper.writeValueAsString(test1);
        log.info("test1_json: {}", json);
        Assertions.assertFalse(json.contains("\"customerId2\":{}")); //customerId2不为空

        final var test2 = objectMapper.readValue(json, CustomerTest.class);
        log.info("test2_json: {}", objectMapper.writeValueAsString(test2));
    }

}

注意项

在使用objectMapper, 因为可以在运行时去修改里面的配置,建议是将公共的objectMapper封装,只提供序列化,反序列化方法。防止在运行时修改里面的配置。

未完待续…​

Jackson的序列化简单的介绍完了,下篇将会介绍分布式日志与Open Telemetry的融合实践。