🦸🏼‍♂️

Iris(Go フレームワーク)の使い方を最速でマスターする

2022/04/25に公開
2

はじめに

https://www.iris-go.com

Iris ロゴ
Iris のマスコットキャラクターらしい(可愛い)

Iris(アイリス)について

Iris は Go のフレームワークのうちの1つです。

その大きな特徴としてはまず、公式サイト でも『Go フレームワークのなかで最速』と謳っている通り、実行速度が挙げられます。
Iris が公開しているベンチマーク によると、他の主要な Go フレームワークと比較して最も高いスコアを出しています。

Iris ベンチマーク

その他には、クロスプラットフォームであることも挙げられます。
Iris は、上述の高速性をWebサーバーやデスクトップ、スマートフォンなどの環境を問わず発揮することが可能です。

また、MVCモデルをサポートするための機能が組み込まれていることなども挙げられますが、
他にも多くの機能が搭載されており、さらに ドキュメント[1] も充実していることなどから効率良く開発が行えます。

Iris を使ってみる(準備)

早速使っていきます。

1. Go をインストール

Go の公式サイト からインストールするか、brew を使用してインストールします。
(ここでは Go 1.21 を使用することを想定しています。)

$ brew install go
# 確認
$ go version
go version go1.21.2

2. Iris をインストール

$ mkdir myapp
$ cd myapp
$ go mod init myapp
$ go get -u github.com/kataras/iris/v12

※ その他参考はこちら

Iris を使ってみる(基本編)

準備が完了したら、Iris を使って簡単なAPIを作成してみます(※ MVCパターンにしない場合)

main.go
package main

import "github.com/kataras/iris/v12"

func main() {
    app := iris.New()

    // ルーティングの設定
    app.Handle("GET", "/", func(ctx iris.Context) {
        ctx.JSON(iris.Map{"message": "ping"})
    })
	
    app.Listen(":8080")
}

go run main.go でサーバーを立ち上げて localhost:8080 にアクセスすると、JSONで {"message":"ping"} が返るかと思います。

上記が基本的な書き方ですが、Iris ではさらに ルーティングをパスごとにグループ化することができます。
これを使いながら上記のコードの func(ctx iris.Context) {...} 箇所を関数として切り出すと以下のように書くこともできます。

main.go
package main

import "github.com/kataras/iris/v12"

type Book struct {
    Title string `json:"title"`
}

func main() {
    app := iris.New()

    // 「/books」から始まるパスに対する処理をグループ化する
    booksAPI := app.Party("/books")
    {
        // GET: http://localhost:8080/books
        booksAPI.Get("/", list)
        // POST: http://localhost:8080/books
        booksAPI.Post("/", create)
    }
	
    app.Listen(":8080")
}

func list(ctx iris.Context) {
    books := []Book{
        {"Mastering Concurrency in Go"},
        {"Go Design Patterns"},
        {"Black Hat Go"},
    }
    ctx.JSON(books)
}

func create(ctx iris.Context) {
    var b Book
    err := ctx.ReadJSON(&b)
    if err != nil {
        ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().
            Title("Book creation failure").DetailErr(err))
        return
    }

    println("Received Book: " + b.Title)
    ctx.StatusCode(iris.StatusCreated)
}

再度 go run main.go でサーバーを立ち上げて localhost:8080/books にアクセスすると以下のレスポンスが返るかと思います。

[{"title":"Mastering Concurrency in Go"},{"title":"Go Design Patterns"},{"title":"Black Hat Go"}]

ミドルウェアの使用

Iris には、以下のように様々なミドルウェア設定方法があります。

  • Application.WrapRouter()
  • Application.UseRouter() ≒ Application.Use()
  • Application.UseGlobal()
  • Party.Use()
  • Party.UseError()
  • Party.Done()
  • Application.DoneGlobal()

ミドルウェアを使うことで、例えばCORSのバリデーションや認証、ロギング、エラーハンドリングなどの共通処理を一箇所に集約し再利用することができたり、リクエストの処理を柔軟に制御できたりと多くの利点があります。

