Runner in the High

技術のことをかくこころみ

elm-firestoreでJSを1行も書かずにElmアプリケーションでFirestoreを使う

この記事はElm Advent Calendar 2020の8日目の記事です

今年の春頃からコツコツと個人開発のアプリケーションで使うためのElm用Firestoreライブラリのパッケージを作っていたので、その紹介をします。

github.com

できることはREADMEに書いてあるとおりで、基本的なCRUDオペレーションが一通りサポートされているのと、たぶんトランザクションもかけられます。

f:id:IzumiSy:20201209230333p:plain

また、このelm-firestoreは独自のエンコーダ/デコーダをパッケージのモジュールとして提供しており、Firestoreとの通信で使用される特別なJSONのデータ構造をパッケージの利用者が意識しなくてもよい作りになっています。

READMEにおいて "A type-safe Firestore integration for Elm." と謳っているのは、JSを書かずにFirestoreとの連携が実装でき、さらにエンコーダ/デコーダでも型安全性が担保されているという理由からです。

使い方

さて、実際のコードの雰囲気を見ていきます。

elm-firestoreでは取得されたデータは Firestore.Document というレコードで取り出せるので、それを格納できるようにしておきます。

また、Firestoreで利用可能な特別な型として ReferenceGeopoint がサポートされています。

import Firestore
import Firestore.Types.Geopoint as Geopoint
import Firestore.Types.Reference as Reference

-- model


type alias Model =
    { firestore : Firestore.Firestore
    , document : Maybe (Firestore.Document Document)
    }


type alias Document =
    { timestamp : Time.Posix
    , geopoint : Geopoint.Geopoint
    , reference : Reference.Reference
    , integer : Int
    , string : String
    , list : List String
    , map : Dict.Dict String String
    , boolean : Bool
    , nullable : Maybe String
    }

Firestore 型の生成は初期化は次のように行います。

もしもFirebase Authorizationを利用している場合には withAuthorization 関数を用いて認証トークンを与えることができます。

その他にも withDatabase 関数などでデフォルト意外のデータベースを指定したりすることもできますが、これらはoptionalです。

import Firestore
import Firestore.Config as Config

init: Firestore.Firestore
init =
    let
        config =
            Config.new
                { apiKey = "your-own-api-key"
                , project = "your-firestore-app"
                }
                |> Config.withDatabase "your-own-database" -- optional
                |> Config.withAuthorization "your-own-auth-token" -- optional
    in
    config
        |> Firestore.init
        |> Firestore.withCollection "documents" -- optional

ドキュメントの取得は非常にシンプルで、次のように書くだけです。

取得先のパスは withCollection 関数を使って事前にセットしておきます。

fetchDocument : Firestore.Firestore -> Cmd Msg
fetchDocument firestore =
    firestore
        |> Firestore.get decoder
        |> Task.attempt GotDocument -- GotDocument (Result Firestore.Error (Firestore.Document Document))

get 関数はデコーダを受け取りますが、このデコーダFirestore.Decode モジュールが提供する関数群で組み立てていきます。

エンコーダのほうも、同様にパッケージから提供されている Firestore.Encode を用いて組み立てていく形になります。

import Firestore
import Firestore.Decode as FSDecode

decoder : FSDecode.Decoder Document
decoder =
    FSDecode.document Document
        |> FSDecode.required "timestamp" FSDecode.timestamp
        |> FSDecode.required "geopoint" FSDecode.geopoint
        |> FSDecode.required "reference" FSDecode.reference
        |> FSDecode.required "integer" FSDecode.int
        |> FSDecode.required "string" FSDecode.string
        |> FSDecode.required "list" (FSDecode.list FSDecode.string)
        |> FSDecode.required "map" (FSDecode.dict FSDecode.string)
        |> FSDecode.required "boolean" FSDecode.bool
        |> FSDecode.required "nullable" (FSDecode.maybe FSDecode.string)

基本的に、Elmにおいては対象となるひとつのデータ構造に対してエンコーダとデコーダを実装することになりますが、それはelm-firestoreでも同じです。

しかし、この手間を省くことができるようにelm-firestoreでは Firestore.Codec としてCodecモジュールが導入されています。Codecを使うことでひとつの実装からエンコーダをデコーダを作ることができるので、基本的にはそれぞれ個別に定義するよりも、こちらを使うほうが楽ちんなのでおすすめです。

