suusan2号の戯れ

SIerでインフラSE⇛WEB系でエンジニアのおっさん

#roppongijs でgRPC-WebについてLTしてきた

speakerdeck.com

gRPC-WebについてLTしてきた。 仕事では結局使わなかったんだけどgRPCをブラウザで使うにはということを色々と調べていたので、それについて話した。

suzan2go.hatenablog.com suzan2go.hatenablog.com

スライドでも触れたけど、公式?のgrpc/grpc-webはまだまだ色々と辛い感じなので、今後の発展に期待という感じである。 本当はTODOくらいVueかReactで作っておこうと思ったんだけど、公式のdockerイメージが全然ビルドできなくて断念。

github.com

gRPC-Webがまだ早いと思った人もいたみたいだけど、こっちのgRPC-Webはツールもこなれてるし全然問題ないんじゃなかろうか。

github.com

今回はフォントの話とか、Nodeの話とか、幅広く色んな話が聞けてよかった。相変わらずメルカリさんの会場、軽食ともすごくよかったー。次回も予定があえば参加したい。

Railsのコントローラでの不可解(に見える)なエラーハンドリングについて

Twitterでこのようなやりとりを見かけて、気になって眠れなかったので調べてみた。 長ったらしく説明しているので結論だけ知りたい方は下までスクロールしてください。

問題

手元ですぐ動かせる Rails5.1.1 / Ruby 2.5.0で試した。

# controller
class WelcomeController < ApplicationController
  def index
    raise ActiveRecord::RecordNotFound
  rescue => e
    raise 're raise'
  end
end

# config/route.rp
Rails.application.routes.draw do
  root to: 'welcome#index'
end

この状態でWEBにアクセスするとこうなる!!

f:id:suzan2go:20180420013817p:plain

!? raise re raise にならん・・・まじだ rescueされてないようにみえる。

ちなみにこれをコンソールでやるとこうなる

f:id:suzan2go:20180420013930p:plain

コンソールだとちゃんと “re raise” が例外になる?!

次にコントローラの例外をRecordNotFoundでなくしてみる

# controller
class WelcomeController < ApplicationController
  def index
    raise ActiveRecordError
  rescue => e
    raise 're raise'
  end
end

f:id:suzan2go:20180420014159p:plain

するとちゃんと re raise の方が例外として上がってくる…不可解

種明かし

rescueされてないよう見えるが、本当にされてないのだろうか。 binding.pry を仕掛けてみよう

class WelcomeController < ApplicationController
  def index
    raise ActiveRecord::RecordNotFound
  rescue => e
    binding.pry
    raise 're raise'
  end
end

f:id:suzan2go:20180420014538p:plain

あれ・・・ちゃんとrescueされてますね・・・ ということは、画面上の表示がおかしいのだろうか

lib/action_dispatch/middleware/debug_exceptions.rb @ line 68 ActionDispatch::DebugExceptions#call:

    57: def call(env)
    58:   request = ActionDispatch::Request.new env
    59:   _, headers, body = response = @app.call(env)
    60:
    61:   if headers["X-Cascade"] == "pass"
    62:     body.close if body.respond_to?(:close)
    63:     raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
    64:   end
    65:
    66:   response
    67: rescue Exception => exception
 => 68:   raise exception unless request.show_exceptions?
    69:   render_exception(request, exception)
    70: end

