論理モデルと物理モデルの実装を分離する話
論理モデル(ドメインモデル)と物理データモデル(例:ERモデル)を、実装上分離したい。その理由を書く。
TL; DR
- ちゃんとカプセル化をして、バグが混入する余地を無くしたいため
- 実装は大変だが、楽する方法はある(ただし、リスクもはらむ)
分離したい理由=カプセル化をするため
例えば、以下の仕様があるとする
- 申込は、複数の申込詳細(商品、個数)を持つ
- 申込の状態が「未申込」のときのみ、詳細の編集が可能
論理モデルで表現するとこうなる
コード(golang)だとこんな感じ
type Order struct { id int status string details []orderDetails } type orderDetails struct { id int product string amount uint } func (rcv Order) Edit(o orderDetails) Order { if rcv.status != "draft" { return rcv } // ... 更新の処理、割愛 }
一方、物理モデルは、正規化をしたERモデルだとこうなる
何とびっくり、依存関係が逆転している。 これをコードで表すと下記のようになる
type Order struct { id int status string } type orderDetails struct { id int product string orderId int } func EditOrder(o Order, od orderDetails) (editedOrderDetails []orderDetails) { if o.status != "draft" { return o } // ... odを更新する処理、割愛 }
見てわかる通り、要件にある「 申込の状態が「未申込」のときのみ、詳細の編集が可能」というロジックが、Orderの外に出ている。 一見問題なさそうに見えるが、暗黙的にEditOrderの引数は、申込と、その申込が持つ申込詳細をわたす という制約事項があり、これを守らないと意図しない更新が行われることになる。
まとめると、物理モデルをそのままアプリケーションに持ち込むと、業務処理におけるいろんな制約事項がかけづらくなるので、バグの混入余地を残してしまったりとか、仕様変更時の影響箇所が分かりづらくなったりとか、そもそも新しくジョインした人が使用を把握しづらかったりとかする。
なので、クリーンアーキテクチャやレイヤードアーキテクチャ、オニオンアーキテクチャの「repository層」でモデルを変換する必要がある。
でも変換って大変じゃん?
楽をする方法はある。
論理モデルのまま永続化する
論理モデルを、JSONとかにパースしてまるっとそのまま永続化すれば、めんどくさい変換をしなくて良い。もしくはデータストアにMongoDBみたいなドキュメント思考DBを使っても同じことが言える。 ただ当然リスクもあって、RDBMSをつかわないということは、多くのRDBMSが採用している制約機能(UNIQUEとか、FOREIGN KEYとか)がつかえないので、 想定していないデータがDBに保存される ことは十分あり得る。
OR Mapperの機能を活用する
OR Mapperはその名の通り、オブジェクトとリレーショナルモデルを相互変換してくれるので、機能を活用することで、論理モデルから物理モデルを自動生成できたりする。 例えば、gormなら、上記の論理モデルでマイグレーションしたら、上記の物理モデルがそのまま出来上がって、検索時もPreloadを使うとまとめて取得できる。
Has Many | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
共通するリスク:サブタイプが使えない
上記のどちらのやり方でも、サブタイプに変換するのが難しい(Factory Methodを指定できるようなOR Mapperなら対応できるけど) サブタイプを使えないということは、例えば、上記申込とはロジックの違う別の種類の申込が出てきた時に表現が難しくなる(例が思いつかないけど、、、
最後に:ガワはわりとどうてもよくね
あくまで個人の感想ですが、クリーンアーキテクチャもオニオンアーキテクチャも、ドメインじゃない「ガワ」の部分にフォーカスしたアーキテクチャだと思っている(クリーンアーキテクチャの本をよんだけど、ドメイン層については3Pくらいで終わってた記憶が)。ただ、個人的には一番大事なのはやっぱり「ドメイン層」だとおもってて、ドメイン層で、要件定義から洗い出した概念モデルがしっかり表現できていれば、後の仕様変更がしやすくなると思っている。
てか、よくレイヤー間を疎結合にするメリットに、テストする時にモックに差し替えやすいみたいなことを言われてたりするけど、モックをつかってまでテストしたいことってある?
ということで、今日は僕の設計思想&ポエム会でした
他人がつくったテーブルなど信用しない
過激なタイトルですが、とても優しい内容ですw BigQuery(以下、BQ)をよく使います。とても便利なのですが、PRIMARY KEY制約(またはUNIQUE制約)がないのが不満です。
例えば、こんなテーブルがあったとき(あえてDDLで書いてます)
CREATE TABLE clients ( client_id NUMERIC , client_name VARCHAR , create_at TIMESTAMP );
client_idにPRIMARY KEY制約が張られていないと、キーが重複するデータが存在しない可能性を否定できません。
作成者に言わせると「いや、clientsってテーブルだから、client_idで一意になるのは当然っしょ♪」ってなるかもだけどうるせー。こっちは、お前の書いたプログラムも、テーブル設計も、ETLのスクリプトも全くもって信用してねー(お前=1年前の俺、とかだったりするのが悲しいところ、、、)
そして、キーが重複すると結合したとき地獄をみます。たとえば、logってテーブル(スタースキーマでいう、Factテーブル)と前述のclients(ディメンションテーブル)を結合するとき
SELECT client_id , count(1) as cnt FROM log JOIN clients USING(client_id) ;
重複データがあるとログの件数が2倍、3倍とかになってしまいます(この集計結果を元に課金とかしてると、誤課金ですね♪)
ということで、自分の身を守るために、結合する前に重複を排除する必要があります。 愚直にやるなら下記のように、GROUP BYを使うやり方があります。
SELECT client_id , MIN(client_name) AS client_name , MIN(create_at) AS create_at FROM clients GROUP BY 1 ;
ただ、これだとカラムが増えるたびにメンテしなきゃだし、なにより全部のカラムを集合関数で書き直すのがめんどうです。
そこで、おそらくBQだけですが、STRUCT型を使って下記のようにすると、もっと楽にかけます。
WITH -- いったんSTRUCTに s AS ( SELECT AS STRUCT c FROM clients) -- idでGROUP BY、のこりはANY_VALUE , g AS ( SELECT c.client_id, ANY_VALUE(c) AS c FROM s GROUP BY 1 -- cを展開 SELECT c.* FROM g ;
ところで、BQは実はSELECT句でテーブル名を指定すると、STRUCTとして展開してくれます。 例えば、下記のようなクエリを実行すると
WITH clients AS ( SELECT 1 AS client_id, 'クライアント名' AS client_name, timestamp('2022-12-01') AS create_at ) SELECT clients FROM clients ;
結果はこうなります
※jsonだとこう
[{ "clients": { "client_id": "1", "client_name": "クライアント名", "create_at": "2022-12-01 00:00:00.000000 UTC" } }]
この仕組みを使うと、実は1行で書けます
SELECT client_id, ARRAY_AGG(clients ORDER BY create_at LIMIT 1)[OFFSET(0)].* EXCEPT (client_id) FROM clients GROUP BY 1;
ARRAY_AGG(clients)とすることで、clientsテーブルのSTRUCTのARRAYにしています。[OFFSET(0)]で1個目の要素をとりだし、.*で構造体の中身を展開しています。EXCEPT (client_id)は、グループ化の基準列もclient_idでダブっているので、1つ除外しています。 また、ARRAY_AGG内でORDER BY hoge とすることで、実行のたびに結果が変わるのを防いでいます(この例だと、create_atが重複したら意味ないですがw)
ということで、上記でviewをつくって自分の管理するデータセットに置いとけば、無用な心配をする必要なく、結合できますね。
CREATE VIEW my_clients AS SELECT client_id, array_agg(clients ORDER BY create_at LIMIT 1)[OFFSET(0)].* EXCEPT (client_id) FROM clients GROUP BY 1;
番外編
重複があっても正しい集計結果を出す方法が他に、log側でUUIDを作ってユニークをとる、というやり方もあります。
WITH l AS (SELECT GENERATE_UUID() AS uuid, log.* FROM log) SELECT client_id , COUNT(DISTINCT uuid) as cnt FROM l JOIN clients USING(client_id) ;
が、FACTテーブルは大概でかいので、スロットの消費時間がえげつないことになるので、やめたほうがいいでしょう。
CXについての話
アドベントカレンダーの空いてる日をエモい記事で埋める担当のdoiluxですw 今日はCXについて書きます
DXは最近よく耳にしますが、CX(某テレビ局じゃないよ!)は初めて聞く方が多いかもしれません。 なぜなら僕が今作った言葉だからw
CXはChild Transformationの略で、子供ができたら仕事のスタイル変えないと死ぬ、、、って話です。 もちろん、保育園に入れてるとか、両親のフォローがもらえるとか、 年収が1億ある とかで事情は変わってくると思いますので、あくまでも参考まで
- プログラミング超好き、休日もなんか作って遊ぶぜ-> 休日は子供と遊ぶぜ
- 体力には自信あり、進捗の遅れは残業でフォローするぜ -> 定時後は育児だぜ。仕事するのは子供が寝てからだぜ
- オンコール対応?オレにまかせろ! -> 電話が鳴ると子供が起きるからやめてー!!いろんなことを想定して電話ならないようにしよう
- 技術的チャレンジもまかせろー。やってみたいこといっぱいあるぜ! -> 時間が(以下略)
- マーケット知識も仕入れてみんなに展開するぜー -> 時間(以下略)
- 我が辞書に積ん読という文字はない、技術書も読むぜー -> (以下、、、)
- コミュニケーションも大事!飲み会も(以下、、、
長々と書いたけど、要するに仕事やスキルアップに振れる時間と体力が減るよ―ってことですw なので、昔の僕みたいに、体力と好奇心と勢いで仕事してきた人は、ワーキングシフトしないと大変です。
そんな僕が、少ない時間で最大限成果を上げるために心がけていることを記載します(正解ではないです。あくまで心がけているだけ)
- 技術的チャレンジは任せた(情報提供をしたり、ロードマップや評価観点のレビューはするけど、自分は積極的に関わらない)
- 進捗遅れなどはみんなでフォローしあおう。そのためにまずは自分が考えたことを積極的にログにのこす(コミュニケーションコストを減らすために、具体的にはissueとかめっちゃ書いている)
- 非同期コミュニケーションを効率化させるために、自分が考えたことを(以下略
- きれいなドキュメントを残すことに時間を割かない
- 勉強会などを開き、記事のシェアを促進し、自分の情報収集コストを下げる(部署のみんな、オラに情報を分けてくれ的な、、、
- 技術書は、読んだ人が感想をTwitterにあげたりとかしているのを収集して読んだ気になる(本当に読んだほういいやつはちゃんとよむ)
- 飲み会行かないかわりに、全体朝会など組織横断のイベントで積極的にチャットでガヤを飛ばし、存在感を残す
最後になりますが、誤解なきようフォローしておくと、子育てで得られているものも多く、子育てを通じて自分が人間的に成長している実感はあります。
以上
地図なき冒険の始まりと羅針盤
PTAアドベントカレンダー2日目です!! 会長とおなじ部署で働く、doiluxと申しますmm 1年に一度のテックブログを書く時期になりましたw 今年はエモい記事を書きます。
私はアンチャーテッドというゲームのファンです。ゲームとしての面白さもさることながら、ストーリーやキャラクターも魅力的で、なによりUncharted(=地図に載っていない)というタイトルにロマンがあり中二心をくすぐられますねw
さて、私は2年ほど前から、エンジニアからプロダクトマネージャに主軸をおいていて、新規事業やDXに関わるプロダクトマネージャのミッションは、まさに”地図なき冒険”であると感じます。というのも、DXではない、「業務改善」の場合は、基本的には現状の業務の”型”があり、どの部分を自動化するか?という観点で考えていましたが、DXの場合はそもそも業務自体をどう変えるか?というところまでディープに踏み込まなければならなくなるので、より森羅万象に首を突っ込むことになるからです。
この地図なき冒険において、定石となる戦略はMVP(Minimum Variable Product)を構築して、学びを得ながら広げていくことだと思いますが、利害関係者が多ければ多いほどMVPわからなくなって、前に進めなくなることがあります。とはいえ、MVPにはいくつかのパターンがあると思うので、煮詰まったときには進め方のパターンを基に議論するといいと思います。
機能を絞る
コアな機能だけ先に作って、受け入れられるか検証するパターン。ToCのプロダクトであれば、 申込みページを作って、裏の運用(デリバリー、契約変更、解約など)は手作業 みたいなパターンですね。その後の展開としては、ユーザーに受け入れられなかったら撤退、受け入れられた場合は裏周りの業務改善をする、という進め方になります。
ユーザーを絞る
利用者を限定するパターン。ToCの場合は 一部のユーザーだけ機能を公開する やつです。 エンジニアリングの現場で、例えば一部のチームだけgithub導入して、ノウハウがたまったら全チーム展開、みたいなのもこれです。 社内で使うシステムの場合は、セキュリティやUIを一旦サボることでローンチまでの期間を短縮できます(↑のgithubの例でいうと、最初は権限管理なしの全オープンで、全チーム展開時に権限管理をしっかりするなど) アーリーアダプターの人選をミスると展開したときに他のユーザーに受け入れられないリスクがあります。
別のツールとセットで提供する
検証したいツールとセットで、ユーザーにとって明らかなメリットがある機能を提供するパターン。 従業員の行動管理のために、スケジュール管理ツールを提供するみたいな。 作る物は増えるけど、撤退してもユーザーに一定の価値を残せるメリットがあります。
既存のツールに何かを仕込むパターン
↑に似ていますが、ユーザーがすでにつかっているものに乗っかるパターン。 例えば、従業員の間でトレロでタスク管理する文化があるとするならば、行動把握のためにトレロをポーリングするとか、BYODであれば通話アプリや電話帳アプリを提供して顧客リストや通話履歴(コンタクト履歴)を管理するなど。
デファクトスタンダードに従ってみるパターン
一旦、世の中にデファクトスタンダードで採用されているフレームワークに乗っかってみるパターン(例:スクラムとか) そこで得た学びを基に自分たちのフレームに仕上げていくという感じですかね。
パッと思いつく限り出してみましたが、他にもありそうですね。 議論が煮詰まったときの参考になれば幸いです。
最後に、冒頭でアンチャーテッドのファンであると書きましたが、実は1秒もプレイしたことがない(プレイ動画はめっちゃ見てますw)ので、このポスト見てくれたサンタさん、PS5とゲームに打ち込める時間のプレゼントを待ってますmm
Figmaを完全に理解する
はじめに
エンジニアの言う「完全に理解した」「なにもわからない」「チョットデキル」って本当はこういう意味?「わかる」の声多数 - Togetter
サーバーサイドエンジニアと兼任でプロダクトマネージャもやってます。最初、モックをパワポでつくったり、既存の画面のhtmlをごにょごにょしたりしてたのですが、もうちっといい方法ないかなーと思ってました。そんなおりに、同僚がFigmaを使っていたので、軽く勉強して使ってみたら最高でした。
ということで、僕みたいな、今までSketchとかそういうのを全く使ってなかった人が、簡単な画面だったらモックを作れるよーくらいになれるような、チュートリアルのチュートリアル的なものとして、この記事を残します(細かいところとか間違いがあったらこっそり教えて下さい)
押さえておくべき概念
最低限、下記の概念を理解しておく必要があります(いったん意味がわからなくてもいいです。あとで読み返してください)
概念 | 説明 |
---|---|
チーム | チームですw View only team memberみたいなのが出てきたりするので、アクセス制御の単位だと思う |
プロジェクト | チームの下の概念。ディレクトリみたいなものかなーと思っている。ちなみに無料プランだと1チーム1プロジェクトしかつくれなそう |
フレーム | 枠のこと。中に下記のコンポーネントだったり、インスタンスだったりをもてる |
コンポーネント | 部品ですw ボタンとかフォームとかそういうの |
インスタンス | コンポーネントから作られたインスタンス(語彙力w |
とりあえずファイルを開くまで
まずはSign Upしてください(キャプチャ忘れた
多分、自分のチームとデフォルトのプロジェクトが作られていると思うので、+ボタンからDesign Fileを選びます
一覧ページを作ってみる
勤務時間登録画面をサンプルに作ってみます。
まずは、画面全体になるフレームを作ります。左上の#がフレームです。右のメニューからサイズを色々選べます(ドラッグして、任意のサイズにもできます)いったん、Desktopを選びました。あと、フレームの名前もわかりやすいように「メインページ」とかにしておきます。
次にコンポーネントの置き場所を作ります。任意のサイズで作りました。背景はわかりやすいように、メインページとは変えておきます。
表のヘッダーを作っていきます。コンポーネントのフレームの中にフレームを作り、テキストをいい感じに配置していきます。
出来上がったら右クリックより Create Componentを選びます。これでコンポーネントになります(アイコンも変わります)
あと、Frame1という名前を「勤務時間リスト/ヘッダー」にしておいてください(キャプチャ取り忘れ)
次に、行を作ります。先程作ったコンポーネントをコピペします。 コピペしたものは、インスタンスになります(アイコンも変わります)
右クリックからDetouch Instanceを選ぶと、インスタンスからフレームになります。あとは色とかテキストを調整します。
調整し終えたら、同じ要領でコンポーネント化し、名前を「勤務時間リスト/行/入力済み」にしておいてください(この名前、すごく重要です)
同じ要領で未入力の行も作ってください。最終的には下図のようになります。
ここまでできたら、各コンポーネントをインスタンス化し、メインページに配置していきます。 日にちの部分は、各インスタンスのテキストを変更してください。
なお、上記の命名規則にしておけば、Swap Instanceから入力済みの行と未入力の行を簡単に入れ替えられます(ボタンとかでよくつかいます。アクティブにしたり、非アクティブにしたりとか)
ボタンをつくる
同じ要領でボタンを作る。フレームにテキストを入れ込む形で作ります。 テキストの位置をセンタリングしたら、ボタンのサイズを変えてもテキストの位置が固定されます。
最終的にはこんな感じで、非アクティブのボタンも作り、コンポーネントに入れ込めば、インタンスに自動で反映されます。
画面遷移をつくる
右のメニューから Prototypeを選び、コンポーネントやインスタンスから矢印を遷移先にうにょ〜と伸ばせばOK (ここはチュートリアル動画の方がかわりやすいかと
最終的にこうなります
Flow1のところを再生すると、モックを確認できます。
モーダルを表示する
削除ボタンを押したときに「本当にええんやな?」的なアラートを出してみます。
まずモーダルをつくります。今までやってきたように、フレームを作って、テキストとボタンのインスタンスを配置します。
削除のボタンからうにょ〜〜〜と線を伸ばし
Navigate ToでOpen Overrayを選択。Add background behind overrayにチェックを入れておくとよいです(モーダルを開いたときに、背景が暗くなります)
削除や戻るボタンから矢印を伸ばし、右上の×(矢印を伸ばすとでてくる)につなげます
これでモーダルが完成しました。
おわりに
ある程度規模がおおきくなってくると、コンポーネントの管理だったりとかが課題になってくると思いますが、簡単な業務システムのモックであれば、これだけ押さえておけばだいたい事足りるかなーと思ったりします。
マインドマップでタスク管理したら捗った
タスク管理でカンバンやめてマインドマップにしたら捗った。
カンバンに感じてた課題
Whyが可視化できないこと。アジャイルなものづくり(課題解決の手段が誰もわからず、PDCAサイクルを回して改善していく)をするなら、今作っている機能がどういう文脈で必要になったかを常に意識したい。最初はチケットに記入していたが、いちいちチケットを開いて確認することに煩わしさを感じていた。あと、朝会で誰が何をするかを共有するので、そもそもチケットでタスク管理しなくてもいいんじゃ?と思ったので、思い切ってmindmeisterを使うようにしてみた。
よかったこと:施策の情報を一眼でわかる
結果は狙い通りで、各施策がどういう文脈で必要になったかが一眼でわかるようになってよかった。また、失敗した施策の原因や、それを踏まえて打った別の施策の内容もフローで確認できるので、同じ施策を再び検討してしまう(そして同じ失敗を繰り返す)という無駄も削減できそう。
デメリットとしては、今誰が何をしているのかを可視化できないが、前述のとおり、それは気にしないことにしたので問題なし。
まとめ
タスク管理はチームの状況を鑑みて何を見える化したいか?でツールを選ぶといいですね。
目標設定前にSWOT分析する
エンジニアリングマネージャーになってから約一年半、色々と試行錯誤した中で、手応えのあった施策を書く。 なお、記載内容は個人的な意見であり、所属組織の見解ではない
前提:目標設定の目的は成長支援とリソース把握
目標設定の目的は、個人の成長を組織的に支援することと、キャリアプランを把握して未来のリソース計画つなげることだと思ってる。
前者は、多くの人は成長実感を得られることがやりがいにつながるので、モチベーション維持のため、後者は今いるメンバーがいつ、何ができるようになるかを把握することで、先手を打ってリソース確保するためである。
人事評価のためではない
目標設定と評価を連動させる(目標80%達成だからB評価な、みたいなやつ)のはいいことがない。目標設定をミスると不利益なるので、目標設定にめちゃくちゃ時間かけてしまう(&ストレスになる)から
あと、目標を3つ立ててるけど実はレバーは一つ、みたいなハックをしたりとか、定量目標を出せと言われてテストのカバレッジみたいな指標をひねり出すのもあるあるだ(時間をかけて手を動かせば上げられる指標は成長目標には不適切だ)
基本、目標設定は上司とのすり合わせも含めて一時間で月1見直すくらいがいいと思う
SWOTで成長戦略の解像度が上がる
本題ですが、経営戦略を立てるときに使うSWOT分析を目標設定に活用すると、目標が立てやすくなった
以下、活用例を記す
Strength:今の強み
今のチームで価値を生み出すための、自分の強みを書く。
例: Go言語の実装力 AWSの知識など
Weakness:伸ばしたいスキル
弱みというより、伸ばしたいスキルを書く。
例: 機械学習の知識
Opportunity:成長機会
成長戦略上のチャンスポイントになりそうな周辺の動きを書く。
例: レコメンドの改善プロジェクトが立ち上がった 優秀なデータサイエンティストがジョインしたなど
Thread:課題
成長の阻害要因になりそうな動きを書く。
例: レガシーシステムの負債 サーバーサイドエンジニアが抜けるなど
ここまで言語化できたら、Oを使ってWを延ばす道筋を考えればOK。
例: レコメンドの改善プロジェクトでサーバーサイドの実装で貢献しつつ、データサイエンティストから技術を盗み、最低限データサイエンティストと会話できる程度のリテラシーを身につけるなど
Tは上司と解決策を考えよう