领域驱动设计应用之WebAPI

news2025/3/1 21:14:45

领域驱动设计应用之WebAPI

此篇文章主要讲述领域驱动设计在WebApi中的应用,以及设计方式,这种设计的原理以及有点。


文章目录

  • 领域驱动设计应用之WebAPI
  • 前言
  • 一、相对于传统设计模式的有点
  • 二、WebAPI对接中的使用案例
    • 业务拆分
    • 父类设计
      • HttpResponse(返回)
      • HttpRequest(请求)
      • Client(客户端发起请求方法)
    • 业务领域设计
      • HttpResponse(返回)
      • HttpRequest(请求)
    • 客户端设计(Client)
  • 总结


前言

随着技术的不断迭代升级,设计方式也在不断迭代更新,目前比较流行的就是领域驱动设计的方式来开发程序,领域驱动设计相对于传统设计模式的有点在于:1、更好地理解业务需求。2、更好的设计质量。3、更好的团队协作。4、更好的的业务创新。


一、相对于传统设计模式的有点

领减驱动设计(Domain-DrixenDesig,简称DDD)是一种软件开发方法论,旨在解决复杂业务场景下的软件设计问
题。DDD的核心理念是将业务领域作为重点,将软件系统分解为多个子域,并通过领域模型、聚合、实体等概念来描述和
实现领域逻辑。
领域驱动设计的好处主要包括以下几点:
1.更好地理解业务需求:DDD强调将业务领域作为设计的重点,通过深入了解业务需求和领域知识,能够更好地把握业务流程和逻辑,从而更好地满足用户需求。
2.更好的设计质量:DDD通过建立领域模型、聚合和实体等概念,能够更好地划分系统结构和职责,提高系统的可维护性、可扩展性和可测试性。同时,领域模型也能够更好地反映业务实体之间的关系和行为,提高系统的质量和稳定性。
3.更好的团队协作:DDD强调跨部门、跨角色的协作,通过建立共同的领域模型和语言,能夠够更好地促进团队之间的
沟通和协作,提高团队的效率和协作能力。
4.更好的业务创新:DDD强调将业务领域作为重点,通过深入理解业务需求和领域知识,能够更好地发掘业务创新
点,提高业务竞争力。
总之,领域驱动设计可以帮助开发团队更好地理解业务需求、提高设计质量、促进团队协作和推动业务创新。

二、WebAPI对接中的使用案例

业务拆分

众所周知,对接结果的操作可分为请求(HttpRequest)和返回(HttpResponse)两个大类的操作
那么我们就根据这两个操作来进行业务拆分。
项目结构展示:
在这里插入图片描述

父类设计

HttpResponse(返回)

HttpResponse的父类 也就是项目结构中的IBaseRes的设计方式

代码如下:

 public interface IBaseRes
    {
        /// <summary>
        /// 请求码
        /// </summary>
        int Code { get; set; }
        /// <summary>
        /// 文本信息
        /// </summary>
        string Msg { get; set; }
        /// <summary>
        /// 返回时间
        /// </summary>
        string Time { get; set; }
    }
    public abstract class BaseRes : IBaseRes
    {
        public BaseRes()
        {
            this.Code = 1;
            this.Msg = "接收返回内容失败";
            this.Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        }
        /// <summary>
        /// 请求码
        /// </summary>
        [JsonProperty("code")]
        public int Code { get; set; }
        /// <summary>
        /// 文本信息
        /// </summary>
        [JsonProperty("msg")]
        public string Msg { get; set; }
        /// <summary>
        /// 返回时间
        /// </summary>
        [JsonProperty("time")]
        public string Time { get; set; }
    }
    public static class Serialize
    {
        public static string ToJson(this GoodsSyncStockReq self) => JsonConvert.SerializeObject(self, Converter.Settings);
    }

    internal static class Converter
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters =
            {
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
            },
        };
    }

这里主要是提取所有接口返回的共用字段,减少代码的冗余
以上代码可以看出,我们先设计了个接口,在接口中规定了有那些共用字段,在下方又实现了接口并使用Newtonsoft序列化工具特性规定其在返回时的字段名称。

