クラウドワークス エンジニアブログ 日本最大級のクラウドソーシング「クラウドワークス」の開発の裏側をお届けするエンジニアブログ 2024-03-08T13:00:00+09:00 crowdworks-blog Hatena::Blog hatenablog://blog/17391345971622185115 チームでアラート運用の見直しを始めました hatenablog://entry/6801883189088583712 2024-03-08T13:00:00+09:00 2024-03-08T13:00:00+09:00 アラート通知先のチャンネル運用を改善をチームで行いました。 行うにあたってSREチームなどに相談などを行いました。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiji_s/20240307/20240307180228.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <p>こんにちは、エンジニアの杉浦です。 法人様向けオンラインアシスタントサービス『<a href="https://bizasst.jp/client/">ビズアシ</a>』の開発を担当しています。</p> <p>突然ですが、みなさん、アラート通知先のチャンネル運用や通知されたアラートの整理は適切にされておりますでしょうか。</p> <p>ビズアシでは、現状、一つのチャンネルでスコープ外のものもまとめて通知されていたり、 通知設定しているもののあまり拾いきれていない状態が起きております。。。</p> <p>今回、これらの状態を解消するために動き始めました!</p> <h1 id="現在のアラート通知状況について">現在のアラート通知状況について</h1> <p><figure class="figure-image figure-image-fotolife" title="アラート通知状況"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiji_s/20240305/20240305153708.png" width="552" height="312" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アラート通知状況</figcaption></figure></p> <p>簡易的ですが、ビズアシでは、請求管理システムの<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>と、顧客用システムのReactとFlaskがあり、 監視する2チャンネルへそれぞれ通知を行っています。</p> <p>それぞれのチャンネルの振る舞いは、下記のようになっています。</p> <ul> <li>チャンネル(A): Rollbarからのエラー出力とDatadogからのモニタリング出力</li> <li>チャンネル(B): チャンネル(A)へ通知される以外のDatadogからのモニタリング出力</li> </ul> <h1 id="問題点">問題点</h1> <p>問題点は主に以下の3点があります。</p> <p>スコープ外のアラート通知: </p> <ul> <li>①エンジニアが対応できないエラーもチャンネルへ通知されてしまっている。</li> <li>②以前発生していたシステムエラーが再び起きていないか定期的に通知がくる。</li> </ul> <p>アラートのフォローアップ不足:</p> <ul> <li>③チャンネル(B)については監視が追いついていない状況。</li> </ul> <p>このように、調査すると上記の問題が上がっておりました。</p> <p>そこで、上記の問題点をチーム内で認識合わせをして、SRE Weeklyで相談をしました!</p> <p>※SRE WeeklyとはSREチームと各チームのインフラ担当などが週一で<a class="keyword" href="https://d.hatena.ne.jp/keyword/MTG">MTG</a>をして相談などを行う場所です。</p> <p>SRE Weeklyは、以下の様な目的で行っております!</p> <blockquote><p>組織横断での<a class="keyword" href="https://d.hatena.ne.jp/keyword/Web%A5%B5%A1%BC%A5%D3%A5%B9">Webサービス</a>インフラ関連の知見の共有</p> <ul> <li>仲良くなる(話しかけやすくなる)</li> <li>共に学ぶ</li> <li>興味関心を広げる</li> </ul> </blockquote> <h1 id="学んだ点">学んだ点</h1> <p>SRE Weeklyでは、各チームがどのようにアラートチャンネルを運用しているのかを聞きました!</p> <p>1チャンネルのみで運用しているチームもあれば、アラートの重要度で分けたり、全体障害とそれ以外の障害で分けて運用しているチームなどがあることを学びました。</p> <h1 id="これから">これから</h1> <p>学んだことを参考にビズアシ内では以下のように進めることで合意しました!</p> <p>問題点①エンジニアが対応できないエラーもチャンネルへ通知されてしまっている。</p> <ul> <li>この問題に対しては、別チャンネルへ通知する実装に切り替える。</li> </ul> <p>問題点②以前発生していたシステムエラーが再び起きていないか定期的に通知がくる。</p> <ul> <li>この問題に対しては、一旦このまま運用で進めるが根本対応後、引き続き通知をするか話し合う。</li> </ul> <p>問題点③チャンネル(B)については監視が追いついていない状況</p> <ul> <li>この問題に対しては、通知内容を精査して、精査後はチャンネル(A)に寄せる。</li> </ul> <h1 id="さいごに">さいごに</h1> <p>やること自体は特に難しいわけではありませんが、このような緊急性低めで重要性があるものも率先して頑張りたいと思います。</p> <p>以上、ビズアシ開発チームの杉浦でした。有難うございました!</p> <h1 id="Were-hiring">We're hiring!</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークス社では、様々なポジションのエンジニアを募集しております。</p> <p>ご興味のある方は、ぜひご連絡ください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcrowdworks.co.jp%2Fcareers%2F" title="採用情報 | 株式会社クラウドワークス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://crowdworks.co.jp/careers/">crowdworks.co.jp</a></cite></p> eiji_s Postmaster Toolsの迷惑メール率をDatadogで監視する hatenablog://entry/6801883189081477789 2024-02-19T10:54:53+09:00 2024-02-19T17:03:39+09:00 こんにちは。エンジニアの砂川です。 2023年10月頃にGoogle・Yahoo!から新しいメール送信者ガイドラインが出されました。 該当するメール送信者は、ガイドラインに沿っているか確認をし、沿っていない場合は対応する必要があります。 blog.google support.google.com 対応完了しきった皆様、お疲れ様でした。 絶賛対応中の方々、頑張っていきましょう。 ところで皆さんGmailのPostmaster Toolsの迷惑メール率の監視はいかがでしょうか? クラウドワークスではGmailガイドラインで定めているPostmaster Toolsの迷惑メール率を監視し、閾値を超… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sunakan/20240208/20240208125628.png" alt="OGP" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。エンジニアの砂川です。</p> <p>2023年10月頃に<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a>・<a class="keyword" href="https://d.hatena.ne.jp/keyword/Yahoo%21">Yahoo!</a>から新しいメール送信者<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AC%A5%A4%A5%C9%A5%E9%A5%A4%A5%F3">ガイドライン</a>が出されました。 該当するメール送信者は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AC%A5%A4%A5%C9%A5%E9%A5%A4%A5%F3">ガイドライン</a>に沿っているか確認をし、沿っていない場合は対応する必要があります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.google%2Fproducts%2Fgmail%2Fgmail-security-authentication-spam-protection%2F" title="New Gmail protections for a safer, less spammy inbox" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.google/products/gmail/gmail-security-authentication-spam-protection/">blog.google</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsupport.google.com%2Fa%2Fanswer%2F81126" title="Email sender guidelines - Google Workspace Admin Help" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://support.google.com/a/answer/81126">support.google.com</a></cite></p> <p>対応完了しきった皆様、お疲れ様でした。 絶賛対応中の方々、頑張っていきましょう。</p> <p>ところで皆さん<a class="keyword" href="https://d.hatena.ne.jp/keyword/Gmail">Gmail</a>のPostmaster Toolsの迷惑メール率の監視はいかがでしょうか?</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスでは<a class="keyword" href="https://d.hatena.ne.jp/keyword/Gmail">Gmail</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AC%A5%A4%A5%C9%A5%E9%A5%A4%A5%F3">ガイドライン</a>で定めているPostmaster Toolsの迷惑メール率を監視し、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%EF%E7%C3%CD">閾値</a>を超えるとアラートを通知するような仕組みを作り、運用をしています。</p> <p>この記事では、その仕組みをご紹介します。</p> <h2 id="なぜ作ったのか">なぜ作ったのか</h2> <ul> <li>定期的にPostmaster Toolsにログインして監視するのが面倒</li> <li>(サブ)<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>毎の迷惑メール率を確認する時、切り替えが面倒</li> </ul> <p>これらの面倒さを解決するために、各<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>のメトリクスを一覧できるようDatadogの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C0%A5%C3%A5%B7%A5%E5">ダッシュ</a>ボードとSlack通知をするような仕組みを作りました。</p> <h2 id="全体像">全体像</h2> <p><figure class="figure-image figure-image-fotolife" title="仕組みの全体像"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sunakan/20240208/20240208110827.png" alt="&#x4ED5;&#x7D44;&#x307F;&#x306E;&#x5168;&#x4F53;&#x50CF;" width="790" height="444" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>仕組みの全体像</figcaption></figure></p> <p>全体像としては上記のように定期的にPostmaster Tools <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>で迷惑メール率等を取得し、Datadogに送信、監視をしてSlack通知をしています。</p> <p>定期的に実行する処理部分は、Go言語で実装しました。</p> <h2 id="Postmaster-Tools-APIを利用する準備">Postmaster Tools <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を利用する準備</h2> <ol> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a> Cloudでプロジェクト作成</li> <li>サービスアカウントを払い出し(メールアドレスが発行されます)</li> <li>Postmaster Toolsのユーザー管理にて、監視したい<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>・<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B5%A5%D6%A5%C9%A5%E1%A5%A4%A5%F3">サブドメイン</a>毎に発行されたメールアドレスを登録</li> <li>払い出したサービスアカウントのキーを発行(<a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>)</li> </ol> <p>払い出したキーは<a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>ファイルとなりますが、本番環境では<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a>で扱いたいので、ローカル開発環境では以下のように<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a>を設定して開発します。</p> <pre class="code .envrc" data-lang=".envrc" data-unlink>export GOOGLE_APPLICATION_CREDENTIALS_JSON=$(cat ./credentials.json | jq -c &#39;.&#39;)</pre> <h2 id="Postmaster-Tools-APIを利用して迷惑メール率を取得">Postmaster Tools <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を利用して迷惑メール率を取得</h2> <p>Postmaster Tools <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を利用して迷惑メール率を取得するコードは以下のようになります。</p> <p>※一部実装を端折っています</p> <pre class="code lang-go" data-lang="go" data-unlink><span class="synStatement">package</span> postmastertools <span class="synStatement">import</span> ( <span class="synConstant">&quot;context&quot;</span> <span class="synConstant">&quot;fmt&quot;</span> <span class="synConstant">&quot;golang.org/x/oauth2/google&quot;</span> <span class="synConstant">&quot;google.golang.org/api/gmailpostmastertools/v1&quot;</span> <span class="synConstant">&quot;google.golang.org/api/googleapi&quot;</span> <span class="synConstant">&quot;google.golang.org/api/option&quot;</span> <span class="synConstant">&quot;os&quot;</span> <span class="synConstant">&quot;time&quot;</span> ) <span class="synComment">// OkTrafficStat はPostmasterTools APIで取得に成功し、利用する値だけを抽出したドメインごとの値</span> <span class="synStatement">type</span> OkTrafficStat <span class="synStatement">struct</span> { <span class="synComment">// ドメイン</span> <span class="synComment">// 例: domains/crowdworks.jp</span> DomainName <span class="synType">string</span> <span class="synComment">// 迷惑メール率</span> <span class="synComment">// 例: 0.001 -&gt; 0.1%</span> UserReportedSpamRatio <span class="synType">float64</span> } <span class="synComment">// NgTrafficStat はPostmasterTools APIで取得に失敗したドメインごとの理由</span> <span class="synStatement">type</span> NgTrafficStat <span class="synStatement">struct</span> { <span class="synComment">// ドメイン</span> <span class="synComment">// 例: domains/crowdworks.jp</span> DomainName <span class="synType">string</span> <span class="synComment">// 失敗した理由</span> <span class="synComment">// 例: &quot;notFound&quot;, &quot;badRequest&quot;</span> Reason <span class="synType">string</span> } <span class="synComment">// FetchResult はPostmaster Tools APIで取得したデータの集合</span> <span class="synStatement">type</span> FetchResult <span class="synStatement">struct</span> { OkTrafficStats []OkTrafficStat NgTrafficStats []NgTrafficStat } <span class="synComment">// FetchPostmasterTrafficStats はPostmaster ToolsのAPIを叩いた結果を返す</span> <span class="synComment">// 1. 環境変数 GOOGLE_APPLICATION_CREDENTIALS_JSON にある client_email が登録されているドメインのリストを取得</span> <span class="synComment">// 2. ドメイン毎にAPIを叩いた結果をスライスに詰め込み、全体の結果を返す</span> <span class="synComment">// TrafficStatの取得に失敗(2**系以外)でも、早期リターンせずに失敗としてスライスに詰め込んでいる理由</span> <span class="synComment">// - そのドメインで、対象日にメールが1通も送信されていなかったりしてデータが無いと404が返ってくる(これで早期リターンをしてしまうと、他ドメインのTrafficStatが取得できない)</span> <span class="synComment">// - 全ドメイン分のTrafficStatを一旦取得するため</span> <span class="synStatement">func</span> FetchPostmasterTrafficStats(targetDate time.Time) (*FetchResult, <span class="synType">error</span>) { ctx := context.Background() creds, err := google.CredentialsFromJSON(ctx, []<span class="synType">byte</span>(os.Getenv(<span class="synConstant">&quot;GOOGLE_APPLICATION_CREDENTIALS_JSON&quot;</span>)), <span class="synConstant">&quot;https://www.googleapis.com/auth/postmaster.readonly&quot;</span>) <span class="synStatement">if</span> err != <span class="synStatement">nil</span> { <span class="synStatement">return</span> <span class="synStatement">nil</span>, fmt.Errorf(<span class="synConstant">&quot;環境変数 GOOGLE_APPLICATION_CREDENTIALS_JSON からクレデンシャル作成に失敗: %v&quot;</span>, err) } gmailpostmastertoolsService, err := gmailpostmastertools.NewService(ctx, option.WithCredentials(creds)) <span class="synStatement">if</span> err != <span class="synStatement">nil</span> { <span class="synStatement">return</span> <span class="synStatement">nil</span>, fmt.Errorf(<span class="synConstant">&quot;NewService作成に失敗: %v&quot;</span>, err) } domains, err := gmailpostmastertoolsService.Domains.List().Do() <span class="synStatement">if</span> err != <span class="synStatement">nil</span> { <span class="synStatement">return</span> <span class="synStatement">nil</span>, fmt.Errorf(<span class="synConstant">&quot;DomainのList取得に失敗: %v&quot;</span>, err) } targetDateStr := targetDate.Format(<span class="synConstant">&quot;20060102&quot;</span>) <span class="synStatement">var</span> okTrafficStats []OkTrafficStat <span class="synStatement">var</span> ngTrafficStats []NgTrafficStat <span class="synStatement">for</span> _, d := <span class="synStatement">range</span> domains.Domains { parent := d.Name + <span class="synConstant">&quot;/trafficStats/&quot;</span> + targetDateStr trafficStat, err := gmailpostmastertoolsService.Domains.TrafficStats.Get(parent).Do() <span class="synStatement">if</span> err != <span class="synStatement">nil</span> { ngTrafficStats = <span class="synStatement">append</span>(ngTrafficStats, NgTrafficStat{ DomainName: d.Name, Reason: err.(*googleapi.Error).Errors[<span class="synConstant">0</span>].Reason, }) } <span class="synStatement">else</span> { okTrafficStats = <span class="synStatement">append</span>(okTrafficStats, OkTrafficStat{ DomainName: d.Name, UserReportedSpamRatio: trafficStat.UserReportedSpamRatio, }) } } <span class="synStatement">return</span> &amp;FetchResult{ OkTrafficStats: okTrafficStats, NgTrafficStats: ngTrafficStats, }, <span class="synStatement">nil</span> } </pre> <p>Postmaster Tools <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a> の詳細は公式のドキュメントを御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdevelopers.google.com%2Fgmail%2Fpostmaster%2Freference%2Frest%3Fhl%3Dja" title="Gmail Postmaster Tools API  |  Google for Developers" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://developers.google.com/gmail/postmaster/reference/rest?hl=ja">developers.google.com</a></cite></p> <h2 id="Datadogへカスタムメトリクスとして送信">Datadogへカスタムメトリクスとして送信</h2> <p>Datadogへカスタムメトリクスを送信するコードは以下のようになっています。</p> <p>※一部コメントや実装を端折っています</p> <pre class="code lang-go" data-lang="go" data-unlink><span class="synStatement">package</span> datadog <span class="synStatement">import</span> ( <span class="synConstant">&quot;context&quot;</span> <span class="synConstant">&quot;fmt&quot;</span> <span class="synConstant">&quot;github.com/DataDog/datadog-api-client-go/v2/api/datadog&quot;</span> <span class="synConstant">&quot;github.com/DataDog/datadog-api-client-go/v2/api/datadogV2&quot;</span> <span class="synConstant">&quot;time&quot;</span> ) <span class="synComment">// Metric はグラフ化しやすいよう加工されたDatadog用のメトリクス</span> <span class="synStatement">type</span> Metric <span class="synStatement">struct</span> { <span class="synComment">// タイムスタンプ</span> timestamp time.Time <span class="synComment">// ドメイン</span> <span class="synComment">// 例: crowdworks.jp</span> DomainName <span class="synType">string</span> <span class="synComment">// 迷惑メール率</span> <span class="synComment">// 例: 0.001 -&gt; 0.1%</span> UserReportedSpamRatio <span class="synType">float64</span> } <span class="synComment">// ToDatadogSeries はDatadogに送信するためのデータを作成</span> <span class="synStatement">func</span> (m Metric) ToDatadogSeries(currentTime time.Time, datadogEnv <span class="synType">string</span>) []datadogV2.MetricSeries { tags := []<span class="synType">string</span>{ <span class="synConstant">&quot;env:&quot;</span> + datadogEnv, <span class="synConstant">&quot;domain:&quot;</span> + m.DomainName, } <span class="synStatement">return</span> []datadogV2.MetricSeries{ { Metric: <span class="synConstant">&quot;crowdworks.postmastertools.user_reported_spam_ratio&quot;</span>, Type: datadogV2.METRICINTAKETYPE_GAUGE.Ptr(), Points: []datadogV2.MetricPoint{ { Timestamp: datadog.PtrInt64(currentTime.Unix()), Value: datadog.PtrFloat64(m.UserReportedSpamRatio), }, }, Tags: tags, }, } } <span class="synComment">// SubmitDatadog はMetricで与えられたドメインごとの値をDatadogに送信</span> <span class="synStatement">func</span> SubmitDatadog(metrics []Metric, currentTime time.Time, datadogEnv <span class="synType">string</span>) <span class="synType">error</span> { <span class="synStatement">var</span> series []datadogV2.MetricSeries <span class="synStatement">for</span> _, e := <span class="synStatement">range</span> metrics { series = <span class="synStatement">append</span>(series, e.ToDatadogSeries(currentTime, datadogEnv)...) } body := datadogV2.MetricPayload{Series: series} ctx := datadog.NewDefaultContext(context.Background()) configuration := datadog.NewConfiguration() apiClient := datadog.NewAPIClient(configuration) api := datadogV2.NewMetricsApi(apiClient) _, r, err := api.SubmitMetrics(ctx, body, *datadogV2.NewSubmitMetricsOptionalParameters()) <span class="synStatement">if</span> err != <span class="synStatement">nil</span> { <span class="synStatement">return</span> fmt.Errorf(<span class="synConstant">&quot;MetricsApi.SubmitMetricsに失敗: %v&quot;</span>, r) } <span class="synStatement">return</span> <span class="synStatement">nil</span> } </pre> <p>迷惑メール率をDatadogのカスタムメトリクスとして送信しています。</p> <p>Datadogの<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>の詳細は以下をご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.datadoghq.com%2Fja%2Fapi%2Flatest%2Fmetrics%2F%23submit-metrics" title="メトリクス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://docs.datadoghq.com/ja/api/latest/metrics/#submit-metrics">docs.datadoghq.com</a></cite></p> <h2 id="Postmaster-ToolsとDatadog-API仕様による気をつけるべきポイント">Postmaster ToolsとDatadog <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>仕様による気をつけるべきポイント</h2> <p>Postmaster Toolsで確認できる最新の迷惑メール率は大体 <code>3日前</code> の迷惑メール率です。 しかし、Datadogのカスタムメトリクス送信時に指定できるタイムスタンプには、未来は10分、過去は1時間という仕様があります。</p> <p>よって、実際には <code>3日前</code> の迷惑メール率を、プログラム実行時の時刻(現在時刻)をタイムスタンプとして、Datadogに送信しています。 このタイムスタンプのズレは、アラート内容に <code>3日前</code> である旨を記述してカバーしています。</p> <p>Datadogのカスタムメトリクスの詳細については、ドキュメントを御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.datadoghq.com%2Fja%2Fmetrics%2Fcustom_metrics%2F" title="カスタムメトリクス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://docs.datadoghq.com/ja/metrics/custom_metrics/">docs.datadoghq.com</a></cite></p> <h2 id="Datadogのダッシュボード">Datadogの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C0%A5%C3%A5%B7%A5%E5">ダッシュ</a>ボード</h2> <p>Datadogの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C0%A5%C3%A5%B7%A5%E5">ダッシュ</a>ボードを用意し、各<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>の迷惑メール率の一覧を出して、見やすくなりました。</p> <p><figure class="figure-image figure-image-fotolife" title="各ドメインの迷惑メール率"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sunakan/20240208/20240208113720.png" alt="&#x5404;&#x30C9;&#x30E1;&#x30A4;&#x30F3;&#x306E;&#x8FF7;&#x60D1;&#x30E1;&#x30FC;&#x30EB;&#x7387;" width="1120" height="433" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>各<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>の迷惑メール率</figcaption></figure></p> <p>監視していて迷惑メール率が上がった場合は、なんらか対策を検討しましょう。</p> <h2 id="まとめ">まとめ</h2> <p>迷惑メール率を1<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>ずつ目視チェック運用を継続するのは面倒です。 そのため、Postmaster Toolsの迷惑メール率を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>毎に一覧でき、悪くなった時もSlack通知ですぐに気付けるようにしました。</p> <h3 id="Were-Hiring-">We're Hiring !</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスでは一緒に課題を解決してくださるエンジニアを募集しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcrowdworks.co.jp%2Fcareers%2F" title="採用情報 | 株式会社クラウドワークス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://crowdworks.co.jp/careers/">crowdworks.co.jp</a></cite></p> sunakan UDXチームの立ち上げ〜現在の取り組みを紹介します! hatenablog://entry/6801883189078129966 2024-02-05T16:33:13+09:00 2024-02-05T16:33:13+09:00 こんにちは、エンジニアの井上と申します。 2023年6月にクラウドワークスへ入社しました。 私が所属しているのは2023年5月に新しく設立された「UDX」というチームです。 この記事では、UDXチームの立ち上げ〜取り組みまでを紹介したいと思います! <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kazuyainoue/20240126/20240126175932.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは、エンジニアの井上と申します。</p> <p>2023年6月に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスへ入社しました。</p> <p>私が所属しているのは2023年5月に新しく設立された「<a class="keyword" href="https://d.hatena.ne.jp/keyword/UDX">UDX</a>」というチームです。</p> <p>この記事では、<a class="keyword" href="https://d.hatena.ne.jp/keyword/UDX">UDX</a>チームの立ち上げ〜取り組みまでを紹介したいと思います!</p> <h2 id="UDXチームとは"><a class="keyword" href="https://d.hatena.ne.jp/keyword/UDX">UDX</a>チームとは?</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/UDX">UDX</a>チームは「ユーザーとの接点が最も多いユーザーサポートチーム(以下ユーサポ)目線の気づきや課題感をプロダクトに反映させることで、より良い価値提供ができるはず」という考えのもと、2023年5月に新設されたチームです。</p> <p>チーム名に関しては、上記の考えを反映し「User Support DX」の略称で「<a class="keyword" href="https://d.hatena.ne.jp/keyword/UDX">UDX</a>」と<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>しました。</p> <p>ユーサポの定常業務の効率化へ向けた技術的サポートやユーザーからの問い合わせ件数の削減を目標とした施策、管理画面の負債解消などに取り組んでいます。</p> <p>私の入社時点ではチーム立ち上げ直後ということもあり、EM1名とエンジニア2名の合計3名でしたが、徐々にチームビルディングが進み、現在はEM1名・エンジニア3名・PO2名の6名体制です。</p> <h2 id="チーム設立の背景とミッション">チーム設立の背景とミッション</h2> <p>チーム立ち上げ以前の課題として、以下の2つがありました。</p> <ul> <li>ユーサポは最もユーザーとの接点が多い部署であるにも関わらず、ユーサポ社員が感じている課題や気づきをサービスに反映させる環境が整っていない</li> <li>技術的サポートによりユーサポの定常業務を効率化させる余地があるものの、着手できていない</li> </ul> <p>こういった問題を解決すべく設立されたのが<a class="keyword" href="https://d.hatena.ne.jp/keyword/UDX">UDX</a>チームです。</p> <p>具体的には以下の2つをチームミッションとして掲げています。</p> <ol> <li>ユーサポの気づき・課題感に(<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>をもつチームがあれば協力して)取り組む</li> <li>ユーサポの業務コスト改善を技術的にサポートする</li> </ol> <p>順番に説明を加えます。</p> <h3 id="ユーサポの気づき課題感にドメインをもつチームがあれば協力して取り組む">①ユーサポの気づき・課題感に(<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>をもつチームがあれば協力して)取り組む</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/UDX">UDX</a>チームが主導となってユーサポ目線の施策を遂行することでより良い価値・体験をユーザーに届けられると考えています。</p> <p>また「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>をもつチームがあれば協力して」とあるように、必ずしも自チームだけでの解決をミッションとしているわけではありません。</p> <p>中長期的には<a class="keyword" href="https://d.hatena.ne.jp/keyword/UDX">UDX</a>チームのみならず、開発組織全体として横断的にユーサポからの施策を遂行できる仕組み作りを目指していく必要があります。</p> <h3 id="ユーサポの業務コスト改善を技術的にサポートする">②ユーサポの業務コスト改善を技術的にサポートする</h3> <p>前述したように、ユーサポの意見をプロダクトに反映させる環境が未整備のため、管理画面の改善に手が回っておらず、日々の業務コストを削減できていない状態となっていました。</p> <p>そこで私たち<a class="keyword" href="https://d.hatena.ne.jp/keyword/UDX">UDX</a>チームが技術的にサポートすることでユーサポの業務コストを改善する役割も担っていきます。</p> <p>これは「ユーサポの業務コスト改善ができれば、その分だけユーザーと向き合う時間や課題発見の時間に充てられるため、結果的にユーザーの満足度・定着度向上に繋げられるはず」という考えに基づいています。</p> <h2 id="現在の取り組み">現在の取り組み</h2> <p>チーム立ち上げから現在までに実施した、主な活動内容をまとめます。</p> <h3 id="ユーサポ定常業務の効率化">①ユーサポ定常業務の効率化</h3> <p>チーム立ち上げ初期はまず、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>のキャッチアップも兼ねて、ユーサポの定常業務の効率化へ向けてUI・UXを中心とした管理画面の改修から着手しました。この取り組みによって特に改善されたのは次の2つの業務です。</p> <ul> <li>目視チェック(掲載する仕事依頼の内容に問題がないか目視で確認する)</li> <li>スカウト目視チェック(スカウトメッセージの文言に問題がないか目視で確認する)</li> </ul> <p>どちらも<a href="https://crowdworks.jp/static/lp/safe_and_secure/">クラウドワークス安心安全宣言</a>で掲げている<a href="https://blog.crowdworks.jp/archives/2870/">「悪質案件(MLM・マルチ商法・ねずみ講・ネットワークビジネスなど)の撲滅」に向けた取り組み</a>の一環として、とても重要な業務です。</p> <p>この2つは毎日の対応件数が多く改善の余地が大きいにも関わらず手を付けられていない状況だったため、画面へ表示する項目の追加や一括更新の機能追加などを実施し効率化を進めました。</p> <p>1つ1つの修正自体は軽微なものでしたが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C4%EA%CE%CC">定量</a>的な<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%A9%BF%F4">工数</a>削減効果はもちろん、実際に定常業務に取り組んでいるユーサポの社員からも「とても楽になった」と嬉しいお言葉をいただいています。</p> <p>このように、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C4%EA%CE%CC">定量</a>的な成果だけではなく管理画面の修正に伴いユーサポの皆さんから直接的なフィードバックが得られるのも<a class="keyword" href="https://d.hatena.ne.jp/keyword/UDX">UDX</a>チームの魅力だと感じています。</p> <h3 id="規約違反アカウントの自動凍結">②<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%AC%CC%F3%B0%E3%C8%BF">規約違反</a>アカウントの自動凍結</h3> <p>残念ながら<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスには<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%AC%CC%F3%B0%E3%C8%BF">規約違反</a>を続けるユーザーが一定数存在します。</p> <p>これまでユーサポの社員がそうした<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%AC%CC%F3%B0%E3%C8%BF">規約違反</a>者を検知すべく工夫を凝らしていました。</p> <p>しかしその精度は高いとは言えず、また手作業でのアカウント凍結の処理をしていたため手間もかかる状況でした。</p> <p>そこで、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%AC%CC%F3%B0%E3%C8%BF">規約違反</a>者のアカウントを自動で凍結する機能を実装しました。</p> <p>ある特定の行動を行っているユーザーを<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%AC%CC%F3%B0%E3%C8%BF">規約違反</a>アカウントとみなし、その行動を検知した時点で自動的にアカウントが凍結される仕組みです。</p> <p>さらにユーサポの皆さんが後続の処理を迅速に行えるよう、自動凍結した時点でSlackチャンネルに通知する機能も盛り込みました。</p> <p>長期的な構想としては<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%A1%B3%A3%B3%D8%BD%AC">機械学習</a>を用いた自動検知も検討段階です。</p> <h3 id="管理画面のVuejs化">③管理画面のVue.js化</h3> <p>ERB(Embedded <a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>)でできている管理画面のVue.js化にも取り組みました。</p> <p>「機能追加がしづらいため、まずは既存仕様のままVue.js化してそれから新機能を実装しよう!」という方針で着手が決定しました。</p> <p>元々弊社には技術的負債の解消を目的としたチームが存在していますが、<a href="https://engineer.crowdworks.jp/entry/crowdworksjp_frontend_2023">こちらの記事</a>でも紹介されているように、それ以外のチームでも全社的にフロントエンドのVue.js化の機運が高まっています。</p> <p>単にERBをVue.jsに置き換えるだけでなく、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>のコントローラー側も<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>版を新規に作る必要があり、その他にOpenAPIの自動型生成やStorybookなどキャッチアップしなくてはいけないことがとても多く大変ではありました。</p> <p>しかしフロントエンド〜バックエンドまで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B0%EC%B5%A4%C4%CC%B4%D3">一気通貫</a>で実装することで大幅に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A5%EB%A5%A2%A5%C3%A5%D7">スキルアップ</a>できたように感じています。</p> <h3 id="仕事の掲載中断理由の見える化">④仕事の掲載中断理由の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%AB%A4%A8%A4%EB%B2%BD">見える化</a></h3> <p>こちらは現在取り組んでいる施策です。</p> <p>ユーザーが公開した仕事の内容や文言が不適切な場合は運営側の判断で掲載を中断している一方で、「なぜ中断されたのか?」がユーザーに具体的に提示されていません。</p> <p>そのため中断の理由についての問い合わせが多発している状況でした。</p> <p>そこで仕事の掲載中断理由の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%AB%A4%A8%A4%EB%B2%BD">見える化</a>へ向けた施策を進めています。</p> <p>この施策は③で紹介した、管理画面のVue.js化を完了させた後に着手しており、その上でさらにテーブル追加を5つ行うなど大規模な施策となったため、非常に長い期間を要しています。</p> <p>いよいよ終盤に差し掛かってきているので慎重に作業を進めていきたいです。</p> <h2 id="今後やりたいこと">今後やりたいこと</h2> <p>最後にこれから<a class="keyword" href="https://d.hatena.ne.jp/keyword/UDX">UDX</a>チームでやっていきたいことをまとめます。</p> <p>以下の内容には私個人の意見が多分に含まれています。</p> <h3 id="ミッションのアップデート">①ミッションのアップデート</h3> <p>冒頭に記載したチームミッションはあくまでもチーム設立時に決めた内容です。</p> <p>そこから現在に至るまで、見直す機会は設けられていません。</p> <p>しかし決めた当時と現在では業務の解像度やメンバーも異なるため、改めてミッションに対する意見交換をすべきだと考えています。</p> <p>他のメンバーも似たような思いを抱えているため、近いうちに話し合いの場を設ける予定となっています。</p> <h3 id="スクラムの見直し">②<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>の見直し</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>開発の進め方にも改善の余地が大きいと感じています。</p> <p>チーム全体として、どのように進めていくべきなのか手探りな状況だからです。</p> <p>1スプリントの期間やスプリントゴールの決め方、コミュニケーションの頻度など見直す点はたくさんありますね。</p> <p>「SCRUM <a class="keyword" href="https://d.hatena.ne.jp/keyword/BOOT%20CAMP">BOOT CAMP</a>」や「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B8%A5%E3%A5%A4%A5%EB">アジャイル</a>サムライ」といった著名な書籍をみんなで読み合わせるなど、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>開発に対する認識を揃える取り組みをしてみても良いかなと思っています。</p> <h3 id="管理画面のVuejs化-1">③管理画面のVue.js化</h3> <p>引き続き管理画面のVue.js化も進めていきたいです。</p> <p>今回は初めての取り組みでしたが、一度経験したことで多少なりとも解像度が高まったように感じています。</p> <p>もちろん反省点も多々あるので、次回以降はこの経験を生かしてもう少し速く、より高いクオリティで遂行していけるはずです。</p> <p>またVue.js化と言いつつバックエンドの<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>実装も改善すべきだと感じました。</p> <p>弊社ではDDDの導入へ向けてルール決めをしている最中なので、今後<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を実装する際はこうしたルールも反映していきたいです。</p> <h2 id="おわりに">おわりに</h2> <p>いかがだったでしょうか? 個人的には整備され尽くしているチームで働くよりも、新設されたチームの一員になれたことでより貴重な経験を積めていると感じています。</p> <p>新たな取り組みを実施したりチームの成熟度が高まってきた際はまた本ブログで紹介できればと思います!</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスではエンジニアを募集しておりますので、是非求人を確認してみてください! <a href="https://crowdworks.co.jp/careers/">https://crowdworks.co.jp/careers/</a></p> kazuyainoue 良いチームについて考え、新しいチームミッションを決めた話 hatenablog://entry/6801883189078157407 2024-01-26T15:04:27+09:00 2024-01-26T15:04:27+09:00 crowdworks.jpでエンジニアリングマネージャー兼スクラムマスターをしている久村です。 2021年からエンジニアリングマネージャーをしており、昨年の5月より1つのチームのスクラムマスターをすることになりました。 スクラムマスターは、良いチームに向き合い続けるものだと思います。 良いチームを自分一人で考えることもあれば、チームで考えることもあります。 今回はスクラムマスターとして、チームで良いチームについて考え、その後に新しいチームミッションを決めた内容を記事にしようと思います。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20240126/20240126111848.png" width="1200" height="629" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>crowdworks.jpでエンジニア<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%F3%A5%B0%A5%DE">リングマ</a>ネージャー兼<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>マスターをしている久村です。 2021年からエンジニア<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%F3%A5%B0%A5%DE">リングマ</a>ネージャーをしており、昨年の5月より1つのチームの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>マスターをすることになりました。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>マスターは、良いチームに向き合い続けるものだと思います。 良いチームを自分一人で考えることもあれば、チームで考えることもあります。</p> <p>今回は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>マスターとして、チームで良いチームについて考え、その後に新しいチームミッションを決めた内容を記事にしようと思います。</p> <h4 id="スクラムがうまくいっているかの確認"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>がうまくいっているかの確認</h4> <p>より良いチームにしていくためにまず行ったのが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>がうまくいっているかの確認でした。 昨年の7月に以下の記事を書いており、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>改善の取り組みを進めていました。 <a href="https://engineer.crowdworks.jp/entry/2023/07/28/182753">https://engineer.crowdworks.jp/entry/2023/07/28/182753</a></p> <p>取り組みの結果として、メンバーが現在のチームの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>についてどう感じているかを把握するのが目的でした。</p> <p>質問項目を考えるのは難しかったので、ryuzeeさんの以下の記事を参考に作成し、メンバーに自己評価をしてもらいました。</p> <p><a href="https://www.ryuzee.com/contents/blog/5033">https://www.ryuzee.com/contents/blog/5033</a> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20240126/20240126112018.png" width="1200" height="1111" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>以下がアンケート結果を集計した内容です。 項目は載せていないですが、3は出来ていると評価しており、1は出来ていないと評価したものになります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20240126/20240126112051.png" width="1200" height="290" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>評価の高かった項目の一部</p> <ul> <li>開発チームが必要なときにはいつでもプロダクトオーナーにコンタクトできる</li> <li>プロダクトオーナーと開発チームが敵対関係でなく会話している</li> <li>プロダクトオーナーが開発チームの意見を聞いている</li> <li>開発チームは困ったことがあったらすぐに開発チームや周りに相談している</li> <li>開発チームは困っている人がいたら助けている</li> <li>ふりかえりでみんなの意見がでる</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>ボードが乱雑ではない</li> <li>開発チームは必要があればすぐに追加のタスクを起票している</li> <li>悪い報告をしても責められない 次に何をしたらよいか?と指示を求めたりしない</li> </ul> <p>評価の低かった項目</p> <ul> <li>開発チームは頻繁にタスクの残時間を更新している</li> <li>デイリー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>までにバーンダウンチャートを更新している</li> <li>メンバーが急病などで休んでも機能している</li> <li>ふりかえりで出たアクションアイテムができているか確認している</li> <li>ふりかえりを待つまでもなく改善できることを常時改善している</li> <li>毎スプリントごとに開発チームのやり方が変わっていっている</li> </ul> <p>結果からチーム内での発言のしやすさやは良好であり、タスクの管理や即時にチーム改善をしていくことは機会点があると認識しました。 評価の低かった項目の中からここを改善していきましょうという流れも良いと思いましたが、より改善すべきポイントがないかを探りたいと思ったので、別のアプローチからチームの注力すべきポイント考えることにしました。</p> <h4 id="良いチームについて考える">良いチームについて考える</h4> <p>チームで良いチームとは?について会話する場を作りました。 「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>がうまくいっているかの確認」は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>にフォーカスした項目であり、より広い観点でチームについて考えたいという意図です。</p> <p>まずは各メンバーに意見を出してもらいました。 幅広く考えて欲しかったので、細かい条件は加えずに、「良いチームとは?」と投げかけをしました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20240126/20240126112118.png" width="1200" height="949" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>それぞれのメンバーの意見が素晴らしかったです!</p> <p>出た意見をグループ分けして、良いチームの要素を俯瞰してみれるようにしました。 まとめていく中で「良いチームの状態になるために」と「ユーザーのために」で分けれると会話し、良いチーム状態になった先に、ユーザーに価値を届ける状態になると考え、→をつけて可視化していきました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20240126/20240126112142.png" width="1200" height="539" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ユーザーのためにの項目では、すでにできている項目、できつつある項目が複数あること、また3か月前にチームの担当<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>をアップデートしたが、それに伴うミッションのアップデートはできていないことから、まずは良いチーム状態になることを目指した方が良いと会話しました。</p> <p>その後、良いチーム状態になるためにの項目の中で特に注力すべき点を会話し、「チームの役割理解」の優先度が高いと判断しました。 チームの役割理解を深めることで、チームの存在意義を認識しそれを軸に活動ができることや、チーム外からの期待値調整を適切にできる状態にしていきたいという考えです。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>がうまくいっているかの確認をした時よりも、今回の内容の方がチーム改善のポイントとして適切であり、改善する納得度も高かったので、「チームの役割理解」を改善することに繋げました。</p> <h4 id="新しいチームミッションを決める">新しいチームミッションを決める</h4> <p>上記の良いチームの会話を行い、チームの役割理解が重要と理解しました。 そこを改善するためにチームミッションのアップデートが望ましいと考えました。</p> <p>元々チームミッションはアップデートしたいと思っていましたが、今回のチームでのやり取りを通じて、よりその重要性が理解でき、優先度を上げて対応を進めました。</p> <p>チームミッションは以下の図のように位置すると考えています。 会社・事業部のミッションに接続できるものであり、日々行っている延長線上にあるものがチームミッションです。ここが明確になることで、自チームの存在意義の理解が深まり、結果としてチームの役割理解にも繋がると考えました。</p> <p>チームミッションの抽象度は難しいですが、図で表現したように事業部ミッションよりは具体的なものにしつつ、具体的にしすぎて手段の制限が強くならないように注意しました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20240126/20240126112209.png" width="1200" height="679" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>進め方としては<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BB%A5%D7%A5%B7%A5%E7%A5%F3">インセプション</a>デッキにもある「我々はなぜここにいるのか」をチームで考えました。 具体的には「チームは何のために存在するのか」を問いとしました。 ただ漠然として答えづらい可能性もあり、参考として以下の問いも立てて、考えやすいものに対して回答してもらいました。</p> <ul> <li>このチームとして成し遂げるべきこと</li> <li>どのような価値を提供していきたいのか</li> <li>3〜5年後にチームとしてこうなっていたい</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20240126/20240126112234.png" width="1200" height="765" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こちらもメンバーの意見がどれもよかったですね!</p> <p>出揃った後で、グルーピングを行い、俯瞰してみやすいようにしました。 このチームは注力<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>として「マッチング」を担っており、この領域での意見が多かったです。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20240126/20240126112255.png" width="1200" height="352" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>マッチングでのミッションを果たすことで、プロダクト、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D6%A5%E9%A5%F3%A5%C7%A5%A3%A5%F3%A5%B0">ブランディング</a>、チーム改善にも繋がるとチームで会話し、ミッションとしては我々の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>に関わる「マッチング」からチームミッションの言葉を考えることにしました。</p> <p>具体的には、体験価値提供、数、質を表現するのにどういう言葉があるかを出していきました。 一例ですが、マッチングの質の場合だと以下の言葉が出てきました。</p> <ul> <li>より適した</li> <li>より合った</li> <li>業務内容が合っている</li> <li>報酬額が合っている</li> <li>働きたい意志がある人に価値を届ける</li> <li>ご機嫌になる</li> <li>ワクワクする</li> </ul> <p>ある程度出揃った後で、その言葉をどう繋ぐと良いかを会話していきました。 いくつかの候補を出し、他の人の意見の一部を変更した方が良い等会話しながらチームミッションの表現に落とし込みました。</p> <p>参考までに決定したチームミッションです。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20240126/20240126112315.png" width="1200" height="678" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>メンバーからのFBもポジティブで、この作業を通して、チームとしてより納得度の高いチームミッションにできたと感じます。</p> <h4 id="チームミッションを浸透させるために">チームミッションを浸透させるために</h4> <p>チームミッションを決めていく中で、チームの役割理解を多少は深めることはできたと思います。 ただそれで完了ではなく、このミッションを基に日々チーム運営できる状態にすることで、より良いチームに近づいていきます。</p> <p>そのために適切なタイミングで、チームミッションへ接続することが重要と考えました。 チームミッションを日々意識するというよりは、日々の行動の先にチームミッションがあります。  普段意識すべきはスプリントゴールで良いと考えています。 よって、プランニング時にチームミッションを確認し、そこに近づけるために今スプリントで何をするかを決める流れが望ましいと思い、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B8%A5%A7%A5%F3%A5%C0">アジェンダ</a>の最初に追記をしました。</p> <p>またPOが主体的にリファインメントの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B8%A5%A7%A5%F3%A5%C0">アジェンダ</a>にも追加してくれていて、PBIの優先度を確認する上で、チームミッションを意識できるようにしてくれました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20240126/20240126112339.png" width="1200" height="400" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>これらを通じて、チームミッションを意識すべき時に触れれるようにし、浸透を促進したいです。</p> <p>加えて、定期的にミッションに向かうことができているかを確認する場が必要です。 具体的には3ヶ月毎にチームミッションに向かえているかを確認する振り返りの場を作りたいと考えています。</p> <p>またチーム名を変えることも検討中です。crowdworks.jpの開発組織ではチーム名を自由に決めて良い文化になっています。現在のチーム名は「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%EA%A5%D0%A1%BC%A5%C9">デリバード</a>」というチーム名ですが、当時のミッションから変更したのもあり、チーム名を再検討する予定です。</p> <h4 id="まとめ">まとめ</h4> <p>良いチームについて考え、その後に新しいチームミッションを決めた話でした。 自分一人ではなくチームで会話を進めることができたことで、ミッションをアップデートする目的や重要性をより理解できました。チームメンバーに感謝です。</p> <p>ミッション作成に関しては、完成度の高いチームミッションを作ることも大事ですが、それ以上にチームメンバーと作り上げるプロセスが大事だと思います。 実際に作ることに関わるからこそ、自分事化できるはずです。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>マスターとして今回の変更の結果、チームが役割を軸に行動できているか、チーム外から自チームの理解を持ってもらえているか確認していき、そこでの気づきを活かして良いチームに繋げていきたいです。</p> <p>引き続き、より良いチームになっていきたいです!</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスではエンジニアを募集しておりますので、是非求人を確認してみてください! <a href="https://crowdworks.co.jp/careers/">https://crowdworks.co.jp/careers/</a></p> murasahi 開発責任者がやるべきことの気づきと"Think Like a CTO" hatenablog://entry/6801883189069636639 2023-12-25T22:45:20+09:00 2023-12-27T11:30:25+09:00 1年をふりかえる 年の瀬ご多端の折、皆様におかれましては本年も大変お世話になりました。crowdworks.jpの開発をしているプロダクト開発部部長の@hihatsです。 本記事はクラウドワークスAdvent Calendar 2023 シリーズ2の25日目の記事です。 今回は、プロダクト開発部として今年取り組んできたことと、その中での気づき、「Think Like a CTO」という書籍の話にも触れていきます。 <p><figure class="figure-image figure-image-fotolife" title="1年をふりかえる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hi-hats/20231225/20231225223207.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>1年をふりかえる</figcaption></figure></p> <p>年の瀬ご多端の折、皆様におかれましては本年も大変お世話になりました。crowdworks.jpの開発をしているプロダクト開発部部長の<a href="https://twitter.com/hihats">@hihats</a>です。 本記事は<a href="https://qiita.com/advent-calendar/2023/crowdworks">クラウドワークスAdvent Calendar 2023</a> シリーズ2の25日目の記事です。</p> <p>今回は、プロダクト開発部として今年取り組んできたことと、その中での気づき、「Think Like a CTO」という書籍の話にも触れていきます。</p> <h2 id="今年やってきたこと">今年やってきたこと</h2> <h3 id="技術的負債解消への取り組み">技術的負債解消への取り組み</h3> <p><a href="https://engineer.crowdworks.jp/entry/2022/12/24/174446">昨年のアドベントカレンダー</a>でも書きましたが、2023年も技術的負債の解消に取り組んで参りました。</p> <p>結果としては、フロントエンドでの注力の割合が大きかった一年でした。詳しくは<a href="https://twitter.com/okuto_oyama">@okuto_oyama</a>さんの<a href="https://engineer.crowdworks.jp/entry/crowdworksjp_frontend_2023">1日目の記事</a>をご覧ください。 記事にもありますが、Vue.js化をフロントエンドを触る全チームが行える体制に整えることで加速させていきます。</p> <p>またバックエンド側でも、不要な機能の削除、不要なデータの削除、不要なライブラリの削除を地道に行い、現在はコアとなる<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>の再設計にも着手しています。</p> <h3 id="インボイス制度への対応本格化とともに組織強化に重点を置いた取り組み"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>制度への対応本格化とともに、組織強化に重点を置いた取り組み</h3> <p>今年の10月から施行された<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>制度への対応の<strong>複雑さという点での難しさ</strong>は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C1%A5%E3%A5%F3%A5%BF">チャンタ</a>マ氏の<a href="https://engineer.crowdworks.jp/entry/pjm_and_pdm">19日目の記事</a> にて詳しく書いてくれています。加えてもう一つ「制度そのものに対する反対世論も多い中、プロダクトの提供価値として設計、実装にいつどの程度舵を切るかどうか」の判断の難しさもありました。他にもやりたいことが山程ありましたからね。</p> <p>私自身は細かい指示を出すことはほぼなく、それぞれ自分たちで考え抜いて進められた経験をもって学習してもらえた手応えはありました。とはいえやはり余裕をもたせることで細かい失敗を許容しやすくするほうが、より健全な学習体験だったろうなという反省があります。</p> <p>並行して組織マネジメントの強化に取り組み、結果としてエンジニア<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%F3%A5%B0%A5%DE">リングマ</a>ネージャー(以下EM)の増員によって強固な体制に。日々の運用の中でペインとなっている業務に対して、なかなか本腰を入れて改善に取り組めていない領域が複数あり、その領域をチーム化することでまずは動き出せるようにしました。</p> <p>具体的には例えば、ユーザーサポートのメンバーは<strong>日常的にユーザーと接しながら感じている課題をプロダクトとして根本から改善する手立てが無い構造になってしまっていました。</strong> そこにユーザーサポート専任の開発チームを設置、ユーザーサポートメンバーからプロダクトオーナーになっていただいて元々いるシニアのPOがサポートすることで軌道にのせていくことができています。</p> <p>個別の領域以外にも、(少なくとも私が入社してから1年半ほどは)目の前の短期的なKPI達成に追われ、中長期について考える時間を取れていないことに危機意識がありました。”何がユーザー満足度に繋がるのか設計し直したり、2〜3年後に何を目指すのかといった戦略を考える”専任のチームを作ることができたのもこの時期でした。</p> <h3 id="プロダクトマネジメントにおける取り組み"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%C0%A5%AF%A5%C8%A5%DE%A5%CD%A5%B8%A5%E1%A5%F3%A5%C8">プロダクトマネジメント</a>における取り組み</h3> <p>プロダクト開発の現場において、「非エンジニアのグループが考える人たち、エンジニアが作る人」という構図になっていた部分が一部ありました。以前は違ったとしても、ちょっとしたことで陥りやすく、そっち側に最適化される何かがあるということです。この構図自体が悪いわけではないけれど、エンジニアとしては「社内受託のようだし、成果を感じにくい」という声がありました。またエンジニア側がリソース効率100%に近い(もしくは超えてる)状態だと、傍から見て相談しづらく、(よかれと思って)<strong>なるべく開発チームに余計な情報を入れないようにという配慮による悪循環</strong>もありました。結果としてエンジニアが知らないところで施策が進んで、あとからパフォーマンス劣化に気づくといったことも起きました。上記は一例ですが、類する事例を抽象化すると根本としては仕組み不足であったので、解消のために施策が「生煮え」の段階でも事業横断で共有する会議を設けました。一見すると出席者の多い会議が増え、コスト的にはオーバーヘッドにも見えますが、未然に知ることで得た恩恵のほうが大きかったというふりかえりができています。</p> <p>また、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>チーム化というチャレンジにおいて着々と進化しているチームもあり、成果に繋げることでモデル化できるようにしたいところです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnote.com%2Fiwao_product%2Fn%2Fnbefa00b7b104" title="チームの成果を最大化するためにPOが意識していること|いわお / PO" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://note.com/iwao_product/n/nbefa00b7b104">note.com</a></cite></p> <h3 id="機械学習による不正な仕事依頼の検知"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%A1%B3%A3%B3%D8%BD%AC">機械学習</a>による不正な仕事依頼の検知</h3> <p>ちょうど一年前ほどから、特徴的な不正仕事依頼が検知されるようになり、対策として<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%A1%B3%A3%B3%D8%BD%AC">機械学習</a>モデルで検知できないか試験運用してみようとなりました。以前も<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D9%A5%A4%A5%BA">ベイズ</a>推定による不正利用の検知は行っていましたが、運用継続がうまくいかず止まってしまっていた状態でした。</p> <p>ここ最近でエコシステムがさらに一段階整備されたこともあり、比較的少人数で実装とMLOps周りを進めていけています。</p> <h3 id="データ分析チーム">データ分析チーム</h3> <p>データアナリストの入社に伴い、よりバイアスを取り除いた手法での分析ができる体制になりました。詳しくは以下をご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnote.com%2Fcolorfulworks_cw%2Fn%2Fnc22c5a402829" title="データの力で人々の人生に選択肢をもたらすーー550万以上のユーザーデータを用いて「個のためのインフラ」を目指すデータアナリストの物語|クラウドワークス公式note" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://note.com/colorfulworks_cw/n/nc22c5a402829">note.com</a></cite></p> <h3 id="全社横断技術のカバー--経営との接続">全社横断技術のカバー & 経営との接続</h3> <p>ここまではcrowdworks.jpというプロダクト開発における取り組みでしたが、弊社における複数の事業のプロダクト開発に共通した課題を見出し、現場と経営の接続をする役割も明確に(意識的に)今年は遂行するようになりました。</p> <p>端的に言うと「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C2%BF%B3%D1%B2%BD">多角化</a>するプロダクト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DD%A1%BC%A5%C8%A5%D5%A5%A9%A5%EA%A5%AA">ポートフォリオ</a>の変化に対応するための技術部門が存在せず、事業単体での局所最適化だけでは、ブリコラージュにしかならない」というAs Is。</p> <p>個別のアプリケーション開発においては、認知負荷における<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>知識の占有率が高いので、まだそれでも良い。しかしより専門性の高いデータエンジニアリングやプラットフォームエンジニアリング寄りの技術に関しては横断的な有効活用が必要だと感じ、<strong>新たなグループを創設</strong>しました。もう一つの狙いとして、横断的にまたがる技術課題の相談先が無いことへの打ち手でもあります。</p> <p>私がこれまで所属したりお手伝いしてきた会社組織と同様、開発組織の課題は開発組織単体で生まれているわけではないものが多く、単に表面的な事象として現場に顕在化しているだけだったりします。「よいプロダクト開発をするためには結局、開発組織だけを見ていればよいわけではない」という考えの確信が高まっていく中で、現場と経営との接続に取り組むのは避けては通れない道でした。</p> <p>と、ここまでの書きっぷりから伝わるように、当初あまり気乗りしてなかったこの動きを続けていくことで実は想定よりもかなり大きい気づきを得ました。</p> <h2 id="これまでの気づき">これまでの気づき</h2> <p>主な気づきについて、数が多いですが、以下に列挙していきます</p> <h3 id="人数が増えると容易に分断を生むことは社会人類学的に回避が難しい">人数が増えると容易に分断を生むことは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BC%D2%B2%F1%BF%CD%CE%E0%B3%D8">社会人類学</a>的に回避が難しい</h3> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C0%A5%F3%A5%D0%A1%BC%BF%F4">ダンバー数</a>について知識としては持っていても、実感を得れる機会はあまりなかった</li> <li>何か悪意があってそうなるわけではなく、そうに違いないという思い込みが増えるのも要因</li> </ul> <h3 id="現場の課題感や専門的な解決法の提示内容は経営に浸透するまで時間がかかる">現場の課題感や専門的な解決法の提示内容は経営に浸透するまで時間がかかる</h3> <ul> <li>厳密に言うと伝わってはいるが、膨大な量の意思決定をしている人たちにはあっという間に忘れられやすい</li> <li>何回かしつこく言い続けるくらいがちょうどいい</li> <li>課題を伝達するだけではスピードが足りない</li> <li>我々が技術進化の速度を感じ、それに適応しようと工夫するのと同じように、ビジネスの変化や<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B7%D0%B1%C4%C8%BD%C3%C7">経営判断</a>の速度に合わせないといけない</li> </ul> <h3 id="開発への投資は回収期間が長くなりがちでわかりづらい">開発への投資は回収期間が長くなりがちでわかりづらい</h3> <p>ごく当然のことですが、その違いを前提に置いて話していく必要があります。 またひと口に開発への投資といっても、何種類か存在します。</p> <ul> <li>比較的回収期間が短めな、(例えば)<a class="keyword" href="https://d.hatena.ne.jp/keyword/SEO">SEO</a>対策や<a class="keyword" href="https://d.hatena.ne.jp/keyword/CPA">CPA</a>改善の施策などはあるが、プロダクトのUXを向上させるものではない</li> <li>社内の業務改善の開発も一定効果が見込みやすい施策ではあるが、これもプロダクトのUXの向上に直接的には繋がらない(間接的には繋がる)</li> <li>プロダクトそのものの品質向上は、関わるみんながやりたいことであるが、成果に対する不確実性は高く、即効性が高くないことが多い</li> <li>「toilを減らすこと」はエンジニアが創造的なタスクに集中できることに繋がり、理論的には効果も高い <ul> <li>だが現場の献身によって表面的には回っているように見えると、toilを減らす必要性も伝わりにくい</li> </ul> </li> </ul> <p>これらのバランスはタイミングごとに異なるので、見極めが重要かつ、どういった状況においても、極端な偏りがないかを可視化する必要があります。</p> <h3 id="その上でケイパビリティを高める">その上でケイパビリティを高める</h3> <ul> <li>会社がどの数字を重点的に追っていくかは、市場の動向や会社の状況によって変わってくるため、あらゆる場面で<strong>利益を生んだり、資産を作ったりできる力</strong>が開発が備えておくべき力</li> <li>それらをユーザーへ価値を届けるのもプロダクトの役目</li> </ul> <p>プロダクトを使ってもらうことで価値交換システムを形成している以上、開発組織のアウトプットの総量を増やしただけで価値が生めるとは言えないわけですが、技術部長の職務の枠から飛び出すと嫌でもそれには直面しました。</p> <h2 id="Think-Like-a-CTO">Think Like a CTO</h2> <p><figure class="figure-image figure-image-fotolife" title="Think Like a CTO"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hi-hats/20231225/20231225224952.png" width="958" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Think Like a CTO</figcaption></figure> 私はちょっと前まで「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BD%A5%D5%A5%C8%A5%A6%A5%A7%A5%A2%B9%A9%B3%D8">ソフトウェア工学</a>やテク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CE%A5%ED">ノロ</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B8%A1%BC">ジー</a>にまつわる<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%EC%A1%BC%A5%C9%A5%AA%A5%D5">トレードオフ</a>を理解した上で意思決定を行う」という専門的な仕事をしていたわけで、簡単なシフトチェンジではないなと感じていました。</p> <p>その頃、Tech Lead Journalを聴いていたときに、<a href="https://www.amazon.co.jp/Alan-Williamson/e/B0BWNTLTXM/ref=dp_byline_cont_book_1">Alan Williamson</a>の書籍「<a href="https://www.amazon.co.jp/Think-Like-CTO-Alan-Williamson/dp/1617298859">Think Like a CTO</a>」が紹介され、そこで話された内容が自分の動きと近かったので、書籍を読んでみました。</p> <h3 id="CTOの役割">CTOの役割</h3> <p>経験上知っていたことは「CTOの役割は会社によっても異なるし、またその会社内のフェーズによっても変わってくる」ということ。知ってはいたものの、自分がビジネスやテク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CE%A5%ED">ノロ</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B8%A1%BC">ジー</a>環境の狭間で変化に合わせた動きをすることで、ああこういうことねと非常に腹落ちしました。</p> <p>それ以外にも書籍には開発責任者がとるべき態度について分かりやすく<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%C0%B8%EC%B2%BD">言語化</a>されており、以下に紹介していきます</p> <h3 id="最も賢い人であってはならない">最も賢い人であってはならない</h3> <ul> <li>そうでなければCTOがチームの蓋をしてしまうことになる</li> <li>Noと言える人を常に近くにおいておくこと</li> </ul> <h3 id="ビジョンを示す">ビジョンを示す</h3> <ul> <li>眼の前の課題について語ることは極論、誰にでもできること</li> <li>誰かが技術戦略やビジョンを指し示してくれるのか?という問いをしてみよう</li> <li>ビジョンが示されていないエンジニアリングチームにマイクロマネジメントしたいのか?</li> </ul> <h3 id="テクノロジーがもっとも重要だとは限らない">テク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CE%A5%ED">ノロ</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B8%A1%BC">ジー</a>がもっとも重要だとは限らない</h3> <ul> <li>テク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CE%A5%ED">ノロ</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B8%A1%BC">ジー</a>は必要不可欠ではあるが、最重要だとは限らないという意味</li> <li>例えば<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C8%BE%C6%B3%C2%CE">半導体</a>メーカーのように、技術力がそのまま強い相関でビジネスに直結する会社であれば別</li> </ul> <h3 id="CEOと経営の言葉で話すこと">CEOと経営の言葉で話すこと</h3> <ul> <li>CEOが特定の技術に興味が無いと決めつけるのは尚早であるかもしれない</li> <li>知りたいことを引き出そう</li> </ul> <p>偶然にも、春先からCEOと定期的に会話する時間を設けてもらっており、幸運なことにCEOが成し遂げたい世界に共感できています。いつも生意気な発言をしていますが、抽象化スキルが試されるフィードバックが返ってきます。</p> <h3 id="マイクロアグレッションは許されない">マイクロアグレッションは許されない</h3> <p>技術者同士でときに無意識のうちに無知を貶めるような会話が行われたことは、経験が長い人ほど目にしてきたのではないかと思います。ただ新しい技術の名前を覚えたから使ってみたかっただけだとしても、経営の中での会話でそれは一発アウトです。伝わる言葉で話すこと。</p> <h3 id="インポスター症候群への対処">インポスター症候群への対処</h3> <ul> <li>CTOになると皆インポスター症候群に罹る</li> </ul> <p>インポスター症候群に罹る人というのは、一定自分への期待値が高く、そこと現実とのギャップに苦しむということだと解釈していますが、私は結構前から「自分がすべてを学べるわけではない」という事実を受け入れているため、インポスター症候群を感じることは記憶の限り無かったです。</p> <p>一方で「新しい技術についてのキャッチアップを諦めてよい」と言ってるわけではないので、開き直りにならないちょうどよいスイートスポットがあるんだろうなと思います。</p> <h3 id="品質の責任者">品質の責任者</h3> <ul> <li>日常的にテク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CE%A5%ED">ノロ</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B8%A1%BC">ジー</a>から遠ざかろうとも、自分が品質に責任をもつ人物であることを忘れてはいけない</li> </ul> <p>これは私のエンジニアとしての根底にあるもので強く同意しました。</p> <h2 id="まとめ">まとめ</h2> <p>一年を振り返ると色々やったなあと感慨深くなりますね。私がやっていることは他の人からは見えづらいことが多いという自覚はあるので、透明性の維持をサボらないよう気をつけたいと思います。</p> <h3 id="Were-Hiring-">We're Hiring !</h3> <p>来年以降も目白押しの開発施策を一緒に推し進めてくれるエンジニアの皆さんお待ちしております。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fherp.careers%2Fv1%2Fcrowdworks%2FAaOS_he9juwD" title="crowdworks.jp Webエンジニア/プロダクト開発部 - 株式会社クラウドワークス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://herp.careers/v1/crowdworks/AaOS_he9juwD">herp.careers</a></cite></p> hi-hats crowdworks.jp のデザインシステム構築活動を振り返る 2023 (実装編) hatenablog://entry/6801883189065924247 2023-12-21T11:44:13+09:00 2023-12-21T11:44:13+09:00 この記事はクラウドワークス Advent Calendar 2023 シリーズ 1 の 21 日目及び、デザインシステム Advent Calendar 2023 21 日目の記事です。 こんにちは、crowdworks.jp のデザインシステム構築に携わっているエンジニアの @t0yohei です。 2023 年は crowdworks.jp にとってデザインシステムの実装元年でした。そんな実装元年において、エンジニアとして実施したことを書いてみました。 デザインシステムを実装中の方、これからデザインシステムを実装していこうという方の参考になれば幸いです。 目次 デザインシステムを学ぶ De… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231220/20231220171817.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この記事は<a href="https://qiita.com/advent-calendar/2023/crowdworks">クラウドワークス Advent Calendar 2023</a> シリーズ 1 の 21 日目及び、<a href="https://qiita.com/advent-calendar/2023/design-system">デザインシステム Advent Calendar 2023</a> 21 日目の記事です。</p> <p>こんにちは、crowdworks.jp のデザインシステム構築に携わっているエンジニアの <a href="https://twitter.com/t0yohei">@t0yohei</a> です。 2023 年は crowdworks.jp にとってデザインシステムの実装元年でした。そんな実装元年において、エンジニアとして実施したことを書いてみました。</p> <p>デザインシステムを実装中の方、これからデザインシステムを実装していこうという方の参考になれば幸いです。</p> <p><strong>目次</strong></p> <ul class="table-of-contents"> <li><a href="#デザインシステムを学ぶ">デザインシステムを学ぶ</a></li> <li><a href="#Design-Token-の実装反映">Design Token の実装反映</a></li> <li><a href="#Design-Token-の画面適用">Design Token の画面適用</a></li> <li><a href="#全画面で表示される-Footer-への-Design-Token-適用">全画面で表示される Footer への Design Token 適用</a></li> <li><a href="#Component-Library-の導入方針決め">Component Library の導入方針決め</a></li> <li><a href="#Component-Library-の構築">Component Library の構築</a></li> <li><a href="#Component-Library-の画面適用">Component Library の画面適用</a></li> <li><a href="#Grid-System-の構築">Grid System の構築</a></li> <li><a href="#今後の予定">今後の予定</a></li> <li><a href="#終わりに">終わりに</a></li> </ul> <h2 id="デザインシステムを学ぶ">デザインシステムを学ぶ</h2> <p>デザインシステムについて何もわからないところから始めたので、まずはデザインシステムについて学ぶことから始めました。 参考にさせて頂いた書籍をいくつか記載いたします。</p> <ul> <li><a href="https://amzn.asia/d/3nTEzLz">Design Systems ―デジタルプロダクトのためのデザインシステム実践ガイド</a> <ul> <li>デザインシステムのバイブル。実装に関する内容は書かれていませんが、デザインシステムとは何かを理解するために必読でした。</li> </ul> </li> <li><a href="https://booth.pm/ja/items/2779567">小さく始める Design System</a> <ul> <li>薄い本のちいデザ。小さく始めるためのデザイン<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンの実装や使い方が簡潔に紹介されていてとても参考になりました。</li> </ul> </li> <li><a href="https://amzn.asia/d/fCyD4Pr">ちいさくはじめるデザインシステム</a> <ul> <li>SmartHR さんのちいデザ。SmartHR さんにおいて、どのようにデザインシステムを構築して行ったかが書かれていて参考になりました。</li> </ul> </li> </ul> <p>他にも、実際に運用されているデザインシステムとして、以下のデザインシステムを参考にさせていただきました。</p> <ul> <li><a href="https://spindle.ameba.design/">Spindle</a></li> <li><a href="https://smarthr.design/">SmartHR Design System</a></li> <li><a href="https://spectrum.adobe.com/">Spectrum, Adobe’s design system</a></li> </ul> <h2 id="Design-Token-の実装反映">Design Token の実装反映</h2> <p>crowdworks.jp のデザインシステム構築は、デザインチーム主導で始まりました。</p> <p>そのため自分が実装担当として携わるより前に、新しいデザインシステム <strong>norman</strong> のブラン<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%AC">ドガ</a>イドライン・デザイン原則、及び Design Token の定義はほぼ完了していました。 そこで、実装としてはまず、定義済みの Design Token をコードに落とし込むタスクを行いました。</p> <p><figure class="figure-image figure-image-fotolife" title="Figma 上で定義した Color の Design Token"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231211/20231211170812.png" width="1200" height="727" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption><a class="keyword" href="https://d.hatena.ne.jp/keyword/Figma">Figma</a> 上で定義した Color の Design Token</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="Color の Design Token 実装"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231214/20231214171303.png" width="1200" height="787" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Color の Design Token 実装</figcaption></figure></p> <p>Design Token は上記添付画像のように、<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> 変数として実装しました。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/json">json</a> で定義した上で <a href="https://github.com/amzn/style-dictionary">style-dictionary</a> などを使用して <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> にパースする手法なども検討しましたが、始めやすさを優先して <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> 変数としてそのまま実装する方法を選択しました。</p> <p>この実装にした場合、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Figma">Figma</a> の方で定義した Design Token との連携は手動で行う必要があるため、自動化改善ポイントの 1 つになっています。</p> <p>実際に実装した Design Token としては以下のようなものが存在し、それぞれ Base Token と、それをベースにした Semantics Token から成り立っています。</p> <ul> <li>BorderRadius</li> <li>Color</li> <li>Line</li> <li>Shadow</li> <li>Spacing</li> <li>Typography</li> </ul> <p>Design Token の分類や Base Token、Semantics Token への分割については <code>デザインシステムを学ぶ</code> で挙げた各種デザインシステムを参考にさせて頂きました。</p> <p>関連: <a href="https://note.com/yucca_wi/n/nff4eb3626b96#18238eea-04ca-4270-9be0-2f4617b95604">デザインシステムを導入する為にやって良かったこと5選| YUCCA</a></p> <h2 id="Design-Token-の画面適用">Design Token の画面適用</h2> <p>実装したものは早速使用してみよう、ということで<a href="https://crowdworks.jp/user/new_email">会員登録画面</a>や<a href="https://crowdworks.jp/login">ログイン画面</a>など、複数の画面にて Design Token を適用しました。記念すべきデザインシステムの本番反映 1 発目です。</p> <p><figure class="figure-image figure-image-fotolife" title="Design Token 適用の実装"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231214/20231214171350.png" width="582" height="367" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Design Token 適用の実装</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="会員登録画面 before/after"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231214/20231214171433.png" width="1200" height="507" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>会員登録画面 before/after</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="ログイン画面 before/after"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231214/20231214171506.png" width="1200" height="475" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ログイン画面 before/after</figcaption></figure></p> <p>チームとしても、自分たちで作ったものを実施にユーザーが使うようになって俄然モチベーションが上がっていきました。</p> <h2 id="全画面で表示される-Footer-への-Design-Token-適用">全画面で表示される Footer への Design Token 適用</h2> <p>今度はもっと影響範囲が大きいところへ、ということで全画面に表示されている Footer に対して Design Token を適用しました。 サービス全体で使用されている機能ということもあって、技術的負債の解消も含め 2,3 ヶ月ほど時間がかかったのですが無事完了できました。</p> <p>これにより、crowdworks.jp の全ページで Design Token が反映された Vue.js の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>が表示されることになります。</p> <p><figure class="figure-image figure-image-fotolife" title="Footer before"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231214/20231214171601.png" width="1200" height="424" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Footer before</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="Footer after"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231214/20231214171621.png" width="1200" height="439" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Footer after</figcaption></figure></p> <p>全ページに関わるレベルのアウトプットが出てきたということで、社内的にもデザインシステム構築活動の認知が進んでいきました。</p> <h2 id="Component-Library-の導入方針決め">Component Library の導入方針決め</h2> <p>Design Token の適用が一定成果となってきたところで、もっとデザインと開発の生産性を改善して行こうということで、Component Library を導入していく方針になりました。</p> <p>Component Library を導入するにあたり深く検討したことは、どういった形で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリを導入するかということです。</p> <p><a href="https://engineering.linecorp.com/ja/blog/uit-meetup19-report">UIT Meetup vol.19『デザインシステムのリアル』</a> にて <code>『デザインシステムを作る意味あるの?』</code> という発表がありました。 この発表の中で、自社デザインシステムないし<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリを<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EB%A5%B9%A5%AF%A5%E9%A5%C3%A5%C1">フルスクラッチ</a>で作ることは、開発コストがかなりかかるため慎重に検討する必要があるという紹介がありました。</p> <p><figure class="figure-image figure-image-fotolife" title="デザインシステム導入における、チーム規模ごとのおすすめ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231214/20231214171657.png" width="1200" height="745" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>デザインシステム導入における、チーム規模ごとのおすすめ</figcaption></figure></p> <p>そこで Component Library の導入前に、</p> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a> の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリをほぼそのまま使う</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a> の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリをカスタマイズして使う</li> <li>自社の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリを<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EB%A5%B9%A5%AF%A5%E9%A5%C3%A5%C1">フルスクラッチ</a>で作る</li> </ul> <p>のどれを選ぶか、エンジニア・デザイナー・PO(PM)それぞれの観点から意見を出し合い、議論を重ねました。 その結果、</p> <ul> <li>10 年もののプロダクトで、この先もさらに機能改善を行っていく予定がある <ul> <li>そのため、現時点での<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリへの投資は、回収できる見込みが高い</li> </ul> </li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a> の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリを採用した場合、将来的に物足りなくなって別の物で置き換えたくなる可能性がある <ul> <li>好き勝手カスタマイズできた方が、ちゃんとメンテナンスすることでずっと使える</li> </ul> </li> </ul> <p>ということで、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EB%A5%B9%A5%AF%A5%E9%A5%C3%A5%C1">フルスクラッチ</a>で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリを作る選択をしました。 この選択の正解・不正解がわかるのは、もう少し先の未来...。</p> <h2 id="Component-Library-の構築">Component Library の構築</h2> <p>Component Library を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EB%A5%B9%A5%AF%A5%E9%A5%C3%A5%C1">フルスクラッチ</a>で作ることが決まったので、今度は Component Library をどう作っていくかの話に入っていきます。何で作るかと、どのようなプロセスで作っていくか(決めることが多いですね...)。</p> <p>何で作るかに関しては、シンプルに Vue.js の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>として作ることにしました。経緯については <a class="keyword" href="https://d.hatena.ne.jp/keyword/ADR">ADR</a>(Architectural Decision Record) という形で以下のように記録を残しました。</p> <p><figure class="figure-image figure-image-fotolife" title="Component Library 構築方法に関する ADR"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231214/20231214171810.png" width="860" height="895" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Component Library 構築方法に関する <a class="keyword" href="https://d.hatena.ne.jp/keyword/ADR">ADR</a></figcaption></figure></p> <p>どのようなプロセスで作っていくかについては、以下の 2 プロセスを検討しました。</p> <ul> <li>Component Library を先に一定レベルで完成させて、新規画面の実装や既存画面への置き換えで使用していく</li> <li>新規画面の実装や既存画面への置き換えの中で必要になった<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を Component Library として登録していく</li> </ul> <p><code>Component Library を先に作りきる</code> か、 <code>作ると使うを並行して進める</code> かという比較です。</p> <p>デザインの観点からは、前者の方が全体の整合性を保ちやすいというメリットがありました。 ただ、モチベーションの観点や開発組織内でのプレゼンス確保の観点から、ユーザーに見えるアウトプットを泥臭く続けていることが重要だと判断し、後者のやり方を選択しました。</p> <p>Component Library の表示に関しては、専用のドキュメントページは用意せず Storybook 上で表示するようにしました。crowdworks.jp では、最新の master ブランチの内容を反映した Storybook のページが <a class="keyword" href="https://d.hatena.ne.jp/keyword/VPN">VPN</a> に繋ぐと見られるようになっていたので、それに乗っかった形です。</p> <p>これによりドキュメントページの環境構築コストや保守コストはほぼゼロに抑えることができました(ドキュメントページの外部公開などをする際は、専用の環境を作る必要が出てきそうですがそれはまた未来の話)。</p> <p>ドキュメントページの内容に関しても、まずは手抜きで作成しました。若干の物足りなさは否めませんが、運用でカバーの範疇だと考え許容しました。完璧を求めるよりまず終わらせようの精神です。</p> <p><figure class="figure-image figure-image-fotolife" title="Button コンポーネントの Storybook"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231214/20231214171841.png" width="1070" height="860" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Button <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の Storybook</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="Button コンポーネントの Figma"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231214/20231214171909.png" width="1200" height="611" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Button <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の <a class="keyword" href="https://d.hatena.ne.jp/keyword/Figma">Figma</a></figcaption></figure></p> <p>この記事作成時点では Component Library として下記の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>が<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BC%C2%C1%F5%BA%D1">実装済</a>みで、現在も拡張を続けています。</p> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/Avatar">Avatar</a></li> <li>ButtonBackToTop</li> <li>Button</li> <li>Checkbox</li> <li>CrowdWorksLogo</li> <li>Heading</li> <li>Icon</li> <li>Input</li> <li>LinkButton</li> <li>SocialLogo</li> <li>Tabs</li> <li>TextLink</li> </ul> <p>Component Library の実装に際しては、以下のデザインシステムや UI ライブラリを参考にさせて頂きました。</p> <ul> <li><a href="https://primer.style/">Primer</a></li> <li><a href="https://carbondesignsystem.com/">Carbon Design System</a></li> <li><a href="https://m3.material.io/">Material Design</a></li> <li><a href="https://vuetifyjs.com/en/">Vuetify</a></li> <li><a href="https://getbootstrap.com/">Bootstrap</a></li> </ul> <h2 id="Component-Library-の画面適用">Component Library の画面適用</h2> <p>Component Library の構築方針として、 <code>作ると使うを並行して進める</code> という方針にしたので、Component Library の登録数が増えると画面適用数も増えるという構造になっています。 作った後に早速自分たちで使うので、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%A3%A1%BC%A5%C9%A5%D0%A5%C3%A5%AF%A5%EB%A1%BC%A5%D7">フィードバックループ</a>がすぐに回るといった副次効果もあります。</p> <p>Component Library の画面適用数は現状 4 画面と、まだまだこれからといった具合です。 デザインシステム構築チーム以外での Component Library の利用も始まってきたため、画面適用数はこれから順調に増えていきそうな兆しがあります。(それと同時に Component Library がどこに適用されているのかという管理の問題も出てきていますが、それはまた別の機会に)</p> <h2 id="Grid-System-の構築">Grid System の構築</h2> <p>Component Library の画面適用や Design Token の画面適用を進めていく中で、画面レイアウトを整えるための Grid System が必要になったので、その構築を実施しました。</p> <p>Grid System の構築で得た知見について以下にまとめておりますので、よろしければこちらもご覧ください。</p> <p><a href="https://engineer.crowdworks.jp/entry/2023/10/28/114403">Vue.js で作る GridSystem - クラウドワークス エンジニアブログ</a></p> <h2 id="今後の予定">今後の予定</h2> <p>2024 年も引き続きデザインシステム構築活動を進めていく予定で、現状で下記の実施を検討しています。</p> <ul> <li>Header への Design Token 適用</li> <li>Component Library の拡充及び、新規・既存画面への Component Library 適用</li> <li>デザインシステム活用の支援と、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%A3%A1%BC%A5%C9%A5%D0%A5%C3%A5%AF%A5%EB%A1%BC%A5%D7">フィードバックループ</a>の仕組み構築</li> </ul> <p>まだまだやることが尽きなくて楽しみですね。</p> <h2 id="終わりに">終わりに</h2> <p>2023 年は crowdworks.jp にとってデザインシステムの実装元年で、やれること・やるべきことを手探りで積み上げていく年でした。 その中で、Design Token や Component Library の実装・適用など、 Design System の実装における基礎的な部分をどうにか形にできたのかなと考えています。</p> <p>2024 年はこの基礎的なものをさらに拡充することで、よりデザインシステムの利用価値を高めていく予定です。</p> <p>それでは、お読みいただきありがとうございました。</p> t0yohei インボイス対応を経て感じたPjMとPdMの違い hatenablog://entry/6801883189066499954 2023-12-19T07:00:00+09:00 2023-12-19T22:14:16+09:00 この記事はクラウドワークスAdvent Calendar 2023 シリーズ2の19日目の記事です。 こんにちは。crowdworks.jp プロダクトマネジメントチームのチャンタマ(@tmchannel)です。プロダクトマネージャーとしていろんなことをやってます。 世は師走、今年を振り返ってみると、やはり一番に思い浮かぶのはインボイス制度ですね。 なんといっても、誰が何を言おうと、何がなんでもインボイスです。 (とまで書いている段階で、今年の漢字2023が「税」と発表されました。ですよね) 2022年10月頃からインボイス対応の担当として(それはもう、本当に!あちこち)駆け回ってきたなかで、… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tama_1028/20231213/20231213185324.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> この記事は<a href="https://qiita.com/advent-calendar/2023/crowdworks">クラウドワークスAdvent Calendar 2023</a> シリーズ2の19日目の記事です。</p> <p>こんにちは。crowdworks.jp <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%C0%A5%AF%A5%C8%A5%DE%A5%CD%A5%B8%A5%E1%A5%F3%A5%C8">プロダクトマネジメント</a>チームの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C1%A5%E3%A5%F3%A5%BF">チャンタ</a>マ(<a href="https://twitter.com/tmchannel_">@tmchannel</a>)です。プロダクトマネージャーとしていろんなことをやってます。</p> <p>世は師走、今年を振り返ってみると、やはり一番に思い浮かぶのは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>制度ですね。 なんといっても、誰が何を言おうと、何がなんでも<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>です。<br> (とまで書いている段階で、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BA%A3%C7%AF%A4%CE%B4%C1%BB%FA">今年の漢字</a>2023が「税」と発表されました。ですよね)</p> <p>2022年10月頃から<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>対応の担当として(それはもう、本当に!あちこち)駆け回ってきたなかで、PdM5年目にして多くの学び、気づき、そして反省...がありました。 今回は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>制度対応を経て得た気付きをしたためて、今年の締めくくりとしたいと思います。</p> <p>※臨場感演出のため、以降ではです・ます調ではなく、だ・である調で書きます</p> <h2 id="自分ならできるやろと思っていたあの夏">自分ならできるやろと思っていた、あの夏</h2> <p>(以下、プロジェクトマネージャー、プロダクトマネージャーをそれぞれPjM、PdMとします)</p> <p>2022年10月、マネージャーからこんな相談が。</p> <p>マネージャー「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C1%A5%E3%A5%F3%A5%BF">チャンタ</a>マさん、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>の担当、お願いしてもいいですか?」<br> <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C1%A5%E3%A5%F3%A5%BF">チャンタ</a>マ「おーっす」</p> <p>2016年に新卒として<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスに入社し、マーケやクライアントワーク、PdMとしてはWebサイト改善もモバイルアプリ改善も、ユーザー向け機能も社内向け機能も、と幅広い経験をしてきたことが少なからず自信に繋がっていた頃だった。</p> <p>だが、私はエンジニア経験がない。<br> 大規模な<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%B9%A5%C6%A5%E0%B3%AB%C8%AF">システム開発</a>はおろか、PjMと呼ばれる人と一緒に仕事をしたこともない。<br> 私に本当に求められていることが何なのか、私は完全には理解できていなかったことを後に知ることになる。</p> <h2 id="インボイス対応で求められたこと"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>対応で求められたこと</h2> <p>crowdworks.jp のサービス開発は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>を組んで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B8%A5%E3%A5%A4%A5%EB">アジャイル</a>に進められている。<br> PO1人、エンジニア2~4人、デザイナー1人。<br> だいたいこのような編成で、毎日デイリーを行い、スプリントごとにプランニングと振り返りを行い、必要に応じてチーム外の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%C6%A1%BC%A5%AF%A5%DB%A5%EB%A5%C0%A1%BC">ステークホルダー</a>とコミュニケーションをとるが、基本的にはチーム内で完結する。</p> <p>だが<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>対応はこれまでとは異なる様相を呈していた。</p> <h3 id="絶対に遅れられない期限がそこにはある">絶対に遅れられない期限がそこにはある</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>制度の開始は2023年10月。それが絶対だった。</p> <p>ふだんのcrowdworks.jp のサービス開発にももちろん期限はあるが、そこは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B8%A5%E3%A5%A4%A5%EB">アジャイル</a>に、向かう方向が間違っていることに気づいたらやるべきことを見直し、スケジュールを立て直すもの。</p> <p>だがここはやはり公的制度対応。日本全国のあらゆる課税事業者、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B7%D0%CD%FD">経理</a>部署、開発部署が足並み揃えて<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>の対応をする中で、絶対に遅れられない。ユーザーに迷惑はかけられない。<br> いつも以上にシビアにスコープを見定め、前提を疑いまくり、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%C0%C7%C4%A3">国税庁</a>の資料を読み込む日々だった。</p> <h3 id="ものすごく複雑でありえないほどでかい">ものすごく複雑で、ありえないほどでかい</h3> <p>crowdworks.jpというサービスは、2011年のリリース以降、基幹部分はほとんどそのままで増改築が繰り返されてきた。</p> <p>今回の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>対応に関連するところだけにフォーカスしても、取引の種類は5種類、支払い方法は5種類あり、加えて<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>発行が可能かどうかの判定<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>を含めると、単純計算で50パターンのケースを考慮する必要がある。 実際には他にも分岐があり、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%A1%B3%A3%C5%AA">機械的</a>に作成したパターン表は300行にも渡っていた。(最終的にはスコープを削るなどして減らした)</p> <p>加えて、帳票を発行できる画面が8画面ほどあること、帳票周りのテーブルを新たに作り直す必要があった<sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup>ことなどから、1チームで対応できる範疇をはるかに超えていた。</p> <p>最終的に、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>対応初期から活動していたプロジェクトチームに加え、3チームに協力いただくことになった。関わった人数はマネージャーや<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B7%D0%CD%FD">経理</a>メンバーも含め総勢30名弱である。</p> <p>あとあと聞いた話だが、昔をよく知るエンジニア曰く、ここまでの数のチームを横断する機能開発はcrowdworks.jpではほとんどなかったとのことだった。</p> <h2 id="インボイス制度対応で私に求められたのはPjM的動きだった"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>制度対応で私に求められたのはPjM的動きだった</h2> <ul> <li>23年10月の期限は変えられないこと</li> <li>サービスの複雑性が高く、影響範囲も広いため複数チーム横断的に動く必要があったこと</li> </ul> <p>この2点から、私はこれまで経験したことのない頭の使い方をする必要があった。</p> <h3 id="crowdworksjpのPdMの思考">crowdworks.jpのPdMの思考</h3> <p><figure class="figure-image figure-image-fotolife" title="ゴールデンサークルの図。三重の円が描かれており、円の中心からWHY / HOW / WHATと並んでいる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tama_1028/20231213/20231213185353.jpg" alt="&#x30B4;&#x30FC;&#x30EB;&#x30C7;&#x30F3;&#x30B5;&#x30FC;&#x30AF;&#x30EB;&#x306E;&#x56F3;&#x3002;&#x4E09;&#x91CD;&#x306E;&#x5186;&#x304C;&#x63CF;&#x304B;&#x308C;&#x3066;&#x304A;&#x308A;&#x3001;&#x5186;&#x306E;&#x4E2D;&#x5FC3;&#x304B;&#x3089;WHY / HOW / WHAT&#x3068;&#x4E26;&#x3093;&#x3067;&#x3044;&#x308B;" width="429" height="391" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure> crowdworks.jpのPdMは施策検討を行う際、ゴールデンサークルでいうWhyの部分を特に重視している。<br> 「なぜやるのか」<br> 「誰のどんな課題を解決するのか」<br> 「誰にどのような価値を届けたいのか」</p> <p>これは持論だが、Whyさえ定義できていれば、具体的なHow, WhatはPdMだけが考える必要はない、むしろエンジニア・デザイナーと一緒に考えるべきとさえ思っている。</p> <p>このWhyを重視する思考が全く役に立たなかったわけではないが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>対応において重要だったのはむしろHow, Whatの部分だった。 自身の主戦場としてきた「Whyの定義」が、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>対応全体の中では比重が軽かったのである。</p> <h3 id="そういえばPjMとPdMってどう違うんだっけ">そういえばPjMとPdMってどう違うんだっけ</h3> <p>自分のWhy思考があまり役に立たないかもしれないことに少し気がつき始めた頃、ふと思い立って調べた「プロジェクトマネージャー」の定義にこんなことが書かれていた。</p> <blockquote><p>IT分野での開発を行うプロジェクトチームの責任者として、<strong>プロジェクト実行計画の作成、予算、要員、進捗の管理など</strong>を行う。短く、PMと表記されることもある。</p> <p>具体的な仕事は、開発計画に基づいてプロジェクトの<strong>実行計画を作成</strong>し、その計画の<strong>遂行に必要な人員やリソース(資源)を調達</strong>し、プロジェクトの体制を作る。プロジェクトが動き出すと<strong>予算、納期、要員、品質等の管理をしながら、プロジェクトが円滑に進むように</strong>する。<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%CA%C4%BD%B4%C9%CD%FD">進捗管理</a>は重要であり、問題や将来見込まれる課題を早期に把握し、適切な対策を事前に講じて、プロジェクトが計画通り進むようにする。</p></blockquote> <p>(<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%FC%CF%AB%BE%CA">厚労省</a>による<a href="https://shigoto.mhlw.go.jp/User/Occupation/Detail/322">職業情報提供サイト</a>からの引用。太字装飾は筆者による)</p> <p>やっぱりそうか、と思った。 なんとなく感じていたうまくいってない感の原因はプロジェクトマネジメントにあったのか、とひどく腹落ちした。<br> PdMがゴールデンサークルのWhyに主軸を置いているとするならば、PjMとはHowのプロフェッショナルなんだろう、と解釈した。</p> <p>もちろん、スケジュールや予算・品質の管理をやったことがないわけではない。PdMの役割の中に含まれているとは思う。 しかしながら今回の規模では、今までのやり方では太刀打ちできなかったのだ。</p> <p>今回<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>対応をおこなった各チームはそれぞれ、フロントエンドも対応可能 / フロントエンドが得意なメンバーがほぼいない / 技術的負債解消をミッションにしている、などの特徴があり、エンジニア経験がなく浅い知識しか持たない私には各チームにどのようにお願いしていいのかすら大変悩ましかった。</p> <p>適切なタスク分解や複数チーム横断のタスク差配の難易度が高く、サービスの複雑度により見積もりも出しづらい中で、どうやってスケジュールに間に合わせるか、私には打ち手の引き出しが少なかったのである。</p> <p>最終的に、<a href="https://twitter.com/hihats">@hihats</a> さんやEMの方々のお力添えにより、残タスクを全てmiroに洗い出し、どのチームが対応しているか、見積もりがどのようになっているか、まだ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B5%A5%A4">アサイ</a>ンできていないタスクはどれか、を可視化することでなんとかリリースすることができた。<br> (その節は、本当に、本当にお世話になりました)</p> <h2 id="まとめ">まとめ</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>対応の経験を経た、個人的なPdMとPjMの違いは以下の通りである。</p> <ul> <li>PdMとPjMは、同じ業務内容をすることはあっても思考の主軸が違うため、それぞれの強みを持っている</li> <li>PdMはユーザーへの価値提供に責任を持ち、Why(なぜやるのか、誰のどんな課題を解決するのか)に強みを持つ</li> <li>PjMはプロジェクトの遂行に責任を持ち、How(実行計画や人員、リソース、品質などの管理をどのように行うか)に強みを持つ</li> </ul> <p>そして施策や機能によっては、PdMやPjMという役割にとらわれずに、目の前の仕事のために何をすべきか愚直に考え取り組むことが重要であると改めて感じた。</p> <p>これまでに経験したことがないほどの、そして今後もあまり経験することが無いであろう規模の機能開発を経験できたことは、本当に良かったと思う。<br> 今後もcrowdworks.jpの発展のため、全力で取り組んでいきたい</p> <hr /> <p>【We're hiring!】<br> <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスでは様々な仲間を募集しております。<br> ご興味のある方は以下のリンクからご応募ください!<br> <a href="https://crowdworks.co.jp/careers/">https://crowdworks.co.jp/careers/</a></p> <div class="footnotes"> <hr/> <ol> <li id="fn:1"> crowdworks.jpでは、媒介者交付特例を用いているため、受注者が<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>事業者かどうかによって<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>を発行してもよいか判定を行う必要がある。弊社自体が<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>発行事業者であっても、すべての帳票を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>にできるわけではない。<a href="#fnref:1" rev="footnote">&#8617;</a></li> <li id="fn:2"> 帳票発行機能は機能リリース当初からほぼ手が加えられておらず、今回の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>対応にそのまま耐えうる設計にはなっていなかった。<a href="#fnref:2" rev="footnote">&#8617;</a></li> </ol> </div> tama_1028 DMARCレポートを眺めるのにdmarc-visualizerがおすすめ hatenablog://entry/6801883189066435787 2023-12-14T07:00:00+09:00 2023-12-14T07:00:03+09:00 DMARCレポートをちょっと眺めたい時に便利なツールがdmarc-visualizerです。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231213/20231213142318.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この記事は <a href="https://qiita.com/advent-calendar/2023/crowdworks">クラウドワークス Advent Calendar 2023</a> シリーズ2 の 14日目の記事です。</p> <p>こんにちは。crowdworks.jp SRE チームの田中(@kangaechu)です。 この記事を読んでいる方は<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a>/Yahoo.comから出されたメール送信者の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AC%A5%A4%A5%C9%A5%E9%A5%A4%A5%F3">ガイドライン</a>対応を絶賛対応中かと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsupport.google.com%2Fmail%2Fanswer%2F81126%3Fsjid%3D4142904716886635430-AP" title="Email sender guidelines - Gmail Help" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://support.google.com/mail/answer/81126?sjid=4142904716886635430-AP">support.google.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsupport.google.com%2Fa%2Fanswer%2F14229414" title="Email sender guidelines FAQ - Google Workspace Admin Help" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://support.google.com/a/answer/14229414">support.google.com</a></cite></p> <p><a href="https://blog.postmaster.yahooinc.com/post/730172167494483968/more-secure-less-spam">Postmaster @ Yahoo &amp; AOL &mdash; More Secure, Less Spam: Enforcing Email Standards...</a></p> <p>みなさん、<a class="keyword" href="https://d.hatena.ne.jp/keyword/DNS">DNS</a>にDMARCの設定を追加しましたか? ひとまずDMARCで設定したレポート<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C1%F7%BF%AE%C0%E8">送信先</a>メールアドレスに大量のレポートが届いているものの、次になにしたらいいんだろう?と思っている方も多いかと思います。</p> <p>DMARCレポートを解析し、いい感じに表示する<a class="keyword" href="https://d.hatena.ne.jp/keyword/SaaS">SaaS</a>もたくさんありますが、ひとまず受け取ったレポートを手元で見てみたい、という方。 そんな方におすすめなのがdmarc-visualizer。この記事では弊社での導入方法をご紹介します。</p> <h2 id="dmarc-visualizerとは">dmarc-visualizerとは</h2> <p>dmarc-visualizerを紹介する前に、parsedmarcを紹介します。 parsedmarcは<a class="keyword" href="https://d.hatena.ne.jp/keyword/Python">Python</a>で書かれた<a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>で、DMARCレポートを<a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>や<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSV">CSV</a>に変換するツールです。また、このツールはElasticsearchやSplunk、ApacheKafkaにも出力することが可能です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fdomainaware%2Fparsedmarc" title="GitHub - domainaware/parsedmarc: A Python package and CLI for parsing aggregate and forensic DMARC reports" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/domainaware/parsedmarc">github.com</a></cite></p> <p>dmarc-visualizerはDocker Composeにより、以下のソフトウェアをローカルで立ち上げる設定を配布する<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>です。</p> <ul> <li>parsedmarc</li> <li>Elasticsearch</li> <li>Grafana</li> </ul> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fdebricked%2Fdmarc-visualizer%3Ftab%3Dreadme-ov-file" title="GitHub - debricked/dmarc-visualizer: Analyse and visualize DMARC results using open-source tools" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/debricked/dmarc-visualizer?tab=readme-ov-file">github.com</a></cite></p> <p>dmarc-visualizerを使用することにより、設定の手間なくDMARCレポートのパース→Elasticsearchへのロード→Grafanaによるビジュアライズを簡単に行うことができます。</p> <h2 id="crowdworksjpでのレポート確認までの手順">crowdworks.jpでのレポート確認までの手順</h2> <p>全体概要は以下のとおりです。</p> <p><figure class="figure-image figure-image-fotolife" title="DMARCレポートを眺めるまでの処理概要図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231213/20231213141557.png" width="1071" height="279" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>DMARCレポートを眺めるまでの処理概要図</figcaption></figure></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスではDMARCレポートを<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a> Workspaceで払い出した<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E1%A1%BC%A5%EA%A5%F3%A5%B0%A5%EA%A5%B9%A5%C8">メーリングリスト</a>に送信しています。 メールは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E1%A1%BC%A5%EA%A5%F3%A5%B0%A5%EA%A5%B9%A5%C8">メーリングリスト</a>の各メンバーに配信されています。 DMARCのレポートメールにはdmarcラベルを付与した状態となっています。</p> <p>これを 「1. 受け取ったDMARCレポートを<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google%20Drive">Google Drive</a>に保存」で<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a> App Scriptを使い、メールの添付ファイルを<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google%20Drive">Google Drive</a>に保存します。 「2. dmarc-visualizerの準備」で準備した<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>を元に、DMARCレポートのファイルを「3. 入力ファイルを配置」で配置し、起動します。 DMARCレポートのパース・Elasticsearchへのロードができたら「4. レポート結果を確認」で結果を確認します。</p> <h3 id="1-受け取ったDMARCレポートをGoogle-Driveに保存">1. 受け取ったDMARCレポートを<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google%20Drive">Google Drive</a>に保存</h3> <p>先ほど説明したとおり、DMARCレポートはメールに添付されています。 当初は<a class="keyword" href="https://d.hatena.ne.jp/keyword/GMail">GMail</a>の画面からメールを開いて添付ファイルをダウンロード、を繰り返していました。 しかし、メールはDMARCを送信するサーバごとに存在し、添付ファイルは1メールに10個以上ある場合もあり、すぐ辛くなりました。 <figure class="figure-image figure-image-fotolife" title="ザ、苦行"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231213/20231213144149.png" width="1200" height="923" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ザ、苦行</figcaption></figure></p> <p>そのため、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a> App Scriptにより<a class="keyword" href="https://d.hatena.ne.jp/keyword/GMail">GMail</a>のdmarcラベルを付与されたメールから添付ファイルを全て<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google%20Drive">Google Drive</a>に保存する<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を作成しました。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">/**</span> <span class="synComment"> * Searches for emails with a specific label, downloads their attachments, and saves them to Google Drive.</span> <span class="synComment"> */</span> <span class="synIdentifier">function</span> main() <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> folderBase = <span class="synConstant">&quot;dmarc&quot;</span>; <span class="synComment">// 出力先のフォルダ名</span> <span class="synComment">// GMailのメール検索演算子</span> <span class="synComment">// https://support.google.com/mail/answer/7190?hl=ja</span> <span class="synIdentifier">var</span> queryLabel = <span class="synConstant">&quot;label:dmarc&quot;</span>; <span class="synComment">// GMailの検索タグ</span> <span class="synIdentifier">var</span> startDate = <span class="synStatement">new</span> <span class="synType">Date</span>(<span class="synType">Date</span>.now()); startDate.setDate(startDate.getDate() - 1); <span class="synComment">// 前日分を処理</span> startDate.setUTCHours(0); startDate.setMinutes(0); startDate.setSeconds(0); startDate.setMilliseconds(0); <span class="synIdentifier">var</span> endDate = <span class="synStatement">new</span> <span class="synType">Date</span>(startDate); endDate.setDate(endDate.getDate() + 1); <span class="synIdentifier">var</span> startDateUnix = startDate.getTime() / 1000; <span class="synIdentifier">var</span> endDateUnix = endDate.getTime() / 1000; <span class="synIdentifier">var</span> query = <span class="synConstant">`</span><span class="synSpecial">${queryLabel}</span><span class="synConstant"> before: </span><span class="synSpecial">${endDateUnix}</span><span class="synConstant"> after: </span><span class="synSpecial">${startDateUnix}</span><span class="synConstant">`</span>; Logger.log(query); <span class="synIdentifier">var</span> folderName = <span class="synConstant">`</span><span class="synSpecial">${folderBase}</span><span class="synConstant">/</span><span class="synSpecial">${Utilities.formatDate(startDate, </span><span class="synConstant">&quot;UTC&quot;</span><span class="synSpecial">, </span><span class="synConstant">&quot;yyyy-MM-dd&quot;</span><span class="synSpecial">)}</span><span class="synConstant">`</span>; saveAttachmentsToDrive(query, folderName); <span class="synIdentifier">}</span> <span class="synComment">/**</span> <span class="synComment"> * Searches for emails with a specific label, downloads their attachments, and saves them to Google Drive.</span> <span class="synComment"> * @param {string} query - The search query to find emails with the desired label and date range.</span> <span class="synComment"> * @param {string} folderName - The name of the folder in Google Drive where the attachments will be saved.</span> <span class="synComment"> */</span> <span class="synIdentifier">function</span> saveAttachmentsToDrive(query, folderName) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> folder = createFolderByPath(folderName); deleteAllFilesInFolder(folder); <span class="synComment">// Save attachments from emails matching the search query</span> <span class="synIdentifier">var</span> threads = GmailApp.search(query); <span class="synStatement">for</span> (<span class="synIdentifier">var</span> i = 0; i &lt; threads.length; i++) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> messages = threads<span class="synIdentifier">[</span>i<span class="synIdentifier">]</span>.getMessages(); <span class="synStatement">for</span> (<span class="synIdentifier">var</span> j = 0; j &lt; messages.length; j++) <span class="synIdentifier">{</span> Logger.log(messages<span class="synIdentifier">[</span>j<span class="synIdentifier">]</span>.getSubject() + <span class="synConstant">&quot; (&quot;</span> + messages<span class="synIdentifier">[</span>j<span class="synIdentifier">]</span>.getDate() + <span class="synConstant">&quot;)&quot;</span>); <span class="synIdentifier">var</span> attachments = messages<span class="synIdentifier">[</span>j<span class="synIdentifier">]</span>.getAttachments(); <span class="synStatement">for</span> (<span class="synIdentifier">var</span> k = 0; k &lt; attachments.length; k++) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> attachment = attachments<span class="synIdentifier">[</span>k<span class="synIdentifier">]</span>; folder.createFile(attachment); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synComment">/**</span> <span class="synComment"> * Creates a folder in Google Drive recursively.</span> <span class="synComment"> * @param {string} path - The path of the folder to create, separated by forward slashes.</span> <span class="synComment"> * @returns {Folder} - The created folder object.</span> <span class="synComment"> */</span> <span class="synIdentifier">function</span> createFolderByPath(path) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> folders = path.split(<span class="synConstant">'/'</span>); <span class="synIdentifier">var</span> folder = DriveApp.getRootFolder(); <span class="synStatement">for</span> (<span class="synIdentifier">var</span> i = 0; i &lt; folders.length; i++) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> subFolders = folder.getFoldersByName(folders<span class="synIdentifier">[</span>i<span class="synIdentifier">]</span>); <span class="synStatement">if</span> (subFolders.hasNext()) <span class="synIdentifier">{</span> folder = subFolders.next(); <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synIdentifier">{</span> folder = folder.createFolder(folders<span class="synIdentifier">[</span>i<span class="synIdentifier">]</span>); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synStatement">return</span> folder; <span class="synIdentifier">}</span> <span class="synComment">/**</span> <span class="synComment"> * Deletes all files in a given folder.</span> <span class="synComment"> * @param {Folder} folder - The folder in Google Drive where the files will be deleted.</span> <span class="synComment"> */</span> <span class="synIdentifier">function</span> deleteAllFilesInFolder(folder) <span class="synIdentifier">{</span> <span class="synStatement">if</span> (folder) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> files = folder.getFiles(); <span class="synStatement">while</span> (files.hasNext()) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> file = files.next(); file.setTrashed(<span class="synConstant">true</span>); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <p>この<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>は<a class="keyword" href="https://d.hatena.ne.jp/keyword/GMail">GMail</a>の指定されたラベル、かつ<a class="keyword" href="https://d.hatena.ne.jp/keyword/UTC">UTC</a>で前日分のメールから添付ファイルを指定のフォルダ(この場合はdmarc)に保存します。 処理のほとんどは<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a> Copilotに書いてもらいました。べんりー。</p> <p>この<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>は毎日1回、午前9時半くらいに実行しています。</p> <h3 id="2-dmarc-visualizerの準備">2. dmarc-visualizerの準備</h3> <p>dmarc-visualizerを準備します。</p> <pre class="code shell" data-lang="shell" data-unlink>git clone https://github.com/debricked/dmarc-visualizer cd dmarc-visualizer docker compose build docker compose pull</pre> <p>parsedmarcがうまくビルドできない場合は<a class="keyword" href="https://d.hatena.ne.jp/keyword/Python">Python</a>のバージョンによるのかもしれません。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/python">python</a>のバージョンを3.11に落としたところ、うまく動きました。 自分は <a href="https://github.com/kangaechu/dmarc-visualizer/tree/test">https://github.com/kangaechu/dmarc-visualizer/tree/test</a> で実行しています。</p> <h3 id="3-入力ファイルを配置">3. 入力ファイルを配置</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Google%20Drive">Google Drive</a>にたまったDMARCレポートのファイルをfiles/以下にコピーします。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/Google%20Drive">Google Drive</a>には日毎にフォルダが作成されていますが、フォルダ以下のファイルのみをコピーしてください。 また、zip/<a class="keyword" href="https://d.hatena.ne.jp/keyword/gzip">gzip</a>など圧縮されていますが圧縮されたままで大丈夫です。</p> <p>ファイルをコピーしたら <code>docker compose up -d</code> で起動します。 <code>parsedmarc</code> コンテナがパース・Elasticsearchへのロード処理を実施します。 処理が完了すると <code>parsedmarc</code> コンテナは終了しますので、コンテナが終了したらレポート結果を確認することができます。 Elasticsearchにロード後はfiles/に置いたファイルは削除しても大丈夫です。</p> <h3 id="4-レポート結果を確認">4. レポート結果を確認</h3> <p><a href="http://localhost:3000">http://localhost:3000</a> をブラウザで開き、Grafanaの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C0%A5%C3%A5%B7%A5%E5">ダッシュ</a>ボード「DMARC Reports」を開きます。</p> <p>Top 2000 Message Sources by Reverse <a class="keyword" href="https://d.hatena.ne.jp/keyword/DNS">DNS</a> では送信元のメールサーバの<a class="keyword" href="https://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>を逆引きしたホスト名と送信数の関連をみれたり、</p> <p><figure class="figure-image figure-image-fotolife" title="Top 2000 Message Sources by Reverse DNS"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231213/20231213141648.png" width="1200" height="303" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Top 2000 Message Sources by Reverse <a class="keyword" href="https://d.hatena.ne.jp/keyword/DNS">DNS</a></figcaption></figure></p> <p>Overviewではメールを送信した<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>ごとの<a class="keyword" href="https://d.hatena.ne.jp/keyword/SPF">SPF</a>/<a class="keyword" href="https://d.hatena.ne.jp/keyword/DKIM">DKIM</a>設定状況などを確認することができます。</p> <p><figure class="figure-image figure-image-fotolife" title="Overview"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231213/20231213154104.png" width="1200" height="355" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Overview</figcaption></figure></p> <p>身に覚えのないホスト名がいくつかありますね。</p> <h2 id="まとめ">まとめ</h2> <p>DMARCレポートは<a class="keyword" href="https://d.hatena.ne.jp/keyword/XML">XML</a>ファイルであり、1件ずつ眺めるのは大変です。 そのため、dmarc-visualizerを使用し、DMARCレポートの統計情報を眺めることができるようになりました。 なんとなく設定していたDMARCレポートですが、現状が見れるようになると楽しくなり、 <a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a>/Yahoo.comから出されたメール送信者の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AC%A5%A4%A5%C9%A5%E9%A5%A4%A5%F3">ガイドライン</a>対応へのモチベーションが上がるのではないでしょうか。</p> kangaechu ChatGPTでユーザの質問に回答するチャットボットを作ろうとしたけど、導入に失敗しました hatenablog://entry/6801883189062075826 2023-12-07T07:00:00+09:00 2023-12-07T07:00:02+09:00 OpenAIのAPIを使用してチャットボットを作りましたが、うまくいかなかったためお蔵入りしました。 今回はその供養のため、どのようなことをやったかを書き残そうかと思います。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231128/20231128130342.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この記事は <a href="https://qiita.com/advent-calendar/2023/crowdworks">クラウドワークス Advent Calendar 2023</a> シリーズ2 の 7日目の記事です。</p> <p>こんにちは。crowdworks.jp SRE チームの田中(@kangaechu)です。 2023年は生成AI、特にLLM(大規模<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%C0%B8%EC%A5%E2%A5%C7%A5%EB">言語モデル</a>)が盛り上がりを見せた年でした。皆さんもChatGPTや<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a> Copilotなどにより、その恩恵にあずかった1年だったかと思います。 自分も流行りに乗り、OpenAIの<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を使用してチャットボットを作りましたが、うまくいかなかったためお蔵入りしました。 今回はその供養のため、どのようなことをやったかを書き残そうかと思います。</p> <h2 id="背景">背景</h2> <h3 id="LLM-大規模言語モデル">LLM (大規模<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%C0%B8%EC%A5%E2%A5%C7%A5%EB">言語モデル</a>)</h3> <p>LLMブームの始まりは2022年11月30日にOpenAIがリリースしたChatGPTでした。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopenai.com%2Fblog%2Fchatgpt" title="Introducing ChatGPT" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://openai.com/blog/chatgpt">openai.com</a></cite></p> <p>ChatGPTは大量のテキストデータを用いて訓練された事前学習モデルです。 モデルは、コンテキストを理解し、人間のような対話や文章生成を行うことができます。 ChatGPTにより、利用者は文章生成、文章の翻訳、質問応答、文章の要約、プログラムのコード生成など、さまざまな<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BC%AB%C1%B3%B8%C0%B8%EC%BD%E8%CD%FD">自然言語処理</a>タスクを試行していました。</p> <p>その後、2023年3月1日にChatGPT <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>がリリースされました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopenai.com%2Fblog%2Fintroducing-chatgpt-and-whisper-apis" title="Introducing ChatGPT and Whisper APIs" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://openai.com/blog/introducing-chatgpt-and-whisper-apis">openai.com</a></cite></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>からgpt-3.5-turboというモデルが使用可能となり、ChatGPT相当の機能をアプリケーションから使用することが可能となりました。 また、<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>経由でのリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トの場合、<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を通じて送信されたデータをモデルト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A1%BC%A5%CB%A5%F3">レーニン</a>グや改善に使用しないことが明示されました。</p> <h3 id="クラウドワークスとLLM"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスとLLM</h3> <p>LLMの盛り上がりを受け、私は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークス社内でChatGPTを何かに使えないかな?と思っていました。</p> <p>まずは3月13日、SlackにChatGPTにより受け答えをするチャットボットを導入しました。 これにより、社内でもChatGPTをかんたんに使えるようになりました。 <figure class="figure-image figure-image-fotolife" title="Slackに導入したチャットボット。導入当初から実在しない情報を教えてくれます"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231128/20231128111529.png" width="1200" height="1022" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Slackに導入したチャットボット。導入当初から実在しない情報を教えてくれます</figcaption></figure></p> <p>また、検証で使用するOpenAIのアカウントを払い出したり、利用ルールを準備し、ChatGPTを業務で使いやすくするような作業も行いました。</p> <p>ただ、誰かがChatGPTを使用したア<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%C7%A5%A2">イデア</a>が実現化するのを待つのではなく、自分でもなにか業務と関連する検証を行ってみたいと思っていました。 そんな中、ChatGPTで実現できそうな<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>があるという話をユーザサポートの方からいただきました。</p> <h3 id="crowdworksjp-でのユーザサポート業務">crowdworks.jp でのユーザサポート業務</h3> <p>ユーザサポートはcrowdworks.jpのユーザが問題や疑問に直面した際に、それに対処し解決策を提供する部門です。 ユーザサポートの業務の一つとして、「crowdworks.jpのユーザフォームによる質問に対して回答を行う」というものがありますが、その対応をより効率的にしたいという思いがありました。 そこで、ChatGPTを効率化の道具として使えないかと私に声をかけてくれました。</p> <p>私としても以下の理由により、最初の一歩に向いているのでは?と思い検証を行うことにしました。</p> <ul> <li>スモールスタートに向いた案件である</li> <li>学習用のデータであるFAQはある程度揃っており、準備する必要がない</li> <li>FAQは公開情報のため、ChatGPTに与えても特に問題はない</li> <li>成功した場合、ユーザ体験の向上に結びつく事例となる</li> <li>期限等の縛りがないため、限られた<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%A9%BF%F4">工数</a>でも対応しやすい</li> <li>ユーザサポートの方がWelcomeなのでやりやすそう</li> </ul> <h2 id="構築">構築</h2> <h3 id="リソース">リソース</h3> <p>LLMについて何も知らない状態ですが、やる気だけはあるのでエンジニア<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%F3%A5%B0%A5%DE">リングマ</a>ネージャ(EM)に聞いてみたところ、問題なく了解をいただきました。 (こういうフットワークの軽さが<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスのいいところですね) ただ、本業はSREチームであり、本業でも色々なことを行なっていました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2F2023sre" title="2023年 crowdworks.jp の SRE チームでやったこと - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/2023sre">engineer.crowdworks.jp</a></cite></p> <p>結果として、この検証は以下のように行いました。</p> <ul> <li>実施期間:2023年4月〜6月</li> <li>実施日:毎週金曜日(他作業と重なる場合は短縮)</li> <li>実施人数:一人(自分です)</li> </ul> <h3 id="導入イメージ">導入イメージ</h3> <p>現在の仕組みを壊さず導入するため、以下の構成としました。 <figure class="figure-image figure-image-fotolife" title="導入イメージ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231127/20231127093238.png" width="635" height="321" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>導入イメージ</figcaption></figure></p> <p>ユーザがcrowdworks.jpのサービス画面からチャットボットに対して質問すると、チャットボットはOpenAIの<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>にアクセスし、質問に対する回答を返します。</p> <h3 id="調査">調査</h3> <h4 id="使用するツールAPI">使用するツール・<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a></h4> <p>使用するツール・<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>については以下のように決めました。</p> <ul> <li>LLM: <a href="https://platform.openai.com/docs/models/gpt-3-5">GPT-3.5-turbo</a> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%D5%A5%A1%A5%AF%A5%C8%A5%B9%A5%BF%A5%F3%A5%C0%A1%BC%A5%C9">デファクトスタンダード</a></li> <li>公式・非公式の資料が大量にあり、開発がしやすい</li> <li>ライブラリも整っている</li> <li>料金もそこまで高くない</li> <li>精度が悪かった場合、GPT-4.0に移行する</li> </ul> </li> <li>LLMを独自データで使用するためのライブラリ: <a href="https://gpt-index.readthedocs.io/en/latest/">LlamaIndex</a> 🦙 <ul> <li>独自データの学習、LLMへの質問などが簡単に書ける</li> <li>公式・非公式の資料が大量にあり、開発がしやすい</li> </ul> </li> </ul> <h4 id="独自データを返す方法">独自データを返す方法</h4> <p>ChatGPTは事前に学習した内容をもとに回答を行うため、crowdworks.jpのFAQのような事前に学習していない内容に関する質問をすると、でたらめな回答をします。 そのため、ChatGPTに独自データを返す方法が必要となります。</p> <p>方法としては大きく分けて以下の2種類があります。(2023年4月時点)</p> <ul> <li>Fine-Tuning</li> <li>In-Context Learning</li> </ul> <p>Fine-TuningはChatGPTの学習データ(モデル)に新しいデータを追加する方法です。 ただ、<a href="https://platform.openai.com/docs/guides/fine-tuning">https://platform.openai.com/docs/guides/fine-tuning</a> にある通り、GPT-3.5-turboはFine-Tuningを行うことはできません。 そのため、In-Context Learningを選択しました。 In-Context Learningとは、事前に質問に対する回答を取得し、その質問と回答をもとにChatGPTで回答を生成してもらうというものです。</p> <p>In-Context Learningでは、ユーザの質問に対して回答を返すいわば<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%A1%BA%F7%A5%A8%A5%F3%A5%B8%A5%F3">検索エンジン</a>のような機能が必要となります。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%B7%C1%C2%D6%C1%C7%B2%F2%C0%CF">形態素解析</a>と文字列マッチでも良いのですが、類義語などに対して弱いため、質問文と回答文をベクトル化し、それぞれの類似度を計算するような仕組みとしました。 ベクトル化は<a href="https://platform.openai.com/docs/guides/embeddings/what-are-embeddings">Embedding</a>と呼ばれます。 EmbeddingにはOpenAIの<code>text-embedding-ada-002</code> というモデルを使用しました。 このモデルは入力したテキストを<code>cl100k_base</code> という<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ナイザーで分割し、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンごとに1,536次元のベクトルを返します。</p> <p>ベクトル化したデータを保持・類似度の計算のため、ChromaDBを使用しました。 <a href="https://www.trychroma.com/">https://www.trychroma.com/</a></p> <p>結果として、以下のような処理概要としました。</p> <p><figure class="figure-image figure-image-fotolife" title="処理概要"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231127/20231127115823.png" width="680" height="501" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>処理概要</figcaption></figure></p> <h2 id="検証">検証</h2> <p>処理概要に沿った検証用のコードを作成しました。 作成した質問とcrowdworks.jpのFAQ一覧を元に、精度検証を行い、以下のような検証結果となりました。 (FAQ検索部分は検索結果を類似度の上位4件以内に正しい回答を含む場合、OKとしました)</p> <ol> <li>質問から回答まで → 精度 18%</li> <li>検索部分のみ → 精度 50%</li> </ol> <p><figure class="figure-image figure-image-fotolife" title="精度検証結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231127/20231127131519.png" width="400" height="480" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>精度検証結果</figcaption></figure></p> <p>ひ、低い...。 ユーザからの質問に18%の精度でしか正しい回答を返さないチャットボット、これは使い物にならないですよね。 また、質問から回答までに30秒以上の時間がかかるのも体験がよくありません。</p> <p>当初はこれよりも精度が低かったのですが精度を改善するため、</p> <ul> <li>検索に使用する質問と回答のセットを増やす</li> <li>質問をChatGPTで要約する</li> <li>検索で与える項目を変更する</li> </ul> <p>などの対応を行いましたが、最終的にこの値を超えることができませんでした。</p> <h2 id="精度が低い原因">精度が低い原因</h2> <p>精度が低い原因は以下の通りと考えています。</p> <ol> <li>「FAQ」によるもの <ul> <li>FAQのカテゴリが多い <ul> <li>600弱のカテゴリがあり、カテゴリ分類での一致はそもそも難しい</li> </ul> </li> <li>FAQが不足している <ul> <li>ピンポイントで使えるFAQが不足している場合があった</li> </ul> </li> <li>似たFAQが存在している <ul> <li>「どちらを選んでも正しそう」というものがある</li> </ul> </li> </ul> </li> <li>「質問文」によるもの <ul> <li>複数の質問が含まれる・明瞭でない質問は精度が出ない</li> <li>質問にコンテキスト(仕事の種類・クライアント/ワーカ)が不足している</li> <li>簡単な質問であればChatGPTで返せるが、複雑なものだとサービスと接続してユーザの状態を取得するなど、作り込みが必要</li> </ul> </li> <li>「ChatGPT」によるもの <ul> <li>回答が最後まで表示されないことがある <ul> <li>回答に必要な<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ン数が不足</li> <li>検証以降に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンの上限が緩和されたので、今であればもう少し良い結果となる可能性がある</li> </ul> </li> <li>個人情報を入れづらい <ul> <li>学習には使わないことは明示されているが、センシティブなデータを外部に出すことに躊躇いがある</li> </ul> </li> <li>しれっと嘘をつく <ul> <li>FAQにあることと反対のことを返したりする。「評価の変更はできない」が「評価の変更はできます」と返したり</li> </ul> </li> </ul> </li> </ol> <h2 id="失敗からの学び">失敗からの学び</h2> <p>精度が出ないことで自分のモチベーションも下がってしまいました。 また、他の対応方法も検討しましたが実現にこぎつけることはできませんでした。 そのため、6月頃に撤退するとの判断をしました。</p> <p>以下、失敗からの学びです。</p> <h3 id="1-検証と導入を分離する">1. 検証と導入を分離する</h3> <p>結果から考えると、当初の目的の一つである検証自体は完了しています。 また、質問に対する回答を正しく返すことはできませんでしたが、検索自体の精度は600弱のカテゴリで50%とそこまで悪くなく、 データの追加や前提の変更により精度が改善する見込みはありました。</p> <p>しかし、導入を目的に進めていたため、精度が出ない・上がらないことで焦りが出てしまい、失敗したと判断しました。 一人プロジェクト・サブプロジェクトで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%A9%BF%F4">工数</a>に余裕がない中、実使用できるレベルの精度を出すのは難しかったです。</p> <p>検証が終わったらひとまずその成果を報告し、次のステップに進む<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%A9%BF%F4">工数</a>・人員を確保できるのであれば導入に向けた方向に進めるべきでした。</p> <h3 id="2-適用範囲を減らす">2. 適用範囲を減らす</h3> <p>当初は「crowdworks.jpのサイトにチャットボットを置いて使用する」という方針で作っていたため、全カテゴリで精度がでないと失敗という判断をしました。 しかし、特定画面・特定の質問のみ回答させるような導入方法も考えられたはずです。</p> <p>最初から大風呂敷を広げず、できるだけスモールスタートで始められるように期待値を下げるべきでした。</p> <h2 id="まとめ">まとめ</h2> <p>ChatGPTの<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を使用し、FAQ検索によるユーザの質問に回答するチャットボットを作成しました。 OpenAIの<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を使用したプログラムを作成し、検証はできましたが、精度が出なかったためお蔵入りとしました。 ただ、ChatGPTが合う<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>は別にあるはずなので、フィットする業務を見つけたら再チャレンジしたいと思っています。</p> kangaechu 2023年 crowdworks.jp の SRE チームでやったこと hatenablog://entry/6801883189060471803 2023-12-06T07:00:00+09:00 2023-12-06T07:00:18+09:00 crowdworks.jp の SRE チームが2023年にやったことを記載していきます。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231121/20231121133913.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この記事は <a href="https://qiita.com/advent-calendar/2023/crowdworks">クラウドワークス Advent Calendar 2023</a> シリーズ1 6日目の記事です。</p> <p>こんにちは。crowdworks.jp SRE チームの田中(@kangaechu)です。 この記事では crowdworks.jp の SRE チームが2023年にやったことを記載していきます。 やっていることは色々で、まとまりはありませんが、そこら辺はご容赦ください。</p> <ul class="table-of-contents"> <li><a href="#2022年の振り返り">2022年の振り返り</a></li> <li><a href="#2023年にやったこと">2023年にやったこと</a><ul> <li><a href="#CircleCI-インシデント対応">CircleCI インシデント対応</a></li> <li><a href="#RundeckのECS化">RundeckのECS化</a></li> <li><a href="#Railsで使用しているMemcachedをRedisに寄せる">Railsで使用しているMemcachedをRedisに寄せる</a><ul> <li><a href="#課題">課題</a><ul> <li><a href="#1-Memcachedでは停止時にデータが揮発する">1. Memcachedでは停止時にデータが揮発する</a></li> <li><a href="#2-Memcached単体ではマルチAZ構成でデータのレプリケーションができない">2. Memcached単体ではマルチAZ構成でデータのレプリケーションができない</a></li> <li><a href="#3-MemcachedRedisともにバージョンアップをしていない">3. Memcached/Redisともにバージョンアップをしていない</a></li> <li><a href="#4-MemcachedとRedisの使い分けが曖昧">4. MemcachedとRedisの使い分けが曖昧</a></li> </ul> </li> <li><a href="#対応">対応</a></li> <li><a href="#結果">結果</a></li> </ul> </li> <li><a href="#MySQL-80-アップデート">MySQL 8.0 アップデート</a></li> <li><a href="#Redshift-AQUA化">Redshift AQUA化</a></li> <li><a href="#メール">メール</a></li> <li><a href="#SRE-NEXT-2023">SRE NEXT 2023</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h2 id="2022年の振り返り">2022年の振り返り</h2> <p>去年までにどのようなことをしてきたかはこの記事を参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2F2022%2F12%2F02%2F090026" title="2022年 crowdworks.jp の SRE チームでやったこと - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/2022/12/02/090026">engineer.crowdworks.jp</a></cite></p> <h2 id="2023年にやったこと">2023年にやったこと</h2> <h3 id="CircleCI-インシデント対応">CircleCI インシデント対応</h3> <p>2023年1月4日、まだお正月気分が抜けていない中、CircleCIのインシデントが発生しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcircleci.com%2Fja%2Fblog%2Fjan-4-2023-incident-report%2F" title="CircleCI 2023年1月4日セキュリティインシデントに関するインシデントレポート " class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://circleci.com/ja/blog/jan-4-2023-incident-report/">circleci.com</a></cite></p> <p>crowdworks.jp ではCircleCIをさまざまな<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>で使用しており、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AB%A5%A4%A5%D6">アーカイブ</a>済みの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>を含めると700超のデプロイキー・<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a>をCircleCIに保持していました。 重要なキーについては当日中の対応を行いました。またcrowdworks.jp のエンジニアにも作業を依頼し、1週間程度で対応を終えることができました。</p> <p>また、2022年の時点でCircleCIと<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>間の認証をアクセスキーからOIDCに変更していたことが影響範囲の減少につながりました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2F2022%2F12%2F02%2F090026" title="2022年 crowdworks.jp の SRE チームでやったこと - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/2022/12/02/090026#GitHub-Actions-%E3%81%A8-CircleCI-%E3%81%AE-OIDC-%E5%AF%BE%E5%BF%9CAWS-%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%82%AD%E3%83%BC%E5%BB%83%E6%AD%A2">engineer.crowdworks.jp</a></cite></p> <p>インシデント対応は突然発生する優先度の高いイベントで辛いですが、OIDC化のようなこまめなセキュリティ改善対応が実を結んだのは嬉しいですね。</p> <h3 id="RundeckのECS化">RundeckのECS化</h3> <p>crowdworks.jpでは、2017年頃からバッチジョブのスケジューラとしてRundeck v2系を使用してきました。 バージョンアップの優先度が上がらないままRundeck v2系のままで運用を続けていましたが、 2022年3月にRundeck v4系がリリースされたこともあり、Rundeckをアップグレードしました。 ついでに構成をEC2からECSへ変更しました。</p> <p>詳細はこちらのブログを参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2F2023%2F05%2F01%2F100000" title="RundeckをFargate化してデプロイを自動化しました - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/2023/05/01/100000">engineer.crowdworks.jp</a></cite></p> <h3 id="Railsで使用しているMemcachedをRedisに寄せる"><a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>で使用している<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>をRedisに寄せる</h3> <h4 id="課題">課題</h4> <p>crowdworks.jpでは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>上で<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>アプリケーションを運用しており、セッションやキャッシュを保持するKVSとして以前は<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>とRedisの両方を利用していました。 しかし、以下の課題を抱えていました。</p> <h5 id="1-Memcachedでは停止時にデータが揮発する">1. <a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>では停止時にデータが揮発する</h5> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>は再起動や停止時にデータが揮発します。 スケールアップやバージョンアップなど、ElastiCacheには停止を必要とする処理があります。 その度にセッションの情報が揮発するとユーザは再ログインが必要となるなど、体験が悪化します。 開発者にとっても変更しやすいシステムの障壁となるような構成はできるだけ避けたいと考えていました。</p> <h5 id="2-Memcached単体ではマルチAZ構成でデータのレプリケーションができない">2. <a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>単体ではマルチAZ構成でデータの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A5%D7%A5%EA%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3">レプリケーション</a>ができない</h5> <p>検討当初、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%B9%A5%BF">クラスタ</a>ー構成をとっておらず、AZごとにノードが起動している状態でした。 また、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%B9%A5%BF">クラスタ</a>ー構成にした場合であっても、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>のみではデータの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A5%D7%A5%EA%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3">レプリケーション</a>構成とすることができません。 AZ障害で<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>が1ノード停止すると、停止したノード分のデータは消失します。 障害に強い構成にし、障害時でもできる限りデータを保持するような構成としたいです。</p> <h5 id="3-MemcachedRedisともにバージョンアップをしていない">3. <a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>/Redisともにバージョンアップをしていない</h5> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>/Redisともに2018年頃からアップデートの優先度が下がっており、放置しても動き続けていたため、いわゆる「なんもわからんが動いているシステム」となっていました。 セキュリティアップデート以外の更新がされておらず、改善が回らない状況となっていました。 このまま<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>/Redisを両方使い続けるのは運用負荷が高く、これからもバージョンアップされないシステムとなる可能性があります。</p> <h5 id="4-MemcachedとRedisの使い分けが曖昧">4. <a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>とRedisの使い分けが曖昧</h5> <p>crowdworks.jpの<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>アプリケーションでは<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>とRedisを併用していましたが、実際は雰囲気で使い分けているようでした。 「<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>でなければ性能要件が満たせない」というようなこともないため、両方運用し続ける必要はないと判断しました。</p> <h4 id="対応">対応</h4> <p>変更前は<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a> x 5台、Redis x 1セットという構成をRedis x 2セットに変更しました。 Redisを2種類準備したのは、データの優先度により格納する<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%B9%A5%BF">クラスタ</a>を分離したからです。 具体的には「セッション情報など、Evictionによるデータ損失を許さない<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%B9%A5%BF">クラスタ</a>」と「キャッシュなど、Evictionによるデータ揮発をある程度許容する<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%B9%A5%BF">クラスタ</a>」に分離しました。</p> <p>アプリケーション側の切り替えも必要でしたが、バックエンドエンジニアのBugfireがデータ移行をガンガン進めてくれたので助かりました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2Fgoodbye_memcached" title="MemcachedからRedisに変更しました - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/goodbye_memcached">engineer.crowdworks.jp</a></cite></p> <h4 id="結果">結果</h4> <p>Redisバージョンを最新化し、新機能が享受できる環境を作ることができました。 また、Redisと<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>を併用していた環境からRedisに片寄せすることで、バージョンアップなどによる運用コストの低減につなげることができます。 また、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>も最新世代が使えるようになり、台数も減らすことができたことでコストの低減にもつながりました。</p> <h3 id="MySQL-80-アップデート"><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0 アップデート</h3> <p>crowdworks.jpでは <a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> RDS <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7を使用していました。 しかし、<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7のEOLが2023年10月だったため、8.0へのアップデートを実施しました。</p> <p>詳細はこちらのブログを参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2Fupdate-mysql8" title="crowdworks.jpのマスタデータベースをAWS RDS MySQL 5.7から8.0にアップデートしました - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/update-mysql8">engineer.crowdworks.jp</a></cite></p> <p>大きな問題もなく移行が完了しました。次はAuroraだ!</p> <h3 id="Redshift-AQUA化">Redshift AQUA化</h3> <p>crowdworks.jpではデータ分析基盤としてRedshiftを使用しています。 RedshiftのクエリにはRedashを使用しており、さまざまな部門の利用者がデータによる現状把握、合理的な意思決定、顧客の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B5%A5%A4%A5%C8">インサイト</a>把握、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%A1%B3%A3%B3%D8%BD%AC">機械学習</a>などに活用しています。 しかし、crowdworks.jpのサービス成長に伴いデータ量が増えたり、データ分析基盤の活用の幅が増えてきたことでRedshiftのリソース不足が時折発生していました。</p> <p>Redshiftには第2世代の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を使用していましたが、第3世代の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>に更新することにより、AQUA(Advanced Query Accelerator)による最新機能の恩恵を受けることができます。 AQUA化によるクエリパフォーマンスの最大10倍向上は嬉しいですね。 また、現在<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> DMS (Database Migration Service) で<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>からRedshiftに同期しているのですが、 2023年11月7日に一般提供が開始された<a class="keyword" href="https://d.hatena.ne.jp/keyword/Amazon%20Aurora">Amazon Aurora</a> <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> zero-ETL integration with <a class="keyword" href="https://d.hatena.ne.jp/keyword/Amazon">Amazon</a> Redshift を使用することで、DMSが不要になりそうです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fabout-aws%2Fwhats-new%2F2023%2F11%2Faws-general-availability-amazon-aurora-mysql-zero-etl-integration-redshift%2F" title="AWS が Amazon Aurora MySQL と Amazon Redshift のゼロ ETL 統合の一般提供開始を発表" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://aws.amazon.com/jp/about-aws/whats-new/2023/11/aws-general-availability-amazon-aurora-mysql-zero-etl-integration-redshift/">aws.amazon.com</a></cite></p> <p>ということで、Redshift AQUA化を行いました。 分析クエリの実行時間が(10倍ではないですが)軒並み速くなったのは嬉しいところです。</p> <p>データ分析基盤にはまだまだ改善できるところが多くありますので、引き続き手をかけていきたいです。</p> <h3 id="メール">メール</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a>とYahooが相次いでメール送信者に対する<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AC%A5%A4%A5%C9%A5%E9%A5%A4%A5%F3">ガイドライン</a>を発表しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsupport.google.com%2Fmail%2Fanswer%2F81126" title="Email sender guidelines - Gmail Help" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://support.google.com/mail/answer/81126">support.google.com</a></cite></p> <p><a href="https://blog.postmaster.yahooinc.com/post/730172167494483968/more-secure-less-spam">Postmaster @ Yahoo &amp; AOL &mdash; More Secure, Less Spam: Enforcing Email Standards...</a></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AC%A5%A4%A5%C9%A5%E9%A5%A4%A5%F3">ガイドライン</a>にはメール認証の有効化・購読解除機能・メールのスパムレートが含まれています。 crowdworks.jpではユーザの体験を最大化し、スパムの少ないインターネット環境を目指してこれらの設定を積極的に進めていきます。</p> <h3 id="SRE-NEXT-2023">SRE NEXT 2023</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスは2023年9月29日に開催されたSRE NEXT 2023で、Bronze sponsorとして協賛しました。 当日は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークス内の複数サービスから複数のSREチームメンバーが集結し、お揃いのTシャツを着て楽しみました。 次回は成果を登壇して発表することを目標に、引き続き活動を進めていきます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2F2023%2F09%2F27%2F154143" title="SRE NEXT 2023 にスポンサーとして参加します - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/2023/09/27/154143">engineer.crowdworks.jp</a></cite></p> <h2 id="まとめ">まとめ</h2> <p>2023年はCircleCIのインシデントに始まり、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a>/Yahooのメール<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AC%A5%A4%A5%C9%A5%E9%A5%A4%A5%F3">ガイドライン</a>対応に終わる外部影響の多い1年だったかもしれません。 その中で、ElastiCacheをRedisに寄せたり、<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>のバージョンを8.0に更新するなど、根幹となるシステムの改善に手をつけれられたかなと思っています。</p> <p>crowdworks.jpの信頼性をさらに高めるため、来年も継続して活動を進めていきます。</p> kangaechu 本業、副業、趣味でソフトウェア開発している話 hatenablog://entry/6801883189062060107 2023-12-02T08:46:02+09:00 2023-12-02T08:46:02+09:00 この記事は クラウドワークス Advent Calendar 2023 シリーズ 1の2日目の記事です。 こんにちは。crowdworks.jp でエンジニアをしている高橋です。 この記事は本業、副業、趣味でソフトウェア開発をしていて感じたことや経験を振り返ったポエムになります。技術的な要素はなく、働き方の話がメインになります。 はじめは、個人のブログや個人利用の文章共有サービスにて書き残そうと思いましたが、フリーランスや副業関連のサービスを提供している側の会社に所属していることもあり、現在の状況について、誰かの参考になればと思い、クラウドワークスのエンジニアブログに書き残すことにしました。 <p><figure class="figure-image figure-image-fotolife" title="本業、副業、趣味でソフトウェア開発している話"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryogift/20231127/20231127100921.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure></p> <p>この記事は <a href="https://qiita.com/advent-calendar/2023/crowdworks">クラウドワークス Advent Calendar 2023</a> シリーズ 1の2日目の記事です。</p> <p>こんにちは。crowdworks.jp でエンジニアをしている高橋です。</p> <p>この記事は本業、副業、趣味でソフトウェア開発をしていて感じたことや経験を振り返ったポエムになります。技術的な要素はなく、働き方の話がメインになります。</p> <p>はじめは、個人のブログや個人利用の文章共有サービスにて書き残そうと思いましたが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>や副業関連のサービスを提供している側の会社に所属していることもあり、現在の状況について、誰かの参考になればと思い、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスのエンジニアブログに書き残すことにしました。</p> <h2 id="目次">目次</h2> <ul> <li><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></li> <li><a href="#%E6%9C%AC%E6%A5%AD%E5%89%AF%E6%A5%AD%E8%B6%A3%E5%91%B3%E3%81%AE%E7%8A%B6%E6%B3%81">本業、副業、趣味の状況</a></li> <li><a href="#%E6%89%80%E6%84%9F">所感</a></li> <li><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></li> <li><a href="#Were-hiring">We're hiring!</a></li> </ul> <h2 id="はじめに">はじめに</h2> <p>ソフトウェア開発者としてのキャリア形成を考える中で、私は、以下の書籍を参考にしながら働き方について模索しております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbookplus.nikkei.com%2Fatcl%2Fcatalog%2F22%2FS00500%2F" title="SOFT SKILLS ソフトウェア開発者の人生マニュアル 第2版" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://bookplus.nikkei.com/atcl/catalog/22/S00500/">bookplus.nikkei.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbookplus.nikkei.com%2Fatcl%2Fcatalog%2F18%2FP55740%2F" title="CAREER SKILLS ソフトウェア開発者の完全キャリアガイド" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://bookplus.nikkei.com/atcl/catalog/18/P55740/">bookplus.nikkei.com</a></cite></p> <p>現在では主流となったリモートワークも、私が働き始めた10年以上前のときには<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>の人しか出来ないようなイメージがあり憧れていました。当時は、どのようなスキルを身につければリモートワークをしながら自由に仕事ができるのだろうかと考えていた時期もあり、そんなときに読んだ書籍でもあります。</p> <p>crowdworks.jp の中でエンジニアをするようになってからは、フルリモートでフ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EB%A5%D5%A5%EC">ルフレ</a>ックス、かつ副業OKという状況になり、当時とは比べものにならないくらいの好条件で働けています。</p> <p>折角、副業OKな会社に所属しているのであれば、機会があったら副業にも挑戦してみたいと思っていたところ、今年は運良く副業をする機会を手に入れることができました。</p> <p>今のところ、半年以上にわたって本業、副業、趣味でソフトウェア開発を続ける機会に恵まれているため、どのような状況で働いているかを書き綴っていきます。</p> <h2 id="本業副業趣味の状況">本業、副業、趣味の状況</h2> <p>現在は、以下の状況でソフトウェア開発をしています。</p> <h6 id="本業">本業</h6> <ul> <li>形態: 株式会社<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスの従業員</li> <li>内容: 中規模なシステムの開発</li> <li>収支: 安定的な収入</li> <li>時間: 平日7〜9時間程度</li> <li>条件: フルリモート勤務、フ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EB%A5%D5%A5%EC">ルフレ</a>ックス制度あり、副業OK</li> </ul> <h6 id="副業">副業</h6> <ul> <li>形態: とある会社と<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B6%C8%CC%B3%B0%D1%C2%F7%B7%C0%CC%F3">業務委託契約</a></li> <li>内容: 小規模なシステムの開発</li> <li>収支: 時給制の変動する収入</li> <li>時間: 平日1〜3時間程度、土日に少し稼働するときもある</li> <li>条件: フルリモート勤務、勤務時間は事前申告で調整可能</li> </ul> <h6 id="趣味">趣味</h6> <ul> <li>形態: 個人</li> <li>内容: 個人でMVP(Minimum Viable Product)開発、技術検証</li> <li>収支: 各種<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>サービスの利用料による支出</li> <li>時間: 平日0〜2時間程度、土日に少し取り組む</li> <li>条件: 特になし</li> </ul> <p>本業、副業ともにリモートで勤務できており、本業の終わりにすぐに副業に切り替えることもできます。リモートワークはありがたいです。</p> <p>本業と趣味の状態から、副業が加わったことによる一番の変化は、時間の制約と生産性をより意識するようになったことです。平日に副業をしていることもあり、本業で残業が発生してしまうと副業に影響してしまうため、時間内で対応できるように考えて行動したり、副業では時給制で仕事をしていることもあり、時間に見合った成果を提供しつづけるということを大切にしています。</p> <p>平日に本業と副業でソフトウェア開発をした後に、趣味でソフトウェア開発も出来るのかというと、個人的には厳しくて出来ない日の方が多いくらいでした。</p> <p>「はじめに」で紹介した書籍にも記載されてますが、時間の管理は大切で夜遅くまで作業をしていると健康が破綻しますし、実際にコンピュータの前に座り続けるのが辛くなりました。やはり息抜きは大切なようです。</p> <h2 id="所感">所感</h2> <p>本業と副業をしているので当然労働時間が長くなりますが、趣味の時間と違って仕事で別業種のユーザーがいるシステムを開発できる機会があるというのは、とても貴重な体験だと感じています。</p> <p>企業のフェーズによって、機能開発の考え方も異なるので仕事としてソフトウェア開発ができることはありがたいです。ただし、技術的な成長では、私の場合は趣味の時間に技術検証をしてみたり、技術書を読んだりすることによって技術の深掘りや習得が出来ていると感じているため、趣味の時間も大切だと感じています。</p> <p>最近は、週休3日制という制度を取り入れている会社もあるので、本業、副業、趣味の割合をもう少し柔軟に調整できると良いですね。</p> <h2 id="おわりに">おわりに</h2> <p>個人的な働き方と所感について、ご紹介しました。現在の働き方をいつまで維持できるかは分かりませんが、経験値を増やすことは大切で今後のキャリア形成にも影響してくると思っています。</p> <p>数年前と比較すると副業に挑戦できる条件や機会が増えてきていますので、機会があったら、ぜひとも挑戦してみてください。</p> <p>より多くのソフトウェア開発経験によって、よりよい crowdworks.jp のサービス提供に貢献していきます。</p> <h2 id="Were-hiring">We're hiring!</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスでは、各種サービスや各種ポジションでエンジニアを募集しています。 ご興味のある方は以下のリンクからご応募ください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcrowdworks.co.jp%2Fcareers%2F" title="採用情報 | 株式会社クラウドワークス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://crowdworks.co.jp/careers/">crowdworks.co.jp</a></cite></p> ryogift 今年の汚れ、今年のうちに!MySQLで使っていないインデックスを削除しよう hatenablog://entry/6801883189060826381 2023-12-02T07:00:00+09:00 2023-12-10T19:53:03+09:00 MySQLで使用していないインデックスを削除する手順です。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20231122/20231122170452.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> この記事は <a href="https://qiita.com/advent-calendar/2023/crowdworks">クラウドワークス Advent Calendar 2023</a> シリーズ2 2日目の記事です。</p> <p>こんにちは。crowdworks.jp SRE チーム 田中(@kangaechu)です。 年末といえば大掃除ですね。 皆さんのデータベースにも使っていないインデックスが溜まっていませんか? お掃除してきれいな新年を迎えましょう。</p> <h2 id="手順">手順</h2> <h3 id="1-MySQLで使っていないインデックスの一覧を取得">1. <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>で使っていないインデックスの一覧を取得</h3> <p>未使用のインデックスは <code>sys.unused_indexes</code> ビューで確認できます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2F8.0%2Fen%2Fsys-schema-unused-indexes.html" title="MySQL :: MySQL 8.0 Reference Manual :: 28.4.3.32 The schema_unused_indexes View" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://dev.mysql.com/doc/refman/8.0/en/sys-schema-unused-indexes.html">dev.mysql.com</a></cite></p> <p>しかし、このビューの元データである <code>performance_schema</code> テーブルは起動時から終了時までのデータしか保持していません。</p> <blockquote><p>Tables in the Performance Schema are in-memory tables that use no persistent on-disk storage. The contents are repopulated beginning at server startup and discarded at server shutdown.</p> <p><a href="https://dev.mysql.com/doc/refman/8.0/en/performance-schema.html">https://dev.mysql.com/doc/refman/8.0/en/performance-schema.html</a></p></blockquote> <p>そのため、長期間起動した状態のDB<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を準備しましょう。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synComment">-- 未使用のインデックスを確認</span> <span class="synStatement">SELECT</span> object_name, index_name <span class="synSpecial">FROM</span> sys.schema_unused_indexes <span class="synSpecial">WHERE</span> object_schema = <span class="synSpecial">'</span><span class="synConstant">test</span><span class="synSpecial">'</span> <span class="synSpecial">ORDER</span> <span class="synSpecial">BY</span> object_name, index_name; </pre> <h3 id="2-削除してはいけないインデックスを除外">2. 削除してはいけないインデックスを除外</h3> <p><code>sys.unused_indexes</code> ビューには、削除してはいけないインデックスも含まれています。 今回対象となったのは、FOREIGN KEY 制約を持つインデックスです。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>のドキュメントにはFOREIGN KEY 制約とインデックスの関係について、以下のような記述があります。</p> <blockquote><p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> requires indexes on foreign keys and referenced keys so that foreign key checks can be fast and not require a table scan. In the referencing table, there must be an index where the foreign key columns are listed as the first columns in the same order. Such an index is created on the referencing table automatically if it does not exist. This index might be silently dropped later if you create another index that can be used to enforce the foreign key constraint. index_name, if given, is used as described previously.</p> <p><a href="https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html">https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html</a></p></blockquote> <ul> <li>FOREIGN KEYのチェックを高速化し、テーブルスキャンを必要としないように、FOREIGN KEYと参照キーのインデックスが必要</li> <li>参照テーブルには、FOREIGN KEY列が同じ順序で最初の列としてリストされるインデックスが必要</li> <li>このようなインデックスが存在しない場合は、参照テーブルに自動的に作成される</li> </ul> <p>つまり、</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synComment">-- parent table</span> <span class="synStatement">CREATE</span> <span class="synSpecial">TABLE</span> parent(id <span class="synType">int</span> PRIMARY KEY, name <span class="synType">varchar</span>(<span class="synConstant">20</span>)); <span class="synComment">-- child table</span> <span class="synStatement">CREATE</span> <span class="synSpecial">TABLE</span> child(id <span class="synType">int</span> PRIMARY KEY, name <span class="synType">varchar</span>(<span class="synConstant">20</span>), parent_id <span class="synType">int</span>, <span class="synSpecial">INDEX</span> index_child_parent_id(parent_id), FOREIGN KEY fk_child_parent_id(parent_id) REFERENCES parent(id) ); </pre> <p>このようなテーブルがある場合、<code>child</code> テーブルの <code>parent_id</code> 列にはインデックスが必要です。 そのため、 <code>index_child_parent_id</code> を削除しようとすると、インデックスが未使用であってもエラーになります。</p> <pre class="code lang-sql" data-lang="sql" data-unlink>mysql&gt; <span class="synStatement">DROP</span> <span class="synSpecial">INDEX</span> index_child_parent_id <span class="synSpecial">ON</span> child ALGORITHM=INPLACE <span class="synStatement">LOCK</span>=NONE; ERROR <span class="synConstant">1553</span> (HY000): Cannot <span class="synStatement">drop</span> <span class="synSpecial">index</span> <span class="synSpecial">'</span><span class="synConstant">index_child_parent_id</span><span class="synSpecial">'</span>: needed <span class="synStatement">in</span> a foreign key constraint </pre> <p>そのため、これらのインデックスは削除対象から除外しましょう。</p> <h3 id="3-インデックスをinvisibleに変更">3. インデックスをinvisibleに変更</h3> <p>削除対象が決まったところで、インデックスを削除したいところです、がやっぱり怖いですよね。 そんなあなたに朗報です。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0からInvisible Indexという機能が追加されました。 これはインデックスをInvisible(=不可視)にできるという機能です。 レコードの追加・変更・削除時にインデックスは更新されますが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AA%A5%D7%A5%C6%A5%A3%A5%DE">オプティマ</a>イザはそのインデックスを使いません。</p> <ul> <li>インデックス削除</li> <li>→ 実は使ってたのでパフォーマンス悪化</li> <li>→ 慌ててインデックス作成</li> <li>→ インデックス作成時にリソースを消費し、パフォーマンス悪化</li> </ul> <p>みたいなことがないように、試しに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AA%A5%D7%A5%C6%A5%A3%A5%DE">オプティマ</a>イザがインデックスを使わない設定を試すことができます。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">ALTER</span> <span class="synSpecial">TABLE</span> child <span class="synStatement">ALTER</span> <span class="synSpecial">INDEX</span> index_child_parent_id INVISIBLE, ALGORITHM=INPLACE, <span class="synStatement">LOCK</span>=NONE; </pre> <h3 id="4-インデックスを削除">4. インデックスを削除</h3> <p>インデックスをinvisibleにして問題ないことが確認できたら、実際に削除しましょう。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">DROP</span> <span class="synSpecial">INDEX</span> index_child_parent_id <span class="synSpecial">ON</span> child ALGORITHM=INPLACE <span class="synStatement">LOCK</span>=NONE; </pre> <p>このクエリでは <code>ALGORITHM=INPLACE</code> を指定していますが、これは クエリ実行時に使用する<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>を指定しています。 指定しない場合、<code>INSTANT</code> → <code>INPLACE</code> → <code>COPY</code>の順に適用されます。 指定した場合、その<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>が使えない場合には失敗します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2F8.0%2Fen%2Falter-table.html" title="MySQL :: MySQL 8.0 Reference Manual :: 13.1.9 ALTER TABLE Statement" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://dev.mysql.com/doc/refman/8.0/en/alter-table.html">dev.mysql.com</a></cite></p> <p>今回は意図しない処理およびロックを防ぐため、明示的に指定しています。 オペレーションごとに指定可能な<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>はドキュメントに記載があります。 今回はOnline <a class="keyword" href="https://d.hatena.ne.jp/keyword/DDL">DDL</a> Operations - Dropping an indexを参照し、<code>In Place</code> が <code>Yes</code> だったため、 <code>INPLACE</code> を指定しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2F8.0%2Fen%2Finnodb-online-ddl-operations.html" title="MySQL :: MySQL 8.0 Reference Manual :: 15.12.1 Online DDL Operations" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html">dev.mysql.com</a></cite></p> <p>記述が重複するため詳細な説明は省きますが、<code>LOCK</code> も<code>ALGORITHM</code> と同様に指定しています。</p> <h3 id="まとめ">まとめ</h3> <p>「テーブルを作ったし、とりあえずインデックスを張っておこう」という理由でインデックスを作成することはよくあるかと思います。 しかし、無駄に作成されたインデックスはレコード追加・変更・削除時にコストが発生し、リソースを消費します。</p> <p>この対応により、crowdworks.jpでは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BB%A5%AB%A5%F3%A5%C0%A5%EA">セカンダリ</a>インデックスの20%以上を削除することができました。</p> <p>統計情報の変更やバージョンアップなどにより、実行計画は変化していくため、必要なインデックスは随時変化します。 不要なインデックスは削除し、必要なインデックスを作成しやすい環境を構築していきたいと思います。</p> kangaechu crowdworks.jp のフロントエンド活動を振り返る2023 hatenablog://entry/6801883189061319342 2023-12-01T09:30:00+09:00 2023-12-01T09:34:22+09:00 この記事はクラウドワークス Advent Calendar 2023 シリーズ 1の1日目の記事です。 クラウドソーシングサービス「クラウドワークス」(以下 crowdworks.jp )にてエンジニアをしております、フロントエンドの可能性をしつこく信じ続ける@okuto_oyamaです。 一昨年・去年と引き続き、今年もアドベントカレンダー初日の盛り上げ手としてやっていきます。よろしくお願いします。 フロントエンド活動の振り返りをしてみよう 一昨年・去年もフロントエンド活動の振り返りをしてみましたが、今年もやっていきます。 qiita.com engineer.crowdworks.jp <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20231124/20231124145608.png" alt="&#x30A2;&#x30A4;&#x30AD;&#x30E3;&#x30C3;&#x30C1;&#xFF1A;&#x30A2;&#x30A4;&#x30AD;&#x30E3;&#x30C3;&#x30C1;&#xFF1A;crowdworks.jp &#x306E;&#x30D5;&#x30ED;&#x30F3;&#x30C8;&#x30A8;&#x30F3;&#x30C9;&#x6D3B;&#x52D5;&#x3092;&#x632F;&#x308A;&#x8FD4;&#x308B;2023" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この記事は<a href="https://qiita.com/advent-calendar/2023/crowdworks">クラウドワークス Advent Calendar 2023</a> シリーズ 1の1日目の記事です。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ソーシングサービス「<a href="https://crowdworks.jp/">クラウドワークス</a>」(以下 crowdworks.jp )にてエンジニアをしております、フロントエンドの可能性をしつこく信じ続ける<a href="https://twitter.com/okuto_oyama">@okuto_oyama</a>です。</p> <p>一昨年・去年と引き続き、今年も<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%C9%A5%D9%A5%F3%A5%C8%A5%AB%A5%EC%A5%F3%A5%C0%A1%BC">アドベントカレンダー</a>初日の盛り上げ手としてやっていきます。よろしくお願いします。</p> <h2 id="フロントエンド活動の振り返りをしてみよう">フロントエンド活動の振り返りをしてみよう</h2> <p>一昨年・去年もフロントエンド活動の振り返りをしてみましたが、今年もやっていきます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fyamanoku%2Fitems%2F29a74ebf3d74b3017581" title="crowdworks.jp のフロントエンド活動を振り返る 2021 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/yamanoku/items/29a74ebf3d74b3017581">qiita.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2Fcrowdworks_frontend_2022" title="crowdworks.jp のフロントエンド活動を振り返る 2022 - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/crowdworks_frontend_2022">engineer.crowdworks.jp</a></cite></p> <h2 id="全体総括編">全体総括編</h2> <p>まずは crowdworks.jp の全体のフロントエンド活動を総括していきます。</p> <h3 id="erbのVuejs化活動が拡大">erbのVue.js化活動が拡大</h3> <p>昨年から続けているフロントエンドとバックエンドの分離作業についてお話しします。<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>のテンプレートエンジンであるerbを用いた表示部分をVue.jsへと移行し、同時にバックエンドの<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>化も進めています。このVue.js化の作業は1チームに限定せず、多くのチームが参加し、活動が広がっています。</p> <p>Vue.jsへの移行に伴うスタイルの移植作業もスムーズに行えるよう、わかりやすい<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AC%A5%A4%A5%C9%A5%E9%A5%A4%A5%F3">ガイドライン</a>を策定しました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20231124/20231124144709.png" alt="Vue&#x5316;&#x306B;&#x304A;&#x3051;&#x308B;CSS&#x5BFE;&#x5FDC;&#x30AC;&#x30A4;&#x30C9;&#x30E9;&#x30A4;&#x30F3;&#x306E;&#x30B9;&#x30AF;&#x30EA;&#x30FC;&#x30F3;&#x30B7;&#x30E7;&#x30C3;&#x30C8;&#x3002;&#x3069;&#x3046;&#x3044;&#x3046;&#x76EE;&#x7684;&#x3067;&#x5236;&#x5B9A;&#x3055;&#x308C;&#x305F;&#x306E;&#x304B;&#x3001;&#x3044;&#x304F;&#x3064;&#x304B;&#x306E;&#x30C1;&#x30A7;&#x30C3;&#x30AF;&#x89B3;&#x70B9;&#x306B;&#x3064;&#x3044;&#x3066;&#x307E;&#x3068;&#x3081;&#x3066;&#x3044;&#x308B;&#x3002;" width="1200" height="960" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>今後はVue<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の粒度やComposablesを用いた<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>の設計に関するドキュメントを作成する予定です。</p> <p>今年Vue.js化された画面は以下の通りです。</p> <ul> <li>クライアントプロフィール画面</li> <li>メッセージボックス画面</li> <li>通知一覧画面</li> <li>ユーザーブロック一覧画面</li> <li>ワーカー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DD%A1%BC%A5%C8%A5%D5%A5%A9%A5%EA%A5%AA">ポートフォリオ</a>詳細画面</li> <li>ユーザーサポート向け管理画面</li> </ul> <p>現在、「<a href="https://crowdworks.jp/public/jobs">仕事を探す</a>」画面のVue.js化を進めています。この作業はまだ途中段階ですが、デスクトップビューではerbからVue<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>への移行が進んでいます(ブラウザの検証ツールで確認してみてください!)。</p> <p>モバイルビューに関しても、引き続きVue<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を作成して対応していきます。</p> <h3 id="フロントエンドディレクトリの再編">フロントエンド<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リの再編</h3> <p>昨年、私たちはモダンなフロントエンドビルドパイプラインをWebpackerからwebpackとSimpackerに変更しました。この変更を機に、フロントエンドとバックエンドの役割を明確に分けるため、従来の<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成を見直し、新たなフロントエンド専用の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成へ移行しました。</p> <p>新しい構成は大まかに以下のようになっています。</p> <pre class="code" data-lang="" data-unlink>- app/assets - 従来通りSprockets管轄のリソース - typescript - TypeScript関連全般 - vue-app - Vue.jsによるフロントエンドアプリ - src (旧app/javascript) - storybook (旧frontend-tools/storybook-norman) - webpack - jest - resources - 環境非依存の共有リソース - openapi - OpenAPI関連</pre> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成の決定には以下の記事を参考にしました。</p> <ul> <li>Project base or Language base の対比: <a href="https://f110.jp/posts/how-to-organize-mono-repository/">モノレポの構成</a></li> <li><a href="https://times.hrbrain.co.jp/entry/monorepo">30近いリポジトリを一つのリポジトリにまとめました - HRBrain Blog</a></li> <li><a href="https://engineering.mercari.com/blog/entry/20210817-8f561697cc/">メルカリShops での monorepo 開発体験記 | メルカリエンジニアリング</a></li> <li><a href="https://note.com/y_matsuwitter/n/nf878a2aa229a">monorepoをなぜ採用したか、及び大まかな構成: 三井物産デジタルアセットマネジメントでの事例|Matsumoto Yuki</a></li> <li><a href="https://blog.giftee.dev/2021-07-30-monorepo-setup-for-gift-wallet-renewal/">システムリニューアルに伴う monorepo 構成を考える | giftee engineer blog</a></li> </ul> <h3 id="コンポーネントライブラリの開発"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリの開発</h3> <p>crowdworks.jp では、デザインシステム「norman」の開発を継続しています。今年は、「<a href="https://www.amazon.co.jp/dp/4862464122">Design Systems ― デジタルプロダクトのためのデザインシステム実践ガイド</a>」の監修者である佐藤伸哉さんに参加していただき、デザインシステムの構築と改善に関するアド<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%B9">バイス</a>を受けていました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnote.com%2Fyucca_wi%2Fn%2Fnf4c0277ea25b" title="デザインシステムにおける意思決定の質を上げる方法|YUCCA" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://note.com/yucca_wi/n/nf4c0277ea25b">note.com</a></cite></p> <p>現在、normanではデザイン<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンを用いて再設計した<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリを開発中で、画面に直接適用できるようにしています。開発中の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>群は以下の通りです。</p> <ul> <li>ButtonBackToTop</li> <li>Button</li> <li>Checkbox</li> <li>CrowdWorksLogo</li> <li>Heading</li> <li>Icon</li> <li>Input</li> <li>SocialLogo</li> <li>Tabs</li> </ul> <p>画面内共通パーツとしては画面下部のフッター<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>(Footer)もnorman製としてリリースされており、現在 crowdworks.jp 画面内で使用されるようになっております。ヘッダー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の作成・適応についても現在取り組んでいる最中です。</p> <p>レイアウトにはGrid Systemを採用したGrid Layout<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を作成しており、画面のレイアウト構築に利用しています。Grid Layoutに関しては、後述するVue Fes Japanでも発表しました。</p> <p><iframe id="talk_frame_1097358" class="speakerdeck-iframe" src="//speakerdeck.com/player/9da16dc65b184a7b9a06e3aca4c0e4f7" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/t0yohei/vue-woshi-tute-grid-system-woshi-zhuang-sitahua">speakerdeck.com</a></cite></p> <p>また、細かなリアクティブな動作には<a href="https://vueuse.org/">VueUse</a>を使用し、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>内に組み込んでいます。</p> <h3 id="CSS-Modulesの有効化"><a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> Modulesの有効化</h3> <p>これまでのVue.js開発では、スタイルが外部に漏れ出ないようにScoped <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>を用いたスタイル開発を行ってきました。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier">&lt;</span><span class="synStatement">style</span><span class="synIdentifier"> scoped&gt;</span> <span class="synIdentifier">.cwv-hoge-component-root</span> <span class="synIdentifier">{</span> <span class="synType">font-family</span>: Meiryo<span class="synSpecial">,</span> <span class="synConstant">'Hiragino Kaku Gothic Pro'</span><span class="synSpecial">,</span> <span class="synConstant">'MS PGothic'</span><span class="synSpecial">,</span> <span class="synConstant">sans-serif</span>; <span class="synType">font-size</span>: <span class="synConstant">12px</span>; <span class="synType">line-height</span>: <span class="synConstant">1.6667</span>; <span class="synType">color</span>: <span class="synConstant">#333</span>; <span class="synIdentifier">}</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">style</span><span class="synIdentifier">&gt;</span> </pre> <p>この手法により、<code>[class*="hoge-"] a</code> といったような形で指定された古いグローバルな<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>との詳細度をうまく制御できるようになっていました。しかし、この方法ではクラス名の重複を避けるための慎重な<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>設計が必要でした。</p> <p>この問題を解決するため、Vue.jsで<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> Modulesを使用できるようにしました。これにより、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>がより容易になり、開発効率が向上しました。</p> <h3 id="静的ランディングページLPのフロントエンド機構にAstroを導入した">静的ランディングページ(LP)のフロントエンド機構にAstroを導入した</h3> <p>これまで、静的なLPは単一のindex.htmlや<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>ファイルで管理していました。しかし、crowdworks.jp で使用しているスナップショットやビジュアル<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%B0%A5%EC%A5%C3%A5%B7%A5%E7%A5%F3">リグレッション</a>テスト(VRT)などの機構をLPにも活用するため、ページをVue<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>化し、SSG(Static Site Generation)用にAstroを導入しました。</p> <h3 id="Nodejs-v16-EOLサポート終了対応">Node.js v16 EOL(サポート終了)対応</h3> <p>crowdworks.jp で使用していたNode.jsのバージョンはv16系でしたが、今年の9月11日にサポートが終了になりました。これを受けて、より次のバージョンのv18.17.1へのアップデートを行いました。</p> <h3 id="Storybook-v7へのバージョンアップ">Storybook v7へのバージョンアップ</h3> <p>crowdworks.jp のフロントエンド開発、特にUI開発において重要な役割を果たしているのがStorybookです。</p> <p>UIの表示パターンの網羅、MSWを用いたモック開発、<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%AF%A5%BF%A5%EA%A5%F3%A5%B0">リファクタリング</a>にて結果の同一を確認するためのVRT(ビジュアル<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%B0%A5%EC%A5%C3%A5%B7%A5%E7%A5%F3">リグレッション</a>テスト)、<a href="https://engineer.crowdworks.jp/entry/crowdworks_frontend_2023_02">スナップショット結果を活用したMarkuplintでのチェック</a>や<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B7%A5%D3%A5%EA%A5%C6%A5%A3">アクセシビリティ</a>チェックなど、多岐にわたる用途で活躍しています。</p> <p>今年、Storybook v7が発表され、crowdworks.jp でもその恩恵を受けられるようアップデートしました。このアップデートにより、Babelの設定が簡素化され、立ち上げ速度の向上や設定における型情報の扱いも容易になりました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fstorybook.js.org%2Fblog%2Fstorybook-7-0%2F" title="Storybook 7.0" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://storybook.js.org/blog/storybook-7-0/">storybook.js.org</a></cite></p> <p>ただし、スナップショットテストの設定が複雑化しているため、現在は別<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リでの運用を行っています。Storybookのファイル数も増加しており、今後は<a href="https://vitest.dev/">Vitest</a>を用いた並列スナップショットテストへの切り替えを検討しています。</p> <h3 id="フロントエンドの技術的負債-みんなで学ぶ-Lunch-LT発表">フロントエンドの技術的負債 みんなで学ぶ Lunch LT発表</h3> <p>ファインディ株式会社主催のフロントエンドの技術的負債に関するLT会で、crowdworks.jp の事例を発表しました。</p> <ul> <li><a href="https://findy.connpass.com/event/281811/">イベントページ</a></li> <li><a href="https://togetter.com/li/2148644">Togetterまとめ</a></li> </ul> <p>crowdworks.jp のフロントエンド開発の歴史を振り返り、私が所属するチーム「ジャンヌ」での技術的負債の継続的な解消について紹介しました。また、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B5%A5%A4%A5%DC%A5%A6%A5%BA">サイボウズ</a>株式会社、Chatwork株式会社、株式会社スリーシェイク、ファインディ株式会社の事例も学ぶことができ、非常に有意義でした。</p> <p>発表資料は公開していますので、ぜひご覧ください。</p> <iframe src="https://docs.google.com/presentation/d/e/2PACX-1vSeyDAwaa3AfYhhQDavajOc1ijSaJLO49_hFj_kvx2KetGvb6ozzlQ3VrQFjxaVCRr44XbId7lckT1k/embed?start=false&loop=false&delayms=3000" frameborder="0" width="960" height="569" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe> <h3 id="Vue-Fes-Japan-2023スポンサー登壇">Vue Fes Japan 2023スポンサー&登壇</h3> <p>日本最大級のVue.jsカンファレンス「<a href="https://vuefes.jp/2023/">Vue Fes Japan 2023</a>」が今年オフラインで開催されました。今年は株式会社<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスが同時通訳スポンサーとして協賛し、社員3名が登壇しました。ボランティアスタッフと一般参加者を含めて合計6名がイベントに参加しました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20231107/20231107155248.png" alt="&#x30AF;&#x30EA;&#x30A8;&#x30A4;&#x30C6;&#x30A3;&#x30D6;&#x30A6;&#x30A9;&#x30FC;&#x30EB;&#x306B;&#x30AF;&#x30E9;&#x30A6;&#x30C9;&#x30EF;&#x30FC;&#x30AF;&#x30B9;&#x306E;&#x30ED;&#x30B4;&#x304C;&#x66F8;&#x304B;&#x308C;&#x3066;&#x304A;&#x308A;&#x3001;&#x5F53;&#x65E5;&#x53C2;&#x52A0;&#x3057;&#x305F;&#x793E;&#x54E1;&#x3067;&#x305D;&#x306E;&#x30DE;&#x30FC;&#x30AF;&#x3092;&#x6307;&#x3055;&#x3057;&#x306A;&#x304C;&#x3089;&#x8A18;&#x5FF5;&#x64AE;&#x5F71;&#x3057;&#x3066;&#x3044;&#x308B;&#x69D8;&#x5B50;" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>以前、<a href="https://engineer.crowdworks.jp/entry/vuefes2019sponsor">2019年のカンファレンスにてスポンサー協賛していた</a>ことがありましたが、台風のため中止となりました。その後のコロナ禍という長い期間を経て、4年ぶりに再びオフラインカンファレンスにてスポンサーとして参加できたのは大変感慨深い経験でした。</p> <p>当日の参加レポートは以下の記事でまとめていますので、ご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2Freport-vue-fes-japan-2023" title="Vue Fes Japan 2023 参加レポート - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/report-vue-fes-japan-2023">engineer.crowdworks.jp</a></cite></p> <h3 id="その他フロントエンド改善ピックアップ">その他フロントエンド改善ピックアップ</h3> <ul> <li>Vue<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の <code>&lt;style&gt;</code> 内順序整理のためにstylelint-orderの導入</li> <li>@vueuse/headから推奨された<a href="https://github.com/unjs/unhead/tree/main/packages/vue">@unhead/vue</a>に変更</li> <li>tsconfigの <code>strict: true</code> を有効にする</li> <li>未使用だったstylelint-scssを廃止</li> <li>HTMLのid属性用にUUIDを自動付与するVue<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%E9%A5%B0%A5%A4%A5%F3">プラグイン</a>作成 <ul> <li>React.jsの <a href="https://react.dev/reference/react/useId"><code>useId</code></a> のような挙動</li> </ul> </li> </ul> <h2 id="個人活動編">個人活動編</h2> <p>一昨年、昨年と引き続きの自分語りですが、<a href="https://twitter.com/okuto_oyama">@okuto_oyama</a>(<a href="https://twitter.com/yamanoku">@yamanoku</a>)の2023年のフロントエンド活動についても紹介させてください。</p> <h3 id="OSSへのコントリビュート"><a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>へのコントリビュート</h3> <p>今年は大きな<a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>2つにコントリビュートができた年でもありました。</p> <p>1つが社内で大活躍中のStorybookへのコントリビュートです。スクリーンリーダー、キーボードユーザーへのアクセシブルな対応として本文へリンク「スキップリンク」がStorybook上でも搭載されているのですが、ロゴが縦長でサイドバーが広がった状態だとスキップリンクが常時見えた状態になる問題があったのですがそれを解消しました。こちらはStorybook v7に搭載された修正対応です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fstorybookjs%2Fstorybook%2Fpull%2F21021" title="fix: skip to canvas link style by yamanoku · Pull Request #21021 · storybookjs/storybook" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/storybookjs/storybook/pull/21021">github.com</a></cite></p> <p>Storybookの公式X(旧<a class="keyword" href="https://d.hatena.ne.jp/keyword/Twitter">Twitter</a>)にてコントリビューターとして紹介してもらったのが大変うれしかったのを記憶しております。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">Shoutout to <a href="https://twitter.com/okuto_oyama?ref_src=twsrc%5Etfw">@okuto_oyama</a> for the spot-on contribution🔍. Thanks to him, we merged <a href="https://t.co/M6dZSHcEvG">https://t.co/M6dZSHcEvG</a> improving our UI.<br><br>Great job at getting your first contribution in💪. Looking forward to your next one. <a href="https://t.co/uLXquLIrI8">pic.twitter.com/uLXquLIrI8</a></p>&mdash; Storybook (@storybookjs) <a href="https://twitter.com/storybookjs/status/1631006416986165254?ref_src=twsrc%5Etfw">2023年3月1日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>もう1つは社内導入をしてみて個人でも活用しているAstroへのコントリビュートです。静的なHTMLを出力する際にインラインで <code>&lt;style&gt;</code> が記述されるのですが、現在既に非推奨となった <code>type="text/css"</code> が付与されていました。個人でMarkuplintを活用してHTMLをチェックしている身としては不要な記述だと感じていたので修正PRを作成しました。こちらはv3.0.13にて反映されました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fwithastro%2Fastro%2Fpull%2F8480" title="Removed `&lt;style&gt;` with `type=&quot;text/css&quot;` from inline output at build time by yamanoku · Pull Request #8480 · withastro/astro" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/withastro/astro/pull/8480">github.com</a></cite></p> <p>そのほか、Vue.js日本語ドキュメントへの翻訳対応や、2021年にIssueとして起票した提案が2年越しのメジャーアップデートにて修正対応してもらいました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fvuejs-translations%2Fdocs-ja%2Fpull%2F1363" title="3.3: defineSlots &amp; slots option by yamanoku · Pull Request #1363 · vuejs-translations/docs-ja" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/vuejs-translations/docs-ja/pull/1363">github.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fnygardk%2Freact-share%2Fissues%2F397" title="ShareButton&#39;s `aria-label` is interfering with what I really want to say. · Issue #397 · nygardk/react-share" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/nygardk/react-share/issues/397">github.com</a></cite></p> <h3 id="社外へのLT発表">社外へのLT発表</h3> <p>社外勉強会ではSaitama.jsにて「<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>開発のこれまでとこれから」と「WAI-<a class="keyword" href="https://d.hatena.ne.jp/keyword/ARIA">ARIA</a>のIDL属性について」をそれぞれ発表してきました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2Fe%2F2PACX-1vQSVfknQSQjLyX64N2e-UqJAY14DNUc6AE4glPiIRi0bEPCidFajb9yCLZ4s2gpFyV5piKL3yiWsi8y%2Fpub%3Fslide%3Did.p" title="JavaScript 開発のこれまでとこれから" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://docs.google.com/presentation/d/e/2PACX-1vQSVfknQSQjLyX64N2e-UqJAY14DNUc6AE4glPiIRi0bEPCidFajb9yCLZ4s2gpFyV5piKL3yiWsi8y/pub?slide=id.p">docs.google.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fscrapbox.io%2Fyamanoku%2FWAI-ARIA_%25E3%2581%25AE_IDL_%25E5%25B1%259E%25E6%2580%25A7%25E3%2581%25AB%25E3%2581%25A4%25E3%2581%2584%25E3%2581%25A6" title="WAI-ARIA の IDL 属性について - yamaScrapbox" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://scrapbox.io/yamanoku/WAI-ARIA_%E3%81%AE_IDL_%E5%B1%9E%E6%80%A7%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">scrapbox.io</a></cite></p> <p><a href="https://jsconf.jp/2023/">JSConfJP 2023</a>では現在新たに策定されているWeb <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>でもあるNavigation <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>がSPAにおいて<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B7%A5%D3%A5%EA%A5%C6%A5%A3">アクセシビリティ</a>観点でどう改善されそうか、について発表してきました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2Fe%2F2PACX-1vSosGMESLA5IiR4NPz3i2u8XF_wkHsqP80pHA1a4q-Gmk9CIFkUobNc5pMvJj6Tth0PEGmoExmalOQj%2Fpub%3Fslide%3Did.p1" title="画面遷移のアクセシビリティ課題を解決しうる Navigation API への期待" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://docs.google.com/presentation/d/e/2PACX-1vSosGMESLA5IiR4NPz3i2u8XF_wkHsqP80pHA1a4q-Gmk9CIFkUobNc5pMvJj6Tth0PEGmoExmalOQj/pub?slide=id.p1">docs.google.com</a></cite></p> <p>12/1に開催されるMeguro.<a class="keyword" href="https://d.hatena.ne.jp/keyword/css">css</a>では2020年〜2023年までの<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>の進化についてを発表してくる予定です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmegurocss.connpass.com%2Fevent%2F300400%2F" title="Meguro.css #9 @ oRo (2023/12/01 19:00〜)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://megurocss.connpass.com/event/300400/">megurocss.connpass.com</a></cite></p> <h2 id="おわりに">おわりに</h2> <p>今年は、フロントエンドの技術的負債の解消に多くのチームが取り組み、Storybookのバージョンアップや作業環境の更新など、様々な改善を行ってきました。新卒エンジニアもフロントエンド技術的負債解消にモブプログラミング等を通じて参加していただいており、頼もしい限りです!</p> <p>しかし、技術的負債を解消したと思っても、新たな負債を生むこともあります。例えば、「モダンフロントエンド」の技術を採用しつつも、Vue<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>内で<a class="keyword" href="https://d.hatena.ne.jp/keyword/jQuery">jQuery</a>を使っているなど、設計上の違いが残っている場合もあります。</p> <p>引き続き技術的負債と向き合いながら、誰にとっても迷いのないフロントエンド開発ができるよう、<a class="keyword" href="https://d.hatena.ne.jp/keyword/ADR">ADR</a>や開発ドキュメントを残すことやフロントエンドに不慣れな人への開発サポートを行うことも計画しています。</p> <p>crowdworks.jp では、このようなフロントエンドの課題に取り組みたい方を募集しています。我こそは!と思ったそこのあなたを私たちは待っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fherp.careers%2Fv1%2Fcrowdworks%2FAaOS_he9juwD" title="crowdworks.jp Webエンジニア/プロダクト開発部 - 株式会社クラウドワークス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://herp.careers/v1/crowdworks/AaOS_he9juwD">herp.careers</a></cite></p> <hr /> <p>ここまでご覧いただきありがとうございました。</p> <p>今年の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%C9%A5%D9%A5%F3%A5%C8%A5%AB%A5%EC%A5%F3%A5%C0%A1%BC">アドベントカレンダー</a>は参加者も増え、シーズン2まで拡大しました。今後も25日まで様々な内容をお届けしますので、お楽しみに!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2023%2Fcrowdworks" title="クラウドワークスのカレンダー | Advent Calendar 2023 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2023/crowdworks">qiita.com</a></cite></p> cardboarder MemcachedからRedisに変更しました hatenablog://entry/6801883189058431308 2023-11-20T12:30:00+09:00 2023-11-20T12:30:00+09:00 MemcachedからRedisに変更しました <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bugfire/20231113/20231113144612.png" alt="&#x30A2;&#x30A4;&#x30AD;&#x30E3;&#x30C3;&#x30C1;&#xFF1A;Memcached&#x304B;&#x3089;Redis&#x306B;&#x5909;&#x66F4;&#x3057;&#x307E;&#x3057;&#x305F;" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <p>こんにちは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークス (<a href="https://crowdworks.jp/">https://crowdworks.jp/</a>) でエンジニアをしているBugfireです。今回は所属しているジャンヌチームと、協力しているSREチームにおける活動をお伝えします。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスでは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>上で<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>アプリケーションを運用しており、以前は<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>とRedisの両方をKVSとして利用していました。最近、より効率的な運用を目指してRedisに統合しました。</p> <h1 id="MemcachedとRedisについて"><a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>とRedisについて</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>とRedisはどちらもキーバリューストア(KVS)を提供するデータベースシステムです。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>はシンプルで高速な一方、Redisは豊富な機能を備えています。詳細は以下の公式ページからご確認ください。</p> <ul> <li><a href="https://memcached.org/">Memcached</a></li> <li><a href="https://redis.io/">Redis</a></li> </ul> <h2 id="移行の背景">移行の背景</h2> <p>当初は <a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a> のみを利用していましたが、後にRedisを追加しました。セッション管理とキャッシュに <a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a> を、その他のKVS要件には<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>とRedisを併用していました。しかし、インフラとコードの管理を単<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BD%E3%B2%BD">純化</a>するために、Redisに一本化する決定をしました。</p> <h2 id="移行プロセス">移行プロセス</h2> <h3 id="セッションストア">セッションストア</h3> <p>最初のステップとして、セッションストアを<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>からRedisに切り替えました。具体的には、</p> <p><code>ActiveSupport::Cache::MemCacheStore</code> から <code>ActiveSupport::Cache::RedisCacheStore</code> への切り替えを行いました。この変更は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%D5%A5%A3%A5%C3%A5%AF">トラフィック</a>の少ない時間帯に実施しました。</p> <p>セッション用にはEviction<a href="#f-3010600a" id="fn-3010600a" name="fn-3010600a" title="EvictionとはMemcached, RedisではTTL(Time-To-Live: データの保持期間)と独立してメモリ不足により一定のルールでデータの削除を行うことがあります。対策としては空きメモリ容量を見積もりと監視により十分に保つこと。">*1</a>されないことを期待するRedis<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を利用しています。</p> <h3 id="キャッシュストア">キャッシュストア</h3> <p>次に、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>のキャッシュメ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AB%A5%CB">カニ</a>ズムを<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>からRedisに切り替えました。これは単純に設定ファイル内の</p> <p><code>config.cache_store</code> の値を <code>:mem_cache_store</code> から <code>:redis_cache_store</code> に変更することで実現しました。</p> <p>こちらはセッションストアと違い、Evictionを許容するRedis<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を利用しています。</p> <h3 id="DalliとRedis-gemのケース">DalliとRedis gemのケース</h3> <p>直接KVSとして<a href="https://github.com/petergoldstein/dalli">Dalli</a>やRedisを使用していて揮発しても構わないものは、キャッシュストアを利用する <code>Rails.cache</code> に置き換えました。これにより、多くの直接的なKVS利用を<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>のキャッシュ機能でカバーしました。</p> <h3 id="揮発が許されないデータ">揮発が許されないデータ</h3> <p>揮発が許されないロックデータ等に関しては、Evictionを許容しない<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>に対してRedis gemを直接利用して実装するように変更しました。</p> <h3 id="CachedCounter">CachedCounter</h3> <p>当社が開発した<a href="https://github.com/crowdworks/cached_counter">CachedCounter</a>の利用を、Redis gemを直接使用する形に置き換えました。</p> <h3 id="データ移行を伴うケース">データ移行を伴うケース</h3> <p>一部のデータは保持したまま移行するため、<a class="keyword" href="https://d.hatena.ne.jp/keyword/TTL">TTL</a>に合わせた30日の移行期間をとりました</p> <ul> <li>30日間 <ul> <li>読み込み時に旧データを読み込む</li> <li>書き込み時に新旧の両方にデータを書き込む</li> </ul> </li> <li>30日後 <ul> <li>新データを読み書きする</li> </ul> </li> </ul> <p>と二段階に分けて更新しました。</p> <h3 id="滅多に読み書きしないケース">滅多に読み書きしないケース</h3> <p>3ヶ月毎に実行するバッチで利用しているケースでは、念の為次回の実行タイミング直前に移行して、万が一のための<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%AB%A5%D0%A5%EA">リカバリ</a>ープランを準備しました。</p> <h2 id="まとめ">まとめ</h2> <p>片手間でありつつも、半年間をかけたこの移行作業は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>へのアクセス数が減少するのを見ると共に、大きな達成感を感じました。</p> <p>今回の移行とは独立した話ですがRedisのSCANコマンドをもちいて、原因不明だった<a class="keyword" href="https://d.hatena.ne.jp/keyword/TTL">TTL</a>未定義のKeyを探し出して削除できたのも成果でした。</p> <h1 id="Were-hiring">We're hiring!</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスでは、様々なポジションのエンジニアを募集しております。 ご興味のある方は、ぜひご連絡ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcrowdworks.co.jp%2Fcareers%2F" title="採用情報 | 株式会社クラウドワークス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://crowdworks.co.jp/careers/">crowdworks.co.jp</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-3010600a" id="f-3010600a" name="f-3010600a" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">Evictionとは<a class="keyword" href="https://d.hatena.ne.jp/keyword/Memcached">Memcached</a>, Redisでは<a class="keyword" href="https://d.hatena.ne.jp/keyword/TTL">TTL</a>(Time-To-Live: データの保持期間)と独立してメモリ不足により一定のルールでデータの削除を行うことがあります。対策としては空きメモリ容量を見積もりと監視により十分に保つこと。</span></p> </div> bugfire StackBlitzを使ったトレーニング資料を作っている話 hatenablog://entry/6801883189054581981 2023-11-10T10:22:29+09:00 2023-11-10T10:22:29+09:00 はじめに こんにちは、クラウドログ事業推進部でエンジニアをしている越阪部です。 普段は、工数管理SaaSアプリ「CrowdLog」のフロントエンド開発推進に従事していて、開発基盤部分の検討と整備や、メンバーのスキルアップ施策などに取り組んでいます。 今回はその取り組みの中で、ハンズオン形式のトレーニング資料を用いてメンバーのスキルアップを図っているお話を紹介させていただきたいと思います。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiji_osakabe/20231030/20231030141237.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="はじめに">はじめに</h3> <p>こんにちは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログ事業推進部でエンジニアをしている越阪部です。<br/> 普段は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%A9%BF%F4">工数</a>管理<a class="keyword" href="https://d.hatena.ne.jp/keyword/SaaS">SaaS</a>アプリ「CrowdLog」のフロントエンド開発推進に従事していて、開発基盤部分の検討と整備や、メンバーの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A5%EB%A5%A2%A5%C3%A5%D7">スキルアップ</a>施策などに取り組んでいます。<br/> 今回はその取り組みの中で、ハンズオン形式のト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A1%BC%A5%CB%A5%F3">レーニン</a>グ資料を用いてメンバーの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A5%EB%A5%A2%A5%C3%A5%D7">スキルアップ</a>を図っているお話を紹介させていただきたいと思います。</p> <h3 id="目次">目次</h3> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#目次">目次</a></li> <li><a href="#背景と課題">背景と課題</a></li> <li><a href="#スキルアップクイズを始めました">スキルアップクイズを始めました</a></li> <li><a href="#クイズ例">クイズ例</a><ul> <li><a href="#導入部">導入部</a></li> <li><a href="#クイズ解答">クイズ解答</a></li> <li><a href="#答え合わせ">答え合わせ</a></li> </ul> </li> <li><a href="#取り組んでみて">取り組んでみて</a><ul> <li><a href="#いいところ">いいところ</a><ul> <li><a href="#より実務をイメージしやすい学習ができる">より実務をイメージしやすい学習ができる</a></li> <li><a href="#学習資料として取り回しがよい">学習資料として取り回しがよい</a></li> <li><a href="#いろいろなお題でクイズが作成できる">いろいろなお題でクイズが作成できる</a></li> </ul> </li> <li><a href="#運用する上で難しいところ">運用する上で難しいところ</a><ul> <li><a href="#作成に時間がかかる">作成に時間がかかる</a></li> </ul> </li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h3 id="背景と課題">背景と課題</h3> <p>直近の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログの開発では、メンバーの技術スタック移行に取り組んでいます。<br/> これは、それまでのレガシーな技術スタック(<a class="keyword" href="https://d.hatena.ne.jp/keyword/Perl">Perl</a>) での開発から、一新されたモダンなスタック (<a class="keyword" href="https://d.hatena.ne.jp/keyword/Golang">Golang</a> x React) での開発に移行していけるように、各メンバーに技術を学んで身につけてもらうことを指します。<br/> そこで、大きく分けてフロントエンド (React) とバックエンド (Go) の2種類に分けてオンボーディング資料が用意されていましたが、フロントエンド側には課題がありました。</p> <p>それは「資料が、読み物が中心となっていて、実務を具体的にイメージできるト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A1%BC%A5%CB%A5%F3">レーニン</a>グ資料がなかったこと」です。</p> <p>もともとオンボーディング資料を用意した当初は、基本的な知識を読み物で身につけてもらい、その後タスクをこなしながら実務的な部分を学んでもらえれば良いかと思っていましたが、オンボーディングを実施したメンバーから、前述した課題のようなフィードバックが多かったため、改善することにしました。</p> <h3 id="スキルアップクイズを始めました"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A5%EB%A5%A2%A5%C3%A5%D7">スキルアップ</a>クイズを始めました</h3> <p>前述の課題に対応するため、「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A5%EB%A5%A2%A5%C3%A5%D7">スキルアップ</a>クイズ」というものをはじめました。</p> <p>これは以下のような特徴を持つ、ハンズオン形式のト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A1%BC%A5%CB%A5%F3">レーニン</a>グ資料を提供する取り組みです:</p> <ul> <li>特定のお題に沿ったクイズが提供される <ul> <li>この取り組みでは、「クイズ」という特定のトピックに沿ったト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A1%BC%A5%CB%A5%F3">レーニン</a>グ資料を提供します。</li> <li>トピック例としては以下のようなものがあります: <ul> <li>カスタムhookの作り方</li> <li>各種<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>作成</li> <li>CrowdLogでの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>に沿ったコードの共<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C4%CC%B2%BD">通化</a></li> </ul> </li> </ul> </li> <li>実際にコードを書きながら取り組む <ul> <li>StackBlitz (<a href="https://stackblitz.com/">https://stackblitz.com/</a>) にクイズの実装部分を用意することで、開発者は実際に手を動かしながら知識の習得をすることができます。</li> <li>これによって、実務に近い実装経験をト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A1%BC%A5%CB%A5%F3">レーニン</a>グの段階で積めることが期待できます。</li> </ul> </li> <li>だれでも空き時間に取り組むことができる <ul> <li>この資料はNotionとStackBlitzで構成されており、手軽にいつでも着手できるようになっています。</li> <li>開発環境がStackBlitzで用意されているため、個々人が環境の用意で時間を取られることもありません。</li> </ul> </li> </ul> <p>開発者はざっくりと以下のような流れでこのクイズに取り組みます:</p> <p><figure class="figure-image figure-image-fotolife" title="クイズ実施の流れ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiji_osakabe/20231030/20231030121036.png" width="1200" height="786" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クイズ実施の流れ</figcaption></figure></p> <p>まず「導入」がNotion上に用意されてあり、ここでクイズの「目的」や「出題内容」について理解します。<br/> 次の「クイズ解答」では、出題内容に沿って、StackBlitz上でクイズに取り組みます。<br/> 最後に、Notion上の解説とStackBlitz上の模範回答コードを元に「答え合わせ」を行います</p> <p>この一連の流れを空き時間に行えるように、15 - 30分程度で終わるような分量でクイズは作成されています。</p> <h3 id="クイズ例">クイズ例</h3> <p>実際に実施されているクイズを抜粋してご紹介します。</p> <p>ご紹介するのは「カスタムhook向けの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>を作成する」というトピックのクイズになります。</p> <h4 id="導入部">導入部</h4> <p>まずはNotion上のクイズ導入部になります。</p> <p>(<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログではオフショア開発を取り入れており、メンバー全体がこの資料を利用できるように、英語で書かれています)</p> <p><figure class="figure-image figure-image-fotolife" title="クイズの導入部 (Notion)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiji_osakabe/20231030/20231030121337.png" width="1200" height="754" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クイズの導入部 (Notion)</figcaption></figure></p> <p>内容としては、まず「概要 (General)」 があり、このクイズがざっくりとなんのためのクイズかが記載されています。<br/> 次に「このクイズから得られること (What you will learn)」があり、このクイズを通して得られる知識について記載されています。今回ではReactのカスタムhookに対して<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>を書くのに必要な基本的なことが学べますと記載されています。<br/> 最後に「出題内容 (Quiz)」があります。ここでは出題されるクイズの内容とStackBlitz上でどういった実装をしてほしいかが記載されています。また、開発者が迷いすぎてしまわないように、「解答のヒント (Hint)」の項目も用意しています。このクイズでは、テストコマンドの走らせ方や、使用するテストライブラリのドキュメントリンクが添付されています。</p> <h4 id="クイズ解答">クイズ解答</h4> <p>次はStackBlitz上でのクイズ解答部分になります。</p> <p><figure class="figure-image figure-image-fotolife" title="クイズの解答部 (StackBlitz)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiji_osakabe/20231030/20231030121448.png" width="1200" height="674" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クイズの解答部 (StackBlitz)</figcaption></figure></p> <p>StackBlitzではモダンなエディターの基本的な機能をブラウザ上で利用できるようになっており、ファイル / フォルダの作成や<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%F3%A5%BF%A5%C3%A5%AF%A5%B9">シンタックス</a>ハイライト、静的解析や<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>のフォーマット、そして各種コードの実行環境などを利用することができます。</p> <p>また、今回のクイズのための使い方として、解答に最低限必要なファイルはあらかじめ用意するようにして、開発者がすぐに解答に取り掛かれるようにしています。<br/> 用意されるファイルとは、今回の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>の例でいくと、「テスト対象となるカスタムhookの実装」「テストファイルの雛形」「テストコードのサンプル」などになります。</p> <p>開発者はこのStackBlitz上でコードを書き、書いたものを実行して動作を確かめながらクイズを解いていくことになります。今回の例では<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>を作成することが目的となりますが、StackBlitzは実行可能なターミナルも提供しており、そちらからテストのためのコマンドを実行できるようになっています。</p> <h4 id="答え合わせ">答え合わせ</h4> <p>最後に答え合わせ部分の紹介です。</p> <p><figure class="figure-image figure-image-fotolife" title="クイズの答え合わせ (Notion)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiji_osakabe/20231030/20231030121627.png" width="1200" height="779" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クイズの答え合わせ (Notion)</figcaption></figure></p> <p>答え合わせの際は、まずNotion上の解説を読みます。</p> <p>今回はカスタムhookの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>作成の基本を知ることが目的だったため、使用しているライブラリについてや、理解してほしかった基本的なポイント(state更新やuseEffectの動作をsimulateする方法)について説明しています。</p> <p><figure class="figure-image figure-image-fotolife" title="クイズの模範回答 (StackBlitz)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiji_osakabe/20231030/20231030121705.png" width="1200" height="672" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クイズの模範回答 (StackBlitz)</figcaption></figure></p> <p>また、StackBlitzに用意されている、模範回答の実装も答え合わせに利用します。<br/> こちらがあることで、開発者は自分のコードとの差分を比較しつつ、具体的な実装を学ぶことができます。</p> <p>以上が「カスタムhook向けの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>を作成する」というお題のクイズの全容となります。<br/> 他のクイズも大枠はこれと同じ要素で構成されています。</p> <h3 id="取り組んでみて">取り組んでみて</h3> <p>この取り組みを半年ほど運用してみていて、クイズは10問ほど作成してきました。<br/> ここまで運用してみての所感を振り返ってみます。</p> <h4 id="いいところ">いいところ</h4> <h5 id="より実務をイメージしやすい学習ができる">より実務をイメージしやすい学習ができる</h5> <p>前述もしていますが、以前のト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A1%BC%A5%CB%A5%F3">レーニン</a>グ資料に比べて、手を動かしつつ学べるようになっている分、より実務をイメージしやすい学習できるようになっているかと思います。<br/> また、手を動かすことが一部となっていることで、よりとっつきやすい学習資料にもなっているかと思います。<br/> 実際にクイズに取り組んだメンバーからも、このクイズがあることで以前より効果的に学習できているというフィードバックをもらっています。</p> <h5 id="学習資料として取り回しがよい">学習資料として取り回しがよい</h5> <p>一度作ってしまえば、さまざまな場面で活用することができるよさがあります。</p> <p>例えば以下のような使い方ができます:</p> <ul> <li>自分が不安だと感じるトピックを選んで学習する</li> <li>コードレビューのコミュニケーションの際に、資料として使う</li> <li>タスクの空き時間を活用するために利用する</li> </ul> <h5 id="いろいろなお題でクイズが作成できる">いろいろなお題でクイズが作成できる</h5> <p>StackBlitzは、開発環境を用意する際に、各種「テンプレート」から土台を作ることができます。<br/> そのテンプレートのおかげで各種お題に沿ったクイズを作成することができます。</p> <p>例えば:</p> <ul> <li>Reactのテンプレート <ul> <li>素のReactでの開発環境を提供します。</li> <li>Reactの実装が絡んでくるクイズではこのテンプレートを利用しています。</li> </ul> </li> <li>Next.jsのテンプレート <ul> <li>Next.jsをベースにした開発環境を提供します。 (使ったことはないです)</li> </ul> </li> <li>Node.jsのテンプレート <ul> <li>Node.jsで<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>を実行するための環境を提供します。</li> <li>トピックがテストコードの場合はこちらのテンプレートを利用しています。</li> </ul> </li> </ul> <h4 id="運用する上で難しいところ">運用する上で難しいところ</h4> <h5 id="作成に時間がかかる">作成に時間がかかる</h5> <p>ここはどうしてもネックになってしまいます。</p> <p>クイズ作成の際は以下のような流れを踏みます:</p> <ul> <li>1- トピックの検討 <ul> <li>どういう目的のクイズにするか考えます</li> </ul> </li> <li>2- トピックに沿ったクイズ内容を考える <ul> <li>どういった出題内容で、クイズの目的を達成してもらうかを考えます</li> </ul> </li> <li>3- 各種資料の作成 <ul> <li>前項までに決めた内容で、Notionの解説や、StackBlitz上の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を用意したりします</li> </ul> </li> </ul> <p>この中で、特に頭と時間を使うのが2番目のクイズ内容を考える部分です。<br/> 出題内容が理解しやすくなるように、そして取り組んだことでちゃんと知識を得られるように、ということを気にしながらクイズ内容を検討します。<br/> また、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>にしても、すぐに理解しやすいように実プロダクトに似せたプログラムにしたりといった工夫をしたりします。</p> <p>こういったことをやっていると、1問作成するのにそこそこの時間がかかってしまいます。</p> <h1 id="まとめ">まとめ</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログで取り組んでいる「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A5%EB%A5%A2%A5%C3%A5%D7">スキルアップ</a>クイズ」の仕組みについて紹介させていただきました。<br/> この取り組みについてはこれからも継続的に取り組んでいき、内容の拡充や運用の仕組みの改善などを考えていきたいと思います。<br/> ここまで読んでいただきありがとうございました。</p> eiji_osakabe Vue Fes Japan 2023 参加レポート hatenablog://entry/6801883189056815027 2023-11-08T15:00:00+09:00 2023-11-08T15:00:01+09:00 皆様こんにちは。クラウドソーシングサービス「クラウドワークス」(以下crowdworks.jp)にてエンジニアをしております@okuto_oyamaです。今回は、10月28日に開催されたVue Fes Japan 2023の参加レポートをお届けします。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20231107/20231107154450.png" alt="&#x30A2;&#x30A4;&#x30AD;&#x30E3;&#x30C3;&#x30C1;&#xFF1A;Vue Fes Japan 2023 &#x53C2;&#x52A0;&#x30EC;&#x30DD;&#x30FC;&#x30C8;" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>皆様こんにちは。<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ソーシングサービス「<a href="http://crowdworks.jp/">クラウドワークス</a>」(以下crowdworks.jp)にてエンジニアをしております<a href="https://twitter.com/okuto_oyama">@okuto_oyama</a>です。今回は、10月28日に開催された<a href="https://vuefes.jp/2023/">Vue Fes Japan 2023</a>の参加レポートをお届けします。</p> <h2 id="久々のオフライン開催">久々のオフライン開催</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20231107/20231107155938.png" alt="Vue Fes Japan 2023 &#x4F1A;&#x5834;&#x306B;&#x8A2D;&#x7F6E;&#x3055;&#x308C;&#x3066;&#x3044;&#x305F;&#x30AF;&#x30EA;&#x30A8;&#x30A4;&#x30C6;&#x30A3;&#x30D6;&#x30A6;&#x30A9;&#x30FC;&#x30EB;&#x3002;&#x4E2D;&#x592E;&#x306B; Vue Fes Japan &#x306E;&#x30ED;&#x30B4;&#x304C;&#x66F8;&#x304B;&#x308C;&#x3066;&#x3042;&#x308A;&#x3001;&#x305D;&#x306E;&#x5468;&#x8FBA;&#x306B;&#x591A;&#x304F;&#x306E;&#x4F01;&#x696D;&#x30ED;&#x30B4;&#x3084;&#x500B;&#x4EBA;&#x306B;&#x3088;&#x308A;&#x66F8;&#x304D;&#x8FBC;&#x307E;&#x308C;&#x3066;&#x3044;&#x308B;&#x3002;" width="1200" height="904" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>2018年以来、台風や<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%B7%B7%BF%A5%B3%A5%ED%A5%CA%A5%A6%A5%A4%A5%EB%A5%B9">新型コロナウイルス</a>の影響でオフラインでの開催が叶わなかったVue Fes Japanが、今年ついに対面でのカンファレンスとして戻ってきました。昨年は完全なオンライン形式で開催されましたが、久々にオフラインのイベントに参加できたのは、感慨深いものがありました。</p> <p>フロントエンドの大規模カンファレンスへの参加が久しぶりだったので、多くの人々が集まる様子、各企業のスポンサーブースでの交流、ランチセッションなど、オフラインならではの体験ができたことはとても懐かしく感じました。</p> <h2 id="同時通訳スポンサーの協賛">同時通訳スポンサーの協賛</h2> <p>今回、株式会社<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスは「同時通訳スポンサー」として協賛しました。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">弊社 <a href="https://twitter.com/CrowdWorksjp?ref_src=twsrc%5Etfw">@CrowdWorksjp</a> も協賛しております!<br>今年の Vue Fes Japan 盛り上げていきましょう🙌 <a href="https://twitter.com/hashtag/vuefes?src=hash&amp;ref_src=twsrc%5Etfw">#vuefes</a><a href="https://t.co/swmHo0imWg">https://t.co/swmHo0imWg</a> <a href="https://t.co/fwk27a1GJB">pic.twitter.com/fwk27a1GJB</a></p>&mdash; オオヤマ オクト (@okuto_oyama) <a href="https://twitter.com/okuto_oyama/status/1678266941491019777?ref_src=twsrc%5Etfw">2023年7月10日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>Evan Youをはじめとする英語を話す登壇者の発表を、日本語と中国語にリアルタイムで通訳するサービスが提供されていました。オフラインでの登壇という形式で行われたリアルタイム発表は、英語が苦手な方々にも理解しやすいよう配慮されていたため、大変ありがたく感じました。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/vuefes?src=hash&amp;ref_src=twsrc%5Etfw">#vuefes</a> おつかれさまでした。同時通訳用に各席にイヤホンが用意されてて、原語でも翻訳でも好きな方を聴けるの良かったです <a href="https://t.co/WHByHEcE2G">pic.twitter.com/WHByHEcE2G</a></p>&mdash; miyaoka (@miyaoka) <a href="https://twitter.com/miyaoka/status/1718246509731532879?ref_src=twsrc%5Etfw">2023年10月28日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <h2 id="3名の社員が発表登壇">3名の社員が発表・登壇</h2> <p>以前<a href="https://engineer.crowdworks.jp/entry/give-a-talk-vue-fes-japan-online-2023">告知ブログ</a>でお伝えした通り、弊社のエンジニア、<a href="https://twitter.com/t0yohei">@t0yohei</a>、<a href="https://twitter.com/yamanoku">@yamanoku</a>、そして<a href="https://twitter.com/53able">@53able</a>がそれぞれ登壇し、発表を行いました。</p> <p><a href="https://twitter.com/t0yohei">@t0yohei</a>は「<strong>Vue.jsを使ってGrid Systemを実装した話</strong>」というテーマで発表しました。</p> <p><iframe id="talk_frame_1097358" class="speakerdeck-iframe" src="//speakerdeck.com/player/9da16dc65b184a7b9a06e3aca4c0e4f7" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/t0yohei/vue-woshi-tute-grid-system-woshi-zhuang-sitahua">speakerdeck.com</a></cite></p> <p>crowdworks.jpのデザインシステムにおける<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリではGrid Systemのアプローチを取り入れた<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を開発しており、その実装方法についてライブコーディングを交えて紹介しました。</p> <p><figure class="figure-image figure-image-fotolife" title="@t0yohei"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20231107/20231107155840.png" alt="t0yohei&#x306E;&#x767B;&#x58C7;&#x5199;&#x771F;" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>@t0yohei</figcaption></figure></p> <p><a href="https://twitter.com/yamanoku">@yamanoku</a>は「<strong>画面遷移から考えるNuxtアプリケーションをアクセシブルにする方法</strong>」について発表しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fyamanoku.net%2Fvuefes-japan-2023%2Fja%2F" title="画面遷移から考えるNuxtアプリケーションをアクセシブルにする方法" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://yamanoku.net/vuefes-japan-2023/ja/">yamanoku.net</a></cite></p> <p>クライアントサイドのルーティングで起こる画面遷移の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B7%A5%D3%A5%EA%A5%C6%A5%A3">アクセシビリティ</a>の問題点を、スクリーンリーダーを使用したデモを通じて指摘し、解決策の実装方法を紹介しました。</p> <p><figure class="figure-image figure-image-fotolife" title="@yamanoku"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20231107/20231107155732.png" alt="yamanoku&#x306E;&#x767B;&#x58C7;&#x5199;&#x771F;" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>@yamanoku</figcaption></figure></p> <p><a href="https://twitter.com/53able">@53able</a>は「<strong>SOLID原則に基づく<a class="keyword" href="https://d.hatena.ne.jp/keyword/SFC">SFC</a>実装</strong>」というテーマで登壇しました。</p> <p><a href="https://slides-one.vercel.app/">https://slides-one.vercel.app/</a></p> <p>SOLID原則の各項目をVue.jsの<a class="keyword" href="https://d.hatena.ne.jp/keyword/SFC">SFC</a>(Single File Components)でどう実現しているかについて解説しました。これは私たちのVue.js実装においても、原則に沿った手法を採用していることから得られる洞察でした。</p> <p><figure class="figure-image figure-image-fotolife" title="@53able"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20231107/20231107155539.png" alt="53able&#x306E;&#x767B;&#x58C7;&#x5199;&#x771F;" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>@53able</figcaption></figure></p> <p>パネルディスカッションでは、<a href="https://twitter.com/yamanoku">@yamanoku</a>が参加し、Vue.jsの導入がもたらした各社でのよかった点や、これからのエコシステムへの期待について話しました。</p> <p><figure class="figure-image figure-image-fotolife" title="左から yamanoku、miyaoka、ushironoko、kazupon、wattanx、takanorip"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20231107/20231107155417.png" alt="&#x30D1;&#x30CD;&#x30EB;&#x30C7;&#x30A3;&#x30B9;&#x30AB;&#x30C3;&#x30B7;&#x30E7;&#x30F3;&#x3067; @yamanoku&#x3001;@miyaoka&#x3001;@ushiro_noko&#x3001;@kazu_pon&#x3001;@wattanx&#x3001;@takanoripe &#x304C;&#x30C8;&#x30FC;&#x30AF;&#x3057;&#x3066;&#x3044;&#x308B;&#x69D8;&#x5B50;" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>左から @yamanoku、@miyaoka、@ushiro_noko、@kazu_pon、@wattanx、@takanoripe</figcaption></figure></p> <p>余談ではありますが、登壇者控え室でSebastien、Daniel、Anthonyといった著名な参加者たちと同席した時の緊張感も、個人的には際立った思い出でありました。</p> <h2 id="Vuejsコミュニティ発で広がってきた多様なセッション">Vue.jsコミュニティ発で広がってきた多様なセッション</h2> <p>Vue Fes Japan 2023では、Vue.js、Vite、Nuxt.jsなどVueコミュニティから生まれた<a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>に関する発表も目立ちましたが、Vue.jsに限定されない様々なセッションが行われたことが特に印象的でした。</p> <p>Vue.jsのLanguage ToolであるVolar.jsは現在、Astroのコアコミッターも参加してAstroでも使用されていること<a href="#f-457cc0f9" id="fn-457cc0f9" name="fn-457cc0f9" title="Vue Language Server から生まれた Volar.js と、それが秘める可能性 - Speaker Deck">*1</a>、Viteを基にした<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>のマルチスレッド処理に関する発表があったこと<a href="#f-fc72cb36" id="fn-fc72cb36" name="fn-fc72cb36" title="マルチスレッドフレンドリーなJavaScriptを求めて - Slidev">*2</a>、デスクトップアプリケーションでのVue.jsの使用例<a href="#f-e7850268" id="fn-e7850268" name="fn-e7850268" title="Vue3/Electronで自作したマークダウンエディタをVue3/Tauriにリプレイスした話 - Speaker Deck">*3</a>や、SvelteでのESLint<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%E9%A5%B0%A5%A4%A5%F3">プラグイン</a>での実装ア<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%C7%A5%A2">イデア</a>をVue.jsのESLint<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%E9%A5%B0%A5%A4%A5%F3">プラグイン</a>でも応用できないか考えられていること<a href="#f-e49cd791" id="fn-e49cd791" name="fn-e49cd791" title="eslint-plugin-vue の現状と今後">*4</a>などの発表がされていました。</p> <p>また、Viteは<a href="https://kit.svelte.dev/">SvelteKit</a>や<a href="https://remix.run/">Remix</a>でのサポートが広がっており、Viteの<a class="keyword" href="https://d.hatena.ne.jp/keyword/SSR">SSR</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%E9%A5%B0%A5%A4%A5%F3">プラグイン</a>プロジェクトである<a href="https://vite-plugin-ssr.com/vike">Vike</a>を通じてReact.jsほか各種<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/SSR">SSR</a>にも対応しています。</p> <p>Nuxt.jsにおいては、<a href="https://github.com/unjs">UnJS</a>という<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>ユーティリティライブラリを中心に構築されており、Vue.jsやNuxt.js特有の環境でのみ動作するわけではなくなっていることが示されました<a href="#f-cd02e8c5" id="fn-cd02e8c5" name="fn-cd02e8c5" title="Deep Dive to UnJS and Nuxt 3 - Speaker Deck">*5</a>。</p> <h2 id="Vue-Fes-Japan-2023を終えて"><strong>Vue Fes Japan 2023を終えて</strong></h2> <p>アフターパー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C6%A5%A3%A1%BC">ティー</a>の後、Evan Youによる一本締めでVue Fes Japan 2023は締めくくられました。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Evanの一本締めでアフターパー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C6%A5%A3%A1%BC">ティー</a>終了‼️<br>ありがとうございました❗️<a href="https://twitter.com/hashtag/vuefes?src=hash&amp;ref_src=twsrc%5Etfw">#vuefes</a> <a href="https://t.co/5LZvpD19yn">pic.twitter.com/5LZvpD19yn</a></p>&mdash; Vue Fes Japan 2023 (@vuefes) <a href="https://twitter.com/vuefes/status/1718215053038801142?ref_src=twsrc%5Etfw">2023年10月28日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>2018年に初めてVue Fes Japanに参加して以来、2022年のオンラインカンファレンスと、今年のオフラインカンファレンスにも参加しました。発表以外においても毎年参加者が楽しめるようなコンテンツがあり、登壇者としても参加者としても、クオリティの高いカンファレンスで充実した時間を過ごせました。</p> <p>そんなカンファレンスを今年も運営してくれたVue.js日本ユーザーグループとボランティアスタッフの皆さんに心から感謝を申し上げます。</p> <p><figure class="figure-image figure-image-fotolife" title="参加していた株式会社クラウドワークス社員でパチリ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20231107/20231107155248.png" alt="&#x30AF;&#x30EA;&#x30A8;&#x30A4;&#x30C6;&#x30A3;&#x30D6;&#x30A6;&#x30A9;&#x30FC;&#x30EB;&#x306B;&#x30AF;&#x30E9;&#x30A6;&#x30C9;&#x30EF;&#x30FC;&#x30AF;&#x30B9;&#x306E;&#x30ED;&#x30B4;&#x304C;&#x66F8;&#x304B;&#x308C;&#x3066;&#x304A;&#x308A;&#x3001;&#x5F53;&#x65E5;&#x53C2;&#x52A0;&#x3057;&#x305F;&#x793E;&#x54E1;&#x3067;&#x305D;&#x306E;&#x30DE;&#x30FC;&#x30AF;&#x3092;&#x6307;&#x3055;&#x3057;&#x306A;&#x304C;&#x3089;&#x8A18;&#x5FF5;&#x64AE;&#x5F71;&#x3057;&#x3066;&#x3044;&#x308B;&#x69D8;&#x5B50;" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>当日参加した株式会社<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークス社員</figcaption></figure></p> <p>今回、株式会社<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスではスポンサーと社員3名による登壇・発表でVue.jsコミュニティに貢献することができました。引き続きVue.jsとそのエコシステムを活用した開発とそこから得られた知見を通じて、コミュニティや<a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>への貢献を続けていきたいと思っています。</p> <hr /> <p>ここまでご覧頂きありがとうございました。来年のVue Fes Japanも楽しみにしています!</p> <div class="footnote"> <p class="footnote"><a href="#fn-457cc0f9" id="f-457cc0f9" name="f-457cc0f9" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://speakerdeck.com/mizdra/vue-language-server-karasheng-mareta-volar-dot-js-to-soregami-meruke-neng-xing">Vue Language Server から生まれた Volar.js と、それが秘める可能性 - Speaker Deck</a></span></p> <p class="footnote"><a href="#fn-fc72cb36" id="f-fc72cb36" name="f-fc72cb36" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://vue-fes-japan-2023-multithread-slide.sapphi.red/">マルチスレッドフレンドリーなJavaScriptを求めて - Slidev</a></span></p> <p class="footnote"><a href="#fn-e7850268" id="f-e7850268" name="f-e7850268" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://speakerdeck.com/yud0uhu/tauriniripureisusitahua">Vue3/Electronで自作したマークダウンエディタをVue3/Tauriにリプレイスした話 - Speaker Deck</a></span></p> <p class="footnote"><a href="#fn-e49cd791" id="f-e49cd791" name="f-e49cd791" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://ota-meshi.github.io/vue-fes-japan-2023-slide/">eslint-plugin-vue の現状と今後</a></span></p> <p class="footnote"><a href="#fn-cd02e8c5" id="f-cd02e8c5" name="f-cd02e8c5" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://speakerdeck.com/nozomuikuta/deep-dive-to-unjs-and-nuxt-3">Deep Dive to UnJS and Nuxt 3 - Speaker Deck</a></span></p> </div> cardboarder Vue.js で作る GridSystem hatenablog://entry/6801883189053784957 2023-10-28T11:44:03+09:00 2023-10-28T12:55:23+09:00 こんにちは、crowdworks.jp でエンジニアをしている @t0yohei です。最近はデザインシステム構築の一貫でコンポーネントライブラリの実装を行なっています。 今回は Vue.js を使って GridSystem を作るにはどうしたらいいか、どういうことを考える必要があるのかをお伝えしていこうと思います。 ※ このブログは、 Vue Fes Japan 2023 のセッション「Vueを使ってGrid Systemを実装した話 」の内容を補足するブログになっています。セッションをご覧になっていない方が読んでも問題ない形式になっておりますのでご安心ください。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231027/20231027142330.png" alt="Vue.js &#x3067;&#x4F5C;&#x308B; GridSystem OGP &#x753B;&#x50CF;" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは、crowdworks.jp でエンジニアをしている <a href="https://twitter.com/t0yohei">@t0yohei</a> です。最近はデザインシステム構築の一貫で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリの実装を行なっています。</p> <p>今回は Vue.js を使って GridSystem を作るにはどうしたらいいか、どういうことを考える必要があるのかをお伝えしていこうと思います。</p> <p>※ このブログは、 Vue Fes Japan 2023 のセッション「<a href="https://vuefes.jp/2023/sessions/t0yohei">Vueを使ってGrid Systemを実装した話</a> 」の内容を補足するブログになっています。セッションをご覧になっていない方が読んでも問題ない形式になっておりますのでご安心ください。</p> <h2 id="目次">目次</h2> <ul class="table-of-contents"> <li><a href="#目次">目次</a></li> <li><a href="#そもそも-GridSystem-って">そもそも GridSystem って?</a></li> <li><a href="#コラムGridSystem-はなぜ必要">【コラム】GridSystem はなぜ必要?</a></li> <li><a href="#コラムGridSystem-はいつ作ることになるか">【コラム】GridSystem はいつ作ることになるか</a></li> <li><a href="#決める必要があること">決める必要があること</a></li> <li><a href="#実装パターン">実装パターン</a></li> <li><a href="#実装方法">実装方法</a></li> <li><a href="#実装-Tips">実装 Tips</a><ul> <li><a href="#GridLayoutFrame-コンポーネント">GridLayoutFrame コンポーネント</a></li> <li><a href="#indexts-での-import-and-export">index.ts での import and export</a></li> <li><a href="#最終的な実装イメージ">最終的な実装イメージ</a></li> </ul> </li> <li><a href="#終わりに">終わりに</a></li> </ul> <h2 id="そもそも-GridSystem-って">そもそも GridSystem って?</h2> <p>GridSystem は 1981 年に刊行された、ヨゼフ・ミューラー゠ブ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%ED%A5%C3%A5%AF%A5%DE%A5%F3">ロックマン</a>の主著『Grid systems in graphic design』にて体系化された概念で、具体的には</p> <blockquote><p> グリッドシステムとは、画面上に架空の縦横線を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AC%A5%A4%A5%C9%A5%E9%A5%A4%A5%F3">ガイドライン</a>として引きブロックごとに文字や図版を配置することでデザインのシンプルな美しさを引き出す手法</p></blockquote> <p>とされています。</p> <p>参考: <a href="https://store.bookandsons.com/?pid=146801609">グリッドシステム グラフィックデザインのために</a></p> <p>元々がデザインの手法であるため、実装として扱う GridSystem はこの定義に実装としての制約を加える必要があります。</p> <p>実装として扱う GridSystem について明確な定義が見つからなかったため、今回は GridSystem 実装の走りと言える Bootstrap での定義を元に実装を進めていこうと思います。</p> <blockquote><p>グリッドシステムは、12カラムのシステムと5段階のレスポンシブ、Sassとmixin、いくつかの定義されたクラスですべての図形とサイズのレイアウトを作成可能です。</p></blockquote> <p>.</p> <blockquote><p>グリッドシステムは、一連のコンテナ、行、列を使用してコンテンツをレイアウトし、整列させます。</p></blockquote> <p>参考: <a href="https://getbootstrap.jp/docs/5.3/layout/grid/">グリッドシステム · Bootstrap v5.3</a></p> <p>大まかにまとめると Bootstrap 風な GridSystem には以下のような機能が求められると言えそうです。</p> <ul> <li>N 個(ex. 12 個)のカラムで<strong>画面を分割</strong>できる</li> <li>カラム数の指定で、<strong>縦・横のコンテンツレイアウトを制御</strong>できる</li> <li>M 段階(ex. 5 段階)の<strong>レスポンシブな画面</strong>にできる</li> </ul> <p><a href="#f-915f9352" name="fn-915f9352" title="あくまで Bootstrap 風な GridSystem を実装する際に求められる要件です。元の GridSystem の定義を別の解釈で実装に落とし込む場合は、別の機能要件になります。">*1</a></p> <p>今回は、この要件を実現する Bootstrap 風の GridSystem を作る方法について言及していきます。 また今後 GridSystem と言う際は、この Bootstrap 風の GridSystem のことを指すことにします。</p> <h2 id="コラムGridSystem-はなぜ必要">【コラム】GridSystem はなぜ必要?</h2> <p>そもそも GridSystem はなぜ必要なのでしょうか。GridSystem が必要とされる要因は以下のようなものが考えられます。</p> <ul> <li>格子状にデザインされた画面を効率よく作りたい</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> Grid や Flexbox を使えば実現できるけど、画面全体やサービス全体で統一感を持たせたい</li> </ul> <p>逆に以上の要望がない場合は、GridSystem を自ら実装したり GridSystem を使うためだけに UI ライブラリを導入するなどは必要ないかもしれません。</p> <h2 id="コラムGridSystem-はいつ作ることになるか">【コラム】GridSystem はいつ作ることになるか</h2> <p>UI ライブラリなどに既に実装されている GridSystem を使うことは数あれど、GridSystem を自ら作る機会はあまりないかもしれません。では一体いつ自身で作ることになるのでしょうか。 主だったタイミングとしては、</p> <ul> <li>Web サービスを作る上での開発方針で、UI <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリを自作しようとなった時</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a> などで UI <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ライブラリを作ろうとした時</li> <li>GridSystem を自分で作ってみたいなーと思った時</li> </ul> <p>などが思い浮かびます。 そういった機会の多寡は読み手の皆さんに任せるとして、この記事は実際に GridSystem を作ることになった方に、少しでも自分の知見を共有できればと思い書いてみました。</p> <h2 id="決める必要があること">決める必要があること</h2> <p>さて、若干話が逸れた気がしますが、具体的に GridSystem を作るために必要なことの話に入って行きます。</p> <p>GridSystem を作るには、まず基本的なこととして以下の内容を決める必要があります。</p> <ul> <li>対応する画面サイズは何パターンにするか <ul> <li>Pc(lg), <a class="keyword" href="https://d.hatena.ne.jp/keyword/Tablet">Tablet</a>(md), Sp(sm) など</li> </ul> </li> <li>それぞれの画面サイズは、何 px 〜 何 px までを割り当てるか <ul> <li>つまり画面サイズの境界値(breakpoint)を何 px にするか</li> </ul> </li> <li>一列の総カラム数は何カラムにするか <ul> <li>画面サイズごとに総カラム数を変えるか、統一するか</li> </ul> </li> </ul> <p>また、応用的な内容として以下のようなことも検討する必要があります</p> <ul> <li>カラム間に間隔(Gutter)を設けるか <ul> <li>設ける場合は何 px にするか</li> <li>縦・横でカラム間の間隔を別にするか、統一するか</li> </ul> </li> <li>GridSystem を適用するエリア(body)に最大幅を設けるか <ul> <li>また、エリア(body)外との Margin は何 px にするか</li> </ul> </li> <li>どういったオプションを用意するか <ul> <li>Order: カラムの順番を入れ替えるためのオプション</li> <li>Offset(Start): カラムの開始位置を指定できるようにするためのオプション</li> <li>etc...</li> </ul> </li> </ul> <p>決めることが多いですが、自分(達)が必要とする GridSystem はどういうものかを調査・議論しながら定義していきます。</p> <p>参考までに、crowdworks.jp の GridSystem において、実際に決めた内容は以下のようなものになります(まだまだ発展中のため、後から変更になる場合もあります)。</p> <p><figure class="figure-image figure-image-fotolife" title="GridSystem の要件まとめテーブル"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231027/20231027110816.png" alt="GridSystem &#x306E;&#x8981;&#x4EF6;&#x307E;&#x3068;&#x3081;&#x30C6;&#x30FC;&#x30D6;&#x30EB;" width="1064" height="373" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>GridSystem の要件まとめテーブル</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="GridSystem のイメージ図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t0yohei/20231028/20231028125427.png" alt="GridSystem &#x306E;&#x30A4;&#x30E1;&#x30FC;&#x30B8;&#x56F3;" width="1200" height="431" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>GridSystem のイメージ図</figcaption></figure></p> <p>また、GridSystem の検討にあたっては実際に運用されている GridSystem を調査してみることも重要になるため、私達が参考にさせてもらった GridSystem いくつかを紹介させて頂きます。</p> <ul> <li><a href="https://getbootstrap.com/docs/5.2/layout/grid/">Grid system · Bootstrap v5.2</a></li> <li><a href="https://vuetifyjs.com/en/components/grids/">Grid system — Vuetify</a></li> <li><a href="https://chakra-ui.com/docs/components/grid/usage">Grid - Chakra UI</a></li> </ul> <h2 id="実装パターン">実装パターン</h2> <p>GridSystem の実装には大きく以下の 3 つの実装パターンがあります。</p> <ul> <li>float を使う方法</li> <li>Flexbox を使う方法</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> Grid を使う方法</li> </ul> <p>歴史的には float を使う方法が一番古く、次に FlexBox を使う方法、<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> Grid を使う方法と並んできます。</p> <p>Bootstrap では、3 系までは float を使う方法で、4 系からは Flexbox を使う方法、5 系からは <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> Grid を使う方法が登場して来ます(5 系では Flexbox を使う方法、<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> Grid を使う方法の両方が提供されています)。 Bootstrap の進化はやっぱりすごいですね。</p> <ul> <li><a href="https://getbootstrap.com/docs/3.4/css/#grid">Grid system · Bootstrap v3.4</a></li> <li><a href="https://getbootstrap.com/docs/4.0/layout/grid/">Grid system · Bootstrap v4.0</a></li> <li><a href="https://getbootstrap.com/docs/5.2/layout/grid/">Grid system · Bootstrap v5.2</a></li> <li><a href="https://getbootstrap.com/docs/5.2/layout/css-grid/">CSS Grid · Bootstrap v5.2</a></li> </ul> <p>Bootstrap 風の GridSystem を作るのであれば、Flexbox を使う方法で十分なのですが、今回はせっかくなので <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> Grid を使う方法で GridSystem を作っていきます。</p> <h2 id="実装方法">実装方法</h2> <p>実装方法については、下記スライドにて画面の表示を交えながら紹介を行ないました。 ここで紹介している GridSystem に肉付けを行なっていくことで、運用可能な GridSystem を作ることができると思います。 <iframe id="talk_frame_1097358" class="speakerdeck-iframe" src="//speakerdeck.com/player/9da16dc65b184a7b9a06e3aca4c0e4f7" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/t0yohei/vue-woshi-tute-grid-system-woshi-zhuang-sitahua?slide=18">speakerdeck.com</a></cite></p> <h2 id="実装-Tips">実装 Tips</h2> <p>時間の都合上、スライドに記載できなかった実装 Tips をいくつかご紹介します。</p> <h3 id="GridLayoutFrame-コンポーネント">GridLayoutFrame <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a></h3> <p>GridSystem を適用するエリア(body)の最大幅と、エリア(body)外との margin を設定するための Layout <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>です。 実装は以下のようなイメージになります。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier">&lt;</span><span class="synStatement">template</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;grid-layout-frame&quot;</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">slot</span><span class="synIdentifier"> /&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">template</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">style</span><span class="synIdentifier"> scoped&gt;</span> <span class="synIdentifier">.grid-layout-frame</span> <span class="synIdentifier">{</span> <span class="synType">max-width</span>: <span class="synConstant">1200px</span>; <span class="synType">margin-right</span>: <span class="synConstant">auto</span>; <span class="synType">margin-left</span>: <span class="synConstant">auto</span>; <span class="synIdentifier">}</span> <span class="synPreProc">@media </span><span class="synSpecial">screen</span><span class="synPreProc"> </span><span class="synStatement">and</span><span class="synPreProc"> (</span><span class="synType">width</span><span class="synPreProc"> &lt;= </span><span class="synConstant">1279px</span><span class="synPreProc">) </span><span class="synIdentifier">{</span> <span class="synIdentifier">.grid-layout-frame</span> <span class="synIdentifier">{</span> <span class="synType">max-width</span>: <span class="synConstant">575px</span>; <span class="synType">margin-right</span>: <span class="synConstant">auto</span>; <span class="synType">margin-left</span>: <span class="synConstant">auto</span>; <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synPreProc">@media </span><span class="synSpecial">screen</span><span class="synPreProc"> </span><span class="synStatement">and</span><span class="synPreProc"> (</span><span class="synType">width</span><span class="synPreProc"> &lt;= </span><span class="synConstant">607px</span><span class="synPreProc">) </span><span class="synIdentifier">{</span> <span class="synIdentifier">.grid-layout-frame</span> <span class="synIdentifier">{</span> <span class="synType">margin-right</span>: <span class="synConstant">16px</span>; <span class="synType">margin-left</span>: <span class="synConstant">16px</span>; <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">style</span><span class="synIdentifier">&gt;</span> </pre> <h3 id="indexts-での-import-and-export">index.ts での import and export</h3> <p>例えば、<code>components/layouts/grid_system</code> にて GridSystem 関連の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を実装している場合、 <code>components/layouts/grid_system/index.ts</code> を作成し以下のように実装することで、実際に使う際の import が簡潔になります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">import</span> GridColumn from <span class="synConstant">'./GridColumn.vue'</span>; <span class="synStatement">import</span> GridLayout from <span class="synConstant">'./GridLayout.vue'</span>; <span class="synStatement">import</span> GridLayoutFrame from <span class="synConstant">'./GridLayoutFrame.vue'</span>; <span class="synStatement">export</span> <span class="synIdentifier">{</span> GridColumn, GridLayout, GridLayoutFrame <span class="synIdentifier">}</span>; </pre> <p>JS のライブラリなどでよく使われているやり方ですね。</p> <h3 id="最終的な実装イメージ">最終的な実装イメージ</h3> <p>GridSystem を適用する画面で一般的に行う実装は次のようになります。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier">&lt;</span><span class="synStatement">script</span><span class="synIdentifier">&gt;</span> <span class="synStatement">import</span><span class="synSpecial"> </span><span class="synIdentifier">{</span><span class="synSpecial"> GridLayoutFrame, GridLayout, GridColumn </span><span class="synIdentifier">}</span><span class="synSpecial"> from </span><span class="synConstant">'@/components/layouts/grid_system'</span><span class="synSpecial">;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">script</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">template</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span>GridLayoutFrame<span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span>GridLayout<span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span>GridColumn<span class="synIdentifier"> :pc=</span><span class="synConstant">&quot;12&quot;</span><span class="synIdentifier"> :tbl=</span><span class="synConstant">&quot;12&quot;</span><span class="synIdentifier"> :sp=</span><span class="synConstant">&quot;12&quot;</span><span class="synIdentifier">&gt;</span> グリッド全体にまたがる要素 <span class="synIdentifier">&lt;/</span>GridColumn<span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span>GridLayout<span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span>GridLayout<span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span>GridColumn<span class="synIdentifier"> :pc=</span><span class="synConstant">&quot;3&quot;</span><span class="synIdentifier"> :tbl=</span><span class="synConstant">&quot;12&quot;</span><span class="synIdentifier"> :sp=</span><span class="synConstant">&quot;12&quot;</span><span class="synIdentifier">&gt;</span> サイドバー <span class="synIdentifier">&lt;/</span>GridColumn<span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span>GridColumn<span class="synIdentifier"> :pc=</span><span class="synConstant">&quot;9&quot;</span><span class="synIdentifier"> :tbl=</span><span class="synConstant">&quot;12&quot;</span><span class="synIdentifier"> :sp=</span><span class="synConstant">&quot;12&quot;</span><span class="synIdentifier">&gt;</span> メインコンテンツ <span class="synIdentifier">&lt;/</span>GridColumn<span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span>GridLayout<span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span>GridLayoutFrame<span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">template</span><span class="synIdentifier">&gt;</span> </pre> <p>簡潔で良い感じですね?</p> <h2 id="終わりに">終わりに</h2> <p>GridSystem は <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> Grid の登場や、<a href="https://developer.mozilla.org/ja/docs/Web/CSS/CSS_grid_layout/Subgrid">Subgrid</a> が遂に全メジャーブラウザに実装されたことにより、更なる進化を遂げる予感がしています。 Bootstrap 風の GridSystem は歴史的な経緯により、縦の画面割をベースとした GridSystem になっており、<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> Grid などが提供する縦横に柔軟な画面配置とはまた違ったものになっています。</p> <p>こういった経緯もあり、どのような GridSystem が主流となっていくか今後も楽しみですね。</p> <p>それでは、以上でこの記事を締めさせていただければと思います。最後まで読んで頂きありがとうございました。</p> <div class="footnote"> <p class="footnote"><a href="#fn-915f9352" name="f-915f9352" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">あくまで Bootstrap 風な GridSystem を実装する際に求められる要件です。元の GridSystem の定義を別の解釈で実装に落とし込む場合は、別の機能要件になります。</span></p> </div> t0yohei データベース接続を伴うテストを並列化した話 hatenablog://entry/6801883189051112908 2023-10-27T09:01:57+09:00 2023-10-27T09:01:57+09:00 はじめに クラウドログ事業推進部でバックエンドエンジニアをしている馬渕です。 早いもので入社してから8ヶ月が経ちました。 最初はプロダクトのバックエンドの実装から始まり、インフラ周りを少しだけ、最近ではフロントエンドの実装にも挑戦しています。 今回はいろいろと経験させていただいた中から、データベース接続を伴うGoのユニットテストで実行時間の短縮をした内容を投稿します。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yusuke_mabuchi/20231020/20231020081950.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="はじめに">はじめに</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログ事業推進部でバックエンドエンジニアをしている馬渕です。</p> <p>早いもので入社してから8ヶ月が経ちました。 最初はプロダクトのバックエンドの実装から始まり、インフラ周りを少しだけ、最近ではフロントエンドの実装にも挑戦しています。</p> <p>今回はいろいろと経験させていただいた中から、データベース接続を伴うGoの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>で実行時間の短縮をした内容を投稿します。</p> <h1 id="背景">背景</h1> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yusuke_mabuchi/20231018/20231018083226.png" width="1200" height="791" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログではバックエンドの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>のうち、データベース接続を伴うテストについてはモックを使用していません。</p> <p>それはなるべく本番に近い形で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>を行うことで品質を担保しているからです。</p> <p>そのため現状のテストの構成では、お互いのテストのデータが干渉しないように直列に実行するようになっています。</p> <h1 id="課題">課題</h1> <p>その結果、開発速度が低下する、という課題を抱えることとなりました。</p> <p>それはすべてのテスト実行の時間がかかるからです。</p> <p>背景でもお伝えした通り、お互いのテストデータが干渉しないようにするため、テストは直列で実行する必要があります。</p> <p>ローカルでの開発においても、またCI上での実行においても時間がかかってしまうという課題となりました。</p> <h1 id="検討">検討</h1> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yusuke_mabuchi/20231018/20231018083313.png" width="1200" height="913" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>これらの課題を解決するため、いくつかの案を元に検討を進めました。</p> <ol> <li>インメモリデータベースの利用</li> <li>データベースプールを作り、空き状況に応じて利用</li> <li>テストごとに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>を利用</li> <li>テストごとにデータベースを生成・利用</li> </ol> <h2 id="1-インメモリデータベースの利用">1. インメモリデータベースの利用</h2> <p>テストにおいて永続化は必要ないため、アクセス速度を上げる目的で検討に上がりました。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>互換を謳うものも存在し、ORMとも動作するため、本番と同等の動作が期待できます。</p> <p>一部、<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>に非互換な部分も残っているので、採用には十分な調査が必要です。</p> <h2 id="2-データベースプール">2. データベースプール</h2> <p>同一のデータベースを参照していることが原因で直列に動作せざるを得ないため、CPUのコア数に応じて複数のデータベース<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を立ち上げるデータベースプールが検討に上がりました。</p> <p>テストは実行時に空いているデータベースを探して実行します。</p> <p>テスト完了後にはクリーンアップが行われます。</p> <p>そのため、データベースプールで確保している分までは並列に実行が可能となります。</p> <h2 id="3-トランザクションの利用">3. <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>の利用</h2> <p>同一のデータベースに対する<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C7%D3%C2%BE%C0%A9%B8%E6">排他制御</a>が問題なのであれば、テストごとに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>をはり、テスト完了後に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%ED%A1%BC%A5%EB%A5%D0%A5%C3%A5%AF">ロールバック</a>をする方法が検討に上がりました。</p> <p>複数のテストが同時に実行したとしても、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>によりお互いのデータが干渉することがなくなります。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>がはられている間は、別のテストがデータベースにアクセスすることができず、待ち時間が発生してしまうことには注意が必要です。</p> <h2 id="4-テストごとにデータベースを生成">4. テストごとにデータベースを生成</h2> <p>こちらも同じく同一のデータベースへのアクセスが問題なのであれば、テストごとにデータベースを生成することで解決するのではないか、という観点から検討に上がりました。</p> <p>テスト用の<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>コンテナとしては1つですが、その中にデータベースを作成し、テスト完了後に<a class="keyword" href="https://d.hatena.ne.jp/keyword/Drop">Drop</a>します。</p> <p>データベースの名前がバッティングする問題については、テストごとに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>を生成してデータベース名につけることで解決を図ります。</p> <h2 id="メリットデメリット">メリット・デメリット</h2> <p>続いて、それぞれの案についてメリット・デメリットをまとめました。</p> <table> <thead> <tr> <th>方針</th> <th>速度</th> <th>導入コスト</th> <th>すべての型に対応</th> <th>既存改修コスト</th> </tr> </thead> <tbody> <tr> <td>1. インメモリデータベース</td> <td>◎</td> <td>◯</td> <td>✗</td> <td>◯</td> </tr> <tr> <td>2. データベースプール</td> <td>△ </td> <td>△</td> <td>◯</td> <td>◯</td> </tr> <tr> <td>3. <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a></td> <td>△</td> <td>◯</td> <td>◯</td> <td>△</td> </tr> <tr> <td>4. テストごとデータベース生成</td> <td>◯</td> <td>◯</td> <td>◯</td> <td>◯</td> </tr> </tbody> </table> <p>これらを元に、</p> <ul> <li>複雑な機構の導入は属人化してしまう可能性がある</li> <li>単純な機構であれば応用が効く</li> </ul> <p>という観点から、今回は4.を採用することにしました。</p> <h1 id="どう解決したか">どう解決したか</h1> <p>テスト用のデータベースコンテナとしては1つを利用するのですが、その中でテストごとにデータベースを作成し、お互いのテストが干渉しないようにしました。</p> <p>詳しくは次のセクションで説明します。</p> <h1 id="具体的なアプローチ">具体的なアプローチ</h1> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yusuke_mabuchi/20231019/20231019084748.png" width="1200" height="515" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="1-テスト用のデータベースを生成する構造体を作成">1. テスト用のデータベースを生成する構造体を作成</h2> <p>テスト用のデータベース周りを管理する、database_generator構造体と生成用の関数を作成します。</p> <p>この構造体は、</p> <ul> <li>テスト用のデータベースの作成</li> <li>初期データベース用のDumpファイル作成</li> <li>データベース削除</li> <li>コネクションクローズ</li> </ul> <p>の機能を持っています。</p> <pre class="code lang-go" data-lang="go" data-unlink> <span class="synStatement">type</span> DatabaseGenerator <span class="synStatement">interface</span> { DumpDB() <span class="synType">error</span> GenerateDB() (gormDB *gorm.DB, <span class="synType">error</span>) DropDB() <span class="synType">error</span> CloseConnection() <span class="synType">error</span> } <span class="synStatement">type</span> databaseGenerator <span class="synStatement">struct</span> { cred Credential <span class="synComment">// 秘匿情報を保持するstruct</span> db *sql.DB dbName <span class="synType">string</span> } <span class="synStatement">func</span> New() (DatabaseGenerator, <span class="synType">error</span>) { cred := config.GetDBCredential() <span class="synError"> </span> dsn := fmt.Sprintf( <span class="synConstant">&quot;%s:%s@tcp(%s:%d)&quot;</span>, cred.User, cred.Password, cred.Host, cred.DBPort, ) sqlDB, _ := sql.Open(<span class="synConstant">&quot;mysql&quot;</span>, dsn) <span class="synStatement">return</span> &amp;databaseGenerator{ cred: cred, db: sqlDB, }, <span class="synStatement">nil</span> }<span class="synError"> </span> </pre> <h2 id="2-テストのMain関数でテスト用のデータベースをDump">2. テストのMain関数でテスト用のデータベースをDump</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%B0%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3">マイグレーション</a>ファイルやseedファイルを元にテスト用の初期データベースを作成し、Dumpします。</p> <pre class="code lang-go" data-lang="go" data-unlink><span class="synStatement">func</span> (dg *databaseGenerator) DumpDB() <span class="synType">error</span> { <span class="synComment">// gormインスタンスの生成、データベース名はランダムなsuffixが入る</span> gormDB, dbName, _ := dg.createTestDB() <span class="synComment">// seedパッケージはマイグレーションやSeedの適用を管理します</span> seed := NewSeed(gormDB) seed.Migrate() <span class="synComment">// テスト用のコンテナにmariadb-clientが必要です </span> cmd := exec.Command( <span class="synConstant">&quot;mysqldump&quot;</span>, <span class="synConstant">&quot;--single-transaction&quot;</span>, <span class="synConstant">&quot;--skip-lock-tables&quot;</span>, fmt.Sprintf(<span class="synConstant">&quot;-h%s&quot;</span>, dg.cred.Host), fmt.Sprintf(<span class="synConstant">&quot;-u%s&quot;</span>, dg.cred.User), fmt.Sprintf(<span class="synConstant">&quot;-p%s&quot;</span>, dg.cred.Password), dbName, ) output, _ := cmd.CombineOutput() file, _ := os.Create(&lt;DUMP_FILE_PATH&gt;) <span class="synStatement">defer</span> file.Close() _, _ := file.Write(output) <span class="synStatement">return</span> <span class="synStatement">nil</span> } </pre> <p>詳しい解説は割愛しますが、createTestDBファンクションはhashを使ったランダムなsuffixを付与したデータベース名でgorm.DBの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を生成し、合わせてdbNameも返します。</p> <p>その後、seedパッケージによる<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%B0%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3">マイグレーション</a>を行い、<a class="keyword" href="https://d.hatena.ne.jp/keyword/mariadb">mariadb</a>-clientのコマンド、mysqldumpを使って<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%B0%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3">マイグレーション</a>が完了したデータベースをDumpします。</p> <p>この処理をテストのメイン関数で実行します。</p> <pre class="code lang-go" data-lang="go" data-unlink><span class="synStatement">func</span> TestMain(m *testing.M) { dbGen, _ := database_generator.New() _, := dbGen.Dump() <span class="synError"> </span> os.Exit(m.Run()) } </pre> <h2 id="3-Dumpからの復元">3. Dumpからの復元</h2> <p>各テストで実行するデータベースを生成するための関数を準備します。</p> <p>この関数では、先程のDumpで作成されたファイルを元にデータベースを復元し、gorm.DBを返します。</p> <pre class="code lang-go" data-lang="go" data-unlink><span class="synStatement">func</span> (dg *database_generator) GenerateDB() (*gorm.DB, <span class="synType">error</span>) { gormDB, _, _ := dg.createTestDB() schema, _ := os.ReadFile(&lt;DUMP_FILE_PATH&gt;) _ := gormDB.Exec(<span class="synType">string</span>(shcema)).Error <span class="synStatement">return</span> gormDB, <span class="synStatement">nil</span> } </pre> <h2 id="4-データベース作成テスト実行データベース削除コネクションクローズ">4. データベース作成、テスト実行、データベース削除、コネクションクローズ</h2> <p>各テストケースの実行前に、database_generate<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を生成し、データベースを作成します。</p> <p>このデータベースはこのテストケースでのみ使われることになります。</p> <pre class="code lang-go" data-lang="go" data-unlink><span class="synStatement">func</span> Test_Hoge(t *testing.T) { dbGen, _ := database_generator.New() testDB, _ := dbGen.GenerateDB() <span class="synComment">// setup</span> <span class="synComment">// テスト用のデータ投入などを行う</span> <span class="synComment">// run tests</span> ... <span class="synComment">// teardown</span> _ := dbGen.DropDB() _ := dbGen.CloseConnection() } </pre> <p>テストの実行が完了したあと、データベースの削除とコネクションを閉じます。</p> <h2 id="5-並列で実行するための処理を追加">5. 並列で実行するための処理を追加</h2> <p>パッケージ内のテストを並列で実行するため、t.Parallel()を追加します。</p> <pre class="code lang-go" data-lang="go" data-unlink><span class="synStatement">func</span> Test_Hoge(t *testing.T) { t.Parallel() ... } </pre> <p>パッケージ内で並列にテストを実行するためには、この記述が必要です。</p> <h2 id="6-テスト実行コマンドを編集">6. テスト実行コマンドを編集</h2> <p>パッケージ内で並列実行したい数をコマンドで指定します</p> <p>指定しなかった場合、利用可能なCPUの数が指定されます</p> <pre class="code bash" data-lang="bash" data-unlink>go test ./... -parallel &lt;並列化したい数&gt;</pre> <h2 id="苦労した点">苦労した点</h2> <h3 id="1-動作確認にとにかく時間がかかる">1. 動作確認にとにかく時間がかかる</h3> <p>実装後に正しく動作するか確認するのですが、その待ち時間が非常に多く発生します。</p> <p>また並列化に伴い、データベース以外のところでデータ干渉が発生するケースもあったため、原因の特定には苦労しました。</p> <h3 id="2-環境差異">2. 環境差異</h3> <p>ローカルでは上手く動作していたものの、CI上でテストをすると上手くいかない、という環境差異に悩まされました。</p> <p>環境が違う状況で実行するものについては、早い段階でそれぞれの環境上のテストが必要だと反省しました。</p> <h1 id="おわりに">おわりに</h1> <p>これらの対応により、約20%程度、実行時間を削減することができました。</p> <p>もしデータベース接続を伴うGoの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>に課題を抱えていらっしゃいましたら、今回の記事が参考になると幸いです。</p> <p>お読みいただきありがとうございました。</p> yusuke_mabuchi LogStudyで進化するCrowdLog hatenablog://entry/6801883189049769923 2023-10-24T17:48:12+09:00 2023-10-27T10:17:50+09:00 はじめに こんにちは、CrowdLog開発チームの 孫、富田 です。 この記事では、CrowdLog内で2年半近く継続的に活動しているLogStudyについてご紹介したいと思います。 LogStudyとは、その名前が示すとおり「Log」+「Study」、つまりCrowdLogでの学習活動のことです。 CrowdLogの開発チームには国内20人、オフショア(フィリピン)7人のメンバーが所属しており、国内メンバーが主に企画・活動をおこなって来ましたが、最近ではオフショアメンバーも参加して活動しています。 LogStudyはトップダウンで行われるような技術研修ではなく開発者1人1人の想いで作り上げて… <p><figure class="figure-image figure-image-fotolife" title="OGP"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/H/HTomita/20231012/20231012133441.png" width="1200" height="626" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure></p> <h3 id="はじめに"><span style="color: #0000cc">はじめに</span></h3> <p>こんにちは、CrowdLog開発チームの 孫、富田 です。 この記事では、CrowdLog内で2年半近く継続的に活動しているLogStudyについてご紹介したいと思います。</p> <p>LogStudyとは、その名前が示すとおり「Log」+「Study」、つまりCrowdLogでの学習活動のことです。</p> <p>CrowdLogの開発チームには国内20人、オフショア(フィリピン)7人のメンバーが所属しており、国内メンバーが主に企画・活動をおこなって来ましたが、最近ではオフショ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%F3%A5%D0%A1%BC">アメンバー</a>も参加して活動しています。</p> <p>LogStudyは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%C3%A5%D7%A5%C0%A5%A6%A5%F3">トップダウン</a>で行われるような技術研修ではなく開発者1人1人の想いで作り上げていく広がりを持った活動です。</p> <p>読者の皆様には、この記事を読まれて学習活動について<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%D1%A5%A4%A5%E4">インスパイヤ</a>してもらうとともに、ぜひ皆様の職場でも同様な活動を実施されることを期待いたします。</p> <p>なお、今回の記事では LogStudy とは何かについて紹介し、今後は個々のLogStudyの内容・成果を紹介していくつもりです。</p> <h3 id="背景"><span style="color: #0000cc">背景</span></h3> <p>LogStudy活動は、当初CrowdLogの開発方法の改善を目指して開始されました。活動当初は国内開発メンバーも5人ほどで、メンバー全員が集まり手探りでスタートを切った状況でした。当時は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B3%AB%C8%AF%A5%D7%A5%ED%A5%BB%A5%B9">開発プロセス</a>を従来の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A6%A5%A9%A1%BC%A5%BF%A1%BC%A5%D5%A5%A9%A1%BC%A5%EB">ウォーターフォール</a>から<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>開発への切り替えをおこなっていました。そんな中から開発メンバーの発案で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>開発を習得するための勉強会の実施が提案された事がLogStudyの始まりです。</p> <p>以来、Crowdlogのサービスと開発組織の成長に合わせて、LogStudyも実施するテーマや運営方法の改善を重ね進化を続けています。</p> <h3 id="目的"><span style="color: #0000cc">目的</span></h3> <p>LogStudyは当初から目的が明確にされていました。これは、LogStudyをただの勉強会に終わらせず業務に役に立つことを念頭においていたからです。以下にLogStudyの4つの目的を示します。</p> <ul> <li>技術習得と活用</li> <li>ナレッジ・シェア</li> <li>スキル向上とスキルの共有</li> <li>共同作業・コミュニケーションの場を提供</li> </ul> <h4 id="目的の説明">【目的の説明】</h4> <h5 id="技術習得と活用">技術習得と活用</h5> <p> LogStudyの主目的は、プロダクトを開発するにあたっての技術習得が第一に挙げられます。技術のスコープは広く、開発方法論、設計方法論、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DF%A5%C9%A5%EB%A5%A6%A5%A7%A5%A2">ミドルウェア</a>等の要素技術、コーディング技術、<a class="keyword" href="https://d.hatena.ne.jp/keyword/SQL">SQL</a>の書き方、テスト技法等、およそ開発に関わる技術については全てが対象となります。</p> <p>また、現在使用している技術に限らず中長期を見据えた技術、普段の業務に限定されない技術も採用しています。ただし、CrowdLog開発でその技術がどのように活用されるかを常に意識してテーマは選定されます。</p> <p>テーマ選定の際は、テーマが明確にされていなくても、漠然とこんな事を解決したいとの提案があれば必要な技術要素を選定してテーマを決めることもあります。</p> <h5 id="ナレッジシェア">ナレッジ・シェア</h5> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%B9%A5%C6%A5%E0%B3%AB%C8%AF">システム開発</a>に当たっては、情報共有が重要な要素になります。数人の開発や、オフィス作業では開発メンバーが近くにいるので、直ぐに情報をシェアすることができます。しかし、ある程度開発者が多くなり、複数のチームに別れ開発を進める場合、またリモート開発、オフショア開発の場合は意識して情報を共有しないと、見逃しや勘違い等が発生し開発上の問題となります。</p> <p>LogStudyはそんな課題も解決します。例えば、システム全体で共有する情報、例えばデザインルール、共通機能のシェア、テーブル追加や変更等のシステムで一貫性を保つ必要がある場合は、その内容を単に通知するだけではなく、LogStudyの場で情報伝達や議論等を通して漏れや誤りが発生しないようにしています。</p> <p> また、ナレッジ・シェアは属人化している作業や知識の改善にも役立っています。</p> <h5 id="スキル向上とスキルの共有">スキル向上とスキルの共有</h5> <p>技術習得により各メンバーのスキルを向上させます。また、ナレッジ・シェアによりチームの技術力をアップさせます。</p> <p>開発チームに新規メンバーが参加した際は、メンバーの経歴、特にメンバーの習得技術をまとめてもらいLogStudyでシェアします。これにより、開発を行う際の適切なタスク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B5%A5%A4">アサイ</a>ン、困った際にヘルプをしてくれる人の特定を容易にしています。 </p> <h5 id="共同作業コミュニケーションの場を提供">共同作業・コミュニケーションの場を提供</h5> <p>システムの開発では多くの人が関わります。各メンバーは自身のタスクを中心に開発作業を進めて行きますが、それだけでは良いシステムを開発することはできません。各開発メンバーがお互いの作業をある程度共有することが必要です。例えば、開発者はシステムの目的、背景をしらなければ適切な機能やUI/UXを開発できません。ビジネスメンバーについては、システムの機能や制約を押さえて置く必要がありお互いのコミュニケーションが重要な点になります。</p> <p>同様にリモートやオフショア開発ではコミュニケーション機会が限定されるのでコミュニケーションの場を提供することが重要です。LogStudyでは、コミュニケーションのハードルを下げメンバの一体感の醸成や協力体制の確立にも役立っています。</p> <h3 id="LogStudy委員会"><span style="color: #0000cc">LogStudy委員会</span></h3> <p>以下ではLogStudyの運営方法を紹介します。</p> <p>LogStudyでは運営のために委員会を設置しています。活動を継続して行うためには運営方法を決め維持・管理しておくことが重要です。運営委員会は、1回/週に委員会を開催し以下の内容を実施しています。</p> <ul> <li>ネタ切れを起こさないように実施したいテーマのア<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%C7%A5%A2">イデア</a>を出しを行う</li> <li>勉強会当日をどのように進めるかをしっかりと事前に考えておく</li> <li>実施したテーマについての振り返りを実施して次回に活かす</li> </ul> <h4 id="テーマ選定と企画">テーマ選定と企画</h4> <p>委員会で、委員とメンバーが参加してテーマのアイディア出し、直近で実施するテーマと内容(進め方・教材等)を決定します。メンバーの参加は自由で、実施したいテーマの企画があれば気軽に提案することができます。委員会ではテーマの効果や実施可能なものかを議論・評価し実施可否が決定されます。また、テーマの実施方法、準備等も議論し最大限効果がでるように工夫します。</p> <h4 id="テーマの実施方法">テーマの実施方法</h4> <ul> <li>実施スケジュール:隔週2時間(毎週行う場合もあり)</li> <li>実施回数:1回〜シリーズものであれば実施内容を分け複数回</li> <li>体制 <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%A1%A5%B7%A5%EA%A5%C6%A1%BC%A5%BF%A1%BC">ファシリテーター</a>:企画・進行</li> <li>参加者:当日は内容により各参加者個別に活動、あるはチームに別れ活動</li> </ul> </li> <li>教材 <ul> <li>担当者が資料を作成するが、書籍やWebサイトなどを利用する場合がある</li> </ul> </li> <li>準備 <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%A1%A5%B7%A5%EA%A5%C6%A1%BC%A5%BF%A1%BC">ファシリテーター</a> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B8%A5%A7%A5%F3%A5%C0">アジェンダ</a>作成、シェアツール環境(<a class="keyword" href="https://d.hatena.ne.jp/keyword/Figma">Figma</a>, Notion)・アプリ環境等準備</li> </ul> </li> <li>参加者 <ul> <li>宿題(資料作成)</li> </ul> </li> </ul> </li> <li>実施内容 <ul> <li>資料発表</li> <li>議論・検討</li> <li>ハンズオン</li> </ul> </li> </ul> <h4 id="振り返り">振り返り</h4> <p>LogStudyは単に企画・実施するだけでなく、実施内容や実施方法がより効果的になるよう振り返りを実施して改善を行っています。</p> <h5 id="振り返り-1">振り返り</h5> <p>毎週、前回に実施したLogStudyの内容について振り返りを実施して実施内容を整理するとともに、課題や問題の対応を次回のLogStudyに反映させています。また、1年の終わりにはその年に実施した LogStudyについて振り返りを実施します。LogStudy参加者が<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>のレトロスペクティブ形式で、良かったこと、問題、チャレンジしたいことを挙げ今後の実施方法に反映させます。</p> <h5 id="成果を記録">成果を記録</h5> <p>LogStudyの大きな目的として、LogStudyで学んだことを実際のCrowdLog開発に活かすことがあります。このために、過去に学んできたことが実際にどの程度活かされているかを計測するための仕組みを用意しました。開発メンバーはLogStudyで学んだスキルを実業務で適用した場合、適用した内容(勉強会のテーマ、適用内容等)をコミットレポートに記録します。LogStudy委員会では、この記録を元に成果を評価します。</p> <h3 id="過去のLogStudyの紹介"><span style="color: #0000cc">過去のLogStudyの紹介</span></h3> <p>以下では、過去実施したLogStudyの活動の一部を紹介します。 LogStudy のテーマは、大きく分けて2つに分かれています。実施する内容が多く複数回に渡るシリーズものと、1回のみ実施する単発ものです。</p> <ul> <li>総実施回数 -74</span>回実施(2022/03 - 2023/09)+ α(2021/04 - 2022/02 記録無し)</li> <li>テーマ(抜粋) <ul> <li>単発 <ul> <li>GraphQL(GraphQLの紹介、ハンズオン)</li> <li>AI居酒屋(プロダクトへのAI活用について議論)</li> <li>モンキーテスト大会(リリース機能について開発、ビジネス再度のメンバーで実施)</li> <li>Gorm Version Up 共有(Gromライブラリ VerUpの経緯、作業内容、注意事項の共有)</li> <li>プログラミング競技会(課題を解決するプログラムを作成する)</li> <li>これまでの経験を共有する(メンバーの開発経験、スキルを共有)</li> <li>LogStudy振り返り(今まで実施してきたLogStudyを振り返る)</li> <li>開発の話が聞きたい(ビジネスチームからの要望で、開発全般、CrowdLogの機能詳細等をシェア)</li> <li>お客様の声が聞きたい(開発チームからの要望で、ビジネスチームに顧客様の要望・不満等をシェア)</li> <li>CrowdLogの理解(開発メンバーが機能、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>、設計、適用技術等をシェア)   :</li> </ul> </li> <li>シリーズ(実施回数) <ul> <li>コーディング勉強会(5回)</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>勉強会(4回)</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%B6%A5%A4%A5%F3%A5%D1%A5%BF%A1%BC%A5%F3">デザインパターン</a>勉強会(10回)</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/SQL">SQL</a>輪読会(6回)</li> </ul> </li> </ul> </li> <li>単発の例 - AI居酒屋(エンジニアチーム+ビジネスチーム) <ul> <li>AIのLogStudyの活用を議論 <ul> <li>AIについての疑問・不安</li> <li>生産性向上のアイディア出し</li> <li>CrowdLogへの活用のアイディア出し</li> </ul> </li> </ul> </li> <li>シリーズの例 - <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%B6%A5%A4%A5%F3%A5%D1%A5%BF%A1%BC%A5%F3">デザインパターン</a>勉強会 <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/GOF">GOF</a>の各<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%B6%A5%A4%A5%F3%A5%D1%A5%BF%A1%BC%A5%F3">デザインパターン</a>を解説してCrowdLogでの適用を議論 <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%B6%A5%A4%A5%F3%A5%D1%A5%BF%A1%BC%A5%F3">デザインパターン</a>解説</li> <li>CrowdLog利用シーン案</li> </ul> </li> </ul> </li> </ul> <h4 id="直近の活動例">直近の活動(例)</h4> <p>具体的な実施内容の例として直近に実施した活動内容を紹介します。</p> <ul> <li>テーマ <ul> <li>コーディング勉強会</li> </ul> </li> <li>目的 <ul> <li>CrowdLogのコーディング品質の向上を図り、信頼性向上、拡張性向上、開発効率向上を狙う</li> </ul> </li> <li>教材 <ul> <li>書籍「良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方」</li> </ul> </li> <li>第5回対象章 <ul> <li>設計の意義と設計への向き合い方</li> <li>設計を妨げる<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B3%AB%C8%AF%A5%D7%A5%ED%A5%BB%A5%B9">開発プロセス</a></li> <li>設計技術の理解の深め方</li> </ul> </li> <li>宿題 <ul> <li>対象章を事前に読み、下記事項をまとめて参加者にシェアする <ul> <li>概要まとめ</li> <li>学び・疑問・感想</li> <li>ディスカッションしたいこと</li> </ul> </li> </ul> </li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B8%A5%A7%A5%F3%A5%C0">アジェンダ</a> <ol> <li>(チーム) まとめてきたことの共有とディスカッション (30分)</li> <li>(全体) 各チームのディスカッションについて共有 (10~15分)</li> <li>(全体) 休憩(5分)</li> <li>(チーム)チームに分かれて課題(40分) <ul> <li>その回で学んだことを使って、お題について各チームで設計/実装してみる</li> <li>毎回同じお題に対してアップデートをかけていく</li> <li>使用する言語・環境・まとめ方は何を使ってもOK 5.   (全体)最後にチーム通しで共有 (15~20分)</li> <li>発表の後に質疑応答</li> </ul> </li> <li>感想 <ul> <li>システム設計は、マクロ(<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>)レベルの設計とミクロ(コーディング)レベルでの設計があります。本LogStudyにより、ミクロレベルでのコードの可読性、保守性、拡張性改善するためのスキルを獲得しました。</li> <li>CrowdLog開発においても本スキルを適用してコーディングを実施するとともに、レビューでも活用していきます。</li> </ul> </li> </ol> </li> </ul> <h5 id="全体の流れ">全体の流れ</h5> <p><figure class="figure-image figure-image-fotolife" title="全体の流れ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/H/HTomita/20231012/20231012111927.png" width="1200" height="479" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure></p> <h3 id="成果適用事例"><span style="color: #0000cc">成果・適用事例</span></h3> <p>LogStudyの成果は「CrowdLog開発にあたっての人的、技術的、開発管理的な基盤確立に貢献し、開発効率を大きく上昇させた」ことです。具体的な内容を以下に示します。</p> <h4 id="ナレッジシェアリングによる共通認識の形成">ナレッジ・シェアリングによる共通認識の形成</h4> <p>以下の項目について開発メンバーに共通認識ができました。</p> <h5 id="Gorm-VerUp">Gorm VerUp</h5> <p>プロダクトで使用しているORM LibraryであるGormのバージョンアップが必要となりました。バージョンアップに伴い大きくライブラリに変更が入ったものの事前に「v1で書いていたものをv2ではどのように書くべきか」や「主要な変更点」について共通認識がもてていたのでアップデート直後も大きな混乱もなく開発・運用ができている。</p> <h5 id="ルール周知">ルール周知</h5> <p>既存の「障害発生時のフロー」の問題を洗い出し、見直して開発メンバーに共有しました。これにより障害発生時の対応を効率的に実施できた。</p> <h5 id="gRPC技術共有">gRPC技術共有</h5> <p>プロダクトの新規機能開発で新技術としてgPRCを導入するとともに、全エンジニアに対しgPRCの仕組み、機能や利用方法をシェアし他の機能でもgPRCを利用できるよう技術をシェアしました。</p> <h4 id="共同作業コミュニケーションの場作りコミュニケーションハードルを下げる">共同作業、コミュニケーションの場作り(コミュニケーションハードルを下げる)</h4> <h5 id="プログラミング競技会">プログラミング競技会</h5> <p>課題を解決するプログラムを作成する競技会を開催しました。各開発者は議論しながらプログラムをすることでコミュニケーションが活発になり一体感が醸成されました。</p> <h5 id="モンキーテスト大会">モンキーテスト大会</h5> <p>新規機能をリリースする前に、開発チームとビジネスチームが新規機能を使って不具合を検出する大会を実施しました。当該機能の開発者でない視点で機能を使用することで予想外に多くの問題・課題を検出することができました。これにより、新機能を高い品質で新機能をリリースすることができました。</p> <h4 id="技術適用">技術適用</h4> <h5 id="デザインパターン適用"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%B6%A5%A4%A5%F3%A5%D1%A5%BF%A1%BC%A5%F3">デザインパターン</a>適用</h5> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/GOF">GOF</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%B6%A5%A4%A5%F3%A5%D1%A5%BF%A1%BC%A5%F3">デザインパターン</a>を担当者毎に解説、合わせてプロダクトへの適用箇所を開発メンバーで議論し理解を深めました。これまでは、システム設計の段階でどのようなデザインにすべきかメンバーで議論を重ね決定していました。勉強会実施後は、抽象化や再利用性等の視点も加わり、「この部分は、〇〇パターンを適用すると汎用的になる」のように設計品質を向上することができた。</p> <h3 id="今後の展望"><span style="color: #0000cc">今後の展望</span></h3> <p>LogStudyのこれからについて考えていることをお話しします。</p> <h4 id="成果のフィードバック強化">成果のフィードバック強化</h4> <p>LogStudyで作成した資料を整備しLogStudyに参加していなかったメンバーでも利用できるようにする。また、インフラ関係をテーマに選定する等、LogStudyによりCrowdLogの開発効率を最大化させるようにLogStudyを運営していきます。</p> <h4 id="オフショアフィリピンメンバーも企画に参加">オフショア(フィリピン)メンバーも企画に参加</h4> <p>当初、LogStudyは国内のメンバーで企画・実施していました。現在では、オフショア(フィリピン)のメンバーもLogStudyに参加しています。ただし、現状は参加が中心で企画には関わっていませんが、今後はオフショ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B5%A5%A4">アサイ</a>ドの課題や問題意識を汲み上げて企画にも参加してもらう予定です。</p> <h4 id="社内外への成果の発信">社内外への成果の発信</h4> <p> LogStudyで得られた成果をCrowdLog開発チーム内だけにとどまらせず、社内の他のエンジニア組織や社外のエンジニアに対して、技術の発信元としてエンジニアリング・ブログやQiita等の手段で広く共有し多くのエンジニアに貢献していきます。</p> <h3 id="最後に伝えたいこと"><span style="color: #0000cc">最後に伝えたいこと</span></h3> <p>私たちが最後に伝えたいことは、LogStudyを参考にして読者の皆様も状況に合わせた「皆様のLogStudy」にチャレンジしてもらいたいと言うことです。</p> <p>我々が携わっている業界は他の業界とは異なり技術が重要な要素になっています。我々が扱う技術(インフラやソフトウェア)は進化が激しく常にキャッチアップしておかないと事業自体が陳腐化して競争力がなくなります。</p> <p>そんな状況の中、我々エンジニアは常に自分のスキルを磨いておく必要のある運命にあります。ただし個人の努力では限界があり、組織として<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A5%EB%A5%A2%A5%C3%A5%D7">スキルアップ</a>をサポートする必要があります。</p> <p>CrowdLogではLogStudyという仕組みを用いて、我々エンジニア、CrowdLogサービスそして会社を発展させ続けています。LogStudyは、単なる学習の場ではなく我々を育てる場にもなっています。</p> <p>読者の皆様もLogStudyを参考にしてご自身のLogStudyを企画・実施していただければ幸いです。</p> HTomita SRE NEXT 2023 にスポンサーとして参加します hatenablog://entry/820878482970794720 2023-09-27T15:41:43+09:00 2023-11-17T08:59:20+09:00 こんにちはSREチームでマネージャーをしている @bayashi_okです。 今回、株式会社クラウドワークスは、2023年9月29日(金)に開催される「SRE NEXT 2023」のブロンズスポンサーとして参加します。 また私自身はSRELounge運営メンバーとして、当日スタッフでの遊撃隊として参加をする予定です。 SRE NEXTとは 「SRE Lounge」のメンバーが中心となり運営・開催されるエンジニアのためのコミュニティベースのカンファレンスです。 過去 SRE NEXT 2020、SRE NEXT 2022 と開催され今年はオンライン/オフラインのハイブリット開催となりスタッフブロ… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bayashi_ok/20230926/20230926170808.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちはSREチームでマネージャーをしている <a href="https://twitter.com/bayashi_ok">@bayashi_ok</a>です。</p> <p>今回、株式会社<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスは、2023年9月29日(金)に開催される「SRE NEXT 2023」のブロンズスポンサーとして参加します。</p> <p>また私自身はSRELounge運営メンバーとして、当日スタッフでの遊撃隊として参加をする予定です。</p> <h1 id="SRE-NEXTとは">SRE NEXTとは</h1> <p>「SRE Lounge」のメンバーが中心となり運営・開催されるエンジニアのためのコミュニティベースのカンファレンスです。</p> <p>過去 SRE NEXT 2020、SRE NEXT 2022 と開催され今年はオンライン/オフラインのハイブリット開催となりスタッフブログに詳細が書かれております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.sre-next.dev%2Fentry%2F2023%2F06%2F09%2F110000" title="SRE NEXT 2023を開催します - SRE NEXT Staff Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://blog.sre-next.dev/entry/2023/06/09/110000">blog.sre-next.dev</a></cite></p> <h1 id="SRE-NEXT-2023-概要">SRE NEXT 2023 概要</h1> <p>開催日:2023年9月29日(金)</p> <p>会場:<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B6%E5%C3%CA%B2%F1%B4%DB">九段会館</a>テラス コンファレンス&<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%F3%A5%B1%A5%C3%A5%C8">バンケット</a></p> <p>公式サイトURL:<a href="https://sre-next.dev/2023/">https://sre-next.dev/2023/</a></p> <p>チケットURL:<a href="https://www.eventbrite.com/e/sre-next-2023-tickets-672744525987">https://www.eventbrite.com/e/sre-next-2023-tickets-672744525987</a></p> <p>※2023年9月27日現在 一般チケット (オンライン)のみ無料での参加申込が可能です。</p> <h1 id="過去のスポンサー実績と今回の参加について">過去のスポンサー実績と今回の参加について</h1> <p>過去、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスは第1回のカンファレンスにおいてシルバースポンサーとしてブース出展をしました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2F2020%2F01%2F27%2F161727" title="SRE NEXT 2020にブース出展しました #srenext - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/2020/01/27/161727">engineer.crowdworks.jp</a></cite></p> <p>今回はブース出展なしのブロンズスポンサーとしての参加になりますが、カンファレンスを盛り上げるため、現地参加するSREメンバー用にSRE NEXT 2023 × CrowdWorksの自作Tシャツを作成することとしました。</p> <p>デザインチームとデザインを話し合い、出来上がったTシャツはこちらです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bayashi_ok/20230926/20230926171548.png" width="640" height="610" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>いくつかデザイン候補があったんですが満場一致でこちらに決まりました!</p> <p>個人的にかなりかっこいい感じに仕上がっております。</p> <h1 id="参加メンバーについて">参加メンバーについて</h1> <p>参加するSREメンバーについても軽くご紹介させていただきます。</p> <p>現在SREチームはマネージャーの私も含め5名でcrowdworks.jpのサービス管理・運用を行っております。</p> <p>最近だと<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7から8.0にアップデートや、tfupdateを使用しての高速化、CloudWatch Logsの細かいTipsなど各メンバーから様々な記事も出ておりますので是非現地でこの辺りの話を深掘りたい方がいましたらTシャツを着たメンバーに声をかけてみてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2Fupdate-mysql8" title="crowdworks.jpのマスタデータベースをAWS RDS MySQL 5.7から8.0にアップデートしました - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/update-mysql8">engineer.crowdworks.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2F2023%2F07%2F18%2F175011" title="tfupdateで複数の.terraform.lock.hclを高速に一括更新する - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/2023/07/18/175011">engineer.crowdworks.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fengineer.crowdworks.jp%2Fentry%2Fcloudtrail-cloudwatch" title="CloudWatch Logsの料金が高い原因はコレだった。CloudTrailとの微妙な関係 - クラウドワークス エンジニアブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://engineer.crowdworks.jp/entry/cloudtrail-cloudwatch">engineer.crowdworks.jp</a></cite></p> <p>また、株式会社<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスはcrowdworks.jp以外にも数多くのサービスを運営しておりその中にもSRE/インフラ領域を担うメンバーが存在しております。</p> <p>最近ではSREWeeklyという組織横断での<a class="keyword" href="https://d.hatena.ne.jp/keyword/Web%A5%B5%A1%BC%A5%D3%A5%B9">Webサービス</a>インフラ関連の知見を共有する会も実施されるようになりました。</p> <p>■各事業一覧</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bayashi_ok/20230926/20230926172713.png" width="1200" height="501" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span style="font-size: 80%">※弊社IR資料より抜粋</span></p> <p>そのため今回は、他サービスのメンバーも合わせ、現地参加メンバー6名、オンライン参加メンバー数名での参戦を予定しております。</p> <p>懇親会にも現地参加メンバー全員で参加する予定ですので皆さまとも交流できればと思っております。</p> <p>また冒頭で少し触れましたが、私自身はスタッフとして参加をしており、おそらく会場内や本部をウロウロしている予定です。 参加者やスタッフの方々もあらためてよろしくお願いします。</p> <p>最後に今回会場にTシャツを着ていくにあたってcrowdworks.jpのSREチームが数年ぶりに全員一斉にオフィスに出社し揃うという珍しい機会がありましたので記念に完成したTシャツと合わせて写真を撮りました</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bayashi_ok/20230926/20230926152333.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>それでは当日会場で皆様にお会いできることを楽しみにしております!</p> bayashi_ok 経済産業省が提供するサービスのクライアントAPIを爆速生成してOSS公開までの手順を紹介してみる hatenablog://entry/820878482969669084 2023-09-25T08:00:00+09:00 2023-09-25T08:00:20+09:00 株式会社クラウドワークスでエンジニアをしている、小西です。おすすめのサウナはウェルビー栄です。 2022年ごろからOSS活動をする機会が増え、OSSの公開にもチャレンジしていたりするのでOSSをテーマに記事を書いてみようと思います。 この記事でやろうとしていること 経済産業省が提供しているgBizINFOのクライアントAPI生成し、オープンソース化することとRubyGemsにアップロードするところまでをゴールとします。 gBizINFOは法人番号や法人名から企業等の活動情報が検索できるプラットフォームで、法人番号公表サイトやEDINET、職場情報総合サイトと紐付けして情報を取得することができま… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922101512.png" alt="&#x30C8;&#x30C3;&#x30D7;&#x753B;&#x50CF;" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><a href="https://crowdworks.co.jp/">株式会社クラウドワークス</a>でエンジニアをしている、<a href="https://github.com/uichi">小西</a>です。おすすめのサウナは<a href="https://www.wellbe.co.jp/sakae/">ウェルビー栄</a>です。</p> <p>2022年ごろから<a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>活動をする機会が増え、<a href="https://rubygems.org/gems/kendama">OSSの公開にもチャレンジ</a>していたりするので<a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>をテーマに記事を書いてみようと思います。</p> <h1 id="この記事でやろうとしていること">この記事でやろうとしていること</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B7%D0%BA%D1%BB%BA%B6%C8%BE%CA">経済産業省</a>が提供している<a href="https://info.gbiz.go.jp/index.html">gBizINFO</a>のクライアント<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>生成し、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AA%A1%BC%A5%D7%A5%F3%A5%BD%A1%BC%A5%B9">オープンソース</a>化することと<a class="keyword" href="https://d.hatena.ne.jp/keyword/RubyGems">RubyGems</a>にアップロードするところまでをゴールとします。</p> <p>gBizINFOは法人番号や法人名から企業等の活動情報が検索できるプラットフォームで、<a href="https://www.houjin-bangou.nta.go.jp/">法人番号公表サイト</a>や<a href="https://disclosure2.edinet-fsa.go.jp/">EDINET</a>、<a href="https://shokuba.mhlw.go.jp/">職場情報総合サイト</a>と紐付けして情報を取得することができます。そのため、法人の住所や<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%A1%A5%A4%A5%CA%A5%F3%A5%B9">ファイナンス</a>情報、事業概要などの情報が一括で閲覧できるサービスとなっています。</p> <p>クライアント<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>は、gBizINFOが公開している<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>仕様を元に自動生成します。タイトル通り爆速に生成するため、実装はほとんどありません。また、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>は<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>で管理します。</p> <h1 id="この記事をおすすめできる方">この記事をおすすめできる方</h1> <p>誰にでもおすすめしたい記事ですが、特におすすめできる対象者を挙げてみます。</p> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>クライアントの自動生成について知りたい方</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>リリースにトライしてみたい方</li> </ul> <h1 id="gemのベースを生成">gemのベースを生成</h1> <p>まずは、gemのベースを生成するため<code>bundle gem</code> を実行します。</p> <pre class="code bash" data-lang="bash" data-unlink>$ bundle gem gbizinfo Creating gem &#39;gbizinfo&#39;... MIT License enabled in config Code of conduct enabled in config Changelog enabled in config RuboCop enabled in config Initializing git repo in /Users/uichi/projects/github/gbizinfo hint: Using &#39;master&#39; as the name for the initial branch. This default branch name hint: is subject to change. To configure the initial branch name to use in all hint: of your new repositories, which will suppress this warning, call: hint: hint: git config --global init.defaultBranch &lt;name&gt; hint: hint: Names commonly chosen instead of &#39;master&#39; are &#39;main&#39;, &#39;trunk&#39; and hint: &#39;development&#39;. The just-created branch can be renamed via this command: hint: hint: git branch -m &lt;name&gt; create gbizinfo/Gemfile create gbizinfo/lib/gbizinfo.rb create gbizinfo/lib/gbizinfo/version.rb create gbizinfo/sig/gbizinfo.rbs create gbizinfo/gbizinfo.gemspec create gbizinfo/Rakefile create gbizinfo/README.md create gbizinfo/bin/console create gbizinfo/bin/setup create gbizinfo/.gitignore create gbizinfo/.rspec create gbizinfo/spec/spec_helper.rb create gbizinfo/spec/gbizinfo_spec.rb create gbizinfo/.github/workflows/main.yml create gbizinfo/LICENSE.txt create gbizinfo/CODE_OF_CONDUCT.md create gbizinfo/CHANGELOG.md create gbizinfo/.rubocop.yml Gem &#39;gbizinfo&#39; was successfully created. For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html</pre> <p>これで、gemライブラリのベースができました。 生成された<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ内がこのようになっていれば、問題なしです。</p> <pre class="code bash" data-lang="bash" data-unlink>./ ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin │   ├── console │   └── setup ├── gbizinfo.gemspec ├── lib │   ├── gbizinfo │   │   └── version.rb │   └── gbizinfo.rb ├── sig │   └── gbizinfo.rbs └── spec ├── gbizinfo_spec.rb └── spec_helper.rb</pre> <h1 id="GitHubにリポジトリを作る"><a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>を作る</h1> <p>開発を進めていく前に、<a href="https://github.com/new">New repository</a>から新しく<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>を作成していきます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922101804.png" width="1200" height="674" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>が作成できたら、先ほど生成したgemのベースを<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>にアップロードします。</p> <pre class="code bash" data-lang="bash" data-unlink>git remote add origin https://github.com/uichi/gbizinfo.git git branch -M main git commit -m &#39;gemのベースを生成&#39; git push -u origin main</pre> <p>これで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>の準備は完了です。</p> <h1 id="gBizINFOのAPIスキーマを準備">gBizINFOの<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>を準備</h1> <p>クライアント<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を生成するうえで重要なポイントとなるのが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>です。<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>が生成の元となるため、gBizINFOが提供している<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>仕様から取得します。</p> <p>gBizINFOの<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>仕様はSwaggerで公開されており、Webで閲覧が可能となっています。</p> <p><a href="https://info.gbiz.go.jp/hojin/swagger-ui.html">Swagger UI</a></p> <p>また、クライアント<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>生成時は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>がOpenAPIフォーマットでなければならないため、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>のフォーマット変換も作業していきます。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>データは<a href="https://info.gbiz.go.jp/hojin/v2/api-docs">https://info.gbiz.go.jp/hojin/v2/api-docs</a> から確認ができますが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>形式なため<a href="https://editor-next.swagger.io/">Swagger Editor</a>からOpenAPIフォーマットへ変換します。</p> <h2 id="JSONのスキーマデータをコピー"><a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>データをコピー</h2> <p><a href="https://info.gbiz.go.jp/hojin/v2/api-docs">https://info.gbiz.go.jp/hojin/v2/api-docs</a>を開くとこのような画面になります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922101858.png" width="1200" height="657" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>表示されている<a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>をすべてコピーします。</p> <h2 id="Swagger-EditorでOpenAPIフォーマットにする">Swagger EditorでOpenAPIフォーマットにする</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>形式でもOpenAPIに対応することはできるのですが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%E1%A5%F3%A5%C8%A5%A2%A5%A6%A5%C8">コメントアウト</a>ができないことや<code>{}</code> をつける必要があるなど個人的に扱いづらさを感じるため<a class="keyword" href="https://d.hatena.ne.jp/keyword/YAML">YAML</a>形式に変換します。<a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>の方がいいという方は、そのままでも問題ありません。</p> <p><a href="https://editor-next.swagger.io/">Swagger Editor</a>を開きます。 初期状態では、このような画面になっています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922102102.png" width="1200" height="663" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>画面左側の黒い部分は、エディターになっているため先ほどコピーした<a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>を貼り付けます。 既存で入っている設定は消して構いません。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922102339.png" width="1200" height="664" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>貼り付け後、画面右側がこのようになりgBizINFOの<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>が読み込まれていることが分かります。</p> <p>Swagger Editor上で<a class="keyword" href="https://d.hatena.ne.jp/keyword/YAML">YAML</a>とOpenAPIに変換ができます。まずは<a class="keyword" href="https://d.hatena.ne.jp/keyword/YAML">YAML</a>形式へと変換します。 Editタブから<strong>Convert to <a class="keyword" href="https://d.hatena.ne.jp/keyword/YAML">YAML</a></strong>を選択してください。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922102400.png" width="1200" height="662" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>変換後、貼り付けした<a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>が<a class="keyword" href="https://d.hatena.ne.jp/keyword/YAML">YAML</a>へと変換されたことが分かります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922102439.png" width="1200" height="662" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>次に、<strong>Convert to OpenAPI 3.0.x</strong>を選択するとOpenAPIへと変換されます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922102456.png" width="1200" height="665" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>変換後はこのうようになります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922102527.png" width="1200" height="666" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>最後に、gbizinfo<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ内に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>を設置します。</p> <pre class="code bash" data-lang="bash" data-unlink>$ mkdir openapi $ touch openapi/root.yaml</pre> <p>設置場所は<code>openapi/root.yaml</code>とします。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>を追加できたところで、<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>にアップロードしておきます。</p> <pre class="code bash" data-lang="bash" data-unlink>$ git add openapi/root.yaml $ git commit -m &#39;OpenAPIスキーマの追加&#39; $ git push origin main</pre> <h1 id="APIクライアントの生成"><a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>クライアントの生成</h1> <p>いよいよ、<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>クライアントを生成していきます。</p> <p>生成には<code>openapi-generator</code>というOpenAPI<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>からクライアント<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を生成できるツールを使います。 インストールは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>のREADMEを参考にしてください。</p> <p><a href="https://github.com/OpenAPITools/openapi-generator">https://github.com/OpenAPITools/openapi-generator</a></p> <p>生成コマンドを実行してみます。</p> <pre class="code bash" data-lang="bash" data-unlink>$ openapi-generator generate -i openapi/root.yaml -g ruby --git-repo-id=gbizinfo --git-user-id=uichi --additional-properties=gemName=gbizinfo --additional-properties=gemVersion=0.1.0 Exception in thread &#34;main&#34; org.openapitools.codegen.SpecValidationException: There were issues with the specification. The option can be disabled via validateSpec (Maven/Gradle) or --skip-validate-spec (CLI). | Error count: 1, Warning count: 1 Errors: -components.schemas.Schema name HashMap«string,string» doesn&#39;t adhere to regular expression ^[a-zA-Z0-9\.\-_]+$ Warnings: -components.schemas.Schema name HashMap«string,string» doesn&#39;t adhere to regular expression ^[a-zA-Z0-9\.\-_]+$ at org.openapitools.codegen.config.CodegenConfigurator.toContext(CodegenConfigurator.java:620) at org.openapitools.codegen.config.CodegenConfigurator.toClientOptInput(CodegenConfigurator.java:647) at org.openapitools.codegen.cmd.Generate.execute(Generate.java:479) at org.openapitools.codegen.cmd.OpenApiGeneratorCommand.run(OpenApiGeneratorCommand.java:32) at org.openapitools.codegen.OpenAPIGenerator.main(OpenAPIGenerator.java:66)</pre> <p>エラーが出て処理が中断してしまいました。おそらく、OpenAPI<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>の構文が原因でエラーが発生していると考えられます。</p> <p><code>--skip-validate-spec</code>を指定すると構文エラーを無視できるかもしれないよとメッセージが出ているので、試してみると無事に初期ファイルとクライアント<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>が生成されました。</p> <p>ここで再び<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>にアップロードしておきます。</p> <pre class="code bash" data-lang="bash" data-unlink>$ git add . $ git commit -m &#39;openapi-generatorを使い初期ファイルとクライアントAPIを生成&#39; $ git push origin main</pre> <h1 id="gemspecを設定する">gemspecを設定する</h1> <p><code>openapi-generator generator</code>を実行した際に<code>gemspec</code>が更新されますが、必要最低限の項目だけ更新されます。変更したい項目がある場合は、このタイミングで設定をしていきます。</p> <pre class="code lang-diff" data-lang="diff" data-unlink> s.name = &quot;gbizinfo&quot; s.version = Gbizinfo::VERSION s.platform = Gem::Platform::RUBY <span class="synSpecial">- s.authors = [&quot;OpenAPI-Generator&quot;]</span> <span class="synSpecial">- s.email = [&quot;&quot;]</span> <span class="synSpecial">- s.homepage = &quot;https://openapi-generator.tech&quot;</span> <span class="synIdentifier">+ s.authors = [&quot;uichi&quot;]</span> <span class="synIdentifier">+ s.email = [&quot;37263474+uichi@users.noreply.github.com&quot;]</span> <span class="synIdentifier">+ s.homepage = &quot;https://github.com/uichi/gbizinfo&quot;</span> s.summary = &quot;gBizINFO REST API Ruby Gem&quot; <span class="synSpecial">- s.description = &quot;&lt;div&gt;各REST APIはHTTPリクエストヘッダX-hojinInfo-api-tokenに動作確認用のAPIトークンDTcLxzo1lZaUYaQPVdSRxdS4MzlXNCs4を指定して動作を確認することができます。&lt;/div&gt;&lt;div&gt;※動作確認用のAPIトークンはこのページでの動作確認でのみ使用してください。&lt;/div&gt;&lt;div&gt;※REST APIを利用する際は必ず、&lt;a href='https://info.gbiz.go.jp/hojin/api_registration/form'&gt;Web API利用申請&lt;/a&gt;を行い、APIトークンを取得してください。&lt;/div&gt;&quot;</span> <span class="synSpecial">- s.license = &quot;Unlicense&quot;</span> <span class="synIdentifier">+ s.description = &quot;経済産業省が提供するgBizInfoのRuby製クライアントAPI&quot;</span> <span class="synIdentifier">+ s.license = &quot;MIT&quot;</span> s.required_ruby_version = &quot;&gt;= 2.7&quot; s.add_runtime_dependency 'typhoeus', '~&gt; 1.0', '&gt;= 1.0.1' </pre> <h1 id="ローカルでAPIリクエストを実行してみる">ローカルで<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>リク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを実行してみる</h1> <p>法人の基本情報を取得するエンドポイントに対して、試しにリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トしてみます。 ローカルで実行するためには、gemをビルドしインストールする必要があります。</p> <pre class="code bash" data-lang="bash" data-unlink>$ rake build gbizinfo 0.1.0 built to pkg/gbizinfo-0.1.0.gem $ bundle install</pre> <p>インストールできたところで、<code>irb</code>からリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト実行してみます。</p> <pre class="code bash" data-lang="bash" data-unlink>$ irb require &#39;gbizinfo&#39; api_instance = Gbizinfo::GBizINFORESTAPIApi.new x_hojin_info_api_token = &#39;xxxxxxxxxxxxxxxxxxxxx&#39; corporate_number = &#39;6010401098453&#39; result = api_instance.get_certification_using_get(x_hojin_info_api_token, corporate_number)</pre> <p><code>result</code> の中身を見てみると、こちらのようになっています。</p> <pre class="code bash" data-lang="bash" data-unlink>#&lt;Gbizinfo::HojinInfoResponse:0x0000000108c465e0 @hojin_infos= [#&lt;Gbizinfo::HojinInfo:0x0000000108c46e78 @capital_stock=2697177000, @certification=[], @corporate_number=&#34;6010401098453&#34;, @employee_number=305, @kana=&#34;くらうどわーくす&#34;, @location=&#34;東京都渋谷区恵比寿4丁目20番3号&#34;, @name=&#34;株式会社クラウドワークス&#34;, @postal_code=&#34;1500013&#34;, @representative_name=&#34;代表取締役社長 吉田 浩一郎&#34;, @status=&#34;-&#34;, @update_date=&#34;2023-04-27T00:00:00+09:00&#34;&gt;], @id=&#34;6010401098453&#34;, @message=&#34;200 - OK.&#34;&gt;</pre> <p>問題なくクライアント<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を使ってデータを取得できました。</p> <p>以上で<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>クライアントの生成が完結しました!</p> <h1 id="GitHubでリリースしてみる"><a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>でリリースしてみる</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>の準備が整ったところで、こちらの記事を参考に<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>からリリースしてみます。</p> <p><a href="https://docs.github.com/ja/repositories/releasing-projects-on-github/managing-releases-in-a-repository">リポジトリのリリースを管理する - GitHub Docs</a></p> <p><strong>Choose a tag</strong>からタグを作成します。 タグの設定をすると、<strong>Generate release notes</strong>ボタンが押せるようになるのでクリックします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922102716.png" width="1200" height="617" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>リリースが整ったところで、<strong>Publish release</strong>を押してリリースします。 ちなみに、<strong>Set as a pre-release</strong>にチェックを入れておくとプレリリースとして公開できます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922102758.png" width="1200" height="889" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>以上でリリースができました!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922102623.png" width="1200" height="655" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="RubyGemsに公開してみる"><a class="keyword" href="https://d.hatena.ne.jp/keyword/RubyGems">RubyGems</a>に公開してみる</h1> <p>お待ちかねの、<a class="keyword" href="https://d.hatena.ne.jp/keyword/RubyGems">RubyGems</a>へのリリースをします。 リリースの対象となるのは、先ほど<code>rake build</code>した際に生成された<code>pkg/gbizinfo-0.1.0.gem</code> です。</p> <p>下記を実行し実際にリリースしてみます。</p> <pre class="code bash" data-lang="bash" data-unlink>$ rake release gbizinfo 0.1.0 built to pkg/gbizinfo-0.1.0.gem. Tag v0.1.0 has already been created. Pushing gem to https://rubygems.org... Successfully registered gem: gbizinfo (0.1.0) Pushed gbizinfo 0.1.0 to rubygems.org</pre> <p>公開されたページを見てみると、、</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/crowdworks-blog/20230922/20230922102828.png" width="1200" height="662" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>無事にリリースされました!</p> <p>これで <code>gem install</code> からgbizinfoをインストールすることができます!🤟🏻</p> <h1 id="最終成果物">最終成果物</h1> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fuichi%2Fgbizinfo" title="GitHub - uichi/gbizinfo: 経済産業省が提供するgBizINFOのRuby製クライアントAPI" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/uichi/gbizinfo">github.com</a></cite></p> uichiyy 大規模言語モデル(LLM)を組み込んだサービスを開発しました hatenablog://entry/820878482964756655 2023-09-11T12:00:00+09:00 2023-09-11T17:26:29+09:00 大規模言語モデル(LLM)を組み込んだサービスについて考えたこと <p><figure class="figure-image figure-image-fotolife" title="大規模言語モデルを組み込んだサービスを開発しました"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/ttaka66/20230906/20230906194257.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>大規模<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%C0%B8%EC%A5%E2%A5%C7%A5%EB">言語モデル</a>を組み込んだサービスを開発しました</figcaption></figure></p> <p>こんにちは。<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスの自称LLMエンジニアの<a href="https://twitter.com/ttaka_66">@ttaka_66</a>です。今回は大規模<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%C0%B8%EC%A5%E2%A5%C7%A5%EB">言語モデル</a>(以下LLM)を組み込んだサービスである<a href="https://lin.ee/5EhlLUQ">AI副業診断</a>を開発したことを書かせていただこうと思います。 このサービスは社内で生成AIを活用するア<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%C7%A5%A2">イデア</a>のコンテストが開催されたことがきっかけで始まりました。そのため、企画段階から開発、リリースまでに考えたことを記載いたします。</p> <h2 id="はじめに">はじめに</h2> <p>私個人の主観的な内容(特に企画に関して)も含まれているので、その点を踏まえて読んでいただけますと幸いです。 また、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Deep%20Learning">Deep Learning</a>やTransformerについては勉強中なので理解に誤りがあればご指摘ください。</p> <h2 id="労働市場の概観と副業"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%CF%AB%C6%AF%BB%D4%BE%EC">労働市場</a>の概観と副業</h2> <p>弊社は大きな括りとしては人材の領域の事業をしております。業界に身を置いている立場から見て、現代の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CF%AB%C6%AF%BB%D4%BE%EC">労働市場</a>では、収入を増やし、キャリアの多様性を追求し、経済的な安定性を確保する手段として副業という選択肢が重要性を増しているように感じています。企業側から見ても副業者は外部からの視点を持ち込むため、組織内の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%CE%A5%D9%A1%BC%A5%B7%A5%E7%A5%F3">イノベーション</a>と創造性を促進することができます。副業の需要と副業人材の拡大は、個人と組織の両方にとって新たな機会を提供しており、これらのトレンドは今後もさらに成長するであろうと考えています。</p> <h2 id="副業のきっかけを作ること">副業のきっかけを作ること</h2> <p>個人と組織にとって需要が高まっているにも関わらず、企業から「お願いしたい業務があるのに人員がいない」という声をよく聞きます。個人側から見ても興味があり、能力があるのに踏み切れない方が多くいらっしゃるように感じています。一因として、副業に限った話ではありませんが、インターネット上には情報が過多に存在し、何を信じればいいのか分からないという感覚に陥ることがあります。副業に関しても情報の選別が難しく、混乱を招くことが副業を始める障壁となっているように思われます。そうした方々のきっかけや出発点を提供できれば、人材の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CE%AE%C6%B0%C0%AD">流動性</a>を高めることができ、人と社会にとっての理想に近づけるのではと考えています。</p> <h2 id="人生の豊かさと新たなキャリア">人生の豊かさと新たなキャリア</h2> <p>少し副業から離れ、近年よく聞かれるキャリアの多様性(Diversity)について述べさせてください。多様性の文脈の中で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BC%AB%B8%CA%BC%C2%B8%BD">自己実現</a>、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EF%A1%BC%A5%AF%A5%E9%A5%A4%A5%D5%A5%D0%A5%E9%A5%F3%A5%B9">ワークライフバランス</a>のようなキーワードが用いられることが多いように感じます。これらのキーワードも多様性と連動して近年になって聞かれるようになったもので、10数年前には注目されていませんでした。背景には働くことに関しての考え方の変化があり、以前では働いていない時間の人生を豊かにするための手段として働いていたのが、近年では働いている時間を含めてトータルで人生の豊かさを最大化しようという考え方に変化しているように思います。この考え方が生まれたことは、社会でそれを許容できるようになりつつあるということで、人類にとって大きな前進であるとポジティブに考えています。その一方で、人生の豊かさは物質的な成功だけでなく、精神的充実、感情的幸福、成長、人間関係、社会的つながりなど多くの要素が含まれるので、選択肢が大幅に増えて選択が難しくなったという側面もあります。</p> <h2 id="なぜLLMなのか">なぜLLMなのか</h2> <p>一方でLLMの視点で考えてみます。まず、LLMが何か、何がLLMであるか、何がLLMでないかについて曖昧な部分がありそうなので、ここでは「プロンプトでアクセスでき、文章や特徴を生成する汎用的なTransformerベースのモデル」と定義づけさせてください。逆に、それ以外のモデルをLLMではないものとします(例えば特定のビジネス<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>に特化したチャットボット、特定の用途に利用される翻訳などのタスクに使われるような<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%A1%B3%A3%B3%D8%BD%AC">機械学習</a>モデル)。LLMを超絶簡単に(雑に)説明すると、大量のウェブ上で入手可能なデータ、書籍等からその文章を学習し、「文章のうちの小さな要素(例えば単語など)で重み付けされた情報から次にくる要素は何か」の確率分布を作り、プロンプトを与えたときにこの確率分布から次の要素を予測しながら文章や特徴を生成しているようです。</p> <p>再び新しいキャリアの問題に話を戻します。選択が難しくなったと述べましたが、これまでの選択と比較して変数が増えたが選択方法は変わっていないという仮定に基づいて、話をしたいと思います。これまでは少ない変数だったので、同じような変数の組み合わせが同様の人がいて、それをモデルケースにすることができたが、問題の新しいキャリアでは変数が多くなって掛け算すると同様の人が見つかる可能性が低くなったと考えることができます。そう考えると、各変数ごとに同じ経験や価値観を持った人はいるが、それを組み合わせを持った先人がいない、もしくはいたとしても探すのが困難ということが言えるのではないでしょうか。その場合、組み合わせの問題と置き換えることができそうです。組み合わせはどうやって見つけるのでしょうか。一つの案として、各変数ごとに同じような価値観を持つ人を探し、その人たちの考え方の良い部分を取り入れることが考えられそうです。これもいいと思いますが、先人の知恵を探し、良い部分を取り入れる作業は多くの時間がかかるものかもしれません。前述したように、LLMは先人たちが残した文章から学習をしていて、与えられた情報から確率的に文章(または文章のようなもの)を生成している性質から「探して良い部分を取り入れる」のタスクに近似していると考えることができ、出発点としては良さそうに思います。繰り返しになりますが、本記事および開発したプロダクトはある仮定を基づいて執筆、開発したもので、現時点では断定できるものではありません。キャリアと人生についての仮定を検証することが我々人材業界の意義であり、テク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CE%A5%ED">ノロ</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B8%A1%BC">ジー</a>と社会の相互接続点を見つけ出すことが我々ソフトウェアエンジニアが事業会社で働くことの意義だと考えています。それには多くの方に使っていただいて意見をいただくことが重要であると考えているため、気軽に意見をいただけると大変嬉しく思います。</p> <h2 id="LLMを組み込んだサービスの開発">LLMを組み込んだサービスの開発</h2> <p>企画についての話はここまでにして、開発フェーズで得られた知見について述べさせてください。ここで述べる内容は、私個人のこれまでの開発経験と比較になります。これまでのソフトウェア開発では、比較的ゴールが明確でした。例えば、AさんからBさんに1000円入金したらAさんの口座から1000円が引かれ、Bさんの口座に1000円が加算されるといったそのルールが満たされていればよく、逆にそれが満たされていなければ成立しないといったものです。</p> <p>一方で、今回取り組んだLLMを組み込んだサービスの開発ですが、初期においてはこれまでの開発と同様にあるルールベースを定義して実装することで進められました。しかし、テスト運用として社内に公開してみたところ、賛否両論ありました。具体的には、自分では考えつかないような新しい気づきが得られたというポジティブな意見がある一方で、的外れなことが返ってきたという意見もありました。この課題に対して、前述の価値観の多様性に対する問題と考え、アプローチしました。実装としては、いくつかのプロンプトを用意し、いずれかの回答には欲しい情報が含まれるであろうという考えに基づき改善しました。</p> <h2 id="LLMを組み込んだサービスの評価改善">LLMを組み込んだサービスの評価、改善</h2> <p>いくつかのプロンプトを用意することで実感として改善することはできたものの、実感だけであり、これでは本当に改善されたのか、何を持って改善されたとみなすかは言及できておらず、誤った判断をしてしまいそうです。ここでは私が思いついた限りのいくつかの改善を測定するための指標を述べますが、これについては完璧ではなく、模索段階でより測定自体の改善も今後行っていくべきと思っています。</p> <p>まず、ゴールを「より多くの人に満足してもらうこと」に設定しました。ゴールを元に評価指標を考えたとき、以下の3つのアプローチを考えました。</p> <ol> <li>LLMで評価するアプローチ: LLMで出力された文章を別のLLMで評価するというもの。手間がかからないが、信頼性はそれほど高くないと思われます。</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%A1%B3%A3%B3%D8%BD%AC">機械学習</a>のアプローチ: 人間によって正解ラベルをつけたものをLLMが選択するか。最も手間がかかりますが、正解があればLLMの性能評価としては信頼できそうです。</li> <li>Webト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E9%A5%C3%A5%AD%A5%F3%A5%B0">ラッキング</a>のアプローチ: クリック率(CTR)、コンバージョン率(CVR)など、サービスとして意図した行動がとられたか。最もゴールに直結する指標です。</li> </ol> <p>これらの全てを利用するのがよさそうというのが私の中での現時点での結論です。まずは1で大まかな評価指標の低いところを改善し、用意された文章を選択するような正解があるタスクなどは2で改善し、3でユーザーの反応を見て課題を洗い出して改善というサイクルを回すことになるのではと考えています。</p> <h2 id="利用していただく方に伝えたいこと">利用していただく方に伝えたいこと</h2> <p>仕事を通じて人生を豊かにしたいすべての人に、特に副業未経験の方々に向けた新たなきっかけを提供したいと考えております。もし副業に興味を持ちながらも、未経験の領域に足を踏み入れる勇気を持てないと感じているなら、AI副業診断で未来を変えるお手伝いをしたいと考えています。また、上記のようにAI副業診断は試行錯誤の段階で、これはAI副業診断に限らず多くのLLMの利用でもより良い活用方法を模索しているように感じています。もし満足な回答を得られなければ、入力を少し変えてみて何度か試してみていただけると嬉しいです。さらに、こういう入力をしたら良い回答が得られたなど意見をいただけたら、サービスの改善に活かしていくことを約束します。一緒に新しい働き方を作っていきましょう!</p> <p><a href="https://lin.ee/5EhlLUQ">こちら</a>からAI副業診断を始められます</p> <p>意見がありましたら<a href="https://forms.gle/ep4mG1NYwsw3VdGS7">アンケートフォーム</a>もしくはX(<a href="https://twitter.com/ttaka_66">@ttaka_66</a>)でいただけますと幸いです!</p> ttaka66 Vue Fes Japan 2023 にクラウドワークスのエンジニアが登壇します hatenablog://entry/820878482963359599 2023-09-07T12:00:00+09:00 2023-09-11T17:26:57+09:00 Vue Fes Japan とは Vue Fes Japan は Vue.js 日本ユーザーグループが主催する日本最大の Vue.js カンファレンスです。 日本における Vue.js 開発者たちによる発表や、Vue クリエイター の Evan You や、 Nuxt.js・Vite などのコアコントリビューターの方たちも登壇されます。 昨年のオンラインカンファレンスを経て、今年は念願のオフライン開催が決定しました。 vuefes.jp 登壇情報 弊社からはクラウドワークスエンジニアの @t0yohei と @yamanoku と @53able それぞれ登壇いたします。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20230907/20230907143913.png" alt="&#x30A2;&#x30A4;&#x30AD;&#x30E3;&#x30C3;&#x30C1;&#xFF1A;Vue Fes Japan Online 2023 &#x306B;&#x30AF;&#x30E9;&#x30A6;&#x30C9;&#x30EF;&#x30FC;&#x30AF;&#x30B9;&#x306E;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;&#x767B;&#x58C7;&#x3057;&#x307E;&#x3059;" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="Vue-Fes-Japan-とは">Vue Fes Japan とは</h2> <p>Vue Fes Japan は Vue.js 日本ユーザーグループが主催する日本最大の Vue.js カンファレンスです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20230831/20230831162443.png" alt="Vue Fes Japan Online 2023 TOP&#x30DA;&#x30FC;&#x30B8;&#x306E;&#x30B9;&#x30AF;&#x30EA;&#x30FC;&#x30F3;&#x30B7;&#x30E7;&#x30C3;&#x30C8;" width="1200" height="673" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>日本における Vue.js 開発者たちによる発表や、Vue クリエイター の Evan You や、 Nuxt.js・Vite などのコアコントリビューターの方たちも登壇されます。</p> <p>昨年のオンラインカンファレンスを経て、今年は念願のオフライン開催が決定しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fvuefes.jp%2F2023%2F" title="Vue Fes Japan 2023" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://vuefes.jp/2023/">vuefes.jp</a></cite></p> <h2 id="登壇情報">登壇情報</h2> <p>弊社からは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスエンジニアの <a href="https://twitter.com/t0yohei">@t0yohei</a> と <a href="https://twitter.com/yamanoku">@yamanoku</a> と <a href="https://twitter.com/53able">@53able</a> それぞれ登壇いたします。</p> <h3 id="Vue-を使って-Grid-System-を実装した話">Vue を使って Grid System を実装した話</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20221004/20221004102602.png" alt="t0yohei" width="256" height="256" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <blockquote><p>自社のデザインシステムを構築していくにあたり、Vue を使って Grid System を実装する機会がありました。Grid System の実装は <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a> の media query における仕様上の問題もあり、中々上手く実装できないのが現状です。検討したいくつかの Grid System 実装パターンと、実際に採用したものをそれぞれのメリット・デメリットなど踏まえてお話しできればと思います。</p></blockquote> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fvuefes.jp%2F2023%2Fsessions%2Ft0yohei" title="t0yohei / トヨヘイ | Vue Fes Japan 2023" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://vuefes.jp/2023/sessions/t0yohei">vuefes.jp</a></cite></p> <p>メドピアトラックトラック(13:30 - 14:00)にて発表</p> <h3 id="画面遷移から考える-Nuxt-アプリケーションをアクセシブルにする方法">画面遷移から考える Nuxt アプリケーションをアクセシブルにする方法</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20221004/20221004102932.png" alt="yamanoku" width="256" height="256" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <blockquote><p>ブラウザ側で画面遷移を制御するクライアントサイドルーティングという手法はサーバーからの待ち時間を無くしスムーズに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D6%A5%E9%A5%A6%A5%B8%A5%F3%A5%B0">ブラウジング</a>できるようにして、画面遷移における認知負荷を減らす点で活用されています。 Nuxt 3.4 からは実験的に搭載された View Transitions <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a> の設定でスムーズな遷移アニメーションが実現できるようになりました。 しかし、これらの手法は情報が正しく伝わるかどうかの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B7%A5%D3%A5%EA%A5%C6%A5%A3">アクセシビリティ</a>観点から考慮が必要なものです。 本セッションでは、画面遷移時にどのように情報が伝わっているのか、Nuxt アプリケーションをよりアクセシブルにするためのアプローチを実装例を元に紹介します。</p></blockquote> <p>エムスリーやっていきトラック(13:30 - 14:00)にて発表</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fvuefes.jp%2F2023%2Fsessions%2Fyamanoku" title="やまのく | Vue Fes Japan 2023" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://vuefes.jp/2023/sessions/yamanoku">vuefes.jp</a></cite></p> <h3 id="SOLID-原則に基づくSFC-実装">SOLID 原則に基づく<a class="keyword" href="https://d.hatena.ne.jp/keyword/SFC">SFC</a> 実装</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cardboarder/20230831/20230831162332.png" alt="53able" width="300" height="300" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <blockquote><p>Vue.js の <a class="keyword" href="https://d.hatena.ne.jp/keyword/SFC">SFC</a>(Single File Component)の柔軟性と保守性を高めるための<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>設計にどのようなアプローチが効果的なのか検討します。 SOLID 原則に基づいて考察し、それぞれの原則を個別に検討し、Vue.js の <a class="keyword" href="https://d.hatena.ne.jp/keyword/SFC">SFC</a> にどのように適用するかを解説します。</p></blockquote> <p>メドピアトラック(15:45 - 16:05)にて発表</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fvuefes.jp%2F2023%2Fsessions%2F53able" title="53able / ゴー | Vue Fes Japan 2023" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://vuefes.jp/2023/sessions/53able">vuefes.jp</a></cite></p> <h2 id="おわりに">おわりに</h2> <p>今年はエンジニアの登壇ほか、Vue.js で開発をする<a href="https://crowdworks.jp/">クラウドワークス</a>の運営をする株式会社<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスから「<strong>同時通訳スポンサー</strong>」として協賛させていただいております。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">弊社 <a href="https://twitter.com/CrowdWorksjp?ref_src=twsrc%5Etfw">@CrowdWorksjp</a> も協賛しております!<br>今年の Vue Fes Japan 盛り上げていきましょう🙌 <a href="https://twitter.com/hashtag/vuefes?src=hash&amp;ref_src=twsrc%5Etfw">#vuefes</a><a href="https://t.co/swmHo0imWg">https://t.co/swmHo0imWg</a> <a href="https://t.co/fwk27a1GJB">pic.twitter.com/fwk27a1GJB</a></p>&mdash; オオヤマ オクト (@okuto_oyama) <a href="https://twitter.com/okuto_oyama/status/1678266941491019777?ref_src=twsrc%5Etfw">2023年7月10日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>セッションの他にもパネルディスカッション、ハンズオンといったイベントもあり、アフターパー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C6%A5%A3%A1%BC">ティー</a>では登壇する<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスのエンジニアも参加予定ですので皆さまとも交流できればと思っております。是非ご参加ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fvuefes.jp%2F2023%2F" title="Vue Fes Japan 2023" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://vuefes.jp/2023/">vuefes.jp</a></cite></p> cardboarder crowdworks.jpのマスタデータベースをAWS RDS MySQL 5.7から8.0にアップデートしました hatenablog://entry/820878482959436259 2023-08-30T12:00:00+09:00 2023-08-30T12:00:02+09:00 CrowdWorks.jpでは、2023年8月にAWS RDS MySQL 5.7から8.0へのアップデートが完了しました。 MySQL 8.0へのアップデートの手順と対応が必要な変更点について書きました。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20230823/20230823081849.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。crowdworks.jp SREチームの田中(kangaechu)です。</p> <p>crowdworks.jpでは、2023年8月に<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> RDS <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7から8.0へのアップデートが完了しました(ようやく!)。 今回は<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0へのアップデートの手順と対応が必要な変更点について書いていきます。</p> <h1 id="MySQL-80にアップデートした理由"><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0にアップデートした理由</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0にアップデートした理由は<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> RDS <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>のEOL対応のためです。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> RDS <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7のEOLは2023年10月(のちに2023年12月に変更されました)であり、8.0へのアップデートが必要でした。 crowdworks.jpで使用している他の<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>データベースは8.0へのバージョンアップを完了していました。 しかしcrowdworks.jpのマスタデータベースは30億行を保持し、1日に約4億クエリを処理しているため、バージョンアップには慎重になる必要がありました。 そのため、他のデータベースのバージョンアップで経験を積んだ後に実施する計画としていました。</p> <p>また、crowdworks.jpのマスタデータベースは長期間使われています。以下はサービス開始からの<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>の状況をまとめたものです。</p> <ul> <li>2012/3 crowdworks.jp サービス開始(<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A4%B5%A4%AF%A4%E9%A5%A4%A5%F3%A5%BF%A1%BC%A5%CD%A5%C3%A5%C8">さくらインターネット</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/VPS">VPS</a>を使用)</li> <li>2013/3 <a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>に移行</li> <li>2014/3 <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.5→5.6 アップデート</li> <li>2021/3 <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.6→5.7 アップデート</li> <li>2023/8 <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7→8.0 アップデート</li> </ul> <p>歴史的経緯のあるデータベースのため、他の<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>バージョンアップでは発生しないような問題が発生する可能性があったので、バージョンアップ作業を慎重に進めていきました。</p> <h1 id="変更概要">変更概要</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> RDSのアップデート (<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7.41 → 8.0.33)</p> <h1 id="手順">手順</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0への移行作業は2023/6-2023/8の2ヶ月半程度で実施しました。 その間にどのような作業を行なっていたのかを紹介します。</p> <h2 id="変更内容の把握影響調査">変更内容の把握・影響調査</h2> <ul> <li><a href="https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html">MySQL 8.0 での変更</a></li> <li><a href="https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html">MySQL 8.0 の新機能</a></li> <li><a href="https://dev.mysql.com/doc/relnotes/mysql/8.0/en/">MySQL 8.0 Release Notes</a></li> <li><a href="https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.MySQL.html">AWS RDS - MySQL DB エンジンのアップグレード</a></li> </ul> <p>まずはバージョンアップによる変更点を読みました。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%D7%A5%EC%A5%C3%A5%C9%A5%B7%A1%BC%A5%C8">スプレッドシート</a>に変更点を転記し、影響の有無について記入しました。 また、各環境でのパラメータグループのデフォルト値の変更についてもここで確認を行いました。</p> <p>ここで注意すべき点は2点です。</p> <ol> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0はセマンティックバージョニングではない<br> 2023/8現在、<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>は8.0.0から8.0.34までのバージョンがあります。 セマンティックバージョニングで見ると、パッチバージョンが上がっているように見えるのですが、バージョン間で破壊的変更やデフォルト値の変更が入っていることに気をつけましょう。</li> <li>英語ドキュメントを読もう<br> 日本語ドキュメントを読みたくなりますが、日本語版には全ての変更点が記載されていません。 英語版のドキュメントを使用し、日本語版を使用する場合は参考程度にとどめましょう。</li> </ol> <h2 id="移行方法の検討">移行方法の検討</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> RDSの移行方法として、 インプレースのアップグレードと<a href="https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/blue-green-deployments-overview.html">Amazon RDS Blue/Green Deployments</a> が使用できます。<a href="https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.MySQL.html">MySQL DB エンジンのアップグレード</a>にもある通り、Blue/Green Deploymentsはアップグレードにかかる停止時間を最小化できるためおすすめです。しかし、今回のアップグレードはインプレースでのアップグレードを選択しました。</p> <p>以前<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスのエンジニアブログ記事<a href="https://engineer.crowdworks.jp/entry/aws-dms">MySQLの約30億レコードをRedshiftにDMSでニアリアルタイム同期した</a>で紹介した通り、crowdworks.jpのマスタデータベースとRedshiftはニアリアルタイムで同期しており、その同期方法としてDMSを選択しています。 マスタデータベースはDMS用のRDS<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A5%D7%A5%EA%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3">レプリケーション</a>します。DMSはDMS用のRDS<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>をソースエンドポイント、Redshiftをターゲットエンドポイントとして継続的<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A5%D7%A5%EA%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3">レプリケーション</a>を行なっています。</p> <p>ここで問題となるのは、DMSが<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>のバイナリログを使用していることです。 DMSがどこまで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A5%D7%A5%EA%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3">レプリケーション</a>を行なったかの管理はバイナリログのポジションを使用しています。 Blue/Green Deploymentsを使用すると、ソース・レプリカ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>をグループとして入れ替えます。 Greenとして作成されたRDS<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>はバージョンアップ作業により、バイナリログのポジションが異なる可能性があります。 そのため、Blue/Green の切り替えを行なった場合、DMSはポジションを見失い、全ロードを実施する可能性があります。 それにより、Redshiftに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A5%D7%A5%EA%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3">レプリケーション</a>が追いつくまでRedshiftを使用した業務が止まることになります。 Redshiftには30億レコード程度のデータを同期しているため、同期が完了するまでに半日から1日程度かかります。 長時間の停止は許容できないため、インプレースのアップグレードを選択しました。</p> <p><figure class="figure-image figure-image-fotolife" title="RDS Blue/Green Deploymentsを使用できなかった"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20230818/20230818165548.png" width="656" height="235" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>RDS Blue/Green Deploymentsを使用できなかった</figcaption></figure></p> <h2 id="開発環境のアップデート">開発環境のアップデート</h2> <p>ローカル環境や検証環境を含め、順番に<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0へのアップグレードを実施しました。</p> <p>ここでハマったのは環境間でテーブル定義が異なることでした。 (こんな事象は他環境で発生しないと思うので読み飛ばしても大丈夫です)</p> <p>crowdworks.jpの開発環境は任意のブランチをデプロイできる仕組みとなっています。 そのため、開発環境でデータベースの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%B0%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3">マイグレーション</a>を含む修正をデプロイすると、データベースの状態がproduction環境と異なる状態が発生します。 そのため、開発環境では全てのテーブルを再作成し、本番環境を同等の状態を作り直しました。</p> <p>また、ローカル環境でも同様の事象が発生しました。 crowdworks.jpのローカル環境を自分のPCに構築した人には移行手順として、<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7でアンロードしたデータを<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0でロードするよう依頼をしたのですが、一部ユーザはテーブル定義の不整合によりロードに失敗していました。 アンロードしたデータを修正してもらうことで対応を行いました。</p> <h2 id="本番環境のアップデート">本番環境のアップデート</h2> <h3 id="移行計画の作成">移行計画の作成</h3> <p>開発環境のアップデートにより検証手順や時間を見積もれたので、まずは移行計画を作成しました。 このようなフォーマットで移行計画を記述し、レビューを行いました。</p> <pre class="code lang-markdown" data-lang="markdown" data-unlink><span class="synSpecial">#</span> 作業概要 <span class="synSpecial">##</span> 背景 <span class="synSpecial">##</span> 変更概要 <span class="synSpecial">##</span> 対象 <span class="synSpecial">##</span> 日時 <span class="synSpecial">#</span> エンドユーザから見たサービス影響範囲 <span class="synSpecial">##</span> 想定されるダウンタイム <span class="synSpecial">##</span> バージョンアップによる影響 <span class="synSpecial">#</span> 作業の全体的な流れ <span class="synSpecial">##</span> 事前作業 <span class="synSpecial">##</span> 当日作業 <span class="synSpecial">#</span> 想定されるリスク </pre> <h3 id="アップデート手順の作成">アップデート手順の作成</h3> <p>移行計画のレビューが完了したので、アップデートの具体的な手順を作成しました。</p> <h3 id="アップデート">アップデート</h3> <p>レプリカ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>は事前にアップデートし、マスタデータベースは深夜メンテナンスでアップデートしました。</p> <h1 id="対応が必要だった変更点">対応が必要だった変更点</h1> <h2 id="アップデート後に一部クエリのパフォーマンスが悪化">アップデート後に一部クエリのパフォーマンスが悪化</h2> <p>アップデート後に一部のクエリのレスポンスタイムが悪化しました。 (データベースサーバのCPU使用率が100%に張り付き、生きた心地がしませんでした)</p> <p>実行計画を確認したところ、 Extra: に <code>Backward index scan</code> とありました。また、使用するインデックスに以下の違いがありました</p> <ul> <li>変更前:フィルタするためのインデックスを選択</li> <li>変更後:ソートするためのインデックスを選択</li> </ul> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0では降順(Descending)インデックスの機能が追加されました。また<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AA%A5%D7%A5%C6%A5%A3%A5%DE">オプティマ</a>イザ自体にも変更が入っています。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0へのアップデートに伴い、実行計画が変わったと判断しました。 そのため、<code>optimizer_switch='prefer_ordering_index=off'</code> を設定し、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AA%A5%D7%A5%C6%A5%A3%A5%DE">オプティマ</a>イザの動作を変更したところ、レスポンスタイムの悪化が解消されました。</p> <p>パラメータの設定について一つ注意点があります。<code>optimizer_switch</code> パラメータは以下の3通りの設定方法が考えられます。</p> <ol> <li>GLOBAL変数</li> <li>RDS パラメータグループ</li> <li>SESSION変数</li> </ol> <p>しかし、実際に設定できるのは3. のSESSION変数のみです。1. GLOBAL変数は管理者ユーザであってもSUPER権限が付いていないため、エラーとなります。</p> <pre class="code lang-sql" data-lang="sql" data-unlink>MySQL &gt; <span class="synStatement">SET</span> GLOBAL optimizer_switch=<span class="synSpecial">'</span><span class="synConstant">prefer_ordering_index=off</span><span class="synSpecial">'</span>; ERROR <span class="synConstant">1227</span> (<span class="synConstant">42000</span>): <span class="synSpecial">Access</span> denied; you need (at <span class="synIdentifier">least</span> one <span class="synSpecial">of</span>) the SUPER <span class="synStatement">or</span> SYSTEM_VARIABLES_ADMIN privilege(s) <span class="synSpecial">for</span> this operation </pre> <p>また、2. のRDSパラメータグループで<code>optimizer_switch</code> パラメータを設定する場合、パラメータに設定できるキー名に制限があり、 <code>prefer_ordering_index</code> を指定するとエラーになりました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kangaechu/20230823/20230823134348.png" width="1200" height="617" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <a href="https://gihyo.jp/dev/serial/01/mysql-road-construction-news/0135#sec2_h1">&#x7B2C;135&#x56DE; MySQL 8.0&#x3067;&#x8FFD;&#x52A0;&#x3055;&#x308C;&#x305F;optimizer_switch&#x306E;&#x30D5;&#x30E9;&#x30B0;&#x306B;&#x3064;&#x3044;&#x3066; | gihyo.jp</a></p> <p>crowdworks.jpでは<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>を使用しているため、<code>config/database.yml</code> にvariablesとして設定しました</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">default</span><span class="synSpecial">:</span> <span class="synType">&amp;default</span> <span class="synIdentifier">adapter</span><span class="synSpecial">:</span> mysql2 <span class="synIdentifier">variables</span><span class="synSpecial">:</span> <span class="synIdentifier">optimizer_switch</span><span class="synSpecial">:</span> <span class="synConstant">'prefer_ordering_index=off'</span> </pre> <h2 id="行フォーマットが古い場合1行に8126バイトを超えるレコードを保存できない">行フォーマットが古い場合、1行に8,126バイトを超えるレコードを保存できない</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/InnoDB">InnoDB</a>では、デフォルトの1レコードのサイズは8,126バイトです。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/ActiveRecord">ActiveRecord</a>では、カラムのデータ型としてstringを使用すると、<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>では <code>VARCHAR(255)</code> のカラムとして扱います。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>セットにutf8mb4を使用している場合、255 * 4 = 1,020バイトとなります。 そのため、8,126/1,020 = 8 なので、8個のstring型を持つモデルはエラーになるのでは?と思ってしまいますが、そうではありません。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>は可変長カラム値 (VARCHAR、VARBINARY、BLOB、TEXT)を保存する場合、行フォーマット(<code>innodb_default_row_format</code>)によって保存の仕方が異なります。</p> <ul> <li><strong><code>REDUNDANT</code> / <code>COMPACT</code> :</strong> 最初の768バイトをレコード(Bツリーノード内のインデックスレコード)に保存、超えたものは別の場所(オーバーフローページ)に保存</li> <li><strong><code>DYNAMIC</code> / <code>COMPRESSED</code> :</strong> 20バイトのポインタをレコードに保存、実体は別の場所に保存</li> </ul> <p><code>innodb_default_row_format=DYNAMIC</code> がデフォルトになったのは<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7から。<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.5から使用しているcrowdworks.jp のテーブルの行フォーマットは <code>COMPACT</code> を使用しています。 行フォーマットが <code>COMPACT</code> の場合、8,126/768 = 10.5 であり、 <code>VARCHAR(255)</code> が10個を超えると保存に失敗する可能性がありました。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0からは1行に8,126バイトを超えるテーブルを作成すると、</p> <ul> <li>アップデート時にワーニング</li> <li>新規作成時にエラー</li> </ul> <p>となります。 crowdworks.jp ではそのようなテーブルが存在しました。 そのため、これらのテーブルについて<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0アップデート前に <code>ALTER TABLE example ROW_FORMAT=DYNAMIC;</code> を実行し、行フォーマットを <code>DYNAMIC</code> に変更しました。 (全テーブルの行フォーマットを <code>DYNAMIC</code>に変換したかったのですが、約1日かかることが判明したため、アップデート作業と別に実施する予定です)</p> <p><a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-row-format.html">InnoDB Row Formats</a></p> <h2 id="BINARY演算子の削除">BINARY<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B1%E9%BB%BB%BB%D2">演算子</a>の削除</h2> <p>BINARY<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B1%E9%BB%BB%BB%D2">演算子</a>は文字セットや照合順序に関わらず、バイナリでの比較などを行うために使用します。 <code>SELECT * FROM CITY WHERE Name = BINARY 'Kabul';</code> のように使う感じですね。 BINARY<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B1%E9%BB%BB%BB%D2">演算子</a>は8.0.27で削除されました。 こんな<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B1%E9%BB%BB%BB%D2">演算子</a>は使ってないだろうと思いながらコード検索をしたところ、1箇所だけ使用していました。 現在は削除しても影響がなさそうだったので、コード自体を修正しました。</p> <p><a href="https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-27.html">MySQL :: MySQL 8.0 Release Notes :: Changes in MySQL 8.0.27 (2021-10-19, General Availability)</a></p> <h2 id="特定の文字列をTIMESTAMP型にパースするとエラー">特定の文字列をTIMESTAMP型にパースするとエラー</h2> <p>外部サービスから受信した <code>2006-01-02T15:04:05.000+0000</code> のようなタイムスタンプっぽい文字列を<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>でTIMESTAMP型にパースしたところ、<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0ではエラーになりました。 <code>2006-01-02T15:04:05.000+00:00</code> では問題ありませんでした。 MySQL8.0.19でTIMESTAMP型へのパース処理が変更されたことによる影響です。 一度<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>でTime型に変換し、それを文字列型に変換するようにコードを修正しました。</p> <p><a href="https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-19.html#mysqld-8-0-19-feature">MySQL :: MySQL 8.0 Release Notes :: Changes in MySQL 8.0.19 (2020-01-13, General Availability)</a></p> <h2 id="予約語に-rank-が追加"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%CD%BD%CC%F3%B8%EC">予約語</a>に <code>rank</code> が追加</h2> <p>列名などで使われることがありそうな <code>rank</code> という単語が<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0から<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CD%BD%CC%F3%B8%EC">予約語</a>に追加されました。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/ActiveRecord">ActiveRecord</a>を使っていれば、テーブル名や列名はバックティックを前後に付与してくれるため、特に問題はありません。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/SQL">SQL</a>文に列名として <code>rank</code> が使われていたところがあったので修正しました。</p> <p><a href="https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-2.html">MySQL :: MySQL 8.0 Release Notes :: Changes in MySQL 8.0.2 (2017-07-17, Development Milestone)</a></p> <h1 id="これからやりたいこと">これからやりたいこと</h1> <h2 id="Amazon-RDS-BlueGreen-Deployments-を使えるようにしたい"><a class="keyword" href="https://d.hatena.ne.jp/keyword/Amazon">Amazon</a> RDS Blue/Green Deployments を使えるようにしたい</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Amazon">Amazon</a> RDS Blue/Green Deploymentsは切り替えにかかる時間が短い・Green側への変更が可能などと便利なため、積極的に使っていきたいです。 行フォーマットの変更、utf8mb4への移行など使いたい場面は多くあります。 今回の移行ではEOLまでの時間が迫っていたためBlue/Green Deploymentsの使用を断念しましたが、環境を整えて変更しやすい構成への移行を行っていきたいと考えています。</p> <h2 id="Aurora化">Aurora化</h2> <p>crowdworks.jpの一部データベースではAuroraを使用していますが、マスタデータベースはRDSを使用しています。 パフォーマンス・可用性・耐障害性の観点からAuroraへの移行を検討しています。</p> <p>また、AuroraとRedshiftとのゼロ ETL 統合(パブリックプレビュー)を使用することにより、DMSでの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A5%D7%A5%EA%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3">レプリケーション</a>を廃止することも可能です。 シンプルで運用負荷の少ない構成を目指したいと考えています。</p> <h1 id="まとめ">まとめ</h1> <p>crowdworks.jp ではマスタデータベースを<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7から8.0へ移行しました。 移行に際して必要な対応がいくつかありましたが、適宜対応を行い、特に問題なく稼働しています。 今後もcrowdworks.jpのサービスを安心・安定してご利用いただけるよう、改善を進めていきます。</p> <h1 id="Were-hiring">We're hiring!</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスはたのしいよ!みんなはいってね!</p> <p><a href="https://crowdworks.co.jp/careers/">https://crowdworks.co.jp/careers/</a></p> kangaechu クラウドワークスに入社し、 3ヶ月経って感じたこと hatenablog://entry/820878482956236327 2023-08-08T10:00:00+09:00 2023-08-08T10:00:00+09:00 クラウドワークスにエンジニアとして入社し、 3ヶ月経って感じたことをまとめてみました。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/mari-su/20230807/20230807165507.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスにエンジニアとして入社し、3ヶ月くらい経過した鈴木というものです。 今回エンジニアブログを書く機会が遂にやってきましたので、まだまだ短い期間ではありますが入社して感じたことを書いてみます。</p> <h2 id="良い意味で驚いた所">良い意味で驚いた所</h2> <h3 id="全社朝会を通して知るクラウドワークス">全社朝会を通して知る<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークス</h3> <h4 id="全社朝会とは">全社朝会とは</h4> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスでは毎週月曜日に全社朝会というものが開催され、社員が主体となりチームや部署を越えて成果の発表、共有が行われます。普段関わりのない人達が何をしているのかWeb 会議で共有され、またそれらの成果に対して経営陣からコメントを貰える場になっています。</p> <p>オンラインで数百名が参加する朝会ですが、ただ結果報告を聞く様な<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B7%F8%B6%EC%A4%B7%A4%A4">堅苦しい</a>場ではありません。 全社朝会は社員主体のイベントであり、誰でもコメントを通じて参加しやすい形となっているため、一方的に偉い人の話を聞いて終わるような会ではないことが大きなポイントだと思います。</p> <p>というのも、朝会専用のコメントチャンネルがSlack上にあり、そのチャンネルにコメントを書くとほぼリアルタイムでオンラインの映像に反映されます。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CB%A5%B3%A5%CB%A5%B3%C6%B0%B2%E8">ニコニコ動画</a>で見かけるあのコメント形式が、名前付きで縦に流れるイメージ(<a class="keyword" href="https://d.hatena.ne.jp/keyword/YouTube">YouTube</a>のコメント付き配信と同じ形式)と言うと、参加しやすい雰囲気が伝わるでしょうか。とても参加しやすいです。</p> <p><figure class="figure-image figure-image-fotolife" title="全社朝会の様子:モザイクをかけていますが、右端に見えるのがコメントです"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/mari-su/20230807/20230807165749.png" width="693" height="404" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>全社朝会の様子:モザイクをかけていますが、右端に見えるのがコメントです</figcaption></figure></p> <p>ちょっとした意見なども記入するとその場で沢山のリアクションが付き、時にはスタンプが経営陣からつくこともあり、経営陣との距離の近さを感じられるのも全社朝会の良い所だと思います。</p> <p>コメントを書くというのは小さな事ですが、小さな事でも自発的な参加を通して、より対象について興味を持ち、より強く当事者意識や連帯感を持つことができる、朝会自体がそんな仕組みになっているように思います。</p> <p>私は自身をどちらかと言うと引っ込み思案な性格だと思っており、この様な明るい場はむしろ苦手としていましたが、盛り上がると面白く、そんな私ですらついコメントをしたくなります。</p> <p>ただ、無理に盛り上がる必要もないので私のような方もご安心いただきたいのと、とても参加しやすいですが真剣な意見、発表が主な場ですので決してふざけている訳ではなく、フランクかつ真剣という二つが同時に成り立っている場である、ということは念の為付け加えておきます。</p> <p>フランクな雰囲気なので参加しやすく、参加しやすいから他部署の事例であっても当事者意識を持ちやすく、当事者意識を持ちやすいので真剣になり、フランクなのでまたついコメント参加してしまう…といい感じに回っているように見えます。</p> <h4 id="エンジニアによる発信">エンジニアによる発信</h4> <p>エンジニアの取り組みについて取り上げられることも多々あり、エンジニアだけが盛り上がる場と化すのかと思いきや、経営陣は勿論、他部署の方からもしっかりコメントが貰えます。</p> <p>エンジニア側も他の職種の方に分かりやすく伝えることをよく意識しており、内輪で閉じず外へもしっかり発信し、営業やコーポレート、エンジニアと互いを尊重し合うという価値観が浸透しているように見受けられます。</p> <p>会社によっては特定の部署が強すぎるがために、その他の部署の取り組みや努力について光が当たらない、という環境もあるのではないかと思いますが、そういった気風は(少なくとも私が知る限り)あまり感じられません。</p> <h4 id="問題に対してオープン">問題に対してオープン</h4> <p>また、テーマとして上がる内容は時に問題改善だったりもするのですが、改善した、と発表をすることは、つまりそこに問題があったことを認めることでもあるので、問題について「隠すべきことだ」と社員が感じているのではなく「改善し成果として出していくべきことだ」と感じていると言えます。</p> <p>入社以来、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスは「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%B4%CD%FD%C5%AA">心理的</a>安全性の高い組織」の特徴の多くを備えていると実は個人的に思っているのですが、上記がまさに典型的な例の一つです。</p> <p>貶され、責められる心配をしていないから発表できる。問題は日に当ててちゃんと浄化する。真っ当です。</p> <p>全社員が見ている公式的な場で問題改善について発表し、そこに賞賛コメントや意見が飛び交う様を見ていると、いつも爽やかな気持ちになります。</p> <h3 id="POがいる有り難み">POがいる有り難み</h3> <p>恥ずかしながら、私はPO(プロダクトオーナー)という役割の方に入社するまで接した事がありませんでした。実際に一緒に業務に取り組んでみて、本当にありがたいと感じました。</p> <p>他部署、他チームの方と同じ方を向いて進めるようコミュニケーションをとりつつ、しっかりと開発に落とし込むということは難易度が高く、時に開発(コーディング)そのものよりも要件整理や方針決定のためのコミュニケーションにかかるコストの方が高いこともあるのではないかと思います。</p> <p>開発に集中していると視野が狭くなってしまう事もあるかと思いますが、POによる顧客視点や他部署からの視点も交えた意見によって、ふと大きな視点を取り戻す、方向が修正される、バランス感覚を取り戻す、そんなシーンをよく見かけます。</p> <p>中にはPOもエンジニアもできてしまう、超人のような方もいるかもしれませんが、責任範囲を分割し互いのポジションに集中して取り組むからこそスピードや品質、ゴール達成に貢献できるのではないでしょうか。</p> <p>POという役割について知るにあたって参考になった記事を紹介します。読んでいただくと分かるかと思いますが、いわゆるPO的な業務だけではなく粒度大きめの視点で業務を捉え直すなどしていたり、普段の業務を通して接する姿とはまた別に、幅広く取り組んでいるんだなあ、やる事多いなあ、と参考になりました。</p> <p><a href="https://qiita.com/e-takazawa/items/67c2c6a558687496c2b2">プロジェクトマネージャーからプロダクトオーナーにジョブチェンジした話</a><br> <a href="https://engineer.crowdworks.jp/entry/team-join-po">チームにプロダクトオーナーが加わり変化したこと</a><br> <a href="https://engineer.crowdworks.jp/entry/20221214-advent-calendar-2022-product-management">プロダクトマネジメント組織メンバーみんなでPOの役割を考え直してみた</a><br></p> <p>初めて接するポジションということもあり業務内容などまだまだよくわからない点も多く、理解を深めていく努力が必要と感じることもあります。 楽させてもらうだけでなく、サポート出来るよう頑張っていきたいなと思います。</p> <h3 id="言語能力の高い人が多くて驚く">言語能力の高い人が多くて驚く</h3> <p>まずは、そう思うきっかけとなった記事のリンクからご紹介します。これらの記事を読んで「言語能力たかー」と心の中で呟きました。</p> <p><a href="https://zenn.dev/tmknom/articles/93f227ad5e55aa">「無人化システム」を駆逐する組織マネジメントとエンジニアリング</a><br> <a href="https://engineer.crowdworks.jp/entry/20201213-advent-calendar-2020-product-management">サービスの成長モデルを定義し、「森も見ながら木を見る」プロダクトマネジメントへ</a><br></p> <p>発掘できていないだけで他にもまだまだ沢山あるはずです。</p> <p>複雑であったり、あるいは抽象度が高い、粒度が大きい、組織横断的、構造的な問題について、そこに問題があることを認識し、綺麗に分解して、分かりやすい言葉にして伝えることが得意な方が多いと感じています。 (当然のことながら、伝えるだけでなく行動し成果も出ている事と思いますが、私視点ですとその辺りはまだ大きくて見えてこず言葉にできる段階ではないため割愛します)</p> <p>特に読んでいて<a href="https://zenn.dev/tmknom/articles/93f227ad5e55aa#%E5%95%8F%E9%A1%8C%E3%81%AE%E7%9F%AE%E5%B0%8F%E5%8C%96%E3%81%AB%E3%82%88%E3%82%8B%E5%BD%93%E4%BA%8B%E8%80%85%E6%84%8F%E8%AD%98%E3%81%AE%E6%AC%A0%E5%A6%82">この辺り</a>は、「言葉にして世に出してもらってありがとうございます、そうですよね…」と頷きながら、ふと浮かばれなかったプロジェクトやエンジニアさん達が頭をよぎりました。</p> <p>入社前、技術的負債を解消するチームがある、ということを知り、その言葉の意味と意義が通じちゃんとリソースが投下される組織とはIT<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%B7%A1%BC">リテラシー</a>が高く、体力のある組織だ、と驚きました。 そして入社後、「説明が上手な人と、理解力のある人が多いからか!」とすごく単純に納得しました。</p> <p>勿論それだけが理由とは思っておらず、様々な取り組みや苦い失敗などを繰り返して獲得した環境や体制だとは思いますが、そもそもシンプルに話の上手な人、話のわかる人が多い印象を持っています。</p> <p>今回はブログという形式上、例として出し易いため、同じ文章ベースであるブログを取り上げていますが、普段の何気ないやり取りでもよく気付く、舵取りが上手い、物事が上手く回るよう自身の役割を心得た上で動いている、すごいなあ…と感じる人が多いです。</p> <p>賢く優しい人達の作る良い流れに乗せてもらっている、としみじみ感じております。</p> <h2 id="苦労した所">苦労した所</h2> <p>ここまで良いと感じた点について書いてきましたが、入社してみてこれは少し難しいな、と感じた所も書いてみます。</p> <h3 id="フルリモートのちょっと困る所">フルリモートのちょっと困る所</h3> <p>フルリモートは素晴らしいです。ご飯をゆっくり作ることができるようになりました。</p> <p>ただ、出社して近くに座っていれば、パッと気軽に聞ける、話せることも少しハードルが上がります。 こんな事聞いていいのだろうか、自分の質問がSlack上に残り続けてしまうのか、誰も見ないで!と若干躊躇います。</p> <p>また、出社しても<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%A2%A5%C9%A5%EC%A5%B9">フリーアドレス</a>のためエンジニアが固まって座るということがなく、「あの人はあのSlackのアイコンの人のような気がする…でも確信がないから話しかけられない…」となったこともありました。 とある人がSlackのアイコンを皆首から下げてはどうか、と言っており、妙案だと思いました。</p> <h3 id="システムの規模">システムの規模</h3> <p>入社前、もう少しシンプルな仕組みを想像していましたが、知れば知るほど複雑でした。周囲が何を言っているか全然わからなくても焦らない心が必要かと思います。 また、社内に溜まっているQiita記事やSlackをとにかく検索する、一見自分に関係ないような話題でも耳を澄まして聞く、他チームの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>巡回の習慣を意識的に取り入れていくなど、意識的に知識を増やすための努力をする必要があると思っています。</p> <h2 id="おわりに">おわりに</h2> <p>他にももっと詳細な点について取り上げたいことはあったのですが、業務内容に直接関連してしまったり個人名を出すことになってしまうため泣く泣くお蔵に入れました。 書ききれなかった事も多いですが、この記事を通して少しでも働き易い環境であることが伝われば幸いです。</p> <h2 id="Were-hiring">We're hiring!</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークス では、働き方の変革に挑戦するエンジニアを募集しています! 個性豊かで、愉快なエンジニアがお待ちしています。まずは気軽にお話してみませんか?</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F73940" title="400万会員以上❘上場人材IT働き方変革に挑戦するRailsエンジニア募集 - 株式会社クラウドワークスのWebエンジニアの採用 - Wantedly" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/73940">www.wantedly.com</a></cite></p> mari-su チームのメンバー変更が良い機会だったので、スクラム改善を進めた話 hatenablog://entry/820878482953474457 2023-07-28T18:27:53+09:00 2023-07-28T18:27:53+09:00 crowdworks.jpでEMをしている久村です。 2023年5月より1つのチームのスクラムマスターをすることになりました。 私は開発者としてスクラムの経験はありましたが、スクラムマスターの経験はなかったです。 この記事では、直近チームで改善したことの内容と、そこからの学びを記事にしようと思います。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728175553.png" width="835" height="440" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> crowdworks.jpでEMをしている久村です。</p> <p>2023年5月より1つのチームの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>マスターをすることになりました。 私は開発者として<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>の経験はありましたが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>マスターの経験はなかったです。</p> <p>この記事では、直近チームで改善したことの内容と、そこからの学びを記事にしようと思います。</p> <h2 id="チーム説明">チーム説明</h2> <p>私が<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>マスターとして関わっているチームは以下です。</p> <p>エンジニ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%F3%A5%D0%A1%BC">アメンバー</a>の異動・退職があり人数が減少し、プロダクトオーナー(PO)もメインとサブ(入社年次が浅い)で2名いましたが、サブの方が独り立ちしてメインで担当されるように変更になりました。 そのタイミングで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>マスターとして私がメンバーに加わりました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728175503.png" width="1200" height="650" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>メンバー変更した当初の状況としては、1週間のスプリント期間で以下の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>イベントは実施しておりました。</p> <ul> <li>デイリー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a></li> <li>リファインメント</li> <li>レトロスペクティブ</li> <li>プランニング</li> </ul> <p>この前提の元、チームとして改善したことを共有できればと思います。</p> <h2 id="改善したこと">改善したこと</h2> <h3 id="スプリントゴールへの意識改善">スプリントゴールへの意識改善</h3> <p>元々プランニングの中で、次回のスプリントで行うタスクの整理を行い、その中でも特に重要なタスクには「今週の目標」ラベルをつけることはやっており、意識できていないわけではなかったです。</p> <p>ただ、「今週の目標」が達成できた要因や、達成できなかった要因の深掘りができているかと言うとそうではなく、コミットメントも弱い状況でした。</p> <h5 id="どのように改善したか">どのように改善したか?</h5> <p>レトロスペクティブの中で、スプリント評価を行うようになりました。 これは組織的(crowdworks.jpのエンジニア組織)な取り組みであり、人事評価にも紐づけるものです。 スプリントレビューと近しいところはありますが、あえてスプリント評価として別イベントとして扱っております。</p> <p>具体的には以下の内容をチームで確認しています。 スプリントゴールの確認だけであれば、「スプリントゴールが達成できたか?」だけで良いと思いますが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>をより円滑にすることも意識したいと思い、質問を増やしました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728175638.png" width="904" height="453" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>評価をそれぞれで記入し、内容を共有する運用にしています。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728175654.png" width="1200" height="284" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>レトロスペクティブだとチーム・個人に関わる幅広い関心ごとを扱いますが、スプリント評価ではスプリントゴールが達成できたかを中心に振り返ることができます。</p> <p>実際にメンバーからも以下のフィードバックを得られており、良い傾向に感じます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728180628.png" width="777" height="205" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こちらの評価をする前に、<a class="keyword" href="https://d.hatena.ne.jp/keyword/KPT">KPT</a>での振り返りを行っており、それを踏まえて、最後にスプリント評価を行うようにしています。ミーティング時間は多少長くなりましたが、必要な時間だと判断しております。</p> <p>スプリント評価を行うことで、スプリントゴールへの意識は強くなっていると感じています。</p> <h3 id="バックログ管理の改善"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>管理の改善</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>の管理にはtrelloを使用していました。 運用上大きな問題はなかったのですが、タスクの親子関係が作れない点や、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>が煩雑になっており、より良いやり方を検討する余地がありました。</p> <h5 id="どのように改善したか-1">どのように改善したか?</h5> <p>trelloからnotionへ移行しました。 利点としては柔軟なタスク管理ができるようになります。親子関係の管理はもちろん、ボードビューなど見せ方も調整することができます。</p> <p>notionへの移行に伴い、プロダクト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>とスプリント<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>を別のボードで管理するようにしました。その際に、アイテムの粒度は以下を意識して管理するようになりました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728175737.png" width="1200" height="700" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>まずはプロダクト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>の説明です。 上記の図でいう、Epic、Feature、User Storyを管理しています。</p> <p>プロダクト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>のボードビューではUser Storyをカード化して管理しています。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728175754.png" width="1200" height="553" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>直近だと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>の対応を行っており、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>対応をEpic、Epicの分解としてFeature、Featureの分解としてUser Storyにしています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728175809.png" width="1200" height="372" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>プロダクト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>はチームの所有物として扱っています。 優先度の判断はPOが行っております。</p> <p>もともとプロダクト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>のボードがなかったので、タスクの状態・ステータスは可視化できていたが、プロダクト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>アイテム(PBI)ごとの状態・ステータスは可視化できていなかったです。 今回プロダクト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>を作ることで、PBIごとの状態、ステータス、優先度を視覚的に把握しやすくなりました。それにより、数スプリント先を見通しやすくなることで、どのPBIまでを詳細化しておくべきかを把握しやすくなりました。</p> <p>またPBIの粒度が大きく、スプリントレビューを導入しづらい状況でもありました。 PBIの粒度をEpicレベルからUser Storyレベルへ細分化し、スプリント毎に1つ以上のUser StoryをDoneにすることを目指せる土台をつくり、スプリントレビューを実施しやすい状態にも改善できました。</p> <p>次はスプリント<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>の説明です。 スプリント<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>は、スプリントで扱うタスク管理をメインで行っております。 タスクはUser Storyに紐づけて管理しています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728175832.png" width="1200" height="550" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ボードビューと別にテーブルビューも作ることで、ポイント計算もしやすくなりました。 今までは手動でポイントを計算していたのも、自動で計算できるようになり、運用の改善につながっています。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728175849.png" width="1200" height="609" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>スプリント<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>は開発者がメインで管理しているものです。</p> <p>スプリント期間中に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>を見る機会は多く、見やすい<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>であることは重要だと感じます。 今回の改善により、タスクの状態や今後やりたいことが把握しやすくなっています。</p> <h3 id="ベロシティ信頼度の改善">ベロシティ信頼度の改善</h3> <p>ベロシティの計測は行っていたのですが、そこに対する納得感が低い状態でした。 メンバーからも「ベロシティの値への納得感が低い」や、「タスクポイントの基準が曖昧」などの意見がありました。</p> <p>その状況では、適切な量のプランニングは難しく、積んだタスクへの納得感がないと、やり切るという意欲も弱くなりやすい状況でした。</p> <h5 id="どのように改善したか-2">どのように改善したか?</h5> <p>タスクポイントの基準見直しと、スプリント期間中でのポイント変更を行いました。</p> <h6 id="タスクポイントの基準見直し">タスクポイントの基準見直し</h6> <p>今までは0.5,1,2,3でポイントをつけることが多く、その中でも0.5,1でポイントづけすることが多かったです。 その結果、0.5よりも分解したいタスクが発生することもあり、以下のような課題感に繋がっておりました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728180907.png" width="690" height="146" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>そこでタスクポイントを見直すミーティングを行い、基準値のすり合わせを行いました。 まずはポイントの考え方として、ポイントは時間見積もりでない、ポイントは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C1%EA%C2%D0%C9%BE%B2%C1">相対評価</a>であることを共有し、その上でポイントの基準合わせを行いました。</p> <p>具体的には、タスク分割できる数を目安にポイントの基準を検討しました。 例えばvueファイルの実装タスクの場合は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/css">css</a>の実装と<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>作成の2つに分割できるので2ptという感じで基準を作りました。 タスクの難易度も考慮すると、基準合わせの時間が膨大になってしまうと感じたので、簡単・一般的なタスクを想定した場合に絞りました。(難しめのタスクの見積もりの時は、その都度会話で認識を合わせる運用にしております。)</p> <p>ポイントの付け方も0.5をなくし、1,2,3,5,8でつけるように変更しました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728175928.png" width="685" height="340" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>FBとしても以下の感想があり、メンバーの納得感も高いと感じます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728175941.png" width="1200" height="109" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h6 id="スプリント期間中でのポイント変更">スプリント期間中でのポイント変更</h6> <p>実際にやってみたら想定よりもポイントが大きかった、小さかったは起こります。 その際には、増加ポイント・減少ポイントを入力してポイントを調整しております。</p> <p>下の画像の例だと、見積もり時は3ptでしたがやってみたら1pt程度の内容だったため、減少ポイントに2を追記しております。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728180003.png" width="1200" height="348" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>タスクポイントの基準見直しと、スプリント期間中でのポイント変更を行い、ベロシティ信頼度の改善を行なっております。</p> <h3 id="プロダクト理解の改善">プロダクト理解の改善</h3> <p>レトロスペクティブでどのようにするとうまく開発できるかの振り返りはできていました。 しかし、何を作ると良いかや、ユーザーはどう思っているかというプロダクト理解についての学習はなかなか進められていませんでした。</p> <h5 id="どのように改善したか-3">どのように改善したか?</h5> <p>評価指標を決める会とスプリントレビューを行いました。</p> <h6 id="評価指標を決める会">評価指標を決める会</h6> <p>KPIに直接的にヒットしない施策でも、リリース後に必ず全員で施策評価、振り返りを実施できる仕組みをつくること目的に評価指標を決める会を実施しました。</p> <p>指標を決める際に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C4%EA%CE%CC">定量</a>的な指標だけでなく、定性的な指標も含めてチームで納得感のある指標にすることを意識しました。</p> <p>具体的には以下の内容をチームで会話し、指標を決定しました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728180031.png" width="726" height="494" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>4つの観点を軸に、それぞれで意見を出し、それを集約させる形で指標を定めました。 直近の施策では以下の項目を定めております。</p> <p>(直近の施策として、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%DC%A5%A4%A5%B9">インボイス</a>発行事業者登録番号の確認機能開発を行っておりました。<a href="https://blog.crowdworks.jp/archives/5339/">https://blog.crowdworks.jp/archives/5339/</a> )</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728180048.png" width="1083" height="382" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この場を設けることで、施策についてより考えることができました。 具体的に「どうなっていたら嬉しいか?」を考えるためには、施策の背景・目的を理解する必要があり、プロダクトの理解を深める効果があったと感じます。</p> <p>現在施策は取り掛かり中ですが、施策リリース後にはこの視点での振り返りを行い、プロダクトに対しての学びをより深めたいと考えております。</p> <h6 id="スプリントレビュー">スプリントレビュー</h6> <p>もう一つはスプリントレビューの実施です。 今までこの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>イベントは実施できていなかったです。</p> <p>実施するにあたり、まずは以下の目的を共有しました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728180111.png" width="756" height="649" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>その後、ユーザーストーリー(= スプリントゴール)の説明、デモの実施、デモ内容についての質問、感想共有という流れでスプリントレビューを実施しました。</p> <p>メンバーからも以下のフィードバックをもらえており、スプリントレビューの有用性を感じられております。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/murasahi/20230728/20230728181150.png" width="755" height="187" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>レトロスペクティブや、スプリント評価ではプロダクト自体に関しての学びは少なかったので、その視点での理解を深める場にできていると感じます。</p> <h2 id="改善を通じて学んだこと">改善を通じて学んだこと</h2> <h3 id="スプリントゴールの意識づけがスクラム改善の起点">スプリントゴールの意識づけが<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>改善の起点</h3> <p>上記の改善でも書きましたが、スプリントゴールの意識づけが<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>改善を進める上でのポイントだと感じました。 この意識が持てることで、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>イベントの目的や<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>理論を理解しやすくなりました。</p> <p>スプリントゴールを達成するために、デイリー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>では検査を行うことが重要であり、進捗共有にならないようにすることを理解できました。 加えて、検査するためには透明性がないといけないことから、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>管理やチームで発言しやすい状況(= <a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%B4%CD%FD%C5%AA">心理的</a>安全性)を作る必要性を理解しました。</p> <p>またゴールが意識できていたからこそ、スプリント期間内でゴールが達成して時間が余ったらどうする?という点もやっていく中で会話することができ、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>への理解を深められていると感じます。 (その際は以下を参考に、勉強、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%AF%A5%BF%A5%EA%A5%F3%A5%B0">リファクタリング</a>、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>リファインメントといった活動に時間を使う選択肢を持つことができるようになりました。<a href="https://www.ryuzee.com/faq/0054/">https://www.ryuzee.com/faq/0054/</a> )</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>の理解が深まることで、改善のア<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%C7%A5%A2">イデア</a>は増えると感じます。その起点としてスプリントゴールの意識づけがポイントだと思います。</p> <h3 id="型にこだわり過ぎない">型にこだわり過ぎない</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>ガイドに書いてあることをまずは試してみたいと思っていましたが、それをやろうとすると大きく変えなくてはいけないこともあります。 まずはできるところから変えていき、時間をかけて<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>ガイドのようにしていくのも良いと感じました。</p> <p>具体的にはスプリントレビューをしたいとなった時です。 スプリントレビューをするには、まずはスプリントゴールを立てる必要があり、スプリントゴールの粒度はユーザーに価値が届けれるアウトプットにする必要があります。それを実現するには、現状の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>の粒度を変更する必要がありと、やりたいことに対して先にやらないといけないことが複数ある状態でした。</p> <p>それらを改善してからスタートをするのもありだと思いますが、そうするとアクション開始に時間がかかってしまいます。</p> <p>まずはタスクの列挙でも良いので、それをスプリントゴールと定めて、改善サイクルを回すのも一つの手段ということを学びました。</p> <p>今回のチーム改善の場合でも、スプリントゴールの意識づけとユーザーストーリー単位でのアイテム管理が可能になったことから、スプリントレビューも実施しやすい状態になっておりました。</p> <p>スプリントを回す中で、あるべき姿に近づけていくことも大事だと思い、最初の段階で型にこだわり過ぎないことも大切だと感じました。</p> <h2 id="まとめ">まとめ</h2> <p>直近チームで改善したことをまとめました。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>ガイドに書いてあることを理解しているつもりでも、実際にやることでより理解が深められました。</p> <p>今回の改善を通じて、形としてプロセスを改善するのではなく、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>の理論を意識して改善することが大事だと感じました。</p> <p>上記で挙げた改善は私が一人でやったものではなく、チームメンバーと一緒に改善してきたものです。 引き続き、チームで改善していければと思います。</p> <p>最後になりますが、crowdworksでエンジニアを募集しております。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>改善に興味がある方も是非です!</p> <p><a href="https://herp.careers/v1/crowdworks/requisition-groups/e282141a-0141-48e3-ae69-bde2f5122b1f">https://herp.careers/v1/crowdworks/requisition-groups/e282141a-0141-48e3-ae69-bde2f5122b1f</a></p> murasahi tfupdateで複数の.terraform.lock.hclを高速に一括更新する hatenablog://entry/820878482950852633 2023-07-18T17:50:11+09:00 2023-07-18T17:50:11+09:00 tfupdate v0.7から、tfupdate lockコマンドが追加され、.terraform.lock.hclの更新に対応しました。この記事では、.terraform.lock.hclとは何かについておさらいした後、これまでの技術的な課題とその解決策について説明します。また、実装する上で調べた.terraform.lock.hclに記録されているハッシュ値の計算方法についても解説します。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/minamijoyo/20230718/20230718170037.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="はじめに">はじめに</h2> <p>Terraform職人の<a href="https://twitter.com/minamijoyo">@minamijoyo</a>です。先日、tfupdateが.terraform.lock.hclの更新に対応しました。v0.7.0から tfupdate lock というコマンドが追加されています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fminamijoyo%2Ftfupdate%2Fpull%2F90" title="Add native support for updating .terraform.lock.hcl by minamijoyo · Pull Request #90 · minamijoyo/tfupdate" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/minamijoyo/tfupdate/pull/90">github.com</a></cite></p> <p>例えば、ある<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ配下のすべての<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>プロバイダを指定バージョンに更新しつつ、複数プラットフォーム混在で使う.terraform.lock.hclもまとめて一括更新するには、以下のようなコマンドで簡単にできるようになりました。</p> <pre class="code bash" data-lang="bash" data-unlink>$ tfupdate provider aws -v 5.7.0 -r ./ $ tfupdate lock --platform=linux_amd64 \ --platform=darwin_amd64 \ --platform=darwin_arm64 \ -r ./</pre> <p>内部的にterraformコマンドには依存せず、バージョン指定の検出からプロバイダのダウンロード、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の計算なども自前で実装したことで、terraform initを省略したり、計算結果をキャッシュすることが可能になりました。これにより、複数の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リに散らばった.terraform.lock.hclを一括更新するような<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>において、大幅にパフォーマンスが改善します。もちろん環境に依存しますが、数万行あるcrowdworks.jpのTerraform設定の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>では、バージョンアップ時のロックファイルの更新処理が300倍高速化しました。</p> <p>この記事では、.terraform.lock.hclとは何かについておさらいした後、これまでの技術的な課題とその解決策について説明します。また、実装する上で調べた.terraform.lock.hclに記録されている<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の計算方法についても解説するので、この記事を読めばあなたも「.terraform.lock.hcl完全に理解した」と言えるでしょう。</p> <p>本稿執筆時点でのTerraformのバージョンはv1.5.2で、tfupdateはv0.7.2です。最新の情報は<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>を参照してください。</p> <h2 id="terraformlockhclとは">.terraform.lock.hclとは</h2> <p>.terraform.lock.hclとは、Terraform v0.14から導入されたプロバイダの依存のロックファイルです。</p> <p><a href="https://developer.hashicorp.com/terraform/language/files/dependency-lock">Dependency Lock File (.terraform.lock.hcl) - Configuration Language | Terraform | HashiCorp Developer</a></p> <p>rootモジュールが要求するバージョン制約を解決した結果、依存するプロバイダのバージョン、制約条件、およびその<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>が記録されています。以下は、.terraform.lock.hclの例です。</p> <pre class="code lang-hcl" data-lang="hcl" data-unlink><span class="synComment"># This file is maintained automatically by &quot;terraform init&quot;.</span> <span class="synComment"># Manual edits may be lost in future updates.</span> provider <span class="synConstant">&quot;registry.terraform.io/hashicorp/null&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">version</span> = <span class="synConstant">&quot;3.2.1&quot;</span> <span class="synIdentifier">constraints</span> = <span class="synConstant">&quot;3.2.1&quot;</span> <span class="synIdentifier">hashes</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;h1:FbGfc+muBsC17Ohy5g806iuI1hQc4SIexpYCrQHQd8w=&quot;</span>, <span class="synConstant">&quot;h1:tSj1mL6OQ8ILGqR2mDu7OYYYWf+hoir0pf9KAQ8IzO8=&quot;</span>, <span class="synConstant">&quot;h1:ydA0/SNRVB1o95btfshvYsmxA+jZFRZcvKzZSB+4S1M=&quot;</span>, <span class="synConstant">&quot;zh:58ed64389620cc7b82f01332e27723856422820cfd302e304b5f6c3436fb9840&quot;</span>, <span class="synConstant">&quot;zh:62a5cc82c3b2ddef7ef3a6f2fedb7b9b3deff4ab7b414938b08e51d6e8be87cb&quot;</span>, <span class="synConstant">&quot;zh:63cff4de03af983175a7e37e52d4bd89d990be256b16b5c7f919aff5ad485aa5&quot;</span>, <span class="synConstant">&quot;zh:74cb22c6700e48486b7cabefa10b33b801dfcab56f1a6ac9b6624531f3d36ea3&quot;</span>, <span class="synConstant">&quot;zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3&quot;</span>, <span class="synConstant">&quot;zh:79e553aff77f1cfa9012a2218b8238dd672ea5e1b2924775ac9ac24d2a75c238&quot;</span>, <span class="synConstant">&quot;zh:a1e06ddda0b5ac48f7e7c7d59e1ab5a4073bbcf876c73c0299e4610ed53859dc&quot;</span>, <span class="synConstant">&quot;zh:c37a97090f1a82222925d45d84483b2aa702ef7ab66532af6cbcfb567818b970&quot;</span>, <span class="synConstant">&quot;zh:e4453fbebf90c53ca3323a92e7ca0f9961427d2f0ce0d2b65523cc04d5d999c2&quot;</span>, <span class="synConstant">&quot;zh:e80a746921946d8b6761e77305b752ad188da60688cfd2059322875d363be5f5&quot;</span>, <span class="synConstant">&quot;zh:fbdb892d9822ed0e4cb60f2fedbdbb556e4da0d88d3b942ae963ed6ff091e48f&quot;</span>, <span class="synConstant">&quot;zh:fca01a623d90d0cad0843102f9b8b9fe0d3ff8244593bd817f126582b52dd694&quot;</span>, <span class="synSpecial">]</span> <span class="synSpecial">}</span> </pre> <p>このロックファイルは、rootモジュール(=<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ)単位で生成されます。Terraformの公式では、.terraform.lock.hclをGitにコミットすることを推奨していますが、他の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%B8%C0%B8%EC">プログラミング言語</a>の依存ライブラリのロックファイルと同様に扱うと、意図せぬ動作をすることがあります。</p> <p>というのも、プロバイダのバイナリは<a class="keyword" href="https://d.hatena.ne.jp/keyword/linux">linux</a>_<a class="keyword" href="https://d.hatena.ne.jp/keyword/amd64">amd64</a>のようなプラットフォーム(OSとCPU<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>の組み合わせ)ごとに異なるため、この<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>もプラットフォームごとに異なります。ただロックファイル上は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>だけ見ても、どのプラットフォームと対応しているかは識別できません。また、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>にはzip<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AB%A5%A4%A5%D6">アーカイブ</a>の状態のzhと、展開後のh1の2種類があります。zhは初回のterraform initのタイミングでTerraform Registryから全プラットフォーム分取得できますが、h1は実行したプラットフォームのもののみが記録されます。あらかじめ必要なプラットフォーム分の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>を記録するには、terraform providers lockコマンドを使用します。</p> <p><a href="https://developer.hashicorp.com/terraform/cli/commands/providers/lock">Command: providers lock | Terraform | HashiCorp Developer</a></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DE%A5%EB%A5%C1%A5%D7%A5%E9%A5%C3%A5%C8%A5%D5%A5%A9%A1%BC%A5%E0">マルチプラットフォーム</a>混在環境では、.terraform.lock.hclを正しく管理していないと、環境によって<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C1%A5%A7%A5%C3%A5%AF%A5%B5%A5%E0">チェックサム</a>ミスマッチエラーになったり、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>が追記されて意図しないタイミングでgit diffが出てしまったりすることがあります。以前は手元が<a class="keyword" href="https://d.hatena.ne.jp/keyword/macOS">macOS</a>だが、CIは<a class="keyword" href="https://d.hatena.ne.jp/keyword/Linux">Linux</a>という場合にはハマりポイントだったのですが、最近は<a class="keyword" href="https://d.hatena.ne.jp/keyword/Intel%20mac">Intel mac</a>とM1/M2 <a class="keyword" href="https://d.hatena.ne.jp/keyword/mac">mac</a>の混在により、CIでの自動化まで整備できていない開発チームでもつまづきポイントになりました。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>が2種類ある理由は歴史的な経緯によるものですが、話し始めると長くなるので、Terraform v0.14当時に書いた解説を以下に置いておきます。興味がある人はこちらをどうぞ。</p> <p><iframe id="talk_frame_703744" class="speakerdeck-iframe" src="//speakerdeck.com/player/2d1d5f729527499896bf26a46cc51840" width="710" height="532" style="aspect-ratio:710/532; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/minamijoyo/how-to-update-terraform-dot-lock-dot-hcl-efficiently">speakerdeck.com</a></cite></p> <h2 id="tfupdateとは">tfupdateとは</h2> <p>tfupdateとは、Terraformの本体/プロバイダ/モジュールのバージョン制約を一括で書き換えるツールです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fminamijoyo%2Ftfupdate" title="GitHub - minamijoyo/tfupdate: Update version constraints in your Terraform configurations" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/minamijoyo/tfupdate">github.com</a></cite></p> <p>terraform planの再現性を担保するためには、依存するプロバイダなどのバージョンを固定する必要があります。しかし、最新版から離れすぎると、バージョンアップしようとしたときに変更差分が多くなり、追いつくのが大変です。そのため、依存関係を頻繁にバージョンアップすることがベストプ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ティスです。バージョン番号を書き換える作業は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リの数が少なければ簡単ですが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ数が多くなると煩雑になります。tfupdateは、指定した<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ配下を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>的にパースして、バージョン制約を一括で書き換えることができます。</p> <p>例えば以下のような設定がmain.tfにあるとして、</p> <pre class="code lang-tf" data-lang="tf" data-unlink>terraform <span class="synSpecial">{</span> required_version <span class="synStatement">=</span> &quot;<span class="synConstant">1.5.2</span>&quot; required_providers <span class="synSpecial">{</span> aws <span class="synStatement">=</span> <span class="synSpecial">{</span> source <span class="synStatement">=</span> &quot;<span class="synConstant">hashicorp/aws</span>&quot; <span class="synStatement">version</span> <span class="synStatement">=</span> &quot;<span class="synConstant">5.6.0</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p>以下のコマンドで<a class="keyword" href="https://d.hatena.ne.jp/keyword/aws">aws</a>プロバイダをv5.7.0に更新できます。</p> <pre class="code bash" data-lang="bash" data-unlink>$ tfupdate provider aws -v 5.7.0 main.tf</pre> <pre class="code lang-tf" data-lang="tf" data-unlink>terraform <span class="synSpecial">{</span> required_version <span class="synStatement">=</span> &quot;<span class="synConstant">1.5.2</span>&quot; required_providers <span class="synSpecial">{</span> aws <span class="synStatement">=</span> <span class="synSpecial">{</span> source <span class="synStatement">=</span> &quot;<span class="synConstant">hashicorp/aws</span>&quot; <span class="synStatement">version</span> <span class="synStatement">=</span> &quot;<span class="synConstant">5.7.0</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p>非常に単純ですが、HCLの構文をパースして書き換えています。また -r オプションで指定の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ配下を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>的に一括で書き換えることも可能です。これをfind/xargs/<a class="keyword" href="https://d.hatena.ne.jp/keyword/grep">grep</a>/<a class="keyword" href="https://d.hatena.ne.jp/keyword/sed">sed</a>などのシェル芸で実装しようとすると、複数行にまたがるコンテキストを<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>でマッチさせる必要があり、エッジケースなども考慮すると、かなり面倒くさそうなのは容易に想像できるでしょう。</p> <p>またtfupdateには、<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a> ReleaseやTerraform Registryなどから最新版のバージョン番号を取得する機能もあるので、これをCIに組み込んで定期的に実行することで、最新版が出たら自動でPullRequestを作成し、CIでplan差分もチェックするというワークフローが自由に作り込めます。あとは人間が<a class="keyword" href="https://d.hatena.ne.jp/keyword/CHANGELOG">CHANGELOG</a>を読んでマージするだけで、常に最新版が使えるという、控えめに言って最高の開発者体験が実現できます。</p> <p>crowdworks.jpのインフラは<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>で運用されており、インフラの管理に長年Terraformを使っています。Terraformの設定は数万行、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ数で300個ぐらいの規模感で運用されていますが、tfupdateを使ってバージョンアップ作業を自動化することで、本稿執筆時点で最新のTerraform v1.5系&<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>プロバイダv5系で運用されており、この規模でも新機能がいつでも試せます。福利厚生です。</p> <p>同じようなことは <a href="https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates">Dependabot</a> や <a href="https://www.mend.io/renovate/">Renovate</a> のような<a class="keyword" href="https://d.hatena.ne.jp/keyword/SaaS">SaaS</a>でも実現できますが、tfupdateはHCLを書き換えるだけの単機能な<a class="keyword" href="https://d.hatena.ne.jp/keyword/CLI">CLI</a>ツールで、<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a> ActionsやCircleCIなど好きなCI/CDツールに組み込んでワークフローを自由にカスタマイズすることができます。もちろんCI/CDに組み込んで自動化までしなくても、単に手元のローカル環境で使うだけでも十分便利です。歴史的には、tfupdateはDependabotやRenovateがTerraformに対応する以前、Terraform v0.12の時代に書かれましたが、今でも単機能な<a class="keyword" href="https://d.hatena.ne.jp/keyword/CLI">CLI</a>ツールとして、Terraform職人の皆さんに一定の需要があるようです。</p> <p>そんな便利なtfupdateですが、これまで Terraform v0.14で追加された .terraform.lock.hcl の更新には直接対応しておらず、公式のterraform providers lockコマンドを使って更新することを推奨していました。一方、DependabotやRenovateは.terraform.lock.hcl更新に対応しており、<a class="keyword" href="https://d.hatena.ne.jp/keyword/SaaS">SaaS</a>だと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>をサーバサイドでキャッシュできてずるいよなぁなどと思いつつ、個人的な懸念点はロックファイルのフォーマットがTerraformの実装詳細なことでした。公式のterraformコマンドでできるものを、あえてtfupdate内に再実装すると、複数のTerraformバージョンをサポートするコストが高く、将来的なメンテナンスの負荷を増やしそうなことは容易に想像できたので、これまで意図的に避けていました。</p> <h2 id="複数のterraformlockhclを更新する場合の問題点">複数の.terraform.lock.hclを更新する場合の問題点</h2> <p>terraformコマンドは同時に1つの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リしか操作できませんが、現実的にTerraformでインフラを管理しようとすると、1つの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リで完結することはないでしょう。環境ごとに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リを分けたり、論理的な<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>単位で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リを分けたりするのが普通です。またチームで開発する場合、複数のプラットフォームが混在することも普通にあります。</p> <p>前述の通り、複数のプラットフォーム用の.terraform.lock.hclを生成するには、terraform providers lockコマンドを使用しますが、このコマンドはプロバイダのキャッシュを意図的に無視します。これはバグではなく仕様です。そのため、複数の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リで実行すると都度ダウンロードが発生してしまい冗長です。無駄なダウンロードを回避するには、terraform providers mirrorコマンドを使用して、ローカルのミラーを作成します。</p> <p><a href="https://developer.hashicorp.com/terraform/cli/commands/providers/mirror">Command: providers mirror | Terraform | HashiCorp Developer</a></p> <p>その他の注意点として、プロバイダの依存はモジュールを介して間接的に追加される可能性があるので、あらかじめterraform initが必要です。また、Terraformのバージョンによっては、.terraform.lock.hclを更新する際に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C1%A5%A7%A5%C3%A5%AF%A5%B5%A5%E0">チェックサム</a>ミスマッチエラーが発生することがあります。エラーの発生有無はTerraformのバージョンによって微妙に挙動が異なります。</p> <p>これらを考慮して、最終的に完成した「ぼくのかんがえたさいきょうのtflock_generate.sh」は以下の通りです。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment">#!/bin/bash</span> <span class="synStatement">set</span><span class="synIdentifier"> </span><span class="synSpecial">-eo</span><span class="synIdentifier"> pipefail</span> <span class="synComment"># create a plugin cache dir</span> <span class="synStatement">export</span> <span class="synIdentifier">TF_PLUGIN_CACHE_DIR</span>=<span class="synStatement">&quot;</span><span class="synConstant">/tmp/terraform.d/plugin-cache</span><span class="synStatement">&quot;</span> <span class="synStatement">mkdir</span> <span class="synSpecial">-p</span> <span class="synStatement">&quot;</span><span class="synPreProc">${TF_PLUGIN_CACHE_DIR}</span><span class="synStatement">&quot;</span> <span class="synComment"># remove an old lock file before providers mirror command</span> <span class="synStatement">rm</span> <span class="synSpecial">-f</span> .terraform.lock.hcl <span class="synComment"># create a local filesystem mirror to avoid duplicate downloads</span> <span class="synIdentifier">FS_MIRROR</span>=<span class="synStatement">&quot;</span><span class="synConstant">/tmp/terraform.d/plugins</span><span class="synStatement">&quot;</span> terraform providers mirror <span class="synSpecial">-platform</span><span class="synStatement">=</span>linux_amd64 <span class="synSpecial">-platform</span><span class="synStatement">=</span>darwin_amd64 <span class="synSpecial">-platform</span><span class="synStatement">=</span>darwin_arm64 <span class="synStatement">&quot;</span><span class="synPreProc">${FS_MIRROR}</span><span class="synStatement">&quot;</span> <span class="synComment"># update the lock file</span> <span class="synIdentifier">ALL_DIRS</span>=<span class="synPreProc">$(</span><span class="synStatement">find</span><span class="synSpecial"> . -type f -name </span><span class="synStatement">'</span><span class="synConstant">*.tf</span><span class="synStatement">'</span><span class="synSpecial"> </span><span class="synStatement">|</span><span class="synSpecial"> xargs -I {} dirname {} </span><span class="synStatement">|</span><span class="synSpecial"> </span><span class="synStatement">sort</span><span class="synSpecial"> </span><span class="synStatement">|</span><span class="synSpecial"> uniq </span><span class="synStatement">|</span><span class="synSpecial"> </span><span class="synStatement">grep</span><span class="synSpecial"> -v </span><span class="synStatement">'</span><span class="synConstant">modules/</span><span class="synStatement">'</span><span class="synPreProc">)</span> <span class="synStatement">for</span> dir <span class="synStatement">in</span> <span class="synPreProc">${ALL_DIRS}</span> <span class="synStatement">do</span> <span class="synStatement">pushd</span> <span class="synStatement">&quot;</span><span class="synPreProc">$dir</span><span class="synStatement">&quot;</span> <span class="synComment"># always create a new lock to avoid duplicate downloads by terraoform init -upgrade</span> <span class="synStatement">rm</span> <span class="synStatement">-f</span> .terraform.lock.hcl <span class="synComment"># get modules to detect provider dependencies inside module</span> terraform init <span class="synSpecial">-input</span><span class="synStatement">=false</span> <span class="synSpecial">-no-color</span> <span class="synSpecial">-backend</span><span class="synStatement">=false</span> <span class="synSpecial">-plugin-dir</span><span class="synStatement">=&quot;</span><span class="synPreProc">${FS_MIRROR}</span><span class="synStatement">&quot;</span> <span class="synComment"># remove a temporary lock file to avoid a checksum mismatch error</span> <span class="synStatement">rm</span> <span class="synStatement">-f</span> .terraform.lock.hcl <span class="synComment"># generate h1 hashes for all platforms you need</span> <span class="synComment"># recording zh hashes requires to download from origin, so we intentionally ignore them.</span> terraform providers lock <span class="synSpecial">-fs-mirror</span><span class="synStatement">=&quot;</span><span class="synPreProc">${FS_MIRROR}</span><span class="synStatement">&quot;</span> <span class="synSpecial">-platform</span><span class="synStatement">=</span>linux_amd64 <span class="synSpecial">-platform</span><span class="synStatement">=</span>darwin_amd64 <span class="synSpecial">-platform</span><span class="synStatement">=</span>darwin_arm64 <span class="synComment"># clean up</span> <span class="synStatement">rm</span> <span class="synSpecial">-rf</span> .terraform <span class="synStatement">popd</span> <span class="synStatement">done</span> </pre> <p>これまで上記のような<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>をtfupdateと一緒に実行することで、.terraform.lock.hclも更新していました。ただ、本来やりたかったことに対して必要以上に複雑なのは否めません。</p> <p>プロバイダの依存は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リごとにそんなに変わらないので、1箇所で全部入りのロックファイルを作ってコピーして配ればよいのではないかと思う人もいるかもしれません。しかしながらその戦略はロックファイルがプロバイダだけではなく、将来的にモジュールの依存も管理するようになると破綻するので、個人的には筋が悪そうと思っています。Terraformのソースを読めば分かりますが、ロックファイルをパースする箇所でmoduleというキーワードが将来のために予約されています。そしてモジュールの依存は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リごとに異なるのが普通です。</p> <p><a href="https://github.com/hashicorp/terraform/blob/v1.5.2/internal/depsfile/locks_file.go#L212-L215">https://github.com/hashicorp/terraform/blob/v1.5.2/internal/depsfile/locks_file.go#L212-L215</a></p> <p>.terraform.lock.hclを完全に理解した結果、必要以上の複雑さは、そもそもTerraform Registryがh1ハッシュを返してくれないのが諸悪の根源であるのは明らかだったので、Terraform v0.14当時に以下のissueで、Terraform Registryの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%C8%A5%B3%A5%EB">プロトコル</a>変更を提案しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fhashicorp%2Fterraform%2Fissues%2F27264" title="Feature request: Generate .terraform.lock.hcl including zh and h1 hash values for given platforms from required_providers block without downloading providers and modules · Issue #27264 · hashicorp/terraform" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/hashicorp/terraform/issues/27264">github.com</a></cite></p> <p>というのがもう2年以上前の話です。時は流れ、状況は日に日に悪化していきました。</p> <p>Terraform Registryの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%C8%A5%B3%A5%EB">プロトコル</a>改善の進展がないまま、Terraform 1.4から.terraform.lock.hclの管理が実質的に必須となり、最終手段として.gitignoreに追加するという逃げ道が塞がれました。公式からは大きくアナウンスされていないので、まだTerraform v1.4での挙動の変更に気づいてない人も多いかもしれませんが、.terraform.lock.hclがないとプロバイダのキャッシュが効かなくなりました。詳細は以下にまとめています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fminamijoyo%2Fitems%2Fa3fef269fecbf7f54c2f" title="Terraform v1.4でのTF_PLUGIN_CACHE_DIRと.terraform.lock.hclの挙動変更 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/minamijoyo/items/a3fef269fecbf7f54c2f">qiita.com</a></cite></p> <p>一方、M1 <a class="keyword" href="https://d.hatena.ne.jp/keyword/Mac">Mac</a>の登場により、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>を計算すべきプラットフォームが増えたり、管理するインフラの規模拡大に伴い<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ数は増える一方で、必要な計算量は増加し続けています。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>プロバイダは毎週新しいバージョンがリリースされますが、バージョンアップ時には、crowdworks.jpでは3つのプラットフォームx約300<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リの.terraform.lock.hclを更新する必要があり、CIでのロックファイルの更新処理時間がいつのまにか1時間20分もかかるようになってしまいました。terraform plan時間を含まずにロックファイルの更新のみでこんなに時間がかかるのは、さすがにどうかと思いますし、なにかが間違っています。</p> <p>観察したところ、どうやら、プロバイダのミラーを作って冗長なダウンロードを回避しても、依然としてzipファイルの解凍や<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>計算は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リごとに行われているようです。<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>プロバイダはzip圧縮状態でおよそ80MB、解凍すると400MBほどのファイルであり、これを<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リごとに再計算するのは純粋に計算コストの無駄であり、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ数が多くなるとそれに比例して時間がかかってしまいます。</p> <p>Terraform Registryの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%C8%A5%B3%A5%EB">プロトコル</a>変更に関する提案は全く進展が見られませんが、幸か不幸か、terraform.lock.hclのフォーマットも全く変更されていないため、あきらめて自分でロックファイルを更新する方法を検討し始めました。</p> <h2 id="ハッシュ値の計算方法"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の計算方法</h2> <p>さいわいHCLを書き換えるのは得意なので(?)、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の計算方法が分かれば、あとはどうにでもなりそうで、まず<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の計算方法を調べました。</p> <p>terraform providers lockコマンドの動作を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>ログを出しながら観察していると、いくつかのエンドポイントと通信していることが観測できます。</p> <p>まず最初に、Terraform Registryから指定のプロバイダの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E1%A5%BF%A5%C7%A1%BC%A5%BF">メタデータ</a>を取得します。わかりやすいように、<a class="keyword" href="https://d.hatena.ne.jp/keyword/curl">curl</a>で再現すると以下のようになります。</p> <pre class="code bash" data-lang="bash" data-unlink>$ curl -s https://registry.terraform.io/v1/providers/hashicorp/null/3.2.1/download/darwin/arm64 | jq .</pre> <p>出力の<a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>を整形すると、以下のようになっています。 ※ascii_armorの部分のみ長いため、一部省略しています。</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">protocols</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">5.0</span>&quot; <span class="synSpecial">]</span>, &quot;<span class="synStatement">os</span>&quot;: &quot;<span class="synConstant">darwin</span>&quot;, &quot;<span class="synStatement">arch</span>&quot;: &quot;<span class="synConstant">arm64</span>&quot;, &quot;<span class="synStatement">filename</span>&quot;: &quot;<span class="synConstant">terraform-provider-null_3.2.1_darwin_arm64.zip</span>&quot;, &quot;<span class="synStatement">download_url</span>&quot;: &quot;<span class="synConstant">https://releases.hashicorp.com/terraform-provider-null/3.2.1/terraform-provider-null_3.2.1_darwin_arm64.zip</span>&quot;, &quot;<span class="synStatement">shasums_url</span>&quot;: &quot;<span class="synConstant">https://releases.hashicorp.com/terraform-provider-null/3.2.1/terraform-provider-null_3.2.1_SHA256SUMS</span>&quot;, &quot;<span class="synStatement">shasums_signature_url</span>&quot;: &quot;<span class="synConstant">https://releases.hashicorp.com/terraform-provider-null/3.2.1/terraform-provider-null_3.2.1_SHA256SUMS.72D7468F.sig</span>&quot;, &quot;<span class="synStatement">shasum</span>&quot;: &quot;<span class="synConstant">e4453fbebf90c53ca3323a92e7ca0f9961427d2f0ce0d2b65523cc04d5d999c2</span>&quot;, &quot;<span class="synStatement">signing_keys</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">gpg_public_keys</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">key_id</span>&quot;: &quot;<span class="synConstant">34365D9472D7468F</span>&quot;, &quot;<span class="synStatement">ascii_armor</span>&quot;: &quot;<span class="synConstant">-----BEGIN PGP PUBLIC KEY BLOCK-----</span><span class="synSpecial">\n\n</span><span class="synConstant"> (snip.) -----END PGP PUBLIC KEY BLOCK-----</span>&quot;, &quot;<span class="synStatement">trust_signature</span>&quot;: &quot;&quot;, &quot;<span class="synStatement">source</span>&quot;: &quot;<span class="synConstant">HashiCorp</span>&quot;, &quot;<span class="synStatement">source_url</span>&quot;: &quot;<span class="synConstant">https://www.hashicorp.com/security.html</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p>download_urlのところにプロバイダのzip<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AB%A5%A4%A5%D6">アーカイブ</a>があるので、これを取得します。</p> <pre class="code bash" data-lang="bash" data-unlink>$ curl -s https://releases.hashicorp.com/terraform-provider-null/3.2.1/terraform-provider-null_3.2.1_darwin_arm64.zip -o tmp/terraform-provider-null_3.2.1_darwin_arm64.zip</pre> <p>zhの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の計算方法は公式ドキュメントに記載のあるとおり、ただのzipファイルのsha256sumです。</p> <p><a href="https://developer.hashicorp.com/terraform/language/files/dependency-lock#zh">https://developer.hashicorp.com/terraform/language/files/dependency-lock#zh</a></p> <blockquote><p><code>zh:</code>: a mnemonic for "zip hash", this is a legacy hash format which is part of the Terraform provider registry protocol and is therefore used for providers that you install directly from an origin registry. This hashing <a class="keyword" href="https://d.hatena.ne.jp/keyword/scheme">scheme</a> captures a SHA256 hash of each of the <a class="keyword" href="https://d.hatena.ne.jp/keyword/official">official</a> <code>.zip</code> packages indexed in the origin registry.</p></blockquote> <p>ダウンロードしたzipのsha256sumを計算すると、先程の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E1%A5%BF%A5%C7%A1%BC%A5%BF">メタデータ</a>のshasumの項目と一致していることが分かります。</p> <pre class="code bash" data-lang="bash" data-unlink>$ sha256sum tmp/terraform-provider-null_3.2.1_darwin_arm64.zip e4453fbebf90c53ca3323a92e7ca0f9961427d2f0ce0d2b65523cc04d5d999c2 tmp/terraform-provider-null_3.2.1_darwin_arm64.zip</pre> <p>ただzhに関しては、全部のプラットフォーム分のzipをダウンロードする必要はありません。shasums_urlにあるSHA256SUMSファイルから、全プラットフォーム分のzhが取得できます。</p> <pre class="code bash" data-lang="bash" data-unlink>$ curl -s https://releases.hashicorp.com/terraform-provider-null/3.2.1/terraform-provider-null_3.2.1_SHA256SUMS 58ed64389620cc7b82f01332e27723856422820cfd302e304b5f6c3436fb9840 terraform-provider-null_3.2.1_freebsd_arm.zip 62a5cc82c3b2ddef7ef3a6f2fedb7b9b3deff4ab7b414938b08e51d6e8be87cb terraform-provider-null_3.2.1_windows_386.zip 63cff4de03af983175a7e37e52d4bd89d990be256b16b5c7f919aff5ad485aa5 terraform-provider-null_3.2.1_darwin_amd64.zip 74cb22c6700e48486b7cabefa10b33b801dfcab56f1a6ac9b6624531f3d36ea3 terraform-provider-null_3.2.1_linux_amd64.zip 78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3 terraform-provider-null_3.2.1_manifest.json 79e553aff77f1cfa9012a2218b8238dd672ea5e1b2924775ac9ac24d2a75c238 terraform-provider-null_3.2.1_windows_amd64.zip a1e06ddda0b5ac48f7e7c7d59e1ab5a4073bbcf876c73c0299e4610ed53859dc terraform-provider-null_3.2.1_freebsd_amd64.zip c37a97090f1a82222925d45d84483b2aa702ef7ab66532af6cbcfb567818b970 terraform-provider-null_3.2.1_linux_arm.zip e4453fbebf90c53ca3323a92e7ca0f9961427d2f0ce0d2b65523cc04d5d999c2 terraform-provider-null_3.2.1_darwin_arm64.zip e80a746921946d8b6761e77305b752ad188da60688cfd2059322875d363be5f5 terraform-provider-null_3.2.1_linux_386.zip fbdb892d9822ed0e4cb60f2fedbdbb556e4da0d88d3b942ae963ed6ff091e48f terraform-provider-null_3.2.1_freebsd_386.zip fca01a623d90d0cad0843102f9b8b9fe0d3ff8244593bd817f126582b52dd694 terraform-provider-null_3.2.1_linux_arm64.zip</pre> <p>shasums_urlのコンテンツの妥当性は、shasums_signature_urlとsigning_keysを使用することで署名の検証ができますが、ここでは割愛します。</p> <p>一方、h1の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の計算方法は、公式ドキュメントによるとzipファイルの中身のsha256sumとのことですが、</p> <p><a href="https://developer.hashicorp.com/terraform/language/files/dependency-lock#h1">https://developer.hashicorp.com/terraform/language/files/dependency-lock#h1</a></p> <blockquote><p><code>h1:</code>: a mnemonic for "hash <a class="keyword" href="https://d.hatena.ne.jp/keyword/scheme">scheme</a> 1", which is the current preferred hashing <a class="keyword" href="https://d.hatena.ne.jp/keyword/scheme">scheme</a>. Hash <a class="keyword" href="https://d.hatena.ne.jp/keyword/scheme">scheme</a> 1 is also a SHA256 hash, but is one computed from the <em>contents</em> of the provider distribution package, rather than of the <code>.zip</code> archive it's contained within.</p></blockquote> <p>単純にダウンロードしたzipを解凍して、プロバイダのバイナリのsha256sumを計算してもロックファイルと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>が一致しませんし、そもそも桁数も違いそうです。</p> <pre class="code bash" data-lang="bash" data-unlink>$ unzip tmp/terraform-provider-null_3.2.1_darwin_arm64.zip -d tmp/terraform-provider-null_3.2.1_darwin_arm64 Archive: tmp/terraform-provider-null_3.2.1_darwin_arm64.zip inflating: tmp/terraform-provider-null_3.2.1_darwin_arm64/terraform-provider-null_v3.2.1_x5 $ sha256sum tmp/terraform-provider-null_3.2.1_darwin_arm64/terraform-provider-null_v3.2.1_x5 1dfc06bc382110e8a252fd748e4d9a877cdb4389f304e2efe6027f2c55fe0155 tmp/terraform-provider-null_3.2.1_darwin_arm64/terraform-provider-null_v3.2.1_x5</pre> <p>h1ハッシュの計算方法は、ドキュメントには詳細は書かれていませんが、コードを読むと分かる通り、Go Modulesで使われているgo.sumの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>計算と同じ方法を流用しています。</p> <p><a href="https://github.com/hashicorp/terraform/blob/v1.5.2/internal/getproviders/hash.go#L352">https://github.com/hashicorp/terraform/blob/v1.5.2/internal/getproviders/hash.go#L352</a></p> <p>正味の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>計算の実装はここにあります。</p> <p><a href="https://github.com/golang/mod/blob/v0.8.0/sumdb/dirhash/hash.go">https://github.com/golang/mod/blob/v0.8.0/sumdb/dirhash/hash.go</a></p> <p>まず<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ内に含まれるそれぞれのファイルのsha256sumの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>を算出し、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>とファイル名を並べた一覧を作成します。そしてさらにその一覧に対してsha256sumの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>を計算することで、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ全体の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>を算出しています。go.sumの実装を流用しているので、ライブラリ関数もそのまま流用できそうです。</p> <p>ちなみに余談ですが、さきほどのコード中のコメントに、以下のような<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%BC%BB%F7%A5%B3%A1%BC%A5%C9">擬似コード</a>が書かれています。</p> <pre class="code bash" data-lang="bash" data-unlink>sha256sum $(find . -type f | sort) | sha256sum</pre> <p>試してみたところ、これでは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>は一致しませんでした。Goの実装を忠実にシェル芸で再現することは困難そうですが、以下のissueでいくつか<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%EB%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">シェルスクリプト</a>での計算方法が議論されています。</p> <p><a href="https://github.com/golang/go/issues/48498">x/mod/sumdb/dirhash: incorrect Unix command example &middot; Issue #48498 &middot; golang/go &middot; GitHub</a></p> <p>上記を参考にしつつ、厳密にやろうとすると複雑ですが、Terraformプロバイダの場合zipに含まれるファイルは1つだけです。簡略化して、以下の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EF%A5%F3%A5%E9%A5%A4%A5%CA%A1%BC">ワンライナー</a>で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の計算が一致するようになりました。</p> <pre class="code bash" data-lang="bash" data-unlink>$ sha256sum terraform-provider-null_v3.2.1_x5 | sha256sum | cut -f1 -d&#39; &#39; | xxd -r -p | {printf &#39;h1:&#39;; base64} h1:ydA0/SNRVB1o95btfshvYsmxA+jZFRZcvKzZSB+4S1M=</pre> <p>元のコメント中の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B5%BC%BB%F7%A5%B3%A1%BC%A5%C9">擬似コード</a>は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ内のファイルの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>を計算し、その全体の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>を取得することを表現していますが、実際にはその結果をさらにバイト列に変換した後、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Base64">Base64</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%F3%A5%B3%A1%BC%A5%C9">エンコード</a>しないと計算が合わないようです。なるほど〜。完全に理解した。</p> <h2 id="設計">設計</h2> <p>というわけで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の計算方法がわかったので、あとは地道に実装すればなんとかなりそうな目処は立ちましたが、実装する上でいくつか考慮したポイントを補足しておきます。</p> <p>まず方針として、Terraform <a class="keyword" href="https://d.hatena.ne.jp/keyword/CLI">CLI</a> (=terraformコマンド) に依存しないようにしました。もちろん将来的に実装詳細が変わった場合に、複数のTerraformバージョンをサポートするメンテナンスコストが上がるので、これまで避けていたわけですが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の計算方法は引き続き<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%D0%A1%BC%A5%B9%A5%A8%A5%F3%A5%B8%A5%CB%A5%A2%A5%EA%A5%F3%A5%B0">リバースエンジニアリング</a>できそうですし、terraformブロックのrequired_versionなどからTerraformバージョンも推測できそうです。<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D2%A5%E5%A1%BC%A5%EA%A5%B9%A5%C6%A5%A3%A5%C3%A5%AF">ヒューリスティック</a>に複数バージョンの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%F3%A5%BF%A5%C3%A5%AF%A5%B9">シンタックス</a>を解釈できるかは、将来的にどのようにロックフォーマットが変更されるかに依存します。どれぐらい面倒くさいかは、現時点ではなんとも言えないところではあります。</p> <p>Terraform <a class="keyword" href="https://d.hatena.ne.jp/keyword/CLI">CLI</a>に依存しないという方針と不可分ですが、terraform initもそこそこ負荷が高い処理なので、いくつかの仮定を置くことで省略することにしました。具体的には、それぞれの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リのrequired_providersブロックで、 <code>version = "3.2.1"</code> のように必要なプロバイダのバージョンを単純に明示することを仮定し、<code>version = "&gt; 3.0"</code> のようなバージョン制約式や、モジュール経由での間接依存は一旦未対応として無視することにしました。もちろん一般的には暗黙に仮定することはできませんが、tfupdateはこれまで.terraform.lock.hclには未対応だったので、tfupdateの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>では、これまでもCIで最新版に更新するには厳密なバージョン指定が必要でした。実運用上は問題ないだろうという実装スコープを最小化するための割り切りです。</p> <p>とはいえバージョン制約式に関しては、理論上バージョン一覧の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E1%A5%BF%A5%C7%A1%BC%A5%BF">メタデータ</a>をメモリ上に持っておけば対応可能な気はしており、もしロックファイルだけ更新したい需要が多そうであれば、そのうち気が向いたら対応するかもしれません。一方モジュール経由での間接依存については、実装するにはモジュールの中身を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>的に解析する必要があり、モジュール参照はローカルだけではなく、リモートとなる可能性があるので、かなり実装のハードルが高そうです。</p> <p>また、プロバイダのsource指定については、さしあたり公式のTerraform Registryのみを対象とし、既に非推奨となっているTerraform v0.13以前の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CC%BE%C1%B0%B6%F5%B4%D6">名前空間</a>の省略記法などのレガシーなバリエーションは、すべて無視するようにしました。.terraform.lock.hclが導入されたのはv0.14からですし、さすがに既に非推奨な古い記法に今さら対応するのもどうかなと思うので。</p> <p>複数の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リを効率よく更新するため、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の計算結果はメモリ上でキャッシュすることにしましたが、required_providersブロックは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ単位でパースすることにより、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リによって依存するプロバイダが異なっても動作するようにしました。</p> <p>その他細かい挙動として、.terraform.lock.hclの中に該当のproviderブロックが存在しない場合は、providerブロックを追記するようにしましたが、.terraform.lock.hclのファイル自体が存在しない場合は、新規にファイル作成はしない方針としました。これは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>的に一括更新するような<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>を想定した場合に、存在しない場合に作成するというのは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%ED%C7%FA">誤爆</a>するリスクが高そうだなという安全側に倒した判断で、もし需要がありそうであれば、opt-inフラグであればありかなという気もしています。あとバージョンは更新されていないが<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%B7%A5%E5%C3%CD">ハッシュ値</a>の一覧が一部欠けているようなパターンは、技術的には検出できなくはないですが、検出コストが高そうなので、ロックファイルのバージョン番号が変わったときのみ更新する方針としました。</p> <p>全体的な方針として、tfupdateは複数の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リを一括更新するという特性上、一部の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リでモジュールのコンテキストを正しく解釈できない可能性があり、未対応の場合は可能な限りエラーとせずにDEBUGレベルのログを出すだけで、無視するようにしました。意図せず更新されない場合は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a> <code>TFUPDATE_LOG=DEBUG</code> で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>ログを出せるようにしてあるので、ログを見るとなにかヒントがあるかもしれません。</p> <h2 id="完成">完成</h2> <p>というわけで、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BC%D6%CE%D8%A4%CE%BA%C6%C8%AF%CC%C0">車輪の再発明</a>をなんだかんだ5000行ぐらい書いて、完成したものがこちらです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fminamijoyo%2Ftfupdate%2Fpull%2F90" title="Add native support for updating .terraform.lock.hcl by minamijoyo · Pull Request #90 · minamijoyo/tfupdate" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/minamijoyo/tfupdate/pull/90">github.com</a></cite></p> <p>あわせて、tfupdateのCircleCI用のサンプルも更新していますので、各自のCI設定で読み替えてください。Terraform <a class="keyword" href="https://d.hatena.ne.jp/keyword/CLI">CLI</a>に依存しなくなったので、terraformコマンドのインストールなども不要になったのは地味にうれしいポイントです。</p> <p><a href="https://github.com/minamijoyo/tfupdate-circleci-example/pull/356">Use native tfupdate lock command for updating .terraform.lock.hcl by minamijoyo &middot; Pull Request #356 &middot; minamijoyo/tfupdate-circleci-example &middot; GitHub</a></p> <p>パフォーマンス比較のため、検証当時の最新版の<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>プロバイダv5.6.2のバージョンアップで検証しました。対象はcrowdworks.jpのTerraform設定のコードベース(<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ数は約300)、プラットフォームは3つ(<a class="keyword" href="https://d.hatena.ne.jp/keyword/linux">linux</a>_<a class="keyword" href="https://d.hatena.ne.jp/keyword/amd64">amd64</a>, <a class="keyword" href="https://d.hatena.ne.jp/keyword/darwin">darwin</a>_<a class="keyword" href="https://d.hatena.ne.jp/keyword/amd64">amd64</a>, <a class="keyword" href="https://d.hatena.ne.jp/keyword/darwin">darwin</a>_arm64)、実行環境は<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a> Actions上の<a class="keyword" href="https://d.hatena.ne.jp/keyword/ubuntu">ubuntu</a>-latestのデフォルトのRunnner(CPU2コア/メモリ7GB)で実行しました。</p> <p>結果として、従来のterraform providers mirror &amp; lockを組み合わせた手法では、ロックファイルの更新に1時間20分かかっていたものが、tfupdate lockコマンドを使用すると16秒で完了し、300倍の高速化が確認できました。改善前の処理時間は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ数に大きく依存しますが、tfupdate lockコマンドの処理時間は、プロバイダのサイズxプラットフォーム数が支配的であり、今後<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ数が増えても十分にスケーラブルなことが確認できました。</p> <p>今度こそ.terraform.lock.hcl完全に理解した(n回目)</p> <h2 id="まとめ">まとめ</h2> <p>tfupdate v0.7から、tfupdate lockコマンドが追加され、.terraform.lock.hclの更新に対応しました。</p> <p>既にtfupdateユーザーの皆さんは、簡単に爆速で.terraform.lock.hclも更新できるようになったので、ぜひお試し下さい。またこれまでtfupdateを使ったことがないぞという人も、もし.terraform.lock.hclの更新に困っているのであれば、この機会にちょっと試してみてください。ご意見ご感想をお待ちしております。</p> <p>気に入ったらスター☆してくれると、今後の開発の励みになります |ω・`)チラッ</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fminamijoyo%2Ftfupdate" title="GitHub - minamijoyo/tfupdate: Update version constraints in your Terraform configurations" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/minamijoyo/tfupdate">github.com</a></cite></p> <p>最後になりましたが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスでは、Terraformが大好きなSREを募集しております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fherp.careers%2Fv1%2Fcrowdworks%2FJ7bA_cEqURXa" title="コンテナを活用して変更に強く可用性の高いインフラを作っていきたいSRE募集 - 株式会社クラウドワークス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://herp.careers/v1/crowdworks/J7bA_cEqURXa">herp.careers</a></cite></p> minamijoyo MeowCopやめました(RuboCop設定の見直しをしてる話) hatenablog://entry/820878482948638014 2023-07-10T12:00:48+09:00 2023-07-10T12:00:48+09:00 CrowdWorks ジャンヌチーム(技術的負債解消チーム)所属のlinyclarです。 「片付ければ片付く、片付けないから片付かない」をモットーに技術的負債解消に取り組んでおります。 長年放置され負債を生み出す原因となっているRuboCop設定の見直しを最近行っています。今回はその事例を共有します。なお、RuboCopでは各チェックのことをCopと言いますが、この記事では表現のしやすさから以下ルールと書いています。 MeowCopとは MeowCopとは、簡単に言えばStyleやLayoutなどのRuboCopのStyle Checkerなルールを無効化し、RuboCopをLinterとして… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/linyclar/20230710/20230710111812.png" alt="&#x8A18;&#x4E8B;&#x30BF;&#x30A4;&#x30C8;&#x30EB;&#x300C;MeowCop&#x3084;&#x3081;&#x307E;&#x3057;&#x305F;&#xFF08;RuboCop&#x8A2D;&#x5B9A;&#x306E;&#x898B;&#x76F4;&#x3057;&#x3092;&#x3057;&#x3066;&#x308B;&#x8A71;&#xFF09;&#x300D;&#x30B5;&#x30E0;&#x30CD;&#x30A4;&#x30EB;" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>CrowdWorks ジャンヌチーム(技術的負債解消チーム)所属の<a href="https://linyclar.github.io/profile/">linyclar</a>です。<br/> 「片付ければ片付く、片付けないから片付かない」をモットーに技術的負債解消に取り組んでおります。</p> <p>長年放置され負債を生み出す原因となっているRuboCop設定の見直しを最近行っています。今回はその事例を共有します。なお、RuboCopでは各チェックのことをCopと言いますが、この記事では表現のしやすさから以下ルールと書いています。</p> <h2 id="MeowCopとは">MeowCopとは</h2> <p>MeowCopとは、簡単に言えばStyleやLayoutなどのRuboCopのStyle Checkerなルールを無効化し、RuboCopをLinterとしてのみ機能させるGemです。<br/> また、兄弟GemにGryという守られているStyleをコードから抽出してくれるGemがあります。<br/> 資料:<a href="http://me.pocke.me/slide-practical-rubocop/#/">実用的な RuboCop の話</a><br/> crowdworks.jpではMeowCopを導入し、上記資料にもでてくるGryを適用した状態になっていました。</p> <h2 id="MeowCopをなぜやめたか">MeowCopをなぜやめたか</h2> <p>一番目の理由は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>が消滅しているためです。<a href="https://rubygems.org/gems/meowcop/">meowcopのRubyGems</a>のページは健在で <code>gem install meowcop</code>でインストールすることはできますが、 <a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>(<code>https://github.com/sider/meowcop</code>)は404になっています。<br/> 去年の4月には更新があったようなので、それ以降となると時期的にsider.reviewサービス終了と関係があるのかなと想像していますが実際のところはわかりません。</p> <p>二番目の理由はMeowCopもGryもcrowdworks.jpにとっては不向きなGemだったためです。crowdworks.jpは10年以上開発が続いているサービスであり、RuboCopは開発開始からかなり遅れて導入されました。必然、RuboCop導入当時の<code>rubocop_todo.yml</code>は大量にルールが無効化されている状態となっていました。<br/> そういった状況ではGryの「隠れたコーディングスタイルを、.rubocop.ymlに抽出する」というコンセプトが機能せず、MeowCopが存在しているルール違反を追認する形になってしまい、大半のLayoutやStyleのルールが無効な状態となっていました。<br/> わかりやすい例を挙げれば、<code>Layout/*Indentation</code>系のインデントルール、<code>Layout/Space*</code>系のスペースルールすら違反箇所が大量に出てくる状態です。</p> <h2 id="どうやめたか">どうやめたか</h2> <p>.rubocop.ymlに書かれている以下の設定を削除し、Gemfileから<code>gem 'meowcop'</code>を除去した上で<code>bundle install</code>を実行すれば削除は完了です。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">inherit_gem</span><span class="synSpecial">:</span> <span class="synIdentifier">meowcop</span><span class="synSpecial">:</span> <span class="synStatement">- </span>config/rubocop.yml </pre> <p>しかし、当然ながらMeowCopが無効化していたルールが有効化されるので、対処しなければなりません。<br/> .rubocop_todo.ymlに追加するでも良いのですが、Metrics系の設定も入っているためMeowCopから設定ファイルを取り込むことにしました。<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>が消滅しているので、インストール済のgemからサルベージしました。<br/> rbenvを利用されているなら<code>~/.rbenv/versions/{Rubyバージョン}/lib/ruby/gems/{Rubyバージョン}/gems/meowcop-3.2.0/config/rubocop.yml</code>にMeowCopのルールがあります。</p> <h2 id="ルールの見直し活動">ルールの見直し活動</h2> <p>ここまでMeowCopについてお話ししましたが、もともとは私の個人的な「なんでこんなにRuboCopのルールがルーズなのか」という憤りに端を発したRuboCop、Reekルール見直し活動があり、MeowCopの除去はその活動の一環でした。そのため、本番はここからでMeowCopによって無効化されていたルールを見直すという活動を現在しています。</p> <p>していることは非常に単純で一週間に一回無効化されているルールを選んでslackで意見を募集し、合意が取れたら有効化するというやり方を現在はしています。</p> <p>しかし、無効化されているルールは300件ほどありあまりにも時間がかかること、議論が紛糾して時間を浪費するリスクがあること、有効化と同時並行でルールに違反したコミットがマージされてCIがfailするリスクがあることなどから、すべてのルールを見直すところまではいかないだろうと考えています。</p> <p>現状で有効にしても警告がでないルールを有効化したり、似たルールをまとめて見直すなどの工夫でペースアップを図っていきたいところです。</p> <h2 id="個人的な雑感">個人的な雑感</h2> <p>みなさん<a href="https://www.lambdanote.com/products/polished-ruby">研鑽Rubyプログラミング</a>は読まれたでしょうか。同意できるところが多く個人的にお勧めできる書籍です。この本の6章でもRuboCopの話題があります。</p> <p>そして、こう書いてあります。私が同意できない数少ない箇所の一つです。</p> <blockquote><p>RuboCopを導入するにあたって、「デフォルトで有効にされているすべてのCopに目を通して適用可否を判断する」のは大変です。<br/> (中略)<br/> 設定パラメーターのAllCops: DisabledByDefaultです。この設定を使うことで、自分たちにとって有用だと確信を持てる構文の検査だけを有効にできます。<br/> 研鑽<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>プログラミング <a class="keyword" href="https://d.hatena.ne.jp/keyword/156p">156p</a></p></blockquote> <p>個人的な経験と現職の状況をみて思うことは「頼むからデフォルトで有効なものはそのままにしておいてくれ!」です。メトリクス系のルールは無効化しようという本書の提案には同意しますが、有効なルールの適用可否を判断するのが大変なら逆の場合でも同じことです。</p> <p>現在MeowCopによって無効化されているルールは300件近くあり、常識的に考えて絶対適用されているべきだと私が考えるものは180件近くあります。逆に無効にしておいた方が良い、または良いかもと思うものは20件もありません。件数的に有効から無効に見直す方が効率的なのは明らかです。</p> <p>また、無効化すればRuboCopは沈黙します。沈黙したルールを見つけて有効化するのと、うるさく警告してくるルールを無効化するのであれば、後者の方が効率が良いでしょう。</p> <p>こういった理由から、私はデフォルト設定でスタートして不要なルールを無効化する方法をお勧めします。</p> <h2 id="CrowdWorksにおける技術的負債解消">CrowdWorksにおける技術的負債解消</h2> <p>このように、長く運用されている関係で基本的なところの問題が放置されていることがあるのですが、声を上げれば改善していける土壌はあり、「誰からも賛同が得られない」とか「そんなことしている暇があるなら」みたいな空気感はありません。RuboCopルールの見直しも、誰に許可を取るわけでもなく私が勝手に始めたことです。もちろん、通常業務をこなした上でとか、結果の合意を取るといった前提はありますが。</p> <p>なので、冒頭でいったモットー「片付ければ片付く、片付けないから片付かない」において片付けられない組織上の理由は基本的にありません。片付けると手を上げれば片付けることができます。</p> <h2 id="We-are-hiring">We are hiring!</h2> <p>CrowdWorksではサービスが持続的に成長するために「自分が片付ける」と手を挙げてくれるチームメンバを募集しています! ご興味ある方は、ぜひお気軽にご連絡ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fherp.careers%2Fv1%2Fcrowdworks%2FocYTBzyizJGX" title="エンジニア/プロダクト開発部 - 株式会社クラウドワークス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://herp.careers/v1/crowdworks/ocYTBzyizJGX">herp.careers</a></cite></p> linyclar 元営業エンジニアのお話 hatenablog://entry/820878482943437087 2023-06-26T10:00:00+09:00 2023-06-26T10:00:04+09:00 こんにちは。2023年3月に入社した武藤です。法人様向けオンラインアシスタントサービス『ビズアシ』(以下ビズアシと省略)の開発を担当しています。 前職ではTypeScriptとPythonをメインの開発言語としていましたが、クラウドワークス入社後は未経験のRuby(主にRails)とPythonで開発をしています。 ビズアシはプロダクトごとに異なる開発言語で構成されているため、以前の開発手法とギャップが少なくストレスフリーで働けています。 入社後3ヶ月で感じた良いところと前職にはあったけどクラウドワークスに無いものを書いていきます。 <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuji_muto/20230621/20230621134918.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。2023年3月に入社した武藤です。法人様向けオンラインアシスタントサービス『<a href="https://bizasst.jp/client/">ビズアシ</a>』(以下ビズアシと省略)の開発を担当しています。</p> <p>前職ではTypeScriptと<a class="keyword" href="https://d.hatena.ne.jp/keyword/Python">Python</a>をメインの開発言語としていましたが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークス入社後は未経験の<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>(主に<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>)と<a class="keyword" href="https://d.hatena.ne.jp/keyword/Python">Python</a>で開発をしています。</p> <p>ビズアシはプロダクトごとに異なる開発言語で構成されているため、以前の開発手法とギャップが少なくストレスフリーで働けています。</p> <p>入社後3ヶ月で感じた良いところと前職にはあったけど<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスに無いものを書いていきます。</p> <h2 id="目次">目次</h2> <ul class="table-of-contents"> <li><a href="#目次">目次</a></li> <li><a href="#簡単な経歴">簡単な経歴</a></li> <li><a href="#入社した理由">入社した理由</a></li> <li><a href="#クラウドワークスの良いところ">クラウドワークスの良いところ</a><ul> <li><a href="#ベンチャーライクな開発手法">ベンチャーライクな開発手法</a></li> <li><a href="#コアタイムなしのフルフレックスタイム制とフルリモートワーク">コアタイムなしのフルフレックスタイム制とフルリモートワーク</a></li> </ul> </li> <li><a href="#クラウドワークスに無いもの">クラウドワークスに無いもの</a><ul> <li><a href="#1チームに1-Nのインフラメンバー">1チームに1-Nのインフラメンバー</a></li> </ul> </li> <li><a href="#さいごに">さいごに</a></li> <li><a href="#Were-hiring">We're hiring!</a></li> </ul> <h2 id="簡単な経歴">簡単な経歴</h2> <p>大学卒業後、某家電量販店の販売員として就職しました。学生時代からファッションとガジェット(主に入力機器)が好きで、平日はアルバイト先の原宿・表参道エリアへ、休日には<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BD%A9%CD%D5%B8%B6">秋葉原</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C4%A5%AF%A5%E2">ツクモ</a>に通う生活を送っていました。</p> <p>入社後は縁あってPC周辺機器の売り場を任せられ、3年目の頃には複数あるカテゴリの売上を一任される部門長という役職に就きました。</p> <p>当時は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%A2%A5%EB%C5%B9%CA%DE">リアル店舗</a> vs ECの対立図が社会的に取り沙汰され、社内的にも各々の領域でプロモーションを打ち合う紛争?が起きていました。</p> <p>如何に利用者に体験をさせるかに重きを置いていた<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%A2%A5%EB%C5%B9%CA%DE">リアル店舗</a>にも陰りが見られたタイミングで友人より声が掛かり社員数15名のIT<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%E3%A1%BC">ベンチャー</a>へ転職しました。</p> <p>入社後は法人営業として事業の売上をどう上げるかを考えて行動に移し、売上が安定する頃には簡単なツールですが<a class="keyword" href="https://d.hatena.ne.jp/keyword/Excel">Excel</a> <a class="keyword" href="https://d.hatena.ne.jp/keyword/VBA">VBA</a>で売上管理表を自前で作成していました。</p> <p>この経験から徐々にですが開発に楽しみを感じ、エンジニアの道へ進むきっかけとなりました。</p> <p>暫くして営業からソフトウェア開発へ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B8%A5%E7%A5%D6%A5%C1%A5%A7%A5%F3%A5%B8">ジョブチェンジ</a>しました。中小<a class="keyword" href="https://d.hatena.ne.jp/keyword/SIer">SIer</a>で開発業務、プライムベンダーで幾つものプロジェクトでPjMを経験したのちに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスへ入社しました。</p> <h2 id="入社した理由">入社した理由</h2> <p>前職のプライムベンダーでは準委任契約が中心で要件定義→開発→完了→納品→次のクライアントの要件定義から入るというサイクルのため、実際の利用者からのフィードバックが無いまま本当に良いプロダクトを提供出来ているのかが全く見えない状況にモヤモヤしていました。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスは幾つもの事業が存在し、社外・社内問わず利用者がいるサービスを展開しているため、細かいフィードバックからより良いプロダクトへ昇華できることが私の期待する働き方にマッチしていると感じました。</p> <h2 id="クラウドワークスの良いところ"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスの良いところ</h2> <h3 id="ベンチャーライクな開発手法"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%E3%A1%BC">ベンチャー</a>ライクな開発手法</h3> <p>各事業部でターゲットやサービスの仕組み・特性に合わせて稼働している環境が異なります。従来勤めてきた企業では先ず母体となるサービス基盤があり、細かいプロダクトは基盤に対して機能追加することが中心であったため開発言語やサーバーは同一のものとする考えが多かったです。</p> <p>ビズアシではワーカー向けシステム、社内請求管理システムがいずれも異なる開発言語と別々の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%D7%A5%EA%A5%B1%A1%BC%A5%B7%A5%E7%A5%F3%A5%B5%A1%BC%A5%D0">アプリケーションサーバ</a>ーの構成となっており、追加や更新、障害時のメンテナンスから切り戻しまで柔軟に行なえるようになっています。このような手法が取れるのは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%E3%A1%BC">ベンチャー</a>っぽさがあって良いと感じます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fprtimes.jp%2Fmain%2Fhtml%2Frd%2Fp%2F000000132.000050142.html" title="クラウドワークスの「ビズアシ」、累計稼働アシスタントが2,000人を突破" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://prtimes.jp/main/html/rd/p/000000132.000050142.html">prtimes.jp</a></cite></p> <h3 id="コアタイムなしのフルフレックスタイム制とフルリモートワーク"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%A2%A5%BF%A5%A4%A5%E0">コアタイム</a>なしのフ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EB%A5%D5%A5%EC">ルフレ</a>ックスタイム制とフルリモートワーク</h3> <p>フ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EB%A5%D5%A5%EC">ルフレ</a>ックスの恩恵は幾つかあり時間に縛られることなく働ける点と、飼い猫(11歳 ♀)が病気持ちのため定期的に通院する必要があるのですが中抜けや早退のスケジュールを組みやすい点が個人的に大きいと感じています。</p> <p>またリモートワークのルールに則り事前に申請を行うことでオフィス以外の環境でも業務に専念できるところにメリットを感じています。</p> <p>反面デメリットで挙げられる会話・コミュニケーションが希薄となってしまう点はチーム内で『Gather』というデジタルオフィスのツールを導入することで解消されています。</p> <p>※どのコミュニケーションツールを使うかはチームに委ねられてます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.gather.town%2F" title="Gather | Building better teams, bit by bit" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.gather.town/">www.gather.town</a></cite></p> <h2 id="クラウドワークスに無いもの"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスに無いもの</h2> <h3 id="1チームに1-Nのインフラメンバー">1チームに1-Nのインフラメンバー</h3> <p>一つのプロジェクトに対して一人以上のインフラメンバーが就き本番環境構築やコンテナデプロイの経路構築などを実施していました。</p> <p>勿論一つのプロジェクトに専任では無いため、多いときには1週に5つのプロジェクト進行を兼任しているメンバーもいました。</p> <p>ただ、この体制は開発ベンダー特有のもので<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスとは相性が良くないと感じます。</p> <h2 id="さいごに">さいごに</h2> <p>私の場合は営業から開発へ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B8%A5%E7%A5%D6%A5%C1%A5%A7%A5%F3%A5%B8">ジョブチェンジ</a>していますが、世の中には様々な職種から開発に転向する人もおり、実際に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークス社内にも営業として入社後に開発へ転向したメンバーもいるためケースとしては少なくないと感じています。</p> <p>私は開発の仕事に誇りを持っているのと同時に他の仕事にも尊敬の念を抱いており、お互いに働きやすい仕組みをプロダクトから提案するのも重要な仕事と捉え日々取り組んでいます!</p> <p>以上、ビズアシ開発チームの武藤でした。有難うございました!</p> <h2 id="Were-hiring">We're hiring!</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークス社では、様々なポジションのエンジニアを募集しております。 ご興味のある方は、ぜひご連絡ください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcrowdworks.co.jp%2Fcareers%2F" title="採用情報 | 株式会社クラウドワークス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://crowdworks.co.jp/careers/">crowdworks.co.jp</a></cite></p> yuji_muto 2023年2月に入社した二人で(勝手に)対談してみた hatenablog://entry/820878482937568786 2023-06-14T10:54:21+09:00 2023-06-14T10:54:21+09:00 初めまして!2023年2月に株式会社クラウドワークスへ中途入社した馬渕です。 2023年5月で入社して約4ヶ月経過しました。 今回は同じ日に入社しました牛嶋さんと、入社後の感想などを(勝手に)対談形式でお話していきます。 中途入社 地方在住 初めてのオフショア開発 こんな方の参考になると思います。 またこの記事では 入社したあとってどんなことをやって、どんな働き方になるの? スクラム開発ってどんな感じ? 開発で英語を使うってどんな感じ? クラウドログってどんな技術を使っている? こんな内容を盛り込みました。 ぜひ最後までお付き合いください! <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yusuke_mabuchi/20230605/20230605090439.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> 初めまして!2023年2月に株式会社<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスへ中途入社した馬渕です。</p> <p>2023年5月で入社して約4ヶ月経過しました。</p> <p>今回は同じ日に入社しました牛嶋さんと、入社後の感想などを(勝手に)対談形式でお話していきます。</p> <ul> <li>中途入社</li> <li>地方在住</li> <li>初めてのオフショア開発</li> </ul> <p>こんな方の参考になると思います。</p> <p>またこの記事では</p> <ul> <li><a href="#onboarding">入社したあとってどんなことをやって、どんな働き方になるの?</a></li> <li><a href="#scrum">スクラム開発ってどんな感じ?</a></li> <li><a href="#english">開発で英語を使うってどんな感じ?</a></li> <li><a href="#tech">クラウドログってどんな技術を使っている?</a></li> </ul> <p>こんな内容を盛り込みました。</p> <p>ぜひ最後までお付き合いください!</p> <h1 id="自己紹介">自己紹介</h1> <p><strong>馬渕</strong> では牛嶋さんから軽く自己紹介をお願いできますか?</p> <p><strong>牛嶋</strong> 牛嶋一登です。現在、社会人3年目で、前職は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>広告配信システムのバックエンドをメインに行っていました。</p> <p>では、馬渕さんお願いします。</p> <p><strong>馬渕</strong>  馬渕勇介です。前職は受託開発企業で主にバックエンド開発とインフラ周りを行っていました。</p> <h1 id="今担当していること">今担当していること</h1> <p><strong>馬渕</strong> 現在担当している業務の紹介をお願いします。</p> <p><strong>牛嶋</strong> 現在はフロントエン<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>でデザインシステムの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>実装を行っています。これからは新規画面の作成をマネージャー1人、エンジニア4人、デザイナー1人の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>チームでエンジニアを担当します。</p> <p><strong>馬渕</strong>  2023年3月までは外部の勤怠システムとの連携機能のバックエンド開発を行っていました。4月から牛嶋さんと同じプロジェクトで、主にバックエンドを担当する予定です。</p> <p>入社直後は別のチームだったのであまり関わりが無さそうですね、なんて言っていましたが同じチームになりましたね!よろしくお願いします!</p> <p><a id="onboarding"></a></p> <h2 id="オンボーディング">オンボーディング</h2> <p><strong>牛嶋</strong> 入社してオンボーディングがあったと思いますが、バックエンドのオンボーディングはどのようなことをされましたか。</p> <p><strong>馬渕</strong> <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログで設定できるカテゴリマスタのエクスポート機能を実装しました。</p> <p>一口にエクスポート機能といっても、ファイルの種類・<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%F3%A5%B3%A1%BC%A5%C9">エンコード</a>の種類・言語の種類をカバーしないといけなかったので、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>のパターン網羅がなかなか大変でした。</p> <p>あとは各種ドキュメントの整備がしっかりしているので、<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>設計やテスト仕様書、外部公開している<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>ドキュメントの修正などもありました。</p> <p><strong>牛嶋</strong> フロントエンドのオンボーディングの一環として、既存UIライブラリの移行とテスト拡充に取り組みました。既存UIライブラリの移行では、ローカル環境での挙動や、Storybookを中心に確認を進めていきました。Storybookとは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の状態を一つずつ、視覚的にテストするためのUI開発環境です。</p> <p>また、テスト拡充では、React-Testing-LibraryとJestを初めて触りながら学びました。React-Testing-Libraryは、React<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の振る舞いをテストするためのライブラリであり、Jestは<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>のテスト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>です。React-Testing-LibraryとJestを使って、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>やカスタムフック単位でのテストを拡充しました。</p> <p><strong>馬渕</strong> フロント側でも結構テストに力入れているんですね!テストを書くこと自体は大変だと思いますが、後の開発に役に立つと言うか、安心して開発できる感じが良いですよね。</p> <p><strong>牛嶋</strong> オンボーディングを通して、採用されている<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%B6%A5%A4%A5%F3%A5%D1%A5%BF%A1%BC%A5%F3">デザインパターン</a>や<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成など、開発における基本的な知識を学びました。</p> <p><strong>馬渕</strong> 私もオンボーディングを通して、設計から実装、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%B0%A5%EC%A5%C3%A5%B7%A5%E7%A5%F3">リグレッション</a>テストと一通りの開発の流れを経験できたと思います。</p> <h1 id="地方在住">地方在住</h1> <p><strong>馬渕</strong> お互い地方在住ですが、現在の住んでいるところ・働き方・出社の頻度はどんな感じですか?</p> <p><strong>牛嶋</strong> 私は現在<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B7%A7%CB%DC%B8%A9">熊本県</a>在住です。基本的にはリモートワークです。入社時の説明会で一度東京の本社に行きましたが、その他、全社や事業部の全体会などでは福岡営業所や大阪営業所などに出社しています。</p> <p><strong>馬渕</strong> 私は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C0%C5%B2%AC%B8%A9">静岡県</a>在住で、牛嶋さんと同じく基本的にリモートワークです。全社や事業部の全体会があるときは東京へ出社することがあります。今のところ四半期に1回くらいのペースのようなので、ほぼリモートワークといった感じです。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログチームの全体を考えると関東圏が多いイメージですが、九州・関西・東海と各地にも何名かメンバーが在籍していますよね。</p> <p>あ、あとフィリピンにも(笑)</p> <h1 id="クラウドワークスクラウドログに決めた理由"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークス、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログに決めた理由</h1> <p><strong>馬渕</strong> 牛嶋さんが<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスに転職を決めた理由って何ですか?</p> <p><strong>牛嶋</strong> 環境がマッチしていると思ったからです。まず、技術面についてですが、React x TypeScriptのモダン技術に触れたかったという気持ちがありました。</p> <p><strong>馬渕</strong> 前職ではもともとバックエンドの業務をされていて、フロントエンドに転向されたんですね。何かきっかけってあったのですか?</p> <p><strong>牛嶋</strong> きっかけはUIデザインを勉強したことですね。個人開発をしているとフロントのコーディングもそうですが、デザインもやりますよね。私はデザイン力が壊滅的だったのでUIデザインを勉強してました。</p> <p><strong>馬渕</strong> おお!個人開発されているんですね!私はMaterial-UIとかでお茶を濁していましたが(笑)</p> <p><strong>牛嶋</strong> それから、より<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A5%E9%A5%AF%A5%C6%A5%A3%A5%D6">インタラクティブ</a>なUIを実装を進めようとする中でReactを勉強して、「こっちで進んでみたい!」と思った経緯ですかね。</p> <p>次に労働環境についてですが、英語を使った環境で働きたい、また地方でのリモートワークが可能、という気持ちがありました。</p> <p><strong>馬渕</strong> 私も英語を話せるようになりたい、という気持ちがずっとあったのですが、入社を機に英語に向き合う覚悟を決めました(笑)</p> <p><strong>牛嶋</strong> 馬渕さんはどうですか?</p> <p><strong>馬渕</strong> もともとバックオフィス関係の業務を行っていたこともあって、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログというサービスに興味を持ったことがきっかけです。あとは、牛嶋さんと同じで英語を使って仕事をしてみたいと思ったところです。</p> <p>それぞれの面談や面接でお話させて頂いた方の印象が良くて、一緒に働いてみたい、と思ったことも大きなきっかけでした。</p> <p><strong>牛嶋</strong> それは私も完全に同意です!</p> <p><strong>馬渕</strong> 面談時なんですが、みなさん全体的にやわらかな雰囲気を持ちながらも、内側では強い思いや情熱がある・・・そんな印象を受けました。まだ数ヶ月程度ですが、現在でもその印象は変わらないですね。</p> <h1 id="クラウドログの良いところ"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログの良いところ</h1> <p><strong>馬渕</strong> お互い<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログ事業部への配属ですが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログチームの良いところってどんなところだと思いますか?</p> <p><strong>牛嶋</strong> まず、エンジニア一人一人が主体性を持って開発を進めているところが良いと思います。</p> <p>あとは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログは、株式会社<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスの1サービスですが、事業部が一つの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%E3%A1%BC%B4%EB%B6%C8">ベンチャー企業</a>のようで、社員全員が<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログを良くしていこう!という気持ちで業務にあたっているところも良いところですね。</p> <p><strong>馬渕</strong> 確かにチームとしての一体感がありますよね!先日行われた下半期全社キックオフの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログチームの発表、盛り上がりましたね!</p> <p>私が思う良いところですが、「みんなでなんとかしてやろう!」っていう雰囲気が開発だけでなくチーム全体で感じられるところです。あとみんな<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログというサービスが好き、っていう感じが伝わってきます。</p> <p>技術面で言うと、フロントエンドもバックエンドも習熟しているエンジニアが在籍しているので、日々刺激をもらえるところも良いところです。</p> <p>勉強熱心な方も多くて、毎週やっているLogStudyというイベントは内容も盛りだくさんでいつもためになってます。</p> <p><a id="scrum"></a></p> <h1 id="スクラムイベントについて"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>イベントについて</h1> <p><strong>馬渕</strong> <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログでは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>のイベントが結構ありますが、その点についてはどうですか?</p> <p><strong>牛嶋</strong> DSUやCheckIn、スプリントレトロスペクティブ、スプリントプランニングなど基本的なイベントがあります。また、PdMチームも<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>開発の勉強会を行っているので、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>開発がやりやすいです。</p> <p>馬渕さんは<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>イベントにどんな印象を持ちましたか。</p> <p><strong>馬渕</strong> 前職で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>開発の経験がありますが、イベントの数でいうと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログの方が多くて、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>のフォーマットに則って開発が進んでいる感じです。</p> <p><strong>牛嶋</strong> 私も前職で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>開発を経験しましたが、もっとイベントの回数や種類は少なかったですね。</p> <p>※<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>イベントについて補足</p> <ul> <li>DSU: デイリー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>のこと。毎朝同じ時間に集まり、進捗の共有や課題に感じていること、気になっていることなどを共有します。</li> <li>CheckIn: 軽い雑談会。1回につき数トピックについて話します。</li> <li>プランニング: そのスプリントで行う内容を検討します。プロダクト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>から行う内容を選び、大体の作業量をみんなで見積もります。その後、スプリント<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%AF%A5%ED%A5%B0">バックログ</a>に分解し、作業時間の見積もりまで行います。</li> <li>レトロスペクティブ: スプリントの最終日に振り返りを行います。間に合いそうにないタスクの確認、<a class="keyword" href="https://d.hatena.ne.jp/keyword/KPT">KPT</a>法を使って良かったことや問題だったこと、その改善方法について話し合います。</li> <li>デモンストレーション: そのときのスプリントで実装された機能をチーム全体に共有します。</li> </ul> <p><a id="english"></a></p> <h1 id="フィリピンメンバーとの英語コミュニケーション">フィリピンメンバーとの英語コミュニケーション</h1> <p><strong>馬渕</strong> 業務で英語を使い始めましたが、フィリピンメンバーとのコミュニケーションについてはどうですか?</p> <p><strong>牛嶋</strong> Slackやドキュメント、<a class="keyword" href="https://d.hatena.ne.jp/keyword/MTG">MTG</a>でフィリピンメンバーが関わるものについてはすべて英語でコミュニケーションを取ることになってますよね。もちろん、毎朝のDSUも英語です。毎日ドキドキしながら話していますね。</p> <p>馬渕さんは英語に自信ありますか。</p> <p><strong>馬渕</strong> いやー、正直自信はないですね(笑)入社前の面談で英語を読んだり書いたりはそれほど問題ないと思いますが、スピーキングはちょっと苦手です、、、と伝えたところ「大丈夫ですよ!全員がペラペラしゃべれるというわけではないので!」と聞いて入社したのですが、みなさん割と普通に英語を話しているという、、、(笑)</p> <p>あとは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a> MeetやSlackのhuddleの字幕機能に助けられています。</p> <p><strong>牛嶋</strong> Slackのhuddleに字幕機能なんてあるんですね、知りませんでした。私はよくDeepLに助けてもらっています。</p> <p><strong>馬渕</strong> DeepLはよく使いますよね(笑)</p> <p>あとは、週に1回「English Assemble」という英語の勉強会があるので参加しています。仕事の中でよく使われる表現や初めて聞いた表現、どんなオンライン英会話がよいかなどを共有し合ってお互い英語力を高め合うことができるのが良いですね。</p> <p><a id="tech"></a></p> <h1 id="技術面のキャッチアップ">技術面のキャッチアップ</h1> <p><strong>馬渕</strong> 入社後、特に牛嶋さんはバックエンドからフロントエンドへ転向されたこともあるので、キャッチアップとかどうしてました?</p> <p><strong>牛嶋</strong> 入社前に<a href="https://booth.pm/ja/items/2368045">りあクト</a>を個人的に購入し、ReactとTypeScriptの勉強を行いました。ボリュームはかなり多かったですが、読んで勉強になることが盛りだくさんでした。それに加えて、Reactは<a href="https://react.dev/">公式ドキュメント</a>を定期的に見返しています。</p> <p><strong>馬渕</strong> おー、素晴らしいですね、最近更新されたみたいですし。</p> <p><strong>牛嶋</strong> そうですね、フロントエンドは変化の流れも早いので追いつくために必死です。他にはReact Hook FormやJestといったライブラリ周りでしょうか。ライブラリに関しては業務の中でキャッチアップした方が効率がいいと思っていたため、入社後に取り組みましたね。</p> <p>バックエンドのキャッチアップについてはどうですか?</p> <p><strong>馬渕</strong> Goについては前職でも使っていたので、基本的なところは大丈夫でした。ですが、ライブラリや<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>は違うので現在勉強中です。ライブラリについては、Gorm、Gin、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>については、DDDやオニオンよりのレイヤード<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>ですね。</p> <p>Gormについてですが結構<a class="keyword" href="https://d.hatena.ne.jp/keyword/SQL">SQL</a>を書く感じなので、「<a href="https://www.amazon.co.jp/dp/4798157821">達人に学ぶSQL徹底指南書</a>」で<a class="keyword" href="https://d.hatena.ne.jp/keyword/SQL">SQL</a>の勉強中です。開始して数年経っているサービスなのでテーブル数も多いですし、複雑なクエリを書くケースもあるのでパフォーマンスまで意識できるようになりたいと思っています。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>に関しては、もともとフラットパッケージ構成でしか実装したことがなかったので、レイヤーごとの責務を確認するようにしています。以前、「<a href="https://pospome.booth.pm/items/1045782">pospomeのサーバサイドアーキテクチャ</a>」を読んでオニオン<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>について頭ではわかっているつもりでしたが、実際に使われている構成と照らし合わせながら落とし込んでいるところです。</p> <p>あとオンボーディング後に外部サービスの<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>から勤怠データを取得して変換して保存するというプロジェクトに携わっていたのですが、そこで使われている<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>サービスや実行の仕組みについて理解をするのに少し時間がかかりました。</p> <p>具体的には、非同期処理で実行するために<a class="keyword" href="https://d.hatena.ne.jp/keyword/Amazon">Amazon</a> SQSを使ってバッチ実行用のコンテナを起動、その後に外部<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>からデータ取得する、みたいな流れでした。<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>サービスが絡む仕組みですとローカルでの確認が若干大変なので、私が担当させてもらった内容はそれほど大きなものではなかったものの、実装は少し大変でした。</p> <p>キャッチアップが大変なところもありますが、福利厚生で半年に2万円までの書籍補助制度<span style="color: #d32f2f">*</span>があって助かっています。せっかくなので月に1冊くらい購入させていただいて、キャッチアップを深めていこうと考えています。</p> <p><strong>牛嶋</strong> 私もフロントエンドを中心に制度を利用して本を購入してます。なんだかんだプログラミングの本って値段しますから助かりますよね。</p> <p><span style="color: #d32f2f">※</span>本制度は2023年5月時点で実施されているものです。今後変更になる可能性があります。</p> <h1 id="まとめ">まとめ</h1> <p><strong>馬渕</strong> 入社4ヶ月を振り返ってみましたが、いかがでした?</p> <p><strong>牛嶋</strong> 日々勉強ですね(笑)勉強すればするほど、わからないことが出てきます。一方で、「困ったことがあったら、どんなことでも相談してきてね」という雰囲気をチームやエンジニアの皆さんから感じられる環境であることに感謝です!</p> <p><strong>馬渕</strong> リモートワークですけど、各種<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>のイベントがあるのでまめにコミュニケーションしながら開発できているのはありがたいなあ、、、と改めて思いました。</p> <p>あとは、相変わらず英語には苦戦中です!(笑)とはいえ、慣れのような気もしますので、気長にじっくり取り組んでいこうかなと思っています。</p> <h1 id="最後に">最後に</h1> <p>いいかがだったでしょうか。</p> <p>2023年2月に入社した二人で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ワークスについて振り返ってみました。</p> <p>今後も<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ログが良いサービスになるよう、より一層頑張っていきます!</p> <p>最後までお付き合いいただきありがとうございました!</p> yusuke_mabuchi