<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
	>

<channel>
	<title>Go | みんたく</title>
	<atom:link href="https://mintaku-blog.net/category/develop/go/feed/" rel="self" type="application/rss+xml" />
	<link>https://mintaku-blog.net</link>
	<description>みんたくの技術ブログ</description>
	<lastBuildDate>Fri, 07 Aug 2020 01:37:51 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.0.11</generator>

<image>
	<url>https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2018/06/cropped-ipad-820272_640.jpg?fit=32%2C32&#038;ssl=1</url>
	<title>Go | みんたく</title>
	<link>https://mintaku-blog.net</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">144480658</site>	<item>
		<title>【Go】GormでシンプルなCRUDのREST APIを実装する</title>
		<link>https://mintaku-blog.net/go-ddd-crud/</link>
					<comments>https://mintaku-blog.net/go-ddd-crud/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Thu, 06 Aug 2020 03:30:20 +0000</pubDate>
				<category><![CDATA[Go]]></category>
		<category><![CDATA[アーキテクチャ]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=1621</guid>

					<description><![CDATA[<p>https://mintaku-blog.net/go-ddd/ 先日上記記事でアウトプットした知識から、より理解を深めるため、ToDoリストをイメージした …</p>
The post <a href="https://mintaku-blog.net/go-ddd-crud/">【Go】GormでシンプルなCRUDのREST APIを実装する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img width="560" height="315" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/574f55832d00032f52803e1e13ae2bb0.png?fit=560%2C315&amp;ssl=1" class="attachment-large size-large wp-post-image" alt="" loading="lazy" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/574f55832d00032f52803e1e13ae2bb0.png?w=560&amp;ssl=1 560w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/574f55832d00032f52803e1e13ae2bb0.png?resize=300%2C169&amp;ssl=1 300w" sizes="(max-width: 560px) 100vw, 560px" data-attachment-id="1608" data-permalink="https://mintaku-blog.net/go-ddd/help-raise-heart-disease-awareness%e3%81%ae%e3%82%b3%e3%83%92%e3%82%9a%e3%83%bc%e3%81%ae%e3%82%b3%e3%83%92%e3%82%9a%e3%83%bc%e3%81%ae%e3%82%b3%e3%83%92%e3%82%9a%e3%83%bc-19-2/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/574f55832d00032f52803e1e13ae2bb0.png?fit=560%2C315&amp;ssl=1" data-orig-size="560,315" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Help raise Heart Disease Awarenessのコピーのコピーのコピー (19)" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/574f55832d00032f52803e1e13ae2bb0.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/574f55832d00032f52803e1e13ae2bb0.png?fit=560%2C315&amp;ssl=1" />			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://mintaku-blog.net/go-ddd/">【Go】DDD + レイヤードアーキテクチャでREST APIを実装する</a>
			</p>
							<div class="ys-blog-card__dscr">
					Go + DDD + レイヤードアーキテクチャを本やネットで調べ、実際に動かしな&hellip;				</div>
										<div class="ys-blog-card__domain">mintaku-blog.net</div>
					</div>
	</div>
</div>

<p>先日上記記事でアウトプットした知識から、より理解を深めるため、ToDoリストをイメージしたシンプルなCRUDのREST APIをDDD + レイヤードアーキテクチャで実装してみました。</p>
<h2>おさらい</h2>
<p><img data-attachment-id="1601" data-permalink="https://mintaku-blog.net/go-ddd/5-2/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?fit=1920%2C1080&amp;ssl=1" data-orig-size="1920,1080" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="5" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1601" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>レイヤードアーキテクチャの概念であるdomain層を確立させ、そこにドメインロジックを凝集させようという発想に、DDDのDIP(依存関係逆転の原則)を用いることで、上の図のような各レイヤごとの依存関係を意識した実装を行います。</p>
<p>実現したい機能を抽象化し、各レイヤごとの依存関係を意識しながら実装に落とし込んでいく作業が、従来のMVCで書いていた時よりも格段に頭を使うことを実感しています。</p>
<p>自分が実装している範囲内ではまだまだこのアーキテクチャを活かしきれていないですが、少しづつ理解を深めてものにしていきたいです。</p>
<p>&nbsp;</p>
<h2>domain層</h2>
<p><img data-attachment-id="1596" data-permalink="https://mintaku-blog.net/go-ddd/%e6%af%94%e8%bc%83%e3%81%ae%e5%a3%81/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?fit=1920%2C1080&amp;ssl=1" data-orig-size="1920,1080" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="比較の壁" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1596" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>Domain層は、システムが扱う業務領域に関するコードを置くところです。今回ToDoリストで必要なItemモデルを定義します。</p>
<p>今回はシンプルなToDoリストなのでこれだけですが、実際はここに実現したいシステムの業務領域を集約させて、全体の開発に展開させていきます。</p>
<p>・domain/model/item.go</p><pre class="crayon-plain-tag">package model

import (
  "time"

  _ "github.com/jinzhu/gorm/dialects/mysql"
)

type Item struct {
  Id        int       `gorm:"unique;not null"`
  Name      string    `gorm:"size:255"`
  Status    int       `gorm:"not null"`
  CreatedAt time.Time
  UpdatedAt time.Time
}</pre><p>&nbsp;</p>
<p>repositoryはDBとのやりとりを定義しますが、技術的関心ごとはinfrastructure層に書くため、ここではインターフェースとしてメソッドを定義します。</p>
<p>今回のToDoリストでは一覧取得、タスク登録、タスク更新のメソッドを定義します。実際の処理はinfrastructure層に書き、domain層に依存するように実装します。</p>
<p>ちなみに削除も追加しようと思ったのですが、ToDoリストの場合はステータスで判断するため、削除はUpdate()で行われることを想定しています。</p>
<p>もし削除機能を追加する場合は、DeletedAtカラムを追加して論理削除するようにします。Gormだとdeleteするとデフォルトで論理削除し、データ取得時もDeletedAtがnullのレコードを取得するようにしてくれます。</p>
<p>・domain/repository/item.go</p><pre class="crayon-plain-tag">package repository

import (
  "github.com/sample/go-ddd-crud/domain/model"
)

// ItemRepository : Item における Repository のインターフェース
type ItemRepository interface {
  FindAll() (items []*model.Item, err error)
  Create(item *model.Item) (err error)
  Update(item *model.Item) (err error)
}</pre><p>&nbsp;</p>
<h2>infrastructure層</h2>
<p><img data-attachment-id="1599" data-permalink="https://mintaku-blog.net/go-ddd/8-3/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?fit=1920%2C1080&amp;ssl=1" data-orig-size="1920,1080" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="8" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1599" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>infrastructure層ではdomain層に依存するように実装します。</p>
<p>先ほどdomain層でインターフェースとして定義したFindAll()、Create()、Update()の3つの技術的処理を書いていきます。</p>
<p>FindAll()では、ToDoリスト一覧を取得し、その結果を返すようにします。</p>
<p>Create()では、POSTで送られてきたパラメータを元にToDoタスクを登録します。</p>
<p>Update()では、PUTで送られてきたパラメータを元にToDoタスクを更新します。</p>
<p>・infrastructure/persistence/item.go</p><pre class="crayon-plain-tag">package persistence

import (
  "time"

  "github.com/jinzhu/gorm"

  "github.com/sample/go-ddd-crud/domain/model"
  "github.com/sample/go-ddd-crud/domain/repository"
)

type itemPersistence struct {
  Conn *gorm.DB
}

// NewItemPersistence : Item データに関する Persistence を生成
func NewItemPersistence(conn *gorm.DB) repository.ItemRepository {
  return &amp;itemPersistence{Conn: conn}
}

// Search : DB から Item データの全件取得（ItemRepository インターフェースの Index() を実装したもの）
func (ip *itemPersistence) FindAll() (items []*model.Item, err error) {
  // DB接続
  db := ip.Conn

  if err := db.Find(&amp;items).Error; err != nil {
    return nil, err
  }

  return items, nil
}

// Create : DB から Item データの新規登録（ItemRepository インターフェースの Create() を実装したもの）
func (ip *itemPersistence) Create(item *model.Item) (err error) {
  // DB接続
  db := ip.Conn
  now := time.Now()

  // 新規登録用Item生成
  createItem := &amp;model.Item{
    Name: item.Name,
    Status: item.Status,
    CreatedAt: now,
    UpdatedAt: now,
  }

  // 新規登録
  if err := db.Create(&amp;createItem).Error; err != nil {
    return err
  }

  return nil
}

// Update : DB から Item データの更新（ItemRepository インターフェースの Update() を実装したもの）
func (ip *itemPersistence) Update(item *model.Item) (err error) {
  // DB接続
  db := ip.Conn
  now := time.Now()

  // 更新対象のItem取得
  updateItem := &amp;model.Item{}
  if err := db.First(&amp;updateItem, item.Id).Error; err != nil {
    return err
  }

  updateItem.Name = item.Name
  updateItem.Status = item.Status
  updateItem.UpdatedAt = now

  // 更新
  if err := db.Save(&amp;updateItem).Error; err != nil {
    return err
  }

  return nil
}</pre><p>&nbsp;</p>
<h2>usecase層</h2>
<p><img data-attachment-id="1598" data-permalink="https://mintaku-blog.net/go-ddd/attachment/7/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?fit=1920%2C1080&amp;ssl=1" data-orig-size="1920,1080" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="7" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1598" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>今回はシンプルなCRUD処理しかしないので、この層の存在価値がわかりにくいですが、複雑なビジネスロジックがあるときは、この層の存在価値が発揮されるようです。</p>
<p>Itemの取得や登録などでDBにアクセスする時にdomain層のrepositoryを介してアクセスすることによって、infrastructure層ではなくdomain層のみに依存させています。</p>
<p>この層で送られてきたパラメータのバリデートしていますが、実際はこの層でバリデートするのが正しいのかちょっとまだわかっていないです。。</p>
<p>usecase層の責務として、interface層から情報を受け取り、domain層で定義してあるメソッドを用いてビジネスロジックを実行することを考えると、この層で行うのが正しい気がしています。</p>
<p>・usecase/item.go</p><pre class="crayon-plain-tag">package usecase

import (
  "errors"

  "github.com/sample/go-ddd-crud/domain/model"
  "github.com/sample/go-ddd-crud/domain/repository"
)

// ItemUseCase : Item における UseCase のインターフェース
type ItemUseCase interface {
  FindAll() (items []*model.Item, err error)
  Create(status int, name string) (err error)
  Update(id, status int, name string) (err error)
}

type itemUseCase struct {
  itemRepository repository.ItemRepository
}

// NewItemUseCase : Item データに関する UseCase を生成
func NewItemUseCase(ir repository.ItemRepository) ItemUseCase {
  return &amp;itemUseCase{
    itemRepository: ir,
  }
}

// Search : Item データを全件取得するためのユースケース
func (iu itemUseCase) FindAll() (items []*model.Item, err error) {
  // Persistence（Repository）を呼び出し
  items, err = iu.itemRepository.FindAll()
  if err != nil {
    return nil, err
  }
  return items, nil
}

// Create : Item データを新規登録するためのユースケース
func (iu itemUseCase) Create(status int, name string) (err error) {
  // Item 引数から構造体生成
  item := &amp;model.Item{
    Name: name,
    Status: status,
  }

  // バリデーションを呼び出し
  if err := validate(item); err != nil {
    return err
  }

  // Persistence（Repository）を呼び出し
  err = iu.itemRepository.Create(item)
  if err != nil {
    return err
  }
  return nil
}

// Update : Item データを更新するためのユースケース
func (iu itemUseCase) Update(id, status int, name string) (err error) {
  // Item 引数から構造体生成
  item := &amp;model.Item{
    Id: id,
    Name: name,
    Status: status,
  }

  // バリデーションを呼び出し
  if err := validate(item); err != nil {
    return err
  }

  // Persistence（Repository）を呼び出し
  err = iu.itemRepository.Update(item)
  if err != nil {
    return err
  }
  return nil
}

// Validate : Item データをバリデート
func validate(item *model.Item) error {
  if len(item.Name) &gt;= 200 {
    return errors.New("タスク名は200文字未満で書いてください。")
  }
  return nil
}</pre><p>&nbsp;</p>
<h2>interface層</h2>
<p><img data-attachment-id="1597" data-permalink="https://mintaku-blog.net/go-ddd/6-3/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?fit=1920%2C1080&amp;ssl=1" data-orig-size="1920,1080" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="6" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1597" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>interface層は、usecase層と切り離すことでリクエストやレスポンスの形に変わってもinterface層の修正だけで済むようになります。</p>
<p>Index()では、usecase層のメソッドを呼び出してToDoリスト一覧を取得し、返り値をJSON形式に変換しレスポンスとして返却しています。</p>
<p>Create()とUpdate()では、パラメータを引数にusecase層のメソッドを呼び出し、正常に成功した場合はHTTPレスポンスコード 200 No Contentで返すようになっています。http.Error(w, &#8220;成功&#8221;, 200)のように明示的にHTTPレスポンスコードを指定してハンドリングすこともできます。</p>
<p>DB接続やバリデートでエラーが発生した場合は、HTTPレスポンスコード 500でerr.Error()によるエラーメッセージを返却します。</p>
<p>・interface/handler/item.go</p><pre class="crayon-plain-tag">package handler

import (
  "encoding/json"
  "fmt"
  "net/http"
  "strconv"

  "github.com/julienschmidt/httprouter"
  "github.com/sample/go-ddd-crud/usecase"
)

// ItemHandler : Item における Handler のインターフェース
type ItemHandler interface {
  Index(http.ResponseWriter, *http.Request, httprouter.Params)
  Create(http.ResponseWriter, *http.Request, httprouter.Params)
  Update(http.ResponseWriter, *http.Request, httprouter.Params)
}

type itemHandler struct {
  itemUseCase usecase.ItemUseCase
}

// NewItemUseCase : Item データに関する Handler を生成
func NewItemHandler(iu usecase.ItemUseCase) ItemHandler {
  return &amp;itemHandler{
    itemUseCase: iu,
  }
}

// ItemIndex : GET /items -&gt; Item データの全件取得結果を返す
func (ih itemHandler) Index(w http.ResponseWriter, r *http.Request, pr httprouter.Params) {
  // ユースケースの呼び出し
  items, err := ih.itemUseCase.FindAll()
  if err != nil {
    http.Error(w, err.Error(), 500)
    return
  }

  // クライアントにレスポンスを返却
  if err = json.NewEncoder(w).Encode(items); err != nil {
    http.Error(w, err.Error(), 500)
    return
  }
}

// ItemCreate : POST /items -&gt; Item データの新規登録結果を返す
func (ih itemHandler) Create(w http.ResponseWriter, r *http.Request, pr httprouter.Params) {
  // パラメータ
  name := r.FormValue("name")
  status, _ := strconv.Atoi(r.FormValue("status"))

  // ユースケースの呼び出し
  err := ih.itemUseCase.Create(status, name)
  if err != nil {
    http.Error(w, err.Error(), 500)
    return
  }

}

// ItemUpdate : PUT /items -&gt; Item データの更新結果を返す
func (ih itemHandler) Update(w http.ResponseWriter, r *http.Request, pr httprouter.Params) {
  // パラメータ
  id, _ := strconv.Atoi(r.FormValue("id"))
  name := r.FormValue("name")
  status, _ := strconv.Atoi(r.FormValue("status"))

  // ユースケースの呼び出し
  err := ih.itemUseCase.Update(id, status, name)
  if err != nil {
    http.Error(w, err.Error(), 500)
    return
  }
}</pre><p>&nbsp;</p>
<h2>main.go</h2>
<p>main.goでは依存関係の定義とルーティングを設定しています。各層の依存関係を定義することで、利用可能な状態にします。</p>
<ol>
<li>DBを操作するためconfig.Connect()でDB情報をitemPersistence(repository.ItemRepositoryを満たす)に注入</li>
<li>そのitemPersistenceをusecase層のitemUsecase(repository.ItemRepositoryをフィールドに持つ)に注入</li>
<li>生成したitemUsecaseをitemHandler(itemUsecaseをフィールドに持つ)に注入</li>
</ol>
<p>また、サーバ起動のタイミングであわせてCORSの設定もしておきます。</p>
<p>・main.go</p><pre class="crayon-plain-tag">package main

import (
  "fmt"
  "log"
  "net/http"

  "github.com/julienschmidt/httprouter"
  "github.com/sample/go-ddd-crud/config"
  "github.com/sample/go-ddd-crud/infrastructure/persistence"
  handler "github.com/sample/go-ddd-crud/interfaces/handler"
  "github.com/sample/go-ddd-crud/usecase"
)

func main() {
  // 依存関係を注入
  itemPersistence := persistence.NewItemPersistence(config.Connect())
  itemUseCase := usecase.NewItemUseCase(itemPersistence)
  itemHandler := handler.NewItemHandler(itemUseCase)

  // ルーティングの設定
  router := httprouter.New()
  router.GET("/api/items", itemHandler.Index)
  router.POST("/api/items", itemHandler.Create)
  router.PUT("/api/items", itemHandler.Update)

  // サーバ起動
  fmt.Println("------------------------")
  fmt.Println("サーバ起動 http://localhost:8080")
  fmt.Println("------------------------")

  http.ListenAndServe(":8080", &amp;Server{router})
  log.Fatal(http.ListenAndServe(":8080", router))
}

type Server struct {
  r *httprouter.Router
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  w.Header().Add("Access-Control-Allow-Origin", "*")
  w.Header().Set("Access-Control-Allow-Methods", "GET POST PUT")
  w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
  w.Header().Add("Access-Control-Allow-Headers", "Origin")
  w.Header().Add("Access-Control-Allow-Headers", "X-Requested-With")
  w.Header().Add("Access-Control-Allow-Headers", "Accept")
  w.Header().Add("Access-Control-Allow-Headers", "Accept-Language")
  w.Header().Set("Content-Type", "application/json")
  s.r.ServeHTTP(w, r)
}</pre><p>&nbsp;</p>
<h2>DB設定</h2>
<p>開発環境と本番環境のDB設定はdatabase.goに書いています。.env.developmentと.env.productionにそれぞれの環境におけるDB情報を定義しておき実行環境変数から取得しています。</p>
<p>db.AutoMigrate(&amp;model.Item{})でサーバ起動時にItemテーブル生成のマイグレーションが実行されます。</p>
<p>ちょっと疑問なのが、すでに生成されたテーブルのカラム変更をしたい場合はどのようにするのでしょうかね。Railsのようにマイグレーションファイルで履歴管理するみたいな感じではないので、別途マイグレーション関連のツールを導入するのが正しいのでしょうか。</p>
<p>→ 調べてみたら以下のようにカラムの型変更などができるみたいです。</p><pre class="crayon-plain-tag">// `User`モデルのdescriptionカラムのデータ型を`text`に変更します
db.Model(&amp;User{}).ModifyColumn("description", "text")</pre><p>参考：</p>

<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-user-contents.imgix.net%2Fhttps%253A%252F%252Fcdn.qiita.com%252Fassets%252Fpublic%252Farticle-ogp-background-afbab5eb44e0b055cce1258705637a91.png%3Fixlib%3Drb-4.0.0%26w%3D1200%26blend64%3DaHR0cHM6Ly9xaWl0YS11c2VyLXByb2ZpbGUtaW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnMzLWFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb20lMkZxaWl0YS1pbWFnZS1zdG9yZSUyRjAlMkYyMjE5NDglMkZhMmFlMWUwZWNkZjJjM2NmOGNmMjY1MGJiOWJhOTJiYzg5YjUxYzBjJTJGeF9sYXJnZS5wbmclM0YxNTgyOTcwNzg5P2l4bGliPXJiLTQuMC4wJmFyPTElM0ExJmZpdD1jcm9wJm1hc2s9ZWxsaXBzZSZiZz1GRkZGRkYmZm09cG5nMzImcz1jOTY4ZTc5ZGRlYmJiZDg0MTM1ZDY3NmQ0NGY0YzhiYw%26blend-x%3D120%26blend-y%3D462%26blend-w%3D90%26blend-h%3D90%26blend-mode%3Dnormal%26mark64%3DaHR0cHM6Ly9xaWl0YS1vcmdhbml6YXRpb24taW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnMzLWFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb20lMkZxaWl0YS1vcmdhbml6YXRpb24taW1hZ2UlMkY5MzU4NjdkMzdkNjAxZTZjNzA1MjQyNDdmMzdmZDZmMjY3NjIyY2JlJTJGb3JpZ2luYWwuanBnJTNGMTYzMzAzODY5Nj9peGxpYj1yYi00LjAuMCZ3PTQ0Jmg9NDQmZml0PWNyb3AmbWFzaz1jb3JuZXJzJmNvcm5lci1yYWRpdXM9OCZiZz1GRkZGRkYmYm9yZGVyPTIlMkNGRkZGRkYmZm09cG5nMzImcz1kOWRiMTZmOGQ0M2NjZGZhOWNhZWJmZDNiOWM4NjVlZg%26mark-x%3D186%26mark-y%3D515%26mark-w%3D40%26mark-h%3D40%26s%3D4eeecb4ffffb39565d5e6424f7b77486?ixlib=rb-4.0.0&amp;w=1200&amp;fm=jpg&amp;mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTk2MCZoPTMyNCZ0eHQ9R08lRTMlODElQUVPUk0lRTMlODIlOTIlRTUlODglODYlRTMlODElOEIlRTMlODIlOEElRTMlODIlODQlRTMlODElOTklRTMlODElOEYlRTMlODElQkUlRTMlODElQTglRTMlODIlODElRTMlODElQTYlRTMlODElQkYlRTMlODElOUYlRTMlODAlOTBHT1JNJUU1JTg1JUFDJUU1JUJDJThGJUUzJTgzJTg5JUUzJTgyJUFEJUUzJTgzJUE1JUUzJTgzJUExJUUzJTgzJUIzJUUzJTgzJTg4JUUzJTgxJUFFJUU3JTg0JUJDJUUzJTgxJThEJUU1JTlCJTlFJUUzJTgxJTk3JUUzJTgwJTkxJnR4dC1hbGlnbj1sZWZ0JTJDdG9wJnR4dC1jb2xvcj0lMjMxRTIxMjEmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9NTYmdHh0LXBhZD0wJnM9YWUxMGY1Y2RkYTI4YjU3MTUyYjkxYWFiMDEwYTllMzE&amp;mark-x=120&amp;mark-y=112&amp;blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTgzOCZoPTU4JnR4dD0lNDBnb2xkLWtvdSZ0eHQtY29sb3I9JTIzMUUyMTIxJnR4dC1mb250PUhpcmFnaW5vJTIwU2FucyUyMFc2JnR4dC1zaXplPTM2JnR4dC1wYWQ9MCZzPTMyNTc5ZGZlNWMwOGNmNWU0ZTc4NDZkMjU2YzRiYWQ2&amp;blend-x=242&amp;blend-y=454&amp;blend-w=838&amp;blend-h=46&amp;blend-fit=crop&amp;blend-crop=left%2Cbottom&amp;blend-mode=normal&amp;txt64=5qCq5byP5Lya56S-Wk9aTw&amp;txt-x=242&amp;txt-y=539&amp;txt-width=838&amp;txt-clip=end%2Cellipsis&amp;txt-color=%231E2121&amp;txt-font=Hiragino%20Sans%20W6&amp;txt-size=28&amp;s=267d0a14532b6c2a35043a7527de1e6b" alt="">			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://qiita.com/gold-kou/items/45a95d61d253184b0f33">GOのORMを分かりやすくまとめてみた【GORM公式ドキュメントの焼き回し】 #Go - Qiita</a>
			</p>
							<div class="ys-blog-card__dscr">
					はじめに GORMは公式ドキュメントがすごく良いのですが、途中から分かりづらかっ&hellip;				</div>
										<div class="ys-blog-card__domain">qiita.com</div>
					</div>
	</div>
</div>

<p>&nbsp;</p>
<p>・config/database.go</p><pre class="crayon-plain-tag">package config

import (
  "os"

  "github.com/sample/go-ddd-crud/domain/model"

  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mysql"
  "github.com/joho/godotenv"
)

var (
  db *gorm.DB
  err error
)

// DB接続
func Connect() *gorm.DB {
  // 実行環境取得
  env := os.Getenv("HAKEN_ENV")

  if "production" == env {
    env = "production"
  } else {
    env = "development"
  }

  // 環境変数取得
  godotenv.Load(".env." + env)
  godotenv.Load()

  // DB接続
  db, err = gorm.Open("mysql", os.Getenv("CONNECT"))

  if err != nil {
    panic(err)
  }

  autoMigration()

  return db
}

// DB終了
func Close() {
  if err := db.Close(); err != nil {
    panic(err)
  }
}

// Item マイグレーション
func autoMigration() {
  db.AutoMigrate(&amp;model.Item{})
}</pre><p>&nbsp;</p>
<h2>動作確認</h2>
<p>最後にTalend API Testerを使って動作確認をします。</p>
<p>・POSTでToDoタスク新規登録</p>
<p><img data-attachment-id="1627" data-permalink="https://mintaku-blog.net/go-ddd-crud/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2020-08-05-16-38-31/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/f27a9ef3b0153b50c0cc3c7cbff72baa.png?fit=2018%2C1222&amp;ssl=1" data-orig-size="2018,1222" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2020-08-05 16.38.31" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/f27a9ef3b0153b50c0cc3c7cbff72baa.png?fit=300%2C182&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/f27a9ef3b0153b50c0cc3c7cbff72baa.png?fit=800%2C484&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1627" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/f27a9ef3b0153b50c0cc3c7cbff72baa.png?resize=800%2C484&#038;ssl=1" alt="" width="800" height="484" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/f27a9ef3b0153b50c0cc3c7cbff72baa.png?resize=1024%2C620&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/f27a9ef3b0153b50c0cc3c7cbff72baa.png?resize=300%2C182&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/f27a9ef3b0153b50c0cc3c7cbff72baa.png?resize=768%2C465&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/f27a9ef3b0153b50c0cc3c7cbff72baa.png?resize=1536%2C930&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/f27a9ef3b0153b50c0cc3c7cbff72baa.png?w=2018&amp;ssl=1 2018w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/f27a9ef3b0153b50c0cc3c7cbff72baa.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>&nbsp;</p>
<p>・GETで新規登録したToDoタスクを取得</p>
<p><img data-attachment-id="1626" data-permalink="https://mintaku-blog.net/go-ddd-crud/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2020-08-05-16-38-52/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/7e75bb091f196ea8bb78cb0556360c39.png?fit=2010%2C1156&amp;ssl=1" data-orig-size="2010,1156" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2020-08-05 16.38.52" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/7e75bb091f196ea8bb78cb0556360c39.png?fit=300%2C173&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/7e75bb091f196ea8bb78cb0556360c39.png?fit=800%2C460&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1626" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/7e75bb091f196ea8bb78cb0556360c39.png?resize=800%2C460&#038;ssl=1" alt="" width="800" height="460" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/7e75bb091f196ea8bb78cb0556360c39.png?resize=1024%2C589&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/7e75bb091f196ea8bb78cb0556360c39.png?resize=300%2C173&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/7e75bb091f196ea8bb78cb0556360c39.png?resize=768%2C442&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/7e75bb091f196ea8bb78cb0556360c39.png?resize=1536%2C883&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/7e75bb091f196ea8bb78cb0556360c39.png?w=2010&amp;ssl=1 2010w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/7e75bb091f196ea8bb78cb0556360c39.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>&nbsp;</p>
<p>・PUTで新規登録したToDoタスクを更新</p>
<p><img data-attachment-id="1625" data-permalink="https://mintaku-blog.net/go-ddd-crud/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2020-08-05-16-39-16/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/90499a0cee562e8833dc01d79afb7813.png?fit=2002%2C1340&amp;ssl=1" data-orig-size="2002,1340" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2020-08-05 16.39.16" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/90499a0cee562e8833dc01d79afb7813.png?fit=300%2C201&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/90499a0cee562e8833dc01d79afb7813.png?fit=800%2C535&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1625" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/90499a0cee562e8833dc01d79afb7813.png?resize=800%2C535&#038;ssl=1" alt="" width="800" height="535" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/90499a0cee562e8833dc01d79afb7813.png?resize=1024%2C685&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/90499a0cee562e8833dc01d79afb7813.png?resize=300%2C201&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/90499a0cee562e8833dc01d79afb7813.png?resize=768%2C514&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/90499a0cee562e8833dc01d79afb7813.png?resize=1536%2C1028&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/90499a0cee562e8833dc01d79afb7813.png?w=2002&amp;ssl=1 2002w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/90499a0cee562e8833dc01d79afb7813.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>&nbsp;</p>
<p>・GETで更新したToDoタスクを取得</p>
<p><img data-attachment-id="1623" data-permalink="https://mintaku-blog.net/go-ddd-crud/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2020-08-05-16-39-39/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/1350a7e7720d5d6e8c3befb0a852fc69.png?fit=2020%2C1238&amp;ssl=1" data-orig-size="2020,1238" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2020-08-05 16.39.39" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/1350a7e7720d5d6e8c3befb0a852fc69.png?fit=300%2C184&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/1350a7e7720d5d6e8c3befb0a852fc69.png?fit=800%2C491&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1623" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/1350a7e7720d5d6e8c3befb0a852fc69.png?resize=800%2C491&#038;ssl=1" alt="" width="800" height="491" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/1350a7e7720d5d6e8c3befb0a852fc69.png?resize=1024%2C628&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/1350a7e7720d5d6e8c3befb0a852fc69.png?resize=300%2C184&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/1350a7e7720d5d6e8c3befb0a852fc69.png?resize=768%2C471&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/1350a7e7720d5d6e8c3befb0a852fc69.png?resize=1536%2C941&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/1350a7e7720d5d6e8c3befb0a852fc69.png?w=2020&amp;ssl=1 2020w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/1350a7e7720d5d6e8c3befb0a852fc69.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>&nbsp;</p>
<p>・バリデーションエラー時</p>
<p><img data-attachment-id="1622" data-permalink="https://mintaku-blog.net/go-ddd-crud/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2020-08-05-16-48-38/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/35010ab5899a2d7884e24e82f301a8a6.png?fit=2012%2C1208&amp;ssl=1" data-orig-size="2012,1208" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2020-08-05 16.48.38" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/35010ab5899a2d7884e24e82f301a8a6.png?fit=300%2C180&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/35010ab5899a2d7884e24e82f301a8a6.png?fit=800%2C480&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1622" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/35010ab5899a2d7884e24e82f301a8a6.png?resize=800%2C480&#038;ssl=1" alt="" width="800" height="480" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/35010ab5899a2d7884e24e82f301a8a6.png?resize=1024%2C615&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/35010ab5899a2d7884e24e82f301a8a6.png?resize=300%2C180&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/35010ab5899a2d7884e24e82f301a8a6.png?resize=768%2C461&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/35010ab5899a2d7884e24e82f301a8a6.png?resize=1536%2C922&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/35010ab5899a2d7884e24e82f301a8a6.png?w=2012&amp;ssl=1 2012w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/35010ab5899a2d7884e24e82f301a8a6.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>&nbsp;</p>
<p>・DB接続エラー時</p>
<p><img data-attachment-id="1631" data-permalink="https://mintaku-blog.net/go-ddd-crud/unnamed/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/unnamed.png?fit=1600%2C903&amp;ssl=1" data-orig-size="1600,903" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="unnamed" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/unnamed.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/unnamed.png?fit=800%2C452&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1631" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/unnamed.png?resize=800%2C452&#038;ssl=1" alt="" width="800" height="452" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/unnamed.png?resize=1024%2C578&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/unnamed.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/unnamed.png?resize=768%2C433&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/unnamed.png?resize=1536%2C867&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/unnamed.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>&nbsp;</p>
<h2>参考</h2>
<p>Goプログラミング実践入門</p>
<p>実践ドメイン駆動設計</p>

<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img src="https://i0.wp.com/tech.yyh-gl.dev/img/2019/06/go_web_api/featured.webp?w=800&#038;ssl=1" alt="" data-recalc-dims="1">			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://yyh-gl.github.io/tech-blog/blog/go_web_api/">【Go + レイヤードアーキテクチャ】DDDを意識してWeb APIを実装してみる - yyh-gl&#039;s Tech Blog</a>
			</p>
							<div class="ys-blog-card__dscr">
					2019/10/30 に内容を一部更新しました				</div>
										<div class="ys-blog-card__domain">yyh-gl.github.io</div>
					</div>
	</div>
</div>


<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-user-contents.imgix.net%2Fhttps%253A%252F%252Fcdn.qiita.com%252Fassets%252Fpublic%252Farticle-ogp-background-afbab5eb44e0b055cce1258705637a91.png%3Fixlib%3Drb-4.0.0%26w%3D1200%26blend64%3DaHR0cHM6Ly9xaWl0YS11c2VyLXByb2ZpbGUtaW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnFpaXRhLWltYWdlLXN0b3JlLnMzLmFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb20lMkYwJTJGMjc2NTg5JTJGcHJvZmlsZS1pbWFnZXMlMkYxNTkwMTQyMzM2P2l4bGliPXJiLTQuMC4wJmFyPTElM0ExJmZpdD1jcm9wJm1hc2s9ZWxsaXBzZSZiZz1GRkZGRkYmZm09cG5nMzImcz00ZTJjYTM4MzZlMjIxMWJlNzczOTNiZTgyZTc1YzJiOQ%26blend-x%3D120%26blend-y%3D467%26blend-w%3D82%26blend-h%3D82%26blend-mode%3Dnormal%26s%3D9e3a4e805c2dbbcddff015d21a4c13c7?ixlib=rb-4.0.0&amp;w=1200&amp;fm=jpg&amp;mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTk2MCZoPTMyNCZ0eHQ9REREJUUzJTgyJTkyJUU2JTg0JThGJUU4JUFEJTk4JUUzJTgxJTk3JUUzJTgxJUFBJUUzJTgxJThDJUUzJTgyJTg5JUUzJTgzJUFDJUUzJTgyJUE0JUUzJTgzJUE0JUUzJTgzJUJDJUUzJTgzJTg5JUUzJTgyJUEyJUUzJTgzJUJDJUUzJTgyJUFEJUUzJTgzJTg2JUUzJTgyJUFGJUUzJTgzJTgxJUUzJTgzJUEzJUUzJTgxJUE4R28lRTMlODElQTdBUEklRTMlODIlQjUlRTMlODMlQkMlRTMlODMlOTAlRTMlODMlQkMlRTMlODIlOTIlRTYlQTclOEIlRTclQUYlODklRTMlODElOTklRTMlODIlOEImdHh0LWFsaWduPWxlZnQlMkN0b3AmdHh0LWNvbG9yPSUyMzFFMjEyMSZ0eHQtZm9udD1IaXJhZ2lubyUyMFNhbnMlMjBXNiZ0eHQtc2l6ZT01NiZ0eHQtcGFkPTAmcz1jNmU3Y2EwNjk5Y2M0N2QwY2ZmODFlNmU4ZDlmOTEwNw&amp;mark-x=120&amp;mark-y=112&amp;blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTgzOCZoPTU4JnR4dD0lNDByeW9ra3k1OSZ0eHQtY29sb3I9JTIzMUUyMTIxJnR4dC1mb250PUhpcmFnaW5vJTIwU2FucyUyMFc2JnR4dC1zaXplPTM2JnR4dC1wYWQ9MCZzPTIyMWMyNzEzMGY5ZjZjYmNiYjQ2OTA3NGRjZTllN2Jh&amp;blend-x=242&amp;blend-y=480&amp;blend-w=838&amp;blend-h=46&amp;blend-fit=crop&amp;blend-crop=left%2Cbottom&amp;blend-mode=normal&amp;s=efe578a7e3a14e4e644a118931c6f6bc" alt="">			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://qiita.com/ryokky59/items/6c2b35169fb6acafce15">DDDを意識しながらレイヤードアーキテクチャとGoでAPIサーバーを構築する #ドメイン駆動設計 - Qiita</a>
			</p>
							<div class="ys-blog-card__dscr">
					今の現場で初めてDDDに触れたので、よく採用されるアーキテクチャとしてレイヤード&hellip;				</div>
										<div class="ys-blog-card__domain">qiita.com</div>
					</div>
	</div>
</div>


<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-user-contents.imgix.net%2Fhttps%253A%252F%252Fcdn.qiita.com%252Fassets%252Fpublic%252Fadvent-calendar-ogp-background-7940cd1c8db80a7ec40711d90f43539e.jpg%3Fixlib%3Drb-4.0.0%26w%3D1200%26blend64%3DaHR0cHM6Ly9xaWl0YS11c2VyLXByb2ZpbGUtaW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRmF2YXRhcnMxLmdpdGh1YnVzZXJjb250ZW50LmNvbSUyRnUlMkYzODcyNTk5NSUzRnYlM0Q0P2l4bGliPXJiLTQuMC4wJmFyPTElM0ExJmZpdD1jcm9wJm1hc2s9ZWxsaXBzZSZiZz1GRkZGRkYmZm09cG5nMzImcz1mOTUyOWNiMjk4NWM4NDlmY2E0MTE1MjU5ZTQ1YzZhNQ%26blend-x%3D120%26blend-y%3D462%26blend-w%3D90%26blend-h%3D90%26blend-mode%3Dnormal%26mark64%3DaHR0cHM6Ly9xaWl0YS1vcmdhbml6YXRpb24taW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnMzLWFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb20lMkZxaWl0YS1vcmdhbml6YXRpb24taW1hZ2UlMkY2OTM2OWQ2NjE4ODlkYTNlNjI3NjE3MTE2NTMyMjc0ODQ1Nzk2ZTU3JTJGb3JpZ2luYWwuanBnJTNGMTU3MjkzODQ3Nz9peGxpYj1yYi00LjAuMCZ3PTQ0Jmg9NDQmZml0PWNyb3AmbWFzaz1jb3JuZXJzJmNvcm5lci1yYWRpdXM9OCZiZz1GRkZGRkYmYm9yZGVyPTIlMkNGRkZGRkYmZm09cG5nMzImcz0wYzRlNDE2MTJiZmI0NWUzZDY3MzZkZTYyMDBlNDBmMg%26mark-x%3D186%26mark-y%3D515%26mark-w%3D40%26mark-h%3D40%26s%3D7508b7450893b023987e6cea33296c74?ixlib=rb-4.0.0&amp;w=1200&amp;fm=jpg&amp;mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTk2MCZoPTMyNCZ0eHQ9JUU0JUJCJThBJUUzJTgxJTk5JUUzJTgxJTkwJUUzJTgwJThDJUUzJTgzJUFDJUUzJTgyJUE0JUUzJTgzJUE0JUUzJTgzJUJDJUUzJTgzJTg5JUUzJTgyJUEyJUUzJTgzJUJDJUUzJTgyJUFEJUUzJTgzJTg2JUUzJTgyJUFGJUUzJTgzJTgxJUUzJTgzJUEzJTJCREREJUUzJTgwJThEJUUzJTgyJTkyJUU3JTkwJTg2JUU4JUE3JUEzJUUzJTgxJTk3JUUzJTgyJTg4JUUzJTgxJTg2JUUzJTgwJTgyJUVGJUJDJTg4Z29sYW5nJUVGJUJDJTg5JnR4dC1hbGlnbj1sZWZ0JTJDdG9wJnR4dC1jb2xvcj0lMjMzQTNDM0MmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9NTYmdHh0LXBhZD0wJnM9Y2M5NDQ3MGM0MGYxMWJiMzFmNDU1N2FhOTdjMDhjOGQ&amp;mark-x=120&amp;mark-y=112&amp;blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTgzOCZoPTU4JnR4dD0lNDB0b25vLW1hcm9uJnR4dC1jb2xvcj0lMjMzQTNDM0MmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9MzYmdHh0LXBhZD0wJnM9YTJmYTRkMjIwM2M4YTRjMDI1ZjU0MWEwZTk4NmU2ZDI&amp;blend-x=242&amp;blend-y=454&amp;blend-w=838&amp;blend-h=46&amp;blend-fit=crop&amp;blend-crop=left%2Cbottom&amp;blend-mode=normal&amp;txt64=bm9uZQ&amp;txt-x=242&amp;txt-y=539&amp;txt-width=838&amp;txt-clip=end%2Cellipsis&amp;txt-color=%233A3C3C&amp;txt-font=Hiragino%20Sans%20W6&amp;txt-size=28&amp;s=e4fd3586dbcf8ac34d5341a229364788" alt="">			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://qiita.com/tono-maron/items/345c433b86f74d314c8d">今すぐ「レイヤードアーキテクチャ+DDD」を理解しよう。（golang） #Go - Qiita</a>
			</p>
							<div class="ys-blog-card__dscr">
					今すぐ「レイヤードアーキテクチャ+DDD」を理解しよう。（golang） とはい&hellip;				</div>
										<div class="ys-blog-card__domain">qiita.com</div>
					</div>
	</div>
</div>

<p><iframe loading="lazy" title="ドメインロジックに集中せよ　〜ドメイン駆動設計 powered by Spring" src="https://www.slideshare.net/slideshow/embed_code/key/8icK2AL01yKOpP" width="427" height="356" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> </p>
<div style="margin-bottom:5px"> <strong> <a href="https://www.slideshare.net/masuda220/powered-by-spring" title="ドメインロジックに集中せよ　〜ドメイン駆動設計 powered by Spring" target="_blank">ドメインロジックに集中せよ　〜ドメイン駆動設計 powered by Spring</a> </strong> from <strong><a href="https://www.slideshare.net/masuda220" target="_blank">増田 亨</a></strong> </div>
<p><iframe loading="lazy" title="Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える" src="https://www.slideshare.net/slideshow/embed_code/key/fvCBgIKDM0EJp7" width="427" height="356" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> </p>
<div style="margin-bottom:5px"> <strong> <a href="https://www.slideshare.net/pospome/go-80591000" title="Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える" target="_blank">Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える</a> </strong> from <strong><a href="https://www.slideshare.net/pospome" target="_blank">pospome</a></strong> </div>The post <a href="https://mintaku-blog.net/go-ddd-crud/">【Go】GormでシンプルなCRUDのREST APIを実装する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/go-ddd-crud/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1621</post-id>	</item>
		<item>
		<title>【Go】DDD + レイヤードアーキテクチャでREST APIを実装する</title>
		<link>https://mintaku-blog.net/go-ddd/</link>
					<comments>https://mintaku-blog.net/go-ddd/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Sun, 26 Jul 2020 08:37:39 +0000</pubDate>
				<category><![CDATA[Go]]></category>
		<category><![CDATA[アーキテクチャ]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=1595</guid>

					<description><![CDATA[<p>Go + DDD + レイヤードアーキテクチャを本やネットで調べ、実際に動かしながらREST APIを構築してみました。 ドメイン駆動設計の本や記事を読んで …</p>
The post <a href="https://mintaku-blog.net/go-ddd/">【Go】DDD + レイヤードアーキテクチャでREST APIを実装する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>Go + DDD + レイヤードアーキテクチャを本やネットで調べ、実際に動かしながらREST APIを構築してみました。</p>
<p>ドメイン駆動設計の本や記事を読んでも実際に自分の手でやってみないと身につかないと思い、簡易的ですが使えるところまで実装してみました。</p>
<h2>DDD（domain driven design）とは</h2>
<p>DDD(ドメイン駆動設計)とはソフトウェアの設計手法であり、ドメインモデリングに着目してソフトウェアの価値を高める手法です。</p>
<p>ソフトウェアの核心にある複雑さに立ち向かうため、チームの共通言語である「ユビキタス言語」を用いて「ドメインモデル」を構築し、それをコードとして実装します。</p>
<p>また、大規模で密結合なシステムにならないように「ドメイン」と「境界づけられたコンテキスト」にシステムを分割し、「コアドメイン」という最重要領域に集中して開発を行います。</p>
<p>DDDについてはまだ理解が浅いので、今はこれらの用語や設計思想についてざっくりとしかイメージできていませんが、今後開発しながら知見を深めていきたいと思います。</p>
<h2>レイヤードアーキテクチャとは</h2>
<p>従来のMVCなどの3層アーキテクチャに比べてdomain層を確立させ、そこにドメインロジックを凝集させようという発想のアーキテクチャです。</p>
<p>以下の図のように各レイヤごとに責務を切り分け、依存の方向を一方向にします。</p>
<p><img data-attachment-id="1600" data-permalink="https://mintaku-blog.net/go-ddd/4-4/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/4.png?fit=1920%2C1080&amp;ssl=1" data-orig-size="1920,1080" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="4" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/4.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/4.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1600" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/4.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/4.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/4.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/4.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/4.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/4.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/4.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>純粋なレイヤードアーキテクチャでは上の図になるのですが、DDDではDIP(依存関係逆転の原則)を用いて下の図のような依存関係になります。</p>
<p><img data-attachment-id="1601" data-permalink="https://mintaku-blog.net/go-ddd/5-2/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?fit=1920%2C1080&amp;ssl=1" data-orig-size="1920,1080" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="5" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1601" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/5.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<h2>レイヤードアーキテクチャが必要な理由</h2>
<p>レイヤードアーキテクチャが必要な理由として以下の2点が挙げられます。</p>
<ol>
<li>コードの依存関係を整理できる</li>
<li>レイヤ内のパッケージの凝縮度を高めることができる</li>
</ol>
<p>また、コード量が多くなると、何がどこに影響するのか管理しづらくなり可読性が低下します。レイヤードアーキテクチャを取り入れることで、システムの保守性が低下しないようにするための効果的なアプローチとなります。</p>
<p>個人的に依存関係が整理されることでシステム規模が大きくなった時や新しくチームにメンバーが入った時にもスムーズに開発に入りやすい点も大きな魅力に感じました。</p>
<h2>各レイヤの責務と役割</h2>
<p>それぞれのレイヤの責務と役割は以下のようになります。</p>
<h3>interface層</h3>
<ul>
<li>HTTPを受け取り、usecaseを呼び結果を出力(JSONやHTML)</li>
</ul>
<p>→ 基本的に薄い実装になるが、HTTPのbodyやheaderのパース処理などで薄くならないこともあります。</p>
<h3>usecase層</h3>
<ul>
<li>アプリケーションレイヤでシステム使用上のユースケースを表現</li>
</ul>
<p>→ ユーザ登録、ユーザ覧表示など</p>
<p>&nbsp;</p>
<ul>
<li>handlerから呼びされる</li>
</ul>
<p>→ 1つのhandlerに対応する専用usecaseが1つ存在します。</p>
<p>&nbsp;</p>
<ul>
<li>基本的にはdomainを触る</li>
</ul>
<h3>domain層</h3>
<ul>
<li>モデルレイヤでドメイン(システムが扱う業務領域)に関する値と振る舞いを持つ</li>
<li>他のレイヤに依存しない</li>
</ul>
<h3>infrastructure層</h3>
<ul>
<li>技術的関心ごとを扱うレイヤで具体的な技術に関する処理を書く</li>
</ul>
<p>→ DBやMail操作など</p>
<p>&nbsp;</p>
<ul>
<li>直接handler、usecaseから呼ばれることもあるが、基本的にdomainのインターフェースによって抽象化される</li>
</ul>
<h2>ディレクトリ構成</h2>
<p>ディレクトリ構成は以下のようになりました。docker-compose.ymlでMySQL立ち上げて使用しています。</p>
<p>sample-api<br />
├── config<br />
│ └── database.go<br />
├── domain<br />
│ └── model<br />
│     └── user.go<br />
│ └── repository<br />
│     └── user.go<br />
├── infrastructure<br />
│ └── persistence<br />
│     └── user.go<br />
├── interface<br />
│ └── handler<br />
│     └── user.go<br />
├── usecase<br />
│ └── user.go<br />
├── .env.development<br />
├── .env.production<br />
├── docker-compose.yml<br />
└── main.go</p>
<h2>domain層</h2>
<p><img data-attachment-id="1596" data-permalink="https://mintaku-blog.net/go-ddd/%e6%af%94%e8%bc%83%e3%81%ae%e5%a3%81/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?fit=1920%2C1080&amp;ssl=1" data-orig-size="1920,1080" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="比較の壁" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1596" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/94168d18f0afbb5b3500313410ac0f5e.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>domain層ではmodelとrepositoryに分けています。上の図でも説明した通りドメイン層はどの層にも依存せずに隔離します。</p>
<p>ドメインロジックを集約させ、ここをインクリメンタルに成長させながら全体の開発に展開させていきます。modelは業務領域における構造体やビジネスロジックを置きます。</p>
<p>ちなみに下記のような属性と値を保持するだけのオブジェクトの状態は「ドメインモデル貧血症」と呼ばれています。</p>
<p>オブジェクト指向において、データ(フィールド変数)とそれについての操作(メソッド)は同じクラス内にあるべきであり、この状態はそうなっていないためです(必ずダメというわけではない)。今回は検索するだけのとてもシンプルなAPIなので本来はここに処理を書いていくことになると思います。</p>
<p>&nbsp;</p>
<p>・domain/model/user.go</p><pre class="crayon-plain-tag">package model

import (
  "time"

  _ "github.com/jinzhu/gorm/dialects/mysql"
)

type User struct {
  Id uint
  name string
  CreatedAt time.Time
}</pre><p>&nbsp;</p>
<p>repositoryはDBとのやりとりを定義しますが、技術的関心ごとはinfrastructure層に書くため、ここではインターフェースとしてメソッドを定義します。</p>
<p>実際の処理はinfrastructure層に書き、domain層に依存するように実装します。</p>
<p>・domain/repository/user.go</p><pre class="crayon-plain-tag">package repository

type UserRepository interface {
  Search(name string) ([]*model.User, error)
}</pre><p>&nbsp;</p>
<h2>infrastructure層</h2>
<p><img data-attachment-id="1599" data-permalink="https://mintaku-blog.net/go-ddd/8-3/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?fit=1920%2C1080&amp;ssl=1" data-orig-size="1920,1080" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="8" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1599" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/8.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>infrastructure層ではdomain層に依存するように実装します。</p>
<p>domain層で定義したインターフェースから実際の技術的処理を書いていきます。</p>
<p>今回はユーザ名を引数にして検索を行う処理を実装しています。DBはMySQLを使用し、DB操作のORMはGORMを使用しています。</p>
<p>・infrastructure/persistence/user.go</p><pre class="crayon-plain-tag">package persistence

import (
  "github.com/jinzhu/gorm"

  "github.com/sample/sample-api/domain/model"
  "github.com/sample/sample-api/domain/repository"
)

// UserにおけるPersistenceのインターフェース
type userPersistence struct {
  Conn *gorm.DB
}

// Userデータに関するPersistenceを生成
func NewUserPersistence(conn *gorm.DB) repository.UserRepository {
  return &amp;userPersistence{Conn: conn}
}

// 検索
func (up *userPersistence) Search(name string) ([]*model.User, error) {
  var user []model.User

  // DB接続確認
  if err := up.Conn.Take(&amp;user).Error; err != nil {
    return nil, err
  }

  db := up.Conn.Find(&amp;user)

  // 名前検索
  if name != "" {
    db = db.Where("name = ?", name).Find(&amp;user)
  }

  return user, nil
}</pre><p>&nbsp;</p>
<h2>usecase層</h2>
<p><img data-attachment-id="1598" data-permalink="https://mintaku-blog.net/go-ddd/attachment/7/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?fit=1920%2C1080&amp;ssl=1" data-orig-size="1920,1080" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="7" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1598" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/7.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>usecase層ではユースケースに沿った処理の流れを実装します。</p>
<p>今回は単純な処理しかしないので、この層の存在価値がわかりにくいですが、複雑なビジネスロジックがあるときは、この層の存在価値が発揮されるようです。</p>
<p>データの取得や保存などでDBにアクセスするときもdomain層のrepositoryを介してアクセスすることによって、infrastructure層ではなくdomain層のみに依存させています。</p>
<p>・usecase/user.go</p><pre class="crayon-plain-tag">package usecase

import (
  "github.com/sample/sample-api/domain/repository"
)

// UserにおけるUseCaseのインターフェース
type UserUseCase interface {
  Search(name string) ([]*model.User, error)
}

type userUseCase struct {
  userRepository repository.UserRepository
}

// Userデータに関するUseCaseを生成
func NewUserUseCase(ur repository.UserRepository) UserUseCase {
  return &amp;userUseCase{
    userRepository: ur,
  }
}

// 検索
func (uu userUseCase) Search(name string) (user []*model.User, err error) {
  user, err = uu.userRepository.Search(name)
  if err != nil {
    return nil, err
  }
  return user, nil
}</pre><p>&nbsp;</p>
<h2>interface層</h2>
<p><img data-attachment-id="1597" data-permalink="https://mintaku-blog.net/go-ddd/6-3/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?fit=1920%2C1080&amp;ssl=1" data-orig-size="1920,1080" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="6" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1597" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?resize=1024%2C576&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?resize=768%2C432&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?resize=1536%2C864&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?w=1920&amp;ssl=1 1920w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/07/6.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>interface層はHTTPリクエスト・レスポンスを扱う層です。</p>
<p>usecase層と切り離すことでリクエストやレスポンスの形に変わってもinterface層の修正だけで済むようになります。</p>
<p>今回はGETパラメータのnameを引数にusecase層の検索メソッドを呼び出し、返り値をJSON形式に変換しレスポンスとして返却しています。</p>
<p>・interface/handler/user.go</p><pre class="crayon-plain-tag">package rest

import (
  "encoding/json"
  "net/http"
  "strconv"

  "github.com/julienschmidt/httprouter"
  "github.com/sample/sample-api/usecase"
)

// userにおけるHandlerのインターフェース
type userHandler interface {
  Index(http.ResponseWriter, *http.Request, httprouter.Params)
}

type userHandler struct {
  userUseCase usecase.UserUseCase
}

// Userデータに関するHandlerを生成
func NewUserHandler(uu usecase.UserUseCase) UserHandler {
  return &amp;userHandler{
    userUseCase: uu,
  }
}

// UserIndex : GET /users -&gt; 検索結果を返す
func (uh userHandler) Index(w http.ResponseWriter, r *http.Request, pr httprouter.Params) {
  // GETパラメータ
  name := r.FormValue("name")

  user, err := jh.userUseCase.Search(name)
  if err != nil {
    http.Error(w, err.Error(), 500)
    return
  }

  // クライアントにレスポンスを返却
  if err = json.NewEncoder(w).Encode(user); err != nil {
    http.Error(w, err.Error(), 500)
    return
  }
}</pre><p>&nbsp;</p>
<h2>main.go</h2>
<p>main.goでは依存関係の定義とルーティングを設定します。各層の依存関係を定義することで、利用可能な状態にします。</p>
<p>infrastructure層でDBを操作するためconfig.Connect()でDB情報をセットしておきます。</p>
<p>また、サーバ起動のタイミングであわせてCORSの設定もしておきます。今回は例として「https://example.com」からGETのみ許可するようにしています。</p>
<p>・main.go</p><pre class="crayon-plain-tag">package main

import (
  "fmt"
  "log"
  "net/http"

  "github.com/julienschmidt/httprouter"
  "github.com/sample/sample-api/config"
  "github.com/sample/sample-api/infrastructure/persistence"
  handler "github.com/sample/sample-api/interfaces/handler"
  "github.com/sample/sample-api/usecase"
)

func main() {
  // 依存関係を定義
  userPersistence := persistence.NewUserPersistence(config.Connect())
  userUseCase := usecase.NewUserUseCase(userPersistence)
  userHandler := handler.NewUserHandler(userUseCase)

  // ルーティングの設定
  router := httprouter.New()
  router.GET("/api/users", userHandler.Index)

  // サーバ起動
  http.ListenAndServe(":8080", &amp;Server{router})
  log.Fatal(http.ListenAndServe(":8080", router))
}

type Server struct {
  r *httprouter.Router
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  w.Header().Add("Access-Control-Allow-Origin", "https://example.com")
  w.Header().Set("Access-Control-Allow-Methods", "GET")
  w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
  w.Header().Add("Access-Control-Allow-Headers", "Origin")
  w.Header().Add("Access-Control-Allow-Headers", "X-Requested-With")
  w.Header().Add("Access-Control-Allow-Headers", "Accept")
  w.Header().Add("Access-Control-Allow-Headers", "Accept-Language")
  w.Header().Set("Content-Type", "application/json")
  s.r.ServeHTTP(w, r)
}</pre><p>&nbsp;</p>
<h2>DB設定</h2>
<p>開発環境と本番環境のDB設定はdatabase.goに書いています。.env.developmentと.env.productionにそれぞれの環境におけるDB情報を定義しておき実行環境変数から取得しています。</p>
<p>・config/database.go</p><pre class="crayon-plain-tag">package config

import (
  "os"

  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mysql"
  "github.com/joho/godotenv"
)

var (
  db *gorm.DB
  err error
)

// DB接続
func Connect() *gorm.DB {
  // 実行環境取得
  env := os.Getenv("ENV")

  if "production" == env {
    env = "production"
  } else {
    env = "development"
  }

  // 環境変数取得
  godotenv.Load(".env." + env)
  godotenv.Load()

  // DB接続
  db, err = gorm.Open("mysql", os.Getenv("CONNECT"))

  if err != nil {
    panic(err)
  }

  return db
}

// DB終了
func Close() {
  if err := db.Close(); err != nil {
    panic(err)
  }
}</pre><p>&nbsp;</p>
<h2>まとめ</h2>
<p>Go + DDD + レイヤードアーキテクチャでREST APIを実装してみました。今回のような簡単なAPIではあまり効果を発揮しないように思いますが、それぞれのレイヤと依存関係を実装しながら学ぶことでDDDとレイヤードアーキテクチャの理解を深めることができました。</p>
<p>WebメディアやサービスではMVCをベースで開発していましたが、アジャイル開発でどんどん仕様が変わり複雑化していく中でよりよい開発手法を取り入れたいとずっと思っていました。</p>
<p>そのため、今後新規開発していく上でアーキテクチャ選定のひとつの選択肢として積極的に取り入れていきたいと思います。</p>
<p>その後、こちらでCRUDのREST APIを実装してみました。</p>

<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img width="560" height="315" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/880ba96bb73e8d149bead6509e8867ce.png?fit=560%2C315&amp;ssl=1" class="attachment-large size-large wp-post-image" alt="" loading="lazy" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/880ba96bb73e8d149bead6509e8867ce.png?w=560&amp;ssl=1 560w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/880ba96bb73e8d149bead6509e8867ce.png?resize=300%2C169&amp;ssl=1 300w" sizes="(max-width: 560px) 100vw, 560px" data-attachment-id="1635" data-permalink="https://mintaku-blog.net/go-ddd-crud/help-raise-heart-disease-awareness%e3%81%ae%e3%82%b3%e3%83%92%e3%82%9a%e3%83%bc%e3%81%ae%e3%82%b3%e3%83%92%e3%82%9a%e3%83%bc%e3%81%ae%e3%82%b3%e3%83%92%e3%82%9a%e3%83%bc-20-2/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/880ba96bb73e8d149bead6509e8867ce.png?fit=560%2C315&amp;ssl=1" data-orig-size="560,315" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Help raise Heart Disease Awarenessのコピーのコピーのコピー (20)" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/880ba96bb73e8d149bead6509e8867ce.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/08/880ba96bb73e8d149bead6509e8867ce.png?fit=560%2C315&amp;ssl=1" />			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://mintaku-blog.net/go-ddd-crud/">【Go】GormでシンプルなCRUDのREST APIを実装する</a>
			</p>
							<div class="ys-blog-card__dscr">
					https://mintaku-blog.net/go-ddd/ 先日上記記事で&hellip;				</div>
										<div class="ys-blog-card__domain">mintaku-blog.net</div>
					</div>
	</div>
</div>

<p>&nbsp;</p>
<h2>参考</h2>
<ul>
<li>Goプログラミング実践入門</li>
<li>実践ドメイン駆動設計</li>
<li><a href="https://yyh-gl.github.io/tech-blog/blog/go_web_api/" target="_blank" rel="noopener noreferrer">https://yyh-gl.github.io/tech-blog/blog/go_web_api/</a></li>
<li><a href="https://qiita.com/ryokky59/items/6c2b35169fb6acafce15" target="_blank" rel="noopener noreferrer">https://qiita.com/ryokky59/items/6c2b35169fb6acafce15</a></li>
<li><a href="https://qiita.com/tono-maron/items/345c433b86f74d314c8d" target="_blank" rel="noopener noreferrer">https://qiita.com/tono-maron/items/345c433b86f74d314c8d</a></li>
<li><a href="https://www.slideshare.net/masuda220/powered-by-spring" target="_blank" rel="noopener noreferrer">https://www.slideshare.net/masuda220/powered-by-spring</a></li>
<li><a href="https://www.slideshare.net/pospome/go-80591000" target="_blank" rel="noopener noreferrer">https://www.slideshare.net/pospome/go-80591000</a></li>
</ul>The post <a href="https://mintaku-blog.net/go-ddd/">【Go】DDD + レイヤードアーキテクチャでREST APIを実装する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/go-ddd/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1595</post-id>	</item>
		<item>
		<title>Goで学ぶCookie操作(設定・送信・取得)まとめ</title>
		<link>https://mintaku-blog.net/go-cookie/</link>
					<comments>https://mintaku-blog.net/go-cookie/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Sat, 18 Jul 2020 13:35:59 +0000</pubDate>
				<category><![CDATA[Go]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=1580</guid>

					<description><![CDATA[<p>GoにおけるCookie操作(設定・送信・取得)について気になったので、本やネットで調べて自分なりにまとめてみました。 Cookieおさらい 正式には「HT …</p>
The post <a href="https://mintaku-blog.net/go-cookie/">Goで学ぶCookie操作(設定・送信・取得)まとめ</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>GoにおけるCookie操作(設定・送信・取得)について気になったので、本やネットで調べて自分なりにまとめてみました。</p>
<h2>Cookieおさらい</h2>
<p>正式には「HTTP cookie」でWebサイトへアクセスした際に、ユーザのデバイス(PCやスマホ)に残していくデータのことです。</p>
<p>サーバからHTTPレスポンスメッセージを通して送られるもので、クライアントに保管されます。次回以降はCookieとともにWebサーバーへアクセスするため、ユーザが保持していたCookieにあわせてページを表示することができます。</p>
<p>分類としてはセッションクッキーと永続性クッキーの2種類に分けられます。</p>
<h2>Goにおけるtype Cookie</h2>
<p>httpパッケージのtype Cookieは以下のような構造体になっています。</p>
<p>基本的にはhttp.Cookie型へのポインタ変数を用意し、NameフィールドにCookie名、ValueフィールドにCookieの値を入れて設定します。</p><pre class="crayon-plain-tag">type Cookie struct {
  Name string
  Value string

  Path string // optional
  Domain string // optional
  Expires time.Time // optional
  RawExpires string // for reading cookies only

  // MaxAge=0 means no 'Max-Age' attribute specified.
  // MaxAge&lt;0 means delete cookie now, equivalently 'Max-Age: 0'
  // MaxAge&gt;0 means Max-Age attribute present and given in seconds
  MaxAge int
  Secure bool
  HttpOnly bool
  SameSite SameSite // Go 1.11
  Raw string
  Unparsed []string // Raw text of unparsed attribute-value pairs
}</pre><p>&nbsp;</p>
<p>ExpiresとMaxAgeフィールドをしているすることで有効期限を設定することができます。</p>
<p>Expiresフィールドは、Cookieの期限切れとなるtime.Time型を指定します。</p><pre class="crayon-plain-tag">ex) Expires: time.Now().Add(24 * time.Hour) // 24時間後に期限切れ</pre><p>&nbsp;</p>
<p>MaxAgeフィールドは、ブラウザ内で生成されてからどれだけの期間有効かを指定します。ちなみに秒単位です。</p><pre class="crayon-plain-tag">ex) MaxAge: 60 * 60 // 1時間後に期限切れ</pre><p>&nbsp;</p>
<p>Expiresはほぼ全てのブラウザに対応しているが、HTTP1.1以降では非推奨となり、MaxAgeが推奨されています。ただ、MaxAgeはIE6、7、8で対応されていません。</p>
<p>参考：<a href="https://golang.org/pkg/net/http/#Cookie" target="_blank" rel="noopener noreferrer">https://golang.org/pkg/net/http/#Cookie</a></p>
<h2>Cookieの設定・送信</h2>
<p>http.Cookie型へのポインタ変数を用意し、それぞれのフィールドに値を入れ、http.ResponseWriterとcookieを引数にセットします。</p><pre class="crayon-plain-tag">cookie := &amp;http.Cookie{
  Name: "sample_name",
  Value: "sample_value",
  HttpOnly: true,
}
http.SetCookie(writer, cookie)</pre><p>&nbsp;</p>
<p>実際に使われる例として、ユーザログイン時にCookieを生成する場合だと以下のような流れになります。</p>
<p>メールアドレスとパスワードがPOSTされ、メールアドレスからユーザが存在するかをチェックします。ここでのdata.UserByEmailメソッド(処理は省略)はメールアドレスからユーザを取得してきています。</p>
<p>次にPOSTされたパスワードが取得したユーザのパスワードと一致しているかをチェックし、user.CreateSession()メソッド(処理は省略)でサーバ側で持つセッションを生成してセッションテーブルにインサートします。</p>
<p>その後、Cookieをセットしログイン後のページにリダイレクトさせます。</p><pre class="crayon-plain-tag">func auth(writer http.ResponseWriter, request *http.Request) {
  err := request.ParseForm()
  user, err := data.UserByEmail(request.PostFormValue("email"))
  if err != nil {
    danger(err, "ユーザが見つかりませんでした。")
  }
  if user.Password == data.Encrypt(request.PostFormValue("password")) {
    session, err := user.CreateSession()
    if err != nil {
      danger(err, "セッションを生成することができません。")
    }
    cookie := &amp;http.Cookie{
      Name: "auth",
      Value: session.Uuid,
      HttpOnly: true,
    }
    http.SetCookie(writer, cookie)
    http.Redirect(writer, request, "/", 302)
  } else {
    http.Redirect(writer, request, "/login", 302)
  }

}</pre><p>&nbsp;</p>
<h2>Cookieの取得</h2>
<p>構造体RequestのCookieメソッドを使用して、HTTPリクエストからのCookieを取得できます。指定したCookieが存在しない場合はエラーを返します。</p>
<p>複数のCookieを取得したい場合は、Cookiesを使うことで全てのCookieをスライスに入れて取得できます。<br />
cookie.ValueでCookieの値を取得することができます。</p><pre class="crayon-plain-tag">cookie, err := request.Cookie("sample")

if err == nil {
  v := cookie.Value
}</pre><p>&nbsp;</p>
<p>実際に使われる例として、クライアントとサーバ間でのCookieのやりとりの場合、以下のような流れになります。</p>
<p>request.Cookie()メソッド(処理は省略)でCookieを取得し、Session構造体にセットします。</p>
<p>その後、送られてきたセッションをチェックし、無効の場合はエラーにします。</p><pre class="crayon-plain-tag">func session(writer http.ResponseWriter, request *http.Request) (sess data.Session, err error) {
  cookie, err := request.Cookie("auth")
  if err == nil {
    sess = data.Session{Uuid: cookie.Value}
    if ok, _ := sess.Check(); !ok {
      err = errors.New("セッションが無効です")
    }
  }
  return
}</pre><p>GoにおけるCookie操作についてのまとめでした。</p>The post <a href="https://mintaku-blog.net/go-cookie/">Goで学ぶCookie操作(設定・送信・取得)まとめ</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/go-cookie/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1580</post-id>	</item>
		<item>
		<title>Go + Gin + GormでREST APIを実装してGAEにデプロイする</title>
		<link>https://mintaku-blog.net/go-gin/</link>
					<comments>https://mintaku-blog.net/go-gin/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Mon, 13 Jul 2020 13:53:12 +0000</pubDate>
				<category><![CDATA[GCP]]></category>
		<category><![CDATA[Go]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=1569</guid>

					<description><![CDATA[<p>Go + Gin + GormでREST APIをさくっと実装してGAEにデプロイしてみました。 そもそもGoの本やGoに関する情報を読んだ感じ、多くのこと …</p>
The post <a href="https://mintaku-blog.net/go-gin/">Go + Gin + GormでREST APIを実装してGAEにデプロイする</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>Go + Gin + GormでREST APIをさくっと実装してGAEにデプロイしてみました。</p>
<p>そもそもGoの本やGoに関する情報を読んだ感じ、多くのことが標準ライブラリでまかなえるため、基本はFWやORMを使わないのが「Goらしい手法」とのことなので、次は標準ライブラリのみでも実装してみたいと思います。</p>
<p>参考：<a href="https://employment.en-japan.com/engineerhub/entry/2018/06/19/110000" target="_blank" rel="noopener noreferrer">https://employment.en-japan.com/engineerhub/entry/2018/06/19/110000</a></p>
<p>参考：<a href="https://golang.org/doc/effective_go.html" target="_blank" rel="noopener noreferrer">https://golang.org/doc/effective_go.html</a></p>
<h2>Ginとは</h2>
<p>Ginとは軽量かつシンプルなインターフェイスが特徴のGoのWebアプリケーションフレームワークです。</p>
<p>また、GoのWebアプリケーションの中では比較的初期から開発されているので安定して使われています。</p>
<h2>Gormとは</h2>
<p>Goでよく使われているORMの一種です。</p>
<p>そもそもORM(O/Rマッピング)とは、オブジェクト指向言語におけるオブジェクトと、リレーショナルデータベースにおけるレコードとを対応づけることです。</p>
<p>ORMによって、リレーショナルデータベースのレコードがオブジェクトとして直感的に扱えるようになり、リレーショナルデータベースにアクセスするプログラムを記述する処理を容易にすることが可能になります。</p>
<h2>プロジェクト構成</h2>
<p>Getパラメータで本検索をして、その結果をJSON形式で返す簡単なAPIを実装していきます。</p>
<p>以下の記事を参考にプロジェクト構成を組んでみました。元々Railsを書いていたこともあり、この構成が今のところしっくりきました(今後勉強していくうちによりより構成が見つかる気がします)。</p>
<p>参考：<a href="https://qiita.com/Asuforce/items/0bde8cabb30ac094fcb4" target="_blank" rel="noopener noreferrer">https://qiita.com/Asuforce/items/0bde8cabb30ac094fcb4</a></p>
<p>sample-go-api<br />
├── controller<br />
│ └──book_controller.go<br />
├── db<br />
│ └── db.go<br />
├── entity<br />
│ └── book.go<br />
├── main.go<br />
├── server<br />
│ └── server.go<br />
├── service<br />
│ └── book_service.go<br />
├── .env.development<br />
├── .env.production<br />
├── .gcloudignore<br />
├── .gitgnore<br />
├── app.yaml<br />
├── docker-compose.yml<br />
├── go.mod<br />
├── go.sum<br />
├── main.go</p>
<h2>DB・モデル実装</h2>
<p>ローカル用にDockerでさくっとMySQLを立ち上げてDBを作成します。</p>
<p>・docker-compose.yml</p><pre class="crayon-plain-tag">version: '3'
services:
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: db
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    ports:
      - "3306:3306"</pre><p>&nbsp;</p>
<p>docker-compose.ymlができたら、起動します。</p><pre class="crayon-plain-tag">$ docker-compose up -d</pre><p>&nbsp;</p>
<p>次にGormとMySQLをgo getでインストールします。</p><pre class="crayon-plain-tag">$ go get github.com/jinzhu/gorm
$ go get github.com/jinzhu/gorm/dialects/mysql</pre><p>&nbsp;</p>
<p>本モデルを作成します。</p>
<p>booksテーブルのカラムを定義します。</p>
<p>・entity/book.go</p><pre class="crayon-plain-tag">package entity

import (
  "time"
)

type Book struct {
  Id uint
  Title string `gorm:"size:128"`
  Category int
  Author string `gorm:"size:64"`
  CreatedAt time.Time
}</pre><p>&nbsp;</p>
<p>DB周りを実装していきます。今回はGAEに本番環境をデプロイするので、developmentとproductionと2つの環境を作りました。そのため、godotenvライブラリを使って実行する環境を取得します。</p>
<p>実行環境に合ったDB接続を行います。ローカルはDockerで立ち上げたMySQL、本番はCloud SQLを使っています。</p>
<p>DB初回起動時にマイグレーションが行われるようにしています。</p>
<p>・db/db.go</p><pre class="crayon-plain-tag">package db

import (
  "os"

  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mysql"
  "github.com/joho/godotenv"

  "github.com/sample/sample-go-api/entity"
)

var (
  db *gorm.DB
  err error
)

// DB初期化
func Init() {
  // 実行環境取得
  env := os.Getenv("ENV")

  if "production" == env {
    env = "production"
  } else {
    env = "development"
  }

  // 環境変数取得
  godotenv.Load(".env." + env)
  godotenv.Load()

  // DB接続
  db, err = gorm.Open("mysql", os.Getenv("CONNECT"))

  if err != nil {
    panic(err)
  }

  autoMigration()
}

// DB取得
func GetDB() *gorm.DB {
  return db
}

// DB接続終了
func Close() {
  if err := db.Close(); err != nil {
    panic(err)
  }
}

// マイグレーション
func autoMigration() {
  db.AutoMigrate(&amp;entity.Book{})
}</pre><p>&nbsp;</p>
<p>ローカル環境用のenv。</p>
<p>・.env.development</p><pre class="crayon-plain-tag">CONNECT=user:password@tcp(localhost:3306)/db?parseTime=true</pre><p>&nbsp;</p>
<p>本番環境用のenv。</p>
<p>・.env.production</p><pre class="crayon-plain-tag">CONNECT=user:password@unix(/cloudsql/sample-project:sample-zone:sample-instance)/sample_db?parseTime=true</pre><p>&nbsp;</p>
<h2>Bookサービスの実装</h2>
<p>検索処理を実装していきます。送られてきたGETパラメータからそれぞれのカラム検索を行います。</p>
<p>Gormを使っていますが、このくらいのSQLならわざわざORMを使わなくても良い気がしますね。</p>
<p>・service/book_service.go</p><pre class="crayon-plain-tag">package book

import (
  "github.com/sample/sample-go-api/db"
  "github.com/sample/sample-go-api/entity"
)

type Service struct{}

type book entity.Book

type Parameter struct {
  title string
  category int
  author string
}

// 検索
func (s Service) Search(title string, category int, author string) ([]Book, error) {

  // DB接続
  db := db.GetDB()

  // 本モデルから作成
  var book []Book

  // パラメータセット
  p := Parameter{title, category, author}

  // DB接続確認
  if err := db.Take(&amp;book).Error; err != nil {
    return nil, err
  }

  // 本検索クエリ
  tx := db
  tx = tx.Find(&amp;book)

  // タイトル
  if p.title != "" {
    tx = tx.Where("title = ?", p.title).Find(&amp;book)
  }

  // カテゴリ
  if p.category != 0 {
    tx = tx.Where("category = ?", p.category).Find(&amp;book)
  }

  // 著者
  if p.author != "" {
    tx = tx.Where("author = ?", p.author).Find(&amp;book)
  }

  return book, nil
}</pre><p>&nbsp;</p>
<h2>Bookコントローラの実装</h2>
<p>Ginをgo getでインストールしておきます。</p><pre class="crayon-plain-tag">$ go get -u github.com/gin-gonic/gin</pre><p>&nbsp;</p>
<p>コントローラを実装します。*gin.ContextでGETパラメータを取得してサービスの検索メソッドに投げています。</p>
<p>検索結果を受け取ってJSON形式で返しています。</p>
<p>・controller/book_controller.go</p><pre class="crayon-plain-tag">package book

import (
  "fmt"
  "net/http"
  "strconv"

  "github.com/gin-gonic/gin"

  book "github.com/sample/sample-go-api/service"
)

type Controller struct{}

// 検索 GET /books
func (pc Controller) Index(c *gin.Context) {
  // パラメータ取得
  title := c.Query("title")
  category, _ := strconv.Atoi(c.Query("category"))
  author := c.Query("author")

  // 検索処理
  var s book.Service
  p, err := s.Search(title, category, author)

  // 検索結果を返す
  if err != nil {
    c.AbortWithStatus(http.StatusNotFound)
    fmt.Println(err)
  } else {
    c.JSON(http.StatusOK, p)
  }
}</pre><p>&nbsp;</p>
<h2>サーバ周り実装</h2>
<p>ルーティング・CORS設定・サーバ起動を実装しています。</p>
<p>・server/server.go</p><pre class="crayon-plain-tag">package server

import (
  "net/http"

  "github.com/gin-gonic/gin"

  book "github.com/sample/sample-go-api/controller"
)

// 初期化
func Init() {
  r := router()

  r.Run()
}

// ルーティング
func router() *gin.Engine {
  r := gin.Default()

  // CORS対応
  r.Use(CORS())

  // ルーティング
  u := r.Group("/books")
  {
    ctrl := book.Controller{}
    u.GET("", ctrl.Index)
  }

  return r
}

// CORS
func CORS() gin.HandlerFunc {
  return func(c *gin.Context) {
    c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
    c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
    c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
    c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")

    if c.Request.Method == "OPTIONS" {
      c.AbortWithStatus(http.StatusNoContent)
      return
    }

    c.Next()
  }
}</pre><p>&nbsp;</p>
<p>main.goの実装</p>
<p>最後にmain.goからDBとサーバを起動して完了です。後は.gitignoreにenvファイルを追加しておきましょう。</p><pre class="crayon-plain-tag">package main

import (
  "github.com/sample/sample-go-api/db"
  "github.com/sample/sample-go-api/server"
)

func main() {
  db.Init()
  server.Init()
}</pre><p>&nbsp;</p>
<h2>GAEにデプロイする</h2>
<p>完成したAPIをGAEにデプロイします。</p>
<p>Goのバージョンは1.12、インスタンスクラスは一番下のF1を指定します(デフォルトF1ですが)。</p>
<p>正規表現 /.* にパスが一致するURLへのすべてのリクエストはmainパッケージのhandle関数で処理されます。</p>
<p>ENVでproductionを指定することでCloud SQLに接続するようにしています。</p>
<p>参考：https://cloud.google.com/appengine/docs/standard/go/config/appref?hl=ja</p><pre class="crayon-plain-tag">runtime: go112
service: sample-go-api-production
instance_class: F1
handlers:
  - url: /.*
    script: auto
env_variables:
  ENV: production</pre><p>&nbsp;</p>
<p>以下のコマンドでGAEにデプロイして完了です。</p><pre class="crayon-plain-tag">$ gcloud app deploy</pre><p></p>The post <a href="https://mintaku-blog.net/go-gin/">Go + Gin + GormでREST APIを実装してGAEにデプロイする</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/go-gin/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1569</post-id>	</item>
	</channel>
</rss>
