用依赖倒置和控制反转,突破Golang循环调用限制之后的思考

在软件开发中,随着项目规模的扩大和业务逻辑的复杂化,重构代码变得越来越重要。本文将介绍如何在既有代码基础上,通过依赖倒置(DIP)和控制反转(IoC),实现新增加的代码可以循环引用到服务层的代码。然后,我们将探讨接口隔离、设计小而清晰的接口和包,以及共同依赖原则等内容。

包引用时的循环依赖问题

在开发服务端代码的时候,我们通常会采用单体分层设计,通常会将大量的领域代码集中在Service层,因为是同一个包,所以互相之间的引用是完全开放的。但是随着项目逐渐变得越来越复杂,无论是否采用微服务和领域驱动,我们总会需要根据领域进行拆分和重构,简化代码的复杂度。

不同于Java,共同依赖原则体现在逻辑上,而Golang的设计理念,是以包为维度组织代码的,引入依赖的的层级就是包而不是单个文件,如果代码出现了包之间物理上的循环引用,在编译阶段就会直接报错。

图片

循环依赖和共同依赖原则

我觉得Golang的这种设计,体现的是面向对象设计中的共同依赖原则,设计的包,应该职责简单清晰,有明确的责任或者功能,因此,依赖了一个包就意味着会依赖包下的所有文件。

基于这个原则,我们常常可以推出两个编程原则。

第一个是,在选择是否依赖某个由其他人或者团队维护的包的时候,要分析这个包中的代码,是否会有其他不可预期的副作用,例如golang的init,或者包内某个文件,依赖的第三方包不够整洁;如果弊大于利,不如自己重写。

第二个是,我们自己在决定某段代码、函数或者类应该放在什么路径(包)的时候,要仔细的设计,是否自己跟当前的包足够内聚,语义匹配,新代码依赖的包或者库,有没有引入破坏当前所在包承诺的代码逻辑或者副作用。

一次代码重构经历

在一次重构代码经历中,我们计划开始渐进式地逐步将大型单体项目拆分成一些微服务,并且对单体代码库的代码进行优化,逐步的将可单独治理的基础设施,根据不同的领域,从大的BFF单体服务中抽离出去。

刚接触的时候,代码是很常见的简单分层架构,分层三层,接口(控制器)层、Service(复用的业务逻辑)层、数据持久化层。接口层的话常规上是一次性的复用可能性低的代码,处理网络请求、验证和响应数据的拼装;数据持久层一般是比较薄的一层代码,是对数据库访问的映射,将业务逻辑的语义翻译成数据引擎的语法,可能会有一些缓存和实体对象的转换。

最复杂的通常会是Service层的代码,在项目的探索阶段往往领域不清晰,资源有限,最简单的方法,是把复用的业务逻辑代码放在同一个包下面,以文件名和Struct为维度,作为相关领域函数的命名空间。

要抽离代码,首先找到一个相对独立,能够内聚的领域,我们首先要把这个领域对应的核心代码,抽离到一个单独的包下面,然后需要聚合数据或者处理某个大的业务逻辑的时候,通过领域包提供的方法,对领域的业务逻辑进行调用。

我们碰到的第一个问题是,因为驱动领域边界的最主要力量,是产品和业务,为了服务治理拆分的服务,往往互相之间不可避免会出现互相依赖,尤其是在BFF的代码库中。

Service层的代码会依赖特定领域的包,而特定领域的代码,也有很多逻辑,需要依赖目前依然在Service的代码。在Golang语言层面,当然不会允许循环调用。

突破循环调用的抽象

我们想到的是依赖倒置原则,要求我们在写代码的时候,高层不依赖于底层、底层也不依赖高层,他们都依赖于抽象,由使用者自己定义或者选择符合自己要求的接口,然后由服务提供方实现接口。

新实现的单独领域代码,可以遵循很多Golang类库的最佳实践,定义一个主包,主包只定义抽象接口,然后定义一个工厂方法和单例。

