Go言語(Golang)でMVCモデルを実現する 後編

はじめに

この記事は以下の記事の続きになります
https://bsblog.casareal.co.jp/archives/4822

この記事では、Go言語(以下、Golangと表記)でのWEBアプリケーション構築の習得を目的に、MVCモデルで開発を進めることを考え、VC部分に続き、M部分の実装を行いました。

用意した環境

  • OS: Windows10
  • Golang: 1.15.2
  • フレームワーク:Gin

環境変数のGOPATHは以下で設定してあります。
GOPATH = C:\Users\murai\Documents\GolangProject

環境構築が必要であれば前々回の記事を参考にしてください。
https://bsblog.casareal.co.jp/archives/4582

この記事での目標

簡易的な会員管理システムを作るにあたって、
前回は各画面の表示ができるところまで実装を行いました。

そこで今回はMysqlからデータ取得、登録、編集する機能を作成し、
Golangを使用した簡易的な会員管理システムの完成を目標とします。

ディレクトリ構成

この記事で使用するプロジェクトのディレクトリ構成は以下になります。

GolangProject
  - src
    - github.com
      - gin-gonic
        - gin                   // ginフレームワーク
      - murai
        - go-user-management    // プロジェクトフォルダ
          - controller
            - top
              - index.go
            - user
              - add.go
              - edit.go
              - list.go
          - server
            - router.go
          - model
            - user.go
          - view
            - index.html
            - user-add.html
            - user-edit.html
            - user-list.html
          main.go

Mysqlを使う準備

GolangでMysqlを使うにあたってドライバーのインストールが必要になるため、
次のコマンドでインストールを行いました。

go get -u github.com/go-sql-driver/mysql

DBの準備

使用するDBのスキーマは「go」、テーブルは「user」としました。

CREATE TABLE `go`.`user` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(20) NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `go`.`user` (`id`, `name`) VALUES ('1', '村井 太郎');
INSERT INTO `go`.`user` (`id`, `name`) VALUES ('2', '村井 次郎');

Modelを作成する

MVCのMの部分にあたるModelを作成しました。
機能としては以下になります。

  • 会員一覧画面で使用する全会員のリスト取得
  • 会員編集画面で使用する指定した会員の情報取得
  • 会員編集時のレコード更新
  • 会員追加時のレコード追加

model/user.go

package model

import (
        "strconv"
        "database/sql"
        _ "github.com/go-sql-driver/mysql"
)

// DBの情報
const username = "root"
const password = "password"
const schema = "go"

// 使用するカラムを定義
type User struct {
	id int
	name string
}

// 会員の一覧を取得するメソッド
func GetUserList()(map[int]string) {
	// DBとの接続
	db, err := sql.Open("mysql", username + ":" + password + "@tcp(127.0.0.1:3306)/" + schema)
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	// クエリを実行
	rows, err := db.Query("select id, name from user order by id")
	if err != nil {
		panic(err.Error())
	}
	defer rows.Close()

	// メソッドの呼び出し元に返す配列を定義
	list := make(map[int]string)

	// 一行ずつアクセスして配列に格納
	for rows.Next() {
		var user User

		err := rows.Scan(&user.id, &user.name)
		if err != nil {
            panic(err)
		}

		list[user.id] = user.name
	}

	// 無事に終了していることを確認
	err = rows.Err()
        if err != nil {
            panic(err)
        }

	return list
}

// 会員の情報を取得するメソッド
func GetUserData(id string)(map[string]string) {
	// DBとの接続
	db, err := sql.Open("mysql", username + ":" + password + "@tcp(127.0.0.1:3306)/" + schema)
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	// プリペアードステートメントを利用してクエリを実行
	rows, err := db.Query("select id, name from user where id = ?", id)
	if err != nil {
		panic(err.Error())
	}
	defer rows.Close()

	data := make(map[string]string)

	// 一行ずつアクセスして配列に格納
	for rows.Next() {
		var user User

		err := rows.Scan(&user.id, &user.name)
		if err != nil {
            panic(err)
		}

		data["id"] = strconv.Itoa(user.id)
		data["name"] = user.name
	}

	// 正常にに終了していることを確認
	err = rows.Err()
        if err != nil {
            panic(err)
        }

	return data
}