コーデックとはなんぞや?という方はこちらの記事をどうぞ。 izumisy.work

他にもドキュメントの作成、更新(patch, upsert)やトランザクションなど諸々ありますが、このあたりは応用的な話になるので説明を端折ります。詳しい使い方などはパッケージのドキュメントにサンプルコードとともに書かれていますので、ぜひ読んでみてください。 package.elm-lang.org

ある程度の機能は揃っていますが、まだまだ色んな機能の追加を画策中ですので将来的にダイナミックにメジャーバージョンで破壊的変更が入る可能性があります。ここだけ留意しておいてもらえれば幸いです。

リアルタイム・アップデートについて

こんだけ宣伝しといてアレですが、ひとつ決定的に惜しい点としてこのパッケージはFirestoreの一番の強みであろうリアルタイム・アップデートがサポートされていません。

これは内部的にHTTPのRESTful APIでFirestoreと通信をしているのが原因で、どうやら公式のJS製FirestoreライブラリとかはgRPCプロトコルを用いてFirestoreと通信をしているみたいです。詳細なことは僕には分かりませんがServer Streaming RPC的なのを使わないとドキュメントの更新はリッスンできないようです。少なくともRESTful APIでは100%できません。

というわけで普通にCRUDするには十分ですが、リアルタイム・アップデートを使うにはまだまだ、という感じです。いずれgRPCプロトコルでFirestoreと通信するように書き換えたいなと思っていますが、現状時間がなくてあまり手がつけられてません。

もし「やってやってもいいぜ」という方がいましたらぜひPRをお待ちしています
もちろん、バグ報告issueなども大歓迎です🙌

mercari/datastoreがDistinctOnメソッドをサポートした

先日PRを出していたものがマージされた。

github.com

GCP DatastoreのDistinctOnは履歴系テーブルを触るときに便利で、例えばチームごとのログイン履歴を保持するLoginHistoryというカインドに対して「全チームの最新のログイン履歴を取得する」というクエリがGQLで言うと以下のように一発で書ける。

select distinct on (teamId) createdAt, teamId 
  from LoginHistory 
  order by teamId asc, createdAt desc

Distinctだけじゃできないのん、と思われるがドキュメントによるとonする対象とprojectionしたいカラムが異なる場合にはやはりDistinctOnが使えないと上記のクエリと同じ結果は出せない。

SELECT DISTINCT a, b, c is identical to SELECT DISTINCT ON (a, b, c) a, b, c

もちろん、DistinctOnがなくてもアプリケーションサイドでgroup-byするような実装を書けばいいが、クエリで出せるならクエリで出したい気持ちがある。

イベント・ソーシングするようなデータ構造を持っている場合には便利に使えると思われる。

ElmでCodecを用いてエンコーダ・デコーダの実装をケチる

この記事はFringe81 Advent Calendar 1日目の記事です。

Elmでアプリケーション開発をしているとほぼ100%出現するのがエンコーダ・デコーダの実装。

基本的に両方セットで実装することが多いが、データ構造が共通であればCodecと呼ばれる類の機能を提供するライブラリを用いてエンコーダ・デコーダの実装をケチることができる。

Codecとは

Codecとは簡単に言うとデータ構造が共通なものであればひとつCodec用の関数を定義しておくだけで、エンコーダとデコーダを同時に実装することができる便利なもの。

まず、Elmでは書き込みと読み込みでエンコーダとデコーダが別れているため、基本的に同じデータ構造に対してそれぞれ実装する工数があるかつ実装が別々であるため改修時に実装ミスが起きるなどの可能性がある。 しかしCodecを利用することでエンコーダとデコーダをそれぞれ実装する必要がなくなる上、デコーダやエンコーダにだけtypoがあった!みたいなバグを減らすことができる。

Codec的なパッケージとしていま利用できるものにはminiBill/elm-codecMartinSStewart/elm-serializeのふたつがある。この記事ではそれぞれの違いや使い分けたほうがよさそうなポイントを紹介する。

elm-codec

package.elm-lang.org

最も最初にCodecという概念を誕生させたのはこのライブラリ。

兎にも角にもまずはサンプルコードから。以下のようなレコードに対してCodecを実装してみる。

type Language
    = Japanese
    | English
    | Other String


type alias Model =
    { name : String
    , age : Int
    , lang : Language
    }

