Classi開発者ブログ

教育プラットフォーム「Classi」を開発・運営するClassi株式会社の開発者ブログです。

monorepoでのWeb Components配信アーキテクチャ

コミュニケーションチームのid:NozomuMiyamotoです。

Classiでは、2021年12月のAngularJSのEOLに向けて、Web Componentsを利用した段階的AngularJS脱出作戦を進めてきました。これはAngular Elementsと呼ばれるAngularで作成したコンポーネントをWebComponentsに変換する技術を用い、部分的にシステムリプレースを行う作戦です。 今回は、そこでぶつかった課題と解決に用いたWebComponentsの配信アーキテクチャについて紹介します。

背景

Classiにはplatformと呼ばれる最初期から存在する巨大なリポジトリがあります。このリポジトリには様々な言語で書かれた複数のサービスが共存しており、全体像を把握することが非常に困難です。これを管理可能なレベルまで解体するとともに、EOLを迎えるシステムの置き換えを行うことが、コミュニケーションチームのミッションの1つです。前述の動画で紹介したAngular Elementsを用いた脱出作戦は、フロントエンドのスコープで、それを果たすための施策でした。

当初、AngularElementsのリポジトリは機能ごとに作られており、成果物を手動でplatformに移植する運用がなされていました。勿論、こうした運用は望ましくなく、オペミスによる障害も引き起こしかねません。ただ、こうした問題の改善をしようにも、リポジトリが分かれていては、その分だけメンテナンスに力がいります。こうした事情から、我々はNxを用いて機能別にリポジトリが散らばったAngular Elementsのモノレポ化を行い、アジリティと安全性を高めるためにデプロイフローの改善に踏み出しました。

問題

モノレポ化により開発体験の向上という恩恵が得られましたが、また別の課題に直面することにもなりました。それはバージョン管理と切り戻しに関する問題です。 全ての機能のAngularElementsを単一のパッケージでバージョン管理した場合、過去のリリースで障害が発生した際に関係ない機能まで切り戻す必要が生じてしまいます。

例えば、アンケート機能とカレンダー機能の2種類の機能がAngular Elementsで提供されており、同一バージョンのパッケージしか利用できないとしましょう。ここで以下のようなケースが考えられます。

  1. アンケートへの機能追加のためにAngular Elementsのパッケージをv1.1.0にアップデートする。
  2. カレンダー機能の修正のためにAngular Elementsのパッケージをv1.2.0にアップデートする。
  3. 1週間後、1. のリリースが原因で致命的な欠陥が見つかる。
  4. Angular Elementsのバージョンをv1.0.0まで切り戻す必要が生じる。

このケースでは、障害の原因はアンケートへの機能追加であるにも関わらず、原因と無関係なカレンダー機能の修正まで切り戻されてしまいます。

目的

この問題は、配信パッケージを分けるなどの方法で機能ごとにバージョンを選択できる状態にしてやれば簡単に解決します。ただ、背景で述べた通り、platformレポジトリはすでに複数のコードにより煩雑になっているため、これ以上、依存関係や開発環境を複雑にするのは望ましくありません。そこで、ここではplatformレポジトリにできる限り変更を加えず、機能ごとのバージョン指定ができるようにすることを目指します。

採用したアプローチ

結論から言えば、我々はURLベースでWebComponentsのバージョン管理を行う手法を採用しました。これは、次のようなscriptタグを用いて、指定したバージョンのWebComponentsを読み込む手法です。

<script src="https://cdn.classi.jp/elements/v1.0.0/questionnaire.js"></script>

(古くからあるCDN経由でJavaScriptのライブラリを読み込み方法ですね)

非常にシンプルな手法ですが、移植先の環境に殆ど変更を加える必要がないですし、npmなど、JSのパッケージマネージャーがない環境でも容易に扱えます。唯一、CDNの環境を整えることだけが少々手間ですが、この作戦の最後にはAngularアプリケーションとして独立したSPAをCDN経由で配信するためこれは将来への投資になると考えられます。

アーキテクチャ

具体的には、Conventional Commit + semantic-release + GitHub Actions +S3 + CloudFrontの構成で下記の流れで、機能ごとに指定したWebComponentsのパッケージを読み込みます。

  1. 開発者がConventional Commitに従ってコミットしてoriginにpushする。
  2. GitHub ActionsがSemantic Releaseを実行し自動バージョンニングを行う。
  3. GitHubActionsがビルドされた成果物をS3 Bucketにアップロードする。
  4. 開発者がplatformのscriptタグの向き先を任意のバージョンのURLに変更する。(※)

※ ここで次のようにバージョンを指定できるため、機能ごとのバージョンアップ(もしくは切り戻し)が可能となる。

<script src="https://cdn.classi.jp/elements/v1.1.0/questionnaire/main.js"></script>
<script src="https://cdn.classi.jp/elements/v1.0.0/calendar/main.js"></script>

採用しなかったアプローチ

もちろん、他にもいくつかのアプローチを検討しましたが、それらについて採用に至らなかった理由を以下で述べます。

npm + Nx Multi Packaging

Nxのマルチパッケージング機能を用い、機能ごとにAngularElmentsをバージョニング・パッケージングし、npmのprivate repositoryで配信する手法です。npm linkが利用できることは開発においても魅力的ですが、フロントエンジニア以外もplatformレポジトリを頻繁に触るため、アクセスキーの取得などが開発環境を複雑にすると考え採用に至りませんでした。

GitHub Release

GitHubのRelease機能を用いて機能ごとに各バージョンのビルドされたWebComponentsを保管し、GitHub Actionsからそれらを取得する手法です。GitHub Actions内で取得する各機能のバージョンを指定するために、何らかのバージョン管理手段を別途用意する必要があるため採用しませんでした。

Git Submodule

AngularElementsのモノレポをplatformのsubmoduleにし、サブモジュールごとビルドする手法。AngularElementsのモノレポ化の構想段階ではこの手法を用いる予定でしたが、今回の切り戻しの問題が懸念されたため採用しませんでした。

むすび

