Spring WebFlux・R2DBCのマルチモジュールプロジェクトでRow Level Security基盤を構築した話

この記事はSpring Advent Calendar 2022の20日目の記事になりました。

qiita.com

前書き

こんにちは、justInCaseTechnorogiesでバックエンドエンジニアをしている宮田です。
弊チームでは、Spring WebFlux(Kotlin Coroutines) x R2DBCのマルチモジュールプロジェクトで、Row Level Securityを有効にし、かつ効率的に開発を行うための基盤を作成しました。
この記事ではその中で得た知見についてまとめます。

技術スタック

  • PostgreSQL 13
  • SpringBoot 2.7.3
  • Kotlin 1.7.10

Row Level Securityについて

本題に入る前に、Row Level Security(以降RLSと記載)について軽く解説します。

RLSは、簡単に言うと、識別子が一致しない行へのアクセスをDB側で禁止する機能です(DB側の機能であるため、この機能を提供していないDBでは利用できません)。
これを利用することで、「A社の操作によって発行されるクエリはA社以外のデータにアクセスできない」状態が実現できます。

これは、例えばSQLに渡す条件を取り違えたり、where句を付け忘れてしまった場合などに役立ちます。

fun findFooBy(fooId: Int): Foo { /* 何らかのクエリ処理 */ }

fun processFoo(fooId: Int, barId: Int) {
    val foo = findFooBy(barId) // 渡すIDを間違えた!!

    /* 取得できてしまうと処理は継続される... */
}

このような場合に、間違えた条件の先にデータが存在していると、そのデータが破壊されたり、不正なデータが画面に表示されたりといった事象が起きます。
場合によっては、「A社のデータがB社から閲覧できてしまう」ようなことも起きるでしょう。
データ破壊や情報流出は言うまでもなく特大の問題です。

一方、RLSを適用していれば、A社のデータはB社から取得できないため、このようなバグを作り込んでしまった場合にも問題の影響範囲を限定することができます。

アプリケーション側の実装について

ここからは、クライアントからのリクエストを処理する一連の流れを紹介します。

説明内では、RLS用の識別子を単にIdentifierと表現します。
この部分は、実際には識別子に合わせて適切な名前を付ける必要があります。

基本方針

シンプルな対応方針としては、以下のように、Identifierを引数として引き回し、クエリ発行時にそれぞれ設定する形も考えられます。

import io.r2dbc.spi.ConnectionFactory
import kotlinx.coroutines.reactive.awaitSingle
import org.springframework.r2dbc.connection.ConnectionFactoryUtils
import org.springframework.stereotype.Repository
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.toMono

@Repository
class Repository(private val cfi: ConnectionFactory) {
    // コネクション発行 -> クエリ処理
    suspend fun query(identifier: String) {
        ConnectionFactoryUtils.getConnection(cfi)
            .flatMap { connection ->
                connection.createStatement("SET app.current_identifier = $1")
                    .bind(0, identifier)
                    .execute()
                    .toMono()
                    .thenReturn(connection)
            }
            .map { /* identifier設定後のコネクションを利用したクエリ処理 */ }
            .awaitSingle()
    }
}

一方、このやり方には以下のような欠点があり、何千と機能を作っていく上では良いやり方とは言えません。

  • 処理が非効率になる
    • 特にORMを利用する場合、発行したコネクションから一々初期化処理を行わなければならない
    • Transaction中など、1度発行したコネクションを使い回す場合には、Identifierに関するクエリを何度も発行することになり非効率
  • 全体的な記述が冗長になる
    • 全クエリ処理にIdentifierの設定処理を書く必要が出る
    • 全関数に引数としてIdentifierを設定するのは大変
    • Identifierが処理に必須とは限らないため、RLSのために全体で引き回すことには違和感が有る

そこで、基本方針としてはRLSが設定されていない場合と同じ見た目になる(= 日常的なコーディングを行う上での労力が最低限になる)形を目指しました。

基本的な処理の流れ

基本的な処理の流れは以下のようになります。 これらの基盤を実装することで、日常的に触れるコードではRLSが設定されていない場合と同じ見た目を実現できます。

  1. スコープ内で利用するIdentifierReactor Contextにセットする
  2. 1でセットしたIdentifierReactor Contextから読み出す
  3. 2で取得したIdentifierをコネクションにセットする

モジュール配置としては、2と3は共通モジュールに、1はモジュール毎に個別の内容を配置する形になります。

以下、基本的な処理の流れに沿ってそれぞれの部分を紹介していきます。