Codecの実装はこんな感じになる。

-- codecs


modelCodec : Codec.Codec Model
modelCodec =
    Codec.object Model
        |> Codec.field "name" .name Codec.string
        |> Codec.field "age" .age Codec.int
        |> Codec.field "lang" .lang languageCodec
        |> Codec.buildObject


languageCodec : Codec.Codec Language
languageCodec =
    Codec.custom
        (\jp en other value ->
            case value of
                Japanese ->
                    jp

                English ->
                    en

                Other v ->
                    other v
        )
        |> Codec.variant0 "japanese" Japanese
        |> Codec.variant0 "english" English
        |> Codec.variant1 "other" Other Codec.string
        |> Codec.buildCustom

上記のようにまずレコードに対するCodecを実装した上で、そこに含まれるカスタム型のCodecも実装できる。

基本的に入れ子構造にしてデータ構造を用意でき、使っている感じはデコーダの実装に近いと思われる。パイプラインでバシバシつなげるのもいい感じ。

あとは用意されている関数を用いてValue型にエンコード・デコードできる。使い方は基本これだけ。

-- encode
Codec.encodeValue modelCodec model

-- decode
Codec.decodeValue modelCodec value

ドキュメントを見てもらえればわかるがstring, bool, intなどの基本的な関数は用意されており、ResultやDict, Setなどにも対応している。また、カスタム型も最大8バリアントまで実装されている。

エンコードされるJSONの構造

さて、ここまでくると一体どういうデータ構造のJSONエンコードされるのかが気になってくる。データ構造上はエンコーダとデコーダが共通でも動くものになるはずだ。

というわけで、上記のコードで示されているModelレコードをエンコードすると以下のようなJSONが作成される。

{
   "lang":{
      "tag":"other",
      "args":[
         "unknown"
      ]
   },
   "age":10,
   "name":"seiya"
}

agename は想像通りだが、カスタム型として定義されている lang には若干クセがある。

tag部分にはelm-codecが提供する Codec.variantN 関数に渡されているフィールド名が入り、そしてもしそのバリアントがタグとなるデータを持っている場合にはそれを args というフィールドで保持するという作りになっている。たしかに、こうでもしないとカスタム型はそのままJSONに格納できない。

elm-codecでは上記のようなデータ構造のみがカスタム型に対してマッピングできる作りになっているため、結論JSONのデータ構造がelm-codecによって制限されることになる。エンコーダ・デコーダを個別に定義しなくてよくなるのは楽ではあるが、JSONのデータ構造の柔軟性を失うのは若干痛い。

elm-serialize

package.elm-lang.org

elm-serializeはelm-codecよりも後発のパッケージであり、elm-codecと比較してパフォーマンスや安全性に関する点で違いがある。

パッケージ自体の使い心地はelm-codecとさほど変わらない。以下が実装例となる。Modelレコードはelm-codecの例で実装されているものと同じものとする。

-- serializer


modelSerializer : Serialize.Codec a Model
modelSerializer =
    Serialize.record Model
        |> Serialize.field .name Serialize.string
        |> Serialize.field .age Serialize.int
        |> Serialize.field .lang languageSerializer
        |> Serialize.finishRecord


languageSerializer : Serialize.Codec a Language
languageSerializer =
    Serialize.customType
        (\jp en other value ->
            case value of
                Japanese ->
                    jp

                English ->
                    en

                Other v ->
                    other v
        )
        |> Serialize.variant0 Japanese
        |> Serialize.variant0 English
        |> Serialize.variant1 Other Serialize.string
        |> Serialize.finishCustomType

定義した雰囲気もほぼほぼ同じだが、まずひとつelm-codecとの違いとしてelm-serializeはValue型だけではなくelm/bytesへの変換もサポートされている。対象となるデータ構造のサイズに応じてValue型とBytes型を使い分けることができる。

エンコードされるJSONの構造

最も異なるのは生成されるJSONの構造で、こちらはelm-codecよりももっとクセがある。

エンコードすると以下のようなデータになる。

[
   1,
   [
      "seiya",
      10,
      [
         2,
         "unknown"
      ]
   ]
]

elm-serializeはREADMEで「human-readableなフォーマットを求める場合には適さない」と書かれており、生成されるJSONの可読性を犠牲にしてJSONのサイズが小さくなるように工夫されており、そのためこのような形のデータ構造となっている。

