キャディ株式会社のブログ - TECH PLAY

TECH PLAY

キャディ株式会社

キャディ株式会社 の技術ブログ

239

こんにちは。CADDi でバックエンドエンジニアをしている @kuwana-kb です。 この記事は CADDi Advent Calendar 12日目の記事です。昨日は、山下さんによる GitOpsの概要と実践例 〜Kustomize + CircleCI編〜 でした! 本日は「DDD のパターンを Rust で表現する ~ Entity 編 ~」と題しまして、 Rust で DDD のパターンを表現してみたいと思います。 目次 [toc] はじめに DDDとは、 Domain-Driven Design(ドメイン駆動設計)の略です。アプリケーションの扱う業務領域に焦点をあてた設計手法であり、エリック・エヴァンスが提唱しました。詳細については、以下の記事で紹介していますので、そちらをご覧ください。 DDDのパターンをRustで表現する ~ Value Object編 ~ また、今回の記事を書く上で「ドメイン駆動設計入門」(著: 成瀬 允宣氏)という書籍を参考にさせていただきました。書籍のサンプルコードは C# でして、本記事ではそのコードを参考に Rust で DDD のパターンを表現しています。 なお、本記事に登場するコードは kuwana-kb/ddd-in-rust に格納しています。 DDD における Entity とは まず、Entity について整理しましょう。 Entity とは、一意なものを表現する概念です。人(Human)で例えてみましょう。 ここに二人の人がいます。 二人の名前はどちらも「山田太郎」さんです。では、この二人は全くの同一人物といえるでしょうか?現実世界では、同姓同名がありえます。つまり、二人は同じ名前でありながら、異なる一意な存在です。コード的な表現でいえば name という属性は同じでも、二人を区別できる必要が有ります。 これは、Entity は属性が同じであっても区別されることを示します。また、二人を区別するための識別子が必要となることも示しています。 また、人は時を経て状態が変わります。例えば、身長が伸びたり、体重が増えたりしますね。これは、ライフサイクルを通じて height や weight といった属性が変化することと同義です。つまり、Entity は可変であるといえるでしょう。 属性として登場した name, height, weight は、属性単位で見たときに値が同じであれば同一とみなせます。これらは Value Object として定義できるでしょう。一方で、Human はその構成要素である name, height, weight がすべて同一だとしても、同じ存在とは限りません。そして、ライフサイクルによって変化することがある。 これが Entity です。 実装パターンの紹介 まずはシンプルに実装してみる ここでは、 EC サイトのユーザーを例にとってみます。ユーザーの仕様は以下とします。 ユーザーには名前を設定できる 名前は 3 文字以上である必要がある Rust の型で表現すると以下のような実装になるでしょう。 use common::MyError; // User // 現時点では、可変性と同一性を持たない。 #[derive(Clone, Debug)] pub struct User { name: String, } impl User { // Userのコンストラクタ pub fn new(name: &str) -> Result { if name.chars().count() とてもシンプルですね。 User 型は属性として name を持っています。また、コンストラクタとして new() 関数を持っています。 しかし、現在の実装だと後から名前を変えたくても変える方法がありません。 可変性を与える 次に User 型に対して、 Entity のもつ特性である可変性を与えます。具体的には、 User.name 属性を変更できるようにしてみましょう。 use common::MyError; // User pub struct User { name: String, } impl User { // User のコンストラクタ pub fn new(name: &str) -> Result { let mut user = Self { name: Default::default(), }; user.change_name(name)?; Ok(user) } // ふるまいを通じて属性を変更する // 変更ロジックはメソッド内に閉じ込めている pub fn change_name(&mut self, name: &str) -> Result { if name.chars().count() User 型に対して名前を変更できる change_name() メソッドを追加しました。これで名前を変更できるようになりましたね。 さて、 change_name() メソッドには、冒頭に述べた「名前は 3 文字以上である必要がある」という仕様が含まれています。この User.name の制約は名前変更時だけでなく、 User を作成するときにも満たす必要が有ります。したがって、 new() 関数の処理は、 User インスタンスを生成してから change_name() メソッドを通じて引数である name を注入しています。なお、 change_name() はバリデーションによって失敗する可能性がある(= Result 型を返す)ため、 new() の返り値も Result になっています。 この実装でも仕様は満たせているのですが、 名前の制約を満たすために毎回 change_name() を呼ぶのは面倒です。また、 new() の処理で user.change_name(name)? のような形で制約を満たすようにしていますが、フィールドが追加される度に user.change_***() といったメソッド呼び出しを追加する必要がでてきそうな点も気になります。 そこで、名前の持つ制約を型に閉じ込めてしまいましょう。具体的には、 Name 型を定義して「名前は 3 文字以上である必要がある」という仕様は Name 型が持つ形にします。 use common::MyError; // User pub struct User { // name 属性は、Name 型を持つ形に変更 name: Name, } impl User { // input の型も String 型 から Name 型に変更 pub fn new(name: Name) -> Self { Self { name: name } } // バリデーションのロジックは Name 型に移譲している pub fn change_name(&mut self, name: Name) { self.name = name; } } // ユーザー名 // // 制約として、3文字以上である必要がある // Name 型を新たに Value Object として定義した pub struct Name(String); impl Name { pub fn new(s: &str) -> Result { if s.chars().count() Name 型を新たに定義しました。 「名前は 3 文字以上である必要がある」という仕様は、 Name::new() に移譲されています。 Name 型のインスタンスが生成できた時点で、上記の仕様を満たした状態であるということが保証されます。 Name 型を定義したことで、 User 型にも変更を加えています。これまで User.name 属性や引数の型は String 型でしたが、 Name 型になっていますね。 Name 型のインスタンスを生成するタイミングでバリデーションが入るため、 User::new() と change_name() の返り値の型から Result 型 が消えています。 さて、今回はドメイン上の仕様をメソッド( change_name() )ではなく、型( Name 型)に移譲してみました。 // name は String 型 // どのような値が入っているかはパット見ではわからない pub struct User { name: String, } // name は Name 型 // Name 型特有の値であることがひと目でわかる // 型の定義に飛べばその制約も知ることができる pub struct User { name: Name, } 私としては、型で表現した方がオブジェクトの持つ特性がより伝わりやすいと感じます。 同一性を与える 次は、 User 型に対して Entity の特性である同一性を与えたいと思います。名前が一緒だからといって、同じユーザーとは限りません(少なくとも今回の例においては)。したがって、 User.name 属性が同じだったとしても、別々の User であると識別できるようにしたいと思います。 use derive_getters::Getters; use common::MyError; /// Userモデルに対して可変性と同一性を与えた #[derive(Clone, Debug, Getters)] pub struct User { id: UserId, name: Name, } impl User { pub fn new(id: UserId, name: Name) -> Self { Self { id, name } } // nameフィールドは可変性を持つ pub fn change_username(&mut self, name: Name) { self.name = name; } } さて、今回は User に対して id 属性を追加しました。 User.id 属性の型は UserId 型です。 簡単ですが、 UserId 型は以下のような文字列を受け取る型とします。 /// ユーザーID #[derive(Clone, Debug, PartialEq, Eq)] pub struct UserId(String); impl UserId { pub fn new(s: &str) -> Self { Self(s.to_string()) } } 次に User 型の同一性をふるまいとして実装します。同値関係をあらわすには、 Eq , PartialEq という Trait を利用します。コード的な表現でいうと x user_a のような形で、同一かどうか検証できるようになります。この時、 User が同一であるかは User.id のみを比較対象とします。 User.name は比較対象にしません。 // trait PartialEq は半同値関係をの性質を表す impl PartialEq for User { fn eq(&self, other: &Self) -> bool { // 今回の実装は、idの値が同一か検証するようにしている self.id == other.id } } // trait Eq は同値関係の性質を表す impl Eq for User {} これで、 User に対して同一性の特性を与えることができました。実装だけだとわかりづらいと思うので、簡単な使用例をみてましょう。 /// 名前 #[test] fn test_user_eq() { // User インスタンスを生成する let user_before = User::new(UserId::new("DummyId1"), Name::new("Hoge").unwrap()); // User インスタンスをコピーし, before と after で2つにする let mut user_after = user_before.clone(); // after のインスタンスの名前を変更する user_after.change_username(Name::new("Fuga").unwrap()); // User が同一であるかを検証する // User の名前を変更しても同一性は同じままである assert_eq!(user_before, user_after); // Ok } 名前を変更する前と後で User が同一であるかを検証しました。User は可変性と同一性の性質をもったオブジェクトであり、 Entity の性質を満たしています。 まとめ 今回は、Rust で Entity をどのように表現できるか、についてご紹介しました。みなさんに Rust の魅力が少しでも伝われば幸いです。 CADDiでは「モノづくり産業のポテンシャルを解放する」ために仲間を探しています。 実現したい世界に向け、作らなければならないもの、改善したいことが無限にあります。 少しでも興味を持って頂けましたら、リニューアルされたばかりの 採用サイト をご覧ください。 また、カジュアル面談も行っていますので、実際にエンジニアに会ってみたい方は こちら から、 どうぞ宜しくお願いいたします。
こんにちは。テクノロジー本部バックエンド開発グループの山下です。 この記事は キャディ Advent Calendar 2020 の11日目です。 前日は大原さんの 図面を管理するために図面版 figma を開発している話 について でした。 今回は 以前のKustomizeの記事 に続き、 「GitOps概要と実践例 〜Kustomize + CircleCI 編〜」と題して、 GitOpsの概要を説明した上で、KustomizeとCirlceCIを利用したk8s上でのGitOpsの実践例について 書いていこうと思います。 [toc] GitOpsとは 概要 WeaveWorksの Guide To GitOps より引用 GitOpsはきちんと説明するだけでもかなりの分量になるので、かなり思い切って要約すると 「Kubernetesクラスターの構成をコード化してGitで管理し、その情報だけを正として環境を展開するもの」です。 え、それもうやってるよ? Terraformでk8sクラスターなどインフラ構築して、イメージをkubectlとかでデプロイしているよ? という方も多いかと思います。 ただ、ここで重要なのは、コード化されたもの 「だけ」 を正とする、ということです。 つまり、コンテナイメージなど動的に生成されうるものなども含めて全てを静的に管理する、ということです。 この前提で継続的デリバリーを構築しようとすると面倒な事実に気付きます。 例えば、リリース対象のアプリケーションをビルドすると当たり前ですがイメージが新しく作られます。 ですが、そのイメージをそのままクラスターにデプロイすると、コードだけを正とするGitOpsの概念から外れます。 何故なら、そのイメージを一意に特定する情報がインフラ側のコードにはないため、同じ状態を再現できないためです。 正しくGitOpsを行おうとすると、コンテナをプッシュするのではなく、 何らかの手段で、ビルドしたイメージの情報をインフラコードに反映させなければなりません。 そうすると、反映されたインフラコードに合わせて実際の環境が収束していきます。 こうすることで、常に定義した内容と現実が一致するようになるのです。 GitOpsのメリット ここまで聞くと、そんな面倒なことしなくても、ビルドしたイメージをデプロイしちゃえば良いじゃない、と思われるかと思います。 ですが、GitOpsにはいくつかのメリットがあり、この面倒なことをする理由になります。 そのメリットの中で、キャディで採用した際に重視した点は大きく三つです。 信頼性と復元力 一つ目が、信頼性と復元力、つまり 必ずある時点での状態に戻すことが出来る ことです。 GitOpsでは必ずコードで定義されている状態に収束します。 つまりアプリケーションのイメージをタグなどを用いて定義してあれば、 必ずクラスターで展開されるイメージ全てが、その時々で一意に決まります。 加えて、キャディでは、Helmなどを利用して各種ミドルウェアの設定などを行っていますが、 その時のバージョンや設定なども合わせて管理されているため、 リリースした当時、バグが発生した当時の状況を確実に再現することができます。 (DBなどState部分に関しては出来ませんが) これによって、バグ発生当時の状況の再現やロールバックなどの動作が容易になります。 関心の分離による生産性の向上 二つ目が、 CIとCDの分離による関心の分離と、それに伴う生産性の向上 です。 キャディでは稼働中のプロダクトが4つ存在しますが、 内部的には、さらに細かいサービスに別れて存在しています。 このサービス毎に、どうやってデプロイしよう、と考えたり 実際にCDの処理を書いたりするのは非常に面倒ですし、 デプロイ先のクラスターのことを理解していないといけないのは開発者にとって負担が大きいです。 しかし、GitOpsでは、イメージをビルドさえ出来れば、 その情報(タグなど)をインフラ側に反映させるだけでよく、 その書き換え処理の共通化も容易です。 (キャディではOrbを使ってCD処理などを共通化しています。以前書いたOrbの記事は こちら です) そのためGitOpsでは、 アプリケーション開発チームはコードを書いてイメージが出来上がるまでに集中すればよく、 後のインフラ構成やインフラエンジニアの稼働状況などを気にせず、 開発を進めることが出来るようになりDX(開発者体験)が高まります。 もし、これが従来のE2Eのパイプラインによるデプロイ形式だと、 実際にアプリケーション側のCI/CDパイプラインでデプロイが完了するまで気にしなくてはいけませんが、 GitOpsであればイメージのタグが書き換わりさえすれば、そのあとはデプロイが何らかの理由で失敗しても 既にコードは変更してあるので、再度デプロイをしたり原因調査をインフラ担当者が行えばよくなります。 これはアプリケーション開発だとPubSubモデルに近く、 実際に処理が完了するまでをPublisher側が気にしなくてもよくなるのに近いです。 高い効率性とセキュリティを保つ運用体制 最後に三つ目が、 高い効率性とセキュリティを保つ運用体制が構築できること です。 これはGitOpsでは、全ての変更やリリースがGitで管理され、その変更に応じて環境が変わるので、 誰が、何を変更したことによって、このリリースを行われたかを確実に分かるためです。 これによって、問題の発覚から原因の特定・対策までをスムーズに行うことが出来ますし、 許可されていないユーザーからの変更も防ぐことが出来ます。 更に、CIとCDを分離したことで、CI側にクラスターを変更する権限を渡すことなくリリースが行えるため、 セキュリティ的にも安全性が高まります。 GitOpsをより詳しく知るには 以上、大胆にGitOpsの定義を削って簡単に説明しましたが、 GitOpsには他にも様々な要素があり学びも大きいです。 詳細を知りたい方は、是非、この考えを提唱したWeaveWorksの 公式資料 をご覧ください。 キャディでの実践例 前置きが長くなりましたが、ここから実際にGitOpsを実現する方法を具体的に書いていきます。 前置きで色々書いていたり、公式資料にも小難しいこと書いているな、と思われるかもしれませんが、 GitOpsを実現する際に考えることは実は大きく三つだけです。 どのような形式でイメージの情報を管理するか(≒構成管理) どのタイミングでイメージの情報を書き換えるか(≒ブランチ戦略) どうやってイメージの情報を書き換えるか(≒CDの実装) どのような形式でイメージの情報を管理するか ここでいうイメージの情報とはImageのtagやdigestなどで、 それをどう保存して管理するか、つまり構成管理の方法を最初に決める必要があります。 キャディではKustomizeを使って構成管理を行っています。 詳しくは こちらの記事 で書いているので詳細は省略しますが、 基本はk8sのPodを定義するbaseのファイルと具体的なタグや設定値などが書いてあるファイルでoverlay つまり、上書きする、という方式を使っています。 具体的なサンプルで説明すると baseのファイル(path: base/services/**/deployment.yaml)が下記だとすると apiVersion: apps/v1 kind: Deployment metadata: name: sample-app spec: replicas: 1 selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: containers: - name: sample-app image: sample-app-image overlayするファイル(path: overlays/${env}/kutomization.yaml)が下記になります。 apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../base images: - name: sample-app newName: gcr.io/bucket-name/image-name newTag: 91c8cee4b05a0ab1642d63198969fac9df5f62ae この二つを元にkustomize buildすると下記のようなmanifestが生成されます。 apiVersion: apps/v1 kind: Deployment metadata: name: sample-app spec: replicas: 1 selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: containers: - name: sample-app image: gcr.io/bucket-name/image-name:91c8cee4b05a0ab1642d63198969fac9df5f62ae つまり、上記のbaseファイルでImage名だけで指定していたものを overlay側のkustomization.yamlでtagを指定して上書きすることで、 その時点でのイメージ情報を確定できます。 overlayのファイルは各環境毎に設定するので環境毎に設定することができます。 (詳細なディレクトリー構成は こちら ) どのタイミングでイメージの情報を書き換えるか CIを定義している各サービスのレポジトリーから インフラのレポジトリーで定義しているイメージ情報を変更する訳ですが、 それをどのタイミングにやるのか、を決める必要があります。 これはイコール、ブランチ戦略を決めることでもあります。 よく使われているパターンだとGitHubフローとGitフローがありますが、 どちらの場合でも下記の二つのアクションをどのブランチやタグに結びつけるか、 が変わるだけになります。 いつイメージをビルドするか いつ、そのイメージ情報をインフラコード側に反映するか 対応表 この結びつきをGitHubフローのバージョンで対応表にすると下記のようになります。 Appブランチ イメージビルド インフラコード変更 デプロイされる環境 feature/* ビルドなし 変更なし なし master ビルド developブランチを変更 Dev環境 masterブランチでtag打ち(vx.y.z) ビルドなし masterブランチを変更 Stg環境 深掘って説明すると、 最初の、featureブランチでは開発しているだけなので特にインフラの変更がないです。 次に、masterブランチでは、実際にbuildが走り、インフラのdevelopが変更されDev環境にデプロイされます。 最後は、GitHubのリリース機能を利用して、masterブランチにtagを打つことで処理が走ります。 tagが打たれたタイミングで、インフラのmasterブランチが変更され、Stg環境にデプロイされます。 イメージビルドなし、が気になるかと思いますが、masterブランチでのbuild時点でイメージタグをcommit hashにしているため、tagを打たれたとしても、あくまで、その時点のhash値を使ってイメージを指定すれば良いのでビルドの必要がないのです。 どうやってイメージの情報を書き換えるか 上記でいつ、どのように保存するかが決まりました。 最後にどのように書き換えるのか、ということですが、 こちらもKustomizeの機能を使って書き換えます。 具体的に利用するのは、KustomizeのEdit機能です。 ( 以前の記事の参考箇所 ) この機能で各アプリケーションサービスのCI後に、 インフラコードにある overlays/${env}/kustomization.yamlを対象の環境分だけ書き換えます (対象の環境は、developブランチであればdevだけ、masterブランチであれば、stgとprod) コマンドとしては下記のようになります。 $ kustomize edit set image image_name:tag_name ただ、このコマンドだけだとイメージがつきにくいと思うので、 キャディでこの書き換え処理を共通化しているOrbの設定を一部抜粋・変更して掲載すると description: kustomization.yamlのimageのtagを変更する処理です parameters: fingerprint: type: string gcr_host_name: type: string default: gcr.io github_group: type: string default: caddijp infra_repo_name: type: string project_name: type: string default: ${CIRCLE_PROJECT_REPONAME} description: the name of the project (usually the same as the repo and image name) steps: - add_ssh_keys: fingerprints: - << parameters.fingerprint >> - run: name: Avoid hosts unknown for github command: echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config - run: name: git clone infra code in specific branch decided by CIRCLE_BRANCH of CircleCI environment variables command: | git clone -b ${INFRA_BRANCH} git@github.com:<< parameters.github_group >>/<< parameters.infra_repo_name >>.git - run: name: rewrite image tag for release command: | if [[ ${INFRA_BRANCH} = develop ]]; then TARGET_ENVS=("dev") IMAGE_TAG="dev-${CIRCLE_SHA1}" elif [[ ${INFRA_BRANCH} = master ]]; then TARGET_ENVS=("stg" "prod") IMAGE_TAG=${CIRCLE_SHA1} else echo "This step failed because branch name is not suitable" echo "We have to set branch name from master, feature/*" exit 1 fi for ENV in ${TARGET_ENVS[@]} do cd ~/infra/<< parameters.infra_repo_name >>/k8s/overlays/${ENV} kustomize edit set image << parameters.project_name >>=<< parameters.gcr_host_name >>/${GOOGLE_PROJECT_ID}/<< parameters.project_name >>:${IMAGE_TAG} done 下記のStep毎に順を追って説明します。 1. SSH keyの追加 2. Hostの追加 3. インフラコードのClone 4. 実際の書き換え処理 SSH Keyの追加 アプリケーション側のCIコンテナ上からインフラコードをCloneする際に必要です。 Hostの追加 CircleCIで対象のレポジトリー以外のレポジトリーからコードを取得する際に必要な設定になります。 インフラコードのClone こちらは、書き換えたいインフラレポジトリーのブランチを ブランチ戦略に合わせて選択してCloneします。 実際の書き換え処理 キャディではインフラレポジトリーは release, master, develop, feature/* でブランチを運用しています。 そのブランチのコードを先ほどのブランチ戦略に対応表に合わせて変更しているのがこの部分になります。 変数の ${INFRA_BRANCH} が対象となるブランチですが、これをブランチ戦略に合わせて処理しているOrbのコマンドを一部変更・抜粋したものが下記になります。 description: ビルド対象のブランチを元に対象となるinfraコードのブランチを設定します steps: - run: name: Set infra branch name with CIRCLE_BRANCH in CircleCI environment variables command: | if [[ ${CIRCLE_TAG} =~ ^v ]]; then INFRA_BRANCH="master" elif [[ ${CIRCLE_BRANCH} = master ]]; then INFRA_BRANCH="develop" elif [[ ${CIRCLE_BRANCH} =~ ^(feature)/ ]]; then echo "In build of feature branch, this step is skipped" exit 0 else echo "This step failed because branch name is not suitable" echo "We have to set branch name from master and feature/*" exit 1 fi echo "${INFRA_BRANCH}" echo "export INFRA_BRANCH=${INFRA_BRANCH}" >> $BASH_ENV ブランチが決まれば、あとは対象のkustomizaton.yamlを書き換えるだけです。 developブランチであれば、 overlays/dev/kustomization.yaml を masterブランチであれば、 overlays/stg/kustomization.yaml と overlays/prod/kustomization.yaml を書き換えます。 stgとprodを同じタイミングで書き換えるのは、stgからprodへの展開する際の手順を出来るだけ最小限にするためです。 (releaseブランチにmasterブランチをマージするだけで本番にリリース出来るようにすることで誤操作などが入らないようにしている) 応用編: GitOpsのPush型とPull型 以上で、キャディで実践しているGitOpsの実践例の紹介は終了となりますが、 ここまでで、GitOpsを理解されている方だと、何か違和感があるな、と思われる方もいらっしゃるかもしれません。 それは、実は、イメージ情報を変更する際や、インフラコードの変更をクラスターに反映する際の方法がPush型になっているからです。 最初のメリットのところで、GitOpsがPubSubのようなものだと書きましたが、 PubSubにPush型とPull型があるように、実はGitOpsにもPush型とPull型があります。 そして、PubSubがそうであるように、GitOpsに関しても同様にPull型の方が理想的だと言われています。 具体的には、ImageRegistryの変更を検知して、インフラコードをConfig Updaterが変更、 (現在ですとCI側でPushして変更していますが、PubSubでいうSubscriberが書き換える方がより適切に分離されることになる) その変更内容をk8s側でwatchしておいて(pubsub的で例えるならsubscribeして)、 インフラコードに合わせて実態を収束させていく、というのが理想的な形になります。 WeaveWorksの Guide To GitOps より引用 ただ、PubSubでPull型で実装するのに、少し手間がかかるのと同様に、GitOpsでもPull型で実装するのは更に追加の対応が必要になります。 そのため、どれぐらいの工数を自分達のチームはかけることが出来るかに応じて、方法を選択してみるのも良いかもしれません。 Pull型で実装する場合は、一から実装するのは工数がかかりすぎるので、 GitOps系のCDツールとして最有力で開発が活発に続いている ArgoCD や GitOpsを最初に提唱したWeaveWorksが開発している Flux(ver2) などを試してみるのも良いかもしれません。 (因みに弊社の飯迫さんがArgo RolloutsでBlueGreenの実装を行った話を 記事 にされていますので、ご興味のある方はどうぞご覧ください) 最後に 長文になりましたが、KustomizeとCircleCI Orbのみで シンプルにGitOpsの考えを実現する方法をご紹介してみました。 応用編でも少し書きましたが、GitOpsはその考えが広まる中で、 それに関連する様々なツールやサービスが出ていますが、 間違いなく、これがデファクトスタンダードだ!と言えるツールやサービスがあるわけではありません。 その中で、今回の方法ですと、依存するものを最小限にしながらGitOpsの考えを取り入れることができ、結果的に学習コストも下がります。 加えて、最後に取り上げたArgoCDも Kustomize対応 をしていますし、 Fluxも、 Kustomizeを使ったサンプル もあったりと、 今後、次の一手を打つ際にも柔軟さを保てるのもメリットです。 GitOpsの考えは良いけど、実際に適応するのをどうすれば良いんだ!?と悩んでいた昔の私に、 こういう理由でこういう選択をしたけど、どう?っという情報があったら、 もっと効果的な判断が出来たのではないかと思い、こちらの記事を書いてみました。 これからGitOpsを実現しよう、という方のお力になれましたら幸いです。 CADDiでは「モノづくり産業のポテンシャルを解放する」ために仲間を探しています。 実現したい世界に向け、作らなければならないもの、改善したいことが無限にあります。 少しでも興味を持って頂けましたら、リニューアルされたばかりの 採用サイト をご覧ください。 また、カジュアル面談も行っていますので、実際にエンジニアに会ってみたい方は こちら から、 どうぞ宜しくお願いいたします。
こんにちは。キャディでソフトウェアエンジニアをしている @sottar です。 この記事は キャディ Advent Calendar 2020 の10日目です。前日は @catupper による async/awaitで躓いて学んだ、「オレは雰囲気でRustをしている!」からの脱し方。 でした。 今日は私がチームで開発してる、図面を管理するためのツールについて紹介します。 図面とは そもそも図面とはなんでしょうか? 作りたい製品の素材やサイズといった仕様が書かれたものだということはみなさんご存知かと思います。 その図面を管理するためのアプリケーションというと一見簡単そうに思えるかもしれませんが、実際に弊社で行っているオペレーションに即して管理を行おうとすると、いくつか考慮しないといけない点があります。 取引先からpdfファイルとしてメールで送られてくることが多い 図面には id のような一意に決まるものがない(図番や品番などはあるが業界として統一されているわけではない) 手書きで文字が書かれていたり、スキャンされたPDFであることがある(テキストを機械的に読み取るのが困難) 途中で仕様(素材やサイズ)が変わることがある 電話でその仕様変更が伝えられることもある 社内では見積もりを行う際に図面に書かれている内容を社内の見積もりシステムに入力する必要があったり、サイズや素材などの変更があった場合にその変更を管理し社内メンバーに周知する必要などがあります。 よりスケーラブルな組織をつくるためにはこのオペレーションをできる限り自動化したいところですが、そのためにはこれらの課題を解決する必要があります。 弊社ではこれらを解決するアプリケーションの開発も行っており、今回はその一部として Figma のようなUIを持つアプリケーション(以下、図面版 Figma)の紹介をします。 図面版 Figma とは 図面版 Figma を開発するに当たっての解決したい課題は主にこの二つです。 1. 図面の拡大/縮小をスムーズに行える 2. 仕様変更があった際にそのアプリ上でやりとり/管理することで窓口を統一することができる ただ単に図面をwebで表示するだけではなく、文字が小さかったり手書きで書かれた文字もあるため拡大をスムーズに行う機能や取引先とのやりとりの窓口を一元化するためにチャットの機能を盛り込みます。 検証としてフロントエンドのみ実装して netlify にあげています。 今回はその検証で使用した GitHub のリポジトリ での実装を参考に進めていきます。 技術 stack 今回は主に以下の技術を使って実装しました。 TypeScript React styled-components PixiJS / react-pixi 状態管理, view のライブラリとして React と styled-components そして図面を表示して拡大縮小を滑らかに行うために WebGL のラッパーである PixiJS とそれを React から使いやすくした react-pixi を使用しました。 PixiJS PixiJS とは 2D のグラフィックス処理を実現するための JavaScrip ライブラリです。 似たような 2D グラフィクス向けのライブラリとしては konvajs や EaselJS などがありますが、それらのライブラリとは異なり WebGL を使ってレンダリングをおこなう点が特徴です。 また、同じく WebGL を利用しているライブラリとして three.js がありますが、three.js は 3D のグラフィック表現を得意としておりそれぞれ得意な領域が異なります。 今回はよりWebGLを使って滑らかに拡大縮小を実現するためにこの PixiJS を使用しました。 セットアップ 必要な npm のインストールを行なっていきます。 (lint や prettier, webpack などは割愛します。詳しくはリポジトリの package.json を参考にしてください) $ npm i --save @inlet/react-pixi pixi.js react react-dom styled-components $ npm i --save-dev @types/react @types/react-dom @types/styled-components typescript App.tsx (簡略化のためスタイルなどのコードは省略しています) アプリケーションのルートとなる App.tsx では PixiJS をマウントするためコンポーネントと、メッセージのやりとりをするコンポーネントをマウントします。 // App.tsx const App = () => { ... return ( <Wrapper> <Pixi /> <Chat /> </Wrapper> ) ... } 図面表示エリアの実装 App コンポーネントからマウントされている Pixi コンポーネントで PixiJS のコードを書いていきます。 PixiJS の基本的な使い方はほかのグラフィックライブラリと似ていて、 Stage をつくりその中に表示するオブジェクト( Container , Sprite など)を記述します。 Container Container は子供の要素を保持する汎用的なオブジェクトで、Graphic や Sprite など、他のオブジェクトのコンテナとして機能するすべてのディスプレイオブジェクトの基底クラス Sprite 画面にレンダリングされるすべてのテクスチャオブジェクトのベースのオブジェクト Stage オブジェクトではグラフィックを表示させるための root の表示域を設定します。 <Stage width={canvasWidth} height={canvasHeight} id="canvas"> ... </Stage> canvasWidth, canvasHeight はそれぞれ windowSize から取得して設定します。 そして Container を定義し、その中に図面を表示させるため Sprite オブジェクトを展開します。 Sprite オブジェクトでは表示する image を指定する他に、画像のサイズや拡大・縮小の割合 scale 、表示する位置 x , y などを設定することができます。 そのため、表示している画像の拡大縮小や位置を変更するにはこの scale や x , y を React の state で管理し、ユーザーの入力に応じて値を変更すれば良さそうです。 その図面の表示に関する state をそれぞれPixi.tsx 内に定義します。 // Pixi.tsx const [scale, setScale] = useState(0); const [position, setPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); そしてその値を Sprite に設定します // Pixi.tsx <Container> <Sprite image="./images/sample.jpg" anchor={0.5} x={position.x + canvasWidth * anchor} y={position.y + canvasHeight * anchor} scale={scale} interactive={true} /> ... </Sprite> </Container> anchor は設定している Sprite の中心の値を設定します。ここでは 0.5 に設定しており WebGL の表示エリアのちょうど中心に設定しています。 x , y もこの anchor と state で管理している x , y の値で設定をします。 拡大縮小 ここまでで図面を表示するための実装ができました。次にスクロールやピンチイン/アウトに応じて図面を拡大縮小させるための関数を書いていきます。 スクロールやピンチイン/アウトにを行うためのイベントとして onWheel という JavaScript のイベントがあります。 このイベントを Pixi.tsx 内で useEffect を使ってイベントの登録を行います。 // Pixi.tsx const wheelHandler = (e: WheelEvent) => { e.preventDefault(); if (!Number.isInteger(e.deltaY)) { setScale(currentState => Math.min(Math.max(0.05, currentState + e.deltaY * -0.001), 1)); return; } setPosition(currentState => ({ x: currentState.x - e.deltaX, y: currentState.y - e.deltaY })); }; useEffect(() => { const el = document.getElementsByTagName('canvas')[0]; el.onwheel = e => { wheelHandler(e); }; return () => { el.onwheel = null; }; }, []); !Number.isInteger(e.deltaY) ここでは現在行われているスクロールが小数点を含んでいるか(整数か)どうかを見ています。 スクロールイベントでは単純な x and/or y 軸方向のスクロールでは deltaY の値は整数になり、ピンチイン/アウトでは小数付きの数字になります。 単純なスクロールの場合は表示している図面の位置を変更し、ピンチイン/アウトでは図面の倍率を変更したいためこの deltaY の値で条件分岐を行い、それぞれのケースで state の値を更新しています。 ここまでで図面の表示とスクロールによる図面の拡大/縮小機能の実装を行なってきました。次にチャット機能を実装していきます。 チャット機能 チャット機能は Figma と同じように図面の上にピンを立てるのと、右カラムに入力されたチャットを表示します。そのため、 App.tsx に必要な state を定義します。 // App.tsx const [chatList, setChatList] = useState< { messages: { id: string; author: string; createdAt: string; message: string }[]; inputValue: string; pin: { xRatio: number; yRatio: number }; // 0 ~ 1 }[] >([]); const [activeChatIndex, setActiveChatIndex] = useState(0); 入力されたチャットと入力された場所を保持するための state と active になっているチャットを保持するための state を定義し、それぞれ子コンポーネントに渡します。 右カラムに表示するチャット欄はいわゆる普通の React アプリなため説明は省略します。 Pixi.tsx では App.tsx から受け取った chatList state の pin の位置を展開します。 // Pixi.tsx <Container> ... {props.pins.map((p, i) => { const positionX = originalImageSize.x * scale * p.xRatio + (canvasWidth - originalImageSize.x * scale) / 2 + position.x; const positionY = originalImageSize.y * scale * p.yRatio + (canvasHeight - originalImageSize.y * scale) / 2 + position.y; return ( <React.Fragment key={`${p.xRatio} ${p.yRatio}`}> <Sprite image="./images/pin.svg" anchor={anchor} x={positionX} y={positionY - 10} scale={0.23} click={() => props.clickPinHandler(i)} interactive={true} cursor="pointer" /> <Text text={String(i + 1)} x={i < 9 ? positionX - 2 : positionX - 5} y={positionY - 18} scale={0.3} style={numberStyle} /> </React.Fragment> ); })} </Container> pinの位置は、拡大/縮小に対応するために絶対値ではなく比率で保存し、それを展開しています。 Pinの画像は図面と同様に Sprite コンポーネントで表示し、ピンの上に表示する番号は Text コンポーネントを使って表示します。 また図面をクリックされた際に新しくピンを表示するために、図面を表示している Sprite に pointerdown 属性を追加し、ピンを追加する関数を実装します。 ここで画像のサイズからピンを表示する位置(比率)を取得し、 App.tsx の state の値を更新しています。 // Pixi.tsx <Sprite image="./images/sample.jpg" anchor={anchor} x={position.x + canvasWidth * anchor} y={position.y + canvasHeight * anchor} scale={scale} interactive={true} pointerdown={(e: PIXI.InteractionEvent) => { const imageSize = { x: originalImageSize.x * scale, y: originalImageSize.y * scale }; const xRatio = (e.data.global.x - ((canvasWidth - imageSize.x) / 2 + position.x)) / imageSize.x; const yRatio = (e.data.global.y - ((canvasHeight - imageSize.y) / 2 + position.y)) / imageSize.y; props.addPin({ xRatio, yRatio, }); }} /> まとめ ここまでwebアプリケーションに WebGL を用いて画像を表示し、クリックされた箇所でピンを表示してチャットを行えるアプリケーションの開発を行ってきました。 ピンをクリックした時にフォーカスを合わせる実装など今回の記事では一部省略した部分もありますが、全体のコードは こちらのリポジトリ にあるので是非参考にしてみてください。 弊社ではこの図面管理のアプリケーション以外にも上記に挙げた課題を解決するために、送られてきた図面の内容を解析するためのアプリなど製造業の課題を解決するためのアプリケーションの開発を行っています。 少しでも興味ある方は是非一度お話ししましょう! カジュアル面談のお申込み
こんにちは。テクノロジー本部バックエンド開発グループの小倉です。 この記事は キャディ Advent Calendar 2020 の9日目です。前日は山田さんの protobuf v3 の optional について でした。 Rustはイカしたエコシステムが充実していて、型が厳しい言語の割にはコンパイラやリンターに従うだけで動くコードが書けたりします。その結果「オレは雰囲気でRustをしている!」という気持ちになったりするんですよね〜。 本記事では、巷にあふれる入門記事を読んで実務に挑み、その結果でてきたよくわからない点を調べた結果得た知識を紹介します。 巷にあふれているRustのasync/awaitの記事。 これらを読みました Rustの非同期プログラミングをマスターする 2019 年の非同期 Rust の動向調査 Rustのasync/awaitとスケジューラの話 / rust-async-await Rustのasync/awaitをスムーズに使うためのテクニック DIFFERENT LEVELS OF ASYNC IN RUST Async Book 読んだ感想 async関数は「あとでやっておきますよオブジェクト」を返すもの。 「あとでやっておきますよオブジェクト」は正しくは ステートマシン(State Machine) という 厳密には「 あとでやっておく」+「言われたら出来るところまで進めておく」。 すなわち「まだ途中で今ここ」「もう終わった」などのステート(状態)を持つマシン(計算機) 残りの処理の内容も含めてステートマシンが持っているので、途中の状態のステートマシンをスレッド間で渡し合うことで、柔軟に並列処理できる(便利!) RustではFutureオブジェクトがステートマシンの実装の一例。かつてFutureはデファクトなクレートだったが、Rust1.39で標準ライブラリに入った。 Future::poll を呼ぶことが「出来るところまで進めるように言う」に相当する。 ステートマシンとは別に「ステートマシン走らせる人」がいて初めて非同期処理が出来る。 「ステートマシン走らせる人」を ランタイム(Runtime) と呼ぶ Rust文脈だとFuture::pollをうまく呼ぶ人 ランタイムはステートマシンの様子を見ながら適宜「出来るところまでやるように言う」。 Runtimeの例:tokio 他にも色々 https://qiita.com/benki/items/0792444cd6c2a162fb56 block_on(非同期タスク)ってやればとりあえず実行できる、、、? 非同期関数を作るときは、awaitのタイミングを意識しないと非同期処理の恩恵が受けられない awaitを付けるとFutureが終わるまで待たせる awaitを付ける行為はステートマシンの「途中まで」の「途中」を増やすだけ。それだけでは並列処理にはならない。 昔作ったfutureをあとでawaitすると並列化した気になるけれど、実はawaitのタイミングまでfutureの中身の処理は走らない。 並列化したかったらspawnして独立したタスクとして切り出すか、joinやselectなど「複数のFutureを渡してよしなに全部終わらせてくれるやつ」を使う必要がある。 実務に挑んだ結果 案外なんとかなる。 同時に「オレは雰囲気でasync/awaitしている!」という気持ちにもなる。 よくわからないけど、コンパイラやリンターに言われたとおりにすればコンパイル通った、という事例がしばしば moveを付けたり付けなかったり Sync, Sendを付けたり付けなかったり Cloneを付けたり付けなかったり つらかったこと Iterator::mapとかで、async関数を渡すとコンパイルが通らない。 身に覚えのないpanic 'main' panicked at 'Cannot start a runtime from within a runtime. This happens because a function (like block_on) attempted\ to block the current thread while the thread is being used to drive asynchronous tasks.' 脱雰囲気のためのナレッジ整理 moveはいつ必要? move はasyncに限らずclosureの文脈で使われるキーワードである。 クロージャー内では引数でもらっていない値を使うことが出来る。 クロージャーが定義されるタイミングの環境にある変数を キャプチャ しているのである。 fn main(){ let x = 3; let is_x = |a| a == x; println!("{}", is_x(3)); } キャプチャは3種類のやり方がある。 - 環境にある変数を所有権ごと奪う。 - 環境にある変数の可変参照を持つ。 - 環境にある変数を(可変でない)参照を持つ。 所有権を奪いたい場合は fn main(){ let x = 3; let is_x = move |a| a == x;//xの所有権はクロージャーに移動 println!("{}", is_x(3)); } のようにすれば良い。 asyncに置けるmoveも同様である。asyncクロージャーやasyncスコープはFutureを返すが、そのFutureは環境のキャプチャを保持している。 #[tokio::main] async fn main(){ let x = 3; //asyncブロック let my_future = async{ x * 2 }; println!("{}", my_future.await); } 以下は、筆者がかつてasyncクロージャのつもりで書いたがコンパイルエラーになるコードである。 #[tokio::main] async fn main(){ let my_closure = |y| async{ 3 + y }; let x = 10; let my_future = my_closure(x); println!("{}", my_future.await); } 筆者が犯した間違いは2つ: - 上記コードの my_closure はasyncクロージャではなく、asyncブロックを返す普通のクロージャである。 - yの所有権が何やら良くない。 asyncクロージャはまだunstableであり、Nightlyでなければ使えないので、普通のクロージャのまま続けるとして、どのようにすれば上記コードのコンパイルが通るだろうか? まず、 y のライフタイムは my_closure クロージャの内部である。 y はasyncブロックの中で参照され、そのasyncブロックは my_closure の戻り値として、 my_closure の外側に放り出される。 このasyncブロックが、すなわちFutureが、awaitされるまで y は生きている必要がある。よって以下のようにすることで解決する。 #[tokio::main] async fn main(){ let my_closure = |y| async move { 3 + y }; println!("{}", my_closure(10).await); } ようするに、 Futureに所有権を移したい時にmoveをつければ良い 。とある変数 x の所有権をFutureに移したくなるのは - Futureが x のスコープを飛び出るとき である、これはスレッド間通信があるときはもちろん、スレッドをまたがなくてもよくある話である。 Sendはいつ必要? Send/Syncはスレッド間で値をやりとり/共有したいときに付けるTraitである。具体的な構造体にSendやSyncをimplするのは非推奨であり、通常はトレイト境界などの文脈で登場するのみである。 非同期タスクはRuntimeによっていくつかのスレッドに振り分けながら実行されていることを思い出すと、async関数の引数や戻り値はSendやSyncをimplしていなければならない気になる。これは事実そうであるが、必ずしもすべてにSendやSyncをimplしなければならないわけではない。ここではそれを詳細に説明する。 tokioのスレッド戦略 詳細な説明の前に、 tokio のスレッド戦略を説明する。 tokioのRuntimeは、1コア内に、少しの コアスレッド と、必要な数の ブロッキングスレッド を建て、それらのスレッドを駆使しながら処理を実行していく。 コアスレッド は非同期タスクをやるためのスレッドで、デフォルトでは1つしか建てない。複数建てるとしても少数である。 コアスレッドではやる準備ができたFutureオブジェクトに対してpollを行い、出来るところまで進めてもらう。複数のFutureを少しずつ進めながらやる場合も、コアスレッドで走らせるタスクを入れ替えながら行っている。 ブロッキングスレッド は重たい処理を行うためのスレッドである。必要に応じてスレッドを建てるので、膨大な数になることもある。 終わるまで時間がかかる処理をコアスレッドで行うと、他の実行可能な処理を待たせることになり非効率である。そのため、時間がかかりそうなタスクは同期非同期かかわらずブロッキングスレッドに「逃がす」運用が想定されている。 重い処理の逃がし方 重い処理を逃がすためには、 spawn_blocking と block_in_place の2つの方法がある。 spawn_blocking use tokio::task; #[tokio::async] async fn main(){ let res = task::spawn_blocking(move || { // めっちゃ重い処理をここでやる "done computing" }).await?; } 上記コードのようにspawn_blockingに重いタスクを渡すと、別のブロッキングスレッドを建てて、そこで重いタスクを実行してくれる。 このときスレッド間の移動が発生するため、 同期タスク と その戻り値 がSend+'staticを実装している必要がある。 'staticを付けているのは、本質的に参照や参照を含むオブジェクトを受け渡しできないようにするためである。('staticの参照などはそれ自体は'staticではないがSendをimplするので受け渡しできる) よもやま:昔は Send:'static だったらしい、 https://rust-lang.github.io/rfcs/0458-send-improvements.html block_in_place use tokio::task; #[tokio::async] async fn main(){ let res = task::block_in_place(move || { // めっちゃ重い処理をここでやる "done computing" }).await?; } 上記コードのようにblock_in_placeに重いタスクを渡すと、今のスレッドをブロッキングスレッドに変更し、別に新たなコアスレッドを建てる。 このときスレッド間移動が発生しないため、 Sendはいらない 。 使い分け Send+'staticが実装されていれば、spawn_blocking, 実装されていなければblock_in_placeを使えば良い。 Syncはいつ必要? 実務でSyncを要請されたのは、実はasync/awaitの文脈ではなく、tower-grpcが原因でした。 tower-grpc上ではApiはSyncをtrait境界にしています。 Cloneはいつ必要か? 実務でCloneを要請されたのは、以下のような箇所( IsContext (仮名)は独自のTraitです) pub async fn my_function< T: 'static + Clone + Send + IsContext, >( ctx: &T, arg: i32, ) -> Result<()> { let ctx = ctx.clone(); spawn_blocking(move || other_function(&ctx, arg)).await? } spawn_blockingのために ctx の参照(&T)を別スレッドにSendしたい。 spawn_blockingで渡すクロージャーは'staticである必要があるため、参照をキャプチャできない。 よって、ctxをクローンして、それをキャプチャさせてSendしている。 ちなみに、T: Syncであれば &T: Sendである。どのみち'staticではないのでTにSyncをimplしようと、Cloneは必要になる。 for_eachとかmapとかfilterをasync文脈で呼び出したいときはどうすればいい? Stream を使いましょう。 futuresトレイトで提供されています: https://docs.rs/futures/0.3.5/futures/stream/index.html これは非同期版Iteratorで、Iteratorでできそうなことはひととおり実装されています。 非同期な関数をmapの引数に入れたいとき このときは Stream::mapではなくStream::thenを使うと良い。 use futures::stream::{self, StreamExt}; async fn twice(x: i32)->i32{ x * 2 } #[tokio::main] async fn main(){ let v = vec![1, 2, 3]; let twiced_v = stream::iter(v).then(twice).collect::<Vec<i32>>().await; assert_eq!(vec![2, 4, 6], twiced_v); } なんでblock_onの中でblock_onしたらダメなの? block_onが呼ばれると、そのスレッドがブロックされて、中身の実行が終わるまで他のことができなくなるから。(ここでいうブロックはOSのAPIを呼んで行われるOSのスレッドのブロック)。 すなわち、block_onの中では非同期タスクが、よしなに走らされているはずで、そのなかでコアスレッドをブロックされると、非同期タスクを非同期に実行できなくなってしまうので、本意から外れることになる。 同様の理由でロックを取る処理も非同期タスクの中で うかつには やってはいけない。非同期タスク内でもロックが取れる機構(例: https://docs.rs/tokio/0.2.22/tokio/sync/struct.RwLock.html )が用意されているので、そちらを使用すれば問題ない。 r2d2-postgresは、非同期に使われる想定がされていなかったため、中ではスレッドをブロックするタイプのロック機構が使われている。よって、非同期タスク内で呼び出すとpanicが起きる。これが冒頭の「身に覚えのないpanic」の正体である。 というのが前回までのあらすじ 水平線で囲まれた、常体(だ、である調)で書かれている範囲が前回までのあらすじです。 上で書いたようなことをまとめて、社内向けに公開したら好評でした。私も鼻高々でした。 問題はその後で、moveとかSend/Syncとかの話は RustBookを読んだら全部書いてあるんですよね 。 びっくりしましたねえ。私は普段塾講師もしていて、中高生相手に情報科学を教えています。口を酸っぱくして「なにごとも、詳しいことが知りたくなったら、まずは公式ドキュメントをあたろう!」と教えてきたのに、、、これはとんだ医者の不養生でございました😌 まあ、最近進展があったasync/awaitあたりはもちろんRustBookには書いてないので、巷の記事をあさらないといけませんでしたね。AsyncBookがありますが、 今は絶賛成長中です 。 Futureを扱うトレートが乱立していたときの話とか、標準ライブラリに取り込まれるまでの歴史とかはBook系ではあまり深く踏み込まないので、そういうのが学びたければ巷の記事をあさるのもいいですね。歴史系は古い記事ほど良いソースです。 巷にあふれている良い記事を読んで思うのは、Rustの難しい部分と向き合いやすくなる「気持ち」を詳しく説明してくれているものが多いことですね。これはRustBookとかだと省かれていがちで、理解度を1から10に上げるときに役に立ちます。逆にRustBookに書かれていることは気持ちというよりは詳細な事実といった感じで、理解度を0から1に上げる部分に該当すると思います。 私が思うに理解度を0から1に上げる資料を読む前に理解度を1から10に上げるための資料を読んでRustに立ち向かうと「雰囲気でRustをしている!」という気持ちになるのでしょう。万物を完全に理解する必要はないので、大抵のものは「雰囲気で○○する」くらいがちょうどいいのだと思います。 ただ、タスクがスレッドを飛び越えまくるコードを書くともっとちゃんと理解しないと不安になってくる部分もあるかと思うので、そういうときはRustBookに帰ってきて、脱雰囲気をすると良いでしょう。 簡にして要を得た結論 まず、巷に溢れた記事を読んで気持ちを知ろう。すると雰囲気でRustができるようになる。 雰囲気でRustをする状態から脱したかったら、RustBookや仕様書に帰ってこよう。 おわりに この記事もまた、巷にあふれる記事の仲間入りをするのでしょう。この記事では私の「気持ち」のフレーバーがかかっております。皆さんのお口にあったのであれば幸いです。そうでなければ、残念。でもまたどこか別の場所で良い「気持ち」に出会えることを願います。 CADDiでは「モノづくり産業のポテンシャルを解放する」ための仲間を探しています。 実現したい世界に向け、作らなければならないもの、改善したいことが無限にあります。 少しでも興味を持っていただけましたたら、リニューアルされたばかりの 採用サイト をご覧ください。(わかりやすく、ヘッダの仕掛けもかっこいいので是非!) また、 カジュアル面談 も行っていますので、実際にエンジニアに会ってみたい方は こちら からどうぞ。
こんにちは。テクノロジー本部バックエンド開発グループの山田です。 この記事は キャディ Advent Calendar 2020 の8日目です。前日は桐生さんの tailwindcss のコンセプトとメリットについての考察 でした。 今回は 以前のgRPCの記事 に引き続き、システム開発の BFF や BE 間の通信で利用している gRPC & protocol buffers (以下 protobuf ) に関する記事です。 開発/運用していく中で protobuf 上での Optional な値の扱いをどうするかという点で何度も困らせられた苦い思い出をもとに、 proto3 で実験的に復活した optional について調査・検証していきます。 検証に利用したコードは こちら にあります。 時間のない方は最下部のまとめ欄をご覧ください。 [toc] optionalの定義方法と実体 久しぶりに protobuf と戯れることにしたため、まずは公式から情報を得ることとします。 公式に記述されている通り、v3.12.0より実験的機能として復活し、内部的には oneof のフィールドとして扱われる実装となっているようです。 protobuf/field_presence.md at master · protocolbuffers/protobuf · GitHub protobuf/implementing_proto3_presence.md at master · protocolbuffers/protobuf · GitHub optionalの検証 実際に触りながら差分を確認していくために、今回は以下のようなprotoの定義を利用して、検証していきます。 syntax = "proto3"; package pingpong; service PingPong { rpc SendPing(Ping) returns (Pong) {} } message Ping { string type = 1; optional string payload = 2; } message Pong { optional string payload = 1; } 今回はキャディのシステム開発で利用している Rust / JS / TS に関して検証することとします。 まずRustでの対応状況を確認していきます。 Rust Option に変換されていい感じに使えるのではないか、と期待していたものの、protobuf の有名所 crate である stepancheg/rust-protobuf と danburkert/prost はどちらも issue がたてられているだけで、まだ実装されていませんでした。 ■ 該当 issue Support Field Presence (proto3) · Issue #513 · stepancheg/rust-protobuf · GitHub Support proto3 Field Presence · Issue #351 · danburkert/prost · GitHub rust-protobuf は version 3 に組み込まれる予定になっているようなので、動向が気になるところです。 rust-protobuf version 3 · Issue #518 · stepancheg/rust-protobuf · GitHub 出落ちのような形で残念ですが、続いて JS / TS での状況を確認してみます。 JavaScript / TypeScript あらかじめ、JS / TS の検証で protoc の呼び出しをするために利用している grpc-tools を導入しておきます。 ※ yarn 及び typescript を導入済みであることを前提にしています yarn add -D grpc-tools 以前のgRPCの記事 でもまとめましたが、 protobufの扱い方はいくつかあるため、複数のライブラリで検証をすすめてみます。 protoc の JS 出力を使う方法 もともと提供されている、protoc の JS 出力を利用することで生成はできました。 しかし残念ながら 出力された JS に関連する TS 向けの型定義ファイルの出力を行う protoc のプラグインは対応されているものを見つけることはできませんでした。 そのため、今回は protoc で生成された JS の確認のみにとどめます。 以下は生成するためのコマンドと、生成された protobuf 向けコードの抜粋です。 生成コードを確認してみると、今回 optional で定義した payload というフィールドに対して、以下のように oneof と同等のコードが生成されていました。 ■ ライブラリ導入 yarn add google-protobuf ■ コード生成 shell の一部 DEST_DIR=./generated "$(yarn bin)"/grpc_tools_node_protoc \ --js_out=import_style=commonjs,binary:${DEST_DIR} \ --experimental_allow_proto3_optional \ -I ../ ../pingpong.proto ■ 生成されたコードの一部 /** * Clears the field making it undefined. * @return {!proto.pingpong.Ping} returns this */ proto.pingpong.Ping.prototype.clearPayload = function() { return jspb.Message.setField(this, 2, undefined); }; /** * Returns whether this field is set. * @return {boolean} */ proto.pingpong.Ping.prototype.hasPayload = function() { return jspb.Message.getField(this, 2) != null; }; 各型での optional については、 公式のテストコード (protobuf/proto3_test.js) にて網羅的に記述されていたので確認してみてください。 続いて protoc の JS 出力を使わない方法 の検証をしていきます。 protocの JS 出力以外の方法 キャディの開発では以前検証した情報をもとに protobuf.js を利用しているため、リポジトリの Issue や PR を覗いてみましたが、残念なことに特に動きがないことがわかりました。 ■関連 issue Support for required fields in proto3 via options · Issue #1468 · protobufjs/protobuf.js · GitHub Implement Field Presence · Issue #1406 · protobufjs/protobuf.js · GitHub しかし、上記 Issue にリンクされている Issue をたどってみると、 stephenh/ts-proto というレポジトリたどりつきました。 たどりついた Issue の中には、なんと 2020年9月に公開された v1.32.0 にて optional に対応したと書かれています。 (対応しているライブラリがほぼ見つけられていない状態だったので、この記事のコンセプトが崩れずにすんでよかったです) ということで、ここから ts-proto で optional の検証をしていきます。 ts-proto の README 等をみてみると、proto ファイルをもとに protobuf.js の encode / decode を利用するようにラップされたコードを生成してくれる protoc のプラグインだということがわかりました。 ここから検証コードを作って生成後のコードをみていきます。 ■ ライブラリ導入 yarn add -D ts-proto yarn add protobufjs ■ コード生成 shell の一部 DEST_DIR=./generated "$(yarn bin)"/grpc_tools_node_protoc \ --plugin="$(yarn bin)/protoc-gen-ts_proto" \ --ts_proto_out="${DEST_DIR}" \ --ts_proto_opt="lowerCaseServiceMethods=true" \ --experimental_allow_proto3_optional \ -I ../ ../pingpong.proto ■ 生成されたコードの一部 export interface Ping { type: string; payload?: string | undefined; } export interface Pong { payload?: string | undefined; } 生成されたコードでは、TS 上で Optional フィールドとして定義されており、oneof とは異なる定義となっています。 ( oneof を利用する場合には union 型で出力するオプションなどがありました) ts-proto の考察 少し脱線しますが、上記確認をするなかで ts-proto が非常に便利そうだったため、今後の開発の検討のために良い点と気になる点をまとめておくことにします。 良い点 optional の対応 protoc の js 出力でできなく、protobuf.js には定義されていたオブジェクトからの代入と同等の実装がある(fromObject) oneof を union 型で出力できる gRPC のサービス実装で注目されている Twirp , grpc-web , NestJS 向けの実装がある 内部の encode / decode に protobuf.js の処理を利用してるため高速(protobuf.jsのベンチマークを信じると) 気になる点 出力されるコードの内部に any が利用されている message に対応するモデルの定義が、 同名の interface と const で指定されており、型が少し扱いにくい grpc-loader 等と組み合わせて利用する場合に、 Service の定義がシンプル(Promiseを利用した引数一つの定義しか出力されない) Service の 第2引数で利用する metadata 対応が nestjs オプションを有効化したときにしかきかない 今回の調査をきっかけに、今後 gRPC 関連の処理を見直す際に検討したいと思える、良いライブラリと出会えてよかったです。 まとめ 今回の調査・検証結果をまとめると以下のとおりです。 protobuf v3.12 から optional が実験的に復活し、内部的に oneof として扱われている protobuf のコード生成ライブラリ Rust で field presence の対応が入った crate は現時点では見つけられず JavaScript で field presence を使う場合は公式の protoc の js_out で対応されている TypeScript で field presence を使う場合は ts-proto で対応されている protoc の TS 向けプラグインには対応されているものはなかった 参考 以下は調査をする中で見つけたサイトの紹介です。 NestJSでgRPC API作るならコード生成はts-protoで決まり ライブラリの調査しているときに見つけた、ts-proto + NestJS で gRPC サーバーを作る記事です。 ありがたいことに 以前のgRPCの記事 へリンクしていただけてました。感謝です。 https://qiita.com/vol1003/items/326a074fb1a605651750 protobuf の optional な値の扱い方とField Presence について チームで検討していたことが簡潔にまとまっており、proto3 の optional についても詳しく書かれていました。 細かく仕様を見てみようと思った矢先に見つけたので、今回の記事では大幅に端折りました。いいまとめをありがとうございます。 https://note.com/dd_techblog/n/n95e4331a8eea おわりに 日頃の開発をするなかで、protobuf での optional な値の扱い方をどうするかを定期的に話し合ってきていましたが、調査・検討をするタイミングを作ることができずにいたため、いい機会になりました。 また、この記事が optional な値の扱い方に困っている方へ多少の手助けになると幸いです。 CADDiでは「モノづくり産業のポテンシャルを解放する」ための仲間を探しています。 実現したい世界に向け、作らなければならないもの、改善したいことが無限にあります。 少しでも興味を持っていただけましたたら、リニューアルされたばかりの 採用サイト をご覧ください。(わかりやすく、ヘッダの仕掛けもかっこいいので是非!) また、 カジュアル面談 も行っていますので、実際にエンジニアに会ってみたい方は こちら からどうぞ。
目次 こんにちは、CADDi でフロントエンド エンジニアをやっている桐生です。この記事は CADDi Advent Calendar 2020 の7日目の記事になります。 昨日の記事はモリさんの あえてケイスケ・ホンダのようなデザイナーになるには でした!まだご覧になっていない方はぜひ目を通してみてください! 本日は、最近触っている tailwindcss についてお話できればと思います。 [ toc ] tailwindcss とは tailwindcss は数ある CSS フレームワーク の1つですが、 Utility first というアプローチでスタイルを組み上げていく点がユニークで、OOCSS, BEM, SMACSS など、これまでの CSS 設計アプローチとは考え方が大きく異なります。 tailwindcss では p-6 , max-w-sm などの CSS class を utility class と呼んでおり、これらの utility class は1つ1つの CSS プロパティに対応するほど細かく primitive なものとしてあらかじめ用意されています。 padding のサイズ違いなどにもそれ専用の class が提供されている ので、 CSS そのものを書く代わりに、用意されている utility class をHTMLのclass属性に追加してスタイリングしていく、という開発スタイルになります。tailwindcss が提供しているものはこの utility class のみで、他 CSS フレームワーク がよく提供しているような コンポーネント という大きさのレベルのスタイルは存在しません。これが tailwindcss の Utility-First というアプローチです。以下は tailwindcss の公式ドキュメントからの引用になります。 <!-- 公式ドキュメントから引用 --> <!-- class 属性に付与さているのは全て utility class --> <div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4"> <div class="flex-shrink-0"> <img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo"> </div> <div> <div class="text-xl font-medium text-black">ChitChat</div> <p class="text-gray-500">You have a new message!</p> </div> </div> tailwindcss のメリットは? tailwindcss の公式ドキュメントでは、Utility-First アプローチをとることで、3つのメリットがあると言っています。 1. CSS 設計・運用コストの低減 You aren't wasting energy inventing class names. No more adding silly class names like sidebar -inner-wrapper just to be able to style something, and no more agonizing over the perfect abstract name for something that's really just a flex container. class 名の発明に無駄なエネルギーを使わなくて済む。 何かをスタイリングするためだけに sidebar-inner-wrapper のようなばかげたクラス名を追加したり、単なるフレックスコンテナに対して抽象化した名前を悩んだりすることがなくなる。 CSS 設計の経験のある人は誰しも悩んだことがあると思います。 CSS class のネーミングは本当に難しい作業です。UI コンポーネント 構造を抽象化して class 名として表現しなくてはなりません。それに加え、ネーミングルールの設計、ネーミングルールの遵守徹底など、非常にコストのかかる作業でもあります。 Utility-First アプローチをとることによって、そもそも class 名のネーミングという作業自体がなくなるので、ネーミングにかけていた時間をゼロにすることができるということですね。ネーミングという行為自体が不要となるので、それに関わる設計・運用コストもなくなり、一石三鳥というわけです。 2. CSS100%再利用による CSS サイズ肥大化抑止 Your CSS stops growing. Using a traditional approach, your CSS files get bigger every time you add a new feature. With utilities, everything is reusable so you rarely need to write new CSS . CSS の肥大化を止めることができる。 従来のアプローチを使用すると、新しい機能を追加するたびに CSS ファイルが大きくなる。utility を使用すると、すべてが再利用可能になるため、新しい CSS を作成する必要はほとんどない。 tailwindcss が提供する utility class は、 CSS プロパティ1つ1つに対して用意されるほど細かいので(primitive utilities という呼ばれ方もしています)、100%再利用可能です。ほとんどの場面においては用意されている utility だけで十分で、開発者が独自に CSS を実装する必要がないということですね。 3. スタイルの変更をHTMLローカルで安全に行える Making changes feels safer. CSS is global and you never know what you're breaking when you make a change. Classes in your HTML are local, so you can change them without worrying about something else breaking. 変更が安全になる。 CSS はグローバルであり、変更を加えたときに何を壊しているのかが分からない。 HTMLの class はローカルであるため、他の何かが壊れることを心配せずに class を変更できる。 utility class は primitive なので、class 内の CSS を変更するということはありません(もちろん変更することは可能で、その場合は config ファイルを介して変更する ことができます)。もしスタイルを変えたくなった場合は、 CSS を変えるのではなく、HTMLのclass属性に付与している CSS class自体を変えることになり、 <div class="p6"></div> <div class="p6"></div> ↓ 1つだけ太字にしたい <div class="p6"></div> <div class="p6 font-bold"></div> <- 影響範囲はここだけ HTMLの変更は影響範囲がそこのみに限定されるため、非常に安心して変更することができるというわけです。よく CSS は グローバル変数 だと揶揄されますが、それとの対比で 「HTMLの class はローカル」 とはうまい例えだと思います。 tailwindcss のデメリットは? 良いことづくめのような話に聞こえますが、果たしてデメリットはないのでしょうか? インラインスタイルじゃダメなの?? HTML の class 属性に utility class を付与していく様はまるでインラインスタイルを書いていくかのようです。 Why not just use inline styles? ところが、 インラインスタイルで埋め込まれた値は magic number となってしまう インラインスタイルは media query が使えない インラインスタイルは擬似クラスが使えない との理由から、インラインスタイルはむしろ悪手となり、utility class を差し置いて採用する意味がありません。 メンテナンス性は大丈夫?? Maintainability concerns 例えばボタンのような非常に基本的な コンポーネント であっても utiliy first で実装すると相当数の CSS class が class 属性内に展開されます。 <!-- 公式ドキュメントから引用 --> <button class="py-2 px-4 font-semibold rounded-lg shadow-md text-white bg-green-500 hover:bg-green-700"> Click me </button> このボタンスタイル一式を様々なところで使いまわしたい時、毎回コピーしなくてはいけないのだとすると、メンテナンス性を著しく損なうことになりますが、これについてはどう対処すればよいのでしょうか? tailwindcss としての回答は「カスタム CSS class を作らずに template や JavaScript Component を作れ」です。 Extracting template components 一旦カスタム CSS class を作ってしまうと、そこからネーミングコスト、 CSS 肥大化につながってしまうので、template や JS Framework にコンポーズする責務を任せ、 CSS 側はあくまで Utility-First に徹することを推奨しています。 こうすることで、Utility-First のメリットを損なうことなく、メンテナンス性を担保することができるというわけです。 CSS プロパティ <-> utility class の翻訳大変じゃない?? CSS プロパティ1つ1つにあたる utility class が提供されているとなると、その数は膨大で、覚え切ることは難しいです。普段 CSS を書き慣れた開発者にとっては、 CSS プロパティ -> utility class の翻訳がもどかしく感じるかもしれません。 そこで tailwindcss では、DXを向上させるための VS Code extension を提供しています。 Tailwind CSS IntelliSense これにより IntelliSense が効くようになって、効率的にスタイリング作業を進めることができます。実際、私も tailwindcss を触り始めた時にもどかしさを感じていたのですが、この extension を入れてからストレスが減ったと思います。 (閑話)いかにして tailwindcss は生まれたのだろう? tailwindcss のドキュメントの一節にこうあります。 Extracting template components It's very rare that all of the information needed to define a UI component can live entirely in CSS — there's almost always some important corresponding HTML structure you need to use as well. UI コンポーネント を定義するために必要なすべての情報が完全に CSS に存在することは非常にまれ。ほとんどの場合、 CSS と対応するHTML構造が必要になる。 昔から、見た目と構造は分離すべしと言われてきましたが、昨今の CSS 設計手法では、むしろ コンポーネント 構造をいかに抽象化し、適したネーミングを CSS class として表現するか、ということに重きが置かれてきたように思います。よくよく考えるとこれは、見た目と構造が分離されるどころか、むしろ結びつきを強める方向に進んでしまっていたのではないかと思うようになりました。 作者は、これこそが根本的な原因であり、その因果として 構造を抽象化した CSS class名 -> CSS 設計コストの増大 構造に依存した CSS -> CSS 再利用性の低下 という問題が生じているのでは、と考えたのではないでしょうか。そして、その解決策を考え突き詰めた結果として Utility-First というアイディアに至ったのではないか、と推察します。tailwindcss が生まれた背景には 見た目と構造の完全分離 という設計思想があるような気がしました。 そう考えると、tailwindcss の Utility-First アプローチは、単なる奇抜な手法ではなく、現状の問題点への深い洞察から導きだされた論理的な解決方法であり、画期的な手法だ!と感心せずにはいられません。 これはあくまで私の解釈ですが、皆さんはどう思われますか?? まとめ tailwindcss は primitive な utility class を提供することで、 CSS の設計コスト、運用コストを下げる。 CSS の再利用性を高める。 CSS 肥大化を防ぐ。 スタイルの変更容易性を上げる。 をもたらしてくれるものである、と言えるのではないでしょうか。 現在 CADDi では、styled-components を使用したスタイリングを行っているのですが、特に CSS の再利用性に課題ありと個人的に感じています。今回 tailwindcss についてのコンセプトとメリットについて理解を深めたことで、課題解決に期待できそうなので、今後試験的に導入することも考えていきたいなと思っています。 ただ、私自身、tailwindcss を触り始めてからまだ日が浅いこともあり、デメリットらしいデメリットに当たっていないだけの場合もありますので、今後、いろいろと知見を溜めて行ければと思います。 付録 この記事を読んで「tailwindcssなんか良さそうだな、今すぐ試してみたい」と思って下さった方もいるのではないかと思います。いたら嬉しいです。そんな方にぜひ。 tailwindcss をとりあえず触ってみたい人向け 公式の playground があるので遊んでみてください。 https://play.tailwindcss.com/ project に組み込んで使ってみたい人向け tailwind をセットアップしたプロジェクトを作ってみたので、お好きに使ってください。README.md にはセットアップ方法をまとめてありますのでご参考ください。 Next.js プロジェクト create-react-app プロジェクト おわりに CADDi では先日採用サイトをリニューアルしたばかりです!非常にカッコよく仕上がっているので、興味ある方はぜひご覧いただければと思います! https://corp.caddi.jp/recruit/
こんにちは、キャディでUI/UXデザインを担当しているモリです。 この記事は CADDi Advent Calendar 6日目の記事です。昨日はマリアンヌによる キャディのエンジニアの開発環境、ぜんぶ見せます です!とっても面白いのでぜひご覧になってみてください! 僕は本田選手のように?幅広く活躍できるデザイナーになるには、について書いていきたいと思います。 ケイスケ・ホンダ=プロフェッショナル "そもそも"を考え、自分の真ん中を決めることで、迷わず進むことができるんです 本田思考 - 本田圭佑 デザイナーも”そもそも”をよく考える職業だと思います。(え?) 最近のデザイン、特にテック領域では、作家性や審美性に重きをおいたデザインから、 ユーザー体験やシステム設計・開発効率性に重きをおいたデザイン に重心が変わってきました。 またそういった変化に合わせて、 デザイナーであってもビジネスやテクノロジーのリテラシーを持った越境型人材(BTC人材) が求められるようになってきました(う〜ん) ビジネス・テクノロジー・クリエイティブのBTCトライアングル 参考: 田川欣哉. イノベーション・スキルセット~世界が求めるBTC型人材とその手引き いやしかし、そんなどこかの記事で読んだような話をされても、、、と思われるでしょう、僕もそう思います。 そこで今回は僕自身がキャディの開発現場でデザイナーとして感じたB(ビジネス)やT(テクノロジー)の必要性や、デザイナーがBやTを伸ばすには?ということについて僕なりに書いてみます! デザイナーにビジネスやテクノロジーは必要か? 僕たちは、一人の力やひとつの専門的スキルでは太刀打ちできないような複雑で複合化した課題を、ソフトウェアの力で解決しようと試みています。こういったプロダクトは、ビジネス、テクノロジー、クリエイティブがかけ合わさった品質が当たり前のクオリティとして求められます。そして、そのようなプロダクト開発の現場では、高い確立でエンジニアとデザイナーが一緒に仕事をする事になるので、デザイナーはエンジニアの仕事内容と気持ちを理解する事と、いざとなったら自分でも手を動かし、コードが書ける、そういった役割が強く求められます。またプロダクトオーナーや経営メンバーと会話する際も、会社として戦略上どこに注力しているか?また現場でボトルネックになっている箇所はどこか?などのビジネス上の前提を踏まえておかなければ、いくらヒアリングしても結局プロダクトに反映することができません。UIをデザインするにしても、画面上の情報の優先順位付けすらも一人でできないような状態になってしまします。(ふう) ↑こんなイメージで書いています。 参考: レッド-山本直樹 要するにデザイナーであってもビジネスとテクノロジーの観点を持って開発しないと夢のプロダクト・マーケット・フィット〜P・M・F〜に到達することなんてできないぜ!ということを改めて実感したよ、と言いたかったのですが。 いや待て、と。ならばデザイナーはビジネス x テクノロジー x クリエイティブの3領域に知識と経験を持つ究極超人にならねばならないのか、と思われる方もいらっしゃると思います。これって普通に考えてかなり難易度が高いことを言っていますよね、僕が本田圭佑であっても厳しいと思います。 でもよく考えてみてください、本田選手もオフェンス・ディフェンス・経営と全て一人でこなしていたわけではありません。長友選手や香川選手など優秀なチームメイトがいたからこそ結果を出せたはずです。 そう、僕や皆さんの隣にもスーパーエンジニアや強力なプロダクトリーダーがいるでしょう!彼らと共にチームとしてBTCトライアングルが形成されていることを目指せば良いのです。 ただそのためには デザイナーがビジネス・テクノロジーそれぞれの言語を理解する 必要があります。 Learn to code 2010年、今から10年前のことですが、 Adobeフォントライブラリの元クリエイティブディレクターでありデザイナーでもあるElliot Jay Stocks氏がこんなツイートをしたそうな。 Honestly, I'm shocked that in 2010 I'm still coming across 'web designers' who can't code their own designs. No excuse. 「いや〜正直言って、2010年になっても自分のデザインをコーディングできないWebデザイナー?がいてマジビビったわ」 恥ずかしがり屋な御仁なのか言葉はちょっとキツめですが、おそらく彼はこう言いたかったのでしょう 「コーディングの知識がないのにWebプロダクトをデザインすることなんてできないでしょ?」と。 なるほど、と。ごもっともだな、と。 コーディングの知識がないデザイナーが作った、実装面の考慮がない夢のようなデザインを渡されても 、一緒に働くエンジニアは混乱するだけだなと。そんなことで開発効率を上げてPMFを達成できるのか、と。 ではデザイナーとしてどこまでこなせるようになると良いでしょうか? 自分のデザインをコーディングしてプロトタイプができるようになるところまで まずはここを目指すと良いかなと思います。 デザイナーとして表現したいことを実際コーディングして動かせるところまでできれば、エンジニアとのコミュニケーションコストも下がるし、実際の画面で触って使えるプロトタイプは説得力が違います。 今は、ReactやVueなどのフロントエンドのフレームワークが充実していて簡単に実装・アウトプットまで持っていくことができるし、自身でコーディングするからこそ見えてくることも多々あります。 動的コンテンツ(テキスト量や画像サイズ等)の制御や、取り扱うデータが増えた場合はどうするか、バリデーションはどう見せるか、そういった 実装面とUXを考慮したデザイン こそがなによりも求められているのです テクノロジーを学び実装面とUXの両方が考慮されたデザインを作る力を身に付けましょう! A Business Perspective まあテクノロジーはわかった、と。じゃあビジネスはどうなんだ、と。ビジネスといっても。経営戦略・マーケティング・財務管理諸々あるだろ、と。 そうです、デザイナーにとってビジネスは理解し難い領域です、、、ビジネス怖い、、、 そこで僕はデザイナーに求められるビジネス観点とは以下のことなのではないかと考えました。 組織が本当に達成したい事を正確にキャッチアップする力 この観点がずれているといくらクオリティの高いデザインを作っても無駄 になってしまいます。 なのでここを鋭くキャッチアップする力を身につけることでビジネス観点を備えたデザイナーになれるのではないでしょうか! 俺は孤立している。あえてね なるほど、と。キャッチアップ力が大事なのはわかった、と。 それならどうすればどうすればキャッチアップ力を身に着けることができるのでしょうか? 俺は(ミランで)孤立している。あえてね 皆さん、この名言をご存知でしょうか、ACミラン在籍時の本田選手の言葉です。 そしてこう続けました。 昔、日本代表でとっていた行動と一緒ですよね。コイツ、何やろうな? と。自分を持っているな、と。試合へのアプローチも全部、自分のやり方で集中しているな、と。そういう風に(周囲の仲間に)思わせているわけです なるほど、と。一理あるな、と。迎合することなくオーラを放つ作戦ですね! しかしこれは本田選手のカリスマがあるからできる態度であってなかなか真似できるものではありませんね。 なので僕がおすすめする方法としては、"情報よこせオーラ"をバンバン発するのではなく、自ら動きビズサイドの情報との接触機会を増やしていくことだと思います。 Slackなどのメッセージアプリを利用していれば、色々なチャンネルにジョインしてログを収集する・Notionなどの情報共有サービスなどを利用していれば溜まったドキュメントに目を通していきます。 デザイナーであってもこのように情報を収集していくと、今現場では何がボトルネックになっているか、戦略にどのような転換があったか、などが自分の中に情報が蓄積され、自然と課題を適切にキャッチアップする力が養われる気がします。 これ実はキャディでは結構みんなやっていて、しかもかなり効果があります!ぜひデザイナーのみなさんも試してみてください! 役割の変化 プロダクト開発の手法がウォーターフォールからアジャイルに変化しているように、そこで求められるデザイナーも 「綺麗なものを作れるけど受け身な人」ではなく「引き受けて改善できる本田圭佑のような人」 に変わってきていると思います。 ウォーターフォールであればプロダクトオーナーなどから与えられた要件に対して適切にスタイリングしていくことが求められますが、アジャイルな開発であれば経営者やプロダクトオーナー、エンジニアのパートナーとしてプロダクトを前進させる本田圭佑のような態度が求められます。 そして僕自身キャディでのプロダクト開発の経験を経て、デザイナーの役割の変化やBTCの重要性に気付けました。 キャディはデザイナーがビジネスやテクノロジーを伸ばすのに最適な環境が整っていると思います。 ビズ・テック共にメッシやCロナウド、V・ファン・ダイク、デ・ブライネ級のメンバーが揃っているので、少しでもキャディのプロダクト開発に興味をもっていただけたら、ぜひ カジュアル面談 に申し込んでみてください!
こんにちは! @ryokotmng です。本記事は、 キャディ Advent Calendar 2020 – Qiita の4日目の記事です。昨日の記事はagate-prisさんの Orphan Ruleよありがとう ~Rustを採用したおかげでリファクタリングが捗った話~ でした。 キャディのエンジニアがどんな開発環境で仕事をしているのかについて、アンケートをとってみました。その結果を (私の心の声を挟みつつ) まとめてみましたので、本日は弊社エンジニアチームの雰囲気を感じてもらえるとうれしいなと思っています💁 回答してくれたエンジニアの経歴と今のお仕事 合計18人のエンジニアが回答してくれました!まずは、回答してくれたエンジニアたちのバックグラウンドと今のお仕事について、ざっくり見ていきたいと思います。 エンジニア経験は何年くらいですか? ※ 一部未回答の質問もあるため、グラフの数字の合計は必ずしも全回答者の合計人数と一致しません。 ※ 縦軸は人数です。 一番経験が長い言語は? ※ 縦軸は人数です。 経験年数、言語共に、バックグラウンドはかなり多様なようです! 今ざっくり言うとどんな開発をしている? 今一番よく使う言語は? ※ 横軸は人数です。 最もよく使われているのはRustでした。 キャディではほぼ全てのサービスでバックエンドをRustで書いており、フロントエンドやBFFではTypeScriptを使っています。また、フロントエンドやバックエンドの担当と言っても境界ははっきりしているわけではなく、バックエンドエンジニアがBFFやフロントを書いたり、その逆もよくあります。 お待ちかね、開発環境について 使っているPCは? ※ 以下円グラフ内に記載された数は回答者数です。 Macを使っている人が多いですね。 個人的には、自作PCを使っている人が4人もいることに驚きました!Rustを用いた開発では、コンパイルに時間がかかることやメモリが足りなくなりがちなこともあり、高スペックなPCを求めて自作しようと思うようになるのかもしれません。 自作PCの詳細を教えてくれた人たちは、Ryzen 9 3900X + GEFORCE RTX 2080 SUPERを使っているそうです。「コンパイル速度もゲームもいい感じ」とのこと。(Ryzen、私も使ってみたいです!めっちゃ速いんだろうなぁ。) バックエンドエンジニアの @kuwana_kb_ さんより、自慢の自作PCの写真をいただきました🧑🏻💻もはやSFっぽい写真ですね!かっこよすぎて、ウェブから適当にイケてる写真を引っ張ってきたんじゃないかとつい疑ってしまいました (ご本人が作成したものです)w 他にも、「ノートPCながらGTX1070搭載はなかなか熱いと思ってます!(排熱的にも)」という意見もありました。 使っているOSは? 使っているシェルは? zshを使っている人が7割と大多数でした。 fishを使っている人は、「履歴補完が便利で他のshellを使う気にならない」とのこと (そんなにすごいのか、ちょっと使ってみたいな...)。 Nushellは知らなかったのですが、Rust製なのですね!2019年9月頃公開された新しいシェルで、fishのように入力補完機能が強力なようです。 使っているエディタは? ※ 複数回答が含まれます。 VSCodeが最も多く、VimとIntelliJ系がそれに続きました。「VSCodeはデファクトスタンダード」という声や、「IntelliJ系の補完、リファクタリング機能、文字列選択機能が優秀」、「Vimは思考のスピードで編集できる」などの感想もあり、なかなかどれが良いとは一概に言えなそうですね。複数使い分ける派の人も数人いました。 なかには、同じJetBrains製品を複数個使い分けている人も。「Rust は CLion、TypeScript は WebStrom、インフラコードは IntelliJ、という風に使い分けています。どのコードを書く時にも同じ操作感で使える点が気に入っています。CLion は Rust の静的解析がとにかく速いので助かってます」とのことです。 VSCodeにはTabNineというAIによるオートコンプリート機能のプラグインがあり、これをおすすめするエンジニアもいました。 これは開発に必須!と思われる便利なツールは? fzf + ghq、zsh-autosuggestions、fishの履歴補完機能など、コマンドを補完するためのツールが多く挙げられていました。 Vimの場合はLSPのcocを使用している人が多いようです。ほかには、ripgrep、iTerm、tmuxなどを使っている人が多いみたいです。 個人にインタビューする機会があれば、この辺は実際の作業を見ながら細かく突っ込みたいところです!(今回の企画が好評だったらやりたい!) 使っているキーボードは? ※ 複数回答が含まれます。 この結果には含まれていませんが、私も今年のクリスマスにHHKBデビューする予定です。HHKBは大人気な一方で、打鍵音がやや大きいため、「奥さんに不評なので、Apple公式のものと使い分けている」という意見も。 普段使うぶんには気になりませんが、ご家庭がある方は、特に音の問題は難しいですよね。 Nizを使っている人からは、「キーが軽くて深くてしっかり戻ってくるのでかなり打ちやすいです。キーバインドがないので Vim & tmux を使う方でも衝突しなくてよい」という声もありました。 こちらはミートアップ等でおなじみ、エンジニアリング・マネージャー @mura_mi さんのキーボード。Realforceの変荷重だそう。配色が可愛いです✨英字配列であることには拘っているとのことです。 自宅、見せてください!! さて、ここで数人のエンジニアの自宅のデスクを写真でお届けしちゃいます💁🏻 まずはアルゴリズムエンジニア、 @ngtkana さんのデスク。キーボードはNiZですね⌨️左手にペンがありますが、メモを取る時にはiPadのGoodNoteを使っているそうです。左手にある書籍スタンドが便利そうすぎて、これを見てつい私もポチってしまいました❣️ アルゴリズムエンジニアいなむさんのデスク。手元でメモを取れるようにiPadを置いていて、Notabilityというアプリを使っているそうです。奥で虹色に光る自作PCがきれいですね🤤こだわりは、「IDEの背景が可愛いキャラクターであることが重要だと思っています」とのこと。 バックエンドエンジニア、 @gushernobindsme さんのデスク。 スッキリしているなかにもオーディオ機器へのこだわりがみられますね。ディスプレイの左手に立てかけてあるのは、プライベート用のPCでしょうか?「 サウナイキタイ 」のステッカーが貼ってありますね🧖‍♂️ 次にバックエンドエンジニアの木村さんです。 ドラマに出てきそうなかっこいい空間に仕上がっています😍 左側のディスプレイの上にある丸いものは、カメラと女優ライト的なやつでしょうか!?これなら登壇や動画配信もバッチリですね💪🏻 こちらは @mura_mi さんのデスク。 ディスプレイやキーボードスライダーはリモートワークの影響で整備したそう😃 メモ用の紙・ノート類は、「1週間のタスクを書き続けるスケジュール手帳」「1on1記録ノート」「ブレスト用のA4コピー用紙」3種類を使っているそうです。 ついテンションが上がってしまいました💦 私の家の環境はしょぼすぎて残念ながら公開できないのですが、みなさんの写真を見ると環境を整えたくなりますね! 仕事に欠かせないものは? 下記3つは、どれも10人以上から回答されていました。 - ディスプレイ - イヤフォン (含ネックスピーカー) - 良い椅子 他にも、お菓子や音楽、ノートは多くの人が挙げていました。 リモートワークの影響でイヤフォンの長時間利用から耳を痛める人も出てきていて、ネックスピーカーを購入した人も数人いました。私も最近愛用しているのですが、かなり軽くてちょっと席を離れる時もつけてて気にならないので (何より耳が痛くないの最高) 個人的には重宝しています。 一方で、「イヤフォンにノイズキャンセリングは必須」という声や、「FocalのStellia(ヘッドホン)は最高に良い音だぞ...」との意見もありました。こちらの方が没入感があって集中力を上げやすそうなので、使い分けるのも良さそうですね。 @kuwana_kb_ さんより、Focalのヘッドホン。またまた写真がイケてますね!悔しいけどかっこいいです! 椅子については、「思い切ってコンテッサの椅子を買ったら腰痛が一気に楽になった」との意見も。また、電動昇降机を使用している人や、座椅子で仕事をしているという人もいました。キャディも基本的にはリモートワークなので、家にいながら生産性を上げる環境は大切ですね。(ぐぬぬ〜〜、椅子高いけど、いいやつ欲しいな〜〜〜〜。) また、「マイクはAKGのC451という楽器用のものを使用しています~」、「毎シーズン良いダージリンを取り揃えております」というこだわり派も。 木村さんのマイク。本格的すぎる😯 いなむさんこだわりのダージリン。収穫年やグレードまで書いてあります! 他にも、確かに!と思ってしまったものに、このような意見もありました→「最近は、開発する人間の環境(近くの飯屋とか)のほうが重要度が高い気がします」。家の近くのランチにはもう飽きた...なんて方も世の中には多いのかもしれません。 最近気になるものは? 下記のようなものが複数人から挙げられていました。 - M1搭載Mac mini - キーボード - 4Kディスプレイ - 椅子 - 自作PC 自作PCについては、私も年末年始を利用して取り組んでみたいと思っています!社用PCは特に問題ないのですが、Rustを始めてから、自分のMac book pro (ケチって4コア😇) ではなかなか開発に苦労するようになってきてしまいました💦 いかがでしたでしょうか、楽しんでいただけましたか?これまで開発環境について話したりする機会は少なかったので、書いている私としてもとても勉強になりました。 キャディでは、楽しいエンジニアたちが日々業界を変えるために頑張っています!今回は仕事の話は全然していませんが、キャディでの開発や技術に興味を持ってくださった方は、ぜひ カジュアル面談に申し込んでみてください ☺️ お読みいただきありがとうございました!寒い日が続きますが、健康に気をつけてくださいね〜!
おはようございます、CADDiでバックエンドエンジニアをしているagate-prisです。本記事は キャディ Advent Calendar 2020 - Qiita の4日目の記事です。昨日の記事は飯迫の Argo Rollouts で Blue-Green Deployment でした。 本記事は、すでにRustを使っている人向けではありません。まだRustを使っていない人向けの「Rustにはこんな機能があるよ。この機能のおかげでこんないいことがあったよ」とRustを勧める記事です。 Cargoについて CargoはRustのビルドツール兼パッケージマネージャです。Cargoを含むRustの様々なツールチェインは、Rustインストーラおよびバージョン管理ツールであるRustupを使うことでインストールできます。 はじめに - Rustプログラミング言語 cargo コマンドによって、新規パッケージの作成、ビルド、コードフォーマット、テストなどができます。 Cargo.toml でパッケージの情報を管理します。ここにはパッケージの基本的な情報や、依存関係などを記述します。 Cargo.toml は丁度、Rubyなら Gemfile に、Goなら Gomfile に相当します。 Hello, Cargo! - The Rust Programming Language 日本語版 Cargoはその存在だけでもRustの採用を勧めたくなる程に強力なツールです。「Rustは豊かな型システムと所有権モデルによる強力なメモリ安全性を備える」という謳い文句は多くの方が聞いたことがあると思いますが、これに加えてCargo(と crates.io )の存在が、RustにnpmやRubyGemsの様なパッケージマネジメントシステム、エコシステムを標準で付加します。 crates.io: Rust Package Registry さらに、サブコマンド fmt で呼び出すことのできるフォーマッタであるRustfmtの強力さも特筆に値します。Rustはその性質上しばしばC++と比較されますが、C++でコードフォーマッタを利用した結果、悲惨なことになった経験がある方は少なくないと思います。況や最新のC++をや、といったところです。 rust-lang/rustfmt: Format Rust code RustにはC++の様な「バーリトゥード、何でもありの強力さ」は(意図的に制限されていることもあり、基本的には)ありませんが、Mozillaが主導となって旗を振りながら、標準化とツールチェインの開発を、それも非常にオープンな形で進めているおかげで足並みが揃ってる、と言えます。 ワークスペース、クレート、そしてOrphan Rule 閑話休題。 弊社では実際のプロダクト開発でRustを利用しています。その過程で段々とパッケージが肥大化していき、見通しが悪くなってきたため、複数のクレート(Rustにおけるコンパイル単位)に分割することにしました。 Cargoの機能の一つである「ワークスペース」を利用することで、一つのプロジェクトを複数のパッケージ、クレートに分割することができます。 Cargoのワークスペース - The Rust Programming Language 日本語版 その過程で、以下のようなエラーに遭遇しました。 Compiling y v0.1.0 (/home/agate-pris/aaa/y) error[E0117]: only traits defined in the current crate can be implemented for arbitrary types --> y/src/lib.rs:1:1 | 1 | impl From<i32> for x::Foo {} | ^^^^^---------^^^^^------ | | | | | | | `Foo` is not defined in the current crate | | `i32` is not defined in the current crate | impl doesn't use only types from inside the current crate | = note: define and implement a trait or new type instead error: aborting due to previous error For more information about this error, try `rustc --explain E0117`. error: could not compile `y` To learn more, run the command again with --verbose. 実際のコードは以下のようなものです。 impl From<i32> for x::Foo {} エラーメッセージの内容は「任意の型に対するトレイトの実装は、現在のクレート内に定義されたトレイトに対してのみ、これを許可する」というものです。 トレイトは、それをある方に対して実装することで型の振る舞いを定義し、ジェネリックプログラミングの手段を提供します。 トレイトオブジェクトで異なる型の値を許容する - The Rust Programming Language 日本語版 上記のエラーメッセージを素直に解釈すると、 From が y にないためエラーになっている、ということになります。これは間違いではなく、実際に y にユーザがトレイトを定義したのであれば、 x::Foo に対するその実装を y の中に書くことはできます。しかし、 From は標準ライブラリが提供しているトレイトなので、この方法では解決できません(し、解決方法としても誤っています)。ここでは x 側に Foo に対する From の実装を書くのが正解です。 この制限は、ワークスペースではなく、外部クレートの場合でも同じです。ワークスペース内か否かに関わらず、「あるクレートの中で、異なるクレートが提供する型に対する異なるクレートが提供するトレイトの実装を定義することはできない」ということです。 これは言い換えれば「ある型に対する、あるトレイトの実装は、そのトレイトを定義するクレート自身か、その型を定義するクレート自身が定義するべきであり、異なるクレートがそれを定義することは容易にメンテナンス性を破壊してしまうため、これを許可しない」と言えます。 これは rust-lang/rust#23086 でIssue化され、 rust-lang/rfcs#1023 で取り入れられた制限です。 rfcs/1023-rebalancing-coherence.md at master · rust-lang/rfcs この制限は coherence と呼ばれるプログラムの特性の一部で、より具体的には orphan rule と呼ばれます。 トレイト:共通の振る舞いを定義する - The Rust Programming Language 日本語版 今回、社内プロダクトをワークスペース機能によって整理していった結果、上記のエラーが起きたことで「本来トレイトの実装によって解決すべきでない問題を、トレイトの実装によって解決してしまっていた」ことに気づくことができました。実際のプロダクトでは、トレイトを実装する代わりに、単なる関数の呼び出しで置き換えました。必要であれば、新しく型を定義してラップすることで解決しても良いでしょう。 Rustで強めに型をつけるPart 1: New Type Pattern | κeenのHappy Hacκing Blog Rustは豊かな型システム、強力なメモリ安全性、優れたツールチェインを備えるだけでなく、このような「言語の機能と制限によって、プログラマが誤ったコードが書くことを未然に防止し、自然により良いコードを書かせる仕組み」が随所に散りばめられています。 crates.io にも様々なパッケージが揃っており、特定の言語の特定のライブラリやフレームワークを利用する強い動機がなければ、業務用アプリケーションを書くにあたって十分におすすめできるものになっていると思います。
こんにちは。CADDi SWE の飯迫です。この記事は キャディ Advent Calendar 2020 の3日目です。昨日は、村上さんによる 「良いチーム開発」を実現するための「組織的学習」と「自己組織化」の話 でした! 今Q は Rust で Backend を書いているのですが、前Q は主に DevOps 周りの改善をしていました。そのときにやったことのひとつ「Argo Rollouts を使った Blue-Green Deployment 移行」について紹介します。 背景と概要 まず前提として、弊社の生産管理システムは GKE 上で運用しており、大まかな Tech Stack はこのようになっています。 https://caddi.tech/tech_stack 上記の Frontend/BFF/Backend は、今回移行するまでは、Deployment リソースとして管理されていました。これらは RollingUpdate でデプロイしていたため、互換性のない変更をしたとき、新旧 Pod の混在によりそこそこエラーが出てしまう問題がありました。そのため、リリースの作業時は Staging 環境で動作確認後ユーザーへ事前告知し、30 分後に本番デプロイ開始するというフローになっていました。つまり、リリー スリード タイムが 30 分無駄に長くなってしまっており、これを改善するために BlueGreen Deployment ができるようにしました。 Argo Rollouts とは Argo Rollouts is a Kubernetes controller and set of CRDs which provide advanced deployment capabilities such as blue-green, canary, canary analysis, experimentation, and progressive delivery features to Kubernetes . https://argoproj.github.io/argo-rollouts/ Argo CD で有名な Argo ファミリーのひとつで、 k8s 標準ではできない Blue-Green や Canary など高度なデプロイが簡単にできるようになります。特に、 Deployment リソースとほぼ使用感が変わらない GitOps フレンドリー というところが、一般的な k8s オペレーションと親和性が高く、導入を容易にしています。また、Blue-Green Deployment は、Service と Deployment を 2 セット用意し、Service から Deployment への向き先を変えることで実現されています。これ自体はシンプルなので自前で用意することも可能ですが、Argo Rollouts では不要になったリソースの破棄や昇格条件などの細かい設定が可能となっています。 v1 でアプリケーションが動いていると仮定して、v2 をデプロイするとまずこうなります。 自動または手動で v2 が昇格する(Active Service からの向き先が変わる)ことで、混在状態がなくデプロイでき、不要になった v1 は自動的に破棄されます。 Argo Rollouts への移行の流れ おおまかな移行の流れは下記の通りです。 k8s に Helm で argo-rollouts を追加 https://argoproj.github.io/argo-helm Service を preview 用 と active 用で2つ追加 preview 用:昇格(入れ替え)前の Rollout(ReplicaSet)を参照するために利用 active 用:実際に各アプリケーションから参照する(既存 Service を置き換えるもの) 既存の Deployment を Rollout に書き換え 詳細は後述 上記の Service と Rollout をデプロイ Service の 参照元 の向き先を新しい方に変更 不要になったリソース(参照されなくなった Service と Deployment)を削除 注意点として、Rollout の初回デプロイ時に 参照元 の向き先も変更していると、Rolloutが生成されるまでの間ダウンタイムが発生してしまいます。向き先はあとで変更しましょう。 Deployment から Rollout への書き換え例 Before apiVersion: apps/v1 kind: Deployment ~~ 中略 ~~ strategy: type: RollingUpdate rollingUpdate: maxSurge: 100% maxUnavailable: 50% After apiVersion: argoproj.io/v1alpha1 kind: Rollout ~~ 中略 ~~ strategy: blueGreen: # Name of the service that the rollout modifies as the active service. activeService: sample-service-active # Name of the service that the rollout modifies as the preview service. +optional previewService: sample-service-preview # The number of replicas to run under the preview service before the switchover. Once the rollout is resumed the new replicaset will be full scaled up before the switch occurs +optional previewReplicaCount: 1 # Indicates if the rollout should automatically promote the new ReplicaSet to the active service or enter a paused state. If not specified, the default value is true. +optional autoPromotionEnabled: true # Adds a delay before scaling down the previous replicaset. If omitted, the Rollout waits 30 seconds before scaling down the previous ReplicaSet. A minimum of 30 seconds is recommended to ensure IP table propagation across the nodes in a cluster. See https://github.com/argoproj/argo-rollouts/issues/19#issuecomment-476329960 for more information scaleDownDelaySeconds: 30 詳しくは 公式資料 に書いてありますが、Blue-Green strategy 設定はこのようになっています。 autoPromotionEnabled 自動昇格フラグ off のときは kubectrl rollouts promote [rollout name] で手動昇格できます previewReplicaCount Preview Rollout(ReplicaSet)のレプリカ数 1 にしておけばリソースの節約になります 昇格の直前に自動的にスケールしてくれます scaleDownDelaySeconds 昇格後、不要になった Rollout(ReplicaSet)を破棄するまでの時間 最小 30sec 推奨 Fast Rollback に関係する設定 https://argoproj.github.io/argo-rollouts/FAQ/#how-does-bluegreen-rollback-work 昇格後、不要になった Rollout(ReplicaSet)が残っている状態で、ひとつ前の Rollout manifest を反映するとすぐに戻すことができます Argo Rollouts コマンド 実際そんなに使う機会はないのですが、Argo Rollouts Plugin のコマンドをいくつか紹介します。 準備 Kubectl Plugin Installation のインストール https://argoproj.github.io/argo-rollouts/installation/#kubectl-plugin-installation 一覧の参照 % kubectl get rollouts NAME DESIRED CURRENT UP-TO-DATE AVAILABLE sample-rollout 1 1 1 1 特定 Rollout の状態取得 % kubectl argo rollouts get ro sample-rollout Name: sample-rollout Namespace: default Status: ✔ Healthy Strategy: BlueGreen Images: gcr.io/sample-dev/sample-image:v1.0.26 (active) Replicas: Desired: 1 Current: 1 Updated: 1 Ready: 1 Available: 1 NAME KIND STATUS AGE INFO ⟳ sample-rollout Rollout ✔ Healthy 26h ├──# revision:14 │ └──⧉ sample-rollout-5bfcfbd578 ReplicaSet ✔ Healthy 130m active │ └──□ sample-rollout-5bfcfbd578-h6dh5 Pod ✔ Running 130m ready:2/2 ├──# revision:13 │ └──⧉ sample-rollout-845f687b6 ReplicaSet • ScaledDown 149m その他 まだ試していないのですが、 kubectl argo rollouts undo が最近追加されたようです https://github.com/argoproj/argo-rollouts/pull/812 運用小ネタ 昇格したかどうかを確認する kubectl apply されてから、実際に 参照元 に反映される(昇格される)まではタイムラグがあります。正確に preview Service や active Service が切り替わったタイミングを外から観測できるようにするため、Datadog logs に SavedView を用意しています。(諸事情あってまだ Argo CD や k8s view tool は入れていません) time="2020-11-27T04:46:53Z" level=info msg="Switched selector for service 'sample-service-active' to value '764cd777bd'" namespace=sample rollout=sample-bff-rollout 特定 Rollout の再起動 Config の修正だけを反映するため、特定の Rollout を再起動したいときがあります。しかし、 kubectl-argo-rollouts restart コマンドは、指定されている strategy (Blue-Green)とは関係なく Pod を再起動するだけです。せっかく Blue-Green Deployment を導入しているので、それを使って再起動相当のこと(つまり、同じ Image Version で新しい Pod 群をつくって入れ替え)をしてほしいのですが、Argo Rollouts コマンドではいまのところできません。ユーザー影響の少ないときは移行前と同様に Pod の再起動をしています。どうしてもダウンタイムが許容できない場面では、 ワークアラウンド として、Rollout の適当な annotations value を書き換えることで通常のフローで Blue-Green Deployment するようにしています。 移行後 移行後は、ユーザーへの価値提供が早くなったと同時に、開発チームが他のことに時間を割きやすくなりました。 リリース回数 * 30min * 人数 なので、なかなか コスパ のよい改善だったと思います。(もちろん移行前は何もせずに待っていたというわけではないですが) 本番リリースの予告はやめ、事後に自動通知 無駄な待ち時間がなくなり、リリー スリード タイムが短縮 わかっている課題 それほど大きなものではありませんが、下記のような課題が残っています。 BFF からの gRPC 接続が Cache されているので、Rollout の昇格と同時には切り替わらない 暫定的に scaleDownDelaySeconds を短くすることで強制的に再接続させている preview Service で 結合テスト することができない 現状、自動昇格を採用しているが、将来的に手動昇格にして preview Service で事前に動作確認できたほうがよい これをやるためには preview Service 同士をつなぎ合わせる変更やルートの設計が必要 自動昇格の優先順が指定できない Service 間には依存関係があるので、Backend -> BFF -> Frontend の順に昇格させたい 参考 https://speakerdeck.com/hi1280/amazon-eks-know-how?slide=32 おわりに あまりにもあっさりできてしまうため、ほとんど書くことがなかったのですが、Argo Rollouts で簡単に Blue-Green Deployment できるということが伝われば幸いです。もし間違いやもっといい方法があれば教えていただけると助かります。また、CADDi では SRE/技術基盤エンジニアも募集しています。まだまだたくさんやるべきことがあるので、少しでも興味がありましたら Twitter からでも採用ページからでも気軽にご連絡ください! https://caddi-careers.studio.site/jobs-tech-sre 明日の CADDi Advent Calendar は、agate-pris さんによる「Rustを採用している社内プロダクトの リファクタリング をしたときの話」です。
こんにちは。CADDi でエンジニアリングマネジメントをしている村上です。 この記事は CADDi Advent Calendar 2日目の記事です。昨日は、いなむさんによる C++20 Approach to Units of Measurement でした! キャディでは、メンバーが持ち回りで発表する勉強会「STUDDi」と、特定領域について講師役を設定してシリーズ物でレクチャーを行う「STUDDi ゼミ」の2種類の勉強会をそれぞれ週1回のペースで行っています。 これまで「インフラ知識」や「ドメイン駆動設計」について扱ってきた「STUDDi ゼミ」ですが、私が9月に4回に渡って紹介したのが 「良いチーム開発を実現するためのアジャイル」 というテーマでした。その内容の一部として、 より良い "チーム開発" を実現するために必要な「組織的な学習」と「自己組織化」 について話したことをダイジェストでまとめようかと思います。 採用サイトや Wantedly の求人としては出していませんが、 アジャイルコーチ的な立場で開発チームを改善するロールの方のジョインも心待ちにしています! チームで学習すること、学習フレーム、心理的安全性 アジャイルソフトウェア開発宣言 の冒頭には 私たちは、ソフトウェア開発の実践あるいは実践を手助けをする活動を通じて、よりよい開発方法を見つけだそうとしている。 という言葉があります。「ソフトウェアのよりよい開発方法」を導くために、自らの実践や他者の実践の手助けの経験を重視するこの態度は、「 経験主義 」と呼ばれます。 「アジャイルなソフトウェア開発プロセスのひとつ」と説明されることの多い スクラム では、「 透明性 」「 検査 」「 適応 」という「3つの柱」を重視していますが、これも「チームの経験から知識を得て、起きたことや経験したことの観察に基づいて意思決定を下す」という経験主義を支えるためのものです。 『 チームが機能するとはどういうことか 』という書籍では、簡単には因果関係を突き止めることの出来ない複雑な問題解決に臨むチームを成功に導く鍵は 「仕事を学習の機会と捉えるか」だと紹介されています 。(このような態度は「学習フレーム」と紹介されており、真反対の態度が「実行フレーム」と呼ばれています。) これも一種の「経験主義」と言えましょう。 このような組織ぐるみでの「経験に基づいた学習」を促進するために重要となるのが「 心理的安全性 」です。 「心理的安全性」とは「 厳しいフィードバックを与えたり、真実を避けずに難しい話し合いをしたりできる 」状況や、「 何かミスをしても、そのために他の人から罰せられたり評価を下げられたりすることはないと思える 」状況を指します。 成長の材料となるはずの「失敗」から目を背けたくなるのは人間の直感的な心理でしょう。その失敗を直視するために必要なのが「心理的安全性」です。 今や広く知られる言葉となったこの言葉ですが、一方で曲解されるケースも目立つと感じています。「心理的安全性」を、単純な「居心地の良さ」や、「何を言っても許される雰囲気」を指すと言われる状況はしばしば見かけます。チームリーダーは、心理的安全性の担保を目的とするのではなく、それによって チームに学習フレームを植え付け、チームがイテレーションごとに強くなってゆくことを目指すという「本来の目的」を見失わないようにしたい ものです。 自己組織化に至るまでの「3つのモード」 チームの文化を実行フレームから学習フレームに移行させる ("リフレーミング" と言います) ことや、それ以外にもチームが「何とか上手く回っている」状況を実現し、維持することがチームリーダーの大きな仕事です。スクラムというフレームワークで言えば、スクラムマスターという役割が主に気を配ることになるでしょう。それでは、 チームはいつまでも チームリーダーやスクラムマスターによるケアやフォローを必要とし続けるのか? そこで重要になるのが、チームの「自己組織化」です。 チームリーダーは、いつまでも自分に依存するチームではなく、自分の元からいつでも巣立ってゆける「自己組織化されたチーム」を作るべきでしょう。『 The Great ScrumMaster 』(邦訳: 『 SCRUMMASTER THE BOOK 』)にも、「自己組織化されたチームの構築に努め、自己組織化を、組織のあらゆる階層において重要な原則にする」ことがスクラムマスターの目標であり責任である、と書かれています。 自己組織化されたチームを目指す上でひとつの指針になるのが『 Elastic Leadership 』という書籍にて紹介されいる、「 チームの3つのモード 」という考え方です。 ※ 書籍『エラスティックリーダーシップ』を参考に筆者が作成 自己組織化されたチームの実現への道は「サバイバルモード」から一足とびには叶わず、まず "火の着いた締切による炎上や難局" を切り抜け、チームを「学習モード」へと遷移させる必要がある、というのが「3つのモード」の特徴です。ここらへんの話は『チームが機能するとはどういうことか』の内容とも通づるところもあり、納得感があります。 GROW モデルと5つのツール スクラムマスターとしてチームの自己組織化を促したり、もしくはメンターとしてジュニアメンバーの成長に貢献したりする際にヒントになるのが 「GROWモデル」です。これは筆者自身が過去に Ebacky による 認定スクラムマスター研修 を受講した際に習った話です。 「誰か」(個人だったりチームだったり) がどこかに向かうとき、まず「 R eality: 現実」を確認した上で「目標: G oal」を定め、そこに至る「選択肢: O ption」を洗い出してから、どの選択肢を「意思: W ill」をもって決める、という枠組みが GROW モデルが示すものです。そんな「誰か」を手助けするときに有効なアプローチが5つ存在し、状況に応じて適切なアプローチを選択する必要があります。 Situationing: 本人の現状を整理し、以下に述べる他の4つのアプローチのどれを適用するべきかを判断する Teaching: 本人に分野や概念の存在をインプットする。知識を一から十まで教えるというより、「より深く調べたい、勉強したい」と思わせるためにインパクトを残すイメージ。 Coaching: 本人の中に潜在しているが自覚していない目標や答えを、問いを通して本人から引き出すことで明らかにする。 Mentoring: 本人にとって明確なゴールへの進み方に迷っている状況で、選択肢を提示して本人に選ばせる。 Facilitating: 「促進する」。脇道に逸れないようにする。現状から目的までの道のりから「脇道」に出た行動を軌道修正させる。 キャディではこの1年間どうだったの? 私自身のキャディでの仕事のひとつは、"klein" と呼ばれるサプライチェーン管理システムの開発・保守・運用を行うチームのコーチングを行うことでした。私たちのチームは「理想的なチーム」と呼ぶには道半ばだと思っていますが、 この記事で言及した概念を STUDDi ゼミにて紹介した9月から現在までの短い期間でも、チームの良い変化をいくつか感じることがあります 。 チームとしての学習 スクラムの定義するイベントの中で、レトロスペクティブ (振り返り) はチームの成長のための戦略を考える重要な場です。 私が klein チームに加入してから、様々な振り返りのフレームワークを紹介したり実践したりしてきましたが、 「振り返りを何故やるか」の理解がチームで共有できたタイミングでクオリティーがぐっと上がった実感 があります。 最近はホワイトボードアプリの miro 上に「klein チームの振り返りボード」を用意して、毎週の振り返りディスカッションログを残していますが、振り返りが「前回の振り返りからチームはどう変わったか」を積み重ねるような進み方をするように、自ずと変化していきました。 スプリント計画における自己組織化 私が加入した頃の klein は、 "誰が" "どの" プロダクトバックログアイテム (PBI) に取り組むかのレベルまで、プロダクトマネージャー (PdM) がケアしている光景がありました。一方で現在では、 チームの開発者たちがスプリントゴールの妥当さや、その達成に向けた作戦を主体的に決めるようになりました 。まさしく「自分たちのするべきことを自分たちで決める」という ”自己組織化された” 姿です。 特に顕著だったのがサブチームの流動化です。klein チームは、ある時期より 3~5人程度の固定化された2つの小規模サブチームで構成されていましたが、振り返りミーティングでの議論を通じ、スプリントごとにスプリントゴールやその達成方法に応じてサブチームをスプリントごとに形成する、という運用になりました。 メンター制度で他者の成長に貢献する風土 キャディのソフトウェアエンジニア職の採用では、経験のない技術要素でもなるべく早く実戦で成果を出すことができるような学習力や自走力を重視しています。しかし、メンバー間でのスキル差は存在するし、他のメンバーと比べて何らかの側面で能力がビハインドしているメンバーには、より一層の成長の機会を提供することが必要です。 このような状況で、キャディのテクノロジーチームでは, 必要な部分から少しずつ メンター・メンティーの関係を構築し始めて おり、またメンター同士の情報交換として隔週30分のフリーディスカッションをする時間を設けています。メンター側も迷いを持つこともある中で、 メンタリング/ティーチング/コーチング/ファシリテーション というアプローチのバリエーションを知っていることで、メンターとしての働きについて客観的な視点を持って振り返ったり議論ができているのではないかと思います。 理解は容易、習得は困難 さて、こういった概念や態度をキャディのエンジニア全員が完全に身につけたのか、と問われれば、答えは「No」だと思います。スクラムについてスクラムガイドが言っていたように、 このような姿勢や考え方は「理解は容易」だったとしても「習得は困難」 なものでしょう (2020年のスクラムガイド改定にて、この文言は削除されたようですが)。私自身だって、知識は頭の中にインデクスとしてはあるけど、それを日々の仕事で体現できているかというと、自信が持てないことも多々あります。 そもそも、「自己組織化」という概念に「完成」は存在しない とも思います。 では、そんな「困難な習得」に攻略法はあるのでしょうか?成功が約束された方程式は無くとも、近道は存在するのではないかと思います。それは 「同じ意識を持ったチームで仕事をする」こと です。 漫然とタスクをこなすチームになるのではなく、常に「自分たちは正しく問題にアプローチしているか」「チームでの学習を促すために自分はどう振る舞うべきか」という視点をチームで共有し、メンバー間の相互作用でチームを強くすることが一番の近道 なのだと、この2ヶ月のチームの成長を目の当たりにして改めて思う次第です。 ぜひ、そんなチームにジョインしませんか?こんな風にチームの成長をサポートするアジャイルコーチを付けたいけど付けられてないチーム、まだまだあります。 ご連絡はこちらからどうぞ 明日の CADDi Advent Calendar は、飯迫さんによる Argo Rollouts の話です。
CADDi で アルゴリズム 系の開発を担当している寺田です。ぜひ一緒に アルゴリズム 開発に携わってくれる仲間を募集したいと思い、筆を執りました。通常の Tech Blog よりPR色の強い内容となりますことをご容赦ください。 そこに課題があるから アルゴリズム が好きな方であれば、 「解くに値する面白い課題」 を欲しているのではないでしょうか。 教科書を読めば、様々な アルゴリズム の技法を学ぶことが出来ます。その技法を使えばどんな問題が解けるかも分かるし、身近なライブラリの背後で使われている理論的背景を知ることが出来ます。 しかし、教科書の例にドンピシャに当てはまる仕事に巡り会う機会はそうあるものではありません。多くの アルゴリズム は既にライブラリとして 実装済 みであり、自分で実装する機会は多くありません。また、現実の問題は教科書のように整理や定式化がされていませんし、場合によっては問題と認識すらされていません。したがって アルゴリズム を活かすには、まず現実を「観察」し、そこから問題を「発見」し、それを アルゴリズム の問題として「定式化」していく必要があります。 こういった機会を得るためには、 問題が顕在もしくは潜在する「現実」 の近くに身を置く必要があります。加えて、 アルゴリズム という選択肢に 戦略的価値 を見出す「組織文化」 が必要です。 この両方が、CADDi にはあります。 調達業務の現実 まず「現実」について。 CADDi は 多品種少量生産 の製造業における受発注プラットフォームです。ただし、受発注を結びつけるだけのマッチングサービスとは違います。 CADDi は仲介業者ではなく、CADDi 自身が受注し、製造を請け負っています。 実際の製造はパートナー企業にご協力を仰いでいますが、品質や納期は CADDi 自身が責任を負って受注するモデルとなっています。 これが意味するところは、問題がゴロゴロしている「現実」と常に隣り合わせであるということです。他品種少量生産の調達業務には、多くの非効率と苦しみが横たわっています。この問題を解くのが CADDi に課せられた使命です。この 調達業務に CADDi が自ら身を置く ことで、突き刺すような現実のペインに晒されることになり、これが問題解決への強烈なモチベーションにつながっています。 困難の一端を紹介します。 まず、ほとんどの案件において、発注部品の仕様は 2次元の図面で届きます 。図面は、DXF のようなCADデータですらありません。画像なのです。それも、紙に印刷されたものをスキャナで取り込んだ画像です。こういった図面が、ひとつのPDFファイルにまとめて送られてきます。大型案件では、PDFは1000ページを超えることもあります。 これを聞くと驚かれるかもしれません。製造業は今や 3D CAD が当たり前であり、3D データを末端まで流通させて効率化していると思われている方も多いしょう。そして実際、自動車・航空・電機、その他多くの業界で 3D CAD は使われています。しかし、「 多品種少量生産 」の「調達」という業務においては、3D CAD のデータが流通することは多くありません。様々な理由が考えられますが、3D CAD には寸法交差や表面粗さといった加工に必要な情報が乗せられない(乗せにくい)ことが理由のひとつかもしれません。 ともかく、これが現実です。 私たちは図面(画像)と向き合わなくてはなりません。ここに多くの「解くべき課題」が横たわっています。 図面には大抵、「図番」という(一意に見えて実は一意ではない)番号が振られており、図面に右下にある表に記載されています。しかし、繰り返しますが、これは画像であってテキスト情報ではありません。従って、1000ページに及ぶPDFから図面を探したくとも、図番で検索することが出来ません。何らかの工夫がなければ、取引先からの問い合わせのたびに目で探すことになってしまいます。(弊社ではかねてより管理シートから該当図面のページ番号を辿れる体制を作っており、最近では図面管理用の社内システムもローンチされました。) 加えて、図面はたびたび「差し替え」が発生します。なんらかのミスの修正や、設計変更が生じうるからです。こういった差し替えが生じると、修正は軽微だとしても、図面の管理コストは跳ね上がります。「xyz.pdf に格納された1000枚の図面のうち、図番 AB-123-345 と図番 CD-987-654 だけは xyz2.pdf の図面を使用する」といったオペレーションは煩雑で、ミスを誘発します。また、差し替え前後の図面はよく似ていますから、その違いを漏れなく読み取るにも注意が必要になります。ミスは不良の発生に直結しますから、影響は甚大です。 問題は複雑であり、複合的であり、単一の施策で全てが解決するわけではありません。ある種の問題は管理システムの構築がソリューションになります。既にテックチームでは図面を管理するためのシステムを社内リリースしており、社内の図面管理体制に変革を起こしつつあります。しかし別種の問題は、Webとデータベースだけでは解決できません。 アルゴリズム が必要だ 、と私たちは考えています。 Orama プロジェクト 弊社では今、図面の画像処理 アルゴリズム を開発するプロジェクトが始動しています。そのプロジェクトは "Orama" と 命名 されました。Orama とは 古代ギリシア 語で「視覚」を意味する語で、panorama の語源となった言葉です。図面管理システムに図面を読み取る「視覚」を授けることが、このプロジェクトの目的です。 課題のひとつは、 図番の自動検出 です。基本的には、 OCR でテキストを検出します。しかし検証の結果、図面をそのまま OCR に渡しても実用に耐えるテキスト検出は望めないことが分かりました。図番は右下の表に書かれていますが、表の罫線が OCR の読み取りを邪魔するようです。従って、まず 外枠や罫線を自動認識 し、これを消去した上で OCR に入力する必要があります。図番が書かれたセルを特定する必要もありますので、その意味でも表の認識は必要です。 下図が、現在得られている成果です。左の入力画像から表の罫線を ベクター データとして検出し、表のセルを認識することに成功しました。(右の画像は SVG 形式でエクスポートされたものです) 他にも 差し替え図面との差分検出 など、さまざまな課題に取り組んでいます。 プログラミング言語 は、主に Python と Rust を使用しています。 Python は OpenCV との親和性がよく知られていますが、Rust は目新しさがあるかもしれません。 Rust は安全性とスピードを兼ね備えているので、パフォーマンスが求められる アルゴリズム の開発には本当に適している と実感しています。 Python + OpenCV だけで賄えない理由は、 ベクター データの処理が必要になるからです。上で紹介した表の罫線認識では、まず画像を ベクター データに変換してから様々な アルゴリズム を組み込んでいます。 ベクター 化 アルゴリズム を含めた各種の アルゴリズム の実装に、Rust が活躍しています。罫線の認識処理は、画像ファイルの読込からセル情報( JSON )のエクスポートまで 200〜400ms ほどで処理が完了します。 アルゴリズム の喜び 上で紹介したテーブル認識処理がうまく動いたときはとても嬉しくて、出力結果を眺めてはニンマリしていました。ちょっと気持ち悪い人ですね。 自分が試行錯誤した アルゴリズム がうまく動いたときの喜び は、格別なものがあります。時には、まるで アルゴリズム が知性を得たかのように思えて、作った本人なのに「どうしてこんなにうまく動くのだろう」と不思議になることがあります。特に、ごくシンプルな アルゴリズム の組み合わせて驚くほど複雑な問題に対処できたとき、それを感じるようです。 再帰 的な アルゴリズム に感じることが多いかもしれません。 こういった喜びは、一部の天才だけに許されたものではありません。私は凡才に過ぎませんが、それでもごく稀にですが、この手の喜びの機会に恵まれています(その瞬間だけ、自分のことが天才だと勘違いできます)。こういった機会に巡り会うためには、自分が天才になる必要はなく、何より 「解かれるのを待っている問題」がある環境に身を置く ことだと思っています。 CADDi が向き合っている問題は、もちろんハードルは高いのですが、しかし天才でないと解けない超難問というわけではないはずです(そういう問題もあるかもしれませんが、それは天才に任せましょう)。何故それが解かれないまま残っているかというと、今まで必要に迫られたプレーヤーがいなかったからではないか、と思います。「 多品種少量生産 」の「調達」という業界で、仲介業ではなく自らが製造責任を負うビジネスモデルで、図面の管理をITの力で解決するという強いモチベーションを持ったプレーヤーが、今まであまりいなかったのではないでしょうか。 あまり前例のないポジションに身を置いていますから、解法が定まっていない問題が多く発生していますし、これからも生じ続けるでしょう。そして解決策はひとつではありません。画像処理も解決策のひとつですが、お客様からCADデータで頂ける機会を増やしていくというビジネス的な解決方法も考えられます。これが可能になれば、そのときはCADデータを扱う アルゴリズム の重要度が増すことになるでしょう。切れば血が出る活きた問題が、次から次へと供給され続ける環境であることは間違いありません。 一翼を担うために CADDi はバリューのひとつとして「一丸」を掲げていますが、半年ほど前までは胸を張って言える状況ではありませんでした。 半年前にあったこと、それは社内業務システムのリリースです。リリースに漕ぎ付ける前は、私たち Tech 組織にとってはやや苦しい期間でした。要件は膨らみがち、リリースは遅れがち、目の前の開発に忙殺される毎日ですが、しかしビジネスに何のバリューも提供できていません。ビジネスサイドから見れば、Tech は「何をしているのかよく分からない人たち」だったことでしょう。 しかし、システムをローンチしてからは状況が一変しました。「効率的になった!」という喜びの声が多くビジネスサイドから届きました。それに伴って、多くの改良要望も届きました。Tech の各チームは今までの鬱憤を晴らすように、それらの要望に迅速に応えています。こうして今、 Biz と Tech が両輪となって業務が回る ようになり、私たちはようやく「一丸」になったと感じています。Biz と Tech は、どちらが上でも下でもなく、対等に互いを尊重し合う良い関係の元で仕事が出来ています。 この流れに、 アルゴリズム も乗せたい…! と私たち Orama チームは考えています。 アルゴリズム が持つ可能性や インパク トの大きさは、社内では十分に理解されています。それと同時に、開発のハードルが高いことも知られています。私たちは アルゴリズム を可能性のまま終わらせず、きちんとビジネスに インパク トを与えるところまで作りきる必要があります。そして Biz 組織と「一丸」になって 継続的に アルゴリズム の開発に取り組んでゆく組織文化 を醸成したい、と思っています。 そのためには、今いるメンバーだけでは力不足です。もっと多くの方の知識や経験、そしてアイディアが必要です。 スキルとマインド では、どんなスキルが求められるのでしょうか? 画像処理? 機械学習 ? 数学? 現時点では、私たちは Orama プロジェクトで画像処理に取り組んでいます。しかし、事業の状況は目まぐるしく変化し、それに応じて求められる開発も変わってゆきます。半年後には全く違うプロジェクトに注力しているかもしれません。従って、画像処理を専門家をお誘いしても、その方が入社される頃にはプロジェクトの内容が変わっているかもしれないのです。入社後に「こんなはずではなかった」というミスマッチが生じるのは本意ではありません。 「画像処理」や「 機械学習 」といった特定の How に軸足を置いて、その技術の専門家として活躍したいという方は、弊社は合わない可能性があります。私たちは「 多品種少量生産 の調達」という What に軸足を置いていて、使う技術は要求に応じて柔軟に切り替えるスタイル だからです。ですから、私たちの事業ミッションに共感し、その問題解決をエキサイティングに感じる マインド をお持ちの方が、より親和性が高いと考えています。 その上で、 数学や アルゴリズム についてある程度の素養がある方、が必要です。 ここの 言語化 が難しいのですが、決して「〇〇の定理について自力で証明できるほど深く理解していること」とか「□□の アルゴリズム について実装および正確な計算量の解析ができること」といったことは必要ありません。もちろんこういった知識は持っていれば何らかの形で活かせるとは思うものの、この知識がなければ仕事が出来ないと断定できるようなものはありません。 ただ、「数式」という論理の表現形式を、(プログラム言語と同様に)言語のひとつとして読み書きする素養は必要だと考えます。例えば、 解析学 の諸定理を即答できる必要はないのですが、$\forall \epsilon>0\ \exists N\ \forall n[n> N \Rightarrow |a_n-a|<\epsilon]$ のような式を見て「ウッ…」と思考停止せずに、「どれどれ…」と読み解いていく地力は欲しいように思います。あとは、初歩的な 線形代数 についてはそれなりに理解していることが望ましいでしょう。 アルゴリズム についても同様で、もちろん初歩的なソート アルゴリズム や二分探索木などは教養として理解している必要がありますが、それ以上は「どの アルゴリズム を知っていれば良い」「これを知らないのはダメ」と断定できるものはありません。ただ、ある程度の複雑な アルゴリズム でも書籍や論文を見ながら自分で実装しきる能力だとか、それを現実の問題に適用して試行錯誤した経験だとかは、あると望ましいものです。 結局のところ、先ほど書いたことと矛盾するようですが、何らかの専門分野(とまでは行かなくとも、得意分野)をお持ちだと良いのかもしれません。その専門がドンピシャに活かせるとは限らないのですが、なんらかの得意分野をお持ちの方であれば上で述べたような素養というのは、既に培われているものと思うからです。 その一例として、手前味噌ながら自己紹介をさせてください。 私は20年近く 3D CAD やその周辺の 3D 技術で仕事をしてきた経歴で、約一年前に CADDi に入社しました。そして見積りシステムのバックエンドの開発を経て、Orama プロジェクトに配属されました。3D 関連の アルゴリズム は経験があるものの 画像処理はほとんど経験がなく 、胸のうちには一抹の不安を抱えていました。しかし始めてみれば、図面の黒い ピクセル を点群として取り出すことで、3D 計測点群を扱ってきた経験を応用できました。図面の ベクター データ変換や、その ベクター データを操作する処理においても、三角形メッシュ等を扱ってきた経験が活きました。 基礎があればなんとかなる ものだと実感しています。 ご連絡をお待ちしています ですので、 今お持ちのスキルと分野が違うからという理由で応募を躊躇う必要はありません し、「まずは軽く話を聞いてみたい」というご要望でも問題ありません。 アルゴリズム 開発の魅力は何と言っても、今までの常識をひっくり返すほどの インパク トを事業や業界に与えるかもしれない、という可能性の大きさにあります。これが達成できたとき、 アルゴリズム は事業の非連続的な成長の支えとなる でしょう。この夢を一緒に叶える仲間を募集しています。皆様からのご連絡をお待ちしております。 弊社のエンジニア採用ページへ (https://corp.caddi.jp/recruit/eng)
TL;DR monorepoは 銀の弾丸 ではない frontend / backend 両方を書く場合、GraphQL / Swagger 等を用いた開発の場合などには有用 monorepoの今 JavaScript / TypeScript におけるmonorepoは babel での利用から有名になったかと思います。 lerna の登場以降ライブラリの更新も落ち着き、導入されている企業も多いのではないでしょうか。 メリット・デメリットに関してはいくつか解説の記事があるかと思いますので、この記事ではmonorepo開発のワークフローとその際の注意点などを記載したいと思います。 monorepo開発のワークフロー Single lint, build, test and release process. Single lint linter , formatter などを共通の設定で扱うことでストレスを減らしたレビューを行うことができます husky + lint-staged の導入により、コミットファイルに統一感のあるフォーマットが適応できます https://github.com/caddijp/frontend/blob/ec53aaf7d751d9bc32c1b2ebb896a502f1a46055/package.json#L41-L52 build workspace 内にある複数のpackageを1つのrepository で操作するのに対してメリットを最大限活かすことになると、package間での依存関係が生まれます。 lerna で提供されている --include-dependencies オプションはpackage間の関係を解決するために便利です。 test and release process テストに関してですが、2種類の方法があります <rootDir>/package.json に共通の設定を定義し、テストを実行する pros 構築が容易 1つのcoverage reportを生成できる cons 複数の前提を持ったアプリのテストに適応が難しい (ex. frontend, backend) workspace ごとにテストの設定を定義し、個別の設定環境(babel ,tsconfig, etc) を前提にテストを実行する pros 個別に設定環境を定義できる cons 複数coverage reportを取り扱う必要がある codecov などの利用 https://www.npmjs.com/package/lcov-result-merger などの利用によりcoverage reportの統合が必要 リリースノートに関しては lerna-changelog を採用しています github を使ったリリース管理にはPRをベースとしたリリース管理としてちょうど良いです CADDi におけるmonorepo導入 一部のプロジェクトにて試験的にmonorepo環境を lerna + yarn workspace 機能を用いて導入しています。 GraphQL / gRPC 定義の共有 マイクロサービス内で利用される共通ライブラリの管理 型定義情報を基準に作成した factory 情報の管理 components / hooks といった汎用性の高い関数の提供 抜粋した中の一部ですが、弊社ではこれらをメンテナンスする担当者が共通であることが多いことなどから、統一した管理が行われていることにもメリットを感じています。 また、node.js では依存する dependency が多いこともあり、ライブラリのバージョン管理を行う上でも役に立つ場合があります。 これからmonorepoを始める方に npm v7よりnpm workspace が利用できるというアナウンスがありましたが、 yarn workspace や nrwl/nx , lerna/lerna を使わない標準の仕組みでmonorepoができるようになるかもしれません。 monorepo化のデメリットであるプロジェクトが肥大化しそうな場合や、複数dockerファイルを管理する方法などまだまだ改善の余地もありますが、 疎結合 な設計を行うことで OSS 公開への敷居が下がることなどメリットもあります。 便利なものを公開して世の中を良くしてゆきましょう。 Refs https://blog.spacemarket.com/code/web-frontend-repository-composition-monorepo-or-manyrepo/ https://blog.cybozu.io/entry/2020/04/21/080000 https://efcl.info/2019/01/26/monorepo-release-flow/ https://medium.com/@mattklein123/monorepos-please-dont-e9a279be011b https://medium.com/@adamhjk/monorepo-please-do-3657e08a4b70
こんにちは、キャディ株式会社の朱です! 今回は10月27日に弊社のPdM笹口( @sasaguchisan )が登壇した pmconf2020のイベントレポート をお届けします。 発表内容は「 製造業PFの立ち上げ期にPMが向き合った課題と突破口の話 」と題しまして、弊社の原価計算システム立ち上げ時にぶつかった課題とそれを解決するためにとった突破口についてお話しさせていただきました。 本レポートでは実際に発表で使われたスライドを抜粋しつつ、内容を簡単にサマリーする形でまとめようと思います。 また、大変有り難いことに 発表後の ask the speaker セッションでたくさんの質問をいただけた ので、そちらの回答についてもまとめさせていただいています。 詳細が気になった方は以下にスライドの全文が共有されているので参照いただくか、後述のリンクからぜひ カジュアル面談 を申し込みいただければ幸いです!(発表ではお伝えし切れなかったプロダクト開発・事業に関するより具体的な内容もお話しできるかと思います!) https://speakerdeck.com/sasaguchi/pmconf2020-zhi-zao-ye-pffalseli-tishang-geqi-nipmgaxiang-kihe-tutake-ti-totu-po-kou-falsehua それでは前置きが長くなりましたが、レポートの本文に参りましょう! [toc] お話しした内容 キャディが取り組んでいる課題のマクロな話 PMとして取り組んでいる課題のミクロな話 どんなPMか 慶應義塾大学法学部を卒業 新卒SIerで常駐派遣エンジニアを経験 ミスミで情シス+事業開発 Yahoo!でPMチームの立ち上げ 2019年6月にキャディへ what's CADDi? CADDiに関するよくある間違い マッチングだけではないよ → 実態は商流に深く入り込んでファブレスメーカー的な動きをしている why CADDi? 多品種少量加工品の調達難易度の高さ 発注者・加工会社双方にペインが存在 → 「 取引コストが高い 」状態 キャディの解 標準インターフェースの実装と自社見積 → 今回のテーマ データに基づいた最適な選定と確定発注 複雑な受発注及び工程管理の効率化 向き合った課題 原価計算は創業以来プロトタイプで開発が行われていた 事業成長に伴いハイペースで拡張を続けた結果、課題が顕在化 原価計算システムの役割 いつまでたっても固まらない業務要件 課題 戦略・組織が3ヶ月ごとに劇的に変化し、要件も変わる トラフィックが増え続けるのでオペレーションがボトルネックにならないことへの要請が強い 突破口 拡張性の高いドメインモデル、アーキテクチャを常に志向 → 多いときは1日に4時間以上エンジニアと議論していた 詳細なケースまで含めて徹底的に思考しきる 開発よりも早く変わっていくプロトタイプ 課題 リソース不足 チカラワザで頑張ったが追いつけず… 事業成長が止まらない プロトタイプの開発も止まらない 突破口 〇月末までプロトタイプの開発を止める、と社長と握る → 失敗 真の課題 スピード不足 とにかく早く作るという意識が先行し、保守性を犠牲に デリバリー速度がローンチ後にボトルネックになりかねなかった たまたま参加していた t_wada さんの講演のおかげで吹っ切れた 真の突破口 保守性を捨てない覚悟・定期的なリファクタリング "Shift Left"の実現と本質的なスピードの追求 ここのお話については先日投稿させていただいた Tech Blogの記事 に詳しく書かれているので、ぜひご覧ください! まとめ ask the speaker(いただいたご質問 & 回答のまとめ) Q) 要件がどんどん増えていったと話にあったが、ビジネスは上手くいっていたのか? A) 万事上手くいっていた訳ではない。もともとWebプラットフォームLikeにやっていた → 現在ではセグメントを絞って提供している、というピボットはあったりする。 Q) (↑の質問の続き) もともとは多品種少量ですらなかった? A) そこは最初から変わっていない。もともとは顧客セグメントを絞っていなかったのを絞るようになった。 Q) スピード不足について。それに気づいたときにプロダクトチーム対してどのようなアクションを取ったか A) もともとは期限意識が強かったのでウォーターフォールLikeになりがちだったが、まずはそこについてエンジニアに謝ってアジャイルにやっていこうと伝えた。社内のPJ調整は裏で握れるように動き、必要な品質面はチーム内で議論を重ねた。 Q) (↑の質問の続き) プロダクトチームからの反応は賛成だった? A) プラスに受け止められた。こういうスタンスのPMは出会ったことがないと驚かれた。 Q) PMとして発注者・メーカー双方のユーザーとどのように関わっているのか? A) 製造業という業態もあって基本的には顧客・パートナーともに営業が回るのを大切にしている。担当者の声の方が解像度が高いのでそれを参考にすることが多い。より深いヒアリングしたいときは営業に同行したりして聞いている。 Q) 過去を振り返って、事業がスケールしたきっかけ(Bizでもプロダクトでも)になった出来事などありましたら教えてください。 A) 事業的には難しい案件をやったとき。これまでの基盤ではこなせないので新規に開拓したりして一度に伸びることが多い。 Q) 規模感を知るために現在のプロダクト開発(PM、デザイナー、エンジニア、QAなどなど、、)の体制についても可能であれば教えてください。 A) Tech本部が30名くらい。PM3名、デザイナー2名、残りがエンジニア。QA専任は置いていない。 Q) 開発のリソース不足、開発止めるのも失敗した中で、どうやってリファクタリングのリソースを確保されたのか教えてもらえますでしょうか A) リファクタリング用のリソースを純増した訳ではない。sprint内の20-30%を投資すると決めた。最初はベロシティが落ちたが、1-2ヶ月経った後は明確にデリバリー速度が高まった。(例を挙げると、1ヶ月かかると言われていた規模の開発が5日で終わるようになった) Q) リソースないのになんでRustを選定したんだろう? A) もともとC++でアルゴリズム開発をしていたメンバーがRustを使って開発したいとなって選定した。アルゴリズムの開発はパフォーマンスが重要なので。Rustは経験者が少ないので最初はみんな結構苦戦している。 Q) リファクタリングする際に何か新しく計測した指標はあった? A) 特にはなかった。もともとSP(ストーリーポイント)を使った見積を行っていたのでそれは継続していた。(もともとスクラムでやっていた) Q) 今後目指されているマイルストーンやプロダクト体制など、どのように目標設定されていますか? A) 3年後にいくらという売上目標は設定している。このあと採用資料を展開するがそこに詳しく書かれているのでぜひ!今後の展望でいうとサービスラインナップの拡充は検討しており、例えばファクタリング事業などはロードマップに入っている。 Q) 最終的にはテスト作成やリファクタリングに踏み切ったとのことですが、なにかのタイミングを見計ったのでしょうか?あるいは、もうムリ!となった時点で舵取りをしたのでしょうか。 A) 明確にタイミングを見計らった訳では正直ない。たまたま参加したイベントでt_wadaさんの「質とスピード」に関する講演を聞き、もともと悩んでいたポイントだったので吹っ切ることができた。また、原価計算のプロダクトは長期的に見るとキャディにとってもコアなプロダクトになるので、デリバリー速度がボトルネックになるのは避けるべきと考えた。 おわりに 駆け足になりましたがイベントレポートは以上になります。ここまでご覧になられた皆さま、ありがとうございました! 発表でもあったようにキャディではまだまだ仲間を募集しています。 https://speakerdeck.com/caddi_eng/caddi-recruit-202008 最新の採用資料 ここに当てはまる方もそうでない方も、 幅広い職種の方々からのご応募をお待ちしております! また、 登壇者の笹口とのカジュアル面談 も可能です。発表で興味を持ってくださった方は、 下記のリンクからまずはお気軽に申し込みください! カジュアル面談リンク
キャディのテクノロジー本部でエンジニアリングマネジメントをしている むらみん ( @mura_mi ) です。 Tech Blog ではありますが、技術者をマネジメントする立場として、 最近取り組んだ 評価制度の改善 についてご紹介します。 大事な話を先に! キャディでは、各領域のソフトウェアエンジニアのみならず、 エンジニアリング・マネージャーも絶賛採用中です!! この記事で紹介しているような、エンジニアリング職メンバーの生産性やキャリア開発に直接関わるような仕事を日々頑張っています。少しでも興味をお持ちいただけたら、当ブログ右上のリンクや、Wantedly の求人記事からカジュアル面談申込みいただけると幸いです。 状況 キャディには "HELIX" という人事評価制度と, ミッショングレード制度があります。 https://twitter.com/yushirodesu1/status/1266729912192270338?s=20 これは社員数がかなり少ない時代から整備され運用されてきたものであり、この制度に基づいた四半期ごとの人事評価 (正式評価は半期ごと) はすでに1年以上運用されていました。私自身、前職のスタートアップ企業にて評価制度やグレード制度が無いまま組織が拡大したことの負の側面も体験していたので、これらの制度の存在を知った際には良いことだなと素直に感じました。 一方で、HELIX 自体は制定当時のキャディの人数構成比もあり、ビジネスメンバーの評価に重きが置かれていた節がありました。具体的なポイントは後述しますが、ソフトウェアエンジニア (以下、単純にエンジニアと呼びます) のメンバーが増える中でそのギャップを早めに埋める必要がありました。 また、エンジニアメンバーが増えていったことで、それまでの 「CTO ひとり による全エンジニアの評価」に様々な課題が生じていました 。 CTOも人間なので与えられた時間は有限であり、エンジニアのヘッドカウントが20人を越えたあたりで、「CTO ひとりが全員の自己評価に目を通し、全員のアウトプットを直接確認して評価を下す」という運用には限界が来ていました。 単純に評価対象者が増えたことだけでなく、人数が増えることで、CTOが普段の仕事を直接見る機会が少なくなったことも難しさの背景でした 。そこで、自然と、テクニカルリード的な役割を果たしているメンバーを評価者に任命し、評価という行為を組織の機能に埋め込もう、となりました。 こういった課題について CTO の小橋と私が話し始めたのが2020年6月頃で、 2020年9月の評価期に間に合うように評価制度運用の改善に着手しました。 やったこと まず、評価制度運用の改善に取り組むことをエンジニアのメンバーに伝えつつ、CTO判断で評価者となるメンバーを決めました。このときのアナウンスは社内Wiki (Docbase) に内容をまとめつつ、定例ミーティングにてスライドモードで表示しながら説明しました (以下一部抜粋)。 その後、実際に行った取り組みは、細かいものを挙げるとキリがありませんが、大きなアクションは以下の2点でした。 評価水準の適度な具体化 セルフレビューシートの改善 それぞれ詳説します。 評価水準の適度な具体化 キャディのエンジニア向けのミッショングレード (以下, MG) は、 「技術力(専門性)」「価値提供」「組織貢献」の3つの評価軸 から構成されています。(以下は社内向け資料より抜粋) しかし、 MGの評価軸と、各軸の水準に関する記述は以前より設けられていましたが、その記載内容は比較的ファジー でした。その記載を元にこれまでどのように評価を下していたかを素直に述べると、それは CTO のさじ加減でした。実際、取り組む問題が移ろうスピードも早く、エンジニアリング組織としての熟達度も高まりきっていない中、業務の中で重視されるスキルや働き方を敢えて予め定義せずに、評価を CTO の判断に任せるというのは一定の合理性がありました。 また、 「CTO が評価を一人で回すこと」の大きなメリットのひとつは、評価基準が一貫し、被評価者間での評価結果のズレが起きづらかった点 です。複数人で評価を回すことで、評価結果が評価者によって高く or 低く付くという傾向を後から調整する必要が出てきますが、予めそのブレが生じないような工夫ができるならしたいものです。 そこで、ミッショングレード水準の記述について 「評価要素の分解および具体化」と「記述の適切な詳細化」をする ことにより、評価者間での目線を揃えやすくするようにしました。 具体的には、以下のステップで評価要素の分解と詳細化を行いました。 直近の評価 (CTO一人で行ったもの) の評価結果の理由についてヒアリングする 評価要素の候補となるものを洗い出して、仮で評価要素と水準のマトリクスを作成 仮で作ったマトリクスに従って、2020年6月時点のメンバーの評価を改めて行ってもらう。 (これを "バックテスト" と呼びました) ・・・① バックテストの内容を鑑み、「実は評価には大きな意味をもたらしていない」要素のあぶり出しや、評価水準の記述の改善を行う ・・・② 上記の①→②を2回ほど繰り返して一旦確定 この MG水準 の文書化にあたっては、CircleCI の Competency Matrix が大いに参考になりました。 また、このMG水準の具体化に取り組むにあたり、以下の点に注意しました。 適度な抽象化/具体化 具体的すぎると、スキルや実績の星取表となってしまうが、ミッショングレードは従業員に期待する職責の表現であるべきであり、ある程度、マネジメントや評価者による主観やメッセージを入れられる余地を設けたい。一方で、抽象的すぎて解釈の幅が拡がりすぎてしまうことは避けたい。 過度に厳密性や客観性にこだわらない 人事評価は「最強の評価関数づくり」ではない。メンバーが評価内容や報酬に納得感を持ち、成長支援につながることがメインミッションであることを常に念頭に置く。 評価基準の「刷新」ではなく「明文化・形式知化」 これまでの評価要素を明文化して、これまで行っていた評価を CTO に過度に依存せずに行えるようにすることが目的。今回のプロセス見直しが理由で過度に「評価されるようになった」「評価されなくなった」と思うメンバーが極力出ないようにする。 セルフレビューシートの改善 HELIX では、評価期間の終わりに「セルフレビューシート」に自己評価を記入して評価者に提出し、評価者はこれをベースに評価を行います。 このレビューシートは、個人レベルまで OKR や実績数字を事細かにトラックするビジネス職メンバーの評価を主眼としたものでした。そのため、「期初に建てた評価軸ごとの目標」と「目標達成のために行ったこと」を記入するフォーマットとなっていました。 これが、エンジニアリング職のメンバーが記入するには少々不便なものでした。キャディのエンジニアの仕事には、 数字には表れない部分も含めた技術的投資の判断をすることが求められたり、計画に追従することより変化に対応したり、複雑な業務をチーム全体で理解・モデリングしながらの価値提供といった定性的な側面 が重要であると考えています。実際、期初に立てた目標の通りに行動できたかを検証するフォーマットであったが故に、 期中に生じた突発的な対応についてセルフレビューにて全く言及されない、という現象も過去の評価ではしばしば発生 していました。 そこで、エンジニア向けのセルフレビューシートのフォーマットを少々改良し、「期初に建てた評価軸ごとの目標」と「各MG評価軸についての自己評価」の間に、 「期中の主な仕事について振り返る」セクションを設けました。 これは、 いわゆる "アジャイル開発" における振り返りにおいて、アイディアを出す前にデータ (事実) を収集する ステップに似ています。 3ヶ月に渡って濃い時間過ごしていると、評価対象期間の序盤については記憶が定かではないこともよくあるので、GitHub の評価期間中の Pull Request 一覧や、Docbase (社内用wiki) に期中に書いた記事一覧を確認できるようなリンクを設けるなどの配慮をしました。 実際、メンバーの Slack times チャネルを見ていると、この変更はかなり好評だったようで、企画冥利に尽きました。実際、評価する側にとっても実際に書いたコードや Pull Request などのアウトプットを確認するポイントを絞って重点的に見ることが出来るようになり、印象での評価や「振る舞いが目立った者の勝ち」な評価を防ぐ上でも大きな助けになりました。 やってみてどうだったか これらの取り組みを経て、2020年9月末のエンジニア評価はなんとか無事完了しました。次回以降、しばらくは今の仕組みの微修正で評価プロセスを回していくことは出来そうです。 (初回特有のモタツキも次回以降改善できるはず!) また、ちょうど現在、キャディのテクノロジー本部では評価結果の被評価者へのフィードバックの真っ最中であり、実際にメンバーがこのプロセスを経験してどう感じたかの意見はヒアリングしなければなりません。 そして当然ながら、 人事評価制度の良し悪しは、短期的な視点よりも、その制度の上でメンバーが実際に成長していったかどうかが重要です 。評価自体は目的ではないことを忘れず、今後も、組織の状況を踏まえて柔軟にアクションを取ってゆく必要があるんだろうなと考えています。特に、ミッショングレード水準の記述は継続的に見直していく必要があるでしょう。 加えて、 思わぬ副作用 として現れたのは、 MG の要素分解や詳細化を行ったことで、採用面接がスムーズになったこと でした。採用面接の質問項目や、どのような側面をどの程度深堀りするかはいつも悩む難しい問題なのですが、MG表として明文化した評価要素を念頭に入れることで、社内での具体的な活躍イメージや期待を持ったり、その期待が妥当なものかを確認する面接が出来るようになってきました。これは、スクラムに於いて完了の定義 (Definition of Done) に基づいてスプリントバックログを書き出す作業に似ているな、と感じました。 というわけで 自分自身、前職にてピープルマネジメントに従事していた時期はあったものの、ここまで人事企画的な仕事に携わったのは初めての経験だったので、非常に刺激的でした。 キャディのエンジニアリング・マネジメントは、今回紹介したメンバーマネジメントのみならず、チームビルディング、プロジェクトマネジメント、開発プロセスマネジメントといった領域でその時々の課題を解決しています。 未だエンジニアリングマネージャーがいない (けど、課題が山積みな) チームもあります。興味を持った方は、ぜひカジュアルにお話できれば幸いです。 それでは。
はじめに TL;DR どんなプロダクトなの? 高度な「見積」業務を民主化する 加工をモデル化して誰でも扱えるように そして本格システム化へ… デリバリー速度改善の3STEP チカラワザ期 もともとあった仕組み プロジェクトの前提にあった不確実性 突破口となった施策 整備期 課題の分解 解消に向けた取り組み Shift Left 期 残った課題 やったこと 余談 学びの抽象化 これから 事業成長に負けない「本質的な速さ」を追求する というわけで… We are hiring ! 参考文献 はじめに はじめまして、キャディ株式会社で原価計算システム(詳細は後述)の QA 兼 PM的ロールを担当している朱( @shugenshugen )と申します。 キャディには2019年の1月にジョインし、約1年間事業開発やデータ分析基盤の整備に携わった後、プロダクトチームに転属されました。 今回お話しするのは、自分が所属しているチームにて直近半年ほどで見られた「プロダクトのデリバリー速度向上のための取り組み」についてです。 TL;DR はじめに、どんな課題があってどのように解決したのかについて簡単にまとめます。 まず、課題については明確に デリバリー速度の遅さ がありました。 (開発当初のリリース目標を複数回に渡って超過しており、チームに求められる開発スピードに追いつけていない状態でした…) これを分解すると、 テスト工程のボトルネック化 開発プロセスの非効率性 が原因として存在しました。 対してチームが採ったアプローチは大きく以下の2つです。 テスト自動化 によるボトルネックの解消 開発の 前工程への染み出し による開発プロセス効率化 取り組みの結果として、当初は 約1ヶ月かかっていた原価計算ロジックのメジャーアップデートが、約1週間で実現できるように なりました。 以降の項では、プロダクト・チームが置かれていた状況をもう少し詳細に解説しつつ、各開発フェーズでどのような取り組みを行ってデリバリー速度を改善していったかを深掘っていこうと思います。 どんなプロダクトなの? 具体的な取り組みの話に入る前に、キャディで開発している「原価計算システム」について少し解説します。 現在キャディでは製造業の受発注領域に特化して、取引を効率化するために複数のプロダクトの開発を進めています。 その内の1つが「原価計算システム」です。 (キャディの開発体制や他のプロダクトについては こちらの記事 も参考に!) 高度な「見積」業務を民主化する キャディでは「お客さまから図面をいただき、製品を製造して納品するまでの全責任を負う」というビジネスモデルを採用していますが、その取引プロセスの中で製品1つ1つに適切な価格を算出する必要があります。 そして、キャディが主に取引の対象としているのは製造業の中でも「特注品」と呼ばれるカテゴリの製品のため、製品の価格といっても一品一様であり 価格算出の難易度が非常に高い ことが特徴として挙げられます。 実際に、この「価格を算出する」業務(= 見積)は、 一般的な町工場だと社長やベテラン社員の方しか担当できない非常に専門性の高い業務 として知られています。 そこで、キャディでは ソフトウェアの力を活用して「見積」業務を民主化する ことに取り組んでいます。 加工をモデル化して誰でも扱えるように 「見積業務の民主化」をするための中心的なアプローチとして取り組んでいるのが、 加工のモデル化 です。 これだけだといまいちピンと来ない方がほとんどだと思うので、具体例を使って説明をします。 例えば、キャディで扱っている製品領域の1つに「板金加工品」というものがあります。ここで言う板金加工とは、金属の板を曲げたり溶接したりして、目的の製品の形に仕上げていく加工の種類です。 (画像は弊社 サービスページ より) さて、このようなボックスの価格はどのように計算されるのでしょうか? 加工の大まかな流れは以下の通りです。 平らな金属の板に穴を開ける(穴あけ) 板を曲げてボックス状にする(曲げ) 曲げた板同士を溶接して隙間を無くす(溶接) 製品の見栄えを良くするために色をつける(塗装) かっこ内はそれぞれ加工の 工程 と呼ばれ、各工程についていくらかかるのかを表すのが 製造原価 という概念です。 基本的にはこの原価の積み重ねで製品の価格は計算されます。 かなり単純化されたチャートですが、この製品の価格は上記のようなモデルで表現できそうです。 実際には、用いる材質や製品の形状によって工程ごとに多種多様なパラメータが存在したり、原価も段取り費用と実加工費用に分解できたりなど、より複雑な要素が価格算出には関わってきます。 加えて更に複雑性を増しているのが、 こうした加工の捉え方が製造業というドメイン内で決して統一されている訳ではない という事実です。 実際に、同じ図面について異なる町工場さんに作り方や製作にかかる費用をヒアリングしても、全く違う製作方法を前提とした金額回答が得られることが良くあります。(そしてこの捉え方の違いこそが各町工場の持つ強み弱みであり、競争力の源泉だったりします。) キャディは町工場のアグリゲーター的な存在であるという立場上、様々な強み弱みを持つ町工場に対して的外れではない製品価格の見積を行い、最適な発注するという責務を負っています。 そのためには、上記のように 業界内でも統一が図れていない幅広い加工に関する知識について、本質的な部分を抽出して繰り返しドメインモデリングを行う ことで、誰にでも使えるシステムに落とし込んでいく必要があります。 そして(まだ道半ばではありますが)キャディは、この取り組みを業界内でもほぼ唯一と言って良いほどの精度で成し遂げている存在であると考えています。 「見積」が一般的には長年の経験を必要とする非常に高度な業務であることは先述しましたが、 キャディでは入社してまず最初に受ける研修が原価計算システムを活用して「見積」が出来るようになること であり、実際に社内(ビジネス本部)のほとんどの人材が業務をこなせるようになっているほど、共通言語として浸透しています。 そして本格システム化へ… 原価計算システムはキャディのビジネスにおけるコアとも呼べる存在のため、その 構築には創業当初から長い時間をかけて投資がなされてきました 。 しかし、どんなシステムでも共通することだと思いますが、長期に渡って開発が続けられた場合には得てして負債が溜まります。 立ち上げ当初はPDCAの回しやすさを重視してなるべくライトな構成で作られていた原価計算システムのプロトタイプも、類に漏れずその道を辿ることになりました。 事業の拡大に伴って取り扱える加工の幅が広がるにつれて、初期に構築したモデルの保守性・拡張性の低さに起因する 品質保証の難しさ、保守運用の職人芸化 が顕在化してきました。 また パフォーマンスやUX面でも課題 を抱えており、システムの利用が社内のオペレーションのボトルネックとなりつつありました。 こうした課題を踏まえて、 保守性・拡張性を考慮したモデルの再設計 実行速度の速いRustによるバックエンドの書き換え UI/UXの全面的見直し などからなる 本格システム化計画 が始動し、現在の「原価計算システム」が形作られることになります。 デリバリー速度改善の3STEP 前置きが長くなってしまいましたが、ここから本題の「デリバリー速度の改善」について書いていきます。 その際に、主にテスト周りの仕組みや開発プロセスのあり方に焦点を当てて、開発期間を以下の3つに分割しようと思います。 チカラワザ期 整備期 Shift Left 期 チカラワザ期 まず最初の期間が、プロダクトの1stリリースまでの「チカラワザ期」です。 ここで生じた課題が、開発の終盤に差し掛かって テスト工程のボトルネック化 が顕在化してきたというものです。 もともとチームで設定していたリリース目標に対して、原価計算ロジックのテストにかかる工数を少なく見積もり過ぎていたことが露見し、数回に渡って目標を後倒しにせざるを得ない状況に追い込まれてしまいました。 もともとあった仕組み この時点でチームに存在していたテストの仕組みは、以下の通りです。 原価計算を担当するコンポーネント内で実装されていた単体テスト Pythonで実装された手動テストツール 両者の住み分けについて補足しておくと、前者の単体テストは工程ごとに最低限のカバレッジしか担保していなかったため、リリースに際して網羅的な品質保証の仕組みとして手動テストツールが誕生した、という経緯があります。 プロジェクトの前提にあった不確実性 これを聞くと「最初からテストの仕組みを担保した状態で開発を進めることは出来なかったの?」という至極真っ当な疑問を持たれる方がいらっしゃるかと思いますが、ここに本プロジェクトにおける最大の不確実性が潜んでいました。 それは、 新規開発を進めている最中にも、プロトタイプ側では事業領域の拡大に合わせてモデルの開発が活発に進む という無慈悲な事実です。 (実際に、開発に着手してからプロダクトの1stリリースまでに、プロトタイプ側のメジャーバージョンは20回以上アップデートされていました。) この前提は当時の事業運営上回避できないポイントだったため、新規開発側では常に要件が不安定な状態での開発を余儀なくされました。 そのため、開発初期には原価計算ロジックについてはテスト駆動開発で開発を進めようと検討がされていましたが、そもそも正となるテスト結果が変動する以上は自動テストのメンテナンスコストの方が上回ってしまうという結論に至り、テストの工程自体も開発終盤に押し込められる結果となりました。 突破口となった施策 最終的に「テスト工程のボトルネック化」という課題自体の解消は次の「整備期」を待つことになりますが、手動テストを用いたチカラワザに頼るしかなかった開発終盤を突破することになった施策があるため紹介しようと思います。 それは、 プロトタイプとの差分の許容値を定量的に定め、リリースの基準とした ことです。 具体的には、プロトタイプを用いて価格算出した過去案件の実績データを用いて、その製品価格との差分が一定の割合以内に収まっていることとしました。 これによって、それまでは工程レベルでわずかにでも価格差分が生じていればバグとして対応しなければならなかったのに対し、金額インパクトが小さいものについてはリリース後に後追いで修正する、という意思決定が容易にできるようになりました。 これが開発リソースのフォーカスに繋がり、数ヶ月遅れではありますが無事にプロダクトのリリースに至りました。 整備期 次に、先述した「テスト工程のボトルネック化」を仕組みの整備で解決しようとしたのが「整備期」です。 課題の分解 ボトルネック化において実際に生じていた課題を分解すると、以下のようになります。 単体テストの負債化 高負荷な手動テスト テストデータのアップデート工数 まず、単体テストの負債化については先にも述べたようにテスト結果そのものが不安定な状態で開発を続けていたため、プロトタイプ側で原価計算ロジックのアップデートがある度に、関連する単体テストの結果部分も書き換える必要がありました。 この書き換えの工数が地味に重く、 実装の約半分近くを単体テストのアップデートに費やしていた という声もあったほどです。 次に、手動テストの負荷については新規開発の過程でモデルの再設計を行っていたため、プロトタイプと新規開発後のモデルで工程ごとの計算結果の粒度が異なるという事象が生じました。 そのため、異なる粒度間での比較を実現するために手動テストを実行する度に手元で追加の計算をするという対応を取ることになり、 実行の負荷が非常に高い 状態でした。(関連して、新規開発側でモデルのアップデートを行った際に リグレッションテストを実行するのもほぼ不可能 になってしまい、デグレの検知が遅れて終盤の開発を圧迫する遠因にもなりました。) 最後にテストデータのアップデートについてですが、当時は手動テストの実行時にプロトタイプから製品ごとの価格の算出結果をCSVファイルとして出力して、手動テストツールに与えるような仕組みになっていました。 その際にプロトタイプのアップデートがあると膨大な数のCSVファイルも更新することになり、この テストデータの更新作業に毎回丸1日かけて人力で取り組む 必要がありました。 解消に向けた取り組み 以上のような課題の解決に向けて、チームでは大きく以下の2つのアプローチで取り組みを行いました。 保守性が低い単体テストは勇気を持って捨てる 代わりとなる自動テストの整備 1点目の単体テストの切り捨ての是非については、チーム内でも繰り返し議論があったポイントです。 実装工数を圧迫しているのは誰の目で見ても明らかだったのですが、複雑な原価計算ロジックの開発を進めるエンジニアの視点に立つと、ビルド時に合わせて実行できて結果もすぐに分かる単体テストは、やはり心強い味方でした。 それでも、今後もロジックのアップデートは定常的に行われていくという前提に立つと、これまでのように カバレッジの低い単体テストを高いメンテナンスコストを払って維持し続けることはROIが低い よねという結論に至り、勇気を持ってこれを捨てる意思決定をしました。 代わりに注力したのが、2点目で挙げている 自動テストの整備 です。 まず実行負荷が高い手動テストツールに代わる仕組みとして、工程間の価格を自動で比較できるテストツールを新規で開発しました。 その際に意識したのが、 必要であればプロトタイプ側にも開発の手を入れる という点です。それまでは目の前のユーザーに直接価値を提供するような機能にリソースをフォーカスさせる形で開発を進めていましたが、以降は たとえ直接的にはユーザー価値に寄与しない部分だとしても(そして長期的には無くなる仕組みだとしても)、デリバリー速度向上に繋がるのであれば時間をかけてでも仕組みを作った方がトータルの工数は下がるはず 、と方針を転換できたという意味で転機だったように思います。 テスト自動化の文脈でもう1つ実施したのが、 テストデータのアップデートの自動化 です。 改善としては一見些細なもので自動化自体も2日ほどで実現できたのですが、これにより作業時間を確保できるようになり、他の改善活動に時間が回せるようになったという点ではインパクトの大きい施策でした。 Shift Left 期 最後の期間が、直近まで続く「Shift Left 期」です。 「整備期」の取り組みが功を奏し、 テスト工程が開発全体のボトルネックになることはなくなりました が、デリバリー速度は依然として求められる水準に達していた訳ではありませんでした。 前提として、新規開発直後のプロダクトには原価計算ロジックをアップデートするためのインターフェースが存在していなかったため、リリース後のロジック更新はプロトタイプで一度実装したものを再度新規開発先に移植する、というフローをとる必要がありました。 結果的に、リリース後最初のメジャーアップデートに要した時間は1ヶ月を超えました。 これは以前の開発スピードからするとかなりの進歩を見せてはいたものの、ビジネス上の要請からはまだまだ遅いと言わざるを得ず、以降はデリバリー速度を更に短縮していくために 開発プロセスそのものの改善を進めていく ことになります。 残った課題 この時点で残っていた課題は ウォーターフォール的な開発プロセスの非効率性 モデルの初期設計起因の保守性の低さ の2つです。 1つ目について、原価計算ロジックのアップデートでは 要件となるプロトタイプの加工モデルの更新を行う → 新規開発先で実装が必要になる差分を洗い出しチケット化する → 実装 → テストを実施しバグを書き出す → バグ修正 & 再度のテスト実施 という一連の流れを経る必要があり、 開発プロセスの長大化によって発生するコンテキストスイッチの積み重ね や 開発アイテムの管理コスト が大きな非効率性を生んでいました。 2つ目の保守性の低さについては、新規開発のタイミングで解消し切れなかった ロジックの複雑な依存関係 や 仕様バグ が、繰り返しバグを生んだり、仕様確認の工数を肥大化させる要因になっており、ボディブローのように効いてきたタイミングでした。 やったこと 対して、行った取り組みは以下の3つです。 テスト駆動開発による開発プロセスの圧縮 モデルの設計段階からレビューに加わる 計画的なリファクタリング まず、コンテキストスイッチや開発アイテムの管理コストを最小化するために、実装の段階からあらかじめ用意されたテストケースを元に自動テストをこまめに実行して、バグが発生していた場合は合わせて修正するようにプロセスを変更しました。 このようなテスト駆動開発的な手法は、 以前に自動テストの仕組みを整備したからこそ採用できた アプローチでした。 次が、プロトタイプから新規開発先へ加工モデルを移植するプロセスにおける改善です。 ここで1点補足をすると、 プロトタイプにおける加工モデルの実装と新規開発先での実装は担当するチームが分かれている という前提があります。これは、前者のチームにはより深いドメイン知識やビジネスサイドとの折衝といった機能が求められるのに対して、後者にはRustを始めとするエンジニアリングの知識が求められるため、分業による効率性を優先した結果このような開発体制に落ち着いたという経緯があります。 この分業体制は本格システム化が実現する前は、キャディのビジネスモデルにおける要でもある加工のモデルの成長速度を落とすこと無く新規開発側の開発も並行して進めることができるというメリットがありましたが、いざプロダクトがリリースされて以降は単純に 両チーム間のコミュニケーションコスト が足かせになったり、 それぞれのチームに部分最適になった開発が進められてしまう といったデメリットの方が勝るようになってきました。 そこで、徐々に両者の組織的な融合を図っていくための第一歩として、 プロトタイプ側で加工モデルを設計するタイミングで新規開発側の開発視点から設計にフィードバックをする機会 を設けるようにしました。 これによって、仕様バグになりそうなポイントを未然に潰すことができたり、実装を同時並行で進めることで新規開発側の開発プロセスそのものを前倒したりすることも可能になってきています。 最後の改善事項として、直近では開発プロセスの改善で生まれた余剰時間を 計画的なリファクタリング に充てるようにしています。 リファクタリング自体は新規開発を進める途上でも必要に応じて行っていましたが、あくまでも個々のエンジニアが実装の片手間で行うに留まっており、 チームとして負債を計画立てて返済していくという形では実施できていませんでした 。 対して、現在では理想の加工モデルの形をチームで議論し、そこに向かってやるべきリファクタリングの内容を列挙したり、 週次のプランニングでタスクの一部として明確に組み込む など、意識的にリファクタリングを進めることができています。 以上のような改善の甲斐もあってか、直近あった原価計算ロジックのメジャーアップデートでは 実装に着手してから約1週間でアップデートを完了させることができました 。これは諸々の改善を行う前からは想像のつかないペースであり、デリバリー速度の向上として一定の成果を残せたのではないかと考えています。 余談 章題にもなっている「Shift Left(Shift left testing)」とはソフトウェア開発の文脈で、 開発ライフサイクルの終盤で実施されることが多いテスト工程をより上流の要件定義や設計の段階で行うことで、手戻りによるコストを削減したり、設計へのフィードバックを通じてソフトウェアの品質を向上させたりする活動 のことを指します。 本章で挙げたテスト駆動開発によるテスト実施タイミングの前倒しや、モデルの設計へのレビュー(静的テスト)は、この Shift Left の活動の一環と呼ぶことができるでしょう。 似たような概念として、製造業には「 フロントローディング 」という用語があります。 フロントローディングとは、製品開発のプロセスにおいて上流工程である設計の初期段階で最も作業負荷をかけることで、最終的な品質を高めたりトータルのコストを削減する取り組みです。 MONOist 「設計者がフロントローディングという怪物に立ち向かうための“3つの武器”」より これは正に Shift Left に通ずる考え方ですが、ソフトウェア開発というデジタルの世界だけでなく、実物を作るモノづくりの世界でも成立する概念なのです。 実を言うと自分は、最初は Shift Left という概念は知らずにフロントローディングという言葉を使ってこの発表をしていました。そこで、「これはソフトウェアでは Shift Left だよね」という指摘をいただいて知識が広がったという経験があったため、この項を書き加えました。 このように、 ソフトウェア開発とモノづくりという似て非なる領域の知見が Connecting the dots 的に繋がる瞬間がある のが、キャディで開発をしていて面白いところでもあります。 (ちなみに更なる余談ですが、モノづくりの世界でソフトウェア開発における「テスト」に相当するのが、CAD(Computer-Aided Design)や CAE(Computer-Aided Engineering)といった技術たちです。これらについては同じチームに専門家がいるので、そのうち解説記事を書いてくれることを密かに期待しています。) 学びの抽象化 今回の開発から得られた学びを抽象化すると、以下のようになります。 プロダクトに求められる品質を定量化し、指標とすべし 自動テストへの投資を怠るな、小さな改善を侮るな 前工程への染み出しで品質をコントロールせよ チームで改善のムーブメントを維持し続けよう 第一に、バグが一切存在しないプロダクトというものは存在しません。 片っ端からバグを潰していくのはアンチパターン です。 大切なのは、許容できるバグとそうでないバグを切り分けるための定量的な基準を定めることです。これができれば開発リソースをフォーカスさせて一時的な突破力を手に入れることができます。(あくまでも一時的なものであり、本質的な問題の解決には以降の取り組みが欠かせないという点がミソです) 第二に、自動テストへの投資は早すぎると思われるくらいのタイミングから始めるべきです。開発を振り返ると、テスト結果が常に変動するという制約があった以上、初期に自動テストの仕組みを構築しても無駄になってしまった可能性が高いとは言えそうです。しかし、少なくとも開発終盤で手動テストを実施するタイミングでは、 愚直に手を動かすよりは仕組みの構築に投資すべきだった と考えています。このような仕組みの類は 時間が経つにつれて複利で効果を発揮する ものだし、開発が長期化した際には避けられない リファクタリングにも自動テストは不可欠 です。 どうせ後でやることになる仕組みの構築は、多少は全体の進捗を止めてでも先に持ってくるべき 、というのが現時点での自分の意見です。 また、同じく仕組みの構築という文脈で、小さな効率化・自動化の力を見くびってはいけないという学びもありました。開発プロセス改善において「これをやっておけば一度に効率化が進む」といった銀の弾丸は存在せず、小さな改善の積み重ねが最終的なデリバリー速度向上に寄与するのだという肌感が得られました。ゆえに、 日々の開発の中でどこがボトルネックになっているのだろう?と考え続け、効率化・自動化できそうな部分があれば積極的に取り組んでいくことが重要なのだ と思います。 第三に、既存の枠組みに閉じた改善では早晩効率化の限界が訪れるため、プロセスの外に踏み込んだ動きを取ることも重要だということです。 実装やテストをいくら効率化しても、そもそもの要件や設計がイケていなければ理想的なデリバリーはできません 。そんなときは与えられた前提を疑い、視野を広く保って開発プロセス全体を Re-Engineering すべきです。 最後に、以上で挙げたような取り組みはプロダクト・チームが置かれている状況によって着手すべきか否かや優先順が大きく変わると思います。その見極めを行いつつも、 改善に向けたムーブメントをチームで絶えず維持し続けること が最終的な成果に繋げる上での最も重要な要素であると自分は考えています。 これから 終わりに、今後の開発で目指したいことをまとめます。 事業成長に負けない「本質的な速さ」を追求する これまでは「プロトタイプで実装された原価計算ロジックを新規開発先のプロダクトに移植するまでのリードタイム」をデリバリー速度の指標として扱っていました。 しかし、今後は新しい「原価計算システム」が正となる時代がやってきます。 目標としていたデリバリー速度の向上は一定の水準で達成できたと言えそうですが、有り難いことに事業はそれ以上のスピードで成長し続けており、 現状の原価計算システムでは価格算出できない領域が日々増えていっています 。 これに応えるためには、より本質的なデリバリー速度の改善に向けて、既存の 2つの開発チーム間の組織的な融合を進めていく ことや 開発体制のスケーラビリティを確保する ことが必須です。 というわけで… We are hiring ! カジュアル面談のお申し込みは こちら から! Twitter( @shugenshugen )のDMからもお気軽にご連絡ください! 参考文献 Shift-left testing | Wikipedia https://en.wikipedia.org/wiki/Shift-left_testing 設計者がフロントローディングという怪物に立ち向かうための“3つの武器” | MONOist https://monoist.atmarkit.co.jp/mn/articles/2003/11/news002.html
いきなり結論 仕事で docker-compose を使いたく、せっかくなのでWSL上で docker-compose しようとしたら、まったく上手くいかず大苦戦した。本記事はその格闘の記録である。 結論から述べると、WSL上でDockerを取り扱いたい場合、WSL 2、Docker for Desktop、そして Visual Studio Code の組み合わせがオススメだ。 各 コンポーネント のインストールと利用方法は以下のオフィシャルのドキュメントに任せるとして、本記事ではどのような問題が、何故発生したかについて述べる。 Using Docker in Windows for Linux Subsystem (WSL) 2 Docker Desktop WSL 2 backend Windows Subsystem for Linux (WSL) を Windows 10 にインストールする どこで躓いたのか? 問題の要旨は以下の通りだ。 Dockerは docker-ce 17.09.1 以降、 MS_SLAVE を用いる。ところがWSLは MS_SLAVE をサポートしていない。この問題を回避するためには 17.09.0 以前の docker-ce を使う必要がある。 しかしながら、新しいCompose file formatを使うためにはDocker Engine releaseも新しい必要がある。古い docker-ce をインストールし、新しいCompose file formatと組み合わせて使用した場合、クライアントのバージョンが古すぎる、と怒られる。 docker-ce をアップデートすると動かなくなる。 以下無限ループ。 というわけで、WSL上で新しいCompose file formatを使おうとすると デッドロック が発生してしまう。尚、ここで言う docker-ce とは、WSL上で apt でインストールできるパッケージを指す。 この問題を回避するために、WSL上でDockerを使う場合は Windows 上にインストールされた Docker Desktop を使用する。 この際、WSL 2を利用することは必ずしも必須ではないが、前述のDockerのオフィシャルドキュメントでも以下の様に「WSL 2、Docker Desktop、 Visual Studio Code の組み合わせを推奨する」と述べられている。 We recommend that you have your code in your default Linux distribution for the best development experience using Docker and WSL 2. After you have enabled WSL 2 on Docker Desktop, you can start working with your code inside the Linux distro and ideally with your IDE still in Windows . This workflow can be pretty straightforward if you are using VSCode . その他、参考文献 Compose file formatのバージョンとDocker Engine releaseのバージョンの対応は以下のオフィシャルドキュメントを参照。 Compose file version 3 reference MS_SLAVE は以下の記事に詳しい。 jygoro's comment from discussion "docker is running natively on wsl" WSL (Ubuntu 18. 04) にDockerを入れる - Qiita 実際に MS_SLAVE が使用されるようになった変更は以下のコミットを参照。 Use rslave instead of rprivate in chrootarchive · docker/docker-ce@6c6a182
※ STUDDi とは 表題の STUDDi とは、弊社 CADDi の Tech 本部で行われている社内勉強会の名称です。週に一回開催されており、メンバー全員の持ち回り制で何らかの技術トピックについて発表します。 去る7月7日、私が担当した回で 3D グラフィックスについて初歩的な説明をしましたので、その時の資料を公開します。 発表スライド 講師役を務めたものの、私は 3D グラフィックスの専門家というわけでは決してなく、高度なグラフィックス技術については理解できておりませんし、このスライドにも何らかの誤りが含まれている可能性は十分あります。 デモページ デモページ スライドの P21, 22, 23 あたりのシェーダーのデモです。 全く高度なデモではありませんので期待しないでください。 '1' のキーを押すとシェーダーが切り替わるようになっています。適当に作ったので雑仕様ですみません。(ブラウザに Vimium 等の プラグイン を入れているとそちらにキーイベントが取られてしまって動作しないようです。その場合は プラグイン を切ってください。) 勉強会用に強引にデモ機能をねじ込んだので、このデモ機能は将来削除してしまうかもしれません。動作しなくてもご容赦いただきたく…。
業務でRustのコードを書いていて、 rustfmt が失敗する事象に遭遇した。 少し調べたところ、 MatchArms の後にカンマを含むコメントがあると、うまく動かないことが分かった。 以下は2つの連続した改行が1つの改行に詰められることを期待したコードである。 rustfmt はマッチ式全体のフォーマットを諦めてしまう。 fn f() { let x = 0; match x { 0 => {} 1 => {} _ => {} // foo // bar, } } 尚、マッチ式の外のフォーマットは継続される。ファイル全体がフォーマットされなくなったりはしない。 おそらく、コメント中のカンマと MatchArms 中の Expression に対応するカンマの区別が出来ず、混乱していると思われる。 いくらか恐ろしいのは、 rustfmt は上記のコードについてフォーマットが失敗したことを一切エラーとして報告しないことだ。フォーマットに失敗するコードがCIの際に検知されず、デプロイまで素通りしてしまう恐れがある。 マッチ式の途中に意図的に空白だけの行を配置する等の手段でエラーとして報告させることはできる。その場合は error[internal]: left behind trailing whitespace として報告される。フォーマット後のバリデーションチェックで( rustfmt 自身の)エラーチェックを行っているものと思われる。 この事象は GitHub でIssueとして報告した。 rustfmt fail to format if there is a multi-line comment at the end of the match expression and it is terminated by a comma · Issue #4037 · rust-lang/rustfmt
平方分割の練習をしようにも難しい問題ばかり、そんなお悩みに狙いを決めて手取り足取りのレクチャーです! ちなみになのですが、「気になる💓あの子を平方分割!列の区間和を求める恋の特効薬アルゴリズムを、幸運なあなただけにご紹介です。」というタイトルは没になりました。(← これはタイトルでふざけすぎて怒られたのに懲りずにこっそり本文でサルベージする人です。) 対象の読者さん紹介です。 当記事は、平方分割にあまりなれていない方が、簡単なものであれば息を吸うようにスラスラかけるようになるのが目的です。したがってですね…… カモンです。 平方分割の実装は大変そうです。 練習のやり方がわかりません。 ノー・カモンです。 遅延評価のない平方分割なんて…… 親に重めなデータ構造を持たせるのが醍醐味ですよね〜〜 クエリ平方分割で常勝!w どのように練習するのが良いでしょう。 おすすめな練習方法です まず、だめなパターンです。 平方分割が大活躍する問題で練習します。 これはだめです。正しくはこうです。 どうにでも解けそうなものを、平方分割で解きます。 いいですか? 欲張ってはいけません。 まずは使わなくても良いところでもどんどん使って練習していきましょう。 平方根は定数です。適切な場面でだけ使うのは、なれてきてからで良いでしょう。ひたすら素振りです! おすすめの問題です。 入門におすすめなのは Range Sum Query です。実装に挑戦してみたい方は、Aizu Online Judge に練習問題( 🔗aoj-dsl-2-b ) がありますから、こちらに挑戦してみると良いです。 どうして Range Sum Query で練習するのがよいでしょう。 理由は 2 つあります。まず 1 つ目に、簡単だからです。平方分割は難しくありません。難しい問題でよく使うので、難しく感じるだけなのです。まずは簡単な問題で使ってみて、「平方分割自体は難しくない」ということを覚え、本質部分をサッとかけるようにしましょう。 2 つ目に、重なるのですが、デバッグがしやすいからです。難易度だけで言えば Range Minimum Query などでもよさそうなのですが、こちらは答えの情報量が少ないですから、デバッグがしづらいがちです。最適化よりも数え上げのほうが、嬉しいですよね。 Range Sum Query さんを平方分割です! 先程もリンクをいたしましたが、再びです。( 🔗aoj-dsl-2-b ) 概要です。長さ $N$ の列があります。リンク先の問題では、これははじめは $0$ で初期化されているのですが、そうではなく任意の初期値で考えましょう。。ここに 2 種類の クエリです。 クエリ 1: $a _ i$ を $x$ 増加させます。 クエリ 2: $a _ l + \dots + a _ { r - 1 }$ を計算です。 愚直だいすきクラブのコーナーです まずは愚直を書きましょう。無駄にはなりません。各クエリは次のように処理です。 クエリ 1 です。これは、$a _ i \leftarrow a _ i + x$ のように代入をすると良いです。 クエリ 2 です。普通は C / C++ でいう for (int i = l; i < r; i++) のようなことをするでしょうが、今回は i を使わずに書いてみましょう。 まずは新しい変数 $\mathtt{ ans }$ に $0$ を格納します。 $l$ が $r$ と異なる限り次のことを続けましょう。 俺のターン、ドローです! $\mathtt{ ans }$ に $a _ l$ を足します。そのあと $l$ を $1$ だけ増加させて、ターン終了です。(どやぁ) 図解をしましょう。これは $l = 1, r = 4$ のときの図です。それでは皆さんの両手を拝借です。いいですか? 左手を [ に、右手を [ に添えましょう。(はい?) $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 10, \ \big[ 30, \ 20, \ 50, \big[ \ 40 & ) \end{array} $$ 繰り返しの $1$ 回目が終わると、$l$ が一つ増えて、もともと $a _ l$ だったものが答えに足されることになります。C / C++ でいうところの、 ans += a.at(++l) です。 $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 10, \ 30, \ \big[ 20, \ 50, \big[ \ 40 & ) \end{array} $$ もちろん逆からでもよいです。では先程の疑似コードとは異なりますが、ここから今度は $r$ を減少させましょう。すると、今度は新しく $a _ r$ になったものが答えにたされることになります。C / C++ でいうところの、 ans += a.at(--r) です。 $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 10, \ 30, \ \big[ 20, \big[ \ 50, \ 40 & ) \end{array} $$ 愚直イヤイヤ期のみなさんにお送りする、スンマートなアーウゴリーズンです。 数列 $a$ を、$k$ 個ごとに分割です。部分和 $b$ を前計算しておくと、このようになります。しかしただ前計算するだけではいけません。クエリ 1 が来るたびに、配列 $b$ も更新していきましょう。 $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 10, \ 30, & |& 20, \ 50, & |& 40 & ) \\ b = & (& 40, & |& 70, & |& 40 & ) \end{array} $$ 難しいのはクエリ 2 です。しかしみなさんには手が 2 本ありますね。これは我々人類がうなぎよりもアルゴリズムが上手であることを示唆しています。存分に活用しましょう! 右手左手の準備はよろしいですか? まずは手の置き場所を決めましょう。数列の隙間に失礼して、求めたい区間を両手ではさみましょう。小さすぎると難しいですから、少し大きめな数列にしましょう。また、添字に言及するのも億劫ですし、値も $0, 1, 2, \dots$ にしてしまいましょう! これは、$l = 1, r = 10$ のときの図です。 $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 0, \ \big[ 1,\ 2, & |& 3, \ 4, \ 5, & |& 6, \ 7, \ 8, & | & 9, \big[ \ 10 & ) \\ b = & (& 3, & |& 12, & |& 21, & | & 19 & ) \end{array} $$ 大筋は先ほどと同じです! 左手と右手を近づけていって、ぺったんこをしたら、ゲーム終了です。みなさんの両手は、両思いです。 ただ近づけるだけではいけません。ここが平方分割のスマート・ポイントなのですが、 一気にできるところは一気に足しましょう! アルゴリズムは 3 段階に分かれます。 おそるおそる左手を近づけてポジショニングです。 おそるおそる右手を近づけてポジショニングです。 おりゃーーです。 まずは 1, 2 です。やり方は先ほどと同じなのですが、どれくらい近づけると良いのでしょう。おりゃーーーができるまで近づけましょう。これで $l = 3, r = 9, \mathtt{ ans } = 11$ です。ぐっと睨むと、$l$ と $r$ が $k = 3$ の倍数になっていることがわかります。プログラムを書くときには、これを目印にしましょう! $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 0, \ 1,\ 2, & |& \big[ 3, \ 4, \ 5 & |& 6, \ 7, \ 8, & | & \big[ 9, \ 10 & ) \\ b = & (& 3, & |& 12, & |& 21, & | & 19 & ) \end{array} $$ おたのしみ、おりゃーーのコーナーです。 疑似コードはこうです。 $l$ が $r$ と異なる限り次のことを続けましょう。 $\mathtt{ ans }$ に $b _ { l / k }$ を足します。(この添字は整数になります。) 図解です。$l$ を $3$ 増やすとこうです。$\mathtt{ ans }$ には、$3 + 4 + 5$ が足されるのですが、$b _ 1 = 12$ を見るのがスマートです。次の一手でトドメがさせるのですが、デキるアルゴリズマーは背中で語ります。最後はみなさんでどうぞです。 $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 0, \ 1,\ 2, & |& 3, \ 4, \ 5, & |& \big[ 6, \ 7, \ 8, & | & \big[ 9, \ 10 & ) \\ b = & (& 3, & |& 12, & |& 21, & | & 19 & ) \end{array} $$ ごめんな、父さんな、コーナーケースなんだ…… 今まで黙っていたのですが、先程のアルゴリズムをそのまま実装するのではだめな場合がります。第 1 段階と第 2 段階に注目です。左手と右手は、$k$ で割ったあまりだけを頼りに突き進んでしまいます。たとえばこちらの場合、$l = 7$ は $9$ まで進み、$r = 8$ は $6$ まで進み、二人の思いはすれ違いです。最悪の場合、$l$ は地平線の果てまで負い続けて、ついには……です。こういった悲しい事故をなくすためにも、$k$ で割り切れるかどうかだけでなく、$l = r$ かどうかを必ずチェックです。(画像: Flat Earth - Wikipedia ) $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 0, \ 1,\ 2, & |& 3, \ 4, \ 5, & |& 6, \ \big[7, \big[ \ 8, & | & 9, \ 10 & ) \\ b = & (& 3, & |& 12, & |& 21, & | & 19 & ) \end{array} $$ 実装です。 C++ ならば、こうです。まずはブロックサイズと、ブロックの数です。 int k = std::sqrt(n), q = (n + k - 1) / k; クエリ 2 だけで良いでしょうか。1 のほうは、背中で語ることにしようと思います。 int ans = 0; for (; l < r && l % k; ans += a.at(l++)); for (; l < r && r % k; ans += a.at(--r)); for (; l < r; ans += a.at((r-=k) / k); ご挨拶 いかがでしたでしょうか。 みなさまの平方分割ライフが、より豊かで快適なものになるよう、願っております。ツイッター・インスタグラムはこちらです。それではまたのご視聴をお待ちしております。ばいばーい👋(← これは言ってみたかっただけの人です。)