20200725のGoに関する記事は8件です。

【Golang】echoを使ってjwtによる認証をしてみた

はじめに

JWTによる認証について調べてみた時に軽く動くものを書いてみたときのもの

JWT(JSON Web Tokens)

クライアント・サーバー間で認証する手段の一つ。
ちなみにJWTと書いてジョットと読むらしい。

フォーマット

{base64エンコードしたheader}.{base64エンコードしたclaims}.{署名}

メリット

  • 安全
    • JWTに署名が含まれているため、改ざんがあってもチェックできるようになっている
  • 管理のしやすさ
    • URLに含むことができる文字で構成されているからhttpリクエストでの取り扱いがしやすい

などがある。

JWTに関するこれ以上の詳しい説明は今回は省く。

より詳しく知りたければ
https://jwt.io/
あたりを参考にすると良さそう。

今回実装するもの

今回はログインと、ログイン済みの時にレスポンスを取得する機能のみとする

  • POST /login: ログイン処理
  • GET /restricted/welcome: 取得したトークンを付与してリクエストした時のみレスポンスが得られる

実装

ディレクトリ構成

.
├── handler
│   └── handler.go
└── server.go

リクエストに対する処理を下記のように実装する

handler/handler.go
package handler

import (
    "net/http"
    "time"
    "github.com/labstack/echo"
    "github.com/dgrijalva/jwt-go"
)

func Login() echo.HandlerFunc {
    return func(c echo.Context) error {
        username := c.FormValue("username")
        password := c.FormValue("password")

        if username == "test" && password == "test" {
            token := jwt.New(jwt.SigningMethodHS256)

            // set claims
            claims := token.Claims.(jwt.MapClaims)
            claims["name"] = "test"
            claims["admin"] = true
      claims["iat"] = time.Now().Unix()
            claims["exp"] = time.Now().Add(time.Hour * 72).Unix()

            // generate encoded token and send it as response
            t, err := token.SignedString([]byte("secret"))
            if err != nil {
                return err
            }
            return c.JSON(http.StatusOK, map[string]string{
                "token": t,
            })
        }

        return echo.ErrUnauthorized
    }
}

func Restricted() echo.HandlerFunc {
    return func(c echo.Context) error {
        user := c.Get("user").(*jwt.Token)
        claims := user.Claims.(jwt.MapClaims)
        name := claims["name"].(string)

        return c.String(http.StatusOK, "Welcome " + name + "!")
    }
}

ルーティングなどは下記のように行う

server.go
package main

import (
    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"

    "jwt_sample/handler"
)

func main() {
    e := echo.New()

    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    e.POST("/login", handler.Login())
    r := e.Group("/restricted")
    r.Use(middleware.JWT([]byte("secret")))
    r.GET("/welcome", handler.Restricted())

    e.Logger.Fatal(e.Start(":1323"))
}

実行

まずは起動させてみる

$ go run server.go

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.1.16
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:1323

正常に起動できてることを確認したら/loginでtokenを取得する。

$ curl -X POST -d 'username=test' -d 'password=test' localhost:1323/login
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTk1OTI5MDcxLCJuYW1lIjoidGVzdCJ9.PF-0TU9aYx6CeLEN8q-EHEARkVrFQxBXYsOu173Sdno"}

取得したトークンをヘッダーのAuthorizationにsetし、/restrictedにリクエストするとWelcomeと表示される。

$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTk1OTI5MDcxLCJuYW1lIjoidGVzdCJ9.PF-0TU9aYx6CeLEN8q-EHEARkVrFQxBXYsOu173Sdno" localhost:1323/restricted/welcome
Welcome test!

また、試しに誤ったトークンでリクエストしてみると

$ curl -H "Authorization: Bearer eyJhbGciOiJIU" localhost:1323/restricted/welcome
{"message":"invalid or expired jwt"}

としっかり弾かれることが確認できる。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語でTimestamp文字列のパースに手間取った話

今までほぼPython一筋でしたが,最近Goを始めてみました。
新しい言語に手を出すと,他の言語ならすぐできるような簡単なことでも非常に引っかかりますね。
ということで,今日起きたそんな一幕をメモがてら記事にしてみました。

やろうとしたこと

Go言語で2020-01-01T09:00:00Zのような文字列から,日本時間における年・月だけ取り出して,つなげて202001のようなint型を返す。

最終的に行き着いたやり方

import (
    "strconv"
    "strings"
    "time"
)

func createYearMonth(stringTimestamp string) int {
    // 1. 文字列を日時オブジェクトへとパース
    // "2020-01-01T09:00:00Z" -> 2020-01-01 09:00:00 +0000 UTC
    t, err := time.Parse(time.RFC3339, stringTimestamp)
    if err != nil {
        fmt.Println("Parse Error: ", err.Error())
    }

    // 2. timezoneの変更
    // 2020-01-01 09:00:00 +0000 UTC -> 2020-01-01 18:00:00 +0900 JST
    loc, _ := time.LoadLocation("Asia/Tokyo")
    t_jst := t.In(loc)

    // 3. Year, Month部分を文字列のまま取得し結合
    // "2020-01-01 18:00:00 +0900 JST" -> ["2020", "01", "01 18:00:00 +0900 JST"]
    splitTimestamp := strings.Split(t_jst.String(), "-")
    // "2020" + "01" -> "202001" -> 202001
    intYearMonth, _ := strconv.Atoi(splitTimestamp[0] + splitTimestamp[1])

    return intYearMonth
}

詰まったところ

1. 文字列を日時オブジェクトへとパース

最初は以下のようにtimestampのフォーマットとなるlayoutを自分で適当に定義してパースしようとしました。

layout := "1970-01-01T00:00:00Z"
t, err := time.Parse(layout, "2020-01-01T09:00:00Z")

ただし,これではうまくパースしてくれず。

timeパッケージの公式ドキュメントを見たところ,パッケージ側でconstantとしてフォーマットがいくつか用意されていることを知り,RFC3339という今回の入力に最も近いもの"2006-01-02T15:04:05Z07:00"をlayoutとして使いました。