また、elm-serializeはパッケージ内で定義されるエンコード・バージョンを生成されるJSONのデータに埋め込んでいる。上記のJSONで言うと配列トップレベルの 1 という数値がそれにあたる。この値はelm-serializeの中で定義されているものになっている。

{-| Possible errors that can occur when decoding.
  - `CustomError` - An error caused by `andThen` returning an Err value.
  - `DataCorrupted` - This most likely will occur if you make breaking changes to your codec and try to decode old data\*. Have a look at `How do I change my codecs and still be able to decode old data?` in the readme for how to avoid introducing breaking changes.
  - `SerializerOutOfDate` - When encoding, this package will include a version number. This makes it possible for me to make improvements to how data gets encoded without introducing breaking changes to your codecs. This error then, says that you're trying to decode data encoded with a newer version of elm-serialize.
\*It's possible for corrupted data to still succeed in decoding (but with nonsense Elm values).
This is because internally we're just encoding Elm values and not storing any kind of structural information.
So if you encoded an Int and then a Float, and then tried decoding it as a Float and then an Int, there's no way for the decoder to know it read the data in the wrong order.
-}
type Error e
    = CustomError e
    | DataCorrupted
    | SerializerOutOfDate


version : Int
version =
    1

https://github.com/MartinSStewart/elm-serialize/blob/1.2.1/src/Serialize.elm#L93-L112

JSONエンコード・バージョンを付与することで、仮に今後elm-serialize自体にbreaking changesがありエンコードされるJSONのデータ構造が変わってデコードエラーが発生したとしても、それがelm-serializeのバージョンアップ起因なのかそれとも純粋にデコード対象のJSONのデータ構造が間違っているのかを型レベルで判別できるようになっている。

このような後方互換性の考慮はelm-codecにはないため、この点はelm-serializeのほうが優れていると言える。

まとめ

エンコードデコーダをそれぞれ定義しなくていいなんて最高だな」と思って触っては見たものの、どちらもある程度のクセはあるため、どういう用途でCodecを使うかによっておそらく使い分けが必要になると思われる。

生成されるJSONのデータ構造をElmアプリケーションの外で触るようであればデータの構造は非常に大事であるのでelm-codecを使うべきだが、LocalStorageに保存するデータなどデータ・サイズに制限があるような環境においてはelm-serializeのほうが適していると言える。

ちなみにだがelm-firestoreにもCodecが実装されていて、データベースやLocalStorageに対するCRUDのようなデータソースの構造が一意に決まるシステムの入出力部分にはCodecのような仕組みを利用するのが適しているということが分かる。このような観点では、常に一意に入出力のデータ構造が決まるとは言えないサーバーサイドとの通信部分でエンコーダ・デコーダが必要になるWebフロントエンドのアプリケーションにおいてはCodecはあまり登場することはないのかもしれない。

ITエンジニアの副業あれこれ

いろいろやってみた知見が溜まったのでまとめ。

開発

時間給で労働を売る古き良きスタイルの副業。副業として機能開発とかバグ修正とかやったりする。

自分はUIがいい感じだったからという理由でOffersを使って副業を探した。

offers.jp

注意点として、日中まともに会社員やっている人だと、稼働時間が自ずと平日夜か土日だけになってしまう。これが厳しいところとして、副業先のチームの開発プロセスには載ることが基本できないため、非同期作業みたいな形になる&開発スピードが求められるコアな機能開発みたいなアイテムには入りづらくなる。

あと純粋にモチベーション的に平日夜とか仕事したくないときもある。でも副業でアサインされてると仕事しないといけない。場合によってはチームの開発作業の進捗妨害をすることにもなったりする。

独立して作業可能なアイテムを貰えるプロジェクトか、例外的に日中もコミュニケーションできる人の場合にはそこそこワークする副業だと思われる。しかし、ひとりで作業を進められない系の人(未経験とか)は確実にやらないほうがいいと思う。

メンタリング

自分はMENTAを使った。

menta.work

注意点として、MENTAはとんでもない低価格でメンターリングプランを提供するユーザーによって価格崩壊が起こっているので、ちゃんと金を稼ぎたければある程度の価格帯(30k-50k)でのメンタリングプランを提供する必要がある。もちろん高ければ高いほどコンバージョンしにくいが、ちゃんとした価格でまともな(?)メンタリングを受けたい層もマイノリティーではあるが存在しているので、そこを取りに行くのがよい。