1. スコープ内で利用するIdentifierReactor Contextにセットする

スコープ内で利用するIdentifierReactor Contextにセットする際には、WebFilterを利用します。
簡単な例として、リクエストヘッダから文字列のIdentifierを読み取り、Reactor Contextにセットするコードは以下のようになります。

import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Mono

const val IDENTIFIER_KEY: String = /* Identifierを管理するためのキー、 */

@Component
@Order(Ordered.LOWEST_PRECEDENCE) // 認証周りより後に実行するため、順序は最低にする
class SetIdentifierFilter : WebFilter {
    // リクエストのヘッダーからIdentifierを抽出する関数
    private fun ServerWebExchange.getIdentifierFromHeader(): String =
        request.headers.getFirst(IDENTIFIER_KEY)!!

    override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
        val identifierMono: Mono<String> = Mono.just(exchange.getIdentifierFromHeader())
        return chain.filter(exchange).contextWrite { it.put(IDENTIFIER_KEY, identifierMono) }
    }
}

filter関数をJWT認証のトークンから読み出す形に修正した場合は以下のようになります。

import org.springframework.security.core.context.ReactiveSecurityContextHolder
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken

    /* class SetIdentifierFilter...は省略 */

    override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
        return chain.filter(exchange).contextWrite { context ->
            // ReactiveSecurityContextHolderはchainに連なる処理内でなければ正常に値を読み出せない
            val identifierMono: Mono<String> = ReactiveSecurityContextHolder.getContext().map {
                (it.authentication as JwtAuthenticationToken)
                    .token
                    .getClaimAsString(IDENTIFIER_KEY)
            }
            context.put(IDENTIFIER_KEY, identifierMono)
        }
    }

以下、これらのサンプルコードに関して解説していきます。

セットする内容について

サンプルコードではMono<String>をセットする形に統一しています。
これはReactiveSecurityContextHolder.getContextに連なる処理で得たMono<String>Stringにアンラップしてセットする方法が思いつかなかったためです。

これについては多くの部分で問題になりませんが、どちらかと言えばStringをセットできた方が嬉しい部分がありますので、分かる方がいらっしゃいましたらコメント頂けると助かります。

Reactor Contextへのアクセスに関する注意点

Reactor Contextは非リアクティブプログラミングにおけるThreadLocalのようなもので、リクエスト単位で固有の値を設定・読み出すことができます。

projectreactor.io

ただし、WebFilter内でReactor Contextにセットされた値を読み出す機能を利用する場合は、必ずchainに連なる処理の中で呼び出す必要があります。
サンプルコードでは、contextWrite内で呼び出しているReactiveSecurityContextHolder.getContextがそれです。

これは、WebFilterReactor Contextchain内のReactor Contextが異なるために必要な対応です。
アプリケーション内同様にReactor Contextにセットされた値を読み出したい場合は、chainに連なる処理の中で呼び出す必要があります。

認証等の情報から読み出す場合の注意点

他のWebFilterによってセットされる値(特に認証周りなど)からIdentifierを参照したい場合が有ります。
この場合、SetIdentifierFilterは他のWebFilterが設定された後に実行される必要があります。
サンプルコードでは、OrderアノテーションでSetIdentifierFilterの実行が最後になるよう制御しています。

2. 1でセットしたIdentifierをReactor Contextから読み出す

1でセットしたIdentifierReactor Contextから読み出すコードは以下のようになります。

import kotlinx.coroutines.reactive.awaitSingle
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono

@Component
class IdentifierGetter {
    // Identifier取得の共通関数
    // 有効なスコープ内で実行すればIdentifierが取得できる(コンポーネント内に配置しているのはモックの都合)
    fun getIdentifierMono(): Mono<String> = Mono.deferContextual {
        it.get<Mono<String>>(IDENTIFIER_KEY)
            .onErrorMap { e -> RuntimeException("コンテキストからIdentifierが取得できませんでした", e) }
    }

    suspend fun getIdentifier(): String = getIdentifierMono().awaitSingle()
}

コメントの通り、Component化している理由はモックを容易にするためで、基本的にはトップレベルにも配置できる内容です。
また、アプリケーション側のコードがCoroutineで書かれている場合にも、これらのコードは機能します。

読み出し処理に関する注意点

この読み出し処理はReactor Contextに対して行われるため、Reactor Contextが異なっていたり、Reactor Contextにアクセスできない場所から呼び出すと正常に機能しません。