WebComponentsはその性質上、一般公開を前提とするため、URLに基づくバージョン管理で十分に要件を満たせると考えます。また、シンプルな構成で利用先への要求がscriptタグの利用以外ないことは大きなメリットです。Railsなどのnpmを用いていない環境下でのシステムリプレイスにも役に立つでしょう。

残された課題として、コンポーネントの粒度でも同じ問題が起こり得ることが挙げられます。これを防ぐためには、同様にコンポーネントの粒度でバージョン指定を可能にする必要がありますが、その場合、各コンポーネントに@angular/coreの一部がバンドルされることになるので全体のファイルサイズが膨れ上がります。それを避けるためには、共通モジュールのみを初期でロードし、各コンポーネントは必要に応じてLazyLoadできるような構成を取る必要があるでしょう。

WebComponentsを用いた取り組みはまだ事例が少なく、我々も手探りで進めている状態ではありますが、その分、この技術には大きな可能性を感じています。Classiでは現在も開発者を募集しておりますので、こうした取り組みにご興味のある方は、ぜひ一度ご連絡ください!

それではまた次回の投稿をお楽しみに!

Mock Service WorkerでAPIをモックして開発をスムーズに進められた話

こんにちは。開発本部 認証連携チームでエンジニアをしている id:ruru8net です。前回はこちらの記事を書かせていただきました。
tech.classi.jp

現在は認証基盤再建というプロジェクトの中で、主にフロントエンド開発を担当しています。この記事ではフロントエンド開発においてAPIのモックのために「Mock Service Worker」を使ったところスムーズに開発を進めることができたので、使い方を紹介したいと思います。
mswjs.io

ツールの導入

弊社ではフロントエンドのフレームワークにAngularを採用しているのでAngularでの導入手順を記します。
基本的にはドキュメントの手順通りです。

1. インストール

$ npm install msw --save-dev
# or
$ yarn add msw --dev

2. モックを定義

src/mocks/handlers.tsを作成し、ファイル内にモックしたいAPIを定義します。

$ mkdir src/mocks
$ touch src/mocks/handlers.ts

今回はREST APIをモックするようにするので以下のように定義します。
src/mocks/handler.ts

import { rest } from 'msw';
    
export const handlers = [
  rest.get('/status', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        success: true
      })
    );
  }),
];

3. Mock ServiceWorker CLIのinitコマンドを実行