[1] pry(#<ActionDispatch::DebugExceptions>)> exception
=> #<RuntimeError: re raise>

render_exception というエラーページを生成しているところまでちゃんと re raise のエラーが渡ってきていますね。

では render_exception の中身を見ていきましょう

# actionpack-5.1.1/lib/action_dispatch/middleware/debug_exceptions.rb

      def render_exception(request, exception)
        backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
        wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
        log_error(request, wrapper)

        if request.get_header("action_dispatch.show_detailed_exceptions")
          content_type = request.formats.first

          if api_request?(content_type)
            render_for_api_request(content_type, wrapper)
          else
            render_for_browser_request(request, wrapper)
          end
        else
          raise exception
        end
      end

ExceptionWrapper.new が怪しそう。さらにこのコードを追ってみます。

# actionpack-5.1.1/lib/action_dispatch/middleware/exception_wrapper.rb

module ActionDispatch
  class ExceptionWrapper
  # 〜〜〜省略〜〜〜〜
    @@rescue_responses.merge!(
      "ActionController::RoutingError"               => :not_found,
      "AbstractController::ActionNotFound"           => :not_found,
      "ActionController::MethodNotAllowed"           => :method_not_allowed,
      "ActionController::UnknownHttpMethod"          => :method_not_allowed,
      "ActionController::NotImplemented"             => :not_implemented,
      "ActionController::UnknownFormat"              => :not_acceptable,
      "ActionController::InvalidAuthenticityToken"   => :unprocessable_entity,
      "ActionController::InvalidCrossOriginRequest"  => :unprocessable_entity,
      "ActionDispatch::Http::Parameters::ParseError" => :bad_request,
      "ActionController::BadRequest"                 => :bad_request,
      "ActionController::ParameterMissing"           => :bad_request,
      "Rack::QueryParser::ParameterTypeError"        => :bad_request,
      "Rack::QueryParser::InvalidParameterError"     => :bad_request
    )
  # 〜〜〜省略〜〜〜〜

  def initialize(backtrace_cleaner, exception)
    @backtrace_cleaner = backtrace_cleaner
    @exception = original_exception(exception)

    expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
  end

  # 〜〜〜省略〜〜〜〜

  private

      def original_exception(exception)
        if @@rescue_responses.has_key?(exception.cause.class.name)
          exception.cause
        else
          exception
        end
      end

  # 〜〜〜省略〜〜〜〜

end

読んでいくと以下のような動きになっていることがわかります。

  1. initializeoriginal_exception というのを呼ぶ、
  2. original_exception ではexception.cause で元々発生していた例外を見に行く
  3. それが rescue_responses に一致する場合には、それをExceptionWrapperexception にセットする

docs.ruby-lang.org

でも、ここには ActiveRecord::NotFound に対応するものはありません。 実はここではなくて、ActiveRecodの方で拡張が行われているようです。(コードちゃんとおったわけではないけど多分これ)

# activerecord-5.1.1/lib/active_record/railtie.rb

module ActiveRecord
  # = Active Record Railtie
  class Railtie < Rails::Railtie # :nodoc:

  # 〜〜〜省略〜〜〜〜

    config.action_dispatch.rescue_responses.merge!(
      "ActiveRecord::RecordNotFound"   => :not_found,
      "ActiveRecord::StaleObjectError" => :conflict,
      "ActiveRecord::RecordInvalid"    => :unprocessable_entity,
      "ActiveRecord::RecordNotSaved"   => :unprocessable_entity
    )

  # 〜〜〜省略〜〜〜〜
end

ここまで来てやっとわかりました。例外のその前に発生していた例外が、ActiveRecord::RecordNotFound の場合には、画面上に発生した例外そのもの(今回でいうと re raise)ではなく、元々の例外であるActiveRecord::RecordNotFound が表示される。という挙動になっているようです。

 結論

実際にTweetされてる方の環境を確認したわけではないので完全なる推測ですが、以下のような状況なのではないでしょうか

  • エラーハンドリングはきちんと行われている
  • しかし特定の例外の場合には、rescue節の例外ではなく、その前に発生していた元々の例外の情報が画面に表示される
  • 上記の挙動によりActiveRecord::RecordNotFoundrescue されていないような不可解な挙動に見えた

ちなみにこの挙動はRails3.2.0RCからあるもののようです。

Add an ExceptionWrapper that wraps an exception and provide convenien… · rails/rails@0b677b1 · GitHub

追記

#roppongijs でNuxt.js + TypeScriptの話をしてきた

speakerdeck.com

仕事で使っている Nuxt.js + TypeScriptの話をしてきた。本当はもっと話したいことあったんだけど、5分なのでTypeScriptに絞って話しをした。 第一回目の発表ということもあって結構レベルの高い発表が多かったんだけど、懇親会では何人かから質問もあったしまあまあ良かったのではと思っている。

SpringからAPIクライアントを自動生成する仕組みのところが皆さん気になっていたようで、結構Swagger定義を手で書くのしんどいっすって話が多かった。自分の例ではSpringのControllerの実装からSwaggerの定義は自動で作っているので、Swaggerは手で書いていない。手で書くのは正直結構しんどいよねーと思います。懇親会で話してたら結構 grpc-web とか grpc-gateway の話に興味を持つ人が多かったので、その辺も機会があればLTしようかな。

suzan2go.hatenablog.com

あと、フロント専任のエンジニアみたいな人が多いのかなと思いきや、割と大きい会社の人でも結構サーバーと兼任みたいな人の方が多くて意外だった。会社の規模が一定以上になると、サーバー / フロントを分けるのが普通なのかなと思ってたけどそうでもないんですね。自分としてはどっちもやっていきたいので、そういう会社が多いと嬉しいな。

メルカリさんの会場は広いし綺麗だし、ミートアップ用の冷蔵庫あったりですごかった。また4月にあるようなので是非参加したい。

本番運用まで行かなかったgRPCの知見をまとめておく

会社のブログに書こうと思ったんだけど、ちょっとマイナスイメージを持つ人もいそうな気がしたので、個人ブログに書くことにした。

この3ヶ月くらい、システムのリニューアル(アプリ間で分散したロジックを集約するバックエンドサーバと、用途に応じたフロントエンドサーバを立てるみたいなマイクロサービス構成)をやっていて、そこでサーバ間のやりとりにgRPCを使っていた。すごーく雑な絵を書くとこんな感じです。

f:id:suzan2go:20180316012243p:plain

しかし、最近になってプロジェクトのスコープについて見直しが入りました。マイクロサービス化ではなく単純にレガシーJavaで独自FWなアプリをリプレースするだけになり、必要なのはSPAとSpringBootのAPIサーバだけに(要するにRails側のロジックをなんとかするのがスコープ外になった)。

で、SPAに提供するAPIのためにgRPC(+ grpc-gateway)を使うのはちょっとオーバースペックだよねーという話になり、gRPCをやめて普通にRESTのAPIを作ることになりました。*1

プロジェクトが終わったら色々知見を公開したいなとおもったんだけど、その機会がなくなってしまい、ちょっと勿体無いのでブログにしておきます。 あくまでプロジェクト都合でgRPCを使うのをやめただけなので、何か問題があってとかではないです。その辺を期待してこの記事を開いた人はスミマセン。

このポストで話すこと

Spring BootでgRPCサーバーを作る

Javaコードを生成する

gradleのプラグインを使いましょう

github.com

なぜかドキュメントに書いてない気がするんだけど、 ./gradlew generateProto.proto からコードが生成できます。 コンパイル時に必ずこれが走るようにしておくとよいでしょう。最初は雑に生成したコードもGitの管理に含めてしまってましたが、割と差分が馬鹿にならない量になっていくので、.gitignore しておくことをオススメします。

クライアントコードを生成する

ruby / gateway用のGoコード、さらにcom.google.protobufで公開されているような Timestamp などを使おうと思うと、これらをビルドする依存関係をインストールするだけでも かなり大変になってきます。 protoeasy というツールを使うと、このあたりの生成が以下のように大分楽になります(公式のDockerImageも提供してくれています)。

protoeasy --go --grpc --go-import-path github.com/user/your-go-project --cpp --ruby .
# exclude protocol buffers files in foo/*

ただ2018年3月13日現在では公式のDockerイメージではSwagger定義を生成することができないので、独自のDocker Imageを作って対応しましょう。 --grpc-gatewa-swagger オプションはよ。

github.com

このツールを使って、コアとなるAPIサーバがmasterにマージされたら各クライアントライブラリと後述するgrpc-gatewayサーバのリポジトリを更新しにいくということをしていました。 私は大本となるgRPCのサーバで.protoファイルも管理してしまっていましたが、このように色んなものをCIでビルドしようなどと思うと結構辛くなってきそうなので、クライアントが増えてきたら.protoだけ配布して、そっちでビルドしてくれ!って世界観もありかなと思います。

.proto ファイルの依存関係を解決してくれるようなツールは標準では私が知る限りないのですが、CyberAgentの方が protodep というツールを作成されているようです。

github.com

Spring BootでgRPCを動かす

Spring BootでgRPCを動かすまでは本当に簡単です。 springboot-starter というライブラリが公開されており、ほぼそれで完結します。 こちらは過去Qiitaにも書いたので、よかったら見てみてください。

qiita.com

.protoファイルの整理について

シンプルなサーバであれば、 .proto ファイル一つで事足りるかもしれませんが、アプリケーションが大きくなってくると、 一つの .protoファイルでは見通しが悪くなってきます。

じゃあどう整理したらいいのかというところですが、私はgoogleが公開しているGCP用の .proto ファイルの構成を参考にしました。(ちなみに結構ファイルによって書き方がマチマチ…w)

github.com

例えば bigtable.proto ファイルは以下の様に3つに分割されています。 googleapis/google/bigtable/v1 at master · googleapis/googleapis · GitHub

bigtable_data.proto  -- Request / Responseで使うmessage定義
bigtable_service.proto -- Service定義
bigtable_service_messages.proto -- ServiceのRequest / Responseの定義

自分は最終的に以下のように整理しました。

src/main/proto/todo
               L todo_service.proto
               L request
                 L get_todo_request.proto
                 L post_todo_request.proto
               L response
                 L post_todo_response.proto
                 L get_todo_response.proto

認証をどのように行うか

このブログで紹介されているように、Interceptorを使ってSpring SecurityのSecurity Contextに認証情報を詰め込むという方法を取っていました。

eng.revinate.com

で、最初は割とうまく行ってたんだけど、普通に動かしてる分にはよくても、Spring Securityまわりでテストが落ちることが頻発したりとちょっと挙動が不安定に。

Spring Security allows us to specify an alternative SecurityContext store by implementing a custom SecurityContextHolderStrategy. Additionally, the gRPC Java runtime provides the Context class, which can be used to carry state across API boundaries and between threads.

と、あるように grpc-javaContext を使って無理やり実装してみたらテストの不安定さは解消されたものの、Securiy Contect の機構に頼らず、素直にgrpc-javaContext を使ったほうが良さそうだなぁという感触です。

エラーハンドリングについて

エラーハンドリングについてはこちらに書きました。

Spring BootでgRPCする - Qiita

例えばバリデーションのような詳細な情報を返したい場合にはMetadataに詰めて返すようにしていました。

grpc-javaのサンプルにもあるように、以下のようにすると良いでしょう。

https://github.com/grpc/grpc-java/blob/master/examples/src/main/java/io/grpc/examples/errorhandling/DetailErrorSample.java#L75

        Metadata trailers = new Metadata();
        trailers.put(DEBUG_INFO_TRAILER_KEY, DEBUG_INFO);
        responseObserver.onError(Status.INTERNAL.withDescription(DEBUG_DESC)
            .asRuntimeException(trailers));

gRPCをアプリケーションのどのレイヤーにおくか

クライアント、サーバで型を共有できるのが、gRPCの良いところではありますが、gRPCで生成されたクラスを所謂ドメインレイヤーまで引きずるような設計にはしないほうが無難です。

この場合gRPCのモデルからドメイン層、アプリケーション層にもってくるときのマッピングがかなり面倒になります。が、gRPCに強く依存する形でアプリケーションを作ってしまうと、今回のようにgRPCを差し替えないといけないことになったときに大変な事になります。

今回意識してgRPCにアプリケーションロジックが依存しないように作っていたので、gRPCからRESTへの書き換えも1週間程度で終わりました。

参考までに、プロジェクトの構成はざっくり以下のようにしていました。

domain/
application/
presentation/
└ grpc/
    ├── interceptor/
    └── service/
infrastructure/

grpc-gateway について

grpc-gatewayを使用して、SPA向けのRESTのエンドポイントを作成していました。grpc-gatewayのためのコード生成時にSwagger定義を同時に生成することができるので、Swagger Codegenを使ってフロントエンド用のTypeScriptクライアントも同時に生成して配布していました。

grpc-web という選択肢もあったのですが、社内にAPIアグリゲータがいるとか、パスを見てnginxでリクエストを振り分ける必要があるとか色々あり、ブラウザから叩かれるエンドポイントとしてはgrpc-gatewayを選択しました。

github.com

grpc-gatewayはエンドポイントとしてのコードを自動で生成してくれるだけなので、実際のGoのサーバーはある程度自分で書く必要があります。逆にいうと足りないものがあれば拡張してくことが可能です。

エラー情報をクライアントにJSONでいい感じに返す

grpc-gateway ではデフォルトでエラーの内容が以下のようになります。

{
    "error": "invalid token",
    "code": 16
}

上記で書いたように、 Metadata に詰めた内容も grpc-gateway でクライアントにJSONとして返したい場合には、エラーの場合の挙動をカスタマイズする必要があります。

実はこの方法は、grpc-gatewaywikiにリンクが貼られている以下のブログに書いてあります。 runtime.HTTPError にエラーハンドラーが定義されているので、これを差し替えます。

mycodesmells.com

色々端折っていますが、自分は以下のようなエラーハンドラを定義しました。

// CustomHTTPError おもにgRPCのMetadataをerrorBodyのような構造に変換し、クライアントに返すためのカスタムハンドラー
func customHTTPError(ctx context.Context, _ *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, _ *http.Request, err error) {
    const fallback = `{"error": "failed to marshal error message"}`

    w.Header().Set("Content-type", marshaler.ContentType())
    w.WriteHeader(runtime.HTTPStatusFromCode(grpc.Code(err)))

    eb := errorBody{
        Err: grpc.ErrorDesc(err),
    }

    md, _ := runtime.ServerMetadataFromContext(ctx)
    for k, v := range md.TrailerMD {
        eb.ErrorDetails = append(eb.ErrorDetails, errorDetail{
            Field:   strings.TrimSuffix(k, "-bin"), // バイナリで帰ってくる文字列は-binのprefixがつくので、クライアントが扱いやすいよう消す
            Message: string(v[0]),                  // サーバー側では文字列としてしか入れていないが、何故かArrayで入ってくるので最初のものだけ取得する
        })
    }

    jErr := json.NewEncoder(w).Encode(eb)

    if jErr != nil {
        w.Write([]byte(fallback))
    }
}

ファイルアップロード / ダウンロードする

以下のIssueにもあるように multipart form requestgrpc-gateway では使えません。

github.com

一番簡単な方法は、Base64エンコーディングして受け渡しをしてしまうことです。

ただしProtocol Buffersの説明にもあるように、Protocol BuffersではMB以上のサイズを受け渡しするのは向いていません。

https://developers.google.com/protocol-buffers/docs/techniques

手元でやったところ大体3MB以上のファイルになると、この方法ではうまく行きませんでした。この場合には、grpc-gatewayに独自のエンドポイントを設けて 一度gatewayでファイルを受けて、Client Streamingで送るとかを検討する必要があるかもしれません。※自分はそれほど大きなファイルを送らずに済んだので、試してませんが…

christina04.hatenablog.com

JSONのMarshallをカスタマイズする

grpc-gateway はデフォルトでは、Protocol Buffersでデフォルト値になっているものをレスポンスから省いてしまいます。

これはproto3では、デフォルト値なのか値がセットされていないのかを区別できないのでこのような挙動になっているようです。

github.com

この挙動は以下のIssueのコメントにもあるように WithMarshalerOption で変える事ができます。

github.com

gwmux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName: true, EmitDefaults: true}))
  • OrigName
    • デフォルトではJSONはキャメルケースになりますが、これをtrueにすると、.proto に定義されたフィールド名で出力されるようになります
  • EmitDefaults
    • proto3でデフォルト値になっているものもJSONで出力することができます。