HttpRequest(请求)

HttpRequest的父类 也就是项目结构中的BaseReq的设计方式

代码如下(示例):

  public interface IBaseReq<T> where T : IBaseRes
    {
        /// <summary>
        /// 获取接口路径
        /// </summary>
        /// <returns></returns>
        string GetApi();
    }

这里主要是限制类型必须是IBaseRes(返回基类)的派生类,并给出获取接口名称的方法

Client(客户端发起请求方法)

public interface IPingAnClient
    {
        /// <summary>
        /// 执行请求
        /// </summary>
        /// <typeparam name="T">领域对象</typeparam>
        /// <param name="request">请求数据</param>
        /// <returns></returns>
        Task<T> Send<T>(IBaseReq<T> request) where T : BaseRes;
    }

这里也是以接口的方式实现,给出了一个Send方法,该方法是用于发起Http请求的方法。

业务领域设计

以下给出的是业务领域模型设计的代码以及设计方式和原理

HttpResponse(返回)

代码如下:

 public  class GoodsListsRes :BaseRes
    {

        [JsonProperty("data")]
        public GoodsListInf Data { get; set; }
    }

以上代码可以看出这里的业务领域模型继承了(BaseRes)返回基类


有的爱思考的朋友就会提问这里为什么data会在这里呢?

这里是因为每个领域返回的data内容的结构是不样的所有,就将他下放至每个领域中各自实现。


HttpRequest(请求)

代码如下(示例):

 public class GoodsListsReq : IBaseReq<GoodsListsRes>
    {
        /// <summary>
        /// 商品类型 1销售中 2仓库中[可选]
        /// </summary>
        public int type { get; set; }
      	/// <summary>
        /// 返回接口请求路径
        /// </summary>
        public string GetApi()
        {
            return "/goods/lists";
        }
    }

以上代码可以看出这里的业务领域模型继承了(IBaseReq)请求基类


眼尖的同学相必已经发现了这里我们传了一个GoodsListsRes那这又是为什么?
这里是为了方便我们后面返回时直接反射创建对象接收返回值。


