# バックエンド勉強会1日目 ## カリキュラム(仮) 1. APIの基礎 2. APIを作ってみよう(全3回程度?) 3. CI/CDの構築 4. 計画中 ## GOAL **WebAPIの基礎を学びデータの追加・保存・読み出しなどが行えるようになる** - ユーザー情報を取り扱うAPIを作ってみる - RESTを理解する - 基本的なサーバー構成をDockerで立ち上げる - DB(Postgers) - アプリケーションサーバー - キャッシュサーバー(Redis) - データベースの基本的な取り扱いを学ぶ - テーブルの定義 - データの登録 - データの取り出し - データ削除 リポジトリ:https://github.com/notchman8600/practice-backend/day1 **本バックエンド勉強会はGo言語で実装を行います** ## なぜ、バックエンドが必要なのか(読み飛ばしてOK) なぜ、バックエンドが必要なのだろうか? 答えは「フロントアプリが表示するデータを提供」ためである。 ここでLINEを例に考えてみよう。LINEに表示されているプロフィール画像はどこに保存されているのだろうか?自分の写真は手元のスマホに入っているかもしれない。しかし、他人のプロフィール画像はどこにあるのだろうか。まさか、友達を追加するたびにプロフィール用の画像を貰ったりはしていないだろう。そんな不便すぎるサービスは誰も使わないだろう。メッセージのやり取りをするためにいちいちUSBメモリを持ち運ぶのは不便である。 バックエンドはこのような不便を解消するために存在する。Webページやモバイルアプリなどいわゆるフロントエンドはデータ処理にとても弱いのである。この要因は取り扱うデータの量と変化の速度である。LINEのユーザーは数千万人だし、各々プロフィール画像は気まぐれに変更する。これらをファイルにしてフロントアプリが読み込むのはとても現実的でない。 だから、膨大なデータを扱え、変化に強く、必要なデータを供給するためのプログラムが必要なのである。それがバックエンドと呼ばれる領域の根幹である。これらを達成するためにデータを保存するデータベース、それを加工・処理するプログラム、フロントとの通信手段を考えるネットワークなどがバックエンドの大きな技術領域である。 これ以上話すと長くなるが、結論はフロントアプリが表示したいデータを提供するのがバックエンドアプリである。だから、何かを表示したいアプリケーションを作るならばバックエンドは必要不可欠である。 ### バックエンドエンジニアの将来性? 最近はFirebaseなど様々なサービスが登場しており、古来のイメージであるLinuxでサーバーを立ち上げてAPIを生やすようなバックエンドエンジニアは不要ではないかという論調がある。私も部分的には同意している。APIの提供手段は日々進化していき、使われなくなった技術はどんどん廃れていくものである。 しかし、技術が日々進化する一方で、根底の考え方は不変である。Firebaseだろうが自作サーバーだろうが「フロントアプリが表示するデータを提供する」というバックエンドの使命は変わらない。その実現手段が変わるだけなのである。よって、基本的な考え方はどのようなサービスでも通用するし、逆に様々な方法で実装できるような知識がないと上手く運用することはできない。 **そのサービスを1万人が同時にアクセスしても死にませんか?** これに「はい、大丈夫です」と答えるためには、今も昔も変わらないバックエンドの知識と知恵が必要である。皆さんもこの問いにより自信をもってYesと言えるように日々を勉強しよう。その自信の大きさが(バックエンド)エンジニアとして生き残っていくのに重要だと考える。 ちなみに私は1000人が精いっぱいである。万を捌くための技術力を持ち合わせていないので、引き続き精進する。 ## REST APIとは REST APIとはREpresentation State Transfer APIの略称である。REST APIは以下を満たした物を指す。1~4の項目はRESTの設計原則を説明している。 1. セッションなどの状態管理を行わない(やり取りされる情報はそれ自体で完結して解釈することができる) 1. 情報を操作する命令の体系が予め定義・共有されている(HTTPのGETやPOSTメソッドなど) 1. すべての情報は汎用的な構文で一意に識別される(URLやURIなど) 1. 情報の内部に、別の情報や(その情報の別の)状態へのリンクを含めることができる 3. リソースに対してURLが対応付けられる REST APIの典型的な例としてCRUD(Create Read Update Delete)がある。これはリソースの登録、読み出し、更新、削除であり、APIの基本動作である。 ユーザー情報のシナリオでREST APIを考えてみよう。 ユーザー情報に対して行なわれるイベントとして以下の4つを考える。 - C:新規ユーザーを追加する - R:ユーザー情報を取得する - U:ユーザー情報を更新する - D:ユーザーを削除する これをRESTの原則に割り当てれば/userというエンドポイントに対してHTTPメソッド毎に以下の機能を割り当てる - /user - POST:追加 - GET:読み出し - PUT(POST,PATCH):更新 - DELETE:削除 CRUDとHTTPメソッドの対応は以下の通りである。 - POST・・・Create - GET・・・Read - PUT・・・Update - DELETE・・・Delete この対応は頻出なので絶対に覚えて欲しい。 ここまでがREST APIの説明である。 詳しい説明はググればたくさん出てくるので、より知りたくなったらそちらをあたって欲しい。 ## REST APIを設計する **ここからハンズオン** では、今から最小限のREST APIを設計しよう。 このハンズオンで完成するプロジェクトは上記のリポジトリに格納している。 プログラミングは写経から始まるので、分からなかったら写経をしても良い。とにかく手を動かすことが大事である。 --- **前提条件** このハンズオンにあたって以下の環境構築が完了していることを前提として進める。 こちらが完了していない場合は各自で調べて欲しい。 チェックボックス式にしてあるので、手元にダウンロードして頂いて確認して欲しい。 - [ ] WSL2の有効化及びUbuntuをインストールしている(Windows) - [ ] Docker for desktopをインストールしている(Windowsの場合はWSL2版) - [ ] Dockerとdocker-composeを使えるようにしている(デスクトップアプリを入れない場合) - [ ] Go1.17~をインストールしている なお、エディター等は特に指定しないが、私はVisual Studio Codeで説明をする。 お好きなエディタがある人は適宜読み替えて欲しい. ちなみに参考だが私のGo言語開発環境を紹介する。 この環境での動作は保証している。 - OS: Windows11 Pro - WSL2: ubuntu20.04LTS - エディタ:VSCode(Remote WSL) - Go1.18(WSL2) --- ### このセクション以降の流れ 初めてAPIサーバーを作る時は以下の手順で作業を進めていくのがおすすめである。 1. アプリケーションを作りHello Worldを実行する 2. データモデルを定義する 3. 各エンドポイントを作る 4. 固定値の値を返せるようにする 5. データベースを作成する 6. データベースとアプリケーションを繋ぎこむ 以降はこの流れに従って説明をしていく。 なお、2からはDay2で行なう ## APIサーバーの実装イメージを掴む **※ここで書かれているプログラムは不完全なGoプログラムです。形式言語と思ってください。** APIサーバーはデータを保存し、読み出しが出来るようなものである。 それでは先ほどの例に登場したユーザー登録APIのサンプルコードを作っていこう。 APIサーバーを作る時は最初にデータモデルを考えると良い。 今回は以下のユーザー情報を扱うものとする。 1. ユーザーID 2. ユーザー名 3. メールアドレス ```go= //ユーザーモデルを定義した構造体 type User struct{ Id string Name string Email string } ``` まずは、それぞれの機能を関数で作ってみよう。 ```go func createUser() (User,error){ //関数の実装を書く return } func readUser() (User,error) { //関数の実装を書く return } func updateUser() (User,error){ //関数の実装を書く return } func deleteUser() (User,error){ //関数の実装を書く return } ``` この関数を呼び出せばUserに対して操作が行うことができる。 では、これらの関数を呼び出すmain関数を作ろう。 Goにおけるmain関数はプログラムを実行したときに最初に呼ばれる関数である。 ```go= //おまじない package main import fmt //ユーザーオブジェクトの定義 var user = User{} //先ほど作った関数群 //main関数 func main(){ //呼び出す user,err := readUser() //実行結果を出力する fmt.Println("Result",user) } ``` このプログラムを実行してみよう。 ```bash= go build -o sample_api ./sample_api > nil ``` 関数内で何も処理を行っていないので結果はヌルとなっているはずである。 ヌルの説明は非常に難しいが、ここでは「何もない値」としておこう。emptyとはニュアンスが異なるので注意。 ※nullは無であり、emptyは空なのだ。無と空は似て非なるものである。 結果は返ってきていないがこれでユーザー情報を呼び出すことが出来た。 この例のように、APIでは読み出しをリクエストして必ずしも結果が返ってくるわけではない。 その点を注意する必要がある。 では、先ほど定義した4つの関数の中身を実装していこう。 実際のAPIではデータの保存にはデータベースなどを用いる。 今回は実装イメージを掴んでもらうためにグローバル変数でデータの保存を行う。 ```go func createUser(name string,email string) (User,error){ //オブジェクトを新規に作成してuserへ保存する(データの永続化) user = User{ Name: name, Email: email, Id: "example-user-id" } return user,nil } func readUser(id string) (User,error) { if user.Id == "example-user-id"{ return user,nil } } func updateUser(updatedUserData User) (User,error){ user = updatedUserData return user,nil } func deleteUser() (User,error){ //空のオブジェクトを代入することで値消去を実現 user = User{} return } ``` では、このプログラムの説明を簡単に行う。 ```go= func createUser(name string,email string) (User,error){ //オブジェクトを新規に作成してuserへ保存する(データの永続化) user = User{ Name: name, Email: email, Id: "example-user-id" } return user,nil } ``` ユーザー作成APIは名前とメールアドレスの情報を元に新しいユーザーを登録する。 関数の引数にはユーザー名とメールアドレスがある。 これらの引数、つまりAPIに渡す情報を一般的にリクエストデータと言う。特にGETではクエリパラメーターと呼ぶこともある。 ここでひとつ疑問に感じたであろう。 IDはリクエストパラメーターにならないのかという話である。 IDはユーザー情報を一意に示すものである。 例えば、同じマイナンバーを持つ2人存在することは許されない。 この場合、マイナンバーは誰が発行するのだろうか? それはこのプログラムの提供者である(制度的には違う用語だがシステム的にはこうである)。 つまり、一意な情報を発行するのはシステム側の責務でありユーザーの責務ではないのである(私のマイナンバーはこれが良いと言って登録できないのと同じである)。 なお、実装のやり方ではIDを込みでリクエストを渡すことでユーザー情報の更新APIを呼び出すものがあったりする。 あまり褒められた実装ではないが、突貫だと作りやすい設計なので参考程度に覚えて欲しい(実務ではやってはいけない)。 続いて読み出しと更新の説明をしよう。 ```go= func readUser(id string) (User,error) { if user.Id == "example-user-id"{ return user,nil } } func updateUser(updatedUserData User) (User,error){ user = updatedUserData return user,nil } ``` 読み出しはidを引数に指定したIDに合致する保存されたデータを返す関数となっている。 よくウェブサイトとかでhttps://example.com?user_id=example-user-id や https://example.com/user/example-user-id となっているものを見たことがあるだろう。 これらのURLにクエリパラメーターを組み込み指定された条件を満たすデータを返却するのが読み出しプログラムである。 ※ちなみに上記のリンクはそれぞれクエリパラメーターとパスパラメーターを用いている。ここの設計は上級者向けのお話になるので割愛する。 更新関数では更新するデータを引数に一括置換している。これはPOSTやPUTで実現しやすいものである。 リソースの部分更新も可能だが、愚直な実装にならざるを得ない。 最後に削除関数を見てみよう。 ```go= func deleteUser() (User,error){ //空のオブジェクトを代入することで値消去を実現 user = User{} return } ``` 削除関数では空オブジェクトを代入して元のデータを完全消去している。 これで、読み出しプログラムを読んでも空の値が取得されるだけで元のデータにはアクセス出来なくなる。 このようなリソースそのものを削除することをハードデリートと呼ぶ。 ちなみにID検索を条件に読み出すプログラムで、削除関数でIDに無効なものをセットする方法をソフトデリートと呼ぶ。 ソフトデリートはセキュリティ的にも好ましくはないが、参考程度に覚えて欲しい。 ```go= //ソフトデリート user.id="invalid" ``` ### まとめ これまでの話をまとめよう。 まず、データの保存場所(今回は変数)を用意する。 次に、データの操作(更新など)の関数を実装し、メイン関数からそれらを呼んでデータの読み書きをする。 これがAPIの基本である。 以後の説明のために、今日作ったプログラムを「ハリボテAPI」と呼ぶことにする。 さて、この例で実装したAPIは不便な部分が多い。 まず、データが永続化されないことである。このプログラムを終了してしまったらデータは消えてしまうのだ。 他にも、外部からアクセス出来ない、いちいちプログラムを実行しないといけない。 そもそも、保存とか書き込みのシナリオがあらかじめ決まっている状態で実行しているなど...。 これらのおかしい部分を1つ1つ直していくと、実際に動いているようなAPIが作れるようになる。 今後の講座では、これらを1つ1つリッチにしていきユーザー情報を扱うAPIの完成を目指す。 ただ、本質は今日説明したようなプログラムである。これらの流れや、1つ1つのやっていることをしっかりと復習して欲しい。 ## Hello, go language 最後にGoの環境構築を兼ねて簡単なプログラムを作成しよう。 (講座中は説明をするが、資料では環境構築は終わっているものとする) では、Hello Worldするプログラムを作ってGo言語デビューをしよう! 最初に以下のコマンドを実行し、フォルダ作成を行う。 ```bash= mkdir ~/example-api mkdir ~/example-api/api cd ~/example-api/api ``` なお、最終的に上記の様なディレクトリ構成であればエクスプローラーやFinderを使ってフォルダを作っても良い。 続いて、Goアプリケーションの初期化を行う。 ```bash= go mod init example.com/example1 ``` go.modはGoモジュールのパスを書いておくファイルである。 続いて同じディレクトリにmain.goを作成する。.goはGo言語で書かれたプログラムのファイルである。 そして以下のプログラムを実装する。 ```go= package main import "fmt" func main() { fmt.Println("Hello World") } ``` これらの作業を実施すると以下ようなファイル構成をなっているはずである。 - go.mod - main.go では、プログラムを実行してみよう。 Goのプログラムを実行するためにはコンパイルをし、実行バイナリを作成する必要がある。 これらは以下のコマンドで行なう事が出来る。 ```bash= $ go build $ ./example1 > Hello Worlds ``` このようにHello Worldが表示されるはずである。 デフォルトのバイナリ名はモジュール名となる。 ちなみに下のようにコンパイルオプションを指定すると任意のバイナリ名を指定出来る。 ```bash= $ go build -o main $ ./main > Hello Worlds ``` 最後にGoプログラムの基本的な開発手順を説明する。 1. go mod init <module名>でプロジェクトを作成 2. go get <パッケージ名>で便利なパッケージを追加 3. .goファイルにプログラムを作成する 4. go buildでコンパイル 5. バイナリを実行 この流れを覚えれば最低限の開発は可能となるだろう。 ここまでGoのプログラムを作る基本の流れを学習した。 Goにもfor文やif文などの構造化プログラミングを行なうための基本的な概念は実装されている。 これらの使い方については各自で調べて欲しい。 ここまでが実行出来れば先ほど作ったハリボテAPIを実行できるはずである。ぜひ試して欲しい。 Go言語の基本を学びたい方は以下のサイトをおすすめする。 ここからSlackに入ることでコンテンツにアクセス出来る。 https://gopherdojo.org/studyroom/