まとめ

いまいちまとまりのないポストになってしまいましたが、プロジェクトで触った、gRPC / grpc-gateway について書きました。

個人的にはgRPCはかなり開発体験が良かったので、次に機会があればまた検討したいなと思います。

*1:GoならgRPCをwrapしてWEBでも使えるようにするgrpc-webをAPIサーバに組み込めるけど、Go以外だと何かしらのサーバ(grpc-gatewayやgrpcwebproxy)を別で立てる必要があるので、SPAのAPIのためだけにそれ立てる??みたいなところとか、インフラ構成が多少複雑になるのがネックだった

カイゼン・ジャーニーを読んだ

カイゼン・ジャーニーを読んだ。展開が熱くて2日くらいで一気に読んで、この熱量を伝えたくて会社の人に布教してしまった。

江島という主人公のストリーに沿ってスクラムアジャイルについて学べる

この本のいいところはタイトルにあるように「たった1人からはじめて」というところだと思う。現場に対して不満や変えたいことがあったとしても、それをいきなり理想形の形に持っていくのはとてもむずかしい。いきなり理想形にもっていくのは現場の社員1人では無理だと思う。それでも1人からはじめて、仲間をどのように作っていくか、そこから書かれている点がとても良いなと思った。

立ち向かう問題がなかなかリアル

