使用带有 golang 和数据库抽象的接口

Posted

技术标签:

【中文标题】使用带有 golang 和数据库抽象的接口【英文标题】:Using interfaces with golang and database abstraction 【发布时间】:2018-04-04 17:08:46 【问题描述】:

我正在尝试弄清楚如何在 Go 中构建数据存储抽象。我想我了解接口的基础知识。但是,我遇到的问题是网上所有的例子都只给你展示了最简单的情况,没有其他的。

我要做的是弄清楚如何以及在何处放置 SQL 代码。我试图编写最简单的代码来说明我正在尝试做的事情(是的,没有错误代码,是的,路径结构不是惯用的)。我有一个包含两个表的数据库。一个用于存储 Circles,一个用于存储 Squares。我在 Go 中有对象来制作这些。我的目录结构是:

project/main.go
project/test.db
project/shapes/shape.go
project/shapes/circle/circle.go
project/shapes/square/square.go
project/datastore/datastore.go
project/datastore/sqlite3/sqlite3.go

我能想到的唯一方法是将 SQL INSERT 和 SELECT 代码放入实际的形状文件(circle.go 和 square.go)中。但这感觉真的不对。它似乎应该是 datastore/sqlite3 包的一部分。我只是不知道如何使这项工作。这是我目前所拥有的:

main.go

package main

import (
    "fmt"
    "project/datastore/sqlite3"
    "project/shapes/circle"
    "project/shapes/square"
)

func main() 
    c := circle.New(4)
    area := c.Area()
    fmt.Println("Area: ", area)

    s := square.New(12)
    area2 := s.Area()
    fmt.Println("Area: ", area2)

    db := sqlite3.New("test.db")
    db.Put(c)
    db.Close()

shapes/shape.go

package shapes

type Shaper interface 
    Area() float64

shapes/circle/circle.go

package circle

import (
    "project/shapes"
)

type CircleType struct 
    Radius float64


func New(radius float64) shapes.Shaper 
    var c CircleType
    c.Radius = radius
    return &c


func (c *CircleType) Area() float64 
    area := 3.1415 * c.Radius * c.Radius
    return area

shapes/square/square.go

package square

import (
    "project/shapes"
)

type SquareType struct 
    Side float64


func New(side float64) shapes.Shaper 
    var s SquareType
    s.Side = side
    return &s


func (s *SquareType) Area() float64 
    area := s.Side * s.Side
    return area

datastore/datastore.go

package datastore

import (
    "project/shapes"
)

type ShapeStorer interface 
    Put(shape shapes.Shaper)
    Close()

datastore/sqlite3/sqlite3.go

package sqlite3

import (
    "database/sql"
    _ "github.com/mattn/go-sqlite3"
    "log"
    "project/datastore"
    "project/shapes"
)

type Sqlite3DatastoreType struct 
    DB *sql.DB


func New(filename string) datastore.ShapeStorer 
    var ds Sqlite3DatastoreType

    db, sqlerr := sql.Open("sqlite3", filename)
    if sqlerr != nil 
        log.Fatalln("Unable to open file due to error: ", sqlerr)
    
    ds.DB = db

    return &ds


func (ds *Sqlite3DatastoreType) Close() 
    err := ds.DB.Close()
    if err != nil 
        log.Fatalln(err)
    


func (ds *Sqlite3DatastoreType) Put(shape shapes.Shaper) 
    log.Println("Help")
    // here you could either do a switch statement on the object type
    // or you could do something like shape.Write(), if Write() was defined
    // on the interface of shape/shapes.go Shaper interface and then
    // implemented in the Square and Circle objects. 

由于 Circle 和 Square 对象的数据库表会有所不同,因此我需要为每个对象设置一个方法。如果我在 circle 包和 square 包中添加一个方法来进行插入,我可以让它工作。但就像我上面说的,感觉就像我做错了。

提前非常感谢。

【问题讨论】:

我认为,没有正确/错误方法。这是风格问题。您可以选择ActiveRecord pattern 或Data Mapper pattern。在前者中,CRUD 操作被定义为 object 的一部分,而在后者中,您的 object 只保存数据而不保存对持久层(数据库)有所了解。 如果你对 datastore/sqlite3/sqlite3.go 中的对象类型执行 switch 语句,那么你所有的 SQL 语句都可以保留在 sqlite 包中。如果您以另一种方式进行操作,正如我在评论中所说明的那样,那么您就是将数据库代码放入对象中。那么当您需要支持 10 个不同的数据存储时会发生什么。这似乎是错误的。似乎我还缺少一些巧妙的技巧来使这项工作更好。 这似乎是错误的,因为您使用类型切换限制解决方案。您可以使用现有的 ORM 包(例如gorm),也可以开发自己的。在后一种情况下,我建议您为与 struct 名称相关的表命名定义一个 convention/rule。例如,您可以为CircleType定义表名将是CircleTypecircle_type等...然后,使用标签定义表的列名和struct的字段名之间的映射.基于这些,您可以使用反射在 datastore 中为简单的 CRUD 构造 SQL。 【参考方案1】:

类型切换是正确的做法。 您的逻辑代码(您编写的代码,因为之前没有其他人做过)应该对存储一无所知。

假设您想要为请求添加一个计数器,并且您决定对 Redis 上的请求进行计数。然后怎样呢?将计数器集合名称也添加到 Shape 中?

然后您应该创建一个新的 ShapeStorer 作为装饰器,并在 put 方法中调用 Redis ShapeStorer 和 sqlite ShapeStorer。

对于无 sql 数据库,您有时根本不关心架构,您只需序列化对象并保存它。

【讨论】:

以上是关于使用带有 golang 和数据库抽象的接口的主要内容,如果未能解决你的问题,请参考以下文章

golang 接口笔记

golang 接口

Golang通脉之接口

golang变相实现抽象类

golang面向对象编程—接口

golang学习随便记8