文件夹结构和框架:谁在起作用?
最近我一直在思考文件夹结构,特别是我们如何组织 Web 应用程序的文件夹结构,才能既符合我们想要的设计理念,又能让其他开发者轻松浏览和理解代码库。引发我思考的是我们某个应用程序遇到的问题,稍后我会详细说明。
文件夹提供上下文信息
打开应用程序时,文件夹结构是我们首先看到的,甚至在我们查看 README 文件之前就能看到。它清晰地展现了概念的层级结构,以及它们之间的关联。杂乱无章的文件夹结构弊大于利,尤其是在需要频繁跳转文件夹的情况下。选择合适的文件夹结构至关重要,这也是为什么许多框架都预置了结构的原因——它为你提供了一个易于构建的基础。
现在有些开发者提倡把所有代码放在一个“按功能”划分的文件夹中,例如控制器、数据库访问器、存储库、配置等等……说实话,我从来没见过这种方法奏效,一旦类超过7个,代码就会变得一团糟,所以我一开始就直接忽略这种做法。
解决了这个问题之后,我们来看看导致问题的文件夹结构。
现状
/app
/Funding
/App
/Domain
/Infra
/bootstrap
/config
/database
/public
/resources
/routes
/storage
/tests
/vendor
这就是我们的 Web 应用程序的结构,正如你所看到的,它是一个相当标准的 Laravel 应用程序,唯一不同的是“app”文件夹结构的内部结构。
简单介绍一下背景,我们的代码库采用的是整洁架构/洋葱架构。我不会赘述太多细节,这里只做一个简要概述:
领域代码:系统的核心代码,用于建模您正在解决的问题。它不包含任何技术细节(例如,不涉及 SQL 或数据库概念),而是侧重于业务语言/概念。
应用:将领域对象组合成单个业务操作(例如 CreateUser),允许领域代码通过接口(例如 NotificationService 或 UserRepository)与外部系统交互。
基础架构:领域/应用概念的实现。所有技术细节和框架绑定都位于此处。在这里,您可以将领域/应用代码与数据库和/或库等技术概念连接起来。HTTP 控制器也位于此处,因为它们是需要插入领域/应用代码的技术概念。
主要原因是将系统与实现细节解耦,从而更容易进行设计、理解和测试。
问题
引发我反思文件夹结构问题的,是层级文件夹“app”。“app”是 Laravel 为你的应用程序代码创建的默认文件夹(因此得名)。然而,你会发现“app”文件夹下还有一个名为“App”的子文件夹。我们不喜欢这样,因为这意味着在层级结构中存在两个同名文件夹,尽管它们的用途不同。一个是框架定义的“app”,另一个是我们为“App”定义的接口,也就是与框架和技术细节解耦的输入输出。这可能会造成混淆。
我们讨论过更改文件夹名称,使其更清晰,因为“app”这个名字不太合适。我们反复尝试了几个名称,包括“components”、“src”,甚至“code”(我没开玩笑)。但这些都不太合适。我们意识到,更改名称会违反 Laravel 的命名规范,这会让新开发者感到困惑。
这让我开始思考,为什么我们要让框架来控制这件事?毕竟这只是一个实现细节。更重要的是,我们的业务代码现在被框架代码包裹和包含。这意味着我们的代码是系统的一个子集,尽管事实恰恰相反。这影响了我们的思维方式,并不断强化了框架掌控一切的观念(尽管它不应该掌控一切)。
我见过很多开发者在这个问题上花费大量时间,他们专注于如何将业务概念融入框架,而不是反过来。这导致所有概念都被框架所束缚,模糊了概念本身,使人们更难理解开发者的意图。
仔细想想,这种结构违反了依赖倒置原则。抽象概念(业务概念)竟然由细节(框架)控制,这必然会导致问题。
相反,我想要的是一种能够促进将我们为解决业务问题而编写的代码与我们用于完成这项工作的代码分开的结构。
综上所述,我们应该如何构建代码库呢?(你大概能猜到我接下来要说什么了)。
新结构
/contexts
/Funding
/App
/Domain
/Fund
/framework
/bootstrap
/config
/database
/resources
/routes
/storage
/public
/tests
/vendor
首先,你会注意到我们的“app”代码现在叫做“contexts”。通过命名为“contexts”,我们可以非常清楚地表明,其中的代码正在为特定的子领域解决特定问题(这里有一些关于有界上下文概念的详细信息)。随着应用程序的增长,我们会添加更多上下文。“app”这个名字太过笼统,而“contexts”则直接体现了上下文的含义(很抱歉用了这个双关语)。
其次,你会注意到框架代码现在位于其自身的文件夹结构中,独立于上下文之外。这清楚地表明,框架只是一个细节,而不是系统的控制器。它是我们上下文使用的一个组件(很像“vendor”文件夹),而不是一个对我们的上下文施加设计控制的系统。
测试代码仍然位于框架之外,因为测试代码应该与上下文耦合,而不是与框架耦合。你是在测试系统是否按预期运行,而不是测试框架是否按预期运行。这种结构鼓励这种思考方式,我相信这有助于更好的系统设计(不要将测试代码直接与框架耦合)。
Public 位于根级别,因为它通常包含许多与框架无关的资源,唯一与框架/系统相关的是启动应用程序的 index 页面中的代码,但这不足以成为将其与所有其他资源(css、js、图像等)一起打包到框架中的充分理由,它实际上是一个独立的东西。
实际上,我已经颠倒了文件夹结构的依赖关系,现在抽象(业务代码)不再包含在细节(框架)中。
结构很重要
代码库的结构方式会影响你的思考方式,而我们这种在框架代码内部构建应用程序代码的做法可能会导致问题。这种做法会迫使开发者产生一种混乱的思维模式,无论对于初级还是高级开发者来说都是如此。
我设计上述结构的目的是为了清晰地表明,上下文才是应用程序的核心,而非框架。这引导开发者专注于编写扎实的上下文代码,从领域的角度出发,而非关注实现细节。我相信这种结构有助于提升设计水平,并帮助开发者理解每个文件夹的职责和功能。简而言之,减少干扰,突出重点。
你呢?有没有什么有趣的建筑结构想要分享?欢迎在下方评论区留言或在推特上私信我。
文章来源:https://dev.to/barryosull/folder-struct-and-frameworks-what-is-exerting-control-4kpi