2. timezoneの変更

最初は以下のようにパース部分でtimezoneの指定も同時に行おうとしていました。

loc, _ := time.LoadLocation("Asia/Tokyo")
t, err := time.ParseInLocation(time.RFC3339, stringTimestamp, loc)

しかしこれをやっても,得られたtUTCのままです。

どうやら上記のParseInLocationにおけるtimezoneの指定は,入力文字列からはtimezoneが判断できない時のためのもののようです。

今回はそもそも入力が"2020-01-01T09:00:00Z"のようにUTCであることが明記されていたので,意味をなさず。

timezoneそのものを変更するには,

loc, _ := time.LoadLocation("Asia/Tokyo")
t_jst := t.In(loc)

のように後から変換を行うのが正解でした。

3. Year, Month部分を文字列のまま取得し結合

こちらはやや力技です。

.Year().Month()で簡単に年・月を取得できるので,単純に考えれば,

stringYearMonth := fmt.Sprintf("%d%d", t.Year(), t.Month())

で澄むはずの話です。

が,これで得られるのは,"20201"であって"202001"ではないのです。

t.Month()がint型なのが問題でした。

ということで,少し遠回りですが,日時オブジェクトを文字列に再変換した後,ハイフン-で文字列を分けて年・月だけ繋げる,という廻りくどいことを行っています。

ここもう少しスマートにやる方法あるかも,,,

2020.07.26 追記

なんてことはない,%dで文字数を指定すれば簡単にできました。

stringYearMonth := fmt.Sprintf("%04d%02d", t.Year(), t.Month())

まとめ(?)

とりあえず動いた,よかった!

一応無理やりポイントとなる話を拾い出すと,

  • 文字列timestampをパースする際のlayoutは,なるべくtimeパッケージ側で用意されているフォーマットを使う。
  • timezoneの変更は,.In()を使う。

あたりかなと。

もっとこうやると簡単にできるよ,というのがあれば是非お寄せください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacでGoの開発環境構築

Mac環境

% sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.5
BuildVersion:   19F101

% echo $SHELL
/bin/zsh

% zsh --version
zsh 5.7.1 (x86_64-apple-darwin19.0)

環境構築手順

  1. Goの公式サイトから、Goの本体をダウンロードしましょう。
    スクリーンショット 2020-07-25 17.29.40.png

  2. ダウンロードしたpkgファイルをダブルクリックして、インストールしましょう。
    特に注意すべきところはありません。
    スクリーンショット 2020-07-25 17.33.03.png

  3. Goが正しくインストールされたか、コマンドで確認してみましょう。

    % /usr/local/go/bin/go version
    go version go1.14.6 darwin/amd64
    % go version
    go version go1.14.6 darwin/amd64
    
  4. Goは、外部のライブラリが格納される場所を、環境変数GOPATHで管理しています。
    ここでは、まず空のディレクトリを作りましょう。

    % mkdir ~/go
    

    それから、GOPATH環境変数の設定を「~/.zshrc」の末尾に追記しましょう。

    ~/.zshrc
    export GOPATH="$HOME/go"
    

    「~/.zshrc」は、ユーザーのログイン時に一度だけ読み込まれるため、ファイルを書き換えただけでは設定が反映されません。少し乱暴ですが、

    % source ~/.zshrc
    

    で強制的に「~/.zshrc」を読み込ませることができます。
    最後に、環境変数GOPATHが設定されていることを確認しましょう。

    % echo $GOPATH
    /Users/[ユーザー名]/go
    
  5. これでGoのインストール作業は完了です。

動作確認

  1. 下記「Hello, World」プログラムを書いて、Goで実行して見ましょう。

    hello.go
    package main
    
    import "fmt"
    
    func main() {
      fmt.Println("Hello, World")
    }
    
  2. 下記コマンドでビルドして、同じディレクトリに「hello」実行ファイルができることを確認しましょう。

    % go build hello.go
    
    % ls
    hello       hello.go
    
  3. 「hello」実行ファイルを実行してみましょう。

    % ./hello
    Hello, World
    

    Goは基本的にコンパイルを前提とした言語ですが、ビルドプレセスを隠蔽したコマンドgo runを使えば、ソースファイルを直接実行することもできます。

    % go run hello.go
    Hello, World
    

参考ページ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitLabの環境変数設定をラクにした話

こんにちは。NRI aslead チームで新サービス開発をしているk5-saitoと申します。
aslead チームでは、GitLabを開発ツールとして活用しているのですが、環境変数を登録する手間を大きく削減したので、その話を書きました。

TL;DR

  • CLIフレームワークとして、cobraを使うことでKubectlライクなコマンドを簡単に作ることができた。
  • クロスコンパイルが容易にできるので、Windows、MacOS、GitLabCIでの実行も可能

背景

プロジェクトのCICDパイプラインとしての利用

ある大規模アプリケーションにCICDを導入して、開発の生産性を向上しようというプロジェクトがありました。しかしアプリケーションが大規模になればなるほど、環境変数は多くなっていき、変更も頻繁に走ります。毎回変更があるたび、環境変数にミスがないかを確認して、手作業で変更して、ということを繰り返していました。

実際のプロジェクトの環境変数の具体例としては、

  • サブシステム名
  • デプロイパス
  • パッケージリポジトリ
  • データベース名

などなど、合計で30個くらいあって、実際の様子がこんな感じでした。実際の画面.png
ぞっとする量ですね。

プロジェクトのインフラ構築job実行基盤として

当初aslead Remoteを社内展開する際、各人のユーザIDや認証情報、接続ポート番号をVariablesに入力をして、パイプラインを回して、インフラを構築していました。
環境変数としては6つほどだったのですが、それが300人くらいに対応する必要がありました。これも手動でコピペをしてパイプラインを操作していました。

これをどうにか楽にできないか、ということで環境変数の入力を簡単にするツールを作ることにしました。