前者は、例えば共通処理を別Coroutine Scopeで実行したような場合に問題となります。
これに関してはReactor Contextの値を引き継ぐことで対処できます。

qiita.com

後者はReactorを利用していない・Reactive Streamsのみ利用している外部ライブラリに連なる処理で実行されたような場合に問題となります。
自分が知っている限りではjOOQがこれに当たります。
詳しくは以下の記事にまとめています。

qiita.com

3. 2で取得したIdentifierをコネクションにセットする

Identifierをコネクションにセットする際は、ConnectionPoolpostAllocateを利用するのが楽だと思います。
最低限の設定だけを抜き出したR2dbcConfigurationは以下のようになります。

import io.r2dbc.pool.ConnectionPool
import io.r2dbc.pool.ConnectionPoolConfiguration
import io.r2dbc.spi.ConnectionFactories
import io.r2dbc.spi.ConnectionFactory
import org.springframework.boot.autoconfigure.r2dbc.R2dbcProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration
import reactor.kotlin.core.publisher.toMono

@Configuration
class R2dbcConfiguration(private val identifierGetter: IdentifierGetter) : AbstractR2dbcConfiguration() {
    @ConfigurationProperties("spring.r2dbc")
    @Bean
    fun r2dbcProperties(): R2dbcProperties = R2dbcProperties()

    @Bean
    override fun connectionFactory(): ConnectionFactory {
        // URL にユーザー名とパスワードが含まれてないと接続ができない
        val defaultConnectionFactory = ConnectionFactories.get(r2dbcProperties().url)

        val config = ConnectionPoolConfiguration.builder(defaultConnectionFactory)
            .postAllocate { connection ->
                identifierGetter.getIdentifierMono()
                    .flatMap {
                        connection.createStatement("SET app.current_identifier = $1")
                            .bind(0, it)
                            .execute()
                            .toMono()
                    }
                    .then()
            }
            .build()

        return ConnectionPool(config)
    }
}

application.propertiesspring.r2dbcの下にはurlプロパティのみ設定している想定です。

spring.r2dbc.url=r2dbc:postgresql://${ユーザー名}:${パスワード}@localhost/${DB名}

この設定を行なったConnectionPoolConnectionFactory)から発行されるConnectionは、アプリケーション内では常に適切なIdentifierが設定された状態になります。

終わりに

この記事では、Spring WebFlux x R2DBCのマルチモジュールプロジェクトでのRLS基盤作成についてまとめました。

弊チームが開発を行なっていた時点では、Spring WebFlux x R2DBCRow Level Securityを有効にする例は世界的に見ても少なく、実装時点ではサンプルコードも中々見つけられない状況でした。
記事ではかなりあっさりした書き方をしていますが、開発を進める中では、知見不足で調査に時間がかかったり、jOOQ固有の問題でハマったりと、多くの苦労が有りました。

同じような構成での開発を行う方へ、この記事が何かのお役に立てば幸いです。

justInCaseでは、さまざまな職種を採用中です。
保険SaaSの開発にご興味がある方は採用ページよりお気軽にご応募ください!
Spring WebFluxを用いたバックエンド開発をやってみたい方大歓迎です!

justincase.jp

joinsure recordチームの開発体制

こんにちは。

justInCaseTechnologies の web frontend engineer の kondei です。

当社は今 "joinsure" という SaaS 型保険システムを開発中です。

チームは Systems of Record, Systems of Engagement という区分を参考にして大きく "record" と "engagement" に区分しています。

そのうちの契約管理などの部分を担当する record チームの最近の開発体制についてご紹介します。

アジャイル × スクラム開発

導入まで

joinsure 以前の開発はハッキリ言って「なんちゃってアジャイル」な部分があり、明確なフレームワークに沿っていませんでした。

それによってミーティングが少なかったり機動力があったりするというメリットはありましたが、感じていたデメリットとしては、以下のようなものがあります。

  • タスクを頼んだと思っていた人と、頼まれたと思っていない人の認識の齟齬があることがある
  • しばらく経つと、誰がボールを持っているのか、何によって待ち状態になっているのか分からないことがある
  • 期間も進捗も分からないことがある
  • ゴールが明確でないときがある

joinsure プロジェクトが明確に立ち上がるとき、開発体制をどうするかコアメンバーで議論しました。

そのとき草案としては既存のオリジナル体制の発展型が提案されており、ウォーターフォール型となんちゃってアジャイルの混ざりあったような状態が想定に入っていましたが、 「既知のプラクティスであるアジャイル × スクラム開発手法に従った方が良い」という議論の結果、導入が決定されました。

