Railsのコントローラでの不可解(に見える)なエラーハンドリングについて
やっぱ家でもなるわ。適当なcontrollerに
— snocchy (@snocchy) 2018年4月19日
def index
raise ActiveRecord::RecordNotFound # (1)
rescue
raise 're-raise' # (2)
end
とか書いてブラウザから開くと何故か(1)で死んだことになってる。コンソールから叩くと(2)になる。raiseするのをActiveRecordErrorにすればどちらも(2)
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にアクセスするとこうなる!!
!? raise re raise
にならん・・・まじだ
rescueされてないようにみえる。
ちなみにこれをコンソールでやるとこうなる
コンソールだとちゃんと “re raise” が例外になる?!
次にコントローラの例外をRecordNotFoundでなくしてみる
# controller class WelcomeController < ApplicationController def index raise ActiveRecordError rescue => e raise 're raise' end end
するとちゃんと re raise
の方が例外として上がってくる…不可解
種明かし
rescueされてないよう見えるが、本当にされてないのだろうか。
binding.pry
を仕掛けてみよう
class WelcomeController < ApplicationController def index raise ActiveRecord::RecordNotFound rescue => e binding.pry raise 're raise' end end
あれ・・・ちゃんと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
読んでいくと以下のような動きになっていることがわかります。
initialize
でoriginal_exception
というのを呼ぶ、original_exception
ではexception.cause
で元々発生していた例外を見に行く- それが
rescue_responses
に一致する場合には、それをExceptionWrapper
のexception
にセットする
でも、ここには 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::RecordNotFound
がrescue
されていないような不可解な挙動に見えた
ちなみにこの挙動はRails3.2.0RCからあるもののようです。
追記
もともとの実装はこっちか https://t.co/DGtctOgyYz
— すーさん二号 (@suusan2go) 2018年4月20日
僕も気になったので調べてみたんですが元のチケットとコミットはこれですねhttps://t.co/yTBxoHM9f6https://t.co/lM1Y0LdoaA
— わかば (@wakaba260yen) 2018年4月20日
何らかの例外をラップした例外を返しても、大本のエラーがrescue_responseの対象として登録されてたらrescue_responseを適用するようにするようにしているみたいです
#roppongijs でNuxt.js + TypeScriptの話をしてきた
仕事で使っている Nuxt.js + TypeScriptの話をしてきた。本当はもっと話したいことあったんだけど、5分なのでTypeScriptに絞って話しをした。 第一回目の発表ということもあって結構レベルの高い発表が多かったんだけど、懇親会では何人かから質問もあったしまあまあ良かったのではと思っている。
SpringからAPIクライアントを自動生成する仕組みのところが皆さん気になっていたようで、結構Swagger定義を手で書くのしんどいっすって話が多かった。自分の例ではSpringのControllerの実装からSwaggerの定義は自動で作っているので、Swaggerは手で書いていない。手で書くのは正直結構しんどいよねーと思います。懇親会で話してたら結構 grpc-web
とか grpc-gateway
の話に興味を持つ人が多かったので、その辺も機会があればLTしようかな。
あと、フロント専任のエンジニアみたいな人が多いのかなと思いきや、割と大きい会社の人でも結構サーバーと兼任みたいな人の方が多くて意外だった。会社の規模が一定以上になると、サーバー / フロントを分けるのが普通なのかなと思ってたけどそうでもないんですね。自分としてはどっちもやっていきたいので、そういう会社が多いと嬉しいな。
メルカリさんの会場は広いし綺麗だし、ミートアップ用の冷蔵庫あったりですごかった。また4月にあるようなので是非参加したい。
本番運用まで行かなかったgRPCの知見をまとめておく
会社のブログに書こうと思ったんだけど、ちょっとマイナスイメージを持つ人もいそうな気がしたので、個人ブログに書くことにした。
この3ヶ月くらい、システムのリニューアル(アプリ間で分散したロジックを集約するバックエンドサーバと、用途に応じたフロントエンドサーバを立てるみたいなマイクロサービス構成)をやっていて、そこでサーバ間のやりとりにgRPCを使っていた。すごーく雑な絵を書くとこんな感じです。
しかし、最近になってプロジェクトのスコープについて見直しが入りました。マイクロサービス化ではなく単純にレガシーJavaで独自FWなアプリをリプレースするだけになり、必要なのはSPAとSpringBootのAPIサーバだけに(要するにRails側のロジックをなんとかするのがスコープ外になった)。
で、SPAに提供するAPIのためにgRPC(+ grpc-gateway)を使うのはちょっとオーバースペックだよねーという話になり、gRPCをやめて普通にRESTのAPIを作ることになりました。*1
プロジェクトが終わったら色々知見を公開したいなとおもったんだけど、その機会がなくなってしまい、ちょっと勿体無いのでブログにしておきます。 あくまでプロジェクト都合でgRPCを使うのをやめただけなので、何か問題があってとかではないです。その辺を期待してこの記事を開いた人はスミマセン。
このポストで話すこと
Spring BootでgRPCサーバーを作る
Javaコードを生成する
gradleのプラグインを使いましょう
なぜかドキュメントに書いてない気がするんだけど、 ./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
オプションはよ。
このツールを使って、コアとなるAPIサーバがmasterにマージされたら各クライアントライブラリと後述するgrpc-gatewayサーバのリポジトリを更新しにいくということをしていました。
私は大本となるgRPCのサーバで.proto
ファイルも管理してしまっていましたが、このように色んなものをCIでビルドしようなどと思うと結構辛くなってきそうなので、クライアントが増えてきたら.proto
だけ配布して、そっちでビルドしてくれ!って世界観もありかなと思います。
.proto
ファイルの依存関係を解決してくれるようなツールは標準では私が知る限りないのですが、CyberAgentの方が protodep
というツールを作成されているようです。
Spring BootでgRPCを動かす
Spring BootでgRPCを動かすまでは本当に簡単です。 springboot-starter
というライブラリが公開されており、ほぼそれで完結します。
こちらは過去Qiitaにも書いたので、よかったら見てみてください。
.protoファイルの整理について
シンプルなサーバであれば、 .proto
ファイル一つで事足りるかもしれませんが、アプリケーションが大きくなってくると、
一つの .proto
ファイルでは見通しが悪くなってきます。
じゃあどう整理したらいいのかというところですが、私はgoogleが公開しているGCP用の .proto
ファイルの構成を参考にしました。(ちなみに結構ファイルによって書き方がマチマチ…w)
例えば 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に認証情報を詰め込むという方法を取っていました。
で、最初は割とうまく行ってたんだけど、普通に動かしてる分にはよくても、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-java の Context
を使って無理やり実装してみたらテストの不安定さは解消されたものの、Securiy Contect
の機構に頼らず、素直にgrpc-java のContext
を使ったほうが良さそうだなぁという感触です。
エラーハンドリングについて
エラーハンドリングについてはこちらに書きました。
例えばバリデーションのような詳細な情報を返したい場合にはMetadataに詰めて返すようにしていました。
grpc-javaのサンプルにもあるように、以下のようにすると良いでしょう。
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を選択しました。
grpc-gatewayはエンドポイントとしてのコードを自動で生成してくれるだけなので、実際のGoのサーバーはある程度自分で書く必要があります。逆にいうと足りないものがあれば拡張してくことが可能です。
エラー情報をクライアントにJSONでいい感じに返す
grpc-gateway
ではデフォルトでエラーの内容が以下のようになります。
{ "error": "invalid token", "code": 16 }
上記で書いたように、 Metadata
に詰めた内容も grpc-gateway
でクライアントにJSONとして返したい場合には、エラーの場合の挙動をカスタマイズする必要があります。
実はこの方法は、grpc-gatewayのwikiにリンクが貼られている以下のブログに書いてあります。
runtime.HTTPError
にエラーハンドラーが定義されているので、これを差し替えます。
色々端折っていますが、自分は以下のようなエラーハンドラを定義しました。
// 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 request
は grpc-gateway
では使えません。
一番簡単な方法は、Base64エンコーディングして受け渡しをしてしまうことです。
ただしProtocol Buffersの説明にもあるように、Protocol BuffersではMB以上のサイズを受け渡しするのは向いていません。
https://developers.google.com/protocol-buffers/docs/techniques
手元でやったところ大体3MB以上のファイルになると、この方法ではうまく行きませんでした。この場合には、grpc-gatewayに独自のエンドポイントを設けて 一度gatewayでファイルを受けて、Client Streamingで送るとかを検討する必要があるかもしれません。※自分はそれほど大きなファイルを送らずに済んだので、試してませんが…
JSONのMarshallをカスタマイズする
grpc-gateway
はデフォルトでは、Protocol Buffersでデフォルト値になっているものをレスポンスから省いてしまいます。
これはproto3では、デフォルト値なのか値がセットされていないのかを区別できないのでこのような挙動になっているようです。
この挙動は以下のIssueのコメントにもあるように WithMarshalerOption
で変える事ができます。
gwmux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName: true, EmitDefaults: true}))
- OrigName
- デフォルトではJSONはキャメルケースになりますが、これをtrueにすると、
.proto
に定義されたフィールド名で出力されるようになります
- デフォルトではJSONはキャメルケースになりますが、これをtrueにすると、
- EmitDefaults
- proto3でデフォルト値になっているものもJSONで出力することができます。
まとめ
いまいちまとまりのないポストになってしまいましたが、プロジェクトで触った、gRPC / grpc-gateway について書きました。
個人的にはgRPCはかなり開発体験が良かったので、次に機会があればまた検討したいなと思います。
カイゼン・ジャーニーを読んだ
カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで
- 作者: 市谷聡啓,新井剛
- 出版社/メーカー: 翔泳社
- 発売日: 2018/02/15
- メディア: Kindle版
- この商品を含むブログを見る
カイゼン・ジャーニーを読んだ。展開が熱くて2日くらいで一気に読んで、この熱量を伝えたくて会社の人に布教してしまった。
江島という主人公のストリーに沿ってスクラム・アジャイルについて学べる
この本のいいところはタイトルにあるように「たった1人からはじめて」というところだと思う。現場に対して不満や変えたいことがあったとしても、それをいきなり理想形の形に持っていくのはとてもむずかしい。いきなり理想形にもっていくのは現場の社員1人では無理だと思う。それでも1人からはじめて、仲間をどのように作っていくか、そこから書かれている点がとても良いなと思った。
立ち向かう問題がなかなかリアル
自分はスクラムの経験が沢山あるわけではないけれど、それでもあーこういうことあったなぁーという問題が本書のなかでも沢山起こっていて、とてもリアルだった。個人的にはデザイナーとの協業のところは、過去に似たような経験があったのでとても学びが大きかった。当時は自分の馬力で何とかした(なってなかった説もある)けど、こういうやり方で進めればよかったなーと思うことが沢山ある。
インセプションデッキを定期的に見直す、とか「向き直り」をする、とかこれまでやったことのないアプローチもあったので真似していきたい。
いちいちセリフが熱い
「みんな、隣の芝生は青く見えている。行ってみると、実は芝生は前にいた場所から続いていて、大して変わらないことに気づくんだ」
「それで、あなたは何をしている人なんですか?」
「僕が取り組むより、部のみんなに展開したほうが良いかもしれない。…といって、また自分からは始めないのが、これまでの僕だ。」
WEB系にいる今でも震えたので、SIerにいたときなら涙がでちゃったかもしれない。
専門家はいない。私たちしかいないんだ。
これは先日あったRuby 25周年イベントのときに何かのスライドで、またエラスティックリーダーシップという本でも出てきたフレーズだ。カイゼンジャーニーに出てくるフレーズではないんだけど、サブタイトルにもなっている「越境」するチームというのは、こういうマインドを持ったチームなんじゃないかと読んでいて思った。
まとめ
現場を変えたいと思っている人にとっては、手法が参考になるのは勿論のこと、やっていこうという熱量を貰える本だと思う。オススメです!
ティール組織を読んだ
Twitterでよく見かけたので、ティール組織を読んだ。
- 作者: フレデリック・ラルー,嘉村賢州,鈴木立哉
- 出版社/メーカー: 英治出版
- 発売日: 2018/01/24
- メディア: 単行本
- この商品を含むブログ (1件) を見る
ティール組織と呼ばれる、従来の組織形態の常識からは考えられないルールで運営され成果を出している組織形態についての話だった。ホラクラシーというと馴染みがあるかもしれない。(ホラクラシーはティール組織の形態の1つみたいな紹介のされ方がされている。)
ティール組織は以下の3つの特徴があるらしい
- セルフマネジメント (自主経営)
- ホールネス (全体性)
- 存在目的
コレだけ見ると、セルフマネジメント以外はなんとなくトンデモ理論に見えるけど、読み進めていくと確かにと思うことが多かった。 1章目は若干スピリチュアルな話(にこの時点では感じること)が入ってるんだけど、2章目からは実際にティール組織で運営が行わている企業の話が続いていくので1章目は我慢して読んでいってほしい。
以下読んでいて自分が思ったこと。
性善説な組織
セルフマネジメントな組織では、従業員は基本的に善良であるという前提にたって組織運営がされている。本書で紹介されている会社では、元々備品の貸出にマネージャーの承認が必要だったり、事細かにタイムカードを付けなければいけなかったものを一切取りやめるだけでなく、目標設定や決済まで各チームに任されて運営がされていた。従業員が基本的に善良であるという前提にたつと冗長なルールや面倒な手続きのほとんどをなくすことができるようだ(だからこのような組織ではオフィス機能にあたる人の数が極端に少ない)。
個人的にも性善説にたっていない組織では、無駄なルールやとりあえずマネージャーに承認をもらうみたいなフローが多くなるという気はしていたので、これは納得だった。
こうなると気になるのは「何か不正が起こったらどうするのか?」ということだけど、本の事例だと以下のパターンだった。
- 罪を犯した人は解雇になり社内にも統制を求めるような声があがったが、社長が中心になりセルフマネジメントで性善説にたった文化を守った
- これまでこのような組織運営に寛容だった取締役陣が、統制を求め始めてセルフマネジメントの文化が失われてしまった
こういう文化を守っていくには、経営層にそれなりの覚悟がいりそうだなと思った。
なぜ最近のオフィスはアットホームな雰囲気のものが多いのか
これはホールネス(全体性)にかかってくる部分。そもそもホールネスってなんだよって感じだけど、本書を読んでいくと職場でも「自分らしくいられるか」ということだということが分かる。職場で「自分らしくいられる」ということの意味は、多分「自分を必要以上に大きく見せ」たり、「必要以上に卑下して見せ」る必要が無く、普段に家や友人と過ごすような気持ちで職場で過ごせるかということなんだと思う。 最近できたオフィスってカフェスペースがあったり、小上がりがあったりするのが多いのって、社員が自然体でいられるようにという狙いもあったりするのかなーと思った。
ティール組織になるとどのようなことが起こるのか
オランダのビュートゾルフというヘルスケアの非営利組織の話が特に面白い。もともとオランダの地域看護師を束ねる組織では、1看護師あたりの生産性を上げるために細かく全ての医療行為に標準時間を設定して内容を指示し、それをモニタリングするために各家庭の玄関ドアにバーコードを付けて医療行為にかかった時間を調べ、遠隔地から監視・モニタリングするということをしていた。(これだけ聞くと結構利にかなっていると思ってしまう。)
一方ビュートゾルフではこういった管理をせず、10〜12人のチームでケアサービスや、患者の受け入れ、スケジュール管理、さらには採用まで受け持つ形態を取っている。しかし結果として、ビュートゾルフの方が他の組織よりも1顧客あたりに費やした介護時間は40%近く少ない(要は効率よく医療行為ができている)というデータが出ているらしい。2006年に10人だった組織が2013年には7000人になっているという成長スピードも凄まじい。
いまの組織をティール組織にするにはどうしたらいいのか
これは結構身も蓋も無いことが書いてあって、正直だいぶ厳しい。
- CEOがティール組織のアイディアについて個人的に素晴らしいと思ってくれているか
- 取締役会もティール組織について理解し、支持しているか
CEOが乗り気でない場合には、自分の権力や時間を注ぐことはそれほど意味がないとまで言われてしまっている。厳しい。
まとめ
最近生産性を上げるにはもっと性善説に立たないといけないと思っていたので、どうやってセルフマネジメントな組織が実現されているのかは納得する部分が多かった。 既存の組織を末端の社員からティール組織に変革していくのは無理ゲーと書かれているわけだけど、組織がどのように変わっていくかの話など勉強になる内容が多かったし、変革までいかなくても改善していける部分もありそう。というかあってくれ頼む。
メルカリやSmartHRはかなりこのあたりうまくやってる印象あるけど、どうなんやろうか。 blog.shojimiyata.com
実践ドメイン駆動設計を読んだ
実践ドメイン駆動設計を読んだ
会社でレガシーシステムのリニューアルをやっていて、それのコアとなるAPIサーバをDDDで作りたいと思い購入した。
エバンス本は既に一度読んでいたのだけれど、いざ実務になるとこれはどこに置いた方がいいんだ?とか、どう実装するべきなんだ?みたいなのは本当によく迷う。
この本で、そのあたりクリアになるんじゃないかなという期待を持っていた。
以下、感想
自分がやっていたのは軽量DDDだった
本書ではDDDの思想を理解せずに、そのアーキテクチャの実装だけを真似ることを軽量DDDと呼んでいて、あまりオススメしないと言っている。
実際、自分もやって見て思ったんだけど、ドメインとは何だろう?という問いにちゃんと答えられないとレイヤー分けが結構難しくなってくる気がする。
ビジネスロジックはドメインなんでしょ?みたいな意識だけだと、複数人の開発で意識合わせていくの辛くなるんじゃないかなと漠然と思ってたので納得。
DDDとは特定の技術的なアーキテクチャのことではない
これはよく公開されているスライド見ててもよく誤用されてるし実際自分も DDDでやってますみたいなこと言っちゃうんだけど、レイヤードアーキテクチャ= DDDではないという話。
特定の技術に依存するものではない、でもヘキサゴナルアーキテクチャがオススメとは本書に書かれていた
エンティティとは?バリューオブジェクトとは?アプリケーションサービスとは?ドメインサービスとは?
このあたりは、概念としては分かっているつもりでも、いざ実装するとこれでいいんだっけ…ってなりがち。本書ではこれらについて具体的なコード例もつけて解説してくれているので、かなり参考になる。また理想的にはこうだけど、パフォーマンス要件とかで難しい場合もあるよねーといった現実的な話もしてくれるのが良かった。
でもこれもやはり、技術的なテクニックというだけの話ではなく、ドメインから導いていくということが大切だなとおもった。(特にアプリケーションサービス、ドメインサービスは単純にビジネスロジック置き場みたいな気持ちだとごちゃごちゃになっていきそう)
全てをバリューオブジェクトにする必要はない
例えばBooleanの値であったり自己完結した数値だったりする場合など、その値自体で意味のある全体を成している場合にはバリューオブジェクトにする必要が無いということは書いてあった。
なんとなくDDDガチ勢の人って全てをバリューオブジェクトでラップする印象があったのでちょっと意外だった。(よく考えたらそりゃそうだという話ではあるけど)
一方でIDみたいなものはどんどんバリューオブジェクト化した方が良さそうなので、自分もやって行こうと思う。
まとめ
おもっていたよりも読みやすい本だった。表紙で損してる気がするw
これを呼んでエバンス本の方をまた読み返し、またこの本を読むみたいなことをすると、理解が深まりそう。
作者が公開しているリポジトリも参考になるのでおススメです。
Netflixでデビルマンを見た
深夜に調べ物をしていてそのお供に見た。ながら見だったんだけど、割とトラウマになった。。。どれくらいかというと、ぼーっとしてるとその辛いシーンが思い出されて陰鬱な気分になるレベル。 ただ本当に傑作だと思った。
以下激しくネタバレ含みます。
続きを読む