作るもの

  • GitLabのVariablesをYAMLで変更できるようなツール
    • gv-ctlという名前にしました。
  • 手元のマシン(Windows)でも動かしたいし、将来的にはGitLabCIパイプラインでも実行したい

python-gitlabというCLIもあるのですが、YAMLファイルからshell芸をするよりかは、Cobraを使った方が便利でした。
Variablesは、認証情報などが入るため隠す必要があります。ここが実現に向けて難しいところでした。

作り方

大まかには3ステップに分かれます。
1. cobraコマンドを使って、雛形を作成
2. 実装する
3. ビルドする

2と3については、プロジェクトによって異なるので、省略します。
cobra 用のプロジェクトのひな型の作成は方法は、

$cobra init gv-ctl --pkg-name gv-ctl

今回は、$GOPATH配下外で作業するので、--pkg-name を追加しました。またそれに伴い、go moduleを有効化をしておいてください。

$ cobra add setup

これでサブコマンドが作れます。
後は実装するだけ。

注意するところ

フラッグ変数の受け渡し

cobra.Command.PersistentFlags().StringP(...)cobra.Command.PersistentFlags().StringVar(...)があります。
短縮系が使えるか、変数のアドレスで指定するかという違いがあります。
私は、短縮形を使いたかったので前者を採用

(サブ)コマンドにflagを登録

func init() {
    setupCmd.PersistentFlags().StringP("filePath", "f", "gvctl.yaml", "variables config file path")
    rootCmd.AddCommand(setupCmd)
}

呼び出し