私見だが、メンタリングにちゃんと金を払える人間のほうがコーチャブル*1でメンタリングに素直だと思うし、安くすることには安くする以上のリスクもある。

執筆(Webメディア)

エンジニアHubみたいなメディアで執筆をする仕事。

izumisy.work

基本的に1記事いくら、みたいな単価計算になる。すごく儲かるということはない。

最も重要な点としてメディアで執筆をお願いされるような記事というのは「自分が得意な分野」が対象になるということ。

自分が得意な分野ということは、記事を書くことは単なるアウトプットであって、自分の勉強になるかというと... そんなことはない。つまり、知っていることを書いているだけなので学びはほとんどない(編集の人が校正してくれるので文章を書く練習にはなるかもしれないが)。

技術書籍を執筆している人たちも同じようなことを言う。自分が知っていることをただ書くだけなので、自分の学びにはならない。それが許容できるなら執筆の仕事をやってみるといい。どちらかと言えば、金をかせぐというよりも自分のブランディングのため、というほうがモチベーションの向き筋としては正しいかもしれない。

技術顧問業(アドバイザー)

Twitterだとか知り合いのツテをたどって、自分の得意な領域でアドバイザー的なやつをやる。

自分はブロックチェーン技術に関する会社でElmのアドバイザーをやらせてもらっていた。

アドバイザー的な立ち位置であるからには「〇〇なときはどうするべきか」「この言語ではどうすることが最善か」などを知識や経験で持っていることが求められる。自分の場合は現職でElmをかなりの規模で使っているため、そのような知見を広められたらいいなという気持ちでやっていた。

また、顧問業は純粋にコードを書くのとはバリューの出し方が異なる。

開発のゴールはプロダクトを作ることだが、アドバイザーは実際に自分の手を動かすことは少ない。逆に、より大局的で大きなインパクトを生むことが期待される。例えばレビュー方針や設計方針など、チーム全体の生産性を向上させるためにはどうするべきか?を知識や経験を元に考えたりアドバイスしたりするなど。

コードを書いて機能開発をしたりバグ修正をしたりするというのは比較的わかりやすい価値提供であるが、顧問業となると単価もそれ相応に高くかつ設計やチーム全体のイシュー解決がメインの業務になるため、事業ドメイン理解も求められる。設計なんかはドメイン理解があって初めて正しさが分かるもので、ただ「デザインパタンやらフレームワークや言語が得意/詳しい」というだけで根本的な解決ができるかというとかなり怪しい。

自分が顧問業をやらせて頂いていたプロダクトは本業とは全く異なるドメイン領域のものだったため、この部分の理解が浅いことで設計のアドバイスが難しく感じることもあった。顧問業をやるにあたってドメイン理解は技術に関する知識以上に武器になる。

「技術顧問とはどうあるべきか?」みたいな話は元メルカリCTOのsotarokさんの記事もほぼ必読だと思う。

note.com

ブログ

自分の場合にはAmazonアフィリエイトリンクを記事中に貼っている。

例えば以下みたいな記事。

izumisy.work

エンジニアブログはうまく記事を書けば、はてぶでバズったりだとかTwitterで拡散されたりするので、その瞬間風速で金を稼ぐ。やり過ぎると「このブログはアフィ目的のやつだ」と気づかれる。自分も会社の後輩にこのブログがアフィ目的なやつだと思われている(実際そうだが)。

一番ラクだが、上で紹介した副業の中でダントツで一番金にならない。せいぜい毎月500円くらい。一度だけ例外的にはてブでバズってアクセスが跳ねたときは6000円くらい稼いだ。基本的に手間がかからないことが魅力。逆に言えばそれだけ。

なお、はてなブログはProプランになってもGoogleアドセンスの審査が通らないのでやれていない。これは、はてなブログ側の問題とのことで結局Pro契約の2万円をドブに捨てることになった。はてなさんどうにかしてくださいマジで。

副業するべきか、どの副業がいいのか

個人的な体感として、一番簡単で金にならないのがブログ、一番難しくて金になるのが技術顧問業という印象。そういう意味では、やはり副業で開発かメンタリングをやるのが無難なのかもしれない。