// ./domain/articletype SaveAndPublishArticleParam struct {    User *types.User    Draft *ArticleDraft    Action string}type ArticleDomain interface {    GetArticleById(ctx context.Context, id string) (*ArticleEntity, error)    SaveAndPublishArticle(ctx context.Context, id string, param *SaveAndPublishArticleParam) (*ArticleEntity, error)}type UserDomain interface {    GetUserById(ctx context.Context, id string) (*types.User, error)}// ./domain/article/factoryfunc CreateArticleDomain(userRepo article.UserDomain) (article.ArticleDomain, error) {    ad = *logic.SiteArticle{        User: userRepo,    }    return ad, nil}

其中的UserDomian,就是article这个领域定义的自己要完成业务逻辑,需要用户领域能提供的能力。UserDomain接口的方法,本来就是Service层已经实现的,所以只需要将service层的user对象直接传进来,就可以实现访问。

最终达到的目标是,service层依赖的是article包定义的抽象接口,而article包接口的实现类,依赖的也是article包定义的抽象,无论article的实现类还是service层都依赖的是抽象,而不是依赖具体实现。

定义好抽象和实现类之后,还差一步,才能实现运行时的有效调用,这个时候就是控制反转登场的时候,我们需要在一个不会被service层和article领域依赖的包中,初始化Service层的对象和article领域的实现类,通过调用各自提供的绑定方法,完成抽象依赖到运行时实现对象的绑定。完成了这一步,在静态代码层面,就解除了循环依赖。​​​​​​​

// ./serverfunc StartServer(ctx context.Context) error {    ur := service.NewUserService(ctx, ....)    ...    ad, _ := articlefactory.CreateArticleDomain(ur)    ur.BindArticleDomain(ad)    ...    runHttpServer(ctx, ...)}

通过示例,我们看到了,只需要通过很简单的代码依赖注入和控制反转的方法,就可以为代码带来很大的灵活性,使代码结构变得更加有弹性。

我们编程的时候,也要透过现象看到本质,在我们重构的代码中,运行时的控制流中,我们关注的应该是函数的循环调用,只有在函数之间互现嵌套调用,才真正有可能发生死循环之类的问题。

而静态代码层面,通过限制包层面的循环引用,更大限度的避免了不可预期的死循环发生,无论静态层面是否有显式循环依赖,运行时都有可能产生循环调用。

回顾依赖倒置和控制反转

依赖倒置原则包含两条主要规则:

1. 高层模块不应该依赖于低层模块。二者都应该依赖于抽象。

2. 抽象不应该依赖于具体实现。具体实现应该依赖于抽象。

换句话说,依赖倒置原则的核心思想是通过依赖抽象(如接口或抽象类)来实现模块之间的依赖,而不是直接依赖具体实现。

通过依赖抽象,模块之间的依赖关系变得更加松散。如果具体实现发生变化,只需要修改依赖于抽象的部分,而不需要修改依赖于具体实现的模块。由于高层模块依赖于抽象,可以轻松地替换或扩展具体实现,而不影响高层模块的功能。依赖抽象使得高层模块更容易进行单元测试,因为可以方便地替换具体实现为模拟对象(Mock)。

控制反转是一个广义的设计原则,它指的是将对象的创建和管理控制权从应用程序代码中移交给一个外部容器或框架。

通过这种方式,对象不再自行控制其依赖对象的创建和管理,而是由IoC容器来完成。这种方式反转了传统的控制流,因此称为“控制反转”。

依赖对象由IoC容器注入,减少了类与类之间的直接依赖,降低了代码耦合度。可以更容易地替换依赖对象,实现模块化开发和系统的灵活扩展。

用依赖倒置和控制反转,突破Golang循环调用限制之后的思考icon-default.png?t=N7T8https://mp.weixin.qq.com/s/ZfXabzh0I8hBcKmpQMu2JQ

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初学Mybatis之动态 SQL

