アーキテクチャ考えるときに考えてる事
こじんてきめも
概略
アーキテクチャ = (プロダクトの特性 + チーム体制 + 個人的に死守すべしと考えるポイント + マネージャor要望者の優先課題 + チームのやりたいこと)
プロダクトの特性
たとえば、えんたーぷらいずなシステム構築なのか、BtoCなシステム構築なのか...
リクエスト数はどれくらいなのかとか、可用性は...とか
アーキテクチャを決める前段階で決まってる内容
チーム体制
長く一緒にやってる超優秀なエンジニアだけで構成されたチームなのか、このプロダクトのために緊急でかき集めたエンジニアなのか...など
アーキテクチャの立ち位置とかもこの辺に絡んでくる
個人的に死守すべしと考えるポイント
たぶん各自違うポイントがある(経験とか指向性とかで)
その中でも、これを守らないと絶対ダメだと考えるポイントのこと
過去の経験とかの積み上げとか、前回の反省とかが生きるところ
判断基準は、「これを守れないプロダクトなら作らない」と言えるレベルかどうか
マネージャor要望者の優先課題
優先度の高い課題があって、大抵はQCDのどれかの事が多いけどたまにそうじゃないものもある
その課題をちゃんと理解して認識しておくことが重要
チームのやりたいこと
たとえば、新しい技術を使いたいとか、新しい手法を使いたいとかそういうの
全く新規性の無いプロダクト開発はそれはそれでつまらないことも多いので
ただし、ここまでに出てきた物の中では優先度が低くなることが多いのと、判断をミスると基本的に大変な尻拭いも待ってる部分なので未経験なことを大量に入れるのは避けた方が賢明なことが多い
アーキテクチャの決め方
プロダクトの特性を大前提にして大枠の仕組みを決める
チーム体制を考慮して実装方法・サンプルの作成などで方向性を確定させる
(ここでチームのやりたいことも入れる
(代替があるので後回しにできるものは後から対応して行く
個人的に死守すべしと考えるポイントは全工程で守れるように気を配り続ける
割と早期にマネージャor要望者の優先課題が満たされているのかを判断する
判断した結果、満たされていない場合は調整 or アーキテクチャの見直しをする
大体出来た辺りで走り始めつつ
後回しにした物を色々追加していく
昔はアーキテクチャって言っても単純だったのに、どうしてこんなに増えたんだろうなぁ〜って思ってふと考えたら、プロダクトの特性が凄く多岐にわたることに気がついた
突然流入する100req/sの更新処理を多いと思うか少ないと思うか、準リアルタイムでのデータ同期がどれくらいあるか...という世界かなぁ〜
クラウドの前だったらサーバ費用がかかりすぎるって言って考えなくて済んだのにw
どうつくるか、どんな手法で作るか問題
どう作るか、どんな手法で作るかは、そのプロジェクト特性・企業文化・案件の前提条件を考慮して、各々が決定すればいい話で「唯一絶対の正解」なんてものは存在しないと思ってる。
比較的広範囲に適用可能な方法とかは存在していると思ってる。
それを否定的な人も居ることは知ってるし、それで良いと思ってる。
適切な決定方法を無視して「自分の思う絶対の正解」を利用したときに酷いことになるのも見てるので、色んな人の意見を見た方が良いと思ってる。
そして観測範囲外に「最も適切になる手法とか」が存在することもあると思ってる。
「そのプロジェクト特性・企業文化・案件の前提条件」を考慮せずに、その時の選択に否定的な意見を言う人も見たことがある。なので、アーキテクトの人は大変だなって思う
まぁ何が言いたいかというと、「makotanの短いソフトウェア開発の経験」程度では「銀の弾丸は探せてない」
2022年にあれこれ作るとしたら
高負荷とかホント嫌いなのに高負荷なサービスに関わる事が多いので、そういう環境を前提にぼーっと考えてた
(考えるきっかけはワクチンの時の待合室
DBはAurora Serverless
APIサーバはFargate
LBはALB
認証は...がんばって
ユーザ向けのサーバはAWSじゃなくて(ぇ?
Cloudflareで全部賄うとする
ユーザのリクエストはCloudflare Workersで受けて、処理する
CloudflareにDBとかS3互換のサービスとかあるので、その辺を上手に使いつつ
APIサーバのリクエストが必要なときだけAPIサーバを呼び出す
ちゃんとCloudflareでCDNも用意する
ここまでやって(AWSの)DBサーバが上限になるようなら待合室を使って待ち行列を作る
とすると...不要なタイミングでは安くて、でも高負荷にも耐えれるサービスが出来そうな気がした(気がするだけです
APIサーバをLambdaじゃなくてFargateにする理由とかはあるけど、ちょっと文字数制限で書けないw
コードジェネレータの分類2022
ぼーっとしてたら増えたので、前のをまるっとコピペして追記
A型(type-A)
Excel等のツールだけを使ってコード生成する
Excelの式とかmacroとかを駆使する
定義と生成の距離が短い
B型(type-B)
既存の何らかの定義を読み込んでコード生成する
DBのSchemaとか、ソースコードとかを入力として使う
一種類の定義からかなりの種類の出力が出来る様になる
C型(type-C)
専用の定義を使ってコード生成する
専用のエディタがあったり、yaml/jsonを入力として使う
定義した内容を使った定義が出来る
広範囲のコード生成が出来る
D型(type-D)
入力と出力を渡すとその間を埋めるコードを生成する
E型(type-E)
機械学習で実装を支援する
IDEに組み込まれて意図を理解しつつ実装を支援する形
1種(type-X1)
生成した物はプログラマには触らせない
専用ツール上で動作するためのコードを生成する
2種(type-X2)
生成したコードはプログラマ変更する前提
再生成は出来ない
3種(type-X3)
生成したコードは拡張ポイントを使ってプログラマが動作を変える
再生成も可能
たとえば・・・って書こうと思って例が懐かしすぎて止めたw
型A,B,C
種1,2,3
はそれぞれ組み合わせが自由なので
YYYってツールはtype-B2とかの表記が出来るよと
今のところ自分が認識してるコード生成ツールがこの分類でどれかに当てはまってる
そしてそんなことを考えてると言うことは・・・
安否確認の仕組みを真面目に考えてみた
安否確認の基本要件
安否確認が必要になったら自動で発動すること
ユーザが一気に来ても耐えられる事
ユーザの事前登録(通知先、居所)が可能なこと
(出来れば)マルチテナントで複数社が相乗り出来ること
ユーザの管理そのものは管理者が行うこと
費用はなるべく安く!
安否確認の情報はなるべく本家に近いところから取得として...
気象業務支援センター Japan Meteorological Business Support Center
地震速報(警報)に限定すれば3万円あれば十分
真面目に探すとサードパーティっぽいAPIはもっと安いので上限はこのくらい
ユーザの管理は色々面倒なので、Auth0あたりにまるっと任せる方針として
Auth0: Secure access for everyone. But not just anyone.
費用はMAU(月間アクティブユーザ)なので、ほぼ10ユーザのアクセスもないんじゃ無いかと想定。最大アクセスはユーザ管理したい全員なので...金額はpriceページでどうぞ
APIへのアクセスは常時実施する必要があるので、Fargateあたりで常駐させる前提
固定IPが必要な場合はNAT経由してと...
ユーザ情報、事前登録情報、警報情報はまるっとDynamoDBで管理
管理画面系はAPI Gateway & Lambda & S3で構築
安否確認とかトレーニングしないときの固定費がかかるのはこれくらい
ユーザ毎の費用は無視すると10万円いかない程度?
安否確認イベントが発生したら...
登録ページを用意(S3)
dynamodbにイベント情報を登録
fargateの通知taskを実行
戻りのURLはLambda@EdgeなURLにしてLambdaで直接DynamoDBに登録しつつ、集計
一定期間の間、定期的にLambdaで管理者への集計結果の通知メッセージ&緊急対応が必要な人のリストを送信
イベント発生時の費用はこれくらいなので...たぶん通知にかかる費用が一番高そうな程度でだいぶ安い
ほぼ固定の費用はないし、リクエストの上限も無視出来る(Lambda@Edgeのリミットに引っかからなければ)
ということで実はAWSとかSaaS使えば安否確認の仕組みって実装コストの方が圧倒的に高いだけだって事に気がついた夜中の地震でした
思いつきで書いたkotlinのコード
別に意味は無い
internal fun enumConditon() {
val noString = ConditionData("")
assertEquals(noString.enum, CDEnum.NOSTRING)
assertEquals("ERROR", perocessData(noString))
assertEquals("strが空です", getMessage(noString))
}
sealed interface DataAddType
class OK: DataAddType
class ERROR: DataAddType
class MISSING: DataAddType
data class ConditionData(
val str: String
) {
val enum: CDEnum = CDEnum.test(this)
}
enum class CDEnum(val condition: (ConditionData) -> Boolean,val addType: DataAddType, val msg: String){
OK(
{it.str.isNotBlank() && 10 <= it.str.length && it.str.length < 50}, OK(), ""
),
NOSTRING(
{it.str.isBlank()}, ERROR(), "strが空です"
),
LENGTH_OVER(
{it.str.isNotBlank() && 50 < it.str.length}, ERROR() ,"strの文字長がオーバーしてます"
),
LENGTH_MINI(
{it.str.isNotBlank() && it.str.length < 10}, ERROR(), "strの文字長が不足してます"
),
MISSING({true}, MISSING(), "判断外のエラーです");
companion object {
fun test(data: ConditionData): CDEnum = values().first { it.condition(data) }
}
}
fun perocessData(data: ConditionData): String {
return when (data.enum.addType) {
is OK -> "OK"
is ERROR -> "ERROR"
is MISSING -> "MISSING"
}
}
fun getMessage(data: ConditionData): String = data.enum.msg
SQLだけで 2WAY SQLっぽいことをやる
ぼんやりしてたらなんとなくイメージが沸いたのでちょっとやってみた
完全に同じ事が出来るかというと...微妙なのでっぽいこと
確認したのはpostgresqlだけ
select * from table1
where (:v is null OR field = :v)
ちょっとだけメモ
:v に null を入れるとis null がtrue になって、この()内の条件が常にtrueになる
:v が null 以外の時は field との比較になるので比較条件がチェックされる