他の手法ではなくこれを選んだ理由としては、以下の通りです。

導入にあたって

導入にあたっては、まず

をチームメンバー全員で読むことでマインドセットと方法の認識合わせをしました。

ツールは BizDev, PdM, designer, engineer全員で YouTrack を使用しています。

また、アジャイルの流儀の 1 つである、インセプションデッキを作成しました。

最近では「何を諦めるのかはっきりさせる」ページの優先度調整が現実に合わせて更新されたりして、全員の方向合わせに有効活用されています。

インセプションデッキ

実践してみての工夫

  • プロジェクト状況に応じて適宜イベントのタイムボックスを調整しています(スプリント期間を 2 週間 → 1 週間 → 2 週間で調整したり、内容が減ってきたらミーティングを短くしたり)
  • スプリントレトロスペクティブは KPT 法で実施しており、なるべく改善案・next action を決めるようにしています
  • 社内ドキュメントに「スクラムを始めるときに読んでおくと良いもの」というページを作って、参考文献をまとめています
  • Slack にアジャイルスクラムの話をするためのチャンネルを作っています

効果

導入によって、以下の効果があったと感じています。

  • スプリントの導入によってゴールが明確に共有されるようになった
  • 誰が何をしているのかわかるようになった
  • 短い締切の繰り返しが生まれることによって、意欲が向上した
  • デイリーやスプリントプランニングによって、計画の見直しがしやすくなった
  • engagement チームの開発にも導入されて、会社全体として IT 開発の素地が整った
  • 良い開発体制を持ったことで、採用上も訴求力が上がった
  • 各チームを超えた会社全体の体制として「大規模スクラム(LeSS )」を見据えることができるようになった

DDD

ドメインコンテキストマップ

ドメインコンテキストマップ

幅広い保険業務を扱う SaaS の開発のために、BizDev がドメインコンテキストマップを作成して、大まかな区分けと依存関係を認識共有しています。

また、個人的に良いと思うところは、このマップに対し、バックエンドのサービス区分・フロントエンドの機能区分をほぼ一致させることができていることです。

ドメインモデル図

ドメインモデル図

ドメインモデル図をクラス図の形式で書き、ドメイン知識のメモ書きを付記して、PdM とエンジニアで共同管理しています。

目的としては、全員のビジネスの想定を揃えることと、バックエンドのドメイン層実装のために使います。

変更があるときは随時議論したり、スプリントレビューで全員に共有されています。

アクティビティ図

アクティビティ図

同様にPdMがアクティビティ図を使ってシステム間に跨ったビジネスのフローを定義・管理しています。

状態遷移図

状態遷移図

「契約」「請求」など、状態がある概念に対しては状態遷移図を作って状態とイベントを定義しています。

これは以下の役に立っています。

  • PdM とエンジニアで明確に認識を合わせること
  • バックエンドもフロントエンドも状態変更操作や用語を正確に図に従って実装すること
  • 状態遷移に伴う UX や用語の改善議論

また、これを作る文化が根づいたことで、メンバーが仕様定義の時点で「これは状態遷移がある概念かどうか。状態とイベントを定義した方が要件定義しやすいかどうか」という判断ができるようになったと感じています。

bitemporal data model

保険の業務では契約やお金の絡むイベントを厳密に扱う必要があったり、特定の日時のデータを出力する必要があったりします。

つまり「データによっては任意時点のイベント・状態を後から復元できる必要がある。どう保持するか?」という課題があります。

それに対して、チームメンバー主導で bitemporal data (model)というデータモデルの導入が提案され、ハンズオンが開催されてチームで勉強して、バックエンドのデータモデルに導入されました。

参考文献

OpenAPI, スキーマ駆動の API 開発

APIスキーマから自動生成したドキュメント

joinsure 以前の API 開発と利用では以下のペインがありました。

  • API 定義が手動生成になっており、ドキュメントの更新漏れ等が発生する
  • API ドキュメントと実装の差分がわからない、検知できない
  • クライアント側の API モデル(型)とコードが手動生成になっていて乖離やコーディングコストで開発効率が悪いので、自動生成したい
  • API 定義がサーバーサイド主導になっており、クライアントサイドが API に対する議論への参画がしづらいことを、スキーマファーストで解決したい

それに対して OpenAPI のスキーマ駆動開発と、コードとドキュメントの自動生成を導入し、ペインがほぼ解消できました。

参考文献