ここでの Partyapp.Party() によってグループ化されたパーティー(ルートのグループ)のことです。(例: booksAPI := app.Party("/books")
それぞれ実行されるタイミングや適用範囲、目的などが異なるため、以下(↓)にまとめます。

メソッド 実行タイミング 説明
Application.WrapRouter() ルーター自体をラップし、リクエストがアプリケーション(ルーター)に到達する前に実行される ルーティングが起こる前段階でリクエストに対して何か共通の処理を行いたい場合に使用する
Application.UseGlobal()(≒ Application.Use()) 全ての、アプリケーションにリクエストが到達したタイミングで実行される(優先順位は Application.WrapRouter() の後) グローバルな前処理に適している。(Application.Use()も同じような動作をするが、Application.UseGlobal() は静的ファイルのリクエストやルーター外のリクエストにも適用される。)
Application.UseRouter() アプリケーションに到達したリクエストが、定義されたどれかの(どれでもOK)ルートにマッチしてルーティングが行われる直前に実行される 全ての、リクエストが定義されたルートにマッチしたタイミングで何か前処理を行いたい場合に使用する
Party.Use() 特定のパーティー(ルートのグループ)内のリクエストにのみ実行される 特定のルートのグループに共通の前処理を行いたい場合に使用する
Party.UseError() 特定のパーティー(ルートのグループ)内でエラーが起こった際に実行される 特定のルートのグループ内で発生したエラーに共通の処理を行いたい場合に使用する
Party.Done() 特定のパーティー(ルートのグループ)内のリクエストが処理された直後に実行される 特定のルートのグループに共通の後処理を行いたい場合に使用する
Application.DoneGlobal() アプリケーション全体のリクエストが処理された後に実行される 全てのリクエストの終了後の共通処理を行いたい場合に使用する

さらに、上述の方法を使いカスタムした自作ミドルウェアを設定できることはもちろん、Iris には備え付きミドルウェアも豊富に用意されているためそれらを使用することも可能です。
それぞれのミドルウェアの使い方や機能については 公式リポジトリ公式ドキュメント を参考にしてください。

main.go
package main

import (
    "net/http"

    "github.com/kataras/iris/v12"
)

func main() {
    app := iris.New()

    app.UseRouter(recover.New())
    
    app.Get("/benchmark", MyBenchLogger(), benchEndpoint)

    authorized := app.Party("/")
    authorized.Use(AuthRequired())
    {
        authorized.Post("/login", loginEndpoint)
        authorized.Post("/submit", submitEndpoint)
        authorized.Post("/read", readEndpoint)
    }

    app.Listen(":8080")
}

HTMLファイルを表示する

まずは、app.RegisterView() という Iris の組み込み関数を使い、表示するHTMLファイルが格納されたディレクトリとその中のどの種類のファイル(.html など)を使用するかの指定をします。
これは基本的にはコードのトップレベルで一度だけ行うようにし、指定したディレクトリ配下にディレクトリやファイルを増やしていくようにします。ルーティング中に都度呼ぶのは冗長なので共通化するためなどの理由からです。
(もちろんルーティングによって、またはファイルの種類によってディレクトリを変えるという使い方もできます。その場合は app.RegisterView() を適切に呼び出しましょう。)

その後ルーティングの設定をします。

ディレクトリ構成
myapp
|- public
|- |- books
|- |- |- list.html
|- |- |- purchase.html
|- go.mod
|- main.go
main.go
package main

import "github.com/kataras/iris/v12"

func main()  {
    app := iris.New()
    
    // public ディレクトリ配下の .html ファイルを返却するように設定
    app.RegisterView(iris.HTML("/public", ".html"))
    
    // GET: "http://localhost:8080/"
    app.Get("/", func(ctx iris.Context) {
        // 返却するHTMLファイルを public ディレクトリからのパスで指定する)
        ctx.View("index.html")
    })

    // 「/books」から始まるパスを受け取る処理をグループ化
    // ファイルは public ディレクトリからのパスで指定
	books := app.Party("/books")
	{
		// GET: http://localhost:8080/books
		books.Get("/", func(ctx iris.Context) {
			err := ctx.View("/books/list.html")
			if err != nil {
				return
			}
		})
		// GET: http://localhost:8080/books/purchase
		books.Get("/purchase", func(ctx iris.Context) {
			err := ctx.View("/books/purchase.html")
			if err != nil {
				return
			}
		})
	}
    
    app.Listen(":8080")
}

go run main.go でサーバーを立ち上げて localhost:8080 にアクセスすると、public/index.html の内容が確認できるかと思います。

静的ファイルを表示する

同じように、Iris の組み込み関数である app.HandleDir() を使用して、

api.HandleDir("URI", iris.Dir("表示したいファイルや画像があるディレクトリまでのパス"), iris.DirOptions{表示するファイルに関する設定})

と設定できます。

main.go
package main

import "github.com/kataras/iris/v12"

func main()  {
    app := iris.New()
    
    // 静的ファイルの表示
    // public ディレクトリ配下の index.html ファイルを指定する
    app.HandleDir("/", iris.Dir("/public"), iris.DirOptions{IndexName: "/index.html", Compress: true})
    
    app.Listen(":8080")
}

go run main.go でサーバーを立ち上げて localhost:8080 をアクセスすると、public ディレクトリ配下の index.html の内容が表示されているかと思います。
もちろんその他の任意のファイルや画像などに関しても指定できます。

※ その他の使用例はこちら

ファイルのアップロード

ファイルのアップロード機能に関しては、

  • ctx.FormFile()
  • ctx.FormFiles()
  • ctx.SaveFormFile()
  • ctx.UploadFormFiles()

などが備わっています。

メソッド 機能
ctx.FormFile() 単数ファイル読み込み
ctx.FormFiles() 複数ファイル読み込み
ctx.SaveFormFile() 単数ファイル保存(アップロード)
ctx.UploadFormFiles() 単数ファイル・複数読み込み + 保存(アップロード)

詳しくはこちら

ctx.FormFile()ctx.FormFiles() はリクエストからそれぞれ単数ファイル・複数ファイルを読み込みます。
読み込んだファイルは ctx.SaveFormFile() によって指定したパスへと保存が可能ですが、このメソッドは単数ファイルのみしか受け取ることができません。

したがってアップロードされた複数ファイルを保存するには ctx.UploadFormFiles() を使う必要があります。
ctx.UploadFormFiles() は、ctx.FormFile() / ctx.FormFiles() を使った単数・複数ファイル読み込みと ctx.SaveFormFile() の保存を一気に行うことができます。
ただし、ファイルを読み込んだ後にそれを使った何らかの処理や操作を行い、その後に保存する、といったことができません。

ですので、ファイルをアップロードしたい場合はなるべく ctx.FormFile() による単数ファイルのアップロード + ctx.SaveFormFile() による保存の組み合わせを使うのが変更への対応がしやすくベターといえます。

main.go
const maxSize = 8 * iris.MB // 8MB

func main() {
    app := iris.New()

    // アプリケーション上で読み込めるファイルの最大容量はデフォルトで32MBですが、以下の方法によって変えることができます。 
    // 1. ミドルウェアとして設定する 
    app.Use(iris.LimitRequestBodySize(maxSize)
    // 2. ルーティング内で ctx.SetMaxRequestBodySize() を使い設定する(下参考↓)
    // 3. アプリケーション起動時に iris.WithPostMaxMemory() を使い設定する(下参考↓)

    // 単数ファイル読み込み + 保存
    app.Post("/upload", func(ctx iris.Context) {
        ctx.SetMaxRequestBodySize(maxSize)
		
        file, fileHeader, err:= ctx.FormFile("file")
        if err != nil {
            ctx.StopWithError(iris.StatusBadRequest, err)
            return
        }
        // 保存先を作成しておく
        // ここでは destination に "./public/ファイル名" というパスが文字列で格納される
        destination := filepath.Join("./public", fileHeader.Filename)
        // 保存処理
        ctx.SaveFormFile(fileHeader, destination)
    })

    // 複数ファイル読み込み + 保存
	app.Post("/upload", func(ctx iris.Context) {
		// 読み込み + 保存処理
        files, n, err := ctx.UploadFormFiles("./uploads")
		if err != nil {
            ctx.StopWithStatus(iris.StatusInternalServerError)
            return
		}
	})

    app.Listen(":8080", iris.WithPostMaxMemory(maxSize))
}

リクエストからクエリパラメータを取得

クエリパラメータはURLの一部として送信されるキーと値のペアです。これらは通常URLの末尾で「?」の後に追加され、複数のクエリがある場合は「&」で区切られます。

http://example.com/products?category=books&sort=asc

クエリパラメータの取得には、

  • ctx.ReadQuery()
  • ctx.URLParam()

などを使用します。

main.go
package main

import "github.com/kataras/iris/v12"

type MyType struct {
    // "url" タグでクエリ名を指定しています
    Name string `url:"name,required"`
    Age  int    `url:"age"`
}

func main()  {
    app := iris.New()
    
    // GET: "http://localhost:8080/users?name=tom&?age=19"
    app.Handle("GET", "/users", func(ctx iris.Context) {
        var t MyType
        // ReadQuery()はリクエストを解析しそこからクエリパラメータを取得し、
        // 受け取ったデータを構造体(struct)に格納します。
        err := ctx.ReadQuery(&t)
        
        // 処理...
    })
    
    // GET: "http://localhost:8080/users?name=tom&?age=19"
    app.Handle("GET", "/users", func(ctx iris.Context) {
        // クエリパラメータ "name" の値を取得
        name := c.Ctx.URLParam("name")
        
        // クエリパラメータ "age" の値を取得
        name := c.Ctx.URLParam("age")
        
        // 処理...
    })
    
    app.Listen(":8080")
}

リクエストからパスパラメータを取得

パスパラメータはURLのパス部分に埋め込まれた動的な値です。たとえば「/users/{id}」のようなルート定義における「{id}」がパスパラメータです。

http://example.com/users/1

パスパラメータの取得には

  • ctx.Params().Get()

を使用します。

main.go
package main

import "github.com/kataras/iris/v12"

func main()  {
    app := iris.New()
    
    // GET: "http://localhost:8080/users/1"
    app.Handle("GET", "/users/{id:int}", func(ctx iris.Context) {
        // パスパラメータ "id" の値を取得
        id, _ := ctx.Params().Get("id")
        
        // 処理...
    })
}

Iris を使ってみる(MVC編)

さて、基本編 の中で使用しているコードはどれも以下のように、受け取るURLとその後の処理を”ベタ書き”したものに過ぎません。

app.Handle("GET", "/", func(ctx iris.Context) {
	ctx.JSON(iris.Map{"message": "ping"})
})

アプリケーションが大きくなるにつれてビジネスロジックは肥大化するため、本来は今回実装するMVCモデルやDDD、何らかのアーキテクチャを導入しアプリケーションの各部分の責務を明確に分離するべきです。
それにより、再利用性と拡張性が向上し保守が容易になるためです。

Iris(アイリス)について でも説明した通り、Iris には、MVCモデルをサポートするための機能が組み込まれています。
ここではそれを使い、簡単なMVCモデルのAPIを作ってみます。

実装機能

ユーザーに対するCRUD機能を実装します。

機能 メソッド URI
新規作成 POST http://localhost:8080/users (jsonのリクエストボディでデータを送る)
一覧取得 GET http://localhost:8080/users/list
更新 PUT http://localhost:8080/users/details/{id:uint}
削除 DELETE http://localhost:8080/users/details/{id:uint}

ディレクトリ構成

example-mvc-api
|- controller
|- |- users
|- |- |- users_controller.go
|- setups
|- |- configure_users_controllers.go  // 依存性注入(DI)を行う箇所
|- model
|- |- user.go
|- service
|- |- user.go
|- go.mod
|- main.go

また、今回DBとのやりとりを行うためのORMに関しては gorp パッケージを想定しています。

main.go

ここでのポイントは、mvc.Configure(users, setups.ConfigureUsersControllers) の箇所です。
これは github.com/kataras/iris/v12/mvc パッケージの Configure() メソッドを使用しています。mvc.Configure() は Iris のMVCパターンを利用するためのメソッドで、主に特定のパスグループに対してコントローラーやロジックをマッピングすること及び依存性の注入に使用されます。
実際のコード を見れば分かりますが、内部的に、同じく github.com/kataras/iris/v12/mvc パッケージ内の mvc.New("パスグループ").Configure() を実行しているため、そう書いても同じです。

iris.New() によって得られる Iris 本体のインスタンスを使用して、手動で全パス毎のコントローラーやモデル、ビューの関連付けと管理を行えばMVCパターンの実装は可能ですが、
mvc.Configure() はコントローラーの初期化や依存関係の注入など、多くのプロセスを自動化します。つまりそちらを使用する方が、MVCパターンの実装がより簡単で効率的になります。特に、大規模なアプリケーションや複雑なビジネスロジックを持つプロジェクトでは mvc.Configure() の方が適切なアプローチといえるでしょう。

main.go
package main

import (
    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/middleware/accesslog"
    "github.com/kataras/iris/v12/middleware/recover"
    "github.com/kataras/iris/v12/mvc"
	
    "go-iris-sample/setups"
)

func main() {
    app := iris.New()

    app.UseRouter(recover.New())

    // 「/users」から始まるパスを受け取る処理をグループ化
    users := app.Party("/users")
    mvc.Configure(users, setups.ConfigureUsersControllers)

    // ポートの指定
    app.Listen(":8080")
}

setups/configure_users_controllers.go(依存性注入(DI)を行う)

API開発において、特定のパスグループ(例えば「/users」)で特定のコントローラーやサービス(例えば UsersController や UserService)を使うように設定し、関連しないコントローラーやサービス(例えば BooksController や AnimalController)が誤って動作しないようにすることは、一般的に良い設計のプラクティスです。
Iris でMVCモデルを形成する際にも、作成したパスグループに対して使用する controller やそこで使用する service、またはDB設定などをの依存性注入(DI)を行う必要があります。
今回はそれをこの setups ディレクトリで行なっています(ディレクトリの名前は何でも大丈夫です)

setups/configure_users_controllers.go
import (
    "github.com/kataras/iris/v12/middleware/accesslog"
    "github.com/kataras/iris/v12/mvc"
    "go-iris-sample/controller/users"
    "go-iris-sample/service/users"
)

// ConfigureUsersControllers
// main.go で定義した特定のパスグループ(ここでは「/users」)に対して必要な controller や service、及びDB接続設定などのDI(依存性注入)を行っている
func ConfigureUsersControllers(app *mvc.Application) {
    setBaseConfiguration(app)
    app.Register(users.NewUserService)
    app.Handle(new(users.UsersController))
}

func setBaseConfiguration(app *mvc.Application) {
	// DB初期化
	db, err := initDb()
	if err != nil {
		log.Fatalf("func %s failed: %v", "InitDb", err)
	}
	app.Register(db)
}

// gorp初期化処理
func initDb() *gorp.DbMap {
    dbUserName := "DB_USERNAME"
    dbPassWord := "DB_PASSWORD"
    dbHost := "DB_HOST"
    dbPort := "DB_PORT"
    dbDbName := "DB_DBNAME"
    db, err := sql.Open("mysql", fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?parseTime=true", dbUserName, dbPassWord, dbHost, dbPort, dbDbName))
    if err != nil {
        fmt.Printf("error! can't open sql driver %v", err)
    }
    dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}
    
   // テーブル作成
    dbmap.AddTableWithName(model.User{}, "user").SetKeys(true, "Id")
    err = dbmap.CreateTablesIfNotExists()
    if err != nil {
        fmt.Printf("error! can't craete table: %v", err)
    }
    
    // ログの取得
    dbmap.TraceOn("[gorp]", log.New(os.Stdout, "go-iris-sample:", log.Lmicroseconds))
    
    return dbmap
}

controller/users/users_controller.go

リクエストを受け UserService に命令を行います。

さて、main.go で「/users」のパスグループを作りましたが、ここで今回作成するAPIのエンドポイント名(受け取るURL)を再び確認してみましょう。

  • /users
  • /users/list
  • /users/details/{id:uint}
  • /users/details/{id:uint}

どれも「/users」からパスが始まりますが、「/users/list」や「/users/details」などのようにURLは分岐しています。

作成したパスグループの中でさらに、基本編 で書いたコードのように、app.Get("GET", "/users/list", 処理...)app.Get("GET", "/users/details", 処理...) というようにURLとそれに紐づける処理を”ベタ書き”した関数をたくさん作っていくのは大変です。
かといって「/users」のような同じパスでのみしか GET/POST/PUT/DELETE... などの処理を作れないとなると拡張性が低いです。

Iris(のMVCサポート機能)では、URLルートを紐づけられたコントローラーのアクションにマッピングする機能が提供されています。main.go にて行った、mvc.Configure() がそれを可能にしています。
具体的には、パスグループに紐づけられたコントローラーのメソッド名を変えることによって、同じパスグループ内での細かいパスの違いに対応できます。

例えば main.go などでURLのグルーピングとコントローラーの紐付けをしたのち(今回は「/users」)、GET で /users/list というURLを受け取る際の処理は、紐付けられたコントローラーに GetList() というメソッドを、
POST で /users/details というURLを受け取る際の処理は、紐付けられたコントローラーに PostDetails() というメソッドを作りその中に処理を書きます。
こうすることで、受け取る想定のURLの、app.Party() などで最初にグループ化指定された箇所(今回は「/users」)以降のパス名を名前に持つコントローラーメソッドに Iris が自動でアクションをマッピングします。

まとめると、例えば main.go などでURLのグルーピングとコントローラーの紐付けをしたのち(今回は「/users」)、

  • GET で /users/list というURLでリクエストがきた場合、Iris(のMVCサポート機能)が自動でそれを判別して紐付けられたコントローラーの GetList() という名前のメソッドの処理が行われる
  • POST で /users/details というURLでリクエストがきた場合、Iris(のMVCサポート機能)が自動でそれを判別して紐付けられたコントローラーの PostDetails() という名前のメソッドの処理が行われる

となります。

controller/users/users_controller.go
package users

import (
    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/mvc"
    "go-iris-sample/model"
    "go-iris-sample/service"
)

type UsersController struct {
	UserService service.UserService
	Ctx         iris.Context
}

// メソッド名でパスの違い・リクエストメソッド・パラメータを受け付けます
// GetList()                GET: "http://localhost:8080/users/list"
// Post()                   POST: "http://localhost:8080/users"
// PutDetails()             PUT: "http://localhost:8080/users/details"
// PutDetailsBy(id uint)    PUT: "http://localhost:8080/users/details/{id:int}"
// DeleteDetails()          DELETE: "http://localhost:8080/users/details"
// DeleteDetailsBy(id uint) DELETE: "http://localhost:8080/users/details/{id:int}"
//
// その他の例
// POST: http://localhost:8080/users/details/example -> PostDetailsExample()
// PUT: http://localhost:8080/users -> Put()

// GetList 一覧取得
func (c *UsersController) GetList() mvc.Response {
    users, err := c.UserService.GetUserList()
    if err != nil {
        return mvc.Response{
            Code: iris.StatusInternalServerError,
        }
    }
    
    // Iris に備え付きのレスポンス用構造体(struct)
    return mvc.Response{
        Code:   iris.StatusOK,
        Object: users,
    }
}

// Post 新規作成
func (c *UsersController) Post() mvc.Response {
    // リクエストボディの json データを読み取り、構造体(struct)に格納する
    var user model.User
    err := c.Ctx.ReadJSON(&user)
    if err != nil {
        c.Ctx.StopWithError(iris.StatusBadRequest, err)
        return mvc.Response{
            Code: iris.StatusBadRequest,
        }
    }
    
    err = c.UserService.CreateUser(&user)
    if err != nil {
        return mvc.Response{
            Code: iris.StatusInternalServerError,
        }
    }
    
    // Iris 備え付きのレスポンス用構造体(struct)
    return mvc.Response{ Code: iris.StatusCreated }
}

// PutDetailsBy 更新
func (c *UsersController) PutDetailsBy(id int) mvc.Response {
    user.Id = id
    err = userService.UpdateUser(&user)
    if err != nil {
        return mvc.Response{
            Code: iris.StatusInternalServerError,
        }
    }
    
    // Iris 備え付きのレスポンス用構造体(struct)
    return mvc.Response{ Code: iris.StatusOK }
}

// DeleteDetailsBy 削除
func (c *UsersController) DeleteDetailsBy(id int) mvc.Response {
    err := c.UserService.DeleteUser(id)
    if err != nil {
        return mvc.Response{
            Code: iris.StatusInternalServerError,
        }
    }
    
    // Iris 備え付きのレスポンス用構造体(struct)
    return mvc.Response{ Code: iris.StatusOK }
}

model/user.go

User 構造体(struct)を定義しています。
db タグはORMを使う際にDBのテーブルのどのカラムに値をマッピングさせるかの指示です。

model/user.go
package model

type User struct {
    Id    string  `json:"id"   db:"id"`
    Name  string  `json:"name" db:"name"`
    Age   uint32  `json:"age"  db:"age"`
}

service/user.go

コントローラーからハンドリングされる処理を行います。

service/user.go
package service

import (
    "go-iris-sample/_example-mvc-api/model"
    
    _ "github.com/go-sql-driver/mysql"
)

type UserService struct {
	db *gorm.DB
}

func NewUserService(db *gorm.DB) UserService {
	return UserService{db: db}
}

// GetUserList ユーザーを全取得
func (s UserService) GetUserList() ([]model.User, error) {
    var users []model.User
    
    _, err := s.db.Select(&users, `SELECT * FROM users`)
    if err != nil {
        return []model.User{}, err
    }
    
    return users, nil
}

// CreateUser 作成
func (s UserService) CreateUser(user *model.User) error {
    tx, _ := s.db.Begin()
    
    err := tx.Insert(user)
    if err != nil {
        tx.Rollback()
        return err
    }
    
    tx.Commit()
    
    return nil
}

// UpdateUser 更新
func (s UserService) UpdateUser(user *model.User) error {
    tx, _ := s.db.Begin()
    
    _, err := tx.Update(user)
    if err != nil {
        tx.Rollback()
        return err
    }
    
    tx.Commit()
    
    return nil
}

// DeleteUser 削除
func (s UserService) DeleteUser(id int) error {
    // id から削除するユーザーを取得
    var user model.User
    err := s.db.SelectOne(&user, `SELECT * FROM users WHERE id = :id`,
        map[string]interface{}{
            "id": id,
        })
    if err != nil {
        fmt.Printf("error! can't find user by id: %v.\n", id)
        return err
    }
    
    tx, _ := s.db.Begin()
    
    _, err := tx.Delete(&user)
    if err != nil {
        tx.Rollback()
        return err
    }
    
    tx.Commit()
    
    return nil
}


こんな感じで、Iris に備わった機能を使い効率よくMVCモデルでAPIを作れます。

DB接続設定後 go run main.go でサーバーを立ち上げて、それぞれのエンドポイントに適切なリクエスト内容でアクセスし、正常に動作することを確認してください。

長くなってしまいましたが、以上になります。

脚注
  1. これ以外に GitBook 製の https://docs.iris-go.com/iris もあります。 ↩︎

Discussion

kyoh86kyoh86

差し出がましい指摘ではございますが、過去には作者のライセンス、コミットログ、Contributors、Issue等の扱い方が悪質であったがゆえにawesome-goなどにも掲載を拒否されたことがあるようなフレームワークですので、採用には慎重さが求められます。
現在の作者の動向は私も把握しておりませんが、念の為にご注意を。

mkosakanamkosakana

コメントありがたいです.こちら5,6年ほど前のIrisの割と有名な話ですよね.....現在においては大きな問題はないと思っていますがそこらへんは職場でも非常に注意して進めていきたいです.読んでいただきありがとうございます!🤲🏻