自分はスクラムの経験が沢山あるわけではないけれど、それでもあーこういうことあったなぁーという問題が本書のなかでも沢山起こっていて、とてもリアルだった。個人的にはデザイナーとの協業のところは、過去に似たような経験があったのでとても学びが大きかった。当時は自分の馬力で何とかした(なってなかった説もある)けど、こういうやり方で進めればよかったなーと思うことが沢山ある。

インセプションデッキを定期的に見直す、とか「向き直り」をする、とかこれまでやったことのないアプローチもあったので真似していきたい。

いちいちセリフが熱い

「みんな、隣の芝生は青く見えている。行ってみると、実は芝生は前にいた場所から続いていて、大して変わらないことに気づくんだ」

「それで、あなたは何をしている人なんですか?」

「僕が取り組むより、部のみんなに展開したほうが良いかもしれない。…といって、また自分からは始めないのが、これまでの僕だ。」

WEB系にいる今でも震えたので、SIerにいたときなら涙がでちゃったかもしれない。

専門家はいない。私たちしかいないんだ。

これは先日あったRuby 25周年イベントのときに何かのスライドで、またエラスティックリーダーシップという本でも出てきたフレーズだ。カイゼンジャーニーに出てくるフレーズではないんだけど、サブタイトルにもなっている「越境」するチームというのは、こういうマインドを持ったチームなんじゃないかと読んでいて思った。