func(cmd *cobra.Command, args []string) {
  filePath, err := cmd.PersistentFlags().GetString("filePath")
  
引数 StringPの入力値 StringVarの入力値
第1引数 フラグ名 変数のアドレス
第2引数 短縮系 フラグ名
第3引数 デフォルト値 デフォルト値
第4引数 説明 説明

YAMLファイルの読み込み

viperを使って、yamlファイルを読み込むことができます。入れ子の構造体も良しなにやってくれて楽。

type Variable struct {
    Key       string
    Value     string
    Protected bool
    Masked    bool
}

type Manifest struct {
    BaseURL   string
    Type      string
    ID        string
    Token     string
    Variables []Variable
}

func readManifest(filePath string) (manifest *Manifest, err error) {
    // YAMLファイルを開き、値を手に入れる
    viper.SetConfigFile(filePath)
    err = viper.ReadInConfig()
    if err != nil {
        return manifest, err
    }
    err = viper.Unmarshal(&manifest)
    if err != nil {
        return manifest, err
    }
    return manifest, nil
}

効果

非公開リポジトリを用意し、頻繁に変更になるVariablesをバージョン管理することができた。
Variablesは隠す必要があるので、別リポジトリを作って、そこでパイプラインを運用する形で運用しました。

また、インフラ構築部分でも1800回分のコピペを削減し、私たちの労力を大幅に削減しました。

おわりに

他にもやりたいことを実現する違うアプローチはあると思いますが、ソースを乗っけておきます。実際に社内で使われたものとそのままというわけではないのでしっかり動くことは保証できませんが、反響があれば順次拡張していければいいなと思っています。

https://github.com/lottotto/gv-ctl

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語はじめた勉強メモ1 フォーマッターとコメントとセミコロン

この記事について

Cでプログラミングが苦手になりJavaで仕事をしJavaScriptでフリーランスになった私がGoエンジニアを目指すためにがんばっている様子の勉強メモです。半年以内にGo言語で開発プロジェクトに参加することが目標。

実は以前にGoとMangoDBで画像アップローダ&Photoギャラリーを5日集中で作ったのですが、フレームワークを使ったことと時間をかけられなかったこともあってほぼGoの基本的なところが抑えれらていないと感じたため0から再度学び直すことにしました。

学習プラン

  • Effective Goをチラ見して雰囲気をつかむ(Done)
  • A Tour of Goをやってみる(Done)
  • UdemyのWeb developmentのコースをやってみる(Now)
  • 何か作ってみる

Effective Goの最初をかいつまんでみる

フォーマット

formatterが標準搭載だそうです。好みの戦争が起きない(はず)
意外と開発時にあとで整えようとするとGitのDiffの関係でなかなかやりにくかったりするフォーマットの統一がすでにあるのは嬉しい。

  • インデントはTab(スペース派閥涙目)
  • 行の長さの制約なし
  • CやJavaと比較して少ないかっこ()を目指した文法

コメントの書き方

見慣れた感じのやつ。

/* こんな感じ
 複数行OK */ 

もしくは

// こんな感じ

セミコロンについて

CやJavaのように行末セミコロンは不要だが、コンパイル時に挿入される。使うのは、IF文の条件式中が複数行にわたる時、典型的なFor文とかとか。

if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

for i := 0; i < 10; i++ {
    sum += i
}

ルールとしては、「Statementを終了させる可能性のあるTokenの後ろに改行があれば、セミコロンを挿入する」とのことです。Effective Goより

“if the newline comes after a token that could end a statement, insert a semicolon”.

とりあえずよくわかんないですね。Tokenは意味のあるかたまりで、ここではコンパイラから見た意味合いのTokenなのだと思います。例えばver, importなどの予約語もToken、自分でつけた変数名は識別子というToken、=, >, - などの演算子、{, }, ; などの記号もTokenなどなど。

何はともあれ、改行は多少気をつけないといけない事はわかりました。なので・・・

if i < f() {
    g()
}

これはOKだけど

if i < f()  // wrong!
{           // wrong!
    g()
}

こうは書けない。(ちなみにこの記述がOKの言語でもこう書かれると私はちょっとイラッとします)

if i < f();
{          
    g();
}

なぜならこう解釈されてしまうためです。(多分)

参考文献

Effective Go

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

goの静的解析が理解できない

go/analysis 周りは tenntennさんなどをはじめいろいろ記事が出てるけど、どうにも理解できない部分が多いので、理解を進めるためにわからない部分をまとめておく。

わからないことのリスト

  • unitchecker / singlechecker / multichecker の棲み分け
  • go vet -vettools= の価値
  • go vet -json が吐き出す JSON もどき のなぜ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React(Next)/Go/GraphQL/Fargateで個人開発したサービス「読み方アンケート」をリリースするまで

個人開発である程度しっかりしたものを作ってリリースまでするってなかなか大変だと思います。

そこには技術的な壁がある人もいれば、なかなか本業が忙しくて時間がとれない人、途中まで開発して「こんなん誰が使うねん...」モードになってしまい頓挫する人。

そんないくつかの壁をなんとか乗り越え、
この度晴れて個人開発サービスである読み方アンケートをリリースできたのでまとめたいと思います。


〜こんな人に読んで欲しい〜
・ 個人開発のモチベーションをあげたい人
・ 個人でWebサービスをリリースするまでにするべきことを知りたい人
・ 以下の技術等に興味のある人
 ・ React(Next.js)
 ・ Go
 ・ GraphQL
 ・ AWS Fargate
 ・ AWS CodePipeline
 ・ GitHub Actions
 ・ TypeScript
 ・ Atomic Design
 ・ Recoil
 ・ Vercel

書くこと(index)

サービス概要

サービス名: 読み方アンケート
URL: https://yomikata-enquete.com/
yomikata-ogp.png

以下のように投票形式で読み方のデータを蓄積することで、世間一般的な読み方を知ることができる読み方情報蓄積サイトです。
スクリーンショット 2020-07-25 8.43.45.png

ITは特にそうですが、専門用語の読み方って人によって違ったり、「え、そんな読み方すんの!?」みたいなのもあってややこしすぎますよね...

Djangoを「でぃーじゃんご」って読んでて陰で笑われてたあの頃...

そんな恥ずかしい読み間違いとサヨナラしてもらうために作りました

積極的に使ってくれるユーザーには恩恵があるようにしたいという思いがあったので、1つのミソとしては上で貼った画像のように読み方を聞いた人のプロフィールは単語ページの上部に大きく表示されます。

読み方を調べようとページに訪れた人の目に止まるので、検索流入によるアカウントの宣伝権利を半永久的に獲得できるというわけです。

また、以下のように投票した数や、その人のおかげで集まった投票数もカウントしているのでこの数字が大きい人のアカウントも目立つようにするつもりです(リリース予定)
スクリーンショット 2020-07-22 18.01.22.png

「シェアして集めた投票数」はログインした状態で各単語ページのシェアボタンからシェアすると、そのリンク経由でページを訪れた人が投票するとカウントアップされていく仕組みです
スクリーンショット 2020-07-22 18.01.44.png

目指すところとしては
■ このサイトで調べればある程度世間一般的な読み方がすぐ分かる
■ 「◯◯ 読み方」で検索したときに検索結果上位にでるサイトにすること

という感じです。

是非使ってみてくださいm(..)m

サービス構成

技術的にどのような構成で作られているかを紹介します。

サービス構成概要

  • 開発環境
    • Docker
  • フロントエンド
    • React(Next)、TypeScript
  • バックエンド
    • Go
  • データベース
    • RDS(PostgreSQL)
  • CI
    • GitHub Actions
  • CD
    • CodePipeline
  • 画像保存
    • S3
  • バックエンドデプロイ先
    • ECS(Fargate)
  • フロントエンドデプロイ先
    • Vercel

フロントエンド

Reactで作成しています。

また、今回はSEOが重要だったためSSR(サーバーサイドレンダリング)が可能なフレームワークであるNext.jsを使用しています。

Nextは目を見張るスピードで進化していて、かなり使いやすいので使ったことない人は是非使ってみてください。

ルーティングの設定が不要ですし、最近(2020年5月)のリリース(ver9.4)で実装されたFast Refleshはかなり開発効率あがります。
コードを変更したときのホットリロードがかなり速く、しかもstateを保持したままリロードされます。

例えばこれまでフォームのテキストボックスとかに値を入力した状態でコード変更すると頑張って入力したのが全部消えていたのが、それらの情報は保持されたまま修正箇所のみ変更されます。はい、最高です。

他にも便利な機能目白押しですb

コンポーネントの設計はAtomic Designをベースに行っています。

適切な粒度でコンポーネントを分割することで、開発効率や保守性が高くなります。詳しくは後述します。


また、APIのやりとりはGraphQLで行っています。

TypeScriptとGraphQLの組み合わせはハッキリ言ってフロント開発の世界が変わります。RESTには戻れなくなります←

これもコードレベルの話は後述します。

バックエンド

バックエンドにはGoを使用しています。

普段は仕事でフロントエンドの開発をメインにしており、ずっとGoを使って自分で0からバックエンドの構築をしてみたいと思っていたので色々試せて面白かったです。

GraphQLを使用するので型付きの言語が相性が良く、マイクロサービスでサクッとAPIサーバーを立てるのにGoは適しているので今回はうまくハマりました。

DBとのやりとりの部分はGoのORM(オーアールマッパー)ライブラリであるgormを使用しました。

インフラ

マイクロサービス構築のノウハウが得たいというのもあり、フロントとバックは別々のサーバーにデプロイしようということは決めていました。

AWSをちゃんと自分で触ったことがなかったため、バックエンドはAWSを使って勉強を兼ねることにし、フロントはVercel(旧Zeit Now)にデプロイしています。

Vercelはフロントのデプロイ先としてめちゃくちゃオススメです。詳しくは後述します。

AWSでは主に以下のサービスを利用しています。
・Fargate(ECS)
・RDS(PostgreSQL)
・S3
・ECR
・ALB
・CodePipeline

AWS周りは事前の知識が皆無だったこともありかなり苦労しましたが、今回の開発を通してだいぶレベルアップできたかなと思います。個人開発の醍醐味ですね。

あとは、GitHubにpushした際にテストを実行したりするのにGitHub Actionsを使用しました。途中まではCircleCIを使ってましたが、やはりGitHub内で完結するのは魅力的です。

なぜリリースまで辿り着けたか

ちょっと技術的な話から離れて、個人開発をリリースまで持っていくためのTips的なものを紹介します。

モチベーションを明確にする

これは個人開発だけでなく趣味でチーム開発をする時とかでも大事なんですが、

この開発におけるwhyを明確にする

ということが大切です。

「開発におけるwhy = モチベーション」と思ってますが、大体は以下のどっちがかなと思います。

1.アイディアを形にしたい
 ・過程より結果重視
 ・使ってもらうことが最優先
 ・作る時ワクワク、これ使われないなーと思うと一気にやる気↓↓

2.開発を通して技術を学びたい
 ・結果より過程重視
 ・学習ありきの開発
 ・最悪使われなくても良いや

チーム開発の場合、上記のようなメンバーのwhyが揃ってないパターンは大体頓挫します。

どちらが良い悪いではなく、自分は何故個人開発をやっているのかを理解して常々選択の軸にすることでブレずにリリースまで持っていける可能性が上がります。

因みに自分は今回の場合、2が70%くらいの比重を占めています。

感覚としてこれ以上2の比重が高くなってしまうとモチベーションを保って開発を続け、リリース日を迎えることがむずかしいです。

タスクを見える化する

仕事だけでなく個人開発でも見える化はめっちゃ大事です。

見える化してないと夜パソコンの前に座ってもどうしてもやる気出てこなかったり、「どこ実装しようかな〜」って考えながら現状でできてるアプリを動かして目についたとこ実装していくみたいな無駄の多い開発になります。

そして「あ、こんな機能ありやな〜」て移動中とか思いついたアイディアが忘れ去られたりします...


具体的にはTrelloに代表されるようなタスク管理ツールを使います。

自分の場合はClickUpを使用しています。

ClickUpは無料で、プライベートな空間で使用できますし、タスクレーンのカスタムもできたり、Slack通知も簡単に設定できるので仕事でも重宝しています。

以下のようにレーンを分けたりラベルを付けて優先順位やタスクタイプを見える化します。(テンション上がる)(一部都合上隠しています)
スクリーンショット 2020-07-22 21.18.23.png

「その先」を妄想する

たぶん個人開発者あるあるだと思います←

サービスをリリースした未来を妄想します。

  • サービスが人気になって「◯◯作った人ですよね!」って認識される未来
  • そこで学んだ技術を生かして仕事で活躍できる未来
  • そのサービスがきっかけでバイネームで仕事を依頼される未来
  • 大当たりして個人開発で食べていける未来

冗談のように聞こえますが、個人開発をずっと続けれている人達はこの妄想力が段違いなんじゃないかと思ってます←

必須スキルかもしれません。

Webサービスをリリースするまでにすること

では個人でWebサービスを開発、リリースするまでどんなことをしなければいけないのか紹介します。

これから開発する方は参考にして頂ければと思います。

(抜け漏れある気がするのでコメントで補足してもらえると助かりますm(..)m)

※ * がついている項目は必須

システム構成を考える*

まずは当たり前ですが構成を考えます。

  • フロント、バックに何を使うか
  • DBは使うのか、使う場合はRDBにするのかNoSQLにするのか等
  • デプロイ先はどこにするのか

ワイヤーフレームを作成する

これは必須ではありませんが、サイトの設計書とも呼ばれるワイヤーフレームを考えた方が開発はスムーズに進みます。

手書きでざっくりでも良いですし、得意な人はXDやFigmaを使って作成しても良いでしょう。

因みに自分は今回は何にも作らなかったです←

なんか個人開発くらい場当たり的に画面作ってみたかったのです...

DB設計をする*

データベースを使用する場合はDB設計は必須です。

これは場当たり的にはやってはいけません

DB設計はどうしても数をこなさないと上手くできないと思いますが、考えることを諦めてはいけません。

実装したい機能からどんなテーブルが必要なのか、将来機能追加したときに破綻しないか等々考慮してテーブルや項目を洗い出していきましょう。

開発する*

ここまでで開発は進めれる状態になったのでつらつらと開発しましょう。

頑張りましょう。

頑張りましょう。。。

デプロイ先を考える*

世に公開するためにはどこかのサーバーに作成したアプリをデプロイしなければいけません。

使用している技術やCI/CDとの相性、ランニングコスト等比較して早い段階で決めておきましょう。

CI/CD環境の整備をする

継続して安定的なサービスを提供し続けるためには今やCI/CD環境は必須と言えます。

GitHub Actions、CircleCI、CodePipeline等々選択し、実装していきましょう。

404や500ページのことも考える

意外と盲点なのが存在しないページが開かれた場合の404ページと、サーバーエラーが発生した場合の500ページです。

一般的なサービスは大体このあたりも綺麗に作成されています。

因みにNextの場合、/pages/以下に404.tsx_error.tsxのようにファイルを作成しておくだけでカスタムできるので最高です。

参考までに読み方アンケートは以下のようにしています。
(500ページにTwitterのタイムラインを載せるTipsはnoteから学びました。サーバートラブルがあってもユーザーに状況を伝えることができます)
スクリーンショット 2020-07-22 22.32.13.png
スクリーンショット 2020-07-22 22.34.10.png

meta,titleタグを整備する*

Webサービスの場合、meta,titleタグの設定は必須です。

検索エンジンやブラウザに伝える情報を記述します。

これらはHTMLのheadタグの中に記述するもので、titleタグはページのタイトル情報でブラウザのタブに表示されるものがそうです。

metaタグは色々な種類があり、SNSにURLをシェアした際の画像や文章を設定するOGPの設定や、ページの説明文などを記述していきます。

faviconやtouch-iconを作成する*

faviconは以下のようなブラウザのタブ等に表示される画像です。間違ってもデフォルトのままリリースしないようにしましょう。
スクリーンショット 2020-07-24 14.01.59.png

apple-touch-iconはスマホやiPadでホーム画面に追加された時の画像を設定します。

faviconに比べ優先度は低いですが、設定しておくにこしたことはありません。(PWA化する場合は必ず設定しておきましょう)

以下のように180×180サイズを設定しておけば大丈夫(のはず)です。

<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />

各ブラウザ、端末での動作を確認する*

一通りシステムが形になってきたら各ブラウザや、スマホ等でレイアウト崩れが起きていないか、機能が正常に動くかを確認しましょう。

PCでスマホのレイアウトを確認する場合、各ブラウザのDeveloper Tool等を使うと思いますが、実機で見たらレイアウトが異なることがあるのでちゃんと実機で見てみることが大事です。

IEは、、、個人開発であれば無視しましょう←

外形監視を導入する

これは最初からやる必要はないと思いますが、個人開発でやってみておくと良い経験になるし仕事に生かせると思います。

Sentryに代表される外形監視ツールは、ユーザーが操作している際フロントエンドで何かエラーが起きたときにSlack等に通知をしてくれるものです。

フロントエンドのエラーはユーザーの使い勝手に直結するので、知らない間にエラーが起きていてユーザーが離れていくということを避けられます。

余裕があれば導入してみましょう。(個人開発レベルであれば無料で使えます)

ドメインを取得する*

https://yomikata-enquete.com/yomikata-enquete.comの部分です。

ドメインの取得は必須です。年間で数百円とかなので惜しまずに買いましょう。

色んなサービス経由で買えますが、私はお名前ドットコムで買っています。

どこでも対して差はないので大丈夫です。

Google Analyticsへの登録

Google Analyticsは必須ではないですが、絶対設定しておいたほうが良いです。

ユーザーが今何人サイトのどのページを見ているかリアルタイムで見れたり、日や月単位で何人に訪問されたのか、直帰率はどのくらいか等々分析することができます。

リリース情報を公開したあとなんかはずっとリアルタイムの数字見てワクワクできます。そりゃあもうビール3杯くらいそれでいけます。

Google Search Consoleへの登録

Google Analyticsがユーザーの分析なら、Search Consoleは検索エンジンの分析です。

どんな検索ワードでサイトが訪問されたのか、googleにindex(検索結果にでるように認識されていること)されているか、されていない場合はリクエストしたり等々

検索流入に対する対策をたてるのに使用できます。GAと並んでとても重要です。どちらも無料で使用できるので導入しておきましょう。



単に開発するだけでなく、上記のことを乗り越えて初めてリリースを迎えることができます。

頑張りましょう!b

フロントエンド

ではここからは実装レベルでの細かいTips等を紹介します。

ざっくりとの紹介になるので、気になる部分がある人は質問等で聞いてもらえたらと思いますm(..)m

フロントエンドでは以下のライブラリを使用しています(一部)

■ React、Next.js、TypeScript

このセットはかなりDX(Developer Experience)が向上します。

VueとReactを比較した記事も書いていてけっこう読んで頂いているので参考にしてみてください。

あらためてReactとVueを比較してみる〔2020年最新版〕

■ eslint、prettier

コード自動整形や静的解析のために使用。

ESLintとPrettierを連携してVSCodeで保存時に自動で整形されるための設定は以下で紹介しています。(GitHubのリポジトリもあります)

良い感じのReact開発をすぐに始めれるテンプレートプロジェクトを作ったので共有します

■ GraphQL

  • https://graphql.org/
    GraphQLはFacebookが開発したRESTに代わる新しいAPIの規格です。
    schema(スキーマ)と呼ばれるAPIの設定ファイルを基盤にAPIを作成していくことからスキーマ駆動開発と呼ばれる開発フローが可能になります。

  • https://www.apollographql.com/
    ApolloはGraphQLを使うための様々な便利な機能を提供しているライブラリで、クライアントもサーバーもサポートしています。キャッシュができたり、reactの場合、hooksライクにAPI実行できたりするので非常に便利です。以下のgraphql-codegenと組み合わせることでschema作成→API呼び出しが超簡単になります。

  • https://github.com/dotansimha/graphql-code-generator
    schema情報から、API実行するための口や型情報を自動生成してくれるためのライブラリです。
    以下に簡単なサンプルを紹介します。

1.schemaを定義
以下はIDから1つの単語を取得するクエリ定義の例です。

# queryはデータ取得。その他mutationやsubscription
schema {
  query: Query
}

# データ取得はQueryに定義する、引数や返却型を定義
type Query {
  word(wordID: Int!): Word!
}

# 型情報
type Word {
  wordId: Int!
  title: String!
}



2.クエリを定義する

schemaを基に取得するためのクエリを定義します。
RESTのようにデータを全て受け取るのではなく、ここでフロントが必要なデータのみ指定できるのもGraphQLの強みです。

word.graphql
query word($wordID: Int!) {
  word(wordID: $wordID) {
    wordId
    title
  }
}



3.自動生成する
以下のコマンドを実行するとschema情報を基にファイルが自動生成されます。(別途設定ファイルの記載は必要。package.jsonのscriptに定義すると楽)

graphql-codegen --config codegen.yml



4.生成されたファイル

以下のようなファイルが自動生成されます。

type Wordの型情報や、useWordQueryなどのAPI実行用の口ができていることが確認できます。

graphqlClientApi.tsx
import gql from 'graphql-tag';
import * as ApolloReactCommon from '@apollo/react-common';
import * as ApolloReactHooks from '@apollo/react-hooks';
export type Maybe<T> = T | null;
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
};

