当前位置: 首页 > news >正文

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

到现在,我们已经将 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")}// 有两个参数,一个是容器,一个是 baseFoldercontainer := 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()// 指定 BaseFoldercore.Bind(&app.HadeAppProvider{BaseFolder: "/tmp"})...
}

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

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

【小结】:

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

相关文章:

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

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

假如你知道这样的MySQL性能优化

1. 为查询缓存优化你的查询 大多数的 MySQL 服务器都开启了查询缓存。这是提高性最有效的方法之 一,而且这是被 MySQL 的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同…...

79、ClimateNeRF: Physically-based Neural Rendering for Extreme Climate Synthesis

简介主页物理模拟可以很好地预测天气影响。神经辐射场产生SOTA场景模型。ClimateNeRF 允许我们渲染真实的天气效果,包括雾霾、雪和洪水 ,结果可以通过有物理意义的变量来控制,比如水位 ,这允许人们可视化气候变化的结果将对他们产…...

前端面试题(一)

目录 前言 一、css3实现布局的方式有哪些? 1.flex布局 2.grid布局 二、jquery的扩展机制? 三、jquery动画和css实现动画的本质区别? 四、不使用css的动画,如何实现盒子从左到右移动? 五、使用过的框架&#xf…...

Java基础常见面试题(七)

序列化和反序列化 Java序列化与反序列化是什么? Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。 序列化: 序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地…...

【springmvc】报文信息转换器

HttpMessageConverter HttpMessageConverter,报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文 HttpMessageConverter提供了两个注解和两个类型: RequestBody, ResponseBody, Reques…...

3.5知识点复习

extern:表示声明。 没有内存空间。 不能提升。const:限定一个变量为只读变量。volatile:防止编译器优化代码。volatile int flg 0; register:定义一个寄存器变量。没有内存地址。register int a 10;字符串:C语言中&a…...

湖南中创教育PMP分享项目经理有哪些优势?

项目经理拥有超强的计划能力;具备大局意识;沟通能力特别强;具备更大的灵活性和反应能力以及总结汇报能力 1、超强的计划能力 项目经理几乎无时无刻都在做计划,因此也就更擅长做计划。 项目管理要抓重点,有主次地处理…...

LeetCode:27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面…...

麻雀算法SSA优化LSTM长短期记忆网络实现分类算法

1、摘要 本文主要讲解:麻雀算法SSA优化LSTM长短期记忆网络实现分类算法 主要思路: 准备一份分类数据,数据介绍在第二章准备好麻雀算法SSA,要用随机数据跑起来用lstm把分类数据跑起来将lstm的超参数交给SSA去优化优化完的最优参数…...

哈希表题目:数组中的 k-diff 数对

文章目录题目标题和出处难度题目描述要求示例数据范围解法思路和算法代码复杂度分析题目 标题和出处 标题:数组中的 k-diff 数对 出处:532. 数组中的 k-diff 数对 难度 4 级 题目描述 要求 给定一个整数数组 nums\texttt{nums}nums 和一个整数 k…...

SAP ERP系统PP模块计划策略2050详解

SAP/ERP系统中面向订单生产的计划策略主要有20和50两个策略,这两个策略都是面向订单生产的计划策略,也是离散制造行业应用比较广泛的策略。它们之间最大差异就是在于20策略完全是由订单驱动,而50策略是预测加订单驱动,本文主要介绍…...

TIA博途中将硬件目录更改为中文的具体方法演示

TIA博途中将硬件目录更改为中文的具体方法演示 基本步骤可参考如下: 第一步: 第二步: 具体的操作演示: 如下图所示,在所示的目录中找到zh-chs文件夹,删除或修改文件夹的名称均可,这里建议大家修改文件夹的名称,防止以后需要恢复成英文目录, 如下...

【多线程操作】线程池模拟实现

目录 一.线程池的作用 二.线程池的模拟实现 1.线程模块(Thread.hpp): 2.线程锁模块(LockGuard.hpp): 3.任务模块(Task.hpp) 4.线程池核心(ThreadPool.hpp&#xff…...

HBase---Hbase安装(单机版)

Hbase安装单机版 文章目录Hbase安装单机版Master/Slave架构安装步骤配置Hbase1.上传压缩包解压更名修改hbase-env.sh修改hbase-site.xml配置HBase环境变量配置Zookeeper复制配置文件修改zoo.cfg配置文件修改myid配置Zookeeper环境变量刷信息配置文件启动hbase步骤hbase shellMa…...

启动项管理工具Autoruns使用实验(20)

实验目的 (1)了解注册表的相关知识; (2)了解程序在开机过程中的自启动; (3)掌握Autoruns在注册表和启动项方面的功能;预备知识 注册表是windows操作系统中的一个核心数据…...

BFD单臂回声实验详解

13.1.1BFD概念 BFD提供了一个通用的、标准化的、介质无关的、协议无关的快速故障检测机制,有以下两大优点: 对相邻转发引擎之间的通道提供轻负荷、快速故障检测。 用单一的机制对任何介质、任何协议层进行实时检测。 BFD是一个简单的“Hello”协议。两个系统之间建立BFD会…...

详解JAVA类加载器

目录 1.概述 2.双亲委派 3.ServiceClassLoader 4.URLClassLoader 5.加载冲突 1.概述 概念: 类加载器(Class Loader)是Java虚拟机(JVM)的一个重要组件,负责加载Java类到内存中并使其可以被JVM执行。类…...

记录一些常用C标准库函数,以及Linux系统调用函数的作用(不断更新)

C标准库函数 perror() 函数 作用:perror函数是C标准库中的一种函数,用于在STDERR(标准错误输出流)中输出给定的错误信息字符串。它不属于Linux系统调用函数。 具体使用方法:perror("调用的函数名") 所需…...

RK3568平台开发系列讲解(显示篇)DRM的atomic接口

🚀返回专栏总目录 文章目录 一、Property二、Standard Properties三、代码案例沉淀、分享、成长,让自己和他人都能有所收获!😄 📢目前DRM主要推荐使用的是 Atomic(原子的) 接口。 一、Property Property(属性)—– Atomic操作必须依赖的基本元素 Property把前面的…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

docker详细操作--未完待续

docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...

Rust 开发环境搭建

环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu ​ 2、Hello World fn main() { println…...

Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合

作者:来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布,Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明,Elastic 作为 …...

MySQL的pymysql操作

本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...

云原生周刊:k0s 成为 CNCF 沙箱项目

开源项目推荐 HAMi HAMi(原名 k8s‑vGPU‑scheduler)是一款 CNCF Sandbox 级别的开源 K8s 中间件,通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度,为容器提供统一接口,实现细粒度资源配额…...