// 会員の情報を更新するメソッド
func EditUserData(id string, name string) {
	// DBとの接続
	db, err := sql.Open("mysql", username + ":" + password + "@tcp(127.0.0.1:3306)/" + schema)
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	// クエリを定義
	update, err := db.Prepare("update user set name = ? where id = ?")
	if err != nil {
		panic(err.Error())
	}
	defer update.Close()

	// プリペアードステートメントを利用してクエリを実行
	_, err = update.Exec(name, id)
	if err != nil {
		panic(err.Error())
	}
}

// 新しく会員を追加するメソッド
func AddUserData(name string) {
	// DBとの接続
	db, err := sql.Open("mysql", username + ":" + password + "@tcp(127.0.0.1:3306)/" + schema)
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	// クエリを定義
	insert, err := db.Prepare("insert user(name) values(?)")
	if err != nil {
		panic(err.Error())
	}
	defer insert.Close()

	// プリペアードステートメントを利用してクエリを実行
	_, err = insert.Exec(name)
	if err != nil {
		panic(err.Error())
	}
}

会員一覧画面の修正

controller/user/list.go

作成したModelをインポートし、
会員の一覧を取得するメソッドを呼び出す記述を加えました。

package user

import (
    "github.com/gin-gonic/gin"
    "github.com/murai/go-user-management/model"
)

func ListDisplayAction(c *gin.Context){
    c.HTML(200, "user-list.html", gin.H{
        // モデルでテーブルから会員全てを取得
        "list": model.GetUserList(),
    })
}

view/user-list.html

controllerから会員リストの変数を受け取って、
一行ごとに描画する処理を追記しました。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>会員一覧</title>
    <!-- 最低限のCSS -->
    <style>
        table {
            width: 25%;
        }
        .user {
            text-align:center;
        }
    </style>
</head>
<body>
    <h1>会員一覧</h1>
    <table border="1">
        <tr>
            <th></th>
            <th>id</th>
            <th>名前</th>
        </tr>
        <!-- 変数「list」の要素数の数だけ回す -->
        {{range $i, $v := .list}}
        <tr class="user">
            <td><a href="/user/edit/{{$i}}">編集</a></td>
            <td>{{$i}}</td>
            <td>{{$v}}</td>
        </tr>
        {{end}}
    </table>
</body>
</html>

実際に表示されたのが次の画像です。
DBから正しく取得できているのが確認できました。

会員編集画面の修正

router.go

会員の編集処理を実行するルートを作っておきます。

package server

import (
    "github.com/gin-gonic/gin"
    "github.com/murai/go-user-management/controller/user"
    "github.com/murai/go-user-management/controller/top"
)

func GetRouter() *gin.Engine {
    router := gin.Default()
    router.LoadHTMLGlob("view/*.html")

    router.GET("/", top.IndexDisplayAction)
    router.GET("/user", user.ListDisplayAction)
    router.GET("/user/add", user.AddDisplayAction)
    router.GET("/user/edit/:id", user.EditDisplayAction)

    // 編集処理
    router.POST("/user/edit/complete", user.EditCompleteAction)

    return router
}

controller/user/edit.go

作成したModelをインポートし、
指定会員の情報を取得するメソッドを呼び出す記述を加えました。
また、会員の編集を実行するメソッドを追加しました。

package user

import (
	"net/http"
	"github.com/gin-gonic/gin"
	"github.com/murai/go-user-management/model"
)

func EditDisplayAction(c *gin.Context){
	id := c.Param("id")
    c.HTML(200, "user-edit.html", gin.H{
		// モデルでテーブルから指定会員の情報を取得
		"user": model.GetUserData(id),
	})
}

