专栏首页云架构分享基于Golang&MongoDB快速构建RESTful服务
原创

基于Golang&MongoDB快速构建RESTful服务

近年来,“微服务”在软件架构出现频次越来越高,其思想主要是指将一个大型的单个应用服务拆分为多个微服务,每个微服务在其自己的进程中运行,并采用轻量级的协议进程通信,通常采用的方法是基于HTTP的RESTful API。本文主要介绍一个RESTful框架的使用方式及其主要实现原理,主要基于Golang和MongoDB实现,协议采用HTTP+JSON,另外搭配ElasticSearch可以实现数据资源的搜索功能。使用者只需要定义好数据资源的结构体,即可快速构建RESTful服务。

1. 代码地址

https://github.com/jimdn/restful

2. 主要特性

  • 定义好数据资源的结构体(包含json和bson2个tags),即可实现HTTP+JSON的CURD服务,并支持过滤、范围、搜索、排序、截取等条件查询,协议如下:

HTTP方法

路径

URL参数

HTTP Body

说明

POST

/{biz}

-

新增的数据内容

新增一条数据

PUT

/{biz}/{id}

-

新增/覆盖的数据内容

新增或覆盖更新一条数据

PATCH

/{biz}/{id}

seq

修改的数据内容

部分更新一条数据(需要带上原数据的seq)

DELETE

/{biz}/{id}

-

-

删除一条数据

GET

/{biz}/{id}

-

-

查询一条数据

GET

/{biz}

page size filter range in nin all search order select

-

分页查询URL各参数示例: page=1 size=10 filter={"star":5, "city":"shenzhen"} range={"age":{"gt":20, "lt":40}} in={"color":["blue", "red"]} nin={"color":["blue", "red"]} all={"color":["blue", "red"]} search=hello order=["+age", "-time"] select=["id", "name", "age"]

  • 定义数据资源结构体时,支持的数据类型包含:

· 普通类型: bool int32 uint32 int64 uint64 float32 float64 string struct · 数组类型: []bool []int32 []uint32 []int64 []uint64 []float32 []float64 []string []struct · 字典类型: map[string]bool map[string]int32 map[string]uint32 map[string]int64 map[string]uint64 map[string]float32 map[string]float64 map[string]string map[string]struct

  • 支持字段级别的只创建、只读配置:

· CreateOnly: 只允许创建,不允许后续修改字段 · ReadOnly: 只允许读取字段,不允许创建和修改,适用于从别的系统导入数据到数据库,然后提供数据的读取服务

  • 具备字段检查功能,传入的数据资源字段类型出错或者不存在,会返回失败并提示具体错误信息。
  • 支持用户传入数据ID或自动创建ID,自动创建ID采用UUIDv4字符串格式,定义数据资源结构体需要固定定义1个id字段,需要注意tags的写法:
type Foo struct {
    Id  *string  `json:"id,omitempty" bson:"_id,omitempty"`
     ...
}
  • 支持跟踪数据的创建和修改时间,定义数据资源结构体需要额外定义2个字段,分别为:

· btime: birth time,记录该条数据创建的时间戳 · mtime: modify time, 记录该条数据最后一次修改的时间戳

  • 支持防并发写,定义数据资源结构体需要额外定义1个seq字段:

· seq: 数据序列号,数据每次被修改都会更新序列号,更新(PATCH)请求需要带上数据原seq防止并发写引起数据错乱

  • 支持自定义传入数据库名称和表名称(集合名称),只需在URL参数里传入:

· db: 数据库名称 · col: 表名称(集合名称) 示例:/{Biz}?db=dbName&col=colName 如未指定,则组件默认以rest_{biz}作为数据库名称,以cn作为表名称

3. 代码示例

框架使用方式非常简单,初始化好一个router路由句柄和mongodb句柄,定义好数据资源的结构,即可服务。

服务前需要先启动一个MongoDB服务,假设服务地址为:mongodb://127.0.0.1:27017

package main

import (
   "fmt"
   "net/http"
   "time"
   "github.com/globalsign/mgo"
   "github.com/gorilla/mux"
   "github.com/jimdn/restful"
)

// step 1: init data structure
type Student struct {
    Id        *string   `json:"id,omitempty" bson:"_id,omitempty"`
    Name      *string   `json:"name,omitempty" bson:"name,omitempty"`
    Age       *int64    `json:"age,omitempty" bson:"age,omitempty"`
    Sex       *string   `json:"sex,omitempty" bson:"sex,omitempty"`
    Hobbies   []string  `json:"hobbies,omitempty" bson:"hobbies,omitempty"`
    Btime     *int64    `json:"btime,omitempty" bson:"btime,omitempty"`   // internal, doc birth time
    Mtime     *int64    `json:"mtime,omitempty" bson:"mtime,omitempty"`   // internal, doc modify time
    Seq       *string   `json:"seq,omitempty" bson:"seq,omitempty"`       // internal, doc seq, prevent concurrent updating
}

