最近都是在写 cms 后端,跟之前写容器调度系统,区别还是超级大的,面向的东西不一样了。发现 cms 后端,也就是 web 后端,还是蛮考察人的抽象能力的,可能比不上系统应用那样的性能以及效率。但是为了开发效率,以及抽象,还是很考察人的代码功底的。但是也没说意味着要牺牲性能。
db
写 web 后端,很大几率都会与 db 交互,使用 sql。那么就会使用到 database/sql 这个包。可能很多开发者都倾向于使用一些 ORM。ORM 当然会省很多力气。但是掌握一些神奇的技巧的话,可以变得很优雅。
比如,我们要写一个表,举例子通常都是
CREATE TABLE `Person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` TEXT NOT NULL,
`email` TEXT NOT NULL,
PRIMARY KEY (`id`)
)Engine=InnoDB DEFAULT CHARSET=utf8mb4;
相当简单的样子,如果只是这种程度,我们直接定义一个
type Person {
ID uint64 `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
}
即可,直接用 ORM 的一些 Get 方法,就能很轻松的处理了。不过我们不会止步于此,我们向更复杂的结构前进。倘若数据表,发生了这样的改进:
ALTER TABLE `Person` ADD COLUMN `permission` TEXT NOT NULL;
permission中,存储的不是普通的字符串,而是json。那么我们的 struct 应该如何做?变成这种吗?
type Person {
ID uint64 `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
Permission string `db:"permission"`
}
然后每次输出的时候,再对 permission 做json.Unmarshal操作吗?
显然不应该这样,这样复用性也不好,多加了一个复杂字段,就要如此改动,显然不可行呀。会多出很多用于数据处理的代码。很棒的是,golang有一些神奇的 interface。
Scanner
// Scanner is an interface used by Scan.
type Scanner interface {
// Scan assigns a value from a database driver.
//
// The src value will be of one of the following types:
//
// int64
// float64
// bool
// []byte
// string
// time.Time
// nil - for NULL values
//
// An error should be returned if the value cannot be stored
// without loss of information.
Scan(src interface{}) error
}
这个就是Scanner接口了。需要实现一个 Scan。这个接口是用于从 db 读出到内存中用的,基础的数据结构,都能直接从 db 中读出来,那么复杂的结构,我们只用实现 Scan 接口,就可以工作了。
按上面的结构举例子,我们可以实现一个结构。
type Permission struct {
Read bool
Write bool
}
然后我们再针对这个结构实现 Scanner 接口即可。
func (p *Permission) Scan(src interface{}) error {
var data []byte
switch src.(type) {
case string:
data = []byte(src.(string))
case []byte:
data = src.([]byte)
default:
return fmt.Errorf("Incompatible type for Permission val: %v", src)
}
err := json.Unmarshal(data, p)
if err != nil {
return fmt.Errorf("Permission Scan Unmarshal data: %v err: %v", data, err)
}
return nil
}
代码很简单。
Valuer
// Valuer is the interface providing the Value method.
//
// Types implementing Valuer interface are able to convert
// themselves to a driver Value.
type Valuer interface {
// Value returns a driver Value.
Value() (Value, error)
}
就是实现与 Scanner 相反的过程,这样理解应该没什么问题。
这样,我们能够将内存中的对象,转化为数据库中存储的格式。
针对我们的 Permission。
func (p Permission) Value() (driver.Value, error) {
b, err := json.Marshal(p)
if err != nil {
return nil, fmt.Errorf("Marshal %v err: %v", p, err)
}
return string(b), err
}
这样就可以了。
一些坑
json 和 go 结合的话,其实有一些坑。
- 默认类型
假设承接 json 的对象结构是,
map[string]interface{},那么可要注意interface{}了。如果原先的值是数字,那么读回来后,这个interface{}的 type 是float64。这个时候,我们选择使用encoding/gob就好。这个序列化的时候是保存类型的。(此处的考虑未考虑性能,只考虑可行,因为具体的性能我也没做过测试。)