// 編集処理
func EditCompleteAction(c *gin.Context){
	id := c.PostForm("id")
	name := c.PostForm("name")

	// モデルでテーブルへの変更
	model.EditUserData(id, name)

	// 会員一覧にリダイレクト
	c.Redirect(http.StatusSeeOther, "/user")
}

view/user-edit.html

controllerから会員情報の変数を受け取って、
formの項目に使用する処理を追記しました。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>会員編集</title>
</head>
<body>
    <h1>会員編集</h1>
    <p>会員ID:{{.user.id}}の編集ページです。</p>
    <form action="/user/edit/complete" method="POST">
        <label>会員ID</label>
        <input type="text" name="id" value='{{.user.id}}' readonly>
        <br>
        <label>名前</label>
        <input type="text" name="name" value='{{.user.name}}'>
        <br>
        <input type="submit" value="更新">
    </form>
</body>
</html>

実際に表示されたのが次の画像です。
DBから正しく取得できているのが確認できました。

会員の編集を行った後の画面が次の画像です。
指定の会員の名前が編集できていることが確認できました。

会員追加画面の修正

router.go

会員の追加処理を実行するルートを作っておきます。

package server

import (
    "github.com/gin-gonic/gin"
    "github.com/murai/go-user-management/controller/user"
    "github.com/murai/go-user-management/controller/top"
)

func GetRouter() *gin.Engine {
    router := gin.Default()
    router.LoadHTMLGlob("view/*.html")

    router.GET("/", top.IndexDisplayAction)
    router.GET("/user", user.ListDisplayAction)
    router.GET("/user/add", user.AddDisplayAction)
    router.GET("/user/edit/:id", user.EditDisplayAction)

    // 編集処理
    router.POST("/user/edit/complete", user.EditCompleteAction)
    // 追加処理
    router.POST("/user/add/complete", user.AddCompleteAction)

    return router
}

controller/user/add.go

作成したModelをインポートし、
また、会員の登録を実行するメソッドを追加しました。

package user

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/murai/go-user-management/model"
)

func AddDisplayAction(c *gin.Context){
    c.HTML(200, "user-add.html", gin.H{})
}

// 登録処理
func AddCompleteAction(c *gin.Context){
    name := c.PostForm("name")

    // モデルでテーブルへの登録
    model.AddUserData(name)

    // 会員一覧にリダイレクト
    c.Redirect(http.StatusSeeOther, "/user")
}

view/user-add.html

会員の登録情報を送るためのformを追記しました。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>会員追加</title>
</head>
<body>
    <h1>会員追加</h1>
    <form action="/user/add/complete" method="POST">
        <label>会員IDは自動で割り当てられます</label>
        <br>
        <label>名前</label>
        <input type="text" name="name">
        <br>
        <input type="submit" value="追加">
    </form>
</body>
</html>

実際に表示されたのが次の画像です。

会員の追加を行った後の画面が次の画像です。
新しく会員が追加できていることが確認できました。

まとめ

最低限ではありますがGolangを使って、
会員のリスト表示、編集、追加ができる会員管理システムを作成することができました。
まだまだ見た目も機能も最低限で実際に運用できるレベルのものではありませんが、
基盤はできたので、ここから肉付けを行っていけば、
実際に運用できるレベルのシステムは作れそうだと感じています。

ここまで作ってみた感想としては、
Golangは、普段PHPを使っている自分にとっては新鮮味がありました。
連想配列に宣言した型しか入れられなかったり(idを文字列化しないといけないなど)、
自作パッケージのインポートがなかなかうまくいかなかったことに一番手を焼きました。。。
今回、Golangのみで完結するWebシステムを作ろうと進めてきましたが、
終わってみると、Golangは一応Webシステムも作れますが、
viewを使わないで、APIとしての運用の方が向いていそうに感じました。


--------------------------
システム開発のご要望・ご相談はこちらから

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です