DMM Sprint 2026 Goコースに参加してきました!
はじめに
こんにちは!28卒B3の soma です。
2026年8月3日〜8月7日に開催された、DMM株式会社の短期インターン「DMM Sprint 2026 Goコース」 に参加してきました!
参加を検討している方の参考になればと思い、5日間の内容や雰囲気、実際に感じたことをまとめます。
また、2025年の参加者の方の記事もとても参考になるので、こちらもぜひ読んでみてください!
5日間のカリキュラム
1日目:Go言語の環境構築・チュートリアル
最初はアイスブレイクとして、何度かチーム替えをしながら共通点を探すゲームを行いました。
その後は環境構築を済ませ、Goのチュートリアルへ。
チュートリアルでは、
- io
- context
- channel
- Generics
など、Goの基本機能を幅広く学びました。
基本的には、
- まず自分で問題を解く
- チームで相談する
- 解説を聞く
という流れで進んでいきます。
私たちのチームでは「まず10分間は各自で考えて、その後みんなで相談する」という進め方をしていました。
途中でモブプログラミングも行ったのですが、他の人がどう考えながらコードを書いているのかを見ることができて、とても勉強になりました。
個人的にはモブプロ、かなりおすすめです!
事前にやっておくと良いこと
Goをほとんど触ったことがない方は、事前に A Tour of Go を一通りやっておくのがおすすめです!
インターンで使用する資料も、A Tour of Goを一度やったことがある方を対象とした内容になっているとのことです。
A Tour of Goでは、
for- 関数・戻り値
structinterface
など、Goの基本的な文法を一通り学ぶことができます。
当日のチュートリアルはかなりテンポよく進むので、事前にA Tour of Goを通して基本的な文法に触れておくと、より余裕を持って取り組めると思います!
2〜5日目:Yatterの開発
2日目からは、本格的に Yatter の開発に入りました!
Yatterは、簡単に説明すると X(旧Twitter)のようなSNSアプリケーション です。投稿やユーザー登録などの機能があり、用意されたコードを読み解きながら、実際に機能を実装していきます。
YAMLやドキュメント、既存のコードなどを読みながら実装を進めていきます。
私たちのチームは、
「みんなで足並みを揃えて進めよう」
という方針だったので、まずはUI層から一緒に実装していきました。
分からないことがあれば、
- 講師の方に聞く
- チームメンバーと相談する
という形で進めました。
講師の方の説明が本当に分かりやすく、
- オニオンアーキテクチャ
- DDD(ドメイン駆動設計)
- UI / UseCase / Domain / Infrastructureそれぞれの責務
- interfaceを使って依存関係を分ける考え方
などについて、かなり理解が深まりました。
AIの使い方について
個人的には、分からないことをAIに聞くのは全然ありだと思います。
ただ、実装自体はできるだけ自分で考えて書くことをおすすめします。
このインターンでは講師の方に直接質問できるので、
「なぜこういう設計になっているんですか?」
みたいな質問をすると、コードだけでは分からない背景まで説明していただけます。
せっかくなので、分からないところはどんどん質問した方がいいです!
オフライン開催
4日目・5日目は六本木オフィスでの開催でした。
オフィスツアーをしていただいたり、DMMのイベントにも参加させていただいたりして、社員の方とも気軽にお話しすることができました。
また、参加者限定のちょっとした特典もありました。
ここは参加してからのお楽しみということで……!
感想
5日間を通して、Goだけでなく、
- DDD
- オニオンアーキテクチャ
- チーム開発
- モブプログラミング
など、かなり多くのことを学ぶことができました。
特に印象に残っているのは、単純に「Goの書き方を学ぶ」というより、
「なぜこの層にこの処理を書くのか」
という設計の部分まで学べたことです。
講師の方々もとても親身に質問へ答えてくださるので、分からないことは積極的に質問することをおすすめします。
Goをこれから学びたい方にも、アーキテクチャを実践的に学びたい方にも、とてもおすすめできるインターンでした!
おわりに
参加を迷っている方は、ぜひXで
#DMMSprint_go
と検索してみてください!
参加者の感想がたくさん投稿されていて、インターンの雰囲気もかなり分かると思います。
最後まで読んでいただき、ありがとうございました!
この下は、今回のインターンで自分が学んだことを備忘録的にまとめています。
興味がある方はぜひ読んでみてください!
おまけ:今回学んだGoの設計について
※自分の理解を整理するために書いている部分もあるので、もし認識が間違っているところがあれば優しく教えてください!
オニオンアーキテクチャって何?
今回のYatterでは、オニオンアーキテクチャをベースにした構成で実装を進めました。
ざっくり言うと、
UI
↓
UseCase
↓
domain/repository
↑
Infrastructure
のように、処理を役割ごとに分けていきます。
実際にはもう少し複雑ですが、自分の中では以下のように理解しています。
| 層 | ざっくりした役割 |
|---|---|
| UI | HTTPリクエストを受け取り、レスポンスを返す |
| UseCase | 処理全体の流れを組み立て、各層を呼び出す |
| Domain | サービスにおけるルールや概念を表現する |
| Repository | データの取得・保存に必要な操作を定義する |
| Infrastructure | DBや外部APIなどとの実際の通信・処理を行う |
例えば、外部APIへアクセスする具体的な処理はInfrastructure層に置きます。
一方で、「外部APIからデータを取得する」といった処理全体の流れを指揮するのはUseCase層です。
UseCase
↓ 利用する
Repository(interface)
↑ 実装する
Infrastructure
↓
DB / 外部API
ここでポイントなのが、UseCaseはInfrastructureの具体的な実装を直接使うのではなく、Repositoryのinterfaceを通して利用するという点です。
このように、それぞれの層で役割を分けることで、
「このコードは何を担当しているのか」
が分かりやすくなり、コードの責務を意識しながら実装できるようになります。
DDDって何?
DDDは Domain-Driven Design(ドメイン駆動設計) の略です。
かなりざっくり言うと、
サービスにおける重要なルールや概念を中心にコードを設計していく考え方
だと理解しています。
例えば、ユーザー登録という処理があったとします。
ユーザー登録では、
ユーザー名が重複していないか確認
↓
ユーザーを作成
↓
DBへ保存
のような処理が必要になるかもしれません。
これらを全部HTTP Handlerの中に書いてしまうと、Handlerの責務がどんどん大きくなってしまいます。
そこで、
Handler
↓
UseCase
↓
Domain / Repository
というように役割を分けます。
機能が増えても、それぞれの責務を分けておくことで変更しやすいコードになります。
さらにおまけ:main.goからコードを追ってみる
ここからは、実際にコードがどのようにつながっているのかを追ってみます。
自分も最初は、
「構造体多すぎない?」
「interfaceどこにつながってるん?」
という状態だったので、同じような人の参考になればと思います。
まずはmain.go
例えばmain.goにこんなコードがあるとします。
if err = server.Run(db); err != nil {
slog.Error("failed to run server", "err", err)
}
server.Run(db)を呼んでいます。
ということで、次はserver.Run()を見にいきます。
Repositoryを作る
Server側を見ていると、例えばこんなコードが出てきます。
userRepo := infra.NewUserRepository(db)
New○○という名前の関数が出てきました。
Goでは、このような関数をコンストラクタやFactory関数のように使うことがよくあります。
例えば、
func NewUserRepository(db *sql.DB) Repository {
return &userRepositoryImpl{
db: db,
}
}
のようなイメージです。
簡単に言えば、
UserRepositoryを使うために必要な値をセットして、使える状態にして返す
という役割です。
ちなみにNewだから一度しか動かないわけではなく、呼び出された回数だけ実行されます。
今回のようにアプリ起動時に1回だけ呼んでいる場合は、結果的に起動時に1回生成される形になります。
UseCaseを作る
次にこんなコードが出てきます。
userCreateUseCase := user.NewUserCreateUseCase(
userRepo, // DBへの保存
usernameUniqueChecker, // ユーザー名の重複確認
transactor, // トランザクション管理
)
最初見たときは、
「めっちゃ色々渡してるな」
と思いました。
ただ、見方が分かるとかなりシンプルです。
UserCreateUseCaseは、
- ユーザーをDBへ保存したい
- ユーザー名の重複を確認したい
- トランザクションを使いたい
ので、その処理に必要なものを外から渡しています。
イメージとしては、
UserCreateUseCase
├── UserRepository
├── UsernameUniqueChecker
└── Transactor
という感じです。
このように外から依存するものを渡すことを Dependency Injection(DI / 依存性注入) と呼びます。
Transactorって何?
途中で、
transactor
という名前が出てきて、
「こいつ何者?」
となりました。
ざっくり言うと、トランザクションを管理するためのものです。
例えば、
① Userを保存
② Profileを保存
という2つのDB操作があったとします。
①には成功したけど②に失敗した場合、
Userだけ保存されている
Profileは保存されていない
となると困ります。
そこでトランザクションを使って、
全部成功 → 保存を確定
どこかで失敗 → 全部取り消し
とします。
「全部成功するか、全部失敗するか」のどちらかにしてくれる仕組みですね。
Handlerを作る
続いてこんなコードがあります。
userHandler := api_user.NewUserHandler(userCreateUseCase)
api_userはUI層にあるpackageです。
そして、先ほど作ったuserCreateUseCaseをHandlerに渡しています。
コードを見ると、
type Handler interface {
SignUp(w http.ResponseWriter, r *http.Request)
}
があります。
これは、
Handlerとして扱うなら、SignUpというメソッドを持っていてね
というinterfaceです。
NewUserHandlerを見てみる
func NewUserHandler(userCreateUseCase user.SignUpUseCase) Handler {
return &userHandlerImpl{
userCreateUseCase: userCreateUseCase,
}
}
ここでは、すでに作られているuserCreateUseCaseを受け取って、
userHandlerImpl
の中にセットしています。
つまり、
UserCreateUseCaseを使える状態のHandlerを作って返している
ということです。
userHandlerImpl
中身を見るとこんな感じです。
type userHandlerImpl struct {
userCreateUseCase user.SignUpUseCase
}
つまり、このHandlerは
userHandlerImpl
│
└── userCreateUseCase
という形でUseCaseを持っています。
HTTPリクエストが来たらHandlerが受け取って、
h.userCreateUseCase.SignUp(...)
のようにUseCaseを呼び出せるわけです。
全体をつなげると、
HTTP Request
↓
Handler
↓
UseCase
↓
Repository
↓
Database
となります。
これが分かった瞬間、最初はバラバラに見えていたコードが一気につながりました。
このコードは何?
さらにこんなコードがあります。
var _ Handler = (*userHandlerImpl)(nil)
最初は完全に、
「Goのおまじない?」
と思っていました。
これは、
userHandlerImplがHandlerinterfaceをちゃんと実装しているか
をコンパイル時にチェックするためのコードです。
例えばHandlerが、
type Handler interface {
SignUp(w http.ResponseWriter, r *http.Request)
}
となっているのに、userHandlerImplにSignUp()がなかった場合はコンパイルエラーになります。
実際にユーザー登録の処理を追ってみる
ここまでで、
HTTP Request
↓
Handler
↓
UseCase
↓
Repository
↓
Infrastructure
↓
DB
という大まかな流れが分かってきました。
では、実際にユーザー登録のコードを上から追ってみます。
UI層を見てみる
まずはユーザー登録を受け取るHandlerです。
func (h *userHandlerImpl) SignUp(w http.ResponseWriter, r *http.Request) {
// ...
}
最初に見たとき、
h *userHandlerImplって何?
となりました。
これはメソッドレシーバーと呼ばれるもので、
h.userCreateUseCase
のように、userHandlerImplが持っている値をSignUpメソッドの中から使えるようにしています。
そして、
w http.ResponseWriter
r *http.Request
はHTTP Handlerでよく見る引数です。
-
r:クライアントから送られてきたHTTPリクエスト -
w:クライアントへHTTPレスポンスを返すためのもの
というイメージです。
RouterからHandlerが呼ばれると、これらの値が渡されます。
UI層では何をしている?
今回のユーザー登録をかなりざっくり追うと、
リクエストを受け取る
↓
JSONをDecodeする
↓
UseCaseを呼ぶ
↓
結果をレスポンス用の形に変換
↓
JSONとしてEncodeして返す
という流れです。
例えばイメージとしては、
var req SignUpRequest
// RequestをGoの構造体に変換
json.NewDecoder(r.Body).Decode(&req)
// UseCaseを呼ぶ
user, err := h.userCreateUseCase.SignUp(
r.Context(),
req.Username,
req.Password,
)
// Response用の構造体に変換して返す
json.NewEncoder(w).Encode(...)
のような感じです。
ここで大事なのは、
UI層ではユーザー登録そのもののルールを実装していない
というところです。
UI層はあくまで、
「HTTPリクエストを受け取ってUseCaseにつなぎ、結果をHTTPレスポンスとして返す」
という役割を担当しています。
意外とやっていることはシンプルです。
続いてUseCase層
次にUseCaseを見てみます。
type SignUpUseCase interface {
SignUp(
ctx context.Context,
username,
password string,
) (*user.User, error)
}
まずinterfaceで、
「ユーザー登録のUseCaseなら
SignUpを持っていてね」
というルールを定義しています。
そして、
var _ SignUpUseCase = (*SignUpUseCaseImpl)(nil)
これは先ほどのHandlerと同じで、
SignUpUseCaseImplがSignUpUseCaseinterfaceをちゃんと満たしているか
をコンパイル時に確認しています。
UseCaseが持っているもの
実装を見ると、
type SignUpUseCaseImpl struct {
userRepo repository.User
usernameUniqueChecker *service.UsernameUniqueChecker
transactor transactor.Transactor
}
となっています。
Handlerのときと似ていますね。
UserCreateUseCaseはユーザー登録を行うために、
SignUpUseCaseImpl
├── UserRepository
├── UsernameUniqueChecker
└── Transactor
を使います。
つまり、
-
userRepo- ユーザーを取得・保存する
-
usernameUniqueChecker- ユーザー名が重複していないか確認する
-
transactor- 複数のDB処理をトランザクションとして管理する
という役割です。
UseCaseはこれらを組み合わせながら、
「ユーザー登録という一連の処理をどう進めるか」
を担当しています。
UI層と似ている理由
自分は最初、
「HandlerにもstructがあってUseCaseにもstructがあるけど、めっちゃ似てない?」
と思いました。
例えば、
type userHandlerImpl struct {
userCreateUseCase user.SignUpUseCase
}
と、
type SignUpUseCaseImpl struct {
userRepo repository.User
}
ですね。
これは、それぞれが自分の処理に必要な依存先を持っているからです。
Handler
│
└── UseCaseを使う
UseCase
│
├── Repositoryを使う
├── Domain Serviceを使う
└── Transactorを使う
という形になっています。
Goではこうしてstructのフィールドとして必要な依存を持たせ、Factory関数などから渡す書き方がよく使われます。
Repository層を見てみる
次はRepositoryです。
type User interface {
// 指定したユーザー名のユーザーを取得する
// ユーザーが存在しない場合は nil, nil を返す
FindByUsername(
ctx context.Context,
username string,
) (*user.User, error)
// ユーザーを新規作成して保存する
// 保存に成功した場合は保存したユーザーを返す
Insert(
ctx context.Context,
pendingUser *user.PendingUser,
) (*user.User, error)
}
これを最初に見たとき、
「めっちゃ短くない?」
と思いました。
実際、ここには
FindByUsername
Insert
くらいしか書かれていません。
というのも、ここでは、
ユーザーを保存したり取得したりするなら、こういう操作が必要ですよ
というinterfaceだけを定義しているからです。
つまり、
Insert(...)
という「何ができるか」は書いていますが、
MySQLにどうINSERTするのか
という具体的な処理はここには書きません。
じゃあDB処理はどこ?
そこで出てくるのがInfrastructure層です。
例えば、
Repository interface
Insert(...)
↑
│ 実装する
│
Infrastructure
↓
SQLを実行
↓
Database
という関係になります。
Repository側では、
「ユーザーを保存できる」
ことだけ定義する。
Infrastructure側では、
「実際にどうやってDBへ保存するのか」
を実装する。
このように具体的なDB処理を外側へ追い出すことで、DomainやUseCaseが特定のDB実装に強く依存しないようにしています。
ここがオニオンアーキテクチャの面白いところでした。
Infrastructure層
Infrastructure層では、
- SQLを実行する
- DBからデータを取得する
- DBのデータからDomainのUserを復元する
といった具体的な処理を担当します。
ここまで書くとかなり長くなるので、今回は省略します!
最後にDomain層
最後は、一番中心にあるDomain層を見てみます。
例えばUserです。
type User struct {
id uint64
username string
passwordHash string
createdAt time.Time
}
ここでは、
「このサービスにおけるUserとは何なのか」
を表現しています。
Userは、
- ID
- ユーザー名
- パスワードハッシュ
- 作成日時
などを持っています。
Domainはただのデータ入れではない
例えばこんなメソッドがあります。
func (u *User) SetID(id uint64) error {
// idは1以上の整数であること
if id < 1 {
return errors.ErrInternal.WithDevMessage(
"id must be more than 0",
)
}
u.id = id
return nil
}
単純に、
u.id = id
としているだけではありません。
その前に、
if id < 1 {
というチェックがあります。
つまり、
UserのIDは1以上でなければならない
というルールをDomain側で守っています。
これがDomain層の大事なところです。
DBやHTTPに関係なく、
「Userとして正しい状態とは何か」
をDomain自身が知っています。
GetterもDomain側にある
フィールドが小文字になっているので、
type User struct {
id uint64
username string
}
package外から直接、
user.id
のように触ることはできません。
そのため、
func (u *User) ID() uint64 {
return u.id
}
のようなメソッドを用意して値を取得します。
こうすることで、外側から好き勝手にDomainの状態を変更されるのを防ぐことができます。
ReconstructUserとは?
DBからUserを取得するときには、すでに存在しているUserを復元する必要があります。
そこで例えば、
ReconstructUser(...)
のような関数を使います。
イメージとしては、
Database
↓
Infrastructure
↓
ReconstructUser(...)
↓
DomainのUser
です。
新規ユーザーを作る処理と、
「DBに保存されていた既存UserをDomainオブジェクトとして復元する処理」
を分けているわけですね。
最後に全部つなげてみる
ここまでをまとめると、アプリケーション起動時は、
main.go
↓
server.Run()
↓
Repositoryを作る
↓
UseCaseを作る
↓
Handlerを作る
↓
Routerに登録
という流れで準備されます。
そして実際にユーザー登録のHTTPリクエストが来ると、
HTTP Request
↓
UI / Handler
リクエストをDecode
↓
UseCase
ユーザー登録の流れを制御
↓
Domain / Domain Service
ユーザーのルール・重複確認など
↓
Repository interface
保存・取得という操作を定義
↓
Infrastructure
SQLなど具体的な処理
↓
Database
という流れで処理されます。
そして結果が、
Database
↓
Infrastructure
↓
UseCase
↓
UI / Handler
↓
JSON Response
と返ってきます。
おわりのおわり
最初にコードを見たときは、
「interface多すぎ!」
「struct多すぎ!」
「どこからどこ呼んでるん?」
という感じでした。
ただ、
UI
↓
UseCase
↓
Domain / Repository
↓
Infrastructure
↓
DB
という大きな流れを理解してからコードを見ると、一気に追いやすくなりました。
個人的には、最初から全部理解しようとするより、
「この層は何を担当しているんだろう?」
という目線で1個ずつ追っていくのがおすすめです。
まだまだ勉強中ですが、今回のDMM Sprintを通して、Goの文法だけでなく、こういった設計について実際のコードを見ながら学べたのが一番大きかったです。
もしこれからDMM Sprint Goコースに参加する方がいたら、分からないところはチームのみんなでゆっくり考えて、講師の方にもたくさん質問してみてください!
この辺りの流れが少し分かっているだけでも、インターン中のコードの見え方がかなり変わると思います。
教え合って、質問しまくって、最高のインターンを楽しんでください!!