【从0到1设计一个网关】自研网关的架构搭建

效果演示链接

项目骨架搭建

这里我使用的IDE工具是IDEA。 从上文中我们了解到,我们的项目大概有五个模块,Client,Common,Register Center,Config Center,Core这五个模块。 下面开始具体骨架的搭建,使用一个父工厂来统筹管理其他子工程,方式如下。 子工厂的初始化写法如下。 之后我们可以开始在我们的Core模块中开始搭建我们的启动类了。 到此位置我们的项目骨架就已经搭建完毕了。

领域模型与DDD

领域模型(Domain Model)是领域驱动设计(Domain-Driven Design,DDD)中的一个核心概念,它是用于表示和描述特定领域的抽象模型。领域模型是一种结构化的、面向对象的编程模型,用于捕捉和反映实际业务领域中的概念、规则和关系。

领域模型的优点是提高对业务领域的理解、增加模块化、强化业务规则,缺点是可能增加复杂性和开发时间,作用是实现业务需求和确保软件系统与业务一致。

领域模型在软件开发中的作用包括:

业务概念的抽象: 领域模型帮助将业务领域中的各种概念和实体抽象化为编程语言中的对象、类和关系。这有助于开发团队更好地理解业务需求。

业务规则的表示: 领域模型可以包含业务规则,这些规则可以在模型中以方法、属性或约束的形式表示。这有助于确保应用程序遵循业务规则。

领域对象的定义: 领域模型包含领域对象,如实体、值对象、聚合根等,这些对象具有业务语义,并在模型中与其他对象互动。

业务流程的建模: 领域模型可以用于表示业务流程、状态转换和工作流程。这有助于开发人员更好地理解和建模业务流程。

问题领域和解决方案的分离: 领域模型帮助将问题领域(业务领域)与解决方案领域(软件开发领域)分离开,使开发人员更专注于解决业务问题。

可维护性和可扩展性: 领域模型的抽象化和清晰性使得代码更易于维护和扩展,因为它反映了业务领域的结构和规则。

简而言之,领域模型的作用就是为了针对某一个具体的业务设计的,而为了这个业务,我们会找出许多和这个业务有关的一些特性,而如果换一个业务场景,可能当前的领域模型就不合适了,就得继续设计一个新的。但是对于当前的这个场景,即使后续有不断的需求进来,领域模型由于在之前的设计过程中就是非常适配这个业务场景的,所以可以确保更好的可用性。

而领域模型的设计思路大概就是:

  • 寻找对应的领域对象
  • 建立对应的实体
  • 为实体设定对应的链接关系

那么按照上面的流程,我们知道,对于我们的网关项目,服务加载配置启动后,网关服务端接收前端请求后,对请求进行解析后,组成内部参数在网关内部流转,比如串行化经过一组过滤器过滤后,转发到后端服务,后端服务处理请求后,结果网关返回客户端。 服务加载配置启动后,网关服务端接收前端请求后,对请求进行解析后,组成内部参数在网关内部流转,比如串行化经过一组过滤器过滤后,转发到后端服务,后端服务处理请求后,结果网关返回客户端。 所以我们就可以得到如下领域对象: Context、Request、Response、Config、Processor、Filter、FilterChain、Rule、HTTP请求对象。

得到领域对象之后,我们就可以开始设定对应的实体对象了。 比如我们的Request中要有一些请求的时间,参数,id编号等, Response中要有返回值,状态码等信息。 而一个Context上下文,就是一个完整的请求对象的处理过程。

所以我们大概也就得出了实体之间的连接关系,也就是: Context包含Request和Response。

核心上下文模型封装

对于核心上下文Context的封装,大概涉及 到如下几个步骤:

  • Context上下文核心定义
  • BaseContext基础上下文实现
  • 上下文参数
  • GatewayRequest实现
  • GatewayResponse实现
  • Rule、FilterConfig实现
  • 最终GatewayContext实现

我们先定义一个抽象类,定义一些核心的功能动作。 设计完毕之后代码大致如下: 在这里我就不贴出具体的代码实现了。 但是我会在这里简单的介绍一下为什么需要这些类。 我们首先从IContext开始,这个是网关上下文的顶级接口类,定义了一些方法,这个类的作用主要是为了限定当前网关请求的一些基本的操作。 比如当前网关请求是否正常执行完毕,或者是否有异常,请求是否长连接,请求是否有回调函数等等。 之后就是对Context内部的Request和Response进行实现了。 也就是网关请求和网关响应信息的实现。 网关请求信息包含网关的全球开始和结束时间,请求id,请求路径,请求主机,路径参数,请求体参数,Cookie,以及经过网关处理之后将要转发到的后端服务的信息。 而网关响应信息包括,响应内容,响应状态码,异步响应对象。 之后,我们还需要设定一个规则,进行过滤。并且规则有序。

静态配置的加载

