マイクロサービスとGridDBで独自のGo Webアプリを構築する

GridDBを使ったWebアプリケーションの作成については、以前にも紹介した: GridDBとSpring Bootを使ったJava Web APIの作成GERNスタックによるCRUD操作です。今回は、このトピックに再び取り組みますが、その代わりにサーバ/バックエンド言語としてGoを利用します。

Goは、その堅牢な標準ライブラリ、静的型付け、メモリ安全性から、将来のバックエンド言語としての位置づけが高まっています。私たちは、このプログラミング言語を使ってWebアプリケーションを構築する方法を紹介したいので、GridDB、Go、そしてブラウザ上のシンプルなフロントエンドWebページを使って、基本的なCRUDアクションを実行する方法を示すことを目的とした、非常にシンプルなデモアプリを作成します。TODOアプリを作成し、TODOアイテムの作成、更新(完了)、GridDBサーバーに存在するすべてのTODOの読み込み、そして最終的にユーザーが適切と判断したときに削除することを紹介します。

マイクロサービスは、その柔軟性から、大規模で堅牢なアプリケーションを構築するためのインフラストラクチャー戦略として人気が高まっています。このようなアプリケーションの構築にちなんで、最終的にはこの記事の続編として、全てのサービスをkubernetesクラスタ(単一ノード)上に移すことを計画しています。このため、少なくとも3つのコンテナが一体となってこのアプリを作成することを目指しています。データ永続化のためのGridDB、Webサーバー、そしてエンドポイントにアクセスするための認証サービスです。

しかし、kubernetesを使ってアプリケーションをクラウド・サービスにデプロイする前に、このアプリケーションをローカルに保ち、dockerとdocker-composeを使ってコードを出荷します。今回も、docker compose yamlに3つのコンテナを用意します。

前提条件とDocker/Docker Compose

この記事では、GridDB、Go、GridDB Go Connector、React.jsを使用します。もし一緒にコードを書きたいのであれば、これらすべてを個別にダウンロードして、”ベアメタル “でこのプロジェクトを実行できます。しかし、Dockerの魔法のおかげで、この記事を読むのと並行してプロジェクトを立ち上げ、すべての動作を確認したい場合は、dockerとdocker composeプラグインをダウンロードするだけです。

Docker Composeを使ってプロジェクトを実行する

このプロジェクトのリポジトリはこちらにあります: GitHub

$ git clone https://github.com/griddbnet/Blogs.git --branch golang_crud

このリポジトリのルートには docker-compose.yaml というファイルがあります。非常にシンプルなアプリの動作を見るには、以下の2つのコマンドを順番に実行するだけです。

$ docker compose build
$ docker compose up -d 

最初のコマンドは、.yamlファイルを読んで各コンポーネントのビルド方法を調べ、2番目のコマンドは、実際にコンテナを共有ネットワーク上で実行します。