会社にもよるが、本業でちゃんと給料もらえているor上がる可能性があるのであれば副業なんてする必要がない。むしろ本業でバリューを発揮して給与上げるほうが長期的には得なことが多い。副業には稼ぎすぎると確定申告とかしないといけなかったり、コンテキストスイッチなどの面倒臭さがある。副業で本業へのやる気が削がれるのは最悪だと思う。

一時期は不動産投資とかも考えていたこともあったが、知れば知るほど小さな会社経営並の難易度だと感じてやめた。サブリース業者に任せたりするのも、なんだか不動産業者という情報強者の言いなりになってしまう不安がある。

izumisy.work

2020年末Rustlang環境構築

以下をやるだけ

# インストール&初期セットアップ
$ brew install rustup rust-analyzer
$ rustup-init
$ rustup update

# プロジェクト作成
$ mkdir your_own_rust_project
$ cd your_own_rust_project
$ cargo init --vcs git --bin

# rust-analyzerを使わないならrlsと関連コンポーネントをインストールする
$ rustup component add rls rust-analysis rust-src 

Rustの環境を構築するには brew install rust ではなく rustup のほうをインストールするのが正解っぽい。インストール後に rustup-init で初期化するとコンパイルに必要なrustcとかもインストールされる。

rust-analyzerはrlsに代わるRust用の言語サーバー。どうやらこっちのほうがナウいっぽい。Githubからクローンしてきてビルドしたりとか自分でバイナリ配置したりしなくてもHomebrewでインストールするのが一番はやい。

vim-lsp-settingsがrust-analyzerに対応しているので、上記ふたつをインストールし終わったら :LspInstallServer するだけでバキバキに補完を効かせてRustのコードが書ける。最高。

ElmでサードパーティーなUI系モジュールを使う際にはTEAを前提にしたインターフェースでラップする

Elm Packagesで公開されているモジュールの中にはTEAでいうところのviewやmodelしか提供されていないものがあったりする。

これがコレクション系モジュールだとか関数単位での機能提供が目的のモジュールであればまだ問題ないが、UIを提供するタイプのモジュール(日付選択のカレンダー、トースト、など)だと話が変わってくる*1

モジュールによっては「仕様上今はupdate関数を提供する必要がないだけ」の可能性もあり、バージョンアップによって独自のMsg型とそれをハンドリングするupdate関数が将来的に現れる場合がある。こうなると利用している箇所すべてにupdate関数のハンドリング処理を追加する必要がでるなど、サードパーティーのバージョンアップによる修正作業が生まれる。

このようなケースを回避するために、UI系のサードパーティー・モジュールは必ず自前のモジュールにラップすることが望ましい。まずこれだけでサードパーティーによる影響範囲を1モジュールにカプセル化できる。

さらに、ラップするモジュールはあらかじめTEAの構成要素となる関数(model, update, view)をモジュールのインターフェースとして提供しておく。こうすることで、仮にupdate関数などを提供していないサードパーティー・モジュールから、提供しているモジュールに乗り換えたりしても、ラッパー・モジュールが変更を吸収することができるため、アプリケーションの大部分は影響を受けない。

仮に現時点で部分的に移譲する実装がサードパーティー・モジュールから提供されてない状態であっても、コードコメントにプレイスホルダーであると書いておけばいい。

module MyWidget exposing (Model, Msg, update, view)

import ThirdPartyWidget

{- 
    ThirdPartyWidgetのラッパー・モジュール

    今時点の実装ではModelとviewのみ内部実装をThirdPartyへ移譲している
    今後ThirdPartyを差し替えたりバージョンアップの際にTEAなモジュールになった場合には
    update関数とMsg型の実装をサードパーティー・モジュールの実装へ移譲する
-}


type Model =
    Model ThirdPartyWidget.Model


type Msg =
    NoOp


update : Model -> Msg -> ( Model, Cmd msg)
update model _ =
  ( model, Cmd.none )


view : Model -> Html msg
view (Model value) =
    ThirdPartyWidget.view value

Elmの素晴らしい点は、どんなに複雑なUIであっても必ずTEAのパターンに帰結するという点。

つまり、TEAから外れるモジュールは原則存在しえない。だからこそ、最も柔軟なインターフェースとしてTEAを構成するモジュールの形にしておけば、最終的にどんな変更が入ったとしても理論上はラッパー・モジュール内部で変更を吸収できる。