/** データ取得はQueryに定義する、引数や返却型を定義 */
export type Query = {
  __typename?: 'Query';
  word: Word;
};

/** データ取得はQueryに定義する、引数や返却型を定義 */
export type QueryWordArgs = {
  wordID: Scalars['Int'];
};

/** 型情報 */
export type Word = {
  __typename?: 'Word';
  wordId: Scalars['Int'];
  title: Scalars['String'];
};

export type WordQueryVariables = {
  wordID: Scalars['Int'];
};

export type WordQuery = { __typename?: 'Query' } & { word: { __typename?: 'Word' } & Pick<Word, 'wordId' | 'title'> };

export const WordDocument = gql`
  query word($wordID: Int!) {
    word(wordID: $wordID) {
      wordId
      title
    }
  }
`;

/**
 * __useWordQuery__
 *
 * To run a query within a React component, call `useWordQuery` and pass it any options that fit your needs.
 * When your component renders, `useWordQuery` returns an object from Apollo Client that contains loading, error, and data properties
 * you can use to render your UI.
 *
 * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
 *
 * @example
 * const { data, loading, error } = useWordQuery({
 *   variables: {
 *      wordID: // value for 'wordID'
 *   },
 * });
 */
export function useWordQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<WordQuery, WordQueryVariables>) {
  return ApolloReactHooks.useQuery<WordQuery, WordQueryVariables>(WordDocument, baseOptions);
}
export function useWordLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<WordQuery, WordQueryVariables>) {
  return ApolloReactHooks.useLazyQuery<WordQuery, WordQueryVariables>(WordDocument, baseOptions);
}
export type WordQueryHookResult = ReturnType<typeof useWordQuery>;
export type WordLazyQueryHookResult = ReturnType<typeof useWordLazyQuery>;
export type WordQueryResult = ApolloReactCommon.QueryResult<WordQuery, WordQueryVariables>;