之后我们就需要开始编写对我们的网关进行配置加载一些类和方法了。 下面是配置类详情,简单的提供了一些配置类的信息。

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class GatewayConfig {
    /**
     * 项目端口
     */
    private int port = 8080;

    /**
     * 项目名称
     */
    private String applicationName = "blossom-gateway";

    /**
     * 注册中心地址
     */
    private String registryAddress = "localhost:8848";

    /**
     * 项目开发环境
     */
    private String env = "dev";


    //netty config

    /**
     * boss线程数量
     */
    private int eventLoopGroupBossNum = 1;

    /**
     * 工作线程数量
     */
    private int eventLoopGroupWorkerNum = Runtime.getRuntime().availableProcessors();

    /**
     * 报文最大长度
     */
    private int maxContentLength = 64 * 1024 * 1024;

    /**
     * 单双异步 默认单异步
     * false:单异步
     * true:双异步
     */
    private boolean oddEvenAsync = false;
}

而下面则提供了配置的加载方式,分别从配置文件,环境变量,JVM参数,运行时参数进行配置信息的加载。

java 复制代码
package blossom.gateway.core.config;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.util.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

/**
 * @author: ZhangBlossom
 * @date: 2023/10/23 21:11
 * @contact: QQ:4602197553
 * @contact: WX:qczjhczs0114
 * @blog: https://blog.csdn.net/Zhangsama1
 * @github: https://github.com/ZhangBlossom
 * ConfigLoader类
 */
public class ConfigLoader {
    public static final String CONFIG_FILE = "gateway.properties";
    public static final String ENV_PREFIX = "GATEWAY_";
    public static final String JVM_PREFIX = "gateway.";

    private static final ConfigLoader INSTANCE = new ConfigLoader();

    private GatewayConfig config;

    private ConfigLoader() {

    }

    public static ConfigLoader getInstance() {
        return INSTANCE;
    }

    public static GatewayConfig getConfig() {
        return INSTANCE.config;
    }

    /**
     * 加载配置方法 优先级高的覆盖优先级低的
     * 运行参数>jvm参数>环境变量>配置文件>配置对象默认值
     *
     * @param args
     * @return
     */
    public GatewayConfig load(String[] args) {
        //配置对象默认值
        config = new GatewayConfig();
        //配置文件 gateway.properties
        loadFromConfigFile();
        //环境变量  port=2222
        loadFromEnv();
        //jvm参数  -Dport=1234
        loadFromJvm();
        //运行参数 --port=1234
        loadFromRuntimeArgs(args);
        return config;
    }

    /**
     * 从运行时参数加载配置
     */
    private void loadFromRuntimeArgs(String[] args) {
        if (ArrayUtils.isNotEmpty(args)) {
            Properties properties = new Properties();
            for (String arg : args) {
                if (arg.startsWith("--") && arg.contains("=")) {
                    properties.put(arg.substring(2, arg.indexOf("=")), arg.substring(arg.indexOf("=") + 1));
                }
            }
            BeanUtil.copyProperties(properties, config);
        }
    }

    /**
     * 从JVM参数中加载配置
     */
    private void loadFromJvm() {
        Properties properties = System.getProperties();
        BeanUtil.copyProperties(properties, config);
    }

    /**
     * 从环境变量加载配置
     */
    private void loadFromEnv() {
        Map<String, String> env = System.getenv();
        Properties properties = new Properties();
        properties.putAll(env);
        BeanUtil.copyProperties(properties, config);
    }

    /**
     * 从配置文件进行加载配置
     */
    private void loadFromConfigFile() {
        InputStream stream = ConfigLoader.class.getClassLoader().getResourceAsStream(CONFIG_FILE);
        if (Objects.nonNull(stream)) {
            Properties properties = new Properties();
            try {
                properties.load(stream);
                BeanUtil.copyProperties(properties, config);
            } catch (Exception e) {
                e.printStackTrace();
                //log.error("load config file{} error!!!", CONFIG_FILE, e);
            } finally {
                try {
                    stream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

组件生命周期

生命周期这一块,就比较简单了,定义一个接口,提供初始化,启动,以及关闭等方法即可。

java 复制代码
public interface LifeCycle {

    /**
     * 初始化
     */
    void init();

    /**
     * 启动
     */
    void start();


    /**
     * 关闭
     */
    void shutdown();


}
相关推荐
王二端茶倒水12 分钟前
大龄程序员兼职跑外卖第五周之亲身感悟
前端·后端·程序员
夜色呦1 小时前
现代电商解决方案:Spring Boot框架实践
数据库·spring boot·后端
爱敲代码的小冰1 小时前
spring boot 请求
java·spring boot·后端
java小吕布3 小时前
Java中的排序算法:探索与比较
java·后端·算法·排序算法
Goboy3 小时前
工欲善其事,必先利其器;小白入门Hadoop必备过程
后端·程序员
李少兄3 小时前
解决 Spring Boot 中 `Ambiguous mapping. Cannot map ‘xxxController‘ method` 错误
java·spring boot·后端
代码小鑫4 小时前
A031-基于SpringBoot的健身房管理系统设计与实现
java·开发语言·数据库·spring boot·后端
Json____4 小时前
学法减分交管12123模拟练习小程序源码前端和后端和搭建教程
前端·后端·学习·小程序·uni-app·学法减分·驾考题库
monkey_meng4 小时前
【Rust类型驱动开发 Type Driven Development】
开发语言·后端·rust
落落落sss4 小时前
MQ集群
java·服务器·开发语言·后端·elasticsearch·adb·ruby