4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-09-23

はじめに

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

サービス説明

実際にサービスインしてみました

Stack<String> Service!!

概要

https://stack-string.herokuapp.com/PushStack?string=hoge
Post or Putリクエストを送ると、サーバーのStackhogeという文字列が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成功
それ以外:処理失敗
PUT /PushStack/<target> string 200 OK: PushStack成功
それ以外:処理失敗
GET /PopStack/<target> - 200: PopStack成功
それ以外:処理失敗
HEAD /isEmpty/<target> - 200: 指定した/のスタックにデータあり
404:指定した/のスタックが空である
それ以外:処理失敗

ソースコード解説

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用ファイル(実行ファイルの記載)

各実装紹介

config.py
DATA_PERSISTENCE_FLAG=False
GLOBAL_TARGET="global"
DATABASE_LOCATION="./local.db"

将来的に変更する可能性のある定数値をまとめたファイルです。
DATA_PERSISTENCE_FLAGTrueにすると、PopStackしてもデータ削除せず、
Stackデータの一覧で取得するモードで動作します。

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を実装しています。

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通信すれば本番環境でも耐えれるセキュリティになる!
と考え、手探りですが今回、実際にサービスインまで行ってみました!

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?