プロジェクトが起動したら、ホストマシンのポート8080にナビゲートするだけです(つまり、ホストマシンがウェブブラウザと同じものであれば、http://localhost:8080)。これで、TODO項目を作成することができます。COMPLETED(またはその逆)に切り替えるには、行をクリックするだけです。データはデータベース(GridDB)に保存されるので、TODOリストはシャットダウンなどでも保持されます。

Docker ComposeとDockerfile

サービスのソースコードに移る前に、プロジェクトのデプロイに役立つコンテナの構築手順を提供するDockerファイルを簡単に見ておきましょう。

Docker Composeファイル

以下は docker-compose.yaml ファイルの全体です。

version: '3.9'
#volumes:
#  griddb:

services:

  griddb-server:
    build:
      context: griddb-server
      dockerfile: Dockerfile
    container_name: griddb-server
    expose:
      - '10001'
      - '10010'
      - '10020'
      - '10040'
      - '20001'
      - '41999'
    environment:
      NOTIFICATION_MEMBER: 1
      GRIDDB_CLUSTER_NAME: myCluster


  authentication:
    build: 
      context: authentication
      dockerfile: Dockerfile
    container_name: authentication
    ports: 
      - '2828:2828'

  web-server:
    build:
      context: web_server
      dockerfile: Dockerfile
    container_name: web-server
    ports: 
      - '8080:8080'

このファイルの主な役割は、すべてを1つのプロジェクトとして整理し、3つのコンテナの共有ネットワークを提供することです。各コンテナの構築手順は、各サービス固有の Dockerfile にあります。

GridDB Dockerfile

まずGridDBのDockerfileを見てみましょう。

from griddb/griddb

USER root

RUN set -eux \
    && apt-get update \
    # Install dependencies for griddb
    && apt-get install -y default-jre --no-install-recommends \
    && apt-get clean all \
    # Delete the apt-get lists after installing something
    && rm -rf /var/lib/apt/lists/*

# Install GridDB c_client
WORKDIR /
RUN wget --no-check-certificate https://github.com/griddb/c_client/releases/download/v5.0.0/griddb-c-client_5.0.0_amd64.deb
RUN dpkg -i griddb-c-client_5.0.0_amd64.deb

RUN wget --no-check-certificate https://github.com/griddb/cli/releases/download/v5.0.0/griddb-ce-cli_5.0.0_amd64.deb
RUN dpkg -i griddb-ce-cli_5.0.0_amd64.deb


ADD start-griddb2.sh /
USER gsadm

ENTRYPOINT ["/bin/bash", "/start-griddb2.sh"]
CMD ["griddb"]

開発チームがDockerhubにアップロードした公式のGridDBイメージ griddb/griddb を使い、少し拡張しています。主にJava、Cクライアント、GridDB CLIをインストールします。これらのライブラリによって、GridDBコマンドラインインターフェイスを利用できるようになり、コンテナでいくつかのことを検証できるようになる。最後に、GridDBをコンテナ形式で起動するスクリプトを呼び出します。

ウェブサーバのDockerfile

次に、ウェブサーバサービスを見てみましょう。このコンテナには、GridDBコンテナとやりとりするGoバックエンドのコードと、GridDBに関連する情報を返すGoバックエンドAPIにHTTPリクエストを送るreact.jsフロントエンドのコード(静的ビルド)が格納されています。

Dockerfileは以下の通りです。

FROM golang:1.20.4-bullseye


RUN set -eux; \
    apt-get update; \
    apt-get install -y --no-install-recommends autotools-dev automake libpcre3 libpcre3-dev
        
WORKDIR /
RUN wget --no-check-certificate https://github.com/griddb/c_client/releases/download/v5.1.0/griddb-c-client_5.1.0_amd64.deb
RUN dpkg -i griddb-c-client_5.1.0_amd64.deb

RUN wget https://prdownloads.sourceforge.net/swig/swig-4.0.2.tar.gz
RUN tar xvzf swig-4.0.2.tar.gz

WORKDIR /swig-4.0.2
RUN /swig-4.0.2/autogen.sh
RUN /swig-4.0.2/configure
RUN make
RUN make install

RUN go env -w GO111MODULE=off
RUN go get -d github.com/griddb/go_client
WORKDIR $GOPATH/src/github.com/griddb/go_client
RUN ls -la
RUN ./run_swig.sh
RUN go install

workdir /app

ADD . /app/web_server
ADD ./frontend /app/frontend

RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs
WORKDIR /app/frontend
RUN npm install
RUN npm install react-scripts@3.4.1 -g --silent
RUN npm run pre-build
RUN npm run build
RUN npm run post-build

WORKDIR /app/web_server
RUN go get github.com/dgrijalva/jwt-go
RUN go get github.com/gorilla/mux
RUN go build -o web_server *.go 
RUN chmod +x /app/web_server/web_server

EXPOSE 8080

cmd ["/app/web_server/web_server"]

このファイルの目的は、上で説明したように、バックエンドとフロントエンドの両方でウェブサーバーのコードを実行することです。まず、Goのベース・イメージから始めて、そこから拡張していきます。GridDBのGoクライアントをインストールし(GridDBとやりとりするため)、node.jsとreactをインストールし(フロントエンドのhtmlコードをビルドするため)、最後にプロジェクトを実行するためのgolangバイナリファイルをビルドします。全ての手順はDockerfileに記載されています。もし何かご不明な点があれば、Stackoverflowでお気軽にお問い合わせください。

認証Dockerfile

このコンテナはJSON Web Tokensを提供するだけなので、コンテナの中では最もシンプルです。

FROM golang:1.20.4-bullseye

ADD . /app/authentication

WORKDIR /app/authentication

RUN go mod download && go mod verify

RUN go get github.com/dgrijalva/jwt-go
RUN go build -o authentication *.go
RUN chmod +x /app/authentication/authentication

cmd ["/app/authentication/authentication"]

ここでは、jsonウェブトークンを発行するためのgoコードのバイナリファイルを作成するだけであり、Dockerfileはそのシンプルさを反映しています。このサービスがTODOアプリとどのように結びついているかは、後ほど説明します。

コード

それでは、このアプリを立ち上げて実行するために使われるソースコードについて説明しましょう。最初に認証サービスを説明し、次にウェブバックエンドに移り、最後にreact.jsフロントエンドを説明します。

認証サービス

このプロジェクトに深みを持たせ、マイクロサービスアーキテクチャを紹介するために、この認証マイクロサービスを含めることにしました。通常、実世界/プロダクションアプリケーションでは、各ユーザーの個人データをサイロ化して安全に保つために、サインアップ/サインイン/サインアウトのプロセス全体を持つことになります。このケースでは、ユーザーに有効なトークンを付与し、APIエンドポイントが使用されるたびにそれをチェックする、大まかなサービスを実装しました。トークンが送信されないか、トークンが無効な場合、エンドポイントは失敗します。

例えば、すべてのTODOアイテムを返す/getエンドポイントを呼び出したい場合、まず認証サービスから有効なトークンを取得する必要があります。そして、/get の HTTP リクエストを作成するときに、トークンをヘッダーに含めます。というわけです。今回は、バックエンドのサービスに合わせてGolangで認証サービスを書きました。この認証サービスは、独自のウェブサーバでもあり、サーバを作成するときに設定したのと同じ署名キーでリクエストしたHTTPクライアントにJSONウェブトークンを提供するというものです。

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"

    jwt "github.com/golang-jwt/jwt"
)

var mySigningKey = []byte("ScruffMcGruff")

func GetJWT() (string, error) {
    token := jwt.New(jwt.SigningMethodHS256)

    claims := token.Claims.(jwt.MapClaims)

    claims["authorized"] = true
    claims["aud"] = "golang-blog"
    claims["iss"] = "israel"
    claims["exp"] = time.Now().Add(time.Hour * 1).Unix()

    tokenString, err := token.SignedString(mySigningKey)

    if err != nil {
        fmt.Errorf("Something Went Wrong: %s", err.Error())
        return "", err
    }

    return tokenString, nil
}

これはトークンを生成するための非常にシンプルなコードです。一意のsigningKeyとclaimオブジェクトを作成し、バックエンド側でトークンを検証して、受け取ったトークンが有効であることを確認します。もちろん、このコードの大部分は、jwtパッケージ(このコードブロックの先頭でインポートされています)によって行われています。

トークン文字列が手に入ったら、(Golang Standard Libraryを使って)シンプルなhttpサーバを立ち上げ、エンドポイントに到達するたびにトークンを提供するだけです。

func enableCors(w *http.ResponseWriter) {
    (*w).Header().Set("Access-Control-Allow-Origin", "*")
}

func Index(w http.ResponseWriter, r *http.Request) {

    enableCors(&w)

    validToken, err := GetJWT()
    fmt.Println(validToken)
    if err != nil {
        fmt.Println("Failed to generate token")
    }

    fmt.Fprintf(w, string(validToken))
}

func handleRequests() {

    http.HandleFunc("/", Index)
    fmt.Println("Starting on port 2828")
    log.Fatal(http.ListenAndServe(":2828", nil))
}

func main() {
    handleRequests()
}

このサーバーをベアメタルで実行できるのであれば、http://localhost:2828/に問い合わせれば、トークン文字列を受け取ることができます。しかし、我々はこれを docker で実行するつもりなので、代わりにホスト名/サービス名 http://authentication:2828/ で呼び出す必要があります。このサービスがトークンを取得してフロントエンドに提供し、エンドポイント・クエリを成功させるからです。

ウェブサーバコンテナ

バックエンドの Go コードを確認しましょう。このコードでは、シンプルなTODOアプリを作成する目的で、GridDB Dockerコンテナに問い合わせるシンプルなCRUD APIを作成します。また、静的なHTMLのReact.jsファイルや、APIをホストするWebサーバも作成します。

まずはGridDB固有のコードを見て、そこから展開していきましょう。

GridDB接続とヘルパー関数

type Todo struct {
    Id        int
    Title     string
    Completed bool
}

func ConnectGridDB() griddb.Store {
    factory := griddb.StoreFactoryGetInstance()

    // Get GridStore object
    gridstore, err := factory.GetStore(map[string]interface{}{
        "notification_member": "griddb-server:10001",
        "cluster_name":        "myCluster",
        "username":            "admin",
        "password":            "admin"})
    if err != nil {
        fmt.Println(err)
        panic("err get store")
    }

    return gridstore
}

func createTodo() {
    gridstore := ConnectGridDB()
    defer griddb.DeleteStore(gridstore)
    conInfo, err := griddb.CreateContainerInfo(map[string]interface{}{
        "name": "todo",
        "column_info_list": [][]interface{}{
            {"id", griddb.TYPE_INTEGER},
            {"title", griddb.TYPE_STRING},
            {"completed", griddb.TYPE_BOOL}},
        "type":    griddb.CONTAINER_COLLECTION,
        "row_key": true})
    if err != nil {
        fmt.Println("Create containerInfo failed, err:", err)
        panic("err CreateContainerInfo")
    }
    _, e := gridstore.PutContainer(conInfo)
    if e != nil {
        fmt.Println("put container failed, err:", e)
        panic("err PutContainer")
    }
}

これは、GridDBの行のスキーマを定義する構造体です。基本的に、アプリケーションの各TODO項目は、ID(rowkey)、タイトル、完了ステータスを持ちます。このスキーマは、TODO構造体とCreateTodo()関数に反映されます。ConnectGridDB関数は、認証情報を入力し、GridDBコンテナとの接続を試みます。ここで使用する認証情報はすべてシステムのデフォルトです。唯一注意すべき点は、notification_memberがGridDB Dockerコンテナのサービス/ホスト名であるgriddb-serverを使用していることです。この関数を呼び出すと、GridDBサーバとやり取りできるようになります。

また、先に説明したように、CreateTodo()関数は実際のTODO Collectionコンテナを作成します。この関数は、サーバーが実行されるたびに実行されますが、コンテナがすでに存在する場合は何もしません。

次に、GridDBのヘルパー関数をいくつか見てみましょう。

func GetContainer(gridstore griddb.Store, cont_name string) griddb.Container {

    col, err := gridstore.GetContainer(cont_name)
    if err != nil {
        fmt.Println("getting failed, err:", err)
        panic("err create query")
    }
    col.SetAutoCommit(false)

    return col
}

func QueryContainer(gridstore griddb.Store, col griddb.Container, query_string string) griddb.RowSet {
    query, err := col.Query(query_string)
    if err != nil {
        fmt.Println("create query failed, err:", err)
        panic("err create query")
    }

    // Execute query
    rs, err := query.Fetch(true)
    if err != nil {
        fmt.Println("fetch failed, err:", err)
        panic("err create rowset")
    }
    return rs
}

これらの関数は、コードをすっきりさせ、1行でクエリー結果を取得するのに役立ちます。

CRUD関数に移る前に、認証サービスに問い合わせて有効なJSONウェブトークンを受け取る関数を示します。

func GetToken(w http.ResponseWriter, r *http.Request)  {

    w.Header().Set("Access-Control-Allow-Origin", "*")

    resp, err := http.Get("http://authentication:2828")
    if err != nil {
        log.Fatalln("error grabbing Token", err)
    }

   body, err := ioutil.ReadAll(resp.Body)
   if err != nil {
      log.Fatalln(err)
   }
    w.Write(body)
}

func main() {
    ....snip
    router.HandleFunc("/getToken", GetToken)
}

フロントエンドからの HTTP リクエストはクライアント側 (つまりブラウザ) で行われるので、フロントエンドから認証サービスに直接問い合わせることはできません。つまり、トークンをフロントエンドに渡すには、バックエンドでトークンを受け取り、 /getToken エンドポイントが呼ばれたときにそれをフロントエンドに渡す必要があります。通常は、誰でもトークンを取得できるようにしないために、何らかのログイン機構(ユーザー名/パスワード)が必要になるでしょうが、デモ目的であれば、この実装で問題ありません。

CRUD 関数

それでは最後に、GoコードのCreate、Read、Update、Delete関数を見てみましょう。CreateはCreateTodoItemで処理され、エンドユーザーが選択した行を作成します。ReadはGet()で処理され、単純にすべての行をユーザーに返します。Update()は、TODO項目の “completed “カラムをtrueまたはfalseに切り替えます。そして最後に DeleteTodoItem() は、ユーザがその行の処理を終えるとその行を削除します。

func Get(w http.ResponseWriter, r *http.Request) {

    w.Header().Set("Content=Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*")

    gridstore := ConnectGridDB()
    defer griddb.DeleteStore(gridstore)

    col := GetContainer(gridstore, "todo")
    defer griddb.DeleteContainer(col)

    rs := QueryContainer(gridstore, col, "select *")
    defer griddb.DeleteRowSet(rs)

    var todos []Todo
    for rs.HasNext() {
        rrow, err := rs.NextRow()
        if err != nil {
            fmt.Println("NextRow from rs failed, err:", err)
            panic("err NextRow from rowset")
        }
        var todo Todo
        todo.Id = rrow[0].(int)
        todo.Title = rrow[1].(string)
        todo.Completed = rrow[2].(bool)
        todos = append(todos, todo)
    }
    if err := json.NewEncoder(w).Encode(todos); err != nil {
        log.Panic(err)
    }
}

func CreateTodoItem(w http.ResponseWriter, r *http.Request) {

    w.Header().Set("Content=Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*")

    gridstore := ConnectGridDB()
    defer griddb.DeleteStore(gridstore)

    col := GetContainer(gridstore, "todo")
    defer griddb.DeleteContainer(col)

    decoder := json.NewDecoder(r.Body)
    var t Todo
    err := decoder.Decode(&t)
    if err != nil {
        fmt.Println("decode err", err)
    }

    col.SetAutoCommit(false)
    err = col.Put([]interface{}{t.Id, t.Title, t.Completed})
    if err != nil {
        fmt.Println("put row name01 fail, err:", err)
    }
    fmt.Println("Creating Item")
    col.Commit()
}

func UpdateTodoItem(w http.ResponseWriter, r *http.Request) {

    w.Header().Set("Content=Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*")

    gridstore := ConnectGridDB()
    defer griddb.DeleteStore(gridstore)

    col := GetContainer(gridstore, "todo")
    defer griddb.DeleteContainer(col)
    col.SetAutoCommit(false)

    vars := mux.Vars(r)
    idStr := vars["id"]
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }

    query_str := fmt.Sprintf("SELECT * WHERE id = %d", id)

    rs := QueryContainer(gridstore, col, query_str)
    defer griddb.DeleteRowSet(rs)

    for rs.HasNext() {
        rrow, err := rs.NextRow()
        if err != nil {
            fmt.Println("GetNextRow err:", err)
            panic("err GetNextRow")
        }

        todo_title := rrow[1]
        todo_done := rrow[2].(bool) //casting interface{} to bool

        //Update row from completed to not, etc
        err2 := rs.Update([]interface{}{id, todo_title, !todo_done})
        if err2 != nil {
            fmt.Println("Update err: ", err2)
        }
    }
    col.Commit()
}

func DeleteTodoItem(w http.ResponseWriter, r *http.Request) {

    w.Header().Set("Content=Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*")

    gridstore := ConnectGridDB()
    defer griddb.DeleteStore(gridstore)

    col := GetContainer(gridstore, "todo")
    defer griddb.DeleteContainer(col)
    col.SetAutoCommit(false)


    vars := mux.Vars(r)
    idStr := vars["id"]
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }

    query_str := fmt.Sprintf("SELECT * WHERE id = %d", id)
    fmt.Println(query_str)

    rs := QueryContainer(gridstore, col, query_str)
    defer griddb.DeleteRowSet(rs)

    for rs.HasNext() {
        rrow, err11 := rs.NextRow() //Avoid invalid results returned
        if err11 != nil {
            fmt.Println("GetNextRow err:", err11)
            panic("err GetNextRow")
        }
        fmt.Println(rrow)
        fmt.Println(("removing"))
        err5 := rs.Remove()
        if err5 != nil {
            fmt.Println("Remove err: ", err5)
        }
    }

    col.Commit()
    fmt.Println("Delete Row")
    fmt.Println("success!")

}

これらの関数はすべて、フロントエンドのコードから特定のルートが呼び出されたときに呼び出されます。例えば、ウェブページはページロード時に /get/ を呼び出すかもしれません。これは、すべてのTODO行を読み込むためにGet()関数を呼び出してユーザーに返す、などです。

別の例として、TODO項目/行を作成することができます。これを行うには、TODO項目のタイトルを指定してPOST HTTPリクエストをGoサーバに送信します。すると、自動的に一意のIDが与えられ、項目が自動的に未完了に設定されます。

認証ミドルウェア

認証サービスは、有効なトークンを提供する小さなhttpサーバーを提供してくれますが、有効なトークンをチェックし、それに応じて行動するメカニズムがなければ、何の意味もありません。このタスクを達成するために、ミドルウェアと呼ばれるものを追加することができます。これは基本的に、各エンドポイントでHTTP Requestを受信するたびにこの関数が実行され、データを読み込んで、入力に基づいてレスポンスを吐き出すことを意味します。例えば、トークンなしでリクエストを受信した場合、トークンが見つからないというエラーを返し、トークンは存在するが有効期限が切れているか無効である場合も同じことが起こります。正しいデータを返す唯一の方法は、有効なトークンを提供することです。

var MySigningKey = []byte("ScruffMcGruff") //same signing key is crucial. this part can be used as os environment to not have this information public

func isAuthorized(endpoint func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
        if r.Header["Token"] != nil {

            token, err := jwt.Parse(r.Header["Token"][0], func(token *jwt.Token) (interface{}, error) {
                if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
                    return nil, fmt.Errorf(("invalid Signing Method"))
                }
                aud := "golang-blog"
                checkAudience := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false)
                if !checkAudience {
                    return nil, fmt.Errorf(("invalid aud"))
                }
                // verify iss claim
                iss := "israel"
                checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false)
                if !checkIss {
                    return nil, fmt.Errorf(("invalid iss"))
                }

                return MySigningKey, nil
            })
            if err != nil {
                fmt.Fprintf(w, err.Error())
            }

            if token.Valid {
                endpoint(w, r)
            }

        } else {
            fmt.Fprintf(w, "No Authorization Token provided")
        }
    }
}

func main() {
    .....snip
    router.HandleFunc("/get", isAuthorized(Get)) //run isAuthorized function as part of the handlefunc
}

基本的にここで起こっていることは、この関数が署名鍵、署名方法、クレーム発行者、クレーム閲覧者を検証し、これらすべてがチェックアウトされれば、元のエンドポイントを返し、どれかが間違っていればエラーを返すということです。このようにして、各エンドポイントの呼び出しが有効なトークンを持っていることを確認し、 渡される情報が安全な場所にしか行かないことを確認することができます。

Go Web サーバー・ルーティング

Go標準ライブラリには、非常に優れた堅牢なnet/httpパッケージが用意されていますが、APIエンドポイントのためにgorilla/muxパッケージを使用することにしました。このセクションのコードには大したことはないので、ここで簡単に共有します。

func main() {

    createTodo()

    router := mux.NewRouter()
    router.Use(mux.CORSMethodMiddleware(router))

    router.HandleFunc("/get", isAuthorized(Get))
    router.HandleFunc("/getToken", GetToken)
    router.HandleFunc("/create", isAuthorized(CreateTodoItem)).Methods("POST")
    router.HandleFunc("/update/{id}", isAuthorized(UpdateTodoItem)).Methods("POST")
    router.HandleFunc("/delete/{id}", isAuthorized(DeleteTodoItem)).Methods("POST")

    router.PathPrefix("/").Handler(http.FileServer(http.Dir("./public/")))

    srv := &http.Server{
        Handler: router,
        Addr:    "0.0.0.0:8080",
    }

    fmt.Println("Listening on port 8080...")
    log.Fatal(srv.ListenAndServe())
}

まずTODOコンテナを作成し(存在しない場合)、gorilla/muxパッケージからルーター・オブジェクトを初期化し、対応する関数でエンドポイント・ルートを作成します。

そして最後に、ルートページ(”/”)に静的なHTML/JSのreact.jsコンテンツを提供するように設定します。

フロントエンドのコード

フロントエンドのコードは、独自のコンテナではないし、特別なことをするわけでもないので、このプロジェクトでは少し対象外です。知っておくべき主なことは、フロントエンドのコードはバックエンドのコードで確立されたエンドポイントにHTTPリクエストを行い、必要なデータを受け取るということです。データを受け取ると、それをグリッドに表示し、エンドユーザーが新しい項目を作成できるようにします。

唯一の注意点は、フロントエンドからバックエンドへの各リクエストには有効なトークンが必要だということです。そのために、ページのロード時にトークンを取得し、その文字列をリアクトステートとして保存します。そして、進行中の各リクエストにはトークンが含まれます。

  const getToken = () => {
    fetch("/getToken").then(function (response) {
      return response.text()
    }).then((data) => {
        let respToken = data
        console.log("respToken: ", data)
        setToken(respToken) //setting react state for token
      }) 
    }

And then here’s a request using the saved token:

const queryForRows = () => {
  axios.get("/get",{ headers:{"Token": token}}).then((response) => {
    let resp = (response.data);
    let lastId = 0
    resp.forEach((val, idx) => {
      val["id"] = idx //adding ids for the datagrid (separate from the GridDB rowkey)
      lastId = val.Id // grab last Id
    })
    setRows([...resp])
    setLastId(lastId + 1)
  })
}

もちろん、上で説明したように、ソースコードはGitHubで公開されているので、このシンプルなウェブページがどのように機能するのか興味があれば、ソースコードを見ることができます。

アプリケーションは以下のような感じになります。

次のステップ

このローカルプロジェクトをkubernetesクラスタに移行することで、より堅牢なクラウドインスタンスに移行し、この記事を反復していく予定です。

ユーザーとしては、より堅牢なTODOリストを作ったり、新しい機能を追加したりすることで、もちろんこのプロジェクトを反復することができます。

結論

以上、Goをバックエンドサーバーとして使ったGridDBウェブアプリの作り方を学びました。また、Docker Composeを使ってマイクロサービス型のアーキテクチャをデプロイし、柔軟なアプリを構築する方法も学びました。

ブログの内容について疑問や質問がある場合は Q&A サイトである Stack Overflow に質問を投稿しましょう。 GridDB 開発者やエンジニアから速やかな回答が得られるようにするためにも "griddb" タグをつけることをお忘れなく。 https://stackoverflow.com/questions/ask?tags=griddb

Leave a Reply

Your email address will not be published. Required fields are marked *