客户端设计(Client)

 public class DefaultPingAnClient : IPingAnClient
    {
        private string serverUrl;
        private string appKey;
        private string appSecret;
        public DefaultPingAnClient(string serverUrl, string appKey, string appSecret)
        {
            this.appKey = appKey;
            this.appSecret = appSecret;
            this.serverUrl = serverUrl;
        }
        /// <summary>
        /// 发起请求
        /// </summary>
        /// <typeparam name="T">领域对象</typeparam>
        /// <param name="request">请求值</param>
        /// <returns></returns>
        public async Task<T> Send<T>(IBaseReq<T> request) where T : BaseRes
        {
            return await DoSend(request);
        }
        private async Task<T> DoSend<T>(IBaseReq<T> request) where T : BaseRes
        {
            return await Response(request, request.GetApi());
        }
        #region 请求相关
        /// <summary>
        /// 签名
        /// </summary>
        /// <param name="dir">请求参数</param>
        /// <returns></returns>
        private string Sign(Dictionary<string, object> dir)
        {
            string sign = string.Empty;

            if (dir.Count() > 0)
            {
                //拼接请求参数,如果不存在不用拼接,拼接前要进行排序(a=1&b=2&c=3)
                dir = dir.OrderBy(x => x.Key).ToDictionary(f => f.Key, f => f.Value);
                sign = JsonConvert.SerializeObject(dir);
            }
            DateTimeOffset now = DateTimeOffset.Now;
            //拼接unix时间戳
            long unixTimestamp = (long)(now - new DateTime(1970, 1, 1)).TotalSeconds;
            sign += unixTimestamp;
            sign += appSecret;
            sign = Md5(sign).ToString();
            return sign.ToLower();
        }
        /// <summary>
        /// 发起请求
        /// </summary>
        /// <param name="Url">请求路径</param>
        /// <returns></returns>
        private async Task<T> Response<T>(IBaseReq<T> request, string Url, ResponseType RType = ResponseType.Post) where T : BaseRes
        {
            var rspModel = Activator.CreateInstance<T>();
            try
            {
                var url = serverUrl + Url;
                Dictionary<string, object> parames = new Dictionary<string, object>();
                if (request != null)
                {
                    parames = JsonConvert.DeserializeObject<Dictionary<string, object>>(JsonConvert.SerializeObject(request)) ?? new Dictionary<string, object>();
                }
                using (var client = new HttpClient())
                {
                    client.DefaultRequestHeaders.Add("sign", Sign(parames));
                    client.DefaultRequestHeaders.Add("timestamp", timeStamp().ToString());
                    client.DefaultRequestHeaders.Add("key", appKey);
                    client.Timeout = TimeSpan.FromSeconds(30);
                    HttpResponseMessage response = new HttpResponseMessage();
                    switch (RType)
                    {
                        case ResponseType.Post:
                            response = client.PostAsync(url, new StringContent(JsonConvert.SerializeObject(parames), Encoding.UTF8, "application/json")).Result;
                            break;
                        case ResponseType.Put:
                            response = client.PutAsync(url, new StringContent(JsonConvert.SerializeObject(parames), Encoding.UTF8, "application/json")).Result;
                            break;
                        case ResponseType.Delete:
                            response = client.DeleteAsync(url + "?" + buildParamStr(parames)).Result;
                            break;
                        case ResponseType.Get:
                            response = client.GetAsync(url + "?" + buildParamStr(parames)).Result;
                            break;
                        default:
                            rspModel.Code = 1;
                            rspModel.Msg = "请求类型错误!";
                            break;
                    }
                    if (response != null && response.IsSuccessStatusCode)
                    {
                        string responseText = response.Content.ReadAsStringAsync().Result;
                        if (!string.IsNullOrWhiteSpace(responseText))
                        {
                            if (responseText.Contains("签名"))
                            {
                                SginErroRes erroRes = JsonConvert.DeserializeObject<SginErroRes>(responseText);
                                rspModel.Msg = erroRes.Msg;
                                rspModel.Time=erroRes.Time;
                            }
                            else
                            {
                                rspModel = JsonConvert.DeserializeObject<T>(responseText);
                            }
                        }
                        else
                        {
                            rspModel.Msg = "返回内容为空!";

                        }


                    }

                };
            }
            catch (Exception ex)
            {
                rspModel.Code = 1;
                rspModel.Msg = ex.Message;
            }


            return rspModel;
        }

        /// <summary>
        /// 请求类型
        /// </summary>
        private enum ResponseType
        {
            Post,
            Put,
            Delete,
            Get,
        }
        /// <summary>
        /// 拼接请求参数
        /// </summary>
        /// <param name="param">参数字典</param>
        /// <returns></returns>
        private static String buildParamStr(Dictionary<string, object> param)
        {
            String paramStr = String.Empty;
            if (param != null)
            {
                foreach (var key in param.Keys.ToList())
                {
                    string keyparam = param[key].ToString().Replace("+", "%2B");
                    //keyparam = HttpUtility.UrlEncode(keyparam);
                    if (param.Keys.ToList().IndexOf(key) == 0)
                    {
                        paramStr += (key + "=" + keyparam);
                    }
                    else
                    {
                        paramStr += ("&" + key + "=" + keyparam);
                    }
                }
            }
            return paramStr;
        }

        /// <summary>
        /// Md5加密
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private string Md5(string str)
        {
            if (string.IsNullOrEmpty(str)) str = "";
            MD5 md5Hash = MD5.Create();
            byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(str));
            StringBuilder sBuilder = new StringBuilder();
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }
            return sBuilder.ToString();

        }
        /// <summary>
        /// 秒级时间戳
        /// </summary>
        /// <returns></returns>
        private long timeStamp()
        {
            return (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000;

        }
        #endregion
    }

这里需要着重讲解一下 Response 方法:
方法里使用到了:Activator.CreateInstance,微软文档是这样说的 传送门

其实我的理解就是利用反射创建对应对象,这里可能会返回null
还用到了:HttpClient 微软文档是这样说的 传送门