5.APIを実行する

上記で作成したAPIから取得したデータを表示したい場合、以下のようにするだけです!
型情報を別途定義する必要もなければ、error情報やloadingの情報も含まれています。
これがめっっっっっちゃ楽です。
gif.gif

■ Atomic Design

Atomic DesignはアメリカのWebデザイナーBrad Frost氏が考案・提唱したデザインシステムです。

細かい説明は色々なサイトで紹介されているので省きますが、自分がフロント構築する場合はAtomic Designベースでやります。

「ベースで」というのが大事で、自分の中でコンポーネント分割はあくまで概念であって、厳密なルールを強制するものではないと思っています。

なので、ベースはありつつもプロジェクト毎にメンバーで話し合って適宜カスタムしていって良いと思います。



コンポーネント分割というと「再利用」というメリットがフォーカスされがちですが、TypeScriptと組み合わせることで実装に制約を注入できるというのも大きいです。

例えば以下のようにloadingというpropsを必須のpropsとして定義しておくことでボタンを配置するときに押下時のloadingを設定しないとエラーになるので実装者にルールを意識してもらえます。

MainButton.tsx
import React, { ReactNode } from 'react';
import { Button } from 'semantic-ui-react';

type Props = {
  children: ReactNode;
  loading: boolean;
  onClick: () => void;
};