まとめ

現場を変えたいと思っている人にとっては、手法が参考になるのは勿論のこと、やっていこうという熱量を貰える本だと思う。オススメです!

ティール組織を読んだ

Twitterでよく見かけたので、ティール組織を読んだ。

ティール組織――マネジメントの常識を覆す次世代型組織の出現

ティール組織――マネジメントの常識を覆す次世代型組織の出現

ティール組織と呼ばれる、従来の組織形態の常識からは考えられないルールで運営され成果を出している組織形態についての話だった。ホラクラシーというと馴染みがあるかもしれない。(ホラクラシーはティール組織の形態の1つみたいな紹介のされ方がされている。)

ティール組織は以下の3つの特徴があるらしい

  • セルフマネジメント (自主経営)
  • ホールネス (全体性)
  • 存在目的

コレだけ見ると、セルフマネジメント以外はなんとなくトンデモ理論に見えるけど、読み進めていくと確かにと思うことが多かった。 1章目は若干スピリチュアルな話(にこの時点では感じること)が入ってるんだけど、2章目からは実際にティール組織で運営が行わている企業の話が続いていくので1章目は我慢して読んでいってほしい。

以下読んでいて自分が思ったこと。

性善説な組織

セルフマネジメントな組織では、従業員は基本的に善良であるという前提にたって組織運営がされている。本書で紹介されている会社では、元々備品の貸出にマネージャーの承認が必要だったり、事細かにタイムカードを付けなければいけなかったものを一切取りやめるだけでなく、目標設定や決済まで各チームに任されて運営がされていた。従業員が基本的に善良であるという前提にたつと冗長なルールや面倒な手続きのほとんどをなくすことができるようだ(だからこのような組織ではオフィス機能にあたる人の数が極端に少ない)。

