12 结构:如何系统设计框架的整体目录?

news2024/9/24 5:27:27

到现在,我们已经将 Gin 集成到框架 hade 中,同时又引入了服务容器和服务提供者,明确框架的核心思想是面向服务编程,一切皆服务,所有服务都是基于协议。后续也会以服务的形式,封装一个个的服务,让我们的框架越来越丰富。

框架和业务区分

业务代码的目录结构是一种工程化的规范。所谓工程化,简单来说就是希望不管是谁,在一个工程项目中,都按照一种做法来完成某个事情。而目录结构,就是项目工程化的一个起点。

在一个公司或者一个部门中,如果有架构团队,基本上要做的第一个事情就是,规范公司或者部门的代码目录结构。整体目录结构不仅仅代表着分层、归纳,也包含着很多架构的思想。

而对于 hade 框架而言,因为目标是将框架应用于实际生产,工程化使用,所以对业务的目录结构,制定最小化的工程化规范,并且提供默认的整体目录结构是很有必要的。

不过请注意,这里是希望制定最小化的工程化规范,就是说,对于这个框架,规范某些最小化的功能性目录是必要的,如果没有这些功能性目录,我们会认为目录设计是不合理的。

如何设计

在制定具体的目录结构之前,我们要明确一点:业务的目录结构也是一个服务,是一个应用目录服务。在这个服务中,我们制定框架要求的最小化的工程化规范,即框架要求业务至少有哪些目录结构。

而在其他服务中,一旦需要用到某个目录,我们能从目录结构服务中,查找出对应的结构。比如后续要创建配置服务,它需要去某个配置目录文件中读取配置,而去哪个配置目录呢,只需要去服务容器中获取这个应用目录服务就能知道了。
(就是说,目录的设置也是一个服务,也要注入到框架中)

所以按照面向接口编程的思想,先为“应用目录服务”定义一下服务接口协议,也就是应用要提供哪些接口。

在今后的章节中,我们会定义许多框架级别的基础服务,这些服务我们都存放在框架目录中。服务的接口协议,统一放在框架目录下的 framework/contract 中,而对应的服务提供者和服务实现,统一存放在框架目录下的 framework/provider 中

这里将所有框架级别的接口协议放在 framework/contract 中的设计有两个好处:
一是框架协议的关键字,我希望使用 contract.xxx 这个语义来区分,比如 App 服务的接口为 contract.App、日志服务的接口为 contract.Log,它们的 namespace 都是 contract,这样在使用的时候记忆成本会比较低。另外将框架提供的所有接口协议都放在一个文件夹中,在阅读框架提供哪些服务的时候,也更清晰明了。

以“应用目录服务”为例,我们在 framework/contract 目录中创建 app.go 存放服务协议。在 framework/contract/app.go 中,我们定义应用目录服务接口名称为 App,代表整个应用。它对应的服务字符串凭证为“hade:app”。
(还有非应用的目录服务吗?)

这个接口的抽象,我们按照创建应用需要哪些目录的顺序来思考。

首先创建一个应用需要明确应用所在的根目录,这个就是根目录 BaseFolder;其次,我们需要一个目录保存配置文件,所以要有一个配置文件的目录 ConfigFolder;应用要输出日志,日志输出的存放路径也是需要设置的,所以我们再创建一个 LogFolder 来设置日志存放。

再考虑下,业务的服务提供者和对应接口需要有一个地方存放,我们将其命名为 ProviderFolder。同时,我们业务也有可能创建自己的中间件,所以需要有个 MiddlewareFolder 来存放中间件。

在后续的章节中,我们预计让框架支持命令行命令,并且使用命令行来控制进程的运行时状态,所以需要有个 CommandFolder 来存放各种命令行命令、一个 RuntimeFolder 来存放运行时的进程 ID 等信息。

最后当然了还需要有一个文件来存放单元测试信息,比如单元测试的初始化和终止的一些默认操作等,所以我们有一个 TestFolder 目录。