动态 SQL 是指根据不同的条件生成不同的 SQL 语句 动态 SQL 详情请看链接 搭建环境: mysql 建立博客表 CREATE TABLE blog(id VARCHAR(50) NOT NULL COMMENT 博客id,title VARCHAR(100) NOT NULL COMMENT 博客标题,author VARCHAR(30) NOT NULL COMMENT 博客作者…

SolidWorks 2022安装包下载(图文详细安装教程)

SolidWorks 2022提供了强大的工具和功能,旨在帮助工程师和设计师进行产品设计和工程分析。它具有直观的用户界面和用户友好的操作,使得用户可以快速上手并进行复杂的设计任务。 主要特点和功能包括: 三维建模和装配:SolidWorks 20…

电脑没有摄像头怎么用手机当摄像头?虚拟摄像头使用的详细教程来了(全)

随着科技水平以及全球化经济的快速发展,视频会议、在线课程和直播已经成为日常办公或者生活中必不可少的一个环节。然而,在如今仍有许多台式电脑和一些老旧的笔记本电脑并没有内置摄像头,亦或者自带的摄像头质量不够理想,这使得视…

《python语言程序设计》2018版第6章第19题几何问题点的位置,利用4.31显示如何测试一个点是在一条有向线的左、右还是刚好在线上

# 这个是4.31的代码,一个函数里包含了。在线上,在线左,在线右 def judgePoint(x0, y0, x1, y1, x2, y2):juMethod ((x1 - x0) * (y2 - y0)) - ((x2 - x0) * (y1 - y0))if juMethod > 0:print("p2 is on the left side of the line f…

学习笔记:MySQL数据库操作5

1. 触发器(Triggers) 触发器是数据库的一种高级功能,它允许在执行特定数据库操作(如INSERT、UPDATE、DELETE)之前或之后自动执行一段代码。 1.1 创建商品和订单表 商品表(goods) gid: 商品编号…

Web3.js 4.x版本事件监听详解:从HTTP到WebSocket的迁移

项目场景 在一个使用以太坊区块链技术的项目中,需要监听智能合约的事件,以便在事件触发时能够及时响应。项目中使用了web3.js库的4.x版本,节点使用Geth启动,并通过HTTP与节点进行通信。 问题描述 合约DataStorage.sol文件已经定…

优雅单片机之STM32C8T6------蓝牙模块基本设置(2)

0,C8T6系列 1,入门之程序的下载 2,蓝牙模块基本设置(本文) 2,蓝牙模块基本应用 3,蓝牙小车(待定) 一,蓝牙模块基础设置 需要硬件:电脑&#x…

数据驱动未来:构建下一代湖仓一体电商数据分析平台,引领实时商业智能革命

1.1 项目背景 本项目是一个创新的湖仓一体实时电商数据分析平台,旨在为电商平台提供深度的数据洞察和业务分析。技术层面,项目涵盖了从基础架构搭建到大数据技术组件的集成,采用了湖仓一体的设计理念,实现了数据仓库与数据湖的有…

NGINX项目实战

一、nginx四层代理 部署支持4层TCP/UDP代理的Nginx服务器 部署nginx服务器 编译安装必须要使用--with-stream参数开启4层代理模块。 [rootproxy ~]# rm -rf /usr/local/nginx/ #清理环境 [rootproxy nginx-1.16.1]# ./configure --with-http_ssl_module --with-stream #开…

Java 沙漏图案(Hour-glass Pattern)

给定正整数 n,以沙漏形式打印数字模式。示例: 输入:rows_no 7 输出: 1 2 3 4 5 6 7 2 3 4 5 6 7 3 4 5 6 7 4 5 6 7 5 6 7 6 7 7 6 7 5 6 7 4 5 6 7 3 4 5 6 7 2 3 4 5 6 7 1 2 3 4 5 6…

phpwamp集成环境中里在php7.1.5下安装php_redis扩展及mysql索引类型和索引方法笔记

一、phpwamp集成环境中里在php7.1.5下安装php_redis扩展 phpwamp集成环境使用起来非常方便,选择要启用的环境启动即可,可以非常方便地在apache,nginx,php各版本之间切换。启动后的界面如下图,不过在使用phpwamp的过程中&#xff0…

C# 6.定时器 timer

使用控件: 开启定时器:timer1.Start(); 关闭定时器:timer1.Stop(); 定时间时间间隔:Interval timer1.Interval 1000; Interva等于1000是每一秒刷新一次 定时器默认时间间隔是100ms 代码创建定时器 ①创建 Timer t1 new Timer(); …

dotnet-starter-kit:一个Web API+Blazor多租户、模块化、简洁DDD架构!

推荐一个Web APIBlazor多租户、模块化、简洁DDD项目框架。 01 项目简介 dotnet-starter-kit是一个基于 .NET 8 的开源项目,架构构建基于 Clean Architecture 原则的解决方案。支持多租户、模块化,一个开箱即用的项目,方便我们快速开发项目。…

lua 游戏架构 之 游戏 AI (八)ai_tbl 行为和优先级

定义一系列的AI行为类型和它们的优先级,以及一个映射表ai_tbl来关联每种AI行为类型与对应的脚本文件和优先级。以下是对代码的详细解释: lua 游戏架构 之 游戏 AI (一)ai_base-CSDN博客https://blog.csdn.net/heyuchang666/artic…

【C++题解】1069. 字符图形5-星号梯形

问题&#xff1a;1069. 字符图形5-星号梯形 类型&#xff1a;嵌套循环、图形输出 题目描述&#xff1a; 打印字符图形。 输入&#xff1a; 一个整数&#xff08; 0<n<10 &#xff09;。 输出&#xff1a; 一个字符图形。 样例&#xff1a; 输入&#xff1a; 3输…

【公式解释】《系统论》《控制论》《信息论》的共同重构:探索核心公式与深度解析

《系统论》《控制论》《信息论》的共同重构&#xff1a;探索核心公式与深度解析 关键词&#xff1a;系统论、控制论、信息论、状态空间方程、系统矩阵。 Keywords: System theory, Control theory, Information theory, State-space equations, System matrices. 核心公式与…

访问控制列表(ACL)

文章目录 ACL原理与基本配置ACL分类ACL组成ACL规则的匹配与应用 ACL原理与基本配置 ACL(Access Control List&#xff0c;访问控制列表) 读取二层、三层、四层报文信息根据预先定义好的规则对报文进行过滤和分类实现网络访问控制、防止网络攻击和提高网络带宽利用率等目的提高…

Linux(虚拟机)的介绍

Linux介绍 常见的操作系统 Windows&#xff1a;微软公司开发的一款桌面操作系统&#xff08;闭源系统&#xff09;。版本有dos&#xff0c;win98&#xff0c;win NT&#xff0c;win XP , win7, win vista. win8, win10&#xff0c;win11。服务器操作系统&#xff1a;winserve…

论文阅读【检测】:商汤 ICLR2021 | Deformable DETR

文章目录 论文地址AbstractMotivation技术细节多尺度backbone特征MSDeformAttention 小结 论文地址 Deformable DETR 推荐视频&#xff1a;bilibili Abstract DETR消除对目标检测中许多手工设计的组件的需求&#xff0c;同时表现出良好的性能。然而&#xff0c;由于Transfor…

学习笔记之JAVA篇(0724)

p 方法 方法声明格式&#xff1a; [修饰符1 修饰符2 ...] 返回值类型 方法名&#xff08;形式参数列表&#xff09;{ java语句;......; } 方法调用方式 普通方法对象.方法名&#xff08;实参列表&#xff09;静态方法类名.方法名&#xff08;实参列表&#xff09; 方法的详…