サードパーティー・モジュールをラップするという考え方

なお、サードパーティーなモジュールをラップするという考え方は特段Elmに限った話ではない。

dry-rbシリーズ*2の作者も「常に自分でコントロール可能なインターフェースにだけ依存させろ」と言っているし、安定依存の原則を考えたらアプリケーション開発では当然の話だと言える。どこか一カ所でしか使わないならまだしも、複数個所で使うようなサードパーティーの機能は必ず自前のモジュールとしてラップしておくことが望ましい。

izumisy.work

*1:カレンダーだとかドロップダウンセレクタなどのUI系モジュールはユーザーからのクリックや入力のようなElmランタイムを経由するライフサイクルが絡むことが多いため、機能が増えるとupdate関数が出現するケースがある

*2:Rubyアプリケーションにモナドやバリデーション基盤などを導入するためのユーティリティ系gem https://dry-rb.org/

マネジメントする立場になって思うこと

今年の夏頃から会社で4-5人チームのリーダーをやっている。

新卒で入社したころはまさか自分がリーダー的存在になるなんて思っても見なかったが、やってみるとこれはこれでおもしろい。来年になる頃には自分がなんでこの選択をしたのか忘れてしまいそうな気がするので、ここに書き残しておく。

やってみようと思った理由

正直なところ、学生時代から「やっぱエンジニアはコード書いてナンボ」みたいな気持ちでいて、当初はリーダーだとかそういうものには微塵も興味がなかった。Twitterとかを見ていても「マネジメント層になるとコードが書けなくなる」みたいな話もちらほら目にしたり、そういうものを見ると尚更「そんなんやるもんじゃねーな...」と思っていた。

しかし、実際チームでマネジメントをしている人をみている限りでは、マネジメントをしているからコードが書けないだとかそんなことはないどころか、ビジネス的な視点とエンジニアリングの視点がいい具合に混ざり合って、大局的に物事が見えているようにも見えた。そのような事実を目にすると、少しづつ自分の中で「自分が目指すキャリアというのはこれなんじゃないか?」という気持ちが高まってきた。

加えて、社外の人々の話を聞くところでは、国内のベンチャー(メガベンチャー、ミドルベンチャークラス)ではどの企業においてもプロジェクトをマネジメントする層が絶対的に足りていないという話を非常によく聞いた。眉唾ではあるが、聞くところによればいまのベンチャー界隈におけるマネジメント層は年齢的に20代後半から30代前半であることが多く、この世代はサブプライムローン危機による就職の難しさやライブドア・ショックでIT企業に対する良くない印象がついた時期でもあったため、そもそもソフトウェア産業に携わる人間の絶対数が窪んでいるのではないかという話である(要出典)

なるほど、そういうことであればなおさらマネジメントをやったほうがいい。人がやりたがらないことをやることで自分の価値が上がる。そんな理由でマネジメント・キャリアに進んでみることにした。

マネジメントをするエンジニア

マネジメントをするようになったとはいえ普通にコードは書くし、いまだに機能開発のコーディング作業が1日の大半を占める。この部分は以前とあまり変わらないところだと思う。逆に、自分の中で大きく変化があったのは、自分のチーム以外の状況を今まで以上に意識するタイミングが増えたことか。

自分の開発しているプロダクトでは自分のチーム以外にもいくつかのチームが存在している。ビジネスサイドのロードマップだったり開発進捗は毎日変化するため、常に状況を鑑みて他チームのリーダーを話し合いチームリソースの配分であったりリスケジュールを行うことになる。この種の意思決定をするには、チームの開発進捗だけではなく他チームとのリソース調整を考えたり、ビジネスサイドの動きをみつつスケジュールを考え直したり、これはかつてのように1プレイヤーとしてただコードを書くだけでではあまり意識できないところだと感じた。

逆に言えば、一度こういう視点を身につけると今度はメンバとして働く上でも強い。仮に自分がリーダーではないとしても、リーダー的な視点を持って意見や提案をできる。これはやってみないと分からないところだと思う。

マネジメントのおもしろさ

今の会社だからというのもあるかもしれないが、やはりマネジメントは「自分でコントロールしている感」がすごくあり、これはめちゃくちゃにおもしろい。もちろんその分責任も大きいが、自分の意思決定がビジネスにインパクトを与えるというのはまた一味違ったおもしろさがある。

