Go言語の構造体を使った設計パターン集(DTO・VOなど)
生徒
「Go言語で構造体って使い道がたくさんあるみたいだけど、DTOとかVOって何ですか?」
先生
「DTOやVOは、データを整理して扱いやすくする“設計パターン”の一つです。Goの構造体を使うと、このような設計がとてもシンプルにできますよ。」
生徒
「DTOって難しそう…。初心者でも大丈夫ですか?」
先生
「大丈夫です!わかりやすい例えやコードで、DTOやVOなどの設計パターンを初心者向けにしっかり解説していきますね!」
1. DTOとVOとは何?
まず用語の整理から始めましょう。DTO(Data Transfer Object/データ転送オブジェクト)は、データをまとめて運ぶための“箱”です。画面から受け取った入力やAPIへ返す結果など、ひとかたまりで受け渡ししたい情報を構造体に詰めます。一方、VO(Value Object/値オブジェクト)は、中身そのものに意味とルールを与えた“値”です。「これはメール」「これは金額」のように、型の名前で意図をはっきり示せます。
イメージとしては、返事を入れる封筒=DTOと、その封筒に入っている手紙=VO。封筒は運搬役、手紙は内容の主役です。Goではこの違いを、構造体と独自の型定義でシンプルに表現できます。まずは最小の例で雰囲気をつかみましょう。
package main
import "fmt"
// DTO: 受け渡し用に情報をまとめた箱
type UserDTO struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"` // 今はシンプルにstring
}
// VO: 値そのものに名前を与えて意味を明確化
type Email string
func main() {
// DTOは「まとめて渡す」ために使う
dto := UserDTO{
ID: 1,
Name: "佐藤",
Email: "sato@example.com",
}
// VOは「これはメールだよ」という意図を型名で示す
var mail Email = "sato@example.com"
fmt.Println(dto.Name, dto.Email)
fmt.Println(mail) // Email型だが中身は文字列
}
この例では、UserDTOが「運ぶための箱」、Emailが「意味のある値」です。プログラミング未経験の方は、DTO=まとめて渡す入れ物/VO=中身のラベル付きの値と覚えるだけで十分。まずは名前を付けて区別できることが大切です。細かな作り込みは後の章で少しずつステップアップしていきます。
2. DTOパターン:構造体でデータをまとめる
DTOは、関数間やAPIとのやり取りで使う「受け渡し用の箱」です。入力フォームやデータベースから集めた情報をひとかたまりにし、必要な項目だけを安全に運べます。ひとつの構造体に整理しておくと、引数が増えてゴチャつくのを防ぎ、読みやすさと変更のしやすさが上がります。
type UserDTO struct {
ID int `json:"id"` // JSONにするときのキー名
Name string `json:"name"` // 大文字で始まるフィールドは外部へ公開される
Email string `json:"email"` // 使う情報だけを詰めるのがポイント
}
初心者向けの最小サンプルとして、DTOを作ってJSON文字列に変換してみましょう。タグ(json:"...")を付けると、APIとやり取りするときのキー名がそろって便利です。
package main
import (
"encoding/json"
"fmt"
)
type UserDTO struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
// 1) 入力をまとめる(受け渡し用の箱を用意)
u := UserDTO{
ID: 1,
Name: "佐藤",
Email: "sato@example.com",
}
// 2) APIに返したいのでJSONへ変換(エンコード)
b, err := json.Marshal(u)
if err != nil {
panic(err)
}
fmt.Println(string(b)) // {"id":1,"name":"佐藤","email":"sato@example.com"}
// 3) 逆に、受け取ったJSONをDTOに読み込む(デコード)こともできます
var received UserDTO
_ = json.Unmarshal(b, &received)
fmt.Println(received.Name) // 佐藤
}
ポイントは次の3つ。①必要な項目だけ入れる(余計な情報は入れない)、②フィールド名は大文字で始めて外部公開(パッケージ外からも使える)、③タグでキー名を明示(APIやファイル保存でズレにくい)。まずはこの型を「受け渡しの形」として決めることが、シンプルな設計への近道です。
3. VOパターン:意味のある型を使って表現
VOは、値そのものを型として扱うパターンです。例えば「メールアドレス」を強調したいときに、stringではなくEmailという型を使います。
type Email string
func NewEmail(e string) Email {
// バリデーションなどの処理も追加できる
return Email(e)
}
これにより、「これはメールアドレスですよ」という設計がコードから伝わります。
4. DTOにVOを組み合わせる
DTOとVOは一緒に使うこともできます。次の例では、Email型のVOをDTOの中に入れています。
type UserDTO struct {
ID int `json:"id"`
Name string `json:"name"`
Email Email `json:"email"`
}
こうすることで、メールアドレスはVOで制約や意味を持ちつつ、DTOとしてデータ箱に収められます。
5. Builderパターン風:構造体の初期化をやさしくする
構造体を初期化するSetter風関数を使うと、複雑なデータ構築がやさしくなります。
type UserBuilder struct {
user UserDTO
}
func NewUserBuilder() *UserBuilder {
return &UserBuilder{}
}
func (b *UserBuilder) SetID(id int) *UserBuilder {
b.user.ID = id
return b
}
func (b *UserBuilder) SetName(name string) *UserBuilder {
b.user.Name = name
return b
}
func (b *UserBuilder) SetEmail(email Email) *UserBuilder {
b.user.Email = email
return b
}
func (b *UserBuilder) Build() UserDTO {
return b.user
}
使い方は次のとおり:
user := NewUserBuilder().
SetID(1).
SetName("佐藤").
SetEmail(NewEmail("sato@example.com")).
Build()
6. Repositoryパターン:データのやり取りを分離
実際のDBやファイルとやり取りする部分を構造体とメソッドでまとめると、コードがシンプルになります。
type UserRepo struct {
// DB接続情報など
}
func (r *UserRepo) Save(u UserDTO) error {
// 保存処理
return nil
}
func (r *UserRepo) FindByID(id int) (UserDTO, error) {
// 取得処理
return UserDTO{}, nil
}
このように構造体とメソッドをまとめることで、保存や取得のロジックを見やすくできます。
7. なぜ設計パターンが役立つの?
構造体とDTO、VO、Builder、Repositoryといった設計パターンを使うと、データの流れをわかりやすく整理できるようになります。特に、コードが読みやすくなり、バグも減りやすくなるメリットがあります。
例えばDTOを使えば、他の関数に必要な情報だけを渡すことができ、関数がシンプルになります。
まとめ
ここまで、Go言語で構造体を使いながら、データを整理して扱いやすくするための設計パターンについて学んできました。たとえば、データをまとめてやり取りするためのDTO、意味を持った値として扱うVO、構造体の初期化をわかりやすくするBuilder、そして保存や取得を任せるRepositoryなど、現実のアプリケーションで出てきやすい考え方を少しずつ紹介しました。どれも難しそうな名前ですが、実際にコードを見てみると、「なるほど、こういうことか」と自然に理解できるようになっています。とくにDTOは、他の関数や外部とデータをやり取りするときに役立ち、VOは中身の文字列に意味を与えて、読みやすさや間違いにくさにつながります。
ここで大切なのは、「きれいに整理されたデータは、バグを減らし、理解をやさしくする」ということです。初心者がつまずきやすい部分は、コードが複雑になってくると、どこからどんなデータが渡っているのかがわからなくなってしまうところです。DTOなら専用の箱として整理され、VOなら名前だけで何を意味しているのかが自然に読み取れます。まるで封筒に宛名を書いて情報を渡すような感覚で、プログラムの中をデータが移動してくれます。
さらに、構造体はGo言語でとても大切な仕組みなので、設計パターンを意識しながら使っていくと、ゆっくりと理解が深まり、複雑なコードでも迷わずに読み進められるようになります。とくに、入力や出力が増えたときほど、設計が生きてきます。必要な値だけを箱につめて関数に渡したり、VOで意味のある名前をつけたりすることで、コード全体の「読みやすさ」がぐっと良くなります。
また、サンプルとして出てきたように、Builder風の書き方を使うと、構造体の初期化がわかりやすくなるという効果もあります。たとえば値が増えても一行ずつ積み重ねる形なので、何を設定しているのかが一目でわかり、初心者でも読みやすい形になります。
Repositoryについても、実際のアプリケーションで使われることが多い考え方です。保存や取得をまとめておくことで、データベース処理を書く場所と、ビジネスロジックを書く場所を分けられます。これによって、ひとつの関数に処理が詰まりすぎず、後から読み返しても理解しやすい形になります。ただ、最初に使うときは名前が難しく見えるかもしれません。ですが「保存の担当」と「取得の担当」をひとつの構造体にまとめたもの、と考えれば自然と受け入れやすいでしょう。
最初は「設計パターン」という言葉に身構えてしまいがちですが、ひとつずつゆっくり触れてみると、むずかしい数学のような仕組みではなく、ただ「読みやすく」「整理された」書き方の工夫に過ぎません。はじめてプログラムを書く人でも、DTOを用意して値を詰めることは簡単にできますし、VOに名前を付けることも特別な技術ではありません。「意味のある名前」「必要な値だけを渡す」「読みやすい形にしておく」という、誰でも感じられるポイントを大切にすれば十分です。
ここでもう一度、シンプルなサンプルを振り返ってみましょう。
package main
import (
"encoding/json"
"fmt"
)
type Email string
type UserDTO struct {
ID int `json:"id"`
Name string `json:"name"`
Email Email `json:"email"`
}
func NewEmail(e string) Email {
return Email(e)
}
func main() {
u := UserDTO{
ID: 1,
Name: "佐藤",
Email: NewEmail("sato@example.com"),
}
b, _ := json.Marshal(u)
fmt.Println(string(b))
}
この短いサンプルの中でも、DTOとVOの役割がはっきり分かれています。UserDTOは情報を一まとめにして渡す箱、Emailは「これはメールアドレスですよ」という意味を持った値です。どんな小さなプログラムでも、この考え方を取り入れて書くことで、後から自分で読み返したときに理解しやすくなります。アプリケーションが大きくなると、こうした小さな積み重ねが大きな違いを生みます。
もう少し大きな視点で考えると、このような設計パターンはチーム開発でも役立ちます。誰かが書いた構造体に意味がついていれば、他の人が読んだとき「ああ、これは入力の箱なんだな」「これはメールアドレスなんだな」とすぐに分かります。言葉で説明する必要がなく、型そのものが説明書の役割を果たしてくれます。つまりプログラマ同士の会話がスムーズになり、誤解も減ります。
初心者のうちは「とにかく動けばいい」という気持ちでコードを書くことが多いのですが、少し余裕が出てきたら、こうした設計の考え方を取り入れてみると、ひとつ上のレベルに進めます。いきなり全部取り入れなくても、まずはDTOとVOだけでも十分効果があります。小さな改善を重ねていくうちに、自然と理解が深まり、今後の学習にも繋がります。
仕組みを知っておくことで、たとえば入力チェックをどこに書くか、データの保存はどこで行うかなどがはっきりして、プログラムが迷子になりません。設計パターンは難しい呪文ではなく、迷子にならないための地図のようなものです。地図があると、途中で道を忘れてもすぐに戻れます。何度も書きながら少しずつ身につけていくのが一番の近道です。
これで、Go言語の構造体を使った設計パターンの基本をひととおり学ぶことができました。現場で活躍するエンジニアも、実はこうした基本的な設計を大切にしながら毎日コードを書いています。初心者でも、ひとつずつ真似していけば必ず身につきます。焦らず、楽しく書き続けてみてください。
生徒
「DTOとかVOって、名前を聞いたときはむずかしそうだと思ったけど、こうして見ていくとただの整理術なんですね。」
先生
「そうなんです。箱に入れるのがDTO、意味を持つ値がVO。道具として使い分けるだけで、コードの見通しがよくなります。」
生徒
「設計パターンって聞くと専門的な印象でしたけど、途中のサンプルみたいに、実際のプログラムにそのまま使える形なんですね。」
先生
「はい。特別な仕掛けではなく、読みやすく整理するための工夫です。これからアプリを作るときにも、自然に役立ちますよ。」
生徒
「少しずつ取り入れて、読みやすいコードを目指してみます!」
この記事を読んだ人からの質問
プログラミング初心者からのよくある疑問/質問を解決します
Go言語のDTOは何に使うのですか?WebアプリやAPI開発で役立ちますか?
Go言語のDTOは、APIやWebアプリでユーザー情報や商品情報などのデータをまとめて運ぶための箱として使われます。関数に大量の値を渡すより、ひとつのDTOでまとめると整理しやすくなり、読みやすいコードになります。
【超入門】ゼロから始めるGo言語プログラミング:最速で「動くアプリ」を作るマンツーマン指導
「プログラミングの仕組み」が根本からわかる。Go言語でバックエンド開発の第一歩を。
本講座を受講することで、単なる文法の暗記ではなく、「プログラムがコンピュータの中でどう動いているか」という本質的な理解につながります。シンプルながら強力なGo言語(Golang)を通じて、現代のバックエンドエンジニアに求められる基礎体力を最短距離で身につけます。
具体的な開発内容と環境
【つくるもの】
ターミナル(黒い画面)上で動作する「対話型計算プログラム」や、データを整理して表示する「ミニ・ツール」をゼロから作成します。自分の書いたコードが形になる感動を体験してください。
【開発環境】
プロの現場でシェアNo.1のVisual Studio Code (VS Code)を使用します。インストールから日本語化、Go言語用の拡張機能設定まで、現場基準の環境を一緒に構築します。
この60分で得られる3つの理解
「なぜ動くのか」という設定の仕組みを理解し、今後の独学で詰まらない土台を作ります。
データの種類やメモリの概念など、他言語にも通じるプログラミングの本質を学びます。
ただ動くだけでなく、誰が見ても分かりやすい「綺麗なコード」を書くための考え方を伝授します。
※本講座は、将来的にバックエンドエンジニアやクラウドインフラに興味がある未経験者のためのエントリー講座です。マンツーマン形式により、あなたの理解度に合わせて進行します。
初めてのGo言語を一緒に学びましょう!