领域驱动设计应用之WebAPI

领域驱动设计应用之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 微软文档是这样说的 传送门


总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从技术走向管理

管理是可以通过后天的学习掌握的一项技能&#xff0c;但同时管理这条路每个人走的都不一样&#xff0c;因为没有一个固定的标准而且前面的路有很多未知和不确定性&#xff0c;所以不同的人对管理的理解、定义以及怎么做管理都会有不同的想法、做法。 很多一线的技术人员通常都…

一文学会服务网格与istio使用

服务网格 现代应用程序通常被设计成微服务的分布式集合&#xff0c;每个服务执行一些离散的业务功能。服务网格是专门的基础设施层&#xff0c;包含了组成这类体系结构的微服务网络。 服务网格不仅描述了这个网络&#xff0c;而且还描述了分布式应用程序组件之间的交互。所有在…

qt学习:多界面跳转+信号+槽函数

目录 概念 分类 多界面编程思路 新建界面 注意 头文件 无数据传输跳转界面 有数据传输跳转界面 对象公有接口 界面之间数据传输 信号与槽函数进行数据传输跳转界面 信号: 槽: 概念 格式1 关联信号和发送信号 格式2 通信步骤 自定义信号和槽函数 总结 实…

手写webpack的loader

一、概念 帮助webpack将不同类型的文件转换为webpack可识别的模块。 二、Loader执行顺序 分类 pre&#xff1a;前置loadernormal&#xff1a;普通loaderinline&#xff1a;内联loaderpost&#xff1a;后置loader 执行顺序 4类loader的执行顺序为per>normal>inline&…

【贪心】重构字符串

/*** 思路&#xff1a;如果s长度小于2&#xff0c;直接返回s&#xff0c;假设字符串s的长度为n。* n为偶数&#xff0c;如果字符串中的某个字符数量超过 n/2 则肯定会存在相邻的字符。* n为奇数&#xff0c;如果字符串中的某个字符的数量超过 &#xff08;n1&am…

绘图工具用的好,头发掉的少

程序员不管是在学习&#xff0c;还是工作过程中&#xff0c;很多时候都需要画图&#xff0c;如产品分析、架构设计、方案选型等&#xff0c;良好的绘图不仅可以让绘图者的思路清晰&#xff0c;也可以让聆听者更好的理解。用好画图&#xff0c;升职加薪少不了&#xff01;今天介…

大数据技术之Hudi

第1章 Hudi概述 1.1 Hudi简介 Apache Hudi&#xff08;Hadoop Upserts Delete and Incremental&#xff09;是下一代流数据湖平台。Apache Hudi将核心仓库和数据库功能直接引入数据湖。Hudi提供了表、事务、高效的upserts/delete、高级索引、流摄取服务、数据集群/压缩优化和…

PPT文档怎么转换PDF?一个方法教你快速实现

在我们的办公、学习中难免会遇到需要将ppt转pdf文件的需求。现在的网络中有各种各样的PDF转换工具&#xff0c;有些操作很复杂&#xff0c;有些需要下载软件非常麻烦。接下来&#xff0c;给大家分享一款草最简单还不用下载软件的PPT转PDF&#xff08;https://www.yasuotu.com/p…

Linux中常使用的命令之ls、cd、pwd、mkdir、rmdir

ls: 列出目录 cd&#xff1a;切换目录 pwd&#xff1a;显示目前的目录 mkdir&#xff1a;创建一个新的目录 -m &#xff1a;配置文件的权限-p &#xff1a;帮助你直接将所需要的目录(包含上一级目录)递归创建起来&#xff01; rmdir&#xff1a;删除一个空的目录 注意这…

2024年该如何招聘科技人员

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 过去几年科技领域发生了令人难以置信的动荡。我可以有把握地说&#xff0c;今天的就业市场比 2000 年代我第一次成为开发人员时更具挑战性。人工智能的繁荣与前所…

conda环境下cannot write keep file问题解决

1 问题描述 conda环境下执行如下命令报错&#xff1a; pip install githttps://github.com/wenet-e2e/wenet.git 错误信息如下&#xff1a; (pt) PS D:\code\ptcontainer> pip install githttps://github.com/wenet-e2e/wenet.git Looking in indexes: http://pypi.doub…

Qt OpenGL初探 - 画坐标轴

Qt OpenGL初探 - 画坐标轴 引言一、过程详解1.1 项目创建1.2 实现细节 二、核心代码三、官方文档3.1 官网地址3.2 官方手册的使用 引言 Qt OpenGL模块可以很方便地将OpenGL应用在Qt程序中&#xff0c;本文使用其画了一个3D坐标轴(见上图),并详细讲解了具体的编码过程与官方手册…

优化的实时换脸项目——DeepFaceLive

DeepFaceLive是一款基于人工智能技术的换脸工具&#xff0c;可以实现实时面部捕捉和换脸效果。它利用深度学习和计算机视觉算法&#xff0c;能够以惊人的准确度和速度将脸部特征无缝地映射到任何人的脸上。DeepFaceLive的特点是可以实时换脸&#xff0c;让用户通过网络摄像头应…

JVM基础(12)——G1调优

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

BitMap源码解析

文章目录 前言数据结构添加与删除操作 JDK中BitSet源码解析重要成员属性初始化添加数据清除数据获取数据size和length方法集合操作&#xff1a;与、或、异或优缺点 前言 为什么称为bitmap&#xff1f; bitmap不仅仅存储介质以及数据结构不同于hashmap&#xff0c;存储的key和v…

没啥特长又想搞钱的进:2024副业小项目推荐

利用副业赚钱&#xff0c;绝对不是找个项目就做那么简单。实际上&#xff0c;网上很多副业项目都是看着高大上&#xff0c;做起来还不如送外卖、打零工实在。思路决定出路&#xff0c;你需要的不是具体的副业项目&#xff0c;你需要的是副业思维。 思维一;经验的二次利用比如你…

【iOS】数据持久化(四)之FMDB基本使用

正如我们前面所看到的&#xff0c;原生SQLite API在使用时还是比较麻烦的&#xff0c;于是&#xff0c;开源社区就出现了一系列将SQLite API进行封装的库&#xff0c;其中FMDB的被大多数人所使用 FMDB和SQLite相比较&#xff0c;SQLite比较原始&#xff0c;操作比较复杂&#…

Unity摇杆+键鼠控制位移、旋转

1、位移 首先我们找到两张图片&#xff0c;一个大圆一个小圆&#xff0c;像这样&#xff1a; 结构是这样的&#xff1a; 然后&#xff0c;新建一个场景&#xff0c;用胶囊去做玩家&#xff0c;摄像机在胶囊下&#xff0c;并且在场景中放两个cube作为参照物 像这样搭好后&#…

【电源专题】案例:在EN脚加个电阻就能解决电源下电输出振荡?

案例背景:在某产品上使用一颗升压芯片发现下电输出波形振荡,但此产品并不是第一个使用此升压芯片的。早先此升压芯片使用在其他产品上没有报过这个异常。 分析方法:使用DEMO板,查看标准DEMO板无异常。将异常板卡上的参数与全部换到DEMO板上发现同样存在异常。 推测原因:…

学习就要从简单的开始嘛,开始学一个个人博客吧

做一个个人博客第一步该怎么做&#xff1f; 好多零基础的同学们不知道怎么迈出第一步。 那么&#xff0c;就找一个现成的模板学一学呗&#xff0c;毕竟我们是高贵的Ctrl c v 工程师。 但是这样也有个问题&#xff0c;那就是&#xff0c;那些模板都&#xff0c;太&#xff01;…