总结

在这里插入图片描述
从这里就可以体现出领域驱动设计的效果了,商品领域的请求和返回只会用到它自己领域的东西。这也很好的避免了传统设计方式会出现的代码冗余问题。也可以方便后期维护,需要修改那个领域就可以很快的找到相关的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1382274.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机三级(网络技术)一综合题(IP地址计算)

例题一 &#xff08;正常算&#xff09; 计算并填写下表 地址类别 A类地址段是1.0.0.0~127.255.255.255 1~127 B类地址段是128.0.0.0~191.255.255.255 128~191 C类地址段是192.0.0.0~223.255.255.255 192~223 所以41填A 网络地址为主机位全0 根据子网掩码&…

计算机网络安全教程(第三版)课后简答题答案大全[6-12章]

目录 第 6 章 网络后门与网络隐身 第 7 章 恶意代码分析与防治 第 8 章 操作系统安全基础 第 9 章 密码学与信息加密 第 10 章 防火墙与入侵检测 第 11 章 IP安全与Web安全 第 12 章 网络安全方案设计 链接&#xff1a;计算机网络安全教程(第三版)课后简答题答案大全[1-5…

Spark---RDD序列化

文章目录 1 什么是序列化2.RDD中的闭包检查3.Kryo 序列化框架 1 什么是序列化 序列化是指 将对象的状态信息转换为可以存储或传输的形式的过程。 在序列化期间&#xff0c;对象将其当前状态写入到临时或持久性存储区。以后&#xff0c;可以通过从存储区中读取或反序列化对象的…

web前端算法简介之链表

链表 链表 VS 数组链表类型链表基本操作 创建链表&#xff1a;插入操作&#xff1a;删除操作&#xff1a;查找操作&#xff1a;显示/打印链表&#xff1a;反转链表&#xff1a;合并两个有序链表&#xff1a;链表基本操作示例 JavaScript中&#xff0c;instanceof环形链表 判断…

重学Java 4 进制转换和位运算

天赋不好好使用的话&#xff0c;可是会被收回的哦 ——24.1.13 一、进制转换 1.常用的进制 2.十进制和二进制之间的转换 1.十进制转二进制 辗转相除法——循环除以2&#xff0c;取余数&#xff0c;除到商为0为止&#xff0c;除完后&#xff0c;由下往上&#xff0c;得出换算后…

设计模式-- 3.适配器模式

适配器模式 将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 角色和职责 请求者&#xff08;client&#xff09;&#xff1a;客户端角色,需要使用适配器的对象&#xff0c;不需要关心适配器内部的实现&#xff0c;…

爬虫—中信证券资管产品抓取

爬虫—中信证券资管产品抓取 中信证券资管产品板块网址&#xff1a;http://www.cs.ecitic.com/newsite/cpzx/jrcpxxgs/zgcp/ 页面截图如下&#xff1a; 目标&#xff1a;抓取上图中红框内的所有资产信息 按F12进入开发者工具模式&#xff0c;在Elements板块下&#xff0c;在…

学习redis有效期和数据类型

1、安装redis和连接redis 参考&#xff1a;ubuntu安装单个redis服务_ubuntu redis单机版安装-CSDN博客 连接redis&#xff1a;redis-cli.exe -h localhost -p 6379 -a 123456 2、Redis数据类型 以下操作我们在图形化界面演示。 2.1、五种常用数据类型介绍 Redis存储的是key…

大创项目推荐 深度学习疲劳检测 驾驶行为检测 - python opencv cnn

文章目录 0 前言1 课题背景2 相关技术2.1 Dlib人脸识别库2.2 疲劳检测算法2.3 YOLOV5算法 3 效果展示3.1 眨眼3.2 打哈欠3.3 使用手机检测3.4 抽烟检测3.5 喝水检测 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习加…

Sonar Qube基本使用

中文化 Sonar Qube的使用方式很多&#xff0c;Maven可以整合&#xff0c;也可以采用sonar-scanner的方式&#xff0c;再查看Sonar Qube的检测效果 Sonar-scanner实现代码检测 下载Sonar-scanner&#xff1a;https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/…