https://logmi.jp/tech/articles/324190

ADR(Architecture Decision Record)

技術選定の議論と理由を残すため、バックエンド・webフロントエンド共に技術選定ドキュメントに ADR が導入されています。

ADRをレビューフローに乗せることで合意を得る意思決定プロセスが明確になり、リポジトリに残ることで新入社員も経緯を追いやすくなります。

参考文献

デザイン開発における Figma の活用

デザイン面の開発は Figma を活用し、以下の流れとなっています。

  1. BizDev, PdM がワイヤーフレームを作成し、デザイナーとブラッシュアップ(場合によってはエンジニアとも議論)
  2. デザイナーがワイヤーフレームから実装用デザインを作成し、BizDev, PdM, フロントエンドエンジニアとブラッシュアップ
  3. フロントエンドエンジニアが実装

ワイヤーフレーム

実装用デザイン

これによって以下の効果がありました。

  • PdMの意図するUXから乖離しにくい
  • UX改善や変更の議論が活発になった

デザイン開発におけるコンポーネント指向開発

これまでの課題として、複数のプロダクトで同じようなデザイン定義とその実装が再作成されている状態がありました。

それを解決する手法として、デザインの時点でコンポーネント指向で定義し、実装もコンポーネント単位で再利用できるものを作っています。

コンポーネント指向デザイン

また、最近では Chromatic というサービスを導入してコンポーネントのVisual Regression Testで差分の検知をできるようにして、品質を高めています。

チームのエンジニア全員でエンジニア採用に関わる状態

エンジニア採用において、人事メンバー主導で数ヶ月前に「スクラム採用」という手法が導入されました。

エンジニア採用のために「採用 weekly」や「転職ドラフトもくもく会」などの定期イベントが設定され、基本的にエンジニアメンバー全員が候補者を探して判断するようになり、開発上求める人員の話・採用活動上の課題・面談や面接やツール利用方法の改善点などが議論されるようになりました。

これによって採用という業務が「人事や一部の人だけが関わるもの、通常メンバーにとって新入社員はいつの間にか降ってくるもの」というような状況から、「チームで一緒に働きたい人を主体的に見つけて判断し、やり方自体を改善していく」という状況に変わりました。

開発体制がスクラム化したこともあり、人事面もスクラム要素が取り入れられたのは相性がよい体制と感じています。

終わりに

justInCaseTechnologiesでは常に現状の改善を進めており、新たな開発メンバーも募集しています。

この記事で少しでも働くイメージが湧いたら嬉しいです!

ご応募お待ちしています。

Server-Side Kotlin Meetup vol.2へ登壇しました

こんにちは、justInCaseTechnorogiesでバックエンドエンジニアをしている宮田です。
先日開催されたServer-Side Kotlin Meetup vol.2へ「jackson-module-kotlinを読もう!」というタイトルで登壇してきました。
資料はこちらです。

speakerdeck.com

Server-Side Kotlin Meetupについて

Server-Side Kotlin Meetupsmartroundマネーフォワード、及びjustInCaseが中心となって開催している、サーバーサイドKotlinに特化した勉強会です。
運営企業に加え、様々な有名企業所属エンジニアやOSSコントリビュータも登壇する勉強会となっています。

第3回は6月16日開催予定で、弊社からは吉本が登壇予定です。
コアな技術的発表からサーバーサイドKotlinの導入話まで幅広い発表が聞ける勉強会となっておりますので是非ご参加ください!

server-side-kotlin-meetup.connpass.com

二部として登壇者や参加者とコミュニケーションの取れる懇親会も有りますのでそちらも是非!!

Tech Team Journalにて、対談記事を掲載いただきました

こんばんは、justInCaseTechnologies CTO の大畑です

Tech Team Journal にて、株式会社デジタルハーツ CTO 城倉氏との対談記事を掲載いただきました。

ttj.paiza.jp

色々と書いていただきましたが、当社の魅力としては以下な感じです ✨

  • レガシーなイメージの保険業界だが、モダンな技術・手法を積極的に取り入れている
  • 優秀なメンバーが集っており、チーム主体で提案・意思決定している
  • 事業と開発が一体となったスクラムで、プロダクトを作っている

当社にご興味を持っていただけた方は、まずはお話できると嬉しいです 😄

TwitterMeety でも、お気軽にご連絡ください!

もちろん、転職意欲がなくても大丈夫です

justincase.jp

Vercel Enterprise 導入しました

こんにちは。justInCaseTechnologiesのwebフロントエンドエンジニアのkondeiです