個人的にも性善説にたっていない組織では、無駄なルールやとりあえずマネージャーに承認をもらうみたいなフローが多くなるという気はしていたので、これは納得だった。

こうなると気になるのは「何か不正が起こったらどうするのか?」ということだけど、本の事例だと以下のパターンだった。

  • 罪を犯した人は解雇になり社内にも統制を求めるような声があがったが、社長が中心になりセルフマネジメントで性善説にたった文化を守った
  • これまでこのような組織運営に寛容だった取締役陣が、統制を求め始めてセルフマネジメントの文化が失われてしまった

こういう文化を守っていくには、経営層にそれなりの覚悟がいりそうだなと思った。

なぜ最近のオフィスはアットホームな雰囲気のものが多いのか

これはホールネス(全体性)にかかってくる部分。そもそもホールネスってなんだよって感じだけど、本書を読んでいくと職場でも「自分らしくいられるか」ということだということが分かる。職場で「自分らしくいられる」ということの意味は、多分「自分を必要以上に大きく見せ」たり、「必要以上に卑下して見せ」る必要が無く、普段に家や友人と過ごすような気持ちで職場で過ごせるかということなんだと思う。 最近できたオフィスってカフェスペースがあったり、小上がりがあったりするのが多いのって、社員が自然体でいられるようにという狙いもあったりするのかなーと思った。