按照目前能想到的需求,可以将目录先分为这些文件夹,后续在开发模块的过程中,遇到需要增加的目录,我们还可以继续迭代修改 App 接口。

按照以上的分析,我们在框架的 framework/contract/app.go 中定义了一个 App 的服务接口:


package contract
// AppKey 定义字符串凭证
const AppKey = "hade:app"
// App 定义接口
type App interface {
   // Version 定义当前版本
   Version() string
   //BaseFolder 定义项目基础地址
   BaseFolder() string
   // ConfigFolder 定义了配置文件的路径
   ConfigFolder() string
   // LogFolder 定义了日志所在路径
   LogFolder() string
   // ProviderFolder 定义业务自己的服务提供者地址
   ProviderFolder() string
   // MiddlewareFolder 定义业务自己定义的中间件
   MiddlewareFolder() string
   // CommandFolder 定义业务定义的命令
   CommandFolder() string
   // RuntimeFolder 定义业务的运行中间态信息
   RuntimeFolder() string
   // TestFolder 存放测试所需要的信息
   TestFolder() string
}

定义默认目录结构

定义好了应用目录服务接口,我们再来思考它的实现,也就是设计使用这个 hade 框架的应用的默认目录。

PHP 目前最火的应用框架 Laravel,它的目录设计非常值得我们学习和参考,很合理地将各个功能模块放在各自的文件,并且文件定义清晰、无歧义。所以有了下面这个默认目录结构,我们来解释下对应的每个目录。

在这里插入图片描述

在根目录下,有五个文件:app、framework、config、storage、test,我们一个个描述功能看看下面都应该怎么划分。

app目录

一个业务就是一个 App,和业务代码相关的,比如所有的请求、控制台命令、控制器、服务提供者等都放在这个目录中,而业务逻辑代码之外的,比如配置文件、缓存、日志等,都放在外面。这样能把**“业务逻辑”和“非业务逻辑”**区分得更清晰。

我们继续看业务逻辑目录 app 下有哪些子目录。

在这里插入图片描述

第一层有三个目录:console、http、provider。因为业务除了提供 Web 服务,也提供控制台进程,所以在 app 目录下有两个子目录:一个是 console,业务中所有的控制台进程逻辑都放在这里;而另一个就是 http 目录,所有 Web 服务的逻辑都存放在里面。

同时这两者有很多逻辑是通用的,比如有可能用到同一个服务提供者,所以我们将两者通用的服务提供者的代码 provider 目录,也放在 app 目录下。

再看第二层的细分目录。

  • 首先是 app 目录下的 console,存放的是所有命令行工具的逻辑代码。

控制台命令设计为多级命令,比如在命令 ./hade task info 中,hade 是命令行工具,task 为一级命令,info 为二级命令。所以在 console 目录下,我们要创建一个 command 目录,并且 command 目录下按照一级、二级……递归保存。(关于命令行工具的更多详细设计和实现我们在下节课实现。)

  • 提供 Web 服务的 http 目录还有更细的层级。

HTTP 服务一般会按照模块划分,比如一个图书馆业务,我们会分为注册模块、图书模块、用户模块等,所以我们在 http 目录下定义了一个 module 目录,这个 module 目录下每个子目录代表一个模块服务。而 Web 服务特有的通用中间件,我们使用 http 目录下的 middleware 目录来保存。

  • app 下的 provider 目录,提供的是定义一个服务需要的文件。

在 provider 目录中,每个子目录就代表一个业务服务。而按照上一节课描述的,每个服务都需要有三个文件:代表服务协议的 contract.go 文件、代表服务提供者的 provider.go 文件,以及代表具体服务实现的 service.go 文件。

这里要强调是,这个 provider 目录存放业务提供的服务,而框架提供的服务,我们会放在 framework 目录下。

framework目录

framework 目录就是我们这个框架所有的代码。在这个目录里,除了之前引入了 Gin 框架有的 gin 目录,还有一些子目录:command、contract、middleware、provider、util。