线性代数——(期末突击)概率统计习题(概率的性质、全概率公式)

目录 概率的性质 题一 全概率公式 题二 题三 概率的性质 有限可加性&#xff1a; 若有限个事件互不相容&#xff0c;则 单调性&#xff1a; 互补性&#xff1a; 加法公式&#xff1a; 可分性&#xff1a; 题一 在某城市中共发行三种报纸&#xff1a;甲、乙、丙。在这个…

[Vue]从数据库中动态加载阿里巴巴矢量图标的两种方式

记录一次在Vue中动态使用阿里巴巴矢量图标库 这是本人第一次使用阿里巴巴的矢量图标库&#xff0c;简单的导入和使用的话网上的教程很多&#xff0c;这里不多赘述&#xff0c;本人的需求是从数据库中加载出来并且显示到页面上&#xff0c;接下来简述一下如何实现。 以下代码均是…

解锁思维潜能,畅享XMind 2024 Mac/win中文版思维导图软件

XMind 2024是一款功能强大的思维导图软件&#xff0c;旨在帮助用户提高工作效率和组织思维。它的核心特点包括多平台同步、强大的协作功能和丰富的导图模板。 首先&#xff0c;XMind 2024支持多平台的无缝同步&#xff0c;用户可以在电脑、手机和平板上随时随地访问和编辑自己…

gpu显卡简介

一、目录 1.基本常用参数 2. nvidia 显卡基本了解(基本简介) 3. 显卡查看算力 4. 显卡算力、驱动版本&#xff08;Driver Version&#xff09;、CUDA Toolkit&#xff08;CUDA Version&#xff09;、PyTorch版本之间的关系 5. 显卡安装流程 6. NVIDIA显卡简介 二、实现 基本常…

Android14实战:打破音频默认重采样的限制(五十二)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只…

在 Windows 11 上通过 Autoawq 启动 Mixtral 8*7B 大语言模型

在 Windows 11 上通过 Autoawq 启动 Mixtral 8*7B 大语言模型 0. 背景1. 安装依赖2. 开发 main.py3. 运行 main.py 0. 背景 看了一些文章之后&#xff0c;今天尝试在 Windows 11 上通过 Autoawq 启动 Mixtral 8*7B 大语言模型。 1. 安装依赖 pip install torch torchvision …

nmealib库编译提示 undefined reference to `ceil‘

一、问题描述 下载了nmealib库文件&#xff0c;默认工程进行编译&#xff0c;报错&#xff0c;提示如下&#xff1a; gcc -I include -c src/generate.c -o build/nmea_gcc/generate.o gcc -I include -c src/generator.c -o build/nmea_gcc/generator.o ar rsc lib/libnm…

2024年AMC8模拟考试实测流程、注意事项和常见问题

和往年的AMC8比赛一样&#xff0c;在正式比赛的前一周左右会开放两天的模拟考试时间&#xff0c;AMC8的主办方建议所有的参赛选手重视且参加模拟考试&#xff0c;以测试设备、熟悉流程&#xff0c;避免将来正式考试不小心违规&#xff0c;或者设备不给力。 2024年的AMC8模拟考…

ChatGPT能帮助我们人类做什么

一、ChatGPT可以在多个方面帮助人类&#xff1a; 回答问题&#xff1a; ChatGPT可以回答各种问题&#xff0c;提供信息和解释概念。 创造性写作&#xff1a; 它可以生成文章、故事、诗歌等创意性文本。 学术辅助&#xff1a; ChatGPT可以辅助学术研究&#xff0c;提供解释、背…

远程开发之vacode插件Remote - SSH

远程开发之vacode插件Remote - SSH vscode插件(Remote - SSH)ssh config自定义配置跳板机ssh-agent配置(使ForwardAgent配置生效, 免密拉代码)拷贝公钥到服务器(实现免密登录服务器) 通过vscode的Remote - SSH插件, 实现远程服务器进行像本地操作一样使用远程服务器, 亦可进行像…