func main () {
    // step 2: init router and mongodb
    router := mux.NewRouter()
    mgoSess, err := mgo.DialWithTimeout("mongodb://127.0.0.1:27017", 5 * time.Second)
    if err != nil {
        fmt.Printf("mongo dial err: %v\n", err)
        return
    }

    // step 3: init restful
    processors := []restful.Processor {
        {
            Biz:          "students",
            URLPath:      "/students",
            DataStruct:   new(Student),
        },
    }
    restfulGlobalCfg := restful.GlobalConfig {
        Mux: router,
        MgoSess: mgoSess,
    }
    err = restful.Init(&restfulGlobalCfg, &processors)
    if err != nil {
        fmt.Printf("restful init err: %v\n", err)
        return
    }

    // step4: start http server
    srv := &http.Server{
        Handler:      router,
        Addr:         "127.0.0.1:8080",
        WriteTimeout: 10 * time.Second,
        ReadTimeout:  10 * time.Second,
    }
    err = srv.ListenAndServe()
    if err != nil {
        fmt.Printf("ListenAndServe err: %v", err)
    }
}

更多示例可以参考代码库里的examples目录里的示例。

4. 主要实现思路

  • 字段解析组件,代码主要在field.go文件: 主要为每个定义好的数据资源结构体做字段解析,主要包含字段类型、字段只创建只读配置、搜索字段等。对该资源的CURD操作涉及到的字段,都会与解析结果做比对,不匹配的会返回失败,并提示错误字段的信息。这里的字段名,取至数据资源结构体字段tags里的json值。
  • 处理器组件,代码主要在processor.go文件: 主要为每个数据资源定义一个处理器,处理器主要存储了该数据资源的业务名{Biz},URL服务路径,字段解析结果,CURD的处理函数等。 - 业务名{Biz}: 主要用于生成默认的数据库名称,若开启了搜索功能,还用于Elasticsearch的biz字段值。 - URL服务路径: 使用者通过配置参数,可以实现自定义URL服务路径,如统一加上版本信息/v2/{Biz},统一加上前缀/cgi/{Biz}。 - 字段解析结果: 主要保存了字段解析组件对数据资源字段信息进行解析的结果,用于对传入的数据资源进行合法性校验。 - CURD的处理函数:一般使用者不需要配置,处理器组件已经实现了默认的CURD处理函数,这些函数主要处理与MongoDB的数据交互。
  • 请求处理组件,代码主要在msg.go文件: 主要实现了HTTP请求的预处理和回包处理。回包格式统一为如下格式:
    type Foo struct {
        Id  *string  `json:"id,omitempty" bson:"_id,omitempty"`
        ...
    }

如请求处理失败,失败代码会同时体现在HttpStatusCode和包体的code,失败信息会体现在包体的msg。

  • 搜索组件,代码主要在es.go文件: 主要支持分页查询的搜索功能,在数据资源初始化时,需要传入支持搜索的字段名列表。后续数据资源的POST、PUT、PATCH请求处理完成后,会创建一个OnWriteDone协程处理搜索处理的更新。在分页查询时,如果URL传入search参数,则会先请求Elasticsearch获取命中搜索词的资源ID列表,再做后续的查询。

4. 待完善功能

字段内容值的合法性判断

字段加密功能

字段脱敏功能

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 基于时序数据库的监控告警系统搭建实践

    随着云计算技术的广泛应用,越来越多的项目部署和迁移到云端,传统的监控告警系统在短时间内还不能适配云上的服务。为了实现实时系统运行状态的展示、故障的及时告警、历史...

    JimmyDeng
  • 2018-06-14 Spring Framework Overview 5.0Spring Framework Overview

    Albert陈凯
  • 编写高质量代码 Web前端开发修炼之道 读书笔记

    用一个{}对象类型的变量作为全局变量的属性。 推荐用大写的GLOBAL作为全局变量的变量名。

    lilugirl
  • Spring Boot-0.基础

    Spring Boot使用“习惯优于配置”的原则,使项目快速运行起来。 Spring Boot可以不需要或者很少的Spring配置,创建一个独立运行(运行ja...

    悠扬前奏
  • 用于进行字符串静态分析的配对自动机和正则表达式(CS SE)

    在本文中,我们形式化并证明了Tarsis的健全性,Tarsis是一个基于抽象解释理论的新抽象领域,该抽象解释理论通过有限状态自动机近似字符串值。Tarsis的主...

    刘子蔚
  • Spring Boot 概述

    Spring Boot可以以jar包的形式独立运行,运行一个Spring Boot项目只需要通过java -jar xx.jar来运行 。

    cherishspring
  • isa详解(一)isa结构

    为什么要用union以及位运算呢。因为在计算机中为二进制。位运算是最快速的计算方式 union C++ 中的共用体。顾名思义 就是在union 中 公用一个内存...

    老沙
  • 关于Spring Boot你不得不知道的事

    2 Spring Boot和Spring MVC 试想一下使用Spring或者Spring MVC的经历,有哪些痛苦?

    Java架构
  • Linux 多核下绑定硬件中断到不同 CPU

    硬件中断发生频繁,是件很消耗 CPU 资源的事情,在多核 CPU 条件下如果有办法把大量硬件中断分配给不同的 CPU (core) 处理显然能很好的平衡性能。 ...

    小小科
  • MySQL binlog日志大小超过限定范围

    my.cnf中有两个参数设置: expire_logs_days = 7 #binlog保留时间7天 max_binlog_size = 1G ...

    MySQL轻松学

扫码关注云+社区

领取腾讯云代金券