この度、当社は Vercel Pro から Enterpriseプランへと変更しました

差分は Pricing – Vercel の表にある通りです

これから検討している方に参考になりそうな情報としては、

  • 契約締結に向けたメールのやり取りについて、こちらからは日本語で書いて送信できた
  • 日本語ができる方によるサポートを受けれている
  • Onboarding Support (Training sessions and shared Slack channel with a dedicated Customer Success Manager.) とあるように、slackによるサポートがある
    • ただし詳細は契約内容による

があります

また、メンバーのohataさんと協力して作った導入申請書の一部を公開します(サービス比較など、不完全な部分も多いですが)

要点としては、メンバーを増やせるということ、SLAがあるということがポイントでした

こういう作業で個人的に印象が深かったのは、非エンジニアに対して必要性が分かりやすい稟議を出すために、工数をどれくらい削減するか見積もるという視点が得られたことでした

導入したいサービス・ツール

概要

  • 現状、Webフロントエンド ホスティングサービスとして、Vercelを利用している。
  • 現状のProプランは、10人までしか使えない。
  • Webフロントエンドエンジニアの増強に伴い、Enterpriseプラン変更をしたい。

導入の目的・解決したい課題

Webフロントエンド エンジニアの生産性の向上

  • AWSエンジニアに頼らず自律して開発を進められる or (AWS特有の)インフラ管理・キャッチアップの工数を減らす
    • Preview を提供するための、デプロイ管理する工数を削減できる
      • 各案件・商品に対して、Preview を迅速・並行して提供することで、社内外の確認を円滑に実施できる
      • Next.js の開発元として安定している、管理・構築が不要
    • Webフロントエンドエンジニアが使いやすい、ドメイン管理機能がある
    • Webフロントエンドエンジニアが使いやすい、アクセス保護機能がある
      • AWSLambdaなどで、Basic認証の実装・連携が不要
      • EnterpriseだとSSO Protectionも選べて、チームの大規模化に伴うパスワード共有の煩雑さを解消しやすい
    • Webフロントエンドエンジニアが使いやすい、サーバーレスファンクション機能がある
      • AWS Lambda のキャッチアップ、構築・運用、管理工数が不要
      • ちょっとしたAPIを実装できる
      • 横断的に挟みたい処理を実装できるnext.jsのmiddlewareがvercelだとサーバーレスファンクションに乗るので楽に利用できる
  • web frontendフレームワーク最大シェアのNext.jsの開発元としてその機能を完璧にサポートし、継続的により良い機能を取り入れられる
  • 分離されたインフラでさらなるビルド速度、実行キュー待ちの削減

Vercelで、Webフロントエンドエンジニア or SRE の工数を、6時間/人月 以上 は 削減している

※ 削減できる工数に加え、そこから生み出すクリエイティブに、より価値がある

現状のプランのままだと、10人までしか使えない。

  • 現状10人利用しており、更にWebフロントエンジニアが数人入社する(やりくりが限界)
  • コミットのAuthorが Vercelのmemberである必要がある
  • デプロイ時の担当者がボトルネックor切り替え工数がかかる
  • 当社は自律したプロダクトチームを推進、MoveForwardでフルスタックな関わりを推奨している

Vercel 以外のものに移行するにも工数はかかるので、Vercelのプランを変更するのが良い。

事業継続・事故防止・損失回避

  • SLA 99.99% を保証する ( 現状のプランのままだと、SLAがない )
  • 各チームの然るべきメンバーが常に利用できる状態にしておくことで、万が一の対応にも備える。

導入にかかるコスト

費用

契約に関わるので秘匿

工数・スケジュール感

  • 導入そのものにコストは掛からない(プラン変更のみ)
  • Enterprise契約の締結なので、段取りに1-2ヶ月かかる
    • 英文契約書のレビュー・締結など
  • Webフロントエンドエンジニアがジョインする時期までにはプラン変更をしたい

検討した選択肢

要件・比較ポイント

  • Next.js 機能のサポート
  • サイトパフォーマンスの上げやすさ
  • 管理工数(CI/CD連携、ビルド など構築、そのキャッチアップ)
  • その他、生産性に寄与する機能

Vercel

Pros

Cons

  • コスト ○
    • コストパフォーマンスは有る
  • Amplify並の細かい設定はない
  • 複数環境ビルドには若干準備が必要

参考

serverless-nextjs を使ってAWSによしなに配置

