シェルスクリプトマガジン

香川大学SLPからお届け!(Vol.78掲載)

著者:樋口 史弥

アプリケーションやサービスは、ユーザーの意見や技術状況に合わせた変更をしやすいように開発するのが理想的です。そうした開発を実現するための設計手法の一つが「クリーンアーキテクチャ」です。今回は、クリーンアーキテクチャを用いて簡単な匿名掲示板を作る方法を解説します。

シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。

図4 投稿削除機能を追加した場合のコード例

// メッセージを表す構造体
type struct Message {
(略)
}
// 全メッセージを取得する関数
func (m *Message)GetAllMessages() error {
(略)
}
// メッセージを投稿する関数
func (m *Message)Post() error {
(略)
}
// メッセージを削除する関数
func (m *Message)Delete() error {
(略)
}

図6 単一責任原則に沿ったコードの例

// メッセージを表す構造体
type struct Message {
(略)
}
// 一般ユーザーを表す構造体
type struct User {
    Message
(略)
}
// メッセージを投稿する関数
func (u *User)Post() error {
(略)
}
// 全メッセージを取得する関数
func (u *User)GetAllMessages() error {
(略)
}
// 管理者を表す構造体
func type struct Administrator {
    Message
(略)
}
// メッセージを削除する関数
func (a *Administrator)Delete() error {
(略)
}

図8 モジュールに依存したコードの例

func (m *Message)Post() error {
(略)
    // MySQLHandlerモジュールのExecute関数を直接実行
    err := MySQLHandler.Execute(sql)
(略)
}

図10 モジュールに依存したコードの例

type struct Message {
 (略)
    i DBInterface
}
func (m *Message)Post() error {
(略)
    // MySQLHandlerモジュールのExecute関数を
    // 直接実行せず、DBInterface経由で実行
    err := m.i.Execute(sql)
(略)
}

図19 messageモジュールのコード

type Message interface {
    // メッセージの文字列を返却する関数
    Message() string
}
type message struct {
    // メッセージの文字列を格納するメンバー
    detail string
}

図20 投稿時刻やハンドルを付加する場合のmessageモジュールのコード

type Message interface {
    // メッセージの文字列を返却する関数
    Message() string
    // 投稿したユーザーのハンドルを返却する関数
    HandleName() string
}
type message struct {
    // メッセージの文字列を格納するメンバー
    detail string
    // 投稿ユーザーのハンドルを格納るメンバー
    handleName string
}

図21 ログイン機能を付加するためのuserモジュールのコード

type User interface {
    // ユーザーIDを返却する関数
    ID() string
    // パスワードが適切かどうかを検査する関数
    MatchPassword(string) bool
}
type user struct {
    id string
    password string
}

図22 メッセージを投稿するというユースケースに対応するコード

type controller struct {
    // データベースに保存するモジュールに
    // アクセスするためのインタフェース
    database db_gateway.DB
}
func (c *controller)Post(m message.Message) error {
    // Messageの中身の検証
    if len(m.Message()) == 0 {
        return MessageNotFound
    }
    // インタフェースを用いてメッセージを保存
    err := c.database.RecordMessage(m)
(略)
}

図23 メッセージを保存するRecordMessageメソッドのコード

type mySQLHandler struct {
    // Frameworks&Drivers層のデータベースクライアントライブラリ
    // のモジュールを呼び出すインタフェース
    database mysql_gateway.MySQLHandler
}
// メッセージを保存するためのハンドラメソッド
func (m *mySQLHandler)RecordMessage(
    message message.Message,
) error {
    // SQL文を生成
    sql := 
    INSERT INTO messages
    (message)
    VALUES (?)
    
    // インタフェースを介してSQL文を実行
    _, err := m.database.Execute(sql, message.Message())
(略)
}

図24 mySQLHandler 構造体のdatabaseメンバーに所属するインタフェースの定義コード

type MySQLHandler interface {
    Query(string, ...interface{}) (Row, error)
    Execute(statement string, args ...interface{}) (Result, error)
}

図25 MySQLHandler インタフェースで定義されるExecute メソッドのコード

import (
(略)
    // データベースクライアントライブラリをインポート
    "database/sql"
)
type mysql struct {
    // データベースに問い合わせるためのコネクション
    Conn *sql.DB
}
func (handler *mysql) Execute(
    statement string,
    args ...interface{},
) (worker.Result, error) {
    res := new(SQLResult)
    // コネクションを用いてSQL文を実行
    result, err := handler.Conn.Exec(statement, args...)
(略)
    res.Result = result
    return res, nil
}

図26 匿名掲示板のMainコンポーネントのコード

func main() {
    // External Interfaceのdatabaseモジュールを初期化
    mysqlDB, f, err := database.NewMySQL(db_user, db_password, 
                                         db_ip, db_port, db_name)
(略)
    // Interface Adaptersのdb_handlerモジュールを初期化
    DBHandler := db_handler.NewMySQL(mysqlDB)
    // Application Business Rulesのcontrollerモジュールを初期化
    controller := interactor.NewController(DBHandler)
    // External Interfaceのserverモジュールを初期化
    server := server.New(port, controller)
    // Application Business Rulesのinteractorモジュールを初期化
    interactor := interactor.NewInteractor(server)
    // Application Business Rulesに制御を渡す
    e := interactor.Run()
(略)
}

図27 データの保存先を変更する際のMainコンポーネント書き換え箇所

func main() {
    // External Interfaceのdatabaseモジュールを初期化
    textDB, err := database.NewText(TextPath)
(略)
    // Interface Adaptersのdb_handlerモジュールを初期化
    DBHandler := db_handler.New(textDB)
    // Application Business Rulesのcontrollerモジュールを初期化
    controller := interactor.NewController(DBHandler)
    // External Interfaceのserverモジュールを初期化
    server := server.New(port, controller)
    // Application Business Rulesのinteractorモジュールを初期化
    interactor := interactor.NewInteractor(server)
    // Application Business Rulesに制御を渡す
    e := interactor.Run()
(略)
}