Go言語でDIを使ってみる。
利用するFW
- mithril.js https://lhorie.github.io/mithril/
- Gin(golang) https://gin-gonic.github.io/gin/
- genmai(goのdbアクセスライブラリ) https://github.com/naoina/genmai
- golang用sqliteドライバ https://github.com/mattn/go-sqlite3
- HotReload用 https://github.com/skelterjohn/rerun
- DIライブラリ https://github.com/facebookgo/inject
アプリ説明
下記をリファクタリング
Mithril+golang Gin を試す
コード
- jsonとDBアクセスで使う構造体 $GOPATH/src/app/model/todo.go
- リファクタリング前と変化なし。
todo.go
package model
type Todo struct {
Id int64 `db:"pk" json:"id"`
Description string `json:"description"`
Done bool `json:"done"`
}
dbアクセス
- DBアクセスするコーッドをinterfaceで抽象化する。
- repository $GOPATH/src/app/repo/repo.go
repo.go
package repo
import "app/model"
type Repo interface{
FindAll() []model.Todo
Save(todo *model.Todo)
}
- DBアクセス用のセットアップ。
- グローバル変数使わないようにリファクタリング
db.go
package db
import (
_ "github.com/mattn/go-sqlite3"
"github.com/naoina/genmai"
"app/model"
"github.com/gin-gonic/gin"
"fmt"
)
func InitDB() *genmai.DB {
DB, err := genmai.New(&genmai.SQLite3Dialect{}, ":memory:")
if err != nil {
panic(err)
}
if err := DB.CreateTable(&model.Todo{}); err != nil {
panic(err)
}
initData := []model.Todo{
{1, "なんかやる", false},
{2, "なんかやる2", true},
}
if _, err = DB.Insert(&initData); err != nil {
panic(err)
}
return DB
}
/**
トランザクション制御のミドルウェア
*/
func GetTransactionHandlerFunc(DB *genmai.DB) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
fmt.Println("END TRANSACTION ROLLBACK")
DB.Rollback()
panic(err)
} else {
DB.Commit()
fmt.Println("END TRANSACTION COMMIT")
}
}()
fmt.Println("START TRANSACTION")
if err := DB.Begin(); err != nil {
panic(err)
}
c.Next()
}
}
- reposigoryの実装。
repo.go
package db
import (
"app/model"
"github.com/naoina/genmai"
)
type RepoDbImpl struct {
DB *genmai.DB `inject:""`
}
func (r *RepoDbImpl) FindAll() []model.Todo {
var todos []model.Todo
if err := r.DB.Select(&todos, r.DB.OrderBy("id", genmai.ASC)); err != nil {
panic(err)
}
return todos
}
func (r *RepoDbImpl) Save(todo *model.Todo) {
if num, err := r.DB.Update(todo); err != nil {
panic(err)
}else if num == 0 {
if _, err := r.DB.Insert(todo);err != nil {
panic(err)
}
}
}
- mainに書いていたコントローラの記述を別パッッケージに切り出す。 $GOPATH/src/app/ctrl/todoCtrl.go
todoCtrl.go
package ctrl
import (
"app/repo"
"github.com/gin-gonic/gin"
"net/http"
"app/model"
)
type BaseCtrl struct {
Repo repo.Repo `inject:""`
}
func (base *BaseCtrl) Index(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
}
func (base *BaseCtrl) GetTodo(c *gin.Context) {
c.JSON(http.StatusOK, base.Repo.FindAll())
}
func (base *BaseCtrl) AddTodo(c *gin.Context) {
var todo model.Todo
if c.BindJSON(&todo) == nil {
base.Repo.Save(&todo)
c.JSON(http.StatusOK, todo)
}else {
c.JSON(http.StatusBadRequest, nil)
}
}
- mainでは初期化,DI,ルーティングを記述 $GOPATH/src/app/main/app.go
main.go
package main
import (
"github.com/gin-gonic/gin"
"app/db"
"github.com/facebookgo/inject"
"app/ctrl"
)
func main() {
r := gin.Default()
r.Static("/static", "static")
r.LoadHTMLGlob("templates/*")
//dbの初期化
DB:=db.InitDB()
r.Use(db.GetTransactionHandlerFunc(DB))
//コントローラのDI
ctrl := new(ctrl.BaseCtrl)
if err := inject.Populate(ctrl, new(db.RepoDbImpl),db.DB); err != nil {
panic(err)
}
r.GET("/", ctrl.Index)
r.GET("/todo", ctrl.GetTodo)
r.POST("/todo", ctrl.AddTodo)
r.Run(":9000")
}
- インデックスページ(htmlテンプレート) $GOPATH/templates/index.html
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="/static/js/mithril.min.js" type="text/javascript" charset="utf-8"></script>
<title>Mithril TODO</title>
</head>
<body>
<div id="$contents"></div>
<script type="text/javascript">
//todo component
var todo = {};
//model
todo.Todo = function (data) {
this.description = m.prop(data.description);
this.done = m.prop(data.done);
};
todo.TodoList = Array;
//define the view-model
todo.vm = (function () {
var vm = {}
vm.init = function () {
//a running list of todos
vm.list = new todo.TodoList();
//a slot to store the name of a new todo before it is created
vm.description = m.prop("");
//init
m.request({method: "GET", url: "/todo"}).then(function (todoList) {
todoList.forEach(function (v) {
vm.list.push(new todo.Todo({description: v.description, done: v.done}));
});
});
vm.add = function () {
if (vm.description()) {
m.request({
method: "POST",
url: "/todo",
data: {id:t.id,description: vm.description()}
}).then(function (t) {
vm.list.push(new todo.Todo({id:t.id,description: t.description}));
vm.description("");
},function(error){
console.log("error ");
});
}
};
vm.update = function (task) {
m.request({
method: "POST",
url: "/todo",
data: {id: task.id(),
description:task.description(),
done:task.done()}
}).then(function () {
console.log("updated");
}, function (error) {
console.log("error ");
});
};
}
return vm
}())
todo.controller = function () {
todo.vm.init()
}
todo.view = function () {
return m("div", [
m("input", {onchange: m.withAttr("value", todo.vm.description), value: todo.vm.description(),}),
m("button", {onclick: todo.vm.add}, "Add"),
m("table", [
todo.vm.list.map(function (task, index) {
return m("tr", [
m("td", [
m("input[type=checkbox]",
{
onclick:function (e) {
task.done(e.target.checked)
todo.vm.update(task)
}
,checked: task.done()
})
]),
m("td", {style: {textDecoration: task.done() ? "line-through" : "none"}}, task.description()),
])
})
])
]);
};
m.mount($contents, {controller: todo.controller, view: todo.view});
</script>
</body>
</html>
- mithrilの置き場 $GOPATH/static/mithril.js
実行
rerun app/main
感想
かなりすっきりした。