Pros

  • next.js機能のサポート ○
  • サイトパフォーマンスの上げやすさ ○
    • Lambda@Edgeに配置されるので良いと思われるが、AWSの使い方による

Cons

  • 管理工数 ×
    • githubと連携してpreviewする方法などは不明
    • なんやかやで勉強と手間がかかることは間違いない
  • その他、生産性に寄与する機能 ×

参考

https://www.serverless.com/blog/serverless-nextjs/

https://qiita.com/fumiki/items/5f4408ce844520a922c2

AmplifyとかS3とかに配置

Pros

  • コスト

Cons

  • next.js機能のサポート ×
    • dynamic routingやSSRのような、staticで済まない機能が必要になった場合に対応できない
  • サイトパフォーマンスの上げやすさ△
    • AWSの使い方による
  • 管理工数 ×
    • Amplifyはビルドが保留のまま止まることがそこそこ多く不安定で支障が出ることがある
    • キャッチアップが難しい
  • その他、生産性に寄与する機能 ×
    • AWSとしてはある、キャッチアップなどが必要

参考

EC2にnode serverとして配置

Pros

  • Next.js 機能のサポート ○
    • SSR, ISRなどの基本機能は使えるがvercel + nextで楽に使えることが前提の機能は厳しいと思われる

Cons

  • 管理工数(CI/CD連携、ビルド など) ×
    • 仮想サーバー管理コストがかかるし、CI/CD を自前で頑張ることになる
  • その他、生産性に寄与する機能 ×

参考

netlify

Pros

  • Next.js 機能のサポート ○
  • 管理工数(CI/CD連携、ビルド など) ○
  • その他、生産性に寄与する機能 ○

Cons

参考

https://www.netlify.com/blog/2020/05/04/building-a-markdown-blog-with-next-9.4-and-netlify/

できるけどあえてnext.jsを開発してるところと違う会社のjamstackにする必要がない

セキュリティ・リスク

都合上内容は割愛

その他、リスク・注意事項

都合上内容は割愛

【開催報告】justInCase社内でGraphQLハッカソンを開催しました

justInCaseTechnologies、ソフトウェアエンジニアの小笠原です。

2022年1月7日に、社内で「GraphQLハッカソン」を開催しました。会社初の試みであり、準備期間も短かったのですが、概ね好評のうちに1日を終えることができました。

チームが成長し続けるための一つのアイデアとして、ここに共有させていただきます。

f:id:justInCaseTechnologies:20220107193333p:plain
ハッカソン集合写真

背景

justInCaseTechnologiesでは現在、保険業界特化SaaS「joinsure」の様々な機能を開発しています。

そうした中で適切な技術選定のためのインプットとすべく、チームでGraphQLについて学ぶ機会がほしい、という声がきっかけでした。

イベント内容

開催まで

ハッカソンを開こうというアイデア自体は2021年の9月ごろからありました。

f:id:justInCaseTechnologies:20220107190913p:plain
社内でGraphQLハッカソンを開くことについて、Slackの投稿

話が本格化したのは年末になってからです。私達はスクラムで開発を進めているのですが、年末年始にスプリント外の期間を設け、そこでハッカソンを開いてはどうか、というアイデアが生まれました。

そこからは急展開で、ハッカソンの形式・内定者への招待・社内での告知などが全て1〜2週間の内に行われました。

イベント当日

ハッカソンには、joinsure Engagementチームの1名を除く全員と、joinsure Recordチームの1名、さらに内定者が1名の合計6名が参加しました。

個人とチームどちらの参加にするか悩みましたが、2名1チームとして合計3チームを完全にランダムに作りました。これはコミュニケーション促進の面でも、審査時間の短縮の面でも良かったと思います。

朝9時に集合し、自己紹介とチーム分けのあとは17時までひたすら開発タイムでした。ちなみに、ミーティングやお客様対応がある人は途中で出たり入ったりが可能になっています。

開発形式はどのチームもペアプログラミングで、コミュニケーション手段は内定者がいることもありGoogle MeetのBreakout Roomを活用して行われました。

審査

17時の開発終了後、CTOやPdMも集まっての発表・審査タイムがありました。

面白かったのは、3チーム中2チームがTwitterライクなWebアプリを作ったことです。

f:id:justInCaseTechnologies:20220107192152p:plain
TwitterライクなWebアプリ

f:id:justInCaseTechnologies:20220107192308p:plain
TwitterライクなWebアプリ2

いずれもツイートやリプライ、ライクができる、1日で作ったとは思えないクオリティでした。

