追記
いくらSQLが嫌いでも、データベースを自作すると大変なことになりかねませんので、やめておきましょう(ぼくもう知らないもん)
目的
- 何のdbを選ぶのかは自分で選べるということです
- ちなみに自作したゆーとりますが、完成度はそんなに高くないです
- ちなみに少し脚色してまs((
関係知識
にらBOTという、Python製DiscordBOTを作っているのですが、もともと、データはpickle
を使ってファイルに保存していたのですが、これがちょっとめんどくさいなーと思い、データベースに保存するように変えました。
最初は、Googleスプレッドシートに保存していましたが、セットアップが面倒なのと、データの読み書き速度が遅い事に難がありました。
てなわけで、セルフホスティングなデータベースを使用する事にしたのです。
さて、「セルフホスティングなデータベースと言えばなんでしょう?」と聞かれたら大半の方が「SQL!」と元気よく答えると思います。
ですがSQLには難点があります。それは...
構文を覚えるのが面倒!(毛嫌い)
てなわけで、自作のもうちょっと楽なデータベースを作ろうと思ったわけです。
実験
Plan
まず、データベース...というより「データを保存する形式」として有名なものがありますよね。
そう、JSON
ですよね???(だいぶ強引)
そして、データベースはサーバーがあって、そこと通信してデータをやり取りしますよね。なので、サーバーサイドとクライアントサイドで通信する機構が必要です。
そう!HTTP
ですよね!!!!
こうして、HTTP通信を使ってJSON形式でやりとりする
と言うところまで決まりました。
さて、まずはサーバーサイドソフトの構造から決めましょう。
HTTP
とJSON
をメインとするのにちょうどいい言語があります。
JSON
という名前から察する方も沢山いるでしょう。
ご名答です。JavaScript
...ではなくPython
です!!!
(ほら...JSON
の最後の2文字とPython
の最後の2文字が同じでしょ...?)
なぜJavaScriptを使わなかったのか
今の時代、サーバーサイドとしてJavaScript
を使っていると、TypeScriptはどうした?^^
と煽られてしまうのと、サーバーサイドのJavaScript
について教養が浅かったからです。
てなわけで、Python
でHTTP
サーバーを建てるということで、前々から気になっていたSanic
に触れる事にしました。
Do
そして出来上がったサーバーサイドソフトウェアがこちらでs((
Sanic
のレスポンス処理で、JSON
を返していくというような感じの、至って単純なものです。
なんせSimple and easy database system using HTTP
ですので...。
さて、ここからはクライアントサイドの構造です。
クライアントサイドは、普通にHTTP POST
をすればいいのですが、にらBOTで扱う時にラッパーがあれば楽だなと言う事でPython
ラッパーを作っていきます。
そして、人生で初めてちゃんとクラスを扱う事になりました。
オブジェクト指向ですねぇ〜
class Client:
def __init__(self, url: str = "http://localhost:8080"):
self.url = url
async def info(self) -> dict:
...
みたいな感じです。(適当)
なぜアドレスとポートに分けずURLとしたのかと言うと、私がリバースプロキシを使ってデータベースのURLをhttps://example.com/example_database
みたいにしたせいで、これをそのまま入れるとexample.com/example_database:443
となってしまって、良い方法も見つからなかったのでこうしました。
Check
ある程度期待の動作をしてくれました。
凄く嬉しい事です。
さて、「このままじゃポート(URL)が分かると、誰からでもデータをいじられてしまう」と言うのが議題に上がりました。
そして、にらBOTのエラーハンドルを適当にしているので、もしデータベースのエラーが起きてた場合に、アドレスが普通に表示されてしまうような気がするので、もう情報ダダ漏れ待ったなしです。
DiscordDeveloperの規定上、ユーザーの個人情報を保護するのは絶対なので、これではいけません。
Action
さて、パスワード保護をつける事にしましょう。
私の中で、パスワード保護は2種類の方法に分けています。
種類 | 説明 |
---|---|
暗号化式 | サーバー/クライアント側で暗号化して送信して、クライアント/サーバー側で復号する |
認証式 | 正しいパスワードでのみデータの受信/送信が出来る |
今回は、ちょうど良い暗号化モジュールがなかったので、認証式にしました。
class Client:
- def __init__(self, url: str = "http://localhost:8080"):
- self.url = url
+ def __init__(self, url: str = "http://localhost:8080", password: str = ""):
+ self.url = url
+ self.password = password
async def info(self) -> dict:
...
こんな風にしてパスワード認証をつけました。
やはり認証は必要ですね。
結果の検討
Check(2度目)
ワイ「おっしゃこれで完成やろ!」
ワイ「BOTからデータいじれるか試してみよ!」
にら「出来たで」
ワイ「おっしゃ!これで完璧や!じゃあ実際にこれからはここにデータを入れよう!」
ワイ「{サーバーID: {チャンネルID: 設定項目 } }
みたいなの送ってみるか。いやーJSONは辞書形式にも対応してくれてて嬉しいわ〜」
にら「送ったで」
ワイ「おっしゃ!あとはこれを読み込んでちゃんと設定項目が確認できれば...」
にら「データベースから読み込んだで。ちなみに、データの中にこのサーバーIDは見つからんかったで」
ワイ「( ˙꒳˙ )?????」
これはJSON
の辞書形式において、int
形式のキーに対応してない事が原因で、データを送る際に勝手に変換されてしまう事が原因でした。
{
1234567890: {
9876543210: true,
1528371918: false,
}
}
{
"1234567890": {
"9876543210": true,
"1528371918": false,
}
}
{
"1234567890": {
"9876543210": true,
"1528371918": false,
}
}
てなわけで、辞書をint
で参照したけどstr
しかないのでそりゃあデータがないと言われるわけです。
解決策をいろいろ模索した結果、最終的にBOT側では「辞書形式で送らない」事にしました。
Action(2度目)
「いや待ちなされ奥さん、辞書で送らないってどう言うことよ」って思われた方も多いと思います。
勿論、後でeval()
で戻せるように辞書をそのまんまstr
に変えるってのは、Nonsense ですよね。
てなわけで、list
で送る事にしました。
辞書形式とは、本当(?)は連想配列形式っていうことなので、楽に配列にする事ができます。
実際に試してみると分かります。
>>> value = {12345: 'hello', 'hola': 67890}
>>> list_value = list(value.items())
>>> print(list_value)
[(12345, 'hello'), ('hola', 67890)]
ね?簡単でしょ?(某ペンギン風)
てなわけで、これを渡すようにしました。
そしてデータを読み込むときは、配列形式を辞書形式にするようにしました。
def listToDict(source) -> dict:
temp = {}
source = dict(source)
for i in list(source.keys()):
if i not in temp:
temp[i] = {}
temp[i].update(dict(source[i]))
return temp
こんなのをutil
にいろいろ突っ込んだりして、なんとかデータベースとちゃんとデータをやり取り出来るようになりました。
結果の整理
てなわけで、実際にデータベースを自作する事ができました。
詳しくは、各レポジトリを見てみてほしいです。
- HTTP_db(自作したデータベース)
- にらBOT(自作しているDiscord BOT)
感想
今回、このデータベース自作を通して、データベースを作るのは大変だと思いました。(小並感)
SQLの方が楽そうだと思いました。(本末転倒)
でもここまで来たら引き下がれませんでした。(謎のプライド)
多分そのうち限界が来たらSQL使います。(諦め)
色々学べる事があって楽しかったです。(ピーク・エンドの法則)
皆さんはSQL使いましょう(何がしたかったんだか)
よければ...
「いいね」とか「Star」よろしくね⭐︎