ティール組織になるとどのようなことが起こるのか

オランダのビュートゾルフというヘルスケアの非営利組織の話が特に面白い。もともとオランダの地域看護師を束ねる組織では、1看護師あたりの生産性を上げるために細かく全ての医療行為に標準時間を設定して内容を指示し、それをモニタリングするために各家庭の玄関ドアにバーコードを付けて医療行為にかかった時間を調べ、遠隔地から監視・モニタリングするということをしていた。(これだけ聞くと結構利にかなっていると思ってしまう。)

一方ビュートゾルフではこういった管理をせず、10〜12人のチームでケアサービスや、患者の受け入れ、スケジュール管理、さらには採用まで受け持つ形態を取っている。しかし結果として、ビュートゾルフの方が他の組織よりも1顧客あたりに費やした介護時間は40%近く少ない(要は効率よく医療行為ができている)というデータが出ているらしい。2006年に10人だった組織が2013年には7000人になっているという成長スピードも凄まじい。

いまの組織をティール組織にするにはどうしたらいいのか

これは結構身も蓋も無いことが書いてあって、正直だいぶ厳しい。

  • CEOがティール組織のアイディアについて個人的に素晴らしいと思ってくれているか
  • 取締役会もティール組織について理解し、支持しているか

CEOが乗り気でない場合には、自分の権力や時間を注ぐことはそれほど意味がないとまで言われてしまっている。厳しい。