在这里插入图片描述
这几个目录的功能也比较好理解。command 提供的是框架自带的命令行工具;middleware 存放框架为 Web 服务提供的中间件;contract 存放框架默认提供的服务协议;而对应服务协议的具体实现以及服务提供者,我们存放在 provider 目录下;util 目录则存放在框架研发过程中通用的一些函数。

不难发现,provider 和 command 的服务,在业务(app 目录)和框架(framework 目录)层面都有相同的目录,区别就是一个是业务提供的,一个是框架提供的。

我们这个项目为了演示框架开发的全过程,把框架和业务合并在一起,但是因为最终版框架在开源发布的时候,framework 目录是会剥离出去,作为一个单独的 git 项目的。所以,你在开发的时候要时刻明确,哪些是框架提供的命令行工具和服务,哪些是业务提供的命令行和服务。框架的内容需要放在 framework 目录下,业务的就要放在 framework 外。

config、storage、test 目录

在应用根目录下还剩 config 、storage、test 目录。config 这个目录存放的是配置文件,至于不同环境的配置文件如何存放和组织,我们后面会统一探讨整体的配置文件存放机制。test 目录存放的是测试相关的信息,比如测试用例或者测试数据等信息。

应用根目录下的 storage 存放应用运行过程中产生的内容。首先是日志,日志是应用运行过程中必然产生的信息,在 storage 下的 log 子目录里保存;其次是运行的进程 ID 等信息,这些都会存放在 storage 下的 runtime 目录中。

好,目录结构讲完了,我们整理下,从应用根目录开始,分为 5 个子目录 app、framework、config、storage、test,其中:

  • app 按应用的使用方式和通用性分为 console 和 http,以及通用的 provider 目录。framework 根据框架需要提供的功能分为 command、contract、gin、middleware、provider 和 util 6 个子目录。
  • config 和 test 分别保存配置和测试相关的信息;storage 目录保存应用运行产生的信息,分为 log 和 runtime 两个目录。

默认目录的实现

最开始我们说了,目录结构也是一个服务,其他服务想要使用目录结构的时候,可以通过服务容器,来获取目录结构服务实例。而现在我们有了想要创建的默认目录结构,那么下面就要实现这个目录结构服务实例了。

我们定义一个 HadeApp 来实现这个目录结构服务接口,这个 HadeApp 结构的元素除了服务实例之外,只需要一个项目的基础路径 basePath。因为我们设计的默认目录结构只要有了这个 basePath,就可以按照设计找出其余的所有目录了。

在 framework/provider/app/service.go 中定义 HadeApp 结构:

// HadeApp 代表 hade 框架的 App 实现
type HadeApp struct {
   container framework.Container // 服务容器
   baseFolder  string              // 基础路径
}

BaseFolder 是获取项目的基础路径,所以我们可以提供一种方式,在注册服务提供者的时候,实现 BaseFolder 的设置。来看代码,设置 hade 框架默认的 App 服务提供者为 HadeAppProvider,它带有一个可以设置的属性 BaseFolder,这个属性作为参数传入给服务实例初始化函数。

我们在 framework/provider/app/provider.go 中定义 HadeAppProvider 结构和方法:


// HadeAppProvider 提供 App 的具体实现方法
type HadeAppProvider struct {
   BaseFolder string
}

...

// Params 获取初始化参数
func (h *HadeAppProvider) Params(container framework.Container) []interface{} {
   return []interface{}{container, h.BaseFolder}
}
...



// NewHadeApp 初始化 HadeApp
func NewHadeApp(params ...interface{}) (interface{}, error) {
   if len(params) != 2 {
      return nil, errors.New("param error")
   }
   // 有两个参数,一个是容器,一个是 baseFolder
   container := params[0].(framework.Container)
   baseFolder := params[1].(string)
   return &HadeApp{baseFolder: baseFolder, container: container}, nil
}

