Skip to content

Inkclip

2022/10/01

機能

ブックマーク

ノート

(YouTube)

バックエンド

Go 採用の理由

リソースが少ないため言語仕様の変更が少ないこと。他言語の良いやり方を知るために Go を採用しました。

sqlc

標準ライブラリの database/sql,gorm,sqlx,sqlc を検討しました。

実装が早く行えることとタイプセーフなコードを書くために、sqlc を採用しました。

add_users.up.sql, user.sqlコードを書きコマンドを実行すると、user.sql.goが生成されます。 user.sql.goに対しテストを書くことで、クエリ単位で安全なコードを書くことができます。

add_users.up.sql

CREATE TABLE "users" (
  "id" uuid PRIMARY KEY DEFAULT (gen_random_uuid()),
  "email" varchar NOT NULL,
  "hashed_password" varchar NOT NULL,
  "password_changed_at" timestamptz NOT NULL DEFAULT '0001-01-01 00:00:00Z',
  "created_at" timestamptz NOT NULL DEFAULT (now())
);

user.sql

-- name: CreateUser :one
INSERT INTO users (
  email,
  hashed_password
) VALUES (
  $1, $2
)
RETURNING *;

-- name: GetUserByEmail :one
SELECT * FROM users
WHERE email = $1 LIMIT 1;

user.sql.go

const createUser = `-- name: CreateUser :one
INSERT INTO users (
  email,
  hashed_password
) VALUES (
  $1, $2
)
RETURNING id, email, hashed_password, password_changed_at, created_at
`

type CreateUserParams struct {
	Email          string `json:"email"`
	HashedPassword string `json:"hashed_password"`
}

func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
	row := q.db.QueryRowContext(ctx, createUser, arg.Email, arg.HashedPassword)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Email,
		&i.HashedPassword,
		&i.PasswordChangedAt,
		&i.CreatedAt,
	)
	return i, err
}

const getUserByEmail = `-- name: GetUserByEmail :one
SELECT id, email, hashed_password, password_changed_at, created_at FROM users
WHERE email = $1 LIMIT 1
`

func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) {
	row := q.db.QueryRowContext(ctx, getUserByEmail, email)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Email,
		&i.HashedPassword,
		&i.PasswordChangedAt,
		&i.CreatedAt,
	)
	return i, err
}

OpenAPI

Open API 定義には、swaggo/swagを利用しました。

デメリットとして、構造体を指定することで実装と仕様は一致しやすいものの、コードと実態の乖離がおこりえます。

openAPI generator のような、仕様からコードを生成するアプローチと比較すると手軽に導入できるというメリットがあります。

type renewAccessTokenRequest struct {
	RefreshToken string `json:"refresh_token" binding:"required"`
}

type renewAccessTokenResponse struct {
	AccessToken          string    `json:"access_token"`
	AccessTokenExpiresAt time.Time `json:"access_token_expires_at"`
}

// @Param request body api.renewAccessTokenRequest true "query params"
// @Success 200 {object} api.renewAccessTokenResponse
// @Router /users/renew_access [post]
// @Tags user
func (server *Server) renewAccessToken(ctx *gin.Context) {
  // ...
}

インフラ

プロジェクト間の切り替えの速さやメンテナンスのしやすさから、開発環境から本番環境まですべてコンテナで開発しています。

fly.io は初めて使いましたが、料金が比較的安く、わかりやすいため個人開発ではファーストチョイスになりそうです。

フロントエンド

API Scheema

OpenAPI から TypeScript の API スキーマを生成し、それを swr で利用する形にしています。

OpenAPI の定義と乖離した実装にならないことが、型定義から保証されます。

【OpenAPI】API スキーマから勝手に型がつく axios を作って幸せになる【openapi-typescript】 がとても参考になりました。

Chrome Extension

公式ドキュメントが充実しているため、こちら実装はしやすいです。

v2 と v3 で仕様が大きく違います。

lp

bookmark

note

extension