export const MainButton = (props: Props) => {
  const { children, loading, onClick } = props;
  return (
    <Button loading={loading} disabled={loading} onClick={onClick}>
      {children}
    </Button>
  );
};

loadingを設定しないとエラーになる。
スクリーンショット 2020-07-24 16.12.33.png

■ Semantic UI React

CSSライブラリとしてSemantic UI Reactを使用しています。

BootstrapやMaterial UIだと人と被るしな〜と思っている人にSemantic UI Reactはオススメです。

サンプルの例が丁寧ですし、色の選択も豊富です。もちろんレスポンジブやGrid系の仕組みも提供されていますし、読み方アンケートのTOPページで実装しているようなアニメーションも簡単に実装できます。

■ styled-components

ReactではCSSのあてかたに様々なパターンがあり、宗教戦争がおきがちです←

自分はstyled-components(以下SC)を好んで使います。

CSS-in-JSの中でもSCを使用している理由はscssライクに使えるのでもし方針の変更があってもscss→SC、SC→scssといった変換が容易というのと、hoverなどの擬似要素やメディアクエリも同様に使えるところが大きいです。

以下は読み方アンケートで使用している、リンクテキスト用のコンポーネントの例です。

LinkText.tsx
import React, { ReactNode } from 'react';
import styled from 'styled-components';

import { COLOR } from 'util/const';

type Props = {
  children: ReactNode;
  onClick: () => void;
};

export const LinkText = (props: Props) => {
  const { onClick } = props;
  return <SLink onClick={onClick}>{props.children}</SLink>;
};

const SLink = styled.p`
  color: ${COLOR.LINK};
  cursor: pointer;
  margin: 0;
  &:hover {
    text-decoration: underline;
    color: ${COLOR.LINK_HOVER};
  }
`;

scssの書き方がそのまま使用できるのでhover時のスタイル変更もjs内で簡単に適用できます。

問題としてあげられるのが、SCを使うと外部ライブラリから読み込んで使用しているコンポーネントか、SCのコンポーネントかパっと見で分かりづらいので、自分はSプレフィックスを付けるルールをよく使います。

■ react-spinners

ローディングをちょっと工夫したい人は是非。
読み方アンケートだと以下のローディングがそうです。
gif.gif

■ Recoil

上記のローディングを表示する箇所に使ってみています。

以下のような状態用atomを定義して

loadingState.ts
import { atom } from 'recoil';

export const loadingState = atom({
  key: 'loadingState',
  default: false,
});

trueの場合、黒い背景(Dimmer)上にローディングアイコンが表示されるようにコンポーネントをセットしておきます。

あとは下記のように任意の場所でstateを設定することでグローバルな状態管理が可能になります。

import React from 'react';
import { Button } from 'semantic-ui-react';
import { useSetRecoilState } from 'recoil';

import { loadingState } from 'store/recoil/loadingState';

export const UserPublish = () => {
  const setRecoilLoading = useSetRecoilState(loadingState);

  const onClickButton = () => {
    setRecoilLoading(true);
  };

  return <Button onClick={onClickButton}>ボタン</Button>;
};

まだ複雑な状態管理は試してないですが、かなり可能性を感じます。

■ react-alert

hooksライクに使えるので重宝しています。

なんでこんなにstarが少ないんだろう..

バックエンド

■ Go

今回の規模のサービスでは特に困ることはありませんでした。ビルドが早いのは良いですね。

■ gorm

  • https://github.com/go-gorm/gorm
    ORMライブラリとしてgormを使用しています。goのORMでは1番シェアが大きいのではないかと思います。

DBとのやりとりがシンプルになりますし、素のSQLも簡単に実行できるので助かりました。

