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

news2024/9/9 1:29:17

在软件开发中,随着项目规模的扩大和业务逻辑的复杂化,重构代码变得越来越重要。本文将介绍如何在既有代码基础上,通过依赖倒置(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.coloradmin.cn/o/1959230.html

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

相关文章

UI设计经验心得:优化设计流程与实战技巧分享

随着互联网的快速发展,UI 设计在中国也逐渐发展起来。UI 设计的目的不仅仅是让用户享受视觉享受,而是解决用户如何与互联网设备互动。因此,UI 设计是通过深入研究用户的使用习惯和操作逻辑来设计界面的互动和视觉效果。那么,UI 设…

邮箱API在CRM系统中如何高效的应用与集成?

邮箱API的高级功能和使用指南?怎么安全集成邮箱API? CRM系统已成为企业与客户保持联系的关键工具。通过集成邮箱API,企业可以大幅提升CRM系统的功能和效率。AokSend将探讨邮箱API在CRM系统中的高效应用与集成。 邮箱API:主要功能…

龙迅LT8642UXE 矩阵HDMI *4转HDMI *2输出切换芯片,支持HDMI 2.0,可带HDCP

LT8642UXE描述: LT8642UXE HDMI2.0/1.4交换机具有4:2的开关,符合HDMI2.0/1.4规格,最大6Gbps高速数据速率,自适应均衡RX输入和预先强调的TX输出,以支持长电缆应用程序。LT8642UXE HDMI2.0/1.4交换机自动检测…

JVM—运行时数据区域

Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。 1、程序计数器—线程私有 字节码解释器工作时通过改变这个计数器的值,选取下一条执行的字节码指令。程序计数器是程序控制的指示器,分支、循环、跳转、异常处理、线…

适合证券公司的跨网传输解决方案,了解一下

证券公司由于其业务特性,涉及大量的敏感财务数据和交易信息,因此通常会在内部实施网络隔离措施。目的是为了保护数据免受未授权访问和网络攻击,确保数据的安全性和保密性,因此急需寻找安全可靠的跨网传输解决方案,实现…

拆解了数百个独立站,我们总结的高转化页面布局精华

高转化的独立站的页面布局都具备什么要素呢? 我们研究了几百个独立站,结合我们自己的项目经验,总结出转化率相对高的网站布局,都有以下注意的要点清单: 1 品牌Logo基本放在头部左边或者中间 2 首屏大banner 3 社交媒…

【可能是全网最丝滑的LangChain教程】十九、LangChain进阶之Agents

幸福,不是长生不老,不是大鱼大肉,不是权倾朝野。幸福是每一个微小的生活愿望达成。当你想吃的时候有得吃,想被爱的时候有人来爱你。 01 Agent介绍 在LangChain中,Agent 是一个核心概念,它代表了一种能够利…

CTF Web信息搜集 25000字详解

目录 前言信息收集常见信息分类域名信息whois备案CDN子域名解析记录 旁站C段服务器信息端口服务器类型数据库类型waf防火墙 网站信息备份文件备份文件常见的后缀名备份文件常见的文件名gedit备份文件vim备份文件收集方法 敏感目录CMS类型(指纹识别)探针泄…

牛客JS题(十二)列表动态渲染

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; 忍者码风reduce注意事项 题干&#xff1a; 我的答案 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"></head><body><ul></ul><…

决策树 和 集成学习、随机森林

决策树是非参数学习算法&#xff0c;可以解决分类问题&#xff0c;天然可以解决多分类问题&#xff08;不同于逻辑回归或者SVM&#xff0c;需要通过OVR&#xff0c;OVO的方法&#xff09;&#xff0c;也可以解决回归问题&#xff0c;甚至是多输出任务&#xff0c;并且决策树有非…

浅谈监听器之后端监听器

浅谈监听器之后端监听器 “后端监听器”&#xff08;Backend Listener&#xff09;是一种高级功能&#xff0c;用于异步地将测试结果数据发送至外部系统&#xff0c;如数据库、消息队列或时间序列数据库等&#xff0c;以便于长期存储、实时分析和可视化展示。 后端监听器的作…

python 可视化探索(二):高级图表与组合图表

总结&#xff1a;本文为和鲸python 可视化探索训练营资料整理而来&#xff0c;加入了自己的理解&#xff08;by GPT4o&#xff09; 原作者&#xff1a;作者&#xff1a;大话数据分析&#xff0c;知乎、公众号【大话数据分析】主理人&#xff0c;5年数据分析经验&#xff0c;前…

centos安装crictl

上章文章已经讲诉了如何安装centos安装containerd-CSDN博客&#xff0c;本文章讲解如何安装crictl 一、官网安装说明文档&#xff0c;官网 二、二进制安装 #!/bin/sh VERSION"v1.30.0" # check latest version in /releases page wget https://github.com/kubernet…

IDEA 本地有jar包依赖文件,但是所有引用的jar包全部爆红

前端时间 看源码&#xff0c;下载源码额按钮不见了&#xff0c;折腾了很久&#xff0c;遂打算重新安装idea&#xff0c;但是重新安装后&#xff0c;发现代码全都爆红&#xff0c;按照晚上说的删除idea 文件夹&#xff0c;idea缓存删除&#xff0c;都不好使&#xff0c;但是看到…

PMP冲刺题及知识点整理

PMP题目整理 冲刺题1错题整理冲刺题1相关重点记录&#xff1a;零散不熟悉知识点整理团队章程责任分配矩阵RAM定义是啥风险识别的工具都有啥 冲刺题2错题整理知识点整理情商风险登记在风险登记册&#xff0c;风险管理计划中没有风险团队章程项目经理来确保进行知识转移交付哪一个…

【C++】STL-红黑树封装出set和map

目录 1、实现红黑树的泛型 2、set和map的插入 3、set和map的迭代器 3.1 operator 3.2 operator-- 3.3 const迭代器 4、find 5、map的operator[] 6、完整代码 6.1 红黑树 6.2 set 6.3 map 1、实现红黑树的泛型 我们先引入上一节中写的红黑树 enum Colour {RED,BL…

Unity Shader 初学者指南

《Unity Shader 初学者指南》(3D Game Shaders For Beginners) 是一个面向初学者的教程项目&#xff0c;由David Lettier创建。该项目通过一系列分步指导&#xff0c;教授如何在3D游戏中实现各种着色技术&#xff0c;包括环境光遮蔽(SSAO)、景深(Depth of Field)、光照、法线贴…

产品经理-​简历内容的可准备点(24)

在互联网岗位中,产品经理是一个没有针对性的专业&#xff0c;知识结构不明确&#xff0c;那该具体准备哪些内容呢&#xff1f; 大家知道大公司实习含金量高&#xff0c;但作为纯产品“小白”&#xff0c;冷启动找实习很困难&#xff08;本科生不太好找产品实习&#xff0c;普通…

OSI七层模型详解

OSI七层模型 OSI&#xff08;Open System Interconnect&#xff09;&#xff0c;即开放式系统互连。 一般都叫OSI参考模型&#xff0c;是ISO组织在1985年研究的网络互连模型。该体系结构标准定义了网络互连的七层框架&#xff08;物理层、数据链路层、网络层、传输层、会话层、…

loguru日志模块:简化Python自动化测试的日志管理!

引言 日志是软件开发中的关键组成部分&#xff0c;为开发和测试人员提供了调试和监控应用程序的重要手段。loguru 是一个第三方的 Python 日志库&#xff0c;以其简洁的 API 和自动化的功能脱颖而出。本文将探讨为什么项目中需要日志&#xff0c;loguru 为何受到青睐&#xff…