マネジメントというと様々な重圧がありそうな雰囲気もあるが、個人的にはいまが一番多くの人に頼れているようなそんな気がしている。チームのメンバ、ほかチームのリーダー、上長、などなど、1メンバとしてチームで開発をしているとき以上に自分が関わるネットワークが大きくなり繋がるノードが増えたような、そんな感じ。

組織パターン

組織パターン

BtoBなソフトウェア開発における必読書3選

BtoBなソフトウェアの開発において業務知識なしにシステム設計をすることはほぼ不可能に近い。

これまで、業務支援系のシステム開発SIer企業たちのお家芸であったが、近年ではWeb系のベンチャー企業やスタートアップであってもこれまでSIerが担っていたようなドメイン領域で戦うことが増えている。

「会計処理」「流通管理」「在庫管理」「人事管理」あたりのドメインにまつわるシステム設計は、BtoBをやっていればいつか必ず遭遇することになる。その際にドメインに関する知識を一切持たずにまともなシステム設計をすることはたいていの場合難しい。

Web系であれなんであれ、BtoBなソフトウェアエンジニアは自分たちが取り組むドメインに関する知識を持ってシステム設計に臨む必要がある。これはデザインパターンや設計手法"以前"の話だ。

ITエンジニアのための「業務知識」がわかる本

財務会計」「販売管理」「物流・在庫管理」など、様々な企業活動の業務知識を集約した一冊。これを読むだけで、ざっくり世の中の企業が日ごろから行っている業務ドメインの活動やそれに関連する法律などを知ることができる。また、各業務ドメインを扱うエンタープライズ系システムにおいてどのような機能が必要とされるか、なども例として紹介されており非常に参考になる。

どちらかといえばこの本は業務のライフサイクルやそれに絡むプロセス、用語などを網羅した本になっている。なので、この本を読んだ上で更に自分が深めたいドメインに特化した書籍に向かうのがよい。その手掛かりをこの本は十分に提供してくれる。

データモデル大全

「データモデル大全」は上の本で学ぶような業務知識をどのようにしてデータモデルに落とし込むべきかが学べる良書。特にエンタープライズ系システムが扱うドメインをテーマとして積極的にカバーしており、具体的には「受注と出荷」「サービスと契約」「在庫」などが含まれる。

ソフトウェアエンジニアである我々は、業務知識を実際のソフトウェアに落としこむにあたってデータモデリングを必ずすることになる。データベース・ミドルウェアを使わないソフトウェアはほとんどないと言っても差し支えない。とくに、在庫管理のデータモデル設計の考え方はECサイトや在庫管理システムを作ることがなくても間違いなく学びになる。

これまでのDB設計で「残り〇〇」のようなデータをDB上の1カラムにステートソーシングで表現してきた人は間違いなく設計の観点がレベルアップするだろう。

システム設計のセオリー

システム設計のセオリー --ユーザー要求を正しく実装へつなぐ

システム設計のセオリー --ユーザー要求を正しく実装へつなぐ

  • 作者:赤 俊哉
  • 発売日: 2016/02/26
  • メディア: 単行本(ソフトカバー)

「システム設計のセオリー」はいわゆる「エンタープライズ系システム」の開発における開発プロセスにおいて、それぞれのプロセスの目的や手法、注意点などを説明している。大規模なソフトウェアの開発プロセスにおいて「どのような観点で仕様を決めるべきか」「どのようなフォーマットで仕様をドキュメントに残すべきか」などを大局的に学べる一冊。*1

アジャイル開発の登場によって、特に新興のWeb系企業でウォーターフォール開発が「時代遅れ」かのように言われがちだが、それがウォーターフォール開発を無視してよい理由にはならない。

どんなソフトウェアの開発にも必ず「要求分析」「要求定義」「外部設計」「内部設計」などのフローが暗黙的に存在しており、それぞれの役割を知って初めて自分たちの開発に特化したプロセスの取捨選択ができる。深く考えずに「ドキュメントは大事じゃないから残さなくていい」「テストはなんとなく画面を触っとけばOKだろう」などの意識でいる人ほど、改めてウォーターフォールに立ち返るべきだ。

izumisy.work

*1:この本に関してはBtoBに限ったはなしではないが、いわゆる基幹系システムの開発という文脈で取り上げてみた。