找回密码
 立即注册
查看: 279|回复: 0

三、Web服务概览

[复制链接]
发表于 2022-7-3 15:47 | 显示全部楼层 |阅读模式
此前,我对农业云平台AI模块的技术选型及大致的架构进行了介绍。
本篇我将对Web服务的整体构造进行概述。
服务的构造形式

        在我的主观认识里, “微服务“架构当中的网关与服务Handlers独立运行,二者之间以RPC等协议进行通信。但前面篇章提到,农业云平台的基础后端和AI模块是独立运行的,某种程度上,平台就是一种简单的“微服务”架构,而我们课题组负责的AI模块,是其中的一个服务。而在AI模块的层次下,如果再采用“微服务”架构,把各功能的服务Handler单独编译运行,令网关与服务通过RPC等协议进行通信,整个系统就会显得过于臃肿。所以,我选择以单体服务架构,编写各服务的Handler,并将它们编译为一个可执行文件。
文件夹结构布局

        网关与服务部分的文件夹布局如下:
.
├── api
├── config
├── etc
│   └── log
│       ├── error
│       └── info
├── global
├── initial
├── model
├── service
└── utils
文件夹内容
Api服务handlers与Python端模型交互所需的protobuf格式以及grpc的通讯接口。
Config基于Viper,对服务的相关参数进行加载。
Etc储存服务参数Yaml文件及日志文件。
Global定义数据库实例、缓存实例、日志Logger等全局使用的对象。
Initial注册路由、初始化数据库连接、初始化日志Logger。
Model定义各类服务的输入和输出结构体。
Service各个功能Handler逻辑实现。
Untils一些通用函数的实现。
服务初始化

package main

import (
    "citrus/global"
    initial "citrus/initial"
)

func main() {
    initial.InitLogger()
    defer global.Logger.Sync()
    e := initial.NewEngine()
    e.Run()
}
服务的开端,始于main.go。主函数的流程很大程度上沿袭了Gin架构的初始化流程,即“初始化一个Engine“->”运行Engine“。不过,为了把初始化过程中可能出现的意外情况记录下来,我选择在初始化Gin框架的Engine前,先对Zap的Logger进行初始化,并且在主函数设置了defer运行的Logger的同步操作。
package initial

import (
    "citrus/config"

    "github.com/gin-gonic/gin"
)

type Engine struct {
    engine *gin.Engine
}

func NewEngine() *Engine {
    initConfig()
    initDB()
    initCache()
    r := gin.Default()
    g := r.Group("/")
    RegisterService(g)
    return &Engine{engine: r}
}

func (e *Engine) Run() {
    e.engine.Run(config.GetPort())
}        主函数当中的e := initial.NewEngine()与e.Run() 是在initial包中定义的。这里的Engine是对gin.Engine的一种包装,为的是实现一个自定义的Engine构造函数,在初始化gin.Engine同时,完成其他部分的初始化操作,包括:服务参数导入、数据库实例初始化、Redis缓存实例初始化、服务Handlers的Gin路由注册。
        上述流程中,initConfig主要涉及Viper模块对本地配置文件的读取,initDB主要涉及Gorm的初始化,initCache主要涉及redis.Client的初始化,这些流程的代码较为常规。值得注意的是,initConfig方法必须在initDB及initCache之前调用,因为后者的初始化过程需要调用config模块的变量。
此处单独提一下路由注册方法。RegisterService方法接受一个gin.RouterGroup指针,然后分别调用各个功能模块的RegisterService方法,将路由路径和对应handler关系写入RouterGroup,最终完成所有服务的路由注册。
package initial

import (
    "citrus/service/camera"
    "citrus/service/detect"
    "citrus/service/dialog"
    "citrus/service/drone"

    "github.com/gin-gonic/gin"
)

func RegisterService(r *gin.RouterGroup) {
    dialog.RegisterService(r)
    drone.RegisterService(r)
    detect.RegisterService(r)
    camera.RegisterService(r)
}        服务初始化的过程中,可能会出现诸如配置文件读取失败、无法创建数据库连接实例等问题。由于我们在运行e := initial.NewEngine() 之前已经初始化了Zap的Logger,因此,但凡出现了初始化失败的错误,我们直接以Fatal级别将其写入日志,并终止服务运行。
package initial

import (
    "citrus/config"
    "citrus/global"

    "github.com/spf13/viper"
)

func initConfig() {
    viper.SetConfigFile("./etc/config.yaml")
    err := viper.ReadInConfig()
    if err != nil {
        global.Logger.Fatal(err)
    }
    config.InitAddress()
}
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-26 01:56 , Processed in 0.086530 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表