不要强行将 Clean Architecture 和类似模式引入 GoLang 项目。
GoLang 不是 Java:
- 没有任何应用程序大小或复杂性能够证明超过三层是合理的。
- 像 Clean、Hexagonal 或任何具有 4 层以上的架构都会使 GoLang 项目变得不必要地复杂。
在代码库上工作时,你总是在过多的层之间跳转,这让人很沮丧——不必要的 DI、奇怪的抽象和除了调用带有少量日志的服务之外什么都不做的用例层。这就像看着一个怪物毫无目的地抛出异常。
在 GoLang 中,您只需要最多三层即可进行适当的 DDD 划分(应用程序、域、基础架构)。
任何超过三层的更多分层都是纯粹的过度设计!
我明白为什么这在 Java 中很常见?
- 显式接口和痛苦的重构使分层和 DI 变得有吸引力
- 但 GoLang 没有这些限制。它的隐式接口使这些模式变得多余。
这些过于复杂的架构正在将 GoLang 生态系统变成它从未想过的样子。
请让我们保持 GoLang 的简单、高效并与其核心理念保持一致。
讨论1:
我喜欢 Go 的一点是,你可以打开项目中的随机文件并期望找到其中的真实代码。这确实有用。你可以跟着做。就像 C 项目一样,好东西就在那里。
最近,我浏览了一个“OOP” Python 项目。我打开了几个文件,结果发现……什么都没有。接口、层次结构和层,以及所有可能使用的“非实际代码”技术。代码在哪里?我们试图做的“事情”是什么?要找到答案要困难得多。
讨论2:
说真的,在从事了 10 年的专业 Java + Spring 开发之后,我已经使用 Go 一年了,它非常棒:简单、没有样板、没有废话,一切都非常简单。感觉就像悟空和小林与龟仙人一起训练的那一集,他们扔掉了 50 公斤的龟壳,突然间就能跳到 100 米高的空中。
讨论3:
我已经在专业领域使用 Java 和 Spring Boot 大约 5 年了,我不喜欢它。我并不讨厌它,但我宁愿使用其他语言。对于个人项目,我一直在使用 Go。我喜欢它。我目前正在申请一份当前技术栈中包含 Go 的工作。
讨论4:
我在 Go 中也见过这种设计模式。大型 Go 公司仍然采用完整的六边形架构,到处都是接口。
讨论5:
你不应该看到代码。每个部分都应该经过单元测试并独立运行。
典型的例子是我使用一台咖啡机,它的接口是自解释的(良好的功能、参数,以及如果有必要的话,返回值名称)。
如果您需要查看代码,并且它们在同一个项目中,您仍然可以看到它们,只需导航到实现即可。否则,关键在于开发人员不需要深入挖掘就能让某些东西发挥作用。
讨论6:
我感觉许多 Java 代码库忘记了它们是咖啡机,而不是通用饮料机。有时它看起来像这样:createConsumable -> createBeverage -> createHotDrink -> createBrewable -> createCoffe 对于可能永远不会到来的未来来说,这些层次实在太多了,如果你想制作一杯冰沙,你就必须重建整个机器,所以这些通用接口没什么用。
讨论7:
todo待办事项列表案例源码:
- https://github.com/golang/pkgsite
- https://github.com/perkeep/perkeep
- https://github.com/upspin/upspin
很好的例子!对于 DDD,只需使用应用程序、域(或业务)和基础架构(或数据库,随便你怎么称呼它)。这被称为分层架构,并且非常普遍。它证明你永远不需要超过 3 层,事实上,就像 上面提供的很好的例子一样,你甚至不需要超过 1 层,需要时可以按亲和性分组。
但是如果你想使用 DDD 并拥有更多的分离,那么 3 层就足够了。分层架构多年来一直这样做,即使在没有隐式接口的语言中也是如此,比如 Go(Go 最适合重构和维护)。
在这 6 年里,我将 Go 用于金融系统和物联网,我从来没有遇到过让我希望有一个像干净架构或其他具有更多层的架构的问题。在 Go 中,我从来没有这种需要,即使是在非常大的项目上。
讨论8:
我倾向于分为 4 层:
- - 应用程序 -> 实例化所有内容,将所有内容插入在一起;通常有一个函数调用来设置我正在使用的任何输入系统的路由
- - 翻译/控制器 -> 接受输入并将其从二进制文件转换为 BL 使用的结构。这些都是样板代码,并没有告诉您代码是什么,只是充当适配器
- - 服务层 -> 这是所有业务逻辑,其中阅读函数名称可以准确地告诉您代码的作用
- - 基础层/网关层 -> 与控制器一样,它将业务数据转换为可存储/可传输的数据并调用任何 SDK。同样,与控制器一样,这只是一个适配器,以防止样板进入 BL 层。
讨论9:
尝试严格遵循《清洁代码》来学习 Go 语言是一个很好的练习,可以了解为什么它很多时候都不起作用。尝试实现主要的设计模式也是如此。它帮助我了解了 Go 语言的优势。而且,就像他们说的,编程有三个层次:不理解抽象、理解抽象和知道何时使用抽象。
讨论10:
在我看来,Upspin是使用 Go 进行原则性项目设计的一个很好的例子。
初级和中级 Go 项目过度设计的一个重要原因是缺乏对Go 软件包大小和架构的正确理解。如果你选择了错误的软件包布局,那么必然会产生非常糟糕的架构。从一个简单的软件包开始,只有在真正需要时才扩展它。精心设计的软件包架构有助于消除不必要的抽象和间接性:避免过早定义接口或使用类型别名或转发器,因为它们不是为此设计的。
讨论11:
说 Go 中不需要 DI 对我来说听起来真的很愚蠢。
DI 只是将构造函数移至最外层。不知道为什么人们讨厌它。它使测试变得容易得多。
也许他们误将 DI 视为 DI 自动化框架,但实际上这是不必要的。
讨论12:
如何看待本·约翰逊的做法?
我做的唯一不同的事情是拥有一个特定的domain包,而不是使用域/业务的“根”包。
非常好!我喜欢使用Domain,因为 DDD(我也从事金融和大多数企业解决方案),但这很完美。我的经验与此完全一致。由于隐式接口和包的工作方式,Go 已经非常适合不断发展的项目。
讨论13:
CA (Clean架构)的核心是将域代码与基础架构代码分离开来,无论你使用两层、三层、四层还是 N 层,只要域代码不依赖于基础架构代码,它就是清洁架构。你似乎还混淆了 DDD 的概念,DDD 是关于上下文分离的,根据你的业务来建模应用程序,与分层无关。
讨论14:
说到 Clean,肯定存在大量过度设计和来自其他语言的糟糕的“代码翻译”。
不过,我认为你关于分层的观点并不只针对 Go。Java 没有任何理由让 4 层比 3 层更好。3 层比 4 层更好的唯一原因是你特定的观点和工作环境。
仅有一个“应用”层意味着 HTTP/gRPC 相关的处理程序逻辑与 DDD 中所谓的“应用程序逻辑”混合在一起。如果您支持多种协议,那么这是行不通的。我的许多项目中的应用层已经足够大,无需担心这些问题,因此 4 层实际上让我的生活更轻松。
如果 4 层让你感到很麻烦,那么这可能意味着设计不适合代码库或团队的编码风格,或者只是以一种糟糕的方式实现。也许 2 层甚至没有层对你来说是最好的。但这并不意味着适用于正在开发的每个 Go 项目。
宣扬“Go 只需要 3 层”之类的教条观点正是 Clean Architecture 本身的很多内容存在的问题。如果您能以更客观、更平和的方式表达您的观点,那么您可能能够说服您的团队改进您使用的架构。
讨论15:
我现在工作的地方就遇到了这个问题。过度设计了 golang,使用了泛型,就好像他们按泛型实现付费一样。创建新服务来调用库的 4 个函数,而我们已经有 4 个调用该库的服务,并且可以轻松地将其添加到其中一个而不是新服务中。这个冲刺,我的任务是开始将 5 个服务中的 3 个从生命支持中移除并合并它们以简化我们的代码库。我可以继续说下去。这真的很糟糕。
问题是后端 Java、Python 和 Ruby 哲学渗透到了 Golang 领域。你需要从 C 的角度看待 Golang。这就是它的原型。
讨论16:
老实说,我不明白。这里很多 Go 瘾君子一直声称“Go 与众不同”,但它只是另一种编程语言,它们都是一样的。设计范式适用于您使用的任何语言进行编码。难以阅读的东西永远难以阅读,灵活的设计永远灵活,无论是 Go、C#、Java 还是其他。
讨论17:
我尊重您的观点,不同意您关于清洁架构和分层设计对于 Go 项目而言本质上“过度设计”的看法。这些模式与 Java 无关,也不纯粹为了满足不必要的抽象需求而存在。它们解决了与组织、解耦和可测试性相关的实际问题,这些问题普遍适用,甚至在 Go 中也是如此。
让我们详细分析一下:
1.“GoLang 不是 Java。像 Clean 和 Hexagonal 这样的架构让 Go 项目变得复杂。”
当然,Go 不是 Java,但 Clean Architecture 和类似模式也不是诞生于 Java。这些概念源于解决软件开发中的架构问题 — 无论使用哪种语言,这些问题都存在。
事实上,这些想法的根源可以追溯到 Robert C. Martin(Uncle Bob)提出的 C 语言。Clean Architecture 旨在将您的业务规则与框架、数据库和外部库等依赖项隔离开来。
如果您的应用规模较小,且复杂性不足以支持多层级,则无需实现所有层级。但在较大的系统中,它们可提供模块化、可测试性和易于更改等长期优势。Go 的简单性非常强大,但这并不意味着您应该完全避免解决组织挑战。
2.“过多的层级会导致令人沮丧的代码库。”
层的存在不是为了“在文件之间跳转”——它们解决了关注点分离问题。例如,拥有专用的用例层可确保您的业务逻辑独立于框架或交付机制。这种解耦使您的代码更易于测试、扩展和维护。
考虑一个应用程序,您可以在 PostgreSQL 或 DynamoDB 之间切换存储数据。如果您将业务逻辑直接与数据库调用混合,则需要为该更改重写大量代码。使用 Clean Architecture 方法,更改只需要修改基础架构层。核心域逻辑不会出现混乱。
如果您感觉自己“在层之间跳转”,这可能是您特定代码库中的设计或命名问题,而不是清洁架构中的根本缺陷。
3.“你只需要三层(应用程序、域、基础设施)。”
这一点过于规范了。清洁架构并不规定固定的层数——这是一个灵活的指导方针。如果三层适用于您的应用,那就太好了!但在某些情况下,额外的层(例如用例、适配器)有助于有效地分离职责。这取决于您的应用程序的复杂性。
例如,在微服务环境中,使用用例层可让您独立测试域逻辑,而无需依赖数据库或 API 等外部基础设施。这不是为了好玩而添加层,而是为了解决具体问题。
4.“Go 的隐式接口使得这种模式变得多余。”
Go 中的隐式接口很棒,但它们并不能神奇地解决所有组织问题。诸如 Clean Architecture 之类的模式依靠层与层之间的明确边界而蓬勃发展。这些边界明确了职责,使您的系统更易于推理。
例如,如果您有一个 PaymentProcessor 接口,您的用例层并不关心实现是否使用 Stripe、PayPal 或其他东西。它只知道合同。这种抽象不是多余的——它对于可测试性和可维护性至关重要,尤其是当您的应用程序增长时。
5.“复杂的架构正在把 Go 变成它从未想过的样子。”
这更像是对过度设计的恐惧,而不是对 Clean Architecture 的公正批评。当然,Go 强调简单性,但简单性并不意味着忽视成熟的架构实践。您可以以符合 Go 哲学的方式应用 Clean Architecture — 在解决可扩展性和解耦问题的同时保持精简。
TL;DR:清洁clean架构和类似模式并不是一刀切的解决方案,但完全忽略它们是短视的。它们是工具——在它们增加价值的地方使用它们。Go 的简单性使编写小型应用程序变得容易,但对于较大的系统,像清洁架构这样的原则可以确保您的代码库不会变得难以管理。嘿,没有人强迫你实现每一层——根据你的需求进行调整。
不要因为这些模式感觉复杂而拒绝它们,也许值得重新评估它们是否能解决你尚未遇到的问题。
讨论18:
我会将“干净clean架构”与“过度工程”区分开来。我从事一个项目,我希望人们使用简单的“干净架构”,而不是这种混乱。我从事一个项目,人们只是向前推进,几年后,它变成了一堆乱七八糟的东西,管理层无法理解为什么任务要花这么长时间。
我想说,过度设计很糟糕,但还有比过度设计更糟糕的事情。清洁clean架构很好,即使过度设计,它也比大多数人不知道如何编写持久代码时产生的框架要好得多(尽管过度设计是一种不知道如何编写持久代码的形式)。
我建议阅读《清晰clean架构》,因为这本书是关于在简洁的框架中编写适应性代码,该代码可适应变化(主要来自管理)。