まとめ

最近生産性を上げるにはもっと性善説に立たないといけないと思っていたので、どうやってセルフマネジメントな組織が実現されているのかは納得する部分が多かった。 既存の組織を末端の社員からティール組織に変革していくのは無理ゲーと書かれているわけだけど、組織がどのように変わっていくかの話など勉強になる内容が多かったし、変革までいかなくても改善していける部分もありそう。というかあってくれ頼む。

メルカリやSmartHRはかなりこのあたりうまくやってる印象あるけど、どうなんやろうか。 blog.shojimiyata.com

industry-co-creation.com

実践ドメイン駆動設計を読んだ

実践ドメイン駆動設計を読んだ

 

実践ドメイン駆動設計

実践ドメイン駆動設計

 

会社でレガシーシステムのリニューアルをやっていて、それのコアとなるAPIサーバをDDDで作りたいと思い購入した。

エバンス本は既に一度読んでいたのだけれど、いざ実務になるとこれはどこに置いた方がいいんだ?とか、どう実装するべきなんだ?みたいなのは本当によく迷う。

この本で、そのあたりクリアになるんじゃないかなという期待を持っていた。

以下、感想

自分がやっていたのは軽量DDDだった

本書ではDDDの思想を理解せずに、そのアーキテクチャの実装だけを真似ることを軽量DDDと呼んでいて、あまりオススメしないと言っている。

実際、自分もやって見て思ったんだけど、ドメインとは何だろう?という問いにちゃんと答えられないとレイヤー分けが結構難しくなってくる気がする。

ビジネスロジックドメインなんでしょ?みたいな意識だけだと、複数人の開発で意識合わせていくの辛くなるんじゃないかなと漠然と思ってたので納得。

 

DDDとは特定の技術的なアーキテクチャのことではない

これはよく公開されているスライド見ててもよく誤用されてるし実際自分も DDDでやってますみたいなこと言っちゃうんだけど、レイヤードアーキテクチャ= DDDではないという話。

特定の技術に依存するものではない、でもヘキサゴナルアーキテクチャがオススメとは本書に書かれていた

 

エンティティとは?バリューオブジェクトとは?アプリケーションサービスとは?ドメインサービスとは?

このあたりは、概念としては分かっているつもりでも、いざ実装するとこれでいいんだっけ…ってなりがち。本書ではこれらについて具体的なコード例もつけて解説してくれているので、かなり参考になる。また理想的にはこうだけど、パフォーマンス要件とかで難しい場合もあるよねーといった現実的な話もしてくれるのが良かった。

でもこれもやはり、技術的なテクニックというだけの話ではなく、ドメインから導いていくということが大切だなとおもった。(特にアプリケーションサービス、ドメインサービスは単純にビジネスロジック置き場みたいな気持ちだとごちゃごちゃになっていきそう) 

全てをバリューオブジェクトにする必要はない

例えばBooleanの値であったり自己完結した数値だったりする場合など、その値自体で意味のある全体を成している場合にはバリューオブジェクトにする必要が無いということは書いてあった。

なんとなくDDDガチ勢の人って全てをバリューオブジェクトでラップする印象があったのでちょっと意外だった。(よく考えたらそりゃそうだという話ではあるけど)

一方でIDみたいなものはどんどんバリューオブジェクト化した方が良さそうなので、自分もやって行こうと思う。

まとめ

おもっていたよりも読みやすい本だった。表紙で損してる気がするw

これを呼んでエバンス本の方をまた読み返し、またこの本を読むみたいなことをすると、理解が深まりそう。 

作者が公開しているリポジトリも参考になるのでおススメです。

GitHub - VaughnVernon/IDDD_Samples: These are the sample Bounded Contexts from the book "Implementing Domain-Driven Design" by Vaughn Vernon: http://vaughnvernon.co/?page_id=168