実はある意味合っていて、参加者の何名かは事前にボイラープレートを作っていました。勉強も目的なので、当然アリです!ちなみにボイラープレートはこちらです。

github.com

github.com

発表後は審査タイムがありました。ちなみに、審査基準は以下のとおりです。

  • 発見があったこと
    • GraphQLのドキュメントを深く読み、みんなが気づかなかった仕様を知っていること
    • エコシステムをよく調査し、便利なライブラリを見つけたこと
    • Engagementでの使用を見据えて、いい感じのユースケースを思いついていること
    • etc...
  • サービスがイケてること
    • 実際に動くこと

審査の結果、フルスタックのボイラープレートを自作して参戦したやる気モリモリのメンバー3月入社の内定者のチームが優勝しました!

作成したのはTwitterのクローンで、リプライやライクができるなど、本物そっくりでした。1日で作成したとは思えないクオリティでした👏

ふりかえり

ハッカソンの後はふりかえりを行いました。

1日を振り返って思いついたことを、FUN, DONE, LEARNのベン図に分類して記述していきました。

f:id:justInCaseTechnologies:20220107193139p:plain
FUN DONE LEARN

普段の開発とは違った環境で、集中できた&学びになった、という意見が多く寄せられました。実際に発表中は驚きの声やツールの紹介などの知見共有も多くあり、開発工数の1日を使ってイベントを開いた意味が十分あったと考えています。

まとめ

チームで勉強する時間がほしい!という意見から始まり、この度業務時間を使ったハッカソンを開くことができました。

チームが成長するためには、先を見据えた勉強が絶対に必要です。忙しい開発と勉強とを両立するための手札の一つとして、ハッカソンを加えることができました。

最後に

justInCaseでは、保険SaaS「joinsure」を一緒に開発するメンバーを募集しています!

このブログの投稿を見て、良い組織だな、と思った方、まずはお話してみませんか?

こちらからご連絡お待ちしております🙏

justincase.jp

AWS DevDay Online 2021登壇のご報告

ソフトウェアエンジニアの @xhiroga です。

2021年9月39日、AWSの数多のカンファレンスで最も開発者に特化した AWS Dev Day Online Japanに登壇してきました。
資料はこちらです → [AWS CDK] 1,000+のCloudWatch Alarmsを自動生成する技術
感想を添えてご報告いたします。

TL;DR

  • 応援してくださった皆様に感謝
  • 登壇をネタに様々な人と繋がれた
  • これまでのAWS CDKに関するLTの集大成
  • Twitterの反響・よいアンケート結果

応援してくださった皆様に感謝

AWS Dev Day Online JapanはCFP制で、一般人気の高いセッションが採用される仕組みでした。
Twitterのお知り合いの皆様やそうでない方にも「見たい!」との反応をいただけたのが、今回の機会につながったと考えています。ありがとうございました!良いものをお届けできたでしょうか?

登壇をネタに様々な人と繋がれた

AWS CDKに関する登壇であったことから、周囲の知見のある方にレビューや練習を申し込ませていただきました。
コロナ禍のリモートワークの折、しばらくぶりのエンジニアの方と練習ついでに雑談もでき嬉しかったです。

また、資料レビューにはAWS CDKユーザーとして大先輩(とこれまで勝手に尊敬していた)のtmkさんにもご協力いただきました。本当にありがとうございました!
CDKアプリを我流で書いてきた自覚があったので、登壇前に客観的なご意見をいただけてとても安心しました。

これまでのAWS CDKに関するLTの集大成

実は今回初出のネタは多くなく、私のCDKに関するLTの集大成だったりします。
普段から気づいたことを細かく発信しており、それが今回の登壇につながったと感じております。

Twitterの反響・よいアンケート結果

登壇中・登壇後にTwitterで知らない方(でもCDK Loverの方)と繋がれるのが登壇の魅力だな、と思っています。
情報は発信する人に集まる、という言葉もありますしね。

登壇後しばらくして事務局からアンケートもいただき、詳細は共有できないものの(私だけで)累計数百人のオーディエンスがいたとのことでした。初めての数でびっくりしています。 (Startup Architecture of the Year 2019も 50~200人くらいだった気がするので(目測)、それよりも多い人数でした)

まとめ

いろんな方の応援のおかげで、得難い経験をさせてもらいました。
今後も社内で面白い取り組みをして発表していきたいので、その際はぜひご視聴・ご意見いただけると幸いです!

面白いSREの仕事をしたい!という方はこちらからご応募お願いします🙏