■ GraphQL

  • https://github.com/99designs/gqlgen
    GoでGraphQLを扱うためのライブラリとしてgqlgenを使用しています。 フロントエンドと同様、shemaファイルを基に型情報やresolverの情報を自動生成できるので重宝します。

また、GraphQL Playgroundが含まれているので、バックエンドだけ実装した後GUI上で簡単にAPIを実行した結果を確認できるのでわざわざAPI実行のために別のツールを導入する必要がなくなります。

■ jwt-go

一般的なJWT系のライブラリと同じ感じで使えます。

■ go-chi

■ CORS

  • https://github.com/rs/cors
    CORSの設定に使用。何もしないと別ドメインからのリクエストはエラーになるのでマイクロサービスの場合は設定必須。

インフラ-環境周り

読み方アンケートで設定しているインフラや環境周りを紹介します。

■ Docker

開発環境はDockerで構築しています。

nginxを含めてリバースプロキシすることでhttp://localhost:3000/ではなく、http://localhost/でアクセスできるようにしています。(色んなプロジェクトで3000使うので不便なんですよね...)

以下は読み方アンケートのdocker-composeファイルです。

docker-compose.yml
version: '3'
services:
  db:
    image: postgres:11-alpine
    volumes:
      - db:/var/lib/postgresql/data
      - ./docker/init:/docker-entrypoint-initdb.d
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "5434:5432"
    container_name: yomikata-postgres

  nginx:
    image: nginx:stable
    container_name: nginx
    ports:
      - '80:80'
    volumes:
      - ./conf.d:/etc/nginx/conf.d
    links:
      - front

  front:
    build:
      context: .
      dockerfile: ./docker/front/Dockerfile
    container_name: yomikata-front
    hostname: front
    volumes:
      - .:/yomikata-enquete:cached
      - node_modules:/yomikata-enquete/node_modules:cached
    ports:
      - '3001:3000'

  api:
    build:
      context: .
      dockerfile: ./docker/api/Dockerfile
    environment:
      DB_INFO: host=db port=5432 user=postgres dbname=postgres password=postgres sslmode=disable
    ports:
      - "8081:8081"
    container_name: yomikata-api
    tty: true
    volumes:
      - .:/usr/src/app
    command: go run ./server/cmd/server.go
    depends_on:
      - db

volumes:
  db:
    driver: local
  node_modules:
    driver: local

■ AWS Fargate

バックエンドはFargateで構築しました。

初めてだったのでまぁまぁ大変でした...

手順もまとめているのですが、ここに書くと長くなるので別途記事にしようと思います。

■ AWS CodePipeline

CDのためにCodePipelineを使用しています。

GitHubのproductionブランチへのpushに反応して動き、ビルドやデプロイを自動でしてくれます。

また、Slackへの通知も簡単に設定できます。

読み方アンケートでは以下のように開始と終了の通知をSlackに送るようにしています。
スクリーンショット 2020-07-24 18.25.15.png

GitHub Actions

各ブランチをGitHubにプッシュした際にテストを実行するためにGitHub Actionsを使用しています。

/.github/workflows/にymlで設定ファイルを記述します。

読み方アンケートでフロントのテストを実行する場合は以下のようなファイルを定義しています。

front_test.yml
name: front_test

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup NodeJs
        uses: actions/setup-node@v1
        with:
          node-version: '10.x'
      - name: Cache dependencies
        uses: actions/cache@v1
        with:
          path: ~/node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-node-
      - name: Install Dependencies
        run: yarn install --frozen-lockfile
      - name: Run test
        run: yarn test

プルリクエストには以下のように表示されます。
スクリーンショット 2020-07-24 18.42.16.png

ちなみにGitHub Actionsの勉強で買った以下の本は読みやすくて良かったです。
GitHub Actions 実践入門
9784844378716.jpg

Vercel

フロントのデプロイ先にはVercel(旧Zeit Now)を使用しています。

GUI上でGitHubのリポジトリとの連携設定を簡単にできて、ブランチをpushする度に各ブランチに対応したPreview用のURLを生成してくれるのでチーム開発では力を発揮します。
(わざわざブランチをpullしてきてローカルで実行して確認しなくて良い)

  • 以下のように自動でVercelがコメントしてくれる スクリーンショット 2020-07-24 23.56.35.png

Next.jsを開発している会社と同じなのでNextのデプロイもめちゃくちゃ簡単ですし、ReactやVueのデプロイもほぼ0configで可能です。

SSL化もデフォルトでされており、別途証明書を発行する手間もありませんし、お名前ドットコムなど外部で取得したドメインを設定するのも簡単にできます。

気になるお値段ですが、


なんと個人利用の場合無制限で無料です


いや、太っ腹かよ( ゚д゚)

ぜひ使ってみてください。

さいごに

個人開発、大変ではありますがやはり実際のプロダクトを作ることで得られる経験というのは本を読んだりUdemyで勉強するのとは段違いだと思います。

仕事と違って誰にも合わせる必要なく自分の思うがままに進めれるのも良いですしね←

ちなみに次は React × AWS Amplify で何か作ってみるつもりです。


読み方アンケートや使用している技術に関して質問があればこの記事にコメントしていただくか、私のTwitterにDMでもかまわないのでお待ちしています。

個人開発がんばりたい人、個人開発の先駆者の方、フォローお待ちしております。お互い頑張りましょうb

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

競プロで使うための GO 入門 ~math パッケージ~

Index

math

package main

import (
    "fmt"
    "math" // math
)

func main() {
}

Abs()

var f float64 = 12.3456
fmt.Println(math.Abs(-1)) // ->1
fmt.Println(math.Abs(1)) // ->1

Cbrt()

func Cbrt(x float64) float64

Ceil()

func Ceil(x float64) float64

Floor()

func Floor(x float64) float64

Pow()

func Pow(x, y float64) float64

Round()

func Round(x float64) float64

var f float64 = 12.3456
fmt.Println(math.Round(f * 100) / 100)   // ->12.35
fmt.Println(math.Round(f * 1000) / 1000) // ->12.346
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む