以下のコマンドを実行します。
(使うフレームワークによってディレクトリのパスが異なります。他のフレームワークの場合はこちら https://mswjs.io/docs/getting-started/integrate/browser#where-is-my-public-directory )

$ npx msw init ./src --save

コマンドを実行するとsrc配下に mockServiceWorker.jsが生成されます。
またAngularの場合はドキュメントに、

and add it to the assets of the angular.json file

とあるので、その通りにangular.jsonのassetsにmockServiceWorker.jsを追加します。

angular.json

{
  --- 省略 ---
     "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
           ...,
            "assets": [
              "src/favicon.ico",
              "src/assets",
+             "src/mockServiceWorker.js",
              "src/robots.txt"
            ],

4. workerを定義

src/mocks/browser.ts を作成し、workerを構成します。
https://mswjs.io/docs/getting-started/integrate/browser#configure-worker

$ touch src/mocks/browser.ts

src/mocks/browser.ts

// src/mocks/browser.js
import { setupWorker } from 'msw';
import { handlers } from './handlers';

 // This configures a Service Worker with the given request handlers.
export const worker = setupWorker(...handlers);

5. workerを起動

https://mswjs.io/docs/getting-started/integrate/browser#start-worker
src/main.ts内にて開発環境でのみworkerを起動するよう定義します。
弊社ではAPI用のURL(apiBaseUrl)がenvironmentに定義されていない場合にworkerを起動するようにしました。
またplatformBrowserDynamic().bootstrapModule(AppModule);よりも前にworkerが起動されていないと、ngOnInit()などで行われるAPI呼び出しがworker起動よりも先に行われNot Foundとなってしまったので、workerの起動が完了してからplatformBrowserDynamic().bootstrapModule(AppModule);を実行させるようにしています。

src/main.ts

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
    
if (environment.production) {
  enableProdMode();
}

async function prepare() {
  if (environment.apiBaseUrl === '') {
    const { worker } = await import('./mocks/browser');
    return await worker.start();
  }
  return;
}

// woker起動であるprepare()が完了してからplatformBrowserDynamic().bootstrapModule(AppModule);を実行する
prepare()
  .then(() => {
    return platformBrowserDynamic().bootstrapModule(AppModule);
  })
  .catch((err) => console.error(err));

6. モックが正しくできているかの確認

  • yarn startなどでサーバーを起動して開発者コンソールを確認します。workerが正しく起動されている場合、以下が表示されます。

    f:id:ruru8net:20220329122725p:plain
    workerが正しく起動した場合のコンソール表示

  • モックしているAPIが呼ばれる度にコンソールに表示されるので、モックの内容が正しいかどうかもコンソールにて確認ができます。

    f:id:ruru8net:20220329122914p:plain
    モックが正しく呼ばれた場合のコンソール表示

  • 404が出る場合は、正しくモックできていない可能性が高いので、src/mocks/handler.tsを見直したり、APIのパスが誤っていないかを確認します。

    f:id:ruru8net:20220329122924p:plain
    モックが呼び出せず404エラーになった場合のコンソール表示

便利だったオプション

Mock Service Workerには様々なオプションがあります。
ドキュメントが丁寧なのでそちらに記載されているのですが、その中でも自分が実際に使い、便利だなと思ったものを紹介します。

request.bodyでの分岐

以下のようにrequest内のbodyを見て、処理を分岐することができます。
これによって正常系や異常系の分岐を簡単におこなうことができました。

src/mocks/handler.ts

rest.post('/login', (req, res, ctx) => {
  const { username } = req.body as { username: string };

  if (username === 'unknown') {
    return res(
      ctx.status(404),
      ctx.json({
        success: false,
        errors: {
          message: 'このユーザは存在しません。',
        },
      })
    );
  }
    
  return res(
    ctx.status(200),
    ctx.json({
      success: true
    })
  );
}),

応答時間を遅らせる

delay() を使うことで応答までの時間を指定秒遅らせることができます。
https://mswjs.io/docs/api/context/delay
レスポンスを受け取ってから処理をするといったような同期的な実装が正しく動作しているかを確認するのに便利でした。

src/mocks/handler.ts

rest.post('/status', (req, res, ctx) => {
  return res(
    // 1000ms = 1s 遅らせる.
    ctx.delay(1000),
    ctx.status(200),
    ctx.json({
      success: true
    })
  )
})

ネットワークエラーを返す

以下のように書くことで、ネットワークエラーとしてレスポンスを返すことができます。
https://mswjs.io/docs/api/response/network-error

src/mocks/handler.ts

rest.get('/status', (req, res, ctx) => {
  return res.networkError('Failed to connect')
})

コンソールではこのように表示されます。

f:id:ruru8net:20220329121210p:plain
MSWでnetworkError()をreturnした場合のコンソール表示

ネットワークエラーのハンドリングを実装する際にとても便利でした。

Mock Service Workerを使った開発でのメリット

サーバサイドに依存しない

今回携わっていたプロジェクトではサーバサイドとフロントエンドでリポジトリも異なり、それぞれ別の人が開発をしていました。 また開発の進め方として、サーバサイドの開発者と一緒にAPIを決定し、定義書にまとめ、その後の開発はそれぞれで進めていくという流れでした。
フロントエンド開発ではMock Service Workerを使えばいくらでもAPIを検証することができ、サーバサイドの開発進捗に依存することなく進められたことが一番良かったです。
開発が進む中で何度かAPIの仕様が変わることもありましたが、src/mocks/handler.ts を修正すれば済むので、APIの追加や修正に負担もなくとても開発が進めやすかったです。

ユーザーエラーの再現のしやすさ

ネットワークエラーはユーザーによく起こりうるものでありながら、開発環境ではなかなか再現がしづらいと感じていました。しかし先程紹介したようにオプションで簡単に定義することができます。その他のエラーも開発環境での再現ができるのでハンドリングのしやすさも大きなメリットの1つだと思います。

型定義を通して実際のレスポンス形式と同期が取れる

handler.tsにモックを定義する際のレスポンスにも、実際のAPIのレスポンスで使用している型をimportすることで、APIの変更があった場合にモックの修正にも気がつくことができます。同期が取れるように型を意識しておくことも大事だと思います。

api.service.ts

type GetStatusResponse = {
  success: true;
  status: string;
};

async getStatus(): Promise<LoginMethod[]> {
  return await this.http
    .get<GetStatusResponse>(`/status`)
    .toPromise()
    .catch(error => console.log(error));
}

src/mocks/handler.ts

import { rest } from 'msw';
import { GetStatusResponse } from 'api.service.ts'

export const handlers = [
  rest.get('/status', (req, res, ctx) => {
    const response: GetStatusResponse = {
      success: true,
      status: 'hoge',
    }
    return res(
      ctx.status(200),
      ctx.json(response)
    );
  }),
];

Mock Service Workerを使った開発でのデメリット

他のモックの手法を使ったことがなく、他との比較はできないのですが困ったところは今のところ全くないです。
もし今後新たな開発をすることになっても Mock Service Workerを採用したいと思っています。

今後やっていきたいこと

モックAPIの定義ファイルのリファクタ

src/mocks/handler.ts に全てのAPIを羅列してしまうとファイルの行数が膨大になり、可読性が下がります。 ファイルをいくつかに切り分け、APIを種類ごとに分類するといったようなリファクタが必要であると思い、現在試行錯誤しています。
できる限りAPI定義書と合わせて見た際に理解がしやすい構成にしたいです。

まとめ

弊社では毎週「フロントエンド互助会」といった各プロジェクトのフロントエンド開発をしているエンジニアが集まり、進捗共有や相談を行う時間があります。
今回、そこでMock Service Workerについて共有したところ、「便利そう」「使って見たい」と言った声や、実は他のプロジェクトでも使っていて、そこではこんなファイル構成をしているよ、といったお話を聞くことができました。またこのブログを執筆する機会もこちらで提案してもらい、書かせていただいてます。
まだまだ社内でのMock Service Workerの浸透率は高くないので、次の一歩として布教活動と今後Mock Service Worker使っていくというプロジェクトがあれば積極的にサポートをしていきたいです。

開発メンバーの保守運用スキルを上げるため実施している朝当番制度の紹介

こんにちは、開発支援部基盤インフラチームの kenryooo です。 Classiでは過去の高負荷によるアクセス障害での反省を踏まえ、エンジニア向けに保守運用スキルを高める施策として、朝当番という制度を運用しています。今回はその紹介をします。

目的

朝当番制度は、下記を目的に運用しています。

  • Classiのピークタイム(毎朝8:00 - 9:30)に問題が起きた場合、社内向けにスムーズな情報連携を行う
  • サービス品質の継続的な改善
    • パフォーマンスや監視内容に異常があった場合や、依存している外部接続システムやSaaSのメンテナンス情報などを担当チームへ共有する
  • 担当エンジニアの育成
    • Classiシステムの全体像の理解
    • 担当外のアプリケーション(リポジトリ)の理解
    • システム監視の入門(Datadog)
    • インシデントハンドリングの入門

背景と課題

朝当番制度は、下記の背景と課題感からスタートしています。

  • 上で述べた、先のアクセス障害時に朝のピークタイムに不安定な状態になることから、対応をスムーズに行うために朝当番担当を設けることになった
  • 人的コストを払って、エンジニアがただ張り付くだけではもったいないので育成の場にすることとした
  • Classiのアーキテクチャは、マイクロサービスではなく分断されたモノリス(複数のアプリケーションが同一のDBに依存している)であり、アプリケーション間でDBを介して密結合になっていることからシステム全体を理解しておく必要がある
  • もともとアラートにすぐ反応するのがシニアエンジニアに偏っており、対応できるエンジニアを育成するため成長機会を用意する必要があった
    • アラートにすぐ反応できない理由として、どう動いていいかわからない、対応できる自信がない、参加メンバーの多いチャンネルで発言することに抵抗がある、などがあった
  • ClassiではDatadogを活用しているが、開発者から「APMは使えるがシステムメトリクスの見方がわからない」という声があった
    • どのシステムメトリクスを見ればサービスに大きい影響を与える事象が起きていることがわかるか、が暗黙知になっていた

期待される役割

朝当番担当エンジニアには、ペア・モブでのオンボーディングを経て下記の役割が期待されることを伝えます。

  • アラートのディスパッチャーとして機能できること
    • アラートを担当チームへ適切に共有する
    • アラートの原因の調査や対応は、必ずしも行う必要はない
      • 複雑なシステム全体を理解し、対応まで行うのは現実的に難しい
  • 担当は基本的に一人
    • 当事者意識を向上させ最大限の効果を得るため
    • ただし、いつでもSREがサポートできる体制を確保している

期待される効果

朝当番を通して、下記の効果を期待しています。

  • アラートや外部接続システム、依存しているSaaSの異常に目を向けられるようになる
  • アラート対応への心理的障壁を下げて、まず反応できるようになることを目指す
    • アラート対応の速筋を鍛える
  • エスカレーション時に、所属しているチーム外の普段関わりにくいメンバーとやりとりをすることで、単純接触効果(ザイオンス効果)によるコミュニケーションの改善を狙う
    • リモートワーク下で減ったエンジニア間のゆるいコミュニケーションを補う

運用

朝当番メンバーの募集から参加までは、下記の内容をもとに実施しています。

  • メンバー募集
    • 挙手制
      • 家庭の都合(子供の送迎など)や、朝弱いタイプのメンバーを考慮
  • 担当期間
    • だいたい3ヶ月を目処
      • 本業に負担をかけすぎないことを意識
  • オンボーディング
    • 説明会
      • 責務や勤怠の説明
      • アラート通知、エラートラッカーの通知チャンネルの説明
      • Datadog のダッシュボードの説明
      • エスカレーションの方法
      • 参考書籍の紹介(入門 監視)
    • ハンズオン
      • 2〜3回、既存メンバーとペアまたはモブ形式で実施
      • システム構成や対応するダッシュボードの説明
      • 各アプリケーション(リポジトリ)と担当チームの説明
      • Datadogでの主要なメトリクスの解説
      • 既知のエラーの紹介
      • 終了時に記載しているレポートとサマリーの説明
        • リクエスト数の変化
        • ピークRAU(Real Time Active User)
        • alert/warning/error
        • 依存SaaSの稼働状況
        • 気づいたこと/やったこと
        • 申し送り

朝当番の振り返り

私の所属する基盤インフラチーム主催で、Infra Performance Working Groupという会を隔週で開催しています。 ユーザーの利用動向やシステムの稼働状況、アラートの対応状況、インフラコスト面の振り返りを行っており、その中で朝当番の振り返りも実施し改善につなげています。

結果

2020年9月からこの制度を運用を開始して、これまで19人のエンジニアが朝当番を経験しました。 制度開始以前は、数人のシニアエンジニアが中心になって行っていたアラート対応ですが、現在は新卒2年目くらいのエンジニアが中心となって問題の対応にあたることも多くなってきています。また、システムメトリクスも意識するプロダクト開発エンジニアが増えたことで、保守運用面での改善に自立的にリソースを割くことが増えたと感じています。過去に掲載したこれらの記事も、そうした影響を受けたものです。

結果として、基盤インフラチーム所属のSREは、本来やりたかったSREingに取り組める余裕が出てきました。

結び

エンジニア(開発者)の保守運用スキル向上についての取り組みについて紹介しました。 継続的に質の良いサービスを提供するためには、アラート対応、インシデント対応をできるメンバーを増やすことは重要ではないでしょうか。

Classiでは、SREプラクティスを活用してサービス基盤を改善していきたいエンジニアを絶賛募集しています!

go generateを忘れていないかCIで検出する

こんにちは・こんばんは・おはようございます 開発支援部基盤バックエンドチームのid:aerealです。

今回は小粒でピリリと辛いちょっとしたテクニックをお届けします。

Goとコード生成

Goとコード生成は密接な関係にあります。 なにしろ go generate というサブコマンドが提供されているほどです。

Classiが開発しているGoを採用したミドルウェア・dronにおいてもgomockによるモック実装の生成やOpenAPI仕様からGoの型の生成などで利用しています。

参考: dron: クラウドネイティブなcron代替の紹介 - Classi開発者ブログ

コード生成とRenovate

コード生成結果に係る変数は主に2つあります。

  1. 入力となるソースコード
  2. 生成するツール

ソースコードは言うまでもありません。リポジトリで管理されるソースコードは常に変更される可能性があります。

忘れられがちなのがコードを生成するツールです。

Go製の開発用ツールもバージョン管理している場合、dependabotやRenovateで他のライブラリ同様に更新がPull Requestで送られます。

アプリケーション内から使用されるライブラリであれば、十分なカバレッジを伴うテストコードがあればマージ前に非互換な挙動を発見できます。

ではコード生成に使うツールはどうでしょうか。ツールの更新に伴い生成結果が変わることもあります。 当然、テスト実行時にはコード生成を伴わないので発見できません。

性質が悪いことにコード生成 (go generate) はそう頻繁に実行するものではないので発見が遅れがちです。 その結果、入力となるソースコードも変更されてしまい一体どんな結果があるべきなのかわからなくなる……ということが考えられます。

では生成するツールを古いバージョンで固定するしかないのでしょうか。

生成したコードの差分をCIで検出する

前述の通りアプリケーション内から利用しているライブラリをある程度の自信をもって更新できるのは自動テストがあるからでした。 コード生成の結果を検証するステップをCIに組み込めばツールの更新が破壊的変更を含まないことを確かめられ、安心してツールの更新ができるというものです。

上記のdronを例にとると以下のstepを記述しています。

  • go generate を実行する
  • その後のワークツリーで git diff --exit-code を実行する

go generate は自明として、 git diff --exit-code について補足します。

--exit-code Make the program exit with codes similar to diff(1). That is, it exits with 1 if there were differences and 0 means no differences.

以上、git-diffのmanより。

差分があれば1を、無ければ0を終了ステータスに設定します。

シェルにおいて非ゼロの終了ステータスは異常を指すので「差分があれば失敗する」という挙動になります。

GitHub Actionsでは非ゼロの終了ステータスを補足するとデフォルトでそのジョブは失敗と報告されるので「go generate して差分があればジョブを失敗させる」ことができます。

おまけ: 自動でコミット・プッシュするというアイデア

GitHub ActionsはGitHubのAPIへアクセスする権限を持ったトークンが特別の設定なく与えられるという強みを持っているので、単純に差分が生じただけであれば git commit && git push させることも可能です。

マーケットプレイスにはAdd & commitのようなワークフローも公開されていますし、似たようなstepを書いてもよいです。

なおオーガニゼーションの設定や将来的なGitHub全体の変更によりトークンにデフォルトで付与される権限が狭められる可能性を考慮し、明示的に permissions を指定するのが望ましいでしょう。

参考: Automatic token authentication - GitHub Docs

むすび

コード生成した結果に差分が生じていないかをCIで検証する方法について紹介しました。

コード生成を活用するGoならではのモチベーションではありますが、一般的なアプリケーション開発に応用できるトピックではないでしょうか。

ClassiではPull Requestのコメント欄でよく見かける指摘を自動化させる気概のあるエンジニアを歓迎します!

コロナ禍で失われた交流と挑戦の機会を回復するためハードル低めの社内LT会を半年運営してみた話

 みなさんはじめまして。Classi で開発支援部に所属するイノウエと申します。

 当社は教育事業を手掛けているため、教育に関心のあるメンバーが集まっており、ジュニアメンバーに対する社内教育にも力を入れています。巷を騒がす感染症の流行により、当社でもリモート勤務体制がはじまったことで個人としては働きやすさが向上したものの、社内教育や他チームとの支援・交流といった面に目を向けると決して良いことばかりではありません。また、以前では盛んに開催されていたウェブ開発者による勉強会コミュニティの縮小傾向も仕方のないこととはいえ無念に感じています。

 このような問題意識に対してなにかできることはないかと思い、「プチトーク会」なる社内イベントを企画し、運営してみたところ比較的感触がよく、まずは半年間続けることができました。この記事ではプチトーク会の目的と設計、運用してみた感想について記したいと思います。

f:id:ClassiJP:20220201112504p:plain

目的

  • 開発者メンバー同士の交流を促すこと
    • リモートワークが中心となった結果、他のチームとの交流の機会が格段に減ったことに対して手を打ちたい
    • 以前であれば立ち話や偶発的に発生していた機会を創出する
  • 発表の場を提供すること
    • コロナ禍で勉強会コミュニティの縮小によりイベントに行きづらくなった
      • 打席に立つことで初めて得られる経験もある
    • 発表の準備を行うことで自分自身の体験・知識が整理される
      • 他者に教えることこそ最高の学習法
    • 他者に対して何かを伝えるということは本質的に難しいということを身をもって学ぶ
      • 聞き手・受け手としての成長にも繋がる

背景と要件

  • 自発的に発表・参加するタイプのカジュアル勉強会は既に社内に存在する
    • 内容は面白く充実しているが、正直参加率が芳しくない
      • 毎回参加する人とまったく参加しない人の二分化
    • 挙手制ではうまく行かない?
      • 他にも自主的に参加する系の集まりがいくつかあるがメンバーが偏る傾向がある(輪読会など)
      • 発表側に事前準備があったり、繁忙期に参加することについて内心チームメンバーに引け目を感じているのでは?
  • 運営・発表・聴衆の三方の負担をほんとうに最小限にすること
    • 私も開発者としての業務のかたわらで運営する必要がある
    • 発表者は事前の準備(スライド作りとか)が大変だとやはり負担になる
      • 指名制なので特に気を使わないといけない
    • 聴衆側も頻度が高いと別の予定と被りやすく負担になる
  • 半年に1度ぐらいは何か発表してほしい
    • 希望を言えばもっと高頻度だと嬉しいけど、業務の片手間としたらこのくらいがちょうどよさそう
    • 開発者メンバーは100名弱なので年間200コマ弱必要

設計

  • 開催スケジュール
    • 30分枠で隔週開催
      • 年52週なので26回開催
    • 30分を1人あたり4分として7コマに分割
      • 7コマ × 26回 = 年182コマで要件に対応
  • 発表レギュレーション
    • 1トークあたり発表3分+質問応答1分で時間厳守(LT大会のドラ方式)
      • 最悪3分であれば当日15分ぐらいあればババっと書ける(と思う)
      • 時間制約上本当に3分で切らないと間に合わないので容赦なく切る
      • 質問は1回のみで質問時間も含め1分以内でバッサリ切る(質問できなかった場合は esa のコメント欄に誘導する)
    • 自己紹介禁止
      • 経験則で発表慣れしてない人ほど自己紹介に時間を半分以上使ってしまい本題に入れないため
      • どうしても入れたければ発表の一番最後にしてもらう
    • テーマは完全自由
      • 自分で決められない人向けのお題ガチャもある(趣味or技術)
      • 私は庭の雑草対策の話をしました
    • 発表スライドは esa のプレゼンテーション機能を使う
      • Markdown から自動変換で作成されるため華美で複雑なスライドを作れない、作れないということは見た目を気にする必要がないので時短◎
      • 制約により内容を簡潔にする必要があるため資料準備の時短◎
      • 自然と esa の1記事となるので情報がストック(蓄積)されて、フィードバックにコメント機能が使える
  • 抽選による指名で発表者を決定する
    • 一巡するまで発表者のかぶりがないようにする

実装

  • Google カレンダーで最大公約数的に比較的予定が入っていなさそうな30分枠を見極めて隔週30分の繰り返し設定で予定を突っ込んでしまう
    • 先に枠を抑えておけば将来的にかぶらなくなっていく
  • Google スプレッドシートを作成して先程のカレンダーから生成された日付を突っ込む
  • 従業員名簿に適当に序数を振った上でかぶりなし乱数を生成して1開催日あたり7人になるように割り振る
  • 運営用の Slack チャンネルを作って次回開催日の発表者にメンションで事前お知らせをする
    • 都合が悪ければ当事者に日時の交換を調整してもらう、交換の調整を行うこと自体も交流のひとつになるので運営者はあまり介入しない
  • 開催日当日は直前にお知らせ用チャンネルで呼び込みする
  • 通話ツールは Google Meet を使い、発表者がそれぞれ資料を画面共有する方式
    • 普段は画面共有機能を使わない人もよい練習になる
  • タイムキープはスマートフォンのストップウォッチで行う
  • ドラ(3分強制終了)の音はいいアイデアがないので私の声(じゃ〜ん)でやっている(恥ずかしい・・・)

結果と感想

 運営者は先程の Google スプレッドシートにたまに乱数を生成して埋めるだけでいいので、ほぼ手間なしで半年間運用することができました。(発表者が一巡しました)

 オーディエンス参加者も平均全体の7〜8割程度でかなり好調に推移しており、普段話したことがない同僚から思わぬ話が飛び出したりすることで、その後の交流やアクションに繋がっているようで、おおむね期待の通りの成果を出せているかなと思います。

 運営者の負担がほとんどかからない(当日の司会業ぐらい)ことから、当社だけでなく他社にも輸出できるフォーマットだと思いましたので、同じような課題感をお持ちの方がいればぜひ参考にしていただけると幸いです。

 続けてきた中でのマイナーチェンジとしては、やはり30分7枠だと時間の余裕が本当にないため、(いい質問してるな〜)と思ったタイミングでも強制終了せざるを得ないのがもったいなく感じ、若干時間オーバーしてもよい余地を持たせるために30分6枠にしたり、つい最近入ってきたばかりの新人メンバーを手厚く歓迎したいので質問時間をやや長めにしたりという工夫をしたりしていますが、ここは司会進行者のクセ次第だと思うのでまずはやってみて感触を確かめながら変えていくと良いと思います。

 また、コロナ禍が落ち着きを見せてコミュニティで発表する機会が復活した折には、飛び込みでLTを募集されたときなどにこのプチトーク会に向けて準備した内容や資料をそのまま流用することもできるはずなので、当社で自然と働いているうちに将来のチャンスに向けた資産を気づかないうちに蓄積できている、ということもウラの狙いの一つです。なかなか普段から意識的に行うのは難しいと思いますので、そういった側面からもメンバーに対する支援になれば良いと思っています。

 最後に宣伝ですが、 Classi では子どもの無限の可能性を引き出すために学校に特化したサービスを提供しています。これまでのキャリアで培った経験を子どもたちの未来のために活かしてみませんか?興味がある方はぜひこちらから応募をお願いします。

新入生募集中

「Python FlaskによるWebアプリ開発入門」の紹介

こんにちは、データAI部Pythonエンジニアの工藤( id:irisuinwl )です。 今回は弊社Pythonエンジニアである平田さんが著者の一人であるPython FlaskによるWebアプリ開発入門のご紹介をいたします。

私もレビューに関わり献本頂いたので、その魅力をお伝えできればと思います。

目次

  • 本の概要
    • 内容紹介
    • 誰向け?
    • ここが嬉しい
  • 各部の説明
    • 第0部 イントロダクション
    • 第1部 Flask入門
    • 第2部[Flask実践1]物体検知アプリの開発
    • 第3部[Flask実践2]物体検知機能のAPI化/デプロイメント
    • 第4部 機械学習APIの開発

本の概要

内容紹介

「Python FlaskによるWebアプリ開発入門」はタイトルの通り、Webアプリケーションを作りながらPythonのAPIフレームワークであるFlaskを学んでいく本です。
Flaskの概要と使い方を紹介した後に実践的にWebアプリケーションを作成していきます。

この本で作るアプリケーションとしては以下になります。

  • 認証機能のある簡単な問合せアプリケーション
  • 物体検知アプリケーション
    • アプリケーション実装
    • 機械学習APIの実装
    • テスト作成
  • 機械学習モデルの検討、データ分析コードを移行したWebアプリケーション

様々な機能のアプリケーションを作ることで実践的なFlaskの使い方を学べる非常に良い本だと感じました。

誰向け?

この本を読む上で向いている読者は以下のように思いました

  • 向いてる人

    • Pythonの文法を抑えているが、アプリケーションを作るためのHTTPの知識やインフラの知識が薄く、アプリケーション開発をしてみたい人
    • Pythonでモデル開発をしているが、プロトタイピングのためにAPIとWebアプリケーションを作成したいと思っているデータサイエンティスト
  • 向いてない人

    • Pythonを全く触ったことのない方

ここが嬉しい

この本を読んで嬉しいポイントを紹介します

  • 様々なフレームワークと比較してFlaskを使う利点が紹介されている
  • APIのコードだけでなく、APIを動かすインフラ、テスト、機械学習モデル開発といったWebアプリケーション開発に関連する項目を広範に扱っている
  • 説明が丁寧
    • APIを作る上で必要なHTTPの知識(Cookieやセッションなど)
    • 関連するPEPの提示
    • 関連するコードのディレクトリ構成が明示されていて分かりやすい

非常に説明が丁寧でアプリケーション開発に関わることをなるべく省略せず解説しているため、Pythonを使ってアプリケーション開発を始めようという人にとって非常に学びの深い本であると感じました。

続きを読む

完走したClassi developers Advent Calendar 2021を振り返って今年を締めよう!

挨拶

こんにちは・こんばんは・おはようございます。開発者ブログ編集部長のid:aerealです。

時は師走、ソフトウェアテック界隈ではおなじみとなったアドベントカレンダーの季節でした。

当社もClassi developers Advent Calendar 2021と銘打ち、25本の記事が集まりました。

これまでもClassiでアドベントカレンダーはやっていたのですが、今年は開発者ブログ編集部を立ち上げてから始めてアドベントカレンダーの企画・編集を編集部が執り行いました。 編集部員が5名に拡大したことにより25日間に渡る怒涛のレビューや進捗管理に挑戦できるだろうと踏んでのことでした。

今年は執筆者のユニークユーザー拡大を目標にこれまでアウトプットの経験が少なかったメンバーを中心に声をかけました。 おかげでアドベントカレンダーをきっかけに始めて記事を書いたよというメンバーも増えて何よりです。

今回は無事にアドベントカレンダーを完走したことを祝して編集部で改めて投稿された記事を振り返り、一言ずつコメントで紹介することにしました。 もしまだご覧になっていない記事があったらぜひこの機会に読んでみてください。

振り返り

12/1 id:tetsuro-ito: 開発本部長になってやったこと - tetsuroitoのブログ

id:ClassiJPより

職位の大きな変更に際し、短い期間で何を考えどう動いたかがまとめられています。見返しててあらためて思ったのですが、1ヶ月で100名弱のメンバー全員との1on1をやったり新部署を作ったり、ここに書かれていることだけでもタフなムーブすぎて驚きです。

12/2 沼沢一樹: GitHub に AWS 認証情報を持たせずに、Actions で S3 Backend な Terraform の plan を実行する - Qiita

id:aerealより

ホットな AssumeRoleWithExternalIdentity の話題でした。

こういった認可まわりを変える際って一般的な書き方になっていて文書から仕様を読み取りづらかったり、試行錯誤するのも大変だったりするので身近な人の事例がまとまっているととても助かります。

余談ですがJWTの sub にリポジトリが入っていてtrust policyでそれを参照できるの、JWTの良いところが出ているなあと思います。こういうところが好きです。

12/3 id:tkdn: JSConf JP に参加してきました - Classi開発者ブログ

id:kiryuanzuより

今年の11月に開催された JS Conf についての感想が書かれた記事です。発表を聞いてアクセシビリティについて改めて学んだ話や OSS やソフトウェアが成立するためのプラットフォームの話など、多方面な発表が行われたようで、自分自身は未参加でしたが大変興味をそそられました。年末年始にアーカイブの方で聴講したいと思います!

12/4 Sab: iOS15からのキーボード回避[UIKit]

id:tkdnより

OS のキーボードが表示された際にどうするか、iOS 15 から実装可能になった UIKeyboardLayoutGuide を紹介していただきました。単に機能紹介ではなく、じゃあこれはどうなの?といった気になるところまで掘った良記事でしたね

12/5 id:lime1024: ecspresso をより安全に使うために - らいむぎばたけ

id:tetsuro-itoより

ecspressoを安全に使うためのモチベーションや実際にハマったポイントなどをコードとセットで紹介していて、他の人の参考にもなりそうでした。

12/6 youichiro ogawa: Next.js, Prisma, Apollo GraphQL, Nexusで作るシンプルTODOアプリ

id:ClassiJPより

実際に作りながら、各種ライブラリはどうして、何に使うのかが丁寧に記載してあってとても参考になります。GraphQL Nexusは知らなかったけど便利そう!って思ったのであとで触ってみようと思いました。

12/7 id:kazumeat: リモートワークのための質問力向上研修を実施しました - Classi開発者ブログ

id:kiryuanzuより

今回のアドベントカレンダーで最多ブックマークを記録した記事です🎉

社内で実施された質問力向上研修について大変丁寧かつ分かりやすくまとめていただきました。質問をする際や回答する際に「この伝え方で大丈夫かな?」と困ったらこの記事を見返したいと思います。

12/8 id:kiryuanzu: Hardening 2021 Active Fault 参加レポート - 桐生あんずです

id:aerealより

id:kiryuanzuさんのこの記事をきっかけにHardeningという競技を知ったのですが、実際の業務にかなり近い……というかほぼそのままといって良い内容で精巧さに驚きました。

「セキュリティホール90%オフ」というチーム名はとんちが効いていて好きです。

12/9 id:ruru8net: Amazon EventBridge(CloudWatch Events)で動かしているバッチをDatadogで監視する仕組みを構築した話 - Classi開発者ブログ

id:tkdnより

Datadog 日本の公式 Twitter にも捕捉されていました。バッチの監視といったところだけではなく、社内の Datadog 勉強会でもカスタムスパンを使ったトレーシングの知見も発表していた id:ruru8net のナイス監視実践記事。

2 年前の記事もリンクされており若手の成長を感じる良い記事ですね。

12/10 id:tomoyanamekawa: Cloud Composer 2へのupgradeでどハマりした話 - Classi開発者ブログ

id:testuro-itoより

最近GAになったCloud Composer2のアップグレードに際して、ハマりやすいポイントを解説していて、良かったです。ドキュメントもまだ少ないので、貴重な記事でした。

12/11 id:onigra: EC2からECSへ移行する道のり - Classi開発者ブログ

id:ClassiJPより

成長機会を積極的に取りに行く若手メンバーとそれを導くムーブができる人がいてめちゃめちゃいい会社じゃん〜と思ったら弊社でした。僕も横で見てましたがすごい勢いで作業が進み若者が成長していく様子は、見ててとても気持ちがよかったですしめちゃめちゃ刺激になりますね!

12/12 北原幹也: デザイナーとエンジニアの対話で、UX/UI の意思決定のボトルネックを解消する - Qiita

id:aerealより

「対話がうまくいっていない」という出発点から始まり、物の本を当たりながら「アンチパターンを踏み抜いていた」「ユーザビリティとはなんなのかの定義が揃っていなかった」「デザイナー視点での評価基準をエンジニアが把握できていなかった」と現状理解を深めひとつずつ解決していく丁寧なストーリーでした。

12/13 id:tasmaniadecoco: IAM Policy Simulator で「必要な権限足りてる?」を確かめる - Classi開発者ブログ

id:kiryuanzuより

IAM Policy Simulator についてはよく知らなかったのですが、自分も記事内の同じ課題に直面していたことが少し前にあったので大変参考になりました🙏

レビュー依頼時のコミュニケーション課題に対して問題意識を抱えて解決しようとする姿勢にも真摯な印象を受け、振る舞いや考え方に対しても参考にしていきたいと思える記事でした。

12/14 id:khaigo: UIKitでDesign Systemを実装する - Classi開発者ブログ

id:tkdnより

アプリにおけるデザインシステムを UIKit で実践する記事です。Swift のケースではどうなるかも記載があって静的型付けしたいよな〜わかる〜という感想が湧いてきました笑 今後の伸長に期待ですね!

tetoru(テトル) に関わる内容が開発者ブログでもちらほらと… id:sasata299エンジニアに役割を変えた記事でも触れています。

12/15 おかじ: 入社前に不安だったリモートワークでのコミュニケーションは実際どうだったのか

id:tetsuro-itoより

リモートワークにおいて、コミュニケーションが重視される中で、とても示唆に富む記事でした。リモート時に入社したことがある人とそうじゃない人のナラティブに端を架けてくれる記事で、いろんな人に読んでもらいたいですね。

12/16 id:seiga_hayashi: 育休を取得することにしたらチームの状態が改善した話 - 目だ!目を狙え!

id:ClassiJPより

とても大事な育児休業についてと、それを取得するために社内でどのような準備をしたかについて解説しています。これから育児休業を取る方の参考になる点もあるのではないでしょうか。

12/17 id:c5meg1012: バックエンドエンジニアが基盤インフラチームに異動して半年ほど経った話 - めるノート

id:aerealより

id:c5meg1012さんとは隣のチームで仕事しているのですが、産休・育休から復帰のタイミングでキャリアチェンジも図るというかなり意欲的な態度で驚くと同時に着実にキャッチアップされていて負けていられないなという気持ちになります。 ちなみにその後、AWS認定 ソリューションアーキテクト アソシエイトに合格されたそうです。

12/18 id:ttakagi1021: トピックモデルを使って問い合わせ内容を分析した話 - Classi開発者ブログ

id:kiryuanzuより

Classi を利用されているお客様からの問い合わせ内容を言語処理技術の一つであるトピックモデルを使って分析した記事です。分析の流れをわかりやすく述べており、その分析結果から今の課題や施策の効果などの考察について説明されています。 データ分析の活動から効果的な施策を検討していく流れの一例を知ることができ、大変面白い内容でした。

12/19 横田貴之: SwiftLintをGithubActionsのmacOSではなくubuntuで実行する

id:tkdnより

GitHub Actions の OS イメージが macOS になることのコスト感どこも課題に感じることはありますね。SwiftLint を動作させるためのステップが詳しく紹介されています。記事内に登場した GitHub Actions におけるトークンのパーミッションの制御は今年できるようになったんですよね。

id:aerealハマっていたようなので GitHub Actions の実行権限について考える場合はご注意を!

12/20 陳巍: 伝わるAPIドキュメントを作成する - Qiita

id:tetsuro-itoより

OpenAPIで伝わるための工夫を書いてくれました。育児でなかなか時間を作れない中でとてもよかったし、こういった工夫の繰り返しが円滑なAPI設計やコミュニケーションの流通を促しますね。

12/21 id:irisuinwl: FirestoreのCRUD APIを作って、負荷試験をする - irisuinwl’s diary

id:ClassiJPより

自作の負荷テスト用アプリケーションの解説にとどまらず、Firestoreとはどんなものかや負荷テストする上での考慮点等にも触れられていてとてもよかったです!

関係ないですが、本記事を読んでこういうツールがあるんだ〜と思ってlocustで検索したら虫の画像が出てきたのでちょっと慌てました。

12/22 id:nakaearth: 推理小説をグルーピングするために特徴語を抽出してみる - nakaearthの日記

id:aerealより

特徴語抽出して遊ぶシリーズは定番ともいえますが、ミステリ小説をかけてみるという発想がおもしろいですね。 word2vecなんかを使って意味・構文埋め込みをして叙述トリックが使われている作品とそれ以外の作品でなんらかの特徴が浮かんでくるんだろうか? という着想を得ました。

12/23 id:minhquang4334: ISUCON11予選課題の27万点まで練習し新人エンジニアが学んだこと - Classi開発者ブログ

id:kiryuanzuより

2020年秋に新卒として入社された id:minhquang4334 さんによる ISUCON11 の振り返り・復習記事です。ISUCON初参加で100/598位という結果も残されていることも大変凄いのですが、その後さらに知らなかったことに対して満遍なく吸収する姿勢が本当に素晴らしく、自分も見習おうとなりました。

12/24 id:JesseTetsuya: Flask App Builderでコンテンツマネジメントシステムとメタデータマネジメントシステムをさくっと作ってみたら役立った話 - Classi開発者ブログ

id:tkdnより

Excel 運用されていたデータのマスター管理によって調査やデバッグに時間がかかっていた課題、Excel に RDB のテーブルスキーマが整理されていたのがつらいという課題へソフトウェアを介入させ、まさに「ザ・カッとなってやった課題解決!」といった記事で気持ちがいいですね。

12/25 id:nkgt_chkonk: 2021年Classiに起こった変化の振り返り - Classi開発者ブログ

id:tetsuro-itoより

今年の技術視点でのふりかえり記事でした。読みながら、今年も変化してきたなと内省することができたし、これからの変化の仕込みにも期待ですね!

おわりに

記事投稿時にレビュアーとして目を通す機会もあり、その時に気付いたことがひとつあります。 それはこれまでに投稿した記事へ言及し「今はこうだよ」「これに刺激を受けてやってみたよ」といった内容が多かったことです。

実際、社内でも「○○さんが詳しいよ、前にこういう記事を書いていて──」と紹介するようなシーンがここ1年で増えたという実感があります。

それぞれ別々のチームで働いていてもやっていること・関心のあることに連続性があると改めて実感します。 そしてこの感覚を編集部だけではなく記事を書いたメンバーにもそれぞれ感じてもらえたことで、アウトプットを積み重ねることの大きな意義が少しでも伝わったのであれば編集部冥利に尽きます。

アドベントカレンダーというお祭りはまた1年後までのお預けになりますが、Classiはこれからも着実にアウトプットを通して研鑽していきます。 それではみなさま良いお年を!

© 2020 Classi Corp.