这样,在业务目录的 main.go 中注册 App 服务的时候,就可以创建指定我们的 BaseFolder:


func main() {
   // 创建 engine 结构
   core := gin.New()
   // 指定 BaseFolder
   core.Bind(&app.HadeAppProvider{BaseFolder: "/tmp"})
   ...
}

但是刚才的实现在每次注册服务的时候,都需要设置一遍,为了提高易用性,我们希望这个 BaseFolder 能自动设置。怎么办?

在业务场景中,这个 BaseFolder 代表业务的开发路径,即业务代码所在的目录;另一个运行场景中,框架开发的业务需要去现网,也就是生产环境中运行,只需要二进制文件、配置文件、日志文件即可,因为这时候的 BaseFolder 其实是会根据项目部署地址变化的。

【小结】:

  1. 程序目录app、config、framework、storage、test
  2. 把目录结构作为一个服务,也注册到框架容器中

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

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

相关文章

ESP-C2系列模组开发板简介

C2是一个芯片采用4毫米x 4毫米封装,与272 kB内存。它运行框架,例如ESP-Jumpstart和ESP造雨者,同时它也运行ESP-IDF。ESP-IDF是Espressif面向嵌入式物联网设备的开源实时操作系统,受到了全球用户的信赖。它由支持Espressif以及所有…

空间复杂度与顺序表的具体实现操作(1)

最近更新的少,主要是因为参加了ACM竞赛空间复杂度空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量…

项目使用windows-root证书

项目使用windows-root证书 将证书导入到本地计算机 方式1:使用windows-root证书配置流程(计算机本地) 输入命令(mmc),进入控制台管理窗口 点击“文件”》“添加或删除管理单元”,进入如下界面 双击证书,选择“计算机账户”…

Swagger生成接口在线文档

OpenAPI规范(OpenAPI Specification 简称OAS)是Linux基金会的一个项目,试图通过定义一种用来描述API格式或API定义的语言,来规范RESTful服务开发过程,目前版本是V3.0,并且已经发布并开源在github上。&#…

C++核心编程<类和对象>(4)

C核心编程<类和对象>4.类和对象4.1封装4.1.1封装的意义封装的意义1封装的意义24.1.2struct和class区别4.1.3成员属性设置为私有4.2对象的初始化和清理4.2.1构造函数和析构函数1.1构造函数语法&#xff1a;类名(){}1.2析构函数语法&#xff1a; ~类名(){}4.2.2构造函数的分…

【JUC2022】第七章 AQS、ReentrantReadWriteLock 和 StampedLock

