マイクロサービスアーキテクチャを自分なりに捉えて、Stack<String>というマイクロサービスを作ってみた。

## はじめに
マイクロサービスアーキテクチャとは、何か。
を言及・解説するつもりは、ありません。未だに完全理解しきれてません！
ただ今回、マイクロサービスアーキテクチャというものを自分なりにとらえ、
[stack-string-service](https://github.com/nekotarolu/stack-string-service)を作ってみたことを発信できればと思い。書き起こしています。

## サービス説明
### 実際にサービスインしてみました
#### [Stack\<String> Service!!](https://stack-string.herokuapp.com/)

### 概要
https://stack-string.herokuapp.com/PushStack?string=hoge
に`Post` or `Put`リクエストを送ると、サーバーの`Stack`に`hoge`という文字列が`Push`でき、
https://stack-string.herokuapp.com/PopStack
に`Get`リクエストを送ると、サーバーの`Stack`から`Pop`できます。
※各URLに\<target>を指定し、自分だけのStackを作成する機能もあります。

### サービス構成
 * Deploy環境       ： heroku
 * 利用言語          ： python 3
 * 利用フレームワーク ： bottle

## REST API解説
|Method|URI|query|ResponseCode|
|:--:|:----|:----|:--|
|POST|/PushStack/\<target>|string|200 OK: PushStack成功<br>それ以外：処理失敗|
|PUT|/PushStack/\<target>|string|200 OK: PushStack成功<br>それ以外：処理失敗|
|GET|/PopStack/\<target>|-|200: PopStack成功<br>それ以外：処理失敗|
|HEAD|/isEmpty/\<target>|-|200: 指定した/<target>のスタックにデータあり<br>404：指定した/<target>のスタックが空である<br>それ以外：処理失敗|

## ソースコード解説
GitHubリポジトリは、以下です。
https://github.com/nekotarolu/stack-string-service

### 各ファイル紹介
```
|-service.py -> bottleのrouting実装(Main)
|-model.py   -> DBへのアクセス系を実装
|-config.py  -> サービスの可変設定を記載
|-requirements.txt -> heroku へのDeploy用ファイル(依存ライブラリを記載)
|-Procfile         -> heroku へのDeploy用ファイル(実行ファイルの記載)
```

### 各実装紹介
```python:config.py
DATA_PERSISTENCE_FLAG=False
GLOBAL_TARGET="global"
DATABASE_LOCATION="./local.db"
```
将来的に変更する可能性のある定数値をまとめたファイルです。
`DATA_PERSISTENCE_FLAG`を`True`にすると、PopStackしてもデータ削除せず、
Stackデータの一覧で取得するモードで動作します。

```python:service.py
import datetime
import os
import sqlite3
from bottle import route, request, abort, run

import config
import model

_GLOBAL_TARGET_="global"

@route("/")
def welcome_service():
        print("config.DATA_PERSISTENCE_FLAG=" + str(config.DATA_PERSISTENCE_FLAG))
        print("config.DATABASE_LOCATION" + str(config.DATABASE_LOCATION))
        print("config.GLOBAL_TARGET" + str(config.GLOBAL_TARGET))

        print("-- database setup start --")
        ret = model.init_db()
        if ret is True:
                print("-- database setup success --")
        else:
                print("-- database setup failed --")
       
        return "Welcome to the StackStringService!!"


@route("/PushStack", method=['PUT', 'POST'])
def push_stack_route_global():
        return push_stack_route(config.GLOBAL_TARGET)


@route("/PushStack/<target>", method=['PUT', 'POST'])
def push_stack_route(target):
        model.push_stack(target, request.query.string)
        return 'OK'


@route("/PopStack")
def pop_stack_route_global():
        return pop_stack_route(config.GLOBAL_TARGET)


@route("/PopStack/<target>")
def pop_stack_route(target):
        result = model.pop_stack(target)
        if result is None:
                abort(404, "No such database.")
        return str(result)


#return status code = 404: Stack is Empty.
#return status code = 200: Stack isnot Empty.
@route("/isEmpty", method="HEAD")
def is_empty_route_global():
        return is_empty_route(config.GLOBAL_TARGET)


@route("/isEmpty/<target>", method="HEAD")
def is_empty_route(target):
        if model.is_empty(target) is True:
                print("isEmpty true!")
                abort(404, "target stack is Empty.")
        else:
                print("isEmpty false.")

        return "isEmpty call."


if __name__ == "__main__":
        run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000)))
```
bottleを利用して、WebサービスのRoutingを実装しています。

```python:model.py
import datetime
import sqlite3
import config

def init_db():
    ret = True

    now = datetime.datetime.now()
    connection = sqlite3.connect(config.DATABASE_LOCATION)
    try:
        coursor = connection.cursor()
        connection.execute("CREATE TABLE IF NOT EXISTS variable (target TEXT PRIMARY KEY NOT NULL, date TEXT NOT NULL)")
        connection.execute("CREATE TABLE IF NOT EXISTS stack (target TEXT NOT NULL, number INTEGER NOT NULL, str TEXT NOT NULL, date TEXT NOT NULL, PRIMARY KEY(target, number))")

        coursor.execute("SELECT * FROM variable WHERE target = '"+ config.GLOBAL_TARGET +"'")
        if len(coursor.fetchall()) > 0:
            print("in if: len(coursor.fetchall()) > 0")
        else:
            print("in else: INSERT INTO variable!!")
            now_str = now.strftime('%Y%m%d%H%M%S')
            connection.execute("INSERT INTO variable (target, date) VALUES ( ?, ? )", (config.GLOBAL_TARGET, now_str))
            
    except sqlite3.Error as e:
        ret = False
        print("sqlite3 error occurred:", e.args[0])

    connection.commit()
    connection.close()

    return ret

def push_stack(target: str, string: str):
    print("-- PushStack start --")

    ret = True

    #DataBase init.
    init_db()

    #PushStack real start.
    now = datetime.datetime.now()
    connection = sqlite3.connect(config.DATABASE_LOCATION)
    try:
        coursor = connection.cursor()

        target_p = target
        if target_p is None:
            print("target_p is None!")
            target_p = "None"
        print("target_p=" + target_p)

        result = coursor.execute("SELECT MAX(number) FROM stack WHERE target ='" + target_p + "'").fetchall()
        print(result)

        number_p = 0
        if result[0][0] is None:
            print("in if: Push result is None.")
        else:
            number_p = result[0][0] + 1
            print("in else: Push result select max(number) ", result[0][0])
        print("number_p=" + str(number_p))

        string_p = string
        if string_p is None:
            print("string_p is None!")
            string_p = "None"
        print("string_p=" + string_p)

        now_str = now.strftime('%Y%m%d%H%M%S')

        insert_sql = "INSERT INTO stack (target, number, str, date) VALUES (?, ?, ?, ?)"
        insert_prm = (target_p, number_p, string_p, now_str)
        coursor.execute(insert_sql, insert_prm)

        print("stack string: insert success!!")
    except sqlite3.Error as e:
        ret = False
        print("sqlite3 error occurred:", e.args[0])

    connection.commit()
    connection.close()

    print("-- PushStack end --")
    return ret


def pop_stack(target: str):
    print("-- PopStack Start --")

    #DataBase init.
    init_db()

    #PopStack real start.
    connection = sqlite3.connect(config.DATABASE_LOCATION)
    try:
        coursor = connection.cursor()
        target_p = target
        if target_p is None:
            print("target_p is None!")
            target_p = "None"
        print("target_p=" + target_p)

        if config.DATA_PERSISTENCE_FLAG is True:
            result = coursor.execute("SELECT number, str FROM stack WHERE target ='"+ target_p + "' ORDER BY number DESC").fetchall()
        else:
            result = coursor.execute("SELECT number, str FROM stack WHERE target ='"+ target_p + "' AND number =(SELECT MAX(number) FROM stack WHERE target ='"+ target_p +"')").fetchall()
            connection.execute("DELETE FROM stack WHERE target ='"+ target_p + "' AND number =(SELECT MAX(number) FROM stack WHERE target ='"+ target_p +"')")
            connection.commit()
        print(result)

    except sqlite3.Error as e:
        result = None
        print("sqlite3 error occurred:", e.args[0])

    connection.close()

    print("-- PopStack End --")
    return result


def is_empty(target: str):
    ret = True

    init_db()

    connection = sqlite3.connect(config.DATABASE_LOCATION)
    try:
        coursor = connection.cursor()
        target_p = target
        if target_p is None:
            print("target_p is None!")
            target_p = "None"
        print("target_p=" + target_p)
        
        result = coursor.execute("SELECT MAX(number) FROM stack WHERE target ='" + target_p + "'").fetchall()
        print(result)
        
        if result[0][0] is None:
            ret = True
            print("isEmpty true!")
        else:
            ret = False
            print("isEmpty false.")
    except sqlite3.Error as e:
        ret = False
        print("sqlite3 error occurred:", e.args[0])

    connection.close()
    print("-- isEmpty End --")

    return ret
```
Routingに対応したservice.pyからの呼び出しにより、
sqlite3を用いて、DBに実際にデータ登録/取得を行います。

## デプロイ方法

Linux上でのデプロイ方法

```
# git clone https://github.com/nekotarolu/stack-string-service.git

# sudo pip install bottle

# python service.py
```
上記のコマンド実行後、http://localhost:5000/ にWebブラウザでアクセスし、
`Welcome to the StackStringService!!`とレスポンスが返ればDeploy成功です。

## おわりに（作りだしたキッカケ）
情報系の大学だったのですが、大学からの友人と飲み交わし、
「今の時代マイクロサービスアーキテクチャだ！」という話題になったのですが、
メーカーで組み込みソフト開発の経験しかない私は、
「マイクロサービスってなに？」という状態でした。
その場で教えてもらいましたが、理解しきれず...持ち帰り勉強しました。
勉強してみると、素晴らしい考えであると感じ自分も作りたい欲が抑えられませんでした！

同時期に社外の有志団体で、Webアプリを創るチャレンジに取り組んでおり、
私にも、いろいろと試作（モック）や、実装の依頼が飛び込んでくるシーンが増えていたので、
何を切り出しマイクロサービス化したら、数多の実装依頼を捌けるだろうと考え、
`Stack<String>`という言語仕様を切り出す。と思い付きました。

完全に思い付きでしたが、`Stack<String>`をマイクロサービスとして切り出せば、
チャットアプリや、掲示板機能など、JavaScript実装のみでモックアップ実装できるようになり、
ゆくゆくは、localhostに閉じてRest通信すれば本番環境でも耐えれるセキュリティになる！
と考え、手探りですが今回、実際にサービスインまで行ってみました！