【JUC2022】第七章 AQS 文章目录【JUC2022】第七章 AQS一、AQS1.概述2.同步器3.抽象的4.队列式二、ReentrantReadWriteLock1.概述2.案例3.存在的问题三、StampedLock1.概述2.案例3.存在的问题一、AQS 1.概述 AQS(AbstractQueueSynchronizer&#xff0c;抽象的队列式同步器)&am…

tesseract -图像识别

下载链接&#xff1a;https://digi.bib.uni-mannheim.de/tesseract/如下选择最新的版本&#xff0c;这里我选择tesseract-ocr-w64-setup-5.3.0.20221222.exe有如下python模块操作tesseractpyocr 国内源&#xff1a;pip install -i https://pypi.mirrors.ustc.edu.cn/simple/ py…

ThreadLocal 学习常见问题

ThreadLocal 这个此类提供线程局部变量。这些变量不同于通常的对应变量&#xff0c;因为每个访问一个变量的线程(通过 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是希望将状态与线程(例如&#xff0c;用户 ID 或事务 ID)关联的类中的私有静态字段。使…

vue router elementui template CDN模式实现多个页面跳转

文章目录前言一、elementui Tabs标签页和NavMenu 导航菜单是什么&#xff1f;二、使用方式1.代码如下2.页面效果总结前言 写上一篇bloghttps://blog.csdn.net/jianyuwuyi/article/details/128959803的时候因为整个前端都写在一个index.html页面里&#xff0c;为了写更少的代码…

CENTO OS上的网络安全工具(十九)ClickHouse集群部署

一、VMware上集群部署ClickHouse &#xff08;一&#xff09;网络设置 1. 通过修改文件设置网络参数 &#xff08;1&#xff09;CentOS 在CENTOS上的网络安全工具&#xff08;十六&#xff09;容器特色的Linux操作_lhyzws的博客-CSDN博客中我们提到过可以使用更改配置文件的方式…

推荐 7 个 Vue.js 插件,也许你的项目用的上(五)

当我们可以通过使用库轻松实现相同的结果时&#xff0c;为什么还要编写自定义功能&#xff1f;开发人员最好的朋友和救星就是这些第三方库。我相信一个好的项目会利用一些可用的最佳库。Vue.js 是创建用户界面的最佳 JavaScript 框架之一。这篇文章是关于 Vue.js 的优秀库系列的…

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 难度&#xff1a;easy\color{Green}{easy}easy 题目描述 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表…

4-3 Linux启动流程

文章目录前言经典启动流程1 按下电源2 开机自检(BIOS)3 MBR引导4 GRUB菜单5 加载内核6 运行init进程7 读取/etc/inittab8 读取/etc/rc.sysinit初始化系统9 运行/etc/rc.d/rcN.d/脚本10 /etc/rc.local11 登录页面logincentos7与centos6前言 Linux系统的启动过程并不是大家想象中…

防静电监控仪可以检测现场设备是否和实际大地接触

随着电子产品集成化度越来越高&#xff0c;对于电子产品装配来说&#xff0c;静电的危害严重影响到产品的质量、成品率和可靠性, 必须对用于电子产品装配的净化间进行系统防静电措施&#xff0c;将生产过程中的静电危害程度降至最低。近年来电子企业对ESD的危害的深入认识&…

代码随想录刷题-数组-二分查找

文章目录写在前面原理习题题目1思路和代码题目-2写在前面 这个专栏是记录我刷代码随想录过程中的随想和总结。每一小节都是根据自己的理解撰写的&#xff0c;文章比较短&#xff0c;主要是为了记录和督促自己。刷完一章后&#xff0c;我会再单独整理一篇文章来总结和分享。 本…

【JVM 由浅入深】JVM入门

JVM入门1. 概述 今天我们对JVM 进行入门讲解&#xff0c;让我们了解下什么是JVM&#xff0c;是专门为Java服务的一款产品吗&#xff1f;&#xff1f;&#xff1f; 好了废话不多说了&#xff0c;让我们开始吧 2. 详解 2.1 Java 是跨平台的 为什么是Java是跨平台的呢&#xff0c…

LeetCode 路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 叶子节点 是指没有子节点…

oneblog_justauth_三方登录配置【Github】

文章目录oneblog添加第三方平台github中创建三方应用完善信息登录oneblog添加第三方平台 1.oneblog管理端&#xff0c;点击左侧菜单 网站管理——>社会化登录配置管理 ,添加一个社会化登录 2.编辑信息如下&#xff0c;选择github平台后复制redirectUri,然后去github获取cl…

Arduino添加ESP32开发板

【2023年3月4日】 最近要在新电脑上安装Arduino&#xff0c;需要进行一些配置&#xff0c;正好记录一下&#xff01; Arduino2.0.1 下的开发板添加操作。 ESP32开发板GitHub链接&#xff1a; GitHub - espressif/arduino-esp32: Arduino core for the ESP32Arduino core for…

ICASSP2023|达摩院语音实验室入选论文全况速览

近日&#xff0c;语音技术领域国际会议ICASSP公布了本届论文审稿结果&#xff0c;阿里巴巴达摩院语音实验室有14篇论文被大会收录。本次被接收的论文研究方向涵盖语音识别、语音唤醒、语音增强、说话人日志、语义理解、多模态预训练等。 01 TOLD: A Novel Two-Stage Overlap-…