MEDLEY Developer Portal株式会社メドレーのエンジニアリング・デザイン情報を発信するポータルサイトです。https://developer.medley.jp/Thu, 19 Mar 2026 06:24:22 GMT- MongoDBのスロークエリ改善を、Datadog DBM × Claude Code × GitHub Issuesで自動化した話https://developer.medley.jp/entry/2026/03/19/153838https://developer.medley.jp/entry/2026/03/19/153838はじめに
医療プラットフォーム本部 プラットフォーム開発室 SREグループの吉田です。医療機関向けSaaSであるCLINICSの安定稼働とシステム信頼性の向上に取り組んでいます。
CLINICSではメインDBとしてMongoDBを使用してお...Thu, 19 Mar 2026 06:38:38 GMT<h1 id="はじめに">はじめに</h1>
<p>医療プラットフォーム本部 プラットフォーム開発室 SREグループの吉田です。医療機関向けSaaSである<a href="https://clinics-cloud.com/">CLINICS</a>の安定稼働とシステム信頼性の向上に取り組んでいます。</p>
<p>CLINICSではメインDBとしてMongoDBを使用しており、以下の3つの目標を掲げて、DBM(Database Monitoring)を導入しました。
なおCLINICSでは監視・オブザーバビリティ基盤としてDatadogをすでに活用(*1)していたため、Datadog DBM(Database Monitoring)を採用しました。</p>
<ul>
<li>クエリのパフォーマンス課題の早期検知</li>
<li>スロークエリの改善サイクルの向上(回数を増やす・サイクルを短くする)</li>
<li>スロークエリのナレッジを蓄積</li>
</ul>
<h1 id="背景課題">背景・課題</h1>
<h2 id="dbm導入前の運用">DBM導入前の運用</h2>
<p>MongoDB AtlasからログをダウンロードしてCOLLSCANや実行時間を分析するスクリプトを運用していました。分析の粒度はコレクション名とFind/Commandの種別に加えて、クエリシグネチャ単位での詳細な分析も行っていました。この作業を週次で実施し、スロークエリを発見・改善するサイクルを回していました。</p>
<h2 id="課題">課題</h2>
<ul>
<li>先週比の悪化検知ができない:スクリプトによる分析はある時点のスナップショットにとどまるため、時系列での比較・悪化検知が困難だった</li>
<li>本番の実行計画を取得できない:Atlasのログには詳細な実行計画が含まれないため、クエリ改善に必要な情報が不十分だった</li>
<li>スロークエリのナレッジが再利用しづらい:Google スプレッドシートにスロークエリの対応履歴を記録していたが、AI エージェントによる検索性に課題があり、過去の対応内容を参照しづらい状況だった</li>
<li>手動ログ取得に工数がかかる:MongoDB Atlas の本番 DB からログを手動でダウンロードする運用となっており、1週間分のログ取得に時間がかることから、毎週約1時間の作業工数が発生していた。</li>
</ul>
<h1 id="解決策の全体像">解決策の全体像</h1>
<p>DBMの導入により、以下の方法でそれぞれの課題を解消しました。</p>
<ul>
<li>先週比の悪化検知ができない:Datadog Dashboard を活用することで解消。週次での傾向比較やパフォーマンス悪化が検知可能に。</li>
<li>本番の実行計画を取得できない:DBMに内包されるクエリサンプリング機能により解消。実行計画(explain plan)をリアルタイムで取得・可視化できるため、クエリ改善に必要な情報を直接確認。</li>
<li>スロークエリに関するナレッジが再利用しづらい:対応方針や問題視した観点などをGitHub Issueに記録。</li>
<li>手動ログ取得による作業工数:Datadog DBM によりスロークエリの収集・分析が自動化されたため、この部分の工数がゼロに。</li>
</ul>
<p>以下が、システム構成図です。</p>
<p><img __ASTRO_IMAGE_="{"src":"./1_system_architecture_overview.png","alt":"","index":0}"></p>
<p>大きく3つのステップで構成されています。</p>
<ol>
<li>Datadogダッシュボードの構築:DBMを用いてスロークエリ・COLLSCAN・クエリ効率を可視化</li>
<li>Goスクリプトによるデータ取得:ダッシュボードの内容をJSON形式で取得し、Claude Codeが対応優先度を判定</li>
<li>GitHub Issueの自動生成:優先度が高いクエリのみをIssue化し、対応漏れを防ぐ</li>
</ol>
<h1 id="実装の詳細">実装の詳細</h1>
<h2 id="datadogダッシュボードの構築">Datadogダッシュボードの構築</h2>
<p>DBMを導入し、以下の3つの観点でダッシュボードを構築しました。</p>
<table><thead><tr><th>観点</th><th>検出条件</th></tr></thead><tbody><tr><td>COLLSCAN</td><td>プライマリーインスタンスで発生しているフルスキャンを特定</td></tr><tr><td>クエリ効率</td><td>クエリ効率が1,000以上かつドキュメントスキャン数が多いクエリ</td></tr><tr><td>新規スロークエリ</td><td>先週比で新たに発生したスロークエリを特定</td></tr></tbody></table>
<p>以下は、実際に作成したDatadog Dashboardです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2_datadog_dashboard.png","alt":"","index":0}"></p>
<h2 id="goスクリプトによるデータ取得と優先度判定">Goスクリプトによるデータ取得と優先度判定</h2>
<p>ダッシュボードで取得している内容をJSON形式で取得するGoスクリプトを作成し、事前に定めたルールに基づいて、Claude Codeが優先度を判定します。</p>
<p>優先度は以下を使用しました。</p>
<ol>
<li>PrimaryインスタンスでのCOLLSCANが発生しているクエリ</li>
<li>クエリ効率が1000以上かつ、ドキュメントスキャン数が多いクエリ</li>
<li>新規発生のスロークエリ</li>
</ol>
<p>取得しているメトリクスは以下です。</p>
<table><thead><tr><th>メトリクス</th><th>用途</th></tr></thead><tbody><tr><td><code>@mongodb.plan_summary</code> (<code>collscan_count</code>を算出)</td><td>インデックス未使用のフルスキャン検出</td></tr><tr><td><code>@mongodb.docs_examined</code>, <code>@mongodb.nreturned</code> (<code>docs_efficiency</code>を算出)</td><td>クエリ効率の評価</td></tr><tr><td><code>@duration</code> (<code>avg_duration_ns</code> / <code>p99_ns</code>を算出)</td><td>今週・先週の実行時間比較</td></tr></tbody></table>
<p><strong>pup 未対応による直接API呼び出し</strong></p>
<p>ダッシュボードの内容取得にあたり、2026年2月にリリースされた <a href="https://github.com/DataDog/pup">https://github.com/DataDog/pup</a> の使用も検討しましたが、DBMのパフォーマンス結果を集計する /api/v2/query/scalar APIに未対応だったため、APIを直接呼び出す形で実装しました。</p>
<p><strong>呼び出し元の特定</strong></p>
<p>なお、CLINICSではAPIと非同期ジョブそれぞれにミドルウェアを自作し、MongoDBクエリにAPIパスやJobクラス名をコメントとして付与する仕組みが既に導入されていました。
コメントがDBMに連携されることで、Datadogコンソール上のクエリ詳細画面にAPIパスやJobクラス名が直接表示されるようになりました。これにより、スロークエリが発生した際にどのコードが起点かをコンソール上で即座に確認できます。以前はMongoDB Atlasからログをダウンロードし、スクリプトを実行してコメントを抽出する必要があったため、1件の呼び出し元特定に数分かかっていました。この手順がなくなり、調査の初動が大幅に短縮されました。</p>
<h2 id="github-issueへの自動登録">GitHub Issueへの自動登録</h2>
<p><strong>QuerySignatureによるIssue管理</strong></p>
<p>スロークエリを一意に識別できるよう、スロークエリ専用のTagをGitHub 上で作成し、IssueのタイトルにQuerySignatureを含めるようにしました。
これにより、同一クエリの重複Issue作成を防ぎ、過去の対応履歴をIssueで追跡できます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>(例) 【スロークエリ改善】sales - 売上月次比較 aggregate クエリ (qs:bed4b54513c9204e)</span></span></code></pre>
<p><strong>改善困難クエリのナレッジストック</strong></p>
<p>問題はあるが解消が難しいクエリ(例:クエリ対象をMongoDBからOpenSearchに変更する必要があるケースなど)はIssueにストックしておき、
既知のクエリとして扱うことで新規スロークエリの検出に集中できるようにしました。</p>
<p>以下は、サンプルのGitHub Issueです。(*2)</p>
<p><img __ASTRO_IMAGE_="{"src":"./3_github_issue_1st_half.png","alt":"","index":0}">
<img __ASTRO_IMAGE_="{"src":"./4_github_issue_2nd_half.png","alt":"","index":0}"></p>
<p><strong>Claude Codeスキルとしての統合</strong></p>
<p>上記の「Goスクリプトによるデータ取得・優先度判定」と「GitHub Issueへの自動登録」の一連のステップは、Claude Codeのスキルとしてまとめています。週次作業時はこのスキルを実行するだけで、スロークエリの検出からIssue生成までが自動的に完了します。</p>
<h1 id="効果">効果</h1>
<ul>
<li>週次の作業工数を1時間から10分に削減:MongoDB Atlasからログをダウンロードして手動で分析していた作業が、Claude Codeスキルにまとめられ、スキルの実行と結果の確認のみになり、週次の作業工数を削減できました。</li>
<li>スロークエリのナレッジを蓄積できる基盤の構築:改善困難なクエリや過去の対応履歴をGitHub Issueにストックすることで、スロークエリに関するナレッジを体系的に蓄積・参照できる基盤を整えることができました。これにより、新規スロークエリの検出に集中できるようになり、チーム内での知見共有も容易になりました。</li>
</ul>
<h1 id="今後の展望">今後の展望</h1>
<p>今回はパフォーマンス課題の発見・Issue化までを自動化しました。最終的には「AIが課題を発見しPRを作成し、人間が責任を持ってリリースする」サイクルの実現を目指しており、次のステップを検討しています。</p>
<ul>
<li>PR作成の自動化:AI推進グループ(*3)と連携し、インデックス追加などのPR作成まで自動化します。</li>
<li>スロークエリデータの長期保存基盤の構築:Datadog DBMのデータ保持期間は2週間であるため、現状では長期的なトレンド分析ができません。Goスクリプトで取得したデータをS3に蓄積し、Athenaで分析する基盤を構築することで、月単位・四半期単位でのパフォーマンス推移の可視化や、改善施策の効果測定を可能にします。</li>
<li>パフォーマンス評価の自動化:DBメトリクスも合わせて総合的な評価を行います。</li>
</ul>
<h1 id="まとめ">まとめ</h1>
<ul>
<li>Datadog DBMの結果からルールベースでスロークエリを自動検出し、Claude Codeスキルを活用してGitHub Issueを自動生成する基盤を構築しました。</li>
<li>最終目標はAIがパフォーマンス課題の発見からPR作成までを担い、人間が責任を持ってリリースすることです。</li>
<li>本記事ではその第一歩として、課題の発見・Issue化までの自動化を解説しました。</li>
</ul>
<p>メドレーでは、SREをはじめ「医療ヘルスケアの未来」を共に創っていくエンジニアを大募集中です!少しでもご興味をお持ちいただけましたらぜひ、ご応募お待ちしております!</p>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs?category=2144686643002470401" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">株式会社メドレー エンジニア の求人一覧</div>
<div class="remark-link-card-plus__description">株式会社メドレー エンジニア の求人一覧です。| HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:FFFFFF,c_mpad,h_200,w_200/m/2468/images/2148742169805250560_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs/1000036" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">SREエンジニア/医療プラットフォーム本部 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">SREエンジニア/医療プラットフォーム本部(株式会社メドレー)の求人情報です。 | HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:333333,c_pad,w_978,h_513/m/577c/images/2118206564349132800_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>※カジュアル面談も大歓迎です!ご希望の際は、「その他の項目(希望記入欄)」にてその旨をご記載ください。</p>
<hr>
<p><strong>注釈</strong></p>
<p>*1: CLINICSでのDatadog活用事例については、こちらの記事を参照ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://findy-tools.io/products/datadog/5/634" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">CLINICSの信頼性を守るDatadog 導入・活用事例(株式会社メドレー)</div>
<div class="remark-link-card-plus__description">株式会社メドレーのDatadogのレビューです。導入に至った背景や導入検討時の悩み、実際に導入してから感じたDatadogのメリットと課題感を導入効果とともにレビューしていただきました。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=findy-tools.io" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">findy-tools.io</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://findy-tools.io/products/datadog/5/634/opengraph-image?de479d738d5ec9b6" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>*2: 具体的なデータは、全てダミーデータを使用しています。</p>
<p>*3: AI推進グループは、「AIで『医師』と『エンジニア』の生産性を最大化する」というミッションのもとで、プロダクトへのAI機能導入と開発者の生産性向上に取り組む組織です。詳しく知りたい方は、以下を参照ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.youtube.com/watch?v=1Qnow5mmWaI" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title"> - YouTube</div>
<div class="remark-link-card-plus__description">Enjoy the videos and music you love, upload original content, and share it all with friends, family, and the world on YouTube.</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.youtube.com/s/desktop/c376667c/img/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.youtube.com</span>
</div>
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://note.com/medley/n/n55214b43dafc" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title"> 「ゼロ距離医療」実現へ。医師とエンジニアの生産性最大化にAIで挑戦|メドレー公式note</div>
<div class="remark-link-card-plus__description">皆さん、こんにちは! 株式会社メドレーでDevRelをしている重田です。 メドレーの開発組織で働く社員を「入社エントリーブログ」の形式でご紹介します🎉 今月ご紹介するのはAI推進グループ エンジニアの大塚さんです。 ぜひご一読いただき、メドレーに興味を持っていただけたら嬉しいです! まず初めに自己紹介をお願いします 大塚と申します。現在は医療プラットフォーム本部の医科診療所プロダクト開発室 AI推進グループでマネジャーを務めており、主にクラウド診療支援システム「CLINICS」に携わっています。 AI推進グループとは? AI推進グループは、「AI で ”医師”と”エンジニア”</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://assets.st-note.com/poc-image/manual/note-common-images/production/svg/production.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">note.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://assets.st-note.com/production/uploads/images/212348380/rectangle_large_type_2_ac92fb5f5fbaa8f1add3fd22a121bb76.png?fit=bounds&quality=85&width=1280" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- Workday Rising 2025 参加レポート - 海外カンファレンスに参加しました!https://developer.medley.jp/entry/2026/02/24/151816https://developer.medley.jp/entry/2026/02/24/151816はじめまして!コーポレートIT室の種田と申します。
コーポレートIT室では全従業員が利用するデバイス、ネットワークインフラ、 SaaS といった IT 全般を統括しています。
メドレーでは自社開発のプロダクト、M&Aで取得したプロ...Tue, 24 Feb 2026 00:00:00 GMT<p>はじめまして!コーポレートIT室の種田と申します。
コーポレートIT室では全従業員が利用するデバイス、ネットワークインフラ、 SaaS といった IT 全般を統括しています。</p>
<p>メドレーでは自社開発のプロダクト、M&Aで取得したプロダクト、またローカルおよびグローバルのプロダクトを、会社や国を越えて統一的に管理・運用することを目指す「Global One」という思想を掲げています。
プロセスやシステムにおいても、この思想に基づき、個別最適に陥ることなく、グローバルに共通して適用できる標準的なIT基盤を構築することが求められています。</p>
<p>コーポレートITでシステムを選定するときも、「グローバルで標準的に活用できること」が観点のひとつとなっています。Google Workspace、Slack、Confluence、ServiceNow、GitHubといった、各領域でデファクトスタンダードとなっているものを導入・活用しています。</p>
<p>メドレーの人事基幹システムは、2022年からグローバル共通で「Workday」を利用しています。私はコーポレートITとしてWorkdayの導入プロジェクトに参加し、現在は運用や新規機能の導入検討などを担当しています。
昨年の9/15-18に、サンフランシスコで開催されたWorkdayの年次カンファレンス「Workday Rising 2025」に初めて参加してきました。開催から少し日数が経過していますが、当日の熱気あふれる会場の様子や海外カンファレンス初参加の感想をお届けします。</p>
<h1 id="会場の様子">会場の様子</h1>
<p>会場はサンフランシスコ Moscone Centerです。合計で500以上のセッションがありました。会場移動とインプットの繰り返しでかなりタフな4日間を過ごしました(最終日が終わった後、ばっちり風邪を引きました)
事前に参加セッションの計画を立てていたのですが、期間中に発表された新機能に関する追加セッションも多くありました。Workdayの機能ごとのブースや協賛企業のブースも多く、4日間ではとても回りきれません!ほとんどのセッションは後から動画が公開されるので、日程後半は会場でしか体験できないハンズオンセッションやブースを中心に参加していました。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG1048.jpg","alt":"workdayrising","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG6803.jpg","alt":"workdayrising","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG6008.jpg","alt":"workdayrising","index":0}"></p>
<h1 id="おもしろかったセッションブース">おもしろかったセッション・ブース</h1>
<h2 id="keynote-the-workday-platform-transforming-work-with-agentic-ai">Keynote: The Workday Platform: Transforming Work with Agentic AI</h2>
<p>AI関連企業の買収やAIを中心とした新機能の発表など、初めて聞くことばかりでとてもわくわくしました。WorkdayでのAIエージェント開発・管理・実行やWorkday外のさまざまなデータとの連携を加速させる動きを感じました。発表された新機能の中で、Workdayのデータをゼロデータコピーで外部システムと連携できる「Workday Data Cloud」が個人的にとても気になりました。
セッションの中でも、Workdayは人事基幹システムから「エンタープライズAIプラットフォーム」へ位置づけを変化していくと話されていました。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG6794.jpg","alt":"workdayrising","index":0}"></p>
<h2 id="equipping-you-to-manage-workday-learning-a-hands-on-lab">Equipping You to Manage Workday Learning: A Hands-On Lab</h2>
<p>Workdayの学習管理システム(LMS)である「Workday Learning」の研修コンテンツ作成をハンズオンで学べるセッションがありました。ひとり1台パソコンが準備されており、ガイドに沿って手を動かしていきます。(意外と簡単に実装できそうでした!)Workday Learningのハンズオンは日本では開催されないため、良い経験になりました。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG1087.jpg","alt":"workdayrising","index":0}"></p>
<h2 id="workdayブース">Workdayブース</h2>
<p>Workdayを開発しているエンジニアの方がブースにいたので、新機能のデモだけではなく、細かな質問にも対応してもらえるのがとてもよかったです!(ただし、私は英語ができないので他の方が質問しているのを聞いて理解するにとどまりました…もっと話せるようになりたい)</p>
<h2 id="協賛企業ブース">協賛企業ブース</h2>
<p>協賛企業ブースには、日本ではあまり知られていないHRTechサービスのブースが多かったです。ここでもAI関連のサービスが多かった印象です。気になったサービスがいくつかあったのでご紹介します!</p>
<h3 id="techwolf">TechWolf</h3>
<div class="remark-link-card-plus__container">
<a href="https://www.techwolf.ai/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">TechWolf | The intelligence to act with confidence</div>
<div class="remark-link-card-plus__description">TechWolf is the foundational data layer for workforce transformation in large enterprises. For the first time ever, you can use accurate skill data to hire smarter, improve learning, fix internal mobility and plan your workforce with confidence.</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.prod.website-files.com/678a30b5f0a91f3580bfdf17/67bdc4b43753baa6eb7a3ef8_techwolf-favicon.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.techwolf.ai</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://cdn.prod.website-files.com/678a30b5f0a91f3580bfdf17/67d0677f610e9bb2aea74055_60e4be73c8b3d99d77cdad3da11e787d_OG-home.jpg" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>Workdayのスキル管理システムとGoogle Workspace / Slack / Jira / Confluenceを連携することで、自分のスキルプロファイルを生成してくれるSaaSがありました。日々の業務でアウトプットしたデータから自分のスキルを提案されると、自身で気づいていない意外なスキルを発見できる可能性もあって良さそうです!</p>
<h3 id="edume">eduMe</h3>
<div class="remark-link-card-plus__container">
<a href="https://www.edume.com/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Training that's built for your frontline</div>
<div class="remark-link-card-plus__description">eduMe is the AI-first frontline training platform that slots invisibly into your existing tools - helping you get better training to your frontline, faster.</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.edume.com/hubfs/eduMe%20Icon%20Square.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.edume.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.edume.com/hubfs/Homepage%20-%20Meta%20image.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>TikTokのような短時間の動画で隙間時間に学べる、デスクレスワーカーをターゲットとした研修プラットフォームです。コンテンツもPDFなどの資料からAIが自動生成してくれるため、管理者側にもメリットがあって良いと思いました。</p>
<h1 id="まとめ">まとめ</h1>
<h2 id="全体を通しての感想">全体を通しての感想</h2>
<p>グローバルシステムの年次カンファレンスへの参加は私にとって初めての経験でした。規模・セッション数ともに桁違いですし、自分が興味があることを開発エンジニアに直接聞くことができるのも年次カンファレンスならではの貴重な機会でした。
また、日本のWorkdayユーザー企業様とも交流する機会が多くありました。ユースケースやお悩みごとなど情報交換をたくさんすることができました。</p>
<p>(おまけ)最終日前日にはサンフランシスコ・ジャイアンツの本拠地であるオラクル・パークを貸し切ったパーティが開催されました。グラウンドやベンチにも入ることができたのがうれしかったです!サンフランシスコに到着した日にドジャース戦を見に行っていたので、大谷選手と同じ場所に立てた…!と思ってしまいました。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG6999.jpg","alt":"workdayrising","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG6692.jpg","alt":"workdayrising","index":0}"></p>
<h2 id="workdayの今後の活用について">Workdayの今後の活用について</h2>
<p>セッションでは、AIエージェントを含むアプリ開発やエージェントのセキュアな管理、外部とのデータ連携といった、エンジニア向けのトピックが多かったことが印象的でした。
AIエージェントとのやりとりで業務が進められていく世界観も提示されていました。Workdayでの各種申請・承認がAIエージェントとのやりとりだけで完結できると、従業員にとってうれしい未来だなと思っています。今後のアップデートも継続してキャッチアップし、便利な機能は積極的に展開していきたいです。
また、Workdayのタレントマネジメント分野ではAIを使った機能拡充が多く予定されています。従業員のタレント情報(スキル、職歴、興味など)のWorkdayへの集約を進め、新機能を活用できる状態に整えていきたいと考えています。</p>
<h1 id="おわりに">おわりに</h1>
<p>Workday Risingへの参加を通して、日本開催のイベントでは得られない多くのインプットを得ることができました。参加する機会をいただけたことを本当に感謝しています。また、このインプットを無駄にせず、今後のメドレーの人事戦略にシステムを通じて貢献していきたいと思ってます。</p>
<p>現在、メドレーでは一緒に働く仲間を募集しています。この記事やイベントを通じて興味を持っていただいた方は、ぜひお気軽にご連絡ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">株式会社メドレー </div>
<div class="remark-link-card-plus__description">株式会社メドレー です。| HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:FFFFFF,c_mpad,h_200,w_200/m/2468/images/2148742169805250560_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>※カジュアル面談も大歓迎です!ご希望の際は、「その他の項目(希望記入欄)」にてその旨をご記載ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://note.com/medley" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メドレー公式note|note</div>
<div class="remark-link-card-plus__description">「医療ヘルスケアの未来をつくる」というミッションのもと、様々な医療課題を解決するためのデジタル活用を推進する【株式会社メドレー】の公式noteです。https://www.medley.jp/</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://assets.st-note.com/poc-image/manual/note-common-images/production/svg/production.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">note.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://assets.st-note.com/production/uploads/images/35822584/2fe62e103bc45baa23b8151b1a5d840b.jpg?fit=bounds&format=jpeg&height=1024&quality=85&width=1024" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- ジョブメドレーアカデミーにおけるLLM活用事例 — Dify導入・解説自動生成・動画翻訳の実装https://developer.medley.jp/entry/2026/01/30/170047https://developer.medley.jp/entry/2026/01/30/170047Fri, 30 Jan 2026 08:00:47 GMTAI
- 医療業界の視点から解説!AWSの国内クロスリージョン推論がもたらす恩恵https://developer.medley.jp/entry/2025/12/16/151816https://developer.medley.jp/entry/2025/12/16/151816はじめに
この記事は Medley(メドレー) Advent Calendar 2025 16日目の記事です。
こんにちは。医療プラットフォーム本部 病院プロダクト統括部 開発戦略室の竹本です。re:Invent 2025 が閉幕し、今年も...Tue, 16 Dec 2025 06:18:16 GMT<h1 id="はじめに">はじめに</h1>
<p><strong>この記事は <a href="https://qiita.com/advent-calendar/2025/medley">Medley(メドレー) Advent Calendar 2025</a> 16日目の記事です。</strong></p>
<p>こんにちは。医療プラットフォーム本部 病院プロダクト統括部 開発戦略室の竹本です。re:Invent 2025 が閉幕し、今年も残すところあと僅かとなりました。</p>
<p>今回は Amazon Bedrock に関する記事を書いてみたいと思います。</p>
<h1 id="bedrockで国内限定のクロスリージョン推論が可能に">Bedrockで国内限定のクロスリージョン推論が可能に!</h1>
<p>少し前のAWSアップデートですが、 2025年9月29日に Amazon Bedrock で Claude Sonnet 4.5 が利用可能になると同時に、Claude Sonnet 4.5で日本国内に限定したクロスリージョン推論機能の提供が開始されました。この機能の特徴については、以下の Amazon Web Services ブログの記事に詳細が記載されています。</p>
<p><a href="https://aws.amazon.com/jp/blogs/news/amazon-bedrock-now-supports-japan-cross-region-inference/">Amazon Web Services ブログ | Amazon Bedrock で日本国内に閉じた Anthropic Claude 4.5 の推論が可能に!日本国内クロスリージョン推論のご紹介</a></p>
<p>この機能を利用すると、<strong>生成 AI の推論リクエストを日本国内の東京・大阪リージョンのみに限定して分散ルーティング</strong>させることができます。また記事の中で日本国内クロスリージョン推論を選択すべきケースのひとつとして医療業界の例が挙げられており、弊社もその例外ではありません。</p>
<p>しかし金融や医療の業界でも積極的にパブリッククラウドが活用されている近年において、なぜ国内にリクエストを閉じなければならないのかピンと来ない方もいらっしゃるかもしれません。</p>
<p>そこで医療系IT企業の視点から、このアップデートが持つ意義について徹底的に解説したいと思います。</p>
<h2 id="本記事に関する免責事項">本記事に関する免責事項</h2>
<p>本記事の中で AWSをはじめ各社が公開しているサービスの情報や 各省が公開しているガイドラインの内容について言及していますが、それぞれ 2025年12月時点の情報をもとに執筆しております。いずれも内容が改訂される可能性がありますので、最新の情報につきましては各機関より正式に公開されている情報をもとにご判断いただくようお願いいたします。</p>
<h1 id="きっかけ">きっかけ</h1>
<p>まず、本記事を書くことになったきっかけからお伝えします。</p>
<p>私が所属する開発戦略室では当時、一部のシステムで Amazon Bedrock で Claude 3.5 Sonnet のモデル ( <code>anthropic.claude-3-5-sonnet-20240620-v1:0</code> ) が利用されていました。<br>2025年12月時点では Claude 3.5 Sonnet は既に Anthropic社としてはEOSを迎えてしまったモデルであり (1) 、Amazon Bedrock ではレガシーモデルと呼ばれる扱いとして 2026年3月1日 までに別のモデルへ移行することが促されています。(2)</p>
<p>そこで、冒頭に記載した日本国内に閉じたクロスリージョン推論機能の利用が 果たして必須となるか否かをこの機会に明らかにすることにしました。</p>
<p>(1) 参考: <a href="https://platform.claude.com/docs/ja/about-claude/model-deprecations">Anthropic | Claude docs - モデルの廃止予定</a><br>
(2) 参考: <a href="https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/model-lifecycle.html">AWS | Amazon Bedrock - モデルのライフサイクル</a><br></p>
<h1 id="クロスリージョン推論の基本情報">クロスリージョン推論の基本情報</h1>
<p>まずはクロスリージョン推論についておさらいをします。</p>
<p>クロスリージョン推論とは、リージョンごとの混雑状況に応じてAWSが基盤モデルを利用可能なリージョンのエンドポイントへ自動的に推論リクエストをルーティングしてくれる機能です。リージョン単位で基盤モデルを呼び出す従来の方式と比較すると、リクエスト数のクォータ (上限) が広がりスロットリングエラーの発生を回避しやすくなります。</p>
<div style="max-width: 700px; margin: 0 auto; border: 1px solid #e1e4e8; padding: 3px; border-radius: 6px;">
<p><img __ASTRO_IMAGE_="{"src":"./cross-region-inference.png","alt":"Cross-region_inference","index":0}"></p>
</div>
<div style="text-align: center; margin-top: 5px;">
<small><a href="https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2024_Amazon-Bedrock-Model-Inference-b_0909_v1.pdf">Amazon Bedrock モデル推論 b.実践編 【Amazon Bedrock Series #02b】</a>より引用</small>
</div>
<br>
<p>Claude Sonnetでは、Claude 3.5 Sonnet以降のモデルはすべてクロスリージョン推論が実行される推論プロファイルのみ提供されています。</p>
<p>ところが、クロスリージョン推論は<strong>ユーザーが利用するリージョンを選択することはできません。</strong> 利用する推論プロファイルごとに既定のいずれかのリージョンへ送信される仕様であり、冒頭のアップデートが発表されるまではいずれも日本国外にあるリージョンが含まれておりました。(3)</p>
<p>詳細は次章で解説しますが、医療情報システムでは<strong>日本国外にある情報機器を使えない</strong>ことが原則です。
では果たして医療情報システムは海外の優れた最新AIを利用することが困難なのでしょうか。</p>
<p>(3) 参考情報:<a href="https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/inference-profiles-support.html">AWS | Amazon Bedrock - 推論プロファイルでサポートされているリージョンとモデル</a></p>
<h1 id="3省2ガイドラインの存在">3省2ガイドラインの存在</h1>
<p>医療情報を取り扱うシステムにおいて、避けて通れないのがいわゆる 3省2ガイドラインです。3省2ガイドラインとは、厚生労働省・総務省・経済産業省が策定した、医療情報を安全に取り扱うための2つの指針の総称です。</p>
<ul>
<li><a href="https://www.mhlw.go.jp/stf/shingi/0000516275_00006.html">厚生労働省 | 医療情報システムの安全管理に関するガイドライン 第6.0版(令和5年5月)</a></li>
<li><a href="https://www.meti.go.jp/policy/mono_info_service/healthcare/teikyoujigyousyagl.html">経済産業省 | 医療情報を取り扱う情報システム・サービスの提供事業者における安全管理ガイドライン</a></li>
</ul>
<p>病院等の医療機関だけでなく、弊社のように医療情報を扱うシステムを提供する事業者もまたこれらのガイドラインに記載された安全管理措置やセキュリティに対する対策を講じることが求められます。</p>
<p>そしてこのガイドラインに含まれる項目のひとつに、<strong>情報を格納する情報機器は日本国内法の適用が及ぶ場所に設置すること</strong> が求められています。</p>
<div style="max-width: 550px; margin: 0 auto; border: 1px solid #e1e4e8; padding: 3px; border-radius: 6px;">
<p><img __ASTRO_IMAGE_="{"src":"./mhlw_management.png","alt":"企画管理編 7章","index":0}"></p>
</div>
<div style="text-align: center; margin-top: 5px;">
<small><a href="https://www.mhlw.go.jp/content/10808000/001102575.pdf">厚生労働省 | 医療情報システムの安全管理に関するガイドライン 第 6.0 版 企画管理編</a> ─ 7章より抜粋</small>
</div>
<br>
<p>では、いかなる場合も日本国外のサービスを利用できないのでしょうか。<br>この疑問点を考える上で、ポイントが大きく分けると 2 点あります。</p>
<ul>
<li><strong>1. 送信された推論リクエストは、Amazon Bedrock内で情報機器に情報が格納・保存されるのか</strong></li>
<li><strong>2. もし Amazon Bedrock で情報が保存されないならば日本国外にリクエストして良いのか</strong></li>
</ul>
<h1 id="1-amazon-bedrock-は-情報を格納-するのか">1. Amazon Bedrock は “情報を格納“ するのか</h1>
<p>結論として、Amazon Bedrock では入出力するデータが保存・記録されないことが公式ドキュメントに明記されており (4) 、クロスリージョン推論であってもこの方針は変わりません。</p>
<p>(4) 参考:<a href="https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/data-protection.html">AWS | Amazon Bedrock - データ保護</a><br><br><strong>▼ 該当箇所を抜粋</strong></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>プロンプトおよび AI のレスポンスを保存またはログに記録することはありません。</span></span></code></pre>
<p>この点については、Amazon Bedrockを既に利用されている方によく知られていることかと思います。</p>
<h1 id="2--それなら国外サーバーを利用してもよいのか">2 . それなら国外サーバーを利用してもよいのか</h1>
<p>では医療情報を取り扱うシステムにおいて、グローバルクロスリージョン推論のように日本国外のサーバーに対してリクエストを送信してもよいのでしょうか。<br>これについては、厚生労働省が公開している別の資料の中に手がかりがありました。</p>
<div style="max-width: 550px; margin: 0 auto; border: 1px solid #e1e4e8; padding: 3px; border-radius: 6px;">
<p><img __ASTRO_IMAGE_="{"src":"./mhlw_qa.png","alt":"Q&A_45頁","index":0}"></p>
</div>
<div style="text-align: center; margin-top: 5px;">
<small><a href="https://www.mhlw.go.jp/content/10808000/001145860.pdf">厚生労働省 | 「医療情報システムの安全管理に関するガイドライン 第 6.0 版」 に関するQ&A</a> ─ P.45 より抜粋</small>
</div>
<p><br>この資料によると、以下の条件に該当する場合には国内法の適用を受けていないサーバーを利用可能であると記載されています。<br><br><strong>▼ 該当箇所を抜粋</strong></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>医療情報が保存されないことが、契約等において担保されている場合は国内法の適用を受けていないサーバを利用可能</span></span></code></pre>
<h2 id="awsドキュメント--契約">AWSドキュメント = 契約…?</h2>
<p>ここでまた新たな疑問が生まれます。<br>AWSドキュメントに明記されていることはこれに該当するのでしょうか。</p>
<p><strong>結論として、「契約等において担保されている」に相当するとまでは言えない</strong> でしょう。</p>
<p>なぜなら AWSドキュメントは、個別具体的な当事者間の合意内容をもとに作成した契約書とは性質が大きく異なるからです。AWSドキュメントはサービスアップデートの中で AWS社によって内容が一方的に変更されることもあり、サービスユーザーである我々はこれを受け入れる or 利用を停止する のいずれかしか選択肢がありません。</p>
<p>したがって、グローバルクロスリージョン推論の利用は明確に禁止されているとまでは言えないものの、医療情報システムのサービス提供者としては日本国内に限定したクロスリージョン推論を採用する方が無難であると考えられます。<br></p>
<h1 id="今回の結論">今回の結論</h1>
<p>以上のことから冒頭のサービスアップデートは、<strong>多くの推論リクエストを処理可能になる・ガイドラインを確実に準拠できる・最新のモデルを利用できる</strong>と多くのメリットを含む内容でした。</p>
<p>入出力トークン数が大きい AIワークロードの場合、料金が 10% 上乗せになってしまう点は痛手になりますが、プロダクトの付加価値とコンプライアンス要件の両立を可能にする選択肢が生まれたことは大きな一歩となりました。</p>
<p>多くのイノベーションが安全性とトレードオフの関係になっている中で、国内の厳格な安全基準を守りつつ最高水準のベンチマークを誇る高性能モデルを安定したインフラで利用できるようになる。<br>そんな選択肢を提供してくれた事こそ、医療業界にもたらす一番の恩恵と言えるでしょう。</p>
<h1 id="最後に">最後に</h1>
<p>本記事では、医療業界における日本国内に閉じたクロスリージョン推論の有用性について解説させていただきました。</p>
<p>私が所属する医療プラットフォーム本部では、医師の偏在や医療従事者の人材不足が引き起こす長時間労働という課題に対し 生成 AI のテクノロジーを用いた業務効率化によって、医療従事者の負担を軽減する試みに取り組んでいます。</p>
<p><strong><a href="https://www.medley.jp/release/20251204.html">メドレー、病院向け電子カルテ「MALL」で医療文書作成をAIで支援する新機能のパイロット版を提供開始</a></strong></p>
<p>このような取り組みの陰には、厳格なルールの中で技術選定を行うエンジニアの苦労と「医療ヘルスケアの未来をつくる」弊社ならではのやりがいがあることを、より多くの人に知ってもらえればと願っております。</p>
<p>本記事がどなたかの参考になれば幸いです。<br></p>
<h2 id="次回予告">次回予告</h2>
<p><a href="https://qiita.com/advent-calendar/2025/medley">Medley(メドレー) Advent Calendar 2025</a> もいよいよ後半戦!<br>次回 17 日目の担当は QA エンジニアの <a href="https://zenn.dev/daishu">@daishu</a> さんです!🎉 明日も是非お楽しみに〜!!<br><br></p>
<h1 id="were-hiring">We’re hiring!</h1>
<p>メドレーでは、「医療ヘルスケアの未来」を共に創っていくエンジニアを大募集中です!少しでもご興味をお持ちいただけましたらぜひ、ご応募お待ちしております!</p>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">株式会社メドレー </div>
<div class="remark-link-card-plus__description">株式会社メドレー です。| HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:FFFFFF,c_mpad,h_200,w_200/m/2468/images/2148742169805250560_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>※カジュアル面談も大歓迎です!ご希望の際は、「その他の項目(希望記入欄)」にてその旨をご記載ください。</p>
- pmconf 2025 参加レポート - ゴールドスポンサーとして協賛しました!https://developer.medley.jp/entry/2025/12/12/151825https://developer.medley.jp/entry/2025/12/12/151825こんにちは。人材プラットフォーム本部でプロダクトマネージャーをやっている渥美です。
メドレーは2025年12月4日に開催された「pmconf 2025」に、ゴールドスポンサーとして協賛しました。
プロダクトマ...Fri, 12 Dec 2025 00:00:00 GMT<p>こんにちは。人材プラットフォーム本部でプロダクトマネージャーをやっている渥美です。
メドレーは2025年12月4日に開催された「pmconf 2025」に、ゴールドスポンサーとして協賛しました。</p>
<div class="remark-link-card-plus__container">
<a href="https://2025.pmconf.jp/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">プロダクトマネージャーカンファレンス 2025 | pmconf 2025</div>
<div class="remark-link-card-plus__description">プロダクトマネージャー⼀⼈ひとりの成⻑と挑戦が、新たな未来を⽣み出す。pmconfは、それを信じています。 pmconf 2025は、プロダクトマネージャーの成⻑意欲と挑戦への熱量がぶつかり合う場へと進化します。 登壇者は、より良い未来を切り拓く挑戦のリアルを語る 。参加者は、切磋琢磨の渦に⾶び込み、挑戦のエネルギーを⾼める。スポンサーは、この挑戦の場を⽀え、熱狂を共に創り上げる。そして、ここで⽣まれる熱量が、未来を動かす原動⼒となる。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://storage.googleapis.com/production-os-assets/assets/b023d89f-f639-4c54-8c7b-3dd80a6ecd18" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">2025.pmconf.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://storage.googleapis.com/production-os-assets/assets/c4c0898d-cb66-4646-a42c-55021ead10d4" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="会場の様子">会場の様子</h1>
<p>年に一度開催される日本最大のPMイベント「pmconf」は、2016年に始まり今年で10回目。
東京会場では2つのホールと5つのルームに分かれて約30本ものトークセッションや、OST、PMフィッシュボウルなどの実践セッションが行われました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./image.png","alt":"会場のバナーの写真","index":0}"></p>
<h1 id="弊社ブースの様子">弊社ブースの様子</h1>
<p>ブースでは、プロダクトマネージャーとしての強みを4つのタイプに診断する「PMタイプ診断」を行い、参加していただいた方にはノベルティをお渡ししました。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_1961.jpg","alt":"ブース写真","index":0}"></p>
<p>新しいロゴのステッカーも初お披露目です!</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_5768.jpg","alt":"ロゴシール","index":0}"></p>
<p>医療にちなんで、総合診療医・臨床研究医・外科医・看護師長の4つのタイプに診断。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_1962.jpg","alt":"pm診断","index":0}"></p>
<p>開場時間からたくさんの方にお越しいただき、メドレーのプロダクトにも多くの関心をいただきました。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_1963.jpg","alt":"watanabesan","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_1964.jpg","alt":"ブースの様子","index":0}"></p>
<h1 id="実践型セッションの様子">実践型セッションの様子</h1>
<p>トークセッションと同じく盛り上がりを見せたのは、OST(Open Space Technology)。
セッション開始時に話したいテーマを紙に書き出し、一緒に議論する仲間に呼びかけます。
参加者はそのテーマを選んで、セッションに加わります。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_2061.jpg","alt":"黄色付箋","index":0}"></p>
<p>皆さん真剣に議論されていて熱気がすごかったです!
弊社のwatanabeも参加。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_1965.jpg","alt":"watanabesan-2","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_2074.jpg","alt":"掲示板","index":0}"></p>
<p>PMフィッシュボウルでは、「そもそもプロダクトマネージャーって必要?」という核心的なテーマのもと、白熱した議論が行われました。自社でもプロダクト横断でやってみると面白いかも?</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_9436.jpg","alt":"後ろからの会場の様子","index":0}"></p>
<h1 id="戦利品">戦利品</h1>
<p><img __ASTRO_IMAGE_="{"src":"IMG_5763.jpg","alt":"ノベルティ","index":0}"></p>
<p>狙っていたスタンプラリーのパーカーは間に合わなかったけど、ロゴ入りタンブラーをゲットできました。
RevenueCatさんの靴下は来場したときから一目惚れだったので当たって嬉しかったです!(猫ちゃんモチーフで全てがかわいかった)</p>
<h1 id="最後に">最後に</h1>
<p>初めてのブース参加でしたが、とても楽しかったです。次回はもっとセッションや交流を楽しみたいと思います。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_2049.jpg","alt":"集合写真","index":0}">
(開場後まだ元気な私。すぐに軟弱な足腰が悲鳴をあげ、マツキヨで湿布を買いました)</p>
<p>メドレーはこれからも、医療ヘルスケアの未来を作るために、様々なプロダクト・サービスを開発し提供していきます。</p>
<p>現在、メドレーでは一緒に働く仲間を募集しています。この記事やイベントを通じて興味を持っていただいた方は、ぜひお気軽にご連絡ください。</p>
<p><a href="https://hrmos.co/pages/medley/jobs">株式会社メドレー 求人一覧</a></p>
- 我が家のNATゲートウェイを覗いてみたhttps://developer.medley.jp/entry/2025/12/04/151815https://developer.medley.jp/entry/2025/12/04/151815はじめに
この記事は Medley(メドレー) Advent Calendar 2025 4日目の記事です。
医療プラットフォーム本部 プラットフォーム開発室 SRE グループの山田です。
医療機関向け SaaS である CLINICS の...Thu, 04 Dec 2025 06:18:15 GMT<h1 id="はじめに">はじめに</h1>
<p><strong>この記事は <a href="https://qiita.com/advent-calendar/2025/medley">Medley(メドレー) Advent Calendar 2025</a> 4日目の記事です。</strong></p>
<p>医療プラットフォーム本部 プラットフォーム開発室 SRE グループの山田です。
医療機関向け SaaS である <a href="https://clinics-cloud.com/">CLINICS</a> の安定稼働とシステム信頼性の向上に取り組んでいます。</p>
<p>本記事では、<a href="https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/flow-logs.html">VPCフローログ</a>/<a href="https://docs.aws.amazon.com/ja_jp/athena/latest/ug/what-is.html">Amazon Athena</a>/<a href="https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-network-acls.html">ネットワークACL</a>を用いて<a href="https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-nat-gateway.html">NATゲートウェイ</a>への通信内容を調査する方法について紹介します。
タイトルにもありますが、かなり力技になりますので本番環境で実施は控えてください。</p>
<h1 id="背景">背景</h1>
<p>CLINICSには外部サービスと連携して機能を提供するAPIが多く存在します。
また、システム間通信にもNATゲートウェイを経由して通信することがあるため、サービスの成長に伴いECS Taskのスケールアウトによって、NATゲートウェイを経由する通信量は着実に増えていきます。</p>
<p>日常の運用では問題が見えにくくても、万が一NATゲートウェイで障害が発生してしまった場合、その影響は広範囲に及び、サービスの復旧に時間を要する可能性があります。</p>
<p>特に懸念される障害シナリオの一つが、トラフィック集中による「ポート割り当てエラーの増加」です。
これにより、NATゲートウェイへの新規接続が失敗し、通信が遮断される可能性があります。</p>
<p>参考:<a href="https://repost.aws/ja/knowledge-center/vpc-resolve-port-allocation-errors">Amazon VPC の NATゲートウェイで発生する「エラーポートアロケーション」エラーを解決するにはどうすればよいですか?</a></p>
<p>こうしたリスクを未然に防ぐためには、「NATゲートウェイの冗長化」や、「NATゲートウェイ経由の通信が必須か」を見直し、可能な通信はVPCエンドポイントへ移行させるといったアーキテクチャの最適化が求められます。</p>
<p>CLINICSでは、この課題への第一歩として、現状のNATゲートウェイの利用実態を詳細に把握する調査を実施しました。
具体的には、NATゲートウェイを介して通信しているシステムと通信先のサービスを洗い出すことに取り組みました。</p>
<p>次章からは、この調査で得られた具体的な知見と、それに基づきどのようにインフラアーキテクチャの改善を進めたのかを紹介します。</p>
<h1 id="調査方法">調査方法</h1>
<p>ここからは、NATゲートウェイ経由でのトラフィックを調査し、CLINICSが通信している外部のサービスを特定する方法について紹介します。
大枠、以下のような流れで調査を進めていきました。</p>
<ol>
<li>VPCフローログを有効化</li>
<li>Athena からVPCフローログを解析</li>
<li>通信先のAWSサービスを特定</li>
<li>通信先のAWS以外のサービスを特定</li>
</ol>
<h2 id="vpcフローログを有効化">VPCフローログを有効化</h2>
<p>VPCフローログはVPC内部の通信内容をキャプチャする機能です。
もちろん、「VPC内部の通信」にはNATゲートウェイの通信も含まれます。</p>
<p>ログはAmazon CloudWatch Logs、Amazon S3、Amazon Kinesis Data Firehoseへ保存することができますが、本記事ではS3へ保存する想定で解説していきます。</p>
<h3 id="ログレコードの形式">ログレコードの形式</h3>
<p>VPCフローログのフォーマットはデフォルト設定でもある程度調査が可能ですが、調査をする上で追加で以下のフィールドを追加しました。</p>
<ul>
<li>tcp-flags: TCP接続の状態(SYN、ACK、FINなど)を確認するために使用します。NATゲートウェイはアイドルタイムアウト時にRSTパケットを送信するため、調査目的で追加しました</li>
<li>pkt-srcaddr: NAT変換前の元の送信元IPアドレス。NATゲートウェイ経由の通信では、送信元を特定するために変換前のIPを追跡するために使用します</li>
<li>pkt-dstaddr: NAT変換前の元の送信先IPアドレス。実際に通信しようとしている外部サービスのIPを特定するために使用します</li>
<li>pkt-src-aws-service: 送信元IPがAWSサービスのIP範囲に該当する場合、そのサービス名が記録されます</li>
<li>pkt-dst-aws-service: 送信先IPがAWSサービスのIP範囲に該当する場合、そのサービス名が記録されます。EC2やS3など、どのAWSサービスと通信しているかを判別できます</li>
<li>traffic-path: トラフィックがどの経路を通っているかを確認できます。NATゲートウェイ経由かどうかの判定に役立ちます</li>
</ul>
<p>参考: <a href="https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/flow-log-records.html">フローログレコード</a></p>
<p>フローログに追加できる情報はかなり種類があり定期的に更新されるので、設定するときは用途に合わせたフォーマットになるようドキュメントを一読することをお勧めします。
また、既存のフローログのフォーマットを変更することはできないので注意が必要です。</p>
<h2 id="vpcフローログを分析するための基盤を作成">VPCフローログを分析するための基盤を作成</h2>
<p>VPCフローログを有効化したので、S3にVPC内のトラフィックに関する情報が保存されるようになりました。
次に、これらのログを集計して通信内容に関するより詳細な情報を取得できるようにするためにAthenaテーブルを用意します。</p>
<details><summary>Athena テーブル作成</summary>
<p>
</p><pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="sql"><code><span class="line"><span style="color:#569CD6">CREATE</span><span style="color:#569CD6"> EXTERNAL</span><span style="color:#569CD6"> TABLE</span><span style="color:#569CD6"> IF</span><span style="color:#569CD6"> NOT</span><span style="color:#569CD6"> EXISTS</span><span style="color:#D4D4D4"> vpc_flow_logs (</span></span>
<span class="line"><span style="color:#D4D4D4"> flowlog_version </span><span style="color:#569CD6">INT</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> account_id STRING,</span></span>
<span class="line"><span style="color:#D4D4D4"> interface_id STRING,</span></span>
<span class="line"><span style="color:#D4D4D4"> srcaddr STRING,</span></span>
<span class="line"><span style="color:#D4D4D4"> dstaddr STRING,</span></span>
<span class="line"><span style="color:#D4D4D4"> srcport </span><span style="color:#569CD6">INT</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> dstport </span><span style="color:#569CD6">INT</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> protocol </span><span style="color:#569CD6">INT</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> packets </span><span style="color:#569CD6">BIGINT</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> bytes </span><span style="color:#569CD6">BIGINT</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> start_time </span><span style="color:#569CD6">BIGINT</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> end_time </span><span style="color:#569CD6">BIGINT</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> traffic_action STRING,</span></span>
<span class="line"><span style="color:#D4D4D4"> log_status STRING,</span></span>
<span class="line"><span style="color:#D4D4D4"> tcp_flags </span><span style="color:#569CD6">INT</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> pkt_srcaddr STRING,</span></span>
<span class="line"><span style="color:#D4D4D4"> pkt_dstaddr STRING,</span></span>
<span class="line"><span style="color:#D4D4D4"> pkt_src_aws_service STRING,</span></span>
<span class="line"><span style="color:#D4D4D4"> pkt_dst_aws_service STRING,</span></span>
<span class="line"><span style="color:#D4D4D4"> traffic_path </span><span style="color:#569CD6">INT</span></span>
<span class="line"><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4">PARTITIONED </span><span style="color:#569CD6">BY</span><span style="color:#D4D4D4"> (dt STRING)</span></span>
<span class="line"><span style="color:#569CD6">ROW</span><span style="color:#D4D4D4"> FORMAT DELIMITED</span></span>
<span class="line"><span style="color:#D4D4D4"> FIELDS TERMINATED </span><span style="color:#569CD6">BY</span><span style="color:#CE9178"> ' '</span></span>
<span class="line"><span style="color:#569CD6">LOCATION</span><span style="color:#CE9178"> 's3://bucket-name/AWSLogs/XXXXXXXXXXXX/vpcflowlogs/region'</span></span>
<span class="line"><span style="color:#D4D4D4">TBLPROPERTIES (</span></span>
<span class="line"><span style="color:#CE9178"> "skip.header.line.count"</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">"1"</span></span>
<span class="line"><span style="color:#D4D4D4">);</span></span></code></pre>
<p></p>
</details>
<p>作成したテーブルに対してパーティションを追加して、データを取り込ませたら準備完了です。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="sql"><code><span class="line"><span style="color:#569CD6">ALTER</span><span style="color:#569CD6"> TABLE</span><span style="color:#D4D4D4"> vpc_flow_logs</span></span>
<span class="line"><span style="color:#569CD6">ADD</span><span style="color:#569CD6"> PARTITION</span><span style="color:#D4D4D4"> (dt=</span><span style="color:#CE9178">'20YY-MM-DD'</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#569CD6">LOCATION</span><span style="color:#CE9178"> 's3://bucket-name/AWSLogs/XXXXXXXXXXXX/vpcflowlogs/region/20YY/MM/DD/'</span><span style="color:#D4D4D4">;</span></span></code></pre>
<h2 id="通信対象の特定">通信対象の特定</h2>
<p>実際にクエリを実行してNATゲートウェイを介してどこと通信しているかを調査します。</p>
<p>下記のクエリでは、特定の日付でNATゲートウェイを介した通信回数を通信元と通信先のIPで集計しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="sql"><code><span class="line"><span style="color:#569CD6">SELECT</span><span style="color:#D4D4D4"> pkt_srcaddr, srcaddr, dstaddr, pkt_dstaddr, dstport, pkt_dst_aws_service, </span><span style="color:#DCDCAA">COUNT</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">1</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">AS</span><span style="color:#D4D4D4"> c</span></span>
<span class="line"><span style="color:#569CD6">FROM</span><span style="color:#D4D4D4"> vpc_flow_logs</span></span>
<span class="line"><span style="color:#569CD6">WHERE</span><span style="color:#D4D4D4"> srcaddr </span><span style="color:#569CD6">LIKE</span><span style="color:#CE9178"> 'x.x.%'</span><span style="color:#6A9955"> -- VPC CIDR</span></span>
<span class="line"><span style="color:#569CD6"> AND</span><span style="color:#D4D4D4"> dstaddr </span><span style="color:#569CD6">IN</span><span style="color:#D4D4D4"> (</span><span style="color:#CE9178">'y.y.y.y'</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">'z.z.z.z'</span><span style="color:#D4D4D4">) </span><span style="color:#6A9955">-- NAT Gateway IP</span></span>
<span class="line"><span style="color:#569CD6"> AND</span><span style="color:#D4D4D4"> dt = </span><span style="color:#CE9178">'20YY-MM-DD'</span></span>
<span class="line"><span style="color:#569CD6">GROUP BY</span><span style="color:#D4D4D4"> pkt_srcaddr, srcaddr, dstaddr, pkt_dstaddr, dstport, pkt_dst_aws_service</span></span>
<span class="line"><span style="color:#569CD6">ORDER BY</span><span style="color:#D4D4D4"> c </span><span style="color:#569CD6">DESC</span><span style="color:#D4D4D4">;</span></span></code></pre>
<p>すると以下のような結果が返ってきます。(もちろんダミーデータです。)</p>
<table><thead><tr><th>#</th><th>pkt_srcaddr</th><th>srcaddr</th><th>dstaddr</th><th>pkt_dstaddr</th><th>dstport</th><th>pkt_dst_aws_service</th><th>c</th></tr></thead><tbody><tr><td>1</td><td>x.x.a.a</td><td>x.x.a.a</td><td>y.y.y.y</td><td>a.a.a.a</td><td>443</td><td>EC2</td><td>1600</td></tr><tr><td>2</td><td>x.x.b.b</td><td>x.x.b.b</td><td>z.z.z.z</td><td>b.b.b.b</td><td>443</td><td>AMAZON</td><td>1500</td></tr><tr><td>3</td><td>x.x.c.c</td><td>x.x.c.c</td><td>y.y.y.y</td><td>c.c.c.c</td><td>443</td><td>-</td><td>700</td></tr><tr><td>4</td><td>x.x.d.d</td><td>x.x.d.d</td><td>y.y.y.y</td><td>d.d.d.d</td><td>443</td><td>-</td><td>400</td></tr></tbody></table>
<h3 id="通信先のawsサービスを特定">通信先のAWSサービスを特定</h3>
<p>通信先がAWSサービスの場合は、<code>pkt_dst_aws_service</code> を確認することで対象を特定することができます。</p>
<p>フローログに記載される<code>pkt-src-aws-service</code>と<code>pkt-dst-aws-service</code>は<code>EC2</code>のように具体的なAWSサービスが記載されていることもあれば、<code>AMAZON</code>のようにどのエンドポイントと通信しているかわからない場合もあります。そのような場合は、AWS Route53 リゾルバーのクエリログ記録を確認することで特定できたりします。</p>
<p>参考: <a href="https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/resolver-query-logs.html">リゾルバーでのクエリのログ記録</a></p>
<p>また、EC2ダッシュボードから確認できるネットワークインターフェース一覧からパブリックIPで検索することで通信先のリソースを特定することも可能です。</p>
<h3 id="通信先のaws以外のサービスを特定">通信先のAWS以外のサービスを特定</h3>
<p>基本的にIPアドレスからどのサービスなのかを完全に特定することはできません。</p>
<p>逆引きDNS(PTRレコード)が設定されている場合は<code>dig</code>を使ったり<code>whois</code>でIP所有者の特定はできるかもしれませんが、外部サービスがIPアドレスの範囲を公開していなければどのサービスかを絞り込むのは難しいです。</p>
<p>では、AWS以外のサービスをどのように特定すれば良いでしょうか?</p>
<p>A. 「特定のIPアドレスへの通信を遮断すれば、システムアラートからどのサービスとの通信が確立できなかったか確認できるのでは?」</p>
<p>以下のような仕組みを考えてみました。</p>
<ol>
<li>CLINICS APIが外部サービス(IPがx.x.x.x)へリクエストを送信する</li>
<li>特定のIPアドレスへの通信を遮断する</li>
<li>外部サービスへのリクエストがタイムアウトすることでアラートが発報される</li>
</ol>
<p>筋が良くはないかもしれませんがやってみました。冒頭にも記載しましたが、本番環境での実施は控えてください。</p>
<h4 id="特定のipアドレスへの通信を遮断する方法">特定のIPアドレスへの通信を遮断する方法</h4>
<p>特定のIPアドレスへの通信を遮断する手段として「ネットワークアクセスコントロールリスト(ネットワークACL)」があります。
ネットワークACLはサブネットレベルで特定のトラフィックを許可/拒否することができる仕組みです。</p>
<p>参考:<a href="https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-network-acls.html">ネットワークアクセスコントロールリストを使用して、サブネットのトラフィックを制御する</a></p>
<p>実際にネットワークACLを作成してみます。
先ほど実行したクエリ結果をもとにブロックしたいIPアドレスをアウトバウンドルールを設定していきます。</p>
<p><img __ASTRO_IMAGE_="{"src":"./nacl_outbound.png","alt":"ネットワークACLアウトバウンドルール","index":0}"></p>
<p>作成したネットワークACLをプライベートサブネットに関連付ければ、NATゲートウェイを介した通信が遮断されるようになります。</p>
<p>全てのIPアドレスをしらみつぶしに検証していくとキリがないので、トラフィックが特に多いものに絞って調査を進めました。</p>
<h3 id="調査結果">調査結果</h3>
<p>上述した方法を用いて、NATゲートウェイを介して通信する外部サービスの特定ができました。
特にトラフィック量が多く、NATゲートウェイを経由しなくても良いサービスは以下のようになりました。</p>
<ul>
<li>Amazon Kinesis</li>
<li>Datadog</li>
</ul>
<h2 id="natゲートウェイを通らないアーキテクチャへ変更">NATゲートウェイを通らないアーキテクチャへ変更</h2>
<p>ここからは、NATゲートウェイ経由で通信していたトラフィックをVPCエンドポイントへ逃していきます。
NATゲートウェイとVPCエンドポイントどちらを採用するかを検討する場合、損益分岐点を計算する必要がありますが本記事では割愛します。</p>
<h3 id="amazon-kinesis">Amazon Kinesis</h3>
<p>Amazon Kinesis はインターフェイスVPCエンドポイントがサポートされているため、VPCエンドポイントを作成して向き先を変えてあげます。</p>
<p>参考: <a href="https://docs.aws.amazon.com/ja_jp/vpc/latest/privatelink/aws-services-privatelink-support.html">AWS のサービス と統合する AWS PrivateLink</a></p>
<h3 id="datadog">Datadog</h3>
<p>DatadogはPrivateLinkを提供しているため、これを採用します。</p>
<p>参考: <a href="https://docs.datadoghq.com/ja/agent/guide/private-link/?tab=crossregionprivatelinkendpoints">AWS PrivateLink を介して Datadog に接続する</a></p>
<p>後になって知りましたが、DatadogさんはIPアドレスの範囲を公開されていたんですね。
以下から確認できました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#DCDCAA">curl</span><span style="color:#569CD6"> -X</span><span style="color:#CE9178"> GET</span><span style="color:#CE9178"> "https://ip-ranges.datadoghq.com/"</span><span style="color:#569CD6"> -H</span><span style="color:#CE9178"> "Accept: application/json"</span></span></code></pre>
<p>参考: <a href="https://docs.datadoghq.com/ja/api/latest/ip-ranges/?code-lang=curl">IP 範囲</a></p>
<h1 id="効果検証">効果検証</h1>
<p>VPCエンドポイントやPrivateLinkへの移行が完了した後、実際にNATゲートウェイを経由するトラフィック量が削減されたかを確認しました。</p>
<h3 id="natゲートウェイ経由のトラフィック削減">NATゲートウェイ経由のトラフィック削減</h3>
<p>VPCエンドポイントやPrivateLinkへの移行後、NATゲートウェイを経由するトラフィック量が大幅に削減されました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./result_v2.png","alt":"VPCエンドポイントにトラフィックを逃した結果","index":0}"></p>
<p><em>VPCエンドポイントへトラフィックが流れた後のNATゲートウェイの送信先へのバイト数の変化</em></p>
<p>具体的には、移行前と比較して以下のような結果が得られました。</p>
<ul>
<li><strong>送信パケット数の削減</strong>: NATゲートウェイを経由するパケット数が約50%削減</li>
<li><strong>データ量の削減</strong>: NATゲートウェイを経由するデータ量が約82%削減</li>
</ul>
<h1 id="まとめ">まとめ</h1>
<p>本記事では、VPCフローログとAmazon Athenaを用いてNATゲートウェイを経由する通信内容を分析し、ネットワークACLを活用した力技で通信先のサービスを特定する方法を紹介しました。</p>
<p>調査の結果、NATゲートウェイを経由する主な通信先としてAmazon KinesisとDatadogが特定できました。これらをVPCエンドポイントやPrivateLinkに移行することで、NATゲートウェイを経由するトラフィック量を削減することができました。</p>
<p>NATゲートウェイのポート割り当てエラーなどのリスクを事前に回避するためには、定期的に通信内容を見直し、可能な限りVPCエンドポイントやPrivateLinkを活用したアーキテクチャに最適化していくことが重要です。
ただし、VPCエンドポイントやPrivateLinkを導入することでかえってインフラコストが増加する可能性もあるため、アーキテクチャの最適化によるリスク低減とインフラコストのバランスを慎重に検討する必要があります。
本記事が、同様の課題に取り組む方々の参考になれば幸いです。</p>
<h1 id="were-hiring">We’re hiring!</h1>
<p>メドレーでは、SREをはじめ「医療ヘルスケアの未来」を共に創っていくエンジニアを大募集中です!少しでもご興味をお持ちいただけましたらぜひ、ご応募お待ちしております!</p>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs?category=2144686643002470401" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">株式会社メドレー エンジニア の求人一覧</div>
<div class="remark-link-card-plus__description">株式会社メドレー エンジニア の求人一覧です。| HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:FFFFFF,c_mpad,h_200,w_200/m/2468/images/2148742169805250560_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>※カジュアル面談も大歓迎です!ご希望の際は、「その他の項目(希望記入欄)」にてその旨をご記載ください。</p>
- FlutterKaigi 2025 参加レポート - Bronzeスポンサーとして協賛しました!https://developer.medley.jp/entry/2025/11/18/100000https://developer.medley.jp/entry/2025/11/18/100000こんにちは、人材プラットフォーム本部CTO室の奥澤です。株式会社メドレーの人材プラットフォーム本部では現在、Flutterを用いて複数のモバイルアプリを開発しています。Flutterに興味を持つメンバーが増え、注目度も高まっています
202...Tue, 18 Nov 2025 01:00:00 GMT<p>こんにちは、人材プラットフォーム本部CTO室の奥澤です。株式会社メドレーの人材プラットフォーム本部では現在、Flutterを用いて複数のモバイルアプリを開発しています。Flutterに興味を持つメンバーが増え、注目度も高まっています</p>
<p>2025年11月13日、日本最大級のカンファレンス「<strong>FlutterKaigi 2025</strong>」が開催されました。<strong>FlutterKaigi</strong>は有志により開催され、メドレーからも複数のメンバーが参加しています。メドレーでは、Bronzeスポンサーとして協賛しており、技術カンファレンスへ継続的に協賛を通じて、技術コミュニティの支援を行っています。</p>
<p>本記事では、FlutterKaigi 2025への参加レポートを報告します。</p>
<h1 id="会場の様子">会場の様子</h1>
<p>FlutterKaigi 2025は、大手町プレイス ホール&カンファレンスで開催されました。会場に着くと、巨大な<a href="https://docs.flutter.dev/dash">Dashちゃん</a>に迎えられました。FlutterKaigiを盛り上げるぞという気持ちが高まります。また、写真では小さいですが、このボードにはBronzeスポンサーとしてメドレーの社名も出ています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./flutterkaigi-2025-1.jpg","alt":"会場入口の様子","index":0}"></p>
<p>会場の2階には、メッセージボードも設置されていました。遠方から参加された方のコメントも多数残されていました。見知らぬ参加者同士でコミュニケーションできるのは良いですね。</p>
<p><img __ASTRO_IMAGE_="{"src":"./flutterkaigi-2025-2.jpg","alt":"メッセージボード","index":0}"></p>
<p>また会場では、スポンサーブースをまわってスタンプを集めるスタンプラリーが開催されていました。自分も各社のブースをまわり、技術、プロダクト、開発体制などの話を聞かせていただきました。いくつかのブースではかなり長い時間にわたって情報交換させていただき、とても有益な時間になりました。スタンプを集めて引けるクジでは当たりを引き、Dashちゃんのぬいぐるみをいただきました。宝物にします。</p>
<p><img __ASTRO_IMAGE_="{"src":"./flutterkaigi-2025-3.jpg","alt":"Dashちゃんのぬいぐるみ","index":0}"></p>
<h1 id="セッションの紹介">セッションの紹介</h1>
<p>印象に残ったセッションを紹介します。</p>
<h2 id="flutterにしてよかった-出前館アプリを2年運用して気づいたこと全部話します">Flutterにしてよかった? 出前館アプリを2年運用して気づいたこと全部話します</h2>
<p>出前館には複数のモバイルアプリがあり、2年前にそのすべてをFlutterに統一したとのこと。それからの2年間で遭遇した技術面・運用面での課題とその課題をどう解消したのかを発表されていました。</p>
<p>まず、2年前まではReact NativeのCode Pushを利用してアプリを配信していたが、Flutterへの移行に伴い2週間のリリーストレインで運用するようになった、という話がありました。モバイルアプリにおけるリリーストレインの事例自体は多いと思いますが、フレームワークの移行に伴ってリリースフローも変更したという話は興味深く聞きました。「ユーザーに素早く価値を提供すること」を起点にリリースフローを再定義されていた点は素晴らしいなと感じました。</p>
<p>次に、Flutter Webを活用して開発中のフィードバックを受けるようにした、という話も良かったです。Flutterで開発しているモバイルアプリを、開発中のフィードバックを得るために社内に向けてWeb版で提供するようにした、とのことです。開発中のアプリをモバイル端末で動かしてもらってフィードバックを受ける、というのは実は心理的な障壁が高く、Web版を動かしてもらってフィードバックを受ける方が心理的障壁が低くフィードバックが集まりやすい…という点は驚きでした。Flutterを使って開発しているからこそ実現できた活用事例でした。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/7c31cf88bcab43ccba6746d27fb90479" title="Flutterにしてよかった?出前館アプリを2年運用して気づいたことを全部話します" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<h2 id="顧客価値を実現するflutterkpisloから考えるキャリア支援アプリのuiux設計">顧客価値を実現するFlutter:KPI・SLOから考えるキャリア支援アプリのUIUX設計</h2>
<p>顧客価値を実現するためにFlutterをどう活用しているか?について実践してきた内容を発表されていました。提供したい顧客価値を定義し、重要指標を特定し、Flutterを用いて重要指標をどうやって改善するか、という事例の話でした。例えば以下のような事例です。</p>
<ul>
<li>入力内容の即時ローカル保存によってフォーム入力における離脱率の指標を改善した</li>
<li>UIを楽観的更新することによって体感レイテンシの指標を改善した</li>
</ul>
<p>発表されていた内容は、顧客価値を実現するためにモバイルアプリエンジニアから提案できるものだと思います。こういった取り組みについては、他社の具体的な事例を知る機会はなかなかないため、ありがたい発表でした。</p>
<h2 id="アクセシビリティまだ完璧じゃないけど--今からできること">アクセシビリティ、まだ完璧じゃないけど ── “今から”できること</h2>
<p>アクセシビリティは重要な品質特性のひとつですが、モバイルアプリにおけるアクセシビリティへの取り組みはまだまだ発展途上にあると思います。しかしながら、カンファレンスでのアクセシビリティに関するセッションも増えつつあり、本セッションもそのひとつです。本セッションでは、Flutterにおけるアクセシビリティの基本や遭遇したトラブルについて発表されていました。</p>
<p>まず、Flutterにおけるアクセシビリティ対応ではSemantics Widgetを利用することが重要である、という説明がありました。その上で、Semantics Widgetの基本的な使い方の説明がありました。またこれまで遭遇したトラブルとしていくつかの事例が共有され、例えば標準WidgetではなくGestureDetectorを使用して実装した場合にスクリーンリーダーが適切に読み上げできない事例などが共有されました。</p>
<p>Flutterにおけるアクセシビリティの実装を再確認できたと共に、どのような場合に適切な読み上げができなくなってしまうのかを認識できました。非常に実用的な、有益なセッションでした。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/0a9bd203a338416983f600b34ec4fb0d" title="アクセシビリティ、まだ完璧じゃないけど ── “今から”できること" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<h1 id="最後に">最後に</h1>
<p><img __ASTRO_IMAGE_="{"src":"./flutterkaigi-2025-4.jpg","alt":"Thank you!","index":0}"></p>
<p>メドレーの人材プラットフォーム本部では、Flutterエンジニアを積極採用中です。Flutterを活用した医療ヘルスケア領域の課題解決に興味がある方は、ぜひカジュアル面談にお越しください!</p>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs/1000096" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Flutterエンジニア/人材プラットフォーム本部 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">Flutterエンジニア/人材プラットフォーム本部(株式会社メドレー)の求人情報です。 | HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:333333,c_pad,w_978,h_513/m/577c/images/2118206564349132800_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- Ruby World Conference 2025 参加レポート - ゴールドスポンサーとして協賛しました!https://developer.medley.jp/entry/2025/11/12/190056https://developer.medley.jp/entry/2025/11/12/190056はじめまして!人材プラットフォーム本部のプラットフォームチームで、プラットフォームエンジニアリングとしてジョブメドレープラットフォームを開発している安藤雄飛と申します。(←プラットフォーム×4がお気に入りの自己紹介)
さて今回は、2025年...Wed, 12 Nov 2025 00:00:00 GMT<p>はじめまして!人材プラットフォーム本部のプラットフォームチームで、プラットフォームエンジニアリングとしてジョブメドレープラットフォームを開発している安藤雄飛と申します。(←プラットフォーム×4がお気に入りの自己紹介)</p>
<p>さて今回は、2025年11月6日と7日に島根県松江市の「くにびきメッセ」で開催された<a href="https://2025.rubyworld-conf.org/ja/">Ruby World Conference 2025</a>に、当社メドレーは<strong>Goldスポンサー</strong>として参加しました。また、メドレーの歯科診療所事業部長であり、Omotesando.rbのオーガナイザーやRuby関連執筆で活動されている牧さんの講演を聴講しましたので、そのカンファレンスの熱気をお届けします。</p>
<h1 id="カンファレンスのサマリ">カンファレンスのサマリ</h1>
<ul>
<li><strong>Rubyコミュニティの懐の深さ</strong>: 30年続くRubyのOSSコミュニティは、産学官連携や地域、国際文化交流まで有機的に発展しており、その間口の広さと懐の深さが際立っていました。</li>
<li><strong>エコシステムの進化と拡充</strong>: Rubyで爆速リリースされたプロダクトが急成長し、次のフェーズに進むにあたり、Rubyエコシステムとメソドロジーがさらなる拡充と進化を遂げていること。</li>
<li><strong>オープンな知識共有</strong>: Rubyのオープンなマインドに共感した「つよいRubyist」たちが、成功事例だけでなく、課題や失敗、挑戦を共有することで、コミュニティ全体の一体感と共有資産を生み出していること。</li>
</ul>
<p><img __ASTRO_IMAGE_="{"src":"01会場.jpg","alt":"会場","index":0}">
Ruby World Conference 2025会場</p>
<p>会場となった「くにびきメッセ」はJR松江駅から徒歩圏内ながらも、澄んだ少し冷たい風を感じながら大橋川を渡っているうちに、どこか神聖な雰囲気に包まれます。メドレーの松江エンジニアリングオフィス<a href="https://developer.medley.jp/entry/2023/07/31/120555/">「Tech Studio MATSUE」</a>も、このすぐ先にあります。</p>
<p>オープニングセレモニーでは、Rubyの父Matz(まつもとゆきひろ氏)をはじめ、島根県知事、松江市長の挨拶があり、Rubyが地域に深く根付いた文化であることが伝わってきました。</p>
<p>2日間にわたる27の講演・プログラムは、Rubyが様々な分野で活用されていることを示していました。主な活用事例は以下の3点です。</p>
<ul>
<li>アフリカ東部を中心とした銀行口座を持たない14億人に向けて、金融排除問題をオフラインのフィーチャーフォンとRubyで解決したBernard Banta氏(<a href="https://rubycommunity.africa/">アフリカRubyコミュニティ</a>議長)の基調講演をはじめ、東日本大震災から首都圏のガスインフラを守った防災システムや事業継続(BCP)マネジメントシステム、政府案件として複雑な台湾語をRubyで解析するなどの <strong>「喫緊の社会イシューをRubyで解決」</strong> した事例</li>
<li><a href="https://github.com/picoruby">PicoRuby</a>によるマイコンやIoT・ビジュアルアートのファンダムや、<a href="https://github.com/ruby/ruby.wasm">ruby.wasm</a>を活用した学生向けプログラミングゲームやメタプログラミングRuby問題集、社内のDXやキャリアチェンジにRubyを活用した人材育成、そしてPythonが優勢なAI/機械学習分野におけるRubyの導入事例や機能移植へのモチベートなど <strong>「Rubyの更なる可能性」</strong> を示唆する講演</li>
<li>ビジネスのスケールに伴うシステムと開発組織の拡大、そしてそれに付随する社会的インパクトに対応するための品質とアジリティの両立に <strong>「Rubyのエコシステムとコミュニティを活用したリアーキ戦略」</strong> の紹介</li>
</ul>
<p>どの講演も自社サービスや担当システムへの愛とRuby愛に溢れるものばかりでした。</p>
<h1 id="歯科医療dxを支えるruby職人の魂">歯科医療DXを支えるRuby職人の魂</h1>
<p>Day1のトリを務めたメドレー歯科診療所事業部長、牧さんの <strong>「歯科医療DXを支えるRuby - クラウド歯科業務支援システム『DENTIS』の開発事例」</strong> の講演の様子がこちらです。</p>
<p><img __ASTRO_IMAGE_="{"src":"09牧さん登壇アップ.jpg","alt":"牧さん登壇","index":0}"></p>
<p>メドレーのミッション <strong>「医療ヘルスケアの未来をつくる」</strong> と、松江の拠点、そして「MEDLEY AI CLOUD」による未来の医療体験の紹介の後、牧部長が牽引するクラウド歯科業務支援システム <strong>「DENTIS」</strong> のデモが披露されました。</p>
<p><img __ASTRO_IMAGE_="{"src":"10牧さん登壇.jpg","alt":"牧さん登壇","index":0}"></p>
<p>単なるサービス紹介に留まらず、歯科医療というドメインの持つ難しさが会場に伝わりました。そして牧さんは、その複雑さを以下の3点にまとめました。</p>
<ul>
<li>医療会計の難しさ</li>
<li>保険診療の難しさ</li>
<li>歯科特有の情報処理</li>
</ul>
<p>具体的な事例を用いたエッジケースや計算ツリーの複雑さの説明では、指数的なカバレッジの増加や、2年に1度見直しが入る診療報酬の改定と、その施行までの期間のタイトさ(本当に開発者にとっては驚くほど短期間です)がとても印象的でした。</p>
<p>そして、保険点数と医療費(月の算定エラーやアラートを含む)のメインロジックをActive Recordのコールバックから<a href="https://github.com/collectiveidea/interactor">interactor gem</a>にリプレースすることと、予約システムデータ連携と電子カルテ・レセコンエンジン部を分離し、独立してスケールできるようなアーキテクチャに進化させ、この複雑なドメインをモデリングするための技術的挑戦を惜しみなく語り、Ruby職人の魂を感じさせました。</p>
<p><img __ASTRO_IMAGE_="{"src":"04牧さん登壇.jpg","alt":"牧さん登壇","index":0}"></p>
<p>最後に、牧さんはこのシステムの「医療・ヘルスケアの未来を作る仲間」を募集していますと締めくくりました。会場にはたくさんのRubyistと学生さんもいましたが、私は、はたしてここまで複雑な事業ドメインを抱えるシステムに対してどれだけのエンジニアがチャレンジしようと思ってくれるのかと一縷の不安がありました。</p>
<p>ところが、質疑応答では、はじめに「大変やりがいのあるシステムだと思います。」と好意的な前置きをした上で「歯科診療のドメイン知識の取得が鍵になると思いますが、どのように解決されていますか?」と質問がありました。</p>
<p>牧さんは、「元々社内に数名の医療ドメインエキスパートがいることや専門家がジョインしていること、エンジニアも厚労省のPDFを読み込んだり勉強会などを通じてキャッチアップしていることにより、社内で情報のインデックス化が進んでいる。」との回答でした。</p>
<h1 id="まとめと未来を作る仲間へ">まとめと、未来を作る仲間へ</h1>
<p>Rubyが入門のし易さや、開発者体験の良さといった <strong>スタートアップ事業における迅速性</strong>のみならず、<strong>ビジネス環境の変革に合わせた柔軟性</strong>や、<strong>システムや組織のスケールの適応性</strong>も兼ね備えている事を再認識した機会でした。特にRubyの <strong>高い生産性</strong> で開発フェーズを圧縮し、結果としてテストに時間を費やせるようになり、金融や防災、医療といった <strong>ミッションクリティカルなシステムでも品質を高められる</strong> という技術的判断に大きく納得させられました。</p>
<p>さらに、このRubyの発展と進化を支えているのが、このRubyコミュニティです。冒頭申し上げましたとおり、OSSコミュニティとして技術者同士のみならず、産学官、地域・国際文化交流がとても自然な形で融合しており、あらゆる形でコミュニティに参加するギバー達の貢献がエコシステムやナレッジとして、Rubyそのものとそれを使っている多様な企業や機関を通じて人々の幸せに還流されていく様子は、まさにOSSコミュニティの理想形ではないでしょうか。</p>
<p>以上、<a href="https://2025.rubyworld-conf.org/ja/">Ruby World Conference 2025</a>のレポートでした!
メドレーでは今後もRubyを含めて様々なOSSコミュニティへの協賛、オーガナイズ、登壇、コントリビュートなどを通じてOSSの発展に寄与してまいります。</p>
<p>そして、私たちと一緒に <strong>「医療・ヘルスケアの未来をつくる仲間」</strong> を大募集しています!メドレーの事業や、OSSを通じた医療DXと医療人材不足解消への挑戦に興味を持たれたエンジニアの方は、ぜひこちら<a href="https://speakerdeck.com/medley/medley-engineer-guide">メドレー採用資料</a>もご覧になってください。</p>
<p><img __ASTRO_IMAGE_="{"src":"05集合写真.jpg","alt":"集合写真","index":0}">
左から安藤、登壇された牧さん、Tech Studio MATSUEの三原さん(積極的にご質問されてました)</p>
<p><img __ASTRO_IMAGE_="{"src":"07太鼓写真.png","alt":"太鼓","index":0}">
レセプションで松江の太鼓「鼕(どう)」を体験するMatz氏とBernard Banta氏</p>
<p><img __ASTRO_IMAGE_="{"src":"06飲み会集合写真.jpg","alt":"飲み会","index":0}">
レセプション後の流れで自然と2次会にお集まり頂いた、牧さんと親交の深い色々な会社の #rubyfriends の皆さんと記念撮影</p>
<p><img __ASTRO_IMAGE_="{"src":"08松江オフィス集合写真.jpg","alt":"松江オフィスの集合写真","index":0}">
メドレー松江エンジニアリングオフィス<a href="https://developer.medley.jp/entry/2023/07/31/120555/">「Tech Studio MATSUE」</a>の皆さんと一緒に</p>
<h1 id="were-hiring">We’re hiring!</h1>
<p>現在、メドレーでは一緒に働く仲間を募集しています。この記事やイベントを通じて興味を持っていただいた方は、ぜひお気軽にご連絡ください。
カジュアル面談も実施しています!</p>
<p><a href="https://hrmos.co/pages/medley/jobs">株式会社メドレー 求人一覧</a></p>メドレー医療 RubyRubyWorld歯科DX
- Hono Conference 2025 参加レポート - Silver Sponsor として協賛しました!https://developer.medley.jp/entry/2025/11/10/141629https://developer.medley.jp/entry/2025/11/10/141629はじめに
こんにちは、人材プラットフォーム本部 プロダクト開発部グループマネージャの松田です。ジョブメドレーアカデミーというオンライン動画研修・勤怠シフト管理の開発を行っています。
2025年10月18日(土)、Hono Conferenc...Mon, 10 Nov 2025 05:16:29 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは、人材プラットフォーム本部 プロダクト開発部グループマネージャの松田です。ジョブメドレーアカデミーという<a href="https://jm-academy.jp/">オンライン動画研修</a>・<a href="https://lp.kintai.jm-academy.jp/">勤怠シフト管理</a>の開発を行っています。</p>
<p>2025年10月18日(土)、<a href="https://honoconf.dev/2025">Hono Conference 2025</a>に参加してきました。Honoは、Cloudflare Workersなどのエッジ環境で注目を集める超高速・軽量Webフレームワークであり、私たちメドレーでも新規プロダクトの技術スタックとして採用しています。</p>
<p>今回、メドレーはSilver Sponsorとして本カンファレンスに協賛しました。私たちはHonoコミュニティの発展を支援するとともに、自社のエンジニアの城間(<a href="https://x.com/shiromie_dev">@shiromie</a>)も登壇し、HonoとAIを組み合わせた開発ワークフローについて発表しました。 本記事では、カンファレンスの熱気ある会場の様子と、<strong>Hono</strong>はもちろん、<strong>AI</strong>や<strong>Bun</strong>など、私たちが普段の業務で使用しているテーマを中心に、印象に残ったセッションを技術的な考察と共にご紹介します。</p>
<h1 id="会場の様子">会場の様子</h1>
<p>Hono Conference 2025は、docomo R&D OPEN LAB ODAIBAで開催されました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./images/venue-entrance.jpg","alt":"会場となった「docomo R&D OPEN LAB ODAIBA」の入口","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./images/reception.jpg","alt":"受付案内","index":0}"></p>
<p>カンファレンスは、Honoの作者<a href="https://x.com/yusukebe">@yusukebe</a>さんによるオープニングセッションから始まりました。</p>
<p>スライドには「<strong>週200万</strong> / <strong>月800万</strong>」というnpmダウンロード数が映し出され、このフレームワークがいかに注目されているかが分かります。この後のブース巡りや各セッションで、Honoを支えるエコシステムや活用事例を見ていきたいと思います。</p>
<p><img __ASTRO_IMAGE_="{"src":"./images/opening.jpg","alt":"Hono作者 yusukebeさんによるオープニング、npmダウンロード数の伸びに圧倒","index":0}"></p>
<p>今回のイベントでは、多くのノベルティがありました。Cloudflareのオレンジ色の靴下は特に印象的でした。また、Vercelのロゴが入ったステッカーと、クッキーをいただきました。ステッカーも充実しており、Denoのかわいい恐竜のステッカーや、JSP(JavaServer Pages)のロゴがありました。 特に、チームとして普段利用しているBunのステッカー(まんじゅう)も頂けて大満足でした。</p>
<p><img __ASTRO_IMAGE_="{"src":"./images/novelty2.jpg","alt":"ノベルティ2","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./images/novelty1.jpg","alt":"ノベルティ1","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./images/novelty3.jpg","alt":"ノベルティ3","index":0}"></p>
<h1 id="セッションのハイライト">セッションのハイライト</h1>
<p>私がリアルタイムで聴講したセッションの中で、印象に残ったいくつかのセッションとその感想を紹介します。</p>
<h2 id="blazing-fast-full-stack-apps-with-hono-and-jsx">Blazing Fast Full Stack Apps with Hono and JSX</h2>
<p>TwitchやYouTubeで人気のCJ氏(<a href="https://x.com/CodingGarden">@CodingGarden</a>)による発表です。ハイライトは、<strong>HonoがAPIフレームワークから、柔軟なフルスタックフレームワークへと進化した</strong>点です。</p>
<p>これまではAPI構築がメインでしたが、今回の発表では、Honoが標準でサポートするJSX、CSS in JSのように使えるHono CSS、ReactのSuspenseのように非同期データを扱えるストリーミング機能、そしてuseStateやuseEffectまで備えた軽量なクライアントコンポーネント(JSX DOM)機能がデモされました。</p>
<p>現在HonoをAPIサーバとして利用しているため、今回紹介されたHonoxは、今後の選択肢の一つとして非常に魅力的だと感じました。</p>
<p>CJ氏は人気のテックポッドキャスト『Syntax.fm』のホストとしても知られており、その語り口はさすがで、発表自体もとても丁寧で分かりやすかったです。</p>
<h2 id="interesting-memory-leak-with-hono-on-bun">Interesting memory leak with Hono on Bun</h2>
<p>Bun社に所属するSosuke Suzuki氏(<a href="https://x.com/__sosukesuzuki">@sosukesuzuki</a>)のセッションです。私たちのチームでもHono on Bunの組み合わせを採用しているため、メモリリークというテーマはとても楽しみにしていました。</p>
<p>ハイライトは、<strong>Honoのストリーミングヘルパーと、BunのGC(ガベージコレクション)の特性が組み合わさった点</strong>でした。</p>
<p>まず、Honoの処理において、レスポンスとストリームが互いを参照し合う「循環参照」が発生します。Bunが採用するJavaScriptCore(JSC)のGCは、本来これを検知して解放できるはずでした。</p>
<p>しかし、レスポンスからストリームへの参照が『ストロングリファレンス』という特殊な参照として実装されていました。これはGCのルートのように扱われるため、GCは「両方とも到達可能である」と判断してしまいます。その結果、どちらも解放されずにメモリリークを引き起こしていた、という内容でした。</p>
<p>Bunを普段から利用するので、こうしたウィークポイントを具体的に知れたのはとても参考になりました。このセッションを受け、Bun自体のバージョンアップを積極的に追随し、修正を確実に取り込んでいく重要性を再認識しました。</p>
<h2 id="scalarhono-api-referenceとmastraでシステム仕様書を自動更新するaiワークフロー構築してみた">@scalar/hono-api-referenceとMastraでシステム仕様書を自動更新するAIワークフロー構築してみた</h2>
<p>続いて、弊社メドレーのエンジニアの城間(<a href="https://x.com/shiromie_dev">@shiromie</a>)による登壇セッションです。このセッションでは、私たちが普段の開発で直面している<strong>ドキュメントの陳腐化</strong>という課題に対し、HonoとAIを組み合わせてどう解決したかを発表しました。</p>
<p>業務の合間を縫って構築したこのワークフローは、RAG(Bedrock + OpenSearch)とClaudeを活用したものです。特に、AIがハルシネーションを起こさず、事実に基づいた仕様書を書くようチューニングする点が、今回の取り組みにおける技術的なハイライトでした。</p>
<p>この取り組みは、まさに「AIが自動で成果物を作成し、人間がレビューするだけ」という理想を体現しています。具体的には、GitHub ActionsがAPI仕様書(llms.txt)の差分を検知すると、RAGが既存ドキュメントの関連箇所を特定し、Claudeが更新内容を判断してプルリクエストを自動作成します。開発者はそのPRをレビューするだけでドキュメントの鮮度が保たれます。</p>
<p>この取り組みは、AIを使った開発をより一層加速させる発表でした。</p>
<p><img __ASTRO_IMAGE_="{"src":"./images/shiroma.png","alt":"城間さんの登壇","index":0}"></p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/dde53684a8804b1291fc833bf419893e" title="Hono Conference 2025 | @scalar/hono-api-reference × Mastra で ドキュメントを自動更新する AIワークフロー構築してみた" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<h1 id="closing---hono-cli-登場">Closing - Hono CLI 登場</h1>
<p>Hono ConfのClosingで終わったかと思いきや、そこで<strong>Hono CLI</strong>が世界最速で発表され、まさかの発表に会場は熱狂に包まれました。
コンセプトは「<strong>CLI for Human and AI</strong>」で、Hono自身もAIフレンドリーを意識した進化を遂げていることがわかりますし、ここにいるエンジニア全員が求めていたものでは?と感じました。</p>
<h3 id="aiと開発者のための主要機能">AIと開発者のための主要機能</h3>
<p><strong>1. AIエージェントの自律性向上 (AI Friendly)</strong></p>
<p>AIエージェントがHonoをより深く理解し、扱えるようになります。</p>
<ul>
<li><code>hono search</code>
<ul>
<li>AIがドキュメントを自律的に検索し、Markdown形式の仕様を正確に把握します。</li>
</ul>
</li>
<li><code>hono request</code>
<ul>
<li>サーバーを一切起動せず(<code>app.request()</code>を直接実行)、型安全なリクエストをテスト実行できます。</li>
</ul>
</li>
</ul>
<p><strong>2. 革命的な開発体験 (Developer Experience)</strong></p>
<p>開発者のワークフローを根本から変える機能が導入されました。</p>
<ul>
<li><code>hono serve --use <middleware></code>
<ul>
<li>個人的に衝撃的だった機能の一つです。</li>
<li>コードを一行も変更せず、コマンドラインから直接ミドルウェアを注入(例:<code>--use logger</code>)できます。</li>
</ul>
</li>
</ul>
<p><strong>3. 劇的な本番性能の最適化 (Performance)</strong></p>
<ul>
<li><code>hono optimize</code>
<ul>
<li>アプリのルート情報を事前コンパイルする <code>PreparedRegExpRouter</code> を生成。</li>
<li>これにより、ルーターの<strong>初期化速度が16倍以上高速化</strong>し、バンドルサイズも大幅に削減されます。</li>
</ul>
</li>
</ul>
<p>Hono CLIを利用することで、開発速度が劇的に加速し、より速くプロダクトの価値を提供できるようになるだろうと感じました。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/3cfa0a2bcf4546488026184dfe88e31b" title="Introduce Hono CLI" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<div class="remark-link-card-plus__container">
<a href="https://github.com/honojs/cli" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">GitHub - honojs/cli: CLI for Humans and AI with Hono</div>
<div class="remark-link-card-plus__description">CLI for Humans and AI with Hono. Contribute to honojs/cli development by creating an account on GitHub.</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://github.githubassets.com/favicons/favicon.svg" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">github.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://opengraph.githubassets.com/2dd0a61393b8c40a543d46fdeb463be43b6cd3796d3938707a031b0b216e5034/honojs/cli" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="終わりに">終わりに</h1>
<p>2025年10月18日に開催された「Hono Conference 2025」に参加してきました。Honoに関わる企業・人々が一堂に集まるとても学びの多かったカンファレンスでした。 技術的な側面では、Honoが単なる「高速なAPIフレームワーク」から、JSXによるフルスタック開発、そしてHono CLIによる「AIエージェント開発基盤」へと、そのエコシステムを急速に拡大させていることを実感しました。 特に、私たちが実践している「Hono × AI」の取り組みと、コミュニティのAIに対する大きな流れが一致していたことは、大きな自信となりましたし、これからのHonoの進化が楽しみです。 素晴らしい場を創り上げた主催者・スタッフ・登壇者・協賛企業の皆様に深く感謝いたします。</p>
<p>メドレーでは、カンファレンスへのスポンサーの他にも、イベントの開催などを通じて技術とコミュニティへの貢献を続けています。</p>
<h1 id="メドレーではエンジニアを積極採用中です">メドレーではエンジニアを積極採用中です!</h1>
<p>メドレーではテクノロジーを活用して、医療ヘルスケアの未来をつくるプロダクトを開発しています。Honoが好きな方はもちろん、バックエンド、フロントエンド、インフラ、モバイルなど、ご自身の専門性を活かして医療ヘルスケア領域の課題解決に取り組みたいという、幅広いエンジニアの方を募集しています。ぜひカジュアル面談にお越しください!</p>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs/1000098" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">エンジニア/人材プラットフォーム本部 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">エンジニア/人材プラットフォーム本部(株式会社メドレー)の求人情報です。 | HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:333333,c_pad,w_978,h_513/m/577c/images/2118206564349132800_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs/1000119" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">テックリード・シニアエンジニア/人材プラットフォーム本部 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">テックリード・シニアエンジニア/人材プラットフォーム本部(株式会社メドレー)の求人情報です。 | HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:333333,c_pad,w_978,h_513/m/577c/images/2118206564349132800_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs/1000105" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">新規事業プロダクトエンジニア/人材プラットフォーム本部 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">新規事業プロダクトエンジニア/人材プラットフォーム本部(株式会社メドレー)の求人情報です。 | HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:333333,c_pad,w_978,h_513/m/577c/images/2118206564349132800_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- Designship 2025 参加レポート - ゴールドスポンサーとして協賛しました!https://developer.medley.jp/entry/2025/10/22/190055https://developer.medley.jp/entry/2025/10/22/190055こんにちは。人材プラットフォーム本部でデザイナーをしている米田です。
メドレーは 2025年10月11日〜12日に開催された 「Designship 2025」 に、ゴールドスポンサーとして協賛しました🎉
会場の様子
「Designshi...Wed, 22 Oct 2025 00:00:00 GMT<p>こんにちは。人材プラットフォーム本部でデザイナーをしている米田です。</p>
<p>メドレーは 2025年10月11日〜12日に開催された 「Designship 2025」 に、ゴールドスポンサーとして協賛しました🎉</p>
<h1 id="会場の様子">会場の様子</h1>
<p>「Designship」はオフラインとオンラインのハイブリッド形式で開催され、メインステージとオープンステージの2会場構成。
<br>2日間にわたり、約80本ものデザインセッションが絶え間なく行われました。圧巻のボリュームと豪華な登壇者陣でした!</p>
<p>当日の雰囲気をショート動画風にまとめてみました。</p>
<iframe src="https://drive.google.com/file/d/1SWilxZhnfMpPaFhKJ_Qwq5VeKzfbQdS3/preview" width="350" height="480" allow="autoplay"></iframe>
<h1 id="弊社ブースの様子">弊社ブースの様子</h1>
<p><img __ASTRO_IMAGE_="{"src":"./1.png","alt":"メドレーブース","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./2.png","alt":"メドレーブース_小山さん","index":0}"></p>
<p>弊社ブースは会場入口付近に設置され、多くの方にお立ち寄りいただきました。
アンケートで、皆さんが日頃感じる医療課題についてお伺いしたところ、「待ち時間が長い」 という声が圧倒的でした。</p>
<p>この課題を解決する一つの取り組みとして、私たちはオンライン診療・服薬指導を通じて「通院の待ち時間」をなくすことを目指した <strong>「総合医療アプリ CLINICS」</strong> を提供しています。
<br>👉 CLINICS公式サイトは<a href="https://clinics-app.com/">こちら</a></p>
<p>また、弊社のイベント情報やテックブログをお届けするメーリングリストに登録いただいた方には、その場でノベルティをお渡ししました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./8.png","alt":"メドレーブース","index":0}"></p>
<h1 id="他社ブースの様子">他社ブースの様子</h1>
<p><img __ASTRO_IMAGE_="{"src":"./3.png","alt":"他社ブース","index":0}"></p>
<p>フラー株式会社様 の「夢を書いていく」ブースでは、他の来場者の夢を拝見できる素敵な展示がありました。「犬・猫を飼いたい」という方がパッと見ただけでも3人いらっしゃいました。素敵ですね。🐶🐱</p>
<p><img __ASTRO_IMAGE_="{"src":"./4.png","alt":"ブレイキングダウン","index":0}"></p>
<p>自分でも書いてみました。</p>
<p>ファインディ株式会社様ではなんとゲームができました!
<br>ゲームの出来が良すぎて任天堂のイベントにきてしまったような気持ち。
<br>クリアポーチもステッカーもとても可愛いです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./5.png","alt":"アガルゲーム","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./6.png","alt":"Findyノベルティ","index":0}"></p>
<h1 id="弊社のセッションの様子">弊社のセッションの様子</h1>
<p>オープンステージでは、弊社から2名が登壇しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./7.png","alt":"登壇写真","index":0}"></p>
<p>医療プラットフォーム本部の<strong>前田(写真左)</strong> がプロダクト群統合とブランドリニューアルの取り組みを、
<br>人材プラットフォーム本部の<strong>小山(写真右)</strong> がアクセシビリティ向上への取り組みを紹介しました。</p>
<h1 id="他社のセッションの様子">他社のセッションの様子</h1>
<p><strong>株式会社スタメン様「組織はみんなでつくる。デザイナーが仕掛ける急拡大する組織のカルチャーづくり」</strong></p>
<div class="remark-link-card-plus__container">
<a href="https://speakerdeck.com/mkasumi/zu-zhi-haminnadetukuru-dezainagashi-gua-keruji-kuo-da-suruzu-zhi-nokarutiyadukuri" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">組織はみんなでつくる。デザイナーが仕掛ける急拡大する組織のカルチャーづくり</div>
<div class="remark-link-card-plus__description">Designship2025でお話ししたセッション資料です。
https://design-ship.jp/2025/</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://d1eu30co0ohy4w.cloudfront.net/assets/favicon-bdd5839d46040a50edf189174e6f7aacc8abb3aaecd56a4711cf00d820883f47.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">speakerdeck.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://files.speakerdeck.com/presentations/b4f6f4c3ab674015b7d0704f335e00c7/slide_0.jpg?36872924" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>組織カルチャーという難しいテーマに対し、<strong>デザインの力で課題解決を試みる</strong> お話が印象的でした。
<br>人数増加によるカルチャーの希薄化を課題と捉え、LT会でのアイデア共有や、うちわ・ペンライトなどを使った施策まで展開。
<br>「課題を見つけ、ユーザーを理解し、解決策を実行していく」、これはまさにデザイナーの仕事そのものですね。
<br>自分の組織もデザインしていく意識を持たねば…と気が引き締まりました。</p>
<p><strong>note株式会社様「作品を盛れない僕が掲げたポートフォリオ不要宣言」</strong></p>
<div class="remark-link-card-plus__container">
<a href="https://note.com/saladdays/n/n698db934651e" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">作品を盛れない僕が掲げたポートフォリオ不要宣言|宇野雄 / note inc. CDO</div>
<div class="remark-link-card-plus__description">これはDesignship 2025の発表資料です。当日はゆったりと見てもらうために、メモや撮影などしなくていいよう、記録として残しています。 -------👇ここから発表内容👇------- まず最初に問いから入ります──「ポートフォリオって、そもそも何でしょう?」 会場の皆さんの多くは、この言葉をご存じで、実際に作ったことがあると思います。 出典:デジタル大辞泉 語源を辿ると、もともとは書類や紙を入れる折りたたみのケースのこと。そこから意味が広がっていきました。 投資の世界での「ポートフォリオ」は資産の一覧や配分を指しますよね。 今日ここで使う意味は、最後の「作品集/</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://assets.st-note.com/poc-image/manual/note-common-images/production/svg/production.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">note.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://assets.st-note.com/production/uploads/images/220840354/rectangle_large_type_2_34d098f21a7f37afd704e487b5f34f66.png?fit=bounds&quality=85&width=1280" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>ポートフォリオといえば、ついつい見栄えをどれだけよくするかなどを考えてしまいますが、こちらは「ポートフォリオをなくしてしまおう」という発想。
<br>ポートフォリオの提出を任意にし、その代わり、実際の業務に取り組んでもらい、評価をしていく形に変えたそう。
<br>私も面接時に人のポートフォリオを拝見する機会がありました。知りたいのは「どういう考えを持って課題解決をしていく方か、どこにこだわりや美意識があるのか、無理せずお互いが合う性格・価値観か」などだったなあと、なぜポートフォリオを見るのかを、今一度考えさせられました。</p>
<h1 id="おわりに">おわりに</h1>
<p>メドレーはこれからも、医療ヘルスケアの未来を作るために、アプリを始めとした様々なプロダクト・サービスを開発し提供していきます。</p>
<p>最後に改めて、Designshipの運営の皆様、登壇されたスピーカーの皆様、参加者の皆様、お疲れ様でした、そしてありがとうございました!</p>
<p>現在、メドレーでは<strong>一緒に働く仲間</strong>を募集しています。この記事やイベントを通じて興味を持っていただいた方は、ぜひお気軽にご連絡ください。
<br>カジュアル面談も実施しています!</p>
<p><a href="https://hrmos.co/pages/medley/jobs">株式会社メドレー 求人一覧</a></p>
- データ分析AIエージェントの実践 - Slack × Devin × Context Engineeringhttps://developer.medley.jp/entry/2025/10/23/190054https://developer.medley.jp/entry/2025/10/23/190054はじめに
こんにちは、医療プラットフォーム本部データ戦略グループの安東です。
以前、私たちは「データ戦略グループにおけるcontext engineeringの取り組み」という記事で、LLMに適切なコンテキストを提供することで分析精度を向上...Thu, 16 Oct 2025 10:00:54 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは、医療プラットフォーム本部データ戦略グループの安東です。</p>
<p>以前、私たちは「<a href="https://developer.medley.jp/entry/2025/09/01/">データ戦略グループにおけるcontext engineeringの取り組み</a>」という記事で、LLMに適切なコンテキストを提供することで分析精度を向上させるContext Engineeringの実践について紹介しました。</p>
<p>本記事は、その取り組みをさらに一歩進め、<strong>ビジネスユーザーが日常的に使える分析AIエージェントとして社内リリースした事例</strong>を紹介します。</p>
<p>組織拡大に伴うデータ分析ニーズ増加とアナリスト不足という課題に対し、私たちはContext Engineeringの知見を活かし、<strong><a href="https://devin.ai/">Devin</a>を分析AIエージェントとして活用した分析基盤</strong>を構築しました。</p>
<p>本記事では、<strong>Slackをインターフェースとした分析AIエージェントのアーキテクチャ</strong>と、データ戦略グループの具体的な取り組みについて解説します。</p>
<p><img __ASTRO_IMAGE_="{"src":"./images/分析AIAgent_データ戦略グループの役割.png","alt":"データ戦略グループの役割","index":0}"></p>
<h1 id="分析aiエージェントの全体フロー">分析AIエージェントの全体フロー</h1>
<p>分析AIエージェント全体の流れを以下の図で示します。</p>
<p><img __ASTRO_IMAGE_="{"src":"./images/分析AIAgent_フロー.png","alt":"分析AIエージェントのフロー","index":0}"></p>
<p>分析は、次の5つのステップで実行されます。</p>
<p><strong>① Slackで自然言語で質問</strong><br>
ビジネスユーザー(PdM、Marketing、Sales、CSなど)が、Slackで自然言語により分析を依頼します。</p>
<p>分析依頼の専用フォームを用いることで、分析の目的、対象プロダクト、希望スピードなどを構造化して入力できます。</p>
<p><strong>② AIエージェントが質問を解釈し、分析設計とツール使用</strong><br>
Devinが中心となり、以下のツールを活用しながら分析を設計・実行します。</p>
<ul>
<li>MCP Toolbox for Databases: データベースへのアクセス</li>
<li>Confluence: 社内ドキュメントの参照</li>
<li>pandas: データ分析とグラフ作成</li>
<li>Playbook/Knowledge: 分析設計とドメイン知識の参照</li>
</ul>
<p><strong>③ 分析に必要なKnowledge参照</strong><br>
GitHubに格納されたKnowledgeリポジトリから、必要な情報を取得します。</p>
<ul>
<li>ビジネスメタデータ: プロダクト固有のビジネスロジックやKPI定義</li>
<li>テクニカルメタデータ: データモデルの構造や分析手法</li>
</ul>
<p><strong>④ データの取得</strong><br>
データウェアハウスから分析に必要なデータを取得します。</p>
<p>データはdbtで変換されたmartモデルを中心に、分析パスに応じて最適なレイヤーにアクセスします。</p>
<p><strong>⑤ Slackで分析結果回答</strong><br>
Slackのスレッドに、以下の形式で分析結果を返却します。</p>
<ul>
<li>分析の結果・要約</li>
<li>Markdown形式のレポート</li>
<li>CSV形式のデータ</li>
<li>グラフ(PNG、HTML)</li>
<li>実行したSQLクエリ</li>
</ul>
<p>この一連の流れにより、ビジネスユーザーはSlack内で数分で分析結果を得られます。</p>
<h1 id="データ戦略グループの取り組み">データ戦略グループの取り組み</h1>
<p>データ戦略グループとして、この分析フローを実現するためにどのような工夫を行っているのか、具体的に解説します。</p>
<h2 id="-slackインターフェースユーザー体験の最適化">① Slackインターフェース:ユーザー体験の最適化</h2>
<h3 id="なぜslackなのか">なぜSlackなのか</h3>
<p><strong>ビジネスユーザーにとって、データ分析の障壁は、「そもそも分析を依頼すること自体が難しい」という点にあります。</strong></p>
<p>SQLエディタを開く、分析の設計をする、アナリストに分析依頼することは、多くのビジネスユーザーにとって日常的な行動ではありません。</p>
<p>一方、Slackは多くの組織で既に日常的に使われているツールです。メッセージを送る感覚で分析を依頼できれば、<strong>認知負荷を最小化</strong>できます。</p>
<p>私たちはこの点に着目し、Slackを分析AIエージェントのインターフェースとして選択しました。</p>
<h3 id="フォーム設計によるプロンプトの最適化">フォーム設計による「プロンプトの最適化」</h3>
<p>しかし、<strong>Slackで自由フォーマットで依頼を受けると、AIエージェントが意図を正しく汲み取れず分析品質がブレてしまいます。</strong></p>
<p>そこで、<strong>構造化フォームを用いることで、分析に必要な情報を確実に収集し、AIエージェントへの入力を最適化する設計</strong>としました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>従来アプローチ: 自由記述 → AIエージェントが意図を推測 → 品質がブレる</span></span>
<span class="line"><span>改善アプローチ: 構造化フォーム → 必要情報を確実に収集 → 品質が安定</span></span></code></pre>
<p>分析フォームは試行錯誤中ですが、分析設計において重要な情報を必須にしています</p>
<p><img __ASTRO_IMAGE_="{"src":"./images/分析AIAgent_slack.png","alt":"Slackでの分析依頼フォーム","index":0}"></p>
<p>この設計により、ユーザーは「どんな分析をしてほしいか」を構造的に伝えられるようになり、AIエージェントは迷わず適切な分析パスを選択できます。</p>
<h4 id="devin-macroの活用">Devin Macroの活用</h4>
<p>フォーム送信時には、Macroを用いてPlaybookパスを自動選択し、<strong>依頼内容を構造化した形でDevinに渡します</strong>。</p>
<p>これにより、以下のようなワークフローが実現されます。</p>
<ol>
<li>ユーザーがSlackフォームで依頼を送信</li>
<li>MacroがPlaybookの該当セクションを特定</li>
<li>構造化された依頼 + Playbookパスの情報がDevinに送信</li>
<li>DevinがKnowledge Repositoryを参照しながら分析を実行</li>
</ol>
<p><strong>工夫のポイントは、自由度と制約のバランス</strong>です。</p>
<p>フォームによる構造化は「制約」ですが、その制約がユーザーに適切な依頼方法を示す効果も持ちます。</p>
<h2 id="-aiエージェント実行devinの活用">② AIエージェント実行:Devinの活用</h2>
<p>Devinは、AIエージェントとして分析タスクを自律的に実行します。</p>
<p>Slackから受け取った構造化された依頼に基づき、以下のプロセスで分析を進めます。</p>
<h3 id="自律的な分析タスクの実行">自律的な分析タスクの実行</h3>
<p>Devinは、単に質問に答えるだけでなく、以下のような複雑なタスクを自律的に遂行します。</p>
<ul>
<li>タスクの理解とプランニング: 依頼内容を解釈し、必要な分析ステップを計画</li>
<li>ツールの選択と使用: MCPや分析パッケージなど、適切なツールを選択</li>
<li>データアクセス: MCP Serverを通じてデータウェアハウスに接続</li>
<li>分析の実行: SQLクエリの作成、データ加工、グラフ作成</li>
</ul>
<h3 id="knowledge-repositoryとの連携">Knowledge Repositoryとの連携</h3>
<p>Devinは、分析実行時にGitHubのKnowledge Repositoryを常に参照し、以下の情報に基づいて適切な判断を行います。</p>
<ul>
<li>Playbook: 分析の作法、コスト最適化、品質ゲート</li>
<li>Product Knowledge: プロダクト固有のビジネスロジックとKPI定義</li>
</ul>
<p>この連携により、Devinは<strong>単なる汎用的なAIエージェントではなく、組織固有の分析ノウハウを持つ専門的なデータアナリストとして機能</strong>します。</p>
<h2 id="-context-engineeringの実践">③ Context Engineeringの実践</h2>
<p><strong>Prompt Engineeringが「どう尋ねるか」に焦点を当てるのに対し、Context Engineeringは「どんな知識を与えるか」に焦点を当てます。</strong></p>
<p>Knowledge Repositoryの利点は、再現性、保守性、チーム管理のしやすさです。</p>
<p>プロンプトに知識を埋め込むのではなく、外部化された知識リポジトリとして管理することで、チーム全体で知識を共有し、継続的に改善できます。</p>
<p>私たちのContext Engineeringへの取り組みの詳細は、以前の記事「<a href="https://developer.medley.jp/entry/2025/09/01/">データ戦略グループにおけるcontext engineeringの取り組み</a>」でも紹介しています。</p>
<h3 id="githubをssotとした知識管理">GitHubをSSOTとした知識管理</h3>
<p>私たちは、GitHubリポジトリをSingle Source of Truth(SSOT)として、分析AIエージェントが参照する全ての知識を一元管理しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>knowledge-repository/</span></span>
<span class="line"><span>├── agent/</span></span>
<span class="line"><span>│ ├── playbook/</span></span>
<span class="line"><span>│ │ ├── playbook-main.md # メインフロー</span></span>
<span class="line"><span>│ │ ├── playbook-paths.md # 分析依頼に応じた分析パス</span></span>
<span class="line"><span>│ │ ├── playbook-cost.md # BigQueryコストの最適化</span></span>
<span class="line"><span>│ │ └── playbook-quality.md # 分析における品質と禁止事項</span></span>
<span class="line"><span>│ └── configurations/</span></span>
<span class="line"><span>└── product_knowledge/</span></span>
<span class="line"><span> ├── product_a_knowledge.md # Product Aのドメイン知識</span></span>
<span class="line"><span> ├── product_b_knowledge.md # Product Bのドメイン知識</span></span>
<span class="line"><span> ├── product_c_knowledge.md # Product Cのドメイン知識</span></span>
<span class="line"><span> ...etc</span></span></code></pre>
<p>この構造化により、AIエージェントは必要な知識を的確に参照でき、チームメンバーは知識の追加と更新を容易に行えます。</p>
<h3 id="playbookの設計思想">Playbookの設計思想</h3>
<p>Playbookは、データアナリストの思考プロセスを再現することを目的に設計されています。</p>
<h4 id="責務の分割">責務の分割</h4>
<p>単一のPlaybookファイルではなく、目的別に分割することで保守性を高めています。</p>
<ul>
<li><strong>playbook-main.md</strong>: 分析の基本フロー
<ol>
<li>分析の目的を明確化</li>
<li>データ品質をチェック</li>
<li>BigQueryコストを見積もり</li>
<li>アウトプットから逆算して分析パスを選択</li>
</ol>
</li>
<li><strong>playbook-paths.md</strong>: 3つの分析パスの定義
<ul>
<li><strong>Fast</strong>: 既存のmartモデルのみを使用、低コスト</li>
<li><strong>Standard</strong>: 中間テーブルを参照、バランス型</li>
<li><strong>Deep</strong>: 複数のレイヤーのデータを参照、高精度・高コスト</li>
</ul>
</li>
<li><strong>playbook-cost.md</strong>: コスト最適化戦略
<ul>
<li>クエリ実行前のドライラン</li>
<li>パーティションとクラスタリングの活用</li>
<li>全量スキャンの回避</li>
</ul>
</li>
<li><strong>playbook-quality.md</strong>: 品質ゲートと禁止事項
<ul>
<li>集計前のデータ件数確認</li>
<li>NULL値の扱いの明示</li>
<li>結果の妥当性チェック</li>
</ul>
</li>
</ul>
<p>この分割により、各Playbookは独立して更新でき、特定の目的(例: コスト削減)に特化した改善が容易になります。</p>
<h3 id="product-knowledgeの役割">Product Knowledgeの役割</h3>
<p>各プロダクトには、固有のビジネスロジックやドメイン知識があります。これらを <code>product_knowledge/</code> 配下にMarkdownファイルとして整理しています。</p>
<p>例えば、 <code>product_a_knowledge.md</code> には以下のような情報が含まれます。</p>
<ul>
<li>プロダクトの概要とビジネスモデル</li>
<li>重要なKPI定義</li>
<li>よくある分析パターン(リテンション分析、コホート分析など)</li>
<li>データの特性や注意点</li>
</ul>
<p>AIエージェントは、依頼フォームで指定されたプロダクトに応じて、該当するProduct Knowledgeを参照し、適切な分析を行います。</p>
<h2 id="-データ基盤aiに最適化されたデータ設計">④ データ基盤:AIに最適化されたデータ設計</h2>
<p>分析AIエージェントが効率的に動作するためには、AIが理解しやすいデータ構造が不可欠です。私たちは、dbtを用いてデータウェアハウスのモデリングを行い、階層的なデータ構造を構築しています。</p>
<h3 id="3層のデータ構造">3層のデータ構造</h3>
<p>データウェアハウスは、以下の3層で構成されています。</p>
<ul>
<li><strong>staging(加工処理データ)</strong>: 各種データソースを加工処理したデータ</li>
<li><strong>intermediate(中間層)</strong>: データクレンジングや結合を行った中間テーブル</li>
<li><strong>mart(分析用データマート)</strong>: ビジネスロジックを適用し、分析に最適化されたテーブル</li>
</ul>
<p>この階層化により、スピードとコストのトレードオフを明示的に管理できます。</p>
<h1 id="実際の動作と効果">実際の動作と効果</h1>
<p>Slackで分析を依頼すると、以下のような流れで結果が返ってきます。</p>
<ol>
<li>ユーザーがフォームで依頼を送信(所要時間: 1分)</li>
<li>DevinがKnowledge Repositoryを参照しながら分析を実行(Fast: 5分、Standard: 30分)</li>
<li>分析結果がSlackスレッドで返却(グラフや数値サマリ付き)</li>
</ol>
<p><img __ASTRO_IMAGE_="{"src":"./images/分析AIAgent_slack_answer_mask.png","alt":"実際の分析結果例","index":0}"></p>
<p>社内リリースからまだ日が浅く、現在も試行錯誤を続けています。</p>
<p>ビジネスユーザーからのフィードバックをもとに、Playbookの改善やProduct Knowledgeの拡充を継続的に行いながら、より多くの分析ニーズに対応できるよう取り組んでいます。</p>
<h1 id="aiエージェントと人間アナリストの役割分担">AIエージェントと人間アナリストの役割分担</h1>
<p>分析AIエージェントは、全ての分析業務を代替するものではありません。分析の複雑性と工数に応じて、適切な役割分担が重要です。</p>
<p><img __ASTRO_IMAGE_="{"src":"./images/analysis_area.png","alt":"分析領域の棲み分け","index":0}"></p>
<p>分析業務は、その複雑性に応じて3つの領域に分類できます。</p>
<ol>
<li><strong>BIツール</strong>: 定型レポートやダッシュボードでの即座な確認</li>
<li><strong>AIエージェント</strong>: 本記事で紹介した領域。アドホックな集計や探索的分析を自律的に実行</li>
<li><strong>データアナリスト + AI</strong>: 曖昧な要件の明確化やシミュレーション、戦略的な意思決定支援</li>
</ol>
<p>現在、AIエージェントは②の領域を中心に活用されており、定型的で構造化された分析を効率化することで、<strong>人間アナリストが戦略的な分析に集中できる環境を作っています。</strong></p>
<h1 id="今後の展望">今後の展望</h1>
<p>現在の分析AIエージェントは、一部プロダクトに対応していますが、今後は以下のような拡張を計画しています。</p>
<ul>
<li>データソースの拡充: セールスやマーケティングデータへの対応</li>
<li>横断的な分析: プロダクトデータとセールスデータの統合分析</li>
<li>より高度な分析手法: 機械学習モデルの適用や予測分析</li>
</ul>
<p>本記事では、Slack、Devin、Context Engineeringを組み合わせた分析AIエージェントのアーキテクチャと設計思想を紹介しました。</p>
<p>データ分析AIエージェントの開発に取り組む方々の参考になれば幸いです。</p>
<h1 id="were-hiring">We’re hiring!</h1>
<p>メドレーの医療プラットフォーム本部のデータ戦略グループでは、AI 時代の新しいデータ分析にチャレンジしたいデータエンジニアを募集しています。医療ヘルスケア領域でのAI×データ活用にご興味をお持ちの方は、ぜひお気軽にお声がけください!</p>
<p><a href="https://hrmos.co/pages/medley/jobs/1000060">データエンジニア/医療プラットフォーム本部の募集詳細はこちら</a></p>AIデータ分析Context EngineeringSlackDevin
- 運用されつづけるブランドをつくるために — MEDLEY AI CLOUD リブランディング「苦悩と葛藤の 8 ヶ月」の舞台裏 —https://developer.medley.jp/entry/2025/09/26/095917https://developer.medley.jp/entry/2025/09/26/095917こんにちは。株式会社メドレー 医療プラットフォーム本部 デザイン室長の前田です。
こちらの記事は「MEDLEY Summer Tech Blog Relay」の 23 日目の記事です。
はじめに
9 月 1 日、メドレーは新ブランド「ME...Fri, 26 Sep 2025 00:59:17 GMT<p>こんにちは。株式会社メドレー 医療プラットフォーム本部 デザイン室長の前田です。</p>
<p>こちらの記事は「MEDLEY Summer Tech Blog Relay」の 23 日目の記事です。</p>
<p><img __ASTRO_IMAGE_="{"src":"image1.png","alt":"MEDLEY AI CLOUD リブランディングのメインビジュアル","index":0}"></p>
<h1 id="はじめに">はじめに</h1>
<p>9 月 1 日、メドレーは新ブランド「MEDLEY AI CLOUD」を<a href="https://www.medley.jp/release/20250901-3.html">ニュースリリース</a>で発表しました。</p>
<p>これまで個別に展開してきた医療機関向けの業務支援システムを「MEDLEY AI CLOUD」として統合的にブランディングし、各プロダクトロゴも刷新しました。</p>
<p>この記事では <strong>「なぜこのプロジェクトを立ち上げたのか」</strong>、<strong>「どんな課題があったのか」</strong>、<strong>「どんな未来を描いているのか」</strong> についてお話しします。</p>
<h1 id="なぜリブランディングをすることにしたのか">なぜリブランディングをすることにしたのか</h1>
<p>メドレーの医療プラットフォームは 10 年前、医療事典 MEDLEY からスタートしました。その後、CLINICS、Pharms(現在、MEDIXS に統合)、DENTIS などのプロダクトを開発・提供し、MALL、MINET、Lalune、@link、MEDIXS などメドレーにジョインするプロダクトも増えてきました。プロダクトによっては 20 年以上提供し続けてきた歴史があります。</p>
<p>私たちの強みは、病院・診療所・薬局向けの基幹システムから患者アプリまでを一気通貫で提供していること。<br>
しかし、プロダクトが増えるにつれ <strong>「統一感のなさ」や「ブランドのつながりが見えないこと」</strong> が課題として浮かび上がってきました。</p>
<p><img __ASTRO_IMAGE_="{"src":"image2.png","alt":"医療プラットフォームのプロダクト群","index":0}">
<em>医療プラットフォームのプロダクト群</em></p>
<h1 id="組織づくりから始まった">組織づくりから始まった</h1>
<p>2024 年初め、メドレーの医療プラットフォームに在籍するデザイナーはわずか 5 名。プロダクト開発業務だけで手一杯で、ブランド構築を考える余裕は微塵もありませんでした。</p>
<p>デザイン人材不足の課題を解消するために、<strong>デザイン組織の立ち上げと採用の強化</strong> から取り組みはじめました。</p>
<p>「デザイナー」という幅広い募集要項で採用をかけていたため、私たちが求めるスキルや役割が十分に伝わらず、なかなか採用決定に至らない状況が続いていました。</p>
<p>そこで、役割を「プロダクトデザイナー」と「コミュニケーションデザイナー」に分けて募集したところ、採用が一気に進展。結果として、2024 年末には 11 名体制となり、デザイナー同士で協力しあえる組織基盤を整えることができました。(2025 年 9 月時点では 14 名)</p>
<p><img __ASTRO_IMAGE_="{"src":"image3.png","alt":"デザイン組織の変遷","index":0}">
<em>デザイン組織の変遷</em></p>
<h1 id="ブランド構築プロジェクト始動">ブランド構築プロジェクト始動</h1>
<p>組織体制が整ってきた 2025 年初め、いよいよブランド構築プロジェクトを開始します。最初のステップは医療プラットフォームの各プロダクトとして核となる「ブランド DNA」の設計から着手しました。</p>
<p>ブランドの軸を定めるため、指針を立てて、わかりやすく言語化することで、関係者の意識統一を図るのが目的です。</p>
<p>正直、私自身この規模のリブランディングを一気に対応していくことが初めての試みだったこともあり、どのように設計していくべきか悩みました。いろんなブランドの本を読み漁ったのですが、その中でも「<a href="https://www.amazon.co.jp/%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%A8%E3%83%BC%E3%82%AF%E3%81%AE%E3%82%A2%E3%83%BC%E3%83%88%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%82%BF%E3%83%BC%E3%81%8C%E3%81%84%E3%81%BE%E3%80%81%E6%97%A5%E6%9C%AC%E3%81%AE%E3%83%93%E3%82%B8%E3%83%8D%E3%82%B9%E3%83%AA%E3%83%BC%E3%83%80%E3%83%BC%E3%81%AB%E4%BC%9D%E3%81%88%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8-%E5%B0%8F%E5%B1%B1%E7%94%B0-%E8%82%B2/dp/4295402958">ニューヨークのアートディレクターがいま、日本のビジネスリーダーに伝えたいこと</a>」が今回のプロジェクトを進めていくうえで参考になりました。</p>
<p>ブランド DNA を設計し、経営層やプロダクト責任者と議論を重ね、ブラッシュアップしていきました。</p>
<p>それと平行して、各プロダクトの LP に活用するデザインシステムの検討も進めました。見た目の統一感だけでなく「顧客に安心と信頼を届けるブランド体験」を実現するフロントエンド設計も同時に目指しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"image4.png","alt":"ブランド DNA 検討中の資料","index":0}">
<em>ブランド DNA 検討中の資料</em></p>
<h1 id="ロゴ刷新は予定外だった">ロゴ刷新は予定外だった</h1>
<p>当初、ロゴの刷新は計画していませんでした。<br>
ちょうど同時期に CLINICS アプリのリブランディングが進んでおり、デザイナーのリソースを集中させる判断をしていたからです。</p>
<p>しかし、ブランド DNA を定義し、各プロダクトのサービスサイトをリニューアルしていく中で、それだけではブランドの一体感や方向性を十分に示せないのではないかという課題が浮かび上がってきました。</p>
<p>加えて、各プロダクトで AI 機能の搭載が検討され始めたことを背景に、今後の医療プラットフォームの魅力を「医療 SaaS + AI」という形で明確に示すため、統合ブランド「MEDLEY AI CLOUD」を立ち上げる決断を下します。</p>
<p>こうして最終的に、「統合されたブランド体験を象徴するためにはロゴ刷新が欠かせない」と判断しました。そこで全プロダクトのロゴ刷新を含む、大規模なリブランディングへと舵を切ったのです。</p>
<h1 id="ai-活用の業務イメージをどう伝えるか">AI 活用の業務イメージをどう伝えるか</h1>
<p>ロゴ刷新の方針が固まり、各プロダクトで共通して使えるデザインシステムの検討も整いました。あとは「実装あるのみ!」と意気込んでいた、その矢先です。</p>
<p>新たな課題が立ちはだかりました。<br>
ーー「医療 SaaS + AI」という魅力を、どう伝えるのか?</p>
<p>当時、AI 機能を搭載したプロダクトはまだ開発途中。<br>
実際の医療現場でどう役立つのか、どのようにして効率化につながるのか。<br>
その具体的なイメージを示す手段が不足しているのでは…。<br>
そんな不安が広がっていったのです。</p>
<h1 id="自ら-ai-活用で業務効率化を体感してみる">自ら AI 活用で業務効率化を体感してみる</h1>
<p>そんな不安を打ち消すきっかけになったのが、「AI 活用のイメージを動画で見せてしまおう」というアイデアでした。</p>
<p>ちょうどその頃、社内では生成 AI 利用のガイドラインが整備され、各部門でもそれに沿った AI 活用が推進されていました。その流れも相まって、私自身も AI を取り入れた動画制作に挑戦することにしたのです。</p>
<p>ナレーションや BGM に AI を駆使し、構成や演出については自ら試行錯誤を重ねました。その結果、短期間でムービーを完成させることができました。</p>
<p>「AI による効率化を語るなら、まず自分が体験してみよう」</p>
<p>その思いから取り組んだ動画は、ブランドやプロダクトの未来像を端的に伝える欠かせない要素となりました。</p>
<video controls preload="" width="100%" poster="/entry/2025/09/26/movie.webp">
<source src="https://medley-cloud.com/brand-promise.mp4">
</video>
<div class="remark-link-card-plus__container">
<a href="https://medley-cloud.com/brand-movie" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">BRAND PROMISE MOVIE - MEDLEY AI CLOUD</div>
<div class="remark-link-card-plus__description">MEDLEY AI CLOUDは、AIを活用して医療機関の業務効率化を実現し、患者・生活者により良い体験を届ける、株式会社メドレーが提供する次世代医療プラットフォームです。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://medley-cloud.com/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">medley-cloud.com</span>
</div>
</div>
</a>
</div>
<h1 id="運用されつづけるデザインシステムをどう構築するか">運用されつづけるデザインシステムをどう構築するか</h1>
<p>Developer ブログらしく、同時並行して進めてるデザインシステム構築についても少し触れておきます。デザインシステムは「作ったら終わり」ではなく、継続的に運用される仕組みがなければ意味がありません。</p>
<h2 id="マルチ-cms-環境という課題">マルチ CMS 環境という課題</h2>
<p>各プロダクトの LP は、WordPress や外部の CMS、Headless CMS など、バラバラな環境で運用されています。そこで、同じ環境でデザインシステムを構築し、どのサイトからでも共通で利用できる状態を目指すことにしました。</p>
<p>この課題を解消するために社内のエンジニアともすり合わせをして、以下を実施することにしました。</p>
<p>・CMS の一本化:Headless CMS へ統一<br>
・モノレポ化:共通デザインシステムを集中管理<br>
・コンポーネント呼び出し:各プロダクトから共通の UI コンポーネントを利用可能に</p>
<p>この仕組みにより、UI やブランドトーンを一貫させつつ、各 LP への高速な展開ができると思っています。</p>
<p>現段階では、 MEDLEY AI CLOUD のみで実装されていますが、今後は各プロダクト LP へも順次展開していく予定です。この取り組みの詳細は、また別の機会に説明できればと思っております。</p>
<h1 id="これから">これから</h1>
<p>ロゴは無事に完成し、各プロダクト LP への反映も進んでいます(ロゴ制作についても別の機会に説明できればと思います)。<br>
けれども、ブランド構築プロジェクトはまだ始まったばかり。ロゴ刷新はあくまで出発点であり、これからはブランド体験の一貫性をさらに高め、医療現場や患者とのさまざまな接点にブランドを浸透させていくフェーズが本格化します。</p>
<p>私たちが提供する MEDLEY AI CLOUD の各プロダクトの使命は、「医療現場の業務効率化と、より良い患者体験を支援する」ことにあります。プロジェクトを進める中で、私自身も AI ツールの活用が推進力になることを強く実感しました。</p>
<p>全プロダクトで共通する 5 つのブランド・プロミス</p>
<ol>
<li>より良い患者体験</li>
<li>AI 活用</li>
<li>安心・安全</li>
<li>オープン連携</li>
<li>リーズナブル</li>
</ol>
<p>これらを軸に、病院・診療所・薬局・歯科といった領域をつなぎ、患者・生活者と直結する「ひとつながりの医療プラットフォーム」を実現していきます。中長期的には、社会インフラの一翼を担うプラットフォームとなることを目指しています。<br>
<img __ASTRO_IMAGE_="{"src":"image5.png","alt":"全プロダクトで共通する 5 つのブランド・プロミス","index":0}"></p>
<h1 id="さいごに">さいごに</h1>
<p>MEDLEY AI CLOUD ブランドの浸透や各プロダクトの開発を進めるうえで、これからもデザイナーの力は欠かせません。</p>
<p>このブランド構築プロジェクトは、私たち自身の想いや未来への取り組みを社内外に届けるため、外部に頼らず、社内メンバーだけでつくり上げてきました。</p>
<p>今後はブランド認知のさらなる拡大と事業成長を見据え、デザイナーをはじめ、多様な専門性を持つ仲間を必要としています。</p>
<p>少しでも興味を持っていただけたら、ぜひ <a href="https://medley-cloud.com/">MEDLEY AI CLOUD サイト内の採用一覧</a>をご覧ください。</p>
<p>私たちと共に、未来の医療体験を一緒につくっていきましょう。</p>
<div class="remark-link-card-plus__container">
<a href="https://medley-cloud.com/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">MEDLEY AI CLOUD</div>
<div class="remark-link-card-plus__description">MEDLEY AI CLOUDは、AIを活用して医療機関の業務効率化を実現し、患者・生活者により良い体験を届ける、株式会社メドレーが提供する次世代医療プラットフォームです。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://medley-cloud.com/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">medley-cloud.com</span>
</div>
</div>
</a>
</div>AI
- iOSDC Japan 2025 参加レポート - ゴールドスポンサーとして協賛しました!https://developer.medley.jp/entry/2025/09/26https://developer.medley.jp/entry/2025/09/26こんにちは!株式会社メドレーでDevRelを担当している重田です。
株式会社メドレーは、2025年9月19日(金)〜21日(日)に開催された iOSDC Japan 2025に、ゴールドスポンサーとして協賛しました。昨年同様、今年はセッショ...Fri, 26 Sep 2025 00:00:00 GMT<p>こんにちは!株式会社メドレーでDevRelを担当している重田です。</p>
<p>株式会社メドレーは、2025年9月19日(金)〜21日(日)に開催された <a href="https://iosdc.jp/2025">iOSDC Japan 2025</a>に、ゴールドスポンサーとして協賛しました。昨年同様、今年はセッション登壇とブース出展の両方を実施しました。記念すべきiOSDC Japan 10周年を、コミュニティの一員として迎えられたことを大変嬉しく思います!</p>
<p><img __ASTRO_IMAGE_="{"src":"monosnap.png","alt":"ゴールドスポンサー","index":0}"></p>
<p>メドレーでは複数のモバイルアプリを開発しており、コミュニティとのつながりを通じて開発力とプロダクト価値の向上を目指しています。その活動の一環として、弊社は2017年からiOSDC Japanに協賛しており、今年で9回目となりました。</p>
<p>本記事では、3日間にわたるイベントの様子と共に、メドレーの取り組み、そして印象に残ったセッションやブースについてレポートします。
2024年の参加レポートもぜひご覧ください → <a href="https://developer.medley.jp/entry/2024/09/10/205504/">https://developer.medley.jp/entry/2024/09/10/205504/</a></p>
<h1 id="会場の様子">会場の様子</h1>
<p>iOSDC Japan 2025は、有明セントラルタワー&カンファレンスにて開催されました。今年は3日間にわたり、のべ1,500名ものiOSエンジニアが有明に集結し、技術への熱気に満ち溢れた空間となりました。スポンサーブースエリアも連日多くの参加者で賑わい、カジュアルな雰囲気で技術交流を楽しむ姿がそこかしこで見受けられました。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0794.jpg","alt":"会場入口","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0734.jpg","alt":"ブースA全体","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0751.jpg","alt":"ブースA全体","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0750.jpg","alt":"ブースA全体","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0749.jpg","alt":"ブースA全体","index":0}"></p>
<p>常に、軽食とフリードリンクが用意されており、ブース運営にとって大変ありがたいサービスでした!</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0741.jpg","alt":"軽食","index":0}"></p>
<p>フリードリンク(お水)には開催年度のデザインが施され、手に取るのが楽しかったです。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0771.jpg","alt":"ドリンク","index":0}"></p>
<p>15時以降になると、アルコール類も提供があり驚きました。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0875.jpg","alt":"アルコール","index":0}"></p>
<h1 id="セッションの様子">セッションの様子</h1>
<p>メドレーからは、医療PF所属の吉田が登壇しました。
ご聴講いただいた皆さま、ありがとうございました!</p>
<div class="remark-link-card-plus__container">
<a href="https://speakerdeck.com/medley/zhi-merarenaiyi-liao-apuri-sotuto-swift-6-he-029d9777-db6e-4684-a7b0-096ec20f6955" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">止められない医療アプリ、そっと Swift 6 へ</div>
<div class="remark-link-card-plus__description">2025年9月21日(日)に開催のiOSDC Japan 2025の登壇資料です。
イベントURL:https://iosdc.jp/2025/
発表者情報:医療プラットフォーム本部 エンジニア 吉田将吾</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://d1eu30co0ohy4w.cloudfront.net/assets/favicon-bdd5839d46040a50edf189174e6f7aacc8abb3aaecd56a4711cf00d820883f47.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">speakerdeck.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://files.speakerdeck.com/presentations/40535c577dbe44888076180f3f2f4052/slide_0.jpg?36709990" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0833.jpg","alt":"吉田さん","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0838.jpg","alt":"吉田さん","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0839.jpg","alt":"吉田さん","index":0}"></p>
<h2 id="エンジニアに聞いた面白かったセッション">エンジニアに聞いた面白かったセッション</h2>
<p>弊社エンジニアが気になった・聴講して面白かったセッションをご紹介します!</p>
<p><strong>アセンブリで学ぶCPUアーキテクチャ by akkey</strong></p>
<div class="remark-link-card-plus__container">
<a href="https://speakerdeck.com/akkeylab/learn-cpu-architecture-with-assembly" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Learn CPU architecture with Assembly</div>
<div class="remark-link-card-plus__description">iOSDC Japan 2025
https://fortee.jp/iosdc-japan-2025/proposal/4316d7ee-b3a0-4623-997a-df8843a9a709</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://d1eu30co0ohy4w.cloudfront.net/assets/favicon-bdd5839d46040a50edf189174e6f7aacc8abb3aaecd56a4711cf00d820883f47.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">speakerdeck.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://files.speakerdeck.com/presentations/6220b9f1e63846039dad83abc0dfcba8/slide_0.jpg?36631020" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>私たちが日々用いている Swift などのプログラミング言語は、LLVM を経由してアセンブリに変換され、最終的に機械語として実行されます。アセンブリは普段あまり意識しない領域ですが、開発の基盤を支える重要な要素です。
本セッションはそのアセンブリに焦点を当て、アセンブリとは何か、具体的な読み方、スタックトレースを用いた実例まで、とっつきにくい内容を非常にわかりやすく解説していました。
日常の開発で直接触れる機会は多くないかもしれませんが、技術的な好奇心を強く刺激する発表でした。</p>
<p><strong>Swiftビルド弾丸ツアー - Swift Buildが作る新しいエコシステム by giginet</strong></p>
<div class="remark-link-card-plus__container">
<a href="https://speakerdeck.com/giginet/swift-build-bullet-tour-ja" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Swiftビルド弾丸ツアー - Swift Buildが作る新しいエコシステム</div>
<div class="remark-link-card-plus__description">https://fortee.jp/iosdc-japan-2025/proposal/c740120d-d52f-4100-986b-a24e8f0d3b87</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://d1eu30co0ohy4w.cloudfront.net/assets/favicon-bdd5839d46040a50edf189174e6f7aacc8abb3aaecd56a4711cf00d820883f47.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">speakerdeck.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://files.speakerdeck.com/presentations/89fc533243314e52a7bca2e17d6a4c23/slide_0.jpg?36652536" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>iOS アプリ開発では、普段あまり意識せずにビルドを行っていますが、今年の初めに Apple がビルドエンジンである「Swift Build」のオープンソース化を発表しました。
本セッションでは、既存のビルドシステムの説明から、Swift Build で何が変わるかを丁寧に説明していました。
CLINICS アプリでも Swift Package Manager を使ったマルチモジュール構成を採用しています。Xcode Project と Package.swift 双方がどのようにビルドされ、最終的にアプリとしてパッケージ化されるのか理解が深まり、非常に有意義な発表でした。</p>
<p><strong>プログラマのための作曲入門 by CHEEBOW</strong></p>
<div class="remark-link-card-plus__container">
<a href="https://speakerdeck.com/cheebow/puroguramanotamenozuo-qu-ru-men" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">プログラマのための作曲入門</div>
<div class="remark-link-card-plus__description">プログラマのための作曲入門
iOSDC Japan 2025
2025/09/21 13:55〜
Track A
音楽とプログラミングはまったく違うもの。芸術と情報工学の間に、共通点などない。
……と思われている方も多いようです。
しかしながら、それは大きな誤解なのです。
なぜ…</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://d1eu30co0ohy4w.cloudfront.net/assets/favicon-bdd5839d46040a50edf189174e6f7aacc8abb3aaecd56a4711cf00d820883f47.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">speakerdeck.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://files.speakerdeck.com/presentations/08151659df8f4630a6d3f72d7f47ce8c/slide_0.jpg?36663746" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>iOSDC というと、iOS に関するセッションだけだと思われがちですが、iOS から離れた技術領域のセッションも採択されることがあります。
本セッションは、実際にアーティストへ楽曲提供している iOS エンジニアの CHEEBOW さんによる、作曲に関するお話でした。
コード進行の実例を交え、作曲をわかりやすく解説しており、作曲未経験の人にも理解しやすい内容でした。作曲とソフトウェア開発は一見まったく異なる領域に思えますが、コード進行にも定番がいくつもあり、ソフトウェア開発でいう「デザインパターン」に相当するものだ、という表現が印象的でした。</p>
<p><strong>「Chatwork」アプリにおけるSVVS実装戦略</strong></p>
<div class="remark-link-card-plus__container">
<a href="https://www.docswell.com/s/terry/KWMNY4-2025-09-20-184238" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">「Chatwork」アプリにおけるSVVS実装戦略 | ドクセル</div>
<div class="remark-link-card-plus__description">iOSDC2025の登壇資料です。 ビジネスチャット「Chatwork」を提供する株式会社kubell(旧Chatwork株式会社...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://bcdn.docswell.com/assets/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.docswell.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://bcdn.docswell.com/page/VJPP3R3XJ8.jpg?width=480" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>SVVSというアーキテクチャでプロダクトを実装していく上で直面した課題とその課題を解決する方法が話されていました。セッションの前半で、ログインのライフサイクルを管理するためにLoginContextを導入した話は興味深く聞きました。ライフサイクル管理のための考え方として、他にも応用が効く考え方であると感じました。</p>
<h1 id="ブース出展の様子">ブース出展の様子</h1>
<p>約50社のブースが出展されており、連日とても賑わっていました。
本記事では、私たちのブースと特に印象に残ったブースをいくつかご紹介します!</p>
<h2 id="弊社ブースの様子">弊社ブースの様子</h2>
<p>私たちは医療×iOSに関する3択3問クイズにチャレンジいただき、正解数に応じてノベルティを配布していました!
日替わりで問題を変更し、全日楽しんでいただけるよう工夫しました。
👇実際に出題した問題を一部ご紹介。最後には解説付きで正誤を確認!</p>
<p><img __ASTRO_IMAGE_="{"src":"Monosnap08.png","alt":"クイズ","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"Monosnap07.png","alt":"クイズ","index":0}"></p>
<p>全問正解の方には、今回初めて登場したノベルティ「ポータブル防災7点セット」をプレゼント!
いざという時に役立つグッズをご用意しました🏃♀️</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0765.jpg","alt":"メドレーブース","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0780.jpg","alt":"メドレーブース","index":0}"></p>
<p>お越しいただいた皆様、ありがとうございました🙌✨
<img __ASTRO_IMAGE_="{"src":"IMG_0774.jpg","alt":"メドレーブース","index":0}"></p>
<h2 id="その他のブースの様子">その他のブースの様子</h2>
<h3 id="kintoテクノロジーズ株式会社">KINTOテクノロジーズ株式会社</h3>
<p>AI広報るぴあちゃんとじゃんけんで勝つとトミカがもらえる楽しいブースでした🚙
トミカ欲しさに何度かチャレンジしたものの、るぴあちゃんが強くてゲットならず・・・(写真に写っている弊社エンジニアは勝ってトミカをゲットしていました👏)
<img __ASTRO_IMAGE_="{"src":"IMG_0789.jpg","alt":"KINTOブース","index":0}"></p>
<h3 id="本田技研工業株式会社">本田技研工業株式会社</h3>
<p>最新の車載システムとiOSの連携について、デモを交えながら詳しく説明されていました。普段触れることのない分野の技術に触れられ、とても興味深かったです。
(なんといってもバイクがカッコよかったです!!!)
<img __ASTRO_IMAGE_="{"src":"IMG_0799.jpg","alt":"HONDAブース","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0800.jpg","alt":"HONDAブース","index":0}"></p>
<h3 id="株式会社luup">株式会社Luup</h3>
<p>新しいモビリティサービスをiOSエンジニアの視点から解説されており、地図や位置情報と密接に関わる開発の面白さを感じることができました。
タップ数をクリアしたらロックが外れトートバッグをゲットできる仕掛けになっていました。</p>
<p><img __ASTRO_IMAGE_="{"src":"IMG_0795.jpg","alt":"LUUPブース","index":0}"></p>
<h1 id="まとめ">まとめ</h1>
<p>DevRelとしてメドレーの事業やプロダクトをコミュニティに発信することを目的として参加した今回のiOSDC。スポンサーセッションやブース出展を通して、多くのiOSエンジニアの皆さんと直接お話しする機会をいただき、非常に有意義な3日間となりました。
ブースに立ち寄ってくださった皆様、ありがとうございました!
今後もメドレーは技術コミュニティへの貢献、そして弊社をより多くの方々に知っていただくためにイベントへの協賛を継続していきます。
またカンファレンスでお会いしましょう!</p>
<h1 id="were-hiring">We’re hiring!</h1>
<p>メドレーではiOSエンジニアをはじめ、
「医療ヘルスケアの未来をつくる」というミッションに共感し、ともに切磋琢磨する仲間を大募集中です。</p>
<p>少しでもご興味のある方はまずはカジュアル面談でお話しましょう!</p>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">株式会社メドレー </div>
<div class="remark-link-card-plus__description">株式会社メドレー です。| HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:FFFFFF,c_mpad,h_200,w_200/m/2468/images/2148742169805250560_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>※カジュアル面談をご希望の際は、「その他の項目(希望記入欄)」にてその旨をご記載ください。</p>
- ライブラリ更新リスクを分離する週2回リリース戦略 〜開発とQAの実践〜https://developer.medley.jp/entry/2025/09/25/143628https://developer.medley.jp/entry/2025/09/25/143628はじめに
この記事は メドレー夏のブログリレー 2025 22 日目の記事です。
メドレー医科診療所プロダクト開発室 AI推進グループの桶谷です。
AI推進グループでは、「AIで “医師” と “エンジニア” の生産性を最大化する」というミ...Thu, 25 Sep 2025 05:36:28 GMT<h1 id="はじめに">はじめに</h1>
<p><strong>この記事は <a href="https://developer.medley.jp/entry/2025/08/15/20250815/">メドレー夏のブログリレー 2025</a> 22 日目の記事です。</strong></p>
<p>メドレー医科診療所プロダクト開発室 AI推進グループの桶谷です。</p>
<p>AI推進グループでは、「AIで “医師” と “エンジニア” の生産性を最大化する」というミッションを掲げ、医師の皆様に向けたAIソリューション(「ゼロ距離医療」の実現)と、社内エンジニアの生産性向上(「CLINICS 10x」)の両輪で様々な施策に取り組んでいます。取り組みの全体像は<a href="https://note.com/medley/n/n55214b43dafc">こちらの記事</a>で詳しく紹介しています。興味のある方はぜひお読みください。</p>
<p>その中でも「CLINICS 10x」では、従来の10倍の速度で機能リリースを実現するための様々な取り組みを進めています。
10倍のリリーススピードを実現するには、単に開発を速めるだけでなく、リリースの品質担保、特にQAの効率化とバグの早期検知が極めて重要になります。
CLINICSは多くの医療機関で利用される基幹システムであり、不具合発生時の影響をいかに局所化し、迅速に修正できる体制を築くかが大きな課題でした。</p>
<p>そこで、10xリリースの土台作りとして、まず「ライブラリ更新」のリリース戦略見直しに着手しました。
今回はその取り組みの中から、CLINICSを開発しているチームで導入した、ライブラリ更新を分離する新しいブランチ戦略についてご紹介します。</p>
<p>※ ライブラリ更新:本記事ではMinor、Patchレベルの更新を指します</p>
<h4 id="想定読者">想定読者</h4>
<ul>
<li>リリースプロセスに課題を感じている人</li>
<li>開発チームと連携して品質保証の仕組みを改善したい人</li>
</ul>
<h1 id="なぜリリース戦略を見直したのか">なぜリリース戦略を見直したのか</h1>
<p>CLINICS は多くの医療機関で利用されている診療所向けクラウド型電子カルテシステムです。
医療機関向け SaaS として提供される一方で、電子カルテ、予約管理、会計、オンライン診療など診療所の基幹業務を一手に担う「基幹システム」として機能しています。日々の診療に直結する業務を支えるため非常に高い安定性と信頼性が求められます。サービスの性質上、ひとつの変更が「受付 → 診療 → 会計」といった一連の業務フロー全体に波及する可能性があるため、品質保証の重要性は非常に高いです。</p>
<p>その品質保証の範囲も単なる UI 動作の確認にとどまらず、業務フローの整合性、データ整合性、権限や監査、請求(算定)ロジック、可用性といった多岐にわたる領域に広がります。しかし、いくらテストを厚くしても、あらゆる組み合わせを事前に網羅するのは現実的に困難であり、検証漏れのリスクを完全に排除することはできません。そのため、リリース設計においては差分を小さく保ち、影響範囲を限定し、原因を切り分けやすくする工夫が不可欠となります。</p>
<p>さらに技術的な背景として、CLINICS は Rails モノリスに複数の SPA を内包し、バックエンド、フロントエンド、一部インフラを単一リポジトリ(モノレポ)で集約的に管理・運用しています。1つのリポジトリに Rails、React、Next.js、Node.js といった技術スタックが同居しており、依存関係は論理的に分離されているものの、ライブラリ更新の頻度は高く、変更の影響範囲も広大になりがちでした。</p>
<p>こうした背景と課題から、まずは機能とライブラリのリリースを分けることで、変更差分と影響を最小化しようと取り組みました。</p>
<h2 id="従来のリリースプロセス">従来のリリースプロセス</h2>
<p>CLINICSでは、<code>master(本番環境相当)/ develop(開発統合)/ release-candidate(次回リリース候補のコードフリーズ)</code> の3ブランチ構成を基本に運用しています。従来は「機能追加」と「ライブラリ更新」を毎週火曜日の定常リリースにまとめて実施していました。具体的には、各 feature ブランチや deps(ライブラリ更新)ブランチを develop に順次マージし、木曜日に develop から release-candidate を作成してコードフリーズ。このrelease-candidate 上で QA を実施し、翌週火曜日に master へまとめてリリースするというフローです。</p>
<p><img __ASTRO_IMAGE_="{"src":"asis-release-flow.png","alt":"AS-ISリリースフロー図","index":0}"></p>
<p>図を見て分かる通り、Git Flow をベースに開発、リリースを実施しており、全ての変更が毎週火曜日の定常リリースに集約されていました。
この方法ではプロダクトの成長とともに、以下の課題が浮き彫りになりました。</p>
<ul>
<li><strong>不具合発生時の原因切り分けが難しい</strong>:不具合が生じても、それが新機能によるものかライブラリ更新によるものか特定に時間がかかる</li>
<li><strong>品質担保コストの増大</strong>:ライブラリ更新は予期せぬ副作用を生むことがあり、機能追加と同時に行うことで QA の範囲が広がり、テストや検証に必要なコストが増大していた</li>
</ul>
<p>リリース時の差分が大きいと、一見些細なライブラリ更新であっても、医療現場の業務フローに予期せぬ不具合を引き起こし、診療や会計といった基幹業務に影響を及ぼすリスクがあります。実際にも、ライブラリ更新と多様なデータ状況など複数の要因が重なれば、一部の医療機関で「会計処理が停止する」といった業務に直結する問題が発生する恐れがありました。こうした事象は、医師やスタッフの負担を増やすだけでなく、患者体験にも直結してしまいます。</p>
<p>さらに昨今では、生成AIを活用した開発の加速によって、リリースごとに扱う差分はますます膨らみ、従来のリリースプロセスのままでは品質とスピードの両立が難しくなってきました。そこで、「CLINICS 10x」の思想に立ち返り、品質を確保しながらもスピードを失わないために、リリース戦略そのものを見直す必要があると判断しました。</p>
<h1 id="新しいリリース戦略で実現したいこと">新しいリリース戦略で実現したいこと</h1>
<p>今回の取り組みで目指したのは、単なるリリース手法の変更ではなく、チーム全体が安心して開発・検証・リリースに取り組める体制を作ることです。そのために、次の3つを軸としました。</p>
<ol>
<li><strong>ライブラリ更新の安定化</strong>
<ul>
<li>不具合発生時に「ライブラリ起因」と即座に判断して対応できるため、機能変更と明確に切り分けてリスク管理が可能となる</li>
<li>不具合発生時にライブラリ更新だけを即時に戻せるため、初動対応を安全に行える</li>
</ul>
</li>
<li><strong>機能リリースの明確化</strong>
<ul>
<li>QAは機能検証に集中でき、テストの粒度と品質が向上する</li>
<li>リリースノートも「メンテナンス」と「アップデート」に分け、ユーザーに分かりやすく伝えられる</li>
</ul>
</li>
<li><strong>品質とスピードを兼ね備えた体制</strong>
<ul>
<li>小さな差分で安全かつ素早くリリースできる体制を確立</li>
<li>領域ごとの責任分担と確認項目を明確化し、安心して高速リリースを実行できる運用体制を整備</li>
</ul>
</li>
</ol>
<p>この取り組みには技術面だけでなく、事業部やQAチームとの合意形成も不可欠です。
特に、カスタマーサクセス(CS)/サポートデスクは変更点の把握が不可欠である一方で、ライブラリ更新といった技術的変更が十分に共有されないままリリース差分が大きくなることに不安を抱えていました。
そこで、週2回のリリース頻度増は「差分を小さく保ち、リスクを分離・局所化すること」を狙いとしていると説明し、リリースノートを「メンテナンス(ライブラリ)/アップデート(機能)」に分けて整理しました。
結果として、関係部署からも「そのほうが安全で把握しやすい」と理解が得られ、スムーズに合意形成へとつなげることができました。</p>
<p>一方で QA チームも、従来はライブラリ更新と機能追加を同時に検証していたため、不具合が発生すると原因切り分けに苦労していました。こうした課題感もあり、「更新と機能を分けた方が効率的に品質を担保できる」という点で意見が一致しました。加えて、開発と QA が一体となって品質を高める文化がすでに定着していたことも後押しし、新しいリリース戦略をスムーズに取り入れることができました。</p>
<h1 id="ライブラリリリースと機能リリースを分ける際のブランチ戦略">ライブラリリリースと機能リリースを分ける際のブランチ戦略</h1>
<p>今回、新たに「ライブラリ更新専用のブランチ」を追加することで、ライブラリ更新を単体でリリースできるようになり、機能リリースと役割を分担した週2回の安定したリリース体制を実現しました。</p>
<table><thead><tr><th>曜日</th><th>リリース成果物</th><th>ブランチ</th></tr></thead><tbody><tr><td>月曜日</td><td>ライブラリ更新</td><td>release-candidate-lib</td></tr><tr><td>火曜日</td><td>機能追加</td><td>release-candidate</td></tr></tbody></table>
<p>この仕組みにより、ライブラリ更新と新機能追加という性質の異なる変更が互いに干渉せず、それぞれに適したテストと品質保証を行える体制を確立することができました。</p>
<h2 id="as-is--to-be-の比較">AS-IS / TO-BE の比較</h2>
<h3 id="as-is従来のリリースフロー">AS-IS(従来のリリースフロー)</h3>
<p><img __ASTRO_IMAGE_="{"src":"asis-release-flow.png","alt":"AS-ISリリースフロー図","index":1}"></p>
<h3 id="to-be新しいリリースフロー">TO-BE(新しいリリースフロー)</h3>
<p><img __ASTRO_IMAGE_="{"src":"tobe-release-flow.png","alt":"TO-BEリリースフロー図","index":0}"></p>
<p>AS-IS では、機能追加(feature)と依存更新(deps)が develop に同居し、木曜日作成の release-candidate 上でまとめて QA を実施し、翌週火曜日に単一リリースしていました。TO-BE ではフローを2本に分離しました。ライブラリ更新は release-candidate-lib-next に集約し、同時に develop へ Reverse Merge することで、日常開発の中で早期に動作検証を行います。安定化した内容は月曜日に release-candidate-lib にて、ライブラリのみのリリースを実施します。機能追加については従来通り release-candidate を経由し、火曜日に本番リリースを行う形にしました。</p>
<h2 id="新しいブランチ戦略におけるキーポイントとメリットデメリット">新しいブランチ戦略におけるキーポイントとメリット・デメリット</h2>
<p>今回の戦略で最大のポイントは、release-candidate-lib-next ブランチの新設です。</p>
<p><img __ASTRO_IMAGE_="{"src":"release-candidate-lib-next.png","alt":"release-candidate-lib-nextブランチ","index":0}"></p>
<p>Renovate/Dependabot が作成するライブラリ更新 PR を release-candidate-lib-next に集約することで、翌々週にリリースされるライブラリ更新の成果物を対象に、長期回帰テストを継続的に実施できるようになりました。コードフリーズブランチを「ライブラリ用」と「機能用」に分離したこともポイントです。ライブラリ更新は回帰確認中心、機能追加は業務フローやユーザー体験検証中心といった形で、QA のスコープを用途ごとに最適化できるようになりました。加えて、ライブラリ更新を単独でリリースできるため、不具合発生時には原因の切り分けが容易になり、ライブラリのみを安全に Revert する対応も可能です。</p>
<p>こうした仕組みによって、品質保証の効率化と精度向上を実現しつつ、不具合対応の即応性も高めることができました。一方で、運用を進める中でいくつかの課題やトレードオフも明らかになってきました。</p>
<ul>
<li><strong>Reverse Merge の増加</strong>:release-candidate-lib-next → develop への取り込み頻度が上がり、特にコンフリクト発生時の解消コストが高い</li>
<li><strong>ブランチ運用の複雑化</strong>:release-candidate-lib-next / release-candidate-lib の追加によって、運用コストや学習コストが増大</li>
<li><strong>CI/テスト負荷の増加</strong>:候補ブランチごとに E2E が走るため、ジョブ数が増えて実行環境への負荷が高い(省力化や高速化に向けた改善は別途実施中)</li>
</ul>
<p>これらの課題は認識しつつも、チーム合意のもと「医療に直結するプロダクトとして品質を優先する」という判断を取りました。
その結果、リスクの局所化と原因特定の迅速化を両立し、週2回の安全なリリースを継続できる体制を確立できています。</p>
<h1 id="qa-チームによる品質担保の仕組み">QA チームによる品質担保の仕組み</h1>
<p>ここからは、QA エンジニアの小島 (<a href="https://x.com/daishu1130">@Daishu</a>) が、今回の改善における E2E テストを用いた品質担保の設計について紹介します。</p>
<h2 id="e2e-テストの実施体制">E2E テストの実施体制</h2>
<p>CLINICS では、QA チームが MagicPod を使って E2E テストを管理しています。</p>
<div style="max-width: 550px; margin: 0 auto; border: 1px solid #e1e4e8; padding: 3px; border-radius: 6px;">
<p><img __ASTRO_IMAGE_="{"src":"slide.png","alt":"E2Eテストの役割","index":0}"></p>
</div>
<div style="text-align: center; margin-top: 5px;">
<small>出典: <a href="https://speakerdeck.com/medley/deep-dive-into-react-component-design-for-medical-systems?slide=60">徹底解剖! 医療業務システムのReactコンポーネント設計</a>を基に作成</small>
</div>
<br>
<p>各ブランチでは、以下の頻度と目的で E2E テストを実行しています:</p>
<p><strong>1. 開発統合 (develop) ブランチ</strong><br>
デイリーで E2E テストを実行し、デグレを検出します。テスト失敗時は原因を分析し、仕様変更による失敗の場合は MagicPod のテストシナリオを更新して対応しています。</p>
<p><strong>2. リリース候補 (release-candidate) ブランチ</strong><br>
ウィークリーで E2E テストと手動テストの両方をフルスイートで実行してリリース可否を判定します。</p>
<h2 id="新しいリリースプロセスへの対応">新しいリリースプロセスへの対応</h2>
<p>新体制への移行にあたり、QA チームの懸念や要望を開発チームに共有し、一緒に品質担保の仕組みを設計しました。</p>
<h3 id="-deps-マージタイミングの最適化">① deps マージタイミングの最適化</h3>
<p><code>deps</code> をマージするタイミングを変更したことで、問題の切り分けが改善されました。</p>
<table><thead><tr><th></th><th>deps マージタイミング</th><th>特徴</th></tr></thead><tbody><tr><td><strong>旧</strong></td><td>release-candidate ブランチカット前</td><td>• ライブラリ更新が翌週リリースとなる<br>• 不具合検出の猶予がなく、問題切り分けに時間がかかる</td></tr><tr><td><strong>新</strong></td><td>release-candidate ブランチカット後</td><td>• ライブラリ更新のリリースが翌々週となる<br>• 開発環境での検証期間を確保でき、問題の切り分けが容易になる</td></tr></tbody></table>
<p>この変更により、<code>deps</code> 起因の問題を効率的に検出・対応できるようになりました。</p>
<h3 id="-release-candidate-lib-ブランチでの-e2e-テスト追加">② release-candidate-lib ブランチでの E2E テスト追加</h3>
<p>前述の <code>release-candidate-lib</code> ブランチに対する E2E テストを追加しました。これにより、ライブラリ更新の品質を単独で検証できるようになりました。</p>
<p><img __ASTRO_IMAGE_="{"src":"e2e-test-flow.png","alt":"リリースプロセスと E2E テスト","index":0}"></p>
<h3 id="-magicpod-ブランチ機能による-ui-バージョン管理">③ MagicPod ブランチ機能による UI バージョン管理</h3>
<p><code>release-candidate-lib</code> は前回リリース済みのコードをベースにしているのに対し、E2E テストは日々更新される最新の <code>develop</code> に合わせてメンテナンスされているため、そのままでは UI の差分でテストが失敗します。この問題は、<a href="https://support.magic-pod.com/hc/ja/articles/40981471377817">MagicPod ブランチ機能</a>で解消しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span># 運用フロー</span></span>
<span class="line"><span> 1. 木曜日:E2E テスト完了時点で MagicPod branch を作成</span></span>
<span class="line"><span> └─ ブランチ名: release-candidate-lib/YYYY-MM-DD</span></span>
<span class="line"><span> 2. 翌週木曜日:前週に作成した branch を指定して、E2E テスト実行</span></span>
<span class="line"><span> 3. テスト完了後:branch を削除して、次のサイクルに備える</span></span></code></pre>
<p>MagicPod の定期実行機能では branch を指定できない仕様のため、GitHub Actions から API 経由で branch を指定して週次で自動実行しています。</p>
<div style="max-width: 500px; margin: 0 auto;">
<p><img __ASTRO_IMAGE_="{"src":"workflow.png","alt":"GitHub Actions のワークフロー","index":0}"></p>
</div>
<p>以上、QA チームの視点から、新しいリリース戦略を支える品質担保の仕組みについて紹介しました。開発チームと共に設計したこの体制により、週 2 回のリリースでも効率的な検証と高い品質維持を実現しています。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回リリース戦略の見直しにより、CLINICS 開発チームでは「品質」と「スピード」の両立に向けて土台を築くことができました。
不具合発生時の原因切り分けは迅速になり、QA プロセスも効率化されています。
今後はさらに、フロントエンドとバックエンドを個別にリリースできる体制や、日中に安心してリリースできる仕組みの整備にも取り組んでいく予定です。
これらの改善を通じて、より柔軟に、そして迅速に、ユーザーにとって価値ある改善を届けられる開発体制を目指していきます。</p>
<h1 id="we-are-hiring">We Are Hiring!</h1>
<p>最後まで読んでいただきありがとうございます!</p>
<p>メドレーでは、開発プロセスの改善や自動化に情熱を注げるエンジニアを募集しています。
もし私たちの取り組みに少しでも興味を持っていただけましたら、ぜひカジュアルにお話ししましょう!</p>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs/1000003" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">テックリード候補 バックエンド/CLINICS | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">テックリード候補 バックエンド/CLINICS(株式会社メドレー)の求人情報です。 | HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:333333,c_pad,w_978,h_513/m/577c/images/2118206564349132800_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>「<a href="https://developer.medley.jp/entry/2025/08/15/20250815/">Medley Summer Tech Blog Relay</a>」 23 日目は、医療プラットフォーム本部の前田さんの記事です!</p>
- 痒い所に手が届く!Step Functions によるトイル削減https://developer.medley.jp/entry/2025/09/24/110824https://developer.medley.jp/entry/2025/09/24/110824はじめに
この記事は メドレー夏のブログリレー 2025 21 日目の記事です。
医療プラットフォーム本部 プラットフォーム開発室 SRE グループの山田です。
医療機関向け SaaS である CLINICS の安定稼働とシステム信頼性の向...Wed, 24 Sep 2025 02:08:24 GMT<h1 id="はじめに">はじめに</h1>
<p><strong>この記事は <a href="https://developer.medley.jp/entry/2025/08/15/20250815/">メドレー夏のブログリレー 2025</a> 21 日目の記事です。</strong></p>
<p>医療プラットフォーム本部 プラットフォーム開発室 SRE グループの山田です。
医療機関向け SaaS である <a href="https://clinics-cloud.com/">CLINICS</a> の安定稼働とシステム信頼性の向上に取り組んでいます。</p>
<p>SREグループは開発組織全体の中でも比較的少人数のチームです。
そのため、腰を据えて事業やプロダクトに中長期的な価値を生む時間を作るためにも、トイルの削減に力を入れています。</p>
<p>繰り返し作業や自動化が可能なタスクを効率化する上で、AWS Step Functionsは非常に強力なサービスです。</p>
<p>本記事では、AWS <a href="https://aws.amazon.com/jp/step-functions/">Step Functions</a> を活用し、1000を超えるデータベースのマイグレーション作業を自動化した事例を題材に、Step Functionsの <strong>実践的な活用方法(tips)</strong> についてご紹介します。</p>
<h1 id="メンテナンス作業のおおまかなワークフロー">メンテナンス作業のおおまかなワークフロー</h1>
<ol>
<li>DBのバックアップ(スナップショットを作成)</li>
<li>DBマイグレーションを実施</li>
<li>マイグレ後のDBスキーマチェック</li>
<li>ECS Task(アプリケーション)のデプロイ</li>
<li>完了/失敗通知を飛ばす</li>
</ol>
<p>更新手順としてはごく一般的なワークフローかと思います。
しかし、手順2で大量のデータベースをどのようにして夜間で更新しきるのかが課題となりました。</p>
<p>プロダクトの成長とともにデータベースの数が増えていくという特性上、なるべく変更管理の手間がかからない仕組みを導入しなくてはなりません。</p>
<h1 id="step-functions-を用いたアプローチ">Step Functions を用いたアプローチ</h1>
<p>SREチームでは上述した課題に対して Step Functions によるメンテナンスを自動化することにしました。</p>
<h4 id="aws-step-functions-とは">AWS Step Functions とは?</h4>
<p>ここでは、詳細な説明は割愛しますが、AWS Step Functions は複数のAWSリソースやサービスを統合してワークフローを構築するためのサービスです。
lambda で実装するには処理が複雑だったり処理時間が15分以上かかる場合、Step Functionsが選択肢となります。
(AWS Step Functions は最大1年実行が可能)</p>
<h2 id="1-map-ステートを活用する">1. Map ステートを活用する</h2>
<p><img __ASTRO_IMAGE_="{"src":"map_state_workflow.png","alt":"Mapステートを用いた実装例","index":0}"></p>
<p>Map ステートは同一ワークフローに異なる入力を与えてタスクを並列実行する(動的並列処理)機能です。
Map ステートに与える入力を増やす/減らすことでワークフローを変更することなく並列実行するタスクをスケールさせることができます。</p>
<p>これにより、大量のDBマイグレーションを同時に実行しメンテナンス時間の短縮をしただけでなく、変更に強いワークフローを構築することができました。</p>
<h4 id="ワークフローの処理内容">ワークフローの処理内容</h4>
<ol>
<li>各ブランチのマイグレーションタスクがどのDBのマイグレーションを実施するのかリストを作成(画像では3並列なので1 Taskあたり約333DBのマイグレーションを実行するように振り分け)</li>
<li>複数のECS Taskを立ち上げてマイグレーションを実施</li>
<li>DBスキーマをチェックして問題ないか確認</li>
</ol>
<p>※各ステートごとにlambdaを用意すると管理しづらくマイグレーション処理も15分以上かかるため、メンテナンス作業用のコンテナイメージを別途、用意しています。</p>
<h2 id="2-sync-を用いてdbマイグレーション完了まで待機させる">2. <code>.sync</code> を用いてDBマイグレーション完了まで待機させる</h2>
<p>Step Functions では通常サービスを呼び出すと次のステートへ遷移しますが、<code>.sync</code>を用いることでジョブが完了するまで状態遷移を待機させることができます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>"更新対象のDBリストを作成": {</span></span>
<span class="line"><span> "Type": "Task",</span></span>
<span class="line"><span> "Next": "DBマイグレーションを実行",</span></span>
<span class="line"><span> "Resource": "arn:aws:states:::ecs:runTask.sync",</span></span>
<span class="line"><span> "Arguments": { /* 省略 */ },</span></span>
<span class="line"><span> "Catch": [</span></span>
<span class="line"><span> {</span></span>
<span class="line"><span> "ErrorEquals": [</span></span>
<span class="line"><span> "States.ALL"</span></span>
<span class="line"><span> ],</span></span>
<span class="line"><span> "Next": "更新処理の失敗を通知"</span></span>
<span class="line"><span> }</span></span>
<span class="line"><span> ]</span></span>
<span class="line"><span>},</span></span></code></pre>
<p>上記のように記述することで、更新対象のデータベースリストを作成するECS Taskが終了しないと、次のステート「DBマイグレーションを実行」へ遷移しないようになります。</p>
<p>ジョブ完了状態のポーリングは <code>DescribeTasks</code>, <code>ChoiceState</code>, <code>WaitState</code> の組み合わせでも可能ですが、Step Functions は状態遷移回数によってサービス利用料が計算されるため、コスト的に優しくありません。</p>
<p>ただし、すべてのAWSサービスで<code>.sync</code>が利用できるわけではないので注意が必要です。</p>
<p>参考:<a href="https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/integrate-optimized.html">最適化されたサービスと Step Functions の統合</a></p>
<h1 id="実装時に意識したこと">実装時に意識したこと</h1>
<h2 id="1-ビジュアルエディタを活用する">1. ビジュアルエディタを活用する</h2>
<p>AWS Step Functions のワークフローはJSON形式で定義する宣言型の <strong>Amazon States Language (ASL)</strong> で記述します。</p>
<p>シンプルなワークフローであればASLで書かれたワークフローを読むのもそこまで苦ではありませんが、分岐が複数あったり複雑なワークフローだと辛くなってきます。
そこで、役立つのがビジュアルエディタです。</p>
<p>Step Functions のビジュアルエディタにはデザインモードとコードモードが存在します。
デザインモードを活用することで、ワークフローの構造をビジュアルで確認することができます。</p>
<p><strong><a href="https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/workflow-studio.html#wfs-interface-design-mode">デザインモード</a></strong></p>
<p><img __ASTRO_IMAGE_="{"src":"design_mode.png","alt":"デザインモードの画面","index":0}"></p>
<ul>
<li>ドラッグ&ドロップでアクションやフローを配置してワークフローの構築ができます。</li>
<li>コードを書く必要がなく直感的に操作することができます。</li>
</ul>
<p><strong><a href="https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/workflow-studio.html#wfs-interface-code-mode">コードモード</a></strong></p>
<p><img __ASTRO_IMAGE_="{"src":"code_mode.png","alt":"コードモードの画面","index":0}"></p>
<ul>
<li>ASLでワークフローを編集することができます。</li>
<li>他のStep Functionsワークフローをコピペして組み込んだりする時には便利です。</li>
<li>ASLの構文エラーはエディタから確認することができるので、間違いに気がつくことはできます。</li>
</ul>
<h2 id="2-teststate-api-を使い倒す">2. TestState API を使い倒す</h2>
<p><img __ASTRO_IMAGE_="{"src":"test_state_api.png","alt":"TestStateAPI画面","index":0}"></p>
<p>ワークフローを構成する各ステートは<a href="https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/test-state-isolation.html">TestState API</a>で単体テストが可能です。
特に参照系APIの戻り値をテストしたりJSONataのような書き慣れないクエリ構文のチェックに利用します。
ただし、処理完了するまで待機する <code>.sync</code> やコールバックが返却されるまでStep Functionsが待機する <code>.waitForTaskToken</code> といったステートはTestState APIをサポートしていないため、その場合は<code>.sync</code> や <code>.waitForTaskToken</code> を外して検証します。</p>
<h1 id="まとめ">まとめ</h1>
<p>本記事では、Step Functions を活用した運用トイル削減の取り組みとそこで得たTipsについて紹介しました。
少人数のSREチームにおいて、手作業による運用コストの削減は重要な課題です。</p>
<p>Step Functions を使用することで、以下の成果を得ることができました:</p>
<ul>
<li><strong>定量的効果</strong>: 月6時間、年間72時間の工数削減</li>
<li><strong>定性的効果</strong>: ヒューマンエラーの排除と心理的負荷の軽減</li>
<li><strong>組織的効果</strong>: より価値の高い業務への時間投入が可能</li>
</ul>
<p>特に、視覚的なワークフロー管理機能により、チーム全体での理解と保守性が向上したことは、持続可能な運用自動化において重要な要素でした。</p>
<p>今後も、運用業務の中で反復的に実施している作業を見つけ出し、Step Functions をはじめとしたAWSサービスを活用した自動化に取り組んでいきます。
これにより、SREとしてのCLINICSの安定性と信頼性をさらに向上させていきたいと考えています。</p>
<p>次回は、医療プラットフォーム本部 桶谷さん・小島さんの、「生成AI時代に向けて、開発効率10xを支えるリリース戦略の見直し」です。お楽しみに。</p>
<h1 id="メドレーではエンジニアを積極採用中です">メドレーではエンジニアを積極採用中です!</h1>
<p>メドレーでは、SREをはじめ「医療ヘルスケアの未来」を共に創っていくエンジニアを大募集中です!少しでもご興味をお持ちいただけましたらぜひ、ご応募お待ちしております!</p>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs?category=2144686643002470401" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">株式会社メドレー エンジニア の求人一覧</div>
<div class="remark-link-card-plus__description">株式会社メドレー エンジニア の求人一覧です。| HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:FFFFFF,c_mpad,h_200,w_200/m/2468/images/2148742169805250560_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>※カジュアル面談も大歓迎です!ご希望の際は、「その他の項目(希望記入欄)」にてその旨をご記載ください。</p>
- DroidKaigi 2025 参加レポート - Supporter Sponsor として協賛しました!https://developer.medley.jp/entry/2025/09/18/200000https://developer.medley.jp/entry/2025/09/18/200000
こんにちは、人材プラットフォーム本部CTO室の奥澤です。2025年9月、株式会社メドレーに入社して人材プラットフォーム本部へ配属されました。メドレーの人材プラットフォーム本部では既に複数のモバイルアプリを開発していますが、モバイルアプリ開...Thu, 18 Sep 2025 11:00:00 GMT<p><img __ASTRO_IMAGE_="{"src":"thumbnail.png","alt":"DroidKaigi 2025 Supporter Sponsorとして協賛しました","index":0}"></p>
<p>こんにちは、人材プラットフォーム本部CTO室の奥澤です。2025年9月、株式会社メドレーに入社して人材プラットフォーム本部へ配属されました。メドレーの人材プラットフォーム本部では既に複数のモバイルアプリを開発していますが、モバイルアプリ開発をさらに加速させていくためにジョインしました。</p>
<p>さて、9月に入って8月ほどの暑さはなくなり、夏の終わりを感じています。そして例年、自分にとって暑い夏の終わりと秋の始まりを告げるイベントである、DroidKaigiに今年も参加してきました!DroidKaigiは、日本国内で最大級のAndroidに関するカンファレンスです。</p>
<p>株式会社メドレーは、2025年9月10日から12日にかけて開催された、DroidKaigi 2025にSupporters Sponsorとして協賛しました。メドレーでは2021年からDroidKaigiに協賛しており、今年で5回目の協賛となっています。メドレーでは、DroidKaigiなどの技術カンファレンスへ継続的に協賛し、技術コミュニティを支援しています。</p>
<p>本記事では、DroidKaigi 2025の会場の様子と共に、印象に残ったセッションを紹介します。</p>
<h1 id="会場の様子">会場の様子</h1>
<p>DroidKaigi 2025は、ベルサール渋谷ガーデンで開催されました。DroidKaigi 2023から同じ会場で開催されています。今年も元気に会場にやってきました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./DroidKaigi-2025-1.png","alt":"DroidKaigi 2025@ベルサール渋谷ガーデン","index":0}"></p>
<p>地下1階に設けられたコミュニケーションエリアには、協賛している各企業のブースが出展されていました。ブースを周り、各企業の話を聞いて歩くのはカンファレンスの大きな楽しみですね。それぞれの会社 / アプリの取り組みについて学び、さらに中の人に詳しい話を聞くことができるのは良いですね。</p>
<p><img __ASTRO_IMAGE_="{"src":"./DroidKaigi-2025-2.png","alt":"コミュニケーションエリアの様子","index":0}"></p>
<p>今年も気合いを入れてスタンプラリーをコンプリートしました。大量にノベルティもいただいたので、家族へのお土産になりました。全部のノベルティが嬉しいものですが、特に印象に残ったノベルティは、株式会社TVer様のタンブラー(かっこいい)、KINTOテクノロジーズ株式会社様のコースター(かわいい)、株式会社メルカリ様のキーキャップ(さっそく使ってます)でした。</p>
<p><img __ASTRO_IMAGE_="{"src":"./DroidKaigi-2025-3.png","alt":"スタンプラリーをコンプリートしました","index":0}"></p>
<h1 id="セッションの様子">セッションの様子</h1>
<p>私がリアルタイムで聴講したセッションの中で、印象に残ったいくつかのセッションとその感想を紹介します。</p>
<h2 id="共有と分離--compose-multiplatform-本番導入-の設計指針">共有と分離 ─ Compose Multiplatform “本番導入” の設計指針</h2>
<p>Kotlin Multiplatform(KMP)は、ここ数年で盛り上がっているクロスプラットフォーム技術で、ロジックをプラットフォーム間で共通化することができるものです。さらに、Compose Multiplatform(CMP)を用いることで、UIも共通化することができます。KMP / CMP、あるいはクロスプラットフォーム技術において、OSの境界をどう引くか?をいかに設計すれば少人数で生産性高くアプリを開発できるのか、という点に興味を持ちました。</p>
<p>本セッションでは、KMP / CMPとそのエコシステムの現状・限界を見極めながら、その本番導入を進めていった過程とその結果を発表されていました。最終的に約90%のソースコードをOS間で共有することができたそうですが、ネイティブで提供できる高品質な触り心地と、ソースコードの共有によって実装効率の向上を両立できた点は素晴らしいですね。</p>
<div class="remark-link-card-plus__container">
<a href="https://2025.droidkaigi.jp/timetable/944860/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">共有と分離 ─ Compose Multiplatform “本番導入” の設計指針</div>
<div class="remark-link-card-plus__description">Kotlin Multiplatform (KMP) は、ネットワーク通信やドメインロジックを Android / iOS で共通化し、Compose Multiplatform (CMP) はそのうえ UI までを共通化します。両者を組み合わせれば、アプリの大部分を単一のコードベースで開発できることが大きな魅力です。
ところが実プロダクトへの導入に踏み出すと
・使いたいライブラリや SDK が KMP に非対応
・OS 固有の API を直接呼びたい
・一部の画面をネイティブ UI でリッチに作り込みたい
といった理由で、 ”共有できない領域” が必ず現れます。
本セッションでは、Android と iOS のエンジニアが共同登壇し、
・プラットフォーム間でコードのどこを共有し、どこを分離するのか
・開発速度 × ユーザ体験 を両立させる設計・実装パターン
を具体例とともに紹介します。実際に CMP を採用して半年弱で Android / iOS 両アプリをリリースしたプロダクトを題材に解説します。
予定しているトピック:
・共有の最前線
CMP + KMP 対応ライブラリの活用でどこまで共通化できるか
・分離のテクニック
・Kotlin × Swift の DI 設計
・KMP に非対応の SDK の安全な統合
・CMP + ネイティブ UI のハイブリッド実装
・両プラットフォーム視点で見る落とし穴と回避策
CMP 導入で必ずぶつかる ”共有できない領域” を見極め、必要に応じてコードを切り離すための指針をお伝えします。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://2025.droidkaigi.jp/favicon.svg" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">2025.droidkaigi.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://2025.droidkaigi.jp/og/sessions/944860.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h2 id="スマホ新法って何12月施行アプリビジネスに影響あるの">スマホ新法って何?12月施行?アプリビジネスに影響あるの?</h2>
<p>2025年12月18日に施行される、「スマートフォンにおいて利用される特定ソフトウェアに係る競争の促進に関する法律」、いわゆるスマホ新法またはスマホ法について、公正取引委員会の方のセッションがありました。スマホ法の趣旨、スマホ法の施行によって何が変わるのか、スマホ法の施行によるアプリデベロッパーへの影響など、わかりやすく説明いただきました。</p>
<p>規制対象となる指定事業者に属していないエンジニアにおいても、スマホ法の施行によって今後どのようなことが起こっていくのか?しっかり理解しておいた方が良さそうだと感じました。セッションに参加したことで興味・理解が深まりました。</p>
<div class="remark-link-card-plus__container">
<a href="https://2025.droidkaigi.jp/timetable/981378/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">スマホ新法って何?12月施行?アプリビジネスに影響あるの?</div>
<div class="remark-link-card-plus__description">今年12月に施行が予定されている新しい法律、スマホソフトウェア競争促進法(スマホ新法)。
この法律がアプリビジネスにどのような影響を与えるのか、エンジニアのみなさまにどんな関係があるのか、公正取引委員会の担当者が解説します。
例えば、
・OS機能(通信機能等)の利用可能性の向上
・新たなアプリストアの登場など決済手段の多様化
・ウェブサイト等のアプリ外での商品提供の拡大
・ソフトウェアの切替え時におけるデータ移転の円滑化
・ユーザーによるブラウザや検索サービスの選択の促進
などなど、これから見込まれる「変化」について説明します。
</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://2025.droidkaigi.jp/favicon.svg" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">2025.droidkaigi.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://2025.droidkaigi.jp/og/sessions/981378.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h2 id="surviving-network-failures-building-resilient-offline-first-flutter-apps">Surviving Network Failures: Building Resilient Offline-First Flutter Apps</h2>
<p>モバイル端末はネットワークが不安定な場所で使われることも多々あります。ネットワークが不安定な状況においてもアプリを使い続けられることは、ユーザーの体験として非常に重要であると考えます。そのためには、「オフラインファースト」でアプリの体験を設計することが必要になり、同時にオフラインファーストな体験を実現するための技術的な戦略も描く必要があります。</p>
<p>このセッションでは、オフラインファーストなアプリの体験を実現するためのUX / 技術的な戦略を提案しています。サンプルコードはFlutter / Dartですが、おおまかな内容はフレームワークによらず参考にできるものでした。セッションの内容については触れませんが、オフラインファーストでアプリの体験 / アーキテクチャを設計するためのガイドラインとなるセッションだったと感じています。</p>
<div class="remark-link-card-plus__container">
<a href="https://2025.droidkaigi.jp/en/timetable/923086/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Surviving Network Failures: Building Resilient Offline-First Flutter Apps</div>
<div class="remark-link-card-plus__description">What happens when your app loses internet, does it gracefully adapt, or does it leave users frustrated? In a world where connectivity is unpredictable, building offline-first apps isn’t just a feature; it’s a necessity.
In this talk, we’ll explore battle-tested strategies to keep your Flutter app running smoothly, even in airplane mode. You’ll learn:
Caching Like a Pro – Choosing between SQLite, Hive, Isar, or ObjectBox for local data storage.
Syncing Without Breaking Things – Ensuring data consistency with background sync techniques.
Handling Conflicts Gracefully – Avoiding data loss when multiple devices update the same record.
Background Tasks & Work Managers – Keeping data fresh even when the app isn’t open.
By the end of this session, you'll walk away with practical solutions to handle network failures, data synchronization, and seamless user experiences, whether your app is used in the subway, remote villages, or just a bad Wi-Fi zone. </div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://2025.droidkaigi.jp/favicon.svg" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">2025.droidkaigi.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://2025.droidkaigi.jp/en/og/sessions/923086.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="最後に">最後に</h1>
<p><img __ASTRO_IMAGE_="{"src":"./DroidKaigi-2025-4.png","alt":"Thank you!","index":0}"></p>
<p>2025年9月10日から12日にかけて開催されたDroidKaigi 2025に参加してきました。Androidに関わる企業・人々が一堂に会するお祭りとして、楽しく、そして学びの多いイベントでした。主催者・スタッフ・登壇者・協賛企業など、関係者に感謝しつつ、弊社メドレーでもイベントや技術コミュニティを盛り上げていきたいと改めて思いました。</p>
<p>メドレーでは、カンファレンスへのスポンサーの他にも、イベントの開催などを通じて技術とコミュニティに貢献しています。</p>
<p>メドレーではエンジニアを積極採用中です!</p>
<p>メドレーではテクノロジーを活用して、医療ヘルスケアの未来をつくるプロダクトを開発しています。モバイルアプリ開発の専門性を活かした医療ヘルスケア領域の課題解決に興味がある方は、ぜひカジュアル面談にお越しください!</p>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs/1000096" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Flutterエンジニア/人材プラットフォーム本部 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">Flutterエンジニア/人材プラットフォーム本部(株式会社メドレー)の求人情報です。 | HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:333333,c_pad,w_978,h_513/m/577c/images/2118206564349132800_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs/1000047" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">iOSアプリケーションエンジニア/melmo(総合医療アプリ) | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">iOSアプリケーションエンジニア/melmo(総合医療アプリ)(株式会社メドレー)の求人情報です。 | HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:333333,c_pad,w_978,h_513/m/577c/images/2118206564349132800_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://hrmos.co/pages/medley/jobs/1000039" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Androidアプリケーションエンジニア/melmo(総合医療アプリ) | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">Androidアプリケーションエンジニア/melmo(総合医療アプリ)(株式会社メドレー)の求人情報です。 | HRMOS</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.hrmos.co/b2b-assets/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hrmos.co</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.hrmos.co/hrmony/b_rgb:333333,c_pad,w_978,h_513/m/577c/images/2118206564349132800_full" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- 複雑な歯科電子レセプトを可視化するWebビューアを作ってみたhttps://developer.medley.jp/entry/2025/09/18/184343https://developer.medley.jp/entry/2025/09/18/184343
こちらの記事は「MEDLEY Summer Tech Blog Relay」の18日目の記事です🎉
今週はFY25新卒エンジニアの4人が連続して記事をお届けします!
はじめに
株式会社メドレーに 2025 年 4 月に新卒入社した、平...Thu, 18 Sep 2025 09:43:43 GMT<!-- Edit here! -->
<p>こちらの記事は「<a href="/entry/2025/08/15/20250815/">MEDLEY Summer Tech Blog Relay</a>」の18日目の記事です🎉</p>
<p>今週はFY25新卒エンジニアの4人が連続して記事をお届けします!</p>
<p><img __ASTRO_IMAGE_="{"src":"thumbnail.png","alt":"サムネイル:複雑な歯科電子レセプトを可視化するWebビューアを作ってみた","index":0}"></p>
<h1 id="はじめに">はじめに</h1>
<p>株式会社メドレーに 2025 年 4 月に新卒入社した、平林です。
医療プラットフォームの歯科診療所事業部でソフトウェアエンジニアをしており、クラウド歯科業務支援システム「Dentis」を開発しています。</p>
<p>皆さんは病院や歯科医院で診察を受けた後、窓口で料金を支払っていると思います。ご存じの方も多いかもしれませんが、ここで払う料金は医療機関の収入の一部に過ぎません。</p>
<p>残りの診療報酬は患者が加入している保険組合(保険者)から支払われることになっており、医療機関は毎月患者に行った診療をまとめた書類(診療報酬請求書、通称レセプト)を作成して支払基金(国保の場合は国民健康保険団体連合会)に請求します。
以下は、社会保険に加入している患者の診療報酬請求の流れです。</p>
<p><img __ASTRO_IMAGE_="{"src":"medical-insurance-system-diagram.png","alt":"日本の医療保険制度における、患者、医療機関、保険者、支払基金の関係を図解したイラスト。中心には患者(被保険者)がおり、医療費の一部を医療機関へ、保険料を保険者へ支払う。医療機関は診療報酬を支払基金に請求し、支払基金は医療機関へ支払い、保険者へも請求を行う。複雑な医療費の流れが示されている。","index":0}"></p>
<div class="remark-link-card-plus__container">
<a href="https://www.ssk.or.jp/smph/aboutkikin/kohoshi/regardingkikin.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">支払基金ってどんなところ?|社会保険診療報酬支払基金</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.ssk.or.jp/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.ssk.or.jp</span>
</div>
</div>
</a>
</div>
<p>このレセプトをコンピューターを使って自動で作成してくれるのが、レセプトコンピューター(レセコン)です。</p>
<p>今回は、このレセプトについて学習するために、レセプトビューアを作ってみました。</p>
<p><img __ASTRO_IMAGE_="{"src":"uke-reader.png","alt":"UKE Readerのスクリーンショット。画面中央にUKEがグリッド形式で表示されている。画面上部に選択項目の説明、サイドには選択項目のプロパティが表示されている。","index":0}"></p>
<p>ソースコードは以下で公開しています。</p>
<div class="remark-link-card-plus__container">
<a href="https://github.com/avaice/dental-uke-reader" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">GitHub - avaice/dental-uke-reader</div>
<div class="remark-link-card-plus__description">Contribute to avaice/dental-uke-reader development by creating an account on GitHub.</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://github.githubassets.com/favicons/favicon.svg" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">github.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://opengraph.githubassets.com/91e249324731618de253442ddc362c979c1cd9a955539de5a91f8a4da8c69f42/avaice/dental-uke-reader" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="電子レセプトについて">電子レセプトについて</h1>
<p>レセプトには、紙レセプトと電子レセプトの2種類があります。元々は紙レセプトのみでしたが、現在はそれをデータ化した電子レセプトが主流となっています。</p>
<p>電子レセプトはCSVライクなカンマ区切りのフォーマットとなっており、UKEという拡張子で保存されます。</p>
<p>以下は、一般的な電子レセプトの例です。※データは架空の情報です</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>UK,2,13,3,1234567,,メドレークリニック,202201,0117,00</span></span>
<span class="line"><span>IR,2,13,3,1234567,,202201,03-1234-5678,0117</span></span>
<span class="line"><span>RE,4,3318,202112,歯科 太郎,1,19400808,,,20210609,1,,,29,,123456/8,,,,,,,,,,シカタロウ,</span></span>
<span class="line"><span>HO,39141320,,23572449,2,1279,,,,,,,,</span></span>
<span class="line"><span>JD,1,,,,,,,,1,,,,,,,,,,,,,,1,,,,,,,,,</span></span>
<span class="line"><span>MF,00,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,</span></span>
<span class="line"><span>HS,,,101700101500101400101300101200101100102100102200102300102400102500102600102700104800104500104400104300104200104100103100103200103300103400103500103700,5234009,,,,,,,,</span></span>
<span class="line"><span>SS,12,1,301001610,,,CA062,,CA045,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,57,2,,,,,,,,1,,,,,,,,,,,,,,1,,,,,,,,,</span></span>
<span class="line"><span>(...以下省略)</span></span>
<span class="line"><span>GO,116,155332,99</span></span></code></pre>
<p>一見よくわからないデータですが、社会保険診療報酬支払基金のWebページには「オンライン又は光ディスク等による 請求に係る記録条件仕様(歯科用)」(以下、仕様書)が公開されているので、それを見ながら読み進めていきます。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.ssk.or.jp/smph/seikyushiharai/iryokikan/iryokikan_02.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">電子レセプトの作成|社会保険診療報酬支払基金</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.ssk.or.jp/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.ssk.or.jp</span>
</div>
</div>
</a>
</div>
<p><img __ASTRO_IMAGE_="{"src":"row.png","alt":"UKEから抜き出した行。UK,2,13,3,1234567,,メドレークリニック,202201,0117,00","index":0}"></p>
<p>1列目には「UK」と書いてあります。これはレコード識別情報です。</p>
<p>レコード識別情報の一覧は、以下のとおりです。</p>
<table><thead><tr><th align="center">値</th><th align="left">レコードの種類</th><th align="left">データの一例</th></tr></thead><tbody><tr><td align="center"><strong>UK</strong></td><td align="left">受付情報レコード</td><td align="left">審査支払機関・医療機関コード・都道府県など</td></tr><tr><td align="center"><strong>IR</strong></td><td align="left">医療機関情報レコード</td><td align="left">医療機関コード・連絡先・請求年月など</td></tr><tr><td align="center"><strong>RE</strong></td><td align="left">レセプト共通レコード</td><td align="left">患者(被保険者)の名前・性別など</td></tr><tr><td align="center"><strong>HO</strong></td><td align="left">保険者レコード</td><td align="left">保険者番号・合計点数など</td></tr><tr><td align="center"><strong>KO</strong></td><td align="left">公費レコード</td><td align="left">負担者番号公費負担者番号など</td></tr><tr><td align="center"><strong>SN</strong></td><td align="left">資格確認レコード</td><td align="left">負担者種別や受給者番号など</td></tr><tr><td align="center"><strong>JD</strong></td><td align="left">受診日等レコード</td><td align="left">日別の受診等区分など</td></tr><tr><td align="center"><strong>MF</strong></td><td align="left">窓口負担額レコード</td><td align="left">高額療養費の扱いなど</td></tr><tr><td align="center"><strong>HS</strong></td><td align="left">傷病名部位レコード</td><td align="left">傷病名コード・部位の<a href="https://shinryohoshu.mhlw.go.jp/shinryohoshu/searchMenu/doSearchDentalInputShishiki">歯式コード</a>など</td></tr><tr><td align="center"><strong>SS</strong></td><td align="left">歯科診療行為レコード</td><td align="left">診療識別・歯科診療行為コードなど</td></tr><tr><td align="center"><strong>SI</strong></td><td align="left">診療行為レコード</td><td align="left">医科診療行為コードなど</td></tr><tr><td align="center"><strong>IY</strong></td><td align="left">医薬品レコード</td><td align="left">診療で使用した医薬品・院内で処方した医薬品など</td></tr><tr><td align="center"><strong>TO</strong></td><td align="left">特定器材レコード</td><td align="left">特定器材コードなど</td></tr><tr><td align="center"><strong>CO</strong></td><td align="left">コメントレコード</td><td align="left">診療識別・コメントコードなど</td></tr><tr><td align="center"><strong>SJ</strong></td><td align="left">症状詳記レコード</td><td align="left">症状詳記(診療内容が明確でない場合などに記録する)など</td></tr><tr><td align="center"><strong>GO</strong></td><td align="left">診療報酬請求書レコード</td><td align="left">総件数など</td></tr></tbody></table>
<p>この値によってこの行がどんな情報を含んでいるのか判別することができます。</p>
<p>今回はUK(受付情報レコード)だけ見てみましょう。仕様書と照らし合わせると、以下の内容であることが分かります。</p>
<table><thead><tr><th align="left">値</th><th align="left">説明</th><th align="left">備考</th></tr></thead><tbody><tr><td align="left"><strong>1</strong></td><td align="left"><strong>審査支払機関</strong></td><td align="left">1: 社会保険診療報酬支払基金、2: 国民健康保険団体連合会</td></tr><tr><td align="left"><strong>13</strong></td><td align="left"><strong>都道府県コード</strong></td><td align="left">総務省が定める都道府県コード(<a href="https://www.soumu.go.jp/denshijiti/code.html">URL</a>) 16: 富山県</td></tr><tr><td align="left"><strong>3</strong></td><td align="left"><strong>点数表種別</strong></td><td align="left">点数表種別 3: 歯科 他にも、1: 医科, 4: 調剤など</td></tr><tr><td align="left"><strong>1234567</strong></td><td align="left"><strong>医療機関コード</strong></td><td align="left">医療機関コード</td></tr><tr><td align="left">(空欄)</td><td align="left"><strong>予備エリア</strong></td><td align="left"></td></tr><tr><td align="left"><strong>メドレークリニック</strong></td><td align="left"><strong>地方厚生(支)局長に届け出た名称</strong></td><td align="left">20文字を超える場合は、決まっている省略名称になる</td></tr><tr><td align="left"><strong>202201</strong></td><td align="left"><strong>請求年月</strong></td><td align="left">レセプトが請求される年月。例: 2022年1月</td></tr><tr><td align="left"><strong>0117</strong></td><td align="left"><strong>施設基準届出コード</strong></td><td align="left">2文字区切りで解釈される。01:補管(クラウン・ブリッジ維持管理料)17:歯科初診料</td></tr><tr><td align="left"><strong>00</strong></td><td align="left"><strong>マルチボリューム識別情報</strong></td><td align="left">フロッピーディスクなど容量の少ない媒体を使用する際、レセプト情報が複数のファイルに分割されている場合に、そのファイルの順番を示す</td></tr></tbody></table>
<p>このような形で各行を読んでいくと、電子レセプトが示している内容が分かります。</p>
<p>レセプトの読み方がなんとなく分かったので、次は実際にこれを読み込むプログラムを作ります。</p>
<h1 id="csvを読み込んでパースする">CSVを読み込んでパースする</h1>
<p>電子レセプトはShift-JISで記録されているので、文字コードを指定して読み込みます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#DCDCAA"> loadFile</span><span style="color:#D4D4D4"> = (</span><span style="color:#9CDCFE">file</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span></span>
<span class="line"><span style="color:#569CD6"> new</span><span style="color:#4EC9B0"> Promise</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">resolve</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">reject</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> reader</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> FileReader</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#9CDCFE"> reader</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">onload</span><span style="color:#D4D4D4"> = (</span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> text</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">target</span><span style="color:#D4D4D4">?.</span><span style="color:#9CDCFE">result</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#569CD6">typeof</span><span style="color:#9CDCFE"> text</span><span style="color:#D4D4D4"> !== </span><span style="color:#CE9178">"string"</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#DCDCAA"> reject</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> Error</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"Failed to load the file"</span><span style="color:#D4D4D4">));</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#DCDCAA"> resolve</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">text</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4"> };</span></span>
<span class="line"><span style="color:#9CDCFE"> reader</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">onerror</span><span style="color:#D4D4D4"> = () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#DCDCAA"> reject</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> Error</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"Failed to read the file"</span><span style="color:#D4D4D4">));</span></span>
<span class="line"><span style="color:#D4D4D4"> };</span></span>
<span class="line"><span style="color:#9CDCFE"> reader</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">readAsText</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">file</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"shift-jis"</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span></code></pre>
<p>読み込んだあとは、UKEデータをパースします。</p>
<p>CSVのパースは考慮することが多くエンジニア泣かせで有名ですが、UKEの仕様は非常にシンプルなので簡単にパースできます。</p>
<p>(以下は、一部抜粋です)</p>
<table><thead><tr><th align="center">符号名称</th><th align="center">図形記号</th><th align="left">用途</th></tr></thead><tbody><tr><td align="center"><strong>コンマ</strong></td><td align="center"><code>,</code></td><td align="left">項目の区切りを表現する</td></tr><tr><td align="center"><strong>引用符</strong></td><td align="center"><code>"</code></td><td align="left">使用しない</td></tr><tr><td align="center"><strong>改行コード</strong></td><td align="center"><code>\n</code></td><td align="left">レコードの区切りを表現する</td></tr></tbody></table>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> csv</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">await</span><span style="color:#DCDCAA"> loadFile</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">file</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> receipts</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">csv</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">trim</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">split</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"</span><span style="color:#D7BA7D">\n</span><span style="color:#CE9178">"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">map</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">line</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#9CDCFE"> line</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">split</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">","</span><span style="color:#D4D4D4">));</span></span></code></pre>
<h1 id="項目の説明を表示する">項目の説明を表示する</h1>
<p>行ごとのレコードと列番号を渡すと説明が返ってくるロジックを作ります。</p>
<p>説明情報は、社会保険診療報酬支払基金のWebページに公開されている「電子レセプトの作成手引き」を参考にします。</p>
<p>「電子レセプトの作成手引き」はPDFで公開されているデータですが、 markitdown を使ってマークダウンに変換し作業スペースに置くことで、CursorやClaude Codeといった開発支援ツールを活用して効率的に実装を進めることができました。</p>
<div class="remark-link-card-plus__container">
<a href="https://github.com/microsoft/markitdown" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">GitHub - microsoft/markitdown: Python tool for converting files and office documents to Markdown.</div>
<div class="remark-link-card-plus__description">Python tool for converting files and office documents to Markdown. - microsoft/markitdown</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://github.githubassets.com/favicons/favicon.svg" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">github.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://opengraph.githubassets.com/d07e33404c1ea9e8735594fc5e279bc0f1254165ccf2191ab8ee478aac058a02/microsoft/markitdown" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> type</span><span style="color:#4EC9B0"> RecordType</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#9CDCFE"> index</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">number</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#9CDCFE"> data</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">[];</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#DCDCAA"> explain</span><span style="color:#D4D4D4"> = (</span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">RecordType</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> switch</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">identification</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#C586C0"> case</span><span style="color:#CE9178"> "UK"</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#DCDCAA"> explainUK</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#C586C0"> case</span><span style="color:#CE9178"> "IR"</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#DCDCAA"> explainIR</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#6A9955"> // ...省略</span></span>
<span class="line"><span style="color:#C586C0"> default</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#569CD6"> null</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span></code></pre>
<p>値によって説明が変わる内容もあるので、動的に説明文を作れるようにしました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> explainUK</span><span style="color:#D4D4D4">: (((</span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">RecordType</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#4EC9B0"> string</span><span style="color:#D4D4D4">) | </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">)[] = [</span></span>
<span class="line"><span style="color:#CE9178"> "この行が受付情報レコードであることを示します"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span></span>
<span class="line"><span style="color:#CE9178"> `審査支払機関を示します。値「</span><span style="color:#569CD6">${</span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">data</span><span style="color:#D4D4D4">[</span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">index</span><span style="color:#D4D4D4">]</span><span style="color:#569CD6">}</span><span style="color:#CE9178">」は「</span><span style="color:#569CD6">${</span><span style="color:#DCDCAA">getShiharaiKikan</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">data</span><span style="color:#D4D4D4">[</span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">index</span><span style="color:#D4D4D4">])</span><span style="color:#569CD6">}</span><span style="color:#CE9178">」です`</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#6A9955"> // ...省略</span></span>
<span class="line"><span style="color:#D4D4D4">];</span></span></code></pre>
<h1 id="マスターファイルから情報を取得する">マスターファイルから情報を取得する</h1>
<p>UKEの中にはそのまま解釈できる内容だけでなく、診療行為コードや医薬品コードのような別のデータ(マスターファイル)を参照しないと内容がわからないものがあります。</p>
<p>マスターファイルや仕様は、厚生労働省がWebで公開してくれています。</p>
<div class="remark-link-card-plus__container">
<a href="https://shinryohoshu.mhlw.go.jp/shinryohoshu/downloadMenu/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">診療報酬情報提供サービス</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<span class="remark-link-card-plus__url">shinryohoshu.mhlw.go.jp</span>
</div>
</div>
</a>
</div>
<p><img __ASTRO_IMAGE_="{"src":"master-file.png","alt":"マスターファイルを表計算ソフトで開いたときのスクリーンショット。","index":0}"></p>
<p>マスターファイルは電子レセプトと仕様の違うCSVで、以下のような仕様になっています。</p>
<ol>
<li>項目間の区切り文字は「,」(カンマ)とする。</li>
<li>各項目の値は、モード(「数字」、「英数」及び「漢字」)に関わらず、引用符「”」(ダブルクォーテーション)を前後に付す。</li>
<li>最大バイトは引用符「“」を除いたバイト数であり、小数部がある値は、小数点及び小数以降の数字を含む。</li>
<li>0バイトの文字列(Null)の場合は、引用符「“」を続けて記録する。</li>
</ol>
<p>値をダブルクォーテーションで囲むとのことなので、値の中のカンマを許容する仕様である可能性があります。愚直に1文字ずつ処理します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#6A9955">// const csv = 生のCSVデータ;</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> lines</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">csv</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">split</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"</span><span style="color:#D7BA7D">\n</span><span style="color:#CE9178">"</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> records</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Record</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">, </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">>[] = [];</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> shobyomeiMasterHeaders</span><span style="color:#D4D4D4"> = [</span><span style="color:#CE9178">"変更区分"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"マスター種別"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"傷病名コード"</span><span style="color:#D4D4D4">, </span><span style="color:#6A9955">/** 以下省略... */</span><span style="color:#D4D4D4">];</span></span>
<span class="line"><span style="color:#C586C0">for</span><span style="color:#D4D4D4"> (</span><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> line</span><span style="color:#569CD6"> of</span><span style="color:#9CDCFE"> lines</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> record</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Record</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">, </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">> = {};</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#9CDCFE"> charIndex</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#9CDCFE"> columnIndex</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#9CDCFE"> inQuotes</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#9CDCFE"> buffer</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">""</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#C586C0"> while</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">charIndex</span><span style="color:#D4D4D4"> < </span><span style="color:#9CDCFE">line</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">length</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> char</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">line</span><span style="color:#D4D4D4">[</span><span style="color:#9CDCFE">charIndex</span><span style="color:#D4D4D4">];</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">char</span><span style="color:#D4D4D4"> === </span><span style="color:#CE9178">'"'</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#9CDCFE"> inQuotes</span><span style="color:#D4D4D4"> = !</span><span style="color:#9CDCFE">inQuotes</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">else</span><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">char</span><span style="color:#D4D4D4"> === </span><span style="color:#CE9178">','</span><span style="color:#D4D4D4"> && !</span><span style="color:#9CDCFE">inQuotes</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#6A9955"> // カンマ区切り、かつ引用符の外</span></span>
<span class="line"><span style="color:#9CDCFE"> record</span><span style="color:#D4D4D4">[</span><span style="color:#9CDCFE">shobyomeiMasterHeaders</span><span style="color:#D4D4D4">[</span><span style="color:#9CDCFE">columnIndex</span><span style="color:#D4D4D4">]] = </span><span style="color:#9CDCFE">buffer</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#9CDCFE"> columnIndex</span><span style="color:#D4D4D4">++;</span></span>
<span class="line"><span style="color:#9CDCFE"> buffer</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">""</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">else</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#6A9955"> // それ以外はバッファに追加</span></span>
<span class="line"><span style="color:#9CDCFE"> buffer</span><span style="color:#D4D4D4"> += </span><span style="color:#9CDCFE">char</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#9CDCFE"> charIndex</span><span style="color:#D4D4D4">++;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">buffer</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">length</span><span style="color:#D4D4D4"> > </span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#9CDCFE"> record</span><span style="color:#D4D4D4">[</span><span style="color:#9CDCFE">shobyomeiMasterHeaders</span><span style="color:#D4D4D4">[</span><span style="color:#9CDCFE">columnIndex</span><span style="color:#D4D4D4">]] = </span><span style="color:#9CDCFE">buffer</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#6A9955"> // データベースへの保存</span></span>
<span class="line"><span style="color:#C586C0"> await</span><span style="color:#DCDCAA"> saveToDatabase</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"shobyomei"</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">傷病名コード</span><span style="color:#D4D4D4">, [</span><span style="color:#9CDCFE">record</span><span style="color:#D4D4D4">]);</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>マスターファイルは全部で20MB程度あり、毎回処理していたら時間がかかってしまうので、indexedDBに展開しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"indexeddb-in-devtools.png","alt":"ブラウザのデベロッパーツールで、IndexedDBのshobyomeiMasterというオブジェクトストアが選択され、その中のデータが表示されている。","index":0}"></p>
<p>これで、診療行為コードや医薬品コードを基にマスターの内容を参照できるようになりました。</p>
<p>ここで一つ気をつけなければいけないのは、医薬品コードの情報には有効期限があるということです。</p>
<p>該当するレコードの診療日が、変更年月日〜廃止年月日の間であることを確認する必要があります。例えば2015年のレセプトから情報を得たいのであれば、2015年時点のマスターファイルを参照する必要があります。</p>
<p>そのため、時系列に合った複数バージョンのマスターを保持できる設計にするために、キーに対してデータは配列で持つようにしました。</p>
<h1 id="表としてレンダリングする">表としてレンダリングする</h1>
<p>UKEのパーサーと情報の取得処理が書けたので、表としてレンダリングします。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="tsx"><code><span class="line"><span style="color:#569CD6">type</span><span style="color:#4EC9B0"> CellPosition</span><span style="color:#D4D4D4"> = { </span><span style="color:#9CDCFE">x</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">number</span><span style="color:#D4D4D4">; </span><span style="color:#9CDCFE">y</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">number</span><span style="color:#D4D4D4"> };</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#DCDCAA"> UKERenderer</span><span style="color:#D4D4D4"> = ({ </span><span style="color:#9CDCFE">uke</span><span style="color:#D4D4D4"> }: { </span><span style="color:#9CDCFE">uke</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">[][] }) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#D4D4D4"> [</span><span style="color:#4FC1FF">activeCell</span><span style="color:#D4D4D4">, </span><span style="color:#4FC1FF">setActiveCell</span><span style="color:#D4D4D4">] = </span><span style="color:#DCDCAA">useState</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">CellPosition</span><span style="color:#D4D4D4"> | </span><span style="color:#4EC9B0">null</span><span style="color:#D4D4D4">>(</span><span style="color:#569CD6">null</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#DCDCAA"> handleClickCell</span><span style="color:#D4D4D4"> = (</span><span style="color:#9CDCFE">x</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">number</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">y</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">number</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#DCDCAA"> setActiveCell</span><span style="color:#D4D4D4">({ </span><span style="color:#9CDCFE">x</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">y</span><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#DCDCAA"> isActiveCell</span><span style="color:#D4D4D4"> = (</span><span style="color:#9CDCFE">x</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">number</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">y</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">number</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span></span>
<span class="line"><span style="color:#9CDCFE"> activeCell</span><span style="color:#D4D4D4"> && </span><span style="color:#9CDCFE">activeCell</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">x</span><span style="color:#D4D4D4"> === </span><span style="color:#9CDCFE">x</span><span style="color:#D4D4D4"> && </span><span style="color:#9CDCFE">activeCell</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">y</span><span style="color:#D4D4D4"> === </span><span style="color:#9CDCFE">y</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#9CDCFE"> uke</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">map</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">row</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">y</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> (</span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#4EC9B0">Row</span><span style="color:#9CDCFE"> key</span><span style="color:#D4D4D4">=</span><span style="color:#569CD6">{</span><span style="color:#CE9178">`row-</span><span style="color:#569CD6">${</span><span style="color:#9CDCFE">y</span><span style="color:#569CD6">}</span><span style="color:#CE9178">`</span><span style="color:#569CD6">}</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#569CD6"> {</span><span style="color:#9CDCFE">row</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">map</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">value</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">x</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> (</span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#4EC9B0">Cell</span></span>
<span class="line"><span style="color:#9CDCFE"> key</span><span style="color:#D4D4D4">=</span><span style="color:#569CD6">{</span><span style="color:#CE9178">`cell-</span><span style="color:#569CD6">${</span><span style="color:#9CDCFE">x</span><span style="color:#569CD6">}</span><span style="color:#CE9178">`</span><span style="color:#569CD6">}</span></span>
<span class="line"><span style="color:#9CDCFE"> onClick</span><span style="color:#D4D4D4">=</span><span style="color:#569CD6">{</span><span style="color:#D4D4D4">() </span><span style="color:#569CD6">=></span><span style="color:#DCDCAA"> handleClickCell</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">x</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">y</span><span style="color:#D4D4D4">)</span><span style="color:#569CD6">}</span></span>
<span class="line"><span style="color:#9CDCFE"> active</span><span style="color:#D4D4D4">=</span><span style="color:#569CD6">{</span><span style="color:#DCDCAA">isActiveCell</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">x</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">y</span><span style="color:#D4D4D4">)</span><span style="color:#569CD6">}</span></span>
<span class="line"><span style="color:#9CDCFE"> description</span><span style="color:#D4D4D4">=</span><span style="color:#569CD6">{</span><span style="color:#DCDCAA">explain</span><span style="color:#D4D4D4">({ </span><span style="color:#9CDCFE">index:</span><span style="color:#9CDCFE"> x</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">data:</span><span style="color:#9CDCFE"> row</span><span style="color:#D4D4D4"> })</span><span style="color:#569CD6">}</span></span>
<span class="line"><span style="color:#808080"> ></span></span>
<span class="line"><span style="color:#569CD6"> {</span><span style="color:#9CDCFE">value</span><span style="color:#569CD6">}</span></span>
<span class="line"><span style="color:#808080"> </</span><span style="color:#4EC9B0">Cell</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#D4D4D4"> ))</span><span style="color:#569CD6">}</span></span>
<span class="line"><span style="color:#808080"> </</span><span style="color:#4EC9B0">Row</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#D4D4D4"> ));</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span></code></pre>
<h1 id="実際に開発したもの">実際に開発したもの</h1>
<p>実際に開発したものは、今まで解説したコードをベースに機能や処理を付け加えています。</p>
<p><img __ASTRO_IMAGE_="{"src":"uke-reader-demo.gif","alt":"UKE Readerを操作する様子が収録されているGIFアニメーション。","index":0}"></p>
<h1 id="開発してみて">開発してみて</h1>
<p>以前は、テキストエディターを使って目視でマスターファイルの内容を照らし合わせており確認作業が大変でしたが、今回作った電子レセプトビューアによって、正しくレセプトが出力されているかの確認が行いやすくなりました。</p>
<p>また、自分自身も電子レセプトの仕様を学んだことで、業務で行っていることが少しずつ分かるようになってきました!</p>
<h1 id="おわりに">おわりに</h1>
<p>今回は、歯科の電子レセプトを理解するために、電子レセプトビューアを開発しました。</p>
<p>私たちソフトウェアエンジニアは、普段医療に関するシステムの話をあまり目にすることは少ないですが、意外と仕様やデータが一般にも公開されており、手軽に触れてみることができます。</p>
<p>この記事を読んで、医療ITに興味を持ってくださる方がいれば幸いです!</p>
<hr>
<p>次は、新卒エンジニア4人目の池田さんです!お楽しみに!!</p>
- 「誰でも使える」ように、ジョブメドレーの求職者向け利用規約を刷新しますhttps://developer.medley.jp/entry/2025/09/12/112057https://developer.medley.jp/entry/2025/09/12/112057
この記事は 「MEDLEY Summer Tech Blog Relay」 15 日目の記事です。
はじめに
こんにちは!人材プラットフォーム本部プロダクト開発部の小泉(@kzmkt)です。
医療介護従事者向けの求人メディア「ジョブメド...Fri, 12 Sep 2025 02:20:57 GMT<!-- Edit here! -->
<p><strong>この記事は 「<a href="https://developer.medley.jp/entry/2025/08/15/20250815/">MEDLEY Summer Tech Blog Relay</a>」 15 日目の記事です。</strong></p>
<p><img __ASTRO_IMAGE_="{"src":"thumbnail.png","alt":"サムネイル:「誰でも使える」ように、ジョブメドレーの求職者向け利用規約を刷新します","index":0}"></p>
<h1 id="はじめに">はじめに</h1>
<p>こんにちは!人材プラットフォーム本部プロダクト開発部の小泉(<a href="https://github.com/kzmkt">@kzmkt</a>)です。<br>
医療介護従事者向けの求人メディア「<a href="https://job-medley.com/">ジョブメドレー</a>」開発チームの一員として、主にフロントエンドのエンジニアをしています。</p>
<p>今回、2025年10月の利用規約改定に備えて、<strong>利用規約ページを刷新</strong>しました。<br>
→ <strong><a href="https://job-medley.com/rule_new/">改定版周知のための利用規約ページはこちら</a></strong></p>
<p>ジョブメドレーは「<strong>誰でも使える</strong>」サービスを目指してデザインリニューアルのプロジェクトに取り組んでいます。<br>
→ <strong><a href="https://note.com/medley/n/ncf100214f838">「誰でも使える」ように、アクセシビリティ向上に向けて取り組んだこと</a></strong></p>
<p>この記事では取り組みの背景、具体的な開発プロセスとソフトウェアエンジニアリングについて、プロジェクトメンバーの話も交えて紹介させていただきます。</p>
<p>今回のプロジェクトでは利用規約を読みやすくするために、総合的な観点が必要だったので、法務担当・外部弁護士・デザイン部・開発チームの共同プロジェクトとして進めました。</p>
<h1 id="利用規約を刷新する理由">利用規約を刷新する理由</h1>
<p>利用規約刷新の具体的な背景と課題について、事業企画室の小松さんとデザイン部の佐竹さんにお話を伺いました。</p>
<h2 id="事業企画室-小松さんのコメント">事業企画室 小松さんのコメント</h2>
<p>ジョブメドレーの利用規約は、あらゆるケースを想定した抽象的で抜け漏れのない規定が必要となります。<br>
そうした文章はともすると、冗長で専門的な用語が多用されるため、誰にとっても読みやすいものとはいえません。<br>
また、サービス規模にあわせて最適な状態に改定してきたものの、運営が15年にもなると複雑でわかりやすいとはいえない規約となっていました。</p>
<p>今回の取り組みでは、わかりづらさを解消し、誰にとってもわかりやすい利用規約を目指すため、以下のような対応を実施しました。</p>
<ul>
<li>冗長な文章を箇条書きや表形式に変更する</li>
<li>条文が適用されたときの具体例を記載する</li>
<li>「〜を承諾するものとします」や「〜に帰属するものとします」など、法律的な言い回しを可能な限り平易なものに変更する</li>
<li>章立ても含めた規約の構成をすべて見直す</li>
</ul>
<h2 id="デザイン部-佐竹さんのコメント">デザイン部 佐竹さんのコメント</h2>
<h3 id="旧デザインの課題">旧デザインの課題</h3>
<p>デザインの視点から見た旧デザインの課題は大きく3つあります。</p>
<ul>
<li><strong>情報構造が不明瞭で、ページ全体の流れをつかみにくい</strong></li>
<li><strong>文字サイズや太さに強弱がなく、重要な情報を見つけづらい</strong></li>
<li><strong>装飾的な補助要素がなく、内容を直感的に理解しにくい</strong></li>
</ul>
<p><img __ASTRO_IMAGE_="{"src":"jm-rule-old.png","alt":"ジョブメドレー利用規約:改訂前","index":0}"></p>
<h3 id="新デザインでの解決策">新デザインでの解決策</h3>
<p>新デザインでは、<strong>読む負担を減らし、内容を把握しやすい表現</strong>を意識しました。</p>
<ul>
<li>適切な余白を設け、テキストをブロック単位で整理</li>
<li>目次を追加し、見出しサイズを最適化することで、章や節の構造を一目で把握しやすく改善</li>
<li>キーワードをフォントウェイトで強調し、重要情報を直感的に理解可能に</li>
<li>イラストを追加し、複雑な内容をサポート。長文の合間に配置することで、読解のストレスを軽減</li>
<li>背景色付きの注釈で補足説明や注意事項を目立たせ、必要な情報をすぐに把握できるように</li>
</ul>
<p><img __ASTRO_IMAGE_="{"src":"jm-rule-new.png","alt":"ジョブメドレー利用規約:改定後","index":0}"></p>
<hr>
<p>ジョブメドレーは2009年11月にサービスを開始しました。</p>
<p>サービスが成長していく中で法改正以外でも利用規約を更新し続けており、その時々では最善を尽くしてきたものの、たび重なる改定により、結果として複雑で読みにくい利用規約になってきてしまっていました。<br>
そのため、今回の改定で全面的に見直しを行い、よりわかりやすい構成と内容に改定することにしました。</p>
<h1 id="使用技術と開発プロセス">使用技術と開発プロセス</h1>
<p>ここからは分かりやすい利用規約の実現及び「<strong>誰でも使える</strong>」ジョブメドレーの実現に向けた、<strong>アクセシビリティに配慮した開発</strong>について紹介します。</p>
<p>ジョブメドレーにはさまざまな開発チームがあり、各チームが並行して機能開発や改善を進めています。多様なチーム構成の中では、チーム間の違いに加えて、企画者・デザイナー・エンジニアなどポジション毎でも、アクセシビリティに関する知見のレベルは様々です。</p>
<p>また、現在は協力会社のサポートを受けながらアクセシビリティに配慮した開発を進めており、今後のアクセシビティ対応の内製化に向けて、<strong>ジョブメドレーのエンジニア全員が「誰でも使える」を「誰でも作れる」ようにする</strong>必要があります。</p>
<p>今回は以下のプロセスで開発を行いました。</p>
<ul>
<li>企画・仕様策定</li>
<li>デザイン作成</li>
<li>開発
<ul>
<li><strong>デザインコンポーネント開発</strong></li>
<li>画面全体の作り込み</li>
<li>ロジックの実装</li>
<li><strong>アクセシビリティレビュー</strong></li>
</ul>
</li>
<li>QA</li>
<li>リリース</li>
<li>監視</li>
<li>運用</li>
</ul>
<p>今回は「<strong>デザインコンポーネント開発</strong>」および「<strong>アクセシビリティレビュー</strong>」における取り組みの一部をご紹介します。</p>
<h2 id="どの画面でも変わらないアクセシビリティ品質を提供する開発プロセス">どの画面でも変わらないアクセシビリティ品質を提供する開発プロセス</h2>
<p>今回の利用規約ページでは、デザインコンポーネントとロジック実装を完全に分けて開発しており、デザインコンポーネント開発段階でアクセシビリティに配慮した実装を行っています。<br>
<a href="https://storybook.js.org/">Storybook</a>を用いてアクセシビリティの品質が担保されたコンポーネントを開発し、それらを組み合わせることで各ページを作成しています。</p>
<h3 id="アクセシビリティ勉強会でstorybookの利用方法を共有">アクセシビリティ勉強会でStorybookの利用方法を共有</h3>
<p>ジョブメドレー開発チームではアクセシビリティ勉強会を実施しており、FigmaのデザインからStorybookを用いたデザインコンポーネント開発を行う際の注意点を紹介しています。<br>
コンポーネント毎にジョブメドレーで目指すアクセシビリティが担保された状態を目標に開発しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"storybook-introduction.png","alt":"アクセシビリティ勉強会:FigmaとStorybookの使い方","index":0}"></p>
<h3 id="利用規約ページにおける各項目要素のコンポーネントを作成">利用規約ページにおける各項目要素のコンポーネントを作成</h3>
<p>今回の利用規約ページでは、各要素に対応したコンポーネントを作成しました。<br>
それぞれのコンポーネントを組み合わせることで、各章や注釈の構造を明確にしています。</p>
<p><img __ASTRO_IMAGE_="{"src":"storybook-component.png","alt":"Storybookによる利用規約ページ用のコンポーネント","index":0}"></p>
<h2 id="ツールやaiによる形式レビューと人による総合的なレビュー">ツールやAIによる形式レビューと人による総合的なレビュー</h2>
<p>アクセシビリティへの配慮は最終的には人の手と目で確認しなければ、本当に必要な対応ができているかを判断することができません。しかし、開発現場では全てを人の手と目で確認することは必ずしも現実的ではありません。</p>
<p>そこで、段階的なレビュープロセスを実施しています。</p>
<ul>
<li><strong>初期確認</strong>
<ul>
<li>AIによる各コンポーネントの実装およびアクセシビリティのレビュー</li>
<li>既存の実装と類似している箇所の事前自動チェック</li>
</ul>
</li>
<li><strong>ページ作成時</strong>
<ul>
<li>ブラウザのアクセシビリティチェックツールによる確認</li>
</ul>
</li>
<li><strong>最終確認</strong>
<ul>
<li>VoiceOverやPC Talkerを用いた実機での確認</li>
<li>ジョブメドレー独自のルール、実際の使用感も含めた人によるレビュー</li>
</ul>
</li>
</ul>
<p>この仕組みによりレビューコストを減らしつつ、<strong>より本質的なアクセシビリティの課題に取り組める</strong>ようにしています。</p>
<h3 id="aiによるレビュー">AIによるレビュー</h3>
<p>ジョブメドレーではAIによるプルリクエストのレビューを行っています。</p>
<p>アクセシビリティ観点だけでなく、開発初期段階におけるレビューとして、既存実装との整合性や単純な実装ミスのチェックはAIに任せています。<br>
この結果、一定の精度をクリアしたものを人間のレビューに回すことになり、負荷を減らしています。</p>
<p><img __ASTRO_IMAGE_="{"src":"review-with-claude-ai.png","alt":"Claude AIによるPRレビュー","index":0}"></p>
<h3 id="ブラウザのアクセシビリティツールによるチェック">ブラウザのアクセシビリティツールによるチェック</h3>
<p>ブラウザ拡張ツールの<a href="https://chromewebstore.google.com/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd">axe DevTools®</a>を用いて、ページのアクセシビリティチェックを実施しています。</p>
<p>これにより基本的なアクセシビリティ課題は自動的に検出することができ、一定の品質を担保できるようにしています。</p>
<p><img __ASTRO_IMAGE_="{"src":"review-using-axe-devtools.png","alt":"ブラウザaxe DevTools®によるチェック","index":0}"></p>
<h3 id="利用規約ページに合わせた人によるレビュー">利用規約ページに合わせた人によるレビュー</h3>
<p>AIやツールによる自動チェックでは対応しきれない部分について、人によるレビューを行っています。<br>
アクセシビリティに可能な限り配慮しつつ、ジョブメドレー固有のユーザビリティ要件や使用感も担保できる状態を目指してレビューします。<br>
自動化できる部分は事前にAIやツールでチェックしているため、人によるレビューではより本質的で高度な判断が必要な部分に集中することができます。</p>
<p><img __ASTRO_IMAGE_="{"src":"review-with-human.png","alt":"人間によるPRレビュー","index":0}"></p>
<h3 id="voiceoverpc-talkerを用いた実機確認">VoiceOver・PC Talkerを用いた実機確認</h3>
<p>実機での読み上げ、タップ操作、キーボード操作の確認を行います。</p>
<p>開発者用の検証ツールと実機では、読み上げや操作に差異が起きる場合があります。<br>
利用者を想定して、実際のPCやスマホ上でも問題のない表現になっているかを改めて確認しています。</p>
<p>サービス体験をより良いものにするために、ウェブサービスは人間だけでなく機械が読みやすい文章にする必要があります。これを<strong>マシンリーダビリティ</strong>と呼び、読み上げツールがより適切なアウトプットを行うことができ、ユーザ体験が向上します。<br>
アクセシビリティに配慮した開発を行う中で読み上げツールに対応した表現になるようにしています。マシンリーダビリティも担保できる形になっています。</p>
<p><img __ASTRO_IMAGE_="{"src":"review-using-tools.png","alt":"iPhoneのVoiceOver/WindowsPCのPC Talkerによる確認","index":0}"></p>
<hr>
<p>アクセシビリティに配慮したウェブサービスを提供することで、人間だけでなく読み上げツールやAIなどの機械にも扱いやすいサービスを作ることができます。</p>
<h1 id="今回の学び">今回の学び</h1>
<p>利用規約の刷新を行う中で様々な気づきがありました。</p>
<p>アクセシビリティに配慮できているウェブサービスであるかどうかは、実際に画面がどのようにHTMLとしてマークアップされるかによって決まります。<br>
そのため、コンポーネント作成段階からアクセシビリティに配慮した開発を進めていくことで、実際の画面を作り込むときのチェックが行いやすくなり、実装者とレビュワーのそれぞれが注力すべき箇所がより明確になります。</p>
<h2 id="コンテンツページのアクセシビリティ">コンテンツページのアクセシビリティ</h2>
<p>静的コンテンツページでの表示はシンプルですが、ただ純粋に文章を羅列するだけではなく、適切なマークアップが必要になります。
適したHTML表現を行うことで、アクセシビリティに配慮したセマンティックなWebページを作ることができます。</p>
<p>今回、一般的なページの開発ではあまり見かけないHTMLタグも使用しています。</p>
<table><thead><tr><th>HTMLタグ</th><th>説明</th></tr></thead><tbody><tr><td><a href="https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/small"><code><small></code></a>: <br>附随コメント要素</td><td>HTML4.1では小さい文字を表現するものでしたが、HTML5ではスタイルの表現とは独立して、著作権表示や法的表記のような、注釈や小さく表示される文を表す仕様に変わっています。</td></tr><tr><td><a href="https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/address"><code><address></code></a>: <br>連絡先要素</td><td>個人、団体、組織の連絡先情報を提供していることを示しています。</td></tr></tbody></table>
<p>利用規約の各要素に明確に意味を持たせることを意図して使用しています。</p>
<h2 id="wcag21-aaaへの配慮">WCAG2.1 AAAへの配慮</h2>
<p>ジョブメドレーではWCAG2.1の適合レベルAおよびAAに配慮した、Webサービス作りを行っています。
今回の利用規約の刷新において、結果的にWCAG 2.1 AAA(<a href="https://waic.jp/translations/WCAG21/">日本語版: WCAG 2.1</a>)にも一部配慮したページになりました。</p>
<table><thead><tr><th>基準名</th><th>達成基準</th><th>対応内容</th></tr></thead><tbody><tr><td><a href="http://waic.jp/translations/WCAG21/#section-headings">達成基準 2.4.10</a>: <br>セクション見出し</td><td>セクション見出しを用いて、コンテンツが整理されている。</td><td>各セクションで<code><h1>~<h6></code>タグを使用して、内容や項目を明確に示す見出しを作成</td></tr><tr><td><a href="https://waic.jp/translations/WCAG21/#reading-level">達成基準 3.1.5</a>: <br>読解レベル (<strong>※達成途中</strong>)</td><td>固有名詞や題名を取り除いた状態で、テキストが前期中等教育レベルを超えた読解力を必要とする場合は、補足コンテンツ又は前期中等教育レベルを超えた読解力を必要としない版が利用できる。</td><td>文章全体として、簡潔な文章となっており、前期中等教育レベルでおおよそ読解できる状態</td></tr></tbody></table>
<p>※ 今回の利用規約刷新では、達成基準 3.1.5 読解レベルを目指していますが、まだ完全に平易なものにはなっておらず改善の余地があります。<br>
この達成基準を目指して改善を重ねていければと思います。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回の利用規約刷新では、「<strong>多くの人にとって読みやすい</strong>」利用規約ページとなるように、これまで以上に、丁寧なアクセシビリティに配慮したページを作成しました。</p>
<p>また、開発チームにもさまざまな知見が溜まりました。<br>
アクセシビリティに配慮した開発に終わりはなく、今後も続いていきます。<br>
ジョブメドレーでは全てのチームがアクセシビリティに配慮したプロダクト作りを行い、これまで以上に「<strong>誰でも使える</strong>」ジョブメドレーを目指して行きます。</p>
<h1 id="we-are-hiring">We Are Hiring!</h1>
<p>最後まで読んでいただきありがとうございます!</p>
<p>メドレーでは一緒にプロダクト開発をしていただけるエンジニア・デザイナー・ディレクターを募集中です。<br>
少しでも興味を持っていただけましたら、ぜひカジュアル面談でお待ちしています!</p>
<ul>
<li><a href="https://www.medley.jp/jobs/">🤝 募集一覧</a></li>
<li><a href="https://pitta.me/uratotsu/medley">🗣️ カジュアル面談</a></li>
</ul>
<p>「<a href="https://developer.medley.jp/entry/2025/08/15/20250815/">Medley Summer Tech Blog Relay</a>」 16 日目は、医療プラットフォーム本部の山田さんの記事です!</p>
- 数字でみるメドレーのAI活用 現在地点とこれからhttps://developer.medley.jp/entry/2025/09/08https://developer.medley.jp/entry/2025/09/08この記事は「MEDLEY Summer Tech Blog Relay」11 日目の記事です。
はじめに
こんにちは、メドレーでAI推進している人材プラットフォーム本部 VPoE の倉林(@terukura)です。
前回の記事「AI fo...Mon, 08 Sep 2025 00:00:00 GMT<p><strong>この記事は「<a href="https://developer.medley.jp/entry/2025/08/15/20250815/">MEDLEY Summer Tech Blog Relay</a>」11 日目の記事です。</strong></p>
<p><img __ASTRO_IMAGE_="{"src":"image.png","alt":"サムネ","index":0}"></p>
<h1 id="はじめに">はじめに</h1>
<p>こんにちは、メドレーでAI推進している人材プラットフォーム本部 VPoE の倉林(<a href="https://x.com/terukura">@terukura</a>)です。</p>
<p>前回の記事「<a href="/entry/2025/04/25/112655/">AI for All - 全社でAI活用を推進する取り組み</a>」でお伝えした通り、メドレーでは「AI for All」を合言葉に、全社的なAI活用を推進しています。あれから約4ヶ月が経過し、プロダクト開発チームにおけるAI活用は大きく前進しました。</p>
<p>本記事では、メドレーのプロダクト開発におけるAI活用の現在地を、具体的な数値とともに振り返ってみます。</p>
<p>AI活用に関する発表では、制度としての一人n万円の支援制度、全社Cursor/Claude Max Plan導入、全社でn億円の投資などマクロな数字が注目されがちですが、本記事ではなるべく実際にどのツールをどれくらい使っているのか、具体的な利用状況を公開している例は意外と少ないため、皆様の参考になれば幸いです。</p>
<p>1〜2週間ごとに新しい技術やツールが登場する昨今、最適解はないと感じています。各社の状況や文化に合わせ変化に強い組織を作ることが重要だと考えています。あくまで一つの事例として、メドレーの取り組みをご紹介します。</p>
<h1 id="メドレーのプロダクト開発におけるai活用の方針">メドレーのプロダクト開発におけるAI活用の方針</h1>
<p>メドレーでは、以下の3つの方針を軸にAI活用を進めています。</p>
<h2 id="1-ツールに縛られない柔軟な選定">1. ツールに縛られない柔軟な選定</h2>
<p>特定のAIツールやサービスに固執せず、AI技術やモデルの進化に合わせて最適なツールを選定しています。例えば、コーディング支援ツールだけでも複数のオプションを並行して検証し、用途や開発者の好みに応じて使い分けています。</p>
<h2 id="2-継続的なモニタリングと改善">2. 継続的なモニタリングと改善</h2>
<p>導入して終わりではなく、日々の利用状況や生産性向上の効果を定量的にモニタリングし、PDCAサイクルを回しています。月次でROIを評価し、効果が薄いツールは見直しを行います。</p>
<h2 id="3-開発プロセスへの深い統合">3. 開発プロセスへの深い統合</h2>
<p>GitHub ActionsでのAIレビュー自動化、監視/アラートへのAI組み込みなど、開発プロセスの各段階にAIを統合しています。これにより、エンジニアが意識せずともAIの恩恵を受けられる環境を構築しています。</p>
<h1 id="現在メドレーで利用検証中の主なaiツールサービス">現在メドレーで利用・検証中の主なAIツール・サービス</h1>
<p><strong>2025年9月時点</strong> で、メドレーでは以下のAIツール・サービスを主に活用しています。</p>
<ul>
<li><strong>コーディング支援ツール</strong>
<ul>
<li>Claude Code、Cursor、Devin、Codex、GitHub Copilot、Gemini CLI</li>
</ul>
</li>
<li><strong>AIモデルプロバイダー</strong>
<ul>
<li>Google Vertex AI、Azure OpenAI、AWS Bedrock、Anthropic API、OpenAI API、BytePlus ModelArk</li>
</ul>
</li>
<li><strong>ワークフロー・自動化</strong>
<ul>
<li>Dify、n8n</li>
</ul>
</li>
<li><strong>コードレビュー支援</strong>
<ul>
<li>Claude Code Action、CodeRabbit、GitHub Copilot Review、Devin Review</li>
</ul>
</li>
<li><strong>チャット・汎用AI</strong>
<ul>
<li>ChatGPT Team、Claude Team 、Gemini</li>
</ul>
</li>
<li><strong>その他のツール</strong>
<ul>
<li>Langfuse、NotebookLM、Dia、Perplexity、Snyk、Mastra</li>
</ul>
</li>
</ul>
<h1 id="数値でみる主な利用状況">数値でみる主な利用状況</h1>
<p><img __ASTRO_IMAGE_="{"src":"ai-usage-20250908.png","alt":"AI Usage Summary","index":0}"></p>
<ul>
<li>Claude CodeについてはパワーユーザーはMax Plan、その他の利用者向けにはVertex AI経由で提供</li>
<li>GPT-5の登場により徐々にCodex CLIの利用も増加傾向(TeamPlan/ProPlan)</li>
<li>利用ModelとしてはClaude Sonnetの割合が高い、次点でOpus・GPT(Claude Max Plan ユーザー除く)</li>
<li>Difyについては徐々に全従業員が触れ合うツールになりつつある、Model+Prompot+フローの検証利用としても拡大中</li>
<li>トータルで350-400万円/月 くらいがコスト観点での現在地</li>
</ul>
<p>生産性への影響、FourKeysの変化などはまた次の機会に詳細を共有させていただければと思っています。</p>
<h1 id="今後の展望">今後の展望</h1>
<h3 id="ai駆動開発ai-driven-developmentへの移行">AI駆動開発(AI-Driven Development)への移行</h3>
<p>AIが開発の中心となる新しいパラダイムへの移行を目指します。</p>
<ul>
<li>自然言語での要件定義から実装までの自動化</li>
<li>AIエージェントの本格導入・開発</li>
<li>AIによるUI/UX設計・プロトタイピング</li>
<li>AIによるアーキテクチャ設計の提案・レビュー</li>
<li>CI/CDパイプラインとの深い統合</li>
<li>QA自動化</li>
<li>パフォーマンス最適化の自動実行</li>
<li>インシデント対応・管理の自動化</li>
</ul>
<p>今回は主にプロダクト開発におけるAI活用の現在地点を利用状況を元に振り返ってみました。
<strong>「同じような利用状況の方」、「もっと活用している方」、「まだまだ未活用で活用方法を聞きたい方」</strong> など
ぜひ詳細など情報交換させていただければと思います。</p>
<p>直近はMEDLEY AI CLOUDの発表や、今回のMEDLEY Summer Tech Blog RelayでのAI関連の記事など
AI関連の発信もどしどし行っていますので、ぜひ興味をもっていただければと思います。</p>
<ul>
<li><a href="https://medley-cloud.com/">MEDLEY AI CLOUD</a></li>
<li><a href="https://zenn.dev/medley/articles/26a8a5d2c8175f">MEDLEY Summer Tech Blog Relay:組織で育てるAI活用テスト設計の仕組み</a></li>
<li><a href="https://zenn.dev/medley/articles/claude-code-context-warning-guide">MEDLEY Summer Tech Blog Relay:Claude Codeで「Context left until auto-compact: 15%」が出たときの対処法</a></li>
<li><a href="https://developer.medley.jp/entry/2025/09/01/">MEDLEY Summer Tech Blog Relay:データ戦略グループにおけるcontext engineeringの取り組み</a></li>
</ul>
<p>明日の「MEDLEY Summer Tech Blog Relay」の記事は、デザイナーの近藤さんです!</p>
<h1 id="were-hiring">We’re hiring!</h1>
<p>メドレーでは一緒に働く仲間を大募集しています。
<strong>AI と共に成長したいエンジニアの方</strong> 、ぜひお話させてください!</p>
<p>カジュアル面談も実施しております。話だけでも聞いてみたい!ちょっと雑談してみたい!でも構いませんので、お気軽にお問い合わせください!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>医療エンジニアリング領域盛り上がっています!メドレーについてお話します!</p>
<div class="remark-link-card-plus__container">
<a href="https://pitta.me/matches/YpnGRBGYqYtR" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メドレーのエンジニア組織・AI活用についてお話します!</div>
<div class="remark-link-card-plus__description">「メドレーのエンジニア組織・AI活用についてお話します!」- 倉林 昭和さん</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://pitta.me/static/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">pitta.me</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.cloudinary.com/meety-inc/image/upload/v1754536828/meety/prod/ogp_images/ba6e9twkq3ecg9ld9vz2.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>AI
- データ戦略グループにおけるcontext engineeringの取り組みhttps://developer.medley.jp/entry/2025/09/01https://developer.medley.jp/entry/2025/09/01この記事は メドレー夏のブログリレー 2025 6 日目の記事です。
はじめに
こんにちは、医療プラットフォーム本部データ戦略グループの安東です。
データ戦略グループでは、データ基盤の構築から可視化、分析、ダッシュボード作成まで担い、デー...Mon, 01 Sep 2025 00:00:00 GMT<p><strong>この記事は <a href="https://developer.medley.jp/entry/2025/08/15/20250815/">メドレー夏のブログリレー 2025</a> 6 日目の記事です。</strong></p>
<p><img __ASTRO_IMAGE_="{"src":"image.png","alt":"サムネ","index":0}"></p>
<h1 id="はじめに">はじめに</h1>
<p>こんにちは、医療プラットフォーム本部データ戦略グループの安東です。
データ戦略グループでは、データ基盤の構築から可視化、分析、ダッシュボード作成まで担い、データ活用を促進することで事業成長と医療ヘルスケアの未来に貢献することをミッションにしています。
そのため、日々データを扱う業務をしているのですが、最近チームでこんな議論がありました。</p>
<p>「AI を用いてデータ分析する際にテーブルのメタデータだけでなく、ビジネスロジックや過去の分析知見、議論の文脈まで理解させることが大切だよね。」</p>
<p>皆さんのデータアナリスト組織でも、AI を活用したデータ分析で同じような課題を感じることはないでしょうか。
当社もデータ分析の生産性を高めるため、Cursor や ClaudeCode といった AI ツールを積極的に活用しています。</p>
<p>その過程で、データアナリストに新たな価値提供の機会が広がっていると感じており、その鍵となるのが「 <strong>context engineering</strong> 」と考えています。</p>
<p><strong>context engineering</strong> とは、 AI がタスクを達成できるように、適切な知識・ツール・求める出力形式を提供することです。(参考:<a href="https://blog.langchain.com/the-rise-of-context-engineering/">The rise of “context engineering”</a>)</p>
<p>今回は、「Analytics Knowledge as Code」という形で、分析における <strong>context engineering</strong> の取り組みについてお話しします。
なお、Analytics Knowledge as Code の考えは、AWS Summit で紹介されていた「<a href="https://pages.awscloud.com/rs/112-TZM-766/images/AWS-57_Development_AWS-Summit-JP-2025.pdf">AI Agent 時代のソフトウェア開発の型 〜 Everything as Code で叡智を伝える 〜</a>」の内容を参考にしています。</p>
<h1 id="ai-によって変わる分析の風景">AI によって変わる分析の風景</h1>
<p>生成 AI の登場で、SQL の記述、前処理、集計、可視化といったデータ分析作業の多くが効率化できるようになりました。</p>
<p>では、すべての分析業務が AI に置き換えられるでしょうか?</p>
<p>少なくとも現時点では、分析業務にはグラデーションがあり、AI に置き換え可能な領域とそうでない領域に分かれると思います。</p>
<p>下図のように、分析要求の複雑性によって、データ分析における AI と人間の役割が分かれてきています。</p>
<p><img __ASTRO_IMAGE_="{"src":"contextengineering.png","alt":"データ戦略グループ","index":0}"></p>
<p>単純なデータ抽出やレポート作成は、BI ツールによるセルフサービスで十分です。やや複雑な要件も、AI Agent で対応できつつあります。</p>
<p>しかし、複雑性の高い分析では<strong>ステークホルダーと議論しながら曖昧な要件を明確にし、分析の定義、解決する問いのディスカッションを通じて、意思決定を支援</strong>することが求められます。</p>
<p>事業部と認識を揃えつつ、丁寧な合意形成も欠かせません。</p>
<p>この領域のデータアナリストには、<strong>課題を主体的に定義し、その内容に即した最適な分析アプローチを設計する力が、核心的な価値として求められるようになってきています。</strong></p>
<h2 id="データ分析は過去現在のコンテキストが重要">データ分析は過去・現在のコンテキストが重要</h2>
<p>ここで重要なのは、企業での分析は一度きりの独立したタスクではないということです。過去の分析結果や現在の議論状況に依存する性質を持っています。</p>
<p>例えば、解約率の分析を行う際には、単に数値の変化を見るだけでは不十分です。</p>
<p><strong>過去の解約要因、顧客セグメントの特性、競合サービスの動向、外部環境の変化</strong>といったコンテキストがあってこそ、意味のある洞察が得られます。</p>
<p>一例ですが、メドレーのような医療ヘルスケア領域では、以下のような特殊な文脈が重要になるケースもあります:</p>
<ul>
<li>診療報酬改定: 制度変更が事業指標に与える影響</li>
<li>季節性: 疾病の流行期や健康診断シーズンなど医療特有のパターン</li>
<li>規制環境: 関連省庁のガイドラインの変更等</li>
<li>業界慣習: 医療における事業者の意思決定プロセスの変化</li>
</ul>
<p>こうした<strong>蓄積された文脈(コンテキスト)があってこそ、質の高い分析が可能になります</strong> 。</p>
<p>しかし、モデルは自動的に賢くなっていく一方で、このコンテキストの整備は人間に委ねられています。</p>
<p>リソースは有限なので、求められる品質やビジネス要件を満たして期日までに意思決定を行うためには、AI が効率的に活用できるよう適切なコンテキストを整備することが不可欠です。</p>
<h2 id="analytics-knowledge-as-codeなぜ今この考え方が重要なのか">Analytics Knowledge as Code:なぜ今、この考え方が重要なのか</h2>
<p>そのため、分析ナレッジを「コード化」する Analytics Knowledge as Code の取り組みを始めています。その背景には 3 つの課題があります。</p>
<p><strong>1. 分析による意思決定過程のナレッジ共有の課題</strong></p>
<p>多くの分析組織では、事業部に専属のデータアナリストが配置される体制を取っているケースが多いと思います。
この体制では、各事業の<strong>データ構造の知識や成り立ち、過去の分析インサイト</strong>が、一人のデータアナリストに集中しがちです。
当社でもデータ分析チーム全体として、共通知見を蓄積することに課題があります。</p>
<p><strong>2. AI には人間向けドキュメントだけでは不十分</strong></p>
<p>社内 Wiki や分析ドキュメントは人間が読むことを前提としていますが、<strong>AI が効率的に参照・活用するには構造化された情報が必要</strong>です。
特に重要なのは、コンテキストエンジニアリングにおいて、AI に広範囲を探索させるよりも、<strong>探索させる空間を狭くして、その空間内の情報の質を高めるアプローチの方がより期待に沿った応答を返してくれる</strong>という点です。
そのため、データ分析における AI が参照するナレッジは共通化して、品質の高い状態を管理する価値が高いと考えています。</p>
<p><strong>3. 再利用性と保守性の担保</strong></p>
<p>ナレッジは日々更新されていくため、継続的に更新し、信頼できる状態を担保する必要があります。
ソフトウェア開発のように、<strong>バージョン管理、レビュープロセス、継続的な改善</strong> を分析ナレッジにも適用することで、組織全体の分析品質を向上させることが必要になります。</p>
<h2 id="コンテキストをコードとして管理する手法">コンテキストを「コード」として管理する手法</h2>
<p>Analytics Knowledge as Code は、データ分析における知識やノウハウを、ソフトウェア開発のコードと同様に管理・共有する考えです。</p>
<p>管理する方法として、<strong>GitHub リポジトリを活用したナレッジ管理</strong>により、以下の要素を実現します。</p>
<p>データカタログやプロンプトエンジニアリングでは難しい領域をカバーします。</p>
<table><thead><tr><th>項目</th><th>内容</th></tr></thead><tbody><tr><td><strong>分析ナレッジの保守</strong></td><td>分析結果のテンプレート化による標準化、バージョン管理による変更履歴の追跡</td></tr><tr><td><strong>ビジネスロジックの反映</strong></td><td>対象の分析がどのようなビジネスモデル、ロジックなのか理解させる</td></tr><tr><td><strong>お手本 SQL や分析手法の例示</strong></td><td>AI にゼロから判断させずに、参考になる SQL や分析パターンを参照させる</td></tr><tr><td><strong>過去分析の知識</strong></td><td>過去の分析議論過程や意思決定の内容を参照させる</td></tr></tbody></table>
<h2 id="analytics-knowledge-as-code-のイメージgithub-リポジトリに分析ナレッジ蓄積">Analytics Knowledge as Code のイメージ:GitHub リポジトリに分析ナレッジ蓄積</h2>
<p><img __ASTRO_IMAGE_="{"src":"contextengineering_02.png","alt":"文中イメージ","index":0}"></p>
<p>私たちは分析プロジェクトごとに、上図のような構成で、ナレッジを GitHub リポジトリで管理する取り組みを始めています。</p>
<p>これにより次回の分析では、過去のナレッジをベースに、条件の検討や定義のズレによるコミュニケーションエラーを減らすことができています。</p>
<p>今後、メンバーに変更があった際にも、過去の分析上の判断根拠を理解した上で分析に取り組めるようになることを目指しています。</p>
<h2 id="mcp-を活用したコンテキスト補完">MCP を活用したコンテキスト補完</h2>
<p>ナレッジリポジトリと MCP を組み合わせることで、AI が分析時に過去の知見を参照できるようにしています。</p>
<p>この取り組みにおいて重要なのは、「人間が決めて、AI が分析を実行する」という以下のような役割分担です</p>
<table><thead><tr><th>項目</th><th>内容</th></tr></thead><tbody><tr><td><strong>問題定義</strong></td><td>データアナリストがステークホルダーと協力して課題を明確化</td></tr><tr><td><strong>分析実行</strong></td><td>AI が SQL 生成、前処理、基本統計の処理、可視化といった分析ワークフローの実施</td></tr><tr><td><strong>結果解釈</strong></td><td>データアナリストが主導し、AI が集計結果から解釈を支援</td></tr><tr><td><strong>意思決定</strong></td><td>最終的な判断は必ず人間が行う</td></tr></tbody></table>
<p>このような役割分担を実現するために、AI が効率的に動作するための<strong>意思決定過程の言語化と構造化された文脈の共有</strong>が不可欠になります。</p>
<p>そのため、私たちは以下の要素も体系的に管理することを目指しています:</p>
<table><thead><tr><th>項目</th><th>内容</th></tr></thead><tbody><tr><td><strong>ドメイン知識</strong></td><td>医療業界特有の制約や業界慣習</td></tr><tr><td><strong>ステークホルダー情報</strong></td><td>各事業部のビジネス戦略、ロードマップ、意思決定軸</td></tr><tr><td><strong>過去のプロジェクトの意思決定履歴</strong></td><td>会議の議事録や議論の文脈</td></tr><tr><td><strong>データ定義と品質ルール</strong></td><td>テーブル構造やデータの具体的な使い方</td></tr></tbody></table>
<p>これにより、データアナリストが持つドメイン知識を組織の資産として活用し、より質の高い分析を効率的に行えるようになることを想定しています。</p>
<h1 id="ai-とデータアナリストの役割分担">AI とデータアナリストの役割分担</h1>
<p>AI はデータ集計やクロス集計といった処理を得意とし、膨大な情報を効率的に整理することができます。</p>
<p><strong>一方で、「何を解くべきか」という問いを立てたり、仮説検証の結果をどう意思決定につなげるかといった部分は、人間だからこそ担える役割</strong>です。</p>
<p>単純な集計や事象確認は AI に任せることでスピードと効率を得られますが、複雑なテーマでは過去の分析や議論、外部環境を踏まえて設計し、ステークホルダーと議論しながら最適な進め方を探る必要があります。</p>
<p>ときには分析にかけるコストと成果を比較し、データ分析以外の手段を選ぶ判断も求められます。</p>
<p><strong>つまり、AI は「効率的に分析を実行する力」、データアナリストは「問いを設定し意思決定へ橋渡しする力」を発揮することで、それぞれが補い合いながら最大の価値を生み出していく</strong>と考えます。</p>
<h1 id="ai--人間のベストな協働を目指して">AI × 人間のベストな協働を目指して</h1>
<p>Analytics Knowledge as Code は、こうした新時代のデータアナリストに求められる能力を組織レベルで実現するための取り組みです。</p>
<p><strong>LLM のモデルは今後も性能が向上していきますが、コンテキストの整備は組織が担う必要があります。</strong></p>
<p>AI エディタや GitHub Copilot などのツールにより、従来はエンジニアの専門領域だったコードやリポジトリの管理の障壁は大幅に下がっていると感じています。</p>
<p><strong>「なぜそれが必要なのか」「どう組織の価値に繋がるのか」</strong> を理解し、積極的に取り組む姿勢があれば、どのようなデータ分析組織でも始められると思います。</p>
<p>AI が正しく・早くデータ分析を遂行するための context engineering の整備が、これからのデータ組織に求められる役割の一つと言えるでしょう。</p>
<p>メドレーでは今後も、医療・ヘルスケア領域における課題解決に向けて、こうした AI × データ分析の発展に取り組んでいきます。</p>
<p>同じような取り組みを検討されている組織の方々と、ぜひ知見を共有できればと思います。</p>
<h1 id="were-hiring">We’re hiring!</h1>
<p>メドレーの医療プラットフォーム本部のデータ戦略グループでは、AI 時代の新しいデータ分析にチャレンジしたいデータアナリスト・データエンジニアを募集しています。Analytics Knowledge as Code の実践や、医療ヘルスケア領域でのデータ活用にご興味をお持ちの方は、ぜひお気軽にお声がけください!</p>
<h2 id="参考">参考</h2>
<div class="remark-link-card-plus__container">
<a href="https://blog.langchain.com/the-rise-of-context-engineering/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">The rise of "context engineering"</div>
<div class="remark-link-card-plus__description">Header image from Dex Horthy on Twitter.
Context engineering is building dynamic systems to provide the right information and tools in the right format such that the LLM can plausibly accomplish the task.
Most of the time when an agent is not performing reliably the underlying cause is that the</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://blog.langchain.com/content/images/size/w256h256/2026/03/App.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">blog.langchain.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://blog.langchain.com/content/images/2025/06/GtRmoOqaUAEXH2i.jpeg" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://pages.awscloud.com/rs/112-TZM-766/images/AWS-57_Development_AWS-Summit-JP-2025.pdf" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">pages.awscloud.com</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=pages.awscloud.com" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">pages.awscloud.com</span>
</div>
</div>
</a>
</div>
<p>Medley Summer Tech Blog Relay 7 日目は、人材プラットフォーム本部の山邉さんの記事です!</p>
- メドレー新卒エンジニア研修2025https://developer.medley.jp/entry/2025/08/29/104150https://developer.medley.jp/entry/2025/08/29/104150はじめに
こんにちは。医療プラットフォーム本部 プラットフォーム開発室の島谷です。メドレーでは 2019 年から新卒エンジニアの採用を続けており、毎年新卒向けエンジニア研修を実施しています。
本記事では、2025 年に実施した新卒研修の設計...Fri, 29 Aug 2025 03:15:50 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは。医療プラットフォーム本部 プラットフォーム開発室の島谷です。メドレーでは 2019 年から新卒エンジニアの採用を続けており、毎年新卒向けエンジニア研修を実施しています。
本記事では、2025 年に実施した新卒研修の設計思想とプログラムの全体像をご紹介します。メドレーで働くことに関心のある学生の方はもちろん、私たちの開発文化に興味のあるエンジニアの方にも、メドレーという会社の雰囲気が伝わる内容になれば幸いです。</p>
<h1 id="新卒研修の目的">新卒研修の目的</h1>
<p>私たちが大切にしているのは、単なるスキルの習得にとどまらず、「課題にどう向き合うか」「どのように学び、成長していくか」といった姿勢やマインドセットを身につけてもらうことです。</p>
<p>新卒研修のミッションは、エンジニアとして必要な基礎技術と、問題解決に欠かせない考え方を習得し、配属後に高い成果を発揮するための土台を築くことにあります。研修では「技術的な基礎力」と「問題解決力」を重点的に養い、配属後すぐに開発を進められる状態を目指します。</p>
<p>もっとも、研修期間だけで一人前のエンジニアになることは難しく、だからこそ長いキャリアを通じて自ら成長し続ける姿勢が欠かせません。そうした背景があるからこそ、私たちはスキル以上に「成長し続けるための姿勢やマインドセット」を大切にしています。</p>
<h1 id="研修の全体像">研修の全体像</h1>
<p>今年の新卒研修は、大きく 4 つのステップで構成されています。
まず社会人として必要なビジネススキルとマインドを身に付け、技術的な基礎を作り、そのうえで実務としての開発を体験し、最後に成果発表会で振り返りを行い今後の成長へつなげる。学びを段階的に深めていく一連の流れを設計しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./schedule.png","alt":"","index":0}">
<em>期間と研修全体の流れ</em></p>
<h2 id="hc-研修外部研修">HC 研修、外部研修</h2>
<p>人事部が実施する内製の HC(Human Capital) 研修と、外部研修を実施しました。HC 研修では、社会人に必要な基本動作(報連相や PDCA)やマインド(ニーズを理解しニーズに応える)の基本を習得してもらい、外部研修では、より体験的な演習を通じて社会人としての自覚を持つきっかけとしました。</p>
<h2 id="基礎研修">基礎研修</h2>
<p>エンジニアに求められる 横断的な基礎知識の習得、深く学び続ける姿勢の醸成、チームで成果を出すためのコミュニケーション能力の向上 を目的としています。
フロントエンド(TypeScript/React)、バックエンド(Ruby on Rails)、インフラやミドルウェア(データベース、AWS)など幅広い領域を実践形式で扱いました。</p>
<p>加えて、CTO・VPoE による事業説明や、メドレーでエンジニアとして働くうえでの心構えを学ぶセッションも実施。QA チームやデザインチームからは、それぞれの取り組みを共有してもらい、組織全体での開発の在り方についても学んでもらいました。今年は新たな取り組みとして、AI 活用に関する講義も行っています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./vpoe_intro.jpg","alt":"","index":0}">
<em>VPoE 山﨑さんによる講義の様子</em></p>
<h2 id="事業部-ojt開発-ojt">事業部 OJT、開発 OJT</h2>
<p>事業部 OJT では、複数の事業部でビジネスの理解を深め、現場業務を体験します。現場社員とのコミュニケーションを通じて、エンジニアへの期待やその期待の裏側にある状況の一端を理解し、顧客志向を育みます。</p>
<p>開発 OJT では基礎研修で身につけた知識やスキルを実際の開発現場で活かすフェーズです。既存プロダクトの開発チームに加わり、実務としての開発を体験します。</p>
<h2 id="成果発表会">成果発表会</h2>
<p>研修の集大成として、これまでの学びや開発 OJT での取り組みを整理し発表してもらいました。</p>
<h1 id="メンター制度">メンター制度</h1>
<p>研修を支える仕組みとして、今年も一人ひとりにメンターをつける「メンター制度」を実施しました。研修本体での学びを補い、成長をより加速させることを目的としています。</p>
<p>研修期間中は毎日、日報を書いてもらっています。その日取り組んだことや学んだことを弊社の行動原則(<a href="https://www.medley.jp/team/culture.html">Our Essentials</a>)に基づき整理し、読み手を意識した文章にまとめることがルールです。日報はメンターが必ず目を通し、毎日フィードバックを行いました。やりとりの内容は、技術的な疑問点への対応だけでなく、課題への向き合い方や物事の捉え方といった長期的な成長につながる視点を与えることを意識してもらっています。短期的な知識の提供ではなく、将来にわたって役立つ考え方を養うことを重視しました。</p>
<p>さらに、週次では 1on1 を実施し、振り返りの場を設けました。目標や課題を言語化し、メドレーの行動規範の観点から自らの振る舞いを点検。翌週に試す具体的な一手へと落とし込むことで、実践的な成長サイクルを回しました。もちろん、真面目な内容だけでなく、砕けた相談も自由にできる場とし、安心して取り組める環境づくりも大切にしています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./mentor.jpg","alt":"","index":0}">
<em>日報に対するフィードバックの様子</em></p>
<h1 id="研修の詳細">研修の詳細</h1>
<h2 id="hc-研修外部研修-1">HC 研修、外部研修</h2>
<p>HC 研修では、社会人に求められるビジネスマナーやキャリアマインド、ロジカルシンキング、コミュニケーション能力などのポータブルスキルを学びました。座学だけでなく、ワークショップを行うことでメドレーグループの他職種の新卒とも交流も行いました。最終日はチーム対抗の寸劇を行うことで、一人ひとりの発言力を高め、一体感を生み出すことができました。</p>
<p>外部研修では、講師が顧客役を演じたり、チームを企業に見立てた競争環境で取り組んだりと、実際の現場に近いハイプレッシャーな状況を体験しました。その中で、適切なコミュニケーションやチームワークの大切さを実感し、成果につなげるプロセスを学ぶことを目指しました。</p>
<h2 id="基礎研修-1">基礎研修</h2>
<p>基礎研修は、実際にプロダクト開発に携わっているメドレーのエンジニアが、教材づくりから講師までを担当します。単なる知識の習得にとどまらず、判断の背景や根拠を言語化し、他者に伝わる形で残すことまで含めて学ぶことを大切にしています。</p>
<p>全体を通して繰り返し伝えられていたことは、「課題を終わらせること自体を目的にしない」ということです。わからない点をそのままにせず、ドキュメント・実装・計測結果を往復しながら自分の言葉で理解し直す。この姿勢を身につけることが、研修を通じて最も大事にしている部分です。</p>
<p>ここでは主要な研修内容を紹介します。</p>
<h3 id="typescript-react">TypeScript/ React</h3>
<p>この研修では、フロントエンド開発の基礎として TypeScript と React を扱いました。TypeScript の型システムなどの言語仕様を理解したうえで、React のコンポーネント設計、状態管理、フック などの基礎を学びます。</p>
<p>演習課題は Pull Request 形式で提出し、背景や選択肢、判断理由を文章にまとめたうえでコードレビューを受ける流れとしました。これにより、単に技術を学ぶだけでなく、技術を深く掘り下げる姿勢や、GitHub を使った開発フロー、レビューを通じたテキストコミュニケーションを実践的に身につけることを狙いとしています。</p>
<h3 id="データベースdb">データベース(DB)</h3>
<p>次に、データベースの基礎を幅広く学びました。テーブル設計や ER 図、リレーションの理解に加え、実際にクエリを書く演習として「クエリ 100 本ノック」に挑戦。WHERE や JOIN といった開発や分析で頻出する構文を身につけました。さらに、クエリ実行計画の読み方やインデックス設計など、パフォーマンスチューニングにも取り組みました。長期的な保守性や変更容易性、パフォーマンスを意識しながらデータベースを扱うための基礎を、実践的に学ぶ内容です。</p>
<h3 id="アプリケーション開発実践">アプリケーション開発実践</h3>
<p>フロントエンドとバックエンドをつなげた実践的な開発演習として、Rails on Rails で API を実装し、React で SPA(Single Page Application)を構築する研修を行いました。フードデリバリーサービス(Uber Eats のようなアプリ)を題材に、決められた要件定義から設計、開発、テストまでを一気通貫で経験します。</p>
<p>ここでは、TypeScript/React 研修で身につけた基礎を応用しながら、実際にプロダクトとして動くアプリケーションを作り上げることに挑戦しました。仕様の整理からテスト設計までを通じて、フロントエンドとバックエンドを横断的に理解し、開発に必要な実践力を養うことが目的です。</p>
<h3 id="docker--aws">Docker / AWS</h3>
<p>最後に、インフラ領域の基礎として Docker と AWS を扱いました。メドレーでは AWS を中心に利用しているため、実際にアプリケーションを AWS 上にデプロイし、動作させるところまでを体験します。可用性やスケーラビリティを考慮したシステム構築に加え、ログ、監視、アラート設定といった運用面も含めて一通り設定。さらに簡易的に負荷をかけ、スケールアウトやアラートが正しく機能するかを検証しました。単に作るだけでなく、安定的に運用できる状態にすることの重要性を学ぶハンズオンとなっています。</p>
<h2 id="事業部-ojt開発-ojt-1">事業部 OJT、開発 OJT</h2>
<p>基礎研修を終えたあとは、現場社員から研修を受ける事業部 OJT に取り組みました。事業部によって取り組む内容は異なりますが、市場環境やミッション、今後の方向性を理解することから始まり、Sales や Customer support の業務の一部を体感します。</p>
<p>その後は、既存プロダクトの開発現場に加わり、実際の開発を体験する OJT に取り組みました。ここでは、抽象度の高い課題に対して自ら問題を整理し、解決に向けた手順を考え、周囲と協調しながら前に進める力が求められます。</p>
<p>開発の過程では、リリースに至るまでの一連のプロセスを経験します。わからないことを適切に言語化して周囲に伝え、協力を得ながら課題を乗り越えることも重要なポイントです。</p>
<p>今年の OJT で取り組まれていたタスクをいくつかピックアップしてみます。</p>
<ul>
<li>デザインシステムの MCP サーバー構築</li>
<li>顧客向け管理画面の UI/UX 改善</li>
<li>電子カルテにおける書類作成機能の改善</li>
<li>患者向け通知機能の UX 改善</li>
</ul>
<p>タスクを進める中では、先輩エンジニアから様々な観点でフィードバックを受けます。これまで自分になかった視点や考え方に触れることで、新卒メンバーは大きな学びを得ていた様子でした。</p>
<h2 id="成果発表会-1">成果発表会</h2>
<p>研修の締めくくりとして行ったのが成果発表会です。ここでは、研修や OJT を通じて学んだこと、自身の成長・変化を自分の言葉で整理し、周囲に伝える力を養うことを目的としています。単に振り返るだけでなく、学びを言語化して共有することで、個人の経験をチーム全体の資産へと昇華させる場でもあります。</p>
<p>当日は、CTO や VPoE、メンター、配属先のマネージャーなど、研修に関わった多くのメンバーが参加しました。
新卒の皆さんは緊張した面持ちでしたが、先輩たちから温かいフィードバックや応援の言葉が寄せられ、会場は和やかな雰囲気に包まれていました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./presentation_session.jpg","alt":"","index":0}">
<em>各 VP の皆様を始め、関わった方々からコメントいただきました</em></p>
<h1 id="まとめ">まとめ</h1>
<p>研修という限られた期間の中で、一人前のエンジニアとしての力をすべて身につけることは容易ではありません。だからこそ、この期間を通じて「これから成長していくための土台」を築いてもらうことを大切にしてきました。本番はここから始まります。</p>
<p>新卒の皆さんには、これからは仲間として実際の開発現場で活躍しながら、さらに経験を積み重ねていくことを期待しています。私たちのミッションである 「医療ヘルスケアの未来をつくる」 を実現するために、それぞれが力を発揮し、共に歩んでいけることを心から楽しみにしています。</p>
<h2 id="were-hiring">We’re hiring!</h2>
<h3 id="サマーインターン参加者募集中">サマーインターン参加者募集中!</h3>
<p>メドレーでは、この夏に開催するエンジニア向けサマーインターンの参加者を募集しています。
実際のプロダクト開発を体験しながら、エンジニアとしての成長につながる機会を提供します。</p>
<p>「まずは話だけ聞いてみたい」「雰囲気を知りたい」という方に向けて、カジュアル面談も実施しています。少しでも興味をお持ちの方は、お気軽にご応募ください。</p>
<p>募集はこちら</p>
<div class="remark-link-card-plus__container">
<a href="https://open.talentio.com/r/1/c/medley/pages/110787" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">open.talentio.com</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=open.talentio.com" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">open.talentio.com</span>
</div>
</div>
</a>
</div>
<h3 id="中途採用も積極募集中">中途採用も積極募集中!</h3>
<p>また、エンジニアをはじめとした中途採用も積極的に行っています。
ヘルスケアの未来を一緒につくる仲間を幅広く募集していますので、ぜひこちらもご覧ください。</p>
<p>中途採用の募集一覧はこちら</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- 「最高のパフォーマンスを引き出す! メドレーエンジニアの働く環境を徹底解剖」https://developer.medley.jp/entry/2025/08/29https://developer.medley.jp/entry/2025/08/29この記事は メドレー夏のブログリレー2025 5日目の記事です。
はじめに
こんにちは!メドレーでDevRelをしている重田です。
突然ですが、転職先を探す時に実際に働く環境や雰囲気って気になりますよね?
この記事では、メドレーのエンジニ...Fri, 29 Aug 2025 00:00:00 GMT<p><strong>この記事は <a href="https://developer.medley.jp/entry/2025/08/15/20250815/">メドレー夏のブログリレー2025</a> 5日目の記事です。</strong></p>
<p><img __ASTRO_IMAGE_="{"src":"43.png","alt":"サムネ","index":0}"></p>
<h1 id="はじめに">はじめに</h1>
<p>こんにちは!メドレーでDevRelをしている重田です。</p>
<p>突然ですが、転職先を探す時に実際に働く環境や雰囲気って気になりますよね?<br>
この記事では、メドレーのエンジニアが普段どんな環境で仕事をしているのかを写真メインでご紹介します!</p>
<p>「メドレーのオフィスで働きたい!」「働くイメージが持てた!」と思っていただけたら嬉しいです🙌</p>
<p>では、早速オフィスツアースタートです📣</p>
<h1 id="-私たちが働くオフィス">🏢 私たちが働くオフィス</h1>
<p>私たちのオフィスは日比谷線 六本木駅直結の六本木ヒルズ森タワーの12階と13階にあります。
詳細は以下記事でご紹介しています🙋♀️</p>
<p><a href="https://note.com/medley/n/n512dfa49c206">メドレーオフィス(13階)へのアクセスと入館方法のご案内</a><br>
<a href="https://note.com/medley/n/n28987e0c1bdf">[六本木ヒルズ森タワー]メドレーのオフィスを初披露</a></p>
<h2 id="️外観">🏙️外観</h2>
<p>六本木ヒルズ森タワーは54F建て、高さ238メートルの超高層オフィスビルです。
上を見上げる高さで、たまに見ると「おお〜〜都会だ!」となり背筋が伸びます。笑
<img __ASTRO_IMAGE_="{"src":"14.jpg","alt":"森ビル","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"15.jpg","alt":"オフィス入口","index":0}"></p>
<h2 id="13階">🏥13階</h2>
<p>エンジニアの中でも医療プラットフォームのプロダクト開発エンジニアやコーポレートITのメンバーが13階で働いています。</p>
<h3 id="エントランス受付">エントランス(受付)</h3>
<p>13階のエントランスは清潔感のある白の壁に温かみのある木目調の床です。
<img __ASTRO_IMAGE_="{"src":"08.jpg","alt":"13階エントランス","index":0}"></p>
<h3 id="憩いスペース">憩いスペース</h3>
<p>約100席と、広々したスペースです。テーブル席の他、ソファもあるのでゆったりと過ごせます。
<img __ASTRO_IMAGE_="{"src":"10.jpg","alt":"13階憩い","index":0}"></p>
<p>カウンターには賞状などが飾られています。
<img __ASTRO_IMAGE_="{"src":"09.jpg","alt":"13階憩いカウンター","index":0}"></p>
<h2 id="12階">🏥12階</h2>
<p>エンジニアの中でも人材プラットフォームのプロダクト開発エンジニアが12階で働いています。</p>
<h3 id="憩いスペース-1">憩いスペース</h3>
<p>こちらは今年の春に増設されたスペースです!<br>
ランチだけではなく、1on1やチームミーティングでも大活躍のスペースです。
<img __ASTRO_IMAGE_="{"src":"23.jpg","alt":"全体1","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"24.jpg","alt":"全体2","index":0}"></p>
<p>ボックス席にはディスプレイが付いており、チームミーティングの際に役立ちます。
<img __ASTRO_IMAGE_="{"src":"18.jpg","alt":"ファミレス席","index":0}"></p>
<p>ゆっくりしたいときはこの席がおすすめです🍵
<img __ASTRO_IMAGE_="{"src":"11.jpg","alt":"憩い一人席","index":0}"></p>
<p>東京タワーを眺めて仕事ができるのは六本木、そして12階に位置する特権です🗼
<img __ASTRO_IMAGE_="{"src":"25.jpg","alt":"憩い作業","index":0}"></p>
<p>憩いスペース入口には弊社プロダクトを体験できるコーナーがあります。
<img __ASTRO_IMAGE_="{"src":"04.jpg","alt":"プロダクトカウンター","index":0}"></p>
<p>また、自動販売機2台・コーヒー販売機2台・食品自動販売機1台・冷蔵庫が完備されています☕️
<img __ASTRO_IMAGE_="{"src":"05.jpg","alt":"自販機","index":0}"></p>
<p>小腹が減った時の救世主🙏コンビニに行くのが面倒な時に大活躍です!
<img __ASTRO_IMAGE_="{"src":"06.jpg","alt":"オフィスコンビニ","index":0}"></p>
<p>持参/購入したお弁当を温められるのも嬉しいポイント💡
<img __ASTRO_IMAGE_="{"src":"07.jpg","alt":"電子レンジ","index":0}"></p>
<h2 id="共通">🏥共通</h2>
<h3 id="会議室">会議室</h3>
<p>会議室は50室以上あり、来客専用・社内専用の会議室が分かれています。
<img __ASTRO_IMAGE_="{"src":"19.jpg","alt":"会議室","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"26.jpg","alt":"会議室","index":0}"></p>
<h1 id="メドレーのカルチャークリーンデスクポリシーに込められた想い">✨メドレーのカルチャー「クリーンデスクポリシー」に込められた想い</h1>
<p>メドレーが大切にしているカルチャーのひとつに「クリーンデスクポリシー」があります。これはデスクの上には仕事道具以外置かない、帰宅時にはモニタ・キーボード・マウス以外は全て片付けるというルールのことです。</p>
<p>「クリーンデスクポリシー」は代表の瀧口が<a href="https://www.amazon.co.jp/gp/product/B009SXCWA6?ie=UTF8&camp=247&creative=1211&creativeASIN=B009SXCWA6&tag=httpktakiameb-22&linkId=c4c114d8a24d079f76347beaf489320b">「佐藤可士和の超整理術」</a>という本から影響を受け、創業時から大切にしているカルチャーです。なぜ「クリーンデスクポリシー」が大切なのか、瀧口が社内で共有している資料から引用してご紹介します。</p>
<blockquote>
<p>整理整頓には、視点が必要です。複雑なものであれば、それぞれの因果関係を見抜き、何が大切なのかの優先順位をつけることが必要です。「頭で理解しているつもりのこと」と「身体に染み付いて実践できること」には大きな隔たりがあります。実践への近道は、たった一つの象徴的なことを徹底することではないでしょうか。<br>
例えば、この記事<a href="https://toyokeizai.net/articles/-/153624?page=2">「日本電産が赤字会社を速攻で再生できたワケ」</a>の「組織体質を変える一番の早道は」を読んでみてください。整理・整頓・清潔・躾(自主的な)を徹底して、営業回数を増やせば成功すると書いています。<br>
そもそも仕事机は、仕事をするためのものです。クリーンデスクというのは整理整頓ができる組織であることの象徴です。このような背景で、クリーンデスクポリシーは僕にとって、大切にしたい考え方なのです。</p>
</blockquote>
<p>実際、社員の机は常に整理されています。
<img __ASTRO_IMAGE_="{"src":"20.jpg","alt":"slack01","index":0}"></p>
<p>また、過去にも「クリーンデスクポリシー」に触れているのでぜひご覧いただけると嬉しいです!<br>
<a href="https://note.com/medley/n/n28987e0c1bdf">[六本木ヒルズ森タワー]メドレーのオフィスを初披露</a></p>
<h1 id="スキルアップ支援やライフステージに合わせた柔軟な働き方を実現">📚スキルアップ支援やライフステージに合わせた柔軟な働き方を実現</h1>
<p>この記事では主にエンジニアの制度をご紹介します!</p>
<h2 id="支援制度">支援制度</h2>
<p><strong>社内勉強会支援</strong><br>
社内で勉強会を開催する際の飲食補助が出ます!</p>
<p><strong>書籍購入の補助</strong><br>
会社の費用で購入可! ※資産管理の観点から電子書籍は除く</p>
<p><strong>資格取得支援</strong><br>
AWS認定試験・Ruby技術認定試験を会社の費用で受験可! ※一定条件あり</p>
<p><strong>カンファレンス参加費用の補助</strong><br>
RubyKaigi などの外部カンファレンスへ会社の費用で参加可!</p>
<p>制度を利用する際は、以下のようにSlackで申請をしています。</p>
<p><img __ASTRO_IMAGE_="{"src":"01.png","alt":"slack01","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"02.png","alt":"slack02","index":0}"></p>
<p><strong>希望PC・モニター・アーロンチェアの貸与</strong><br>
エンジニアの方には希望PCとモニターが貸与されます。
基本的にはスペックも自由に選べます🙆♀️</p>
<p>湾曲型モニター
<img __ASTRO_IMAGE_="{"src":"27.jpg","alt":"モニター","index":0}"></p>
<p>また、希望者にはアーロンチェアも用意しています。<br>
<img __ASTRO_IMAGE_="{"src":"21.jpg","alt":"アーロンチェア","index":0}"></p>
<h2 id="働く環境">働く環境</h2>
<p>よくある質問をもとにご紹介します!</p>
<p>Q. 勤務時間は決まっていますか?<br>
A. 開発職では裁量労働制を採用しており、多くの社員は事業部の稼働時間に合わせて10:00~19:00で勤務しています。<br>
一方で、子育て中の社員については、勤務時間中に一時的に離席するなど、個々の状況に応じた柔軟な体制をとっています。</p>
<p>Q. 出社とリモートの頻度はどのぐらいですか?<br>
A. エンジニア/デザイナーの場合は週2出社、週3リモートをベースにしています。<br>
こちらも勤務時間と同様、体調やご家庭の事情など、柔軟に対応できる体制になっています。</p>
<p>Q. キャリアロールはどのようになっていますか?<br>
A. 「スペシャリスト」と「マネジメント」の2つに大別されます。事業責任者のロールも存在しますが、役職ではなく、一つのロールとして位置づけています。</p>
<p>Q. 担当するプロダクトは決まっていますか?<br>
A. エージェントからのご紹介時・スカウトをお送りする際などにプロダクトを限定している場合もありますが、基本的には選考を通じて皆様のご希望や志向、スキルスタックなども合わせてご提案・ご相談しながら決めています。</p>
<h1 id="イベントでも大活躍のオフィス">イベントでも大活躍のオフィス</h1>
<p>メドレーでは定期的にイベントを開催したり、オフィスを他社様や外部コミュニティのイベント会場として提供したりしています。</p>
<p>こちらは5月にリンケージ社とヘンリー社と開催したイベント、<br>
<a href="https://medley.connpass.com/event/350496/">HealthTech Meetup</a>の様子です。
<img __ASTRO_IMAGE_="{"src":"42.jpg","alt":"HealthTech Meetup","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"32.jpg","alt":"HealthTech Meetup","index":0}"></p>
<p><a href="https://roppongirb.connpass.com">Roppongi.rb</a>、<a href="https://omotesandorb.connpass.com/">Omotesando.rb</a> の会場としても定期的にご利用いただいています🙌
<img __ASTRO_IMAGE_="{"src":"29.png","alt":"Roppongi.rb","index":0}">
<img __ASTRO_IMAGE_="{"src":"30.png","alt":"Omotesando.rb","index":0}"></p>
<h1 id="最後に">🍀最後に</h1>
<p>最後までご覧いただきありがとうございました!<br>
メドレーで働くイメージはつきましたでしょうか!?</p>
<p>直近では、以下のイベントを開催予定です!<br>
少しでもご興味のある方は、ぜひイベントを機にオフィスにいらしてください!ご参加お待ちしております✨</p>
<p><strong>9/4(木)19:30</strong><br>
<a href="https://omotesandorb.connpass.com/event/366283/">Omotesando.rb#113</a>
<img __ASTRO_IMAGE_="{"src":"40.png","alt":"magicpod","index":0}"></p>
<p><strong>9/5(金)19:00</strong><br>
<a href="https://trident-qa.connpass.com/event/362140/">【有料プランユーザー限定×オフライン】MagicPodユーザーミートアップ</a>
<img __ASTRO_IMAGE_="{"src":"33.png","alt":"magicpod","index":0}"></p>
<h1 id="were-hiring">We’re hiring</h1>
<p>最後まで読んでいただきありがとうございます!<br>
メドレーでは、一緒に働く仲間を大募集中です👫<br>
少しでも興味を持っていただけましたら、ぜひカジュアル面談でお待ちしています!</p>
<p><strong>🤝 <a href="https://www.medley.jp/jobs">募集一覧</a></strong><br>
<strong>🗣️ <a href="https://pitta.me/uratotsu/medley">カジュアル面談</a></strong></p>
<p>Medley Summer Tech Blog Relay 6 日目は、医療プラットフォーム本部の安東(Andō)さんの記事です!<br>
それでは良い週末をお過ごしください!</p>
- MEDLEY Summer Tech Blog Relayhttps://developer.medley.jp/entry/2025/08/15/20250815https://developer.medley.jp/entry/2025/08/15/20250815
こんにちは!メドレーでDevRelをしている重田です。
今年も暑い日が続いていますがいかがお過ごしですか?
メドレーでは夏企画として『MEDLEY Summer Tech Blog Relay』と題して、ブログリレーを開催します!
8/2...Fri, 22 Aug 2025 00:00:00 GMT<p><img __ASTRO_IMAGE_="{"src":"./thumbnail4.png","alt":"サムネイル","index":0}"></p>
<p>こんにちは!メドレーでDevRelをしている重田です。
今年も暑い日が続いていますがいかがお過ごしですか?</p>
<p>メドレーでは夏企画として『MEDLEY Summer Tech Blog Relay』と題して、ブログリレーを開催します!
8/25(月)〜9/26(金)まで毎日異なるメンバーが技術やエンジニアリング、個人開発など幅広いテーマでテックブログを公開していきます!</p>
<p>本記事にて毎日ブログを追記更新していくので、ぜひお楽しみください✨
※土日祝を除く</p>
<h2 id="ブログリレーカレンダー">ブログリレーカレンダー</h2>
<h3 id="️第1週825829">🗓️第1週(8/25~8/29)</h3>
<ul>
<li>Day1:<a href="https://qiita.com/yujittttttt/items/ab1df699da03044eab2f">SRE屋のひとりごと(玉井)</a></li>
<li>Day2:<a href="https://zenn.dev/medley/articles/26a8a5d2c8175f">組織で育てるAI活用テスト設計の仕組み(小島)</a></li>
<li>Day3:<a href="https://qiita.com/morishio/items/912cb910e54d4654fa6e">画像配信時にサイズ変換 🔄 〜Lambda@Edgeを添えて〜(森川)</a></li>
<li>Day4:<a href="https://zenn.dev/medley/articles/claude-code-context-warning-guide">【初心者向け】Claude Codeで「Context left until auto-compact: 15%」が出たときの対処法(中村)</a></li>
<li>Day5:<a href="https://developer.medley.jp/entry/2025/08/29/">「最高のパフォーマンスを引き出す! メドレーエンジニアの働く環境を徹底解剖」(重田)</a></li>
</ul>
<h3 id="️第2週9195">🗓️第2週(9/1~9/5)</h3>
<ul>
<li>Day6:<a href="https://developer.medley.jp/entry/2025/09/01/">データ戦略グループにおけるcontext engineeringの取り組み(安東)</a></li>
<li>Day7:<a href="https://zenn.dev/medley/articles/6d175c038d140c">Trocco で App Store Connect のデータを BigQuery に連携する(山邊)</a></li>
<li>Day8:<a href="https://zenn.dev/medley/articles/c92f7aae40f853">事業部からQAエンジニアにジョブチェンジした話(内堀)</a></li>
<li>Day9:<a href="https://zenn.dev/medley/articles/opensearch_with_rails">全文検索導入に向けた設計と実践ガイド【Rails×OpenSearch】(山下)</a></li>
<li>Day10:<a href="https://zenn.dev/medley/articles/7e8a68406483c5">百聞百見は一験にしかず ──WACATEで見えた、知識から実践への道筋(井津)</a></li>
</ul>
<h3 id="️第3週98912">🗓️第3週(9/8~9/12)</h3>
<ul>
<li>Day11:<a href="https://developer.medley.jp/entry/2025/09/08/">数字でみるメドレーのAI活用 現在地点とこれから(倉林)</a></li>
<li>Day12:<a href="https://note.com/rosy_poppy3026/n/nb8a849fbc7cc">電子カルテのプロダクトデザインに向き合って見えてきたこと(近藤)</a></li>
<li>Day13:<a href="https://qiita.com/inamuu/items/0aa90d4b58d43f15227e">Terraformでdotfilesを管理する(稲村)</a></li>
<li>Day14:<a href="https://note.com/shin_is_yay/n/n8ac7fa3d3123">デザインアセットをテンプレート化!Figmaで誰でも手軽に作れる仕組み(進)</a></li>
<li>Day15:<a href="https://developer.medley.jp/entry/2025/09/12/112057/">「誰でも使える」ように、ジョブメドレーの求職者向け利用規約を刷新します(小泉)</a></li>
</ul>
<h3 id="️第4週916919">🗓️第4週(9/16~9/19)</h3>
<ul>
<li>Day16:<a href="https://zenn.dev/medley/articles/d1cdc872796eb1">Active Job Continuationsがリリースされたので調査してみた(山田)</a></li>
<li>Day17:<a href="https://zenn.dev/oginoshikibu/articles/c9c2fb44886e86">【GraphQL Ruby】型定義から値解決するまでの道のり(川原)</a></li>
<li>Day18:<a href="https://developer.medley.jp/entry/2025/09/18/184343/">複雑な歯科電子レセプトを可視化するWebビューアを作ってみた(平林)</a></li>
<li>Day19:<a href="https://zenn.dev/medley/articles/bb2637c6ca89fc">フロントエンド学習にPlaywright MCPを使う(池田)</a></li>
</ul>
<h3 id="️第5週922926">🗓️第5週(9/22~9/26)</h3>
<ul>
<li>Day20:<a href="https://zenn.dev/medley/articles/f6cf78b5e08672">生成AIと進める、制約ある環境でのテストコード導入(山下)</a></li>
<li>Day21:<a href="https://developer.medley.jp/entry/2025/09/24/110824/">痒い所に手が届く!Step Functions によるトイル削減(山田)</a></li>
<li>Day22:<a href="https://developer.medley.jp/entry/2025/09/25/143628/">ライブラリ更新リスクを分離する週2回リリース戦略 〜開発とQAの実践〜(桶谷・小島)</a>__</li>
<li><strong>NEW🎉Day23:<a href="https://developer.medley.jp/entry/2025/09/26/095917/">運用されつづけるブランドをつくるために — MEDLEY AI CLOUD リブランディング「苦悩と葛藤の 8 ヶ月」の舞台裏 —(前田)</a></strong></li>
</ul>
<h2 id="were-hiring">🍉We’re hiring!</h2>
<p>メドレーでは、「医療ヘルスケアの未来をつくる」仲間を大募集しています!
少しでも興味をお持ちいただけましたら、ぜひ、カジュアル面談にお越しください🙌</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/team/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メドレーで働く | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの組織文化や募集要項をご紹介します</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://medley-inc.notion.site/medley-engineer-entrance-book" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Medley Engineer Entrance Book</div>
<div class="remark-link-card-plus__description">この度は株式会社メドレーに興味をお寄せいただきありがとうございます。本資料は、メドレーへの転職をご検討いただいている皆様に、当社をより深くご理解いただくために作成いたしました。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://medley-inc.notion.site/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">medley-inc.notion.site</span>
</div>
</div>
</a>
</div>
- BigQuery Data Transfer Service を通して学ぶ Google 広告のデータモデルhttps://developer.medley.jp/entry/2025/07/24/120228https://developer.medley.jp/entry/2025/07/24/120228Thu, 24 Jul 2025 03:02:28 GMTBigQueryGoogle 広告DTSデータモデリング
- SRE NEXT 2025 参加レポートhttps://developer.medley.jp/entry/2025/07/17/102438https://developer.medley.jp/entry/2025/07/17/102438はじめに
こんにちは! 医療プラットフォーム本部 プラットフォーム開発室 SRE グループの山田です。
医療機関向け SaaS である CLINICS の安定稼働とシステム信頼性の向上に取り組んでいます。
メドレーは 7 月 11 日、12...Thu, 17 Jul 2025 01:24:38 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは! 医療プラットフォーム本部 プラットフォーム開発室 SRE グループの山田です。
医療機関向け SaaS である <a href="https://clinics-cloud.com/">CLINICS</a> の安定稼働とシステム信頼性の向上に取り組んでいます。</p>
<p>メドレーは 7 月 11 日、12 日に<a href="https://toc-ariake.jp/">TOC 有明</a>(東京都江東区)で開催された <a href="https://sre-next.dev/2025/">SRE NEXT 2025</a> に LOGO Sponsor として協賛しました!
SRE NEXT は、信頼性に関するプラクティスに深い関心を持つエンジニアのためのカンファレンスです。
医療プラットフォーム本部 SRE グループは発足して間もないため、他社のさまざまな挑戦や SRE プラクティスを学ぶべく、私を含め数名のエンジニアが参加し、たくさんの方々と交流させていただきました。
本レポートでは、SRE NEXT 2025 の会場や企業ブースの様子、そして発表の内容についてご紹介します。</p>
<h1 id="会場の様子">会場の様子</h1>
<p>SRE NEXT 2025 は、オンラインとオフラインのハイブリッド形式で開催されました。
50 を超える企業の協賛のもと、740 名もの SRE エンジニアが現地に参加しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./venue.jpg","alt":"","index":0}">
<em>広々とした会場</em></p>
<p>企業ブースも多数出展しており、SRE にまつわるアンケートや SRE プラクティスの紹介など非常に面白かったです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./timee_booth.jpg","alt":"","index":0}">
<em>株式会社タイミー様のブースにお邪魔させていただきました</em></p>
<p>2 日目の最後には懇親会も催され、様々な SRE エンジニアの方と交流することができました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./social_gathering.jpg","alt":"","index":0}">
<em>懇親会の様子</em></p>
<h1 id="発表の様子">発表の様子</h1>
<p>どのセッションも大変興味深かったのですが、特に印象深かった下記のセッションについてご紹介します。</p>
<ul>
<li>Day1: SRE 不在の開発チームが障害対応と 向き合った 100 日間</li>
<li>Day2: 伴走から自律へ:形式知へと導く SRE イネーブリングによる、プロダクトチームの信頼性オーナーシップ向上</li>
<li>Day2: Four Keys から始める信頼性の改善</li>
</ul>
<h2 id="sre-不在の開発チームが障害対応と-向き合った-100-日間-loglass-勝丸真さん">SRE 不在の開発チームが障害対応と 向き合った 100 日間 (Loglass 勝丸真さん)</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/61e71ecbdfc341e1ba5361cda143eb9f" title="SRE不在の開発チームが障害対応と 向き合った100日間 / 100 days dealing with issues without SREs" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p><em>引用元: <a href="https://speakerdeck.com/shin1988/100-days-dealing-with-issues-without-sres">speakerdeck.com</a></em></p>
<p>カスタマーサクセスチームからのフィードバックをきっかけに、インシデント対応の改善に取り組んだ際の課題と、その解決策が紹介されました。</p>
<p>開発チームが障害対応に直面する中で、エンジニアによって対応品質にばらつきがあり、カスタマーサクセスチームから「障害対応がスムーズに進まない」「全体の体制や連絡手段が曖昧」といったフィードバックを受けるという課題があったとのことです。特に、あるエンジニアは単純な修正作業のみを行う一方で、別のエンジニアは影響範囲の特定やカスタマーサクセスへの回避策伝達まで含めた包括的な対応を実施するなど、対応者によって大きな差が生じていました。</p>
<p>そこで、専任のインシデントコマンダーチームを編成し、全エンジニアによるローテーション制から専門化による品質向上を図るアプローチへ切り替えたことが説明されました。また、障害対応フローの明確化とシンプル化、インシデントレベルの再定義、外部ツール「Warroom」の導入による自動記録・AI 要約機能の活用についても具体的な手法が紹介されました。
さらに、プロジェクトの推進においてはプロセス整備だけでなく、ビジネスチームやプロダクトチームとの継続的な対話を重視し、「なぜこの変更が必要なのか」という背景を丁寧に共有することで組織全体の理解を深め、技術的なベストプラクティスとビジネス要求のギャップを埋めていく文化づくりの重要性も解説されていました。</p>
<h3 id="所感">所感</h3>
<p>このセッションで特に印象的だったのは、教科書的なベストプラクティスをそのまま適用するのではなく、現場の実情に合わせて大胆な割り切りを行っていた点です。中でも、「全エンジニアがインシデントコマンダーになる」という理想を一旦捨て、あえて属人化を許容して専門チームを作るという判断は、実践が進んでいるからこその現実的な選択だと感じました。</p>
<p>CLINICS SRE でも、理想的なインシデント対応体制を構築しようとする際に、全エンジニアのスキルレベルやモチベーションのばらつきという現実的な課題に直面することがあります。この発表から学んだのは、完璧な体制を目指すよりも、まずは実効性のある仕組みを作り上げることの重要性です。専門チームによる安定した対応基盤があってこそ、その後の全体的なスキル向上や体制の民主化が可能になるのだと理解しました。</p>
<p>また、プロセス整備だけでは解決できない人間関係や組織文化の課題に対し、継続的な対話を通じて理解を深めていく姿勢も非常に参考になりました。CLINICS でも、開発チームや事業部との間で、障害対応時の連携や認識に齟齬が生じることは少なくありません。この発表から学んだ対話を重ねることが重要であるという点は、今後の SRE の取り組みに活かしていきたいと思います。</p>
<p>現場の泥臭い課題に真摯に向き合い、組織として解決策を模索する姿勢は、同じような課題に取り組む SRE チームにとって大きな学びとなる発表でした。</p>
<h2 id="伴走から自律へ形式知へと導く-sre-イネーブリングによるプロダクトチームの信頼性オーナーシップ向上ビズリーチ-佐々木康徳さん">伴走から自律へ:形式知へと導く SRE イネーブリングによる、プロダクトチームの信頼性オーナーシップ向上 (ビズリーチ 佐々木康徳さん)</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/faff8cfeff2b4736b6777c9710f6d30d" title="伴走から自律へ: 形式知へと導くSREイネーブリングによる プロダクトチームの信頼性オーナーシップ向上 / SRE NEXT 2025" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p><em>引用元: <a href="https://speakerdeck.com/visional_engineering_and_design/sre-next-2025">speakerdeck.com</a></em></p>
<p>このセッションでは、WAF 運用をプロダクトチームへイネーブリングするにあたり、SECI モデルに基づいて実践された事例が紹介されました。</p>
<p>具体的には、共同化、表出化、連結化、内面化の 4 つのプロセスを通じて、SRE が持つ暗黙知を段階的にプロダクトチームに移転し、チームのオーナーシップを醸成する具体的な方法が示されました。</p>
<p>この取り組みにより、WAF 運用をプロダクトチームへ移管できただけでなく、プロダクト開発チームのオーナーシップ向上やコミュニケーションの効率化といった効果も得られたと説明されていました。</p>
<h3 id="所感-1">所感</h3>
<p>このセッションで、SECI モデルという概念を初めて知りました。
私自身、暗黙知から他者への暗黙知のプロセスを飛ばしていきなりドキュメント化(表出化)から始めたり、連結化まで進めずに終わってしまったりと、SECI モデルの観点から見ると不完全な取り組みでイネーブリングに失敗した経験があります。そのため、今回の発表は非常に納得感があり、共感を深く覚えました。</p>
<p>今後、CLINICS SRE でも SECI モデルを意識した開発チームへのイネーブリングを進めていきたいと考えています。</p>
<h2 id="four-keys-から始める信頼性の改善dmm-尾崎耕太さん">Four Keys から始める信頼性の改善 (DMM 尾崎耕太さん)</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/25ed35450d4a46a88550d3a6234a0eb7" title="Four Keysから始める信頼性の改善 - SRE NEXT 2025" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p><em>引用元: <a href="https://speakerdeck.com/ozakikota/four-keyskarashi-meruxin-lai-xing-nogai-shan-sre-next-2025">speakerdeck.com</a></em></p>
<p>このセッションは、Four Keys (チーム生産性を可視化することを目的とした指標のこと) を軸に DevOps 文化を作り、その結果としてユーザにとって魅力的で信頼されるプロダクトを提供することを目指す戦略を取ることで、信頼性の改善を進めたという事例と導入効果について紹介されました。</p>
<p>信頼性指標である SLI/SLO やエラーバジェットは、機能開発チームにとって理解しにくく、日々の開発サイクルに組み込みにくいという課題がありました。そこで、より開発プロセスに近く、チームがオーナーシップを持ちやすい Four Keys を採用。これを軸に DevOps 文化を醸成することで、段階的に信頼性を獲得するというアプローチをとったと説明されています。</p>
<p>その結果、Four Keys の数値が改善しただけでなく、プロダクト開発チームが自律的に変化に取り組むようになったという大きな効果があったと紹介されていました。</p>
<h3 id="所感-2">所感</h3>
<p>最も印象的だったのは、「指標の改善が目的ではなく、その先の状態が目標」という考え方です。指標を取るのは気づきを得て、アクションに繋げるためのものであるため、「指標を改善すること」にこだわりすぎない方が良いという話には深く共感できました。数値の向上に注力するあまり、なぜその指標を見ているのかという根本的な目的を見失ってしまうことは、実際の現場でもよく起こりがちな問題です。</p>
<p>CLINICS でも SLO 運用を行っていますが、この考え方は非常に参考になりました。SLO の数値を改善することに集中してしまい、そもそもなぜ SLO を設定しているのか、SLO 運用を通じて何を実現したいのかという本質的な目的を見失わないよう気をつけたいと思います。
SLO 運用をした先に SRE チームが何を目指しているのか、サービスの信頼性向上によってどのような価値をユーザーや事業に提供したいのかという意識を、開発チームにもさらに伝えられるようにしたいと感じました。</p>
<p>また、開発チームがオーナーシップを持って変化に取り組んでいることが重要という点も非常に納得できました。具体的な成果よりも、開発チーム自身が主体的に改善活動を推進していることの方が本質的な価値があるという考え方は、持続可能な改善文化を構築する上で欠かせない要素だと思います。</p>
<p>実践面で特に参考になったのは、単純に Four Keys を導入するのではなく、プロダクト開発チームと対話してアクションに繋げられるように Keys を詳細化している点です。開発者が「なぜ悪化したのか」「どの活動が効果的だったのか」を理解できるよう工夫している取り組みは、実際の現場での指標活用を考える上で非常に実用的なアプローチだと感じました。</p>
<h1 id="さいごに">さいごに</h1>
<p>他社のさまざまな挑戦や SRE プラクティスを学べただけでなく、たくさんの方々と交流することができて刺激的な 2 日間でした。</p>
<p>来年も 7 月 10 日、11 日に<a href="https://toc-ariake.jp/">TOC 有明</a> での開催を予定しているそうです。
メドレーは今後も SRE NEXT だけでなく、他の技術イベントやコミュニティの発展を積極的に支援し、参加、貢献していきます。</p>
<p>運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございました!</p>
<h1 id="メドレーではエンジニアを積極採用中です">メドレーではエンジニアを積極採用中です!</h1>
<p><img __ASTRO_IMAGE_="{"src":"./recruit_eng.jpg","alt":"","index":0}">
<em>エンジニアを積極採用中です</em></p>
<p>メドレーでは、「医療ヘルスケアの未来」を共に創っていく SRE エンジニアを積極的に採用しています。
興味を持たれた方は、以下のリンクより、ぜひカジュアル面談の応募をお願いします。</p>
<p>募集の一覧 <a href="https://www.medley.jp/jobs/">https://www.medley.jp/jobs/</a></p>
<p>※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください</p>
- 高可用性システムにおけるBlue/Greenデプロイメント実装 - 医療システム統合基盤での取り組みhttps://developer.medley.jp/entry/2025/06/30/102900https://developer.medley.jp/entry/2025/06/30/102900こんにちは。医療プラットフォーム本部の日下(@mkusaka)です。
私の所属する統合基盤チームでは、医療プラットフォームの複数のシステムを支えるサービス群を運用しています。
これまではユーザーへの影響を最小限に抑えるため、リリース作業を深...Mon, 30 Jun 2025 01:29:00 GMT<p>こんにちは。医療プラットフォーム本部の日下(<a href="https://github.com/mkusaka">@mkusaka</a>)です。
私の所属する統合基盤チームでは、医療プラットフォームの複数のシステムを支えるサービス群を運用しています。</p>
<p>これまではユーザーへの影響を最小限に抑えるため、リリース作業を深夜や早朝に限定していましたが、その結果として運用チームへの負担増やリリースタイミングの制約といった課題が生じていました。</p>
<p>こうした課題を解決し、日中でも安全かつ段階的にリリースを行うため、統合基盤に Blue/Green デプロイメントを導入しました。今回はその詳細を紹介します。</p>
<h1 id="統合基盤コンポーネントの紹介">統合基盤コンポーネントの紹介</h1>
<p>統合基盤チームでは、患者と医療機関の双方に使われる医療システムの根幹を支える重要な基盤の開発・運用を行っています。
管理するコンポーネントは、「医療機関向け」と「患者向け」の 2 つに分類できます。</p>
<p>「医療機関向け」機能は、<a href="https://pharms-cloud.com/">Pharms</a>、<a href="https://dentis-cloud.com/">Dentis</a>、<a href="https://clinics-cloud.com/">CLINICS</a> などの各サービス間でコミュニケーションのハブとして機能し、イベントの配信を行います。</p>
<p><img __ASTRO_IMAGE_="{"src":"./toB.svg","alt":"","index":0}"></p>
<p>一方、「患者向け」機能としては、<a href="https://clinics-app.com/">総合医療アプリ CLINICS</a>から送られたリクエストを適切なサービスに振り分けるゲートウェイの役割を担っています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./toC.svg","alt":"","index":0}"></p>
<p>このようなアーキテクチャを採用した背景には、医療プラットフォームが複数のプロダクト(Pharms、Dentis、CLINICS など)を統合的に運用する必要があるという背景があります。</p>
<p>医療機関向けには、いずれか一方のシステムの状態に他方が引きずられない構成とすることで、各医療機関の業務システムの可用性を最大限高めるという狙いがあります。</p>
<p>一方、患者向けには、複数の医療機関向けシステムに対して共通のアプリからアクセスできるようにするため、患者情報の一元管理や統一された認証基盤の整備が不可欠です。これにより、患者にとってシームレスな体験を実現し、より効率的で質の高い医療サービスの提供を目指しています。</p>
<p>つまり、統合基盤が管理するこれらのコンポーネントに障害が発生すると、サービス間にまたがる業務だけでなく、患者向けのサービス提供にも影響が及ぶため、高い可用性が求められています。</p>
<h1 id="bluegreen-デプロイメント導入の目的">Blue/Green デプロイメント導入の目的</h1>
<p>統合基盤の各サービスは、AWS ECS(Elastic Container Service)上で運用されています。これまでは、運用のシンプルさからローリングデプロイを採用していました。しかしこの手法では、デプロイが開始されると新バージョンが一斉に展開されてしまうため、問題発生時の影響範囲が広く、迅速なロールバックも難しいという課題がありました。</p>
<p>こうした課題への対策として、Blue/Green デプロイメントを導入することを決定しました。
Blue/Green デプロイメントとは、新旧 2 つの環境(Blue と Green)を用意し、新バージョンを片方に展開した後、徐々にトラフィックを移行していく方法です。これにより、致命的な問題やパフォーマンスの劣化を早期に発見し、迅速なロールバックが可能になります。</p>
<h1 id="bluegreen-デプロイメントの要件と独自実装の選択">Blue/Green デプロイメントの要件と独自実装の選択</h1>
<p>システムを Blue/Green デプロイする際に、大きく分けて 2 つの考慮事項がありました。
1 つ目は 2 つの明確な検証フェーズを設けることです。</p>
<ul>
<li><strong>致命的なエラー検出フェーズ</strong>:新環境に約 10%のトラフィックを流し、システムの安定性を確認。</li>
<li><strong>負荷時のパフォーマンス検証フェーズ</strong>:新環境に約 50%のトラフィックを流し、負荷によるパフォーマンスの劣化を確認。</li>
</ul>
<p>これら 2 つのフェーズを明確に設け、十分な検証時間を取ることで、より高い自信をもってリリース作業を進めることが可能になると考えました。</p>
<p>2 つ目は非同期処理を担当する Worker サービスを新旧の環境それぞれで用意することです。
Worker サービスでは、ジョブが SQS(Simple Queue Service)キューに投入されてから実際に処理されるまでタイムラグがあります。その間に新しい環境へ切り替えが行われると、旧環境のジョブが新環境の Worker によって処理され、データの不整合が起きる可能性があります。このリスクを防ぐため、Blue と Green の環境それぞれに独立した SQS キューを用意し、それぞれのジョブが確実に自環境の Worker で処理を完了する形式を取ることとしました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./split-queue.svg","alt":"","index":0}"></p>
<p>ECS には CodeDeploy を利用した標準的な Blue/Green の段階的デプロイ戦略として、Canary デプロイ(一部のトラフィックで検証後、一気に切り替え)や Linear デプロイ(一定割合ずつ徐々に適用)が提供されています。しかし、私たちが求めるようなトラフィック調整にはこれらの手法が十分に適しておらず、また SQS を分離するための Blue 環境/Green 環境どちらかを判定するような仕組みが整備されていないようだったので、内製化することとしました。</p>
<h1 id="採用した構成">採用した構成</h1>
<p>自前の Blue/Green デプロイを実現するため、以下の構成を採用しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./weight.svg","alt":"","index":0}"></p>
<ol>
<li>ALB(Application Load Balancer)の weighted target groups を利用して、トラフィックを柔軟に調整しています。</li>
<li>ECS 環境は Blue/Green のそれぞれが並行稼働し、SQS は各環境ごとに独立したキューを設けます。</li>
</ol>
<p>新バージョンは Green 環境として起動し、初期状態では Blue 環境(現行バージョン)がすべてのトラフィックを処理します。その後、Green 環境へのトラフィックを 10%、50%と段階的に増やしながら、各段階でメトリクスの監視を行います。
10%の段階で致命的なエラーがないことを確認し、50%の段階で負荷時のパフォーマンスに問題ないと判断されたら、最終的にすべてのトラフィックを Green 環境へ切り替えます(100%)。</p>
<p>また、各種ステップは承認操作やロールバックも含めて CI 上で完結するように整備を行うことで、デプロイの複雑性も抑えています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./deploy.svg","alt":"","index":0}"></p>
<h2 id="実装サンプル">実装サンプル</h2>
<p>Terraform と AWS CLI を組み合わせて実装しています。</p>
<h3 id="alb-の-weighted-routing-設定terraform">ALB の weighted routing 設定(Terraform)</h3>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="hcl"><code><span class="line"><span style="color:#4EC9B0">resource</span><span style="color:#4FC1FF"> "aws_lb_listener_rule"</span><span style="color:#4FC1FF"> "weighted_routing"</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> listener_arn </span><span style="color:#D4D4D4">=</span><span style="color:#D4D4D4"> aws_lb_listener.</span><span style="color:#9CDCFE">https</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">arn</span></span>
<span class="line"></span>
<span class="line"><span style="color:#4EC9B0"> action</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> type </span><span style="color:#D4D4D4">=</span><span style="color:#CE9178"> "forward"</span></span>
<span class="line"><span style="color:#4EC9B0"> forward</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#4EC9B0"> target_group</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> arn </span><span style="color:#D4D4D4">=</span><span style="color:#D4D4D4"> aws_lb_target_group.</span><span style="color:#9CDCFE">app_blue</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">arn</span></span>
<span class="line"><span style="color:#9CDCFE"> weight </span><span style="color:#D4D4D4">=</span><span style="color:#B5CEA8"> 100</span><span style="color:#6A9955"> # 初期状態では Blue が 100%</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#4EC9B0"> target_group</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> arn </span><span style="color:#D4D4D4">=</span><span style="color:#D4D4D4"> aws_lb_target_group.</span><span style="color:#9CDCFE">app_green</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">arn</span></span>
<span class="line"><span style="color:#9CDCFE"> weight </span><span style="color:#D4D4D4">=</span><span style="color:#B5CEA8"> 0</span><span style="color:#6A9955"> # Green は 0%</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> # AWS CLI で weight を動的に調整するため、Terraform での変更を無視</span></span>
<span class="line"><span style="color:#4EC9B0"> lifecycle</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> ignore_changes </span><span style="color:#D4D4D4">=</span><span style="color:#D4D4D4"> [</span><span style="color:#9CDCFE">action</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<h3 id="トラフィック配分の変更aws-cli">トラフィック配分の変更(AWS CLI)</h3>
<p>デプロイ時のトラフィック配分は、AWS CLI を使用して動的に変更します。実際の運用では、Green の weight を指定すると Blue が自動的に <code>100 - Green</code> になるように調整しています:</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6A9955"># Green の weight を指定して Blue/Green の配分を設定</span></span>
<span class="line"><span style="color:#6A9955"># 例:GREEN_WEIGHT=10 の場合、Blue=90、Green=10 に自動計算</span></span>
<span class="line"><span style="color:#DCDCAA">set-weight:</span></span>
<span class="line"><span style="color:#DCDCAA"> @aws</span><span style="color:#CE9178"> elbv2</span><span style="color:#CE9178"> modify-rule</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --rule-arn</span><span style="color:#D4D4D4"> $(</span><span style="color:#DCDCAA">LISTENER_RULE_ARN</span><span style="color:#D4D4D4">) </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --actions</span><span style="color:#CE9178"> '[{</span></span>
<span class="line"><span style="color:#CE9178"> "Type": "forward",</span></span>
<span class="line"><span style="color:#CE9178"> "ForwardConfig": {</span></span>
<span class="line"><span style="color:#CE9178"> "TargetGroups": [</span></span>
<span class="line"><span style="color:#CE9178"> {</span></span>
<span class="line"><span style="color:#CE9178"> "TargetGroupArn": "$(BLUE_TG_ARN)",</span></span>
<span class="line"><span style="color:#CE9178"> "Weight": '</span><span style="color:#569CD6">$$</span><span style="color:#D4D4D4">((</span><span style="color:#DCDCAA">100</span><span style="color:#CE9178"> -</span><span style="color:#D4D4D4"> $(</span><span style="color:#DCDCAA">GREEN_WEIGHT</span><span style="color:#D4D4D4">)))</span><span style="color:#CE9178">'</span></span>
<span class="line"><span style="color:#CE9178"> },</span></span>
<span class="line"><span style="color:#CE9178"> {</span></span>
<span class="line"><span style="color:#CE9178"> "TargetGroupArn": "$(GREEN_TG_ARN)",</span></span>
<span class="line"><span style="color:#CE9178"> "Weight": '</span><span style="color:#D4D4D4">$(</span><span style="color:#DCDCAA">GREEN_WEIGHT</span><span style="color:#D4D4D4">)</span><span style="color:#CE9178">'</span></span>
<span class="line"><span style="color:#CE9178"> }</span></span>
<span class="line"><span style="color:#CE9178"> ]</span></span>
<span class="line"><span style="color:#CE9178"> }</span></span>
<span class="line"><span style="color:#CE9178"> }]'</span></span></code></pre>
<h3 id="デプロイフロー">デプロイフロー</h3>
<p>実際のデプロイでは、以下のような手順で段階的にトラフィックを移行します:</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6A9955"># 1. Green 環境へのデプロイ</span></span>
<span class="line"><span style="color:#DCDCAA">make</span><span style="color:#CE9178"> app-green.deploy</span><span style="color:#CE9178"> TAG=</span><span style="color:#D4D4D4">$(</span><span style="color:#DCDCAA">NEW_VERSION</span><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 2. トラフィックを段階的に移行</span></span>
<span class="line"><span style="color:#DCDCAA">make</span><span style="color:#CE9178"> set-weight</span><span style="color:#CE9178"> GREEN_WEIGHT=</span><span style="color:#B5CEA8">10</span><span style="color:#6A9955"> # 10% を Green へ</span></span>
<span class="line"><span style="color:#DCDCAA">make</span><span style="color:#CE9178"> set-weight</span><span style="color:#CE9178"> GREEN_WEIGHT=</span><span style="color:#B5CEA8">50</span><span style="color:#6A9955"> # 50% を Green へ</span></span>
<span class="line"><span style="color:#DCDCAA">make</span><span style="color:#CE9178"> set-weight</span><span style="color:#CE9178"> GREEN_WEIGHT=</span><span style="color:#B5CEA8">100</span><span style="color:#6A9955"> # 100% を Green へ(切り替え完了)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 3. 問題が発生した場合のロールバック</span></span>
<span class="line"><span style="color:#DCDCAA">make</span><span style="color:#CE9178"> set-weight</span><span style="color:#CE9178"> GREEN_WEIGHT=</span><span style="color:#B5CEA8">0</span><span style="color:#6A9955"> # すべてのトラフィックを Blue へ戻す</span></span></code></pre>
<p>この実装により、段階的なリリースと問題発生時の迅速なロールバックが可能になっています。</p>
<h1 id="導入後の効果">導入後の効果</h1>
<p>Blue/Green デプロイメント導入後、深夜のリリース作業がなくなり、運用チームの負担が大幅に軽減されました。また日中であってもリスクを最小限に抑えたリリースが実現でき、問題が発生した場合にも迅速にロールバックできるようになったため、システムの安定性を犠牲にすることなくリリースを行えるようになりました。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回は、医療システム統合基盤における Blue/Green デプロイメントの導入事例を紹介しました。</p>
<p>医療システムには高い可用性が求められる中、従来のローリングデプロイでは段階的なリリースや迅速なロールバックが困難で、深夜作業が必須となっていました。</p>
<p>Blue/Green デプロイメントの導入により、以下を実現しました:</p>
<ul>
<li>ALB の weighted target groups を活用した段階的トラフィック移行(10%→50%→100%)により品質を確認しながらのリリース進行</li>
<li>Worker サービス用の SQS キューを環境ごとに分離し、データ不整合を防止</li>
<li>深夜作業を廃止し、日中でも安全にリリース作業を実施可能に</li>
</ul>
<p>今後も医療システムに求められる高可用性を維持しながら、開発効率の向上を目指し、継続的な改善を進めていきます。</p>
<h1 id="were-hiring">We’re hiring</h1>
<p>統合基盤チームでは、医療システムの根幹を支える重要な基盤の開発・運用を行っています。</p>
<p>今回紹介したような Blue/Green デプロイメントの実装をはじめ、高可用性が求められるシステムにおいて、技術的な課題に向き合いながら医療現場と患者体験の向上に貢献できる環境があります。</p>
<p>医療の未来をエンジニアリングで支えることに興味がある方のご応募をお待ちしています。</p>
<div class="remark-link-card-plus__container">
<a href="https://open.talentio.com/r/1/c/medley/pages/107444" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">open.talentio.com</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=open.talentio.com" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">open.talentio.com</span>
</div>
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- TSKaigi 2025 参加レポート:新卒2年目エンジニアが感じたTypeScriptの最前線https://developer.medley.jp/entry/2025/05/29/151914https://developer.medley.jp/entry/2025/05/29/151914はじめに
こんにちは! 人材プラットフォーム本部プロダクト統括部プロダクト開発部アカデミー開発グループ所属の城間(シロマ)です。
私は 2024 年 4 月に新卒エンジニアとして入社し、現在はオンライン動画研修サービス「ジョブメドレーアカデ...Thu, 29 May 2025 06:19:14 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは! 人材プラットフォーム本部プロダクト統括部プロダクト開発部アカデミー開発グループ所属の城間(シロマ)です。
私は 2024 年 4 月に新卒エンジニアとして入社し、現在はオンライン動画研修サービス「<a href="https://jm-academy.jp/">ジョブメドレーアカデミー</a>」の新規プロダクト開発に携わっています。</p>
<p>先日、5 月 23 日、24 日に東京都千代田区のベルサール神田にて開催された <a href="https://2025.tskaigi.org/">TSKaigi 2025</a> にメドレーは <strong>Silver Sponsor</strong> として協賛しました!
TypeScript をテーマにしたこの大規模なカンファレンスには、多様なバックグラウンドを持つ TypeScript エンジニアが全国から集結。</p>
<p>惜しくもブースの抽選には外れてしまいましたが、私も含め弊社からは数名のエンジニアが現地参加し、多くの素晴らしい出会いと学びがありました。
今回は、当日の会場の様子や印象に残ったセッション、そして今、私が感じていることを中心にご紹介します!</p>
<h1 id="会場の熱気と型にとらわれないエンジニアたちの交流">会場の熱気と「型にとらわれない」エンジニアたちの交流</h1>
<p>今年の TSKaigi 2025 は 2 日間とも天候に恵まれ、<strong>61</strong> もの企業スポンサーと「TypeScript を扱う型にとらわれないエンジニア」が集結し、会場は終日、熱気と活気に満ち溢れていました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./session.jpeg","alt":"","index":0}">
<em>場内の様子</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./sponsor-board.jpeg","alt":"","index":0}">
<em>スポンサーボード</em></p>
<p>2 日間に渡り、合計 3 会場でセッションや LT が盛んに行われ、時には立ち見の人が出るほどに盛況していました。</p>
<p>参加者同士でテーマに対してトークする <strong>OST(Open Space Technology)</strong> や、夜の豪華な懇親会では、セッションでは聞けないような深掘りした技術の話や、各社の開発文化に関する交流が盛んに行われ、当日はおよそ 50 名以上のエンジニアの方々と色々なお話をさせていただきました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./ost.png","alt":"","index":0}">
<em>OST のテーマ</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./party.jpeg","alt":"","index":0}">
<em>懇親会の様子</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./ts-cake.jpeg","alt":"","index":0}">
<em>TypeScript ケーキ</em></p>
<p>また、今回参加者に配布された公式グッズは、TSKaigi のオリジナル T シャツやトートバッグに加え、各企業が工夫を凝らした遊び心あふれるグッズで盛りだくさんでした。</p>
<p>特に、今回ブース出展のあった <strong>19 社全てを周るスタンプラリー</strong> では、全てのスタンプを集めることで豪華景品が当たる抽選に参加でき、会場の賑わいに一役買っていました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./novelty.jpg","alt":"","index":0}">
<em>公式ノベルティ(一部)</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./stamp-rally-1.jpeg","alt":"","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./stamp-rally-2.jpeg","alt":"","index":0}">
<em>スタンプラリー</em></p>
<h1 id="印象に残ったセッションai-エージェント全盛の時代と-typescript-の可能性">印象に残ったセッション:AI エージェント全盛の時代と TypeScript の可能性</h1>
<p>どのセッションも非常に興味深く、時には理解が追いつかないほど最先端の内容でしたが、特に今の私が行う開発と照らし合わせて印象に残ったセッションをいくつかご紹介します。</p>
<h2 id="day1-ai-coding-agent-enablement-in-typescript">Day1: AI Coding Agent Enablement in TypeScript</h2>
<p><em>引用元: <a href="https://speakerdeck.com/yukukotani/ai-coding-agents-enablement-in-typescript">AI Coding Agents Enablement in TypeScript - Speaker Deck</a></em></p>
<h3 id="概要">概要</h3>
<p>このセッションでは、人間による介入を最小限に抑え、AI エージェントが大規模な作業をどのように「自走」して実行させるかについて深く掘り下げられました。
なぜ自走が難しいのか?その主な要因は AI エージェントが「任意の TypeScript」のようなあまりにも広い「解空間」で動くため、精度が低くなってしまうためです。
すなわち AI エージェントの精度を高めるためには <strong>「可能な限り解空間を絞る」</strong> ことが基本方針であり、会社やプロジェクト固有の規約、ドメイン知識、デザインなどに基づいて適切に制約を与える重要性が何度も強調されていました。
その解空間を絞る具体的なアプローチとして <strong>「コンテキスト注入」</strong> と <strong>「機械的検査とフィードバック」</strong> の 2 つが挙げられました。</p>
<ol>
<li><strong>コンテキスト注入</strong>: ドキュメント、規約、ドメイン知識などを LLM にインプットし、「解空間の定義」を与えること。特に TypeScript コード自体をドキュメントとして育てることが、コンテキスト注入に役立つ。</li>
<li><strong>機械的検査とフィードバック</strong>: LLM が生成したコードが定義された解空間から外れていないか、型チェック、Linter、自動テストといった古典的な手法で検査し、NG の場合はエージェントにフィードバックして解空間へと押し戻すように促す。Linter は次にやるべきアクションを提示しやすく、決定的である点が非常に有効である。</li>
</ol>
<p>また、TypeScript 開発における型の役割についても興味深い見解がありました。現状、型によってコード生成の精度が上がるという定量的な根拠はまだないものの、「解空間に押し戻す」ためのエージェントへの入力(フィードバック)としては役立つ可能性は大いにあるとのこと。AI はまだ高度な型解決が苦手なので、人間がドメインモデルや関数のシグネチャ(型定義)をしっかり書き、その後の実装をエージェントに任せるアプローチが有効であるという話は、非常に現実的で納得感がありました。</p>
<p>最後に、<strong>「エコシステムの未来 - Speed is King」</strong> という言葉が印象的でした。これは、AI エージェントのコード生成が加速すると、開発プロセス全体の速度がボトルネックとなることを示唆しています。具体的には、人間がコードを 30 分かけて書く時代には 1 分程度の静的解析は許容範囲でしたが、AI が同じコードをわずか 30 秒で書くようになると同じ 1 分間の静的解析は AI の作業時間に対して相対的に非常に長く、無視できないボトルネックになるということです。さらにクラウド型エージェントではチャットごとにコンテナが作成される仕組みが主流であり、その際にパッケージマネージャの速度がボトルネックになります。また、AI エージェントによるコード生産量が爆発的に増加すれば、デプロイの機会も増えるためビルド(バンドラー)の速度もボトルネックとして顕在化し、加えて将来的に LLM がコードを生成するタイミング(デコーディング時)ごとに型制約を守ろうとする「制約付きデコーディング」のようなアプローチが進めば、型チェッカーがボトルネックになる可能性も指摘されていました。故に、ツールチェイン全体の高速化が今後より重要になるという示唆は、将来的な開発環境を考える上で非常に重要な視点だと感じました。</p>
<h2 id="day2-ts-特化-cline-プログラミング">Day2: TS 特化 Cline プログラミング</h2>
<p><em>引用元: <a href="https://tskaigi.mizchi.workers.dev/">tskaigi.mizchi.workers.dev</a></em></p>
<h3 id="概要-1">概要</h3>
<p>このセッションでは、LLM を活用した「Cline Agentic Coding」の全般について、特に TypeScript に特化した実践的なプロンプトのコツが紹介されました。
まず冒頭に <strong>うまくいくうまくいくプロンプトのコツ</strong> として、以下が挙げられていました。</p>
<ul>
<li>書きすぎない(再現性ある範囲で詠唱破棄)</li>
<li>執拗に出力例を例示する</li>
<li>両立条件の矛盾を避ける</li>
<li>規模感に合わせて厳しくする</li>
</ul>
<p>また特に強調されていたのは、<strong>テスト駆動開発(Test Driven Development)</strong> の重要性です。コード生成時に対応するユニットテストを常に生成し、コード修正時にはテストがパスすることを確認することで、AI エージェントの自己修復能力を高め、放置しても完成する高品質なコードに繋がるとのことでした。</p>
<p>その他にも、次のような実践的なプロンプトの一例が紹介されていました。</p>
<ul>
<li><strong>コメントによる自己記述</strong>: 各ファイルの冒頭にコメントで仕様を記述することで、再修正時のコード解釈の一貫性を保つ</li>
<li><strong>In Source Testing</strong>: 実装と同じファイルにユニットテストを書くことで、コメント・実装・テストを三位一体で管理する</li>
<li><strong>types.ts にドメイン型を集約</strong>: 中規模以上のプロジェクトでは src/types.ts にドメインモデルを集約し、SSoT(Single Source of Truth)とすることで、ファイル間の整合性を保ち read_file の頻度を減らす</li>
<li><strong>TS + 関数型ドメインモデリング</strong>: 状態の発散を抑えるために class を使わず関数による実装を優先し、代数的データ型でドメインをモデリングする</li>
<li><strong>ファイル配置規則の明記</strong>: モノレポなどのファイル配置規則を明記することで、タスクごとのエージェントの推測コストを減らす</li>
<li><strong>詳細指示を docs/*.md に分割</strong>: 大規模プロジェクトでは無関係な指示によるノイズを減らすために、詳細な指示をドキュメントファイルに分割し、必要に応じて参照させる</li>
<li><strong>カバレッジに基づくテストの自動生成</strong>: vitest でカバレッジを計測し、最もカバレッジが上がるテストコードを AI に考察・追加させる</li>
<li><strong>機械的なマイグレーション</strong>: lodash の削除や類似 API を持つライブラリへの置き換えなど、面倒なマイグレーション作業を AI に自動化させる</li>
<li><strong>URL を読む能力</strong>: URL の内容を読み込ませ、要約・保存させることで、Deep Research 的な挙動を可能にする</li>
</ul>
<p>一方で、<strong>うまくいかないプロンプト</strong> のパターンもいくつか示唆されました。</p>
<ul>
<li><strong>型だけで設計しようとする</strong>: ほぼ確実に無視され、抽象的な設計能力は期待できない</li>
<li><strong>非同期例外処理が下手</strong>: 思考停止気味に try-catch で握り潰しがちで、大規模開発で破綻の原因となる</li>
<li><strong>環境構築が下手</strong>: ゼロショットでの環境構築は発散しやすく、手数が仇となり環境を破壊する可能性がある</li>
<li><strong>モジュールインターフェースが発散</strong>: 実装次第で全て export してしまい、モジュール間の契約が肥大化・破綻する</li>
<li><strong>「ある」のがよくない(チェーホフの銃の法則)</strong>: 無関係なリソースを読み込ませると、それを使うことに固執し、大規模コードではノイズになる</li>
<li><strong>デバッグログを食いすぎる</strong>: 自身が生成したプリントデバッグでコンテキストウィンドウを消費し、デバッグコードを放置しがちである</li>
</ul>
<p>結論として、現状の LLM はコーディングが下手であり、低品質なコードで設計が破綻し自滅する傾向があるとの見解でした。特に、リファクタリングの指針がなく、不要コードの判定やモジュール視点での API 設計が苦手で、ユーザー側でリファクタリングしても元の低品質なコードに書き戻すこともあるという少し厳しい評価が下されていました。
しかし TypeScript と LLM の組み合わせには良い点も多く、GitHub の公開コードが豊富で学習量が多いこと、安全性よりも表現力を選んだピーキーな型システムが自然言語と対応した型のモデリングをしやすいこと、豊富な静的解析手段とユーザー層の厚さがあることなどが挙げられていました。
現時点でのベストプラクティスとしては、PoC/プロトタイプのコード生成(1 ファイル完結 800 行以内が目安)に活用し、人間によるインターフェース設計、失敗パターンのプロンプトへの反映、成功するパターンのドキュメント化、そしてこちらでも Lint ルールの整備についての言及がありました。
そして最後にテスト駆動開発や LLM の得意領域・発達段階を予測する技術、プロンプトエンジニアリングの重要性の増加など Agentic Coding によるプログラミング自体の変質と不変である点について説明され締めくくられました。</p>
<h1 id="感想">感想</h1>
<p>これらのセッションを通して、私が携わる新規プロダクト開発において、<strong>実践できている部分</strong> と <strong>改善の余地がある部分</strong> が明確になりました。
実践できている点として、全社的に積極的に利用している AI エディタ「Cursor」のプロジェクトルールや自立型 AI エージェント「Devin」の knowledge には、プロジェクトごとに解空間を明確にするコンテキストを与えています。これは、「AI Coding Agent Enablement in TypeScript」で述べられていた「コンテキスト注入」と「解空間を絞る」という考え方に合致していると感じました。また、コードと仕様を記述したドキュメントを同じリポジトリ内で管理し、開発を進めている点も、AI へのコンテキスト注入に貢献していると言えるでしょう。
開発フローにおいても、AI の特性を踏まえた工夫を凝らしています。「AI Coding Agent Enablement in TypeScript」で言及があった「3 回くらいループした末に any とかキャストで誤魔化しがち」という点に対し、例えば API 開発では、まず仕様やテストケースのドキュメントをコードベースと同じファイル内に記述し、次に Request や Response Body を zod を用いて厳密に型を記述します。これらをエンジニアがレビューした後、テストデータのセットアップや記法などをエンジニアが整え、残りを AI で実装を進めます。さらにアプリケーションコードの実装においても、用意したユニットテストが通過するという制約の元 AI に実装させることにより、型解決などに AI エージェントが費やす時間が最小化され、<strong>実装フェーズの開発工数を大幅に短縮することに成功</strong> しています。
mizchi さんのセッションで言及のあったテスト駆動開発(Test Driven Development)のスキルは今後もより重要になっていくでしょう。しかし、もう少し先の未来では、弊社メドレーが大切にしている価値観(Our Essential)の一つである「<a href="https://www.medley.jp/team/culture.html">ドキュメントドリブン</a>」に倣い、<strong>ドキュメントドリブン開発(Document Driven Development)</strong> のスキルがより重要になっていくのではないか、と私は考えています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./sample.png","alt":"","index":0}">
<em>具体的なイメージ</em></p>
<p>さらに「Speed is King」の思想を体現するために、Linter と Formatter には Biome、パッケージマネージャーには Bun を採用するなどツールチェインの速度にはかなりシビアな意思決定をしており、より AI エージェントが生産的に活動できるような体制を整えています。</p>
<p>一方で、<strong>改善の余地がある点</strong> も明確になりました。特に双方のセッションで強調されていた「機械的検査とフィードバック」の継続的な改善、すなわち Linter については開発初期から現在に至るまであまり積極的に更新できていないと痛感しています。AI に与えるフィードバックをその場での単発のもので終わらせるのではなく、(人間と同じように)再発防止策を考えさせるように Linter を定期的にアップデートし、より決定的で高速に AI エージェントが機能するような環境をより一層整備していきます。</p>
<h1 id="さいごに">さいごに</h1>
<p>TypeScript の可能性を肌で感じ、熱意ある仲間たちと出会い、技術への情熱を改めて確認できた素晴らしいイベントでした。
メドレーは今後も TSKaigi だけでなく、他の技術イベントやコミュニティの発展を積極的に支援し、参加、貢献していきます。</p>
<p>過去にスポンサーとして協賛した技術カンファレンスの参加レポート記事はこちら!</p>
<div class="remark-link-card-plus__container">
<a href="https://developer.medley.jp/entry/2025/04/23/144812" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">RubyKaigi 2025 参加レポート - Platinum Sponsor として協賛しました! | MEDLEY Developer Portal</div>
<div class="remark-link-card-plus__description">はじめに
こんにちは! 人材プラットフォーム本部プロダクト開発室 第一開発グループ所属の山下です。
メドレーには今年2月に入社したエンジニアで、日本最大級の医療介護求人サイト ジョブメドレー の開発を担当しています。
メドレーは 4 月 1...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://developer.medley.jp/icon.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">developer.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://developer.medley.jp/_astro/hero.Dl9wq2O0.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://developer.medley.jp/entry/2025/01/09/115753" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">pmconf 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal</div>
<div class="remark-link-card-plus__description">
こんにちは。医療プラットフォーム本部で Product Manager をしている佐藤です。2024 年 5 月にメドレーにジョインし、医療機関向けプロダクト開発に奔走しています。社内では ”papa”、家では”おじさん”と呼ばれ可愛がら...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://developer.medley.jp/icon.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">developer.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://developer.medley.jp/_astro/thumbnail.BngdtqWe.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://developer.medley.jp/entry/2024/12/06/183134" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">JSConf JP 2024に プレミアムスポンサーとして協賛しました! | MEDLEY Developer Portal</div>
<div class="remark-link-card-plus__description">こんにちは!人材プラットフォーム本部で技術広報兼エンジニア採用をしている重田(@Shige0096)です。2024 年 11 月にメドレーにジョインし、初の社外イベントに参加してきました。
今回、メドレーは 2024/11/23 に九段坂上...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://developer.medley.jp/icon.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">developer.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://developer.medley.jp/_astro/thumbnail.ClSUZF-G.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://developer.medley.jp/entry/2024/10/15/120419" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">DroidKaigi 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal</div>
<div class="remark-link-card-plus__description">こんにちは。人材プラットフォーム本部でエンジニアをしている山河です。2023 年 4 月に新卒として入社し、徐々に業務の幅を広げています!
さて、メドレーは 2024/9/11 〜 9/13 の 3 日間にベルサール渋谷ガーデンにて開催され...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://developer.medley.jp/icon.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">developer.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://developer.medley.jp/_astro/thumbnail.DIaVzZ4A.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>TSKaigi 2025 の運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございました!</p>
<h1 id="メドレーではエンジニアを積極採用中です">メドレーではエンジニアを積極採用中です!</h1>
<p>メドレーでは領域を問わず、TypeScript を積極的に活用して医療ヘルスケアの未来をつくるプロダクトを開発しています。
TypeScript を活用した医療ヘルスケア領域の課題解決に興味がある方は、ぜひお気軽にご連絡ください!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください</p>
- Webでユーザビリティの高い手書きアプリケーションを実装するためのTipshttps://developer.medley.jp/entry/2025/05/27/153652https://developer.medley.jp/entry/2025/05/27/153652はじめに
こんにちは。医療プラットフォーム本部 CLINICS 開発グループの吉岡(@yuya333_)と村上(@yuporonM)です。
吉岡は 2022 年に、村上は 2024 年に新卒でエンジニアとしてメドレーに入社しました。
私たち...Tue, 27 May 2025 00:00:00 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは。医療プラットフォーム本部 CLINICS 開発グループの吉岡(<a href="https://x.com/yuya333_">@yuya333_</a>)と村上(<a href="https://x.com/yuporonM">@yuporonM</a>)です。
吉岡は 2022 年に、村上は 2024 年に新卒でエンジニアとしてメドレーに入社しました。</p>
<p>私たちは、医科診療所向けの電子カルテである<a href="https://clinics-cloud.com/karte">CLINICS カルテ</a>を開発しています。CLINICS カルテは 2025 年 3 月に iPad 手書き機能をリリースしました。iPad 手書き機能とは、iPad を使って主訴・所見やシェーマ(身体の部位の絵)を手書きできる機能です。</p>
<h2 id="主訴所見の手書き">主訴・所見の手書き</h2>
<video controls preload="" style="max-width: 500px;" width="100%" poster="/entry/2025/05/27/chief_complaint_free_drawing_thumbnail.webp">
<source src="/entry/2025/05/27/chief_complaint_free_drawing.mp4">
</video>
<h2 id="シェーマの手書き">シェーマの手書き</h2>
<video controls preload="" style="max-width: 500px;" width="100%" poster="/entry/2025/05/27/schema_free_drawing_thumbnail.webp">
<source src="/entry/2025/05/27/schema_free_drawing.mp4">
</video>
<p>本記事では iPad 手書き機能を開発するにあたって工夫した点を紹介します。</p>
<h1 id="ライブラリ選定">ライブラリ選定</h1>
<p>手書き機能を開発するにあたって、手書き処理すべてを実装するのは工数の観点から現実的ではありませんでした。
そのため、要件を満たせるかつメンテナンス面から以下のライブラリが候補となりました。</p>
<ul>
<li><a href="https://github.com/fabricjs/fabric.js">fabric</a></li>
<li><a href="https://github.com/konvajs/konva">konva</a></li>
</ul>
<p>メドレーでは、歯科診療所向けに電子カルテ <a href="https://dentis-cloud.com/">Dentis</a> を開発しており、Dentis では既に手書き機能を提供しています。そこで、手書き機能の実装に関して知見を持っている Dentis のエンジニアにライブラリ選定について相談したところ、以下の理由で fabric を採用することに決めました。</p>
<ul>
<li>konva では線が荒くなってしまうことがある
<ul>
<li><a href="https://github.com/konvajs/konva/issues/1144">https://github.com/konvajs/konva/issues/1144</a> で解消方法も紹介されていましたが、細かく線を書きたいケースに対応できない</li>
</ul>
</li>
<li>konva では線を描くたびに再レンダリングが発生してしまう
<ul>
<li>mousemove や touchevent のデータをキャッシュすることで解決できるが、遅延が気になる</li>
</ul>
</li>
</ul>
<p>また、fabric を使用した手書き機能の実装方法に関しては、2024 年 12 月にメドレーのアドベントカレンダーで吉岡が書いた以下の記事を参考にしてみてください。</p>
<div class="remark-link-card-plus__container">
<a href="https://zenn.dev/medley/articles/ee7a92676938a5" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">React+fabric.jsで作る手書きアプリ</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://static.zenn.studio/images/logo-transparent.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">zenn.dev</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.cloudinary.com/zenn/image/upload/s--7a919NyJ--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:React%252Bfabric.js%25E3%2581%25A7%25E4%25BD%259C%25E3%2582%258B%25E6%2589%258B%25E6%259B%25B8%25E3%2581%258D%25E3%2582%25A2%25E3%2583%2597%25E3%2583%25AA%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_34:yuya333%2Cx_220%2Cy_108/bo_3px_solid_rgb:d6e3ed%2Cg_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyLzU5YTVhMjE3YTguanBlZw==%2Cr_20%2Cw_90%2Cx_92%2Cy_102/co_rgb:6e7b85%2Cg_south_west%2Cl_text:notosansjp-medium.otf_30:%25E6%25A0%25AA%25E5%25BC%258F%25E4%25BC%259A%25E7%25A4%25BE%25E3%2583%25A1%25E3%2583%2589%25E3%2583%25AC%25E3%2583%25BC%2Cx_220%2Cy_160/bo_4px_solid_white%2Cg_south_west%2Ch_50%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyL2RiZGM1YzI2MjkuanBlZw==%2Cr_max%2Cw_50%2Cx_139%2Cy_84/v1627283836/default/og-base-w1200-v2.png?_a=BACAGSGT" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="手書き機能で工夫したポイント-4-選">手書き機能で工夫したポイント 4 選</h1>
<h2 id="工夫-1-手を画面につけながらペンで手書きできるようにした">工夫 1. 手を画面につけながらペンで手書きできるようにした</h2>
<p>画面に手をつけながらペンで書いたときに、以下のように線が乱れてしまうことがありました。</p>
<video controls preload="" style="max-width: 500px;" width="100%" poster="/entry/2025/05/27/multi_touch_thumbnail.webp">
<source src="/entry/2025/05/27/multi_touch.mp4">
</video>
<p>画面に接触した手はペンや指で書いたときよりも接触面積が大きくなるので、接触部分が一定よりも大きくなった場合には線を書けないように実装しました。具体的には、<a href="https://developer.mozilla.org/ja/docs/Web/API/Touch/radiusX">radiusX</a>プロパティを使用して、radiusX がある値より大きいときには、<a href="https://fabricjs.com/demos/free-drawing/">isDrawingMode</a>が false になるようにしました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#9CDCFE">canvas</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">on</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"mouse:down:before"</span><span style="color:#D4D4D4">, (</span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#CE9178">"TouchEvent"</span><span style="color:#569CD6"> in</span><span style="color:#9CDCFE"> window</span><span style="color:#D4D4D4"> && </span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">e</span><span style="color:#569CD6"> instanceof</span><span style="color:#4EC9B0"> TouchEvent</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> touch</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">touches</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">item</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (!</span><span style="color:#9CDCFE">touch</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // 接触面積がペンや指よりも大きいときに線を書けなくする</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">touch</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">radiusX</span><span style="color:#D4D4D4"> > </span><span style="color:#B5CEA8">35</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#9CDCFE"> canvas</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">isDrawingMode</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// 画面から手を離す度にisDrawingModeをリセット</span></span>
<span class="line"><span style="color:#9CDCFE">canvas</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">on</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"mouse:up:before"</span><span style="color:#D4D4D4">, () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> canvas</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">isDrawingMode</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">true</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span></code></pre>
<h2 id="工夫-2-消しゴムで線を消すときのラグを解消した">工夫 2. 消しゴムで線を消すときのラグを解消した</h2>
<p>消しゴム機能は <a href="https://github.com/ShaMan123/erase2d">erase2d</a> ライブラリを使って実装しました。
fabric v5 までは消しゴム機能が内包されていましたが、fabric v6 から erase2d に切り出されました。
erase2d を使って消しゴムを実装すると、次の動画のようにユーザビリティを大きく損なうほどのラグが発生していました。</p>
<video controls preload="" style="max-width: 500px;" width="100%" poster="/entry/2025/05/27/eraser_lag_thumbnail.webp">
<source src="/entry/2025/05/27/eraser_lag.mp4">
</video>
<p>このラグは PC では発生せず、iPad でのみ発生していました。</p>
<p>この現象について原因を調査したところ、以下の 2 点が原因であることが分かりました。</p>
<p><strong>原因 1. Retina ディスプレイによるピクセル密度の違い</strong></p>
<p>iPad は Retina ディスプレイを搭載しており、ピクセル密度が通常のディスプレイよりも高くなっていました。
検証に利用していた 11 インチ iPad Air (M2) では、PC と比較してピクセル密度が 4 倍にもなっていました。</p>
<p><strong>原因 2. ピクセル密度に依存した消しゴム機能特有の処理</strong></p>
<p>消しゴム機能は、ペン機能と比較して複雑な処理が行われていました。
ペンは 1 つのキャンバスに線を描くだけであるのに対して、消しゴムは複数のキャンバスを扱い、それらを合成する処理が必要でした。
具体的には、消しゴムの軌跡を一時的なキャンバスに描画し、それをメインキャンバスに合成するといった処理等が行われていました。
これは、ピクセル密度に依存しており、ピクセル密度が高い環境では負荷が高くなっていました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#6A9955">// https://github.com/ShaMan123/erase2d/blob/7ee2b789b4c9161d662ea8c8aaf87af4c37bab13/packages/core/src/erase.ts#L15-L40</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#DCDCAA"> erase</span><span style="color:#D4D4D4"> = (</span></span>
<span class="line"><span style="color:#9CDCFE"> destination</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">CanvasRenderingContext2D</span><span style="color:#D4D4D4">, </span><span style="color:#6A9955">// メインキャンバス</span></span>
<span class="line"><span style="color:#9CDCFE"> source</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">CanvasRenderingContext2D</span><span style="color:#D4D4D4">, </span><span style="color:#6A9955">// 消しゴムの軌跡が描かれたキャンバス</span></span>
<span class="line"><span style="color:#9CDCFE"> erasingEffect</span><span style="color:#D4D4D4">?: </span><span style="color:#4EC9B0">CanvasRenderingContext2D</span><span style="color:#6A9955"> // 消したくないものを保護するためのキャンバス</span></span>
<span class="line"><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#6A9955"> // メインキャンバスから、消しゴムの軌跡部分を消去</span></span>
<span class="line"><span style="color:#DCDCAA"> drawImage</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">destination</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">source</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"destination-out"</span><span style="color:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">erasingEffect</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#6A9955"> // 消しゴムの軌跡に、消したくないものがあれば、消さずに保護</span></span>
<span class="line"><span style="color:#DCDCAA"> drawImage</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">source</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">erasingEffect</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"source-in"</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">else</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#6A9955"> // 消しゴムの軌跡を描いたキャンバスを初期化</span></span>
<span class="line"><span style="color:#9CDCFE"> source</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">save</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#9CDCFE"> source</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">resetTransform</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#9CDCFE"> source</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">clearRect</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">source</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">canvas</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">width</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">source</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">canvas</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">height</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#9CDCFE"> source</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">restore</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span></code></pre>
<p>これらの原因によって、Retina ディスプレイの高解像度環境では消しゴムで線を消すときにラグが発生していました。
解決策として Canvas の初期化時に <a href="https://fabricjs.com/api/interfaces/canvasoptions/#enableretinascaling">enableRetinaScaling</a> オプションを<code>false</code>に設定し、Retina ディスプレイの高解像度環境においても同じピクセル密度で処理を行うようにしました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> canvas</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">new</span><span style="color:#9CDCFE"> fabric</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Canvas</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">canvasElement</span><span style="color:#D4D4D4">, {</span></span>
<span class="line"><span style="color:#9CDCFE"> enableRetinaScaling:</span><span style="color:#569CD6"> false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span></code></pre>
<p>この設定により、一定の解像度を担保しつつ、iPad でも滑らかな消しゴム操作を実現できました。</p>
<h2 id="工夫-3-ipad-で手書きして保存した画像を-pc-に即時反映されるようにした">工夫 3. iPad で手書きして保存した画像を PC に即時反映されるようにした</h2>
<p>iPad で手書きしながら PC でカルテを使うということを想定しているため、iPad で保存した手書き画像は PC に即時反映される必要があります。こちらを実現するために、以下が候補に挙げられました。</p>
<ul>
<li>SSE(Server-Sent Events)</li>
<li>WebSocket</li>
<li><a href="https://firebase.google.com/docs/database?hl=ja">Firebase Realtime Database</a></li>
</ul>
<p>Firebase Realtime Database を既に使用しており、今回保存するのは更新時間のみで、保存容量が大幅に増えることもないため、今回は Firebase Realtime Database を使用することにしました。</p>
<p>以下が、Realtime Database で変更の監視及び通知を行う実装です。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#C586C0">import</span><span style="color:#D4D4D4"> { </span><span style="color:#9CDCFE">initializeApp</span><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">from</span><span style="color:#CE9178"> "firebase/app"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#C586C0">import</span><span style="color:#D4D4D4"> { </span><span style="color:#9CDCFE">getDatabase</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">off</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">onValue</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">ref</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">set</span><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">from</span><span style="color:#CE9178"> "firebase/database"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> app</span><span style="color:#D4D4D4"> = </span><span style="color:#DCDCAA">initializeApp</span><span style="color:#D4D4D4">(</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#6A9955"> /* 省略’*/</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#CE9178"> "firebaseapp"</span></span>
<span class="line"><span style="color:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#DCDCAA"> db</span><span style="color:#D4D4D4"> = () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#DCDCAA"> getDatabase</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">app</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> PATH</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">"medicalRecordChiefComplaintImages"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// refを生成</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#DCDCAA"> medicalRecordChiefComplaintImagesRef</span><span style="color:#D4D4D4"> = (</span><span style="color:#9CDCFE">medicalRecordId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#DCDCAA"> ref</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">db</span><span style="color:#D4D4D4">(), </span><span style="color:#CE9178">`/</span><span style="color:#569CD6">${</span><span style="color:#4FC1FF">PATH</span><span style="color:#569CD6">}</span><span style="color:#CE9178">/</span><span style="color:#569CD6">${</span><span style="color:#9CDCFE">medicalRecordId</span><span style="color:#569CD6">}</span><span style="color:#CE9178">`</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// 主訴所見の手書き画像が更新されたことを通知する</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#DCDCAA"> updateMedicalRecordChiefComplaintImageIds</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">async</span><span style="color:#D4D4D4"> ({</span></span>
<span class="line"><span style="color:#9CDCFE"> medicalRecordId</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">}: {</span></span>
<span class="line"><span style="color:#9CDCFE"> medicalRecordId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">}) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> await</span><span style="color:#DCDCAA"> set</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">medicalRecordChiefComplaintImagesRef</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">medicalRecordId</span><span style="color:#D4D4D4">), </span><span style="color:#9CDCFE">Date</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">now</span><span style="color:#D4D4D4">());</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// 主訴所見の手書き画像の変更を監視する</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#DCDCAA"> watchMedicalRecordChiefComplaintImages</span><span style="color:#D4D4D4"> = ({</span></span>
<span class="line"><span style="color:#9CDCFE"> medicalRecordId</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> refreshMedicalRecordChiefComplaintImages</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">}: {</span></span>
<span class="line"><span style="color:#9CDCFE"> medicalRecordId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#DCDCAA"> refreshMedicalRecordChiefComplaintImages</span><span style="color:#D4D4D4">: () </span><span style="color:#569CD6">=></span><span style="color:#4EC9B0"> void</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">}) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#DCDCAA"> onValue</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">medicalRecordChiefComplaintImagesRef</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">medicalRecordId</span><span style="color:#D4D4D4">), () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#6A9955"> // Realtime Databaseが更新されたときに実行する処理</span></span>
<span class="line"><span style="color:#DCDCAA"> refreshMedicalRecordChiefComplaintImages</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// 主訴所見の手書き画像の変更の監視を解除する</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#DCDCAA"> unwatchMedicalRecordChiefComplaintImages</span><span style="color:#D4D4D4"> = ({</span></span>
<span class="line"><span style="color:#9CDCFE"> medicalRecordId</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">}: {</span></span>
<span class="line"><span style="color:#9CDCFE"> medicalRecordId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">}) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#DCDCAA"> off</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">medicalRecordChiefComplaintImagesRef</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">medicalRecordId</span><span style="color:#D4D4D4">));</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span></code></pre>
<p>フロントエンドでは、データフェッチに <a href="https://tanstack.com/query/latest">TanStack Query</a> を使用しています。以下のように Realtime Database によって手書き画像の更新を監視して refetch しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#DCDCAA"> useMedicalChiefComplaintImagesWatchQuery</span><span style="color:#D4D4D4"> = ({</span></span>
<span class="line"><span style="color:#9CDCFE"> medicalRecordId</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> initialChiefComplaintImages</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">}: </span><span style="color:#4EC9B0">UseMedicalChiefComplaintImagesWatchQueryProps</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> medicalChiefComplaintImagesQuery</span><span style="color:#D4D4D4"> = </span><span style="color:#DCDCAA">useQuery</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> queryKey:</span><span style="color:#9CDCFE"> medicalChiefComplaintImageKey</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">list</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">medicalRecordId</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#DCDCAA"> queryFn</span><span style="color:#9CDCFE">:</span><span style="color:#D4D4D4"> () </span><span style="color:#569CD6">=></span><span style="color:#9CDCFE"> query</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">get</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">request</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">get</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">medicalRecordId</span><span style="color:#D4D4D4">)),</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> useEffect</span><span style="color:#D4D4D4">(() </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#DCDCAA"> watchMedicalRecordChiefComplaintImages</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> medicalRecordId:</span><span style="color:#9CDCFE"> medicalRecordId</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#6A9955"> // Realtime Databaseの更新時にrefetchする</span></span>
<span class="line"><span style="color:#9CDCFE"> refreshMedicalRecordChiefComplaintImages:</span></span>
<span class="line"><span style="color:#9CDCFE"> medicalChiefComplaintImagesQuery</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">refetch</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4"> () </span><span style="color:#569CD6">=></span><span style="color:#DCDCAA"> unwatchMedicalRecordChiefComplaintImages</span><span style="color:#D4D4D4">({ </span><span style="color:#9CDCFE">medicalRecordId</span><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#D4D4D4"> }, [</span><span style="color:#9CDCFE">medicalRecordId</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">medicalChiefComplaintImagesQuery</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">refetch</span><span style="color:#D4D4D4">]);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#9CDCFE"> medicalChiefComplaintImagesQuery</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span></code></pre>
<p>最後に、iPad で手書き画像を更新したタイミングで<code>updateMedicalRecordChiefComplaintImageIds</code>を呼び出せば、以下のように iPad での変更が PC に即時に反映されるようになります。</p>
<video controls preload="" style="max-width: 500px;" width="100%" poster="/entry/2025/05/27/image_sync_thumbnail.webp">
<source src="/entry/2025/05/27/image_sync.mov">
</video>
<h2 id="工夫-4-手書きツールバーを自由に配置できるようにした">工夫 4. 手書きツールバーを自由に配置できるようにした</h2>
<p>手書き機能では太さや色の変更、ペンと消しゴムの切り替え、履歴管理ができるようにツールバーを実装しています。ツールバーは頻繁に使われるため、ユーザーが使用しやすい位置に移動できるようにしました。こちらは <a href="https://dndkit.com/">dnd-kit</a> を使用して実装しました。</p>
<video controls preload="" style="max-width: 500px;" width="100%" poster="/entry/2025/05/27/toolbar_draggable_thumbnail.webp">
<source src="/entry/2025/05/27/toolbar_draggable.mov">
</video>
<p>動画にあるようにツールバーが親要素からはみ出ないようになっています。こちらは <a href="https://docs.dndkit.com/api-documentation/modifiers#restricttoparentelement">restrictToParentElement</a> を使用することで実現できます。
また、CSS ライブラリに <a href="https://emotion.sh/docs/introduction">emotion</a> を使用しており、今回の Draggable 対応では動的にスタイルを変更する必要があるため、<code>style</code> prop を使うように注意する必要があります( <a href="https://emotion.sh/docs/best-practices#use-the-style-prop-for-dynamic-styles">https://emotion.sh/docs/best-practices#use-the-style-prop-for-dynamic-styles</a> )。</p>
<h1 id="ライブラリの不具合">ライブラリの不具合</h1>
<p>上記のようにユーザーが使いやすいように手書き機能を開発しましたが、消しゴムで線を消すと消えて欲しくない背景画像まで消えてしまうという不具合がライブラリに存在していました。こちらは同じ fabric を使用している Dentis のエンジニア大岡に相談したところ、ライブラリに PR を出して解消していただけました!</p>
<div class="remark-link-card-plus__container">
<a href="https://github.com/ShaMan123/erase2d/pull/49" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">fix(): backgroundImage/overlayImage erasable by yasupeke · Pull Request #49 · ShaMan123/erase2d</div>
<div class="remark-link-card-plus__description">I always appreciate the convenience of using this tool. I have fixed an issue where the erasable setting for backgroundImage/overlayImage was not being applied. Please review the changes.</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://github.githubassets.com/favicons/favicon.svg" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">github.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://opengraph.githubassets.com/c7a9e72fc15ee80bc9004f530563310b369d3e367252b5a241032f179ce00228/ShaMan123/erase2d/pull/49" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="まとめ">まとめ</h1>
<p>本記事では、Web でユーザビリティの高い手書きアプリケーションを実装するための工夫点について紹介しました。手書き機能の実装においては、実装速度とユーザビリティを両立するために適切なライブラリ選定が重要です。私達は fabric を採用することで滑らかな手書きアプリケーションを実現できました。
実装にあたり、以下の 4 つを工夫しました。</p>
<ul>
<li><strong>手を画面に置きながら書ける機能:</strong> 接触面積の大きさを検出してペン入力と区別することで、自然な書き心地を実現しました</li>
<li><strong>消しゴム操作のラグ解消:</strong> Retina ディスプレイ対応を最適化し、高負荷な消しゴム処理でもスムーズな操作感を実現しました</li>
<li><strong>リアルタイム同期:</strong> Firebase Realtime Database を活用し、複数デバイス間での即時反映を実現しました</li>
<li><strong>柔軟な UI:</strong> dnd-kit によるドラッグ可能なツールバーで、ユーザー体験を向上しました</li>
</ul>
<p>また、ライブラリの不具合に対してはプロダクトを横断したコミュニケーションやコミュニティと協力することで解決しました。これらの工夫により、医科診療所で快適に使用できる手書き機能を開発することができました。
今後もユーザビリティを重視しながら、より使いやすい機能の開発を続けていきます。</p>
<h1 id="were-hiring">We’re hiring</h1>
<p>メドレーでは一緒に働く仲間を大募集しています!
カジュアル面談も実施しておりますので、「お話だけでも聞いてみたい!」「ちょっと雑談してみたい!」でも構いませんので、お気軽にお問い合わせください!</p>
<p>募集の一覧</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>医療エンジニアリング領域盛り上がっています!メドレーについてお話します!</p>
<div class="remark-link-card-plus__container">
<a href="https://pitta.me/matches/BtcyDvCvUZtx" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メドレーの人材プラットフォーム事業についてお話しします!</div>
<div class="remark-link-card-plus__description">「メドレーの人材プラットフォーム事業についてお話しします!」- 倉林 昭和さん</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://pitta.me/static/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">pitta.me</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.cloudinary.com/meety-inc/image/upload/v1740621337/meety/prod/ogp_images/rcih772q1mvmhdfn3dq3.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>メドレーの開発チームについて知りたい方!ぜひお話ししましょう!</p>
<div class="remark-link-card-plus__container">
<a href="https://pitta.me/matches/sNeEHMdSLZpB" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メドレーの開発チームについて知りたい方!ぜひお話ししましょう!</div>
<div class="remark-link-card-plus__description">「メドレーの開発チームについて知りたい方!ぜひお話ししましょう!」- 德永 諒介さん</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://pitta.me/static/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">pitta.me</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.cloudinary.com/meety-inc/image/upload/v1734051217/meety/prod/ogp_images/hduyp3sdb6perb91tlfc.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- 医療プラットフォーム本部におけるSREグループの立ち上げhttps://developer.medley.jp/entry/2025/04/30/080431https://developer.medley.jp/entry/2025/04/30/080431はじめに
こんにちは、医療プラットフォーム本部 SRE グループの大塚です。入社から 3 年ほど CLINICS のソフトウェアエンジニアとして開発に携わり、その中でプロダクトの安定稼働のための信頼性向上の技術的な取り込みも担当していました...Wed, 30 Apr 2025 00:00:00 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは、医療プラットフォーム本部 SRE グループの大塚です。入社から 3 年ほど CLINICS のソフトウェアエンジニアとして開発に携わり、その中でプロダクトの安定稼働のための信頼性向上の技術的な取り込みも担当していました。</p>
<p>このたび、医療プラットフォームにおけるシステムの安定稼働と信頼性の確保がこれまで以上に求められる状況になり、正式に SRE チームを立ち上げました。医療機関向け SaaS である <a href="https://clinics-cloud.com/">CLINICS</a> の信頼性向上を主眼に発足し、立ち上げから SLO の策定や運用効率化などの成果が出ています。</p>
<p>この記事では、以下の内容についてお話しします。</p>
<ul>
<li>医療プラットフォームにおける SRE チーム立ち上げの経緯</li>
<li>医療プラットフォームの中核である CLINICS について</li>
<li>医療プラットフォーム SRE チームの組織体制と役割</li>
<li>医療プラットフォーム SRE チームとしての展望</li>
</ul>
<p>医療系 SaaS に興味があるエンジニアや、SRE としてのキャリアに関心がある方に向けて書いています。医療の発展にテクノロジーで貢献したいと考える方にとって参考になれば幸いです。</p>
<h1 id="なぜ医療プラットフォームで-sre-チーム立ち上げたのか">なぜ、医療プラットフォームで SRE チーム立ち上げたのか?</h1>
<h2 id="医療プラットフォームの中核を担う-clinics">医療プラットフォームの中核を担う CLINICS</h2>
<p>医療プラットフォームは「医療ヘルスケアの未来をつくる」、「納得できる医療」の実現のために、様々な医療領域のプロダクトを提供しています。</p>
<p>その中でも、医療機関向け SaaS である <a href="https://clinics-cloud.com/">CLINICS</a> は高い信頼性が求められています。CLINICS は電子カルテ、予約管理、会計システム、オンライン診療など、診療所の基幹業務をトータルでサポートするクラウドサービスです。診療所の日々の運営に不可欠な業務を担うシステムであるため、高い信頼性と安定性が求められます。</p>
<p>また、CLINICS は医療機関と患者をつなぐ接点でもあり、システムには患者向けの API なども含まれます。そのため、システムの信頼性は患者の観点から見ても重要です。</p>
<p><img __ASTRO_IMAGE_="{"src":"./mpf.png","alt":"医療プラットフォームのプロダクト構成図","index":0}"></p>
<h2 id="clinics-に求められる非機能要件">CLINICS に求められる非機能要件</h2>
<p>CLINICS は診療業務の基幹システムであり、診療所の業務をリアルタイムでサポートします。機能面の品質はもちろん、非機能要件(信頼性、可用性、パフォーマンス、保守性など)も極めて重要です。</p>
<p>例えば、システムの応答速度が遅くなり患者が受付や会計で長時間待たされると、医療機関だけでなく患者の体験も悪化します。そして、システム停止は診療所の業務が遂行できなくなることを意味します。</p>
<p>その結果、CLINICS サポートへの負荷増大や医療機関の信頼性低下などの影響を招きます。障害復旧対応で開発計画の変更が余儀なくされ、開発速度の低下を生じさせる一因になります。</p>
<p>しかし、CLINICS においてはこれらの非機能要件を満たすことは簡単ではありません。CLINICS は医療プラットフォームの中で最も歴史が長いプロダクトです。契約医療機関数は数千を超え、診療所の業務時間中は常に大量のデータとトラフィックを処理します。また、 CLINICS はレセコン(診療報酬請求事務を管理・自動化するシステム) などの複数のシステムで構成され、インフラなどの運用コストも高くなっています。</p>
<h2 id="clinics-を含む医療プラットフォームの成長に伴う技術課題">CLINICS を含む医療プラットフォームの成長に伴う技術課題</h2>
<p>私自身も CLINICS のソフトウェアエンジニアとして開発に携わり、インフラに詳しいメンバー数人と共に可用性や信頼性に関するタスクを担当してきました。しかし、事業の成長に伴いシステム規模が拡大する中で、機能開発とシステム信頼性の両立が困難になっていました。</p>
<p>システムが深刻な状態には至っていませんでしたが、概ね以下のような課題を抱えていました。</p>
<ul>
<li>長年の運用の中で非効率な DB へのクエリが蓄積し、処理に時間がかかる箇所が散見される</li>
<li>トイル(SRE における反復的な運用作業)が増大し、運用業務の負荷が大きくなり、さらに悪循環を生む</li>
<li>ライブラリ対応の優先度が下がり、 EOL 対応に追われている</li>
<li>アラートの振り返りと暫定的な再発防止策は実施するが、根本対応までやりきれない</li>
</ul>
<p>また、これらの課題は、<a href="https://dentis-cloud.com/">Dentis</a> や <a href="https://pharms-cloud.com/">Pharms</a> などの医療プラットフォーム内の他のプロダクトでも発生する可能性が高いです。他のプロダクトも CLINICS と同様の体制で開発と運用されており、すでに同じ課題が顕在化しつつあります。</p>
<p>CLINICS の医療機関数は日々増加しており、今後も事業拡大が見込まれます。それは他のプロダクトでも同様です。CLINICS に限らず医療プラットフォーム全体の成長を加速させるには、 信頼性の高い基盤構築に向き合えるチーム体制の構築が不可欠です。</p>
<h2 id="医療プラットフォーム-sre-チームの発足">医療プラットフォーム SRE チームの発足</h2>
<p>信頼性課題などの中長期的な課題解決を目指す医療プラットフォーム SRE チームを発足しました。CLINICS に限らず、医療プラットフォーム全体を対応範囲としています。先程述べたように、システム信頼性の課題は医療プラットフォーム全体の課題であるためです。SRE チームが医療プラットフォームの横断組織として機能することで、システム信頼性に関する知見を集約します。</p>
<p>まずは、医療プラットフォームの中でも 中心的な役割を担う大規模なシステムである CLINICS の信頼性向上を最優先課題としています。SRE チームは CLINICS での取り組みの中で得た知見を医療プラットフォーム全体に還元します。</p>
<p>2025 年 4 月現在、SRE チームは CLINICS プロダクトに専任する形で活動しています。一般に知られている Embedded SRE です。この形態では、SRE チームメンバーが開発チームに直接入り込み、密に連携しながら信頼性向上に取り組みます。</p>
<h1 id="clinics-の信頼性向上を目指した-sre-の取り組み">CLINICS の信頼性向上を目指した SRE の取り組み</h1>
<p>ここからは CLINICS に対して現在行っている取り組みを説明します。</p>
<h2 id="sre-チームの役割とミッション">SRE チームの役割とミッション</h2>
<p>CLINICS における SRE チームのミッションは <strong>「事業成長を支え、安定した開発基盤と顧客の信頼性を維持する文化、仕組みを構築すること」</strong> です。SRE チームは信頼性向上に専念することで、開発チームは機能開発に集中できる状態を目指し、CLINICS プロダクトは事業と品質の両輪での成長を図ります。</p>
<p>前半で述べた課題に対応するために、SRE チームの立ち上げ時にまず 3 つの重点目標を設定しました。それぞれの目標に対する取り組みと一部の成果を紹介します。</p>
<h3 id="目標-1-トイル排除と中長期課題へのシフト">目標 1: トイル排除と中長期課題へのシフト</h3>
<p>課題:</p>
<ul>
<li>日常的な運用作業(トイル)を自動化し、少人数での複数システムの運用ができ、足元の課題から中長期的な課題へ時間を使えるようにする</li>
</ul>
<p>主な取り組み:</p>
<ul>
<li><strong>既存インフラリソースの <a href="https://developer.hashicorp.com/terraform/cli/import">Terraform import</a> を実施</strong>: 新しいインフラリソースは Terraform で作成されていました。しかし、リリース初期から存在するリソースは違い、インフラ運用の属人化を引き起こしていました。Terraform import を定常的に実施し、誰でも実装可能で、レビューしやすい運用を実現しました。</li>
<li><strong>定常的に発生する運用業務の自動化</strong>: CLINICS は運用するシステムも多く、月次の更新処理や定常的なスケールアップ対応など、数多くの運用タスクがありました。<a href="https://aws.amazon.com/jp/step-functions/">AWS Step Functions</a> などにより定常的な業務を自動化し、運用負荷軽減を実現しました。</li>
</ul>
<h2 id="目標-2-改善文化の浸透">目標 2: 改善文化の浸透</h2>
<p>課題:</p>
<ul>
<li><a href="https://sre.google/sre-book/postmortem-culture/">ポストモーテム</a> 文化(障害後の振り返り文化)を確立し、再発防止が適切に実行され、改善課題が積み上がり続ける状態から脱する。</li>
</ul>
<p>主な取り組み:</p>
<ul>
<li><strong>ポストモーテムの標準化:</strong> 以前から振り返りの文化はありましたが、定式化されておらず、各々が各自の形式で振り返りを実施していました。 チームでのナレッジ共有がなされず、障害対応での属人化の要因となっていました。体系的に障害を振り返る仕組みを作り、CLINICS のチーム全体に展開することで、チームでの障害対応力を向上させました。</li>
<li><strong>アラート改善を定常的に回す仕組みの策定</strong>: 緊急性が高い事象や顧客とのコミュニケーションが必要な事象はアラートの検知時に迅速に解決できていました。しかし、タイミングの問題で発生する瑣末なフロントエンドのエラーなどは「オオカミ少年」になり、アラートを定期的に検知していました。開発チーム全体で根本対応できるものは実施する、すぐに解決が難しいものは一定期間無視するなど、対応ルールを精緻化しました。</li>
</ul>
<h2 id="目標-3-slo-による信頼性可視化">目標 3: SLO による信頼性可視化</h2>
<p>課題:</p>
<ul>
<li>CLINICS の関係者が納得できる <a href="https://cloud.google.com/blog/products/devops-sre/availability-part-deux-cre-life-lessons?hl=en">信頼性の指標(SLO)</a> を構築し、改善施策はこれらの指標改善を優先する</li>
</ul>
<p>主な取り組み:</p>
<ul>
<li><strong>重要な顧客体験 (Critical User Journey) の策定と SLO ダッシュボードの作成:</strong> CUJ(Critical User Journey) を特定し、Datadog 上に SLO のダッシュボードを作成しました。フロントエンドのパフォーマンスモニタリングを目的とした Datadog RUM(Realtime User Monitoring) などを導入することで、オブザーバビリティの強化も実現し、顧客体験をより厳密にトラッキングできるようにしました。</li>
<li><strong>SLO モニタリングの実施:</strong> 作成したダッシュボードをもとに受付、診察、会計など 3 つの顧客体験について、可用性、レイテンシー、エラー率の目標値を設定しました。閾値の調整やエラーバジェットのモニタリングを定期実施し、アラート改善に役立てる取り組みを実現しました。</li>
</ul>
<h2 id="sre-チームにおけるその他の取り組み">SRE チームにおけるその他の取り組み</h2>
<p>上記の取り組み以外にも、SRE の一般的な以下の業務にも取り組んでいます。</p>
<ul>
<li>事業成長を見越したキャパシティプランニング</li>
<li>システムのモニタリングによる事前の課題発見</li>
<li>障害対応プロセスの改善</li>
<li>インフラコストの削減</li>
<li>DB 等のパフォーマンス問題の改善</li>
<li>ライブラリ対応やセキュリティ対応の効率化</li>
<li>アーキテクチャとリリースフローの改善…など</li>
</ul>
<h2 id="sre-チームの取り組みの成果が実を結び始める">SRE チームの取り組みの成果が実を結び始める</h2>
<p>これらの取り組みにより、CLINICS 開発メンバー全体(開発チームを含む)の運用負担が軽減されました。</p>
<p>信頼性の維持をしつつ、目の前の課題だけでなく将来を見据えた取り組みに注力する余裕が生まれています。開発チームとの協業を強化し、機能開発時に SLO の組み込みを進め、改善目標の指標としても活用する計画を立てています。他にも障害時の信頼性向上を目的とした<a href="https://sidekiq.org/products/pro.html">Sidekiq Pro</a>の導入など、信頼性向上にも取り組んでいます。</p>
<p>このように私たちの取り組みが徐々に成果を上げ始めています。<strong>開発チームと SRE チームの協働により、当初実現したかった CLINICS の機能開発とシステム信頼性の両立が実現しつつあります</strong>。今後もこの連携と SRE の取り組みを深め、より安定したサービス提供を目指してまいります。</p>
<h1 id="医療プラットフォーム-sre-チームとしての展望">医療プラットフォーム SRE チームとしての展望</h1>
<p>ここまでは、CLINICS における SRE の取り組みを書いてきました。ここからは、今後医療プラットフォーム全体に SRE としてどのように貢献しようとしているのかの展望を述べます。</p>
<h2 id="医療プラットフォーム全体への-sre-ノウハウ展開">医療プラットフォーム全体への SRE ノウハウ展開</h2>
<p>CLINICS での知見を活かし、今後、<a href="https://dentis-cloud.com/">Dentis</a> や <a href="https://pharms-cloud.com/">Pharms</a> といった医療プラットフォームの他のプロダクトにも SRE のノウハウを展開していく予定です。</p>
<p>CLINICS は規模が大きく、信頼性要求も高いため、培ったノウハウは他のプロダクトにも十分応用できると考えています。ノウハウを展開することで、医療プラットフォーム全体での信頼性の底上げを実現します。</p>
<h2 id="プロダクトの拡充に対して柔軟に対応できる仕組みづくり">プロダクトの拡充に対して柔軟に対応できる仕組みづくり</h2>
<p>医療プラットフォームは、「医療ヘルスケアの未来をつくる」というミッション実現のために、今後も新規開発や M&A によるプロダクト増加も見込まれます。</p>
<p>新たなプロダクトが加わった際にも、医療プラットフォーム標準の信頼性水準に迅速に引き上げることが重要です。</p>
<p>SRE チームは、医療プラットフォームに属する全てのプロダクトが高い信頼性を維持できる体制を目指します。</p>
<h2 id="事業運営に関わる全関係者が納得する-sre-文化の醸成">事業運営に関わる全関係者が納得する SRE 文化の醸成</h2>
<p>SRE の取り組みは開発チームだけのものではありません。ポストモーテムや開発優先度の判断において、非開発メンバーの視点は不可欠です。プロダクトマネージャーや QA メンバーはもちろん、カスタマーサクセス・サポートチームも信頼性の考え方を共有することで、「なぜ機能開発より安定性向上を優先すべきか」といった判断に事業全体の納得感が生まれます。</p>
<p>SLO などの指標は単なる技術的な数値ではなく、顧客体験と事業成長を支える重要な要素です。医療プラットフォーム全体で信頼性と機能開発のバランスを適切に取りながら前進できる文化を育むこと、これも SRE チームの重要なミッションの一つです。</p>
<h1 id="まとめ医療ヘルスケアの未来を支える-sre">まとめ:医療ヘルスケアの未来を支える SRE</h1>
<p>医療プラットフォームの SRE 立ち上げについてお伝えしました。医療プラットフォームの成長と「医療ヘルスケアの未来」創造のために、SRE による信頼性確保は欠かせないミッションです。</p>
<p>SRE としても医療プラットフォームとしても、挑戦したいことは山積みです。事業成長を加速し、医療機関と患者の体験を向上させるために、SRE の活動は不可欠です。</p>
<p>何より、「医療ヘルスケアの未来をつくる」というミッションに共感し、技術で医療の課題解決に貢献したいという情熱を持つ方をお待ちしています。私たちと一緒にこのミッションを実現しませんか?</p>
<p>興味を持たれた方は、以下のリンクより、ぜひカジュアル面談の応募をお願いします。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- メドレーのAI活用戦略:「AI for All」https://developer.medley.jp/entry/2025/04/25/112655https://developer.medley.jp/entry/2025/04/25/112655メドレーの AI 活用戦略
こんにちは!メドレー株式会社 人材プラットフォーム本部 VPoE の倉林(@terukura)です。今回は、メドレーにおける AI 活用の取り組みについてご紹介します。
昨今、多くの企業が AI の活用に取り組ん...Fri, 25 Apr 2025 02:26:55 GMT<h1 id="メドレーの-ai-活用戦略">メドレーの AI 活用戦略</h1>
<p>こんにちは!メドレー株式会社 人材プラットフォーム本部 VPoE の倉林(<a href="https://x.com/terukura">@terukura</a>)です。今回は、メドレーにおける AI 活用の取り組みについてご紹介します。</p>
<p>昨今、多くの企業が AI の活用に取り組んでいますが、メドレーでは**「全職種が全業務で当たり前に AI を活用している企業:AI for All」**を目指して、それぞれの役割や業務に合わせた AI 活用を進めています。</p>
<p>メドレーでは「AI for All」を旗印に、AI 活用戦略を大きく 3 つの領域に分けています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./ai-for-all.png","alt":"AI for All","index":0}">
<em>AI for All</em></p>
<p><strong>1. AI 活用による業務オペレーションの効率化</strong>:日々の業務プロセスを AI で効率化し、ルーティンワークの時間を削減することで、より創造的な業務に集中できる環境を作ります。</p>
<p><strong>2. AI 活用によるエンジニアリング生産性向上</strong>:開発サイクルの短縮と品質向上を実現し、より多くの価値を生み出せるようにします。</p>
<p><strong>3. AI 活用によるプロダクト価値提供</strong>:ユーザーに直接価値を届ける AI 機能を実装し、メドレーのサービスをさらに進化させます。</p>
<p>またこれらの領域をそれぞれブーストさせるために、土台となる基礎活動もしっかり推進しています。</p>
<p><strong>安心・安全な AI 活用のためのガイドライン整備</strong>:医療領域を扱う企業として、セキュリティとプライバシー保護は最優先事項です。社内では「AI ガイドライン」を整備し、明確な指針を提供することで、安心して AI ツールを活用できる環境を整えています。</p>
<p><strong>AI Enabling</strong>:全社の AI 活用レベルを底上げするため、様々な AI ツールや活用環境の整備、最新ツールの評価・共有、定期的な勉強会開催などを推進しています。社内 AI プラットフォームとして Dify を導入し、各部門が様々なモデルやフローの検証・活用を容易に行える環境を提供することで、技術的な障壁を低減しています。</p>
<p>今回の blog では、こうした全社的な取り組みの中でもエンジニア面談・面接でよく質問いただく <strong>「AI 活用によるエンジニアリング生産性向上」</strong> について、具体的な活用ツールなども踏まえてご紹介できればと思います。</p>
<h1 id="プロダクト開発エンジニアリングの変化">プロダクト開発・エンジニアリングの変化</h1>
<p>特に最近では、Claude Sonnet 3.7 の登場もあり、コーディング領域における AI 活用は大きなゲームチェンジを迎えていると感じています。 エンジニアリング生産性の飛躍的な向上が現実のものとなり、従来の開発スタイルを根本から見直す時期に来ています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./ai-dev.png","alt":"開発スタイルの変化","index":0}">
<em>開発スタイルの変化</em></p>
<p>AI を活用しているエンジニアとそうでないエンジニアの間で、処理できるタスク量に大きな差が生まれつつあります。もちろん、AI が活用しやすい領域とそうでない領域があるものの、その差は想像以上に大きく、今後さらに拡大していくでしょう。</p>
<p>メドレーでは 2025 年末までに、プロダクト開発のあらゆる職種(PdM/QA/Design/Engineer)・工程において AI 活用率 100%を目指しています。これは単なる数値目標ではなく、全デベロッパーが AI を自分の「パートナー」として効果的に活用できる状態を実現するためのビジョンです。</p>
<h2 id="活用している-ai-ツール">活用している AI ツール</h2>
<p>メドレーのエンジニアリング組織では、様々な AI ツールを開発プロセスに組み込み、生産性と創造性を高めています。以下では、特に効果を発揮している主要なツールとその実際の活用シーンについてご紹介します。</p>
<h3 id="コード生成開発支援ツール-cursorgithub-copilotclinewindsurf">コード生成・開発支援ツール: Cursor、GitHub Copilot、Cline、Windsurf</h3>
<p>元々 GitHub Copilot は全社導入していましたが、最近では Cursor をはじめとする AI エージェントや AI 搭載コードエディタも全社で利用できる環境を整えています。</p>
<p>これらのツールは単なるコード補完にとどまらず、開発体験そのものを変革しています。</p>
<h3 id="思考支援問題解決ツール-claudechatgptgemininotebooklm">思考支援・問題解決ツール: Claude、ChatGPT、Gemini、NotebookLM</h3>
<p>対話型 AI は、思考のパートナーとして日々活躍しています。特に複雑な課題に直面した際、これらのツールを活用して問題解決の糸口を見つけています。</p>
<p>UIUX のプロトタイピングにもこれらのツールが大いに貢献しています。特に artifact 生成機能を活用することで、アイデアから視覚的な UI 案まで素早く作成でき、PdM・デザイナー・エンジニア間のコミュニケーションが格段にスムーズになっていく兆しを感じています。</p>
<h3 id="自律型開発エージェント-devin">自律型開発エージェント: Devin</h3>
<p>Devin は、開発スタイルに新たな可能性をもたらしています。「非同期での開発」を可能にし、開発者が他のタスクに取り組んでいる間でも、定型的な開発作業や特定の機能実装を進めることが現実となってきています。</p>
<p>検証初月で約 1,500 ACUs(約 375 時間相当)の活用実績があり、Devin を活用しているデベロッパーも現在 80 名を超えています。今後も予算の一部を Devin に割り当て、さらなる活用強化を進めています。</p>
<h3 id="ai-開発プラットフォーム-dify">AI 開発プラットフォーム: Dify</h3>
<p>Dify のようなプラットフォームは、社内の AI 活用の実験場となっています。様々なモデルやプロンプトの検証、複雑な AI ワークフローの構築など、AI の可能性を探求する基盤として活用しています。</p>
<h2 id="ai-ツール活用の今後の基本方針">AI ツール活用の今後の基本方針</h2>
<p>これらのツールを適材適所で組み合わせることで、開発プロセス全体の効率化を実現し、全社員が AI を自分のパートナーとして活用できる環境づくりを進めています。</p>
<p>その際、私たちが特に重視している点は以下の 2 つです:</p>
<ul>
<li>単なるツール導入にとどまらず、効果的な活用方法のナレッジを組織内で共有し、継続的に改善していくこと</li>
<li>FourKeys を軸に開発生産性をモニタリングし、データに基づいた改善を進めること</li>
</ul>
<p>まだ過渡期ではありますが、すでに複数のチームで開発サイクルの短縮や品質向上といった、生産性向上の明確な兆候が表れ始めています。</p>
<h1 id="推進検証中の領域">推進・検証中の領域</h1>
<p>以下の領域は現在積極的に推進・検証中です。成果が出次第、順次アウトプットしていきます。</p>
<h3 id="aiqa">AI×QA</h3>
<ul>
<li>AI を活用したテスト自動生成によるテストカバレッジの向上と QA 工数の削減</li>
<li>エッジケースの自動検出機能による人間が見落としがちなバグの早期発見</li>
</ul>
<h3 id="ai-データマネージメント">AI× データマネージメント</h3>
<ul>
<li>大規模データセットからのインサイト抽出と意思決定支援</li>
<li>データクレンジングと前処理の自動化によるデータ品質向上</li>
</ul>
<h3 id="ai-ファーストの開発フロー">「AI ファースト」の開発フロー</h3>
<ul>
<li>AI が理解しやすいコード構造とドキュメント体系の標準化</li>
<li>ナレッジやルールを AI が理解しやすい形で蓄積・管理</li>
</ul>
<h3 id="mcp-の利用ルールとガードレールの整備">MCP の利用ルールとガードレールの整備</h3>
<ul>
<li>安全かつ効率的な AI 活用のための社内 MCP ポリシーとガードレール設計</li>
<li>ドメイン知識を組み込んだ自社 MCP の開発と展開</li>
</ul>
<h3 id="ai-活用時代のエンジニア採用人材要件">AI 活用時代のエンジニア採用・人材要件</h3>
<ul>
<li>AI ツールを効果的に活用できる思考力と指示能力を重視した採用基準</li>
<li>技術的深さと AI との協業スキルを評価する新たな人材ペルソナ</li>
</ul>
<h3 id="ai-関連費用対効果費用モニタリング">AI 関連費用対効果・費用モニタリング</h3>
<ul>
<li>各種ツールのコスト・モデルのコストなど様々なコスト管理</li>
<li>生産性モニタリング</li>
</ul>
<h1 id="医療-ai-の可能性">医療 ×AI の可能性</h1>
<p>医療現場では従来から画像診断や遺伝子解析などの臨床領域で AI 活用が進んでいますが、その他の業務領域にも AI を取り入れることで、さらなる効率化や価値創出の可能性が広がっていると考えています。</p>
<p>患者情報の入力や整理、診療記録の作成、医療スタッフ間のコミュニケーション、スケジュール管理など、医療現場の日常業務に AI を活用することで、医療従事者の負担を軽減し、本来の患者ケアにより集中できる環境づくりにつながる可能性があると思います。</p>
<p>メドレーは医療 DX から医療 AX へと進化する中で、医療従事者と患者の双方に価値を提供できる独自のポジションにあります。今後も AI を活用した新しいプロダクトラインアップを発表予定ですので、ご期待ください!</p>
<h2 id="were-hiring">We’re hiring</h2>
<p>メドレーでは一緒に働く仲間を大募集しています。
<strong>AI と共に成長したいエンジニアの方</strong> 、ぜひお話させてください!</p>
<p>カジュアル面談も実施しております。話だけでも聞いてみたい!ちょっと雑談してみたい!でも構いませんので、お気軽にお問い合わせください!</p>
<p>募集の一覧</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>医療エンジニアリング領域盛り上がっています!メドレーについてお話します!</p>
<div class="remark-link-card-plus__container">
<a href="https://pitta.me/matches/YpnGRBGYqYtR" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メドレーのエンジニア組織・AI活用についてお話します!</div>
<div class="remark-link-card-plus__description">「メドレーのエンジニア組織・AI活用についてお話します!」- 倉林 昭和さん</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://pitta.me/static/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">pitta.me</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://res.cloudinary.com/meety-inc/image/upload/v1754536828/meety/prod/ogp_images/ba6e9twkq3ecg9ld9vz2.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>AI
- RubyKaigi 2025 参加レポート - Platinum Sponsor として協賛しました!https://developer.medley.jp/entry/2025/04/23/144812https://developer.medley.jp/entry/2025/04/23/144812はじめに
こんにちは! 人材プラットフォーム本部プロダクト開発室 第一開発グループ所属の山下です。
メドレーには今年2月に入社したエンジニアで、日本最大級の医療介護求人サイト ジョブメドレー の開発を担当しています。
メドレーは 4 月 1...Wed, 23 Apr 2025 06:00:00 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは! 人材プラットフォーム本部プロダクト開発室 第一開発グループ所属の山下です。
メドレーには今年2月に入社したエンジニアで、日本最大級の医療介護求人サイト <a href="https://job-medley.com/">ジョブメドレー</a> の開発を担当しています。</p>
<p>メドレーは 4 月 16 日から 18 日に 愛媛県松山市の <a href="https://www.kenbun.jp/">愛媛県県民文化会館</a> にて開催された <a href="https://rubykaigi.org/2025/">RubyKaigi 2025</a> に Platinum Sponsor として協賛しました!
RubyKaigi 2025 は Ruby をテーマとした国際的なカンファレンスで、世界中から様々な Ruby エンジニアが集う大規模なイベントです。
入社したての私も含め、エンジニアとエンジニア採用担当の計 13 名が現地参加し、たくさんの方々と交流させて頂きました。
今回は会場・ブースと発表の様子をご紹介します。</p>
<h1 id="会場の様子">会場の様子</h1>
<p>当日は3日間とも天候に恵まれ穏やかな春の日和の中、RubyKaigi 2025 が行われました。
愛媛県民文化会館は松山市の道後温泉と大街道にほど近い立地で、県内最大級の文化施設の一つです。メインホールには2,725席を有しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./gaikan.jpg","alt":"","index":0}">
<em>重厚で堅牢な印象を与える外観</em></p>
<p>100を超える企業の協賛のもと、1,518名ものRubyistが参加。歴代でも最大規模のイベントとなりました!</p>
<p><img __ASTRO_IMAGE_="{"src":"./jounai.jpg","alt":"","index":0}">
<em>広々として開放感のある場内</em></p>
<p>参加者に配布された公式グッズは、オリジナルTシャツに加えて御朱印帳や砥部焼の箸置き、松竹錠の形をしたアクリルキーホルダーなど、開催地・愛媛県の特色を活かしたグッズとなっていました。
<img __ASTRO_IMAGE_="{"src":"./official_novelty.jpg","alt":"","index":0}"></p>
<h1 id="弊社ブースの様子">弊社ブースの様子</h1>
<p>弊社は会場に入ってすぐ正面の場所にブースを設置させていただきました。</p>
<p>みなさんが感じている医療DXの課題をアンケートでヒアリングし、弊社がそれらの課題に技術を活用してどのように取り組んでいるかをご説明しました。
また、<a href="https://x.com/medley_dev">弊社公式X</a> をフォローしていただいた方や、弊社のイベントやテックブログなどの情報をご案内するメーリングリストに登録していただいた方にノベルティも差し上げました。医療事業を手がける企業としてのアピールも兼ねて、ノベルティには衛生キットと絆創膏をご用意しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./medley_novelty.jpg","alt":"","index":0}">
<em>オリジナルデザインの絆創膏も衛生キットも大人気でした!</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./communication1.jpg","alt":"","index":0}"></p>
<p>医療DXの課題アンケートで特に回答が多かったのが <strong>「待ち時間が長い」</strong> という課題でした。
メドレーの提供するオンライン診療・服薬指導アプリ「<a href="https://clinics-cloud.com/">CLINICS</a>」では病院や調剤薬局の予約、事前の問診票入力などが可能です。オンラインによる診療・服薬指導(薬剤師によるお薬の説明)を予約した場合は待ち時間だけではなく移動時間も削減されることや、Uber Eats との連携で服薬指導後、最短30分程度で自宅までお薬を届けてもらうことも可能なことをご説明しました。</p>
<p>また、直接的ではありませんが、待ち時間の長さには人員不足や院内オペレーション、利用システムも影響していることがあります。それらをジョブメドレーや医療機関向けのシステム(<a href="https://clinics-cloud.com/">CLINICS</a>、<a href="https://pcmed.jp/product-mall/">MALL</a>、<a href="https://pharms-cloud.com/">Pharms</a>、<a href="https://dentis-cloud.com/">Dentis</a>)を通じてサポートしていること、それぞれの課題に対して多様なサービスで様々なアプローチをとっていることなどをお話ししました。</p>
<p>メドレーブースにお越しいただいた皆様、ありがとうございました!
<img __ASTRO_IMAGE_="{"src":"./members.jpg","alt":"","index":0}">
<em>参加メンバー全員で</em></p>
<h1 id="発表の様子">発表の様子</h1>
<p>どのセッションも大変興味深く、一部難解な内容もありましたが、特に印象深かった下記のセッションについてご紹介します。</p>
<ul>
<li>Day1 Keynote: Ruby Taught Me About Encoding Under the Hood - Mari Imaizumi</li>
<li>Day1: Introducing Type Guard to Steep - Takeshi KOMIYA</li>
<li>Day2: Making TCPSocket.new “Happy”! Misaki Shioi</li>
<li>Day3: Matz Keynote</li>
</ul>
<h2 id="day1-keynote-ruby-taught-me-about-encoding-under-the-hood---mari-imaizumi">Day1 Keynote: Ruby Taught Me About Encoding Under the Hood - Mari Imaizumi</h2>
<p>Day1の基調講演は、@ima1zumiさんによる、普段意識せずに使っている「文字」のコンピュータ上での表現と、Rubyでの扱いに関するセッションでした。文字コードの歴史的背景から最新動向まで深く掘り下げられました。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/0a7e33e668e7481ab760985a6b03de0e" title="Ruby Taught Me About Under the Hood" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p><em>引用元: <a href="https://speakerdeck.com/ima1zumi/ruby-taught-me-about-under-the-hood">speakerdeck.com</a></em></p>
<h3 id="概要">概要</h3>
<h4 id="文字コードの進化">文字コードの進化</h4>
<p>文字符号化の歴史として、のろしやモールス信号から、コンピュータ時代の ASCII、EBCDIC が紹介されました。特にEBCDICでの日本語表現の困難さ( Shift-In/Out による 1バイト/2バイト 切り替え)の話は印象的で、現在の文字入力環境のありがたさを痛感しました。その後、世界中の文字を統一的に扱うUnicodeが誕生。「Universal」「Efficient」「Unambiguous」を目指し、UTF-8 や UTF-16 といったエンコーディング方式が普及しました。</p>
<h4 id="reline-の事例から見えた課題">Reline の事例から見えた課題</h4>
<p>Rubyの REPL ライブラリ「Reline」で、特定の絵文字 🧑🧑🧒 家族 を入力して Backspace を押すとクラッシュする問題がありました。この絵文字は見た目上1文字でも、内部的には複数の Unicode コードポイント(7つ)で構成されています。Reline がこれをバイト数で処理しようとしたため問題が発生しました。ここで重要なのが、ユーザーが「1文字」と認識する単位である「書記素クラスタ (Grapheme Cluster)」です。合成文字や結合絵文字のように、見た目の文字数と内部コードポイント数が一致しないケースが存在します。Reline の問題は、この書記素クラスタを正しく認識していなかった点にありました。</p>
<h4 id="ruby-における-unicode-サポート">Ruby における Unicode サポート</h4>
<p>Ruby は Unicode 15.1.0 仕様への準拠を進めています。Unicode標準のデータベースファイルを取り込み、内部処理に反映させています。正規表現エンジン「Onigmo」もUnicodeに対応しており、<code>\X</code>(書記素クラスタ)や <code>\p{Property}</code>(Unicodeプロパティ)といったメタ文字が利用できます。これにより、開発者は内部表現を意識せずとも直感的な操作が可能です。書記素クラスタの分割ルール(例:GB9c)にも準拠しようとしています。今後の課題としてUnicode 16.0.0への追従や正規化対応が挙げられました。</p>
<h3 id="感想">感想</h3>
<p>文字コードの奥深さ、支える技術、そして Ruby の対応を具体例と共に学べました。文字を入力し表現できていることが当たり前ではないこと、Unicode の複雑さに対応し、開発者が直感的に文字を扱えるようにする Ruby コミュニティの継続的な努力に感銘を受けました。今後の Unicode 標準と Ruby の進化に注目していきたいです。</p>
<h2 id="day1-introducing-type-guard-to-steep">Day1: Introducing Type Guard to Steep</h2>
<p>Takeshi KOMIYA さんによるセッションでは、Ruby の静的型チェッカー Steep における型ガード機能の改善と今後の展望が語られました。</p>
<iframe frameborder="0" src="https://drive.google.com/file/d/1VpFDEG0ZOghhvAYlTrthzD9QdlXbCbSn9MUd2FCGGC4/preview" title=" Introducing Type Guard to Steep" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 420;" data-ratio="1.3333333333333333"></iframe>
<p><em>引用元 <a href="https://drive.google.com/file/d/1VpFDEG0ZOghhvAYlTrthzD9QdlXbCbSn9MUd2FCGGC4/preview">drive.google.com</a></em></p>
<h3 id="概要-1">概要</h3>
<h4 id="これまでの-steep-と型ガードの課題">これまでの Steep と型ガードの課題</h4>
<p>Steep は型誤りを防ぐツールですが、従来の型ガードではRubyで多用される<code>present?</code>メソッドのような存在確認と型絞り込みを兼ねるパターンが機能せず、不必要な型エラーが発生し、開発体験を損なう一因となっていました。</p>
<h4 id="union-type-に対する型ガードの強化">Union Type に対する型ガードの強化</h4>
<p>この課題に対応するため、Steep 1.10 以降で UnionType(例: <code>String | nil</code>)への型ガードが強化されました。設定記述により<code>present?</code>のようなメソッド呼び出しで nil でない型へ絞り込めるようになり、Ruby らしい自然なコードで型チェックの恩恵を受けられます。この機能は開発者定義のメソッドにも適用可能で、柔軟性が向上しました。</p>
<p>また、より直感的な型ガード宣言のため、新しいアノテーション構文 <code>a{guard: self is AdminUser}</code> が紹介されました。特定の条件下で変数の型が特定の型であることを Steep に伝え、複雑な条件分岐における型情報を明確にします。</p>
<h4 id="今後の展望">今後の展望</h4>
<p>将来的な拡張として「Conditional Types」構想が紹介されました。これは特定条件下でのカスタム型ガード適用や返り値型の限定を可能にする機能です。複数の型情報を組み合わせる機能(例: <code>UserAdmin & Publish</code>)も関連して検討されており、より高度で精密な型チェックが実現します。</p>
<h3 id="感想-1">感想</h3>
<p>Steep が開発者のフィードバックを取り入れ、より実践的に進化していると感じました。Guard アノテーションや Conditional Types など、今後の機能追加にも期待が持てます。Ruby における静的型チェックがより身近で強力なものになりつつあり、Steep の活用を検討したいと思いました。</p>
<h2 id="day2-making-tcpsocketnew-happy">Day2: Making TCPSocket.new “Happy” !</h2>
<p>Misaki Shioi さんによるセッションでは、Ruby 標準ライブラリのTCP接続改善、特に<code>TCPSocket.new</code>への Happy Eyeballs Version 2 (HEv2) 実装の経緯と技術的挑戦が語られました。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/9b22e554321e48f88a39c3ef31c4970d" title="Making TCPSocket.new "Happy"!" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p><em>引用元: <a href="https://speakerdeck.com/player/9b22e554321e48f88a39c3ef31c4970d">speakerdeck.com</a></em></p>
<h3 id="概要-2">概要</h3>
<h4 id="tcpsocketnew-を-happy-にする試み">TCPSocket.new を “Happy” にする試み</h4>
<p><code>TCPSocket.new</code>への Happy Eyeballs Version 2 (HEv2, RFC8305) の実装について。HEv2はIPv4/IPv6両対応ホストへの接続を高速化する技術で、名前解決を並行し、早く応答があった方へ接続することで遅延を最小化します。Shioiさんは先行して<code>Socket.tcp</code>にHEv2を実装した経験を活かし、より低レイヤーの<code>TCPSocket.new</code>への改善に挑みました。</p>
<h4 id="課題">課題</h4>
<p>先行実装した<code>Socket.tcp</code> 自体の課題(並行処理によるステートマシンの問題)に直面し、if文ベースのロジックに再実装して解決しました。この知見をもとに<code>TCPSocket.new</code>(C言語)へのHEv2実装に着手しましたが、RubyとCでの名前解決ライブラリの挙動差異など、新たな壁にぶつかりました。プラットフォーム間の差異吸収など、C言語レベルでの細かな調整を経てプルリクエストを完成させました。</p>
<p>プルリクエストのマージ後も、CI環境でのテスト失敗やユーザーからのバグ報告など、リリース直前まで予期せぬ問題への対応に追われました。これらの困難を乗り越え、この改善はRuby 3.4に無事に取り込まれました。</p>
<h3 id="感想-2">感想</h3>
<p>HEv2実装の一連の取り組みが、時系列で非常に分かりやすく解説されました。<code>Socket.tcp</code>の再実装から<code>TCPSocket.new</code>への実装、リリースまでの課題と解決策を追体験できました。実装コードの引用も多く、理解の助けになりました。ネットワーク低レイヤーの改善は Ruby エコシステム全体の品質向上に繋がる重要な取り組みであり、普段利用するTCP接続の裏側で行われているパフォーマンスと信頼性向上のための緻密な努力に感銘を受けました。</p>
<h2 id="day3-matz-keynote">Day3: Matz Keynote</h2>
<p><img __ASTRO_IMAGE_="{"src":"./matz_keynote.jpg","alt":"","index":0}"></p>
<p>RubyKaigi 2025 の最後は Matz 氏によるセッションでした。ステージのせり上がるギミックを利用して堂々と登場し、会場を驚きと笑いで包んだ Matz 氏は、現代の開発環境と Ruby の未来について語りました。</p>
<p>AIによる開発支援が急速に進む現代において、本来「楽しい」はずのプログラミングが、AIへの指示出し作業に終始し、人間がまるでAIの「しもべ」であるかのように勘違いしてしまう……。そんな「逆アルファシンドローム」に陥らないよう、Matz 氏は注意を促しました。Ruby の根幹にある「プログラミングを楽しむ」という精神を、技術が進化する今だからこそ、改めて胸に刻むべきだと語りました。</p>
<p>一方で、AIとの協調も未来の重要なテーマです。Matz氏は、将来の高度なAIとのコミュニケーションにおいて、Ruby が持つ「簡潔さ」「豊かな表現力」「高い拡張性」といった特性が、理想的なインターフェースとなり得るとしました。そして、AIがより良く学習できるよう、モダンで質の高い Ruby コードをコミュニティ全体で積極的に公開していくことの重要性を提言しました。</p>
<p>そして Ruby 自体の進化についても、RuboCop、IRB や Parser の進化といった開発者体験を向上させるツールの充実、YJIT/ZJIT や Deoptimization といった技術による着実なパフォーマンス向上についても触れ、Ruby がより強力で使いやすい言語へと進化し続けていることを強調しました。</p>
<p>また、Matz 氏は改めて Ruby の強さの源泉はコミュニティにあると述べました。多様なバックグラウンドを持つ人々を歓迎するオープンな姿勢と、コミュニティ全体で Ruby という言語を育てていく文化こそが、Rubyを特別なものにしています。</p>
<p>さらに、今年の12月に迎えるRubyの30周年を記念して、Ruby4.0 のリリースを予定していることを発表していました。</p>
<p>昨今のAIの目覚ましい発展は、エンジニアとして非常に心躍るものである一方、一抹の不安を感じさせる面もあります。しかし Matz 氏の講演は、むしろ Ruby と共に新しい未来を切り拓いていく希望を与えてくれるものでした。Ruby の「楽しさ」という原点を決して忘れず、AIを強力なパートナーとして、より創造的な開発を追求していきたいと、決意を新たにしました!</p>
<h2 id="さいごに">さいごに</h2>
<p>Ruby の可能性を肌で感じ、熱意ある仲間たちと出会い、技術への情熱を改めて確認できた素晴らしいイベントでした。<br>
運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございます!</p>
<p>来年は北海道・函館での開催を予定しているそうです。
メドレーには はこだて未来大学出身のメンバーも在籍しており、学内セミナーへの参加も積極的に行なっております。ゆかりある地での開催に向けて、今から非常に楽しみです。
私たちはこれからも Ruby コミュニティとともにさらに成長し、チャレンジし続けます!</p>
<h3 id="after-rubykaigi-2025-を開催します">After RubyKaigi 2025 を開催します!</h3>
<p><img __ASTRO_IMAGE_="{"src":"./after_event.jpg","alt":"","index":0}">
<em><a href="https://increments.connpass.com/event/351891/">https://increments.connpass.com/event/351891/</a></em></p>
<p>メドレーは同じくRubyKaigi スポンサー企業である Qiita社・OPTiM社 との合同開催によるアフターイベントを 5 月 14 日 に行います。<br>
RubyKaigi の思い出や Kaigi Effect を受けて挑戦したことなど、楽しく話し合いましょう!</p>
<p>メドレーではこのようなイベントの開催のほかにも、Roppongi.rb など地域コミュニティや技術カンファレンスのスポンサーなどを通して、技術とコミュニティの発展を応援しています。</p>
<h3 id="メドレーではエンジニアを積極採用中です">メドレーではエンジニアを積極採用中です!</h3>
<p>メドレーでは領域を問わず、Ruby を積極的に活用して医療ヘルスケアの未来をつくるプロダクトを開発しています。<br>
Ruby を活用した医療ヘルスケア領域の課題解決に興味がある方は、ぜひお気軽にご連絡ください!</p>
<p>募集の一覧 <a href="https://www.medley.jp/jobs/">https://www.medley.jp/jobs/</a><br>
※カジュアル面談ご希望の際は、<その他>にてその旨をご記載ください</p>
- Docker DesktopからOrbStackへ移行してみたhttps://developer.medley.jp/entry/2025/03/25/151147https://developer.medley.jp/entry/2025/03/25/151147はじめに
こんにちは。人材プラットフォーム本部 第一開発グループの坂井(@Hiroshi_mars)です。
2024 年 12 月に主にバックエンドのエンジニアとしてメドレーに入社しました。
入社して日が浅いですが、この記事を通じて少しでも...Tue, 25 Mar 2025 06:11:00 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは。人材プラットフォーム本部 第一開発グループの坂井(<a href="https://x.com/Hiroshi_mars">@Hiroshi_mars</a>)です。
2024 年 12 月に主にバックエンドのエンジニアとしてメドレーに入社しました。
入社して日が浅いですが、この記事を通じて少しでもためになったりメドレーについて知っていただけたら嬉しいです。</p>
<p>今回、私は、Docker Desktop for Mac から OrbStack へ移行するプロジェクトを担当しました。以降、Docker Desktop は Docker Desktop for Mac を指します。</p>
<ul>
<li>対象プロダクト: <a href="https://job-medley.com/">ジョブメドレーのサイトページ</a>や社内オペレーターが利用するサイト、施設の方々が利用するサイトなど。</li>
<li>期間: 2~3 週間</li>
</ul>
<h1 id="orbstack-とは">OrbStack とは</h1>
<p><a href="https://orbstack.dev/">公式</a></p>
<p>OrbStack とは</p>
<blockquote>
<p>OrbStack is the fast, light, and easy way to run Docker containers and Linux. Develop at lightspeed with our Docker Desktop alternative. (公式サイトから引用)</p>
</blockquote>
<p>OrbStack は、Docker コンテナおよび Linux を高速かつ軽量に、容易に実行するためのツールです。
と書かれています。
Docker Desktop の代替として注目されています。</p>
<h2 id="orbstack-の特徴">OrbStack の特徴</h2>
<ol>
<li>Docker Desktop より高速で軽量</li>
<li>既存の Docker コマンドや docker-compose 系のコマンドも利用可能</li>
<li>Docker Desktop からの移行が容易</li>
</ol>
<p>CPU やメモリの使用量が抑えられ、既存の Docker コマンドとの互換性も高いため、移行や運用の負担が少ない点が大きなメリットです。</p>
<h1 id="orbstack-へ移行した理由">OrbStack へ移行した理由</h1>
<p>大きく二点あります。</p>
<h2 id="orbstack-へ移行した理由-その-1">OrbStack へ移行した理由 その 1</h2>
<p><strong>【OrbStack と Docker Desktop の商用利用の料金差】</strong></p>
<ul>
<li>OrbStack の料金は<a href="https://orbstack.dev/pricing">こちら</a></li>
<li>Docker Desktop の料金は<a href="https://www.docker.com/ja-jp/pricing/">こちら</a></li>
</ul>
<p>今まで Docker Desktop の<strong>Docker Team</strong>のプランで商用利用していました。しかし、ありがたいことに開発メンバーが増加し、管理できるアカウント数の上限を超えてしまいそうになりました。</p>
<p>Docker Desktop のプランを<strong>Docker Team</strong>から<strong>Docker Business</strong>に上げると 1 ユーザーあたりの月額利用料が 15 ドルから 24 ドルに増加します。
そのため、「プランのランクを一つ上に上げる」or「別のツールに移行する」という選択がありました。</p>
<p>「プランのランクを一つ上に上げる」のも可能ではありましたが、<a href="#orbstack-%E3%81%B8%E7%A7%BB%E8%A1%8C%E3%81%97%E3%81%9F%E7%90%86%E7%94%B1-%E3%81%9D%E3%81%AE-2">OrbStack へ移行した理由 その 2</a>に記載されている通りどうやら移行難易度や工数コストが低そうということもあって、「別のツールに移行する」選択をとりました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./cost_comparison.png","alt":"","index":0}"></p>
<p>この判断はメドレーの行動原則である(<a href="https://www.medley.jp/team/culture.html">Our Essentials</a>)の中の**『日々の倹約と大胆な投資の両立』**に適っています。
無駄なコストを落とせるのなら落とし、落とした分だけ別の大胆な行動に利用する。そういった行動原則に基づいた動きでもあったかと思います。
逆に、移行による工数コストがかなり高かった場合、Docker Desktop のプランを上げる方針になっていた可能性もあります。</p>
<h2 id="orbstack-へ移行した理由-その-2">OrbStack へ移行した理由 その 2</h2>
<p><strong>【OrbStack へ移行が容易】</strong></p>
<p>ブログなどの各種情報源において、OrbStack への移行手順は容易であり移行にかかる工数が低いことが報告されています。</p>
<blockquote>
<p>他ブログの一例: <a href="https://qiita.com/shoki-y/items/4b8e48a525062a8ec9ad">https://qiita.com/shoki-y/items/4b8e48a525062a8ec9ad</a></p>
</blockquote>
<p>また、社内の別プロダクトで既に移行した実績がありました。 社内の別プロダクトの技術スタックは、今回の移行対象と近しいものでした。</p>
<p>そのため、社内の別プロダクトの移行を行なった方にヒアリングした際も移行コストが小さいことがわかりました。</p>
<p>移行作業に伴う環境差異やそれに伴う工数負担は大きな課題となりやすいですが、OrbStack ではそれらの負担が少ないことが社内、社外の知見調査によって見込まれたため、移行を決断するに至りました。</p>
<h1 id="orbstack-への移行手順">OrbStack への移行手順</h1>
<h2 id="1-orbstack-をインストール">1. OrbStack をインストール</h2>
<p>インストール方法は 2 種類あります。</p>
<ol>
<li>Homebrew を使ったインストール</li>
</ol>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">brew</span><span style="color:#CE9178"> install</span><span style="color:#CE9178"> orbstack</span></span></code></pre>
<p>2.<a href="https://orbstack.dev/download">公式サイト</a>から dmg 形式のファイルをダウンロードする</p>
<h2 id="2-orbstack-の起動">2. OrbStack の起動</h2>
<p>インストール後、Finder のアプリケーションから OrbStack のアイコンを探し、クリックし起動。起動後、以下のような画面が出てきます。
<img __ASTRO_IMAGE_="{"src":"./OrbStack_menu.png","alt":"","index":0}">
Docker, Kubernetes, Linux どれを利用するのか選択します。 今回は、Docker Desktop から OrbStack に移行するので、Docker を選択します。</p>
<p><img __ASTRO_IMAGE_="{"src":"./OrbStack_top.png","alt":"","index":0}">
Docker を選択すると上記のようなトップ画面が出ます。</p>
<h2 id="3-docker-desktop-から-orbstack-へコンテナやイメージの情報を移行する">3. Docker Desktop から OrbStack へコンテナやイメージの情報を移行する</h2>
<p>2 種類方法があります。</p>
<ol>
<li>トップ画面に表示されている”Migrate from Docker Desktop”をクリックする</li>
<li>上部メニューから”File”->“Migrate Docker Data”をクリックする
Docker Desktop 上のコンテナ、ボリューム、イメージが OrbStack に移行されます</li>
</ol>
<p>*2 のメニューについての画面
<img __ASTRO_IMAGE_="{"src":"./migrate_menu.png","alt":"","index":0}"></p>
<p>migrate した後、下記のようなポップアップが出ることがあります。
<img __ASTRO_IMAGE_="{"src":"./migrate_error_mosaic.png","alt":"","index":0}"></p>
<p>内容はモザイクをつけていますが、下記のようなログが出力される場合があります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">container</span><span style="color:#CE9178"> /~:</span><span style="color:#CE9178"> create</span><span style="color:#CE9178"> container:</span><span style="color:#D4D4D4"> [Docker] No such image: ...</span></span></code></pre>
<p>これは、一部のコンテナやイメージの移行に失敗していることを示しています。</p>
<p>今回の移行に関してはこのエラーメッセージを migrate 時に解消しなくても問題ありませんでした。理由としては、エラー対象が自身で以前作成したデータなどが migrate できなかったのみで、後ほど再度作成すればよかったためです。</p>
<h3 id="4-docker-コンテキスト情報の確認と変更">4. Docker コンテキスト情報の確認と変更</h3>
<p>migrate が完了したら、“Docker Desktop”と”OrbStack”どちらを利用して Docker Daemon にアクセスするのか確認する必要があります。</p>
<p>次のコマンドでコンテキストを確認します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">docker</span><span style="color:#CE9178"> context</span><span style="color:#CE9178"> ls</span></span></code></pre>
<p>出力例:</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> docker</span><span style="color:#CE9178"> context</span><span style="color:#CE9178"> ls</span></span>
<span class="line"><span style="color:#DCDCAA">NAME</span><span style="color:#CE9178"> DESCRIPTION</span><span style="color:#CE9178"> DOCKER</span><span style="color:#CE9178"> ENDPOINT</span><span style="color:#CE9178"> ERROR</span></span>
<span class="line"><span style="color:#DCDCAA">default</span><span style="color:#CE9178"> Current</span><span style="color:#CE9178"> DOCKER_HOST</span><span style="color:#CE9178"> based</span><span style="color:#CE9178"> configuration</span><span style="color:#CE9178"> unix:///hogehoge</span></span>
<span class="line"><span style="color:#DCDCAA">orbstack</span><span style="color:#569CD6"> *</span><span style="color:#CE9178"> OrbStack</span><span style="color:#CE9178"> unix:///hogehoge</span></span></code></pre>
<p>”*“が今利用しているコンテキストになります</p>
<p>“Docker Desktop” が利用されている場合は次のコマンドで OrbStack に変更します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">docker</span><span style="color:#CE9178"> context</span><span style="color:#CE9178"> use</span><span style="color:#CE9178"> orbstack</span></span></code></pre>
<p>出力例:</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">%</span><span style="color:#CE9178"> docker</span><span style="color:#CE9178"> context</span><span style="color:#CE9178"> use</span><span style="color:#CE9178"> orbstack</span></span>
<span class="line"><span style="color:#DCDCAA">orbstack</span></span>
<span class="line"><span style="color:#DCDCAA">Current</span><span style="color:#CE9178"> context</span><span style="color:#CE9178"> is</span><span style="color:#CE9178"> now</span><span style="color:#CE9178"> "orbstack"</span></span></code></pre>
<h3 id="5-docker-コンテナの立ち上げ">5. Docker コンテナの立ち上げ</h3>
<p>Docker コンテナを起動して、正常に移行できたことを確認します。
完全に移行が完了したら Docker Desktop を削除しても問題ないかと思います。</p>
<p>コンテナの起動方法は各自の利用環境に応じて手順が異なるため、本稿では詳細を割愛します。</p>
<h1 id="ハマりポイントや苦労した点">ハマりポイントや苦労した点</h1>
<p>少しハマったポイントや苦労した点などを記載しています。ご参考になれば幸いです。</p>
<h2 id="docker-network-周りのエラー">Docker Network 周りのエラー</h2>
<p>Docker Desktop でコンテナを立ち上げて、作業していた場合、Docker Network を多かれ少なかれ利用されているかと思います。</p>
<p>Docker Desktop で作成していた Docker Network が存在していた場合、OrbStack で Docker Network を作成しようとすると同じ Docker Network を作成することになってしまい、ネームの被りなどが発生することがあります。
そのため、次のコマンドで Docker Network を確認します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">docker</span><span style="color:#CE9178"> network</span><span style="color:#CE9178"> ls</span></span></code></pre>
<p>不要なネットワークが存在していた場合、次のコマンドで削除します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">docker</span><span style="color:#CE9178"> network</span><span style="color:#CE9178"> rm</span><span style="color:#CE9178"> Hogehoge</span></span></code></pre>
<h2 id="マルチビルド周りのエラー">マルチビルド周りのエラー</h2>
<p>先ほど記載した<a href="#Docker-Network%E5%91%A8%E3%82%8A%E3%81%AE%E3%82%A8%E3%83%A9%E3%83%BC">Docker_Network 周りのエラー</a>と似たようなエラーになります。</p>
<p>Docker Desktop で作成済みのマルチビルド環境が OrbStack と競合する場合があります。その場合は既存のマルチビルド環境を削除し、OrbStack で新規にマルチビルド環境を構築することで問題を解消できます。</p>
<p>エラー内容</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">error:</span><span style="color:#CE9178"> Error</span><span style="color:#CE9178"> response</span><span style="color:#CE9178"> from</span><span style="color:#CE9178"> daemon:</span><span style="color:#CE9178"> Conflict.</span><span style="color:#CE9178"> The</span><span style="color:#CE9178"> container</span><span style="color:#CE9178"> name</span><span style="color:#CE9178"> "multibuild"</span><span style="color:#CE9178"> is</span><span style="color:#CE9178"> already</span><span style="color:#CE9178"> in</span><span style="color:#CE9178"> use</span><span style="color:#CE9178"> by</span><span style="color:#CE9178"> container</span><span style="color:#CE9178"> "container_id".</span><span style="color:#CE9178"> You</span><span style="color:#CE9178"> have</span><span style="color:#CE9178"> to</span><span style="color:#CE9178"> remove</span><span style="color:#D4D4D4"> (or </span><span style="color:#CE9178">rename</span><span style="color:#D4D4D4">) that container to be able to reuse that name.</span></span></code></pre>
<p>次のコマンドで、新たにマルチビルドを作成して利用できます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">docker</span><span style="color:#CE9178"> buildx</span><span style="color:#CE9178"> rm</span><span style="color:#CE9178"> multibuild</span></span>
<span class="line"><span style="color:#DCDCAA">docker</span><span style="color:#CE9178"> buildx</span><span style="color:#CE9178"> create</span><span style="color:#569CD6"> --name</span><span style="color:#CE9178"> multibuild_new_type</span></span>
<span class="line"><span style="color:#DCDCAA">docker</span><span style="color:#CE9178"> buildx</span><span style="color:#CE9178"> use</span><span style="color:#CE9178"> multibuild_new_type</span></span></code></pre>
<h2 id="コンテナとローカルのファイル差分が更新されない問題">コンテナとローカルのファイル差分が更新されない問題</h2>
<p>通常であれば、Volume を設定していれば、ローカルでファイルを新たに作成したり、mv, cp 等のコマンドでファイルを移したり、ファイル内を書き換えても Docker コンテナ内のファイルも更新されるかと思います。
それが今回、Docker コンテナ内でファイルが更新されない問題が起きていました。</p>
<p>こちらの問題は、原因追及にかなり苦労しました。</p>
<p>調査の結果、以前から利用していた docker-sync が OrbStack への移行後に<strong>正常に</strong>動作していないことがわかりました。</p>
<p>docker-sync とは、Docker 環境でローカルとコンテナ間のファイル同期を高速化するためのツールです。</p>
<p>原因の切り分けには苦労しましたが、次の手順で特定できました。</p>
<ul>
<li>先行して OrbStack に移行していた別部署の方と連携を取り、両者の環境の違いを聞いた中で docker-sync の利用の有無が挙げられた</li>
<li>ファイルの更新が反映されない問題であったため、ファイル同期を最適化する docker-sync が原因ではないかと推測した</li>
<li>docker-sync を使用しない環境で検証して問題の解消を確認した</li>
</ul>
<p>そのため、OrbStack への移行を機に docker-sync の利用をやめることで コンテナとローカルのファイル差分が更新されない問題を解決しました。</p>
<h1 id="移行後の-orbstack-の使用感">移行後の OrbStack の使用感</h1>
<ul>
<li>Docker Desktop より UI などがシンプルであり、操作が容易です。</li>
<li>migrates するだけで OrbStack に移行できる手軽さがよかったです。</li>
<li>商用利用の料金も Docker Desktop のプランをあげるより利用料が抑えられるので、良いかと思います。</li>
</ul>
<h1 id="まとめ">まとめ</h1>
<p>OrbStack 移行を実際にやってみて、思っている以上に容易に移行できたと実感しました。</p>
<p>もちろん Docker Desktop も良いかと思いますが、速度や利用料などを加味して、移行の選択肢として OrbStack も入ってきそうな気がしています。
また、OrbStack を実際に使ってみてください。実際に利用することでわかることが多く出てくるかと思います。</p>
<h1 id="were-hiring">We’re hiring</h1>
<p>メドレーでは一緒に働く仲間を大募集しています!
カジュアル面談も実施しておりますので、お話しだけでも聞いてみたい!ちょっと雑談してみたい!でも構いませんので、お気軽にお問い合わせください!</p>
<p>募集の一覧
<a href="https://www.medley.jp/jobs/">https://www.medley.jp/jobs/</a></p>
<p>医療エンジニアリング領域盛り上がっています!メドレーについてお話します!
<a href="https://pitta.me/matches/BtcyDvCvUZtx">https://pitta.me/matches/BtcyDvCvUZtx</a></p>
<p>メドレーの開発チームについて知りたい方!ぜひお話ししましょう!
<a href="https://pitta.me/matches/sNeEHMdSLZpB">https://pitta.me/matches/sNeEHMdSLZpB</a></p>
- テックブログを継続運営するためのメドレーの取り組みhttps://developer.medley.jp/entry/2025/02/26/104016https://developer.medley.jp/entry/2025/02/26/104016こんにちは、Jobley でエンジニアをしている新居です。
メドレーでは 2022 年 7 月から現在まで約 2 年半、テックブログをひと月に 1 記事以上のリズムで公開し続けてきました。
この記事では企業のテックブログを一定のリズムで継続...Wed, 26 Feb 2025 08:00:00 GMT<p>こんにちは、<a href="https://us.job-medley.com/">Jobley</a> でエンジニアをしている新居です。</p>
<p>メドレーでは 2022 年 7 月から現在まで約 2 年半、テックブログをひと月に 1 記事以上のリズムで公開し続けてきました。</p>
<p>この記事では企業のテックブログを一定のリズムで継続運営する方法を、メドレーの事例に沿って紹介します。テックブログを継続運営することにお困りの方の手助けになれば幸いです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./top.png","alt":"メドレーのテックブログのトップページ","index":0}">
<em>メドレーのテックブログのトップページ</em></p>
<h1 id="テックブログ運営にまつわる課題">テックブログ運営にまつわる課題</h1>
<p>テックブログ運営で最も難しいことは、継続的に記事を執筆し公開し続けることだと思います。</p>
<p>過去を振り返ると、今回紹介する方法をとる前も、コンスタントに記事を公開していました。しかし、記事を安定供給できる体制ではありませんでした。</p>
<p>具体的には、社内勉強会で発表した内容をテックブログにも書いてもらう運用をしていた時期があり、勉強会の準備とテックブログの執筆の両方を業務と並行して行うことは、執筆者にとって相当な負荷となっていました。執筆者の業務が忙しくなり、公開が滞るときもありました。</p>
<p>もちろん有志で執筆してもらうケースも多々ありましたが、このような負荷の高いルールに頼る運営は苦しく、長く続きませんでした。</p>
<p>それでもメドレーでは、採用活動の一環として社外のエンジニアやデザイナー、プロダクト開発に関わる方々にメドレーのことを知っていただきたいという目的があり、どうにかテックブログを継続できる方法を模索していました。</p>
<h1 id="テックブログを継続運営するための取り組み">テックブログを継続運営するための取り組み</h1>
<p>ここからは、上記の課題を改善し、メドレーで約 2 年半継続してきたテックブログ運営の方法を紹介します。</p>
<p>メドレーでは、テックブログ運営委員会を設立し、組織的に運営するというアプローチをとっています。</p>
<p>具体的にどのように運営しているのか、以下の順に紹介していきます。</p>
<ul>
<li>運営委員会のメンバー構成</li>
<li>運営委員会の活動</li>
<li>執筆推進の流れ</li>
<li>運営委員会の KPI</li>
<li>大切にしていること</li>
</ul>
<h1 id="運営委員会のメンバー構成">運営委員会のメンバー構成</h1>
<p>まずはメンバー構成についてです。</p>
<h2 id="リーダー必須">リーダー(必須)</h2>
<ul>
<li>テックブログや運営体制のビジョンを示すのが主な役目</li>
<li>**会社のブランディング戦略や採用強化中の職種・ポジションを理解し、公開する記事の方向性や品質をコントロールする必要があるため、**会社の意思決定者( CTO やマネージャー陣、また採用チームなど)と密に連携を取れる人が良い</li>
</ul>
<h2 id="コーディネーター必須">コーディネーター(必須)</h2>
<ul>
<li>記事執筆推進や定例のファシリテーション、議事録の共有が主な役目</li>
<li><strong>記事ネタの収集や執筆依頼を円滑に進行するため、各事業からコーディネーターを選出することがポイント</strong></li>
<li>メドレーでは人材プラットフォーム事業と医療プラットフォーム事業の 2 事業から、それぞれ 1 名ずつエンジニアを選出している</li>
</ul>
<h2 id="アドバイザー任意">アドバイザー(任意)</h2>
<ul>
<li>記事ネタの提案や運営体制の改善などが主な役目</li>
<li><strong>採用活動に対する解像度が高い人や会社全体を広く見ている人に協力してもらうと記事ネタの収集力が高まるため、適切に巻き込んでいくと良い</strong></li>
<li>メドレーでは採用チームの人や CTO 室所属の人にアドバイザーとして参加してもらっている</li>
</ul>
<p>現在メドレーではリーダー 1 名、コーディネーター 2 名、アドバイザー 3 名の計 6 名で運営委員会を構成しています。各メンバーは運営委員会とは別にメインの業務を持ちながら、兼任で活動しています。</p>
<p>この体制により、組織全体を縦と横に幅広くカバーでき、テックブログの運営を効率的に進めることができています。</p>
<p>特にコーディネーターについては、各事業から最低 1 名を選出することで、それぞれの事業内で行われた施策や活動を把握しやすくなり、組織規模の拡大にも対応しやすくなります。<strong>継続的な運営を実現するためには、業務の負担が特定の事業に偏らないような体制を整えることが大切です。</strong></p>
<p>チームで協力して活動することで、個人に依存しない仕組みも築くことができ、突発的な問題や課題が発生しても、メンバー全員で協力して解決できます。</p>
<h1 id="運営委員会の活動">運営委員会の活動</h1>
<p>続いて、運営委員会の中でやってることについてです。定例、振り返り、執筆推進が主になります。</p>
<h2 id="定例">定例</h2>
<ul>
<li>隔週で開催</li>
<li>アジェンダは前回決めたネクストアクションの状況共有、執筆状況の共有、次月以降の記事案の検討、その他議論共有</li>
<li><strong>ここでのポイントとして、スケジュールを逆算して次の記事案を必ず決めることが超重要(決まらないまま終わらない)</strong></li>
<li>例えば、記事を来月の最終営業日に公開する場合、遅くとも今月の中旬には記事案が確定し執筆者への合意もとれてる状態になってる必要があるので、スケジュールを逆算して次の記事案を必ず決める</li>
</ul>
<h2 id="振り返り">振り返り</h2>
<ul>
<li>年 1 回で実施</li>
<li>今年度を振り返り、次年度の継続アクション・改善アクションを決める</li>
<li>直近の振り返りでは、技術系記事の数を増やしたことによる PV 数の増加や、カジュアル面談の場でテックブログの話題になることで面談がしやすくなったという成果も確認でき、継続アクションのひとつとすることを決定できた</li>
</ul>
<h2 id="執筆推進">執筆推進</h2>
<ul>
<li>以下で詳細に説明</li>
</ul>
<h1 id="執筆推進の流れ">執筆推進の流れ</h1>
<p>続いて、執筆推進の流れを詳しく紹介します。</p>
<p>全体の流れは図の通りで、Step 1 から Step 3 までの流れで記事を作成していきます。</p>
<p>時間軸としては、Step 1 で記事の執筆依頼を終えた後、Step 2 を 3 ~ 4 週間くらいで、Step 3 を 2 週間くらいで完了させ、記事を公開するという感じで進めています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./flow.png","alt":"記事執筆の流れ","index":0}">
<em>記事執筆の流れ</em></p>
<p>では、Step 1 から順に説明していきます。</p>
<h2 id="step-1">Step 1</h2>
<h3 id="1-記事案の選定と決定">1. 記事案の選定と決定</h3>
<ul>
<li>担当: 運営委員会</li>
<li>定例で記事案を決定する</li>
<li>後述する頭出しのときに NG が出た場合に備えて候補をいくつか用意しておく</li>
<li>記事案のカテゴリ
<ul>
<li>技術系記事(メドレーならではの制約や困ったポイントなどがあれば、それらを明確にする)
<ul>
<li>例: <a href="https://developer.medley.jp/entry/2024/12/03/100122">トイルの削減も、情報漏洩リスクの削減も、両方手に入れる。IAM Identity Center は欲張りなんだ。</a></li>
</ul>
</li>
<li>インタビュー記事
<ul>
<li>例: <a href="https://developer.medley.jp/entry/2024/04/05/234106">FY22 新卒入社エンジニアはこの 1 年でどのような成長をしてきたのかインタビューしました</a></li>
</ul>
</li>
<li>イベント参加記事
<ul>
<li>例: <a href="https://developer.medley.jp/entry/2025/01/09/115753">pmconf 2024 にゴールドスポンサーとして協賛しました!</a></li>
</ul>
</li>
<li>etc</li>
</ul>
</li>
</ul>
<h3 id="2-記事案の頭出し">2. 記事案の頭出し</h3>
<ul>
<li>担当: コーディネーター</li>
<li><strong>執筆完了後に NG になるのを防ぐため、先に CTO へ頭出しを行う</strong></li>
<li>定例の議事録共有と合わせる形で、来月はこの記事で進める予定であることを共有する</li>
</ul>
<h3 id="3-執筆の事前依頼">3. 執筆の事前依頼</h3>
<ul>
<li>担当: コーディネーター</li>
<li>突然執筆を依頼して驚かれないように、執筆を正式依頼する前に執筆者に依頼の頭出しをしておく</li>
</ul>
<h3 id="4-執筆の正式依頼">4. 執筆の正式依頼</h3>
<ul>
<li>担当: コーディネーター</li>
<li><strong>執筆は業務と同じ扱いなので、Slack のパブリックなチャンネルで、メンションに CTO や執筆者の上長など関係者を含み、執筆を正式に依頼する</strong></li>
</ul>
<h2 id="step-2">Step 2</h2>
<h3 id="5-スケジュールの作成">5. スケジュールの作成</h3>
<ul>
<li>担当: コーディネーター</li>
<li>執筆、初稿提出、レビュー、最終レビューまでの流れをカレンダーに落とし込み、関係者に共有する</li>
<li><strong>締切を明確にしつつ、通常業務の合間でも最後まで執筆作業を走りきれるよう、しっかりコーディネーターが進捗確認などしつつフォローする</strong></li>
</ul>
<h3 id="6-執筆環境の構築">6. 執筆環境の構築</h3>
<ul>
<li>担当: コーディネーター</li>
<li>GitHub で記事を管理しているので、記事のベースファイルを作成し、Issue と Pull Request を用意する</li>
<li>執筆者の負担をなるべく減らし、記事執筆のみに集中してもらえるようにする</li>
</ul>
<h3 id="7-記事構成案の作成">7. 記事構成案の作成</h3>
<ul>
<li>担当: 執筆者</li>
<li><strong>さあ執筆しよう!という気持ちを抑え、わかりやすい記事構成となるよう、先に記事の構成案(章立てと各章の概要)を作り骨子を固める(コーディネーターが壁打ちなどサポート)</strong></li>
</ul>
<h3 id="8-執筆">8. 執筆</h3>
<ul>
<li>担当: 執筆者</li>
<li>作成した記事構成案をベースにして執筆する</li>
</ul>
<h2 id="step-3">Step 3</h2>
<h3 id="9-運営レビュー">9. 運営レビュー</h3>
<ul>
<li>担当: 運営委員会、その他関係者(執筆者の上長など)</li>
<li>誤字脱字の基本的な確認に加え、記事をより良くするための提案を行う</li>
<li>後続の最終レビューの負担を減らすために、ここでできる限り記事を公開可能な状態まで持っていく</li>
</ul>
<h3 id="10-最終レビュー">10. 最終レビュー</h3>
<ul>
<li>担当: 広報チーム、CTO</li>
<li>広報チームには広報視点で問題ないかを確認してもらい、CTO には網羅的な視点で問題ないかを最終確認してもらう</li>
<li>ここで記事を公開可能な状態に仕上げる</li>
</ul>
<h3 id="11-公開">11. 公開</h3>
<ul>
<li>担当: コーディネーター、執筆者、広報チーム</li>
<li>記事を公開し、社内外に共有する
<ul>
<li><a href="https://x.com/medley_dev">メドレーの公式 X アカウント</a>でも発信しているので、フォローよろしくお願いします</li>
</ul>
</li>
</ul>
<h1 id="運営委員会の-kpi">運営委員会の KPI</h1>
<p>続いて、運営委員会で設定している KPI についてです。</p>
<ul>
<li>KPI はシンプルに、ひと月に最低 1 記事以上を公開すること</li>
<li><strong>まずは継続することが大事なので、最初はシンプルな KPI にしておくこと。もし KPI を増やすなら継続できる体制が整ってから</strong></li>
</ul>
<h1 id="大切にしていること">大切にしていること</h1>
<p>最後に、ここまでで太字で強調したポイント以外に、運営上特に大切な 2 点を紹介します。</p>
<h2 id="テックブログをやる目的を明確にする">テックブログをやる目的を明確にする</h2>
<ul>
<li>何をやるにしても目的を定めて共通認識として持つことは大切ですが、テックブログの運営も同様に目的を明確にしてブレないようにする</li>
<li><strong>メドレーでは採用に繋げることを目的としており、テックブログを通じて社外の方々にメドレーことを知っていただき、解像度を上げてもらえるように努めている</strong></li>
<li>目的が明確だと記事案の検討にも軸ができ、例えばプロダクトマネージャーの採用を強化してるならプロダクトマネージャー向けの記事を用意したりという動きができる</li>
</ul>
<h2 id="執筆は業務であることを明確にする">執筆は業務であることを明確にする</h2>
<ul>
<li>記事執筆において軽視されがちな印象で、執筆が業務であることが当たり前のようで当たり前になってなかったりする</li>
<li>通常業務との兼ね合いは必要になるが、業務であるということはイコール業務中に時間を確保して取り組むべきことである</li>
<li>しかし、そこがふわっとしていると業務中に執筆し辛くなり、執筆がビハインドして公開が遅れる可能性が高くなる</li>
<li><strong>メドレーでは運営委員会の発足時に CTO から執筆は業務としてしっかり取り組んで欲しいという話があり、その文脈を含んで運営オペレーションを構築してきた背景がある</strong></li>
<li>例えば執筆依頼を執筆者の上長を含めて行ったり、皆が見える場所で依頼などのコミュニケーションをとったり、公開までのスケジュールを細かく引いて開発プロジェクトと同じように進めたり</li>
</ul>
<h1 id="おわりに">おわりに</h1>
<p>メドレーが約 2 年半継続してきたテックブログ運営の方法を紹介しました。</p>
<p>リーダーとコーディネーターを中心とした運営委員会により、組織全体を縦と横に幅広くカバーし、開発プロジェクトと同じように関係者との調整やスケジューリングを行い、運営にも執筆者にも負担が集中しない無理のない体制を築くことにより、テックブログを継続運営しています。</p>
<p>会社の規模やフェーズによってはそのまま取り入れるのは難しいかもしれません。エッセンスだけを抽出して会社のスタイルや風土に合う形にカスタマイズし、ミニマムに運営を始めて推進していくのも良いかもしれません。</p>
<p>今回の記事が、テックブログを継続運営することにお困りの方の参考になれば幸いです。</p>
<h1 id="were-hiring">We’re hiring</h1>
<p>メドレーでは一緒に働く仲間を大募集しています。カジュアル面談も実施しておりますので、少しでもご興味のある方はぜひご応募お待ちしております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- アドベントカレンダー2024を振り返ってみたhttps://developer.medley.jp/entry/2025/01/22/115430https://developer.medley.jp/entry/2025/01/22/115430こんにちは!メドレー DevRel の重田です。
2024 年 12 月に実施したアドベントカレンダー(以下、アドカレ)について、「実施の目的〜実施してどうだったのか」を振り返ろうと思います。
▼Medley(メドレー) Advent Ca...Wed, 22 Jan 2025 02:54:30 GMT<p>こんにちは!メドレー DevRel の重田です。<br>
2024 年 12 月に実施したアドベントカレンダー(以下、アドカレ)について、「実施の目的〜実施してどうだったのか」を振り返ろうと思います。</p>
<p>▼Medley(メドレー) Advent Calendar 2024<br>
<a href="https://qiita.com/advent-calendar/2024/medley">https://qiita.com/advent-calendar/2024/medley</a></p>
<p><img __ASTRO_IMAGE_="{"src":"./cal.png","alt":"実施したカレンダーの内容一覧。25日分全てが埋まっている","index":0}"></p>
<h1 id="なぜアドカレを実施したのか">なぜアドカレを実施したのか</h1>
<p>メドレーは採用・広報・テックブログ運営の各チームで連携して日々発信しています。</p>
<p>▼株式会社メドレー note<br>
<a href="https://note.com/medley">https://note.com/medley</a></p>
<p>▼テックブログ MEDLEY Developer Portal<br>
<a href="https://developer.medley.jp/entries/">https://developer.medley.jp/entries/</a></p>
<p>その中でアドカレを実施した理由は大きく二つです。</p>
<ul>
<li>社外に向けてメドレーエンジニア組織の情報発信量を増やすため</li>
<li>事業部を超えたエンジニア同士の知見/情報交換の機会を増やすため</li>
</ul>
<p>そして何より、アドベントカレンダーという年末行事をみんなでワイワイ楽しみたい気持ちで実施しました 🎅</p>
<p>こうした目的があり、テーマは絞らず社員一人一人のカラーが出るようにしました。<br>
(個人的にはカジュアルに楽しんで参加してもらいたい、テーマを絞ることでハードルを上げたくなかったという思いもありました)</p>
<h1 id="どうやって運営をしたのか">どうやって運営をしたのか</h1>
<h2 id="-はじめに決めたこと">▼ はじめに決めたこと</h2>
<p><strong>実施の目的</strong><br>
この取り組みの背景を明確にし、その目的をしっかりと定義することで、全員が共通認識を持って取り組めるようにしました。</p>
<p><strong>執筆ガイドラインの作成</strong><br>
社外に向けて発信する記事であるため、記載ルールを明確に設定しました。ガイドラインがあることで、レビュー時の手戻りを最小限に抑え、スムーズに運営することを目指しました。</p>
<p><strong>執筆場所(Qiita、Zenn、はてなブログなど)</strong><br>
執筆する媒体については、基本的に各自にお任せしました。各媒体での個人アカウントを用いて記事を作成し、その後、企業アカウントに紐付けることで管理を統一しています。</p>
<p><strong>執筆〜公開までのタスクをドキュメント化</strong><br>
執筆から公開に至るまでのタスクを具体的にドキュメント化しました。これにより、全員が同じフローに沿って円滑に作業を進めることができました。</p>
<h2 id="-気をつけたこと">▼ 気をつけたこと</h2>
<p>メドレーでは今回のアドカレをはじめ、社外へ情報発信をする際はしっかりとレビューをする体制を敷いています。<br>
アドカレについては、公開前に必ずエンジニア視点での技術レビューと広報視点でのレビューのダブルチェックを実施していました。また、公開後は CTO・テックブログ運営チームで最終チェックをしていました。<br>
今回のアドカレはカジュアルに楽しんで参加してもらう取り組みなので自由に執筆してもらいましたが、上場企業としてダブルチェックは欠かせません…!</p>
<h1 id="社内の反応">社内の反応</h1>
<p>スタート時は不安もありましたが、各方面から協力があり無事に枠を埋めることができました 🙌</p>
<p>「ブログ書こう!」と積極的に呼びかけてくれる場面もあり、メドレー社員の温かさにほっこりしていました…感謝です!</p>
<p><img __ASTRO_IMAGE_="{"src":"./slack1.png","alt":"Slackでの投稿内容1つ目。記載内容は以下の通り。「〇〇でも周知されてましたが、よければメドレーのアドベントカレンダーに寄稿してみませんか。今回は会社の developer blog に載せるというわけでもなく、 Qiita / Zenn / はてなブログ / 個人ブログ もOKと、かなりカジュアルに執筆できる環境です!〇〇も一本記事を書く予定で、初めてZennの環境構築してみて、うおーめっちゃ体験良い!、となったので、今後の自身の記事執筆のリズム作りの一発目とかにもよさそう。忙しいところはありつつ、内容はちょっとしたものとかでも全然構わない(多分)はずなので、〇〇チームからいっぱい出せたらなーと思っています!わくわく。こういうのが将来的に採用にも繋がると思っていて、内部からも外部からも魅力的な開発組織にしていきたいですー」記載内容ここまで。","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./slack2.png","alt":"Slackでの投稿内容2つ目。記載内容は以下の通り。「アドカレを埋め尽くすぞ!(有志)のカレンダーでも入れるか」記載内容ここまで。","index":0}"></p>
<p>また、以下のようにポジティブな反応をもらうことができました ✨</p>
<p><img __ASTRO_IMAGE_="{"src":"./positive.png","alt":"Slackでの投稿内容。記載内容は以下の通り。「アドベントカレンダー、チーム内のデイリースクラムで毎日紹介していいね押してもらうということをやっていて、毎日社内記事読んで感想言うの楽しいというチーム内の意見も出てきています!」記載内容ここまで。","index":0}"></p>
<p>最終的には目的であった、</p>
<ul>
<li>社外に向けてメドレーエンジニア組織の情報発信量を増やすため</li>
<li>事業部を超えたエンジニア同士の知見/情報交換の機会を増やすため</li>
</ul>
<p>のどちらも実現できたと考えています。</p>
<h1 id="最後に">最後に</h1>
<p>人気があった記事や個人的に面白かったブログをいくつかピックアップします。<br>
他にも素敵なブログがたくさんあるのでぜひご一読いただけると嬉しいです🙌✨</p>
<p>耳コピアプリの個人開発記録:ドキュメントドリブンで描くプロダクト設計 <a href="https://qiita.com/nayucolony">_@nayucolony</a><br>
<a href="https://zenn.dev/medley/articles/personal-project-with-document-driven">https://zenn.dev/medley/articles/personal-project-with-document-driven</a></p>
<p>トイルの削減も、情報漏洩リスクの削減も、両方手に入れる。IAM Identity Center は欲張りなんだ <a href="https://x.com/yujittttttt">_@yujittttttt</a>
<a href="https://developer.medley.jp/entry/2024/12/03/100122">https://developer.medley.jp/entry/2024/12/03/100122</a></p>
<p>私と地域 Ruby コミュニティとメドレーの関わり <a href="https://x.com/Kirika_K2">_@Kirika_K2</a><br>
<a href="https://developer.medley.jp/entry/2024/12/12/180014">https://developer.medley.jp/entry/2024/12/12/180014</a></p>
<p>エンジニアがユーザーヒアリングやってみた <a href="https://qiita.com/eriririri">_@eriririri</a><br>
<a href="https://qiita.com/eriririri/items/738274113dbc93e10d82">https://qiita.com/eriririri/items/738274113dbc93e10d82</a></p>
<p>ルビと格闘した話 <a href="https://qiita.com/rio-song">_@rio-song</a><br>
<a href="https://qiita.com/rio-song/items/94879fc82ce90253c5b4">https://qiita.com/rio-song/items/94879fc82ce90253c5b4</a></p>
<p>EM やテックリードにチャレンジすることになったら考えたい「時間」のこと <a href="https://ymzkmct.hatenablog.com/about">_@ymzkmct</a><br>
<a href="https://ymzkmct.hatenablog.com/entry/2024/12/22/100000">https://ymzkmct.hatenablog.com/entry/2024/12/22/100000</a></p>
<p>1,000 人を超える規模の組織で、全社生成 AI 推進プラットフォームとして Dify を導入し始めた話 <a href="https://x.com/terukura">_@terukura</a><br>
<a href="https://zenn.dev/medley/articles/54ad12a3557656">https://zenn.dev/medley/articles/54ad12a3557656</a></p>
<h1 id="were-hiring">We’re hiring</h1>
<p>メドレーでは一緒に働く仲間を大募集しています!<br>
カジュアル面談も実施しておりますので、少しでもご興味のある方はぜひご応募お待ちしております!</p>
<p>募集の一覧<br>
<a href="https://www.medley.jp/jobs/">https://www.medley.jp/jobs/</a></p>
<p>医療エンジニアリング領域盛り上がっています!メドレーについてお話します!<br>
<a href="https://pitta.me/matches/BtcyDvCvUZtx">https://pitta.me/matches/BtcyDvCvUZtx</a></p>
<p>メドレーの開発チームについて知りたい方!ぜひお話ししましょう!<br>
<a href="https://pitta.me/matches/sNeEHMdSLZpB">https://pitta.me/matches/sNeEHMdSLZpB</a></p>
<p>株式会社メドレーのデザインについてお話します!<br>
<a href="https://pitta.me/matches/vshCmTBHhzCQ">https://pitta.me/matches/vshCmTBHhzCQ</a></p>
- pmconf 2024にゴールドスポンサーとして協賛しました!https://developer.medley.jp/entry/2025/01/09/115753https://developer.medley.jp/entry/2025/01/09/115753
こんにちは。医療プラットフォーム本部で Product Manager をしている佐藤です。2024 年 5 月にメドレーにジョインし、医療機関向けプロダクト開発に奔走しています。社内では ”papa”、家では”おじさん”と呼ばれ可愛がら...Thu, 09 Jan 2025 02:57:53 GMT<!-- Edit here! -->
<p>こんにちは。医療プラットフォーム本部で Product Manager をしている佐藤です。2024 年 5 月にメドレーにジョインし、医療機関向けプロダクト開発に奔走しています。社内では ”papa”、家では”おじさん”と呼ばれ可愛がられています。</p>
<p>表題のとおり、メドレーは 2024/12/5-6 の 2 日間にわたって開催された『<a href="https://2024.pmconf.jp/">pmconf 2024</a>』にゴールドスポンサーとして協賛させていただきました。pmconf は 2016 年から開催されており、Product Manager 向けのカンファレンスとしては国内最大(2024 年 6 月時点)の規模を誇ります。プロダクトマネジメントに携わる人たちが共に学び・切磋琢磨する場として数々の発見・挑戦・苦悩が共有され、今年も大変刺激的な 2 日間となりました。</p>
<p>今回は Day 1 (オンライン配信)の舞台裏+ Day 2 (会場現地)の様子を、参加したメンバーの感想とあわせて皆さんにお伝えします!</p>
<h1 id="day-1オンライン配信">Day 1(オンライン配信)</h1>
<p>1 日目は YouTube Live でのオンライン開催。今年のテーマ “QUEST.” に沿って、日々の探究から生まれた濃密な経験や知見が 5 トラック・ 40 セッションに渡って展開されました。</p>
<p>メドレーからは私が登壇し、入社直後にぶつかった壁と、それを乗り越える過程で発見した『新任 PdM に求められる行動様式』について皆様にシェアする…つもりだったのですが…なんと当日全く声が出ないという想定外の事態に…。急遽 CLINICS の Product Manager 田所にプレゼンをお願いする展開となりました。</p>
<div style="text-align: center">
<iframe width="560" height="315" src="https://www.youtube.com/embed/rxiFKp14zgY?si=pnITGt2voqg3RtI7" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen>
</iframe>
</div>
<div style="font-style: italic; color: #2E3C4D; margin-bottom: 32px;">
(突然の代打登壇を抜群の安定感で進める田所と、恐縮そうな顔をしつつ本番中に手元で台本を update している私)
</div>
<p>ご視聴くださった皆様、本当にありがとうございます!そして突然の変更にも関わらず臨機応変に対応してくださった運営メンバーの皆様にも、この場を借りて御礼申し上げます。</p>
<p>なお Day 1 は前述のとおり 5 トラック構成なので「どれかを選ぶとその他は選べない」という Product Manager らしい意思決定が求められるのですが、後日 YouTube にアーカイブ動画がアップされるので安心して選んだセッションに集中することができます。今年のアーカイブもすでにアップされていますので、ぜひご視聴ください。</p>
<p><a href="https://www.youtube.com/@pmconf6320/videos">プロダクトマネージャーカンファレンス / pmconf</a></p>
<p>ちなみにメドレーが提供している患者向け総合医療アプリ「<a href="https://clinics-app.com/">CLINICS(クリニクス)</a>」を利用すると、例えば会議の合間にオンライン診療・オンライン服薬指導を受けられるのはもちろんのこと、その処方薬を薬局から Uber Eats が即日配送してくれるという多忙な皆さまに最適なサービスがスタートしました。
おかげで私も速やかに処方薬を服用することができ、重症化する前に声を取り戻せました。宣伝のために身体を張ったことにしておきます。
心からオススメです。</p>
<p><a href="https://www.medley.jp/release/20240328.html">メドレー、患者向け総合医療アプリ「CLINICS」で処方薬の当日配達を開始 〜Uber Eats との連携で、オンライン診療・服薬指導後に全国の Pharms 導入薬局を通じた即時配送が可能に〜 | 株式会社メドレー </a></p>
<h1 id="day-2-ベルサール羽田空港">Day 2 (ベルサール羽田空港)</h1>
<p>2 日目はオンサイト開催。
会場は空港ターミナル直結のベルサール羽田空港でした。
定員 300 名をゆうに超える申込みがあり早期にキャンセル待ちになっていましたが、それだけに参加されていた皆さんの熱量は想像以上でした。</p>
<p>メドレーからは人材プラットフォーム・医療プラットフォームそれぞれの PdM に採用・広報チームも加わり、メイン会場の展示ブースを中心に多くの皆様と交流させていただきました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./Group_167.png","alt":"ブースの様子1","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./Group_166.png","alt":"ブースの様子2","index":0}"></p>
<p>会場ではパネルディスカッションに始まり、ワークショップ形式の Open Space Technology (OST) 、ブースを巡るスタンプラリーなど多数のイベントが展開され、至るところでエネルギッシュなやり取りを見ることができました。
ワークショップ時間以外メドレーブースも終始盛況で、事業やプロダクトについて様々な角度から質問をいただきました(メンバー一同、脳にたくさん汗をかく 1 日でした)。</p>
<p><img __ASTRO_IMAGE_="{"src":"./IMG_3710.jpg","alt":"セッション01","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./Group_168.png","alt":"セッション02","index":0}"></p>
<h2 id="医療-ux-のここなんとかならない">医療 UX の「ここなんとかならない?」</h2>
<p><a href="https://developer.medley.jp/entry/2024/09/10/205504">iOSDC 2024</a> や <a href="https://developer.medley.jp/entry/2024/10/15/120419">DroidKaigi 2024</a> 同様、メドレーブースでは展開しているプロダクト群の紹介に加え、アンケートパネルを用いて参加者の方々に医療体験に関するお悩みを伺いました。</p>
<p>pmconf 2024 での結果はこちら!</p>
<p><img __ASTRO_IMAGE_="{"src":"./IMG_1451.jpg","alt":"セッション01","index":0}"></p>
<p><strong>待ち時間が長い!問診票を毎回書いたり、同じことを聞かれるのが面倒!病院探しに時間がかかる・オンラインで予約したい!</strong> …という項目に票が集まる結果となりました。</p>
<p>これらの課題は私たちメドレーが患者向けに提供している総合医療アプリ「CLINICS」で解決・解消を目指しているもので、すでに提供済みのオンライン予約・事前問診・キャッシュレス決済等のお話をさせていただくと「<strong>こんなに機能あるんですね</strong>」「<strong>めちゃくちゃ便利!</strong>」という反応をいただくことが多かったです。
嬉しい反面、価値を届けきるためのアクションをもっと積み重ねていかなくては…と痛感する時間でもありました。</p>
<h1 id="終わりに">終わりに</h1>
<p>メドレーはこれからも、医療ヘルスケアの未来をつくるために、様々なプロダクト・サービスを構想し、開発し、提供していきます。</p>
<p>最後に改めて、pmconf 2024 の運営の皆様、登壇されたスピーカーの皆様、参加者の皆様、お疲れ様でした!そして、刺激的な 2 日間をありがとうございました!</p>
<h1 id="メドレーでは一緒に働く仲間を募集しています">メドレーでは一緒に働く仲間を募集しています</h1>
<p>この記事やブースなどでメドレーに興味を持ってくださった皆さま、ぜひご連絡をお待ちしております。カジュアル面談などのアレンジも可能ですので、お気軽にお問い合わせください!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- 私と地域Rubyコミュニティとメドレーの関わりhttps://developer.medley.jp/entry/2024/12/12/180014https://developer.medley.jp/entry/2024/12/12/180014
こちらの記事は Medley(メドレー) Advent Calendar 2024 の12日目の記事です。
はじめに
はじめまして。医療プラットフォーム事業部 プロダクト開発室 Dentis開発グループでグループマネジャーをしている牧(X...Thu, 12 Dec 2024 09:00:14 GMT<!-- Edit here! -->
<p>こちらの記事は <strong><a href="https://qiita.com/advent-calendar/2024/medley">Medley(メドレー) Advent Calendar 2024</a></strong> の12日目の記事です。</p>
<h1 id="はじめに">はじめに</h1>
<p>はじめまして。医療プラットフォーム事業部 プロダクト開発室 Dentis開発グループでグループマネジャーをしている牧(X: <a href="https://x.com/Kirika_K2">@Kirika_K2</a>)と申します。</p>
<p>メドレーには2019年に入社し、以来、歯科医院向けのクラウドサービス<a href="https://dentis-cloud.com/">Dentis</a>の立ち上げ、プロダクト開発に携わっています。<br>
Dentisは医療機関の予約・受付・問診・カルテの記録・会計業務・保険請求業務までをトータルでサポートするクラウドシステムで、主にRuby on RailsとReactで作られています。
日々歯科医療ドメインでお客様の声に向き合いながら、医療DXを推進しています。<br>
着実に成長しているプロダクトですので、もしご興味ありましたら、カジュアル面談等受け付けておりますので、お声がけください。(宣伝終)</p>
<p>普段はプロダクト開発に従事していますが、プライベートでは地域Rubyコミュニティの表参道.rbを主催しておりまして、メドレーでも地域Rubyコミュニティ支援という形で、表参道.rbのスポンサー企業として、協力してもらっています。<br>
今回のAdvent Calendarでは、一地域Rubyコミュニティオーガナイザーの立場から見たメドレーの地域Rubyコミュニティとの関わりについて書こうと思います。</p>
<h1 id="表参道rbと私の関わり">表参道.rbと私の関わり</h1>
<p>表参道.rbは2015年頃から始まった、表参道周辺のエンジニアが集まってRubyの周辺技術に関する情報交換をする地域Rubyコミュニティです。<br>
毎月第1木曜日に開催しており、毎月1人10分程度のトークセッションを6〜7本と交流会を行っています。</p>
<p>一部例外があり、開催日が祝日等と重なる場合は、1週間ずらして第2木曜日に開催しています。<br>
ちょうど今月の第1木曜日は島根県松江市で、RubyWorld Conference 2025が開催されており、表参道.rbに普段来てもらっている多くの方も松江に集合しているので、第2木曜日の開催になります。(つまり今日です!)</p>
<p>毎月第2週は、後ほどお話する六本木.rbが定期開催されており、合同開催という形での開催しています。<br>
どちらも楽しいコミュニティですので、是非遊びに来てください。</p>
<p>表参道.rb<br>
<a href="https://omotesandorb.connpass.com/">https://omotesandorb.connpass.com/</a></p>
<p>六本木.rb<br>
<a href="https://roppongirb.connpass.com/">https://roppongirb.connpass.com/</a></p>
<p>私は、2018年頃から運営に関わり始め、その後メドレーに入社し、職場が六本木に変わった後も続けていました。</p>
<h1 id="コロナ禍によるオンライン開催移行そしてオフライン開催の復活">コロナ禍によるオンライン開催移行、そしてオフライン開催の復活</h1>
<p>2020年にコロナ禍になり、オフラインでの開催ができなくなり半年ほど開催を見送っていたこともありましたが、オンライン開催に移行して開催してきました。<br>
ちょうどコロナ禍で混乱していた頃、コミュニティを活動休止にするか、オンライン開催に移行するか悩みましたが、一度中止にしてしまうと再開するきっかけを失ってしまう可能性もあったため、これまで通りの頻度で開催することに決めました。<br>
またオンラインに移行するにあたって、もくもく会など、これまでと異なる形式での開催も考えましたが、いくつかのコミュニティがオンラインに移行している中、トークセッションがあるコミュニティは貴重、という意見をいただき、これまで通りの形式で進めていくことにしました。</p>
<p>オンライン開催の時は人の集まりが良くない時もあり、5名ぐらいで開催することもありましたが、毎回誰かが発表ネタを持ち込んでくれ、また発表が少なかった日は、少人数で余った時間をディスカッションに充てることができたので、これはこれで面白みがありました。<br>
また普段遠方で表参道.rbに参加できないような方にも参加していただいていたので、今も表参道.rbが続いているのは、当時少人数でも途切れないようにコミュニティを支えてくださった方々のお陰です。とてもありがたいことですね。<br>
御存知のとおり、コロナ禍は長く続くことになり、オンラインでの開催は2020年11月から2023年6月まで行われました。</p>
<p>RubyKaigiがオフライン開催に戻り、2023年の松本での開催から東京に戻った後、表参道.rbもオフラインに戻すことを考え始めました。<br>
その際に必要になるのが開催する会場です。当時はまだオフラインで会場を提供していただける場所は多くなく、まず最初に相談したのは自分の勤務先であるメドレーでした。<br>
メドレーでは過去にRubyコミュニティのために会場提供をしたことはありませんでしたが、私が地域Rubyコミュニティの主催を継続していたこともあり、活動を後押ししていただけ、快く会場提供と懇親会の予算をつけてもらえました。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">表参道.rb #87 では弊社が会場提供させていただいております!<a href="https://twitter.com/hashtag/omotesandorb?src=hash&ref_src=twsrc%5Etfw">#omotesandorb</a> <a href="https://twitter.com/hashtag/ruby?src=hash&ref_src=twsrc%5Etfw">#ruby</a><a href="https://t.co/PDmvLeqLPN">https://t.co/PDmvLeqLPN</a> <a href="https://twitter.com/hashtag/omotesandorb?src=hash&ref_src=twsrc%5Etfw">#omotesandorb</a></p>— メドレー ディベロッパー公式 (@medley_dev) <a href="https://twitter.com/medley_dev/status/1669515426907422720?ref_src=twsrc%5Etfw">June 16, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>ありがたいことに、この回の表参道.rbに参加してくださった方の中から、「自分の会社でも会場提供したい」という申し出を受け、現在の表参道.rbは10社ぐらいの企業から会場スポンサードをしていただき、毎回開催会場を変えながら、継続しています。</p>
<p>そして、今年2024年8月に表参道.rbはめでたく100回目の開催を迎えました。<br>
本当に様々な人に支えられて開催できた100回開催だな、と思います。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">表参道rb 100回〜〜!!👏👏👏👏👏<a href="https://twitter.com/hashtag/omotesandorb?src=hash&ref_src=twsrc%5Etfw">#omotesandorb</a> <a href="https://twitter.com/hashtag/roppongirb?src=hash&ref_src=twsrc%5Etfw">#roppongirb</a> <a href="https://t.co/1qe2aJWMcZ">pic.twitter.com/1qe2aJWMcZ</a></p>— あっきー🍺ツクリンクEM (@kuronekopunk) <a href="https://twitter.com/kuronekopunk/status/1818976996480909574?ref_src=twsrc%5Etfw">August 1, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h1 id="六本木rbの復活とメドレーとの関わり">六本木.rbの復活とメドレーとの関わり</h1>
<p>ちょうど今年のはじめぐらいの頃に、表参道.rbに参加されていたryoskさん(X: <a href="https://x.com/ryosk7">@ryosk7</a>)から六本木.rbを再始動させたいという話があり、会場を探しているという話がありました。<br>
六本木.rbは六本木周辺の企業で働くエンジニアが集まるRubyコミュニティで、コロナ前までは時々開催されていたようですが、こちらもコロナで休止していたコミュニティでした。<br>
メドレーは六本木の企業ということもあり、表参道.rb同様に六本木.rbも支援してもらいたいということで会社に相談したところ、こちらも会場提供と懇親会予算をつけてもらえることになりました。</p>
<p>この時メドレーはRubyKaigi 2024のRubyスポンサーとして協賛しており、最終日の基調講演前のスポンサーセッション枠をもらったタイミングだったため、どうせなら、盛大に宣伝してもらおうということで、弊社VPoEの山崎にも地域.rb支援の件について紹介してもらいました。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ただいま発表がありましたが、<br>Roppongi.rb が復活します!!!🎉<br><br>六本木周辺の企業さんのお力を借りてオフライン開催をします。<br>6月13日はメドレーさんのオフィスをお借りします。<br><br>これからconnpassの方も展開します!<br>よろしくお願いします🙌<a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw">#rubykaigi</a> <a href="https://t.co/zo2t6KGPrj">https://t.co/zo2t6KGPrj</a></p>— ryosk7🦎 (@ryosk7) <a href="https://twitter.com/ryosk7/status/1791374814281957551?ref_src=twsrc%5Etfw">May 17, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><a href="https://developer.medley.jp/entry/2024/05/24/182914">RubyKaigi 2024 に Ruby Sponsor として協賛しました! - MEDLEY Developers Portal</a></p>
<p>それ以来、六本木.rbはメドレーを含む4社で月1回の定期開催しています。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">メドレーさんのオフィスに着きました!<br>今日はこちらでRoppongi.rbを行います。<a href="https://twitter.com/hashtag/roppongirb?src=hash&ref_src=twsrc%5Etfw">#roppongirb</a> <a href="https://t.co/UioFP3FEyY">pic.twitter.com/UioFP3FEyY</a></p>— ryosk7🦎 (@ryosk7) <a href="https://twitter.com/ryosk7/status/1801195679806783588?ref_src=twsrc%5Etfw">June 13, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h1 id="地域rubyコミュニティ支援に伴うポジティブな効果">地域Rubyコミュニティ支援に伴う、ポジティブな効果</h1>
<p>このような活動を続けてきて、会社にどのような変化があったかについて触れたいと思います。<br>
まず1つ目は「普段の仕事で会わない社内の人たちと社外のイベントで顔を合わせるようになった」ことです。</p>
<p><em>2022年RubyKaigiの様子、この年はコロナ休止からのリブートということもあり、ブース出展もなかったので、自分しか参加してなかった</em>
<img __ASTRO_IMAGE_="{"src":"./rubykaigi2022.jpg","alt":"2022年のRubyKaigiの様子","index":0}"></p>
<p><em>2023年RubyKaigiの様子、会場の様子を見てきて欲しいと言われた若手エンジニアと。実はこの時写真を撮ってもらった人が、その後新卒としてメドレーに入るというミラクルが起きました</em>
<img __ASTRO_IMAGE_="{"src":"./rubykaigi2023.jpg","alt":"2023年のRubyKaigiの様子","index":0}"></p>
<p><em>2024年RubyKaigiの様子、スポンサーブースにて</em>
<img __ASTRO_IMAGE_="{"src":"../../05/24/IMG_3804.jpg","alt":"2024年のRubyKaigiの様子","index":0}"></p>
<p>社内でRubyのイベントを告知するようになってから、参加してくれる人も増えました。
<img __ASTRO_IMAGE_="{"src":"./slack.png","alt":"omotesando.rb社内告知の様子","index":0}"></p>
<p>先日のKaigi on Railsでも、業務とは関係なく来たと言う人達で集まる一幕もあり、変化を感じた部分でもありました。</p>
<p>もう一つは、やはり社外への認知で、今年のRubyKaigi 2024ではRubyスポンサーでブースを出していたのですが、「表参道.rbで会社に行ったことがあります」と教えてくださる人が数名いらっしゃったり、今年入社された方が「表参道.rbに参加したことがあります」と教えてくださったり、少なからずポジティブな側面があるなぁ、と感じることがありました。こういった声が定期的に聞けるのはスポンサードしている側としてはとても嬉しいです。</p>
<h1 id="地域rubyコミュニティのオーガナイザーから見たメドレーと地域rubyコミュニティとの関わり">地域Rubyコミュニティのオーガナイザーから見た、メドレーと地域Rubyコミュニティとの関わり</h1>
<p>さて、ここまではメドレーと地域Rubyコミュニティとの関わりについてお話してきました。<br>
ここからは私の所感について書きます。ここから先は私がオーガナイザーとして普段コミュニティ活動について考えていることですので、特段会社から何か言われているということではありません。誤解なきようお願いします(笑</p>
<p>会社から見ると「地域.rbのスポンサードをしました!」、地域Rubyコミュニティのオーガナイザーから見ると「地域.rbを開催しました!」という話で終わるのですが、地域Rubyコミュニティのオーガナイザーをしつつ、会社として地域.rbの支援をする、という立場でみると、少し違った見え方になります。</p>
<p>企業がこういったスポンサードを行う際には、一定の費用対効果を考える必要があるからです。<br>
私もスポンサードする側の立場から、そういった事情を抜きにすれば、「知名度向上のために積極的に行うべき」「採用面にも少なからずポジティブに働いている」と主張することはできますし、実際そういった側面はあると思います。しかし、それがどの程度プラスに働いているのかを正しく計測する方法がないため、基本的にはコミュニティはボランティアに近い形で支援してもらうことになります。<br>
そういったものは、長く続くとは限りませんし、普段ご支援いただいている会社の窓口担当の方や、決済責任者が変わるだけで状況は変わります。</p>
<p>これについては、オーガナイザーとしては仕方ないものと考えており、コミュニティを持続可能なものにするために、最大限努力をする必要があると考えています。</p>
<p>以前表参道.rb 100回のときに、「表参道.rb 100回に寄せて」というタイトルで、「コミュニティが継続していくためにはどうすればよいか」というお話をさせていただきました。
その中で、コミュニティが継続するための3要素「主催者のやる気・スポンサード企業・参加者」の3つを挙げ、コミュニティが長く続くためにどうするべきか、というお話をさせていただきました。</p>
<p><em>「表参道.rb 100回に寄せて」</em>
<img __ASTRO_IMAGE_="{"src":"./slide_image_010.jpeg","alt":"コミュニティが継続していくためには・主催者","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./slide_image_011.jpeg","alt":"コミュニティが継続していくためには・会場","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./slide_image_012.jpeg","alt":"コミュニティが継続していくためには・参加者","index":0}"></p>
<p>メドレーも現在は地域Rubyコミュニティを無償で応援している立場ではありますが、これが未来永劫続くかは分かりません。が、可能な限り長期間支援できるように、一地域Rubyコミュニティのオーガナイザーとして、そして会社の中の人として、努力したいと思っています。</p>
<h1 id="まとめ">まとめ</h1>
<p>地域Rubyコミュニティのオーガナイザーから見た、メドレーと地域Rubyコミュニティとの関わりについて書かせていただきました。<br>
メドレーでは、表参道.rbと六本木.rbの地域Rubyコミュニティの支援をしており、定期的にメドレーでも開催しております。</p>
<p>もし、ご興味ありましたら是非遊びに来てください。</p>
<p>表参道.rb<br>
<a href="https://omotesandorb.connpass.com/">https://omotesandorb.connpass.com/</a></p>
<p>六本木.rb<br>
<a href="https://roppongirb.connpass.com/">https://roppongirb.connpass.com/</a></p>
- JSConf JP 2024に プレミアムスポンサーとして協賛しました!https://developer.medley.jp/entry/2024/12/06/183134https://developer.medley.jp/entry/2024/12/06/183134こんにちは!人材プラットフォーム本部で技術広報兼エンジニア採用をしている重田(@Shige0096)です。2024 年 11 月にメドレーにジョインし、初の社外イベントに参加してきました。
今回、メドレーは 2024/11/23 に九段坂上...Fri, 06 Dec 2024 09:31:34 GMT<p>こんにちは!人材プラットフォーム本部で技術広報兼エンジニア採用をしている重田(<a href="https://x.com/Shige0096">@Shige0096</a>)です。2024 年 11 月にメドレーにジョインし、初の社外イベントに参加してきました。</p>
<p>今回、メドレーは 2024/11/23 に九段坂上 KS ビルにて開催された「<a href="https://jsconf.jp/2024/">JSConf JP 2024</a>」にプレミアムスポンサーとして協賛させていただきました。</p>
<p>JSConf JP は一般社団法人 Japan Node.js Association によって企画・運営されている JavaScript に関する“ お祭り ”です。日本と海外の Web 開発者を繋げる目的で企画されています。2020 年から開催され、今年で 5 回目。今年もフロントエンドエンジニア・採用人事・技術広報担当を中心に多くの参加者が訪れました。</p>
<h1 id="会場の様子">会場の様子</h1>
<p>会場は九段坂上 KS ビル。今年はオンライン・オフラインのハイブリッド開催でした。現地チケットは売り切れてしまったので、オンライン配信があるのは嬉しいですね。</p>
<p>当日は 4 つのトラックに分かれ、時間帯によっては 4 セッションが同時並行していました。
セッション数が多くとても充実したイベントになりました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./image8.png","alt":"会場入り口","index":0}">
<em>会場入り口</em></p>
<h1 id="メドレーブースの様子">メドレーブースの様子</h1>
<p>メドレーブースでは「医療体験あるある」をパネルを用いてアンケート調査しました。
エンジニアをはじめ多くの方にお越しいただきました!お越しいただいた皆様ありがとうございました ✨</p>
<p>特に多かった「医療体験あるある」は以下 2 つ。</p>
<p>① キャッシュレス決済がしたい<br>
⇨ キャッシュレス対応が進んできたがまだまだ現金対応の病院が多い<br>
⇨ 現金を引き落としに一度コンビニに行くことがあった</p>
<p>② 待ち時間が長い(ほぼ同率 1 位)<br>
⇨ 予約したはずなのに待ち時間が発生している<br>
⇨ いつも混んでいるので病院に行くのが疲れる</p>
<p>実際に <a href="https://clinics-app.com/">CLINICS アプリ</a>の画面をご覧いただきながら以下のようなお話をしました。オンライン診療を受けたのち、ご要望に合わせて薬を Uber で配達することも可能であることや、何よりキャッシュレス決済もスムーズにできてとても良い!などのお声をいただきました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./image4.jpg","alt":"医療UXの「ここなんとかならない?」","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./image5.png","alt":"ブース1","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./image1.png","alt":"ブース2","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./image13.png","alt":"ブース3","index":0}">
<em>多くの方にお越しいただきました 🙌</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./image2.png","alt":"ブース4","index":0}">
<em>自身が開発しているプロダクトに込める思いは人一倍 👀✨</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./image11.png","alt":"ブース5","index":0}"></p>
<h1 id="メドレー登壇">メドレー登壇</h1>
<p>当社の髙橋(医療プラットフォーム本部)と德永(人材プラットフォーム)が登壇しました。</p>
<h2 id="高橋のセッションの様子">高橋のセッションの様子</h2>
<p><img __ASTRO_IMAGE_="{"src":"./image12.png","alt":"","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./image6.png","alt":"","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./image7.png","alt":"","index":0}"></p>
<p>次々に参加者が増えていき、最終的には 50 名近くの方にご覧いただきました 🙌
オンライン配信を含め、ご視聴いただいた皆様、ありがとうございました!</p>
<p>登壇した髙橋より一言</p>
<hr>
<p>セッションや登壇資料をご覧頂いた皆様、ありがとうございました!当日のブースや懇親会、また X やブログを通じて、皆様から多くのご感想を頂き感謝の気持ちでいっぱいです。発表で詳しく取り上げられなかった内容に関する Q&A を以下に公開します。</p>
<h3 id="コンポーネントの分割粒度をチーム全体でどのように統一していますか">コンポーネントの分割粒度をチーム全体でどのように統一していますか?</h3>
<p>発表内で紹介した{関心事}{状況}{ベースコンポーネント名}の命名規則を守ることで Button や Dialog の単位で自然とコンポーネントが分割されるようなメンタルモデルを築いています。加えて <a href="https://plopjs.com/">Plop</a> を使ってテンプレートコードを生成することで実装がブレないようにしています。</p>
<h3 id="状態管理ライブラリはどのように使い分けていますか">状態管理ライブラリはどのように使い分けていますか?</h3>
<p>非同期状態管理は <a href="https://tanstack.com/query/latest">Tanstack Query</a>、フォーム状態管理は <a href="https://www.react-hook-form.com/">React Hook Form</a>、その他の UI に関する状態は React.useState のように使い分けています。複合コンポーネント間で親子関係がある場合は React.createContext を使って依存関係を表現することがあります。このとき、子コンポーネントの <a href="https://storybook.js.org/">Storybook</a> の decorator に Context Provider を設定することで、複合コンポーネント間の連携を含めた Story とそのテストを実装しています。<a href="https://redux.js.org/">Redux</a> はサーバキャッシュとして運用していましたが、現在は Tanstack Query への移行を進めているため積極的な利用は避けています。</p>
<hr>
<p>👇 登壇資料はこちら</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/1776024da9354b3b96f2e593d1b5bd16" title="徹底解剖! 医療業務システムのReactコンポーネント設計 / Deep Dive into React Component Design for Medical Systems" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<h2 id="德永のセッションの様子">德永のセッションの様子</h2>
<p><img __ASTRO_IMAGE_="{"src":"./image3.png","alt":"","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./image10.png","alt":"","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./image9.png","alt":"","index":0}"></p>
<p>登壇した徳永より一言</p>
<hr>
<p><a href="https://remix.run/">Remix</a> のファイルベースルーティングについて LT させていただきました。当日に React Router v7 が正式リリースされるなど想定外のことが起こりつつも、皆さんにホットな話題として取り上げていただき有り難かったです。</p>
<hr>
<p>👇 登壇資料はこちら</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/d0661a266a58412c847e36326909c64b" title="Remix SPAモードのファイルベースルーティングで進めるフロントエンド構築" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<h1 id="終わりに">終わりに</h1>
<p>メドレーはこれからも医療ヘルスケア領域において、医療体験にインパクトを与えるプロダクトやサービスの開発に取り組んでいきます。引き続き技術的なチャレンジも行っていきますのでご注目ください!</p>
<p>最後に、改めて JSConf JP 2024 の運営の皆様、登壇されたスピーカーの皆様、参加者の皆様、ありがとうございました。来年の開催も楽しみにしています!</p>
<p><img __ASTRO_IMAGE_="{"src":"./image14.jpg","alt":"ミイダス社とメルカリ社の素敵なノベルティをいただきました✨","index":0}">
<em>ミイダス社とメルカリ社の素敵なノベルティをいただきました ✨</em></p>
<h1 id="メドレーでは一緒に働く仲間を募集しています">メドレーでは一緒に働く仲間を募集しています。</h1>
<p>ぜひこの記事やブースや登壇などで興味を持っていただいた方はご連絡をお待ちしております!カジュアル面談も大歓迎ですので、お気軽にお問い合わせください!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"></cite>
<p>当日会場でお時間いただいた皆様、ありがとうございました!</p>
<p>メドレーは今後も技術イベントへ出展/登壇する予定です。今後も皆さんにお会いすることを楽しみにしています!</p>
- トイルの削減も、情報漏洩リスクの削減も、両方手に入れる。IAM Identity Centerは欲張りなんだ。https://developer.medley.jp/entry/2024/12/03/100122https://developer.medley.jp/entry/2024/12/03/100122
こちらの記事は Medley(メドレー) Advent Calendar 2024 の3日目の記事です。
Who are you?
メドレーで主にSRE活動を行っている人材プラットフォーム本部 第四開発グループの玉井です。
最初に簡単にF...Tue, 03 Dec 2024 01:01:22 GMT<!-- Edit here! -->
<p>こちらの記事は <strong><a href="https://qiita.com/advent-calendar/2024/medley">Medley(メドレー) Advent Calendar 2024</a></strong> の3日目の記事です。</p>
<h1 id="who-are-you">Who are you?</h1>
<p>メドレーで主にSRE活動を行っている人材プラットフォーム本部 第四開発グループの玉井です。</p>
<p>最初に簡単にFAQ形式で自己紹介させていただこうと思います。</p>
<ul>
<li><em>(昨日は何を食べましたか)</em> : 麺系ファストフードでは一番好きな小諸そば。</li>
<li><em>(好きな本は何ですか)</em> : 柳井正さんの自伝書『一勝九敗』は心に残っています。</li>
<li><em>(遊びに行くならどこに行きますか)</em> : アウトドアが好きなのでキャンプなど。</li>
</ul>
<p>今回は利用しているAWS環境に <strong>IAM Identity Center</strong> を導入した話をさせていただこうと思います。</p>
<h1 id="セキュリティと管理コストの天秤">セキュリティと管理コストの天秤</h1>
<p>メドレーではクレデンシャルな情報は厳重に管理され、平文で外部ツールに保存されることはありません。AWSのIAMユーザのアクセスキーに関しても同じです。
ただ、それでも心配になるのは <strong>PCの中に平文で残る情報</strong> 。PCも厳重に管理されているのでそうそう漏洩するものではありませんが、それでも可能性はゼロではありません。</p>
<p>一時はAssumeRoleを使うことで漏洩時のリスクを減らしました。
最初の認証では何もできない弱い権限で、AssumeRoleで権限の強いロールに変身する方式を導入しました。
この話を聞いて、腕に包帯を巻いた学生服の少年が天狗のお面を被るいらすとが浮かんだあなたはAWSデベロッパーお馴染みのあのブログの敬虔な読者です。</p>
<p><em>AssumeRole概要図</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./about_assumerole.png","alt":"AssumeRole概要図","index":0}"></p>
<p>それでもまだ課題はありました。 <strong>IAMユーザとAssumeRoleの管理コスト</strong> です。</p>
<p>AWSアカウントはサービスごとに複数存在し、さらに本番環境、ステージング環境でも分けてあります。それぞれのAWSアカウントにアクセスできる環境を開発メンバーに提供する必要があります。</p>
<p>AssumeRoleは変身する元と変身した先の設定をする必要があり、AWSアカウントを跨ぐ場合はそれぞれのAWSアカウントに設定が必要になります。開発メンバーの異動がある度にあちらこちらのAWSアカウントにその設定をすることが、面倒なトイルになっていました。</p>
<p><strong>セキュアな環境を維持したまま、管理コストもなるべく下げたい。</strong></p>
<p>そこで導入を決めたのが <strong>IAM Identity Center</strong> でした。</p>
<h1 id="iam-identity-centerの導入検討">IAM Identity Centerの導入検討</h1>
<p>結論を先に申し上げますと、IAM Identity Centerを導入することで <strong>セキュアな環境を維持しながらトイルの削減も実現することができました</strong> 。
完璧で究極というほどではありませんが、誰もが目を奪われていく理想の環境に一歩近づくことがました。</p>
<p>IAM Identity Centerをさらに管理するAWS Control Towerの導入も検討しましたが、既にある環境に導入することの予期せぬ制限がかかることのリスクや、AWS Configが多用されることで思わぬコスト増が発生するとの情報もあり、今回は見送ることにしました。</p>
<h2 id="iam-identity-centerとは">IAM Identity Centerとは</h2>
<p>元は <strong>AWS Single Sign-On (AWS SSO)</strong> というサービス名でした。
これはIAMユーザとは全く別物のユーザ(これをSSOユーザとします)を作成し、SSOユーザが各AWSアカウントの各IAMロールを持ったユーザに変身することができる機能です。</p>
<p><em>IAM Identity Center概要図</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./about_iic.png","alt":"IAM Identity Center概要図","index":0}"></p>
<h2 id="iam-identity-centerを導入した決め手">IAM Identity Centerを導入した決め手</h2>
<p>SSOユーザと権限の管理は一つのAWSアカウントのIAM Identity Centerで管理することができます。
これはAWS Organizationsの機能の上に乗る機能ですが、幸い元々それを導入していたことで、すぐに組み込むことができました(ちなみに現在はIdPを設定することでAWS Organizationsがなくてもこの機能を使うことができます)。</p>
<p>つまり、 <strong>複数のAWSアカウントの複数のユーザの複数のロールを一つのIAM Identity Centerで全て管理することができるのです</strong> 。</p>
<p>では、各AWSアカウントで既に作成してしまったIAMポリシーを、IAM Identity Centerで同じ内容で作り直さないといけないかというとそんなこともなく、カスタムロールという設定で、IAMポリシー名だけ定義することで既存のIAMポリシーをそのまま活かすことができます。</p>
<p>そして、SSOユーザは恒久的なアクセスキーを持つことができず、最大で12時間の期限付きアクセスキーしか持つことができません。従って、 <strong>仮に万が一アクセスキーが漏洩しても12時間後には使えなくなります</strong> 。</p>
<p>まさに最強で無敵のIAM Identity Centerです。</p>
<h1 id="iam-identity-centerの導入">IAM Identity Centerの導入</h1>
<p>ここではIAM Identity Centerの導入の流れを簡単に説明いたします。
一番最初はIAM Identity Centerを有効にすることですが、それは割愛して有効化した後の手順を記載していきます。</p>
<p><em>IAM Identity Center全体図</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./detail_iic.png","alt":"IAM Identity Center全体図","index":0}"></p>
<h2 id="ssoユーザの作成">SSOユーザの作成</h2>
<p>SSOユーザは前述した通りIAMユーザとは全く別物なので、新たに全員分作成する必要があります。
最低限必要なのは名前とメールアドレスのみです。
作成画面に任意項目で住所や電話番号などあったりするのですが、システム的には全く関係ないようです。IAM Identity CenterをIdPとして使うときのための設定でしょうか。</p>
<p><em>SSOユーザ作成画面</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./create_user.jpg","alt":"SSOユーザ作成画面","index":0}"></p>
<h2 id="ssoグループの作成">SSOグループの作成</h2>
<p>SSOグループとは、SSOユーザと後述する許可セットをまとめるためのものです。
新しいSSOユーザが増えた場合は、該当のグループに追加してあげるだけでOKです。</p>
<p><em>SSOグループ作成画面</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./create_group.jpg","alt":"SSOグループ作成画面","index":0}"></p>
<h2 id="許可セットの作成">許可セットの作成</h2>
<p>許可セットは、IAMロールの素になるものです。
ここで設定したポリシーが各AWSアカウントにIAMロール、IAMポリシーとしてプロビジョニングされます。</p>
<p>プロビジョニング先のAWSアカウントに既にあるIAMポリシーを使いたい場合は、カスタマーマネージドポリシーでその名前を設定すれば割り当てることができます。
名前が間違っていて存在しないIAMポリシーを設定してしまった場合は、プロビジョニング時にエラーになります。</p>
<p><em>許可セット作成画面</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./create_permisisonset.jpg","alt":"許可セット作成画面","index":0}"></p>
<h2 id="プロビジョニング">プロビジョニング</h2>
<p>“SSOユーザまたはグループ” と “許可セット” を適用したいAWSアカウントにプロビジョニングすることで、対象SSOユーザで該当AWSアカウントにログイン可能になります。</p>
<p><em>プロビジョニング概要図</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./about_provisioning.png","alt":"プロビジョニング概要図","index":0}"></p>
<h2 id="ssoアクセスポータル画面">SSOアクセスポータル画面</h2>
<p>SSOユーザでログインするAWSアクセスポータル画面に、AWSアカウントへのリンクの一覧が表示されます。
このリンクから各AWSアカウントに、任意のロールでログインすることができます。便利ですね!</p>
<p><em>AWSアクセスポータル画面</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./accessportal.jpg","alt":"AWSアクセスポータル画面","index":0}"></p>
<p>一時的なアクセスキーはCLIでも取得できますが、この画面からも <strong>アクセスキー</strong> のリンクをクリックすると一覧画面が表示されます。</p>
<p><em>AWSアクセスポータル アクセスキー確認画面</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./accessportal_key.jpg","alt":"AWSアクセスポータル アクセスキー確認画面","index":0}"></p>
<h1 id="導入後に対応したこと">導入後に対応したこと</h1>
<p>これまでの手順でマネジメントコンソールのでログインはできるようになっていますが、それ以外のツール等で利用するためにしたことをここでは説明します。</p>
<h2 id="awsconfigの修正">~/.aws/configの修正</h2>
<p>ターミナルでAWS CLIを利用するために、これまでアクセスキーを直接指定していたのを、SSOユーザ対応の記述に変更する必要があります。
アクセスキーなどのクレデンシャルな情報の記載が不要になり、この内容が漏洩してもダメージが少ないです。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="toml"><code><span class="line"><span style="color:#D4D4D4">[default]</span></span>
<span class="line"><span style="color:#9CDCFE">sso_session</span><span style="color:#D4D4D4"> = m</span><span style="color:#F44747">y-sso</span></span>
<span class="line"><span style="color:#9CDCFE">sso_account_id</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">999999999999</span></span>
<span class="line"><span style="color:#9CDCFE">sso_role_name</span><span style="color:#D4D4D4"> = x</span><span style="color:#F44747">xxxxxxxxxxx</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">[sso-session my-sso]</span></span>
<span class="line"><span style="color:#9CDCFE">sso_start_url</span><span style="color:#D4D4D4"> = h</span><span style="color:#F44747">ttps://xxxxxxxxxxxx.awsapps.com/start</span></span>
<span class="line"><span style="color:#9CDCFE">sso_registration_scopes</span><span style="color:#D4D4D4"> = s</span><span style="color:#F44747">so:account:access</span></span></code></pre>
<p>sso_sessionの設定が古いaws_sdkだと対応していなかったりしたので、最新のaws_sdkを使うように <a href="https://mise.jdx.dev/">mise</a> (開発ツール管理用ツール)に設定しました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="toml"><code><span class="line"><span style="color:#D4D4D4">[tools]</span></span>
<span class="line"><span style="color:#9CDCFE">awscli</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">"latest"</span></span></code></pre>
<p>この設定をした状態で <code>aws sso login</code> を実行すると認証画面のブラウザが自動で立ち上がり、ブラウザでの認証完了後にターミナルでCLIが使えるようになります。</p>
<p><em>ターミナルでsso login</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./sso_login.jpg","alt":"ターミナルでsso login","index":0}"></p>
<p><em>ブラウザで認証</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./sso_auth.png","alt":"ブラウザで認証","index":0}"></p>
<h2 id="開発環境へのアクセスキーの渡し方">開発環境へのアクセスキーの渡し方</h2>
<p>開発環境においてアクセスキーの設定が必要な箇所があり、それを毎日ポータル画面からコピペで更新してくださいというのは面倒すぎるので、毎日1回最初にシェルを実行してもらうようにしました(これも面倒なのでこれなしでどうにかできれば良かったのですが・・・無念)。</p>
<p>この実行にはSSOの認証情報をシェル内の処理で利用するため <a href="https://github.com/linaro-its/aws2-wrap">aws2-wrap</a> を利用し、.envファイルの更新には <a href="https://github.com/theskumar/python-dotenv">python-dotenv</a> を利用しました。</p>
<p>これもmiseで定義することで、全開発メンバーにもれなく対応できました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6A9955"># 一部抜粋</span></span>
<span class="line"><span style="color:#C586C0">if</span><span style="color:#D4D4D4"> ! </span><span style="color:#DCDCAA">$(aws2-wrap</span><span style="color:#569CD6"> --profile</span><span style="color:#CE9178"> "</span><span style="color:#9CDCFE">$profile</span><span style="color:#CE9178">"</span><span style="color:#569CD6"> --export</span><span style="color:#D4D4D4">); </span><span style="color:#C586C0">then</span></span>
<span class="line"><span style="color:#DCDCAA"> aws</span><span style="color:#CE9178"> sso</span><span style="color:#CE9178"> login</span><span style="color:#569CD6"> --profile=</span><span style="color:#CE9178">"</span><span style="color:#9CDCFE">$profile</span><span style="color:#CE9178">"</span></span>
<span class="line"><span style="color:#DCDCAA"> eval</span><span style="color:#CE9178"> "$(</span><span style="color:#DCDCAA">aws2-wrap</span><span style="color:#569CD6"> --profile</span><span style="color:#CE9178"> "</span><span style="color:#9CDCFE">$profile</span><span style="color:#CE9178">" </span><span style="color:#569CD6">--export</span><span style="color:#CE9178">)"</span></span>
<span class="line"><span style="color:#C586C0">fi</span></span></code></pre>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#6A9955"># 一部抜粋</span></span>
<span class="line"><span style="color:#C586C0">from</span><span style="color:#D4D4D4"> dotenv </span><span style="color:#C586C0">import</span><span style="color:#D4D4D4"> set_key</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">def</span><span style="color:#DCDCAA"> update_env_file</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">env_file</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">key_value_pairs</span><span style="color:#D4D4D4">):</span></span>
<span class="line"><span style="color:#C586C0"> try</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#C586C0"> for</span><span style="color:#D4D4D4"> key </span><span style="color:#C586C0">in</span><span style="color:#D4D4D4"> key_value_pairs:</span></span>
<span class="line"><span style="color:#D4D4D4"> value = key_value_pairs[key]</span></span>
<span class="line"><span style="color:#D4D4D4"> set_key(env_file, key, value, </span><span style="color:#9CDCFE">quote_mode</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">'never'</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0"> except</span><span style="color:#4EC9B0"> Exception</span><span style="color:#C586C0"> as</span><span style="color:#D4D4D4"> e:</span></span>
<span class="line"><span style="color:#DCDCAA"> print</span><span style="color:#D4D4D4">(e)</span></span></code></pre>
<h2 id="terraform-packerの対応">Terraform, Packerの対応</h2>
<p>最新のTerraform、Packerではaws ssoに対応しているので、ssoのprofileをそのまま使うことができます(AssumeRoleには対応していなかったので、それを対応させた時は大変でした・・・)。</p>
<p>Terraformは環境依存をなくすため実行環境をコンテナ化しコンテナ内で実行しているのですが、“ログイン時にSSOセッションの存在有無の確認” → “なければSSOログイン” をさせることで、 <code>aws sso login</code> の事前実行を意識しなくても良いようにしました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6A9955"># 一部抜粋</span></span>
<span class="line"><span style="color:#569CD6">function</span><span style="color:#DCDCAA"> get_caller_identity</span><span style="color:#D4D4D4"> () {</span></span>
<span class="line"><span style="color:#DCDCAA"> set</span><span style="color:#CE9178"> +eu</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> for</span><span style="color:#9CDCFE"> i</span><span style="color:#C586C0"> in</span><span style="color:#D4D4D4"> {</span><span style="color:#DCDCAA">1..3}</span><span style="color:#D4D4D4"> ; </span><span style="color:#C586C0">do</span></span>
<span class="line"><span style="color:#DCDCAA"> command</span><span style="color:#CE9178"> "aws sts get-caller-identity --profile </span><span style="color:#9CDCFE">$1</span><span style="color:#CE9178">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> [[ </span><span style="color:#569CD6">$?</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4"> ]]; </span><span style="color:#C586C0">then</span></span>
<span class="line"><span style="color:#C586C0"> break</span></span>
<span class="line"><span style="color:#C586C0"> else</span></span>
<span class="line"><span style="color:#DCDCAA"> echo</span><span style="color:#CE9178"> "Not logged in"</span></span>
<span class="line"><span style="color:#DCDCAA"> command</span><span style="color:#CE9178"> "aws sso login --profile </span><span style="color:#9CDCFE">$1</span><span style="color:#CE9178">"</span></span>
<span class="line"><span style="color:#C586C0"> fi</span></span>
<span class="line"><span style="color:#C586C0"> done</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> set</span><span style="color:#569CD6"> -eu</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>最新のPackerはaws ssoに対応していると前述しましたが、なぜだか環境により <code>sso-session</code> が効かずにエラーになる場合がありました。その場合はレガシーな記述方法で回避できます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="toml"><code><span class="line"><span style="color:#D4D4D4">[profile packer]</span></span>
<span class="line"><span style="color:#9CDCFE">credential_process</span><span style="color:#D4D4D4"> = a</span><span style="color:#F44747">ws --profile default configure export-credentials --format process</span></span>
<span class="line"><span style="color:#9CDCFE">region</span><span style="color:#D4D4D4"> = a</span><span style="color:#F44747">p-northeast-1</span></span>
<span class="line"><span style="color:#9CDCFE">output</span><span style="color:#D4D4D4"> = j</span><span style="color:#F44747">son</span></span></code></pre>
<h1 id="対応して良かったこと">対応して良かったこと</h1>
<h2 id="管理者目線で良かったこと">管理者目線で良かったこと</h2>
<p>管理者目線では、AWSのユーザ管理が大分楽になりました。</p>
<p>これまで開発メンバーが増えた際には、複数のAWSアカウントに対しそれぞれに、IAMユーザの作成、アクセスキーの生成をし、その後初期ログインパスワードとアクセスキーをメンバーに配布しなければならなかったため、作業が大分煩雑でした。</p>
<p>IAM Identity Center導入後は、 <strong>1つのAWSアカウントでSSOユーザを作成するだけです</strong> 。招待メールも勝手に送られます。</p>
<p>また、与えている権限の管理も楽になりました。</p>
<p><strong>IAM Identity Centerの画面を見れば、誰がどのAWSアカウントにどんな権限を持っているか全てわかりますし、設定するのもそこだけなので</strong> 、いろんなAWSアカウントを出入りして確認・設定する必要がなくなりました。</p>
<h2 id="利用者目線で良かったこと">利用者目線で良かったこと</h2>
<p>利用者目線では、各AWSアカウントへのログインがポータル画面のリンクから行くことができるので、 <strong>AWSアカウントごとにログインの作業が必要なくなったのも楽ですし、各AWSアカウント毎にログイン情報、MFA情報を保持しなくても良くなったのも管理が気楽になりました</strong> 。</p>
<h1 id="対応して困ったこと">対応して困ったこと</h1>
<h2 id="12時間の壁">12時間の壁</h2>
<p>SSOユーザのセッション時間の上限が12時間のため、朝9時にログインすると夜9時にセッションが切れてしまいます。そのことを忘れて12時間後に急に開発環境でエラーが出始めて「なんじゃこりゃあああ!」とたまに驚かされます(12時間以上働くことがほとんどないのでめったに遭遇しませんが、だからこそ忘れます)。</p>
<p>深夜メンテの際にそれが起きるとトラブルになりますので、その際は作業前に必ずログインし直すようにしています。</p>
<p>個人的にはセッション時間の上限の時間もう少し上げて欲しいです。</p>
<h2 id="利用できないサードパーティーツールの存在">利用できないサードパーティーツールの存在</h2>
<p>AWSにアクセスするためのサードパーティーツールは様々ありますが、<strong>AWS_ACCESS_KEY_ID</strong> 、 <strong>AWS_SECRET_ACCESS_KEY</strong> の2項目しか設定できないものがあります。</p>
<p>SSOユーザでログインするには、それらに加え <strong>AWS_SESSION_TOKEN</strong> の値が必要であるため、設定できないツールはSSOユーザでは利用できないことになります。</p>
<p>最近ではSSOユーザでも利用できるようにアップグレードされているツールも増えてきていますので、今まで使っていたツールが使えなくなった場合は代替のツールを使いましょう。</p>
<h1 id="まとめ">まとめ</h1>
<p>これは絶対嘘ではなくて、IAM Identity Centerを導入することで、トイルの削減も、情報漏洩リスクの削減も、両方実現することができました。</p>
<p>もちろん私たちの挑戦はこれで終わりではなく、サービスの品質を上げるため、開発メンバーが開発に集中できる環境を作るため、 <strong>俺たちの戦いはまだ始まったばかり</strong> 、です。</p>
<p>そして、明日12月4日は @shigerisa さんが何やらもっと面白い記事を書いてくれているようです!お楽しみに!</p>
<h1 id="were-hiring">We’re hiring!</h1>
<p>メドレーでは各種エンジニアを絶賛募集中です!</p>
<p>カジュアル面談いつでもWelcomeですので、お話しだけでも聞いてみたい、むしろ私の推しの話を聞いて欲しい、などなんでもかまいませんので、お気軽にお問い合わせください!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- 日本 CTO 協会 DX Criteria WG 所属の ahomu さんをお招きして Web フロントエンド版 DX Criteria 勉強会を開催しましたhttps://developer.medley.jp/entry/2024/11/29/094550https://developer.medley.jp/entry/2024/11/29/094550はじめに
皆さん、こんにちは! エンジニア / エンジニア・デザイナー採用担当の平木です。
弊社では社内のエンジニアが定期・不定期で勉強会を開催しています。この勉強会は、プロダクト開発の話はもちろんですが、各技術の最新情報の共有などを、事業...Fri, 29 Nov 2024 00:45:50 GMT<h1 id="はじめに">はじめに</h1>
<p>皆さん、こんにちは! エンジニア / エンジニア・デザイナー採用担当の平木です。
弊社では社内のエンジニアが定期・不定期で勉強会を開催しています。この勉強会は、プロダクト開発の話はもちろんですが、各技術の最新情報の共有などを、事業部に留まらず全社的に行う会となっています。</p>
<p>11/15(金)にも社内勉強会を開催したのですが、題材を<a href="https://dxcriteria.cto-a.org/frontend">Web フロントエンド版 DX Criteria</a>についてとしました。そこで作成者の 1 人である日本 CTO 協会 DX Criteria WG の佐藤 歩(<a href="https://x.com/ahomu">@ahomu</a>)さん(以下 ahomu さん)を講師としてお招きして講演をしていただきましたので、そのレポートとなります。</p>
<h1 id="今回の勉強会開催の背景">今回の勉強会開催の背景</h1>
<p>今年 4 月に Web フロントエンド版 DX Criteria が<a href="https://prtimes.jp/main/html/rd/p/000000024.000081310.html">発表</a>されたのを見て、内容的にメドレーでも参考にできる部分が非常に多いと思いました。</p>
<p>作成者の 1 人である ahomu さんは筆者の前職同僚だったということもあり、勉強会講師を打診して快諾いただいたため、開催の運びとなりました。</p>
<h1 id="勉強会について">勉強会について</h1>
<p><img __ASTRO_IMAGE_="{"src":"./2024-11-15_01.jpg","alt":"","index":0}">
<em>講演の様子</em></p>
<p>今回は社外から講師をお招きするということもあり、勉強会へは 25 名ほどの参加となりました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024-11-15_02.jpg","alt":"","index":0}">
<em>真剣に講演を聞くメンバー</em></p>
<p>社内限定での勉強会ということもあり、ahomu さんからも、メドレーに合わせて濃いお話をしていただきました。</p>
<ul>
<li>歴史を踏まえた昨今のフロントエンドの技術的な変遷</li>
<li>現代のフロントエンドの設計で重視しなければならないポイントと提供するプロダクトの特性の関係</li>
<li>開発持続性を実現するための、開発環境について</li>
<li>Web フロントエンド版 DX Criteria の成り立ちや目的を、弊社の状況も交じえながら解説</li>
<li>Web フロントエンド版 DX Criteria のそれぞれの項目について、弊社の状況を踏まえて解説</li>
<li>アセスメント後に、どのような整理をして改善していけばよいかの指針について</li>
</ul>
<p><img __ASTRO_IMAGE_="{"src":"./2024-11-15_03.jpg","alt":"","index":0}">
<em>佳境に入った講演の様子</em></p>
<p>上述したものは内容の一例で、全てをお伝えできませんが参加者がより Web フロントエンド版 DX Criteria を「メドレーのプロダクト」ベースで考えられるような講義を行なっていただきました。</p>
<p>参加者からも Web フロントエンド版 DX Criteria の内容を越えたフロントエンド技術についての質問なども飛び出して、大変盛況のうちに終了しました。</p>
<p>改めてプロダクトのフロントエンド開発を事業発展にどのように繋げるかという視点で考えることができた、非常に有意義な時間となりました。</p>
<p>多忙の中、今回の勉強会講師を快諾してくださった ahomu さんに大変感謝しております。ありがとうございました!</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024-11-15_04.jpg","alt":"","index":0}">
<em>ahomu さんと記念撮影</em></p>
<h1 id="さいごに">さいごに</h1>
<p>メドレーではこれからも機会があれば、社内の開発者向けに外部講師を招聘して勉強会などを開いていく予定です。</p>
<p>自分の技術を色々な角度から高めていきたい!と考えているエンジニア・デザイナーの方はぜひご連絡ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- JSConf JP 2024に協賛し、医療業務システムのReactコンポーネント設計を紹介しますhttps://developer.medley.jp/entry/2024/11/11/155408https://developer.medley.jp/entry/2024/11/11/155408こんにちは。医療プラットフォーム本部プロダクト開発室 CLINICS 第二開発グループ所属の髙橋です。
メドレーは、2024 年 11 月 23 日に九段坂上 KS ビルにて開催される JSConf JP 2024 にプレミアムスポンサーと...Mon, 11 Nov 2024 06:54:08 GMT<p>こんにちは。医療プラットフォーム本部プロダクト開発室 CLINICS 第二開発グループ所属の髙橋です。</p>
<p>メドレーは、2024 年 11 月 23 日に九段坂上 KS ビルにて開催される JSConf JP 2024 にプレミアムスポンサーとして協賛します。</p>
<p>今回は、来週末に控えている JSConf JP 2024 で発表するスポンサーワークショップの内容について紹介します。</p>
<h1 id="jsconf-jp-2024-とは">JSConf JP 2024 とは</h1>
<p>JSConf JP は、一般社団法人 Japan Node.js Association が主催する JavaScript の祭典です。</p>
<p>国内はもちろん、海外からも Web 開発者を招き、合計 4 トラックで Web 開発に関連する多数のセッションが予定されています。</p>
<p>公式サイト: <a href="https://jsconf.jp/2024/">https://jsconf.jp/2024/</a></p>
<h1 id="スポンサーワークショップでは-react-コンポーネント設計について発表します">スポンサーワークショップでは React コンポーネント設計について発表します</h1>
<p>16:00 からトラック D にて、<a href="https://clinics-cloud.com/">クラウド診療支援システム CLINICS</a> の開発で実践している React コンポーネント設計について、開発体制や医療業務システムの観点を踏まえて紹介します。</p>
<blockquote>
<p><a href="https://jsconf.jp/2024/talk/medley/">徹底解剖!医療業務システムの React コンポーネント設計 - 株式会社メドレー | JSConf JP</a></p>
</blockquote>
<p>【ワークショップタイトル】</p>
<p>徹底解剖!医療業務システムの React コンポーネント設計</p>
<p>【ワークショップ詳細】</p>
<p>医療業務システムの Web フロントエンド開発では、高い安全性の要求に応えつつ、複雑化する医療業務に対して診療効率改善につながる質の高いユーザー体験を提供することが求められます。
加えて、長期利用を見据えた保守性の維持も重要です。
これらの要求に応えるため、私たちは約 4 年間の継続的なアーキテクチャ改善を通して React コンポーネント設計を確立してきました。</p>
<p>本セッションでは、メドレーの開発体制の背景を交えながら、クラウド診療支援システム CLINICS の Web フロントエンド開発で実践している React コンポーネント設計を紹介します。
具体的には次の内容を予定しています:</p>
<ul>
<li>コンポーネントレイヤーと分割粒度</li>
<li>独立性の高い複合コンポーネント設計とデザインパターン</li>
<li>代表的なフロントエンドロジックの分類とそれぞれの実装手法</li>
<li>クロスファンクショナル(非職能別)開発チームにおける Storybook 駆動開発の活用</li>
<li>開発速度と品質を両立するためのテスト戦略</li>
</ul>
<p><img __ASTRO_IMAGE_="{"src":"./example.png","alt":"発表予定内容の一部:コンポーネント分割粒度の紹介","index":0}"></p>
<p><em>発表予定内容の一部:コンポーネント分割粒度の紹介</em></p>
<h1 id="ブースでメドレーのエンジニアとお話しましょう">ブースでメドレーのエンジニアとお話しましょう</h1>
<p>トラック D がある 2F の Communication Area にブースを出展します。</p>
<p>ブースでは、メドレーのプロダクトや使用技術、開発組織について、エンジニアとざっくばらんに交流できる場を設ける予定です。</p>
<p>休憩エリアの近くになっていますので、セッションの合間などにぜひお立ち寄りください。</p>
<h1 id="おわりに">おわりに</h1>
<p>最後までお読み頂きありがとうございました。</p>
<p>当日のブースやワークショップでみなさまとお会いできることを楽しみにしております!</p>
- 患者向け薬局検索機能のパフォーマンスを OpenSearch 導入により改善した話https://developer.medley.jp/entry/2024/10/31/233600https://developer.medley.jp/entry/2024/10/31/233600こんにちは。かかりつけ薬局支援システム「Pharms」の開発を担当している熊本です。先日、総合医療アプリ「CLINICS」の薬局検索機能のパフォーマンスを OpenSearch の導入により改善しましたので、その経緯と結果について話していき...Thu, 31 Oct 2024 14:36:00 GMT<p>こんにちは。<a href="https://pharms-cloud.com/">かかりつけ薬局支援システム「Pharms」</a>の開発を担当している熊本です。先日、<a href="https://clinics-app.com/">総合医療アプリ「CLINICS」</a>の薬局検索機能のパフォーマンスを <a href="https://opensearch.org/">OpenSearch</a> の導入により改善しましたので、その経緯と結果について話していきたいと思います。</p>
<h1 id="医療プラットフォームのプロダクト紹介と構成">医療プラットフォームのプロダクト紹介と構成</h1>
<p>まずは私が所属する医療プラットフォーム(以下、医療 PF)のプロダクトをご紹介します。オンライン診療をはじめ様々な医療体験を提供する患者・生活者向けのプロダクトと、医療機関における業務効率と患者体験の向上を支援する事業者向けのプロダクトがあります。プロダクト間のデータ連携に関しては、患者統合基盤というプロダクトによって実現しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024_10_31_1.png","alt":"","index":0}"></p>
<p>患者情報やその医療情報に関するデータに関しては患者統合基盤で保持していますが、薬局店舗のリストや営業時間など調剤薬局店舗に関するデータに関しては Pharms 側で保持しています。本記事では、この調剤薬局店舗に関するデータを扱う Pharms の API についてお話しします。</p>
<h1 id="clinics-の薬局検索機能">CLINICS の薬局検索機能</h1>
<p>CLINICS にはお薬手帳や、お薬辞典、薬局検索など薬局に関する機能がいくつか存在します。
本記事ではその内の薬局検索機能についてお話しするため、簡単にご紹介します。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024_10_31_3.png","alt":"","index":0}"></p>
<p>薬局名で検索できるのはもちろんのこと、マップの範囲検索や市区町村、受付日時などで絞り込むことでご自身の都合にあった薬局を探すことが可能です。
病院等で処方箋を受け取った後に、この薬局検索機能を使ってお薬を受け取りたい薬局を探し、その薬局に対して CLINICS から事前に処方箋を送信しておくことができます。待ち時間なくお薬を受け取ることができるので個人的にも愛用しています!</p>
<p>他にも検索条件はいくつかあるのですが、オンラインで服薬指導を受けた際には Uber Eats により当日中にお薬を配達してくれる機能もあるので、当日配達に対応しているかどうかで絞り込むことも可能です。</p>
<p>この検索機能のバックエンドを Pharms の API が担っており、内部的には検索エンジンを使わず RDB による絞り込みで実現していました。</p>
<h1 id="潜在課題の表層化">潜在課題の表層化</h1>
<p>先ほどご紹介したように薬局検索機能はとても便利なのですが、 Pharms の事業成長に伴い以下のような課題も徐々に出てきました。</p>
<ul>
<li>データ量と検索トラフィックが増加してきた</li>
<li>データ量の増加に伴い検索時のレスポンスが遅くなり患者のユーザビリティが悪化してきた</li>
<li>検索条件の増加に伴い SQL クエリが複雑になり、変更難易度が上がり開発・運用の工数が肥大化してきた</li>
</ul>
<p>潜在的な課題を認識しつつも何とか開発・運用を続けてきたのですが、複雑な組み合わせの検索をクローラーなどにより集中的に実行された場合、処理コストの高い SQL クエリが大量に発行されてしまいレイテンシ悪化が起きるようになってしまいました。</p>
<p>患者側のメトリクスは患者統合基盤チームでもモニタリングしていることもあり、患者統合基盤チームと連携してレイテンシ悪化の原因調査や対応策の検討に取り組み、迅速に一次対応を打つことができました。<strong>異なる開発チームでも必要に応じて連携しながら課題解決に取り組めるところは弊社の特徴</strong>かなと改めて感じました。</p>
<p>しかし、サーバの強化や SQL クエリの改善で対処していたものの、あくまで暫定的な対応であり、対応工数・インフラコストなども鑑みると恒久対応の優先度が上がってきました。</p>
<h1 id="課題の対応に向けて">課題の対応に向けて</h1>
<p>上述の課題の他に、開発プロセスの改善や品質向上に向けた取り組みなど、今後の機能開発を見据えて、一度足元の強化が必要という議論が Pharms 開発チームであったため、薬局検索機能のパフォーマンス改善に限らず「開発基盤改善」としてプロジェクトを発足し、集中的にチーム課題に向き合うことになりました。</p>
<p>ここに関しては、<strong>プロダクトマネージャーと技術課題に関する目線合わせがスムーズにできた</strong>ことも、プロジェクトが早いタイミングで発足した一因であると思います。
Pharms の開発チームにおいてはプロダクトマネージャーが事業部出身であるため、開発計画を練る際には <strong>プロダクトマネージャー/テックリード</strong> がそれぞれ <strong>攻め(KPI の達成を担うもの)/守り(事故リスクを減らすもの)</strong> の観点でやりたいことを列挙しフラットに議論する体制をとっています。普段から守りの観点も重視されている上で、既にユーザビリティに悪影響を及ぼしていることや、その影響範囲・対応工数など諸々を鑑みて意思決定が行われました。</p>
<h1 id="対応">対応</h1>
<p>パフォーマンス問題への対応は2つの観点で行うこととし、現状課題の根本解決に向けて OpenSearch の導入を、再発防止と継続的な改善に向けてモニタリングの強化を行うことにしました。</p>
<h2 id="opensearch-の導入">OpenSearch の導入</h2>
<h3 id="設計実装">設計・実装</h3>
<p>OpenSearch を導入することになるのですが、他いくつかのプロダクトで導入実績があったことや、現状の技術構成に対する親和性、個人的にも過去に別プロダクトで導入経験があったことなどを踏まえて導入自体は非常に早い段階で決定しました。</p>
<p>まずは設計にも関わってくるのでどれくらいのレスポンスを目標とするか等の受け入れ要件を定め、その後 index の設計に移りました。
基本的には既存の検索条件に合わせたシンプルな設計になったのですが、予約枠の表現に関しては頭を悩まされました。
というのも、「準備でき次第」のような検索条件に関しては、営業時間内かどうかに加え、その枠が予約で埋まっていないかどうかも見る必要があり、その表現やリアルタイム性を加味して設計を行いました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024_10_31_2.png","alt":"","index":0}"></p>
<p>index の設計が終わった後は粛々と既存の検索ロジックを OpenSearch のクエリに置き換え、検索に関連するテーブルが更新される箇所に OpenSearch のドキュメントを更新する処理を追加していきました。</p>
<h3 id="検証">検証</h3>
<p>実装後はデグレチェックと負荷耐久性の観点で検証を行いました。
医療 PF 内の QA エンジニアと連携し、各検索条件の組み合わせにおいて期待通りの結果が返却されるかどうかを検証しました。パターンが多かったのでテストケースの作成から実施まで協力してもらい助かりました。おかげさまで私としては Pharms の店舗画面や CLINICS を操作した際に正しく OpenSearch のドキュメントが更新されるかどうかの確認であったり、後述する負荷試験に集中的に取り組むことができました。</p>
<p>負荷試験は、検証環境に本番相当のデータ量を用意した上で、過去に最もレイテンシが悪化した際のリクエスト頻度を少し上回る頻度でリクエストを一定時間かけ続け、<a href="https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/handling-errors.html">AWSのトラブルシューティング</a>などを参考にして基準を十分にクリアできるかどうかの観点で行いました。</p>
<h3 id="リリース">リリース</h3>
<p>諸々の検証をクリアしてリリースを迎えるのですが、ここでも少しだけ工夫した点をご紹介します。
一点目は Feature Flag を利用していつでも以前のロジック(OpenSearch を使わない検索)に戻せるようにしていた点です。念入りに検証したものの Pharms としては初めての OpenSearch 導入だったので、不測の自体が起きた場合でもデプロイなしにいつでも切り戻せるようにしていました。</p>
<p>二点目は index のエイリアス設定です。マッピングの変更などに伴い index の再構築を行う際にダウンタイムが発生しないようにエイリアスの設定をしていました。一般的なベストプラクティスだとは思いますが、ここに限らず過去の知見を活かしながら開発進行できたのは良かったかなと思います。</p>
<h2 id="モニタリング強化">モニタリング強化</h2>
<p>これまで OpenSearch 導入の話をしてきましたが、今後も継続してパフォーマンス劣化をより精度高く、迅速に検知できるようにするためにモニタリングも強化しました。</p>
<p>インフラ側は主に CloudWatch を使い、アプリケーション側は主に Datadog を使ってアラート設定を追加しました。
これまでも主要メトリクスに関しては設定していたのですが、今回調査・対応した知見を活かして、より詳細かつ網羅的に整備しました。
また、アラートが上がる前に気づけるようにするための取り組みとして、隔週で各メトリクスのトレンド監視をする会を設けており、CPU使用率などが徐々に悪化していないかを見るようにしています。</p>
<h1 id="結果">結果</h1>
<p>結果として、薬局検索機能のパフォーマンスに関してはレイテンシが約90%も改善しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024_10_31_4.png","alt":"","index":0}"></p>
<p>パフォーマンスを大幅に改善しユーザビリティを向上させられただけでなく、これまで CloudWatch Logs に流れていた巨大な SQL クエリのログを削減することができたので AWS のコスト改善にも繋がりました。
また、メトリクスのトレンド監視によりアラートが上がるその手前で怪しい動きを見つけることができ、実際に早期対応することができています。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回は CLINICS における薬局検索機能のパフォーマンス改善について、プロジェクト発足の背景からクロージングに至るまでの過程をご紹介しました。</p>
<p>Pharms 開発チームは少数体制なのですが、今回のように QA チームや患者統合基盤チームなど周りを柔軟に巻き込んだ動き方ができて非常に面白いです。また、プロダクトマネージャーなど非エンジニアの方とも建設的にそれぞれの観点で対等に議論ができる点も弊社の大きな特徴です。</p>
<p>メドレーは絶賛エンジニア募集中ですので、弊社の取り組みに興味を持っていただいた方やもっと話を聞いてみたいと思った方は是非ご連絡ください!最後まで読んでいただき、ありがとうございました。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- DroidKaigi 2024にゴールドスポンサーとして協賛しました!https://developer.medley.jp/entry/2024/10/15/120419https://developer.medley.jp/entry/2024/10/15/120419こんにちは。人材プラットフォーム本部でエンジニアをしている山河です。2023 年 4 月に新卒として入社し、徐々に業務の幅を広げています!
さて、メドレーは 2024/9/11 〜 9/13 の 3 日間にベルサール渋谷ガーデンにて開催され...Tue, 15 Oct 2024 03:04:19 GMT<p>こんにちは。人材プラットフォーム本部でエンジニアをしている山河です。2023 年 4 月に新卒として入社し、徐々に業務の幅を広げています!</p>
<p>さて、メドレーは 2024/9/11 〜 9/13 の 3 日間にベルサール渋谷ガーデンにて開催された「<a href="https://2024.droidkaigi.jp/">DroidKaigi 2024</a>」にゴールドスポンサーとして協賛させていただきました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./photo-booth.png","alt":"フォトブースの様子。DroidKaigiのロゴが描かれた大きなバックパネルの前に、宇宙船に乗ったAndroidのキャラクターやDroidKaigiロゴが描かれた、手持ちサイズのパネルが、数枚並んでいます。","index":0}"></p>
<p>DroidKaigi は、<strong>今年で 10 年目を迎える Android カンファレンス</strong>です。国内外から Android エンジニアが集い、幅広い内容のセッションとともに、Android 技術情報の共有とコミュニケーションが行われています。</p>
<p>そんな歴史あるカンファレンスですが、メドレーがブースを出すのは今年が初めて。採用担当・広報・ネイティブアプリエンジニアなど総勢 7 名で参加し、様々なセッションを聞いたり、多くのエンジニアとの交流を深めることができました。</p>
<p>今回は、参加した社員の感想をもとに、DroidKaigi 2024 の様子をお伝えします!</p>
<h1 id="会場の様子">会場の様子</h1>
<p>DroidKaigi 2024 は、たくさんの人で溢れた、非常に賑やかなカンファレンスでした!
こちらはオープニングの様子。朝の時点で、かなりの人数が集まっていることがわかると思います。</p>
<p><img __ASTRO_IMAGE_="{"src":"./opening.png","alt":"オープニングの様子です。広い会場に数百名の参加者が座っており、カンファレンスの説明を聞いています。会場の前方には大きなスクリーンが2枚あり、スライドが投影されています","index":0}"></p>
<p>軽食やコーヒーの提供があったり、ネイルブースに、プリクラまで、様々な企画が行われていました。ネイルブースでは、Android のキャラクターとロゴを使ったネイルを施していただきました!</p>
<p><img __ASTRO_IMAGE_="{"src":"./nail-and-purikura.png","alt":"左右に2枚の画像が並んでいます。左は、右手に施してもらったネイルの写真です。白地のネイルで、親指にAndroidのキャラクターとDroidKaigiのロゴ、薬指にAndroidのキャラクターのデザインが施されています。右は、プリクラブースの写真です。「無料です。譲り合ってご利用ください」と表示されていました。また、前に撮影されたプリクラも貼ってありました","index":0}"></p>
<p>また、DroidKaigi 2024 では、名札にシールを貼って、自分の興味分野を示すことができます。他の参加者の方と話す場面でも、このシールを見ながら技術の話ができ、とても良い取り組みだなと思いました。
私の作った名札はこんな感じです。私は <a href="https://reactnative.dev/">React Native</a> でクロスプラットフォーム開発をしているので、React Native のシールも欲しかったです。残念!</p>
<p><img __ASTRO_IMAGE_="{"src":"./attendance-card.png","alt":"名札の写真です。台紙には上から順に、アイコン、「ATTENDEE」という参加者であることを示す文言、参加者の名前が書かれています。台紙は、DroidKaigi2024と書かれたカラフルなネックストラップのついた、透明なケースに入っています。ケースには、「Frontend」「BackEnd」「iOS」などの属性を表すシールと、Androidのキャラクターなどのデザインのためのシールが貼られています","index":0}"></p>
<p>DroidKaigi 2024 で最も賑やかだったのが、1 日目の夜に開催されたアフターパーティーです。たくさんのお食事が並ぶ中、立食形式で参加者同士が自由に交流できるイベントでした。かなり広いスペースが確保されていたのですが、それでもかなり混雑しているほどの盛況ぶり!
私も参加し、様々な企業の方とお話しすることができました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./party.png","alt":"パーティで提供された料理の写真。右は巨大な刺身の舟盛りの写真、左はローストビーフやカレーなどの写真","index":0}"></p>
<p>また、メドレーは iOSDC Japan 2024 でもスポンサーをさせていただきました(詳細は<a href="https://developer.medley.jp/entry/2024/09/10/205504">こちら</a>)が、iOSDC と最も違うところは、国際色ではないかと思います。iOSDC では日本語でのセッションのみでしたが、DroidKaigi では英語のセッションがあったり、ブースでも英語や韓国語で話しかけられることがありました!</p>
<p>多様な人がいるからこそ、新しい出会いや学びもより多く得られたのではないかと思います。</p>
<h1 id="メドレーブースの様子">メドレーブースの様子</h1>
<p>メドレーブースでは、iOSDC 同様、メドレーが展開するアプリの紹介と、アンケートパネルを用いて参加者の方々に医療体験に関するお悩みを伺いました。</p>
<p>DroidKaigi 2024 での最終結果はこちら!</p>
<p><img __ASTRO_IMAGE_="{"src":"./questionnaire.png","alt":"「医療UXのここなんとかならない?」と題されたアンケートパネルの最終結果。医療に関する12の課題の下に、その課題に同意した参加者が貼った、赤丸のシールが貼られている","index":0}"></p>
<hr>
<p><strong>1 位 待ち時間が長い!</strong></p>
<p><strong>2 位 何科に通えば良いかわからない!</strong></p>
<p><strong>3 位 病院探しに時間がかかる・オンラインで予約したい</strong></p>
<hr>
<p>iOSDC の時と似たような結果になりました!やはりみなさん同じような悩みを抱えていらっしゃるようです。</p>
<p>メドレーでは、<strong>これらの課題を解決するべく、様々なプロダクト・サービスを展開しています</strong>。上位にランクインした課題は、患者向けに提供しているオンライン診療・服薬指導アプリ「<a href="https://clinics-app.com/">CLINICS</a>」(以降、CLINICS アプリ)で以下のような機能を提供することで解決を目指しています。現地では実際に画面を見せながら、機能の説明を行いました。</p>
<hr>
<p><strong>待ち時間が長い!</strong></p>
<ul>
<li>CLINICS アプリを使ってオンラインで診療を受けることができます</li>
<li>CLINICS アプリを使って事前に問診票に回答したり、CLINICS アプリでキャッシュレス決済を行うことでも、待ち時間を減らすことができます</li>
</ul>
<p><strong>何科に通えば良いかわからない!</strong></p>
<ul>
<li>CLINICS アプリで気になる症状を選択し設問に回答すると、考えられる病気と診療科を紹介します</li>
</ul>
<p><strong>病院探しに時間がかかる・オンラインで予約したい</strong></p>
<ul>
<li>CLINICS アプリから、さまざまな条件で病院を探し、そのまま予約をすることができます</li>
</ul>
<hr>
<p>また、オンライン服薬指導や、Amazon ファーマシーとの連携、処方薬の配送などについてもお話ししました。その他にも、モバイルアプリの開発体制や、オンラインビデオ通話の実現方法など、技術的な面でもたくさんのご質問をいただきました。</p>
<p>CLINICS アプリについて説明していくと、「めちゃくちゃ良いですね!」といった反応をいただける反面、「つまりはまだまだ知られていないな」ということを痛感します。もっともっとメドレーの、そして提供しているサービスやプロダクトの認知度を上げていくために、様々な取り組みをしていかなければいけませんね。</p>
<h1 id="セッションやブース">セッションやブース</h1>
<p>DroidKaigi 2024 では、48 のセッションと、30 以上のスポンサーブースが設置されていました。メドレー社員が参加したものの中から、いくつかピックアップしてご紹介します。</p>
<h2 id="セッション宣言的-ui-を学ぶ際に知っておくべき重要なコンセプト">セッション:宣言的 UI を学ぶ際に知っておくべき重要なコンセプト</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/831ef06a093949fdba6aeb542cae6dfc" title="Essential concepts to know when learning Declarative UI" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p><a href="https://developer.apple.com/jp/xcode/swiftui/">SwiftUI</a>、<a href="https://developer.android.com/compose">Jetpack Compose</a>、React Native の実装を通して、宣言的 UI がどのようなコンセプトを持っているかを紹介していました。それぞれ異なるツールキットですが、<strong>宣言的 UI という観点から見ると確かに同じ思想をしている</strong>なと感じることができました。</p>
<p>(詳細ページは<a href="https://2024.droidkaigi.jp/timetable/694168/">こちら</a>)</p>
<h2 id="セッション起動時間で差をつけろアプリ起動パフォーマンス改善">セッション:起動時間で差をつけろ!アプリ起動パフォーマンス改善!</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/867cc425bf9948aebacd2187d6a0cb48" title="起動時間で差をつけろ! アプリ起動パフォーマンス改善!" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p>アプリの起動、と一口に言っても、プロセスがない状態から起動するケースもあれば、バックグラウンドでプロセスが実行されているケースなど様々な起動方法があります。このセッションでは、<strong>アプリの起動にどのような種類があるか、そしてそれぞれのケースでどのような処理が行われるのかを整理</strong>した上で、起動時間の計測方法や改善方法などを、具体例を通じて示していました。今まで起動時間は計測したことがなかったので、これを気にチャレンジしてみたいです。</p>
<p>(詳細ページは<a href="https://2024.droidkaigi.jp/timetable/691431/">こちら</a>)</p>
<h2 id="セッションandroid-view-から-jetpack-compose-へ-jetpack-compose-移行のすゝめ">セッション:Android View から Jetpack Compose へ 〜Jetpack Compose 移行のすゝめ〜</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/5af80c06accb473a937c5b72aa867d72" title="[DroidKaigi 2024] Android ViewからJetpack Composeへ 〜Jetpack Compose移行のすゝめ〜 / From Android View to Jetpack Compose: A Guide to Migration" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p>タイトルの通り、<strong>Jetpack Compose 移行に関するお話</strong>で、<strong>具体的な移行の事例も交えながら説明</strong>されていました。メドレーでも Jetpack Compose を導入しており、その際に苦労した、Jetpack Compose の自由度の高さなども話題に上がっていました。
これから Jetpack Compose へ移行していく人にはとてもわかりやすい発表内容でした。</p>
<p>(詳細ページは<a href="https://2024.droidkaigi.jp/timetable/689254/">こちら</a>)</p>
<h2 id="ブース株式会社ゆめみ">ブース:株式会社ゆめみ</h2>
<p>DroidKaigi ならでは!と思ったブースが、株式会社ゆめみさんのブースです。</p>
<p>Android アプリの開発では Android Studio という IDE が使われることが多いのですが、Android Studio はバージョン名が動物の名前になっています。その動物たちを、リリース順に並び替えよう!というものです(なお、各動物を英語で表した時の頭文字がアルファベット順になるように選ばれているので、全動物を完璧に英訳できればその方針でもクリアできます。)</p>
<p>私もチャレンジしてみましたが、2 箇所間違えてしまいました……難しい!
みなさんは分かりましたか?</p>
<p><img __ASTRO_IMAGE_="{"src":"./yumemi-android-studio-quiz.png","alt":"Android Studioの並び替えクイズ","index":0}"></p>
<h1 id="終わりに">終わりに</h1>
<p>メドレーはこれからも、医療ヘルスケアの未来を作るために、アプリを始めとした様々なプロダクト・サービスを開発し提供していきます。</p>
<p>最後に改めて、DroidKaigi 2024 の運営の皆様、登壇されたスピーカーの皆様、参加者の皆様、お疲れ様でした、そしてありがとうございました!</p>
<h2 id="メドレーでは一緒に働く仲間を募集しています">メドレーでは一緒に働く仲間を募集しています。</h2>
<p>ぜひこの記事やブースなどで興味を持っていただいた方はご連絡いただければと思います!カジュアル面談など実施可能ですので、お気軽にお問い合わせください!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- シンプルで低コストな動画配信の実現:Cloudflare Stream 導入事例https://developer.medley.jp/entry/2024/09/30/102447https://developer.medley.jp/entry/2024/09/30/102447こんにちは。ジョブメドレーアカデミーの開発を担当している德永です。今回は、私たちのオンライン動画研修サービスで Cloudflare Stream を導入した経緯と、そのメリット・デメリットについて詳しくお話しします。
背景
ジョブメドレー...Mon, 30 Sep 2024 01:24:47 GMT<p>こんにちは。ジョブメドレーアカデミーの開発を担当している德永です。今回は、私たちのオンライン動画研修サービスで Cloudflare Stream を導入した経緯と、そのメリット・デメリットについて詳しくお話しします。</p>
<h1 id="背景">背景</h1>
<p><a href="https://jm-academy.jp/">ジョブメドレーアカデミー</a>は、Web / モバイルアプリで提供している介護・障がい福祉・在宅医療事業者向けのオンライン動画研修サービスです。直近では複数の業種への提供を開始し、多くの職員の方々に利用していただけるプロダクトに成長しています。</p>
<p><a href="https://jm-academy.jp/"><img __ASTRO_IMAGE_="{"src":"./job-medley-academy.png","alt":"ジョブメドレーアカデミー","index":0}"></a></p>
<h2 id="要件に合った動画配信プラットフォームを探す">要件に合った動画配信プラットフォームを探す</h2>
<p>ジョブメドレーアカデミーでは、外部の動画配信プラットフォームに研修動画をアップロードして、発行される配信用の URL をプロダクト内で利用し、サービス提供を行っています。これは動画配信基盤をメンテナンスするコストを削減し、動画配信の安定性を確保出来ることを目的としています。</p>
<p>もともと利用していた動画配信プラットフォームは、非常に機能が豊富で様々なユースケース・プロダクトに利用することが可能でした。しかし、豊富な機能が提供されていてもジョブメドレーアカデミーで必要とされているのは主に動画の配信のみであり、その他の機能はほとんど必要ありませんでした。</p>
<p><img __ASTRO_IMAGE_="{"src":"./job-medley-academy-viewing-video.png","alt":"ジョブメドレーアカデミーの動画","index":0}"></p>
<p>また、プロダクトの急成長に伴って動画配信費用の増大 / 定期的な動画配信容量の見積もりやそれに伴う商談といった、サービス利用の上で発生するコスト面に課題を感じていました。</p>
<p>そこで、動画配信プラットフォームの移行を検討することにしました。移行先の動画配信プラットフォームを選定する際、私たちが重視したのは以下のポイントです。</p>
<ul>
<li><strong>シンプルな運用</strong>:専任のインフラエンジニアがいなくても簡単に運用できること</li>
<li><strong>コスト効率</strong>:使った分だけの料金が発生し、予算管理がしやすいこと</li>
<li><strong>機能面</strong>:高度な機能は不要で、動画配信に必要な最低限の機能があること</li>
</ul>
<p>これらの要件を満たすサービスを検討した結果、私たちは Cloudflare Stream を採用することにしました。</p>
<h1 id="競合サービスとの比較">競合サービスとの比較</h1>
<p>ジョブメドレーアカデミーでは、Cloudflare Stream 導入前にいくつかの動画配信プラットフォームを検討しました。その中でも、特に比較検討したのが ULIZA と AWS Media Services でした。</p>
<h2 id="uliza">ULIZA</h2>
<p><a href="https://www.uliza.jp/">ULIZA</a> は日本企業が提供する動画配信プラットフォームで、日本語でのサポートが充実している点が魅力でした。また、動画の編集、配信、分析など、多岐にわたる機能を提供しており、Web サービス上で CMS 機能も利用できるため、多くのユースケースに対応できる柔軟性があります。</p>
<p>しかし、ジョブメドレーアカデミーでは、動画配信の基本的な機能のみが必要であり、ULIZA のようなリッチな機能は必ずしも必要ではありませんでした。また、料金体系も Cloudflare Stream と比較して高額であったため、コスト面での懸念がありました。</p>
<h2 id="aws-media-services">AWS Media Services</h2>
<p><a href="https://aws.amazon.com/jp/media-services/">AWS Media Services</a> は、AWS 上で動画配信インフラを構築できるサービスです。柔軟性が高く、プロダクトの要件に合わせてカスタマイズできる点が魅力ですが、インフラ構築や運用には開発工数・保守運用コストを要するため、トータルでのコストが高くなる可能性があります。</p>
<p>ジョブメドレーアカデミーでは、前述の通りインフラ専任のエンジニアがいないため、運用負荷の低いサーバーレスな動画配信プラットフォームが求められていました。そのため、AWS Media Services は採用を見送ることになりました。</p>
<h1 id="cloudflare-stream-を採用した理由">Cloudflare Stream を採用した理由</h1>
<p>Cloudflare Stream の採用を決定した理由には、いくつかの要素があります。</p>
<h2 id="シンプルな料金体系">シンプルな料金体系</h2>
<p>Cloudflare Stream は、動画の配信時間と保存時間に基づくシンプルな従量課金制です。</p>
<ul>
<li>配信費用: 1,000 分あたり 1 ドル</li>
<li>保存費用: 1,000 分あたり 5 ドル</li>
</ul>
<blockquote>
<p><a href="https://developers.cloudflare.com/stream/pricing/">Pricing | Cloudflare Stream docs</a></p>
</blockquote>
<p>他の動画配信プラットフォームと比べてかなり明瞭な料金体系となっており、予算管理を行いやすいのが特徴です。課金形態が少し異なるため比較しづらいのですが、ジョブメドレーアカデミーのケースでは競合サービスと比較しても安価な料金での利用が可能でした。</p>
<p>ジョブメドレーアカデミーでは、7,500 本以上の動画を提供しており、月額で約 390 ドルの保存費用 + 再生時間による配信費用が発生しています。この料金体系のおかげで、今後のコストを明確に予測でき、予算に応じた運用が可能になりました。</p>
<p><img __ASTRO_IMAGE_="{"src":"cloudflare-stream-pricing.png","alt":"Cloudflare Stream 料金体系","index":0}"></p>
<p><em>実際の課金時の画面</em></p>
<h2 id="高品質な動画を安定して提供">高品質な動画を安定して提供</h2>
<p>Cloudflare Stream では、ユーザーが高画質・低画質どちらで視聴しても料金が変わらないという特長があります。特に、私たちのような研修サービスでは、動画内のテキストや細かい部分の可読性が重要となってくる場合もあります。以前利用していたサービスでは、データ転送量によって料金が変動したため、高画質の動画を提供する際にコスト面での懸念がありましたが、Cloudflare Stream ではその心配がなく、安定した高画質の動画配信が可能になりました。</p>
<h2 id="移行コストの低さ">移行コストの低さ</h2>
<p>ジョブメドレーアカデミーでは前述の通り、もともとは別の動画配信プラットフォームを利用していました。そちらから移行する作業も簡単に実現可能です。</p>
<p>プロダクト上で問題なく動画が視聴できるかの確認は、Web 上で設定や管理が完結するため、動画をアップロードしただけで配信用の URL なども発行され、インフラ専任のエンジニアがいなくても問題なく検証に進むことが可能でした。</p>
<p>実際の動画ファイル移行についても、<a href="https://developers.cloudflare.com/api/operations/stream-videos-upload-videos-from-a-url">アクセス可能な URL から自動で取り込む機能を API として提供している</a>ため、既存の動画プラットフォームで期限付きのリンクを発行し、Cloudflare Stream API 経由で動画配信の URL を渡すだけでアップロードすることが可能です。</p>
<h2 id="開発者体験の良さ">開発者体験の良さ</h2>
<p>開発者体験という面でも、 Cloudflare Stream の魅力は非常に大きかったです。また、Go、TypeScript、Python 向けの SDK が提供されており、API を使った開発が非常にスムーズに進みました。</p>
<p>さらに、専用プレイヤーの提供もあるため、動画の視聴環境を迅速に構築できました。<a href="https://developers.cloudflare.com/api/operations/stream-videos-list-videos">API ドキュメント</a>も非常に充実しており、SDK が未対応の言語でも容易に実装が可能です。</p>
<p><img __ASTRO_IMAGE_="{"src":"./cloudflare-stream-api-reference.png","alt":"Cloudflare Stream API Reference","index":0}"></p>
<p><em>Example なども充実した API ドキュメント</em></p>
<h1 id="実際に導入しての所感">実際に導入しての所感</h1>
<p>Cloudflare Stream を実際に運用してみて感じたメリットと、触ってみるまで判明しなかった課題についていくつかご紹介します。</p>
<h2 id="コスト面での満足度">コスト面での満足度 ⭕</h2>
<p>期待していた通り、シンプルな料金体系で予算管理が非常に容易でした。また、記載されている料金以外の追加課金が発生せず、コスト面での不安は感じていません。</p>
<h2 id="安定性">安定性 ⭕</h2>
<p>導入してから今まで、Cloudflare Stream の安定性に課題感はありません。障害情報は <a href="https://www.cloudflarestatus.com/">Cloudflare Status</a> で確認できるため、運用の際の不安要素も少なく済んでいます。</p>
<h2 id="運用面での評価">運用面での評価 💭</h2>
<p>以前利用していたサービスでは、利用帯域幅の監視やプラン変更の手続き・次回契約のための想定帯域幅算出に工数がかかっていましたが、Cloudflare Stream ではそれが不要になり、運用工数を削減できました。</p>
<p>一方、CMS 機能が最小限に抑えられている点は導入において課題になりやすい点です。例えば、動画のディレクトリ管理ができないため、ID ベースでの管理を自前で設計する必要があります。また、検索機能なども最低限しか提供されていないため、基本的に私達のサービスでは、 Cloudflare Stream 上にアップロードされた動画の meta フィールドに API 経由でアクセスし、動画の種別管理(テスト用の動画 / 本番用の動画など)を行っています。</p>
<p>その他にも、 Cloudflare 側で提供されているアラートの粒度が粗いのは課題としてありました。配信容量と異なり保存容量は月単位で事前購入する課金形態です。そのため、ユーザーの利用状況に合わせて残容量が減ってきたら容量を購入する必要があります。完全にサービス提供側でアップロードする動画を管理できる場合は問題にならないのですが、ジョブメドレーアカデミーではユーザーが動画をアップロードするユースケースも存在するため、容量の監視が必要です。</p>
<p>Cloudflare が提供している機能として容量の上限が近づくとメールが届くのですが、割合での通知のため、総利用容量が増えた場合は「残り 数%ではあるが、容量の実数(nGB アップロード可能か)を見ると当分問題ない範囲である」ということが多々あり、実際に容量を購入しないと不足してしまうタイミングがいつかわかりません。Cloudflare Stream 側で残容量を取得する API が提供されているため、動画アップロード時にこの API を利用して容量確認を行い、残容量が少なくなった場合に Slack 通知を行うように開発を行いました。</p>
<p><img __ASTRO_IMAGE_="{"src":"cloudflare-stream-alert-mail.png","alt":"実際に送信されたメール","index":0}"></p>
<p><em>実際に送信されるメール通知。3,322 分あれば直近は充分なのだが、毎日通知が来る</em></p>
<p>また、Web ダッシュボード上でのファイル管理が限定的であるため、他の動画配信プラットフォーム(ULIZA などのリッチな機能を持つ動画配信プラットフォーム)に比べて管理の手間がかかる部分もあります。</p>
<p><img __ASTRO_IMAGE_="{"src":"cloudflare-stream-dashboard.png","alt":"Cloudflare Stream ダッシュボード画面","index":0}"></p>
<p><em>実際のダッシュボード画面</em></p>
<p><img __ASTRO_IMAGE_="{"src":"cloudflare-stream-video-detail.png","alt":"Cloudflare Stream 動画詳細画面","index":0}"></p>
<p><em>実際の動画詳細画面</em></p>
<h2 id="機能面での評価">機能面での評価 💭</h2>
<p>基本的には期待していた通り、動画配信の実装をシンプルに実現することができました。しかし、一部のケースにおいて <a href="https://www.cloudflare.com/ja-jp/learning/video/what-is-http-live-streaming/">HLS</a> / <a href="https://www.cloudflare.com/ja-jp/learning/video/what-is-mpeg-dash/">MPEG-DASH</a> を使った動画配信への対応に工数がかかりました。</p>
<p>HLS / MPEG-DASH での配信は<a href="https://www.cloudflare.com/ja-jp/learning/video/what-is-adaptive-bitrate-streaming/">アダプティブ・ビットレート・ストリーミング</a>が基本となり、視聴者のネットワーク環境によって動的に配信される画質が決まります。この機能はネットワーク環境に最適な画質が自動で選択されるため、動画を視聴する職員の方々にとってより良い視聴体験を提供することに繋がりました。</p>
<p>一方、この機能を利用すると一部の動画プレイヤーでは視聴端末のネットワーク環境に適した最高画質でのみ配信され、画質がプレイヤー上で選択出来ないという問題がありました。ジョブメドレーアカデミーの動作環境の中では、iOS Safari x Stream Player や React Native の Expo Player が該当します。</p>
<p>ジョブメドレーアカデミーでは、私用スマホを利用して動画研修を受講される職員の方が Wi-Fi 環境を用意できず、データ通信量を節約するために画質を下げて視聴するというケースがあります。自動的に最高画質での配信が行われてしまう場合、意図せずデータ通信量が増大してしまうことで一部の職員の方にとって不利益が生じる可能性があります。</p>
<p>以前まで利用していた動画配信プラットフォーム上では、mp4 ファイルで複数画質の動画が提供されていたため、ダウンロードする mp4 ファイルの画質を選択することで視聴する職員の方が画質を制御することが出来ました。一方、 Cloudflare Stream では mp4 ファイルでの配信自体は設定により可能ですが、配信される mp4 の画質は常に最高画質での配信に固定されるため、今回の画質を選択したいというケースには利用できませんでした。</p>
<p>また、 mp4 を利用すると動画配信が発生した段階で動画全体の再生時間が課金対象になります。例えば 30 分の動画を 1 分だけ視聴して配信画面を閉じた場合、 HLS / MPEG-DASH だと 1 分が配信費用の課金対象として計上されるのに対し、 mp4 での配信だと 30 分が配信費用の課金対象として計上されてしまいます。利用状況によってはコストがかさむ可能性があるため、注意が必要です。</p>
<p>この配信する画質についての課題は、 Cloudflare Stream で提供されている動画配信時に<a href="https://developers.cloudflare.com/stream/viewing-videos/using-own-player/#customize-manifests-by-specifying-available-client-bandwidth">画質を制御するオプション(clientBandwidthHint)</a>を利用することで解決することが出来ました。このオプションは最終手段であるという旨がドキュメント内に記載されていますが、今回のケースでの利用は妥当だということを Cloudflare Stream の開発者である Zaid Saleem 氏に X 上で確認済みです。</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Makes sense. Have you already tried clientBandwidthHint feature of Stream? <a href="https://t.co/6KAf94MNuR">pic.twitter.com/6KAf94MNuR</a></p>— Zaid Saleem (@zaid) <a href="https://twitter.com/zaid/status/1791274100729471168?ref_src=twsrc%5Etfw">May 17, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>結果的に画質を選択する機能を提供することが出来ました。が、Cloudflare Stream が提供する配信方法の仕様上仕方ないところではありつつ、他の動画配信プラットフォームで提供している mp4 の特定の解像度を配信する方法と比較すると、直感的ではなく開発工数がかかってしまいました。</p>
<details>
<summary>clientBandwidthHint と解像度の対応表(ジョブメドレーアカデミーのフルHD動画で検証したデータ)</summary>
<table><thead><tr><th>clientBandwidthHint</th><th>画質</th></tr></thead><tbody><tr><td>0.1</td><td>240p</td></tr><tr><td>0.2</td><td>360p</td></tr><tr><td>0.3</td><td>480p</td></tr><tr><td>0.4</td><td>480p</td></tr><tr><td>0.5</td><td>720p</td></tr><tr><td>0.6</td><td>720p</td></tr><tr><td>0.7</td><td>720p</td></tr><tr><td>0.8</td><td>720p</td></tr><tr><td>0.9</td><td>720p</td></tr><tr><td>1.0</td><td>1080p</td></tr></tbody></table>
</details>
<h1 id="cloudflare-stream-の採用を検討している方へ">Cloudflare Stream の採用を検討している方へ</h1>
<p>Cloudflare Stream を導入するメリットは大きいですが、サービスの特性上、すべてのケースに適しているわけではありません。以下のポイントを踏まえて検討することをお勧めします。</p>
<h2 id="cloudflare-stream-が向いているケース">Cloudflare Stream が向いているケース</h2>
<ul>
<li>シンプルで予測可能な料金体系を希望している</li>
<li>高品質な動画を安定して提供したい</li>
<li>開発リソースや運用リソースが限られている</li>
<li>開発者向けのサポートやドキュメントが充実している環境を求めている</li>
</ul>
<p>特に、Cloudflare 内の他サービス(Workers や Pages など)を利用している場合、Stream との組み合わせでコスト削減や運用の効率化が期待できます。</p>
<h2 id="cloudflare-stream-が向いていないケース">Cloudflare Stream が向いていないケース</h2>
<p>一方で、以下のようなケースでは他のサービスを検討することも選択肢に入れるべきです。</p>
<h3 id="動画サイズが-30gb-を超えるような動画が必要な場合">動画サイズが 30GB を超えるような動画が必要な場合</h3>
<p>Cloudflare Stream では、1 動画あたり最大 30GB のアップロード制限があります。長時間の高解像度動画を扱う場合は、この制限に注意が必要です。</p>
<blockquote>
<p>By default, a video upload can be at most 30 GB.<br>
<a href="https://developers.cloudflare.com/stream/faq/#is-there-a-limit-to-the-amount-of-videos-i-can-upload">Is there a limit to the amount of videos I can upload? | Frequently asked questions about Cloudflare Stream</a></p>
</blockquote>
<h3 id="高度な分析機能が必要な場合">高度な分析機能が必要な場合</h3>
<p>Cloudflare Stream の分析機能はミニマムなものであるため、視聴履歴や詳細な視聴データの分析は外部ツールで行う必要がある場合があります。</p>
<p><img __ASTRO_IMAGE_="{"src":"cloudflare-stream-analytics.png","alt":"Cloudflare Stream 分析画面","index":0}"></p>
<p><em>実際の分析画面 - 基本的に課金単位の再生時間に注目した、分析機能のみ提供されている</em></p>
<h3 id="動画管理機能が求められる場合">動画管理機能が求められる場合</h3>
<p>前述の通り、 Cloudflare Stream は他サービスと比べ、CMS 機能は非常に限られています。動画のディレクトリ管理やタグ付けなど、細かい管理が必要な場合には、自前で管理システムを構築するか、他のサービスを選ぶ方が効率的です。</p>
<h1 id="おわりに">おわりに</h1>
<p>Cloudflare Stream のシンプルな料金体系と高品質な動画提供は大きな魅力です。一方で、CMS 機能や高度な分析機能を求める場合には、他のサービスも視野に入れる必要があります。</p>
<p>動画配信プラットフォームの選定は、プロダクトの特性やチームの状況に応じて異なりますが、この記事がその一助になれば幸いです。今後もユーザー体験の向上とプロダクトの成長に向けて、最適な技術選定を続けていきたいと思います。</p>
<hr>
<p><em>2024 年 9 月時点の情報をもとに執筆しました。サービス内容や仕様は変更される可能性がありますので、最新情報は公式の情報をご参照ください。</em></p>
- iOSDC Japan 2024に プラチナスポンサーとして協賛しました!https://developer.medley.jp/entry/2024/09/10/205504https://developer.medley.jp/entry/2024/09/10/205504
こんにちは。人材プラットフォーム本部で EngineeringManager をしている倉林です。2024 年 4 月にメドレーにジョインし、少しづつエンジニアの組織作りに携わり始めています。
メドレーは 2024/8/22 〜 8/24...Tue, 10 Sep 2024 11:55:04 GMT<p><img __ASTRO_IMAGE_="{"src":"./image7.png","alt":"","index":0}"></p>
<p>こんにちは。人材プラットフォーム本部で EngineeringManager をしている倉林です。2024 年 4 月にメドレーにジョインし、少しづつエンジニアの組織作りに携わり始めています。</p>
<p>メドレーは 2024/8/22 〜 8/24 の 3 日間に早稲田大学西早稲田キャンパスにて開催された「<a href="https://iosdc.jp/2024/">iOSDC Japan 2024</a>」にプラチナスポンサーとして協賛させていただきました。
iOSDC は、iOS 関連技術をコアのテーマとしたソフトウェア技術者のためのカンファレンスで、各所からアプリエンジニアが集う大規模なイベントです。毎年、多くのアプリエンジニアが集まり、最新の技術やトレンドを共有する活気あるイベントとして知られています。</p>
<p>2016 年から開催され、今年で 9 回目。メドレーはなんと今年で 8 年連続のスポンサー参加となり、今年も採用担当・広報・ネイティブアプリエンジニアが参加し、多くのエンジニアや関係者の方々との交流を深めることができました。</p>
<h1 id="会場の様子">会場の様子</h1>
<p>会場は早稲田大学西早稲田キャンパス。今年はオンライン・オフラインのハイブリッド開催で、オフライン 1100 名、オンライン 400 名の合計約 1500 名以上の参加者で賑わいました。</p>
<p>登壇や LT などもたくさんあり、通常セッションが 50 セッション、LT がルーキーズ LT 含めて 30 本以上で内容も幅広く、様々なインプットを得ることができました。</p>
<p>まさに「アプリエンジニアのお祭り」という雰囲気でした!</p>
<p>会場は各ブースをスタンプラリー方式で巡る形式だったこともあり、どのブース・空間も終始賑やかで、スポンサー・一般参加者・エンジニア・人事関係なく交流が行われていました。大学での開催ということもあるのか学生も多く参加しているのが印象的でした。</p>
<p>スポンサーブーススタンプラリー、フリードリンクや軽食、お昼時にはキッチンカー、朝ドーナツや夕方からはアルコール類の提供、ネイルやボディペイントなどのアートブースなど楽しむための様々な工夫がされていて、まさにコミュニケーションの場となる空間作りがされたカンファレンスだったと感じました。運営の方々の準備と当日のスムーズな対応に感謝です。
(スタンプを 1 列揃えて BINGO するとたこ焼き、2 列揃えるとクレープ、全部集めると iOSDC ノベルティと交換)</p>
<p><img __ASTRO_IMAGE_="{"src":"./image2.jpg","alt":"","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./image3.jpg","alt":"","index":0}"></p>
<h1 id="メドレーブースの様子">メドレーブースの様子</h1>
<p>メドレーブースでは、メドレーが展開するアプリの紹介と、アンケートパネルを用いて参加者の方々に医療体験に関するお悩みを伺いました。
特に課題として多かったは以下の 3 つ</p>
<ol>
<li>待ち時間が長い
<ol>
<li>圧倒的一位でした、特に子供がいる家庭は子供を連れて薬局へも行かなくてはいけないのが辛い との回答が多数</li>
</ol>
</li>
<li>キャッシュレス決済がしたい
<ol>
<li>大分増えてはきたけど。。。との声でした!</li>
</ol>
</li>
<li>オンラインで予約したい
<ol>
<li>電話で予約するのが面倒という方が圧倒的に多かった</li>
<li>予約とはちょっと違うけど病院の口コミを見たい という声も多かったです</li>
</ol>
</li>
</ol>
<p>それぞれの課題についてメドレーのプロダクト・サービスを通じて解決できていること・これから解決していくことなどをご紹介させていただきました。</p>
<p>iOS のイベントということもあり、実施に <a href="https://clinics-app.com/">CLINICS アプリ</a>の画面をお見せしながら説明させていただき以下のような感想もいただきました。</p>
<ul>
<li>オンライン診療が受けれて、薬が Uber Eats で届く。もうそこまで体験が到達しているとな思わなかった</li>
<li>病院検索の UI が体感が早く心地よい・どうやって実装しているのか</li>
<li>オンライン診療で話した内容を電子カルテに書き込んだりできないのか?など新しい機能提案</li>
</ul>
<p>などなど。</p>
<p><img __ASTRO_IMAGE_="{"src":"./image5.png","alt":"","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./image1.png","alt":"","index":0}"></p>
<p>メドレーはアプリを展開している印象があまりなかったので、参考になったなどの声も頂きました。</p>
<p>各アプリの技術スタックは以下の画像の通りで、React Native や Flutter などのクロスプラットフォーム技術で開発しているものもあれば Swift, Kotlin などネイティブ言語で開発しているものもあり提供したい価値やアプリ特性によって様々な技術スタックを選定しています。</p>
<p>CLINICS では全画面の実装が SwiftUI 、SPM を用いたマルチモジュール構成だったりと新しい技術を積極的に導入しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./image8.png","alt":"","index":0}"></p>
<h1 id="セッションやブース">セッションやブース</h1>
<p>個人的に気になったセッション及び、弊社メドレーの吉田による登壇について少し紹介させていただければと思います。</p>
<h2 id="ditto-sdk-紹介-インターネットなしで快適なデータ同期">Ditto SDK 紹介: インターネットなしで快適なデータ同期</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/29d85c71e06b49efba8ea4a294d2a770" title="Ditto SDK: インターネットなしで快適なデータ同期" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p>アプリ内に SDK の処理を入れておくと、P2P で端末同士がローカルでやり取りしてデータを同期できます、というサービスの紹介。
Flutter や RN にも対応していて「ローカルの端末同士で P2P をいい感じにやる部分」と「ローカルのデータを吸い上げて適宜配信する中央サーバー」といった印象で、需要ありそうだと感じました。</p>
<h2 id="swift-6-の-typed-throws-と-swift-におけるエラーハンドリングの全体像を学ぶ">Swift 6 の Typed throws と Swift におけるエラーハンドリングの全体像を学ぶ</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/dbfb8adf36b44c0e890c62bf3d770e09" title="Swift 6のTyped throwsとSwiftにおけるエラーハンドリングの全体像を学ぶ" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p>Swift 6 の新機能の 1 つである Typed throws についての紹介。
安易に使うべきではないとしながら、どういう場面では効果的かなど、実践的な内容も聞くことができました。
普段使っている Optional がエラーハンドリングにおいてどの位置づけかを含め、エラーハンドリングの全体像の概観を理解することができました。</p>
<h2 id="座談会-strict-concurrency-と-swift-6-が開く新時代-私たちはどう生きるか">座談会 「Strict Concurrency と Swift 6 が開く新時代: 私たちはどう生きるか?」</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/9a8c967a5f554178a06b19fb6114a131" title="座談会 「Strict ConcurrencyとSwift 6が開く新時代: 私たちはどう生きるか?」" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p>超満員の人気セッションで、Swift 6 対応の盛り上がりを感じました。
CLINICS アプリでも Swift 6 対応を進めているのですが、どこまでを MainActor のスコープとするか、どのレベルでのリファクタリングを同時にやっていくかが議論になっていたので、大変参考になりました。
座談会という形式も普段のトークとは異なり新鮮でした。</p>
<h2 id="bitkey-さんブースbitlink-をさがして">bitkey さんブース:bitlink をさがして!</h2>
<p>個人的に一番面白かったのは Bitkey さんのブースです。iBeacon になる自社製品(bitlink)を携帯して歩いている社員の方がいるので、iOS アプリを自作してその人を見つけられれば、景品を渡す、という企画でした。
メドレーメンバーも結構早めにアプリ作って探しましたが、3 番目だったとのこと。残念!</p>
<p><img __ASTRO_IMAGE_="{"src":"./image4.png","alt":"","index":0}"></p>
<h2 id="forkwell-さんブース">Forkwell さんブース</h2>
<p>会場で配布していた資料に、イチ推し求人としてメドレーの求人情報を掲載していただいていました!いつもお世話になっております!</p>
<p><img __ASTRO_IMAGE_="{"src":"./image9.png","alt":"","index":0}"></p>
<h2 id="メドレー登壇">メドレー登壇</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/0f32d4cea02342dbb26a3b5cef715586" title="医療アプリ開発の最前線 - 安全性と生産性の両立への挑戦 -" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<p><img __ASTRO_IMAGE_="{"src":"./image11.jpg","alt":"","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./image10.jpg","alt":"","index":0}"></p>
<p>医療アプリ開発における安全性と生産性のバランスについて医療プラットフォーム本部 患者プロダクト開発グループの吉田 (<a href="https://x.com/shzero5">@shzero5</a>) が登壇させていただきました。</p>
<p>医療アプリの開発においては、患者の安全と利便性の向上が最重要課題となります。一方で、医療制度の変更への迅速な対応や新規機能の実装など、高い生産性も求められます。この 2 つの要件は相反するものですが、その両立は避けられない課題だと感じています。</p>
<p>今回の登壇では、メドレーのオンライン診療サービスを含む総合医療アプリ「CLINICS」の開発を通じて、いかにして安全性と生産性の両立に取り組んでいるかをご紹介しました。</p>
<ul>
<li>診療報酬改定などの制度変更に柔軟に対応できる体制</li>
<li>SPM マルチモジュール、トランクベース開発など生産性向上の工夫</li>
<li>MagicPod を活用した自動テスト環境での効率的な品質確保</li>
<li>法律・ガイドライン・制度対応や社内他部門との連携を含む総合的なアプローチ</li>
</ul>
<p>医療アプリの開発は技術的側面のみならず、適切な規制対応や組織間調整など様々な課題に直面します。
今回の登壇では、そうした課題への具体的な取り組みをご紹介しつつ、品質と速度の両立に向けた実践的な知見をお話ししました。</p>
<p>質問もいくつかいただきました。</p>
<ul>
<li>トランクベース開発とのことだが、ブランチ作成からマージまでの間隔は?
<ul>
<li>大きな機能開発でも、ブランチ作成からコードレビューを通してマージまで、1 日かからない程度の粒度に分割しています</li>
</ul>
</li>
<li>MagicPod の導入をチームで進めているが、自動化するテストシナリオの選択基準は?
<ul>
<li>MagicPod で自動化できるテストシナリオは全て自動化しているが、ビデオ通話など、どうしても自動テストしづらい部分は課題として残っています</li>
</ul>
</li>
<li>iOS、Android、Web で越境して実装してるとのことだが、コードレビューはどうしているのか
<ul>
<li>個々で強みのプラットフォームがあるので、知見のある人がレビューすることが多いです</li>
</ul>
</li>
</ul>
<p>発表後にブースに来ていただいて面白かったとコメント頂いたり、改めて医療領域に興味をもっていただけたりしました。医療 × テクノロジーの最前線で得られた学びを、皆様の開発にも活かしていただければ幸いです。</p>
<h1 id="終わりに">終わりに</h1>
<p>メドレーはこれからもアプリを通じて様々な医療体験にインパクト与えるようにプロダクト・サービスを開発していきます。引き続き技術的なチャレンジも行っていきますのでご注目ください!</p>
<p>最後に、改めて iOSDC Japan 2024 の運営の皆様、登壇されたスピーカーの皆様、参加者の皆様、ありがとうございました。来年 10 周年の開催も楽しみにしています!</p>
<h1 id="メドレーでは一緒に働く仲間を募集しています">メドレーでは一緒に働く仲間を募集しています。</h1>
<p>ぜひこの記事やブースや登壇などで興味を持っていただいた方はご連絡いただければと思います!カジュアル面談など実施可能ですので、お気軽にお問い合わせください!
<a href="https://www.medley.jp/jobs/">https://www.medley.jp/jobs/</a></p>
<p>また <a href="https://2024.droidkaigi.jp/">DroidKaigi 2024</a>にもスポンサードしてブースなど予定していますので、興味のある方はぜひ会場にて会話させていただけると嬉しいです。</p>
- E2Eテストの信頼性と向き合ってMagicPodのヘルススコアが100に達した話https://developer.medley.jp/entry/2024/08/30/144708https://developer.medley.jp/entry/2024/08/30/144708
こんにちは、医療プラットフォーム本部 プロダクト開発室 QA グループ所属の小島大周 (@Daishu) です。2022 年 4 月に QA エンジニアとしてメドレーに入社しました。主に “クラウド診療支援システム CLINICS” のプ...Fri, 30 Aug 2024 05:47:08 GMT<!-- Edit here! -->
<p>こんにちは、医療プラットフォーム本部 プロダクト開発室 QA グループ所属の小島大周 (<a href="https://x.com/daishu1130?s=11&t=_aFmdhMTTjU9g5_qC7qJnw">@Daishu</a>) です。2022 年 4 月に QA エンジニアとしてメドレーに入社しました。主に “<a href="https://clinics-cloud.com/">クラウド診療支援システム CLINICS</a>” のプロダクト開発における QA と、E2E テスト自動化を担当しています。</p>
<p>今回は、MagicPod という自動テストツールに半年間向き合い、テスト内容と結果の信頼性を高める改善を行った話をさせていただきます。</p>
<p>※ 自動テストツール全般に共通する話なので、自動テストに携わっている方々にご覧いただけると大変嬉しいです。</p>
<h1 id="magicpod">MagicPod</h1>
<p>弊社は E2E テストの自動化に対して <a href="https://magicpod.com/">MagicPod</a> というツールを採用しています。MagicPod の詳細は割愛しますが、導入時の話は「<a href="https://developer.medley.jp/entry/2021/01/15/180126">UIテストの自動化に MagicPod を導入した話</a>」にありますので、ぜひご覧ください。</p>
<h1 id="活用状況と背景">活用状況と背景</h1>
<p>私が所属する医療プラットフォーム本部では、医療機関に向けて予約や問診、診察 (電子カルテ)、会計等の機能を提供する “<a href="https://clinics-cloud.com/">クラウド診療支援システム CLINICS</a>” と、患者向けにオンライン診療をはじめ、総合的な医療体験を提供する “<a href="https://clinics-app.com/">オンライン診療・服薬指導アプリ CLINICS</a>” を展開しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024_8_30_1.png","alt":"","index":0}"></p>
<p>私は画像左側の医療機関向けのプロダクトにおいて社内で **周辺領域<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref="" aria-describedby="footnote-label">1</a></sup> ** と呼ぶ機能群 を中心としたQA・テスト自動化を担当しています。MagicPod は導入して4年目になりますが、半年前までこの周辺領域の機能に関しては、MagicPod を十分活用しきれておらず、以下のような状況でした。</p>
<ul>
<li>MagicPod の自動テスト実行頻度が週 1 回のみ</li>
<li>自動化したリグレッションテストが 3 件しかない (<a href="https://speakerdeck.com/magicpod/magicpodnotesutozi-dong-hua-herususukoahadouyatutejue-marunoka?slide=15">20件が推奨値</a>)</li>
<li>テスト結果が不安定で、自動テストの修正にかかる調査コストが大きい</li>
</ul>
<p>また、MagicPod には “<a href="https://prtimes.jp/main/html/rd/p/000000030.000027392.html">ヘルススコア機能</a>” がありますが、当時のヘルススコアは 30 点と低い数字でした。これは端的に言うと「<strong>自動テストの信頼性が低い</strong>」という状態です。一方で、画像右側の CLINICS アプリ (患者向け) は MagicPod を十分活用できており、既にヘルススコアも 100 点に達していました。それゆえに、CLINICS (医療機関向け) の自動テストの改善が望まれていました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024_8_30_4.png","alt":"","index":0}"></p>
<div style="text-align: center; font-style: italic; color: #2E3C4D; margin-bottom: 32px;">
当時の CLINICS (医療機関向け) のヘルススコア
</div>
<h1 id="magicpod-ヘルススコア機能">MagicPod ヘルススコア機能</h1>
<p>自動テストの改善について話す前に、MagicPod が提供しているヘルススコア機能について説明します。ヘルススコア機能とは、自動テストの活用度合いや成功率などを 100 点満点でスコアリングしたもので、2023 年 12 月にリリースされました。</p>
<p>ざっくりとした採点基準の内訳と配点は下記となっています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024_8_30_7.png","alt":"","index":0}"></p>
<div style="text-align: center; font-style: italic; color: #2E3C4D; margin-bottom: 32px;">
<a href="https://speakerdeck.com/magicpod/leantodevopsnotamenie2etesutogadekirukoto?slide=16">引用元:LeanとDevOpsのために E2E テストができること</a>
</div>
<p>注目すべきは、「<strong>テストから信頼性のあるフィードバックを早いサイクルで得られているか</strong>」を重視している点です。これは、手動テストの工数削減や、自動テストでの不具合検出数のような<strong>費用対効果を意識した指標ではなく、開発生産性への貢献を意識した指標</strong>となっています。スコアが低いということは「テストに信頼性がなく必要なフィードバックが得られていない」ことを意味しており、自動テストが開発生産性に貢献できていない状態となります。</p>
<p>私個人も、テストカバレッジが十分ある自動テストが統合ブランチでパスしているなら「開発ブランチで不具合を潰してマージされている」と考えますし、自動テストでは「<strong>統合ブランチが stable であることを保証する</strong>」ことが、「不具合を見つけた件数」と並んで重要と捉えています。ヘルススコア機能がリリースされたことで、こういった活動も定量的に測定できるので重宝しています。</p>
<h1 id="主に3つの改善を実施しました">主に3つの改善を実施しました</h1>
<p>本題である E2E 自動テストの改善について話していきます。約半年前から本腰をいれて自動テストの信頼性向上に取り組みました。</p>
<h2 id="-デイリー実行で不安定なテストを解消した">① デイリー実行で不安定なテストを解消した</h2>
<p>早いサイクルでフィードバックを得るには、自動テストのデイリー実行は必須です。そもそも、ウィークリー実行にしていた理由は、テスト結果が不安定なケース (以下、Flaky test) が多数あり、不具合かどうか調査するコストが大きかったからです。実際、調査してもほとんどが Flaky test で自動テスト側の不備で終わることが多く、ここに時間を割くなら開発機能の QA に使いたいという考えが当時ありました。とはいえ、Flaky test と一口に言っても突き詰めると何かしらの原因があり、発生パターンは限定的です。テスト結果を信頼できるものにするためにも、デイリー実行で Flaky test と向き合いました。</p>
<p>まず、デイリー実行すれば Flaky test となるパターンに遭遇できます。次に「なぜ Flaky test として失敗するのか?」を 1 件ずつ追求していくことで、見つけた “穴” を確実に塞いでいきます。そして、この活動を毎日繰り返すことで、堅牢で精度の高い自動テストを構築できました。これは信頼性のあるフィードバックを得る上でも重要な要素でした。今は「<strong>自動テストはデイリー実行で育てるもの</strong>」という考えで、日々の自動テストの結果と向き合うことができています。</p>
<h2 id="-テストケースの二重メンテナンスを止めた">② テストケースの二重メンテナンスを止めた</h2>
<p>テストカバレッジが十分でないと、毎日自動テストを動かしても「統合ブランチが stable である」と言い切ることはできません。信頼性を高める上で、テスト対象とする機能の充実化は必須要件でした。しかし、当時のリグレッションテストの自動化フローは、手動テスト用のケースをスプレッドシートで作成した上で自動化する流れとなっており、テストケースを二重メンテナンスする形となっていました。このように本来の必要以上の工数がかかってしまうことからテストカバレッジを素早く上げづらい状況がありました。</p>
<p>手動テスト用のケースは、自動化後に触る機会がほとんどないにも関わらず、仕様変更などで自動テストに手を加えるたびにメンテナンスコストが発生します。もちろん、仕様キャッチアップ等の特定シーンで役立つ事はありますが、常にではありません。こういった運用コストの課題もあり、<strong>テストケースの二重メンテナンスはしない方針</strong> に切り替えました。</p>
<p>現在は下記フローで MagicPod のテスト実装のみ行なっています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024_8_30_6.png","alt":"","index":0}"></p>
<div style="text-align: center; font-style: italic; color: #2E3C4D; margin-bottom: 32px;">
リグレッションテストの自動化手順
</div>
<p>この方針により、約半年で <strong>CLINICS の周辺領域に含まれる機能のテストカバレッジを 100% まで拡充</strong>できました。一方で、手動テスト用のケースがないことで「テストケースを使った仕様キャッチアップができない、そもそもどんな自動テストがあるのか外から見えづらい」とった課題が生まれましたが、MagicPod がテスト実行時に取得するキャプチャを用いた仕様書や、機能一覧と自動化したテストケースをマッピングした資料を用意することで対応しています。この取り組みについては、別途またご紹介できればと思います。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024_8_30_2.png","alt":"","index":0}"></p>
<div style="text-align: center; font-style: italic; color: #2E3C4D; margin-bottom: 32px;">
MagicPod のテスト概要欄の記載例
</div>
<h2 id="-先行して共有ステップを作成した">③ 先行して共有ステップを作成した</h2>
<p>この改善が最も成果に結びつきました。<a href="https://support.magic-pod.com/hc/ja/articles/4408910141977">共有ステップ</a>とは MagicPod の標準機能で、テストステップを汎用的なステップ (関数化) として流用できるものです。一般的に、複数のテストで同一のステップがある場合、共有ステップを作成されると思いますが、私は現時点で同一ステップがなくても、<strong>先行投資的に共有ステップを作成する方針</strong>で実装を進めました。これにより、早い段階で共有ステップを拡充できました。</p>
<p>現在は、共有ステップをパズルのように組み上げる、実際のコーディングに近い実装スタイルを確立できました。実装スピードは格段に上がり、再発防止等で確認項目を追加する場合は 10 分ほど、ゼロから大きめの E2E テストを実装する場合でも 1-2 時間ほどで作成できます。</p>
<p>実装スピードの向上はテストカバレッジを上げる上でも役立ちましたが、他のテストで動作保証できた共有ステップを使い回すことで「<strong>デイリー実行で自動テストを育てる前から安定したテストを作りやすい</strong>」という点も大きかったです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024_8_30_5.png","alt":"","index":0}"></p>
<div style="text-align: center; font-style: italic; color: #2E3C4D; margin-bottom: 32px;">
共有ステップ例:Mailtrapに届いたメールの本文から正規表現に一致するテキストを取得
</div>
<h1 id="改善した結果どうなったか">改善した結果どうなったか</h1>
<p>一番の成果は、前日にマージされた Pull Request の不具合検出が毎朝行えるようになり、冒頭紹介した「テストから信頼性のあるフィードバックを早いサイクルで得られる」点が達成できたことです。また、テストカバレッジも十分あるので「統合ブランチが stable である」ことが保証でき、<strong>機能開発や QA しやすい状況づくり (=開発生産性) にもコミットできている</strong>と思います。</p>
<p>障害発生時には、MagicPod で再発防止策の自動テストを作るという手段を QA エンジニアだけで完結できるようになりました。これまで QA 側で対応できることは手動のリグレッションテストを追加する方法が多かったですが、<strong>自動テストで再発を抑止するという解決手段を QA エンジニアが持てることは運用コスト的にも大きい</strong>です。</p>
<p>これらの改善を通して、開発エンジニアの方々からの自動テストに対する信頼感が高まってきた、と感じることも増えてきました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2024_8_30_3.png","alt":"","index":0}"></p>
<div style="text-align: center; font-style: italic; color: #2E3C4D; margin-bottom: 32px;">
障害の再発防止として自動テストを作成した際
</div>
<p>また先日にはMagicPod 社の<a href="https://note.com/magicpod/n/n8d74743d75d5">ミートアップイベント</a>に登壇する機会もいただきました。登壇時に利用したスライドは下記です。共有ステップの Tips や安定した Xpath ロケータを取得する方法などを紹介しているので、既に MagicPod を利用されている方はぜひご覧ください。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/eb8feeba29354cf1a4218d718e64bfa1" title="ヘルススコア100に到達した理由 / MagicPod Meetup Health score Night" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>
<h1 id="今後やりたいこと">今後やりたいこと</h1>
<p>上述した改善により、CLINICS (医療機関向け) の周辺領域における MagicPod のヘルススコアは 30 点から 100 点まで上げることができました。現在は、自動テストの新規追加や変更を加えた際にスコアが変動しないか注視しながら改善を継続しており、<strong>デイリー実行する自動テストの安定稼働と拡張の両立</strong>ができています。</p>
<p>今後もこのプロセスを崩さず、<strong>CLINICS の基幹領域<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref="" aria-describedby="footnote-label">2</a></sup> に対してもテストカバレッジを広げていきたい</strong>と考えています。まずは、基幹領域の中でE2Eで確認すべき機能の洗い出し、次に洗い出した機能の自動化タスクを個人にアサインできる体制づくりを検討しています。そのためにも、オンボーディングやレビュー体制、コーディングルール等の仕組みづくりもセットで考えていきます。</p>
<p>また、<strong>CLINICS に携わる開発の方々は実装しながらテストコードを書く素晴らしい文化</strong>がありますが、MagicPod の自動テストと一部重複して確認している箇所があります。テスト対象は重複しつつも、<strong>テスト粒度の方針を定めて自動テスト全体の最適化</strong>にも取り組んでいく予定です。</p>
<h1 id="おわりに">おわりに</h1>
<p>私が所属する医療プラットフォームの QA チームはいわゆる完全内製型で少数体制です。しかし、MagicPod の実装の容易さ、メンテナンスコストの低さにより少数でも複数のプロダクトを広範囲でカバーできています。組織体制にも大きく貢献できる MagicPod は素晴らしいツールです。この場をお借りして改めて感謝申し上げたいと思います。いつもありがとうございます。</p>
<p>メドレーは、現在 QA エンジニアを募集しています。医療分野における高い品質目標と、いち早くお客様に価値提供するという課題の両立に対して、QA エンジニアだからできる解決策の打ち手を一緒に考えてコミットしていける方と是非お会いしたいです。</p>
<div class="remark-link-card-plus__container">
<a href="https://open.talentio.com/r/1/c/medley/pages/105574" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">open.talentio.com</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=open.talentio.com" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">open.talentio.com</span>
</div>
</div>
</a>
</div>
<section data-footnotes="" class="footnotes"><h2 class="sr-only" id="footnote-label">脚注</h2>
<ol>
<li id="user-content-fn-1">
<p><small>周辺領域とは、主に CLINICS の予約や問診、資格確認といった医療機関と患者との接点となるシステムを指す。社内での呼称。</small> <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p><small>基幹領域とは、主に CLINICS の受付・診察・会計といった業務において、電子カルテとレセコン(レセプトコンピュータの略。医療機関から健康保険組合などの支払い機関に対し、診療報酬を請求するために、レセプトと呼ばれる診療報酬明細書を作成するシステム)とのやりとりを行うシステムを指す。社内での呼称。 </small> <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>
- Next.js と Server-side Rendering をプロダクト環境で3年運用してきた知見と率直な所感https://developer.medley.jp/entry/2024/07/31/120423https://developer.medley.jp/entry/2024/07/31/120423こんにちは、医療プラットフォーム本部・プロダクト開発室・第1開発グループ所属の加藤です。
オンライン診療・オンライン服薬指導アプリ「CLINICS」の開発を担当しています。
今回は CLINICS で採用している Next.js と Ser...Tue, 23 Jul 2024 08:00:00 GMT<p>こんにちは、医療プラットフォーム本部・プロダクト開発室・第1開発グループ所属の加藤です。
<a href="https://clinics-app.com/">オンライン診療・オンライン服薬指導アプリ「CLINICS」</a>の開発を担当しています。</p>
<p>今回は CLINICS で採用している Next.js と Server-side Rendering (SSR) についてお話ししたいと思います。</p>
<p>Next.js は昨今注目を集めている React ベースの Web フレームワークです。
これから Web フロントエンドの開発を始めるにあたって採用を検討している方も多いのではないでしょうか。</p>
<p>Next.js といえば React コンポーネントをサーバー上で実行して HTML を返す SSR に対応しているのが大きな特徴です。
SSR には様々なメリットがある一方で、採用にあたって自分たちのプロダクトに SSR は不要なのではないかといった懸念や Node.js の実行環境の運用コストを気にする声もよく耳にします。</p>
<p>この記事ではメドレーでの Next.js, SSR の採用事例を紹介しつつ、採用にあたって一般に懸念される点を実際に採用してみた上での所感も交えてお話しできればと思っています。</p>
<h1 id="メドレーの医療プラットフォーム事業と-clinics-アプリについて">メドレーの医療プラットフォーム事業と CLINICS アプリについて</h1>
<p>まずはメドレーの医療プラットフォーム事業と CLINICS アプリについて簡単に紹介させてください。</p>
<p>メドレーの医療プラットフォーム事業は、2016年にオンライン診療システムとしてリリースされた<a href="https://clinics-cloud.com/">「CLINICS」</a>を原点としています。
当時は医療機関の方が操作する業務システム部分と患者さん向けの iOS/Android アプリとその Web 版が存在するという構成でした。</p>
<p>その後 CLINICS には電子カルテなどの様々な機能が追加され、現在ではオンライン診療に留まらず医療機関の様々な業務を支援するクラウド診療支援システムへと発展しています。
また、2020年にリリースされたかかりつけ薬局支援システム<a href="https://pharms-cloud.com/">「Pharms」</a>や2022年にリリースされたクラウド歯科支援システム<a href="https://dentis-cloud.com/">「Dentis」</a>もプロダクトのラインナップに加わり、より多くの医療機関の業務を支援するプラットフォームとしてサービスの規模を拡大しています。</p>
<p>医療機関向けの業務システムは医科・歯科・調剤薬局とそれぞれの業務特性に合わせてプロダクトが分かれている一方で、患者さん向けの機能はすべて CLINICS アプリに集約されています。
業務システムの機能拡充に伴って CLINICS アプリの機能も拡充されており、現在ではオンライン服薬指導機能や処方箋の事前送信機能など様々な機能が追加されています。</p>
<p>今回お話しするのはこの CLINICS アプリの Web 版 (以下、CLINICS) のフロントエンド開発についてです。</p>
<h1 id="nextjs-採用のモチベーション">Next.js 採用のモチベーション</h1>
<p>CLINICS アプリはリリース以後続々と機能が拡張されてきたのですが、こうした開発を支えるため 2021年に大規模なリニューアルを実施しました。
特に Web 版はリニューアルのタイミングでフルスクラッチで実装し直す選択をしています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./renewal_before.png","alt":"","index":0}"></p>
<div style="text-align: center; font-style: italic; color: #2E3C4D; margin-bottom: 32px;">
リニューアル以前のトップページ
</div>
<p><img __ASTRO_IMAGE_="{"src":"./renewal_after.png","alt":"","index":0}"></p>
<div style="text-align: center; font-style: italic; color: #2E3C4D; margin-bottom: 32px;">
現在のトップページ
</div>
<p>リニューアル以前の CLINICS はログインの前後で実装が分かれており、この点がフルスクラッチでの実装を選択する大きなモチベーションとなりました。
CLINICS は開発当初から Ruby on Rails で開発されているのですが、患者さん向けのページは Rails のテンプレートエンジンを用いて実装されている部分と SPA として実装されている部分が混在していました。
医療機関の検索や詳細などの検索エンジンからの流入を確保したいページではテンプレートエンジンを用いて HTML を直接レスポンスする、ログインページや予約ページなどの操作性を重視したページでは SPA として実装されている、といった具合です。</p>
<p>こうしたテンプレートエンジンと SPA の併用構成では、ログインの前後で一貫したユーザー体験を提供するのは困難だと感じていました。
例えば、同じ機能がログイン前と後とで2つ存在する箇所もあり、ユーザーから見るとログインを挟んで UI が変わってしまい、あまり良いユーザー体験とは言えない状態でした。
もちろんこういった課題はログイン前後の両方のページの UI を丁寧に設計することで解決が可能ではありますが、異なる技術スタックで同じような機能を実装しつつ UI の一貫性を保つのは容易ではありません。
そういった背景からリニューアルではログイン前後での実装を一本化する方針を立てていました。</p>
<p>実装を一本化する上で SSR に対応している Next.js は非常に魅力的な選択肢でした。
SSR には初期表示のパフォーマンス向上などのメリットもあるのですが、採用にあたっては SEO 面でのメリットを重視していました。
近年では Google のクローラーが JavaScript を実行してくれるのですが、直接 HTML をレスポンスできるのであればその方がより確実だと判断しました。
これが Next.js を採用するに至った最大の理由です。</p>
<p>ちなみに、この当時は App Router がリリースされる前だったため、<a href="https://nextjs.org/docs/pages">Pages Router</a> で開発を開始しました。
以降の内容も基本的に Pages Router を前提としてお話ししていきますが、<a href="https://nextjs.org/docs/app">App Router</a> や他のフレームワークを使用している場合でも SSR についての考え方は共通している部分が多いので少しでも参考にしていただけたら幸いです。</p>
<h1 id="server-side-rendering-の仕組み">Server-side Rendering の仕組み</h1>
<p>Server-side Rendering (SSR) とは、Next.js や Remix などの React ベースのフレームワークが持つ機能のひとつです。
SSR という用語は昨今の React に関する話題の中ではよく耳にする言葉ですが、実は React の公式ドキュメントに SSR とは何かという記述は存在していません。
それでは React 本体にはサーバー環境向けの機能が存在しないのかというとそうではなく、React は<a href="https://ja.react.dev/reference/react-dom/server">コンポーネントから HTML を生成するための API</a> を提供しています。
Next.js や Remix はこうした API を利用してサーバー環境で HTML を生成しつつブラウザ環境では従来の SPA のような振る舞いを実現する機能を提供しており、こうした機能には SSR という名前が付けられています。
例えば、Next.js の Pages Router では <a href="https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props"><code>getServerSideProps</code></a> を使用してリクエスト毎に HTML を生成する機能を SSR と呼んでいます。
各フレームワークによってサーバー側でのデータ取得の方法は異なりますが、React コンポーネントから HTML を生成する仕組みは共通しています。</p>
<p>サーバー上で HTML を生成すると言うとテンプレートエンジンなどを用いる場合と変わらないように感じられるかもしれませんが、SSR には<a href="https://ja.react.dev/reference/react-dom/client/hydrateRoot">ハイドレーション</a>という仕組みが存在するのが大きな特徴です。
生成された HTML はクライアントにレスポンスされてブラウザ上で表示されるのですが、この時点ではイベントハンドラーが DOM にアタッチされておらず、ユーザーの操作に対して反応することができない状態です。
そこで React はブラウザ上で再度コンポーネントをレンダリングしてイベントハンドラーを DOM にアタッチします。
この処理はハイドレーションと呼ばれています。</p>
<p>ハイドレーションが行われるとアプリケーションはユーザー操作に反応することができるようになります。
アプリケーションによってはいわゆる Client-side Rendering (CSR) と呼ばれる useEffect によるデータの取得と追加の描画が行われる場合もあります。
さらに、ハイドレーションが行われた状態からページ遷移が行われる場合には history API が使用されます。</p>
<p>一連の流れをシーケンス図にすると以下のようになります。</p>
<p><img __ASTRO_IMAGE_="{"src":"./ssr_flow.png","alt":"","index":0}"></p>
<p>このように SSR を使用しているアプリケーションでもハイドレーションが行われた後は従来の SPA と同様に振る舞います。
そのため、Context API や Redux などの状態管理ライブラリを使用してページを跨いで状態を共有することも可能です。
こういった特徴から考えると、SSR はこれまでの React と対立するものではなく、むしろその機能を一層強化するものと言えるでしょう。</p>
<h1 id="clinics-での活用事例の紹介">CLINICS での活用事例の紹介</h1>
<p>ここからは実際にどういった形で Next.js と SSR を利用しているのか、CLINICS の活用事例を紹介していきます。</p>
<p>CLINICS のシステム構成は以下のようになっています。
(実際にはもう少し複雑な構成を取っているのですが、今回の説明では簡略化しています)</p>
<p><img __ASTRO_IMAGE_="{"src":"./architecture.png","alt":"","index":0}"></p>
<p>CLINICS の Next.js サーバーは固有のデータベースを持っておらず、必要なデータはすべてバックエンドの API から取得しています。
SSR の際には Next.js サーバーから、CSR の際にはブラウザから API にリクエストが送信されます。</p>
<p>セッション情報については API サーバー側で管理しており、Next.js サーバー側にはセッション情報を共有しない方針を取っています。
SEO 面でのメリットを重視していたため、SSR するのはログイン不要で閲覧可能なページのみで十分だろうと判断しました。
そのため、SSR をする際にはログイン不要で取得できる情報のみを表示し、ログインが必要な情報を表示する際には CSR を使用しています。
SSR を使用しているページでも部分的に CSR を組み合わせて使用しており、ページによっては SSR を用いずに全体が CSR で実装されているケースもあります。
感覚的には従来の SPA をベースとして部分的に SSR を組み合わせていると言っても良いかもしれません。</p>
<h1 id="採用して良かったと感じている点">採用して良かったと感じている点</h1>
<p>ここからは実際に Next.js を使ってみた所感を交えてお話ししていこうと思います。</p>
<p>まずは、Next.js を採用して良かったと感じている点についてです。</p>
<h2 id="全体的な開発体験">全体的な開発体験</h2>
<p>フルスクラッチで実装し直す上で Next.js は良い選択だったと感じています。
Next.js というと SSR のイメージが強いかもしれませんが、SSR を抜きにしても SPA の開発に必要な要素は一通り備わっています。
Webpack やトランスパイラなどのビルドツールの設定がデフォルトで含まれており、React のプロジェクトを新規に立ち上げる際には非常に便利なフレームワークだと感じています。
CLINICS では CSS 関連で追加のライブラリを使用しているのを除けば、ほとんどの実装を Next.js と React の標準機能だけで開発しています。</p>
<h2 id="seo-面での扱いやすさ">SEO 面での扱いやすさ</h2>
<p>こちらは Next.js を採用するにあたって重視していた点なのですが、やはり SSR でサーバーから直接レスポンスを返せるという点で SEO 面では扱いやすいと感じています。
title タグや meta タグなどももちろんですが、HTTP ステータスコードも制御できるため、クローラーに意図を伝える伝統的な方法がそのまま使える点は非常に便利です。
SEO とは直接関係ありませんが、SNS でリンクがシェアされる際の OGP 用の meta タグを動的に生成したいといったケースへの対応も容易です。</p>
<p>とはいえ、近年は Google のクローラーも JavaScript を実行してくれるので SPA が必ずしも SEO 的に不利というわけではありません。
クローラーが JavaScript を理解する能力は年々向上しているので、適切に設計された SPA であれば SEO 的に問題ないというケースも多いようです。
こうしたクローラーの進歩や実装上のテクニックも踏まえると SSR の SEO 面でのメリットはあくまでその扱いやすさという観点で考えると良いかもしれません。</p>
<h2 id="パフォーマンスの向上">パフォーマンスの向上</h2>
<p>SSR を取り入れるにあたってパフォーマンス面について特に意識していたわけではないのですが、実際に SSR を使ってみて <a href="https://web.dev/articles/fcp?hl=ja">FCP</a> (First Contentful Paint) や <a href="https://web.dev/articles/lcp?hl=ja">LCP</a> (Largest Contentful Paint) といった初期表示の速度を表す指標の改善に効果があるなと感じています。
CSR では JS でのデータ取得が完了して始めて LCP 要素が表示されるケースが多いため、JS ファイルのダウンロードや実行の分だけ FCP や LCP が遅延しやすい傾向にあります。
SSR ではこうした JS のダウンロードや実行の時間が減る分、初期表示のパフォーマンス改善が期待できます。
体感的にもローディングインジケーターの表示を挟まずにコンテンツが表示されるため、ユーザーにとってはより快適な体験となるのではないかと感じています。</p>
<p>また、SSR 以外にも Next.js は画像の最適化やコードスプリッティングなどのパフォーマンス向上に役立つ機能を多く提供しているため、安定したパフォーマンスの Web アプリケーションを開発する上では非常に心強い存在です。</p>
<h1 id="セッション管理についての反省点">セッション管理についての反省点</h1>
<p>Next.js そのものについて不便に感じている点はあまりないのですが、SSR を取り入れるにあたってセッション管理についてはよく考えておくべきだったと反省している部分もあります。</p>
<p>従来の SPA であればセッション管理は API サーバーが管理するというのが一般的ですが、SSR を採用する場合では Next.js サーバーと API サーバーが存在するという構成になるケースが多いかと思います。
この場合、Next.js サーバーと API サーバーのどちらでセッションを管理するのか、セッション情報を共有するのか、共有するのであればどのような方法で共有するのかといった点で複数の選択肢が考えられます。
CLINICS では前述の通り API サーバーでセッションを管理して Next.js サーバーとの間でセッション情報を共有しない構成を採用しています。</p>
<p>こうした構成の都合で Next.js サーバーでセッション情報を扱うことができないため、ログイン済みのユーザー固有の情報は SSR することができず、CSR で取得して描画する必要があります。
このようにして SSR と CSR を組み合わせた場合、CSR で取得したデータを描画する際にレイアウトシフトが発生しやすくなります。
また、ログイン済みのユーザーに対しては検索結果をカスタマイズしたり並び替えたりといった要件への対応も難しくなってしまいます。
こういった面で SSR と CSR を組み合わせざるを得ない現状の認証構成には課題があると感じています。</p>
<p>こうした課題を解消するためにも、今後はセッション情報を Next.js サーバーと共有する構成へと変更し、CSR している部分を SSR する形に変更していきたいと考えています。</p>
<p>このように SSR を活用する場合には従来の SPA よりもセッション管理の選択肢が増えるため、セッション管理については特に慎重に考える必要があると感じています。
SSR を今は必要としていないというケースでも、将来的な導入に備えてセッション管理について少し考えを巡らせておくと良いかもしれません。</p>
<h1 id="nextjs-の採用を検討している方へ">Next.js の採用を検討している方へ</h1>
<p>最後に、これから Next.js を採用しようとしている方が気になっているのではないかと思われるポイントについてお話ししていこうと思います。</p>
<p>Next.js を採用するにあたって最も気になるポイントはやはり Node.js の実行環境の運用コストではないでしょうか。
Next.js では一部の例外を除けば SSR を使用するか否かに関わらず実行には Node.js サーバーが必要となります。
SSR は必要としていないが Next.js の様々な恩恵を受けたいという開発者にとっては Node.js サーバーの運用コストが気になるところかと思います。</p>
<p>CLINICS では AWS ECS/Fargate で Next.js サーバーを運用しているのですが、Node.js の実行環境を運用するコストは確かに発生しているものの、現状ではそれほど大きな負担には感じていません。
API サーバーが別で存在する構成では Next.js サーバーが担うのはルーティングに従って API サーバーからデータを取得して SSR するといった比較的シンプルな役割になります。
そのため、データベースを扱うような本格的なバックエンドに比べると運用の手間は低く抑えられている印象です。</p>
<p>どうしても Node.js の実行環境を持たずに運用したいという場合には <a href="https://nextjs.org/docs/app/building-your-application/deploying/static-exports">Static Exports</a> というビルド時に HTML を含む静的アセットを出力する機能の利用も検討してみると良いかもしれません。
ただし、機能面でいくつかの制約があるので、採用を検討する際にはよく確認しておくことをお勧めします。
どちらかと言えば Static Exports は静的なサイトを構築するのに向いている機能であり、動的なコンテンツを扱うようなアプリケーションにはあまり向いていないというのが正直な感想です。
従来の SPA に多く見られるようなビルド済みの JavaScript, CSS などの静的アセットを S3 などのストレージに配置して HTML はバックエンドのサーバーから配信するといった構成が想定されていない点も注意が必要かと思います。</p>
<p>また、Next.js の採用にあたって App Router に追従すべきかというのも悩ましいポイントです。
App Router は Next.js 13 で導入された新しいルーティングシステムで、Server Components/Actions などの React の新機能もサポートしている点が魅力ですが、リリースから日が浅くまだしばらくは様子を見たいと考えている方も多いのではないでしょうか。</p>
<p>現状 CLINICS ではまだ App Router への移行は考えておらず実際の使用感などをお話しすることはできないのですが、ドキュメントを読む限りでは Pages Router の扱いにくい部分がうまく改善されている印象はあります。
ただ、Pages Router のルーティング機能と完全な互換性がない点が移行を考える上で最大のネックだと感じています。
こういった移行のコストや Next.js 自体が App Router への移行を推奨していることを踏まえると、今から新規に開発を始めるのであれば App Router で開発を始めてしまった方が無難な選択かもしれません。</p>
<p>総じて、Node.js サーバーの運用コストが許容できるか、App Router や Server Components/Actions に魅力を感じるかといった点が Next.js の採用を検討する上でのポイントになるのではないでしょうか。</p>
<h1 id="まとめ">まとめ</h1>
<p>CLINICS では Next.js を採用しており、SSR と CSR を併用する形で比較的ライトに SSR を取り入れてフロントエンドの開発を進めています。
当初は SEO 面でのメリットを重視していましたが、実際に使ってみるとパフォーマンスの向上や開発体験の向上など様々なメリットを感じています。
セッション管理については課題を抱えている部分もありますが、今後はそういった部分を改善しより一層 SSR の恩恵を受けられるようにしていきたいと考えています。</p>
<p>Next.js の他にも <a href="https://remix.run/blog/merging-remix-and-react-router">Remix と React Router の統合</a>が発表されるなど、さらに SSR を活用しやすい環境が整いつつあるという点でも、SSR の今後は非常に楽しみな部分が多いと感じています。</p>
- 大規模プロジェクトの課題を解消する、たった1時間で行うふりかえりの工夫https://developer.medley.jp/entry/2024/07/02/185622https://developer.medley.jp/entry/2024/07/02/185622
はじめに
こんにちは。CLINICS カルテの QA 担当をしております QA エンジニアの かみむら です。
医療プラットフォーム本部 CLINICS 開発チームでは、2年以上に渡り自社レセコン1の開発を行っています。プロダクトは公開済...Tue, 02 Jul 2024 09:56:22 GMT<!-- Edit here! -->
<h1 id="はじめに">はじめに</h1>
<p>こんにちは。<a href="https://clinics.medley.life/karte">CLINICS カルテ</a>の QA 担当をしております QA エンジニアの かみむら です。<br>
医療プラットフォーム本部 CLINICS 開発チームでは、2年以上に渡り自社レセコン<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref="" aria-describedby="footnote-label">1</a></sup>の開発を行っています。プロダクトは公開済みであるものの鋭意追加機能の開発を続けており、今後も継続して開発する予定になっています。</p>
<p>QA エンジニアの大切な役割の1つとして、プロセス改善があります。ふりかえりはプロセス改善のアイデアを関係者全員で話し合うための肝となるアクティビティですので、規模の大小問わず取り入れたいものです。<br>
この記事ではレセコン開発におけるプロジェクト体制構築時の黎明期から現在の成熟期に至るまでに行った、四半期毎のふりかえり手法や効果について、かいつまんでご紹介します。</p>
<h1 id="プロジェクトの状況とふりかえりの進め方">プロジェクトの状況とふりかえりの進め方</h1>
<p>まずレセコン開発プロジェクトのチーム体制についてご説明します。メンバーは、初期から現在まで総勢20〜25名前後(内訳:ディレクター3〜5名、カスタマーサクセス2〜4名、エンジニア10数名、QA エンジニア1〜2名)で取り組んでいます。<br>
※開発詳細については、<a href="https://developer.medley.jp/entry/2023/07/31/120555">Tech Studio MATSUE オフィスのご紹介</a>からご覧いただけます!</p>
<p>元々医療プラットフォーム本部では四半期に一度、個々のプロジェクトの垣根なくプロダクトチーム全体でのふりかえりを KPT( Keep / Problem / Try )で行うという習慣があります。そこで、レセコン開発のプロジェクトも時期を合わせてふりかえりを行うようにすればメンバーの気持ちと時間の調整がしやすいのではないか? と考え、プロジェクトマネージャーに相談しながら各四半期の終わり頃に開催することとしました。</p>
<p>当プロジェクトには専任メンバーもいますが、多くのメンバーは他のプロジェクトとの兼務のためふりかえりへの時間の使い方と開催形式には気をつかいました。<br>
特に開発メンバーの半数近くが別拠点(松江オフィス)に勤務していることもあり、東京オフィスメンバーとのコミュニケーションのため完全オンライン形式にする必要がありました(同じ拠点同士は対面で集まり、拠点間のみオンライン接続するというやり方もありますが、経験上個々の発言が拾いにくい等負の側面があることをわかっていたため、全員オンラインでの参加としました)。</p>
<p>具体的には次のように進めています。</p>
<ol>
<li>”今期の課題感” についてプロジェクトマネージャーとすり合わせ、その課題に合いそうなふりかえり手法を選定する(参加者の対象をどこまで広げるか、数名ずつのグループに分ける場合はどういう基準で分けるか等も相談)</li>
<li>候補に挙げた手法とタイムテーブルを <a href="https://www.atlassian.com/ja/software/confluence">Confluence</a> に書き起こし、前日までに Slack で事前周知</li>
<li>Google Meet でオンライン開催する</li>
<li>ふりかえり結果の要約 / 抜粋 / アクションプランを Confluence に追記し、参加者に Slack で報告</li>
</ol>
<p>毎回以下の形式を守って執り行いました。</p>
<ul>
<li>完全オンライン形式で開催</li>
<li>参加者の事前準備はほぼ無し</li>
<li>ふりかえりイベント当日にかける時間は1時間だけ</li>
</ul>
<p>一般的には四半期という長期間を対象とするふりかえりの場合、半日くらいかけて開催してもおかしくないと思うのですが、対象者全員が空いている時間帯を確保するのもなかなか難しく「1時間以内でやり切り最大の効果をあげること」を自分に課していました。</p>
<h1 id="具体例-象死んだ魚嘔吐">具体例① 「象、死んだ魚、嘔吐」</h1>
<p>開催時期:初期(2022年6月)<br>
参加メンバー:12名</p>
<p>まだ「ふりかえりをこうしていこう!」とも決めていなかった頃の、最初に行ったふりかえりは「<a href="https://no-kill-switch.ghost.io/elephants-dead-fish-vomit/">象、死んだ魚、嘔吐</a>」という手法を採用しました。<br>
Airbnb 創業者が考案したコミュニケーション改善の手法で、以下の3要素から構成されています。</p>
<ol>
<li>「象」…存在しないかのように扱われるが、誰もが知っている事実。</li>
<li>「死んだ魚」…過去に起こったこと(悪臭と腐敗)を乗り越えて、将来への期待を設定する。</li>
<li>「嘔吐」…普段は言わないこと、言うのを控えていることなどをぶっちゃけて話す。</li>
</ol>
<p>最初期のためチーム全体としても遠慮し合うような雰囲気がまだうっすらとあり、松江と東京の物理的な距離感も相まって「もう全員でぶっちゃけて話そうよ!」という思いでこの手法を選びました(個人的にも気になっていた手法でした)。
少し前に行ったプロダクトチーム全体のふりかえりから、現在のチームは「良い面も悪い面もコミュニケーションのあり方がキーポイントになっていそう」という推測が出ていたことも背景にありました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./dev_20240702_01.png","alt":"elephants-dead-fish-vomit_image","index":0}">
<em>「象」「死んだ魚」「嘔吐」それぞれでボードを使用</em></p>
<p>「四半期毎の開催」と述べましたが、実はこの最初のチーム全員のふりかえりまではプロジェクト開始から半年経過しています。そのためレセコン開発プロジェクト全体としては初回のふりかえりということもあり、「そもそも何のためにふりかえりを行うのか」「このプロジェクトの目的(共通目標)は何か」という部分は特に丁寧に対話する時間をとりました。<br>
ツールは Jamboard を使用し、心理的安全性を高めるためクローズドな状態にして「本当に何でも言える」環境をつくることに腐心しました。<br>
結果、1時間のうち集中して話ができたのは30分程度ですが、かなりいろんなぶっちゃけ話ができたと思います。もちろんこのふりかえりだけが要因ではないですが、その後のプロジェクトメンバー内のコミュニケーションは徐々に滑らかになっていってるな、チームビルディングという意味でも効果があったかなと感じました。</p>
<h1 id="具体例-ポジティブふりかえりマッピング">具体例② 「ポジティブふりかえりマッピング」</h1>
<p>開催時期:初回リリース後(2023年3月)<br>
参加メンバー:25名</p>
<p>初回リリース後の運用期間を経て次の大型リリースの準備を進めていく中で、関わる人数やロールも一段と増えた時期でした。これまではふりかえりに参加していなかったカスタマーサクセス等のメンバーも招待し、次の期へのキックオフを兼ねて明るい雰囲気で未来へつながっていくお話をしたく、ポジティブふりかえりマッピングを使うことにしました。<br>
この手法は、はじめに「どんな道をたどったにせよ、当時の知識・技術・能力・利用可能なリソース・状況の中で、みんなができる限り最高の仕事をしたはずです。それを心から信じます。」という声明を発表し、「あなたは⚪︎⚪︎プロジェクトにいましたね。どういう点が素晴らしかったですか?」と質問を続けるという作法で進みます。メンバーが次々と素晴らしい点を挙げていくので、みんなが嬉しく和やかな雰囲気になります。</p>
<p><img __ASTRO_IMAGE_="{"src":"./dev_20240702_02.png","alt":"ポジティブふりかえりマッピングの進め方","index":0}">
<em>当時のConfluence</em></p>
<p>参加メンバーも大人数になってきたので、5名ずつのグループをあらかじめつくっておき、グループ毎の Jamboard へ意見を記載してもらいました。持ち時間は10分です。時間短縮のためブレイクアウトルームはつくらず、25名全員がメインルームに参加したままの状態で各自ふせんをつくっていきます。<br>
キックオフとして有用な場になるよう、この回のグループ編成は以下の点に配慮しました。</p>
<ul>
<li>同じグループ内でなるべく職種が偏らないように</li>
<li>年齢(経験年数)も偏らないように</li>
<li>とはいえまったく業務上の接点もないということがないように</li>
</ul>
<p>次に各グループ毎に挙がった内容が近いふせんを寄せたり、ペンで囲んだりといったグルーピングを5分で行います。<br>
最後に各グループの結果を全員で見ていき要約する時間を15分とります。<br>
メンバーからは「プロジェクトの推進における各自の機動力、組織力が素晴らしい」「効率化のための仕組みが効いて次期稼働可能な状態までもっていけた」等の意見が挙がりました。<br>
素晴らしい点をお互いに見つけて言語化することによって労いや協力の気持ちが思い出され、ふりかえりとしてもキックオフとしても良いイベントになったかなと思います。</p>
<p><img __ASTRO_IMAGE_="{"src":"./dev_20240702_03.png","alt":"ポジティブふりかえりマッピング","index":0}">
<em>とあるグループの最終形態</em></p>
<h1 id="具体例-温度計">具体例③ 「温度計」</h1>
<p>開催時期:成長期(2023年12月)<br>
参加メンバー:19名</p>
<p>自社レセコンが稼働する医療機関数の更なる増加と診療報酬改定<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref="" aria-describedby="footnote-label">2</a></sup>に向けて、持続的なプロダクト開発とオペレーション改善のための話し合いを行いたい時期でした。<br>
このふりかえりでは、以下の3点を重要課題と位置づけました。</p>
<ul>
<li>良いところを伸ばす</li>
<li>問題を解消する</li>
<li>関係の質を高める</li>
</ul>
<p>この3点の目的にマッチする「温度計」という手法は、チームメンバーが「チームに起きていること」「チームに望むこと」を報告し合います。現在位置から今後に視点を向けることを意識するため、以下の3ステップで進めました。</p>
<ol>
<li>感謝・興奮</li>
<li>気づき・興味</li>
<li>提案・希望・願望</li>
</ol>
<p>「温度計」という名の通り、中央に温度計のイラストを配置します。本来は縦長の用紙下部に低温(よくないこと)、上部にいくほど高温(興奮や願望)のふせんを貼る、というルールになっていますが、オンライン画面上ではスクロールが必要な状況を避けたかったため、各ステップのふせんの色を変えることにより(青→黄色→ピンク)温度感を表現することにしました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./dev_20240702_04.png","alt":"温度計","index":0}">
<em>「感謝・興奮」は全員で1つのボードを使用し、ありがとうの気持ちが溢れました</em></p>
<p>「大変だけど楽しい期だった」「チーム感があった」「インフラチームのおかげで開発・不具合対応に集中できた」等の意見があがり、初期から比べると本当に良いチームになっているなと感じました。<br>
またこの頃から「⚪︎⚪︎に関する勉強会 / 品評会をしてみたい」という意見も徐々に増えてきており、一部実現したものもありますが未だにやれていないジャンルもあり、熱が冷めないうちに何らかの形で達成することが今後の課題です。</p>
<h1 id="まとめ">まとめ</h1>
<p>ここまで、実際に行ったふりかえり事例を3つご紹介しました。<br>
最後に、どのふりかえりでも共通して行っていたことを踏まえてまとめます。</p>
<ul>
<li>ふりかえりをする意義を再認識し、場をつくる</li>
<li>前回のふりかえりをふりかえる</li>
<li>他のメンバーが挙げた意見に賛成や近い意見がある場合は、ドット投票を行う</li>
</ul>
<p>以下に詳しくご説明します。</p>
<h3 id="ふりかえりをする意義を再認識し場をつくる">ふりかえりをする意義を再認識し、場をつくる</h3>
<p>メンバー全員が「ふりかえり」に慣れているわけではなく、経験が少ないメンバーもいることから、「なぜふりかえりをするのか?」という意義については毎回冒頭で触れて、気持ちをつくっていくということをしていました。毎回別の言葉で、「たいへんな時ほど立ち止まって周りをみることが、これからも一緒に走っていく仲間として大切」のようなお話をしました。<br>
会の前半は説明のため私が1人で話している時間も長めで、オンラインでは参加者の反応がわかりづらくちょっとした無音の時間が続いてしまいがちです。特別にアイスブレイクの時間をとっていないこともあり自然に場を盛り上げていくために、参加メンバーには「環境的にミュート解除しても問題ない人はなるべく声出してください〜」「スタンプくださーい」等と促してわいわいしてもらいました。</p>
<h3 id="前回のふりかえりをふりかえる">前回のふりかえりをふりかえる</h3>
<p>四半期毎にふりかえりをする、というリズムもできたので、毎回冒頭で「前回はこういうふりかえりをしましたね」という「ふりかえりのふりかえり」をするようにしています。<br>
あまり時間もとれないのでさらっと触れる程度ですが、今から行うふりかえりの気持ちをつくる上でも重要なパートかなと思っています。</p>
<h3 id="誰かが挙げた意見に賛成や近い意見がある場合はドット投票を行う">誰かが挙げた意見に賛成や近い意見がある場合は、ドット投票を行う</h3>
<p>「ドット投票」もふりかえりの手法の1つですが、あらかじめマークとなるアイコンを準備しておき、それを各自コピーして共感した他の人のふせんの近くに貼ってもらいました。<br>
これによりみんなの関心がある課題が明確になって時間も短縮でき、取り上げる議題に集中できます。</p>
<p><img __ASTRO_IMAGE_="{"src":"./dev_20240702_05.png","alt":"ドット投票の例","index":0}">
<em>ドット投票の例</em></p>
<h2 id="ふりかえり手法の選定について">ふりかえり手法の選定について</h2>
<p>さて、私がなぜ毎回課題感に合わせて最適な(と思われる)ふりかえり手法を使うことができたのか、それはふりかえり& Miro エバンジェリストである<a href="https://x.com/viva_tweet_x">びばさん</a>の『<a href="https://booth.pm/ja/items/1711909">ふりかえりチートシート</a>』にかなりお世話になっているためです。<br>
このシートには各ふりかえり手法がどの目的(解消したい課題)に適しているか詳細にマッピングされているので、「今回のふりかえりで解決 / 達成したいこと」を2〜3つ選定し、その中から合いそうな手法をピックアップして詳しく調べてから1時間でやり切れる形式に落とし込んでいきました。<br>
また、課題によっては1つの手法に収めず、複数の手法を組み合わせて使うことも多いです。前述の「ドット投票」もそうですが、軽量な手法である「感謝」(自分が感謝すべき人たちのことを考え、精一杯の誠意を込めて「ありがとう」と言う)は他の手法と組み合わせてアイスブレイク的に使ったりもしました。</p>
<p>このように、毎回異なるふりかえり手法を取り入れることのできる柔軟な組織で一緒に働いてみたい! と思った方はお気軽に<a href="https://www.medley.jp/jobs/">カジュアル面談</a>をお申し込みください。<br>
良いふりかえり事例を持っている、あるいはふりかえりの仕方に悩んでいる等とにかく語りたい方も一度お話ししてみましょう!<br>
最後までお読みいただき、ありがとうございました。</p>
<h3 id="referencethanksふりかえりの参考図書">Reference&Thanks(ふりかえりの参考図書)</h3>
<ul>
<li><a href="https://booth.pm/ja/items/1076615">ふりかえり読本 場作り編~ふりかえるその前に~</a></li>
<li><a href="https://www.amazon.co.jp/Fearless-Change-%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%81%AB%E5%8A%B9%E3%81%8F-%E3%82%A2%E3%82%A4%E3%83%87%E3%82%A2%E3%82%92%E7%B5%84%E7%B9%94%E3%81%AB%E5%BA%83%E3%82%81%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE48%E3%81%AE%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3-Manns/dp/462108786X">Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン</a></li>
<li><a href="https://shop.ohmsha.co.jp/shopdetail/000000001770/">アジャイルレトロスペクティブズ 強いチームを育てる「ふりかえり」の手引き</a></li>
</ul>
<section data-footnotes="" class="footnotes"><h2 class="sr-only" id="footnote-label">脚注</h2>
<ol>
<li id="user-content-fn-1">
<p>レセプトコンピュータ。レセプト(診療報酬明細書)を作成するためのコンピュータ。<br>
<a href="https://clinics-cloud.com/column/64">https://clinics-cloud.com/column/64</a> <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>通常2年毎に行われる、医療行為に対する点数の見直し。<br>
<a href="https://clinics-cloud.com/column/406">https://clinics-cloud.com/column/406</a> <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>
- RubyKaigi 2024 に Ruby Sponsor として協賛しました!https://developer.medley.jp/entry/2024/05/24/182914https://developer.medley.jp/entry/2024/05/24/182914こんにちは。人材プラットフォーム本部プロダクト開発室 第五開発グループ所属の寺内です。
新卒からメドレーに入社して今年で 4 年目のエンジニアで、老人ホーム・介護施設の検索サイトである 介護のほんねの開発を担当しています。
メドレーは 5 ...Fri, 24 May 2024 09:29:14 GMT<p>こんにちは。人材プラットフォーム本部プロダクト開発室 第五開発グループ所属の寺内です。
新卒からメドレーに入社して今年で 4 年目のエンジニアで、老人ホーム・介護施設の検索サイトである <a href="https://kaigonohonne.com/">介護のほんね</a>の開発を担当しています。</p>
<p>メドレーは 5 月 15 日から 17 日に 沖縄県那覇市の<a href="https://www.nahart.jp/">那覇文化芸術劇場なはーと</a> にて開催された <a href="https://rubykaigi.org/2024/">RubyKaigi 2024</a>に Ruby Sponsor として協賛しました!
RubyKaigi 2024 は Ruby をテーマとした国際的なカンファレンスで、世界中から様々な Ruby エンジニアが集う大規模なイベントです。</p>
<p>エンジニアとエンジニア採用担当の計 12 名が現地参加し、たくさんの方々と交流させて頂きました。また、スポンサー LT もさせていただきました。
今回は会場・ブース・発表の様子と、スポンサー LT の内容をご紹介します。</p>
<h1 id="会場の様子">会場の様子</h1>
<p>会場となった那覇文化芸術劇場なはーとは、国際通りからほど近い場所でありながら閑静な市街地にありました。
<img __ASTRO_IMAGE_="{"src":"./IMG_5169.jpg","alt":"","index":0}">
<em>沖縄らしさを感じさせる建築デザイン</em></p>
<p>建物内は 4 階まで吹き抜けとなっていて開放感が溢れている会場でしたが、セッション間はこのように各社のブースを訪問する人々でいっぱいになり、RubyKaigi の盛況ぶりを感じることができました。
なんと今年の来場者数は約 1400 人だったそうです!
<img __ASTRO_IMAGE_="{"src":"./IMG_5151_mosaic.jpg","alt":"","index":0}"></p>
<p>RubyKaigi 2024 の公式配布グッズは、例年とは異なり T シャツではなくかりゆしとビーチサンダルでした!運営の粋な計らいのおかげで、会期中会場がカラフルでしたね。
<img __ASTRO_IMAGE_="{"src":"./IMG_8876.jpg","alt":"","index":0}"></p>
<p>2 日目から始まったスタンプラリーでは来場者のブース訪問がより活発になったのを感じました。ブースを巡ってスタンプを集めると限定バッジがもらえました。
<img __ASTRO_IMAGE_="{"src":"./IMG_5342.jpg","alt":"","index":0}">
<em>バッジ交換時にスタッフさんが全力で拍手をしてくださるのでとても幸せな気持ちになりました!</em></p>
<h1 id="弊社ブースの様子">弊社ブースの様子</h1>
<p>メドレーブースではうちわやビーチサンダルなどの沖縄らしいものをはじめ、医療事業を手がける企業のアピールとして絆創膏をノベルティとしてご用意しました。
<img __ASTRO_IMAGE_="{"src":"./IMG_5127.jpg","alt":"","index":0}">
<em>靴擦れしてしまった方に絆創膏をお渡ししたらとても喜んでいただきました</em></p>
<p>アンケートパネルを用いて参加者の方が抱えている医療体験のお悩みを伺いました。
伺ったお悩みからわかる課題に対してメドレーがプロダクトを通じてどのように向き合い・解決しているのかをご紹介しました。
<img __ASTRO_IMAGE_="{"src":"./IMG_5381_mosaic.jpg","alt":"","index":0}"></p>
<p>特に回答が多かったのが <strong>「待ち時間が」長い</strong> という課題でした。
メドレーの提供するオンライン診療・服薬指導アプリ「<a href="https://clinics-cloud.com/">CLINICS</a>」では病院や調剤薬局の予約、事前の問診票入力などが可能です。オンラインによる診療・服薬指導(薬剤師によるお薬の説明)を予約した場合は待ち時間だけではなく移動時間も削減されることや、直近では Uber Eats との連携で服薬指導後30分程度で自宅までお薬を届けてもらうことも可能になったことをご説明すると、「ここまで便利になっていることは知らなかった!」と驚きのお声を多数いただきました。
<img __ASTRO_IMAGE_="{"src":"./IMG_4018_2.jpg","alt":"","index":0}">
<em>会場の那覇付近でもこれだけの医療機関が見つかりました</em></p>
<p>また、直接的ではありませんが、待ち時間の長さには人員不足や院内オペレーション、利用システムも影響していることがあります。それらをジョブメドレーや医療機関向けのシステム(<a href="https://clinics-cloud.com/">CLINICS</a>、<a href="https://pcmed.jp/product-mall/">MALL</a>、<a href="https://pharms-cloud.com/">Pharms</a>、<a href="https://dentis-cloud.com/">Dentis</a>)を通じてサポートしていること、一つの課題に対して様々なアプローチをとっていることなどをお話ししました。
<img __ASTRO_IMAGE_="{"src":"./IMG_5318.jpg","alt":"","index":0}">
<em>ツアー風 T シャツには今年メドレーがスポンサードしたイベントの一覧が掲載されています</em></p>
<p>メドレーブースにお越しいただいた皆様、ありがとうございました!
<img __ASTRO_IMAGE_="{"src":"./IMG_3804.jpg","alt":"","index":0}">
<em>参加したメドレーメンバー全員で</em></p>
<h1 id="発表の様子">発表の様子</h1>
<p>3 日間にわたってさまざまなセッションがありましたが、私が気になった以下のセッションについて少しご紹介します。</p>
<ul>
<li>Keynote</li>
<li>DAY1 rubykaigiB 「Long journey of Ruby standard library」</li>
<li>DAY2 rubykaigiB 「Let’s use LLMs from Ruby 〜 Refine RBS types using LLM 〜」</li>
<li>Matz Keynote</li>
</ul>
<h2 id="keynote">Keynote</h2>
<p>今年の Keynote は tomoya ishida さんによる「Writing Weird Code」という発表でした。
Quine というコードと実行結果が同じ内容になるプログラムに関する発表で、Rubyの表現力と楽しさを紹介するものでした。
中でも発表中に行われた一人 TRICK(Transcendental Ruby Imbroglio Contest for RubyKaigi)では、Most 〇〇 と名付けられたグラフィカルなコードが紹介され、会場は大盛りあがりでした!
紹介されていたものは GitHub にて<a href="https://github.com/tompng/selftrick2024">ソースコード</a>が公開されています。</p>
<iframe frameborder="0" src="https://drive.google.com/file/d/1Dkx15u_5UAGoFqJHCeAuj2FXS-z_U7EE/preview" title="Writing Weird Code" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 420;" data-ratio="1.3333333333333333"></iframe>
<p>引用元:<a href="https://drive.google.com/file/d/1Dkx15u_5UAGoFqJHCeAuj2FXS-z_U7EE/preview">drive.google.com</a></p>
<h2 id="day1-rubykaigib-long-journey-of-ruby-standard-library">DAY1 rubykaigiB 「Long journey of Ruby standard library」</h2>
<p>Hiroshi SHIBATA さんによる、Ruby の標準ライブラリとは何なのかと、その歴史についての発表でした。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/andpad/long-journey-of-ruby-standard-library-rubykaigi-2024" title="Long journey of Ruby standard library" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 420;" data-ratio="1.3333333333333333"></iframe>
<p>引用元:<a href="https://speakerdeck.com/andpad/long-journey-of-ruby-standard-library-rubykaigi-2024">speakerdeck.com</a></p>
<h3 id="組み込みクラスと標準ライブラリの違い">組み込みクラスと標準ライブラリの違い</h3>
<p>組み込みクラスとは <code>require</code> しなくても使えるもので、標準ライブラリとは <code>require</code> しないと使えないものです。標準ライブラリの中には Ruby だけで書かれたものと C 拡張で書かれたものがあります。</p>
<h3 id="歴史と傾向">歴史と傾向</h3>
<p>Ruby の標準ライブラリの数は Ruby1.8 から減少傾向にあります。1.6 から 1.8 では数が増えたようですが、これはインターネットが常時接続ではなかったため、<code>gem install</code> でインストールするより、Ruby をインストールしただけでたくさんの機能が使えたほうが良いのではという背景のもとに増えたとのことです。</p>
<h3 id="default-gems-と-bundled-gems-という概念の導入">default gems と bundled gems という概念の導入</h3>
<p>default gems にコミットされたものは自動で Ruby 本体にもコミットされ、bundled gems にコミットされたものは Ruby 本体にコミットされないようになったそうです。
default gems や bundled gemsに関する活動をする理由は、セキュリティ対応と Ruby の持続可能性を高めるためとのことでした。
セキュリティ対応については、サプライチェーンアタックを防ぐために gem 同士の依存関係を少なくするようにしているそうです。これにより脆弱性対応時に単体のライブラリをアップデートするだけで済むようになり、結果として工数を削減することができるようになったのは開発者として大変ありがたいことだと思いました。
Ruby 自体の持続可能性については、Ruby にコミットしたい人に適切に権限を付与できるようにし、ライブラリ単体でも開発できる状態を目指すということだそうで、Ruby のこれからを支える人々の動きを促進する素晴らしいものだと感じました。</p>
<h3 id="影響">影響</h3>
<p>default gems が bundled gems になる影響として、bundler の下で使う際に Gemfile に書かないと使えなくなってしまうことが紹介されました。default gems でもバージョン指定する際は Gemfile に書き込む必要があるようです。
これを解決するために、Ruby3.3 では警告機能が実装されました。<code>require "csv"</code>などで実行すると、Ruby3.4 では bundled gems になる旨を伝える警告が出力されるというものです。これを元に Gemfile に情報を追加すれば良いので、対応がわかりやすくなったというメリットがあります。
また、自分が使っていないライブラリの警告だとしても、他のどのライブラリから呼び出されているのかも確認できるそうです。これを確認する際に、使っていないものであれば削除して依存関係を少なくする整理にも役立つそうです。</p>
<h3 id="感想">感想</h3>
<p>引き続き bundled gems の数を増やして default gems の数を減らすことでメンテナンス性を高める方針のようなので、適宜分類をチェックして Gemfile への追加や Gem を使っているかの整理を進めて行きたいです。</p>
<h2 id="day2-rubykaigib-lets-use-llms-from-ruby--refine-rbs-types-using-llm">DAY2 rubykaigiB 「Let’s use LLMs from Ruby 〜 Refine RBS types using LLM 〜」</h2>
<p>Shunsuke Mori(kokuyou)さんによる、Ruby で大型言語モデル(LLM)を使用して RBS 型シグネチャを改良するツールである RBS Goose を作成されたことに関する発表でした。</p>
<iframe frameborder="0" src="https://slides.com/kokuyouwind/lets-use-llms-from-ruby/embed" title="Let's use LLMs from Ruby ~ Refine RBS types using LLM ~" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 420;" data-ratio="1.3333333333333333"></iframe>
<p>引用元:<a href="https://slides.com/kokuyouwind/lets-use-llms-from-ruby/">slides.com</a></p>
<h3 id="rbs-とは--既存の-rbs-作成手法">RBS とは? & 既存の RBS 作成手法</h3>
<p><a href="https://github.com/ruby/rbs">RBS</a>とは、Ruby の型構造を定義するためのもので、型検査による安全な開発や補完の強化など開発体験を向上させる目的で使用されます。
既存の RBS 作成手法としては以下の 3 つが紹介されていました。</p>
<ul>
<li>静的構文解析: 高速であるが推測できるものが少ない。attr_accessor などはメタプログラミングで生成されるものなので静的構文解析では RBS に出力されない。ruby にそもそも型情報がないので untyped になる。</li>
<li>動的ロード: ロード時にクラス全体が読み込まれ、その結果をリフレクションで RBS に書き出すことで、メタプログラミングで生成している attr_accessor のメソッドも定義される。しかし、こちらも型情報がないので untyped になる。</li>
<li>型レベル実行: 実行時の値を渡すときに型の情報を受け渡し、これをとっておいて RBS に出力する。コードを実際に動かす必要がある。
これらの手法では untyped を手作業で直したり、足りないものを補うのが大変であり、これを解決したい動機で RBS Goose を作成されたそうです。</li>
</ul>
<h3 id="rbs-goose-の仕組み">RBS Goose の仕組み</h3>
<p>RBS Goose は RBS を生成する既存手法でネックになっていた untyped を手作業で直したり、足りないものを補う作業を LLM の力を借りて解決する仕組みのようでした。
RBS 生成の流れは以下のようになっています。</p>
<ol>
<li><code>hoge.rb</code> に対して静的構文解析などで <code>hoge.rbs</code> を作成</li>
<li>LLM の入力に使う prompt を組み立て
<ol>
<li>この際に <code>hoge.rb</code> とは全く違う <code>fuga.rb</code> とそれを静的構文解析などで作った <code>fuga.rbs</code> と LLM に出力してほしいお手本の <code>refined_fuga.rbs</code> の 3 つを追加で入れる</li>
<li>お手本を入れることで期待する答えを出しやすくする Few-shot プロンプティング という手法を採用している</li>
</ol>
</li>
<li>LLM に prompt を渡す</li>
<li>RBS を出力</li>
</ol>
<p>実用レベルにするには複数ファイルを扱えるようにする必要があり、それをどうするかの実現ビジョンについても語られていました。</p>
<h3 id="llm-を使った開発の-tips">LLM を使った開発の Tips</h3>
<p>開発時の Tips が共有されていたのですが、その中でも特に学びになったのはテスト時の Tips でした。
従量課金のコストがかかる & 応答時間がかかるのでテストに時間がかかってしまうことを解決するために、<a href="https://github.com/vcr/vcr">VCR</a>のような Web モックを利用すると良いということ、 LLM の再現性確保のために Temperature(言語モデルの出力のランダム性を制御するパラメータのこと)の設定は 0 にすることがおすすめであるという Tips が共有されました。</p>
<h3 id="感想-1">感想</h3>
<p>性能については割愛しますが、RBS Goose を用いてより良い RBS を得られると、補完の強化などで開発体験が向上することが期待できそうで、実用化が待ち遠しいです。
<a href="https://www.youtube.com/watch?v=184Sc0hsnek&list=PLbFmgWm555yYvWS7VGTFipF7ckduvSeT-">去年の Keynote</a>で ChatGPT に型を推論させてもある程度形になることが語られていましたが、今年の発表で LLM を使用して型情報の定義をサポートするツールが発表されたことで、RBS への興味関心が大きくなりました!</p>
<h2 id="matz-keynote">Matz Keynote</h2>
<p>今年の RubyKaigi 最後のセッションは Matz が担当しました。どうしたら Ruby をもっとよくできるかについての話では、パフォーマンス改善がキーになるというお話がありました。
パフォーマンス改善のために必要な要素は多岐にわたるため、これをコミュニティ全体で力を合わせて解決していく必要がある というメッセージや、カンファレンスで人々が話をしたことがきっかけでRubyGems が生まれたことから、コミュニティの人々が一緒に取り組めばもっとRuby を良くすることができるはずである というメッセージを受け、Ruby コミュニティの一員として胸の熱くなる想いでした。
<img __ASTRO_IMAGE_="{"src":"./IMG_3809.jpg","alt":"","index":0}">
<em>メドレーブースに遊びに来てくれた Matz と</em></p>
<h1 id="スポンサー-lt-の様子">スポンサー LT の様子</h1>
<p>メドレーの LT 登壇は Matz の ClosingLT の直前ということもあり、会場が超満員に。登壇した VPoE の山﨑を応援しようと、サプライズで応援うちわを作っていったのですが、残念ながらあの大舞台からは見えなかったようです(笑)
<img __ASTRO_IMAGE_="{"src":"./DSC00342_mosaic.jpg","alt":"","index":0}">
<em>うちわは社内で活用させていただきます</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./IMG_5413.jpg","alt":"","index":0}">
<em>あそこまで大きな会場だと思ってなかった…!by 山﨑</em></p>
<p>LT で発表させていただきましたが、メドレーはコミュニティへの支援を行っています。6 月 13 日に約 1 年半ぶりの開催となる Roppongi.rb をスポンサードしており、メドレーオフィスで開催予定です。ご興味のある方はぜひご参加ください。
<a href="https://roppongirb.connpass.com/event/319768/">https://roppongirb.connpass.com/event/319768/</a></p>
<p>私も地域 Ruby コミュニティに積極的に参加しようと思いますので、Ruby コミュニティを一緒に盛り上げていきましょう!</p>
<h1 id="さいごに">さいごに</h1>
<p>RubyKaigi 2024 に Ruby Sponsor として協賛した様子をお届けしました。
ブースやパーティーを通じて交流させていただいた皆様、Ruby とそのコミュニティの素晴らしさを存分に体感できるイベントを運営してくださった皆様に心より感謝いたします。</p>
<p>メドレーでは領域を問わず、Ruby を積極的に活用して医療ヘルスケアの未来をつくるプロダクトを開発しています。
ご興味を持っていただいた方からのご連絡をお待ちしております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- TSKaigi 2024のスポンサーLTでTypeScriptコード改善の取り組みについて紹介しましたhttps://developer.medley.jp/entry/2024/05/13/tskaigihttps://developer.medley.jp/entry/2024/05/13/tskaigiこんにちは。医療プラットフォーム本部プロダクト開発室 CLINICS 第二開発グループ所属の髙橋です。
メドレーは 5 月 11 日に中野セントラルパークカンファレンスにて開催された TSKaigi 2024 に Gold Sponsor ...Mon, 13 May 2024 14:41:06 GMT<p>こんにちは。医療プラットフォーム本部プロダクト開発室 CLINICS 第二開発グループ所属の髙橋です。</p>
<p>メドレーは 5 月 11 日に中野セントラルパークカンファレンスにて開催された <a href="https://tskaigi.org/">TSKaigi 2024</a> に Gold Sponsor として協賛しました!
TSKaigi は、今年から開催された日本最大級の TypeScript をテーマとした技術カンファレンスです。</p>
<p>エンジニアの徳永と吉岡、髙橋の計 3 名が現地参加し、たくさんの方々と交流させて頂きました。
今回はスポンサー LT とブースの様子をご紹介します。</p>
<h1 id="スポンサー-lt">スポンサー LT</h1>
<p>スポンサー LT セッションでは、「チームで挑む TypeScript コードの漸進的改善」というテーマで髙橋が発表しました。</p>
<p>発表では、<a href="https://clinics-cloud.com/">クラウド診療支援システム CLINICS</a> の Web フロントエンドのコードを「typescript-eslint のルール読み合わせ会」を通じて改善している取り組みについて紹介しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./sponsor-lt.jpg","alt":"スポンサーLTでの発表の様子","index":0}"></p>
<p>詳細は次の発表資料にまとめていますので、是非ご覧ください。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/88aaed77681345e2b1f20492747ddb3f" title="TypeScriptコードの漸進的改善 / Progressive Improvement of TypeScript Code" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 420;" data-ratio="1.3333333333333333"></iframe>
<h1 id="ブースの様子">ブースの様子</h1>
<p>ブースでは、ノベルティの絆創膏、うちわ、パンフレットなどを配布しながら、メドレーのプロダクトや開発文化を紹介しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./booth.png","alt":"ブースの様子","index":0}"></p>
<p>また、医療 UX の「ここなんとかならない?」をテーマに、参加者の方々が抱える医療体験のお悩みをパネル投票する企画を実施しました。
その結果、待ち時間の長さ、問診票の非効率性、予約に関連する 3 つのお悩みが特に多くの投票を集めました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./booth-panel.png","alt":"パネル投票の結果","index":0}"></p>
<p>メドレーが提供する<a href="https://clinics-app.com/">オンライン診療・服薬指導アプリ CLINICS</a> では、インターネットを通じて、自宅や職場から診察、服薬指導を受けることができるので、これらのお悩みを解決することができます!
機会があれば是非ご活用ください。</p>
<h1 id="さいごに">さいごに</h1>
<p>TSKaigi に協賛した様子をお届けしました。</p>
<p>LT セッションやブースにお越しくださった皆様、ありがとうございました!
そして何よりも、素晴らしいカンファレンスを企画・開催して頂いた運営スタッフの皆様、本当にありがとうございました!</p>
<p>メドレーでは領域を問わず、TypeScript を積極的に活用して医療ヘルスケアの未来をつくるプロダクトを開発しています。
ご興味がある方からのご連絡をお待ちしております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- FY22 新卒入社エンジニアはこの 1 年でどのような成長をしてきたのかインタビューしましたhttps://developer.medley.jp/entry/2024/04/05/234106https://developer.medley.jp/entry/2024/04/05/234106はじめに
みなさん、こんにちは。エンジニアの新居です。今回は 2022 年新卒入社したエンジニア達にインタビューした記事をお送りします。
この 1 年で彼らがどのような業務を経験してきたのかや、リアルな現在の立ち位置などをお伝えしたいと考え...Fri, 05 Apr 2024 14:41:06 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。エンジニアの新居です。今回は 2022 年新卒入社したエンジニア達にインタビューした記事をお送りします。</p>
<p>この 1 年で彼らがどのような業務を経験してきたのかや、リアルな現在の立ち位置などをお伝えしたいと考えています。</p>
<p>それでは、ご覧ください!</p>
<h1 id="自己紹介">自己紹介</h1>
<h2 id="飯田さん">飯田さん</h2>
<p>研修後は<a href="https://job-medley.com/">ジョブメドレー</a>で顧客向けの機能開発を担当。その後<a href="https://shigotalk.jp/">シゴトーク</a>でインフラ領域を中心にリニューアルに従事する。現在は <a href="https://us.job-medley.com/">Jobley</a> で施策の開発やインフラ周りまで幅広く業務を担当。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202404__003.jpeg","alt":"","index":0}"></p>
<p><em>飯田さん</em></p>
<h2 id="岡田さん">岡田さん</h2>
<p>研修後はジョブメドレーでの開発業務を担当後、シゴトークへ。ジョブメドレーとの連携強化のプロジェクトなど経験し、現在はシゴトークの開発全般を担当。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202404__007.jpeg","alt":"","index":0}"></p>
<p><em>岡田さん</em></p>
<h2 id="徳永さん">徳永さん</h2>
<p>研修後は<a href="https://jm-academy.jp/">ジョブメドレーアカデミー</a>に配属。プロダクト改善などを担当しつつ、直近では生成 AI を使った機能の開発やバックエンドからモバイルまで幅広い開発に従事。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202404__011.jpeg","alt":"","index":0}"></p>
<p><em>徳永さん</em></p>
<h2 id="古川さん">古川さん</h2>
<p>研修後は <a href="https://pharms-cloud.com/">Pharms</a> に配属。フロントエンドからインフラまで幅広く担当。小規模施策の開発に始まり現在は中規模施策のプロジェクトリードをしながら開発を行なう。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202404__014.jpeg","alt":"","index":0}"></p>
<p><em>古川さん</em></p>
<h2 id="村上さん">村上さん</h2>
<p>研修後は <a href="https://clinics-cloud.com/">CLINICS</a> に配属。電子カルテ関連の開発を経て、現在は中規模施策のプロジェクトリードをしながら開発を行なう。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202404__006.jpeg","alt":"","index":0}"></p>
<p><em>村上さん</em></p>
<h2 id="吉岡さん">吉岡さん</h2>
<p>研修後は<a href="https://clinics-app.com/">患者アプリ CLINICS</a> に配属。Web アプリとネイティブアプリの機能開発を担当した後、CLINICS に配属。現在は定常改善や、フロントエンドの大規模改修などに従事。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202404__010.jpeg","alt":"","index":0}"></p>
<p><em>吉岡さん</em></p>
<h1 id="ファーストキャリアとしてメドレーを選んだ理由について">ファーストキャリアとしてメドレーを選んだ理由について</h1>
<p><strong>──</strong> まずはファーストキャリアにメドレーへ入社を決めた理由をお伺いしていきたいと思います。</p>
<p><strong>飯田</strong>: 元々ベンチャー気質が高いことを軸に会社選びをしていました。新卒入社の社員が多数いるような規模が大きい会社だと、自分がその中に埋もれてしまい、自由度が高い動きができなさそうだと考えていたからです。メドレーは新卒が少ないのでその辺りはマッチしていました。医療という領域に関しても、<strong>日常生活に関わるところで社会に貢献できるような領域が良いなと考えていたことや、家族が看護師をやっていることもあり医療業界の課題を身近に感じられ、医療に対して興味があった</strong>ことが理由になっています。</p>
<p><strong>岡田</strong>: 自分の体調が悪くなることが昔から多かったためヘルスケア業界に興味があったのですが、そこに近い業界だったのがひとつの理由です。また会社のミッションがとても分かりやすかったというのがあります。他社に比べて、メドレーは<strong>世の中を良くしてくという会社のビジョンに、ちゃんと基づいてプロダクトを開発している</strong>んだなというのが具体的に分かって一貫性を感じられたのが大きいです。</p>
<p>他には、「エンジニア」という職種だともちろん技術分野が専門領域になっていくわけですが、その領域に閉じず、プロダクトを中心に課題解決する上でエンジニアリング以外のことも考えながら開発をしているプロダクトファーストな考えも魅力的でした。</p>
<p><strong>徳永</strong>: 自分は岡田くんのように特にヘルスケア領域に興味があったということはなかったんです。どちらかというと、<strong>エンジニアとしてこれから働いていく上で自分が大切にしたい志向や価値観というのが、メドレーのエンジニアの方達と話をしていく中でマッチする</strong>なと感じたのが大きいです。</p>
<p>それまでも色々な会社さんとの選考をしていたんですが、メドレーのエンジニアは「本質を追い求める」という姿勢の元、エンジニアリングが好きで、それをプロダクトへ還元していくという部分が自分のフィーリングにあったという感じでした。</p>
<p><strong>──</strong> フィーリングに合ったということですが、他にもマッチすると思った部分はありましたか?</p>
<p><strong>徳永</strong>: エンジニアの雰囲気も大人な感じの方が多く、体育会系の「気合と根性で頑張る」という進め方ではなく、合理的に物事を進めるために必要なコミュニケーションをきちんと取っていくところなどが魅力でした。そうした部分も含めて自分が働いている将来というのが想像できました。</p>
<p><strong>古川</strong>: 自分は親が薬剤師で医療分野に元々興味があり、医療 x IT という軸で就活をしていく過程でメドレーを知ったのが最初の出会いでした。お話を聞いていくうちに「課題解決のためのプロダクト作り」という点が非常に魅力に感じました。社会を良くするためにプロダクトを良くしていこうという姿勢で会社に一体感が生まれているのが、すごく良いなと。</p>
<p>会社に限らないかもしれないですが、組織も人が多くなってくるとやっぱり方向性が少しずつズレていくという感じになりがちだと思うんですが、メドレーの場合は<strong>会社として「医療業界をもっと良くしていくための事業をしていく」という軸が通っている</strong>のを感じて、最終的にはこの部分に惹かれて入社を決めました。</p>
<p>あとは、上場企業という「安心感」もファーストキャリアの選択に影響しました。</p>
<p><strong>岡田</strong>: 自分も新卒入社の社員目線だと、入社する会社の安定感というのは重要だと思っていました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202404__016.jpeg","alt":"","index":0}"></p>
<p><strong>村上</strong>: 就活する前は特定の業界に限らず色々な会社を見ていました。学生時代に様々な業界のエンジニアインターンやアルバイトなどしていたのですが、その過程で「せっかく正社員で働くなら、より社会的意義がある事業をやるべきだな」と感じました。そうした意識で就活に臨んで<strong>自分が興味を持って取り組めそうなのは医療業界だなと思った</strong>のが最初のきっかけでした。</p>
<p>メドレーは選考の中で、技術領域を問わずに開発できることなどがマッチすると感じたのと、サービスが医療機関だけでなく患者側も充実しているということも入社の決め手になりました。自分が患者になることも多いので、とても課題が分かりやすかったんです。</p>
<p><strong>吉岡</strong>: 自分も元々エンタメなどの領域より、医療や教育、金融領域を中心に就活をしていました。中でもメドレーの「医療ヘルスケアの未来をつくる」というミッションへの自分の共感度が非常に高かったのが、入社の理由でした。日本の医療制度をきちんと維持するためのプロダクトを作っていくんだという姿勢が非常に良かったです。</p>
<p>またさきほどの話にも出ていましたが、<strong>医療機関のためのプロダクトと患者のためのプロダクトという両面へのアプローチをしている</strong>のも決め手でした。やはり医療費の問題などは医療機関だけへのアプローチでは解決できないと思うので、そういう意味でも一貫性が感じられて良かったです。</p>
<h2 id="現在のチーム内での役割について">現在のチーム内での役割について</h2>
<p><strong>──</strong> では次に現在のチームでの皆さんの役割や動き方、チームメンバーとの関わり方について聞いていきたいと思います。</p>
<p><strong>飯田</strong>: 現在 Jobley の開発チームではエンジニアが 4 人いるのですが、その内 2 人がプロダクト施策の立案を行ないつつ、自分を含む残りの 2 人が実装をメインにして開発を行なっています。状況に合わせ PM 兼任のエンジニアと実装担当のエンジニアとで二人三脚で開発をしたり、単独での開発を行なったりと柔軟に対応していってます。</p>
<p>チームメンバーとの関わりという点では、<strong>エンジニアチームだけで開発するということはなく、セールスやカスタマーサクセスの方たちと一緒に要件を詰めていく</strong>ことが最近は多いです。そのためセールスの業務フローなどもキャッチアップする必要があるので大変ですが、面白くやりがいを感じるところでもありますね。</p>
<p><strong>岡田</strong>: シゴトークでは、PM とエンジニアの二人三脚という体制で開発をしています。PM と日々要件を刷り合わせながら、実装は自分が行なっていていくというスタイルです。Pull Request のレビューだけは元々シゴトークを開発されていたエンジニアの方々にお願いしています。</p>
<p>また、例えばプロダクトのセキュリティ強化の取り組みやアプリの認証機能の一新、QA 環境の整備など、自分が普段触れない領域の施策を行った際はスキルアップを実感できました。ライブラリのアップデートや、リファクタリングなども<strong>裁量を持ってやらせてもらっています</strong>。</p>
<p><strong>徳永</strong>: ジョブメドレーアカデミーはジョブメドレーや CLINICS に比べると小規模なチーム構成となっています。エンジニアが 7 人、PM が 3 人、プロダクトオーナーが 1 人という体制で開発をしてるのですが、<strong>良い意味で新卒入社エンジニアとして裁量を持って開発をさせてもらっています</strong>。</p>
<p>開発チームも徐々に大きくなっているので、タスクを上手く回すための仕組みを考えたり、チームを上手く機能させるために朝会の構成を変更してみたり。技術的な部分では CI/CD など開発全体に関わる分野についても、任せてもらっています。</p>
<p>また<a href="https://www.medley.jp/release/20230518.html">ニュースリリース</a>にもなりましたが、社内で初めての<a href="https://developer.medley.jp/entry/2023/11/30/211050">生成 AI を使った機能の開発</a>は、プロダクトオーナーと自分の 2 人が中心になって進めていきました。<strong>開発に入る前段階でロジカルに「こういった課題をこの機能で解決していく」ということが伝えられれば、ある程度技術的に実験的なものも導入することができる環境です</strong>。</p>
<p><strong>古川</strong>: Pharms では現在エンジニアが 4 人、PM 1 人、デザイナーが 1 人というのが中心メンバーとなっています。Pharms は現在色々と機能拡大をしていっているのですが、エンジニアが 4 人なので本当に全員で何でもやっていくという感じで開発しています。とはいえ、メンバー各々で得意分野は違うので、得意なところは積極的に拾いに行く感じです。</p>
<p>自分はと言うと、得意分野のインフラ部分に関してはほとんど任せてもらっており、自分からの発信で先輩のテックリードエンジニアの方とすり合わせしながら色々な施策を行なっています。その他にもクォーター毎に数個は中・大規模な施策が計画されるので、そうしたもののいくつかのリードをさせてもらったりしています。ですので、<strong>アプリケーションの開発という面でもプロジェクトを上手く進行させるという面でも、とても楽しく業務をしています</strong>。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202404__017.jpeg","alt":"","index":0}"></p>
<p><strong>村上</strong>: CLINICS ではまず「基幹」と「周辺」とざっくり 2 つのチームに分かれており、「基幹」が主にレセコン(レセプトコンピュータの略。医療施設から健康保険組合などの支払い機関に対し、診療報酬を請求するために、電子カルテと連動してレセプトと呼ばれる診療報酬明細書を作成するシステム)とのやりとりを、「周辺」では予約や問診、資格確認など CLINICS と患者との接点となるシステム、という形で担当業務が分かれています。自分はずっと基幹チームにアサインされています。チーム全対応のエンジニアの内訳はアプリケーション担当が 6~7 人、インフラ担当が 3 人ほどになっています。</p>
<p>現在は大きなプロジェクトが 1 つ進行していて、既存のレセコンをメドレーで新規開発しているレセコンに移行するというものですが、その中に<strong>中規模なプロジェクトが複数平行して進んでいて、その内のいくつかを自分がリード</strong>させてもらっています。</p>
<p>リードしているプロジェクトの進行管理やチームメンバー間での問題共有方法などを考えているのですが、この辺りは挑戦できる幅が広く、難しいですが面白いと感じています。課題についてプロジェクトに限定せず、他のシステムや進捗状況に影響がないかを配慮する必要があり、そうした視点で適切に異なる立場の関係者を巻き込むことで、より包括的な解決策を導き出そうと意識をしています。</p>
<p><strong>吉岡</strong>: 自分は今は村上と一緒のチームで業務をしているんですが、レセコンなどの部分というよりは、先程の話の「周辺」の開発をすることが多いです。基本的に平行して複数のプロジェクトが走っており、それぞれのプロジェクトでディレクター 1 人、エンジニア数人、他にデザイナーという形で進めていくことが多いです。</p>
<p>大まかにディレクターが要件などをまとめたものをベースとして、その後にデザイナーやエンジニアも入って要件定義などをしていき、徐々に形にしていくという事が多いですね。</p>
<p><strong>──</strong> お二人で組んで仕事をすることもあるんですか?</p>
<p><strong>村上</strong>: はい、とても多いです。 進行しているプロジェクトの他にも、定常改善やバグ改修などのタスクがありますが、そうしたタスクを「どのタイミングで」「どの優先度で」対応していくかなどをチーム内で議論してから実施するので。</p>
<p><strong>吉岡</strong>: プロジェクトやそうしたタスクとは別に、エンジニア主導で大規模なリファクタリングなども行なっています。担当しているプロジェクトの他にそうしたものを手がけていて、一緒になることが多いです。</p>
<h2 id="入社してメドレーの文化を強く意識した出来事">入社してメドレーの文化を強く意識した出来事</h2>
<p><strong>──</strong> メドレーで働いている中で、特に文化を強く感じたエピソードなど聞いてみたいです。</p>
<p><strong>飯田</strong>: 開発に関係する人全員が「プロダクト志向が強い」ということでしょうか。具体的には、開発の要件定義段階から全員関わるので、もちろんビジネスサイドの方からも「こうした方が良いのではないか」というアイデアは出てきますが、開発サイドからも「どういう機能が一番ユーザに価値を届けられるか」という視点で一緒になって考えるんです。そもそもどういった課題を解決するものなのかという基礎の部分から一緒に考えていくのがメドレーでは当たり前になっているという感じがします。</p>
<p><strong>開発サイドもビジネスサイドも同じ方向を向いているので、提案などもちゃんとロジカルで筋が通っているものであれば通るというのは、すごく意見しがいがある</strong>と思います。そのための相談なんかも、もちろんしやすいですし、とても良い文化だなと感じています。</p>
<p><strong>岡田</strong>: 開発にあたって PM が 施策などを Issue
として立ててくれるのですが、言語化が行き届いていて何が問題で、どうして実施するのか、その影響なんかがすごく分かりやすいという点はメドレーが大事にしている「ドキュメントドリブン」という文化を感じます。</p>
<p>そのような環境なので、大抵のことは最低限の文章のやり取りだけでスムーズに進みますし、上手くいかない状況になっても善後策などが立てやすくなっていると思います。</p>
<p><strong>徳永</strong>: メドレーは新卒入社の社員数は少ないので、中途入社の方がほとんどという環境です。入社してすぐは「自分の意見は通るのかな」とか「自分より後に入社した経験年数が長い中途入社の方とどう付き合おう」などが心配でした。でも<strong>メドレーでは「建設的に進める」という文化があるので、立場に関係なく議論できる環境があるので、とても仕事がしやすい</strong>です。自分の意見が例え間違っていたとしても議論が起きるということは、認識がズレていたということだからと軌道修正もできるので本当に働きやすいです。</p>
<p>また、とても良いなと思ったのが、部署や職種に関わらず<a href="https://www.atlassian.com/ja/software/confluence">Confluence</a>の情報に誰でもアクセスできるという文化ですね。秘匿情報以外は基本的に全て Confluence でオープンになっているおかげで、自分とは直接関係ない部署の現在の動きなどがキャッチアップできるので、そういった人たちとコミュニケーションを取るときにもすごく助かります。</p>
<p><strong>古川</strong>: 徳永とかぶるところがあるんですが、「建設的に進める」という部分は非常にメドレーらしさを感じる文化です。例えば PM が「施策をこうしましょう」となっても、自分が「このフローってこうなった方が良いですよね」という意見を言えるし、そこを起点に<strong>議論に発展してより良いものを作っていけるというのは本当に素晴らしい</strong>と感じています。</p>
<p>実際に自分がリードしていた施策の中でもエンジニア・ デザイナー・ PM ・カスタマーサクセスという立場が違うメンバー同士で改めて薬局の業務フローを整理しなおして、より良い仕様にできたりということがありました。 機能リリース後もちゃんと「あるべき姿」を目指して改善をできたりしたので、こういう考え方はとても仕事がしやすいです。</p>
<p><strong>村上</strong>: 自分からは「組織水準を高める」という文化について言及させてください。自分が関わっている電子カルテやレセコンという領域は深い業務知識が必要で、初めて関わった人からすると、そもそもどういうフローで機能が使われているかとか、特定の場合に何でこうなるんだろうというところが、理解しづらいんです。でも、自分のチームは新卒・中途問わず、まだ深いドメイン知識がない、といった状態からでも入って活躍しやすい環境だというのを感じています。</p>
<p>いきなり専門の業務知識が必要な開発というのはやはり難しいので、そうした部分と直接は関係しない周辺領域の開発から始めてもらって、徐々に電子カルテやレセコンに直接関係する開発に入ってもらえるようなオンボーディングができています。開発の中で、あるべき姿について議論になったりで色々なメンバーと話をしていくので、<strong>医療業務のキャッチアップはかなりしやすい環境</strong>だと思います。</p>
<p>もうひとつ「長期のカスタマー価値を追求する」という部分も文化を感じていて、主にマネージャーを含め、短期的な KPI に沿った開発よりも、長期的に顧客への価値提供に寄与できる開発を優先していくという意識が非常に強いのが良いと感じます。</p>
<p>特に今現在、社内で開発が進んでいる新規レセコンの開発については強く思いますね。</p>
<p><strong>吉岡</strong>: 自分が強く感じているメドレーの文化としては「ドキュメントドリブン」です。ミーティングなども、こういうことの結論を得るために協議する、というのが明確に分かる資料を事前に用意して行います。ですので、「これ何の時間だっけ?」みたいな状態になりにくいのが良いです。</p>
<p>あとは<strong>ミーティングによる同期的、Slack や Confluence などによる非同期のコミュニケーションを適材適所で全員が使いわけているので、そもそもコミュニケーションで無駄な行き違いのようなものも、ほとんど無い</strong>のも、仕事がしやすいと思っています。</p>
<p>もうひとつは、自分もやはりプロダクト志向の強さというところを感じています。現在はディレクターなどと一緒に開発をしていく体制になっているんですが、UI や顧客体験なども含めてエンジニアでも、ちゃんとプロダクトとしての理想の姿を考えて開発するという姿勢がとても強いです。自分の専門ではない領域でもきちんと考えながら開発をしていくので、やりがいがあります。</p>
<h2 id="これからメドレーに入社する方へのメッセージ">これからメドレーに入社する方へのメッセージ</h2>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202404__020.jpeg","alt":"","index":0}"></p>
<p><strong>──</strong> では最後になりますが、特に新卒でメドレーの入社を考えている方へのメッセージなどあれば、お伺いしたいです。</p>
<p><strong>飯田</strong>: そうですね、「先輩が新卒に対してとても期待してくれている」というのは伝えたいと思います。自分の場合、研修が終わって 3 ヶ月経ったぐらいで、シゴトークのインフラ環境を全部刷新するという大きなプロジェクトのリードを任せてもらえたんです。もちろん SRE の先輩などと協力しつつ進めたものだったんですが、<strong>研修終わってすぐにそんな大きなプロジェクトを裁量持って進められるような環境なので、すごくやりがいがあります</strong>。</p>
<p><strong>岡田</strong>: 稲本(人材プラットフォーム CTO)が「新卒だから」みたいな接し方をせずに、いちエンジニアとして接してくれているのは個人的に嬉しいポイントです。また<strong>責任を持った上で裁量持って自由に動いていける業務環境があるのが、すごく良い</strong>です。ちゃんとプロダクトの事を考えて開発をしたい人にはおすすめできる環境だと思います。</p>
<p><strong>徳永</strong>: 新卒の良いところは「同期」感を持てるところかなと思います。今もランチ等でこのメンバーと技術的なところなどを気軽に相談できるのはとても良いです。<strong>切磋琢磨していく環境じゃないかなと思っているので、そうした環境を求めるならすごく良い</strong>と思います。</p>
<p><strong>古川</strong>: 受け身で仕事を行うのではなく、ガツガツと自分の方から求めにいく姿勢の人には、すごくマッチする会社だと思います。せっかく裁量持って業務を進めることができる環境なので、<strong>遠慮せずにがんがんと物事進めることができる人には最高</strong>なんじゃないかと考えています。</p>
<p>自分はインフラだけではなくて、サーバやフロントエンドも開発していきたいと考えているんですが、小規模なプロジェクトであれば全て行える環境なため、自分一人でプロジェクトを完結することができるのは、性に合っているなと思ってます。周りのサポートや先輩との壁打ちなどはちゃんとやってもらえるので、とても勉強になり成長できると思います。</p>
<p><strong>村上</strong>: メドレーで開発をしていくについれて、医療ドメインという領域で自分が関わっていることが人の役に立っているんだなというのが日々実感できるところは良いです。特に色々な医療機関のサポートができるというプロダクトに関わっているので、直接医療機関やその先にいる患者さんの<strong>役に立っているという実感は、正直 1 年目で感じられるものではないと思っていたので、良い驚き</strong>です。</p>
<p>また大きいデータを取り扱うという経験ができているのも貴重だなと感じています。現在のプロダクトでは契約医療機関も非常に多いですし、その大量のデータの設計を変更したりというところを、裁量持って実施できるのは良い経験になっています。</p>
<p><strong>吉岡</strong>: メドレーの「プロダクト志向」を持っている先輩エンジニアと働けるというのはとても良い経験だと思っています。一般的にはエンジニアは技術さえあればという感じになりやすかったりすると思うのですが、メドレーではもちろん新しい技術なども使いながら、それを<strong>いかにプロダクトやその先のユーザへの価値提供に繋げるかを考えている方が集っている</strong>ので、そういう思考でプロダクト開発ができているのはすごく勉強になります。</p>
<p><strong>──</strong> ありがとうございました! 本当に 1 年で皆さんが濃い業務経験を積んでいるのが分かるインタビューになりました。</p>
<h2 id="さいごに">さいごに</h2>
<p>それぞれ異なるチームや業務内容ですが、新卒エンジニアでも裁量を持って業務に臨めることや、プロダクトの重要な部分の開発にどんどん関わっていけることが伝わるインタビューとなりました。</p>
<p>メドレーは新卒入社のエンジニアも積極的に募集していますので、今回気になったという方はお気軽にカジュアル面談をしましょう!</p>
<div class="remark-link-card-plus__container">
<a href="https://open.talentio.com/r/1/c/medley/pages/54863" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">open.talentio.com</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=open.talentio.com" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">open.talentio.com</span>
</div>
</div>
</a>
</div>
- 新卒配属2ヶ月目でChatGPTを活用した新規機能開発プロジェクトにアサインされた話https://developer.medley.jp/entry/2024/03/29/191108https://developer.medley.jp/entry/2024/03/29/191108はじめに
自己紹介
はじめまして。人材プラットフォーム本部プロダクト開発室第一開発グループの田中です。
私は、2023 年の 4 月に新卒エンジニアとして入社し、日本最大級の医療介護求人サイト「ジョブメドレー」のフロントエンドとバックエンド...Fri, 29 Mar 2024 08:00:00 GMT<h1 id="はじめに">はじめに</h1>
<h2 id="自己紹介">自己紹介</h2>
<p>はじめまして。人材プラットフォーム本部プロダクト開発室第一開発グループの田中です。</p>
<p>私は、2023 年の 4 月に新卒エンジニアとして入社し、日本最大級の医療介護求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」のフロントエンドとバックエンドの開発を担当しています。</p>
<p>学生時代は情報系ではなく、経済学を専攻していました。プログラミングには大学の授業で出会い、主にデータ分析に用いる <a href="https://www.r-project.org/">R</a> や <a href="https://www.python.org/">Python</a> を触っていました。そのため Web アプリケーションの開発は行っていませんでした。</p>
<p>エンジニアという職業に興味を持ったきっかけは、大学院時代に参加したインターンでした。インターン先の会社ではエンジニアが誰もいなかったのですが、開発未経験の私に機械学習を使ったアプリの PoC を作れという、かなり破天荒な経験をしたためです。今ではとてもレアな経験だったと思いますが、試行錯誤しながら何かを作ることに興奮を覚えたのを今でも鮮明に覚えています。</p>
<p><a href="https://www.medley.jp/">メドレー</a>に入社した理由は、大きな社会課題に取り組んでいる、触れられる技術がフロントエンド・バックエンドのように領域に限られていないなどありましたが、一番大きな理由は「<strong>技術はあくまで手段であり、顧客への価値提供を目的にしている</strong>」という考えに共感したことです。入社してまだ日は浅いですが、この考えは随所で感じられます。</p>
<p><img __ASTRO_IMAGE_="{"src":"./dev_202403_00.jpg","alt":"大学近くのビーチの夕焼け","index":0}">
<em>大学近くのビーチの夕焼け</em></p>
<h2 id="入社から初プロジェクトまで">入社から初プロジェクトまで</h2>
<p>さて、今回の記事では新卒エンジニアの私が初めて携わったプロジェクトの紹介を通して、メドレーでは新卒エンジニアが入社後にどのような働き方をしてるか伝えることができればと思います。</p>
<p>入社してから研修を終えて、配属後 1 ヶ月半くらいは主にプロダクトの定常改善系のタスクに取り組みながら、仕事のリズムを覚えていきました。</p>
<p>その後、今回紹介するプロジェクトに初めてアサインされました。この時点でプロジェクトで主に使用するスキルセットのフロントエンドは、新卒研修で学んだ程度でした。そのような状態の新卒でも、経験をたくさん積ませてもらえる良い環境だということもお伝えできればと思っています。</p>
<h1 id="プロジェクトで解決する課題と解決策">プロジェクトで解決する課題と解決策</h1>
<p>まず、今回担当したプロジェクトが発足した背景となる課題と、その課題を解決するために提供した新機能について紹介します。このプロジェクトについては、<a href="https://www.medley.jp/release/20240313.html">ニュースリリース</a>も公開していますので、合わせてご覧ください。</p>
<h2 id="ユーザーの抱える課題">ユーザーの抱える課題</h2>
<p>これまで「ジョブメドレー」では顧客が求人を作成する際に、フォーム形式で募集要項を記入する形式となっていました。記入の自由度が高いことから、法令やガイドライン等で定められた項目を漏れなく記載することや、事業所の魅力を効果的に伝える工夫が必要であり、顧客にとっては大きな負担になっていました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./dev_202403_01.png","alt":"フォーム形式の求人作成機能のイメージ","index":0}">
<em>フォーム形式の求人作成機能のイメージ</em></p>
<p>社内オペレーションの観点で見ると、求人が掲載基準を満たしているのか、入力された文章から読み取り確認する業務に時間がかかっていました。</p>
<p>そこで、顧客と社内オペレーションの双方が感じている課題を解決するプロジェクトが発足し、私も参画しました。</p>
<h2 id="ユーザー目線の解決策">ユーザー目線の解決策</h2>
<p>顧客と社内オペレーションの双方の課題を解決するため、求人作成プロセスをより直感的かつ効率的に改善しました。具体的には、従来のフォーム入力方式から、対話式の質問と回答を通じて求人を作成できる機能を導入しました。この改善により、顧客は簡単に、迷うことなく掲載基準に沿った求人を作成しやすくなりました。</p>
<p>さらに、文章作成の労力を軽減するために、 <a href="https://chat.openai.com/">ChatGPT</a> を活用した提案機能も導入しました。この機能を通じて、顧客は入力した内容に基づき、求職者の目に止まりやすい求人タイトルや訴求文を ChatGPT から提案してもらえます。これにより、求人原稿作成にかかる手間や煩雑さが軽減され、専任の採用担当がいない事業所でも採用活動を効率的に進めることができ、迅速な人材の確保へとつながります。社内オペレーションの観点でも、掲載基準を満たしているかを確認する業務の時間短縮が期待できます。</p>
<p><img __ASTRO_IMAGE_="{"src":"./dev_202403_02.png","alt":"質問形式の求人作成補助機能のイメージ","index":0}">
<em>質問形式の求人作成補助機能のイメージ</em></p>
<p>ChatGPT の導入有無に関しては社内でも協議され、文章生成に ChatGPT を用いることで顧客の作業コストを下げられるのであれば導入しようという意思決定がありました。流行っている技術だから導入するのではなく、<strong>顧客への価値提供のために新しい技術を導入する</strong>考えが現れた意思決定だと思います。</p>
<h2 id="技術スタック">技術スタック</h2>
<p>今回のプロジェクトでは次のような技術スタックで開発を行いました。フロントエンドは <a href="https://ja.reactjs.org/">React</a> を使用し状態管理ライブラリとして <a href="https://ja.react.dev/reference/react/hooks">React Hooks</a> を選択しました。従来の求人作成機能では <a href="https://redux.js.org/">Redux</a> で状態管理を行っていましたが、プロダクト全体の方針として今後 React Hooks を使用していく方針であったことと、実装のスピードを早めるために React Hooks を採用しました。</p>
<p>ChatGPT を用いた部分に関しては、顧客からのリクエストを受けて発行したトークンを保存するために <a href="https://aws.amazon.com/jp/dynamodb/">Amazon DynamoDB</a> を使用しました。DynamoDB に保存したトークンのバリデーションと GPT へプロンプトを渡し、提案の受け取り・返却する部分は <a href="https://aws.amazon.com/jp/lambda/">AWS Lambda</a> function に任せていました。GPT 本体は <a href="https://azure.microsoft.com/ja-jp/products/ai-services/openai-service">Azure OpenAI Service</a> を利用する構成でした。Azure OpenAI Service を採用した理由は、顧客情報や入力内容が学習に使用されることがなくプライバシー面でのリスクを回避できるためでした。</p>
<p><img __ASTRO_IMAGE_="{"src":"./dev_202403_04.png","alt":"文章生成機能の構成イメージ","index":0}">
<em>文章生成機能の構成イメージ</em></p>
<h1 id="プロジェクトへの関わり">プロジェクトへの関わり</h1>
<p>今回のプロジェクトのメンバー構成はエンジニア 3 人、デザイナー 1 人、PM 1 人という構成でした。開発には約 3 ヶ月の期間を要しました。</p>
<h3 id="プロジェクト初期">プロジェクト初期</h3>
<p>私自身のプロジェクトへの関わり方としては、プロジェクト初期段階からフロントエンドの実装タスクが中心でした。その中でスタイルの実装に関してはオーナーシップを持たせていただきました。今回のプロジェクトで作成する UI コンポーネントでは、似たようなものが複数あったため、できるだけ拡張性を持たせ使い回せるように意識して設計していました。仕事のサイクルとしては、デザイナーが作ったデザインを元に、細かい仕様を調整して実装し、デザインレビューをしてもらうサイクルを回していました。</p>
<p>ただ、React などをキャッチアップしながらの実装だったため、他のエンジニアより実装に時間がかかってしまっていました。適宜先輩エンジニアに質問しながら進めていましたが、いくつかのタスクは巻き取ってもらうことになるなど、悔しい気持ちを覚えたプロジェクト初期でした。</p>
<h3 id="プロジェクト中盤以降">プロジェクト中盤以降</h3>
<p>プロジェクト中盤あたりで一つエンジニアとしての仕事の向き合い方に変化がありました。プロジェクトのスケジュールがタイトだったのも相まって、プロジェクト初期ではとりあえず実装して早くタスクを終わらせることに注力していました。しかし、時間がかかっても良いから一つ一つ丁寧にコードを追って理解することに努めようと先輩エンジニアにアドバイスをいただきました。</p>
<p>そこから、理解に注力することで思考がクリアになり、結果的に実装スピードが上がっていきました。また、実装以外にも目を向ける余裕が生まれ、仕様について PM やデザイナーに提案することもできました。プロジェクト中盤以降は自信を持ってタスクに取り組むことができ、終盤には他のエンジニアのタスクを巻き取ることができるまでになりました。</p>
<p>プロジェクトの中では、プロンプトの改善タスクも任せてもらえました。内容としては意図しない回答を弾く改善でしたが、AWS Lambda を触るなど、必要があればクラウドサービスを触る機会もあると感じました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./dev_202403_03.png","alt":"ChatGPT による提案機能のイメージ","index":0}">
<em>ChatGPT による提案機能のイメージ</em></p>
<h1 id="さいごに">さいごに</h1>
<h2 id="プロジェクトを振り返って">プロジェクトを振り返って</h2>
<p>新機能がリリースされて数ヶ月経ちましたが、定めている KPI を達成し、結果が現れていることがとても嬉しいです。一例として、顧客が新機能で求人を作成してから社内での審査を経て掲載に至るまでの日数が、従来の作成方法と比べて半分以下と大幅に削減されています。</p>
<p>チームとしては今回のプロジェクトの進め方について振り返りを行いました。PM 、デザイナー、エンジニアそれぞれが次のプロジェクトをより良く遂行する提案を出し合い、今回のプロジェクトの経験が無駄にならないようにしています。現在、私自身はエンジニア一人の新しいプロジェクトを任されていますが、この時の振り返りでの反省をタスク分解やスケジュール策定に役立てられています。</p>
<p>個人的には今回のプロジェクトを通して、フロントエンドの技術習得はもちろんのこと、プロジェクトメンバーとのコミュニケーションで文章の構造化や画像に編集を加えるなどの工夫を加えることでコミュニケーションコストを下げることができるといった技術以外のことも学ぶことができました。</p>
<p>またプロジェクトを通して、若手のエンジニアが成長できる環境だと肌で感じました。配属後まもなくプロジェクトに参画させてもらえることや必要に応じて様々な技術を触らせてもらえることはもちろんのことですが、分からないことを聞きやすくしてくれる雰囲気や手を上げたら挑戦させてもらえる環境は贅沢に感じるほどです。</p>
<p>このような成長できる環境で若手時代を過ごしたい新卒エンジニアの方や、技術はあくまで手段であり、顧客への価値提供を目的にしているチームで働きたいエンジニアの方を絶賛募集していますので、ご興味がある方はぜひカジュアルにお話から始めてみませんか。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- 複数プロダクト横断したプロジェクトをどうやって成功させたのかメンバーに聞いてみましたhttps://developer.medley.jp/entry/2024/02/29/232209https://developer.medley.jp/entry/2024/02/29/232209はじめに
みなさん、こんにちは。エンジニアの古川です。
今回は CLINICS / Pharms 同時予約プロジェクト(以下、同時予約 Pj)というプロダクト横断で様々な部署が関わった開発プロジェクトについて、尽力された皆さんに座談会形式で...Thu, 29 Feb 2024 14:22:09 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。エンジニアの古川です。</p>
<p>今回は CLINICS / Pharms 同時予約プロジェクト(以下、同時予約 Pj)というプロダクト横断で様々な部署が関わった開発プロジェクトについて、尽力された皆さんに座談会形式でお話を伺いました。</p>
<p>メドレーの医療プラットフォーム(以下、医療 PF)でどのようにこうした横断プロジェクトを完遂したのかの様子を皆さんに知ってもらえればと思います。</p>
<h1 id="対談メンバー紹介">対談メンバー紹介</h1>
<p><strong>── はじめに、皆さんの自己紹介をお願いします。</strong></p>
<p><strong>江藤</strong>: 所属は医療 PF のプロダクト開発室の Pharms 開発チームです。 役割としては調剤薬局向けのシステムの Pharms のプロダクトオーナーを担っています。</p>
<p>経歴としては、2021 年の 1 月から Pharms のカスタマーサクセスとしてメドレーにジョインしまして、2023 年 1 月から開発チームに来ました。</p>
<p>これまでは事業側の経験が長く、営業全般や事業開発をメインでやってきたキャリアです。</p>
<p>このプロジェクトの PO としてリードする動きをしており、Pharms が担う調剤側のリードに加えて、全体の旗振りや調整等を主軸にやっていました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202403_005.jpeg","alt":"","index":0}"></p>
<p><em>江藤さん</em></p>
<p><strong>── 江藤さんがプロジェクトの中心となって推進したんですね。</strong></p>
<p><strong>江藤</strong>: 調剤ドメインの Pharms にとって事業インパクトがすごく大きい開発だったので、調剤ドメインが責任持ってやりましょう、という整理です。</p>
<p><strong>小田</strong>: 自分の所属も江藤さんと同じになります。 経歴は、2021 年 7 月にエンジニアとして入社し、これまでずっと Pharms の開発をやってます。</p>
<p>入社後はクライアント認証機能や法人担当者向けの本部機能などの機能開発を行ってきました。
昨年から Pharms のリードエンジニアとしてチーム全体を見つつ、Pharms の技術責任者として江藤さんと両輪で動くような形でプロジェクトの推進に関わっています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202403_014.jpeg","alt":"","index":0}"></p>
<p><em>小田さん</em></p>
<p><strong>有馬</strong>: 医療 PF プロダクト開発室、患者統合基盤チームに所属しています。患者統合基盤とは、CLINICS(患者アプリ)というアプリのバックエンドシステムであり、診療所や歯科医院、薬局向けのシステムとの連携基盤としても機能しているプロダクトになります。</p>
<p>経歴は、このメンバーの中では一番古く 2018 年 3 月に入社して、当初は CLINICS カルテの機能拡張の開発に参加し、PKI 基盤や CLINICS エージェントを始めとした他社システムとの連携基盤の開発を行っていました。2020 年くらいから患者統合基盤の立ち上げに参加して、現在に至ります。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202403_009.jpeg","alt":"","index":0}"></p>
<p><em>有馬さん</em></p>
<p><strong>酒井</strong>: 入社は 2019 年 8 月なので、 5 年目となります。 デザイナーとして入社後 1 年間はジョブメドレーに在籍していました。その後オンライン診療が伸びてきた時期になり、CLINICS 側のデザイナーの人数が足りなくなったことがありまして、そこからは電子カルテを含めた CLINICS のデザイン全般を担当していました。去年から電子カルテ以外の業務領域の PdM として活動しています。軸足はデザイナーなのですがプロダクトのマネジメントみたいなこともし始めてます。</p>
<p>このプロジェクトにおいては、さっき江藤さんがおっしゃった通り、Pharms の価値向上が中心のプロジェクトなのでプロジェクトの一番大きいリードは江藤さんになるかと思うんですけど、CLINICS などの医科側のプロジェクトリードと言う立ち位置が僕になるかなと思います。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202403_012.jpeg","alt":"","index":0}"></p>
<p><em>酒井さん</em></p>
<p><strong>小島</strong>: 医療 PF プロダクト開発室の QA グループに所属しています。QA グループのマネージャである<a href="https://developer.medley.jp/entry/2022/05/31/203823">米山</a>さんのご紹介で 2022 年 4 月にメドレー3人目の QA エンジニアとして入社しました。主担当は CLINICS の予約機能を中心とした周辺領域です。ここまで酒井さんと一緒に働かせて頂く機会が多く、直近では予約方法を拡張する「リクエスト予約」機能の QA を担当しました。</p>
<p>同時予約に関しては、主に医科側の部分の QA から始まり、調剤側の QA も必要ということになり、最終的には全体の QA を担当しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202403_016.jpeg","alt":"","index":0}"></p>
<p><em>小島さん</em></p>
<h1 id="同時予約の機能や解決した課題">同時予約の機能や解決した課題</h1>
<p><strong>── ありがとうございます。次に、今回の同時予約についてどういった機能で、どういった課題を解決するべきかをお話しいただきたいです。</strong></p>
<p><strong>江藤</strong>: 機能としては、<strong>CLINICS(患者アプリ)からオンライン診療を予約する際に、薬の受け取り先として薬局を事前指定できる機能</strong>になっています。
この機能に関係するドメインは患者・医療機関・調剤薬局の3つです。</p>
<p>患者と医療機関のメリットとしては、薬の処方までの手間が減ることです。</p>
<p>オンライン診療では、患者が医療機関に直接訪問するわけではないので、薬を直接薬局に行って受け取るのか、薬局から配送を希望するのかといった薬の受け取り方を、医療機関と患者さんで口頭で話して決めてもらっており、確認に手間が発生していました。患者さんは口頭で話した内容に沿って、オンライン診療終了後に、薬局への予約を取っていました。</p>
<p>同時予約機能が実装されたことにより、診察の予約時にどの薬局でどういう受け取り方をしますという部分を患者さんが事前指定できるようになったので、<strong>医療機関は事務的な話に時間をかけずに診察に集中でき、また患者さんから医師に口頭で追加説明しなくても希望通りに薬をもらえる、というフローに変更されたことが医療機関と患者のメリット</strong>だと思ってます。</p>
<p><img __ASTRO_IMAGE_="{"src":"./mag_Pharms_1.png","alt":"","index":0}"></p>
<p><em>同時予約機能のリリース前後でのフローの違い</em></p>
<p>もう一つのドメインである調剤薬局については、前述のように診察の予約をする時に患者さんがメドレーのサービスである Pharms を使っている薬局から事前指定をできますので、<strong>オンライン診療後に発行された処方箋の流入が増加します</strong>。</p>
<p>調剤薬局の売り上げは処方箋の枚数 × 単価で大部分が決まりますので、この枚数を増やせるという観点では薬局にとってインパクトのある機能だと思っています。</p>
<p><strong>── 患者の体験も本当に良くなるし、患者と医療機関の無駄なコミュニケーションも減り、かつ薬局に対しても処方箋枚数が増えて、導線も楽に使えるようになっているというのが、今回のプロジェクトの肝になっている感じですね。これらを踏まえ、プロダクト間を横断して開発しないといけないというのが大変そうです。</strong></p>
<h1 id="開発プロジェクトについて">開発プロジェクトについて</h1>
<p><strong>── どのようなチームがこの横断プロジェクトに関わっていたかを教えてください。</strong></p>
<p><strong>江藤</strong>: 開発側で言うと4つのドメインになってます。</p>
<p>調剤、患者、医科、そして <strong>患者統合基盤</strong>です。患者統合基盤とは医療 PF の各ドメインに存在する業務システムと患者アプリを繋ぐ根幹となるプロダクトです。</p>
<p>ただ今回の機能は医療機関のオペレーションも変わるため、開発側でのみ完結するプロジェクトではないんですね。</p>
<p><strong>いかに現場の運用に合わせつつ、体験を改善できるか?という視点で考えると、各事業側のカスタマーサクセスチームが重要になります</strong>。特に医科と調剤のカスタマーサクセスチームとは、密に壁打ちをしながら開発を進めていました。</p>
<p><strong>── 今回のプロジェクトは特に事業部との関係性が重要な鍵だったんですね。</strong></p>
<p><strong>江藤</strong>: そうですね。特に医療現場は業務が逼迫している場面も多いですので、例えば「ワンクリック増えます」「ここでページ遷移が増えます」といったことでも業務への影響が非常に大きくなります。</p>
<p>またオペレーショナルに現場を回している箇所も多く、プロダクト都合で勝手にこの方が良いよね、合理的だよね、と決めてプロダクトを作ってしまうと今までの現場オペレーションが崩れてしまい混乱を招くことがあります。</p>
<p><strong>現場の習慣を理解し、我々が目指す姿と調整しながら最適解を出す</strong>のが、必須になってくる業界・業態だと思うので、その調整は事業部ともかなり気を遣ってやっていました。</p>
<p><strong>── 同時予約 Pj は、開発側がやろうということで始まったプロジェクトなんでしょうか?</strong></p>
<p><strong>江藤</strong>: はい。これはメドレーのどのプロダクトもそうなのですが、事業責任者とプロダクト責任者がそれぞれおり、事業と開発の両観点から議論し、事業方針を決めていく、という体制をとっています。</p>
<p>Pharms を正しく成長させていくという観点で見ると、現フェーズでは処方箋をしっかりと調剤薬局に届けることが至上命題でした。サービスのあるべき姿とは?という観点で事業と開発両輪でやりましょうという形で決まったものになります。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202403_003.jpeg","alt":"","index":0}"></p>
<h1 id="横断プロジェクトで工夫をしたポイント">横断プロジェクトで工夫をしたポイント</h1>
<p><strong>── 今回、横断で色々な開発プロダクトが関係してきますが、工夫した点とか気をつけた点など、どんなものがありましたか。</strong></p>
<p><strong>江藤</strong>: 一番は各ドメインで追っている事業成果がかなり違っていましたので、その調整を時間をかけて擦り合わせした点です。</p>
<p>例えば、調剤の観点では処方箋をちゃんと薬局に届けるというのが、すごく重要な事業インパクトの大きい項目である一方で、医療の観点で見た時に、もちろん手間を減らすのはすごい重要ではあるんですが、医療ドメインとしてその改善に調剤と同規模のメリットを見出してもらえるかと言うと当然そうではなかったりします。</p>
<p>こうした部分で、ドメイン間でこのプロジェクトを達成した後に得られるメリットに差分が出てきます。メリットが大きい事業からしたら工数も大きく割けるのですが、メリットが弱い事業では同じだけ工数かけてやりましょう、という意思決定は難しいと思います。</p>
<p>そこで<strong>本 Pj を通じて各ドメインではどういう事業成果を生むのか、そのために何の KPI を追うのか、を明確にしました</strong>。事業的なメリットが多い調剤のために手伝ってくださいという形ではなく、各ドメインでどういうメリットを生んでいきますか?というところを揃えきるのが、すごく重要だったと思っています。</p>
<p>これを怠ると、メリットの弱い事業にとってはお手伝いみたいなプロジェクトになります。単純にエンジニアリングという部分でも面白いものにならないですよね。ですので、関係する全員でプロジェクトを実施することでどういう結果を得るのか?をちゃんと各ドメイン間で揃えました。</p>
<p><strong>── なるほど。そうするとそうした擦り合わせは江藤さんと酒井さんがやっていたんでしょうか。</strong></p>
<p><strong>酒井</strong>: はい、僕のほうで行っていました。僕が初期のすり合わせ時に重視していたのは、スコープを適切に区切るという部分でした。調剤側の提供価値の最大化だけを追って初期から大きな機能追加を行うのではなく、変更による医科・患者・調剤へ与えるリスクや開発コストを抑えつつ、価値を最大化できる落とし所はどこか、という部分から刷り合わせしていきました。</p>
<p>メドレーが面白いなって思うのが、プロダクト単独で動いていくわけではなくて、<strong>ペイシェントジャーニー(患者体験)を中心としてプロタクト群が存在しているところ</strong>です。 患者体験をより良く構築するためにそれぞれのプロダクトが動いていけるので、お手伝いというニュアンスじゃなく、プロダクトを超えてここはよくしていきましょうと言える文化があるところがいい部分かなと思いますし、やりがいがある部分かなと思います。</p>
<p><strong>江藤</strong>: 特に横断のプロジェクトだとこうした目的意識の統一に一番時間を使っても良いんじゃないかと考えています。ここが決まりさえすれば、後々迷った場合も目的に即した意思決定ができますし。ここを固めた後に各ドメインでの調整するという順番が大事だと思います。</p>
<p><strong>── 技術面で気をつけたポイントはどんなものがあったんでしょうか。</strong></p>
<p><strong>有馬</strong>: 同時予約機能としてやりたいことはありつつ、診療所側の業務フローや患者の導線、今あるシステムの制約などを踏まえ、どのような形に落とし込めば<strong>診療所も薬局も患者にとっても価値のある機能となるか</strong>がポイントです。</p>
<p>ですので、技術的な面と事業的な面を併せて考えながら、システム全体の要件を決めていくというのが最初に手がけた部分でした。</p>
<p><strong>── 機能を考える上で、全体を見据えた設計などをやっていくと思うんですが、今回はどこから着手されたんでしょうか?患者の導線だったり医療機関の業務フローなど把握した後に、取りかかった部分を聞きたいのですが。</strong></p>
<p><strong>有馬</strong>: 同時予約におけるシステム全体の要件を固めていくと同時に、全体のシーケンスとステートマシンを作っていきました。各システム間の連携フローと、それにより生み出される予約や処方箋といったエンティティの状態遷移を決めていき、この状態の時はこういったことはできないとか、そういった辻褄を合わせるところをしっかりやっていったという感じですかね。</p>
<p><strong>酒井</strong>: 今回は、特に<strong>プロダクト横断の共通基盤として、各プロダクトでそれぞれ全く別物の仕組みやステータスを保有しているのを、共通化・抽象化するという業務が必要になる</strong>ので、すごく難しいし、難易度が高い部分だなと思っているんですけど、それを綺麗にシーケンス図に落とし込んだ有馬さんが凄いなと思いました。</p>
<p><strong>江藤</strong>: 基盤側で作っていただいたシーケンス図は UX の骨組みなんですよね。そこをいわゆる UX デザイナーが別途やるといった細分化をせず、実装の制限を加味しつつ、正しく運用を回すために、この体験を作るためにこのステータス遷移であるべきだよね、という部分を有馬さんがエンジニア観点で最初に作ってくれてるというのが、<strong>メドレーのエンジニアのとても良い部分</strong>だと思っています。技術だけでなく UX などにも必要であれば越境していける文化ですね。</p>
<h1 id="同時予約-pj-開発の実際">同時予約 Pj 開発の実際</h1>
<p><strong>── そうした初期フェーズを経て Pharms と CLINICS どちらの開発にも関わった小田さんに聞きたいのが、開発の流れの中でいわゆるリソース的な問題って大変だったと思うのですが、そこはどう解決したんでしょうか。</strong></p>
<p><strong>小田</strong>: 先に Pharms 側の機能 を作ってその後に CLINICS の仕様を決めてもらってから CLINICS の開発に入る流れだったので開発が並行することはなかったです。ただ Pharms ではリードエンジニアという立場なので 、CLINICS の開発に入るための準備と、Pharms 側のレビューやフォローのリソース配分は自分で考えて、各タスクを実施する時間とタイミングを調整しながらやっていました。</p>
<p>一方で、この体制でも進めることができたのは <strong>Pharms チームの各メンバーが既に自走できる状態にあったので、そこに支えられたからこそできた</strong>かなと思ってます。</p>
<p><strong>── プロダクトも越境しながら開発するという文化はすごく良いと思ったのですが、ここはフローなどあったりしたんですか?</strong></p>
<p><strong>江藤</strong>: 先程も話した通り Pharms として本プロジェクトの優先度は高かったのですが、CLINICS の事業計画やリソースを踏まえると医科側の実装着手が大きく遅れそうな見立てでした。そこで、小田さんを 医科側の開発にもアサインできたらリソース問題が解決できるのではと調整しました。医科側のエンジニアからのフォローアップは必要なので、その調整もしつつですが、開発を早められるなら良し、と開発チーム間で最終的に判断しました。</p>
<p><strong>酒井</strong>: 今まで触ってなかった CLINICS のシステムにも小田さんはガンガン切り込んでくださったので、元々 CLINICS の開発をやってたのではと思うくらいキャッチアップが早かったです。</p>
<p><strong>── プロダクトのグロースのためにリソース調整を成功させたという形だったんですね。プロダクト間の越境がしやすいような文化だったり、プロジェクトを成功させようという目線合わせが自然にできるのは良いですね。</strong></p>
<p>チーム間という話でいえば QA は各チームに跨っての活動だと思いますが、その観点ではいかがですか?</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202403_002.jpeg","alt":"","index":0}"></p>
<p><strong>小島</strong>: 自分は開発が終盤に入ったタイミングでプロジェクトに合流しました。それまでは米山さんが QA として入っていたのですが、医科側でも検証すべきテスト観点が多数あった背景もあり、医科側の QA をメインで見る担当として参画しました。</p>
<p>後半に参画した背景もあり、そもそも処方箋送付という業務や Pharms プロダクトの仕様理解も浅い…という状態でしたが、<strong>開発目的・要件定義・設計が詳細にドキュメント化されていたので、スムーズに検証に入ることができました</strong>。検証の中で QA エンジニアとしても「この部分の仕様は見直した方が良くなる」というポイントがいくつか出てきたので、仕様の改善提案も行いました。後発参加でしたが、実際に取り入れてもらった改善も多いです。</p>
<p>メドレーのプロダクト開発の文化として QA エンジニアも<strong>要件定義からレビューに参加したり、意見を言いつつプロダクトをより良くするための改善が行える</strong>ので、とても働きやすいですね。</p>
<p><strong>── 後から入っても動きやすいというのは、ドキュメントなどが整備されているからできたことだというのが分かりますね。横断プロジェクトだからこそ気がついた他のチームの良い点などあったりしますか?</strong></p>
<p><strong>酒井</strong>: Pharms のプロジェクトってやりやすいな、と思ったのが KPI 設計がすごく明確なところだったという点でした。</p>
<p>色々紆余曲折あった結果ここに辿り着いてるという苦労を知ってはいるんですけど、今の <strong>KPI 設計ってやっぱりすごくシンプル</strong>で処方箋送信数ですとなると、そこから逆算した KPI を各 PF に振ればいいと言う体制だったので、すごくこれはやりやすかったポイントの一つかなと思います。</p>
<p>話す論点も結局そこっていう部分がすごくコミュニケーションコストが安くすんだ部分ではあるかなと思いました。</p>
<h1 id="横断プロジェクトならではのエンジニアリング">横断プロジェクトならではのエンジニアリング</h1>
<p><strong>── 全体の話から個別のドメインごとのエンジニアリングという点ではどのようなことをされていましたか?</strong></p>
<p><strong>小田</strong>: 基本的には有馬さんのシーケンス図を元に開発を進めていました。CLINICS では A という状態になる、その時に B というアクションがあると Pharms では C という状態になるというように状態遷移が複雑だったため、設計時にあるべき姿に迷うことが多かったのですが、シーケンスに立ち返るとすぐにその疑問が解決するといった具合です。</p>
<p><strong>有馬</strong>: <strong>シーケンスの整合性や難しくなりすぎないように設計する</strong>ことには気をつけました。「同時予約」という言葉が先行するとついつい複雑な新機能のようなものを作りがちですが、診療所や薬局の予約機能、処方箋送信機能といった個々のパーツはもともとあるわけで、なるべくシンプルにそれらをつなぎ合わせられるように心がけました。</p>
<p><strong>江藤</strong>: 細かいプロダクト間の調整が必要になるので ちゃんと調剤の欲しい KPI も求めつつ、かつ現場のオペレーション品質を落とさないっていう観点で調整を各ドメインでやりました。</p>
<p>工夫したことでいうと、UX 調査を徹底したところでしょうか。</p>
<p>今回のサービスって業界的にみた時に新しいかというとそうではないんですよね。ですので、同業界の企業さんの各アプリの体験とか、自分でも結構色々使いながら体験してみましたし、それらをベースに逆に現状のうちの仕様を踏まえると、こういう風にうちでは実装しましょうみたいなところを色々やっていました。</p>
<p><strong>小田</strong>: エンジニアリングの観点でいうと、特に患者アプリチームとの API 連携の仕様調整と、開発の進め方は密に連携しながら進めました。先にこっちができてないと向こうも開発できない状態になるので、担当者同士でコミュニケーションとりつつ、工夫しながら進めていました。</p>
<p><strong>酒井</strong>: CLINICS で気にしてた部分はどちらかというと<strong>リスクとコストの部分、いかに最小化して価値を最大化するかが一番重要</strong>かなと思っていたので、一番大事なのはやっぱり医科にとってどういう価値が出て、医科にとってどういうリスクが発生するかを考えてました。</p>
<p>例えば一番最初のすり合わせで薬局予約する時に日時まで確定させましょうという話もあったんですけど、そこまでスコープを広げなくてもメリットが出るのであれば、広げたくないですという話をしていました。</p>
<p>あとはリリースプランの策定とかかな?全医療機関に一斉に出すのではなく、まずは一部医療機関様だけに公開するリリース方式にしましょうみたいなところも含めてですかね。医科側で一旦テストをやって検証して、スムーズに機能が使えるところを確認したりするステップを設けましょうと安全策を取りながら進めました。</p>
<p>これはこのプロジェクトに関わらず、どんなプロジェクトでも一番重要でそこが後からひっくり返るのが一番手戻りになってくるので、そこは重要かなと思いながら進めてましたね。</p>
<p><strong>小島</strong>: QA 視点だと横断プロジェクトゆえにステートが本当に複雑で、メドレー入社後に経験したプロジェクトの中で検証の難易度が最も高かったですね。</p>
<p>ですが、皆さん仰られているように各ユースケースに対する設計が図でドキュメント化されていたので、テストケースとして転用できた部分も多くありました。有馬さんが作成した設計図があったからこそ漏れなくテストできた部分は大きいです。大勢が参加するプロジェクトにおいて、全員が認識合わせる上でも設計図は本当に大事だなと痛感しました。</p>
<p>工夫した点としては、小田さんは Pharms 開発のドメイン知識が豊富だったので、QA 時は Pharms 側をメインに見てもらいつつ、自分は CLINICS 側の QA を手厚く見るようにしました。どちらのドメインにも関係する契約プラン等の複雑な仕様は自分が担当しました。</p>
<p>QA 以外の面だと、リリースする上で必要な浮いたボールがあれば積極的に取りにいくようにしました。</p>
<p>例えば、これまで一部の医療機関様だけに機能リリースする場合、その後のフィードバック回収はメールや面談で個別に行っていましたが、日々の診療の中でお時間を確保頂くのが難しいという状況がありました。その結果、本 Pj においても全体リリースして問題ないか?の判断がしづらい時期がありました。</p>
<p>そこで Google フォームでアンケートを作成して短時間で回答できる形に切り替えました。結果的に 100% 回答を得ることができました。</p>
<p>アンケート結果から「従来機能より価値があり、院内オペレーションに組み込んで頂くコストもそこまで高くない」という点が評価できたので、自信を持って全体公開リリースに向けて GO 判定できました。** メドレーは役割に縛られずボールを任せて頂ける環境があるので、自分の専門領域以外のことも学べている**と思うことが多いです。</p>
<p>幸いリリース後も大きな不具合はないのでこれはもう皆さんのおかげですね。</p>
<h1 id="同時予約-pj-を終えて">同時予約 Pj を終えて</h1>
<p><strong>── では最後にこのプロジェクトの総括をしていだければ。</strong></p>
<p><strong>江藤</strong>: 横断ではあるけど各ドメインでそれぞれのプロフェッショナルがちゃんと力を発揮してプロジェクトをやり切れたというのはすごく大きい資産でした。</p>
<p><strong>医療 PF はアセットをたくさん持っているので、色々組み合わせると、他の会社だとできないけど、うちならできそうだね、みたいなことが色々ある</strong>んです。</p>
<p>なので、この後も横断プロジェクトは予定されていますが、それに先駆けてひとつやりきって、かつ僕らが欲しかった期待成果を現状出せているところを含めるとすごく良い一歩だったのかな、と思ってます。</p>
<p>次は品質を担保しつつ、もっとスピードを上げていきたいというのが観点として大きいですね。</p>
<p><strong>── なるほど。こうした横断プロジェクトもやっていく医療 PF ではどんなエンジニアがマッチするポイントでしょう?</strong></p>
<p><strong>有馬</strong>: 技術と併せてユーザーへの価値提供も考えながらエンジニアリングしたい人がマッチするのではないかと思います。</p>
<p><strong>── 各プロダクトの持っている役割を踏まえて、俯瞰の視点で開発もしたいという方にはすごく魅力的ですね。本日はありがとうございました。</strong></p>
<h1 id="さいごに">さいごに</h1>
<p>同時予約 Pj について、プロジェクトを推進したメンバーに話を聞きましたが、複雑な要件を実現可能な形にして各チームで垣根を越えて開発をしている様子がとても印象的でした。特に医療 PF ではドメイン同士で有機的に結合していき、コラボレーションすることによりステークホルダーへの価値を高めているというのは、やりがいがあるのではないかと感じます。</p>
<p>このようなプロジェクトで力を発揮したいと思った方はぜひ、お気軽にお話をしましょう!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- 稼働中の Amazon Aurora PostgreSQL を暗号化した話https://developer.medley.jp/entry/2023/12/22/163911https://developer.medley.jp/entry/2023/12/22/163911はじめに
こんにちは。
プロダクト開発室第二開発グループ所属の古川です。
私は 22 年新卒としてメドレーに入社し、現在はインフラとアプリケーションを 5 : 5 の割合ぐらいで開発・運用・保守などを行っています。
学生時代からインフラ周り...Fri, 22 Dec 2023 07:39:11 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは。
プロダクト開発室第二開発グループ所属の古川です。</p>
<p>私は 22 年新卒としてメドレーに入社し、現在はインフラとアプリケーションを 5 : 5 の割合ぐらいで開発・運用・保守などを行っています。
学生時代からインフラ周りに触る機会も多く、入社前に <a href="https://aws.amazon.com/jp/certification/certified-solutions-architect-professional/">AWS Certified Solutions Architect - Professional</a> を取得しました。</p>
<p>大のコーヒ好きで自宅で生豆から焙煎をし、毎朝豆を挽き、抽出したコーヒーを飲むことが日々の幸せです。
好きなコーヒー豆の種類は インドネシア・スマトラ島産・アラビカ種のマンデリン( G1 )の中深煎りです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202312_000.jpg","alt":"","index":0}"></p>
<p>さて、今回の記事では私が携わっているプロダクトで運用している <a href="https://aws.amazon.com/jp/rds/aurora/">Amazon Aurora</a>(以下 Aurora )を暗号化した時の検証・暗号化の作業について紹介させていただきます。</p>
<p>私が検証を始める前の Aurora 周りに関する私自身の認識は以下の通りでした。</p>
<ul>
<li><a href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> のサービス全体は大体知っている</li>
<li>論理レプリケーションというものが存在することは知っており触ったこともあるが、実際に稼働中のものに対して実施したことはない</li>
<li>非暗号化の Aurora DB クラスターを暗号化するのは大変であるという理解</li>
</ul>
<p>上記のような認識の新卒エンジニアが、検証をしながら大きな問題なく暗号化作業を実施した過程を紹介できればと思います。</p>
<h1 id="暗号化を実施する-aurora-とその周辺アプリケーションについて">暗号化を実施する Aurora とその周辺アプリケーションについて</h1>
<h2 id="影響範囲と暗号化をする-aurora-db-クラスターについて">影響範囲と暗号化をする Aurora DB クラスターについて</h2>
<p>今回暗号化を行う Aurora DB クラスターの周辺アプリケーションはざっくり以下のようになっています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202312_001.png","alt":"","index":0}"></p>
<p>実際に暗号化をする Aurora DB クラスターのエンジンは Aurora PostgreSQL で、主に <a href="https://aws.amazon.com/jp/fargate/">AWS Fargate</a> 上にある <a href="https://rubyonrails.org/">Ruby on Rails</a> (以下 Rails )で書かれた API サーバからのアクセスがあります。
インフラ構築は IaC ( Infrastructure as Code ) ツールの <a href="https://www.terraform.io/">Terraform</a> で管理しています。
また、暗号化対象の Aurora DB クラスターのデータ量は 5GiB 以下で比較的容量は小さめです。</p>
<h2 id="aurora-db-クラスターの周辺について">Aurora DB クラスターの周辺について</h2>
<p>複数プロダクトへ同時にアクセスを行うクライアントから、本クラスターにはアクセスが頻繁にあります。
そのため長時間読み込み処理ができないと他のプロダクトにも影響がでる可能性があります。</p>
<p>また、本 Aurora DB クラスターに対して書き込みが行われるメイン機能が実行されるタイミングでは、アプリケーションへの影響を最小限にしたいと考えました。
API サーバ内では基本的にロールバック処理がされているので、データ不整合は起きませんが処理に失敗はします。
当たり前ですがユーザ体験は悪くなるので、できるだけ失敗しないように暗号化作業を行う必要があります。</p>
<h1 id="実施方法の検討">実施方法の検討</h1>
<p>実施方法の検討をする上で以下の観点を意識しました。</p>
<ul>
<li>暗号化を実施する時間帯</li>
<li>暗号化するためにかかる時間</li>
<li>移行までの準備と労力・アプリケーションコードへの依存</li>
</ul>
<h2 id="暗号化を実施する時間帯">暗号化を実施する時間帯</h2>
<p>前提として<strong>ライターインスタンスの停止は 10 分程度にしたい</strong> という目標のもと時間帯を検討しました。この時間は暗号化実施時に予期せぬことが起き、元の状態に戻すことが必要になった時も踏まえ、できるだけ短く・かつクライアントの影響範囲が比較的少なくなるように決めたものです。
また、暗号化を実施する Aurora DB クラスターは複数プロダクトからのアクセスがあります。その中でも、今回は書き込み処理が行われる機能の利用が少ない時間帯に実施することにしました。</p>
<p><a href="https://aws.amazon.com/jp/cloudwatch/">Amazon CloudWatch</a> の Logs や <a href="https://www.datadoghq.com/ja/">Datadog</a> の情報をもとに、あるメイン機能は夜間・休日の利用が少ない傾向にあり、特に土曜日の深夜 26:00 が 1 番少ないと判明したのでその時間に実施することにしました。</p>
<h2 id="暗号化するためにかかる時間">暗号化するためにかかる時間</h2>
<p>暗号化をするために行う作業はいくつか存在し、その内容とかかる時間が異なります。
例えば、 Rails がアクセスする DB ホスト名を変更しただけだとコネクションプールが残ってしまい、変更前の非暗号化クラスターに接続しようとしてしまいます。そのため、ホスト名を変更した後に <strong>AWS Fargate サービスの再起動が必要</strong>で、その時間も安定稼働までに確保しなければなりません。</p>
<p>以上のような時間が発生することと、ライターインスタンスの停止は 10 分程度にしたいという目標を踏まえ、検証時に時間を計測しました。</p>
<h2 id="移行までの準備と労力アプリケーションコードへの依存">移行までの準備と労力・アプリケーションコードへの依存</h2>
<p>基本的に 1 人で検証・移行作業を行う、かつ私自身が別のプロジェクトも並行して進めているため、準備に時間がかかりすぎてしまうものは採用しないことにしました。
そのためできるだけ変更箇所は少ないもの、特に<strong>できればアプリケーションコードの変更が極力ない方法</strong>というのを検証時点で決めておきました。</p>
<p>上記を踏まえ、以下の 3 つの方法で検証をしたのでそれぞれについて説明します。</p>
<ul>
<li>スナップショットを用いて新規暗号化済みクラスターを作成する方法(方法 1 )</li>
<li><a href="https://aws.amazon.com/jp/dms/">AWS Database Migration Service</a> (以下 AWS DMS )を用いて継続的論理レプリケーションを使用する方法(方法 2 )</li>
<li><code>pg_dump</code> & <code>pg_restore</code> を使用する方法(方法 3 )</li>
</ul>
<h3 id="スナップショットを用いて新規暗号化済みクラスターを作成する方法">スナップショットを用いて新規暗号化済みクラスターを作成する方法</h3>
<p>こちらの方法は Web 上で「 Aurora 暗号化 後から」などと検索すると良くヒットする方法です。
すでに起動している非暗号化クラスターのスナップショットを取得し、そのスナップショットから暗号化済みの新クラスターを復元する方法です。データの不整合が起きないように、スナップショットを取得する段階から書き込みがされないように制限する必要があります。</p>
<p>準備することはほぼなく簡単に実施できるのですが、ライターインスタンスの停止時間が長すぎるため不採用としました。
検証時ではそれぞれ以下の時間だけかかり、ライターインスタンスの停止許容時間を大きく上回り現実的ではないと判断しました。</p>
<ul>
<li>スナップショットの取得時間:3 分</li>
<li>暗号化済み新クラスターの作成:約 30 分</li>
<li>アプリケーションから接続する DB ホストの切り替え:数秒程度</li>
<li>AWS Fargate の再起動:3 分強</li>
</ul>
<h3 id="aws-dms-を用いて継続的論理レプリケーションを使用する方法">AWS DMS を用いて継続的論理レプリケーションを使用する方法</h3>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202312_002.png","alt":"","index":0}"></p>
<p>こちらの方法は AWS DMS で論理レプリケーションを行い、旧クラスターの変更を検知して、新クラスターに反映させる方法です。
想定しているライターインスタンスの停止時間は、旧クラスターを読み取り専用に変更 〜 レプリケーションが追いつき DB ホストの変更が終わるまでの時間です。
この停止時間は 5 分弱と想定しており採用するつもりだったのですが、AWS DMS の移行前評価を行った時すぐに実施できないことが明らかになりました。</p>
<p>移行前評価の詳細は省きますが、主な原因は Large Object ( LOB ) の制約で、150 以上のカラムがそのままの状態では移行することができかったためです。
そのほとんどが null 許可の制約だったので解消しようとしましたが、テーブル構造だけでなくアプリケーション側の変更もいくつか必要だったため、不採用としました。</p>
<p>また論理レプリケーション設定を ON にするためには事前にライターインスタンスの再起動が必要で、結局別のタイミングで少しのダウンタイムは発生してしまうこともあり実施しないことにしました。</p>
<h3 id="pg_dump--pg_restore-を使用する方法">pg_dump & pg_restore を使用する方法</h3>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202312_003.png","alt":"","index":0}"></p>
<p>こちらの方法は、 PostgreSQL クライアントに標準で提供されている <code>pg_dump</code> と <code>pg_restore</code> を使用する方法です。
実施する時は、 <a href="https://aws.amazon.com/jp/systems-manager/features/">AWS Systems Manager</a> のセッションマネージャーを介してクラスターにアクセス可能な状態を作り、コマンドを実行します。</p>
<p>結論からいうとこちらの方法を実際に採用しました。
理由は主に以下になりますが、<strong>暗号化するクラスターのデータサイズが 5GiB 以下</strong>だったので、本方法でもそこまで時間がかからなかったのが一番の理由です。</p>
<ul>
<li>検証時にライターインスタンスの停止時間は 8 分程度だったこと
<ul>
<li><code>pg_dump</code> & <code>pg_restore</code> : 4 分弱</li>
<li>アプリケーションから接続する DB ホストの切り替え:数秒程度</li>
<li>AWS Fargate サービスの再起動: 3 分強</li>
</ul>
</li>
<li>移行時に実行するシェルスクリプトを用意すれば良いので準備が大変ではないこと</li>
<li>検証時に開発環境で実行した後も不具合は確認されなかったこと</li>
</ul>
<p>またこちらの方法は Amazon Aurora PostgreSQL をマイグレーションする時の方法として公式にも掲載されていたので、迷いなく行えると思いました( <a href="https://docs.aws.amazon.com/ja_jp/dms/latest/sbs/chap-manageddatabases.postgresql-rds-postgresql-full-load-pd_dump.html">pg_dump and pg_restore</a> )。</p>
<p>それぞれの検証結果をまとめると以下のようになります。
今回のユースケースでは方法 3 の <code>pg_dump</code> & <code>pg_restore</code> を使用した暗号化が良いと判断し、実施することにしました。</p>
<table><thead><tr><th>項目</th><th>方法 1</th><th>方法 2</th><th>方法 3</th></tr></thead><tbody><tr><td>暗号化するためにかかる時間</td><td>40 分弱</td><td>2 〜 5 分程度</td><td>5 〜 8 分程度</td></tr><tr><td>移行までの準備と労力</td><td>小</td><td>大</td><td>小</td></tr><tr><td>アプリケーションコードへの依存</td><td>なし</td><td>あり</td><td>なし</td></tr></tbody></table>
<h1 id="実施内容">実施内容</h1>
<p>行った作業内容は以下の通りで、それぞれ説明していきます。
移行時は、私とは別にもう 1 人の方にアラートなどの状況を <a href="https://sentry.io/welcome/">Sentry</a> 等で確認していただき、予期せぬ問題が起きていないかを確認します(当たり前ですが、ダブルチェックはすごく大事)。</p>
<ol>
<li>API アプリケーションから Aurora に接続する時には DNS Alias で名前解決するように設定する</li>
<li>暗号化された Aurora DB 新クラスターを Terraform で構築する</li>
<li>旧クラスターを読み取り専用に変更する</li>
<li>移行スクリプトを実行する(実行時間: 3 分強)</li>
<li>DNS Alias を旧クラスターから新クラスターに切り替える(実行時間:数秒)</li>
<li>AWS Fargate サービスを再起動する(実行時間: 3 分強)</li>
</ol>
<p><strong>1. API アプリケーションから Aurora に接続する時には DNS Alias で名前解決するように設定する</strong></p>
<p>AWS のマネージドサービスである <a href="https://aws.amazon.com/jp/route53/">Route 53</a> で、レコードに旧クラスターのエンドポイントを指定します。エイリアスの名前は、取得済みのドメインからサブドメインを指定しています。API アプリケーションから、そのエイリアス名で DB ホスト名を名前解決できるように設定を変更しておきます。
本 API アプリケーションは DB のホスト名は <a href="https://aws.amazon.com/jp/systems-manager/features/#Parameter_Store">Parameter Store</a> で管理していたので、AWS Fargate の再起動を行い起動時に新しい値が読み込まれるようにしました。</p>
<p><strong>2. 暗号化された Aurora DB 新クラスターを Terraform で構築する</strong></p>
<p>移行の前に暗号化済みの新クラスター作成しておきます。暗号化には <a href="https://aws.amazon.com/jp/kms/">AWS Key Management Service</a> を使用します( <a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key">Terraform の公式リファレンス</a>を見ればすぐに構築可能です)。</p>
<p>余談ですが、 Terraform で Aurora クラスターを作成する時、 <a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster">aws_rds_cluster</a> に記載されているサンプルでは、暗号化設定が記述にはないので作成する時は気をつけた方が良さそうです。</p>
<p><strong>3. 旧クラスターを読み取り専用に変更する</strong></p>
<p>旧クラスターに紐づいているパラメーターグループの <code>default_transaction_read_only</code> を 0 から 1 に変更します。この作業が完了すると旧クラスターへの書き込み処理はされなくなります。</p>
<p><strong>4. 移行スクリプトを実行する(実行時間: 3 分強)</strong></p>
<p>移行するためのスクリプトを実行します。
実際に実行した時には、いくつかのオプションをつけましたが、基本的にやったことは、 <code>pg_dump</code> したものを暗号化された Aurora DB 新クラスターに <code>pg_restore</code> するだけです。
2 で構築した新クラスターは、旧クラスターと同じプライベートサブネットに作成しておけば旧クラスターにアクセスできる状態になるので、移行コマンドは問題なく実行できると思います。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="sh"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> pg_dump</span><span style="color:#569CD6"> -h</span><span style="color:#D4D4D4"> <</span><span style="color:#CE9178">旧クラスターのホスト</span><span style="color:#D4D4D4">名> </span><span style="color:#569CD6">-p</span><span style="color:#D4D4D4"> <</span><span style="color:#CE9178">ポート番</span><span style="color:#D4D4D4">号> </span><span style="color:#569CD6">-U</span><span style="color:#D4D4D4"> <</span><span style="color:#CE9178">ユーザー</span><span style="color:#D4D4D4">名> </span><span style="color:#569CD6">-d</span><span style="color:#D4D4D4"> <</span><span style="color:#CE9178">データベース</span><span style="color:#D4D4D4">名> | </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#DCDCAA"> pg_restore</span><span style="color:#569CD6"> -h</span><span style="color:#D4D4D4"> <</span><span style="color:#CE9178">新クラスターのホスト</span><span style="color:#D4D4D4">名> </span><span style="color:#569CD6">-U</span><span style="color:#D4D4D4"> <</span><span style="color:#CE9178">ユーザ</span><span style="color:#D4D4D4">名> </span><span style="color:#569CD6">-d</span><span style="color:#D4D4D4"> <</span><span style="color:#CE9178">データベース</span><span style="color:#D4D4D4">名></span></span></code></pre>
<p><strong>5. DNS Alias を旧クラスターから新クラスターに切り替える(実行時間:数秒)</strong></p>
<p>移行が完了したら、 1 で作成した DNS Alias を新クラスターに向くように変更します。
移行時は TTL( Time to Live )はできるだけ短く設定( 15 秒)することで、ホストの切り替えに時間がかからないようにします。</p>
<p><strong>6. AWS Fargate サービスを再起動する(実行時間: 3 分強)</strong></p>
<p>ホストが切り替わったら、AWS Fargate サービスを再起動します(以下コマンドを実行するだけ)。
先述しましたが、本作業を行わないと Rails から Aurora DB クラスターへの DB コネクションプールが残ってしまいます。
すでにホスト名は変更されているためコネクションが失敗すると自動で新しく接続をし直し、失敗後は意図したホスト(暗号化済みの新クラスター)に接続されますが、接続先を必ず変更したいので、サービスの再起動を行います。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="sh"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> aws</span><span style="color:#CE9178"> ecs</span><span style="color:#CE9178"> update-service</span><span style="color:#569CD6"> --profile</span><span style="color:#D4D4D4"> <</span><span style="color:#CE9178">プロファイ</span><span style="color:#D4D4D4">ル> </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --cluster</span><span style="color:#D4D4D4"> <</span><span style="color:#CE9178">クラスター</span><span style="color:#D4D4D4">名> </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --service</span><span style="color:#D4D4D4"> <</span><span style="color:#CE9178">サービス</span><span style="color:#D4D4D4">名> </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --force-new-deployment</span></span></code></pre>
<p>本作業は検証時に DNS Alias を変更しただけだと接続先が変わらないことに気付き入れた対応でした。</p>
<p>ここまでの作業が完了したら実際に動かしているアプリケーションに不具合がないかを確認し作業完了です ✨</p>
<h1 id="さいごに">さいごに</h1>
<h2 id="まとめ">まとめ</h2>
<p>今回は稼働中の Amazon Aurora PostgreSQL を暗号化した時の検証・実施内容を紹介しました。</p>
<p>今回の方法は、対象のプロダクトで行う上での一例ですが、比較的少ないダウンタイムで作業を完了することができました。</p>
<p>また、 <code>pg_dump</code> と <code>pg_restore</code> 実行時に並列度を増やすことでもっと早く移行作業は完了できると思いますが、暗号化するデータの容量が大きくない、Aurora クラスター側に負荷をかける必要がなかったので今回は 1 で実行しました。</p>
<p>私自身本作業を終えて、パラメータグループの細かい設定についての知見も増え、この後にやった他の施策にも活きているので自分自身の成長にも繋がりました。
先輩方に意見を求めたりもしましたが、施策自体は 1 人でやり切れたことはとても良い経験になりました。
普段は、 AWS のマネージドサービス内で行えないかを真っ先に考えるのですが、マネージドサービスに依存しない形で行うこともユースケースによっては必要だなと再認識できた良い経験でした。
少しでも私の経験が誰かの役に立てばうれしいなと思います。</p>
<p>このように、メドレーは新卒・中途に関わらず裁量を持って施策を実行できる環境です。インフラやアプリケーションにとらわれず様々なプロジェクトに携われる開発チームにジョインしてみませんか?</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- toB 向け E ラーニングシステムに ChatGPT を導入して業務効率化を達成した事例について語ってもらいましたhttps://developer.medley.jp/entry/2023/11/30/211050https://developer.medley.jp/entry/2023/11/30/211050はじめに
エンジニアの山田です。
弊社のニュースリリースにあるのですが、ジョブメドレーアカデミー(以下、アカデミー)では Azure OpenAI Service の GPT モデルを駆使した新機能「研修作成アシスタント」をリリースしていま...Thu, 30 Nov 2023 12:10:50 GMT<h1 id="はじめに">はじめに</h1>
<p>エンジニアの山田です。</p>
<p>弊社の<a href="https://www.medley.jp/release/20230518.html">ニュースリリース</a>にあるのですが、ジョブメドレーアカデミー(以下、アカデミー)では <a href="https://azure.microsoft.com/ja-jp/products/ai-services/openai-service">Azure OpenAI Service</a> の GPT モデルを駆使した新機能「<strong>研修作成アシスタント</strong>」をリリースしています。 この生成 AI を活用した機能により研修作成担当者の作業時間を 75%軽減し、業務効率化に寄与するものになっています。</p>
<p>今回はこの研修作成アシスタントの開発について、担当した二人に話を聞きました。</p>
<h1 id="インタビュイー紹介">インタビュイー紹介</h1>
<h2 id="岸田さん">岸田さん</h2>
<p>SES 企業で業務システム開発、オフショア開発をしている会社でブリッジエンジニアという経験を経て、2019 年メドレー入社。
その後ジョブメドレーで開発をリード。現在はアカデミーのプロダクト責任者を務める。アカデミーのプロダクトに関する各施策を統括して管理している。</p>
<h2 id="徳永さん">徳永さん</h2>
<p>2022 年メドレーに新卒入社。
入社時からアカデミーの開発に携わる。大学では、対話 AI システム・ AI との対話用プラットフォームの研究開発をしていた。現在は自身も開発をしながら開発のリードにも挑戦している。</p>
<p><img __ASTRO_IMAGE_="{"src":"./202311_dev_005.jpg","alt":"","index":0}">
<em>左: 岸田さん 右: 徳永さん</em></p>
<h1 id="ジョブメドレーアカデミーのサービス概要">ジョブメドレーアカデミーのサービス概要</h1>
<p><strong>──</strong> <strong>今回アカデミーで採用した生成 AI を用いた新機能について話をしていきたいと思います。その前にアカデミーについてはこのブログでも何度か取り上げていますが、改めてアカデミーというプロダクトがどのようなサービス内容なのか教えてもらってもよいでしょうか?</strong></p>
<p><strong>岸田</strong>: アカデミーの概要としては、現在「介護」「障がい福祉」「訪問歯科」「在宅調剤」という **4 つの業種に対応した「オンライン動画研修サービス」**となります。</p>
<p>研修については、導入してくださっている顧客である介護施設側が、1 つ 1 つ研修内容に合わせてアカデミーに登録されている動画を組み合わせてカリキュラムを決めていき、職員の方たちはそれらの動画を見て、研修を受けてレポートを書いたりできるプロダクトになります。これに付随して研修そのものを計画したり管理できたりする、というとイメージしやすいでしょうか。</p>
<p><strong>──</strong> <strong>介護施設の研修業務を効率化するというプロダクトですね。どのようなミッションを持って、開発しているんですか?</strong></p>
<p><img __ASTRO_IMAGE_="{"src":"./202311_dev_001.jpg","alt":"","index":0}"></p>
<p><strong>岸田</strong>: 大前提として、提供している<strong>研修によって施設で働く人のスキルアップに役立たせる</strong>、という目的があります。それ以外では BtoB のサービスなので<strong>介護施設の事業効率化という点も重要視</strong>しています。本来、施設の本業は「介護」になるので、そこに本質的には関係しないような管理業務などは、アカデミーを使うことによって効率的に作業ができるようになる、というところを目指しています。</p>
<h1 id="最近の業務内容">最近の業務内容</h1>
<p><strong>──</strong> <strong>介護施設が「介護」以外に時間を取られているという課題を解決するプロダクトなんですね。そんなアカデミーでお二人は現在どのような業務をされているのでしょう。</strong></p>
<p><strong>徳永</strong>: 私は直近はチームとしてアカデミーの機能の一番コアな部分である研修システムの割り当ての改修をしています。今までは、施設職員への研修割り当ての機能が、小規模の施設だと少し使い勝手が悪く自由度が高くない機能だったんですが、その割り当て自由度を高めるためにコードレベルでばっさりと書き換えをするというプロジェクトを<strong>エンジニア 7 人 + プロダクトマネージャ 1 人で進めており、そのプロジェクトの開発リード</strong>をしています。</p>
<p>最初は 5 人で始めたプロジェクトでしたが、プロジェクト自体のボリュームも増えてきて現在の人数になりました。このプロジェクトは 11 月中のリリース予定になっています。</p>
<p><strong>──</strong> <strong>かなり大きい規模のプロジェクトのリードをされているんですね! 岸田さんはいかがですか?</strong></p>
<p><strong>岸田</strong>: 私は最近は開発に直接携わるよりも、プロダクト全体の方針決めなどをメインの業務にしています。次のクォーターではどんな開発をするべきか、来年はどのようにプロダクトを発展させていくかなどを考えるという仕事ですね。他にはアカデミーに在籍しているプロダクトマネージャ達の企画の精査だとか、コードレビューなどをしています。</p>
<p><strong>──</strong> <strong>メンバーのピープルマネジメントなどは引き続きやっていらっしゃるんですか?</strong></p>
<p><strong>岸田</strong>: はい、そちらも引き続きやっています。全員の 1on1 などは最近は他のリードの方にお願いしていたりもします。</p>
<p><strong>徳永</strong>: 現在のプロジェクトのメンバーの 1on1 は自分が担当させてもらっていることが多いです。主に話をする内容は開発に関する提案やディスカッションになります。</p>
<h1 id="生成-ai-を活用した研修作成アシスタントについて">生成 AI を活用した研修作成アシスタントについて</h1>
<h2 id="機能の概要について">機能の概要について</h2>
<p><strong>──</strong> <strong>ありがとうございます。最近のお二人のお仕事が分かったところで、本題である生成 AI を利用した「研修作成アシスタント」機能についてお話を伺えればと思います。ニュースリリースにも書いてあるのですが、こちらの機能の説明をざっくりとお願いしてよいでしょうか。</strong></p>
<p><strong>岸田</strong>: 先程のサービス説明のときにもお話したのですが、研修を考えるときには施設の方は「どういった研修にするか」を考える必要があります。「どんなテーマの研修にするか」「今月は制度で決まっているから、この研修をしないといけない」など色々な理由はあるんですが、テーマが決まって研修を実施するまでに「テーマに合った動画を探して選択する」というプロセスが入ります。</p>
<p>プロセスとしては他に「いつ誰に受けてもらうか決める」などもあるのですが、<strong>研修カリキュラムを作成する一番のペインは「テーマに合った動画を探して選択する」という部分</strong>なんです。</p>
<p>今までは、この「動画を選択する機能」としては以下の 2 つをプロダクトとしては用意していました。</p>
<ol>
<li>メドレー社内で選定された動画を研修のプリセットとして使える機能(頻出する研修のみ)</li>
<li>一つひとつ動画を選んでオリジナルな研修を作成する機能</li>
</ol>
<p>この 2 つだと 0 か 100 かみたいな形で中間となる 50 にあたる機能がなく、ここに相当する<strong>セミオーダーメイドのような機能が欲しいというのが以前からプロダクトで抱えていたニーズ</strong>だったんです。</p>
<p>今回提供した「研修作成アシスタント」機能では、<strong>大まかにどんな研修をやりたいかを研修名として入力してもらえれば、そこから生成 AI が「研修で具体的に学んでほしいこと」(研修の目的)や、「目的にマッチする動画」を提案</strong>してくれます。 管理者の方には生成 AI が提案してくる内容を取捨選択・訂正していただく形になっています。</p>
<p>これによって既存の機能が抱えていた「多様なニーズへの対応ができない」「研修作成に時間がかかり過ぎる」という両方の課題を解決する機能というイメージです。</p>
<h2 id="そもそもこの機能を開発した理由">そもそもこの機能を開発した理由</h2>
<p><strong>──</strong> <strong>「研修作成アシスタント」ですが、元々はどういった経緯で開発をすることになったんでしょうか。</strong></p>
<p><strong>岸田</strong>: アカデミーのサービスとしてのウリの 1 つでもあるんですが、動画を内製していてその数も 6,500 本以上に上ります。多くの場合 1 つの研修につき 5 本ほどの動画を設定するのですが、<strong>数千本の動画の中から研修にぴったりと合った動画を見つけ出すのは大変な作業</strong>でした。</p>
<p><strong>徳永</strong>: 研修を作るのに一番大事なのが動画選びなのは間違いないのですが、その他にも研修の目標を考えるのも同じくらい大事です。そもそも目標がないと、受講者もどういう背景でやる研修なのかが分からなかったり、<strong>「目標設定をして研修をする」ことが介護保険の加算要件に入っている場合もある</strong>ので、この目標設定は大事なんですが、こちらも 1 から考えるとなると中々に苦労する部分だったりしていました。</p>
<p><strong>──</strong> <strong>研修動画を選ぶのも、目標設定をするのも今までコストがかかっていたということですね。</strong></p>
<p><strong>徳永</strong>: こららのコストを何とか減らせないかというところが開発の立脚点になっています。研修名だけは考えてもらえれば、あとはこちらで自動生成して提案しますよという形になれば、<strong>プロダクトが便利になるなという予感</strong>がありました。</p>
<h2 id="アカデミーのサービス特性と-chatgpt-の相性の良さ">アカデミーのサービス特性と ChatGPT の相性の良さ</h2>
<p><strong>──</strong> <strong>実際に作業時間を 75%削減する機能となっており便利になっていますもんね。この機能の開発に ChatGPT を使っていますが、どうして ChatGPT を選んだんですか?</strong></p>
<p><strong>徳永</strong>: この機能を作ろうと動き出したのが、2023 年 3 月くらいだったのですが、時期的にちょうど GPT-4 が出るか出ないかというところでした。ですので、Google Bard など他の選択肢がそもそもなかった状態だったというのが理由の 1 つであります。</p>
<p>あとは Azure のほうは生成系 AI を使っても、こちらのデータは学習に使われない環境を提供していて、プライバシーの側面も考えると、しっかりと業務で使える環境が整っていたのが ChatGPT だった…という感じでしたので、選びました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./202311_dev_003.jpg","alt":"","index":0}"></p>
<p><strong>──</strong> <strong>こうした新しい技術へ向き合う姿勢という点ではどういったものがありましたか?</strong></p>
<p><strong>徳永</strong>: 今回特に ChatGPT など生成 AI が盛り上がってきたタイミングで、このまま行くと<strong>生成 AI が使われていないと事業としても不利になっていくタイミングが来る</strong>んじゃないかなと考えました。一度触れてみて、技術の進化の分岐点ではないかと感じたので、このタイミングでちゃんと社内の効率化みたいなものをやりたいと最初は考えていました。</p>
<p><strong>──</strong> <strong>社内でオペレーションをする方向けの機能を最初は考えていたということでしょうか。</strong></p>
<p><strong>岸田</strong>: そうです。アカデミーでは動画は内製しているため、企画の素案作りだとか動画の説明文を生成することなどを最初は考えていたんですが、アカデミーのプロダクトとして価値を高めるため**「社内ではなくて顧客向けの機能にしても良いのでないか」**と思い直したんです。</p>
<p>こうした点も考えると、生成 AI の導入によるデメリットというものがメリットに比べて限りなく少ないという判断ができましたので、元々課題として考えていた研修生成に使うことを決めました。</p>
<p><strong>徳永</strong>: 最初は日本でも生成 AI をプロダクトに取り込んでいるのは数件程度ということもあったので、さっと取り入れてしまったほうがメリットがあると感じました。</p>
<h2 id="研修作成アシスタントができるまで">研修作成アシスタントができるまで</h2>
<p><strong>──</strong> <strong>最初はどんな感じで企画など進行したんでしょう?</strong></p>
<p><strong>徳永</strong>: 最初は現在アカデミーで感じる課題に対して生成 AI でどのようなソリューションが出せるのかを思考実験という感じで Confluence (社内 wiki)にいくつか並行してまとめていきました。その中で一番費用対効果が高いというか、生成 AI を使わない従来の技術だと一番太刀打ちができない課題が「研修作成アシスタント」の機能でした。</p>
<p><strong>岸田</strong>: 生成 AI を使わない場合のアプローチは考えてはいたんですが、結局のところ<strong>検索を高性能にしていくか、おすすめをパッケージングするくらいしか効率的に動画を探す方法が見出せずにいました</strong>。</p>
<p><strong>──</strong> <strong>動画の検索を高性能にということだと…検索に使うメタ情報をちゃんと用意して…というような手段になりそうですね。</strong></p>
<p><strong>徳永</strong>: アカデミーの動画だと説明文などが、必要最低限しか記録されていないことが多く、動画内の情報を取ってくるのも難しかったのです。ですので、中々コストがかかってきて改善が現実的じゃなかったんですが**「生成 AI を使えばこの問題を解決できそう」という感触がありました**。</p>
<p><strong>──</strong> <strong>なるほど、一番解決が難しい問題を生成 AI でコストを最小限で解決できそうということだったんですね。こうした企画から実際に実装するまではどんな進行をしたんですか?</strong></p>
<p><strong>岸田</strong>: 最初は徳永さんと、もう 1 名のエンジニアとで 1 週間くらい検証作業をしていました。実際にちゃんとできるのかや、精度として顧客が満足するくらいまで出せるのかなどを検証していました。</p>
<p>ある程度行けそうという目星が立ったのですが、その途中で先程の動画の説明文不足が、障害になるというのも分かってきました。これを解決するために<strong>Google の <a href="https://cloud.google.com/speech-to-text?hl=ja">Speech-to-Text</a> を使って動画内の文字起こしを自動でできるようにしてメタ情報を作るようにしたり、ベクトル検索ができるような外部サービスを導入した</strong>んです。それだけだと精度が低かったので、生成したメタ情報をさらに ChatGPT で整形するような方式を取りました。</p>
<p>これらを DB に入れてベクトル検索をかけるようにして検索時に目標やタイトルと関連付けて検索できるようにしたという流れです。</p>
<p><strong>──</strong> <strong>なるほど 3 段構えくらいで機能を実現しているんですね。</strong></p>
<p><strong>徳永</strong>: とはいえ生成 AI は API の出力結果が若干不安定だったりするので、どこで最初はエラーが出たのかというのが分かりにくかったりしたので苦労しました。検索も今の形になるまでは <a href="https://opensearch.org/">OpenSearch</a> の全文検索を使ったりしたんですが、検索精度が低かったりして紆余曲折はありました。そこから今の形式になり精度を高めて、合計 1 ヶ月くらいでチューニングまで含めてリリースしたという感じです。</p>
<h2 id="今回のプロジェクトでの経験で学んだこと">今回のプロジェクトでの経験で学んだこと</h2>
<p><strong>──</strong> <strong>今回のプロジェクトで良い経験だったと思う点はどんなものがありますか?</strong></p>
<p><strong>徳永</strong>: 今回はあまり先行事例もないですし、調査と検証を繰り返していました。途中で色々問題も出たりしたのですが、やっぱり<strong>未知の状態で物事を進めるにはひたすら PDCA を回していくのが一番良いんだなということを学びました</strong>。</p>
<p>元々大学での研究が対話 AI などだったので、言語モデルをどう扱えばよいかなどの知識があったのは良かったなと思いましたね。</p>
<p><strong>──</strong> <strong>では今回のプロジェクトに適任だったんですね!</strong></p>
<p><strong>岸田</strong>: あと学んだことではないですが、今回のプロジェクトの副産物としてベクトル化した動画の<strong>メタ情報が手に入ったんで、これを発展させてまたユーザへ価値提供できる機能が作れるなというのは良かった点</strong>でした。</p>
<h2 id="今後のアカデミーの技術的な方向性">今後のアカデミーの技術的な方向性</h2>
<p><strong>──</strong> <strong>これから解決していきたいアカデミーの技術的な課題のようなものってありますか?</strong></p>
<p><strong>岸田</strong>: 今回初めてプロダクトに生成 AI を導入しましたが、<strong>プロダクトの他の機能にも取り入れることができないかなどは導入前よりスムーズに考えられるようになったので、ぜひ検討していきたい</strong>です。また、発展してディベロッパーエクスペリエンスの向上などに使えないかなども考えたいと思います。</p>
<p><strong>徳永</strong>: ちょっと話が AI ではなくなりますが、アカデミーのコードベースは DDD を行いつつ開発しているのですが、こちらの構造を<strong>もっと最適化し、新しくアサインされたエンジニアもよりスムーズに開発できるようにしたい</strong>なとは考えています。</p>
<p><strong>──</strong> <strong>ありがとうございました!</strong></p>
<h1 id="さいごに">さいごに</h1>
<p><img __ASTRO_IMAGE_="{"src":"./202311_dev_006.jpg","alt":"","index":0}"></p>
<p>元々アカデミー開発チームは、責任を持ってチャレンジしていこうという雰囲気があったと思うのですが、今回の ChatGPT を使った機能開発にもそうしたチームの雰囲気が良く作用しているなとインタビューをしていて感じました。</p>
<p>また生成 AI の導入も「新しくブームになりそうだから取り入れる」という考えではなく「日頃から解決を考えていた課題を解決できそうだ」という視点から導入していたのが印象的でした。</p>
<p>こんな雰囲気のアカデミー開発チームに興味が出た方は、ぜひカジュアルにお話しましょう!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- 歯科医院の業務効率を高める Dentis のサブカルテ機能開発について聞くhttps://developer.medley.jp/entry/2023/10/31/003732https://developer.medley.jp/entry/2023/10/31/003732はじめに
みなさん、こんにちは。エンジニアの新居です。今回は今年の 3 月にリリースされた Dentis のサブカルテ機能について、開発したメンバーに話を聞いてみました!
対談メンバー紹介
Dentis 開発メンバーの紹介
新居:
よろしく...Mon, 30 Oct 2023 15:37:32 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。エンジニアの新居です。今回は今年の 3 月にリリースされた <a href="https://dentis-cloud.com/">Dentis</a> のサブカルテ機能について、開発したメンバーに話を聞いてみました!</p>
<h1 id="対談メンバー紹介">対談メンバー紹介</h1>
<h2 id="dentis-開発メンバーの紹介">Dentis 開発メンバーの紹介</h2>
<p><strong>新居</strong>:
よろしくお願いします。早速ですが、メドレーに入社するまでや、入ってからどんな事をしていたのかを話してもらえればと思います。</p>
<p><strong>前田</strong>: <a href="https://clinics-cloud.com/">CLINICS</a> のリリース前から、ロゴデザインや UI デザインに携わったのがきっかけでメドレーに入社しました。入社してからは、CLINICS アプリや CLINICS カルテ、薬局向けシステム <a href="https://pharms-cloud.com/">Pharms</a> などのプロダクトをこのメンバーと一緒に開発していました。今は歯科医院向けの Dentis のデザインや企画、プロモーションなどに関わっています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_note010_dev_002.png","alt":"","index":0}">
<em>前田さん</em></p>
<p><strong>大岡</strong>: ソーシャルゲームを 6~7 年開発していたのですが、人材プラットフォームの CTO である稲本が中学の同級生で、彼の記事を読んだのがきっかけでメドレーに入社しました。入社してからは CLINICS カルテや iOS アプリの開発などをやっていました。その後は前田と同じで Pharms の開発をしてから、現在は Dentis を開発しています。他には電子処方箋の実証事業の開発などもしていたりしました。現在は Web のフロントエンド中心に開発をしています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_note010_dev_003.png","alt":"","index":0}">
<em>大岡さん</em></p>
<p><strong>宮内</strong>: 元同僚が働いていたので、メドレーにリファラルで入社しました。CLINICS オンライン診療の立ち上げをやった後は他の 2 人と同じで Pharms の開発をして、現在は Dentis の開発という感じです。今は主にサーバサイドの開発を中心に業務をしています。</p>
<h1 id="dentis-について">Dentis について</h1>
<h2 id="dentis-誕生まで">Dentis 誕生まで</h2>
<p><strong>新居</strong>: ありがとうございました。では、Dentis についてお聞きしたいのですが、どんなきっかけで作られたサービスなんですか?</p>
<p><strong>前田</strong>: 当初は、CLINICS カルテをベースとした歯科医院向けの予約システムを開発していたのですが、歯科レセコン(電子カルテの機能のうち主に会計など診療報酬の計算をするソフトウェア)のソースコードを<a href="https://www.daiwair.co.jp/td_download.cgi?c=4480&i=2036759">取得</a>するタイミングも重なり、予約システムだけではなく、レセコン一体型のシステムとして、本格的に開発をスタートしました。</p>
<p><em>詳しくは<a href="https://note.com/medley/n/nec25af1f8974">こちらの記事</a>を参照ください。</em></p>
<p><strong>新居</strong>: なるほど。そんな形で開発が始まった Dentis ですが、コロナ禍で途中で<a href="https://note.com/medley/n/nce07196a36e4">作り直していますよね</a>。再開してからは 1 から開発したんですか?</p>
<p><strong>前田</strong>: はい、CLINICS をベースに開発していたものの、歯科医院へのヒアリングなどを実施、調査をしていくうちに医科と歯科では業務オペレーションが異なり予約システムとしては不十分な機能だったことが判明して。特に予約部分などはコンセプトから考え直して UI を一から作り直しました。</p>
<p><strong>宮内</strong>: 途中で Pharms を開発してリリースしたことにより、そこで使った設計や技術などを応用できたというプラスの側面もあり、再度作り直しています。<a href="https://nextjs.org/">Next.js</a> を導入したりなどですね。</p>
<p><strong>大岡</strong>: 以前の Dentis はパイロット版として提供をしていたこともあり、しっかり作り変える後押しになったかもしれないです。</p>
<p><strong>新居</strong>: そうすると開発自体も以前よりも、やりやすくもなったんですね。スムーズに開発できるようになったおかげで、 <a href="https://www.j-platpat.inpit.go.jp/c1800/PU/JP-7335412/9F8179D09679876150E1B0ABB942C3F3D24F71E278BC7E6A2B456B2EEB2032B2/15/ja">特許</a>も取得できる UI ができたわけですね。</p>
<h2 id="dentis-のアーキテクチャについて">Dentis のアーキテクチャについて</h2>
<p><strong>新居</strong>: 現在の Dentis のアーキテクチャはどんなものになっているんでしょう。</p>
<p><strong>宮内</strong>: サーバサイドは <a href="https://rubyonrails.org/">Rails</a> アプリケーションになっています。そこにプラスして <a href="https://graphql.org/">GraphQL</a> を使用しています。フロントエンドは Next.js と <a href="https://www.apollographql.com/">Apollo</a> を使って GraphQL にアクセスするという形で全て AWS 上に乗っているという感じですね。</p>
<p><strong>新居</strong>: オーソドックスな構成になっているんですね。何か使いづらいなどはないですか?</p>
<p><strong>大岡</strong>: 使いづらいということはないんですが、フロントエンド側でデータをオーバフェッチしている所があるんで、そこは見直さないといけないと思っているところです。</p>
<p><strong>新居</strong>: フロントエンドの状態管理はどのようにしているんですか?</p>
<p><strong>大岡</strong>: <a href="https://recoiljs.org/">Recoil</a> を使っています。ですが、ライブラリの持続可能性を考えて別の方法に移行したほうがよいなとは考えているところですね…。</p>
<h1 id="サブカルテ機能の開発">サブカルテ機能の開発</h1>
<h2 id="サブカルテ機能とは">サブカルテ機能とは</h2>
<p><strong>新居</strong>: では今回のメインの話である<a href="https://www.medley.jp/release/20230328.html">サブカルテ</a>機能について、お話を伺えればと思いますが、そもそもサブカルテとはどんなものなんでしょうか。</p>
<p><strong>前田</strong>: 歯科特有の機能になると思うのですが、カルテに記録しない患者に関する情報、たとえばレントゲンや口腔写真などのファイル管理や文書管理などをデジタル化する機能です。デジタルで管理することで、写真やファイルを保管する場所を確保する必要がなくなり、院内空間を有効活用することもできますよね。また、Dentis にあって他社にない機能としては「アクティビティ」でしょうか。院内のスタッフ間で情報共有ができる機能で、Dentis を活用いただいている歯科医院から好評をいただいている機能のひとつです。</p>
<p>普通は院内でスタッフの情報共有をする場合は、紙カルテなどに直接書き込みするのですが、Slack のようなタイムライン形式で情報共有できリアクションなども取れるようにしているのが「アクティビティ」機能になります。</p>
<p><strong>宮内</strong>: 歯科医院は歯科医師以外にも、歯科衛生士や歯科助手など多数の人達が連携して治療にあたるチーム医療をしているので、このような使い方をしているようです。またサブカルテの内容としては医院独自になっているようなので、Dentis のサブカルテは様々な使い方をリサーチして一般的に使いやすいであろうという形にしています。</p>
<p><strong>新居</strong>: アクティビティなどは、今の形に落ち着くまで機能を考えるのは大変そうですね。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_note010_dev_008.png","alt":"","index":0}"></p>
<p><strong>前田</strong>: はい、最初は Markdown を書いていけるような Wiki のような形も考えていたりしたので、タイムライン形式に落ち着くまでは試行錯誤しました。医院のコミュニケーションの仕方によって使用率も違いますが、結構使ってくださっていますね。</p>
<p><strong>大岡</strong>: 画像ファイルにメモなどを残している医院などもあるようで、ワークフローによって使いやすい方を選んでいるようです。</p>
<p><strong>前田</strong>: Dentis は医院のペーパーレス化を促進するプロダクトなので、使い方としては様々ですが紙でのワークフローの感覚でそのまま、もっと便利に使えるという部分を重視しています。</p>
<h2 id="サブカルテ機能の開発背景">サブカルテ機能の開発背景</h2>
<p><strong>新居</strong>: そんなサブカルテ機能ですが、開発しようとなった背景としてはどんなものがあったんでしょうか。</p>
<p><strong>前田</strong>: Dentis では「シームレスにつながる」というのをひとつのコンセプトとして設計しているのですが、予約〜カルテ〜会計まで機能的につなげても、業務フローとしてはどうしても隙間が生じていて、シームレスにつながってない事に気づいたんですよね。院内スタッフ間でのちょっとしたコミュニケーションもデジタルで管理できれば、その隙間を埋め、歯科医院の DX 化を推進できるのではないかと思い、サブカルテの開発を始めました。先程も言ったように、ペーパーレス化を促進という目的にも合致していましたしね。</p>
<h2 id="技術的に難しかった部分">技術的に難しかった部分</h2>
<p><strong>新居</strong>: 技術的に困難なところなどはありましたか?</p>
<p><strong>大岡</strong>: 手書きメモと組写真機能(撮影した口腔内の画像を 5 枚や 10 枚で組み合わせて表示する歯科の画像表示形式)を Chrome と iPad / Mac Safari に対応させるのは難しかったかもしれません。</p>
<p>どれかに対応させると、どれかが上手く表示できないなどがあったので。</p>
<p><strong>宮内</strong>: 画像の表示ライブラリを途中で変更したりしましたよね。</p>
<p><strong>大岡</strong>: 最初は <a href="https://konvajs.org/">Konva</a> というライブラリだったんですが、ゆっくりペンを動かすと、滑らかな線が書けないという問題が発生しまして…。<a href="http://fabricjs.com/">Fabric.js</a> を使うように変更しました。</p>
<h2 id="サブカルテ機能を作ったことによるプロダクトへの良い効果">サブカルテ機能を作ったことによるプロダクトへの良い効果</h2>
<p><strong>新居</strong>: ありがとうございます。サブカルテ機能は今の形になるまで、どのような検討をしたんでしょうか。</p>
<p><strong>前田</strong>: 元々ファイル管理機能などは既に Dentis であった機能だったんです。そこは活かしつつも、求められる他の機能は何かというところを考えていきながら、様々なサブカルテの活用事例もリサーチしながら、今の形に落ち着いたという感じです。</p>
<p><strong>大岡</strong>: 副産物としては今まで Dentis にはなかった「患者詳細」という画面をこのサブカルテ機能を入れるために拡張していったので、その後のオンライン資格確認などへ展開しやすいという状態になりました。情報設計を見直せたおかげです。</p>
<p><strong>前田</strong>: サブカルテ起点で患者検索もさらに詳細にできたりしたので、そうした副産物も大変良かったです。</p>
<h2 id="サブカルテ機能追加による反響">サブカルテ機能追加による反響</h2>
<p><strong>新居</strong>: 医院からはサブカルテの追加により何か反響はあったんでしょうか。</p>
<p><strong>前田</strong>: ネガティブな意見はあまりないですし、むしろ「追加機能としてこういうのが欲しい」というご意見をいただくので、結構使っていただけていると思います。</p>
<p><strong>宮内</strong>: ちょっと興味深かったのが、承認ボタンが欲しいというご意見がありました。医院のワークフローとして、医師以外の方が行なおうとする処置などを「アクティビティ」上で医師に承認してもらうというフローのようです。すぐに導入できるかというとちょっと分からないのですが。</p>
<h2 id="dentis-チームにマッチするメンバー">Dentis チームにマッチするメンバー</h2>
<p><strong>新居</strong>: ありがとうございます。最後にそんな Dentis チームで一緒に働いてみたいと思うような方はどんな人でしょうか。</p>
<p><strong>前田</strong>: 高齢化が進んでおり、そろそろ若い人に来てほしいですね(笑)</p>
<p><strong>宮内</strong>: どの領域の開発メンバーでも現在は少人数で運営しているので、入ってほしいなとは思います…。一番欲しいというメンバーでいうと「歯科レセコンに精通した開発者」の方ですかね。</p>
<p><strong>新居</strong>: なかなかハードルが高そうな経験ですね(笑) 本日はありがとうございました!</p>
<h1 id="さいごに">さいごに</h1>
<p>Dentis のサブカルテ機能の開発について、開発者に聞いてみました。歯科には歯科独自のワークフローがあり、紙の運用が重視されてきた風潮の中で、業務オペレーションをふまえて開発していくユーザーファーストな開発者の発想やそれを実現する技術アプローチなど、とても面白い開発をしているなと話を聞いていて思いました。
Dentis の開発チームはベテラン揃いなので、彼らと肩を並べて働くことで、自身の成長を加速させる絶好の機会になると思います。</p>
<p>Dentis の開発に興味を持った方はぜひご連絡お待ちしております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- 医療 PF 患者アプリチームのデータドリブンな改善https://developer.medley.jp/entry/2023/09/29/233827https://developer.medley.jp/entry/2023/09/29/233827はじめに
みなさん、こんにちは。エンジニアの山田です。メドレーでは医療プラットフォームで患者が使う「CLINICS」アプリを iOS / Android / Web で開発・運営しています。日々、患者が便利に使えるように改善をしていますが、...Fri, 29 Sep 2023 14:38:27 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。エンジニアの山田です。メドレーでは医療プラットフォームで患者が使う「CLINICS」アプリを iOS / Android / Web で開発・運営しています。日々、患者が便利に使えるように改善をしていますが、特徴として<strong>エンジニアがプロダクトマネージャー(以下、PdM)と一緒に数値を見ながら、改善を行なっている</strong>という点が挙げられます。</p>
<p>今回は「CLINICS」アプリを開発している吉岡さんに、どのような開発フローで改善を行なっているのかをインタビューしてみました。</p>
<h1 id="自己紹介">自己紹介</h1>
<h2 id="吉岡さん">吉岡さん</h2>
<p>医療 PF プロダクト開発室第一開発グループグロースチームにて患者向けアプリ「CLINICS」の開発業務を担当。主に薬局に関する機能開発を行っている。大学の専攻は物理で、大学途中からプログラミングを始めた。その後、 大学 4 年から長期インターンで開発に取り組み、Ruby on Rails や React.js など主に Web 開発を経験したあと 2022 年に新卒でメドレー入社。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_note202309_dev_004.png","alt":"","index":0}"></p>
<p><em>吉岡さん</em></p>
<h1 id="メドレーに入社した理由">メドレーに入社した理由</h1>
<h2 id="メドレーに入社するまでの経歴">メドレーに入社するまでの経歴</h2>
<p><strong>山田</strong>: よろしくお願いします。早速ですが、学生時代からメドレーに入社するまでの経歴をお話いただけますか?</p>
<p><strong>吉岡</strong>: 学生時代は物理学を専攻していましたが、途中からプログラミングを始めました。そのまま Web 開発をしている企業で長期インターンをして Ruby on Rails(以下、RoR) や React.js を使った開発経験を経てメドレーに入社しました。</p>
<p><strong>山田</strong>: 物理学を専攻していたということですが、授業でプログラミングをしていたんですか?</p>
<p><strong>吉岡</strong>: いえ、コロナ禍に入り、ちょうど予定していたオーロラ観察のためのフィンランド行きが無くなってしまったということがありまして。予定が無くなり<strong>時間ができたので「何か始めてみよう」ということでプログラミングを始めた</strong>というのが最初のきっかけでした。プログラミング自体は前から気になっていたので、手を出してみたという次第です。</p>
<p><strong>山田</strong>: そうだったんですね! ではコロナ禍にならずに旅行に行くことになっていたら…。</p>
<p><strong>吉岡</strong>: はい、プログラミングしておらず入社もしていなかったかもしれないです(笑)</p>
<h2 id="なぜメドレーに入社しようと思ったのか">なぜメドレーに入社しようと思ったのか</h2>
<p><strong>山田</strong>: そんな吉岡さんは、どうしてメドレーに入社を決めたんですか?</p>
<p><strong>吉岡</strong>: まず最初にメドレーの「医療ヘルスケアの未来をつくる」という<strong>ミッションに共感したというのが大きい要因</strong>です。また<strong>プロダクトファーストな考え方をしつつ、プロダクト開発への取り組み方が長期で課題に対してじっくりとアプローチ</strong>をするという姿勢がとても良いと感じたのも理由になります。</p>
<p><strong>山田</strong>: 入社前に受けた印象と実際に入社後に感じた印象のギャップなどはありますか?</p>
<p><strong>吉岡</strong>: プロダクト開発に関しては特に感じてはいないです。技術スタックについては入社前には RoR と React.js という Web アプリの技術スタックを自分は持っていたので、入社後もその部分のギャップはないかな?と思って入社したのですが、今の配属は患者アプリということで iOS / Android のネイティブアプリの技術に触ることにもなるので、そこの部分がギャップといえばギャップになります。</p>
<p><strong>山田</strong>: 入社前は Web エンジニアとしてだけ業務すると思っていたけど、入ったらネイティブアプリエンジニアとしての業務もあったという話ですね(笑)</p>
<p><strong>吉岡</strong>: そうなりますね(笑) ただ、<strong>iOS / Android を触っていくうちに新しい技術に対する自分のキャッチアップ力には自信が付きました</strong>。「初見の iOS ・ Android でもキャッチアップして開発できたから、他の未知領域もキャッチアップしていけるだろう」という感覚が強くなりましたね。</p>
<h1 id="取り組んでいる業務について">取り組んでいる業務について</h1>
<p><strong>山田</strong>: それでは吉岡さんは現在のチームでどのような業務を担当しているか、聞かせてください。</p>
<p><strong>吉岡</strong>: はい。自分は患者アプリの開発を担当しています。患者さんの日々の通院や服薬を助けるということを目的として、オンライン診療・薬局への処方箋送付・お薬手帳などがこの患者アプリに含まれている機能になります。チームとしては自分は「グロースチーム」というチームに所属しています。主に既存機能の UI / UX の改善であったり、お薬手帳など患者が日常的にアプリを使ってもらえるような機能開発を担当しています。</p>
<p>チーム構成としてはデザイナー兼 PdM 1 人・ PdM 1 人・エンジニアが自分を含め 3 人という構成になっています。施策の規模によるのですが、小さいものであれば 1 人で iOS / Android / Web の 3 プラットフォームを開発しますし、ある程度の規模の施策であれば、それぞれ 1 プラットフォームずつエンジニアがアサインされて開発するようになっています。PdM の方はそれぞれの施策につき 1 人で担当されていることが多いです。</p>
<p>エンジニアはそれぞれ得意としているプラットフォームが分かれているので、プルリクエストを適宜レビューしてもらったり、不明な部分を質問したりという感じで開発していきます。</p>
<p>チームの定例は、毎日 30 分の夕会があり、そこで困り事があったら相談したり、共有するべき事を話したりという感じでやっています。その他は適宜、施策のキックオフだったり、自分が分からないことがあったりしたら周りに聞いたりということはしています。</p>
<p><img __ASTRO_IMAGE_="{"src":"_note202309_dev_001.png","alt":"","index":0}"></p>
<h1 id="お薬手帳改善プロジェクト">お薬手帳改善プロジェクト</h1>
<h2 id="お薬手帳とはどんな機能か">お薬手帳とは、どんな機能か</h2>
<p><strong>山田</strong>: ありがとうございます。では、本題となりますが、吉岡さんは「お薬手帳改善プロジェクト」というプロジェクトのメインのエンジニアとして担当をされていたということで、そちらのお話を聞いていきたいと思います。まず前提として「お薬手帳」の機能はどんなものなんでしょうか。</p>
<p><strong>吉岡</strong>: 皆さんも調剤薬局に行くと「お薬手帳をお持ちですか」と聞かれると思いますが、<strong>その機能をアプリ化して便利</strong>にしたものになります。</p>
<p>例えば、今まで自分が服用してきた薬についての履歴を参照できるようになります。また単に履歴だけではなく、服用した薬の副作用やアレルギー歴なども記録できるので、自分に合わない薬などを避け処方してもらえるようになるものです。この機能は調剤薬局で処方される薬だけではなく、市販薬についても記録できます。</p>
<p>また、患者アプリでのお薬手帳は QR コードで処方された薬を登録できるという機能と、薬の飲み忘れを防ぐため服用時間に通知を端末に送付する「服用アラーム」という機能も存在しています。</p>
<p><strong>山田</strong>: なるほど。では「お薬手帳改善プロジェクト」はどのような事を目的としていたプロジェクトだったんでしょうか。</p>
<p><strong>吉岡</strong>: 全体の目的として**「お薬手帳」機能を日常的に使っていただく患者数を増加させるために、UX の向上**を目指していました。私が担当した施策は、その中でも薬の飲み忘れを防止するための「服用アラーム」は使っていただいている患者の割合が低かったため、こちらの機能をもっと使ってもらうように改善すれば、必然的に日常的に「患者アプリ」を使う患者数も増えるだろうという背景のもと行われました。</p>
<h2 id="既存の服用アラームの課題">既存の「服用アラーム」の課題</h2>
<p><strong>山田</strong>: 「服用アラーム」はもともとそれ程には使われていない機能だったんですか?</p>
<p><strong>吉岡</strong>: もちろん使っていただいている患者数としては一定数いらっしゃったんですが、期待しているほどには多くなかったという感じでした。そこで使っていただけていない理由や背景を<strong>PdM が事前に分析などした結果として以下のような課題が浮き彫り</strong>になってきました。</p>
<ul>
<li>そもそも「服用アラーム」機能を知らなかった</li>
<li>知っていても「服用アラーム」機能の使い方が分からない</li>
<li>「服用アラーム」機能を設定していても通知が届かない場合があった</li>
</ul>
<p>そこで、これらの課題を整理して以下のような順番で改善を行なっていくことにしました。</p>
<ol>
<li>服用アラーム設定の UI 改善</li>
<li>服用アラームの通知が届かない問題の解消</li>
<li>お薬登録から服用アラームの登録導線の改善</li>
</ol>
<p>こうして、課題から実装方針までをまず決めていきました。</p>
<h2 id="服用アラーム設定の-ui-改善">服用アラーム設定の UI 改善</h2>
<p><strong>山田</strong>: 方針の内、最初の服用アラーム設定の UI 改善はどのように行なっていったんでしょうか。</p>
<p><strong>吉岡</strong>: まず、PdM が主体となりユーザーインタビューを行なった結果や、ユーザーサポートの問い合わせ傾向の分析をした結果、<strong>服用アラーム設定の画面でこちらが意図した通りに使ってもらえていないという事実が分かりました</strong>。UI 上でユーザーに少し混乱を招いてしまっていました。</p>
<p>そこで、その概念や設定フローを整理して UI に反映させて、混乱を少なくするように改善を行ないました。</p>
<p><strong>山田</strong>: ちゃんとこちらの概念が正しく UI に反映するようにしたということですね。</p>
<h2 id="服用アラームの通知が届かない問題の解消">服用アラームの通知が届かない問題の解消</h2>
<p><strong>吉岡</strong>: はい。これでまず設定が正しくできないという状態が解消されたので、次のステップに進むことにしました。次は正しく設定しているのにも関わらず、一部のユーザーに通知が設定通りに来ない場合があったのを解消しました。こちらの方で特にエラーなどは検知できなかったので、<strong>PdM と仮説を立てて検証</strong>していきました。</p>
<p><strong>山田</strong>: なるほど。どのような仮説だったんですか?</p>
<p><strong>吉岡</strong>: そもそもユーザーが Push 通知を実はオンにしていないのではないかという仮説です。そこで、仮説検証のためまずデータを取って実際どの位のユーザーが Push 通知をオンにしているかを調査したのですが、予想以上に多くのユーザーが Push 通知をオンにしていませんでした。仮説が正しいものだと証明されたので、<strong>Push 通知の許諾を促すための改善を行ないました</strong>。この施策の結果として 2 倍以上のユーザーが Push 通知をオンにしてくれたので、<strong>「服用アラーム」の通知が届かないという問い合わせが減少</strong>しました。</p>
<p><strong>山田</strong>: かなり効果的な施策になったようですね。工数的にはそこまでかからずに実現できたんですか?</p>
<p><strong>吉岡</strong>: はい、工数はあまりかかってないので、結果を見るとコストパフォーマンスが良い施策になったと思います。</p>
<p><img __ASTRO_IMAGE_="{"src":"_note202309_dev_005.png","alt":"","index":0}"></p>
<h2 id="服用アラームの登録導線の改善">服用アラームの登録導線の改善</h2>
<p><strong>吉岡</strong>: ここまでで、下準備が整ったので「服用アラーム」自体を実際にさらにユーザーに使ってもらうための施策として、登録までの導線を改善することにしました。今まではお薬を登録するか、お薬と服用アラームを同時に登録するか選べたのですが、お薬手帳への薬の登録後に「服用アラーム」の設定を促す導線を表示するように改善しました。理由としては、<strong>薬の登録をしてから「服用アラーム」の登録をしたユーザーの割合を調べてみたところ、大多数のユーザーが設定をしていなかったことが分かった</strong>からです。</p>
<p><strong>山田</strong>: かなりの方が「服用アラーム」を設定せずにいたのは驚きですね。ユーザーはなぜ設定していなかったんですか?</p>
<p><strong>吉岡</strong>: 改善以前の UI では、薬を登録をするためのボタンがあり、その下に「服用アラーム」設定ボタンが並列で設置されていました。 開発側としては、お薬登録と同時に服用アラームを設定する導線がユーザーには分かりやすいだろうと考えていました。しかし、フタを開けてみると登録数が増えないことになっていました。</p>
<p>良く考えると 当たり前だったんですが、開発側としてみれば「服用アラーム」という名前の機能については自明で「こういう動きをする機能だろう」というのが分かった上で、導線として認識していたんです。しかし、ユーザー側の視点とすると最初にこの画面を見て思うことは「服用アラームってなんなんだろう?」という疑問で、「良く分からないからお薬の登録だけしよう」という心理になってしまっていたようなんです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./before.png","alt":"","index":0}">
<em>登録導線改善前の UI</em></p>
<p><strong>山田</strong>: 確かに説明がないまま「服用アラーム」と言われると、まずは「何だこれ?」となりそうです。それでは、どんな改善を行なって、この課題に対処したんですか?</p>
<p><strong>吉岡</strong>: 先程もお話した、薬の登録から「服用アラーム」設定までのデータを PdM と一緒に検討した結果、一番多くアラームの設定をしてもらえるパターンとしては薬の登録をしたのと同時に設定しているということが分かりました。</p>
<p>ですので、<strong>導線は今までの「薬の登録」+「服用アラーム」ではなく「薬の登録」→「服用アラームの説明と設定」という形に変更</strong>しました。この改善によって、まずは薬の登録をしたいというニーズを満たし、次に「服用アラーム」とはどんな機能なのかを伝え、最後に「服用アラーム」の設定をしてもらうという、ユーザーにとって自然な流れになるように改善しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./after.png","alt":"","index":0}">
<em>登録導線改善後の UI</em></p>
<p><strong>山田</strong>: 説明も入ってちゃんとユーザーに便利な機能だということも分かってもらえるようにしたんですね。こちらの改善施策の効果はどんな感じだったんですか?</p>
<p><strong>吉岡</strong>: こちらの施策は改善の前後で 2 倍近く「服用アラーム」を設定してくれるユーザーが増えました。この改善も効果的なものになったんじゃないかと考えています。</p>
<h2 id="お薬手帳改善プロジェクトで苦労した部分">お薬手帳改善プロジェクトで苦労した部分</h2>
<p><strong>山田</strong>: お薬手帳改善プロジェクトでは PdM と二人三脚でデータを取りながら、仮説を立てて改善していくプロセスで結果に繋げていると感じたのですが、このプロジェクトで苦労した部分は何かありますか?</p>
<p><strong>吉岡</strong>: 技術的に難易度が高いという改善ではなかったのですが、個人的に苦労した点としては iOS / Android それぞれのプラットフォームで Push 通知に関する仕様が当たり前ですが全然違うので、<strong>詳しいメンバーにアドバイスをもらいながら通知に関してキャッチアップする必要があった点が、大変</strong>ではありました。</p>
<p><strong>山田</strong>: これまであまり Push 通知周りの実装の経験を持っていない中だと、各プラットフォームに合わせて実装するのはちょっと大変ですよね。</p>
<p><strong>吉岡</strong>: もちろん、公式ドキュメントも充実しているので、そうしたものを調べたり、最終レビューとしては各プラットフォームの知見が深いエンジニアに見てもらったりもしたので、キャッチアップもしやすくはありましたけども。</p>
<p>その他に<strong>ネイティブアプリの開発で必要な知識などは、当たり前ですが書籍などで勉強</strong>などはした上で、局地的にこうした Push 通知などに取り組んだ感じではあります。</p>
<h2 id="お薬手帳改善プロジェクトで学んだことやりがいと思ったところ">お薬手帳改善プロジェクトで学んだこと・やりがいと思ったところ</h2>
<p><strong>山田</strong>: 今回の一連のプロジェクトで吉岡さんが学んだ部分として、どんなものがありましたか?</p>
<p><strong>吉岡</strong>: エンジニアリングというと、一般的な印象として難しい実装をして結果を出すという感じに思いがちですが、今回のプロジェクトの施策のように、そこまで工数をかけない実装であっても、<strong>数字を見つつ仮説をちゃんと立てながら本質的な改善をすると結果にちゃんと結びつく</strong>んだということが、実感できたという点は大きいです。</p>
<p>また、今回のプロジェクトの主となる目的としては「服用アラームを使ってくれるユーザーを増やす」というものでしたが、そこに至るまでの<strong>準備をちゃんとしておかないと結局ユーザーにとって真に使いやすいものにならない</strong>ので、こうした下準備をして丁寧に改善することの大切さというのが分かりました。</p>
<p><strong>山田</strong>: ちゃんと足場を固めて、長期的な視点も入れて改善をしていくというのは、プロダクトの価値を出す上で大事な事ですよね。それでは今回のプロジェクトでのやりがいに思った部分はどんな部分ですか?</p>
<p><strong>吉岡</strong>: ストアを見るとレビューで星 3 をつけていたユーザーの方が改善後に星 5 のレビューにしてくれているのを見たりすると**「ユーザーのために本当にやって良かったな」**と感じます。レビューだけではなく、数字は継続的に見ているので、その数字が向上しているのを観測できたら同じようにやりがいがあると思いました。</p>
<h1 id="目指すエンジニア像">目指すエンジニア像</h1>
<p><strong>山田</strong>: ここまで吉岡さんが、関わったプロジェクトについて聞いてきましたが、これからどんなエンジニアになっていきたいかという将来像はどんな風に考えているんでしょうか。</p>
<p><strong>吉岡</strong>: まずは同じチームの中の周りのエンジニアの方達を目標としたいなと思っております。具体的には他のエンジニアの皆さんの<strong>課題解決能力の高さ</strong>を身に付けていきたいと思っています。</p>
<p>技術面でもプロダクト面でも、ちゃんと状況を分析して「こうすれば良いんではないか」という提案をさっとされたりしています。またその提案自体も複数出しつつ、それぞれのメリット・デメリットを提示してベストなやり方を選んでいる方ばかりなので、そうした部分を参考にしていきたいですね。</p>
<p><strong>山田</strong>: どのようにそうした能力を高めていこうと考えているんですか?</p>
<p><strong>吉岡</strong>: まずは普通に技術力を高めるというのはやっていかないといけないプロセスだとは思っています。 iOS / Android の開発もそうですし、データ構造などもちゃんと考えて実装が必要になったりする部分もあるので、サーバサイドなどもあるタイミングでちゃんとやっていきたいと考えています。</p>
<p>その上で、周りのエンジニアの方達と同じ考え方や、やり方を参考にしながら自身の課題に対処していく…ということを繰り返し経験していくことかなと考えています。</p>
<h1 id="チームで一緒に働いていきたいエンジニア像">チームで一緒に働いていきたいエンジニア像</h1>
<p><strong>山田</strong>: 最後になりますが、吉岡さんはどんなエンジニアと一緒に働いてみたいと考えていますか?</p>
<p><strong>吉岡</strong>: そうですね…。今のチームだと PdM とエンジニアが二人三脚で開発を進めていくというスタイルとなっていますので、そこで PdM が言うことだけをやるというのではなく、<strong>ちゃんと自身の意見を持ってプロダクトをより良くしていくという意識を持っているエンジニア</strong>の方と一緒に働きたいです。</p>
<p>色々な施策をする上で、PdM の方とちゃんと役割分担をしつつも、最終目標としてはプロダクトを良くする。その為に PdM を含めエンジニア以外のカスタマーサポートなど他の職種の方へのリスペクトが欠かせないのではないかと思いますので、そうした事も自然とできる方が良いです。</p>
<p><strong>山田</strong>: とても大事な意識ですね。本日はありがとうございました!</p>
<h1 id="最後に">最後に</h1>
<p>新卒 2 年目ながらユーザーへの価値提供をするために、一連のプロジェクトをメインに担当している吉岡さん。インタビューをしていてメドレーの他のプロダクトのエンジニアにも通じる考え方で真摯に開発をしている様子が伺えました。</p>
<p>メドレーでは今回のように、データドリブンでユーザーへ価値を提供する開発をしていくネイティブアプリエンジニアも絶賛募集していますので、ご興味がある方はぜひカジュアルにお話からしましょう。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- ジョブメドレー SRE Unit の業務を円滑に行う取り組みについてhttps://developer.medley.jp/entry/2023/08/31/164352https://developer.medley.jp/entry/2023/08/31/164352はじめに
こんにちは。
人材プラットフォーム本部プロダクト開発室第一開発グループの阪本です。
毎日夕方になっても 30 度を超えるような猛暑が続く中、皆さんはいかがお過ごしでしょうか?
今まで何度も紹介させていただきましたが、私は阪神タイガ...Thu, 31 Aug 2023 07:43:52 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは。
人材プラットフォーム本部プロダクト開発室第一開発グループの阪本です。</p>
<p>毎日夕方になっても 30 度を超えるような猛暑が続く中、皆さんはいかがお過ごしでしょうか?</p>
<p>今まで何度も紹介させていただきましたが、私は阪神タイガースのファンです。
今年は第二次岡田政権の 1 年目、開幕からの好調を維持し交流戦までは首位独走。交流戦明けに一時的に首位を明け渡す事態になりましたが、8 月に入り破竹の 2 桁連勝で首位奪還どころか独走状態という最高のシーズンを迎えております。</p>
<p>投手は元々良いチームではありましたが、今年は課題であった守備と出塁率に改善が見られ <strong>普通にやれば勝てる</strong> チームに近づいたというのが大きいのではないでしょうか。</p>
<p>この記事が公開される頃には優勝マジックが点灯し、18 年振りの「あれ」まで秒読みとなっていることでしょう。待ちに待ったその瞬間まで、あと少しです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202308_000a.jpg","alt":"","index":0}"></p>
<p>さて、現在私は SRE Unit リーダの立場で主に<a href="https://job-medley.com/">ジョブメドレー</a>の安定稼働に向けた取り組みに日々取り組んでいます。
今回はチーム内で抱える課題と、それを解決するための取り組みについて紹介させていただきます。</p>
<h1 id="sre-unit-が取り組んでいる課題">SRE Unit が取り組んでいる課題</h1>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202308_001a.png","alt":"","index":0}"></p>
<p>SRE Unit の業務は</p>
<ul>
<li>運用/メンテナンス</li>
<li>障害対応</li>
<li>アカウント管理</li>
<li>環境整備</li>
</ul>
<p>と多岐に渡り、ジョブメドレーを含めた大小 5 システムを対象として 4 名のメンバーで推進しています。</p>
<p>この中でも最近の SRE Unit での業務の大半を占めるものがリソースのバージョン追従対応ですが、明確な期限が存在する上に対象システム/リソースは多いといった特徴があるため、計画的に行動しないと突発業務が発生した際に期限内の対応が困難になります。</p>
<p>特に AWS といったマネージドサービス系はアップデートが強制的にスケジューリングされるため、こちら側の都合を挟む余地はありません。
こういった事態にならないために行なっている SRE Unit での <strong>この先に何が起こるのか?</strong> という「予測/計画」と、<strong>計画を阻害する要因</strong> となる「突発作業の削減」の 2 軸の取り組みについて紹介します。</p>
<h1 id="バージョン追従対応とは">バージョン追従対応とは?</h1>
<p>まずは前情報としてバージョン追従作業というものがどういったものなのかを説明します。</p>
<p>これはシステムが利用しているサーバ/データベースといったリソースのサポート期限終了 (EOL:End Of Life) までに最新バージョンに引き上げることを指します。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202308_003.png","alt":"","index":0}"></p>
<p>EOL を迎えたリソースは公式でのサポート終了を意味するものであるため、以降発生するバグや脆弱性に対しての修正対応が期待できない状態になります。</p>
<p>バージョンアップ対応を行わない場合、運良くバグや脆弱性の問題が発生しなければソフトウェア的に動作の継続は可能ですが、これに関連するライブラリなども古いバージョンのサポートを切るタイミングが出てくるので遅かれ早かれ影響は出てきます。</p>
<p>また AWS のマネージドサービス (ex.RDS/ElastiCache/OpenSearch) では、サポート期限の終了に伴い強制的に最新バージョンへの更新がスケジュールされます。</p>
<p>「最新バージョンへと更新してくれる」、その言葉だけ見ると素晴らしいものですが、もちろん更新中に発生するダウンタイムや接続するアプリケーションに対する影響まで面倒は見てくれません。</p>
<p>そのため、マネージドだろうが非マネージドだろうがバージョンアップ対応は必ず行う必要があると言うことです。</p>
<h2 id="作業の内容">作業の内容</h2>
<p>この作業は単純にリソースのバージョンアップをするだけで済む作業ではありません。</p>
<p>前述でのダウンタイムやバージョン間による変更差分によりアプリケーションが今まで通り動いているかを保証する必要があるため、下記をセットで実施することでシステム稼働を担保しています。</p>
<ul>
<li>新旧バージョンの変更履歴を把握</li>
<li>変更によるアプリケーション影響の把握</li>
<li>アプリケーションの動作チェック</li>
<li>負荷チェック</li>
<li>移行計画の策定</li>
<li>移行作業の実施</li>
</ul>
<h2 id="取り組み-1予測計画">取り組み 1.予測/計画</h2>
<p>これらの作業は決して少ない工数では無く、システムやリソースの増加に伴って作業量も増えていく一方です。</p>
<p>AWS であれば EOL 通知はメール等で行われるため今までは通知が来てから計画を立てるというフローを採っていましたが、対象システムの増加に伴うリソース増により上記内容を期限内に消化するのが困難になってきています。</p>
<p>さらに <a href="https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/MySQL.Concepts.VersionMgmt.html">RDS for MySQL</a> に関してはバージョンあたりのサポート期限が短縮傾向にあるため、一度バージョンを上げればこの先数年は安泰といった状態でも無くなってきています。</p>
<p>この状態を踏まえて、2023 年からは通知が来る前に先手を打つ方針に切り替えました。
いつ何が起きるのか?(どれがいつ EOL を迎えるのか?)を事前に把握し、それを元に逆算し計画をすることで無駄の無い行動を目指すというものです。</p>
<h3 id="対象リソースのリストアップ">対象リソースのリストアップ</h3>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202308_002a.png","alt":"","index":0}">
まずは各システムが利用するリソースのバージョンと EOL をリストアップし、自分たちが抱えるシステムとリソースの全量とそれらのバージョンがどういった状態なのかを把握します。</p>
<p>ここから各バージョンの EOL 時期を収集することになるのですが、幸いバージョン EOL については個々の公式サイトで明記されていることが多いので把握はそれほど難しくはありませんでした。</p>
<p>ex.)</p>
<ul>
<li><a href="https://www.ruby-lang.org/en/downloads/branches/">Ruby</a></li>
<li><a href="https://www.oracle.com/jp/java/technologies/java-se-support-roadmap.html">Java</a></li>
<li><a href="https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/MySQL.Concepts.VersionMgmt.html">AWS RDS/MySQL</a></li>
</ul>
<p>AWS といったマネージドサービスに対しては公式の EOL と必ずしもイコールにはなりませんが、マネージドサービス側が公式 EOL より期限が早いことは無いので、公式 EOL を期限として考えるようにしています。</p>
<p>また公式サイトが見つからない/明記されていないといったものが稀にあり、それらは <a href="https://endoflife.date/">endoflife.date</a> というサイトを活用しました。</p>
<h3 id="優先度の決定">優先度の決定</h3>
<p>リストアップした時点でかなりの量に及ぶため、ここから対応優先度を決定します。</p>
<p>最優先とすべきポイントとしては、期限を迎えると強制的にバージョンアップされてしまうマネージドサービス系のリソースです。</p>
<p>これらについては切られた期限に対する猶予は基本与えられないため、優先度が一番高いものと考えます。
次点として EC2 に自前でインストールするようなリソースが続きます。これは最悪 EOL を迎えても動作は継続できるため、上記よりは優先度を下げています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202308_005a.png","alt":"","index":0}"></p>
<p>後はこれらの 優先度/EOL の 2 軸で対応順を検討し、2023 年の年間スケジュールに落とし込みました。</p>
<h3 id="効果">効果</h3>
<p>2023 年 8 月の時点で、AWS のマネージドサービス系のリソースについては通知を受ける前にバージョン追従作業を済ませることができています。</p>
<p>特に 2023 年 10 月に EOL を迎える RDS の MySQL5.7 → MySQL8.0 対応は大変大きな規模の作業になりましたが、対象 4 サービス、DB インスタンス十数台を 2023 年 7 月に無事すべて切り替えることができました。</p>
<p>途中 AWS では OpenSearch や ElastiCache の緊急セキュリティアップデート対応が突発で入りましたが、全ての計画において先手というバッファを見込んでいたため予定を大きく狂わせず完了することができています。</p>
<h2 id="取り組み-2突発作業の削減">取り組み 2.突発作業の削減</h2>
<p>先程は計画を立てて無駄のない行動を目指すことを目的としていましたが、どれだけ予測や計画の精度を上げた所で突発作業はどうしても発生します。</p>
<p>社内からの要因であれば対処の仕方もありますが、外部要因となると予測も対応も困難であるものが多くなります。
特に「何も変更していないのに急に動かなくなった!」が一番脅威で、OS 仕様変更や外部ライブラリの廃止に伴うものがチームの実績として多く感じています。
こういった外部要因による事象を発生させにくい状態にするため、以下の対策を行っています。</p>
<h3 id="privategem-サーバの構築">PrivateGem サーバの構築</h3>
<p>ジョブメドレーは Ruby で動くアプリケーションですが、外部ライブラリとして多数の Gem を併用しています。
この Gem がある日突然リポジトリから削除され <code>bundle install</code> が通らず起動しない、といったことが稀にありました。
バージョンアップで対応できるものであればまだ良いのですが、そもそも廃止といった状況だと対応の長期化は必至。</p>
<p>そういった事態を避けるため <a href="https://github.com/geminabox/geminabox">Geminabox</a> にて PrivateGem サーバを構築しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202308_006a.png","alt":"","index":0}"></p>
<p>このサーバは RubyGems 向けの Proxy/キャッシュサーバ という位置づけで、このサーバ経由で RubyGems に Gem を取得すると同時にサーバ内にも Gem はキャッシュします。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202308_007a.png","alt":"","index":0}"></p>
<p>キャッシュされた Gem については以降 RubyGems に再度取りに行くことは無いため、RubyGems 上で取得できない事態が発生しても喫緊の対応が必要な状況にはなりません。</p>
<h3 id="動作環境の-docker-イメージ化">動作環境の Docker イメージ化</h3>
<p>とある Python によるプログラムを対象としているのですが OS/Python バージョン/Pip ライブラリ の兼ね合いがとても厳しく、少しでもバージョンにズレが生じると動作不良を起こし対応に回るといったことが年〜半年に一度発生していました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202308_008.png","alt":"","index":0}"></p>
<p>動作環境としては</p>
<ul>
<li>プログラム開始時に EC2 起動</li>
<li>コードのダウンロード</li>
<li>ライブラリのインストール</li>
<li>プログラム実行</li>
<li>プログラム終了後に EC2 停止</li>
</ul>
<p>という形態を取っていましたが、それぞれのステップに於いて毎度同じ環境になる保証がない状態というのが大きな課題でした。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202308_009.png","alt":"","index":0}"></p>
<p>具体的には EC2 の起動時は Amazon Linux 2 の仕様で Python のバージョンが変わって動かない、ライブラリのインストールではバージョン固定が甘くある日バージョンが変わって動かなくなる、といったことが起こる再現性の低い仕組みで組まれている状態です。</p>
<p>そこで、毎回同じ動作環境を維持するための取り組みとして Docker イメージによる環境の固定化を行いました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202308_010a.png","alt":"","index":0}"></p>
<p>これは Docker イメージによって OS を固定した上でイメージ内にコード/ライブラリまでを含めてしまうもので、OS 起動からプログラムの起動まですべて同じ状態になるようにしました。</p>
<p>また副産物としてプログラム起動までのステップが短縮され、プログラム起動までのオーバヘッドの短縮にも繋げることができました。</p>
<h3 id="効果-1">効果</h3>
<p>双方の仕組みの導入により、今年は対象システム回りでの突発作業は発生しない状態を維持できています。</p>
<h1 id="最後に">最後に</h1>
<p>今回は SRE Unit の業務の一つであるバージョン追従対応についてフォーカスを当てて紹介しましたが、抱える業務は他にも多数あります。</p>
<p>我々 SRE Unit の目的はサイトの安定稼働/信頼性を維持することであり、今回のようなバージョンアップはその中の一部でしかありません。この他にも負荷の監視やパフォーマンスチューニング、障害対応などを取り組むべきことは山ほどあります。
まだチームの人数は決して多くは無いこの現状で、いかにアイデア/技術/仕組み化によって効率的に動けるか切磋琢磨し、より安定したサービスを提供できるように日々取り組んでいます。</p>
<p>もしこれらの取り組みに興味のある方は、是非我々と一緒に働きませんか?</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- Tech Studio MATSUE オフィスのご紹介https://developer.medley.jp/entry/2023/07/31/120555https://developer.medley.jp/entry/2023/07/31/120555はじめに
みなさん、こんにちは。医療プラットフォーム本部プロダクト開発室第三開発グループ グループマネージャの太田です。レセコン(電子カルテの機能のうち主に会計など診療報酬の計算をするソフトウェア)新規開発プロジェクトでレセコンの開発を行...Mon, 31 Jul 2023 03:05:55 GMT<h1 id="はじめに">はじめに</h1>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202307_003.png","alt":"","index":0}"></p>
<p>みなさん、こんにちは。医療プラットフォーム本部プロダクト開発室第三開発グループ グループマネージャの太田です。レセコン(電子カルテの機能のうち主に会計など診療報酬の計算をするソフトウェア)新規開発プロジェクトでレセコンの開発を行っています。</p>
<p>2021 年 9 月 1 日に Tech Studio MATSUE オフィス(以下松江オフィス)が設立された際、他の初期メンバー 4 人と一緒にメドレーに入社しました。</p>
<p>記事を読み終わられた際は、1 人でも多くの方が松江オフィスに興味を持っていただけると嬉しいです。</p>
<h1 id="松江オフィスでやっていること">松江オフィスでやっていること</h1>
<p>松江オフィスではレセコン新規開発プロジェクトとして、<a href="https://clinics-cloud.com/karte">CLINICS カルテ</a> に内包されるレセコン部分の開発を Ruby on Rails で行っています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202307_001.png","alt":"","index":0}"></p>
<p>医療機関でどちらも使われるものですが、電子カルテがドクター向けのアプリケーションなのに対して、レセコンは事務員さん向けのアプリケーションとなります。</p>
<p>レセコンでは電子カルテに登録された診療内容を元に診療報酬の計算を行い、日々の業務としては請求書兼領収書、診療費明細書、処方箋、日報の作成を行い、月次の業務としては、保険者に対しての請求データの作成などの処理を行っています。診療報酬とは我々が医療機関を受診した際に受けた医療行為について、その対価として医療機関に支払われる費用です。医療行為1つ1つに点数が設定されており、この点数を「1 点=10 円」として診療報酬の計算を行います。</p>
<p>電子カルテとレセコンの関連性は強く、2 つのアプリケーションが近い関係であればあるほど、CLINICS の顧客に対してより価値の高いサービスの提供が可能となります。</p>
<p>これらの問題を解決すべく、<strong>Sustainable (維持し続けられる)、Reasonable (合理的である)、Improvable (改善し続ける)、Manageable (管理しやすい)を基本コンセプトにレセコン新規開発プロジェクトが始まり松江でレセコンの開発が行われることになりました。</strong></p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202307_002.png","alt":"","index":0}"></p>
<p>アプリケーション側で使いやすい API の開発を効率的に進めています。これには、アプリケーション側の開発チームとの関係強化が必須です。このため、東京の CLINICS チームとは日々 Slack や Google Meet などを通じて密に連絡を取り合いながら相互理解に努めることで、信頼関係を強化し、効率的に開発ができるよう取り組んでいます。</p>
<h1 id="松江オフィスで利用してる技術">松江オフィスで利用してる技術</h1>
<p>サービスのバックエンドということもあり、特別な技術スタックは使用せずオーソドックスに Ruby on Rails、MySQL を利用しています。
インフラも同様でサービスがスケールすることを念頭において AWS を利用しています。また IaC として Terraform を利用しています。
各人の開発環境も GitHub や Docker といった極めて一般的なものを採用しています。</p>
<p>新人を除く 5 人は元々別の会社において新規レセコンとは異なるレセコンの開発に携わっていました。当時は COBOL で開発を行っていましたが、レガシーな言語ゆえ、冗長的なコードになったり、同じようなロジックが複数のコードに存在していたりとメンテナンスに大変苦労していました。</p>
<p>現在は Ruby で開発を行っており、Ruby なりの難しさはありますが、<strong>COBOL で同じコードを書いたら 5 倍の量になるなと感じるくらい、当時と比べると開発コストは大幅に下がっていると実感しています。</strong></p>
<h1 id="松江オフィスの雰囲気文化">松江オフィスの雰囲気・文化</h1>
<p>松江オフィスがある松江市は中海、宍道湖と大きな湖を 2 つもつことで水の都と言われています。この 2 つの湖を結ぶ大橋川ではシーバス釣りが盛んです。
年に 1 回秋頃にレガッタの大会が行われ、弊社のメンバーもチーム TAGBORTS として参加しています(まだオフィスのメンバーだけではチームが組めないため助っ人をお願いしている状況です…)。去年は 2 回戦敗退と悔しい結果となりましたが、今年は更に上を目指したいと思います。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202307_007.png","alt":"","index":0}"></p>
<p>松江オフィスは事務所のような雰囲気は一切なく、都会的なデザインとなっています。内装は設計・施工を行っていただきました <a href="https://trailheads.jp/works/tech-studio-matsue/">トレイルヘッズ様の HP</a> にてご確認いただくことが可能です。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202307_005.jpeg","alt":"","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202307_006.jpeg","alt":"","index":0}"></p>
<p>開発用デスクは周囲が気にならないようにパーティションを設置しています。椅子にはアーロンチェアを採用しており、長時間の作業で体に負担がかからないよう配慮されています。</p>
<p>東京オフィスとのコミュニケーションは Google Meet か Slack がメインのため、電話がかかってくることもなく、集中して開発に取り組むことができる環境となっています。</p>
<p>現在エンジニア 5 名、ディレクター 1 名の計 6 名が在籍しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./_dev202307_004.png","alt":"","index":0}"></p>
<p>松江オフィスではレセコンに関する業務的な質問や Ruby に関するロジック的な質問など誰とでも気兼ねなく行うことができるように、メンバー間のコミュニケーションが取りやすい環境づくりを心がけています。例えば、今年からの取り組みですが、毎日 10 分程度の朝会を行うようにしました。会の目的としては進捗の遅れや、詰まってることなどを話すきっかけづくりです。</p>
<p>レセコン開発の性質上、エンジニアも診療報酬の知識のインプットが必要です。このため、ディレクターを中心に診療報酬の勉強会も行っています。</p>
<p><strong>以前開発していたレセコンを超えるレセコンを作り上げる。それがメンバーの共通の目標です。</strong></p>
<h1 id="松江オフィスでこんな人と働きたい">松江オフィスでこんな人と働きたい</h1>
<p>以下に当てはまる方、大募集中です。</p>
<ul>
<li>プログラミング(技術)が大好きで、それを手段として課題に対して積極的に行動できる</li>
<li><a href="https://www.medley.jp/team/culture.html">Our Essentials</a> に共感できる</li>
<li>医療業界に革新を起こす壮大なミッションにチャレンジしてみたい</li>
</ul>
<p>下記のリンクからご応募お待ちしています。</p>
<ul>
<li>中途採用は<a href="https://www.gogo-jobcafe-shimane.jp/job/detail?job_id=790936">こちら</a></li>
<li>2024 年度新卒採用は<a href="https://www.gogo-jobcafe-shimane.jp/job/detail?job_id=888626">こちら</a></li>
</ul>
<h1 id="おわりに">おわりに</h1>
<p>簡単ですが、松江オフィスの紹介をさせていただきました。</p>
<p>今後も松江オフィスのメンバー一丸となって レセコン の開発を進め、CLINICS チームと協力して稼働医療機関を増やしていき、より患者が医療にアクセスしやすい環境が作れるように取り組んでいきます!</p>medley
- メドレーの開発組織をリードするグループマネージャについて語ってもらいましたhttps://developer.medley.jp/entry/2023/06/30/122607https://developer.medley.jp/entry/2023/06/30/122607はじめに
みなさん、こんにちは。エンジニアの新居です。
今回のインタビューは前回の CTO へのインタビューに続いて、メドレーの開発組織についてご紹介していきたいと思います。
メドレーでは開発組織をリードするロールとして、CTO の他、「グ...Fri, 30 Jun 2023 03:26:07 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。エンジニアの新居です。</p>
<p>今回のインタビューは前回の <a href="https://developer.medley.jp/entry/2023/04/28/022245">CTO へのインタビュー</a>に続いて、メドレーの開発組織についてご紹介していきたいと思います。</p>
<p>メドレーでは開発組織をリードするロールとして、CTO の他、「グループマネージャ」(以下、GM)という役職があります。今回は医療プラットフォーム(以下、医療 PF)における開発グループの GM を担う二人に、具体的にどのような役割なのか聞いていきます。</p>
<h1 id="インタビュイー紹介">インタビュイー紹介</h1>
<p>今回の紹介はインタビュー中で前職からメドレー入社の話を聞いているので、シンプルにお送りします。</p>
<h2 id="平山さん">平山さん</h2>
<p>医療 PF 第一本部 プロダクト開発室 第四開発グループ GM。現在は <a href="https://clinics-cloud.com/">CLINICS</a> 基幹システムチームのマネジメントを担当。</p>
<h2 id="岡村さん">岡村さん</h2>
<p>医療 PF 第一本部 プロダクト開発室 第三開発グループ GM。現在はレセコン(電子カルテの機能のうち主に会計など診療報酬の計算をするソフトウェア)新規開発チームのマネジメントを担当。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note0630_011.jpeg","alt":"","title":"集合写真","index":0}"></p>
<p><em>左から岡村さん、平山さん、筆者</em></p>
<h1 id="前職までの経歴とメドレー入社の理由について">前職までの経歴とメドレー入社の理由について</h1>
<h2 id="二人の経験について">二人の経験について</h2>
<p><strong>新居</strong>: 早速始めていきますが、まずはお二人の経歴を教えていただきたいと思います。</p>
<p><strong>平山</strong>: メドレーは 2 社目の会社となります。前職は SIer で 14~5 年くらい受託開発や客先常駐して開発をしていました。SIer というと場合によっては二次請け・三次請けの開発をしているところもあるんですが、自分は一次請けとして直接顧客と関わる形で業務を行っていました。</p>
<p>顧客のサービスをステークホルダーと提案・折衝しながら、技術選定・要件定義から開発、リリースまで行い、そこからの保守運用といった部分までの一連の流れを様々な業態の顧客と関わらせて頂きました。</p>
<p><strong>新居</strong>: ありがとうございます。かなり自社開発と同じ感覚の開発を長年されてきたんですね。それでは、岡村さんお願いします。</p>
<p><strong>岡村</strong>: 新卒で外資系の IT コンサルティング会社で IT コンサルタントとしてキャリアを始めました。その中でも特に「情報系」と呼ばれるデータアナリティクスや BI (Business Intelligence) などを取り扱う部門で業務をしていました。領域的には金融・製薬・省庁など堅めの領域を主に担当していたので、いわゆる Web アプリケーションを作るというよりは SQL をガリガリ書いてデータ分析を基に業務改善していく…という感じの業務が多かったです。</p>
<p>2 社目はスタートアップ企業の立ち上げフェイズにジョインしました。その会社はいわゆる Insure Tech と呼ばれる保険業界 x テクノロジー領域のサービスを作っている会社でした。そこで、その会社でのプロダクトマネージャ業務と、運転資金を確保するために大手保険会社向けのコンサルタント業務に半々で従事していました。</p>
<p>その後にメドレーにジョインするという経歴です。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note0630_005.jpeg","alt":"","title":"岡村さん","index":0}"></p>
<p><em>岡村さん</em></p>
<h2 id="メドレー入社の経緯について">メドレー入社の経緯について</h2>
<p><strong>新居</strong>: ありがとうございます、お二人はメドレーに入社してどのくらい経ちますっけ?</p>
<p><strong>平山</strong>: 私は 3 年半ですね。</p>
<p><strong>岡村</strong>: 私は 4 年経ちました。</p>
<p><strong>新居</strong>: もうそんなに経つんですね。みなさんの前職を転職しようと思った動機とはどんなものだったんですか?</p>
<p><strong>平山</strong>: 前職でプレイングマネージャとして業務をしてきましたが、キャリアを重ねてくると徐々にマネジメントの割合が増えてきます。それ自体は自然なことではあるのですが、前職の延長線上でマネジメントに比重を置くことに対して、危機感を感じていたんです。手を動かす部分を含めて、エンジニアとして良いキャリアを積むには別の環境に身を置く必要があると思い、転職を決意しました。</p>
<p>転職に際しては、自分が<strong>身近に感じるサービスで特に自分の家族が関わるような領域が良いと考えており、医療はそうした意味でぴったりな領域</strong>でした。これらの条件で転職エージェントを通じてメドレーに辿りつきました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note0630_003.jpeg","alt":"","title":"平山さん","index":0}"></p>
<p><em>平山さん</em></p>
<p><strong>岡村</strong>: 先程も少し話しましたが、前職は自社プロダクトと他社向けコンサルティングを並行して運営していた会社でした。しかし、途中から経営方針が変わってコンサルティング業務をメインに据えるようになったんです。自分としては、自社プロダクトを作っている点を大事に考えていたため、転職を考えはじめました。</p>
<p>会社選びの軸としては、もちろん自社サービスが事業の主軸である会社というのが一番でした。他の条件としては「上場一歩手前」くらいのフェイズの会社というものがありました。これは、そのくらいのフェイズの会社であれば今までやっていた事業をいきなりピボットするという可能性が限りなく低いのではないかと考えたからです。そんな感じで会社を探していて、LinkedIn の広告経由でメドレーにアプローチして入社しました。</p>
<p><strong>新居</strong>: 岡村さんは次の会社の事業領域は、意識していたんでしょうか?</p>
<p><strong>岡村</strong>: 医療領域を名指しで考えていたわけではないです。しかし、いわゆる<strong>昔ながらの慣習などが多く残るような業界や領域を、自分達が手がけたサービスで変えていくというのが良い</strong>という考えでした。これは前職でも考えていたことだったので、引き続き志向していました。</p>
<h1 id="メドレー入社後にどのような仕事をしていたのか">メドレー入社後にどのような仕事をしていたのか</h1>
<p><strong>新居</strong>: ありがとうございます。そんな経緯でメドレーに入社したお二人ですが、入社後はどのような業務をされていたんでしょうか。</p>
<p><strong>平山</strong>: 私は入社後から一貫して CLINICS カルテを担当しています。入社後は医療に関する業務知識を身につけながら、周辺業務から徐々に関わっていきシステム面のキャッチアップをしていきました。一番最初に携わったプロジェクトとしては、CLINICS カルテにおける診察業務のコントロールを担う「診療ステータス」の拡張を行うという、カルテ全体に影響のある大きな改修に携わりました。それ以降も、小さいタスクを捌きつつ、中・大規模な施策に携わってきた感じです。</p>
<p>そこから医療 PF 横断のプロジェクトにも関わっていくようになりました。CLINICS というプロダクトは患者が使用するアプリを始め、調剤・歯科のサービスも提供しています。<strong>医療 PF 全体の体験設計を踏まえて、周辺プロダクトとも積極的に関わっていく必要がある</strong>という点は特徴的だと思います。</p>
<p><strong>岡村</strong>: 私は入社当時は「社長室コンサル」という役割で入社し、この部署は M&A などをメインに担当していた部署だったんです。そのため最初にアサインされたのが NaCl メディカル社(現在はメドレー本体に統合)の業務面での PMI でした。これと平行して、現在も引き続き携わっている新しいレセコンを作るプロジェクトのプロダクトマネージャを担当してきました。</p>
<p><strong>新規レセコンは CLINICS カルテと連携しているプロダクトなので、平山さんが率いるチームと連携</strong>しながら、このプロダクトを作っている開発チームを率いています。</p>
<h1 id="gm-としてのミッションと就任してから変わったこと変わらないこと">GM としてのミッションと就任してから変わったこと・変わらないこと</h1>
<p><strong>新居</strong>: 話を聞いていると、お二人とも最初の仕事を順調にスケールアップしていったという形で現在の業務に繋っているんですね。さて、ここからは GM に就任してからの自身の業務がどのように変化してきたのかという点を聞きたいのですが、この点どうでしょうか。</p>
<p><strong>平山</strong>: ミッションはやはり変化しました。GM としてのミッションで一番求められるものは「<strong>プロダクトを前進させる</strong>」という部分と考えています。自身の管掌組織は当然ですが、組織を横断する形の取り組みも必要になります。</p>
<p>「ピープルマネジメント」という点も GM としての変化の1つかなと思います。とはいえ、先ほどの話と通じる部分はあり**「プロダクトに軸足を置いて目線を合わせていくこと」が基本**となります。また、組織上の持続可能性という意味においても「任せていく」というところは大事にしています。メンバーに優秀な方が多いので助けられている部分は多いと感じます。</p>
<p>エンジニアリングという意味では、手を動かす量は減りました。手の動かし方という意味では入社当初から横の動きを見るようにはしていたので GM になって変わったという部分は無いですね。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note0630_008.jpeg","alt":"","title":"GM","index":0}"></p>
<p><strong>岡村</strong>: レセコン新規開発プロジェクトでの GM のミッションとして、「プロジェクトの完遂」、「チームメンバーのピープルマネジメント」、「Tech Studio MATSUE(島根県にあるメドレーの拠点。レセコン新規開発プロジェクトの開発をしている)の管理」がメインになります。</p>
<p>GM に就任したタイミングは NaCl メディカル社がメドレー本体に吸収合併されたというタイミングでした。就任前はグループ会社ということもあり、開発業務を受発注するという動きに近い感じで協業してきました。GM になってからは、同じ会社になったのでより近い距離でチーム開発をするようになったり、メンバーのピープルマネジメントの割合が多くなるという変化がありました。</p>
<p>担当領域としてはレセコンのプロダクトマネジメントをしているという部分は変わらない部分です。また平山さんがおっしゃっていたように、いつまでも自分が上にいて指揮するという状態を良しとするのではなく、<strong>メンバーの人をどんどんと上のロールに引き上げていくというのが GM の役割の 1 つ</strong>かと思うので、そういう意識でメンバーのマネジメントをしています。</p>
<p>エンジニアリングとマネジメントの比率はプロダクトのフェイズによって変わってくるし、変わるべきかなと思っています。現在は 8 対 2 でマネジメントの比率が多くなっています。一方でプロダクトの初期でどんどんと開発をしていかないといけないという場合には自分も含めて人手をかけて開発していくようにします。</p>
<p><strong>新居</strong>: お話をうかがうと、やはり GM 前よりもマネジメントの比率が多くなっているのではないかと思うのですが、葛藤などはありましたか。</p>
<p><strong>平山</strong>: 元々の自分のスタンスとして「自分も駒の一つ」としてプロダクト開発を推進していくという思考があります。GM の話が来たときにも、やれるかなという不安感はありましたが、今の場面においては自身がやった方が良いと感じて受けました。</p>
<p><strong>岡村</strong>: 私は出自がエンジニアではないというのもあって、葛藤のようなものも平山さんとは違って、特にありませんでした。元々 NaCl メディカル社だった松江オフィスとのハブになるのは、自分しかいないと思っていましたので躊躇のようなものも全く無かったです。</p>
<h1 id="gm-で苦労したポイントどう克服していたか">GM で苦労したポイント・どう克服していたか</h1>
<p><strong>新居</strong>: ありがとうございます。では、GM になって苦労したとか大変だったというところってどんなところがありましたか。</p>
<p><strong>平山</strong>: 自分は口下手な方なんで、以前より喋る機会が増えたのが困ったところですね(笑)</p>
<p><strong>新居</strong>: 平山さんに口下手な印象は全然ありませんが(笑)</p>
<p><strong>平山</strong>: 他には、これはどちらかというと PdM 属性の仕事にも関わってくるんですが、開発計画の策定は大変です(メドレーでは 3 ヶ月ごとに開発計画を立てている)。</p>
<p>大規模な施策の実施と並行して優先度の高いタスクの差し込みを踏まえて、優先度の再設計や施策のフェージングや運用の検討したりしています。<strong>自分で抱え込む必要はないですが、関係者と調整しつつも最終意思決定は自分になるので、様々な葛藤をしながら取り組んでいます</strong>。</p>
<p><strong>新居</strong>: ピープルマネジメント面では何かありましたか。</p>
<p><strong>平山</strong>: 先程お話した「プロダクトに軸足を置いて目線を合わせていくこと」を大切にコミュニケーションを実施し、日々の業務を通してメンバー一人一人でシナジーが出る・良いキャリアが重ねられるようにしたいと考えていますが、先ほどの開発計画の難しさもここにあります。</p>
<p><strong>新居</strong>: ありがとうございます。では岡村さんはいかがでしょう。</p>
<p><strong>岡村</strong>: 平山さんに全く同感ですね(笑)</p>
<p>冗談は置いておいて、計画外の差し込みタスクなどが入ると、どうしてもチームの負荷が高くなってくるので、そこをコントロールしきれずに申し訳ないなと思うことがあります。</p>
<p>他には、メンバーのキャリア形成をどうするかという点が難しいと思います。<strong>メンバーの志向に合わせての将来像を見据えて現在の業務に取り組んでもらいたい</strong>のですが、実際のタスクとそうしたキャリアの方向性が必ずしもぴったりと合うときばかりではないので、ここをどう揃えていくかという点にも苦労はしています。</p>
<p><strong>新居</strong>: なるほど、岡村さんの場合は元々として島根と東京という立地間でのマネジメントをされていたと思うんですが、リモートコミュニケーションが中心という環境での苦労は何かありましたか。</p>
<p><strong>岡村</strong>: 実はこれと言って無いんですよ。それでもちゃんと上手く機能している要因としては、2 つあります。1 つは島根の方に現地のメンバーを取りしきってくれるスキルを持ったメンバーが居てくれたことです。ここは今でも本当に助かっています。</p>
<p>また、当初から技術的な部分も含めて私もメンバーと一緒に勉強しながら、新しい取り組みをするというスタンスを持ってプロジェクトを推進できたことです。ですので、離れたところから命令する人のような感じではなく、<strong>チームで一緒にプロジェクトを進める人という認識を持ってもらえた</strong>んじゃないかなと考えています。</p>
<p>もちろん、関係値を作れるまでは私が最初の頃は 1 ヶ月に 1~2 回程度島根に出張して、実際にメンバーと会ったりすることもしていましたが。</p>
<h1 id="gm-を担う上で役に立った経験知識">GM を担う上で、役に立った経験・知識</h1>
<p><strong>新居</strong>: ここからは、お二人が今までの業務の中で積んできた経験・知識の内、現在 GM として活動する上で役に立っているものとして、どんなものがあるのか聞ければと思います。</p>
<p><strong>平山</strong>: そうですね、一番役に立っているなと感じているのは前職で経験した「<strong>様々なステークホルダーとの主体性を持った折衝経験</strong>」です。色々な会社や立場の方とコミュニケーションを取ることが多かったんですね。専門性もそれぞれ違う方達とお話して物事を進める経験ができたことは、今の GM という役割に本当に役立っていると思います。</p>
<p><strong>岡村</strong>: 私が役に立っていると感じる経験は、「<strong>一回深く細かいところまで業務を見てみて理解をしてから、改めて俯瞰した視点で業務を見直してメンバーにお願いする</strong>」という習慣です。この相反する方法をバランス良くできるようになっているというのが、良い点なのかなと思っています。また、深く見るというのは自分が興味を持っている分野でないと中々難しいので、何にでも興味を持って業務に取り組むという心持ちも必要になるかと考えています。</p>
<p>こうした形で業務を分解してメンバーにお願いできるところはしながら、GM としては適切に権限委譲していくというのに役立っています。</p>
<h1 id="エンジニアのピープルマネジメントで意識していること">エンジニアのピープルマネジメントで意識していること</h1>
<p><strong>新居</strong>: 今までのお話でも少し出ている話題になるのですが、GM としてエンジニアのマネジメントで意識しているポイントって、どんなことがありますか。</p>
<p><strong>平山</strong>: 自分達が作る<strong>プロダクトへの興味関心・解像度を高め、何を作るのか</strong>。という点において各所コミュニケーションを含めて主体的に動くこと。そうした積み重ねの延長線上にあるエンジニアとしてのキャリア形成を意識しています。</p>
<p>この考え方に加えて「プロダクトの持続可能性を高める」というテーマで、エンジニア・デザイナー・ディレクター・ QA エンジニア・カスタマーサクセスといったプロダクトに関わる人達の専門性を踏まえたタスクの再配布・プロジェクト設計を行う。といった体制作りも推進しようとしています。</p>
<p><strong>新居</strong>: 「プロダクトの持続可能性を高める」というのは、すごく難しい試みですよね。</p>
<p><strong>平山</strong>: 確かにとても難しいなと思います。でも打ち手としては色々あるのではないかと考えていて。「属人性の排除」といった点は、分かりやすいのではないかなと思います。誰かに依存した状態は、組織変更や事業スケールの変化に弱くなりがちですよね。</p>
<p>大事なのは「人から組織への移行」「仕組み化」と捉えています。「できる人を増やす」については、大事だけど秘伝のタレが受け継がれるだけになってしまうので、持続可能性の観点では本質的ではないかなと。</p>
<p>一方で「仕組み化するための計画的な属人性」については推奨できると考えてます。新しいことをいきなり「組織に転換」や「人を増やしてローテする」だと、ナレッジが蓄積されづらく、次の一手が打ちづらくなるので。</p>
<p><strong>新居</strong>: そうした取り組みをするのに、例えばエンジニアだと作ってみたものがプロダクトの目指す方向性とズレが生じていたというような事態があると思うんですが、そうしたズレを事前に仕組みで抑えるような事をしているんですか。</p>
<p><strong>平山</strong>: きっちりとした仕組みなどは作ってはいないです。仕組み化というよりも、そうならないように<strong>メンバーから上がってきた疑問などに対する壁打ちなどは気軽にできる</strong>ようにしています。また、そういった相談を気軽にできるような自分自身のプロデュースといいますか、キャラ作りみたいなものも含めてメンバーが相談しにくいという空気を作らないようにはしていますね。</p>
<p><strong>新居</strong>: ありがとうございます。岡村さんはどんな意識をしていますか。</p>
<p><strong>岡村</strong>: やっぱり平山さんに全く同感ですね(笑)</p>
<p>付け加えると、ソフト面ですが<strong>メンバーであるエンジニアが楽しめる環境作り</strong>というところを意識しています。楽しさにも色々な種類があるとは思いますが、単にコーディングするだけではなく、やはりプロダクトそれ自体の提供価値というものをしっかりと共有して、プロダクトを作ること自体に楽しさを感じてもらう…というのは重要じゃないかなと思っています。</p>
<p>また楽しさの別側面ですが、新しいテクノロジーなどもどんどんと取り入れることができるような仕組み作りもしています。島根のメンバーは今回のプロジェクトで初めて Ruby を使うというメンバーも多かったので、ここで楽しさを感じられないと、こうしたテクノロジー自体を嫌いになってしまう恐れがあるなと思ったので。</p>
<p>そうした「楽しい仕事」にしていくと「時間をかけなきゃ」ではなく「時間をかけたい」という感じで取り組めるようになると思います。</p>
<h1 id="gm-という役割の醍醐味とは">GM という役割の醍醐味とは</h1>
<p><strong>新居</strong>: それでは終盤になってきましたが、GM の「やりがい」や「醍醐味」はどういったものになりますか。</p>
<p><strong>岡村</strong>: 一番は「ピープルマネジメント」になるんじゃないかと考えています。最近、岸田首相の所信表明演説で聞いて感銘を受けたアフリカの諺ですが「早く行きたいなら 1 人で行け、遠くへ行きたいならみんなで行け」という言葉はピープルマネジメントの本質なんじゃないかなと思っています。<strong>メンバーと一緒に遠くに行くために組織作り</strong>などをしていけるのが醍醐味だと感じています。</p>
<p><strong>平山</strong>: メンバーの<strong>個人スキルを高めつつ「チーム」に還元してもらうための仕組み</strong>を作っていく。というのは、醍醐味と言えるのかなと。CTO インタビューで田中も言っていますが「チームバリュー」は大切だと考えています。</p>
<p>チームといっても、狭義のチームではなくプロダクトに関わる関係者全員を含めたチームという感じで意識しています。開発組織に閉じないバリューの発揮がメンバーそれぞれでできると心強いと思います。</p>
<h1 id="メドレーで-gm-やリードエンジニアのロールに向いている方">メドレーで GM やリードエンジニアのロールに向いている方</h1>
<p><strong>新居</strong>: 最後になるのですが、メドレーで GM やテックリードといったロールに向いているのは、どんな人だと思いますか。</p>
<p><strong>平山</strong>: 基本的に<strong>物事を前に進めるための解像度を高く描ける人</strong>かなと思っています。何を誰のためにというのを体現しつつ、関係者を引っぱっていける人ですね。メドレーの <a href="https://www.medley.jp/team/culture.html">Our Essentials</a> 全般必要だとは思いますが、その中でも「成果を出す」「自分をアップデート」「組織水準を高める」「革新と改善を主導」というところが必要になるかな。困難な状況でも結果を出しつつ、自分の行動も高めていき、結果組織・プロダクトを良くしていける…というのがメドレーのリードと呼ばれているエンジニアかと思います。</p>
<p>やり方としては、マイクロマネジメントではなく、基本となる考え方をちゃんと伝えてメンバーに学んでもらう…という、やり方ができる人が向いていると思います。</p>
<p><strong>岡村</strong>: <strong>困難な状況であっても何とかして前に進めることができる人</strong>でしょうか。無理矢理に進めるというわけではなく、状況の整理や色々な部署との調整などもしたりして、何とか結果という形にしていける能力がある人がリードとしてメンバーを引っぱっていける人だと思います。</p>
<p>もちろん、フェイズによってメンバーとの関わりなどはちゃんと調整していける柔軟さなども必要になると思います。</p>
<p><strong>新居</strong>: なるほど。本日は色々なお話をしていただきまして、ありがとうございました!</p>
<h1 id="さいごに">さいごに</h1>
<p><img __ASTRO_IMAGE_="{"src":"./note0630_010.jpeg","alt":"","title":"集合写真 2","index":0}"></p>
<p>メドレーの GM 二人に仕事について聞いていきました。</p>
<p>前回の CTO インタビューでも語られた開発組織の話ですが、より現場に近い GM という立場のメンバーからの話でまた違った雰囲気などを感じていただけたかと思います。</p>
<p>このような GM を「ぜひやりたい!」という方や「こんな上司の元で自分の力を発揮していきたい!」という方はぜひカジュアルにお話ができればと思います。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- 新体制になったメドレー開発組織について CTO 2 人に語ってもらいましたhttps://developer.medley.jp/entry/2023/04/28/022245https://developer.medley.jp/entry/2023/04/28/022245はじめに
みなさん、こんにちは。技術広報・エンジニアの平木です。
既に 2 月に発表されていますが、4 月より弊社の経営体制を大幅に変更しました。開発組織について大きく変わった部分として CTO 2 名体制になった点があります。そこで、なぜ...Thu, 27 Apr 2023 17:22:45 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。技術広報・エンジニアの平木です。</p>
<p>既に 2 月に<a href="https://ssl4.eir-parts.net/doc/4480/tdnet/2238569/00.pdf">発表</a>されていますが、4 月より弊社の経営体制を大幅に変更しました。開発組織について大きく変わった部分として CTO 2 名体制になった点があります。そこで、なぜ CTO を 2 名にしたのかや、これからのメドレーの開発組織についてを CTO になった 2 人にインタビューしました。</p>
<h1 id="インタビュイー紹介">インタビュイー紹介</h1>
<h2 id="稲本さん">稲本さん</h2>
<p>執行役員。人材プラットフォーム(以下、人材 PF)CTO。独立系 SIer でのインフラエンジニアに始まり、インターネット企業での様々なサービスのインフラ構築を経て、音楽配信サービスやインターネットラジオサービスのサーバサイドエンジニアとして従事。2014 年メドレー入社後は創業時から運営しているジョブメドレーのプロダクト開発に従事。その後、同サービスのリードエンジニア、開発責任者を経て、2023 年 4 月より現職。</p>
<h2 id="田中さん">田中さん</h2>
<p>執行役員。医療プラットフォーム(以下、医療 PF)CTO。独立系 SIer でのアプリケーションエンジニアや IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとしてソーシャルゲームや動画サービスなどの開発立ち上げに従事。2016 年メドレー入社後は CLINICS カルテの立ち上げを経験。その後 CLINICS 開発責任者を経て医療 PF プロダクト横断基盤の立ち上げ・開発責任者を経て 2023 年 4 月より現職。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note04_003.jpeg","alt":"","title":"集合写真","index":0}"></p>
<p><em>左から田中さん、稲本さん</em></p>
<h1 id="cto-を-2-名体制にして開発組織の体制をアップデートした理由">CTO を 2 名体制にして開発組織の体制をアップデートした理由</h1>
<p><strong>平木</strong>: さっそくですが、2023 年 4 月よりお二人がそれぞれ人材 PF と医療 PF の CTO に就任されて、改めて開発組織のアップデートがされましたが、どういった背景でこの体制になったんでしょうか。</p>
<p><strong>田中</strong>: 会社全体の組織設計として医療 PF と人材 PF に分かれたというのがまず最初にあったんですが、当初、開発組織は両 PF を横断する形で 1 人の CTO が見ていました。各 PF 内にそれぞれプロダクトがあり開発もプロダクトごとにチームを分けていましたが、それぞれの開発チームが有機的に動きつつも組織全体の小回りの効きやすさなども考えて、こうした体制になっていました。</p>
<p>ただ、時間が経つに連れ段々と開発チームも各 PF 自体 の規模も大きくなってきたので、 1 人の CTO ではマネジメントが難しくなってきました。また医療 PF と人材 PF で共通する開発組織の文化などはありますが、扱っている事業内容の差異から選定技術やプロジェクトの進め方などの違う部分も出てきたので、それぞれの PF で CTO を置いて組織面・技術面の課題に対応しようということになったためです。</p>
<p><strong>稲本</strong>: 開発組織の人数としては 100 名を越えているのですが、それだけではなく就業場所の違いやサービスの多様化など、それぞれの PF で特性が違う課題がこれから先も出てくるだろうということで、先を見据えての開発組織のアップデートの一環として CTO 2 名体制にしたというところですね。</p>
<p><strong>平木</strong>: これからの組織の変化を見据えた動きだということですね。</p>
<h1 id="新しい開発体制になって変わっていく部分変わらない部分">新しい開発体制になって変わっていく部分・変わらない部分</h1>
<p><strong>平木</strong>: 新体制になってまだ日が浅いですが、これから変えていく部分というのはどういったところになっていくんでしょうか。</p>
<p><strong>稲本</strong>: 自分達 2 人が持っている色々な役割を分解していって他のメンバーへ役割を委譲していくことですね。CTO が色々持ちすぎると組織のスケールに対しボトルネックになることは目に見えているので。</p>
<p>それ以外には、役割を担ったチームやメンバーが自身で思考し行動しやすい組織にしていくことで、更なるチャレンジがしやすかったりパフォーマンスを発揮しやすい状態にしていけたらと考えています。</p>
<p><strong>平木</strong>: なるほど。田中さんは何かありますか。</p>
<p><strong>田中</strong>: 「役割の明確化」もその 1 つですが、将来取り組むべき課題に向けて中長期を見据えた開発組織の設計や運営に向き合うフェイズになってきたと感じており、強化していきたいと考えています。</p>
<p><strong>平木</strong>: 将来の課題に対して技術・組織なども先手を打っていくイメージですね。</p>
<h1 id="新体制での開発組織のミッション">新体制での開発組織のミッション</h1>
<p><strong>平木</strong>: それでは新体制になって改めて開発組織のミッションとしてはどういったものがありますか。</p>
<p><strong>田中</strong>: 会社全体のミッションと基本は一緒ではあります。「医療」という大きい領域の課題に対して、何のために開発をするのかや、その開発をすることによってどういった顧客価値を提供できるのかというところは、ちゃんとミッションに紐付いている部分なので変えずにいきます。</p>
<p>その上で、有効な施策をどれだけ素早くデリバリーできるかというところには今以上に注力していきたいところです。そのためには無駄を省き、やるべき事に集中してより生産性の高い開発を目指します。</p>
<p>プロダクト開発において無駄な機能開発を行なうと、後からそこを削ろうと思っても難しいですし、作った結果誰も幸せにならないということになってしまいます。こうならないためにもスピード感は落とさないで、事業側メンバーと協力しながら適切な要件整理をしたりなど、どうやって顧客へ適切なプロダクトを届けるか?という部分により重きを置いていきたいです。</p>
<h1 id="事業とプロダクト開発の関係性">事業とプロダクト開発の関係性</h1>
<p><strong>平木</strong>: さて、次に先程のお話にも出てきましたが、事業とプロダクト開発の関係性についてお聞きします。お二人が考えるこの関係性はどのようなものでしょうか。</p>
<p><strong>田中</strong>: ここも以前から変わらないスタンスですが、改めて定義するなら「テクノロジーを最大限に使って医療領域の課題を解決する」という関係になります。テクノロジーとメドレーの事業である医療領域の課題解決という 2 つの側面のどれが欠けても、プロダクトとして良いものは生まれないと考えています。</p>
<p><strong>平木</strong>: 主に Web のテクノロジーを適材適所で課題解決のために使っていくというスタンスですね。</p>
<p><strong>田中</strong>: そうですね。先に来るのはあくまでも、「医療領域の課題解決をする」という部分になるのですが、その手段として必要な技術は全部使っていこうという姿勢です。なので、まずは開発組織のメンバー一人ひとりが、ドメインの理解をするということが始めの一歩です。そうして課題の本質を見極めて解決に必要な手段は何があるのかを考えていく必要が出てきます。</p>
<p>解決のために最適であれば、その技術の新旧や使用実績などを問わず取り入れて開発していくという心構えですね。そのためにはきちんと技術動向を追っていき日頃からユースケースなどを想像しながら、その技術を触ったりする必要も出てきます。</p>
<p><strong>稲本</strong>: ドメイン知識とテクノロジーを最大限に駆使し、プロダクト開発を通して価値を届けるというのが我々のミッションなのかなと思います。</p>
<p><img __ASTRO_IMAGE_="{"src":"note04_002.jpeg","alt":"","title":"稲本さん","index":0}"></p>
<p><em>稲本さん</em></p>
<p><strong>平木</strong>: 医療領域の未来を変えていくために、技術を最大限に用いたプロダクトドリブンであることが大切ということですね。</p>
<p><strong>田中</strong>: メドレーではそれだけじゃない部分も意識していますね。一言で「プロダクト」と言ってもサービスとそれを作る開発者というだけではなく、顧客接点を持つ事業部などのメンバー全ての動きも合わせて「プロダクト」という意識を持っています。</p>
<p><strong>平木</strong>: なるほど、プロダクトに関わっている人全員を合わせて「プロダクト」だよということですか。</p>
<p><strong>田中</strong>: 例えば開発メンバーが良いプロダクトを提供したとしても、そのプロダクトを使ったユーザーが疑問に思った部分があったとします。そこで問い合わせをしたときに、その体験が良くない場合はやっぱり「プロダクトに関する体験が良くない」という印象になってしまいますよね。これはセールスなど他の領域でも起き得る話ですが、メドレーではそういった部分も含めて「プロダクト開発」という意識を持っているので、他部署だからということではなくトータルでユーザーに価値を感じてもらえるようにしていくということを大切にしています。</p>
<p><strong>平木</strong>: 確かにユーザーはそういった関わるもの全部一緒に「プロダクト」と思いますもんね。</p>
<p><strong>稲本</strong>: 日々の業務の中で直接ユーザーと対話してくれている方々がたくさんいます。</p>
<p><a href="https://www.medley.jp/team/culture.html">Our Essentials</a>(以下、OE)で特に好きなものの一つで「信頼を獲得する」というものがあります。OE 自体は主に社内向けのメッセージにはなるんですが、日々の業務の中で他者からの信頼を得ることが出来るような振る舞いや成果を出すこと、その信頼の積み重ねがプロダクト全体にも反映され、結果としてユーザーからも信頼される/価値を感じてもらえるプロダクトの提供につながっているのだと感じています。</p>
<h1 id="プロダクトと技術の関係について">プロダクトと技術の関係について</h1>
<p><strong>平木</strong>: もう少し開発についての話を掘り下げていこうと思います。弊社では現在のところ Ruby や Ruby on Rails(以下 Rails)で作られているプロダクトが大半ではありますが、Go や Node.js などをメインに使っているプロダクトもありますよね。そうした技術選定のときの基本姿勢としてはどういったものがありますか。</p>
<p><strong>田中</strong>: 基本として、「それぞれのプロダクトにとって現時点でこの技術で開発するのが、良いことだよね」というのを大切にしています。組織的に知見が多いということもあって、Rails を使っているプロダクトが多いのはそうなんですが、この技術じゃないとダメという縛りがあるわけではないです。Go や Node.js
にしても、そのプロダクトに現状最適だという視点で技術選定をしています。</p>
<p><strong>稲本</strong>: 今までできなかったことが、新しい技術を使ったら解決するのにという場面も結構あると思います。そういうときに「今使ってないから…」というのではなく、その技術を使ったほうがプロダクトにプラスになると判断したら使っていくようにしています。</p>
<p><strong>田中</strong>: ですので、今プロダクトで使っている技術で守っていくというより、個々人でアンテナを張って新しい技術は積極的にキャッチアップしていきながら、チーム内で「これ使うと良さそうだからやっていこう」という感じですね。</p>
<p><strong>平木</strong>: 先程も出てきたユーザーに価値提供を素早くするという要素の一つということですね。そうすると、例えば開発者体験を良くするような技術やフローなんかを取り入れるというのも、そうした志向の一部ということになりますか?</p>
<p><strong>田中</strong>: そうですね。良いものを早くユーザーに届けるという一側面になります。開発者体験を良くするというのが最終的な目的ではなくて、その結果としてチームの生産性が上がるのなら体験を良くすると良いよねという。プロダクトコードの負債解消とかもそうですが、バランスが確かに難しいのですが、こういったところを疎かにすると結果として顧客への十分な価値提供ができなくなってしまうリスクもあるので、短期と中長期できちんとバランシングしつつ開発に取り組んでいきたいと思っています。</p>
<p><img __ASTRO_IMAGE_="{"src":"note04_001.jpeg","alt":"","title":"田中さん","index":0}"></p>
<p><em>田中さん</em></p>
<p><strong>平木</strong>: 改めてここで現在の開発体制について伺っていければと思います。</p>
<p><strong>田中</strong>: 人材 PF も医療 PF もプロダクトに紐付いて複数のチームに分かれていて、チームの人数規模としてみると多少の上下はあるんですが、プロダクトマネージャ(以下、PdM) やデザイナー、エンジニアを合わせて 10 人前後というチームが多いです。</p>
<p><strong>平木</strong>: そうしたチーム内で、特にエンジニアはどのような役割分担をしていることが多いですか?</p>
<p><strong>稲本</strong>: もちろんチームによっても違ってくるんですが、チームの中に PM と TL を置いて、そのチームの中にメンバーが数人いる形が多いですね。この単位をベースにして toC 向けのプロジェクト、toB 向けのプロジェクトのような感じでチームを分けて開発を行なっています。</p>
<p><strong>田中</strong>: メンバー個々人はサーバサイドやフロントエンド、インフラのような得意領域がありそこを考慮した開発をしていっていますが、サーバサイドだけやる人という形ではなく、得意領域と隣り合う領域についても意識し、可能な限り理解してコラボレーションしていこうというやり方を取っています。これによりコミュニケーションコストが下がることで生産性高く、品質向上にも寄与するというのが理由になります。</p>
<p>もちろんプロとして自分の得意分野で力を存分に発揮はしてもらうというのは大前提ですが、その強みを周囲のメンバーに還元しつつ、得意領域以外はそこが得意なメンバーから還元してもらいながら、良いプロダクトを作っていくというのがメドレーの開発組織のスタンスになります。</p>
<h1 id="開発組織の雰囲気やメンバーの働き方について">開発組織の雰囲気やメンバーの働き方について</h1>
<p><strong>平木</strong>: 今まで開発組織の制度的な側面を中心に聞いてきましたが、組織のソフト面についてお聞きします。メドレーの開発組織の雰囲気ですが、どんな雰囲気だったりしますか?</p>
<p><strong>稲本</strong>: 全体的にはわりと和やかな雰囲気は持ちつつも、各自のバリューに対してはストイックな感じかなと思います。</p>
<p>自分たちで決めた期日など守るところはしっかり守ろうという意識を持ちつつ、無理な期日になりそうであればスケジュールやスコープを調整したり、プロジェクトの進め方で上手く行ったこと/行かなかったことを振り返りを通して改善を図ったりするなど、当たり前のことを高い水準でやり遂げていく習慣が根付いていると思います。</p>
<p>コミュニケーションに関しては、非同期コミュニケーションがベースではあります。口頭での相談なんかももちろんしますが、認識がずれないように話したことを issue などに残したりしてます。 雑談みたいなものはミーティングの後半パートにあえて雑談パートを作るなどしていますが、業務中にずっとするようなことはないですね。開発中はそれぞれが集中していることが多いです。</p>
<p><strong>田中</strong>: チームごとに違いますが、朝会や夕会などでちゃんとコミュニケーションが取れるようにしているということが多いですね。なので、あんまり他の時間に雑談みたいなものが必要ないという感じです。</p>
<p><strong>平木</strong>: 和やかながらも、締めるところは締めるという雰囲気ですね。開発チームの皆さんはどんな働き方をしている人が多いですか?</p>
<p><strong>稲本</strong>: これも個々人で違っていますが出社とリモートのハイブリッドな方が多いかと思います。</p>
<p>やはり開発に集中したい場面ではリモートの方が割り込みも少なくなりやすく集中しやすいですし、物事の認識を揃えたり決めていく場合は、ミーティングなどで実際に顔を合わせた方が効率も良いことはあると思いますね。</p>
<p><strong>田中</strong>: 自分のバリューを最大化できるように働いていこうというのが基本になっています。</p>
<p><strong>平木</strong>: 各自ちゃんと考えてプロダクトに貢献できるようにということですね。そうした働き方の先にキャリアパスがあると思いますが、その観点ではどのようなパスがありますか?</p>
<p><strong>田中</strong>: 大きく分けては、マネジメントをメインにするか、テクニカル部分を特化させたスペシャリストとなるかという 2 つになります。役割分担にもちょっと関わりますが、内容としては、例えば PdM を目指していくというのも道としてはありますし、自分の得意領域を最大限に伸ばしてテックリードとして技術をもってプロダクトを引っぱっていくという道なんかもあります。1on1 や評価などでそうした部分は本人と刷り合わせをしていきながら決めていく形になります。</p>
<p><strong>平木</strong>: 評価のお話が出てきましたが、どういった観点で評価されますか?</p>
<p><strong>稲本</strong>: ベースとしては会社として決めた OE に沿ったものになります。OE を踏まえつつ、業務目標や技術的な目標を立ててそれができたかどうかが基準になりますね。</p>
<p>立てた目標が色々な理由で達成できなかったというケースももちろん出てくると思いますが、内容として自分はどういった部分は頑張ったとか、どういう理由で達成できなかったなどの自分なりの説明ができるかというのを重視しています。</p>
<p><strong>田中</strong>: 目標はチームバリューにいかに寄与したかを重視しています。OE に基づきチームとして達成しないといけない目標があってそれをブレイクダウンした上で、自分が寄与できるのはどこかという感じで目標にしてもらいます。一見目に見えにくい動きだったとしても、ちゃんとチームに貢献しているねとなれば、もちろん評価の対象になりますし、逆に例えばいくら頑張って新技術を習得したとなってもチームに貢献してなければ評価はされにくいです。</p>
<p><strong>平木</strong>: ありがとうございます。最後にメドレーで開発することの良さを教えてもらえればと思います。</p>
<p><strong>田中</strong>: メドレーは長期を見据えて課題に取り組んでいる会社であり、泥臭く地道に積み重ねなければいけないこともすごく多いです。そのため短期で結果が見えづらいこともあるかもしれません。ですが、医療領域でどっしりと腰を据えて長期的に本質を捉えた開発をしているため、技術面だけではなくプロダクトデザインとして何のために、どのようなアプローチが適切か、という思考力や設計能力も身に付きやすいのではないかと思います。</p>
<p><strong>稲本</strong>: 長期で課題に取り組むとなると「同じことの繰り返しではないか」と感じる方もいるかもしれませんが、プロダクトが成長すればフェーズも変わりますし、取り組むことにも変化が生じていくので成長や変化を楽しめる人には合っているのではないかと思います。</p>
<h1 id="さいごに">さいごに</h1>
<p>新体制で CTO 2 人にメドレーの開発組織について色々と語ってもらいました。</p>
<p>2 人も話をしていましたが、メドレーでは長期思考・未来志向の考え方をベースに、ユーザーの本質的な課題はなにか、どうすればそれらを解決できるか?という部分にフォーカスを当てて開発をするという志向がとても強いと思います。</p>
<p>そうした環境でじっくりと確実にユーザーに価値を提供できるという仕事だと思いますので、ご興味を持たれた方はぜひカジュアルにお話をさせていただければと思います!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- Tanstack Query を活用したフロントエンドアーキテクチャの紹介 ~ 持続可能な開発を目指して ~https://developer.medley.jp/entry/2023/03/31/194059https://developer.medley.jp/entry/2023/03/31/194059こんにちは。医療プラットフォーム第一本部プロダクト開発室所属エンジニアの髙橋です。
普段の業務では、医療 SaaS プラットフォーム CLINICS の医療機関向けアプリケーション(以下、CLINICS)の開発を担当しています。
CLINI...Fri, 31 Mar 2023 10:40:59 GMT<p>こんにちは。医療プラットフォーム第一本部プロダクト開発室所属エンジニアの髙橋です。
普段の業務では、<a href="https://clinics-cloud.com/">医療 SaaS プラットフォーム CLINICS</a> の医療機関向けアプリケーション(以下、CLINICS)の開発を担当しています。</p>
<p>CLINICS では、昨年 10 月頃から React コードベースのリアーキテクチャに取り組んでいます。
その取り組みの 1 つとして、非同期状態管理に関連する実装を Tanstack Query を使って刷新しています。</p>
<p>この記事では、CLINICS における Tanstack Query の活用方法を導入背景と狙いを含めて紹介します。</p>
<p><strong>目次</strong></p>
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
<ul>
<li><a href="#tanstack-query-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">Tanstack Query について</a></li>
<li><a href="#tanstack-query-%E3%82%92%E6%B4%BB%E7%94%A8%E3%81%97%E3%81%9F%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3">Tanstack Query を活用したフロントエンドアーキテクチャ</a>
<ul>
<li><a href="#resource-operation-%E3%81%AE%E5%AE%9F%E8%A3%85">Resource Operation の実装</a>
<ul>
<li><a href="#%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E6%88%90">ディレクトリ構成</a></li>
<li><a href="#cachets">cache.ts</a></li>
<li><a href="#queriests">queries.ts</a></li>
<li><a href="#mutationsts">mutations.ts</a></li>
</ul>
</li>
<li><a href="#viewmodel-%E3%81%AE%E5%AE%9F%E8%A3%85">ViewModel の実装</a>
<ul>
<li><a href="#%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E6%88%90-1">ディレクトリ構成</a></li>
<li><a href="#%E3%82%B5%E3%83%BC%E3%83%90%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88">サーバステート</a></li>
<li><a href="#%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E3%81%AB%E9%96%89%E3%81%98%E3%81%9F%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%9E">フロントエンドに閉じたスキーマ</a></li>
<li><a href="#%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E3%81%AB%E9%96%89%E3%81%98%E3%81%9F%E5%9E%8B">フロントエンドに閉じた型</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#tanstack-query-%E5%B0%8E%E5%85%A5%E3%81%AE%E8%83%8C%E6%99%AF%E3%81%A8%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E3%81%AE%E7%8B%99%E3%81%84">Tanstack Query 導入の背景とアーキテクチャの狙い</a>
<ul>
<li><a href="#%E8%83%8C%E6%99%AF%E3%82%B3%E3%83%BC%E3%83%89%E5%93%81%E8%B3%AA%E3%81%AE%E8%AA%B2%E9%A1%8C">背景:コード品質の課題</a></li>
<li><a href="#%E7%8B%99%E3%81%84-1tanstack-query-%E3%81%AE%E5%B0%8E%E5%85%A5%E3%81%AB%E3%82%88%E3%82%8B%E9%96%8B%E7%99%BA%E4%BD%93%E9%A8%93%E3%81%AE%E5%90%91%E4%B8%8A">狙い 1:Tanstack Query の導入による開発体験の向上</a></li>
<li><a href="#%E7%8B%99%E3%81%84-2%E3%82%B7%E3%83%B3%E3%83%97%E3%83%AB%E3%81%AA%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E5%88%86%E5%89%B2%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92%E5%AE%B9%E6%98%93%E6%80%A7%E3%81%AE%E5%90%91%E4%B8%8A">狙い 2:シンプルなレイヤー分割による学習容易性の向上</a></li>
<li><a href="#%E7%8B%99%E3%81%84-3%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF%E3%82%92%E7%B4%94%E7%B2%8B%E9%96%A2%E6%95%B0%E3%81%A7%E8%A1%A8%E7%8F%BE%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%AB%E3%82%88%E3%82%8B%E5%8F%AF%E8%AA%AD%E6%80%A7%E3%83%BB%E3%83%86%E3%82%B9%E3%83%88%E5%AE%B9%E6%98%93%E6%80%A7%E3%81%AE%E5%90%91%E4%B8%8A">狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上</a></li>
</ul>
</li>
<li><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></li>
<li><a href="#%E3%81%95%E3%81%84%E3%81%94%E3%81%AB">さいごに</a></li>
</ul>
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<a id="tanstack-query-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">
<h1 id="tanstack-query-について">Tanstack Query について</h1>
</a><p><a id="tanstack-query-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">Tanstack Query は、Web アプリケーションのサーバ状態の取得、キャッシュ、同期、更新を簡単に行うことができるライブラリです。<sup></sup></a><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref="" aria-describedby="footnote-label">1</a></p>
<p>類似ライブラリには SWR、Apollo Client、RTK Query 等が挙げられます。<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref="" aria-describedby="footnote-label">2</a></sup></p>
<p>私たちは、機能性・ドキュメントの充実度・コミュニティの将来性を総合的に判断した結果、Tanstack Query(React Query)を採用して React コードベースの再構築を進めています。
導入背景の詳細は、後のセクションで詳しく紹介します。</p>
<a id="tanstack-query-%E3%82%92%E6%B4%BB%E7%94%A8%E3%81%97%E3%81%9F%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3">
<h1 id="tanstack-query-を活用したフロントエンドアーキテクチャ">Tanstack Query を活用したフロントエンドアーキテクチャ</h1>
<p>それでは本題の Tanstack Query を活用したフロントエンドアーキテクチャについて紹介します。
始めに、アーキテクチャの全体像を示した次の図をご覧ください。</p>
<p><img __ASTRO_IMAGE_="{"src":"./architecture.jpg","alt":"Tanstack Query を活用したフロントエンドアーキテクチャ","index":0}"></p>
<p>まず注目して頂きたいポイントは、Backend と View の間にある Resource Operation です。</p>
<p>Resource Operation は Tanstack Query を主軸に実装されているレイヤーです。
役割を大きく分類すると次の 2 つが挙げられます。</p>
<ol>
<li>非同期状態管理に関連する実装の集約</li>
<li>Backend で扱うデータ(OpenAPI スキーマ)と View で扱うデータ(ViewModel)の相互変換</li>
</ol>
<p>全体像を把握するために、左右のレイヤーにも注目してください。</p>
</a><p><a id="tanstack-query-%E3%82%92%E6%B4%BB%E7%94%A8%E3%81%97%E3%81%9F%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3">右の Backend は CLINICS では Ruby on Rails で実装された REST API を提供するモノリシックなサーバです。
API で扱うスキーマは OpenAPI を使って定義しています。
OpenAPI スキーマは、</a><a href="https://github.com/willnet/committee-rails">committee-rails</a> を使ったレスポンス検証と、<a href="https://github.com/OpenAPITools/openapi-generator">openapi-generator</a> を使った API クライアントコードの自動生成に活用しています。</p>
<p>左の View は CLINICS では React で実装されたコンポーネントになります。
View では Backend の OpenAPI スキーマを直接参照することを避け、 ViewModel と呼ばれるフロントエンドで定義したモデル<sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref="" aria-describedby="footnote-label">3</a></sup>のみを参照する設計としています。</p>
<p>Resource Operation は View と Backend の境界レイヤーとして非同期状態管理とデータの相互変換を行うシンプルなレイヤーとなっています。</p>
<p>続いて、上記の運用のための実装詳細を紹介します。</p>
<a id="resource-operation-%E3%81%AE%E5%AE%9F%E8%A3%85">
<h2 id="resource-operation-の実装">Resource Operation の実装</h2>
</a><a id="%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E6%88%90">
<h3 id="ディレクトリ構成">ディレクトリ構成</h3>
<p>Resource Operation レイヤーでは、リソースごとに非同期状態管理とデータの相互変換の処理をまとめています。</p>
<p>ディレクトリツリーで Resource Operation レイヤー全体を表現すると次のようになります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="sh"><code><span class="line"><span style="color:#DCDCAA">src/resourceOperations</span></span>
<span class="line"><span style="color:#DCDCAA">├──</span><span style="color:#CE9178"> resourceTagName</span><span style="color:#6A9955"> # 例: todo</span></span>
<span class="line"><span style="color:#DCDCAA">│</span><span style="color:#CE9178"> ├──</span><span style="color:#CE9178"> cache.ts</span></span>
<span class="line"><span style="color:#DCDCAA">│</span><span style="color:#CE9178"> ├──</span><span style="color:#CE9178"> mutations.ts</span></span>
<span class="line"><span style="color:#DCDCAA">│</span><span style="color:#CE9178"> └──</span><span style="color:#CE9178"> queries.ts</span></span>
<span class="line"><span style="color:#DCDCAA">└──</span><span style="color:#CE9178"> resourceGroupTagName</span><span style="color:#6A9955"> # 例: systemSettings(リソースに階層がある場合)</span></span>
<span class="line"><span style="color:#DCDCAA"> └──</span><span style="color:#CE9178"> resourceTagName</span><span style="color:#6A9955"> # 例: organization(リソースグループ配下のリソース)</span></span>
<span class="line"><span style="color:#DCDCAA"> ├──</span><span style="color:#CE9178"> cache.ts</span></span>
<span class="line"><span style="color:#DCDCAA"> ├──</span><span style="color:#CE9178"> mutations.ts</span></span>
<span class="line"><span style="color:#DCDCAA"> └──</span><span style="color:#CE9178"> queries.ts</span></span></code></pre>
<p>ディレクトリ名の <code>resourceTagName</code> と <code>resourceGroupTagName</code> は OpenAPI スキーマでエンドポイントごとに割り振られている tag を元に命名しています。</p>
<p>ディレクトリごとに 次の 3 つのファイルを定義しています。</p>
<table><thead><tr><th>ファイル</th><th>役割</th></tr></thead><tbody><tr><td>cache.ts</td><td>Query Key の定義、キャッシュ操作のためのカスタムフックの定義</td></tr><tr><td>queries.ts</td><td>useQuery をラップしたカスタムフックの定義、API Request/Response の整形</td></tr><tr><td>mutations.ts</td><td>useMutation をラップしたカスタムフックの定義、API Request の整形</td></tr></tbody></table>
</a><a id="cachets">
<h3 id="cachets">cache.ts</h3>
<p>cache.ts は次のように実装しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#6A9955">// ① Query Key</span></span>
<span class="line"><span style="color:#6A9955">// queries.ts がある場合に必要に応じて宣言</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> todoKeys</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#9CDCFE"> all:</span><span style="color:#D4D4D4"> [</span><span style="color:#CE9178">"todo"</span><span style="color:#D4D4D4">] </span><span style="color:#C586C0">as</span><span style="color:#569CD6"> const</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#DCDCAA"> list</span><span style="color:#9CDCFE">:</span><span style="color:#D4D4D4"> () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> [...</span><span style="color:#9CDCFE">todoKeys</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">all</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"list"</span><span style="color:#D4D4D4">] </span><span style="color:#C586C0">as</span><span style="color:#569CD6"> const</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#DCDCAA"> paginateList</span><span style="color:#9CDCFE">:</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">page</span><span style="color:#D4D4D4">?: </span><span style="color:#4EC9B0">number</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> [...</span><span style="color:#9CDCFE">todoKeys</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">list</span><span style="color:#D4D4D4">(), { </span><span style="color:#9CDCFE">page</span><span style="color:#D4D4D4"> }] </span><span style="color:#C586C0">as</span><span style="color:#569CD6"> const</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#DCDCAA"> detail</span><span style="color:#9CDCFE">:</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> [...</span><span style="color:#9CDCFE">todoKeys</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">all</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"detail"</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">] </span><span style="color:#C586C0">as</span><span style="color:#569CD6"> const</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// ② キャッシュ操作のためのカスタムフック</span></span>
<span class="line"><span style="color:#6A9955">// mutations.ts がある場合に必要に応じて宣言</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> function</span><span style="color:#DCDCAA"> useTodoCache</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> queryClient</span><span style="color:#D4D4D4"> = </span><span style="color:#DCDCAA">useQueryClient</span><span style="color:#D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#DCDCAA"> useMemo</span><span style="color:#D4D4D4">(</span></span>
<span class="line"><span style="color:#D4D4D4"> () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> ({</span></span>
<span class="line"><span style="color:#DCDCAA"> invalidateList</span><span style="color:#9CDCFE">:</span><span style="color:#D4D4D4"> () </span><span style="color:#569CD6">=></span><span style="color:#9CDCFE"> queryClient</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">invalidateQueries</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">todoKeys</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">list</span><span style="color:#D4D4D4">()),</span></span>
<span class="line"><span style="color:#DCDCAA"> invalidateDetail</span><span style="color:#9CDCFE">:</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span></span>
<span class="line"><span style="color:#9CDCFE"> queryClient</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">invalidateQueries</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">todoKeys</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">detail</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">)),</span></span>
<span class="line"><span style="color:#D4D4D4"> }),</span></span>
<span class="line"><span style="color:#D4D4D4"> [</span><span style="color:#9CDCFE">queryClient</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> );</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
</a><p><a id="cachets">① Query Key は、<code>useQuery</code> の <code>queryKey</code> オプションに与える値を定義した定数です。これは後述の queries.ts で利用します。
実装は Tanstack Query メンテナの Dominik さんのブログで紹介されている </a><a href="https://tkdodo.eu/blog/effective-react-query-keys#use-query-key-factories">Query Key factories</a> パターンを使っています。
ディレクトリ名を <code>all</code> の値とすることで、大規模なアプリケーションにおいてもキーの衝突を防ぐことが可能です。</p>
<p>② キャッシュ操作のためのカスタムフックでは QueryClient を利用したキャッシュ操作をまとめています。これは後述の mutations.ts で利用します。
CLINICS では<a href="https://tanstack.com/query/v4/docs/react/guides/optimistic-updates">楽観的更新</a>をしない方針としているため、 <code>invalidateQueries</code> を実行する関数群のみを定義しています。</p>
<a id="queriests">
<h3 id="queriests">queries.ts</h3>
<p>queries.ts は次のように実装しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#C586C0">import</span><span style="color:#D4D4D4"> { </span><span style="color:#C586C0">type</span><span style="color:#9CDCFE"> Todo</span><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">from</span><span style="color:#CE9178"> "@/viewModels/todo"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#C586C0">import</span><span style="color:#D4D4D4"> { </span><span style="color:#9CDCFE">todoApi</span><span style="color:#D4D4D4">, </span><span style="color:#C586C0">type</span><span style="color:#9CDCFE"> GetTodoDetailRequest</span><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">from</span><span style="color:#CE9178"> "@/api/generated"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// ③ queryFn</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> query</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#DCDCAA"> getTodoList</span><span style="color:#9CDCFE">:</span><span style="color:#569CD6"> async</span><span style="color:#D4D4D4"> (): </span><span style="color:#4EC9B0">Promise</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">Todo</span><span style="color:#D4D4D4">[]> </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#D4D4D4"> { </span><span style="color:#4FC1FF">data</span><span style="color:#D4D4D4"> } = </span><span style="color:#C586C0">await</span><span style="color:#9CDCFE"> todoApi</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getTodoList</span><span style="color:#D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#9CDCFE"> data</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">todoList</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#DCDCAA"> getTodoDetail</span><span style="color:#9CDCFE">:</span><span style="color:#569CD6"> async</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">request</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">GetTodoDetailRequest</span><span style="color:#D4D4D4">): </span><span style="color:#4EC9B0">Promise</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">Todo</span><span style="color:#D4D4D4">> </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#D4D4D4"> { </span><span style="color:#4FC1FF">data</span><span style="color:#D4D4D4"> } = </span><span style="color:#C586C0">await</span><span style="color:#9CDCFE"> todoApi</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getTodoDetail</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">request</span><span style="color:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#9CDCFE"> data</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">todo</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// ④ Request Selector</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> request</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#DCDCAA"> getTodoDetail</span><span style="color:#9CDCFE">:</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4"> | </span><span style="color:#4EC9B0">undefined</span><span style="color:#D4D4D4">): </span><span style="color:#4EC9B0">GetTodoDetailRequest</span><span style="color:#569CD6"> =></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4"> === </span><span style="color:#569CD6">undefined</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#6A9955"> // `enabled: false` となる条件の引数が与えられた場合例外とすることで、</span></span>
<span class="line"><span style="color:#6A9955"> // queryFn 内の型の整合性を保つ</span></span>
<span class="line"><span style="color:#C586C0"> throw</span><span style="color:#569CD6"> new</span><span style="color:#DCDCAA"> InvalidRequestParameterError</span><span style="color:#D4D4D4">(</span></span>
<span class="line"><span style="color:#CE9178"> "Required parameter id was undefined when calling request.getTodoDetail."</span></span>
<span class="line"><span style="color:#D4D4D4"> );</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> id</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> };</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span></code></pre>
<p>③ queryFn は、API へのリクエストを責務とした関数群です。
ここで使用している ApiClient や Request の型は openapi-generator から生成しています。
<strong>queryFn の戻り値の型は、後述の ViewModel で定義した型を明示</strong>しています。
このようにフロントエンド側でサーバステートの型を別途定義することで、フロントエンドとバックエンドを分業して実装する際に開発しやすくなるメリットがあります。
省略していますが、レスポンスに応じたエラーの <code>throw</code> もここで行います。</p>
<p>④ Request Selector は View から受け取った値をリクエストパラメータへマッピングすることを責務とした関数群です。
useQuery の条件付き実行を制御する <code>enabled</code> オプションで <code>false</code> となる条件を例外とすることで、queryFn 内で 型ガードをしなくて良い設計としています。</p>
<p>これらを使って View とのインターフェースとなる useQuery ラッパーを定義します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#6A9955">// ⑤ Base Query</span></span>
<span class="line"><span style="color:#6A9955">// API Response を整形せずに返す</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> type</span><span style="color:#4EC9B0"> UseTodoDetailQueryProps</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">QueryResult</span><span style="color:#D4D4D4">> = {</span></span>
<span class="line"><span style="color:#9CDCFE"> id</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4"> | </span><span style="color:#4EC9B0">undefined</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#DCDCAA"> select</span><span style="color:#D4D4D4">?: (</span><span style="color:#9CDCFE">data</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Todo</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#4EC9B0"> QueryResult</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> function</span><span style="color:#DCDCAA"> useTodoDetailQuery</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">QueryResult</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">Todo</span><span style="color:#D4D4D4">>(</span></span>
<span class="line"><span style="color:#9CDCFE"> props</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">UseTodoDetailQueryProps</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">QueryResult</span><span style="color:#D4D4D4">></span></span>
<span class="line"><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#DCDCAA"> useQuery</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> queryKey:</span><span style="color:#9CDCFE"> todoKeys</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">detail</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">props</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#DCDCAA"> queryFn</span><span style="color:#9CDCFE">:</span><span style="color:#D4D4D4"> () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#9CDCFE"> query</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getTodoDetail</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">request</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getTodoDetail</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">props</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">));</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#9CDCFE"> enabled:</span><span style="color:#D4D4D4"> !!</span><span style="color:#9CDCFE">props</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> select:</span><span style="color:#9CDCFE"> props</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">select</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> useErrorBoundary:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">import</span><span style="color:#D4D4D4"> { </span><span style="color:#9CDCFE">selectTodoForm</span><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">from</span><span style="color:#CE9178"> "@/viewModels/todo/todoForm"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// ⑥ Selector Query</span></span>
<span class="line"><span style="color:#6A9955">// API Response を View で参照したいフォーマットに整形して返す</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> function</span><span style="color:#DCDCAA"> useInitialTodoFormQuery</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#6A9955"> // Base Query に select オプションを与える</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#DCDCAA"> useTodoDetailQuery</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> select:</span><span style="color:#9CDCFE"> selectTodoForm</span><span style="color:#D4D4D4">, </span><span style="color:#6A9955">// selectTodoForm は Todo を TodoForm に変換する ViewModel Selector(後述)</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
</a><p><a id="queriests">useQuery ラッパーは、⑤ Base Query と ⑥ Selector Query に分けて定義しています。
CLINICS では同じデータソースに対して画面ごとに異なるフォーマットで表示することが多くあります。
Selector Query で 任意のフォーマットに整形することで、画面ごとに最適化されたデータの取得をスケーラブルに実現しています。<sup></sup></a><a href="#user-content-fn-4" id="user-content-fnref-4" data-footnote-ref="" aria-describedby="footnote-label">4</a></p>
<p>加えて、この手法では Selector にドメインロジックが凝集されるため、<strong>Selector を重点的にテストすることで品質を担保しやすい</strong>メリットがあります。</p>
<p>Query のエラー制御は <code>useErrorBoundary</code> オプションを使って <a href="https://ja.reactjs.org/docs/error-boundaries.html">Error Boundary</a> を表示する方針としています。</p>
<a id="mutationsts">
<h3 id="mutationsts">mutations.ts</h3>
<p>mutations.ts は次のように実装しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#C586C0">import</span><span style="color:#D4D4D4"> { </span><span style="color:#C586C0">type</span><span style="color:#9CDCFE"> TodoForm</span><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">from</span><span style="color:#CE9178"> "@/viewModels/todo/todoForm"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#C586C0">import</span><span style="color:#D4D4D4"> { </span><span style="color:#9CDCFE">todoApi</span><span style="color:#D4D4D4">, </span><span style="color:#C586C0">type</span><span style="color:#9CDCFE"> PostTodoRequest</span><span style="color:#D4D4D4"> } </span><span style="color:#CE9178">"@/api/generated"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// ⑦ mutationFn</span></span>
<span class="line"><span style="color:#9CDCFE">export</span><span style="color:#9CDCFE"> const</span><span style="color:#9CDCFE"> mutation</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#9CDCFE"> createTodo</span><span style="color:#D4D4D4">: (</span><span style="color:#9CDCFE">request</span><span style="color:#D4D4D4">: </span><span style="color:#9CDCFE">PostTodoRequest</span><span style="color:#D4D4D4">) => {</span></span>
<span class="line"><span style="color:#9CDCFE"> return</span><span style="color:#9CDCFE"> todoApi</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">postTodo</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">request</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// ⑧ Request Selector</span></span>
<span class="line"><span style="color:#6A9955">// ViewModel から API Request へ変換する</span></span>
<span class="line"><span style="color:#9CDCFE">export</span><span style="color:#9CDCFE"> const</span><span style="color:#9CDCFE"> request</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#9CDCFE"> createTodo</span><span style="color:#D4D4D4">: (</span><span style="color:#9CDCFE">todoForm</span><span style="color:#D4D4D4">: </span><span style="color:#9CDCFE">TodoForm</span><span style="color:#D4D4D4">): </span><span style="color:#9CDCFE">PostTodoRequest</span><span style="color:#D4D4D4"> => {</span></span>
<span class="line"><span style="color:#9CDCFE"> return</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> PostTodoRequest</span><span style="color:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#9CDCFE"> todo</span><span style="color:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#9CDCFE"> title</span><span style="color:#D4D4D4">: </span><span style="color:#9CDCFE">todoForm</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">title</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> description</span><span style="color:#D4D4D4">: </span><span style="color:#9CDCFE">todoForm</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">description</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> status</span><span style="color:#D4D4D4">: </span><span style="color:#9CDCFE">todoForm</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">status</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> favorite</span><span style="color:#D4D4D4">: </span><span style="color:#9CDCFE">todoForm</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">favorite</span><span style="color:#D4D4D4"> === </span><span style="color:#CE9178">"true"</span><span style="color:#D4D4D4"> ? </span><span style="color:#9CDCFE">true</span><span style="color:#D4D4D4"> : </span><span style="color:#9CDCFE">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4"> };</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// ⑨ Custom Mutation</span></span>
<span class="line"><span style="color:#9CDCFE">export</span><span style="color:#9CDCFE"> function</span><span style="color:#9CDCFE"> useCreateTodoMutation</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#9CDCFE"> const</span><span style="color:#D4D4D4"> { </span><span style="color:#9CDCFE">invalidateList</span><span style="color:#D4D4D4"> } = </span><span style="color:#9CDCFE">useTodoCache</span><span style="color:#D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> return</span><span style="color:#9CDCFE"> useMutation</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> mutationFn</span><span style="color:#D4D4D4">: (</span><span style="color:#9CDCFE">todoForm</span><span style="color:#D4D4D4">: </span><span style="color:#9CDCFE">TodoForm</span><span style="color:#D4D4D4">) => {</span></span>
<span class="line"><span style="color:#9CDCFE"> return</span><span style="color:#9CDCFE"> mutation</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">createTodo</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">request</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">createTodo</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">todoForm</span><span style="color:#D4D4D4">));</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#9CDCFE"> onSuccess</span><span style="color:#D4D4D4">: () => {</span></span>
<span class="line"><span style="color:#9CDCFE"> return</span><span style="color:#9CDCFE"> invalidateList</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>基本的な構成は queries.ts と同じです。</p>
<p>mutations.ts でも queries.ts と同様に ⑧ Request Selector を定義します。
Request Selector には、データ整形を扱うためドメインロジックが集まりやすいです。
そのため、入出力、境界値、例外のテストを積極的に書いて品質の担保に繋げています。</p>
<p>Mutation のエラーは画面ごとに UI でフィードバックするため、コンポーネント側で制御しています。</p>
<p>Resource Operation レイヤーに関する実装の紹介は以上です。</p>
</a><a id="viewmodel-%E3%81%AE%E5%AE%9F%E8%A3%85">
<h2 id="viewmodel-の実装">ViewModel の実装</h2>
<p>ViewModel とは、View(React Component) で扱うデータのスキーマと型です。</p>
<p>Resource Operation の実装では、API Response を ViewModel に整形して View に提供することを紹介しました。</p>
<p>全体の理解を深めるため、ViewModel の実装も紹介します。</p>
</a><a id="%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E6%88%90-1">
<h3 id="ディレクトリ構成-1">ディレクトリ構成</h3>
<p>ViewModel は関心を分離するため Resource Operation とは別のディレクトリに定義しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="sh"><code><span class="line"><span style="color:#DCDCAA">src/viewModels</span></span>
<span class="line"><span style="color:#DCDCAA">└──</span><span style="color:#CE9178"> todo</span></span>
<span class="line"><span style="color:#DCDCAA"> ├──</span><span style="color:#CE9178"> todo.ts</span><span style="color:#6A9955"> # サーバステート</span></span>
<span class="line"><span style="color:#DCDCAA"> ├──</span><span style="color:#CE9178"> todoForm.test.ts</span><span style="color:#6A9955"> # フロントエンドに閉じたスキーマのテスト</span></span>
<span class="line"><span style="color:#DCDCAA"> ├──</span><span style="color:#CE9178"> todoForm.ts</span><span style="color:#6A9955"> # フロントエンドに閉じたスキーマ</span></span>
<span class="line"><span style="color:#DCDCAA"> └──</span><span style="color:#CE9178"> todoSearchCondition.ts</span><span style="color:#6A9955"> # フロントエンドに閉じた型</span></span></code></pre>
<p>ViewModel で定義するスキーマ・型は大きく分けて 3 つに分類されます。</p>
<ul>
<li>サーバステート</li>
<li>フロントエンドに閉じたスキーマ</li>
<li>フロントエンドに閉じた型</li>
</ul>
</a><a id="%E3%82%B5%E3%83%BC%E3%83%90%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88">
<h3 id="サーバステート">サーバステート</h3>
<p>サーバステートは、 API Response として期待するデータのスキーマ及び型です。
前述の queries.ts 内の ③ queryFn で使用します。</p>
</a><p><a id="%E3%82%B5%E3%83%BC%E3%83%90%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88"></a><a href="https://github.com/colinhacks/zod">zod</a> を使って次のように実装しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#6A9955">// src/viewModels/todo/todo.ts</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// enum は View で <option value={todoStatus.enum.ready} /> のように使用する</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> todoStatus</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">z</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">enum</span><span style="color:#D4D4D4">([</span><span style="color:#CE9178">"ready"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"doing"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"done"</span><span style="color:#D4D4D4">]);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// ⑩ サーバステートのスキーマ</span></span>
<span class="line"><span style="color:#6A9955">// 開発中のみ ③ queryFn 内で API Response を parse することで、スキーマの不整合を検出するための補助輪として使用する</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> todo</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">z</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">object</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> id:</span><span style="color:#9CDCFE"> z</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">string</span><span style="color:#D4D4D4">(),</span></span>
<span class="line"><span style="color:#9CDCFE"> title:</span><span style="color:#9CDCFE"> z</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">string</span><span style="color:#D4D4D4">(),</span></span>
<span class="line"><span style="color:#9CDCFE"> description:</span><span style="color:#9CDCFE"> z</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">string</span><span style="color:#D4D4D4">().</span><span style="color:#DCDCAA">nullable</span><span style="color:#D4D4D4">(),</span></span>
<span class="line"><span style="color:#9CDCFE"> status:</span><span style="color:#9CDCFE"> todoStatus</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> favorite:</span><span style="color:#9CDCFE"> z</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">boolean</span><span style="color:#D4D4D4">(),</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// ⑪ サーバステートの型</span></span>
<span class="line"><span style="color:#6A9955">// ③ queryFn の戻り値の型として使用する</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> type</span><span style="color:#4EC9B0"> Todo</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">z</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">infer</span><span style="color:#D4D4D4"><</span><span style="color:#569CD6">typeof</span><span style="color:#9CDCFE"> todo</span><span style="color:#D4D4D4">>;</span></span></code></pre>
<p>⑩ サーバステートのスキーマは、⑪ サーバステートの型の生成と開発時のスキーマ検証に使用しています。</p>
<p>開発時のみ API Response を検証することで、⑩ サーバステートのスキーマと Open API スキーマの整合性を確認しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#6A9955">// src/resourceOperations/todo/queries.ts</span></span>
<span class="line"><span style="color:#C586C0">import</span><span style="color:#D4D4D4"> { </span><span style="color:#9CDCFE">todo</span><span style="color:#D4D4D4">, </span><span style="color:#C586C0">type</span><span style="color:#9CDCFE"> Todo</span><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">from</span><span style="color:#CE9178"> "@/viewModels/todo"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> query</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#DCDCAA"> getTodoList</span><span style="color:#9CDCFE">:</span><span style="color:#569CD6"> async</span><span style="color:#D4D4D4"> (): </span><span style="color:#4EC9B0">Promise</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">Todo</span><span style="color:#D4D4D4">[]> </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#D4D4D4"> { </span><span style="color:#4FC1FF">data</span><span style="color:#D4D4D4"> } = </span><span style="color:#C586C0">await</span><span style="color:#9CDCFE"> todoApi</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getTodoList</span><span style="color:#D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // 開発時、APIとの結合タイミングで検証してフロントエンドとバックエンドでスキーマの齟齬がないことを確認する</span></span>
<span class="line"><span style="color:#6A9955"> // 検証が済んだら parse 処理を外す</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#9CDCFE"> data</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">todoList</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">map</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">x</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#9CDCFE"> todo</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">parse</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">x</span><span style="color:#D4D4D4">));</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span></code></pre>
<p>例外として、<strong>外部サービスから取得したデータに関しては常にサーバステートのスキーマを使って検証</strong>しています。
例えば、外部サービスの仕様として長さが 1 以上の配列が返ってくると決まっていて、フロントエンド側もその仕様に基づいた処理を実装している場合、 <code>z.array().min(1)</code> のスキーマで常に検証します。
ネットワークに近い箇所で不正なデータを検出することで、例外発生時の調査を容易にするメリットがあると考えています。</p>
<a id="%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E3%81%AB%E9%96%89%E3%81%98%E3%81%9F%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%9E">
<h3 id="フロントエンドに閉じたスキーマ">フロントエンドに閉じたスキーマ</h3>
<p>フロントエンドに閉じたスキーマは、そのほとんどがフォームのバリデーションスキーマです。
こちらも zod を使って定義しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#6A9955">// src/viewModels/todo/todoForm.ts</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">import</span><span style="color:#D4D4D4"> { </span><span style="color:#C586C0">type</span><span style="color:#9CDCFE"> Todo</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">todo</span><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">from</span><span style="color:#CE9178"> "./todo"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// フロントエンドに閉じたスキーマ</span></span>
<span class="line"><span style="color:#6A9955">// フォームのスキーマは react-hook-form と連携して使用する(省略)</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> todoForm</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">z</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">object</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> title:</span><span style="color:#9CDCFE"> z</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">string</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">min</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">1</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"入力してください"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">max</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">200</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"200字以内で入力してください"</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#9CDCFE"> description:</span><span style="color:#9CDCFE"> z</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">string</span><span style="color:#D4D4D4">().</span><span style="color:#DCDCAA">max</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">500</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"500字以内で入力してください"</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#9CDCFE"> status:</span><span style="color:#9CDCFE"> todo</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">shape</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">status</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> favorite:</span><span style="color:#9CDCFE"> z</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">union</span><span style="color:#D4D4D4">([</span><span style="color:#9CDCFE">z</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">literal</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"true"</span><span style="color:#D4D4D4">), </span><span style="color:#9CDCFE">z</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">literal</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"false"</span><span style="color:#D4D4D4">)]),</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> type</span><span style="color:#4EC9B0"> TodoForm</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">z</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">infer</span><span style="color:#D4D4D4"><</span><span style="color:#569CD6">typeof</span><span style="color:#9CDCFE"> todoForm</span><span style="color:#D4D4D4">>;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// ⑫ ViewModel Selector</span></span>
<span class="line"><span style="color:#6A9955">// サーバステートからフロントエンドに閉じたスキーマへ変換する</span></span>
<span class="line"><span style="color:#6A9955">// 前述の queries.ts 内 ⑥ Selector Query で使用する</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> function</span><span style="color:#DCDCAA"> selectTodoForm</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">todo</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Todo</span><span style="color:#D4D4D4">): </span><span style="color:#4EC9B0">TodoForm</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> title:</span><span style="color:#9CDCFE"> todo</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">title</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> description:</span><span style="color:#9CDCFE"> todo</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">description</span><span style="color:#D4D4D4"> ?? </span><span style="color:#CE9178">""</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> status:</span><span style="color:#9CDCFE"> todo</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">status</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> favorite:</span><span style="color:#9CDCFE"> todo</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">favorite</span><span style="color:#D4D4D4"> ? </span><span style="color:#CE9178">"true"</span><span style="color:#D4D4D4"> : </span><span style="color:#CE9178">"false"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> };</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>フロントエンドに閉じたスキーマは、必ずサーバステートを元に生成する運用としています。
そのために、フロントエンドに閉じたスキーマを宣言した直下に、サーバステートからフロントエンドに閉じたスキーマへ変換する ⑫ ViewModel Selector を定義します。
<code>null</code> の 空文字への変換や時間データのフォーマットのような、 サーバステート と View で使う値の差分吸収はこのセレクタ内で行います。</p>
<p>このように、<strong>ViewModel レイヤーでは他のレイヤーと依存しないようにドメインロジックを表現</strong>しています。
ドメインロジックを Tanstack Query に依存しないことで、今後技術基盤を刷新する場合でも影響を最小限に留めることを狙いとしています。</p>
</a><a id="%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E3%81%AB%E9%96%89%E3%81%98%E3%81%9F%E5%9E%8B">
<h3 id="フロントエンドに閉じた型">フロントエンドに閉じた型</h3>
<p>依存関係を整えるため、Resource Operation と View から参照するフロントエンドに閉じた型は viewModels 配下に宣言しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#6A9955">// src/viewModels/todo/todoSearchParams.ts</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> type</span><span style="color:#4EC9B0"> TodoSearchCondition</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#9CDCFE"> sort</span><span style="color:#D4D4D4">?: </span><span style="color:#CE9178">"created_at_asc"</span><span style="color:#D4D4D4"> | </span><span style="color:#CE9178">"created_at_desc"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span></code></pre>
<p>ViewModel に関する実装の紹介は以上です。</p>
</a><a id="tanstack-query-%E5%B0%8E%E5%85%A5%E3%81%AE%E8%83%8C%E6%99%AF%E3%81%A8%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E3%81%AE%E7%8B%99%E3%81%84">
<h1 id="tanstack-query-導入の背景とアーキテクチャの狙い">Tanstack Query 導入の背景とアーキテクチャの狙い</h1>
</a><a id="%E8%83%8C%E6%99%AF%E3%82%B3%E3%83%BC%E3%83%89%E5%93%81%E8%B3%AA%E3%81%AE%E8%AA%B2%E9%A1%8C">
<h2 id="背景コード品質の課題">背景:コード品質の課題</h2>
<p>Tanstack Query を導入する以前の CLINICS の非同期処理周辺のコードベースではいくつかの課題がありました。</p>
<p><strong>開発体験の課題</strong></p>
</a><p><a id="%E8%83%8C%E6%99%AF%E3%82%B3%E3%83%BC%E3%83%89%E5%93%81%E8%B3%AA%E3%81%AE%E8%AA%B2%E9%A1%8C">非同期処理のためのミニマムな基盤フックを独自に実装していた<sup></sup></a><a href="#user-content-fn-5" id="user-content-fnref-5" data-footnote-ref="" aria-describedby="footnote-label">5</a>ことにより、開発体験の観点で次のような課題がありました。</p>
<ul>
<li>新しい要件(ポーリング・無限読み込み等)が発生した際に、最初に担当する開発者が都度機能拡張する必要がある</li>
<li>テストが実装されていなかったため変更時に品質確認の負担が大きい</li>
</ul>
<p><strong>学習容易性の課題</strong></p>
<p>上述の基盤フックにドキュメントがなかったため、学習容易性の観点で次のような課題がありました。</p>
<ul>
<li>基盤フックにドキュメントがなく、新規メンバーの学習コストが高い</li>
<li>様々な実装手法が混在することで、実装時に迷いが生じている</li>
</ul>
<p><strong>可読性の課題</strong></p>
<p>CLINICS は開発開始から約 7 年以上が経過しています。
その中でもフロントエンドは技術トレンドの変化が早いため、様々なライブラリやパターンを使って実装されています。
特に近年から漸進的に導入した React を使った実装については、明確な設計が確立されていませんでした。</p>
<p>これらの背景からチームで安定したアウトプットを出すことが困難で、可読性の観点で次のような課題がありました。</p>
<ul>
<li>画面によってフックや関数の粒度、定義場所が違うことでコードリーディングの負荷が高い</li>
<li>コードレビュー時のレビュワーの負担が大きい</li>
</ul>
<p><strong>テスト容易性の課題</strong></p>
<p>多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、テスト容易性の観点で次のような課題がありました。</p>
<ul>
<li>テストが実装しづらい</li>
<li>テストカバレッジが低い</li>
</ul>
<p>CLINICS はオンライン診療・電子カルテ等の医療機関業務を支える機能を提供する SaaS プラットフォームです。
今後長きに渡って多くの医療機関の方々に CLINICS を利用して頂くためには、<strong>コードを読みやすく、変更しやすく、維持しやすい状態に保ち続けること</strong>が重要です。</p>
<p>前の章で紹介したアーキテクチャは<strong>持続可能な開発を目指して</strong>設計しました。
具体的には、<strong>開発体験・学習容易性・可読性・テスト容易性を向上することを狙い</strong>としています。</p>
<a id="%E7%8B%99%E3%81%84-1tanstack-query-%E3%81%AE%E5%B0%8E%E5%85%A5%E3%81%AB%E3%82%88%E3%82%8B%E9%96%8B%E7%99%BA%E4%BD%93%E9%A8%93%E3%81%AE%E5%90%91%E4%B8%8A">
<h2 id="狙い-1tanstack-query-の導入による開発体験の向上">狙い 1:Tanstack Query の導入による開発体験の向上</h2>
<p>背景で説明したとおり、Tanstack Query 導入以前は独自実装したフックを使って非同期処理を実装していました。</p>
<p>Tanstack Query を導入したきっかけは、チーム内での雑談の中で、独自実装のフックが使いづらいという声が挙がったことです。
そこで、独自実装のフック自体の質を高めるか、質の高いライブラリを導入するかを議論した結果、次の理由でライブラリを導入する決定をしました。</p>
<ul>
<li>極力自分たちでコードを書かずに非同期処理・状態管理の実装を実現したい</li>
<li>実装に困ったときにドキュメントを読めば解決する環境にしたい</li>
</ul>
</a><p><a id="%E7%8B%99%E3%81%84-1tanstack-query-%E3%81%AE%E5%B0%8E%E5%85%A5%E3%81%AB%E3%82%88%E3%82%8B%E9%96%8B%E7%99%BA%E4%BD%93%E9%A8%93%E3%81%AE%E5%90%91%E4%B8%8A">CLINICS では技術的な背景<sup></sup></a><a href="#user-content-fn-6" id="user-content-fnref-6" data-footnote-ref="" aria-describedby="footnote-label">6</a>から Tanstack Query と SWR が候補に上がりましたが、 <code>select</code> オプションや <code>invalidateQueries</code> の <a href="https://tanstack.com/query/v4/docs/react/guides/query-invalidation#query-matching-with-invalidatequeries">Partial Query Matching</a> 等の機能性と、ドキュメントの充実度合いの観点から Tanstack Query を採用しました。</p>
<p>Tanstack Query を採用したことで、<strong>非同期処理のためのコードの記述量が大幅に削減</strong>されたほか、データの特性に応じて Query ごとにキャッシュの時間を調整することが可能となり開発体験が大幅に向上しました。</p>
<a id="%E7%8B%99%E3%81%84-2%E3%82%B7%E3%83%B3%E3%83%97%E3%83%AB%E3%81%AA%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E5%88%86%E5%89%B2%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92%E5%AE%B9%E6%98%93%E6%80%A7%E3%81%AE%E5%90%91%E4%B8%8A">
<h2 id="狙い-2シンプルなレイヤー分割による学習容易性の向上">狙い 2:シンプルなレイヤー分割による学習容易性の向上</h2>
<p>CLINICS では、事業の拡大にともないコードベースに関わるエンジニアが増え続けています。
実装の進め方はプロジェクトによって最適な形式を選択しています。</p>
<ul>
<li>バックエンドからフロントエンドまで一気通貫で実装</li>
<li>技術領域に分けて分業</li>
</ul>
<p>このように多くのエンジニアが様々な形で関わる環境では、コードベースの学習容易性を高め、実装からコードレビューの完了までをスムーズに行えることが重要です。</p>
<p>前の章で紹介したアーキテクチャは、<strong>レイヤー分割をシンプルにすることでコードベースに慣れるまでの時間を最小限にする</strong>ことを意識しています。</p>
<p>加えて、<strong>実装パターンを定形化することで、コードベースに馴染みがなくても迷いなく実装できる</strong>ほか、関数の粒度が統一されることで、コードレビューの負荷軽減にも繋がっています。</p>
<p>レイヤー分割の粒度や実装パターンについては、次の記事を参考にさせて頂きました。</p>
</a><ul><a id="%E7%8B%99%E3%81%84-2%E3%82%B7%E3%83%B3%E3%83%97%E3%83%AB%E3%81%AA%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E5%88%86%E5%89%B2%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92%E5%AE%B9%E6%98%93%E6%80%A7%E3%81%AE%E5%90%91%E4%B8%8A">
</a><li><a id="%E7%8B%99%E3%81%84-2%E3%82%B7%E3%83%B3%E3%83%97%E3%83%AB%E3%81%AA%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E5%88%86%E5%89%B2%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92%E5%AE%B9%E6%98%93%E6%80%A7%E3%81%AE%E5%90%91%E4%B8%8A"></a><a href="https://zenn.dev/yoshiko/articles/91a3dd575f99a2">フロントエンドアーキテクチャの話: Resource Set の紹介</a></li>
</ul>
<p>ほかにも CLINICS では学習容易性の向上の取り組みとして、<strong>新しいライブラリやアーキテクチャを導入した際は勉強会を開催</strong>してライブラリの基本的な使い方や頻出の実装パターンに関する知見を共有しています。</p>
<a id="%E7%8B%99%E3%81%84-3%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF%E3%82%92%E7%B4%94%E7%B2%8B%E9%96%A2%E6%95%B0%E3%81%A7%E8%A1%A8%E7%8F%BE%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%AB%E3%82%88%E3%82%8B%E5%8F%AF%E8%AA%AD%E6%80%A7%E3%83%BB%E3%83%86%E3%82%B9%E3%83%88%E5%AE%B9%E6%98%93%E6%80%A7%E3%81%AE%E5%90%91%E4%B8%8A">
<h2 id="狙い-3ドメインロジックを純粋関数で表現することによる可読性テスト容易性の向上">狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上</h2>
<p>アーキテクチャを刷新する以前は、多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、可読性やテスト容易性に支障をきたしていました。</p>
<p>CLINICS のフロントエンドには、医療システムに関する複雑なドメインロジックが多いため、<strong>シンプルでテストしやすいコードベース</strong>を作っていくことがとりわけ重要だと考えています。</p>
<p>この課題は、ドメインロジックが集まる傾向にある queries.ts、 mutations.ts の Request Selector や ViewModel Selector の実装を定型化し、純粋関数で表現することにより解決しました。</p>
</a><a id="%E3%81%BE%E3%81%A8%E3%82%81">
<h1 id="まとめ">まとめ</h1>
<p>Tanstack Query を使ったフロントエンドアーキテクチャの実例を紹介しました。</p>
<ul>
<li>Resource Operation レイヤーに Tanstack Query の実装を定型化して集約しています。</li>
<li>レイヤー分割をシンプルにし、実装を定型化することで、開発組織のスケールに対応しています。</li>
<li>useQuery の <code>select</code> オプションを使い、スケーラブルにデータ変換処理を記述しています。</li>
<li>データ変換処理にはドメインロジックが集まりやすいため、なるべく小さい粒度の純粋関数で表現することで、可読性・テスト容易性の向上を狙っています。</li>
</ul>
<p>この記事の内容が Tanstack Query の導入を考えている方の参考になれば幸いです。</p>
</a><a id="%E3%81%95%E3%81%84%E3%81%94%E3%81%AB">
<h1 id="さいごに">さいごに</h1>
<p>CLINICS では、機能開発と並行してフロントエンド基盤を改善する取り組みも実施しています。</p>
</a><ul><a id="%E3%81%95%E3%81%84%E3%81%94%E3%81%AB">
<li>Redux から Tanstack Query への移行</li>
</a><li><a id="%E3%81%95%E3%81%84%E3%81%94%E3%81%AB">UI ライブラリの </a><a href="https://mithril-ja.js.org/">Mithril</a> から React への移行<sup><a href="#user-content-fn-7" id="user-content-fnref-7" data-footnote-ref="" aria-describedby="footnote-label">7</a></sup></li>
<li>デザインシステムの構築とプロダクトへの反映</li>
</ul>
<p>このような取り組みに興味がある方は次のリンクから是非ご連絡ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<section data-footnotes="" class="footnotes"><h2 class="sr-only" id="footnote-label">脚注</h2>
<ol>
<li id="user-content-fn-1">
<p><small><a href="https://tanstack.com/query/latest/docs/react/overview">Overview | TanStack Query Docs</a></small> <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p><small><a href="https://tanstack.com/query/v4/docs/react/comparison">Comparison | React Query vs SWR vs Apollo vs RTK Query vs React Router | TanStack Query Docs</a></small> <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p><small>この記事で紹介している ViewModel は View レイヤー専用のモデルを表す概念です。 MVVM アーキテクチャの ViewModel とは異なります。</small> <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-4">
<p><small>Tanstack Query におけるデータ変換手法の詳細は <a href="https://tkdodo.eu/blog/react-query-data-transformations">React Query Data Transformations | TkDodo’s blog</a> で紹介されています。</small> <a href="#user-content-fnref-4" data-footnote-backref="" aria-label="Back to reference 4" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-5">
<p><small>Tanstack Query 導入以前の非同期処理は <a href="https://usehooks.com/useAsync/">useAsync React Hook - useHooks</a> をカスタマイズしたフックを使って実装していました。</small> <a href="#user-content-fnref-5" data-footnote-backref="" aria-label="Back to reference 5" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-6">
<p><small>CLINICS では 一部の実装箇所で Redux を使用していますが、 <a href="https://redux-toolkit.js.org/rtk-query/overview">RTK Query</a> は今回採用するライブラリの候補から除外しました。これは、現在使用している Redux のバージョンが低く、レガシーな周辺ライブラリも複数使用している背景で <a href="https://redux-toolkit.js.org/">Redux Toolkit</a> への移行に相当な工数を必要とするためです。</small> <a href="#user-content-fnref-6" data-footnote-backref="" aria-label="Back to reference 6" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-7">
<p><small>2023 年 3 月現在、 CLINICS のフロントエンドの 約 50% は、 Mithril と Redux で構成されています。開発体験の向上のため、 React への完全移行を目指して日々改善を続けています。</small> <a href="#user-content-fnref-7" data-footnote-backref="" aria-label="Back to reference 7" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>
- ジョブメドレー開発チームにおける 2 年目の成長と活躍https://developer.medley.jp/entry/2023/02/28/151536https://developer.medley.jp/entry/2023/02/28/151536はじめに
みなさん、こんにちは。エンジニアの新居です。今回は医療介護求人サイトの ジョブメドレー 開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いていこうと思います。
インタビュイー紹介
堀内さん
2021 年...Tue, 28 Feb 2023 06:15:36 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。エンジニアの新居です。今回は医療介護求人サイトの <a href="https://job-medley.com/">ジョブメドレー</a> 開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いていこうと思います。</p>
<h1 id="インタビュイー紹介">インタビュイー紹介</h1>
<h2 id="堀内さん">堀内さん</h2>
<p>2021 年入社。人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属。ジョブメドレーの開発を担当。現在は求職者側の UI/UX 改善などの開発に携わる。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note202302_001.jpeg","alt":"堀内さん","index":0}">
<em>堀内さん</em></p>
<h1 id="メドレーに入社した理由">メドレーに入社した理由</h1>
<p><strong>新居</strong>: まずはじめに、堀内さんはどういう経緯でメドレーに入社したのでしたっけ?</p>
<p><strong>堀内</strong>: 以前のインタビュー(<a href="https://developer.medley.jp/entry/2022/07/22/214813">21 年新卒入社エンジニアと座談会で振り返る新卒研修</a>)の時にもお話したのですが、就活の軸として、自分が成長できそうか・風通しが良いか・合理的な社風かなどを軸として探していました。特に、<strong>ビジネス側と開発側の距離が近い</strong>部分に惹かれ、総合的にメドレーが良さそうだと考えて入社しました。</p>
<p><strong>新居</strong>: 入社前の印象と実際働きだしてからで、ズレなどはありましたか?</p>
<p><strong>堀内</strong>: そんなにギャップはなくて、社風とか人間関係みたいなところは入社前のイメージ通りでした。入社前に何回か会社見学をさせてもらったり、その時の社員と複数回面談をセッティングしてもらったりして、そこでイメージをすり合わせできたことが大きかったですね。</p>
<p><strong>新居</strong>: 何人くらいの社員と会ったのですか?</p>
<p><strong>堀内</strong>: 6 ~ 7 人です。色々なポジションや性格の人達と話ができたので、安心して入社することができました。</p>
<h1 id="新卒研修の開発-ojt-ではどんなことをやっていたか">新卒研修の開発 OJT ではどんなことをやっていたか?</h1>
<p><strong>新居</strong>: 堀内さんは新卒研修の開発 OJT のタイミングでジョブメドレー開発チームに仮配属されたのですよね。当時はどういうことをやってたのですか?</p>
<p><strong>堀内</strong>: まずはジョブメドレーのドメイン知識をしっかり吸収していこうということで、業界や業務自体の理解を進めました。</p>
<p>その後、求職者と事業者のサポート業務を行う社内オペレーターが利用する画面の拡張タスクに取り組みました。</p>
<p>技術スタックは <a href="https://nextjs.org/">Next.js</a> / <a href="https://ja.reactjs.org/">React</a> / <a href="https://www.typescriptlang.org/">TypeScript</a> / <a href="https://graphql.org/">GraphQL</a> といったシステムで、個人的にあまり触れてこなかった領域だったので、技術のキャッチアップもしつつ丁寧に対応していきました。</p>
<p>また、開発 OJT よりも前の研修で輪読会も経験したのですが、その時に学んだ <strong>Cookie や Session まわりの知識が役に立ったり、座学で学んだことがこうやって実践で活きてくるんだな、ということを実感</strong>できてとても良い経験になりました。</p>
<p>今までとは比べ物にならないくらい大規模なシステムだったので、プレッシャーも大きかったのですが、チームの人達と相談したり、細かいレビュー・サポートがあったおかげで、しっかり成果を出すことができたと思います。</p>
<p><strong>新居</strong>: 良い OJT ですね!堀内さん自身の努力があったのは勿論ですが、チームに新しいメンバーを迎え入れて成長をサポートする環境が整っていたのも良かったんでしょうね。</p>
<p>ちなみに開発 OJT の中で大変だったこととかはありますか?</p>
<p><strong>堀内</strong>: そうですね、やはり大規模な既存コードの把握・理解が大変でした。社内オペレーターが使う画面とジョブメドレー本体はそれぞれ別システムとして連携しているので、ジョブメドレー本体のコードや仕組みの理解も必要です。</p>
<p>大変だった分、<strong>システムの全体像を理解するのに大いに役立った</strong>ので、今思い返すと良い経験だったなあと思います。</p>
<h1 id="色々な経験を積めた求人カード改修プロジェクト">色々な経験を積めた求人カード改修プロジェクト</h1>
<p><strong>新居</strong>: では、新卒研修を終えてから 1 年以上経っていて、入社 2 年目の終わりに差し掛かっている現在ですが、直近で堀内さんがリードを務めていた、ジョブメドレーの求人カード改修プロジェクトについて話を聞いていきたいと思います。まずは、このプロジェクトの概要について、教えてもらえますか?</p>
<h2 id="求人カード改修プロジェクトとは">求人カード改修プロジェクトとは?</h2>
<p><strong>堀内</strong>: それでは、プロジェクト説明に先立ちまして、まず、ジョブメドレーの「求人カード」について簡単に説明します。
ジョブメドレーで求人を検索すると、検索結果が並ぶ画面が出てきます。我々が「求人カード」と呼んでいるのは、この検索結果画面上に表示される、求人情報のことになります。</p>
<p>検索結果画面の求人カードでは、給与や大まかな業務内容、応募要件、職場の住所などが分かる形になっていますが、求職者の方々がより便利に使っていただけるよう、情報の出し方を見直すことが今回のプロジェクト内容です。</p>
<p>もう少し詳しくお話すると、採用決定率の改善を目的に、どのような改善を実施すべきか検討しています。
求職者にとって欲しい情報が手に入りやすくなるということは、その分、応募に繋がりやすくなると言い換えることもできますので、改修による効果については、継続的に検証し、効果の有無を明らかにしたいと考えています。</p>
<p><strong>新居</strong>: なるほど。端的に言うと、ジョブメドレーにおける求人情報検索の体験を更に良くする為のプロジェクトということですね。</p>
<h2 id="プロジェクトのはじまり">プロジェクトのはじまり</h2>
<p><strong>新居</strong>: 今回のプロジェクトをどのようなメンバーと何をどのように行っていったのか、チーム構成から教えてください。</p>
<p><strong>堀内</strong>: チーム構成としては、自分を含めたエンジニアが 2 人いて、その他は、デザイナー、マーケター、プロダクトマネージャー(以下、PdM)がそれぞれ 1 人ずつプロジェクトにアサインされました。</p>
<p>プロジェクトのきっかけとしては、求職者体験を更に良くする為の企画の一つとしてマーケターと PdM が企画したところから始まり、その後、エンジニアとマーケター間で、細かい具体的な要件まで落とし込んで、プロジェクト化に至りました。</p>
<h2 id="仕様についてメンバー間で徹底議論">仕様についてメンバー間で徹底議論</h2>
<p><strong>堀内</strong>: 要件まで落とし込んだ後は、具体的にそれらをどうやって実装するかを検討しました。特に <strong>UI の仕様については、デザイナーを中心にかなり長い時間をかけて詰めていきました</strong>。</p>
<p><strong>新居</strong>: 確かに、情報の出し方によっては、かえって、求職者体験を悪化させてしまう可能性がありそうですね…。</p>
<p><strong>堀内</strong>: はい。使い勝手の良い UI にする為に、デザイナーがまず <a href="https://www.figma.com/ja/">Figma</a> でモックアップを作り、プロジェクトが始まった最初の週からデイリーの夕会などで、UI に関して徹底的にプロジェクトメンバー間で議論をしていました。</p>
<p>その時、特にメンバー全員が気を付けていたのは、実際に求職者が新しい一覧画面を使う時に、価値を感じてもらえるかどうかです。メドレーの <a href="https://www.medley.jp/team/culture.html">Our Essentials</a> の一つとして、<strong>長期のカスタマー価値を追求</strong> という項目があります。「全ての利用者にとって価値のある施策であること」、「利用者の片方だけを見て、もう片方を無視することがあってはならない」ということで、事業者側の求人情報を多く載せるだけではなく、求職者にとっての使い勝手も向上しているか?などを徹底的にチェックしていました。</p>
<p>使い勝手をチェックするにあたって、ジョブメドレーのメインユーザーの年齢層などが予め分かっていたので、その方達にとって使いやすいかどうかを判断軸として、考えていました。</p>
<p>デザイナーが完成させた UI でも、でき上がったモックアップをメンバーそれぞれの視点で触ってみると、改善点が出る場面がありました。そういった場合には、こういう UI にしてみたらどうか?などとそれぞれが意見し、良さそうな案についてはデザイナーがそれらの提案を取り込んだり、改良したりして、UI の仕様を詰めていきました。</p>
<h2 id="技術面について">技術面について</h2>
<p><strong>新居</strong>: UI を実装に落とし込む際、技術面で工夫した点や気を付けていたポイントなどはありますか?</p>
<p><strong>堀内</strong>: 今回のプロジェクトでは、求人カードのデザインを大幅に変更する為、事前の影響範囲の洗い出しを徹底するよう注意していました。例えば、一覧画面における求人情報の出力量がかなり増えることで、1 ページあたりの読み込み処理に時間がかかってしまうことが予想された為、コンテンツの遅延読み込みやキャッシュの利用によりチューニングを行っています。また、デザイン変更が多くのページにわたって発生する為、リリース後の不具合を起こさないような対策を複数行っています。</p>
<h2 id="担当したプロジェクトのリード">担当したプロジェクトのリード</h2>
<p><strong>新居</strong>: 今回のプロジェクトでは、エンジニアとして開発をしつつ、堀内さんがプロジェクトのリードを任されていたと聞いています。リードとしては、どんな動きをプロジェクトの中でされていたのでしょうか?</p>
<p><strong>堀内</strong>: まずは、最初にプロジェクト全体の大まかなスケジュールを引き、PdM と確認しました。次に、タスク分解をしてメンバーへのアサインを行ったり、プロジェクトのタスクのなかで不確実性を極力少なくする為に、自分達だけではコントロールできなさそうな部分を洗い出したりしました。洗い出したアンコントローラブルな部分は企画者と共に関係各所へ連絡・調整をしてもらうようお願いしていました。</p>
<p><strong>新居</strong>: なるほど。開発スケジュールの出し方など、その辺りで工夫した点などはありますか?</p>
<p><strong>堀内</strong>: スケジュールの共有方法については、視覚的に分かりやすいように、<a href="https://www.figma.com/ja/figjam/">FigJam</a> を使って、ガントチャートのような形式で、ボードを作成しました。</p>
<p>メンバー毎にレーンを振り分けて、誰がいつまでに何をしなければいけないのかを夕会で確認していました。また、スケジュールのボードは常に、<strong>第三者が見ても進捗状況が直ぐに分かるような状態にしておく</strong>よう努めました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note202302_004.png","alt":"スケジュールボード","index":0}">
<em>スケジュールボード</em></p>
<p>スケジューリングで特に意識していたことは、不確実な部分を早めに洗い出すことでした。タスク分解をしていく中で、どこが自分達だけではコントロールできなさそうな部分なのかを把握することから始めていました。</p>
<p>現状の仕様や実装がどうなっているのかなどは直ぐに確認できるので、そういったところから、なるべく不確実な部分を減らしていき、最終的に各タスクの工数を算出するにあたって、不確実性の高さに応じて、バッファを設定しました。</p>
<p><strong>新居</strong>: 少し手を動かせば確認できるようなところは直ぐに確認して、なるべく解像度が高い状態にもっていく進め方は確かに良さそうですね!工数を見積もるにあたって、解像度が低い状態だと、見積もりの精度が低くなってしまいますからね。</p>
<p><strong>堀内</strong>: また、タスクは GitHub Issue を発行して管理していたのですが、切り出す粒度はなるべく細かくしていました。Issue の粒度が荒くなってしまうと、それに比例して手戻りリスクが高くなってしまうので、目安としては、長くても 2 日以内には完了できる粒度で切り出していました。</p>
<p>結果、今回のプロジェクトにおいては、ほとんど手戻りすることなく、ほぼ全ての期間において、オンスケジュールで進めることができました。</p>
<h2 id="ab-テストの導入効果検証の仕組み化">A/B テストの導入(効果検証の仕組み化)</h2>
<p><strong>新居</strong>: オンスケで進められていたのは素晴らしいですね!開発者として実装することと、リードとしてプロジェクトを管理することの他、堀内さんが今回のプロジェクトで担当していた役割などはありますか?</p>
<p><strong>堀内</strong>: 今回のプロジェクトでは、効果検証を行う為の仕組みとして、A/B テストを本格的に導入する目標がありました。なぜ A/B テストを導入するのか? A/B テストで何をしたいのか?を明確にして、A/B テストの設計から導入、分析までを行いました。</p>
<p>統計的な分析手法の選択から実際のテスト環境の開発みたいなところまで一気通貫で、PdM、マーケター、エンジニアを巻き込んで行いました。</p>
<p>A/B テストで大事なのは、論理的な仮説を立ててそれを検証し、その結果からより効果があると判断できた施策を導入していく、というサイクルだと考えています。とはいえ、有意差が出たからといって必ずしも導入したほうが良いというわけではありません。その時の経営面のメリット・デメリットを考慮し、これまでの経験にもとづいた判断も加えて、より効果の高い施策を継続的に導入できるようにする、というのが今回の目標です。</p>
<p>今回のプロジェクトを皮切りに、他のプロジェクトでも同じように A/B テストの仕組みを使える状態にしたい。そういった横展開や汎用性みたいなところも、プロジェクト当初からセットで考えていました。</p>
<p><strong>新居</strong>: とても興味深い取り組みですね!実際、どのように仕組み化を進めていくのでしょうか?</p>
<p><strong>堀内</strong>: A/B テストに必要な知見をドキュメント化して、それを見るだけで、エンジニアのサポートを受けつつ誰でも A/B テストが実施できるようにしていく取り組みや、スプレッドシートにデータを入力すれば、簡単に結果が判るような仕組み化を進行中です。
機能のリリース時は、その結果に応じて結局、どうすれば良いのか?といった指針も Issue やドキュメントに記載しております。</p>
<h2 id="実装や設計面における悩み">実装や設計面における悩み</h2>
<p><strong>新居</strong>: 実装や設計面において、今回のプロジェクトで悩んだようなところはありましたか?</p>
<p><strong>堀内</strong>: はい。自分自身の力量の問題で、ドメイン知識や技術力などが不足していたことから、ジョブメドレーのコードの中で今までの慣習を受け継ぎつつ、良い感じに使い回しができるコードをどう上手く書いていくか、という点について悩んでいました。</p>
<p><strong>新居</strong>: なるほど…。ジョブメドレー自体が 10 年以上の歴史があるサービスなので、そこに手を入れていくのは確かに難しい部分もありそうですね。その悩みに対しては、どう対策をされていたのでしょう?</p>
<p><strong>堀内</strong>: そうですね。今の自分の知見だけで設計と実装を進めてしまうと手戻りが発生してしまう恐れがあったので、まずはドメイン知識が豊富な、同じチーム内のベテラン社員に設計レビューをお願いしました。そこで、ジョブメドレーのインフラ構成におけるキャッシュ戦略や、保守性を高める為の実装の切り出し方など、壁打ちで相談させてもらっていました。</p>
<p>その先輩社員との設計レビュー以外にも、普段から行ってもらっている 1on1 ミーティングを通じて、自分のメンターと相談させてもらい、どういう設計だったら使いやすいのか、などを様々な視点から検討しました。</p>
<p><strong>新居</strong>: なるべく手戻りが発生しないようにする為に、<strong>設計段階で身近にいるエンジニアを巻き込んで進めていった</strong>のですね。直ぐに相談できる相手がいるのは心強いですね。</p>
<h2 id="学びになった点">学びになった点</h2>
<p><strong>新居</strong>: 今回のプロジェクトを通じて、特に学びになった点を教えていただけますか?</p>
<p><strong>堀内</strong>: 技術的な面とプロジェクトの進め方の面でそれぞれ学びがありました。</p>
<p>技術的な面で言うと、<strong>保守性が高く、今後、運用しやすいコードとはどういうものなのかというのを、今回の実装を通じて体得できた</strong>ところだと思っています。</p>
<p>プロジェクトの進め方の面では、過去のプロジェクトのドキュメントを参考に、そこに書かれていた知見を活かしながら、プロジェクトを進めていったので、ノウハウのようなところが学べたかと思っています。</p>
<h2 id="新卒研修開発実践のリードとの違い">新卒研修・開発実践のリードとの違い</h2>
<p><strong>新居</strong>: 堀内さんは、新卒研修の一環で行われた開発実践の期間中、新卒同士のチーム開発でもリードを務めていたと思います。今回のプロジェクトを通じて経験されたリードとの違いについて教えてください。</p>
<p><strong>堀内</strong>: プロジェクトの規模や影響範囲の違いなどから、研修時よりも多くの関係者との調整が必要でした。全員と情報を共有・連携しつつ、プロジェクトを進める必要があった点が一番大きな違いでした。</p>
<p>また、開発実践のプロジェクトはあくまで社内向けシステムを構築するものだったので、会社の売上について意識することはありませんでしたが、今回のプロジェクトでは、施策によって応募率が下がった場合に売上に悪影響を及ぼす可能性がありましたので、開発部署以外への影響を意識して管理する必要があったことも大きいポイントだったと思っています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note202302_002.jpeg","alt":"堀内さん","index":0}"></p>
<h1 id="ジョブメドレーの開発におけるやりがい">ジョブメドレーの開発におけるやりがい</h1>
<p><strong>新居</strong>: ここまで直近で担当されていたプロジェクトについて聞いてきましたが、ジョブメドレーの開発をしていて、特にやりがいを感じる場面について、教えてもらえますか?</p>
<p><strong>堀内</strong>: やはり、利用してくださっている求職者や事業者の方々に向けて、新規機能開発や機能改修を行う工程です。目的と照らし合わせて仕様面から妥協せずに考え抜き、リリースするまでの過程にやりがいを感じています。ユーザー数がとても多いだけに、良い機能をリリースできたときの反響も大きく、非常に楽しいです。</p>
<p>勿論、エンジニアとして技術力を上げていくことも貪欲に今後もやっていかなければならない部分だと思っていますが、企画として案件があがってきた段階で、それをなぜ作るのか?なぜそれで効果が出ると思っているのか?などと考えることが、企画職だけでなく、エンジニアにとっても大事だと考えています。</p>
<p>リリースした後には、実際に効果が出たのか出なかったのか、なぜ効果が出たのか、出なかったのか?などの効果検証を行った上で、PdM を中心にエンジニア以外のメンバーも交えて振り返り、次の施策に活かしていくといった、<strong>PDCA サイクルを回していっている開発スタイルに楽しさを感じています</strong>。</p>
<p>これは、自分の入社時にやりたいこととしてあげていた、エンジニアリング面だけではなくビジネス面についても多くの経験を積みたい、というところにリンクしていて、今在籍しているジョブメドレーの開発チームでは、企画やマーケティングを行う人達とエンジニアがとても近い距離にいることで、様々な知見を得られつつアウトプットできる、とても学びになる環境だと感じています。</p>
<h1 id="ジョブメドレー開発チームにおける-1-日の流れ">ジョブメドレー開発チームにおける 1 日の流れ</h1>
<p><strong>新居</strong>: 現在のジョブメドレー開発チームは、Growth Unit(求職者の利便性を高める施策を行うチーム)と Customer Unit(求人を掲載する事業者が使う採用管理システムの改善施策を行うチーム)の 2 つに分かれているんですよね。堀内さんが所属しているのは、Growth Unit の方ですが、参加されているミーティングなど、1 日の流れについて教えてください。</p>
<p><strong>堀内</strong>: まず、勤務開始の 10:00 頃には Slack に投稿する形で、その日のタスクや参加予定のミーティングなどについて、開発用 channel で共有します。</p>
<p>現在の自分のスケジュールでは、午前中は特に定常的なミーティングはありません。但し、アサインされるプロジェクトによっては、午前中に「朝会」としてプロジェクトメンバー同士が集まって、進捗共有などを行っています。</p>
<p>お昼過ぎの 14:30 からは、 30 分程度、Growth Unit のエンジニアだけで集まるミーティングがあります。この場では、各自の進捗状況や抱えている不安・不明点などを共有します。今何に困っているのかを共有するだけでなく、<strong>困りごとに対し、各々が知恵を出し合って、問題解決までのリードタイムを短くする</strong>ことができる貴重な場となっています。</p>
<p>18:00 からは「夕会」として、PdM とデザイナーも含めた Growth Unit 全体のミーティングがあります。そこで各自、「今日やったこと」や担当 Issue のリリース目処などを共有しています。この場では、エンジニア以外のメンバーも参加しているので、そこまで技術的に突っ込んだ話まではしません。</p>
<p>さらに、終業前にはまたエンジニアだけで集まり、FigJam を使って付箋を貼っていく形で、<strong>各自が技術的トピックや、特定のドメインについて聞きたいことなどを持ち寄り、ざっくばらんに雑談形式で話す</strong>「技術共有会」を行っています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note202302_005.png","alt":"技術共有会の雰囲気","index":0}">
<em>技術共有会の雰囲気</em></p>
<p>ミーティングの場以外は黙々と開発を進めていきますが、聞きたいことや相談したいことが出てきたら都度、開発用のスレッドに投稿しています。</p>
<p><strong>新居</strong>: なるほど。質問や相談はしやすい感じですか?</p>
<p><strong>堀内</strong>: はい。勿論、自分で調べられることはなるべく調べて、自己解決するのが基本ではありますが、あまり調査に時間をかけ過ぎて全然進まないような事態に陥っても良くないので、それらを皆、前提として意識した上で、聞くべき時は躊躇なく聞ける雰囲気があります。</p>
<p><strong>新居</strong>: ミーティングの種類としては、先ほどあげてもらったもの以外にありますか?</p>
<p><strong>堀内</strong>: あとは、週一で行われている「プロダクト定例」と隔週で行ってもらっている「1on1 ミーティング」があります。</p>
<p>プロダクト定例は、比較的規模の大きいミーティングで、我々、プロダクト開発室以外のメンバーの他、マーケティング室や事業企画室のメンバーも含めて、大体いつも 40 名前後の人数で参加しています。</p>
<p>この場では、現在追っている KPI だったり、ジョブメドレーで動いている全てのプロジェクト状況が共有されます。そこで共有される KPI の数字によって、次に着手すべき Issue にも影響してくるので、この場で共有される情報をしっかりとインプットして、今後の心構えとすることにしています。</p>
<p>直近で担当したプロジェクトの話でもちらっと出てきましたが、1on1 ミーティングは担当メンターと行なっています。普段の仕事の相談から、自分のキャリアに関するような相談をしており、メンターさんの経験なども教えていただきながら、勉強させていただいています。</p>
<p>1on1 ミーティングの意義としては、そういった仕事の他、<strong>プライベートのことも含めて、何でも相談ができる場</strong>としてあるのですが、1on1 を通じて、メンターさんとの信頼関係も築けるので、チームビルディングの一つとしても機能しています。</p>
<h1 id="ジョブメドレー開発チームで一緒に働きたいと思う人はどんな人">ジョブメドレー開発チームで一緒に働きたいと思う人はどんな人?</h1>
<p><strong>新居</strong>: 堀内さんがジョブメドレー開発チームで一緒に働きたいと思う人はどんな人でしょうか?</p>
<p><strong>堀内</strong>: 今の環境で働いてて良かったなあと思うことは、<strong>人間関係が良好であること</strong>です。もちろん馴れ合いの中での業務というわけではなく、各々が<strong>しっかりと仕事に対して責任を持ちつつ、チーム内で生じた違和感は遠慮なく指摘し合い受けとめる</strong>という、良い意味で心理的安全性のある環境でプロダクト開発に集中して取り組めることです。</p>
<p>このような環境で、課題に対して長期的な視点で本質を捉え、目的を設定し、ボトムアップで解決策の立案から改善までを主導すること、さらには仕組み化まで行うことが好きな方、またはそれらをやってみたい方が合っているのではないか、と思います。</p>
<p>自分もそのような人間になりたいと思いながら、日々仕事に取り組んでいます。</p>
<h1 id="さいごに">さいごに</h1>
<p>ジョブメドレー開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いてきましたが、いかがだったでしょうか?
こんなチームでプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう!</p>
<p><img __ASTRO_IMAGE_="{"src":"./note202302_003.jpeg","alt":"インタビュアーとインタビュイー","index":0}"></p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- Pharms 開発チームが取り組んでいるチームビルディング "TGIF" とは?https://developer.medley.jp/entry/2023/01/31/112033https://developer.medley.jp/entry/2023/01/31/112033はじめに
みなさん、こんにちは。エンジニアの山田です。今回は Pharms 開発チームがチームビルディングの一環として行なっている「TGIF」という施策について参加している皆さんに、どのような効果があるのかなどを聞いていこうと思います。
イ...Tue, 31 Jan 2023 02:20:34 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。エンジニアの山田です。今回は <a href="https://pharms-cloud.com/">Pharms</a> 開発チームがチームビルディングの一環として行なっている「TGIF」という施策について参加している皆さんに、どのような効果があるのかなどを聞いていこうと思います。</p>
<h1 id="インタビュイー紹介">インタビュイー紹介</h1>
<h2 id="新居さん">新居さん</h2>
<p>2016 年入社。ソーシャルゲーム開発会社を経て、メドレーへ入社。入社当初はジョブメドレーのプロダクト開発などを担当していたが、2020 年から Pharms で中規模施策の開発を中心に業務を行なう。</p>
<h2 id="小田さん">小田さん</h2>
<p>2021 年入社。大手メーカーで病院や調剤薬局向けの IT システムの開発業務を経て、メドレー入社。Pharms では中規模施策の開発を中心に業務を行なう。</p>
<h2 id="兒玉さん">兒玉さん</h2>
<p>2018 年入社。2021 年 4 月まで CLINICS オンライン診療のセールス部に所属。その後エンジニアに社内転職し、Pharms の開発に携わる。現在はサーバサイドの開発を中心に担当している。</p>
<h2 id="新倉さん">新倉さん</h2>
<p>2021 年入社。不動産テック企業でのデザイン業務を経て、メドレーへ入社。入社時から Pharms のデザイナーとして各種デザインの業務を行なう。</p>
<h2 id="古川さん">古川さん</h2>
<p>2022 年に新卒入社。現在はプロダクトの改善を幅広く担当している。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note202301_001.jpeg","alt":"Pharms 開発チームのみなさん","index":0}"></p>
<h1 id="pharms-というプロダクトについて">Pharms というプロダクトについて</h1>
<p><strong>山田</strong>: まず「TGIF」について聞いていく前に、Pharms とはどのようなプロダクトなのか教えてもらってもよいですか?</p>
<p><strong>新居</strong>: 一言で言うと、薬局の業務効率化支援システムです。<a href="https://clinics-cloud.com/online">CLINICS オンライン診療</a>で診察を受けた患者さんや、直接来院された患者さんからの処方箋などを受けて、Pharms を通して服薬指導や、会計、服薬フォローアップなどを一貫して行なえるシステムになっています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note202301_011.jpeg","alt":"新居さん","index":0}">
<em>新居さん</em></p>
<p><strong>山田</strong>: ユーザーについてですが、例えば、自分が開発を担当している医療介護求人サイトの<a href="https://job-medley.com/">ジョブメドレー</a>では、お仕事をお探しの求職者の方々と、求人情報を掲載いただく医院・事業所の方々が使用しているシステムなのですが、Pharms はどのような方に使われるのでしょうか?</p>
<p><strong>小田</strong>: CLINICS アプリを通して間接的に患者さんが使っていますが、大きい割合を占めるのは顧客である薬局のスタッフさん(薬剤師など)ですね。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note202301_007.jpeg","alt":"小田さん","index":0}">
<em>小田さん</em></p>
<p><strong>山田</strong>: やはり現場の薬剤師さんが使っているサービスという性格が強いんですね。</p>
<h1 id="pharms-開発チームの-tgif-という試みについて">Pharms 開発チームの TGIF という試みについて</h1>
<h2 id="tgif-を始める前の課題感">TGIF を始める前の課題感</h2>
<p><strong>山田</strong>: それでは、いよいよ Pharms 開発チームで行なっている TGIF について聞いていきたいと思います。元々、この言葉はどんな経緯で付いたんですか?</p>
<p><strong>小田</strong>: 元々は英語圏で言われている “Thank God It’s Friday” というのが語源で、そのまま金曜日の夕方に開催されるものだったのでこの名前を付けています。ちょっと昔の言葉で言うと「花金」ですね。</p>
<p><strong>山田</strong>: 「1 週間が終わりだー!」っていう開放感を表わしてるんですね。どういうきっかけでこの TGIF をやっていこうと思ったんでしょうか?</p>
<p><strong>小田</strong>: 一昨年の自分の入社前のタイミングで、Pharms 立ち上げ時のメンバーが別のプロジェクトに集中することとなり、チーム体制がガラッと変わる時期がありました。それ以降、チーム内の情報がサイロ化したり、チームとしての一体感がないという課題を感じていました。この課題を解決するために<strong>雑多に何でも話せるコミュニケーションの場</strong>を作ろうというのが、きっかけでした。</p>
<p><strong>新居</strong>: もちろん、それまでもチームで話す機会が全く無かったわけではなく、開発計画をクォーター単位で作っているのでそのタイミングでディスカッションする場を設けていました。でも <strong>もうちょっとコミュニケーションの頻度を増やしたらより良くなりそうだよね</strong> という話があり、毎週金曜日の TGIF が誕生しました。</p>
<p><strong>山田</strong>: そういう経緯だったんですね。それまでエンジニア・デザイナーが参加する定例のような会議体はどのようなものがあったんですか?</p>
<p><strong>新居</strong>: クォーター毎のディスカッション以外だと、週一で行なわれる「プロダクト定例」だけでした。こちらは開発メンバーだけではなく、事業部のメンバーも参加してプロダクト開発全般についての共有を目的にしたものです。それ以外で定例の会議体はなく、デイリーで行われるような「朝会・夕会」も Pharms 開発チームでは行なっていませんでした。</p>
<p><strong>山田</strong>: チーム体制の変更後、新しいメンバーの加入もあり、今までのコミュニケーション量では足りなくなってしまったんですね。</p>
<p><strong>新居</strong>: はい。チームの状況が変わって行く中で、<strong>もっと生産性を上げていきたいという思いがありました</strong> 。チームが小規模だった頃は隣の席の同僚エンジニアに相談しつつ開発をドンドン前に進めていくことができたんですが、人数が増えてくるとそうしたコミュニケーションも希薄になります。具体的には、開発をしていて設計方針・実装方針などがズレることは多々あると思うんですが、それをさっと会話して軌道修正し、開発を推進していくというのが難しくなってきていました。</p>
<h2 id="tgif-の実際">TGIF の実際</h2>
<p><strong>山田</strong>: では、具体的に TGIF でどのような事を話し合われるんでしょうか?</p>
<p><strong>新居</strong>: まずは今週の振り返りから始まります。前週の TGIF で Try に対して個々がどう動いていくかを決めているので、それに対して振り返りをしていきます。各自コメントを添えて何%達成したのか話していきます。</p>
<p><strong>小田</strong>: その後、今週のチーム全体の動きに対して KPT を実施します。チーム全体として良かった動きや課題などを挙げていき、内容を深ぼったり課題解決に向けて意見を交換したりしています。また、以前はチームの動きとは関係ない個人の話などが挙がってくることが多かったのですが、 <strong>チーム全体での生産性の向上ということを目的として考えた場合、それと関連のない個人の話をしても寄与しない</strong> ので、チーム全体に関わることをメインにするよう改善し今の形になってきました。</p>
<p>KPT が終わったら、隔週でチーム目標と Problem で挙がったことなども踏まえて各自の来週の Try を決めます。</p>
<p><strong>新居</strong>: 最後に、ちょっとしたことでも良いのでチームに共有したい「気になること」を話していきます。ここは開発に関係する・しないに関わらない話題を出しています。例えば 、Pharms のプロダクト自体や会社の組織的なこと、プルリクエストのレビューのお願いなど雑多な話題ですね。</p>
<p>その中で「ライブラリアップデート」についての課題や進捗などの共有もします。単純にバージョンアップして済むというものであれば良いですが、アップデートの結果、広範囲に影響があったりだとか、そもそもエラーなどで上手くアップデートできないなどがあるので、そうした困り事の共有と解決を目的としています。</p>
<p><strong>山田</strong>: 先ほどチーム目標を隔週で決めるというお話でしたが、こちらはどういう風に決めていくんですか?</p>
<p><strong>小田</strong>: KPT の内容を踏まえて、基本的には <a href="https://www.medley.jp/team/culture.html">Our Essentials</a>(以下 OE) に照らし合わせて決めていきます。そのチーム目標に対して、個人の目標をブレイクダウンして決める形にしています。</p>
<h2 id="固くなりすぎない会を目指して">固くなりすぎない会を目指して</h2>
<p><strong>山田</strong>: なるほど。 <strong>OE に合わせて目標設定していくというのは、分かりやすくて良い</strong> ですね。話は少し逸れますが、この TGIF では毎回お菓子を持ち寄って行なっているということですが、どうしてなのでしょうか?</p>
<p><strong>小田</strong>: TGIF を行なっている内に、メンバー間での意思疎通の少なさによって、チームの生産性が上がらないという課題は解決していきました。しかし、続けている内に段々と会自体が固い雰囲気になってきて、かっちりとした振り返りの場という感じになってしまいました。元々 TGIF の在り方としてはどういうものだっけという原点に立ち返ると、やはり <strong>「気軽にコミュニケーションができる場」という点が重要</strong> だと再認識しまして、そこからは場の雰囲気を柔らげるためにお菓子を持ち寄り始めました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./IMG_0914.jpg","alt":"お菓子を囲んでの TGIF","index":0}">
<em>お菓子を囲んでの TGIF</em></p>
<p><strong>山田</strong>: 確かに固すぎると、ちょっと会の名前とそぐわない感じになってしまいますね。</p>
<h2 id="tgif-運用の秘訣">TGIF 運用の秘訣</h2>
<p><strong>山田</strong>: 他に TGIF を運用する上での工夫などはありますか?</p>
<p><strong>新倉</strong>: 会の性質上、話が盛り上がって時間がオーバーしがちなので「 TGIF で話す内容はチームの成長につながることに限定する」など、 <strong>効率的かつ意味のある運用</strong> になるよう進め方の見直しと実践は日々試行錯誤しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note202301_008.jpeg","alt":"新倉さん","index":0}">
<em>新倉さん</em></p>
<p><strong>古川</strong>: 自分は去年の新卒入社ですので、「言ってもしょうがないかな?」というような事もあるのですが、あえてそういった事を考えずに積極的に発言するようにしています。 <strong>実際に発言したために他の方との共有もできますし、学びになるような議論に発展することも多い</strong> ので、言って良かったという場合が多いです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note202301_003.jpeg","alt":"古川さん","index":0}">
<em>古川さん</em></p>
<p><strong>兒玉</strong>: ささいな事でも、誰かが <strong>チームの為に動いた事に対しては積極的にお礼を言っています</strong> 。誰かがちゃんと自分のやったことを見てくれていると認識できることが、チームの雰囲気を良くし、チーム運営の好循環に繋がっているかと思います。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note202301_009.jpeg","alt":"兒玉さん","index":0}">
<em>兒玉さん</em></p>
<p><strong>新居</strong>: ちゃんとチーム全員にとって意味がある時間にするということは意識しています。仮にあまり生産的な時間ではなくなったら TGIF を止めてしまっても良いと思っています。その為には <strong>「チームのために話しをした方がよいことは必ず話す」「本音ベースで建設的に議論する」「出てきた課題は必ずクリアにして次週以降に解決を目指す」</strong> という事を意識しています。</p>
<p>こうして話すとある意味当たり前の事ばかりなんですが、参加者全員がこうした点を共通認識として持っていないと、単に KPT を発表するだけの場となり会自体が形骸化していってしまいます。ですので、きちんとこの共通認識を持って臨めるようにするというのを大切にしています。</p>
<p><strong>小田</strong>: また、TGIF で話が盛り上がるのは良いことなのですが、週に一回のこの場を待たずにもっと会話を活性化する取り組みとして <strong>週の頭に Slack のプロダクトチャンネルにテディベアがリマインダーで現れる</strong> ので、そのスレッドで話しをするようにしています。</p>
<p><strong>山田</strong>: へえー!何でテディベアなんですか?</p>
<p><strong>新居</strong>: プログラミングで自分で解決できない問題が出てきたときに、テディベアのぬいぐるみに話しかけると問題が整理されて解決できるというテディベア効果から取っています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./image2023-1-6_19-45-27.png","alt":"テディベア","index":0}">
<em>テディベアの</em>リマインダーにコメントが付いている様子</p>
<p><strong>山田</strong>: なるほど、それでテディベアが出てくるんですね。</p>
<p><strong>小田</strong>: TGIF を待つまでの時間がムダだよねっていう話もあって先程の効率化の話も含めて行なっている施策です。</p>
<p><strong>新倉</strong>: これまで TGIF やテディベアが無い時は下手をすると一週間誰とも会話をしていないということもあったんですが、この取り組みをしてから <strong>本当にコミュニケーションが増えました</strong> 。</p>
<h2 id="tgif-の利点とこれからについて">TGIF の利点とこれからについて</h2>
<p><strong>山田</strong>: ありがとうございます。既に話にも出ていますが、TGIF をやって良かった点はどういったものがあるか改めて聞かせてください。</p>
<p><strong>小田</strong>: やっぱり格段に「チームでの開発」をしているという意識が高まったことが良いことでした。それまではやはりコミュニケーションなどが不足していたのですが、今は TGIF などのおかげですぐに相談しようという感じにもなって良いチームになってきたなという感触があります。 <strong>さらにチームを良くする土壌</strong> にするようにしていきたいです。</p>
<p><strong>新居</strong>: 1 つ目は以前よりお互いのことを知れるようになって心理的安全性が高まったこと、2 つ目はチームとしての方針や共通認識を合わせる場として機能していること、3 つ目は毎週前週からの課題を改善でき、チームや個人の成長を感じられるようになったことです。 <strong>全体的にチーム運営にプラス</strong> になっていると感じています。</p>
<p><strong>新倉</strong>: 私は TGIF が始まってから半年後にジョインしました。元々はエンジニアの皆さんだけのものだと思ってちょっと尻込みしていたところがあったのですが、蓋を開けてみるとプロダクトやチーム自体についての話が多く、 <strong>デザイナーとしても非常に学び</strong> があるものでした。全体的にはクォーター毎に目標を決めてそれに向けて動くのですが、その目標までの道のりを埋めるという意味でも、TGIF があって良かったです。</p>
<p><strong>新居</strong>: 時にはエンジニアだけにしかわからない話題などもあったりもしますが、大部分は新倉さんが話したようにプロダクトやチームの改善をいかに行なうかという話題になっているので、そこでデザイナー視点での話が聞けてエンジニアとしても大変助かります。</p>
<p><strong>古川</strong>: 自分は入ってからずっと TGIF があるのである種当たり前の存在なのですが…。チームメンバーの人となりなどが分かるのは大変助かります。また、自分とは全然違う経歴を持っているエンジニアの方などは同じ話題でも、 <strong>自分とは全然違う視点を持って話をしていたりするので、すごく勉強になっています</strong> 。チームの課題が話し合われるので、以前は自分のタスクが終わったら、「次は何をしよう」という感じになっていたんですが、TGIF のおかげで「チームがここで困っているなら自分がやろう」と客観的に分かるようになっています。また、OE は半年毎の自己評価の際にも体現できているかどうかチェックすることになるので、TGIF での目標達成度がそれに役立ちます。</p>
<p><strong>兒玉</strong>: 一番は心理的安全性が高くなったことです。エンジニアとしての働き方とセールスをしていた時の働き方が全然違うところが多かったので、1on1 で新居さんとそういった差異について聞くという機会しかなかったんですが、今は TGIF があるのでここで皆さんに聞けるようになって様々な視点でのアドバイスを頂けるようになりました。また、エンジニアとしてお勧めの書籍や勉強法など普通に聞こうとすると、 <strong>あまり機会がないような部分も気軽に聞けるようになったので、勉強の効率も良くなりました</strong> 。</p>
<p><strong>山田</strong>: みなさん、ありがとうございます。それぞれの立場ですごく有用な会なのが分かりますね。 TGIF のこれからについてもお話ください。</p>
<p><strong>新居</strong>: 先ほども少し話しましたが、形骸化してしまい惰性で行なってしまうということになるのが本当に怖いと思っています。運用することが目的になってしまい、本来の目的が達成できないと本末転倒ですし。今もやっていることですが、ちゃんとメンバー構成や状況に応じて運用をアップデートしていくということが一番大切だと思っています。</p>
<p><strong>小田</strong>: 今までエンジニアだけの会から TGIF は始まっていますが、デザイナーの新倉さんが参加してくれたのを始めとして、昨年末から企画職の方も入ってくれるようになったので、裾野の広がりを感じています。こういった立場が別々のメンバーが入ってもスムーズに目的が達成できるように TGIF のアップデートを続けていきたいですね。</p>
<p><strong>新倉</strong>: 色々な役割の人がフラットに発言できる場があるおかげで、プロダクトについて総合的な視点を持つことができたと感じています。実際に自分達が試行錯誤して出した機能が「お客様にこんな風に使われて、こんな感想をもらえた」などすぐに共有してもらえるので、開発のモチベーションにもつながる良いサイクルになっています。</p>
<h2 id="pharms-開発チームではどんな人と一緒に働きたいと考えている">Pharms 開発チームでは、どんな人と一緒に働きたいと考えている?</h2>
<p><strong>山田</strong>: ありがとうございます。最後に Pharms の開発チームには、どんな方がジョインされると嬉しいですか?</p>
<p><strong>小田</strong>: いつもチーム内で話をしているのが「<strong>顧客ファースト</strong>」という言葉です。これは Pharms に限った話ではなくメドレー全体の話でもあるのですが、顧客や患者などサービスを使っている人達に対して提供できる価値は何かということをきちんと意識して開発ができる方です。技術や数字だけを見るわけじゃなく、その先の顧客をちゃんと考えて開発ができる人ですね。</p>
<p><strong>新居</strong>: メドレーの事業やバリューに共感があるのは大前提です。また、小田さんが話されたことに加えて、プロダクトをより良くしようと考えてるメンバーと共に、自分はこうしたいという意志も持ちつつ、ときには建設的に議論もしながらプロダクト開発を推進できる人と一緒に仕事ができると嬉しいです。大変なことも勿論ありますが、それを楽しみ、周囲を巻き込みながら技術・技術以外の部分も含め前に進めていける人が良いなと思います。</p>
<p><strong>山田</strong>: TGIF も顧客ファーストの理念から生まれたものということになりますよね。ありがとうございました!</p>
<h1 id="さいごに">さいごに</h1>
<p>Pharms 開発チームが実践している「TGIF」というチームビルディングの方法についてインタビューしてきましたが、いかがだったでしょうか? 実際に TGIF
をしている様子などを見ると、和やかな雰囲気ではありますが、全員真剣にプロダクトについて議論をしている姿が印象的でした。</p>
<p>こんなチームで調剤薬局や患者のためのプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- Slack × AppSheet × GAS を利用した ChatOps なアプリ開発 〜テクノロジーと創意工夫で従業員のパフォーマンスの最大化を図る〜https://developer.medley.jp/entry/2022/12/23/153818https://developer.medley.jp/entry/2022/12/23/153818はじめに
こんにちは。コーポレートデザイン部でコーポレート IT を担当している清水です。
コーポレート IT では全従業員が利用するネットワークインフラや SaaS といった IT 全般を統括しており、社内で発生している課題について IT...Fri, 23 Dec 2022 06:38:18 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは。コーポレートデザイン部でコーポレート IT を担当している清水です。</p>
<p>コーポレート IT では全従業員が利用するネットワークインフラや SaaS といった IT 全般を統括しており、社内で発生している課題について IT を駆使して解決しています。</p>
<p>今回は、こうした課題解決の一例として、「<strong>呼び鈴アプリ</strong>」の開発について背景と実装のご紹介をし、最後に<a href="#about">コーポレート IT チーム</a>の業務内容や理念についてご紹介したいと思います。</p>
<h1 id="呼び鈴アプリの開発背景">呼び鈴アプリの開発背景</h1>
<p>弊社では、チャットツールである「<a href="https://slack.com/intl/ja-jp/enterprise">Slack</a>」と「<a href="https://workspace.google.co.jp/intl/ja/features/">Google Workspace</a>」や「<a href="https://www.teamspirit.com/ja-jp/">TeamSpirit</a>」等を連携して、来客者受付システムや<a href="https://developer.medley.jp/entry/2020/12/25/180058">稟議ワークフローシステム</a>、オフィス空調最適化システム等、チャットをベースにした業務運用(ChatOps)を数多く行っています。</p>
<p>今回はその中でも、サポートを受けたい場合にコーポレート部門の担当者を呼び出しできるアプリ、<strong>通称:呼び鈴アプリ</strong> をご紹介しようと思います。</p>
<h2 id="呼び鈴アプリ導入前の困りごと">呼び鈴アプリ導入前の困りごと</h2>
<p>IT 備品や健康保険証等の物の受け渡しや PC トラブルへの対応など、従業員へのサポート対応の際、サポートを依頼した従業員とコーポレート部門との間で、以下のような困りごとが発生していました。</p>
<h3 id="-従業員側の困りごと">■ 従業員側の困りごと</h3>
<h4 id="誰に話しかけたらよいかわからない">・誰に話しかけたらよいかわからない</h4>
<p>IT 備品や健康保険証等を受け取りにコーポレート部門の座席に来たが、誰に話しかけたらよいかわからず、ウロウロしている方が結構いました。結果、別の部門に話しかけて、別の部門の方が正しい部門に案内するという時間も発生していました。</p>
<h4 id="時間調整に手間がかかる">・時間調整に手間がかかる</h4>
<p>PC トラブルへの対応の際は、コーポレート IT の担当者が依頼者の席まで行って作業をしていたため、担当者と依頼者で時間の調整が必要でした。直前で都合が悪くなり、リスケが発生することもしばしばありました。</p>
<h3 id="-コーポレート部門の困りごと">■ コーポレート部門の困りごと</h3>
<h4 id="依頼者が見つけづらい">・依頼者が見つけづらい</h4>
<p>座席表を見て依頼者のところまで行くが、直近で座席変更があって居ると思っていたところに居ないことや、指定の時間に席に居ないといったことが度々発生していました。</p>
<h4 id="個人情報が見えると困る">・個人情報が見えると困る</h4>
<p>従業員が人事部から健康保険証等の書類を受け取る際、人事部の方は個人情報を扱うため、PC 画面の見える位置まで来られると困るという事情があり、特定の位置で待ってもらいたいというニーズがありました。</p>
<h4 id="従業員の氏名確認に手間がかかる">・従業員の氏名確認に手間がかかる</h4>
<p>全従業員分の氏名は把握できないため、コーポレート部門に来た従業員本人に名前を聞いて確認し、一旦席に戻って、貸与する IT 備品や健康保険証等を探して渡すという、席を行ったり来たりする状況でした。</p>
<h4 id="周りの業務状況に配慮が必要">・周りの業務状況に配慮が必要</h4>
<p>依頼者の席で PC トラブル対応をすると、周りの人が業務に集中していたり、顧客と電話していたりするため、業務に支障がでないようになるべく小声で話す必要がありました。</p>
<h4 id="依頼者への対応が属人化しがち">・依頼者への対応が属人化しがち</h4>
<p>コーポレート部門の担当者と依頼者の間で作業の時間を調整していましたが、担当者が急な打ち合わせやお休みで不在になってしまったときは、他の担当者への引き継ぎ対応が漏れてしまうことがありました。</p>
<p>上記の課題を解決するために、<strong>従業員の方向けにコーポレート部門の担当者を呼び出すアプリ</strong>を作りました!</p>
<h2 id="どういったアプリか">どういったアプリか</h2>
<p>呼び鈴アプリは、従業員とコーポレート部門で対面での作業/受け渡しが発生すること(PC トラブルへの対応/落とし物の受け渡し/健康保険証のお渡し等)に関して、iPad で依頼内容に応じた<strong>コーポレート部門の担当者を呼び出すことができるアプリ</strong>です。</p>
<p>コーポレート部門の近くのカウンターの上に iPad が置いてあり、そこから呼び出してもらいます。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_006.jpg","alt":"","index":0}"></p>
<p>ちなみに、カウンターはこんな感じのスペースで、従業員の方がセルフサービスで IT 備品交換を行えるブースもあります。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_015.jpg","alt":"","index":0}"></p>
<h3 id="アプリの利用イメージ">アプリの利用イメージ</h3>
<p>まず、従業員はカウンターまで行き、iPad の呼び鈴アプリから呼び出し依頼を行います。</p>
<p>呼び出し依頼は専用の Slack チャンネルに通知が来るので、各コーポレート部門の担当者が通知内容を確認して、従業員が居るカウンターに行き、対応という流れになります。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_005.jpg","alt":"","index":0}"></p>
<h3 id="画面イメージ">画面イメージ</h3>
<p>利用方法は、自身の名前を選択してもらい、コーポレート部門への依頼内容を選択するという形です。
<img __ASTRO_IMAGE_="{"src":"./note1223_001.jpg","alt":"","index":0}"></p>
<h2 id="導入後の効果">導入後の効果</h2>
<p>呼び鈴アプリの導入によって、</p>
<ul>
<li>従業員がコーポレート部門からサポートを受けたり、物を受け渡ししたいときは、<strong>自身が都合の良いタイミングで</strong>カウンターの呼び鈴アプリに来れば良く、<strong>利便性が向上した</strong>。</li>
<li>Slack 通知に依頼者氏名が表示されるので、<strong>氏名を確認する必要がなくなった。</strong></li>
<li>コーポレート部門の担当者が<strong>依頼者の席を探して尋ねなくてよくなった。</strong></li>
</ul>
<p>というように困りごとが改善されました。</p>
<p>また、特定のメンバーが対応するのではなく、呼び出されたタイミングで席に居る全メンバーが対応できるような運用にすることで、PC/IT 備品や健康保険証等の物の受け渡し、PC のトラブル対応等のサポート対応が属人的にならず、離席していても、休暇をとっていても誰でも対応できる体制になりました。</p>
<h1 id="呼び鈴アプリの構成">呼び鈴アプリの構成</h1>
<p>呼び鈴アプリの開発にあたっては</p>
<p><strong>1. 先述の課題を解決すること</strong></p>
<p><strong>2. コーポレート IT 内でプロジェクトが複数動いているため、他のプロジェクトの進捗に影響がでないように、工数をさほどかけずにクイックに開発できること</strong></p>
<p><strong>3. なるべくランニングコストをかけないこと</strong></p>
<p>という要件から、<strong>AppSheet</strong> と <a href="https://workspace.google.co.jp/intl/ja/products/apps-script/"><strong>Google App Script(以降 GAS)</strong></a>で実装することを決めました。</p>
<p>機能検証期間を含めて 1 週間程度で実装できています。</p>
<blockquote>
<p><a href="https://cloud.google.com/appsheet?hl=ja">AppSheet</a> は、Google から提供されている、ノーコードでアプリケーション開発ができるツールです。</p>
<p>AppSheet は、エンジニアではなく「現場で働く人が作る」という思想をもとにサービスが開始されました。そのため、プログラミング知識がない人でも、マウス操作のみで簡単にアプリケーションを開発できるようになっています。</p>
</blockquote>
<h2 id="サービス連携図">サービス連携図</h2>
<p>UI を AppSheet で、Slack への通知を GAS で実装しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_004.jpg","alt":"","index":0}"></p>
<h2 id="画面構成">画面構成</h2>
<p>画面は依頼者選択画面、依頼内容一覧画面、依頼詳細一覧画面の3画面で構成しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_002.jpg","alt":"","index":0}"></p>
<h1 id="呼び鈴アプリの実装">呼び鈴アプリの実装</h1>
<p>呼び鈴アプリをどのように実装しているか解説していきます。</p>
<h2 id="appsheet-の設定">AppSheet の設定</h2>
<p>AppSheet を利用したアプリの作成方法については、Google 公式の <a href="https://about.appsheet.com/how-to-create-an-app/">How to create an app</a> や Google で AppSheet を検索するといくつか作成例のサイトが表示されますので、今回の機能を実装するにあたり重要な <strong>「依頼詳細一覧画面から Slack 通知」</strong> 設定の部分にフォーカスして解説します。</p>
<h3 id="1-data-の設定">1. Data の設定</h3>
<p>Tables と Slices について、それぞれ設定します。</p>
<blockquote>
<p>Tables では、アプリケーションの元となるデータを格納する「テーブル」を作成します。
Slices では、テーブルから必要な行と列のみを表示させたり、条件にあうデータのみを表示させたりして、必要なデータを抽出した「テーブルの一部分」を作成します。</p>
</blockquote>
<h4 id="1-1-tables-の設定">1-1. Tables の設定</h4>
<p>今回 Table は以下の 5 つを用意しました。</p>
<p>テーブルは、スプレッドシートにテーブルとなるデータを作成した後、AppSheet と連携します。連携方法は <a href="https://about.appsheet.com/how-to-create-an-app/#step2">Connect your data to AppSheet</a> の動画を参考にしてください。</p>
<table><thead><tr><th>テーブル名</th><th>利用用途</th></tr></thead><tbody><tr><td>Client</td><td>依頼者選択画面で選択されたユーザー名を一時的に保存するテーブル</td></tr><tr><td>Doorbell</td><td>依頼内容一覧画面に表示するテーブル</td></tr><tr><td>DoorbellDetails</td><td>依頼詳細一覧画面に表示するテーブル</td></tr><tr><td>CallHistory</td><td>呼出履歴を保存するテーブル</td></tr><tr><td>UserList</td><td>依頼者選択画面に表示するテーブル</td></tr></tbody></table>
<p>依頼詳細一覧画面と呼び出し機能に関連する、「DoorbellDetails」と「CallHistory」について詳しく説明します。</p>
<h5 id="-doorbelldetails">・ DoorbellDetails</h5>
<p>依頼詳細一覧画面に表示したい内容を一覧で記載します。</p>
<p>分類 ID 列は Doorbell でも同名の列を作成し、そこで設定した値と合わせてください。また、対応 ID は分類 ID 内でユニークになるように設定してください。</p>
<p>対応説明に関しては、任意で入力していただければ大丈夫です。</p>
<p>SlackChannel 列に Slack チャンネルに投稿する用の Webhook URL を設定してください。<a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">Incoming Webhooks</a> 等を使用すると、 Webhook URL が取得できます。</p>
<p>3 行目以降のデータは、 2 行目を参考に設定してください。</p>
<p><strong>スプレッドシートへのデータ入力例</strong></p>
<table><thead><tr><th>行番号</th><th>A 列</th><th>B 列</th><th>C 列</th><th>D 列</th><th>E 列</th></tr></thead><tbody><tr><td>1</td><td>分類 ID</td><td>対応 ID</td><td>対応名</td><td>対応説明</td><td>SlackChannel</td></tr><tr><td>2</td><td>receipt</td><td>1</td><td>入社にあたって貸与物を受け取りたい</td><td></td><td>(通知先チャンネルの Webhook URL を設定)</td></tr></tbody></table>
<p><strong>Table での設定例</strong></p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_008c.jpg","alt":"","index":0}"></p>
<p>分類 ID と対応 ID を複合キー(分類 ID と対応 ID に「REQUIRE」を設定)としています。</p>
<h5 id="-callhistory">・ CallHistory</h5>
<p>呼出履歴を保存するテーブルのため、ヘッダー以外の入力は不要です。</p>
<p><strong>スプレッドシートへのデータ入力例</strong></p>
<table><thead><tr><th>行番号</th><th>A 列</th><th>B 列</th><th>C 列</th><th>D 列</th><th>E 列</th><th>F 列</th><th>G 列</th></tr></thead><tbody><tr><td>1</td><td>CH_id</td><td>CH_分類 ID</td><td>CH_対応 ID</td><td>CH_対応名</td><td>CH_SlackChannel</td><td>CH_呼び出し日時</td><td>CH_依頼者</td></tr></tbody></table>
<p><strong>Table での設定例</strong></p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_008d.jpg","alt":"","index":0}"></p>
<h4 id="1-2-slices-の設定">1-2. Slices の設定</h4>
<p>次に、Slice の設定を行います。Slice は依頼内容ごとに画面表示を切り替えるために「DoorbellDetails」のデータを元に作成します。</p>
<p>貸与物受け取りに関する Slice の場合、以下のように設定します。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_008f.jpg","alt":"","index":0}"></p>
<table><thead><tr><th>設定項目名</th><th>設定値</th></tr></thead><tbody><tr><td>Slice name</td><td>貸与物受け取り</td></tr><tr><td>Source table</td><td>DoorbellDetails</td></tr><tr><td>Row filter condition</td><td><code>[分類 ID]="receipt"</code></td></tr></tbody></table>
<h3 id="2-app-の設定">2. App の設定</h3>
<p>Views について、それぞれ設定します。</p>
<h4 id="2-1-views-の設定">2-1. Views の設定</h4>
<p>依頼詳細一覧画面について詳しく説明します。</p>
<h5 id="依頼詳細一覧画面">・依頼詳細一覧画面</h5>
<p>依頼内容一覧画面から遷移できるように、依頼詳細一覧画面は依頼内容ごとに準備します。ここでは貸与物受け取り画面を例として設定しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_009e.jpg","alt":"","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_009f.jpg","alt":"","index":0}"></p>
<table><thead><tr><th>設定項目名</th><th>設定値</th></tr></thead><tbody><tr><td>View name</td><td>貸与物受け取り画面</td></tr><tr><td>For this data</td><td>貸与物受け取り</td></tr><tr><td>View type</td><td>deck</td></tr><tr><td>Position</td><td>ref</td></tr><tr><td>Primary header</td><td>_RowNumber</td></tr><tr><td>Secondary header</td><td>対応名</td></tr><tr><td>Event Actions(Row Selected)</td><td>SaveAndMoveToClientForm(Actions の設定後、設定可能になります)</td></tr></tbody></table>
<h3 id="3-actions-の設定">3. Actions の設定</h3>
<p>各ボタンを押したときの動作を定義します。</p>
<p>実際には他にもいくつか Action を設定していますが、Slack 通知に関係する Action は以下の 3 つです。</p>
<table><thead><tr><th>Action 名</th><th>利用用途</th></tr></thead><tbody><tr><td>MoveToClientForm</td><td>依頼者選択画面に遷移する</td></tr><tr><td>SaveCallHistory</td><td>CallHistory にデータを保存する</td></tr><tr><td>SaveAndMoveToClientForm</td><td>CallHistory に保存後、依頼者選択画面に遷移する</td></tr></tbody></table>
<h4 id="-movetoclientform">・ MoveToClientForm</h4>
<p>依頼者選択画面に遷移する Action です。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_010c.jpg","alt":"","index":0}"></p>
<table><thead><tr><th>設定項目名</th><th>設定値</th></tr></thead><tbody><tr><td>Action name</td><td>MoveToClientForm</td></tr><tr><td>For a record of this table</td><td>DoorbellDetails</td></tr><tr><td>Do this</td><td>App: go to another view within this app</td></tr><tr><td>Target</td><td>”#view=依頼者選択”</td></tr></tbody></table>
<h4 id="-savecallhistory">・ SaveCallHistory</h4>
<p>「CallHistory」テーブルにデータを保存する Action です。</p>
<p>選択された氏名については「Client」テーブルからデータを取得しています。</p>
<p>また、SlackChannel 列の記載がない場合は、画面に「呼び出し」ボタンを表示させないように設定しています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_010d1.jpg","alt":"","index":0}">
<img __ASTRO_IMAGE_="{"src":"./note1223_010d2.jpg","alt":"","index":0}"></p>
<table><thead><tr><th>設定項目名</th><th>設定値</th></tr></thead><tbody><tr><td>Action name</td><td>SaveCallHistory</td></tr><tr><td>For a record of this table</td><td>DoorbellDetails</td></tr><tr><td>Do this</td><td>Data: add a new row to another table using values from this row</td></tr><tr><td>Set these columns</td><td>CH_id = UNIQUEID()</td></tr><tr><td></td><td>CH_分類 ID = [分類 ID]</td></tr><tr><td></td><td>CH_対応 ID = [対応 ID]</td></tr><tr><td></td><td>CH_対応名 = [対応名]</td></tr><tr><td></td><td>CH_SlackChannel = [SlackChannel]</td></tr><tr><td></td><td>CH_呼び出し日次 = TODAY() & ” ” & TIMENOW()</td></tr><tr><td></td><td>CH_依頼者 = Client[ご自身の氏名]</td></tr><tr><td>Display name</td><td>呼び出し</td></tr><tr><td>Only if this condition is true</td><td>ISNOTBLANK([SlackChannel])</td></tr><tr><td>Needs confirmation?</td><td>True</td></tr><tr><td>Confirmation Message</td><td>CONCATENATE(“依頼者:“,Client[ご自身の氏名],"",“呼び出し内容:”,[対応名],"",“担当者を呼び出しますか?”)</td></tr></tbody></table>
<h5 id="-saveandmovetoclientform">・ SaveAndMoveToClientForm</h5>
<p>呼出履歴に保存後、依頼者選択画面に遷移する Action です。この Action を依頼詳細一覧画面の Event Actions に紐付けます。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_010f.jpg","alt":"","index":0}"></p>
<table><thead><tr><th>設定項目名</th><th>設定値</th></tr></thead><tbody><tr><td>Action name</td><td>SaveAndMoveToClientForm</td></tr><tr><td>For a record of this table</td><td>DoorbellDetails</td></tr><tr><td>Do this</td><td>Grouped: execute a sequence of actions</td></tr><tr><td>Actions</td><td>SaveCallHistory</td></tr><tr><td></td><td>MoveToClientForm</td></tr></tbody></table>
<h2 id="スプレッドシート-の設定">スプレッドシート の設定</h2>
<p>AppSheet からスプレッドシートの「CallHistory」シートに呼出履歴データが登録(行追加)されたら、Slack 通知 GAS を実行するように設定します。</p>
<h3 id="gas-の記述">GAS の記述</h3>
<p>先程テーブルを作成したスプレッドシートの拡張機能 →Apps Script を開きます。</p>
<p>App Script 上で新しく「.gs」ファイルを作成し、「CallHistory」 のシートが更新されたときだけ、Slack に通知するように GAS を記述します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#569CD6">function</span><span style="color:#DCDCAA"> onChange</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#6A9955"> // イベントが"EDIT"の場合のみ実行。</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">changeType</span><span style="color:#D4D4D4"> === </span><span style="color:#CE9178">"EDIT"</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> ar</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">source</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getActiveRange</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> sheet</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">source</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getActiveSheet</span><span style="color:#D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // CallHistory シートが更新された場合のみ Slack に通知する</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">source</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getSheetName</span><span style="color:#D4D4D4">() == </span><span style="color:#CE9178">"CallHistory"</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> inquiry</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">sheet</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getRange</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">ar</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getRow</span><span style="color:#D4D4D4">(), </span><span style="color:#B5CEA8">4</span><span style="color:#D4D4D4">).</span><span style="color:#DCDCAA">getValue</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> slackWebHook</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">sheet</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getRange</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">ar</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getRow</span><span style="color:#D4D4D4">(), </span><span style="color:#B5CEA8">5</span><span style="color:#D4D4D4">).</span><span style="color:#DCDCAA">getValue</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> clientName</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">sheet</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getRange</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">ar</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getRow</span><span style="color:#D4D4D4">(), </span><span style="color:#B5CEA8">7</span><span style="color:#D4D4D4">).</span><span style="color:#DCDCAA">getValue</span><span style="color:#D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> notifyToSlack</span><span style="color:#D4D4D4">(</span></span>
<span class="line"><span style="color:#9CDCFE"> slackWebHook</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#CE9178"> "コーポカウンター呼び鈴"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#CE9178"> ":notification_bell:"</span><span style="color:#D4D4D4">, </span><span style="color:#6A9955">// お好きな Slack の絵文字を設定して下さい。</span></span>
<span class="line"><span style="color:#CE9178"> `<!here></span></span>
<span class="line"><span style="color:#569CD6"> ${</span><span style="color:#9CDCFE">clientName</span><span style="color:#569CD6">}</span><span style="color:#CE9178">さんが</span><span style="color:#569CD6">${</span><span style="color:#9CDCFE">inquiry</span><span style="color:#569CD6">}</span><span style="color:#CE9178">で呼び出ししています。`</span></span>
<span class="line"><span style="color:#D4D4D4"> );</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="js"><code><span class="line"><span style="color:#569CD6">function</span><span style="color:#DCDCAA"> notifyToSlack</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">url</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">username</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">icon</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">message</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> jsonData</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#9CDCFE"> username:</span><span style="color:#9CDCFE"> username</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> icon_emoji:</span><span style="color:#9CDCFE"> icon</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> text:</span><span style="color:#9CDCFE"> message</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> };</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> payload</span><span style="color:#D4D4D4"> = </span><span style="color:#4FC1FF">JSON</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">stringify</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">jsonData</span><span style="color:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> options</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#9CDCFE"> method:</span><span style="color:#CE9178"> "post"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> contentType:</span><span style="color:#CE9178"> "application/json"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> payload:</span><span style="color:#9CDCFE"> payload</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> UrlFetchApp</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">fetch</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">url</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">options</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<h3 id="トリガーの設定">トリガーの設定</h3>
<p>スプレッドシートに呼出履歴が登録(行追加)されたら、<code>onChange()</code> を実行するように設定します。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_007.jpg","alt":"","index":0}"></p>
<p>問題なく動作すると、Slack に以下のようなメッセージが通知されます。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_013.jpg","alt":"","index":0}"></p>
<h3 id="実装する上で難しかったこと">実装する上で難しかったこと</h3>
<p>AppSheet でアプリを開発するにあたり、ノーコード開発の制約でかゆいところに手が届かず、いくつか工夫して実装する必要がありました。</p>
<p><strong>1. 氏名の選択結果を保持したまま、依頼内容選択の結果を「CallHistory」に書き出すこと</strong></p>
<p>AppSheet が複数の選択結果を保持できるような仕組みになっておらず、依頼者選択画面から依頼内容一覧画面に遷移すると選択結果が消えるため、依頼者選択結果をデータとしてどこかに保持する必要がありました。</p>
<p>→ 氏名の選択結果を格納する一行だけのテーブル「Client」を作成し、「CallHistory」への書き出し時に「Client」の一行目のデータを読むように設定しました。</p>
<p><strong>2. ボタンの配置と表示が変更できないこと</strong></p>
<p>基本的にボタンに表示されているテキストは変更できず、ボタン位置も自由に変更できないため(画面の最上部か最下部のみ)、画面の設計には苦労しました。</p>
<p>→ テキストに関しては、Settings → Views → Localization で「Save」を「次へ」に変更して対応しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_012.jpg","alt":"","index":0}"></p>
<p>ボタン位置に関してはどうにもならなかったため、なるべく導線がわかりやすいように画面上に説明を入れるようにしています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1223_014.jpg","alt":"","index":0}"></p>
<p>ノーコードがゆえの制約はありますが、AppSheet での開発は簡単かつ迅速にできるため、小規模なアプリケーションであれば、おすすめの環境です。</p>
<p>以上、呼び鈴アプリの開発についてお話しました。</p>
<p>ここからは、コーポレート IT についてご紹介していきます。</p>
<h1 id="about">コーポレート IT について</h1>
<p>コーポレートデザイン部は、<strong>「未来志向で社内の IT インフラを含めたオフィス環境をデザインすること」</strong> をミッションに業務しており、現在 2 つのチームがあります。</p>
<p>一つはワークプレイスチームで、ペン一本からオフィス施工に関することまで、オフィス環境全般を担当しています。</p>
<p>そして、もう一つがコーポレート IT チームで、全社で利用する SaaS の管理やオフィスのネットワークインフラ、社内アプリの開発等、全従業員が利用する IT ソリューション全般の企画・開発・運用を担当しています。</p>
<h2 id="コーポレート-it-の役割">コーポレート IT の役割</h2>
<p>コーポレート IT としてのミッションは、**「テクノロジーと創意工夫で従業員のパフォーマンスの最大化を図ること」**です。</p>
<p>そのために、単に上流・下流というフェーズ分担ではなく、システム導入だけでもない、全社の組織カルチャーづくりも見据えた全従業員が利用するコーポレート基盤をデザインし、そのデザインに基づいて IT ソリューションを実装、運用していきます。</p>
<p>逆に、各部門のミッション達成、業務目的達成のための個別の IT ソリューションは、ツールゆえ各部門が主管です。コーポレート IT は IT プロフェッショナルとしてアドバイスしたり、システム設計や実装する形で、技術的実現を担うものとして各部門とコラボします。</p>
<p>各部門の IT ソリューションに対する PDCA を各部門がオーナーシップを持つことで、利用時の要望や改善に対して、非常に早いサイクルで実施することができています。</p>
<h1 id="業務内容紹介">業務内容紹介</h1>
<p>コーポレート IT では、日々の従業員への IT サポート以外に、複数のプロジェクトが同時に走っています。以下に記載している業務はほんの一例です。</p>
<ul>
<li>端末管理アプリケーションおよびセキュリティ製品の企画/導入/運用</li>
<li>オフィス移転でのネットワーク構築(ネットワークの設計、機器選定〜運用まで)</li>
<li>社外来客用受付システムの改修/運用</li>
<li>呼び鈴アプリの企画/開発/運用</li>
<li>従業員向けの情報セキュリティ研修</li>
<li>人事基幹システムの導入支援/各システムとの連携機能構築
etc</li>
</ul>
<p>また、メンバー自ら提案して企画化、予算を確保することもあり、アイデアに対して柔軟に対応しています。</p>
<h1 id="業務を行う上で常に意識していること大事にしていること">業務を行う上で常に意識していること・大事にしていること</h1>
<p>コーポレート IT として業務を行う上で大事にしている考え方が、3 つあります。従業員の生産性が向上する環境の構築に必要不可欠と考えており、これらをもとに意思決定しています。</p>
<h2 id="1-システム選定技術選定の考え方">1. システム選定/技術選定の考え方</h2>
<p>システム選定/技術選定の原則としては、グローバルトップシェアで、かつ、北米急成長企業が選定しているものを未来志向で活用していくことを意識していますが、その技術やツールを導入して、得たい本来の目的が達成できるかどうかを、広い視点から判断して意思決定しています。</p>
<h2 id="2-ユーザー-it-サポートの考え方">2. ユーザー IT サポートの考え方</h2>
<p>老子が言っている『授人以魚 不如授人以漁(人に魚を授けることは、漁の仕方を教えるに及ばない)』ということを大切にしており、「わからない」という人に単に答えを「教える」ことをせず、組織の未来のために、組織全体の IT リテラシーを上げること、スムーズに自己解決できる環境を整えることに努めています。</p>
<p>「ユーザー IT サポートが増え続け、サポート人件費が増大し、高コストな生産性の低いコーポレート IT チーム」とならないよう心がけています。</p>
<h2 id="3-ルールを作らず仕組み仕掛けで解決する">3. ルールを作らず、仕組み/仕掛けで解決する</h2>
<p>ルールを覚えなくても、意識しなくとも、狙ったことが自然に進むよう、仕組み/仕掛けによって目的を成す方法を作ることを意識しています。</p>
<p>例えば、高速道路のサービスエリアにある駐車場の逆走を禁止するために斜めに駐車位置ラインを引くといった、意識せずとも行動を一定方向に促す行動設計が望ましく、呼び鈴アプリもそういった仕掛けやプロセス設計の一環です。</p>
<h1 id="さいごに">さいごに</h1>
<p>コーポレート IT は全従業員が利用する IT 基盤の維持・改善が求められるため、非常に重要な役割です。また、急拡大する組織の中においては、今までの仕組みが通用しなくなり、未来志向で新たな仕組みを設計し続けていく必要があり、常にコーポレート IT チームの活躍の余地がたくさん存在しています。</p>
<p>そのため、未来志向で <a href="https://www.medley.jp/team/culture.html">#革新と改善を主導</a> できる方を絶賛募集しております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/corporate-design.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。様々な業種でスペシャリストを募集しています。興味がある方は是非ご連絡ください。医療という社会貢献性の高い領域へ、一緒に挑戦しませんか?</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- NestJS を使ってジョブメドレーアカデミーのバックエンド開発をどのように行なっているのかhttps://developer.medley.jp/entry/2022/11/30/104006https://developer.medley.jp/entry/2022/11/30/104006はじめに
みなさん、こんにちは。エンジニアの山田です。今回はジョブメドレーアカデミー(以下、アカデミー)の開発の皆さんに集まってもらい、TypeScript と NestJS を使ったバックエンド開発をどのように行なっているのかをインタビュ...Wed, 30 Nov 2022 01:40:06 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。エンジニアの山田です。今回はジョブメドレーアカデミー(以下、アカデミー)の開発の皆さんに集まってもらい、<a href="https://www.typescriptlang.org/">TypeScript</a> と <a href="https://nestjs.com/">NestJS</a> を使ったバックエンド開発をどのように行なっているのかをインタビューしました。</p>
<p>以前、アカデミーがリニューアルした際にチームメンバーにインタビューした <a href="https://note.com/medley/n/na0d591fe3f65">note</a> もあるので、未読の方はぜひそちらもご覧いただければと思います。</p>
<h1 id="インタビュイー紹介">インタビュイー紹介</h1>
<h2 id="岸田さん">岸田さん</h2>
<p>SES 会社で業務システム開発、バングラデシュに支社があるオフショア開発をしている会社でブリッジエンジニアという経験を経て、2019 年メドレー入社。 その後ジョブメドレーで開発をリード。現在はアカデミーのプロダクト責任者を務める。</p>
<h2 id="熊本さん">熊本さん</h2>
<p>2019 年メドレーに新卒入社。半年の研修を経てジョブメドレーの開発に携わる。現在はアカデミーの開発に携わる。大学では機械学習を用いた自動車の自動運転の研究をしていた。</p>
<h2 id="牧野さん">牧野さん</h2>
<p>2021 年 11 月にメドレー入社。不動産業界の SaaS を開発している会社でのマネージャー・エンジニアを経験。現在はアカデミーの開発に携わる。</p>
<h2 id="徳永さん">徳永さん</h2>
<p>2022 年メドレーに新卒入社。 OJT 期間中からアカデミーの開発に携わる。大学では、対話 AI システム・ AI との対話用プラットフォームの研究開発をしていた。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1104_001.jpeg","alt":"photo1","index":0}"></p>
<h1 id="アカデミーのアーキテクチャ">アカデミーのアーキテクチャ</h1>
<p><strong>山田</strong>: 早速ですが、現在のアカデミーのアーキテクチャについて話を伺いたいと思います。全体の構成としては現在どんな形になっているんでしょうか。</p>
<p><strong>岸田</strong>: 以前話をしたところから大幅に変わったところはないですが、大まかにはこんな感じになっています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./academy_arch.png","alt":"Architecture","index":0}"></p>
<p><strong>山田</strong>: ありがとうございます、分かりやすいですね。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1104_009.jpeg","alt":"photo2","index":0}">
<em>(手前)岸田さん・(奥)牧野さん</em></p>
<p><strong>山田</strong>: Web フロントエンドに関しては <a href="https://nextjs.org/">Next.js</a> とグローバルの状態管理として <a href="https://www.apollographql.com/apollo-client/">Apollo Client</a> を使っていると伺っています。バックエンドは色々あると思うのですが、DB に関しては <a href="https://www.postgresql.org/">PostgreSQL</a> を使っているんですよね?</p>
<p><strong>岸田</strong>: はい、ORM としては <a href="https://www.prisma.io/">Prisma</a> を使っています。</p>
<p><strong>山田</strong>: こうした開発環境はどのように決まったんでしょうか?特に、開発言語を全て TypeScript で統一する、という意思決定が先にあってバックエンド側のフレームワークは NestJS にしようとなったのか、逆に NestJS を使おうとして TypeScript になったのかをお聞きできれば。</p>
<p><strong>岸田</strong>: 細かいところまでは記憶にないのですが、TypeScript を使って型で開発をしたいというのが一番大きい理由でした。</p>
<p><strong>山田</strong>: まずは TypeScript からだったんですね。そこから Web フレームワークとしては <a href="https://expressjs.com/ja/">Express</a> など他の物もあったと思いますが、NestJS を採用した理由は何だったのでしょうか?</p>
<p><strong>岸田</strong>: そうですね、色々選択肢はあったと思うんですが、現実的にサービスを運用するとなると Express と NestJS のどちらかかな、という感じになっていました。</p>
<p>どちらも実開発での経験を持っているメンバーはいなかったんですが、Web 開発で良く使われる Web フレームワークは、基本的な機能やコンセプトについては共通する部分が多いことに加えて、アカデミーも特殊な仕様があるプロダクトではないので、どちらを選んだとしてもメンバーが困ることはないだろうと考えました。</p>
<p>その上で細かい使い勝手などを検討したり GraphQL との親和性の高さや Express という枯れた技術をベースにしていて、<strong>フレームワーク自体の信頼感もあるので NestJS に決めた</strong>という感じでした。</p>
<p><strong>熊本</strong>: 絞り込んだ段階で各メンバーが NestJS を試験的に触り、使用感を見てみましたが、大きく問題になる点もなかったので全員一致で決まった感じですね。</p>
<p><strong>岸田</strong>: 前述のように Express をベースにしているので何か困ったら最悪 Express にすれば良いなというのもありました。</p>
<p><strong>牧野</strong>: ここまで実際大きな問題というのは無かったです。</p>
<p><strong>山田</strong>: 今は <a href="https://graphql.org/">GraphQL</a> を使って API を作っていると思いますが、REST で作るという選択肢は最初から無かったんですか?</p>
<p><strong>岸田</strong>: はい、GraphQL を使えるメンバーが揃っているという状況であえて REST にするメリットは無いなという事で考えていませんでした。外部公開する API があるとか、パフォーマンスがすごく重要な重い API 呼び出しというのがあったら別だったでしょうけども。</p>
<p><strong>山田</strong>: そもそもの話になってしまうんですが、弊社のプロダクト開発でよく使われている <a href="https://rubyonrails.org/">Ruby on Rails</a> を使う選択肢は無かったんでしょうか?</p>
<p><strong>岸田</strong>: 選択肢としてはあるにはあったんですが、プロダクトのフェイズとして現在既に成長をしているサービスであることや、メンバーのスキルセットとして Rails に慣れたメンバーがそこまで多くなかったことを考えると、いかに技術的負債を少なく保ちつつ、機能開発をしていけるかという点が重要になるので、現在の技術構成にしました。</p>
<p>これが既存サービスのリニューアルではなくて、新規で一から全部作っていかないといけないという状況であれば Rails を使うかもしれませんが、やっぱり型があってある程度開発効率が高くなるというのは魅力的だったので、<strong>これからどんどんと機能開発をするには今の構成が良い</strong>と判断しました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1104_006.jpeg","alt":"photo3","index":0}">
<em>(手前)徳永さん・(奥)熊本さん</em></p>
<p><strong>山田</strong>: DB に関して PostgreSQL ですが、<a href="https://www.mysql.com/jp/">MySQL</a> などにするということは考えなかったですか?</p>
<p><strong>熊本</strong>: そうですね、リニューアル前のアカデミーが使っていたということがあり、大きく変えるメリットはあまり無かったのが大きいです。</p>
<p><strong>岸田</strong>: あとこれは自分の感覚なのですが、PostgreSQL の方がスマートに処理できることが多いという印象があるというのもありました。</p>
<p><strong>山田</strong>: 元々サービスで使っていたというのは、やっぱり大きいですね。ORM は Prisma ですよね。例えば他に <a href="https://typeorm.io/">TypeORM</a> などの選択肢もあったと思いますが、こちらはどういった経緯で使うことになりましたか?</p>
<p><strong>熊本</strong>: 色々と触ってみた結果、Prisma が<strong>自分たちの使い方にマッチしてた</strong>のが大きかったです。がっつりと SQL を自動生成するなどの手厚い機能は、自分達の使い方では必要もなかったですし。キャッチアップのためにペアプロなどもしましたが、<strong>メンバーも使いやすい</strong>ということが確認できました。</p>
<p><strong>山田</strong>: 今ペアプロの話が出ましたが、どのような目的でやっていたんでしょうか。</p>
<p><strong>熊本</strong>: <strong>初期の頃はメンバー間の知識などに偏りもあったので、そういったものを実際にコードを触りながら埋めていく</strong>という目的でした。実際に、GraphQL を使ったことがなかったメンバーも早く慣れてくれました。</p>
<p><strong>山田</strong>: なるほど。ある種学習も兼ねていたんですね。最終的に現在このアーキテクチャの使い勝手としては感想としてどうでしょうか?</p>
<p><strong>岸田</strong>: 言葉は違うかもしれないですが「<strong>普通に使い勝手が良い</strong>」という感じです。</p>
<p>もちろんメンバーが習熟してきたので、色々とコード的に改善しようという部分はあったりもしますが、今のところ本当に困った問題というのは無いので、非常に良い選択だったかと思います。</p>
<h1 id="nestjs-のテストコードについて">NestJS のテストコードについて</h1>
<p><strong>山田</strong>: テストコードは基本書いているんですよね?</p>
<p><strong>熊本</strong>: はい、バックエンドは基本的に Jest で全部書いています。フロントエンドに関しては今まさに E2E テストをどのように実施していくかを試行錯誤しているところですね。</p>
<p><strong>山田</strong>: NestJS でテストコード書くのに何か困ったこととかありましたか?</p>
<p><strong>岸田</strong>: 困っているというわけではないですが、Rails でほぼ必ず使われている <a href="https://github.com/thoughtbot/factory_bot">Factorybot</a> に相当する機能が欲しいですね…。</p>
<p><strong>山田</strong>: 確かに <a href="https://nodejs.org/ja/">Node.js</a> だとそういったライブラリを見かけないですね…。</p>
<p><strong>岸田</strong>: 皆さんあんまり困ってないのかな?ということで今は簡易的なものを自作して使っています。</p>
<p><strong>山田</strong>: OSS で作ったら需要があるかもしれませんね。</p>
<h1 id="現状の-nestjs-での開発の課題感">現状の NestJS での開発の課題感</h1>
<p><strong>山田</strong>: ここまで NestJS での開発について聞いてきましたが、プロダクト開発での課題は何かあったりしますか?</p>
<p><strong>岸田</strong>: 先ほども少し話をしたメンバーの習熟度が高くなったことによるコード改善の他には、NestJS というか Node.js が Rails に比べると定番ライブラリが少なめなのがあります。</p>
<p>例えば、<a href="https://sidekiq.org/">Sidekiq</a> のようなライブラリって便利だと思うんですが、相当するライブラリでデファクトスタンダードというようなものが Node.js だと少ない気がしています。ライブラリが一杯あるのですが、デファクトまでいかないというものが多いという感想です。</p>
<p><strong>山田</strong>: 確かに Node.js だと同じ目的のライブラリが複数あって機能がちょっとずつ違うという感じがありますね。</p>
<p><strong>牧野</strong>: あとは機能開発にリソースを取っているので、細かい部分でのライブラリのアップデートが遅れ気味というのが課題感としてあります。</p>
<p>今のところはとてつもなく遅れているというわけではないのですが、積り積っていくとこういった部分が負債になっていくので、対応したいと思っています。</p>
<p><strong>岸田</strong>: こういったものも含めてプロダクト開発を円滑に回すために、自分も含めた各メンバーのスキルを広げて深くしていかないといけないなというのもありますね。</p>
<h1 id="チームへのオンボーディングについて">チームへのオンボーディングについて</h1>
<p><strong>山田</strong>: さて、話が変わって現在アカデミーで一番最後にジョインしたのが徳永さんだと思いますが、オンボーディングなどはどのような感じだったんでしょうか。学生時代に NestJS はがっつり触っていたんでしたっけ?</p>
<p><strong>徳永</strong>: いえ、学生時代はチュートリアルをしたくらいで、深く使っていたということはなかったです。ですので、最初は全然プロダクトのコードが分からないというレベルで、ほとんど未経験でした。</p>
<p><strong>山田</strong>: なるほど。ほぼ未経験で新卒研修を経て、アカデミーの開発チームにジョインして困った部分などはありましたか?</p>
<p><strong>徳永</strong>: TypeScript を使っているというのは凄く自分にとって良くて、言語でつまづくということはほとんどありませんでした。</p>
<p>やはり<strong>公式ドキュメントがすごく充実していますし、コミュニティの質の高い情報も多い</strong>ので。プロダクトに関しては過去の Pull Request や Issue 上のやり取りなどで、<strong>どういう意図で実装されたものなのかを参照できたのが大きかった</strong>です。一方でバックエンドのデザインパターンなどは経験が少なく苦労した部分がありました。その部分はドキュメントで学習したり、周りのメンバーに直接質問しながらキャッチアップしていきました。</p>
<p><strong>山田</strong>: 今年は入社後研修も TypeScript で書く割合が Ruby よりも多かったので、それがもし逆だったら、もっと苦労していたかもしれないですね。</p>
<p><strong>徳永</strong>: そうですね、その可能性は十分にあり得ます。</p>
<p><strong>山田</strong>: あとアカデミーチームは火水木に勉強会を開催していますよね。ジョブメドレーの開発チームでも噂になっています。</p>
<p><strong>岸田</strong>: これは全員で集まる勉強会というわけではなく、<strong>月によって各自テーマを決めて火水木 1 時間ずつスキルアップのために時間を確保し、学んだことなどをアウトプットしていく</strong>という形式です。アウトプットしたものは、お互いに確認して知識を深めていこうというものですね。</p>
<p><strong>山田</strong>: それは素敵な取り組みですね。あとは <a href="https://www.figma.com/ja/">Figma</a> で開発ドキュメントをまとめているのが印象的ですよね。</p>
<p><strong>牧野</strong>: 最初は岸田さんが率先して、開発した部分のドキュメントなんかを書いていたんですが、最近はチームで共有するようなものもほとんど Figma でまとめるようになっています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note1104_010.png","alt":"Figma","index":0}"></p>
<p><strong>熊本</strong>: もちろん永続化しないといけないドキュメントは README なりコンフルエンスなりに書いていくのですが、ちょっとしたものを気軽に Figma にまとめられるのはすごく良いです。</p>
<p><strong>岸田</strong>: <strong>他の人がやっていることなんかも絶対に目につく</strong>ので、そういった意味でもドキュメントが散らばらなくて今は良く機能していると思っています。</p>
<h1 id="現状の課題感とアカデミーの開発に向く人">現状の課題感とアカデミーの開発に向く人</h1>
<p><strong>山田</strong>: 最後になりますが、アカデミーに来てほしい人材はどういった方でしょうか?</p>
<p><strong>徳永</strong>: この<strong>プロダクトにわくわくできる方に来てほしい</strong>と思います。福祉業界に明るい方なんかは大歓迎です!</p>
<p><strong>山田</strong>: なるほど。プロダクトのビジネス面の理解をして楽しんで開発できる方ですね。技術的な部分では何かありますか?</p>
<p><strong>岸田</strong>: 今まで TypeScript と NestJS を中心に話していましたが、実はこれらができる人というのは必須条件ではありません。どの言語でもフレームワークでも<strong>ちゃんと Web 開発が分かっている方</strong>であればという感じです。</p>
<p>他には<strong>情報感度が高い人が良い</strong>と思います。言語化が難しいのですが、開発環境や言語、ライブラリなどできちんと最新の情報が取捨選択できる方がいると、チームがより活性化すると考えています。</p>
<p>バックエンドでいうとベース部分などもきちんと手を入れることができて、なおかつ人に教えるのが好きな方が入ってもらえると、チームへの知識伝播などもより上手くいくと考えています。</p>
<p>あるとより良いなという所だと、現在アプリは <a href="https://reactnative.dev/">React Native</a> で作られているんですが、React Native でのアプリ開発に強い方だと嬉しいですね。ユーザーに一番使われているのはアプリなので、さらにそのユーザビリティなどを高めるためには、アプリ開発に強い方に入ってもらえるとありがたいです。</p>
<p><strong>山田</strong>: よりアカデミーというプロダクトをドライブさせてくれる方にぜひ来てほしいところですね。本日はありがとうございました!</p>
<h1 id="おわりに">おわりに</h1>
<p>メドレーの開発チームの中でも野心的なアーキテクチャで開発をしているアカデミーチームについてお伝えしました。</p>
<p>サービスを伸ばすために、適切に言語やフレームワークを選択していき、柔軟性を持って開発していってるんだなというのが伝わってきたインタビューでした。少しでも興味が出た方はぜひ、下のリンクからお話できればと思います!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- S3 Object Lambda を使って処方箋プレビューに透かしを入れるhttps://developer.medley.jp/entry/2022/10/28/153818https://developer.medley.jp/entry/2022/10/28/153818こんにちは。医療プラットフォーム本部プロダクト開発室エンジニアの中畑です。主にオンライン診療・服薬指導アプリ CLINICS の開発を担当しています。
今回は CLINICS アプリ内で扱う処方箋プレビューに透かし(watermark)を入...Fri, 28 Oct 2022 06:38:18 GMT<p>こんにちは。医療プラットフォーム本部プロダクト開発室エンジニアの中畑です。主にオンライン診療・服薬指導アプリ <a href="https://clinics-app.com/">CLINICS</a> の開発を担当しています。</p>
<p>今回は CLINICS アプリ内で扱う処方箋プレビューに透かし(watermark)を入れた話を紹介したいと思います。なぜ実施したのか、実装方法、パフォーマンスチューニングの 3 本立てでお送りしたいと思います。</p>
<h1 id="課題と解決方針">課題と解決方針</h1>
<p>まず、なぜ処方箋プレビューに透かしを入れることにしたのか。</p>
<p>CLINICS では診察後に患者が希望すると、かかりつけ薬局支援システム <a href="https://pharms-cloud.com/">Pharms</a> を導入している調剤薬局にてオンライン服薬指導を受けることができます。その際に医療機関から処方箋の画像ファイルや PDF をアップロードし、患者は CLINICS アプリを通じて、オンライン服薬指導を受けたい調剤薬局に処方箋データを送る必要があります。</p>
<img src="https://pharms-cloud.com/images/media-kit/online-flow.png" alt="オンライン服薬指導の流れ" width="1092" height="236">
<p>患者はオンライン服薬指導を予約する前に医療機関からアップロードされた処方箋画像をプレビューできます。この処方箋プレビューは原本とは扱いが異なるため、患者がアプリ内でそれぞれを明確に区別できるような対応を入れる必要があり、その手段として処方箋プレビューに透かしを入れることにしたというのが理由となります。</p>
<p><img __ASTRO_IMAGE_="{"src":"./shohosen.jpg","alt":"処方箋プレビューの透かし対応前後","index":0}"></p>
<h1 id="実現実装方法">実現・実装方法</h1>
<p>まずは画像に透かしを入れる方法を考え、それからシステム上でどのタイミングで透かしを入れるか検討しました。前提条件ですが、処方箋プレビュー画像は以下のような形で保存されています。</p>
<ul>
<li>フォーマット: JPEG, PNG, GIF, PDF(画像以外もある)</li>
<li>保存場所: S3</li>
</ul>
<h2 id="画像に透かしを入れる方法">画像に透かしを入れる方法</h2>
<p>透かしは技術的には 2 枚の画像をアルファブレンド<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref="" aria-describedby="footnote-label">1</a></sup>を使って合成した画像となります。</p>
<p>アルファブレンドの実現方法は言語やライブラリによっていろいろありますが、今回は処方箋を扱うシステムが <a href="https://go.dev/">Go</a> を利用しているので、Go の以下の標準ライブラリを使って実装しました。また、画像には向き(<code>orientation</code>)の情報が保持されているのですが、向きを意識しないと合成時におかしくなることがあります。端末やツールによって取り扱いが異なる部分が多いので、それを解消してくれる <code>disintegration/imaging</code> パッケージを利用しました。</p>
<ul>
<li><a href="https://pkg.go.dev/image">image パッケージ</a>: ベースライブラリ</li>
<li><a href="https://pkg.go.dev/github.com/yssk22/go/x/ximage">ximage パッケージ</a>: 描画処理</li>
<li><a href="https://github.com/disintegration/imaging">disintegration/imaging パッケージ</a>: 読み込み時に向きを補正</li>
</ul>
<p>詳細は省きますが、以下のようなコードで合成できます</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="go"><code><span class="line"><span style="color:#569CD6">func</span><span style="color:#DCDCAA"> Watermark</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">srcFile</span><span style="color:#4EC9B0"> string</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#6A9955"> // 元画像</span></span>
<span class="line"><span style="color:#9CDCFE"> imgb</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">_</span><span style="color:#D4D4D4"> := </span><span style="color:#9CDCFE">os</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Open</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">srcFile</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#569CD6"> var</span><span style="color:#9CDCFE"> img</span><span style="color:#4EC9B0"> image</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">Image</span></span>
<span class="line"><span style="color:#9CDCFE"> img</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">_</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">imaging</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Decode</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">imgb</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">imaging</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">AutoOrientation</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">true</span><span style="color:#D4D4D4">))</span></span>
<span class="line"><span style="color:#C586C0"> defer</span><span style="color:#9CDCFE"> imgb</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Close</span><span style="color:#D4D4D4">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // 上に重ねる透かし画像</span></span>
<span class="line"><span style="color:#9CDCFE"> wmb</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">_</span><span style="color:#D4D4D4"> := </span><span style="color:#9CDCFE">os</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Open</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"watermark.png"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> watermark</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">_</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">_</span><span style="color:#D4D4D4"> := </span><span style="color:#9CDCFE">image</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Decode</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">wmb</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0"> defer</span><span style="color:#9CDCFE"> wmb</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Close</span><span style="color:#D4D4D4">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // 透かしを配置する場所 (画像サイズによって異なるため、実際は計算が必要)</span></span>
<span class="line"><span style="color:#9CDCFE"> watermarkRect</span><span style="color:#D4D4D4"> := </span><span style="color:#4EC9B0">image</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">Rectangle</span><span style="color:#D4D4D4">{</span><span style="color:#4EC9B0">image</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">Point</span><span style="color:#D4D4D4">{</span><span style="color:#B5CEA8">100</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">100</span><span style="color:#D4D4D4">}, </span><span style="color:#4EC9B0">image</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">Point</span><span style="color:#D4D4D4">{</span><span style="color:#B5CEA8">200</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">300</span><span style="color:#D4D4D4">}}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // 合成処理(ここではバイリニア補間で実施)</span></span>
<span class="line"><span style="color:#9CDCFE"> b</span><span style="color:#D4D4D4"> := </span><span style="color:#9CDCFE">img</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Bounds</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#9CDCFE"> m</span><span style="color:#D4D4D4"> := </span><span style="color:#9CDCFE">image</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">NewNRGBA</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">b</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> draw</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Draw</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">m</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">b</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">img</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">image</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">ZP</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">draw</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">Src</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> draw</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">BiLinear</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Scale</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">m</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">watermarkRect</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">watermark</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">watermark</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Bounds</span><span style="color:#D4D4D4">(), </span><span style="color:#9CDCFE">draw</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">Over</span><span style="color:#D4D4D4">, </span><span style="color:#569CD6">nil</span><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // 合成画像の出力(JPEG 出力。他のエンコーダーを利用すれば他の画像形式も可)</span></span>
<span class="line"><span style="color:#9CDCFE"> imgw</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">_</span><span style="color:#D4D4D4"> := </span><span style="color:#9CDCFE">os</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Create</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"/tmp/dest.jpg"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> jpeg</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Encode</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">imgw</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">m</span><span style="color:#D4D4D4">, &</span><span style="color:#4EC9B0">jpeg</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">Options</span><span style="color:#D4D4D4">{</span><span style="color:#9CDCFE">Quality</span><span style="color:#D4D4D4">: </span><span style="color:#9CDCFE">jpeg</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">DefaultQuality</span><span style="color:#D4D4D4">})</span></span>
<span class="line"><span style="color:#C586C0"> defer</span><span style="color:#9CDCFE"> imgw</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">Close</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<h2 id="pdf-に透かしを入れる方法">PDF に透かしを入れる方法</h2>
<p>また、今回の要件には PDF ファイルにも透かしを入れる必要がありました。PDF にはレイヤーという概念があり、元の PDF に対して透かしを入れるレイヤーを追加すれば実現できます。Go には PDF を標準で扱えるパッケージがなかったので、今回は <a href="https://pkg.go.dev/github.com/hhrutter/pdfcpu/pkg/pdfcpu">pdfcpu</a> を利用しました。</p>
<p>透かしのレイヤーを追加する関数が用意されており、以下のコードだけで実現できて大変便利です。<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref="" aria-describedby="footnote-label">2</a></sup></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="go"><code><span class="line"><span style="color:#9CDCFE">wm</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">_</span><span style="color:#D4D4D4"> := </span><span style="color:#9CDCFE">api</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">ImageWatermark</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"watermark.jpg"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"sc:.8 rel, rot:0"</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">onTop</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">update</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">pdfcpu</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">POINTS</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE">api</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">AddWatermarksFile</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"src.pdf"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"dest.pdf"</span><span style="color:#D4D4D4">, </span><span style="color:#569CD6">nil</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">wm</span><span style="color:#D4D4D4">, </span><span style="color:#569CD6">nil</span><span style="color:#D4D4D4">)</span></span></code></pre>
<h2 id="s3-画像に透かしを入れる方法">S3 画像に透かしを入れる方法</h2>
<p>AWS を利用していると S3 に各種ファイルを保存することも多いと思います。
基本的には S3 画像を読み込んで透かし処理をすれば実現できますが、AWS には S3 のレスポンスを加工するマネージメントサービスがいくつかあり、今回は <a href="https://aws.amazon.com/jp/s3/features/object-lambda/">S3 Object Lambda</a> を利用しました。</p>
<p>S3 Object Lambda を利用することで S3 からファイルを取得する際に <a href="https://aws.amazon.com/jp/lambda/">Lambda</a> による処理を実行したうえで返却することができます。これを利用して、元の画像に Lambda で透かしを入れて返却することができます。</p>
<p>処方箋プレビューに透かしを入れる前の構成は以下のようなイメージです。
<img __ASTRO_IMAGE_="{"src":"./image_001.png","alt":"透かし対応前","index":0}"></p>
<ol>
<li>アプリサーバーにて処方箋プレビューの S3 presigned URL(署名付き URL)を発行</li>
<li>クライアントに返す</li>
<li>クライアントは受け取った URL にアクセス。処方箋プレビューが表示される</li>
</ol>
<p>これに S3 Object Lambda を利用して透かしを入れると以下のようになります。
<img __ASTRO_IMAGE_="{"src":"./image_002.png","alt":"透かし対応後","index":0}"></p>
<ol>
<li>アプリサーバーにて処方箋プレビューの <code>S3 Object Lambda Access Point</code> の presigned URL(署名付き URL)を発行</li>
<li>クライアントは受け取った URL にアクセス</li>
<li><code>S3 Object Lambda Access Point</code> は透かし処理を行う Lambda 関数を実行。その際に <code>S3 Access Point</code> の署名付き URL を発行し、リクエストパラメータに設定</li>
<li><code>Lambda Function</code> は受け取った URL にアクセスし処方箋プレビューの元画像を取得して透かしを入れて返却</li>
<li>透かし入りの処方箋プレビューが表示される</li>
</ol>
<p>新たに <code>S3 Object Lambda Access Point</code>, <code>Lambda Function</code>, <code>S3 Access Point</code> を用意する必要があって少しややこしいですが簡単に説明すると以下のような役割のものとなります。</p>
<table><thead><tr><th>icon</th><th>title</th><th>description</th></tr></thead><tbody><tr><td><img __ASTRO_IMAGE_="{"src":"./Res_Amazon-Simple-Storage-Service_General-Access-Points_48_Light.png","alt":"S3 Access Point","index":0}"></td><td>S3 Access Point</td><td>S3 Bucket に対する alias。S3 Object Lambda は必ずこれを経由して S3 にアクセスする必要がある</td></tr><tr><td><img __ASTRO_IMAGE_="{"src":"./Res_AWS-Lambda_Lambda-Function_48_Light.png","alt":"Lambda Function","index":0}"></td><td>Lambda Function</td><td>S3 のファイルに対してなんらかの処理を実施する関数</td></tr><tr><td><img __ASTRO_IMAGE_="{"src":"./Res_Amazon-Simple-Storage-Service_S3-Object-Lambda-Access-Points_48_Light.png","alt":"S3 Object Lambda Access Point","index":0}"></td><td>S3 Object Lambda Access Point</td><td>S3 Access Point に対して Lambda 関数を実行するためのアクセスポイント</td></tr></tbody></table>
<p>透かし処理を実際呼び出す際は、アプリケーション側からは S3 の presigned URL から S3 Object Lambda Access Point の presigned URL に切り替えるだけなので、透かしありなしの切り替えも簡単です。</p>
<p>S3 presigned URL:</p>
<blockquote>
<p><code>https://[bucket-name].s3.ap-northeast-1.amazonaws.com/prescription.jpg?[signature]</code></p>
</blockquote>
<p>↓
S3 Object Lambda Access Point presigned URL:</p>
<blockquote>
<p><code>https://[s3-object-lambda-access-point]-[account-id].s3-object-lambda.ap-northeast-1.amazonaws.com/prescription.jpg?[signature]</code></p>
</blockquote>
<p>S3 Object Lambda を利用することで以下のようなメリットがあります。</p>
<ul>
<li>追加のインフラ構築が不要
<ul>
<li>既存サーバーのリソース追加や、新たに透かし処理用のサーバーを用意する必要がない。既存のアプリサーバーへの影響を軽微にできる</li>
<li>Lambda が自動的にスケールを実施してくれるので負荷対策が容易</li>
<li>利用した分だけの課金となる(S3 Get + Lambda + S3 Object Lambda の利用料)</li>
</ul>
</li>
<li>透かしを入れた処方箋プレビュー画像を事前に用意する必要がない
<ul>
<li>事前に用意するとリリース前に保持した画像に対しても処理する必要が出てくる</li>
<li>(ただし、今回は特定のユーザーのみのアクセスなので不特定多数のアクセスが見込まれる場合は事前生成したほうがよいかもしれません)</li>
</ul>
</li>
</ul>
<p>もちろん画像だけではなく、テキストデータなど他のものにも利用できるので、S3 に保管した機密情報等をフィルタリングして返したい用途などにもマッチします。</p>
<p>AWS 公式ドキュメントに詳細な解説と様々なユースケースのチュートリアルがありますので、興味がある方は是非御覧ください。</p>
<ul>
<li><a href="https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/transforming-objects.html">S3 Object Lambda を使用したオブジェクトの変換</a></li>
</ul>
<h1 id="パフォーマンスチューニング">パフォーマンス・チューニング</h1>
<p>ここまでで、処方箋プレビューに透かしを入れる処理自体は完成しました。ここで気になってくるのがパフォーマンスについてです。今までは S3 の画像を表示するだけでしたが透かし処理に S3 Object Lambda を使うことになったため、速度と料金が気になるところです。</p>
<p>様々なチューニングポイントがありますが、今回は Lambda のメモリ設定に焦点を絞ってお話したいと思います。</p>
<h2 id="lambda-の-cpu-パフォーマンス">Lambda の CPU パフォーマンス</h2>
<p>Lambda はメモリ設定しかできませんが、メモリ量に比例して CPU 性能が向上する仕組みとなっています。
<a href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-function-common.html">公式ドキュメント</a>上では 1769MB あたり 1vCPU であるとされていて、MAX の 10240MB で 6vCPU 使えるとしています。実際に Lambda の vCPU の設定を調べたブログ (<a href="https://www.sentiatechblog.com/aws-re-invent-2020-day-3-optimizing-lambda-cost-with-multi-threading">SENTIA tech blog</a>) では以下のようになっていたそうです。</p>
<table><thead><tr><th>Memory</th><th>vCPUs</th></tr></thead><tbody><tr><td>128 - 3008 MB</td><td>2</td></tr><tr><td>3009 - 5307 MB</td><td>3</td></tr><tr><td>5308 - 7076 MB</td><td>4</td></tr><tr><td>7077 - 8845 MB</td><td>5</td></tr><tr><td>8846 - 10240 MB</td><td>6</td></tr></tbody></table>
<p>ただし、メモリをいくら増やしたところで関数自体がマルチスレッド・マルチプロセス化されていない場合はパフォーマンス向上が見込めないということにもなります。適切に並列処理が実装されていれば、vCPU が増える境目で大きな性能向上が期待できます。</p>
<h2 id="lambda-の料金">Lambda の料金</h2>
<p>東京リージョン(ap-northeast-1)において、2022 年 10 月現在は以下のような料金となります。<sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref="" aria-describedby="footnote-label">3</a></sup></p>
<ul>
<li>無料枠: 1 ヶ月あたり 100 万リクエスト、40 万 GB-秒(仮に 1 GB で動かしたとして 40 万秒使える)
<ul>
<li>128 MB だと 320 万秒、 10240 MB だと 4 万秒</li>
</ul>
</li>
<li>有料枠: 100 万リクエストあたり 0.20 USD, GB-秒あたり 0.00001666667 USD
<ul>
<li>1 億リクエストあたり 20 USD</li>
<li>128MB で 1 万秒 使うと 0.02083 USD</li>
<li>1024MB で 1 万秒 使うと 0.16666 USD</li>
</ul>
</li>
<li>上記は x86(amd64) の値段、arm だと 2 割引
<ul>
<li>arm は CPU パフォーマンスも 2 割向上すると言われているので、可能であれば arm を選択すると更にコスパ向上が期待できる</li>
</ul>
</li>
</ul>
<p>このように見てみると、サーバーやコンテナを利用した場合はどんなに最小構成でも冗長化を考えると数十ドルはかかるので、特に頻繁に実行されない機能は Lambda によるサーバーレス化を検討すると良さそうです。</p>
<p>また、時間あたりで料金がかかるということは、API などネットワークアクセスで時間を使った場合も料金がかかることに注意が必要です。外部 API のパフォーマンスに依存してしまうので、なるべく Lambda は CPU を使った処理が支配的になるとコスパよく利用することができます。</p>
<h2 id="ベンチマーク">ベンチマーク</h2>
<p>Lambda のパフォーマンス計測には、<a href="https://github.com/alexcasalboni/aws-lambda-power-tuning">AWS Lambda Power Tuning</a> を利用しました。AWS Lambda Power Tuning は Step Functions と Lambda を使って Lambda の速度とコストを計測できるツールです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./state-machine-screenshot.png","alt":"AWS Lambda Power Tuning の Step Functions","index":0}"></p>
<p>Step Functions と Lambda をデプロイした上で、Step Functions の state machine に計測したい Lambda の ARN, memory 設定, 実行回数, payload を入力することでベンチマーク結果を取得できます。
実行すると最終結果に URL が出力され、その URL をブラウザで表示することで、グラフで結果とスコアボードで結果が可視化されます。また 2 つの結果を比較して表示することもできますので改善後に比較することも容易です。<sup><a href="#user-content-fn-4" id="user-content-fnref-4" data-footnote-ref="" aria-describedby="footnote-label">4</a></sup></p>
<p><img __ASTRO_IMAGE_="{"src":"./image_003.png","alt":"ベンチマーク結果の可視化","index":0}"></p>
<h3 id="aws-lambda-power-tuning-の導入">AWS Lambda Power Tuning の導入</h3>
<p><a href="https://github.com/alexcasalboni/aws-lambda-power-tuning/blob/master/README-DEPLOY.md">リポジトリのドキュメント</a>に従い導入していきます。</p>
<p>今回は <a href="https://aws.amazon.com/jp/serverless/serverlessrepo/">AWS Serverless Application Repository(SAR)</a> を使って導入しました。AWS Serverless Application Repository(SAR) とはその名の通り、サーバーレスアプリケーションを管理するリポジトリですが、組織内に限らず公開することも可能で、他の人も再利用可能になっています。また、導入する際は公開ページの Deploy ボタンをポチるだけで自身の AWS アカウント上にデプロイされ、 <a href="https://aws.amazon.com/jp/cloudformation/">CloudFormation</a> の Stack として管理されます。削除も CloudFormation の Stack を削除するだけで実施できます。</p>
<p>AWS Lambda Power Tuning は<a href="https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:451282441545:applications~aws-lambda-power-tuning">こちらから</a>自身のアカウントにデプロイできます。
また、AWS や個人だけでなく Datadog や New Relic など他のパブリッシャーもサーバーレスアプリケーションを公開しているので、他にも使いたいものがないか探してみるとよいかもしれません。</p>
<p><img __ASTRO_IMAGE_="{"src":"./image_004.png","alt":"ポチるだけでインストール完了!","index":0}"></p>
<h3 id="aws-lambda-power-tuning-の実行">AWS Lambda Power Tuning の実行</h3>
<p>デプロイが完了すると、AWS Step Functions 上に state machine があるので、state machine にパラメータを入力することで計測ができます。<a href="https://github.com/alexcasalboni/aws-lambda-power-tuning/blob/master/README-EXECUTE.md">リポジトリのドキュメント</a>にいくつかやり方が記載されています。ここでは
実行パラメータを定義した json ファイルを用意してスクリプトから実行する方法を紹介します。</p>
<p>まずは、<a href="https://github.com/alexcasalboni/aws-lambda-power-tuning">AWS Lambda Power Tuning リポジトリ</a>を <code>git clone</code> し、<a href="https://github.com/alexcasalboni/aws-lambda-power-tuning/blob/master/scripts/sample-execution-input.json">scripts/sample-execution-input.json</a> を編集します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#9CDCFE"> "lambdaARN"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-performance-tuning"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "powerValues"</span><span style="color:#D4D4D4">: [</span><span style="color:#B5CEA8">128</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">256</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">512</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">832</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">1024</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">1536</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">1769</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">1770</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">2048</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">3008</span><span style="color:#D4D4D4">],</span></span>
<span class="line"><span style="color:#9CDCFE"> "num"</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">100</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "payload"</span><span style="color:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#9CDCFE"> "body"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"{</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">id</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">: </span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">123</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">,</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">name</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">:</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">performance</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">}"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "path"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"/api/performance"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "httpMethod"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"GET"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "isBase64Encoded"</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "multiValueHeaders"</span><span style="color:#D4D4D4">: { </span><span style="color:#9CDCFE">"Accept"</span><span style="color:#D4D4D4">: [</span><span style="color:#CE9178">"application/json"</span><span style="color:#D4D4D4">] }</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#9CDCFE"> "parallelInvocation"</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">false</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>入力値の詳細は以下の通りです。<sup><a href="#user-content-fn-5" id="user-content-fnref-5" data-footnote-ref="" aria-describedby="footnote-label">5</a></sup></p>
<ul>
<li>lambdaARN: Lambda Function の ARN</li>
<li>powerValues: 計測したい Lambda のメモリを指定</li>
<li>num: メモリごとの Lambda 実行回数</li>
<li>payload: Lambda Function にわたす Event JSON の内容</li>
<li>parallelInvocation: ベンチマークを並列実行する</li>
</ul>
<p>次に、 <a href="https://github.com/alexcasalboni/aws-lambda-power-tuning/blob/master/scripts/execute.sh">scripts/execute.sh</a> を実行すると、ベンチマークが開始され、最後に結果 URL が出力されます。デプロイ時の設定によって、CloudFormation の Stack Name が違う場合もありますので、必要に応じて書き換えてください。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sh</span><span style="color:#CE9178"> ./scripts/execute.sh</span></span>
<span class="line"><span style="color:#DCDCAA">-n</span><span style="color:#CE9178"> Execution</span><span style="color:#CE9178"> started...</span></span>
<span class="line"><span style="color:#DCDCAA">-n</span><span style="color:#CE9178"> .</span></span>
<span class="line"><span style="color:#DCDCAA">:</span></span>
<span class="line"><span style="color:#DCDCAA">-n</span><span style="color:#CE9178"> .</span></span>
<span class="line"><span style="color:#DCDCAA">SUCCEEDED</span></span>
<span class="line"><span style="color:#DCDCAA">Execution</span><span style="color:#CE9178"> output:</span></span>
<span class="line"><span style="color:#D4D4D4">{</span><span style="color:#DCDCAA">"power"</span><span style="color:#DCDCAA">:128,</span><span style="color:#DCDCAA">"cost"</span><span style="color:#DCDCAA">:8.4E-9,</span><span style="color:#DCDCAA">"duration"</span><span style="color:#DCDCAA">:3.6706666666666674,</span><span style="color:#DCDCAA">"stateMachine"</span><span style="color:#DCDCAA">:</span><span style="color:#CE9178">{</span><span style="color:#D4D4D4">"</span><span style="color:#DCDCAA">executionCost</span><span style="color:#DCDCAA">":4.0E-4,"</span><span style="color:#DCDCAA">lambdaCost</span><span style="color:#DCDCAA">":1.0178708203125003E-4,"</span><span style="color:#DCDCAA">visualization</span><span style="color:#DCDCAA">":"</span><span style="color:#DCDCAA">https://lambda-power-tuning.show/#gAAAAQACQAMABAAG6QbqBgAIwAs</span><span style="color:#CE9178">=</span><span style="color:#D4D4D4">;</span><span style="color:#9CDCFE">NOxqQP9GmkDFII5AbxKBQLErfECGXW1AWDmGQNNNjkBpSo1ARf1nQA</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">=</span><span style="color:#D4D4D4">;</span><span style="color:#9CDCFE">l08QMn1jtDJ9YzQz1pCSM5dPkDNjd9gzb9AbNPzmGzR9YzQ05vRTNA</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">="}}</span></span></code></pre>
<p>計測方法は以上、簡単ですね 👌</p>
<h3 id="aws-lambda-power-tuning-の結果確認">AWS Lambda Power Tuning の結果確認</h3>
<p>計測結果を確認します。関数によって傾向は異なりますが、ここでは 2 種類のパターンを紹介します。</p>
<h4 id="db-や-api-アクセスなど外部処理が支配的なケースnetwork-intensive">DB や API アクセスなど外部処理が支配的なケース(Network-intensive)</h4>
<p>Lambda 内の処理が外部処理で占めている場合は、<a href="https://lambda-power-tuning.show/#gAAAAQACQAMABAAG6QbqBgAIwAs=;NOxqQP9GmkDFII5AbxKBQLErfECGXW1AWDmGQNNNjkBpSo1ARf1nQA==;l08QMn1jtDJ9YzQz1pCSM5dPkDNjd9gzb9AbNPzmGzR9YzQ05vRTNA==">以下のようなグラフ</a>になります。
赤色の実行時間がどのメモリ設定でも 4ms 前後と大きな変化がなく、青色のコストだけが右肩上がりになっています。
この場合は、Lambda の最低メモリである 128MB が最適ということになります。</p>
<p><img __ASTRO_IMAGE_="{"src":"./image_005.png","alt":"Network-intensive","index":0}"></p>
<h4 id="cpu-実行時間が支配的なケースcpu-intensive">CPU 実行時間が支配的なケース(CPU-intensive)</h4>
<p>Lambda 内の処理が内部処理で占めている場合は、<a href="https://lambda-power-tuning.show/#gAAAAQACQAMABAAG6QbqBgAIwAs=;+U9pRV6q0kTBq4NEiuI7ROmlIkQbFyNEV/cMRKk3DESOaAxELLkKRA==;ioUDNwub7TbsiRQ3+zYsNzN9NzciCoo3r0+JN3moiDddZ54398HlNw==">以下のようなグラフ</a>になります。透かし処理は画像合成処理に多くの CPU を利用するので、こちらのパターンとなりました。
メモリが少ないと速度が出ておらず、メモリを増やすに連れて速度も改善しており、コストの増加も緩やかです。ただし、今回はプログラムがマルチスレッド・マルチプロセス対応はしていないため、コア数が増える 1770MB 以降は大きな速度改善は見られず、コストが上がる結果となっています。
よって、このケースではコストだけを考えたら低メモリが有利ですが、速度も考えたら 1024MB ~ 1770MB あたりにすることを検討すると良さそうです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./image_006.png","alt":"CPU-intensive","index":0}"></p>
<h1 id="まとめ">まとめ</h1>
<p>処方箋プレビューに透かしを入れることになった背景、実現方法を紹介いたしました。</p>
<ul>
<li>透かし処理はアルファブレンド処理を実施することで実現でき、PDF については透かし用のレイヤーを重ねることで実現できます。</li>
<li>S3 Object Lambda を利用することで、追加のサーバー構築をすることなく、S3 のファイルに対してフィルタリングをかけることができます。</li>
<li>Lambda のパフォーマンス・チューニングについては、AWS Lambda Power Tuning を利用して可視化できます。外部 API などネットワーク依存(Network-intensive)ではなく、CPU 処理が中心(CPU-intensive)になると、コスパよく利用することができます。</li>
</ul>
<p>S3 のファイルに対してなんらかの処理を実施したいと考えてる方は、一度 S3 Object Lambda の利用を検討してみてはいかがでしょうか?</p>
<p>また、Lambda のチューニングポイントは色々ありますが、AWS Summit Online で紹介された以下のセッションがおすすめなので、もっと深くチューニングしたい方は是非ご覧ください!</p>
<ul>
<li><a href="https://aws.amazon.com/jp/summits/japan/sessions/?aws-summit-japan-2022-cards.sort-by=item.additionalFields.sortOrder&aws-summit-japan-2022-cards.sort-order=asc&awsf.level=*all&awsf.session-category=*all&awsf.company-category=*all&awsf.industry=*all&awsf.use-case=*all&aws-summit-japan-2022-cards.q=Lambda%2BPerformance%2BTuning%2BDeep%2BDive&aws-summit-japan-2022-cards.q_operator=AND">AWS Lambda Performance Tuning Deep Dive〜本当に知りたいのは”ここ”だった〜</a></li>
</ul>
<h1 id="さいごに">さいごに</h1>
<p>メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。医療という分野においては、機微な情報を扱ったり診療という大事な業務を止めないよう、可用性、パフォーマンス、セキュリティともに高いサービスレベルを求められます。興味がある方は是非ご連絡ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<section data-footnotes="" class="footnotes"><h2 class="sr-only" id="footnote-label">脚注</h2>
<ol>
<li id="user-content-fn-1">
<p><small>元画像に直接文字や図形などを描画することももちろん可能ですが、透かしを画像データとしてデザイン、管理したいこともあり、今回はアルファブレンドを選択した。<a href="https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%AB%E3%83%95%E3%82%A1%E3%83%96%E3%83%AC%E3%83%B3%E3%83%89">アルファブレンド - wikipedia</a></small> <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p><small>pdfcpu API document: <a href="https://pkg.go.dev/github.com/pdfcpu/pdfcpu/pkg/api#example-AddWatermarksFile">watermark example</a></small> <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p><small>ただし最初の 60 億 GB 秒/月。昨今は円安なのでドル建てで利用しているインフラコストも上がっており、USD 表記だとコストが変わらないようにみえるので要注意。<a href="https://aws.amazon.com/jp/lambda/pricing/">AWS Lambda 料金</a></small> <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-4">
<p><small>この UI は <a href="https://github.com/matteo-ronchetti/aws-lambda-power-tuning-ui">AWS Lambda Power Tuning UI</a> というツールで別途提供されており、 <code>lambda-power-tuning.show</code> ドメインでアクセスできるので、自前で用意する必要はない。URL は <code>https://lambda-power-tuning.show/#AAEAAgADAAQ=;xKDwRUqaakU5+RtFcXLqRA==;UqkHOPJCBDjA6AM46DAEOA==;AAEAAgADAAQ=;aYP6RdyeeUUQKiVFuC76RA==;ZDoNOJy3DDiJrQs4zhENOA==;100;50</code> のような形式でデータが URL のパスパラメータにエンコードされている。</small> <a href="#user-content-fnref-4" data-footnote-backref="" aria-label="Back to reference 4" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-5">
<p><small>この他にも <code>strategy</code> など様々なパラメータがある: aws-lambda-power-tuning: <a href="https://github.com/alexcasalboni/aws-lambda-power-tuning/blob/master/README-INPUT-OUTPUT.md#user-content-state-machine-input">README-INPUT-OUTPUT.md</a></small> <a href="#user-content-fnref-5" data-footnote-backref="" aria-label="Back to reference 5" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>medley
- クラウド診療支援システム「CLINICS」の患者タッチポイントを強化する PRM プロジェクトはどう作り上げたのかhttps://developer.medley.jp/entry/2022/09/30/222326https://developer.medley.jp/entry/2022/09/30/222326はじめに
みなさん、こんにちは。エンジニアの新居です。クラウド診療支援システム CLINICS が 8 月から大幅にアップデートして、より患者と医療機関が密接に繋がるようになりました。
今回のアップデートで中心になっている機能領域として P...Fri, 30 Sep 2022 13:23:26 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。エンジニアの新居です。クラウド診療支援システム CLINICS が 8 月から大幅に<a href="https://clinics-cloud.com/news/58">アップデート</a>して、より患者と医療機関が密接に繋がるようになりました。</p>
<p>今回のアップデートで中心になっている機能領域として <strong>PRM(Patient Relationship Management)</strong> というものがあります。 この PRM が一体どういったものなのか、プロジェクトはどのように進んでいったのかを関係者にインタビューしていきました。</p>
<p>メドレーで大規模なプロジェクト開発をどのように行なっているかの一端をお見せできればと考えています!</p>
<h1 id="インタビュイー紹介">インタビュイー紹介</h1>
<h2 id="宍戸さん">宍戸さん</h2>
<p>医療プラットフォーム第一本部 プロダクト開発室第二グループに所属。クラウド診療支援システム <a href="https://clinics-cloud.com/">CLINICS</a> の開発責任者。前職は株式会社サイバーエージェントでサーバサイドエンジニアとして従事。2017 年メドレー入社。</p>
<h2 id="酒井さん">酒井さん</h2>
<p>医療プラットフォーム第一本部 プロダクト開発室第二グループに所属。クラウド診療支援システム CLINICS の UI/UX 全般を担当。本プロジェクトのリード。前職は受託開発会社で様々なサイトやシステムのデザインに従事。2019 年メドレー入社。</p>
<h2 id="田中さん">田中さん</h2>
<p>医療プラットフォーム CTO。医療プラットフォームの開発統括をしながら、自身は患者統合基盤の開発を担当。前職までは SIer や IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとして従事。2016 年メドレー入社。</p>
<h2 id="来田さん">来田さん</h2>
<p>前職まではクリニックで精神科医として従事。CLINCS オンライン診療の立ち上げ時から関わる。現在は CLINICS のディレクションを中心として事業部と開発の架け橋となっている。</p>
<h1 id="prm-とはどういうものなのか">PRM とはどういうものなのか?</h1>
<p><strong>新居</strong> : では今回中心となった PRM とはいったい何なのかというところから解説していただいて良いですか? 一般的にはそこまで知られてない言葉なのではないかと思うのですが…。</p>
<p><strong>宍戸</strong>: 元々 CRM(Customer Relationship Management)というマーケティングなどの文脈の用語があります。これは「自社サービスや商品を使っている顧客」と「サービスや商品を提供している企業」との関係性を管理することを指しますが、それをもとに適切なアクションをおこなうことで最終的には利益向上を目指すものと考えられます。<strong>PRM は CRM と同様に医療機関と患者さんの関係性を管理することで必要なサービスを提供する</strong>ためのもの、と考えています。</p>
<p>CLINICS で基幹システムである電子カルテなどではなく、<strong>オンライン診療・予約・問診など患者接点となる機能をターゲットにしてそれらを強化・改善</strong>していったのが、CLINICS の今回の PRM プロジェクトになります。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note0908_010.jpeg","alt":"","index":0}">
<em>CLINICS 開発責任者 宍戸さん</em></p>
<p><strong>新居</strong> : 確かに CRM は最近良く聞く言葉でしたが、PRM は患者と医療サービスを使う医療機関との関係を深めるためのものなんですね。</p>
<h1 id="prm-プロジェクトの始まり">PRM プロジェクトの始まり</h1>
<p><img __ASTRO_IMAGE_="{"src":"./note0908_009.jpeg","alt":"","index":0}"></p>
<h2 id="clinics-に感じていた課題とは">CLINICS に感じていた課題とは</h2>
<p><strong>新居</strong> : このプロジェクトは半年ほどかけていると聞きましたが、「PRM をやっていこう」となったのが半年前からだったんでしょうか?</p>
<p><strong>宍戸</strong> : 元々 CLINICS はオンライン診療のシステムとして 2016 年からサービスを提供してきました。2018 年からは CLINICS カルテとして電子カルテシステムも提供を開始しましたが、そのタイミング以降はオンライン診療部分に関してはそこまで大きくアップデートはしてこなかったんです。</p>
<p>現在はクラウド診療支援システムとして販売していますが、これまでは電子カルテなど基幹部分の強化がメインになっていたからです。しかし、数年開発・運用してきた中で、本来医療機関の診察のメインである対面診療の予約については課題が積み残されていたり、昨今の状況から来る<strong>オンライン診療のニーズなどを始めとして、患者接点を強化していく必要がある</strong>ということになり、今回取り組むことになりました。</p>
<p><strong>酒井</strong> : PRM というと本当にここ最近の潮流なのですが、<strong>CLINICS はもともと「患者とつながる」をコンセプト</strong>としており、今回の機能強化などもこのコンセプトに基づき実施しています。その上で、患者とのタッチポイントで抜けていた部分や強化したほうが良い部分をより作り込んでいったという形です。</p>
<p>例えば、CLINICS は元々オンライン診療のシステムとして始まっており、対面診療予約に関して使いづらさを感じるケースがあったため、対面診療予約をより行いやすくするよう改修を行うなどです。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note0908_011.jpeg","alt":"","index":0}">
<em>CLINICS デザイナー 酒井さん</em></p>
<p><strong>新居</strong> : 元々あったものをさらに患者とのタッチポイントを強化するために、改修・改善していったんですね。</p>
<h1 id="プロジェクトの背景">プロジェクトの背景</h1>
<p><img __ASTRO_IMAGE_="{"src":"./note0908_007.jpeg","alt":"","index":0}"></p>
<h2 id="どのくらいの規模のプロジェクトだったのか">どのくらいの規模のプロジェクトだったのか</h2>
<p><strong>新居</strong> : 半年のプロジェクトということですが、規模としてはどの位の大きさだったんでしょうか?</p>
<p><strong>酒井</strong> : 私が経験したプロジェクトの中でも関わる部署など考えると一番大きかったと思います。開発はもちろんですが、CLINICS チームの<strong>セールス・カスタマーサクセス・マーケティング、広報もなのでほぼ CLINICS の全部署と関わっていました</strong>。</p>
<p><strong>田中</strong> : 他にも、サービスとしては患者統合基盤・ 患者向けアプリ・ Dentis が関係しています。</p>
<p><strong>宍戸</strong> : QA チームにもしっかり入ってもらって QA を行なったりしてもらったので、本当に<strong>医療プラットフォームのほぼ全てのチームと関わって開発していった</strong>感じです。最終的にプロダクトの見せ方や売り方なども含めて調整していったので事業部とも密接に関わって作り上げていきました。</p>
<h2 id="新しい-prm-機能をどうやってプロダクトに融合させたのか">新しい PRM 機能をどうやって、プロダクトに融合させたのか</h2>
<p><strong>新居</strong> : そこまで大きい規模のプロジェクトだということで、PRM の各種機能をどうやって上手くプロダクトに落としこむのか、実際にはどのようにみなさん推進されていったんでしょう。</p>
<p><strong>宍戸</strong> : プロジェクトの推進という意味では、最初は来田さんや( Dentis の運営を行なっている)プロダクト戦略室の平山さんと<strong>スモールスタートで意見を出し合いながらモックを作っていき、まずはビジュアル面から機能のイメージなどを刷り合わせ</strong>ていくのに、酒井さんに手を動かしてもらいながら進めていました。</p>
<p>その過程でこのプロジェクトにおける重要な意思決定がありました。CLINICS ではログイン不要のシンプルな予約機能を以前より提供していました。この機能は患者アプリとは元々違うドメインで運営していたものでしたが、今回のタイミングでこの両者のアプリケーションを患者アプリと同じドメインに統合して運営していこうという決定をしました。</p>
<p>患者アプリは CLINICS チームとは別チームで運営しているという背景もあり、チーム間の連携が格段に増え 、考えなければいけない事柄も多くなります。プロジェクトとして関係者が多くなってきたので、<strong>今までの考え方を 1 度リセットしてプロジェクトの進め方を新しく模索する必要がありました</strong>。</p>
<p>この点がかなり自分の中では大変でした。来田さんも大変だったと思います。こうした全体像を描いたり、全体スケジュールを決めたりというところでは、田中さんにアドバイスをもらいながらプロジェクトを進めていきました。</p>
<h1 id="プロジェクトを推進するために必要だったこと">プロジェクトを推進するために必要だったこと</h1>
<h2 id="難しい意思決定をどのようにしていた">難しい意思決定をどのようにしていた?</h2>
<p><strong>新居</strong> : 途中から一気にプロジェクトがスケールアップしたということなんですね。今回のプロジェクトは色々な部署も関わるし、たくさんの機能や改修が入っています。開発側と事業部側との意識合わせが、かなり必要だったんではないかと思うのですが、その辺はいかがだったでしょうか。</p>
<p><strong>来田</strong> : 例えば、今まで CLINICS では対面診療予約の際に患者のクレジットカード登録を必須としていたのですが、今回の改修でクレジットカード登録を不要に変更できるように対応を行いました。この対応は、CLINICS オンライン診療の初期から念願の機能だったりします。自分は事業部長である田中大介さんと今年の年明けくらいから話をして、メンバーに「よし、このプロジェクトをやっていこう」という話を持っていくようにしていました。やっぱり、大変なプロジェクトだというのはあったのでスタートを切れる状態に徐々にしていった感じです。</p>
<p>プロジェクトが佳境に入ってからは自分は各部署との調整がメインの業務でした。開発側と事業側とか CLINICS と Dentis だとかそういった立場の違いから来る要求の違いを納得できる仕様に落とし込む作業です。やっぱり、それぞれの立場でベストを尽くそうとみんな考えているんですが、全部を取り入れることはできないので。私が調整するときには<strong>それぞれ反対の立場の人たちの考えをきちんと伝えるようにということをしながら、仕様を決めていく</strong>ことを念頭に置いていました。</p>
<p>具体的な調整の例としては、開発する機能とセールスする際のプランの刷り合わせだったりですね。「機能としてはこうしたほうが使いやすいよね」というだけだと、セールス側としては「どこをウリにして売っていくのかというのが見えてこない」という話になったんですね。これはどちらの言い分も一理あるものなんですが、こういうところを時間をかけて<strong>双方納得行くように決めていきました</strong>。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note0908_002.jpeg","alt":"","index":0}">
<em>CLINICS ディレクター・医師 来田さん</em></p>
<p><strong>酒井</strong> : この例でやったことは、まず現在のプランと機能を全部書き出して、組み合わせを整理するということでした。これをベースにして話を刷り合わせていったんですが、文字ベースだと人間分かった気になるんですが、実はそこまでピンと来ない。ですので、<strong>LP のモックを作ってこれを見た顧客が導入したくなるようなウリになっているのかをすぐに判断できる状態</strong>にしました。</p>
<p><strong>宍戸</strong> : こうしてちゃんと機能を解いていって細かい部分も含めて、時には CLINICS のコンセプトに立ち返って事業部・開発双方で時間をかけて認識を合わせることができたのは、大変でしたが良かったですね。</p>
<h2 id="リリースまでのプロセスで顧客の声を反映">リリースまでのプロセスで顧客の声を反映</h2>
<p><strong>新居</strong> : やはりここまでのプロジェクトだと最初の段階でちゃんとリリースまでを見据えた意識合わせをチーム内で行なっていたんですね。開発が進行していって、リリースするまではどのようにプロジェクトは進んでいったんでしょう。</p>
<p><strong>宍戸</strong> : リリースをする直前も、単純に仕様や機能について文書で説明しても理解するのは難しいので、酒井さんにディレクションをしてもらいながら、開発環境を使って QA チームやカスタマーサポートチームと一緒に認識合わせやリリースに向けた準備を行なっていきました。</p>
<p><strong>来田</strong> : あとは関係が構築できている<strong>顧客に実際機能を見てもらって反応を伺う</strong>ということもしていきました。色々な使い方をしている顧客 10 件くらいにモックをベースにヒアリングをかけていきました。価格や導入時期なども含めた話を聞いていって、そこをすぐにプロジェクトにフィードバックする体制を構築していました。</p>
<p><strong>酒井</strong> : 実際に顧客の声をこうして聞けるというのは本当にプロダクトにとってありがたいことでした。特にこういう使い勝手などに直接関わる部分では、こうしたフィードバックの有無で完成度が違ってきます。<strong>機能の正式リリース前から機能評価に協力していただける医療機関がいるのは、カスタマーサポートの皆さんが顧客と良好な関係を築けているおかげ</strong>だと思っています。こういった点はメドレーの強みだと思います。</p>
<h2 id="プロジェクトで大切にしたこと">プロジェクトで大切にしたこと</h2>
<p><strong>新居</strong> : 開発段階からユーザーの声を吸い上げていけるのは、本当に良いですね。プロダクトの改善スピードなんかも段違いに早くなりそうです。他に大切にしていた部分などありますか?</p>
<p><strong>酒井</strong> : 今回の機能開発においては、プロジェクトの責任者として、開発以外のメンバーともしっかりコミュニケーションし合意形成を図ることを意識してプロジェクトを推進しました。</p>
<p>先ほどの話にも出ましたが 1 つの機能を実装するとなっても、実際の医療現場での使い勝手や、メドレーでのサポート、売り方など色々な視点で考えないといけないプロジェクトでした。そのため、そういったところをカスタマーサクセスのメンバー含む 10 人ほどと毎週 1 つ 1 つ納得がいくように詰めていくという作業をしていったりしました。<strong>来田さんのように現役の医師の意見、顧客の意見なども取り入れられますし、カスタマーサポートには医療事務経験者の方もいらっしゃるので、多方面の視点を総合的に取り入れられるコミュニケーション</strong>を心がけていました。</p>
<p>手段はテキストや Figma でのデザイン、実際の実装を見せるなど適切な手段を選んでコミュニケーションコストを低くするようにして進行していました。<strong>実際の機能実装にあたってはエンジニアも自身で考えて細かいボールを拾ったりしながら、実装</strong>していってもらいました。この辺りはこのプロジェクトだからというよりも、メドレーの開発のベースになっている部分なので良いですよね。</p>
<h2 id="メンバーのイメージ合わせがプロジェクト成功の鍵">メンバーのイメージ合わせがプロジェクト成功の鍵</h2>
<p><strong>新居</strong> : 先ほどもお話がありましたが、プロジェクト途中の意思決定で関わるプロダクトが多くなったのでスイッチングが大変だったということでしたが、実際にそのタイミングになったときに全体のシステム設計なんかも変わったりしたんでしょうか。</p>
<p><strong>田中</strong> : 私はプロジェクトの途中から入ったんですが、そのタイミングで、宍戸さんや来田さんがちょうどシステム設計を考え直していたり、プロジェクト計画の引き直しをしているところでした。そこでまずは全体像を把握したいと考えてドキュメントなどを見せてもらったんです。各機能の細かい要件は分かったのですが、このプロジェクト全体としての各システムの責務や依存関係などがいまいち分かりづらく、メンバー間の認識も若干ズレが発生しているように感じました。なのでまずは**メンバーの認識を合わせ、各チームが何をするべきかを明確化するために、各システムの責務など全体感の整理整頓をして「見える化」**することから始めました。</p>
<p>このプロジェクトに限らずですが、プロジェクトとして抽象度の高い段階でのドキュメントは、文章のみの記載だと実は関係者にしっかりと伝わっていなかったということが多々あります。各自が頭の中でイメージした図に差異が発生している状態です。自分はそういう際に良く言っているのが**「大枠で良いので、まずは整理結果を図にしてみんなのイメージを合わせる**」ということです。こうして認識を合わせることで各チームが進めやすくなり後戻りリスクも軽減されるので、この段階でしっかりとイメージをすりあわせる事がとても重要だと考えています。</p>
<p><img __ASTRO_IMAGE_="{"src":"./note0908_004.jpeg","alt":"","index":0}">
<em>医療プラットフォーム CTO 田中さん</em></p>
<p><strong>新居</strong> : たしかにテキストで仕様なんかを書きながら説明しても、実は認識に齟齬があった…という経験があります。その後はどのようなプロセスでプロジェクト推進したんですか?</p>
<p><strong>田中</strong> : その後は各プロダクトにて実装予定の機能で、プロダクト間で共通化できそうな機能を共通サービスとして切り出すかどうかをみんなと考えていきました。結果として大きな機能では「ビデオ通話」部分を共通化するという意思決定をしました。他にも「問診票の提供」機能が候補としてあがっていましたが、共通化せずに各プロダクトの責務としてそれぞれ実装するという結論となりました。同じような機能に見えても、医科と歯科という業務ドメイン毎のコンテキスト差異を十分に検討した上での判断でした。<strong>その差異がプロダクト機能としての強みとなり、安易な共通化はプロダクト成長の足枷となるリスクも存在</strong>するためです。この判断も先ほどの全体の依存関係などが整理されたからこそ、意思決定できるものでした。</p>
<p><strong>酒井</strong> : 機能実装時に実感しましたが、やはり医科と歯科では細かい部分の要件などが異なる部分が多かったので、この段階で共通化の判断ができたのは大変助かりました。</p>
<h1 id="prm-プロジェクトの大きな成果">PRM プロジェクトの大きな成果</h1>
<p><strong>新居</strong> : 最後になりますが、このプロジェクトが終わっての成果はどのように考えていますか?</p>
<p><strong>宍戸</strong> : 冒頭に話した CLINICS の課題に関してはある程度、解決ができたと思います。<strong>医療機関の業務に沿った形でプロダクトが進化</strong>できた点はよかったと思います。特に問診周りなどは発熱外来などニーズの多様化に即して使い勝手が良くなったと思っています。</p>
<p><strong>酒井</strong> : 他にもビデオ通話部分など「オンライン診療」を提供するプロダクトとして「対面診療」だけではなく、もっと有用なプロダクトにできたかと思っています。</p>
<p><strong>来田</strong> : まだリリースしたばかりですが、初月の販売実績も期待していたよりも良く、そうした意味でも<strong>マーケットフィットした機能を出せたなという手応え</strong>が実感としてもありますね。</p>
<p><strong>田中</strong> : 予約やオンライン診療もドメインを統一した結果、会員登録数が増えていますので、そうした点も成果と言えます。</p>
<p><strong>酒井</strong> : あとは直接の成果というわけではないのですが、今回のプロジェクトの副産物として</p>
<p>CLINICS の機能を整理整頓していった結果、<strong>改めて「一気通貫で医療機関の業務をサポートできる」というトータルでの機能提供と各機能のシームレスな連携は CLINICS の強み</strong>だ、というのが再確認できたのは大きいです。</p>
<p><strong>新居</strong> : これからのさらなる CLINICS の発展に期待できますね!本日は長い時間お話ありがとうございました!</p>
<h1 id="おわりに">おわりに</h1>
<p>メドレーとしてもかなり大規模なプロジェクトだった今回の PRM プロジェクトでしたが、みなさんのお話を聞いて各部署・各プロダクトで綿密に連携しながらプロジェクトを成功させようとしている様子が感じられるインタビューでした。</p>
<p>特にプロジェクト進行の話は非常に参考になりました。皆さんのご参考になる部分があれば幸いです。</p>
<p>こうしたプロジェクトで自分の力を発揮したい!と思った方はぜひお気軽に下記からご応募ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- 22 年度新卒研修に取り入れた QA 講座について QA エンジニアに語ってもらいましたhttps://developer.medley.jp/entry/2022/08/31/223542https://developer.medley.jp/entry/2022/08/31/223542はじめに
みなさん、こんにちは。エンジニアの山田です。
今年も新卒入社で 6 人のメンバーがエンジニアとして入社しており、現在も絶賛新卒研修中となっています。毎年、新卒研修はプロセスやコンテンツを見直しているのですが、今年度は初めて Qua...Wed, 31 Aug 2022 13:35:42 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。エンジニアの山田です。</p>
<p>今年も新卒入社で 6 人のメンバーがエンジニアとして入社しており、現在も絶賛新卒研修中となっています。毎年、新卒研修はプロセスやコンテンツを見直しているのですが、今年度は初めて Quality Assurance(以下 QA)についての講座を取り入れるようにしました。</p>
<p>今回は講師をお願いした QA エンジニアお二人にどのようなことを念頭に講義を実施したのかをインタビューしてみました。</p>
<h1 id="今年から-qa-講座を新卒研修のカリキュラムとして取り入れた理由について">今年から QA 講座を新卒研修のカリキュラムとして取り入れた理由について</h1>
<p>インタビュー本編をお届けする前に、今回 QA 講座を新卒研修に取り入れた理由について研修企画担当の 1 人としてご説明します。</p>
<p>QA 講座を新卒研修のカリキュラムに取り入れたいと思ったきっかけは、昨年の「開発実践研修」でアプリケーション開発の一環としてテスト設計も含めて実施していましたが、メンターとしてレビューをしている際「<strong>せっかくメドレーには QA のスペシャリストがいるんだから、今回のレビューにも加わってもらえば良かった</strong>…」と感じたことでした。</p>
<p>今年はメンターではなく、新卒研修の企画・運営を主に担当することになり、昨年よりもさらに研修の質を上げるためにはどうするかを考えた時、上記のきっかけもあり
QA エンジニアの皆さんには「開発実践研修」のテスト設計レビューを含め、座学による講義もお願いしたいと思うようになりました。</p>
<p>講義をお願いしたいと思った理由として大きく 3 つあります。</p>
<h2 id="1-qa-の経験不足を補う">1. QA の経験不足を補う</h2>
<p>全体的な傾向として弊社の新卒エンジニアは、インターンや学校の活動などで開発経験は相応にあるのですが、テスト設計をはじめとする QA を特に力を入れてやった経験が浅いという事実があります。その上でこれから先メドレーでプロダクト開発をする上では、テストや QA などについて自走して考え実施することができるようにしたかったため。</p>
<h2 id="2-qa-に対する認識を揃え意識を高める">2. QA に対する認識を揃え、意識を高める</h2>
<p>「QA とは何ぞや?」という話を専門性を持った QA エンジニアから新卒エンジニアに伝えてもらいプロダクトの QA に対して意識を高めてもらいたかったため。</p>
<h2 id="3-社内勉強会の内容がとても為になると感じた">3. 社内勉強会の内容がとても為になると感じた</h2>
<p>弊社では、TechLunch という社内勉強会を開催していますが、その中で今回インタビューした QA エンジニアお二人の発表内容が大変良かったので、その内容をぜひ新卒エンジニアにも伝えてほしいと感じたため。</p>
<p>このような理由により、今年度からは QA 講座を研修の一部として開くことになりました。</p>
<p>さて、前置きが長くなりましたがそんな QA 講座を担当してくださった QA エンジニアのお二人へのインタビューをご覧ください。</p>
<h1 id="インタビュイー紹介">インタビュイー紹介</h1>
<h2 id="上村さん">上村さん</h2>
<p>医療プラットフォーム第一本部 プロダクト開発室
第一開発グループに所属。クラウド診療支援システム <a href="https://clinics-cloud.com/">CLINICS</a>
の医療機関側システム(カルテ、オンライン診療など)の品質向上活動を担当。</p>
<h2 id="米山さん">米山さん</h2>
<p>医療プラットフォーム第一本部 プロダクト開発室
第一開発グループに所属。オンライン診療・服薬指導アプリ <a href="https://clinics-app.com/">CLINICS</a> のアプリ・
Web ・基盤を扱う開発チームで QA プロセス全般を担当。</p>
<p><img __ASTRO_IMAGE_="{"src":"./0804_001.jpeg","alt":"","index":0}"></p>
<p><em>左から上村さん・米山さん・筆者</em></p>
<h1 id="qa-講座を担当した-qa-エンジニアのインタビュー">QA 講座を担当した QA エンジニアのインタビュー</h1>
<h2 id="チームの雰囲気">チームの雰囲気</h2>
<p><strong>山田</strong>: 早速ですが、お二人とも医療プラットフォームで QA
を担当されていますが、所属しているチームの雰囲気はどんな感じなんでしょうか?</p>
<p><strong>上村</strong>:
チームというよりもメドレーのエンジニア・デザイナーの皆さんに共通している話かもしれませんが、<strong>セルフマネジメントできている方がとても多い</strong>と感じます。そういった方達なので視野を広く持っていて、開発の時にメンバー間で落ちそうなボールを自分でさっと拾って担当したりを自然にやっていますね。後は CLINICS の特に電子カルテは開発に必要なドメイン知識が広範囲で、私も入社して 2 年強経ちますが、未だに分かっていない部分などが出てくることもあり、そんな時でも<strong>気軽に周囲のメンバーに聞ける雰囲気</strong>なのが良いです。</p>
<p><strong>山田</strong>: 現在は QA エンジニアとして、仕様策定のフェイズから関わっていることが多いんでしょうか?</p>
<p><strong>上村</strong>: 関わり方はプロジェクトや時期により違っています。メインで担当するプロジェクトは最初から入って仕様レビューから関わる場合もありますし、複数のプロジェクトが平行している時は要所でアドバイス的な形を取る場合もあります。現在は開発中の大きいプロジェクトでの活動に振り切っているフェイズになっているところです。</p>
<p><strong>山田</strong>: 米山さんのチームの雰囲気などはいかがですか?</p>
<p><strong>米山</strong>: 上村さんとは違うチームに所属していますが、似た印象を持っています。一言で言うと「大人が集まっている」チームだと思います。ミーティングなどでも<strong>本質を捉えた議論が多く、皆さん人格者で仕事ができる方々</strong>だと感じています。今所属してるのが患者さんが使うアプリの開発チームで、Android / iOS / Web と 3 プラットフォームのプロダクト開発をしているのですが、1 人のエンジニアが <strong>3 プラットフォーム全てを実装したりするのはこのチームの特徴の一つ</strong>かもしれません。他社さんと比較すると珍しい体制ではないかと思います。</p>
<p><strong>山田</strong>: なるほど。リソース効率を考えた結果のチーム運営なんですね。</p>
<h2 id="研修のオファーの打診がされた時に考えたこと">研修のオファーの打診がされた時に考えたこと</h2>
<p><strong>山田</strong>: 今回お二人に QA 講座の担当をオファーさせていただいた訳ですが、オファーを受けた時にどのような思いでしたか?</p>
<p><strong>上村</strong>: 「ついにこの時が来たか…!」と思いました w</p>
<p><strong>山田</strong>: QA の啓蒙をされている上村さんとしては、やはり講師のオファーを待ち望んでいたわけなんですね。</p>
<p><strong>上村</strong>: はい、私が入社したのが一昨年の 4 月だったので当時の新卒研修カリキュラムはもう決まっていたと思うのですが、もしその時にオファーが突然あっても大丈夫でした。去年はオファーが無かったので「まだ早いのかな」と少し残念だったのですが、<strong>今年オファーがあって良かった</strong>です。</p>
<p><strong>米山</strong>: メドレーは元々プロダクト品質に対する意識が高いと感じているので、お話をもらった時に違和感はなかったです。こういった意識の高さは、経験豊富なメンバー達が作ってきた開発文化が社内に浸透しているからだと思います。ですので、<strong>入社時にこうした研修を通して目線を合わせるという意味で良い取り組み</strong>だと考えています。</p>
<p><img __ASTRO_IMAGE_="{"src":"0804_005.jpeg","alt":"","index":0}"></p>
<h2 id="qa-研修コンテンツの準備">QA 研修コンテンツの準備</h2>
<p><strong>山田</strong>: 今回の研修コンテンツを作る上で、工夫されていた点や意識されたポイントなどは、どのようなものがありましたか?</p>
<p><strong>上村</strong>: 準備段階で私のほうで素案を作ってから、直属の上司で研修の責任者でもある田中(医療 PF
CTO)に相談したところ、「品質というのがプロダクトに取ってどれだけ重要なのかを教えてほしい」というオーダーを頂きました。</p>
<p>その後に米山さんと、どのように講義を分けるかを話し合い、私は QA の重要性や **QA とは?**をテーマにテスト技法の演習を通して、まずは「品質」という観点がいかにプロダクトを作る上で大事なのかという大枠を話すことになりました。一方で米山さんはより実務に即した観点から、メドレーでの具体的な取り組みや自動テスト、開発プロセスとの関わり方などを話すことになりました。</p>
<p>こんな形で役割分担はスムーズに決まりましたので、まずは研修目的を新卒のスキルレベルから考えて設定しました。研修全体では田中から「メドレーのエンジニアに求めること」の講座があり、内容としてはマインドセットなど抽象度が高めの内容になります。その後に具体的な「開発基礎研修」が続くので、ちょうどその中間的な抽象度になるように今回の講座は作るように意識しました。</p>
<p>私が考えるカリキュラムで大事にしているのは、<strong>講義中にも手を動かしたり、自分でも考えてもらいながら参加してもらう</strong>所なので、そこも意識しています。</p>
<p><strong>山田</strong>: ちょうど良い抽象度で QA の重要性を全般的に分かるようにまとめていったんですね。米山さんは、どんな点が挙げられますか?</p>
<p><strong>米山</strong>: <strong>来年以降でも使えるようなコンテンツにしたい</strong>と考えていました。前職でも非 QA エンジニア向けの研修があったのですが、講義の感想で「もっと現場での実際の話なども聞いてみたかった」というような意見があるのを見ていたので、私のコンテンツでは抽象度が高い話だけではなく、実際のプロジェクトでの事例なども盛り込むようにしました。</p>
<p>また、 <strong>テスト自動化</strong> なども人によって考え方も様々だったりするので、テストピラミッドの話なども含めて現実的な考え方や活用方法などを講座には入れています。</p>
<p><img __ASTRO_IMAGE_="{"src":"0804_002.jpeg","alt":"","index":0}"></p>
<h2 id="qa-研修講座について紹介">QA 研修講座について紹介</h2>
<p><strong>山田</strong>: そのように工夫をして作っていただいた講座ですが、それぞれどのような内容だったのか教えていただけますか?</p>
<p><strong>上村</strong>: 一般的に知られる障害の事例として例えば「<a href="https://en.wikipedia.org/wiki/Space_Shuttle_Challenger_disaster">チャレンジャー号爆発事故</a>」などを挙げて、まず**「品質」がプロダクトに与える影響について知ってもらう**ようにしました。事業ドメインなどが違う事例をいくつか挙げています。</p>
<p>後半では、具体的にどのように「品質を上げること」ができるかという部分にスポットを当てて紹介をしています。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/d0d3369efc7d432a94d6fb48f9fd4a17" title="FY22 新卒研修 Quality Assurance 〜「QA」「品質」「テスト」とは?〜" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 315px;" data-ratio="1.7777777777777777"></iframe>
<p><em>上村の発表スライド</em></p>
<p><strong>米山</strong>: 私は具体的なメドレーでの品質向上のためにやっている取り組み( <a href="https://developer.medley.jp/entry/2021/01/15/180126">Magic Pod 導入の話</a> など)や、品質とテストに対する考え方などをお話しました。またメドレーのメンバーの良い取り組み事例として<strong>ホットフィックスでも、きちんとテストコードを追加してリリース</strong>している部分などを、どう考えてこの対応をしたのだろうかというような事も講義内容に入れています。</p>
<h2 id="講座についての手応え">講座についての手応え</h2>
<p><strong>山田</strong>: お話を聞いていると、すごくバランスが取れた講座に仕上がってる印象を持ちました。講義をした手応えとしては、どんなものがありましたか?</p>
<p><strong>米山</strong>: 今回の講義はオンライン形式だったこともあり、少し一方通行で終わってしまったかもしれないなと思いました。もう少しカジュアルに質疑応答も含めてインタラクティブに新卒メンバーと交流できたら、もっと良かったかなと思います。(<u>編注: 今年はコロナ対策として、オンラインで研修を行ないました</u>)</p>
<p><strong>上村</strong>: 途中少し問いかけのタイミングなどもつくったんですけどもね。</p>
<p><strong>山田</strong>: 研修の時期的に新卒メンバーの緊張なんかもあったんでしょうね。話は変わりますが、今回 1 講座をお二人に担当していただきましたが、新卒研修全体へのお二人の印象などはどんなものでしょうか?</p>
<p><strong>上村</strong>: 羨ましいなと思っちゃいますね w 自分が IT 業界で働き始めた時は特に「即 OJT」という感じでしたので、コンテンツがとても充実していて、実業務に入る時にハードルが低くなると思います。</p>
<p><strong>米山</strong>: 規模がとても大きい企業で充実した研修というのは分かるのですが、現在のメドレーの規模でここまで充実した研修をしているのは凄いなと感じます。去年までに入社した新卒メンバーも配属後にすごく活躍しているのを見ているので、充実した研修が活きているのではないでしょうか。</p>
<p>自分が QA エンジニアのキャリアを始めた時にも、複数の研修を受けたのですが、今でもちゃんと記憶に残っている研修というのがあるので、講師側としてはそういった研修にできるようにしていきたいですね。</p>
<p><img __ASTRO_IMAGE_="{"src":"./0804_003.jpeg","alt":"","index":0}"></p>
<h2 id="来年に向けて">来年に向けて</h2>
<p><strong>山田</strong>: これからの話もちょうど出てきたところなので、来年の QA 講座はどのようにバージョンアップしていきたいというアイディアはありますか?</p>
<p><strong>上村</strong>: 今回のように 90 分も良いのですが、<strong>できれば 1 日ワークショップをやってみたい</strong>なと考えていました。それだけ時間があれば他にもやりたいことが盛り込めるなと思います。それだけの時間が確保できるかが課題なのですが…。あとは来年はできれば対面で実施できれば良いなと思います。</p>
<p><strong>米山</strong>: 新卒メンバーは私達の研修の後に「開発実践研修」で実際にプロダクトを作るフェイズがあるので、来年はその開発実践研修の内容とリンクさせて、<strong>すぐに活用できるような実践的な話もできたらと考えています</strong>。</p>
<p><strong>上村</strong>: 確かに何かアプリを触ってもらって、テスト観点はどういったものがあるかなんかを考えてもらうなども良いですね。</p>
<p>具体的なテスト技法とか考え方なんかは検索すれば出てくるものですが、実際にそれらをどう使うか、いつ使うかという点が難しいところなので、きちんと伝えていきたいです。</p>
<p><strong>米山</strong>: あとは中長期的な目標としては、こうした研修をしていくことによって<strong>社内で QA エンジニアを志望する方が出てくるような講座</strong>ができれば良いなという願望があります w</p>
<p><strong>山田</strong>: なるほど、社内スカウトができるように充実した研修にするということですね w</p>
<p>本日はありがとうございました。</p>
<h2 id="おわりに">おわりに</h2>
<p>今年初めての試みである QA 新卒講座をどのように作っていったのかを、お送りしました。</p>
<p>メドレーでは QA エンジニアだけが品質に責任を持つというわけではなく、開発に携わる全員が責任を持っていくというスタイルでプロダクト開発をしているため、そうした姿勢を新卒メンバーに伝える上でも今回の講座はとても有用だったと考えています。</p>
<p>このような開発スタイルで日々プロダクトを作っているメンバーに興味が出て話を聞きたい! という方はぜひカジュアルにお話できればと思います。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 21 年新卒入社エンジニアと座談会で振り返る新卒研修https://developer.medley.jp/entry/2022/07/22/214813https://developer.medley.jp/entry/2022/07/22/214813はじめに
みなさん、こんにちは。エンジニアの新居です。
今回は 2021 年新卒入社のエンジニア 5 人に対し、新卒研修を終えてからこれまでにプロダクト開発業務を進めている中で、どのように新卒研修が活きているのかを振り返ってもらおうという座...Fri, 22 Jul 2022 12:48:13 GMT<h1 id="はじめに">はじめに</h1>
<p>みなさん、こんにちは。エンジニアの新居です。</p>
<p>今回は 2021 年新卒入社のエンジニア 5 人に対し、<a href="https://developer.medley.jp/entry/2021/10/12/180147">新卒研修</a>を終えてからこれまでにプロダクト開発業務を進めている中で、どのように新卒研修が活きているのかを振り返ってもらおうという座談会の様子をお送りしようと思います。</p>
<p>今までブログでお伝えしてきた新卒研修はメンター側の立場で書かれていましたが、当時メンティーだった 2021 年新卒メンバーの紹介をしながら、初めての試みとしてメンティー側はどのようなことを当時考えていたのかをお伝えできればと思います。</p>
<h1 id="2021-年新卒入社メンバー紹介">2021 年新卒入社メンバー紹介</h1>
<p><img __ASTRO_IMAGE_="{"src":"./image_003.jpeg","alt":"集合写真","index":0}"></p>
<blockquote>
<p>(左から)筆者 / 内田さん / 高橋さん / 堀内さん / 佐々岡さん / 寺内さん</p>
</blockquote>
<h2 id="高橋さん">高橋さん</h2>
<p>医療プラットフォーム第一本部 プロダクト開発室 第二開発グループ所属。CLINICS の開発を担当。現在は周辺領域拡張プロジェクトに携わる。</p>
<h2 id="佐々岡さん">佐々岡さん</h2>
<p>人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属。ジョブメドレーの開発を担当。現在は求職者側の UI/UX 改善などの開発に携わる。</p>
<h2 id="寺内さん">寺内さん</h2>
<p>人材プラットフォーム本部 プロダクト開発室 第三開発グループ所属。介護のほんねの開発を担当。定常開発や新機能開発に携わる。</p>
<h2 id="堀内さん">堀内さん</h2>
<p>人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属。ジョブメドレーの開発を担当。現在は求職者側の UI/UX 改善などの開発に携わる。</p>
<h2 id="内田さん">内田さん</h2>
<p>医療プラットフォーム第一本部 プロダクト開発室 第四開発グループ所属。CLINICS の開発を担当。現在は CLINICS カルテの基幹システムの開発に携わる。</p>
<h1 id="チームの雰囲気について">チームの雰囲気について</h1>
<p><em>メドレー入社後 1 年が経ち、5 人の皆さんはどのような雰囲気のチームで働いているかを話してもらいました。</em></p>
<p><strong>高橋</strong>: CLINICS の中でもうちのチームは<strong>出社頻度が高めで対面でコミュニケーションを取る機会が多い</strong>ですね。新規機能を中心に開発しているので、細かな調整がしやすくスピード感を持って開発できています。</p>
<p><strong>内田</strong>: 自分は別のチームなんですが、夕会をスタンディングでやっているのを見てカッコいいなと思っていて羨しいです w</p>
<p><strong>佐々岡</strong>:
ジョブメドレーチームの雰囲気としては結構リモートで開発している方の割合が多めです。<strong>家庭持ちの方とかは家で実装していたりすることが多い</strong>です。デザイナーとエンジニアはすごく近い距離で仕事をしています。 その中で自分は、主に求職者の応募体験を良くするための改善などを行なっています。</p>
<p><strong>寺内</strong>: 介護のほんねは、プロダクト開発チームがコンパクトなので他のみんなと違い明確に担当が分かれているということはないです。自分は社内ツールの業務改善系の開発を担当することが多いです。</p>
<p>社内ツールの非効率な部分を改善したり、定常業務で必要になるシステム改善や修正を主に手がけています。
サーバーサイドの開発が主で、必要に応じてフロントエンドの開発も行なっています。チームの雰囲気は、<strong>締めるところは締める雰囲気</strong>です。MTG なども少なめなので、実装や考えることに時間を使っていける環境です。</p>
<p><strong>佐々岡</strong>: 寺内はチームのプロダクト責任者が直接メンタリングしてくれるのが良いなーと思っています。</p>
<p><strong>寺内</strong>: そうですね。<strong>直属の上司にレビューを含め、メンタリングなどもしていただいている</strong>ので、ありがたいです。</p>
<p><strong>堀内</strong>: 自分も佐々岡と一緒でジョブメドレーの開発を担当しています。自分が思うチームの雰囲気ですが、「温和なチーム」と感じています。 心理的安全性が高いチームですね。</p>
<p>自分が今担当しているのは求職者側の UI/UX 改善であったり、ページのパフォーマンス改善などです。開発の特徴としては、開発案件を進めるときに<strong>すぐにプロダクトマネージャやリードエンジニアといった方々と会話して進めていける</strong>部分でしょうか。</p>
<p>ジョブメドレー自体、サービスとしては 10 年選手です。ですから、業務フローが大変成熟したものになっています。
<strong>洗練された企画・開発フローを実際に体感しながら開発できる、という環境がとても勉強になります</strong>ね。</p>
<p>またこのフローは<a href="https://www.medley.jp/team/culture.html">ドキュメントドリブン</a>という考え方で作られた資料に基いていますので、後から蓄積された知見をキャッチアップすることも容易ですし、リモートの人と出社している人との間で齟齬が起きることなく仕事が進められています。</p>
<p><strong>内田</strong>: CLINICS 電子カルテの基盤チームで開発しています。基盤チームでは本当にこれぞカルテという部分を扱っているので、レセプトに関係する部分なども担当しています。</p>
<p>チームの特徴としては、チーム構成として<strong>デザイナーやディレクターといった職種の方達がいるんですが、彼らと一緒に会話をしながら、開発を進めていく</strong>ということでしょうか。</p>
<p>職種で分けずに全員で認識を揃えながらの開発ができています。あとはメドレーのデザイナーは多かれ少なかれ全員そうだと思うのですが、特に今のチームのデザイナーは自分でフロントエンドの <a href="https://ja.reactjs.org/">React.js</a> のコードを書いているので、手戻りなども無くすごく仕事がやりやすい環境です。こうした環境で自分が要件から考えて実装していくという開発をできているのが、とてもやりがいがあります。</p>
<p><em>それぞれ配属されているチームが違うこともあり、チームの雰囲気などはやはり色々と違うようです。しかし、共通してチームで動きながら職種に囚われずに要件定義から関わって開発をしているというところは、全員が感じているようですね。</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./image_014.jpeg","alt":"全景","index":0}"></p>
<h1 id="今までのプログラミング経験">今までのプログラミング経験</h1>
<p><em>次の話題として、21 年入社メンバーの入社前のプログラミング経験について聞いてみました。みなさんどんな形でプログラミングに関わってきたんでしょうか?</em></p>
<p><strong>高橋</strong>: プログラミングを始めたのは高校 1 年生の頃からです。<strong>中学生のころからコンピュータに触れはじめて漠然とプログラミングの勉強がしたい</strong>という思いから、情報系学科に入学してゲームプログラミングから始めたのが最初です。</p>
<p>大学時代は、実践型のプロジェクト学習に力を入れている公立はこだて未来大学で様々な開発経験を積みました。学部 3 年生の 1 年間は、4 大学合同でチーム開発をするという講義の中で iOS アプリケーションの開発を経験しました。</p>
<p>学部 4 年生からは、都内にある SIer 企業の社内システムを開発する学内プロジェクトに参加しました。他にもエンジニアとして働けるサマーインターンにも参加しました。</p>
<p><strong>内田</strong>: 自分達と同世代だと中学生のときくらいに、例えば PSP を始めとした<strong>ゲーム機だとか、家のパソコンを触っていくことをきっかけにして、プログラミングに興味を持つ</strong>という人が多い印象です。この 5 人の共通の思い出として、やっぱりこの辺の話題が出てきたりします。</p>
<p><strong>佐々岡</strong>: 自分が<strong>プログラミングを始めたのは、情報系の大学に入ってから</strong>でした。始めてからは <a href="https://rubyonrails.org/">Ruby on Rails</a> での開発や <a href="https://www.python.org/">Python</a> の <a href="https://www.djangoproject.com/">Django</a> での開発をアルバイトで経験を積んでいきました。サークルでも PHP を使った Web アプリケーションを作ったりと、のめり込んでいました。</p>
<p><strong>堀内</strong>: 自分は大学では情報系学科ではなく、経済学部を専攻しました。高校のときは理系コースで「理系の大学に行くんだ」と頑張っていたんですが、どちらかというと文系科目の方が好きだったので、数学の知識も使いつつ文系である経済学を選んだ形です。</p>
<p>プログラミングの原体験は Web になります。もうサービスは無くなってしまったんですが、<strong>中学生のときに Yahoo!ジオシティーズというホームページを簡単に作れるというサービスに夢中になった</strong>時期がありました。</p>
<p>このサービスはエディタにテキストを入れると HTML が生成されるというシステムだったんで、そこでこういった感じで Web が出来るんだなと思い面白いなと思ったのがきっかけです。そこから色を変えたいから CSS を勉強する、クジ引きアプリ作りたいから JavaScript を勉強するみたいな感じで、のめり込んでいきました。</p>
<p>中高くらいにスマホが出てきたんですが、当時カスタムされた OS が流通していて、そういうのも面白いと触っていました。同時期にブログを運営していたのでその時に <a href="https://www.php.net/">PHP</a> での開発をしていきました。一通り Web プログラミングが出来るようになってからは、個人事業主として開発や保守を受託することが多く、その時のお仕事で、フロント / バックエンド / インフラ 満遍なく貴重な経験をさせて頂けたと思います。</p>
<p><strong>内田</strong>: 僕は<strong>小学校 3~4 年生くらいに最初にプログラミングに触れました</strong>。当時、宇宙がすごく好きでちょうど <a href="https://www.jaxa.jp/">JAXA</a> が小学生向けのプロジェクトやっていてそれがロボットの開発をするというものだったんです。そのロボットの制御をするためにプログラミングが必要になって触り始めたのが最初です。</p>
<p>中学生で Web プログラミングに触れまして。ちょうど YouTube が盛り上るくらいのタイミングだったんで、今でいう YouTuber っぽい活動していてその宣伝のためのホームページを作るためにサイトを作ったりしていました。</p>
<p>でも、そこからはあんまりプログラミングに触れていなくて、大学 2 年生くらいになって部活でアプリ開発をやり始めて、それがスケールアップして起業もしました。</p>
<p>その会社は儲からなくて閉じたんですが、そこからある程度プログラミング経験が積めたなと思ったので、医療系を含む、色々な企業でインターンとしてプログラミングをしていました。そこからメドレーに入社しました。</p>
<p><strong>寺内</strong>: 僕も佐々岡と一緒で大学に入ってからでした。高校のときは遺伝子に興味があったんですが、Web 広告で<strong>プログラマーについて知ってこっちの方がより面白そうだと思って情報系学科に入る</strong>ことにしました。</p>
<p>サークルで知りあった人にインターンをおすすめされたので、1 年生の後半あたりからずっとインターンとしてプログラミングをしていました。その中にメドレーもありました。</p>
<p><em>話を聞くと皆さん大分早い段階からプログラミングに興味を持ち出していますね。大学でインターンを有効活用してスキルアップしてきた人も多いです。最近あまり言わないですが、デジタルネイティブという印象です。</em></p>
<h1 id="メドレーに入社を決めた理由">メドレーに入社を決めた理由</h1>
<p><em>そうした経験を積んできた 5 人はどんな理由でメドレーの入社を決めたのかが気になるので、聞いてみました。</em></p>
<p><strong>高橋</strong>:
就活の軸として、社会的な課題を解決するプロダクトの開発に携わりたいという思いがありました。</p>
<p>この軸は、大学時代までに個人開発やプロジェクトを通して開発してきた成果物が、思うような価値を生み出せなかったという悔しさが原体験としてあったためです。そこから課題解決が好きだったこともあり、エンジニアリングを通して大きな社会課題を解決したいという考えを持つようになりました。</p>
<p>最終的には、インターンシップや就活を通して社員の方々から話を伺った中で「医療ヘルスケアの未来をつくる」という<strong>メドレーのミッションに強く惹かれ</strong>入社を決めました。</p>
<p><strong>佐々岡</strong>: 生活のインフラに直結する分野の社会課題を解決するという所が良かったので、メドレーに入社しました。もう一つの理由としてはメドレーの「ドキュメントドリブン」という文化に心惹かれたからです。</p>
<p>色々<strong>社内の知見などをきちんとドキュメント化している</strong>ので、そういったドキュメントを参考にしていれば、自分の成長スピードも早くなるのではと思いました。</p>
<p><strong>内田</strong>: 他の人と同じで、医療という大きいドメインでの課題解決をしているという点はもちろんなんですが、働いている<strong>エンジニアの方々が少数精鋭でレベルが高いなと感じた</strong>のも大きいです。</p>
<p>そんな中で、新卒入社のエンジニアの人数もそこまで多くはないので、入社したらそんな方達と一緒に仕事ができるようになるなと感じたからです。</p>
<p><strong>堀内</strong>: 就活の軸として、自分が成長できそうか・風通しが良いか・合理的な社風かなどを軸として探していました。将来的には経営をしたいと考えているので、<strong>ビジネス側と開発側の距離が近い</strong>ことも考えて総合的にメドレーが良さそうだと考えて入社しました。</p>
<p><strong>寺内</strong>: 私はメドレーの夏の<strong>インターンシップに参加したのですが、医療・ヘルスケアドメインの課題の多さと、それに正面から向き合うメドレーという会社が非常にエキサイティングだと感じた</strong>のが、きっかけです。</p>
<p>そこから自分の医療の原体験というのを考えたときに、高校の時に親族が難病かもしれないという疑いが出てきたという出来事がありました。結局は大丈夫だったのですが、その時に医療情報などの探しにくさなどを実感したことを思い起しました。メドレーはそうした問題に取り組んでいる会社だというのが一番の理由でした。もちろん優秀な方が働いているというのもあります。</p>
<p><em>メドレーが「医療」という分野での事業をしているということに共感して入社している方が多いですね。共通して自分が作ったものが、社会にインパクトを生み出せるか?ということを意識しているメンバーが多いと思いました。</em></p>
<p><img __ASTRO_IMAGE_="{"src":"./image_012.jpeg","alt":"image1","index":0}"></p>
<h1 id="2021-年度新卒研修の感想">2021 年度新卒研修の感想</h1>
<p><em>ここからは、新卒研修を受けた感想を 1 年越しに聞いてみたいと思います。特にチームとして開発をしていった「開発実践研修」について中心に聞いてみました。当時は大変なこともあったと思いますが、経験を積んだ今はどのような感想を持っているのでしょうか。</em></p>
<h2 id="研修で良かったこと">研修で良かったこと</h2>
<p><strong>佐々岡</strong>: 開発実践研修で良かったのはまず、<strong>要件定義からリリースまで一気通貫にできたので自分のその時点での不足している部分というのが可視化されたこと</strong>でした。</p>
<p>また開発するツールはそのまま社内で使い続けていくものだったので、レビュアーの方にも親身になってレビューしていただけたり、そういった所が良かった所です。</p>
<p>開発実践では React.js を使って開発をしていたんですが、配属されてからも業務として使って活きている部分です。<strong>メドレーで開発業務をするということが、どういうものかをこの研修を通じて勉強</strong>できました。 おかげで、配属された後も違和感なく実務ができるようになりました。</p>
<p><strong>高橋</strong>: エンジニアとしてこれからプロとして開発をしていく上で自信が付いたのが良かったです。</p>
<p>学生のときは作って終わりというプロダクトが多かったのですが、<strong>社会人になって初めて作ったプロダクトが今も毎日稼動しているということで、エンジニアとしての自信に繋がりました</strong>。</p>
<p>この経験のおかげで、現在の業務でも自分が違和感などを感じたりしたときには、「間違ってるかな」などと臆せずにズバズバと言えるようになっています。</p>
<p>現在のプロジェクトは要件の固まりきっていない新機能を開発しているフェーズなので、日々の業務に活かせている実感があります。</p>
<p><strong>堀内</strong>: 今に繋がってるなと思っているのは<strong>チームがちゃんと目標を持っているときと、そこがあやふやだったときのチームのパフォーマンスの出方が全然違うというのを体験</strong>できた点です。</p>
<p>開発中は自分はチームリードとして、チームビルディングを中心に行っていたのですが、ちゃんと目標をイメージできる形にする・そのイメージをチームで認識が揃うまでしっかりと擦り合せる、という 2 点に注意していました。</p>
<p>最初の目的をちゃんと話しあって固めた後は、朝会などでちゃんとメンバーの作業について共有していくようにしたら、上手くチームが回り始めました。最初にイメージを固めたら、MTG を小まめにしなくてもちゃんと作業が進んでいきました。</p>
<p><strong>寺内</strong>: 「開発実践研修」の前に受講した外部研修の「ビジネススタンス研修」が良かったです。研修の序盤に受けられて<strong>社会人としてのスタンスが学べたのが良かった</strong>です。ここで習ったことが、開発実践研修でチームで話し合う場でもきちんと応用できましたし、もちろん業務をしている今でもちゃんと活きていると実感しています。研修の全メニューが良かったと感じているのは言うまでもないかもしれませんが w</p>
<p><img __ASTRO_IMAGE_="{"src":"image_011.jpeg","alt":"image2","index":0}"></p>
<h2 id="研修で大変だったこと">研修で大変だったこと</h2>
<p><strong>佐々岡</strong>: 最初の段階での要件定義のときに課題設定が想定と違ってきたことが一番大変でした。</p>
<p>研修企画側から予め提示されていた課題と自分達が考えた要件定義とのズレを、 <a href="https://miro.com/ja/">Miro</a> を使ってのミーティングを繰り返して修正するまでに、時間がかかってしまいました。そうしたミーティングなどで課題の解決方法などを構造化して考えたりしていき、ようやく修正できました。</p>
<p>この経験で<strong>最初の課題設定も正しいかどうかを鵜呑みにせずに、ちゃんと自分達で調べて納得するような形にしないといけない</strong>ということを学べました。</p>
<p><strong>高橋</strong>: やはり要件定義フェイズでした。当初の予定より大分伸びてしまいました。これは着地点が不明確なまま、議論が進んでしまっていたのが原因でした。</p>
<p>対応として<strong>全員でちゃんとコンセンサスを取りながら、着地点を明確にして要件定義をやり直した</strong>結果立て直すことができました。要件定義をしっかりやるというのはこの研修が初めてに近い状態だったので、大変でした。</p>
<p>当時フワフワとした状態で進めてしまったところが反省点だったので、不明な部分などは自分の中で全部つぶした上で開発するということが大事なんだということが学べました。</p>
<p><strong>内田</strong>: 要件定義していた最初の 1 週間は本当に進みが悪くて大変でした。1 週間経ってようやく**「自分達がオーナーシップを取らないと開発が進まない」ということを実感**したので、そこから勢いが付いた感じです。それまではどこか自分事として捉えられてなかったんだと思います。</p>
<p><strong>寺内</strong>: 最初のほうではメンバーの向き不向きとか性格などがやはり分からなかったので、お互いの期待値調整が大変だったなと思います。</p>
<p>最初の時点で目的に対する姿勢も、メンバーごとに温度感が違いました。そこで、全員でお互いの得意・不得意などや期待することを、擦り合せる時間を作ってようやく役割分担もできました。</p>
<p>これで、ようやくスタートラインに立ってゴールもそこに至る道筋も明確になりました。自分は<strong>今のチームでも上司や同僚に自分の情報を開示しつつ、期待値を把握して仕事をする</strong>ことによって目指すべき場所が認識できるようになり、仕事が円滑に進むようになった実感があります。</p>
<p><em>研修については、皆さんそろっての初の共同作業ということもあり、苦労も達成感も感じていますね。
現在の業務にも、きちんと活きているというのは嬉しい限りです。要件定義から自分達でオーナーシップを持って目的を持ってチームで開発をするという、実践的な研修を心がけていたので、ちゃんとそこを経験してもらっているようです。</em></p>
<p><img __ASTRO_IMAGE_="{"src":"image_009.jpeg","alt":"image3","index":0}"></p>
<h1 id="今後について">今後について</h1>
<p><em>最後にメンバーそれぞれの皆さんにこれから目指すエンジニア像を聞いてみました。</em></p>
<p><strong>佐々岡</strong>: 今後は<strong>ビジネス側の知見・現場の課題感・プロダクトのあるべき姿という 3 つの視点をきちんと身につけていけるエンジニア</strong>になっていきたいと思っています。</p>
<p>現時点ではまだまだという自覚があるので、これから色々な先輩方の背中を追いかけつつ、成長していきたいと思います。</p>
<p><strong>高橋</strong>: 今はプロダクトマネージャやリーダーがプロダクトの根本的な基本要件を考えて、そこから詳細要件を自分達が考えて実装しているのですが、行く行くはそうした<strong>基本要件から自分で考えて、周りと連携しつつ実装していけるようになりたい</strong>です。</p>
<p>ここまで出来るようになると、自分が社会にインパクトを与えるプロダクトを作ったと胸を張って言えるんじゃないかなと思っています。</p>
<p><strong>堀内</strong>: 将来的には経営者の道を目指そうと思っています。ただ、プロダクト開発をしっかり理解し、一定の技術力を身につけていない状態では、人も付いてこないと思うんです。</p>
<p>ですので、<strong>説得力を持てるくらい技術力を身につけていきたい</strong>と思います。まだ出来てないと自覚しているのですが、自分が勉強させて頂いている部分を、早くチームの方達に恩返しできるようにしてきたいです。</p>
<p><strong>内田</strong>: 元々課題解決するプロダクトを作りたいという思いで入社しましたが、実現するためには 3 つやることがあると思っています。
1 つは「解決のために最適解を選択できること」です。これは幅広い技術を身につけたエンジニアになることかなと思っています。</p>
<p>2 つ目は「ちゃんとドメイン知識を習得すること」です。最適解を選ぶのにも業務知識が無いままだと絶対に良い選択をできないと考えています。</p>
<p>最後は「自分の考えをきちんと推し進められるビジネススキル」です。色々と関わる人にちゃんとコミュニケーションを取って、自分が良いと思うものを広められればと考えています。メドレーは<strong>自分が凄いと思ったエンジニアの方達がたくさんいらっしゃるので、行く行くは自分もそう思われる位になれたら</strong>…と思います。</p>
<p><strong>寺内</strong>: なりたいエンジニア像については、既にみんなに言いたいことを言われているんですが w
直近の目標は、マネージャから見ても「一人前である」と思われる実力を身につけたエンジニアになることです。
また、内側に目を向けられるエンジニアになることも目標です。新機能や定常業務の開発だけではなく、部署内の<strong>開発以外の人達が気持ち良く働けるようになる開発をしていけば、顧客などにも良い影響が出て、結果として事業の成長になる</strong>のではないかと考えています。</p>
<p>ですので、部署の皆さんにも喜んでもらえるような一人前のエンジニアになっていきたいですね。</p>
<p><em>現在は技術や仕事の仕方などを吸収しながらぐんぐんと成長している皆さんですが、最終的な目標はプロダクトに貢献できるようにという背景を感じます。ちゃんとビジネス面も分かったエンジニアにという考え方を持っていてメドレーでエンジニアに求められる部分を意識しているなと思いました。</em></p>
<h1 id="おわりに">おわりに</h1>
<p>21 年入社エンジニアの座談会はいかがでしたでしょうか。</p>
<p>メドレーの新卒研修を受けた側の感想を公開するのは初めての試みでしたが、自分もインタビューをしていて改めて当時メンバーの皆さんが苦悩していた部分や、現在に活きている部分などを聞けて勉強になりました。</p>
<p>今年も既に 22 年新卒入社のエンジニアメンバーが研修をしている真っ最中ですが、こうした経験などを後輩に伝えてもらうと、より良い組織になっていくのではないかと思っています。</p>
<p>最後に<strong>一緒に医療の未来を作っていく仲間を募集</strong>しているので、この記事を読んでメドレーに興味が湧いた方はぜひ、話をしましょう!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p><img __ASTRO_IMAGE_="{"src":"./image_002.jpeg","alt":"集合写真 2","index":0}"></p>medley
- CLINICSアプリのリニューアルの裏側(Android編)https://developer.medley.jp/entry/2022/06/30/115307https://developer.medley.jp/entry/2022/06/30/115307はじめまして。医療プラットフォーム本部プロダクト開発室エンジニアの小形(@ogaclejapan)です。
普段は、オンライン診療・服薬指導アプリ「CLINICS」の開発を担当しています。
近々、転職の入社エントリーも会社の公式 noteに公...Thu, 30 Jun 2022 02:53:07 GMT<p>はじめまして。医療プラットフォーム本部プロダクト開発室エンジニアの小形(<a href="https://twitter.com/ogaclejapan">@ogaclejapan</a>)です。
普段は、オンライン診療・服薬指導アプリ「<a href="https://clinics-app.com/">CLINICS</a>」の開発を担当しています。
近々、転職の入社エントリーも会社の<a href="https://note.com/medley/">公式 note</a>に公開されますので、ぜひ読んでみてください。</p>
<p>さて、昨年 12 月に CLINICS アプリは<a href="https://www.medley.jp/release/20211207.html">UI のフルリニューアル</a>を行いました。
少し経ちましたが、「<a href="https://developer.medley.jp/entry/2022/02/15/180002">CLINICS アプリのリニューアルの裏側(iOS 編)</a>」に続く、Android 編ということで裏話を書いていきます。</p>
<h1 id="コミットグラフから振り返るアプリ開発史">コミットグラフから振り返るアプリ開発史</h1>
<p>リニューアルを終えた直近のコミットグラフから CLINICS の Android 版アプリの歴史を振り返ると、大きく 4 つの時代を超えてきました。</p>
<ol>
<li>立ち上げ期:2016/7 -</li>
<li>閑散期:2018/4 -</li>
<li>再始動期:2020/5 -</li>
<li>リニューアル期: 2021/5 - 2021/12</li>
</ol>
<p><img __ASTRO_IMAGE_="{"src":"./2022_06_30_01.png","alt":"直近のコミットグラフ","index":0}"></p>
<h2 id="1-立ち上げ期">1. 立ち上げ期</h2>
<p>記念すべき PR#1 は、2016 年 7 月でした。すでにコミットグラフの表示期間外なのであまり詳しく追えていませんが、1〜2 名がメインで関わりつつ、他のエンジニアの方のコミットもちらほらとありました。
私のようにネイティブアプリ開発を中心に経験してきた人は、まだ社内に少ないですが、フロントエンドからバックエンドまで幅広くできる人がとても多いのも納得です。</p>
<p>実装は SmartUI と名がつけられた<del>アンチパターンの</del>ように、ネットワーク通信から画面の表示制御まで単一のファイル内にすべて書かれていました。
アプリ経験者不在の中で開発を続けてきたことに尊敬の念を抱きつつも、リニューアル開発で全面的にコードを書き直すまで色々なツラみがありました…。R.I.P. FatActivity 🛐</p>
<h2 id="2-閑散期">2. 閑散期</h2>
<p>2018 年から約 2 年ほど開発がほぼ止まっていました。もともとオンライン診察機能のために開発された経緯があり、この期間は電子カルテのシステム開発にリソースを注力していたようです。</p>
<h2 id="3-再始動期">3. 再始動期</h2>
<p>2020 年 5 月から再びアプリ開発は活発になり、オンライン服薬指導機能を提供する調剤薬局窓口支援システム「<a href="https://pharms-cloud.com/">Pharms</a>」と連携する開発などが 1〜2 名で行われていました。</p>
<p>2020 年度通期の<a href="https://ssl4.eir-parts.net/doc/4480/ir_material_for_fiscal_ym/95472/00.pdf">決算説明資料</a>を読んでみると、患者(≒ アプリ)を中心にプロダクトラインナップを増やしていく医療 PF 事業投資の図が初登場していました。
この事業計画に沿って、Pharms 連携やお薬手帳機能など患者向けアプリを強化する動きが加速し、2016 年から引き継がれてきた UI デザインや拡張性をゼロベースで見直すべく、翌年のリニューアル開発へ繋がっていきます。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2022_06_30_02.png","alt":"A.医療PFにおける事業投資 - 2020年度通期決算説明資料 p32","index":0}"></p>
<p>余談ですが、今年の 2 月に歯科業務支援システム「<a href="https://dentis-cloud.com/">Dentis</a>」をリリースして、4 月からアプリとの連携も試験的に開始しています。
リニューアル開発が完了していたからこそ、新規プロダクトへの連携にもスムーズに対応できました。</p>
<h2 id="4-リニューアル期">4. リニューアル期</h2>
<p>リニューアル開発は、2021 年 5 月下旬頃から本格的に実装を開始し、同年 12 月に無事リリースすることができました。
当初、Android アプリ開発メンバーの頭数が足りておらず、私と協力会社の社外エンジニアのみで進めていく状況でしたが、6 月途中からアプリ開発経験のある社内エンジニアが 1 名異動してきてくれたおかげでピーク時を乗り切れました。</p>
<p>リニューアル後のアプリ内モジュール構成と技術スタックはこんな感じです。
今年の 3 月に<a href="https://android-developers-jp.googleblog.com/2022/03/rebuilding-our-guide-to-app-architecture.html">刷新された公式のアプリアーキテクチャガイド</a>に近く、最低限データアクセス部分を分離したレイヤー構成としました。
モノクロではない色付きの技術スタックが今回新たに導入したものになります。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2022_06_30.png","alt":"アプリ内モジュール構成と技術スタック","index":0}"></p>
<p>次章からリニューアル開発で取り組んだことについて、詳しく書いていきます。</p>
<h1 id="リニューアル開発で取り組んだこと">リニューアル開発で取り組んだこと</h1>
<p>リニューアル開発のアプリをリードしていく役割で意識的に取り組んだことは次の 3 つです。</p>
<ul>
<li>未来像を描く</li>
<li>PoC を作成する</li>
<li>期間と機能の折り合いをつける</li>
</ul>
<h2 id="未来像を描く">未来像を描く</h2>
<p>当たり前のことですが、リニューアル開発が最終的な自分たちのゴールではありません。
この点を意識しながら、最初に <a href="https://developer.medley.jp/entry/2022/02/15/180002">iOS アプリをリード</a>したエンジニアの世嘉良さんとメドレーに合ったアプリ開発のあるべき姿を一緒に描きました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2022_06_30_03.png","alt":"202X未来像","index":0}"></p>
<p>ざっくりとした図ですが、202X 未来像をメンバーと共有できたのは良かったと思っています。
事業的な観点や技術的な視点から自分たちがこの先に向かいたい方向性が明確になりました。
私が入社したときからすでに Web、iOS、Android の 3 つのプラットフォームを横断するチーム体制だったこともあり、今後も横断しながら効率的に取り組める開発スタイルを目指しています。</p>
<p>最終的には、未来と現在を結ぶ通過点として今回のリニューアルを据え、期間や体制面などから実現可能な範囲を話し合って決めました。
当時、私は入社 2 ヶ月目でコードの理解がまだ浅かったので、認識ズレのギャップを埋めることにもこの図が役立ちました。</p>
<h2 id="poc-を作成する">PoC を作成する</h2>
<p>開発するメンバー間の経験差が気になるときや実現性に不安が残るときは、PoC(Proof of Concept)モデルとして 1 つ以上の主要な実装パターンを事前に検証しておくと不確実性を低減できます。
今回は自分がリードする立場であり、Android 開発経験が豊富なメンバーが揃ってガンガンと進めていける感じではなかったので、入念に事前準備してリニューアル開発に臨みました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2022_06_30_04.png","alt":"PoCモデルを検証したPR","index":0}"></p>
<p>PoC で検証できた実装方針は、メンバーにも既存 1 画面の書き換えを試してもらい、実際に PR をレビューして本番環境へ投入しています。
5 年前のコードベースが多く残るアプリをどのように移行させるか検証は少し大変でしたが、この事前準備の甲斐あって、リニューアル開発の期間中は技術的な要素でハマることなくタスクに集中できました。</p>
<p><img __ASTRO_IMAGE_="{"src":"./2022_06_30_05.png","alt":"PoCモデルを検証したPRコメント","index":0}"></p>
<h2 id="期間と機能の折り合いをつける">期間と機能の折り合いをつける</h2>
<p>PoC モデルを作成して技術的な実現性への不安を解消できましたが、リニューアル開発を進めていく期間と仕様面で、まだいくつかの不安要素は残っている状況でした。</p>
<ul>
<li>私自身、入社 2 ヶ月目で既存の業務ロジックは一部しか把握できてない<br>
⇒ 業務の仕様を理解しながら新しい画面へ書き換えていくので時間かかるかも…🧑💻</li>
<li>要素しか決まっていない画面が多数あり、画面デザインが並行して進んでいた<br>
⇒ どのタイミングで要素を取得するべきなのか、デザイン次第で変わりそう…🎨</li>
<li>協力会社から一緒に手伝ってくれる社外エンジニアの実力値が未知数だった<br>
⇒ 軽く感覚は掴んでもらったものの、良しなにタスクを進められるだろうか…🧗♀️</li>
</ul>
<p>いくら経験を積んだエンジニアでも不確実性を多く含むタスクの工数は正確に見積もれません。
そこで、リニューアルに関するアプリの開発定例で「もし想定する期間に間に合わなかったときにどうするか?」という議題を提起しました。
当たり前の話になっちゃいますが、うまくいかないときに取る行動を全員が共通認識しておくことは大切だと思っています。結論の候補は次のどちらかになるのではないでしょうか。</p>
<ol>
<li>リリース日を延ばして計画したリニューアルのタスクはすべてやりきる</li>
<li>一部機能を落としてリリース日はずらさない</li>
</ol>
<p>今回のリニューアル開発では、後者にあたるリリース日を死守する結論になりました。
ただし、一部の機能を落とすのではなく、既存の旧画面をスタイル調整してでも機能は出したいという要望があり、この点を設計やスケジュールのタスク優先度に折り込みました。</p>
<p>12 月に無事リリースできた Android アプリの裏側には、事業サイドとのこのような合意がありました。
実際、予定外のことも起きて一部の機能はリニューアル後に回しましたが、取る行動を決めておいたことで終盤あたふたすることなく、安定したものをリリースできました。</p>
<h1 id="これから">これから</h1>
<p>ここまでお読みいただきありがとうございました。同じような立場でこれからリニューアル開発を進めていく方のヒントになったら幸いです。
コードは 1 行も載せていませんが、Android アプリ開発者なら図の構成でなんとなく想像が付くと思い、今回は進め方にフォーカスしてみました。</p>
<p>さて、患者アプリはリニューアルが無事完了し、モダンな技術スタックで効率良く開発できる状態になりましたが、まだまだ技術的な視点で取り組みたいことはたくさんあります。</p>
<ul>
<li>Kotlin Multiplatform/Kotlin Multiplatform Mobile の活用[^1]<br>
⇒ iOS、Android(可能なら Web も)のロジック統合など生産性を上げる</li>
<li>宣言的 UI への移行(iOS はすでに SwiftUI)<br>
⇒ UI アーキテクチャ構成やコンポーネント化など UI の共通感覚を高める</li>
</ul>
<p>一方、患者アプリのビジネス視点では、オンライン診療を主軸とした予約・フォローアップの体験向上が 1 つの役目になります。
私自身は、「オンライン診療の実施に当たっての基本理念[^2]」に沿った成長路線がより良い患者体験につながると考えています。</p>
<blockquote>
<p><small>Ⅳ オンライン診療の実施に当たっての基本理念</small><br><br><small>① 患者の日常生活の情報も得ることにより、医療の質のさらなる向上に結び付けていくこと</small><br><small>② 医療を必要とする患者に対して、医療に対するアクセシビリティ(アクセスの容易性)を確保し、よりよい医療を得られる機会を増やすこと</small><br><small>③ 患者が治療に能動的に参画することにより、治療の効果を最大化すること</small><br><br></p>
</blockquote>
<p>この基本理念の 3 項目をアプリが満たすべき姿としたのが次の 2 つの状態です。</p>
<ul>
<li>医療を身近に行動できる状態(基本理念の ① と ③ に該当)<br>
⇒ 「探す」「知る」「伝える」といった行動手順の簡素化</li>
<li>必要とする人が適切に使えている状態(基本理念の ② に該当)<br>
⇒ アクセシビリティへの配慮、分かりやすい UI(≒ ユニバーサルデザイン)</li>
</ul>
<p>このような領域に興味のあるエンジニアやデザイナーさんがおりましたら、ぜひお近くのメドレーへ。
医療ヘルスケアの未来を一緒につくりませんか?</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>最後までお読みいただきありがとうございました。</p>
<p>[^1] <small>Flutter や React Native といった選択肢もありますが、現時点では外部 SDK 含めた既存コードとの親和性や時間的な制約から段階的に移行できる技術を重視しています</small><br>
[^2] <small>厚生労働省が公開する「<a href="https://www.mhlw.go.jp/content/000889114.pdf">オンライン診察の適切な実施に関する指針 10 ページ目</a>」より抜粋 </small></p>
- Showcase Gig さん主催の QA エンジニア向けイベントに弊社米山が登壇しましたhttps://developer.medley.jp/entry/2022/05/31/203823https://developer.medley.jp/entry/2022/05/31/203823はじめに
こんにちは。技術広報・エンジニアの平木です。最近使っている Emacs を Spacemacs から Doom Emacs に変更したところ気分も新たにテキスト活動が捗るようになりました。
さて、2022/05/11 に行なわれた...Tue, 31 May 2022 11:38:23 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは。技術広報・エンジニアの平木です。最近使っている Emacs を Spacemacs から Doom Emacs に変更したところ気分も新たにテキスト活動が捗るようになりました。</p>
<p>さて、2022/05/11 に行なわれた 「<a href="https://showcase-gig.connpass.com/event/244393/">QA Night 〜組織内で QA エンジニアがバリューを発揮し、キャリアアップするには〜</a>」 という QA エンジニア向けイベントで弊社 QA エンジニア米山がパネリストとして登壇しましたので、その様子をイベントレポートとしてお届けしたいと思います。</p>
<h1 id="qa-night-とは">QA Night とは</h1>
<p><img __ASTRO_IMAGE_="{"src":"./photo_00.png","alt":"img","index":0}"></p>
<p>こちらのイベントは <a href="https://www.showcase-gig.com/" title="https://www.showcase-gig.com/">Showcase Gig </a>さんが主催しているイベントである Geek Gig というエンジニアリングについての定期イベントがありまして、そのシリーズの 1 つとして運営されたものです。</p>
<p>今回は Showcase Gig さんも弊社もテスト自動化ツールの <a href="https://magic-pod.com/">MagicPod</a> を使っているというご縁からお声がけいただきイベント開催の運びとなりました。</p>
<h1 id="イベントについて">イベントについて</h1>
<p>今回のイベントは、パネルディスカッションとして 2 社の QA についてそれぞれ語る形式になりました。</p>
<p>モデレーターを Showcase Gig ソフトウェアエンジニアで VP of Technology である<a href="https://twitter.com/_pochi">菊池さん</a>が行ない、パネリストとして Showcase Gig QA エンジニア <a href="https://twitter.com/y_6_5_">横田さん</a>と、弊社 QA エンジニア米山とでざっくばらんにイベントが進行していきました。</p>
<p>QA エンジニアの方だけではなく、モデレーターにエンジニアの方が入ったことによりバランスが良いパネルディスカッションになっているという感想でした。</p>
<p>こちらのイベントは connpass で公開後、あれよあれよと 140 人まで申し込みがあり、注目度の高さが伺えました。</p>
<p>当日のイベントの様子は下記からご覧いただけますので、ご興味のある方はぜひ。</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/NGIpDGQKYFM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h1 id="パネルディスカッション">パネルディスカッション</h1>
<p>パネルディスカッションは大まかに以下のようなセクションに分かれて実施されました。</p>
<ul>
<li>パネリスト・モデレーター自己紹介</li>
<li>各社の開発/QA プロセスの理想と現実</li>
<li>QA エンジニアがいなかったらどうなる?</li>
<li>QA エンジニアのキャリア</li>
</ul>
<p>これらのセクションに合わせて、パネリストそれぞれの立場で回答をしていきましたが、途中で視聴されている方の質問などは、モデレーターの菊池さんがほぼリアルタイムで拾っていきながらの進行だったので、イベントの一体感が非常に感じられて面白かったです。</p>
<h2 id="自己紹介">自己紹介</h2>
<p><img __ASTRO_IMAGE_="{"src":"./photo_01.png","alt":"img","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./photo_02.png","alt":"img","index":0}"></p>
<p>横田さんも米山も現職に至るまでの経歴がかなり似ていました。第三者検証会社で様々な業態の会社を顧客として、様々な経験を積み重ねてから、事業会社での QA エンジニアとして特定のプロダクトの品質を高める仕事をしていらっしゃるという点です。</p>
<p>第三者検証会社で様々なニーズに対応したことが、プロダクトへの貢献に活きていそうだと感じました。</p>
<h2 id="各社の開発qa-プロセスの理想と現実">各社の開発/QA プロセスの理想と現実</h2>
<p><img __ASTRO_IMAGE_="{"src":"./photo_03.png","alt":"img","index":0}"></p>
<p><img __ASTRO_IMAGE_="{"src":"./photo_04.png","alt":"img","index":0}"></p>
<p>QA を含めた開発体制については、両社で違いがもちろんありますが大きい違いでいうと、現在のメドレーではプロダクト開発チームの一員として QA エンジニアが所属しています。他方、Showcase Gig さんでは複数の開発チームを横断して QA チームが存在しているという部分でしょうか。</p>
<p>メドレーの現在の開発体制では、チーム内で開発メンバーと密接にやり取りをしながら品質を高めていくというプロセスが取られており、別の開発チームのヘルプなども、もちろんありますがアドバイザー的な感じで、実際にはそのチーム内で品質を高めるように動いています。</p>
<p>一方で Showcase Gig さんでは横断チームとして各プロダクトを俯瞰して見れるような体制にしている印象でした。それぞれの開発チームとは要所でシンクアップすることにより会社全体での QA プロセスを統一しながら各チームに展開できるのが良さそうだなと感じました。</p>
<h3 id="理想と現実について">理想と現実について</h3>
<p>そんな両社ですが、共通する部分として事業領域の難しさがありました。 メドレーは医療領域という難しさ、Showcase Gig さんではオンラインとオフラインの両立をしなければならない部分が特に難しいとのこと。</p>
<p>そこから話は QA の理想と現実に移っていきます。メドレーではプロダクトのより上流部分である仕様策定部分からバグを潰せるようにしていくのを目指しつつも、ガチガチな方法や体制にならないように日々 QA を行なっています。また E2E テストの失敗が非常事態だとメンバーが焦るくらいに成功率を高めていきたいという理想がありますが、どうしてもマニュアルでのテストが必要になってくるプロダクトなので、E2E テストだけに頼らないように試行錯誤しています。</p>
<p>Showcase Gig さんでは品質は担保しつつ、今以上にリリース頻度を上げるためにどうしたら良いかという点を開発チームと協業で改善しようとされているそうです。MagicPod を使った E2E テストだけでリリースまでできるようにするのが理想ではありますが、その前にプロセスの制定など前段階の準備を着々としているそうです。</p>
<h2 id="qa-エンジニアがいなかったらどうなる">QA エンジニアがいなかったらどうなる?</h2>
<p><img __ASTRO_IMAGE_="{"src":"./photo_06.png","alt":"img","index":0}"></p>
<p>QA エンジニアのイベントで中々出てこないような設問もあり大変面白かったのですが、それがこの「QA エンジニアがいなかったらどうなる?」という設問でした。</p>
<p>興味深かったのはお二人とも同じような返答だったことです。両社とも「リリースは通常通り行なわれるだろうが、リリース後の不具合対応などが増えるかもしれない」ということでした。またこれも共通して「QA エンジニアがいなくても品質保証がされることが理想」というお話でした。</p>
<p>実際に QA エンジニアはいなくてもきちんと高い品質のプロダクトを世に出していける仕組みや体制が理想というのは、関係者に品質についての意識が根差していけるようにするという意気込みのように聞こえ感銘を受けました。</p>
<h2 id="qa-エンジニアのキャリア">QA エンジニアのキャリア</h2>
<p>また普段お二人はどのような意識配分で日々の仕事をされているかというのを円グラフで表わす試みが良かったです。
普段どのような事に時間や意識を割いているかというのが分かると自分の仕事の比率と比べてみちゃいますね。</p>
<p><img __ASTRO_IMAGE_="{"src":"./photo_07.png","alt":"img","index":0}"></p>
<p>ここから、皆さんが気になる QA エンジニアのキャリアについてお二人が話をされました。</p>
<p>米山は 15 年の QA 経験を持っていますが、その中でアジャイルやテスト自動化などのトレンドには「自分でもできるかな」と思いながら導入をしていったとのことでした。そうしながら、自分の中のプライオリティはあくまでも事業・プロダクトという部分だったので、ここにコミットができる立ち位置での仕事をずっと続けてきたと言います。</p>
<p>先のキャリアとしては、ポジションに捕われず事業価値を高めるように動いていき、どのような状況のチームでも品質を高めるための推進ができるようにしていきたいということでした。</p>
<p>一方の横田さんも今までの経験を踏まえ QA のポジションを高めていけるような動きをしていきたいということでした。テストなどに興味を持つ人の裾野を広げていきたいという意欲を感じました。個人としては UI/UX なども含めて品質を高めるということができればとのことでした。</p>
<p>お二人とも、「自分のキャリア」に留まらず周囲の人間にも、良い影響を与えていきたいということをおっしゃっているのが非常に印象的でした。</p>
<h1 id="最後に">最後に</h1>
<p>紹介した以外にも現場に即した QA についてや、品質についての考え方の一端が分かるようなイベントでした。自分はエンジニアという立場で視聴していましたが、そうした人間にも大変に示唆に富むお話が多く、あっという間に 1 時間が経過しました。QA エンジニアの方はもちろん、その他プロダクトを作るということに関わる方はぜひ、アーカイブを視聴してみてください!</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/NGIpDGQKYFM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>qa
- メドレー エンジニア・デザイナーブログをリニューアルしましたhttps://developer.medley.jp/entry/2022/04/01/160416https://developer.medley.jp/entry/2022/04/01/160416はじめに
皆さん、こんにちは。エンジニア・技術広報の平木です。
以前からご覧になっていただいていた方にはお分かりかと思いますが、3/18 に今ご覧いただいているエンジニア・デザイナーブログを「Developer Portal」という名称に変...Fri, 01 Apr 2022 07:04:16 GMT<h1 id="はじめに">はじめに</h1>
<p>皆さん、こんにちは。エンジニア・技術広報の平木です。</p>
<p>以前からご覧になっていただいていた方にはお分かりかと思いますが、3/18 に今ご覧いただいているエンジニア・デザイナーブログを「Developer Portal」という名称に変更して 2017 年以来 5 年ぶりにリニューアルをしました。今回はリニューアルについて、目指すところと若干の技術的な側面をお伝えできればと思います。</p>
<h2 id="ブログトップ新旧比較">ブログトップ新旧比較</h2>
<h3 id="旧">旧</h3>
<p><img __ASTRO_IMAGE_="{"src":"./2022_04_01_01.png","alt":"旧ブログ","index":0}"></p>
<h3 id="新">新</h3>
<p><img __ASTRO_IMAGE_="{"src":"./2022_04_01_02.png","alt":"新ブログ","index":0}"></p>
<h1 id="エンジニアデザイナーブログリニューアルの背景">エンジニア・デザイナーブログリニューアルの背景</h1>
<p>まずは、 <strong>なぜリニューアルしたか?</strong> という背景についてです。弊社のエンジニア・デザイナーブログの変遷と共にお話していきたいと思います。</p>
<h2 id="エンジニアデザイナーブログの変遷">エンジニア・デザイナーブログの変遷</h2>
<h3 id="第-1-期201604--201707">第 1 期(2016/04 ~ 2017/07)</h3>
<p>Developer Blog は 2016/04 月に会社公式ブログにエンジニア向けの記事を掲載し始めたころから、しばらくは他の会社情報とともに掲載をしていました。記事内容はエンジニアが登壇したイベント情報や、今も続いている TechLunch(全社横断のエンジニア・デザイナー向け社内勉強会) の発表レポートなどがメインとなっていました。</p>
<p>この頃は開発組織の人数もそこまで多くなかったのですが、メドレーの開発組織を社内の様々な部署の雰囲気と共にお伝えしようという目的がメインでした。まずは、メドレーの開発組織としてのプレゼンスを高め、プロダクトを内製で日々開発をしていることを、外部の皆さんに知ってもらおうという目的が一番大きかったように思います。</p>
<p>このような形で 2017 年中頃までは会社公式ブログでの更新をしていましたが、開発組織が拡大するにつれ、組織として出来ることも増えてきました。それに伴い、これまでのように「開発組織の存在のアピール」や「組織の取り組み」に加えて、純粋に技術的な側面をもっと打ち出していき「医療 x IT」というテーマにメドレーはどのように向きあっているかを知ってもらおうという機運が高まってきました。</p>
<h3 id="第-2-期201707--202203">第 2 期(2017/07 ~ 2022/03)</h3>
<p>こうした運びで新しく Medley Developer Blog を 2017/07 月に立ち上げることになりました。会社公式ブログから完全に独立したテックブログということで、目論見通りにそれまで以上に技術的な投稿もできるようになりました。編集方式もこの時から現在までエンジニア・デザイナーが主体となっています。</p>
<p>更新頻度も不定期だったものを月 1~2 回と増やし、コンスタントにメドレーの開発に関する話題を取り扱うようにしました。運営をしていた 5 年の間にいくつかの記事は、おかげさまではてなブックマークなどで<a href="https://b.hatena.ne.jp/site/developer.medley.jp/?sort=count">話題</a>になることもあり、第 1 期よりもさらに皆さんに読んでいただけるようなブログになりました。</p>
<p>この間にメドレーも様々なエンジニア・デザイナーイベントにスポンサードさせていただいたりもして、ブログと合わせて一定のプレゼンスを得ることができてきたのではないかと思っています。特に「医療ヘルスケア業界」という馴染みが無い方にはハードルが高いと思われることが多い業界でのプロダクト開発に、一般的なインターネットテクノロジーを駆使しているという点を、色々な側面からお伝えできるようになったことは大きいと感じています。</p>
<h3 id="現在202203">現在(2022/03 ~)</h3>
<p>そんなブログでしたが、開設から 5 年経ち以下のような課題が出てきました。</p>
<ul>
<li>会社がまだ上場前に作られたデザインだったため、現在のコーポレートサイトなどのトーンとズレが出てきている</li>
<li>会社や組織規模が大きくなり、希薄になりがちな内部で働いている個人にもフォーカスが当てられるようなコンテンツが欲しい</li>
<li>採用的な側面として、メドレーに興味を持った方へ提供できる情報がバラバラになっている</li>
</ul>
<p>以上の課題を解決するために、ブログのリニューアルをすることになりました。</p>
<p>特に今年から全社的な方針として、メドレーのソフト面での話題にフォーカスしたコンテンツを作っていくということで、改めて公式<a href="https://note.com/medley">note</a>の更新に注力していることもあり、エンジニア・デザイナーブログもこの動きと連動する必要がありました。</p>
<p>以上の理由から、コンテンツとしては従来通りの <strong><a href="https://developer.medley.jp/entries">ブログ記事</a></strong> 、様々なメディアでメンバーが露出している <strong><a href="https://developer.medley.jp/interviews">インタビュー記事</a></strong> 、イベントなどで使われた <strong><a href="https://speakerdeck.com/medley">発表スライド</a></strong> を全て見られるような総合的なサイトにしていくこととなりました。</p>
<p>インタビューはこれまでもメンバーが様々なメディアに出ていたり、スライドも以前から Speaker Deck を更新していたのですが、まとまった形での提供ができていませんでした。</p>
<p>今回のリニューアルで、これらの要素をまとめて皆さんにお届けできるようになったため、「Medley Developer Blog」から「Medley Developer Portal」と名称変更をしました。</p>
<p>これからも、メドレーの開発組織をより外部の皆さんに知ってもらうために、運営ができればと考えていますので、よろしくお願いします。</p>
<h1 id="エンジニアデザイナーブログリニューアルの技術面について">エンジニア・デザイナーブログリニューアルの技術面について</h1>
<p>さて、ここまでリニューアルの背景をお伝えしましたが、ここからは今回使った技術面について軽く触れていこうと思います。</p>
<h2 id="使用技術について">使用技術について</h2>
<p>旧ブログは「はてなブログ」での運用をしていました。リニューアルにあたって上記の課題を解決するためには、独自にブログを開発したほうがよいと考えました。新ブログは以下の技術を使って開発しました。</p>
<ul>
<li><a href="https://www.gatsbyjs.com/">Gatsby</a>
<ul>
<li><a href="https://emotion.sh/docs/introduction">Emotion</a></li>
<li><a href="https://www.typescriptlang.org/">TypeScript</a></li>
</ul>
</li>
<li><a href="https://www.netlify.com/">Netlify</a></li>
</ul>
<p>Gatsby に決めた理由は大きくは以下でした。</p>
<ul>
<li>既に弊社内で Gatsby の導入実績があるため、社内のメンバーがいざとなったら触れる</li>
<li>ブログ + α のサイトを作るのに十分な柔軟性がある</li>
<li>公式やコミュニティプラグインが充実しており、開発が省力化できる</li>
</ul>
<p>Gatsby は精力的にアップデートが行なわれており、早いサイクルで進化するのも魅力の 1 つでした。</p>
<p>Gatsby 製サイトをどのように公開するかは、悩んだのですが、結果的に事例も多くありデプロイの簡易さや機能の豊富さを考えて Netlify にしました。</p>
<h2 id="はてなブログからの記事移行について">はてなブログからの記事移行について</h2>
<p>最初から旧ブログでのコンテンツは新ブログに移行することがマストだったので、まずはどのように移行ができるかということを考慮することから始めました。はてなブログは通常のエクスポートだと Movable Type 方式になるので、なんとか Markdown に変換するか…などと考えていました。</p>
<p>しかし、<a href="https://github.com/x-motemen/blogsync">blogsync</a>というはてなブログの CLI クライアントを通すとローカルに Markdown ファイルとしてブログエントリを同期することが可能だったため、</p>
<ol>
<li>blogsync で全ての旧ブログコンテンツをローカルに同期(ダウンロード)</li>
<li>ローカルに Markdown ファイルとしてダウンロードできたブログコンテンツを Gatsby 製の新ブログにコピー</li>
<li><code>gatsby-source-filesystem</code> でコピーした Markdown ファイルを読み込みブログとして表示</li>
</ol>
<p>という手順で既存のブログ記事を全て移行することができました。</p>
<p>また、ドメインについては独自ドメインをそのまま新ブログにも移行することとしたため、はてなブログと同じ URL 形式でブログが読み込めるようにルーティングをしました。</p>
<h2 id="苦労した部分">苦労した部分</h2>
<p>今回の開発で苦労した部分をダイジェストで書いていきます。自分のニーズに合わせて調べてみても、特に深い部分に関して、あまり情報が出てこないというパターンが多かったように思います。</p>
<h3 id="バージョン固有の情報やプラグインの情報が少なかった">バージョン固有の情報や、プラグインの情報が少なかった</h3>
<p>開発当初は Gatsby v4 が出たばかりだったので、何かに困って検索しても公式以外の情報は以前のバージョンで使えないことが多かったです。</p>
<p>またプラグイン関係の情報も調べる内容によっては中々目的の情報が出てこなかったりしました。<code>gatsby-plugin-image</code> 周りで顕著な印象だったので、画像周りの表示に泣かされることがありました。</p>
<p>他にも Markdown 表示は <code>gatsby-plugin-mdx</code> を使用していますが、従来の <code>gatsby-transformer-remark</code> と情報が混在している印象がありましたので混乱することも。</p>
<h3 id="ページネーションでベストなプラグインが見つからなかった">ページネーションでベストなプラグインが見つからなかった</h3>
<p><a href="https://www.gatsbyjs.com/docs/adding-pagination/">公式</a>ガイドを参考にページネーションを自前で作っていたのですが、<a href="https://graphql.org/">GraphQL</a>から持ってきたデータを表示するだけではあまりユーザビリティが良くなかったため、プラグインなど探したのですがこれといったものが見つからなかったです。</p>
<p>公式ガイド参考に作ると延々記事が増えたらページネーションの数も増えていってしまい微妙な感じでした…。自前で制御も考えましたが、最終的には<a href="https://mui.com/api/pagination/">mui/pagination</a>コンポーネントを組み合わせることで対応しました。</p>
<h3 id="ogp-画像自動生成に関して情報が少ない">OGP 画像自動生成に関して情報が少ない</h3>
<p>OGP 画像は自動生成にしたいと思いましたが、案外ニーズが無いのか情報が少なかった印象です。最終的に<a href="https://blog.kentarom.com/create-gatsbyjs-plugin-to-dynamically-generate-og-images/">こちら</a>のブログを発見し、<a href="https://github.com/kentaro-m/catchy-image">kentaro-m/catchy-image</a>を使って生成するようにしました。</p>
<h2 id="これからやりたいこと">これからやりたいこと</h2>
<p>機能の拡充以外だと、今まではできなかった<a href="https://github.com/textlint/textlint">textlint</a>での校正などをして、編集時の負荷軽減などをやっていきたいと考えています。</p>
<h1 id="さいごに">さいごに</h1>
<p>以上、 Developer Portal のリニューアルについて書かせていただきました。 引き続き、情報発信などをしていきメドレー開発組織について、皆さんに知っていただければと思います。</p>
<p>最後になりますが、医療ヘルスケアのプロダクト開発に(このようなブログ製作も)関わっていきたい!という方はぜひ下記よりご応募お待ちしております!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
- CLINICS アプリのリニューアルの裏側 (iOS 編)https://developer.medley.jp/entry/2022/02/15/180002https://developer.medley.jp/entry/2022/02/15/180002こんにちは、医療プラットフォーム本部/プロダクト開発室/第一開発グループ所属の世嘉良です。
メドレーには 2018 年の頭に入社しており、今年で 4 年目になります。 当初はサーバーサイドを中心に開発を担当していたのですが、最近は患者エンゲ...Tue, 15 Feb 2022 09:00:02 GMT<p>こんにちは、医療プラットフォーム本部/プロダクト開発室/第一開発グループ所属の世嘉良です。
メドレーには 2018 年の頭に入社しており、今年で 4 年目になります。 当初はサーバーサイドを中心に開発を担当していたのですが、最近は患者エンゲージメントチームという患者様に提供するサービスを開発するチームで主に iOS の仕事を担当することが多いです。</p>
<p>さて去年の 12 月になりますが、 <a href="https://clinics-app.com/">CLINICS アプリ</a> は <a href="https://www.medley.jp/release/20211207.html">UI のフルリニューアル</a>を行いました。
今回はリニューアルの裏話 (iOS について) をしていきたいと思います。</p>
<h1 id="これまでの-clinics-アプリについて">これまでの CLINICS アプリについて</h1>
<p>本題を書く前に、CLINICS アプリの歴史を紹介します。
ファーストコミットを見てみると、アプリの開発は 2016 年 2 月ごろからスタートし、ファーストリリースが行われたのが 2016 年 5 月でした。</p>
<p>当初、担当していたエンジニアは iOS の経験が豊富な方はおらず、全員で試行錯誤しながら開発を進めていたようです。
しばらくの間は機能の追加などが行われていましたが、<a href="https://clinics-cloud.com/karte">CLINICS カルテ</a> の開発に注力するために大きな開発はストップし、<a href="https://pharms-cloud.com">Pharms</a> との連携が開始される 2020 年 5 月頃まで機能や設計に関する見直しがほとんど行われてきませんでした。</p>
<p>iOS に詳しいエンジニアがいなかったにも関わらず、かなりのスピード感でリリースしていることはさすがの開発力という一言に尽きるのですが、Pharms との連携やお薬手帳といった機能が追加されたり、今後の開発を見据えた際に既存機能や設計の見直しを行いたいと思うようになってきました。</p>
<p>改善したい部分はたくさんあったのですが、対応工数を踏まえ今回のリニューアルでは以下の 2 点の改善に注力することにしました。</p>
<ol>
<li>Storyboard による View の管理</li>
<li>View とロジックの分離</li>
</ol>
<p>この 2 点についてそれぞれ詳しく説明します。</p>
<h1 id="1-storyboard-による-view-の管理">1. Storyboard による View の管理</h1>
<p>従来の開発では Storyboard を利用して View を作成していました。
Storyboard を使うとレイアウトや画面遷移を簡単に実装することができますが、開発を続けているうちに以下のような問題が目立つようになってきました。</p>
<ul>
<li>Storyboard が巨大化していき、複数人開発を行う際に支障がでる</li>
<li>レイアウトに追加・変更が行われた場合に AutoLayout の再設定に時間がかかる</li>
<li>コンポーネント自体にサイズが設定されていることがあり、コンポーネントを再利用できないことがある</li>
</ul>
<h2 id="課題">課題</h2>
<p>こちらは実際にあった Storyboard のキャプチャですが、複数の画面が 1 つの Storyboard 内に詰め込まれた状態となっていました。
Storyboard は Xcode のバージョンにより微妙な差分が発生してしまったり、AutoLayout の調整が必要になる場合があります。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20220210/20220210195921.png" alt="f:id:medley_inc:20220210195921p:plain"></p>
<p>こちらは古い Storyboard の画面を開いた後の差分なのですが、この変更がシステムによって加えられた変更なのか、他人の改修による変更なのかを後から見た時にわかりづらいという問題がありました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20220210/20220210200019.png" alt="f:id:medley_inc:20220210200019p:plain"></p>
<p>仮に問題が発生した場合は修正を試みるのですが、独自の XML によって表現されているため、iOS の経験が浅い僕にとっては修正が非常に難しかったです。</p>
<p>また、複数人で並行して開発する際にもコンフリクトが起きやすくなってしまい解消に時間がかかるという問題となっていました。</p>
<p>さらに従来の CLINICS アプリは <a href="https://speakerdeck.com/medley/clinics-dls-fan-shi-che-di-tesainyan-yu-sisutemufalsekoshao-jie">DLS</a> を利用してレイアウトを作成していたのですが、コンポーネントに直接サイズが設定されているものがあり、ある画面では微妙にサイズを調整したい…といった要件に対応できず、せっかくのコンポーネントを再利用できないケースがありました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20220210/20220210200108.png" alt="f:id:medley_inc:20220210200108p:plain"></p>
<h2 id="解決案">解決案</h2>
<p>課題に対する解決案は色々と考えられますが、弊社の開発スタイルやエンジニアのスキルを考慮し以下のような方針を立てました。</p>
<ul>
<li>Storyboard と各画面の実装は 1 : 1 の関係とする</li>
<li>画面遷移の責務も Storyboard から切り離す</li>
<li>コンポーネントにアトミックデザインを適用する</li>
</ul>
<p>Storyboard の分割は以下のようなステップで行っていました。</p>
<ol>
<li>Refactor to Storyboard を使って巨大な Storyboard を分割する</li>
<li>SwiftGen を利用し画面生成のコードを自動作成する</li>
<li>2 で生成したものを利用して画面遷移を行う</li>
<li>既存の Segue を削除する</li>
</ol>
<p>まず最初に Xcode 7 から利用可能となった「Refactor to Storyboard」を使って画面を分割するところから始めます。</p>
<p>この機能を利用すると選択した View が新しい Storyboard に切り出され、元の Storyboard には切り出した Storyboard へのリファレンスや Segue 等の接続が保持された状態になります。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20220210/20220210200138.png" alt="f:id:medley_inc:20220210200138p:plain"></p>
<p>次に各画面間の遷移を Segue を使わずに行うようにします。
Segue は便利なのですが、Identifier が単なる文字列であったり、Storyboard を分割してもリファレンスは保持しておく必要がある点が微妙に感じてしまい利用しないことにしました。</p>
<p>Segue を使わずに画面遷移を行う必要があるため、画面生成と画面遷移の方法を自前で実装する必要があります。
今回は画面生成の処理を <a href="https://github.com/SwiftGen/SwiftGen">SwiftGen</a> を利用して自動生成可能にし、VIPER というアーキテクチャの Router を参考にして画面遷移を Storyboard から切り離すことにしました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20220210/20220210200200.png" alt="f:id:medley_inc:20220210200200p:plain"></p>
<p>※ SwiftGen の利用方法は <a href="https://github.com/SwiftGen/SwiftGen#interface-builder">SwiftGen#interface-builder</a> を参照ください。</p>
<p>Router の実装は以下の通りです。
実装されたプロトコルを遷移元の VC に継承し、画面遷移のコードを呼び出すだけで画面遷移を行うことができます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="swift"><code><span class="line"><span style="color:#569CD6">protocol</span><span style="color:#4EC9B0"> ClinicWireframe</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">AnyObject </span><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#569CD6"> var</span><span style="color:#D4D4D4"> viewController: UIViewController { </span><span style="color:#569CD6">get</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> presentClinic</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinicId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> pushClinic</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinicId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">extension</span><span style="color:#4EC9B0"> ClinicWireframe</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> presentClinic</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinicId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> vc = StoryboardScene.</span><span style="color:#9CDCFE">Clinic</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">initialScene</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">instantiate</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#DCDCAA"> ClinicViewController</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">coder</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">$0</span><span style="color:#D4D4D4">, </span><span style="color:#DCDCAA">isHeaderEnabled</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">true</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> presenter = </span><span style="color:#DCDCAA">ClinicPresenter</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">view</span><span style="color:#D4D4D4">: vc)</span></span>
<span class="line"><span style="color:#D4D4D4"> presenter.</span><span style="color:#DCDCAA">setClinicId</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinicId</span><span style="color:#D4D4D4">: clinicId)</span></span>
<span class="line"><span style="color:#D4D4D4"> vc.</span><span style="color:#DCDCAA">inject</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">presenter</span><span style="color:#D4D4D4">: presenter)</span></span>
<span class="line"><span style="color:#D4D4D4"> vc.</span><span style="color:#9CDCFE">modalPresentationStyle</span><span style="color:#D4D4D4"> = .</span><span style="color:#9CDCFE">pageSheet</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> nc = </span><span style="color:#DCDCAA">UINavigationController</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">rootViewController</span><span style="color:#D4D4D4">: vc)</span></span>
<span class="line"><span style="color:#D4D4D4"> viewController.</span><span style="color:#DCDCAA">present</span><span style="color:#D4D4D4">(nc, </span><span style="color:#DCDCAA">animated</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">true</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> pushClinic</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinicId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> vc = StoryboardScene.</span><span style="color:#9CDCFE">Clinic</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">initialScene</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">instantiate</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#DCDCAA"> ClinicViewController</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">coder</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">$0</span><span style="color:#D4D4D4">, </span><span style="color:#DCDCAA">isHeaderEnabled</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> presenter = </span><span style="color:#DCDCAA">ClinicPresenter</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">view</span><span style="color:#D4D4D4">: vc)</span></span>
<span class="line"><span style="color:#D4D4D4"> presenter.</span><span style="color:#DCDCAA">setClinicId</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinicId</span><span style="color:#D4D4D4">: clinicId)</span></span>
<span class="line"><span style="color:#D4D4D4"> vc.</span><span style="color:#DCDCAA">inject</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">presenter</span><span style="color:#D4D4D4">: presenter)</span></span>
<span class="line"><span style="color:#D4D4D4"> viewController.</span><span style="color:#9CDCFE">navigationController</span><span style="color:#D4D4D4">?.</span><span style="color:#DCDCAA">pushViewController</span><span style="color:#D4D4D4">(vc, </span><span style="color:#DCDCAA">animated</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">true</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>最後にコンポーネントの調整についてですが、CLINICS アプリではアトミックデザインを簡略化し、以下のような基準でコンポーネントを実装しています。</p>
<ul>
<li>Block: UI の最小単位 (他の PJT にも持ち込めそうなレベルまで分解されたもの)</li>
<li>Partial: CLINICS の PJT 固有のコンポーネント</li>
<li>Layout: ヘルプ用のモーダルやエラー画面など他の画面から呼び出されることで初めて意味をなす画面など</li>
<li>Page: 各 ViewController とそれに紐づく Storyboard</li>
</ul>
<p>この管理方法自体は 弊社の別プロダクトの開発を担当しているエンジニアによって考案されたものです。
弊社ではサーバー・フロント分け隔てなく開発を任されることも多く、厳密なアトミックデザインだとコンポーネントの分類に困ることが多かったためこのような管理方法をとっているそうです。
今回のリニューアルに際して CLINICS アプリでもこれを参考にすることにしました。</p>
<p>これまで DLS として管理されていたコンポーネントは Block で実装しなおし、Width / Height といったサイズの設定を行わないようにしました。</p>
<p>このようにコンポーネントの実装レベルを明確にしたことで、実装に一定の指針が生まれ使い回しの効くコンポーネントを作成しやすくなりました。</p>
<h1 id="2-view-とロジックの分離">2. View とロジックの分離</h1>
<p>スピード開発が求められた背景を考えると仕方のないことではあるのですが、従来の CLINICS アプリでは ViewController にロジックが記述されており、俗にいう Fat ViewController の状態になってしまっていました。</p>
<p>Fat ViewController の問題点については既にさまざまな方が取り上げていますが、以下の部分が問題と感じています。</p>
<ul>
<li>UI とロジックが分離されていないため、テストを書くことが難しい</li>
<li>可読性が低くなりがち</li>
<li>ロジックが切り出されていないため似たような実装が点在する場合がある</li>
</ul>
<h2 id="課題-1">課題</h2>
<p>こちらは実際にあった ViewController の一部です。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20220210/20220210200226.png" alt="f:id:medley_inc:20220210200226p:plain"></p>
<p>このコードに関しては以下の部分が問題と感じていました。</p>
<ul>
<li>通信処理の呼び出しが ViewController の責務になっていること</li>
<li>通信結果を整形するロジックが ViewController の責務になっていること</li>
<li>画面描画のための State 管理が ViewController の責務になっていること</li>
</ul>
<h2 id="解決案-1">解決案</h2>
<p>UI とロジックを分離するための手法はさまざまなものがありますが、弊社のアプリには以下のような特徴があります。</p>
<ul>
<li>iOS 以外にも複数のプラットフォームをサポートしているためフロントエンドで保持するデータや複雑なロジック自体は少ない (サーバー側でなるべく担保している)</li>
<li>実装時や QA 時に感じた違和感について細かくディレクターと打ち合わせし、その結果次第では仕様を変更することがある</li>
</ul>
<p>これらを考慮すると、プロジェクトの期間的に初期の導入コストが低く、データフローがシンプルなものに留めたいというように考えがまとまってきました。</p>
<p>これらを考慮し、CLINICS アプリでは <strong>MVP</strong> というアーキテクチャを採用することにしました。
より詳細には MVP の Passive View 方式を採用しており、以下のような形で実装しています。</p>
<ul>
<li>View は基本的にすべての入力イベントに対応した Presenter の処理を呼び出す</li>
<li>Presenter は入力に応じて通信処理などの外部要因となる処理を呼び出し、結果を整形する</li>
<li>プレゼンテーションロジックの結果を描画するように View に指示を出す</li>
<li>View は Presenter の指示によってのみ描画処理を行い、自身を起点とした描画処理は行わない</li>
</ul>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20220210/20220210201010.png" alt="f:id:medley_inc:20220210201010p:plain" title="[Model–view–presenter - Wikipedia](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter)"></p>
<p><a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter">Model–view–presenter - Wikipedia</a></p>
<p>既に Router は導入してるため、複雑な処理フローを実装したい場合は Interactor を導入するだけで VIPER アーキテクチャへと発展させることが簡単にできる点も魅力でした。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20220210/20220210200319.png" alt="f:id:medley_inc:20220210200319p:plain"></p>
<p>CLINICS アプリでの ViewController / Presenter の実装を簡単にまとめたものは以下のようになっています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="swift"><code><span class="line"><span style="color:#569CD6">final</span><span style="color:#569CD6"> class</span><span style="color:#4EC9B0"> ClinicViewController</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">UIViewController </span><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#569CD6"> private</span><span style="color:#569CD6"> lazy</span><span style="color:#569CD6"> var</span><span style="color:#D4D4D4"> presenter: ClinicPresenterInput = {</span></span>
<span class="line"><span style="color:#DCDCAA"> fatalError</span><span style="color:#D4D4D4">(“Failed to inject presenter”)</span></span>
<span class="line"><span style="color:#D4D4D4"> }()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> override</span><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> viewDidLoad</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#569CD6"> super</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">viewDidLoad</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#D4D4D4"> presenter?.</span><span style="color:#DCDCAA">refresh</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> inject</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">presenter</span><span style="color:#D4D4D4">: ClinicPresenterInput) {</span></span>
<span class="line"><span style="color:#569CD6"> self</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">presenter</span><span style="color:#D4D4D4"> = presenter</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // タップされた際に呼び出す</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> onTapServiceCell</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">_</span><span style="color:#9CDCFE"> service</span><span style="color:#D4D4D4">: ServiceEntity, </span><span style="color:#DCDCAA">isTelemedicine</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Bool</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#D4D4D4"> presenter?.</span><span style="color:#DCDCAA">didTapServiceButton</span><span style="color:#D4D4D4">(service, </span><span style="color:#DCDCAA">isTelemedicine</span><span style="color:#D4D4D4">: isTelemedicine)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// MARK: - ClinicPresenterOutput</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">extension</span><span style="color:#4EC9B0"> ClinicViewController</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">ClinicPresenterOutput </span><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> reloadData</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinic</span><span style="color:#D4D4D4">: ClinicEntity?)</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#D4D4D4"> clinicViewStore.</span><span style="color:#9CDCFE">clinic</span><span style="color:#D4D4D4"> = clinic</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> openCreateAppointmentView</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinic</span><span style="color:#D4D4D4">: ClinicEntity, </span><span style="color:#DCDCAA">service</span><span style="color:#D4D4D4">: ServiceEntity, </span><span style="color:#DCDCAA">isTelemedicine</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Bool</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#6A9955"> // 予約へ進む</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> showErrorAlert</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">_</span><span style="color:#9CDCFE"> error</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Error</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#6A9955"> // エラー表示を行う</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="swift"><code><span class="line"><span style="color:#569CD6">protocol</span><span style="color:#4EC9B0"> ClinicPresenterInput</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">AnyObject </span><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> refresh</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> didTapServiceButton</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">_</span><span style="color:#9CDCFE"> service</span><span style="color:#D4D4D4">: ServiceEntity, </span><span style="color:#DCDCAA">isTelemedicine</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Bool</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">protocol</span><span style="color:#4EC9B0"> ClinicPresenterOutput</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">AnyObject </span><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> reloadData</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinic</span><span style="color:#D4D4D4">: ClinicEntity?)</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> openCreateAppointmentView</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinic</span><span style="color:#D4D4D4">: ClinicEntity, </span><span style="color:#DCDCAA">service</span><span style="color:#D4D4D4">: ServiceEntity, </span><span style="color:#DCDCAA">isTelemedicine</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Bool</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> showErrorAlert</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">_</span><span style="color:#9CDCFE"> error</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Error</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">final</span><span style="color:#569CD6"> class</span><span style="color:#4EC9B0"> ClinicPresenter</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> private</span><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> clinicRepository: ClinicRepository</span></span>
<span class="line"><span style="color:#569CD6"> private</span><span style="color:#569CD6"> var</span><span style="color:#D4D4D4"> clinic: ClinicEntity?</span></span>
<span class="line"><span style="color:#569CD6"> private</span><span style="color:#569CD6"> var</span><span style="color:#D4D4D4"> cancellables: </span><span style="color:#4EC9B0">Set</span><span style="color:#D4D4D4"><AnyCancellable> = .</span><span style="color:#569CD6">init</span><span style="color:#D4D4D4">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> init</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">view</span><span style="color:#D4D4D4">: ClinicPresenterOutput, </span><span style="color:#DCDCAA">container</span><span style="color:#D4D4D4">: DIContainer) {</span></span>
<span class="line"><span style="color:#569CD6"> self</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">view</span><span style="color:#D4D4D4"> = view</span></span>
<span class="line"><span style="color:#D4D4D4"> clinicRepository = container.</span><span style="color:#DCDCAA">resolve</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> private</span><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> reloadView</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#D4D4D4"> view?.</span><span style="color:#DCDCAA">reloadData</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinic</span><span style="color:#D4D4D4">: clinic)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// MARK: - PresenterInput</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">extension</span><span style="color:#4EC9B0"> ClinicPresenter</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">ClinicPresenterInput </span><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> refresh</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#C586C0"> guard</span><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> clinicId = clinicId </span><span style="color:#C586C0">else</span><span style="color:#D4D4D4"> { </span><span style="color:#C586C0">return</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> clinicRepository.</span><span style="color:#DCDCAA">getClinic</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinicId</span><span style="color:#D4D4D4">: clinicId)</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">sink</span><span style="color:#D4D4D4">(</span></span>
<span class="line"><span style="color:#DCDCAA"> receiveCompletion</span><span style="color:#D4D4D4">: { [</span><span style="color:#569CD6">weak</span><span style="color:#569CD6"> self</span><span style="color:#D4D4D4">] completion </span><span style="color:#C586C0">in</span></span>
<span class="line"><span style="color:#C586C0"> guard</span><span style="color:#569CD6"> let</span><span style="color:#569CD6"> self</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">self</span><span style="color:#C586C0"> else</span><span style="color:#D4D4D4"> { </span><span style="color:#C586C0">return</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#C586C0"> guard</span><span style="color:#C586C0"> case</span><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">failure</span><span style="color:#D4D4D4">(error) = completion </span><span style="color:#C586C0">else</span><span style="color:#D4D4D4"> { </span><span style="color:#C586C0">return</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6"> self</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">view</span><span style="color:#D4D4D4">?.</span><span style="color:#DCDCAA">showErrorAlert</span><span style="color:#D4D4D4">(error)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#DCDCAA"> receiveValue</span><span style="color:#D4D4D4">: { [</span><span style="color:#569CD6">weak</span><span style="color:#569CD6"> self</span><span style="color:#D4D4D4">] response </span><span style="color:#C586C0">in</span></span>
<span class="line"><span style="color:#C586C0"> guard</span><span style="color:#569CD6"> let</span><span style="color:#569CD6"> self</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">self</span><span style="color:#C586C0"> else</span><span style="color:#D4D4D4"> { </span><span style="color:#C586C0">return</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6"> self</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">clinic</span><span style="color:#D4D4D4"> = response.</span><span style="color:#9CDCFE">data</span></span>
<span class="line"><span style="color:#569CD6"> self</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">reloadView</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> )</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">store</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">in</span><span style="color:#D4D4D4">: &cancellables)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> didTapServiceButton</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">_</span><span style="color:#9CDCFE"> service</span><span style="color:#D4D4D4">: ClinicsServiceEntity, </span><span style="color:#DCDCAA">isTelemedicine</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Bool</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#C586C0"> guard</span><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> clinic = clinic </span><span style="color:#C586C0">else</span><span style="color:#D4D4D4"> { </span><span style="color:#C586C0">return</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> view?.</span><span style="color:#DCDCAA">openCreateAppointmentView</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinic</span><span style="color:#D4D4D4">: clinic, </span><span style="color:#DCDCAA">service</span><span style="color:#D4D4D4">: service, </span><span style="color:#DCDCAA">isTelemedicine</span><span style="color:#D4D4D4">: isTelemedicine)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>ViewController と Presenter は以下のように Router の内部で DI するようにしています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="swift"><code><span class="line"><span style="color:#569CD6">protocol</span><span style="color:#4EC9B0"> ClinicWireframe</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">AnyObject </span><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#569CD6"> var</span><span style="color:#D4D4D4"> viewController: UIViewController { </span><span style="color:#569CD6">get</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> pushClinic</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinicId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">extension</span><span style="color:#4EC9B0"> ClinicWireframe</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> pushClinic</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinicId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> vc = StoryboardScene.</span><span style="color:#9CDCFE">Clinic</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">initialScene</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">instantiate</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#DCDCAA"> ClinicViewController</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">coder</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">$0</span><span style="color:#D4D4D4">, </span><span style="color:#DCDCAA">isHeaderEnabled</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> presenter = </span><span style="color:#DCDCAA">ClinicPresenter</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">view</span><span style="color:#D4D4D4">: vc, </span><span style="color:#DCDCAA">container</span><span style="color:#D4D4D4">: </span><span style="color:#DCDCAA">DIContainer</span><span style="color:#D4D4D4">())</span></span>
<span class="line"><span style="color:#D4D4D4"> presenter.</span><span style="color:#DCDCAA">setClinicId</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">clinicId</span><span style="color:#D4D4D4">: clinicId)</span></span>
<span class="line"><span style="color:#D4D4D4"> vc.</span><span style="color:#DCDCAA">inject</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">presenter</span><span style="color:#D4D4D4">: presenter)</span></span>
<span class="line"><span style="color:#D4D4D4"> viewController.</span><span style="color:#9CDCFE">navigationController</span><span style="color:#D4D4D4">?.</span><span style="color:#DCDCAA">pushViewController</span><span style="color:#D4D4D4">(vc, </span><span style="color:#DCDCAA">animated</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">true</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>これによって、もともと ViewController にあった処理は以下のように分離されました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20220210/20220210200341.png" alt="f:id:medley_inc:20220210200341p:plain"></p>
<p>また、View と Presenter はインターフェースを介してしかお互いを知らないため、実装の交換が簡単にできるようになりました。
Presenter を交換することで 1 つの View で別々の処理を表現することが可能となったり、View をテストコードを交換することで Presenter の入出力値をテストすることができるようになっています。</p>
<h1 id="まとめ">まとめ</h1>
<p>CLINICS アプリは歴史のあるプロジェクトですが、今回のプロジェクトを通して UI だけでなく裏側の実装も刷新しています。
巨大な Storyboard を分解したことでメンテナビリティが向上し、MVP (+ Router) の導入によって View とロジックの交換が簡単になり、テストなどの実装を取り入れやすくなりました。</p>
<h1 id="さいごに">さいごに</h1>
<p>ブログの本編には書けなかったのですが、今回リニューアルされた画面に関しては SwiftUI を利用してフルスクラッチで実装していたり、 Asset の管理方法についても大きな見直しを行いました。</p>
<p>またアーキテクチャの選定にあたり、「<a href="https://peaks.cc/books/iOS_architecture">iOS アプリ設計パターン入門</a>」が大変参考になりました。iOS の開発を行う方はぜひ一読してほしいと思います。</p>
<p>約半年ほどかかった大きなプロジェクトでしたが、患者エンゲージメントチームのメンバー全員で取り組み無事にリリースまで漕ぎ着けることができました。まだまだ手探りな部分もありますが、今後も患者さんにとってより安心して使えるサービスとなるように開発を続けていければと思っています。</p>
<p>長くなりましたが、最後までお読みいただきありがとうございました。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 医療ヘルスケアベンチャーで開発チームと Sync した知財部門を立ち上げた話https://developer.medley.jp/entry/2021/12/03/180544https://developer.medley.jp/entry/2021/12/03/180544はじめに
こんにちは、メドレーのコーポレート本部法務コンプライアンス部で、知的財産関連の業務を担当している鬼鞍です。
1 年ほど前に本ブログで「特許とはなにか」というテーマで当社における特許啓蒙活動を紹介させて頂いたのですが、思っていた以上...Fri, 03 Dec 2021 09:05:44 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは、メドレーのコーポレート本部法務コンプライアンス部で、知的財産関連の業務を担当している鬼鞍です。</p>
<p>1 年ほど前に本ブログで「<a href="/entry/2020/05/22/173902">特許とはなにか</a>」というテーマで当社における特許啓蒙活動を紹介させて頂いたのですが、思っていた以上に反響があり、「面白い」とか「わかりやすい」という嬉しいコメントも頂戴しました。</p>
<p>私がメドレーに入社して最初のミッションが知財部門の立ち上げだったので、次はぜひ**「ベンチャーでの知財部門の立ち上げ方」**的なテーマでブログ発信したいと思っていたのですが、当初はまだ具体的な社内事例が少なかったため、実績ができた段階で改めて本ブログで発表したいと思っていました。</p>
<p>それから1年以上が経過して実際に特許も取得するなど知財活動としての実績が蓄積されてきたため、これを機会に<strong>ベンチャーで知財部門を立ち上げる際にやるべきことについて、実例に触れながら具体的にまとめてみました。</strong></p>
<p>かなり長いコンテンツになってしまいましたが、<strong>フェーズごとの知財活動計画</strong>であったり、<strong>知財ロードマップの作り方</strong>など、<strong>なかなか表に出てこない知財部門立ち上げストーリーの具体的事例を惜しみなく書かせていただいております</strong>ので、ぜひご一読いただき、お役に立てれば幸いです。</p>
<h1 id="知財は開発チームとの信頼関係の構築がまず第一">知財は開発チームとの信頼関係の構築がまず第一</h1>
<p>先ほどは知財部門を立ち上げストーリの中で、知財活動計画とか、知財ロードマップであるとか少し格好つけたような文字が羅列していましたが、実は私が知財専任として初めに配置されて最初にやったことはそんな大それたことではありませんでした。</p>
<p>最初になにをやったかというと、<strong>エンジニアの方とランチに行く、あるいは飲みに行く</strong>、ということでした。当時はまだ Covid-19 が蔓延する前だったので気軽にそういうことができました。入社して最初の 1 ヶ月くらいは週 2 回くらいのペースで誰かとランチか飲みにいくということをしていたと思います。</p>
<p>飲みに行く、というとふざけているとか思われるかもしれませんが、知財部門の立ち上げストーリーを語る上で、このプロセスは無視できないほど重要だと考えています。先に断っておくと私は酒好きでもなければ、家でも一切飲みません。</p>
<p>当たり前のことなのですが、知財というのはプロダクトを生み出す開発組織があって初めて価値を発揮するものです。知財はあくまで事業や開発をサポートするところであって、それ単体で存在意義を発揮することはほぼありません。特に特許活動については開発チームとの連携が非常に重要です。</p>
<p>一緒にご飯を食べることが信頼関係を構築する上で最良の方法、とまでは言いませんが、少なくともご飯を食べない人はいないわけで、時間を無駄にしている感もなければ、オフィスの外で会えば仕事以外のことを話題にし易いので互いの趣味嗜好を知ることができます。みなさんもそうだと思いますが、通常、仲の悪い人とは一緒に食事はしないと思います。</p>
<p>特に、この後にもご紹介する<strong>社内のアイデアを発掘するという発明発掘活動は、話しやすいカジュアルな雰囲気で雑談のような感じでブレストするというのが非常に重要</strong>で、このときに相手がどんな人間かを互いに知っているか、知らないかでは大きな差がでてきます。</p>
<p>また更に、この後にご紹介する知財計画立案や知財ロードマップを作成することも当然重要なのですが、結局仕事というのは人と人との間を思考がやりとりされて実現化されていくものなので、人間関係・信頼関係が全ての基礎であり、実は一番大切にするべきものだと思います。</p>
<p>これは何も知財に限ったことではなく、部門を跨いで仕事をする上で信頼関係は非常に重要ですし、知財の観点で見ても<strong>円滑なコミュニケーションが結果的にいい発明やアイデアを生み出すための土壌づくりに貢献する</strong>ものだと考えています。</p>
<h1 id="知財活動計画は事業の成長フェーズに合わせて設定する">知財活動計画は、事業の成長フェーズに合わせて設定する</h1>
<p>知財部門の設立にあたって、エンジニアとのコミュニケーションと並行して進めたのが、知財活動計画の立案でした。知財活動とは、知財サイクルをうまく事業サイクルにインストールしてサイクルを循環させることであり、更にはこのサイクルが循環するための運用基盤を構築することを指します。また、ここでいう知財サイクルとは、例えば、社内に埋もれたアイデアを発掘し(創発)→ それを知財権化し(保護)→ 権利化した知財を活用し(活用)→ 活用した知財で得られた利益や結果を創発支援に適用できる形に変換する、というものです。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211126/20211126120413.jpg" alt="20211126120413.jpg">
<p>このような知財サイクルを、何の計画や戦略もなしに事業サイクルへインストールしようとするとうまくいきません。なぜなら、事業というのは日々成長し変化していくもので、<strong>事業の成長に応じて知財活動の目的や課題も変化するからです</strong>。</p>
<p>例えば、子会社が増えて事業規模が拡大すると、その子会社の事業領域で既に多くの特許出願がされている場合には、発明発掘活動よりもまずは法的リスクを回避することを最優先にして、他社の特許調査をしっかりやってからプロダクトをリリースする仕組みを作ろう、ということになります。あるいは、事業の成長に伴いプロダクトが成熟してきた場面では、似たようなプロダクトが乱立する結果、尖った機能や最先端技術で差別化をするようになるため、こうした場合には先行した特許取得が重要な目的になってきます。</p>
<p>このように、事業の成長フェーズに応じた知財活動の計画を立案するべく、まずはメドレーのミッションおよびそのミッションを実現するためのプロダクトの性質を把握する必要がありました。</p>
<p>メドレーは「医療ヘルスケアの未来をつくる」というミッションを掲げ、インターネットテクノロジーによる医療のあり方の変革を目指す企業です。</p>
<p>このミッションと向き合いつつ、<strong>事業プラットフォームの成長フェーズごとに知財活動の方向性・目的を3段階にわけて設定し、成長フェーズごとに知財活動計画を立案しました</strong>。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211126/20211126120416.jpg" alt="20211126120416.jpg">
<p>例えば、プラットフォームの創設期においては新規ユーザ獲得が重要となり、使い勝手のいいシンプルな機能に開発の方向性が向いているため、独自性のある技術を特許化するというよりも、まずは守りを固めるべく知財権侵害のリスクを最小限にして法的安定性の確保に軸足を置いた知財活動を推進します。</p>
<p>このような計画は、知財の観点で一方的にできるものではありません。</p>
<p>知財活動の計画に際しては、開発チームを統括するマネージャにプロダクト開発の方向性をヒアリングして特許取得の判断基準を検討したり、経営層に将来の事業の方向性についてヒアリングをして長期志向で目標設定したりして、ブラッシュアップしていきました。</p>
<p>そういった検討や議論を重ねていくうちに自然と、その企業風土や事業ミッションにあった知財活動計画というものができあがってくるのだと思います。</p>
<h1 id="知財活動ロードマップで現在位置を把握しつつ定期的に軌道修正する">知財活動ロードマップで現在位置を把握しつつ定期的に軌道修正する</h1>
<p>長期的な方向性を可視化したら次はそれをどう実行するかという実行計画が必要になってきました。</p>
<p>そもそも自分たちは今何ができていて、何ができていないのか。またどのようなことを今後やっていかなければならないのかが不明確だよね、という話になり、それを具体的に俯瞰するための知財ロードマッピングというものを作成しました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211126/20211126120419.jpg" alt="20211126120419.jpg">
<p>縦軸に知的財産の種類、横軸に時間軸であるフェーズを設定して、知財活動の内容に過不足ないかをチェックできるようにしました。</p>
<p>定期的に内容を見直してメドレーの事業規模にあった形に修正しながら自分達の現在位置を把握し、今後の方向性を見定めるためのツールとして有効でした。</p>
<p>このようなロードマップがあれば、知財担当者の立場からすると、実績が可視化されるため知財活動を推進していく上での達成感もありますし、逆に知財担当者を評価する立場からしても、明確な評価基準がない中で、知財担当者の評価をする際に役に立ちます。</p>
<p>以上のように、何もないところから他部門と関わりながら知財活動計画を立案し、ロードマップを達成していくのは、知財部門を立ち上げるという業務の面白さの1つでもあります。</p>
<h1 id="初期段階から長期視点で知財素人でも回せる業務運用作りをする">初期段階から長期視点で知財素人でも回せる業務運用作りをする</h1>
<p>1 人法務や 1 人知財担当という状況は、遅かれ早かれ属人化という問題が必ずやってきます。</p>
<p>属人化は組織運営上、健全ではありません。</p>
<p>そのため、<strong>知財に関する知識がない人間でも、誰でもその業務を回すことができるような状態をつくる</strong>ために、知財部門立ち上げの初期段階から、自分が担当している業務でルーチン化できるものは全て仕組み化してドキュメントに落とし込むようにしていました。</p>
<p>例えば「怪しい特許を見つけた場合の動き方」、「発明報奨金の決定プロセス」、「他社特許調査及び発明発掘のハイブリッド調査の進め方」などなど、ありとあらゆるプロセス業務を全てフロー図や概念図を作成し、誰か見てもわかる業務ということに留意しています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211126/20211126120422.jpg" alt="20211126120422.jpg">
<p>現状はまだ引き継ぎや新たな知財部員の登用という話がないので、わかりやすい効果を発揮しているわけではないのですが、他部門からの問い合わせがあった際は資料だけ渡せば理解してもらえるので、コミュニケーションの効率化には貢献していると思います。</p>
<h1 id="どのような特許を取得すべきなのか">どのような特許を取得すべきなのか</h1>
<p>企業としてどのような特許を取得していくべきか、ということは「知財を最大限に活用する」という知財の出口から考えていく上で、重要な事項になります。</p>
<p>そしてどのような特許を取得するかは、事業領域や事業ミッションに沿って設定されるべきです。</p>
<p>メドレーは、オープンプラットフォームによる医療のあり方の変革を目指している企業なので、開発チームから上がってきたアイデアについては、<strong>プラットフォーム事業を適切に運営していく上で必要な技術かどうか</strong>、という観点で特許化するかどうかを判断しています。</p>
<p>例えば、最近特許を取得した**医療データの管理方法(<a href="https://www.j-platpat.inpit.go.jp/c1800/PU/JP-6921177/A0C5C2D58C02064BEB59C6825B06C3CBE4628C3B499220B9ED43E50DD8EF6E7C/15/ja">特許第 6921177 号</a>)**というものがあります。これは、アプリ端末から入力された患者データと、医療機関の各業務システムから入力された患者データという 2 つの類似したデータをシームレスに管理するために、両方のデータを 2 つのデータベース上で統合管理することにより、データ管理の責任分担の明確化及び厳格な情報管理を可能にするというものです。</p>
<p>仕組みとしてはとてもシンプルなのですが、<strong>患者の持つ端末と、医科・歯科・調剤等の各業務システムをシームレスに連携させる仕組みが、医療プラットフォームを適切に運営していく上で必要な技術だと判断</strong>したため、特許化しました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211126/20211126120426.jpg" alt="20211126120426.jpg">
<p>また、上記事例のように実際にプロダクトに実装されている技術を特許化したものだけでなく、実際にプロダクトに実装するかわからないけれども将来を見据えた先見的な特許として、**ブロックチェーンを用いた電子処方箋の管理方法(<a href="https://www.j-platpat.inpit.go.jp/c1800/PU/JP-6936763/AF400254F45C09B0BC8C9BA831F8C3CB3C41740DF9A6E4E697B15FA9348785F1/15/ja">特許第 6936763 号</a>)**という特許を取得しています。</p>
<p>これは、ブロックチェーンを用いたアクセス権の管理機能として全体的に秘密状態を保ちながら電子処方箋を管理するためのアクセス制御に関する仕組みで、ブロックチェーンデータベース上において処方箋データと患者データとを紐づけて、医師端末からの患者レコードへの一時的なアクセス権限を付与し、会計終了時に患者端末の操作に応じて医師端末へのアクセス権限を剥奪するというものです。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211126/20211126120429.jpg" alt="20211126120429.jpg">
<p>このような特許を取得した背景には、<strong>マーケットを独占して権利主張をするためというよりも、メドレーが考える未来のプロダクトビジョンであったり、開発部門の先見的な創作活動を対外的に発信したい</strong>、という思いがあります。</p>
<p>メドレーは、顧客利用率の高いプロダクト、機能拡張性の高いプロダクトの開発を推進しているため、**独創的で最新技術を取り入れた尖った機能を目指すというよりはユーザにとって使いやすいシンプルさが多く取り入れられています。**また、それだけではなく、前述したブロックチェーンと電子処方箋との組み合わせ技術の先見的な特許取得のように、10 年先を見据えた未来のプロダクトのあり方についても日々追求しています。</p>
<h1 id="特許を身近に感じてもらうためのユニークな話も取り入れて社内に啓蒙する">特許を身近に感じてもらうためのユニークな話も取り入れて社内に啓蒙する</h1>
<p>いい特許を生み出すためには地道な社内の啓蒙活動も重要です。</p>
<p>メドレーでは、開発チーム間の技術格差の是正や、技術情報の共有による活性化を目的として、エンジニアが日々の業務で扱っている技術や取り組みについて共有するテックランチという勉強会が定期的に開催されています。</p>
<p>先日そのテックランチで、社内のエンジニアの方達に少しでも特許を身近に感じてもらおうと、「特許の頭体操」というコーナーを設けて、実際に頭を使って体験してもらいました。</p>
<p>下の例では、おたまとスプーンとの違いを考えようというお題を通して、ある物の特徴を把握する際に、<strong>「違い」から「もの」を観察すると、その物の特徴が浮き出てくる</strong>、というメッセージが含まれています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211126/20211126120432.jpg" alt="20211126120432.jpg">
<p>おたまもスプーンも対象物をすくい上げるという機能においては共通しているのですが、おたまの先端のお皿部分とスプーンの先端のお皿部分とは、その形状が異なります。スプーンのお皿部分は楕円形ですが、おたまのお皿部分は円形になっています。</p>
<p>つまり、おたまの特徴は、特許的にいうと「その先端にあるお皿部分が円形の開口を有した半球状で、対象物をすくうための所定の深さを有している」ということになります。</p>
<p>この辺はちょっと固苦しい表現が続いたせいもあり、「難しい…」というコメントをもらいました。馴染みのない人にとってはただただ面倒な作業だと思います。</p>
<p>しかし、ここで言いたかったのは、<strong>これはあくまで構造物についての話であって、ソフトウェアの特徴を把握するということはそんなに難しいことではないのだよ</strong>、ということでした。</p>
<p>ソフトウェアの場合は、ちょっとした機能を追加すれば、それが従来のソフトとは異なるものとなり、比較的簡単に特許になってしまうケースが少なくありません。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211126/20211126120842.jpg" alt="20211126120842.jpg">
<p>つまり、エンジニアの方は日々新しい機能を実装すべく開発業務を行っているわけなので、<strong>知財の観点から言うと、極端なことを言えば日々発明をしている</strong>ということになります。当然特許になるかどうかは別の話になりますが、日々の開発業務で自分が発明をしていることに気づいていない、という状況が多分にあるということです。</p>
<p>さらに、<strong>知財担当がこれに気づかなければ、日の目を見ることない埋蔵知財が量産される</strong>ということになってしまいます。</p>
<p>では、どのようにしてエンジニアの方が自ら発明をしていることに気づいていないものを発掘し、社内の知的財産として認定していくのか、というのが次の話になります。</p>
<h1 id="面白いアイデアは雑談から生まれる">面白いアイデアは雑談から生まれる</h1>
<p>日々の開発業務の延長で生まれてくる機能や技術を特許化するという活動が基本であることは間違いないのですが、実際にプロダクトに実装するかどうか不確定要素を多く含むアイデアを特許化するという活動は、エンジニアの開発成果物を知的財産として見える化することで、エンジニアの成果が報われる土壌を作るという意味においては大切な活動になってきます。</p>
<p>ただ、どの企業知財部でも発明発掘業務はされていると思いますし、「アイデアは雑談から生まれる」ということは既に周知の事実かもしれません。また、知財活動として特段新しいことをしているわけではないのですが、実際にそれを実行する際の心構えであったり、具体的に気をつけていることについて、事例を通してご紹介したいと思います。</p>
<p>私は、<strong>エンジニアの方というのは、アイデアの卵をもっている</strong>という前提に立ってエンジニアの方とコミュニケーションをとるようにしています。</p>
<p>そうすると、「実はアイデアありますよね、なんで言ってくれないんですか〜。」という具合にカジュアルな会話をスタートすることができ、スムーズにブレストを進めることができたりします。大したことではないのですが、<strong>意外とこういう心がけが円滑なコミュニケーションを生み出すきっかけになっている</strong>かもしれません。</p>
<p>一方で、エンジニアの方は暇ではありません。</p>
<p>日々の開発では、既存機能の修正や新規機能の開発などの案件に追われるため、頭の中にあるアイデアの卵も埋もれてしまいがちです。</p>
<p>以上のことを踏まえ、エンジニアからアイデアを出してもらうためのブレスト MTG では主に以下の点に気をつけています。</p>
<ul>
<li>エンジニアの方の負担にならないように短時間に設定すること(長くて 30 分)</li>
<li>カジュアルに話しやすい雑談ベースの雰囲気にすること</li>
<li>そのために少人数で行うこと</li>
<li>出てきたアイデアを楽しむこと</li>
</ul>
<p>実際に下図に示すような雑談ベースの会話から、「診療方式を患者と医療機関との間の距離に応じて、対面診療かオンライン診療かを自動的に切り替える」というアイデアが生まれ、実際に特許出願につながりました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211126/20211126120918.jpg" alt="20211126120918.jpg">
<p>この時にエンジニアの方々からは「とても面白い!」とか「またやりたい!」という前向きなコメントをもらえて、カジュアルベースの雑談効果を実感しました。</p>
<p>このようなブレストを通して一番大きな気づきというのは、エンジニアの皆さん、本当によくプロダクトについて考えているということ。</p>
<p>エンジニアの方々の創意工夫が全て知的財産「権」になるわけではないのですが、知的財産であることには違いありません。</p>
<p>これからは、そういった<strong>エンジニアの方の頭の中に埋もれがちな創意・工夫というものが報われるような土壌作りを、知的財産というツールを使って構築していきたい</strong>と考えています。</p>
<h1 id="このブログも特許を対外的にどう見せるかという実験の1つ">このブログも「特許を対外的にどう見せるか」という実験の1つ</h1>
<p>ここまでご紹介してきた内容は、知財計画を立てて、立てた計画に沿って知財業務を運用し、運用していく中でアイデア発掘して知財化する、という話にとどまってましたが、実際に取得した知財権をどのように活用して企業価値の向上につなげるのか、ということがこれからの知財部門にとっての重要なミッションになると考えています。</p>
<p>従来は、特許権の独占排他権という性質を使って参入障壁として活用したり、模倣の抑止に活用されるのが常識だったと思いますが、メドレーではこれまでにない新たな活用方法を模索しています。</p>
<p>実はこのブログもそうなのですが、特許を対外的にどうみせるか、ということの1つの実験です。</p>
<p>どういうことかというと、ここまでご紹介してきた<strong>知財活動自体も人の知的活動によって生み出された知的財産であり、このようにブログで発信しなければ単なる埋蔵知財になってしまいます</strong>。</p>
<p>例えば、特許取得についてもただ特許を取ったという事実を公表するのではなく、どうしてそのような特許を取得してどのように活用しているのか、またそこに関連したメンバーは誰でどのような検討があったのか、というストーリーメイキングをすると、特許というものを中心とした1つのドラマが浮き上がってきます。</p>
<p>今回は知財活動のご紹介という趣旨で、特許を中心とした内容になっているため、特許に関心がない人にはあまりささらないコンテンツになっているかもしれませんが、例えば特許と広報との掛け算でストーリーメイキングをすれば<strong>特許に関心がある人だけでなく、広報に関心がある人にも情報がリーチすることになります</strong>。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211126/20211126120933.jpg" alt="20211126120933.jpg">
<p>これは<strong>特許というものが、人材・業務ノウハウ・広報・採用活動等と同列に知的財産である</strong>がゆえになせるものです。これからは、知的財産=特許、商標という視点ではなく、知財=目に見えない創造的な活動という捉え方をした上で、企業価値に貢献していく必要があります。</p>
<p>知財部門が、採用、IR、広報、開発、社内 IT といった企業内にある多数の部門とコラボレーションすることでこれまで見たことのない新しい知財活用の形を模索し、<strong>知財を最大活用することによって企業価値を向上させていきたい</strong>と考えています。</p>
<h1 id="さいごに">さいごに</h1>
<p>ここまで、知財活動の一部をご紹介させていただきましたが、これが知財活動の進め方として正解というわけではなく、企業風土や事業領域によって、知財活動のあり方は異なります。そして、そこを考えながら知財活動を推進していくことが仕事の面白さであり醍醐味とも言えます。新しい知財部のあり方や、新しい知的財産の活用方法を検証しながらも、建設的かつ柔軟に日々の業務を進め、メドレーの事業をしっかりとバックアップしていきたいと考えています。少しでも当社の知財活動が参考になれば幸いです。最後までお付き合い頂きありがとうございました!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- サービスのダウンタイムなく RDS を Aurora に移行した話https://developer.medley.jp/entry/2021/11/08/180120https://developer.medley.jp/entry/2021/11/08/180120こんにちは、人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属の森川です。医療介護求人サイト「ジョブメドレー」の開発チームの一員として、現在はインフラタスクをメインに取り組んでいます。
今年(2021 年)の 6 月の話ではあ...Mon, 08 Nov 2021 09:01:20 GMT<p>こんにちは、人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属の森川です。医療介護求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」の開発チームの一員として、現在はインフラタスクをメインに取り組んでいます。</p>
<p>今年(2021 年)の 6 月の話ではありますが、ジョブメドレーの連携サービスにて使用しているデータベースを、通常の RDS(<a href="https://aws.amazon.com/jp/rds/mysql/">Amazon RDS for MySQL</a>)から Aurora(<a href="https://aws.amazon.com/jp/rds/aurora/?aurora-whats-new.sort-by=item.additionalFields.postDateTime&aurora-whats-new.sort-order=desc">Amazon Aurora</a>)に移行しました。</p>
<p>タイトルは自分でも大きく出たなと思ってはいるのですが、サービスの特性に助けられたところはありつつも、ダウンタイムなく移行することができました。今回はそのいきさつについてお話させていただこうと思います。</p>
<p>本稿では Amazon RDS for MySQL を「通常の RDS」、Amazon Aurora を「Aurora」と表記させていただきます。</p>
<h1 id="ジョブメドレーの連携サービスについて">ジョブメドレーの連携サービスについて</h1>
<p>ジョブメドレーは医療介護従事経験者が運営する就職・復職・転職のための求人サイトです。このジョブメドレーの連携サービスとして医療・介護・福祉・歯科従事者向けの情報サービスを他社と共同運営しています(以降、本サービス)。</p>
<p>本サービスはアプリケーションシステムのバックエンドの開発フレームワークとして Ruby on Rails(以降、Rails)を使用し、弊社で開発・運用を行っています。表示コンテンツは他社から共有していただくものを表示する形となっています。</p>
<h1 id="発端">発端</h1>
<p>今年の 1 月末に AWS から以下のようなメールが届きました(内容を一部抜粋しています)。</p>
<blockquote>
<p>Amazon RDS は、MySQL メジャーバージョン 5.6 の廃止 (EOL) プロセスを開始しています。</p>
</blockquote>
<blockquote>
<p>お客様は古いバージョンで実行されている RDS MySQL インスタンスを1つ以上お持ちであるように見受けられます。</p>
</blockquote>
<blockquote>
<p>Amazon RDS for MySQL 5.6 は、UTC 協定世界時間の 2021 年 8 月 3 日に廃止されます。</p>
</blockquote>
<p>公式のお知らせは<a href="https://aws.amazon.com/jp/blogs/news/amazon-rds-for-mysql-5-6-retirement/">こちら</a>です。</p>
<p>EOL 通知はドキッとします。できれば見たくないものです。</p>
<p>この対象となっていたのが、本サービスで使用している RDS の MySQL v5.6.39(当時)でした。</p>
<p>対応方針として以下を考えました。</p>
<ul>
<li>通常の RDS のまま MySQL のバージョンを 5.7 に上げる</li>
<li>通常の RDS から Aurora への変更も行いつつ、MySQL のバージョンを 5.7 に上げる</li>
<li>MySQL のバージョンを 8 まで上げる</li>
</ul>
<p>MySQL 8 系へのバージョンアップは対応範囲が大きくなりすぎるため今回は断念しました。5.6 の EOL 期限が迫っているため時間の余裕はあまりありません。また、もし 8 系に上げるとしても、5.7 へのバージョンアップは挟む必要がありそうです。</p>
<p>ジョブメドレー本体のシステムにおいても、性能やメンテナンス性の向上の観点から、通常の RDS から Aurora への移行を計画しております。その足がかりとして、関連アプリケーションの中でも比較的システム規模の小さいものから、先行して移行作業をしておきたいという要望がありました。</p>
<p>これらを加味して、今回のミッションは「<strong>通常の RDS から Aurora への変更も行いつつ、MySQL のバージョンを 5.7 に上げる</strong>」となりました。</p>
<h1 id="検証">検証</h1>
<p>インフラ構成に変更を加える際には言うまでもなく検証が必要です。インフラ関連タスクに取り組むにあたって大きなウェイトを占めるのが検討・検証なのではないかと個人的には考えています。</p>
<p>Aurora への乗り換え検証のために、新たに DB インスタンスを用意しました。既存相当のスペックの通常 RDS とそれよりも少しスペックの落とした Aurora です。正確な比較を行う場合であれば、両 DB インスタンスのスペックは揃えるべきかとは思いますが、現行の RDS がオーバースペックであることも課題のひとつであったため、検証段階から Aurora のスペックを落として検証を行っています。よって検証結果による成否判断としては「著しい悪化が見られないか」という観点になっています。</p>
<p>既存相当の RDS のスペックは<code>db.m4.large</code> 、Aurora のスペックは暫定的に<code>db.t3.medium</code>を選択しました。また、データベースに流し込むデータは本番相当のものを個人情報などはマスクした上で用意しています。</p>
<p>検証の項目は以下の 4 点です。</p>
<ul>
<li>レスポンスタイム検証</li>
<li>SQL ごとの速度検証</li>
<li>実行計画の検証</li>
<li>フェイルオーバー後に元 writer に対する接続が継続される問題への対応検証</li>
</ul>
<h2 id="レスポンスタイム検証">レスポンスタイム検証</h2>
<p>まずは、レスポンスタイムの影響について確認しました。</p>
<p>本サービスにおける主要なページの URL を選出します。それぞれのページに対して数回のリクエストを行い「低負荷状態」でのレスポンスタイムを比較しました。Aurora の方が一桁ミリ秒程度遅いという結果となりました。</p>
<p>次に<a href="https://httpd.apache.org/docs/2.4/programs/ab.html">Apache Bench</a>を用いて短時間に並列でのリクエストを行い「高負荷状態」を再現して比較を行いました。本サービスはスパイクで負荷が高まるような性質ではないため、普段のアクセス頻度を元に「高負荷状態」がどの程度かを想定して定義しています。結果として、一部のページにて 1 秒ほど遅くなっていることが分かりました。スペックを上げることで解決ができるのかを調査するため、インスタンスサイズを<code>db.t3.medium</code>から<code>db.t3.large</code>に変更して改めて試しました。しかし Aurora の方が遅いという結果に変化は見られませんでした。アプリケーション側の調査を進めると、データベースの問題ではなくアプリケーション側の問題であることが分かり、別途対応ということになりました。</p>
<p>以上より、Aurora への移行によるレスポンスタイムへの影響としては、多少の性能の低下は見られたものの許容範囲内であり、大きな問題は見られないことが分かりました。</p>
<h2 id="sql-ごとの速度検証">SQL ごとの速度検証</h2>
<p>こちらはレスポンスタイムの検証に近しい検証ではありますが、生の SQL の速度の調査も行いました。アプリケーションを通して実行される SQL の中でも実行される頻度の高いものを選出し、アプリケーションを介さずクライアントツールから実行しています。</p>
<p>こちらも大きな問題は見られませんでした。</p>
<h2 id="実行計画の検証">実行計画の検証</h2>
<p>この検証でもアプリケーションの中で実行される頻度が高いクエリを選出し調査を行いました。使用したのはお馴染みの<code>EXPLAIN</code>ステートメントです。</p>
<p>実行計画に差分は見られなかったため問題はありませんでした。</p>
<h2 id="フェイルオーバー後に元-writer-に対する接続が継続される問題への対応検証">フェイルオーバー後に元 writer に対する接続が継続される問題への対応検証</h2>
<p>Rails アプリケーションへの Aurora 導入において、必ずと言ってよいほど障壁となるのがこの問題かと思います。</p>
<h3 id="aurora-のフェイルオーバーと-rails-への影響">Aurora のフェイルオーバーと Rails への影響</h3>
<p>Aurora の特徴や通常の RDS との差分については様々なサイト・記事で詳しくまとまっている情報ですので本稿での詳解は割愛しますが、Aurora のフェイルオーバーについてのみ概要を説明させていただきます。</p>
<p>マルチ AZ での Aurora DB クラスターのプライマリ DB インスタンス(writer と呼ばれる)において障害が発生した場合、フェイルオーバー(待機システムへの切り替え)が自動で実行されます。プライマリ DB インスタンスのリードレプリカ(reader と呼ばれる)を配置していた場合は、それが次のプライマリへと昇格し、エンドポイントも切り替わってくれます。</p>
<p>次に Rails の ActiveRecord のコネクションプールについてです。ActiveRecord はデータベースへの接続情報を保持し、そのコネクションを再利用することで接続時のオーバーヘッドを解消し、高速化を図る仕組みを保有しています。本サービスにおいて MySQL のクライアントとして使用している<a href="https://github.com/brianmario/mysql2">mysql2</a>では、コネクションがアクティブであることを<code>mysql_ping</code>が判定してくれるようです。しかし残念なことに Aurora 側でのフェイルオーバーの発生までは検知することができないようです。</p>
<p>したがって、フェイルオーバーが発生すると、プライマリから降格した元 writer 現 reader である DB インスタンスに対してコネクションが張り続けられてしまうため、書き込みのリクエストに対してはエラーとなってしまう状態になります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211105/20211105182842.png" alt="20211105182842.png">
<h3 id="対応策">対応策</h3>
<p>対応策としては以下を考えました。</p>
<ol>
<li>Aurora 用のクライアント Gem を使用する</li>
<li>Mysql2::Error が発生した場合、サーバーエラーにしつつコネクションを切断して再接続を促す</li>
<li>一定回数リクエストを処理するごとに再接続させる</li>
</ol>
<p>今回採用したのは上記の 3 の「一定回数リクエストを処理するごとに再接続させる」対応です。</p>
<p>当初は対応策 1 の Gem の使用を検討しておりましたが、トランザクション中にフェイルオーバーが発生した際の挙動が本サービスには適合しないことが分かりました。またいくつかの対応策を複合して実装するという方法も考えられました。</p>
<p>今回においては、実装工数や、サービスの規模から考えうる必要十分の最小値を検討した結果、この判断としています。</p>
<p>再接続の度に発生するオーバーヘッドによる速度影響についても低負荷・高負荷の状態で検証を行いましたが、数値的には軽微であり、少々の遅延は許容することになりました。</p>
<p>この対応は、Aurora への移行の前、つまり通常の RDS で稼働している状態であってもリリースして問題ないものだったため、事前に実装・リリースを行うことができました。</p>
<h1 id="料金">料金</h1>
<p>「現行の RDS がオーバースペックであることも課題のひとつ」と上記しておりましたが、その是正による料金の合理化についても副次的効果として期待していました。</p>
<p>Aurora の料金形態には、通常の RDS と同じ「DB インスタンス時間従量課金」「ストレージ時間従量課金」に加え、「I/O リクエスト数従量課金」という項目が追加されているため、比較を行う際は注意が必要です。</p>
<p>移行前後の条件は以下の通りです。</p>
<ul>
<li>通常の RDS(移行前)
<ul>
<li>オンデマンド</li>
<li>RDS で稼働しているのは本番環境のみ</li>
<li>db.m4.large</li>
<li>ストレージ 100GB</li>
</ul>
</li>
<li>Aurora(移行後)
<ul>
<li>オンデマンド</li>
<li>本番環境だけでなく検証環境も Aurora で稼働させる</li>
<li>本番環境 db.t3.medium, 検証環境 db.t3.small</li>
<li>ストレージ 100GB(両環境とも)</li>
</ul>
</li>
</ul>
<p>移行前の通常の RDS では本番環境のみで月々 4 万円以上かかっていたコストですが、移行後は本番環境に加え検証環境においても Aurora を使用するようにしても合計 3 万円以下という計算となりました。月々 1 万円、年額 12 万円の節約になります。さらにはオンデマンドからリザーブドへ変更することも今後検討ができるため、コストカットの余地がまだ残されているのも嬉しいところです。</p>
<h1 id="リリースに向けた準備">リリースに向けた準備</h1>
<p>リリース作業のキモとなるデータベースの切り替えは、「リードレプリカからの昇格」という方法を採ることとしました。MySQL のバージョンアップについては、プライマリが通常の RDS から Aurora に切り替わった後に、Aurora インスタンスの MySQL バージョンを上げるための変更を実行します。</p>
<p>これらの作業をデータの不整合を発生させることなく完遂させるためには、<strong>データベースへの書き込み動作をすべて停止させる</strong>必要がありました。</p>
<p>これを実現するためには、データベースへの書き込み処理が発生するケースをすべて洗い出さなければいけません。調査をしたところ、本サービスでは「社内管理画面」と「バッチ処理」でのみ書き込み処理が実行されていることが分かりました。</p>
<p>つまり、一般ユーザからの書き込み処理は本サービスでは保有していないため、書き込みが行われるタイミングを把握・調整することが可能でした。したがって、一般ユーザが見ることのできるページをメンテナンスモードに切り替えるなどの「サービスのダウンタイム」を発生させることなく Aurora への移行と MySQL のバージョンアップを行える、ということになります。</p>
<p>リリース時に影響が生じる周辺情報の洗い出しも一通り済んだところで、次に「手順書」の作成に取り掛かりました。必要な全ての操作について順序に沿って詳細にまとめます。その中には、各段階にて万が一障害が発生した場合の切り戻し手順についても記載しています。</p>
<p>手順書を元に、検証環境にて同様の手順を実行し、予行演習を行いました。これによって、本番での作業を鮮明にイメージすることができます。また大抵の場合、手順書の不備やブラッシュアップできるところが見つかります。予行演習は本番作業を滞りなく実行するための最も重要なファクターのひとつかと思います。</p>
<figure class="figure-image figure-image-fotolife" title="実際に使用した手順書(一部抜粋)"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211105/20211105182450.png" alt="20211105182450.png"><figcaption>実際に使用した手順書(一部抜粋)</figcaption></figure>
<h1 id="リリース作業">リリース作業</h1>
<p>リリースで行った作業の要点をまとめると以下となります。</p>
<ul>
<li>本番用データベースのリードレプリカを Aurora で作成</li>
<li>データベースへの書き込みを停止
<ul>
<li>社内管理画面の操作を停止いただくよう社内へのアナウンス</li>
<li>バッチ処理の実行をすべて停止</li>
</ul>
</li>
<li>レプリカ Aurora をプライマリへ昇格</li>
<li>データベースのコネクション更新のためサーバ 1 台ずつアプリケーションの再起動</li>
<li>バッチ処理の再開</li>
<li>動作確認</li>
</ul>
<p>大きめのリリース作業はアクセスの多い時間帯を避けて行われることもあるかと思いますが、今回はサービスのダウンタイムなく作業を行えるケースであるため午前中からの作業開始となりました。また作業を行う自分の隣には先輩社員が構えており、ダブルチェックをしていだきました。</p>
<p>手順書の作成と予行演習の甲斐もあって、本番リリースを問題なく進めることができました。</p>
<h1 id="まとめ">まとめ</h1>
<p>サービスの特性に助けられたところは大きいですが、サービス停止などのダウンタイムを発生させることなく通常の RDS から Aurora への移行を完了させることができました。</p>
<p>スティーブ・ジョブズは数分のプレゼン発表のために数百時間の準備をしていたといいます。プレゼンで言うところのシナリオの作成とリハーサルの実行もそうですが、サービス特性の理解や検証を綿密に行うことも本番を成功させるためには欠かせません。準備を怠らないエンジニアでありたい、と執筆しながら改めて感じているところです。</p>
<h1 id="さいごに">さいごに</h1>
<p>メドレーでは「医療ヘルスケアの未来をつくる」というミッション実現のため、日々開発・運営を行っています。エンジニア・デザイナーをはじめ多くのポジションでメンバーを募集しております。もし少しでもご興味をお持ちいただけましたら、ぜひお気軽にお話しさせていただければと思います。</p>
<p>最後までお読みいただき、ありがとうございました。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 2021 年度新卒エンジニア研修についてhttps://developer.medley.jp/entry/2021/10/12/180147https://developer.medley.jp/entry/2021/10/12/180147こんにちは。医療介護求人サイト「ジョブメドレー」の開発を担当しているエンジニアの山田です。 今年の新卒エンジニア研修において、メンターを担当しました。
メドレーでは 2019 年度から新卒採用を行なっており、今年 2021 年度は 5 名の...Tue, 12 Oct 2021 09:01:47 GMT<p>こんにちは。医療介護求人サイト「ジョブメドレー」の開発を担当しているエンジニアの山田です。 今年の新卒エンジニア研修において、メンターを担当しました。</p>
<p>メドレーでは 2019 年度から新卒採用を行なっており、今年 2021 年度は 5 名の新卒がエンジニアとして入社しました。</p>
<p>例年と同じく 4 月から 9 月にかけて、約 5 ヶ月間の新卒エンジニア研修を実施しましたので、その取り組みを、研修受講者である新卒からの声も交えてご紹介します。</p>
<h1 id="新卒研修の概要">新卒研修の概要</h1>
<p>今年の新卒研修の最終ゴールは、「<strong>メドレーのエンジニアとして、<a href="https://www.medley.jp/team/culture.html">Our Essentials</a>(※) を体現し、顧客へ価値提供できるようになるための基礎を身につけ、経験を得ること</strong>」として掲げました。</p>
<p><em>※) メドレーの行動原則</em></p>
<p>メドレーの新卒エンジニア研修では、技術を身につけることだけではなく、ビジネスパーソンとしての基礎を身につけ、メドレーが大切にしている価値観を理解し、体現する意識をもって、顧客への価値提供について自分の言葉で話せるようになることまでを目指してもらいます。</p>
<p>研修は昨年同様、大きく分けて、4 つのフェーズに区切って行いました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211013/20211013113305.png" alt="20211013113305.png">
<p>また、全研修期間を通じて、各新卒にはメンターを一人ずつ付けました。メンターは、一週間に 1~2 回のペースで新卒と 1on1 ミーティングを実施し、フィジカルとメンタルの両面を気遣い、個別にフォローを行いました。</p>
<h1 id="新卒研修の内容">新卒研修の内容</h1>
<h2 id="フェーズ-1社会人メドレー基礎研修">フェーズ 1:社会人&メドレー基礎研修</h2>
<h4 id="リスク研修">リスク研修</h4>
<ul>
<li>インサイダー取引防止研修</li>
<li>コンプライアンス研修</li>
<li>情報セキュリティ研修</li>
</ul>
<h4 id="ビジネス研修">ビジネス研修</h4>
<ul>
<li>ビジネスマナー研修</li>
<li>ビジネススキル研修</li>
<li>ビジネススタンス研修(外部研修)</li>
</ul>
<p>フェーズ 1 では、<strong>成果を出し、価値を発揮するために必要なビジネスパーソンとしての基本的な仕事の型を身につけること</strong>をゴールとしました。</p>
<p>リスク研修では、メドレー社員として、社会人として、身の周りで起こりうるリスクについて考え、いかにそれらのリスクと向き合うかを講義形式で学んでもらいました。</p>
<p>ビジネス研修では、社会人としての最低限のマナーを学び、論理的思考力、コミュニケーション力など、エンジニア職に限らない課題解決力へつながるポータブルな知識を、座学とワークショップを通じて定着してもらうことを図りました。</p>
<p>また、社会人の基準で仕事と向き合い、適切な報連相によって周囲と協働していくことの重要性についても学んでもらいました。</p>
<h5 id="新卒からの声">新卒からの声</h5>
<blockquote>
<ul>
<li>質の高い、多量のインプット・アウトプットができた</li>
<li>伝わるメールの書き方、名刺の渡し方など、社会人に必須のマナーやスキルを認識できた</li>
<li>ワークを通じて、言葉では理解していても行動するとできないことを洗い出せた</li>
</ul>
</blockquote>
<h2 id="フェーズ-2エンジニア基礎研修">フェーズ 2:エンジニア基礎研修</h2>
<h4 id="開発基礎-1">開発基礎 1</h4>
<ul>
<li>メドレーエンジニアとして求めること</li>
<li>事業(ジョブメドレー ・ CLINICS ・介護のほんね)の概要説明</li>
<li>開発基礎研修(Ruby on Rails チュートリアル)</li>
<li>開発実践 (要件定義〜リリースまで)</li>
</ul>
<h4 id="開発基礎-2">開発基礎 2</h4>
<ul>
<li>技術書の輪読会</li>
<li>ドキュメンテーションスキル研修</li>
<li>プレゼンテーションスキル研修</li>
<li>中間レポート作成</li>
<li>中間報告会</li>
</ul>
<p>フェーズ 2 では、新卒研修後に開発業務に入ってもらえるよう、<strong>エンジニアとしての基礎を身につけること</strong>をゴールとしました。</p>
<h3 id="メドレーエンジニアとして求めること">メドレーエンジニアとして求めること</h3>
<p>開発に関わるこのフェーズにおいても、要件定義を含む汎用的な技術的スキルは勿論のこと、メドレーエンジニアが共通して持つべき価値観などを共有するため、フェーズ 2 初日は「<strong>メドレーエンジニアとして求めること</strong>」と題して、エンジニアの執行役員 田中が講義を行いました。</p>
<p>講義では、「<strong>エンジニア</strong>とは、<strong>エンジニアの価値</strong>とは、<strong>プロエンジニア</strong>とはなんでしょうか?」という問いから始まり、講義の終わりにはもう一度同じ問いかけをして締めくくり、新卒がメドレーの求めるエンジニア像について自身の言葉で話せるように考えてもらいました。</p>
<p>メドレーが求めるエンジニア像については、CTO 平山の<a href="https://toppa.medley.jp/05.html"> メドレー平山の中央突破: THE エンジニア</a> にも書かれていますので、よろしければ、あわせてご覧ください。</p>
<p>さらに、メドレーが展開する各事業および関連するプロダクトの概要説明をプロダクトマネージャーが行い、メドレーで開発する意義をあらためて認識してもらいました。</p>
<h3 id="開発基礎研修">開発基礎研修</h3>
<p>2 日目より、<a href="https://railstutorial.jp/">Ruby on Rails チュートリアル</a>(以下、「Rails チュートリアル」)を教材とした、開発基礎研修に移りました。</p>
<p>メドレーのプロダクトは Rails で作られているものが多く、Web アプリケーションを開発するための基礎を身につけるためにも、Rails チュートリアルの内容を実施してもらいました。</p>
<p>単純に、Rails チュートリアルの内容に沿って、ダラダラと写経するのではなく、随時、学んだことは Confluence にまとめ、GitHub 上で Pull Request を作成する形で、ソースコードを共有してもらいました。</p>
<p>学んだことを自分の言葉に置き換えてアウトプットすることで反復学習を促し、Pull Request を作成してもらうことで GitHub の使い方に慣れてもらうことを図りました。</p>
<p>また、デイリーで朝会と夕会を実施しました。朝会は仕事のリズムを整えるための顔合わせ、夕会は新卒から質問・成果を共有してメンターがそれに対してフィードバックをする場としてそれぞれ実施しました。</p>
<p>研修前に既に Rails チュートリアルを一周していた新卒もいましたが、二周目を実施して新たな気付きを得たり、AWS を用いてクラウド上に環境構築し、作成した Web アプリケーションをデプロイするまでを実践してもらうなど、インフラに関しても理解を深めてもらうことができました。</p>
<h5 id="新卒からの声-1">新卒からの声</h5>
<blockquote>
<ul>
<li>システムのパフォーマンスを上げるための工夫を知ることができた</li>
<li>バグ発生〜原因特定〜修正、というデバッグのスピードが、研修序盤から飛躍的に上がった</li>
<li>プロダクトで利用している AWS の各種サービスの概要を理解できたことに加え、サービス間の繋がりやネットワークの流れに関しても理解を深めることができた</li>
</ul>
</blockquote>
<h3 id="開発実践">開発実践</h3>
<p>開発基礎研修にて Web アプリケーション開発の基礎を学んだ後は、「<strong>メドレー/グループ会社で使う来訪者受付システム</strong>」を開発題材として、開発実践研修を行いました。</p>
<p>開発業務全体の流れを把握することで、<strong>チームで開発(課題解決)することを経験し、今後の仕事に役立たせること</strong>を目的としました。</p>
<p>本研修で達成すべきこととして掲げていたものは主に、次の通りです。</p>
<ul>
<li>プロジェクト管理能力を身につけること</li>
<li>開発する対象を体系的に整理できる能力を養うこと</li>
<li>システム設計に関する基礎的な物事を理解すること</li>
<li>チーム開発を理解すること</li>
<li>品質を理解すること</li>
</ul>
<p>既に決まりきった仕様書に沿って開発するのではなく、新卒自身が現状の問題把握や課題整理を行って、ユーザーへ価値提供するために何を作るべきかを考えることから始まり、リリース後の運用方法やランニングコストのことまで考え提案してもらいました。</p>
<p>開発実践研修は約 1 ヶ月の期間をかけて行いました。大まかな流れとしては、次の通りです。</p>
<ol>
<li>要件定義(ヒアリング・現状把握・課題整理・要求分析・機能/非機能要件の洗い出し・ UI 草案)</li>
<li>プロジェクト計画(役割分担・ WBS/ガントチャート作成)</li>
<li>設計(画面設計・機能設計・データモデリング・方式設計・インフラ設計)</li>
<li>開発(実装・コードレビュー)</li>
<li>QA(テスト設計・テスト)</li>
<li>成果発表(成果物を関係者へプレゼン・リリース)</li>
</ol>
<p>方式設計の一部として、開発に使用する言語などの選定も新卒自身が行いました。</p>
<p>今回作成した社員向け管理画面と来訪者向け画面はいずれも SPA(一部、PWA)のアーキテクチャを採用し、主なライブラリ/フレームワークに関して、フロントエンドは<a href="https://www.typescriptlang.org/">TypeScript</a>, <a href="https://ja.reactjs.org/">React</a>, <a href="https://nextjs.org/">Next.js</a>, <a href="https://chakra-ui.com/">Chakra UI</a>, <a href="https://ionicframework.com/jp/docs/">Ionic Framework</a>、バックエンドは Ruby on Rails(API モード)をそれぞれ利用することとなりました。</p>
<p>選定理由としては主に、次の通りです。</p>
<ul>
<li>TypeScript, React(両画面共通)
<ul>
<li>ロジックからテンプレートまでの全てのコードを静的型付けで書くことができ、堅牢性に優れているため</li>
</ul>
</li>
<li>Next.js, Chakra UI(社員向け管理画面)
<ul>
<li>ゼロコンフィグでビルドやレンダリングを最適化できるため</li>
<li>アクセシビリティに優れたリッチな UI を素早く構築できるため</li>
</ul>
</li>
<li>Ionic Framework(来訪者向け画面)
<ul>
<li>iPad 上で、ネイティブアプリのような UI/UX を提供するため</li>
</ul>
</li>
<li>Ruby on Rails API モード
<ul>
<li>フロントエンドとバックエンドを分離して疎結合にするため</li>
<li>短期間で構築するため</li>
<li>社内でもよく使われておりメンテナンスしやすいため</li>
</ul>
</li>
</ul>
<p>インフラは AWS を採用し、EC2, S3, RDS, CloudFront, Route53, CloudWatch などのサービスを利用しました。</p>
<p>結果的に、本研修プログラムの成果物としてリリースされたシステムは「<strong>Medley Entrance</strong>」という名前で、社内ツールとして現在、毎日稼働しており、ユーザーとしてメドレー/グループ社員だけではなく、来訪者の方々にも使っていただいています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211007/20211007112134.png" alt="20211007112134.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20211007/20211007112210.png" alt="20211007112210.png">
<p><em>Medley Entrance(上:社員向け管理画面、下:来訪者向け画面)</em></p>
<p>チームで課題解決に臨み、価値提供までの実績を残せたことは自信につながり、開発実践研修のやりがいとして、感じてもらえたのではないでしょうか。</p>
<p>要件定義などの期間中、想定よりもスムーズに進められなかった時も他責にせず、各々がリーダーシップを発揮し、建設的に進めていく新卒の様子をメンターの一人として傍で見させてもらいました。</p>
<p>この 1 ヶ月間の開発実践研修を通じて、技術力はさることながら、課題解決に対する十分な熱意と主体性を新卒から感じられ、とても頼もしい印象として残りました。</p>
<h5 id="新卒からの声-2">新卒からの声</h5>
<blockquote>
<ul>
<li>開発の中での方針を意識して設計/実装することができた(シンプルにする)</li>
<li>QA とはそもそも何かというリサーチから入り、有識者の考え方を軸に方針を決めてから始められた</li>
<li>各々が最適なパフォーマンスを発揮できる環境づくりを意識して、高速な意思決定が可能な体制を整えることができた</li>
<li>要件が決まりきっていない中で設計するのは難しかった</li>
<li>開発タスクが集中していた時に、プロジェクト全体の現状を把握できていなかった</li>
<li>文章を作るスキルが足りていない</li>
</ul>
</blockquote>
<h3 id="技術書の輪読会">技術書の輪読会</h3>
<p>フェーズ 2 の開発基礎 2 の輪読会では、 <a href="https://www.amazon.co.jp/dp/4774142042/">『Web を支える技術 -HTTP、URI、HTML、そして REST』</a> を題材書籍として、7 日間に渡って毎日、次の手順で実施しました。</p>
<ol>
<li>参加者が同じパートをあらかじめ読んでおき、書籍から学んだこと、ネットなどで調べても解消しきれなかった疑問点などをまとめる</li>
<li>その内容をもとに、夕方のミーティング時において、各自が発表してディスカッションを行う</li>
<li>ディスカッションした内容は議事録にまとめる</li>
</ol>
<p>輪読会は他者からの学びを共有してもらうことで、自分にはなかった視点・気付きを獲得し、その書籍への理解をより深められる効果があります。</p>
<p>本研修プログラムにおける輪読会の目的としては、<strong>Web サービスを開発していく上で必要となる知識へ触れることにより、今後獲得していくべき知識のベースラインを理解すること</strong> でしたが、輪読会ならではのメリット・楽しさを新卒に実感してもらえたことも、副次的な効果としてあったと思います。</p>
<h5 id="新卒からの声-3">新卒からの声</h5>
<blockquote>
<ul>
<li>Web の基本的な知識を「なぜ登場したのか」を理解しながら網羅的に学ぶことができた</li>
<li>文書でまとめた後に、口で説明することが学んだ内容の定着に良いと感じた</li>
<li>これまでなんとなく実装していたことの仕組みを学ぶことで、知識として定着することができた</li>
</ul>
</blockquote>
<h3 id="中間報告会">中間報告会</h3>
<p>フェーズ 2:エンジニア基礎研修最後の研修プログラムである中間報告会に向けて、ドキュメンテーションスキル研修、プレゼンテーションスキル研修を実施しました。</p>
<p>上記スキルが必要となる背景は、次の通りです。</p>
<ul>
<li>エンジニアリングを通じた課題解決とはプログラムを書くだけでは解決しない場面もある</li>
<li>背景、目的を正しくステークホルダーへ共有しながらチームとして取り組んでいくことになる</li>
<li>伝えたいことを文章として整理し、他者へ分かりやすく伝えていくことが求められる</li>
</ul>
<p>また、メドレーの行動原則 <a href="https://www.medley.jp/team/culture.html">Our Essentials</a> を構成する要素として、「ドキュメントドリブン」「全てを明確に」という項目が含まれており、これらを実現するためにも、とても重要なスキルとしてメドレーは考えています。</p>
<p>新卒研修が終わった後も、エンジニアとして技術的なスキルを身につける機会は日常的に多くありますが、上記のようなスキルをまとめて習得する機会は少ないため、このような研修を社会人のはじめから受けておくことで、その後の伸びしろが違ってくるのではないかと思います。</p>
<p>研修が終わった後は、各自で報告会用の資料を作成し、研修講師からの添削を受けました。</p>
<p>中間報告会は各部署の開発マネージャーを発表相手として、当日は程よい緊張感をもって、良い雰囲気で報告会を終えられました。</p>
<h2 id="フェーズ-3事業部-ojt">フェーズ 3:事業部 OJT</h2>
<h3 id="事業部研修">事業部研修</h3>
<ul>
<li>取締役豊田からの講義</li>
<li>CLINICS 事業部研修</li>
<li>ジョブメドレー事業部研修</li>
</ul>
<h3 id="開発-ojt">開発 OJT</h3>
<ul>
<li>システム全体像説明</li>
<li>環境構築</li>
<li>各プロダクトの開発チームでの OJT</li>
</ul>
<p>フェーズ 3 では、<strong>顧客の課題と、顧客への価値提供のための各チームの連携を体感し、メドレーの顧客提供価値を自分の言葉で話せるようになること</strong>をゴールとしました。</p>
<p>フェーズ 3 のはじめに、取締役豊田による日本の医療の課題とメドレーの取り組みに関する講義を受講してもらいました。</p>
<p>持続可能な医療体制を構築していくためにメドレーが成すべきことなどの話を聞いた後に、新卒からの質問の受け答えによって理解を深め、メドレーの社会的意義をあらためて認識してもらい、エンジニアとしてだけではなく、<strong>メドレー社員としての自覚</strong>を強めてもらいました。</p>
<h3 id="事業部研修-1">事業部研修</h3>
<p>開発 OJT で手を動かす前に、自分たちが何のために開発するのかを具体的にイメージできるよう、次のように、各現場に参加してもらいました。</p>
<ul>
<li>見込顧客への架電業務見学</li>
<li>商談前の社内ミーティング参加</li>
<li>商談現場同席</li>
<li>定例会議参加</li>
</ul>
<p>事業部のスタッフが、顧客の課題に対して、どのような対応をしていて、どのようにプロダクトを説明しているのか、事業部の各チームが、どのように連携して最終的に顧客に価値を届けるのかの全体観を知ってもらうことを狙いとしていました。</p>
<p>開発チームのエンジニアは業務上、プロダクトのエンドユーザーである顧客の声などを商談のタイミングから聞ける機会はなかなか無いので、研修を通じて話を聞けたことは、今後の開発モチベーションにも影響する良い機会だったと思います。</p>
<h5 id="新卒からの声-4">新卒からの声</h5>
<blockquote>
<ul>
<li>ユーザー、顧客、事業部が抱える課題を確認できたことで、開発以外にも目を向けるきっかけになり良かった</li>
<li>各部署が KPI として定めている数字を知ることができ、開発に降りてきている施策の影響箇所がどの部分かを理解できた上で、開発に取り組むことができるようになった</li>
<li>各部署のミーディングに参加することで、各部署がどのような考えで何を目指しているのかを理解でき、メドレー全体として目指している方向性が掴めた</li>
</ul>
</blockquote>
<h3 id="開発-ojt-1">開発 OJT</h3>
<p>事業部研修に続く開発 OJT では、<a href="https://job-medley.com/">ジョブメドレー</a> 、<a href="https://clinics.medley.life/">CLINICS</a>、<a href="https://www.kaigonohonne.com/">介護のほんね</a>の開発チームに分かれて、研修を実施しました。</p>
<p>OJT 配属先では、メンターとは別に、トレーナーを付けて業務の進め方などをサポートしました。トレーナーは配属先の先輩エンジニアが担当しました。</p>
<p>OJT の流れとしては、初日に、プロダクトがどのように動いているのか、システム全体像を把握することから始まり、各自、ドキュメントに沿って、PC にローカル開発環境をセットアップしました。</p>
<p>その後は、他の先輩エンジニアと同様に、GitHub Issue で管理されている課題を解消することを日々の目標としてこなしてもらいました。ただし、単に Issue に書かれている課題をクリアしようとするのではなく、<strong>そもそも、なぜそれをやるのか、Issue の背景や起票者の意図を十分に理解した上で、プロダクトのあるべき姿に導く</strong>ことを意識してもらいました。</p>
<p>Issue に書かれている内容の理解が不十分だったり、解決方針がうまく定まらない場合は随時、ミーティングの時間を設けて、Issue 起票者やトレーナーと認識合わせを行い、認識の相違から生まれるミスコミュニケーションを極力減らすよう、取り組みました。</p>
<p>技術的な質問に関しても、定期的に質問タイムを設けたり、複雑になりそうな実装や、つまずきポイントとなりそうな箇所に関しては、<strong>画面共有を用いてレビュー</strong>を行い、疑問点に関してもその場で確認して、解消してもらいました。</p>
<p>緊急事態宣言期間中だったため、会社全体で原則、在宅勤務の体制となっており、対面でのコミュニケーションが希薄になりがちでしたが、朝会、夕会を含め、たとえ新卒から質問が無くても質問タイムでのミーティングは定期的に実施するなど、<strong>できるだけ頻繁に顔合わせして、新卒本人の声と顔を確認する</strong>よう心がけました。</p>
<p>Issue へのアサインから始まって、実装 -> レビュー依頼 -> QA -> リリース -> Issue 起票者への報告まで、一連の開発フローを経験してもらい、チーム内での開発業務に慣れてもらうことができました。</p>
<h2 id="フェーズ-4最終報告">フェーズ 4:最終報告</h2>
<p>新卒研修最後のプログラムとして、メドレー役員陣に向けた最終報告会を実施しました。</p>
<p>最終報告会の目的としては、次の通りです。</p>
<ul>
<li>学んだことの知識を深化させる</li>
<li>自らの得手・不得手を捉え、将来の成長計画を立てる</li>
<li>体系的に整理・文書化して他者へ伝えるスキルを向上させる</li>
<li>役員陣に向けてプレゼンすることで、本配属に向けた決意表明として区切りを付ける</li>
</ul>
<p>役員陣への発表であることに加え、一人あたりの発表時間にも制限が設けられており、当日の緊張はかなりのものだったと思います。</p>
<p>前日に発表会場を下見して、リハーサルを入念に行うなど、当日の発表会を成功させるため、メドレーのエンジニアとしての自覚を持って、発表準備に取り組んでいました。</p>
<h1 id="技術志向とプロダクト志向の両輪を目指すエンジニア募集中">技術志向とプロダクト志向の両輪を目指すエンジニア募集中</h1>
<p>メドレーの研修では、技術的な講義や実践だけで終わるのではなく、ビジネスパーソンとして必要な基礎も身につけ、なぜ開発するのかを追究し、プロダクトを通じた課題解決を実体験してもらうことを重視しています。</p>
<p>メドレーでは、医療ヘルスケア分野の課題解決に挑みたいエンジニアを募集しています。</p>
<p>新卒の学生に限らず、中途採用も行っているので、エンジニアの方で少しでも興味を持っていただけたら、是非、面談でお話ししましょう。</p>
<p>最後までお読みいただき、誠にありがとうございました。</p>
<p>P.S.</p>
<p>昨年、一昨年の新卒研修の様子はこちらより、それぞれご覧いただけます。</p>
<ul>
<li><a href="/entry/2020/09/25/174527">2020 年度新卒エンジニア研修について</a></li>
<li><a href="/entry/2019/12/16/165947">2019 年度新卒エンジニア研修について</a></li>
</ul>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 新卒エンジニアが 2 年目でプロジェクトリーダーを経験して得た学びhttps://developer.medley.jp/entry/2021/09/01/180351https://developer.medley.jp/entry/2021/09/01/180351はじめまして。メドレーのエンジニア熊本です。新卒で入社し今年で 3 年目になりまして、2019 年度エンジニア新卒の研修を終えてから早 2 年が経とうとしています。
そんな私ですが去年の 11 月頃から先月までの間、とあるプロジェクトのリー...Wed, 01 Sep 2021 09:03:51 GMT<p>はじめまして。メドレーのエンジニア熊本です。新卒で入社し今年で 3 年目になりまして、<a href="/entry/2019/12/16/165947">2019 年度エンジニア新卒の研修</a>を終えてから早 2 年が経とうとしています。</p>
<p>そんな私ですが去年の 11 月頃から先月までの間、とあるプロジェクトのリーダーを任せてもらっていたので、そのお話をさせていただきます。</p>
<h1 id="はじめに">はじめに</h1>
<p>私は新卒研修を終えてから医療介護求人サイト<a href="https://job-medley.com/">ジョブメドレー</a>のチームで開発をしていましたが、そのジョブメドレーを支える社内管理システムのリニューアルプロジェクトに初期から携わっていました。</p>
<p>こちらのプロジェクトにつきましては、弊社デザイナーの酒井が<a href="/entry/2020/06/19/194558">デザイナーがデザインツールを使わずに、React を使ってデザインした話</a>を、弊社エンジニアの山田が<a href="/entry/2020/11/06/180208">GraphQL, TypeScript, React を用いて型安全に社内システムをリニューアルした話</a>を以前ブログにしていますので、よろしければあわせてご覧ください。</p>
<p>その社内管理システムをどのような流れでリニューアルし、その中で自分の役割がどう変化しどう対応したのかなどについて、次の章からお話ししていきます。</p>
<h1 id="プロジェクトについて">プロジェクトについて</h1>
<p>リニューアルの背景やシステムの概要については上に紹介した記事でも説明しているため割愛しますが、求職者や求人を掲載する顧客に関する業務を行っているシステムをおよそ 1 年半かけて刷新するという大きなプロジェクトでした。</p>
<p>システムの中でも求職者関連を「Phase1」、顧客関連を「Phase2」として分割し、リニューアルを進めました。</p>
<h1 id="プロジェクト内での自分の役割の変遷">プロジェクト内での自分の役割の変遷</h1>
<p>Phase1 の最初期は先輩方がアーキテクチャの設計やスケジューリングをしていました。当時まだ新卒 1 年目で未熟な私でしたが、権限管理のテーブル設計をするタスクをアサインしてもらいました。ここでは詳細を省きますが、初めてのテーブル設計で右も左も分からない状態から責任感を持って何とか形にすることができ、(もちろんリニューアル中に多少の見直しはありましたが)大きな達成感を得たことを覚えています。</p>
<p>各種設計、技術選定、開発の進め方などが大方固まり本格的に開発が始まるわけですが、Phase1 の際は先輩社員がプロジェクトリーダーとして引っ張っていただき、自分は開発メンバーの一員として API の作成などに奮闘していました。</p>
<p><a href="https://graphql.org/">GraphQL</a>といった技術やスケジュールが厳密に引かれたプロジェクトでの開発など初めて経験することも多々ありましたが、先輩方にサポートをいただいたり、同期と切磋琢磨しながら取り組めたおかげで、Phase1 を乗り切ることができました。</p>
<p>さて、ここからが本題になりますが、Phase2 になるとプロジェクトメンバーの入れ替えや私自身の目標設定も重なり、プロジェクトリーダーを任せてもらうことになります。まずはプロジェクトリーダーに任命されてから、どういった仕事をしていたのかご紹介します。</p>
<h1 id="プロジェクトリーダーの仕事">プロジェクトリーダーの仕事</h1>
<p>プロジェクトリーダーとして期待されていたことは以下の通りです。</p>
<ul>
<li>プロジェクト管理</li>
<li>システム設計</li>
<li>開発</li>
<li>チームマネジメント</li>
</ul>
<p>これを更に細分化し、私の実業務と照らし合わせながら並べてみると、多少粒度にばらつきがあるかもしれませんが以下のようなことが挙げられます。</p>
<ol>
<li>要件定義・画面設計(ディレクターとデザイナー主導で進めつつ、エンジニアも実データや既存ロジックを踏まえた観点を持ち合わせて参加しました)</li>
<li>開発方針の検討</li>
<li>開発タスクへの落とし込み</li>
<li>技術調査・選定</li>
<li>API 設計</li>
<li>工数算出・スケジューリング</li>
<li>実装・レビュー</li>
<li>QA(Quality Assurance)テスト</li>
<li>リリースマネジメント</li>
</ol>
<p>Phase2 は段階的にリリースを行ったため、その度に 1 から 9 までを繰り返していたような流れになります。また、上記に加え、定例ミーティングでの報告や開発メンバーのタスクマネジメントも随時行っていました。</p>
<p>もちろん苦労したことは多く、全部を挙げようとするとキリがないのですが、その中でもいくつかに絞った上で紹介したいと思います。</p>
<h1 id="苦労と工夫">苦労と工夫</h1>
<h2 id="1-そもそも何をやればいいのか">1. 「そもそも何をやればいいのか」</h2>
<p>まず最初に苦労したことは「そもそも何をやればいいのかわからない」ということでした。初めから先ほど挙げたような動きをイメージできていたわけではなく、記事や本を読み漁ったり先輩との 1on1 で質問攻めにしたりと基本的な知識を叩き込むわけですが、実際にとった最初の動きとしては「できる部分を見つけてやっていく」ということだったと思います。
自分がリーダーに任命された時点でのプロジェクトの状況としては要件定義や画面設計が進んでいる最中でしたが、これらがまとまるのを待つのではなく「全部決まらないとやれないこと」と「現時点でやれること」を切り分けて動きました。こうしたところから少しずつリズムを作り、最終的に先ほど列挙したような一通りのことがイメージ・実行できるようになったのだと思います。</p>
<h2 id="2-工数見積もり">2. 工数見積もり</h2>
<p>一般的に工数見積もりに関する記事は世の中に多く存在しますが、私の場合は工数見積もりの方法がわからなかったというよりも、「どういう思想で見積もったのか、どういう選択肢があるのか」を曖昧にしていたことが当初の問題でした。</p>
<p>初めて見積もった時は単に開発タスクを積み上げた工数を報告して満足してしまいましたが、様々な方のフィードバックを受けプロダクト価値を高めるためにどういう動きができるのかを考える必要があったことを痛感しました。単純に工数を積み上げる場合や事業的な都合を踏まえてミニマムで開発する場合など、いくつかの選択肢をそれぞれの軸で考える必要があったことを学びました(この時期は夜な夜な夢の中で工数見積もりをしていたのも今ではいい思い出です)。</p>
<h2 id="3-意思決定">3. 意思決定</h2>
<p>これはいつになっても正解が存在する類のものではないのですが、特に意思決定には苦労しました。意思決定といっても開発方針から技術選定まで様々な粒度のものがありますが、特に最初から苦労したのは技術的な決定でした。</p>
<p>それまで先輩に頼ることの多かった私がプロジェクトリーダーになった直後から何もかもできるようになるわけではないことは明々白々ですが、「自分が決めないと」と焦ってしまっていた時期もあったと思います。</p>
<p>そこで一度立ち止まって意識したことは、「何ができて何ができないのかを他者に明示する」ことでした。はっきりと自分に足りていないことを他者に伝えることで、周りもサポートしやすくなると思いますし、自分自身なにがやれることなのか明確になるので単純なことですが効果的であったと思います。他にも開発メンバーの提案で、インセプションデッキを取り入れてみたことも効果的でした。</p>
<p>また、意思決定とは文脈が少し変わってきますが、モブプロやペアプロを実施してチーム力を高め属人化をなくしつつ開発効率を向上させる取り組みも、時間が経てば経つほど効果を実感できて良かったと思います。このようにアジャイル開発の手法からチームにフィットする手法をいくつか取り入れることもできました。</p>
<h1 id="プロジェクトを通して成長したこと">プロジェクトを通して成長したこと</h1>
<p>これまで小出しで色々とお話しさせていただきましたが、自分が特に成長したと感じていることをまとめさせていただきます。</p>
<h2 id="一通りの経験を通して得られたリード力">一通りの経験を通して得られたリード力</h2>
<p>「API 設計だけ」ではなく一通り全てを任せていただいたことはとても大きな経験になりました。初めて個人ではなくチーム・プロジェクト全体として効率が良くなる動きを考える経験もできたと思います。</p>
<h2 id="技術力">技術力</h2>
<p>もちろん実装を通じて得た技術は数えきれないほどありますが、その中でも特に責任を持って他者のコードをレビューしたり、自分が書くコードの影響範囲やスコープを意識し続けたことが大きな糧になっている気がします。</p>
<h2 id="リスク管理力">リスク管理力</h2>
<p>スケジュール遅延のリスク、方向性がずれてしまうリスク、技術的なリスク、様々ありますがこれらのリスクヘッジを考える力がプロジェクトリーダーには必要です。</p>
<p>リスク管理において「先読みが大切」とよく言われますが、私の場合はある先輩社員から「常に 2 週間先を見据えておけ」という具体的な日数のアドバイスをいただきました。具体的にすることであらゆることが想像しやすくなりましたし、それを 1 年以上毎日意識し実行し続けたことが、プロジェクトをやり切ることができた要因にもなっていると思います。もちろんこの言葉は家宝にしようと思っています。</p>
<h2 id="価値に対する視野">価値に対する視野</h2>
<p>何よりも「プロダクトのユーザーに価値を提供すること」の意味を理解しました。ここまでに書いてきたようなスケジュール管理やリスク管理などは、あくまでプロジェクトを遂行する上で必要な仕事の一つでしかないはずです。プロジェクトを通してシステムを使っている社員、更にはその先の顧客・求職者へ如何に価値を提供できるか考えるべきですが、一時期は「どうやるのか・なにをやるのか」というプロジェクト自体を完遂させることしか考えられていない時期もありました。</p>
<p>視野が狭くなっていたことに周りからの指摘で気づくことができ、それ以降は「そもそも本当にこの機能はいるのか」などユーザーの立場からの観点も徐々に身に付けることができました。これがきっかけとなり、周りとも頻繁に「なぜやるのか」を議論できるようになったと思います。新卒 1 年目で口酸っぱく言われていた「目的意識」をようやく腹落ちさせ体現することができました。</p>
<h1 id="最後に">最後に</h1>
<p>最後となりますが、プロジェクトリーダーについて語ってきた私ですが、入社するまでは Web 開発未経験でして、メドレーでの成長を非常に実感しています。そんなメドレーではエンジニア・デザイナーをはじめ多くのポジションで新たなメンバーを募集していますので、少しでもご興味をお持ちいただけた方は、是非お気軽にお話しさせていただければと思います!</p>
<p>ここまでお付き合いいただき、ありがとうございました。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- オンライン診察機能に画面共有を実装した話https://developer.medley.jp/entry/2021/07/30/173508https://developer.medley.jp/entry/2021/07/30/173508事業本部 プロダクト開発室エンジニアの日下です。
オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」の、患者・医療機関に向けたアプリケーションの機能開発、開発基盤、インフラ周りを担当しております。
今回 CLINICS が...Fri, 30 Jul 2021 08:35:08 GMT<p>事業本部 プロダクト開発室エンジニアの日下です。</p>
<p><a href="https://clinics.medley.life/">オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」</a>の、患者・医療機関に向けたアプリケーションの機能開発、開発基盤、インフラ周りを担当しております。</p>
<p>今回 CLINICS が提供するオンライン診療機能に「画面共有機能」を追加しましたので、その背景・技術的な話をまとめます。</p>
<h1 id="画面共有機能実装の背景">画面共有機能実装の背景</h1>
<h2 id="clinics-とオンライン診療">CLINICS とオンライン診療</h2>
<p>普段皆さんが病院にかかるとき、多くの場合は病院に行き、医師の診察を対面で受け、会計をして帰るといった流れになるかと思います。</p>
<p>CLINICS のオンライン診療はこの流れをインターネットを通して提供するサービスです。</p>
<p>※ オンライン診療は、一度、初診等で対面診療を受けた際に医師が可能と判断した場合、次回以降の診察において可能になります。また、現在は新型コロナウイルス感染症対策時限措置として、初診からオンライン診療を受けることが可能となっています。</p>
<p>CLINICS を利用した場合、事前に予約した時間にスマホまたは PC で待機をする、医師の診察をビデオチャットで受け、会計はクレジットカードで行われるという流れとなっています。</p>
<p>クラウド診療支援システムとしての CLINICS は <a href="https://www.medley.jp/release/clinics-6.html">2016 年に「オンライン診療のためのシステム」としてローンチ</a>され、<a href="https://www.medley.jp/release/20180429-01.html">2018 年にはクラウド型電子カルテ機能を</a>、<a href="https://www.medley.jp/release/clinics-cloud.html">2019 年には予約管理システム機能を</a>、<a href="https://www.medley.jp/release/pharms-0903-1.html">2020 年にはかかりつけ薬局支援システム Pharms との連携機能も追加し</a>、患者向けアプリからオンライン服薬指導をシームレスに受けることができるようになりました。</p>
<p>プロダクト開発室ではこれらオンライン診療機能・電子カルテ機能・予約管理機能・連携機能の改善を日々行っています。</p>
<h2 id="画面共有機能の需要の高まり実装の決定">画面共有機能の需要の高まり、実装の決定</h2>
<p>このように CLINICS の改善を日々行なっている中、昨年から始まった<a href="https://medley.life/diseases/5e621d26dd126037cfc60fc9/">新型コロナウイルス感染症(COVID-19)</a>の流行に伴った需要の増加により、オンライン診療の件数が急増しました。</p>
<p>CLINICS も数多くの医療機関にご利用いただく中で、オンライン診療に関わるさまざまなご要望をいただくようになりました。その中でも特に多かったものが、今回紹介する画面共有機能です。</p>
<p>対面での診察の際に医師が検査結果などを患者に見せながら説明するように、オンラインで診察する場合でも資料をリアルタイムで共有しながら説明ができるようになれば、今まで以上にオンラインでも質の高い診察を行えるようになります。</p>
<p>こういったユースケース、要望などを検討した結果、CLINICS を利用するすべての医療機関及び患者にとって大きな恩恵が見込まれたため、オンライン診察(ビデオチャット)中に医師の PC 画面をリアルタイムで患者に共有する機能として実装をすることにしました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210729/20210729115806.png" alt="20210729115806.png">
<h1 id="画面共有機能の実装">画面共有機能の実装</h1>
<p>画面共有をする医師側向けのコードでどういった実装方法があるのか、大まかな流れをまとめます。</p>
<p>※ 以下に記載しているコードは説明のための疑似コードですので、このままでは動作しないことにご注意ください。また、医師側の実装例を掲載しているため、患者側(画面共有を受ける側)の実装は別途必要になります。</p>
<h2 id="オンライン診察開始までの処理">オンライン診察開始までの処理</h2>
<p>オンライン診察を開始するには医師側のマイクとカメラで取得した情報を患者側に送付する必要があります。ここではそこまでの実装の流れを見ていきます。</p>
<h3 id="カメラマイクのストリームの取得">カメラ・マイクのストリームの取得</h3>
<p>オンライン診察開始時点で医師側のマイク・カメラの情報を共有するため、まずはそれらのストリームを取得する必要があります。こういったメディアコンテンツのストリームを司るインターフェイスとして <a href="https://developer.mozilla.org/ja/docs/Web/API/MediaStream">MediaStream</a> が定義されています。</p>
<p>マイク・カメラの MediaStream は、例えば <a href="https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/getUserMedia">MediaDevices.getUserMedia()</a> を利用して取得できます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> userMediaStream</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">MediaStream</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">await</span><span style="color:#9CDCFE"> navigator</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">mediaDevices</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getUserMedia</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> video:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> audio:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span></code></pre>
<h3 id="skyway-経由でオンライン診察を始める">SkyWay 経由でオンライン診察を始める</h3>
<p>WebRTC で P2P のビデオチャットを利用するためには、初期の接続のための処理及び接続の維持などの処理を行う必要があります。弊社ではこのあたりの処理を WebRTC SaaS の <a href="https://webrtc.ecl.ntt.com/">SkyWay</a> 及びその SDK を利用することで簡略化しています。</p>
<p>オンライン診察開始時には、先程取得した医師側のマイク・カメラの MediaStream を SkyWay の SDK に渡すことで、一対一でのリアルタイムビデオチャットを実現できます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#C586C0">import</span><span style="color:#9CDCFE"> Peer</span><span style="color:#D4D4D4">, { </span><span style="color:#9CDCFE">MediaConnection</span><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">from</span><span style="color:#CE9178"> "skyway-js"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> peer</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> Peer</span><span style="color:#D4D4D4">({ </span><span style="color:#9CDCFE">key:</span><span style="color:#CE9178"> "your-api-key"</span><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// 事前に患者と共有しておいた peer id に対して call メソッドと MediaStream を渡すことで診察を開始できる。</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> mediaConnection</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">MediaConnection</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">peer</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">call</span><span style="color:#D4D4D4">(</span></span>
<span class="line"><span style="color:#CE9178"> "shared-peer-id"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> userMediaStream</span></span>
<span class="line"><span style="color:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// 注: 患者側は送付された処理をハンドリングする機能を実装する必要がある</span></span></code></pre>
<p>ここまでがオンライン診察を開始するまでの処理です。</p>
<p>※ 詳細は SkyWay 公式の<a href="https://webrtc.ecl.ntt.com/documents/javascript-sdk.html#%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB">チュートリアル</a>などを参照ください。</p>
<h2 id="画面共有の処理">画面共有の処理</h2>
<p>ここまでで患者に対して医師側のカメラ・マイクで取得された映像・音声が表示されている状態のため、これを切り替える処理が必要になります。今回は現在接続に利用している MediaStream を、画面共有用の MediaStream に入れ替えることで実現しました。</p>
<h3 id="画面の-mediastream-の取得">画面の MediaStream の取得</h3>
<p>まずは共有する画面の MediaStream を取得する必要があります。これは <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia">MediaDevices.getDisplayMedia()</a> を使うことで実現できます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> displayStream</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">MediaStream</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">await</span><span style="color:#9CDCFE"> navigator</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">mediaDevices</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getDisplayMedia</span><span style="color:#D4D4D4">(</span></span>
<span class="line"><span style="color:#D4D4D4"> { </span><span style="color:#9CDCFE">video:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">);</span></span></code></pre>
<h3 id="画面共有用の-mediastream-を作る">画面共有用の MediaStream を作る</h3>
<p><code>getDisplayMedia()</code> から共有する画面の MediaStream を取得できるものの、そのまま利用するとマイクの音声が入りません。</p>
<p>これは <code>getDisplayMedia()</code> から取れる MediaStream にマイクの音声が含まれていないことが原因なので、必要な画像・音声の組み合わせを持った画面共有用の MediaStream を作成することで対処ができます。</p>
<h4 id="mediastreamtrack-を組みあわせて画面共有用の-mediastream-を作る">MediaStreamTrack を組みあわせて画面共有用の MediaStream を作る</h4>
<p>画面共有用の MediaStream を作成する前にまず、MediaStreamTrack と MediaStream の関係を理解する必要があります。</p>
<p><a href="https://developer.mozilla.org/ja/docs/Web/API/MediaStreamTrack">MediaStreamTrack</a> はストリームに含まれる一つのメディアトラックを表現するものです。
<a href="https://developer.mozilla.org/ja/docs/Web/API/MediaStreamTrack/kind">kind</a> という読み取り専用プロパティがあり、オーディオトラックであれば <code>"audio"</code> が、ビデオトラックであれば <code>"video"</code> が設定されています。</p>
<p>また、 MediaStream は複数の MediaStreamTrack から成り、オーディオトラック・ビデオトラックを取り出すメソッドがそれぞれ <a href="https://developer.mozilla.org/ja/docs/Web/API/MediaStream/getAudioTracks">MediaStream.getAudioTracks()</a>・<a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaStream/getVideoTracks">MediaStream.getVideoTracks()</a> として実装されています。</p>
<p>これらを組み合わせることで、マイクと画面の MediaStreamTrack を持つ MediaStream を作ることができ、これを SkyWay の SDK に渡すことで、画面共有を実現できます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#D4D4D4"> [</span><span style="color:#4FC1FF">displayVideoTrack</span><span style="color:#D4D4D4">]: </span><span style="color:#4EC9B0">MediaStreamTrack</span><span style="color:#D4D4D4">[] = </span><span style="color:#9CDCFE">displayStream</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getVideoTracks</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#6A9955">// 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#D4D4D4"> [</span><span style="color:#4FC1FF">userAudioTrack</span><span style="color:#D4D4D4">]: </span><span style="color:#4EC9B0">MediaStreamTrack</span><span style="color:#D4D4D4">[] = </span><span style="color:#9CDCFE">userMediaStream</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getAudioTracks</span><span style="color:#D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る)</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> sharingMediaStream</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">MediaStream</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> MediaStream</span><span style="color:#D4D4D4">([</span></span>
<span class="line"><span style="color:#9CDCFE"> displayVideoTrack</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> userAudioTrack</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">]);</span></span></code></pre>
<h3 id="mediastream-の入れ替え">MediaStream の入れ替え</h3>
<p>最後に画面共有状態への切り替えです。マイク・カメラが共有されている状態からの切り替えにはいくつかの方法が考えられます。</p>
<p>例えば、多重化であれば MediaConnection( Skyway の SDK の単位で、「接続先 Peer へのメディアチャネル接続」を管理する)の多重化、MediaStream の多重化、MediaStreamTrack の多重化がそれぞれ考えられます。これらの方法はマイク・カメラの切り替え時のチラつき抑制など実装上の選択肢が増えるメリットがある一方で、通信量が多くなってしまう点がデメリットと言えます。</p>
<p>今回は多重化をせずに既存の MediaStream を切り替える実装を紹介します。この方法のメリットは、多重化に比べると通信量が少なく、またすでに MediaStream が一つである前提で作られている場合は、画面共有を受ける側の実装の変更が不要という点です。</p>
<p>この方法は、 SkyWay の SDK であれば MediaConnection の <a href="https://webrtc.ecl.ntt.com/api-reference/javascript.html#methods-2">replaceStream というメソッド</a>に対して新しい MediaStream を渡すことで実現ができます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A9955">// 画面共有用の MediaStream を渡すことで、画面共有を開始する</span></span>
<span class="line"><span style="color:#6A9955">// MediaConnection は先程 `peer.call` した際の返り値として取れているため、それを利用する</span></span>
<span class="line"><span style="color:#9CDCFE">mediaConnection</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">replaceStream</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">sharingMediaStream</span><span style="color:#D4D4D4">);</span></span></code></pre>
<p>実装前に懸念していたマイク・カメラの切り替え時のチラつきなども気になるほどはなく、実用に足るような品質を保つことができることを確認しています。</p>
<h2 id="実装の全体概要">実装の全体概要</h2>
<p>以上の流れを実装すると、次のようなコードになります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#C586C0">import</span><span style="color:#9CDCFE"> Peer</span><span style="color:#D4D4D4">, { </span><span style="color:#9CDCFE">MediaConnection</span><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">from</span><span style="color:#CE9178"> "skyway-js"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">/** 医師側のマイク・カメラを共有してオンライン診察開始するところまで **/</span></span>
<span class="line"><span style="color:#6A9955">// getUserMedia()でカメラ・マイクのストリームを取得</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> userMediaStream</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">MediaStream</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">await</span><span style="color:#9CDCFE"> navigator</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">mediaDevices</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getUserMedia</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> video:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> audio:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// Skyway sdk の初期化処理</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> peer</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> Peer</span><span style="color:#D4D4D4">({ </span><span style="color:#9CDCFE">key:</span><span style="color:#CE9178"> "your-api-key"</span><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// オンライン診察の開始</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> mediaConnection</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">MediaConnection</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">peer</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">call</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"peerId"</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">userMediaStream</span><span style="color:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">/** 画面共有を開始する処理 **/</span></span>
<span class="line"><span style="color:#6A9955">// 画面共有する画面の stream を取る</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> displayStream</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">MediaStream</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">await</span><span style="color:#9CDCFE"> navigator</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">mediaDevices</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getDisplayMedia</span><span style="color:#D4D4D4">(</span></span>
<span class="line"><span style="color:#D4D4D4"> { </span><span style="color:#9CDCFE">video:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#D4D4D4"> [</span><span style="color:#4FC1FF">displayVideoTrack</span><span style="color:#D4D4D4">]: </span><span style="color:#4EC9B0">MediaStreamTrack</span><span style="color:#D4D4D4">[] = </span><span style="color:#9CDCFE">displayStream</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getVideoTracks</span><span style="color:#D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#D4D4D4"> [</span><span style="color:#4FC1FF">userAudioTrack</span><span style="color:#D4D4D4">]: </span><span style="color:#4EC9B0">MediaStreamTrack</span><span style="color:#D4D4D4">[] = </span><span style="color:#9CDCFE">userMediaStream</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getAudioTracks</span><span style="color:#D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る)</span></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> sharingStream</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">MediaStream</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> MediaStream</span><span style="color:#D4D4D4">([</span></span>
<span class="line"><span style="color:#9CDCFE"> displayVideoTrack</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> userAudioTrack</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">]);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// 画面共有用の MediaStream を渡すことで、画面共有を開始する</span></span>
<span class="line"><span style="color:#9CDCFE">mediaConnection</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">replaceStream</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">sharingStream</span><span style="color:#D4D4D4">);</span></span></code></pre>
<h2 id="開発中に遭遇した問題への対応">開発中に遭遇した問題への対応</h2>
<h3 id="スリープモード共有を停止ボタンを押したときの対応">スリープモード・共有を停止ボタンを押したときの対応</h3>
<p><a href="https://www.google.com/intl/ja_jp/chrome/">Google Chrome</a> で画面共有の際に表示される「共有を停止」ボタンを押下したり、PC をスリープモードにすると、画面の MediaStreamTrack が途切れてしまいます。</p>
<p>これは該当の MediaStreamTrack に <code>"ended"</code> のイベントリスナを登録しておくことでハンドリングできます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#9CDCFE">displayVideoTrack</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">addEventListener</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"ended"</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">handleEndedEvent</span><span style="color:#D4D4D4">, { </span><span style="color:#9CDCFE">once:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4"> });</span></span></code></pre>
<h3 id="typescript-の型の対応">TypeScript の型の対応</h3>
<p>現状 TypeScript の型が <code>getDisplayMedia()</code> に対応していなかったため、今回は実装の参考にしている <a href="https://github.com/skyway/skyway-conf/blob/5c5531b79d0cd9407c071911498dedd695414039/src/conf/utils/types.ts#L98-L102">skyway-conf で利用されている型</a>を流用する形で対応をしました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#569CD6">declare</span><span style="color:#9CDCFE"> global</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> interface</span><span style="color:#4EC9B0"> MediaDevices</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#DCDCAA"> getDisplayMedia</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">constraints</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">MediaStreamConstraints</span><span style="color:#D4D4D4">): </span><span style="color:#4EC9B0">Promise</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">MediaStream</span><span style="color:#D4D4D4">>;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>これは根本的には TypeScript の dom.d.ts に型定義が入っていないことが起因していますが、<a href="https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029">TypeScript4.4 で対応がされるようです</a>。</p>
<h1 id="まとめ">まとめ</h1>
<p>昨今の状況により、オンライン診察のニーズが高まり、画面共有機能の重要性が高まりました。
診察中の画面共有機能は以下の api を組み合わせることで実現することができます。</p>
<ul>
<li>PC 画面の MediaStream は <code>getDisplayMedia()</code> を使うことで取得</li>
<li>MediaStream に含める音声・画像ストリームを変更したい場合は MediaStreamTrack の組み合わせを変えることで作成</li>
<li>接続中の MediaStream の変更は SkyWay の SDK の <code>MediaConnection.replaceStream()</code> を使う</li>
</ul>
<h1 id="最後に">最後に</h1>
<p>CLINICS では本稿で紹介した画面共有などの新規機能の導入や日々の改善を通じて、医療機関・患者双方に支持されるプロダクトを目指し開発を行っています。興味を持たれたエンジニアの方がいらっしゃいましたらぜひ<a href="https://www.medley.jp/jobs/engineer1.html">こちら</a>にご連絡いただければと思います。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 「ChatOps 稟議」×「電子契約締結の API 連携」でワークフローの生産性を追求した話https://developer.medley.jp/entry/2021/07/16/174740https://developer.medley.jp/entry/2021/07/16/174740はじめに
はじめまして、コーポレートエンジニアの山下です。
2020 年にSlackを活用した ChatOps 稟議ワークフローを内製で開発したのですが、さらに、2021 年 4 月にこの Slack 稟議と電子契約システムであるクラウドサ...Fri, 16 Jul 2021 08:47:40 GMT<h1 id="はじめに">はじめに</h1>
<p>はじめまして、コーポレートエンジニアの山下です。</p>
<p>2020 年に<a href="https://slack.com/">Slack</a>を活用した ChatOps 稟議ワークフローを内製で開発したのですが、さらに、2021 年 4 月にこの Slack 稟議と電子契約システムである<a href="https://www.cloudsign.jp/">クラウドサイン</a>を連携させて、電子契約をもっと便利に使い、生産性の向上を実現しましたのでお話しいたします。</p>
<p>まず、当社の稟議システムは<a href="/entry/2020/12/25/180058">2020 年 12 月の当社の記事</a>のおさらいになりますが、稟議の作業が Slack 上で完結する、<strong>ChatOps による稟議ワークフロー</strong>となっております。本稿については 2021 年 7 月に執筆しておりますので丁度導入から 1 年程経過し、その間大きなトラブルも無く、今も当社の極めて迅速な意思決定の一助になっています。ChatOps による稟議ワークフローについては、直近、2021 年 6 月に LayerX 社が<a href="https://layerx.co.jp/news/pr210603">LayerX ワークフローの新機能として発表</a>し、サービスとしても提供され、<a href="https://www.nikkei.com/article/DGXZQOUC031TZ0T00C21A6000000/">日経新聞</a>でも取り上げられていることから、今現在のパラダイムとして、先進的で有効な一手法であったと再認識しております。</p>
<p>今回、新型コロナウイルス感染拡大防止に伴うリモートワークの加速という状況もあり、当社で 2021 年 4 月に電子契約システムとしてクラウドサインを導入しました。電子契約に限らず、契約押印作業は稟議の後続作業に当たるため、ただ導入して使用するのみならず、クラウドサインの API を利用して稟議上にあるデータを電子契約に送信させることでシームレスな連携を実現しています。本稿では当社が行ったシームレスな連携手法について詳細をご説明いたします。</p>
<h1 id="teamspirit-とクラウドサインの-api-連携について">TeamSpirit とクラウドサインの API 連携について</h1>
<h2 id="実装概要">実装概要</h2>
<p>弊社の稟議システムである<a href="https://www.teamspirit.com/">TeamSpirit</a>とクラウドサインとの連携についてお話しします。まず、本稿の開発部分とシステム構成は下記になっております。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210715/20210715210552.png" alt="20210715210552.png">
<p>処理内容の詳細は後ほど述べますが、概要としては TeamSprit(Apex)からクラウドサインの API をコールし、クラウドサイン上で作成した契約文書へ稟議に記載されている契約書ファイルや先方担当者等の情報を連携する仕組みとなっております。これにより契約担当者はクラウドサインにログイン後、下記の 3 ステップで先方に送信できるようになっています。</p>
<ol>
<li>記載内容の確認</li>
<li>押印・署名箇所の設定</li>
<li>先方への送信</li>
</ol>
<p>クラウドサインを使用して契約文書を一から作成する場合のユーザ作業と、当社で採用した API 連携行った場合のユーザ作業を比較したものが下記の表です。作業が半分程度削減されたことが分かります。</p>
<table><thead><tr><th>作業項番</th><th>一から作成する場合</th><th>API 連携を利用した当社の場合</th></tr></thead><tbody><tr><td>1</td><td>ログイン</td><td>ログイン</td></tr><tr><td>2</td><td>契約文書の作成(件名、契約文書としての宛名設定等)</td><td>なし</td></tr><tr><td>3</td><td>契約書ファイルのアップロード</td><td>なし</td></tr><tr><td>4</td><td>先方の送信先設定</td><td>なし</td></tr><tr><td>5</td><td>押印欄の設定</td><td>押印欄の設定</td></tr><tr><td>6</td><td>先方への送信</td><td>先方への送信</td></tr></tbody></table>
<h1 id="実装">実装</h1>
<p>今回の開発で使用した<a href="https://app.swaggerhub.com/apis/CloudSign/cloudsign-web_api/">クラウドサイン API</a>は下記の 5 つの API を使用しました
(※以降、クラウドサイン API に倣い、変数を表現する場合は<code>{}</code>で括ります)。</p>
<table><thead><tr><th>API 種類</th><th>使用用途</th></tr></thead><tbody><tr><td>post /token</td><td>アクセストークンの取得</td></tr><tr><td>post /document</td><td>契約文書の作成</td></tr><tr><td>put /documents/{documentID}/attribute</td><td>契約文書の作成で設定できない、細かい項目の設定</td></tr><tr><td>post /documents/{documentID}/files</td><td>ファイルのアップロード</td></tr><tr><td>post /documents/{documentID}/participants</td><td>先方の送信先設定</td></tr></tbody></table>
<p>全体像で記載したクラウドサインの連携部について、上記の API を織り交ぜて詳細化すると下図のようになります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210715/20210715210621.png" alt="20210715210621.png">
<p>実装方法としてはクラウドサイン API のリファレンスを参照し、テスト実行時に出力される<code>curl</code>コマンドを参考に同様のレスポンスを得るように Apex で HTTP リクエストを実装しました。アクセストークンの取得を例にとると下記のようになります。</p>
<h4 id="api-リファレンスでの-curl-コマンド例">API リファレンスでの curl コマンド例</h4>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">curl</span><span style="color:#569CD6"> -X</span><span style="color:#CE9178"> 'POST'</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#CE9178">'https://api.cloudsign.jp/token'</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4">-H </span><span style="color:#CE9178">'accept: application/json'</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4">-H </span><span style="color:#CE9178">'Content-Type: application/x-www-form-urlencoded'</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4">-d </span><span style="color:#CE9178">'client_id=xxxxxxyyyyyyzzzzzz'</span></span></code></pre>
<h4 id="apex-でのリクエスト実装例">Apex でのリクエスト実装例</h4>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="java"><code><span class="line"><span style="color:#4EC9B0">HttpRequest</span><span style="color:#9CDCFE"> req</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">new</span><span style="color:#DCDCAA"> HttpRequest</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#9CDCFE">req</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setMethod</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'POST'</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#9CDCFE">req</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setEndpoint</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'https://api.cloudsign.jp/token'</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#9CDCFE">req</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setHeader</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'accept'</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">'application/json'</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#9CDCFE">req</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setHeader</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'Content-Type'</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">'application/x-www-form-urlencoded'</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#9CDCFE">req</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setBody</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'client_id='</span><span style="color:#D4D4D4"> + </span><span style="color:#CE9178">'xxxxxxyyyyyyzzzzzz'</span><span style="color:#D4D4D4">);</span></span></code></pre>
<p>私自身、Apex もクラウドサイン API もこの案件を担当するまで触ったことがありませんでしたが、リクエストの試行から実装まで 2 週間かからない程度で実装することができました。</p>
<p>ただし、実装や運用にあたっては下記 2 点について注意が必要になります。</p>
<ol>
<li>Apex からクラウドサインへのファイルのアップロードは単純ではない</li>
<li>アクセストークンの有効期限はクラウドサインでコントロールされる</li>
</ol>
<h2 id="1-apex-からクラウドサインへのファイルのアップロードは単純ではない">1. Apex からクラウドサインへのファイルのアップロードは単純ではない</h2>
<p>ファイルのアップロードについては今回使用した API の中で、唯一、テスト実行の<code>curl</code>と Apex のリクエスト実装で差分が生まれます。まず、その差分を確認するために curl コマンド例と Apex のリクエスト実装例で header、body にセットしている値を比較してみます。</p>
<h4 id="api-リファレンスでの-curl-コマンド例-1">API リファレンスでの curl コマンド例</h4>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">curl</span><span style="color:#569CD6"> -X</span><span style="color:#CE9178"> 'POST'</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#CE9178">'https://api.cloudsign.jp/documents/{document_id}/files'</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4">-H </span><span style="color:#CE9178">'accept: application/json'</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4">-H </span><span style="color:#CE9178">'Authorization: AAAAAABBBBBBCCCCCC'</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4">-H </span><span style="color:#CE9178">'Content-Type: multipart/form-data'</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4">-F </span><span style="color:#CE9178">'name=テスト'</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4">-F </span><span style="color:#CE9178">'uploadfile=@テスト.pdf;type=application/pdf'</span></span></code></pre>
<h4 id="apex-でのリクエスト実装例-1">Apex でのリクエスト実装例</h4>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="java"><code><span class="line"><span style="color:#4EC9B0">HttpRequest</span><span style="color:#9CDCFE"> req</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">new</span><span style="color:#DCDCAA"> HttpRequest</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#9CDCFE">req</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setMethod</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'POST'</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#9CDCFE"> req</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setEndpoint</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'https://api.cloudsign.jp/documents/{document_id}/files'</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#9CDCFE">req</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setHeader</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'accept'</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">'application/json'</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#9CDCFE">req</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setHeader</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'Authorization'</span><span style="color:#D4D4D4">, ‘AAAAAABBBBBBCCCCCC’);</span></span>
<span class="line"><span style="color:#9CDCFE">req</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setHeader</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'Content-Type'</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">'multipart/form-data; boundary={boundary}'</span><span style="color:#D4D4D4">); </span><span style="color:#6A9955">// ※1</span></span>
<span class="line"><span style="color:#9CDCFE">req</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setBodyAsBlob</span><span style="color:#D4D4D4">({multipartBody}); </span><span style="color:#6A9955">// ※2</span></span></code></pre>
<p>主な違いは <code>※1</code>, <code>※2</code> とコメントした部分になります。</p>
<p>Apex では HTTP リクエストの値を手で書いていくことになるので、テスト実行例のように<code>curl</code>がよしなに処理している部分(-F オプションの部分や Apex で記載している boundary)も実装しなければなりません。これが単純に実装できない理由になります。</p>
<p>boundary については multipart/form-data を送信する際に必要な境界でヘッダーでどの文字列が境界であるかを設定します。<code>curl</code>の-F オプションで定義していた文字列とファイル指定部分は、Apex でファイル(バイナリ)を扱うため、その body に含まれる文字列も含めて Blob 型で扱う必要があります(<code>Content-Transfer-Encoding: base64</code>に API 提供側が対応している場合は例外になります)。そのため、文字列とバイナリデータを結合し一つの Blob にする方法は下記になります。</p>
<ol>
<li>「バイナリデータ」、「body の開始からバイナリデータまでの文字列」、「バイナリデータ以降から終端までの文字列」の 3 グループに分ける。</li>
<li>3 グループをそれぞれ<a href="https://ja.wikipedia.org/wiki/Base64">Base64</a>で符号化する。</li>
<li>符号化した「バイナリデータ」と「body の開始からバイナリデータまでの文字列」について、Base64 のデータパディングを示す”=”が含まれないように改行コードで調整する。</li>
<li>「body の開始からバイナリデータまでの文字列」、「バイナリデータ」、「バイナリデータ以降から終端までの文字列」の順で結合する。</li>
<li>結合した Base64 のデータを復号して、一つの Blob とする。</li>
</ol>
<h2 id="2-アクセストークンの有効期限はクラウドサインでコントロールされる">2. アクセストークンの有効期限はクラウドサインでコントロールされる</h2>
<p>アクセストークンやその有効期限は token API を発行した際のレスポンスとしてクラウドサインから発行されます。</p>
<h4 id="発行されたレスポンス例">発行されたレスポンス例</h4>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#9CDCFE"> "access_token"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"AAAAAABBBBBBCCCCCC"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "token_type"</span><span style="color:#D4D4D4">: </span><span style="color:#F44747">“xxxx”</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "expires_in"</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">3600</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>このレスポンスの内、expires_in の値がトークンの有効期限になります。掲題の通り、有効期限の管理はクラウドサイン側で行われ、有効期限内に再度トークンのリクエストを行った場合、経過した時間だけ expires_in の値が小さくなった結果が返ってきて、access_token などは同じ値が取得されます。有効期限内に token API を再度実行した結果が下記になります。</p>
<h4 id="有効期限切れ前に-token-api-を発行した際のレスポンス例">有効期限切れ前に token API を発行した際のレスポンス例</h4>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#9CDCFE"> "access_token"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"AAAAAABBBBBBCCCCCC"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "token_type"</span><span style="color:#D4D4D4">: </span><span style="color:#F44747">“xxxx”</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "expires_in"</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">762</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>一方、有効期限後にトークンのリクエストを実行すると、それまでと異なるアクセストークンを取得し、新しい有効期限が設定されます。</p>
<h4 id="有効期限切れ後に-token-api-を発行した際のレスポンス例">有効期限切れ後に token API を発行した際のレスポンス例</h4>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#9CDCFE"> "access_token"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"XXXXXXYYYYYYZZZZZZ"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "token_type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"xxxx"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "expires_in"</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">3600</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>そのため、API 連携が一度動いた後、有効期限ぎりぎりでもう一度 API 連携が動いてしまった場合、タイミングが悪いと契約文書の作成から最終処理である先方の送信先設定までのプロセス内のどこかから、トークンの有効期限切れが発生する可能性が想定されます。実際に期限切れが発生した場合、発生時以降に発行したその回の API 連携処理が失敗します。</p>
<p>トークンの有効期限切れが発生した際、API リファレンスより HTTP ステータスコードが 401 かつエラー内容が”unauthorized”で応答されることから、当社ではこのエラーを受けた場合にトークンを再取得して処理をリトライするように実装しました。</p>
<p>押印文書作成を例にとると下記のような実装イメージになります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="java"><code><span class="line"><span style="color:#6A9955">//クラウドサイン上に押印文書を作成し、作成した文書 ID を取得する</span></span>
<span class="line"><span style="color:#569CD6">public</span><span style="color:#4EC9B0"> String</span><span style="color:#DCDCAA"> getDocumentId</span><span style="color:#D4D4D4">(</span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4"> authToken, </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4"> title, </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4"> message){</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ・・・中略・・・</span></span>
<span class="line"></span>
<span class="line"><span style="color:#4EC9B0"> HTTPResponse</span><span style="color:#9CDCFE"> res</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">http</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">send</span><span style="color:#D4D4D4">(req);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">res</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getStatusCode</span><span style="color:#D4D4D4">() == </span><span style="color:#B5CEA8">200</span><span style="color:#D4D4D4">){</span></span>
<span class="line"><span style="color:#D4D4D4"> ・・・正常に終了した際の処理・・・</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> //タイミングが悪く token がタイムアウトした場合、トークンを取得し直して、リトライする</span></span>
<span class="line"><span style="color:#C586C0"> else</span><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">res</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getStatusCode</span><span style="color:#D4D4D4">() == </span><span style="color:#B5CEA8">401</span><span style="color:#D4D4D4">){</span></span>
<span class="line"><span style="color:#6A9955"> //レスポンスの内容を確認するため、エラーレスポンスの中身を取得する</span></span>
<span class="line"><span style="color:#4EC9B0"> Map</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">, </span><span style="color:#4EC9B0">Object</span><span style="color:#D4D4D4">> </span><span style="color:#9CDCFE">responseBody</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">new</span><span style="color:#4EC9B0"> Map</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">, </span><span style="color:#4EC9B0">Object</span><span style="color:#D4D4D4">>();</span></span>
<span class="line"><span style="color:#D4D4D4"> responseBody = (</span><span style="color:#4EC9B0">Map</span><span style="color:#D4D4D4"><String, Object>)</span><span style="color:#9CDCFE">JSON</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">deserializeUntyped</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">res</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getBody</span><span style="color:#D4D4D4">());</span></span>
<span class="line"><span style="color:#4EC9B0"> String</span><span style="color:#9CDCFE"> errorVal</span><span style="color:#D4D4D4"> = (String)</span><span style="color:#9CDCFE">responseBody</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">get</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'error'</span><span style="color:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> //リファレンス上、アクセストークンが無効(有効期限切れ)の場合、'unauthorized’となる</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">errorVal</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">equals</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'unauthorized'</span><span style="color:#D4D4D4">)){</span></span>
<span class="line"><span style="color:#6A9955"> //クラウドサインのアクセストークンの再取得</span></span>
<span class="line"><span style="color:#D4D4D4"> authToken = </span><span style="color:#DCDCAA">getAuthToken</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#6A9955"> //単純再帰で再実行する。</span></span>
<span class="line"><span style="color:#D4D4D4"> documentId = </span><span style="color:#DCDCAA">getDocumentId</span><span style="color:#D4D4D4">(authToken, title, message);</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> ・・・中略・・・</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> ・・・以下省略・・・</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<h1 id="実装を終えて">実装を終えて</h1>
<p>上記を実装した結果、稟議と入力内容が同じ、または、稟議から生成できる内容は全てシステム連携で自動生成するため、押印担当は稟議とクラウドサインの画面を並べて転記するような煩雑な作業を必要としない環境になりました。また、契約書の製本、郵送等の紙媒体であるが故の事務の削減ができるようになる等の、電子契約を導入することのそもそものメリットも併せて享受しています。</p>
<p>当社では 2021 年 4 月後半からクラウドサインを導入しましたが、2021 年 6 月時点ではすでに<strong>月間で締結した契約書の「3 割以上」が電子契約を活用しており</strong>、押印担当の展望として今後も利用を拡大していく予定です。</p>
<h1 id="コーポレートエンジニア募集中">コーポレートエンジニア募集中</h1>
<p>メドレーのコーポレート部門では、本稿のように、SaaS の導入ひとつとっても検討を尽くし、既存のシステムと有機的に結合させることで「徹底的に合理性を追求した組織基盤や、仕掛けづくり」を行っています。</p>
<p>面白そう!興味がある!と感じた方は、ぜひ当社採用ページからご応募お願いします!</p>
<p>最後までお読みいただきありがとうございました。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- Pharms のブランドルーツを辿るhttps://developer.medley.jp/entry/2021/06/25/180108https://developer.medley.jp/entry/2021/06/25/180108「2020 年 9 月に調剤薬局向けのプロダクトをリリースする」
このプレスリリースが発表されたのは、COVID-19 の感染拡大に端を発する緊急事態宣言が発令されていた 2020 年 4 月半ば。その 1 ヶ月後、私はリモートワーク下での...Fri, 25 Jun 2021 09:01:08 GMT<p>「2020 年 9 月に調剤薬局向けのプロダクトをリリースする」</p>
<p>この<a href="https://www.medley.jp/release/20209-9clinics.html">プレスリリース</a>が発表されたのは、COVID-19 の感染拡大に端を発する緊急事態宣言が発令されていた 2020 年 4 月半ば。その 1 ヶ月後、私はリモートワーク下でのオンライン MTG になじめない状態のまま、調剤薬局向けプロダクトのブランディングについて役員陣や主要プロジェクトメンバーにプレゼンを行っていた。</p>
<p>今回は当時のプレゼン資料をたどりながら<a href="https://pharms-cloud.com/">Pharms</a>のブランド設計について説明していこうと思う。</p>
<h1 id="医薬分業のルーツとは">医薬分業のルーツとは</h1>
<p>デザイナーとして前田がメドレーに入社してから、オンライン診療や電子カルテなど、主に医療機関向けのプロダクトデザインを担当していたものの、調剤薬局のプロダクトデザインは未知の領域。ブランディングを検討する上で、薬の処方を行う医師と調剤を実施する薬剤師が分担して行う医薬分業のルーツついて調べることからはじめた。</p>
<p>医薬分業は、毒殺を恐れたフリードリヒ 2 世が主治医の処方した薬を、毒が盛られてないか他者にチェックさせたのが始まりとされている。</p>
<p>(参考:<a href="https://www.nichiyaku.or.jp/activities/division/about.html">公益社団法人 日本薬剤師会 HP |医薬分業とは</a>)</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210625/20210625142428.png" alt="20210625142428.png">
<h1 id="医療プラットフォームの未来を見据えたブランド定義">医療プラットフォームの未来を見据えたブランド定義</h1>
<p>次に、調剤に関する法制度や競合などの外部要因、メドレーとしてのブランド力や開発力などの内部要因について簡易な SWOT 分析を行い、調剤薬局のプロジェクトの妥当性を検証。メドレーが取り組む医療プラットフォーム事業(※)に、あらたに調剤薬局プロジェクトが加わることによる他のプロダクトとのバランスも考慮しながらブランドネーミング検討を行っていった。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210625/20210625142452.png" alt="20210625142452.png">
<p><em>※) 医療プラットフォーム事業では、患者と医療領域の業務システムを SaaS プロダクトでつなぎ、患者と医療機関双方にとって、テクノロジーの恩恵を受けることのできるプラットフォームづくりを行っている。主要サービスはクラウド診療支援システム「CLINICS」やオンライン診療・服薬指導アプリ「CLINICS」。</em></p>
<p>メドレーのこれまでの歴史を振り返ると、プロダクト内容を明確かつ端的に表したネーミングが多く、メドレーらしさ = 「中央突破なネーミング」ということを定義し、ネーミングを検討していった。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210625/20210625142510.png" alt="20210625142510.png">
<p>最終的に調剤薬局を表す「Pharmacies(ファーマシーズ)」と「Pharms(ファームス)」の 2 案に絞込み、それぞれのメリット・デメリットを整理していった。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210625/20210625142528.png" alt="20210625142528.png">
<p>当時、社内では調剤薬局システム = Pharmacies と呼ばれており、その中央突破なネーミングが最有力候補であった。一方で、ブランドで体現すべきアイデンティティの欠如や、呼びづらさなどが課題として散見された。さらには医療プラットフォーム全体を見据えたブランド構築という観点から考慮すると、バランス面での課題が浮き彫りになり、それら課題をクリアにして誕生したのが Pharms(ファームス)である。</p>
<h1 id="ヴィジュアルアイデンティティの設計">ヴィジュアル・アイデンティティの設計</h1>
<p>ブランド名が固まれば、あとはヴィジュアル・アイデンティティを突き詰めていくのみ。視認性や可読性を考慮したフォントフェイスの検証や調剤薬局と想起させるブランドカラーの選定、またシンボルの設計などに取り掛かっていく。</p>
<p>ブランドカラーの選定においては、なんとなく「緑」というイメージがチーム内でもあったが、より精緻化するため、薬の起源や調剤薬局本来の役割を踏まえ詳細に落とし込んでいった。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210625/20210625142546.png" alt="20210625142546.png">
<p>ロゴにシンボルを含めるか、含めないかも検討のひとつであったが、医療プラットフォーム事業にある CLINICS のロゴがシンボルマーク付きであるため、医療プラットフォームに関連するプロダクト = シンボルを定義するというルールを策定しシンボルも設計。シンボルは薬の構造式に利用される「ハニカム構造」をモチーフとし、ブランドカラーと合わせて詳細に作り込んでいった。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210625/20210625142603.png" alt="20210625142603.png">
<p>また、Pharms の製品紹介用ランディングページやプロダクトデザインのモックアップを作成し、ロゴとのバランスなども考慮しながら調整を行っていった。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210625/20210625142619.png" alt="20210625142619.png">
<p>最終的に、患者・調剤薬局・医療機関の 3 者のつながりを三角形で表現しつつ、中心を先述した「ハニカム構造」をモチーフとした形状と、Pharms の頭文字「P」をカプセルと錠剤で表現して、調剤薬局システムとしてシンボルマークに命を吹き込んだ。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210625/20210625142638.png" alt="20210625142638.png">
<h1 id="まとめ">まとめ</h1>
<p>このような過程を経て、Pharms のブランドが完成したのだが、これらは調剤薬局システムの開発としては氷山の一角でしかない。本丸はプロダクトデザイン。一般的にはロゴとプロダクトデザインは別プロジェクトで進行したり、担当するデザイナーが別だったりすることも多いのではないだろうか。</p>
<p>Pharms はブランド設計、プロダクトデザイン、マーケティング資材といったデザイン領域をすべて私が担当していくことになるのだが、それ故に事業全体を俯瞰し理解しながらデザインや UI デザインに魂を込めて携わることができた。デザイナーキャリアとしてもここまで幅広く携われたことは非常に貴重な経験を得ることができたと自負している。</p>
<p>続いてプロダクトデザイン開発の秘話について語りたいところだが、現在 Pharms 以外の医療プラットフォーム事業に関連する新たなプロダクト開発に注力しているため、その話はまたの機会に。</p>
<hr>
<p>医療プラットフォーム事業に関連するプロダクトをこれからも創出し成長させていく面白い時期にあり、現在デザイナーを積極採用中です。カジュアルに話を聞きたい、医療領域のデザインに興味があるといったデザイナーの方は、ぜひ<a href="https://www.medley.jp/jobs/designer-new.html">こちら</a>までご連絡いただけると幸いです。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/designer-new.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- メドレーでセキュリティエンジニアとして働くということhttps://developer.medley.jp/entry/2021/05/28/175004https://developer.medley.jp/entry/2021/05/28/175004はじめまして。メドレーでセキュリティエンジニアをしている三浦です。
私はメドレーには今年(2021 年)の 2 月に入社し、このブログを執筆している時点で入社 4 ヶ月目となります。現在、全社的なセキュリティを担当しています。
今回のブログ...Fri, 28 May 2021 08:50:04 GMT<p>はじめまして。メドレーでセキュリティエンジニアをしている三浦です。</p>
<p>私はメドレーには今年(2021 年)の 2 月に入社し、このブログを執筆している時点で入社 4 ヶ月目となります。現在、全社的なセキュリティを担当しています。</p>
<p>今回のブログは以下のような構成でお届けします。</p>
<h1 id="目次">目次</h1>
<ol>
<li>自己紹介と、本ブログのテーマについて</li>
<li>前職でのセキュリティエンジニアとしての仕事</li>
<li>メドレーでのセキュリティエンジニアの仕事</li>
<li>セキュリティエンジニアとしての活動内容や役割の違いと気づき、課題</li>
<li>セキュリティエンジニア募集中!</li>
</ol>
<h1 id="自己紹介と本ブログのテーマについて">自己紹介と、本ブログのテーマについて</h1>
<p>私は前職でセキュリティ専業会社のセキュリティエンジニアとして脆弱性診断やペネトレーションテストのほか、セキュリティ研修の講師、ファイアウォールや Web アプリケーションファイアウォール(WAF)と呼ばれる機器の導入支援などに携わっていました。</p>
<p>このなかでも脆弱性診断の案件に多く関わっていたこともあり、最後には脆弱性診断のサービスオーナーとしてサービスに関わるすべてを取りまとめる立場でした。</p>
<p>今では事業会社のセキュリティエンジニアとして活動しているわけですが、このブログではセキュリティエンジニアとしての活動内容や役割にどのような変化があり、その変化により直面している自分自身の課題や苦労について書いていきます。</p>
<h1 id="前職でのセキュリティエンジニアとしての仕事">前職でのセキュリティエンジニアとしての仕事</h1>
<p>先述のとおり前職では脆弱性診断に関わることが多かったため、ここでは脆弱性診断について取り上げます。</p>
<p>脆弱性診断はスポット案件として実施することがほとんどでしたので、案件の流れという観点で仕事内容を整理すると以下のようになります。</p>
<ol>
<li>営業フォロー(同行や Q&A など)</li>
<li>スケジュールや診断対象などについてお客様にヒアリング
a. ここで診断用環境の準備をお願いすることが多い</li>
<li>診断対象の調査、クロール作業</li>
<li>スケジュール感や制限事項についてお客様とすり合わせ</li>
<li>計画書を作成</li>
<li>社内で計画書レビュー、お客様へ送付</li>
<li>診断作業
a. 自動診断ツールの調整
a. 手動診断の実施
a. 出力結果の精査、再現性の確認
a. 速報があれば当日中に整理して送付</li>
<li>診断結果のエビデンス整理</li>
<li>報告書の下書き、リスク度合いの検討</li>
<li>社内で報告書レビュー</li>
<li>お客様へ報告書を納品</li>
<li>報告会の実施</li>
</ol>
<p>このなかで、特に診断担当者の技術的スキルが問われたのが「手動診断の実施」と「出力結果の精査、再現性の確認」で、全体のうち約 4 割ほど。残りはドキュメント作成、レビュー、ミーティング、業務改善といった定型業務が占めていました。</p>
<p>私は「この 4 割の部分に対して付加価値を提供したい」というエンジニアとしての気負いと、個人的な関心という 2 つの理由から、寝る時間を削っては脆弱性の悪用(Exploit)手法の習得と、知識的な補強としての資格取得に明け暮れる時期がありました。</p>
<p>結局、寝不足が続いたことが原因で肺炎を発症し、入院一歩前まで炎症マーカーが悪化するという自分自身の脆弱性が露呈することになったのですが。ちゃんと夜は寝ましょう。</p>
<p>話がすこし脱線しましたが、脆弱性診断や SI 案件などは年度末にピークを迎えるものの年間を通じて五月雨で受注することも多く、一定の品質を維持しながらも納期を守るために目の前の案件で必死だったというのが今までの仕事のスタイルでした。</p>
<p>目の前の案件をひたすらこなし、組織に対して「報告書を提出して終わり」の関わり方に対して『これで社会の役に立っているのだろうか』という疑問が生まれたことが、メドレーに入社する転機になっています。</p>
<h1 id="メドレーでのセキュリティエンジニアの仕事">メドレーでのセキュリティエンジニアの仕事</h1>
<p>さて、セキュリティサービスを提供する側で案件に追われていた私がメドレーでセキュリティエンジニアとして活動することになっているわけですが、具体的に今なにをしているのか?というと、大きくは以下の 6 つが挙げられます。</p>
<ul>
<li>自社サービスに対する脆弱性診断</li>
<li>脅威情報や脆弱性情報の収集、周知</li>
<li>全社的な業務プロセス上のリスク調査</li>
<li>全社システムの BCP 整備、保守</li>
<li>ISMS 運用</li>
<li>セキュリティ相談</li>
</ul>
<p>自社サービスに対する脆弱性診断というのは「脆弱性診断の内製化」で、脆弱性診断を外部の業者にお願いするのではなく自社内で実施して開発にフィードバックする形です。</p>
<p>内製による診断であっても、基本的な流れはすでにご紹介した流れで進めていますが、対象サービスをクロールしてから診断対象を確定させるまでの主要なポイントでは、操作手順に漏れがないかなどの観点で開発からのレビューを挟むようにしています。これは内製であるからこそ実現できているアプローチだと思います。</p>
<figure class="figure-image figure-image-fotolife" title="内製で実施している診断対象一覧の例"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210527/20210527151142.png" alt="20210527151142.png"><figcaption>内製で実施している診断対象一覧の例</figcaption></figure>
<p>なお、ここまで脆弱性診断について書いてきましたが、これは現在の業務のほんの一部で、このほかには ISMS の運用(事務局)としての活動のほか、コーポレート IT メンバーと共に事業継続計画(BCP)整備なども並行して進めなければなりません。とにかく関わる範囲が広く、そして、とても地道な活動です。</p>
<p>ですが、会社全体を俯瞰してセキュリティのことを意識して物事を考え続けられる立場というのはセキュリティ会社では経験できないことで、自社にとっての最適解は何か?を探求し続けることの新たな面白さを見いだすことができています。</p>
<h1 id="セキュリティエンジニアとしての活動内容や役割の違いと気づき課題">セキュリティエンジニアとしての活動内容や役割の違いと気づき、課題</h1>
<p>ここまでの内容でお気づきの方もいるかと思いますが、現在の業務は ISMS 運用を初めとしてリスクマネジメントの分野も多く、実務的なバックグラウンドのない業務も出てきています。</p>
<p>システム的なセキュリティに関しても「どうやって悪用できるか…好物の BOF*は無いのか..BOF はどこだ…」という攻撃観点の思考回路のみで判断するのではなく、「どのようなソリューションや仕組み、またはルールでリスク低減できるのか、そして、それをなるべく自動で運用できるようにはどうするか」という守る側にとっての実務的な課題を考えなければなりません。</p>
<p>* バッファオーバーフローのこと</p>
<p>このほかに、私が実際に感じているセキュリティエンジニアとしての気づきや考え方の変化を挙げていくと、以下のようなものがあります。</p>
<ul>
<li>攻撃手法の理解を踏まえ、(社内リソースを鑑みた)現実的に運用しうる対策の仕組みを提案できなければならないことを痛感した</li>
<li>仕組み化やルール化するにも、エンジニアではない従業員の存在を考慮するようになった</li>
<li>脆弱性単体の影響範囲や度合いではなく、システムの利用方法や情報の種類など、社内だからこそ持てる視点を持って評価するようになった</li>
<li>「アップデートしたくてもできない」問題が生じないシステム設計が肝要であることを改めて認識した</li>
</ul>
<p>これらの変化への対応については、すべてソリューションや自動化に対して私の知見がまだまだ不足しているため、これからは「攻撃者目線」の考え方に加えて「ソリューションと自動化」の知見を補完しながら、メドレーのセキュリティ向上に貢献し続けていきたいと考えています。</p>
<h1 id="セキュリティエンジニア募集中">セキュリティエンジニア募集中!</h1>
<p>最後となりますが、セキュリティエンジニアは関わる業務の幅広さに加え、絶対的な正解が存在しない問題にソリューションを提供することも多い仕事です。</p>
<p>メドレーでの内製による脆弱性診断や全社セキュリティに興味がある方はぜひジョインしてください。いつでもお待ちしています!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 介護のほんねリニューアルの話https://developer.medley.jp/entry/2021/04/06/180010https://developer.medley.jp/entry/2021/04/06/180010はじめまして。メドレーでデザイナーをしているおおのです。わたしはメドレーには昨年(2020 年)の 6 月に入社し、現在老人ホーム・介護施設の検索サイト「介護のほんね」を担当しています。
介護のほんねは昨年リニューアルを行いました。今回は、...Tue, 06 Apr 2021 09:00:10 GMT<p>はじめまして。メドレーでデザイナーをしているおおのです。わたしはメドレーには昨年(2020 年)の 6 月に入社し、現在老人ホーム・介護施設の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」を担当しています。</p>
<p>介護のほんねは昨年リニューアルを行いました。今回は、そのリニューアルプロジェクトの中で自分が取り組んだこと(主にサイトトップのリニューアルについて)についてお話しようと思います。</p>
<h1 id="目次">目次</h1>
<ol>
<li>介護のほんねとは</li>
<li>リニューアルの背景</li>
<li>プロジェクトについて</li>
<li>プロジェクトとの関わりについて</li>
<li>サイトトップの制作</li>
<li>プロジェクトを終えて</li>
</ol>
<h1 id="介護のほんねとは">介護のほんねとは</h1>
<p>介護のほんねは、納得できる老人ホーム・介護施設探しができる検索サイトです。介護のほんねには、全国にある多くの施設が掲載されています。予算やエリアなどお好みの条件で施設を検索したり、気になった施設へ見学予約や資料請求などお問い合わせができます。また社内の入居相談員による施設に関する資料送付や条件にあった施設紹介、施設見学の日程調整などのサポートにも対応しています。</p>
<h1 id="リニューアルの背景">リニューアルの背景</h1>
<p>介護のほんねは 2014 年にローンチされました。しかし、長い間の運用の中で古いデザインと新しいデザインが入り混じっている部分があったり、SEO の観点での強化が必要だったり、今後の成長に向けて手直しする部分が積もり始めていました。また、2019 年に「医療につよい老人ホーム検索サイト」にコンセプトを変更しましたが、より多くの方にご利用いただくためにも、医療につよいというコンセプトからさらに一歩進み、様々な状況のお客様に向き合い、お客様が介護に対して前向きに、そして後悔のない選択ができるよう寄り添うことのできるサービスにしていきたいという思いから今回のリニューアルが始まりました。</p>
<h1 id="プロジェクトについて">プロジェクトについて</h1>
<p>リニューアルプロジェクトは昨年 5 月頃から始まり、外部のデザイン制作会社と連携して進められました。制作会社の方には、デザイン業務の支援をお願いしつつ、週 1〜2 回の定例で進捗報告や業務内容の確認を行っていました。</p>
<h1 id="プロジェクトとの関わりについて">プロジェクトとの関わりについて</h1>
<p>自分が入社したのが昨年 6 月後半だったので、デザインや開発も部分的に進んでいる状況でした。介護領域の勉強や、担当サービス、競合調査、リニューアルプロジェクトや介護のほんねのこれまでの歩みについてなどのキャッチアップと並行してリニューアルのデザインのことも考えていました。</p>
<p>そこで次のようなことを意識して動きました。</p>
<h2 id="プロジェクトに対して">プロジェクトに対して</h2>
<ul>
<li>社内のメンバーといつでもコミュニケーションがとれることを活かし、外注先とも相談して自分自身も手を動かしながらデザインをブラッシュアップすること</li>
</ul>
<h2 id="社内に対して">社内に対して</h2>
<ul>
<li>サービスに対するメンバーの思いを確認しつつ、これまでのサービスの歩みを尊重して動くこと</li>
<li>社内のデザイナーの先輩など頼れる人には頼ること</li>
</ul>
<h1 id="サイトトップの制作">サイトトップの制作</h1>
<p>プロジェクトにジョイン後、主にサービスの顔であるサイトトップについて、アイデア出しやデザインをしました。</p>
<p>サイトトップにどのようなコンテンツを掲載するのか、また、お客様が介護に対して前向きに、そして後悔のない選択ができるよう寄り添いたいというサービスの思いをどのように表現すればよいか考えていきました。</p>
<p>掲載するコンテンツに関しては、サイトトップを誰に向けて作るのかという部分を介護施設探しが初めての人に設定し、このサイトでは施設が探せることを伝えてサービスのコア機能を全面に出しつつ、介護のほんねでの施設探しの魅力ポイントや、介護や施設探しに関するコラムや Q&A などお役立ち情報も掲載するようにしました。</p>
<p><b>▼ リニューアル前のサイトトップ</b></p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210401/20210401103421.png" alt="20210401103421.png">
<p>また、当時のサイトトップは上に貼ったキャプチャの通りで、落ち着いた青を基調としたクリーンで誠実そうな印象がありました。これまで築き上げてきたプロダクトのイメージや印象は、リニューアル後も受け継いで残していきたいなと思いました。</p>
<p>それをふまえた上で、サイトトップの新しいキャッチコピーやキービジュアルをどういうものにするのかプロジェクトのメンバーとアイデアを出しながら考えました。しかし、「どのアイデアも間違ってないけどもっといいのがありそうだな」という気持ちが拭えず、なかなか決まりませんでした。</p>
<p>そこで、改めて原点に帰って情報を整理するために次のことに取り組みました。</p>
<ul>
<li>ユーザーを知る</li>
<li>介護のほんねが提供する価値を整理する</li>
<li>信頼できる情報から納得できる介護サービスと出会えることをキャッチコピーに落とす</li>
<li>介護のほんねの世界観を視覚的に伝えられるようなメインビジュアルの選定</li>
</ul>
<h2 id="ユーザーを知る">ユーザーを知る</h2>
<p>まずはじめに介護のほんねのユーザーデータを 1 件 1 件見ていきました。そこにはお客様の情報や施設を探している理由など様々な情報がまとまっています。それらのデータを見ていくと、おおよそ 4 つのパターンにわけられることがわかりました。</p>
<p>① 退院後の施設を探したい<br>
転倒など何かしらの理由で入院している家族の退院期限が迫っており、退院後に在宅での介護ではなく施設にいれる必要があるケース</p>
<p>② 施設を移動しないといけなくなった<br>
介護度があがったことで施設の受け入れ可能範囲から外れた場合や、施設の中でトラブルがあるなどの事情により施設を移る必要があるケース</p>
<p>③ 今後に備えて早めに動きたい<br>
今すぐ介護施設に入れる必要があるわけではないものの、親が高齢でひとり暮らしをしていて不安なためまだ自分で動けるうちに施設にいれておきたいケース</p>
<p>④ 在宅介護では限界がきてしまった<br>
自分自身も高齢になってきたため、仕事や家事に加えて介護の両立が難しくなってきた方が施設を探しているケース</p>
<p>このようなお客様のデータを見ていると、事情が事情だけになるべく急いで施設を探しているものの、離れても家族が幸せに暮らせるように慎重に施設を探したい、という思いが伝わってきました。</p>
<p><b>▼ ユーザーの大まかなパターン</b></p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210401/20210401115623.png" alt="20210401115623.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210401/20210401115632.png" alt="20210401115632.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210401/20210401115641.png" alt="20210401115641.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210401/20210401115649.png" alt="20210401115649.png">
<h2 id="介護のほんねが提供する価値を整理する">介護のほんねが提供する価値を整理する</h2>
<p>次に、ユーザーの方に対して介護のほんねができることはどういうことかを整理しました。</p>
<p>介護のほんねは「老人ホーム・介護施設の検索サイト」ということからもわかるように、施設を探すことができ、プロの入居相談員が施設探しから実際の入居までサポートするサービスです。また介護のほんねとしては、お客様が介護に対して前向きに、そして後悔のない施設探しができるよう寄り添いたいという思いがあります。そこで、介護のほんねの価値を機能的なものと情緒的なものにわけて整理してみました。</p>
<p>機能的価値=すぐに納得できる施設が見つかること<br>
情緒的価値=家族のために、いろんな思いをもって施設探しをしているユーザーに寄り添うこと</p>
<p>ユーザーデータから見えてきた「事情が事情だけになるべく急いで施設を探しているものの離れても幸せに暮らせるように慎重に施設を探したい」、そのようなユーザーに介護のほんねは寄り添って、後悔することなく納得できる施設がすぐに見つかるようにサポートしていくことを伝えたいなと思いました。</p>
<h2 id="納得できる介護サービスと出会えることをキャッチコピーに落とす">納得できる介護サービスと出会えることをキャッチコピーに落とす</h2>
<p>ユーザーやサービスの提供価値について整理をしたところで、新しいコンセプトをサイトトップのキャッチコピーにどう落とし込むのかを考えました。</p>
<p>そもそもサイトトップのキャッチコピーですので、前提として「サービス概要、コンセプトが端的に伝わること」は大事にしたいと思いました。サービス概要は、繰り返しになりますが老人ホーム・介護施設の検索サイトです。そして端的にサービス価値を伝えるためにも、先程述べたサービスの機能的価値である「すぐに納得できる施設がみつかること」をキャッチコピーに盛り込もうと考えました。</p>
<p>色々アイデアを出した結果、「老人ホームが見つかる」というサービスのコア機能に加え、介護のほんねならではの「すぐに」見つかるということや、後悔のない納得いく施設選びができるというエッセンスを入れて、最終的に「納得できる老人ホームがすぐ見つかる」というコピーになりました。</p>
<h2 id="介護のほんねの世界観を視覚的に伝えられるようなメインビジュアルの選定">介護のほんねの世界観を視覚的に伝えられるようなメインビジュアルの選定</h2>
<p>キャッチコピーはサービスの機能やできることをわかりやすく伝えるものにしたため、キービジュアルは先程述べたサービスの情緒的価値のエッセンスをいれたいと思いました。それを踏まえ、いろんな思いを持った相談者の方に寄り添い、ご家族が施設に入居されてから始まる新しい生活を前向きに捉えられるようなビジュアルにしようと思いました。</p>
<p>キービジュアルの選定にあたり、チーム内で意見を集めながら、色々アイデアが出ました。入居後のイメージやサービス概要が伝わりやすい老人ホームの屋内風景の写真、入居後の楽しい生活が期待できそうな施設のスタッフと入居者の笑顔の写真や、サービスの寄り添うスタンスを抽象的に伝える手を握り合うような写真など、たくさんの写真をあてはめて検討を繰り返しました。</p>
<p><b>▼ イメージ選定</b></p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210401/20210401103430.png" alt="20210401103430.png">
<ul>
<li>車椅子が写りこんでいると、自立度が高い状態で施設を探している方(※介護の必要がない元気な方向けの施設もあり、元気なうちから施設に入られる方もいらっしゃいます)にとって自分が使っていいサービスではないのかとマイナスなイメージにならないか?</li>
<li>施設スタッフと入居者が笑顔で写っている人が写ったモデル風の綺麗な写真だと素材感が出すぎて嘘っぽくならないか?</li>
<li>手を包みこむなどモチーフが抽象的すぎるとかえって何も伝わらないのではないか?</li>
</ul>
<p>写真を選ぶ中でチーム内でも相談しながら、小さな違和感をひとつずつつぶしていきました。そして、最終的に下のようなキービジュアルになりました。</p>
<p><b>▼ リニューアル前とリニューアル後</b></p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210401/20210401115327.png" alt="20210401115327.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210401/20210401115319.png" alt="20210401115319.png">
<p>これからの新しい生活がポジティブなものに捉えられるような、スタッフと笑顔で生活する入居者が写っており、背景に程よく雑多感が残る素材感を抑えた写真を選びました。写真に写っているのは入居者と入居者に寄り添うスタッフですが、介護のほんねも同じように、施設探しをしている方に寄り添う姿勢がこの写真から間接的に伝われば嬉しいなと思っています。</p>
<h1 id="プロジェクトを終えて">プロジェクトを終えて</h1>
<p>プロジェクトは一段落しましたが、スタートラインにたったところなので、まだまだ追加したい機能や磨き込みたい部分も山積みだなと感じています。</p>
<p>今回、自分のデザインに納得感をだすためにサービスや介護の知識、チームのメンバーが考えていることへの理解を深めながら並行してリニューアルのデザインを手掛けたことは、とてもやりがいのあるものでした。</p>
<p>また、リニューアルをきっかけに改めてサービスの価値や目指したい世界を整理できたのはとても良かったです。これからも介護のほんねの目指したい姿を見据えながら、より多くの方につかってもらえるようなサービスにしていきたいと思っています。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/designer-new.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 開発チームと一体となった QAhttps://developer.medley.jp/entry/2021/03/09/175356https://developer.medley.jp/entry/2021/03/09/175356みなさんこんにちは、メドレーの QA エンジニア かみむら です。
入社して間もなく 1 年になります。
CLINICS 開発チームの QA 活動を行っています。
私自身の経歴としては、テスト・品質関連業務に足を突っ込んでから早 20 年に...Tue, 09 Mar 2021 08:53:56 GMT<p>みなさんこんにちは、メドレーの QA エンジニア かみむら です。
入社して間もなく 1 年になります。
CLINICS 開発チームの QA 活動を行っています。</p>
<p>私自身の経歴としては、テスト・品質関連業務に足を突っ込んでから早 20 年になろうとしています。
2020 年はメドレーの QA エンジニアが一気に 0 名 →2 名になりました。
先日 <a href="/entry/2021/01/15/180126">Magic Pod 導入の記事</a> を公開した米山とはかつての同僚でもあります。
現在は別々の部署に所属していますが、お互い得意分野を発揮しつつ時折情報交換や相談ごとなどをしているような関係性です。
自分とは違うタイプの同職種がいると、何かと捗ります(その辺りはまた別の機会に…)。</p>
<p>CLINICS 開発チームでは、エンジニア・デザイナー・ QA エンジニアがワンチームで開発を進めています。
これまで私が経験してきた現場では、QA は開発チームの外側にいる(ステークホルダーとして)ことが多く、新鮮な気持ちでいます。</p>
<p>この記事では私の入社以降取り組んできた QA 活動の概要についてお話ししたいと思います。</p>
<h1 id="clinics-の-qaとは">「CLINICS の QA」とは?</h1>
<p>さて、何しろこれまで「QA エンジニア」という職種のひとが存在しなかった開発チームのため、まずは「CLINICS に必要な QA ってなんだろう?」というところから考えはじめました。
もちろん、数々のプロダクトを大きな障害なくリリース・運用してきているので、それなりに QA/テストの技術力はあるはずです。</p>
<p>入社前にも、何度も</p>
<ul>
<li>なぜ(他の手段ではなく)QA エンジニアの採用が必要なのか?</li>
<li>QA エンジニアにどんな役割を期待しているのか?</li>
</ul>
<p>といった点を開発チームの上長と話し合いました。</p>
<h2 id="なぜ他の手段ではなくqa-エンジニアの採用が必要なのか">なぜ(他の手段ではなく)QA エンジニアの採用が必要なのか?</h2>
<p>私は第三者検証会社に所属していた期間が長いこともあり、品質についての悩みがある開発チームにテスト支援だったりコンサルティング的役割で関わることが多かったので、「てっとり早く他社に相談するのではなく、採用したいのはなぜだろう?」という疑問が単純にありました。</p>
<p>現場の思いとして、以下の点が挙げられていました。</p>
<ul>
<li>プロダクト開発エンジニアがリリース時のリグレッションテスト(シナリオテスト)をメンテ・実施しているが、CLINICS(電子カルテ)の複雑さに追いついていくのが難しくなってきた</li>
<li>ここに対して専門性をもって取り組むことで、複雑なドメイン知識を扱うプロダクトを安定して開発リリースできる仕組みを作りたい</li>
</ul>
<h2 id="qa-エンジニアにどんな役割を期待しているのか">QA エンジニアにどんな役割を期待しているのか?</h2>
<p>描いている組織体制像としては以下のようなお話でした。</p>
<ul>
<li>QA エンジニアにテストフェーズだけ縦割り的に関わってもらうのではなく、プロダクト開発チームとしてひとつになって、有るべき開発プロセスを一緒につくり上げていきたい</li>
<li>開発エンジニアがテストに関してしっかりと理解をしていくことで、そもそも品質の高いプロダクトをつくることができる、といった世界を目指したい</li>
</ul>
<p>これら課題に対してチャレンジ的に「やってみたい」と強く思ったのと、私はこれまでにいろいろな現場の開発体制の中でテストエンジニア/QA エンジニアとして活動してきていましたが、「プロダクト開発チームとしてひとつになって」というところがすごく「それ良いな!」と思ったのを覚えています。
専門職に任せるのではなく、「一緒に理想の世界をつくりあげたい」という気持ちがとても見える良い組織だと思いました。</p>
<h2 id="qa-エンジニアテストエンジニアsetswet">「QA エンジニア」「テストエンジニア」「SET/SWET」</h2>
<p>ここで少し職種名に関する補足説明をしますと、「QA エンジニア」という呼称は比較的新しい概念なんじゃないかと思います。</p>
<p>一般的には「テストエンジニア」と言い、文字通り「テスト業務に特化したエンジニア」を指していました。その後『<a href="https://www.amazon.co.jp/%E3%83%86%E3%82%B9%E3%83%88%E3%81%8B%E3%82%89%E8%A6%8B%E3%81%88%E3%81%A6%E3%81%8F%E3%82%8B-%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB%E3%81%AE%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA-%E3%82%B8%E3%82%A7%E3%83%BC%E3%83%A0%E3%82%BA%E3%83%BB%E3%82%A6%E3%82%A3%E3%83%86%E3%82%AB%E3%83%BC/dp/482228512X">テストから見えてくる グーグルのソフトウェア開発</a> 』(2013 年日本語版発行)から「SET/SWET(Software Engineer in Test)」が日本でも認知され、国内の導入事例が出てきたことで一気に広まった印象です。</p>
<p>私の解釈では、「QA エンジニア」と呼称する場合、「テストエンジニア」や「SET/SWET」の素養も含みつつテスト以外にも「品質向上のための活動全般を積極的に担う役割」という意味合いが濃くなるんじゃないかと捉えています。</p>
<h1 id="clinics-のありたいqaの姿">CLINICS のありたい「QA」の姿</h1>
<p>上長と話し合った結果、以下のような活動をメインに据えていきましょうということになりました。</p>
<ol>
<li>(現状行っている)テストの改善</li>
<li>プロセス改善</li>
<li>知識の底上げ</li>
</ol>
<p>それぞれのトピックについて、現在 CLINICS の QA 活動としてどのように取り組んでいるかを 1 つずつ詳しく説明していきます。</p>
<h2 id="テストの改善">「テストの改善」</h2>
<p>現状 CLINICS の開発サイクルは週 1 回のリリースとしています。
毎回リリース用にコードフリーズした環境に対してリグレッションテストを開発チーム全員で手作業(マニュアルテスト)により行っています。
日々の機能追加や改修の際に手をいれてはいますが、リグレッションテストのシナリオもツギハギ感がみえるようになってきました。
そして増えてきたシナリオによってどこがどう品質担保されているのか見通しが悪くなっている点が大きな悩みでした。</p>
<p>そこでまず、「現状のシナリオテストを分析し、全体的に再設計する」という計画をたて、現在は開発チームの中でもカルテに造詣の深い一部メンバーで定期的に MTG(レビュー会)を行いながらテスト設計の方針を組み立てています。
設計図ではマインドマップツールやマトリクスを作成して方向性や粒度をすり合わせしています。</p>
<p>私自身も、今までの業務でこれだけじっくり丁寧にテスト設計をしてきたことがないため、厳しくもたいへんやりがいのあるタスクとなっています。</p>
<h2 id="プロセス改善">「プロセス改善」</h2>
<p>こちらはテストそのものではなく、開発チームのルーチンワークや体制に関わる改善です。</p>
<p>種まき的にスモールチームで新しいふりかえり手法を試してみたり、開発定例会で共有している障害情報とテスト実施中に見つかったバグの情報を一元化して残す仕組みを導入したりなど行いました。
特に「ふりかえり」については常に改善を意識できるプロセスで、上手なふりかえりをすればするほど開発品質が向上すると考えられているため、勉強会の後にフィードバックコメントをもらう仕組みをつくったりなどちょっとした隙間にも「ふりかえり」を小さく回せるように腐心しています。</p>
<p>それとまだ着手できていませんが、記録した障害・バグ情報も近いうちに分析・分類していって今後の開発に役立てたいと考えています。
バグ分析は時間がかかるのでなかなかサクッとはいきませんが、長期的視点では有用な財産になります。</p>
<p>また「開発プロセス」「業務フロー」自体の現状の悩みごとを現場の声として私が直接探るためと、開発チーム内で「共通の目標」を認識するためのブレストをリード陣と行いました。
進め方は「<a href="https://www.software-quasol.com/sapid3-0/">SaPID</a> 」という改善手法を参考にしています。</p>
<blockquote>
<p>SaPID とは、”Systems analysis/Systems approach based Process Improvement methoD”の略語で、当事者自らが(最終的には仲間と共に)解決すべき問題点を特定し、現実的に解決、改善、そして革新を実現しながら段階的・継続的に自律運営へのゴールを目指す手法です。</p>
<p>誰かにやらせる、やらされるのではなく、当事者自らの意思、チーム・組織の意思で自律的に運営を進めることを志向するのが特徴です。</p>
</blockquote>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210305/20210305203657.png" alt="20210305203657.png">
<p>コロナ禍の中、日によってリモートで参加のメンバーがいたりなどふせんをつかったワークにも工夫が必要でしたが、最終的には「共通の目標」として</p>
<ul>
<li>自分と身の周りに役に立つ状況をつくる</li>
<li>世間に認知されるプロダクトをつくる</li>
</ul>
<p>というような定義をつくることができました。</p>
<h2 id="知識の底上げ">「知識の底上げ」</h2>
<p>CLINICS はこれまで中途採用メンバーが多かったため、OJT 中心で体系的な教育はまだまだ整備をしている段階です。
新卒入社も増えてきている昨今ではメンバー全体で知識レベルが合わないことによる弊害が出てきており、目線を合わせていくことが喫緊の課題でした。</p>
<p>普段の業務の中で断片的な情報を得ることはできてもなかなか体系的な知識を効率的に身につけることは難しいので(専門書は分厚くてハードルが高い)、上長からのたっての願いでもあり、QA に従事している者にとっては割と初歩的なテスト技法から教えることにしました。
私のようにずっと QA 活動をしてきた者にとっては当たり前の技法でも、開発エンジニアにとっては意外と知る機会がなかったりするものです。
覚えておくとテストの段階だけではなく設計品質もあげることができるので、定着するように日々取り組んでいます。</p>
<p>まずは教科書的な内容を CLINICS チームの<a href="https://www.atlassian.com/ja/software/confluence">Confluence</a>に書いて、講義形式で CLINICS 開発チームのメンバーに説明し、実践編として宿題を出して答え合わせと解説を行う、という流れで行っています。
学習者としては話を聞いているだけでは覚えにくく、実際に手を動かしたり、日々の業務で本当に困った経験をすると学びたい欲に火がつくと思っているので、実践を大切にしています。
演習問題は以下のような書籍を参考にして作問しています。ありがとうございます、著者の方々。</p>
<p>『<a href="https://www.amazon.co.jp/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%83%86%E3%82%B9%E3%83%88%E6%8A%80%E6%B3%95%E3%83%89%E3%83%AA%E3%83%AB%E2%80%95%E3%83%86%E3%82%B9%E3%83%88%E8%A8%AD%E8%A8%88%E3%81%AE%E8%80%83%E3%81%88%E6%96%B9%E3%81%A8%E5%AE%9F%E9%9A%9B-%E7%A7%8B%E5%B1%B1-%E6%B5%A9%E4%B8%80/dp/4817193603/">ソフトウェアテスト技法ドリル―テスト設計の考え方と実際</a> 』<br>
『<a href="https://www.amazon.co.jp/%E3%81%AF%E3%81%98%E3%82%81%E3%81%A6%E5%AD%A6%E3%81%B6%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88%E6%8A%80%E6%B3%95-%E3%83%AA%E3%83%BC-%E3%82%B3%E3%83%BC%E3%83%97%E3%83%A9%E3%83%B3%E3%83%89-ebook/dp/B00HE8082Q/">はじめて学ぶソフトウェアのテスト技法</a> 』<br>
『<a href="https://www.amazon.co.jp/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%83%86%E3%82%B9%E3%83%88%E6%8A%80%E6%B3%95%E7%B7%B4%E7%BF%92%E5%B8%B3-%E7%9F%A5%E8%AD%98%E3%82%92%E7%B5%8C%E9%A8%93%E3%81%AB%E5%A4%89%E3%81%88%E3%82%8B40%E5%95%8F-%E6%A2%85%E6%B4%A5-%E6%AD%A3%E6%B4%8B/dp/429711061X/">ソフトウェアテスト技法練習帳 <del>知識を経験に変える 40 問</del></a> 』</p>
<p>一度教わっただけではなかなか覚えるのも難しいので、大事なテスト技法(境界値分析とか…)は折に触れて何度も何度も口にするようにしています。</p>
<h1 id="再clinics-の-qaとは">(再)「CLINICS の QA」とは?</h1>
<p>冒頭で引用した <a href="/entry/2021/01/15/180126">Magic Pod 導入の記事</a> では、「テストの自動化は(リリース後即座に修正できない)アプリから着手していく」方針としました。
現在は、CLINICS の Web ページ側のテスト自動化も推進しています。</p>
<p>テスト自動化の目的は現場によっていろいろと思いがあるものです。なぜ「テスト自動化をやるのか?」については、機械にリグレッションテストを任せて手が空いた分、より高度な(経験則が必要な)探索的テストができるようになるから、と考えています。</p>
<p>最初の問いに戻ります。<br>
「CLINICS の QA」とは何か?</p>
<p>品質向上する仕組みが自然にできている自律した組織で、私は開発チームメンバーと「おもしろいテスト」「楽しいテスト」をしていきたい、と思っています。
それによって顧客が出会う可能性のある不具合が減り、「そもそも品質の高いプロダクトをつくることができるという世界」に近づけるのではないかな、と考えています。</p>
<p>「おもしろいテスト」「楽しいテスト」とは発見であり、学習であり、フィードバックのサイクルによって生まれます。
そのためにも前述の「プロセス改善」と「知識の底上げ」は両輪で進めていく必要があります。</p>
<p>品質向上のための手段は、テストの他にも実に多岐に渡ります。
長年やってきた私もまだ全貌を掴み切れていない「QA のエンジニアリング」ってこんなに奥深く楽しい! ということが開発チームメンバーの共通認識になるとうれしいです。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210305/20210305203713.png" alt="20210305203713.png">
<p>最後までお読みいただきありがとうございました。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- HTTP コンテンツ圧縮でパフォーマンス改善https://developer.medley.jp/entry/2021/02/01/180003https://developer.medley.jp/entry/2021/02/01/180003事業本部 プロダクト開発室のエンジニアの中畑です。
オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」の開発・基盤周りを担当しております。
今回は、HTTP のコンテンツ圧縮について調査・対応する機会があったので、本ブログ...Mon, 01 Feb 2021 09:00:03 GMT<p>事業本部 プロダクト開発室のエンジニアの中畑です。</p>
<p>オンライン診療・服薬指導・クラウド診療支援システム<a href="https://clinics.medley.life/">「CLINICS」</a>の開発・基盤周りを担当しております。</p>
<p>今回は、HTTP のコンテンツ圧縮について調査・対応する機会があったので、本ブログにて紹介したいと思います。</p>
<h1 id="http-コンテンツの圧縮とは">HTTP コンテンツの圧縮とは</h1>
<p>HTTP コンテンツの圧縮とは、HTTP の通信において Web サーバー側が返すデータを、なんらかの形式で圧縮してクライアントに返すことです。圧縮されたレスポンスをクライアント側は解凍して利用します。</p>
<p>HTTP コンテンツの圧縮によって得られるメリット・デメリットは以下の通りです。</p>
<h2 id="-メリット">⤴ メリット</h2>
<ul>
<li>通信の帯域使用量を減らせる</li>
<li>それによって通信にかかる時間を削減し、<strong>ページ表示速度を向上</strong>できる</li>
</ul>
<h2 id="-デメリット">⤵ デメリット</h2>
<ul>
<li>圧縮・解凍コストがかかる
<ul>
<li>ただし、圧縮・解凍コストはほとんどの場合は小さいため、メリットを下回る</li>
</ul>
</li>
<li>大容量ファイルやもともと圧縮されているファイル(画像や動画、PDF ファイルなど)を圧縮するのは、圧縮してもサイズがそれほど小さくならないため非効率である
<ul>
<li>サイズがあまり削減できない割に、圧縮・解凍に CPU リソースを使い、数百 MB を超えるファイルになるとそれぞれ数秒かかることもある</li>
</ul>
</li>
</ul>
<h1 id="http-コンテンツを圧縮するためには">HTTP コンテンツを圧縮するためには</h1>
<p>HTTP コンテンツを圧縮するためには、クライアントが解凍可能な圧縮形式を指定する必要があります。解凍可能な圧縮形式を指定するには、リクエストヘッダに<code>Accept-Encoding</code>ヘッダを指定します。</p>
<p>最近のブラウザでは、HTTP リクエスト時に自動的に<code>Accept-Encoding</code>ヘッダを自動的に付加してアクセスしているので、ブラウザ経由の場合は特に明示的に指定する必要はありません。Chrome, Safari, Edge など、ほとんどのメジャーなブラウザでは<code>Accept-Encoding: gzip, deflate, br</code>が指定されています(※2021-01-23 時点)。</p>
<h2 id="圧縮形式gzip-deflate-br">圧縮形式(gzip, deflate, br)</h2>
<p>圧縮形式はいくつかありますが、ブラウザを利用する場合は以下のいずれかが選択肢になります。</p>
<ul>
<li>gzip: LZ77 と 32 ビット CR を用いた圧縮形式</li>
<li>deflate: zlib 構造体と deflate 圧縮アルゴリズムを用いた圧縮形式</li>
<li>br: Brotli アルゴリズムを用いた圧縮形式。gzip に近いが大容量の言語辞書を用いて、頻出するパターンの単語を圧縮して効率化。そのため文章的なテキストでは<strong>gzip よりも圧縮率が高い</strong>と言われる</li>
</ul>
<p><a href="https://github.com/google/brotli">Brotli</a>は比較的新しい形式で、ほとんどのサーバー、ブラウザで対応しています。</p>
<h1 id="サーバーでの-http-コンテンツの圧縮方法gzip">サーバーでの HTTP コンテンツの圧縮方法(gzip)</h1>
<p>サーバーはクライアントの<code>Accept-Encoding</code>リクエストヘッダを受け取り、その中から 1 つを選択して圧縮処理を行い、<code>Content-Encoding</code>レスポンスヘッダを付加してクライアントに結果を知らせます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210129/20210129123916.png" alt="20210129123916.png">
<p>CLINICS が利用しているそれぞれのアプリケーション・ミドルウェアに絞って、どのように HTTP コンテンツ圧縮を実現しているか解説したいと思います。いくつか圧縮形式はありますが、ここでは gzip 形式での圧縮方法について解説します。</p>
<h2 id="nginx">NGINX</h2>
<p>NGINX の<code>ngx_http_gzip_module</code>を利用することで gzip 圧縮することができます。</p>
<p>nginx.conf の<code>gzip</code>ディレクティブを<code>on</code>にすることで圧縮が有効になります。ただし、タイプを指定しないと<code>Content-Type: text/html</code>のときにしか圧縮されません。他のタイプでも圧縮したいときは<code>gzip_types</code>ディレクティブも合わせて指定する必要があります。<code>gzip_types</code>に<code>*</code>を指定することで、すべてのコンテンツを圧縮することもできます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="nginx"><code><span class="line"><span style="color:#D4D4D4">gzip: on;</span></span>
<span class="line"><span style="color:#D4D4D4">gzip_types: text/css application/javascript application/json</span></span></code></pre>
<p>また、CloudFront など Proxy を経由してのアクセスの場合はデフォルトでは行われません。Proxy 経由のアクセスかどうかは、リクエストヘッダに<code>Via</code>ヘッダがあるかどうかで判定します。</p>
<p>CloudFront 経由でのアクセスの場合は<code>Via: 1.1 xxxxx.cloudfront.net (CloudFront)</code>のように<code>Via</code>ヘッダが付加されているため、NGINX にて Proxy 経由であると判定します。Proxy 経由であっても何かしらの条件で圧縮したい場合は<code>gzip_proxied</code>ディレクティブを指定する必要があります。</p>
<p>ref. <a href="https://nginx.org/en/docs/http/ngx_http_gzip_module.html">https://nginx.org/en/docs/http/ngx_http_gzip_module.html</a></p>
<h2 id="cloudfront">CloudFront</h2>
<p>CloudFront の Behavior の設定にて設定します。Compress Objects Automatically を有効化することで、gzip 圧縮が有効になります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210129/20210129124017.png" alt="20210129124017.png">
<p>上記を有効化すると、CloudFront では以下の条件で圧縮が行われます。</p>
<ul>
<li>ファイルサイズが 1,000(≒1KB) 〜 10,000,000(≒10MB) バイトの間
<ul>
<li>よって、オリジンからファイルサイズを判定するための<code>Content-Length</code>ヘッダが付与されていない場合は、サイズ判別できないため圧縮されない</li>
</ul>
</li>
<li>特定の<code>Content-Type</code>のコンテンツを圧縮する
<ul>
<li>テキスト系のコンテンツは圧縮するが、画像や動画、PDF など、もともと圧縮されているものは対象外。詳しくは<a href="https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html#compressed-content-cloudfront-file-types">こちら</a></li>
</ul>
</li>
<li>オリジン側(NGINX や Rails など)から圧縮して返される場合は、<strong>再度圧縮は行わない</strong>
<ul>
<li><code>Content-Encoding</code>ヘッダの有無で判定している</li>
</ul>
</li>
</ul>
<p>ref. <a href="https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html">https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html</a></p>
<h2 id="rails">Rails</h2>
<p>Rails はデフォルトでは HTTP コンテンツの圧縮は行いません。Rails でコンテンツ圧縮を行いたい場合は、Rails の Rack Middleware の<a href="https://github.com/rack/rack/blob/master/lib/rack/deflater.rb">Rack::Deflater</a>を導入するのが簡単です。しかしながら、Rack::Deflater はすべての<code>Content-Type</code>のコンテンツでも圧縮するので、画像や動画・ PDF など圧縮するべきでないコンテンツまで圧縮してしまいます。NGINX や CloudFront など、Rails 外の他のサービスやミドルウェアに任せるのが良いと思います。</p>
<h1 id="clinics-での-http-コンテンツ圧縮のシーケンス">CLINICS での HTTP コンテンツ圧縮のシーケンス</h1>
<p>前章で解説したアプリケーション・ミドルウェアは、CLINICS では以下のように連携しています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210129/20210129132442.png" alt="20210129132442.png">
<p>AWS 上に Rails アプリケーションをデプロイしており、通常のアクセスはロードバランサーから NGINX を経由して Rails にアクセスし、静的ファイルなどキャッシュコンテンツは CloudFront 経由でアクセスしています。</p>
<p>CLINICS では用途に合わせた圧縮を行っています。3 つのケースを紹介します。</p>
<h2 id="1-nginx-経由で-rails-にアクセスした時">1. NGINX 経由で Rails にアクセスした時</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210129/20210129124126.png" alt="20210129124126.png">
<p>API アクセスなどは上記シーケンスでアクセスしています。ほとんどが<code>text/html</code>や<code>application/json</code>形式のコンテンツとなり、NGINX にて gzip 圧縮処理を行っています。Rails はアプリケーションの処理のみを行い、圧縮は行わないようにしています。</p>
<h2 id="2-cloudfront-経由で-s3-にアクセスした時">2. CloudFront 経由で S3 にアクセスした時</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210129/20210129124148.png" alt="20210129124148.png">
<p>画像ファイルや PDF、静的な js、css ファイルなどはサービスのデプロイ時に S3 にアップロードしています。クライアントは CloudFront 経由でアクセスし、S3 から取得して、CloudFront で gzip に圧縮処理を行っています。また、一定期間 CloudFront 上にキャッシュされるので、効率よく圧縮コンテンツを返します。</p>
<h2 id="3-cloudfrontnginxrails-経由で-s3-にアクセスした時">3. CloudFront→NGINX→Rails 経由で S3 にアクセスした時</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210129/20210129124210.png" alt="20210129124210.png">
<p>静的ファイルの中でもシグネチャをチェックしているものは、このフローでアクセスしています。NGINX でも圧縮設定を ON にしていますが、<code>Via</code>ヘッダがあるため、NGINX では圧縮しないようになっています。</p>
<h1 id="まとめ">まとめ</h1>
<p>HTTP コンテンツの圧縮を適切に行うことで、サービス全体のパフォーマンス向上が見込めます。更に CloudFront を活用することで、アプリケーションやミドルウェアでの圧縮処理をなくし、更なるパフォーマンス向上が見込めます。</p>
<p>今回は HTTP コンテンツの gzip 圧縮についてのみ触れましたが、Brotli 圧縮についても NGINX、CloudFront ともに可能なため、今後取り入れていきたいと考えています。もし HTTP コンテンツの圧縮設定を特に気にしたことがない方は一度確認してみてはいかがでしょうか?</p>
<h1 id="最後に">最後に</h1>
<p>メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しています。医療という分野においては、機微な情報を扱ったり診療を止めないようにするために、パフォーマンス・セキュリティ共に高いサービスレベルが求められます。興味を持った方がいらっしゃいましたら、まずは気軽に面談できればと思いますので、是非ご応募ください!</p>
<p>最後までお読みいただきありがとうございました。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- UI テストの自動化に Magic Pod を導入した話https://developer.medley.jp/entry/2021/01/15/180126https://developer.medley.jp/entry/2021/01/15/180126こんにちは。インキュベーション本部の QA エンジニアの米山です。主に CLINICS アプリの QA を担当しています。メドレーには 2020 年 8 月に入社しました。
今回は入社してまず行ったことの一つ、リグレッションテストの自動化と...Fri, 15 Jan 2021 09:01:26 GMT<p>こんにちは。インキュベーション本部の QA エンジニアの米山です。主に CLINICS アプリの QA を担当しています。メドレーには 2020 年 8 月に入社しました。</p>
<p>今回は入社してまず行ったことの一つ、リグレッションテストの自動化と、そのために導入した Magic Pod というツールについて、経緯や導入してみた結果をご紹介したいと思います。</p>
<h1 id="clinics-とは">CLINICS とは</h1>
<p>私の所属するチームで開発している<a href="https://clinics.medley.life/">CLINICS</a>というプロダクトはアプリでオンライン診療や、クリニック・病院から処方箋を発行してもらうことができ、オンライン上で診察からお薬の受け取りまで完結できるサービスです。
プラットフォームは iOS と Android のネイティブアプリ、それから同様のサービスを Web ブラウザからも利用することが出来ます。</p>
<h1 id="qaリリース周りの状況">QA/リリース周りの状況</h1>
<p>CLINICS の開発組織に QA エンジニアがジョインしたのは昨年(2020 年)ですが、サービス自体は 2016 年にローンチされています。</p>
<p>本組織ではリリース前に行うリグレッションテストについては、開発メンバを中心にチーム全体で行う文化があります。
アプリのリリースは隔週で行っており、その都度開発メンバ自身によってテストが行われていましたが、自動化された UI テストは存在していませんでした。</p>
<p>メドレーでは QA エンジニアがジョインして間もないため、やりたいこと・やるべきことは多岐にわたる中でまず何から着手するべきか検討しました。</p>
<p>QA プロセスの策定・改善から、新機能をリリースまで推進するための QA 活動もあり、並行して幾つか動いている中でテスト自動化をどのタイミングで、どうやってスタートするか悩みました。</p>
<h1 id="バリューから考える">バリューから考える</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210115/20210115142126.png" alt="20210115142126.png">
<p>メドレーのバリューはこの三つです。これらのバリュー視点で考えてみました。</p>
<p>「凡事徹底」として、リリース前のリグレッションテストをしっかり行うことは当然のこととして考えられます。</p>
<p>「中央突破」の視点ではどうかと考えると、やはりテストプロセスにおいて、特にリリース毎に繰り返し作業となるリグレッションテストを自動化することは王道であり、ベストプラクティスの一つだと考えられます。
そのため自動化は優先度高く進めるべきではあります。</p>
<p>残る一つ「未来志向」については、例えば 1~2 年後やその先を考えて、リグレッションテストが自動化されているべきか否かで言うとやはり Yes です。</p>
<p>また、別の観点として、現在はわずか 2 人の QA エンジニアに対して、複数のプロダクトが存在している状況で、QA エンジニアがアサインされていないプロダクトも多くあります。</p>
<p>私自身も昨年 10 月からアプリ・基盤チームに異動したこともあり、今後についてもまた体制が変わっていくことは十分に考えられました。</p>
<p>そんな状況下では、仮に UI テストを自動化した環境を用意できたとして、その後に担当者が不在になった場合も考慮しておく必要があります(自動テストにおいて、担当が不在になったことでメンテされなくなり形骸化するケースはよくある話です)。</p>
<p>そのため、仮に実装者が不在となった後でも誰かに引き継ぎやすく、またエンジニア以外でも運用できる環境が望ましいと考えました。そういった観点でツールとしては基本ノーコードでもメンテできる Magic Pod は有力な候補となりました。</p>
<p>これらをまとめると、以下のような結論に至りました。</p>
<ul>
<li>テストの自動化は推進した方が良い</li>
<li>ただし、他のメンバでもメンテしやすい環境を選定する</li>
</ul>
<p>ただし QA としてやるべき事が沢山ある中で、テスト自動化だけに専念できる状況ではありません。
そのためなるべく他タスクと並行して小コストで進められる事も重要な要素でした。</p>
<p>自動化された UI テストは全くない事や、他のテストの密度も鑑みると、なるべく早い段階で一定の自動テスト環境は用意したいという想いもありました。</p>
<p>これらの状況も踏まえ、ツールを選定・トライアルしてみた結果、Magic Pod を導入することに決めました。</p>
<h1 id="magic-pod-の紹介">Magic Pod の紹介</h1>
<p><a href="https://www.magic-pod.com/">Magic Pod</a>について、サービス自体の詳細は割愛しますが、端的にいうとクラウド環境かつ GUI から UI テストの実装及び実行を行うことができるツールです。</p>
<p>GUI で自動テストが実装できるツールだと、<a href="https://autify.com/ja">Autify</a>なども有名です。
Autify はブラウザ向けのツールですが、実装方法は Magic Pod とは少し異なり、操作をレコーディングしてテストシナリオが自動で生成される形が基本です。</p>
<p>一方、Magic Pod は以下のようにアプリの画面をまずキャプチャで取り込み、そこからテストで使いたい項目を選択し、シナリオにドラッグアンドドロップしていくことでテストシナリオを生成することができます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20210115/20210115141554.png" alt="20210115141554.png">
<p>ログインなど、複数のテストで使う部分は共通化しておきます。</p>
<p>テスト対象が iOS アプリであろうと、Android アプリやブラウザであろうと基本的に同じ I/F からテストの生成・メンテが出来ることは大きな強みの一つです。</p>
<p>また、テストで使用するフィールドの要素を選択可能なことも、状態変化に強いテストとする上での強みとなります。</p>
<p>例えば「調剤薬局名でさがす」というテキストフィールドに対して、そのテキストを使うのか、ID なのか、テキストフィールドなのか xpath なのかといった所です。</p>
<p>そのため、</p>
<ul>
<li>テキストが頻繁に変わるような場所(例えば日付など)ではテキストを使わない</li>
<li>アプリ内部でリファクタリングなどが動いている場合であれば逆に ID は変わる可能性が高いため、テキストで指定する</li>
</ul>
<p>UI テストを作り込む上では当然のことではありますが、上記のような工夫によりテストの成功率を上げることができます。</p>
<h1 id="導入してみて">導入してみて</h1>
<p>トライアル中は探りながらの部分はあったものの、慣れると実装工数は非常に短期間で実装でき、トータルでも iOS で 2<del>3 週間(オンボーディング含む)、Android の UI テストについては実質 2</del>3 日で基本的なテストシナリオの自動化を行う事ができました。</p>
<p>その後、運用しながら落ちやすいテストの改修を行ったり、運用が安定してからは CI にも連携しています。</p>
<p>UI テストの運用においては定期的に実行することは非常に重要なことですが、Magic Pod の場合、Bitrise では UI 上から設定でき、Circle CI に対してもドキュメントを参照しながら比較的容易に設定できます。</p>
<p>実際、昨年 1 クォーター運用してみて、幾つかのクラッシュをリリース前に検知してくれました。</p>
<p>また、私自身、過去には XCTest における UITest(<a href="https://developer.apple.com/documentation/xcode/testing_your_apps_in_xcode">Testing Your Apps in Xcode</a>)や<a href="https://appium.io/">Appium</a>を使って UI テストを運用していたため、以下ではそれら他ツールとの比較も含めて紹介してみたいと思います。</p>
<h2 id="実装コスト">実装コスト</h2>
<p>実装コストにも初期構築と、その後のメンテコストで分かれますが、他のツールと比較して、大きく異なるのは初期構築コストだと思います。</p>
<p>Magic Pod については環境構築コストは非常に低コストで行うことができます(基本的な部分は 1 日あれば十分だと思います)。
またテストのレポーティングやキャプチャ機能なども標準で付いていますので、この辺りも自前で頑張る必要はありません。</p>
<p>次にメンテコストですが、例えば XCUITest ではまずビルドを行い、debug して各ボタンなどの要素の ID などを確認し、それらを用いてコーディングしていました。
Magic Pod では一度アプリをアップロードして、スキャンすることで画面の要素を一括で取得でき、その中から操作したい要素を選択することができます。</p>
<p>そのためこちらもコストはだいぶ下がります。ただ、この部分については他のツールや言語でも慣れればそう時間はかからないのでもしかしたら大差ないかもしれません。</p>
<p>あえて言うと debug で ID を確認する手間が楽になる、実装したテストを試して実行するのが容易(ビルド待ちの時間がない)といった辺りでしょうか。</p>
<h2 id="運用コスト">運用コスト</h2>
<p>UI テストといえば Flaky なテスト(落ちたり落ちなかったりするテスト)に悩まされることは多いですが、運用してみると最初の内はそういったこともありましたが、現状ではほぼ起きていません。</p>
<p>これは Magic Pod に限った話ではありませんが、</p>
<ul>
<li>クラウド上で実行されることで環境要因で落ちることは稀</li>
<li>落ちた時には自動でリトライされる</li>
<li>ビルドも CI 上で実行している</li>
<li>実行はメンバが活動していない時間帯に行っている</li>
</ul>
<p>といった辺りが要因かと思います。</p>
<p>また Magic Pod のようなツールを使っている場合に助かる部分としては、Xcode など、UI テストに必要なツールのアップデートに対するメンテが不要ということも挙げられます。</p>
<h2 id="逆に少し辛い所">逆に少し辛い所</h2>
<p>ここまで Magic Pod の良い部分を多く書きましたが、逆にこのような GUI でのテストツールを使うことで少しやり辛い点も紹介しておきたいと思います。</p>
<h4 id="1-テストコードのレビュー">1. テストコードのレビュー</h4>
<p>テストコード(ケース)は Magic Pod 上で管理されているため、PR レビューなどのプロセスを行うことができません。
そのため、ケースの修正に対して、反映させる前にレビューしてもらいたい場合は、テストケースをコピーしてから編集するなど少し工夫が必要になるかと思います。</p>
<p>現状では困ることはありませんが、複数人で同一のプロジェクトに対して運用したい場合は少し煩雑になりそうです。</p>
<h4 id="2-テストコードの管理">2. テストコードの管理</h4>
<p>自動テストにおいて、テスト結果に影響が出る仕様変更が入るような場合、仕様変更に対するテストコードの修正は開発と並行して用意しておき、プロダクトへの変更がマージされるタイミングで同時にテストコードの修正もマージしたいケースがあります。</p>
<p>Magic Pod では GitHub 上でテストコードを管理していないため、このようなケースへの対応を自動で行うことが難しく、予めテストケースを分けて用意しておき、実装がマージされた後に手動で置き換えるか、マージされた後に影響のあるテストケースを修正するといった手動でのプロセスが必要になります。</p>
<p>現時点で気になったのは上記の 2 点ですが、これらも今後改善されていく可能性は大いにありますし、プロセスの中での工夫次第で対処も可能かと思います。</p>
<h2 id="その他">その他</h2>
<p>基本的に UI テストを自動化する上で気をつけるべきことやアンチパターンはどんなツールを使っても同じです。
他のツールでは難しいことが、このツールでは実現出来るということも稀で、時にはプロダクト側で手を入れる必要もあります。
どんなツールであれ、何かしら工夫すれば達成出来ることが多いため、違いが出るのは実装や運用、オンボーディング等のコスト部分が最も大きいのではないかと感じています。</p>
<h1 id="周囲のサポート">周囲のサポート</h1>
<p>テスト自動化を行う場合(だけではないですが)、周囲の理解を得ることは大事な部分ですが、チームメンバは皆前向きで興味を持ってくれて進めやすい環境でした。</p>
<p>特に CI 連携の部分では iOS/Android の開発の方にもサポートしていただき大変助かりました。</p>
<p>そして Magic Pod については、数年前から運用している株式会社ノハナの武田さんにも事前に話を伺ったり、オンボーディング中は質問させていただいたりしました(ありがとうございました!)。</p>
<p>また Magic Pod の伊藤様には導入時からトラブルシューティングに多大なサポートをいただいています。</p>
<p>Circle CI に入れ込む際には、ちょっと詰まった点があり伊藤様とメールでやり取りしていたのですが、その日のうちに<a href="https://www.trident-qa.com/magic-pod-circleci/">ドキュメント</a>がアップされたり、
とある環境下で不明なエラーが出ていて相談した際には、ストアから CLINICS アプリをダウンロードして試していただいたり、とにかくいつも迅速かつご丁寧な対応が印象的でした。</p>
<p>まだ QA チームもないような少人数の状況では、こういったトラブルに対して相談でき、共に解決法を探れる方がいるという意味でも非常に心強いです。</p>
<h1 id="今後について">今後について</h1>
<p>アプリの UI テストについて、改善していきたいことはまだまだ沢山あるのですが、現状でも基本的なテストは用意できているため、じっくり腰を据えて改善していきたいと考えています。</p>
<p>また現在はブラウザのテスト自動化を進めています。メドレーの CLINICS 以外のプロダクトの多くは Web ブラウザをプラットフォームとしているため、Web についてはプロダクトを跨いだ活動も行っていければと考えています。</p>
<p>長くなりましたが、最後までお読みいただきありがとうございました。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- ChatOps な稟議ワークフローシステムを開発しました https://developer.medley.jp/entry/2020/12/25/180058https://developer.medley.jp/entry/2020/12/25/180058はじめに
こんにちは。コーポレートエンジニアの溝口です。
メドレーでは、今年 7 月に稟議ワークフローシステムを導入しました。
詳細は「システム概要」の章でご紹介しますが、システムの全体像としては以下のようになっております。
ワークフロー...Fri, 25 Dec 2020 09:00:58 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは。コーポレートエンジニアの溝口です。
メドレーでは、今年 7 月に稟議ワークフローシステムを導入しました。
詳細は「システム概要」の章でご紹介しますが、システムの全体像としては以下のようになっております。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201225/20201225155753.png" alt="20201225155753.png">
<p>ワークフローシステムと聞かれたら、どんなシステムを思い浮かべますか?
申請者がシステムで申請すると、予め定められた承認者へ承認依頼がメールで通知され、申請内容の確認及び承認のためにシステムへログインする、という流れがよくあるワークフローシステムではないかなと思います。</p>
<p>我々はコーポレート部門として「徹底的に合理性を追求した組織基盤や、仕掛けづくりを行っていく」ことを目指しています。故に、メドレーの稟議ワークフローシステムにおいても<strong>便利</strong>で<strong>合理的</strong>なシステムを目指して開発を行いました。今回<strong>ChatOps</strong>の概念を取り入れることで、一般的なワークフローシステムよりも洗練されたシステムを構築できたかなと思います。
本稿ではシステム概要及び、裏側の仕組みをご紹介していきます。
最後までお付き合いいただければ幸いです。</p>
<h1 id="chatops-とは">ChatOps とは</h1>
<p>ChatOps とは「チャットサービス(Chat)をベースとして、システム運用(Ops)を行う」という意味です。ざっくり書くと「システムから Chat へメッセージを飛ばし、次のアクションが同じ Chat 画面で開始できる」というものとなります。(下記フローはあくまで一例です)</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201225/20201225155817.png" alt="20201225155817.png">
<p>ChatOps には以下のメリットがあると考えています。</p>
<ol>
<li>
<p>常に立ち上げているツールという共通インターフェースである</p>
</li>
<li>
<p>インタラクティブなコミュニケーションにつながり、スピーディである</p>
</li>
<li>
<p>共有しやすく、記録に残しやすい</p>
</li>
</ol>
<p>本稿では、詳しく説明はしませんが、興味がある方は事例等を解説しているサイトもあるので、是非探してみてください。</p>
<h1 id="なぜ-chatops-なのか">なぜ ChatOps なのか</h1>
<p>稟議申請においては
承認者「これ値引きしてもらって ×× 円になったはずだけど、金額間違ってない?」
申請者「すいません、変更し忘れました。差戻しお願いします」
などのコミュニケーションが度々発生します。
通常のシステムであれば、確認事項がある際はシステム内のコミュニケーション機能を使う、もしくは、Chat に URL や稟議番号を転記して確認のためのコミュニケーションを取ることが想定されます。</p>
<p>メドレー内の業務コミュニケーションは<strong>Slack</strong>上で殆ど完結しています。
Slack ではない他の場所で会話が発生すると情報が分散しますし、Slack に URL を転記するといった行為や、別システムへのログインなども非効率です。
そこで、<strong>共通インターフェースの Chat を中心にシステム構築する= ChatOps</strong>を採用し、稟議ワークフローを構築してみようと考えました。結果、稟議ワークフローシステムの情報を Slack へ連携し、稟議におけるコミュニケーションは Slack に集約、承認行為も Slack 上で可能、というシステムを構築することができました。</p>
<h1 id="システム概要">システム概要</h1>
<h2 id="申請">申請</h2>
<p>申請者は<a href="https://www.teamspirit.com/ja-jp/">TeamSpirit</a>上で稟議内容を記入し、稟議申請を行います。 TeamSpirit とは、勤怠管理や工数管理、経費精算などを管理できるクラウドサービスです。Salesforce をプラットフォームとして採用しており、アイデア次第でいろいろなカスタマイズが可能です。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201225/20201225155854.png" alt="20201225155854.png">
<p>Slack から申請できるようにするのが ChatOps のあるべき姿かもしれませんが、過去の申請からコピーしたい、申請種別ごとに入力する項目が異なる等の要件を考慮し、TeamSpirit から申請するように設計しました。申請の導線については、今後もよりよい仕組みに磨き上げていきたいと考えています。</p>
<h2 id="承認">承認</h2>
<p>申請者が「稟議申請」ボタンを押下すると、Slack の稟議チャンネルに申請内容及び添付ファイルが自動投稿されます。
承認者は申請内容に問題がなければ、投稿に配置されているボタンを利用して承認・差戻しが行えます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201225/20201225155917.png" alt="20201225155917.png">
<p>承認者は稟議ワークフローシステムへアクセスすることなく、Slack で承認行為が完結できます。稟議内容において確認事項がある場合には Slack の投稿スレッドで申請者と質疑応答のやり取りができ、承認・差戻しの判断に必要なコミュニケーションが行えます。</p>
<h2 id="後続のアクション">後続のアクション</h2>
<p>承認後には、
・申請者に承認 or 差戻し結果を Slack の DM(ダイレクトメッセージ)で通知する</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201225/20201225160246.png" alt="20201225160246.png">
<br clear="all">
<ul>
<li>後続の担当者へ Slack で通知する</li>
<li>(法務押印などの)承認後タスクを作成し担当者に通知する
等、後続のアクションへつながっていく仕組みも用意しました。</li>
</ul>
<h1 id="システムの裏側">システムの裏側</h1>
<h2 id="入力インターフェース">入力インターフェース</h2>
<p>入力画面は、TeamSpirit で標準提供されている<a href="https://www.teamspirit.com/ja-jp/blog/entry/post-31.html">稟議オブジェクト</a>を利用しました。入力項目は標準で用意されているコンポーネントを利用し、メドレー独自で定義しています。承認プロセスを定義すれば、Slack を使わずに TeamSpirit のみでも運用は可能です。</p>
<h2 id="slack-通知">Slack 通知</h2>
<p>Salesforce の標準機能と<a href="https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_intro_what_is_apex.htm">Apex</a> を用いた Script 処理を使って Slack 通知をしています。</p>
<blockquote>
<p>Apex とは、Salesforce 内で利用するビジネスロジック用のオブジェクト指向のプログラミング言語(ほぼ Java)のことです。</p>
</blockquote>
<p>Slack 通知までの大きな流れは以下です。</p>
<ol>
<li>稟議申請ボタンを押したタイミングでステータス項目を「未申請」から「申請中」へ変更</li>
<li>プロセスビルダーにてステータス項目が「申請中」になったことを検知して Apex をコール</li>
<li>Apex 内で申請情報や承認者情報の取得</li>
<li>Slack API をコールし、Slack へ投稿</li>
</ol>
<p>1~4 のプロセスを詳しく見ていきます。</p>
<h3 id="1-稟議申請ボタンを押したタイミングでステータス項目を未申請から申請中へ変更">1. 稟議申請ボタンを押したタイミングでステータス項目を「未申請」から「申請中」へ変更</h3>
<p>申請者が「稟議申請」ボタンを押したタイミングで承認プロセスを走らせます。
申請時のアクションとして、 ステータス「申請中」とします。ステータスが変わる毎に処理を走らせているので、ステータス定義は一つ肝になります。</p>
<h3 id="2-プロセスビルダーにてステータス項目が申請中になったことを検知して-apex-をコール">2. プロセスビルダーにてステータス項目が「申請中」になったことを検知して Apex をコール</h3>
<p>プロセスビルダーを利用することで「稟議レコードを作成または編集したとき」に何らかの処理を実施することが可能です。今回は、ステータスが「申請中」になった場合に Apex をコールする、という処理にしています。</p>
<h3 id="3-apex-内で申請情報や承認者情報の取得">3. Apex 内で申請情報や承認者情報の取得</h3>
<p>通知に必要な情報を揃えるため、Apex の処理では稟議オブジェクトの申請情報と合わせて次の承認者情報も取得しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="apex"><code><span class="line"><span style="color:#569CD6">String</span><span style="color:#9CDCFE"> ownerId</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">p</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">OwnerId</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#6A9955">//申請者のユーザ名を取得</span></span>
<span class="line"><span style="color:#569CD6">String</span><span style="color:#9CDCFE"> applicant</span><span style="color:#D4D4D4"> = [SELECT </span><span style="color:#569CD6">Username</span><span style="color:#D4D4D4"> FROM </span><span style="color:#569CD6">User</span><span style="color:#D4D4D4"> WHERE </span><span style="color:#569CD6">Id</span><span style="color:#D4D4D4"> = : </span><span style="color:#9CDCFE">ownerId</span><span style="color:#D4D4D4">].</span><span style="color:#9CDCFE">Username</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#6A9955">//承認プロセスのレコード取得</span></span>
<span class="line"><span style="color:#569CD6">String</span><span style="color:#9CDCFE"> processInstanceId</span><span style="color:#D4D4D4"> = [SELECT </span><span style="color:#569CD6">Id</span><span style="color:#D4D4D4"> FROM </span><span style="color:#569CD6">ProcessInstance</span><span style="color:#D4D4D4"> WHERE </span><span style="color:#569CD6">TargetObjectId</span><span style="color:#D4D4D4"> = : </span><span style="color:#9CDCFE">p</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">Id</span><span style="color:#D4D4D4"> ORDER BY </span><span style="color:#569CD6">CreatedDate</span><span style="color:#D4D4D4"> DESC </span><span style="color:#569CD6">limit</span><span style="color:#B5CEA8"> 1</span><span style="color:#D4D4D4">].</span><span style="color:#9CDCFE">Id</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#6A9955">//承認者の ID を取得</span></span>
<span class="line"><span style="color:#569CD6">String</span><span style="color:#9CDCFE"> approveId</span><span style="color:#D4D4D4"> = [SELECT </span><span style="color:#569CD6">OriginalActorId</span><span style="color:#D4D4D4"> FROM </span><span style="color:#569CD6">ProcessInstanceWorkitem</span><span style="color:#D4D4D4"> WHERE </span><span style="color:#569CD6">ProcessInstanceId</span><span style="color:#D4D4D4"> = : </span><span style="color:#9CDCFE">processInstanceId</span><span style="color:#D4D4D4">].</span><span style="color:#9CDCFE">OriginalActorId</span><span style="color:#D4D4D4">;</span></span></code></pre>
<h3 id="4-slack-api-をコールしslack-へ投稿">4. Slack API をコールし、Slack へ投稿</h3>
<p>Apex が取得した情報をもとに、Slack に投稿します。
稟議内容を記載し、申請者・承認者に対してメンションされるようにユーザ名も記載します。
また、今回は承認者用にインタラクティブボタンを配置する必要があったので、<a href="https://api.slack.com/block-kit">Block Kit</a>を利用し、ボタン付きメッセージを作成しました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#9CDCFE"> "text"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"hoge"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "blocks"</span><span style="color:#D4D4D4">: [</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"section"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "text"</span><span style="color:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"mrkdwn"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "text"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"fuga"</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#F44747"> ...</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"actions"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "elements"</span><span style="color:#D4D4D4">: [</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"button"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "text"</span><span style="color:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"plain_text"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "text"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"承認"</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#9CDCFE"> "value"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"Approve"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "style"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"primary"</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"button"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "text"</span><span style="color:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"plain_text"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "text"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"差戻し"</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#9CDCFE"> "value"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"Reject"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "style"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"danger"</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> ]</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> ]</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>TeamSpirit(Salesforce)→Slack への投稿は開発において苦労したポイントの一つです。</p>
<h2 id="slack-からのアクション">Slack からのアクション</h2>
<p>Slack の投稿に埋め込んでいるボタンがクリックされた際は、Lambda を経由して TeamSpirit(Salesforce)の RestAPI をコールし、承認処理を実行しています。
また承認後は、ボタンを「承認」スタンプに置き換えています。</p>
<h1 id="開発を終えて">開発を終えて</h1>
<p>稟議ワークフローシステムを導入するにあたり、ChatOps の概念を取り入れ Slack に連携する業務システムを構築しました。
承認者からは「Slack で承認やコメントができ、社外からでもすぐに対応できるので便利」「Salesforce-Slack 連携は他にも活用できるので是非やっていこう」などのコメントをいただきました。また、承認後にもスレッドにて、「振込お願いします」「物品届きました」等のやりとりも行っており、情報が Slack に集約されていく狙い通りの運用になったかと思っています。</p>
<p>Chat サービスを利用している会社では、今回ご紹介した ChatOps は業務効率化するにあたり、有効な手法になるのではないでしょうか。もちろん、すべて Chat に連携すればよいというものでもなく、しっかり設計や運用検討を行う必要があります。
今後は ChatOps に限らず業務効率化につながるものはどんどんやっていきたいと考えています。</p>
<h1 id="さいごに">さいごに</h1>
<p>メドレーのコーポレート部門では「徹底的に合理性を追求した組織基盤や、仕掛けづくりを行っていく」ことを目指して、業務効率改善のための開発を推進しています。面白そう!と感じた方、メドレーでどんどん改善してみたい!と思っていただけた方は、ぜひ弊社採用ページからご応募お願いします!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>最後まで読んでいただきありがとうございました。</p>medley
- CloudFront のエラー監視精度を上げた話https://developer.medley.jp/entry/2020/12/23/175428https://developer.medley.jp/entry/2020/12/23/175428はじめに
はじめまして、メドレー新卒入社 2 年目の森川です。
インフラ経験がまだ 4 ヶ月ほどの未熟者ですが、AWS 認定資格クラウドプラクティショナー の試験に合格することができました。上位の資格取得に向けて今後も勉強していきます。
先...Wed, 23 Dec 2020 08:54:28 GMT<h1 id="はじめに">はじめに</h1>
<p>はじめまして、メドレー新卒入社 2 年目の森川です。</p>
<p>インフラ経験がまだ 4 ヶ月ほどの未熟者ですが、<a href="https://aws.amazon.com/jp/certification/certified-cloud-practitioner/">AWS 認定資格クラウドプラクティショナー</a> の試験に合格することができました。上位の資格取得に向けて今後も勉強していきます。</p>
<p>先日私が担当させていただいた CloudFront のアラート改善について、問題の原因と対応方法を本記事で書かせていただきます。</p>
<p>よろしければお付き合いください。</p>
<h1 id="背景と問題">背景と問題</h1>
<p>弊社が運営しているプロダクトの一つ <a href="https://job-medley.com/">ジョブメドレー</a> ではインフラ環境に AWS を利用しています。</p>
<p>監視には CloudWatch や Datadog などを使用しています。サービスの異常を検知するための設定のひとつに、CloudFront のエラーレスポンス増加を検知するためのアラート通知があります。</p>
<p>CloudFront が返すレスポンスのうち、特定の時間範囲の中で 4xx, 5xx 系のエラーを返した割合が閾値を超過したことを検知して、CloudWatch アラームから Lambda を通して Slack に通知を行っています。</p>
<p>ところが、ある頃を境に CloudFront での 4xx 系エラーレスポンスの発生割合が増加し、アラートの通知頻度が想定以上に高くなってしまいました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201221/20201221172048.png" alt="20201221172048.png">
<h1 id="原因">原因</h1>
<p>調査を行ったところ、刷新した社内システムにて以下 2 つの原因でアラートが発生していることが分かりました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201221/20201221172100.png" alt="20201221172100.png">
<h2 id="原因-1-社外サービスからのアクセスでアラートが発生">原因 1. 社外サービスからのアクセスでアラートが発生</h2>
<p>CloudFront のログを確認したところ、社外サービス(Slack, Google スプレッドシートなど)からのアクセスに対してステータスコード 403 を返しているレスポンスログが数多く記録されていました。</p>
<p>これらのサービスに弊社の社内管理システムの URL がポストされると、プレビューを表示するためのリクエストが送信されますが、この時のリクエストが社外からのアクセスとして WAF で制限されていました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201221/20201221172124.png" alt="20201221172124.png">
<p>インフラ刷新前から現在まで稼働している CloudFront のログも確認したところ、こちらでも同様のエラーレスポンスが発生していることが分かりました。しかし、エラー割合増加のアラートが頻発することは現在でもほとんどありません。</p>
<p>以前はジョブメドレーが持つシステム全体へのアクセスをひとつの CloudFront で処理していたため、アラート通知の割合として計算する際の母数が大きく、社外からのアクセスによるエラーが発生していても、その割合が閾値を超過することが少なかったからだと考えられます。</p>
<p>インフラ構成を刷新したことをきっかけに、これまで目立っていなかった社外からのアクセスという問題が表面化してきたのです。</p>
<h2 id="原因-2-利用者が少ない時間にエラーレートが高くなりアラートが発生">原因 2. 利用者が少ない時間にエラーレートが高くなりアラートが発生</h2>
<p>CloudWatch アラームでは、一定期間内でのレスポンスのうち、4xx, 5xx 系のエラーごとにその割合が閾値を超過したことを検知してアラートを発生させる設定としていました。</p>
<p>しかし、深夜など利用者が少ない時間に一度でもエラーが発生すると、その割合が跳ね上がってしまうことでアラート発生頻度が増加し、誤検知と言える状態になっていました。</p>
<p>以下の画像では、4xx 系エラーの割合が夜間に 100%となっている箇所が確認できます。(表示時間は UTC です)</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201221/20201221172140.png" alt="20201221172140.png">
<h1 id="対応方法">対応方法</h1>
<p>2 つの原因に対し、それぞれ対応を行いました。</p>
<h2 id="対応-1-特定の社外サービスからのアクセスをエラー検知の対象外とする">対応 1. 特定の社外サービスからのアクセスをエラー検知の対象外とする</h2>
<p>各サービスの設定により、プレビュー表示によるアクセスを停止させる選択肢が考えられます。しかし、該当するサービスすべてに設定を行うのは難しく、管理も複雑になりそうです。</p>
<p>そこで、特定の社外サービスからのアクセスを <strong>エラー検知の対象外とする</strong> 方針で対応を行いました。</p>
<p>ログのすべてを CloudWatch アラームの評価対象としていたために、誤検知と言えるアラートが発生しているのが現状です。したがって、評価させたいログだけに絞り CloudWatch で評価させることができれば解決が図れます。今回であれば、特定のユーザーエージェントや IP アドレスなどを除外して CloudWatch に渡すという処理が求められます。</p>
<p>その実現のため、今回新たに作成したのが Lambda の関数です。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201221/20201221172156.png" alt="20201221172156.png">
<p>S3 に CloudFront のログが保存されたことをトリガーに Lambda を起動させるように設定しました。</p>
<p>ログごとに記録されているリクエスト元のユーザーエージェントや IP アドレスなどを確認し、除外対象かどうかを判定します。</p>
<p>そうして選別を通過したログを今度はステータスコードの 5 つのクラス(1xx, 2xx, 3xx, 4xx, 5xx 系)ごとに振り分けます。</p>
<p>ただし、CloudFront ではステータスコードに <code>000</code> が入ることがあります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201221/20201221172213.png" alt="20201221172213.png">
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html
<p>ステータスコード <code>000</code> はアラートで検知したところで対応できることが特にないため、検知対象から除外する方針としました。</p>
<p>(S3 のログを直接確認すると <code>000</code> なのですが、Athena でログを確認すると <code>0</code> で表示されるため、少しハマりました)</p>
<p>こういった意図しない値がステータスコードに含まれていた場合などを検知できるようにするため、5 つのクラス以外の値が含まれていた場合に <code>UNKNOWN_STATUS_CODE</code> なクラスとして分類するようにしました。</p>
<p>必要なものに絞ったログを 6 つのステータスパターンに分け、それぞれの件数を CloudWatch メトリクスへ PUT させます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201221/20201221172229.png" alt="20201221172229.png">
<p>ここまでが Lambda の仕事となります。</p>
<p>各ステータスのログの件数を CloudWatch メトリクスで確認できるようになったので、レスポンス全体における 4xx, 5xx 系エラーの割合が算出できます。これを元に閾値を設定し、以前のようなアラートを作成することができました。</p>
<h2 id="対応-2-cloudwatch-アラームの検知ルールを調整する">対応 2. CloudWatch アラームの検知ルールを調整する</h2>
<p>利用者が少ない時間にエラーレートが高くなりアラートが発生する件については、
<strong>CloudWatch アラームの検知ルールを調整</strong> することによって対応しました。</p>
<p>一定期間でのエラー数に閾値を定め、超過した際にアラートを通知するように変更しました。つまり、割合ではなく絶対数で判断させるようにしています。</p>
<p>以下の画像の緑色のグラフが新たな検知ルールで参照するものとなります。橙色で示しているのが 4xx 系エラーの割合ですが、これが 100%となっている箇所においても新たな検知ルールには反応していないことが分かります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201221/20201221172242.png" alt="20201221172242.png">
<h1 id="対応を終えて">対応を終えて</h1>
<p>Lambda を用いた集計処理の作成と、アラートの検知ルールの調整を行うことで、CloudFront のエラー監視精度を向上させることができました。</p>
<p>以前は頻繁にアラートがあがっていましたが、対応後はすっかり落ち着きを見せています。</p>
<p>システムの安定稼働を実現するためにも、適切にアラートを検知できるように今後も改善を図っていきたいと思います。</p>
<p>今回の課題に対する解決手段としてはシンプルな対応であったかとは思いますが、私には実りの多い紆余曲折な経験となりました。</p>
<p>AWS の基本的なサービスの連携を学ぶことができたことに加え、新たに作成する AWS のサービスの課金額の試算や、実行計画を定めてからの実装など事前準備を意識して取り組むことができました。恵まれた環境の中、日々学ばせていただいております。</p>
<h1 id="さいごに">さいごに</h1>
<p>メドレーでは「医療ヘルスケアの未来をつくる」というミッションを掲げ、各プロダクトの開発・運営が進められています。</p>
<p>エンジニア・デザイナーをはじめ多くのポジションで新たなメンバーを募集しています。ご興味をお持ちいただけた方は、ぜひお気軽にお話しさせていただければと思います!</p>
<p>ここまでお付き合いいただき、ありがとうございました。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- AWS MediaConvert と hls.js で動画配信サービスを構築しましたhttps://developer.medley.jp/entry/2020/11/27/170006https://developer.medley.jp/entry/2020/11/27/170006こんにちは、第一開発グループの矢野です。ジョブメドレー開発エンジニアとして、主にバックエンドを担当しています。
直近では、ジョブメドレーが先月リリースした 「動画選考」 機能の開発プロジェクトに携わっており、動画ファイルのアップロード/配信...Fri, 27 Nov 2020 08:00:06 GMT<p>こんにちは、第一開発グループの矢野です。ジョブメドレー開発エンジニアとして、主にバックエンドを担当しています。</p>
<p>直近では、ジョブメドレーが先月リリースした <strong>「動画選考」</strong> 機能の開発プロジェクトに携わっており、動画ファイルのアップロード/配信環境の設計・実装を行っていました。</p>
<p>今回のブログでは、この「動画選考」機能の開発に利用した <strong>AWS Elemental MediaConvert</strong> サービスと、<strong>hls.js</strong> という OSS ライブラリについて紹介したいと思います。</p>
<h1 id="ジョブメドレーの動画選考機能">ジョブメドレーの「動画選考」機能</h1>
<p>はじめに、今回リリースした「動画選考」機能について概要を紹介します。</p>
<blockquote>
<p>新型コロナウイルス感染拡大によって、対面での面接に不安を感じたり、公共交通機関の利用が難しくなったりすることにより、満足な転職活動ができなくなっている方もいらっしゃるかと思います。
このような課題を解決するために、ジョブメドレーではリアルタイムにオンラインで面接を行う「WEB 面接」と、事業者があらかじめ設定した質問に対して応募者が動画で回答を送る「動画選考」の 2 つの機能を提供開始いたしました。</p>
<p>ref. <a href="https://job-medley.com/release/70/">WEB 面接・動画選考機能のリリースのお知らせ</a></p>
</blockquote>
<p>動画選考(動画面接)は、近年増加傾向にあるオンライン選考の一種です。一般的に、求職者 / 就活生が PC ・スマートフォン等のカメラで、予め用意された設問に応じて動画を撮影し、企業に送ることで選考を行います。</p>
<blockquote>
<p>ref. <a href="https://job-medley.com/tips/detail/1151/#i3-1">WEB 面接・動画選考とは? 実施の流れ、使用ツール、マナー、注意点などを徹底解説!</a></p>
</blockquote>
<p>私たちジョブメドレーの動画選考では、事業所があらかじめ設定した質問に対して、求職者が回答動画を提出することができます。事業所も求職者も、動画で質問・回答を送ることで、書類だけでは伝わらない雰囲気や強みを相手に伝えることができます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201126/20201126150024.png" alt="20201126150024.png">
<blockquote>
<p><a href="https://job-medley.com/release/70/">WEB 面接・動画選考機能のリリースのお知らせ</a></p>
</blockquote>
<h1 id="動画配信サービスの設計ポイント">動画配信サービスの設計ポイント</h1>
<p>Web アプリでこのような動画配信サービスを開発する場合、「ユーザによる動画アップロード環境」と「ユーザへの動画の配信・再生環境」を提供する必要があります。</p>
<p>ジョブメドレーで扱う動画は一般公開されるものではなく、公開条件も複雑です。</p>
<p>よって今回は、この「動画アップロード/配信環境」を自サービス内に構築する方針をとり、以下のような動画まわりの設計ポイントについて検討・技術選定を行うことにしました。</p>
<p>(もちろん、要件によっては YouTube や、法人向け動画配信プラットフォームを契約した方が手軽な場合もあるかと思います)</p>
<ul>
<li>動画の録画・撮影
<ul>
<li>サポートしたい動画ファイルのフォーマットをどうするか</li>
<li>Web アプリ内に録画機能を設けるか</li>
</ul>
</li>
<li>動画のアップロード(ストレージ)
<ul>
<li>動画ファイルのバリデーションで「動画ファイルの解析」を行うか</li>
<li>動画ファイルのアップロード先(ストレージ)をどこにするか</li>
</ul>
</li>
<li><strong>動画のエンコード</strong>
<ul>
<li>動画ファイルのエンコード形式(H.264、HLS 等)をどうするか</li>
<li>非同期エンコードの場合、ステータス検知・エラーハンドリングをどうするか</li>
</ul>
</li>
<li>動画の配信(ダウンロード)
<ul>
<li>配信形式(ダウンロード/ストリーミング)をどうするか</li>
<li>暗号化をする場合、復号をどのように行うか</li>
<li>動画ファイルの公開方法(アクセス制限)をどうするか</li>
</ul>
</li>
<li><strong>動画の再生</strong>
<ul>
<li>Web ページ上で再生させるのか、その場合の表示・再生制御をどうするか</li>
<li>ブラウザサポートをどこまでにするか、非対応・エラー時の制御をどうするか</li>
</ul>
</li>
</ul>
<p>今回は、上記の太字で記載した <strong>「動画のエンコード」に MediaConvert</strong> を、<strong>「動画の再生」に hls.js</strong> をそれぞれ採用しています。</p>
<p>各項の詳細は省きますが、全体を通して大まかに、以下のフローで「動画アップロード → エンコード(変換)→ 配信・再生」を実現することにしました。</p>
<ol>
<li>ブラウザから Ajax で動画を S3 へアップロードする</li>
<li>MediaConvert が動画を HLS 形式にエンコード(変換)する</li>
<li>ブラウザで hls.js を使い動画を CloudFront からストリーミング形式で受信、再生する</li>
</ol>
<p>今回はこの「動画アップロード → エンコード(変換)→ 配信・再生」に焦点を絞り、MediaConvert と hls.js をどのように使ったのかを紹介します。</p>
<h1 id="mediaconvert-による-hls-エンコード">MediaConvert による HLS エンコード</h1>
<p>AWS Elemental MediaConvert は、S3 との親和性が高いファイルベースの動画変換サービスです。自前で ffmpeg などを使って動画エンコードサーバを構築・管理することなく、スケーラブルな動画変換処理を手軽にシステムに組み込むことができます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201126/20201126150358.png" alt="20201126150358.png">
<blockquote>
<p>ref. <a href="https://aws.amazon.com/jp/mediaconvert/">AWS Elemental MediaConvert</a></p>
</blockquote>
<p>料金は出力する動画の再生時間に応じた従量課金です。AWS コンソールから GUI ベースでエンコード設定を作成したり、ジョブ(エンコード処理)を登録することができます。</p>
<p>また、他 AWS サービス同様に API が提供されており、AWS CLI や各言語の SDK を使ってプログラムからエンコード処理を登録することができ、システム連携も容易です。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A9955"># CLI でエンコードジョブを登録する例</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> aws</span><span style="color:#569CD6"> --endpoint-url</span><span style="color:#CE9178"> https://abcd1234.mediaconvert.region-name-1.amazonaws.com</span><span style="color:#569CD6"> --region</span><span style="color:#CE9178"> region-name-1</span><span style="color:#CE9178"> mediaconvert</span><span style="color:#CE9178"> create-job</span><span style="color:#569CD6"> --cli-input-json</span><span style="color:#CE9178"> file://~/job.json</span></span></code></pre>
<p>上記 CLI コマンドで下のようなエンコード設定を記載した JSON を使いジョブを作成すると、S3 上の動画ファイルをサクッとエンコードしてくれます。ジョブはキューイングされ、内部で並列処理されるため、大量のエンコード要求にも簡単に応じることができます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#F44747"> ...</span></span>
<span class="line"><span style="color:#9CDCFE"> "Settings"</span><span style="color:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#9CDCFE"> "Inputs"</span><span style="color:#D4D4D4">: [</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#F44747"> #</span><span style="color:#F44747"> 入力元の</span><span style="color:#F44747"> S3</span><span style="color:#F44747"> バケット上の動画ファイル</span><span style="color:#F44747"> key</span><span style="color:#F44747"> を指定</span></span>
<span class="line"><span style="color:#9CDCFE"> "FileInput"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"s3://testcontent/720/example_input_720p.mov"</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> ],</span></span>
<span class="line"><span style="color:#9CDCFE"> "OutputGroups"</span><span style="color:#D4D4D4">: [</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "OutputGroupSettings"</span><span style="color:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#9CDCFE"> "FileGroupSettings"</span><span style="color:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#F44747"> #</span><span style="color:#F44747"> 出力先の</span><span style="color:#F44747"> S3</span><span style="color:#F44747"> バケット</span><span style="color:#F44747"> key</span><span style="color:#F44747"> を指定</span></span>
<span class="line"><span style="color:#9CDCFE"> "Destination"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"s3://testbucket/output"</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#F44747"> #</span><span style="color:#F44747"> 動画・音声のエンコード設定を指定</span></span>
<span class="line"><span style="color:#F44747"> #</span><span style="color:#F44747"> ここで品質レベル毎に振り分けた複数のファイルを出力したり</span></span>
<span class="line"><span style="color:#F44747"> #</span><span style="color:#F44747"> サムネイル</span><span style="color:#F44747"> jpg</span><span style="color:#F44747"> を作成したりすることも可能</span></span>
<span class="line"><span style="color:#9CDCFE"> "Outputs"</span><span style="color:#D4D4D4">: [</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "VideoDescription"</span><span style="color:#D4D4D4">: { </span><span style="color:#F44747">…</span><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#9CDCFE"> "AudioDescriptions"</span><span style="color:#D4D4D4">: { </span><span style="color:#F44747">…</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> ]</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> ]</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<blockquote>
<p>ref. <a href="https://docs.aws.amazon.com/mediaconvert/latest/apireference/aws-cli.html">AWSCLI を使用した AWSElemental MediaConvertCreateJob の例</a></p>
</blockquote>
<p>エンコードが完了したジョブは、cron + SDK などで API を介して定期チェックする他に、CloudWatch Events によるイベント監視 → Lambda で処理するようなこともできます。</p>
<blockquote>
<p>ref. <a href="https://docs.aws.amazon.com/ja_jp/mediaconvert/latest/ug/cloudwatch_events.html">AWS Elemental MediaConvert による CloudWatch イベント の使用</a></p>
</blockquote>
<h2 id="なぜ動画を再エンコードするのか">なぜ動画を再エンコードするのか</h2>
<p>通常、ユーザからアップロードされる動画ファイルは、既に何らかのコーデックで圧縮され <code>.mp4</code> や <code>.mov</code> などのコンテナフォーマットに変換されていることが殆どです。</p>
<p>しかし Web ページで <code><video></code> タグを使いこれら動画ファイルを再生しようとした場合、 <strong>「動画フォーマットにブラウザが非対応だと再生できない」</strong> という環境依存問題があります。</p>
<p><em>ブラウザと動画フォーマットのサポート表</em></p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201126/20201126150540.png" alt="20201126150540.png">
<blockquote>
<p>ref. <a href="https://en.wikipedia.org/wiki/HTML5_video#Browser_support">HTML5 video > Browser support</a></p>
</blockquote>
<p>この問題に対応するため、多くの動画配信サービスでは、ユーザの動画を多くの環境で再生可能な MP4 コンテナフォーマット(H.264 + AAC コーデック)などの形式へ「再エンコード」しています。</p>
<p>ジョブメドレーの動画選考では上記目的に加えて、動画閲覧時の回線・端末負荷を抑える <strong>「HTTP ストリーミング形式」</strong> で動画を配信するために、アップロードされた動画を全て <strong>HLS 形式</strong> にエンコードしています。</p>
<h2 id="hls---http-live-streaming-形式">HLS - HTTP Live Streaming 形式</h2>
<p>HLS は HTTP Live Streaming の略で、Apple 社の開発した規格です。HTTP ベースのストリーミング通信プロトコルで、細切れにした MP4 動画ファイルを分割ダウンロードさせることで動画のストリーミング配信を実現しています。</p>
<p>HLS 形式にエンコードされた動画は <code>.ts</code> という分割されたメディアファイル群と、 <code>.m3u8</code> という、メディアファイルの取得先や秒数などを記載したテキストファイルで構成されます。</p>
<p><em>.m3u8 ファイルの例(マニフェストファイル、プレイリストファイルとも)</em></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>#EXTM3U</span></span>
<span class="line"><span>#EXT-X-TARGETDURATION:10</span></span>
<span class="line"><span>#EXT-X-VERSION:3</span></span>
<span class="line"><span>#EXT-X-MEDIA-SEQUENCE:0</span></span>
<span class="line"><span>#EXT-X-PLAYLIST-TYPE:VOD</span></span>
<span class="line"><span>#EXTINF:9.97663,</span></span>
<span class="line"><span>media-0.ts</span></span>
<span class="line"><span>#EXTINF:9.97663,</span></span>
<span class="line"><span>media-1.ts</span></span>
<span class="line"><span>#EXTINF:7.10710,</span></span>
<span class="line"><span>media-2.ts</span></span>
<span class="line"><span>#EXT-X-ENDLIST</span></span></code></pre>
<blockquote>
<p>ref. <a href="https://tools.ietf.org/html/rfc8216">RFC 8216: HTTP Live Streaming</a></p>
</blockquote>
<p>HLS は他のストリーミング形式と比較して、ライブ配信 / VOD どちらにも対応可能なこと、対応ブラウザが多いこと、専用の配信サーバを使わずに配信可能なことなどから、近年の動画配信サービスで広く利用されています。</p>
<p>Web エンジニアの視点から見ても、 HTTP ベースなためキャッシュや HTTPS 暗号化など、既存 Web 技術と掛け合わせることが想像しやすく、扱いやすい印象でした。</p>
<h2 id="mediaconvert-の-hls-エンコードジョブ設定">MediaConvert の HLS エンコードジョブ設定</h2>
<p>実際にプログラムから API 経由で HLS エンコードジョブを登録する際の設定 JSON は、以下のように GUI でジョブテンプレートを作成して確認することができます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201126/20201126150659.png" alt="20201126150659.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201126/20201126150726.png" alt="20201126150726.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201126/20201126150804.png" alt="20201126150804.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201126/20201126150827.png" alt="20201126150827.png">
<p>この「 JSON を表示」で、前述した CLI コマンド <code>mediaconvert create-job --cli-input-json</code> に渡せる JSON が表示されます。実装の際にはこちらを参考にしながら、<a href="https://docs.aws.amazon.com/ja_jp/mediaconvert/latest/ug/what-is.html">ユーザーガイド</a> を参照して利用したい機能にあわせた設定を追加していくことをおすすめします。</p>
<h2 id="注意点つまづいたポイント">注意点・つまづいたポイント</h2>
<ul>
<li>利用前に IAM で MediaConvert 用ロールの設定が必要です
<ul>
<li><a href="https://docs.aws.amazon.com/ja_jp/mediaconvert/latest/ug/iam-role.html">ステップ 3. IAM 権限の設定</a></li>
</ul>
</li>
<li>AWS コンソールの Service Quotas > AWS サービス > AWS Elemental MediaConvert から確認できますが、エンコード並行処理の同時実行数上限は 20 になっています
<ul>
<li>AWS ルートアカウント 1 つにつき 1 サービスが割当てられるので、これを増やしたい場合は申請が必要です</li>
</ul>
</li>
<li>エンコードジョブをキューイングする「キュー」を作成して、ジョブの登録時に選べるのですが、上記した「並行処理の同時実行数上限」はこの「キュー」毎に均等に振り分けられます
<ul>
<li>例えば「本番キュー」と「検証キュー」の 2 つのキューを作成した場合、それぞれの並行処理の同時実行数上限は 10 ずつになるので注意してください</li>
</ul>
</li>
<li>マニフェスト期間形式(Manifest duration format)に整数(INTEGER)を指定していると、iOS Safari で「動画の実際の再生時間と、再生プレイヤーのシークバーに表示される合計時間にズレが生じる」問題がありました
<ul>
<li>浮動小数点(FLOATING POINT)に変更することで対応しました、マニフェストファイルに出力される各 <code>.ts</code> ファイルの長さが、浮動小数点 → 整数に変換され切り上げられることでズレが生じているようでした</li>
</ul>
</li>
</ul>
<h1 id="hlsjs-による-hls-動画の再生制御">hls.js による HLS 動画の再生制御</h1>
<p>MediaConvert により HLS 形式にエンコードされた動画を、Web ブラウザで再生するために必要なのが、hls.js です。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201126/20201126151007.png" alt="20201126151007.png">
<blockquote>
<p>ref. <a href="https://github.com/video-dev/hls.js/">video-dev/hls.js</a></p>
</blockquote>
<p>実は HLS によるストリーミング配信は、現状 <strong>Safari など限られたブラウザでしかネイティブでサポートされていません。</strong></p>
<blockquote>
<p>ref. <a href="https://caniuse.com/http-live-streaming">https://caniuse.com/http-live-streaming</a></p>
</blockquote>
<p>この HLS 動画を Safari 以外の Google Chrome や IE11 などの主要ブラウザで再生可能にするため、hls.js が利用されています。内部的には、非対応ブラウザ環境において、ブラウザの <a href="https://w3c.github.io/media-source/">MediaSource 拡張</a> を使って HLS 動画を再生する仕様になっています。</p>
<h2 id="videojs-との比較">Video.js との比較</h2>
<p>似たようなライブラリに <a href="https://github.com/videojs/video.js">Video.js</a> というものもあり、導入を迷ったのですが …</p>
<ul>
<li>Video.js は UI もセットになった「 HLS に対応した再生プレイヤー」ライブラリ
<ul>
<li>HLS 対応以外にも、字幕や章分けなど機能が豊富</li>
</ul>
</li>
<li>hls.js はブラウザ標準の <code><video></code> タグで HLS に対応することだけを目的にした「 HLS クライアント」ライブラリ
<ul>
<li>UI などはなく、動画再生プレイヤーはブラウザ標準のまま</li>
</ul>
</li>
</ul>
<p>…と、上記のように hls.js の方がシンプルにやりたいことを実現できるため、今回は hls.js を採用しました。</p>
<p>GitHub のスター数は先発の Video.js の方が多いのですが、hls.js も開発は活発で、日本では <a href="https://gunosy.com/">グノシー</a> さん、世界的には <a href="https://www.ted.com/">TED</a> や <a href="https://twitter.com/">Twitter</a> でも採用されており、十分実績があるかと思います。</p>
<h2 id="hlsjs-による実装">hls.js による実装</h2>
<p>基本的には README の <a href="https://github.com/video-dev/hls.js#getting-started">Getting Started</a> の通りで実装できます。一部 README のサンプルコードから抜粋して解説すると…</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#569CD6">var</span><span style="color:#9CDCFE"> video</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">document</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getElementById</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"video"</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#569CD6">var</span><span style="color:#9CDCFE"> videoSrc</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">"https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">Hls</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">isSupported</span><span style="color:#D4D4D4">()) {</span></span>
<span class="line"><span style="color:#569CD6"> var</span><span style="color:#9CDCFE"> hls</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> Hls</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#9CDCFE"> hls</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">loadSource</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">videoSrc</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#9CDCFE"> hls</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">attachMedia</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">video</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#9CDCFE"> hls</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">on</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">Hls</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">Events</span><span style="color:#D4D4D4">.</span><span style="color:#4FC1FF">MANIFEST_PARSED</span><span style="color:#D4D4D4">, </span><span style="color:#569CD6">function</span><span style="color:#D4D4D4"> () {</span></span>
<span class="line"><span style="color:#9CDCFE"> video</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">play</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>上記 <code>Hls.isSupported()</code> の分岐で、HLS をネイティブサポートしていないブラウザの処理を実装しています。
本来 <code><video></code> の <code>src</code> 属性にセットするべき <code>.m3u8</code> ファイルの URL へ <code>hls.loadSource()</code> でアクセスさせ、クライアントから XHR リクエストを飛ばします。その後 <code>hls.attachMedia()</code> でインスタンスを DOM 上の <code><video></code> タグに紐づけています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#C586C0"> else</span><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">video</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">canPlayType</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'application/vnd.apple.mpegurl'</span><span style="color:#D4D4D4">)) {</span></span>
<span class="line"><span style="color:#9CDCFE"> video</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">src</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">videoSrc</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#9CDCFE"> video</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">addEventListener</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'loadedmetadata'</span><span style="color:#D4D4D4">, </span><span style="color:#569CD6">function</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#9CDCFE"> video</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">play</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span></code></pre>
<p>上記の分岐が iOS Safari など、HLS 動画をネイティブサポートしているブラウザ向けの処理です。単純に <code>.m3u8</code> への URL を <code><video></code> タグの <code>src</code> へ付与しているだけですね。</p>
<p>(サンプルコードでは、マニフェストファイルのロード後に自動再生させるようになっているようです)</p>
<h2 id="注意点つまづいたポイント-1">注意点・つまづいたポイント</h2>
<ul>
<li>hls.js クライアントが取得する HLS 動画ファイル群は、CORS ヘッダで GET リクエストを許可された環境に設置する必要があります</li>
<li><code>.m3u8</code> マニフェストファイルをアプリの API などから返却する場合、Content-Type を <code>application/x-mpegURL</code> にして渡す必要があります</li>
<li>iOS Safari などの hls.js 非対応ブラウザ向けの実装を意識する必要があります
<ul>
<li>hls.js による制御が複雑になるケースでは、同じような制御を hls.js 非対応ブラウザ向けに実装できるか?をイメージできないと手戻りが発生しそうです</li>
</ul>
</li>
</ul>
<p>この他、フロントエンドでは <code><video></code> タグのブラウザ毎の挙動や、表示の違いに時間がかかりました。(ある程度予想はしていましたが、やはりメディアの取り扱いは難しい…)</p>
<p>hls.js 自体は導入も手軽で、サクッと HLS 動画のマルチブラウザ対応が実現でき、とても使いやすかったです。@types も存在するので、TypeScript 環境でも難なく実装できました。</p>
<p>SSR や HLS + AES-128 の再生にも対応しているので、興味のある方は一度 <a href="https://hls-js.netlify.app/api-docs/">公式ドキュメント</a> を確認してみてください。</p>
<h1 id="おわりに">おわりに</h1>
<p>従来、動画配信サービスを構築する場合、ffmpeg を載せたエンコードサーバや、ストリーミング配信サーバを別建てして、負荷に応じてスケールさせて…のような設計が必要だったかと思います。</p>
<p>今回、MediaConvert をはじめとした AWS サービスと hls.js を利用することで、手軽に、スケーラブルな動画エンコード/HTTP ストリーミング配信環境を構築することができました。</p>
<p>ジョブメドレーの動画選考はまだリリースしたばかりですので、今後反響を見ながら、さらなる改善を重ねていけたらと思います。最後までお読みいただきありがとうございました。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- WEB 面接の裏側https://developer.medley.jp/entry/2020/11/26/190103https://developer.medley.jp/entry/2020/11/26/190103株式会社メドレーのエンジニアの笹塚です。 私が開発を担当しているジョブメドレーで、先月 10 月 23 日に WEB 面接・動画選考をリリースしました。
job-medley.com WEB 面接、動画選考ともに、昨今の非対面での就職活...Thu, 26 Nov 2020 10:01:03 GMT<p>株式会社メドレーのエンジニアの笹塚です。 私が開発を担当しているジョブメドレーで、先月 10 月 23 日に WEB 面接・動画選考をリリースしました。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="WEB 面接・動画選考機能のリリースのお知らせ | ジョブメドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjob-medley.com%2Frelease%2F70%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://job-medley.com/release/70/">job-medley.com</a></cite> WEB 面接、動画選考ともに、昨今の非対面での就職活動ニーズに応えるべく開発しました。
<p>リリースは 2 つの機能を同時ですが、今回は WEB 面接の裏側に絞ってご紹介します。</p>
<h1 id="web-面接概要">WEB 面接概要</h1>
<p>WEB 面接とは、リアルタイムで事業者様と求職者様が、オンライン面接を行うことができる機能です。 専用のアプリケーションは必要なく、PC、スマートフォンのブラウザから利用できます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201126/20201126143843.png" alt="20201126143843.png">
<h1 id="サービス選定">サービス選定</h1>
<p>開発にあたり、いくつかの候補があがりましたが、最終的には自社内でも導入実績のある SkyWay を使用することにしました。</p>
<h1 id="skyway-とは">SkyWay とは</h1>
<p>WebRTC(Web Real Time Communication)を使用したオンラインのビデオ通話を、サービスに導入できるマルチプラットフォーム SDK です。</p>
<p>2020 年 11 月時点で、JavaScript SDK、iOS SDK、Android SDK が提供されています。</p>
<ul>
<li>シグナリングサーバなどの WebRTC に必要となるインフラ構築が不要です</li>
<li>使用上限つきの無料プランもあります</li>
<li>NTT コミュニケーションズが開発しています</li>
</ul>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="SkyWay | アプリや Web サービスに、ビデオ・音声通話をかんたんに導入・実装できる SDK" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwebrtc.ecl.ntt.com%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://webrtc.ecl.ntt.com/">webrtc.ecl.ntt.com</a></cite>
<p>WEB 面接の対応ブラウザバージョン(2020 年 11 月時点)</p>
<table><thead><tr><th>プラットフォーム</th><th>対応バージョン</th></tr></thead><tbody><tr><td>PC: Google Chrome</td><td>バージョン 84 以上</td></tr><tr><td>PC: Microsoft Edge</td><td>バージョン 84 以上</td></tr><tr><td>iOS: Safari</td><td>iOS12 以上</td></tr><tr><td>Android: Google Chrome</td><td>バージョン 85 以上 Andoid9 以上</td></tr></tbody></table>
<p>SkyWay JavaScript SDK の動作確認ブラウザ、WebRTC の対応状況、利用者の利用傾向から対応ブラウザのバージョンを上記のように設定しました。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Javascript SDK | ドキュメント | SkyWay(アプリや Web サービスに、ビデオ・音声通話をかんたんに導入・実装できる SDK)" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwebrtc.ecl.ntt.com%2Fdocuments%2Fjavascript-sdk.html%23javascript-sdk" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://webrtc.ecl.ntt.com/documents/javascript-sdk.html#javascript-sdk">webrtc.ecl.ntt.com</a></cite>
<h1 id="skyway-の接続モデル">SkyWay の接続モデル</h1>
<p>SkyWay でビデオ通話を実装する場合、2 種類の接続モデルから選びます。</p>
<h2 id="skyway-電話モデル">SkyWay 電話モデル</h2>
<ul>
<li>電話のように 1 対 1 でのビデオ通話を想定したモデルです。</li>
<li>Peer インスタンス(シグナリングサーバによって発行された一意の PeerID を持つ)同士で接続します。</li>
<li>接続するためには、相手の PeerID が必要になります。</li>
</ul>
<h2 id="skyway-ルームモデル">SkyWay ルームモデル</h2>
<ul>
<li>同一ルーム内の全ての Peer でビデオ通話するモデルです。</li>
<li>ルーム名を使用して参加します。相手の PeerID を知る必要はありません。</li>
<li>ルーム名は API キー毎に独立しています。</li>
<li>ルームの接続タイプはフルメッシュか SFU の 2 種類から選べます。</li>
<li>参加者全員へのチャットなどのデータ送信もできます。</li>
</ul>
<p>ジョブメドレーの WEB 面接では、 今後の機能拡張を想定して、こちらのルームモデルを採用しました。</p>
<h1 id="ルームの通信タイプ">ルームの通信タイプ</h1>
<p>ルームに複数人が参加している場合、それぞれの通信をどう行うかを、メッシュと SFU から選ぶことができます。</p>
<h2 id="フルメッシュ">フルメッシュ</h2>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="画像引用元: https://webrtc.ecl.ntt.com/skyway/overview.html#_4-sfu%E3%82%B5%E3%83%BC%E3%83%90">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201126/20201126180010.png" alt="20201126180010.png">
<figcaption class="mceEditable">画像引用元: https://webrtc.ecl.ntt.com/skyway/overview.html#_4-sfu%E3%82%B5%E3%83%BC%E3%83%90</figcaption>
</figure>
<p>全員が相互に通信を行います。人数が増えると、人数分端末のエンコード負荷と通信量が増加します。</p>
<h2 id="sfu">SFU</h2>
<p>SFU の場合、上りの接続は 1 本になるので、メッシュよりも端末のエンコード負荷や、通信量の軽減が期待できます。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="画像引用元: https://webrtc.ecl.ntt.com/skyway/overview.html#_4-sfu%E3%82%B5%E3%83%BC%E3%83%90">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201126/20201126180029.png" alt="20201126180029.png">
<figcaption class="mceEditable">画像引用元: https://webrtc.ecl.ntt.com/skyway/overview.html#_4-sfu%E3%82%B5%E3%83%BC%E3%83%90</figcaption>
</figure>
<p>通信方式の違いは SkyWay が隠蔽してくれるので、joinRoom 時の mode を mesh から sfu に変更するだけで切り替わります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#9CDCFE">peer</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">joinRoom</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">roomName</span><span style="color:#D4D4D4">, { </span><span style="color:#9CDCFE">mode:</span><span style="color:#CE9178"> "mesh か sfu を指定"</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">stream:</span><span style="color:#9CDCFE"> mediaStream</span><span style="color:#D4D4D4"> });</span></span></code></pre>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="概要 | SkyWay(アプリや Web サービスに、ビデオ・音声通話をかんたんに導入・実装できる SDK)" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwebrtc.ecl.ntt.com%2Fskyway%2Foverview.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://webrtc.ecl.ntt.com/skyway/overview.html">webrtc.ecl.ntt.com</a></cite>
<p>ジョブメドレーの WEB 面接では、面接参加人数を考慮して mesh を使用しています。</p>
<h1 id="実装イメージ">実装イメージ</h1>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> peer</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> Peer</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">peer_id</span><span style="color:#D4D4D4">, {</span></span>
<span class="line"><span style="color:#9CDCFE"> key:</span><span style="color:#9CDCFE"> api_key</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">peer</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">once</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"open"</span><span style="color:#D4D4D4">, () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> room</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">once</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"open"</span><span style="color:#D4D4D4">, () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#6A9955"> // ルーム参加後に発生するイベント</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> room</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">on</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"peerJoin"</span><span style="color:#D4D4D4">, (</span><span style="color:#9CDCFE">peerId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">string</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#6A9955"> // ルームに誰か参加した場合に発生するイベント</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> room</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">on</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"stream"</span><span style="color:#D4D4D4">, (</span><span style="color:#9CDCFE">stream</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">RoomStream</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#6A9955"> // stream を受けた場合に発生するイベント</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> room</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">on</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"data"</span><span style="color:#D4D4D4">, ({ </span><span style="color:#9CDCFE">src</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">data</span><span style="color:#D4D4D4"> }) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#6A9955"> // data を受けた場合に発生するイベント</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">peer</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">on</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"error"</span><span style="color:#D4D4D4">, (</span><span style="color:#9CDCFE">error</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Error</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#6A9955"> // エラー発生時に発生するイベント</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">peer</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">joinRoom</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">roomName</span><span style="color:#D4D4D4">, { </span><span style="color:#9CDCFE">mode:</span><span style="color:#CE9178"> "mesh"</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">stream:</span><span style="color:#9CDCFE"> mediaStream</span><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">room</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">close</span><span style="color:#D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">peer</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">disconnect</span><span style="color:#D4D4D4">();</span></span></code></pre>
<p>Peer を作成し</p>
<ol>
<li>peer.joinRoom() でルームに参加</li>
<li>room.stream イベントで他の参加者の stream を受け取る</li>
<li>room.data() でチャットなど、データ送信もできる</li>
<li>room.close() でルームから退出 が SkyWay の JavaScript SDK を使用した基本的な実装になります。</li>
</ol>
<p>この実装に</p>
<ul>
<li>navigator.mediaDevices.getUserMedia() で取得した stream を joinRoom で渡す</li>
<li>steam イベントで受け取った stream を video で再生する</li>
</ul>
<p>を追加すれば、オンラインでのビデオ通話が可能になります。</p>
<h1 id="スマートフォン対応">スマートフォン対応</h1>
<p>PC と同様のコードでほぼ動作しましたが、iOS や Android 端末で、機種依存と思われる挙動の調査と対応に時間がかかりました。その一部を紹介します。</p>
<h2 id="タブを移動すると映像が映らない">タブを移動すると映像が映らない</h2>
<p>発生した問題 iOS12、iOS13 などで</p>
<ul>
<li>複数のタブをひらいた場合に、映像の取得ができなくなる</li>
<li>スクリーンロックからの復帰時に、映像が取得できなくなる</li>
</ul>
<p>という問題が起きました。iOS14 ではタブ切り替え時に映像を取得できるようになっていましたが、スクリーンロックからの復帰時は映像を取得できないままでした。</p>
<h3 id="対応">対応</h3>
<p>この対応は visibilitychange イベントで、タブの切り替えと、スクリーンロックからの復帰時のイベントを拾い</p>
<ol>
<li>取得済の stream の track を stop する</li>
<li>stream を取り直す</li>
<li>SkyWay の room.replaceStream() で、WebRTC で使用している stream を差し替える</li>
</ol>
<p>以上の実装により、対応しました。 __</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#9CDCFE">document</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">addEventListener</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"visibilitychange"</span><span style="color:#D4D4D4">, </span><span style="color:#569CD6">async</span><span style="color:#D4D4D4"> () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">document</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">visibilityState</span><span style="color:#D4D4D4"> !== </span><span style="color:#CE9178">"visible"</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> localMedia</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getTracks</span><span style="color:#D4D4D4">().</span><span style="color:#DCDCAA">forEach</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">track</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">MediaStreamTrack</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> track</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">stop</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> replaceStream</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">await</span><span style="color:#9CDCFE"> navigator</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">mediaDevices</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getUserMedia</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> video:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> audio:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> room</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">replaceStream</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">replaceStream</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span></code></pre>
<h2 id="イヤホンの操作で映像が止まる">イヤホンの操作で映像が止まる</h2>
<h3 id="発生した問題">発生した問題</h3>
<p>iOS12、iOS13 では起こりませんでしたが、iOS14 でオンライン面接途中にイヤホンをスマートフォンから外すと、相手側の映像が止まるようになりました。</p>
<h3 id="対応-1">対応</h3>
<p>それまでは、相手の映像と音声を再生するために、video タグに stream を渡して映像と音声を再生していましたが</p>
<ul>
<li>video : mute にして映像を再生</li>
<li>audio : 音声を再生</li>
</ul>
<p>と、音声と再生を分けたところ、イヤホンを外しても停止することはなくなりました。</p>
<h2 id="制約設定">制約設定</h2>
<p>getUserMedia で取得する MediaStream は、制約を設定することでデバイスの消費リソースを抑えることができます。</p>
<h3 id="設定例">設定例:</h3>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#9CDCFE">navigator</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">mediaDevices</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getUserMedia</span><span style="color:#D4D4D4">({ </span><span style="color:#9CDCFE">audio:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">video:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4"> });</span></span></code></pre>
<p>↓</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#9CDCFE">navigator</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">mediaDevices</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getUserMedia</span><span style="color:#D4D4D4">({ </span><span style="color:#9CDCFE">audio:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">video:</span><span style="color:#D4D4D4"> { </span><span style="color:#9CDCFE">frameRate:</span><span style="color:#B5CEA8"> 15</span><span style="color:#D4D4D4"> } });</span></span></code></pre>
<h3 id="制約が設定可能かどうかの確認">制約が設定可能かどうかの確認:</h3>
<p><code>getSupportedConstraints()</code> で、対応している制約名を取得することができます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> supportedConstraints</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">navigator</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">mediaDevices</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getSupportedConstraints</span><span style="color:#D4D4D4">();</span></span></code></pre>
<p>使用しているブラウザがその制約に対応しているかを確認し、対応している場合のみ設定を有効にします。</p>
<p>例えば、スマートフォンの場合に以下の設定をすれば、インカメラを使用し、フレームレートを 20 に抑え、320x320 の解像度に制限することができます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> options</span><span style="color:#D4D4D4"> = { </span><span style="color:#9CDCFE">facingMode:</span><span style="color:#CE9178"> "user"</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">frameRate:</span><span style="color:#B5CEA8"> 20</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">width:</span><span style="color:#B5CEA8"> 320</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">height:</span><span style="color:#B5CEA8"> 320</span><span style="color:#D4D4D4"> };</span></span></code></pre>
<p>指定した制限が必ず使用される保証はなく、機種依存の影響を受ける設定でもあるので、対象としている環境にあわせて検証と調整をする必要がある点には注意が必要です。</p>
<p>動作検証中に、機種依存と思われる挙動をした例を紹介します。</p>
<table><thead><tr><th>オプション</th><th>挙動</th></tr></thead><tbody><tr><td>frameRate</td><td>指定すると一部 Android 端末で以下の挙動をした。<br> 1. Android 端末で面接に参加する<br> 2. iOS Safari で参加する<br> 3. Android 側の映像と音声が Safari に送られない Safari で先に参加する場合には問題がない。</td></tr><tr><td>解像度指定</td><td>例: width: 320, height: 320 frameRate だけ指定していたときに上記の挙動をした Android、iOS Safari の組み合わせで問題が起こらなくなった。</td></tr></tbody></table>
<p>解像度を指定するとインカメラではなく、リアカメラを使う Android 端末があった。</p>
<p>制約設定については、現在も調整中です。</p>
<h1 id="参考情報">参考情報</h1>
<h2 id="skyway-conference">SkyWay Conference</h2>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="SkyWay Conference" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fconf.webrtc.ecl.ntt.com%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://conf.webrtc.ecl.ntt.com/">conf.webrtc.ecl.ntt.com</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="skyway/skyway-conf" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fskyway%2Fskyway-conf" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://github.com/skyway/skyway-conf">github.com</a></cite>
<p>SkyWay のルーム機能を使用したデモ環境です。 GitHub にコードも公開されているので、ルーム を使用した場合の挙動と実装方法を確認できます。 開発をしていると、実装の問題なのか、SkyWay の SDK の仕様なのか、特定のデバイスで起こる問題なのかの切り分けに時間がかかるため、こちらの環境が参考になりました。</p>
<h1 id="まとめ">まとめ</h1>
<p>SkyWay が WebRTC とグループでのビデオ通話の実装を統合して提供してくれるため、開発時は、自社サービスとしての WEB 面接の機能に集中することができました。</p>
<p>スマートフォンのブラウザ対応と調査に時間がかかることもありますが、今後も利用者からのフィードバックを得ながら改善していきたいと思います。</p>
<p>メドレーでは、ニーズにあわせた新機能の開発にも力を入れています。多くの利用者に実際に使われるサービスの開発をしてみたいと思った方、ぜひお気軽にお話しましょう!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集の一覧 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>medley
- GraphQL, TypeScript, React を用いて型安全に社内システムをリニューアルした話https://developer.medley.jp/entry/2020/11/06/180208https://developer.medley.jp/entry/2020/11/06/180208こんにちは。メドレーのエンジニアの山田です。現在、医療介護求人サイト「ジョブメドレー」のチームで開発を担当しています。
今回、ジョブメドレーの社内スタッフが利用する社内システムをリニューアルした事例をご紹介します。
リニューアル対象はバック...Fri, 06 Nov 2020 09:02:08 GMT<p>こんにちは。メドレーのエンジニアの山田です。現在、医療介護求人サイト「ジョブメドレー」のチームで開発を担当しています。</p>
<p>今回、ジョブメドレーの社内スタッフが利用する社内システムをリニューアルした事例をご紹介します。</p>
<p>リニューアル対象はバックエンド領域も含まれますが、本記事ではフロントエンドの話を中心にご紹介します。</p>
<p>また、弊社デザイナー酒井が以前投稿した <a href="/entry/2020/06/19/194558">デザイナーがデザインツールを使わずに、React を使ってデザインした話</a> も関連しているので、よろしければあわせてご覧ください。</p>
<h1 id="リニューアルの背景">リニューアルの背景</h1>
<p>社内システムでは、求人サイト「ジョブメドレー」を利用する求職者に関する情報や求職者の応募状況を管理しています。</p>
<p>前回のリニューアルから時間が経ち、複雑性が高くなってきました。その複雑性に比例して、新機能の追加や改修するためのコストも高くなっていました。</p>
<p>そこで上記の課題を解決するため、状態管理がしやすく、テストコードも書きやすい、メンテナブルなアーキテクチャにすべくリニューアルを実施することにしました。</p>
<p>検証期間も経て、今回のリニューアルにあわせて新規に作成する API は、GraphQL によって実装することを決めました。</p>
<p>型システムを持つため画面に必要なデータを柔軟に過不足なく取得できる、手動でドキュメントに落とし込まなくてもスキーマが定義されていれば API の仕様を簡単に把握できる、等がメリットとして感じられました。</p>
<p>特に、GraphQL が持つ型システムが、TypeScript、Apollo、GraphQL Code Generator のライブラリを組み合わせることで、API に渡すパラメータや、レスポンスにも型が適用され、GraphQL スキーマの変更にクライアントの実装が比較的容易に追従できることが、大きなポイントでした。</p>
<h1 id="フロントエンドの技術的なリニューアル内容">フロントエンドの技術的なリニューアル内容</h1>
<p>今回は特に、リニューアルに用いられたフレームワークやライブラリ、Apollo Client を用いた状態管理、テストコード実装における Tips 等をそれぞれ部分的にご紹介します。</p>
<h2 id="採用したフレームワークと主要ライブラリ">採用したフレームワークと主要ライブラリ</h2>
<table>
<tbody><tr>
<td><strong>採用ライブラリ</strong>
</td>
<td><strong>説明</strong>
</td>
</tr>
<tr>
<td><a href="https://nextjs.org/">Next.js</a>
</td>
<td>React 用のフレームワーク(ボイラープレート)
</td>
</tr>
<tr>
<td><a href="https://www.typescriptlang.org/">TypeScript</a>
</td>
<td>JavaScript のスーパーセットで、静的型付け言語
</td>
</tr>
<tr>
<td><a href="https://ja.reactjs.org/">React</a>
</td>
<td>UI を構築するためのライブラリ(バージョン 16.8.0 でリリースされた hooks を全面的に使用)
</td>
</tr>
<tr>
<td><a href="https://www.apollographql.com/docs/react/">Apollo Client</a>
</td>
<td>GraphQL API のクライアントで、アプリケーション全体の状態管理を実施
</td>
</tr>
<tr>
<td><a href="https://graphql-code-generator.com/">GraphQL Code Generator</a>
</td>
<td>GraphQL スキーマから定義ファイル(型、カスタム hooks 等)を生成
</td>
</tr>
<tr>
<td><a href="https://emotion.sh/">emotion</a> + <a href="https://styled-system.com/">Styled System</a>
</td>
<td>CSS in JS として利用
</td>
</tr>
<tr>
<td><a href="https://jaredpalmer.com/formik/">formik</a> + <a href="https://github.com/jquense/yup">yup</a>
</td>
<td>フォームのビルダー + バリデーター
</td>
</tr>
<tr>
<td><a href="https://jestjs.io/ja/">Jest</a> + <a href="https://testing-library.com/docs/react-testing-library/intro">React Testing Library</a>
</td>
<td>テストコード実装用のツール群
</td>
</tr>
<tr>
<td><a href="https://eslint.org/">ESLint</a> + <a href="https://prettier.io/">Prettier</a>
</td>
<td>ルールに基づいたコードの静的解析 + スタイリング
</td>
</tr>
</tbody></table>
<h3 id="typescript">TypeScript</h3>
<p>今回のリニューアルで求められたことの一つとして、さらなる改善・新規機能追加などをしていく上で、ソフトウェア品質を担保するための、アプリケーションの堅牢さがありました。</p>
<p>そこで、フロントエンド側の開発言語としては、プログラムコード内で宣言された型によって、エラーを未然に防ぎつつ、VSCode をはじめとするエディタのコード補完の恩恵を受けられるメリット等を考慮して TypeScript の採用を決めました。また、他のプロジェクトでも既に TypeScript は部分的に利用し始めていた事情もあり、逆に TypeScript を採用しない、という選択肢はあまり考えられませんでした。</p>
<h3 id="react">React</h3>
<p>UI を構築するためのライブラリ/フレームワークは React を採用しました。こちらも、弊社では別プロジェクトで React を既に利用し始めていたこともあり、学習コストの観点から、新たに他のフレームワークを選択するメリットはほぼ無かったためです。しかし、その事を差し引いたとしても TypeScript と GraphQL との相性の良さで、React が優勢でした。</p>
<p>特に、React の場合は、GraphQL スキーマをベースに、GraphQL Code Generator によって型定義ファイルだけではなく、GraphQL API とのやり取りに使えるカスタム hooks も生成して利用できるという点が、大きな利点として考えられました。</p>
<h3 id="nextjs">Next.js</h3>
<p>フロントエンド開発環境を素早く構築するため、ボイラープレートとして Next.js を採用しました。</p>
<p>Next.js の具体的な採用ポイントとしては、主に次の3点です。</p>
<ol>
<li>webpack における、バンドルやコンパイル、ホットリロード等の設定に時間を費やすことなく、ビジネスロジックの実装に集中できる
<ul>
<li>必要があれば、next.config.js で設定を拡張できる</li>
<li>CRA(Create React App)とは異なり、拡張性に優れている</li>
</ul>
</li>
<li><code>pages</code> 配下に置く React Component のディレクトリ構成が、自動的にルーティングとして定義される
<ul>
<li>ルーティングに関する設計作業が不要になる</li>
</ul>
</li>
<li>自動コード分割等によるパフォーマンス最適化をよしなに行ってくれる</li>
</ol>
<h2 id="react-component-の分類">React Component の分類</h2>
<p>component は大きく2つに分類し、<code>src/components/app/</code>と<code>src/components/ui/</code> それぞれのディレクトリに component を置いています。分類は以下の基準で行ないました。</p>
<ul>
<li>
<p><strong>app</strong>: 本アプリケーション固有で使用される想定のもので、再利用性が低く、具体的な component</p>
</li>
<li>
<p><strong>ui</strong>: 本アプリケーション外でも使用可能な、再利用性が高く、抽象的な component</p>
</li>
</ul>
<p>社内向けシステムではあるものの、Material-UI や Ant Design 等をはじめとする、外部の UI ライブラリは使用せず、カスタマイズがしやすいように、全て自前で作成しました。</p>
<p><code>app</code>配下と<code>ui</code>配下、どちらの component も基本的には <a href="https://ja.reactjs.org/docs/faq-structure.html#dont-overthink-it">コロケーション</a> の考え方でファイルを構成しています。</p>
<blockquote>
<p>一般的には、よく一緒に変更するファイルを近くに置いておくのは良いアイディアです。
この原則は、「コロケーション」と呼ばれます。</p>
</blockquote>
<p>この考え方でファイルを構成することで、関連するファイルがまとまっていて、作業がしやすくなります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"></span>
<span class="line"><span style="color:#DCDCAA">src/</span></span>
<span class="line"><span style="color:#DCDCAA"> components/</span></span>
<span class="line"><span style="color:#DCDCAA"> app/</span></span>
<span class="line"><span style="color:#DCDCAA"> partials/</span></span>
<span class="line"><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">component</span><span style="color:#D4D4D4"> 名}</span><span style="color:#DCDCAA">/</span></span>
<span class="line"><span style="color:#DCDCAA"> apollo.cache.ts</span></span>
<span class="line"><span style="color:#DCDCAA"> apollo.query.graphql</span></span>
<span class="line"><span style="color:#DCDCAA"> index.tsx</span></span>
<span class="line"><span style="color:#DCDCAA"> index.test.tsx</span></span>
<span class="line"><span style="color:#DCDCAA"> ...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> screens/</span></span>
<span class="line"><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">component</span><span style="color:#D4D4D4"> 名}</span><span style="color:#DCDCAA">/</span></span>
<span class="line"><span style="color:#DCDCAA"> apollo.cache.ts</span></span>
<span class="line"><span style="color:#DCDCAA"> apollo.query.graphql</span></span>
<span class="line"><span style="color:#DCDCAA"> index.tsx</span></span>
<span class="line"><span style="color:#DCDCAA"> index.test.tsx</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ${子 </span><span style="color:#9CDCFE">component</span><span style="color:#D4D4D4"> 名}</span><span style="color:#DCDCAA">/</span></span>
<span class="line"><span style="color:#DCDCAA"> apollo.cache.ts</span></span>
<span class="line"><span style="color:#DCDCAA"> apollo.query.graphql</span></span>
<span class="line"><span style="color:#DCDCAA"> index.tsx</span></span>
<span class="line"><span style="color:#DCDCAA"> index.test.tsx</span></span>
<span class="line"><span style="color:#DCDCAA"> validation.ts</span></span>
<span class="line"></span></code></pre>
<p>src/components/app ディレクトリ配下でさらに、<code>partials</code>と<code>screens</code>のディレクトリで component を分けています。</p>
<p><code>screens</code>には、Next.js で route として扱われる src/pages 配下の component から import される component が配置されています。</p>
<p>画面のバリエーションが増える度に、この<code>screens</code>にファイルが追加されていきます。</p>
<p><code>partials</code>には、app 配下で複数の component から利用される component(画面をまたいで共有されるもの等)を配置しています。</p>
<p><code>screens</code>と<code>partials</code>それぞれ直下の component で、必要であれば適宜、component を分割して子 component を持つ構成にしています。</p>
<p><code>apollo.cache.ts</code>と<code>apollo.query.graphql</code>については後述の状態管理の話でご紹介します。</p>
<h2 id="状態管理">状態管理</h2>
<p>アプリケーションの状態管理については、グローバルにアクセスできる状態の管理には Apollo Client の <a href="https://www.apollographql.com/docs/react/api/cache/InMemoryCache/">InMemoryCache</a> による cache 機構で行い、特定の component 内に閉じている局所的な状態の管理には<a href="https://ja.reactjs.org/docs/hooks-state.html">useState</a>等の React Hooks を使って行っています。</p>
<p>状態管理の必要性が生じた際、アプリケーションの複雑性を上げないように、なるべく useState 等の hooks を用いた local state だけで済ませられないかどうかを検討します。</p>
<p>例えば、クリックするとドロップダウンリストが表示されるセレクトボックスの component で、ドロップダウンリストの表示状態をその component 内だけで扱いたいのであれば useState を用いた local state で十分であると考えられます。</p>
<p>親子関係ではない component 同士でのやりとりが必要になった時や、サーバのデータと関連する場合等で、ローカルのデータを一元管理しておいた方が良さそうなケースでは、Apollo Client の cache を利用します。</p>
<h3 id="apollo-client">Apollo Client</h3>
<p>Apollo に関連するファイルの構成については以下の通りです。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">src/</span></span>
<span class="line"><span style="color:#DCDCAA"> apollo/</span></span>
<span class="line"><span style="color:#DCDCAA"> cache.ts</span></span>
<span class="line"><span style="color:#DCDCAA"> client.ts</span></span>
<span class="line"><span style="color:#DCDCAA"> types.ts</span></span>
<span class="line"><span style="color:#DCDCAA"> withApollo.ts</span></span>
<span class="line"></span></code></pre>
<ul>
<li>
<p><strong><code>cache.ts</code></strong>: Apollo における local state の<code>initialState</code>と<code>resolver</code>を全画面分このファイルでまとめて、最終的に Next.js の<code>src/pages/_app.tsx</code>に渡るようにする</p>
<ul>
<li>component 固有の local state に関する<code>initialState</code>および state の updater となる<code>resolver</code>は component 毎の<code>apollo.cache.ts</code>にて、別途定義</li>
</ul>
</li>
<li>
<p><strong><code>client.ts</code></strong>: Apollo Client のインスタンスを生成するファイル</p>
</li>
<li>
<p><strong><code>types.ts</code></strong>: Apollo 関連の型定義ファイル</p>
</li>
<li>
<p><strong><code>withApollo.ts</code></strong>: Apllo Client の <code>&lt;ApolloProvider /></code> でラップして返す Higher-Order Compoents(HOC)</p>
</li>
</ul>
<p>実装については割愛しますが、client.ts と withApollo.ts に関しては、Next.js の example(<a href="https://github.com/vercel/next.js/tree/1a50d99fbd3278aa09f749bf33e1d801bedc5826/examples/with-apollo">with-apollo</a>)等を参考にしました。</p>
<p>画面固有の Apollo の状態管理に関わるファイルは<code>src/components/**/${component 名}/</code>配下に置いています。</p>
<p>こちらもコロケーションの考え方で、component に関わる状態管理は該当の component と同じ場所に置くことを意識しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">src/</span></span>
<span class="line"><span style="color:#DCDCAA"> components/</span></span>
<span class="line"><span style="color:#DCDCAA"> app/</span></span>
<span class="line"><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">component</span><span style="color:#D4D4D4"> 名}</span><span style="color:#DCDCAA">/</span></span>
<span class="line"><span style="color:#DCDCAA"> apollo.cache.ts</span></span>
<span class="line"><span style="color:#DCDCAA"> apollo.query.graphql</span></span>
<span class="line"><span style="color:#DCDCAA"> apollo.schema.graphql</span></span>
<span class="line"></span></code></pre>
<ul>
<li>
<p><strong><code>apollo.cache.ts</code></strong>: component 固有の Apollo における local state の<code>initialState</code>および<code>resolver</code>を定義するファイル</p>
</li>
<li>
<p><strong><code>apollo.query.graphql</code></strong>: クエリを定義するファイル</p>
</li>
<li>
<p><strong><code>apollo.schema.graphql</code></strong>: local state の GraphQL スキーマを定義ファイル</p>
</li>
</ul>
<p>ファイルの命名について、<a href="https://ja.reactjs.org/docs/faq-structure.html#avoid-too-much-nesting">ディレクトリ階層をできるだけ深くしたくない</a> ので、<code>apollo</code>等によるディレクトリは設けていませんが、Apollo 関連のファイル群として認識できるよう、ファイル名に<code>apollo.</code>のプレフィックスをつけて命名しています。</p>
<h2 id="query-と-mutation-の実行について">Query と Mutation の実行について</h2>
<p>GraphQL Code Generator のプラグイン <a href="https://graphql-code-generator.com/docs/plugins/typescript-react-apollo">TypeScript React Apollo</a> をインストールして、hooks を生成する設定にした上で、component 毎にそれぞれ GraphQL のスキーマとクエリが記述された<code>.graphql</code>ファイルをもとに、GraphQL Code Generator が生成するカスタム hooks を利用します。</p>
<p>こちらのカスタム hooks を React Component で利用することで、Apollo Client 経由で GraphQL API とローカルの Apollo cache に接続して、データのやり取りを行うことができます。</p>
<h3 id="query">Query</h3>
<p>Query の hooks は2種類あり、実行するタイミングによっていずれか適切な方を選んで実行しています。</p>
<table>
<tbody><tr>
<td><strong>API</strong>
</td>
<td><strong>実行タイミング</strong>
</td>
</tr>
<tr>
<td><code><a href="https://www.apollographql.com/docs/react/api/react-hooks/#usequery">useQuery</a></code>
</td>
<td>Component が render されたらクエリ実行
</td>
</tr>
<tr>
<td><code><a href="https://www.apollographql.com/docs/react/api/react-hooks/#uselazyquery">useLazyQuery</a></code>
</td>
<td>任意のイベントをトリガーにしてクエリ実行
</td>
</tr>
</tbody></table>
<h4 id="usequery"><code>use***Query</code></h4>
<p>通常であれば<code>useQuery</code>でクエリの結果を render しますが、GraphQL Code Generator を利用する場合は、それぞれのクエリをラップしたカスタム hooks が生成されるので、<code>useQuery</code>,<code>useLazyQuery</code>をそのまま使うことはありません。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#9CDCFE">query</span><span style="color:#9CDCFE"> AllPosts</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> allPosts</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> id</span></span>
<span class="line"><span style="color:#9CDCFE"> title</span></span>
<span class="line"><span style="color:#9CDCFE"> rating</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span></code></pre>
<p>↑ のようなクエリを用意すると<code>src/__generated__/graphql.tsx</code>に対して、次のようなカスタム hooks が型と一緒に生成される設定にしています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A9955">// Apollo Client: 2.6.9、GraphQL Code Generator: 1.15.0 の場合の例</span></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> function</span><span style="color:#DCDCAA"> useAllPostsQuery</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">baseOptions</span><span style="color:#D4D4D4">?: </span><span style="color:#4EC9B0">ApolloReactHooks</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">QueryHookOptions</span><span style="color:#D4D4D4">&</span><span style="color:#4EC9B0">lt</span><span style="color:#D4D4D4">;</span><span style="color:#4EC9B0">AllPostsQuery</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">AllPostsQueryVariables</span><span style="color:#D4D4D4">>) {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#9CDCFE"> ApolloReactHooks</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">useQuery</span><span style="color:#D4D4D4">&</span><span style="color:#9CDCFE">lt</span><span style="color:#D4D4D4">;</span><span style="color:#9CDCFE">AllPostsQuery</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">AllPostsQueryVariables</span><span style="color:#D4D4D4">>(</span><span style="color:#9CDCFE">AllPostsDocument</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">baseOptions</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> function</span><span style="color:#DCDCAA"> useAllPostsLazyQuery</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">baseOptions</span><span style="color:#D4D4D4">?: </span><span style="color:#4EC9B0">ApolloReactHooks</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">LazyQueryHookOptions</span><span style="color:#D4D4D4">&</span><span style="color:#4EC9B0">lt</span><span style="color:#D4D4D4">;</span><span style="color:#4EC9B0">AllPostsQuery</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">AllPostsQueryVariables</span><span style="color:#D4D4D4">>) {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#9CDCFE"> ApolloReactHooks</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">useLazyQuery</span><span style="color:#D4D4D4">&</span><span style="color:#9CDCFE">lt</span><span style="color:#D4D4D4">;</span><span style="color:#9CDCFE">AllPostsQuery</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">AllPostsQueryVariables</span><span style="color:#D4D4D4">>(</span><span style="color:#9CDCFE">AllPostsDocument</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">baseOptions</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>React Component では生成されたカスタム hooks を次のように呼び出してサーバーから返ってくる結果を受け取って、データ出力、ローディング状態のチェック、エラーハンドリング等を行います。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#D4D4D4"> { </span><span style="color:#4FC1FF">data</span><span style="color:#D4D4D4">, </span><span style="color:#4FC1FF">loading</span><span style="color:#D4D4D4">, </span><span style="color:#4FC1FF">error</span><span style="color:#D4D4D4"> } = </span><span style="color:#DCDCAA">useAllPostsQuery</span><span style="color:#D4D4D4">();</span></span></code></pre>
<h3 id="mutation">Mutation</h3>
<p>データの書き込みは<a href="https://www.apollographql.com/docs/react/api/react-hooks/#usemutation"><code>useMutation</code></a>で行います。</p>
<p>Query 同様、<a href="%5Bhttps://graphql-code-generator.com/%5D(https://graphql-code-generator.com/)">GraphQL Code Generator</a>によって生成されたカスタム hooks <code>use***Mutation</code> を使っています。</p>
<h4 id="cache-の更新">cache の更新</h4>
<p>Mutation が複数エンティティの更新、エンティティの新規作成または削除の場合、Apollo Client の cache は自動更新されず、Mutation の結果が自動的に render されません。</p>
<p>このような場合でも、<a href="https://www.apollographql.com/docs/react/api/react/hooks/#usemutation">useMutation</a> の <code>update</code> option を使えば、<code>cache</code>オブジェクトを引数に取れる関数を設定できるので、この関数内で直接 cache を更新できます。</p>
<p>また、<code>update</code>の代わりに <code>refetchQueries</code> の option を使って、任意の Query を実行して、シンプルに cache を更新することもできます。</p>
<p>但し、この方法だと Network 通信によるオーバーヘッドが発生します。</p>
<p>このオーバーヘッドを犠牲にしてでも、サーバーからデータ取得したい Query があるような場合には、この<code>refetchQueries</code>が有効です。</p>
<h3 id="local-state-の管理">local state の管理</h3>
<p>ここからは特定の component の状態管理を local state を使ってどのように管理しているかを、ご説明していきます。</p>
<h4 id="client-を使った-query">@client を使った Query</h4>
<p>Next.js のプロジェクトで、local state の管理を Apollo Client で行う場合の例としては、次の通りです。</p>
<p><strong>スキーマ:</strong></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#D4D4D4"># </span><span style="color:#9CDCFE">src</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">components</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">app</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">Home</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">apollo</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">schema</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">graphql</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">type</span><span style="color:#4EC9B0"> Home</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#D4D4D4"> currentPostId: Int!</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">extend</span><span style="color:#569CD6"> type</span><span style="color:#4EC9B0"> Query</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#D4D4D4"> home: Home</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span></code></pre>
<p><strong>クエリ:</strong></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#D4D4D4"># </span><span style="color:#9CDCFE">src</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">components</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">app</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">Home</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">apollo</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">query</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">graphql</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">query</span><span style="color:#9CDCFE"> HomeCurrentPostId</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> home</span><span style="color:#D4D4D4"> @</span><span style="color:#9CDCFE">client</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> currentPostId</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span></code></pre>
<p><strong>キャッシュの初期値:</strong></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"></span>
<span class="line"><span style="color:#6A9955">// src/components/app/Home/apollo.cache.ts</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> cache</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#9CDCFE"> __typename:</span><span style="color:#CE9178"> 'Home'</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> currentPostId:</span><span style="color:#B5CEA8"> 0</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> ...,</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span></code></pre>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"></span>
<span class="line"><span style="color:#6A9955">// src/apollo/cache.ts</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> caches</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#D4D4D4"> ...,</span></span>
<span class="line"><span style="color:#9CDCFE"> home:</span><span style="color:#9CDCFE"> home</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">cache</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#D4D4D4"> ...,</span></span>
<span class="line"><span style="color:#9CDCFE"> caches</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span></code></pre>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"></span>
<span class="line"><span style="color:#6A9955">// src/pages/_app.tsx</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> cache</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> InMemoryCache</span><span style="color:#D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> client</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> ApolloClient</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> link</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> cache:</span><span style="color:#9CDCFE"> cache</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">restore</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">initialState</span><span style="color:#D4D4D4"> || {}),</span></span>
<span class="line"><span style="color:#9CDCFE"> resolvers</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> connectToDevTools:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">cache</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">writeData</span><span style="color:#D4D4D4">({ </span><span style="color:#9CDCFE">data:</span><span style="color:#9CDCFE"> caches</span><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span></code></pre>
<p>GraphQL クエリとスキーマが定義されていれば GraphQL Code Generator が<code>use***Query</code>のコードを生成する設定にしています。</p>
<p>ローカルデータの場合、クエリで<code>@client</code>ディレクティブをつけてローカルデータであることを明示します。</p>
<h4 id="client-を使った-mutation">@client を使った Mutation</h4>
<p>local state の更新を GraphQL の Mutation として行う場合の例としては、次の通りです。</p>
<p><strong>スキーマ:</strong></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"></span>
<span class="line"><span style="color:#D4D4D4"># </span><span style="color:#9CDCFE">src</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">components</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">app</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">Home</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">apollo</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">schema</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">graphql</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">type</span><span style="color:#4EC9B0"> UpdateCurrentPostId</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#D4D4D4"> currentPostId: Int!</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">extend</span><span style="color:#569CD6"> type</span><span style="color:#4EC9B0"> Mutation</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#D4D4D4"> updateCurrentPostId(id: Int!): UpdateCurrentPostId</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span></code></pre>
<p><strong>クエリ:</strong></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#D4D4D4"># </span><span style="color:#9CDCFE">src</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">components</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">app</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">Home</span><span style="color:#D4D4D4">/</span><span style="color:#9CDCFE">apollo</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">query</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">graphql</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">mutation</span><span style="color:#DCDCAA"> UpdateCurrentPostId</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">$id</span><span style="color:#D4D4D4">: </span><span style="color:#9CDCFE">Int</span><span style="color:#D4D4D4">!) {</span></span>
<span class="line"><span style="color:#DCDCAA"> updateCurrentPostId</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">: </span><span style="color:#9CDCFE">$id</span><span style="color:#D4D4D4">) @</span><span style="color:#9CDCFE">client</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> currentPostId</span><span style="color:#D4D4D4"> @</span><span style="color:#9CDCFE">client</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span></code></pre>
<p><strong>resolver:</strong></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#6A9955">// src/components/app/Home/apollo.cache.ts</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#DCDCAA"> updateCurrentPostId</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">MutationResolvers</span><span style="color:#D4D4D4">[</span><span style="color:#CE9178">"updateCurrentPostId"</span><span style="color:#D4D4D4">] = (</span></span>
<span class="line"><span style="color:#9CDCFE"> _</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> args</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> { </span><span style="color:#9CDCFE">cache</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> cache</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">writeData</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> data:</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> home:</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> __typename:</span><span style="color:#CE9178"> "Home"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> currentPostId:</span><span style="color:#9CDCFE"> args</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#569CD6"> null</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> Mutation</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#9CDCFE"> updateCurrentPostId</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span></code></pre>
<p>Query 同様に<code>@client</code>ディレクティブをつけてローカルデータであることを明示します。</p>
<p>実際の Mutation の処理自体は<code>resolver</code>の中に<code>cache.writeData()</code>を使って記述します。</p>
<p>Mutation の命名は、<a href="https://blog.apollographql.com/designing-graphql-mutations-e09de826ed97">動詞+名詞の形式で可能な限り意味のある具体的な名前をつける</a>ことを意識しています。</p>
<h3 id="apollo-を使った開発を便利にしてくれるツール">Apollo を使った開発を便利にしてくれるツール</h3>
<p>Apollo Client を使って開発する際は、ローカルの Apollo cache の状態や、クエリを試しに実行するためのツールとして、Google Chrome の拡張機能 <a href="https://chrome.google.com/webstore/detail/apollo-client-developer-t/jdkknkkbebbapilgoeccciglkfbmbnfm">Apollo Client Developer Tools</a> が非常に便利です。</p>
<p>こちらの拡張機能を Chrome にインストールすると、Apollo Client を使って GraphQL API にアクセスするサイトに遷移した状態で Chrome Dev Tools を開くと <code>Apollo</code> のタブが表示されます。そこでクエリの実行や、API 仕様の確認、ローカルの Apollo cache の確認等を行うことができます。</p>
<h2 id="graphql-関連のテストコードについて">GraphQL 関連のテストコードについて</h2>
<p>Apollo Client を使った React Component の開発で、Query および Mutation 実行のテストを実施するには、テストフレームワークの Jest、react-testing-library とあわせて、Apollo 公式でも紹介されている <a href="https://www.apollographql.com/docs/react/development-testing/testing/#mockedprovider">MockedProvider</a> を用いる方法が一般的かと思います。</p>
<p>クエリとクエリに対するレスポンスを組み合わせたモックデータを用意しておき、ApolloProvider の代わりに MockedProvider でテスト対象の component をラップすることで、API サーバーや Network 環境に依存せず、モックで指定したクエリがリクエストされると、モックでそれに対応するように用意したレスポンスデータが確実に取得できる仕組みを作れます。</p>
<p>その仕組みと react-testing-library を使って、component で render される UI 上の操作をトリガーにして実行される、クエリのテストを行うことができます。</p>
<p>Query だけではなく Mutation もモックすることができて、便利なツールではありますが、テストケース毎にモックデータは手動で作成しなければならない点が、なかなか骨が折れる作業です。</p>
<p>実際にアプリケーションを動かして、テスト対象の component を render し、Query に渡される variables やレスポンスの値を Console に出力し、ブラウザの Dev Tools 上で一個一個オブジェクトをコピーして、エディタに貼り付けしたりする作業が発生します。</p>
<h3 id="automockedprovider-の作成">AutoMockedProvider の作成</h3>
<p>そこで、わざわざテスト作成やスキーマ変更の度に、手動でモックデータを用意しなくても、GraphQL スキーマで定義されている型を見て、自動でクエリに対するレスポンスをモックしてくれる AutoMockedProvider を、 <a href="https://www.telerik.com/blogs/mocking-and-testing-graphql-in-react">こちらの記事</a> を参考にして作成しました。</p>
<p>MockedProvider の代わりに、AutoMockedProvider を用いてテスト対象の Component をラップすることで、MockedProvider を使ってテストしていた内容と同じテストが実施できます。</p>
<p>MockedProvider を使って毎回モックデータを用意し、テストを実施することに疲れている方は是非、お試しください。</p>
<p>(紹介先の記事では、<code>graphql-tools</code>の<code>makeExecutableSchema()</code>に渡す<code>schemaSDL</code>が json ファイルで定義されていますが、<a href="https://github.com/apollographql/graphql-tag">graphql-tag</a>のライブラリを併用すれば、graphql ファイルでも同様に<code>schemaSDL</code>として適用することも可能です)</p>
<h1 id="リニューアルを振り返って">リニューアルを振り返って</h1>
<p>今回のリニューアルでは、GraphQL、TypeScript、React をセットで採用したことにより、フロント側では GraphQL Code Generator を使って、あらかじめ用意しておいた GraphQL スキーマから、TypeScript の型だけではなく、React の Hooks 関数まで生成して利用できたことが、開発効率の向上に非常に影響を与えたと思います。</p>
<p>GraphQL API のクライアントで、アプリケーション全体の状態管理を行う Apollo Client の cache 機構の使い方等を体得するまでに、学習コストは決してゼロではありませんでしたが、TypeScript と GraphQL の型システムの恩恵をフルに受け、Next.js のレールにのっかり、型安全な開発環境を手に入れることができました。</p>
<p>我々、開発者の体験だけではなく、今後のプロダクト全体への生産性にも良い影響を及ぼしてくれると確信しています。</p>
<h1 id="さいごに">さいごに</h1>
<p>メドレーではエンジニア・デザイナーを積極募集しています。</p>
<p>「テクノロジーを活用して医療ヘルスケアの未来をつくる」というミッションに共感し、課題解決を行いたい方は是非、ご応募ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 技術を使うための技術も大切というお話https://developer.medley.jp/entry/2020/10/23/181924https://developer.medley.jp/entry/2020/10/23/181924初めまして。CLINICS の開発を担当しているエンジニアの平山です。
(同姓ですが CTO ではございません)
CLINICS の開発は「スモールチーム制」をとっておりまして、現在そのうちの1つをチームリードしています。
前職は長らく S...Fri, 23 Oct 2020 09:19:24 GMT<p>初めまして。CLINICS の開発を担当しているエンジニアの平山です。
<strong>(同姓ですが CTO ではございません)</strong></p>
<p>CLINICS の開発は「スモールチーム制」をとっておりまして、現在そのうちの1つをチームリードしています。</p>
<p>前職は長らく SIer に勤めていました。去年の 12 月にメドレーに JOIN して、間も無く1年経とうとしている。。と思うと、あっという間だったなぁという印象です。</p>
<p>さて、本日はメドレーで隔週開催している社内勉強会(TechLunch)において発表した内容についてご紹介させて頂ければと思います。</p>
<h1 id="はじめに">はじめに</h1>
<p>「技術を使うための技術」というテーマとなりますが、プロダクト開発をしていく上では欠かせない素養と考えています。メドレーに所属しているエンジニアの1人として、どのように日々課題と向き合っているのか。当テーマを通してお伝えできればと思います。</p>
<p>また、この考え方自体は「医療というテーマ」や「事業の背景(ベンチャー・ SIer)」を問わず必要とされる場面があるかもしれません。(自身も前職では様々な場面でお世話になりました…)</p>
<p>即効性のあるものではありませんが、じわじわ効いてくる内容ではないかと思います。よろしければお付き合いください。</p>
<h1 id="本題">本題</h1>
<p>「技術を使うための技術」</p>
<p>みなさんはこの言葉から、何を思い浮かべるでしょうか。筆者が試しに Google で検索した際上位にヒットしたのは</p>
<blockquote>
<p>AI エンジニア
IoT</p>
</blockquote>
<p>といった結果でした。なるほど。少し雑に解釈すると「技術(アルゴリズム等)を使うための技術(機械学習、家電等)」といった感じなのでしょうか。(この結果を拾ってきたというのも、いい意味で Google すごいなって感じました)</p>
<p><strong>筆者が今回のテーマとして指しているのは、下記となります。</strong></p>
<blockquote>
<p>ロジカルシンキング
推論</p>
</blockquote>
<p>これらの <code>思考を整理する「手段」</code> とエンジニアの武器である <code>技術という「手段」</code> をかけ合わせることで、大きなテーマである「医療」に向き合っています。</p>
<h1 id="手段を目的にしてはならない">手段を目的にしてはならない</h1>
<p>先に挙げたとおり「思考の整理」も「技術」も <strong>「手段」</strong> に過ぎません。これらを用いて、適切な一手を指していく為には「目的」に対する解像度を高く描く必要があります。</p>
<p><strong>筆者の発表から抜粋した「技術を使うための技術」の要素をイメージした図</strong></p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201023/20201023135338.png" alt="20201023135338.png">
<h2 id="整理力">整理力</h2>
<p>目的を達成する為に必要な「情報」を取捨選択するための要素。</p>
<p>SaaS ✖️ toB の世界においては「プロダクトが解決すべき課題か否かを業務の本質を踏まえて取捨選択する」と言い換えられるかもしれません。</p>
<h2 id="業務知識">業務知識</h2>
<p>目的の「解像度」を高めるための要素。</p>
<p>CLINICS においては、医療情報を扱う上での規制(3 省 2 ガイドライン)や、医療機関(医師・医事・診療科の特性)の業務、診療報酬についての知識、法改正、レセコン(ORCA)… と様々あります。</p>
<h2 id="技術知識">技術知識</h2>
<p>目的を達成する為に必要な「指し手」を選択するための要素。</p>
<p>(エンジニアにとっては説明するまでもない内容であると思いますが)
ここが欠けてしまうと絵に描いた餅で終わってしまいます。メドレーのエンジニアにおいても日々研鑽し、プロダクトに対してコミットを続けています。</p>
<h2 id="行動力">行動力</h2>
<p>目的を達成するための「推進力」を高めるための要素。</p>
<p>各種知識と整理した情報を推進力に変えていく為には、その時の状況に応じた動きをする必要があります。ステークホルダーとの調整は当然ですが、組織内連携といった「横の動き」も必要です。</p>
<h2 id="想像力">想像力</h2>
<p>これまで挙げたそれぞれの手段を適切に利用していくための要素としての土台が「想像力」であると考えています。</p>
<p><strong>課題(Issue)への取り組み例</strong></p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20201023/20201023135418.png" alt="20201023135418.png">
<table><thead><tr><th>番号</th><th>概要</th></tr></thead><tbody><tr><td>①</td><td>Issue に取り組む際に「本当に解決すべきこと」についての想像を働かせます。Issue に記載されていることが <strong>本当にプロダクトとして解決すべきことなのか</strong> を含めて考えます。これまでの運用が必ずしも正しいわけではない。という点がポイントです。</td></tr><tr><td>②</td><td>① について「想像のまま」で終わらせてはいけないので、業務知識と照らし合わせて確度を高めていきます。常勤医師やカスタマーサポートにヒアリングしながら、<strong>医療業務としてのあるべき形の解像度を上げていく</strong> プロセスです。</td></tr><tr><td>③</td><td>① 及び ② で高めた解像度は言葉の延長線上なので、関係者間の認識のギャップが発生しやすいです。プロトタイプを作成して、視覚・触感レベルでギャップを埋めていくことで、あるべき形に向けて洗練させていきます。</td></tr><tr><td>④</td><td>① 〜 ③ のタイミングを問わず、必要に応じて関係者と相談しながら進めていきます。エンジニアが立てた仮説をデザイナーの目線で評価・ UI/UX 最適化をして頂いたり、大きめの機能については、医療機関にパイロット運用のご協力を仰いだりすることもあります。</td></tr></tbody></table>
<p>① 及び ② の項に作業のウェイトが偏っているように見えるかと思います。実際、課題を解決する為の半分以上をここに割いています。</p>
<p>理由は「1度作って公開した機能」は、その後1人歩きをして作成者の意図しない方向で利用をされることがあるからです。</p>
<p>そして、利用者がその運用を定着させてしまうと <code>誤った機能においても「削ぎ落とすことが困難」</code> です。これは「使われていない機能」よりも直接的な負債といった形でボディブローのように効いてきます。</p>
<ul>
<li>「どのような使われ方をするか」について想像すること</li>
<li>その使われ方が、プロダクトの目指す世界と合っていること</li>
</ul>
<p><strong>エンジニアは技術を形にする上で、常に想像力を働かせて取り組む必要がある。</strong> というのが筆者の持論です。</p>
<h1 id="さいごに">さいごに</h1>
<p>執筆の締めにあたって CTO ブログを見返してみると、大事なことはここに詰まっていました。筆者は前職の SIer 時代に読んだのですが、この記事にすごく共感したのを覚えています。</p>
<div class="remark-link-card-plus__container">
<a href="https://toppa.medley.jp/02.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">toppa.medley.jp</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=toppa.medley.jp" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">toppa.medley.jp</span>
</div>
</div>
</a>
</div>
<p>メドレーでは <a href="https://www.medley.jp/">医療ヘルスケアの未来を作る</a> という大きな目標、そしてその未来を作る為に解決すべき課題に向かって、今回ご紹介したプロセスや考え方も含め試行錯誤しながら、事業部一丸でプロダクト開発を推進しています。</p>
<p>エンジニアの総合力を発揮して医療ヘルスケアの未来を一緒に作り上げていきたい!という方にお会い出来ることを楽しみにしております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- フロントエンド開発環境の継続的なリファクタリングhttps://developer.medley.jp/entry/2020/10/20/170426https://developer.medley.jp/entry/2020/10/20/170426こんにちは、第二開発グループエンジニアの西村です。主にCLINICSの開発を担当しています。
はじめに
CLINICS は電子カルテ、オンライン診療、予約システム、患者アプリなどを含む統合アプリです。CLINICS がローンチしてから現在に...Tue, 20 Oct 2020 08:04:26 GMT<p>こんにちは、第二開発グループエンジニアの西村です。主に<a href="https://clinics.medley.life/">CLINICS</a>の開発を担当しています。</p>
<h1 id="はじめに">はじめに</h1>
<p>CLINICS は電子カルテ、オンライン診療、予約システム、患者アプリなどを含む統合アプリです。CLINICS がローンチしてから現在に至るまで常に新機能開発と定常改善が行われており、開発環境のメンテナンスは後手になりがちでした。今回はそういった状況を改善すべく、開発環境のメンテナンス、リファクタリングを行った過程から得られたプラクティスについて紹介していこうと思います。</p>
<h1 id="モチベーション">モチベーション</h1>
<p>プロダクトの新規開発時に行われる技術選定は非常に難しく、業務要件やチーム状況など総合的に考慮してその時点でのベストな選択をする必要があります。</p>
<p>しかし、選択した技術で長期運用をしていくうちに、メンテナンスが行き届かなくなったコードやライブラリが出てしまいます。</p>
<p>CLINICS ローンチ当初はオンライン診療のみを提供していました。SPA で構成されていましたが、1つの package.json で効率的に開発できていました。他に、現在ほど TypeScript が主流ではなかったので JavaScript のコードがメインで実装されてました。</p>
<p>新たなアプリケーション(電子カルテや予約システムなど)を導入するタイミング、すなわちプロダクトが小規模から中規模に変遷するタイミングや、フロントエンドの時流によって、開発環境を改善できた部分もありますが、しきれていない部分も出てきました。</p>
<p>その改善しきれていない部分を残す状態が続くと Developer Experience(DX)の低下に繋がってしまいます。ですので、私たちは改善しきれていない部分を取り除いていき、よりモダンとされる開発環境へリファクタリングをしていこうと考えました。</p>
<p>DX を向上していくことで技術的なノイズに時間を取られないようになります。そして提供する機能そのものについて考える時間が増え、結果的に CLINICS をより良いプロダクトへ進化させていくのが当リファクタリングの目的です。</p>
<h1 id="課題整理">課題整理</h1>
<p>改善していくためには、現状整理、課題整理を行わないことには何も始まりません。フロントエンド開発環境をメンテナンスするタスクは、プロダクトの機能(ユーザに提供される機能)に直接プラスの影響があるわけではありません。自ずと通常の機能開発や定常改善に比べ優先度は落ちるため、スキマ時間で改善をしていくことになります。こうしたスキマ時間を有効活用するためには、タスクの難易度の理解、タスクを適当に分割、フェージングの計画を行うことが極めて大事です。</p>
<p>そのように考慮した課題の中で、本記事で記載するのは以下の2つです。</p>
<ol>
<li>ライブラリを定期的にアップデートする運用が固まってない</li>
</ol>
<ul>
<li>運用方式が固まっていないことで、放置されてしまいがちです。率先してアップデートするメンバーがいたとしても、属人化の課題が残ってしまいます</li>
<li>放置されてしまったことにより、最新版との差分が大きくなりアップデートするコストも大きくなってしまいます</li>
<li>結果的に、ライブラリのセキュリティフィックス対応や新しく提供された機能をすぐに適応できない環境になってしまいます</li>
</ul>
<ol start="2">
<li>複数の SPA の依存を1つの package.json で管理している</li>
</ol>
<ul>
<li>電子カルテ・オンライン診療、社内管理 Web アプリ、患者 Web アプリはそれぞれ別の SPA として作られています</li>
<li>それらを1つの package.json で管理しているためそれぞれの SPA が同じ依存パッケージを使わなくてはなりません。小規模のときはこのような構成で十分でしたが、規模が大きくなるにつれて柔軟性が失われると共に、ライブラリのアップデートがもたらす影響範囲が広がってしまうため、容易にアップデートできなくなってしまいます</li>
</ul>
<p>本記事では記載しませんが「Redux の書き方が混在している」「フロントエンドのテストが少ない」「網羅的に TypeScript 化できていなく JavaScript がまだ残っている」などの課題も挙げられました。</p>
<p>こういう課題は、どのプロダクトにも存在すると思います。それはローンチ当時の技術流行であったり、プロダクトの期待規模、少数メンバに適した設計など要因は様々あり、プログラムのリファクタリングと同様に<strong>プロダクトの成長に伴ってリファクタリングしていくことが正</strong>だと信じています。</p>
<p>モチベーションにて記載した通り、これら複数の課題は開発環境のノイズであり、除去することによって、より良い DX が得られると考えています。他にこのようなリファクタリングを行うことによって、プロダクトをより堅牢にできるという側面もあります。</p>
<p>上記の2つの課題に対してそれぞれ「ライブラリを定期的にアップデートする運用手段を設けた」「package.json を SPA 単位に分割した」話をこれからしていきます。</p>
<h1 id="フロントエンド開発環境のリファクタリング">フロントエンド開発環境のリファクタリング</h1>
<h2 id="ライブラリの定期的なアップデートをする運用手順を設けた">ライブラリの定期的なアップデートをする運用手順を設けた</h2>
<h3 id="手動アップデート">手動アップデート</h3>
<p>ライブラリをアップデートするにはコマンドを叩くだけだと考えていましたが、依存している別のライブラリに影響が本当にないかなど調査する必要があると知り、ライブラリのアップデート方法を模索するところから開始しました。</p>
<p>ライブラリではないですが、Node.js のアップデートをしようとすると、node-sass や Firebase が影響していたりして、芋づる式で根っこにあるライブラリのアップデートをする必要が出てきたりするので、一つ一つ問題がないか調査するのが大変でした。</p>
<p>何より、アップデート対象ライブラリのリリースノートに Breaking Changes が書かれていなかったり、semver が守られているかわからなかったりと、プロダクトに影響がないか調べる必要があり、問題の切り分け方が難しかったのです。</p>
<p>ここで得られたライブラリアップデートの安全性担保のプラクティスとして <strong>webpack によるビルド結果が変わらないケース</strong>と、<strong>QA テストによって担保するケース</strong>があることがわかりました。前者は webpack による成果物が変わらないのであれば今回のアップデートが安全であるといえ、後者はエンジニアと QA エンジニアによってライブラリの影響範囲にハレーションがないことを確かめて安全であるといえるというものです。</p>
<h3 id="renovate-の運用開始">renovate の運用開始</h3>
<p>数カ月間は上記のようにライブラリのアップデートを手動で行っていましたが、確認工数が増えてしまい、他のタスクの時間を圧迫してしまうほどでした。</p>
<p>そこで、アップデートを自動化する <a href="https://www.whitesourcesoftware.com/free-developer-tools/renovate">renovate</a> と<a href="https://dependabot.com/">dependabot</a> を視野に入れました。renovate は、dependabot に比べて高機能でかつ、無料であるという理由で選定しました。</p>
<p>運用当初、renovate が Pull Request を作成してくれたり、diff によりライブラリの変更点が見やすかったりと、恩恵を感じていました。しかし、徐々に「アップデート対象が多く、それぞれがどういうライブラリで、影響範囲がどこなのか」ということの調査に時間が取られるようになってしまいました。</p>
<p>ここで得られた調査時間を短縮するプラクティスとして**「本番影響のあるもの」「開発向け」「ビルド周り」と renovate から来る Pull Request を整理する**ことです。このような整理を行うことで、本番影響のあるものに注力してレビューできるようになり、苦にならずにアップデートをできるようになりました。</p>
<h3 id="結果">結果</h3>
<p>ライブラリアップデートの運用手順を設けることによって、今まで以上に堅牢な環境になりました。それから、renovate によって自動的に重要な(本番影響のある)ライブラリのみに集中してレビューを行うことによって、少ない工数でアップデートしていけるようになりました。</p>
<h2 id="packagejson-を-spa-単位に分割">package.json を SPA 単位に分割</h2>
<p>課題整理で記載した通り、電子カルテ・オンライン診療、社内管理 Web アプリ、患者 Web アプリはそれぞれ別の SPA として作られていますが、1つの package.json で管理しています。ですのでそれぞれの SPA が同じ依存パッケージを使わなければなりません。</p>
<p>弊害として package.json に対して1つの変更があったときにすべての SPA に影響が出てしまいます。ですので、この肥大化した package.json をそれぞれの SPA に分割しようとしました。</p>
<p>package.json を SPA 単位に分割することは<strong>責務分離</strong>という側面もあり、ライブラリだけでなく、共通していた定数、ロジック、コンポーネント、webpack.config.js、babel.config.js と tsconfig.json などすべてをそれぞれの SPA に依存のない形に閉じるようにしました。これらの分割する作業は非常に泥臭いもので、本記事に記載するほどのものではありませんが、得られた結果について記載していこうと思います。</p>
<h3 id="結果-1">結果</h3>
<p>まず、責務分離ができたので、1つの SPA に対する変更があったときに、他の全ての SPA に対する影響が出なくなりました。よって、1つの SPA に対して新たな Web フレームワークやライブラリを試すことが容易になりました。他にも、1つの webpack ですべての SPA をシーケンシャルにビルドしていたのに対して、現在はパラレルでビルドできるようになりビルド時間が短縮されたため、今まで以上にコミットからデプロイまでのイテレーションが小さくなりました。</p>
<p>これらの結果からフロント開発環境の改善および DX 向上が果されました。</p>
<h1 id="今後の課題">今後の課題</h1>
<p>持続的なリファクタリングをする仕組み作り</p>
<p>「ライブラリの定期的なアップデートをする運用手順を設けた」はまさに持続的にライブラリをアップデートするための手段です。「package.json を SPA 単位に分割する」もそれぞれの SPA をメンテナンスしやすい環境作りとしては欠かせない作業でした。</p>
<p>しかし、このままリファクタリングを中断すれば、プロダクトの規模が大きくなるときやフロントエンドの時流によって再びメンテナンスしづらい環境になってしまいます。</p>
<p>なので、持続的なリファクタリングをするためには仕組み作りが欠かせないと考えています。そのためには、属人化によらない仕組みづくり、メンテナンスしやすい環境改善、エンジニアそれぞれのフロントエンド開発環境に対するリテラシを高める取り組みを行っていく必要があります。そのため、現在<a href="/entry/2020/08/14/135516">横軸勉強会</a>などで CLINICS フロントエンドの実装背景や、リファクタリングしやすい書き方などのナレッジを共有しています。</p>
<h1 id="まとめ">まとめ</h1>
<p>フロントエンド開発環境のメンテナンス・リファクタリング自体はあくまでもユーザに新しい機能を提供しているわけではなく、粛々と行っていくものです。しかし、課題を洗い出し、向き合って、解決していったことによって得られたプラクティスは多くあり、フロントエンドのエコシステムに対する理解も多く得られました。</p>
<p>これらのリファクタリングを行うことによって DX が向上していき、技術的なノイズに悩む時間が減り、エンジニアはよりプロダクトの機能開発に専念できるようになっていると信じています。</p>
<p>今回私たちが課題を解決したことによって、持続的にリファクタリングをしやすい土台作りをしたという側面もあると思います。今後の課題として、この土台を基にそれぞれのエンジニアが意識を持ってメンテナンスできるような仕組みづくりも行っていきたいと思います。</p>
<p>最後まで読んでいただきありがとうございました。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集の一覧 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>medley
- iOSDC Japan 2020 にメドレーが協賛しましたhttps://developer.medley.jp/entry/2020/10/02/181443https://developer.medley.jp/entry/2020/10/02/181443皆様こんにちは。インキュベーション本部エンジニアの濱中です。
9/19〜21 にiOSDC Japan 2020(以下 iOSDC)が開催されました。先日の記事の通り、メドレーは 2017 年より iOSDC に協賛しております。
メドレー...Fri, 02 Oct 2020 09:14:43 GMT<p>皆様こんにちは。インキュベーション本部エンジニアの濱中です。
9/19〜21 に<a href="https://iosdc.jp/2020/">iOSDC Japan 2020</a>(以下 iOSDC)が開催されました。<a href="/entry/2020/09/14/181100">先日の記事</a>の通り、メドレーは 2017 年より iOSDC に<a href="https://iosdc.jp/2020/sponsor.html">協賛</a>しております。
メドレーでは、Swift を利用してオンライン診療/服薬指導アプリ「CLINICS」iOS 版の開発をしています。</p>
<p><a href="https://apps.apple.com/jp/app/clinics-%E3%82%AF%E3%83%AA%E3%83%8B%E3%82%AF%E3%82%B9/id1106261604?uo=4&at=10l8JW&ct=hatenablog">CLINICS(クリニクス) オンライン診療・服薬指導アプリ</a></p>
<p>5 回目となる今回は、初のオンライン開催となり、主にニコニコ生放送、Discord 上で発表・コミュニケーションが行われました。私が iOS 版 CLINICS の開発に携わっている縁で、今回スポンサー枠として iOSDC に参加させていただきましたので紹介させていただきます。</p>
<h1 id="イベント全体について">イベント全体について</h1>
<p>オンライン開催となったため、会場の様子や企業ブースなど、雰囲気の伝わる写真をお届けできないのが残念ですが、発表の主会場となったニコニコ生放送では、終始穏やかな雰囲気でありつつも活発にコメントがなされ、大いに盛り上がっていました。</p>
<p>前回同様、初日は day 0 として夕方から、2 日目以降は朝〜夕方まで、最大 5 つのチャンネルで並行して発表が行われました。セッションについては事前に録画したものを放送し、LT のみリモートにてリアルタイム発表という形式となっていました。</p>
<p>質疑応答については、各発表の終了直後に Discord チャンネルに発表者が待機して対応していました。初のオンライン開催ということで、イントロダクション動画をはじめとし、各所で積極的なフィードバック・コミュニケーションが奨励されていたように思います(なお、イントロダクション・スポンサー紹介の各動画はナレーションが声優の緒方恵美さん・三石琴乃さんと、とても豪華でした。生放送中のコメントや、<a href="/entry/2018/09/13/175702">18 年の弊社ブログ</a>を見る限りは毎年恒例のようですね。すごいです!)。</p>
<iframe src="https://www.youtube.com/embed/XBytN1a7MPg" width="560" height="315" frameborder="0" allowfullscreen></iframe>
<h1 id="セッションについて">セッションについて</h1>
<p>昨年 iOS13 とともに発表された SwiftUI への移行や、コード移行・モジュール分割等、プロジェクトの最適化についてのトピックが多かったように思います。</p>
<p>SwiftUI は、従来 Storyboard で設計していた UI をコードベースで記述できる画期的なフレームワークですが、SwiftUI を使ったアプリは iOS13 未満の端末では利用できなくなってしまうこともあり、アプリの公開対象を広めにもっておきたい場合はなかなか乗り換えづらい印象でした。2020 年 6 月現在で iOS13 のシェアが 9 割以上となったことで、ちょうど iOSDC での発表トピックを決めるころに導入作業を行った(かつ苦労した)というケースが多かったのかな、と思います。</p>
<p>以下、視聴したセッションのうち気になったものをいくつかご紹介いたします。</p>
<h2 id="オープンソースの-altswiftui-の発表">オープンソースの AltSwiftUI の発表</h2>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="オープンソースの AltSwiftUI の発表" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2020%2Fproposal%2F3ab710d8-4b19-4a57-b14c-8e23368dec27" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://fortee.jp/iosdc-japan-2020/proposal/3ab710d8-4b19-4a57-b14c-8e23368dec27">fortee.jp</a></cite>
<p>楽天のエンジニアの方による、SwiftUI の提供するネイティブコンポーネントに対応しつつ、iOS11 以上で利用可能なオープンソースのフレームワーク<a href="https://github.com/rakutentech/AltSwiftUI">AltSwiftUI</a>(<a href="https://altswiftui.com/">公式 Doc</a>)の開発についてのセッションでした。</p>
<p>iOS12 以下の対応を切らずに SwiftUI への乗り換えを進められる(かつ本家と違ってオープンソースである)便利さもそうですが、別途フレームワークが出来上がってしまうあたりに、SwiftUI への移行対応への苦労がしのばれる内容でもありました(ストアにある楽天提供の iOS アプリの数を考えると乗り換えコストが大変そう…)。</p>
<h2 id="それ自動化できますよ-note-を支えるワークフロー大全">「それ、自動化できますよ」: note を支えるワークフロー大全</h2>
<iframe id="talk_frame_667616" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/44078ad96961436287ee7c2af9d5ad87" width="710" height="371" frameborder="0" allowfullscreen></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/laprasdrum/ios-dev-workflow-automation-for-note">speakerdeck.com</a></cite>
<p>改修要望が上がってから、実際に改修を行ってアプリをリリースするまでの作業をできる限り自動化した、というセッションです。CLINICS でも、「証明書の有効期限確認、プッシュからのテスト、マージからのリリース準備」は Bitrise のトリガを利用して自動化しています。</p>
<p>上記のような、GitHub 上でのアクション(プッシュ、マージなど)をトリガとする自動化はよく聞く話ではあるのですが、Slack のポストに特定のスタンプつけると Issue 化、はちょっと目新しくて面白いなと思いました(気をつけないと表記揺れで同じような Issue が乱立しそうですが)。</p>
<p>Issue もきちんと管理しておかないとトラブルの起きやすい部分ですよね。起票者と実装者の間でボールが浮いてしまったり、プロジェクトに紐づいていなかったために対応から洩れてしまったり…。</p>
<p>また、このスライドですが終始手書きの挿絵がかわいくて、そういった意味でもコメント欄が盛り上がっていたのが印象的でした。</p>
<h2 id="100-人でアプリをリファクタリングして見えてきた最強の-ios-アプリ設計に求められること">100 人でアプリをリファクタリングして見えてきた、最強の iOS アプリ設計に求められること</h2>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="100 人でアプリをリファクタリングして見えてきた、最強の iOS アプリ設計に求められること" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2020%2Fproposal%2Fa90d763e-d8d4-4d4f-b5ca-927ad253bc2a" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://fortee.jp/iosdc-japan-2020/proposal/a90d763e-d8d4-4d4f-b5ca-927ad253bc2a">fortee.jp</a></cite>
<p>アプリを長期に運用していくとほぼ必須となる課題でありながら、人ごとに基準が曖昧だったり、機能開発におされて対応が後手後手になったり…と、何かとつらい話をよく聞くソースコードのリファクタリングに関するセッションでした。</p>
<p>同じ状態のソースコードを多数のエンジニアがリファクタした結果を比較することで、「良いリファクタリングのための考え方」とは何か?を模索した内容です。</p>
<p>ビューとロジックの分割をしっかり行う、というのは PR レビューでエンジニアが口を酸っぱくしてよく言われることではありますが、「ロジックの中でも、アプリの仕様に依存するものとそうでない普遍的なものは分離すべき」というアイデアは個人的には眼から鱗が落ちるものでした。</p>
<p>また、これを説明するリバーシの具体例(「対戦相手が人間か AI か」はアプリ仕様に依存するロジック、「そこに石を置けるか」は普遍的なロジック=リバーシのルールそのもの)も非常にわかりやすかったです。</p>
<p>そのほか、React / Redux でフロントエンド開発を行っている人にはお馴染みの Action / Reducer を使った単一方向のデータフローの導入なども紹介されています。</p>
<h2 id="新規機能開発からモジュール分割を始めてみる">新規機能開発からモジュール分割を始めてみる</h2>
<iframe id="talk_frame_667466" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/be43e85126b546cc82a1ca471dda6969" width="710" height="399" frameborder="0" allowfullscreen></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/rizumi/xin-gui-ji-neng-kai-fa-karamoziyurufen-ge-woshi-metemiru">speakerdeck.com</a></cite>
<p>一つのアプリが長期間運用されていくなかで、複数の機能が統合されたスーパーアプリになることがあります。そうなった場合、ソースコードが肥大化 → ビルド・テストも長大化、となってメンテナンス性が低下するため、対策としてコードを分割してテストやビルドの単位を小さくする必要があります。</p>
<p>いきなりアプリ全体をモジュールに分割するのは時間がかかるため、本セッションではまず第一段階として新規に開発する機能を別モジュールとして実装し、その時得た知見について触れられていました。</p>
<p>「分割したモジュール側でのサードパーティ製フレームワークのリンク方法(※リンクを正しく行わないと、ビルドして動作はするのにアーカイブに失敗してリリースできなくなる)」などは、今後の開発にモジュール分割を取り入れていく際に参考になりそうです。</p>
<h2 id="swift-で始める静的解析">Swift で始める静的解析</h2>
<iframe id="talk_frame_667483" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/fdf9c396f7c84d7c9ebc0774947fd6a7" width="710" height="399" frameborder="0" allowfullscreen></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/matsuji/swiftdeshi-merujing-de-jie-xi">speakerdeck.com</a></cite>
<p>Swift ソースコードからの構文木の生成、解析を行うライブラリ SwiftSyntax の紹介と、それを用いたソースコード重複検出機能の実装についてのセッションでした。</p>
<p>普段 Xcode を始めとした IDE で開発していると、コード重複、不要なローカル変数、型や Nullable の不一致に変数リファクタリング…等々の便利な機能を気軽に使えてしまいますが、その裏の動作を改めて一つずつ具体的に追っていくと、そのありがたみが身に染みます…。</p>
<p>静的解析そのものは Swift に限らず様々な言語のソースコードに対して適用できるトピックではあるのですが、普段何気なく使ってしまう IDE の機能について考えるよい機会になったため、紹介させていただきました。</p>
<h1 id="まとめ">まとめ</h1>
<p>ちょうど業務でも iOS アプリ開発を担当していることもあり、興味深い知見が得られ、よい経験となりました。また、事前録画形式になったことで発表の構成がよく練られ、結果として聞きやすく(皆様かなり気をつけてゆっくり発声されていました)、個性のある発表が多かったように思います。</p>
<p>今回、初のオンラインでの開催ということで、運営委員会の皆様もいろいろと苦労されていらっしゃるようでした。ただ、その甲斐あってか当日の進行はスムーズで、会場はとても盛り上がっていました。関係者の皆様、本当にお疲れ様でした。</p>
<p>情勢を踏まえ、来年度の開催可否・形式は未定とのことでしたが、オンライン・対面のそれぞれの良さを取り入れつつ、より多くの人が参加できる形態になっているとよいなと思います。</p>
<p><a href="https://www.youtube.com/channel/UCF-W8FRL7d_9konHA9eNObA">公式 YouTube チャンネル</a>で過去の発表を視聴できますので、ご興味のある方はぜひどうぞ(今年の発表分も一ヶ月ほどしたら公開されるとのことでした)。</p>
<p>またメドレーでは iOS / Android ネイティブアプリ開発エンジニアを募集しています。興味がある方、ぜひお気軽にお話しましょう!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集の一覧 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>medley
- 2020 年度新卒エンジニア研修についてhttps://developer.medley.jp/entry/2020/09/25/174527https://developer.medley.jp/entry/2020/09/25/174527こんにちは。ジョブメドレーの開発チームでエンジニアをしている新居です。
はじめに
2020 年 4 月に、新卒エンジニア 3 名が入社しました。
入社後は新卒エンジニア研修を実施し、先日 8 月 25 日の最終報告会をもって終了しました。
...Fri, 25 Sep 2020 08:45:27 GMT<p>こんにちは。<a href="https://job-medley.com/">ジョブメドレー</a>の開発チームでエンジニアをしている新居です。</p>
<h1 id="はじめに">はじめに</h1>
<p>2020 年 4 月に、新卒エンジニア 3 名が入社しました。
入社後は新卒エンジニア研修を実施し、先日 8 月 25 日の最終報告会をもって終了しました。</p>
<p>コロナウイルスの影響で入社間もなくフルリモート勤務となり、不慣れなところもありましたが、本年度の研修の取り組みを紹介します。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200924/20200924130505.png" alt="20200924130505.png">
<p><em>2020 年度新卒エンジニア</em></p>
<h1 id="研修の概要">研修の概要</h1>
<p>メドレーでは昨年度から新卒エンジニアを迎えており、合わせて研修も開始しました。</p>
<p>初めての研修をどのような視点で計画・実施したかについては、昨年平木がこちらにまとめています。</p>
<p><a href="/entry/2019/12/16/165947">2019 年度エンジニア新卒の研修について - Medley Developer Blog</a></p>
<p>今年は人事部のご協力もいただきながら、昨年の内容を少しアップデートして行いました。</p>
<p>研修の目的は、<strong>新卒メンバーが同じ空間で互いに刺激し合いながら、社会人への思考転換をはかり、業務遂行に必要となる基礎知識とスキルを習得すること</strong>です。</p>
<p>研修は以下の 4 つのフェーズに区切って行いました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200924/20200924130533.png" alt="20200924130533.png">
<p><em>研修のフェーズ</em></p>
<h1 id="研修の内容">研修の内容</h1>
<p>ここから 4 つのフェーズ毎に内容を紹介します。</p>
<h2 id="フェーズ-1社会人メドレー基礎研修">フェーズ 1:社会人&メドレー基礎研修</h2>
<p><strong>1. オリエンテーション</strong></p>
<ul>
<li>メドレーの事業や組織、大切にしている行動規範などの概要説明</li>
<li>セキュリティ研修とコンプライアンス研修</li>
</ul>
<p><strong>2. ビジネス研修</strong></p>
<ul>
<li>ビジネスマナー研修</li>
<li>ビジネススキル研修</li>
<li>ビジネススタンス研修(外部研修)</li>
</ul>
<p>フェーズ 1 では「医療ヘルスケアの未来をつくる」メンバーの一員として大切にして欲しいことを学ぶフェーズでした。</p>
<p>社会人としての最低限のマナーやスタンスは勿論ですが、メドレーで働く上で土台となるマインドをここで学び、<strong>医療ヘルスケア分野の課題を解決する一員</strong>として共にプロダクトを作るためのベースを築けたと思います。</p>
<h2 id="フェーズ-2エンジニア基礎研修">フェーズ 2:エンジニア基礎研修</h2>
<p><strong>1. 開発基礎 1</strong></p>
<ul>
<li>講義「メドレーが求めるエンジニアとは」</li>
<li><a href="https://job-medley.com/">ジョブメドレー</a>と<a href="https://clinics.medley.life/">CLINICS</a>の事業・プロダクトについての概要説明</li>
<li>Ruby on Rails(以下 Rails)の基礎トレーニング</li>
</ul>
<p><strong>2. 開発実践</strong></p>
<ul>
<li>チーム開発体験</li>
</ul>
<p><strong>3. 開発基礎 2</strong></p>
<ul>
<li>書籍<a href="https://www.amazon.co.jp/dp/4774142042/">『Web を支える技術 -HTTP、URI、HTML、そして REST』</a>の輪読会</li>
<li>ドキュメンテーション研修とプレゼンテーション研修</li>
<li>中間報告会の準備と実施</li>
</ul>
<p>フェーズ 2 から開発の研修がスタートしました。</p>
<h3 id="開発基礎-1">開発基礎 1</h3>
<p>開発の研修に入る前に、エンジニアの執行役員 田中から「メドレーが求めるエンジニアとは」というテーマで講義を行いました。メドレーがどういうエンジニアを求めているのか、目指すところの視点合わせを行い、これから行う研修に対する意識や取り組みの質を上げることを目指しました。</p>
<p>メドレーが求めるエンジニア像については CTO 平山の記事にも詳しく書いています。</p>
<p><a href="https://toppa.medley.jp/05.html">メドレー平山の中央突破: THE エンジニア</a></p>
<p>その後ジョブメドレーと CLINICS の事業・プロダクトについての概要説明、Rails の基礎トレーニングを行いました。メドレーのプロダクトは Rails 製です。Rails やウェブアプリケーション開発に慣れていない新卒メンバーは、Rails の基礎トレーニングで<a href="https://railstutorial.jp/">Ruby on Rails チュートリアル</a>を使って基礎からみっちり学びます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200924/20200924130918.png" alt="20200924130918.png">
<p><em>Ruby on Rails チュートリアルの進め方</em></p>
<p>ここで大切なことは、漠然とひたすら写経してチュートリアルを進めるのではなく、毎日のフィードバック会でしっかりその日の進捗や学びを共有し、不明点はメンターに質問してもらうようにすることです。</p>
<p>メンターは、新卒メンバーが理解が浅いまま進めてたり、理解していて欲しいところがいい加減になってたり、進め方や学びの方向性がズレてる場合などはアドバイスを入れて軌道修正することを心掛けました。</p>
<p>また「実際の開発ではこうだよ」といった実務を踏まえたアドバイスや、意識していることも伝えていきました。</p>
<p><em>20 新卒 S さんの感想</em></p>
<blockquote>
<p>「毎日のフィードバック会の中で、その日学んだ技術がジョブメドレーではどのように利用されているのか、どういったことに留意して利用しているのかなどを確認できたので、現場の人の感覚を少しずつ知ることができた。」</p>
</blockquote>
<p>新卒メンバーは毎日リズム良く進捗を出し、メンターは新卒メンバーのフォローと引き上げを意識し、成果を最大化できるよう努めました。</p>
<h3 id="開発実践">開発実践</h3>
<p>続いて開発実践ではチーム開発を行いました。前回までは各自個別に進めていましたが、ここではジョブメドレーに関する課題解決を目的としたプロジェクトに対して、チームで向き合う研修でした。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200924/20200924131002.png" alt="20200924131002.png">
<p><em>開発実践の目的と達成すべきこと</em></p>
<p>実務ではチーム開発が基本となり、チームメンバーとの協働は必須です。学生時代に業務レベルのチーム開発を経験しているのは稀ですし、実務に入る前にチームでプロジェクト推進するとはどういうことかを知るのは、とても価値があると思います。</p>
<p>加えて、チームで課題をどう解決していくのかも、新卒メンバー同士で話し合って決める必要があるので、課題解決力も養われたと思います。</p>
<p>今回はプロトタイプを作って成果発表するところまででしたが、プロジェクト推進においてはスケジューリング、要件定義、各種設計、開発フローやコミュニケーションフローの整備、実装、テスト、などなど、やることは尽きません。</p>
<p>また、このプロセスの中で密なコミュニケーションが必要不可欠となるので、新卒メンバー間のチームワークも向上し、お互いのことを更に知ることができたと思います。</p>
<p>この段階で、チームでプロジェクトを推進することの全体感を知り、プロジェクト推進の苦悩苦闘を実体験できたのは大きな経験値になったと思います。</p>
<p><em>20 新卒 O さんの感想</em></p>
<blockquote>
<p>「各機能の影響を互いに受けないためにブランチを細分化するブランチ戦略や GitHub を用いたコード管理について理解できた。一方で、UI を考える際にチームの各メンバーの認識のズレが生じたことや、序盤は各メンバーの進捗の詳細を把握できていなかったことから報・連・相の重要性を再認識した。」</p>
</blockquote>
<p><em>20 新卒 T さんの感想</em></p>
<blockquote>
<p>「初めてのチーム開発であったことから、実装の序盤は、どのファイルなら編集しても他のメンバーの作業に影響がないのか、動作確認のための画面を作るファイルは自由に作成して良いのか、どのテーブルから関係する他のテーブルの情報を含めた情報を取得をするかなどに悩んでいた。今になって振り返るとチーム内で話せば解決することに対して、技術的にも心理的にも難しさを感じた。」</p>
</blockquote>
<p>###開発基礎 2</p>
<p>フェーズ 2 最後の開発基礎 2 では、書籍<a href="https://www.amazon.co.jp/dp/4774142042/">『Web を支える技術 -HTTP、URI、HTML、そして REST』</a>の輪読会を行いました。もっと前のフェーズでの実施も検討しましたが、開発基礎 1 と開発実践を経た後の方が書籍の内容の納得感が高くなるだろうという判断で、この段階での実施となりました。</p>
<p>その後、中間報告会に向けたドキュメンテーション研修とプレゼンテーション研修を行いました。仕事を進めていく上では、背景や目的を正しくステークホルダーへ共有しながら進めていくことが必要で、伝えたいことを適切に文章として整理し、他者へ分かりやすく伝えていくことが求められます。中間報告会では、そういったことを意識しながらこれまでの研修でやってきたことや成果、学びをレポートにまとめ、プロダクト開発室の室長と副室長、メンター陣の前でプレゼンテーションして報告しました。</p>
<h2 id="フェーズ-3事業部-ojt">フェーズ 3:事業部 OJT</h2>
<p><strong>1.代表取締役医師 豊田の講義</strong></p>
<ul>
<li>日本における医療制度の課題とそれに対するメドレーの位置付けについての講義</li>
</ul>
<p><strong>2.ジョブメドレー開発 OJT</strong></p>
<ul>
<li>ジョブメドレーの実際の開発 Issue に対応し、ジョブメドレーの開発プロセスを体験</li>
</ul>
<p>フェーズ 3 の最初は豊田代表の講義を受けました。基礎的な研修を終え、ジョブメドレー開発 OJT に入る前にメドレー社の社会的意義などを改めて代表から伝えていただきました。</p>
<p>続いて、ジョブメドレー開発 OJT は以下の狙いを持って進めました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200924/20200924131055.png" alt="20200924131055.png">
<p><em>ジョブメドレー開発 OJT の狙い</em></p>
<p>実際に行ったことは、手始めに小さい不具合系 Issue の対応に取り組んだ後、サーバーレスポンス改善の Issue に取り組むというものでした。</p>
<p>サーバーレスポンスの改善は、1 人 1 画面を担当し、「プロファイルツールで分析 → 改善できそうな箇所を調査 → 改善方針の検討 → 提案 → 実装 → レビュー → リリース」というサイクルを回しました。</p>
<p>1 つ目の Issue 対応でも心掛けたことですが、言われたものを実装するのではなく、<strong>ある課題を解決するためにどうしたら良いかを自分で主体的に考えることを重視しました。実装力と同じくらい課題解決力も大切にしているためです。</strong></p>
<p>とはいえ成果が何も出せないというのは、精神衛生上良くないので、日々の朝会と夕会にて進捗や状況をしっかり確認しながら適宜フォローやインプットを行い、ヒントになりそうな過去 Issue のリンクを渡して参考にしてもらったり、メンターの適切なバックアップも必要です。</p>
<p>今回の OJT を通じて実務を知ることで、実際にエンジニアとして仕事をするイメージが沸き、同時に仕事をする上で足りないことも明確になります。自分の課題と向き合うことで、直近の自身の学習プランの軌道修正もできます。</p>
<p>課題は簡単なものではありませんでしたが、最終的に 1 人 1 つ以上のサーバーレスポンス改善 PR をリリースすることができました。</p>
<p><em>S さんの感想</em></p>
<blockquote>
<p>「速度パフォーマンスの悪いコードに対する嗅覚、オブジェクトを生成しすぎていないか、無駄に通信を走らせていないかなどを学べた。」</p>
</blockquote>
<p><em>O さんの感想</em></p>
<blockquote>
<p>「Issue を本質的に解決するメドレーの開発姿勢について学んだ。Issue を表面的に解決するのではなく、背景や目的、関連するコードなどの不明点を調査し、十分に理解・納得した上で修正することの意識付けができた。また、Issue に関連する一部分のコードを修正すれば良いという狭い視野を持たず、修正による影響範囲を考慮した上で全体の最適化を考えることが重要であると学んだ。」</p>
</blockquote>
<h2 id="フェーズ-4最終報告">フェーズ 4:最終報告</h2>
<p><strong>1. 最終報告会</strong></p>
<ul>
<li>最終報告会の準備と実施</li>
</ul>
<p>最後のフェーズ 4 では、これまでの研修でやってきたことや成果、学びをレポートにまとめ、役員陣の前でプレゼンテーションして報告しました。</p>
<p>役員陣の前なので緊張は最高潮になりますが、自分達が研修を通じて何を得てどう成長したか、今後どういうエンジニアを目指していくのか、などを役員陣の前でアピールする貴重な機会となりました。</p>
<p>1 人 10 分の枠内で役員陣にどういう情報をどういう表現でプレゼンテーションするかを考える機会にもなったので、情報整理力や表現力が培われる場にもなりました。</p>
<h1 id="さいごに">さいごに</h1>
<p>2020 年度の新卒エンジニア研修も無事終了することができました。改めて、新卒メンバーのみなさん、関係者のみなさん本当にお疲れ様でした。</p>
<p>振り返ると、メドレー社員としてのマインドや開発の基本的なことをしっかりインプットしつつ、新卒メンバーが主体的に課題解決に取り組む研修ができたように思います。</p>
<p>必要な技術スタックを 1 から 10 まで懇切丁寧に資料に落とし込み、講義形式で行う研修も身になることは多いですが、<strong>メドレーの研修のような課題解決を実体験する研修はより実務に繋がる実践的な研修だと思います。</strong></p>
<p>少しハードな面もあるかもしれませんが、このような研修を乗り越え、医療ヘルスケア分野の課題解決に取り組みたい学生エンジニアのみなさん、少しでも興味を持っていただけると幸いです。</p>
<p>もちろん中途エンジニアの方もぜひお気軽にお話をしましょう。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- Fargate 上で動くコンテナアプリケーションに SessionManager で接続するhttps://developer.medley.jp/entry/2020/09/18/180404https://developer.medley.jp/entry/2020/09/18/180404自己紹介
株式会社メドレーのエンジニア阪本です。
9 月に入っても暑い日が続く中、皆さんはいかがお過ごしでしょうか。
前回のブログでも書きましたが私は野球観戦(虎党)が趣味で、毎日の試合結果に一喜一憂しています。
このブログの執筆時点ではセ...Fri, 18 Sep 2020 09:04:04 GMT<h1 id="自己紹介">自己紹介</h1>
<p>株式会社メドレーのエンジニア阪本です。</p>
<p>9 月に入っても暑い日が続く中、皆さんはいかがお過ごしでしょうか。</p>
<p>前回のブログでも書きましたが私は野球観戦(虎党)が趣味で、毎日の試合結果に一喜一憂しています。
このブログの執筆時点ではセ・リーグは首位巨人にマジックが点灯し、残試合 50 を切った状況でのゲーム差 10。優勝を目指す阪神にとっては厳しい状況となりましたが、残り直接対決をすべて勝てば可能性は見えてくるはず。ここは諦めずに応援しようと思います。</p>
<p>次の記事を書く頃には何らかの決着が着いていると思いますので、その時には喜びのメッセージが書ける事を願っています。</p>
<h1 id="はじめに">はじめに</h1>
<p>突然ですが、皆さんは<a href="https://aws.amazon.com/jp/fargate/">Fargate</a>を使いこなしていますか?
今まで ECS(EC2)での運用がメインでしたが、ジョブメドレーのシステムでも Fargate に触れる機会が増えてきました。
筆者自身は初めての Fargate でしたが、EC2 の存在が無くなることに不思議な感覚を覚えつつも、管理するものが減って運用が楽になったと実感しています。</p>
<p>しかし、EC2 が無くなった事によりサーバ内にターミナルで入る手段を失いました。
公式の<a href="https://aws.amazon.com/jp/blogs/startup/techblog-container-fargate-1/#04">ガイド</a>では</p>
<blockquote>
<p>質問2:コンテナの中に ssh や docker exec で入ることは出来ますか?</p>
<p>こちら現状サポートしておりませんが、コンテナワークロードでは「<a href="https://12factor.net/ja/dev-prod-parity">同一の環境でしっかりテストを行う</a>」ことや「<a href="https://12factor.net/ja/logs">ログを外部に全て出す</a>」ことがベストプラクティスとされていて、コンテナに入る必要性を出来る限り減らす方が将来的にも好ましいです。</p>
</blockquote>
<p>と「事前にテストを十分に実施する」「ログは(S3 や CloudWatchLogs など)外部に出力する」
さえ出来ていれば入る必要が無いはずと記載されているものの・・・心配性な自分は不測の事態が発生した時の事を考えてサーバに入る手段を求めてしまいます。</p>
<p>そこで代替手段を模索していた所、<a href="https://aws.amazon.com/jp/systems-manager/features/#Session_Manager">SessionManager</a>を使えば可能との文献を見つけました。
すでに SessionManager 自体は EC2 に対して運用を行っていたため導入の敷居は低いと考え、この手段を採用することにしました。</p>
<h1 id="session-manager-とは">Session Manager とは?</h1>
<p>Session Manager とは AWS の Systems Manager サービスの 1 機能で、AWS 上で管理しているサーバ(マネージドインスタンス)に対してターミナルのアクセスを可能にするものです。</p>
<p>ターミナルへのアクセス手段は、よくある手段だと SSH がありますが
この方法は SSH ポートを外部に開放する必要があるため攻撃の対象になりやすく、あまり好ましくありません。</p>
<p>それに比べて Session Manager は SSH ポートの開放は不要でサーバから見てアウトバウンド方向の HTTPS 通信の確保で済む為、外部からの攻撃も受けず安全にアクセス経路の確保が実現できます。
※通信経路の確保以外にも IAM ロールの設定などが必要となります。詳しくは<a href="https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-quick-setup.html#quick-setup-instance-profile">こちら</a>を参考</p>
<p>「AWS 上で管理しているサーバ」と先に記載しましたが、このサーバは EC2 インスタンスに限らないのが本記事の重要なポイントになります。
オンプレミスなサーバも AWS 上で管理されているのであれば、SessionManager エージェントをインストールする事により管理対象に含めることが可能になるのです。</p>
<p>今回は EC2 で動いていた SessionManager エージェントを Fargate で動くコンテナにインストールする事によりコンテナ自体をサーバと見立ててマネージドインスタンスとし、SessionManager でアクセスする事を目指します。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200918/20200918142724.png" alt="20200918142724.png">
<h1 id="対応">対応</h1>
<h2 id="systems-manager-インスタンス枠をスタンダードからアドバンスドに変更する--aws-コンソールでの設定">Systems Manager インスタンス枠をスタンダードからアドバンスドに変更する / AWS コンソールでの設定</h2>
<p>オンプレミスなサーバに SessionManager にてアクセスする場合、Systems Manager のオンプレミスインスタンスティアを<a href="https://aws.amazon.com/jp/systems-manager/pricing/#On-Premises_Instance_Management">スタンダードからアドバンスドへと変更する必要</a>があります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200918/20200918142745.png" alt="20200918142745.png">
<p>この変更作業は AWS コンソールから行う事になりますが、アドバンスドへの変更に当たって既存のマネージドインスタンスに影響することはありません。
ただし、この逆の場合、スタンダードに戻す際には考慮が必要となるので<a href="https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-managed-instances-advanced-reverting.html">こちら</a>を参照ください。</p>
<h2 id="ssm-agent-のインストール--コンテナに対する設定">SSM Agent のインストール / コンテナに対する設定</h2>
<p>ここからは実際に動くコンテナに対する設定になります。
AWS のドキュメント<a href="https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/sysman-manual-agent-install.html">手動でインストール SSM エージェント EC2 で Linux のインスタンス</a>を参考に、OS に合った方法で SessionManager エージェントのインストールを行います。</p>
<p>インストール作業には通信などの時間が必要となるので、事前にコンテナイメージにインストールする形としました。</p>
<h2 id="コンテナをマネージドインスタンスとして登録する--コンテナに対する設定">コンテナをマネージドインスタンスとして登録する / コンテナに対する設定</h2>
<p>コンテナをマネージドインスタンスとして登録するため、コンテナのスタートアップ(Entrypoint)にて下記スクリプトを実行します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A9955"># 1. ハイブリッドアクティベーションの作成</span></span>
<span class="line"><span style="color:#9CDCFE">SSM_ACTIVATE_INFO</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">`</span><span style="color:#DCDCAA">aws</span><span style="color:#CE9178"> ssm create-activation </span><span style="color:#569CD6">--iam-role</span><span style="color:#CE9178"> service-role/AmazonEC2RunCommandRoleForManagedInstances </span><span style="color:#569CD6">--registration-limit</span><span style="color:#B5CEA8"> 1</span><span style="color:#569CD6"> --region</span><span style="color:#CE9178"> ap-northeast-1 </span><span style="color:#569CD6">--default-instance-name</span><span style="color:#CE9178"> medley-blog-fargate-container`</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 2. アウトプットからアクティベーションコード/ID の抽出</span></span>
<span class="line"><span style="color:#9CDCFE">SSM_ACTIVATE_CODE</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">`</span><span style="color:#DCDCAA">echo</span><span style="color:#9CDCFE"> $SSM_ACTIVATE_INFO</span><span style="color:#D4D4D4"> |</span><span style="color:#DCDCAA"> jq</span><span style="color:#569CD6"> -r</span><span style="color:#CE9178"> '.ActivationCode'`</span></span>
<span class="line"><span style="color:#9CDCFE">SSM_ACTIVATE_ID</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">`</span><span style="color:#DCDCAA">echo</span><span style="color:#9CDCFE"> $SSM_ACTIVATE_INFO</span><span style="color:#D4D4D4"> |</span><span style="color:#DCDCAA"> jq</span><span style="color:#569CD6"> -r</span><span style="color:#CE9178"> '.ActivationId'`</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 3. コンテナ自身をマネージドインスタンスへの登録</span></span>
<span class="line"><span style="color:#DCDCAA">amazon-ssm-agent</span><span style="color:#569CD6"> -register</span><span style="color:#569CD6"> -code</span><span style="color:#9CDCFE"> $SSM_ACTIVATE_CODE</span><span style="color:#569CD6"> -id</span><span style="color:#9CDCFE"> $SSM_ACTIVATE_ID</span><span style="color:#569CD6"> -region</span><span style="color:#CE9178"> "ap-northeast-1"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 4. SessionManager エージェントの起動</span></span>
<span class="line"><span style="color:#DCDCAA">amazon-ssm-agent</span><span style="color:#D4D4D4"> &</span></span></code></pre>
<p>これらのコマンドについては各行ごとに説明します</p>
<h3 id="1ハイブリッドアクティベーションの作成">1.ハイブリッドアクティベーションの作成</h3>
<p>マネージドインスタンス登録に必要なアクティベーションコード/ID 取得のため、ハイブリッドアクティベーションの登録を行います。
後々 AWS コンソールにてインスタンスの識別ができるように、<code>--default-instance-name</code>オプションにて<code>medley-blog-fargate-container</code>という名前を付与しています。</p>
<p>このコマンドのアウトプットにて下記のような JSON が返される為、一旦変数に格納しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#D4D4D4">{ </span><span style="color:#DCDCAA">"ActivationId"</span><span style="color:#DCDCAA">:</span><span style="color:#CE9178"> "<<アクティベーション ID>>",</span><span style="color:#CE9178"> "ActivationCode":</span><span style="color:#CE9178"> "<<アクティベーションコード>>"</span><span style="color:#CE9178"> }</span></span></code></pre>
<p>また<code>--iam-role</code>オプションでサービスロール AmazonEC2RunCommandRoleForManagedInstances を使っています。
このロールがまだ存在しない場合、AWS コンソールからハイブリッドアクティベーションから IAM ロール →「必要な権限を備えたシステムのデフォルトコマンド実行ロールを作成」により作成してください。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200918/20200918142823.png" alt="20200918142823.png">
<h3 id="2-アウトプットからアクティベーションコードid-の抽出">2. アウトプットからアクティベーションコード/ID の抽出</h3>
<p>前項のアウトプットからアクティベーションコード/ID を個々の変数に分離しています。</p>
<h3 id="3-コンテナ自身をマネージドインスタンスへの登録">3. コンテナ自身をマネージドインスタンスへの登録</h3>
<p>取得したアクティベーションコード/ID を利用し、コンテナ自身をマネージドインスタンスとして登録します。
環境変数<code>AWS_DEFAULT_REGION</code>のように AWS の Credential にてデフォルトの Region 設定があった場合でも、このコマンドではオプションで Region 指定が必須となるのでご注意ください。</p>
<h3 id="4-sessionmanager-エージェントの起動">4. SessionManager エージェントの起動</h3>
<p>最後に SessionManager エージェントを起動します。
これにより AWS との通信が確立され、SessionManager が利用できるようになります。</p>
<p>この状態でマネージドインスタンス画面にて目的のインスタンスが「オンライン」であれば設定が完了です。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200918/20200918142901.png" alt="20200918142901.png">
<h2 id="接続確認">接続確認</h2>
<p>設定が完了したので、実際に SessionManager にて接続してみます。
AWS コンソールのセッションマネージャからセッションの開始を選択し、名前が事前に登録した<code>medley-blog-fargate-container</code>であるものを選択します。
外見は他の EC2 と変わらない表示ですが、この手段で登録したものはインスタンス ID が<strong>mi</strong>から始まる ID になります(通常の EC2 は<strong>i</strong>から始まる ID)。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200918/20200918142910.png" alt="20200918142910.png">
<p>後は普段通りにセッションを開始するとコンテナ内へのアクセスが開始されます。
これで EC2 の無い Fargate 上のコンテナに対してもターミナルに入ることができました。</p>
<h1 id="運用してみての感想">運用してみての感想</h1>
<p>Fargate 上のコンテナに直接入ることにより、今まで出来なかった
<code>top</code>コマンドによるプロセス状態の確認
<code>df</code>コマンドによる Disk 容量の確認
などの環境調査が出来るようになりました。
利用頻度が多い訳ではありませんが、調査の選択肢を増やす事により有事の際の早期対応に繋げれられていると感じています。</p>
<h1 id="注意点">注意点</h1>
<p>ここからは運用に当たっての注意点について数点紹介します。</p>
<h2 id="料金">料金</h2>
<p>EC2 での SessionManager と違い、<strong>この方法で登録したサーバへの SessionManager 通信は料金が発生します</strong>。
正確には
「SessionManager エージェントが起動し AWS と通信が確立している状態」
での時間課金で SessionManager での接続有無は問いません。
よって、常時必要ではない場合は SessionManager エージェントを止めて節約する方法を検討してください。</p>
<h2 id="アクティベーションやマネージドインスタンスがリストに残る">アクティベーションやマネージドインスタンスがリストに残る</h2>
<p>コンテナ終了時でも AWS コンソールのハイブリッドアクティベーション一覧やマネージドインスタンス一覧に表示が残ってしまうようです。
一覧の上限の記載は見つからなかったものの、無限に増えるとも思えないので適時 Lambda を使って削除しています。</p>
<h2 id="sessionmanager-での操作について">SessionManager での操作について</h2>
<p>EC2 インスタンスに対しての SessionManager と同様ですが、セッション開始直後のユーザは ssm-user 固定になるようです。
特定ユーザでの操作が必要となる場合、<code>su</code>コマンドでのユーザ切り替えが必要になります。</p>
<h1 id="さいごに">さいごに</h1>
<p>今回のようにメドレーでは新たな技術を取り入れつつ、サービスの安定を目的とした様々な施策を導入しています。
こういった働き方に興味を持たれた方、まずは下記リンクからお気軽にお話しませんか?</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- iOSDC 2020 にシルバースポンサーとして協賛しますhttps://developer.medley.jp/entry/2020/09/14/181100https://developer.medley.jp/entry/2020/09/14/181100
みなさん、こんにちは。エンジニア・デザイナー採用担当の平木です。
メドレーでは iOS 開発コミュニティに微力ながら還元したいと、2017 年より iOSDC に協賛をさせていただいておりますが、今年もシルバースポンサーとして協賛させてい...Mon, 14 Sep 2020 09:11:00 GMT<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200914/20200914173618.png" alt="20200914173618.png">
<p>みなさん、こんにちは。エンジニア・デザイナー採用担当の平木です。</p>
<p>メドレーでは iOS 開発コミュニティに微力ながら還元したいと、2017 年より iOSDC に協賛をさせていただいておりますが、今年も<a href="https://iosdc.jp/2020/sponsor.html">シルバースポンサー</a>として協賛させていただくことになりました。</p>
<p><em>過去の参加レポート</em></p>
<p><a href="/entry/2018/09/13/175702">entry/2018/09/13/175702</a></p>
<p><a href="/entry/2017/09/27/120000">entry/2017/09/27/120000</a></p>
<h1 id="iosdc-初のオンライン開催">iOSDC 初のオンライン開催</h1>
<p>今年の iOSDC は初のオンライン開催ということで、例年とはまた違った楽しみ方ができそうです。</p>
<p>公式スタッフブログに記載されていますが、今年はノベルティグッズが<a href="https://blog.iosdc.jp/2020/08/21/novelty-items/">配送</a>されるとのことで、既に届いている方もいらっしゃるようです。弊社のパンフレットも同封させていただいてます。</p>
<p>今年の<a href="https://fortee.jp/iosdc-japan-2020/timetable">タイムテーブル</a>も大変興味深い内容が多く、今から視聴が楽しみですが(スポンサーチケットを頂いてはいますが、筆者も自腹でチケット購入しています)<a href="https://blog.iosdc.jp/2020/09/07/how-to-enjoy/">ニコニコ生放送での視聴</a>ということで、例年とは違い、コメントなどで盛り上がりがあるのではないかと、大変期待しています。</p>
<p>Ask the Speaker では<a href="https://discord.com/">Discord</a>を使用するということで、対面とはまた違ったコミュニケーションが取れそうです。参考 URL や図などがすぐに出してもらえたりなど、さらに学びが深くなるのではないかと思っています。</p>
<h1 id="最後に">最後に</h1>
<p>今回は直接会場でみなさんとの交流はできませんが、弊社のエンジニアも参加予定ですので、一緒に楽しんでいきたいと思います。後日イベントレポートもこちらで公開したいと考えています。</p>
<p>また、メドレーではこうしたイベントにも興味をお持ちの iOS エンジニアを絶賛募集中です。お気軽に下記よりご連絡いただければ、カジュアルにお話をしたいです!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- OpenAPI や Protocol Buffers のおかげで開発がかなり捗っている話https://developer.medley.jp/entry/2020/08/21/190934https://developer.medley.jp/entry/2020/08/21/190934こんにちは、インキュベーション本部エンジニアの加藤です。
主に CLINICS アプリの開発を担当しています。
はじめに
CLINICS アプリの開発では OpenAPI や gRPC を利用しています。
OpenAPI と gRPC の間...Fri, 21 Aug 2020 10:09:34 GMT<p>こんにちは、インキュベーション本部エンジニアの加藤です。
主に <a href="https://clinics.medley.life/">CLINICS アプリ</a>の開発を担当しています。</p>
<h1 id="はじめに">はじめに</h1>
<p>CLINICS アプリの開発では <a href="https://www.openapis.org/">OpenAPI</a> や <a href="https://grpc.io/">gRPC</a> を利用しています。
OpenAPI と gRPC の間には何の関係もないのですが、どちらも API の仕様をスキーマ言語で記述するという点では共通しています。
今回はこの API スキーマが開発にもたらすメリットについて紹介していこうと思います。</p>
<h1 id="api-ドキュメントとしてのスキーマ定義">API ドキュメントとしてのスキーマ定義</h1>
<p>既存のコードに機能を追加する際や修正を加える際に気にすることの多い部分は API の仕様ではないかと思います。
「リクエストやレスポンスはどのようなデータなのか」「この値は必須なのか、任意なのか」「データの型は数値なのか、文字列なのか」「フォーマットは決まっているのか」など、多くのことを把握する必要があります。</p>
<p>しかし、こういった API 仕様のドキュメントが整備されていないということも多いのではないでしょうか。
また、ドキュメントはあるけれどリクエストとレスポンスのサンプル JSON が貼ってあるだけだったり、時間が経つうちに実装とドキュメントが乖離してしまいアテにならなくなってしまっている場合もあります。
参考にできるドキュメントがない場合は、直接バックエンドの実装を見に行くこともあるのですが、目的のコードを探し出して読み解くのは意外と時間がかかります。
特に自分以外のエンジニアが実装した機能の場合、API の仕様を実装から紐解くのはかなり骨が折れる作業です。</p>
<p>CLINICS アプリの開発ではこういった手間を減らすために OpenAPI や gRPC を利用しています。
REST API のスキーマは OpenAPI を使って記述しています。
gRPC を利用している部分は <a href="https://developers.google.com/protocol-buffers">Protocol Buffers</a> で RPC のインターフェースが記述されるため、これが API のスキーマになっています。
OpenAPI や Protocol Buffers の定義を参照することで API の仕様が容易に把握できるため、非常に便利です。</p>
<p>OpenAPI や Protocol Buffers などのスキーマ言語の良い点は、宣言的な DSL でスキーマを定義できる点です。
実装言語に依存しない形での記述ができ、自然言語のような曖昧さもないためより正確に API 仕様を記述することができます。</p>
<p>ここからは OpenAPI や Protocol Buffers の実際に記述例を挙げながら、どういった形でスキーマを記述していくのか簡単に見ていきたいと思います。</p>
<h1 id="openapi">OpenAPI</h1>
<p>OpenAPI は REST API のインターフェースを記述する仕様です。
エンドポイントのリクエストパラメータやレスポンスの構造などを JSON や YAML で記述していくことができます。
かつては Swagger という名称だったため、そちらの名前で知っているという方もいらっしゃるかもしれません。</p>
<p>実際の OpenAPI の定義は以下のような形になります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="yaml"><code><span class="line"><span style="color:#569CD6">openapi</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">3.0.2</span></span>
<span class="line"><span style="color:#569CD6">info</span><span style="color:#D4D4D4">: { </span><span style="color:#569CD6">title</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">OpenAPI Sample</span><span style="color:#D4D4D4">, </span><span style="color:#569CD6">version</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">1.0.0</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6">paths</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#CE9178"> "/users/{user_id}"</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> get</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> parameters</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">user_id</span></span>
<span class="line"><span style="color:#569CD6"> in</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">path</span></span>
<span class="line"><span style="color:#569CD6"> required</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">true</span></span>
<span class="line"><span style="color:#569CD6"> schema</span><span style="color:#D4D4D4">: { </span><span style="color:#569CD6">type</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">string</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6"> responses</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#CE9178"> "200"</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> description</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">OK</span></span>
<span class="line"><span style="color:#569CD6"> content</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> application/json</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> schema</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> type</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">object</span></span>
<span class="line"><span style="color:#569CD6"> properties</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> id</span><span style="color:#D4D4D4">: { </span><span style="color:#569CD6">type</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">string</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: { </span><span style="color:#569CD6">type</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">string</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6"> age</span><span style="color:#D4D4D4">: { </span><span style="color:#569CD6">type</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">integer</span><span style="color:#D4D4D4">, </span><span style="color:#569CD6">nullable</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">true</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#569CD6"> required</span><span style="color:#D4D4D4">: [</span><span style="color:#CE9178">id</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">name</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">age</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#CE9178"> "404"</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> description</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">Not Found</span></span>
<span class="line"><span style="color:#CE9178"> "500"</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> description</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">Internal Server Error</span></span></code></pre>
<p>例としてユーザー ID からユーザーのデータを取得する API を記述してみました。
上記の定義を見ると、</p>
<ul>
<li><code>GET: /users/{user_id}</code> というエンドポイントがある</li>
<li>ユーザーの ID (<code>user_id</code>) を string でパラメータとしてパスの中に埋め込む</li>
<li>レスポンスは 200, 404, 500 が返ってくる可能性がある</li>
</ul>
<p>といったことが読み取れるかと思います。
また、レスポンスは以下のような JSON が返ってくることもスキーマから読み取ることができます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#9CDCFE"> "id"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"12345678-1234-1234-1234-123456789abc"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "name"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"user name"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "age"</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">25</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>ここでは OpenAPI の記述の仕方についてあまり深く触れませんが、この他にもデータのフォーマットや省略された場合のデフォルト値などの記述なども可能です。
より詳しくは<a href="https://swagger.io/docs/specification/about/">こちら</a>を参考にしてみてください。
これらをうまく使うことでより正確に API の仕様を記述することができます。</p>
<h1 id="protocol-buffers">Protocol Buffers</h1>
<p>Protocol Buffers は主に gRPC で用いられるデータのシリアライゼーション形式です。
専用のスキーマ言語を用いてデータの構造や RPC のメソッドの定義を <code>.proto</code> ファイルに記述していきます。</p>
<p>実際の <code>.proto</code> ファイルは以下のような形になります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="protobuf"><code><span class="line"><span style="color:#569CD6">syntax</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">"proto3"</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6">package</span><span style="color:#CE9178"> example.protobuf</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">service</span><span style="color:#4EC9B0"> UserService</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> rpc</span><span style="color:#DCDCAA"> GetUser</span><span style="color:#D4D4D4">(</span><span style="color:#4EC9B0">GetUserRequest</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">returns</span><span style="color:#D4D4D4"> (</span><span style="color:#4EC9B0">GetUserResponse</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">message</span><span style="color:#4EC9B0"> GetUserRequest</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> string</span><span style="color:#9CDCFE"> id</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">1</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">message</span><span style="color:#4EC9B0"> GetUserResponse</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> User</span><span style="color:#9CDCFE"> user</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">1</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">message</span><span style="color:#4EC9B0"> User</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> string</span><span style="color:#9CDCFE"> id</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">1</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6"> string</span><span style="color:#9CDCFE"> name</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">2</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6"> int32</span><span style="color:#9CDCFE"> age</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">3</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>少し独特な見た目をしていますが、<code>message</code> として構造体のようなデータ構造が定義されていることが見て取れると思います。
各メッセージにはフィールドが定義されており、型とフィールド名が宣言されています。</p>
<h1 id="スキーマ言語を使うメリット">スキーマ言語を使うメリット</h1>
<p>スキーマはドキュメントの代わりとして使えるだけでなく、他にもいくつか使い道があるので紹介していこうと思います。</p>
<h2 id="スキーマからソースコードを自動生成できる">スキーマからソースコードを自動生成できる</h2>
<p>スキーマ言語による記述は人間にとって読みやすいだけでなく機械的に処理できる形式でもあるので、スキーマからソースコードを自動生成することも可能です。
プログラミング言語や使っているフレームワークによらず、サーバー側ではリクエストのバリデーションを、またクライアント側ではレスポンスの JSON をクラスや構造体に詰めるような処理を書くことが多いのではないでしょうか。
事前にスキーマ定義を書いている場合はスキーマを見ながらこういったコードを書き起こしていくことになるのですが、せっかく machine-readable な形のスキーマがあるのだから自動でコードを生成したくなるのが自然な考えでしょう。
自動で生成してしまえば定型のコードを書く手間を減らせるだけでなく、手書きするとどうしても起こしがちな JSON のキー名を間違えるといったミスを防ぐこともできます。
また、Web, iOS, Android などの複数プラットフォームでサービスを展開している場合でも、それぞれの実装が乖離しないように管理していくことも容易です。</p>
<p>gRPC の場合は Protocol Buffers からソースコードを生成するのがほぼ前提となっています。
protoc コマンドを利用すると <code>.proto</code> ファイルから各言語のサーバー・クライアントコードを生成することができます。</p>
<p>OpenAPI の場合、<a href="https://github.com/OpenAPITools/openapi-generator">openapi-generator</a> や <a href="https://github.com/swagger-api/swagger-codegen">swagger-codegen</a> などのツールを利用することで各種言語のコードを自動生成が可能です。
openapi-generator や swagger-codegen には多くの言語とフレームワークのサーバースタブと API クライアントのジェネレータが用意されています。</p>
<p>既存のジェネレータを使うだけでも十分に強力なのですが、さらに自前でコードジェネレータを書けばプロジェクト固有のルールでコードを生成するといったことも可能です。
OpenAPI のドキュメント自体はただの YAML/JSON なので、パースしてスキーマ定義を再帰的に読んでいけばコード生成に必要な情報を集めることができます。
Protocol Buffers からコードを生成する場合は <a href="https://qiita.com/yugui/items/87d00d77dee159e74886">protoc のプラグインを書く</a>のが簡単でオススメです。</p>
<h2 id="モックサーバーを使ってクライアント開発を進められる">モックサーバーを使ってクライアント開発を進められる</h2>
<p>コードの自動生成の他にもスキーマからモックサーバを立ち上げるといった活用方法もあります。
<a href="https://github.com/stoplightio/prism">Prism</a> のようなスキーマ定義から固定のデータを返すモックサーバを立ち上げるツールを利用すれば、クライアント側の開発もしやすくなります。
特にフロントエンドとバックエンドで担当が分かれているような場合は、開発初期はモックサーバーを使って開発を始め、バックエンドの実装ができてきた頃に結合するという流れを踏むことで、フロントエンドもより早いタイミングから開発に着手することができるようになります。</p>
<p>実際に Prism を使ってモックサーバーを立ち上げるとこんな感じになります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200821/20200821181330.png" alt="20200821181330.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200821/20200821181342.png" alt="20200821181342.png">
<h1 id="スキーマファースト開発">スキーマファースト開発</h1>
<p>このような開発スタイルはスキーマファースト開発・スキーマ駆動開発などと呼ばれています。
明確な定義や出典があるわけではないのですが、スキーマファーストな開発は以下のような開発スタイル全般を指しています。</p>
<ul>
<li>スキーマは実装言語によらない machine-readable な形で記述する</li>
<li>ドキュメントをスキーマから自動生成する</li>
<li>クライアント/サーバーのコードをスキーマから自動生成する</li>
</ul>
<p>実際にスキーマファーストな開発を実践するためにはスキーマをメンテナンスするモチベーションを高く保つ必要があります。
コードも書いた上でスキーマも書かないいけないとなるとおそらくスキーマが更新されなくなっていくので、可能な限りスキーマからコードを生成する努力が必要になります。
一方、うまく実践すればスキーマという形で 100% 信頼できる API ドキュメントが手に入り、クライアント/サーバー間での齟齬も減らせるので、実践してみる価値はあるのではないかと思います。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回は CLINICS アプリの開発でのスキーマ言語の活用例について紹介しました。
実際に開発の中でスキーマ言語を使うことで API 仕様について把握する労力が減り、エンジニア間のコミュニケーションも取りやすくなったと感じています。
また、一部のクライアントコードは OpenAPI や Protocol Buffers から自動生成しているのですが、かなり手間が省けると同時に人間の手で書くと起こりがちなミスも防ぐことができています。
OpenAPI であれば既存の REST API の仕様を書き起こすところから使い始められるので、興味のある方はぜひ一度使ってみてはいかがでしょうか。</p>
<h1 id="最後に">最後に</h1>
<p><a href="https://www.medley.jp/team/creator-story-incubation.html">インキュベーション本部</a>では「医療ヘルスケアの未来」をつくる新規事業の立ち上げに挑戦しています。そんな私たちと一緒に働いてみたいと思った方は、ぜひ以下の採用情報もチェックしてみてください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- CLINICS 開発チームで横軸の取り組みをした話https://developer.medley.jp/entry/2020/08/14/135516https://developer.medley.jp/entry/2020/08/14/135516こんにちは、19 新卒の桶谷です。現在は CLINICS で検査周りの開発を担当しています。
最近は、私自身が基礎疾患持ちということもあり、社会情勢を考慮してフルリモートで業務に勤しんでいます。オンラインコミュニケーションは対面と比べ、意識...Fri, 14 Aug 2020 04:55:16 GMT<p>こんにちは、19 新卒の桶谷です。現在は CLINICS で検査周りの開発を担当しています。</p>
<p>最近は、私自身が基礎疾患持ちということもあり、社会情勢を考慮してフルリモートで業務に勤しんでいます。オンラインコミュニケーションは対面と比べ、意識しなければならない点が多く、慣れが必要であると日々感じています。少しでも対面に近い環境を実現するために、一眼レフカメラを Web カメラとして代用したり、ダイナミックマイクを使用してクリアな音声を届けられるように作業環境の改善に取り組んでいます。</p>
<p>今回は CLINICS 開発チームで課題となっていた「オンボーディングやキャッチアップに時間がかかる問題」を解決するために行った、横軸の取り組みについて紹介します。</p>
<h1 id="はじめに">はじめに</h1>
<p>CLINICS 開発チームではクラウド診療支援システム「<a href="https://clinics-cloud.com/">CLINICS</a>」の機能として主に電子カルテ、オンライン診療、予約システムなどの機能の開発を行っています。医療システムの開発において医療業務知識は必要不可欠であり、例えば「会計」や「検査」といった医療機関で行われる業務に関する理解が要求されます。そのため、各々が日々キャッチアップに勤しんでいる一方、ひとつひとつの業務が複雑であるために、複数の機能を横断したプロダクト理解、全体把握が難しい課題がありました。そこで、この問題を解決するべく「横軸勉強会」と題して、CLINICS のドメイン知識共有会を行うことにしました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200812/20200812191102.png" alt="20200812191102.png">
<h1 id="ドメイン知識共有会の概要">ドメイン知識共有会の概要</h1>
<p>ドメイン知識共有会は各々が携わった中で蓄積してきた業務知識や開発の背景を共有する会です。本会は毎週 1 時間トピックに沿ってオンライン(Google Meet)で実施します。あらかじめトピック、発表者、ファシリテーターを指定し、発表者には簡単な資料を用意してもらいます。加えて参加者には以下のことを意識してもらいました。</p>
<ul>
<li>発表者は対話型の発表を意識しフラットな雰囲気をつくる</li>
<li>参加者はチャットを用いて思ったことをつぶやく</li>
</ul>
<p>各トピックは医療システムを開発する上で必要なドメイン知識を切り口として、技術寄りの内容となっています。発表者によっては勉強会後の宿題が用意されたり、おすすめの本紹介があったりと多種多様です。発表形式をあえて定めないことで、各回ごとに発表者の個性が出るというのも面白みの一つだと思います。特に宿題があった回では、後日にフィードバック会が設けられ知識の定着に繋がりました。</p>
<p><em>実際に行ったトピック</em>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200812/20200812184411.png" alt="20200812184411.png"></p>
<p><em>実際の様子</em>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200812/20200812201630.png" alt="20200812201630.png"></p>
<h1 id="ドメイン知識共有会を終えて">ドメイン知識共有会を終えて</h1>
<p>今回ドメイン知識共有会を通して、機能の実装背景や概要を幅広く知ることができました。「会計業務の複雑さ」や「受付から診察までの動線設計」などのこれまでは共有する機会がなかった個々が持っている知識・知見を学ぶことができ、チーム全体でドメイン知識に関する認識合わせをする良い機会となりました。</p>
<p>最後に勉強会の振り返りを行ったところ、以下のような意見がありました。</p>
<ul>
<li>ドメイン知識共有会を通してチーム力が上がってきている</li>
<li>勘違いしていた知識を訂正できた</li>
<li>相互理解が深まりチーム全体としてコミュニケーションのハードルを下げることができた</li>
<li>各専門領域に関するドキュメントを一箇所にまとめることができた</li>
</ul>
<p>ポジティブな意見が多く、取り組みの結果としてチームの基礎力向上にも繋がったと感じています。加えて、ドメイン知識共有会用に作成した資料はドキュメントとして残るため、オンボーディング資料などに活用ができそうです。しかしながら、以下のような問題もあります。</p>
<ul>
<li>発表者の準備コストが高い</li>
<li>概要説明で終わってしまい即日業務で使用できる内容ではない</li>
<li>学んだことを深堀りできていないため知識の定着に繋がらない</li>
</ul>
<p>上記については、例えば発表者を 2 人ペアにして準備コストを分散させたり、宿題を出して次回にフィードバックする、開発チーム以外も気軽に参加ができるようにライトなトピックを追加するなど、今後のドメイン知識共有会のなかで改善していきたいと思います。</p>
<h1 id="さいごに">さいごに</h1>
<p>今回は CLINICS 開発チームで行った横軸の取り組みについて紹介させていただきました。</p>
<p>横軸でお互いをフォローしあうような「情報共有の場」を作ることで、より良い組織になっていくと感じています。今後、さらにプロダクトの機能が増えるとともに事業部全体として、関わるメンバーが増えるため、横軸での知識共有の仕組みは事業部を含めて必要であると考えています。開発チームに限らずに、プロダクトに関わるメンバーを巻き込み、組織コミュニケーションの改善を続けていきたいです。</p>
<p>最後までお読みいただきありがとうございました。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- Terraform のテスト環境を Terraform Workspaces で構築したhttps://developer.medley.jp/entry/2020/07/31/165905https://developer.medley.jp/entry/2020/07/31/165905株式会社メドレーのエンジニアの笹塚です。
主にジョブメドレーのインフラを担当しています。
直近では、コンテナ化されていなかった環境の移行などをしていました。
休日は主にゲームをやっています。今は、日本語版がリリースされたばかりの「レムナン...Fri, 31 Jul 2020 07:59:05 GMT<p>株式会社メドレーのエンジニアの笹塚です。
主にジョブメドレーのインフラを担当しています。</p>
<ul>
<li>直近では、コンテナ化されていなかった環境の移行などをしていました。</li>
<li>休日は主にゲームをやっています。今は、日本語版がリリースされたばかりの「レムナント:フロム・ジ・アッシュ」に夢中です。</li>
</ul>
<p>今回は <a href="https://www.terraform.io/">Terraform</a> のテスト環境を、<a href="https://www.terraform.io/docs/state/workspaces.html">Terraform Workspaces</a> を使用して構築した事例を紹介します。</p>
<h1 id="背景">背景</h1>
<p>ジョブメドレーにはいくつかのサービスがあり、Terraform によるコード化が進んでいるサービスと、Ansible、 itamae などで部分的にコード化はされているものの、Terraform の使用が遅れているサービスが混在しており、これらのサービスについても Terraform への移行を進めています。</p>
<p>Terraform への移行とあわせて、メンテナンスを担当するメンバーを増やす必要があるのですが</p>
<p>【作業担当】</p>
<ul>
<li>Terraform のコードから、実際に稼働させる環境を作るのは慣れていても難しい。</li>
</ul>
<p>【レビュワー】</p>
<ul>
<li>Terraform のコードの差分だけで、内容をすぐに把握するのは難しい。</li>
</ul>
<p>などの理由から、作業担当、レビュワーともにハードルが低いとは言えず、Terraform によるインフラの変更内容を事前に確認できる環境構築が必要だと感じるようになりました。</p>
<h1 id="検討内容">検討内容</h1>
<p>事前に確認できる環境を作るにあたり、まず最初に検討したのは、各ステージのコードを共通化することでした。
ジョブメドレーの関連サービスでは、大きくわけて 3 つのステージで構成しています。</p>
<p>Sandbox(個人の検証用) → QA(リリース前の検証用) → Production</p>
<p>この各ステージのコードを共通化できれば、Production には QA 環境までで確認済みのコードを apply することができます。</p>
<p>ですが、Sandbox 環境、QA 環境、Production 環境はそれぞれ似てはいるものの、全てが同じ構成ではありません。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200731/20200731151334.png" alt="20200731151334.png">
<p>Terraform の HCL では</p>
<ul>
<li>if 構文がない( resource の作成を count で制御することはできる)</li>
<li>module 単位でのリソース作成の制御ができない(Terraform 0.13 から <a href="https://github.com/hashicorp/terraform/tree/guide-v0.13-beta/module-repetition">module でも count や for_each が可能になる</a>ようです)</li>
</ul>
<p>などの制限があり、構造の差分をコードで吸収しにくく、共通化できたとしてもメンテナンス性を下げる可能性が高いです。
また、すでに Terraform でコード化されている状態からの移行作業も楽ではないでしょう。</p>
<p>そこで、ステージごとのコードを共通化するのではなく、<a href="https://aws.amazon.com/jp/organizations/">AWS Organizations</a> で別アカウントを作り、各ステージのコードを試せる環境を作ることにしました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200731/20200731151544.png" alt="20200731151544.png">
<p><目標とする></p>
<p>Terraform のコードをプロダクションアカウントで実行する前に、テストアカウントで実行し、設定した内容を AWS マネジメントコンソール や AWS CLI で確認することできる。</p>
<ul>
<li>テストアカウント上で設定を確認した後、プロダクションアカウントに同じコードを apply することができる。</li>
<li>コストを抑えるため、テストアカウント上のリソースは、確認が終了したら destory することができる。</li>
</ul>
<p><目標としない></p>
<ul>
<li>テストアカウント上でサービスが稼働することは目標としない。</li>
<li>必要なデータセットの作成など用意するものが増えるので、目標には含めませんでした。</li>
</ul>
<h1 id="設定作業">設定作業</h1>
<p>必要な作業は大きくわけて 2 つです。</p>
<ol>
<li>Terraform Workspaces の設定</li>
<li>アカウントごとに必要なコードの追加、修正</li>
</ol>
<h2 id="terraform-workspaces-の設定">Terraform Workspaces の設定</h2>
<p>Terraform と AWS provider の設定を変更することで、workspace を切り替えることができます。</p>
<p>以下が作業イメージです。</p>
<h3 id="workspace-の追加">workspace の追加</h3>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> terraform</span><span style="color:#CE9178"> workspace</span><span style="color:#CE9178"> new</span><span style="color:#CE9178"> production</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> terraform</span><span style="color:#CE9178"> workspace</span><span style="color:#CE9178"> list</span></span>
<span class="line"><span style="color:#DCDCAA"> default</span></span>
<span class="line"><span style="color:#D4D4D4">* production</span></span></code></pre>
<h3 id="terraform-の環境変更">Terraform の環境変更</h3>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="hcl"><code><span class="line"><span style="color:#4EC9B0">terraform</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> required_version </span><span style="color:#D4D4D4">=</span><span style="color:#CE9178"> "= 0.12.28"</span></span>
<span class="line"><span style="color:#4EC9B0"> backend</span><span style="color:#4FC1FF"> "s3"</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> bucket </span><span style="color:#D4D4D4">=</span><span style="color:#CE9178"> "example-state"</span></span>
<span class="line"><span style="color:#9CDCFE"> workspace_key_prefix </span><span style="color:#D4D4D4">=</span><span style="color:#CE9178"> "workspace"</span></span>
<span class="line"><span style="color:#9CDCFE"> dynamodb_table </span><span style="color:#D4D4D4">=</span><span style="color:#CE9178"> "terraform-state-lock"</span></span>
<span class="line"><span style="color:#9CDCFE"> key </span><span style="color:#D4D4D4">=</span><span style="color:#CE9178"> "example.tfstate"</span></span>
<span class="line"><span style="color:#9CDCFE"> region </span><span style="color:#D4D4D4">=</span><span style="color:#CE9178"> "ap-northeast-1"</span></span>
<span class="line"><span style="color:#9CDCFE"> profile </span><span style="color:#D4D4D4">=</span><span style="color:#CE9178"> "production"</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#4EC9B0">provider</span><span style="color:#4FC1FF"> "aws"</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> region </span><span style="color:#D4D4D4">=</span><span style="color:#CE9178"> "ap-northeast-1"</span></span>
<span class="line"><span style="color:#9CDCFE"> version </span><span style="color:#D4D4D4">=</span><span style="color:#CE9178"> "= 2.70.0"</span></span>
<span class="line"><span style="color:#9CDCFE"> profile </span><span style="color:#D4D4D4">=</span><span style="color:#D4D4D4"> var.</span><span style="color:#9CDCFE">workspace_profile</span><span style="color:#D4D4D4">[</span><span style="color:#9CDCFE">terraform</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">workspace</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#4EC9B0">variable</span><span style="color:#4FC1FF"> "workspace_profile"</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> type </span><span style="color:#D4D4D4">=</span><span style="color:#569CD6"> map</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">string</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> default </span><span style="color:#D4D4D4">=</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> default</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">"test"</span></span>
<span class="line"><span style="color:#9CDCFE"> production</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">"production"</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>この例では、default workspace はテスト環境、Production を実際にサービスが稼働する環境のアカウントになるように設定しています。
それぞれ default は、aws config の test profile、Production は production profile を参照しています。</p>
<p><code>s3://example-state/example.tfstate</code> がテスト環境、<code>s3://example-state/workspace/production/example.tfstate</code> が、プロダクション環境の state ファイルになります。</p>
<p>Terraform のコード内からは、現在の workspace 名を terraform.workspace で参照することができます。</p>
<p>ここまでの設定で</p>
<p><code>$ terraform workspace select [workspace 名] </code></p>
<p>で workspace を切り替えることができるようになりました。</p>
<p>あとは、アカウントごとに必要な変更をしていきます。</p>
<h2 id="アカウントをまたいだ共通のリソースの定義">アカウントをまたいだ共通のリソースの定義</h2>
<p>全アカウントでユニークにする必要があるリソース(S3 bucket、DNS など)の場合は、名称の分岐処理が必要です。
テストアカウントでは、リソース名に prefix をつけて作成するようにしました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="hcl"><code><span class="line"><span style="color:#6A9955"># 定義</span></span>
<span class="line"><span style="color:#4EC9B0"> variable</span><span style="color:#4FC1FF"> "example_bucket_name"</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> type </span><span style="color:#D4D4D4">=</span><span style="color:#569CD6"> map</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">string</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> default </span><span style="color:#D4D4D4">=</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> default</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">"test-example-bucket"</span></span>
<span class="line"><span style="color:#9CDCFE"> production</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">"example-bucket"</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># リソースでの参照時</span></span>
<span class="line"><span style="color:#4EC9B0">resource</span><span style="color:#4FC1FF"> "aws_s3_bucket"</span><span style="color:#4FC1FF"> "example"</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> bucket </span><span style="color:#D4D4D4">=</span><span style="color:#D4D4D4"> var.</span><span style="color:#9CDCFE">example_bucket_name</span><span style="color:#D4D4D4">[</span><span style="color:#9CDCFE">terraform</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">workspace</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> ..</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<h2 id="テストアカウントで起動するインスタンスタイプや台数の変更">テストアカウントで起動するインスタンスタイプや台数の変更</h2>
<p>テストアカウントでは EC2 のインスタンスを起動させなくても良い場合には、台数を変更するようにしました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="hcl"><code><span class="line"><span style="color:#4EC9B0">resource</span><span style="color:#4FC1FF"> "aws_autoscaling_group"</span><span style="color:#4FC1FF"> "example_web"</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> name </span><span style="color:#D4D4D4">=</span><span style="color:#CE9178"> "example-web"</span></span>
<span class="line"><span style="color:#9CDCFE"> max_size </span><span style="color:#D4D4D4">=</span><span style="color:#B5CEA8"> 12</span></span>
<span class="line"><span style="color:#9CDCFE"> min_size </span><span style="color:#D4D4D4">=</span><span style="color:#B5CEA8"> 6</span></span>
<span class="line"><span style="color:#9CDCFE"> desired_capacity </span><span style="color:#D4D4D4">=</span><span style="color:#D4D4D4"> terraform.</span><span style="color:#9CDCFE">workspace</span><span style="color:#D4D4D4"> == </span><span style="color:#CE9178">"production"</span><span style="color:#D4D4D4"> ? </span><span style="color:#B5CEA8">6</span><span style="color:#D4D4D4"> : </span><span style="color:#B5CEA8">0</span></span>
<span class="line"><span style="color:#D4D4D4"> …</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>インスタンスの起動が必要な場合も、同様の分岐でインスタンスタイプの変更を行っています。</p>
<h2 id="コード化をしないリソースの扱い">コード化をしないリソースの扱い</h2>
<p>プロダクションアカウントで一旦コード化を保留したリソースについては data source で参照しますが、テストアカウントにはリソースが存在しないため作成しなければいけません。
テストアカウントのリソースは確認が終了した段階で destroy したいので</p>
<ul>
<li>ステージ用の Terraform コードとは別に、必要なリソースを作成するコードを用意する</li>
<li>必要なリソース用のコード → ステージ用のコードの順に apply する</li>
</ul>
<p>ようにしました。
data source で参照できれば良い範囲でのコード化なので、この追加のリソースも最小限のコストになるようにしています。</p>
<p>以上の設定で、同じ Terraform のコードを workspace を切り替えて plan、apply ができるようになりました。</p>
<h1 id="運用してみて">運用してみて</h1>
<p>プロダクションアカウントに apply する前に、テストアカウントで事前に apply することができるようになったので、作業中の試行錯誤もしやすくなりました。</p>
<p>レビュワーも、テストアカウント上で実際に apply された結果を確認することができるようになり、apply の差分だけではわかりにくかった変更内容を確認できるようになりました。</p>
<p>これなら新しく担当するメンバーの不安を、少しかもしれませんが解消できそうです。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回は Terraform のテスト環境を、Terraform Workspaces を使用して構築した事例を紹介させていただきました。
テストアカウント上のリソースの自動 destroy や、 plan、apply の自動化については触れられていませんが、また別の機会に紹介できればと思います。</p>
<p>長らく運用しているサービスでは、サービスを稼働させたまま解決しなければいけない課題が数多くあります。
それらの課題を、一つずつ着実に解決していくことに楽しさを見いだせる方、ぜひメドレーで一緒に働きましょう!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- PWA, PRPL Pattern の概要と採用状況の調査https://developer.medley.jp/entry/2020/07/17/170711https://developer.medley.jp/entry/2020/07/17/170711こんにちは。メドレーにてジョブメドレー開発エンジニアをしています、矢野と申します。
ジョブメドレーでは、主にバックエンド ( Ruby on Rails ) の改修を担当してます
直近では 「サイトパフォーマンス改善施策」 として、Rai...Fri, 17 Jul 2020 08:07:11 GMT<p>こんにちは。メドレーにてジョブメドレー開発エンジニアをしています、矢野と申します。</p>
<ul>
<li>ジョブメドレーでは、主にバックエンド ( Ruby on Rails ) の改修を担当してます</li>
<li>直近では <strong>「サイトパフォーマンス改善施策」</strong> として、Rails コードのリファクタリングによる TTFB 高速化に取り組んでました</li>
<li>「もう絶対にコケないのが分かってる」ビルドやテストを、手元のコンソールで何度も叩いて「わー。ちゃんと通る!」っていう時間が好きです</li>
</ul>
<p>今回は、上記の「サイトパフォーマンス改善施策」の文脈で調査した、PWA の実装パターンである <strong>PRPL Pattern</strong> という <strong>リソース提供の設計アーキテクチャ</strong> について紹介します。</p>
<h1 id="prpl-pattern-とは">PRPL Pattern とは</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200716/20200716154256.png" alt="20200716154256.png">
<blockquote>
<p>ref. <a href="https://web.dev/apply-instant-loading-with-prpl/">Apply instant loading with the PRPL pattern - web.dev</a></p>
</blockquote>
<p>PRPL Pattern は、Google I/O 2016 で提案された <strong>PWA - Progressive Web Application</strong> の構築・配信のための設計アーキテクチャです。</p>
<p>Web サイト・アプリケーションが、回線強度やスペックが高くないスマートフォンなどのデバイスでもストレスなく機能するよう、リソース配信とアプリ起動時のパフォーマンス ( = 高速化 ) に重点を置いています。</p>
<h2 id="prpl-meanings">PRPL meanings</h2>
<p>では具体的に「どうやって速くするの?」ということで、PRPL が提唱している 4 つのサイトレンダリング手法について見ていきます。</p>
<ul>
<li>Push: <code><link preload></code> および <strong>HTTP/2</strong> を使用して、初期 URL ルートの重要なリソースを Server Push する</li>
<li>Render: クライアントが初期ルートをなるべく早くレンダリングする</li>
<li>Pre-cache: 残りのルートをクライアントが <strong>Service Worker</strong> でプリキャッシュする</li>
<li>Lazy load: クライアントはオンデマンドで残りのルートを遅延読込みして作成する</li>
</ul>
<p>PRPL は上記 4 つの頭文字をとったものですね。 PRPL は <a href="https://tools.ietf.org/html/rfc7540">HTTP/2</a> の Server Push や、PWA の <a href="https://developers.google.com/web/fundamentals/primers/service-workers">Service Worker</a> など、Web プラットフォームの最新技術を駆使してサイトパフォーマンスをあげよう!というプラクティスです。</p>
<blockquote>
<p>PWApps とは、最新の Web 技術を有効に活用し、漸進的 ( Progressive ) に高度なユーザー体験を提供しようとする概念です。この PWApps の概念を具体化する一つの手法として、「 PRPL 」 ( パープル ) と名付けられた開発・提供パターンが提案されました。</p>
<p>(中略)</p>
<p>Web Components や、 Service Worker、 HTTP/2 Server Push といった Web の最新技術をフルに活用し、レスポンス性の高いユーザー体験を提供しようというものです。</p>
<p>ref. <a href="https://html5experts.jp/komasshu/19704/">Google が新たに提唱する Progressive Web Apps の新たな開発パターン「 PRPL 」とは?</a></p>
</blockquote>
<h1 id="周辺知識---http2">周辺知識 - HTTP/2</h1>
<p>まずは、周辺知識からおさらいしていきます。PRPL は HTTP/2 の Server Push を利用する、という話でした。そもそも HTTP/2 とはどんなものでしょうか。</p>
<ul>
<li>Hyper Text Transfer Protocol と呼ばれる TCP 上の通信プロトコルの次世代バージョン</li>
<li>普段私たちが Web サイトを閲覧する際に利用しているプロトコル</li>
<li>HTTP/1.1 が 1997 年に策定され、2015 年にようやく /2 が標準化</li>
<li>Express, Apache, Nginx など各 Web サーバ、各ブラウザも対応してきている</li>
</ul>
<blockquote>
<p>ref. <a href="https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/Evolution_of_HTTP">HTTP の進化</a></p>
</blockquote>
<h2 id="現行の一般的なバージョンは-http11">現行の一般的なバージョンは HTTP/1.1</h2>
<p>HTTP は普段私たちが Web サイトを閲覧する際に利用する通信プロトコルです。HTTP/2 はその次世代バージョンになります。HTTP/1.1 には以下のような特徴があります。</p>
<ul>
<li>ステートレスな通信</li>
<li>テキストベースで情報をやりとりする</li>
<li>原則 1 リクエストに対して 1 レスポンスである</li>
<li>→ 複数リソースを得るために何度もリクエストしてコネクションを貼り直す必要があり <strong>パフォーマンス上の課題がある</strong></li>
</ul>
<p>これに対して、1.1 の次期バージョンである HTTP/2 は以下のような特徴があります。</p>
<ul>
<li>通信時にヘッダを圧縮し使い回す省エネ設計 = 一部ステートがある</li>
<li>バイナリベースで情報をやりとりする</li>
<li>ストリームという概念で、1 コネクション中で Request / Response を多重化できる</li>
<li>→ <strong>1 コネクションの中で複数リソースを並行して Request / Response できる!</strong></li>
<li>リソースの Server Push が可能</li>
</ul>
<p>HTTP/2 自体が HTTP/1.1 の課題であった通信のオーバヘッドを改善する規格であることがわかりますね。また「 request / response の多重化」により、1.1 と比較してどの程度「速く」なるのかについては、以下 Akamai 社のブログサイトが参考になります。</p>
<blockquote>
<p>HTTP/1.1</p>
</blockquote>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200716/20200716154352.png" alt="20200716154352.png">
<blockquote>
<p>HTTP/2 ( request / response の多重化 )</p>
</blockquote>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200716/20200716154439.png" alt="20200716154439.png">
<blockquote>
<p>ref. <a href="https://blogs.akamai.com/jp/2017/03/AdaptiveAcceleration.html">HTTP/2 を活用するパフォーマンス最適化 ADAPTIVE ACCELERATION</a></p>
</blockquote>
<h2 id="http2-の採用事例">HTTP/2 の採用事例</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200716/20200716154502.png" alt="20200716154502.png">
<blockquote>
<p>ref. <a href="https://caniuse.com/#feat=http2">caniuse.com</a></p>
</blockquote>
<p>上記対応状況からも分かる通り、モダンブラウザでは一通り対応しています。実際のプロダクションでも 日本ではメルカリさん、世界的なサービスだと Twitter, Facebook, Instagram など SNS サービスや、Slack, Dropbox などが HTTP/2 に対応しているようです。</p>
<p>ジョブメドレーはまだ HTTP/1.1 でのサービス提供しか行っていませんが、ゆくゆくはバージョンアップ対応を行っていきたいと思っています。</p>
<h1 id="周辺知識---pwa">周辺知識 - PWA</h1>
<p>次に、PWA についておさらいします。PRPL は PWA の Service Worker を利用した実装パターンという話でしたが、そもそも PWA や Service Worker とはどんなものなのでしょうか。</p>
<h2 id="pwa---progressive-web-application">PWA - Progressive Web Application</h2>
<ul>
<li>Web プラットフォームの新機能を使って「ネイティブアプリとウェブアプリのいいとこ取りした、UX の高いウェブアプリ」という概念</li>
<li>ウェブアプリの特性 ( Secure, Linkable, Indexable … ) を保ちつつ、ネイティブアプリの多機能さ ( インストール、プッシュ通知、オフライン動作 … ) を最新のブラウザ機能 = JavaScript API で実現する</li>
<li>必ずしも SPA であったり、最新機能の全てを使っている必要はなく、「斬新的に Web 新 API でネイティブな機能を取り入れていける」というコンセプト</li>
</ul>
<blockquote>
<p>ref. <a href="https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps/Introduction">プログレッシブウェブアプリの紹介 - MDN</a></p>
</blockquote>
<h2 id="pwa-を構成する新機能たち">PWA を構成する新機能たち</h2>
<ul>
<li>HTTPS: HyperText Transfer Protocol Secure 、 SSL / TLS による通信の暗号化</li>
<li>Service Worker: Web ページで動作するスクリプトから独立したイベント駆動型の worker</li>
<li>Cache API: Request / Response オブジェクトのストレージキャッシュ</li>
<li>Push API / Notifications API: サーバーからアプリへの通知送信</li>
<li>マニフェスト: アプリストアを通さず Web アプリをホーム画面にインストール可能</li>
</ul>
<blockquote>
<p>ref. <a href="https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps">プログレッシブウェブアプリ - MDN</a></p>
</blockquote>
<p>上記以外にも、PWA には様々な API ・機能が存在します。その中でも PWA の、そして PRPL アーキテクチャの中核を成す重要な機能が <strong>Service Worker</strong> です。</p>
<h2 id="service-worker-とは">Service Worker とは</h2>
<ul>
<li>ブラウザのバックグラウンドプロセスとして動作する Worker</li>
<li>Web ページで動作するスクリプトとは独立して動作する</li>
<li>サーバサイドでいうところの Worker プロセスと同じような使い方ができる</li>
</ul>
<blockquote>
<p>ref. <a href="https://developers.google.com/web/fundamentals/primers/service-workers">Service Worker の紹介</a></p>
</blockquote>
<p>Web ページの JavaScript プロセスとは切り離された文脈で、予め登録しておいた処理を、様々なイベントに応じて発火させることができるイメージですね。</p>
<p>プッシュ通知など、いわゆる「ネイティブアプリのような機能」は、この Service Worker を利用することで実現しています。</p>
<h2 id="pwa-の採用状況">PWA の採用状況</h2>
<p>Google からの提唱当初 ( PWA も Google のプロジェクトです ) こそ、先進的すぎてなかなか受け入れられなかった PWA ですが、2020 年現在は各ブラウザの対応状況も少しずつ向上されています。</p>
<p>日本では SUUMO、日経電子版、一休.com、世界的なサービスだと Instagram などが PWA による Web サイトを提供しているようです。</p>
<ul>
<li><a href="https://markezine.jp/article/detail/23623">リクルートの『SUUMO』、Android スマートフォン用サイトでプッシュ通知できる機能を実装</a></li>
<li><a href="https://employment.en-japan.com/engineerhub/entry/2018/06/05/110000">PWA で表示速度が 2 倍に! スピード改善を妥協しない日経電子版に学ぶ、PWA のメリット&デメリット</a></li>
<li><a href="https://blog.agektmr.com/2018/03/instagram-pwa.html">なぜ Instagram は PWA を作ったのか?</a></li>
</ul>
<p>特に、既にネイティブアプリで大成功している Instagram が、回線・端末スペックの低い新興国をターゲットとした PWA をリリースしているという点はプロダクト観点からもとても興味深いですね。</p>
<h1 id="prpl-パターンの利点">PRPL パターンの利点</h1>
<p>さて、話を戻して PRPL パターンが HTTP/2 や Service Worker を使って、具体的にどのようにサイトパフォーマンスを向上するのか?という点を見ていきます。</p>
<h2 id="server-push--service-worker-による-pre-cache">Server Push + Service Worker による Pre-cache</h2>
<p>PRPL の 4 要素を、改めてもう少しわかりやすく記載してみると以下のようになります。</p>
<ul>
<li>Push
<ul>
<li>初回コネクションで <strong>HTTP/2 Server Push で 必要リソースをまとめて Push</strong></li>
</ul>
</li>
<li>Render
<ul>
<li>上記で受け取った HTML リソースを元に初期画面をレンダリングする</li>
</ul>
</li>
<li>Pre-cache
<ul>
<li>上記初期画面で利用されるリソースは、<strong>Server Push により非同期的に Service Worker が Pre-cache する</strong></li>
<li>また、今後利用しそうな追加リソースについても、非同期・投機的に Service Worker が事前に DL 、キャッシュする</li>
</ul>
</li>
<li>Lazy load
<ul>
<li>初期画面以降で必要になった画像などのリソースを、画面スクロールなどを検知し、表示に必要になったタイミングで遅延読み込みする</li>
</ul>
</li>
</ul>
<p>上記の太字箇所を画像で説明すると、以下のようになります。</p>
<blockquote>
<p>HTTP/2 ( Server Push )</p>
</blockquote>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200716/20200716154542.png" alt="20200716154542.png">
<blockquote>
<p><a href="https://blogs.akamai.com/jp/2017/03/AdaptiveAcceleration.html">HTTP/2 を活用するパフォーマンス最適化 ADAPTIVE ACCELERATION</a></p>
</blockquote>
<p>HTTP/2 の Request / Response の多重化だけを利用したケースと比較すると、「ブラウザがページを解析して、必要リソースを Request する」よりも前に「サーバが必要リソースを強制的に Push 」しているのがわかります。</p>
<p>この Push されたリソースを Service Worker が受け取り → キャッシュ化することで <strong>「ページ解析が終わった時点では既に必要リソースがブラウザにキャッシュされている」</strong> 状態となり、アプリの初回起動が速くなる、というのが Push & Pre-Cache の速度改善の仕組みです。</p>
<h2 id="service-worker-で必要になりそうなリソースの事前キャッシュ">Service Worker で必要になりそうなリソースの事前キャッシュ</h2>
<p>また、初期画面に必ず必要なリソース以外については、「このあと必要になりそう・なるはずの追加リソース」ということで、Service Worker に投機的に事前 DL → Pre-cache されることも可能です。</p>
<p>このあたりのキャッシュ戦略・導入事例は以下の一休さんの記事が詳しいです。</p>
<p><a href="https://user-first.ikyu.co.jp/entry/2019/12/02/080000">一休.com に Service Worker(Workbox)を導入しました</a></p>
<h1 id="prpl-pattern-の採用状況">PRPL Pattern の採用状況</h1>
<p>さて、そんなパフォーマンスに嬉しい PRPL Pattern ですが、HTTP/2、PWA 自体の普及率も高くなくまだまだプロダクションでの採用事例は少ない印象です。</p>
<ul>
<li><a href="https://hack.nikkei.com/blog/nikkei-featured-at-google-io/">Google I/O で日経電子版が事例として紹介された話</a>
<ul>
<li>PRPL パターンを参考にした Service Worker を使ったキャッシュ、HTTP/2 Push でのリソース配信などが採用されている</li>
</ul>
</li>
<li>ライブラリでは有名どころだと Gatsby が標準対応、当たり前だが Google の Polymer ライブラリも PRPL パターンで実装されている</li>
</ul>
<p>とはいえ、PWA 化している Web サイトであればパターンの適用はそこまで難しくありません。</p>
<p>また Next.js の PWA 化ライブラリ <a href="https://github.com/shadowwalker/next-pwa">next-pwa</a> では、Next.js 本体の Code Splitting 機能と連携した「 Service Worker での追加コード読み込み」をサポートするなど、このようなアーキテクチャパターンの潮流は今後も派生していくのかな?という気がしています。</p>
<h1 id="まとめ---http2--pwa--prpl-pattern">まとめ - HTTP/2 + PWA + PRPL Pattern</h1>
<p>まとめです。</p>
<ul>
<li>PWA とは
<ul>
<li>Web プラットフォームの新機能を使った「ネイティブアプリとウェブアプリのいいとこ取りした UX の高いウェブアプリ」という概念</li>
</ul>
</li>
<li>PRPL パターンとは
<ul>
<li>スマートフォンなど回線強度・スペックの低いデバイスのために、Google が UX 向上のために提唱する PWA の設計アーキテクチャ</li>
<li>HTTP/2, Service Worker などを使って (リソースの) Server push, Pre-cache, Lazy load を行う</li>
</ul>
</li>
<li>プロダクトで使えるのか
<ul>
<li>Web サイトの PWA をする/しているのであれば、サーバの HTTP/2 化をして、リソースの Push、Pre-cache を導入するのはパフォーマンス観点で十分検討できるのでは</li>
<li>但し、SPA + SSR 構成のサイトでは、Next.js などフレームワークのコード分割に寄せるのが今の所無難そうではある</li>
</ul>
</li>
</ul>
<p>今回は調査のみで、プロダクトへの実践投入は行いませんでしたが、今後プロダクトの PWA 化が企画されるような場合は、ぜひ導入してみたい技術だなと感じました。</p>
<p>以上、ここまで読んでくださり、ありがとうございました。</p>medley
- ECS サービス間の通信を Amazon ECS サービスディスカバリで実現した話https://developer.medley.jp/entry/2020/06/30/175336https://developer.medley.jp/entry/2020/06/30/175336株式会社メドレーのエンジニアの阪本です。
緊急事態宣言も開け、普段の生活を取り戻しつつあるこの時期、
皆さんはいかがお過ごしでしょうか?
私は野球観戦(虎党)を毎日の楽しみとしています。
今年はコロナ渦の影響で開幕予定が遅延したものの、自粛...Tue, 30 Jun 2020 08:53:36 GMT<p>株式会社メドレーのエンジニアの阪本です。</p>
<p>緊急事態宣言も開け、普段の生活を取り戻しつつあるこの時期、
皆さんはいかがお過ごしでしょうか?</p>
<p>私は野球観戦(虎党)を毎日の楽しみとしています。
今年はコロナ渦の影響で開幕予定が遅延したものの、自粛期間を経て 6 月中旬にめでたくシーズン開幕を迎えることができました。
ここまでの「長い冬」が明け、テレビをつけると野球が見られる。
これで私自身も 2020 年が開幕したなと実感しています。</p>
<p>今回は、私がインフラ開発時に直面した問題と解決までの事例について紹介させて頂きます。</p>
<h1 id="背景">背景</h1>
<p>私はジョブメドレーのサービス開発を行っています。
このシステムは多くの機能で構成された大規模なもので、AWS の Elastic Container Service(以下 ECS)にて稼働しています。</p>
<p>このシステムに対し既存機能のリプレース案件に携わる機会がありました。
現在のシステムは多くの時間をかけて多くの機能を実装した結果、かなり大きなコードとなっています。
これにより、一つの変更が及ぼす影響が甚大なものになり得る状況だったため、リプレース対象の機能を新システムとして別アプリケーションに切り出して開発することにしました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629201809.png" alt="20200629201809.png">
<p>しかし、別システムとして一部の機能を切り出すものの、この機能は
既存システムとの連携が必要となります。
そのため、この連携をシステム間の API リクエストで実現することにしました。</p>
<h1 id="課題">課題</h1>
<p>ここで1つの問題が発生しました。
システム間の通信が必要になりましたが、お互い ECS サービスで分離した構成となるため
このままではアドレスの解決が出来ないことに気づきました。</p>
<p>同一 ECS サービス内でかつ、ネットワークモードが awsvpc モードであれば
ポート番号を分ける事により相互でアクセスが可能であるものの
異なるサービスであればポート以前にアドレス解決ができません。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629201843.png" alt="20200629201843.png">
<p>そのため、何らかの手段を持ってお互いの場所を認識できる状態にする必要があります。
そこで、これを実現できる幾つかの方法を検討しました。</p>
<h1 id="アプローチ">アプローチ</h1>
<h2 id="その1全て1つの-ecs-のサービスにまとめる">その1 全て1つの ECS のサービスにまとめる</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629201919.png" alt="20200629201919.png">
<p>上記の通り、既存システムが動いている ECS サービス/タスクに新システム(のコンテナ)を全て混ぜる方法です。
この場合、全てのポートを個別に割り振ることで 127.0.0.1:port によるアクセスが可能となるため
相互のリクエストも実現できることになります。</p>
<p>ただ、インフラ的には2つのシステムが1つの塊として構成される事になるため
デプロイの単位やスケールの単位を常に双方共有することになります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629201942.png" alt="20200629201942.png">
<p>こうなると、せっかくアプリケーションを分けたにも関わらず
運用の部分では何もメリットを得られないどころか制約が増えただけのようになりそうなのが問題です。</p>
<h2 id="その2内部-application-load-balancer-を経由する">その2 内部 Application Load Balancer を経由する</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629202007.png" alt="20200629202007.png">
<p>VPC 内部に internal な Application Load Balancer(以下 ALB) を設置し、接続先となる既存システムを TargetGroup に登録します。
この方法であれば、ALB のエンドポイントに向けてリクエストすることで配下の既存システムにアクセスする経路が確保できます。</p>
<p>また ECS サービスと TargetGroup が紐づくことにより既存システム側のデプロイやスケールが自動的に ALB 側にも連動することになるため、新システム側は既存システムのステータスを意識する必要は少なくなります。</p>
<p>これだと不自然な点も無くアプリケーション間の通信経路も確保できると期待しましたが・・・新たに問題が発生しました。</p>
<p>検証時に ALB/TargetGroup を新規作成。
ECS サービスについては
後付けで TargetGroup 脱着はできない
複数 TargetGroup の付与は AWS コンソールでは対応していない
といった理由のために AWS CLI での作成作業を行ったのですが、下記エラーが発生してしまいました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>An error occurred (InvalidParameterException) when calling the CreateService operation: load balancers can have at most 5 items.</span></span></code></pre>
<p>これは AWS による制限で、1 つの ECS サービスに関連付けることのできる TargetGroup は最大 5 つまでという事を表しています。
つまり既存システムは多くの機能やコンテナが同居している ECS サービスとなっていたため、既に TargetGroup が 5 つ存在しており上限に達していたのです。</p>
<blockquote>
<p>サービスで使用するロードバランサーを表すロードバランサーオブジェクト。Application Load Balancer または Network Load Balancer を使用するサービスの場合、サービスにアタッチできる 5 つのターゲットグループの制限があります。</p>
</blockquote>
<div class="remark-link-card-plus__container">
<a href="https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service_definition_parameters.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Amazon ECS サービス定義パラメータ - Amazon Elastic Container Service</div>
<div class="remark-link-card-plus__description">Amazon ECS サービスの実行方法を定義するサービス定義パラメータについて説明します。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://docs.aws.amazon.com/assets/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">docs.aws.amazon.com</span>
</div>
</div>
</a>
</div>
<p>この方法を取るなら不要な TargetGroup を削るかまとめるかの手を打つ必要がありますが、
周辺環境に対する影響があまりにも大きい為に現実的ではありませんでした。
そのため、新たな選択肢を探すことにしました。</p>
<h2 id="その3-amazon-ecs-サービスディスカバリを使用する">その3 Amazon ECS サービスディスカバリを使用する</h2>
<p>そんな中、ECS の機能としてサービスディスカバリ(サービス検出)というものを見つけました。</p>
<div class="remark-link-card-plus__container">
<a href="https://aws.amazon.com/jp/blogs/news/amazon-ecs-service-discovery/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Amazon ECS サービスディスカバリ | Amazon Web Services</div>
<div class="remark-link-card-plus__description">Amazon ECS でサービスディスカバリがサポートされました。これにより、ECS サービスが A […]</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://a0.awsstatic.com/main/images/site/fav/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">aws.amazon.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://d2908q01vomqb2.cloudfront.net/827bfc458708f0b442009c9c9836f7e4b65557fb/2020/06/03/Blog-Post_thumbnail.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629202146.png" alt="20200629202146.png">
<p>これは ECS サービスと Route53 内部ホスト空間を紐付ける機能です。
また ECS サービスの起動・停止・スケールといった対象が変動した場合にも、自動的にホスト空間のレコードを登録/解除し最新の状態に追従してくれるものです。</p>
<p>この場合、別々のサービス間でもお互い相手の名称を把握することができる上、
TargetGroup も新規に必要としないため、今回のニーズに適した方法になりそうです。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629202201.png" alt="20200629202201.png">
<h1 id="導入してみた結果">導入してみた結果</h1>
<p>試しに ECS サービスにサービスディスカバリを導入し、疎通できるか試してみます。
既に存在するサービスに後付でのサービスディスカバリは出来ないため、新規にサービスを作成 することになります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629202228.png" alt="20200629202228.png">
<p>今回は初めてのサービスディスカバリとなるので、名前空間も同時に作成することになります。
ひとまず名前空間を「<code>medley-blog.local</code>」、サービス検出名を「<code>service-discovery</code>」としてみます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629202502.png" alt="20200629202502.png">
<p>このまま ECS サービスを作成すると、Route 53 にて新たな名前空間「<code>medley-blog.local</code>」が作成されていることが確認できます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629202249.png" alt="20200629202249.png">
<p>この状態でサービスのタスクを 1 つ起動ししばらく待つと、<code>サービス検出名.名前空間</code>となる
<code>service-discovery.medley-blog.local</code>
に A レコードが追加されています。
このレコードに紐づく IP アドレスこそ、ECS タスクに紐付けられている IP アドレスになります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629202731.png" alt="20200629202731.png">
<p>あとはこの名称で別環境から疎通できるか試してみます。
<code>curl</code>コマンドで<code>service-discovery.medley-blog.local</code>で通信してみると、見事にレスポンスを受け取ることが出来ました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200629/20200629202751.png" alt="20200629202751.png">
<p>※ここでは検証のため、接続先アプリケーションは Nginx コンテナを配置しています</p>
<p>これにより、異なる ECS サービス間での通信を実現することができました。</p>
<h1 id="今後の予定">今後の予定</h1>
<p>現段階では検証段階のため評価環境への導入のみとなりますが、今の所は大きな問題も発生せず順調に稼働しています。
このまま特に問題無く稼働できれば、本番環境への導入も目指したいと思います。</p>
<h1 id="さいごに">さいごに</h1>
<p>メドレーのエンジニアは大小問わず課題に対して真摯に向き合い、試行錯誤し、突破口を開く取り組みを常に続けています。
そんな我々と一緒に働きたいと思った方、まずは下記リンクからご応募いただきカジュアルにお話しませんか?</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/engineer1.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">www.medley.jp</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=www.medley.jp" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
</a>
</div>medley
- デザイナーがデザインツールを使わずに、React を使ってデザインした話https://developer.medley.jp/entry/2020/06/19/194558https://developer.medley.jp/entry/2020/06/19/194558メドレーのデザイナー酒井です。最近、JobMedleyからCLINICSに異動しました。
自分はデザインはもちろん、HTML/CSS/JS 実装してプルリク送ったりしているちょっとフロントエンド実装領域に軸足が寄ったタイプのデザイナーです。...Fri, 19 Jun 2020 10:45:58 GMT<p>メドレーのデザイナー酒井です。最近、<a href="https://job-medley.com/">JobMedley</a>から<a href="https://clinics.medley.life/">CLINICS</a>に異動しました。
自分はデザインはもちろん、HTML/CSS/JS 実装してプルリク送ったりしているちょっとフロントエンド実装領域に軸足が寄ったタイプのデザイナーです。</p>
<p>ここでは以前所属していた JobMedley 事業部の話をさせていただきます。</p>
<p>当時、JobMedley の社内システムのリニューアルプロジェクトにデザイナーとして参加していました。通常、デザイナーがデザインをするときには Skecth や Figma のようなデザインツールを利用するのが一般的かと思います。</p>
<p>弊社でも基本的にはデザインツールでデザインを行うことが多いのですが、プロジェクトによっては、よりリアルなモックアップが必要なため、デザイナー自身がコーディングでデザインを行い、ブラッシュアップしていくことがあります。その後、フロントエンド実装者が、デザイナーが作ったデザインを参考に、しっかりと設計されたもので作り直します。</p>
<p>今回のリニューアルプロジェクトのデザイン・モックアップの制作では、Figma や Sketch などのデザインツールは使用せず、React でコーディング行い、デザインを制作しています。ここが特殊な制作フローになると思うので、このエントリでは React をデザインツールとして使ったときの流れとメリット・デメリット、ちょっとニッチなポイントに絞ってお話していきます。</p>
<h1 id="デザインの流れ">デザインの流れ</h1>
<h2 id="1まずは-react-を勉強する">1.まずは React を勉強する</h2>
<p>元々受託制作会社でデザイナーをやっていた時代に Vue.js での開発経験はありましたが、React は今回が初めてでした。そのため、一旦公式チュートリアルを一通り読み、Google などで調べつつ、基礎知識のインプットすることからはじめました。</p>
<h2 id="2ワイヤーフレームを作る">2.ワイヤーフレームを作る</h2>
<p>さすがにワイヤーフレーム無しでコード実装には入れないので、ここでは Figma などのツールを使います。ワイヤーフレームを作り、要件定義を進めていきますが、デザインに関してもある程度のあたりを付けていきます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200619/20200619172937.png" alt="20200619172937.png">
<h2 id="3開発環境構築">3.開発環境構築</h2>
<p>今回は Next.js で制作を行っています。
そもそも、プロダクトの実装で Next.js を使用することが決まっていたので、デザイン側も Next.js を採用しましたが、結果的に良かったなと感じています。</p>
<p>Next.js で作っておけば、めんどくさい router の設定やデザイナーが苦手な webpack の config を記述しなくてすみます。</p>
<p>また、<code>next build && next export</code>で静的な HTML ファイルとして書き出されるため、サーバがあればすぐに共有が可能です。今回はエンジニアさんに、develop ブランチに merge されると、<code>next build && next export</code>が走り、自動で Amazon S3 に静的ファイルをデプロイされるようにしてもらいました。</p>
<p>この時点でプロダクト側のフロントエンド設計も進んでいたため、ESLint やコンポーネント設計、ライブラリなどを流用し、デザインプロジェクト用に最適化させます。</p>
<h2 id="4nextjs-の設計">4.Next.js の設計</h2>
<p>実際にエンジニアがプロダクトを実装する際には色々なことを検討する必要があると思いますが、デザインツールとして使用する際には、逆にそこまでガチガチにしてしまうと実装に工数がかかりすぎてしまうので「割り切り」が必要になります。私が実装した際の環境は以下のようなものです。</p>
<ul>
<li>TypeScript は入れない。要件が固まりきっていない状態で型定義をやり始めると修正に時間がかかりすぎるため。</li>
<li>非同期通信処理も入れない。データがほしければローカルに json ファイルを配置しておく。</li>
<li>デザイン側のソースコードの汚さはあまり気にしない。プロダクト版を作成するときにフロントエンドエンジニアがきれいに作り直してくれる。</li>
<li>コンポーネント設計を意識する。共通コンポーネントとして使用する汎用的なものは先に洗い出しておく。</li>
<li>テストコードは書かない。そこまでページ数が多くない上、上記のような割り切りをしているので複雑性も極端に上がらない。</li>
<li>Google Chrome 最新版のみ対応とする。他のブラウザは無視する。</li>
</ul>
<p>(昨今のフロントエンド開発の流れと真逆、、、)</p>
<h2 id="5デザインルール設計">5.デザインルール設計</h2>
<p>続いてデザインルールの設計を行います。</p>
<p>theme.js ファイルを作成し、以下を定義します。この時点で定義が完全にできていることは無いと思うので、分かる範囲で書いていきます。最終的に、不要なものを消したり、一緒にできるものは統合して定義を減らしていきます。</p>
<p>各コンポーネントで padding や margin の余白を数値で指定したり、色味のカラーコドをベタ打ちせず、必ず theme ファイルから値を引っ張ってくるようにします。そうすると、修正が容易になる上、あとからどこで何が使われているかを把握しやすくなります。</p>
<p>■theme 内に記述する内容のイメージ</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>fontFamily// fontFamily を定義</span></span>
<span class="line"><span>colors// カラーコードの一覧</span></span>
<span class="line"><span>spaces // 4/8/16 など余白に使う数字を定義。</span></span>
<span class="line"><span>fontSizes // fontSize を定義</span></span>
<span class="line"><span>lineHeight // lineHeight を定義</span></span>
<span class="line"><span>fontWeight // fontWeight を定義。</span></span>
<span class="line"><span>boxShadow // ドロップシャドウを使用するなら定義</span></span></code></pre>
<h2 id="6コンポーネント設計">6.コンポーネント設計</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200619/20200619173007.png" alt="20200619173007.png">
<p>続いて、コンポーネントとコンポーネントに必要な props を洗い出します。</p>
<p>世の中には優秀な UI フレームワークが複数存在していますのでそういったものを参考にします。そういったものはほとんど同じようなコンポーネント設計になっているのでコンポーネントの分け方や受け取る props などを参考にすると作業が捗ります。</p>
<ul>
<li>Ant Design <a href="https://ant.design/">https://ant.design/</a></li>
<li>Material-UI <a href="https://material-ui.com/">https://material-ui.com/</a></li>
<li>Element <a href="https://element.eleme.io/#/en-US">https://element.eleme.io/#/en-US</a></li>
</ul>
<p>ここで気をつけたいのは、コンポーネントの数を極力減らすように努力することです。</p>
<p>同一機能の別コンポーネントが複数できてしまうと、今後の機能拡張時に、どのコンポーネントを使うか迷うことになります。ほとんどのエンジニアやデザイナーにとって、コンポーネント選びで迷うことは時間の無駄です。選択肢は極力減らす努力をし、減らせないようなら使用する場所を明確に定義する必要があります。</p>
<p>例えば</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>component/tab1/</span></span>
<span class="line"><span>component/tab2/</span></span>
<span class="line"><span>component/tab3/</span></span></code></pre>
<p>のようにタブコンポーネントが複数あると、あとから見たときにどれを使えばいいかわからなくなりますよね。
これを許容してしまうと、知らぬ間に</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>component/tab4/</span></span></code></pre>
<p>ができあがっていることでしょう。</p>
<h2 id="7実装">7.実装</h2>
<p>まずは大枠のページテンプレートを制作します。その後、小さいコンポーネントから順に作り上げていき、ページテンプレートに配置していきます。コンポーネント化ができていれば、見た目の変更はあとから行えるので、まずは page 内に配置し、機能することを目指します。</p>
<p>この時点で、デザインを細部まで作り込んだとしても、結局全体を見渡してから細かく調整をかけていくと思うので、そこまでセンシティブにならずに行っていきます。</p>
<h2 id="8確認ヒアリング修正の繰り返し">8.確認・ヒアリング・修正の繰り返し</h2>
<p>見た目が完全にできていない状態で、確認観点を絞って、PM や、ディレクター、実際に業務で使う方々にチェックしてもらいフィードバックを受けます。
徐々にブラッシュアップしていき、完成を目指します。</p>
<p>デザイン中には常に以下を意識して作業することで、無駄なものを介在させない設計を目指します。</p>
<ul>
<li>コンポーネントを共通化できないか</li>
<li>色数を減らせないか</li>
<li>fontSize や lineheight などの定義を減らせないか</li>
</ul>
<h2 id="9各種資料の作成">9.各種資料の作成</h2>
<p>作っているデザインではエラー処理などすべてを表現しきれないので、補足資料として別途スプレッドシートなどにまとめていきます。</p>
<p>また、実際のコンポーネントと想定している props、実装時の注意事項などを記載した UI コンポーネント集ページを制作し、フロントエンドエンジニアと共有します。</p>
<p>Storybook での共有も検討しましたが、受け渡す props の想定、エンジニアへの指示、要件の補足を記載していくだけですので、Storybook ほどの機能は不要と考え、Next.js 内にシンプルな Page を作り、そこに記述する方法を選択しました。</p>
<p>また、デザインの設計ルールも資料化し、最終的には各種資料と、制作したコード、S3 にアップされている URL を成果物としてエンジニアに実装をしていってもらいデザインのフェーズを完了させます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200619/20200619173035.png" alt="20200619173035.png">
<h1 id="react-でデザインした場合のメリット">React でデザインした場合のメリット</h1>
<h2 id="忠実度の高いモックアップが制作できる">忠実度の高いモックアップが制作できる</h2>
<p>プロトタイプツールは数多くあれど、実装にまさる忠実度の高さなし。
最近は Figma の AutoLayout や XD のスタックなど、便利な機能も登場してきていますし、プロトタイピングに関しても様々なアクションが設定できるので、非常に便利になってきました。ただ、それでも CSS や JS の表現力にはまだ届かない部分も多いかなと感じています。</p>
<p>ボタンを押したとき、特定の<code><select/></code>の<code><option/></code>が選択されたとき、複数条件下でのみ表示させるもの、ウインドウサイズが小さくなったときの表示など、デザインツールで実装するにはやや面倒な箇所も、コードベースなら対応可能で、デザインファイルと実装されたページでの印象差も起こりづらいです。</p>
<h2 id="実際に操作できる環境を渡すことで高い精度のフィードバックをもらえる">実際に操作できる環境を渡すことで高い精度のフィードバックをもらえる</h2>
<p>様々なリテラシーのユーザーが使用するサービスであるからこそ、よりリアルなプロトタイプを使用し、ミスコミュニケーションを減らせました。</p>
<p>結果論ですが、新型コロナウィルスによって、コミュニケーションを取りにくい状況が続きましたが、挙動を忠実に再現しているので、精度の高い意見を聞けたのは良かったです。</p>
<h2 id="プロダクト版のフロントエンドの実装時にこちらの意図が伝わりやすい">プロダクト版のフロントエンドの実装時に、こちらの意図が伝わりやすい</h2>
<p>fontSize や space、color を theme.js ファイルにまとめておき、そこからコンポーネント側では theme 側から読み込むようにしておけば、デザインルールが把握しやすくなります。また、CSS がすでに記述されているので、プロダクト版への移植も可能となり、「デザインファイルと実装でデザインが違う」が起こりにくくなります。</p>
<h2 id="git-やエディタの恩恵をフルに受けられる">Git やエディタの恩恵をフルに受けられる</h2>
<p>一旦別ブランチでデザイン案を作る、作ったブランチの commit から cherry-pick する、過去の log を見る、特定の commit に戻るなど、Git の恩恵をフルに受けられます。また、文字列の一括検索や置換が可能なので、色味を一気に置き換えたい、どこで使われているかを探したい場合などかなり重宝しました。</p>
<h1 id="とはいえいいことばかりではなかった">とはいえいいことばかりではなかった。。。</h1>
<ul>
<li>ちょっと要素の位置を変える、などはデザインファイルのほうが全然楽。コードベースだと CSS と HTML 構造自体を書き換えないといけない</li>
<li>React を理解していないと、動かなくなったりした時に、修正で時間を余計に使ってしまう</li>
<li>他のデザイナーに引き継ぎしずらい</li>
</ul>
<p>など、通常のデザインファイルでは起こらないようなデメリットもありました。</p>
<p>結局の所プロジェクトによって向き不向きはあります。</p>
<p>個人的には社内の管理システム系のように、一般的なコンポーネントを組み合わせて、色々なデータを表示する必要があるサイト制作に向いているかなと思います。</p>
<p>逆に、特殊なコンポーネントを量産するケースや、LP のようにグラフィック要素が多くなってくるとデザインツールのほうがはかどります。</p>
<p>プロジェクトの性格に応じて最適なデザイン方法を選んでいきたいですね。</p>
<p>デザイナーだけどコードも書きたい方や、一緒にプルリク送ってくれるデザイナーの方いらっしゃれば、ぜひメドレーで一緒に働きましょう〜</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/designer-new.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- メドレー知財担当がエンジニア・デザイナー向け社内勉強会で"特許"について発表しましたhttps://developer.medley.jp/entry/2020/05/22/173902https://developer.medley.jp/entry/2020/05/22/173902はじめに
こんにちは、メドレーのコーポレート本部法務コンプライアンス部で知的財産関連の業務を担当している鬼鞍です。コーポレート本部といっても、知財担当の仕事内容としてはエンジニア、デザイナーの方々としっかり協働することが一番大事なので、テッ...Fri, 22 May 2020 08:39:02 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは、メドレーのコーポレート本部法務コンプライアンス部で知的財産関連の業務を担当している鬼鞍です。コーポレート本部といっても、知財担当の仕事内容としてはエンジニア、デザイナーの方々としっかり協働することが一番大事なので、テックブログにも僭越ながら登場させていただきました。</p>
<p>先日、社内勉強会のテックランチにて、「特許」についてお話しする機会がありました。</p>
<p>今回の勉強会は、コロナウイルスの影響で全員オンライン参加という状況下でうまく伝えられるか心配だったのですが、予想以上に質問を頂いてそれなりに反響がありましたので、ここで紹介させていただこうと思います(厳密には特許権のことですがここでは説明の便宜上、以下特許と称することにします)。</p>
<p>みなさん、特許と聞いてどのようなイメージをもたれるでしょうか?</p>
<p>あなたが知財を仕事にしている人ではない限り、多くの方にとっては馴染みの薄い世界かもしれません。今回は、世間ではあまり表に出てこない特許という世界にスポットライトを当てつつ、少しでも特許の面白さみたいなものをお伝えできればと思います。</p>
<h1 id="特許とは陣地に敷かれた地雷でもあり身を守る鎧でもある">特許とは陣地に敷かれた地雷でもあり、身を守る鎧でもある</h1>
<p>さて、秒で特許のイメージをつかんでいただくためにここでは極端な例えをします。</p>
<p>他人が取得した特許は陣地に置かれた地雷のようなものであり、この地雷を踏んでしまうと特許権侵害というリスクで怪我をしてしまいます。あまりいい例えではないかもしれませんが、これは特許が技術を独占的に実施し、他社が実施した場合には排他できるという側面を表現しています。</p>
<p>企業の知財部門は、地雷を踏まないように日々特許調査をし、地雷のない安全な道を探る、いわば道先案内人のようなものなのです。</p>
<p>また逆に、自分たちが取得した特許があれば、地雷を踏んで権利を主張されてもカウンターパンチを当てることができるので、身を守るための鎧にもなるわけです。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200522/20200522160511.jpg" alt="f:id:medley_inc:20200522160511j:plain" title="f:id:medley_inc:20200522160511j:plain"></p>
<h1 id="特許とはエンジニアデザイナーの成果物である">特許とはエンジニア、デザイナーの成果物である</h1>
<p>ここで、特許について少し目線を変えて見てみましょう。</p>
<p>特許というものは、皆さんが努力・工夫した目に見えない技術的なアイディアが見える化された成果物である、という見方もできます。皆さんの頭の中にあるアイディアに知的財産権という形を与えることで、外部の人たちに明確に示すことができます。これによって、例えば投資家から高い評価を得たり、自分たちの技術力を PR するためのツールとして特許を用いることもできるのです。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200522/20200522152632.jpg" alt="f:id:medley_inc:20200522152632j:plain" title="f:id:medley_inc:20200522152632j:plain"></p>
<h1 id="特許権は国から与えられるグッドアイディア賞みたいなもの">特許権は国から与えられるグッドアイディア賞みたいなもの</h1>
<p>特許については知らなくてもグッドデザイン賞についてはご存知の方が多いかもしれません。</p>
<p>メドレーのプロダクトであるクラウド診療システム「CLINICS」はグッドデザイン賞を受賞しています。</p>
<p>「<a href="https://www.g-mark.org/award/describe/49638">クラウド診療支援システム [CLINICS] | 受賞対象一覧 | Good Design Award</a>」</p>
<p>また、ISMS(情報セキュリティマネジメントシステム)についての認証も受けています。</p>
<p>「<a href="/entry/2019/02/01/172457">全社で本気になってリーンに ISMS の仕組みをつくった話</a>」</p>
<p>なぜ、こんな話をしているかというと、グッドデザイン賞も ISMS も特許権も 1 つの共通点があるからです。それはいずれも客観的な審査を経て合格したものにだけ与えられてるものだという点です。</p>
<p>そして合格したものは、対外的に一定の信用を示すことができる、という機能を備えていいます。日本の特許庁の審査レベルは世界的に見ても高水準であると言われています。特許権というのは、その企業が技術的なアイディアに優れているという一定の信用を示すためのツールとしての性質も兼ね備えているのです。</p>
<h1 id="特許に潜むリスク">特許に潜むリスク</h1>
<p>先ほど特許は地雷であるという話をしました。より具体的に地雷を踏むとどんなリスクがあるのでしょうか?そのリスクは主に差し止め請求や損害賠償です。仮にこのような請求を受けてしまった場合は、大変な労力とコストが伴います。訴訟まで発展してしまうと、周囲の信用を失うことにもなりかねません。</p>
<p>このようなリスクを最小限にするために、知財担当の私が各プロダクトごとの開発定例に同席して自社の開発動向をキャッチアップし、他社権利の特許調査を行っています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200522/20200522152754.jpg" alt="f:id:medley_inc:20200522152754j:plain" title="f:id:medley_inc:20200522152754j:plain"></p>
<h1 id="特許的な思考プロセスとは本質を考えること">特許的な思考プロセスとは本質を考えること</h1>
<p>さて、ここで「特許的な思考プロセス」を使って皆さんに少し頭の体操をしていただこうと思います。</p>
<p>知財関連ではよく例に取り上げられるのですが、鉛筆の発明を題材にして考えてみます。</p>
<p>あなたは断面が円形の鉛筆を使っていましたが、机の上を転がって下へ落ちてしまうという問題点に気がつきました。あなたは、鉛筆の断面形状に着目し、断面を五角形にすることにより、この問題を解決したとしましょう。そして、「断面が五角形の鉛筆」という内容を特許請求の範囲(特許権の権利範囲部分を決めるもの)に記載して特許権を得ることができたとします。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200522/20200522152846.jpg" alt="f:id:medley_inc:20200522152846j:plain" title="f:id:medley_inc:20200522152846j:plain"></p>
<p>しかし、その後、あなたの断面五角形の鉛筆を見て他の業者が断面を三角形や四角形にした鉛筆を思いつき、それが市場に多く流通し、あなたの断面五角形の鉛筆の売り上げが下がってしまいました。あなたは特許請求の範囲に何と記載すべきだったのでしょうか?</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200522/20200522152924.jpg" alt="f:id:medley_inc:20200522152924j:plain" title="f:id:medley_inc:20200522152924j:plain"></p>
<p>そう、あなたは「断面が五角形の鉛筆」ではなく「断面が多角形の鉛筆」と書いていれば良かったのですね。</p>
<p>そうすれば、三角形や四角形などの類似品の流通を防ぐことができました。これは簡単です。</p>
<p>その後、あなたは断面が楕円形でも、机の上を転がりにくいという効果を生み出すことに気がつきます。では、多角形も楕円形も含めるためにはどのような表現がいいのでしょうか?ちょっと考えてみてください。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200522/20200522153019.jpg" alt="f:id:medley_inc:20200522153019j:plain" title="f:id:medley_inc:20200522153019j:plain"></p>
<p>あくまで答えの一例ですが、「断面が非円形の鉛筆」とか、「断面がその中心からの距離である長軸と短軸とを有する鉛筆」などの表現であれば上記の全ての形状をカバーすることができます。</p>
<p>このように、いくつかの具体例から、効果を生み出すために必要最低限な要素は何なのか、共通点は何なのかを探し出すことが、アイディアの本質を捉えることになります。具体と抽象の間を行ったり来たりするプロセスです。</p>
<p>アイディアの本質を捉えるためには多くの具体例を出す発想力が必要です。</p>
<p>もしあなたが発想力豊かなエンジニアやデザイナーであれば強力な特許権が取得できるかもしれません。実際にプロダクトに生かされていない技術的なアイディアというのはあなたの頭の中に埋もれていることが多いものです。</p>
<h1 id="エンジニアやデザイナーの成果が報われる土壌作りに貢献したい">エンジニアやデザイナーの成果が報われる土壌作りに貢献したい</h1>
<p>知的財産権の生みの親はエンジニアやデザイナーの皆さんです。皆さんはあまり意識していないかもしれませんが、技術的なアイディアは日々の開発業務で生み出されていて、それが形になるものもあればならないものもあります。</p>
<p>メドレーでは、これまで検討された技術アイディアのうち、プロダクトとして実現化されたものや、そうでないものにかかわらず将来性を見込めそうなものについては、実際に特許出願をしてきました。</p>
<p>・<a href="https://www.j-platpat.inpit.go.jp/c1800/PU/JP-2019-185642/AF400254F45C09B0BC8C9BA831F8C3CB3C41740DF9A6E4E697B15FA9348785F1/11/ja">ブロックチェーンを用いた電子処方箋の管理方法(特願 2018-078928)</a></p>
<p>・<a href="https://www.j-platpat.inpit.go.jp/c1800/PU/JP-2018-120430/E64C6A57EDBBA6D793A8F2BC7966E6C6A71D17228FF6D498D8BB7E3A7EFB89F0/11/ja">症状チェッカー(特願 2017-011444)</a></p>
<p>・患者統合基盤(特願 2019-233247)(出願中につき概念図を IR 資料より抜粋)</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200522/20200522153158.jpg" alt="f:id:medley_inc:20200522153158j:plain" title="f:id:medley_inc:20200522153158j:plain"></p>
<p>また、メドレーでは、エンジニア、デザイナーの方々の成果物がプロダクトだけでなく、知的財産としてもきちんと社内外で認められ、それが名誉となるような土壌作りをしていこうと考えています。</p>
<h1 id="さいごに">さいごに</h1>
<p>メドレーは、テクノロジーを使って医療分野のデジタル化を推進し、医療ヘルスケアの未来をつくる会社です。そんなメドレーで生み出される素晴らしい技術やデザインを知的財産という分野で可能な限りバックアップしてきたいと思っています。少しでも特許の世界を感じて頂ければ幸いです。最後までお付き合い頂きありがとうございました!</p>medley
- Kotlin/Native を検証してみたhttps://developer.medley.jp/entry/2020/05/08/180203https://developer.medley.jp/entry/2020/05/08/180203こんにちは、インキュベーション本部でエンジニアをしています世嘉良です。
インキュベーション本部は 2020 年 2 月から新規事業の開拓などを目的に新設されたのですが、その中でも若手の部類として日々頑張っています。
CTO 平山のインタビュ...Fri, 08 May 2020 09:02:03 GMT<p>こんにちは、インキュベーション本部でエンジニアをしています世嘉良です。</p>
<p>インキュベーション本部は 2020 年 2 月から新規事業の開拓などを目的に新設されたのですが、その中でも若手の部類として日々頑張っています。
CTO 平山のインタビューとともにインキュベーションチームの紹介記事が、コーポレートサイトに掲載されています。こちらもぜひご覧ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/team/creator-story-incubation.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">www.medley.jp</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=www.medley.jp" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
</a>
</div>
<p>さて、今回は社内で行っている勉強会:テックランチの中で「Kotlin/Native」について発表する機会があったので紹介させていただきます。</p>
<h1 id="kotlinnative-について">Kotlin/Native について</h1>
<p>Kotlin は JetBrains 社によって 2011 年に発表されたプログラミング言語です。</p>
<p>現在では Android 開発などで主流の言語となっており、多くの人に利用されていると思います。
Kotlin/Native とはそんな Kotlin を使って様々なプラットフォーム上で実行可能なファイルを生成しようというプロジェクトです。</p>
<p><a href="https://kotlinlang.org/docs/reference/native-overview.html">Kotlin/Native - Kotlin Programming Language</a></p>
<p>仕組みとしては Kotlin のコードをもとに LLVM を生成し、それを様々なプラットフォームで利用可能なネイティブバイナリにコンパイルすることで追加のランタイムや JVM なしで動作するようにしています。</p>
<p>サポートしているプラットフォームは下記のとおりです。</p>
<ul>
<li>iOS (arm32, arm64, simulator x86_64)</li>
<li>macOS (x86_64)</li>
<li>watchOS (arm32, arm64, x86)</li>
<li>tvOS (arm64, x86_64)</li>
<li>Android (arm32, arm64, x86, x86_64)</li>
<li>Windows (mingw x86_64, x86)</li>
<li>Linux (x86_64, arm32, arm64, MIPS, MIPS little endian)</li>
<li>WebAssembly (wasm32)</li>
</ul>
<h1 id="コードの共通化について">コードの共通化について</h1>
<p>Kotlin/Native のチュートリアルを参考に、Android / iOS で共通化の流れを追ってみます。</p>
<p>作成されるプロジェクトは Android / iOS でそれぞれ “Hello, World!” を出力するシンプルなプログラムです。</p>
<p><a href="https://play.kotlinlang.org/hands-on/Targeting%20iOS%20and%20Android%20with%20Kotlin%20Multiplatform/01_Introduction">Kotlin Playground: Edit, Run, Share Kotlin Code Online</a></p>
<p>通常のプロジェクトとは別に SharedCode というプロジェクトを作成し、この中に Kotlin/Native (Android / iOS 共通のコード) を記述していきます。
それぞれのコードのレイアウトは以下の通りです。
※ SharedCode を読み込むためのマルチプラットフォームのための gradle ファイルも必要で、先程のウェブサイトに記載されています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200508/20200508170713.png" alt="20200508170713.png">
<p>commonMain の中に 「expect」 というキーワードを使って抽象型のようなものを宣言し、 androidMain / iOSMain といった各プラットフォームの実装の中で 「actual」 というキーワードを使用してその内容を記述します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="kotlin"><code><span class="line"><span style="color:#6A9955">// commonMain</span></span>
<span class="line"><span style="color:#D4D4D4">expect </span><span style="color:#569CD6">fun</span><span style="color:#DCDCAA"> platformName</span><span style="color:#D4D4D4">(): </span><span style="color:#4EC9B0">String</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// androidMain</span></span>
<span class="line"><span style="color:#D4D4D4">actual </span><span style="color:#569CD6">fun</span><span style="color:#DCDCAA"> platformName</span><span style="color:#D4D4D4">(): </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#CE9178"> "Android"</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955">// iOSMain</span></span>
<span class="line"><span style="color:#D4D4D4">actual </span><span style="color:#569CD6">fun</span><span style="color:#DCDCAA"> platformName</span><span style="color:#D4D4D4">(): </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4"> UIDevice.currentDevice.</span><span style="color:#DCDCAA">systemName</span><span style="color:#D4D4D4">() + </span><span style="color:#CE9178">" "</span><span style="color:#D4D4D4"> + UIDevice.currentDevice.systemVersion</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>このチュートリアルには記載ありませんが、「expect」 というキーワードはクラスや関数・アノテーションといったすべてのものに定義することができます。</p>
<p><a href="https://kotlinlang.org/docs/reference/platform-specific-declarations.html">Platform-Specific Declarations - Kotlin Programming Language</a></p>
<p>また iOSMain 内であれば iOS の Foundation フレームワークといったように、各プラットフォームごとのフレームワークもそれぞれ利用することができます。</p>
<p>こうしてできた、SharedCode のライブラリは Android/iOS のプロジェクトから使い慣れたライブラリと同じようにインポートすることができます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200508/20200508170931.png" alt="20200508170931.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200508/20200508170949.png" alt="20200508170949.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200508/20200508171007.png" alt="20200508171007.png">
<h1 id="kotlinnative-の制約について">Kotlin/Native の制約について</h1>
<p>JVM 上で Kotlin を動かしていた時には標準ライブラリやメモリ管理には Java の資産を利用することができましたが、Kotlin/Native ではそれを利用することができないという制約があります。</p>
<p>具体的にアプリを作成する際には下記の点に注意が必要でした。</p>
<ul>
<li>Java の標準ライブラリを使用しているライブラリを利用できないため Kotlin で記述されたライブラリのみが使用する</li>
<li>iOS アプリを開発する際には memScope, alloc / free などを利用してメモリ管理を自分で行う必要がある</li>
</ul>
<p>Java の資産が利用できない Kotlin に対して心配することもありましたが、Kotlin/Native 向けに様々なライブラリが 提供されておりそれらを利用することで問題を解決しました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200508/20200508171035.png" alt="20200508171035.png">
<p>現状は日付管理ですらサードパーティ製のライブラリで操作する必要があるのが不満ですが、Kotlin/Native の発展によってこういった不便さは解決されるように願っています。</p>
<ul>
<li>「suspend」 関数を iOS 側から直接呼び出すことができない (コールバック関数やなどに変換して呼び出す必要がある)</li>
<li>Kotlin で定義した宣言や型の一部が Swift だと別名になっている (「
objc_runtime_name」 や 「swift_name」 を参照する必要がある)</li>
<li>「companion object」 や 「object」 などシングルトンの宣言に対してオブジェクトをフリーズするか 「ThreadLocal / SharedImmutable」 のアノテーションを付与する必要がある</li>
</ul>
<p>こちらは iOS で利用する場合の注意ですが、いくつかの Kotlin の機能が利用できなかったり、追加の記述が必要なものがあります。
詳しくは下記の資料を参照ください。</p>
<p><a href="https://kotlinlang.org/docs/tutorials/native/apple-framework.html#kotlinnative-runtime-declarations">Kotlin/Native as an Apple Framework - Kotlin Programming Language</a></p>
<p><a href="https://github.com/JetBrains/kotlin-native/blob/985385e2fac037a1e9d8f2253139fced195c7421/IMMUTABILITY.md#L18">kotlin-native/IMMUTABILITY.md</a></p>
<h1 id="kotlinnative-の利用例">Kotlin/Native の利用例</h1>
<p>まだまだ利用例が少ないため参考になる資料などがあまりないのですが、以下の実装がオープンソースとして公開されているため構成や雰囲気を掴むためにはちょうど良い資料となりました。</p>
<ul>
<li><a href="https://github.com/JetBrains/kotlinconf-app">GitHub - JetBrains/kotlinconf-app: KotlinConf Schedule Application</a></li>
<li><a href="https://github.com/DroidKaigi/conference-app-2020">GitHub - DroidKaigi/conference-app-2020: The Official Conference App for DroidKaigi 2020 Tokyo</a></li>
</ul>
<h1 id="まとめ">まとめ</h1>
<p>弊社では 「CLINICS」 というオンライン診察を行うことができるアプリをリリースしています。</p>
<p>CLINICS を長期的に運用していくための技術調査の一環として、近年の Android 界隈で話題にあがっていた「Kotlin/Native」について検証し発表してみました。</p>
<p>まだまだ発展途上感のある技術ですが、実用性のあるプロジェクトだと思いますのでご興味のある方はぜひ一度お試しください!</p>medley
- 社内勉強会 TechLunch でジョブメドレーでの CircleCI の活用と改善について発表しましたhttps://developer.medley.jp/entry/2020/04/30/193950https://developer.medley.jp/entry/2020/04/30/193950こんにちは、メドレープロダクト開発室 エンジニアの岸田です。
先日、社内勉強会 TechLunch にて、弊社の提供する医療介護分野の人材プラットフォーム「ジョブメドレー」の開発で利用している CircleCI での CI/CD についての...Thu, 30 Apr 2020 10:39:50 GMT<p>こんにちは、メドレープロダクト開発室 エンジニアの岸田です。</p>
<p>先日、社内勉強会 TechLunch にて、弊社の提供する医療介護分野の人材プラットフォーム「ジョブメドレー」の開発で利用している CircleCI での CI/CD についての取り組みを発表しましたので、紹介させていただきたいと思います。</p>
<h1 id="ジョブメドレーの開発で-circleci-をどのように利用しているか">ジョブメドレーの開発で CircleCI をどのように利用しているか</h1>
<p>ジョブメドレーの開発では、主に次の 2 つを CircleCI を用いて行なっています。</p>
<ul>
<li>ユニットテスト・構文チェック</li>
<li>デプロイ</li>
</ul>
<p>デプロイに関しては、ECS 環境と EC2 環境への 2 通りのデプロイを CircleCI を利用して行なっています。</p>
<p>そのため CircleCI の Workflow は「ユニットテスト・構文チェック」「EC2 へのデプロイ」「ECS へのデプロイ」の 3 つに分かれています。</p>
<p>3 つの Workflow を大まかに説明させていただきます。</p>
<h2 id="ユニットテスト構文チェック">ユニットテスト・構文チェック</h2>
<p>ジョブメドレーは主にサーバサイドを Ruby on Rails、フロントエンドを React を使って開発をしています。
そのためユニットテストには RSpec ・ Jest を、構文チェックには Rubocop と ESLint 利用しています。</p>
<p>この Workflow は 3 つのジョブで構成されています。</p>
<ol>
<li>ライブラリのインストール
<ul>
<li><code>bundle install</code>・ <code>yarn install</code>などを行い、ユニットテスト・構文チェックの実行に必要な依存ライブラリをインストールし、CircleCI のキャッシュ機能を用いてキャッシュを行なっています</li>
</ul>
</li>
<li>RSpec の実行
<ul>
<li>2 コンテナを利用しての並列実行を行なっています</li>
</ul>
</li>
<li>構文チェックと Jest の実行
<ul>
<li>RSpec と比較して Jest でのテストコードはボリュームが少なく・実行時間が短いため、構文チェックと一緒に 1 つのジョブにしています</li>
</ul>
</li>
</ol>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200430/20200430190805.png" alt="20200430190805.png">
<p>この Workflow はどのブランチでも GitHub へ Push が行われるたびに実行されるようにしています。</p>
<h2 id="ec2-へのデプロイ">EC2 へのデプロイ</h2>
<p>この Workflow ではデプロイを行うための準備と、文字通り EC2 へのデプロイを行なっています。
デプロイには Ruby gem の Capistrano を利用しています。</p>
<p>下記のような 2 つのジョブで構成しています。</p>
<ol>
<li>ライブラリのインストール
<ul>
<li>前述のユニットテスト・構文チェックの Workflow の 1 つ目と同じ役割です</li>
</ul>
</li>
<li>ビルドとデプロイ
<ul>
<li>Rails の assets:precompile や<code>yarn build</code>などの処理と、Capistrano を用いたデプロイを行なっています</li>
</ul>
</li>
</ol>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200430/20200430190826.png" alt="20200430190826.png">
<p>この Workflow は特定のブランチでしか実行されないようになっています。</p>
<h2 id="ecs-へのデプロイ">ECS へのデプロイ</h2>
<p>この Workflow では ECS へデプロイするための Docker image 作成とそのためのビルドなどを行なっています。</p>
<p>ジョブは下記のように 4 つで構成されています。</p>
<ol>
<li>npm package のインストール</li>
<li>フロントエンドのビルド</li>
<li>Gem のインストールと Rails の assets:precompile</li>
<li>Docker image の作成と Push</li>
</ol>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200430/20200430190429.png" alt="20200430190429.png">
<p>こちらの Workflow も特定のブランチでしか実行されないようになっています。</p>
<h1 id="抱えていた課題と-assetsprecompile">抱えていた課題と assets:precompile</h1>
<p>ジョブメドレーの開発では CircleCI の各ジョブが全て正常に完了することを PR をマージする条件の 1 つにしています。
しかし各 Workflow ・ジョブの実行時間が長く、ジョブの実行待ちをしなければいけないという状況がよく起こってしまっていました。</p>
<p>特に時間がかかっていたのが、下記の 3 つでした。</p>
<ol>
<li>「EC2 へのデプロイ」Workflow の「デプロイ」ジョブ</li>
<li>「ECS へのデプロイ」Workflow の「Gem のインストールと Rails の asset_precompile」ジョブ</li>
<li>「RSpec の実行」ジョブ
<ul>
<li>RSpec の書き方などを改善することで短縮できるため、割愛させていただきます</li>
</ul>
</li>
</ol>
<p>これらについて調査したところ、1・ 2 は、assets:precompile が主に時間を使っていることが分かりました。
この点について原因と行なった改善を説明をさせていただこうと思います。</p>
<h2 id="assetsprecompile-に時間がかかる">assets:precompile に時間がかかる</h2>
<p>ジョブメドレーでは Rails のアセットパイプラインを利用して、アセットファイルのコンパイル・最小化などを行なっています。
これを実行する際に <code>assets:precompile</code> コマンドを利用しています。
また、同コマンド実行時にコンパイルしたファイルを AWS S3 バケットにアップロードするために <code>asset_sync</code> gem を利用しています。</p>
<p>このコマンドの実行には 9 分から 10 分ほどの時間がかかっており、下記の 2 つの原因で遅くなっていました。</p>
<ol>
<li>毎回 1 からコンパイルを行なっていた</li>
<li>コンパイル後のファイルをアップロードする S3 バケットに大量のファイルが存在する</li>
</ol>
<h3 id="毎回-1-からコンパイルを行なっていた">毎回 1 からコンパイルを行なっていた</h3>
<p>こちらについては読んで字のごとくですが、デプロイ Workflow が実行されるたびにアセットファイルの変更有無に関わらず、毎回 3 分ほどを費やして全てのアセットファイルをコンパイル・最小化していました。
こちらの解決策としては、<a href="https://circleci.com/docs/ja/2.0/caching/#%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%81%AE%E4%BF%9D%E5%AD%98%E5%BE%A9%E5%85%83%E3%81%AE%E5%8F%82%E8%80%83%E4%BE%8B">CircleCI の公式ドキュメントでも例が載せられています</a>が、 <code>assets:precompile</code> コマンドで生成されるファイルが置かれるディレクトリ(<code>public/assets</code>)を CircleCI でキャッシュさせることで対応しました。
キャッシュさせることで、毎回 3 分ほどかかっていた処理を 1 分ほどに短縮することができました。</p>
<h3 id="コンパイル後のファイルをアップロードする-s3-バケットに大量のファイルが存在する">コンパイル後のファイルをアップロードする S3 バケットに大量のファイルが存在する</h3>
<p>こちらついてはもう少し背景が複雑かつ、まだ解決まではできていません。</p>
<p>まず <code>asset_sync</code> を利用したファイルのアップロード処理ですが、毎回 6 分以上の時間がかかっていました。
CircleCI のログをよくよく見てみるとアップロード自体に時間がかかっているのではなく、「どのファイルをアップロードすべきか」を判断する処理に多くの時間を費やしていることが分かりました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200430/20200430190719.png" alt="20200430190719.png">
<p>(1 ファイルもアップロードしていないが 6 分 20 秒かかっている)</p>
<p>そこで、<code>asset_sync</code> gem のソースコードを読んでみると、「アップロード先の S3 バケットにあるファイルを全て取得し、 <code>assets:precompile</code> コマンドが生成したファイルのファイル名と比較する」という処理がありました。
この処理が怪しいのではないかと思い、S3 バケットを確認してみたところ数十万件以上のファイルが存在することが分かりました。</p>
<p>数十万件以上のファイルの情報を取得していることを考えると 6 分以上時間がかかるのも納得です。</p>
<p>この問題の解決策は下記の 2 つが考え得ると考えました。</p>
<ol>
<li>不要なファイルを S3 バケットから削除する</li>
<li>前回のデプロイとの比較をして S3 にアクセスせずにアップロードすべきファイルかどうかを判断するようにする</li>
</ol>
<p>1 つ目の方法は正攻法で、数十万ファイルを全て使っているわけではないため利用されていないファイルを削除してしまう方法です。</p>
<p>asset_sync も早くなり、S3 の利用料も少なくなるためこの方法を取れるのであれば、この方法で解決するのが良いように思います。
ジョブメドレーでもこの方法を取れないかと検討しましたが、ジョブメドレーから配信している HTML メールなどでも利用しているファイルがあるため一概にアクセスされていないからといって削除することができず、この方法での解決は一旦断念しました。</p>
<p>2 つ目の方法は<code>assets:precompile</code>コマンドが生成する manifest ファイル(生成したファイルのリストなどが記述されている)と CircleCI のキャッシュ機能を使って短縮する方法です。</p>
<p>manifest ファイルは、コンパイル後のアセットファイルが出力されるディレクトリ(<code>public/assets/</code>)に同じように出力され、また、上記で<code>public/assets</code>を CircleCI のキャッシュ機能でキャッシュするようにしたため、manifest ファイルも一緒にキャッシュされるようになっています。</p>
<p><code>assets:precompile</code>の実行により今回作成された manifest ファイルと、キャッシュされていた manifest ファイルを比較して差分が出たファイルだけを S3 にアップロードするようにしようという試みです。
この処理は<code>asset_sync</code> gem ではできそうになかったので、シェルスクリプトと Rake タスクを作成して実行してみたところ、「アップロードすべきファイルかどうか」を判断するための時間がほぼなくなり、6 分以上の短縮をすることができました。</p>
<p>ただし検証が不十分なため、実運用に乗せることはまだできていません。</p>
<h1 id="circleci-のジョブ実行時間を短縮する小さな改善事項">CircleCI のジョブ実行時間を短縮する小さな改善事項</h1>
<p>上述の通り、各 Workflow で大きく時間を費やしているのは<code>assets:precompile</code>と RSpec の実行でしたが、細かな点としては他にも小さい改善をしたことがあります。</p>
<h2 id="コードのチェックアウト">コードのチェックアウト</h2>
<p>CircleCI では組み込みのコマンドとして <code>checkout</code> コマンドがあります。
これは対象のリポジトリのブランチをクローンしてくれるコマンドですが、ジョブメドレーはモノレポに近い構成になっており、コードベースのサイズや履歴が大きいためクローンするだけである程度の時間がかかってしまっています。</p>
<p>そこでまずは、<code>.git</code>ディレクトリを CircleCI のキャッシュ機能を利用してキャッシュするようにしてみました。
すると、<code>checkout</code>コマンド自体は大きく短縮しましたが、キャッシュの save/restore に短縮した以上の時間がかかるようになってしまいました。</p>
<p>別の方法として<code>checkout</code>コマンドを利用せずに、Git の Shallow clone や Sparse clone を駆使して必要なファイルや履歴だけをクローンするということができます。
現在は Sparse clone を一部導入してみており、Shallow clone も導入したいと考えています。</p>
<h2 id="docker-image-の作成">Docker image の作成</h2>
<p>ジョブメドレーでは ECS へのデプロイの際に CircleCI 上で Docker image をビルドしているため、<code>docker build</code>コマンドの実行時間も可能な限り抑えたいと考えています。</p>
<p>短縮する方法は様々あるかと思いますが、CircleCI 上でも Dokcer Buildkit を利用することがきますので、それを利用してビルドすることで簡単に短縮することができます。</p>
<p><a href="https://matsuand.github.io/docs.docker.jp.onthefly/develop/develop-images/build_enhancements/">詳しくは Docker のガイド</a>に記載の通りではありますが、DOCKER_BUILDKIT=1 を指定して<code>docker build</code>を実行するだけで利用することができ、ジョブメドレーでは 2 分ほどかかっていたビルドを 40 秒ほど短縮することができました。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回は、TechLunch で発表したジョブメドレーにおける CircleCI の活用と改善の取り組みについて紹介させていただきました。
今回紹介させていただいた以外にも様々な用途があり、今後もさらにうまく活用していきたいと思っています。</p>
<p>ご覧いただきありがとうございました。</p>medley
- 社内勉強会 TechLunch で CLINICS を取りまく監視ツール群について発表しましたhttps://developer.medley.jp/entry/2020/04/10/192919https://developer.medley.jp/entry/2020/04/10/192919皆様こんにちは。 プロダクト開発室 エンジニアの濱中です。
新型コロナウイルス感染症が猛威を振るっておりますが、皆様いかがお過ごしでしょうか。先日の緊急事態宣言を受けて、メドレーも全社員が原則リモートで業務に当たっています。オンライン医療事...Fri, 10 Apr 2020 10:29:19 GMT<p>皆様こんにちは。 プロダクト開発室 エンジニアの濱中です。</p>
<p>新型コロナウイルス感染症が猛威を振るっておりますが、皆様いかがお過ごしでしょうか。先日の緊急事態宣言を受けて、メドレーも全社員が原則リモートで業務に当たっています。オンライン医療事典「MEDLEY」にも、<a href="https://medley.life/covid19s/">コロナウイルス関連の特集</a>が組まれていますのでぜひご覧ください。</p>
<p>さて先日、社内勉強会 TechLunch にて、弊社の提供するクラウド診療支援システム「CLINICS」のサービス運営で使われている監視ツール群について発表したので、ご紹介させていただきます。</p>
<h1 id="clinics-の概要とサービスにおける監視の立ち位置について">CLINICS の概要と、サービスにおける監視の立ち位置について</h1>
<p><a href="https://clinics-cloud.com/">公式ページ</a>で「クラウド診療支援システム」と銘打っている CLINICS ですが、より詳細には、電子カルテシステム・オンライン予約システムも提供しています。これら予約・診療・カルテの 3 プロダクトで医療現場の業務を一手に引き受けることを目的としたサービスが CLINICS です。</p>
<p>このうち、電子カルテシステムは、「<a href="https://www.jma-receipt.jp/">日医標準レセプトソフト ORCA</a>」という製品を内包する形で提供しており、ORCA 自体は独立した環境で動作しています。</p>
<p>当然ながら、ORCA 自体が CLINICS とは独立した 1 プロダクトであるため、CLINICS 側に問題がなくても ORCA サーバがパフォーマンス低下や不具合を起こしてしまったり、あるいは CLINICS の web サーバとの通信がうまくいかなくなるケースも考えられます。</p>
<p>電子カルテシステムは医療機関の診察・会計といった日常業務の重要なパートを担っており、ここで障害が発生すると業務が止まってしまいます(最悪、患者さんをお待たせしてしまうこともあります)。</p>
<p>そのため、業務に大きな影響を与える箇所を中心に、複数の監視ツールでアプリケーションの動作状況をチェックしています。</p>
<h1 id="監視ツールあれこれ">監視ツールあれこれ</h1>
<p>ここからは実際に CLINICS で利用しているツールを簡潔にご紹介していきます。</p>
<h2 id="sentry">Sentry</h2>
<div class="remark-link-card-plus__container">
<a href="https://sentry.io/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Application Performance Monitoring & Error Tracking Software</div>
<div class="remark-link-card-plus__description">Application performance monitoring for developers & software teams to see errors clearer, solve issues faster & continue learning continuously. Get started at sentry.io.</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://sentry.io/static/favicon-46f8676a36982f8eb852ac6860387755.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">sentry.io</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://sentry.io/_astro/home-hero-meta.DJDejDMD.webp" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>弊社の他プロダクト(「<a href="https://job-medley.com/">ジョブメドレー</a>」、「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」)でも利用している監視ツールです。</p>
<p>基本的には、アプリケーションで例外が発生すると自動でアラートを投げてくれますが、アプリケーション側で catch してしまうものや、例外ではないが通知したいイベントについて、任意にアラートをあげることも可能です。</p>
<p>CLINICS では例えばアカウント ID やアクセスした API エンドポイント・パラメータなど渡して、エラー調査に活用しています。</p>
<h2 id="mackerel">Mackerel</h2>
<div class="remark-link-card-plus__container">
<a href="https://mackerel.io/ja/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Mackerel(マカレル): 始めやすくて奥深い、可観測性プラットフォーム</div>
<div class="remark-link-card-plus__description">Mackerel(マカレル)は誰でも簡単に始めやすく奥深い可観測性プラットフォーム。運用をイージーにするオブザーバビリティを高め、未知の問題に立ち向かう開発者に力を与えます。サーバー監視をMackerelではじめてみませんか?無料プランや2週間のトライアルもあります。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://ja.mackerel.io/hubfs/favicon-normal.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">mackerel.io</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://ja.mackerel.io/hubfs/ogp.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>GUI で簡単に監視設定が可能なサービスです。</p>
<p>前職で使っていた時は GUI でできるところまでしか触っていなかったのですが、設定ファイルを手製すれば、自作スクリプトの出力をチェックしたり、チェックに失敗した時何か処理をしたり…といったマニュアルな監視も可能です。</p>
<p>現在 CLINICS においては、ORCA インスタンスがそれぞれ独立して動作していますが、Mackerel ではこの ORCA インスタンスを監視しています。また監視するだけでなく、動作不良を起こした時の再起動処理なども担当しています。</p>
<h2 id="aws-cloudwatch--lambda">AWS CloudWatch & Lambda</h2>
<div class="remark-link-card-plus__container">
<a href="https://aws.amazon.com/jp/cloudwatch/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">APM Tool - Amazon CloudWatch - AWS</div>
<div class="remark-link-card-plus__description">Amazon CloudWatch は、DevOps エンジニア、デベロッパー、サイト信頼性エンジニア (SRE)、IT マネージャー、および製品所有者のために構築されたモニタリングサービスです。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://a0.awsstatic.com/libra-css/images/site/fav/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">aws.amazon.com</span>
</div>
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://aws.amazon.com/jp/lambda/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">サーバーレス関数、FaaS サーバーレス — AWS Lambda — AWS</div>
<div class="remark-link-card-plus__description">AWS Lambda は、サーバーのプロビジョンや管理をすることなくコードを実行するためのサーバーレスコンピューティングサービスです。料金は、コンピューティングに使用した時間に対してのみ発生します。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://a0.awsstatic.com/libra-css/images/site/fav/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">aws.amazon.com</span>
</div>
</div>
</a>
</div>
<p>どちらも AWS の提供するツールです。</p>
<p>CloudWatch は主にログの記録と監視を行うためのツールです。複数の機能がありますが、CLINICS では主に Logs, Metrics, Alarm を使用しています。</p>
<p>Logs はその名の通り、主に AWS のツールが発行するログを収集・検索・閲覧する機能で、問題発生時の HTTP リクエストのパラメータを確認する時などに利用しています。</p>
<p>Metrics は、「プロダクト(EC2 ならインスタンス, RDS なら DB インスタンスなど製品の一単位)」×「メトリクス種別(CPU 利用率など)」を一単位として、統計情報を可視化するツールです。</p>
<p>それらに閾値を設け、超えた場合にアラートを発行するのが Alarm…という位置付けです。</p>
<p>対して Lambda は、そもそも監視ツールではありません。定期実行、あるいは何かのイベント(S3 に何かアップロードされた、など)をトリガーとして、処理を行うツールです。処理した結果を、さらに別のツールへ連携(Slack 通知などの外部連携も含む)させることも可能です。</p>
<p>CLINICS では、上記アラート通知のほか、<a href="https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager.html">SessionManager</a>経由でインスタンスに ssh ログインした時の通知や、時間のかかるインポートタスクなどが終了した時の通知などにも利用しています。</p>
<h2 id="new-relic">New Relic</h2>
<div class="remark-link-card-plus__container">
<a href="https://newrelic.co.jp/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">思考するオブザーバビリティ</div>
<div class="remark-link-card-plus__description">New Relic、New Relicオブザーバビリティ、New Relicソフトウェアオブザーバビリティ、New Relicオブザーバビリティプラットフォーム</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://newrelic.co.jp/themes/custom/erno/assets/images/metadata/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">newrelic.co.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://newrelic.com/themes/custom/erno/assets/images/metadata/NROG_Image.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>New Relic は、CLINICS ではパフォーマンス評価ツールとして利用しています。</p>
<p>メインとなるのは APM(Application Performance Monitoring)で、アプリケーション内のどの処理にどれだけ時間がかかったかなどを閲覧することができます。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200410/20200410183154.png" alt="f:id:medley_inc:20200410183154p:plain" title="f:id:medley_inc:20200410183154p:plain"></p>
<p>医療機関は時間によって診察ペースが大きく変わるため、1 回 1 回は少しの遅延であっても、業務フローのコアな部分に絡んでいる場合、遅延が響いてくることはあります。</p>
<p>このような、「エラーは出ないけど、レスポンスが遅い」というご連絡を受けた時の調査・メンテナンスに主に活躍してくれます。</p>
<p>Sentry のようにエラー検知を行なったりもでき、ツール自体の多機能さから取得できる情報の多さが段違いといった印象です。</p>
<h2 id="firebase-crashlytics">Firebase Crashlytics</h2>
<div class="remark-link-card-plus__container">
<a href="https://firebase.google.com/?hl=ja" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Firebase | Google's Mobile and Web App Development Platform</div>
<div class="remark-link-card-plus__description">Firebase は、デベロッパーがユーザーに人気のアプリやゲームを開発できるよう支援する Google のモバイルおよびウェブアプリ開発プラットフォームです。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.gstatic.com/devrel-devsite/prod/vb733f83c4d98b98a393545fb4caff59cc746f444c6f32fab64ef45038fb6ff9b/firebase/images/favicon.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">firebase.google.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://firebase.google.com/images/social.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>Firebase もまた”多機能な”ツールですが、CLINICS では監視機能として Crashlytics を使っています。</p>
<p>CLINICS では、患者さんが予約・オンライン診療を行うためのアプリを iOS / Android で公開しており、医療機関向けの Web アプリケーション側と連携して動作します。このネイティブアプリで発生したクラッシュを集計しているのが Crashlytics です。</p>
<p>Android の場合は build.gradle に設定を追記してアプリをビルド、iOS の場合は cocoapods で外部ライブラリとしてインストールすることで集計が可能になります。Unity アプリにも対応しているとのことです。</p>
<p>その他、監視ではないですがアプリを利用している端末や OS バージョンなどの統計情報の収集も可能です。</p>
<h1 id="pagerduty-による監視集約">PagerDuty による監視集約</h1>
<div class="remark-link-card-plus__container">
<a href="https://ja.pagerduty.com/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">PagerDuty|インシデント管理プラットフォーム|PagerDuty株式会社</div>
<div class="remark-link-card-plus__description">PagerDuty(ペイジャーデューティ)はシステムのインシデント対応を一元化するプラットフォームです。DX時代におけるオペレーショナル・レジリエンスを高め、障害対応に費やす時間を軽減することで、貴重なエンジニアリソースをビジネス拡大に充てることができます。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.pagerduty.co.jp/wordpress/wp-content/uploads/2022/08/apple-touch-icon.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">ja.pagerduty.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.pagerduty.co.jp/wordpress/wp-content/uploads/2023/07/pd_ogp.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>CLINICS ではここまでに挙げてきたツールを使って監視を行っておりますが、昨年、複数の監視ソースからのアラートを一本化すべく、インシデント管理サービス PagerDuty を導入しました。</p>
<p>導入後は、全てのアラートツールはアラートを一旦 PagerDuty に集約し、PagerDuty から Slack 通知を行うという形式になりました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200410/20200410183430.png" alt="f:id:medley_inc:20200410183430p:plain" title="f:id:medley_inc:20200410183430p:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200410/20200410183455.png" alt="f:id:medley_inc:20200410183455p:plain" title="f:id:medley_inc:20200410183455p:plain"></p>
<p>また、PagerDuty が出すアラートは、担当者が確認してレスポンスを返す(acknowledge)ことを行わずに一定時間が過ぎた場合、電話や SMS での通知へ展開していくように設定することができます。これによって、クリティカルなエラーの見落としを確実に防げるようになりました。</p>
<h1 id="まとめ">まとめ</h1>
<p>CLINICS で利用している監視ツール+α を紹介させていただきました。</p>
<p>これらのツールを駆使しながら、ユーザの皆様に安心してお使いいただけるようサービスの安定稼動に尽力しています。</p>
<p>ご覧いただきありがとうございました。</p>medley
- 2020 年、プロダクト開発合宿に行ってきました!@伊東https://developer.medley.jp/entry/2020/03/27/184623https://developer.medley.jp/entry/2020/03/27/184623こんにちは、インキュベーション本部の加藤です。
2019 年に新卒でメドレーに入社し、間もなく 1 年が経とうとしています。
少し時間が経ってしまったのですが、今回は 1 月に行われたプロダクト開発合宿の様子をお届けします。
プロダクト開発...Fri, 27 Mar 2020 09:46:23 GMT<p>こんにちは、インキュベーション本部の加藤です。
2019 年に新卒でメドレーに入社し、間もなく 1 年が経とうとしています。</p>
<p>少し時間が経ってしまったのですが、今回は 1 月に行われたプロダクト開発合宿の様子をお届けします。</p>
<h1 id="プロダクト開発合宿の目的">プロダクト開発合宿の目的</h1>
<p>メドレーでは「1 年間の方針を共有すること」「メンバー間の親睦を深めること」を目的とした開発合宿を、毎年年始に行っています。</p>
<p>合宿の恒例行事となっている「1 年間のプロダクト開発の指針」を CTO 平山がプレゼンし、全員で共有する大切な機会です。
また、親睦を深める観点で、集中して一気に何かを開発するような合宿ではなく、普段あまり体験しないであろうアクティビティが中心です。</p>
<h2 id="アクティビティの紹介">アクティビティの紹介</h2>
<h3 id="そば打ち体験">そば打ち体験</h3>
<p>今年の合宿のアクティビティは、そば打ち体験でした。
「チームでひとつの作業に臨むことで、普段の業務以外でのメンバー間の絆を深めてもらいたい」という理由から、そば打ちがアクティビティに選ばれました。</p>
<p>今回お世話になった<a href="https://taiken-jp.net/kannon/">観音亭</a>さんは、他ではなかなか経験のできない「石臼挽き」から体験できるおそば屋さんです。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200327/20200327183356.jpg" alt="20200327183356.jpg">
<p>石臼挽きを終えたら、挽いたそば粉をこねて伸ばしていきます。
職人さんの説明をよく聞きながら、普段食べている蕎麦の太さになるように薄く生地を伸ばしていきます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200327/20200327183437.jpg" alt="20200327183437.jpg">
<p>生地が薄くなりすぎると破れてしまうことも…!</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200327/20200327183459.jpg" alt="20200327183459.jpg">
<p>伸ばした生地を大きな包丁で切れば、見慣れた蕎麦の形になっていきます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200327/20200327183538.jpg" alt="20200327183538.jpg">
<p>自分たちで打ったそばは、少し太さにばらつきはあるものの、その場で茹でていただきとても美味しくいただくことができました。
他のメンバーもそば打ちを楽しむことができたようで、満足した様子でした。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200327/20200327183616.jpg" alt="20200327183616.jpg">
<h2 id="城ヶ崎海岸を散策">城ヶ崎海岸を散策</h2>
<p>そば打ち体験の後は、サスペンスドラマの撮影で使われることでも有名な城ヶ崎海岸に移動し、周辺を散策しました。
門脇吊り橋という大きな吊り橋を渡って少しスリルを味わいつつ、城ヶ崎海岸の絶景を楽しむことができました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200327/20200327183707.jpg" alt="20200327183707.jpg">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200327/20200327183729.jpg" alt="20200327183729.jpg">
<h2 id="バーベキュー">バーベキュー</h2>
<p>夕方には宿泊先に到着し、バーベキューを開始!
お酒を飲んだり、雑談したり…時にはプロダクトや組織について熱く語りあったりと楽しい時間を過ごすことができました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200327/20200327183804.jpg" alt="20200327183804.jpg">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200327/20200327183823.jpg" alt="20200327183823.jpg">
<h2 id="2020-年キックオフ">2020 年キックオフ!</h2>
<p>バーベキューの後は CTO 平山から 2020 年に向けてのキックオフです。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200327/20200327183849.jpg" alt="20200327183849.jpg">
<p>まず、2019 年の振り返りから。</p>
<p>昨年はメドレーにとってもプロダクトにとっても大きな変化のある 1 年でした。</p>
<p>会社としては創業 10 周年を迎え、東証マザーズへ上場を果たしました。
ジョブメドレーの大幅な<a href="https://job-medley.com/release/55/">デザインリニューアル</a>や CLINICS の<a href="https://www.medley.jp/release/clinics2019100.html">グッドデザイン・ベスト 100 の受賞</a>など様々なことがあった 1 年でした。</p>
<p>続いて 2020 年について。
詳細は記載できず残念なのですが、なにを目指すのか、それをどう実現していくのか…CTO からのメッセージを全員で確認する良い機会となりました。</p>
<p>普段の業務では目の前のタスクばかりに目が向き、つい視野が狭くなってしまうため、日常や業務を離れて少し引いた目でプロダクトや開発組織のこれからについて考える時間も必要なのだと感じることができました。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回はメドレーのプロダクト開発合宿の様子をお届けしました。</p>
<p>私自身、今回初めての合宿参加でしたが、メンバーと同じ時間を過ごすことで、普段見ることのできない一面を知ることができました。
また、これからの 1 年について考える時間ともなり、2020 年、より良い 1 年にしていきたいです。</p>
<p>最後に、今回の合宿の企画運営をしてくださった前田さん・新居さん、本当にありがとうございました!</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20200327/20200327183919.jpg" alt="20200327183919.jpg">medley
- 2019 年度新卒エンジニア研修についてhttps://developer.medley.jp/entry/2019/12/16/165947https://developer.medley.jp/entry/2019/12/16/165947こんにちは。エンジニアの平木です。
メドレーでは今年度より新卒採用活動を本格化しており、今年はエンジニア 4 名が新卒社員として入社しました。
現在、新卒メンバーは 6 ヶ月の開発研修が無事終了し、各部署で業務に勤しんでいますが、このエント...Mon, 16 Dec 2019 07:59:47 GMT<p>こんにちは。エンジニアの平木です。</p>
<p>メドレーでは今年度より新卒採用活動を本格化しており、今年はエンジニア 4 名が新卒社員として入社しました。</p>
<p>現在、新卒メンバーは 6 ヶ月の開発研修が無事終了し、各部署で業務に勤しんでいますが、このエントリでは、**初めての新卒研修をどのような視点で計画・実行していったか?**を書いていきたいと思います。</p>
<figure class="figure-image figure-image-fotolife" title="2019 年度新卒入社メンバー"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20191216/20191216115245.jpg" alt="20191216115245.jpg"><figcaption>2019 年度新卒入社メンバー</figcaption></figure>
<h1 id="新卒研修を決めるまでやったこと">新卒研修を決めるまでやったこと</h1>
<h2 id="研修の大枠決定">研修の大枠決定</h2>
<p>カリキュラムの大枠を決めるため、まず CTO の平山が「こんなことをやろう」という大枠の方向性を出し、施策レベルに筆者が落としこんでいきました。</p>
<p>決めたことは、以下3点です。</p>
<ul>
<li>入社後の導入部分
<ul>
<li>社会人としてのビジネスマナーや考え方の基礎を学ぶ</li>
<li>プロダクト開発をする上で必要になる基礎を学ぶ</li>
<li>会社の事業内容を理解する</li>
</ul>
</li>
<li>OJT をメインとした各チームへの仮配属</li>
<li>新卒社員へのフォローアップ
<ul>
<li>メンターや部長との定期報告会</li>
<li>半年後に役員陣への成果報告会</li>
</ul>
</li>
</ul>
<p>エンジニアとして価値を出していくため必要な**「社会の課題を解決するために、日々自身の腕を磨き、純粋に取り組む、ただそれだけ」**という、メドレーが求めるエンジニアとしての姿勢をきちんと体得してもらうことが目標です。</p>
<p>こちらについては平山のブログに詳しく書かれているので、ぜひご参照ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://toppa.medley.jp/05.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">toppa.medley.jp</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=toppa.medley.jp" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">toppa.medley.jp</span>
</div>
</div>
</a>
</div>
<h2 id="情報収集">情報収集</h2>
<h3 id="社内エンジニアへのヒアリング">社内エンジニアへのヒアリング</h3>
<p>前職で新卒研修を受けたことがある社内の 20 代~30 代のエンジニア 4 人を対象に、研修内容についてヒアリングしました。</p>
<p>ヒアリングしたポイントは</p>
<ul>
<li>研修の期間</li>
<li>研修全体の流れ</li>
<li>各研修メニューの内容</li>
<li>OJT はどういった形で受けたか</li>
<li>研修を受けての感想</li>
</ul>
<p>など。各社・各年代で個性が出るなという印象を持ちました。</p>
<p>やはり、エンジニアということで座学よりも手を動かすハンズオン形式の方が印象に残ることや、印象的な研修メニューは時間が経っても覚えているという傾向が分かりました。</p>
<h3 id="ネットでの情報収集">ネットでの情報収集</h3>
<p>ご存知の通り、近年では新卒研修のブログや、学習プログラムに関しては多くの企業様が書かれています。</p>
<p>しかし、内容としては**「アプリ開発をこのような形式で行ないました」という情報が多く、研修全体や社内情勢も踏まえた背景などが書かれているものは意外に少ないな**という印象を持ちました。</p>
<p>そんな中、各社の新卒研修エントリが Gist にまとめられているのを発見しました。
こちらは大変参考になりました。作者の方にはこの場をお借りして感謝させていただきます。</p>
<div class="remark-link-card-plus__container">
<a href="https://gist.github.com/gcchaan/02f4746a323acac4095c30e0783a3912" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">研修資料まとめ.md</div>
<div class="remark-link-card-plus__description">GitHub Gist: instantly share code, notes, and snippets.</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://github.githubassets.com/favicons/favicon.svg" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">gist.github.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://github.githubassets.com/assets/gist-og-image-54fd7dc0713e.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="研修メニュー決定">研修メニュー決定</h1>
<h2 id="研修の詳細メニュー">研修の詳細メニュー</h2>
<p>ここまでの過程を踏まえ研修メニューを筆者が考え、CTO や開発部部長の田中・副部長の稲本と刷り合わせをしていきました。</p>
<p>結果として、以下のメニュー構成でいくことになりました。</p>
<ul>
<li>オリエンテーション
<ul>
<li>**[座学]**社会人研修や会社全般の知識の習得を目的にする</li>
</ul>
</li>
<li>開発基礎研修
<ul>
<li>**[座学]**Web アプリケーション開発の基本や、エンジニアとしての心得、会社の展開するサービスの基本知識を習得することを目的にする</li>
</ul>
</li>
<li>開発実践研修
<ul>
<li>**[OJT]**社内向けに Web アプリケーションを開発し、チーム開発を実践することを目的にする</li>
</ul>
</li>
<li>開発部 OJT
<ul>
<li>**[OJT]**稼動しているサービスの開発を通して、業務としての開発を実践することを目的にする</li>
</ul>
</li>
<li>事業部 OJT
<ul>
<li>**[OJT]**ビジネスサイドの業務を体験し、開発以外の会社の業務の全体像を理解することを目的にする</li>
</ul>
</li>
</ul>
<p>特に事業部 OJT は新卒メンバーには必ず理解してもらいたい、と決まった研修項目です。</p>
<p>メドレーではプロダクトファーストで開発しますが、プロダクトの機能や要望は実際のビジネスの流れと密接に関わっています。</p>
<p>新卒メンバーもこういった流れを把握出来ていないと、要点を押さえた開発ができません。<strong>実際にどのような流れで顧客と関わっていき、自分達が開発するプロダクトにどう影響していくかを体験</strong>してもらうために、この事業部 OJT をメニューに組み込みました。</p>
<h1 id="研修期間">研修期間</h1>
<p>研修の期間についても様々な意見が出ましたが、最終的には配属後の業務にスムーズに入ってもらえるように、基礎を重視して長めに研修時間を取ったほうがよいと決め、以下の通り全 6 ヶ月という研修期間を定めました。</p>
<ul>
<li>オリエンテーション・開発基礎研修・開発実践研修
<ul>
<li>2 ヶ月</li>
<li>期間中、午前:開発基礎、午後:開発実践</li>
</ul>
</li>
<li>開発部 OJT ・事業部 OJT
<ul>
<li>4 ヶ月</li>
<li>事業部 OJT は期間中に 2 週間 x 2 つの事業部で実施</li>
</ul>
</li>
</ul>
<h1 id="メンター">メンター</h1>
<p>研修全体の統括は筆者になりますが、新卒メンバーがいつでも相談できるように 1 人につき 1 人のメンターを付けています。社会人経験が 3 年以上ある社員をメンターとしました。</p>
<p>最初の 2 週は毎日 1on1 を 15 分程度行ない、以降は毎週金曜日に 1 時間の 1on1 を実施。
また、メンター同士の交流として、2 週に 1 度メンターの共有会を開き、相談や近況報告を行いました。</p>
<p>メンター制自体も初めての試みだったためメンター同士の対応内容や新卒全員の様子が共有される場作りは、実施してよかったです。</p>
<h1 id="研修メニュー内容">研修メニュー内容</h1>
<p>各研修内容のご紹介です。</p>
<h2 id="オリエンテーション">オリエンテーション</h2>
<p>オリエンテーションは</p>
<ul>
<li>外部ビジネス研修</li>
<li>コンプライアンス研修</li>
<li>セキュリティ研修</li>
<li>開発環境の整備</li>
<li>開発ツールの解説</li>
</ul>
<p>がメインです。</p>
<p>Mac を渡されて、「勝手に開発環境作ってね」というのは新卒エンジニアにはハードルが高いので、筆者が付きっきりで時間をかけてフォローしました。</p>
<p><strong>オリエンテーションは約 1 週間程度ですが、ここで「メドレー社員の一人になった」という実感を持ってもらう</strong>ことができました。</p>
<h2 id="開発基礎研修">開発基礎研修</h2>
<p>大きく 2 部構成です。最初の部では、メドレーでエンジニアに期待していることや、既存サービスなどのシステム構成・ビジネスの流れなどを座学で実施しました。</p>
<p>もう一つの部では書籍の輪読をしました。この輪読は、Web サービスの基本の仕組みと、Web サーバ自体を支える OS である Linux の基本を覚えてもらいたいという趣旨で開催しました。</p>
<h3 id="メドレーについて">メドレーについて</h3>
<p>こちらの部では CTO 平山から「メドレーのエンジニアとして求めること」と題して、求められるエンジニアリングとは何なのか?自分達のキャリアパスなどをディスカッションしました。</p>
<p>その後は、<strong>ジョブメドレー</strong>・<strong>CLINICS</strong>・<strong>介護のほんね</strong> などメドレーが提供しているプロダクトについて、システムの概要やビジネスモデルとシステムの対応について講義。</p>
<p>メドレーでのエンジニアは各々専門性がある技術領域を持ちつつ、その領域に限定せずに広い分野で開発をしていますし、プロダクトを第一に考え開発しますが、そのために技術を武器としていくバランス感覚が必要になります。</p>
<p>この段階でまずこうした<strong>メドレーでエンジニアとして働くとはどういうことなのか</strong>という知識や考え方を新卒メンバーに勉強してもらいました。
エンジニアとは何か
エンジニアの価値は何か
プロエンジニアとは何か
エンジニアではない職種との違いは何か
といった基本的な話から、
2030 年のみんなはどうなっているのか、どうなっていたいのか
先輩エンジニアはどのようなことに悩み、どのようにアプローチをして、力をつけてきたのか、それを超えるためのヒントはどこにあるか
メドレーのエンジニアとして求めたいことは何か
といった踏み込んだ話も含め、個人ワークやグループワークも交えながら伝えていきました。</p>
<p>導入があることで、これからのカリキュラムを実施していく上での心構えができる期間だったのではないかと思います。</p>
<h3 id="輪読会">輪読会</h3>
<p>座学でプログラミングのことを勉強させるのではなく、主体性を持ち、基礎知識を習得してほしいと考えていたので、<strong>プランを作っている初期段階から輪読会をすることを決めていました。</strong>
参考にさせていただいたのは<a href="https://ch.nicovideo.jp/dwango-engineer/blomaga/ar1054007">ドワンゴさんの 2016 年の研修の記事</a>です。</p>
<p>輪読で使用する本の選定は悩んだのですが、弊社で使っている Ruby や Ruby on Rails のことや、JavaScript などの技術より、基本となる Web 自体についての知識や、Web サーバで使う Linux の事などを習得してもらいたいということを考えて以下の 2 冊で輪読を行いました。</p>
<ul>
<li><a href="https://gihyo.jp/magazine/wdpress/plus/978-4-7741-4204-3">Web を支える技術 ── HTTP,URI,HTML,そして REST(WEB+DB PRESS plus シリーズ)</a></li>
<li><a href="https://www.lambdanote.com/products/go">Go ならわかるシステムプログラミング</a></li>
</ul>
<p>「Web を支える技術」の輪読の形式は毎回決まった章を筆者が講義しつつ、時折質問などをメンバーに投げかけたりしながら理解を深めました。</p>
<p>議事録も新卒メンバー全員で順番を決めてもらうという、自主性に基づくスタイルにしました。<strong>まず輪読の雰囲気になれてもらうこと、それから議事録を取りつつドキュメンテーションの練習をすること</strong>を狙いとしています。</p>
<p>最初はぎこちなかった部分もありましたが、回を重ねるにつれて、自分の経験や知識も含めたディスカッションにも発展するようになり、全員自主性を持ちながら実施できました。</p>
<p>対して「Go ならわかるシステムプログラミング」については<strong>新卒メンバーに章を割り当てて、各自講師をしてもらいました。</strong></p>
<p>元々レポートを 3 回発表することを予定していたので、本の内容を読んできちんと理解した上で、<strong>他人へ伝える力を養う</strong>ことをサブ目標にしています。</p>
<p>さらなる自主性も必要になる上、本の性質上ほとんどのメンバーが触っていない Go 言語を使ってプログラムを書いてシステムのことを理解しないといけない為、プログラムの本質的な部分に触れることもできたのではないかと思います。</p>
<h2 id="開発実践">開発実践</h2>
<p>開発実践は上記の開発基礎と並行して午後から実施しました。
1 日の午前が開発基礎、午後が開発実践というスケジュールです。</p>
<p>実践は新卒メンバーで社内ツールを実際の業務形式で開発してもらいました。</p>
<p>題材は**「メドレーの使い方に合ったビルの予約システムを作る」**というものです(以前 SmartHR さんが同じビルだったので少しアプローチが違いますが<a href="https://tech.smarthr.jp/entry/2019/06/28/134701">ブログ</a>に書いてました)。</p>
<p>メドレーでの業務開発と同じ形式で</p>
<ul>
<li>ユーザーからの要望をヒアリング</li>
<li>要件定義</li>
<li>開発計画の策定</li>
<li>システム設計</li>
<li>チームで分担してながらの開発</li>
<li>ベータ版としてリリースしたものをブラッシュアップ</li>
<li>実稼働するための環境整備をしながらリリースする</li>
</ul>
<p>という一連のプロセスで開発しました。</p>
<p>「開発自体どうやって進めるか」「仕様をどうするか」など基本的な所から、全行程を新卒メンバー主体で考えて実行してもらい、メンター陣は所々 MTG や PR をレビューしながら、アドバイスをするという形式で行いました。</p>
<p>新卒メンバー全員が同じスキルセットを持っているわけでもないですし、得意・不得意も違う為、まず最初に彼ら自身にリーダーを決めてもらい、リーダーがオーナーシップを持って開発を進めてもらいました。</p>
<p>チームでの開発というのはほとんど全員が初めてだったため、かなり困惑しているところもありましたが、研修の過程で理解した互いのスペシャリティを活かして、役割分担をしつつ、決まった納期にきちんとリリースすることができました。メンターとしてそばで見ていましたが、新卒メンバー全員の成長を実感しましたし、純粋に凄いと感じました。</p>
<p>途中で納期に間に合わせるための要件の取捨選択をしなければならなかったり、自分達が考えた仕様が必ずしもベストというわけではなく、さらに色々と考える要素が必要だったりという<strong>開発のリアルを経験できた</strong>のは次の OJT に活きたと思います。</p>
<p>余談ですが、こちらの元々のシステムを調査する上で「Web を支える技術」の知識が早速役に立つという場面が多く、やはり<strong>基本は大事だな、と思った</strong>次第です。</p>
<h2 id="開発部-ojt">開発部 OJT</h2>
<p>基礎研修が終わってから、既存サービスにジョインしての OJT 実施となりました。</p>
<p>ここまでの研修で、ある程度の知識や業務の進め方などは習得していたので、何をすればよいか分からないということはありませんでした。</p>
<p>メンバーによってはここで時間を取って Ruby on Rails について習得をしてもらいました。
大きい失敗なども特にはなく粛々と進められたのは良かったのではないかと思います。</p>
<h2 id="事業部-ojt">事業部 OJT</h2>
<p>開発部 OJT の途中で 2 週間ずつ時間を取って、人材プラットフォーム事業と医療メディア事業での事業部 OJT を行いました。</p>
<p>**どこで顧客とコンタクトし、お金をいただき、どのような形で自分達が作ったシステムに関わっていくのか?**というのをこの時期に体験してもらうことが目的です。</p>
<p>研修はビジネスの流れに沿って行ないました。研修メニューは、事業部の主要メンバーにコンセプトの共有をし、メニューを考えて実施してもらいました。(ありがとうございます!)</p>
<p>実際に電話でアポイントを取ったり、顧客のサポートを電話やビデオチャットでしたり、営業をどうやっているのかを体験したり…などを 2 週間かけて新卒メンバーに体験してもらいました。</p>
<p>この後に実際**「このシステムの機能はこういうときに使うのかと実感した」とか「やっていてこのような仕様のほうが良いと思った」などの感想**をそれぞれのメンバーが持っていて、やはり実施して良かったなと強く感じました。</p>
<h2 id="レポート発表">レポート発表</h2>
<p>それぞれの研修の終わりにはレポート発表の時間を設け、一人一人振り返りを書いてもらいます。その為に週報の提出も義務付けました。また研修期間の終了時には弊社役員陣にプレゼンする最終レポート発表の時間を取りました。</p>
<p>ひたすら研修内容をこなしていくという形になりがちなので、<strong>振り返りの期間を設けて記録を付けていき、自分の成長や反省などを可視化したい</strong>という目的です。</p>
<p>また、メドレーでは<strong>ドキュメントドリブンで開発に限らず全社員が Confluence でドキュメントを書いていく文化</strong>なので、文書を誰にでも分かるように論理的に書く技術が求められます。こういったレポートの記述やそのフィードバックから文書技術を高めてほしいという目的もありました。</p>
<p>レポートに関してはメンター陣やマネージャ陣に発表してもらうという形にしていたので、発表自体やレポートのまとめ方などに都度フィードバックをするようにしました。</p>
<p><strong>最初はレポートが読みづらいなどもありましたが、回を重ねるごとに段々洗練されたレポートや発表になってきたのが印象的</strong>です。</p>
<p>最終レポートは代表を始めとした経営陣に向けてレポートを発表してもらいました。今まで自分達がやってきた全ての業務を、前提の知識がない聴衆に向けて発表するということで、各自が趣向を凝らしての発表になり、経営陣からも高評価をもらっていました。</p>
<h1 id="まとめ">まとめ</h1>
<p>メドレーで初の新卒研修は以上のような形で終わることができました。</p>
<p>かなりのハードスケジュールだったとか、開発部 OJT をもう少し現場と色々と話しあったほうが効果的だったかもなど反省点もありますが、現在新卒メンバーが 10 月から実際の業務で活躍しているところを見ているので、ある程度の成功を収めることができたのではないかと思います。</p>
<p>来年度のメニューはまた違ったものになる可能性がありますが、今年の研修でも重視した**「メドレーでエンジニアとして働くことに対する意義を感じながら業務をしてもらう」**という部分はズラさずに実施していければと思います。</p>
<p>長々とお付き合いいただきありがとうございました。</p>medley
- 社内勉強会 TechLunch で Badging API について発表しましたhttps://developer.medley.jp/entry/2019/08/27/174256https://developer.medley.jp/entry/2019/08/27/174256みなさん、こんにちは。開発本部のエンジニアの舘野です。先日、社内勉強会「TechLunch」で Badging API について発表したので、その内容を紹介させていただきます。
Badging API とは
Badging API とは、ネ...Tue, 27 Aug 2019 08:42:56 GMT<p>みなさん、こんにちは。開発本部のエンジニアの舘野です。先日、社内勉強会「TechLunch」で Badging API について発表したので、その内容を紹介させていただきます。</p>
<h1 id="badging-api-とは">Badging API とは</h1>
<p>Badging API とは、ネイティブアプリのアプリアイコン上に表示されるバッジと同様に、ウェブアプリのアイコン上にバッジを表示することができる Web API です。</p>
<p>ネイティブアプリで可能なこと全てをウェブアプリでも可能にすることを目指す、<a href="https://www.chromium.org/teams/web-capabilities-fugu">Fugu</a> というプロジェクトで実現に向けて動いている API の 1 つで、Chrome 73 から <a href="https://github.com/GoogleChrome/OriginTrials">Origin Trials</a> として利用可能になっています。Origin Trials とは、試験的に特定の開発者に限定して API を利用できるようにする仕組みのことで、正式リリース前に API に対する有用なフィードバックを受け取ることができるものです。</p>
<p>この API の最新の概要や仕様については、 <a href="https://wicg.io/">WICG</a> が Github に <a href="https://github.com/WICG/badging/">Badging API のリポジトリ</a>を用意しているので、そこで確認することができます。</p>
<p>WICG(The Web Incubator Community Group)は、先進的なウェブ技術について検討するコミュニティグループで、W3C のグループの 1 つです。</p>
<p>提案されている API のインターフェースは、現時点(2019/08/14)では以下のようになっています。</p>
<div class="remark-link-card-plus__container">
<a href="https://github.com/WICG/badging/blob/master/explainer.md#the-api" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">badging/explainer.md at main · w3c/badging</div>
<div class="remark-link-card-plus__description">Badging API. Contribute to w3c/badging development by creating an account on GitHub.</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://github.githubassets.com/favicons/favicon.svg" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">github.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://opengraph.githubassets.com/9bc0afea9f7b17ef3e2fd3b52d86ae17a94f73d6b6e34e6f65dd2a864a0a084e/w3c/badging" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<ul>
<li><code>Badge</code>を<code>window</code>オブジェクトのメンバとして持つ</li>
<li><code>Badge</code>には 2 つのメソッドが存在する
<ul>
<li><code>Badge.set()</code></li>
<li><code>Badge.clear()</code></li>
</ul>
</li>
<li><code>Badge.set(5)</code>のように<code>set()</code>に整数を渡してバッジ上に数字を表示する</li>
<li>単に<code>Badge.set()</code>で呼び出すとフラグとしてバッジを表示する</li>
<li><code>Badge.set(5, { scope: ‘/baz’ })</code>のようにオプションを渡して特定のスコープ配下で表示されるように指定できる
<ul>
<li>オプションでスコープが指定されてない場合、スコープは<code>/</code>になる</li>
</ul>
</li>
</ul>
<h1 id="ローカル環境で試す">ローカル環境で試す</h1>
<p>実際にどのような形でバッジを表示できるかを確認するために、今回はローカル環境(macOS 10.14、Chrome76)で試してみました。</p>
<p>API 自体は非常にシンプルなので、PWA のアプリを用意してインストールするだけで簡単に試すことができます。</p>
<p>インストールされていないウェブアプリでも、タブの favicon 上やブックマークアイコン上にバッジを表示することも議論されているようですが、今のところインストール済みのウェブアプリでしかバッジは表示されません。</p>
<p>最初に API を利用可能な状態にする必要がありますが、上述の通り API 自体が現在 Origin Trials の段階なので、Origin Trials の利用申請を行うかローカル環境であれば chrome://flags で実験的な機能を有効にする(#enable-experimental-web-platform-features)かのいずれかを行う必要があります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190827/20190827173844.png" alt="20190827173844.png">
<p>今回はローカル環境で試すだけなので、chrome://flags から enable-experimental-web-platform-features を有効にしておきます。</p>
<p>実験的な機能を利用可能にしたことで Badging API 自体は利用可能になりますが、<code>window.Badge</code>として利用可能になっているのではなく、Origin Trials の段階では<code>Badge</code>ではなく<code>ExperimentalBadge</code>として提供されています。</p>
<p>次に、サンプルのプロジェクトを用意して webpack-dev-server でローカルサーバを用意します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> yarn</span><span style="color:#CE9178"> init</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> yarn</span><span style="color:#CE9178"> add</span><span style="color:#569CD6"> --dev</span><span style="color:#CE9178"> webpack</span><span style="color:#CE9178"> webpack-cli</span><span style="color:#CE9178"> webpack-dev-server</span><span style="color:#CE9178"> html-webpack-plugin</span><span style="color:#CE9178"> copy-webpack-plugin</span></span>
<span class="line"></span></code></pre>
<p>webpack-dev-server でローカルサーバが見れる状態になるように webpack.config.js に設定を記述します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> path</span><span style="color:#D4D4D4"> = </span><span style="color:#DCDCAA">require</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"path"</span><span style="color:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> HtmlWwebpackPlugin</span><span style="color:#D4D4D4"> = </span><span style="color:#DCDCAA">require</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"html-webpack-plugin"</span><span style="color:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#4EC9B0">module</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">exports</span><span style="color:#D4D4D4"> = {</span></span>
<span class="line"><span style="color:#9CDCFE"> mode:</span><span style="color:#CE9178"> "development"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> devServer:</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> https:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> entry:</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> app:</span><span style="color:#D4D4D4"> [</span><span style="color:#CE9178">"./src/js/app.js"</span><span style="color:#D4D4D4">],</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> output:</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> path:</span><span style="color:#9CDCFE"> path</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">resolve</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">__dirname</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"./dist"</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> plugins:</span><span style="color:#D4D4D4"> [</span></span>
<span class="line"><span style="color:#569CD6"> new</span><span style="color:#DCDCAA"> HtmlWwebpackPlugin</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> template:</span><span style="color:#CE9178"> "src/index.html"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> }),</span></span>
<span class="line"><span style="color:#D4D4D4"> ],</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span></code></pre>
<p>次に manifest.json を用意します。manifest.json は、そのアプリがどういったものか、また、インストールした時にどのように振る舞うかをブラウザに伝えるための設定ファイルになります。</p>
<p><a href="https://app-manifest.firebaseapp.com/">https://app-manifest.firebaseapp.com/</a> のようなサービスで manifest.json とアイコンを各種サイズ自動生成できるので、今回はこのサービスで生成します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#D4D4D4">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "name"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"badging-api-playground"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "short_name"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"badge"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "theme_color"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"#5B5CFD"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "background_color"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"#5B5CFD"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "display"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"standalone"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "orientation"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"portrait"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "prefer_related_applications"</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "Scope"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"/"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "start_url"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"/"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "icons"</span><span style="color:#D4D4D4">: [</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "src"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"images/icons/icon-72x72.png"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "sizes"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"72x72"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"image/png"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "src"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"images/icons/icon-96x96.png"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "sizes"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"96x96"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"image/png"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "src"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"images/icons/icon-128x128.png"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "sizes"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"128x128"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"image/png"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "src"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"images/icons/icon-144x144.png"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "sizes"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"144x144"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"image/png"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F44747"> ….</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ],</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> "splash_pages"</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">null</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span></code></pre>
<p>webpack.config.js の方に manifest.json をローカルサーバで配信されるように設定を追加しておきます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="diff"><code><span class="line"><span style="color:#D4D4D4">const path = require('path')</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">const HtmlWwebpackPlugin = require('html-webpack-plugin')</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> const CopyPlugin = require('copy-webpack-plugin')</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">module.exports = {</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ….</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> plugins: [</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ….</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> new CopyPlugin([</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> {</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> from: 'src/manifest.json',</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> to: '',</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> {</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> from: 'src/images/icons',</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> to: 'images/icons/'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> ]),</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ],</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span></code></pre>
<p>ここまでで Chrome の Application タブから manifest.json が認識されいてることが確認できますが、インストール可能な状態にはなっていません。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190826/20190826185511.png" alt="20190826185511.png">
<p>アプリをインストール可能な状態にするには <a href="https://developers.google.com/web/fundamentals/app-install-banners/#criteria">いくつかの基準</a> があり、service worker が必要になります。今回は<a href="https://developers.google.com/web/tools/workbox/">Workbox</a> の webpack プラグインで対応します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">yarn</span><span style="color:#CE9178"> add</span><span style="color:#569CD6"> --dev</span><span style="color:#CE9178"> workbox-webpack-plugin</span></span>
<span class="line"></span></code></pre>
<p>workbox には GenerateSW と injectManifest の 2 つのモードがあり、今回はどちらでも問題ないかと思いますが injectManifest モードを利用します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="diff"><code><span class="line"><span style="color:#D4D4D4">const path = require('path')</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">const HtmlWwebpackPlugin = require('html-webpack-plugin')</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">const CopyPlugin = require('copy-webpack-plugin')</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> const { InjectManifest } = require('workbox-webpack-plugin')</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">module.exports = {</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ….</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> plugins: [</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ….</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> new InjectManifest({</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> swSrc: path.resolve(__dirname, 'src/sw.js'),</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B5CEA8"><span style="user-select: none;">+</span> }),</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ],</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span></code></pre>
<p>app.js の方で service worker の登録がされるように記述しておきます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#C586C0">if</span><span style="color:#D4D4D4"> (</span><span style="color:#CE9178">"serviceWorker"</span><span style="color:#569CD6"> in</span><span style="color:#9CDCFE"> navigator</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#9CDCFE"> window</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">addEventListener</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"load"</span><span style="color:#D4D4D4">, () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> navigator</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">serviceWorker</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">register</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"./sw.js"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">then</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">res</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> console</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">log</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">res</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4"> })</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">catch</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">err</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> console</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">error</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">err</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>service worker の対応が済むとインストール可能なアプリの基準を満たすので、Chrome76 ではアドレスバーにオムニボックスが表示され、そこからインストールが可能になっています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190826/20190826185538.png" alt="20190826185538.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190826/20190826185554.png" alt="20190826185554.png">
<p>インストールすると Launchpad にアプリアイコンが表示されたので、実際に Badge API を試してみます。</p>
<p><code>window.ExperimentalBadge.set()</code>を呼び出すと、フラグとしてバッジがつきます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190826/20190826185622.png" alt="20190826185622.png">
<p><code>window.ExperimentalBadge.set(1)</code>のように引数に数値を入れて呼び出すと、バッジは数字が入った状態で表示されます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190826/20190826185641.png" alt="20190826185641.png">
<p><code>window.ExperimentalBadge.clear()</code>でバッジがクリアされ、元のアプリアイコンだけの状態に戻ります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190826/20190826185653.png" alt="20190826185653.png">
<p>非常に簡単ではありますが、このようにしてウェブアプリのアイコンにバッジをつけられることが確認できました。</p>
<p>なお、このサンプルプロジェクトは <a href="https://github.com/makotot/badging-api-playground">https://github.com/makotot/badging-api-playground</a> にあげてあります。</p>
<h1 id="まとめ">まとめ</h1>
<p>近い将来正式リリースされる可能性が高い API を試すことで、今後ウェブアプリでどのようなことが実現可能になっていくかの一端を垣間見ることができました。</p>
<p>API 自体が Origin Trial の段階で、ブラウザのタブの favicon 上やブックマークアイコン上に表示したいケースであったり、バッジはどこまでの範囲で適用するべきかのスコープの問題であったり、最終的にどのような形に仕様が整理されるかまだ明確ではない部分もあります。</p>
<p>最終的にどのように課題が解決されていくか注目したいと思います。</p>medley
- Google Cloud Next '19 in Tokyo にて、App Maker の活用事例を紹介しましたhttps://developer.medley.jp/entry/2019/08/26/142457https://developer.medley.jp/entry/2019/08/26/142457はじめに
こんにちは、コーポレートエンジニアの若林です。
普段はいわゆる社内 SE 的な領域を担当していますが、その名の通りエンジニアの視点も持ったコーポレート機能という立ち位置で、社員の働く基盤や事業の発展を支えています。
過去のコーポレ...Mon, 26 Aug 2019 05:24:57 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは、コーポレートエンジニアの若林です。</p>
<p>普段はいわゆる社内 SE 的な領域を担当していますが、その名の通り<strong>エンジニアの視点も持ったコーポレート機能</strong>という立ち位置で、社員の働く基盤や事業の発展を支えています。
過去のコーポレートエンジニアの取り組みや、マインドについては以下が参考になるかと思います。</p>
<ul>
<li><a href="https://www.wantedly.com/companies/medley/post_articles/102877">コーポレート IT 担当として会社の成長を支える兼松さんに「聞いてみた」</a></li>
<li><a href="/entry/2019/02/01/172457">全社で本気になってリーンに ISMS の仕組みをつくった話</a></li>
</ul>
<p>今回もその取り組みの一環で、先日開催された <a href="https://cloud.withgoogle.com/next/tokyo">Google Cloud Next ‘19 in Tokyo</a> に、カスタマースピーカーという形で弊社執行役員の兼松と共に登壇させて頂きました。</p>
<p>今回は、登壇させて頂いたセッション <a href="https://cloud.withgoogle.com/next/tokyo/sessions?session=D2-4-S10">Google Apps Script / AppMaker 最新アップデートと活用のヒント</a> について、セッションでお伝えした内容を含めたレポートを書きたいと思います。</p>
<h1 id="google-cloud-next-19-in-tokyo-について">Google Cloud Next ‘19 in Tokyo について</h1>
<p>Google Cloud Next は、年一度 Google 主催で催される Google テクノロジーに関するカンファレンスです(今年は、サンフランシスコ、東京、ロンドンの世界 3 会場開催)</p>
<ul>
<li>日程;7/30(火) - 8/1(木)</li>
<li>会場:東京プリンスホテルおよびザ・プリンス パークタワー東京</li>
<li>イベントサイト:<a href="https://cloud.withgoogle.com/next/tokyo">Google Cloud Next ‘19 in Tokyo</a></li>
</ul>
<p>メドレー社内でも、G Suite を中心に Google Cloud の活用事例が増えてきていることもあり、社内の新しいアプリケーションプラットフォームとして採用している App Maker の話でスピーカー応募してみようということになりました。</p>
<p>なお、登壇させて頂いたセッションですが、一般申込受付が始まってすぐに満席になってしまい、私が SNS でアナウンスしても既に満席で申し込めないような状態になっておりました・・・</p>
<h1 id="会場の雰囲気">会場の雰囲気</h1>
<figure class="figure-image figure-image-fotolife" title="東京タワーとの一枚"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190821/20190821171227.jpg" alt="20190821171227.jpg"><figcaption>東京タワーとの一枚</figcaption></figure>
<figure class="figure-image figure-image-fotolife" title="登壇の様子(背景と同色なのは偶然です)"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190821/20190821171259.jpg" alt="20190821171259.jpg"><figcaption>登壇の様子(背景と同色なのは偶然です)</figcaption></figure>
<h1 id="セッション内容について">セッション内容について</h1>
<h2 id="google-apps-script--appmaker-最新アップデートと活用のヒント"><a href="https://cloud.withgoogle.com/next/tokyo/sessions?session=D2-4-S10">Google Apps Script / AppMaker 最新アップデートと活用のヒント</a></h2>
<p>今回は、Google の深堀さん、嘉穂無線の太田さんとの合同セッションという形で登壇させて頂きました。</p>
<p>まず Google の深掘さんが、「G Suite を拡張開発するテクノロジーの全体概要と活用事例」というテーマで、Google Apps Script / App Maker を含む G Suite 周辺の開発プラットフォームについて、位置付けの整理とグローバルでの活用事例、今後のアップデートについてお話されました。次に、嘉穂無線の太田さんが、「Google Apps Script の活用事例」というテーマで、Google Apps Script を活用して開発した社内システムについて、デモを交えて紹介されました。</p>
<p>それを受けて、メドレーでは「Google App Maker 活用事例と開発 Tips」というテーマで、Google App Maker を活用して開発した社内システムを、デモを交えて紹介させて頂きました。</p>
<p>App Maker は GA されてからまだ一年強ということもあって、Apps Script と比較してもオンラインに蓄積されているナレッジが非常に少なく(あってもほぼ英語)、現時点において日本で活用できている企業は本当に限られていると思っています。</p>
<p>しかし実際に触っていく中でも非常に可能性を秘めたプラットフォームだと感じているので、日本でも活用する会社が増えてくれれば嬉しいという思いを込めて、今回プレゼンさせて頂きました。</p>
<p>ここからは、実際にメドレーのパートでお話した内容を、振り返りも兼ねて書かせて頂ければと思います。</p>
<h2 id="google-app-maker-とは"><a href="https://gsuite.google.co.jp/intl/ja/products/app-maker/">Google App Maker</a> とは</h2>
<p>一言で言うと…</p>
<p><strong>G Suite Business / Enterprise プランに含まれるローコードアプリケーション開発ツール</strong></p>
<p>です!</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190821/20190821171328.png" alt="20190821171328.png">
<p>主に以下のような特徴があります。</p>
<ul>
<li>バックエンドのデータストアとして、RDB(Cloud SQL)が利用される</li>
<li>開発言語は JavaScript をベースとしたスクリプト言語である Google Apps Script である</li>
<li>G Suite のサービス(ユーザーディレクトリ、メール、ドライブ等)と容易に連携が可能である</li>
<li>UI のカスタマイズの容易さと高い自由度がある</li>
</ul>
<h2 id="メドレーでの社内活用事例">メドレーでの社内活用事例</h2>
<p>メドレーでは、Google App Maker を活用して複数のアプリケーションを実装しています。</p>
<p>今回は、そのうちの一つである社内稟議システムについての概要を紹介させて頂きました。(当日はデモも実施しているので、デモの内容を見たい方は<a href="https://www.youtube.com/watch?v=eOBcrwade48">セッション動画</a>をご覧下さい)
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190822/20190822102114.png" alt="20190822102114.png"></p>
<h2 id="app-maker-を使ったアプリケーション開発の流れと開発-tips">App Maker を使ったアプリケーション開発の流れと開発 Tips</h2>
<p>App Maker はアプリケーション開発プラットフォームなので、開発の流れを以下の 3step に分割した上で、Tips を紹介させて頂きます。</p>
<ol>
<li>データモデルの定義</li>
<li>UI 作成</li>
<li>ビジネスロジック記述</li>
</ol>
<h3 id="1-データモデルの定義">1. データモデルの定義</h3>
<p>はじめに、GUI ベースで RDB のメタデータを定義していきます。</p>
<h4 id="モデルテーブルの主な設定項目">モデル(テーブル)の主な設定項目</h4>
<ul>
<li>FIELDS:モデルのフィールド情報を定義</li>
<li>DATASOURCES:データベースでいうところのビューに近い概念の設定</li>
<li>RELATIONS:モデル間のリレーションを定義</li>
<li>EVENTS:クライアントのデータベース操作に対するイベントスクリプトを定義</li>
<li>SECURITY:モデルに対する権限を制御</li>
</ul>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190821/20190821171355.png" alt="20190821171355.png">
<h4 id="データモデルの定義における開発-tips">データモデルの定義における開発 Tips</h4>
<ul>
<li><strong>EVENTS の挙動</strong>:MODEL の設定項目なので一見データベーストリガーと思いがちですが、<a href="https://developers.google.com/appmaker/models/events?hl=ja">このイベントは、サーバーサイドスクリプトや外部からのデータベースレコード操作では、トリガーされません。</a>(クライアントサイドからの操作限定)サーバーサイドスクリプトや外部からの操作には、別途関数呼び出しを組み込む等の配慮が必要になります。</li>
<li><strong>データベースへの更新反映タイミング</strong>:既定の設定ではクライアントでデータソースの情報が変更された場合に、サーバーに即時反映される動き(Auto Save Mode)になります。この動きを止めたければ <a href="https://developers.google.com/appmaker/models/datasources?hl=ja#manual_save_mode">Manual Save Mode</a> に切り替えることも可能ですが、コード記述量が大きく増えることになります。Auto Save Mode のままでも UI/UX の工夫で回避できたりするので、まずそこを検討するのが重要かなと思います。</li>
<li><strong>細かな権限制御</strong>:GUI からできる権限制御設定は限られています。アプリケーション内での動的な権限制御や、フィールドの値に基づく細かな権限制御が必要な場合は、<a href="https://developers.google.com/appmaker/models/datasources#query_builder">Query Builder</a> や <a href="https://developers.google.com/appmaker/models/datasources#query_script">Query Script</a> による読み込み制御が必要になりますが開発コストが結構増加するので、他の回避策がないか事前に検討することをお勧めします。</li>
</ul>
<h3 id="2-ui-作成">2. UI 作成</h3>
<p>次に、ウィジェット(画面を構成する部品)をドラッグ&ドロップで配置して画面を作成していきます。</p>
<p>一般的に必要となるボタンやドロップダウンに加えて、G Suite ならではの <a href="https://developers.google.com/appmaker/ui/input-widgets?hl=ja#header_13">User Picker</a> や <a href="https://developers.google.com/appmaker/ui/input-widgets?hl=ja#header_12">Drive Picker</a> 、更には<a href="https://developers.google.com/appmaker/ui/popups?hl=ja">リッチなポップアップ、ダイアログ等</a>も利用可能です。</p>
<p>スタイルに関しては、<a href="https://developers.google.com/appmaker/ui/styles?hl=ja#Variants">バリアント</a>を利用することで、統一感のあるスタイル適用が容易に可能になっています</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190821/20190821171425.png" alt="20190821171425.png">
<h4 id="ui-作成における開発-tips">UI 作成における開発 Tips</h4>
<ul>
<li><strong>組み込みのマテリアルデザインアイコン</strong>:ラベルやボタンの text を特定の文字列にすると、元々組み込まれているマテリアルデザインアイコンを利用することが可能です。(<a href="https://material.io/resources/icons/?style=baseline">文字列とアイコンの対応表</a>)一般的に利用されているようなアイコンはほとんど用意されているので、ガンガン活用していきましょう。</li>
</ul>
<h3 id="3-ビジネスロジック記述">3. ビジネスロジック記述</h3>
<p>最後に、2 種類のスクリプト(Client Script, Server Script)を利用して、ビジネスロジックを記述していきます。(アイテム生成とか、読み込みとかは基本的に記述不要)</p>
<p>Client Script から Server Script を呼び出したい場合は、 Google Apps Script 同様、以下のような形で実行が可能になっています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#9CDCFE">google</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">script</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">run</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">ServerSideFunction</span><span style="color:#D4D4D4">();</span></span></code></pre>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190821/20190821171501.png" alt="20190821171501.png">
<h4 id="ビジネスロジック記述における開発-tips">ビジネスロジック記述における開発 Tips</h4>
<ul>
<li><strong>外部公開できない</strong>:Google Apps Script で言うところの、doGet/doPost 関数のような Web API は、App Maker のスクリプト内に作ることができないようになっています。外部システムとの連携が必要な場合、Cloud Function 等を介して、Cloud SQL を直接操作する必要があります。(トランザクション処理等は複雑化します)</li>
<li><strong>ローカルの開発環境と上手く連携できない</strong>:Google Apps Script で利用可能な <a href="https://github.com/google/clasp">clasp</a> は、現時点で App Maker には対応していません。バージョン管理、デプロイ管理機能は開発コンソールに実装されていますが、コードレベルでの差分レビュー等を実施したい場合は、現時点では少しめんどくさいです。</li>
<li><strong>定期実行トリガーを GUI で設定できない</strong>:Google Apps Script では GUI から設定が可能な、「時間ベース」のトリガーが GUI で簡単に設定できません。設定が必要な場合は、以下のようなスクリプトベースで設定する必要があります。</li>
</ul>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#9CDCFE">ScriptApp</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">newTrigger</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"functionName"</span><span style="color:#D4D4D4">).</span><span style="color:#DCDCAA">timeBased</span><span style="color:#D4D4D4">().</span><span style="color:#DCDCAA">at</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">date</span><span style="color:#D4D4D4">);</span></span></code></pre>
<h3 id="app-maker-をこれから始める人向けの情報">App Maker をこれから始める人向けの情報</h3>
<p>なお、今回紹介させて頂いた内容は App Maker を全く触ったことがない人には少し難しい内容だと思っています。</p>
<p>知識がゼロからの方は、以下のナレッジを元に是非一度チュートリアルなどで簡単に触ってから、改めて読んで頂けると理解が深まるかと思います。</p>
<h4 id="アプリ作成の一連の流れを掴む">アプリ作成の一連の流れを掴む</h4>
<ul>
<li><a href="https://www.youtube.com/watch?v=0B8Pao5vU_0">How to Build Enterprise Workflows with App Maker (Cloud Next ‘18)</a></li>
<li><a href="https://www.youtube.com/watch?v=hfPrGNO6Frs">Improve Processes by integrating App Maker, Data Studio and GCP (Cloud Next ‘19)</a></li>
</ul>
<h4 id="実際に触ってみる">実際に触ってみる</h4>
<ul>
<li><a href="https://developers.google.com/appmaker/tutorials/">チュートリアル</a></li>
<li><a href="https://developers.google.com/appmaker/templates/">テンプレート</a></li>
</ul>
<h4 id="情報共有プラットフォームを活用する">情報共有プラットフォームを活用する</h4>
<ul>
<li><a href="https://stackoverflow.com/questions/tagged/google-app-maker">stack overflow [google-app-maker] タグ</a></li>
<li><a href="https://groups.google.com/forum/#!forum/appmaker-users">Google グループ[App Maker Users]</a></li>
</ul>
<h1 id="まとめ">まとめ</h1>
<p>今回紹介させて頂いた Google App Maker は、G Suite をディープに利用している企業であれば、Google Apps Script に匹敵する強力な社内アプリケーションプラットフォームになると感じています。</p>
<p>App Maker のようなローコード/ノーコード開発プラットフォームは既に市場にいくつか出ており、Google App Maker はその中でも比較的コーディング・技術力が求められるプラットフォームだと思います。しかし、その分既存社内システムと親和性の高い高度な自動化が実現しやすいプラットフォームでもあると思うので、臆せずにチャレンジする価値は十分にあると思います。</p>
<p>なお、セッション中に紹介させて頂いた稟議アプリケーションは、App Maker に対する知識が 0 の状態から 1~2 ヶ月程で Slack との連携も含めて 1 人で実装できました。(RDB や GAS に対する知識は、ある程度習得済みの状態が前提にはなっています)</p>
<p>GA 直後ということもあってインターネット上のナレッジが少ない中、探り探りに得られた活用のヒントを今回の場でお話しさせて頂きましたが、引き続き App Maker というプラットフォームを上手く利用していくには、ユーザーコミュニティの盛り上がりが欠かせないと思っていますので、引き続き情報発信等を通じて盛り上げていければと考えております。</p>
<p>最後になりますが、メドレーではこのような市場に出て間もないテクノロジーでもチャレンジしていける風土があります。こういったチャレンジを求めている方は、ぜひご応募下さい。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/team/creator-story.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- Developers Summit 2019 Summer で弊社田中がエンジニアのキャリアについてお話させていただきましたhttps://developer.medley.jp/entry/2019/07/19/194356https://developer.medley.jp/entry/2019/07/19/194356みなさん、こんにちは。開発部エンジニア平木です。
少しレポートまで間が空いてしまいましたが、去る 7/2(月)にソラシティカンファレンスセンターで開催されたDevelopers Summit 2019 Summerにメドレーが協賛させていた...Fri, 19 Jul 2019 10:43:56 GMT<p>みなさん、こんにちは。開発部エンジニア平木です。</p>
<p>少しレポートまで間が空いてしまいましたが、去る 7/2(月)にソラシティカンファレンスセンターで開催された<a href="https://event.shoeisha.jp/devsumi/20190702">Developers Summit 2019 Summer</a>にメドレーが協賛させていただきました。</p>
<p>また、弊社の開発部長である田中が<a href="https://event.shoeisha.jp/devsumi/20190702/session/2089/">SI × Web の総合力で切り拓く新しいエンジニアのキャリアパス</a>というタイトルのセッションでお話をさせていただきましたので、レポートを書きたいと思います。</p>
<h1 id="会場の雰囲気">会場の雰囲気</h1>
<figure class="figure-image figure-image-fotolife" title="会場入口の立て看板がステキでした"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190719/20190719173752.jpg" alt="20190719173752.jpg"><figcaption>会場入口の立て看板がステキでした</figcaption></figure>
<p>会場ですがやはり今回も来場者が多く、セッション前には列が出来ていました。休憩時間中のブースにも人だかりといった感じで熱気を感じました。</p>
<figure class="figure-image figure-image-fotolife" title="次のセッション待ちで長蛇の列が"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190719/20190719172816.jpg" alt="20190719172816.jpg"><figcaption>次のセッション待ちで長蛇の列が</figcaption></figure>
<figure class="figure-image figure-image-fotolife" title="ブースも人が切れませんでした"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190719/20190719172807.jpg" alt="20190719172807.jpg"><figcaption>ブースも人が切れませんでした</figcaption></figure>
<p>しかしながら、運営が大変にスムーズだったので大きな混乱もなく次々と快適にセッションが見られるのは、やはり今までの経験によるものだろうなーと感じました。</p>
<h1 id="印象に残ったセッション">印象に残ったセッション</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190719/20190719174247.jpg" alt="20190719174247.jpg">
<p>色々とセッションを見ましたが、個人的にはやはり、<a href="https://twitter.com/t_wada">@t-wada</a>さんのセッションは何度見ても惹き込まれるなと今回も拝見して感じました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190719/20190719173721.jpg" alt="20190719173721.jpg">
<p>今回の内容を聞くと例え、全くテストを導入していないようなプロジェクトだったとしても「明日からちょっとずつやっていこう」と勇気をもらえるような内容でした。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190719/20190719173729.jpg" alt="20190719173729.jpg">
<h1 id="エンジニアのキャリアパス">エンジニアのキャリアパス</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190719/20190719173659.jpg" alt="20190719173659.jpg">
<p>いよいよ、弊社田中のセッションです。残念ながら、資料は非公開とさせていただいています…。申し訳ありません。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190719/20190719174601.jpg" alt="20190719174601.jpg">
<p>内容としては、少し前まではいわゆる SIer と Web 系という 2 つの領域で結構明確に壁を感じることもあったと思います。しかし、近年 X-Tech などの文脈では、関連省庁の法令やガイドラインなどを遵守しつつ、使いやすい UI への落とし込みやマネージドサービスを使用したインフラの構築などを設計する必要があります。これらの設計の上で、Web サービスとして高速に PDCA を回しサービスを改善していくという能力も他方で必要になります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190719/20190719174328.jpg" alt="20190719174328.jpg">
<p>こういった背景により、きっちりとした要件定義や設計する能力を求められる SIer 的な要素と手を動かして早くサービスを改善するサイクルを回す能力が必要な Web 的な要素という、それぞれの得意領域をミックスしたエンジニアが求められているのではないかというお話をさせていただきました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190719/20190719173302.jpg" alt="20190719173302.jpg">
<p>途中田中のキャリア変遷や、弊社の CLINICS を例に取り具体的にどのような能力を求められるのか?ということをお話させていただきました。</p>
<p>終了後のアンケートなど拝見する限り、やはり業界の括りに囚われがちなところも多いなか、キャリアパスの一つの実例として、ご好評をいただいたのは嬉しい限りでした。</p>
<h1 id="まとめ">まとめ</h1>
<p>メドレーのエンジニアやデザイナーは、きっちりと法令やガイドラインを守りつつ、その概念をプロダクトの設計に落しこみ、出来るだけ早く PDCA を回し、早く確実に実装することを目指しています。Developers Summit 2019 Summer のような多種多様なキャリアの方々が来場するイベントで、このようなお話をさせていただいたのはとても有意義だったと感じています。</p>
<figure class="figure-image figure-image-fotolife" title="セッションが無事終了し、ほっと一息付きながらメドレーロゴと一緒に"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190719/20190719173131.jpg" alt="20190719173131.jpg"><figcaption>セッションが無事終了し、ほっと一息付きながらメドレーロゴと一緒に</figcaption></figure>
<p>▼ メドレーってどんな会社?気になった方はこちら</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/team/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メドレーで働く | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの組織文化や募集要項をご紹介します</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- データウェアハウスとして使う Amazon Redshift についてhttps://developer.medley.jp/entry/2019/07/01/193427https://developer.medley.jp/entry/2019/07/01/193427はじめに
こんにちは。開発本部の阪本です。
今回は私が社内勉強会(TechLunch)にて Amazon Redshift(以下 Redshift)についてお話した内容を紹介させていただきます。
Redshift とは
概要
Redshi...Mon, 01 Jul 2019 10:34:27 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは。開発本部の阪本です。</p>
<p>今回は私が社内勉強会(TechLunch)にて Amazon Redshift(以下 Redshift)についてお話した内容を紹介させていただきます。</p>
<h1 id="redshift-とは">Redshift とは</h1>
<h2 id="概要">概要</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165024.png" alt="20190701165024.png">
<p><a href="https://aws.amazon.com/jp/redshift/">Redshift</a>とは AWS サービスが提供しているデータウェアハウスで、高可用/高パフォーマンス/柔軟なスケーラビリティを実現しているのが特徴です。</p>
<p>競合としては<a href="https://cloud.google.com/bigquery/?hl=ja">BigQuery</a>や<a href="https://hadoop.apache.org/">Hadoop</a>、また同じ AWS サービスでは<a href="https://aws.amazon.com/jp/athena/">Amazon Athena</a>も同様の位置付けになると思います。</p>
<h2 id="データベースとしての特徴">データベースとしての特徴</h2>
<p>Redshift の特徴として、列志向型データベースという点があります。</p>
<p>MySQL のようなリレーショナルデータベースはデータを行(レコード)単位で保持している事に対し、Redshift は列単位で保持しています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165052.png" alt="20190701165052.png">
<p>列単位でデータを持っているため集計クエリのような特定の列に対して大量の行を精査するのが高速である反面、行を特定してのアクセスは MySQL や PostgreSQL のような行志向のデータベースに比べてのクエリに比べて遅い傾向にあります。</p>
<p>またデータには SQL でアクセスすることができ、構文も PostgreSQL と互換性があります。
最近スキーマレスなデータベースなどが多く出てきていますが、Redshift は事前にテーブルを作成する必要のある従来型の RDBMS の形となっており、テーブル作成時は<code>CREATE TABLE</code>といったデータ定義言語(DDL)を使うことになります。</p>
<h2 id="機能面の特徴">機能面の特徴</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165113.png" alt="20190701165113.png">
<p>先にも書きましたが、データアクセス時に使う SQL は<a href="https://www.postgresql.org/">PostgreSQL</a>の構文と互換性があります。</p>
<p>よって PostgreSQL 用ドライバ(JDBC 含む)さえ使えれば、後は特別に意識することなく Redshift に対して接続やクエリ発行が行えるということになります。</p>
<p>この恩恵はプログラムだけではなく他社が展開しているサービスにも受けることができ、BI ツールの<a href="https://www.tableau.com/ja-jp">Tableau</a>や<a href="https://redash.io/">Redash</a>などもそのままデータソースとして利用することが出来ます。</p>
<p>次に、一般的な RDBMS との差についてです。
RDBMS にはあり Redshift には無いものとしては</p>
<ul>
<li>UNIQUE 制約</li>
<li>外部キー制約</li>
<li>インデックスが無い</li>
</ul>
<p>などがあります。</p>
<p>インデックスに関しては遠い意味での代用品(Sort Key)があるものの、基本的には使うことが出来ません。
注意点としてクエリ自体は PostgreSQL 互換なのでこれらを作る DDL 構文を受け入れてくれるますが、Redshift では無視されますのでご注意ください。</p>
<p>逆に、Redshift 固有のものとしては</p>
<ul>
<li>Sort Key</li>
<li>分散キー</li>
<li>列圧縮</li>
</ul>
<p>などがあります。
これらについては以下で少し掘り下げて説明します。</p>
<h3 id="sort-key">Sort Key</h3>
<p>テーブルをソートする際に使うインデックスのようなものです。
列単位で指定することができ、<code>ORDER BY</code>や<code>GROUP BY</code>句などの精査速度に影響します。</p>
<h3 id="分散キー">分散キー</h3>
<p>MySQL でいうパーティショニングキーに近いものとなります。</p>
<p>Redshift のデータ分散方法は</p>
<ul>
<li>均等に分散</li>
<li>キー値による分散</li>
<li>全コピー</li>
<li>Auto(負荷状況による自動選択)</li>
</ul>
<p>の 4 つで、データ量や特性によって使い分けることが出来ます。</p>
<p>分散キーは Redshift を上記の方法でクラスタリングした際に、どのノードにどのデータを保持するかを決定する判断材料となるキーです。</p>
<h2 id="運用面の特徴">運用面の特徴</h2>
<h3 id="aws-コンソール">AWS コンソール</h3>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165138.png" alt="20190701165138.png">
<p>Redshift は AWS コンソール上からも詳細な情報を確認することが出来ます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165201.png" alt="20190701165201.png">
<p><a href="https://aws.amazon.com/jp/rds/">Amazon RDS</a>にあるような一般的なメトリクスに加えてクエリ単位での実行状況や実行計画、そしてクエリの強制停止もコンソールから実行することができます。</p>
<h3 id="データ取り込み">データ取り込み</h3>
<p>Redshift はインポート元となるデータ取り込み選択肢が豊富ということも特徴の一つです。</p>
<h4 id="取り込み可能な形式">取り込み可能な形式</h4>
<blockquote>
<p>CSV/JSON/AVRO/PARQUET/ORC + これらの形式を圧縮したもの(BZIP,GZIP など)</p>
</blockquote>
<h4 id="読み取り元">読み取り元</h4>
<blockquote>
<p><a href="https://aws.amazon.com/jp/s3/">Amazon S3</a>/<a href="https://aws.amazon.com/jp/emr/">Amazon EMR</a>/<a href="https://aws.amazon.com/jp/dynamodb/">Amazon DynamoDB</a>など</p>
</blockquote>
<p>特にデータ配置元として S3 がサポートされているので、各種サービスが出力するログや、<a href="https://aws.amazon.com/jp/cloudwatch/">Amazon CloudWatch Logs</a>からの S3 や<a href="https://aws.amazon.com/jp/kinesis/">Amazon Kinesis Data Firehose</a>からの S3 ・・など、組み合わせ次第で可能性がとても広がります。</p>
<p>また S3 に配置しているデータはインポートせずに外部テーブルとして直接クエリを実行する機能 Amazon Redshift Spectrum があります。データ量がとても多い場合などにはこちらを利用するのも有効な手段です。</p>
<h3 id="ワークロード管理">ワークロード管理</h3>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165215.png" alt="20190701165215.png">
<p>負荷に関する運用については WLM(Work Load Management)という機能があります。</p>
<p>これは Redshift に接続するユーザをグループ単位で負荷制御を行うことが出来る機能です。</p>
<p>制御できる項目としては</p>
<h4 id="並列クエリ実行数">並列クエリ実行数</h4>
<p>同一グループ内でのクエリ同時実行数。上限に達すると待ち行列に入って詰まる。</p>
<h4 id="クエリ実行時間">クエリ実行時間</h4>
<p>クエリ実行時間の上限。</p>
<h4 id="メモリ使用量">メモリ使用量</h4>
<p>クエリ実行時に使うメモリ使用量の上限。</p>
<p>などがあります。</p>
<h3 id="可用性について">可用性について</h3>
<p>Redshift はクラスタリングをサポートしており、クラスタにはクエリの待受を行うリーダー(Leader)ノードと実処理を行うコンピューティングノードが作成されます。</p>
<p>AWS も推奨しているように、Redshift はマルチノード運用を基本としています。</p>
<p>これは Redshift の個々のデータノードは RAID5 のような形で各ノードに分散しているため、ノード障害が発生した場合でも生存ノードからノード復旧を自動で行ってくれます。
(同時に障害が発生しても復旧可能なノード数については、クラスタ内のノード数に依存します)</p>
<h2 id="将来性について">将来性について</h2>
<p>過去1年半の間でもこれだけの新機能が追加されており、まだまだ進化しています。</p>
<ul>
<li><code>UNLOAD</code>コマンドの CSV 対応</li>
<li>Concurrency Scaling</li>
<li><code>ALTER</code>文で<code>VARCHAR</code>の桁数変更</li>
<li>Elastic Resize</li>
<li><code>UNLOAD</code>コマンドのヘッダ行出力対応</li>
<li>コンソールにてクエリ実行環境追加</li>
<li>ネスト化されたデータのサポート</li>
<li>自動バージョンアップ方式の設定/予告確認可能</li>
<li>Parquet、ORC からの IMPORT サポート</li>
<li>Amazon Redshift Spectrum 東京サポート</li>
<li>新ノードタイプ DC2</li>
<li>Query Editor の追加</li>
<li>PL/SQL プロシージャのサポート</li>
<li><code>Vacum</code>コマンドの自動化 <em>New!!(2018/12 リリース)</em></li>
<li>WLM ワークロード管理の自動化 <em>New!!(2019/06 リリース)</em></li>
</ul>
<h1 id="実際の使い勝手">実際の使い勝手</h1>
<p>では、実際のところ Redshift の使い勝手がどういったものなのかを実例を含めて紹介します。
ここでは dc2.large ノード数 2 のサンプル環境を使用します。</p>
<h2 id="データのロード">データのロード</h2>
<p>クエリを発行するにも、まずは元になるデータが必要です。</p>
<p>ここでは<a href="https://ja.wikipedia.org/wiki/Wikipedia:%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89">日本語 Wikipedia の目次ダンプデータ</a>を 100 セット分用意し、その内容を Redshift にロードしてみます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165252.png" alt="20190701165252.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165257.png" alt="20190701165257.png">
<p>まずは目次データをこちらの画像の様に加工し、100 セット分のファイルとして分割し S3 へとアップロードします。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165317.png" alt="20190701165317.png">
<p>今回のロードするデータ量は<strong>235,732,000 レコードの 11.9GB</strong>となりました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165331.png" alt="20190701165331.png">
<p>S3 にファイルが配置出来たら、それを格納するテーブルを Redshift に作ります。
この際 PostgreSQL の<code>CREATE TABLE</code>によってテーブルを作成します。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165355.png" alt="20190701165355.png">
<p>テーブルの作成が完了したら、次はデータのロードです。
これも SQL クエリの<code>COPY</code>コマンドによって取り込みが行われます。
今回このロード処理は<strong>8 分 55 秒</strong>で完了しました。</p>
<p>データ量から考えるとかなり早いと感じますが、これはロード処理において並列処理の恩恵を最大限に受けているということが理由と考えられます。</p>
<p>Redshift のロード処理は分割されたファイルを使って並列処理を実行するため、巨大な単一ファイルを取り込むより短時間で取り込むことができます。</p>
<h2 id="クエリ発行">クエリ発行</h2>
<p>次に、クエリ発行についてですが、これはそのまま PostgreSQL のクエリを実行することになります。</p>
<p>今回は先のステップで取り込んだ目次ページをタイトルごとに<code>DISTINCT</code>する集計クエリを発行してみます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190701/20190701165409.png" alt="20190701165409.png">
<p>すると<strong>49 秒</strong>で結果が帰ってきました。
最低限のスペックで 235,732,000 レコードを精査するクエリの実行時間としては良いスコアではないでしょうか。</p>
<h1 id="不便に感じたこと">不便に感じたこと</h1>
<p>ここでは私が Redshift を運用していて不便に感じた事をいくつか紹介します。</p>
<h2 id="料金が高い">料金が高い</h2>
<p>これだけの機能とスペックが含まれているので仕方が無いかもしれませんが、AWS の他のサービスと比較して高価な印象があります。</p>
<p>さらにマルチノードとなると料金が掛け算で増えることになり、スペックの選択肢が他のサービスと比べても少ないため運用の際にはよく見積もりされることをおすすめします。</p>
<h2 id="更新クエリが遅い">更新クエリが遅い</h2>
<p>列志向型のせいなのかランダムアクセスが苦手で、特定の行を探して更新する<code>UPDATE</code>や<code>DELETE</code>は遅いです。</p>
<p>そもそも Redshift は頻繁に<code>UPDATE</code>/<code>DELETE</code>する用途には向いておらず(後述)、<code>INSERT</code>のみの積み上げ型や全レコード洗い替えが基本の用途になります。</p>
<p>また、<code>UPDATE</code>/<code>DELETE</code>を繰り返すとパフォーマンスが低下します。</p>
<p>これは内部的に保持している SortKey の状態が更新するたびに劣化し、連動してパフォーマンスが低下するためです。</p>
<p>解消するためには SortKey の再構築(<code>VACUM</code>/<code>OPTIMIZE</code>コマンド)により回復しますが、そもそもコマンド実行時間が長く、負荷も大きいので実行タイミングは検討が必要となります。</p>
<p>(追記)<strong>2018/12 の<a href="https://aws.amazon.com/jp/about-aws/whats-new/2018/12/amazon-redshift-automatic-vacuum/">アップデート</a>で自動実行機能が追加されました!</strong></p>
<h2 id="aws-コンソールが機能しないことがある">AWS コンソールが機能しないことがある</h2>
<p>先に多くの便利な機能を紹介しましたが、なぜかこれらが AWS コンソール上で機能してくれないことが割とあります。</p>
<p>WLM の設定次第なのか不明ですが、実行中のクエリが出なかったりクエリの強制停止が効かないなど、イザという時に限って使えないことがよくありました。</p>
<h2 id="メンテナンスが高頻度">メンテナンスが高頻度</h2>
<p>新機能が続々追加されていると紹介していますが、この度にメンテナンスが発生するものとなります。</p>
<p>タイミングは事前に設定したメンテナンスウインドウの週一の曜日/時間帯ですが、経験から 2 週間に 1 度ぐらいの頻度で発生していました。</p>
<p>この時間帯は再起動を伴う場合もあるため、Write どころか Read すら出来ない状態になることもあります。</p>
<p>そのため日中は社内業務。夜間はバッチでといった24時間ずっと稼働する要件を満たす事は少し厳しいものとなります。</p>
<h1 id="まとめ">まとめ</h1>
<p>まとめととなりますが、Redshift は特徴をふまえると下記のような場面で利用すれば良いかなと感じています。</p>
<h2 id="bi-ツール等のデータソースとして">BI ツール等のデータソースとして</h2>
<p>メンテ頻度や負荷の問題があるので、自分達のアプリから直接は繋げない。</p>
<h2 id="履歴やマスタデータのような大量の積み上げ型データの集計">履歴やマスタデータのような大量の積み上げ型データの集計</h2>
<p><code>UPDATE</code>が発生するなら、全件入れ替えが可能なデータ。</p>
<h2 id="1日の利用頻度がそれなりにあること">1日の利用頻度がそれなりにあること</h2>
<p>頻度が高く無いのであれば、Athena の方が安い。</p>
<p>どのサービスにも言えることですが、要件の合ったサービス選びをすることが一番大事です。</p>
<p>Redshift についても特徴がはっきりしているタイプのサービスなので、使い所を間違えないように、上手く使っていければと思います。</p>medley
- メドレーが協賛予定のイベントをご紹介します。(2019/7~)https://developer.medley.jp/entry/2019/06/26/161748https://developer.medley.jp/entry/2019/06/26/161748こんにちは。開発本部エンジニアの平木です。
メドレーでは、技術や業界の発展に少しでも寄与できればという考えから、エンジニア・デザイナーの技術イベントなどに、積極的に協賛させていただきたいと考えています。7 月以降のイベントも積極的にスポンサ...Wed, 26 Jun 2019 07:17:48 GMT<p>こんにちは。開発本部エンジニアの平木です。</p>
<p>メドレーでは、技術や業界の発展に少しでも寄与できればという考えから、エンジニア・デザイナーの技術イベントなどに、積極的に協賛させていただきたいと考えています。7 月以降のイベントも積極的にスポンサードさせていただきますので、ぜひ皆様にもお越しいただきたく、このエントリではメドレーが協賛するイベントの魅力をご紹介します。</p>
<h1 id="developers-summit-2019-summer">Developers Summit 2019 Summer</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190620/20190620190529.png" alt="20190620190529.png">
<ul>
<li><a href="https://event.shoeisha.jp/devsumi/20190702">公式サイト</a></li>
<li>2019/07/02(火) @ソラシティカンファレンスセンター</li>
<li>ブロンズスポンサー</li>
</ul>
<p>一番最初にご紹介するのは、ご存知 Developers Summit 2019 Summer(以降デブサミ夏)です。</p>
<p>本家である Developers Summit 2019 冬でも協賛させていただきましたが、今回のデブサミ夏ではブロンズスポンサーとして<a href="https://www.medley.jp/team/creator-story-tanaka.html">執行役員である田中</a>が<a href="https://event.shoeisha.jp/devsumi/20190702/session/2089/">SI × Web の総合力で切り拓く新しいエンジニアのキャリアパス</a>というタイトルでお話します。</p>
<p>ここ数年で<strong>X-Tech</strong>や<strong>DX</strong>の必要性が一気に叫ばれるようになってきましたが、メドレーでも<a href="https://www.medley.jp/approach/medical-dx.html">医療におけるデジタルトランスフォーメーションの推進</a>を目指して日々プロダクトの開発を進めています。X-Tech や DX を推進する上で、いわゆる「Web 系エンジニア」と「SI 系エンジニア」という垣根を越えた、ハイブリッドな新しいエンジニア像が求められるようになってきたと感じます。「課題を解決するために必要とされるようになったハイブリットな能力とは?」「これからのエンジニアはどう成長していくのがよいか?」ということをお話させていただきます。</p>
<p>おかげさまで、現時点でセッションは満員になっていますが、ぜひご覧いただければ幸いです。</p>
<h1 id="cloudnativedays-tokyo-2019--openstack-days-tokyo-2019">CloudNativeDays Tokyo 2019 / OpenStack Days Tokyo 2019</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190620/20190620190716.png" alt="20190620190716.png">
<ul>
<li><a href="https://cloudnativedays.jp/cndt2019/">公式サイト</a></li>
<li>2019/07/22(月) ~ 23(火) @虎ノ門ヒルズフォーラム</li>
<li>ノベルティスポンサー(トートバッグ)</li>
</ul>
<p>今年はイベントの統合もされ、名称も一新された日本最大級のコンテナ技術を始めとしたクラウドネイティブとオープンインフラの祭典である、こちらのイベントにも協賛させていただきます。</p>
<p>当日会場で配られるトートバッグに公式ロゴと共にメドレーのロゴを入れていただきました。実際にどんなデザインになるかは当日にご来場していただくまでのお楽しみとさせていただきますが、とても良いものになっているかと思います。</p>
<h1 id="builderscon-tokyo-2019">builderscon tokyo 2019</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190620/20190620190740.png" alt="20190620190740.png">
<ul>
<li><a href="https://builderscon.io/tokyo/2019">公式サイト</a></li>
<li>2019/08/29(木) ~ 31(土) @東京電機大学(東京千住キャンパス)1 号館</li>
<li>バックパネルスポンサー</li>
</ul>
<p>2016 年から毎年開催されている、色々な分野のエンジニアの様々なセッションが一気に聞けるイベント「builderscon tokyo」ですが(去年は電子名札バッジがインパクトありましたね)、今年は初めてスポンサーとして参加させていただくことになりました。</p>
<p>会場である東京電機大学千住キャンパスの部屋の一つ、100 周年記念ホールでセッションするスピーカーさんの後ろに設置されるバックパネルのなかに、メドレーのロゴが入ることに。</p>
<p>こちらのイベントでは弊社エンジニアもお邪魔する予定ですので、会場でお気軽にお声がけしていただければと思います。</p>
<h1 id="code-blue-2019">CODE BLUE 2019</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190620/20190620190759.png" alt="20190620190759.png">
<ul>
<li><a href="https://codeblue.jp/2019/">公式サイト</a></li>
<li>2019/10/29(火) ~ 30(水) @ベルサール渋谷ガーデン</li>
<li>ブロンズスポンサー</li>
</ul>
<p>今年で 7 回目の開催となる情報セキュリティの国際会議「CODE BLUE 2019」に初めてスポンサーをさせていただいています。</p>
<p>メドレーでも取り扱っている情報の重要性から、会社としての取り組みで<a href="/entry/2019/02/01/172457">ISMS 認証 / ISMS クラウドセキュリティ認証を取得</a>していますが、このようなセキュリティに関するイベントにも業界の発展に少しでも寄与できればと今年から協賛させていただくことになりました。</p>
<h1 id="desginship-2019">DesginShip 2019</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190620/20190620190818.png" alt="20190620190818.png">
<ul>
<li><a href="https://design-ship.jp/">公式サイト</a></li>
<li>2019/11/23(土 ) ~ 24(日) @東京国際フォーラム B7 ・ B5</li>
<li>シルバースポンサー</li>
</ul>
<p>去年から開催されているデザインカンファレンス「DesignShip 2019」に、去年から続きスポンサーをさせていただきます。</p>
<p>今年は会場も東京国際フォーラムになるということで、メドレーも去年よりもさらにアクティブな形でイベントに参加させていただくことになりそうです。</p>
<h1 id="まとめ">まとめ</h1>
<p>ここまでご紹介させていただいたイベント以外にも現在、スポンサーの打診をさせていただいているイベントがいくつかありますが、こちらも決定次第おしらせできればと考えています。</p>
<p>メドレーでは、技術や業界の発展に少しでも寄与できればという考えから、エンジニア・デザイナーの技術イベントなどにこれからも積極的に協賛させていただくスタンスを取っています。</p>
<p>全てのイベントに協賛できるわけではありませんが、スポンサーを探しているイベント運営者の方がいらっしゃいましたら、一度お気軽にお問い合わせいただければと思いますので、よろしくお願いします。</p>
<p>▼ メドレーってどんな会社?気になった方はこちら</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/team/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メドレーで働く | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの組織文化や募集要項をご紹介します</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- ユーザー認証と OpenID Connecthttps://developer.medley.jp/entry/2019/04/26/170946https://developer.medley.jp/entry/2019/04/26/170946こんにちは。開発本部のエンジニアの鶴です。 今回は先月に行った社内の勉強会 TechLunch の内容をご紹介させていただきます。
イントロ
Web サービスでは、ユーザーにアカウントを作ってもらい、ログインをしてサービスを利用してもらう、...Fri, 26 Apr 2019 08:09:46 GMT<p>こんにちは。開発本部のエンジニアの鶴です。 今回は先月に行った社内の勉強会 TechLunch の内容をご紹介させていただきます。</p>
<h1 id="イントロ">イントロ</h1>
<p>Web サービスでは、ユーザーにアカウントを作ってもらい、ログインをしてサービスを利用してもらう、というユーザー認証を利用するサービスも多いかと思います。 Web サービスを開発する側としては、サービスごとに都度ユーザー認証の仕組みを構築する必要がありますが、セキュリティ対策の観点から考慮することが多く、地味に開発の工数がかかってしまいます。</p>
<p>また最近では、<a href="https://aws.amazon.com/jp/cognito/">Amazon Cognito</a>や<a href="https://firebase.google.com/products/auth/?hl=ja">Firebase Authentication</a>、<a href="https://auth0.com/jp/">Auth0</a>など、ユーザー認証サービスがいくつかリリースされ、ユーザー認証の機能をこれらの外部サービスに任せて開発の手間を省くという選択肢も取れるようになってきています。 自分自身、かつて担当したプロジェクトでユーザー認証の仕組みを Amazon Cognito にまかせてシステムを構築したことがありました。</p>
<p>しかし、当時は特にユーザープールの機能がリリースされて間もないこともあり、SDK の動作やサービスの仕様の理解にかなり手間取ったことを覚えています。</p>
<p>ユーザー認証サービスでは<a href="https://openid.net/connect/">OpenID Connect</a>という仕様に準拠していることが多いのですが、おそらく自分にとってこの仕様の理解が疎かだったことが原因の一つだったと思います。</p>
<p>そこで今回は、ユーザー認証と OpenID Connect の仕組みについて改めて勉強し直したので、その内容を簡単に解説をさせていただこうと思います。</p>
<h1 id="ユーザー認証とは">ユーザー認証とは</h1>
<p>ユーザー認証の前に、そもそも認証とはどういう操作のことを指すのでしょうか。 みんな大好き<a href="https://ja.wikipedia.org/wiki/%E8%AA%8D%E8%A8%BC">Wikipedia 先生</a>によると、以下のような記載があります。</p>
<blockquote>
<p>認証(にんしょう)とは、何かによって、対象の正当性を確認する行為を指す。 認証行為は認証対象よって分類され、認証対象が人間である場合には相手認証(本人認証)、メッセージである場合にはメッセージ認証、時刻の場合には時刻認証と呼ぶ。 単に認証と言った場合には相手認証を指す場合が多い。</p>
</blockquote>
<p>ユーザー認証は Web サービスにとってリクエストを送信してきた相手の正当性を認証することなので、相手認証の 1 つですね。 さらに相手認証の認証方法として 2 通りの方法があります。</p>
<blockquote>
<p>第 1 の方法は、被認証者が認証者に、秘密鍵をもっていることによって得られる何らかの能力の証明を行う方法である。第 2 の方法は、被認証者が認証者に、被認証者の公開鍵に対応する秘密鍵の知識の証明を行う方法である。</p>
</blockquote>
<p>ユーザー認証の場合、多くはこの第 1 の方法での認証で、ログイン時にユーザー ID に加えて、この「秘密鍵」としてアカウント作成時に登録しておいたパスワードを入力することでユーザー認証を行っているかと思います。</p>
<h1 id="web-サービスでのユーザー認証">Web サービスでのユーザー認証</h1>
<p>Web サービスで扱う情報の秘匿性が高くなればなるほど、この「秘密鍵」が本当にそのユーザーにしか提供できない情報であることが求められます。</p>
<p>上述のようなパスワードによる認証の場合、パスワードが推測されるなどして悪意のある第三者にアカウントが乗っ取られてしまう事件はよく耳にします。</p>
<p>よりセキュリティを高めるため、パスワード以外の認証や多要素認証などを用いる事が増えてきました。</p>
<p>また、セキュリティの観点だけでなく利便性の観点からも、パスワード入力の代わりに指紋認証や顔認証によるログインや、あるいは各種 SNS アカウントによるログインも増えてきています。自社の複数のサービスを連携できるようユーザーに共通 ID を提供したい、といったケースもあるかもしれません。</p>
<p>最近ではパスワードレス認証や<a href="https://www.w3.org/TR/webauthn/">WebAuthn</a>も注目されていますね。今回は紹介は割愛しますが、パスワードレス認証の一つである<a href="https://fidoalliance.org/fido%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF/?lang=ja">FIDO 認証</a>は、前述の「被認証者が認証者に、被認証者の公開鍵に対応する秘密鍵の知識の証明を行う方法」を利用した認証方式のようです。(<a href="https://fidoalliance.org/">ref1</a>,<a href="https://www.slideshare.net/techblogyahoo/fido-124019677"> ref2</a>)</p>
<p>このように、セキュリティの観点やユーザー利便性の観点などにより、Web サービスにおけるユーザー認証機能は 1 回作ったら終わりではなく、時流に応じて適宜改修する必要が出てくることもあるかと思います。</p>
<p>しかし、特にユーザー認証がメインのサービスと密結合している場合などでは、認証の前後など認証処理そのものだけでなくその周辺の処理への影響範囲も無視できない場合もあり、ユーザー認証の改修に工数が思ったよりかかってしまったり、対応が滞ってしまうこともあるかもしれません。</p>
<p>そんなとき、認証サービスをメインのサービスと切り離すことでより柔軟なユーザー認証手段を提供できるよう、OpenID Connect の導入を検討してみても良いかもしれません。</p>
<h1 id="openid-connect-とは">OpenID Connect とは</h1>
<p>OpenID Connect(以降、OIDC)について、本家サイトでは以下のように説明されています。</p>
<blockquote>
<p>OpenID Connect 1.0 は, OAuth 2.0 プロトコルの上にシンプルなアイデンティティレイヤーを付与したものである. このプロトコルは Client が Authorization Server の認証結果に基づいて End-User のアイデンティティを検証可能にする. また同時に End-User の必要最低限のプロフィール情報を, 相互運用可能かつ RESTful な形で取得することも可能にする.</p>
<p>この仕様は, OpenID Connect の主要な機能である OAuth 2.0 上で End-User の情報伝達のためにクレームを用いる認証機能を定義する. この仕様はまた, OpenID Connect を利用するための Security, Privacy Considerations を説明する.</p>
</blockquote>
<p>(<a href="https://www.openid.or.jp/document/index.html#op-doc-openid-connect">日本語</a>,<a href="https://openid.net/connect/"> 英語</a>)</p>
<p>個人的には、メインのサービスと認証サービスを切り離して運用することを想定して仕様が規定されている点が重要と考えます。 OIDC を利用することで、ユーザー認証をより柔軟に改修したり新しい認証方法に対応したりすることがしやすくなることが期待されるからです。</p>
<p>なお、OIDC の仕様には認証手段自体(パスワード認証や多要素認証など)に関しては規定されておらず、あくまで認証サービスによる認証結果の取得方法や扱い方についてが規定されています。</p>
<p>また、様々なユースケースに対応できるよういくつかの処理フローやオプショナルな設定が提供されていますが、その反面セキュリティの確保は実装者に委ねられており、ユースケースに応じて適切な実装を行う必要があります。</p>
<p>前述したユーザー認証サービスである Amazon Cognito や FirebaseAuthentication などは、認証手段が標準でいくつか提供されており、加えてバックエンドと SDK に OIDC 固有のセキュアな実装が施されてあるため、開発者は最小限の設定だけでユーザー認証機能が利用できるようになります。便利ですね。</p>
<h1 id="処理フローの解説">処理フローの解説</h1>
<p>さて、OIDC の具体的な処理について解説していこうと思います。</p>
<p>まず登場人物です。</p>
<ul>
<li>OpenID Provider(OP):認証認可を行うサービス。ユーザー認証情報(識別子やパスワードなど)を管理したり、認証に関するユーザー属性情報(氏名やユーザー名など)を保持する。</li>
<li>RelyingParty(RP): アクセス元のユーザーの認証とユーザー属性情報を要求するサービス。ユーザーからのリクエストに対し OP による認証結果を信頼(rely)してリソースへのアクセスを許可する(例えばマイページを表示するなど)。</li>
<li>EndUser:ログインをしてサービスを利用しようとしているユーザー。</li>
</ul>
<p>基本的な用語も先に簡単に紹介しておきます。</p>
<ul>
<li>クライアント ID:OpenID Provider で管理する、RelyingParty の識別情報。</li>
<li>クライアントシークレット:OpenID Provider が RelyingParty ごとに発行する秘密鍵。</li>
<li>認証コード:後述する AuthorizationCodeFlow で OpenID Provider が発行する短命のパスワードのようなもの。</li>
<li>ID トークン:OpenID Provider から発行される、ユーザーによる認証を行った証明情報。<a href="https://jwt.io/">JSON Web Token(JWT)</a>で表現され、検証により改ざん検知することができる。認証の内容(OpenID Provider、ユーザー識別子、RelyingParty のクライアント ID、有効期限など)やユーザー属性情報が格納される。</li>
<li>アクセストークン:OpenID Provider が保持するユーザー属性情報に対しアクセスするための OAuth2 の認可トークン。</li>
</ul>
<p>OIDC の処理フローは大きく分けて 3 種類が規定されています。</p>
<ul>
<li>AuthorizationCodeFlow:認証成功時に OpenID Provider が RelyingParty に対し認証コードを発行し、RelyingParty はこれを用いて OpenID Provider から ID トークン等を取得する。RelyingParty がサーバーサイドアプリケーションで、OpenID Provider から発行されるクライアントシークレットを安全に管理することができる場合などに用いられる。</li>
<li>ImplicitFlow:認証コードを使わず認証結果のレスポンスで ID トークン等を取得する。RelyingParty がクライアントアプリケーションの場合など、クライアントシークレットが安全に管理できない場合などに用いられる。</li>
<li>HybridFlow:AuthorizationCodeFlow と ImplicitFlow の組み合わせ。</li>
</ul>
<p>これらのフローの違いは以下の表のとおりです。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190426/20190426141901.png" alt="f:id:medley_inc:20190426141901p:plain" title="f:id:medley_inc:20190426141901p:plain"></p>
<p>(<a href="https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html#Authentication">公式より引用</a>)</p>
<p>今回は、<a href="https://openid-foundation-japan.github.io/openid-connect-basic-1_0.ja.html">公式</a>や<a href="https://www.slideshare.net/mobile/kura_lab/openid-connect-id">こちらの解説記事</a>などを参照しながら、基本の処理フローである AuthorizationCodeFlow について解説します。</p>
<p>簡略化のため、イメージ重視で登場人物は「<em>ユーザー</em>」「(ユーザーにサービスを提供する)<em>Web サービス</em>」「<em>認証サービス</em>」と表現することにします。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190426/20190426141939.png" alt="f:id:medley_inc:20190426141939p:plain" title="f:id:medley_inc:20190426141939p:plain"></p>
<p>大まかには以下のステップで処理が行われます。</p>
<ol>
<li><em>ユーザー</em>からのアクセスに対し、<em>Web サービス</em>は<em>認証サービス</em>にユーザー認証を要求する</li>
<li><em>認証サービス</em>はユーザー認証を行い、認証コードを発行して、<em>ユーザー</em>を<em>Web サービス</em>にリダイレクトさせる</li>
<li><em>Web サービス</em>は 2 で取得した認証コードを用いて<em>認証サービス</em>に ID トークン等をリクエストする</li>
<li><em>Web サービス</em>は 3 で取得した ID トークンを検証し、<em>ユーザー</em>の識別子を取得する</li>
</ol>
<h2 id="step0--事前準備">Step.0 : 事前準備</h2>
<p>あらかじめ<em>Web サービス</em>は<em>認証サービス</em>からクライアント ID とクライアントシークレットを取得し保持しておきます。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190426/20190426142007.png" alt="f:id:medley_inc:20190426142007p:plain" title="f:id:medley_inc:20190426142007p:plain"></p>
<h2 id="step1-ユーザー認証の要求">Step.1: ユーザー認証の要求</h2>
<p><em>ユーザー</em>が<em>Web サービス</em>に対し一般的なログインの流れでログインを要求すると、<em>Web サービス</em>は<em>認証サービス</em>にリクエストをリダイレクトします。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190426/20190426142502.png" alt="f:id:medley_inc:20190426142502p:plain" title="f:id:medley_inc:20190426142502p:plain"></p>
<p><em>Web サービス</em>から<em>認証サービス</em>へのリダイレクトの URL は以下のような感じです。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="http"><code><span class="line"><span style="color:#569CD6">HTTP</span><span style="color:#D4D4D4">/</span><span style="color:#B5CEA8">1.1</span><span style="color:#B5CEA8"> 302</span><span style="color:#CE9178"> Found</span></span>
<span class="line"><span style="color:#569CD6">Location:</span><span style="color:#CE9178"> https://server.example.com/authorize?</span></span>
<span class="line"><span style="color:#D4D4D4"> response_type=code</span></span>
<span class="line"><span style="color:#D4D4D4"> &</span><span style="color:#9CDCFE">client_id</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">s6BhdRkqt3</span></span>
<span class="line"><span style="color:#D4D4D4"> &</span><span style="color:#9CDCFE">redirect_uri</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">https%3A%2F%2Fclient.example.org%2Fcb</span></span>
<span class="line"><span style="color:#D4D4D4"> &</span><span style="color:#9CDCFE">scope</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">openid%20profile</span></span>
<span class="line"><span style="color:#D4D4D4"> &</span><span style="color:#9CDCFE">state</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">af0ifjsldkj</span></span></code></pre>
<p>response_type で OIDC のどの認証フローを使うかを指定します。</p>
<p>redirect_uri は、<em>認証サービス</em>での認証が成功したときの<em>Web サービス</em>にコールバックする URL です。これは事前に認証サービスに登録しておく必要があります。</p>
<p>scope には認証の内容を設定します。openid は必須で、他には OAuth2 のアクセストークンを使って取得できるユーザー属性情報を指定します。</p>
<p>scope で指定できるユーザー属性情報は以下のとおりです。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190426/20190426142232.png" alt="f:id:medley_inc:20190426142232p:plain" title="f:id:medley_inc:20190426142232p:plain"></p>
<p>(<a href="https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html#StandardClaims">公式より引用</a>)</p>
<p>ユーザーの認証でよく使われそうな「氏名」や「メールアドレス」など基本的な属性情報が定義されています。</p>
<p>state は CSRF 対策などのためのランダム値です。認証フローを開始するたびに<em>Web サービス</em>が発行し、リクエストとコールバックの間で値が維持されます。</p>
<p>他にもいくつかのパラメータ(nonce など)が定義されており、必要に応じて利用します。</p>
<h2 id="step2-ユーザー認証と認証コードの発行">Step.2: ユーザー認証と認証コードの発行</h2>
<p><em>認証サービス</em>では認証手段に応じてログイン ID ・パスワードの入力フォームなどを表示し、<em>ユーザー</em>から認証情報を取得して認証処理を行います。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190426/20190426142212.png" alt="f:id:medley_inc:20190426142212p:plain" title="f:id:medley_inc:20190426142212p:plain"></p>
<p><em>認証サービス</em>はユーザーの認証に成功すると、認証コードを発行し、<em>ユーザー</em>を<em>Web サービス</em>にリダイレクトさせます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="http"><code><span class="line"><span style="color:#569CD6">HTTP</span><span style="color:#D4D4D4">/</span><span style="color:#B5CEA8">1.1</span><span style="color:#B5CEA8"> 302</span><span style="color:#CE9178"> Found</span></span>
<span class="line"><span style="color:#569CD6">Location:</span><span style="color:#CE9178"> https://client.example.org/cb?</span></span>
<span class="line"><span style="color:#D4D4D4"> code=SplxlOBeZQQYbYS6WxSbIA</span></span>
<span class="line"><span style="color:#D4D4D4"> &</span><span style="color:#9CDCFE">state</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">af0ifjsldkj</span></span></code></pre>
<p>リダイレクト先について、<em>認証サービス</em>は Step.1 で受け取った redirect<em>url を</em>認証サービス<em>に予め登録されている URL と合致することを検証する必要があります。_Web サービス</em>のなりすましを防ぐためです。</p>
<p>また<em>Web サービス</em>側で<em>認証サービス</em>からのレスポンスであることを確認できるよう、state もパラメータに含めます。</p>
<p>なお、認証に失敗した場合は下記のように認証エラーした内容をパラメーターに加えて Web サービスにリダイレクトさせます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="http"><code><span class="line"><span style="color:#569CD6">HTTP</span><span style="color:#D4D4D4">/</span><span style="color:#B5CEA8">1.1</span><span style="color:#B5CEA8"> 302</span><span style="color:#CE9178"> Found</span></span>
<span class="line"><span style="color:#569CD6">Location:</span><span style="color:#CE9178"> https://client.example.org/cb?</span></span>
<span class="line"><span style="color:#D4D4D4"> error=invalid_request</span></span>
<span class="line"><span style="color:#D4D4D4"> &</span><span style="color:#9CDCFE">error_description</span><span style="color:#D4D4D4">=</span></span>
<span class="line"><span style="color:#D4D4D4"> Unsupported%20response_type%20value</span></span>
<span class="line"><span style="color:#D4D4D4"> &</span><span style="color:#9CDCFE">state</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">af0ifjsldkj</span></span></code></pre>
<h2 id="step3-認証結果の取得">Step.3: 認証結果の取得</h2>
<p>Step.2 で<em>Web サービス</em>は<em>認証サービス</em>からのリダイレクトを受け、認証コードを取得すると、この認証コードを利用して<em>認証サービス</em>に対して認証結果情報(ID トークンなど)を取得します。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190426/20190426142612.png" alt="f:id:medley_inc:20190426142612p:plain" title="f:id:medley_inc:20190426142612p:plain"></p>
<p><em>Web サービス</em>から<em>認証サービス</em>への認証結果取得リクエストは以下のような形式になります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="http"><code><span class="line"><span style="color:#C586C0">POST</span><span style="color:#D4D4D4"> /token </span><span style="color:#569CD6">HTTP</span><span style="color:#D4D4D4">/</span><span style="color:#B5CEA8">1.1</span></span>
<span class="line"><span style="color:#569CD6">Host:</span><span style="color:#CE9178"> server.example.com</span></span>
<span class="line"><span style="color:#569CD6">Authorization:</span><span style="color:#CE9178"> Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW</span></span>
<span class="line"><span style="color:#569CD6">Content-Type:</span><span style="color:#CE9178"> application/x-www-form-urlencoded</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA</span></span>
<span class="line"><span style="color:#D4D4D4"> &</span><span style="color:#9CDCFE">redirect_uri</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb</span></span></code></pre>
<p>認証コードを送信する必要があるため、POST メソッドでリクエストします。またクライアント ID とクライアントシークレットによる BASIC 認証を行います。</p>
<p>このリクエストでクライアントシークレットが必要になるのですが、これは<em>認証サービス</em>にとって<em>Web サービス</em>の正当性を検証するための重要なパラメータであり、安全に管理される必要があります。</p>
<p>SinglePageApplication のようにユーザー側にあるアプリケーションで OIDC を処理する場合には、このクライアントシークレットが安全に管理される保証がないため、AuthenticationCodeFlow ではなく<a href="https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html#ImplicitFlowAuth">ImplicitFlow</a>などを利用する必要があります。</p>
<p><em>Web サービス</em>からのリクエストを受け取った<em>認証サービス</em>は grant_type に Step.1 で指定した処理フローに該当する情報を渡し、認証コード( code )と合わせて<em>認証サービス</em>にリクエストの検証をさせます。</p>
<p><em>認証サービス</em>はリクエストの検証に成功すると、<em>Web サービス</em>に対し認証結果として ID トークン等を返却します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="http"><code><span class="line"><span style="color:#569CD6">HTTP</span><span style="color:#D4D4D4">/</span><span style="color:#B5CEA8">1.1</span><span style="color:#B5CEA8"> 200</span><span style="color:#CE9178"> OK</span></span>
<span class="line"><span style="color:#569CD6">Content-Type:</span><span style="color:#CE9178"> application/json</span></span>
<span class="line"><span style="color:#569CD6">Cache-Control:</span><span style="color:#CE9178"> no-cache, no-store</span></span>
<span class="line"><span style="color:#569CD6">Pragma:</span><span style="color:#CE9178"> no-cache</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "access_token"</span><span style="color:#D4D4D4">:</span><span style="color:#CE9178">"SlAV32hkKG"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "token_type"</span><span style="color:#D4D4D4">:</span><span style="color:#CE9178">"Bearer"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "expires_in"</span><span style="color:#D4D4D4">:</span><span style="color:#B5CEA8">3600</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "refresh_token"</span><span style="color:#D4D4D4">:</span><span style="color:#CE9178">"tGzv3JOkF0XG5Qx2TlKWIA"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "id_token"</span><span style="color:#D4D4D4">:</span><span style="color:#CE9178">"eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso"</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span></code></pre>
<p>access_token は<em>認証サービス</em>で管理しているユーザー属性情報を取得するための OAuth2 トークンです。</p>
<p>refresh_token は<em>認証サービス</em>から access_token を再発行する際に利用します。</p>
<h2 id="step4-認証結果の検証">Step.4: 認証結果の検証</h2>
<p>Web サービス<em>は</em>認証サービス*から取得した ID トークンを検証します。ID トークンは前述の通り JWT で表現されており、*認証サービス_の公開鍵を用いて検証することができます。</p>
<p>手順は<a href="https://openid-foundation-japan.github.io/openid-connect-basic-1_0.ja.html#IDTokenValidation"> こちら</a> をご確認ください。他にも参考リンクを紹介しておきます。</p>
<ul>
<li><a href="https://qiita.com/bobunderson/items/d48f89e2b3e6ad9f9c4c">https://qiita.com/bobunderson/items/d48f89e2b3e6ad9f9c4c</a></li>
<li><a href="https://qiita.com/TakahikoKawasaki/items/8f0e422c7edd2d220e06">https://qiita.com/TakahikoKawasaki/items/8f0e422c7edd2d220e06</a></li>
</ul>
<p>下記は ID トークンに含まれる認証情報の例です。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#9CDCFE"> "iss"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"https://server.example.com"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "sub"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"24400320"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "aud"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"s6BhdRkqt3"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "exp"</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">1311281970</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "iat"</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">1311280970</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>このうち sub が<em>認証サービス</em>で管理されている<em>ユーザー</em>の識別子です。</p>
<p>iss は<em>認証サービス</em>、aud は<em>Web サービス</em>のクライアント ID になります。</p>
<p>exp、iat はそれぞれ認証の有効期限と認証したタイムスタンプです。</p>
<p><em>Web サービス</em>は ID トークンが正しい内容であることが確認できれば、これをログインセッションと紐づけて保管します。</p>
<p>以上で認証処理は完了です。</p>
<h1 id="ユーザー属性情報の取得">ユーザー属性情報の取得</h1>
<p>ユーザー認証後、<em>Web サービス</em>がユーザー名などのユーザー属性情報が必要になった場合、Step.3 で取得した access_token を利用し<em>認証サービス</em>に対してユーザー属性情報をリクエストします。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="http"><code><span class="line"><span style="color:#C586C0">GET</span><span style="color:#D4D4D4"> /userinfo </span><span style="color:#569CD6">HTTP</span><span style="color:#D4D4D4">/</span><span style="color:#B5CEA8">1.1</span></span>
<span class="line"><span style="color:#569CD6">Host:</span><span style="color:#CE9178"> server.example.com</span></span>
<span class="line"><span style="color:#569CD6">Authorization:</span><span style="color:#CE9178"> Bearer SlAV32hkKG</span></span></code></pre>
<p>このリクエストにより、Step.1 の scope で指定したユーザー属性情報が取得できます。</p>
<h1 id="まとめ">まとめ</h1>
<p>以上少し長くなりましたが、ユーザー認証と OpenID Connect、特に基本の AuthenticationCodeFlow について解説しました。限られた発表時間の中での解説のため厳密さより雰囲気を重視した内容となりましたが、お気づきの点などあればお知らせいただければと思います。</p>
<p>サービスの要件やフェーズによって OIDC を取り入れるかどうかは様々ですが、ユーザー認証の実装を自前で実装、メンテナンスしていくだけでなく、Amazon Cognito などの便利な認証サービスを利用していくことも選択肢の一つとして検討してみても良いかもしれません。</p>
<p>そしてそれら便利な認証サービスをうまく使いこなすためにも、その背景にある OIDC の仕様や思想、そもそも認証の仕組みについて立ち返ってみると、理解が一段と深まるかとおもいます。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="CREATOR'S STORY | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fteam%2Fcreator-story.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/team/creator-story.html">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>medley
- RubyKaigi 2019 にメドレーが Breakfast Sponsor として参加しましたhttps://developer.medley.jp/entry/2019/04/24/175425https://developer.medley.jp/entry/2019/04/24/175425こんにちは。開発本部のエンジニア・新居です。
メドレーは 4/18〜4/20 に開催された RubyKaigi 2019 に Breakfast Sponsor(朝食スポンサー)として協賛させていただきました(一昨年の Ruby Spons...Wed, 24 Apr 2019 08:54:25 GMT<p>こんにちは。開発本部のエンジニア・新居です。</p>
<p>メドレーは 4/18〜4/20 に開催された RubyKaigi 2019 に Breakfast Sponsor(朝食スポンサー)として協賛させていただきました(<a href="/entry/2017/09/28/120000">一昨年の Ruby Sponsor</a>、<a href="/entry/2018/06/06/151300">昨年の</a><a href="/entry/2018/06/06/151300">Lightning Talks Sponsor</a>に続き、3 年目の協賛です)。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="RubyKaigi 2019" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Frubykaigi.org%2F2019" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://rubykaigi.org/2019">rubykaigi.org</a></cite>
<h2 id="2017-年のレポート">2017 年のレポート</h2>
<iframe class="embed-card embed-blogcard" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" title="RubyKaigi 2017 にメドレーが Ruby Sponsor として参加しました - Medley Developer Blog" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.medley.jp%2Fentry%2F2017%2F09%2F28%2F120000" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="/entry/2017/09/28/120000">developer.medley.jp</a></cite>
<h2 id="2018-年のレポート">2018 年のレポート</h2>
<iframe class="embed-card embed-blogcard" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" title="Lightning Talks Sponsor として RubyKaigi 2018 に参加してきました - Medley Developer Blog" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.medley.jp%2Fentry%2F2018%2F06%2F06%2F151300" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="entry/2018/06/06/151300">developer.medley.jp</a></cite>
<p>2019 年は福岡県の福岡国際会議場での開催でした。会場に向かう途中の博多駅ではさっそく RubyKaigi のポスターに迎えられ、RubyKaigi ムードが高まっておりました。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="博多駅ではさっそく RubyKaigi のポスターがお出迎え">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424131901.jpg" alt="RubyKaigi">
<figcaption>博多駅ではさっそく RubyKaigi のポスターがお出迎え</figcaption>
</figure>
<p>ということで、今年の RubyKaigi の様子をレポートさせていただきます。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="【福岡国際会議場】" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.marinemesse.or.jp%2Fcongress%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.marinemesse.or.jp/congress/">www.marinemesse.or.jp</a></cite>
<iframe style="border: 0;" src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3323.0337958381538!2d130.4012956152033!3d33.60442758073009!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x354191ef7124343b%3A0x68f0d80606e3c997!2z56aP5bKh5Zu96Zqb5Lya6K2w5aC0!5e0!3m2!1sja!2sjp!4v1556083242524!5m2!1sja!2sjp" width="600" height="450" frameborder="0" allowfullscreen></iframe>
<h1 id="breakfast-sponsor-の様子">Breakfast Sponsor の様子</h1>
<p>今回は Breakfast Sponsor としての協賛で、RubyKaigi2 日目と 3 日目の朝 8 時 30 分から 10 時までの時間帯で、RubyKaigi に参加されるみなさんに朝食をご提供させていただきました。</p>
<p>朝食会場は福岡国際会議場内の 1F にあるレストラン <a href="https://www.f-sunpalace.com/restaurant/raconter/#raconter%E2%80%8B">ラコンテ</a>さん。朝食の内容はビュッフェ形式で、焼き魚やオムレツなどの定番おかずと、福岡名産の辛子明太子や辛子高菜をたらふく食べられる朝食でした。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="入り口でみなさんをお出迎えするエンジニア稲本">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424132555.jpg" alt="">
<figcaption>入り口でみなさんをお出迎えするエンジニア稲本</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="おしゃれなテラス席">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424132612.jpg" alt="">
<figcaption>おしゃれなテラス席</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="ビュッフェに並ぶおかず">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424132701.jpg" alt="">
<figcaption>ビュッフェに並ぶおかず</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="フルーツもたくさん">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424132608.jpg" alt="">
<figcaption>フルーツもたくさん</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="辛子明太 & 辛子高菜」乗せごはん">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424132602.jpg" alt="">
<figcaption>「辛子明太 & 辛子高菜」乗せごはん</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="福岡名産の辛子明太子と辛子高菜を白ごはんに添えてたらふくいただきました">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424132651.jpg" alt="">
<figcaption>福岡名産の辛子明太子と辛子高菜を白ごはんに添えてたらふくいただきました</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="会場の様子、大盛況">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424132735.jpg" alt="">
<figcaption>会場の様子、大盛況でした</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="テラス席はグローバルな感じ">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424132743.jpg" alt="">
<figcaption>テラス席はグローバルな感じ</figcaption>
</figure>
<p>Twitter でも朝食の様子を話題にしていただけたりと、ご満足いただけた様子が伺えました。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">MEDLEY さんの朝食サービス、めっちゃメニューある。外で優雅に食べてます</p>— Tsukasa OISHI (@tsukasa_oishi) <a href="https://twitter.com/tsukasa_oishi/status/1119022532697268224?ref_src=twsrc%5Etfw">April 18, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">MEDLEY さん提供の朝食はブュッフェ形式。洋風にしてみたけど、他にご飯や味噌汁もある。 <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw">#rubykaigi</a> <a href="https://t.co/W4eggL6PQt">pic.twitter.com/W4eggL6PQt</a></p>— thinca (@thinca) <a href="https://twitter.com/thinca/status/1119028472507015168?ref_src=twsrc%5Etfw">April 19, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">RubyKaigi と MEDLEY さんのおかげで健康的な生活してる…。<a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw">#rubykaigi</a> <a href="https://t.co/Z502pk3yKg">pic.twitter.com/Z502pk3yKg</a></p>— yebis0942 (@yebis0942) <a href="https://twitter.com/yebis0942/status/1119078057518571520?ref_src=twsrc%5Etfw">April 19, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">朝食で食べた明太子・高菜ご飯がおいしかった! MEDLEY さん、ありがとうございます! <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw">#rubykaigi</a></p>— snagasawa (@snagasawa_) <a href="https://twitter.com/snagasawa_/status/1119393835048558592?ref_src=twsrc%5Etfw">April 20, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>ご来場いただいた人数は、RubyKaigi2 日目(朝食 1 日目)は 379 名、3 日目(朝食 2 日目)は 411 名でした。</p>
<p>朝食会場の出入り口でご案内していると「美味しかったです、ありがとうございました!」というお声を直接いただけたりと、みなさんにご満足いただけたようで良かったです。</p>
<p>Breakfast Sponsor での参加は初めてでしたが、みなさんが満足気に朝食会場を後にする姿を見ているとこちらも嬉しい気持ちになりました。</p>
<p>朝食をご利用いただいたみなさん、本当にありがとうございました。</p>
<p>今年は Breakfast Sponsor という形でしたが、Ruby と Ruby コミュニティの発展のため、今後も様々な形で貢献していければと思います。</p>
<h1 id="rubykaigi-2019-の様子">RubyKaigi 2019 の様子</h1>
<p>今年も毎年恒例の Matz さんの Keynote から始まりました。</p>
<p>今年は「The Year of Concurrency」というテーマで、Ruby 3 についての発表でした。</p>
<p>Ruby 3 の Static Analysis、Performance、Concurrency あたりの話を中心に、Ruby の今後の展望について語られました。普段 Ruby を使って仕事をしている身として、Matz さんから Ruby 3 の話を聞けたのは貴重な時間でしたし、改めて Ruby の進化を実感できるセッションでした。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="Matz さんの Keynote">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424133336.jpg" alt="">
<figcaption>Matz さんの Keynote</figcaption>
</figure>
<p>その他のセッションスケジュールについてはこちら。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="RubyKaigi 2019" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Frubykaigi.org%2F2019%2Fschedule" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://rubykaigi.org/2019/schedule">rubykaigi.org</a></cite>
<p>2 階にはブースゾーンがあり、セッションの合間にはたくさんの参加者で溢れかえっていました。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="ブースゾーンは大盛況">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424133441.jpg" alt="">
<figcaption>ブースゾーンは大盛況</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="ブースゾーンの出入り口付近には各社のノベルティ">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424133642.jpg" alt="">
<figcaption>ブースゾーンの出入り口付近には各社のノベルティ</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="Ruby 関連書籍の展示も">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424133523.jpg" alt="">
<figcaption>Ruby 関連書籍の展示も</figcaption>
</figure>
会場 5 階(最上階)にはハックスペースが用意されていました。
<p>ここで各々休憩したり、黙々と作業したり、仲間と談笑したりと、様々な用途に使われていました。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="ハックスペース入り口">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424133847.jpg" alt="">
<figcaption>ハックスペース入り口</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title=" ハックスペースの中(このときはセッション中だったので空いてました)">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424133922.jpg" alt="">
<figcaption>ハックスペースの中(このときはセッション中だったので空いてました)</figcaption>
</figure>
福岡開催ならではの屋台スペースも用意されていました。
<p>ランチタイムには屋台ラーメンなどが振る舞われ、めちゃくちゃ混み合っていましたが、こういう地域特有の催しは RubyKaigi を盛り上げてくれるので良いですね。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="ランチ前になるとゾロゾロと人集りが">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424134029.jpg" alt="">
<figcaption>ランチ前になるとゾロゾロと人集りが</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="ランチタイムの屋台は大行列">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424134059.jpg" alt="">
<figcaption>ランチタイムの屋台は大行列</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="屋台でラーメンを食らう参加者を羨むエンジニア中畑">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424134120.jpg" alt="">
<figcaption>屋台でラーメンを食らう参加者を羨むエンジニア中畑</figcaption>
</figure>
<h1 id="メドレーブースの様子">メドレーブースの様子</h1>
<p>また、2 階のブースゾーンにはメドレーのブースも出展しました。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="今回参加したメンバー全員で">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424134208.jpg" alt="">
<figcaption>今回参加したメンバー全員で</figcaption>
</figure>
<p>ノベルティーにはメドレーロゴ付きのうちわ、ステッカー、お水、そして恒例の絆創膏を用意しました。</p>
<p>RubyKaigi 2018 でもらったメドレーの絆創膏が、子供が怪我したときに役に立ったよ〜という嬉しいお声をいただいたりもしました。</p>
<p>靴擦れや擦り傷を負ったとき、お子さんが転んだときとかに役に立つので、今度見かけた際はぜひお持ち帰りください。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="メドレーの絆創膏">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424134245.jpg" alt="">
<figcaption>メドレーの絆創膏</figcaption>
</figure>
<p>ブースではメドレーの会社説明やメドレーが提供するサービスの説明などを行いました。</p>
<p>スポンサーとして初参加だった RubyKaigi 2017 に参加したときよりもメドレーのことを知っている人が増えているなあと実感しました。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="ブースで対応するエンジニア達(中畑、稲本、橋本)">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424134320.jpg" alt="">
<figcaption>ブースで対応するエンジニア達(中畑、稲本、橋本)</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="ブースで対応するエンジニア達(中畑、稲本、橋本)">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424134405.jpg" alt="">
<figcaption>Matz さんにも来ていただきました!</figcaption>
</figure>
<h1 id="福岡の様子">福岡の様子</h1>
<p>そして福岡といえば美味しいごはんですよね。</p>
<p>1 日目の夜は<strong>RubyKaigi 2019 Official Party</strong>が開催され、中洲川端商店街を半貸切にして日本酒やうどん、ラーメンや地鶏などが振る舞われました。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="RubyKaigi 2019 Official Party の会場・中洲川端商店街の Guide Map">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424134516.jpg" alt="">
<figcaption>RubyKaigi 2019 Official Party の会場・中洲川端商店街の Guide Map</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="大樽に入って日本酒が配られてました">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424134552.jpg" alt="">
<figcaption>大樽に入って日本酒が配られてました</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="日本酒のラインナップ">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424134633.jpg" alt="">
<figcaption>日本酒のラインナップ</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="うどんを前に満面の笑みの広報・深澤">

<figcaption>うどんを前に満面の笑みの広報・深澤</figcaption>
</figure>
夜は福岡の美食を求めて街に繰り出しました。
<figure class="figure-image figure-image-fotolife mceNonEditable" title="絶品イカの活造り">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424135955.jpg" alt="">
<figcaption>絶品イカの活造り</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="定番モツ鍋">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424140028.jpg" alt="">
<figcaption>定番モツ鍋</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="出汁が最高の水炊き">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424140056.jpg" alt="">
<figcaption>出汁が最高の水炊き</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="会場近くの某有名ラーメン店のきくらげラーメン">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424140125.jpg" alt="">
<figcaption>会場近くの某有名ラーメン店のきくらげラーメン</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="あまりのうまさに昇天するエンジニア中畑">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424140152.jpg" alt="">
<figcaption>あまりのうまさに昇天するエンジニア中畑</figcaption>
</figure>
また、RubyKaigi 初参加の 2017 年から続けている神社参拝にも行ってきました。
<p>今回は警固神社(けごじんじゃ)に参拝してきました。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="宗教法人 警固神社" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fkegojinja.or.jp%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://kegojinja.or.jp/">kegojinja.or.jp</a></cite>
<iframe style="border: 0;" src="https://www.google.com/maps/embed?pb=!1m14!1m8!1m3!1d3323.6747705996254!2d130.3977272!3d33.5877926!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x354191855856aaab%3A0x1a1ab9a2c3e2cfa5!2z6K2m5Zu656We56S-!5e0!3m2!1sja!2sjp!4v1556083915959!5m2!1sja!2sjp" width="600" height="450" frameborder="0" allowfullscreen></iframe>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="警固神社入り口">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424140248.jpg" alt="">
<figcaption>警固神社入り口</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="参拝するエンジニア中畑">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190424/20190424140311.jpg" alt="">
<figcaption>参拝するエンジニア中畑</figcaption>
</figure>
<h1 id="さいごに">さいごに</h1>
<p>2017 年から 3 度目となる RubyKaigi の協賛の様子をお届けしました。</p>
<p>Breakfast Sponsor として、RubyKaigi 参加者のみなさんにご満足いただけて本当に良かったなあと思います。</p>
<p>繰り返しになりますが、Ruby と Ruby コミュニティの発展のため、今後も様々な形で貢献していければと思います。</p>
<p>来年 2020 年は長野県松本市での開催ということで、またみなさんとお会いできることを願って、レポートを締めたいと思います。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="RubyKaigi 2020" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Frubykaigi.org%2F2020" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://rubykaigi.org/2020">rubykaigi.org</a></cite>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは医療業界に存在する課題に IT を駆使して取り組んでいきたいメンバーを、デザイナー・エンジニアを中心に全職種絶賛募集中です。皆さまからのご応募お待ちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="MEDLEY DESIGN&ENGINEERING" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- RubyKaigi 2019 に朝食スポンサーとして協賛しますhttps://developer.medley.jp/entry/2019/04/12/184055https://developer.medley.jp/entry/2019/04/12/184055みなさん、こんにちは。開発本部の平木です。
2017 年から、RubyKaigi にスポンサーとして参加していましたが、今年も朝食スポンサーとして協賛することになりました!
(去年とおととしの参加レポート)
RubyKaigi 2017 ...Fri, 12 Apr 2019 09:40:55 GMT<p>みなさん、こんにちは。開発本部の平木です。</p>
<p>2017 年から、RubyKaigi にスポンサーとして参加していましたが、今年も<a href="https://rubykaigi.org/2019/sponsors">朝食スポンサー</a>として協賛することになりました!</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190412/20190412183606.png" alt="20190412183606.png">
<p>(去年とおととしの参加レポート)</p>
<p><a href="/entry/2017/09/28/120000">RubyKaigi 2017 にメドレーが Ruby Sponsor として参加しました</a></p>
<p><a href="/entry/2018/06/06/151300">Lightning Talks Sponsor として RubyKaigi 2018 に参加してきました</a></p>
<p>会場である、福岡国際会議場内の 1F にあるレストラン <a href="https://www.f-sunpalace.com/restaurant/raconter/#raconter%E2%80%8B">ラコンテ</a>さんでビュッフェ形式の朝食を楽しむことができます。</p>
<p>会期中の 4/19~20 日の 8:30 ~ 10:00 で開催しています。当日はメドレーメンバーがご案内する予定になっていますので、ぜひお気軽に話かけてください!目印として、メンバーはメドレーパーカーを着用しています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190412/20190412183045.jpg" alt="20190412183045.jpg">
<p>メニューは和洋それぞれ用意があり、福岡ならではのメニューも入っていますので、1 日の始まりにぜひおいしい朝食を食べて元気にセッションに臨んでください。</p>
<p>また、スポンサーブース内にもメドレーブースが常設されており、エンジニア 4 人も行く予定となっていますので、ぜひお立ち寄りいただいて、みなさんと交流できればと思います。</p>
<p>それでは、RubyKaigi という大きなイベントでまた皆様にお会いできるのを楽しみにしています!!</p>medley
- 電子処方箋実証事業における FHIR の活用と標準化の展望https://developer.medley.jp/entry/2019/04/02/02https://developer.medley.jp/entry/2019/04/02/02こんにちは。CLINICS 事業部の児玉です。
今回は、メドレーが 2018 年 12 月に厚生労働省から受託した「電子処方箋の本格運用に向けた実証事業」で、医療情報標準規格のFHIRを基盤とした電子処方箋管理システムを構築しましたので、そ...Tue, 02 Apr 2019 03:00:00 GMT<p>こんにちは。CLINICS 事業部の児玉です。</p>
<p>今回は、メドレーが 2018 年 12 月に厚生労働省から受託した「電子処方箋の本格運用に向けた実証事業」で、医療情報標準規格の<a href="https://www.hl7.org/fhir/">FHIR</a>を基盤とした電子処方箋管理システムを構築しましたので、その内容についてご紹介します。</p>
<h1 id="fhir-とは">FHIR とは</h1>
<p>FHIR(Fast Healthcare Interoperability Resources)とは、医療情報交換の国際標準規格である HL7(Health Level 7)の中で最も新しい規格であり、インターネットテクノロジーをベースとした、シンプルで効率的にシステム間での情報共有を可能にする「<strong>次世代の医療情報標準規格</strong>」として世界各国で注目されています。</p>
<p>HL7 は、世界に 30 以上の国際支部を有しており、日本では 1998 年に<a href="https://www.hl7.jp/">日本 HL7 協会</a>が設立されました。そして、現在流通している HL7 規格には、データ交換に利用される<a href="https://www.hl7.org/implement/standards/product_brief.cfm?product_id=185">HL7v2</a>と、医用文書の記述に利用される<a href="https://www.hl7.org/implement/standards/product_brief.cfm?product_id=7">CDA</a>(Clinical Document Architecture)が存在します。</p>
<h1 id="電子処方箋とは">電子処方箋とは</h1>
<p>電子処方箋とは、従来の紙に印刷された処方箋ではなく、医療機関と調剤薬局が電子データを用いて処方内容をやりとりする仕組みです。単純にペーパーレス化することだけが目的ではなく、患者個人の服薬履歴を電子的に管理して、重複投薬の適正化を図るといった目的もあります。</p>
<h1 id="実証事業の背景と概略">実証事業の背景と概略</h1>
<p>2016 年 3 月の法令改正で、処方箋の電子的な交付が可能となり、<a href="https://www.mhlw.go.jp/content/10800000/000342367.pdf">運用ガイドライン</a>も策定されました。しかし、法令改正から数年が経過した現在においても、実運用での課題が払拭できずに、電子処方箋の全国的な普及は進んでおりません。</p>
<figure class="figure-image figure-image-fotolife" title="ガイドラインに定められた運用フロー"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190328/20190328142501.png" alt="20190328142501.png"><figcaption>ガイドラインに定められた運用フロー(電子処方せんの運用ガイドラインより転載)</figcaption></figure>
<p>元来、処方箋には医師の記名押印、または署名が必要であると医師法で定められています。現行のガイドラインは、この規則を踏襲して設計されていますので、紙の処方箋の代替として<strong>CDA で記述された静的ファイル</strong>を必要とし、記名押印の代替として<a href="https://www.jmaca.med.or.jp/hpki/">HPKI</a>を使用した<strong>電子署名</strong>が必要となります。</p>
<p>また、クライアント端末で生成された静的ファイルが、インターネットに流通するフローとなっていますので、改ざんの検知を可能とするために<strong>電子タイムスタンプ</strong>を付与する必要があります。</p>
<p>このように複雑化された運用フローが、電子処方箋の普及を阻害する要因の 1 つであると考えられます。</p>
<p>こうした状況を踏まえ、現行のガイドラインに縛られず、円滑な運用ができる仕組みを検討するために、実際の医療機関と調剤薬局を使用したフィールド実験を実施しました。</p>
<hr>
<p><em>実証事業概略</em></p>
<ul>
<li>実施目的:電子処方箋の運用の仕組みの検討・実証・考察</li>
<li>実施期間:2019 年 2 月 4 日 - 2019 年 3 月 17 日</li>
<li>実証エリア:東京都港区</li>
<li>協力施設:2 医療機関 / 6 薬局</li>
<li>利用実績:64 件</li>
</ul>
<hr>
<h1 id="電子処方箋管理システムの概要">電子処方箋管理システムの概要</h1>
<p>以下に示すのは、今回の実証事業で開発した評価システムです。本システムは「処方箋管理システム」「医療機関システム」「薬局システム」「PHR アプリ」の 4 つのシステムから構成されています。</p>
<figure class="figure-image figure-image-fotolife" title="システム概観"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190328/20190328140004.png" alt="20190328140004.png"><figcaption>システム概観</figcaption></figure>
<hr>
<ul>
<li>
<p><strong>処方箋管理システム</strong></p>
<p>医療機関システム、薬局システム及び PHR アプリから接続され、各システムからの処理要求を受けて、処方データと調剤データの返答、作成、編集、及び削除を行う。</p>
</li>
<li>
<p><strong>医療機関システム</strong></p>
<p>診療結果としての処方データを処方箋管理システムに登録し、その結果として返される処方箋アクセスコードを患者に共有する。処方箋アクセスコードは QR コードの活用を想定し、QR コードを印字した紙又は電子データを患者に共有する。</p>
</li>
</ul>
<div style="text-align: right; font-size: 7pt;">
*「QR コード」は株式会社デンソーウェーブの登録商標です。
</div>
<ul>
<li>
<p><strong>薬局システム</strong></p>
<p>訪問してきた患者の本人確認を行った上で、処方箋アクセスコードを受け取り、処方箋管理システムにアクセスし処方データを参照する。処方後は調剤データを処方箋管理システムに格納する。</p>
</li>
<li>
<p><strong>PHR アプリ</strong></p>
<p>患者がオンライン診療やお薬手帳の機能を利用するためのアプリ。処方箋管理システムへのアクセスが許可され、医療機関システムから受け取った処方箋アクセスコードを元に、調剤結果を参照することを可能にする。</p>
</li>
</ul>
<hr>
<p>医療機関システムは、メドレーのクラウド電子カルテ「CLINICS カルテ」を利用しましたが、システム連携に関わる部分については、他の電子カルテなどでも活用できるよう、疎に結合した設計を意識しました。</p>
<p>処方箋管理システムは、前述の通り FHIR インターフェイスを基盤として構築しました。これは現行のガイドラインには記載されていない新たな試みです。</p>
<p>HPKI を利用した<strong>SSO(Single Sign On)による本人資格確認</strong>と、FHIR インターフェイスを利用した<strong>クラウドベースでのデータフロー</strong>を実現することが可能であれば、複雑化の要因となっている静的ファイル、電子署名、タイムスタンプは不要であると考えました。</p>
<h1 id="データ設計">データ設計</h1>
<p>FHIR には「リソース」と呼ばれるデータセットが定められています。リソースは、Patient(患者)、Practitioner(施術者)、Organization(組織)といった粒度で設計されており、リソース単位でのデータ交換が可能です。</p>
<p>FHIR を利用するためには、その目的に応じたリソースの選定と、リソースの下位概念であるフィールドに設定する具体的な値を定義する必要があります。</p>
<p>例えば、日本では人名を記述する際に、漢字氏名とカナ氏名を併記することが一般的ですが、グローバル・スタンダードではありません。このような国の事情に応じて、ローカライズされた記述ルールを定める必要があります。</p>
<p><em>実証事業で使用したリソース</em></p>
<table><thead><tr><th>リソース</th><th>説明</th></tr></thead><tbody><tr><td><a href="https://www.hl7.org/fhir/patient.html">Patient</a></td><td>患者</td></tr><tr><td><a href="https://www.hl7.org/fhir/practitioner.html">Practitioner</a></td><td>施術者(処方医/薬剤師)</td></tr><tr><td><a href="https://www.hl7.org/fhir/practitionerrole.html">PractitionerRole</a></td><td>施術者役割</td></tr><tr><td><a href="https://www.hl7.org/fhir/organization.html">Organization</a></td><td>組織(医療機関/調剤薬局)</td></tr><tr><td><a href="https://www.hl7.org/fhir/medicationrequest.html">MedicationRequest</a></td><td>投薬要求</td></tr><tr><td><a href="https://www.hl7.org/fhir/medicationdispense.html">MedicationDispense</a></td><td>調剤実施</td></tr><tr><td><a href="https://www.hl7.org/fhir/coverage.html">Coverage</a></td><td>保険</td></tr></tbody></table>
<h1 id="fhir-を使用した所感">FHIR を使用した所感</h1>
<p>FHIR の改版は on-Going で行われています。実証事業のための開発を始めた頃は、STU3(Standard for Trial Use 3)が公式バージョンでした。しかし、あるとき急に FHIR のホームページが接続不安定になり困っていたところ、翌日には R4(Release 4)に更新されていたということもありました。</p>
<p>このように、日進月歩の FHIR をプロダクトに組み込むには、タイミングの見極めと、アップデートに追従する「覚悟」を決めることが大切だと思います。</p>
<figure class="figure-image figure-image-fotolife" title="2018 年 12 月 27 日に R4 が Current Version になりました"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190328/20190328210514.png" alt="20190328210514.png"><figcaption>2018 年 12 月 27 日に R4 が Current Version になりました</figcaption></figure>
<p>FHIR の利点は、従来の HL7 規格と比較した<strong>実装容易性</strong>にあると考えます。REST(REpresentational State Transfer)のように、Web サービスでは一般的に普及している概念を標準として取り込んでいるため、**実装者は特別な知識を習得する必要がありません。**また、FHIR を実装するためのオープンソースライブラリが豊富に提供されており、これらを活用することによって、本当に必要な処理に注力して開発することが可能となります。</p>
<p>臨床概念モデルとしての観点では、CDA のベースとなっている<a href="https://www.hl7.org/implement/standards/rim.cfm">HL7v3-RIM</a>(Reference Information Model)と比較すると、クラス、継承のようなオブジェクト指向の概念が廃止されています。あらゆる臨床概念をモデリング可能とすることを目指した RIM と比べると、さすがに表現可能な範囲は狭まると思われますが、網羅性は実用域のレベルには達しているのではないでしょうか。</p>
<p>また、1:1 のシステム連携におけるデータ交換では、すでに稼働している v2 インターフェイスを、無理して FHIR に置き換える必要は無いと思います。用途に応じて、既存の規格と共存していくのが大切であると考えます。</p>
<h1 id="実運用へ向けての課題">実運用へ向けての課題</h1>
<p>FHIR リソースは、臨床概念としてユニバーサルに利用可能なものを中心に構成されています。保険情報のような各国の医療制度に応じて異なるものや、調剤技法に対する「一包化」や「粉砕」などの細かな指示情報に関しては、ある程度アドホックにマッピングするしかありませんでした。実装者によるマッピングのブレが生じないようにするためには、<strong>指標となるガイドラインの策定</strong>が必要になります。</p>
<p>また、リソースにマッピングする医薬品や用法のマスターコードについては、<a href="https://helics.umin.ac.jp/helicsStdList.html">厚生労働省標準マスター</a>の利用が望まれます。しかしながら、満遍なく普及しているとは言い難い状況ですので、<strong>標準マスター利用の推奨</strong>も併せてガイドラインに明記することが必要です。</p>
<p>ガイドラインは、メドレー 1 社で策定することが可能なものではなく、医療情報システムの開発に関わる多くの開発者に FHIR を利用していただき、<strong>実装方法についての議論</strong>を交わす必要があります。しかし、現時点での日本国内における FHIR の活用事例は、残念ながらほとんど見られません。</p>
<p>次にご紹介するのは、FHIR を使用した簡単なコードのサンプルと、ローカル環境で FHIR Server を簡易的に動かす方法です。実装者の方が FHIR に触れるきっかけになればと思って書きましたので、ご参考にしていただけますと幸いです。</p>
<h1 id="fhir-の実装サンプル">FHIR の実装サンプル</h1>
<p>FHIR を理解するためには、実際にコードを書いて動かしてみるのが一番だと思います。ここでは、ローカル環境で FHIR Server を起動して、リソースを登録する簡単なプログラムを書いてみました。(オープンソースの恩恵を最大限に享受しています。)</p>
<h2 id="実行環境">実行環境</h2>
<ul>
<li>macOS v10.14.3</li>
<li>Ruby v2.5.1</li>
<li>Docker v18.09.2</li>
</ul>
<h2 id="fhir-server">FHIR Server</h2>
<p>Docker コンテナで FHIR Server を起動します。簡単に動かせるように、DockerHub に公開されている<a href="https://hapifhir.io/">HAPI-FHIR</a>のコンテナイメージを利用します。</p>
<p>HAPI-FHIR とは、カナダのトロントにある医療研究機関の UHN(University Health Network)が立ち上げたプロジェクトで、FHIR のオープンソースライブラリを提供することを目的に活動しています。</p>
<div class="remark-link-card-plus__container">
<a href="https://github.com/jamesagnew/hapi-fhir" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">GitHub - jamesagnew/hapi-fhir: 🔥 HAPI FHIR - Java API for HL7 FHIR Clients and Servers</div>
<div class="remark-link-card-plus__description">🔥 HAPI FHIR - Java API for HL7 FHIR Clients and Servers - jamesagnew/hapi-fhir</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://github.githubassets.com/favicons/favicon.svg" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">github.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://opengraph.githubassets.com/23637e497bbc4bf9f589b8de34195a2cae2c275ccb7c737830aac35644715995/jamesagnew/hapi-fhir" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://hub.docker.com/r/petersonjared/hapi-fhir" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">petersonjared/hapi-fhir - Docker Image</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://hub.docker.com/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">hub.docker.com</span>
</div>
</div>
</a>
</div>
<p>Docker コンテナイメージの取得</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> docker</span><span style="color:#CE9178"> pull</span><span style="color:#CE9178"> petersonjared/hapi-fhir</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> docker</span><span style="color:#CE9178"> run</span><span style="color:#569CD6"> -d</span><span style="color:#569CD6"> -p</span><span style="color:#CE9178"> 8080:8080</span><span style="color:#569CD6"> --name=fhir</span><span style="color:#CE9178"> petersonjared/hapi-fhir:dstu3</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> docker</span><span style="color:#CE9178"> exec</span><span style="color:#569CD6"> -it</span><span style="color:#CE9178"> fhir</span><span style="color:#CE9178"> /docker-entrypoint.sh</span><span style="color:#CE9178"> java</span><span style="color:#569CD6"> -jar</span><span style="color:#CE9178"> /usr/local/jetty/start.jar</span></span></code></pre>
<p>Docker をインストールするのが面倒な人は、HAPI-FHIR がグローバルに公開している<a href="https://hapi.fhir.org/baseDstu3">テストサーバー</a>を利用することも可能です。ただし、世界中の人がテストに利用しているサーバーですので、機微な個人情報は登録しないよう注意が必要です。</p>
<h2 id="fhir-client">FHIR Client</h2>
<p>FHIR Client は、Ruby で動かします。以下の Gem を利用します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> gem</span><span style="color:#CE9178"> install</span><span style="color:#CE9178"> fhir_client</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> gem</span><span style="color:#CE9178"> install</span><span style="color:#CE9178"> fhir_models</span></span></code></pre>
<p>Ruby のコードです。Patient リソースを作成して FHIR Server に登録します。</p>
<p><em>patient.rb</em></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#569CD6">require</span><span style="color:#CE9178"> 'fhir_client'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">client</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">FHIR::Client</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">new</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"https://localhost:8080/baseDstu3"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#4EC9B0">FHIR::Model</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">client</span><span style="color:#D4D4D4"> = client</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">patient</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">FHIR::Patient</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">new</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 患者 ID</span></span>
<span class="line"><span style="color:#9CDCFE">identifier</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">FHIR::Identifier</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">new</span></span>
<span class="line"><span style="color:#D4D4D4">identifier.</span><span style="color:#DCDCAA">value</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">'12345678'</span></span>
<span class="line"><span style="color:#D4D4D4">patient.</span><span style="color:#DCDCAA">identifier</span><span style="color:#D4D4D4"> = identifier</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 患者氏名</span></span>
<span class="line"><span style="color:#9CDCFE">human_name</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">FHIR::HumanName</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">new</span></span>
<span class="line"><span style="color:#D4D4D4">human_name.</span><span style="color:#DCDCAA">family</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">'Kodama'</span></span>
<span class="line"><span style="color:#D4D4D4">human_name.</span><span style="color:#DCDCAA">given</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">'Yoshinori'</span></span>
<span class="line"><span style="color:#D4D4D4">patient.</span><span style="color:#DCDCAA">name</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">push</span><span style="color:#D4D4D4">(human_name)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 性別</span></span>
<span class="line"><span style="color:#D4D4D4">patient.</span><span style="color:#DCDCAA">gender</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">'male'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">patient.</span><span style="color:#DCDCAA">create</span></span>
<span class="line"><span style="color:#DCDCAA">puts</span><span style="color:#D4D4D4"> patient.</span><span style="color:#DCDCAA">id</span></span></code></pre>
<p>上記のコードを実行すると、FHIR Server に Patient リソースが登録されます。ここでリソースを一意に特定するリソース ID が採番されます。今回は <strong>19953</strong> が採番されました。(puts patient.id で確認)</p>
<p>このリソース ID を利用して、FHIR Server に HTTP リクエストを送ります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> curl</span><span style="color:#CE9178"> https://localhost:8080/baseDstu3/Patient/19953</span></span></code></pre>
<p>そうすると、先ほど登録したリソースが JSON 形式で取得できます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#9CDCFE"> "resourceType"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"Patient"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "id"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"19953"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "meta"</span><span style="color:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#9CDCFE"> "versionId"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"1"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "lastUpdated"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"2019-03-21T09:12:44.660+00:00"</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#9CDCFE"> "text"</span><span style="color:#D4D4D4">: {</span></span>
<span class="line"><span style="color:#9CDCFE"> "status"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"generated"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "div"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"<div xmlns=</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">https://www.w3.org/1999/xhtml</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">><div class=</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">hapiHeaderText</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">>Yoshinori <b>KODAMA </b></div><table class=</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">hapiPropertyTable</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">><tbody><tr><td>Identifier</td><td>12345678</td></tr></tbody></table></div>"</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#9CDCFE"> "identifier"</span><span style="color:#D4D4D4">: [</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "value"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"12345678"</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> ],</span></span>
<span class="line"><span style="color:#9CDCFE"> "name"</span><span style="color:#D4D4D4">: [</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "family"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"Kodama"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "given"</span><span style="color:#D4D4D4">: [</span><span style="color:#CE9178">"Yoshinori"</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> ],</span></span>
<span class="line"><span style="color:#9CDCFE"> "gender"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"male"</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>もちろん、患者 ID など利用して検索することも可能です。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> curl</span><span style="color:#CE9178"> https://localhost:8080/baseDstu3/Patient?identifier=</span><span style="color:#B5CEA8">12345678</span></span></code></pre>
<h1 id="さいごに">さいごに</h1>
<p>今回の実証事業では、FHIR のインターフェイスを活用することにより、シンプルなアーキテクチャとシームレスなフローで、迅速に処方箋管理システムの構築ができ、電子処方箋の運用評価を行うことができました。本実証事業の最終成果報告書は<a href="https://www.mhlw.go.jp/stf/denshishohousen.html">厚生労働省のサイト</a>に公開されていますので、詳細について興味のある方はこちらをご参照ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.mhlw.go.jp/stf/denshishohousen.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">電子処方箋</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=www.mhlw.go.jp" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.mhlw.go.jp</span>
</div>
</div>
</a>
</div>
<p>メドレーは、実証事業で得られた知見を可能な限りオープンにして電子処方箋の普及推進に取り組むことはもちろん、インターネットを活用したオープンな技術の普及活動に積極的に取り組み、医療機関と患者の双方にとってより良い医療の実現を目指してまいります。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>今回の電子処方箋実証事業の成果と、システム開発に活用した FHIR について共有するイベントを、以下のとおり 4 月 23 日(火)に開催します。お時間ある方はぜひご参加ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://techplay.jp/event/725549" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">techplay.jp</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=techplay.jp" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">techplay.jp</span>
</div>
</div>
</a>
</div>
<p>また、メドレーでは医療業界に存在する課題に IT を駆使して取り組んでいきたいメンバーを、デザイナー・エンジニアを中心に全職種絶賛募集中です。皆さまからのご応募お待ちしております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- フロントエンドエンジニアが UI 設計〜実装で考えていることhttps://developer.medley.jp/entry/2019/03/27/153854https://developer.medley.jp/entry/2019/03/27/153854おはこんばんちは、開発本部エンジニアの大岡です。
先日、TechLunch という社内勉強会で「フロントエンジニアが UI 設計〜実装で考えていること」という内容で発表しました。UI 設計から実装の細かい話ではなくどういう気持ちで望んでるか...Wed, 27 Mar 2019 06:38:54 GMT<p>おはこんばんちは、開発本部エンジニアの大岡です。</p>
<p>先日、TechLunch という社内勉強会で「フロントエンジニアが UI 設計〜実装で考えていること」という内容で発表しました。UI 設計から実装の細かい話ではなくどういう気持ちで望んでるかという話で、基本的なことしか書いていませんが紹介させていただければと思います。</p>
<h1 id="ui-設計">UI 設計</h1>
<p>UI 設計をデザイナーがして、それをエンジニアが実装する場合とエンジニアが単独で UI 設計から実装までする場合があります。今回はそれぞれのケースで、フロントエンドエンジニアがどういうことを考えてるかを紹介します。</p>
<p>とその前に、他人と UI について話すときに気をつけてる言葉で分かりやすくまとまっているツイートがあったので紹介します。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">自分が他の人に UI デザインの説明をするとき、あまり使わないようにしている言葉がいくつかあるので、それらについて描きました。<br>私の中では、これ系の言葉は、<br>初心者:使いがち<br>中級者:ほぼ使わない<br>上級者:たまに使ってる<br>みたいな印象があります。 <a href="https://t.co/O2URgRCVHe">pic.twitter.com/O2URgRCVHe</a></p>— シンギュラリティ (@singularity8888) <a href="https://twitter.com/singularity8888/status/1069378693259128834?ref_src=twsrc%5Etfw">December 2, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<ul>
<li>普通はそうする</li>
<li>直感的にわかる</li>
<li>使いやすい</li>
<li>わかりやすい</li>
</ul>
<p>これらの言葉は誰にとってなのか?やそれまでの経験によって意味合いが変わってきてしまうので対象を明確にしないと相手と話が噛み合わなくなることもあるので気をつけています。<strong>今回何ヶ所か使っていますが実際 UI について話すときは使わないように心がけています</strong>。</p>
<h1 id="デザイナーから画面仕様をもらう場合">デザイナーから画面仕様をもらう場合</h1>
<p>デザイナーが優秀であればあるほど、もらった画面仕様を元に早く手を動かしたくなります。ですがここはグッと堪えます。落ち着いて以下のようなことを考えてます。</p>
<ul>
<li>なぜこの課題がうまれたのか</li>
<li>解決したい課題の本質はなんなのか</li>
<li>他の解決策はないのか</li>
<li>この機能を追加してしまうことにより他に影響がでてしまわないか</li>
<li>追加ではなく他の機能を落とすことにより解決できないのか</li>
<li>そもそも必要なのか</li>
</ul>
<p>など、この段階で理解できないことはチームで話したりして解決しておきます。次に画面仕様に目を向けまた考えます。</p>
<ul>
<li>似たようなコンポーネントがあったら統一できないか</li>
<li>より使いやすくできないのか</li>
<li>操作感が他と明らかに違わないか</li>
<li>操作に対して予想した動きになっているか</li>
<li>表示されている情報に意味があるのか</li>
</ul>
<p>など、画面仕様からは理解できないことや疑問点などはデザイナーとコミュニケーションをとり齟齬がでないようにします。</p>
<p><strong>手を動かす前にある程度ユーザが解決したい課題を精度高く汲み取り落としこめる</strong>ように努力してます。</p>
<h1 id="自分で-ui-設計をする場合">自分で UI 設計をする場合</h1>
<p>「デザイナーから画面仕様をもらう場合」に書いたことをまずは考えています。</p>
<p>それらを踏まえ、<strong>すでに実装済みの UI で似たようなものがあれば似たように作ります</strong>。理由としては既にユーザが学習済みの UI のため新たに追加した機能だとしても比較的学習コストが低くなるのではないかと考えるからです。</p>
<p>似たような UI がなく新たに作らなければならないときは以下を意識しています。</p>
<ul>
<li>表示する情報の精査</li>
<li>表示する情報はリストなのか詳細表示なのか</li>
<li>表示する情報に対しての操作はどんなものがあるのか</li>
<li>操作に対してのレスポンス・納得感はどうすれば得られるのか</li>
<li>エラー表示をどうするのか</li>
<li>表示する情報が多すぎて収まり切らない場合どうするのか</li>
<li>表示する情報がない場合どうするのか</li>
<li>画面サイズ毎に適切に表示できるか</li>
<li>状態(クリックやホバーなど)を表現できてるか</li>
<li>リストの場合デフォルトの順番は適切なのか</li>
</ul>
<p>などをざっくりノートなどに書き出し詳細を詰めていっています。行き詰まったら迷わずデザイナーに相談しにいきます笑</p>
<h1 id="実装">実装</h1>
<p>細かい実装部分というよりはもっと大枠の基本的なことですが紹介します。</p>
<ul>
<li>何も考えずにコピペしない
<ul>
<li>修正が大変になるので一箇所になるべくまとめる</li>
</ul>
</li>
<li>用途にあったコンポーネントを使う
<ul>
<li>似てるからと使い回すとコンポーネント修正時に思わぬ変更が加わる可能性があるかも</li>
</ul>
</li>
<li>コンポーネント間の関係が疎になるようにする
<ul>
<li>密接な関係だと気を使い合わなくてはならず修正が大変</li>
</ul>
</li>
<li>コンポーネントを作成する際に無理に汎用性をもたせない
<ul>
<li>修正が困難になり逆に汎用性がなくなる場合が多い</li>
</ul>
</li>
<li><a href="https://redux.js.org/">Redux</a>に管理させるステートの粒度は適切か</li>
<li>なんでもかんでも Redux に状態を管理させてしまっていないか</li>
<li>無駄な再レンダリングをしていないか</li>
<li>無駄な通信をしていないか</li>
<li>非同期通信のレスポンスが遅い場合にサーバー側の処理に問題がないか</li>
<li>関数や変数は適切な命名で適切な場所に書かれているか</li>
<li>マジックナンバーを使ってしまっていないか
<ul>
<li>使ってると修正大変</li>
</ul>
</li>
<li>なるべく簡単に書けないか</li>
<li>エラー・例外時の動きは適切か</li>
</ul>
<p>など細かいことを言い出したらキリがないですが、こんなこと意識してます。</p>
<h1 id="よりよいものを届けるために">よりよいものを届けるために</h1>
<p>いろいろと書きましたが UI 設計から実装完了まではなるべく早く終わらせようと意識しています。1サイクルを</p>
<ol>
<li>UI 設計</li>
<li>実装</li>
<li>触る</li>
<li>フィードバック</li>
</ol>
<p>とした場合に、<strong>このサイクルをリリースまでに多く回すことによってより多くの気づきが得られる</strong>からです。基本的には一回作ってうまくいくことは希かと思っているからです。</p>
<p>ただ、フィードバックの段階で本当に色々な意見がでてきます。心が折れかけることやイライラすることもあるかもしれません。ですが、プロダクトとして必要なのか、今必要なのか、対象とする利用者としてなのか、ただの愚痴なのか等を見極め軸をぶらさないことが大切です。</p>
<p>フロントエンドの UI やパフォーマンス系は事業的に見ると優先度が低い場合が多い印象を受けます。優先度が上がるときは強い競合、数字的な上昇が見られなくなった時など危機感がでてきたときかなと。。。足りない機能を追加しないといけないし、そもそも人的リソースが足りないなどしょうがないことなのかなとも思っています。</p>
<p>初回リリース時に時間が足りなくても精度の高いものを作りたいという気持ちがあるため先ほどのサイクルを多く回し<strong>一人でも多くの人に触ってもらい気付ける回数を増やすことは大事</strong>なことだと思っています。</p>
<h1 id="さいごに">さいごに</h1>
<p>あくまで私個人が考えてることなのでフロントエンジニア全てにあてはまるわけではありません。出来てないこと抜けてしまう時や本質的に理解していないことも多々あると思いますが、自分はこんなことを考えているという紹介でした。最後までお読みいただきありがとうございました。</p>
<p>▼ メドレーで働くクリエイターたちのストーリーはこちら</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="MEDLEY DESIGN&ENGINEERING" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- 改めて BigQuery の Partitioned tables と戯れた話https://developer.medley.jp/entry/2019/03/15/145338https://developer.medley.jp/entry/2019/03/15/145338こんにちは、開発本部の宍戸です。先日のメドレー社内勉強会「TechLunch」で、BigQuery のPartitioned tableについて発表しましたので、その話について書きたいと思います。
なぜ今 Partitioned table...Fri, 15 Mar 2019 05:53:38 GMT<p>こんにちは、開発本部の宍戸です。先日のメドレー社内勉強会「TechLunch」で、BigQuery の<a href="https://cloud.google.com/bigquery/docs/partitioned-tables?hl=ja">Partitioned table</a>について発表しましたので、その話について書きたいと思います。</p>
<h1 id="なぜ今-partitioned-table">なぜ今 Partitioned table?</h1>
<p>ある案件でユーザーの操作ログを扱う必要があり、データ保管先に BigQuery を利用しようと考えていました。その際に、「以前は β 版だった分割テーブル、そういえば今使えるよね」という話になり色々調べてみた、というのが今回このテーマを選んだ背景です。</p>
<h1 id="なぜ分割テーブルを使おうと思ったのか">なぜ分割テーブルを使おうと思ったのか</h1>
<p>ユーザーの行動/操作ログの確認については API サーバーのアクセスログで、ある程度その目的を果たすことができていました。</p>
<p>しかし、API のアクセスログですと今回対象にしたい”ユーザーの操作”以外にも多くの操作ログ(通知の取得ログなどをはじめとする各種データ参照など)を抱えてしまっているため、必要な情報を抽出するためには無駄が発生してしまう状態でした。無駄なスキャンで無駄なコストが発生するのは嫌ですよね。</p>
<p>ということで目的に即したデータに限定して保存/閲覧できるように、行動/操作ログは新しく Partitioned table に保存することにしました。</p>
<p>また、新しいテーブルに保存する対象となる操作に関しては、これまで保存していた過去分のアクセスログのデータも新しいテーブルへ移行したいというニーズもあり、既存のデータを取得し加工をしたうえでデータを移行するというタスクも行いました。</p>
<h1 id="分割テーブルの各種方法について">分割テーブルの各種方法について</h1>
<p>まずひとくちに「分割テーブル(Partitioned table)」と言っても、BigQuery ではいくつかの実現方法があります。</p>
<ul>
<li>「取り込み時間で分割」されたテーブル</li>
<li>「特定のカラムに基づいて分割」されたテーブル</li>
<li>「時間ベースの命名方法を使用して分割」したテーブル</li>
</ul>
<p>前述のとおり今回は過去データの移行なども同時に行いたい背景があったため「取り込み時間で分割」されたテーブルではなく、「特定のカラムに基づいて分割」されたテーブルを利用するようにしました。こちらのテーブルは分割するための列として DATE 型または TIMESTAMP 型を指定することで利用することができます。</p>
<p>こうして作られた分割テーブルは、パーティションと呼ばれるセグメントに内部的にさらに分割されており、参照するパーティションを限定することで、効率の良いアクセス(スキャン対象データ量の限定、高速化)が可能になります。</p>
<p>注意点として<a href="https://cloud.google.com/bigquery/docs/querying-partitioned-tables#querying_partitioned_tables_2">公式ドキュメント</a>にもありますが、クエリの記述方法によっては期待したパーティションの絞り込みがされないことがあります。また分割テーブルへのクエリにはレガシー SQL を利用することができないため、標準 SQL を利用する必要があります。</p>
<h1 id="クエリキャッシュ">クエリキャッシュ</h1>
<p>分割テーブルを使うときに気になるコストについてですが、クエリキャッシュの仕組みを利用することでコスト削減が見込めます。そこで今回利用する分割テーブルで、過去のパーティションに対するアクセスはクエリキャッシュが効くのかどうか確認してみました。</p>
<p>そもそも、クエリキャッシュの仕組み上「対象のテーブルがストリーミングバッファを添付されている状態ではクエリの結果はキャッシュに保存されない」という制約(例外)があります。</p>
<p>今回の私達の用途ですと断続的に書き込みが発生する状況になるため、テーブルがストリーミングバッファを持たなくなる状況というのは発生しづらいです。この状態ではフェッチしたいデータ(パーティション)がいかに過去のものであってもテーブルは一つであるため、前述の制約が残り続けてしまいます。</p>
<p>そのため残念ながらクエリキャッシュが有効になるケースは稀そうだなという結論に至りました。(ストリーミングバッファが付いているかどうかは、WebUI のテーブル情報の詳細タブから確認できます)</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190315/20190315130255.png" alt="f:id:medley_inc:20190315130255p:plain" title="f:id:medley_inc:20190315130255p:plain"></p>
<h1 id="データ移行">データ移行</h1>
<p>前述の要件の中に「過去のアクセスログから一部データを新しいテーブルに移行したい」という話がありました。こちらの作業内容としては以下のようなものになります。</p>
<ul>
<li>「access_yyyymm」的なテーブルから、移行用のデータを取得</li>
<li>必要なデータを付与</li>
<li>移行先のテーブルにデータを insert</li>
</ul>
<p>とてもシンプルですね。データが少なければ適当なスクリプトで簡単に移行できそうです。</p>
<h1 id="やってみた">やってみた</h1>
<p>まずは試しに <a href="https://github.com/googleapis/google-cloud-ruby">google-cloud-ruby</a> を使って、ストリーミングインサートを利用した移行プログラムを作って、実行してみたところ…</p>
<p>「You can only stream to date range within 7 days in the past and 3 days in the future relative to the current date.」</p>
<p>と、怒られてしまいました。</p>
<p>分割テーブルの制限としてストリーミングインサートは、過去7日までしか書き込むことができないようでした。・・・と!発表当時は公式ドキュメントにも書いてあったと思ったのですが、改めて<a href="https://cloud.google.com/bigquery/docs/partitioned-tables?hl=ja">公式ドキュメント(パーティショニング オプションの比較)</a>見てみると、過去1年まで扱えるようになっている!?ようです。この辺りは定期的にアップデートされているようなので、実際に作業する前にきちんと公式情報を確認しておく必要がありそうです。</p>
<h1 id="最終的に">最終的に</h1>
<p>前述のようなストリーミングに関する話もあり、また移行対象データの量もかなり多かった為、ストリーミングの方式は諦めて、GCS にインポート用のファイルを用意してそちらからインポートする形にし、無事移行することができました。</p>
<p>作業当初はデータ移行を行う手段として Embulk を利用することも検討していました。<a href="https://www.embulk.org/docs/">Embulk</a>はさまざまなストレージ、データベース、NoSQL、クラウドサービス間のデータ転送を支援してくれる並列バルクデータローダーです。しかし今回は簡易的な方法で移行できそう、との思惑から利用を見送っていました。</p>
<p>あとから気になったので、Embulk のプラグインである<a href="https://github.com/embulk/embulk-output-bigquery">embulk-output-bigquery</a>を調べてみると、やはりストリーミングインサートは実装しておらず、また GCS 経由でのデータインポートができるなど、結果的には独自手法でやったことはまさにこのプラグインが提供してくれる機能そのものでした。こちらを最初から利用していればよかったかも…という気持ちで今はいっぱいです ( ꒪⌓꒪)</p>
<h1 id="まとめ">まとめ</h1>
<p>今更ながら BigQuery の Partitioned table 周りについて調べてみたこと、やってみたことを発表しました。なんとなく知ってるような気がする、という感じだったコスト周り、分割テーブル、クエリキャッシュなどの項目に対する理解を私自身少し深めることができたと思います。また、弊社では色々な場面で BigQuery を利用しているため、このネタが他チームでの今後の活用に何か役に立てば嬉しいなと思います。</p>
<p>今回のデータ移行を行った後あたりに<a href="https://cloud.google.com/bigquery/docs/clustered-tables">Clustered Table(beta)</a>のことを知りましたが、こちらも対応することでさらにスキャン量の削減、高速化が期待できそうです。</p>
<p>また、TechLunch での発表後、今回のような用途に対して、先日公開された <a href="https://aws.amazon.com/jp/qldb/">Amazon QLDB</a> あたりも使えるのでは?と同僚から教えてもらいました。監査機能などを目的としたログデータについては、QLDB はより適した用途のように思いますので、こちらも今後検証していきたいと思います。</p>medley
- JAWS DAYS 2019 にメドレーがランチサポーターとして協賛しましたhttps://developer.medley.jp/entry/2019/03/01/120049https://developer.medley.jp/entry/2019/03/01/120049こんにちは。開発本部エンジニアの平木です。
去る 2019/02/23(土)に開催された JAWS-UG(AWS User Group – Japan)主催の全国規模での交流イベントであるJAWS DAYS 2019にメドレーが ランチサ...Fri, 01 Mar 2019 03:00:49 GMT<p>こんにちは。開発本部エンジニアの平木です。</p>
<p>去る 2019/02/23(土)に開催された JAWS-UG(AWS User Group – Japan)主催の全国規模での交流イベントである<a href="https://jawsdays2019.jaws-ug.jp/">JAWS DAYS 2019</a>にメドレーが<strong> ランチサポーター</strong>として協賛させていただきましたので、当日の様子などをお伝えしていきます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190228/20190228122657.jpg" alt="20190228122657.jpg">
<h1 id="会場の様子">会場の様子</h1>
<p>自分はこちらのイベントに初参加だったのですが、来場者数がかなり多い!ということに圧倒されました。会場の<a href="https://messe.toc.co.jp/">TOC 五反田メッセ</a>はイベント会場としては大分広い方でしたが、セッションの合間などでは移動するのも中々大変な位でした。日本全国から AWS ユーザが集まるイベントの規模の大きさが伺えました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190228/20190228122724.jpg" alt="20190228122724.jpg">
<p><a href="https://jawsdays2019.jaws-ug.jp/archives/2597/">公式発表</a>で 1,900 人の来場だったということで、歴史があるユーザグループの凄さを実感しました。このため、大体のセッションも満席か立ち見が出ているのが印象的でした。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190228/20190228122752.jpg" alt="20190228122752.jpg">
<p>セッションもバラエティに富んだ<a href="https://jawsdays2019.jaws-ug.jp/timetable/">ラインナップ</a>で見たいものが若干カブってしまっていた位で、大変有意義なものでした。</p>
<p>またこちらは、アマゾンウェブサービスジャパン株式会社さんが後援しているイベントということで、<a href="https://twitter.com/jawsdays">JAWS-UG 公式 Twitter</a>に負けず劣らず、<a href="https://twitter.com/awscloud_jp">アマゾンウェブサービス公式 Twitter</a>でイベントの様子を Tweet していたのは、さすがだなと思わされました。</p>
<h1 id="メドレーのスポンサー活動">メドレーのスポンサー活動</h1>
<p>さて、そんな活気に包まれていた JAWS DAYS 2019 での弊社のスポンサーとしての取り組みについてですが、今回は<strong> ランチサポーター</strong>として<a href="https://jawsdays2019.jaws-ug.jp/supporter/">協賛させていただきました</a>。</p>
<h2 id="企業サポーターブース">企業サポーターブース</h2>
<p>まずは企業サポーターブースで弊社のエンジニア・デザイナー採用パンフレットと、ノベルティとしてロゴ入りの絆創膏を設置させてもらいました!この絆創膏ですが、十分な数かと思っていたのですが、おかげさまで朝にセッティングしたところお昼までには無くなってしまいました。もっと持っていけば良かったと反省しています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190228/20190228122821.jpg" alt="20190228122821.jpg">
<p>パンフレットを置かせていただいていたのは、会場内で 2 番目くらいに大きい場所でしたがこちらも各サポーター企業さんのブースや同人誌即売コーナーなどで人が溢れんばかりでした。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190228/20190228122855.jpg" alt="20190228122855.jpg">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190228/20190228122904.jpg" alt="20190228122904.jpg">
<h2 id="ランチセッション">ランチセッション</h2>
<p>そして、ランチサポーターとしてランチセッションで弊社の田中が<a href="https://jawsdays2019.jaws-ug.jp/session/1000/">AWS マネージドサービスをフル活用して医療系システムを構築・運用するためのノウハウ</a>と題したセッションでお話させていただきました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190228/20190228123004.jpg" alt="20190228123004.jpg">
<p>メドレーで運営しているサービスである CLINICS カルテを AWS を使って、国で定められたガイドラインにきちんと準拠しながら構築・運用するために今まで行なったことを中心に、AWS の各サービスをどのように使っているか?というお話をさせていただきました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190228/20190228123035.jpg" alt="20190228123035.jpg">
<p>詳細としては</p>
<ul>
<li>医療情報システムを構成する際に準拠しなければならない 3 省 3 ガイドラインの抜粋</li>
<li>CLINICS カルテでガイドラインがシステム構成に影響を与えた部分
<ul>
<li>日本国内法の適用が及ぶ場所へのシステムの設置</li>
<li>ユーザである医療機関の使用するアプリ側で TLS クライアント認証を実施</li>
<li>ユーザアカウント毎の権限管理と各種操作ログの管理</li>
</ul>
</li>
<li>AWS 東京リージョンへの移行や、システム構成を AWS の各サービス(<a href="https://aws.amazon.com/jp/ecs/features/">ECS</a>や<a href="https://aws.amazon.com/jp/fargate/">fargate</a>、<a href="https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/network/introduction.html">NLB</a>など)を使いながら TLS クライアントに対応していった話
セキュリティを強固にするために AWS サービス群をどのように使用しているかの話
<ul>
<li><a href="https://aws.amazon.com/jp/cloudtrail/">AWS CloudTrail</a></li>
<li><a href="https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/flow-logs.html">Flow logs</a></li>
<li><a href="https://aws.amazon.com/jp/guardduty/">Amazon GuardDuty</a></li>
<li><a href="https://aws.amazon.com/jp/config/">AWS Config</a></li>
<li><a href="https://aws.amazon.com/jp/systems-manager/#Session_Manager">Session Manager</a></li>
</ul>
</li>
</ul>
<p>といった内容の発表でした。
来場していただいた方々の関心も高いようで、写真を撮りながら聞いていただいて大変ありがたかったです。セッションが終了したあとも参加していただいた方々に個別に話を聞きに来ていただいたりと、情報交換という意味でも大変有意義なセッションとなりました。</p>
<figure class="figure-image figure-image-fotolife" title="セッションが終わって一安心した田中"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190228/20190228123114.jpg" alt="20190228123114.jpg"><figcaption>セッションが終わって一安心した田中</figcaption></figure>
<h1 id="最後に">最後に</h1>
<p>イベントを運営していただいたスタッフの方々、そして参加者の皆さまありがとうございました!</p>
<p>メドレーではこの他にもエンジニア・デザイナー向けのイベントのスポンサーを積極的にしております。スポンサーを募集しているイベント主催者の方などいらっしゃいましたら、お気軽に<a href="https://www.medley.jp/contact/">お問い合わせ</a>いただければと思います。</p>medley
- デザイナーがブラックボックスを作らないためにできることhttps://developer.medley.jp/entry/2019/02/22/151724https://developer.medley.jp/entry/2019/02/22/151724
こんにちは。昨年末にクリエイターズストーリーが公開され顔面が白日のもとにさらされ『イケメンじゃなくね?イケメンつーか犯罪者顔じゃん!』と影口たたかれてないか不安な日々をおくる開発本部のガラスのハートことイケメン担当デザイナーの小山です。
...Fri, 22 Feb 2019 06:17:24 GMT<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190221/20190221150235.png" alt="f:id:medley_inc:20190221150235p:plain" title="f:id:medley_inc:20190221150235p:plain"></p>
<p>こんにちは。昨年末に<a href="https://www.medley.jp/recruit/creative.html">クリエイターズストーリー</a>が公開され顔面が白日のもとにさらされ『イケメンじゃなくね?イケメンつーか犯罪者顔じゃん!』と影口たたかれてないか不安な日々をおくる開発本部のガラスのハートことイケメン担当デザイナーの小山です。</p>
<p>さて今回は先月末におこなった社内の勉強会の内容をご紹介させていただきます。</p>
<p>デザインのプロセスは、時代とともに明らかになりつつあり、個人主導より参加型のデザインワークが主流になりつつあります。例えばサービスデザインはステークホルダーと課題を協議しながら解決策を見出していきます。視点は異なりますが、デザインスプリントも各参加者の視点や思考を総動員して短期でプロダクト(解決策)を作り出す手法です。</p>
<p>関係者が参加したデザインプロセスにより出来たデザインを製品レベルへ品質を高めるため、デザイナーの手によってブラッシュアップされていきます。この段階から本格的にデザイナーの感覚由来の思考が組み込まれ、プロダクトが洗練されていきます。</p>
<p>ですが、思考が組み込まれると、周囲とは別世界の領域として扱われている感覚になることがあります。どういう思考プロセスを経て、デザインされたのか周囲は触れることができません。それゆえに、周囲はデザインにおいての思考プロセスはデザイナー頼みになりやすく、作業依頼の最後の決まり文句として『いい感じに!』という言葉を掛けがちです。</p>
<p>こうした周囲が踏み込みづらい距離感が生まれてしまうのは仕方のないことだと思っています。</p>
<p>原因は、デザイナーの感覚由来の思考プロセスが共有されない状態=ブラックボックス化にあります。またデザイナーが自発的にブラックボックスをオープンにして、周囲の目に留まる環境を用意できていない点も原因としてあります。</p>
<p>目に留まる環境ではないため、周囲はアウトプットだけしか注目できず『いい感じに!』以外の言葉を掛けようがありません。</p>
<p>デザイナーは、色や形を論理的な思考と感情的な思考の掛け合わせでアウトプットします。目では見えにくいプロセスに突入するので、周囲からは抽象的で曖昧な領域と思われがちです。</p>
<p>今回は、これまでの経験の中で最もその『曖昧な領域と思われがち』だと感じている<strong>余白</strong>について、デザイナーの視点でご紹介*し、ブラックボックスの中身を少しお見せしたいと思います。</p>
<p>*個人的見解なので世のデザイナー全員に当てはまる訳ではないことをご理解いただければ幸いです。</p>
<h1 id="余白について">余白について</h1>
<p>みなさん、余白の言葉の意味はご存知ですか? 辞書では “文字などに書いた紙面の何も記されないままで残っている部分”と記されています。字面としては言い得ているのですが、デザイナー視点ではこれが全てではありません。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/keisukekoyama/20190221/20190221220602.png" alt="f:id:keisukekoyama:20190221220602p:plain" title="f:id:keisukekoyama:20190221220602p:plain"></p>
<p>もちろん”何も記されていない”が余白の前提なのですが、なぜそれが必要か?が重要です。</p>
<p>まずは余白の機能から説明します。余白には最も伝えたいメッセージを視覚的に浮き彫らせ、理解を促す効果があります。多種多様で氾濫した情報から希望したものを抜き出すプロセスを、見ている人に強いるのではなく、デザインが肩代わりします。</p>
<p>昨今の日本のデザインは、見ている人の情報に対しての許容量が増え、膨大な量を詰め込んでも理解してもらえる場面が多々あります。</p>
<p>ただスタミナが必要です。スポーツをするのと同じように情報に触れるだけで人はスタミナを消費します。そして、人間であればスタミナには限りがあります。</p>
<p>デザインは、その限りあるスタミナを情報の探索に使うのではなく、本来の目的に沿った行動に運用できるような仕組みに整備します。その手段の1つとして余白があります。</p>
<p> </p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/keisukekoyama/20190221/20190221220641.png" alt="f:id:keisukekoyama:20190221220641p:plain" title="f:id:keisukekoyama:20190221220641p:plain"></p>
<p><em>photo by <a href="https://unsplash.com/photos/csq3MV5pPEk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Guillermo Pérez</a></em></p>
<p>余白は目的ではなく情報を届けるための1つ手段です。</p>
<p>届けたい情報がどういう文脈であるべきなのか、そことセットで考えなくては余白や目的が破綻します。このプロセスが抜けおちた状態でプロジェクトが成功してしまうとブラックボックス化が進みます。</p>
<p>そうすると周囲の参加が難しい状況になり、『なんだかわからないけど良いだろう…』という状況に陥ります。いつまで経ってもデザインの思考プロセスは理解されないままの状態が続くでしょう。そのために余白を意味あるものとして文脈が必要となります。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/keisukekoyama/20190221/20190221220659.png" alt="f:id:keisukekoyama:20190221220659p:plain" title="f:id:keisukekoyama:20190221220659p:plain"><em>Apple Store 表参道店 店内 Photo by <a href="https://www.flickr.com/photos/wongwt/">wongwt</a></em></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/keisukekoyama/20190221/20190221220716.png" alt="f:id:keisukekoyama:20190221220716p:plain" title="f:id:keisukekoyama:20190221220716p:plain"></p>
<p><em>ヨドバシカメラの店内 Photo by <a href="https://www.flickr.com/photos/chesterbr/">chesterbr</a></em></p>
<p>文脈は明快であるべきです。その例として AppleStore とヨドバシカメラがあります。AppleStore は電化製品を扱う店舗の部類としては、延べ床面積に対しての商品数が極端に少ないお店。電化製品としてではなく、利用者の人生に大きな影響を与える重要なプロダクトになり得るものを提供しているプライドが伺えます。高級ブランドに近い売りかたですね。</p>
<p>ヨドバシカメラは様々なメーカーの商品を一度に、そして大量に伝達するために、より多くの商品を陳列します。世の中には商品が多くあり、その全てをありのままに伝達しスタッフのアドバイスを受けながら最適解を導き出す戦略です。</p>
<p>この2つの店舗の場合、それぞれ余白はどうあるべきなのかその機能や意味が見出せます。</p>
<p>AppleStore は通路を広くとり、商品を最も強調させるために机以外のノイズを限りなく削ぎ落とし、空間デザインにおいても余白を有効活用しています。物理的な余白を意図的に作ることで、商品に人の視線や意識が集まりやすくなり、より魅力が伝わる設計がなされています。</p>
<p>一方、ヨドバシカメラは様々なメーカーの商品を多く陳列することで横断して比較でき、自分にあった商品を見つけられる購入体験を実現します。そのために狭いスペースの中に如何に商品を詰め込められるかを念頭に設計しているように見えます。そこに余白はどこにもなく、情報の洪水が押し寄せているように感じられます。</p>
<p>ですが、余白はしっかりあります。それは人の頭の中です。</p>
<p>ヨドバシカメラは種別ごとに商品をしっかりまとめ、近しい情報であれば同じ見栄えのポップで整理し、遠くからでもどこにあるか商品がわかりやすいように陳列の技術を駆使し、来店する人のスタミナの消費量を軽減しています。</p>
<p>情報の整理をすることで、探す労力を利用者に強いることなく、無意識のうちに理解できるスイッチが入るように設計しています。ヨドバシカメラの場合は物理的な余白をとるのではなく、意識下に余白を作り出している高度な設計をしています。</p>
<p>文脈を設計し伝えるべき情報を効率良く届けるために、余白は様々な手段があります。2つの事例をあげましたが、ここには物理的な余白、そして意識的な余白があります。</p>
<p>主に UI デザインする個人的な意見ですが、余白の真の意味は、利用する側の頭の中の情報を整理し、スタミナ消費を軽減し意思決定のアクションを促せる『余地』を作ることにあると思っています。</p>
<p>と、自分なりの余白の考えをお話しました。普段の業務の中でデザイナーの立場として、こうした話をする機会はなかなかありません。しかし考え話すことでデザインに周囲が参加できる素地を作らなければ、いつまで経っても、「いい感じにつくって」の状態から逃れられません。</p>
<p>デザイナーの役目として作ることは大前提。しかし、単に手を動かして作るだけでは意図を理解されにくいため、対話を通して思考プロセスを共有しないとその後の改善はその場限りになります。これはデザインに限りません。</p>
<p>事業編成による人事異動や事業規模の拡大など大きな変化のなかでも作り続けれるようにするために、余すところなく言語化し共有することが大事だと思います。</p>
<p>メドレーには凡事徹底9箇条という仕事の質を高める指標があり、全社員がそれを意識しています。その指標の1つに『ドキュメントドリブン』というものがあります。全てを記録し共有できる状態にし無駄な取り組みをしないというものです。またドキュメントドリブンやドキュメントそのものの意味は、次の人にバトンを渡すことでもあると思っています。</p>
<p>言語化されたデザインの思考プロセスは、デザイナー以外にバトンとして次に繋ぐことで、本当にプロダクトが良いものになるのか、検証と改善を継続できるようにする道しるべにもなります。</p>
<p>幸運にもブラックボックスを開けて周囲を巻き込む術はたくさんあります。冒頭の手法以外にもライブデザイニングという手法があります。これはデザイナーのデザイン業務や思考プロセスを公開しながら進めていくものです。詳しくは
<a href="https://note.mu/norinity1103/n/naf3688df2ba0">こちら</a>。</p>
<p>弊社のデザイナーはプロダクト毎に、一人専任でデザインを任されています。それだけに重要な仕事を任される面白さはあるものの、デザイナーの感覚由来の思考プロセスで解決策が生まれやすい状況とも言えます。</p>
<p>デザインはデザイナーだけのものではないので、チームならチームに組織なら組織に思考のプロセスを明らかにすることで、新しい発見や解決策が生み出せるかもしれません。その機会をデザイナーから作り出し、より良い事業推進に貢献できたらと思います。</p>
<p>最後まで読んでいただきありがとうございます。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは医療業界に存在する課題に IT を駆使して取り組んでいきたいデザイナー・エンジニアを募集中です。</p>
<p>皆さまからのご応募お待ちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="MEDLEY DESIGN&ENGINEERING" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- 2019 年、今年も開発本部キックオフ合宿に行ってきました!@河口湖https://developer.medley.jp/entry/2019/02/08/135427https://developer.medley.jp/entry/2019/02/08/135427こんにちは、開発本部の日下です。
インターンを経て新卒としてメドレーに入り、エンジニアとしては 2 年目となりました。
現在は医療介護の求人サイト「ジョブメドレー」の開発をしております。
先日、恒例となった年始の開発本部キックオフ合宿を河口...Fri, 08 Feb 2019 04:54:27 GMT<p>こんにちは、開発本部の日下です。</p>
<p>インターンを経て新卒としてメドレーに入り、エンジニアとしては 2 年目となりました。</p>
<p>現在は医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」の開発をしております。</p>
<p>先日、恒例となった年始の開発本部キックオフ合宿を河口湖で行ってきましたので、詳細をレポートします。 </p>
<h1 id="キックオフを年始に行う意義">キックオフを年始に行う意義</h1>
<p>メドレーの開発本部では毎年年始に開発本部全体でキックオフ合宿を行っています。</p>
<p>合宿を年始に実施する意義は<a href="/entry/2018/02/09/130000">昨年もまとめ</a>ましたが、再掲すると次の二つになります。</p>
<h2 id="親睦を深める">親睦を深める</h2>
<p>一つ目はチームビルディングにあります。</p>
<p>合宿ではアクティビティやバーベキュー、キックオフプレゼンなど、みんなで一つの場所で同じ時間を共有するイベントを行っています。</p>
<p>同じ時間を共有することで、より一体感をもち、連携の取りやすい関係性を醸成することを目的としています。</p>
<p>実際、より長い時間を共有することで、普段知ることのできない仲間の一面を見ることができるなど、よりお互いの理解を深める事ができる時間が過ごせます。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208133854.jpg" alt="f:id:medley_inc:20190208133854j:plain" title="f:id:medley_inc:20190208133854j:plain"></p>
<h2 id="今年の方向性を共有する">今年の方向性を共有する</h2>
<p>二つ目は今年一年の開発本部としての方向性の共有にあります。</p>
<p>普段開発していると、全体のことを考える時間はなかなか取りづらいかと思います。</p>
<p>そこでメドレーでは普段の業務から一旦離れた場所で、開発部全体としてどういった動き方をしていくのかという認識を作る機会を作っています。</p>
<p>これが合宿を年始に行う理由にもなっています。</p>
<h1 id="合宿先アクティビティの紹介">合宿先・アクティビティの紹介</h1>
<h2 id="合宿先の紹介">合宿先の紹介</h2>
<p>合宿先は昨年と同じ<a href="https://www.c-ban.com/">河口湖カントリーコテージ ban</a>さんにお世話になることにしました。</p>
<p>昨年よりメンバーが増えたため、昨年より少し大きなコテージを借りての実施となりました。</p>
<p>コテージも夜景も美しく、とても満足のいく環境でした。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208133927.jpg" alt="f:id:medley_inc:20190208133927j:plain" title="f:id:medley_inc:20190208133927j:plain"></p>
<h2 id="ワカサギ釣り">ワカサギ釣り</h2>
<p>合宿ではリフレッシュを兼ねたアクティビティを行っており、今年はワカサギ釣りでした。</p>
<p>ワカサギ釣りと言われると氷に穴を開けて糸を垂らすイメージが強く、極寒の中で行うのではと思われる方もいるかもしれません。</p>
<p>今回お世話になった<a href="https://www.mfi.or.jp/bass/wakasagi.html">マリンハウス momo さん</a>のプランでは、提供されたドーム型の船の中で釣ることができたため非常に快適でした。</p>
<p>(<a href="https://info.medley.jp/entry/2019/01/25/194253">先日の週間メドレー</a>で着ていたライフジャケットは、このドーム型の船に乗るためのアイテムでした。)</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208134010.jpg" alt="f:id:medley_inc:20190208134010j:plain" title="f:id:medley_inc:20190208134010j:plain"></p>
<p>ワカサギは群れで移動する魚らしく、ちょうど群れが来たときはまさにフィーバータイム。釣り糸を垂れるとすぐにかかるので、とても楽しめました。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="5 匹一気に釣り上げ喜ぶ CTO">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208134024.jpg " alt="f:id:medley_inc:20190208134024j:plain">
<figcaption>5 匹一気に釣り上げ喜ぶ CTO</figcaption>
</figure>
<p>釣りが得意なメンバーはバケツ満杯に釣れるなど、釣果は上々。当初釣れた数を数える予定でしたが、あまりにも多すぎたため途中で数えるのをやめてしましました。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="袋いっぱいのワカサギ。多すぎて 2 袋に分けたうちの一つです。">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208134051.jpg " alt="f:id:medley_inc:20190208134051j:plain">
<figcaption>袋いっぱいのワカサギ。多すぎて 2 袋に分けたうちの一つです。</figcaption>
</figure>
<p>釣れた魚は合宿先に持ち帰り、唐揚げにしたり串焼きにしたりと、食事面でも楽しめるとても良いアクティビティでした。</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="一気に焼くために串に刺されるワカサギ">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208134120.jpg " alt="f:id:medley_inc:20190208134120j:plain">
<figcaption>一気に焼くために串に刺されるワカサギ</figcaption>
</figure>
<p>くさみもなく、とても美味しかったです。</p>
<h1 id="2019-年の開発本部キックオフ">2019 年の開発本部キックオフ!</h1>
<p>アクティビティのあとはコテージに移り、お風呂やバーベキューの準備をしながらお酒を少し入れたタイミングで、2019 年キックオフプレゼンを CTO ・平山が行いました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208134432.jpg" alt="f:id:medley_inc:20190208134432j:plain" title="f:id:medley_inc:20190208134432j:plain"></p>
<p>まずは昨年の振り返りから。昨年も各プロダクト大きな変化がありました。</p>
<p>JobMedley はユーザ数が順調に伸び、<a href="https://job-medley.com/release/44/">テレビで CM をするほどの規模に成長</a>、<a href="https://clinics.medley.life/karte%E2%80%8B">CLINICS カルテ</a>のリリース、<a href="https://www.kaigonohonne.com/">介護のほんね</a>のリニューアル、<a href="https://medley.life/">MEDLEY</a>は医師向け機能の強化をするなど、プロダクトを大きく成長させました。</p>
<p>また会社としては、医療ヘルスケア分野における技術のオープン化、および情報活用を推進すること目的とした<a href="https://drive.medley.jp">MEDLEY DRIVE</a>という大型プロジェクトを開始しました。</p>
<p>他にもセキュリティ強化を目的として<a href="/entry/2019/02/01/172457">ISMS と ISMS クラウドを取得</a>するなど、攻める一方で守りの面も強化された一年でした。</p>
<p>次に開発本部の 2019 年について。</p>
<p>目指すべき未来を示しつつ、なぜ?どうやって?といった基本的な考え方を共有することで、メンバーの中に納得感ができたキックオフになりました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208134505.jpg" alt="f:id:medley_inc:20190208134505j:plain" title="f:id:medley_inc:20190208134505j:plain"></p>
<p>個人的には昨年から関わるプロダクトが増えたため、これまで肌で感じてきたプロダクトの成長を振り返ることができました。</p>
<p>また今年はどういったことができるか考える良い時間になりました。</p>
<p>平山のプロダクト・事業・会社に対する熱い思いは<a href="https://toppa.medley.jp/">メドレー平山の中央突破</a>にも詳細に書かれていますので、ぜひご覧ください。</p>
<h1 id="バーベキュー">バーベキュー</h1>
<p>恒例のキックオフ後のバーベキューもみんなでワイワイしながらの実施となりました。</p>
<p>今年は人数が昨年同月比 1.5 倍ほどになったのもあり、昨年より賑やかな会となりました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208134558.jpg" alt="f:id:medley_inc:20190208134558j:plain" title="f:id:medley_inc:20190208134558j:plain"></p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="今年は自前の燻製器を運び込んだ猛者も">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208134604.jpg " alt="f:id:medley_inc:20190208134604j:plain">
<figcaption>今年は自前の燻製器を運び込んだ猛者も</figcaption>
</figure>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="恒例となっているバーベキュー">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208134610.jpg " alt="f:id:medley_inc:20190208134610j:plain">
<figcaption>恒例となっているバーベキュー</figcaption>
</figure>
<p>食べて飲んで寛いで、プロダクトや組織への思いなどお酒を交えつつ談笑し、夜遅くまで楽しい時間を過ごしました。</p>
<h1 id="まとめと感想">まとめと感想</h1>
<p>メドレー開発本部合宿の様子はいかがだっだでしょうか?少しでもその様子が伝わっていれば幸いです。</p>
<p>今年の合宿も、メンバー同士が物理的に近い状態で同じ体験をする時間を通じ、相互理解を深めたり共通認識を持つことのできる良い機会となりました。</p>
<p>昨年よりいろいろな人が増えたおかげで、バラエティの富んだ時間を過ごすことができたのは良い発見でした。</p>
<p>また人が増えたものの雰囲気としては昨年と変わらず進められたことは、組織としてのメドレーらしさのようなものが醸成されている証ではないかと感じています。</p>
<p>個人的にもプロダクト的にも昨年は一昨年に比べて変化の激しく楽しい年だったように思います。</p>
<p>今年はより変化のある一年になりそうということもわかり、今から期待に胸を膨らませています。</p>
<p>改めてこういった時間を開発本部としてとるタイミングがあるのは良いことだと、再度認識することのできた時間となりました。</p>
<h1 id="最後に">最後に</h1>
<p>最後に、今回の合宿の企画運営を行って頂いた新居さん、忙しい中本当にありがとうございました!</p>
<figure class="figure-image figure-image-fotolife mceNonEditable" title="また来年も合宿しましょう!">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190208/20190208134755.jpg" alt="f:id:medley_inc:20190208134755j:plain">
<figcaption>また来年も合宿しましょう!</figcaption>
</figure>
<p>メドレーでは、このようなチームで新しい医療体験を提供するプロダクトを一緒に創るディレクターやエンジニア、デザイナーを募集しています。</p>
<p>ご興味ある方は、お気軽に話を聞きにお越しください!!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="CREATOR'S STORY | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fteam%2Fcreator-story.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/team/creator-story.html">www.medley.jp</a></cite>medley
- 全社で本気になってリーンに ISMS の仕組みをつくった話https://developer.medley.jp/entry/2019/02/01/172457https://developer.medley.jp/entry/2019/02/01/172457こんにちは、コーポレートエンジニアの兼松です。
今回は、メドレーがリーンな仕組みで ISMS 認証を取得したので、その過程について、弊社ならではの工夫したポイントを交えてご紹介します。
はじめに
以下のニュースリリースのとおり、情報セキュリ...Fri, 01 Feb 2019 08:24:57 GMT<p>こんにちは、コーポレートエンジニアの兼松です。</p>
<p>今回は、メドレーがリーンな仕組みで ISMS 認証を取得したので、その過程について、弊社ならではの工夫したポイントを交えてご紹介します。</p>
<h1 id="はじめに">はじめに</h1>
<p>以下のニュースリリースのとおり、情報セキュリティ管理の仕組みである ISMS を構築し、オンライン診療システム「CLINICS」、クラウド型電子カルテ「CLINICS カルテ」の事業において「ISMS 認証」と「ISMS クラウドセキュリティ認証」という 2 つの第三者認証を同時取得しました。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="メドレーが国際規格に基づく ISMS クラウドセキュリティ認証を取得 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Frelease%2Fisms.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/release/isms.html">www.medley.jp</a></cite>
<p>特に「ISMS クラウドセキュリティ認証」は、国内でも取得している企業はまだ少なく、現時点で私の知る限り、オンライン診療システムとしては弊社の CLINICS(SaaS)が日本初だと思います。</p>
<h1 id="isms-とは">ISMS とは</h1>
<p>ISMS とは、Information Security Management System の略で、組織における情報セキュリティを管理するための枠組みのことです。具体的には、国際規格である<a href="https://www.iso.org/isoiec-27001-information-security.html">ISO/IEC 27001</a>に定められた様々な要求事項を満たし、組織内に情報セキュリティ水準向上のための体制を整備してプロセスを実行し続ける仕組みのことです。</p>
<h1 id="isms-認証とisms-クラウドセキュリティ認証とは">「ISMS 認証」と「ISMS クラウドセキュリティ認証」とは</h1>
<p>「ISMS 認証」とは、ISMS が適切に構築されていることを第三者機関が認証する仕組みです。また、「ISMS クラウドセキュリティ認証」は、ISMS 認証の取得を前提として ISO/IEC27017 に定められたクラウドサービス固有の情報セキュリティ管理要件を満たしている組織を第三者機関が認証する仕組みです。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190201/20190201170325.png" alt="f:id:medley_inc:20190201170325p:plain" title="f:id:medley_inc:20190201170325p:plain">(「ISMS クラウドセキュリティ認証」は「ISMS 認証」の取得を前提とした、クラウドセキュリティ分野に特化した追加認証という位置付け)</p>
<p>クラウドサービスを利用する組織(CSC)とクラウドサービスを提供する組織(CSP)の両方の組織を対象としており、メドレーは SaaS 提供企業として CSC と CSP の両方に該当します。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190201/20190201170346.png" alt="f:id:medley_inc:20190201170346p:plain" title="f:id:medley_inc:20190201170346p:plain"></p>
<p>(<a href="https://www.bsigroup.com/ja-JP/ISO27017/">https://www.bsigroup.com/ja-JP/ISO27017/</a> より引用)</p>
<h1 id="なぜ-isms-認証を取得することにしたのか">なぜ ISMS 認証を取得することにしたのか</h1>
<p>メドレーでは、これまでも個人情報保護認証の「<a href="https://www.truste.or.jp/more/">TRUSTe</a>」マークの取得や、CLINICS 事業においては医療情報を取り扱う際の安全管理に関するガイドラインである「3 省 3 ガイドライン」への対応など、情報セキュリティ体制の強化に努めてきました。</p>
<p>しかし、機密性の高い情報を取り扱うサービスをクラウドで提供する企業として、顧客からのより高度なセキュリティ、コンプライアンス要求にお応えできる体制を作っていく、という経営陣の強い意思決定がキッカケとなり、上記の取り組みに加えて ISMS 認証を活用しての組織的な仕組みづくりをすることに決まりました。</p>
<p>そしてさらに CLINICS 事業では、医療情報という機密性の非常に高い情報を取り扱うサービスをクラウドで提供するため、導入医療機関様や患者の皆様により安心して利用してもらえるよう、今回取得した 2 つの第三者認証を取得することに決定しました。</p>
<h1 id="脱isms-あるある">脱「ISMS あるある」</h1>
<p>ISMS についてご存知の方は、ISMS 認証取得について少々ネガティブなイメージを持たれている方も少なくないかもしれません。</p>
<p>例えば、「分厚い規程類がたくさん出来上がる」、「ガチガチのルール作りを強いられる」、「やたらと手間のかかる台帳を使わないといけない」といったような、「はぁ、昔はラクで良かった…。」というイメージです。</p>
<p>私自身も、過去に ISMS 活動の一環として、勤務先が決めた面倒なルール遵守をたくさん強いられてきた記憶がありましたし、後述しますが今回一緒に取り組んだメンバーの多くも、同じような経験をしてきていました。</p>
<p>そこで、堅牢なセキュリティにしつつも、このような面倒な作業をできるだけせずに、自分自身が働きたい環境となるよう、脱「ISMS あるある」のために意識したポイントや工夫の一部を 3 点ほどご紹介したいと思います。</p>
<p>ポイント1:ゼロベースで考える</p>
<p>今回の ISMS 取得プロジェクトにおいて初志貫徹したコンセプトがあります。それは、「ムダを削ぎ落としたリーンな仕組みを作る」ということです。</p>
<p>多くの組織は、認証取得支援コンサル会社などから入手した雛型をバコッとはめて、結果として大量の社内文書ができあがったりしてしまいがちですが、今回は本来要求されている ISO 規格の要求事項を本質的に実現するにはどうすればよいかという事に注力しました。</p>
<p>例えば、プロジェクトミーティングで、情報セキュリティ委員会(ISMS 取得企業では設置するのが一般的)の設置について、私が「そもそも、これって必要ありますか?」と発言した際には、他社で ISMS 取得経験があるメンバーに唖然とされました。あとで「あの時は、この調子で進んで本当に認証取得できるのか不安でしたよ(笑)」と言われたほどです。</p>
<p>このように、当初からこのポイントをいちばん大切にしており、形式だけの仕組みができる位なら、むしろやらない方が良いというくらいに考えて取り組んでいました。</p>
<p>ポイント2:各部門の責任者と共創する</p>
<p>ムダがなくサスティナブルな仕組みづくりのためには、各部門の視点からしっかりと考えていく必要があります。そこで、責任者クラスでの組織横断の ISMS 取得プロジェクトを発足させました。</p>
<p>例えば、管理部門サイドの視点だけで仕組みを作ってしまうと、どうしても事業の実態に沿わないムダの多い仕組みとなりがちです。また、エンジニアサイドの視点だけで作ってしまうと、ムダの少ないオペレーションとなる一方で既存規程との不整合などがおこってしまうことも考えられます。</p>
<p>そこで、メドレーでは、コーポレート IT 責任者が全体統括を担当しつつも、開発業務とプロダクトに精通<strong>する開発部長と、<strong>事業プロセスに精通</strong>する事業責任者およびマネージャを中心メンバーとしてプロジェクトを作りました。また、<strong>他社での取得経験者</strong>や社内</strong>弁護士ともコラボレーションして規程群を作成しました。</p>
<p>この体制により、最も難しいタスクの一つであるリスク対策の決定や、各部門での実オペレーションへの組込みにおいて、実用的な管理策を「即断・即決・即実行」できました。</p>
<p>この取り組みの成果として例えば、ISMS 管理策の事例として重複管理や更新漏れなどがよく発生しがちな「管理台帳」を、極力作らずに運用できるようになりました。また、情報の重要度を識別するための ISMS 要件である「情報資産に対するラベリング」も、リスク発生のケースを具体的に想定することで特定のケースのみラベルを付けることとしました。</p>
<p>各部門の責任者と共にこのような細かな工夫の積み重ねることによって、全従業員にとって手間の少ないシンプルな設計にすることができました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190201/20190201170507.jpg" alt="f:id:medley_inc:20190201170507j:plain" title="f:id:medley_inc:20190201170507j:plain"></p>
<p>(開発部長、事業責任者、他社における ISMS 認証取得経験者と共に記念撮影。左から 2 人目が筆者。)</p>
<p>ポイント3:情報を集約する</p>
<p>最後に特徴的なポイントとして、セキュアなクラウド環境に情報を集約したことです。元々メドレーの社内にはサーバを置いていません。もちろん管理するデータセンターなどもなく、全員クラウドベースで業務しています。</p>
<p>そして、そのクラウド上の厳選したセキュアなツールに、機密度や用途に応じて情報を集約して保管することで、重複や探す手間を極力少なくしています。例えば、規程群・業務フロー/マニュアル・営業資料・企画書などのストックとフロー情報は基本的に全て<a href="https://ja.atlassian.com/software/confluence">Confluence</a>(社内 Wiki)に構造化して保管されています。さらにオープンを原則としているため社内のコラボレーションもとてもスムーズになっています。</p>
<p>このように、どこに何があるかが誰にとっても明確になっているので、リスクも把握しやすく対策も立てやすくなります。結果としてセキュリティレベルも向上します。</p>
<p>また、認証審査の際には、審査員から求められた情報を瞬時に提示することができました。結果、あまりにも審査の進捗が速く進んだため、審査員の方々にとても驚かれました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190201/20190201170529.png" alt="f:id:medley_inc:20190201170529p:plain" title="f:id:medley_inc:20190201170529p:plain"></p>
<p>(情報を集約している弊社の Confluence の一例)</p>
<h1 id="審査機関の反応">審査機関の反応</h1>
<p>認証審査の第三者機関からは、以下のような点でメドレーの取り組みに対して非常に高い評価を受けることができました。</p>
<p>-オープンな社内コミュニケーション環境による情報共有や意思疎通 -開発部門と事業部門との協業関係 -役割責任の明確化と専門家(医師、弁護士)のアサイン -組織における機微情報であるカスタマーデータに対する高いセキュリティ管理運用</p>
<p>など</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190201/20190201170557.png" alt="f:id:medley_inc:20190201170557p:plain" title="f:id:medley_inc:20190201170557p:plain"></p>
<p>(取得した「ISMS クラウドセキュリティ認証」マーク)</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20190201/20190201170613.png" alt="f:id:medley_inc:20190201170613p:plain" title="f:id:medley_inc:20190201170613p:plain"></p>
<p>(取得した「ISMS 認証」マーク)</p>
<h1 id="さいごに">さいごに</h1>
<p>今回の活動を通して改めて感じたことがあります。それは、こういった活動をやり遂げるために最も大切なのは、社長や CTO を始めとする「経営陣の情報セキュリティに対する意識の高さとコミット」だということです。</p>
<p>今回、メドレーでは経営陣が社内メッセージを発信し続けたからこそ、部門横断でコラボレーションし、このような工夫を生むことができたのだと思います。</p>
<p>これからも弊社では、ISMS をさらに磨きあげ、事業運営の効率化にまで寄与する ISMS として運用することで、良質なサービスの提供を通じた”納得できる医療”の実現を目指していきたいと思います。
最後まで拙文を読んでいただき、ありがとうございました。</p>
<p>メドレーでは、提供サービスの拡大を受けて、その成長を支えるクリエイター(エンジニア・デザイナー)を募集しています。</p>
<p>また、組織も急成長しているため、組織の抱える課題や社内 IT 環境について、様々な効率化や改革を進めるコーポレートエンジニアを募集しています。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="CREATOR'S STORY | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fteam%2Fcreator-story.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/team/creator-story.html">www.medley.jp</a></cite>
<p>ISMS の情報交換とかでも結構です。</p>
<p>少しでも興味を持っていただいた方、ぜひお気軽にご連絡ください!</p>medley
- Classi / メドレー合同で AWS 勉強会を開催しました!https://developer.medley.jp/entry/2018/12/13/150249https://developer.medley.jp/entry/2018/12/13/150249みなさん、こんにちは。開発本部エンジニアの新居です。
先日 12/7(金)、AWS の re:Invent の余韻冷めやらぬ中、メドレーと教育現場支援の ICT プラットフォームを展開している Classi さんとの合同で AWS 勉強会を...Thu, 13 Dec 2018 06:02:49 GMT<p>みなさん、こんにちは。開発本部エンジニアの新居です。</p>
<p>先日 12/7(金)、AWS の re:Invent の余韻冷めやらぬ中、メドレーと教育現場支援の ICT プラットフォームを展開している Classi さんとの合同で AWS 勉強会を開催しました。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Real Tech Night #3 (2018/12/07 19:30〜)" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Frealtech-night.connpass.com%2Fevent%2F110083%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://realtech-night.connpass.com/event/110083/">realtech-night.connpass.com</a></cite>
<p>サブタイトルに「AWS を最大限活用して教育/医療業界の課題をスマートに解決していく」とある通り、教育/医療業界ならではの課題を両社がどのように AWS を使って解決しているのかといったことを中心に、各社 3 名ずつ発表を行いました。</p>
<p>当日は SNS 禁止で外部公開できないディープな話などもありましたが、この記事では簡単に当日の発表内容を紹介したいと思います。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181213/20181213125501.jpg" alt="f:id:medley_inc:20181213125501j:plain" title="f:id:medley_inc:20181213125501j:plain"><em>場所はサポーターズさんのレンタルスペースをお借りしました</em></p>
<h1 id="発表内容の紹介">発表内容の紹介</h1>
<h2 id="1-ガイドライン対応のための-aws-セキュリティ系マネージドサービスの活用メドレー-中畑">1. ガイドライン対応のための AWS セキュリティ系マネージドサービスの活用(メドレー 中畑)</h2>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181213/20181213125753.jpg" alt="f:id:medley_inc:20181213125753j:plain" title="f:id:medley_inc:20181213125753j:plain"><em>メドレー 中畑の発表</em></p>
<iframe id="talk_frame_481139" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/93d35b5d1fd34db993326e63ca593d65" width="710" height="399" frameborder="0" allowfullscreen></iframe>
<div class="remark-link-card-plus__container">
<a href="https://speakerdeck.com/medley/real-tech-night-number-3" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">ガイドライン対応のためのAWSセキュリティ系マネージドサービスの活用 / Real Tech Night #3</div>
<div class="remark-link-card-plus__description">2018/12/7 開催の"Real Tech Night #3 AWSを最大限活用して教育/医療業界の課題をスマートに解決していく" での中畑の発表資料です。
https://realtech-night.connpass.com/event/110083/</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://d1eu30co0ohy4w.cloudfront.net/assets/favicon-bdd5839d46040a50edf189174e6f7aacc8abb3aaecd56a4711cf00d820883f47.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">speakerdeck.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://files.speakerdeck.com/presentations/93d35b5d1fd34db993326e63ca593d65/slide_0.jpg?11407379" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>一人目はメドレー中畑から、「CLINICS カルテ」と「3 省 3 ガイドライン」(<em>開発当初は 3 省 4 ガイドラインでしたが、2018 年 7 月から 3 省 3 ガイドラインに変更されました。以下 3 省 3 ガイドライン</em>)、「CLINICS カルテで利用している AWS のセキュリティ系マネージドサービス」について発表しました。</p>
<h3 id="発表内容の要約">発表内容の要約</h3>
<ul>
<li>
<p>3 省 3 ガイドラインとは「電子カルテをはじめ、電子化された医療情報をパブリッククラウドなどに外部保存する際に遵守する必要があるガイドライン」のことで、CLINICS カルテの開発を行う上でも遵守する必要がある</p>
</li>
<li>
<p>今回はそのガイドラインの中にある「アカウントごとの権限管理や各種操作のログ管理」を遵守するために AWS のサービスを使ってどのように対応しているかをメインで紹介</p>
</li>
<li>
<p>詳細は資料を見ていただくとして、主に利用しているサービスとして以下を紹介</p>
</li>
<li>
<p><a href="https://aws.amazon.com/jp/cloudtrail/">AWS CloudTrail</a></p>
</li>
<li>
<p><a href="https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/flow-logs.html">Flow logs</a></p>
</li>
<li>
<p><a href="https://aws.amazon.com/jp/guardduty/">Amazon GuardDuty</a></p>
</li>
<li>
<p><a href="https://aws.amazon.com/jp/config/">AWS Config</a></p>
</li>
<li>
<p><a href="https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-paramstore.html">Parameter Store</a></p>
</li>
<li>
<p><a href="https://aws.amazon.com/jp/systems-manager/#Session_Manager">Session Manager</a></p>
</li>
<li>
<p><a href="https://aws.amazon.com/jp/blogs/news/new-amazon-cloudwatch-logs-insights-fast-interactive-log-analytics/">CloudWatch Logs Insights</a></p>
</li>
</ul>
<p>先日の re:Invent で発表された CloudWatch Logs Insights を早速導入していたりと、新しい技術に対して高いアンテナを持ち、日々改善を続けています。</p>
<p>また、プロダクトの特性を踏まえて様々なマネージドサービスを活用することで、自前で仕組みを用意することなくセキュアにシステムを構築でき、プロダクト開発に集中できる環境作りを心掛けていることが伝わる発表でした。</p>
<h2 id="2-システムを-ec2-から-fargate-へ安全にリプレイスclassi-高田さん">2. システムを EC2 から Fargate へ安全にリプレイス(Classi 高田さん)</h2>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181213/20181213125515.jpg" alt="f:id:medley_inc:20181213125515j:plain" title="f:id:medley_inc:20181213125515j:plain"><em>Classi 高田さんの発表</em></p>
<iframe style="border: 1px solid #CCC; border-width: 1px; margin-bottom: 5px; max-width: 100%;" src="https://www.slideshare.net/slideshow/embed_code/key/B2iXWrWaPeeKL7" width="427" height="356" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" allowfullscreen></iframe>
https://www.slideshare.net/spinning13/real-tech-night-3-ec2fargate
<p>二人目は Classi 高田さんで、現在 EC2 上で動いている既存システムを ECS の Fargate に安全に移行する方法についての発表でした。</p>
<h3 id="発表内容の要約-1">発表内容の要約</h3>
<ul>
<li>ECS の起動タイプには「EC2」と「Fargate」の 2 種類があり、マイクロサービスアーキテクチャでアプリケーションの種類が多いケースなどでは EC2 インスタンスの管理が不要な Fargate が便利</li>
<li>Fargate への移行の際は「既存の環境のシステムと相互に切り替えが可能なこと」と「切り替えが容易であること」などを考慮</li>
<li>新環境と既存環境の振り分けには OpenResty を採用</li>
</ul>
<p>Fargate へのリプレイス時などには参考になる事例ではないでしょうか!</p>
<h2 id="3-クライアント認証対応のための-aws-構成の変遷メドレー-田中">3. クライアント認証対応のための AWS 構成の変遷(メドレー 田中)</h2>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181213/20181213125521.jpg" alt="f:id:medley_inc:20181213125521j:plain" title="f:id:medley_inc:20181213125521j:plain"><em>メドレー 田中の発表</em></p>
<p>【発表資料は非公開】</p>
<p>三人目はメドレー田中で、一人目の中畑に引き続き、3 省 3 ガイドラインへの対応について発表しました。</p>
<p>3 省 3 ガイドラインの中でもシステム構成に特に影響が大きい内容として</p>
<ul>
<li>サービス提供に用いるシステム、アプリケーションを日本国内法の適用が及ぶ場所に設置すること</li>
<li>クライアント証明書を利用した TLS クライアント認証を実施すること</li>
</ul>
<p>があり、これらを AWS を利用してどのように対応したかという内容でした。</p>
<p>発表資料は非公開なので詳細には触れませんが、この辺の話については<a href="/entry/2018/05/21/180652">こちら</a>の記事にもありますので、興味がある方は御覧いただければと思います。</p>
<h2 id="4-classi-での-amazon-elasticsearh-service-の活用classi-中村さん">4. Classi での Amazon Elasticsearh Service の活用(Classi 中村さん)</h2>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181213/20181213125527.jpg" alt="f:id:medley_inc:20181213125527j:plain" title="f:id:medley_inc:20181213125527j:plain"><em>Classi 中村さんの発表</em></p>
<iframe id="talk_frame_481070" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/745e6cfc7dff484aa0d6b5f9527170aa" width="710" height="491" frameborder="0" allowfullscreen></iframe>
<cite class="hatena-citation"></cite>
<div class="remark-link-card-plus__container">
<a href="https://speakerdeck.com/nakaearth/classidefalseelasticsearchfalseli-yong-nituite" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title"> ClassiでのElasticsearchの利用について</div>
<div class="remark-link-card-plus__description">ClassiでどのようにAmazon Elasticsearch Serviceを利用しているのか、その紹介です。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://d1eu30co0ohy4w.cloudfront.net/assets/favicon-bdd5839d46040a50edf189174e6f7aacc8abb3aaecd56a4711cf00d820883f47.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">speakerdeck.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://files.speakerdeck.com/presentations/745e6cfc7dff484aa0d6b5f9527170aa/slide_0.jpg?11405196" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>四人目は Classi 中村さんで、Classi で Elasticsearch をどのように利用しているかについての発表でした。</p>
<h3 id="発表内容の要約-2">発表内容の要約</h3>
<ul>
<li>Classi の「コンテンツボックス」と「校内グループ」の機能で Amazon Elasticsearch Service を利用</li>
<li>「コンテンツボックス」では、レスポンスが 10 秒以上掛かっていたものが 1~2 秒まで短縮できたが、Elasticsearch のバージョンアップに追随するためにどのように対応したかの紹介</li>
<li>「校内グループ」では、当初学校毎に index を作成していたが、サービスの成長に伴い学校数が多くなり index の数は数千以上に。ここから安定稼働にためにどのような対応したかの紹介</li>
</ul>
<p>Elasticsearch は弊社でも利用しており、自分が担当している別のサービスでは 1 or 2 系から 5 系以上へのバージョンアップ(ちなみに 3 系 4 系はありません)も過去に経験しており、この辺の泥臭さは個人的には共感値が高かったです!</p>
<h2 id="5-クラウドと院内システムをつなぐための-aws-iot-の活用メドレー-有馬">5. クラウドと院内システムをつなぐための AWS IoT の活用(メドレー 有馬)</h2>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181213/20181213125536.jpg" alt="f:id:medley_inc:20181213125536j:plain" title="f:id:medley_inc:20181213125536j:plain"><em>メドレー 有馬の発表</em></p>
<p>【発表資料は非公開】</p>
<p>五人目はメドレー有馬で、クラウドと院内システムをつなぐための AWS IoT の活用について発表しました。</p>
<p>CLINICS カルテを始めとした電子カルテでは、医療機関内の院内機器などの外部機器やサービスと連携する必要があります。</p>
<p>こうした外部機器やサービスと、CLINICS カルテをどのように連携させていくのか?また可用性を担保しつつ、AWS をどう活用して実現していくのかという内容でした。</p>
<p>こちらは SNS 禁止で外部公開できないディープな話でしたので、また別の機会でお話できるときがあればという感じですね。</p>
<h2 id="6-ソシャゲ業界出身者が驚いた学校教育支援システムの裏側classi-本間さん">6. ソシャゲ業界出身者が驚いた、学校教育支援システムの裏側(Classi 本間さん)</h2>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181213/20181213125547.jpg" alt="f:id:medley_inc:20181213125547j:plain" title="f:id:medley_inc:20181213125547j:plain"><em>Classi 本間さんの発表</em></p>
<iframe id="talk_frame_481386" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/dd7bda6207c54b8ea46d3b9c5f02e9b9" width="710" height="491" frameborder="0" allowfullscreen></iframe>
<div class="remark-link-card-plus__container">
<a href="https://speakerdeck.com/ckreal/sosiyageye-jie-chu-shen-zhe-gajing-ita-xue-xiao-jiao-yu-zhi-yuan-sisutemufalseli-ce" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">ソシャゲ業界出身者が驚いた、学校教育支援システムの裏側</div>
<div class="remark-link-card-plus__description">2018/12/07 Real Tech Night #3 発表資料</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://d1eu30co0ohy4w.cloudfront.net/assets/favicon-bdd5839d46040a50edf189174e6f7aacc8abb3aaecd56a4711cf00d820883f47.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">speakerdeck.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://files.speakerdeck.com/presentations/dd7bda6207c54b8ea46d3b9c5f02e9b9/slide_0.jpg?11414964" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>最後六人目は Classi 本間さん。Classi さんもメドレーも、前職でソーシャルゲーム業界だったメンバーが多数在籍しており、ソーシャルゲームの開発との違いなども絡めて Classi での AWS 活用事例の発表をいただきました。</p>
<h3 id="発表内容の要約-3">発表内容の要約</h3>
<ul>
<li>教育業界特有のガイドラインについて</li>
<li>本番環境への接続制限</li>
<li>ECS 化と CloudWatch Logs の利用</li>
<li>データベースの監査ログ取得</li>
<li>CloudWatchLogs の費用感</li>
<li>ManagementConsole 操作権限</li>
</ul>
<p>最後のまとめのところで</p>
<ul>
<li>学校向けのサービス提供は、セキュリティへの理解が重要</li>
<li>セキュリティを保ちつつ、運用負荷を軽減する機能が AWS にはたくさんある</li>
<li>とはいえ、もっともっと AWS を活用できる筈。なので…</li>
</ul>
<p>とありましたが、この辺は医療業界ともリンクする部分だよなあと改めて実感することができました。本番環境への接続制限など結構シビアにされているところも垣間見え、興味深いお話を聞くことができました。</p>
<h1 id="懇親会">懇親会</h1>
<p>発表終了後は、お酒とフードを交えてざっくばらんに交流しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181213/20181213125552.jpg" alt="f:id:medley_inc:20181213125552j:plain" title="f:id:medley_inc:20181213125552j:plain"><em>懇親会の様子</em></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181213/20181213125602.jpg" alt="f:id:medley_inc:20181213125602j:plain" title="f:id:medley_inc:20181213125602j:plain"><em>本間さんの re:Invent 参加レポート</em></p>
<p>本間さんからは re:Invent の参加レポートもあり、現地で体験した人の生のレポートが聞け、その興奮を少し感じることができたのは良かったです!</p>
<iframe id="talk_frame_481440" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/d5928b5490434e499ed97fef30624bbb" width="710" height="491" frameborder="0" allowfullscreen></iframe>
<cite class="hatena-citation"></cite>
<div class="remark-link-card-plus__container">
<a href="https://speakerdeck.com/ckreal/re-inventnichu-can-jia-sitekitayo" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">re:Inventに初参加してきたよ</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://d1eu30co0ohy4w.cloudfront.net/assets/favicon-bdd5839d46040a50edf189174e6f7aacc8abb3aaecd56a4711cf00d820883f47.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">speakerdeck.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://files.speakerdeck.com/presentations/d5928b5490434e499ed97fef30624bbb/slide_0.jpg?11416335" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="まとめ">まとめ</h1>
<p>教育業界と医療業界はもちろん別業界ではありますが、それぞれの業界で特有のセキュリティ要件があり、それらを AWS でどのように解決していくかなど、リンクする部分も多々あることを実感することができました。</p>
<p>そして両社共、AWS に詳しいメンバーが多数在籍しており、AWS をフル活用して効率良く柔軟性を持たせたシステムを構築していることが垣間見えた会となりました。</p>
<p>教育/医療業界というと Web 系のエンジニアにとってはまだまだ未知でよくわからないイメージなのかなあと思います。実際この業界でエンジニアとして働いていると、レガシーなシステムや業界特有の慣習がサービス開発の障壁となることもあります。</p>
<p>しかしそういった障壁や課題を、新しい技術を適切に用いてうまく解決していくことがやりがいのひとつですし、そのような課題解決のひとつひとつがエンジニアとしての成長の糧にもなるのではないでしょうか。</p>
<p>両社の歩みが教育業界と医療業界のそれぞれの発展になればとても素晴らしいことですし、その担い手となる仲間が一人でも多く増えることを切に願っています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181213/20181213125557.jpg" alt="f:id:medley_inc:20181213125557j:plain" title="f:id:medley_inc:20181213125557j:plain"></p>
<p><em>最後に両社のメンバーで記念撮影</em> </p>
<p>「新しい医療体験を創造する」</p>
<p>メドレーで働くクリエイターたちのストーリーを公開中。ぜひご覧ください。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="MEDLEY DESIGN&ENGINEERING" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- HTML5 Conference 2018 にメドレーが協賛しました!https://developer.medley.jp/entry/2018/12/07/120015https://developer.medley.jp/entry/2018/12/07/120015こんにちは。開発本部の日下です。昨年新卒として入社してからはや 1 年半、最近は医療介護の求人サイト「ジョブメドレー」にてサーバーからインフラ近くまでをメインに担当しております。
先日開催されたHTML5 Conference 2018にメ...Fri, 07 Dec 2018 03:00:15 GMT<p>こんにちは。開発本部の日下です。昨年新卒として入社してからはや 1 年半、最近は医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」にてサーバーからインフラ近くまでをメインに担当しております。</p>
<p>先日開催された<a href="https://events.html5j.org/conference/2018/11/">HTML5 Conference 2018</a>にメドレーがスポンサーとして協賛させていただきました。</p>
<p>今まで Web フロントをメインとしたカンファレンスに参加する機会がなかったのですが、こういった縁があり僕も参加させていただきましたので、当日の様子をレポートさせていただきます。</p>
<h1 id="html5-conference-とは">HTML5 Conference とは</h1>
<p>HTML5 Web に関する最新技術のトレンドを集め、交流する祭典として 2011 年に Chrome+HTML5 Conference として始まったカンファレンスです。</p>
<p>以降 HTML5 Conference として開催し、今年で 7 回目の開催となります。</p>
<p>定員枠は年々埋まるのが早くなっているそうで、今年は 1600 人の定員が 1 日で埋まったほどの人気でした。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181207/20181207020902.png" alt="f:id:medley_inc:20181207020902p:plain" title="f:id:medley_inc:20181207020902p:plain"></p>
<p><em>今年のテーマは「The Web is shifting to the next gear」</em></p>
<h1 id="会場の様子">会場の様子</h1>
<p>500 人が収容できる大講堂や、可動式の壁で自由に大きさを変えられる講義室など会場自体はとても整備されていました。</p>
<p>一方で立ち見、入場規制をうけてしまうほど人が多く盛況だったことが印象的でした。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181207/20181207021007.png" alt="f:id:medley_inc:20181207021007p:plain" title="f:id:medley_inc:20181207021007p:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181207/20181207021047.png" alt="f:id:medley_inc:20181207021047p:plain" title="f:id:medley_inc:20181207021047p:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181207/20181207021114.png" alt="f:id:medley_inc:20181207021114p:plain" title="f:id:medley_inc:20181207021114p:plain"><em>弊社リーフレットを発見!</em></p>
<h1 id="セッション">セッション</h1>
<p>各セッションでは、スピーカーの方々が Web フロントに関する最新技術の様々なトレンドの発表をしていました。</p>
<p>参加した中からいくつか気になったセッションを紹介します。</p>
<h2 id="通常セッション">通常セッション</h2>
<h3 id="typescript-evolution-by-倉見洋輔">TypeScript Evolution by 倉見洋輔</h3>
<p>弊社でも TypeScript を実運用しているプロダクトがあり、また Flow を使用している他プロダクトも TypeScript へ移行しようかという話がでています。</p>
<p>そのため、TypeScript に関するディープな話しが聞きたいなと思いこちらのセッションに参加しました。</p>
<p>倉見さんによる本セッションでは、TypeScript に関する概要から最新情報まで、40 分で 78 枚のスライドをまさに駆け抜けるかたちで紹介いただきました。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/2644bc630b204e1b8538ef613360a68c" title="TypeScript Evolution" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 314px;" data-ratio="1.78343949044586"></iframe>
<p>@types のリポジトリ、リリースサイクル、TC39 などとの兼ね合いと言った基本的な TypeScript に関する情報から最新の 3.x 系に含まれる変更まで、具体的なコードと共に紹介いただけたのでわかりやすいセッションでした。</p>
<p>特に構造的部分型は初めて聞いたキーワードだったのですが、例えなどを交えた説明がとても腹落ちしたことが印象的でした。</p>
<p>これを機に型に関する知識を深めようと考えています。</p>
<h3 id="pwa-の導入で得られた成果と見えてきた課題-from-宍戸俊哉">PWA の導入で得られた成果と見えてきた課題 from 宍戸俊哉</h3>
<p>今回のカンファレンスでも多くのセッションがパフォーマンス絡みでしたが、この分野はビジネスに直結するため、Web サービスをしている上では重要な分野です。</p>
<p>もちろん弊社もパフォーマンスに関してもより良くしていこうと常に意識しています。</p>
<p>そのため、今年 5 月の<a href="https://hack.nikkei.com/blog/nikkei-featured-at-google-io/">Google I/O でも紹介された</a>ことも記憶に新しい日経さんの事例は聞いておきたいと思い参加しました。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/0309b649c4ab42529afe5160c4bad351" title="PWA 導入の成果と課題 / nikkei-pwa-html5conf2018" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 314px;" data-ratio="1.78343949044586"></iframe>
<p>Team Performance Day(通常業務はせず、パフォーマンス改善のみを各自行う日)といった日経のパフォーマンスに関する文化や、PageSpeed Insights や SpeedCurve など実際に使用しているツールやモニタリングの様子など、事例を交えての発表だったのでとてもイメージしやすくかったです。</p>
<p>また、Performance Budget(パフォーマンス関連のメトリクスに対する予算)など最新の情報も事例を交えて紹介いただけたので、とてもためになりました。</p>
<h2 id="その他のセッション">その他のセッション</h2>
<p>残念ながら見に行くことができなかったセッションも多々ありましたが、<a href="https://events.html5j.org/conference/2018/11/session/">Twitter の</a><a href="https://events.html5j.org/conference/2018/11/session/">ハッシュタグがとても良く整備・周知されている</a>ため、発表の様子やスライドなど後から他の部屋の情報を入手するのに非常に役に立ちました。</p>
<p>また、動画もしばらくしたら HTML5 Conference 側から公開されるようですので<a href="https://twitter.com/html5j">公式 Twitter</a>または<a href="https://www.youtube.com/user/html5j">公式 YouTube アカウント</a>を見ておくと良さそうです。公開されたら参加していないセッションはもちろんのこと、参加したセッションも復習がてら見てみようと思います。</p>
<h2 id="スペシャルセッション">スペシャルセッション</h2>
<p>最後に行われたスペシャルセッションでは、LT や Web 標準に関する難易度の高いクイズがありました。</p>
<p>LT では AMP 対応の話から始まり PureScript、UNIQLO のパフォーマンス改善、CSS 組版の話しやブラウザの内部構造の話など、濃縮された Web フロントに関する情報が得られたのでとても勉強になりました。</p>
<p>クイズでは Web 標準に関するかなり高度な(例えばある時期はこの回答が正しいが、現在だとこちらが正しいといったような)選択肢が散りばめられているひっかけ問題が出題され、その解説込みで Web の歴史の一部を覗くことのできたとても楽しい会となりました。</p>
<p>いずれも現時点でライブ配信のアーカイブが公開されていますので、気になる方は覗いてみると面白いかもしれません。</p>
<iframe src="https://www.youtube.com/embed/_GjWo2r96eM" width="655" height="368" frameborder="0" allowfullscreen></iframe>
<h1 id="まとめ">まとめ</h1>
<p>はじめての Web フロントメインのカンファレンスへの参加でしたが、Web に関する最新技術の様々なトレンドの事例を含めた実務に近い知識を得ることのできるとても刺激的なカンファレンスでした。</p>
<p>Web フロントに関しては「<a href="/entry/2017/11/09/171047">フロントエンドに再入門する</a>」という社内勉強会で基礎的なところを学んでいましたが、最近はサーバーがメインだったこともあり情報が不足しがちでした。</p>
<p>今回のカンファレンスは、特に不足を感じていた最新情報をアップデートする良い機会となりました。</p>
<p>また、長年運営されてるイベントで公式の<a href="https://events.html5j.org/conference/2018/11/session/">Twitter ハッシュタグがよく整備・周知されていたため</a>、各セッションの資料が収集しやすかったのはとても助かりました。</p>
<p>その他にも、通常のセッションの他にクイズなど会場参加型のものもあり、開催側が参加者を飽きさせない工夫がそこかしこにあり、カンファレンス全体を通して楽しむことができました。</p>
<p>来年もチャンスがあればまた参加したいと思います。</p>
<p>企画された方も参加された方もお疲れ様でした!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="MEDLEY DESIGN&ENGINEERING" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- 【2018/12/7 開催予告】Classi / メドレー 合同で AWS 勉強会を開催します!https://developer.medley.jp/entry/2018/12/04/175913https://developer.medley.jp/entry/2018/12/04/175913
みなさん、こんにちは。開発本部エンジニアの平木です。
さて、いよいよ 12 月に突入して世間の雰囲気も師走に相応わしいムードになっている中、みなさんいかがお過しでしょうか?
来たる 2018/12/7(金)にメドレーと教育現場支援の IC...Tue, 04 Dec 2018 08:59:13 GMT<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181204/20181204165947.jpg" alt="20181204165947.jpg">
<p>みなさん、こんにちは。開発本部エンジニアの平木です。</p>
<p>さて、いよいよ 12 月に突入して世間の雰囲気も師走に相応わしいムードになっている中、みなさんいかがお過しでしょうか?</p>
<p>来たる 2018/12/7(金)にメドレーと教育現場支援の ICT プラットフォームを展開している Classi さんの合同で AWS 勉強会を開催します。</p>
<div class="remark-link-card-plus__container">
<a href="https://realtech-night.connpass.com/event/110083/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Real Tech Night #3 (2018/12/07 19:30〜)</div>
<div class="remark-link-card-plus__description">## イベント概要 AWSを使って教育/医療業界の課題を解決していく2社が共同開催するLTイベント。 * 教育プラットフォームとして学校教育と一体となりICTを活用したクラウド型学習支援サービスの開発に取り組むClassi * 次世代につながる医療の未来がどうあるべきか?を第一に考え、インターネットを通じて「医療ヘルスケア分野の課題」を解決していくメドレー AWSを使って時にはスマートに、時には泥臭く課題を解決しているかをLT形式でお届けします。なお、内容によってはSNS禁止部分もあるかもしれませんので予めご了承ください。 イベントの後半は懇親会を予定しておりますので、より...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://connpass.com/static/20260316.9b5a655/img/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">realtech-night.connpass.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://media.connpass.com/thumbs/ab/1c/ab1c01a2e3479623c8ddaa700e6cb824.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>そこで今回は主にメドレーの発表はどのようなものがあるかをちらっと、ご紹介しながらイベントの告知をしようと思います。</p>
<h1 id="なぜclassi-さんとの合同イベントをするのか">なぜ、Classi さんとの合同イベントをするのか</h1>
<p>弊社メドレーは医療・ヘルスケア分野の課題解決するサービスを展開しており現在は 5 つのサービスを運営しています。全てのサービスで AWS を活用しており、その恩恵をフルに活かして日々運用をしています。Classi さんも全国の教育機関で使われるサービスのインフラを AWS で構築されています。</p>
<p>医療業界のメドレー、教育業界の Classi さんと業界は全然違うのですが、国の施策や法律に密着したサービス運営をしている点や、社会のインフラ的な事業にチャレンジしている点、もちろん先ほど紹介したように AWS をフル活用している点などの共通項がありました。</p>
<p>こうしたことから、それぞれの業界ならではの話も折り混ぜた AWS の活用・運用・構築などの事例を合同で発表すると面白いのではないか?ということで実現しました。</p>
<h1 id="メドレーの発表を少し頭出し">メドレーの発表を少し頭出し</h1>
<p>ということで実現したこの勉強会ですが、メドレーのセッションのタイトルと内容を少しだけご紹介していきたいと思います。</p>
<h2 id="ガイドライン対応のための-aws-セキュリティ系マネージドサービスの活用">ガイドライン対応のための AWS セキュリティ系マネージドサービスの活用</h2>
<p>そもそもガイドラインとは何ぞや?という話になってきますが、“<a href="https://clinics.medley.life/karte">CLINICS カルテ</a>”を構築する上で避けては通れなかった 3 省 4 ガイドラインという、関係省庁から出されていた医療情報取り扱いにおけるガイドラインです。(現在は 3 省 3 ガイドラインになっていますが…)</p>
<p>このガイドラインに準拠するためにどのように AWS の各サービスを使って CLINICS カルテのインフラを構築しているか?という発表を中畑からします。</p>
<h2 id="クライアント認証対応のための-aws-構成の変遷">クライアント認証対応のための AWS 構成の変遷</h2>
<p>田中からは、こちらもガイドラインで対応必須とされている”クライアント認証”を CLINICS カルテではどのように実現しているか?というお話をします。</p>
<p>AWS 上に構築された CLINICS カルテで、紆余曲折ありながらクライアント認証を実現させるための苦労話を発表していきます。</p>
<h2 id="クラウドと院内システムをつなぐための-aws-iot-の活用">クラウドと院内システムをつなぐための AWS IoT の活用</h2>
<p>CLINICS カルテを始めとした電子カルテでは、医療機関内の院内機器など外部の機器やサービスと連携する必要があります。</p>
<p>こうした外部機器やサービスと、CLINICS カルテをどのように連携させていくのか?また可用性を担保しつつ、AWS をどう活用して実現していくのかというセッションを有馬からお話します。</p>
<h1 id="まとめ">まとめ</h1>
<p>このように、色々な制約などがあるなかで、AWS をどのように活用しているのかというのが違った側面から分かるようなセッション内容になっていると思います。</p>
<p>また、発表者の 1 人 Classi の本間さんは先日の<a href="https://reinvent.awsevents.com/">re:Invnent 2018</a>に参加されてきたとのことで、現地のお土産話などが懇親会で聞けると思います!</p>
<p>年の瀬も差しせまってきた忙しい中だとは思いますが、みなさま参加してみてはいかがでしょうか?</p>
<p>ご参加、お待ちしています。</p>
<div class="remark-link-card-plus__container">
<a href="https://realtech-night.connpass.com/event/110083/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Real Tech Night #3 (2018/12/07 19:30〜)</div>
<div class="remark-link-card-plus__description">## イベント概要 AWSを使って教育/医療業界の課題を解決していく2社が共同開催するLTイベント。 * 教育プラットフォームとして学校教育と一体となりICTを活用したクラウド型学習支援サービスの開発に取り組むClassi * 次世代につながる医療の未来がどうあるべきか?を第一に考え、インターネットを通じて「医療ヘルスケア分野の課題」を解決していくメドレー AWSを使って時にはスマートに、時には泥臭く課題を解決しているかをLT形式でお届けします。なお、内容によってはSNS禁止部分もあるかもしれませんので予めご了承ください。 イベントの後半は懇親会を予定しておりますので、より...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://connpass.com/static/20260316.9b5a655/img/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">realtech-night.connpass.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://media.connpass.com/thumbs/ab/1c/ab1c01a2e3479623c8ddaa700e6cb824.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- クリエイターのストーリーに振り切ったサイトを作った話https://developer.medley.jp/entry/2018/11/30/122942https://developer.medley.jp/entry/2018/11/30/122942こんにちは。デザイナーの波切(はきり)です。
普段は介護のほんねのデザイン全般を担当しておりますが、今回はマエダからのバトンを受け、先日リリースをしたクリエイターページのリニューアルの経緯について書かせていただきます。
「MEDLEY DE...Fri, 30 Nov 2018 03:29:42 GMT<p>こんにちは。デザイナーの波切(はきり)です。</p>
<p>普段は<a href="https://www.kaigonohonne.com/">介護のほんね</a>のデザイン全般を担当しておりますが、今回は<a href="/entry/2018/11/22/181939">マエダからのバトン</a>を受け、先日リリースをしたクリエイターページのリニューアルの経緯について書かせていただきます。</p>
<p>「MEDLEY DESIGN & ENGINEERING」という言葉に秘められた想いはマエダからも説明がありましたが、メドレーでは「ビジネス」「テクノロジー」「クリエイティブ」の 3 軸の横断的な理解と実践を志向しており、この文化を表わす言葉として「MEDLEY DESIGN & ENGINEERING」を掲げたクリエイター向けのページを公開しています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181130/20181130120407.png" alt="f:id:medley_inc:20181130120407p:plain" title="f:id:medley_inc:20181130120407p:plain"></p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>2016 年から公開していたこちらのページも、時間と共に伝えたい内容が少しずつ変化してきていたこと、<a href="https://drive.medley.jp/">MEDLEY DRIVE</a>というプロジェクト公開に際して、より広範な分野のクリエイターにメドレーへの興味を持ってもらいたいことから、今回のリニューアルがスタートしました。</p>
<h1 id="何を伝えたかったのか">何を伝えたかったのか</h1>
<p>今回はインタビューを主軸にした構成となっていますが、これはエンジニアリングやデザインなどのスキルを持つ人が、社会の課題解決のためにその力を発揮したいと考えた時、このサイトを思い出してその背中を後押しできるようなサイトにしたい、と思ったのがきっかけになっています。</p>
<p>リニューアル前のクリエイターページでは Q&A 形式で業務内容などを紹介する構成でしたが、読んだ人の記憶に残るものにするためにも、各メンバーの境遇などから多くの人の共感を呼べるストーリーが必要になると思い、人物を主軸としたインタビュー構成にと考えました。</p>
<p>MEDLEY DESIGN & ENGINEERING というテーマから事前イメージとしてページのラフデザインなどの検討はしましたが、インタビューが主軸となる今回の制作はデザイン案作成よりもまず先に取材・撮影を行う<strong>エディトリアルドリブンでの進め方</strong>となりました。</p>
<h2 id="それぞれに志向するものがありつつもメドレーとしての課題解決に尽力していること">それぞれに志向するものがありつつも、メドレーとしての課題解決に尽力していること</h2>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181130/20181130120514.jpg" alt="f:id:medley_inc:20181130120514j:plain" title="f:id:medley_inc:20181130120514j:plain"></p>
<p>インタビューを進めていく内に見えてきたのは、自己成長のため、身近な医療体験など、それぞれに入社理由がありつつもメドレーのビジョンを理解し、<strong>医療ヘルスケア分野の課題解決のために尽力するメンバーの姿</strong>でした。</p>
<p>入り口は違えど、メドレーに参画して課題解決に取り組むまでのストーリーは「なぜメドレーで働くのか?」といった問いに寄り添うものになっていると思います。</p>
<h2 id="プロフェッショナルさと覚悟">プロフェッショナルさと覚悟</h2>
<p>メンバーへのインタビューの他にもエンジニア・デザイナーが所属する開発本部という組織のマインドセットを掲載しており、自律し自走する組織を目指す開発本部の目指すところが、各メンバーに浸透していることも読み取ることができます。</p>
<p>MEDLEY DESIGN & ENGINEERING というテーマの本質である課題解決のために職種やキャリアを問わないスキルの越境、組織パフォーマンスの最大化、社会への貢献といったキーワードが各自の言葉で語られています。この一貫性は「メドレーらしさ」や開発本部の文化を象徴しており、インタビューの中でも特筆すべきポイントになっています。</p>
<h1 id="そのために何をしたのか">そのために何をしたのか</h1>
<p>サイトで伝えたいこと・インタビューを通して伝えたいことが出てきたところでそれをどう表現するかですが、前述した通り今回はエディトリアルドリブンで大半の表現をすることができました。</p>
<h3 id="内省とストーリーのエディトリアル"><strong>内省とストーリーのエディトリアル</strong></h3>
<p>制作クレジットにある通り、今回は取材・編集に小山さん<a href="https://twitter.com/kkzyk">@kkzyk</a>、撮影に<a href="https://cargocollective.com/shunsukeimai">今井さん</a>を迎えてインタビュー取材と撮影を行っています。</p>
<p>対外的に情報の少ない社内の人間をまとめて取材するのは人物理解などハードルが高いですが果敢にも挑戦していただき、アウトプットもいわゆる A 対 B の対話形式ではなく、独白を交えたインタビュアー視点での編集となっており、人物に焦点を当てつつストーリーとして読み取りやすい文体に仕上げていただきました。</p>
<p>今井さんの表情を引き出す写真も相まって、メンバー自身のこれまでを振り返る内省とストーリーを垣間見るような編集が出来たのではないかと思います。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181130/20181130120537.jpg" alt="f:id:medley_inc:20181130120537j:plain" title="f:id:medley_inc:20181130120537j:plain"></p>
<h2 id="編集の熱量で作り上げたデザイン">編集の熱量で作り上げたデザイン</h2>
<p>編集のブラッシュアップとデザインや実装のブラッシュアップはほぼ同時進行で進めていき、デザインや文言の細かな調整などもその場で実装しなおしアップデートしていくスタイルで制作しました。</p>
<p>デザイン面では各クリエイターの内省とストーリーを読み手に伝えるために、読み心地や読後の余韻を残すべく余白を活かしたレイアウトにしました。</p>
<p>また、写真と文章を活かすようなデザインを心がけました。写真は大きいディスプレイでも迫力を追求するために全面を使いつつ、文章は 2 分割のレイアウトで文章幅を広げすぎず読みやすさを確保しました。</p>
<p>スマートフォンでも読みやすさを担保するため文字サイズにこだわり、結果としてどのデバイスでも読みやすく、文章と写真が伝わるためのデザインであることを心がけて制作しています。</p>
<p>このようなシンプルで装飾のないレイアウトで勝負できたのも、写真や文章と構成の力強さあってのものであり、こういった質実剛健さは<a href="/entry/2018/10/10/120246">オフィスのデザイン</a>などにも通じるメドレーらしさの表出となっているかもしれません。</p>
<h1 id="作ってみて">作ってみて</h1>
<p>元々はクリエイター採用ページとしてリニューアルを検討していましたが、インタビュー形式に振り切ったことで、今回 CREATOR’S STORY というサブタイトルをつけ、あからさまな採用色をなくしました。こうした実験的な編集やデザインとして表現できたのも、日頃から<a href="https://www.wantedly.com/feed/s/medley">入社理由シリーズ</a>や<a href="https://www.wantedly.com/feed/s/medley-int">聞いてみたシリーズ</a>などバラエティ豊かな情報発信を行っているメドレーの土台があるからと考えています(いつもありがとうございます!)。</p>
<p>ここまで書いたように、自身のスキルを社会の課題解決のために発揮したいと考えた時にふと思い出していただきたく、こういったページがあることを記憶に留めていただけましたら幸いです。</p>
<p>事業会社のデザイナーとして現在は「介護のほんね」をメインで担当しているのですが、クリエイターサイトやオフィス移転など、事業だけなくコーポレートに関わるさまざまなデザインに携わる機会があります。</p>
<p>このようにやるべきことが多くありチャンスとやりがいが溢れる今、ブランディングとプロダクトの両輪を同時に頑張りたい方、医療介護領域の課題解決のために腕を奮ってみたいと考える方、ぜひ気軽に弊社に遊びにきて話をしてみませんか?</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、医療介護の人材採用システム「ジョブメドレー」や、医師たちがつくるオンライン医療事典「MEDLEY」、医療につよい介護施設・老人ホームの検索メディア「介護のほんね」、オンライン診療アプリ「CLINICS」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="MEDLEY DESIGN&ENGINEERING" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>
<p>少しだけ興味があるという方も、ぜひお気軽にご連絡ください! お待ちしております。</p>medley
- クリエイターページに刻まれた「MEDLEY DESIGN & ENGINEERING」について紐解くhttps://developer.medley.jp/entry/2018/11/22/181939https://developer.medley.jp/entry/2018/11/22/181939はじめに
こんにちは。デザイナーマエダです。
メドレーのクリエイターページを最近リニューアルしたのですが、ご覧いただけましたでしょうか。
リニューアルまでの経緯は、デザインを担当した波切から後日このブログでエントリがあると思いますので詳細は...Thu, 22 Nov 2018 09:19:39 GMT<h1 id="はじめに">はじめに</h1>
<p>こんにちは。デザイナーマエダです。</p>
<p>メドレーの<a href="https://www.medley.jp/recruit/creative.html">クリエイターページ</a>を最近リニューアルしたのですが、ご覧いただけましたでしょうか。</p>
<p>リニューアルまでの経緯は、デザインを担当した波切から後日このブログでエントリがあると思いますので詳細は割愛させていただきますが、リニューアル以前から「MEDLEY DESIGN & ENGINEERING」というサイトタイトルが使われているのは、みなさんご存じだったでしょうか。</p>
<p>今回はこの「MEDLEY DESIGN & ENGINEERING」という言葉に秘められた意味について TechLunch で発表したのですが、事前に社内のエンジニアにヒアリングしてみたところ、この言葉の意味に気付いてない人もいたので、あらためて紐解いてみました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181122/20181122161427.png" alt="f:id:medley_inc:20181122161427p:plain" title="f:id:medley_inc:20181122161427p:plain"></p>
<h1 id="クリエイティブ業界の最近の風潮">クリエイティブ業界の最近の風潮</h1>
<p>今年 5 月に経済産業省・特許庁が「<a href="https://www.meti.go.jp/press/2018/05/20180523002/20180523002-1.pdf">デザイン経営</a>」宣言を公表しました。経営にデザインを活用した手法や効果、この手法を推進するための政策提言についてまとめられたものとなっており、ご存知の方もいらっしゃると思います。また、ジョン・マエダが発表した「<a href="https://ja.takram.com/projects/design-in-tech-report-2018-translation/">Design in Tech Report 2018</a>」などを見ても、特にインターネットテクノロジー業界におけるデザイナーのスタンスに変化が起こって来ているなと感じる昨今でもあります。</p>
<p>テクノロジー企業が急成長をしている中でも、特にデザインを強みとした企業が自社の独自性を出しつつ成長しているという状況で、今後どのようなスタイルのデザイナーが求められるのかという事を端的に示しているなと感じました。</p>
<p>他方で、インターネット業界でデザインに携わってそこそこの年数を経た私としては、クリエイティブを制作するうえで、デザインとテクノロジーは密接に関わっているという実感があり、常に 2 人 3 脚の関係性でお互い尊重しあいながらプロダクト開発をしてきました。</p>
<p>デザイン単体ではなくエンジニアリングと密に関わることでよいプロダクトになるという意識を持っているため、「デザイン」という概念だけで主張する風潮には、すこし違和感を覚える部分があったりします。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181122/20181122161449.png" alt="f:id:medley_inc:20181122161449p:plain" title="f:id:medley_inc:20181122161449p:plain"></p>
<h1 id="メドレーのクリエイティブ文化">メドレーのクリエイティブ文化</h1>
<p>メドレーはデザイン部という組織はありますが、プロダクトごとに個々デザイナーが担当しているため、デザイナー同士よりもエンジニアとのコミュニケーションが大半を占めます。</p>
<p>一方、プロダクトごとにビジネスモデルの理解や医療に関する知識など、基礎情報をインプットしておくことが重要ですので、十分な理解がないままに安易にペルソナを設計したりせず、事業をしっかり理解したうえでユーザーヒアリングや仮説検証を行うことも重視しています。</p>
<p>このようにメドレーのデザイナーは事業理解はもちろんのこと、テクノロジーの領域においても同様に理解をし、共通言語でコミュニケーションを取る必要があります。</p>
<p>メドレーのデザイナーは現在 3 名と少数ではありますが、BTC 人材として、それぞれ活躍しています。</p>
<p>「BTC 人材」とは、私が入社した当初からデザイナーとして意識している「ビジネス」「テクノロジー」「クリエイティブ」3 軸の領域を横断的に思考してデザインに結びつけるスタンスですが、ただ単に、自分の持つ領域を広げるだけではいいクリエイティブには繋がりません。あくまでデザイナーとしての本来の「軸」を持ちつつ、他の領域に対しても思考を広げていくことが重要なのですが、メドレーのデザイナーはこれを理解し実践しています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181122/20181122161516.png" alt="f:id:medley_inc:20181122161516p:plain" title="f:id:medley_inc:20181122161516p:plain"></p>
<p>エンジニアの方も逆に技術だけでなく、事業やクリエイティブへの理解を進め、プロダクトとしての質を高めるために、UI や UX の良し悪しについて我々デザイナーと議論をしています。よく聞くような、実装時にデザインとエンジニア同士で揉めたり、考え方の違いによるミスコミュニケーションに陥るということがありません。</p>
<p>より良いプロダクトを作るために、ユーザーに価値をどう提供すべきかお互いの領域に踏み込んでコミュニケーションがとれる文化は、デザイナー・エンジニアを含めたクリエイター組織として強みだと感じています。</p>
<h1 id="まとめ">まとめ</h1>
<p>このようにメドレーでは自分の専門分野と周辺分野を分け隔てることなく融合し、クリエイティブに結びつけようという文化が根付いています。この文化を一言で表わしているのが「MEDLEY DESIGN & ENGINEERING」という言葉になっているのです。</p>
<p>クリエイターページリニューアルの翌日に「<a href="https://drive.medley.jp/">MEDLEY DRIVE</a>」のリリースもさせていただきました。このプロジェクトは医療ヘルスケア分野において、インターネットテクノロジーの活用を推進するための支援を目的としたプロジェクトです。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181122/20181122161531.png" alt="f:id:medley_inc:20181122161531p:plain" title="f:id:medley_inc:20181122161531p:plain"></p>
<p>この MEDLEY DRIVE というプロジェクトを通して、医療の課題に対して、デザイナーがもっと活躍していく機会が増えていくと思うとワクワクします。</p>
<p>実際問題、カルテなどの医療システムは業務の性質上、複雑な構成になりやすかったりするのですが、そこにデザイナーが介入することによって医療機関や患者に対して、より価値あるプロダクトを推進していける分野でもあると思っています。</p>
<p>医療の課題に向き合って価値のあるプロダクトを創出したい方はもちろん、現在医療に興味がない方でも、気軽に弊社に遊びにきて話をしてみませんか?</p>
<p>お知らせ
メドレーでは、医療介護の人材採用システム「ジョブメドレー」や、医師たちがつくるオンライン医療事典「MEDLEY」、医療につよい介護施設・老人ホームの検索メディア「介護のほんね」、オンライン診療アプリ「CLINICS」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<p>ちょっと興味があるという方も、ぜひお気軽にご連絡ください!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="MEDLEY DESIGN&ENGINEERING" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>
<p><span style="font-weight: 400;"> </span></p>medley
- Android で HTML をいい感じで表示できるようにした話https://developer.medley.jp/entry/2018/10/30/190717https://developer.medley.jp/entry/2018/10/30/190717こんにちは。開発本部の CLINICS カルテ の開発を担当している @seka です。メドレーでは貴重な (?) エンジニアの若者枠として日々奮闘しております。
今回、開発本部で定期的に開催している勉強会「TechLunch」で、「And...Tue, 30 Oct 2018 10:07:17 GMT<p>こんにちは。開発本部の <a href="https://clinics.medley.life/karte">CLINICS カルテ</a> の開発を担当している @seka です。メドレーでは貴重な (?) エンジニアの若者枠として日々奮闘しております。</p>
<p>今回、開発本部で定期的に開催している勉強会「TechLunch」で、「Android で HTML をいい感じで表示できるようにした話」 という題で発表しましたので、その内容について紹介させていただきます。</p>
<h1 id="1-きっかけ">1. きっかけ</h1>
<p><a href="https://medley.life/">医師たちがつくるオンライン医療事典 MEDLEY (メドレー)</a> をアプリ化することができるか検証してみて欲しいという相談を受け、Android のモックを作成することになりました。</p>
<p>アプリらしい UI を目指して開発を進めていたのですが、MEDLEY では病気記事が CMS などに見られるような HTML 形式で管理されており、そのまま表示してもイメージしたようなデザインが実現できないかも…という課題に直面しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181030/20181030115802.png" alt="f:id:medley_inc:20181030115802p:plain" title="f:id:medley_inc:20181030115802p:plain"></p>
<h1 id="2-html-を表示するまで">2. HTML を表示するまで</h1>
<p>いくつかのステップに分解して、HTML の要素を Android のコンポーネントに置換していくことで対応する方針を立て、その実現可能性を調べました。大まかなフローはこんな感じです。</p>
<ol>
<li>事前に HTML と Android のコンポーネントの対応を決める</li>
<li>HTML を Kotlin でも扱える形式に変換する</li>
<li>HTML の要素を探索する</li>
<li>マッチした要素を Android のコンポーネントで置き換える それぞれのフローについて解説していきます。</li>
</ol>
<h2 id="1-事前に-html-と-android-のコンポーネントの対応表を作る">1. 事前に HTML と Android のコンポーネントの対応表を作る</h2>
<p>簡単にですが下表のような対応を決めます。(検証段階だったので、いくつかの要素は省略しています)</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181029/20181029222937.png" alt="f:id:medley_inc:20181029222937p:plain" title="f:id:medley_inc:20181029222937p:plain"></p>
<h2 id="2-html-を-kotlin-で扱える形式に変換する">2. HTML を Kotlin で扱える形式に変換する</h2>
<p>HTML を生の String として操作するのは流石に辛いので、Kotlin でも扱いやすいような形に変換します。</p>
<p>今回は <a href="https://github.com/jhy/jsoup?utm_source=android-arsenal.com&utm_medium=referral&utm_campaign=191">jhy/jsoup</a> という便利そうなライブラリを見つけたため、これを利用することにしました。</p>
<p>jsoup は Java 製の HTML パーサーで <a href="https://github.com/jhy/jsoup?utm_source=android-arsenal.com&utm_medium=referral&utm_campaign=191#example">example</a> にもあるように HTML を Kotlin でも扱いやすい形式に変換することができます。</p>
<p>下記の例は Wikipedia Root Node から小要素を探索していく様子です。</p>
<p>HTML タグの情報・親要素・内容のテキストを利用したいため保持しておきます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="kotlin"><code><span class="line"><span style="color:#569CD6">val</span><span style="color:#D4D4D4"> doc = Jsoup.</span><span style="color:#DCDCAA">connect</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"https://en.wikipedia.org/"</span><span style="color:#D4D4D4">).</span><span style="color:#DCDCAA">get</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#D4D4D4">doc.</span><span style="color:#DCDCAA">childNode</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">1</span><span style="color:#D4D4D4">).</span><span style="color:#DCDCAA">childNode</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">2</span><span style="color:#D4D4D4">).</span><span style="color:#DCDCAA">childNode</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">1</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#6A9955">/*</span></span>
<span class="line"><span style="color:#6A9955">{Element@5739} "<div id="mw-page-base" class="noprint"></div>"</span></span>
<span class="line"><span style="color:#6A9955"> attributes = {Attributes@5755}</span></span>
<span class="line"><span style="color:#6A9955"> baseUri = "https://en.wikipedia.org/wiki/Main_Page"</span></span>
<span class="line"><span style="color:#6A9955"> childNodes = {Collections$EmptyList@5720} size = 0</span></span>
<span class="line"><span style="color:#6A9955"> shadowChildrenRef = null</span></span>
<span class="line"><span style="color:#6A9955"> tag = {Tag@5756}</span></span>
<span class="line"><span style="color:#6A9955"> canContainInline = true</span></span>
<span class="line"><span style="color:#6A9955"> empty = false</span></span>
<span class="line"><span style="color:#6A9955"> formList = false</span></span>
<span class="line"><span style="color:#6A9955"> formSubmit = false</span></span>
<span class="line"><span style="color:#6A9955"> formatAsBlock = true</span></span>
<span class="line"><span style="color:#6A9955"> isBlock = true</span></span>
<span class="line"><span style="color:#6A9955"> preserveWhitespace = false</span></span>
<span class="line"><span style="color:#6A9955"> selfClosing = false</span></span>
<span class="line"><span style="color:#6A9955"> tagName = "div"</span></span>
<span class="line"><span style="color:#6A9955"> parentNode = {Element@5729}</span></span>
<span class="line"><span style="color:#6A9955"> siblingIndex = 1</span></span>
<span class="line"><span style="color:#6A9955">*/</span></span></code></pre>
<h2 id="3-html-の要素を探索する">3. HTML の要素を探索する</h2>
<p>HTML の要素を探索する 再帰を利用して Root Node から小要素を探索し、jsoup で置き換えていきます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="kotlin"><code><span class="line"><span style="color:#569CD6">class</span><span style="color:#4EC9B0"> HTMLConverter</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">private</span><span style="color:#569CD6"> val</span><span style="color:#D4D4D4"> html: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> fun</span><span style="color:#DCDCAA"> parse</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#569CD6"> val</span><span style="color:#D4D4D4"> body = Jsoup.</span><span style="color:#DCDCAA">parse</span><span style="color:#D4D4D4">(html).</span><span style="color:#DCDCAA">normalise</span><span style="color:#D4D4D4">().</span><span style="color:#DCDCAA">body</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#DCDCAA"> inspect</span><span style="color:#D4D4D4">(body, </span><span style="color:#DCDCAA">ElementViewHolder</span><span style="color:#D4D4D4">())</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> private</span><span style="color:#569CD6"> fun</span><span style="color:#DCDCAA"> inspect</span><span style="color:#D4D4D4">(parent: </span><span style="color:#4EC9B0">Element</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#D4D4D4"> parent.</span><span style="color:#DCDCAA">children</span><span style="color:#D4D4D4">().</span><span style="color:#DCDCAA">forEach</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (parent.children.</span><span style="color:#DCDCAA">isEmpty</span><span style="color:#D4D4D4">()) {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4">@forEach</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#DCDCAA"> inspect</span><span style="color:#D4D4D4">(it)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<h2 id="4-マッチした要素を-android-のコンポーネントで置き換える">4. マッチした要素を Android のコンポーネントで置き換える</h2>
<p>事前に定義した方法に従って、HTML をそれぞれ対応する Android コンポーネントに変換していきます。</p>
<p>Img 要素の場合:</p>
<p>Glide を利用して画像を非同期で取得し ImageView にラップする。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="kotlin"><code><span class="line"><span style="color:#569CD6">private</span><span style="color:#569CD6"> fun</span><span style="color:#DCDCAA"> convertImage</span><span style="color:#D4D4D4">(el: </span><span style="color:#4EC9B0">Element</span><span style="color:#D4D4D4">): </span><span style="color:#4EC9B0">View</span><span style="color:#D4D4D4">? {</span></span>
<span class="line"><span style="color:#569CD6"> val</span><span style="color:#D4D4D4"> url = HttpUrl.</span><span style="color:#DCDCAA">parse</span><span style="color:#D4D4D4">(el.</span><span style="color:#DCDCAA">absUrl</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"src"</span><span style="color:#D4D4D4">)) ?: </span><span style="color:#C586C0">return</span><span style="color:#569CD6"> null</span><span style="color:#C586C0"> return</span><span style="color:#DCDCAA"> ImageView</span><span style="color:#D4D4D4">(context).</span><span style="color:#DCDCAA">apply</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#D4D4D4"> layoutParams = LinearLayout.</span><span style="color:#DCDCAA">LayoutParams</span><span style="color:#D4D4D4">(MATCH_PARENT, WRAP_CONTENT)</span></span>
<span class="line"><span style="color:#569CD6"> val</span><span style="color:#D4D4D4"> header = LazyHeaders.</span><span style="color:#DCDCAA">Builder</span><span style="color:#D4D4D4">().</span><span style="color:#DCDCAA">addHeader</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"Content-Type"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"image/bmp"</span><span style="color:#D4D4D4">).</span><span style="color:#DCDCAA">build</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#569CD6"> val</span><span style="color:#D4D4D4"> glideUrl = </span><span style="color:#DCDCAA">GlideUrl</span><span style="color:#D4D4D4">(url.</span><span style="color:#DCDCAA">url</span><span style="color:#D4D4D4">(), header)</span></span>
<span class="line"><span style="color:#D4D4D4"> Glide.</span><span style="color:#DCDCAA">with</span><span style="color:#D4D4D4">(context)</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">asBitmap</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">load</span><span style="color:#D4D4D4">(glideUrl)</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">into</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">this</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>Table 要素の場合:</p>
<p>Table と TableRow をそれぞれ作成して合成する。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="kotlin"><code><span class="line"><span style="color:#569CD6">private</span><span style="color:#569CD6"> fun</span><span style="color:#DCDCAA"> parseTable</span><span style="color:#D4D4D4">(el: </span><span style="color:#4EC9B0">Element</span><span style="color:#D4D4D4">): </span><span style="color:#4EC9B0">View</span><span style="color:#D4D4D4">? {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#DCDCAA"> TableLayout</span><span style="color:#D4D4D4">(context).</span><span style="color:#DCDCAA">apply</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#D4D4D4"> layoutParams = LinearLayout.</span><span style="color:#DCDCAA">LayoutParams</span><span style="color:#D4D4D4">(MATCH_PARENT, WRAP_CONTENT)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">private</span><span style="color:#569CD6"> fun</span><span style="color:#DCDCAA"> createTableRow</span><span style="color:#D4D4D4">(): </span><span style="color:#4EC9B0">View</span><span style="color:#D4D4D4">? {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#DCDCAA"> TableRow</span><span style="color:#D4D4D4">(context)</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">private</span><span style="color:#569CD6"> fun</span><span style="color:#DCDCAA"> composeTableRow</span><span style="color:#D4D4D4">(self: </span><span style="color:#4EC9B0">TableRow</span><span style="color:#D4D4D4">, parent: </span><span style="color:#4EC9B0">TableLayout</span><span style="color:#D4D4D4">): </span><span style="color:#4EC9B0">Boolean</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#D4D4D4"> table.</span><span style="color:#DCDCAA">addView</span><span style="color:#D4D4D4">(self)</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#569CD6"> true</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<h1 id="android-emulator-で表示してみる">Android Emulator で表示してみる</h1>
<p>上記の方法で作成したモックがこちらになります。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181029/20181029223014.gif" alt="f:id:medley_inc:20181029223014g:plain" title="f:id:medley_inc:20181029223014g:plain"></p>
<p>ただ HTML を表示するだけであれば WebView を利用すればもっと楽に表示することもできますが、UI をカスタマイズしたい場合にはデータに変更を加えなければいけません。</p>
<p>今回のようなアプローチをとることで、もともとある HTML のデータを壊すことなく Android に適したデザインを実現しやすくなったのではないでしょうか。</p>
<h1 id="さいごに">さいごに</h1>
<p>コミュ障故に人前で話すことを避けてきたのですが、 TechLunch という機会をいただき Android なネタを発表をさせていただきました。</p>
<p>今回作成したモックは技術検証目的であるためリリースの予定はありませんが、発表のために書いた実装例は <a href="https://gist.github.com/seka/46b042df4ae8b37b86edce3f5ff83b9a">seka/HTMLConverter.kt</a> として公開しています。</p>
<p>このようなアプローチを取る機会は少ないと思いますが、読んでくださった方の力になれれば幸いです!</p>medley
- 「とりまわかる TTS」というお話https://developer.medley.jp/entry/2018/10/19/114955https://developer.medley.jp/entry/2018/10/19/114955こんにちは、開発本部の宮内です。先日のメドレーの社内勉強会「TechLunch」で、「とりまわかる TTS」と題して Web Speech API のお話をしました。
Web Speech API とは?
macOS に、sayというコマン...Fri, 19 Oct 2018 02:49:55 GMT<p>こんにちは、開発本部の宮内です。先日のメドレーの社内勉強会「TechLunch」で、「とりまわかる TTS」と題して Web Speech API のお話をしました。</p>
<h1 id="web-speech-api-とは">Web Speech API とは?</h1>
<p>macOS に、<code>say</code>というコマンドがあるのはご存知でしょうか? このコマンドは引数で受け取った文字列を発音してくれるというコマンドです。</p>
<p>ターミナルアプリを開いて、次のようなコマンドを入力してみてください。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">say</span><span style="color:#569CD6"> -v</span><span style="color:#CE9178"> Kyoko</span><span style="color:#CE9178"> "ご用件は何でしょう?"</span></span></code></pre>
<p>このようにテキストから人間の声のように発音させる仕組みを<a href="https://ja.wikipedia.org/wiki/%E9%9F%B3%E5%A3%B0%E5%90%88%E6%88%90">音声合成</a>といいます。</p>
<p>このような音声合成や<a href="https://ja.wikipedia.org/wiki/%E9%9F%B3%E5%A3%B0%E8%AA%8D%E8%AD%98">音声認識</a>に関しては macOS 以外にも<a href="https://aws.amazon.com/jp/transcribe/">Amazon Transcribe</a>や<a href="https://cloud.google.com/speech-to-text/docs/reference/rest/?hl=ja">Google Cloud Speech API</a>などのクラウドサービスや Android では<a href="https://developer.android.com/reference/android/speech/tts/TextToSpeech">TextToSpeech クラス</a>という API が用意されていたりもします。</p>
<p>今回の発表で使った<a href="https://w3c.github.io/speech-api/">Web Speech API</a>は、ブラウザでこれらの音声合成・認識を行うための API 仕様です。</p>
<h1 id="実際に試してみる">実際に試してみる</h1>
<p>音声合成の<a href="https://miyucy.github.io/tts.html">サンプルページ</a>を作りましたので、サンプルプログラムを抜粋して使い方の説明をしていきます。</p>
<h2 id="voiceschanged-イベント内で利用可能な音声を取得する">voiceschanged イベント内で利用可能な音声を取得する</h2>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#9CDCFE">constspeechSynthesis</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">window</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">speechSynthesis</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">speechSynthesis</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">addEventListener</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"voiceschanged"</span><span style="color:#D4D4D4">, () </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#DCDCAA"> buildVoiceOption</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">$voices</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">speechSynthesis</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getVoices</span><span style="color:#D4D4D4">());</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span></code></pre>
<p><code>window.speechSynthesis.getVoices</code>関数を使うと利用可能な音声の一覧が取得できます。</p>
<p>ただし、ページロード直後ですと、タイミングによっては空配列が帰ってくることがあります。 そのため、<code>voiceschanged</code>イベントを受け取ってから、<code>window.speechSynthesis.getVoices</code>関数を呼び出すことによって、確実に実行できるようにしています。</p>
<p>返却されてくる voice は仕様としてはユーザエージェントとブラウザの場合だとローカルに用意されている音声の種類で決ってくることになっています。</p>
<p>macOS の日本語であれば以下のような種類の voice が返ってきます。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181019/20181019115926.png" alt="f:id:medley_inc:20181019115926p:plain" title="f:id:medley_inc:20181019115926p:plain"></p>
<p><code>buildVoiceOption</code>は利用する音声を select タグで選択できるように設定する関数なので割愛します。</p>
<h2 id="speechsynthesisutteranceで音声の設定をしていく"><a href="https://developer.mozilla.org/ja/docs/Web/API/SpeechSynthesisUtterance">SpeechSynthesisUtterance</a>で音声の設定をしていく</h2>
<p>SpeechSynthesisUtterance とは以下のような働きをする API になります。</p>
<blockquote>
<p>Web Speech API の SpeechSynthesisUtterance インターフェイスは、音声要求を表現します。 これには、音声サービスが読むべき内容とその読み方に関する情報(例えば、言語、ピッチ、音量など)が含まれています。</p>
<p><a href="https://developer.mozilla.org/ja/docs/Web/API/SpeechSynthesisUtterance">https://developer.mozilla.org/ja/docs/Web/API/SpeechSynthesisUtterance</a></p>
</blockquote>
<p><code>SpeechSynthesis</code>を使って実際に音声を発音させるにはこの API を使ってどのように発音させるのか?を設定していく必要があります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#569CD6">function</span><span style="color:#DCDCAA"> setVoice</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">utterance</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">voices</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">voiceName</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#9CDCFE"> constchoices</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">voices</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">filter</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">voice</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#9CDCFE"> voice</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">name</span><span style="color:#D4D4D4"> === </span><span style="color:#9CDCFE">voiceName</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">choices</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">length</span><span style="color:#D4D4D4"> > </span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#9CDCFE"> utterance</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">voice</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">choices</span><span style="color:#D4D4D4">[</span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">];</span></span>
<span class="line"><span style="color:#D4D4D4"> } </span><span style="color:#C586C0">else</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> defaultVoice</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">voices</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">find</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">voice</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#9CDCFE"> voice</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">default</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">defaultVoice</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#9CDCFE"> utterance</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">voice</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">defaultVoice</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">$form</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">addEventListener</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"submit"</span><span style="color:#D4D4D4">, (</span><span style="color:#9CDCFE">event</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> event</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">preventDefault</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#569CD6"> const</span><span style="color:#4FC1FF"> utterance</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">new</span><span style="color:#DCDCAA"> SpeechSynthesisUtterance</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">$textarea</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">value</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#DCDCAA"> setVoice</span><span style="color:#D4D4D4">(</span></span>
<span class="line"><span style="color:#9CDCFE"> utterance</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> speechSynthesis</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getVoices</span><span style="color:#D4D4D4">(),</span></span>
<span class="line"><span style="color:#9CDCFE"> $voices</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">selectedOptions</span><span style="color:#D4D4D4">[</span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">].</span><span style="color:#9CDCFE">dataset</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">name</span></span>
<span class="line"><span style="color:#D4D4D4"> );</span></span>
<span class="line"><span style="color:#9CDCFE"> utterance</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">pitch</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">$pitch</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">valueAsNumber</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#9CDCFE"> utterance</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">rate</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">$rate</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">valueAsNumber</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#9CDCFE"> utterance</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">volume</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">$volume</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">valueAsNumber</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#9CDCFE"> speechSynthesis</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">speak</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">utterance</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span></code></pre>
<p>フォームがサブミットされたら <code>SpeechSynthesisUtterance</code>クラスのインスタンスを作成し、 それを引数に<code>speechSynthesis.speak</code>関数を呼び出せば、テキストボックスに入力されたテキストと<code>SpeechSynthesisUtterance</code>で設定したピッチ、音量などを元に設定された音声が出力されるようにしています。</p>
<p>(サンプルプログラムではエラー処理を省いているため、ピッチや音量の調整スライダーで極端な値を指定すると、正しく音声が出力されないことがあります)</p>
<p>今回の記事では紹介していませんが、Web Speech API にはもうひとつ<a href="https://developer.mozilla.org/ja/docs/Web/API/SpeechRecognition">SpeechRecognition API</a>があり、こちらは音声認識をブラウザでできるようにする API になっています。興味があればぜひ調べてみてください。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回は Web Speech API の音声合成のさわりを紹介しましたが、ご覧のとおりとても簡単に使うことができます。</p>
<p>例えば、ブラウザの内容の読み上げをしてアクセシビリティを高めたりなどの使い方や、読みが難しい専門用語の発音を聞かせるようにするなど色々な使いかたが考えられるのではないでしょうか?</p>
<p>この記事をご覧のみなさんもちょっと忘れかけられた、この API を使ってみてはいかがでしょう?</p>medley
- 新オフィスのデザインを任された話https://developer.medley.jp/entry/2018/10/10/120246https://developer.medley.jp/entry/2018/10/10/120246先日、新卒エンジニアさんの**内定式(メドレー初!)**をおこないました。バキバキでキラキラのイケメン揃いなので中途イケメン枠で入社した私にとってこれまでの地位が危ぶまれ戦々恐々としています。開発本部デザイナーの小山です。
内定式もですが、...Wed, 10 Oct 2018 03:02:46 GMT<p>先日、新卒エンジニアさんの**内定式(メドレー初!)**をおこないました。バキバキでキラキラのイケメン揃いなので中途イケメン枠で入社した私にとってこれまでの地位が危ぶまれ戦々恐々としています。開発本部デザイナーの小山です。</p>
<p>内定式もですが、大きな変化がメドレーにありデザイナーの役割が広がる機会が訪れたので、この場を借りてお話させていただきます。</p>
<p>近年デザイナーは分野を飛び越えた取りくみが求められる職種になりつつあります。クラシカルデザインが中心だった頃、それができるのはスターデザイナーであり、ごく限られた人たちでした。いまではテクノロジーやフレームワークの進歩により、デザイン思考やコンピュテーショーナルデザインなど、デザイナーが関われる分野はさらに広がりを見せています。いちデザイナーの私もそれに応えたいという想いはありつつも取りくむ難しさを感じています。</p>
<p>今回は分野を飛び越える難しさを日頃感じている UI デザイナーの私が、<strong>オフィスデザインという普段とは異なる分野</strong>に取りくんだお話をさせていただきます。</p>
<h1 id="これからの受け皿を設計する">これからの受け皿を設計する</h1>
<p>実は先月 9 月上旬に、メドレーは古巣の新六本木ビルを離れ六本木グランドタワーへ<a href="https://www.wantedly.com/companies/medley/post_articles/133871">本社移転</a>しました。私が入社した去年 3 月時点から社員数が 3 倍近く増えたので、「<strong>社員数拡大のためのキャパシティ確保</strong>」「<strong>設備の充実</strong>」「<strong>組織の一体感の強化</strong>」が主に取りくむべき移転の課題でした。その解決のためオフィス専門の設計チームが初期のアーキテクトと施工管理を担当し、空間の方針とデザインと什器のディレクションを<a href="https://unleash.tokyo/2018/08/02/medley_clinics_karte/"><strong>マエダ</strong></a>と私が担当しました。</p>
<p>先だって取りくんだのは空間の方針です。この先のメドレーの姿を踏まえ、そこに到達するための受け皿となるように設計しました。</p>
<p>これまでメドレーは社会の公器としての意識を持ち、様々なサービスで医療課題に取りくんできました。それが今年で創業 10 年を迎えています。規模も拡大し、さまざまな人がこのメドレーに参画するようになりベンチャー企業としてだけでなく社会の大きな責任を果たす存在になりつつあります。</p>
<p>そうしたときの空間の役割として<strong>これまでのベンチャーマインドとこれからの大きな責任を背負う姿勢の両方を意識できる空間づくり</strong>が良いのではと考え以下のような方針を組み立てました。</p>
<h1 id="可変と不変の両極を横断する空間">可変と不変の両極を横断する空間</h1>
<p>急速に成長するためのベンチャーマインドと、拡大していく社会の公器として責任を負う姿勢を同居させるために、ただ新しくするだけではなく、今までのメドレーはしっかり持ち合わせる。そしてそれを日々の業務のなかで行き来できるように空間の方針をつくり、デザインに反映していきました。もちろん制約が多く結果論的な部分はありますが、新しくするために全てを壊すのではなく、かといって古いものを大事に取っておくのでもない、<strong>これまでのメドレーらしさとこれからのメドレーの2つをポジティブに意識し設計</strong>しました。</p>
<p>執務室は旧本社ビルの雰囲気をそのままスライドさせつつ、先に挙げた 3 つの課題を取りくむため機能を拡張しました。もっとも変えたのはエントランスから会議室にかけてです。コーポレートカラーである赤色を一切使わず真っ白な空間にしました。執務室が変えない場所であるなら、ここは変える場所であり、自分たちを一度否定し絶えず新しくしていく場所として位置付けました。これから様々な人と出会える場所としても日々新鮮な気持ちになれると考えたからです。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181010/20181010113717.png" alt="f:id:medley_inc:20181010113717p:plain" title="f:id:medley_inc:20181010113717p:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181010/20181010113722.jpg" alt="f:id:medley_inc:20181010113722j:plain" title="f:id:medley_inc:20181010113722j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181010/20181010113729.png" alt="f:id:medley_inc:20181010113729p:plain" title="f:id:medley_inc:20181010113729p:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181010/20181010113741.png" alt="f:id:medley_inc:20181010113741p:plain" title="f:id:medley_inc:20181010113741p:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20181010/20181010113736.png" alt="f:id:medley_inc:20181010113736p:plain" title="f:id:medley_inc:20181010113736p:plain"></p>
<h1 id="空間デザインの独特な難しさと向きあう">空間デザインの独特な難しさと向きあう</h1>
<p>この空間をつくるために、この分野特有の壁と向きあいました。UI デザインでは扱わない大きなサイズ感や専門知識を総動員して出来上がる空間を想像する力など、普段の仕事にはない技術や感覚を求められることが多く、独特の難しさを痛感しました。</p>
<p>たとえば UI デザインでは簡単にモックアップがつくれますが空間で「よし試しに壁たてるか!」なんてことはできません。仮にモックアップをつくれたとしても UI デザインほどの情報量で仕上がることはなく、そこは培ってきた経験と知識で補わなければなりません。専門のチームがいるので、えーい丸投げ!あとヨロシク!という考えも頭をよぎりましたが、決めるのは私の役目でもあるので、円滑なコラボレーションにするために、その人たちの知識や感覚に追いつくことは急務でした。</p>
<p>ここまでの話を聞くと体力的にキツそうと思われるかもしれませんが、実際は現業と通じる部分やデザイナーの感覚を分野を超えて持ち込める部分もありましたので、楽しみながら取りくめました。例えばデザイナーは物の形や色の違いに敏感な種族なので、それが 2D でも 3D でもすぐさま察知できます(程度にもよりますが)。図面と比較してわずかな壁の色や照明の輝度や色、わずかな目地のズレなどなど。</p>
<p>とはいえ異なる分野で直感1つで勝負できないのも理解しました。まだまだ学びは多そうですが、この取りくみを日頃の仕事にフィードバックしたいと思います。下記は取りくみを通して同じような機会を得たときに使える備忘録としてまとめてみたポイントです。</p>
<ul>
<li><strong>考えかたの定型化</strong> - 何かをデザインするのなら進め方に違いはないはず。違っても自分の考えかたの型をもとにカスタマイズする</li>
<li><strong>礼儀としての知識</strong> - よほど未開拓でない限りその分野の専門家がいるはず。コラボレーションするために最低限の礼儀として知識は身につける</li>
<li><strong>違和感を無視しない</strong> - その分野の常識や知識のインプットが追いつかなくても、そこで感じる引っかかりを共有することで周りが立ち返れる</li>
</ul>
<h1 id="分野が違うだけで別世界ではない">分野が違うだけで別世界ではない</h1>
<p>今回は分野を変えることで発見したことや取りくみを、オフィスデザインを通してお話させていただきました。振りかえると備忘録の 3 つや思考の整理の方法など、異なる分野だとしても、自分の専門分野での大事な考え方と共通する部分も多く感じました。もちろん全ての分野を渡り歩いたわけではないので当てはまらない場合はありますし、先述したように独特の難しさもあります。ただこの 3 つがどんな分野でも飛び越えれる最初の道具のうちの1つにすることで、良いスタートが切れるのかなと私は感じています。</p>
<p>異なる分野は今までの常識が全く通用しない別の世界ではないことを意識しながら、新しいことに挑戦していきたいと思います。</p>
<p>ここまで読んでいただき、ありがとうございました!</p>
<h1 id="さいごに">さいごに</h1>
<p>メドレーが向き合う医療の課題は複雑です。課題を解決するために1つの分野からの一点突破もありますが、多角的な分野からの突破もおこなっています。デザイナーでありながら、エンジニアでありながら、様々な分野に横断しスイッチでき課題解決に向き合うことができます。もしご興味のある方はぜひご連絡ください。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="CREATOR'S STORY | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fteam%2Fcreator-story.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/team/creator-story.html">www.medley.jp</a></cite>medley
- 松江で再びサテライトオフィス体験してきましたhttps://developer.medley.jp/entry/2018/09/28/135838https://developer.medley.jp/entry/2018/09/28/135838お疲れ様です。開発本部の宍戸です。
9/11〜9/14 まで、昨年もお世話になった松江市でお試しサテライトオフィス勤務を行ってきました。
昨年度は総務省のサテライトオフィス事業を利用する形でしたが、今年は松江市独自のプロジェクトとして実施さ...Fri, 28 Sep 2018 04:58:38 GMT<p>お疲れ様です。開発本部の宍戸です。</p>
<p>9/11〜9/14 まで、<a href="/entry/2017/05/23/171859">昨年もお世話になった</a>松江市でお試しサテライトオフィス勤務を行ってきました。</p>
<p>昨年度は<a href="https://www.soumu.go.jp/satellite-office/">総務省のサテライトオフィス事業</a>を利用する形でしたが、今年は<a href="https://www1.city.matsue.shimane.jp/jigyousha/sangyou/kigyou/otameshi.html">松江市独自のプロジェクト</a>として実施されるとのことで再度松江市さんからお声がけをいただき、開発本部から 3 名でサテライトオフィス勤務をしてきましたので、その様子をレポートします。</p>
<h1 id="オフィスの様子など">オフィスの様子など</h1>
<p>今回のサテライトオフィス勤務は、全日程を<a href="https://www.sanbg.com/terrsa/outline/index.html">松江テルサ別館</a>で行いました。昨年もお世話になったこちらの施設ですが、開発に必要なものは基本的にすべて揃っているので、到着してすぐに作業することができました。(なぜ真ん中の卓に全員座らなかったのか、仲悪すぎだろと言われたりしますが決してそういうわけではありません(真顔))</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/L/Layzie/20180928/20180928172848.jpg" alt="f:id:Layzie:20180928172848j:plain" title="f:id:Layzie:20180928172848j:plain"></p>
<p>(写真は、今回アテンドしてくださった<a href="https://www.facebook.com/photo.php?fbid=731347827201388&set=a.304643019871873&type=3&theater">松江市の土江さん</a>の許可をいただき使わせていただきました)</p>
<p>こちら松江テルサ別館には、<a href="https://www1.city.matsue.shimane.jp/jigyousha/sangyou/ruby/rabo_open.html">松江オープンソースラボ</a>という OSS に関する作業や交流のために提供される施設があります。今回伺ったタイミングでは、このラボのエリアは、区画を広げるべく現在改装中でした。</p>
<p>話を伺うと、<strong>松江市にサテライトオフィスを構える Web 系企業も徐々に増えつつある</strong>ようで、現地での勉強会の開催などに利用しやすいよう今回の改装を行っているとのこと。(大部屋として使ったり、分割して使ったりなどなどできるようにしているそうです)</p>
<p>働く環境だけでなく、コミュニティ支援についても市が積極的にサポートしていく雰囲気を伺い知ることができ、あらためて<strong>Ruby City としての気概</strong>を感じました。</p>
<h1 id="松江の雰囲気など">松江の雰囲気など</h1>
<p>松江への到着は初日のお昼頃でしたので、まずは前回も伺った<a href="https://tabelog.com/shimane/A3201/A320101/32000017/">八雲庵</a>さんでお蕎麦をいただきました。松江城のお堀のすぐ近くにあり、歴史ある雰囲気が印象的でした。割子そばという、何段かに分けられたそばに直接つゆを注いで食べるスタイルが、こちらのメジャーな食べ方とのこと。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/L/Layzie/20180928/20180928172901.jpg" alt="f:id:Layzie:20180928172901j:plain" title="f:id:Layzie:20180928172901j:plain"></p>
<p>お昼を食べた後、再びサテライトオフィスで開発をした後は、前回も伺った現地の居酒屋、<a href="https://tabelog.com/shimane/A3201/A320101/32000087/">根っこや</a>さんで夕食をいただきました。</p>
<p>今回もコーディネーターの方のオススメで地酒の「<a href="https://www.ouroku.com/">王祿の渓</a>」をいただきました。期間中はいくつかのお店を回りましたが、日本海の魚から宍道湖のしじみ、そば、地酒などなど、美味しいものに困らない土地だなーという印象が強く、ごはん(とお酒)大好き人間の自分には魅力的なものばかりでした。</p>
<p>前回お邪魔した古民家風オフィス「<a href="https://www.soumu.go.jp/satellite-office/shimane/post1.html">松江城下</a>」なども、お昼休憩の際に簡単に案内していただきましたが、すでにこちらは<strong>サテライトオフィスとして企業が契約をし、稼働を開始している</strong>とのこと。(個人的にはちょっと中を覗いてみたかったので残念…)</p>
<p>松江城を囲むお堀も観光スポットの一つとのことで、最終日には松江城と共にこちらも体験させていただきました。仕事の疲れを史跡巡りで癒やされに来るのも良さそうです。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/L/Layzie/20180914/20180914101033.jpg" alt="f:id:Layzie:20180914101033j:plain" title="f:id:Layzie:20180914101033j:plain"></p>
<h1 id="定番のお参り">定番のお参り</h1>
<p>最終日には、プロダクトの成功祈願も兼ねて出雲大社にお参りに行ってきました。(出雲大社は医療の神様とも言われる<a href="https://www.izumooyashiro.or.jp/about/ookami">大国主命</a>をお祀りしており、その出自は<a href="https://www.izumooyashiro.or.jp/about/inaba">因幡の白うさぎ</a>という古事記の一節とのこと。不勉強にてこのタイミングで知りました・・・)</p>
<p>当日はあいにくのお天気でしたが、雨の中濡れる静かな出雲大社も厳かな雰囲気があり、パワーを沢山いただいてきました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/L/Layzie/20180914/20180914134135.jpg" alt="f:id:Layzie:20180914134135j:plain" title="f:id:Layzie:20180914134135j:plain"></p>
<h1 id="まとめ">まとめ</h1>
<p>以上、簡単ではありますが、松江市でのサテライトオフィスお試し勤務の様子を振り返ってみました。</p>
<p>昨年のブログでも書かれていますが、<strong>思ったよりも都内からのアクセスが良い</strong>なと思いましたし、街自体もおちついて静かなので(テルサ別館が駅前にあるのに、日中は電車の音くらいしか気にならない)、非常に集中して作業することができました。</p>
<p>また普段一緒に机を並べて作業しているチームから一時的とはいえ離れて作業をしてみて、<strong>隔離された環境での集中しやすさを得た一方、やはり面と向かってメンバーと話したほうが円滑なコミュニケーション取れる</strong>なということも改めて実感しました。(実際にこの期間中、東京のメンバーに色々動いてもらっていたのに、自分はリモートだったので、もどかしく感じる部分もありました・・・💦)</p>
<p>短期間ですが、個人ではなくスモールチームでリモートワークをしてみて良いところ、対面のほうが良いところを実際に把握できたのも収穫でした。</p>
<h1 id="さいごに">さいごに</h1>
<p>今回のお試し勤務は、松江市役所の土江さんにコーディネートいただき、実現することができました。</p>
<p>スケジュール等々きっちり準備頂いたおかげもあり、滞りなく業務を行うことができましたし、ただただオフィスに籠もって開発をしていただけではきっとわからなかった、松江という場所の雰囲気なども知ることができました。土江さんをはじめ、今回お世話になりました皆様、改めて本当にありがとうございました!</p>
<p>ということで、簡単ではありますが、第二回松江市でのサテライトオフィスお試し勤務のレポートでした。</p>
<p>メドレーでは、エンジニア・デザイナーを絶賛募集中です。ご興味ある方は、こちらからぜひご連絡ください。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="株式会社メドレーの会社情報 - Wantedly" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fmedley" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.wantedly.com/companies/medley">www.wantedly.com</a></cite>medley
- iOSDC Japan 2018 にメドレーが協賛しましたhttps://developer.medley.jp/entry/2018/09/13/175702https://developer.medley.jp/entry/2018/09/13/175702こんにちは、開発本部の高井です。
メドレーは、去年に引き続き 8/30〜9/2 に早稲田大学で開催された iOSDC Japan 2018(以下 iOSDC)に協賛しました。
みなさんご存知かと思いますが、iOSDC は国内の iOS イベ...Thu, 13 Sep 2018 08:57:02 GMT<p>こんにちは、開発本部の高井です。</p>
<p>メドレーは、去年に引き続き 8/30〜9/2 に早稲田大学で開催された <a href="https://iosdc.jp/2018/">iOSDC Japan 2018</a>(以下 iOSDC)に協賛しました。
みなさんご存知かと思いますが、iOSDC は国内の iOS イベントの中では try! Swift と並ぶ最大級のイベントです。
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180913/20180913175434.png" alt="20180913175434.png"></p>
<p>(オンライン診療アプリ「CLINICS」初期開発時から Swift で実装しています)</p>
<p><a href="https://itunes.apple.com/jp/app/id1106261604">CLINICS(クリニクス) オンライン診療・服薬指導アプリ</a></p>
<p>メドレーは今回、シルバースポンサーとして協賛させていただきました。ブース出展はしていないのですが、スポンサー枠で私が参加してきました。</p>
<p>オープニングのスポンサー紹介は去年に引き続き今年も三石琴乃さんのナレーションでした!豪華です。</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Wy6XCIDJRIU" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
<h1 id="イベントの様子">イベントの様子</h1>
<p>ランチのお弁当はもちろん、朝はドーナツ、夕方の LT が始まるとビールが提供されるなど至れり尽くせりでした。セッションは最大 4 つが同時に進み、それに加えてアンカンファレンスと特定のテーマを設けたディスカッション企画などもあったのですが、進行も非常に円滑で非常に参加者の満足度の高いカンファレンスではないかと思います。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180913/20180913175617.png" alt="20180913175617.png">
<h1 id="セッション">セッション</h1>
<p>今年は Swift のコンパイラのソースから Swift の機能を解説するようなセッションがいくつかありました。去年はそのようなセッションはなかったのではないかと思います。Swift がオープンソース化されて 3 年弱ぐらいになり、日本でも Swift のコミュニティがより成熟しているように感じました。
また、機械学習などのキャッチーなトピックの話題が少し減って、業務上得た知見や設計についてなどの実用的なテーマが多かった印象でした。
特に View の設計や実装についてのセッションが面白かったです。やはり、iOS アプリ開発では View 周りの実装が悩みの多い領域ですよね。</p>
<p>ここからは特に気になったセッションをいくつかご紹介します。</p>
<h2 id="microviewcontroller-で無限にスケールする-ios-開発">MicroViewController で無限にスケールする iOS 開発</h2>
<div class="remark-link-card-plus__container">
<a href="https://www.icloud.com/keynote/0vgTYDXyHQTd0l1FKTiF1jT7g#MicroViewController-en" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">MicroViewController-en</div>
<div class="remark-link-card-plus__description">Shared by 齋藤暢郎</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.icloud.com/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.icloud.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://p50-iworkthumbnailws.icloud.com/iw/ws/thumbnail?short_token=0vgTYDXyHQTd0l1FKTiF1jT7g&maxWidth=2048&forceLandscape=true&cropAspectRatioHint=2&socialMetaTagsForApp=keynote" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>UI パーツごとに ViewController を持たせて、一つの画面の異なる機能を複数のエンジニアで開発しやすいように実装しているというお話でした。一般的な画面単位で ViewController を持つ実装だと、開発の人数を増やしてもコンフリクトやオーバーヘッドが発生しやすくなり、効率的に開発することが難しくなってしまいます。そのため、開発チームの規模をスケールさせることができないということからそのような方法を取り入れたそうです。</p>
<p>MicroViewController を採用したことによって、一つのアプリに 20 人のエンジニアを充てることができるようになったということでしたが、他にそんなところあるのでしょうか、、、という気がしないでもなかったです。ただ、数人でやっていてもコンフリクトはよく起こりますし(project.pbxproj、、、!)、機能を追加したり、削除したりということを素早く試行錯誤するのには良さそうだなと思いました。</p>
<p>また、ビルドの効率化のためにサンドボックスアプリを用意したり、ViewController のテンプレートを作って実装の効率化を図るなど開発効率向上の参考になる取り組みも多かったです。</p>
<h2 id="デバイス-os-バージョンの依存が少なくメンテナンスしやすいビューを作る">デバイス・ OS バージョンの依存が少なく、メンテナンスしやすいビューを作る</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/b3f154dc44134795b521b00673ff5563" title="iOSDC2018.pdf" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 314px;" data-ratio="1.78343949044586"></iframe>
<p>ビューの実装について起こりがちな問題とその対処法について紹介したセッションでした。特にレイアウト崩れを防ぐために意識すべきポイントについて、具体的な失敗例を示しながら紹介されていました。</p>
<p>UI コンポーネントのサンプル実装(<a href="https://github.com/folio-sec/Folio-UI-Collection/tree/master/Folio-UI-Collection">https://github.com/folio-sec/Folio-UI-Collection/tree/master/Folio-UI-Collection</a>)も紹介されていましたが、CLINICS でも共通 UI をコンポーネント化して使っているので、カスタム View の実装方法やコンポーネントの粒度などが再確認でき、非常に有益でした。また、UI のユニットテストもあり、CLINICS ではあまり UI 関連のテストは書けていないので参考にしたいと思います。</p>
<h2 id="宣言的-uicollectionview">宣言的 UICollectionView</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/72eec24556a74fcaa13fa374289acfe3" title="Declarative UICollectionView" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 314px;" data-ratio="1.78343949044586"></iframe>
<p>UICollectionView に複数の種類がある場合(よくあります!)に、宣言的な実装をすることでコードの見通しをよくする方法を提案されているセッションでした。</p>
<p>自分もあの switch 文をまとめようとして、うまくいかなかった経験があるのでとても興味深かったです。すぐに自分たちのコードにも適用できる実用的なセッションでした。</p>
<p>あとは ReactorKit のライブコーディングや差分検出アルゴリズムに関連する話がいくつかあり、ちょうど業務で ReactorKit と RxDataSources を使っているので参考になりました。</p>
<h2 id="差分アルゴリズムの原理について">差分アルゴリズムの原理について</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/f7b35578419d420088be83eb2638ac5a" title="Difference Algorithm" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 420px;" data-ratio="1.3333333333333333"></iframe>
<p>UITableView などで変更があった箇所だけを更新するのに利用される差分検出のアルゴリズムについて解説したセッションでした。IGListKit や RxDataSources などで使われているアルゴリズムと Android の DiffUtil で利用されているアルゴリズムの原理について紹介されていました。ライブラリを使っていると、利用方法は分かるけど内部でどう動いてるかはあまり理解していないということも割とあると思いますが、その辺りを理解できると用途に応じて最適なライブラリを選択できたり、より効果的な使い方ができそうだと思いました。</p>
<h2 id="5000-行の-uitableview-を差分更新する">5000 行の UITableView を差分更新する</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/9b0b1ea657494e6f840886cd039b90af" title="5000 行の UITableView を差分更新する / Difference update UITableView with 5000 rows" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 314px;" data-ratio="1.78343949044586"></iframe>
<p>差分更新のライブラリを使って多数行を更新した際に発生した問題の紹介とその原因を特定して、改善した内容についてのお話でした。採用していたライブラリは Differ でしたが、ライブラリごとの特徴や Instruments によるボトルネックの特定などが参考になりました。</p>
<h1 id="まとめ">まとめ</h1>
<p>iOSDC は去年に引き続きの参加だったのですが、今年のセッションもどれも興味深く、勉強になることが多かったです。またセッションの裏で特定の技術についてディスカッションするコーナーができるなど年々充実してきているように感じました。また、LT のときの会場の一体感はとても良いなあと思いました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180913/20180913175405.png" alt="20180913175405.png">
<p>弊社のアプリ開発でもそういった知見などを活かして開発していける仲間を引き続き募集しています! 興味がある方は、こちらからご連絡ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.wantedly.com/companies/medley" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">株式会社メドレーの会社情報 - Wantedly</div>
<div class="remark-link-card-plus__description">株式会社メドレーの魅力を伝えるコンテンツと、住所や代表・従業員などの会社情報です。急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。
しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.wantedly.com/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.wantedly.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://images.wantedly.com/i/YMVT7Fy?h=1440&w=1440" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- Kotlin Fest 2018 にメドレーが"ひよこスポンサー"として協賛しましたhttps://developer.medley.jp/entry/2018/09/03/120000https://developer.medley.jp/entry/2018/09/03/120000こんにちは、開発本部平木です。去る 8/25 に行われた、日本初(!!)の Kotlin の言語カンファレンスであるKotlin Fest 2018に弊社は”ひよこスポンサー”として協賛させていただきました。
公式 Twitter で紹介さ...Mon, 03 Sep 2018 03:00:00 GMT<p>こんにちは、開発本部平木です。去る 8/25 に行われた、日本初(!!)の Kotlin の言語カンファレンスである<a href="https://kotlin.connpass.com/event/91666/">Kotlin Fest 2018</a>に弊社は”ひよこスポンサー”として協賛させていただきました。</p>
<p><em>公式 Twitter で紹介された様子</em></p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">スポンサーのご紹介です。Medley (<a href="https://twitter.com/medley_life?ref_src=twsrc%5Etfw">@medley_life</a>) 様に Kotlin Fest のひよこスポンサーになっていただきました!よろしくお願いします! <a href="https://twitter.com/hashtag/kotlinfest?src=hash&ref_src=twsrc%5Etfw">#kotlinfest</a><a href="https://t.co/RojsiqlGMi">https://t.co/RojsiqlGMi</a></p>— Kotlin Fest (@kotlin_fest) <a href="https://twitter.com/kotlin_fest/status/1032930515731410944?ref_src=twsrc%5Etfw">August 24, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>今回スポンサーチケットで参加させていただいたので、つれづれとレポートを書いてまいります。</p>
<h1 id="メドレーと-kotlin-の関わり">メドレーと Kotlin の関わり</h1>
<p>なぜ、メドレーが Kotlin Fest に協賛したかというと Kotlin を使って Android アプリを作っているからになります。</p>
<p>オンライン診療アプリ CLINICS の Android 版で使っています。</p>
<div class="remark-link-card-plus__container">
<a href="https://play.google.com/store/apps/details?id=life.medley.clinics" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">melmo(メルモ) - Apps on Google Play</div>
<div class="remark-link-card-plus__description">Making hospital visits easier and medical care more accessible. Melmo allows you to make appointments, send prescriptions in advance, have medication delivered, and even manage your medication record, all with just one app.</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.gstatic.com/android/market_images/web/favicon_v3.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">play.google.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://play-lh.googleusercontent.com/6eBRhSkB38YqOdjd6MoxdLLrlo-rSukJn9ky9sGrRpjMlhJ7AnT4V-Fa86vhKJ4nJkwVrblPdbFNWO_dzCiB" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>Android で正式に Kotlin サポートすることが<a href="https://android-developers.googleblog.com/2017/05/android-announces-support-for-kotlin.html">アナウンス</a>されてからできるところを Java から Kotlin に書きかえていっています。</p>
<p>方針としては、ムリに全部のソースを Kotlin にするという形ではなく改修などで触ったソースで余力があれば書きかえるというスタンスでやっていますが、それでも現在 50%弱のソースが Kotlin になっています。</p>
<p><img src="https://d2mxuefqeaa7sj.cloudfront.net/s_024FF11B0CBA409D403D1FFC8B358DB011B011904F47178CD795ECA7FE629F5C_1535525616476_screenshot.png" alt=""></p>
<p>Kotlin で書いた場合に Java よりも可読性や堅牢性が上がるというメリットを実感していたところ、今回の Kotlin Fest の開催を知り、協賛させていただいたという次第です。</p>
<h1 id="イベントの様子">イベントの様子</h1>
<p>会場は<a href="https://www.tokyo-cc.co.jp/shinagawa/index.html#access">東京コンファレンスセンター品川</a>でした。自分は初訪問だったのですが、設備も充実しており良い会場だと思いました。</p>
<p>無限に出てくるかのようなコーヒー・飲み物とお菓子が大変ホスピタリティを感じさせます。</p>
<p>スポンサーブースも盛況で、なかでも Yahoo! Japan さんのモブプロ実演や、CyberAgent さんの Kotlin クイズなどが人気を集めていました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180903/20180903111847.jpg" alt="20180903111847.jpg">
<p><strong>M3 さんのブースでいただいたロゴ入りじゃがりこ</strong></p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180903/20180903111902.jpg" alt="20180903111902.jpg">
<h1 id="セッション">セッション</h1>
<p>セッションは 2 セッションが同時に行われるという形式でした。</p>
<p>自分が参加したセッションのみですが簡単な感想でご紹介します。</p>
<h2 id="kotlin-で改善する-android-アプリの品質">Kotlin で改善する Android アプリの品質</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/f8df5829e1934f2a885fbbe24fb6c7b0" title="KotlinFest.pdf" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 420px;" data-ratio="1.3333333333333333"></iframe>
<p>Java から Kotlin への書きかえを考えた場合のアプリの品質を主軸にしてメリットを紹介する…というものでした。</p>
<p>Effective Java の中で紹介されている項目について、Kotlin ではどうなるかという視点での紹介は大変興味深かったです。</p>
<p>Kotlin は Java よりも Null 安全を始め、堅牢だというイメージがありましたが、こうして Java で<strong>するべきである</strong>という項目が Kotlin では言語仕様レベルで対応されていることが多いというのを目の当たりにすると、さらに頼もしく思えるというようなセッションでした。</p>
<h2 id="kotlin-アプリのリファクタリングポイント">Kotlin アプリのリファクタリングポイント</h2>
<iframe src="//www.slideshare.net/slideshow/embed_code/key/crHCtpqwXf4ABn" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/RecruitLifestyle/refactoring-point-of-kotlin-application" title="Refactoring point of Kotlin application" target="_blank">Refactoring point of Kotlin application</a> </strong> from <strong><a href="//www.slideshare.net/RecruitLifestyle" target="_blank">Recruit Lifestyle Co., Ltd.</a></strong> </div>
<p>既に存在する Kotlin のコードをどのような指針でリファクタリングしていくかというセッションです。</p>
<p>「こういうときに書き方複数あるけどどうしよう…」という例ばかりだったので、自分達のアプリでもすぐに使えるような実践的なセッションでした。</p>
<p>中でもいかに<code>Mutable</code>なプロパティを避けるかというフローチャートは、理路整然としていてこれから Kotlin を書いていく上でかなり参考になりました。</p>
<h2 id="kotlin-linter">Kotlin linter</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/7b962f63e61c42f6b42db2a8482be3d7" title="kotlin linter" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 420px;" data-ratio="1.3333333333333333"></iframe>
<p>自分の場合、JavaScript などでもわりと Linter は興味がある分野だったのですが、Kotlin の Lint について
は android-lint しか使ったことがなかったので、紹介されている Linter の情報が参考になるセッションでした。</p>
<p>Kotlin の Linter もやはり AST を触らないとオリジナルルールの設定ができないのかーなど普段触れていなかった知識が得られて有意義でした。</p>
<p>が、Kotlin の AST は<a href="https://plugins.jetbrains.com/plugin/227-psiviewer">PsiViewer</a>という IntliJ プラグインくらいしか対応してなさそうで、自分で設定するとなると若干つらそうだなという印象でした。</p>
<p><a href="https://astexplorer.net/">AST Explore</a>あたりで気軽に試せると良いですね。</p>
<h2 id="kotlin-で愛でる-microservices">Kotlin で愛でる Microservices</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/8b7d8bd62d2b4a64966d3762c1923678" title="kotlin-fest" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 314px;" data-ratio="1.78343949044586"></iframe>
<p>サーバサイド Kotlin を Microservice でガンガン使用するために必要なエッセンスがまとまったセッションでした。</p>
<p>元々使っていた Go との使いわけや、実際どのようなアーキテクチャで作って、デプロイや監視など運用をどのようにしているのかなどがコンパクトにまとまっていて大変分かりやすかったです。</p>
<p>サーバサイド Kotlin は弊社で使う予定は現状まだ無いのですが、このセッションを見た限りミニマムに始めることが可能な感じに思えたのが収穫でした。</p>
<h1 id="まとめ">まとめ</h1>
<p>Kotlin の日本初のカンファレンスでしたが、来場者もかなり多く Kotlin エンジニアの裾野が広いなという印象でした(セッションによっては立ち見も出ていました)。</p>
<p>またセッションも初級から上級まで幅広く取り揃えられていたので、飽きることなく楽しめましたし、来年以降も続いていってほしいと思ったカンファレンスでした。</p>
<p>メドレーでは今後も色々な Tech カンファレンスをスポンサードして参ります。色々な場所で、お会いできたらと思います!</p>medley
- HTTP Cache で求人サイトのスピード改善を試してみた話https://developer.medley.jp/entry/2018/08/27/124832https://developer.medley.jp/entry/2018/08/27/124832こんにちは、開発本部の楊です。メドレーの社内勉強会「TechLunch」で、前回は、React の基本を紹介しましたが、今回は HTTP Cache で、医療介護求人サイト「ジョブメドレー」のスピード改善ができないか検討した話について、共有...Mon, 27 Aug 2018 03:48:32 GMT<p>こんにちは、開発本部の楊です。メドレーの社内勉強会「TechLunch」で、前回は、<a href="/entry/2017/06/29/161001">React の基本</a>を紹介しましたが、今回は HTTP Cache で、<a href="https://job-medley.com/">医療介護求人サイト「ジョブメドレー」</a>のスピード改善ができないか検討した話について、共有しました。</p>
<h1 id="なぜ-http-cache-について話すことにしたのか">なぜ HTTP Cache について話すことにしたのか</h1>
<p>ジョブメドレーには、医療機関や保育園、介護施設などさまざまな事業所の求人が掲載されています。現在、14 万を超える事業部の求人が掲載されており、かつ事業所側で求人原稿を修正することもできるため、求職者側が閲覧するスピードについては、いつも意識して改善に取り組んでいます。</p>
<p>その試行錯誤の中で、HTTP Cache を使って改善する方法について、実現性など含めて検証することにしました。</p>
<p>今回は、あるページをモデルケースに試してみました。このページは、PC は平均 250ms、モバイルは 180ms になっています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180824/20180824171410.png" alt="20180824171410.png">
<p>ユーザが該当のページを見たときに「内容に更新がないときはブラウザ側のキャッシュを利用してスピードを最適化する」「ページが更新されていたら、最新の内容をすぐユーザへ反映する」という要件でスピード改善されることを目指すことにしました。</p>
<h1 id="http-cache-とは">HTTP Cache とは?</h1>
<p><a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=ja">Google Developers</a>では以下のように説明されています。</p>
<blockquote>
<p>ネットワーク経由で情報を取得するには時間もコストもかかります。レスポンスが大きいと、クライアントとサーバ間のラウンドトリップを何度も繰り返す必要があるため、レスポンスが利用可能となってブラウザで処理できるようになるまで時間がかかります。さらに、ユーザ側ではデータの通信コストが発生します。そのため、前に取得したリソースをキャッシュに保存して再使用できることは、パフォーマンスを最適化する上で非常に重要です。</p>
</blockquote>
<p>この機能はほぼ全てのブラウザに対応しており、HTTP ヘッダーで<code>CacheControl</code>、<code>ETAG</code>、<code>Last-Modified</code>などを利用して、リソース更新するタイミングなどを細かくコントロール可能です。</p>
<h1 id="rails-での-http-cache">Rails での HTTP Cache</h1>
<h3 id="expire_in-でキャッシュ">expire_in でキャッシュ</h3>
<p>1 時間キャッシュしたい場合は、コントローラにコードを一行書けば大丈夫です。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#DCDCAA">expires_in</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">1</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">hour</span><span style="color:#D4D4D4">)</span></span></code></pre>
<p>これで問題なく 1 時間キャッシュされるのですが、「ページが編集されたら即時にユーザへ反映する」という要件を満たしてはいません。</p>
<h3 id="etag-を利用">ETAG を利用</h3>
<p>では「ページが更新されたら最新の内容をすぐユーザへ反映して、更新がない時はブラウザ側のキャッシュを利用してスピードを最適化する」を行いたい場合はどうすればよいでしょうか。
ここでは<code>ETAG</code>を利用しようと思います。</p>
<p><code>ETAG</code>はページの内容によって、ユニークな文字列を作成して、ブラウザ側でページの更新あるかどうかを判定するものです。</p>
<h3 id="etag-はどう作成されているか">ETAG はどう作成されているか</h3>
<p>Rails のデフォルトでは HTTP Cache を有効になっていて、<code>ETAG</code>を自動的に作成しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#D4D4D4">header[</span><span style="color:#CE9178">'ETag'</span><span style="color:#D4D4D4">] = </span><span style="color:#4EC9B0">Digest::MD5</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">hexdigest</span><span style="color:#D4D4D4">(body)</span></span></code></pre>
<p>上のコードのようなイメージで、レスポンス Body から<code>ETAG</code>を作成します。</p>
<p>つまり、サーバから返される HTML ソースの内容が毎回同じであれば、ブラウザは前回キャッシュした内容を読み込むようになります。</p>
<h3 id="サーバレスポンスタイムの短縮">サーバレスポンスタイムの短縮</h3>
<p>今回の対象ページの機能としてサーバ側では主に二つの部分で時間がかかります。</p>
<ol>
<li>DB、Redis などで画面表示が必要なデータ取得、ロジック処理</li>
<li>クライアントに返す HTML のレンダリング</li>
</ol>
<p>対象ページは「HTML のレンダリング」する時間が長かったので、それを改善できれば、サーバレスポンスタイムを一気に短くできます。</p>
<p>画面に表示する必要なデータに変換がなければ、HTML のレンダリングをせずにレスポンスを返す機能がないかをさらに調べました。</p>
<h3 id="fresh_when-を使う">fresh_when を使う</h3>
<p>名前の通り、いつ画面更新するかをコントロールするメソッドです。</p>
<p>データベース中の該当データが更新されたら、新しい<code>ETAG</code>を作成するコードは以下のようになります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#DCDCAA">fresh_when</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">etag:</span><span style="color:#D4D4D4"> [</span><span style="color:#9CDCFE">@job_offer</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">@job_offer</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">facility</span><span style="color:#D4D4D4">])</span></span></code></pre>
<h3 id="model-から-etag-をどう作成するか">model から ETAG をどう作成するか</h3>
<p>model の cache_key から ETAG を作ります。</p>
<p>該当ページで使用する job_offer モデルの cache_key は<code>"job_offer/5-20071224150000"</code>のような感じになります。</p>
<p>model の id と updated_at の組み合わせでユニークな cache_key を作成しています。</p>
<p><a href="https://github.com/rails/rails/blob/v4.2.10/activerecord/lib/active_record/integration.rb#L55">Rails の該当コード</a></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#C586C0">def</span><span style="color:#DCDCAA"> cache_key</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">*</span><span style="color:#9CDCFE">timestamp_names</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0"> case</span></span>
<span class="line"><span style="color:#C586C0"> when</span><span style="color:#D4D4D4"> new_record?</span></span>
<span class="line"><span style="color:#CE9178"> "</span><span style="color:#569CD6">#{</span><span style="color:#D4D4D4">model_name.</span><span style="color:#DCDCAA">cache_key</span><span style="color:#569CD6">}</span><span style="color:#CE9178">/new"</span></span>
<span class="line"><span style="color:#C586C0"> when</span><span style="color:#D4D4D4"> timestamp_names.</span><span style="color:#DCDCAA">any?</span></span>
<span class="line"><span style="color:#9CDCFE"> timestamp</span><span style="color:#D4D4D4"> = </span><span style="color:#DCDCAA">max_updated_column_timestamp</span><span style="color:#D4D4D4">(timestamp_names)</span></span>
<span class="line"><span style="color:#9CDCFE"> timestamp</span><span style="color:#D4D4D4"> = timestamp.</span><span style="color:#DCDCAA">utc</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">to_s</span><span style="color:#D4D4D4">(cache_timestamp_format)</span></span>
<span class="line"><span style="color:#CE9178"> "</span><span style="color:#569CD6">#{</span><span style="color:#D4D4D4">model_name.</span><span style="color:#DCDCAA">cache_key</span><span style="color:#569CD6">}</span><span style="color:#CE9178">/</span><span style="color:#569CD6">#{</span><span style="color:#D4D4D4">id</span><span style="color:#569CD6">}</span><span style="color:#CE9178">-</span><span style="color:#569CD6">#{</span><span style="color:#D4D4D4">timestamp</span><span style="color:#569CD6">}</span><span style="color:#CE9178">"</span></span>
<span class="line"><span style="color:#C586C0"> when</span><span style="color:#D4D4D4"> timestamp = max_updated_column_timestamp</span></span>
<span class="line"><span style="color:#9CDCFE"> timestamp</span><span style="color:#D4D4D4"> = timestamp.</span><span style="color:#DCDCAA">utc</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">to_s</span><span style="color:#D4D4D4">(cache_timestamp_format)</span></span>
<span class="line"><span style="color:#CE9178"> "</span><span style="color:#569CD6">#{</span><span style="color:#D4D4D4">model_name.</span><span style="color:#DCDCAA">cache_key</span><span style="color:#569CD6">}</span><span style="color:#CE9178">/</span><span style="color:#569CD6">#{</span><span style="color:#D4D4D4">id</span><span style="color:#569CD6">}</span><span style="color:#CE9178">-</span><span style="color:#569CD6">#{</span><span style="color:#D4D4D4">timestamp</span><span style="color:#569CD6">}</span><span style="color:#CE9178">"</span></span>
<span class="line"><span style="color:#C586C0"> else</span></span>
<span class="line"><span style="color:#CE9178"> "</span><span style="color:#569CD6">#{</span><span style="color:#D4D4D4">model_name.</span><span style="color:#DCDCAA">cache_key</span><span style="color:#569CD6">}</span><span style="color:#CE9178">/</span><span style="color:#569CD6">#{</span><span style="color:#D4D4D4">id</span><span style="color:#569CD6">}</span><span style="color:#CE9178">"</span></span>
<span class="line"><span style="color:#C586C0"> end</span></span>
<span class="line"><span style="color:#C586C0">end</span></span></code></pre>
<h3 id="自前の作成したクラスから-etag-をどう作成するか">自前の作成したクラスから ETAG をどう作成するか</h3>
<p>model だけではなく、自前で作成したクラスでデータ管理をしてるところもあります。
そちらでの実現方法も試してみました。</p>
<p>そこで<code>ETAG</code>を作るコードが Rails でどうなっているか追ってみました。</p>
<p><a href="https://github.com/rails/rails/blob/v4.2.10/activesupport/lib/active_support/cache.rb#L92">Rails の該当コード</a></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#C586C0">def</span><span style="color:#DCDCAA"> retrieve_cache_key</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">key</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0"> case</span></span>
<span class="line"><span style="color:#C586C0"> when</span><span style="color:#D4D4D4"> key.</span><span style="color:#DCDCAA">respond_to?</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">:cache_key</span><span style="color:#D4D4D4">) </span><span style="color:#C586C0">then</span><span style="color:#D4D4D4"> key.</span><span style="color:#DCDCAA">cache_key</span></span>
<span class="line"><span style="color:#C586C0"> when</span><span style="color:#D4D4D4"> key.</span><span style="color:#DCDCAA">is_a?</span><span style="color:#D4D4D4">(</span><span style="color:#4FC1FF">Array</span><span style="color:#D4D4D4">) </span><span style="color:#C586C0">then</span><span style="color:#D4D4D4"> key.</span><span style="color:#DCDCAA">map</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">element</span><span style="color:#D4D4D4">| </span><span style="color:#DCDCAA">retrieve_cache_key</span><span style="color:#D4D4D4">(element) }.</span><span style="color:#DCDCAA">to_param</span></span>
<span class="line"><span style="color:#C586C0"> when</span><span style="color:#D4D4D4"> key.</span><span style="color:#DCDCAA">respond_to?</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">:to_a</span><span style="color:#D4D4D4">) </span><span style="color:#C586C0">then</span><span style="color:#DCDCAA"> retrieve_cache_key</span><span style="color:#D4D4D4">(key.</span><span style="color:#DCDCAA">to_a</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0"> else</span><span style="color:#D4D4D4"> key.</span><span style="color:#DCDCAA">to_param</span></span>
<span class="line"><span style="color:#C586C0"> end</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">to_s</span></span>
<span class="line"><span style="color:#C586C0">end</span></span></code></pre>
<p>このように、自作クラスに<code>cache_key</code>のメソッドを定義したら、その結果から ETAG が作成されるようになっています。このメソッドを使って、ユニークな値を返せば大丈夫です。</p>
<p>ここまで調査したことを踏まえて、該当ページを HTTP Cache で実装をしてみました。以下が該当の疑似コードになります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#C586C0">class</span><span style="color:#4EC9B0"> JobOfferBrowsingHistory</span></span>
<span class="line"><span style="color:#C586C0"> def</span><span style="color:#DCDCAA"> cache_key</span></span>
<span class="line"><span style="color:#6A9955"> # return job offer ids</span></span>
<span class="line"><span style="color:#C586C0"> end</span></span>
<span class="line"><span style="color:#C586C0">end</span></span></code></pre>
<p>fresh_when に渡す</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#9CDCFE">@user_history</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">JobOfferBrowsingHistory</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">new</span></span>
<span class="line"><span style="color:#DCDCAA">fresh_when</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">etag:</span><span style="color:#D4D4D4"> [</span><span style="color:#9CDCFE">@job_offer</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">@job_offer</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">facility</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">@user_history</span><span style="color:#D4D4D4">])</span></span></code></pre>
<h3 id="テスト">テスト</h3>
<p>開発環境で該当ページを 2 回ロードしました。
1 回目は 1200ms、キャッシュが効いた 2 回目は 130ms になりました。すごく早いです!
もちろん、ページ内のデータが更新されたら、最新の内容がページに反映されるようにもなっています。</p>
<p>これで「ページのデータが更新されたら、最新の内容をすぐユーザへ反映する」「更新がなければ HTTP Cache を返す」という状態が実現しました。</p>
<h3 id="課題">課題</h3>
<ul>
<li>実装ミスで、更新が必要なのに、更新されない問題が起こりえる
<ul>
<li>例えば、画面に新しい model を追加したが、<code>fresh_when</code>に渡すのを忘れたとか</li>
</ul>
</li>
<li>問題なく更新されることを保証する仕組みの実装</li>
</ul>
<h1 id="まとめ">まとめ</h1>
<p>HTTP Cache を利用して、ジョブメドレーのスピード改善を検討してみた調査過程を紹介しました。</p>
<p><code>expire_in</code>、<code>ETAG</code>の作成方法など色々調べて、最終的に<code>fresh_when</code>で実現できましたが、運用していく上での課題については、引き続き検討して行きたいと思います。</p>medley
- Rails Developers Meetup 2018 Day 3 Extreme で弊社の宍戸が発表させていただきましたhttps://developer.medley.jp/entry/2018/07/19/120506https://developer.medley.jp/entry/2018/07/19/120506こんにちは、開発本部の平木です。
去る 7/14(土)にRails Developers Meetup 2018 Day 3 Extremeというイベントが開催されまして、弊社の宍戸が電子カルテとセキュリティガイドラインと AWS と私とい...Thu, 19 Jul 2018 03:05:06 GMT<p>こんにちは、開発本部の平木です。</p>
<p>去る 7/14(土)に<a href="https://techplay.jp/event/679666">Rails Developers Meetup 2018 Day 3 Extreme</a>というイベントが開催されまして、<a href="https://www.wantedly.com/companies/medley/post_articles/63206">弊社の宍戸</a>が<strong>電子カルテとセキュリティガイドラインと AWS と私</strong>というタイトルで発表させていただきましたので、レポートさせていただきます。
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180719/20180719114338.jpg" alt="20180719114338.jpg"></p>
<h1 id="発表のきっかけ">発表のきっかけ</h1>
<p>昨年に引き続き<a href="https://rubykaigi.org/2018">RubyKaigi 2018</a>で弊社 CTO の平山が<a href="/entry/2018/06/06/151300">LT スポンサーとして発表させていただいたり、ブースに出展をしていた</a>ことがきっかけになり、Rails Developers Meetup の主催者である平野さん(<a href="https://twitter.com/yoshi_hirano">@yoshi_hirano</a>)から平山へ出演のお声がけをしていただきました。イベントからイベントへ関係がつながるのはとても嬉しいですね。</p>
<h1 id="発表テーマについて">発表テーマについて</h1>
<p>せっかく、お声がけしていただいたので、発表テーマも弊社の特色が出るものが良いであろうと決まったのが今回のテーマです。</p>
<p>4 月に発表した電子カルテシステム<strong>CLINICS カルテ</strong>ですが、何度かこちらのブログでもエントリが上がっていますとおり、電子カルテというシステムを構築する上で、セキュリティはかなり重要なウェイトを占める要素になっています。</p>
<p>このセキュリティを担保するための指針として<em>3 省 4 ガイドライン</em>という総務省・経産省・厚労省の各省庁から出ている 4 種類のガイドラインが出ています。</p>
<p>これを実際に AWS をベースとしたシステムを構築する際の一例として CLINICS カルテでの構築例を発表しようということになりました。</p>
<h1 id="発表スライド">発表スライド</h1>
<p>当日の発表スライドはこちらになります。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/8a3ebe6ac58e48efa9e0494f2372131e" title="電子カルテとセキュリティガイドラインと AWS と私 /Rails Developers Meetup 2018 Day 3 Extreme" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 420px;" data-ratio="1.3333333333333333"></iframe>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">電カルでガイドラインに沿うよう cloud trail とか vpc flow logs といった細かな AWS のサービス使ってるのか。<a href="https://twitter.com/hashtag/rdm2018A?src=hash&ref_src=twsrc%5Etfw">#rdm2018A</a> <a href="https://twitter.com/hashtag/railsdm?src=hash&ref_src=twsrc%5Etfw">#railsdm</a></p>— yuyasat (@yuyasat) <a href="https://twitter.com/yuyasat/status/1018036135581270017?ref_src=twsrc%5Etfw">July 14, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>会場ではスライドの色見がすっごく色褪せしていたりしましたが、発表した中での AWS のサービスは会場のみなさんの中でも、あまり馴染みがないものが多いようでスライドの写真など撮られている方もいらっしゃって、知見共有という意味で参考になった模様です。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">クライアント認証の話たのしいしめっちゃ知見だな。ALB, API Gateway は TLS クライアント認証に非対応だったので、Nginx で認証させてるとのこと。<a href="https://twitter.com/hashtag/rdm2018A?src=hash&ref_src=twsrc%5Etfw">#rdm2018A</a> <a href="https://twitter.com/hashtag/railsdm?src=hash&ref_src=twsrc%5Etfw">#railsdm</a></p>— かつひささん (@katsuhisa__) <a href="https://twitter.com/katsuhisa__/status/1018037952553730049?ref_src=twsrc%5Etfw">July 14, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">メドレーさんの発表、すげーよかった。<br>派手さはなくとも、こうやって一個ずつ地道に技術課題を解決している話はめちゃくちゃおもしろい。<a href="https://twitter.com/hashtag/rdm2018A?src=hash&ref_src=twsrc%5Etfw">#rdm2018A</a> <a href="https://twitter.com/hashtag/railsdm?src=hash&ref_src=twsrc%5Etfw">#railsdm</a></p>— かつひささん (@katsuhisa__) <a href="https://twitter.com/katsuhisa__/status/1018038937477005312?ref_src=twsrc%5Etfw">July 14, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>やはりクライアント認証に関しては普段サービス開発ではそこまでは使われないので、興味を持っていただいたようで良かったです!</p>
<h1 id="まとめ">まとめ</h1>
<p>弊社のような医療業界ではなくても、フルマネージドサービスでセキュリティを意識するような場面があればご参考になるかもしれないテーマをエンジニアの宍戸から発表させていただきました。</p>
<p>社内で貯まった知見で機会があれば、ぜひ公開していきたいと思います。</p>
<p>メドレーについて気になった方は、こちらからどうぞ。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.wantedly.com/companies/medley" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">株式会社メドレーの会社情報 - Wantedly</div>
<div class="remark-link-card-plus__description">株式会社メドレーの魅力を伝えるコンテンツと、住所や代表・従業員などの会社情報です。急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。
しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.wantedly.com/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.wantedly.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://images.wantedly.com/i/YMVT7Fy?h=1440&w=1440" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- Ruby を使って HPKI カードのデータを読み取るhttps://developer.medley.jp/entry/2018/07/12/152331https://developer.medley.jp/entry/2018/07/12/152331こんにちは、開発本部の宮内です。今回、HPKI カードについて調査を行いましたので、それについて書きます。
JAHIS HPKI 対応 IC カードガイドライン Ver.3.0を参考にして、HPKI テストカードから実際に公開鍵証明書を取得...Thu, 12 Jul 2018 06:23:31 GMT<p>こんにちは、開発本部の宮内です。今回、HPKI カードについて調査を行いましたので、それについて書きます。</p>
<p><a href="https://www.jahis.jp/standard/detail/id=609">JAHIS HPKI 対応 IC カードガイドライン Ver.3.0</a>を参考にして、HPKI テストカードから実際に公開鍵証明書を取得しました。</p>
<p>今後も HPKI について調査を続行していきたいと思います。</p>
<h1 id="hpki-とは">HPKI とは?</h1>
<p><a href="https://www.medis.or.jp/8_hpki/">HPKI</a>とは厚生労働省が認める医療福祉関係資格(医師・薬剤師・看護師など 26 種類の保健医療福祉分野の国家資格と、院長・管理薬剤師など 5 種類の管理者資格)を認証することができる PKI です。</p>
<p>配布された HPKI カードには、ルート CA、中間 CA、証明書が格納されています。</p>
<p>このカードは、電子署名などに使用することができ、今後普及していけば、医療文書(処方箋や病院への紹介状など)を印刷、押印、送付するなどの非効率な業務をすることなく、すべてデジタル化することができるようになります。</p>
<p>また、電子認証用の証明書も含まれているため、認証・認可処理にも使用することができます。</p>
<p>今回、HPKI テストカードを用いて調査を行いました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180712/20180712151907.jpg" alt="20180712151907.jpg">
<h1 id="調査環境">調査環境</h1>
<ul>
<li>macOS v10.13.5</li>
<li>ACR39-NTTCom</li>
<li>Ruby v2.5.1</li>
<li>smartcard v0.5.6</li>
<li>HPKI テスト用カード</li>
</ul>
<h1 id="pcsc">PC/SC</h1>
<p>HPKI カードのような IC カードとやり取りを行うには、<a href="https://en.wikipedia.org/wiki/PC/SC">PC/SC</a>という API 仕様を使う必要があります。</p>
<p>PC/SC はもともと Windows 環境のみで利用可能でしたが、pcsc-lite という OSS 実装があり、現在では様々な UNIX like OS でも利用できます。</p>
<p>macOS の場合、<code>/System/Library/Frameworks/PCSC.framework/PCSC</code>にライブラリが用意されており、特に準備する必要なく利用可能です。(2018 年 07 月現在)</p>
<p>ただし、IC カードリーダーのドライバーをインストールする必要があります。</p>
<p>今回利用した<code>ACR39-NTTCom</code>は<a href="https://www.ntt.com/business/services/application/authentication/jpki/download6.html">ダウンロードページ</a>に macOS v10.13 に対応したドライバーが配布されていなかったため、IC カードリーダーのチップメーカーである ACS 社の<a href="https://www.acs.com.hk/en/driver/302/acr39u-smart-card-reader/">ダウンロードページ</a>からドライバーを入手しました。</p>
<h1 id="smartcard">smartcard</h1>
<p>検証する際に使用した gem は<a href="https://rubygems.org/gems/smartcard">smartcard</a>です。
普通の rubygem と同じく<code>gem install</code>して利用します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">gem</span><span style="color:#CE9178"> install</span><span style="color:#CE9178"> smartcard</span></span></code></pre>
<p>IC カードリーダーを PC に接続し、</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">ruby</span><span style="color:#569CD6"> -rsmartcard</span><span style="color:#569CD6"> -e</span><span style="color:#CE9178"> 'pp Smartcard::PCSC::Context.new.readers'</span></span></code></pre>
<p>を実行し、IC カードリーダー名が表示されれば接続成功です。</p>
<h1 id="アプリケーション識別子の取得">アプリケーション識別子の取得</h1>
<p>実際に HPKI テストカードから情報を取得していきます。</p>
<p><a href="https://www.jahis.jp/standard/detail/id=609">ガイドライン</a>の「附属書 A(参考)PKI カードアプリケーション利用のシーケンス」にある「A.2.2 JIS X 6320-15 に従った PKI カードアプリケーションの検索と利用」を実装していきます。</p>
<blockquote>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180712/20180712152004.png" alt="20180712152004.png">
<p>引用 <a href="https://www.jahis.jp/standard/detail/id=609">ガイドライン</a></p>
</blockquote>
<p><em>prog01.rb</em></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#6A9955"># prog01.rb</span></span>
<span class="line"><span style="color:#569CD6">require</span><span style="color:#CE9178"> "smartcard"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">def</span><span style="color:#DCDCAA"> puts_response</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">response</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#DCDCAA"> puts</span><span style="color:#CE9178"> "status = %04X"</span><span style="color:#D4D4D4"> % response[</span><span style="color:#569CD6">:status</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#DCDCAA"> puts</span><span style="color:#CE9178"> "data = %s"</span><span style="color:#D4D4D4"> % response[</span><span style="color:#569CD6">:data</span><span style="color:#D4D4D4">].</span><span style="color:#DCDCAA">map</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">i</span><span style="color:#D4D4D4">| </span><span style="color:#CE9178">"%02X"</span><span style="color:#D4D4D4"> % i }.</span><span style="color:#DCDCAA">join</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">" "</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">context</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">Smartcard::PCSC::Context</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">new</span></span>
<span class="line"><span style="color:#C586C0">begin</span></span>
<span class="line"><span style="color:#9CDCFE"> card</span><span style="color:#D4D4D4"> = context.</span><span style="color:#DCDCAA">card</span><span style="color:#D4D4D4"> context.</span><span style="color:#DCDCAA">readers</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">first</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> # SELECT コマンドで`E8 28 BD 08 0F`をパーシャル指定した DF を指定</span></span>
<span class="line"><span style="color:#9CDCFE"> apdu</span><span style="color:#D4D4D4"> = [</span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xA4</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x04</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x05</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xE8</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x28</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xBD</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x08</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x0F</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#9CDCFE"> response</span><span style="color:#D4D4D4"> = card.</span><span style="color:#DCDCAA">transmit</span><span style="color:#D4D4D4"> apdu.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> response</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">Smartcard::Iso::IsoCardMixin</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">deserialize_response</span><span style="color:#D4D4D4"> response.</span><span style="color:#DCDCAA">unpack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> puts_response response</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> while</span><span style="color:#D4D4D4"> response[</span><span style="color:#569CD6">:status</span><span style="color:#D4D4D4">] == </span><span style="color:#B5CEA8">0x9000</span></span>
<span class="line"><span style="color:#6A9955"> # SELECT コマンドで次の DF を探す</span></span>
<span class="line"><span style="color:#9CDCFE"> apdu</span><span style="color:#D4D4D4"> = [</span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xA4</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x04</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x02</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x05</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xE8</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x28</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xBD</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x08</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x0F</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#9CDCFE"> response</span><span style="color:#D4D4D4"> = card.</span><span style="color:#DCDCAA">transmit</span><span style="color:#D4D4D4"> apdu.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> response</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">Smartcard::Iso::IsoCardMixin</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">deserialize_response</span><span style="color:#D4D4D4"> response.</span><span style="color:#DCDCAA">unpack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> puts_response response</span></span>
<span class="line"><span style="color:#C586C0"> end</span></span>
<span class="line"><span style="color:#C586C0">ensure</span></span>
<span class="line"><span style="color:#D4D4D4"> context.</span><span style="color:#DCDCAA">release</span></span>
<span class="line"><span style="color:#C586C0">end</span></span></code></pre>
<p>上記のプログラムを実行すると、次のような出力が得られます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">status</span><span style="color:#CE9178"> =</span><span style="color:#B5CEA8"> 9000</span></span>
<span class="line"><span style="color:#DCDCAA">data</span><span style="color:#CE9178"> =</span><span style="color:#CE9178"> 6F</span><span style="color:#B5CEA8"> 12</span><span style="color:#B5CEA8"> 84</span><span style="color:#B5CEA8"> 10</span><span style="color:#CE9178"> E8</span><span style="color:#B5CEA8"> 28</span><span style="color:#CE9178"> BD</span><span style="color:#B5CEA8"> 08</span><span style="color:#CE9178"> 0F</span><span style="color:#CE9178"> A0</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 03</span><span style="color:#B5CEA8"> 91</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 01</span></span>
<span class="line"><span style="color:#DCDCAA">status</span><span style="color:#CE9178"> =</span><span style="color:#B5CEA8"> 9000</span></span>
<span class="line"><span style="color:#DCDCAA">data</span><span style="color:#CE9178"> =</span><span style="color:#CE9178"> 6F</span><span style="color:#B5CEA8"> 12</span><span style="color:#B5CEA8"> 84</span><span style="color:#B5CEA8"> 10</span><span style="color:#CE9178"> E8</span><span style="color:#B5CEA8"> 28</span><span style="color:#CE9178"> BD</span><span style="color:#B5CEA8"> 08</span><span style="color:#CE9178"> 0F</span><span style="color:#CE9178"> A0</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 03</span><span style="color:#B5CEA8"> 91</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 00</span><span style="color:#B5CEA8"> 02</span></span>
<span class="line"><span style="color:#DCDCAA">status</span><span style="color:#CE9178"> =</span><span style="color:#CE9178"> 6A82</span></span>
<span class="line"><span style="color:#DCDCAA">data</span><span style="color:#CE9178"> =</span></span></code></pre>
<p>SELECT コマンドを発行すると<a href="https://en.wikipedia.org/wiki/X.690#BER_encoding">BER-TLV</a>で符号化された FCI(ファイル制御情報)が取得できます。</p>
<p>1つ目のデータから見ていきます。</p>
<p>1バイト目は<code>6F</code>なので、このデータはファイル制御パラメタ及びファイル管理データの集合を表します。</p>
<blockquote>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180712/20180712152144.png" alt="20180712152144.png">
<p>引用 JIS X 6320-4 表 8-ファイル制御情報用の産業感共通利用テンプレート</p>
</blockquote>
<p>2バイト目は<code>12</code>なので、後続するデータの長さが 18 バイトあることを表します。</p>
<p>3バイト目は<code>84</code>なので、データ要素が DF 名であることを表します。</p>
<blockquote>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180712/20180712152208.png" alt="20180712152208.png">
<p>引用 JIS X 6320-4 表 10-ファイル制御パラメタデータオブジェクト</p>
</blockquote>
<p>4バイト目は<code>10</code>なので、後続するデータの長さが 16 バイトあることを表します。
5バイト目以降は、DF 名(= アプリケーション識別子)です。</p>
<p>2つ目のデータもデータ構造は同じなため省略します。</p>
<p>これで HPKI テストカードには、</p>
<ul>
<li><code>E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 01</code></li>
<li><code>E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 02</code></li>
</ul>
<p>という2つのアプリケーション識別子が含まれていることが分かります。</p>
<h1 id="公開鍵証明書を取得する">公開鍵証明書を取得する</h1>
<p>前段にて HPKI テストカードに含まれているアプリケーション識別子が分かりましたので、次は公開鍵証明書を取得していきます。</p>
<p><a href="https://www.jahis.jp/standard/detail/id=609">ガイドライン</a>の「A.3.2 証明書の読み出し」にあるコマンドの通りに APDU を発行しても、正しいデータは返ってきません。
これは、HPKI テストカードの EF 識別子が、ガイドラインに記載されている EF 識別子とは異なるためです。</p>
<p>HPKI カードは JIS X 6320 に準拠しているため、各種暗号情報オブジェクトへのパス情報を含んだ EF.OD が存在しています。
この EF.OD を使い公開鍵証明書へのパスを取得してから、公開鍵証明書を取得していきます。</p>
<blockquote>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180712/20180712152245.png" alt="20180712152245.png">
<p>引用 <a href="https://www.jahis.jp/standard/detail/id=609">ガイドライン</a></p>
</blockquote>
<h2 id="efod-を読み込む">EF.OD を読み込む</h2>
<p><em>prog02.rb</em></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#6A9955"># prog02.rb</span></span>
<span class="line"><span style="color:#569CD6">require</span><span style="color:#CE9178"> "smartcard"</span></span>
<span class="line"><span style="color:#569CD6">require</span><span style="color:#CE9178"> "openssl"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">def</span><span style="color:#DCDCAA"> puts_response</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">response</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#DCDCAA"> puts</span><span style="color:#CE9178"> "status = %04X"</span><span style="color:#D4D4D4"> % response[</span><span style="color:#569CD6">:status</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#DCDCAA"> puts</span><span style="color:#CE9178"> "data = %s"</span><span style="color:#D4D4D4"> % response[</span><span style="color:#569CD6">:data</span><span style="color:#D4D4D4">].</span><span style="color:#DCDCAA">map</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">i</span><span style="color:#D4D4D4">| </span><span style="color:#CE9178">"%02X"</span><span style="color:#D4D4D4"> % i }.</span><span style="color:#DCDCAA">join</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">" "</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">def</span><span style="color:#DCDCAA"> decode_asn1</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">response</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> data</span><span style="color:#D4D4D4"> = response[</span><span style="color:#569CD6">:data</span><span style="color:#D4D4D4">].</span><span style="color:#DCDCAA">reverse_each</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">drop_while</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">i</span><span style="color:#D4D4D4">| i == </span><span style="color:#B5CEA8">0xFF</span><span style="color:#D4D4D4"> }.</span><span style="color:#DCDCAA">reverse</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> data.</span><span style="color:#DCDCAA">empty?</span></span>
<span class="line"><span style="color:#4EC9B0"> OpenSSL::ASN1</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">decode_all</span><span style="color:#D4D4D4"> data.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">context</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">Smartcard::PCSC::Context</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">new</span></span>
<span class="line"><span style="color:#C586C0">begin</span></span>
<span class="line"><span style="color:#9CDCFE"> card</span><span style="color:#D4D4D4"> = context.</span><span style="color:#DCDCAA">card</span><span style="color:#D4D4D4"> context.</span><span style="color:#DCDCAA">readers</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">first</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> [</span></span>
<span class="line"><span style="color:#D4D4D4"> [</span><span style="color:#B5CEA8">0xE8</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x28</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xBD</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x08</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x0F</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xA0</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x03</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x91</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x01</span><span style="color:#D4D4D4">],</span></span>
<span class="line"><span style="color:#D4D4D4"> [</span><span style="color:#B5CEA8">0xE8</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x28</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xBD</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x08</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x0F</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xA0</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x03</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x91</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x02</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> ].</span><span style="color:#DCDCAA">each</span><span style="color:#C586C0"> do</span><span style="color:#D4D4D4"> |</span><span style="color:#9CDCFE">aid</span><span style="color:#D4D4D4">|</span></span>
<span class="line"><span style="color:#6A9955"> # SELECT コマンドでアプリケーションを選択する</span></span>
<span class="line"><span style="color:#9CDCFE"> apdu</span><span style="color:#D4D4D4"> = [</span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xA4</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x04</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x10</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> *aid,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> card.</span><span style="color:#DCDCAA">transmit</span><span style="color:#D4D4D4"> apdu.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> # EF.OD の読み出し</span></span>
<span class="line"><span style="color:#9CDCFE"> apdu</span><span style="color:#D4D4D4"> = [</span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xB0</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x91</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#9CDCFE"> response</span><span style="color:#D4D4D4"> = card.</span><span style="color:#DCDCAA">transmit</span><span style="color:#D4D4D4"> apdu.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> response</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">Smartcard::Iso::IsoCardMixin</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">deserialize_response</span><span style="color:#D4D4D4"> response.</span><span style="color:#DCDCAA">unpack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> pp decode_asn1 response</span></span>
<span class="line"><span style="color:#C586C0"> end</span></span>
<span class="line"><span style="color:#C586C0">ensure</span></span>
<span class="line"><span style="color:#D4D4D4"> context.</span><span style="color:#DCDCAA">release</span></span>
<span class="line"><span style="color:#C586C0">end</span></span></code></pre>
<p>EF.OD を読み込むと DER 符号化されたデータが返ってきます。
これを <code>OpenSSL::ANS1</code> モジュールで復号化すると、次に取得するべき EF 識別子が分かります。</p>
<p>EF.OD の ASN.1 定義は以下のようになっているため、タグが 4 であるデータを読み込めば良さそうです。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>CIOChoice ::= CHOICE {</span></span>
<span class="line"><span> privateKeys [0] PrivateKeys,</span></span>
<span class="line"><span> publicKeys [1] PublicKeys,</span></span>
<span class="line"><span> trustedPublicKeys [2] PublicKeys,</span></span>
<span class="line"><span> secretKeys [3] SecretKeys,</span></span>
<span class="line"><span> certificates [4] Certificates,</span></span>
<span class="line"><span> trustedCertificates [5] Certificates,</span></span>
<span class="line"><span> usefulCertificates [6] Certificates,</span></span>
<span class="line"><span> dataContainerObjects [7] DataContainerObjects,</span></span>
<span class="line"><span> authObjects [8] AuthObjects,</span></span>
<span class="line"><span>}</span></span></code></pre>
<p>prog02.rb を実行して実際に得られたデータ</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#D4D4D4">[</span></span>
<span class="line"><span style="color:#6A9955"> # 中略</span></span>
<span class="line"><span style="color:#6A9955"> #<OpenSSL::ASN1::ASN1Data:0x00007f8b8e0ef7b0</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=4,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:CONTEXT_SPECIFIC,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span></span>
<span class="line"><span style="color:#D4D4D4"> [#<OpenSSL::ASN1::Sequence:0x00007f8b8e0ef7d8</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=16,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:UNIVERSAL,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tagging=nil,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span></span>
<span class="line"><span style="color:#D4D4D4"> [#<OpenSSL::ASN1::OctetString:0x00007f8b8e0ef800</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=4,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:UNIVERSAL,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tagging=nil,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span><span style="color:#CE9178">"\x00\x04"</span><span style="color:#D4D4D4">>]>]></span></span>
<span class="line"><span style="color:#6A9955"> # 中略</span></span>
<span class="line"><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4">[</span></span>
<span class="line"><span style="color:#6A9955"> # 中略</span></span>
<span class="line"><span style="color:#6A9955"> #<OpenSSL::ASN1::ASN1Data:0x00007f8b8d118df0</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=4,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:CONTEXT_SPECIFIC,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span></span>
<span class="line"><span style="color:#D4D4D4"> [#<OpenSSL::ASN1::Sequence:0x00007f8b8d118e18</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=16,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:UNIVERSAL,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tagging=nil,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span></span>
<span class="line"><span style="color:#D4D4D4"> [#<OpenSSL::ASN1::OctetString:0x00007f8b8d118e40</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=4,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:UNIVERSAL,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tagging=nil,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span><span style="color:#CE9178">"\x00\x04"</span><span style="color:#D4D4D4">>]>]>,</span></span>
<span class="line"><span style="color:#6A9955"> # 中略</span></span>
<span class="line"><span style="color:#D4D4D4">]</span></span></code></pre>
<p>どちらのアプリケーションも<code>00 04</code>が EF.CD(証明書オブジェクト情報)の EF 識別子だということが分かります。</p>
<h2 id="efcd-を読み込む">EF.CD を読み込む</h2>
<p><em>prog03.rb</em></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#6A9955"># prog03.rb</span></span>
<span class="line"><span style="color:#569CD6">require</span><span style="color:#CE9178"> "smartcard"</span></span>
<span class="line"><span style="color:#569CD6">require</span><span style="color:#CE9178"> "openssl"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">def</span><span style="color:#DCDCAA"> puts_response</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">response</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#DCDCAA"> puts</span><span style="color:#CE9178"> "status = %04X"</span><span style="color:#D4D4D4"> % response[</span><span style="color:#569CD6">:status</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#DCDCAA"> puts</span><span style="color:#CE9178"> "data = %s"</span><span style="color:#D4D4D4"> % response[</span><span style="color:#569CD6">:data</span><span style="color:#D4D4D4">].</span><span style="color:#DCDCAA">map</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">i</span><span style="color:#D4D4D4">| </span><span style="color:#CE9178">"%02X"</span><span style="color:#D4D4D4"> % i }.</span><span style="color:#DCDCAA">join</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">" "</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">def</span><span style="color:#DCDCAA"> decode_asn1</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">response</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> data</span><span style="color:#D4D4D4"> = response[</span><span style="color:#569CD6">:data</span><span style="color:#D4D4D4">].</span><span style="color:#DCDCAA">reverse_each</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">drop_while</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">i</span><span style="color:#D4D4D4">| i == </span><span style="color:#B5CEA8">0xFF</span><span style="color:#D4D4D4"> }.</span><span style="color:#DCDCAA">reverse</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> data.</span><span style="color:#DCDCAA">empty?</span></span>
<span class="line"><span style="color:#4EC9B0"> OpenSSL::ASN1</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">decode_all</span><span style="color:#D4D4D4"> data.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0">end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">context</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">Smartcard::PCSC::Context</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">new</span></span>
<span class="line"><span style="color:#C586C0">begin</span></span>
<span class="line"><span style="color:#9CDCFE"> card</span><span style="color:#D4D4D4"> = context.</span><span style="color:#DCDCAA">card</span><span style="color:#D4D4D4"> context.</span><span style="color:#DCDCAA">readers</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">first</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> [</span></span>
<span class="line"><span style="color:#D4D4D4"> [</span><span style="color:#B5CEA8">0xE8</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x28</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xBD</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x08</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x0F</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xA0</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x03</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x91</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x01</span><span style="color:#D4D4D4">],</span></span>
<span class="line"><span style="color:#D4D4D4"> [</span><span style="color:#B5CEA8">0xE8</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x28</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xBD</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x08</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x0F</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xA0</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x03</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x91</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x02</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> ].</span><span style="color:#DCDCAA">each</span><span style="color:#C586C0"> do</span><span style="color:#D4D4D4"> |</span><span style="color:#9CDCFE">aid</span><span style="color:#D4D4D4">|</span></span>
<span class="line"><span style="color:#6A9955"> # SELECT コマンドでアプリケーションを apdu</span></span>
<span class="line"><span style="color:#D4D4D4"> 選択する = [</span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xA4</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x04</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x10</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> *aid,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> card.</span><span style="color:#DCDCAA">transmit</span><span style="color:#D4D4D4"> apdu.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> # SELECT コマンドで EF 識別子`00 04`を選択する</span></span>
<span class="line"><span style="color:#9CDCFE"> apdu</span><span style="color:#D4D4D4"> = [</span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xA4</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x02</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x0C</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x02</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x04</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> card.</span><span style="color:#DCDCAA">transmit</span><span style="color:#D4D4D4"> apdu.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> # READ BINARY コマンドでファイルを読み込む</span></span>
<span class="line"><span style="color:#9CDCFE"> data</span><span style="color:#D4D4D4"> = []</span></span>
<span class="line"><span style="color:#9CDCFE"> offset</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">0</span></span>
<span class="line"><span style="color:#569CD6"> loop</span><span style="color:#C586C0"> do</span></span>
<span class="line"><span style="color:#9CDCFE"> apdu</span><span style="color:#D4D4D4"> = [</span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xB0</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> (offset & </span><span style="color:#B5CEA8">0x7FFF</span><span style="color:#D4D4D4">) >> </span><span style="color:#B5CEA8">8</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> (offset & </span><span style="color:#B5CEA8">0x00FF</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#9CDCFE"> response</span><span style="color:#D4D4D4"> = card.</span><span style="color:#DCDCAA">transmit</span><span style="color:#D4D4D4"> apdu.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> response</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">Smartcard::Iso::IsoCardMixin</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">deserialize_response</span><span style="color:#D4D4D4"> response.</span><span style="color:#DCDCAA">unpack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> data.</span><span style="color:#DCDCAA">concat</span><span style="color:#D4D4D4"> response[</span><span style="color:#569CD6">:data</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#C586C0"> break</span><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> response[</span><span style="color:#569CD6">:data</span><span style="color:#D4D4D4">].</span><span style="color:#DCDCAA">all?</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">| e == </span><span style="color:#B5CEA8">0xFF</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#C586C0"> break</span><span style="color:#C586C0"> unless</span><span style="color:#D4D4D4"> response[</span><span style="color:#569CD6">:status</span><span style="color:#D4D4D4">] == </span><span style="color:#B5CEA8">0x9000</span></span>
<span class="line"><span style="color:#9CDCFE"> offset</span><span style="color:#D4D4D4"> += response[</span><span style="color:#569CD6">:data</span><span style="color:#D4D4D4">].</span><span style="color:#DCDCAA">size</span></span>
<span class="line"><span style="color:#C586C0"> end</span></span>
<span class="line"><span style="color:#D4D4D4"> pp decode_asn1 </span><span style="color:#569CD6">data:</span><span style="color:#D4D4D4"> data</span></span>
<span class="line"><span style="color:#C586C0"> end</span></span>
<span class="line"><span style="color:#C586C0">ensure</span></span>
<span class="line"><span style="color:#D4D4D4"> context.</span><span style="color:#DCDCAA">release</span></span>
<span class="line"><span style="color:#C586C0">end</span></span></code></pre>
<p>prog03.rb を実行して実際に得られたデータ</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#D4D4D4">[</span></span>
<span class="line"><span style="color:#6A9955"> # 中略</span></span>
<span class="line"><span style="color:#6A9955"> #<OpenSSL::ASN1::Sequence:0x00007ffdf99aaf70</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=16,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:UNIVERSAL,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tagging=nil,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span></span>
<span class="line"><span style="color:#D4D4D4"> [#<OpenSSL::ASN1::OctetString:0x00007ffdf99ab038</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=4,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:UNIVERSAL,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tagging=nil,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span><span style="color:#CE9178">"\x00\x16"</span><span style="color:#D4D4D4">>,</span></span>
<span class="line"><span style="color:#6A9955"> #<OpenSSL::ASN1::Integer:0x00007ffdf99aafe8</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=2,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:UNIVERSAL,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tagging=nil,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=#<OpenSSL::BN 0>>,</span></span>
<span class="line"><span style="color:#6A9955"> #<OpenSSL::ASN1::ASN1Data:0x00007ffdf99aaf98</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=0,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:CONTEXT_SPECIFIC,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span><span style="color:#CE9178">"\x05\x17"</span><span style="color:#D4D4D4">>]></span></span>
<span class="line"><span style="color:#6A9955"> # 中略</span></span>
<span class="line"><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4">[</span></span>
<span class="line"><span style="color:#6A9955"> # 中略</span></span>
<span class="line"><span style="color:#6A9955"> #<OpenSSL::ASN1::Sequence:0x00007ffdfa072308</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=16,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:UNIVERSAL,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tagging=nil,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span></span>
<span class="line"><span style="color:#D4D4D4"> [#<OpenSSL::ASN1::OctetString:0x00007ffdfa072448</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=4,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:UNIVERSAL,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tagging=nil,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span><span style="color:#CE9178">"\x00\x16"</span><span style="color:#D4D4D4">>,</span></span>
<span class="line"><span style="color:#6A9955"> #<OpenSSL::ASN1::Integer:0x00007ffdfa0723d0</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=2,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:UNIVERSAL,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tagging=nil,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=#<OpenSSL::BN 0>>,</span></span>
<span class="line"><span style="color:#6A9955"> #<OpenSSL::ASN1::ASN1Data:0x00007ffdfa072380</span></span>
<span class="line"><span style="color:#D4D4D4"> @indefinite_length=</span><span style="color:#569CD6">false</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag=0,</span></span>
<span class="line"><span style="color:#D4D4D4"> @tag_class=:CONTEXT_SPECIFIC,</span></span>
<span class="line"><span style="color:#D4D4D4"> @value=</span><span style="color:#CE9178">"\x05%"</span><span style="color:#D4D4D4">>]></span></span>
<span class="line"><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#6A9955"> # 中略</span></span></code></pre>
<p>これで公開鍵証明書ファイルの EF 識別子が<code>00 16</code>であることが判明しました。</p>
<h2 id="公開鍵証明書を読み込む">公開鍵証明書を読み込む</h2>
<p><em>prog04.rb</em></p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#6A9955"># prog04.rb</span></span>
<span class="line"><span style="color:#569CD6">require</span><span style="color:#CE9178"> "smartcard"</span></span>
<span class="line"><span style="color:#569CD6">require</span><span style="color:#CE9178"> "openssl"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE">context</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">Smartcard::PCSC::Context</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">new</span></span>
<span class="line"><span style="color:#C586C0">begin</span></span>
<span class="line"><span style="color:#9CDCFE"> card</span><span style="color:#D4D4D4"> = context.</span><span style="color:#DCDCAA">card</span><span style="color:#D4D4D4"> context.</span><span style="color:#DCDCAA">readers</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">first</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> [</span></span>
<span class="line"><span style="color:#D4D4D4"> [</span><span style="color:#B5CEA8">0xE8</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x28</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xBD</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x08</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x0F</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xA0</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x03</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x91</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x01</span><span style="color:#D4D4D4">],</span></span>
<span class="line"><span style="color:#D4D4D4"> [</span><span style="color:#B5CEA8">0xE8</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x28</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xBD</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x08</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x0F</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0xA0</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x03</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x91</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x02</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> ].</span><span style="color:#DCDCAA">each</span><span style="color:#C586C0"> do</span><span style="color:#D4D4D4"> |</span><span style="color:#9CDCFE">aid</span><span style="color:#D4D4D4">|</span></span>
<span class="line"><span style="color:#6A9955"> # SELECT コマンドでアプリケーションを選択する</span></span>
<span class="line"><span style="color:#9CDCFE"> apdu</span><span style="color:#D4D4D4"> = [</span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xA4</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x04</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x10</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> *aid,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> card.</span><span style="color:#DCDCAA">transmit</span><span style="color:#D4D4D4"> apdu.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> # SELECT コマンドで EF 識別子`00 16`を選択する</span></span>
<span class="line"><span style="color:#9CDCFE"> apdu</span><span style="color:#D4D4D4"> = [</span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xA4</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x02</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x0C</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x02</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">0x16</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#D4D4D4"> card.</span><span style="color:#DCDCAA">transmit</span><span style="color:#D4D4D4"> apdu.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> # READ BINARY コマンドでファイルを読み込む</span></span>
<span class="line"><span style="color:#9CDCFE"> data</span><span style="color:#D4D4D4"> = []</span></span>
<span class="line"><span style="color:#9CDCFE"> offset</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">0</span></span>
<span class="line"><span style="color:#569CD6"> loop</span><span style="color:#C586C0"> do</span></span>
<span class="line"><span style="color:#9CDCFE"> apdu</span><span style="color:#D4D4D4"> = [</span><span style="color:#B5CEA8">0x00</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#B5CEA8"> 0xB0</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> (offset & </span><span style="color:#B5CEA8">0x7FFF</span><span style="color:#D4D4D4">) >> </span><span style="color:#B5CEA8">8</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> (offset & </span><span style="color:#B5CEA8">0x00FF</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#B5CEA8"> 0x00</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#9CDCFE"> response</span><span style="color:#D4D4D4"> = card.</span><span style="color:#DCDCAA">transmit</span><span style="color:#D4D4D4"> apdu.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#9CDCFE"> response</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">Smartcard::Iso::IsoCardMixin</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">deserialize_response</span><span style="color:#D4D4D4"> response.</span><span style="color:#DCDCAA">unpack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> data.</span><span style="color:#DCDCAA">concat</span><span style="color:#D4D4D4"> response[</span><span style="color:#569CD6">:data</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#C586C0"> break</span><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> response[</span><span style="color:#569CD6">:data</span><span style="color:#D4D4D4">].</span><span style="color:#DCDCAA">all?</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">e</span><span style="color:#D4D4D4">| e == </span><span style="color:#B5CEA8">0xFF</span><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#C586C0"> break</span><span style="color:#C586C0"> unless</span><span style="color:#D4D4D4"> response[</span><span style="color:#569CD6">:status</span><span style="color:#D4D4D4">] == </span><span style="color:#B5CEA8">0x9000</span></span>
<span class="line"><span style="color:#9CDCFE"> offset</span><span style="color:#D4D4D4"> += response[</span><span style="color:#569CD6">:data</span><span style="color:#D4D4D4">].</span><span style="color:#DCDCAA">size</span></span>
<span class="line"><span style="color:#C586C0"> end</span></span>
<span class="line"><span style="color:#9CDCFE"> cert</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">OpenSSL::X509::Certificate</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">new</span><span style="color:#D4D4D4">(data.</span><span style="color:#DCDCAA">reverse_each</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">drop_while</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">i</span><span style="color:#D4D4D4">| i == </span><span style="color:#B5CEA8">0xFF</span><span style="color:#D4D4D4"> }.</span><span style="color:#DCDCAA">reverse</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">pack</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"C*"</span><span style="color:#D4D4D4">))</span></span>
<span class="line"><span style="color:#DCDCAA"> puts</span><span style="color:#D4D4D4"> cert.</span><span style="color:#DCDCAA">to_text</span></span>
<span class="line"><span style="color:#C586C0"> end</span></span>
<span class="line"><span style="color:#C586C0">ensure</span></span>
<span class="line"><span style="color:#D4D4D4"> context.</span><span style="color:#DCDCAA">release</span></span>
<span class="line"><span style="color:#C586C0">end</span></span></code></pre>
<p>HPKI テストカードから DER 符号化された公開鍵証明書データが取得できるので、<code>OpenSSL::X509::Certificate.new</code>でインスタンス化できます。</p>
<p>上記の prog04.rb を実行すると下記のような出力が得られます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">Certificate:</span></span>
<span class="line"><span style="color:#DCDCAA"> Data:</span></span>
<span class="line"><span style="color:#DCDCAA"> Version:</span><span style="color:#B5CEA8"> 3</span><span style="color:#D4D4D4"> (0x2)</span></span>
<span class="line"><span style="color:#DCDCAA"> Serial</span><span style="color:#CE9178"> Number:</span><span style="color:#B5CEA8"> 13023</span><span style="color:#D4D4D4"> (0x32df)</span></span>
<span class="line"><span style="color:#DCDCAA"> Signature</span><span style="color:#CE9178"> Algorithm:</span><span style="color:#CE9178"> sha256WithRSAEncryption</span></span>
<span class="line"><span style="color:#DCDCAA"> Issuer:</span><span style="color:#CE9178"> C=JP,</span><span style="color:#CE9178"> O=Japan</span><span style="color:#CE9178"> Medical</span><span style="color:#CE9178"> Association,</span><span style="color:#CE9178"> OU=Digital</span><span style="color:#CE9178"> Certificate</span><span style="color:#CE9178"> Center,</span><span style="color:#CE9178"> CN=HPKI-01-HPKI_JV2-forNonRepudiation</span></span>
<span class="line"><span style="color:#DCDCAA"> Validity</span></span>
<span class="line"><span style="color:#DCDCAA"> Not</span><span style="color:#CE9178"> Before:</span><span style="color:#CE9178"> Aug</span><span style="color:#B5CEA8"> 15</span><span style="color:#CE9178"> 15:00:00</span><span style="color:#B5CEA8"> 2017</span><span style="color:#CE9178"> GMT</span></span>
<span class="line"><span style="color:#DCDCAA"> Not</span><span style="color:#CE9178"> After</span><span style="color:#CE9178"> :</span><span style="color:#CE9178"> Aug</span><span style="color:#B5CEA8"> 15</span><span style="color:#CE9178"> 14:59:59</span><span style="color:#B5CEA8"> 2018</span><span style="color:#CE9178"> GMT</span></span>
<span class="line"><span style="color:#DCDCAA"> Subject:</span><span style="color:#CE9178"> C=JP,</span><span style="color:#CE9178"> CN=JMACombi20413/serialNumber=TESTC20413</span></span>
<span class="line"><span style="color:#DCDCAA"> Subject</span><span style="color:#CE9178"> Public</span><span style="color:#CE9178"> Key</span><span style="color:#CE9178"> Info:</span></span>
<span class="line"><span style="color:#DCDCAA"> Public</span><span style="color:#CE9178"> Key</span><span style="color:#CE9178"> Algorithm:</span><span style="color:#CE9178"> rsaEncryption</span></span>
<span class="line"><span style="color:#DCDCAA"> Public-Key:</span><span style="color:#D4D4D4"> (2048 </span><span style="color:#CE9178">bit</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#DCDCAA"> Modulus:</span></span>
<span class="line"><span style="color:#DCDCAA"> 00:94:dd:09:40:f4:58:f9:0f:ec:3a:ea:e3:47:33:</span></span>
<span class="line"><span style="color:#6A9955"> # 中略</span></span>
<span class="line"><span style="color:#DCDCAA"> Exponent:</span><span style="color:#B5CEA8"> 65537</span><span style="color:#D4D4D4"> (0x10001)</span></span>
<span class="line"><span style="color:#DCDCAA"> X509v3</span><span style="color:#CE9178"> extensions:</span></span>
<span class="line"><span style="color:#DCDCAA"> X509v3</span><span style="color:#CE9178"> Authority</span><span style="color:#CE9178"> Key</span><span style="color:#CE9178"> Identifier:</span></span>
<span class="line"><span style="color:#DCDCAA"> keyid:44:E9:20:05:4D:6D:C4:B7:FA:4B:F0:1B:C6:EA:C8:D6:5B:16:22:F4</span></span>
<span class="line"><span style="color:#DCDCAA"> DirName:/C</span><span style="color:#CE9178">=JP/O=Ministry</span><span style="color:#CE9178"> of</span><span style="color:#CE9178"> Health,</span><span style="color:#CE9178"> Labour</span><span style="color:#CE9178"> and</span><span style="color:#CE9178"> Welfare/OU=Director-General</span><span style="color:#CE9178"> for</span><span style="color:#CE9178"> Policy</span><span style="color:#CE9178"> Planning</span><span style="color:#CE9178"> and</span><span style="color:#CE9178"> Evaluation/OU=MHLW</span><span style="color:#CE9178"> HPKI</span><span style="color:#CE9178"> Root</span><span style="color:#CE9178"> CA</span><span style="color:#CE9178"> V2</span></span>
<span class="line"><span style="color:#DCDCAA"> serial:02</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> X509v3</span><span style="color:#CE9178"> Subject</span><span style="color:#CE9178"> Key</span><span style="color:#CE9178"> Identifier:</span></span>
<span class="line"><span style="color:#DCDCAA"> 9E:E5:71:59:1E:A7:FC:1E:4A:31:F8:7B:30:0B:E3:7F:05:3D:9A:40</span></span>
<span class="line"><span style="color:#DCDCAA"> X509v3</span><span style="color:#CE9178"> Key</span><span style="color:#CE9178"> Usage:</span><span style="color:#CE9178"> critical</span></span>
<span class="line"><span style="color:#DCDCAA"> Non</span><span style="color:#CE9178"> Repudiation</span></span>
<span class="line"><span style="color:#DCDCAA"> X509v3</span><span style="color:#CE9178"> CRL</span><span style="color:#CE9178"> Distribution</span><span style="color:#CE9178"> Points:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> Full</span><span style="color:#CE9178"> Name:</span></span>
<span class="line"><span style="color:#DCDCAA"> URI:https://crl.pki.med.or.jp/repository/crl/crl-sign2.crl</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> X509v3</span><span style="color:#CE9178"> Subject</span><span style="color:#CE9178"> Directory</span><span style="color:#CE9178"> Attributes:</span></span>
<span class="line"><span style="color:#DCDCAA"> 0402..(..B..1(1</span><span style="color:#D4D4D4">&</span><span style="color:#DCDCAA">0$.</span><span style="color:#DCDCAA">"1 ...</span></span>
<span class="line"><span style="color:#DCDCAA">*.............Medical Doctor</span></span>
<span class="line"><span style="color:#DCDCAA"> X509v3 Certificate Policies: critical</span></span>
<span class="line"><span style="color:#DCDCAA"> Policy: 1.2.392.100495.1.5.1.1.0.1</span></span>
<span class="line"><span style="color:#DCDCAA"> CPS: https://www.pki.med.or.jp/certpolicy/</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> Signature Algorithm: sha256WithRSAEncryption</span></span>
<span class="line"><span style="color:#DCDCAA"> 84:ae:95:45:5e:e7:64:8b:0c:6e:20:5f:9f:1f:0d:5c:ae:4a:</span></span>
<span class="line"><span style="color:#DCDCAA"> # 中略</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA">Certificate:</span></span>
<span class="line"><span style="color:#DCDCAA"> Data:</span></span>
<span class="line"><span style="color:#DCDCAA"> Version: 3 (0x2)</span></span>
<span class="line"><span style="color:#DCDCAA"> Serial Number: 12927 (0x327f)</span></span>
<span class="line"><span style="color:#DCDCAA"> Signature Algorithm: sha256WithRSAEncryption</span></span>
<span class="line"><span style="color:#DCDCAA"> Issuer: C=JP, O=Japan Medical Association, OU=Digital Certificate Center, CN=HPKI-01-HPKI_JV2-forAuthentication-forIndividual</span></span>
<span class="line"><span style="color:#DCDCAA"> Validity</span></span>
<span class="line"><span style="color:#DCDCAA"> Not Before: Aug 15 15:00:00 2017 GMT</span></span>
<span class="line"><span style="color:#DCDCAA"> Not After : Aug 15 14:59:59 2018 GMT</span></span>
<span class="line"><span style="color:#DCDCAA"> Subject: C=JP, CN=JMACombi20413/serialNumber=TESTC20413</span></span>
<span class="line"><span style="color:#DCDCAA"> Subject Public Key Info:</span></span>
<span class="line"><span style="color:#DCDCAA"> Public Key Algorithm: rsaEncryption</span></span>
<span class="line"><span style="color:#DCDCAA"> Public-Key: (2048 bit)</span></span>
<span class="line"><span style="color:#DCDCAA"> Modulus:</span></span>
<span class="line"><span style="color:#DCDCAA"> 00:c6:f9:06:26:58:5e:11:b7:12:f2:8a:3e:97:0a:</span></span>
<span class="line"><span style="color:#DCDCAA"> # 中略</span></span>
<span class="line"><span style="color:#DCDCAA"> Exponent: 65537 (0x10001)</span></span>
<span class="line"><span style="color:#DCDCAA"> X509v3 extensions:</span></span>
<span class="line"><span style="color:#DCDCAA"> X509v3 Authority Key Identifier:</span></span>
<span class="line"><span style="color:#DCDCAA"> keyid:62:12:93:82:DE:3C:D7:FF:A8:D3:63:01:D3:01:6A:AE:6C:3B:C0:D4</span></span>
<span class="line"><span style="color:#DCDCAA"> DirName:/C=JP/O=Ministry of Health, Labour and Welfare/OU=Director-General for Policy Planning and Evaluation/OU=MHLW HPKI Root CA V2</span></span>
<span class="line"><span style="color:#DCDCAA"> serial:03</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> X509v3 Subject Key Identifier:</span></span>
<span class="line"><span style="color:#DCDCAA"> 45:2B:7B:B4:47:89:3D:6C:05:6D:82:4D:4C:C8:80:B8:B4:B0:89:81</span></span>
<span class="line"><span style="color:#DCDCAA"> X509v3 Key Usage: critical</span></span>
<span class="line"><span style="color:#DCDCAA"> Digital Signature</span></span>
<span class="line"><span style="color:#DCDCAA"> X509v3 CRL Distribution Points:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> Full Name:</span></span>
<span class="line"><span style="color:#DCDCAA"> URI:https://crl.pki.med.or.jp/repository/crl/crl-auth2.crl</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> X509v3 Subject Directory Attributes:</span></span>
<span class="line"><span style="color:#DCDCAA"> 0402..(..B..1(1&0$."</span><span style="color:#DCDCAA">1</span><span style="color:#CE9178"> ...</span></span>
<span class="line"><span style="color:#D4D4D4">*.............Medical Doctor</span></span>
<span class="line"><span style="color:#DCDCAA"> X509v3</span><span style="color:#CE9178"> Certificate</span><span style="color:#CE9178"> Policies:</span><span style="color:#CE9178"> critical</span></span>
<span class="line"><span style="color:#DCDCAA"> Policy:</span><span style="color:#B5CEA8"> 1.2.392.100495.1.5.1.2.0.1</span></span>
<span class="line"><span style="color:#DCDCAA"> CPS:</span><span style="color:#CE9178"> https://www.pki.med.or.jp/certpolicy/</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA"> Signature</span><span style="color:#CE9178"> Algorithm:</span><span style="color:#CE9178"> sha256WithRSAEncryption</span></span>
<span class="line"><span style="color:#6A9955"> # 中略</span></span></code></pre>
<p>それぞれのアプリケーションから正しく公開鍵証明書が取得できました。</p>
<p><a href="https://www.jahis.jp/standard/detail/id=131">電子認証ガイドライン</a>によると、電子認証に使用する証明書は Issuer の CN(Common Name)が<code>HPKI-01-*-forAuthentication-forIndividual</code>であることが定められているため、
使用した HPKI テストカードでは、電子認証に使用するアプリケーション識別子は<code>E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 02</code>であることが分かります。
また、電子署名に使用するアプリケーション識別子は<code>E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 01</code>であることが分かりました。</p>
<h1 id="最後に">最後に</h1>
<p>以上で<a href="https://www.jahis.jp/standard/detail/id=609">ガイドライン</a>の「A.2.2 JIS X 6320-15 に従った PKI カードアプリケーションの検索と利用」にある「PKI カードアプリケーションの検索」まで実装できました。</p>
<p>今後、次のステップである暗号計算を実装していきたいと思います。</p>medley
- 電子レセプトについて調べた話https://developer.medley.jp/entry/2018/07/06/114000https://developer.medley.jp/entry/2018/07/06/114000こんにちは、開発本部の竹内です。最近子どものプリンセスへの強い憧れに若干引いております。
さて先日、TechLunch という社内勉強会で「電子レセプト」について話しましたので、こちらでも簡単に紹介させていただきます。
レセプトとは
ところ...Fri, 06 Jul 2018 02:40:00 GMT<p>こんにちは、開発本部の竹内です。最近子どものプリンセスへの強い憧れに若干引いております。</p>
<p>さて先日、TechLunch という社内勉強会で「電子レセプト」について話しましたので、こちらでも簡単に紹介させていただきます。</p>
<h1 id="レセプトとは">レセプトとは</h1>
<p>ところで、みなさまは「レセプト」についてご存知でしょうか?私はメドレーに入社するまで知りませんでした。</p>
<p>レセプトとは医療機関が支払基金へ診療報酬を請求するための明細書情報のことです。</p>
<p>と言っても、初めて聞かれる方もいらっしゃると思いますので、医療機関におけるお金の流れとともに簡単に説明します。</p>
<p><img src="https://www.ssk.or.jp/kikin.images/kikin_image01.png" alt="https://www.ssk.or.jp/kikin.images/kikin_image01.png"></p>
<p><a href="https://www.ssk.or.jp/kikin.html">(支払基金ってどんなところ?|社会保険診療報酬支払基金より)</a></p>
<p>医療機関は「診療」の対価として、被保険者等(≒ 患者)からお金を受け取るわけですが、被保険者の加入する保険や公費によってその額は変わります。負担割合が 3 割の場合、残りの 7 割を被保険者が加入する保険組合などへ請求する必要があります。</p>
<p>この保険組合などへの請求を取りまとめ、内容を審査しているのが支払基金と呼ばれる組織で、医療機関は月に一度、前月の患者ごとの診療点数を計算し「レセプト」として支払基金に提出することになります。</p>
<p><img src="https://www.ssk.or.jp/kikin.images/kikin_image03.png" alt="https://www.ssk.or.jp/kikin.images/kikin_image03.png"></p>
<p><a href="https://www.ssk.or.jp/kikin.html">(支払基金ってどんなところ?|社会保険診療報酬支払基金より)</a></p>
<p>レセプトには、請求する診療点数のほか、医療機関の情報、被保険者の情報(氏名などの基本情報、加入している保険者情報)、診療行為や傷病名に関する情報などが含まれています。</p>
<p>「レセプト」には紙と電子データとありますが、現在は原則として電子レセプトを提出することが求められているそうです(<a href="https://www.ssk.or.jp/seikyushiharai/rezept/iryokikan/rezept_03.html#cmsyuyo02">電子レセプト請求に係る猶予措置及び免除措置について|社会保険診療報酬支払基金</a>)。</p>
<h1 id="電子レセプトとレセ電ビューア">電子レセプトとレセ電ビューア</h1>
<p>電子レセプトについての仕様は支払基金によって公開されています。今回は「<a href="https://www.ssk.or.jp/seikyushiharai/rezept/iryokikan/iryokikan_02.html">電子レセプト作成の手引き</a>」という資料を元に「医科」のレセプトについて調べて発表しました。</p>
<p>発表資料はこちら。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/2a1e3455db2b48ada3377cf44d575ba8" title="電子レセプトの所感 ~わかりあえないことから~ " allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 420px;" data-ratio="1.3333333333333333"></iframe>
<p>電子レセプトの実体は CSV 形式のシンプルなテキストファイルです(拡張子は UKE なので、UKE ファイルと呼ぶこともあるようです)。ただ、電子レセプトの仕様を把握したとしても、やはり CSV ファイルを見て内容を把握するのは至難の業です。ファイル上では診療行為や医薬品、傷病名はコードとして表現されているため、<a href="https://www.ssk.or.jp/seikyushiharai/tensuhyo/kihonmasta/index.html">マスタデータ</a>を参照しなければその内容まで理解することはできないからです。</p>
<p>そこで登場するのが、レセ電ビューアというツールで、ORCA Project によって公開されているフリーの電子レセプトビューアです。「レセ電=電子レセプト」ですね。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.orca.med.or.jp/receipt/use/jma-receview.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">ORCA Project: レセ電ビューワ</div>
<div class="remark-link-card-plus__description">日本医師会開発・日医標準レセプトソフトウェアのサイトです</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.orca.med.or.jp/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.orca.med.or.jp</span>
</div>
</div>
</a>
</div>
<p>(※上記ページでは「レセ電ビューア」と「レセ電ビューワ」が混在していますが、本ブログでは「レセ電ビューア」で統一しています)</p>
<p>レセ電ビューアは Windows と Ubuntu で動作し、上述した UKE ファイルを読み込み、見やすく表示してくれる便利ツールです。ここからはレセ電ビューアをインストールし、電子レセプトを読み込んで表示するところまでを紹介したいと思います。</p>
<h2 id="レセ電ビューアのインストール">レセ電ビューアのインストール</h2>
<p>レセ電ビューアは Windows と Ubuntu 上で動作しますので、まずは Ubuntu 環境を準備します。私は VirtualBox 上に Ubuntu 環境を用意しました。
公式のインストールマニュアル(<a href="https://ftp.orca.med.or.jp/pub/receview/manual/jma-receview-install-linux.pdf">ubuntu 環境へのレセ電ビューアインストール</a>)に基づいて作業していきます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A9955"># Keyring と apt-line の追加</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> su</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> wget</span><span style="color:#569CD6"> -q</span><span style="color:#CE9178"> https://ftp.orca.med.or.jp/pub/ubuntu/archive.key</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> apt-key</span><span style="color:#CE9178"> add</span><span style="color:#CE9178"> archive.key</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> wget</span><span style="color:#569CD6"> -q</span><span style="color:#569CD6"> -O</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4">/etc/apt/sources.list.d/jma-receipt-xenial50.list </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#D4D4D4">https://ftp.orca.med.or.jp/pub/ubuntu/jma-receipt-xenial50.list</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> apt-get</span><span style="color:#CE9178"> update</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> apt-get</span><span style="color:#CE9178"> dist-upgrade</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># レセ電ビューアパッケージインストール</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> apt-get</span><span style="color:#CE9178"> install</span><span style="color:#CE9178"> jma-receview</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> apt-get</span><span style="color:#CE9178"> install</span><span style="color:#CE9178"> jma-receview-server</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># レセ電ビューア起動</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> jma-receview</span></span></code></pre>
<h2 id="レセ電ビューアの設定">レセ電ビューアの設定</h2>
<p>電子レセプトに含まれる診療行為などのコードに対応するマスタデータを参照するため、日レセ(jma-receipt)の DB を利用することができます。今回は DBFile 形式で DB に接続します。レセ電ビューアに付属するスクリプトを実行し、jma-receipt の DB から必要なテーブルをダンプすることができます。このファイルをレセ電ビューアに設定することで、電子レセプトの表示がよりわかりやすくなります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A9955"># レセ電ビューアで使う DBFile を作る</span></span>
<span class="line"><span style="color:#6A9955"># https://ftp.orca.med.or.jp/pub/receview/manual/jma-receview.pdf</span></span>
<span class="line"><span style="color:#6A9955"># 「2.7.4 DBFile の作成方法」</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> su</span><span style="color:#CE9178"> orca</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> ls</span><span style="color:#569CD6"> -la</span><span style="color:#CE9178"> /usr/share/jma-receview/db/</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> mkdir</span><span style="color:#CE9178"> /var/tmp/dbfile</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> cp</span><span style="color:#CE9178"> /usr/share/jma-receview/db/make_dbfile.sh</span><span style="color:#CE9178"> /var/tmp/dbfile/</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> cd</span><span style="color:#CE9178"> /var/tmp/dbfile/</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sh</span><span style="color:#CE9178"> ./make_dbfile.sh</span><span style="color:#B5CEA8"> 20170101</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> ls</span><span style="color:#569CD6"> -lh</span></span>
<span class="line"><span style="color:#DCDCAA">合計</span><span style="color:#CE9178"> 6.9M</span></span>
<span class="line"><span style="color:#DCDCAA">-rwxr-xr-x</span><span style="color:#B5CEA8"> 1</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> 2.9K</span><span style="color:#B5CEA8"> 5</span><span style="color:#CE9178"> 月</span><span style="color:#B5CEA8"> 18</span><span style="color:#CE9178"> 14:55</span><span style="color:#CE9178"> make_dbfile.sh</span></span>
<span class="line"><span style="color:#DCDCAA">-rw-r--r--</span><span style="color:#B5CEA8"> 1</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> 1.2M</span><span style="color:#B5CEA8"> 5</span><span style="color:#CE9178"> 月</span><span style="color:#B5CEA8"> 18</span><span style="color:#CE9178"> 14:57</span><span style="color:#CE9178"> tbl_byomei.rdb</span></span>
<span class="line"><span style="color:#DCDCAA">-rw-r--r--</span><span style="color:#B5CEA8"> 1</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> orca</span><span style="color:#B5CEA8"> 89</span><span style="color:#B5CEA8"> 5</span><span style="color:#CE9178"> 月</span><span style="color:#B5CEA8"> 18</span><span style="color:#CE9178"> 14:57</span><span style="color:#CE9178"> tbl_dbkanri.rdb</span></span>
<span class="line"><span style="color:#DCDCAA">-rw-r--r--</span><span style="color:#B5CEA8"> 1</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> 330K</span><span style="color:#B5CEA8"> 5</span><span style="color:#CE9178"> 月</span><span style="color:#B5CEA8"> 18</span><span style="color:#CE9178"> 14:57</span><span style="color:#CE9178"> tbl_hknjainf.rdb</span></span>
<span class="line"><span style="color:#DCDCAA">-rw-r--r--</span><span style="color:#B5CEA8"> 1</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> 9.6K</span><span style="color:#B5CEA8"> 5</span><span style="color:#CE9178"> 月</span><span style="color:#B5CEA8"> 18</span><span style="color:#CE9178"> 14:57</span><span style="color:#CE9178"> tbl_labor_sio.rdb</span></span>
<span class="line"><span style="color:#DCDCAA">-rw-r--r--</span><span style="color:#B5CEA8"> 1</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> 343K</span><span style="color:#B5CEA8"> 5</span><span style="color:#CE9178"> 月</span><span style="color:#B5CEA8"> 18</span><span style="color:#CE9178"> 14:57</span><span style="color:#CE9178"> tbl_syskanri.rdb</span></span>
<span class="line"><span style="color:#DCDCAA">-rw-r--r--</span><span style="color:#B5CEA8"> 1</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> orca</span><span style="color:#CE9178"> 5.1M</span><span style="color:#B5CEA8"> 5</span><span style="color:#CE9178"> 月</span><span style="color:#B5CEA8"> 18</span><span style="color:#CE9178"> 14:57</span><span style="color:#CE9178"> tbl_tensu.rdb</span></span></code></pre>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180706/20180706112205.png" alt="20180706112205.png">
<p>接続設定で「DBFile」を選択し、先ほど作成した DBFile を選択します。</p>
<h2 id="レセ電ビューア近影">レセ電ビューア近影</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180706/20180706112221.png" alt="20180706112221.png">
<p>「ファイル」から UKE ファイルを開くと、レセプトに基づいて患者基本情報、保険・公費情報、診療行為情報のほか、紙レセプトのプレビューや患者単位での電子レセプトを表示することができます。また、「編集モード」に切り替えることで患者情報や病名の編集が可能で、編集した内容でレセプトを再出力することもできるようです。</p>
<h1 id="おまけ">おまけ</h1>
<p>ここまで紹介してきたレセ電ビューアですが、調べてみるとどうやら Ruby で実装されているようです。これらのコードを読んでいくことで新たな地平を開くことができるかもしれません。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> dpkg</span><span style="color:#569CD6"> -L</span><span style="color:#CE9178"> jma-receview</span><span style="color:#D4D4D4"> | </span><span style="color:#DCDCAA">grep</span><span style="color:#CE9178"> ruby</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/menu.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/intconv.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/base.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/dbfile_lib.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/gui.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/yearconv.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/exception.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/api.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/config.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/image.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/dialog.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/upstart.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/dbslib.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/thread.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/receview.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/sickname_edit.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/dayconv.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/isoimage.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/help.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/other_csv.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/print.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/env.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/generation.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/red2cairo.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/strconv.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/log.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/hokenconv.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/keybind.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/version.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/gtk2_fix.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/preview_widget.rb</span></span>
<span class="line"><span style="color:#DCDCAA">/usr/lib/ruby/2.3.0/jma/receview/command.rb</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># /usr/bin/jma-receview も ruby で書かれてました(12000 行以上ある…)</span></span></code></pre>
<h1 id="まとめ">まとめ</h1>
<p>今回は電子レセプトとレセ電ビューアについて、簡単に紹介しました。</p>
<p>わたしたちの生活とは切り離せない「医療」に関するシステムや仕様は、意外と一般公開されているものもあり、誰でも触れることができます。ただ、動作環境が制限されていたり、インターネット界隈のエンジニアがよく目にする技術とは異なるスタックで構築されていたり、それなりにハードルがあるように感じています。</p>
<p>これらのハードルを下げ、より多くの人が「調べてみよう」「ちょっと触ってみよう」と思うようになれば、医療に関わるシステムや仕様もよりシンプルで使いやすいものになり、ひいては各医療問題の解決・患者体験の改善につながっていくのではないかな、メドレーがつなげていきたいなと思っています。</p>
<p>最後はいいことを言って締めたい性分なのですが、いかがだったでしょうか。</p>
<p>ここまでお読みいただき、ありがとうございました。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーの開発にご興味ある方は、こちらからご連絡ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>7/27 に開催されるデブサミ 2018Summer に協賛させていただきます。ぜひ遊びにいらしてください。</p>
<div class="remark-link-card-plus__container">
<a href="https://event.shoeisha.jp/devsumi/20180727" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title"> Developers Summit 2018 Summer</div>
<div class="remark-link-card-plus__description">ITエンジニアの祭典「Developers Summit 2018 Summer」(デブサミ2018夏)は、2018年7月27日に開催!</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://dm25qh0q2b215.cloudfront.net/static/images/conference/1/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">event.shoeisha.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://event.shoeisha.jp/static/images/event/797/1200.jpg" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 社内勉強会 TechLunch で"JavaScript AST ことはじめ"という発表をしましたhttps://developer.medley.jp/entry/2018/06/22/130000https://developer.medley.jp/entry/2018/06/22/130000みなさん、こんにちは。開発本部エンジニアの平木です。こちらのブログの投稿自体はほぼ 1 年ぶりになりそうな勢いですが、みなさまお元気でしょうか?
弊社で定期的に開催してる社内勉強会 TechLunch で自分の順番が回ってきたため、どうしよ...Fri, 22 Jun 2018 04:00:00 GMT<p>みなさん、こんにちは。開発本部エンジニアの平木です。こちらのブログの投稿自体はほぼ 1 年ぶりになりそうな勢いですが、みなさまお元気でしょうか?</p>
<p>弊社で定期的に開催してる社内勉強会 TechLunch で自分の順番が回ってきたため、どうしようか迷った末に<strong>JavaScript AST ことはじめ</strong>という発表をしたので、そのことについて書いていきます。</p>
<h1 id="なぜ-javascript-ast-について話そうと思ったのか">なぜ JavaScript AST について話そうと思ったのか</h1>
<p>現在、弊社のエンジニアメンバーのバックグラウンドで一番多数派なのは「元サーバサイドエンジニア」です。もちろん、業務ではサーバサイド・フロントエンド・ネイティブアプリとバックグラウンドに関わらず、必要に応じて分け隔てなく開発しています。</p>
<p>とはいえ、ちゃんとサービス開発自体はできるとしても、やはり得意な分野以外で基本原理など含めて把握して開発できるかというと、ちょっと難しいところもあります。しかし、そういった基本原理なんかを知っていると、その言語やツールなどの理解が捗るのは確かですよね。</p>
<p>そんな中、弊社で開発している人間がほぼ全て恩恵を受けているはずなのに、具体的にどんな風に動いているのかが一番分かりにくいであろう<code>Babel</code>ひいては JavaScript AST の話をしたら、まあ興味持って話を聞いてくれるかなーということででこのテーマを選んだ次第です。</p>
<h1 id="どのように伝えるか">どのように伝えるか</h1>
<p>自分は JavaScript AST についてとても詳しいわけではないのですが、以前仕事で<code>acorn</code>を使ってコンバータみたいなのを作ったりしていたので、それなりに興味は持っているという人間です。</p>
<p>ですので、どうやって紹介をしようかと悩んだ結果、ほぼ全面的に<a href="https://astexplorer.net/">AST Explore</a>に頼っていくというスタイルにしました。AST Explore は本当に最高ですね。前述の仕事をしていたときはこんな便利なツールはなかったんで、ひたすら AST に変換するコード書いては出来た AST を見て、それをトランスフォームさせて結果と睨めっこして試行錯誤するという毎日でした。</p>
<p>ということで、当日のスライドはこちらになります。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/45c318b24d7741728e858deb887957c0" title="JavaScript AST ことはじめ /JavaScript AST" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 314px;" data-ratio="1.78343949044586"></iframe>
<p>スライドで紹介したデモはそれぞれこちらになります。</p>
<ul>
<li><a href="https://astexplorer.net/#/gist/82742676286b2dced595ce36cdeb8aae/latest">https://astexplorer.net/#/gist/82742676286b2dced595ce36cdeb8aae/latest</a></li>
<li><a href="https://astexplorer.net/#/gist/52d871c2f3a8d9cefc68d17badf4f165/latest">https://astexplorer.net/#/gist/52d871c2f3a8d9cefc68d17badf4f165/latest</a></li>
</ul>
<h1 id="今回伝えたかったこと">今回伝えたかったこと</h1>
<p>まず、AST が JavaScript の発展にとても寄与しているものだということを知ってもらいたかったため、JavaScript AST の今までの簡単な流れや、現在どのような形で使われているのかの説明をしました。(個人的に Node.js の誕生と JavaScript AST の存在が現在のフロントエンドの発展にとても重要だと思っているので)</p>
<p>最初のうちは聞いてる人も「何の話なんだろ…」感がありましたが、やはり実際に自分が使っているツールなどに使われているという説明をしたあとだと、聞いているメンバーも俄然興味が出てきたという雰囲気になった気がします(当社比)。</p>
<p>AST の文法などは自分が説明するよりは、ちゃんと資料が揃っているので必要な部分以外簡略化しました。逆にちょっと端折りすぎたきらいもありますが、興味を持ったときに何となくでも調べる道標くらいにはなるかなと考えています。</p>
<p>次に知ってもらいたかったのは、やろうと思えば Babel のプラグインなんかも AST で作れちゃいますよということでした。仮にいきなり「Babel プラグイン作りましょう」となったとしても正直あまりピンと来ないと思いますが、どういう原理でプロダクトが動いているのか?が分かると、<a href="https://github.com/jamiebuilds/babel-handbook">babel-handbook</a>などを読んでも理解が進むのではないかと思います。</p>
<h1 id="ast-explore-のこと">AST Explore のこと</h1>
<p>このように今回の発表で全面的に活躍した AST Explore ですが、TechLunch 中でも軽い説明だけで使ってしまったので、使いかたなど簡単にご紹介していきます。</p>
<h2 id="ast-explore-とは">AST Explore とは</h2>
<p><a href="https://astexplorer.net/">AST Explore</a>は<a href="https://github.com/fkling">Felix Kling</a>さんが、2014 年頃から開発しているプロダクトです。</p>
<p>余談ですが、Felix さんは現在 Facebook で働いていらっしゃるようで、<a href="https://github.com/facebook/jscodeshift">facebook/jscodeshift</a>や<a href="https://github.com/reactjs/react-docgen">reactjs/react-docgen</a>なんかの開発にも携わっていらっしゃる模様。(react-docgen は<a href="https://github.com/babel/babylon">babylon</a>を使っているようですが)</p>
<p>ここまで書いてきた通りに、このツールは色々な言語をコピペするだけで AST をツリー形式で分かりやすく表示したり、トランスフォームさせることができたりするという AST を触るには大変便利なツールです。去年の v2.0 のアップデートにより、セーブすると gist を匿名で作ってくれてリンクが生成されるなどの便利機能が付きました。</p>
<p>プロジェクトの README に書いていますが、パーサだけであれば、かなりパースできるものが多く、また JavaScript / CSS / 正規表現 / Handlebars に関してはトランスフォームまでできるようになっています。</p>
<p>README から抜粋すると以下のような感じです。</p>
<h2 id="ast-explore-でパースできるもの">AST Explore でパースできるもの</h2>
<ul>
<li>CSS:
<ul>
<li>cssom</li>
<li>csstree</li>
<li>postcss + postcss-safe-parser & postcss-scss</li>
<li>rework</li>
</ul>
</li>
<li>GraphQL</li>
<li>Graphviz:
<ul>
<li>redot</li>
</ul>
</li>
<li>Handlebars:
<ul>
<li>glimmer</li>
<li>handlebars</li>
</ul>
</li>
<li>HTML:
<ul>
<li>htmlparser2</li>
<li>parse5</li>
</ul>
</li>
<li>ICU</li>
<li>JavaScript:
<ul>
<li>acorn + acorn-jsx</li>
<li>babel-eslint</li>
<li>babylon</li>
<li>espree</li>
<li>esformatter</li>
<li>esprima</li>
<li>flow-parser</li>
<li>recast</li>
<li>shift</li>
<li>traceur</li>
<li>typescript</li>
<li>typescript-eslint-parser</li>
<li>uglify-js</li>
</ul>
</li>
<li>JSON</li>
<li>Lua:
<ul>
<li>luaparse</li>
</ul>
</li>
<li>Markdown:
<ul>
<li>remark</li>
</ul>
</li>
<li>PHP
<ul>
<li>php-parser</li>
</ul>
</li>
<li>Regular Expressions:
<ul>
<li>regexp-tree</li>
</ul>
</li>
<li>Scala
<ul>
<li>Scalameta</li>
</ul>
</li>
<li>SQL:
<ul>
<li>sqlite-parser</li>
</ul>
</li>
<li>WebIDL</li>
<li>YAML</li>
</ul>
<h2 id="実験的だったりするけどパースできるもの">実験的だったりするけどパースできるもの</h2>
<ul>
<li>ES6: arrow functions, destructuring, classes, …</li>
<li>ES7 proposals: async/await, object rest / spread, …</li>
<li>JSX</li>
<li>Typed JavaScript Flow and TypeScript</li>
<li>SASS</li>
</ul>
<h2 id="パースしたものをトランスフォームできるもの">パースしたものをトランスフォームできるもの</h2>
<ul>
<li>JavaScript
<ul>
<li>babel (v5, v6)</li>
<li>ESLint (v1, v2, v3)</li>
<li>jscodeshift</li>
<li>tslint</li>
</ul>
</li>
<li>CSS
<ul>
<li>postcss</li>
</ul>
</li>
<li>Regular Expressions
<ul>
<li>regexp-tree</li>
</ul>
</li>
<li>Handlebars
<ul>
<li>glimmer</li>
</ul>
</li>
</ul>
<h2 id="ast-explore-の使い方の簡単な解説">AST Explore の使い方の簡単な解説</h2>
<p>サイトにアクセスするとこのような画面になっているはずです。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180622/20180622111218.png" alt="20180622111218.png">
<h2 id="メイン画面">メイン画面</h2>
<p>JavaScript にフォーカスして解説していきますと、左ペインが AST に変換したいソースコード、右ペインが変換後の AST をツリー構造で見せています。</p>
<p>初期表示時に、左ペインのソースコードをクリックすると該当箇所の AST ツリーが展開してハイライトします。また右ペインをポイントするとソースコードの該当箇所がハイライトします。お互いの関係が分かりやすい仕様になっています。</p>
<img src="https://layzie.d.pr/kfoFZu+">
<p>本来 JavaScript AST で生成されるものは JSON オブジェクトになりますが、右ペインの上の<strong>Tree</strong>と<strong>JSON</strong>のタブを切りかえることによって AST の表示を変更することができます。</p>
<h2 id="ヘッダー部分">ヘッダー部分</h2>
<p>ヘッダーに色々な機能がまとまっています。</p>
<p>初期表示では以下のようになっているはずです。</p>
<ul>
<li>Snippet
<ul>
<li>俗にいうファイルメニュー。
<ul>
<li>新規作成・(gist への)セーブ・(gist の)フォーク・シェアがある</li>
</ul>
</li>
</ul>
</li>
<li>JavaScript
<ul>
<li>パースする言語選択
<ul>
<li>ここで AST にしたい言語を切り替える</li>
<li>選んだ言語によって<em>Transform</em>が使えなくなる</li>
</ul>
</li>
</ul>
</li>
<li>acorn
<ul>
<li>パーサ選択
<ul>
<li>各言語のパーサを切り替える</li>
</ul>
</li>
</ul>
</li>
<li>Transform
<ul>
<li>トランスフォーマ選択
<ul>
<li>選択した言語にトランスフォーマがあれば選択できるようになる</li>
<li>こちらを選択すると 2 ペインだったのが 4 ペインになる(後述)</li>
</ul>
</li>
</ul>
</li>
<li>default
<ul>
<li>ソースコードなどを書くときのキーバインド選択
<ul>
<li>default / Vim / Emacs / Sublime の 4 種類がある</li>
<li>うれしみがあります</li>
</ul>
</li>
</ul>
</li>
<li>?
<ul>
<li>ヘルプ
<ul>
<li>GitHub の README に飛ばされるだけです…</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="javascript-のトランスフォーム">JavaScript のトランスフォーム</h2>
<p>先程説明したトランスフォームを選ぶと、メインの画面が 4 画面になります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180622/20180622111427.png" alt="20180622111427.png">
<p>今までの<em>ソースコード</em>と<em>AST ツリー</em>は変わりませんが、下に 2 つペインが追加されます。
左下がトランスフォーマコード、右下がトランスフォームした後のソースコードとなっています。</p>
<p>左下のトランスフォーマを色々触っていくと左上のソースコードが変換されて、右下に表示されるという流れですね。</p>
<p>以下 JavaScript コードのトランスフォームする際の Tips です</p>
<ul>
<li><code>jscodeshift</code>を選択すると<code>Ctrl + Space</code>で jscodeshift の補完が効くようになります</li>
<li><code>babel-plugin-macro</code>を選ぶとトランスフォーマのコード自体がそのまま babel-plugin として使えるようになるので、プラグイン作るときに捗るはずです</li>
</ul>
<h1 id="まとめ">まとめ</h1>
<p>後で参加メンバーに聞いてみましたが、伝えたかったことは、ちゃんと伝わっていた様子だったので安心しました。最後の Vue.js の v1 から v2 のマイグレーションのデモは紹介した結果、JavaScript AST 便利そうという感触になったと思います。</p>
<p>現在弊社のプロダクトで、JavaScript AST をガッツリと使うようなプロジェクトはないのですが、Babel などは全プロダクトで使用しており、結構プラグインを多用しているところもあるので、いざというときの基礎知識として覚えておいて損はないはずです。</p>
<p>こういった部分の勉強も欠かさず続けていきたいと改めて思う機会にもなりました。</p>
<p>弊社の開発文化など気になる方は、こちらからどうぞ。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- Lightning Talks Sponsor として RubyKaigi 2018 に参加してきましたhttps://developer.medley.jp/entry/2018/06/06/151300https://developer.medley.jp/entry/2018/06/06/151300こんにちは!開発本部のエンジニア・後藤です。
メドレーは 5/31〜6/2 に開催されたRurbyKaigi 2018に Lightning Talks Sponsor として協賛させていただきました(昨年の Ruby Sponsor に続...Wed, 06 Jun 2018 06:13:05 GMT<p>こんにちは!開発本部のエンジニア・後藤です。</p>
<p>メドレーは 5/31〜6/2 に開催された<a href="https://rubykaigi.org/2018">RurbyKaigi 2018</a>に Lightning Talks Sponsor として協賛させていただきました(<a href="https://developer.medley.jp/entry/2017/09/28/120000">昨年</a>の Ruby Sponsor に続き、2 年目の協賛です)。</p>
<p>イベント当日は、弊社から CTO の平山、採用・広報の阿部と深澤、エンジニアの田中、宍戸、後藤の 6 人が参加しました。今回はその様子などをレポートします。</p>
<h1 id="会場の様子">会場の様子</h1>
<p>RubyKaigi 2018 は<a href="https://rubykaigi.org/2018/venue">仙台国際センター</a>での開催でした。昨年は<a href="https://rubykaigi.org/2017/venue">広島</a>、一昨年は<a href="https://rubykaigi.org/2016/venue">京都</a>ということで、これで天橋立・宮島・松島の日本三景をめぐる旅が一旦完結になりますね。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605172640.jpg" alt="20180605172640.jpg">
<p>仙台国際センターは仙台駅から地下鉄東西線で 3 駅目というアクセスの良い好立地にありながら緑に囲まれた心地よい場所にありました。</p>
<p>メイン会場は 1000 人収容できる広い会場になっています。各セッション、この会場が埋まるぐらいの盛況ぶりでした。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605172745.jpg" alt="20180605172745.jpg">
<p>世界地図&日本地図。様々な地域からの参加者がいますね!(rubyists に map メソッドをかましてますね。ブロック内容は各参加者がシールを貼って実装。)</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605172857.jpg" alt="20180605172857.jpg">
<p>地図の隣のスポンサーボード覧にメドレーロゴがあることを確認してパシャり。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605173736.png" alt="20180605173736.png">
<h1 id="ブースの様子">ブースの様子</h1>
<p>続いてブースの様子を紹介します。メドレーコーポレートカラーの赤をベースとした以下な感じの仕上がりになりました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605173908.jpg" alt="20180605173908.jpg">
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">All photographs will be uploaded to this URLs(google photo).<br>day1 <a href="https://t.co/w6Dgq8HBb4">https://t.co/w6Dgq8HBb4</a><br>day2 <a href="https://t.co/X1x03bFEDF">https://t.co/X1x03bFEDF</a><br>day3 <a href="https://t.co/8rlXpfZTEB">https://t.co/8rlXpfZTEB</a><br><br>See you next year! <a href="https://twitter.com/hashtag/RubyKaigi?src=hash&ref_src=twsrc%5Etfw">#RubyKaigi</a> <a href="https://twitter.com/hashtag/RubyKaigi2018?src=hash&ref_src=twsrc%5Etfw">#RubyKaigi2018</a></p>— nil (@KatsumaNarisawa) <a href="https://twitter.com/KatsumaNarisawa/status/1002858233453633541?ref_src=twsrc%5Etfw">June 2, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>ノベルティも用意してブースにお越しいただいた方にお配りしていました。ステッカー、うちわ、パンフレットに加えて医療らしさが伝わる絆創膏も用意しました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605174458.jpg" alt="20180605174458.jpg">
<p>ブースにはおかげさまで、たくさんの方にお越しいただきました!</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605174539.jpg" alt="20180605174539.jpg">
<h1 id="cto-平山の発表">CTO 平山の発表</h1>
<p>そして、初日の Lightning Talks 前のスポンサーの PR 枠にて弊社の CTO 平山が発表をしました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180606/20180606145341.png" alt="20180606145341.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605175431.jpg" alt="20180605175431.jpg">
<p>発表では、「医療ヘルスケア分野の課題を解決する」というミッションのもとメドレーが 4 つの事業を行っていること、また、以前<a href="https://developer.medley.jp/entry/2018/05/01">本ブログで紹介</a>しました 3 本のニュースリリースを含めたこの 1 年のアップデート内容を中心に紹介させていただきました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605175456.jpg" alt="20180605175456.jpg">
<p>公演の途中での CTO 平山からメドレーのことを知っている人?との問いかけで、7・ 8 割の方が挙手をしていたのは感慨深かったです。</p>
<p>当日のスライドはこちらです。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/f09aaf0eccc14d9f9e958c5957c30823" title="Company & Product Information /RubyKaigi2018" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 420px;" data-ratio="1.3333333333333333"></iframe>
<h1 id="現地での反応">現地での反応</h1>
<p>ブース展示、PR 枠での発表を通じて以下のような嬉しい反応もいただきました!</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">オンライン診療はもっと普及すべきですね、地方医療の課題解決にも繋がる素晴らしい取り組みだと思う。<a href="https://twitter.com/hashtag/rubikaigi2018?src=hash&ref_src=twsrc%5Etfw">#rubikaigi2018</a> <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw">#rubykaigi</a><a href="https://twitter.com/hashtag/%E3%83%A1%E3%83%89%E3%83%AC%E3%83%BC?src=hash&ref_src=twsrc%5Etfw">#メドレー</a></p>— ふじこ / SHOWROOM の人事おねえさん (@yufujikochan) <a href="https://twitter.com/yufujikochan/status/1002106292163366912?ref_src=twsrc%5Etfw">May 31, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>[https://twitter.com/yucao24hours/status/1002105819297595392:embed]
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">指先を切ってしまったけど絆創膏をノベルティで配ってるから安心 <a href="https://twitter.com/hashtag/rubykaigi?src=hash&ref_src=twsrc%5Etfw">#rubykaigi</a> <a href="https://t.co/NeXWfTif6m">pic.twitter.com/NeXWfTif6m</a></p>— Motonori Iwata (@mobcov) <a href="https://twitter.com/mobcov/status/1002397188226351104?ref_src=twsrc%5Etfw">June 1, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>ブースでも、2 日目以降「1 日目のセッション見ましたよー」と話してくださる方も多く、メドレーとそのプロダクトについて Rubyist の皆様に知っていただくとても良い機会になっていたと思いました。</p>
<h1 id="セッションの様子">セッションの様子</h1>
<p>エンジニアはブースでの会社紹介の合間に各自気になったセッションを聴講したりもしました。
感覚ですが mruby や型、パフォーマンス周りの話題が多かったように思います。まさに 2018 年現在の Ruby を取り巻く環境を表している感じがします。エンジニアとしてこういった技術のセッションを聞けるのは純粋に楽しいですし、日々の開発に活かせそうなネタもあったりと、とても有意義な時間を過ごせました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605175826.jpg" alt="20180605175826.jpg">
<p><a href="https://rubykaigi.org/2018/presentations/yukihiro_matz.html#may31">初日の keynote</a>での 1 コマ。</p>
<p>Matz さんの keynote にもありましたが何事も「塞翁が馬」です。毎年 Ruby は死に、そして生まれ変わります(クリスマスに)。Ruby も常に進化していることを肌で感じることのできるセッション群でした。</p>
<h1 id="番外編">番外編</h1>
<p>さて、メドレー恒例(?)のお参りですが今回は大崎八幡宮に参詣することになりました。</p>
<p>大崎八幡宮の社殿は国宝にも指定されており、安土桃山時代の豪華絢爛な様式の建築でとても雰囲気のある神社でした。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605175920.jpg" alt="20180605175920.jpg">
<p>参拝する 3 人。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605175948.jpg" alt="20180605175948.jpg">
<p>参拝する 2 人とそれを撮影する 2 人。</p>
<h1 id="さいごに">さいごに</h1>
<p>昨年に引き続きメドレーの RubyKaigi 協賛は 2 度目になりました。</p>
<p>ブースで会社やプロダクトの説明していると「メドレー知ってます」との声を聞く機会も多く、とても嬉しい限りでした。これまで以上に Ruby、医療 ×IT を盛り上げていければという気持ちを胸に仙台を後にしました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180605/20180605180049.jpg" alt="20180605180049.jpg">
<p>(新幹線から仙台の夕焼けをパシャリ)</p>
<h1 id="お知らせ">お知らせ</h1>
<p>弊社では「医療 x IT への挑戦」に取り組みたいエンジニアのみなさんを心からお待ちしております!
興味がある方は、こちらの「話を聞いてみたい」からご連絡ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.wantedly.com/companies/medley" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">株式会社メドレーの会社情報 - Wantedly</div>
<div class="remark-link-card-plus__description">株式会社メドレーの魅力を伝えるコンテンツと、住所や代表・従業員などの会社情報です。急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。
しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.wantedly.com/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.wantedly.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://images.wantedly.com/i/YMVT7Fy?h=1440&w=1440" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>開発本部の雰囲気をもっと知りたい方は、こちらからどうぞ。
<a href="https://www.medley.jp/recruit/creative.html">https://www.medley.jp/recruit/creative.html</a></p>medley
- アプリエンジニアがのぞいた React Native 〜メドレー TechLunch〜https://developer.medley.jp/entry/2018/05/29/164125https://developer.medley.jp/entry/2018/05/29/164125こんにちは、開発本部の高井です。メドレー開発本部で行われている勉強会「TechLunch」でReact Nativeについて発表しました。
私は普段は Swift、Kotlin/Java を使ってネイティブアプリを開発しており、React ...Tue, 29 May 2018 07:41:25 GMT<p>こんにちは、開発本部の高井です。メドレー開発本部で行われている勉強会「TechLunch」で<a href="https://facebook.github.io/react-native/">React Native</a>について発表しました。</p>
<p>私は普段は Swift、Kotlin/Java を使ってネイティブアプリを開発しており、React Native に触るのは初めてでした。そこで今回は、アプリエンジニアの視点から、実装するための基本的な知識と弊社の実際の開発で使えそうかを検討した結果についてご紹介します。</p>
<h1 id="なぜ-react-native-を触ってみようと思ったか">なぜ React Native を触ってみようと思ったか</h1>
<p>オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」の開発では、iOS/Android アプリをそれぞれのネイティブ言語で別々に開発しているため、実装やレビューの際にはプラットフォーム間の仕様の違いを理解する必要があり、なかなか大変だと感じていました。</p>
<p>これらの課題に対して<a href="https://developer.medley.jp/entry/2018/03/27/180621">こちら</a>のブログでも紹介したような施策を行って改善を行ってきましたが、ソースを共通化することでより開発効率を向上できないかと思い、クロスプラットフォーム開発についても調べてみることにしました。</p>
<p>その中でも以下の理由から、今回は React Native について調べてみることにしました。</p>
<ul>
<li>JavaScript(以下 JS)・ React のため、Web エンジニアがネイティブアプリ開発を行う際のハードルを低くすることができそう</li>
<li>UI の実装にネイティブ UI を使用しているので自然なデザインやインタラクションを作りやすそう</li>
<li>ある程度リリースから時間が経って情報が豊富にある</li>
</ul>
<p>特に弊社では、ネイティブアプリより Web 開発をメインに行ってきたエンジニアの方が多く、また Web フロントに React が使われているプロダクトもいくつかあるので、React Native を採用することでチームの開発効率の向上だけでなく、開発本部全体でもネイティブアプリ開発の学習コストが低くなるのではないかと考えました。</p>
<p>そこで、以降ではネイティブアプリエンジニアと Web エンジニアそれぞれにとって、開発しやすいかどうかという観点で React Native の開発方法を見ていきたいと思います。</p>
<h1 id="初期設定について">初期設定について</h1>
<h2 id="インストールアプリ実行">インストール〜アプリ実行</h2>
<p>インストールは公式の<a href="https://facebook.github.io/react-native/docs/getting-started.html">Getting Started</a>にもありますが、以下のコマンドで完了です。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> brew</span><span style="color:#CE9178"> install</span><span style="color:#CE9178"> node</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> brew</span><span style="color:#CE9178"> install</span><span style="color:#CE9178"> watchman</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> npm</span><span style="color:#CE9178"> install</span><span style="color:#569CD6"> -g</span><span style="color:#CE9178"> react-native-cli</span></span>
<span class="line"></span></code></pre>
<p>今回、私が触るにあたっては Xcode と Android Studio のシミュレータ(エミュレータ)で実行しながら開発しましたが、React Native は Xcode や Android Studio がインストールされていなくても、<a href="https://expo.io/">Expo</a>というクライアントアプリを実機にインストールすることで画面をプレビューしながら開発することができます。</p>
<p>これなら、Xcode のダウンロードを待つ必要もありません!(iOS エンジニアでもアップグレードのたびに Xcode のダウンロードを待つのはイライラします)</p>
<p>次に、プロジェクト作成、シミュレータでのアプリ実行は以下のコマンドで実行できます。</p>
<p>ただし、シミュレータ実行前に Xcode Command Line Tools のインストールと Android Studio でいくつかの設定(SDK のインストール、AVD の作成、環境変数の設定)が必要です。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A9955"># プロジェクト作成</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> react-native</span><span style="color:#CE9178"> init</span><span style="color:#CE9178"> AwesomeProject</span></span>
<span class="line"><span style="color:#DCDCAA"> #</span><span style="color:#CE9178"> アプリ実行(シミュレータ)</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> cd</span><span style="color:#CE9178"> AwesomeProject</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> react-native</span><span style="color:#CE9178"> run-ios</span><span style="color:#CE9178"> or</span><span style="color:#CE9178"> react-native</span><span style="color:#CE9178"> run-android</span><span style="color:#6A9955"> # Android の場合、emulator を別途起動してからでないと実行できない</span></span></code></pre>
<h1 id="実装してみた感想">実装してみた感想</h1>
<h2 id="ui-の実装方法">UI の実装方法</h2>
<p>React Native では React 同様 UI の各パーツをコンポーネントと呼び、それらを配置することで UI を実装していきます。違いは Web の HTML の代わりに Native の UI を描画するためのコンポーネントとして使う点です。</p>
<p>見た目やレイアウトは CSS と似たような形式で記述します。React Native で使えるスタイルのプロパティは各コンポーネントで異なりますが、例えば、View コンポーネントに設定できるプロパティには以下のようなものがあります。</p>
<ul>
<li><a href="https://facebook.github.io/react-native/docs/view-style-props.html">View Style Props</a></li>
<li><a href="https://facebook.github.io/react-native/docs/layout-props.html">Layout Props</a></li>
</ul>
<p>Web で使われているものと全く同じというわけではないですが、flex、margin、border などの使い慣れている CSS のプロパティ名で設定できるので、Web エンジニアにとっては実装のハードルが下がるのではないかと思いました。ただ、普段 CSS を触っていないネイティブアプリエンジニアにとっては学習コストがかかると思います。</p>
<p>また一度ビルドすれば、JS による修正内容をビルドなしでシミュレータに反映することができるので、View 周りの調整は効率的にできそうでした。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#6A9955">// js/components/home.js</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#DCDCAA"> renderItem</span><span style="color:#D4D4D4"> = ({ </span><span style="color:#9CDCFE">item</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">index</span><span style="color:#D4D4D4"> }) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> (</span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#4EC9B0">View</span><span style="color:#9CDCFE"> style</span><span style="color:#D4D4D4">=</span><span style="color:#569CD6">{</span><span style="color:#9CDCFE">styles</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">row</span><span style="color:#569CD6">}</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#4EC9B0">Text</span><span style="color:#9CDCFE"> style</span><span style="color:#D4D4D4">=</span><span style="color:#569CD6">{</span><span style="color:#9CDCFE">styles</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">title</span><span style="color:#569CD6">}</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#569CD6"> {</span><span style="color:#DCDCAA">parseInt</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">index</span><span style="color:#D4D4D4">, </span><span style="color:#B5CEA8">10</span><span style="color:#D4D4D4">) + </span><span style="color:#B5CEA8">1</span><span style="color:#569CD6">}</span></span>
<span class="line"><span style="color:#569CD6"> {</span><span style="color:#CE9178">". "</span><span style="color:#569CD6">}</span></span>
<span class="line"><span style="color:#569CD6"> {</span><span style="color:#9CDCFE">item</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">title</span><span style="color:#569CD6">}</span></span>
<span class="line"><span style="color:#808080"> </</span><span style="color:#4EC9B0">Text</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#4EC9B0">Text</span><span style="color:#9CDCFE"> style</span><span style="color:#D4D4D4">=</span><span style="color:#569CD6">{</span><span style="color:#9CDCFE">styles</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">description</span><span style="color:#569CD6">}</span><span style="color:#808080">></span><span style="color:#569CD6">{</span><span style="color:#9CDCFE">item</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">description</span><span style="color:#569CD6">}</span><span style="color:#808080"></</span><span style="color:#4EC9B0">Text</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> </</span><span style="color:#4EC9B0">View</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">const</span><span style="color:#4FC1FF"> styles</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">StyleSheet</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">create</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#9CDCFE"> row:</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> borderBottomWidth:</span><span style="color:#B5CEA8"> 1</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> borderColor:</span><span style="color:#CE9178"> "#ccc"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> padding:</span><span style="color:#B5CEA8"> 10</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> title:</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> fontSize:</span><span style="color:#B5CEA8"> 15</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> fontWeight:</span><span style="color:#CE9178"> "600"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> description:</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> marginTop:</span><span style="color:#B5CEA8"> 5</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> fontSize:</span><span style="color:#B5CEA8"> 14</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span></code></pre>
<p>ただ、OS ごとにデザインを合わせる方針にしない限りは、コードも別々になるところがわりと多くなりそうだと感じました。</p>
<p>例えば、TabBar(iOS)と DrawerLayout(Android)、DatePicker(iOS)と TimePicker(Android)、ProgressView(iOS)と ProgressBar(Android)などは React Native では別コンポーネントとして提供されていて、API も違っていました。</p>
<h2 id="画面遷移">画面遷移</h2>
<p>画面遷移(プッシュ、モーダル、タブ遷移など)のために API として公式で提供されているのは iOS のみで Android は別途、実装するか、サードパーティのライブラリを使用する必要があります。</p>
<p>わたしが使ってみた<a href="https://wix.github.io/react-native-navigation/">react-native-navigation</a>はモーダル、プッシュなどの遷移がネイティブ API ベースで実装されているので、ネイティブ言語で実装した場合と比べて違和感なく実装することができました。</p>
<p>下記のような形でプッシュやモーダル表示での遷移ができます。特定の画面に戻る機能については開発中であったり、機能的な制約は少しありそうです。そういう細かいところはネイティブ言語でやった方が自由が効くので、良いなと思います。</p>
<p>それでも、iOS と Android では複数画面の管理や遷移についての考え方が違い、Android を初めて開発した時に同じことを実現するのが難しかった覚えがあるので、共通の方法で実現できるのは便利でした。特に Web だとあまり画面間の遷移について考えることはないと思うので、共通化されているとネイティブアプリ開発の学習コストが下がると思います。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#569CD6">this</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">props</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">navigator</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">push</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#6A9955"> // プッシュ</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> screen:</span><span style="color:#CE9178"> "example.PushedScreen"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> title:</span><span style="color:#CE9178"> "Pushed Screen"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span>
<span class="line"><span style="color:#569CD6">this</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">props</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">navigator</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">pop</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#6A9955"> // 前の画面に戻る</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> animated:</span><span style="color:#569CD6"> true</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> animationType:</span><span style="color:#CE9178"> "fade"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span></code></pre>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#569CD6">this</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">props</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">navigator</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">showModal</span><span style="color:#D4D4D4">({</span></span>
<span class="line"><span style="color:#6A9955"> // モーダル</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> screen:</span><span style="color:#CE9178"> "example.ModalScreen"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> title:</span><span style="color:#CE9178"> "Modal"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9CDCFE"> animationType:</span><span style="color:#CE9178"> "slide-up"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#D4D4D4">});</span></span></code></pre>
<h2 id="ネットワーク周り">ネットワーク周り</h2>
<p>ネットワーク経由でデータを取得して、モデルに変換し、アプリ内で使うというよくある操作を行うには Fetch API を利用します。<a href="https://developer.mozilla.org/ja/docs/Web/API/Fetch_API">Fetch API</a>は JS で提供されている Promise ベースの API です。例えば、以下のような形で JSON を返す API からデータを取得し、<code>receiveHelthNews</code>関数にオブジェクトに変換した配列を渡すことができます。JS の API なので Web エンジニアにとっては使いやすいのではないかと思います。</p>
<p>ネイティブ言語でそれぞれ実装する場合は、URLSession(iOS)と HttpURLConnection(Android)、あるいは各プラットフォーム向けに提供されているサードパーティのライブラリなどを使うと思いますが、当然、API は異なるのでそれぞれの実装方法を把握しないといけなくなります。それに比べると学習コストは低くなりそうです。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#6A9955">// js/actions/index.js</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0">export</span><span style="color:#569CD6"> function</span><span style="color:#DCDCAA"> fetchHelthNews</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4"> (</span><span style="color:#9CDCFE">dispatch</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span></span>
<span class="line"><span style="color:#DCDCAA"> fetch</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">constructHealthNewsUrl</span><span style="color:#D4D4D4">())</span></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">then</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">response</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#9CDCFE"> response</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">json</span><span style="color:#D4D4D4">())</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">then</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">json</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#DCDCAA"> dispatch</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">receiveHelthNews</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">json</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">articles</span><span style="color:#D4D4D4">)))</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">catch</span><span style="color:#D4D4D4">((</span><span style="color:#9CDCFE">error</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> console</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">log</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">error</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4"> });</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<h2 id="ネイティブアプリ特有の機能について">ネイティブアプリ特有の機能について</h2>
<p>その他のアプリ開発でよく使うネイティブアプリ特有の機能を実装する方法は以下のようになります。多くの機能がサードパーティのライブラリに依存しているので、各言語のバージョンアップ時の対応が少し心配ではありますが、よく使う機能については実現することができます。</p>
<ul>
<li>Push 通知:Android はハンドリングする API が公式で提供されていないのでサードパーティのライブラリを使う</li>
<li>カメラ、キーチェーンアクセス/ユーザデフォルト:公式の API は提供されていないのでサードパーティのライブラリを使う</li>
<li>位置情報の取得:公式 API が提供されている</li>
<li>ディープリンク:アプリ起動時にハンドリングを行う API は提供されている。ユニバーサルリンクや IntentFilter の設定は各プラットフォームで個別に必要になる</li>
</ul>
<h1 id="リリースはどうやるか">リリースはどうやるか</h1>
<p>アーカイブやストア配布についてはネイティブアプリの配布と同じプロセスになります。</p>
<p>各プラットフォームで証明書等の設定を行い、Xcode や Android Studio、あるいは CLI でコマンドを実行して、ipa / apk ファイルを作成し、各 Store にアップロードする必要があります。</p>
<p>CI は<a href="https://www.bitrise.io/">Bitrise</a>などが使えます。Bitrise でビルドを試してみましたが、React Native のリポジトリと接続したときにできるデフォルトのワークフローを使えば、同時に 2 プラットフォームのアーカイブが作成できて便利でした。</p>
<p>あと、まだ試せてはいないのですが<a href="https://docs.microsoft.com/en-us/appcenter/distribution/codepush/">CodePush</a>を使えば、審査に提出することなしに既存のアプリを変更することもできるらしいので、非常に便利だと思いました。</p>
<h1 id="どんな場合に-react-native-を採用できそうか">どんな場合に React Native を採用できそうか?</h1>
<p>React Native で開発することで、Web 開発者の視点で見るとプラットフォームのネイティブ言語で開発するよりも、だいぶ学習コストが下がるのではないかと思いました。またサードパーティのライブラリを使えば、機能的に大きな問題となるようなことはなさそうでした。ただ、画面遷移のライブラリがそうだったように、もしやりたいことができないという場合は、妥協しないといけない部分が出てきそうだと思いました。</p>
<p>一方、アプリエンジニアにとっては、慣れるまではかなり開発速度が下がりそうなのでデメリットも大きいかなと思いました。React と JS、また Redux なども新たに理解しつつ開発していたので結構ハードルが高いと感じました。開発環境もビルドが通ってるうちは、View 周りの調整がすぐに確認できて良かったのですが、ランタイムエラーになるなどで、シミュレータがリロードできなくなった場合に再度ビルドし直すということがよく起こり、常に快適に開発できるというわけではありませんでした。</p>
<p>運用面でみると一通りアプリ開発に必要そうなツールは揃っているし(クラッシュ監視、CI、テスト配信、リリース)、Code Push など便利なツールもあるので利点が多いと思いました。</p>
<p>結論としては、Web エンジニアが社内に多かったり、開発チームに React、JS が得意なメンバーがいるなら、実際の開発でも使えるかなと思いました。ただ、アプリエンジニアにとってはかなりストレスがたまるプロジェクトになりそうだと感じました。</p>
<h1 id="まとめ">まとめ</h1>
<p>自分の現状で考えると、アプリは得意だけど JS や React にそこまで詳しくないので、もし直近のプロジェクトで難易度もそこそこ高いようであれば、正直あんまり使いたくないなあという気持ちがあります。ただ、技術の幅を広げるとか、組織全体のポータビリティなどの観点で考えると利点が結構あるのかなと思います。チャンスがあればぜひ、チャレンジしてみたいです。</p>
<p>わたしはどちらかというと技術や開発ツールの新しさよりも、ユーザとの接点のところで新しいことや面白いことを追求したいと思っていますが、開発効率や品質の向上のために最適なものを選択できるように、今後も新しいツールのキャッチアップは積極的に行っていきたいと思っています。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーは、5/31-6/2 に開催される RubyKaigi 2018 に LT スポンサーとして協賛します。ブースも構えておりますので、イベントにお越しになる方は、ぜひブースにも遊びにいらしてください!</p>
<div class="remark-link-card-plus__container">
<a href="https://rubykaigi.org/2018" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">RubyKaigi 2018, 5/31...6/2, Sendai, Miyagi, Japan #rubykaigi</div>
<div class="remark-link-card-plus__description">RubyKaigi 2018, 5/31...6/2, Sendai, Miyagi, Japan</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://rubykaigi.org/2018/images/favicon.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">rubykaigi.org</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="http://rubykaigi.org/2018/images/og_image.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- ElastiCache for Redis 運用小話 〜メドレー・ TechLunch〜https://developer.medley.jp/entry/2018/05/25/120000https://developer.medley.jp/entry/2018/05/25/120000こんにちは、開発本部の後藤です。医療介護の求人サイト「ジョブメドレー」の開発を担当しています。
ジョブメドレーでは各種キャッシュや sidekiq の queue 等にElastiCache for Redisを利用しています。
先日、メド...Fri, 25 May 2018 03:00:00 GMT<p>こんにちは、開発本部の後藤です。医療介護の求人サイト「ジョブメドレー」の開発を担当しています。</p>
<p>ジョブメドレーでは各種キャッシュや <a href="https://github.com/mperham/sidekiq">sidekiq</a> の queue 等に<a href="https://aws.amazon.com/jp/elasticache/redis/">ElastiCache for Redis</a>を利用しています。</p>
<p>先日、メドレーで定期開催している社内勉強会 TechLunch にて、ジョブメドレーでの ElastiCache for Redis の運用周りのネタや知見について発表しました。本記事では、その中から抜粋してメモリ周りの話について紹介します。</p>
<h1 id="キー削除周りの仕様について">キー削除周りの仕様について</h1>
<p>キャッシュとして ElastiCache for Redis を利用していく上でまず把握しておきたいのが、キーの削除周りの仕様です。</p>
<h2 id="redis-本家の-expirationeviction">Redis 本家の Expiration/Eviction</h2>
<p>Expiration については Redis ではキーに TTL を設定することで有効期限を設定することができ、<a href="https://redis.io/commands/expire#how-redis-expires-keys">こちら</a> に記載のロジックで削除処理を実施してくれます。</p>
<p>Eviction については Redis 本家の実装では以下のような仕様になっています。Redis 本家ドキュメントは<a href="https://redis.io/topics/lru-cache">こちら</a>。</p>
<ul>
<li><code>used_memory</code>(アプリが利用しているメモリ量)が<code>maxmemory</code>値を超え始めたら Eviction が発火し始める
<ul>
<li><code>maxmemory</code>は <em>redis.conf</em> 指定か <a href="https://redis.io/commands/config-set"><code>CONFIG SET</code></a>コマンドで設定できる</li>
</ul>
</li>
<li><code>maxmemory</code>に到達したらどのように振る舞うべきかを<code>maxmemory-policy</code>で指定
<ul>
<li><code>noeviction</code>: 容量が必要なオペレーションでは evit せず、エラーとなる</li>
<li><code>allkeys-lru</code>: 常に LRU アルゴリズムで削除対象選定</li>
<li><code>volatile-lru</code>: TTL が設定されたキー内で LRU アルゴリズムで削除対象選定</li>
<li><code>allkeys-random</code>: 常にランダムに削除対象選定して削除</li>
<li><code>volatile-random</code>: TTL が設定されたキー内でランダムに削除対象選定</li>
<li><code>volatile-ttl</code>: TTL が設定されたキー内で TTL</li>
</ul>
</li>
</ul>
<p>また、ElastiCache for Redis ではまだ 3 系列までしか使えませんが、4 系列では新たに<a href="https://antirez.com/news/109">LFU</a>も選択肢に入ってくるようです。</p>
<p>Redis 本家の Eviction の動作仕様としては以下のイメージです。</p>
<div class="remark-link-card-plus__container">
<a href="https://github.com/antirez/redis/blob/3.2.10/src/server.c#L3343-L3357" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">github.com</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=github.com" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">github.com</span>
</div>
</div>
</a>
</div>
<p>ElastiCache for Redis では<code>maxmemory</code>は編集できず、その代わりに後述する <code>reserved-memory</code> (<code>reserved-memory-percent</code>)を設定すると Eviction が発火するメモリ量の閾値が変わることから上記周りの実装は AWS 側で手を入れていそうです。</p>
<h2 id="elasticache-for-redis-での-eviction">ElastiCache for Redis での Eviction</h2>
<p>ElastiCache for Redis では以下のように Redis 本家にはない<code>reserved-memory</code>という概念があるので注意が必要です。</p>
<ul>
<li><code>maxmemory</code> はインスタンスタイプによって固定値に設定されている</li>
<li><code>maxmemory</code> は<strong>変更できない</strong>
<ul>
<li>代わりに <code>reserved-memory</code> or <code>reserved-memory-percent</code> を設定して Eviction 発火メモリ量を設定できる</li>
<li><code>maxmemory</code> - <code>reserved-memory</code> < <code>used_memory</code> で Eviction が発火する</li>
</ul>
</li>
<li>古めのインスタンス(2017 年 3 月 16 日以前作成)では <code>reserved-memory</code> がパラメータとして利用できるが、<code>reserved-memory</code> のデフォルト値は 0byte
<ul>
<li><code>reserved-memory-percent</code> のデフォルト値は 25%</li>
</ul>
</li>
<li>この周辺の AWS 公式ドキュメントは<a href="https://docs.aws.amazon.com/ja_jp/AmazonElastiCache/latest/UserGuide/redis-memory-management.html">こちら</a></li>
</ul>
<p>ジョブメドレーでは単一の ElastiCache for Redis インスタンスで運用対象を減らす戦略を取っています(Redis 本家では用途別に分けて適切にチューニングすることを推奨しているため、将来的にはこの構成は変わるかもしれません)。そのため、キャッシュ利用のキーには必ず TTL を設定し、Eviction policy は<code>volatile-lru</code>としています。
そして、少し余裕をもたせて <code>reserved-memory</code> を設定、Evictions メトリクスの監視をしています。</p>
<h1 id="メモリ利用量を-sql-で分析する">メモリ利用量を SQL で分析する</h1>
<p>現在稼働している本番環境上でどのパターンのキーがどの程度のメモリを使っているかを把握したくなる場面に出くわした方もいらっしゃるのではないでしょうか。</p>
<p>ElastiCache for Redis では簡単に RDB スナップショットを取得することができます。このデータをうまく使えば直接本番稼動のインスタンスを触りにいくことなく、手元で安心して分析作業が実施できます。</p>
<p>この分析作業に便利なのが <a href="https://github.com/sripathikrishnan/redis-rdb-tools">redis-rdb-tools</a> です。このツールは RDB ファイルの内容をもとにキーごとの byte 値を以下のような CSV に出力することができます(<code>size_in_bytes</code>は理論値)。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> rdb</span><span style="color:#569CD6"> -c</span><span style="color:#CE9178"> memory</span><span style="color:#CE9178"> dump.rdb</span><span style="color:#D4D4D4"> > </span><span style="color:#CE9178">memory.csv</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> cat</span><span style="color:#CE9178"> memory.csv</span></span>
<span class="line"></span>
<span class="line"><span style="color:#DCDCAA">database,type,key,size_in_bytes,encoding,num_elements,len_largest_element</span></span>
<span class="line"><span style="color:#DCDCAA">0,list,lizards,241,quicklist,5,19</span></span>
<span class="line"><span style="color:#DCDCAA">2,hash,baloon,138,ziplist,3,11</span></span></code></pre>
<p>この csv を以下のように SQLite 等のデータベースに突っ込むことで手元で SQL を使ってメモリ統計の分析ができるようになります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> rdb</span><span style="color:#569CD6"> -c</span><span style="color:#CE9178"> memory</span><span style="color:#CE9178"> dump.rdb</span><span style="color:#D4D4D4"> > </span><span style="color:#CE9178">memory.csv</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sqlite3</span><span style="color:#CE9178"> memory.db</span></span>
<span class="line"><span style="color:#DCDCAA">sqlite</span><span style="color:#D4D4D4">> </span><span style="color:#CE9178">create</span><span style="color:#CE9178"> table</span><span style="color:#CE9178"> memory</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">database</span><span style="color:#CE9178"> int,type</span><span style="color:#CE9178"> varchar</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">128</span><span style="color:#D4D4D4">)</span><span style="color:#CE9178">,key</span><span style="color:#CE9178"> varchar</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">128</span><span style="color:#D4D4D4">)</span><span style="color:#CE9178">,size_in_bytes</span><span style="color:#CE9178"> int,encoding</span><span style="color:#CE9178"> varchar</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">128</span><span style="color:#D4D4D4">)</span><span style="color:#CE9178">,num_elements</span><span style="color:#CE9178"> int,len_largest_element</span><span style="color:#CE9178"> varchar</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">128</span><span style="color:#D4D4D4">));</span></span>
<span class="line"><span style="color:#DCDCAA">sqlite</span><span style="color:#D4D4D4">></span><span style="color:#CE9178">.mode</span><span style="color:#CE9178"> csv</span><span style="color:#CE9178"> memory</span></span>
<span class="line"><span style="color:#DCDCAA">sqlite</span><span style="color:#D4D4D4">></span><span style="color:#CE9178">.import</span><span style="color:#CE9178"> memory.csv</span><span style="color:#CE9178"> memory</span></span></code></pre>
<p>ざっくりとした分析の流れは</p>
<ol>
<li>本番環境の daily backup RDB 取得</li>
<li><code>redis-rdb-tools</code>で csv 出力</li>
<li>csv を sqlite に csv format で import</li>
<li>SQL で分析</li>
</ol>
<p>のようになります。データベースにインポートさえ出来てしまえば以下のように様々な分析が可能になります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">sqlite</span><span style="color:#D4D4D4">> </span><span style="color:#CE9178">select</span><span style="color:#CE9178"> sum</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">size_in_bytes</span><span style="color:#D4D4D4">) </span><span style="color:#CE9178">from</span><span style="color:#CE9178"> memory</span><span style="color:#CE9178"> where</span><span style="color:#CE9178"> key</span><span style="color:#CE9178"> like</span><span style="color:#CE9178"> '%cells%'</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#DCDCAA">XXXXX</span></span>
<span class="line"><span style="color:#DCDCAA">sqlite</span><span style="color:#D4D4D4">> </span><span style="color:#CE9178">select</span><span style="color:#CE9178"> count</span><span style="color:#D4D4D4">(*) </span><span style="color:#CE9178">from</span><span style="color:#CE9178"> memory</span><span style="color:#CE9178"> where</span><span style="color:#CE9178"> key</span><span style="color:#CE9178"> like</span><span style="color:#CE9178"> '%cells%'</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#DCDCAA">XXXXX</span></span></code></pre>
<p>注意点としては実際に本番稼動しているメモリ量を取得しているわけではないので実測値と値がずれてくることです(感覚的には理論値は実測値の 1/2 ぐらいでした)。こちらの詳細の推定ロジックが気になる方は<a href="https://github.com/sripathikrishnan/redis-rdb-tools/blob/master/rdbtools/memprofiler.py">実装</a>を確認いただければと思います。</p>
<p>ジョブメドレーではこの手法を使って分析することでアプリロジックを修正して不要なメモリ利用の改善等に役立てています。</p>
<h1 id="まとめ">まとめ</h1>
<p>ElastiCache for Redis のメモリ周りを中心とした運用ネタについて紹介しました。</p>
<p>ElastiCache for Redis はとても便利でシュッと設定してそれなりの規模でもなんとなく運用できてしまう手軽さがありますが、油断していると思わぬ落とし穴にはまることもあります。</p>
<p>本記事がみなさまの ElastiCache for Redis 運用ライフの一助になれば幸いです。</p>
<p>また、ジョブメドレーをはじめ、メドレーの開発にご興味ある方は、ぜひご連絡ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>メドレーが LT スポンサーを務める RubyKaigi2018 にもお邪魔する予定(たまにブースに立っています)ので、そちらでもお会いしましょう。</p>
<div class="remark-link-card-plus__container">
<a href="https://rubykaigi.org/2018" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">RubyKaigi 2018, 5/31...6/2, Sendai, Miyagi, Japan #rubykaigi</div>
<div class="remark-link-card-plus__description">RubyKaigi 2018, 5/31...6/2, Sendai, Miyagi, Japan</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://rubykaigi.org/2018/images/favicon.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">rubykaigi.org</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="http://rubykaigi.org/2018/images/og_image.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 電子カルテシステム開発の難しさを解決するためのフルマネージドサービスの活用https://developer.medley.jp/entry/2018/05/21/180652https://developer.medley.jp/entry/2018/05/21/1806524 月末に新たにリリースしたクラウド型電子カルテ「CLINICS カルテ」の開発を担当している田中です。
電子カルテという医療行為を支えるプロダクト開発ならではの醍醐味や難しさを感じる日々を過ごしています。前回はCLINICS カルテのデザ...Mon, 21 May 2018 09:06:52 GMT<p>4 月末に新たにリリースしたクラウド型電子カルテ「<a href="https://clinics.medley.life/karte">CLINICS カルテ</a>」の開発を担当している田中です。</p>
<p>電子カルテという医療行為を支えるプロダクト開発ならではの醍醐味や難しさを感じる日々を過ごしています。前回は<a href="https://developer.medley.jp/entry/2018/05/18/130000">CLINICS カルテのデザインについてマエダが紹介しました</a>が、今回はエンジニアから見た苦悩と葛藤についてお話します。</p>
<p>苦労した点としては、医療事務の業務そのものの複雑さやそれに伴うアプリケーション開発の複雑さはもちろんのこと、 <strong>電子カルテ開発の特徴として関連省庁のガイドラインの準拠やレセプトソフト(医療会計専用のシステム)のシステム管理など、多岐にわたり対応が必要なこと</strong> が挙げられます。</p>
<p>ガイドラインへの対応に関するお話は細かくなってしまうので、大まかな内容として、今回は主にレセプトシステムとして連携している ORCA のシステム管理方法についてお伝えさせていただければと思います(ORCA に関する説明は後述します)。</p>
<h1 id="ガイドラインへの対応">ガイドラインへの対応</h1>
<p>電子カルテのように医療情報を扱う際に遵守する必要があるガイドラインとして 3 省 4 ガイドライン(厚生労働省、経済産業省、総務省の 3 省が出している 4 つのガイドライン)があります。</p>
<p>CLINICS カルテの開発を進めるにあたり、このガイドラインがシステム設計や運用に大きな影響を及ぼすため、まずはガイドラインを読み込み整理するところから始めました。</p>
<p>システム、アプリケーションともに対応すべきことは多かったのですが、システム構成に特に影響が大きかったのは以下の 2 点となります。</p>
<ul>
<li>サービス提供に用いるシステム、アプリケーションを日本国内法の適用が及ぶ場所に設置すること</li>
<li>クライアント証明書を利用した TLS クライアント認証を実施すること</li>
</ul>
<p>CLINICS カルテは弊社で既に運用しているオンライン診療アプリ「CLINICS」と連携させる前提であったため、ガイドラインに即したシステム構成としていくために、まず CLINICS のシステムを Heroku から AWS に移行することから始まりました。</p>
<p>また、クライアント認証を行うために AWS の構成を色々と検討し、Nginx とも日々格闘したりしました。。。</p>
<p>これらの対応内容に関連するブログも過去に書いていますので、興味がある方はぜひご参考にしてください。</p>
<p><a href="https://developer.medley.jp/entry/2017/08/24/120000_01">https://developer.medley.jp/entry/2017/08/24/120000_01</a>
<a href="https://developer.medley.jp/entry/2017/08/24/120000_02">https://developer.medley.jp/entry/2017/08/24/120000_02</a>
<a href="https://developer.medley.jp/entry/2017/09/22/124000">https://developer.medley.jp/entry/2017/09/22/124000</a></p>
<h1 id="orca-サーバの管理方法">ORCA サーバの管理方法</h1>
<p>CLINICS カルテは、日本医師会 ORCA 管理機構株式会社が提供する医療会計ソフトである「ORCA」を組み込んだ「ORCA 内包型」のクラウド型電子カルテです。</p>
<h2 id="参考-orca-とは">参考: ORCA とは</h2>
<p>医療現場 IT 化を推進する日本医師会が会員等のために提供しているレセプトソフト(診療報酬の請求業務を行うための医療会計ソフト)。2002 年にオープンソースとして公開され、現在、診療所を中心に 17,000 を超える全国の医療機関に導入されています。</p>
<p>医療情報ネットワーク推進委員会にて「医師会総合情報ネットワーク構想」(1997 年 情報化検討委員会)を構成するツールの一つとして認められた日本医師会の研究事業プロジェクト「ORCA プロジェクト」が開発、提供を開始したものです。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.orca.med.or.jp/orca/summary/outline.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">ORCA Project: ORCAプロジェクトの概要</div>
<div class="remark-link-card-plus__description">日本医師会開発・日医標準レセプトソフトウェアのサイトです</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.orca.med.or.jp/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.orca.med.or.jp</span>
</div>
</div>
</a>
</div>
<p>CLINICS カルテはおおまかに以下のような構成となっており、Ruby on Rails で稼働しているカルテアプリケーションと医療機関ごとの ORCA サーバがあり、その間を ORCA が提供している API で接続しています。</p>
<p>(下図は一例として上げているもので台数や詳細な構成は実際のシステムとは異なります)</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180521/20180521152610.png" alt="20180521152610.png">
<p>この ORCA サーバですが、医療機関ごとに 1 つの EC2 インスタンス構成となっており、利用する医療機関が増える度に EC2 インスタンスが増えるため、導入医療機関が増えるほど構築も運用もつらくなっていきます。このあたりを出来るだけ自動化したお話が今回のメインテーマです。</p>
<p>(これよりももっと楽ができる構成にしようと色々検討もしたのですが、システムの特殊性などの諸事情があり、結果 EC2 インスタンス単位で管理する構成になっています)</p>
<h1 id="orca-サーバー関連で主に使用した-aws-サービス">ORCA サーバー関連で主に使用した AWS サービス</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180521/20180521152646.png" alt="20180521152646.png">
<p>AMI 作成やインスタンス作成などのビルド系は<a href="https://aws.amazon.com/jp/codebuild/">CodeBuild</a>、パッチ適用やコマンド実行などのインスタンス管理は<a href="https://aws.amazon.com/jp/systems-manager/">Systems Manager</a>、脆弱性チェックなどのセキュリティ関連には<a href="https://aws.amazon.com/jp/guardduty/">Guard Duty</a>といったサービスを主に利用しています。</p>
<p>次に、インスタンス構築に関してもう少し具体的に説明します。</p>
<h1 id="orca-サーバのインスタンス構築">ORCA サーバのインスタンス構築</h1>
<h2 id="orca-サーバ用-amiゴールデンイメージ作成">ORCA サーバ用 AMI(ゴールデンイメージ)作成</h2>
<p>ビルドの実行は CodeBuild で行っています。AMI のビルドには<a href="https://www.packer.io/">Packer</a>を使用しており、ビルド完了時に作成された AMI の ID をパラメータストアに登録しています。この AMI ID をインスタンス作成時に参照します。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180521/20180521152801.png" alt="20180521152801.png">
<p>参考までに、CodeBuild で使用している buildspec.yaml は以下のようになります(説明箇所など、一部実際とは異なります)。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="yaml"><code><span class="line"><span style="color:#569CD6">phases</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> pre_build</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#6A9955"> ## 説明</span></span>
<span class="line"><span style="color:#D4D4D4"> * </span><span style="color:#CE9178">packer と jq をビルド用コンテナにインストール</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> commands</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">curl -qL -o packer.zip https://releases.hashicorp.com/packer/1.1.3/packer_1.1.3_linux_amd64.zip && unzip packer.zip</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">curl -qL -o jq https://stedolan.github.io/jq/download/linux64/jq && chmod +x ./jq</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> build</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#6A9955"> ## 説明</span></span>
<span class="line"><span style="color:#D4D4D4"> * </span><span style="color:#CE9178">処理に必要な AWS credential 情報を取得&設定(後の aws cli 使う用)</span></span>
<span class="line"><span style="color:#D4D4D4"> * </span><span style="color:#CE9178">packer ビルド実行、AMI 作成</span></span>
<span class="line"><span style="color:#D4D4D4"> * </span><span style="color:#CE9178">packer ビルド結果から作成された AMI ID を取得</span></span>
<span class="line"><span style="color:#D4D4D4"> * </span><span style="color:#CE9178">AMI ID をパラメータストアに登録</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> commands</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">curl -qL -o aws_credentials.json https://169.254.170.2/$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI > aws_credentials.json</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">aws configure set region $AWS_REGION</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">aws configure set aws_access_key_id `./jq -r '.AccessKeyId' aws_credentials.json`</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">aws configure set aws_secret_access_key `./jq -r '.SecretAccessKey' aws_credentials.json`</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">aws configure set aws_session_token `./jq -r '.Token' aws_credentials.json`</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">../packer build -machine-readable bin/packer.json | tee packer.log</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">cat packer.log | grep "artifact,0,id" | cut -d "," -f6 | cut -d ":" -f2 > ami.txt</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">ORCA_AMI_ID=`cat ami.txt`</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">aws ssm put-parameter --name ORCA_AMI_ID --value $ORCA_AMI_ID --type String --overwrite</span></span></code></pre>
<h2 id="orca-サーバ用-ec2-インスタンス作成">ORCA サーバ用 EC2 インスタンス作成</h2>
<p>医療機関用のアカウントを新規追加する場合、社内用の管理ツールでまず医療機関の基本情報を登録します。その後、ORCA インスタンス設定に必要なパラメータ(医療機関を識別する ID など)を設定し、インスタンス作成のビルド処理を実行します。</p>
<p>CodeBuild がゴールデンイメージ用の AMI をベースに ORCA インスタンスを構築します。ORCA インスタンスは起動時に各種設定処理を行います(cloud-init)</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180521/20180521152838.png" alt="20180521152838.png">
<p>なお、インスタンス起動時に行っている処理として主に以下のようなことを行っています。</p>
<ul>
<li>該当インスタンスの内部 IP を Private DNS に登録</li>
<li>ORCA のセットアップ/プログラム/マスタ更新</li>
<li>Postgres のバックアップ設定</li>
<li>Mackerel や Systems Manager 等の各種 Agent インストール、設定</li>
</ul>
<h2 id="orca-サーバ運用">ORCA サーバ運用</h2>
<p>構築が完了し稼働した後も、ORCA 自体のアップデートやパッチの適用など様々な運用が発生します。それらをどのように行っているか、まだ改善中ではありますが一部ご紹介します。</p>
<h3 id="インスタンスに対する各種操作の実行">インスタンスに対する各種操作の実行</h3>
<p>ORCA のアップデートなど、定期的に行う操作に必要なコマンドを document として登録し、RunCommand で特定インスタンスまたは複数インスタンスに一括実行できるようにしています。</p>
<h3 id="orca-アプリケーションの死活監視">ORCA アプリケーションの死活監視</h3>
<p>ORCA アプリケーション自体構成が複雑で、いくつものミドルウェアと連携して動いています。これらの各種プロセス/ログ監視は行った上で、API として正しく稼働しているかを確認するための死活監視を Mackerel のカスタムメトリクスとして登録し、一定数以上失敗した場合は各種プロセスを再起動し、それでも正常に動かない場合は<a href="https://mackerel.io/ja/">Mackerel</a>から Slack に通知させています。</p>
<h3 id="セキュリティチェック">セキュリティチェック</h3>
<p><a href="https://aws.amazon.com/jp/cloudtrail/">CloudTrail</a>や VPC フローログなどをもとに、GuardDuty で定期的にチェックし脆弱性が見つかった場合は Slack に通知させるようにしています。その他、セキュリティパッチ適用などもパッチマネージャーを利用し自動化させています。</p>
<h1 id="まとめ">まとめ</h1>
<p>電子カルテのシステムは検査や画像データなどを取り扱う外部システムと連携したいという需要もあります。そのような連携も視野に入れるとデータ標準化の問題などもあり難しい分野ではありますが、CLINICS カルテとして日々進化していくためにも、フルマネージドサービスなど活用できるところは積極的に活用し、集中すべき問題を解決していきたいと思います。</p>
<p>また、電子カルテのシステムとしての性質上、絶対に落とさないシステム運用が必要です。さらに堅牢なシステムにするために、トラブルを未然に防ぐような非機能面の強化や日々のモニタリングなど地道なことも含め、もっともっと取り組んでいく必要があります。</p>
<p>まだまだプロダクトとして未成熟な段階ではありますが、「医療の課題を解決する」ため、CLINICS カルテでは医療業務を効率化し、医療プラットフォームとなるべく進化していきたいという構想を持っています。(CLINICS カルテが目指す医療のプラットフォームとは?という話は、こちらで詳しく書かれています)</p>
<div class="remark-link-card-plus__container">
<a href="https://www.wantedly.com/companies/medley/post_articles/118281" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">「CLINICSカルテ」が目指す ”医療のプラットフォーム” とは? | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">お久しぶりです。メドレーで採用と広報を担当している加藤です。昨年よりはじめた「そのテーマ、役員みんなで話しました。」のコーナーですが、昨年と状況も変わりいろいろなメンバーにJOINしてもらえるよ...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.wantedly.com/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.wantedly.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.wantedly.com/post_articles/118281/ogp_image" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>CLINICS カルテは、まずは多くの医療機関に使ってもらうものではありますが、将来的には、患者さんと医療機関をつなぎ、診療や検査データなどをやりとりするプラットフォームとなる予定です。こうした新しい医療のプロダクトに興味のあるデザイナー・エンジニア・ディレクターの方はぜひこちらまでご応募おまちしております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 電子カルテの経験から、複雑なプロダクトデザインのアプローチを考えるhttps://developer.medley.jp/entry/2018/05/18/130000https://developer.medley.jp/entry/2018/05/18/130000こんにちは。プレスリリースや前回の平山のブログでも紹介がありました、患者とつながるクラウド型電子カルテ「CLINICS カルテ」のデザインを担当しているマエダです。
この電子カルテは、医療情報という複雑かつ独特なデータを扱うため、これまで自...Fri, 18 May 2018 04:00:00 GMT<p>こんにちは。プレスリリースや前回の平山のブログでも紹介がありました、患者とつながるクラウド型電子カルテ「<a href="https://clinics.medley.life/karte">CLINICS カルテ</a>」のデザインを担当しているマエダです。</p>
<p>この電子カルテは、医療情報という複雑かつ独特なデータを扱うため、これまで自分自身が取り組んで来たような Web サービスとは違ったデザインのアプローチが必要でした。</p>
<p>今回は、そんなデザイナーの苦悩と葛藤についてお話します。医療に限らず、複雑な業務フローの業界でデザインに悩む人のお役に立てれば嬉しいです。</p>
<h1 id="デザインに取り掛かる前の準備">デザインに取り掛かる前の準備</h1>
<p>CLINICS カルテは「日医標準レセプトソフト(ORCA)」を完全内包しているカルテなのですが、医療にかなり詳しい人でないと、「レセプトソフトって?」というところから疑問ですよね。</p>
<p>自分も同様で、レセプト?オーダー?DO 処方?…など医療の用語がわからない状態だったので、医療知識の理解や業務フローなどを知るところからプロジェクトに入り始めました。</p>
<p>デザイン業務を行う前に、医療事務の書籍を読んだり、医療事務がどのようにレセプト業務を行っているのかを学ぶことで、どのような UI が適しているかなど掴んでいきました。</p>
<p>ちなみに、皆さんが医療機関で受診した時に診療費の一部負担して支払われると思いますが(よく「3 割負担」など聞くこともあるかもしれません)残りの診療費については、 加入している医療保険の保険者(健康保険組合など)が支払います。</p>
<p>この診療費の請求のために発行する明細を診療報酬明細書(レセプト) といい、レセプトを作成し診療報酬を請求する業務のことをレセプト業務と言います。</p>
<p>そのレセプトを作成するための専用システムを「レセプトソフト」と言いますが、数あるレセプトソフトの中でも、日本医師会がオープンにして提供している「日医標準レセプトソフト(ORCA)」を、CLINICS カルテは内包しています。</p>
<p><a href="https://clinics.medley.life/karte"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180516/20180516184514.png" alt="f:id:medley_inc:20180516184514p:plain" title="f:id:medley_inc:20180516184514p:plain"></a></p>
<p>実際に ORCA を使ってみると、レセプト業務を知らない初心者には難易度が高い操作性に戸惑い、ウェブデザイン歴 14 年のマエダも機能ひとつひとつ理解するのに苦しめられました。</p>
<p>Web サービスの管理ツールの利用に慣れていると、操作順序だったりボタンをクリックした後の挙動についてある程度予測できるのですが、ORCA はぜんぜん予測できない…。</p>
<p>そんなときに大活躍したのが、超大作 1400 ページ程もある ORCA の操作マニュアル。人生でこんなに読み込んだマニュアルはないと断言できるほど、カルテをデザインした際に大変重宝しました。</p>
<p>最初は操作に非常に大変でしたが、業務フローを理解したうえで、操作に慣れてくると ORCA の UI が請求業務をする上で理にかなっていることも理解できて、UI の奥深さにあらためて気付かされました。</p>
<p>ORCA は実際にダウンロードして試してみることができるので、興味のある方はぜひ触ってみてはいかがでしょうか。</p>
<ul>
<li>ORCA 日レセクライアント:<a href="https://www.orca.med.or.jp/receipt/download/java-client/">https://www.orca.med.or.jp/receip</a><a href="https://www.orca.med.or.jp/receipt/download/java-client/">t/download/java-client/</a></li>
<li>ORCA 操作マニュアル:<a href="https://manual.orca.med.or.jp/5.0/html/">https://manual.orca.med.or.jp/5.0/html/</a></li>
</ul>
<p>また、医療機関にカルテ入力〜会計処理までのフローのヒアリングなども行い、カルテのデザインをする上での前提知識を蓄えたり、既存カルテの課題なども整理することで、CLINICS カルテをどのようなデザインにしていくか、徐々に UI 方針を固めていきました。</p>
<h1 id="ようやくデザインへのアプローチのお話">ようやくデザインへのアプローチのお話</h1>
<p>さて、ここまできてやっとデザインについての話です。</p>
<p>昨今の Web デザインはワイヤーを引いて、モックを Sketch 等のデザインツールで制作し、動作検証するためにプロトタイピングツールで触れるようにした後、システム実装に取り掛かるというのが一般的だと思います。しかし、カルテは画面ごとに細かい機能がたくさんあり、それぞれの機能についてデザインして、プロトタイピングして…という一連の作業を行うとデザイン業務だけで恐ろしいほどの工数がかかるのが目にみえてました。</p>
<p>そこで私は、デザインに取り掛かるアプローチを普段と変えてみることにしました。</p>
<h1 id="デザインするけどデザインツールは使わない">デザインするけどデザインツールは使わない</h1>
<p>カルテはほぼコーディングだけでデザインしました。俗にいうインブラウザコーディングというやつでしょうか。</p>
<p>これによりワイヤー → デザインツール → プロトタイピングという、一連の工程を大幅に短縮して開発することができました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180516/20180516184701.png" alt="f:id:medley_inc:20180516184701p:plain" title="f:id:medley_inc:20180516184701p:plain"></p>
<p>カルテが動作する開発環境のほかに UI デザイン用の環境があります。UI デザインの環境でコーディングした HTML/CSS ファイルを開発環境に移植することができるため、エンジニアは UI 実装する手間も大幅に削減でき、システム開発に専念することができたのもよかった点です。</p>
<p>またインタラクションも CSS で制御するようにしたため、動作の部分もデザイナー自身の思い描いたイメージに合うように調整できたので、制限もあるプロトタイピングツールを使うよりも実際の挙動に近いかたちで検証できたこともメリットでした。</p>
<p>ちなみに<a href="https://developer.medley.jp/entry/2017/08/03/160000">以前にマエダが書いた記事</a>として CLINICS アプリでデザイン言語システムを導入した事をお伝えしましたが、CLINICS カルテの UI は絶賛改善中のため、まだ導入はしていません。ある程度 UI 設計の軸が固まった段階で改めて整理したいとおもっています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180516/20180516184720.jpg" alt="f:id:medley_inc:20180516184720j:plain" title="f:id:medley_inc:20180516184720j:plain"></p>
<h1 id="ui-の方針を定義することの重要性">UI の方針を定義することの重要性</h1>
<p>私自身これまで Web サービスをいくつも手がけてきましたが、カルテのデザインほど UI に悩まされた経験はありませんでした。</p>
<p>これまで見てきた電子カルテは機能毎にいくつものポップアップが表示されたり、深い階層を辿ったりするため、かなり独自の UI になっているように思えました。さらに、医療機関によって個別にカスタマイズされていることが多く、裏をかえせば UI のあるべき姿の方針が開発側ではなく、ユーザである医療機関側に委ねられているように思えました。実際に医療機関にヒアリングをしても、UI へのこだわりを持っている方が多い印象を受けました。</p>
<p>こうなると、電子カルテを使っている医師は職場を変わる度に新しい電子カルテの使い方を学びなおすということになります。また、医療機関間で情報連携しようと思った時も、データの保管形式などにバラツキが出てしまいます。</p>
<p>そこで CLINICS カルテでは、開発者側が UI 方針をしっかり定義し一貫性をもった操作性を提供することが診療効率の改善にもつながると考え、UI 開発を重要視しています。
カスタマイズに慣れている医療機関からすると、こうしてプロダクト側から UI を提示されることは珍しいことだと思います。しかしカスタマイズをしない分、UI の良し悪しが導入の判断を左右するという自負を持ちながら、どのような UI が最適なのかを日々模索し続けています。</p>
<p>見た目的には問題なくても、触ってみるとクリック数が多くなってしまったり、適切な導線設計をしたつもりが逆にユーザーを迷わせる導線になってしまったりと、機能ひとつとっても「作る → 検証 → 改善」の工程を幾度となく繰り返しながら、使い勝手向上と機能の両立を目指しています。右脳と左脳が行き来するため脳内キャッチボールが発生し、疲弊することもしばしばありましたが、その分想いの強いプロダクトとしてリリースできたことはデザイナーをしていてもなかなか巡り合うことのできない貴重な経験を得られたと思っています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180516/20180516184733.jpg" alt="f:id:medley_inc:20180516184733j:plain" title="f:id:medley_inc:20180516184733j:plain"></p>
<h1 id="上流工程としてだけでなく運用フェーズのデザインの重要性">上流工程としてだけでなく運用フェーズのデザインの重要性</h1>
<p>プロダクトに携わるデザイナーの役割がどんどん幅広くなってきている昨今、広範囲でプロダクトに携わらないと、本質を捉えたデザインができず、プロダクトの方向に迷いが生じてしまうことになるので、全体像を理解したうえでデザインにどう落とし込んで行くかが重要になってきます。</p>
<p>デザイナーは上流工程だけでなく、システムがどういう設計で成り立っているのか踏まえ、実運用も理解することで、今後の改善やアイデアに活かせると思います。</p>
<p>どこが課題でどれを優先して開発するかなどもデザインする上で重要で、それも踏まえて企画検討することがプロダクトとして良いものにできるかどうかに影響します。</p>
<p>上流だけでは結局のところ表面的な部分だけの品質を重視してしまいがちですが、全体像を捉えることがプロダクト品質を底上げするようにしていくことの大切さを、このプロダクト開発に携わることで学ばせてもらっています。</p>
<h1 id="まとめ">まとめ</h1>
<p>すでに実際にご利用いただいてる医療機関もあり、実運用からのフィードバックを経て日々進化している CLINICS カルテ。まだまだプロダクトとして未成熟な段階ではありますが、「医療の課題を解決する」ため、CLINICS カルテでは医療業務の課題解決をしていきます。</p>
<p>実際に CLINICS カルテを利用する方は医療機関の方に限られてしまうのですが、興味のあるデザイナー・エンジニア・ディレクターの方はぜひこちらまでご応募おまちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>
<p>次回は CLINICS カルテのエンジニア兼飲み友の苦悩と葛藤について発表いたしますので、ご期待ください!</p>medley
- 医療 IT の未来に向けて取り組むことhttps://developer.medley.jp/entry/2018/05/01/01https://developer.medley.jp/entry/2018/05/01/01こんにちは、平山です。メドレーのプロダクト開発全般を管掌しています。先日 4/29 (日)に虎ノ門ヒルズフォーラムで開催されたCLINICS SUMMIT 2018と合わせて、3 本のニュースリリースをだしました。
これらのニュースリリース...Tue, 01 May 2018 03:54:26 GMT<p>こんにちは、平山です。メドレーのプロダクト開発全般を管掌しています。先日 4/29 (日)に虎ノ門ヒルズフォーラムで開催された<a href="https://clinics-info.medley.life/summit2018">CLINICS SUMMIT 2018</a>と合わせて、3 本のニュースリリースをだしました。</p>
<p>これらのニュースリリースはひとつのストーリーにもとづいているのですが、それぞれを読んだだけではメッセージが伝わりづらいと思いますので、このブログで補足させて頂きます。</p>
<h1 id="ニュースリリース">ニュースリリース</h1>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="クラウド型電子カルテ「CLINICS カルテ」の提供を開始 〜日医標準レセプトソフト(ORCA)を内包、オンライン診療も実施できる次世代の電子カルテ〜 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frelease%2F20180429-01.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/release/20180429-01.html">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="日医標準レセプトソフト(ORCA)と医療情報システムを容易に連携する API クライアントライブラリ「ORCA API」を公開し、オープンソース化 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frelease%2F20180429-02.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/release/20180429-02.html">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="ブロックチェーンを活用した電子処方せんの管理方式に関する特許を出願 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frelease%2F20180429-03.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/release/20180429-03.html">www.medley.jp</a></cite>
<cite class="hatena-citation"></cite>
<h1 id="オンライン診療システムのいま">オンライン診療システムのいま</h1>
<p>まず背景として我々が提供しているプロダクト「CLINICS」について振り返るところから話を進めます。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180501/20180501111913.png" alt="f:id:medley_inc:20180501111913p:plain" title="f:id:medley_inc:20180501111913p:plain"></p>
<p>オンライン診療システム CLINICS は、2015 年 8 月に厚生労働省から示された遠隔診療に関する通知をうけて、2016 年 2 月にプロダクトをローンチしたところからはじまりました。</p>
<p>ローンチ当初はネイティブアプリもなくウェブアプリケーションのみで、オンライン診療の肝となるビデオチャット機能も外部サービスで代替するなど、わかりやすい MVP (Minimum Viable Product) からスタートしました。その後、様々な医療機関で利用頂き、多くの医療機関スタッフの皆様からの叱咤激励をうけながら、プロダクトやオペレーションを磨き進化を続けて参りました。</p>
<p>その結果として、現在では契約医療機関は 800 を超え、1 ヶ月に 100 回以上ものオンライン診療を実施するような医療機関もでてきました。また<a href="https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/0000188411.html">平成 30 年度の診療報酬改定</a>では<strong>オンライン診療料が新設</strong>され、オンライン診療というものが正式に医療の現場で認められるようになってきています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20180427/20180427172532.png" alt="f:id:dev-medley:20180427172532p:plain" title="f:id:dev-medley:20180427172532p:plain"></p>
<p>しかし、オンライン診療システムの進化の中で常につきまとう課題がありました。それは<strong>電子カルテや医事会計システムなど医療機関の中心で使われるシステムとの連携</strong>です。 </p>
<h1 id="オンライン診療から電子カルテへ">オンライン診療から電子カルテへ</h1>
<p>医療現場の業務において中心で使われるシステムはオンライン診療システムではなく、一般的には電子カルテや医事会計システムとなります。</p>
<p>オンライン診療システムは、予約、問診、診察、会計、薬または処方せんの配送、これら一連の業務を対面・オンラインに限らずにワンストップで処理できるという思想で開発を進めてきました。しかし、実際の現場においては、オンライン診療システムでの業務と電子カルテシステムでの業務を連携させるために人手を介在させる必要があり、オンライン診療システムをいれると現場オペレーションが増えてしまうといった課題がありました。</p>
<p>このオンライン診療システムにつきまとう課題を解決するため、昨年より電子カルテに関する調査を進めてきました。他社電子カルテシステムとの連携など様々な選択肢があったなか、この調査を経て<strong>現状の電子カルテをとりまく課題と将来の可能性</strong>を感じ、電子カルテを内製で開発しオンライン診療システムを電子カルテシステムに進化させるという結論に至りました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20180501/20180501101005.png" alt="f:id:dev-medley:20180501101005p:plain" title="f:id:dev-medley:20180501101005p:plain"></p>
<p>おりしも 2017 年は 3 省 4 ガイドラインの改定 (医療情報システムの安全管理に関するガイドライン第 5 版) や、日本医師会 ORCA 管理機構による<a href="https://www.orcamo.co.jp/products/orca/cloud.html">日レセクラウド</a>や<a href="https://www.orcamo.co.jp/api-council/">API 拡張 (HAORI)</a>の提供開始などの動きがあり、医療業界においてもクラウド化の波が確実にきていることを感じました。これが電子カルテ開発の背景です。 </p>
<h1 id="患者とつながる電子カルテclinics-カルテ">患者とつながる電子カルテ「CLINICS カルテ」</h1>
<p>開発した電子カルテシステム「CLINICS カルテ」は、医事会計ソフトである ORCA を会計エンジンとして組み込んだ<strong>ORCA 内包型のクラウド電子カルテ</strong>となります。</p>
<p>医療機関のスタッフは予約から受付、患者管理、診察、オーダリング、会計、請求といった、ひととおりの業務をウェブブラウザだけあれば行うことができます。またオンライン診療の機能が搭載されているため、患者がアプリから予約しチェックインした情報をもとに、医療機関スタッフが患者情報を登録し、そのままオンライン診療を実施し、会計まで進めるということも可能となります(もちろんオンライン診療を実施するための基準を満たしていることが前提です)。</p>
<p>まさに<strong>患者とつながる電子カルテ</strong>ということを実現したプロダクトとなります。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20180430/20180430230423.png" alt="f:id:dev-medley:20180430230423p:plain" title="f:id:dev-medley:20180430230423p:plain"></p>
<p>もちろんセキュリティ統制の強化についても昨年から本格的に取り組んできておりまして、3 省 4 ガイドラインへの準拠は当然のことながら、第 3 者認証機関による認証取得に向けても動いています (<a href="https://www.medley.jp/notice/20180314.html">TRUSTe 取得完了</a>、ISMS 取得作業中)。 </p>
<h1 id="医療-it-の課題とアプローチ">医療 IT の課題とアプローチ</h1>
<p>しかし、CLINICS カルテをはじめとしたクラウド電子カルテが普及するには時間がかかると考えています。</p>
<p>クラウドサービスに慣れた若い世代が開業し、システム導入の意思決定をするための世代交代に一定以上の時間が必要であるというのはもちろんですが、それ以上に<strong>医療情報システム関連技術の標準化が遅れている</strong>ことや、ローカルネットワークを前提とする<strong>院内システムのエコシステムができあがっている</strong>ためです。これにより、ウェブ系の新興プレイヤーが参入することが難しくなり、結果として医療業界全体がテクノロジーの進化の恩恵をうけづらくなっているように感じます。</p>
<p>この現状をふまえ、我々は CLINICS カルテの公開とあわせて、医療 IT の世界をオープンにし、様々な新興プレイヤーが参入しやすい土壌をつくることについてもコミットしていきたいと考えています。その思いが**「ORCA API のオープンソース公開」<strong>と</strong>「ブロックチェーンを活用した電子処方せん管理方式に関する特許出願」**という 2 つのニュースリリースにあらわれています。 </p>
<h2 id="orca-api-のオープンソース公開">ORCA API のオープンソース公開</h2>
<p>ORCA API は ORCA((正確には ORCA プロジェクトが提供している日医標準レセプトソフト))が提供する API を Ruby から利用するためのライブラリです。ORCA API をオープンソースとして公開することで、ORCA と接続するウェブアプリケーションの開発が促進されることを期待するものです。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="orca-api/orca-api" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Forca-api%2Forca-api" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://github.com/orca-api/orca-api">github.com</a></cite>
<h2 id="ブロックチェーンを活用した電子処方せん管理方式に関する特許出願">ブロックチェーンを活用した電子処方せん管理方式に関する特許出願</h2>
<p>電子処方せんに関してはすでに<a href="https://www.jahis.jp/files/user/04_JAHIS%20standard/17-104_JAHIS%E9%9B%BB%E5%AD%90%E5%87%A6%E6%96%B9%E3%81%9B%E3%82%93%E5%AE%9F%E8%A3%85%E3%82%AC%E3%82%A4%E3%83%89Ver.1.0.r2.pdf">実装ガイド</a>が作成され指針が示されていますが、この中で認められているように、実装ガイドに基づいて電子処方せんシステムを実装しても実運用で使うにはいくつかの課題が残ります。また、それらの課題を解決するために<a href="https://www8.cao.go.jp/kisei-kaikaku/suishin/meeting/wg/iryou/20171219/171219iryou05.pdf">今後の方向性についての議論</a>も行われているようですが、なかなか前に進む気配が見られません。</p>
<p>それに対する我々からのひとつの考えを示してみたのが今回の特許出願となります (我々が独占的に利用するといった意図の特許出願ではありません)。</p>
<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td>名称</td><td>電子処方せん管理方法、電子処方せん管理システム、及びプログラム</td></tr><tr><td>内容</td><td>処方せんの電磁的記録による作成、交付及び保存を実施するための電子処方せん管理方法、電子処方せん管理システム、及びプログラム</td></tr><tr><td>概要</td><td>ASPサーバを用いずとも、実運用が可能な電子処方せんを実現するもの</td></tr></tbody></table>
<p>この 2 つは現状の医療 IT に存在している課題に対してアプローチしたものになりますが、これ以外にも検査結果データの標準化や HPKI のオープン化と促進 (特定プラットフォーム依存性の排除)、SS-MIX の促進など、標準化という観点で医療 IT の世界には取り組むべき課題が多くあります。</p>
<p>標準化においてはトップダウンによるアプローチが理想だと思いますが、トップダウンでの標準化には時間がかかり、<strong>テクノロジーの進化の時間軸との間にギャップ</strong>が発生しがちです。我々は今回の ORCA API や電子処方せんブロックチェーンでのアプローチのように、トップダウンでの標準化の動きを待つだけでなく、テクノロジーを活用した<strong>ボトムアップの技術提案</strong>も積極的に行っていきたいと考えています。</p>
<p>その結果として、技術の標準化が進み、新興プレイヤーが増え、医療機関間の連携も円滑になり、地域医療構想のような動きも加速され、医療現場の IT 化が進み、医療従事者が診療により専念できる環境がつくられる。</p>
<p>そのような世界の実現にむけて我々は取り組んでいくという意思を明確にするために、今回のニュースリリースを公開させて頂きました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20180501/20180501101035.png" alt="f:id:dev-medley:20180501101035p:plain" title="f:id:dev-medley:20180501101035p:plain"></p>
<h1 id="医療-it-の未来に向けて">医療 IT の未来に向けて</h1>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="医療×インターネットの未来 | メドレー平山の中央突破" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Ftoppa.medley.jp%2F01.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://toppa.medley.jp/01.html">toppa.medley.jp</a></cite>
<p>以前このブログでも書いたとおり、インターネット業界で活躍してきたような、高い能力をもちプロダクトにこだわりをもって開発をしてきたような人が圧倒的に少ないことが、医療 IT における課題であると私は思っています。</p>
<p>若く優秀なクリエイターたちが医療 IT の世界に参加し、様々な取り組みをすることで、業界内の循環が進み、結果として業界の進化にもつながるものと考えています。まずは我々が積極的に技術をオープンにしていくことで、その流れの起点をつくっていきたいと思います。 </p>
<p>メドレーは「医療ヘルスケア分野の課題を解決する」というミッションのもと、医療 IT の世界における標準化の課題に対しても積極的にアプローチしていきます。</p>
<h1 id="さいごに">さいごに</h1>
<p>メドレーではこのような医療業界に存在する課題に取り組んでいきたいメンバーをデザイナー・エンジニアを中心に全職種絶賛募集中です。皆さまからのご応募お待ちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- 小さく始める Design System ~メドレー TechLunch~https://developer.medley.jp/entry/2018/04/26/174923https://developer.medley.jp/entry/2018/04/26/174923こんにちは、開発本部の舘野です。
先日、メドレーで定期開催している社内勉強会「TechLunch」にて、Design System について発表しました。医療介護の求人サイト「ジョブメドレー」において、Design System を「小さく...Thu, 26 Apr 2018 08:49:23 GMT<p>こんにちは、開発本部の舘野です。</p>
<p>先日、メドレーで定期開催している社内勉強会「TechLunch」にて、Design System について発表しました。医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」において、Design System を「小さく始める」手法で導入を進めているので、そのプロセスについて紹介させていただこうと思います。</p>
<h1 id="design-system-とは何か">Design System とは何か</h1>
<p>Design System とは、Salesforce の<a href="https://lightningdesignsystem.com">Lightning Design System</a>や IBM の<a href="https://carbondesignsystem.com/">Carbon Design System</a>などが代表的な例として挙げられると思いますが、平たく言ってしまうとプロダクト独自の<a href="https://getbootstrap.com/">Bootstrap</a>となるものです。</p>
<p>UI 開発の領域では、これまでスタイルガイドを作ることでデザイナーとエンジニア間の共通言語とし、プロダクトの UI の一貫性を保つように努めることが一般的かと思いますが、Design System ではスタイルガイドだけでなくデザインの原則や UI コンポーネントの CSS や JS なども含めてプロダクトのインターフェースに関わる全てを、1 つのプロダクトとする考え方です。</p>
<p>Design System はスタイルガイドと明確にどこが違うのかについて言及しているウェブ上の記事の多くは、Nathan Curtis 氏の<a href="https://medium.com/eightshapes-llc/a-design-system-isn-t-a-project-it-s-a-product-serving-products-74dcfffef935">「A Design System isn’t a Project. It’s a Product, Serving Products.」</a>という言葉を引用して、その差異を示しています。</p>
<p>スタイルガイドがプロダクトにおけるプロジェクト以上の存在ではないのに対して、Design System はプロダクトに対して UI のエコシステムを提供するプロダクトである、ということが Design System の基幹となる考え方だと思います。</p>
<h1 id="ジョブメドレーにおける-design-system">ジョブメドレーにおける Design System</h1>
<p>TechLunch で発表したスライドはこちら。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/4716e3f00a9e441da1ec4fdce5d1ff4a" title="小さく始める Design System /Design System" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 314px;" data-ratio="1.78343949044586"></iframe>
<p>今回 Design System を段階的に導入しているのは、医療介護の求人サイト「ジョブメドレー」です。</p>
<p>ジョブメドレーのインターフェースが今後より一層色々な形でユーザに使われる場面が増えていくことが想定される中で、プロダクトの UI の一貫性や生産性を担保し続けていくためには、スタイルガイドを作るだけでなく Design System によってより包括的にプロダクトの UI 開発に対する取り組み方を変える必要があるのではと考えていました。</p>
<p>とはいえ最初からプロダクトの UI 全てを Design System に置き換えるというのは変化が大き過ぎるし、Design System がうまく機能せずに失敗する場合も考慮しておく必要があったので、導入がうまくいかないければすぐにやめられるように以下の点を導入前に決めていました。</p>
<ul>
<li>最初から一気に色々やらない</li>
<li>まずは一部だけ導入してみる</li>
<li>上手くいく部分といかない部分を検証する</li>
<li>Design System の改善と段階導入を繰り返す</li>
</ul>
<p>Design System はプロダクトに UI のエコシステムを提供するプロダクトなので、一定期間で作って終わりではなく継続して改善を繰り返していくという点では、このような進め方が適切と考えました。</p>
<p>実際のところ、現在のジョブメドレーでは一部分だけジョブメドレーの Design System として npm 化したモジュールから提供するようにしています。</p>
<p>npm 化して Design System から提供するようにしたのは、以下の 3 つのみです。</p>
<ul>
<li>
<p><a href="https://www.npmjs.com/package/jmds-tools">jmds-tools</a></p>
<ul>
<li>sass mixin や function を提供するユーティリティモジュール</li>
</ul>
</li>
<li>
<p><a href="https://www.npmjs.com/package/jmds-tokens">jmds-tokens</a></p>
<ul>
<li>design tokens(デザイン上の値を信頼できる唯一の情報源として 1 つの場所で定義されるもの)</li>
</ul>
</li>
<li>
<p><a href="https://www.npmjs.com/package/jmds-flex">jmds-flex</a></p>
<ul>
<li>flexbox ユーティリティ</li>
</ul>
</li>
</ul>
<p>プロダクトの UI として見ると、flexbox のユーティリティクラスを Design system から提供するようになっただけです。</p>
<p>ただ、今は全体のごく一部が Design System から提供されていますが、Design System に移行できる UI コンポーネントを選定して段階的に npm 化をしていくことで、プロダクトの UI コンポーネントの多くが Design System から提供されている状態にすることが可能だと思います。</p>
<p>単純に npm 化することが目的ではなく、npm 化した Design System をプロダクトチームでメンテナンスしていくことで、より一貫性のある UI をジョブメドレーのプロダクトに提供していくことが目的です。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回は、TechLunch で発表したジョブメドレーにおける Design System の取り組みについて紹介させていただきました。</p>
<p>今回紹介した Design System が今後デザイナー、エンジニア、プロダクトマネージャーと協力しながら、ジョブメドレーのプロダクトの UI を支える強固な土台へと成長させられるように取り組んでいこうと思います。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、医療介護の求人サイト「ジョブメドレー」の他にも、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」、口コミで探せる介護施設の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」、オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<p>ちょっと興味があるという方も、ぜひお気軽にご連絡ください!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- メドレーが協賛させていただいた「MANABIYA」に行って来ました!https://developer.medley.jp/entry/2018/04/11/155000https://developer.medley.jp/entry/2018/04/11/155000皆さんこんにちは。開発本部の日下です。普段はオンライン診療アプリ「CLINICS」およびオンライン医療事典「MEDLEY」の開発を担当しています。(昨年、新卒で昨年メドレーに入ったのでエンジニア歴は 1 年弱。ベテランが多いメドレーのエンジ...Wed, 11 Apr 2018 06:50:00 GMT<p>皆さんこんにちは。開発本部の日下です。普段は<a href="https://clinics.medley.life/">オンライン診療アプリ「CLINICS」</a>および<a href="https://medley.life/">オンライン医療事典「MEDLEY」</a>の開発を担当しています。(昨年、新卒で昨年メドレーに入ったのでエンジニア歴は 1 年弱。ベテランが多いメドレーのエンジニアチームで奮闘しています)</p>
<p>先日開催された<a href="https://teratail.com/">エンジニア特化型 Q&A サイト「</a><a href="https://teratail.com/">teratail</a><a href="https://teratail.com/">」</a>さんのイベント、<a href="https://manabiya.tech/">MANABIYA</a>にメドレーがスポンサーとして協力させていただきました。
エンジニアの平木と参加しましたので、イベント当日の様子をレポートします。</p>
<h1 id="manabiya-とは">MANABIYA とは</h1>
<p>teratail はご存知の方も多いかもしれませんが、MANABIYA というイベントをご存じない方もいるかもしれません。
MANABIYA は今回が初開催のイベントで、”疑問” を日本中から集め、カンファレンスを通じて解決策を見出し、”知恵” を生み出すことを目的とし、秋葉原から近い<a href="https://www.3331.jp/">3331 Arts Chiyoda</a>にて 2 日間に渡って開催されました。</p>
<h1 id="全体の雰囲気">全体の雰囲気</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180411/20180411115536.png" alt="20180411115536.png">
全体的に初開催とは思えないほど人が多く、一部のセッションでは教室が満杯になり、入場制限がかかるほどでした。また、speaker として豪華なメンバー面々が数多く集まっており、最初から最後まで、参加したすべてのセッションで立ち見が出てるほど盛況だったのがとても印象的でした。
<p>また、イベントの名前にもなっているように、学び舎というイメージがぴったりのコンセプトで統一されているのも特徴的でした。
会場自体も元々中学校だったものを利用したアートギャラリーを使用しており、教室や階段など、元を活かした作りになっていたのでイベントの雰囲気ととてもマッチしていました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180411/20180411160342.png" alt="20180411160342.png">
<figure class="figure-image figure-image-fotolife" title="元々教室だったところでセッションが行われました"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180411/20180411115957.jpg" alt="20180411115957.jpg"><figcaption>元々教室だったところでセッションが行われました</figcaption></figure>
<h1 id="セッション">セッション</h1>
<p>セッションは Web、インフラ、DB、プログラミングから、AI、IoT 等の最近話題になっている分野まで幅広く開かれていました。</p>
<p>我々が参加した 2 日目はプログラミング、Web、IoT のセッションが主に開催されていました。</p>
<p>メドレーのプロダクトはすべて Web サービスとして提供されていることもあり、私は、Web 関連のセッションを中心に、時間が空いたら他の分野のセッションもふらっと覗き見るような形で参加しました。</p>
<p>エンジニアとして成長する上で必要な戦略の話や、Service Worker や Web Payments など最近環境が整ってきた技術をプロダクトに組み込んでみた話など、幅広く見ることができました。</p>
<figure class="figure-image figure-image-fotolife" title="入場制限状態でやっと入れたものの立ち見の方の後ろから見ることに"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180411/20180411120051.jpg" alt="20180411120051.jpg"><figcaption>入場制限状態でやっと入れたものの立ち見の方の後ろから見ることに</figcaption></figure>
<figure class="figure-image figure-image-fotolife" title="運良く前の方の席が取れましたが、こちらも立ち見の方がたくさんいらっしゃいました"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180411/20180411120136.jpg" alt="20180411120136.jpg"><figcaption>運良く前の方の席が取れましたが、こちらも立ち見の方がたくさんいらっしゃいました</figcaption></figure>
<p>個人的に古川氏による「<strong>Web Application 2018 From Performance Perspective</strong>」は特に勉強になったセッションでした。
Web サイトのパフォーマンスに関してのエッセンスを詰めたようなセッションで、その歴史と共に、その時々のベストプラクティス、そしてその時代のバイブルをまとめ、今どういった背景でどういった対策をするべきか、その指針となるような話聞くことができました。</p>
<figure class="figure-image figure-image-fotolife" title="サイトパフォーマンスの歴史の概要を説明していただきました"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180411/20180411120434.png" alt="20180411120434.png"><figcaption>サイトパフォーマンスの歴史の概要を説明していただきました</figcaption></figure>
<p>特に、Web 側は SPA として動いている CLINICS を開発しているため、 <code>Server rendered pages are not optional</code> という言葉の意味が衝撃的で、帰る途中からずっとそのあたりのことを考えてしまうほどでした。
Speaker Deck にスライドが上がっていますので、気になる方はそちらをご参照ください。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/4336cbe6b51b4fa4af2dbe3dabd244f9" title="Web Application 2018 From Performance Perspective" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 420px;" data-ratio="1.3333333333333333"></iframe>
<p>Web アプリケーションのパフォーマンスに関することは個人的に気になっていたものの、どこから手を付けてよいのかわからず理解があまり進んでない分野だったため、必要な知識を 40 分という短い時間で把握することができたのは非常にありがたかったです。</p>
<p>まさにイベントの趣旨である”気になっていること”、「回答」を知りたいがなかなか ”知る機会がなかったこと” という ”疑問” を、カンファレンスを通じて解決策を見出し、”知恵” を生み出す体験ができたのではないかと思っています。</p>
<figure class="figure-image figure-image-fotolife" title="リアル版 teratail も登場"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180411/20180411120233.jpg" alt="20180411120233.jpg"><figcaption>リアル版 teratail も登場</figcaption></figure>
<h1 id="まとめ">まとめ</h1>
<p>大規模かつ幅広い分野を取り扱っており、自分の専門や興味のある分野以外も同時に開催されたため、気軽に参加することができるイベントでした。
初めてエンジニアリングに触れて 1 年弱が経ち、その間に業務などを通じて学んできた過程で言語化できていなかったことが思っていた以上にあったことを、このイベントを通じて改めて知ることができました。</p>
<p>こういった大規模なイベントは初参加だったのですが、多くのことが知れたのでとても良かったです。
次回開催される際は、また行ってみようと思っています。</p>
<p>イベントでメドレーのことを知りご興味持っていただいた方は、お気軽にご連絡ください!
どんなエンジニア・デザイナーが働いているか、どんなことをやっているか等、ぜひ HP をご覧ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://www.wantedly.com/feed/s/medley-int" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title"> メドレー平木の「気になるあの人に聞いてみた」</div>
<div class="remark-link-card-plus__description">メドレーのエンジニア平木が、社内の気になる人に仕事内容やこれまで等をゆるりとインタビューするコーナーです。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.wantedly.com/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.wantedly.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://d2v9k5u4v94ulw.cloudfront.net/small_light(dw=1200,da=l,ds=s,cc=FFFFFF)/assets/images/1017926/original/e105bfe8-f026-40ff-998f-c48814ecad82.jpeg?1483003119" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 「サイトの会員登録増加」に効果が出た施策の話https://developer.medley.jp/entry/2018/04/06/164439https://developer.medley.jp/entry/2018/04/06/164439こんにちは。開発本部で医療介護の求人サイト「ジョブメドレー」の開発を担当している田村です。
メドレー開発本部で行われている勉強会「TechLunch」で、ジョブメドレーについて「求人サイトでやって良かった会員登録施策」というタイトルでお話さ...Fri, 06 Apr 2018 07:44:39 GMT<p>こんにちは。開発本部で医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」の開発を担当している田村です。
メドレー開発本部で行われている勉強会「TechLunch」で、ジョブメドレーについて「求人サイトでやって良かった会員登録施策」というタイトルでお話させていただきました。インターネットで検索するとこういう内容はたくさん出てきますが、そのうちの一つとして、参考にしていただければ幸いです。</p>
<h1 id="背景">背景</h1>
<p>医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」は、創業当初(2009 年)にリリースし会社と共に進化し続けてきたメドレーで最も歴史のあるサービスです。リリース開始から 9 年ほど経ちますが、個人・事業所にとって使いやすく愛されるサービスとなるよう、日々改善を続けています。その中でも、サービスの成長に重要な要素の 1 つである「ユーザの獲得」に向けた施策は、とても大切にしています。昨年に実施した中で「サイトの会員登録増加に効果が出た施策」について、社内の他サービスにとってもノウハウとなればと、少しだけ紹介させていただきしました。</p>
<h1 id="ab-テストによる改善">AB テストによる改善</h1>
<p>まずはじめに、既存のサイト内で改善できる点を洗い出して優先順位を付けて費用対効果が高そうなものから着手しました。実際に以下の 2 つを AB テストを行い改善しました。</p>
<ul>
<li>LP の改善
<ul>
<li>デザイン変更</li>
<li>入力フォームの変更</li>
</ul>
</li>
<li>会員登録バナーの変更</li>
</ul>
<h2 id="lp-の改善">LP の改善</h2>
<p><img src="https://d2mxuefqeaa7sj.cloudfront.net/s_2F2F8059CA09971E07EAC1AC1B6FD38AA8BB29DC86D47D526F5E89985961A37B_1521801430519_001.png" alt=""></p>
<p><strong>BEFORE / AFTER の変更点</strong></p>
<ul>
<li>ユーザの満足度や求人の特徴を強調</li>
<li>メリハリを付け、より直感的にわかりやすく</li>
<li>職種をイメージしやすいビジュアルに変更</li>
</ul>
<p><img src="https://d2mxuefqeaa7sj.cloudfront.net/s_2F2F8059CA09971E07EAC1AC1B6FD38AA8BB29DC86D47D526F5E89985961A37B_1521801447861_002.png" alt=""></p>
<p><strong>BEFORE / AFTER の変更点</strong></p>
<ul>
<li>全ての入力項目のある長い 1 ステップ形式から入力項目を短く分割して 5 ステップ形式に変更</li>
<li>ユーザがゴールイメージができるように現在のステップを表示</li>
<li>入力しやすいように各項目を大きく表示</li>
<li>各ステップへの移動がしやすいようにスクロールしなくても「つぎへ」「もどる」ボタンが押せるようなボタン配置</li>
<li>サクサク入力できるように各ステップへの移動でページ遷移をさせない</li>
</ul>
<p><strong>どのくらい効果があったのか</strong></p>
<p>ビジュアルや訴求内容のブラッシュアップ、そしてユーザビリティを改善したことで会員登録数が約 1.2 倍という結果になりました。1.2 倍で少ないと思うかもしれませんが、サイトの規模が大きくなるほど、<strong>ちょっとした改善を積み上げていくことが重要</strong>だと考えています。</p>
<h2 id="会員登録バナーの変更">会員登録バナーの変更</h2>
<p><img src="https://d2mxuefqeaa7sj.cloudfront.net/s_2F2F8059CA09971E07EAC1AC1B6FD38AA8BB29DC86D47D526F5E89985961A37B_1521801467092_003.png" alt=""></p>
<p><strong>BEFORE / AFTER の変更点</strong></p>
<ul>
<li>ユーザがイメージしやすい訴求内容に</li>
<li>訴求内容を絞り、ユーザが内容をすぐ把握できるようにビジュアルを追加</li>
<li>サイトの配色に合ったバナーデザイン</li>
</ul>
<p><strong>どのくらい効果があったのか</strong></p>
<p><img src="https://d2mxuefqeaa7sj.cloudfront.net/s_2F2F8059CA09971E07EAC1AC1B6FD38AA8BB29DC86D47D526F5E89985961A37B_1521802733296_006.png" alt=""></p>
<p>バナーの訴求内容とデザインを変更したことで、CVR が約 2 倍という結果になりました。訴求内容を多く見せることも大事ですが、<strong>ユーザがバナーを見て内容をすぐに理解できることを重視</strong>しています。</p>
<h1 id="ユーザの行動を分析して施策を立てる">ユーザの行動を分析して施策を立てる</h1>
<p>ジョブメドレーには、会員登録をしていないユーザが気になった求人を保存しておくことができる「キープ機能」があります。求人の保存期限は 2 週間でこの期間内に会員登録をすると保存期限の上限がなくなり、気になった求人をずっと保存しておくことができます。</p>
<p><img src="https://d2mxuefqeaa7sj.cloudfront.net/s_2F2F8059CA09971E07EAC1AC1B6FD38AA8BB29DC86D47D526F5E89985961A37B_1521801722224_004.png" alt=""></p>
<p>施策のきっかけは、ある調査でユーザが積極的にキープ機能を活用していることがわかったことです。このキープ機能を利用しているユーザに保存期間に関する会員登録のメリットを提示すれば、会員登録してくれるのでは?という仮説を持ち、施策を実施することになりました。</p>
<p><img src="https://d2mxuefqeaa7sj.cloudfront.net/s_2F2F8059CA09971E07EAC1AC1B6FD38AA8BB29DC86D47D526F5E89985961A37B_1521801733472_005.png" alt=""></p>
<p><strong>BEFORE / AFTER の変更点</strong></p>
<ul>
<li>キープボタンをクリックした際、モーダルを表示</li>
<li>保存期間は 2 週間で会員登録すると期間を越えて利用できます!と表記</li>
<li>会員登録ボタンを設置</li>
</ul>
<p><strong>どのくらい効果があったのか</strong></p>
<p>具体的な数値は公表できませんが、リリース当初の会員登録数と比較すると、最近ではおよそ 10 倍程度の結果となりました。ユーザにしてほしい行動・適切な導線改善などから見えてくる施策があるので、積極的に<strong>ユーザの行動を分析し施策を立てて実行することが重要</strong>だと言えます。</p>
<h1 id="まとめ">まとめ</h1>
<p>いかがでしたでしょうか。今回ご紹介した内容の他にも、ジョブメドレーでは、様々な改善を日々続けています。こうした取り組みを通じて、AB テストでちょっとした改善を積み上げていくこと、ユーザの行動を分析し施策を立てて実行すること、そして地道な改善を積み上げていくことがサービスの成長には不可欠だと実感しています。こうした地道な取り組みに励むエンジニアの方は少なくないと思いますが、私たちが実施している施策事例が、皆様の参考となれば幸いです。</p>
<p>今後も「<a href="https://job-medley.com/">ジョブメドレー</a>」を会社と共に成長し続けるサービスにしていきたいと思い、日々奮闘していきたいと思います。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、医療介護の求人サイト「ジョブメドレー」の他にも、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」、口コミで探せる介護施設の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」、オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<p>ちょっと興味があるという方も、ぜひお気軽にご連絡ください!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- プラットフォームをまたぎブレない仕様を実現するための、ネイティブアプリ開発施策https://developer.medley.jp/entry/2018/03/27/180621https://developer.medley.jp/entry/2018/03/27/180621こんにちは、開発本部の高井です。オンライン診療アプリ「CLINICS」のアプリ開発を主に担当しています。
CLINICS では Web に加えて、iOS 版と Android 版の各プラットフォームの仕様変更や機能追加などをほぼ同時に開発し...Tue, 27 Mar 2018 09:06:21 GMT<p>こんにちは、開発本部の高井です。オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」のアプリ開発を主に担当しています。</p>
<p>CLINICS では Web に加えて、iOS 版と Android 版の各プラットフォームの仕様変更や機能追加などをほぼ同時に開発しているのですが、担当する人数が増えたりすることで、仕様に差が出たり、その結果手戻りが起きるということも増え始めていました。</p>
<p>そうした課題を解決するために実践した様々な施策の中から、特に有効だった 3 つの改善策について、今日はご紹介します。</p>
<h1 id="背景">背景</h1>
<p>CLINICS の開発チームでは 5 人ほどのエンジニアがタスク単位で全てのプラットフォームを実装したり、大きいタスクの場合はプラットフォーム毎に別の開発者が担当する形で開発しています。</p>
<p>そのような形で機能追加や不具合対応の開発を進める中で、以下のような課題がありました。</p>
<ul>
<li>プラットフォーム間で仕様やデザインが違う</li>
<li>リリース直前に仕様の違いが見つかり、手戻りが発生する</li>
<li>各プラットフォームに対する習熟度にバラつきがあるため、開発者によって実装方法が違う</li>
</ul>
<p>特にプラットフォーム毎に開発者がほぼ固定されてしまっていた時期には、コードレビューはしていても微妙な違いに気づかなかったり、同じ UI にするのに実装コストが高くてあきらめたり、ということが起こりがちでした。** プラットフォーム間で仕様やデザインが違うとユーザ体験の質がプラットフォームによってバラついてしまいますし、デザインや企画の作業も増えてしまいます** 。これらに加えて、エンジニアの人数が増えたり、デザイナーやカスタマーサポートなどエンジニア以外のメンバーとのコミュニケーションも増えたりしてきたこともあって、開発スピードも段々と遅くなってきていました。</p>
<p>そのような状況を改善するために、チーム内で継続的に実装方法や開発フローを見直し、改善策を実施してきました。</p>
<p>今回は以下の 3 つの改善策をご紹介します。具体的な実装については、主に iOS で使用しているコードを引用してご紹介します。(コードの一部を抜粋しているので、そのままでは使用することはできません。あくまでも参考コードとして読んでください。)</p>
<ul>
<li>DLS(デザイン言語システム)の導入</li>
<li>アプリエラーの共通化</li>
<li>コードレビューの手順改善</li>
</ul>
<h1 id="改善策-1-dlsデザイン言語システムの導入">改善策 1 DLS(デザイン言語システム)の導入</h1>
<p>まずは DLS(デザイン言語システム)の導入についてです。DLS とは以前、本ブログでもデザイナーの前田がご紹介させていただきましたが(<a href="https://developer.medley.jp/entry/2017/08/03/160000">デザイン言語システムを入れたらコミュニケーションコストがぐっと下がった話〜メドレー TechLunch〜</a>)、** UI に一貫性をもたせるため、配色やレイアウト、タイポグラフィやマージンなどのルール **を策定し、チーム全体で継続的に運用していくための仕組みです。策定したルールを組み込んだ各コンポーネントのデザインを元に、Web / iOS / Android の各プラットフォームで UI を実装して開発時に再利用できるようにしています。デザイン自体は下記のような形で Sketch ファイルで管理しています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180327/20180327164233.png" alt="20180327164233.png">
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180327/20180327164243.png" alt="20180327164243.png">
<p>iOS については各コンポーネントをカスタムビュークラスとして実装し、再利用できるようにしました。DLS 導入以前はプラットフォーム毎に違った UI やルールで開発していたので、実装段階で担当する開発者毎の認識によって品質や仕様に差が出ている状態でした。DLS 導入によってそのような差が出にくくなり、一定の品質を保つことができるようになりました。
また、** UI の微調整などが減って、機能ロジックに重点を置いた開発に専念できるようになり、さらにデザイナーとの認識合わせが最小限になったことにより開発効率も上がった **と感じています。UI の基盤をつくったことで新しく画面を開発する場合でもコンポーネントを組み合わせ、エンジニアだけで実装が完了することも多くなり、その分デザイナーは次の施策やプロジェクトに専念できるようになりました。</p>
<p>実装についてですが、各コンポーネント毎に xib ファイルで UI パーツを作成し、それをクラスファイルで読み込んでカスタムビュークラスの見た目として使っています。カスタムビューは再利用しやすく、利用時にバラツキが出にくいように以下の点を満たすように実装しました。</p>
<ul>
<li>Interface Builder/コードのどちらからでも初期化できる</li>
<li>ビルドする前に Storyboard 上で UI パーツのデザインを確認できるように IBDesignable と IBInspectable を指定する</li>
<li>カスタムビューの中で UI 要素のマージンや高さを指定する</li>
</ul>
<p>例えば、セレクトフォームコンポーネントのカスタムビューは以下のような実装になっています。</p>
<ul>
<li>
<p>xib ファイル
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180327/20180327164320.png" alt="20180327164320.png"></p>
</li>
<li>
<p>クラスファイル</p>
</li>
</ul>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="swift"><code><span class="line"><span style="color:#C586C0">import</span><span style="color:#4EC9B0"> UIKit</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">protocol</span><span style="color:#4EC9B0"> ClinicsFormSelectDelegate</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">class</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> didClickFormSelect</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">sender</span><span style="color:#D4D4D4">: ClinicsFormSelect)</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">@IBDesignable</span><span style="color:#569CD6"> class</span><span style="color:#4EC9B0"> ClinicsFormSelect</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">UIView </span><span style="color:#D4D4D4">{</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> @IBOutlet</span><span style="color:#569CD6"> weak</span><span style="color:#569CD6"> var</span><span style="color:#D4D4D4"> selectView: SelectView!</span></span>
<span class="line"><span style="color:#569CD6"> @IBInspectable</span><span style="color:#569CD6"> var</span><span style="color:#D4D4D4"> labelText: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">"Form-parts"</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> didSet</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#D4D4D4"> selectView.</span><span style="color:#9CDCFE">labelText</span><span style="color:#D4D4D4"> = labelText</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> weak</span><span style="color:#569CD6"> var</span><span style="color:#D4D4D4"> delegate: ClinicsFormSelectDelegate?</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // コードから初期化する場合に呼ばれる</span></span>
<span class="line"><span style="color:#569CD6"> override</span><span style="color:#569CD6"> init</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">frame</span><span style="color:#D4D4D4">: CGRect) {</span></span>
<span class="line"><span style="color:#569CD6"> super</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">init</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">frame</span><span style="color:#D4D4D4">: frame)</span></span>
<span class="line"><span style="color:#DCDCAA"> commonInit</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // Interface Builder から初期化する場合に呼ばれる</span></span>
<span class="line"><span style="color:#569CD6"> required</span><span style="color:#569CD6"> init?</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">coder</span><span style="color:#9CDCFE"> aDecoder</span><span style="color:#D4D4D4">: NSCoder) {</span></span>
<span class="line"><span style="color:#569CD6"> super</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">init</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">coder</span><span style="color:#D4D4D4">: aDecoder)</span></span>
<span class="line"><span style="color:#DCDCAA"> commonInit</span><span style="color:#D4D4D4">()</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> private</span><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> commonInit</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#6A9955"> // xib ファイルの読み込み</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> bundle = </span><span style="color:#DCDCAA">Bundle</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">for</span><span style="color:#D4D4D4">: </span><span style="color:#DCDCAA">type</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">of</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">self</span><span style="color:#D4D4D4">))</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> view = </span><span style="color:#DCDCAA">UINib</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">nibName</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"ClinicsFormSelect"</span><span style="color:#D4D4D4">, </span><span style="color:#DCDCAA">bundle</span><span style="color:#D4D4D4">: bundle).</span><span style="color:#DCDCAA">instantiate</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">withOwner</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">self</span><span style="color:#D4D4D4">, </span><span style="color:#DCDCAA">options</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">nil</span><span style="color:#D4D4D4">).</span><span style="color:#9CDCFE">first</span><span style="color:#D4D4D4"> as! UIView</span></span>
<span class="line"><span style="color:#DCDCAA"> addSubview</span><span style="color:#D4D4D4">(view)</span></span>
<span class="line"><span style="color:#D4D4D4"> backgroundColor = .</span><span style="color:#9CDCFE">clear</span></span>
<span class="line"><span style="color:#D4D4D4"> view.</span><span style="color:#9CDCFE">backgroundColor</span><span style="color:#D4D4D4"> = .</span><span style="color:#9CDCFE">clear</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // 読み込んだ View のサイズがカスタムクラス(ClinicsFormSelect)と同じサイズになるように Constraint を設定する</span></span>
<span class="line"><span style="color:#D4D4D4"> view.</span><span style="color:#9CDCFE">translatesAutoresizingMaskIntoConstraints</span><span style="color:#D4D4D4"> = </span><span style="color:#569CD6">false</span></span>
<span class="line"><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> bindings = [</span><span style="color:#CE9178">"view"</span><span style="color:#D4D4D4">: view]</span></span>
<span class="line"><span style="color:#DCDCAA"> addConstraints</span><span style="color:#D4D4D4">(NSLayoutConstraint.</span><span style="color:#DCDCAA">constraints</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">withVisualFormat</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"H:|[view]|"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#DCDCAA"> options</span><span style="color:#D4D4D4">:</span><span style="color:#DCDCAA">NSLayoutFormatOptions</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">rawValue</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#DCDCAA"> metrics</span><span style="color:#D4D4D4">:</span><span style="color:#569CD6">nil</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#DCDCAA"> views</span><span style="color:#D4D4D4">: bindings))</span></span>
<span class="line"><span style="color:#DCDCAA"> addConstraints</span><span style="color:#D4D4D4">(NSLayoutConstraint.</span><span style="color:#DCDCAA">constraints</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">withVisualFormat</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"V:|[view]|"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#DCDCAA"> options</span><span style="color:#D4D4D4">:</span><span style="color:#DCDCAA">NSLayoutFormatOptions</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">rawValue</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#DCDCAA"> metrics</span><span style="color:#D4D4D4">:</span><span style="color:#569CD6">nil</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#DCDCAA"> views</span><span style="color:#D4D4D4">: bindings))</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> // xib ファイルの中に配置した UI 要素へのアクションのハンドリング</span></span>
<span class="line"><span style="color:#569CD6"> @IBAction</span><span style="color:#569CD6"> func</span><span style="color:#DCDCAA"> didTap</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">_</span><span style="color:#9CDCFE"> sender</span><span style="color:#D4D4D4">: UITapGestureRecognizer) {</span></span>
<span class="line"><span style="color:#D4D4D4"> delegate?.</span><span style="color:#DCDCAA">didClickFormSelect</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">sender</span><span style="color:#D4D4D4">: </span><span style="color:#569CD6">self</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span></code></pre>
<p>CLINICS では主に Storyboard を使って UI を実装しているので、使用するときは Storyboard に UIView を置き、コンポーネントのクラス名を指定して使います。テキストなどのプロパティを設定し、Constraint を指定して配置すれば完了です。ユーザによるアクションのハンドリングや動的にプロパティを切り替える必要がある場合は、呼び出し側で処理を追加します。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180327/20180327172653.png" alt="20180327172653.png">
<p>最終的にビルドすると以下のように表示されます。(表示されている内容は開発中に作成した仮のデータで実際のものとは異なります。)</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180327/20180327172923.png" alt="20180327172923.png">
<h1 id="改善策-2-アプリエラーの共通化">改善策 2 アプリエラーの共通化</h1>
<p>以前は業務的に重要な処理のエラー以外はプラットフォーム毎で表示するエラーメッセージが異なっていたり、エラーハンドリング時に違った挙動をしていることがありました。その結果、ユーザ体験が一貫したものになっていないというだけでなく、お問い合わせがあってもカスタマーサポートが一次回答しにくかったり、伝えられた内容が曖昧なため開発者が調査するのに時間がかかったりすることがありました。</p>
<p>そこで、改めてフロント側で発生するエラーの定義を共通化し、エラーメッセージやエラーハンドリング時の処理も統一しました。** 問い合わせの効率化のために共通のエラーコードも決めて、エラー発生時に表示されるアラートに追加し、それらのエラー定義はドキュメントで一覧化して、カスタマーサポートにも共有 **するようにしました。</p>
<p>また、エラーハンドリング時にクラッシュレポートのログに記録する内容や送信するタイミングを統一して、開発者全員が理解しやすいようにしました。エラーコードの表示については、改善を検討していた時期にちょうど参加していた iOSDC Japan 2017 で、同じような課題に対する知見を<a href="https://iosdc.jp/2017/node/1422">発表</a>されていたのを見て、早速取り入れました。最近ではユーザからの問い合わせにもエラーコードが使われることがあり、実際にコミュニケーションコストを低下させることができているように思います。</p>
<p>エラーのフィードバックは細かいところではありますが、ユーザのアクションを継続させるために重要な要素のひとつです。CLINICS はユーザ属性が老若男女問わず幅広いので特に気を配って改善を行ってきました。
実装についてですが、iOS では以下のように定義しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="swift"><code><span class="line"><span style="color:#569CD6">enum</span><span style="color:#4EC9B0"> ApplicationError</span><span style="color:#D4D4D4">: </span><span style="color:#4EC9B0">Error </span><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#569CD6"> case</span><span style="color:#4FC1FF"> commonRequestError</span><span style="color:#D4D4D4">(</span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#569CD6"> case</span><span style="color:#4FC1FF"> createReservationCardError</span></span>
<span class="line"><span style="color:#569CD6"> case</span><span style="color:#4FC1FF"> createReservationScheduleIsFullError</span></span>
<span class="line"><span style="color:#D4D4D4"> ~</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> var</span><span style="color:#D4D4D4"> errorCode: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> switch</span><span style="color:#569CD6"> self</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> case</span><span style="color:#569CD6"> let</span><span style="color:#D4D4D4"> .</span><span style="color:#DCDCAA">commonRequestError</span><span style="color:#D4D4D4">(viewId):</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#CE9178"> "</span><span style="color:#569CD6">\(</span><span style="color:#D4D4D4">viewId</span><span style="color:#569CD6">)</span><span style="color:#CE9178">-0000"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> case</span><span style="color:#D4D4D4"> .</span><span style="color:#9CDCFE">createReservationCardError</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#CE9178"> "40-0001"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> case</span><span style="color:#D4D4D4"> .</span><span style="color:#9CDCFE">createReservationScheduleIsFullError</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#CE9178"> "40-0002"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ~</span></span>
<span class="line"><span style="color:#569CD6">var</span><span style="color:#D4D4D4"> title: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> switch</span><span style="color:#569CD6"> self</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> case</span><span style="color:#D4D4D4"> .</span><span style="color:#9CDCFE">commonRequestError</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#CE9178"> "接続エラー"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> case</span><span style="color:#D4D4D4"> .</span><span style="color:#9CDCFE">createReservationCardError</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#CE9178"> "決済失敗エラー"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> ~</span></span>
<span class="line"><span style="color:#569CD6">var</span><span style="color:#D4D4D4"> description: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> switch</span><span style="color:#569CD6"> self</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> case</span><span style="color:#D4D4D4"> .</span><span style="color:#9CDCFE">commonRequestError</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#CE9178"> "データを正しく表示出来ない可能性があります。</span><span style="color:#D7BA7D">\n</span><span style="color:#CE9178"> 通信状況をお確かめいただくか、しばらく経ってから再度起動してください。"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> case</span><span style="color:#D4D4D4"> .</span><span style="color:#9CDCFE">createReservationCardError</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#CE9178"> "ご登録されているクレジットカードの決済中にエラーが発生しました。</span><span style="color:#D7BA7D">\n</span><span style="color:#CE9178"> おそれいりますが、もう一度最初から操作ください。"</span></span>
<span class="line"><span style="color:#D4D4D4"> ~</span></span>
<span class="line"></span></code></pre>
<p>Android でも同様に enum で定義しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="kotlin"><code><span class="line"><span style="color:#569CD6">enum</span><span style="color:#569CD6"> class</span><span style="color:#4EC9B0"> ApplicationError</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">var</span><span style="color:#D4D4D4"> code: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">, </span><span style="color:#569CD6">val</span><span style="color:#D4D4D4"> title: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">, </span><span style="color:#569CD6">val</span><span style="color:#D4D4D4"> description: </span><span style="color:#4EC9B0">String</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#DCDCAA"> CommonRequestError</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"0000"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"接続エラー"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"データを正しく表示出来ない可能性があります。</span><span style="color:#D7BA7D">\n</span><span style="color:#CE9178"> 通信状況をお確かめいただくか、しばらく経ってから再度起動してください。"</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#DCDCAA"> CreateReservationCardError</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"40-0001"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"決済失敗エラー"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"ご登録されているクレジットカードの決済中にエラーが発生しました。</span><span style="color:#D7BA7D">\n</span><span style="color:#CE9178"> おそれいりますが、もう一度最初から操作ください。"</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#DCDCAA"> CreateReservationScheduleIsFullError</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"40-0002"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"スケジュール空きなしエラー"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"選択された予約日時のスケジュールに空きがありませんでした。</span><span style="color:#D7BA7D">\n</span><span style="color:#CE9178"> おそれいりますが、別の予約日時をご選択のうえ、もう一度最初から操作ください。"</span><span style="color:#D4D4D4">),</span></span>
<span class="line"><span style="color:#D4D4D4"> ~</span></span>
<span class="line"></span></code></pre>
<h1 id="改善策-3-コードレビューの手順改善">改善策 3 コードレビューの手順改善</h1>
<p>リリース当初から実装者以外のメンバーによるレビューは適宜行なっていましたが、レビューの段階でデグレや仕様の違いを見逃してしまうことがあったので、レビュー体制の強化とメンバーのソース理解の向上を図るために、以下のようにルールを設定しました。</p>
<ul>
<li>セルフマージはしない</li>
<li>PR に対して 2 人以上でレビューする</li>
<li>ビューの変更があった場合には画面キャプチャを貼る</li>
</ul>
<p>それらを守りやすく、より効率的にするために<a href="https://danger.systems/ruby/">Danger</a>も導入しました。
導入手順は<a href="https://danger.systems/guides/getting_started.html">こちら</a>にまとめられているほか、検索すればけっこう出てくるので省略します。弊社では iOS の CI は Bitrise を使用しているので Bitrise 上で実行して GitHub の PR に反映させています。</p>
<p>Danger では、以下の項目をチェックしています。上記のルールを反映しているのに加えて、PR の向き先と SwiftLint の実行結果もチェックしています。CLINICS の iOS アプリでは GitFlow を導入しているため、release ブランチと hot-fix ブランチ以外からの PR の向き先が develop ブランチになっていない場合には警告を出すようにしています。</p>
<ul>
<li>レビュアーの人数が 2 人以上になっているか</li>
<li>ビューの変更(xib、storyboard を触ったかどうかのみ確認)があった場合に画面キャプチャを貼っているかどうか</li>
<li>PR が develop に向けて作成されているか</li>
<li>SwiftLint のチェックを通っているか</li>
</ul>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180327/20180327164527.png" alt="20180327164527.png">
<p>弊社が iOS 開発で利用している Danger ファイルは以下の通りとなっています。導入する際のご参考にしてください。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#6A9955"># for only difference</span></span>
<span class="line"><span style="color:#D4D4D4">github.</span><span style="color:#DCDCAA">dismiss_out_of_range_messages</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># reviewers</span></span>
<span class="line"><span style="color:#DCDCAA">warn</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"レビュアーは 2 人以上指定してください"</span><span style="color:#D4D4D4">) </span><span style="color:#C586C0">if</span><span style="color:#D4D4D4"> github.</span><span style="color:#DCDCAA">github</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">pr_json</span><span style="color:#D4D4D4">[</span><span style="color:#CE9178">"requested_reviewers"</span><span style="color:#D4D4D4">].</span><span style="color:#DCDCAA">length</span><span style="color:#D4D4D4"> < </span><span style="color:#B5CEA8">2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># view changes</span></span>
<span class="line"><span style="color:#9CDCFE">view_extensions</span><span style="color:#D4D4D4"> = [</span><span style="color:#CE9178">".xib"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">".storyboard"</span><span style="color:#D4D4D4">]</span></span>
<span class="line"><span style="color:#9CDCFE">has_view_changes</span><span style="color:#D4D4D4"> = git.</span><span style="color:#DCDCAA">modified_files</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">any?</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">file</span><span style="color:#D4D4D4">| view_extensions.</span><span style="color:#DCDCAA">any?</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">ext</span><span style="color:#D4D4D4">| file.</span><span style="color:#DCDCAA">end_with?</span><span style="color:#D4D4D4"> ext }}</span></span>
<span class="line"><span style="color:#9CDCFE">has_view_added</span><span style="color:#D4D4D4"> = git.</span><span style="color:#DCDCAA">added_files</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">any?</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">file</span><span style="color:#D4D4D4">| view_extensions.</span><span style="color:#DCDCAA">any?</span><span style="color:#D4D4D4"> { |</span><span style="color:#9CDCFE">ext</span><span style="color:#D4D4D4">| file.</span><span style="color:#DCDCAA">end_with?</span><span style="color:#D4D4D4"> ext }}</span></span>
<span class="line"><span style="color:#9CDCFE">pr_has_screenshot</span><span style="color:#D4D4D4"> = github.</span><span style="color:#DCDCAA">pr_body</span><span style="color:#D4D4D4"> =~ </span><span style="color:#D16969">/https?:</span><span style="color:#D7BA7D">\/\/\S</span><span style="color:#D16969">*</span><span style="color:#D7BA7D">\.</span><span style="color:#D16969">(png|jpg|jpeg|gif){1}/</span></span>
<span class="line"><span style="color:#DCDCAA">warn</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"見た目に変更がある場合は画面キャプチャを貼ってください"</span><span style="color:#D4D4D4">) </span><span style="color:#C586C0">if</span><span style="color:#D4D4D4"> (has_view_changes or has_view_added) and !pr_has_screenshot</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># base branch</span></span>
<span class="line"><span style="color:#9CDCFE">is_to_master</span><span style="color:#D4D4D4"> = github.</span><span style="color:#DCDCAA">branch_for_base</span><span style="color:#D4D4D4"> == </span><span style="color:#CE9178">'master'</span></span>
<span class="line"><span style="color:#9CDCFE">is_to_develop</span><span style="color:#D4D4D4"> = github.</span><span style="color:#DCDCAA">branch_for_base</span><span style="color:#D4D4D4"> == </span><span style="color:#CE9178">'develop'</span></span>
<span class="line"><span style="color:#9CDCFE">is_from_releases</span><span style="color:#D4D4D4"> = !!github.</span><span style="color:#DCDCAA">branch_for_head</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">match</span><span style="color:#D4D4D4">(</span><span style="color:#D16969">/releases</span><span style="color:#D7BA7D">\/</span><span style="color:#D16969">[0-9]+</span><span style="color:#D7BA7D">\.</span><span style="color:#D16969">[0-9]+</span><span style="color:#D7BA7D">\.</span><span style="color:#D16969">[0-9]/</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#DCDCAA">warn</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">'PR は develop に向けてください'</span><span style="color:#D4D4D4">) </span><span style="color:#C586C0">if</span><span style="color:#D4D4D4"> !is_to_develop and !(is_from_releases and is_to_master)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># swiftLint</span></span>
<span class="line"><span style="color:#D4D4D4">swiftlint.</span><span style="color:#DCDCAA">lint_files</span><span style="color:#569CD6"> inline_mode:</span><span style="color:#569CD6"> true</span></span></code></pre>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180327/20180327164549.png" alt="20180327164549.png">
<h1 id="まとめ">まとめ</h1>
<p>CLINICS におけるアプリ開発の品質と効率性を向上するための取り組みをご紹介しました。これらの取り組みによって<strong>プラットフォーム毎のデザインや機能のブレが少なくなり、認識ずれによる手戻りなどが少なくなったことで開発効率が上がった</strong>と感じます。プラットフォーム毎の違いを少なくして、より多くのメンバーがコードに手を入れやすい状態にすることで実装やコードレビューの質も向上しているように思います。</p>
<p>React Native などを利用して、コードそのものを共通化する方法もあるとは思いますが、プラットフォーム毎に別のコードで開発する場合でも、仕様や実装のルールを工夫することでより効率的に開発できるのではないでしょうか。</p>
<p>CLINICS チームでは他にも実装や開発プロセス、プロダクト運用について日々改善を行なっています。今後も、こうした取り組みを積極的に実践し、KPT 形式で振り返って、また次のアクションにつなげることで、多くの方に愛されるプロダクトを育てていきたいと思っています。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、エンジニアやデザイナーを募集しています。ご興味のある方は、こちらからどうぞ!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 非デザイナーのための「ブランド再入門」https://developer.medley.jp/entry/2018/03/09/183308https://developer.medley.jp/entry/2018/03/09/183308こんにちは。開発本部の医療メディアチームでデザインをしている波切です。
メドレー開発本部で行われている勉強会「TechLunch」で、デザイナー以外の方も知っておいて損はない「ブランドとは?」というお話をさせていただきました。多くの方が何と...Fri, 09 Mar 2018 09:33:08 GMT<p>こんにちは。開発本部の医療メディアチームでデザインをしている波切です。</p>
<p>メドレー開発本部で行われている勉強会「TechLunch」で、デザイナー以外の方も知っておいて損はない「ブランドとは?」というお話をさせていただきました。多くの方が何となく知っていることも多いかもしれませんが、再入門的に参考にしていただけると嬉しいです。</p>
<h1 id="背景">背景</h1>
<p>メドレーでは時期を問わずプロダクトの在り方について常日頃から様々な場面で議論がなされています。</p>
<p>こういった議論の中でブランドとしてどのように見せられるのが良いだろうか、といった検討をすることも多々あり、改めてブランドについて学び直したいと思ったのが今回のきっかけになります。</p>
<p>TechLunch ではエンジニア・デザイナー・ディレクター・医師(!)と様々な職種の人が参加しています。</p>
<p>この時代では当たり前かもしれないブランドの基礎を改めて学び直し、デザイナー以外の職種の方にも共有しましたので、ブログでもご紹介させていただければと思います。</p>
<h1 id="ブランドってなんでしょう">ブランドってなんでしょう</h1>
<h2 id="ブランドとブランディングの違いって">ブランドとブランディングの違いって?</h2>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180309/20180309175130.png" alt="f:id:medley_inc:20180309175130p:plain" title="f:id:medley_inc:20180309175130p:plain"></p>
<p>まずは混合しやすいブランドとブランディングの定義から。</p>
<blockquote>
<p>ある特定の商品やサービスが消費者・顧客によって識別されているとき、</p>
<p>その商品やサービスを「ブランド」と呼ぶ</p>
<p>※製品名、パッケージング、広告、価格、使用経験などにより、その製品につけられた製品特性と価値(機能的および非機能的)とのユニークなコンビネーション。消費者・顧客の目から見た場合、その製品を競合から差別化するもの。</p>
<p>(via <a href="https://www.brand-mgr.org/knowledge/word/">ブランド・マネージャー認定協会 用語集</a>)</p>
<p>ブランディングとは、ブランドに対する共感や信頼などを通じて顧客にとっての価値を高めていく、企業と組織のマーケティング戦略の 1 つ。ブランドとして認知されていないものをブランドに育て上げる、あるいはブランド構成要素を強化し、活性・維持管理していくこと。また、その手法。ここでいうブランドとは高級消費財に限らず、その対象としては、商品やサービス、それらを供給する企業や団体のほか、人物・建築物・史跡・地域 ・祭事など、あらゆるものが該当する。</p>
<p>(via <a href="https://ja.wikipedia.org/wiki/%E3%83%96%E3%83%A9%E3%83%B3%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0">wikipedia:ブランディング</a>)</p>
</blockquote>
<p><strong>ブランド</strong>とは「企業・製品に対して消費者が持っているイメージ(記号)であり、競合との差別化を生み出す価値の総体」を指し、</p>
<p><strong>ブランディング</strong>は「ブランド価値を与えるための手段」と捉えることができると思います。</p>
<p>この定義になぞらえてコカ・コーラを例にブランドとして認知される具体的なステップとして紹介すると、以下のようになります。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180309/20180309175152.png" alt="f:id:medley_inc:20180309175152p:plain" title="f:id:medley_inc:20180309175152p:plain"></p>
<ul>
<li>
<p>ロゴなどの識別記号が記憶される</p>
</li>
<li>
<p>コカ・コーラのロゴが記憶される (生活者の頭の中に、ロゴなどの識別記号が記憶される)</p>
</li>
<li>
<p>記号から体験が思い浮かぶ</p>
</li>
<li>
<p>コカ・コーラのロゴを見れば炭酸飲料であることや、爽やかな気分になれることが思い浮ぶ (識別記号を見れば、知覚体験を想起できる)</p>
</li>
<li>
<p>体験から記号を思い出す</p>
</li>
<li>
<p>爽やかな気分になりたいと思ったら、コカ・コーラを思い出す。ロゴが思い浮かぶ (知覚価値が頭に浮かんだら、識別記号が想起される) </p>
</li>
</ul>
<p>「ブランド連想」「ブランド資産」といった言葉もあり、ブランド(識別記号と知覚体験)に対してポジティブな印象を築き蓄積させることがブランディングのカギになってきます。</p>
<h1 id="ブランド構築">ブランド構築</h1>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180309/20180309175222.png" alt="f:id:medley_inc:20180309175222p:plain" title="f:id:medley_inc:20180309175222p:plain"></p>
<p>前述の記号をブランドの土台とするとコーポレートアイデンティティ(CI)やヴィジュアルアイデンティティ(VI)といったクリエイティブが大きな役割を果たしますが、現在ではユーザーはブランドを知覚する機会が多く、差別化のためにもブランドは顧客体験をベースに作っていくことも重要になります。</p>
<p>「良い顧客体験を提供しブランドの競争力を高める」 という考え方に基づいて、ブランド戦略を考える順番は理想的な体験を描いてから必要なモノや技術を考えます。これはブランドに限らず様々な場面で意識したいことです。</p>
<h2 id="ブランドに大事な一貫性">ブランドに大事な一貫性</h2>
<p>魅力的なブランドは顧客体験が蓄積して形成されていきます。蓄積されるブランド体験の要素は「体験の魅力度」「体験の量と時間」「体験の一貫性」と 3 つあり、ブランドの価値はこの 3 つの要素のかけ算にあります。</p>
<p>体験が魅力的であり、その体験の回数が多く長い時間にわたって経験し、体験自体に一貫性があるブランドがユーザーとって良いものとされます。</p>
<p>3 つのうち特に重要なものは最後の 「体験の一貫性」 にあり、この一貫性は 2 つに分けることができます。</p>
<ul>
<li><strong>時系列で一貫性</strong>:時代で左右されない顧客体験がブランドから提供される</li>
<li><strong>接点の一貫性</strong>:ブランドを買ったり利用する前から、購入プロセス、購入後の利用シーンにおいて、一貫した顧客体験ができる</li>
</ul>
<p>「モノとコト」にも例えやすいところですが、製品としての一貫性に加えて体験する前後のグランドデザインにも一貫性を持たすことが重要になります。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180309/20180309175407.png" alt="f:id:medley_inc:20180309175407p:plain" title="f:id:medley_inc:20180309175407p:plain"></p>
<p>例としてスターバックスは広告費をほとんどかけずに今のブランド認知度を築いたことは有名ですが、そこにはブランドの価値を築くためのブランディングに一貫性があり、かつそれらが時代性とマッチしたのが要因ではないかと思います。</p>
<h2 id="ブランディングのメリット">ブランディングのメリット</h2>
<ul>
<li>選択意思決定の単純化・固定化</li>
<li>顧客の知識が整理されることで競合と差別化され再び同じ物を選ぶようになる</li>
<li>ユーザーのロイヤル化</li>
<li>親しみや信頼が増大されることでブランドロイヤリティが形成される</li>
</ul>
<p>なぜブランドにこだわるのか、という点でもこれらメリットを実現していくことが重要です。</p>
<p>デザイナーとして気遣い溢れる UI や、サービスの思想を記号に落とし込んだ VI などモノづくり視点でのこだわりや重要さを踏まえた上で、他職種の方にもこれらのメリットや競争としての必要であることを理解してもらえるよう働きかけることを意識していきたいところです。</p>
<h2 id="ブランディングの悩みどころ">ブランディングの悩みどころ</h2>
<p>ここからは基礎を踏まえて、実践的に行われているブランディングで悩みやすいところを事例で紹介しながら簡単なディスカッションをしていきました。一部だけ抜粋してご紹介します。</p>
<h2 id="製品ブランドの管理">製品ブランドの管理</h2>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180309/20180309175511.png" alt="f:id:medley_inc:20180309175511p:plain" title="f:id:medley_inc:20180309175511p:plain"></p>
<p>会社のブランド管理の思想を定義できても、変化の早い業界であればイメージ通りになかなか通らないことも多々有りえます。</p>
<p>少ないブランド資産を活用する意味でもスタートアップは横串型に整理されることが多くありますが、ある程度の規模感になって基礎体力がある企業であると個別適応型でもブランドとして信頼性を保てますし、横串型に比べて動きやすさがあると思います。</p>
<p>一方で、プラットフォーマーとして存在したいなら横串のブランド価値からファンを囲い込むべきかもしれません。Techlunch の中でもメドレーであればどうあると良いかについて議論ができました。</p>
<h2 id="ブランディングのタイミング">ブランディングのタイミング</h2>
<p>顧客体験の蓄積が重要なブランドでありますが、どの段階でブランディングに取り組むかは状況によりますが意見が出やすいところだと思います。</p>
<p>ICC FUKUOKA2017 にてブランディングについてのセッションがあり、取り組むタイミングについての議論がうまくまとめられていたため Techlunch で事例として共有しました。プロダクトとしての差別化が基本であることに加えて、ブランドの在り方を組織にインストールさせるためにブランディングに取り組むケースも紹介されています。</p>
<blockquote>
<p><strong>僕はブランドが常に必要かと言うと“No”だと思います。</strong></p>
<p>コモディティ化し始めたり、競争が始まって差別化しなくてはならない時に、プロダクトの機能などで差別化ができない、または必ずしもパフォーマンスで差別化ができない時に唯一残された選択が、ブランドを作るということであると思います。</p>
<p>[株式会社 Bloom&Co. 彌野泰弘]</p>
<p><strong>ユーザーを獲りにいくというフェーズですよね。</strong></p>
<p>メルカリの場合、やはり最初の 100 万ダウンロードくらいまでは、結構チューニングをしながらオンラインのマーケティングで(ユーザーを)獲得してきてきました。</p>
<p><del>中略</del></p>
<p>やはり 100 万ダウンロードくらいあって、リテンションレートが高くユーザーが積み上がる状態になっていれば、大きくマスマーケティング(TVCM)をやっても獲得したユーザーは残りますからね。</p>
<p>[株式会社メルカリ 小泉文明]</p>
<p><strong>僕は、ユーザーを獲得する手前でブランドが必要だと思っているんですよね。</strong></p>
<p>ブランドというのは必ずしも外に対してだけではなくて、(組織の)中の人に対して必要であることもすごく多いのです。</p>
<p><del>中略</del></p>
<p>資生堂は当時 130 年くらい経っている会社だったので、その 130 年もこの後の 130 年も、世の中を美しくするとか、人を美しくするとか、それは女性に限らず、お化粧に限らず美しくするということが必要だよね、軸だよねという話になって、そのスローガンと共に前田新社長体制で進んでいくことになりました。</p>
<p>[株式会社 dof 齋藤太郎]</p>
<p>(via <a href="https://industry-co-creation.com/management/19837">スタートアップのブランディングはいつから必要か?【F17-7C #3】</a>)</p>
</blockquote>
<p>共有した後には、リリース当初からブランドやクリエイティブが作り込まれたプロダクトが最近話題になることが多いということも話に上がりました。</p>
<p>もちろんブランディングのタイミングは状況により千差万別ですが、プロダクトをゼロから作る際には、プロダクトの在り方と合わせてどんなブランドを作って行きたいのかも早めに考えられることが必要だ、など具体的なディスカッションも出来ました。</p>
<h2 id="新たなブランドが受け入れられないことも">新たなブランドが受け入れられないことも</h2>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180309/20180309175734.png" alt="f:id:medley_inc:20180309175734p:plain" title="f:id:medley_inc:20180309175734p:plain"></p>
<p>基本的にブランドを新しくすることは見慣れたものが変わってしまう恐怖から反発を生むケースが多いですが、その中でも GAP はロゴの 2010 年リニューアル公表後ネット上からの反発が強くわずか 6 日間という短期間で新しいロゴの使用を止め、元のロゴに戻してしまうという一件がありました。(この件は<a href="https://techcrunch.com/2010/10/11/gap-logo-redesign/">株価</a>にも大きな影響を与えました)</p>
<p>一方、同じような境遇で Airbnb も 2014 年のリニューアル当初ロゴは不評でしたが今ではリニューアルの成功例として扱われる存在になっています。</p>
<p>Techlunch でも歴史の有無が差になったのか、など様々な意見が出てきました。この件はロゴとしての王道を極めようとした以上の背景を提示出来なかった Gap に対して、<a href="https://www.underconsideration.com/brandnew/archives/new_logo_and_identity_for_airbnb_by_designstudio.php">Airbnb はサービスのストーリーをロゴに内包</a>し、地道にロゴを介したコミュニケーションに徹したことが明暗を分けたように思います。</p>
<p>この事例はロゴのアイデンティティをどこまで確立させられたかの違いにあることに加えて、デザインの意思決定を一時的な多数意見に委ねた事例としても学ぶことが多いです。</p>
<h1 id="まとめ">まとめ</h1>
<p>私個人が元々グラフィックデザインをしていた経験もあり、今まではブランディングの中でも興味が CI や VI に傾倒していましたが、体験全体がブランドに大きな影響を与えるこの時代、組織としてブランド構築をしていく意識が重要であることが今回の大きな学びでした。</p>
<p>また基礎を学ぶ上で書籍を読んでいましたが、最近の事例などを学ぶ際にはブログなどでの情報収集がとても有効で、学び方も変わってきているなと思いました。今回紹介した内容はあくまで入門編なので、興味があれば今回参考にさせていただいた本とブログ・記事からより深堀りが出来るのでオススメします。</p>
<ul>
<li><a href="https://www.amazon.co.jp/%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0-%E3%83%96%E3%83%A9%E3%83%B3%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0-%E5%B7%9D%E4%B8%8A-%E6%85%8E%E5%B8%82%E9%83%8E/dp/4797373113">プラットフォームブランディング</a></li>
<li><a href="https://www.amazon.co.jp/%E3%82%B3%E3%83%BC%E3%83%9D%E3%83%AC%E3%83%BC%E3%83%88%E3%83%BB%E3%82%A2%E3%82%A4%E3%83%87%E3%83%B3%E3%83%86%E3%82%A3%E3%83%86%E3%82%A3%E6%88%A6%E7%95%A5%E2%80%95%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%8C%E4%BC%81%E6%A5%AD%E7%B5%8C%E5%96%B6%E3%82%92%E5%A4%89%E3%81%88%E3%82%8B-%E4%B8%AD%E8%A5%BF-%E5%85%83%E7%94%B7/dp/4416610130">コーポレート・アイデンティティ戦略―デザインが企業経営を変える</a></li>
<li><a href="https://www.jacs.gr.jp/announcement/20120305_AOKI.pdf">ブランド・エクイティ研究の展望 ~価値をめぐる議論の系譜を中心に~</a></li>
<li><a href="https://www.slideshare.net/takehisagokaichi/ux-46532066">UX とブランド</a></li>
<li><a href="https://www.missiondrivenbrand.jp/entry/brandidentity">ブランドアイデンティティとは|ブランド構築を成果に導く BI の創り方|成功事例有</a></li>
<li><a href="https://industry-co-creation.com/management/19837">スタートアップのブランディングはいつから必要か?</a></li>
<li><a href="https://blog.btrax.com/jp/2017/07/12/gapandairbnb/">ロゴのリデザインーなぜ Gap が失敗し Airbnb が受け入れられたのか</a></li>
<li><a href="https://forbesjapan.com/articles/detail/19596">武器になる「ロゴ」を生み出す、CI デザインとは何か?</a></li>
</ul>
<p>Techlunch ではデザイナー以外の方からも事例に対して意見が出たり、疑問に対して参加者でディスカッションが出来たことがとても有意義でした。今後はこれらを意識しながらブランドの土台となる価値や在り方をデザイン出来るようにしていきたいと思います。</p>
<h1 id="お知らせ">お知らせ</h1>
<p> メドレーでは、医療介護の求人サイト「ジョブメドレー」、医師たちがつくるオンライン医療事典「MEDLEY」、口コミで探せる介護施設の検索サイト「介護のほんね」、オンライン診療アプリ「CLINICS」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<p>興味のある方、ぜひメドレーへ遊びにいらしてください。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>
<p><cite class="hatena-citation"></cite></p>medley
- アナログな業界をテクノロジーで変える、X-Tech な CTO 大集合! - デブサミ 2018 に登壇して来ました -https://developer.medley.jp/entry/2018/03/08/155432https://developer.medley.jp/entry/2018/03/08/155432こんにちは、メドレー広報の阿部です。先日開催された Developers Summit(デブサミ)2018 に、メドレーの CTO ・平山が登壇しました。
デブサミの今回のテーマは「変わるもの × 変わらないもの」。
レガシーな業界がインタ...Thu, 08 Mar 2018 06:54:32 GMT<p>こんにちは、メドレー広報の阿部です。先日開催された Developers Summit(デブサミ)2018 に、メドレーの CTO ・平山が登壇しました。</p>
<p>デブサミの今回のテーマは「<strong>変わるもの × 変わらないもの</strong>」。</p>
<p>レガシーな業界がインターネットの力で変わりつつある、その面白さをエンジニアに知ってもらえたらいいですね、と<a href="https://codezine.jp/">CodeZine</a>/<a href="https://edtechzine.jp/">EdTechZine</a>編集長の斉木さんと盛り上がったことで、トークセッションが実現。</p>
<p><strong>「医療 ×IT」としてメドレー CTO ・平山が、「金融 ×IT」としてマネーフォワード CTO ・中出さんが、「飲食 ×IT」としてトレタ CTO ・増井さん</strong>が登壇。ファシリテーターに<a href="https://edtechzine.jp/"> </a>CodeZine/EdTechZine 編集長の斉木さんをお迎えしての実施となりました。</p>
<p>どんな話が飛び出したのか、一部ではありますが紹介します!</p>
<p><a href="https://event.shoeisha.jp/static/images/event/741/1200.png"><img src="https://event.shoeisha.jp/static/images/event/741/1200.png" alt="https://event.shoeisha.jp/static/images/event/741/1200.png"></a></p>
<h1 id="そもそも-x-tech-って">そもそも X-Tech って?</h1>
<p> 斉木さんは冒頭で、X-Tech について「<strong>IT の導入が遅れている業界において、 スタートアップが洗練された IT 技術により新たな価値や仕組みを提供する動き</strong>」と定義。実際に各事業ではどういう感じか?と話が進みました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180228/20180228141346.jpg" alt="f:id:medley_inc:20180228141346j:plain" title="f:id:medley_inc:20180228141346j:plain"></p>
<p>Fintech の将来像として「<strong>キャッシュレス社会を実現したい</strong>。お店にとって、現金って本当は不便なもののはず。毎日お釣り金を準備しないといけないし、あまり大金をお店に置いておけないから、定期的に銀行に入金しに行ったりしているのが現状。そういう煩わしさを、少しずつ無くしていきたいですね」と、マネーフォワード・中出さん。</p>
<p>お金=現金というような”当たり前”が深く根ざしているのは、医療の世界も同様です。</p>
<p>「医療は、未だに紙のカルテや FAX 文化が残っていたり、そもそもインターネットが浸透していない。もちろん医療システムもありますが、医師や医療従事者の言うことをそのまま聞いて作ったというものも多くて、<strong>全体最適が取れていない</strong>という課題もあります」と弊社・平山も話します。</p>
<h1 id="多くの店舗に共通する課題を本質的に解決していくことが大切">多くの店舗に共通する課題を本質的に解決していくことが大切</h1>
<p>X-Tech では、インターネットサービスにより大きな変化がおこる分、これまで使い慣れていないサービスだけに、様々な改善要望が入ることも少なくありません。</p>
<p>「実際にサービスを使って頂いている飲食店から様々な要望を頂くのですが、弊社は一切カスタマイズをしない、というスタンスをとっています。飲食店の価格やタイプは様々だけど、突き詰めると実は課題は共通していたりする。ヒアリングしながら、<strong>本質的な課題を見つけて、解決策を提供する</strong>ようにしています」と、予約/顧客管理サービス「<a href="https://toreta.in/jp/">トレタ</a>」を提供するトレタ・増井さん。</p>
<p>これは医療でも同様で「レガシーな業界だと、医師のいう通りにプロダクトを作ることに従うなどの関係性も生まれやすいという状況はあります。でもそこは、<strong>専門分野が違う対等な存在と捉えて、プライドを持って議論をできることが大切</strong>ですよね」と平山も答えます。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180228/20180228141409.jpg" alt="f:id:medley_inc:20180228141409j:plain" title="f:id:medley_inc:20180228141409j:plain"></p>
<h1 id="x-tech-で活躍できるエンジニアは">X-Tech で活躍できるエンジニアは?</h1>
<p>CTO 対談ということもあり、話は「X-Tech を支える組織作り」に。</p>
<p>会社で働くエンジニアの特徴や求められるものを聞かれると、</p>
<p>「飲食経験者が多いわけではないのですが、食べるのが好きな人は多い。社員同士で食事に行くことも多く、たまにエンゲル計数大丈夫かなと思います(笑)。スタートアップだからこそ、求められる役割が固定されず日々変わっています。有機的に変わることに抵抗感がないことが大切ですね」と、増井さん。</p>
<p>中出さんも「たしかに、世の中の課題解決へのモチベーションが強いと思う」とこれに同意。さらに平山も「<strong>プロダクトに誇りを持っている人が多い</strong>ですよね」と続けました。</p>
<h1 id="x-tech-ならではの開発チームって">X-Tech ならではの開発チームって?</h1>
<p>最後に、組織づくりや採用で気をつけていることには、各社こんな回答がありました。</p>
<p>「マネーフォワードでは、プロダクトごとにチームを組んでいて、<strong>スモールチームで運営することを心がけています</strong>。技術選定も含めて、そのチームが使うべきと判断したらいいと。共有化も大切ですが、それが足かせになることもある。私が CTO になってから、そういうものは最低限に整理しました。」 (中出さん)</p>
<p>「採用はずっと頑張っているけど、ずっと足りていません。もともと経験的にシニア〜ミドル層を採用したいと考えていましたが、現在はジュニアまで幅を広げています。ただ、スタートアップでは残念ながらゼロからプログラムを教える余裕はないことが多い。だからこそ”<strong>僕らが何を与えられるのか”をすごく考えています</strong>。技術やビジネスマナーのイロハは十分に教えられないけれど、業界や技術の面白さはトレタだからこそ与えられることがあると思う。」(増井さん)</p>
<p>「今メドレーはエンジニアとデザイナー、ディレクター、医師で 30 人くらいの開発チームですが、<strong>職種間の隙間をなくすことを徹底しています</strong>。iOS エンジニア、サーバサイド、フロントエンド、と担当を分けることで情報断絶が起きる。ミッションによって人をアサインし、職種を横断して取り組める環境を作っています」(平山)</p>
<p>最後に平山は「X-Tech は Web エンジニアのものと思われがちですが、toB 向けに広く展開しているプロダクトが多く、(デブサミの参加者に多いような)SIer 系のエンジニアの方にも近しい匂いを感じてもらえる世界だと思う。ぜひ次のキャリアとして、インターネットの会社も選択肢にあるんだというのを思っていただけると嬉しいです!」と力強くコメントして、セッションを締めました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180222/20180222161519.jpg" alt="f:id:medley_inc:20180222161519j:plain" title="f:id:medley_inc:20180222161519j:plain"></p>
<p>2 月には「<a href="https://tech.nikkeibp.co.jp/">日経 xTECH</a>」が創刊されるなど、ますます注目の X-Tech 分野。</p>
<p>どんなことができる業界なのか、メドレーはどんなビジョンに向けて動いているのかなど、もっと話を聞いてみたいという方は、ぜひご連絡ください!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="株式会社メドレーの最新情報 - Wantedly" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fmedley" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.wantedly.com/companies/medley">www.wantedly.com</a></cite>medley
- Composable な UI 設計を目指したフロントエンド開発https://developer.medley.jp/entry/2018/02/27/170000https://developer.medley.jp/entry/2018/02/27/170000こんにちは、開発本部の舘野です。医療介護の求人サイト「ジョブメドレー」の開発を担当しています。
昨年、ジョブメドレーでは事業所が利用する採用管理画面の UI リニューアルを行いました。ユーザが使いやすい UI づくりを目指すために、長期間に...Tue, 27 Feb 2018 08:00:00 GMT<p>こんにちは、開発本部の舘野です。医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」の開発を担当しています。</p>
<p>昨年、ジョブメドレーでは事業所が利用する採用管理画面の UI リニューアルを行いました。ユーザが使いやすい UI づくりを目指すために、長期間にわたり誰が開発しても一貫性ある UI を実現できるようなシステムが必要です。そこで今回は「Composable」な UI システムの実現をテーマに、どのように開発を行ったのかについて、共有させていただきます。</p>
<h1 id="背景画面や機能追加のたびに-ui-の一貫性がなくなっていた">背景:画面や機能追加のたびに UI の一貫性がなくなっていた</h1>
<p>ジョブメドレーの採用管理画面とは、医療機関や介護施設の採用担当者が求人情報の管理や応募者の選考状況の管理などを行う画面です。</p>
<p>この採用管理画面ですが、リニューアル以前は<a href="https://angular.io/">Angular</a>をフレームワークとして採用した SPA で、UI に関しては<a href="https://angular-ui.github.io/bootstrap/">AngularUI の Bootstrap</a>を利用して、それぞれのエンジニアが実装を行っていました。</p>
<p>それなりの UI をスピーディーに実現できる点においては、Bootsrap のような UI フレームワークを利用することで受けられる恩恵は大きかったのですが、一方で、包括的に UI 設計を行っているわけではなく、<b>各人が局所的に UI を作っていくので、画面や機能を追加していく中で一貫性がない UI が増えていく状態</b>になっていました。</p>
<p>実際にユーザインタビューなどを行ってみると、「ログインした後どうすれば良いのか分からない」、「〇〇という機能があることを今まで知らなかった」、「xx がどこにあるのか分からない」などの意見が多々あり、全面的な UI の見直しが必要になっていました。</p>
<p>医療や介護の現場での人材不足を解消するために採用担当者に提供するツールとして、今後さらに機能拡充していくことが求められていましたが、機能拡充していくことに耐えうる状態にはないというのがプロダクトチームのメンバーの共通認識でした。</p>
<p>そこで、全体的に情報設計から見直してデザインを刷新し、今後プロダクトを成長させていく上でスケール可能な UI を提供できるようにするため、UI リニューアルを決定しました。</p>
<h1 id="フロントエンドで必要だったこと">フロントエンドで必要だったこと</h1>
<p>Bootstrap を用いてエンジニアのみで UI を作っていたのとは異なり、リニューアルでは社内のデザイナーが現状の UI 上の課題を整理したデザインを作成しました。</p>
<p>これに伴って、自前で全ての UI パーツを作成することになりましたが、Bootstrap に頼りきっていたときとは違い、堅牢性と柔軟性を伴った UI システムを自分たちで構築する必要がありました。</p>
<p>リニューアル前の採用管理画面の UI は一貫性に欠けており、ユーザは非常に多くの操作を学ぶ必要がありましたが、この責任はデザイナーだけでなく UI 開発をするエンジニアにも大いにあります。
良いデザインができても、最終的にプロダクトの UI はコードによって作り上げられるものなので、エンジニア次第で一貫性に欠ける UI になってしまうことは十分にあり得ると思います。</p>
<p>往々にして起こり得るのは、目にする機会が比較的少ない画面であったり、改修対象ではない部分などが気づいたら崩れていたり、意図しない UI になってしまっていたりということですが、こういった状況に陥る大きな要因としてはフロントエンドの部分で一貫性に対する配慮ができてないことが 1 つだと思います。</p>
<p>そこで、すでにある採用管理画面を使いやすくするのはもちろん、今後スケールしていく中で<b>一貫性のある UI を担保し続けていくためには、リニューアルでフロントエンドも堅牢で柔軟な UI システムへと変える必要がありました</b>。</p>
<h1 id="ui-リニューアルで開発上大切にしたこと">UI リニューアルで開発上大切にしたこと</h1>
<p>UI の一貫性を保つとなると、今のフロントエンドではもはや当然のことかもしれませんが、コンポーネント指向で構成することになると思います。</p>
<p>技術選定としては、上述の通りリニューアル以前は Angular(v1.4.11)を利用していましたが、リニューアルのタイミングで React へ移行しました。</p>
<p>React を選択した理由としては、学習コストの点やコミュニティが活発でエコシステムが充実している点、単一方向のデータフロー、シンプルな API などを総合的に判断してのものですが、目下の課題である UI コンポーネントのメンテナビリティに関しても適切な選択肢であると考えました。</p>
<p>CSS の方はというと、リニューアル前は Bootstrap でまかなえない部分は Sass でそれぞれのエンジニアが書きたいように書くという状態でしたが、リニューアルで Sass に加えて一部 PostCSS という構成に変更して、設計は ITCSS、Lint を stylelint で行う、という形にしました。</p>
<p>ITCSS を選択した背景としては、その詳細度順のレイヤー階層によってカスケードを管理しやすい点やレイヤーの増減で容易にスケールできる点などから選択しました。</p>
<p>CSS in JS も考慮はしましたが、リニューアルの時点ではこれという決定的な選択肢が無かったこともあり(まだ styled-components も正式リリースされてなかった)、<a href="https://github.com/JedWatson/classnames">classnames</a>を利用しました。</p>
<p>フレームワークやライブラリの選定も重要ですが、UI システムを刷新する上で<b>開発上最も重視したのは「Composability(コンポーネントの組み合わせが容易であること)」</b>でした。</p>
<p>Composable であるということは、つまり様々な状況において組み込み可能な状態であり、再利用性が高いということになります。
それぞれのコンポーネントを組み合わせることが容易に出来るとともに、複数のコンポーネントを組み合わせた状態から 1 つ 1 つ分解することも容易な状態が望ましく、結果的にそれで UI が構築しやすく改修しやすい状態に自然となるはずです。</p>
<p>モーダルを例にあげると、モーダルの中で表示するコンテンツ要素やモーダルの背面に敷くオーバーレイコンポーネントは、モーダルコンポーネント自体には含まず別のコンポーネントとして切り出した方が再利用しやすく、組み合わせやすい、ということです。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="html"><code><span class="line"><span style="color:#808080"><</span><span style="color:#F44747">ModalFrame</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#F44747">Modal</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#F44747">ModalHead</span><span style="color:#808080">></span><span style="color:#D4D4D4">...</span><span style="color:#808080"></</span><span style="color:#F44747">ModalHead</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#F44747">ModalBody</span><span style="color:#808080">></span><span style="color:#D4D4D4">...</span><span style="color:#808080"></</span><span style="color:#F44747">ModalBody</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> </</span><span style="color:#F44747">Modal</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#F44747">Overlay</span><span style="color:#808080"> /></span></span>
<span class="line"><span style="color:#808080"></</span><span style="color:#F44747">ModalFrame</span><span style="color:#808080">></span></span></code></pre>
<p>上記の例でいうと、モーダルを画面の中央に配置することは<code><ModalFrame /></code>が行い、<code><Modal /></code>自体はモーダルに内包されるコンテンツのコンテナとしての役割だけを持ちます。<code><Overlay /></code>も独立したコンポーネントの 1 つで、モーダル以外とも組み合わせて利用しています。</p>
<p>コンテナとなるコンポーネントとその子となるコンポーネントは、別コンポーネントに分離されていることで、お互いに依存しないようになります。</p>
<p>また、Sass ファイルもこのコンポーネント構成に合わせて分けています。
ITCSS において、<code><ModalFrame /></code>のようなレイアウトのみの役割を持つ場合のスタイルは Objects レイヤー(装飾を持たない UI パターンのレイヤー)となり、装飾を持つ<code><Modal /></code>や<code><Overlay /></code>は Components レイヤーとして扱います。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="sass"><code><span class="line"><span style="color:#C586C0">@import</span><span style="color:#569CD6"> “objects.modal-frame”;</span></span>
<span class="line"><span style="color:#C586C0">@import</span><span style="color:#569CD6"> “components.modal”;</span></span>
<span class="line"><span style="color:#C586C0">@import</span><span style="color:#569CD6"> “components.overlay”;</span></span></code></pre>
<p>CSS はその特有のカスケードや詳細度によって決定されるスタイルがあり、依存関係を持たない状態を作ることが困難ですが、ITCSS の考えに則ってそれらの CSS の特徴に逆らわないように詳細度の低いものから順番に<code>@import</code>するようにしています。</p>
<p>Sass ファイルの中身ですが、<code>_objecst.modal-frame.scss</code>は<code><ModalFrame /></code>のスタイルのみを記述するようにします。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="sass"><code><span class="line"><span style="color:#D7BA7D">.o-modal-frame</span><span style="color:#F44747"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> position</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">fixed</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> top</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">0</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> left</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">0</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> right</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">0</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> bottom</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">0</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> z-index</span><span style="color:#D4D4D4">: </span><span style="color:#DCDCAA">map-get(</span><span style="color:#9CDCFE">$zIndexMap</span><span style="color:#6A9955">,</span><span style="color:#CE9178"> modalFrame</span><span style="color:#DCDCAA">)</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#F44747">}</span></span></code></pre>
<p><code>_components.modal.scss</code>も同様に<code><Modal /></code>のスタイルのみを記述します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="sass"><code><span class="line"><span style="color:#D7BA7D">.c-modal</span><span style="color:#F44747"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> position</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">relative</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> margin</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">0</span><span style="color:#CE9178"> auto</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> width</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">900</span><span style="color:#C586C0">px</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> min-width</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">640</span><span style="color:#C586C0">px</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> background-color</span><span style="color:#D4D4D4">: </span><span style="color:#9CDCFE">$JM-White</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> box-shadow</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">0</span><span style="color:#B5CEA8"> 1</span><span style="color:#C586C0">px</span><span style="color:#B5CEA8"> 6</span><span style="color:#C586C0">px</span><span style="color:#B5CEA8"> 0</span><span style="color:#DCDCAA"> rgba(</span><span style="color:#9CDCFE">$JM-Black</span><span style="color:#6A9955">,</span><span style="color:#B5CEA8"> 0.2</span><span style="color:#DCDCAA">)</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> z-index</span><span style="color:#D4D4D4">: </span><span style="color:#DCDCAA">map-get(</span><span style="color:#9CDCFE">$zIndexMap</span><span style="color:#6A9955">,</span><span style="color:#CE9178"> modal</span><span style="color:#DCDCAA">)</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#F44747">}</span></span></code></pre>
<p>このように Sass と React コンポーネント毎に 1 対 1 の関係になるようにしています。
プレフィックスとして付与している<code>c-</code>や<code>o-</code>は ITCSS のレイヤーのことを指します。
<code>o-</code>は Objects レイヤーのプレフィックスで、<code>c-</code>は Components レイヤーのプレフィックスです。
基本的に React の UI コンポーネント内では、コンポーネントの種別に応じて<code>c-</code>か<code>o-</code>のプレフィックスを持つクラスと、状態によって付けたり外したりする State レイヤーの<code>s-</code>プレフィックスのクラスのみを使用します。</p>
<p>話を React に戻すと、下記のようなヘッダー要素を画面上部に固定表示するだけの役割を持つ<code><AppBar /></code>コンポーネントは、<code>props.children</code>で子要素を受け取れるようになっているだけで、その内容には関知しないようになっています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="javascript"><code><span class="line"><span style="color:#569CD6">const</span><span style="color:#DCDCAA"> AppBar</span><span style="color:#D4D4D4"> = (</span><span style="color:#9CDCFE">props</span><span style="color:#D4D4D4">) </span><span style="color:#569CD6">=></span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#808080"> <</span><span style="color:#569CD6">div</span><span style="color:#9CDCFE"> className</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"o-app-bar"</span><span style="color:#808080">></span><span style="color:#569CD6">{</span><span style="color:#9CDCFE">props</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">children</span><span style="color:#569CD6">}</span><span style="color:#808080"></</span><span style="color:#569CD6">div</span><span style="color:#808080">></span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">};</span></span></code></pre>
<p>内包する子コンポーネントが何であれ、<code><AppBar /></code>は自分自身の責任だけを果たせば良いので、開発上もシンプルに考えられます。</p>
<p><code>className</code>に渡している<code>o-app-bar</code>は ITCSS の Objects レイヤーのクラスです。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="sass"><code><span class="line"><span style="color:#D7BA7D">.o-app-bar</span><span style="color:#F44747"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> position</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">fixed</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> top</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">0</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> left</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">0</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> right</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">0</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#9CDCFE"> z-index</span><span style="color:#D4D4D4">: </span><span style="color:#DCDCAA">map-get(</span><span style="color:#9CDCFE">$zIndexMap</span><span style="color:#6A9955">,</span><span style="color:#CE9178"> appBar</span><span style="color:#DCDCAA">)</span><span style="color:#F44747">;</span></span>
<span class="line"><span style="color:#F44747">}</span></span></code></pre>
<p>ヘッダー要素を画面上部に固定表示する、レイアウトのみの役割を持つコンポーネントなので、Objects レイヤーとなり、<code>o-app-bar</code>にはレイアウト目的のスタイルのみを持たせます。</p>
<p>ジョブメドレーの採用管理画面では、医療機関や介護施設から求職者に向けた情報を入力していただくために多くのフォーム要素があり、非常に煩雑になりがちですが、<b>それぞれの役割を果たすコンポーネントを組み合わせることで、UI 開発上の堅牢性、柔軟性を高められる</b>ように努めました。</p>
<p>実際のリニューアル開発時には、全ての UI コンポーネントを実装する前に、開発側ではデザイナーが用意した Sketch から、全ての UI パーツを洗い出す作業を行い、その中で分解不可能なレベルまでコンポーネントを分解していき、実装すべきコンポーネントを一覧化しました。</p>
<p>その後、作成したコンポーネント一覧から全 UI コンポーネントを Storybook に実装していきました。</p>
<p>Storybook は、UI コンポーネント開発のサンドボックス環境として、React や Vue を利用した開発では割と一般的に利用されるようになっていると思います。リニューアル時も各コンポーネントの開発環境として利用して、コンポーネントのパターンや組み合わせの確認などを Storybook 上で行いました。</p>
<p>画面を作っていく段階では、<b>用意した UI コンポーネントを組み合わせて利用すれば画面全体の大半の UI が出来上がる</b>ようになっていました。</p>
<p>細かい部分では、事前に用意するコンポーネントに不足があったり、実装した後で仕様の変更によりコンポーネント自体を削除することや、分解不可能な状態まで落とし込めてないコンポーネントが見つかったりと、様々な反省点はありました。ですがリニューアル全体を通して振り返ると、Composable な UI コンポーネントで堅牢で柔軟性のある構成にするということに一定の成果は出せたかなと思います。</p>
<h1 id="まとめ">まとめ</h1>
<p>UI リニューアル以降、採用管理画面ではリニューアル時の UI システムを土台にして、継続的に機能を追加・改修しています。</p>
<p>プロダクトで設けている KPI も順調に遷移していて、顧客からの問い合わせもリニューアル以前のような、UI 上の問題で利用が困難であるというものは減少し、ポジティブな結果が得られています。</p>
<p>開発をする上でも Composable になるようにコンポーネント群を作成したことで、<b>リニューアル以降は UI の改修がシンプルに行えるようになり、開発メンバーのスキルセットに左右される部分が少なくなり、開発効率が上がりました</b>。</p>
<p>このような点からリニューアル自体は良かったと思うと同時に、一方でさらに良い UI を提供するために取り組むべきことは、少なくないと感じます。</p>
<p>例えば採用管理画面が十分にアクセシブルだとは言えないし、パフォーマンス面でもより一層の努力が求められます。もちろん UI コンポーネントの堅牢性もまだ十分とは言えません。</p>
<p>より良いプロダクトを提供するためにそういった課題に対しても継続して取り組んでいきたいと思います。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、エンジニア・デザイナーを募集しています。
メドレーでの開発にご興味ある方は、こちらをご覧ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 2018 年の開発本部キックオフ合宿を行いました!@河口湖https://developer.medley.jp/entry/2018/02/09/130000https://developer.medley.jp/entry/2018/02/09/130000こんにちは、開発本部の日下です。インターンを経て新卒としてメドレーに入り、現在は 1 年生エンジニアとしてオンライン診療アプリ CLINICSの開発を担当しています。先日開発本部でキックオフ合宿を河口湖で行ってきましたので、詳細をレポートし...Fri, 09 Feb 2018 04:00:00 GMT<p>こんにちは、開発本部の日下です。インターンを経て新卒としてメドレーに入り、現在は 1 年生エンジニアとして<a href="https://clinics.medley.life/">オンライン診療アプリ CLINICS</a>の開発を担当しています。先日開発本部でキックオフ合宿を河口湖で行ってきましたので、詳細をレポートします。</p>
<h1 id="年始のキックオフを合宿で行う意図">年始のキックオフを合宿で行う意図</h1>
<p>メドレーの開発本部では、チームビルディングを目的としてバーベキューなど全体でリフレッシュをする機会を年に数回設けています。</p>
<p>そのリフレッシュの一環として行っているメドレーの「年始のキックオフ合宿」は、昨年から行われています。昨年のレポートは<a href="https://developer.medley.jp/entry/2017/02/14/180948">こちら</a>にあります。</p>
<p>メドレー開発本部全体のキックオフを合宿で行う意図は主に二つあります。</p>
<h2 id="親睦を深める">親睦を深める</h2>
<p>一つ目の理由は親睦を深めることにあります。</p>
<p>リフレッシュを兼ねた<strong>一体感の醸成、連携・親密感を深める場</strong>を定期的に用意するようにしています。実際に合宿を終えて振り返ってみても、<strong>合宿は日常より密な交流をする場として最高</strong>だったなと思います。</p>
<h2 id="方向性の共有">方向性の共有</h2>
<p>二つ目の理由は方向性の共有のためです。</p>
<p>昨年の合宿では、今後どういった方向を会社として、また各プロダクトとして目指して行くのかといった話を CTO ・平山から共有しました。普段の業務から離れた場所でこうした話に向き合えるという意味で、<strong>共通の軸をより強く持つためには合宿が適切</strong>だと実感し、今年も合宿を行うことになりました。</p>
<h1 id="合宿先アクティビティの選定">合宿先・アクティビティの選定</h1>
<p>いわゆる開発合宿ではなかったため、選定には次の項目を重視しました。</p>
<ul>
<li><strong>二十数名を収容できるキャパシティがあること</strong></li>
<li><strong>プライベートでも行きたい、思い出になるような場所であること</strong></li>
<li><strong>アクティビティが近くにあること</strong></li>
<li><strong>+α でバーベキューなどができること</strong></li>
</ul>
<p>上記の条件をすべて満たした場所を調査しましたが、「宿はあるがアクティビティがない」「アクティビティはあるが宿がない」のようになかなかマッチするところがなく苦労しました。</p>
<p>最終的にはこれら全てにマッチした先として、宿泊先を<a href="https://www.c-ban.com/"><strong>河口湖カントリーコテージ</strong></a><strong>さん</strong>に、アクティビティは<a href="https://www.c-ls.jp/"><strong>カントリーレイクシステムズ</strong></a><strong>さん</strong>にお世話になることにしました。</p>
<p>また、アクティビティに関しては、カヌー体験やほうとうづくりなど様々なものが用意されていましたが、次の観点から<a href="https://www.c-ls.jp/%E6%A8%B9%E6%B5%B7%E6%B4%9E%E7%AA%9F%E4%BD%93%E9%A8%93"><strong>洞窟探検</strong></a>を選択しました。</p>
<ul>
<li><strong>普段座りっぱなしなので、運動が行えること</strong></li>
<li><strong>新しい刺激を受けられるように、非日常感が体験できること</strong></li>
<li><strong>メンバーの普段見れない様子がでてきそうな、高揚感の高まる場所であること</strong></li>
</ul>
<h1 id="非日常な体験ができる洞窟探検">非日常な体験ができる洞窟探検</h1>
<p>当日、河口湖駅に集合した後はまずアクティビティに挑みました。</p>
<p>みんなで非日常を体験し、リフレッシュしてもらおうと選択したものでしたが、私も含め洞窟探検をした人はおらず、どういったものだろうとワクワクしながら富士の樹海を散策しました。</p>
<p>今回体験した洞窟は富士の青木ヶ原樹海にある<a href="https://www.yamanashi-kankou.jp/kankou/spot/p1_4905.html">富士風穴</a>、初めて本格的な洞窟を見た我々は息を呑むような風景に圧倒されました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180208/20180208175850.jpg" alt="20180208175850.jpg">
<p><a href="https://info.medley.jp/entry/2018/01/19/183500">先日の週刊メドレー</a>で掲載された写真はまさに洞窟に入ろうとするメンバーでした。 思った以上に洞窟感のある洞窟が姿を表しおおーっとなりました。</p>
<p>天井に頭をぶつけたり、手をついて怪我をしたりする可能性がありましたが、ヘルメット・手袋・真っ赤なつなぎといったグッズがレンタルできたので、すべて装着して洞窟に挑みました。</p>
<p>洞窟の中は手すりや階段のない、自然そのままの洞窟らしく、岩がむき出しで天井が低かったりしたので思った以上に装備が役に立ちました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180208/20180208175926.jpg" alt="20180208175926.jpg">
<p>氷柱がある中、奥の方まで進むメンバー</p>
<p>洞窟といえば氷柱!と思っていたのですが、ちょうど数日前に雨が降った影響で、普段より足元や天井から氷柱がたくさん生えていたので触ってみたり、初めての経験ばかりでした。</p>
<p>氷が多い影響で滑りかけることもありましたが、ガイドさんと先行しているメンバーからの助言に助けられつつ進むことができました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180208/20180208180641.jpg" alt="20180208180641.jpg">
<p>無事に生還しました!</p>
<p>普段は一緒に仕事をしている仲間と会社ではなく洞窟という空間で一緒にいるという不思議な状況と、適度な運動のせいか気分は高揚し、普段は見られないような同僚の一面を見ることができた良い経験になったと思います。</p>
<h1 id="2018-年の開発本部キックオフ">2018 年の開発本部キックオフ!</h1>
<p>アクティビティで適度に運動をした後はコテージに移り、少しずつお酒を飲んでくつろぎ始めたタイミングで、2018 年のキックオフとして CTO ・平山によるプレゼンを行いました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180208/20180208180054.png" alt="20180208180054.png">
<p>昨年 2017 年は四つのプロダクトのうちジョブメドレー・ MEDLEY ・ CLINICS の大規模リニューアルがあり、大きな変化の年となりました。</p>
<p>また、今後どういった会社にしていきたいか?医療業界にどういった形で我々が貢献していくのか?そしてそのために事業として、またプロダクトとして 2018 年はどういった体制で推進していくのかといった事柄が共有されました。</p>
<p>なぜやるのか?どうやってやるのか?といった根本的な指針を背景を元に共有してもらうことで、今後の仕事における一つの軸ができたキックオフになったかと思います。</p>
<p>私自身一年間 CLINICS に関わってきたため、肌身で感じてきた変化や苦労を思い出しつつ、プロダクト・事業が着実に成長していることを感じることができ、次の指針を確認する良い機会となりました。</p>
<p>平山のプロダクト・事業・会社に対する熱い思いは<a href="https://toppa.medley.jp/">メドレー平山の中央突破</a>にも書かれていますので、ぜひご覧ください。</p>
<h1 id="バーベキュー飲み">バーベキュー&飲み</h1>
<p>キックオフ後はバーベキュー!そして飲み!</p>
<p>火に強いメンバーは肉を焼いたり、料理が得意なメンバーは焼きそばやパスタを作っては食べて飲んで談笑し、思い思いに合宿を楽しんでいました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180208/20180208180114.jpg" alt="20180208180114.jpg">
<p>まずは肉を焼く火に強いメンバー</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180208/20180208180137.jpg" alt="20180208180137.jpg">
<p>談笑するメンバー(酔いが回りピントがボケ気味)</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180208/20180208180200.jpg" alt="20180208180200.jpg">
<p>思い思いにくつろぐメンバー</p>
<p>余談ですが、上の画像で全員が着ている灰色のパーカーはキックオフ直前に配られたメドレーオフィシャルグッズの一つ、<a href="https://www.wantedly.com/companies/medley/post_articles/62275">デザイン部長・マエダ</a>がデザインしたメドレーパーカーです。</p>
<p>実はメドレーではパーカー以外にもうちわや T シャツ、絆創膏などグッズが増えています。</p>
<p>気がついたら増えているので収集は大変ですが、今年はどういったグッズが作られるのか今から楽しみです。</p>
<p>メンバー同士で飲んで食べてくつろぎながら、プロダクトへの思いやプライベートの話などを語り合う宴会が夜遅くまで続きました。</p>
<h1 id="まとめ">まとめ</h1>
<p>いかがだったでしょうか?メドレー開発本部のキックオフ合宿の様子が少しでも伝われば幸いです。</p>
<p>アクティビティやバーベキューを通じてより親密に、プレゼンを通じて認識の共有ができた良い合宿になったと思います。</p>
<p>なお、今回の宿泊先は<a href="https://www.c-ban.com/cottage.php">大人数でも対応可能なコテージが用意され</a>、また Wi-Fi などの通信設備も整い、受付の方の対応も丁寧で、プライベートでも利用したくなる素敵な宿でした。</p>
<p>開発合宿等の大人数での宿を探している方にご参考になれば幸いです。</p>
<h1 id="最後に">最後に</h1>
<p>最後になりましたが、今回の合宿の企画運営をリードしてくれた新居さんありがとうございました!</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180208/20180208180347.jpg" alt="20180208180347.jpg">
<p>来年またキックオフ合宿したいですね!(CTO が撮影してくれたので、CTO 不在の集合写真)</p>
<p>メドレーでは、チームで新しい医療体験を提供するプロダクトを一緒に創るディレクターやエンジニア、デザイナーを募集しています。</p>
<p>ご興味ある方は、まずはお気軽に話を聞きにお越しください!</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 自分たちにも優しい”デザインとは?https://developer.medley.jp/entry/2018/01/31/161100https://developer.medley.jp/entry/2018/01/31/161100こんにちは。最近白髪が目立つようになりました。最初は蛍光灯の反射かな?とおもっていましたが実はそうじゃないらしいイケメン担当デザイナーの小山です。
私が担当しているジョブメドレーではサービスの改善をいくつも重ねますがその結果、全体の統一感が...Wed, 31 Jan 2018 07:11:00 GMT<p>こんにちは。最近白髪が目立つようになりました。最初は蛍光灯の反射かな?とおもっていましたが実はそうじゃないらしいイケメン担当デザイナーの小山です。</p>
<p>私が担当しているジョブメドレーではサービスの改善をいくつも重ねますがその結果、全体の統一感が薄くなりデザインの改善をすることがあります。</p>
<p>こういった場合、もちろんユーザーの使いやすさを前提にリニューアルを考えますが、<strong>自分たち(開発者)にも使いやすいデザイン</strong>はどういう姿か追いながら取り組むように心がけるようにしてみることにしました。</p>
<p>この試みの背景にはサービスの<strong>プロダクト基本理念</strong>が関係しています。このエントリでは、先般行ったモバイル向けの求職者画面のデザインリニューアルを通して、プロダクト基本理念に基づいた「自分たちにも使いやすいデザイン」を実現した話をご紹介いたします。</p>
<h1 id="開発工数を増大させるデザイン">開発工数を増大させるデザイン</h1>
<p>まず始めにジョブメドレーの求職者画面についてです。これは医療従事者向けの求人を探しているユーザー『求職者』のための画面で、求人の絞り込む検索、求人の詳細情報、職種の情報を発信するブログ、サービスのコンセプト紹介など多彩な情報で構成されています。</p>
<p>メドレーが運営しているサービスの中で最も歴史があり、求職者の希望に見合った求人をスマートに探すことができるように、リニューアルや機能拡充を繰り返しおこなってきました。おかげさまで毎年安定的な成長をとげています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180130/20180130202329.png" alt="20180130202329.png">
<span style="color: #999999">求職者画面の一部</span>
<p>ただその一方で、リニューアルに対応しきれなかったページも数多くあります。未対応のページは時期を改めて対応するはずでしたが、開発リソースの事情で全て対応できている状態ではありません。</p>
<p>そうした未対応がリニューアルごとに積み重なり、数世代分のデザインパーツが並行運用され、開発の作業効率がなかなか向上できない状況でした。</p>
<p>たとえばボタンやアイコンが同じであったとしても挙動や意味付けにズレが生じていたり、使われているはずがないデザインパーツが全く別の使われ方で存在していたり、全体の把握が追いついていない状況が所々ありました。</p>
<p>これは求職者にとって使い勝手が複雑になる上に、似たようなデザインパーツが増殖されたり誤った使い方をすることで、デザインのルールが曖昧になりエンジニアの作業工数が肥大化していきます。もちろんデザイナーの工数もです。</p>
<p>今回のデザインリニューアルの発端は、ユーザーのために更に使いやすさの改善と、用途が曖昧なデザインパーツの並行運用がもたらす開発上の問題を解決したいということもありました。</p>
<h1 id="プロダクト基本理念に立ち返る">プロダクト基本理念に立ち返る</h1>
<p>問題を解決するためにどういう方向性が良いか検討するため、まずプロダクト理念まで立ち返ってみました。</p>
<p>ジョブメドレーのプロダクト理念は、各部署が業務を行うための行動指針のようなものです。
プロダクトを一定の品質を保つため、求職者の求職活動を支えるキャリアサポート、事業所の効果的な求人掲載を提案するカスタマーサクセスやクリエイションチーム、これから求人を掲載したい事業所をサポートするセールスチーム、そして開発チームがこのプロダクト理念をもとに動いています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180130/20180130202400.jpg" alt="20180130202400.jpg">
<p>求人を探すユーザー『求職者』と求人を出すユーザー『事業者』の双方が満足のいく意思決定をするためのこれら3つの理念を核としています。また、その理念を通して実現する<a href="https://job-medley.com/mission/">サービスのミッション</a>の達成に向かってメンバーは業務を行なっています。デザインパーツもこの理念をもとに制作されています。</p>
<p>ただ前述の通り、求職者画面が理念を守れる万全な状況にあるかというと、そうではありませんでした。必ずしも完璧なデザインが世の中にないように、ジョブメドレーももっと良くしていけるポイントはデザイン面で数多くあり、今回のリニューアルでは、そうした課題解決を目指しました。</p>
<p>さらに進めるにあたり、長期的にプロダクト理念に基づいた開発・デザインが行えるよう「求職者と開発の両方に働きかけられる環境」の整備を行うことにしました。</p>
<h2 id="求職活動だけに集中できる環境">求職活動だけに集中できる環境</h2>
<p>満足のいく意思決定を行う環境は、情報を読むことに悩むよう環境ではなくて、読んだ情報をじっくり考えられる環境だと私は考えています。</p>
<p>これまでのリニューアルで生まれた数世代ぶんの負債のために、字が読みにくいだとか、ボタン押したらどうなるかわからないだとか、そういった本質的ではない部分でつまづかない環境を作りました。</p>
<h2 id="デザイナーがいなくても開発できる環境">デザイナーがいなくても開発できる環境</h2>
<p>いくら見栄えがよくユーザーにとって使い勝手が良かったとしても、それを運用するために開発のコストがかかるのであれば良い UI とは言えません。</p>
<p>使い勝手を維持するために開発を逼迫させ本来コストをかけるべきところに注力できずに別の部分の使い勝手を落とし、結果的にユーザーの不利益や不信感に繋がるかもしれないからです。これは極端な考えかもしれませんが、起こる可能性としては低くはないはずです。</p>
<p>それを解決する方法の1つとしてプロジェクトの関係者が把握できる内容にまでデザインを単純化させることなのではと私は考えています。デザイナーしか把握できないものではなく、むしろデザイナーが関わらなくても把握できるものを目指しました。</p>
<h1 id="エンジニアだけで作れるデザイン">エンジニアだけで作れるデザイン</h1>
<h2 id="扱いやすいデザインとその意図">扱いやすいデザインとその意図</h2>
<p>今回できたデザインは「普通」です。なるべく特殊はことは避け、デザイナー以外でもわかるデザインを目指しました。またデザインの意図もできる限りシンプルにまとめることに注力しました。</p>
<p>理路整然とした意図でも、それが複雑だとつくった本人しか扱えないものになります。なので聞きなれないワードや表現は控え、真に守らなくてはいけないことを抽出しました。またコンポーネントも限界まで削ぎ落とし最適化しました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180130/20180130202419.jpg" alt="20180130202419.jpg">
<span style="color: #999999">デザインの意図を明文化</span>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180130/20180130202442.jpg" alt="20180130202442.jpg">
<span style="color: #999999">把握しやすい数に色を減らす 左がリニューアル前:右がリニューアル後</span>
<p>この過程でプロジェクトでうれしい場面がありました。それはエンジニアさんから新しいデザインがあがったこと。</p>
<p>デザインパーツが足りず追加制作していたところ、既にエンジニアさんがデザイントーンを参考に足りないパーツを製作してくれていました。しかも私のデザインよりも良いもので。。。複雑な心境になりましたが、それぐらいデザインが扱いやすくなっているということでもあるのかなと<del>自分に言い聞かせました</del>ポジティブに考えるようにしました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20180130/20180130202502.jpg" alt="20180130202502.jpg">
<span style="color: #999999">左:私のデザイン 右:エンジニアのデザイン どう見ても左より右が周囲と喧嘩してない。左はしゃしゃり出る</span>
<p>今回のプロジェクトは UI を綺麗にすることだけじゃなく、それ自体を関係者が扱いやすいものに改善する目的でもあったので、この出来事は当初考えていたこと以上の結果で良かったと思っています。</p>
<p>この要因は単純にルールが簡単で覚えやすかったことなのかもしれません。そうした覚えやすいデザインは学習コストが軽く、まわりめぐってユーザーにも扱いやすいものなると考えています。</p>
<h2 id="デザインを維持できるコンポーネントの運用">デザインを維持できるコンポーネントの運用</h2>
<p>昨今ではデザインシステムやスタイルガイドの普及により、デザインパーツ、コンポーネントなどを運用することはさほど特別なことではないと思います。</p>
<p>今回のリニューアルでは整備した環境を維持するためにスタンダードな運用にも着手します。こうした取り組みは以前にも行なっていたものの開発チームに明文化されていなかったので、改善を繰りかえし動かしていきたいと思います。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回のリニューアルはバラバラだったデザイントーンに統一感を持たせることでユーザーに使いやすさを担保する、その過程で自分たちにも使いやすいデザインを目指しました。</p>
<p>一般的にはリニューアルではユーザーを中心したものが多いかもしれません。今回はその中に自分たち(開発者)も加味して考え、サービスのミッションの達成のためのプロダクト基本理念を支えられる環境作りを目指しました。</p>
<p>デザインの役割は物事を綺麗にするだけではなく、理念のような根っこの部分に立ち返れる機会や根っこそのものを具現化させる役割を担っていると考えています。とはいえ今回はその第一歩。今後は更に運用方法やコンテンツ設計や導線設計に磨きをかける必要があります。</p>
<p>また進捗があり次第ご報告できればと思います。ここまで読んでいただきありがとうございました。</p>
<h1 id="最後に">最後に</h1>
<p>メドレーでは、医療介護の求人サイト「ジョブメドレー」、医師たちがつくるオンライン医療事典「MEDLEY」、口コミで探せる介護施設の検索サイト「介護のほんね」、オンライン診療アプリ「CLINICS」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- オンライン診療アプリ「CLINICS」の開発で重視している 3 つの習慣https://developer.medley.jp/entry/2018/01/10/120500https://developer.medley.jp/entry/2018/01/10/120500こんにちは、開発本部の横尾です。
これまで介護施設の口コミサイト「介護のほんね」の立ち上げや医療介護の人材採用システム「ジョブメドレー」のディレクター・コンテンツ編集などを経験し、現在はオンライン診療アプリ「CLINICS(クリニクス)」の...Wed, 10 Jan 2018 03:05:00 GMT<p>こんにちは、開発本部の横尾です。</p>
<p>これまで介護施設の口コミサイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」の立ち上げや医療介護の人材採用システム「<a href="https://job-medley.com/">ジョブメドレー</a>」のディレクター・コンテンツ編集などを経験し、現在はオンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS(クリニクス)</a>」のディレクターをしています。</p>
<p>CLINICS では、慎重さとスピード感を両立して開発できるよう、3 つの習慣を取り入れています。半年ほど実施してみて手法としてまとまってきましたので、ブログでも紹介させていただきます。</p>
<h1 id="背景">背景</h1>
<p>CLINICS は、スマートフォンや PC からビデオチャットでかかりつけ医の診察を受けられるオンライン診療アプリです。医療機関向けにはブラウザで予約管理や問診、診察、会計を行うことができる機能も提供しています。CLINICS を開発する際は、医療に関するプロダクトゆえに特に意識しなければいけない点がいくつかあります。</p>
<p>一つは人の命に関わるということ。オンライン診療は症状の落ち着いている方が対象となりますが、少しの使いにくさやミスも誰かの生命・生活に何らか影響するかもしれない緊張感が常にあります。</p>
<p>もう一つは、老若男女幅広く使われていること。病院にかかったことのない人はほとんどいないように、さまざまな人が CLINICS を利用しているので、どんな人でも不安なくスムーズに使えることが重要になってきます。</p>
<p>このような背景から、CLINICS では<strong>慎重にゴールを設定し、丁寧に検証しながら開発を進める必要があります</strong>。一方で、あまりに<strong>慎重・丁寧すぎても何もできなかったり迷走したりしてスピード感を失いかねません</strong>。そこで、両者のバランスをうまく取り開発を推進するために次の 3 つを行っています。</p>
<h1 id="clinics-開発をうまく進める-3-つの習慣">CLINICS 開発をうまく進める 3 つの習慣</h1>
<h2 id="1-プロダクトプリンシプルを共有する">1. プロダクトプリンシプルを共有する</h2>
<p>プロダクトプリンシプルは、<strong>サービスとしてのやること・やらないことを簡潔に言語化</strong>したものです。</p>
<p>例えば CLINICS のプロダクトプリンシプルには「1UU の重み、活用率 100%を目指すこだわり:一人の人の命を預かっているという使命感を持ち、一人でも多くの人が使え、今まで使えていた人が使えなくなることが一人でもないようにする」というフレーズがあります。</p>
<p>前述の通り、CLINICS は人の命に関わるプロダクトであり老若男女幅広く使われています。そのためあえてターゲットを絞らず、すべての人が使えるプロダクトを目指していることをこのフレーズで共有しています。これにより、施策検討段階では一部の人にとってとても使いやすいが使いこなせない人がいる UI と、ほぼすべての人がそこそこ使える UI があった時、私たちは迷わず後者を選択でき、すぐに実装に取り掛かることができます。</p>
<p>プロダクトプリンシプルの作成にあたっては、日頃寄せられる患者の声・医療機関の声をよく聞き、医療の成り立ちや医療業界の構造なども深いところまで学習するようにしました。患者にとって、医療機関にとって、そして社会にとってのあるべきプロダクト像を理念として映し出すようにしています。</p>
<h2 id="2-年間四半期ごとのロードマップを策定する">2. 年間・四半期ごとのロードマップを策定する</h2>
<p>ロードマップは、<strong>いつ頃どんな課題に取り組むのかを概ね年間・四半期ごとに定めたもの</strong>です。こちらは比較的多くの開発現場で取り入れられている方法ではないでしょうか。</p>
<p>ロードマップをチームで意識するようにすると、先々に開発・リリースする機能を一人ひとりが理解でき、先回りして技術調査を進めたり関連する施策の効果分析の優先度を上げたりといったことができます。また、営業チームなど社内のほかの部署から開発提案があった際にはロードマップを見ながら今後の見通しを案内できるようになり、 他チームのメンバーからの信頼を得ながら開発に専念することができます。</p>
<p>ロードマップを策定する際は、プロダクトプリンシプルを開発内容として落とし込みつつ、事業として伸ばすべき数値や直近で解決すべきお問い合わせなども考慮するようにしています。また、四半期ごとに KPT 法を使ってロードマップを振り返り、開発の期間や体制を見直すようにしています。</p>
<h2 id="3-週次で計画し日次で進捗を追う">3. 週次で計画し、日次で進捗を追う</h2>
<p>週次の計画と日次の進捗確認には、<strong>スタンディングミーティング</strong>を行っています。</p>
<p>CLINICS では GitHub のプロジェクト機能を使ってカンバンを作成しています。任意のタイミングでロードマップで挙げた課題をだいたい 1 週間程度で区切りのつくサイズの issue に落とし込み、カンバンの backlog にセットします。毎週月曜日の午前中に週次定例を行い、その週に着手する issue を backlog から in progress に移します。火曜以降は毎日 issue ごとに進捗を確認し、終わったものを done に移していきます。</p>
<p>このように週次・日次で計画と進捗確認を細かく繰り返すことで、一つひとつの issue をスピーディに進めるリズムができます。ミーティング時点で一つの issue も進んでいないということがないようにしようという意識が働き、個々人のパフォーマンスもアップします。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回は、開発推進のために CLINICS で取り入れている習慣をご紹介しました。</p>
<p>この 3 つの習慣は、<strong>どれか 1 つのみを行うのでもだめで、3 つセットで実施することが今の CLINICS 開発のカギ</strong>になっています。プロダクトプリンシプルだけでは理想論のように思えたことも、ロードマップや計画になるとより具体的になって着手しやすくなり、また、計画で示しきれない改善の方向性はプロダクトプリンシプルに立ち返ることでお互いの認識をそろえやすくなります。</p>
<p>そうすれば不必要なルールの制定や社内コミュニケーションから解放され、日々やるべきことに集中でき、<strong>結果的により早くより優れたプロダクトをユーザーに届けることができる</strong>と考えています。</p>
<p>メドレーでは、こうした様々な手法を取り入れながら新しい医療体験を提供するプロダクトを、一緒に創るディレクターやエンジニア、デザイナーを募集しています。</p>
<p>ご興味ある方は、まずはお気軽に話を聞きにお越しください!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- SkyWay UG Tokyo #1 に参加してきましたhttps://developer.medley.jp/entry/2017/12/22/143307https://developer.medley.jp/entry/2017/12/22/143307こんにちは、開発本部 平木です。2017/12/04(月)に SkyWay UG Tokyo #1 という勉強会がありました。弊社から開発本部の宍戸がセッション枠で発表させていただきましたので、イベントレポートをお送りします。
はじめに
弊...Fri, 22 Dec 2017 05:33:07 GMT<p>こんにちは、開発本部 平木です。2017/12/04(月)に <a href="https://skyway.connpass.com/event/71316/">SkyWay UG Tokyo #1</a> という勉強会がありました。弊社から開発本部の宍戸がセッション枠で発表させていただきましたので、イベントレポートをお送りします。</p>
<h1 id="はじめに">はじめに</h1>
<p>弊社の運営サービスの 1 つである <a href="https://clinics.medley.life/">オンライン診察アプリ「CLINICS」</a> では運用初期の頃から医療機関と患者さんとの診察に SkyWay を使ったビデオチャットを導入しており、現在も Web / iOS / Android の各プラットフォームで SkyWay が稼動しています。
そのようなご縁もあり今回の勉強会では、CLINICS で SkyWay をどのように利用しているかをお話させていただきました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20171221/20171221191518.jpg" alt="20171221191518.jpg">
<h1 id="セッション">セッション</h1>
<p>それでは、各セッションの流れなどをご紹介していきたいと思います。</p>
<h2 id="はじめに-1">はじめに</h2>
<p>勉強会の冒頭、UG のキックオフと参加者の自己紹介タイムがありました。</p>
<h3 id="ug-キックオフ">UG キックオフ</h3>
<p>NTT コミュニケーションズの仲さんから、SkyWay UG をなぜ作るのか?というキックオフから会がスタートしました。</p>
<p>SkyWay の大きなミッションとして <strong>リアルタイムコミュニケーションを身近にする</strong> というものがあり、そのためには <strong>開発者にサービス自体がフレンドリーであるべき</strong> であるという理念があるそうです。</p>
<p>開発者フレンドリーであるための一環として <strong>開発者コミュニティの運用をしていく</strong> ということになりこの SkyWay UG Tokyo が発足されたという経緯が説明されました。</p>
<p>確かにこのような形で使っているサービスの UG が開催されると、サービスの開発者の方々や参加している他のユーザさん達と気軽に交流できるなというのを今回参加して実感しました。</p>
<p>SkyWay UG は東京だけではなく <a href="https://skyway.connpass.com/event/74875/">関西</a> や <a href="https://skyway.connpass.com/event/74270/">福岡</a> でも続々と発足するとのことで、他の開発者サービスと同じように盛り上がっていくのではないでしょうか。</p>
<h3 id="参加者自己紹介">参加者自己紹介</h3>
<p>キックオフが終わったところで、参加者の自己紹介タイムとなりました。</p>
<p>30 人近くの参加者が順々に自己紹介していくという形だったのですが、個人的には珍しいなと思うのと同時に、懇親会で何をやっている方なのか?というのが分かりつつ話をすることができたという良さがありました。(参加者として自己紹介する勉強会は初めてでした!)</p>
<p>参加者の感想としては</p>
<ul>
<li>自分達と同じ Web 業界の方がメイン…というわけでもなく、組み込みなどを仕事にしている方なども 1/3 くらいの割合でいたのが印象的だった</li>
<li>実際にサービスやプロダクトに SkyWay を使っているという参加者の方が多かった
<ul>
<li>とはいえ使ってないが、興味があって参加したという方も</li>
</ul>
</li>
<li>中には弊社の CLINICS に興味があると言っていただけたりもした</li>
</ul>
<p>という感じでした。ハードウェアに SkyWay を組み込んで使うということをやっている方も多く、改めて幅広い開発者に受け入れられてるプラットフォームだと感じました。</p>
<h2 id="skype-から-skyway-へ">Skype から SkyWay へ</h2>
<p>最初のセッションは会場を提供されていた <a href="https://www.rarejob.com/">レアジョブ</a> の Inoue さんからの発表でした。</p>
<p>Inoue さんは現在は主にフロントエンド開発をされているそうで、このセッションも JavaScript SDK の知見がいろいろされた有意義なものでした。</p>
<p>レアジョブさんでは創業時から Skype を使ってサービスを運用していたそうなのですが、サードパーティに依存しすぎるとビジネスとして危ういということで、SkyWay への切り換えをしていっているそうです。</p>
<p>SkyWay は 9 月からトライアル版から正式版を提供されていますが、まだなかなか情報が少ないということで、新 JavaScript SDK で開発していくなかで得た Tips が発表されました。</p>
<p>実サービスを運用してる方ならではの、 <strong>ビデオ・マイクのミュート</strong> や <strong>ビデオチャット中のページ離脱防止策</strong> などの対応などは実用的で役立つものでした。</p>
<p>Inoue さんもこの後の LT で発表された仲さんもおっしゃっていましたが、SkyWay というか WebRTC のデバッグには <a href="chrome://webrtc-internls">chrome://webrtc-internls</a> が重要だと改めて思った次第です。</p>
<h2 id="オンライン診察アプリの作り方">オンライン診察アプリの作り方</h2>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/d16004ecfa7c41349fc892e693effe8d" title="オンライン診察アプリの作り方 / clinics-skyway-ug #1" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 420px;" data-ratio="1.3333333333333333"></iframe>
<p>弊社の宍戸の発表です。</p>
<p>まずはメドレーと運営しているプロダクト紹介から始まり、そもそも CLINICS で実現したかったビデオチャットはどのような条件があったか?SkyWay を選んだ理由、SkyWay と Firebase Realtime Database を併用したオンライン診察の実装内容などをご紹介しました。</p>
<p>駆け足でのご紹介にはなりましたが、実際に商用サービスでの事例ということもあり、おかげさまで好評でした。</p>
<p>スライド中でも紹介した SkyWay と Firebase を使った通話制御は他のプロダクトでも使ってることが多いらしく、相性的にもお勧めなのではないでしょうか。</p>
<h2 id="lt">LT</h2>
<p>セッション枠が終わり LT となったのですが、この LT が SkyWay の中の方達が担当するという豪華なものでした。</p>
<h3 id="webrtc-で統計情報を収集する">WebRTC で統計情報を収集する</h3>
<div class="remark-link-card-plus__container">
<a href="https://qiita.com/yusuke84/items/dc6e5a4ed109c4631b66" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">WebRTCで統計情報を収集する - Qiita</div>
<div class="remark-link-card-plus__description">統計情報とは? WebRTCでは、基盤となるネットワーク環境や送受信されるメディアの情報を監視することが出来るようにように、統計情報のAPIが規定されています。 最新の仕様書は https://w3c.github.io/webrtc-stats/ です。 このAPIで取...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.qiita.com/assets/favicons/public/production-c620d3e403342b1022967ba5e3db1aaa.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">qiita.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-user-contents.imgix.net%2Fhttps%253A%252F%252Fcdn.qiita.com%252Fassets%252Fpublic%252Fadvent-calendar-ogp-background-7940cd1c8db80a7ec40711d90f43539e.jpg%3Fixlib%3Drb-4.0.0%26w%3D1200%26blend64%3DaHR0cHM6Ly9xaWl0YS11c2VyLXByb2ZpbGUtaW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnMzLWFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb20lMkZxaWl0YS1pbWFnZS1zdG9yZSUyRjAlMkY2NjUxJTJGNDJjYTg2Y2UwNmQ0NGRjMjZhMTAwY2Q4MjExMGQ4MWRiM2EzZjQ0NSUyRnhfbGFyZ2UucG5nJTNGMTcxODk1NDUzNz9peGxpYj1yYi00LjAuMCZhcj0xJTNBMSZmaXQ9Y3JvcCZtYXNrPWVsbGlwc2UmYmc9RkZGRkZGJmZtPXBuZzMyJnM9MmU5NzNmNjIwNTIwOGQyODlhMTZhMjkzNmM3OWQ0MjM%26blend-x%3D120%26blend-y%3D467%26blend-w%3D82%26blend-h%3D82%26blend-mode%3Dnormal%26s%3D4d7e1ce125070e08da7b33e7c4eba89c?ixlib=rb-4.0.0&w=1200&fm=jpg&mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTk2MCZoPTMyNCZ0eHQ9V2ViUlRDJUUzJTgxJUE3JUU3JUI1JUIxJUU4JUE4JTg4JUU2JTgzJTg1JUU1JUEwJUIxJUUzJTgyJTkyJUU1JThGJThFJUU5JTlCJTg2JUUzJTgxJTk5JUUzJTgyJThCJnR4dC1hbGlnbj1sZWZ0JTJDdG9wJnR4dC1jb2xvcj0lMjMzQTNDM0MmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9NTYmdHh0LXBhZD0wJnM9ZTFkZjI0NWQ3Mzk5MDMwNmFhMDk1MGMyMTJlMjk1ZmY&mark-x=120&mark-y=112&blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTgzOCZoPTU4JnR4dD0lNDB5dXN1a2U4NCZ0eHQtY29sb3I9JTIzM0EzQzNDJnR4dC1mb250PUhpcmFnaW5vJTIwU2FucyUyMFc2JnR4dC1zaXplPTM2JnR4dC1wYWQ9MCZzPTE0ZmU5YjkyZmRjNmEzNTEyODgxNjhmOGYzZWFiYjY1&blend-x=242&blend-y=480&blend-w=838&blend-h=46&blend-fit=crop&blend-crop=left%2Cbottom&blend-mode=normal&s=fa2d9ceebe7a8d5cf22e777106cf9b53" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>NTT コミュニケーションズ仲さんの LT です。 <a href="chrome://webrtc-internls">chrome://webrtc-internls</a> で表示されるような統計情報を JavaScript SDK を使って表示することができるという解説とデモでした。</p>
<p>WebRTC のデバッグや、ユーザからの問い合わせなどにすぐに使えそうな実践的な内容でした。が、現バージョンでは残念ながら、Android / iOS ではこの統計情報が取れない…とのことだったので、将来的には取れるようになると弊社でもデバックが捗るので首を長くして待ちたいと思います。</p>
<p>紹介されている<a href="https://www.callstats.io/">callstats.io</a>は知らなかったので、参考になりました。行く行くは使ってみたいですね。</p>
<h3 id="recaptcha-と-api-認証で手軽に利用できて不正利用に強いアプリを作ろう">reCAPTCHA と API 認証で手軽に利用できて不正利用に強いアプリを作ろう</h3>
<iframe src="//www.slideshare.net/slideshow/embed_code/key/bA04wVMkunLlbG" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/rotsuya/recaptchaskywayapi" title="reCAPTCHA と SkyWay の API 認証で手軽に利用できて不正利用に強いアプリを作ろう" target="_blank">reCAPTCHA と SkyWay の API 認証で手軽に利用できて不正利用に強いアプリを作ろう</a> </strong> from <strong><a href="//www.slideshare.net/rotsuya" target="_blank">Ryosuke Otsuya</a></strong> </div>
<p>NTT コミュニケーションズ大津谷さんの LT です。SkyWay では 2018/9 月に<a href="https://github.com/skyway/skyway-peer-authentication-samples">API 認証</a>機能がリリースされたため、この機能と reCAPTCHA を使い API キーの不正利用を防ぎながらログインが不要なサービスを作れるというものでした。</p>
<p>reCAPTCHA はこういう風に使えるんだ!という驚きと同時にプロトタイプなどをサッと作ったりするの大変よさそうな機能だと感じました。</p>
<h1 id="まとめ">まとめ</h1>
<p>以上、SkyWay UG Tokyo #1 のレポートでした。SkyWay を現在使っている・使いたいというような開発者には気軽に情報交換もでき大変よいイベントでした。また、SkyWay 開発陣がユーザからの意見などをすぐに吸い上げてくれるのが改めて分かりました。</p>
<p>次回の東京での開催はまだ未定なようですが、ご興味ある方はぜひいかがでしょうか。</p>
<p>弊社では、SkyWay(WebRTC)を使った開発に興味があるエンジニアさん、ビデオチャットの UI を良くしたい!と思っているデザイナーさんの募集をしています。ご応募お待ちしています。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/team/creator-story.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- Prometheus で Rails アプリケーションのパフォーマンスが簡単に見れますという発表をしましたhttps://developer.medley.jp/entry/2017/12/08/130304https://developer.medley.jp/entry/2017/12/08/130304こんにちは。エンジニアの宍戸です。先日、社内勉強会「TechLunch」にてPrometheusを使ったアプリケーションのモニタリングについて発表する機会がありましたので、その内容を少しご紹介できればと思います。
Prometheus
先日...Fri, 08 Dec 2017 04:03:04 GMT<p>こんにちは。エンジニアの宍戸です。先日、社内勉強会「TechLunch」にて<a href="https://prometheus.io/">Prometheus</a>を使ったアプリケーションのモニタリングについて発表する機会がありましたので、その内容を少しご紹介できればと思います。</p>
<h1 id="prometheus">Prometheus</h1>
<p><a href="https://prometheus.io/blog/2017/11/08/announcing-prometheus-2-0/">先日 2.0 がリリースされた</a>ばかりの統合監視ツールです。監視対象からメトリクスを取得し、その情報を<a href="https://prometheus.io/docs/visualization/grafana/">Grafana</a>と連携してグラフィカルに表示したり、<a href="https://prometheus.io/docs/alerting/alertmanager/">アラートマネージャ機能</a>を利用してメトリクスの状況に応じて通知を作成することなどが可能です。また、Prometheus は Pull 型のメトリクス収集形式をとっていますが、サービスディスカバリ用の設定を入れることで、対象のサーバ群の増減の度に作業すること無く、監視対象を管理することができるなど、現在のアプリケーション稼働環境にあった運用が可能なものとなっています。(Prometheus 自体に関する情報はかなり多くの方が記事を書かれているので、詳細はそちらにお任せしたいと思います 🙏)</p>
<p>Prometheus は前職時代にもお世話になっており、そのときには Go で書かれたサーバのモニタリングに利用していました。当時から便利に利用していたこともあり、弊社のプロダクトは Rails を利用しているため、今回は Prometheus を利用して、Rails アプリケーション自体のパフォーマンスに関するメトリクスを取得してみました。</p>
<h1 id="アプリケーションパフォーマンスのモニタリング">アプリケーションパフォーマンスのモニタリング</h1>
<p>アプリケーション自身のパフォーマンスの情報を定常的にモニタリングしておく事は、潜在的なボトルネックの発見や、アクセス状況の変化、リリース単位でのパフォーマンスの変化(改善/悪化)を早期に発見することに繋がると考えています。</p>
<p>アプリケーションのモニタリングは有料ツールを含めてかなり多くの選択肢があります。NewRelic の<a href="https://newrelic.com/application-monitoring">APM</a>は代表的なツールの一つで、実は現時点ではこちらを利用してモニタリングを行っています。また<a href="https://stackify.com/application-performance-management-tools/">その他様々なツール</a>でも同じようなインサイトを得ることができます。アプリケーションパフォーマンスのモニタリング(マネジメント)ツールについては<a href="https://stackify.com/application-performance-management-tools/">こちら</a>に良くまとまっていましたので、合わせて見ていただくと良いかもしれません。</p>
<h1 id="メトリクスについて">メトリクスについて</h1>
<p>Prometheus も様々な機能がありますが、監視対象となるサーバは<code>Exporter</code>と表現されます。この Exporter に対して、Prometheus Server がデータを取りに来る格好です。</p>
<p><a href="https://github.com/prometheus/client_ruby">https://github.com/prometheus/client_ruby</a>というライブラリが公式に提供されています。こちらにある<code>Prometheus::Middleware::Collector</code> と <code>Prometheus::Middleware::Exporter</code> という2つの Rack middleware を利用することで設定に数行追加するだけで以下に記載した情報を Prometheus Server から利用する準備が整います。</p>
<table><thead><tr><th align="left">key</th><th align="left">意味</th></tr></thead><tbody><tr><td align="left"><code>http_server_requests_total</code></td><td align="left">アプリケーションの処理したリクエスト総数</td></tr><tr><td align="left"><code>http_server_request_duration_seconds_bucket</code></td><td align="left">あるエンドポイントの要求処理時間のヒストグラム</td></tr><tr><td align="left"><code>http_server_request_duration_seconds_sum</code></td><td align="left">上記の合計値</td></tr><tr><td align="left"><code>http_server_request_duration_seconds_count</code></td><td align="left">あるエンドポイントへのリクエストの総数</td></tr><tr><td align="left"><code>http_server_exceptions_total</code></td><td align="left">アプリケーションで発生した例外の合計数</td></tr></tbody></table>
<p>ここで出て来るバケットというのは特定の範囲のデータを格納する領域のようなものです。
<a href="https://prometheus.io/docs/practices/histograms/">ヒストグラム</a>の計算などの際に利用します。</p>
<p>これらを用いて、</p>
<ul>
<li>直近 10 分のエンドポイント毎のレイテンシ
<ul>
<li><code>rate(http_server_request_duration_seconds_sum{path=~"/api.*"}[5m])</code> / <code>rate(http_server_request_duration_seconds_count{path=~"/api.*"}[5m])</code></li>
</ul>
</li>
<li>API のエンドポイント毎のステータスコードが 200 以外となったレスポンスの数
<ul>
<li><code>sum(http_server_requests_total{job="rack-example", code!~"^2..$", path=~"/api.*"} offset 10m ) by (path)</code></li>
</ul>
</li>
</ul>
<p>などを<a href="https://prometheus.io/docs/prometheus/latest/querying/basics/">PromQL</a>という Prometheus の独自クエリ言語を用いて集計することができます。デフォルトで提供されるダッシュボードで即座に確認できるのでとても便利です。</p>
<p>実際に発表した際のスライドはこちら。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/9d247d64bd2d4cca8783566abcc62c53" title="Prometheus でアプリーケーションモニタリング /prometheus" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 314px;" data-ratio="1.78343949044586"></iframe>
<h1 id="まとめ">まとめ</h1>
<p>今回は Prometheus を利用した Rails アプリケーションのモニタリングについてご紹介しました。
Prometheus がどうという話はあまりできませんでしたが、ここまで手軽に準備できるもんかというのは正直びっくりしました。他のツールを利用していない状態でアプリケーションの運用をしている場合には、特に Rails 等であれば簡単に監視を始められるので、導入を検討する価値はあるのではないかと思いました。</p>
<p>現在は比較的安定してサーバを稼働させることが出来ていると思いますが、日頃からこういったメトリクスを見つつ、今後の安定運用につなげていきたいと思います。</p>medley
- フロントエンドエンジニアが Ionic を触ってみた〜メドレー TechLunch〜https://developer.medley.jp/entry/2017/11/24/120000https://developer.medley.jp/entry/2017/11/24/120000こんにちは。開発本部の大岡です。オンライン診療アプリ「CLINICS」の開発を担当しているエンジニアです。2017 年 6 月にメドレーに転職してきて初めて気づきましたが、僕は人見知りでした。
メドレーでは、定期的に TechLunch と...Fri, 24 Nov 2017 03:00:00 GMT<p>こんにちは。開発本部の<a href="https://www.wantedly.com/companies/medley/post_articles/83820">大岡</a>です。オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」の開発を担当しているエンジニアです。2017 年 6 月にメドレーに転職してきて初めて気づきましたが、僕は人見知りでした。</p>
<p>メドレーでは、定期的に TechLunch という社内勉強会を実施しています。今回僕が担当になりましたので、その時の内容をご紹介させていただければと思います。テーマは「フロントエンドエンジニアが Ionic を触ってみた」です。</p>
<p>色々な事情を考えずに好き放題言っています。個人的にウェブアプリが好きということもありウェブアプリよりの偏ったことを書いていると思います。ご了承ください。</p>
<h1 id="なぜ-ionic-を触ろうと思ったか">なぜ Ionic を触ろうと思ったか</h1>
<p>ウェブアプリ・ネイティブアプリ(このエントリーでは iOS/Android 各プラットフォーム固有の言語を使って開発したものを指しています)それぞれにメリットデメリットはあると思いますが、ゲームのようなグラフィックごりごりのものでなければウェブアプリで十分に感じています。</p>
<p>最近では、両方に展開しているもののネイティブアプリにコストを割いてしまっているのか、モバイルのウェブアプリの UI が本当にひどかったり最適化されていないと感じる例があります。最適化もしてないし、導線はネイティブアプリに向けてるのにネイティブアプリの方が使われてるじゃん!って言われてもウェブアプリの気持ちになると「そりゃそうでしょ。。。」って感じです笑</p>
<p>とはいえ、いくらウェブアプリが好きでもネイティブアプリを作らざるをえないとなった場合に iOS/Android それぞれ作ることが適切なのかなと思ってしまいます。そこでクロスプラットフォーム開発できるものを探していると、自分のスキルセットに合いそうなものがいくつかありました。その中で今回は、Ionic を触ってみることにしました。</p>
<p>今回 TechLunch で発表したスライドは以下です。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/2e269264c45c4511b972bca1f04fda80" title="フロントエンジニアが Ionic を触ってみた /lonic" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 314px;" data-ratio="1.78343949044586"></iframe>
<p>スライドと重複してる箇所もありますが、テキストでも解説してみます。ご興味がありましたら以下もどうぞ。</p>
<h1 id="ウェブアプリとネイティブアプリ">ウェブアプリとネイティブアプリ</h1>
<p>ウェブアプリとネイティブアプリはよく比較されますが、その違いは以下のような感じかなと思います。</p>
<table><thead><tr><th>項目</th><th>ウェブアプリ</th><th>ネイティブアプリ</th></tr></thead><tbody><tr><td>動作速度</td><td>遅め</td><td>早め</td></tr><tr><td>デバイスの機能</td><td>使えるものもある</td><td>利用可</td></tr><tr><td>開発コスト</td><td>普通</td><td>多め</td></tr><tr><td>審査</td><td>無し</td><td>有り</td></tr><tr><td>インストール</td><td>不要</td><td>必要</td></tr></tbody></table>
<p>動作速度は遅めとはいえ、ゲームではなく EC サイトのようなものであれば、そこまで気にするほどではない思います(作りにもよると思いますが)。デバイスの機能に関しては Chrome なら割と使えます。開発コストはワンソースでいけるのでネイティブアプリよりは優れているかなと思います。</p>
<p>あとは何と言っても、ブラウザと URL さえあればどこからでも使えるっていうのがメリットです。最近ですとウェブアプリをネイティブアプリっぽく動かす Progressive Web Application(以下 PWA)というワードもよく目にするようになりました。PWA は動作速度の面やプッシュ通知・オフラインでも利用できたりとウェブアプリの弱点を補ってくれるのですが、PWA に必要な技術である Service Worker の実装がされていなかったりと環境により十分に恩恵を受けることができない場合があります。</p>
<p>個人的に以下のようなパターンの場合</p>
<ul>
<li>ネイティブの実装が必要</li>
<li>重要だけど機能に更新がはいることがほぼない</li>
<li>アプリを使う上で毎回必要なわけではない</li>
</ul>
<p>アプリによっては、この部分だけアプリに切り出して、ウェブアプリで必要になったら呼び出すとかでもいいのかなと思います。</p>
<p>上記のようなことをネイティブアプリとして実現できるものに Apach Cordova(以下 Cordova)があります。ウェブアプリは WebView(ネイティブアプリ内でウェブページを表示する部品みたいなもの)に表示し、ネイティブ機能はプラグインとして提供されているのでそれを JavaScript から呼び出すことが可能です。</p>
<h1 id="ionicとは"><a href="https://ionicframework.com/">Ionic</a>とは</h1>
<p>ウェブの技術を用いてネイティブアプリの開発を可能にするフレームワークです。
主な特徴は以下です。</p>
<ul>
<li>オープンソース</li>
<li>Apach Cordova 上に構築されている</li>
<li>HTML5 を用いたクロスプラットフォームなハイブッドアプリ(PWA 含む)の開発が可能</li>
<li>JavaScript のフレームワークは Angular</li>
<li>Angular を採用しているので AltJS は TypeScript</li>
<li><a href="https://ionicframework.com/docs/components">UI コンポーネント</a>がいい感じ</li>
<li>ネイティブ機能をフルで利用可</li>
</ul>
<h1 id="実際に触ってみた">実際に触ってみた</h1>
<p><a href="https://ionicframework.com/getting-started/">公式の Get started</a>にもあるようにブラウザでアプリを表示するまでは 3 ステップです。
※Node.js がインストールされていることが前提</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A9955"># 1. ionic と cordova のインストール(この時の Ionic CLI は 3.18.0)</span></span>
<span class="line"><span style="color:#DCDCAA"> $</span><span style="color:#CE9178"> npm</span><span style="color:#CE9178"> install</span><span style="color:#569CD6"> -g</span><span style="color:#CE9178"> cordova</span><span style="color:#CE9178"> ionic</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 2. アプリケーションの作成</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> ionic</span><span style="color:#CE9178"> start</span><span style="color:#D4D4D4"> [アプリ名] [テンプレート]</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 3. アプリケーション起動</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> cd</span><span style="color:#D4D4D4"> [アプリ名]</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> ionic</span><span style="color:#CE9178"> serve</span></span></code></pre>
<p><a href="https://ionicframework.com/docs/cli/starters.html">テンプレート</a>もよく見る形のものは用意されているのでこだわらなければサクッとそれっぽいものが作れます。下の画像は tabs テンプレートを使用しました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171122/20171122191851.gif" alt="20171122191851.gif">
<p>Android/iOS の開発環境が整った状態ですと以下のコマンドで、エミュレータを起動しネイティブアプリとしてインストールすることができます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A9955"># 1. 対象の OS を追加</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> ionic</span><span style="color:#CE9178"> cordova</span><span style="color:#CE9178"> platform</span><span style="color:#CE9178"> add</span><span style="color:#D4D4D4"> [ios/android]</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 2. エミュレータ起動かつインストール</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> ionic</span><span style="color:#CE9178"> cordova</span><span style="color:#CE9178"> run</span><span style="color:#D4D4D4"> [ios/android]</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 2.5 ウェブアセット更新と連動してアプリ更新させる場合</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> ionic</span><span style="color:#CE9178"> cordova</span><span style="color:#CE9178"> run</span><span style="color:#D4D4D4"> [ios/android] --livereload</span></span></code></pre>
<p>ここまでは本当に簡単です。起動時に<code>--livereload</code>オプションをつけるとウェブのアセットが更新されるとアプリ内の画面も更新されるので便利です。ネイティブの機能を利用したくなった場合も多くのプラグインが提供されているのでウェブの知識だけでもある程度のアプリは作れると思います。</p>
<p>便利そうだし簡単にアプリ作れそう!となりますが、やりたいことがプラグインで提供されていなかった場合は自分で作成しないといけません。プラグインの作成は iOS/Android それぞれで作らないといけないため知識もそれぞれ必要です。そして、このプラグインを作るのがウェブの知識だけだと割と苦戦します。</p>
<h1 id="skywayを利用してビデオチャットアプリを作ってみた"><a href="https://webrtc.ecl.ntt.com/">SkyWay</a>を利用してビデオチャットアプリを作ってみた</h1>
<p>SkyWay とは NTT コミュニケーションズが提供している WebRTC を利用したビデオチャット等ができるサービスです。今回この<a href="https://webrtc.ecl.ntt.com/">SkyWay</a>を利用して、Android のみですがビデオチャットアプリを作ってみました。</p>
<h3 id="android-プラグイン作成">Android プラグイン作成</h3>
<p>SkyWay のプラグインがなかったのでプラグインを作成します。画面のあるプラグインの作成に結構はまりました(基本的なプラグイン作成の説明は、検索すればすぐに見つかると思うので省略させていただきます)。解決して思うようには動いたのですが、これでいいのかわかりません。いい感じの解決法があったら教えてください。</p>
<p>今回は<a href="https://github.com/skyway/skyway-android-sdk/tree/master/examples">SkyWay の Android サンプルコード</a>に少し手を入れて利用させて頂きプラグインを作成してみました。ざっくりディレクトリ構成は以下のようになりました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA"> plugin-skyway</span></span>
<span class="line"><span style="color:#DCDCAA"> ├──</span><span style="color:#CE9178"> platform</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> └──</span><span style="color:#CE9178"> android</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> └──</span><span style="color:#CE9178"> AndroidStudio</span><span style="color:#CE9178"> で作成したプロジェクト</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">SkyWay</span><span style="color:#CE9178"> サンプルソース</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#DCDCAA"> └──</span><span style="color:#CE9178"> plugin</span></span>
<span class="line"><span style="color:#DCDCAA"> ├──</span><span style="color:#CE9178"> package.json</span></span>
<span class="line"><span style="color:#DCDCAA"> ├──</span><span style="color:#CE9178"> plugin.xml</span></span>
<span class="line"><span style="color:#DCDCAA"> ├──</span><span style="color:#CE9178"> src</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> ├──</span><span style="color:#CE9178"> android</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> │</span><span style="color:#CE9178"> ├──</span><span style="color:#CE9178"> cordova-plugin-skyway.gradle</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> │</span><span style="color:#CE9178"> ├──</span><span style="color:#CE9178"> MainActivity.java</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> │</span><span style="color:#CE9178"> ├──</span><span style="color:#CE9178"> PeerListDialogFragment.java</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> │</span><span style="color:#CE9178"> ├──</span><span style="color:#CE9178"> SkyWay.java</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> │</span><span style="color:#CE9178"> ├──</span><span style="color:#CE9178"> layout</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> │</span><span style="color:#CE9178"> │</span><span style="color:#CE9178"> ├──</span><span style="color:#CE9178"> activity_main.xml</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> │</span><span style="color:#CE9178"> │</span><span style="color:#CE9178"> └──</span><span style="color:#CE9178"> fragment_dialog_peerlist.xml</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> │</span><span style="color:#CE9178"> └──</span><span style="color:#CE9178"> libs</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> │</span><span style="color:#CE9178"> └──</span><span style="color:#CE9178"> skyway.aar</span></span>
<span class="line"><span style="color:#DCDCAA"> │</span><span style="color:#CE9178"> └──</span><span style="color:#CE9178"> ios</span></span>
<span class="line"><span style="color:#DCDCAA"> └──</span><span style="color:#CE9178"> www</span></span>
<span class="line"><span style="color:#DCDCAA"> └──</span><span style="color:#CE9178"> SkyWay.js</span></span></code></pre>
<p><code>plugin-skyway/platform</code>に各 OS のプラグイン用プロジェクトがある感じです。<code>plugin-skyway/plugin</code>が Ionic のプロジェクトにインストールされるディレクトリです。</p>
<p>はじめは、<code>plugin-skyway/plugin/src/android</code>に AndroidStudio で作成したプロジェクトを置いてました。しかし、Ionic のプロジェクトに作成したプラグインをスンストールすると不要なものまで含まれてしまったので、<code>plugin-skyway/platform/android</code>にプロジェクトを作成し必要なファイルのみを<code>plugin-skyway/plugin/src/android</code>にコピーすることにしました。</p>
<p>AndroidStudio でプラグインを開発する際は、一度 Ionic のプロジェクトを Android でビルドし、Ionic プロジェクト内の<code>platforms/android/CordovaLib/build/outputs/aar/CordovaLib-debug.aar</code>をプラグインのプロジェクトで取り込まないといけないようです。</p>
<p>いざ Ionic プロジェクトにインストールしてビルドすると ConstraintLayout が無いとか SkyWay の SDK のバージョンがどうとか怒られますが、Android の開発の仕組みが理解できていなかったので下記の「パッケージ R は存在しません」というエラーに時間を取られました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA"> ..</span><span style="color:#CE9178">./MainActivity.java:258:</span><span style="color:#CE9178"> エラー:</span><span style="color:#CE9178"> パッケージ</span><span style="color:#CE9178"> R</span><span style="color:#CE9178"> は存在しません</span></span>
<span class="line"><span style="color:#DCDCAA"> Canvas</span><span style="color:#CE9178"> canvas</span><span style="color:#CE9178"> =</span><span style="color:#D4D4D4"> (Canvas) findViewById(</span><span style="color:#DCDCAA">R.id.svLocalView</span><span style="color:#D4D4D4">);</span></span></code></pre>
<p>のように R.java がないと怒られます。
AndroidStudio から GUI でポチポチと画面を作ると R.java というのができいい感じに解決してくれるようなのですが、今回のように xml のみを移動させるだけだと R.java が作られません。</p>
<p>そこで<code>R.id.svLocalView</code>のような<code>R.</code>の箇所を下記のように置き換えて、やっとビルドが通るようになりました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="java"><code><span class="line"><span style="color:#DCDCAA">getResources</span><span style="color:#D4D4D4">().</span><span style="color:#DCDCAA">getIdentifier</span><span style="color:#D4D4D4">(</span><span style="color:#CE9178">"svLocalView"</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"id"</span><span style="color:#D4D4D4">, </span><span style="color:#DCDCAA">getPackageName</span><span style="color:#D4D4D4">())</span></span></code></pre>
<p>最終的に以下のようなものができました。
SkyWay ボタンをタップするとネイティブの画面が立ち上がり、Android の SkyWay SDK を利用してテレビ電話をすることができます。
クローズボタンをタップするとネイティブ画面が終了し、WebView 部分に戻ってきます。実際触ってみた感じフルネイティブアプリと違和感なく感じます。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171122/20171122192030.gif" alt="20171122192030.gif">
<p><em>※アニメーション GIF の容量が重くなったので相手側でのコード入力中の空白時間を削ったり編集した影響で左下の緑と赤の画面が飛んだりしてますが、実際は滑らかです</em></p>
<h1 id="まとめ">まとめ</h1>
<p>今回触ってみたというより指先が触れた程度ですが、全然 Ionic でもいけそうな雰囲気を感じました。普通に触ってる分にはネイティブ部分なのかウェブ部分なのかわからなかったです。ただし、銀の弾丸ではないので、実際に開発に導入する場合はメリット・デメリットをちゃんと把握する必要があります。
ウェブアプリ寄りのことを言ってはきたものの、結局はウェブだろうがネイティブだろうがどんなツールを使おうが、<strong>使ってくれる人が求めるもの</strong>を提供できればいいと思います。今後も何かあったときの引き出しのために色々なツールなど触っていこうと思います。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>12/6(水)、こんなイベント(というか飲み会)をやります。
今回のブログの話が詳しく聞きたい、医療ヘルスケア領域の開発ってどんな感じだろう、社会貢献性の高いプロダクトに関わりたい、など思っているエンジニア・デザイナーの方、ビール片手に開発の話で盛り上がりませんか?</p>
<div class="remark-link-card-plus__container">
<a href="https://www.wantedly.com/projects/170112" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">www.wantedly.com</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=www.wantedly.com" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.wantedly.com</span>
</div>
</div>
</a>
</div>
<p>その日は予定が入っているんだけど話を聞きたいという方は、こちらの「話を聞きたい」ボタンからどうぞ。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.wantedly.com/companies/medley" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">株式会社メドレーの会社情報 - Wantedly</div>
<div class="remark-link-card-plus__description">株式会社メドレーの魅力を伝えるコンテンツと、住所や代表・従業員などの会社情報です。急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。
しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.wantedly.com/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.wantedly.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://images.wantedly.com/i/YMVT7Fy?h=1440&w=1440" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 提供価値によって異なるデザインプロセスhttps://developer.medley.jp/entry/2017/11/17/120000https://developer.medley.jp/entry/2017/11/17/120000最近 PS4 のグランツーリスモスポーツをやり始めて、自宅のネット環境の遅さに気づいたデザイナーのマエダです。前回はDLS についてご紹介させていただきましたが、今回はメドレーに入社して感じた「デザインプロセスの違い」について自分なりにまと...Fri, 17 Nov 2017 03:00:00 GMT<p>最近 PS4 のグランツーリスモスポーツをやり始めて、自宅のネット環境の遅さに気づいたデザイナーの<a href="https://www.wantedly.com/companies/medley/post_articles/49119">マエダ</a>です。前回は<a href="https://developer.medley.jp/entry/2017/08/03/160000">DLS について</a>ご紹介させていただきましたが、今回はメドレーに入社して感じた「デザインプロセスの違い」について自分なりにまとめてみました。</p>
<p>あとで読みたい人向けに、エレベーターピッチ風にまとめると、</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>[ CLINICS ] というサービスは</span></span>
<span class="line"><span>[ 患者と医療機関向け ] それぞれサービスを提供しているが</span></span>
<span class="line"><span>[ 提供価値の違い ] によって</span></span>
<span class="line"><span>[ デザインの役割が異なる ] ことに気づいた</span></span>
<span class="line"><span></span></span>
<span class="line"><span>特に [ 医療機関向け ] は</span></span>
<span class="line"><span>[ UI が重要 ] となり</span></span>
<span class="line"><span>[ 伝えることを目的とした Web サイト ] とは違って</span></span>
<span class="line"><span>[ UI デザインの良し悪しがプロダクト全体の品質に関わる ] ため</span></span>
<span class="line"><span>[ 事業や技術を理解 ] した[ デザインオリエンテッド ] が求められる</span></span></code></pre>
<p>というような内容です。</p>
<h1 id="ユーザーによって異なる提供価値">ユーザーによって異なる提供価値</h1>
<p>メドレーに入社してから、オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS(クリニクス)</a>」というサービスのデザインを主に担当しているのですが、患者と医療機関側で提供しているプロダクトの内容が異なります。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171116/20171116131612.png" alt="20171116131612.png">
<ul>
<li>オンライン診療の内容を伝え、利用を促すための Web サイト(患者・医療機関双方)</li>
<li>患者がオンラインで通院を行うためのアプリ</li>
<li>医療機関側がオンラインで遠隔診療を行うためのシステム</li>
</ul>
<h2 id="サービスの入り口となる-web-サイト">サービスの入り口となる Web サイト</h2>
<p>Web ページの役割としては、オンライン診療というものがどういったもので、CLINICS を利用するとどういう課題解決につながるのか、というサービスの特徴を患者と医療機関双方に「<strong>伝えるためのデザイン</strong>」が必要となります。</p>
<p>デザインするにあたって注力すべきポイントは、装飾やイメージ画像などシンボリックなデザインとストーリー性のある導線設計をすることで、視覚的に特徴を伝わりやすくし、またコンバージョンポイントへ誘導するため、ボタン等は目立たせるなど、適切に情報を伝え、行動を促すためのデザインが重要になります。</p>
<h2 id="患者がオンライン診療を行うためのアプリ">患者がオンライン診療を行うためのアプリ</h2>
<p>アプリは患者がオンライン診療をするための「病院検索」→「予約」→「問診」→「診察」→「決済」の機能をシームレスに繋げるためストレスのかからないユーザー体験を提供することが重要です。</p>
<p>そもそもオンライン診療のメリットとして、待ち時間の軽減や、落ち着いた環境で診察ができるなどが挙げられるため、そこに至るまでのユーザー体験を台無しにしてしまう UI では元も子もなくなってしまいます。</p>
<p>アプリでは「伝えるデザイン」よりも「<strong>機能的なデザイン</strong>」が必要になりますが、ユーザーの行動を途切れさせないよう、不必要な要素や導線を極力排除して、非常にシンプルな UI 設計をおこなっており、機能自体を主張しないデザインを心がけています。前回このブログでもとりあげた<a href="https://developer.medley.jp/entry/2017/08/03/160000">DLS</a>も、そういった設計思想のもと開発を進めていたからこそ実現できたと思っています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20171117/20171117113501.png" alt="20171117113501.png">
<h2 id="医療機関向けの遠隔診療システム">医療機関向けの遠隔診療システム</h2>
<p>医療機関側に提供しているシステムは、オンライン診療を行うためのツールです。
Web サイトのように「伝えるデザイン」やコンバージョン重視ではなく、「<strong>より機能的に使いやすいデザイン</strong>」が重要になります。
このような画面を Bootstrap のような UI テンプレートそのままの見た目で構築してしまうと、技術的に素晴らしいものができたとしても、使い勝手が悪く貧弱な機能と見られかねないため、<strong>デザイナーが管理画面の UI 設計に携わることは、ビジネス的にも非常に重要な要素</strong>です。</p>
<p>特に CLINICS の医療機関向けのシステムは、実際にオンラインで患者の診察を行うツールのため、医療機関側が診療行為をつつがなく終えられるような UI 設計が重要です。</p>
<p>MVP 的な手法で、とりあえずリリースして検証を重ねていくというスタンスがとれないため、リリースするまでに機能的に不備がないか、医療従事者が迷わず正しく使える UI 設計になっているかなど、社内や実際の医療機関のテストを繰り返し、試行錯誤を経てリリースするという、プロダクトデザインをしている感覚になり「伝えるデザイン」とは違った思考でデザインに取り組んでいます。</p>
<h1 id="機能重視なサービスこそ直感的にシンプルな-ui-が重要">機能重視なサービスこそ、直感的にシンプルな UI が重要</h1>
<p>このように CLINICS という 1 つのサービスを構成する要素の中でも、ターゲットユーザーや提供価値の違いによって、デザインアプローチや重要視する視点が異なります。</p>
<p>たとえば、自分も業務でよく利用するプロトタイピングツールの inVision もログイン前は、利用シーンやより魅力的なサービスであるということを伝えるためのデザインをしており、ログイン後は直感的にわかりやすいシンプルな UI 設計で、ログイン前後でサービスの UI が異なります。</p>
<p>inVision も機能は豊富ですが、プロトタイピングを行う上で非常にシンプルな操作性を提供しており、無駄な説明や導線がなくても直感的に使える UI は、継続して利用できる安心感にもつながり、見習うべき UI だなと思っています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171116/20171116131754.png" alt="20171116131754.png">
<p>(inVision の画面の違い)</p>
<p>CLINICS の医療機関向けシステムも受付管理やスケジュール、オンライン診療を行う機能など複数の機能をひとつのサービスとして提供しているため、UI 的に複雑になりかねません。複雑な機能をまとめ、画面上にシンプルに落とし込めるかどうかというのを吟味し、作っては壊し、作っては壊しを繰り返しします。</p>
<p>これならいける!とおもった UI も、機能面での見落としなどがあったりすると導線に矛盾が生じたり、シンプルに表現したつもりが、逆に使い勝手の悪い UI になってしまったり。UI を考えるというのは、感性に訴えるデザインとは違ったよりロジカルな思考が必要で、デザインしながら四苦八苦して、ぶつぶつ独り言を言うことが多くなります w</p>
<h1 id="デザインオリエンテッドなプロダクト開発">デザインオリエンテッドなプロダクト開発</h1>
<p>Web サービスであればいかに目標としているコンバージョン率を高めるかどうか、分析〜調査〜開発というサイクルをベースとした運用になりますが、特に<strong>医療機関向けのシステムの場合は、ムダな機能や使いづらい UI だとサービス的に不安要素を抱かせる要因にもなり、ビジネスの成功可否に直結します</strong>。</p>
<p>そのため、リリースまでに UI を磨けるだけ磨き、実際に医療機関の現場に出向いてどういうフローで診察を行っているのかなどヒアリングしたり、出来上がった機能を試してもらうなどし、十分に検討した上でリリースしています。こうしたデザインを重視した開発が行える点はメドレーだからこその開発体制かもしれません。</p>
<p>このような<strong>デザインオリエンテッドなプロダクト開発を行う上で重要なポイントは、事業理解とエンジニアとの密な連携</strong>です。表面的なデザインではなく、実際に使われる利用シーンを想像しつつも、体験することが難しい医療サービスだからこそ、前提の知識やヒアリング、ユーザーテストなどが重要となりますし、機能的な部分に関してはエンジニアと仕様について議論したり、ユースケースを踏まえてどのような UI に落とし込むべきかを考えながらデザインに落とし込んでいきます。インタラクティブな表現など inVision でも表現しきれない細かい動きや導線などは、実装時にエンジニアに伝わりやすいようフロントエンド部分のコーディングを自ら行うことでコードを通じてエンジニアとコミュニケーションが取りやすくもなるので、フロントエンドも把握しておくことは重要です。</p>
<h1 id="まとめ">まとめ</h1>
<p>個人的には「伝えるデザイン」と「機能的なデザイン」で、明確に思考をわけて考えてデザインしてきたわけではなかったのですが、提供すべき価値の違いによって<strong>左脳と右脳それぞれ使い分けてデザインしている</strong>かもしれないということに気付かされました。これは以前デザイナーの小山がブログで書いた<a href="https://developer.medley.jp/entry/2017/09/14/132031">システム 1(自動的に直感で動く”早い思考”)とシステム 2(手動で論理的に動く”遅い思考”)</a>が自分の中で振り子のように行き来してるだろうなと感じたので、興味のある方は「思考とデザインスキル」も読んでもらうとわかりやすいです。</p>
<div class="remark-link-card-plus__container">
<a href="https://developer.medley.jp/entry/2017/09/14/132031" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">思考とデザインスキル 〜メドレー TechLunch〜 | MEDLEY Developer Portal</div>
<div class="remark-link-card-plus__description">はじめまして!最近みるみる太りだしてはいるものの、まだ機は熟していないとダイエットの時期をぐっと堪えている開発本部イケメン担当のデザイナー・小山です。
メドレーでは TechLunch という社内勉強会を実施しているのですが、前田に引き続き...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://developer.medley.jp/icon.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">developer.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://developer.medley.jp/_astro/2017_09_14.Cm4Q2hKU.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="最後に">最後に</h1>
<p><a href="https://www.wantedly.com/companies/medley/post_articles/62275">ふだんビールばっかり呑んで</a>適当な人とレッテルを貼られているマエダですが、今回は真面目なことを書いてみましたがいかがでしたでしょうか。このブログを書いてて正直疲れたので、システム 2 が働いてるに違いないと思います。こんな私と一緒に仕事がしたい、呑みたいというデザイナーやエンジニアさん。応募お待ちしております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/team/creator-story.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- 「フロントエンド開発に再入門する」タスクフォースの進め方https://developer.medley.jp/entry/2017/11/09/171047https://developer.medley.jp/entry/2017/11/09/171047こんにちは。開発本部の宍戸です。
メドレーでは定期的に、テーマに沿って組織の技術的な底上げを行うための機会(タスクフォースと呼んでいます)を行っています。そのタスクフォースの1つとして先日、フロントエンド開発力のベースアップを目的としたタス...Thu, 09 Nov 2017 08:10:47 GMT<p>こんにちは。開発本部の<a href="https://www.wantedly.com/companies/medley/post_articles/63206">宍戸</a>です。
メドレーでは定期的に、テーマに沿って組織の技術的な底上げを行うための機会(<a href="https://developer.medley.jp/entry/2017/10/20/122000">タスクフォース</a>と呼んでいます)を行っています。そのタスクフォースの1つとして先日、フロントエンド開発力のベースアップを目的としたタスクフォースを行いました。本記事では、その取組みについてご紹介したいと思います。</p>
<h1 id="背景">背景</h1>
<p>メドレーには現在 20 人弱のエンジニアが在籍しており、その約半数がサーバーサイド出身者です。また普段の開発においては、一つの機能をフロントからサーバーサイドまで一貫して一人が担当するケースが多くあります。サーバーサイド出身者のフロントエンド開発のスキルセットには多少ばらつきはあるものの、普段の開発業務ではレビュー等でそれぞれサポートしつつ開発を行っています。</p>
<p>しかし、フロントエンドの基礎的な部分や最新の流れまで聞かれると不安なメンバーも少なくありません。フロントエンド出身のメンバーが主導し、改めて基礎や最新情報に関して整理・フォローを行うことで、組織全体のフロントエンドの開発力を高めていきたいと考えました。</p>
<h1 id="タスクフォースの目的">タスクフォースの目的</h1>
<p>今回のタスクフォースは『フロントエンドの基本や最近のトレンドに関して学ぶ』ことで『(フロントエンド開発における)技術選定、設計、実装ができる基礎を身につける』、そしてこれらをもとに『新規のプロジェクトで設計段階から自走できるようになる』ことを目的としました。</p>
<p>その中でも特にここ数年、変化の流れが早かった JavaScript を中心にトピックを選定しました。</p>
<p>参加者は、これまでサーバーサイド開発を中心に行ってきたメンバー数名です。背景でも触れたとおり、業務経験はそれぞれある前提でのスタートであったため、基礎をみっちりというよりは、基礎的な話から最近の話題までを一通り確認しながら、各自の持っている知識の整理と土台を固めることで、今後の設計や技術選定を行う際の指針を得ることを目的としました。</p>
<h1 id="進め方">進め方</h1>
<p>今回のタスクフォースは期間を 3 ヶ月と定め、毎週 1 時間程度集まって行いました。</p>
<p>毎週、事前に講師陣が選んだ資料を読んでおき、当日は講師陣が参加者の不明点、疑問点に対してフォローアップするという形式で進めました。その他には、社内のプロダクトでの利用事例なども交えながらテーマに関する質問会のような形で進むことが多かったです。</p>
<p>また毎回のタスクフォースの時間のあとに、参加者がその日の内容をまとめた議事録形式の資料を作成し、参加者全員と共有することで、その日に話された内容や、それぞれの理解度を再度確認するようにしました。</p>
<h1 id="内容">内容</h1>
<p>およその流れは上記の通りですが、約 3 ヶ月でどのようなテーマに触れてきたのか、一部をご紹介します。</p>
<h2 id="フロントエンドの基礎">フロントエンドの基礎</h2>
<p>序盤はこちらの資料を利用させていただきながら進めました。</p>
<ul>
<li><a href="https://www.html5rocks.com/ja/tutorials/internals/howbrowserswork/">ブラウザの仕組み: 最新ウェブブラウザの内部構造</a></li>
<li><a href="https://speakerdeck.com/rtechkouhou/css?slide=22">Asg Boot Camp HTML/CSS</a></li>
<li><a href="https://javascript.info/intro">An Introduction to JavaScript</a></li>
</ul>
<p>ブラウザの仕組み、HTML/CSS の基本的な話のおさらい、JavaScript の話に関連して、これまでに出てきた<a href="https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS">AltJS</a>についてもいくつか特徴や何故流行ったのかなどについて読み込みました。</p>
<p>この中でも、<a href="https://www.html5rocks.com/ja/tutorials/internals/howbrowserswork/">ブラウザの仕組み: 最新ウェブブラウザの内部構造</a>で DOM の解析、レンダリング、レイアウトといったブラウザ内部で具体的に何が行われているのかといった話はここで確認できてよかったという声が多くありました。</p>
<h2 id="javascript基礎es2015-以降">JavaScript(基礎〜ES2015 以降)</h2>
<p>JavaScript の話題への導入編としてこちらを資料として読み込みました。</p>
<ul>
<li><a href="https://github.com/getify/You-Dont-Know-JS/blob/master/up%20%26%20going/ch2.md">You Don’t Know JS: Up & Going</a></li>
<li><a href="https://bonsaiden.github.io/JavaScript-Garden/ja/">JavaScript Garden</a></li>
</ul>
<p>このパートでは JavaScript の基礎は、あえて ES3〜5 をベースにすることで JavaScript と他言語との違い・特徴を再確認していきました。上記の内容を踏まえ、今では使わない書き方などについてはその理由も確認しながら進めていきました。</p>
<p>その後は<a href="https://babeljs.io/learn-es2015/">Learn ES2015 · Babel</a>を参照しながら、Promise, Class などは普段の開発でも当たり前のように利用しているものの、ES2015 以降での書き方は ES5 だとどのようになっていたかもここで同時に学習していきました。(<a href="https://www.typescriptlang.org/play/">Playground ・ TypeScript</a>で、その雰囲気を見ることが出来ます)</p>
<img width="1378" alt="Screen Shot 2017-10-27 at 16.14.49.png (424.4 kB)" src="https://img.esa.io/uploads/production/attachments/6223/2017/10/27/2208/d03f2bd8-4c1c-4b61-add7-aa873d6e2788.png">
<h2 id="モダン-javascript">モダン JavaScript</h2>
<p>これまでの知識を踏まえ、モダンな JavaScript の利用(実装)例として、<a href="https://github.com/jser/jser.github.io">jser.github.io</a>と<a href="https://github.com/developit/preact-www/">preact-www</a>のソースを読んでいきました。</p>
<p>これまでの JavaScript の言語自体に関する内容から、ライブラリを利用したコンポーネント間でのデータフローや、コンポーネントのライフサイクルに関する部分まで確認していきました。また初見のプロジェクトであればどのあたりから目を通していくか、などコード全体の読み方についても講師陣からアドバイスがありました。また時折出てくる DOM API に「なんだっけこれ・・・?」などとなりつつもコードを紐解いていくことで改めてフロントエンド JavaScript の特徴的な部分を垣間見ることができたように思います。</p>
<p>また最後に、現在開発に利用されているツール群について、<a href="https://qiita.com/azu/items/2921f62127b8d3a1aa03">Qiita:JavaScript フレームワーク選定の議論</a>を参考に確認しました。それぞれのツールがどのような背景で使われている(あるいはいない)のかなども合わせて確認をしました。</p>
<p>ここまでのテーマを振り返りつつ、JavaScript が言語としてどのように変化してきたかを考えた時、webpack や TypeScript がなぜ使われるのか、ようやく腹に落ちたように思います。また上記資料も、どのようなケースで何を選択するのかや、アプリケーションの寿命とライブラリやツールの寿命といった運用フェーズで理解していく必要のある事項にも触れられており、非常に参考になりました。</p>
<h1 id="実際にやってみて">実際にやってみて</h1>
<p>今回のタスクフォースでは、3 ヶ月という期間の中で、1 ヶ月半の時点と、全体終了後に振り返りを実施しました。
その中で、参加メンバーからは</p>
<ul>
<li>業務においては必要な部分に絞った調査で終わってしまったり、周りのコードを参考にしたりすることでなんとなくできた気になってしまっていたが、今回改めて基礎的な知識を学習する、復習する時間としてできてよかった。</li>
<li>断片的でまばらな知識しか持っていなかった部分が資料を読み込むことで理解が深まった</li>
<li>数年前にバズってあとで読まない「あとで読む」ブクマ記事をきちんと読めてよかった</li>
<li>フロントエンド経験の長い講師陣の生きた知見(ツラミなども含めて)を聞くことができたのはありがたかった</li>
</ul>
<p>などの感想が出ました。
私自身も参加していましたが、個人的には基礎部分を改めて固めることができた機会だったように感じています。
また、普段は新しいツールなどの情報のキャッチアップへの意識が向きがちですが、今回のタスクフォースで言語・ツールの変遷を一度通して見ることができたことで、新しく現れる技術がどういった課題を解決するものなのか、これまでに似たツールがあったのかどうかなどを調べる指針が得られたことはよかったのかなと思っています。</p>
<p>一方で、今回のタスクフォースの中では「コードを書く」時間は作りませんでした。これについては振り返りの中でも話題となりましたが、それについては<a href="https://github.com/tastejs/todomvc">TodoMVC</a>などを参考に、自分で手を動かしてみることが必要だろうと今後の課題として挙げられました。このあたりは実務以外での個々の頑張りが必要になってくる部分かと思います。</p>
<h1 id="まとめ">まとめ</h1>
<p>「サーバーサイドエンジニアのフロントエンド開発力の底上げ」をテーマとしたタスクフォースについてご紹介しました。こういった形で、同じようなスキルセットのメンバーが集まり、お互いにわからない部分について話をしたり、経験豊富なメンバーから、個人の経験を含めて話を聞くというのは、改めて非常に貴重な機会だったように思います。</p>
<p>こういった取り組みを繰り返していくことで、個々で尖った部分はもちろん伸ばしつつ、全体の底上げを行いながら、よりよいプロダクト開発に繋げられればと考えています。</p>medley
- CircleCI2.0 に移行してビルド実行速度を向上https://developer.medley.jp/entry/2017/10/24/174000https://developer.medley.jp/entry/2017/10/24/174000こんにちは。開発本部の稲本です。医療介護の求人サイト「ジョブメドレー」の開発を担当しているエンジニアです。
最近ジョブメドレーでは CircleCI2.0 への移行を行いました。移行の方法はもちろん、その際に調べたこと、CircleCI の...Tue, 24 Oct 2017 08:40:00 GMT<p>こんにちは。開発本部の<a href="https://www.wantedly.com/companies/medley/post_articles/61935">稲本</a>です。医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」の開発を担当しているエンジニアです。</p>
<p>最近ジョブメドレーでは CircleCI2.0 への移行を行いました。移行の方法はもちろん、その際に調べたこと、CircleCI の新機能を利用してどうだったかなどを書いていきたいと思います。</p>
<h1 id="課題感">課題感</h1>
<p>弊社では、全プロダクト(<a href="https://clinics.medley.life/">CLINICS</a>、<a href="https://medley.life/">MEDLEY</a> 、<a href="https://www.kaigonohonne.com/">介護のほんね</a>、<a href="https://job-medley.com/">ジョブメドレー</a>)で CircleCI を利用しています。</p>
<p>ジョブメドレーでは CI によるテスト実行に 37 分前後掛かっていました(コンテナを 2 つ利用した実行時間です)。
さらに、開発メンバーが増えて来たこともあり、CI のリソースが足りなくなり開発効率が落ちかねない状況でした。</p>
<p>まぁよくある話ですよね。</p>
<p>コンテナを増やすというのも解決策の一つとしてはあるのですが、速度の改善に期待が出来ると評判も良かったので CirclecCI2.0 へ移行しました。</p>
<h1 id="circleci20-への移行メリット">CircleCI2.0 への移行メリット</h1>
<p>基本的には速度の改善に期待が出来る、というのが大きなメリットではありますが、公式では以下のように記載されています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171024/20171024100735.png" alt="20171024100735.png">
<p>抜粋ですが大きな特徴としては以下の点でしょうか。</p>
<ul>
<li>First-class Docker Support: Docker のネイティブサポートと Docker レイヤーキャッシュの導入</li>
<li>Workflows: ビルド、テスト、デプロイをジョブとして柔軟に管理できるようになった</li>
<li>Advanced Caching: キャッシュの保存とリストアをイメージ、ソースコード、依存関係に対して行うことができるようになった。</li>
</ul>
<p>この辺りの機能を活用し CI の速度改善へ繋げてみたいと思います。</p>
<h1 id="ジョブメドレーのアプリケーション構成">ジョブメドレーのアプリケーション構成</h1>
<p>移行の前提として、ジョブメドレーのアプリケーション構成について記載します。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171024/20171024100814.png" alt="20171024100814.png">
<p>フロントエンドのビルドを yarn+webpack で行い、生成したアセットを public/assets へ吐き出し、manifest ファイルのパスを rails の helper 経由で取得し読み込んでいます。(Rails4.2.x)</p>
<p>このような構成のアプリケーションを CirlceCI2.0 でビルド、テスト、デプロイ出来るようにしていきます。</p>
<h1 id="configyml-の全体像">config.yml の全体像</h1>
<p>今回、作成した config.yml はこのような形になりました。</p>
<p>ざっくりは</p>
<ul>
<li>build: bundle install, yarn install</li>
<li>code_analyze: rubocop, brakeman, scss-lint</li>
<li>rspec</li>
<li>deploy: capistrano</li>
</ul>
<p>を行っており、ブランチによってどのジョブを実行するのかを workflows を利用して使い分けています。</p>
<p>詳しい解説は以降、記載していきます。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="yaml"><code><span class="line"><span style="color:#569CD6">defaults</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">&</span><span style="color:#4EC9B0">defaults</span></span>
<span class="line"><span style="color:#569CD6"> working_directory</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">~/job-medley</span></span>
<span class="line"><span style="color:#569CD6"> docker</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">image</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">circleci/ruby:2.4.2-node-browsers</span></span>
<span class="line"><span style="color:#569CD6"> environment</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> TZ</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/usr/share/zoneinfo/Asia/Tokyo</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">image</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">circleci/mysql:x.x.x</span></span>
<span class="line"><span style="color:#569CD6"> environment</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> TZ</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/usr/share/zoneinfo/Asia/Tokyo</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">image</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">redis:x.x.x</span></span>
<span class="line"><span style="color:#569CD6"> environment</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> TZ</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/usr/share/zoneinfo/Asia/Tokyo</span></span>
<span class="line"><span style="color:#569CD6">version</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">2</span></span>
<span class="line"><span style="color:#569CD6">jobs</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> build</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> <<</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">*</span><span style="color:#9CDCFE">defaults</span></span>
<span class="line"><span style="color:#569CD6"> steps</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">checkout</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">restore_cache</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> key</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">job-medley-app-{{ checksum "Gemfile.lock" }}</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">bundle install</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">bundle install --jobs=4 --path=vendor/bundle</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">save_cache</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> key</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">job-medley-app-{{ checksum "Gemfile.lock" }}</span></span>
<span class="line"><span style="color:#569CD6"> paths</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">vendor/bundle</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">restore_cache</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> key</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">job-medley-yarn-{{ checksum "yarn.lock" }}</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">Yarn install</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">|</span></span>
<span class="line"><span style="color:#CE9178"> echo "Node $(node -v)"</span></span>
<span class="line"><span style="color:#CE9178"> echo "Yarn v$(yarn --version)"</span></span>
<span class="line"><span style="color:#CE9178"> yarn config set cache-folder ./yarn_cache</span></span>
<span class="line"><span style="color:#CE9178"> echo "Yarn v$(yarn cache dir)"</span></span>
<span class="line"><span style="color:#CE9178"> yarn install</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">save_cache</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> key</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">job-medley-yarn-{{ checksum "yarn.lock" }}</span></span>
<span class="line"><span style="color:#569CD6"> paths</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">node_modules</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">yarn_cache</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">persist_to_workspace</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> root</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">~/job-medley</span></span>
<span class="line"><span style="color:#569CD6"> paths</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">./*</span></span>
<span class="line"><span style="color:#569CD6"> code_analyze</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> <<</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">*</span><span style="color:#9CDCFE">defaults</span></span>
<span class="line"><span style="color:#569CD6"> steps</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">attach_workspace</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> at</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">~/job-medley</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">run rubocop</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">bundle exec rubocop</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">run brakeman</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">bundle exec brakeman -qz</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">run scss-lint</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">bundle exec scss-lint</span></span>
<span class="line"><span style="color:#569CD6"> rspec</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> parallelism</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">2</span></span>
<span class="line"><span style="color:#569CD6"> <<</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">*</span><span style="color:#9CDCFE">defaults</span></span>
<span class="line"><span style="color:#569CD6"> steps</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">attach_workspace</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> at</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">~/job-medley</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">restore_cache</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> key</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">job-medley-elasticsearch</span></span>
<span class="line"><span style="color:#6A9955"> # rspec で es のコマンドを一部実行しているため、primary container 側へ install</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">Elasticsearch install</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">|</span></span>
<span class="line"><span style="color:#CE9178"> wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-x.x.x.tar.gz && \</span></span>
<span class="line"><span style="color:#CE9178"> tar -xvf elasticsearch-x.x.x.tar.gz && \</span></span>
<span class="line"><span style="color:#CE9178"> if [ -z "`elasticsearch-x.x.x/bin/plugin -l | grep analysis-kuromoji`" ]; then \</span></span>
<span class="line"><span style="color:#CE9178"> elasticsearch-x.x.x/bin/plugin -install elasticsearch/elasticsearch-analysis-kuromoji/x.x.x; fi</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">save_cache</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> key</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">job-medley-elasticsearch</span></span>
<span class="line"><span style="color:#569CD6"> paths</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">./elasticsearch-x.x.x</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">database create</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">bundle exec rake db:create</span></span>
<span class="line"><span style="color:#569CD6"> environment</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> RAILS_ENV</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">test</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">run test</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">|</span></span>
<span class="line"><span style="color:#CE9178"> circleci tests glob 'spec/**/*_spec.*' \</span></span>
<span class="line"><span style="color:#CE9178"> | circleci tests split --split-by=timings --timings-type=filename \</span></span>
<span class="line"><span style="color:#CE9178"> | tee -a /dev/stderr \</span></span>
<span class="line"><span style="color:#CE9178"> | xargs bundle exec rspec \</span></span>
<span class="line"><span style="color:#CE9178"> --profile 100 \</span></span>
<span class="line"><span style="color:#CE9178"> --format RspecJunitFormatter \</span></span>
<span class="line"><span style="color:#CE9178"> --out rspec/rspec.xml \</span></span>
<span class="line"><span style="color:#CE9178"> --format progress</span></span>
<span class="line"><span style="color:#569CD6"> environment</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> RAILS_ENV</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">test</span></span>
<span class="line"><span style="color:#569CD6"> TEST_CLUSTER_COMMAND</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">elasticsearch-x.x.x/bin/elasticsearch</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">store_artifacts</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> path</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">artifacts/</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">store_test_results</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> path</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">rspec/</span></span>
<span class="line"><span style="color:#569CD6"> deploy_qa</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> <<</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">*</span><span style="color:#9CDCFE">defaults</span></span>
<span class="line"><span style="color:#569CD6"> steps</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">attach_workspace</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> at</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">~/job-medley</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">run deploy</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">|</span></span>
<span class="line"><span style="color:#CE9178"> sh scripts/init_deploy.sh</span></span>
<span class="line"><span style="color:#CE9178"> BRANCH="${CIRCLE_BRANCH}" bundle exec cap develop deploy deploy:restart</span></span>
<span class="line"><span style="color:#569CD6"> deploy_only</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> <<</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">*</span><span style="color:#9CDCFE">defaults</span></span>
<span class="line"><span style="color:#569CD6"> steps</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">attach_workspace</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> at</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">~/job-medley</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">run deploy</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">|</span></span>
<span class="line"><span style="color:#CE9178"> BRANCH="${CIRCLE_BRANCH}" bundle exec cap production deploy deploy:restart</span></span>
<span class="line"><span style="color:#569CD6">workflows</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> version</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">2</span></span>
<span class="line"><span style="color:#569CD6"> workflows</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> jobs</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">build</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">code_analyze</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> requires</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">build</span></span>
<span class="line"><span style="color:#569CD6"> filters</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> branches</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> ignore</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/^sandbox.*|^master$|^staging$/</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">rspec</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> requires</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">build</span></span>
<span class="line"><span style="color:#569CD6"> filters</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> branches</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> ignore</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/^sandbox.*|^master$|^staging$/</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">deploy_qa</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> requires</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">code_analyze</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">rspec</span></span>
<span class="line"><span style="color:#569CD6"> filters</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> branches</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> only</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">develop</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">deploy_only</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> requires</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">build</span></span>
<span class="line"><span style="color:#569CD6"> filters</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> branches</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> only</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/^sandbox.*|^master$|^staging$/</span></span></code></pre>
<h1 id="dockerimage-の選定">DockerImage の選定</h1>
<p>元々、Docker を使わずに CI を回していましたが、CircleCI2.0 へ移行するに辺り Docker への利用に切り替えました。</p>
<p>※<a href="https://circleci.com/docs/ja/2.0/executor-types/#overview">Specifying Container Images</a></p>
<p>DockerImage は DockerHub へ登録されている<a href="https://hub.docker.com/r/circleci/">CircleCI</a><a href="https://hub.docker.com/r/circleci/">公式のも</a><a href="https://hub.docker.com/r/circleci/">の</a>を利用しました。</p>
<p>アプリケーションの一部で React を使用しており、フロントのビルドは yarn+webpack を利用しています。その為、以下の image を選択しました。</p>
<ul>
<li>circleci/ruby:2.4.2-node-browsers
<ul>
<li>node のインストールと、E2E のテストに必要なソフトウェアがインストールされています。</li>
<li>※詳細は<a href="https://qiita.com/inuscript/items/09d15ee52b1657872f80">こちらの記事</a>を参考にさせていただきました。</li>
</ul>
</li>
</ul>
<p>その他、現在利用している MySQL のバージョン、ElasticCacheRedis のバージョンと合わせた image を選択しました。</p>
<p>注意点としては、複数の DockerImage を利用する場合、一つ目に指定した image が primary として扱われます。</p>
<p>以下の例ですと、Ruby の image を最初に指定し、MySQL、Redis の image を指定していますが、MySQL コマンド自体は Ruby の image に含まれていないため、Ruby コマンドを実行できても MySQL コマンドを実行することは出来ません。</p>
<p>※詳細は<a href="https://circleci.com/docs/ja/2.0/configuration-reference/#docker">こちら</a>に記載されています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="yaml"><code><span class="line"><span style="color:#569CD6">docker</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">image</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">circleci/ruby:2.4.2-node-browsers</span></span>
<span class="line"><span style="color:#569CD6"> environment</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> TZ</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/usr/share/zoneinfo/Asia/Tokyo</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">image</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">circleci/mysql:x.x.x</span></span>
<span class="line"><span style="color:#569CD6"> environment</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> TZ</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/usr/share/zoneinfo/Asia/Tokyo</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">image</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">redis:x.x.x</span></span>
<span class="line"><span style="color:#569CD6"> environment</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> TZ</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/usr/share/zoneinfo/Asia/Tokyo</span></span></code></pre>
<p>また、用意されている image をカスタマイズする必要がある場合は、Docker でカスタム image を作り、public で良ければ<a href="https://hub.docker.com/">Docker Hub</a>へ登録、private が良ければ<a href="https://aws.amazon.com/jp/ecr/">Amazon EC2 Container Registry</a>へ登録しておくことで呼び出すことも可能になっています。</p>
<p>※Using Custom-Built Docker Images
<a href="https://circleci.com/docs/ja/2.0/custom-images/">https://circleci.com/docs/ja/2.0/custom-images/</a></p>
<p>※Using Private Images
<a href="https://circleci.com/docs/ja/2.0/private-images/">https://circleci.com/docs/ja/2.0/private-images/</a></p>
<h1 id="build-の設定と-cache">build の設定と cache</h1>
<p>CI で実行するアプリケーションの build に関してです。</p>
<p><code>checkout</code>で github からコードを checkout し、その後の定義でアプリケーションのインストール、キャッシュ保存、キャッシュの展開を行っています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="yaml"><code><span class="line"><span style="color:#569CD6">steps</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">checkout</span></span>
<span class="line"><span style="color:#6A9955"> # Rails application setup</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">restore_cache</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> key</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">job-medley-app-{{ checksum "Gemfile.lock" }}</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">bundle install</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">bundle install --jobs=4 --path=vendor/bundle</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">save_cache</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> key</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">job-medley-app-{{ checksum "Gemfile.lock" }}</span></span>
<span class="line"><span style="color:#569CD6"> paths</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">vendor/bundle</span></span></code></pre>
<ul>
<li><code>save_cache:</code> key に Gemfile.lock を指定することでキャッシュキーとして扱い、paths に設定したパスをキャッシュするようにしています。</li>
<li><code>restore_cache:</code> key と一致するキャッシュがあれば、save_cache 時に指定したパスを展開し直しています。</li>
<li><code>run:</code> こちらは rails のアプリケーションインストールしているだけです。</li>
</ul>
<p>CircleCI1.0 よりもキャッシュ管理を柔軟に行えることがわかります。</p>
<h1 id="circleci-コマンドによる-rspec-の並列実行">circleci コマンドによる Rspec の並列実行</h1>
<p>rspec によるテストの実行に関してです。
<a href="https://circleci.com/docs/ja/2.0/parallelism-faster-jobs/#supported-globbing-patterns">circleci コマンド</a>を利用することでテストの並列実行を効率的に行うことが出来るます。</p>
<p>今回は <code>--split-by=timings --timings-type=filename</code> のオプションを指定し、ファイル名ベースでの分割でテストを実行します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="yaml"><code><span class="line"><span style="color:#D4D4D4">- </span><span style="color:#569CD6">run</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> name</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">run test</span></span>
<span class="line"><span style="color:#569CD6"> command</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">|</span></span>
<span class="line"><span style="color:#CE9178"> circleci tests glob 'spec/**/*_spec.*' \</span></span>
<span class="line"><span style="color:#CE9178"> | circleci tests split --split-by=timings --timings-type=filename \</span></span>
<span class="line"><span style="color:#CE9178"> | tee -a /dev/stderr \</span></span>
<span class="line"><span style="color:#CE9178"> | xargs bundle exec rspec \</span></span>
<span class="line"><span style="color:#CE9178"> --profile 100 \</span></span>
<span class="line"><span style="color:#CE9178"> --format RspecJunitFormatter \</span></span>
<span class="line"><span style="color:#CE9178"> --out rspec/rspec.xml \</span></span>
<span class="line"><span style="color:#CE9178"> --format progress</span></span>
<span class="line"><span style="color:#569CD6"> environment</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> RAILS_ENV</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">test</span></span>
<span class="line"><span style="color:#569CD6"> TEST_CLUSTER_COMMAND</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">elasticsearch-x.x.x/bin/elasticsearch</span></span>
<span class="line"><span style="color:#D4D4D4">- </span><span style="color:#569CD6">store_artifacts</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> path</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">artifacts/</span></span>
<span class="line"><span style="color:#D4D4D4">- </span><span style="color:#569CD6">store_test_results</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> path</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">rspec/</span></span></code></pre>
<ul>
<li><code>store_artifacts:</code> 以前からもある機能ですが、テスト結果の成果物を保存するパスになります。</li>
<li><code>store_test_results:</code> こちらはテストの実行結果を保存しておくことで、コンテナ間で rspec の実行時間にばらつきが出ないよう、対象のファイルを最適に振り分けてくれるようなのですが、workflows を利用するとサポートされないようです。</li>
</ul>
<p>※参考: <a href="https://circleci.com/docs/ja/2.0/configuration-reference/#store_test_results">https://circleci.com/docs/ja/2.0/configuration-reference/#store_test_results</a></p>
<p>このような形で Artifacts が保存されています。
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171024/20171024101642.png" alt="20171024101642.png"></p>
<p>また、artifacts には coverage と capybara の screenshot などを保存しています</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="yaml"><code><span class="line"><span style="color:#D4D4D4">- </span><span style="color:#CE9178">simple_cov</span></span>
<span class="line"><span style="color:#CE9178"> if ENV['CI']</span></span>
<span class="line"><span style="color:#CE9178"> SimpleCov.coverage_dir File.join(ENV['CIRCLE_WORKING_DIRECTORY'], 'artifacts', 'coverage')</span></span>
<span class="line"><span style="color:#CE9178"> SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[</span></span>
<span class="line"><span style="color:#CE9178"> SimpleCov::Formatter::HTMLFormatter</span></span>
<span class="line"><span style="color:#D4D4D4"> ]</span></span>
<span class="line"><span style="color:#CE9178"> SimpleCov.start do</span></span>
<span class="line"><span style="color:#CE9178"> add_filter '/vendor/'</span></span>
<span class="line"><span style="color:#CE9178"> add_filter '/spec/'</span></span>
<span class="line"><span style="color:#CE9178"> add_filter '/config/'</span></span>
<span class="line"><span style="color:#CE9178"> add_filter '/db/'</span></span>
<span class="line"><span style="color:#CE9178"> end</span></span>
<span class="line"><span style="color:#CE9178"> end</span></span></code></pre>
<ul>
<li><code>capybara</code>
<ul>
<li><code>Capybara.save_and_open_page_path = File.join(ENV['CIRCLE_WORKING_DIRECTORY'], 'artifacts', 'capybara') if ENV['CI']</code></li>
</ul>
</li>
</ul>
<p>※CircleCI2.0 から CI の環境変数が変わっています。詳細は以下のリンクへ記載されています。</p>
<div class="remark-link-card-plus__container">
<a href="https://circleci.com/docs/ja/2.0/env-vars/#circleci-environment-variable-descriptions" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">circleci.com</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=circleci.com" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">circleci.com</span>
</div>
</div>
</a>
</div>
<h1 id="workflows-の設定">Workflows の設定</h1>
<p>CircleCI2.0 から Workflows の利用が可能になりました。</p>
<div class="remark-link-card-plus__container">
<a href="https://circleci.com/docs/ja/2.0/workflows/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">circleci.com</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=circleci.com" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">circleci.com</span>
</div>
</div>
</a>
</div>
<p>コンテナ毎に分割しジョブを実行することで更なる並列実行の効率化、及び、ジョブ間の依存関係まで設定できるようです。
ジョブメドレーではコードの静的解析に少し実行時間が掛かっていることから、多少の改善を図れると考え<del>単純に使ってみたかった</del>こちらの機能を利用してみました。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="yaml"><code><span class="line"><span style="color:#569CD6">workflows</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> version</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">2</span></span>
<span class="line"><span style="color:#569CD6"> workflows</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> jobs</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">build</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">code_analyze</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> requires</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">build</span></span>
<span class="line"><span style="color:#569CD6"> filters</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> branches</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> ignore</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/^sandbox.*|^master$|^staging$/</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">rspec</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> requires</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">build</span></span>
<span class="line"><span style="color:#569CD6"> filters</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> branches</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> ignore</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/^sandbox.*|^master$|^staging$/</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">deploy_qa</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> requires</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">code_analyze</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">rspec</span></span>
<span class="line"><span style="color:#569CD6"> filters</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> branches</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> only</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">develop</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">deploy_only</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> requires</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">build</span></span>
<span class="line"><span style="color:#569CD6"> filters</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> branches</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> only</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">/^sandbox.*|^master$|^staging$/</span></span></code></pre>
<ul>
<li><code>build:</code> 各ジョブで実行前に行っておく処理を定義</li>
<li><code>code_analyze:</code> rubocop、brakeman、scss-lint などの静的解析処理を定義</li>
<li><code>rspec:</code> アプリケーションのテストを定義</li>
<li><code>deploy:</code> デプロイに関する処理を定義</li>
</ul>
<p>ジョブメドレーでは、ブランチ管理に Git-flow を採用していますが、それとは別に sandbox というテスト環境を用意し運用しています。
develop ブランチでコード解析やテストをクリアしたコードだけ、master へ反映し、master ではテストフェーズなしに deploy する構成を取っています。
極力、CI のリソースを節約するように各ブランチごとで実行する処理を分けています。</p>
<p>各ブランチの運用は以下の通りです。</p>
<ul>
<li><code>feature:</code>
<ul>
<li>コード解析、テスト実行</li>
</ul>
</li>
<li><code>sandbox:</code>
<ul>
<li>デプロイのみ実行(一時レビュー用ブランチ)</li>
</ul>
</li>
<li><code>develop:</code>
<ul>
<li>コード解析、テスト実行、デプロイ実行</li>
</ul>
</li>
<li><code>master:</code>
<ul>
<li>デプロイのみ実行</li>
</ul>
</li>
</ul>
<p>上記の例では、</p>
<ul>
<li>code_analyze:
<ul>
<li>sandbox、master、staging ブランチ以外は実行</li>
<li>requires で依存関係を指定し、build が正常に終了しなければ実行されないようになっています。</li>
</ul>
</li>
<li>rspec:
<ul>
<li>sandbox、master、staging ブランチ以外は実行</li>
<li>requires で依存関係を指定し、build が正常に終了しなければ実行されないようになっています。</li>
</ul>
</li>
<li>deploy_qa:
<ul>
<li>develop ブランチでのみ実行</li>
<li>requires で依存関係を指定し、code_analyze、rspec が正常に終了しなければ実行されないようになっています。</li>
</ul>
</li>
<li>deploy_only:
<ul>
<li>sandbox、master、staging ブランチのみ利用するジョブ</li>
<li>requires で依存関係を指定し、build が正常に終了しなければ実行されないようになっています。</li>
</ul>
</li>
</ul>
<p>どのような workflow が出来あがるのか、以下に例を示します。</p>
<h3 id="feature-ブランチの例">feature ブランチの例:</h3>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171024/20171024101757.png" alt="20171024101757.png">
<h3 id="develop-ブランチの例">develop ブランチの例:</h3>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171024/20171024101818.png" alt="20171024101818.png">
<h3 id="mastersandbox-ブランチの例">master、sandbox ブランチの例:</h3>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171024/20171024101852.png" alt="20171024101852.png">
<p>今回の例だとブランチ毎に workflow を変えているため、ignore、only の書き方で意図せず振り分けされないように考慮は必要ですが、柔軟に workflow を作れることがわかると思います。(やり過ぎると読み解くのが大変になりそうですね)</p>
<p>Workflows に関しては<a href="https://circleci.com/docs/ja/2.0/workflows/">こちら</a>に色々なパターンの組み方が記載されているので、こちらを読むとより理解が深まると思います。</p>
<h1 id="ジョブ間でのデータ共有">ジョブ間でのデータ共有</h1>
<p>ジョブを分けてビルドする=何回もアプリケーションの初期化が必要なんじゃないか?
と当然疑問に思う点ではありますが、それに対する解決策も用意されています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="yaml"><code><span class="line"><span style="color:#569CD6">build</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> steps</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#CE9178"> ===省略===</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">persist_to_workspace</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> root</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">~/job-medley</span></span>
<span class="line"><span style="color:#569CD6"> paths</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#CE9178">./*</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">deploy_qa</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> <<</span><span style="color:#D4D4D4">: </span><span style="color:#C586C0">*</span><span style="color:#9CDCFE">defaults</span></span>
<span class="line"><span style="color:#569CD6"> steps</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#D4D4D4"> - </span><span style="color:#569CD6">attach_workspace</span><span style="color:#D4D4D4">:</span></span>
<span class="line"><span style="color:#569CD6"> at</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">~/job-medley</span></span></code></pre>
<ul>
<li><code>persist_to_workspace:</code> 指定したパスにあるデータを一時的に保管してくれます</li>
<li><code>attach_workspace:</code> 保管済みのデータを展開してくれます</li>
</ul>
<p>この機能により、ビルドプロセスで生成したものを各ジョブで実行するコンテナへ渡すことが出来ます。
ただし、そもそもビルドプロセスでキャッシュを入れていることもあり、これ自体の効果は殆どありませんでした。
コンパイル済みのデータを受け渡す際には効果を発揮しそうですね。(<a href="https://circleci.com/docs/ja/2.0/workflows/#using-workspaces-to-share-data-among-jobs">公式でも</a>そのような利用を想定していそうです)</p>
<h1 id="改善結果">改善結果</h1>
<p>肝心の速度改善結果です。結果は以下の通りになりました。</p>
<h2 id="改善前-circleci10">改善前: CircleCI1.0</h2>
<p>rspec の実行時間: 26:59</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171025/20171025162233.png" alt="20171025162233.png">
<p>以下は、CircleCI2.0 へ移行しただけの結果です。このケースでは workflows を利用していません。</p>
<h2 id="改善後-circleci20">改善後: CircleCI2.0</h2>
<p>rspec の実行時間: 19:40</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171024/20171024102115.png" alt="20171024102115.png">
<p>CircleCI1.0 から CircleCI2.0 へ移行することにより、<strong>約 12 分程</strong>テストの実行時間を短縮することが出来ました。
Workflows など特に利用していない、かつ、ビルドフェーズの実行時間も関係しないため、CircleCI2.0 を利用するだけで単純にテスト実行速度の向上を見込めることがわかると思います。</p>
<p>続いて Workflows を利用した結果です。</p>
<h2 id="改善後-circleci20-with-workflows">改善後: CircleCI2.0 with Workflows</h2>
<p>rspec の実行時間: 21:14</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20171024/20171024102148.png" alt="20171024102148.png">
<p>結果は、Workflows を利用しないケースと、利用したケースでは、Workflows を利用したほうが rspec の実行時間は長くなってしまいましたが、build-code-analyze-rspec の実行に掛かったトータルの時間に差は見られませんでした。</p>
<p>これは、「circleci コマンドによる Rspec の並列実行」のセクションへも記載した通り、store_test_results がサポートとされないことにより、コンテナ間での分散が最適化されていない為です。</p>
<blockquote>
<p>コンテナ間で rspec の実行時間にばらつきが出ないよう、対象のファイルを最適に振り分けてくれるようなのですが、Workflows を利用するとサポートされないようです。</p>
</blockquote>
<p>実行時間にばらつきが出てしまい、code_analyze のジョブを分散することで見込んでいた改善時間(約 3 分)とばらつきにより発生したテスト実行時間のロス( (20:01 - 14:57) / 2 = 2:32 )が大体同じであるため、トータルでの実行時間に差が出ない結果となりました。</p>
<p>ばらつきを出さない方法や、ジョブの分け方については今後も工夫してみたいと思います。
また、フロントエンドのテストをもう少し厚くしていきたいと考えているので、フロントエンドのテスト、サーバサイドのテストを Workflows を上手く使いながら分散していければ良いのかなとも思っています。</p>
<h1 id="さいごに">さいごに</h1>
<p>移行に際して、<a href="https://circleci.com/docs/ja/2.0/migrating-from-1-2/">CircleCI2.0 の移行ガイド</a>を読みながら進めていましたが、基本的な記法の変更、timezone、environment の定義方法の変更、variable の変更などが多々有り、ドキュメントを結構読み込まないとどこに何が定義できるのか把握できませんでした。</p>
<p>また、Workflows の組み立てなど<a href="https://github.com/CircleCI-Public/circleci-demo-workflows/tree/try-schedule-workflow">公式に良いサンプル</a>は沢山あるのですが、依存関係の定義を色々試すのに苦労した気がします。</p>
<p>※素直に小規模なアプリを用意して、<a href="https://circleci.com/docs/ja/2.0/local-jobs/">ローカルで circleci を実行</a>してみた方が効率良く進められたかもしれません。</p>
<p>※ただ、<a href="https://circleci.com/docs/ja/2.0/">公式のドキュメント</a>や<a href="https://discuss.circleci.com/">CommunityForum</a>をしっかり読めば余すことなく情報は合ったので非常に助かりました。</p>
<p>CircleCI2.0 でどのような事が出来るのか、それはどのように行えるのか。
この記事がその概観をつかむ助けになれば良いなと思っています。</p>
<h1 id="参考リンク">参考リンク</h1>
<p>CircleCI2.0 へ移行するにあたり、以下の記事を参考にさせていただきました。ありがとうございます。</p>
<div class="remark-link-card-plus__container">
<a href="https://qiita.com/inuscript/items/09d15ee52b1657872f80" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">circleci/ruby:2.4.1-node-browsersって何入ってるのか知りたかった - Qiita</div>
<div class="remark-link-card-plus__description">2018-11-19追記 https://github.com/CircleCI-Public/circleci-dockerfiles/blob/master/ruby/images/ いつの間にか公開されてました 動機 circleciが提供しているdockerファ...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.qiita.com/assets/favicons/public/production-c620d3e403342b1022967ba5e3db1aaa.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">qiita.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-user-contents.imgix.net%2Fhttps%253A%252F%252Fcdn.qiita.com%252Fassets%252Fpublic%252Farticle-ogp-background-afbab5eb44e0b055cce1258705637a91.png%3Fixlib%3Drb-4.0.0%26w%3D1200%26blend64%3DaHR0cHM6Ly9xaWl0YS11c2VyLXByb2ZpbGUtaW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnFpaXRhLWltYWdlLXN0b3JlLnMzLmFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb20lMkYwJTJGNzMwNyUyRnByb2ZpbGUtaW1hZ2VzJTJGMTU3MDI3NjUyNT9peGxpYj1yYi00LjAuMCZhcj0xJTNBMSZmaXQ9Y3JvcCZtYXNrPWVsbGlwc2UmYmc9RkZGRkZGJmZtPXBuZzMyJnM9YTkwNjQ1YjE1NWFiNjlhNTI3YjI5NmYyMjUyMjhjNmQ%26blend-x%3D120%26blend-y%3D467%26blend-w%3D82%26blend-h%3D82%26blend-mode%3Dnormal%26s%3D7ff96656d4ab6eca3700fb115f167149?ixlib=rb-4.0.0&w=1200&fm=jpg&mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTk2MCZoPTMyNCZ0eHQ9Y2lyY2xlY2klMkZydWJ5JTNBMi40LjEtbm9kZS1icm93c2VycyVFMyU4MSVBMyVFMyU4MSVBNiVFNCVCRCU5NSVFNSU4NSVBNSVFMyU4MSVBMyVFMyU4MSVBNiVFMyU4MiU4QiVFMyU4MSVBRSVFMyU4MSU4QiVFNyU5RiVBNSVFMyU4MiU4QSVFMyU4MSU5RiVFMyU4MSU4QiVFMyU4MSVBMyVFMyU4MSU5RiZ0eHQtYWxpZ249bGVmdCUyQ3RvcCZ0eHQtY29sb3I9JTIzMUUyMTIxJnR4dC1mb250PUhpcmFnaW5vJTIwU2FucyUyMFc2JnR4dC1zaXplPTU2JnR4dC1wYWQ9MCZzPWE3MjAzMjc3MTY2NmZiYTgzNzE2ZDhkOTcxNGE1Y2M2&mark-x=120&mark-y=112&blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTgzOCZoPTU4JnR4dD0lNDB0ZXJyaWVyc2NyaXB0JnR4dC1jb2xvcj0lMjMxRTIxMjEmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9MzYmdHh0LXBhZD0wJnM9ZjVmNWI4NjYxNTcyODNmNzFiY2YzMWU5MTkyNWMzMzM&blend-x=242&blend-y=480&blend-w=838&blend-h=46&blend-fit=crop&blend-crop=left%2Cbottom&blend-mode=normal&s=a91f4dae6ee03b7e21bf7315419beb69" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://medium.com/@timakin/circleci2-0%E3%81%AEworkflows%E3%82%92%E8%A9%A6%E3%81%99-1329042122fd" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">medium.com</div>
<div class="remark-link-card-plus__description"></div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.google.com/s2/favicons?domain=medium.com" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">medium.com</span>
</div>
</div>
</a>
</div>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」、口コミで探せる介護施設の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」、オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。</p>medley
- 開発本部のセキュリティ知識を底上げする、タスクフォースの進め方https://developer.medley.jp/entry/2017/10/20/122000https://developer.medley.jp/entry/2017/10/20/122000ジョブメドレーの開発運用を担当している新居です。
メドレーでは開発本部のメンバーの技術力底上げや課題解決を目的とした短期プロジェクト(タスクフォースと呼んでいます)を実施しています。この取り組みの一環として、6〜8 月はセキュリティ知識の底...Fri, 20 Oct 2017 03:20:00 GMT<p>ジョブメドレーの開発運用を担当している<a href="https://www.wantedly.com/companies/medley/post_articles/54029">新居</a>です。</p>
<p>メドレーでは開発本部のメンバーの技術力底上げや課題解決を目的とした短期プロジェクト(タスクフォースと呼んでいます)を実施しています。この取り組みの一環として、6〜8 月はセキュリティ知識の底上げを目指した「セキュリティタスクフォース」を実施しました。今回は、その取り組み内容を紹介します。</p>
<h1 id="背景">背景</h1>
<p>現在、メドレーの開発本部には約 20 名のエンジニアが在籍しており、それぞれ多種多様な開発経験やスキルセットを持ったエンジニアが集まっています。</p>
<p>そして、前職ではフロントエンド専門でやってきたエンジニアもサーバーサイドの開発を行ったり、またその逆のケースもあったりと、各自の専門領域にとらわれないスタイルでの開発を行うことも多々あります。</p>
<h1 id="課題">課題</h1>
<p>そういった背景の中、エンジニアが増えていくにつれエンジニア間のスキルセットに差が生まれ、ばらつきが見られるようにもなっています。今後、更に組織が拡大していくのに伴い、こうしたエンジニア間のスキルセットの差も大きくなっていくことが懸念されます。</p>
<p>また、メドレーは医療ヘルスケア分野に向けてサービス提供を行っており、多くの個人情報を扱っていることはもちろん、医療・介護という領域の性質上、セキュリティには非常に気をつかう必要があります。データベースに入れて保守・運用している個人情報などの取り扱いや、システム改善や新機能開発などを行うときには必然的にセキュリティにも配慮して開発を進める必要があります。</p>
<p>そして背景のところでも述べたように、元々フロントエンド専門のエンジニアがサーバーサイドの開発に関わるケースや、そもそもサーバーサイド開発の経験が浅いエンジニアも中にはいて、サーバーサイドのセキュリティに関して自信がなかったり、具体的な対策方法がすぐに出てこないこともあるという課題がありました(当然ですが実際の開発では PR などでレビューして問題が起きないように対応しています)。</p>
<p>そこで、そういったメンバーのセキュリティ知識の底上げを行い、エンジニア間のスキルセットの差を縮めていくことが必要であると考えました。</p>
<h1 id="取り組み">取り組み</h1>
<p>上述した通り、</p>
<ul>
<li>スキルセットにばらつきや差が見られる</li>
<li>医療ヘルスケア分野は特にセキュリティに気を使う必要がある</li>
<li>セキュリティに関して自信がない、具体的な対策方法がすぐに出てこないこともある</li>
</ul>
<p>といった課題感から、まずはフロントエンド専門でやってきたメンバーやサーバーサイド開発の経験が浅いメンバーをメインターゲットとして、セキュリティ知識の底上げをやっていくことにフォーカスしました。</p>
<p>目標としては、ウェブアプリケーション開発における最低限のセキュリティ知識や対策方法をしっかり再整理し、さらに開発で使っている Ruby on Rails 上でどのように対策するべきかを押さえるというところを目標におきました。</p>
<h4 id="形式">形式</h4>
<p>形式としては、参加者に事前に教材の対象範囲を読んできてもらい、隔週開催の TechLunch(社内勉強会)終了後の約 20 分を利用して、内容の簡単な説明や補足、質疑応答、議論などを行う場(フォロー会)を設けました。</p>
<h4 id="教材">教材</h4>
<p>以下を使用しました。</p>
<p>メイン教材:「IPA の安全なウェブサイトの作り方」</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="安全なウェブサイトの作り方:IPA 独立行政法人 情報処理推進機構" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.ipa.go.jp%2Fsecurity%2Fvuln%2Fwebsecurity.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.ipa.go.jp/security/vuln/websecurity.html">www.ipa.go.jp</a></cite>
<p>サブ教材:「Rails セキュリティガイド」</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Ruby on Rails ガイド:体系的に Rails を学ぼう" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Frailsguides.jp%2Fsecurity.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://railsguides.jp/security.html">railsguides.jp</a></cite>
<p>メイン教材の「IPA の安全なウェブサイトの作り方」は、IPA が届出を受けた脆弱性関連情報を基にして作られており、セキュリティを考慮したウェブサイトを作る上での最低限の知識が整理できるだろうということで採用しました。</p>
<p>サブ教材の「Rails セキュリティガイド」は、Rails ではどのようにセキュリティの問題を回避しているのかといった方法が解説されており、実際の開発のときにどうすれば良いのかといったことが押さえられるだろうということで採用しました。</p>
<p>実際の開催スケジュールと、フォロー会の対象範囲はこちら。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170925/20170925122130.png" alt="f:id:dev-medley:20170925122130p:plain" title="f:id:dev-medley:20170925122130p:plain"></p>
<h1 id="取り組みを終えて">取り組みを終えて</h1>
<p>取り組みを終えて感じたこととしては以下になります。</p>
<h4 id="技術的な観点">技術的な観点</h4>
<ul>
<li>SQL インジェクションや XSS、CSRF などのメジャーな攻撃手法を参加者間で再整理できた</li>
<li>技術的に不安なところなどは「ここはこうですよね?」という感じで確かめ合うことができた</li>
<li>参加者が前職での経験談などをシェアしてくれる場面もあり、知見の共有ができた</li>
<li>セキュリティに詳しいエンジニアも交えて話すことで、随所で効果的にツッコミをいただき、濃密な議論ができた</li>
<li>ベテランエンジニアからは、昔流行った某サービスの某セキュリティ系障害の有名事例なども共有され、過去の歴史を知ることができる場となった</li>
</ul>
<h4 id="技術以外の観点">技術以外の観点</h4>
<ul>
<li>普段はチーム毎に黙々と仕事に取り組んでおり、チームを跨いであるひとつの話題(今回はセキュリティ)についてみんなと会話する機会は少ないので、知識の底上げはもちろんのこと、コミュニケーションの場としても良かった</li>
</ul>
<p>今回のように質疑応答や議論ができる場を設けることにより、他のメンバーの経験や知見も効果的に共有することができ、教材の読み合わせや講義形式では得られない知識も共有でき、取り組みとしてはうまくいったかなあと思います。</p>
<h1 id="まとめ">まとめ</h1>
<p>ということで、今回はセキュリティタスクフォースについてご紹介しました。</p>
<p>セキュリティの知識はだれかひとりが押さえていれば良いというものではなく、開発に関わるエンジニア全員が最低限は押さえておく必要があると思います。</p>
<p>組織の拡大と共に、日々のプロダクトの運用・開発も大切ですが、それらを支えるエンジニアの知識の底上げなどの開発以外の部分もより大切になってきますし、そういった取り組みが組織力を高めていくのではないかと思います。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」、口コミで探せる<a href="https://d.hatena.ne.jp/keyword/%B2%F0%B8%EE%BB%DC%C0%DF">介護施設</a>の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」、オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>
<p>医療や介護とは全く違う業界で経験を積んできたエンジニア・デザイナーが多いですが、こうした定期的な勉強会などで必要知識をインプットしながら開発しています。</p>
<p>メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております!</p>medley
- runit が便利なので、使い方を紹介した話〜メドレー TechLunch〜https://developer.medley.jp/entry/2017/10/04/120000https://developer.medley.jp/entry/2017/10/04/120000メドレー開発本部の nakatani です。
開発本部で定期的に開催している勉強会「TechLunch」で、runitという unix のプロセススーパバイザについてお話しました。
その内容について紹介させていただきます。
runit 自体...Wed, 04 Oct 2017 03:00:00 GMT<p>メドレー開発本部の nakatani です。</p>
<p>開発本部で定期的に開催している勉強会「TechLunch」で、<a href="https://smarden.org/runit/">runit</a>という unix のプロセススーパバイザについてお話しました。
その内容について紹介させていただきます。</p>
<p>runit 自体は特に目新しい技術ではなく(Linux の busybox に収められていたりする枯れた技術です)、大して難しい話題でもありません。</p>
<p>ただ、個人的には便利に使っている<strong>手放せないツール</strong>であり、もしスーパバイザというものの存在を知らずに<strong>使わずにいる人がいると勿体無いなあ</strong>という思いから、TechLunch のテーマとして取り上げた次第です。</p>
<h1 id="runit-とはなんなのか">runit とはなんなのか</h1>
<p>プロセスをデーモンとして立ち上げて、プロセスが死んでも再度起動し続けてくれるツール郡です。C 言語で開発されています。</p>
<p>Linux などの unix ではたいてい標準で init, Upstart, Systemd, launchd などのスーパバイザが組み込まれています。
runit はそれらと同じような位置づけのものです。</p>
<p>qmail の作者である<a href="https://cr.yp.to/djb.html">djb</a>が作った<a href="https://cr.yp.to/daemontools.html">daemontools</a>の後継のプロダクトです。</p>
<h1 id="runit-があると何が便利なのか">runit があると何が便利なのか</h1>
<h2 id="-マシンが起動しているかぎりプロセスを動作させ続けることができる">■ マシンが起動しているかぎり、プロセスを動作させ続けることができる</h2>
<p>マシンを立ち上げたあとに、起動コマンドを叩いたり、プロセスが落ちたときに再起動をする必要がありません。
また、フォアグラウンドで動作するプロセスを起動した後に、端末を切り離す操作をする必要もありません。</p>
<p>ただ、これは他のスーパバイザでも同じことが実現できます。</p>
<h2 id="-その場しのぎで作ったスクリプトをほぼそのままデーモン化できる">■ その場しのぎで作ったスクリプトを、ほぼそのままデーモン化できる</h2>
<p>Shell, Ruby, Perl, Haskell どのような言語で作ったスクリプトであっても、
<strong>シェルなどで実行可能なファイルがあれば、それをそのままデーモンとして実行することができます</strong>。</p>
<h2 id="-スクリプトの標準出力をそのままログファイルとして扱うことができる">■ スクリプトの標準出力をそのままログファイルとして扱うことができる</h2>
<p>svlogd というプログラムが、デーモンの標準出力をログ化し、ローテーションなどの面倒も見てくれます。
自作のデーモンが思った通りに動かない際のデバッグが容易です。</p>
<h1 id="macos-への導入方法">macOS への導入方法</h1>
<p>macOS に導入するための手順を記載します。詳しくはスライドや runit のドキュメントなどを理解して使うようにしてください。</p>
<p>Xcode や Homebrew を macOS に導入していることが前提です。</p>
<h2 id="serviceを-root-の-runit-ディレクトリとして設定する手順"><code>/service</code>を root の runit ディレクトリとして設定する手順</h2>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> brew</span><span style="color:#CE9178"> install</span><span style="color:#CE9178"> runit</span><span style="color:#6A9955"> # macports feels better.</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> mkdir</span><span style="color:#CE9178"> /service</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> cat</span><span style="color:#D4D4D4"> <<</span><span style="color:#D4D4D4">EOF</span><span style="color:#D4D4D4"> |</span><span style="color:#DCDCAA">sudo</span><span style="color:#CE9178"> tee</span><span style="color:#CE9178"> /Library/LaunchDaemons/runit.plist</span></span>
<span class="line"><span style="color:#CE9178"><?xml version='1.0' encoding='UTF-8'?></span></span>
<span class="line"><span style="color:#CE9178"><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"</span></span>
<span class="line"><span style="color:#CE9178">"https://www.apple.com/DTDs/PropertyList-1.0.dtd" ></span></span>
<span class="line"><span style="color:#CE9178"><plist version='1.0'></span></span>
<span class="line"><span style="color:#CE9178"> <dict></span></span>
<span class="line"><span style="color:#CE9178"> <key>Label</key><string>runit</string></span></span>
<span class="line"><span style="color:#CE9178"> <key>ProgramArguments</key></span></span>
<span class="line"><span style="color:#CE9178"> <array></span></span>
<span class="line"><span style="color:#CE9178"> <string>sh</string><string>-c</string></span></span>
<span class="line"><span style="color:#CE9178"> <string>PATH="/usr/local/sbin:/usr/local/bin:</span><span style="color:#9CDCFE">$PATH</span><span style="color:#CE9178">"</span></span>
<span class="line"><span style="color:#CE9178"> exec '/usr/local/bin/runsvdir' '/service'</span></span>
<span class="line"><span style="color:#CE9178"> </string></span></span>
<span class="line"><span style="color:#CE9178"> <string>;</string></span></span>
<span class="line"><span style="color:#CE9178"> <string>--pid=exec</string></span></span>
<span class="line"><span style="color:#CE9178"> </array></span></span>
<span class="line"><span style="color:#CE9178"> <key>Debug</key><false/><key>Disabled</key><true/><key>KeepAlive</key><true/></span></span>
<span class="line"><span style="color:#CE9178"> </dict></span></span>
<span class="line"><span style="color:#CE9178"></plist></span></span>
<span class="line"><span style="color:#D4D4D4">EOF</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> launchctl</span><span style="color:#CE9178"> load</span><span style="color:#569CD6"> -w</span><span style="color:#D4D4D4"> ></span><span style="color:#CE9178">/Library/LaunchDaemons/runit.plist</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> launchctl</span><span style="color:#CE9178"> list</span><span style="color:#D4D4D4"> | </span><span style="color:#DCDCAA">grep</span><span style="color:#CE9178"> runit</span></span></code></pre>
<h2 id="自作スクリプトをデーモンにする手順">自作スクリプトをデーモンにする手順</h2>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> cd</span><span style="color:#CE9178"> /service/</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> mkdir</span><span style="color:#569CD6"> -p</span><span style="color:#CE9178"> hello/log</span><span style="color:#6A9955"> # log を同時につくると runsvdir が log の準備もする</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> cd</span><span style="color:#CE9178"> hello</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> cat</span><span style="color:#D4D4D4"> <<</span><span style="color:#D4D4D4">EOF</span><span style="color:#D4D4D4"> |</span><span style="color:#DCDCAA">sudo</span><span style="color:#CE9178"> tee</span><span style="color:#CE9178"> run</span></span>
<span class="line"><span style="color:#CE9178">#!/usr/bin/env ruby</span></span>
<span class="line"><span style="color:#CE9178"># 自作スクリプト</span></span>
<span class="line"><span style="color:#CE9178">while true</span></span>
<span class="line"><span style="color:#CE9178"> puts("hello ruby #{Time.now.to_i % 100}");</span></span>
<span class="line"><span style="color:#CE9178"> STDOUT.flush();</span></span>
<span class="line"><span style="color:#CE9178"> sleep(1);</span></span>
<span class="line"><span style="color:#CE9178">end</span></span>
<span class="line"><span style="color:#D4D4D4">EOF</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> cat</span><span style="color:#D4D4D4"> <<</span><span style="color:#D4D4D4">EOF</span><span style="color:#D4D4D4"> |</span><span style="color:#DCDCAA">sudo</span><span style="color:#CE9178"> tee</span><span style="color:#CE9178"> log/run</span></span>
<span class="line"><span style="color:#CE9178">#!/bin/sh</span></span>
<span class="line"><span style="color:#CE9178">exec svlogd -ttt .</span></span>
<span class="line"><span style="color:#D4D4D4">EOF</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> chmod</span><span style="color:#B5CEA8"> 755</span><span style="color:#CE9178"> run</span><span style="color:#CE9178"> log/run</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> tail</span><span style="color:#569CD6"> -F</span><span style="color:#CE9178"> log/current</span><span style="color:#6A9955"> # ログが見られる。</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> sv</span><span style="color:#CE9178"> st</span><span style="color:#CE9178"> .</span><span style="color:#6A9955"> # daemon の状態を確認する。</span></span>
<span class="line"><span style="color:#DCDCAA">run:</span><span style="color:#CE9178"> .:</span><span style="color:#D4D4D4"> (pid </span><span style="color:#B5CEA8">35517</span><span style="color:#D4D4D4">) 46s; </span><span style="color:#DCDCAA">run:</span><span style="color:#CE9178"> log:</span><span style="color:#D4D4D4"> (pid </span><span style="color:#B5CEA8">34727</span><span style="color:#D4D4D4">) 456s</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> sv</span><span style="color:#CE9178"> st</span><span style="color:#CE9178"> /service/hello/</span><span style="color:#6A9955"> # ディレクトリの指定方法は自由。</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> sv</span><span style="color:#CE9178"> t</span><span style="color:#CE9178"> .</span><span style="color:#6A9955"> # TERM シグナルを daemon に送る</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> sudo</span><span style="color:#CE9178"> sv</span><span style="color:#CE9178"> st</span><span style="color:#CE9178"> .</span></span>
<span class="line"><span style="color:#DCDCAA">run:</span><span style="color:#CE9178"> .:</span><span style="color:#D4D4D4"> (pid </span><span style="color:#B5CEA8">35589</span><span style="color:#D4D4D4">) 1s; </span><span style="color:#DCDCAA">run:</span><span style="color:#CE9178"> log:</span><span style="color:#D4D4D4"> (pid </span><span style="color:#B5CEA8">34727</span><span style="color:#D4D4D4">) 470s </span><span style="color:#6A9955"># 起動時間が 1s になってる。</span></span></code></pre>
<h1 id="まとめ">まとめ</h1>
<p>結局のところ、**「使えばわかるし使わないと便利さがよくわからない」**というのが正直なところです。
そのため、TechLunch においては、<strong>使うための手順</strong>を時間をかけて解説をするようにしました。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/79a0358fe5e6414dbe34717bce3ee066" title="runit が便利なので、使い方を紹介した話 /runit" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 430px;" data-ratio="1.302325581395349"></iframe>
<p>みなさんも興味があれば、ぜひ導入して使ってみてください。</p>
<p>僕自身、開発マシンである macbook pro に runit を入れて、開発環境の Ruby や MongoDB, Elasticsearch, Nginx などのサーバ群、
定期的に動かしたいちょっとしたスクリプトなどを runit で管理しています。</p>
<p>異なる設定のサーバ群を一つのマシンに同居させる場合も、設定ファイルを分けて別ポートで立ち上げたりしています。</p>
<p>以前のプロジェクトでは本番環境を runit で構築したこともありますし、今のプロジェクトでも、たまったゴミデータを削除し続けるスクリプトを runit で対応してそのまま放置(放置しても OK なくらいメンテナンスフリー)していたりします。</p>
<p>最近はクラウドやコンテナ技術が活況であり、環境を抽象化しようという流れがあります。しかしながら、そもそもプロセスや UNIX OS 自体が環境を抽象化するための技術群です。そういった基本的な技術と仲良くすることで、物事がシンプルになることがあるのではないかと考えたりしながら、日々開発に取り組んでいます。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」、口コミで探せる介護施設の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」、オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。</p>medley
- RubyKaigi 2017 にメドレーが Ruby Sponsor として参加しましたhttps://developer.medley.jp/entry/2017/09/28/120000https://developer.medley.jp/entry/2017/09/28/120000こんにちは!開発本部のエンジニア・新居です。
メドレーは 9/18〜20 に開催された、RubyKaigi 2017の Ruby Sponsor を務めさせていただきました。(9/15〜17 に開催されたiOSDC JAPAN 2017に引...Thu, 28 Sep 2017 03:02:29 GMT<p>こんにちは!開発本部のエンジニア・<a href="https://www.wantedly.com/companies/medley/post_articles/54029">新居</a>です。</p>
<p>メドレーは 9/18〜20 に開催された、<a href="https://rubykaigi.org/2017/">RubyKaigi 2017</a>の Ruby Sponsor を務めさせていただきました。(9/15〜17 に開催された<a href="https://iosdc.jp/2017/">iOSDC JAPAN 2017</a>に引き続きの協賛です!メドレーでは今年からテックカンファレンスへの協賛を積極的に行っています)</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170928/20170928115311.jpg" alt="f:id:medley_inc:20170928115311j:plain" title="f:id:medley_inc:20170928115311j:plain"></p>
<p>イベント当日は、弊社から CTO の平山、エンジニアの平木と宍戸、そして新居の 4 人が参加しました。今回はそのときの様子などをレポートしたいと思います。</p>
<h1 id="会場の様子">会場の様子</h1>
<p>今年は広島県の広島国際会議場での開催でした。</p>
<p>広島国際会議場は平和記念公園の敷地内にあり、近くには原爆ドームや広島城などもあるので RubyKaigi のついでに観光しちゃおうなんて人も多かったのではないでしょうか。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922103927.jpg" alt="f:id:dev-medley:20170922103927j:plain" title="f:id:dev-medley:20170922103927j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922103952.jpg" alt="f:id:dev-medley:20170922103952j:plain" title="f:id:dev-medley:20170922103952j:plain"></p>
<p>会場の一角には今回の RubyKaigi 参加者がどこから来たかを掲示するボードが設置されていました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922104014.jpg" alt="f:id:dev-medley:20170922104014j:plain" title="f:id:dev-medley:20170922104014j:plain"></p>
<p>日本以外からもたくさんの人が参加しており RubyKaigi の注目度の高さが伺えます。</p>
<p>そして隣にあったスポンサーボードの MEDLEY ロゴをしっかり確認して、CTO 平山とパシャリ!!!</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922104049.jpg" alt="f:id:dev-medley:20170922104049j:plain" title="f:id:dev-medley:20170922104049j:plain"></p>
<h1 id="ブースの様子">ブースの様子</h1>
<p>続いてブースの様子を!
ブースの仕上がりはこんな感じで、メドレーコーポレートカラーの赤が目立って良い感じでした!</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170928/20170928115453.jpg" alt="f:id:medley_inc:20170928115453j:plain" title="f:id:medley_inc:20170928115453j:plain"></p>
<p>両サイドにはメドレーの事業概要と、メディアなどからも注目されることが多いオンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」を紹介するポスターを配置、写真中央のモニターでは、スマートフォンや PC で医師の診療が受けられるという CLINICS のサービス概要がわかる紹介動画を流し、会社全体やプロダクトについて説明できる準備を整えました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922104208.jpg" alt="f:id:dev-medley:20170922104208j:plain" title="f:id:dev-medley:20170922104208j:plain"></p>
<p>ノベルティは「MEDLEY ステッカー」「MEDLEY うちわ」「MEDLEY 絆創膏」の 3 種類を用意しました。</p>
<p>1 日目は台風一過で気温が上昇し、暑さをしのぐため MEDLEY うちわが大活躍してくれました!</p>
<p>MEDLEY 絆創膏はデザインのかわいさと医療系ならではということもありとても好評でした!参加者の中には靴擦れしている人や小さい擦り傷を負ってる人もいて、MEDLEY 絆創膏が参加者のお役に立てたのではないでしょうか。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922104242.jpg" alt="f:id:dev-medley:20170922104242j:plain" title="f:id:dev-medley:20170922104242j:plain"></p>
<p>ブース前で意気込む 3 人!</p>
<p>ちなみに T シャツはメドレー特製の<a href="https://www.wantedly.com/companies/medley/post_articles/76141">バリュー T シャツ(左)とカレッジロゴ T シャツ(中央と右)</a>を着用しています。</p>
<p>写真では見えませんが、バリュー T シャツにはメドレーのバリュー(行動規範)のひとつである「中央突破」の文字が入っています。</p>
<p>両 T シャツ共、目を留めてくれた参加者からはかわいいという声もいただけて、こちらも好評でした。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170928/20170928115637.jpg" alt="f:id:medley_inc:20170928115637j:plain" title="f:id:medley_inc:20170928115637j:plain"></p>
<p>午後の 15:20~15:50 の Afternoon Break では大勢の参加者がブースゾーンに。</p>
<p>実際にブースに足を運んでいただいた参加者とお話していると、メドレーや CLINICS のことを知らない人がほとんどでしたが、メドレーが医療系のサービスを提供している会社であることや、CLINICS というオンライン診療アプリを開発していることをお話すると興味を持っていただけることが多かったように思います。</p>
<p>「今度病院で見かけたら使ってみます!」という声をいただいたり、参加者自身の医療体験を元に CLINICS というサービスについて意見を交わしたりと、メドレーエンジニアと参加者が密にコミュニケーションをとれる良い機会になりました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170928/20170928115734.jpg" alt="f:id:medley_inc:20170928115734j:plain" title="f:id:medley_inc:20170928115734j:plain"></p>
<p>大盛況です!</p>
<h1 id="cto-平山の発表">CTO 平山の発表</h1>
<p>そして 3 日目の最後の Keynote 前には Ruby スポンサーの PR 枠があり、弊社の CTO 平山が発表しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170928/20170928120004.jpg" alt="f:id:medley_inc:20170928120004j:plain" title="f:id:medley_inc:20170928120004j:plain"></p>
<p>発表では、「医療ヘルスケア分野の課題を解決する」というミッションのもとメドレーが 4 つの事業を行っていること、その中のひとつである CLINICS のプロダクトについて、「医療 x IT への挑戦」にむけて<a href="https://amp.review/2017/09/01/medley/">医療従事者とエンジニア・デザイナーが協力してプロダクト開発を行う体制</a>などを紹介しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922104626.jpg" alt="f:id:dev-medley:20170922104626j:plain" title="f:id:dev-medley:20170922104626j:plain"></p>
<p>最終日 3 日目の最後ということもありお疲れの方も多かったと思いますが、会場を笑いで沸かすシーンもあり、メドレーと、そのプロダクトのことを少しでも多くの人に知っていただくとても良い機会になったのではないでしょうか。</p>
<p>当日の発表資料はこちらからどうぞ。</p>
<iframe id="talk_frame_407759" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/7029a5f722844d96a7ef0ea793b488e2" width="710" height="596" frameborder="0" allowfullscreen="true"></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/medley/company-and-product-information-rubykaigi-2017">speakerdeck.com</a></cite>
<h1 id="周辺散策の様子おまけ">周辺散策の様子(おまけ)</h1>
<p>最後におまけということで周辺散策の様子を!</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922120050.jpg" alt="f:id:dev-medley:20170922120050j:plain" title="f:id:dev-medley:20170922120050j:plain"></p>
<p>まずは原爆ドーム。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170928/20170928120937.jpg" alt="f:id:medley_inc:20170928120937j:plain" title="f:id:medley_inc:20170928120937j:plain"></p>
<p>続いて広島城跡へ。御門橋。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922120112.jpg" alt="f:id:dev-medley:20170922120112j:plain" title="f:id:dev-medley:20170922120112j:plain"></p>
<p>歩みを進める 3 人。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170928/20170928120718.jpg" alt="f:id:medley_inc:20170928120718j:plain" title="f:id:medley_inc:20170928120718j:plain"></p>
<p>そして天守閣。原爆で倒壊したためコンクリート建築として復元されたようです。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922115844.jpg" alt="f:id:dev-medley:20170922115844j:plain" title="f:id:dev-medley:20170922115844j:plain"></p>
<p>近くには広島護国神社。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922115739.jpg" alt="f:id:dev-medley:20170922115739j:plain" title="f:id:dev-medley:20170922115739j:plain"></p>
<p>メドレー<a href="https://info.medley.jp/entry/2017/05/23/171859">恒例</a>の?参拝!</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170922/20170922115919.jpg" alt="f:id:dev-medley:20170922115919j:plain" title="f:id:dev-medley:20170922115919j:plain"></p>
<p>美味しい広島のお好み焼きもいただきました!</p>
<h1 id="さいごに">さいごに</h1>
<p>今回はメドレーとして初の RubyKaigi 参加でした。</p>
<p>ブース運営などへの不安もありましたが、実際に当日を迎えてみると、参加者のみなさんとお話でき、メドレーのことを少しでも多くの人に認知していただくとても良い機会だったと思います。</p>
<p>まだまだメドレーのことをご存知ない方も多かったですが、実際にお会いして話してみると、医療というリアル産業をインターネットの力で変えていくという面白さに興味を持っていただけたことが多かったなという印象を受けました。</p>
<p>良いプロダクトを作ることも大切ですが、医療分野でのプロダクト作りの醍醐味や産み出せる価値などを多くのエンジニア・デザイナーに知ってもらえるよう、こうしたテックカンファレンスの場などで発信し続けていくことも大切だなあと改めて実感しました。</p>
<p>弊社で「医療 x IT への挑戦」に取り組みたいエンジニアのみなさんを心からお待ちしております!興味がある方は、こちらの「話を聞いてみたい」からご連絡ください。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="株式会社メドレーの最新情報 - Wantedly" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fmedley" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.wantedly.com/companies/medley">www.wantedly.com</a></cite>
<p>開発本部の雰囲気をもっと知りたい方は、こちらからどうぞ。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- iOSDC Japan 2017 にメドレーが協賛しましたhttps://developer.medley.jp/entry/2017/09/27/120000https://developer.medley.jp/entry/2017/09/27/120000みなさん、こんにちは。開発本部のエンジニア・平木です。
表題の通りなんですが、メドレーは今年からテックカンファレンスの協賛を本格的に開始しています。その内の 1 つが 9/15〜17 に早稲田大学で開かれた iOSDC Japan 2017...Wed, 27 Sep 2017 03:00:00 GMT<p>みなさん、こんにちは。開発本部のエンジニア・平木です。</p>
<p>表題の通りなんですが、メドレーは今年からテックカンファレンスの協賛を本格的に開始しています。その内の 1 つが 9/15〜17 に早稲田大学で開かれた <a href="https://iosdc.jp/2017/">iOSDC Japan 2017</a>(以下 iOSDC)です。</p>
<p>みなさんご存知かと思いますが、iOSDC は国内の iOS イベントの中では try! Swift と並ぶ最大級のイベントです。</p>
<p>メドレーは今回、ゴールドスポンサーとして協力させていただきました。ブース出展はしていないのですが、CLINICS の iOS 版をメインで担当している高井と一緒に参加してきました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170925/20170925173341.png" alt="20170925173341.png">
<h1 id="会場の雰囲気">会場の雰囲気</h1>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170927/20170927113807.png" alt="20170927113807.png">
<p>まず第一印象として人が多かったです。</p>
<p>会場は 4 つに分かれていたのですが、一番大きいトラック A 以外はセッションが始まるころになると満席になっていることが多く、自分が見たセッションでは大体立ち見が出ている感じでした。</p>
<p>企業ブースを出展している企業さんの中では、サイバーエージェントさんのブースが iOS 開発に関するアンケートを取っていたようで、盛況でした。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170925/20170925174226.png" alt="20170925174226.png">
<p>スタッフさんの人数も多かったということもあるのか、スケジュール通りにセッションが進んでいるのが印象的でした。機材トラブルなどもほとんど 見受けられなかったので、運営がとてもスムーズでした。</p>
<p>また、朝にオープニングムービーが流れてスポンサーの紹介などをしている映像が流れるのですが、まさかの三石琴乃さんがナレーションでビビりました。 すげえ。</p>
<p><code>video: https://youtu.be/AC7C5CY1Meo?t=4m55s</code></p>
<p>iOSDC ではランチが配られる形式なのですが、来場者が多かったはずなのに、食べる場所がない…みたいなこともなく良かったですね。</p>
<p>ランチはサンドイッチで、大変おいしかったです。席が近いからか知らない人とも話しやすい雰囲気を感じました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170927/20170927113648.png" alt="20170927113648.png">
<h1 id="セッション">セッション</h1>
<p>一口に iOS 関連といってもセッションはかなりバラエティに富んだ内容です。Swift に関するもの、設計に関するもの、運用に関わるものなどなど…。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170925/20170925174414.png" alt="20170925174414.png">
<p>ちょいちょいと Android や Kotlin と関係したセッションがあったのは意外でした。が、考えてみると両方をやってるエンジニアの方も多いでしょうし、 Swift と Kotlin も文法なんかは割と似てるということで、親和性があるんでしょうね。</p>
<p>全体としては、やはり、iPhone X や iOS11 の話題がちらほらと出ており、さすが専門のカンファレンスだけあってわりとつっこんだ話が聞けたのが良かったです。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170927/20170927113424.png" alt="20170927113424.png">
<p>個人的には言語の話なども面白かったんですが、運用周りの話をしてるセッションがとても面白かったです。</p>
<p>リニューアル周りの話題や CI での運用の話などは大変に参考になりましたし、特にトレタさんの <a href="https://iosdc.jp/2017/node/1422">ロギング</a>の話はぜひ弊社でも実践せねばという話でした。</p>
<p>LT の時間前にビールが配られて、飲みながら参加できるのは大変ポイントが高いですね!</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170927/20170927113925.png" alt="20170927113925.png">
<h1 id="最後に">最後に</h1>
<p>iOSDC は自分は初めての参加だったのですが、大変よいイベントだなと感じました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170925/20170925180142.png" alt="20170925180142.png">
<p>iOS だと特に大体毎年新しいバージョンや新しい機種が出てきますし、 そうなると新しい SDK や API もどんどん出てくるという感じです。もちろんネットの情報などで知識をアップデートしたりするのも必要ですが、こういったイベントでより新鮮な知見などを共有できるというのは、やはり素晴しいなあという感想です。</p>
<p>弊社のアプリ開発でもそういった知見などを活かして開発していける仲間を引き続き募集しています!
興味がある方は、こちらの「話を聞いてみたい」からご連絡ください。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.wantedly.com/companies/medley" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">株式会社メドレーの会社情報 - Wantedly</div>
<div class="remark-link-card-plus__description">株式会社メドレーの魅力を伝えるコンテンツと、住所や代表・従業員などの会社情報です。急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。
しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.wantedly.com/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.wantedly.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://images.wantedly.com/i/YMVT7Fy?h=1440&w=1440" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>開発本部の雰囲気を知りたい方はこちらからどうぞ。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- PaaS を Heroku から AWS Elastic Beanstalk に移行した話https://developer.medley.jp/entry/2017/09/22/124000https://developer.medley.jp/entry/2017/09/22/124000こんにちは、開発本部の宮内です。
さて、これまで弊社のオンライン診療アプリ「CLINICS」では、ローンチ時よりHerokuを利用しておりました。
Heroku とは、PaaS の一種で Web アプリケーションを簡単にデプロイ、ホスティン...Fri, 22 Sep 2017 03:40:00 GMT<p>こんにちは、開発本部の宮内です。</p>
<p>さて、これまで弊社のオンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」では、ローンチ時より<a href="https://www.heroku.com/">Heroku</a>を利用しておりました。
Heroku とは、PaaS の一種で Web アプリケーションを簡単にデプロイ、ホスティングできるサービスです。
ある程度の制約はつきますが、(大体の制約は金で解決できるので)使っているかたも多いのではないでしょうか。</p>
<p>今回、事情により Heroku から<a href="https://aws.amazon.com/jp/elasticbeanstalk/">AWS Elastic Beanstalk</a>(以下、EB)へ移行することになりましたので、そのあたりでやったことを共有できればと思います。</p>
<h1 id="private-paas-にしないの">Private PaaS にしないの?</h1>
<p>まずはじめに移行にあたり、Priavte PaaS を構築する方法を模索しました。
ですが、これらのクラスタの構築はできても、専任の(Ops|SRE|インフラ)チームがいないため、日々の管理や運用に手が回らないだろうという思いから、Private PaaS の構築は見送りました。
この辺りは今後チーム人数が増えたら挑戦していきたいです…。</p>
<h2 id="検討時参考にしたリンク">検討時、参考にしたリンク</h2>
<ul>
<li><a href="https://www.jancarloviray.com/blog/paas-comparison-2017-dokku-flynn-deis-kubernetes-docker-swarm/">PAAS comparison - Dokku vs Flynn vs Deis vs Kubernetes vs Docker Swarm (2017)</a></li>
<li><a href="https://convox.com/">Convox</a></li>
<li><a href="https://deis.io/">Deis</a></li>
<li><a href="https://flynn.io/">Flynn</a></li>
</ul>
<h1 id="なぜ-aws-elastic-beanstalk-にしたの">なぜ AWS Elastic Beanstalk にしたの?</h1>
<p>EB には Rails や Sinatra で作成されたウェブアプリケーションを実行するための<a href="https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/create_deploy_Ruby.html">Ruby プラットフォーム</a>が予め用意されております。
ただし、今回の移行では、アプリケーションへの変更を一切加えずに行いたかったため、Ruby プラットフォームを利用しませんでした。
代わりに Docker コンテナが実行できる<a href="https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/create_deploy_docker.html">プラットフォーム</a>があったため、そちらを使うことにしました。</p>
<h1 id="herokuish-で-docker-イメージを作成">herokuish で Docker イメージを作成</h1>
<p>アプリケーションの Docker イメージ化には、<a href="https://github.com/gliderlabs/herokuish">gliderlabs/herokuish</a>を使いました。
これは<a href="https://devcenter.heroku.com/articles/buildpacks">buildpack</a>を使いアプリケーションを slug 化したり、slug を実行するためのツールです。</p>
<p>Docker イメージ作成の手順は以下の通りです。</p>
<ol>
<li><code>herokuish buildpack build</code> と <code>herokuish slug generate</code> でアプリケーションを slug にする</li>
<li><code>herokuish slug import</code>で slug をインポートして完成</li>
</ol>
<p>それでは、それぞれ簡単に説明していきたいと思います。</p>
<h1 id="アプリケーションを-slug-にする">アプリケーションを slug にする</h1>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">docker</span><span style="color:#CE9178"> pull</span><span style="color:#CE9178"> gliderlabs/herokuish</span></span>
<span class="line"><span style="color:#DCDCAA">docker</span><span style="color:#CE9178"> run</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> -v</span><span style="color:#CE9178"> "/path/to/app:/tmp/build"</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> -v</span><span style="color:#CE9178"> "/path/to/cache:/tmp/cache"</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> -v</span><span style="color:#CE9178"> "/path/to/slug:/tmp/slug"</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#CE9178"> gliderlabs/herokuish</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#CE9178"> /bin/bash</span><span style="color:#569CD6"> -c</span><span style="color:#CE9178"> 'herokuish buildpack build && herokuish slug generate && herokuish slug export > /tmp/slug/slug.tgz'</span></span></code></pre>
<p>herokuish は特定のディレクトリに対して処理を行います。↑ ではビルドするアプリケーションまでのディレクトリパスを<code>/tmp/build</code>にマッピングしています。</p>
<p><code>/tmp/cache</code>は buildpack が利用するキャッシュ置き場です。このディレクトリを次回以降のビルドでもマッピングしておくとビルドの高速化が見込めます。</p>
<p>最後の<code>/tmp/slug</code>はビルドした slug をコンテナからホストへコピーするために指定しています。(これは herokuish で用意されてるものではなくコンテナからホストへファイルをコピーする方法を悩んだ末のアドホックな対応です…)</p>
<p>他にも様々なディレクトリがあります。詳しくは<a href="https://github.com/gliderlabs/herokuish#paths">ドキュメント</a>をご覧ください。</p>
<h1 id="slug-をインポートする">slug をインポートする</h1>
<p>次に作成した slug を使いアプリケーションの Docker イメージをつくります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">cd</span><span style="color:#D4D4D4"> $(</span><span style="color:#DCDCAA">mktemp</span><span style="color:#569CD6"> -d</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#DCDCAA">mv</span><span style="color:#CE9178"> /path/to/slug/slug.tgz</span><span style="color:#CE9178"> .</span></span>
<span class="line"><span style="color:#DCDCAA">echo</span><span style="color:#CE9178"> '</span></span>
<span class="line"><span style="color:#CE9178">FROM gliderlabs/herokuish</span></span>
<span class="line"></span>
<span class="line"><span style="color:#CE9178">COPY slug.tgz /tmp/slug.tgz</span></span>
<span class="line"><span style="color:#CE9178">RUN cat /tmp/slug.tgz | herokuish slug import && rm /tmp/slug.tgz</span></span>
<span class="line"></span>
<span class="line"><span style="color:#CE9178">EXPOSE 5000</span></span>
<span class="line"><span style="color:#CE9178">'</span><span style="color:#D4D4D4"> > </span><span style="color:#CE9178">Dockerfile</span></span>
<span class="line"><span style="color:#DCDCAA">docker</span><span style="color:#CE9178"> build</span><span style="color:#569CD6"> -f</span><span style="color:#CE9178"> Dockerfile</span><span style="color:#CE9178"> .</span></span></code></pre>
<p><code>docker build</code>時に Context としてカレントディレクトリ全体が送られるため、一時ディレクトリを作成しその中で<code>docker build</code>を行っています。</p>
<h1 id="終わり">終わり</h1>
<p>CLINICS では上記のような手段で作成した Docker イメージを<a href="https://aws.amazon.com/jp/ecr/">Amazon EC2 Container Registry</a>にアップロードし EB 上で実行しています。</p>
<p>本来であれば、アプリケーションを slug にする部分と、slug をインポートする部分を分割しなくても良いと思いますが、CircleCI で Docker イメージを作成する関係上でこのような方法になりました。</p>
<p>先日 GA となった CircleCI 2.0 にはまだ対応できていないので、今後の課題としたいと思います。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、CLINICS だけでなく、医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」、口コミで探せる介護施設の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」などのプロダクトも提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p>メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。</p>medley
- 思考とデザインスキル 〜メドレー TechLunch〜https://developer.medley.jp/entry/2017/09/14/132031https://developer.medley.jp/entry/2017/09/14/132031はじめまして!最近みるみる太りだしてはいるものの、まだ機は熟していないとダイエットの時期をぐっと堪えている開発本部イケメン担当のデザイナー・小山です。
メドレーでは TechLunch という社内勉強会を実施しているのですが、前田に引き続き...Thu, 14 Sep 2017 04:20:31 GMT<p>はじめまして!最近みるみる太りだしてはいるものの、まだ機は熟していないとダイエットの時期をぐっと堪えている開発本部イケメン担当のデザイナー・小山です。</p>
<p>メドレーでは TechLunch という社内勉強会を実施しているのですが、<a href="https://developer.medley.jp/entry/2017/08/03/160000">前田</a>に引き続き私も発表する機会をいただきましたので、その内容を紹介させていただきます。テーマは「思考とデザインスキル」です。発表資料は記事の最後をご覧ください。</p>
<h1 id="テーマに入るまえに">テーマに入るまえに…</h1>
<p>みなさん『黄金比』ってご存知ですか?</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170913/20170913121842.png" alt="f:id:medley_inc:20170913121842p:plain" title="f:id:medley_inc:20170913121842p:plain"></p>
<p>黄金比は美しいとされる物の形に共通してみられる比率で、古くから絵画や彫刻、建築などに使われています。デザイナーであれば、一度以上は使ったことあるのではないでしょうか?私もデザイナーなので何度も使っています。</p>
<p>ただ使ってはいるものの、なぜ美しくなるのか上手く説明ができません。当たり前のように世の中に広まっていて『困ったときの黄金比!』といった安易な思考で使っています。三十路を軽く超えたのでそれではいけないと思い、すこし調べてみました。</p>
<p>そもそも黄金比はエウクレイデスというユーグリッド幾何学を体系化した数学者が見つけた比率なのですが、そのとき彼は『黄金比=美しい』とは明言していないそうです。自然物や人工物の形には一定の比率で成り立っていると考え、その比率に黄金比と名付けだけとのこと。</p>
<p>その後、その使いやすさから至るところで活用され、その比率に親しみが芽生え巷で受け入れられるようになりました。</p>
<p>認知心理学では、それを『<strong>単純接触効果</strong>』と呼びます。たくさん触れるうちに親しみが沸く機能が人にはもともと備わっていて、黄金比をつかったものが美しく見えるのもその影響ではないか?と言われています。この説を聞いた時、基礎的なのに原理が曖昧なデザイナーのスキルの輪郭がすこしだけハッキリしてきました。</p>
<p>今回の TechLunch では、曖昧だったデザインのスキルを人の思考や心理現象という視点から捉え直してみると、新しい発見があるのかも?と考え、『思考とデザインスキル』のテーマを選びました。</p>
<h1 id="早い思考と遅い思考">『早い思考』と『遅い思考』</h1>
<p>まず最初に人の思考がどういう構造になっているか整理したいと思います。</p>
<p>*思考の捉え方には幅があるため、今回は Wikipedia で【思考】の狭義にあたる『情報処理』の内容になります。</p>
<p>ここで 3 つの質問を左から順に答えてください。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170913/20170913122003.png" alt="f:id:medley_inc:20170913122003p:plain" title="f:id:medley_inc:20170913122003p:plain"></p>
<p>大抵の人であれば 2 問目までは瞬時に答えが出たと思います。3 問目はどうでしょうか?</p>
<p>3 問目は他よりも時間がかかったかと思います。情報を処理するときの思考には 1 問目と 2 問目のように瞬時に答えれるのは『<strong>早い思考</strong>』、3 問目のように少し時間は必要な『<strong>遅い思考</strong>』の 2 つがあります。</p>
<p>行動経済学では、この早い思考を『<strong>システム 1</strong>』、遅い思考を『<strong>システム 2</strong>』と呼んでいます。</p>
<h1 id="システム-1-は自動的に直感で動く">システム 1 は、自動的に直感で動く</h1>
<p>では次にこちらをご覧ください。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170913/20170913122043.png" alt="f:id:medley_inc:20170913122043p:plain" title="f:id:medley_inc:20170913122043p:plain"></p>
<p>どちらの直線が長いでしょうか?答えはどちらも同じです。この問題をご存知の方でも見た瞬間は A が長く見えるのではないでしょうか?</p>
<p>ではこちらではどうでしょうか?</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170913/20170913122101.png" alt="f:id:medley_inc:20170913122101p:plain" title="f:id:medley_inc:20170913122101p:plain"></p>
<p>こちらの文字をみたときに、海水浴していて溺れている、もしくはそれに近いシーンを思い浮かべてないでしょうか? この二語のあいだには『りんご あまい』というような直接の相関はありません。それにもかかわらず出来れば避けたくなるようなことでも無意識に関連づけられ、シーンが思い浮かんだはずです。</p>
<p>システム 1 には、さきほどの直線の長さのように間違っていたとしても『<strong>見たまま</strong>』を認識する機能と 2 つの文字から『<strong>関連づけ</strong>』をおこないストーリーを組み立てる機能があります。そのどちらもが自分の意識とは関係なく自動で、しかも強力に働いています。</p>
<h1 id="システム-2-は手動で論理的に動く">システム 2 は、手動で論理的に動く</h1>
<p>最初の二桁の掛け算を思い出してください。日本で算数を学んだ人であれば、二桁の掛け算のとき『考える』段階に移ると思います。これがシステム 2 のスイッチです。システム 1 は常時スイッチが入っていてほぼ自動で答えを出しますが、システム 2 は意識的に『考える』というステップを踏まないと動きません。システム 1 は自動ですが、システム 2 は手動です。</p>
<p>手動のため手間かかりますが、システム 1 にはない用心深さと慎重さがあります。こちらの問題をご覧ください。</p>
<blockquote>
<p>『<strong>バットとボールで 110 円、バットはボールより 100 円高い、ボールの値段は?</strong>』</p>
</blockquote>
<p>即答で答えた人は 10 円と答える方が多いようです。こういう問題にはシステム 2 がうってつけで、論理的かつ正確に答えを出そうとします。答えは 5 円です。なかには頭の回転が早く即答できる人がいらっしゃることだと思います。そんな方にはこちらの問題を答えていだだきましょう。</p>
<blockquote>
<p><strong>3 日前から食べた夕飯の献立を口に出して発表しながら、『26x673』『245x287』『346x4546』の 3 問を 90 秒以内に解答してください。</strong></p>
</blockquote>
<p>いかがでしょうか?システム 2 は手動でうごき論理的で正確に答え出そうとしますが、複雑すぎる演算やマルチタスクにめっぽう弱くスタミナもありません。あきらめて電卓を叩いた方は、システム 2 がギブアップしたということかもしれません。</p>
<p>システム 1 と 2 の特徴は以下。どちらも良いところとそうでないところがあります。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170913/20170913122233.png" alt="f:id:medley_inc:20170913122233p:plain" title="f:id:medley_inc:20170913122233p:plain"></p>
<h1 id="デザインを再構築">デザインを再構築</h1>
<p>これらシステム 1&2 の特性を踏まえた上で簡単なニュース記事のタイポグラフィを整理してみました。</p>
<p>発表資料の 58 ページからご覧ください。</p>
<iframe id="talk_frame_406667" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/b39b3240b99247979b07297b660b9c9f" width="710" height="596" frameborder="0" allowfullscreen="true"></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/medley/si-kao-todezainsukiru">speakerdeck.com</a></cite>
<p>こと細かく情報をシステム 1&2 のフィルターを通すことで、タイトルやリード、本文の行間、文字サイズ、それらがシステム 2 が情報を理解しやすくするための1つ機能として捉えることができます。当たり前に使っていたスキルや機能を別の視点で捉えることで違った深い意味を見出せるようになり、大きな発見につながりました。</p>
<p>今回はタイポグラフィだったのでシステム 2 寄りのものでしたが、ビジュアルアイデンティティが強く抽象的なデザインは、意図的にシステム 2 を封じ込めシステム 1 の直感性を利用して内容を理解してもらうこともできます。</p>
<p>システム 1&2 の 2 つの特性を踏まえて使いこなすことで、スキルの深い理解はもちろんですが、感覚とは違うデザインの伝え方の新しいヒントにもなりそうです。</p>
<h1 id="さいごに">さいごに</h1>
<p>YouTube や Instagram、LINE はどちらかというと直感性を促すシステム 1 が活躍するアプリです。それだけでなく身の回りにあるサービス全体がその傾向という印象をうけます。システム 1 は普段からスイッチが入っているため、比較的に簡易なステップで働きかけることができます。</p>
<p>一方でシステム 2 は複雑な演算には耐えれず、しっかりとケアしながらでないと十分な運用ができません。ただケアをしっかりすると恩恵も大きいといえます。</p>
<p>金融、教育、雇用、そして医療と、20 年前とは比較にならないほどインターネットは人生の節目に深く関わるようになったからです。人生に関わる選択をネットで行うとき、<strong>直感だけでなくシステム 2 を働かせて熟考し納得のいく決断を行うことが、よりユーザーの利益につながる</strong>と私は考えています。</p>
<p>メドレーで提供しているサービスはユーザーの人生に少なからず関わる性質をもっています。すぐに結論づけしてもらうより、ユーザーにとって正しいと思える判断ができるように、<strong>システム 2 をうまく働かせることができる環境をデザイナーとしてつくっていきたい</strong>と思います。</p>
<h1 id="おまけ">おまけ</h1>
<p>今回のテックランチで参考にさせていただいた書籍は以下のものになります。</p>
<p>『<a href="https://www.amazon.co.jp/gp/product/B0716S2Z29/ref=bpbB0716S2Z29?pf_rd_m=AN1VRQENFRJN5&pf_rd_s=product-alert&pf_rd_r=BZN853DD5XN3M0CSRSAQ&pf_rd_t=201&pf_rd_p=458809509&pf_rd_i=4150504105">ファスト&スロー(上・下)</a>』ダニエル・カーネマン</p>
<p>『<a href="https://www.amazon.co.jp/dp/4150503915">予想通りに不合理</a>』ダン・アリエリ</p>
<p>『<a href="https://www.amazon.co.jp/dp/4163736700/">錯覚の科学</a>』クリストファー・チャブルス&ダニエル・シモンズ</p>
<p>『<a href="https://www.amazon.co.jp/dp/B00VJF2044/">UI デザインの心理学</a>』ジェフ・ジョンソン</p>
<p>今回のお話は、本当に本当に表面の部分になります。流し読みでも参考になるので手に取って見てください。またこういうことに興味がわいたデザイナーさん・エンジニアさん、是非是非メドレーに遊びに来てください!(絶賛募集中です!)</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、ジョブメドレーだけでなく、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」やオンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」、口コミで探せる介護施設の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」などのプロダクトも提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https://www.medley.jp/recruit/creative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>
<p>今後ともメドレーを、よろしくお願いいたします!</p>medley
- クライアント認証と Path Based Routing が必要なサーバを AWS で構築(後編:App 層)https://developer.medley.jp/entry/2017/08/24/120000_02https://developer.medley.jp/entry/2017/08/24/120000_02今回の内容について
メドレー開発本部の田中です。
先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があ...Thu, 24 Aug 2017 03:00:00 GMT<h1 id="今回の内容について">今回の内容について</h1>
<p>メドレー開発本部の<a href="https://www.wantedly.com/companies/medley/post_articles/57252">田中</a>です。
先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があることで苦労した点もあり(<a href="https://developer.medley.jp/entry/2017/08/24/120000_01">前編参照</a>)、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます。
<a href="https://developer.medley.jp/entry/2017/08/24/120000_01">前編では</a>Proxy 層の構成として、主に Nginx を使用した Path Based Routing 周りについてのお話でした。後編では App 層で使用した EC2、 Systems Manager パラメータストアあたりについて共有いたします。</p>
<h1 id="app-層の構成">App 層の構成</h1>
<p>App 層の方針や構築の流れ等をまとめると以下の通りです。</p>
<ul>
<li>ゴールデンイメージとして OS 設定やサーバアプリケーションをインストールした image(AMI)を作成しておく</li>
<li>上記の AMI を元に、クライアント毎に EC2 インスタンスを作成する
<ul>
<li>インスタンス作成時に必要な Tag の値や環境変数を設定しておく</li>
<li>環境変数はパラメータストアに登録</li>
</ul>
</li>
<li>EC2 インスタンス起動時に、クライアントに応じた Tag や環境変数をもとにサーバアプリケーションのセットアップを行う</li>
<li>自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する</li>
</ul>
<p>それでは、それぞれの詳細について説明していきたいと思います。</p>
<h2 id="ami-作成">AMI 作成</h2>
<p><a href="https://www.packer.io/">Packer</a>を使用して各インスタンス共通となる AMI を作成します。<code>provisioners</code>で指定した構築用スクリプトで OS 設定や必要ライブラリ、またメインとなるサーバアプリケーションをインストールします。また、cloud-init を使用して初回起動時に動かすスクリプト類もコピーしておきます。</p>
<p>なお、cloud-init から実行するスクリプトは Git や S3 などから動的に取得する方法もありますが、さほどスクリプトの内容に変更は発生しない点と、内容的に変更ある場合は image 再作成がどちらにしても必要になりそうだったので割り切って image 内に含めることにしています。</p>
<p>作成した packer.json の<code>provisioners</code>部分を抜粋するとこのような感じになります(説明コメント部分は実際には記載していません)</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="color:#CE9178"> "provisioners"</span><span style="color:#D4D4D4">: [</span></span>
<span class="line"><span style="color:#F44747"> --</span><span style="color:#F44747"> type:</span><span style="color:#F44747"> shell</span><span style="color:#F44747"> として、構築用スクリプト指定。ビルド時に実行される</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"shell"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "scripts"</span><span style="color:#D4D4D4">: [</span></span>
<span class="line"><span style="color:#CE9178"> "scripts/provision.sh"</span></span>
<span class="line"><span style="color:#D4D4D4"> ]</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F44747"> --</span><span style="color:#F44747"> type:</span><span style="color:#F44747"> file</span><span style="color:#F44747"> でインスタンス起動時に実行させるスクリプト群をコピー</span></span>
<span class="line"><span style="color:#F44747"> --</span><span style="color:#F44747"> これらのスクリプトは</span><span style="color:#F44747"> cloud-init</span><span style="color:#F44747"> から実行される(cloud-init</span><span style="color:#F44747"> の設定は別途インスタンス作成時に行っている)</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"file"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "source"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"./scripts"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "destination"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"/home/hoge"</span></span>
<span class="line"><span style="color:#D4D4D4"> },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F44747"> --</span><span style="color:#F44747"> 上記のスクリプトに対して実行権限付与</span></span>
<span class="line"><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> "type"</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">"shell"</span><span style="color:#D4D4D4">,</span></span>
<span class="line"><span style="color:#9CDCFE"> "inline"</span><span style="color:#D4D4D4">: [</span></span>
<span class="line"><span style="color:#CE9178"> "chmod +x /home/hoge/scripts/*"</span></span>
<span class="line"><span style="color:#D4D4D4"> ]</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> ]</span></span></code></pre>
<p><code>packer build</code> でビルドした image が AWS に今回の共通で使用する AMI として登録されます</p>
<h2 id="ec2-インスタンス作成">EC2 インスタンス作成</h2>
<p>作成した AMI を元に、クライアントごとのインスタンスを作成します。なお、インスタンス作成は <code>Terraform</code>や<code>CloudFormation</code>などは使わず、AWS CLI を利用したスクリプトを作成して実行しています。</p>
<p>インスタンス作成スクリプトはこのような流れの処理となります。</p>
<ul>
<li>引数でクライアント識別 ID やその他サーバアプリケーションセットアップに必要となる環境変数を指定</li>
<li>AWS CLI で EC2 インスタンス作成</li>
<li>引数で指定された環境変数を AWS CLI でパラメータストアに登録</li>
</ul>
<h3 id="インスタンス作成">インスタンス作成</h3>
<p>以下のように、<code>aws ec2 run-instances</code> コマンドを使用し、Tag にクライアント識別 ID を指定して作成しています。
ここで指定したクライアント識別 ID を元にパラメータストアから自分用の環境変数を登録/取得したり、Private DNS 用のドメインに使用します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">aws</span><span style="color:#CE9178"> ec2</span><span style="color:#CE9178"> run-instances</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --image-id</span><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">AMI_ID</span><span style="color:#D4D4D4">} </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --key-name</span><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">KEY_NAME</span><span style="color:#D4D4D4">}</span></span>
<span class="line"><span style="color:#DCDCAA"> --region</span><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">REGION</span><span style="color:#D4D4D4">} </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --subnet-id</span><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">SUBNET_ID</span><span style="color:#D4D4D4">} </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --security-group-ids</span><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">SECURITY_GROUP</span><span style="color:#D4D4D4">} </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --user-data</span><span style="color:#CE9178"> file://</span><span style="color:#D4D4D4">${</span><span style="color:#9CDCFE">USER_DATA</span><span style="color:#D4D4D4">} </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --instance-type</span><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">INSTANCE_TYPE</span><span style="color:#D4D4D4">} </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --tag-specifications</span><span style="color:#CE9178"> "ResourceType=instance,Tags=[{Key=ClientId,Value=${</span><span style="color:#9CDCFE">CLIENT_ID</span><span style="color:#CE9178">}}]"</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --iam-instance-profile</span><span style="color:#CE9178"> "Arn=${</span><span style="color:#9CDCFE">SERVICE_ROLE</span><span style="color:#CE9178">}"</span></span>
<span class="line"></span>
<span class="line"></span></code></pre>
<p><code>user-data</code> には初回起動時に実行したいスクリプト(Packer でビルド時にコピーしておいたスクリプト)を指定しているだけとなります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A9955">#!/bin/bash</span></span>
<span class="line"><span style="color:#DCDCAA">/home/hoge/scripts/bootstrap.sh</span></span></code></pre>
<h3 id="パラメータストアに環境変数登録">パラメータストアに環境変数登録</h3>
<p>使用する環境変数は、Key は共通ですが値がクライアントによって異なります。そのため、HOGE という Key を使用する場合、<code><クライアント識別 ID>.HOGE</code> という形式でパラメータストアに登録しています。</p>
<p>(注. パラメータストアに<a href="https://aws.amazon.com/jp/about-aws/whats-new/2017/06/amazon-ec2-systems-manager-adds-hierarchy-tagging-and-notification-support-for-parameter-store/">階層やタグ付けがサポートされた</a>らしく、このあたりの構成は今後見直す予定です)</p>
<p>登録は <code>aws ssm put-parameter</code> を実行します</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#DCDCAA">aws</span><span style="color:#CE9178"> ssm</span><span style="color:#CE9178"> put-parameter</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --name</span><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">KEY</span><span style="color:#D4D4D4">} </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --value</span><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">VALUE</span><span style="color:#D4D4D4">} </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --type</span><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">PARAMETER_TYPE</span><span style="color:#D4D4D4">} </span><span style="color:#D7BA7D">\ </span><span style="color:#6A9955"> # String、SecureString など</span></span>
<span class="line"><span style="color:#DCDCAA"> --overwrite</span></span></code></pre>
<p>これでクライアントごとの EC2 インスタンスが作成、起動されます。次にインスタンス起動時の流れについてです。</p>
<h2 id="ec2-インスタンス起動">EC2 インスタンス起動</h2>
<p>起動時は、初回起動と毎回起動でそれぞれ以下のような処理を行います。</p>
<ul>
<li>初回: パラメータストアから自身に関連する環境変数を取得し、サーバアプリケーションのセットアップ</li>
<li>毎回: 自身の内部 IP を Route53 の Private DNS に登録/更新</li>
</ul>
<p>内部 IP は固定しておらず起動時に割り振られるため、毎回更新するようにしています。</p>
<p>それではそれぞれの内容について見ていきます。</p>
<h3 id="パラメータストアから環境変数取得">パラメータストアから環境変数取得</h3>
<p>登録時の内容で記載しましたが、環境変数は <code><クライアント識別 ID>.HOGE</code>という形式で登録しています。そのため、まずは自身のクライアント識別 ID を判定した後に必要な環境変数を <code>aws ssm get-parameters</code>で取得します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A9955"># 自身のインスタンス ID をメタデータから取得</span></span>
<span class="line"><span style="color:#9CDCFE">INSTANCE_ID</span><span style="color:#D4D4D4">=$(</span><span style="color:#DCDCAA">curl</span><span style="color:#569CD6"> -s</span><span style="color:#CE9178"> https://169.254.169.254/latest/meta-data/instance-id</span><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># クライアント識別 ID をインスタンス作成時に指定した Tag から取得</span></span>
<span class="line"><span style="color:#6A9955"># (describe-instances の filter に自身のインスタンス ID を指定)</span></span>
<span class="line"><span style="color:#9CDCFE">CLIENT_ID_TAG</span><span style="color:#D4D4D4">=$(</span><span style="color:#DCDCAA">aws</span><span style="color:#CE9178"> ec2</span><span style="color:#CE9178"> describe-instances</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --region=${</span><span style="color:#9CDCFE">REGION</span><span style="color:#569CD6">}</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --filters</span><span style="color:#CE9178"> "Name=instance-id,Values=${</span><span style="color:#9CDCFE">INSTANCE_ID</span><span style="color:#CE9178">}"</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4"> | </span><span style="color:#DCDCAA">jq</span><span style="color:#569CD6"> -r</span><span style="color:#CE9178"> '.Reservations[].Instances[].Tags[] | select(.Key == "ClientId").Value'</span></span>
<span class="line"><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 環境変数を取得</span></span>
<span class="line"><span style="color:#6A9955"># タイプを SecureString にしている変数もあるため、一律 --with-decryption オプションを指定している</span></span>
<span class="line"><span style="color:#9CDCFE">HOGE</span><span style="color:#D4D4D4">=$(</span><span style="color:#DCDCAA">aws</span><span style="color:#CE9178"> ssm</span><span style="color:#CE9178"> get-parameters</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --name</span><span style="color:#CE9178"> "${</span><span style="color:#9CDCFE">CLIENT_ID_TAG</span><span style="color:#CE9178">}.HOGE"</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --with-decryption</span><span style="color:#569CD6"> --region</span><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">REGION</span><span style="color:#D4D4D4">} </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#D4D4D4"> | </span><span style="color:#DCDCAA">jq</span><span style="color:#569CD6"> -r</span><span style="color:#CE9178"> ".Parameters[].Value"</span><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6">export</span><span style="color:#9CDCFE"> HOGE</span><span style="color:#D4D4D4">=${</span><span style="color:#9CDCFE">HOGE</span><span style="color:#D4D4D4">}</span></span></code></pre>
<h3 id="内部-ip-を-private-dns-に登録">内部 IP を Private DNS に登録</h3>
<p>最後に、Proxy 層から Private DNS で名前解決できるように自身の IP を Route 53 に登録してやります。</p>
<p>なお、Route53 には事前に対象の Hosted Zone を <code>Private Hosted Zone for Amazon VPC</code>タイプとして登録しておきます。ここでは例として Domain Name を local とします。</p>
<p>EC2 インスタンスから登録される RecordSet は以下の形式とします。</p>
<ul>
<li>Name: <クライアント識別 ID>.local</li>
<li>Type: CNAME</li>
<li>Value: EC2 インスタンスの内部 IP</li>
</ul>
<p>これらを行うスクリプト例は以下となります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A9955"># 内部 IP を取得</span></span>
<span class="line"><span style="color:#6A9955"># (describe-instances の filter に自身のインスタンス ID を指定)</span></span>
<span class="line"><span style="color:#9CDCFE">PRIVATE_IP</span><span style="color:#D4D4D4">=$(</span><span style="color:#DCDCAA">aws</span><span style="color:#CE9178"> ec2</span><span style="color:#CE9178"> describe-instances</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --region=${</span><span style="color:#9CDCFE">REGION</span><span style="color:#569CD6">}</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --filters</span><span style="color:#CE9178"> "Name=instance-id,Values=${</span><span style="color:#9CDCFE">INSTANCE_ID</span><span style="color:#CE9178">}"</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4"> | </span><span style="color:#DCDCAA">jq</span><span style="color:#569CD6"> -r</span><span style="color:#CE9178"> '.Reservations[].Instances[].PrivateIpAddress'</span></span>
<span class="line"><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># Route53 の登録先 Hosted Zone ID を取得</span></span>
<span class="line"><span style="color:#6A9955"># SEARCH_KEY は今回の例でいうと 'local.' になります</span></span>
<span class="line"><span style="color:#9CDCFE">HOSTED_ZONE_ID</span><span style="color:#D4D4D4">=$(</span><span style="color:#DCDCAA">aws</span><span style="color:#CE9178"> route53</span><span style="color:#CE9178"> list-hosted-zones</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --region=${</span><span style="color:#9CDCFE">REGION</span><span style="color:#569CD6">}</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#D4D4D4"> | </span><span style="color:#DCDCAA">jq</span><span style="color:#569CD6"> -r</span><span style="color:#CE9178"> ".HostedZones[] | select(.Name == </span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">${</span><span style="color:#9CDCFE">SEARCH_KEY</span><span style="color:#CE9178">}</span><span style="color:#D7BA7D">\"</span><span style="color:#CE9178">).Id"</span></span>
<span class="line"><span style="color:#D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># この後の登録コマンドで指定するための定義ファイル</span></span>
<span class="line"><span style="color:#6A9955"># 毎起動時の登録用(IP が変わるため)に、Action には 'UPSERT' を指定</span></span>
<span class="line"><span style="color:#9CDCFE">RECORDSET_FILE</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"/tmp/create_recordset.json"</span></span>
<span class="line"><span style="color:#DCDCAA">cat</span><span style="color:#D4D4D4"> <<</span><span style="color:#D4D4D4">EOT</span><span style="color:#D4D4D4"> > ${</span><span style="color:#9CDCFE">RECORDSET_FILE</span><span style="color:#D4D4D4">}</span></span>
<span class="line"><span style="color:#CE9178">{</span></span>
<span class="line"><span style="color:#CE9178"> "Changes": [</span></span>
<span class="line"><span style="color:#CE9178"> {</span></span>
<span class="line"><span style="color:#CE9178"> "Action": "UPSERT",</span></span>
<span class="line"><span style="color:#CE9178"> "ResourceRecordSet": {</span></span>
<span class="line"><span style="color:#CE9178"> "Name": "<クライアント識別 ID>.local",</span></span>
<span class="line"><span style="color:#CE9178"> "Type": "CNAME",</span></span>
<span class="line"><span style="color:#CE9178"> "TTL": 300,</span></span>
<span class="line"><span style="color:#CE9178"> "ResourceRecords": [</span></span>
<span class="line"><span style="color:#CE9178"> {</span></span>
<span class="line"><span style="color:#CE9178"> "Value": "${</span><span style="color:#9CDCFE">PRIVATE_IP</span><span style="color:#CE9178">}"</span></span>
<span class="line"><span style="color:#CE9178"> }</span></span>
<span class="line"><span style="color:#CE9178"> ]</span></span>
<span class="line"><span style="color:#CE9178"> }</span></span>
<span class="line"><span style="color:#CE9178"> }</span></span>
<span class="line"><span style="color:#CE9178"> ]</span></span>
<span class="line"><span style="color:#CE9178">}</span></span>
<span class="line"><span style="color:#D4D4D4">EOT</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"># 作成した定義ファイルを指定し、Route53 に登録</span></span>
<span class="line"><span style="color:#DCDCAA">aws</span><span style="color:#CE9178"> route53</span><span style="color:#CE9178"> change-resource-record-sets</span><span style="color:#D7BA7D"> \</span></span>
<span class="line"><span style="color:#569CD6"> --hosted-zone-id</span><span style="color:#D4D4D4"> ${</span><span style="color:#9CDCFE">HOSTED_ZONE_ID</span><span style="color:#D4D4D4">} </span><span style="color:#D7BA7D">\</span></span>
<span class="line"><span style="color:#569CD6"> --change-batch</span><span style="color:#CE9178"> file:///</span><span style="color:#D4D4D4">${</span><span style="color:#9CDCFE">RECORDSET_FILE</span><span style="color:#D4D4D4">}</span></span></code></pre>
<p>実行するステップはやや多いですが、このような構成をとることで VPC 内ではドメイン指定でのアクセスが可能となるため、IP を意識する必要がなくなるため柔軟な構成になるかと思います。</p>
<h1 id="今回のまとめ">今回のまとめ</h1>
<p>いまさらインスタンス立てるとかめんどくさいなぁ、、、とか思いながら色々調べて構築しましたが、EC2 まわりのサービスも増えてるんだなぁ、なんて感じました(特にパラメータストアはとても便利)</p>
<p>パラメータストア以外にも Systems Manager には Run Command や Patch Manager など EC2 インスタンスを管理する上でとても便利な仕組みが揃っていますのでこのあたりも導入していきたいと思います。</p>
<p>余談ですが、Systems Manager の存在は re:Invent 2016 で発表された時から名前だけは知ってましたが、今回の対応するまでずっとオンプレ専用のサービスだと勘違いしてて記憶から消えかけていました。。。</p>
<h1 id="最後に">最後に</h1>
<p><a href="https://developer.medley.jp/entry/2017/08/24/120000_01">前編を Proxy 層</a>(Nginx)、後編を App 層(EC2)について書かせていただきましたがいかかだったでしょうか。
そもそもの要件自体がけっこう特殊だったりもするので、なんでこんな構成に?みたいなとこもあるかも知れませんが、どなたかの参考になれば幸いです。もう少し聞いてみたい、というかたは wantedly の「<a href="https://www.wantedly.com/companies/medley">話を聞いてみたい</a>」ボタンからどうぞ。</p>
<p>※前編をあらためて読みたい方はこちらからどうぞ
<a href="https://developer.medley.jp/entry/2017/08/24/120000_01">https://developer.medley.jp/entry/2017/08/24/120000_01</a></p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」、オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」、医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」、口コミで探せる介護施設の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<p>メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- クライアント認証と Path Based Routing が必要なサーバを AWS で構築(前編:Proxy 層)https://developer.medley.jp/entry/2017/08/24/120000_01https://developer.medley.jp/entry/2017/08/24/120000_01今回の内容について
メドレー開発本部の田中です。
先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、後述する...Thu, 24 Aug 2017 02:00:00 GMT<h1 id="今回の内容について">今回の内容について</h1>
<p>メドレー開発本部の<a href="https://www.wantedly.com/companies/medley/post_articles/57252">田中</a>です。
先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、後述する制約から苦労した点もあり、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます(先にお伝えしておくと、特殊な事情がなければ今回のようなケースでは ALB で対応する ECS サービスに Path Based Routing してやるのが良いと思います)。</p>
<p>技術要素として、Nginx(OpenResty)/ Route53 Private DNS / EC2 / Systems Manager パラメータストア あたりに触れたいと思います。(Beanstalk は Multicontainer Docker を使用し、それも慣れるまでちょっとクセあったなぁと思ったのですが分量が多くなりそうなのでまた別の機会に共有させて頂きます)</p>
<p>まず前編として Proxy 層、主に Nginx を使用した Dynamic Path Based Routing についてお話して、<a href="https://developer.medley.jp/entry/2017/08/24/120000_02">後編は App 層</a>について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。</p>
<h1 id="設計構築する上での前提と方針">設計/構築する上での前提と方針</h1>
<p>対象となる案件を進める上での要件・制限内容は諸事情あり、ざっとまとめるとこのような感じです。</p>
<ul>
<li>環境は AWS を使用する</li>
<li>サーバアプリケーション、クライアントアプリケーションはユーザ毎で、サーバアプリケーションは共用できない(ユーザが増える度にクライアント/サーバのセットが増えるイメージ)</li>
<li>ただし、クライアントからの接続先となる Endpoint は同じだが、Host Based Routing は訳あって利用できない</li>
<li>クライアント認証を使用する</li>
</ul>
<p>上記から、以下の設計方針で進める事にしました。</p>
<ul>
<li>Proxy 層でクライアント認証を行い、Path Based Routing で対象となるサーバにリクエストを proxy する。Path 部分にクライアント別の識別 ID を含め、その値を元に Private DNS で名前解決する
<ul>
<li>例) <code>https://example.com/a-client/api</code> => <code>https://a-client.local/api</code></li>
<li>App 層は個別 EC2 インスタンスとする</li>
</ul>
</li>
</ul>
<h1 id="設計する上で悩んだ点">設計する上で悩んだ点</h1>
<p>主に 2 点ありますが、まずは Proxy 層です。出来るだけ AWS のマネージド・サービスで済ませたかったので、クライアント認証と Path Based Routing が可能でやりたい事に合うかどうか調べましたが以下の理由で断念し、普通(?)に ELB + Nginx を利用することにしました。</p>
<ul>
<li>ALB: クライアント認証に非対応。また SSL 終端となるので Nginx 側でクライアント認証が出来ない</li>
<li>API GW: クライアント認証は対応しており Routing 部分もがんばればいけるかも?、と思ったが Proxy 先が動的に増えたリするので管理ふくめ難しそうであった</li>
</ul>
<p>次に App 層の構成をどうするかでした。集積度を高めるためにコンテナ利用も検討したのですが、使用するアプリケーションの必要スペックや要件などからいまいちフィットせず、個別の EC2 インスタンスにすることにしました(今でももっと良い方法がないか悩んでたりします)</p>
<h1 id="全体構成">全体構成</h1>
<p>出来上がった全体構成のイメージは以下となります。なお台数は実環境と異なり、今回の内容と関係ない部分などは省略しています。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170823/20170823180401.png" alt="20170823180401.png">
<p>次に、今回の本題となる Proxy 層の構成について触れたいと思います。</p>
<h1 id="proxy-層の構成">Proxy 層の構成</h1>
<p>Proxy 層の方針等はまとめると以下の通りで、proxy 先の動的判定と名前解決する箇所がキモとなります。</p>
<ul>
<li>App 層のインスタンスは、起動時に自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する
<ul>
<li>クライアント識別 ID が a-client の場合、a-client.local のように登録</li>
</ul>
</li>
<li>Proxy 層の Nginx はクライアント認証を行い、リクエストパスから取り出したクライアント識別 ID を元に転送先 Endpoint を生成し、backend に proxy する
<ul>
<li>App 層のインスタンスは動的に増えるため、リクエスト時に名前解決したい(インスタンスが増える度に自動で Nginx の conf を編集することも検討したが追加数が読めず、conf がふくれあがるのもなぁ、、、という思いがあり止めました)</li>
</ul>
</li>
</ul>
<p>Nginx は backend が増えても起動しっぱなしで動的に名前解決して動作させたかったため、lua-nginx-module を導入し <code>balancer_by_lua</code> ディレクティブと <code>lua-resty-dns</code> モジュールを使用することとし、構築の手間の関係から OpenResty を導入することにしました。</p>
<h2 id="lua-nginx-module-を使用した-conf-ファイル">lua-nginx-module を使用した conf ファイル</h2>
<p>conf ファイル全体としては以下となります(関係ない箇所は省いています)。ポイントと記載した部分についての説明は後述します。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="nginx"><code><span class="line"><span style="color:#569CD6">http</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> upstream</span><span style="color:#D4D4D4"> app {</span></span>
<span class="line"><span style="color:#6A9955"> # ポイント 1.</span></span>
<span class="line"><span style="color:#6A9955"> # Private DNS で設定した IP(CNAME に設定)を元に動的 Routing</span></span>
<span class="line"><span style="color:#569CD6"> balancer_by_lua_block</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> local</span><span style="color:#9CDCFE"> balancer</span><span style="color:#D4D4D4"> = </span><span style="color:#DCDCAA">require</span><span style="color:#CE9178"> "ngx.balancer"</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> local</span><span style="color:#9CDCFE"> host</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">ngx</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">ctx</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">upstream_server</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">cname</span></span>
<span class="line"><span style="color:#569CD6"> local</span><span style="color:#9CDCFE"> port</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">'8888'</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> local</span><span style="color:#9CDCFE"> ok</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">err</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">balancer</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">set_current_peer</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">host</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">port</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> not </span><span style="color:#9CDCFE">ok</span><span style="color:#C586C0"> then</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#9CDCFE"> ngx</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">exit</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">500</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0"> end</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> server</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> listen </span><span style="color:#B5CEA8">443</span><span style="color:#D4D4D4"> ssl;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> set </span><span style="color:#D4D4D4">$</span><span style="color:#9CDCFE">proxy_upstream_host</span><span style="color:#CE9178"> ''</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6"> set </span><span style="color:#D4D4D4">$</span><span style="color:#9CDCFE">proxy_upstream_domain</span><span style="color:#CE9178"> '.local'</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> location</span><span style="color:#D4D4D4"> ^~ </span><span style="color:#D16969">/api/ </span><span style="color:#D4D4D4">{</span></span>
<span class="line"><span style="color:#569CD6"> rewrite_by_lua_block</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> -- path からクライアント識別 ID を取得し、Private DNS に設定したドメインを生成</span></span>
<span class="line"><span style="color:#6A9955"> -- https://example.com/<id>/api という形式のリクエストから、<id>.local というドメインを生成して</span></span>
<span class="line"><span style="color:#6A9955"> -- ngx.var.proxy_upstream_host 変数に格納</span></span>
<span class="line"><span style="color:#569CD6"> local</span><span style="color:#9CDCFE"> ngx_re</span><span style="color:#D4D4D4"> = </span><span style="color:#DCDCAA">require</span><span style="color:#CE9178"> "ngx.re"</span></span>
<span class="line"><span style="color:#569CD6"> local</span><span style="color:#9CDCFE"> res</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">err</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">ngx_re</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">split</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">ngx</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">var</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">request_uri</span><span style="color:#D4D4D4">, </span><span style="color:#CE9178">"/"</span><span style="color:#D4D4D4">, </span><span style="color:#569CD6">nil</span><span style="color:#D4D4D4">, {</span><span style="color:#9CDCFE">pos</span><span style="color:#D4D4D4"> = </span><span style="color:#B5CEA8">0</span><span style="color:#D4D4D4">})</span></span>
<span class="line"><span style="color:#569CD6"> local</span><span style="color:#D4D4D4"> id = res[3]</span></span>
<span class="line"><span style="color:#D4D4D4"> ngx.var.</span><span style="color:#569CD6">proxy_upstream_host</span><span style="color:#D4D4D4"> = id..ngx.var.proxy_upstream_domain;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> --</span><span style="color:#569CD6"> resolver </span><span style="color:#D4D4D4">設定</span></span>
<span class="line"><span style="color:#D4D4D4"> local resolver = require </span><span style="color:#CE9178">"resty.dns.resolver"</span></span>
<span class="line"><span style="color:#D4D4D4"> local r, err = resolver:new{</span></span>
<span class="line"><span style="color:#D4D4D4"> nameservers = {{</span><span style="color:#CE9178">"x.x.x.x"</span><span style="color:#D4D4D4">, 53}}, -- 使用する nameserver</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> if not r then</span></span>
<span class="line"><span style="color:#D4D4D4"> ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)</span></span>
<span class="line"><span style="color:#D4D4D4"> end</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> -- ポイント 2.</span></span>
<span class="line"><span style="color:#D4D4D4"> -- 生成したドメイン名(<id>.local)を元に名前解決し、取得した結果を ngx.ctx にセット</span></span>
<span class="line"><span style="color:#D4D4D4"> -- (balancer_by_lua_block で使用する)</span></span>
<span class="line"><span style="color:#D4D4D4"> local answers, err = r:query(ngx.var.proxy_upstream_host, { qtype = r.TYPE_CNAME })</span></span>
<span class="line"><span style="color:#D4D4D4"> if not answers then</span></span>
<span class="line"><span style="color:#D4D4D4"> ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)</span></span>
<span class="line"><span style="color:#D4D4D4"> end</span></span>
<span class="line"><span style="color:#D4D4D4"> if answers.errcode then</span></span>
<span class="line"><span style="color:#D4D4D4"> ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)</span></span>
<span class="line"><span style="color:#D4D4D4"> end</span></span>
<span class="line"><span style="color:#D4D4D4"> ngx.ctx.upstream_server = answers[1]</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> proxy_set_header Host $</span><span style="color:#9CDCFE">host</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6"> proxy_set_header </span><span style="color:#D4D4D4">X-Real-IP $</span><span style="color:#9CDCFE">remote_addr</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6"> proxy_set_header </span><span style="color:#D4D4D4">X-Forwarded-For $</span><span style="color:#9CDCFE">proxy_add_x_forwarded_for</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6"> proxy_set_header </span><span style="color:#D4D4D4">X-Forwarded-Proto $</span><span style="color:#9CDCFE">scheme</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> -- https://<id>.local/api に proxy</span></span>
<span class="line"><span style="color:#D4D4D4"> rewrite </span><span style="color:#D16969">^/api/(.+)$</span><span style="color:#D4D4D4"> /api/ break;</span></span>
<span class="line"><span style="color:#569CD6"> proxy_pass </span><span style="color:#D4D4D4">https://app;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<h3 id="ポイント-1-動的-routing">ポイント 1. 動的 Routing</h3>
<p><code>balancer.set_current_peer</code> にて proxy 先を動的に設定します。</p>
<p><code>host</code> 部分にはドメインを直接指定することができないため、ポイント 2. で <code>ngx.ctx</code> にセットした DNS の値から IP(Route53 に CNAME レコードとして設定している)を指定しています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="lua"><code><span class="line"><span style="color:#DCDCAA">balancer_by_lua_block</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> local</span><span style="color:#9CDCFE"> balancer</span><span style="color:#D4D4D4"> = </span><span style="color:#DCDCAA">require</span><span style="color:#CE9178"> "ngx.balancer"</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> -- ngx.ctx にセットしていた、Private DNS から取得した内部 IP をセット</span></span>
<span class="line"><span style="color:#569CD6"> local</span><span style="color:#9CDCFE"> host</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">ngx</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">ctx</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">upstream_server</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">cname</span></span>
<span class="line"><span style="color:#569CD6"> local</span><span style="color:#9CDCFE"> port</span><span style="color:#D4D4D4"> = </span><span style="color:#CE9178">'8888'</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color:#6A9955"> -- proxy 先セット。host にドメインは直接指定できない</span></span>
<span class="line"><span style="color:#569CD6"> local</span><span style="color:#9CDCFE"> ok</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">err</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">balancer</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">set_current_peer</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">host</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">port</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> not </span><span style="color:#9CDCFE">ok</span><span style="color:#C586C0"> then</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#9CDCFE"> ngx</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">exit</span><span style="color:#D4D4D4">(</span><span style="color:#B5CEA8">500</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0"> end</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>詳細については OpenResty の<a href="https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/balancer.md">ドキュメント</a>を参照してください</p>
<h3 id="ポイント-2-動的名前解決">ポイント 2. 動的名前解決</h3>
<p><code>r:query</code> にて、生成したドメイン名(<code><id>.local</code>)を問い合わせます。<code>r</code> 部分は <code>resolver:new</code> で nameserver を指定した resolver となります。</p>
<p>なお、nameserver に指定する IP は今回は Route53 の Private DNS を指定するため、外部 nameserver ではなくローカルの nameserver(10.0.0.2 など)を指定することになります。</p>
<p>問い合わせ結果の<code>answers</code>部分は Lua table 形式の配列となります。今回の例でいうと対象は 1 件となるので、その値を<code>balancer_by_lua_block</code>で使用するために<code>ngx.ctx</code>にセットしています。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="lua"><code><span class="line"><span style="color:#569CD6">local</span><span style="color:#9CDCFE"> answers</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">err</span><span style="color:#D4D4D4"> = </span><span style="color:#4EC9B0">r</span><span style="color:#D4D4D4">:</span><span style="color:#DCDCAA">query</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">ngx</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">var</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">proxy_upstream_host</span><span style="color:#D4D4D4">, { </span><span style="color:#9CDCFE">qtype</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">r</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">TYPE_CNAME</span><span style="color:#D4D4D4"> })</span></span>
<span class="line"><span style="color:#C586C0">if</span><span style="color:#D4D4D4"> not </span><span style="color:#9CDCFE">answers</span><span style="color:#C586C0"> then</span></span>
<span class="line"><span style="color:#9CDCFE"> ngx</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">exit</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">ngx</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">HTTP_INTERNAL_SERVER_ERROR</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0">end</span></span>
<span class="line"><span style="color:#C586C0">if</span><span style="color:#9CDCFE"> answers</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">errcode</span><span style="color:#C586C0"> then</span></span>
<span class="line"><span style="color:#9CDCFE"> ngx</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">exit</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">ngx</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">HTTP_INTERNAL_SERVER_ERROR</span><span style="color:#D4D4D4">)</span></span>
<span class="line"><span style="color:#C586C0">end</span></span>
<span class="line"><span style="color:#9CDCFE">ngx</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">ctx</span><span style="color:#D4D4D4">.</span><span style="color:#4EC9B0">upstream_server</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">answers</span><span style="color:#D4D4D4">[</span><span style="color:#B5CEA8">1</span><span style="color:#D4D4D4">]</span></span></code></pre>
<p>詳細については OpenResty の<a href="https://github.com/openresty/lua-resty-dns">ドキュメント</a>を参照してください</p>
<h1 id="今回のまとめ">今回のまとめ</h1>
<p>upstream 先を動的に判定して proxy するという要件はそうそう無いかもしれませんし、途中までは複雑な構成になりそうだなぁとドキドキしてしましたが、結果としてはそれなりにシンプルになったかなと思います。今更ながら Nginx(と lua module)は柔軟で良く出来てるなぁという感想でした。</p>
<p>後編は App 層について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。</p>
<div class="remark-link-card-plus__container">
<a href="https://developer.medley.jp/entry/2017/08/24/120000_02" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">クライアント認証と Path Based Routing が必要なサーバを AWS で構築(後編:App 層) | MEDLEY Developer Portal</div>
<div class="remark-link-card-plus__description">今回の内容について
メドレー開発本部の田中です。
先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があ...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://developer.medley.jp/icon.png" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">developer.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://developer.medley.jp/_astro/2017_08_24_02.B7Y26eOY.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="参考リンク">参考リンク</h1>
<p>構築にあたり、下記記事を参考にさせていただきました。ありがとうございます。</p>
<div class="remark-link-card-plus__container">
<a href="https://qiita.com/toritori0318/items/a9305d528b52936c0573" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">Nginx balancer_by_luaの話とupstream名前解決の話 - Qiita</div>
<div class="remark-link-card-plus__description">balancer_by_lua_xxxxx いつの間にやら1 lua-nginx-module に balancer_by_lua_xxx という新しいディレクティブが増えていました。 以下ドキュメントより抜粋。 http { upstream backend {...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://cdn.qiita.com/assets/favicons/public/production-c620d3e403342b1022967ba5e3db1aaa.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">qiita.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-user-contents.imgix.net%2Fhttps%253A%252F%252Fcdn.qiita.com%252Fassets%252Fpublic%252Farticle-ogp-background-afbab5eb44e0b055cce1258705637a91.png%3Fixlib%3Drb-4.0.0%26w%3D1200%26blend64%3DaHR0cHM6Ly9xaWl0YS11c2VyLXByb2ZpbGUtaW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnFpaXRhLWltYWdlLXN0b3JlLnMzLmFtYXpvbmF3cy5jb20lMkYwJTJGOTA5OSUyRnByb2ZpbGUtaW1hZ2VzJTJGMTQ3MzY4MTI2MD9peGxpYj1yYi00LjAuMCZhcj0xJTNBMSZmaXQ9Y3JvcCZtYXNrPWVsbGlwc2UmYmc9RkZGRkZGJmZtPXBuZzMyJnM9MWQ0NjVlYTJmNzdhN2U2NzBmYTZjZGFjYzAyMmI2ZmQ%26blend-x%3D120%26blend-y%3D467%26blend-w%3D82%26blend-h%3D82%26blend-mode%3Dnormal%26s%3D9a60042f7aabc5c9a0d37d74f4b8e966?ixlib=rb-4.0.0&w=1200&fm=jpg&mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTk2MCZoPTMyNCZ0eHQ9TmdpbnglMjBiYWxhbmNlcl9ieV9sdWElRTMlODElQUUlRTglQTklQjElRTMlODElQTh1cHN0cmVhbSVFNSU5MCU4RCVFNSU4OSU4RCVFOCVBNyVBMyVFNiVCMSVCQSVFMyU4MSVBRSVFOCVBOSVCMSZ0eHQtYWxpZ249bGVmdCUyQ3RvcCZ0eHQtY29sb3I9JTIzMUUyMTIxJnR4dC1mb250PUhpcmFnaW5vJTIwU2FucyUyMFc2JnR4dC1zaXplPTU2JnR4dC1wYWQ9MCZzPTE5Zjk0OWJhMDUyYmE2YjYxNDIxYmY3YWJlZTQ0Zjg2&mark-x=120&mark-y=112&blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTgzOCZoPTU4JnR4dD0lNDB0b3JpdG9yaTAzMTgmdHh0LWNvbG9yPSUyMzFFMjEyMSZ0eHQtZm9udD1IaXJhZ2lubyUyMFNhbnMlMjBXNiZ0eHQtc2l6ZT0zNiZ0eHQtcGFkPTAmcz0xMTEzMDAxY2U3MGUxZmU5NmRlODBkYmYwZmJkZDM3MQ&blend-x=242&blend-y=480&blend-w=838&blend-h=46&blend-fit=crop&blend-crop=left%2Cbottom&blend-mode=normal&s=d4fc32e18ed3588b7ad19806c98f5b95" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」、オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」、医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」、口コミで探せる介護施設の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<p>メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/recruit/creative.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- NDB オープンデータをオープン化してみた話https://developer.medley.jp/entry/2017/08/17/170000https://developer.medley.jp/entry/2017/08/17/170000開発本部の平山です。先日、社内勉強会「TechLunch」にて社外に公開できない内容の発表をしてしまいましたので、その代わりとして、厚生労働省が提供する「NDB オープンデータ」をオープン化した話について、ブログを書こうと思います。
ND...Thu, 17 Aug 2017 08:00:00 GMT<p>開発本部の平山です。先日、社内勉強会「TechLunch」にて社外に公開できない内容の発表をしてしまいましたので、その代わりとして、厚生労働省が提供する「NDB オープンデータ」をオープン化した話について、ブログを書こうと思います。 </p>
<h1 id="ndb-オープンデータとは">NDB オープンデータとは?</h1>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="第1回 NDB オープンデータ |厚生労働省" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.mhlw.go.jp%2Fstf%2Fseisakunitsuite%2Fbunya%2F0000139390.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation" style="font-style: normal; font-size: 14.4px; opacity: 0.75; display: block;"><a href="https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/0000139390.html" style="color: #999999; opacity: 1; font-size: 11.52px;">www.mhlw.go.jp</a></cite>
<blockquote>
<p>作成の背景</p>
<p>◆ レセプト情報・特定健診等情報データベース(NDB)は、悉皆性が高いレセプト情報、および検査値などの詳細な情報を有する特定健診等情報が含まれており、国民の医療動向を評価するうえで有用なデータだと考えられている。</p>
<p>◆ 2011 年度より、医療費適正化計画策定に資する目的以外での NDB データの利用が認められたが、NDB データの機微性の高さに鑑み、利用者に対しては高いレベルのセキュリティ要件を課したうえで、データ提供が行われてきた。</p>
<p>◆ 一方で、多くの研究者が必ずしも詳細な個票データを必要とするわけではないため、多くの人々が使用できるような、あらかじめ定式化された集計データを NDB データをもとに整備することが重要ではないか、という議論が有識者会議等でなされてきた。</p>
<p>◆ NDB の民間提供に関する議論でも、「レセプト情報等の提供に関するワーキンググループ」からの報告では、汎用性が高く様々なニーズに一定程度応えうる基礎的な集計表を作成し、公表していくことがむしろ適当である、という指摘がみられた。</p>
<p>作成の目的</p>
<p>◆ 多くの人々が NDB データに基づいた保健医療に関する知見に接することが出来るよう、NDB データを用いて基礎的な集計表を作成したうえで、公表する。</p>
<p>◆ NDB データに基づき、医療の提供実態や特定健診等の結果をわかりやすく示す。</p>
</blockquote>
<p>要は皆さんが、病院に行った時にもらう明細書に記載されている初診〇〇点、外来診療料〇〇点のようなデータが個人情報が匿名化された状態で収集しその統計データを一般に公開する、といったところでしょうか。</p>
<p>このようなデータがオープンになっていることはとても意義のあることだと思いますし、公開にまでこぎつけた関係者の苦労が想像されます。しかし、このような画期的なデータ提供ではありますが、Excel ファイルでの提供となっており、かつ加工がしづらいデータ構造になっているため、データを細かくみてみようとすると非常に手間がかかるという問題があります。</p>
<h1 id="ndb-オープンデータのオープン化">NDB オープンデータのオープン化</h1>
<p>そこで NDB オープンデータとして公開されている Excel ファイルを加工し、DB に格納し BI ツール(<a href="https://redash.io/">Redash</a>)から参照させるようにしてみました。</p>
<h2 id="1-データ加工--db-取り込み">1. データ加工 & DB 取り込み</h2>
<p><a href="https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/0000139390.html">公開サイト</a>にある医科診療行為に関する Excel ファイルを取得し、ログテーブルとしてよくあるフォーマットに変換し DB に取り込む。</p>
<h3 id="変換前">変換前</h3>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170721/20170721150757.png" alt="f:id:medley_inc:20170721150757p:plain" title="f:id:medley_inc:20170721150757p:plain"></p>
<h3 id="変換後">変換後</h3>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>*************************** 1. row ***************************</span></span>
<span class="line"><span>id: 1</span></span>
<span class="line"><span>practice_category_code: A000</span></span>
<span class="line"><span>practice_category_name: 初診料</span></span>
<span class="line"><span>practice_code: 111000110</span></span>
<span class="line"><span>practice_name: 初診</span></span>
<span class="line"><span>practice_type: 外来</span></span>
<span class="line"><span>target: all</span></span>
<span class="line"><span>revision: 2014</span></span>
<span class="line"><span>prefecture:</span></span>
<span class="line"><span>sex:</span></span>
<span class="line"><span>age:</span></span>
<span class="line"><span>score: 251700771</span></span>
<span class="line"><span>created_at: 0000-00-00 00:00:00</span></span>
<span class="line"><span>updated_at: 0000-00-00 00:00:00</span></span>
<span class="line"><span>*************************** 2. row ***************************</span></span>
<span class="line"><span>id: 2</span></span>
<span class="line"><span>practice_category_code: A000</span></span>
<span class="line"><span>practice_category_name: 初診料</span></span>
<span class="line"><span>practice_code: 111000110</span></span>
<span class="line"><span>practice_name: 初診</span></span>
<span class="line"><span>practice_type: 外来</span></span>
<span class="line"><span>target: sex_age</span></span>
<span class="line"><span>revision: 2014</span></span>
<span class="line"><span>prefecture:</span></span>
<span class="line"><span>sex: 男性</span></span>
<span class="line"><span>age: 0~4 歳</span></span>
<span class="line"><span>score: 13158090</span></span>
<span class="line"><span>created_at: 0000-00-00 00:00:00</span></span>
<span class="line"><span>updated_at: 0000-00-00 00:00:00</span></span>
<span class="line"><span>*************************** 3. row ***************************</span></span>
<span class="line"><span>id: 3</span></span>
<span class="line"><span>practice_category_code: A000</span></span>
<span class="line"><span>practice_category_name: 初診料</span></span>
<span class="line"><span>practice_code: 111000110</span></span>
<span class="line"><span>practice_name: 初診</span></span>
<span class="line"><span>practice_type: 外来</span></span>
<span class="line"><span>target: sex_age</span></span>
<span class="line"><span>revision: 2014</span></span>
<span class="line"><span>prefecture:</span></span>
<span class="line"><span>sex: 男性</span></span>
<span class="line"><span>age: 5~9 歳</span></span>
<span class="line"><span>score: 12444947</span></span>
<span class="line"><span>created_at: 0000-00-00 00:00:00</span></span>
<span class="line"><span>updated_at: 0000-00-00 00:00:00</span></span></code></pre>
<h2 id="2-データの参照">2. データの参照</h2>
<p>変換したデータを取り込んだ DB を Redash から参照。分析したいデータを取得するためのクエリを書いてダッシュボード化。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170721/20170721153210.png" alt="f:id:medley_inc:20170721153210p:plain" title="f:id:medley_inc:20170721153210p:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170726/20170726125246.png" alt="f:id:medley_inc:20170726125246p:plain" title="f:id:medley_inc:20170726125246p:plain"></p>
<h3 id="ndb-オープンデータの活用例">NDB オープンデータの活用例</h3>
<p>以下に簡単なデータ活用のサンプルを載せました。医薬診療行為だけでなく特定健診や薬剤のデータを使うともう少し面白い気付きがあるかもしれません。</p>
<p>いずれにせよ、このように加工可能な形でのデータ提供こそがオープンデータ提供の価値だと思うので、このような仕組みが加速すれば良いなと思います。</p>
<h4 id="0-4-歳-男性-診療行為点数">0-4 歳 男性 診療行為点数</h4>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170817/20170817101900.png" alt="f:id:dev-medley:20170817101900p:plain" title="f:id:dev-medley:20170817101900p:plain"></p>
<h4 id="90-歳以上-男性-診療行為点数">90 歳以上 男性 診療行為点数</h4>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170817/20170817101934.png" alt="f:id:dev-medley:20170817101934p:plain" title="f:id:dev-medley:20170817101934p:plain"></p>
<h4 id="140023350-胃瘻より流動食点滴注入-都道府県別">140023350 胃瘻より流動食点滴注入 都道府県別</h4>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170817/20170817102021.png" alt="f:id:dev-medley:20170817102021p:plain" title="f:id:dev-medley:20170817102021p:plain"></p>
<h4 id="150086210-角膜移植術-年齡別">150086210 角膜移植術 年齡別</h4>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170817/20170817102100.png" alt="f:id:dev-medley:20170817102100p:plain" title="f:id:dev-medley:20170817102100p:plain"></p>
<h1 id="まとめ">まとめ</h1>
<p>以上、NDB オープンデータをオープン化してみた話について書いてみました。</p>
<p>このように.go.jp から提供されるデータは一般的に Excel や PDF でのファイル提供が基本で、インターネットサービスのように API のような形で提供されることはありません。せっかく貴重なデータが提供されているにも関わらず、それが IT システムと連動しづらいことで、活用されない状況になっているのはとても残念なことに思います。<a href="https://www.codeforamerica.org/">Code for America</a>の事例ではないですが、もっとインターネット系の人材がこのような取り組みに入り込んでいくようになれば、より合理的でスマートな仕組みが加速し、業界全体の IT 化も加速するのではないでしょうか。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」、オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」、医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」、口コミで探せる介護施設の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<p>メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- デザイン言語システムを入れたらコミュニケーションコストがぐっと下がった話〜メドレー TechLunch〜https://developer.medley.jp/entry/2017/08/03/160000https://developer.medley.jp/entry/2017/08/03/160000ビールが美味しい季節ですね!
最近飲みすぎて嫁に叱られて、飲み会自粛中のデザイナー・マエダです。
メドレーでは TechLunch という社内勉強会を実施しているのですが、デザインについて私も発表する機会をいただきましたので、その内容を紹介...Thu, 03 Aug 2017 07:00:00 GMT<p>ビールが美味しい季節ですね!</p>
<p>最近飲みすぎて嫁に叱られて、飲み会自粛中のデザイナー・<a href="https://www.wantedly.com/companies/medley/post_articles/49119">マエダ</a>です。</p>
<p>メドレーでは TechLunch という社内勉強会を実施しているのですが、デザインについて私も発表する機会をいただきましたので、その内容を紹介させていただきます。テーマは「DLS の導入について」です。発表資料は記事の最後をご覧ください。</p>
<h1 id="dlsデザイン言語システムとは">DLS(デザイン言語システム)とは</h1>
<p>DLS とは DesignLanguageSystem の略で、すごい単純にいえばデザインガイドラインみたいに<strong>UI に一貫性をもたせるため、配色やレイアウト、タイポグラフィやマージンなどのルールを策定するもの</strong>です。</p>
<p>私が主に担当しているオンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」は、iOS、Android、Web と 3 つのプラットフォームで運用しているのですが、入社した当初はプラットフォーム毎に違った UI やルールで開発しており、サービスとして一貫性のあるサービス体験を提供できるとは言えない状況でした。また新たに機能を追加する際、それぞれ違なるデザインをしなければならず、デザイン作業においても負荷がかかっていました。</p>
<h1 id="dls-が必要な理由">DLS が必要な理由</h1>
<p>CLINICS ではプラットフォームごとに異なる UI を提供していたため、一貫したサービス品質をユーザーに提供できていないこと、開発者ごとに UI に対して認識にズレが生じていたことが課題でした。それに伴い開発速度も決して速いとは言えず、どうにか一定の品質を担保しつつ開発スピードも改善できないかと悩んでいたところ、Airbnb の開発者ブログで Airbnb Design System という記事を見かけました。</p>
<p>これまでのデザインガイドラインは単にカラーやマージンの定義を取り決めるだけだったのですが、<strong>Airbnb ではデザイン言語として定義し、他の言語と同じようにチームと共有し、エンジニア・デザイナー同士で理解できる設計を作り上げている</strong>といった内容でした。</p>
<p>※参考</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Building a Visual Language – Airbnb Design – Medium" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmedium.com%2Fairbnb-design%2Fbuilding-a-visual-language-behind-the-scenes-of-our-airbnb-design-system-224748775e4e" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://medium.com/airbnb-design/building-a-visual-language-behind-the-scenes-of-our-airbnb-design-system-224748775e4e">medium.com</a></cite>
<p>私がデザイナーとしてサービスデザインに携わる重要な役割のひとつとして、単にデザインをするだけでなく<strong>デザインを通して UI 設計の制約をつくり、継続的に運用しやすいプロダクトに仕上げること</strong>があると思っています。</p>
<p>DLS を起点として、各プラットフォームの開発者が共通の認識でシステム開発が行えれば、これまで以上にスピーディが開発が行え、一貫性のある体験をユーザーに提供できるプロダクトにできるはずだと考え、CLINICS 独自の DLS を開発することにしました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170725/20170725121754.png" alt="f:id:dev-medley:20170725121754p:plain" title="f:id:dev-medley:20170725121754p:plain">
CLINICS の DLS の一部</p>
<h1 id="dls-を導入してどうなったか">DLS を導入してどうなったか</h1>
<p>CLINICS では、Android、iOS、web で担当するエンジニアが異なるのですが、DLS を設計しシステムの詳細を共有することで、UI に対しての共通認識が生まれ、一貫した品質を担保できるようになりました。さらにこれまでエンジニアだけでデザインを考えて悩んでいた時間を、コンポーネント設計で組み立てたデザインを DLS で定義したことで、UI に悩むことなく機能ロジックに重点を置いた開発に専念できるようになったのではないかと思います。</p>
<p>デザイン言語でサービス設計の基礎を築けたことで、開発だけに追われていた状況から、より良いサービスを作るためにはどんな UX をユーザーに提供すべきかという声がエンジニアからも頻繁に声が上がりはじめたことも良かった点です。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170725/20170725122157.jpg" alt="f:id:dev-medley:20170725122157j:plain" title="f:id:dev-medley:20170725122157j:plain">
エンジニア陣が UI について熱い議論を交わしている様子</p>
<p>TechLunch では、こうした内容について実際に作ったコンポーネント設計なども見せながらお話しました。発表資料はこちら。</p>
<iframe id="talk_frame_401874" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/fef793505d9e4dcb8163cf1f13233a90" width="710" height="596" frameborder="0" allowfullscreen="true"></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/medley/clinics-dls-fan-shi-che-di-tesainyan-yu-sisutemufalsekoshao-jie">speakerdeck.com</a></cite>
<h1 id="まとめ">まとめ</h1>
<p>DLS 導入以前は、エンジニアが開発に追われていたということもあり、プラットフォーム毎に議論ができていなかったエンジニアがお互いの担当プラットフォームを意識したコミュニケーションがとれるようになったことは予想外に良かった点です(別に仲が悪かったとかそういうことではなく w)。</p>
<p>さらに DLS で UI の基盤をつくったことで、デザイナーが手を動かさずともコンポーネントの組み合わせを話し合うだけで、エンジニア完結で一貫した品質で機能実装できるようになりました。これにより私も次の施策やプロジェクトに専念できるようになり、効率的に仕事ができるようになりました。</p>
<p>まだデザインルールに一貫性がないプロジェクトを担当しているデザイナーやエンジニアは、ぜひ DLS の導入を検討してみてはいかがでしょうか。
単にデザイン品質だけでなく、チームコミュニケーションも改善されるとおもいます。</p>
<p>より詳しく話を聞きたいかたは、気軽に「<a href="https://www.wantedly.com/companies/medley">話を聞きに行きたい</a>」をクリックしてみてください。ビールを飲みながらデザイン談義をしましょう!(嫁に怒られない程度に w)</p>
<p>メドレー開発本部について、もっと詳しく知りたい方はこちらからどうぞ。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="株式会社メドレーの最新情報 - Wantedly" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fmedley" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.wantedly.com/companies/medley">www.wantedly.com</a></cite>medley
- 元フロントエンドエンジニアから見た Android 開発https://developer.medley.jp/entry/2017/07/28/125000https://developer.medley.jp/entry/2017/07/28/125000今回の内容について
みなさん、こんにちは。開発本部でオンライン診療アプリ「CLINICS」の開発を担当している平木です。
弊社では、インフラ・サーバ・フロントで役割を区切らず、全ての開発メンバーが必要に応じてスキルを広げながら開発に取り組ん...Fri, 28 Jul 2017 03:50:00 GMT<h1 id="今回の内容について">今回の内容について</h1>
<p>みなさん、こんにちは。開発本部でオンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」の開発を担当している<a href="https://profile.hatena.ne.jp/Layzie/">平木</a>です。</p>
<p>弊社では、インフラ・サーバ・フロントで役割を区切らず、全ての開発メンバーが必要に応じてスキルを広げながら開発に取り組んでいます。 自分も入社前はフロントエンド専門のエンジニアでしたが、入社後はそれに加えて Rails を使ったサーバサイドの開発や、Swift を使った iOS アプリ開発、 そして、現在メインにやっている Android 開発と一通りのプラットフォームや言語を使って開発するようになっています。</p>
<p>エンジニアが自身のスキルを広げる場合、自分の経験や知識を応用して、新しいプラットフォームを理解していくということが多いと思います。</p>
<p>元フロントエンジニアの経験を持っている自分が Android 開発に関わってみて <strong>やりやすかった部分</strong>や<strong>つらかった部分</strong>などを書いていこうと思います。同じような立場でこれから開発しようと思う方には少しお役に立てるかもしれません。</p>
<h1 id="やりやすかった部分">やりやすかった部分</h1>
<h2 id="android-studio-のありがたさ">Android Studio のありがたさ</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170727/20170727194320.png" alt="20170727194320.png">
<p>いきなり IDE の話になっていますが。やはり Android Studio のオールインワン感がとても便利です。Web フロントエンドの開発だと IDE といえば WebStorm など使ったことがありましたが、ライブラリを含めた補完周りなどはやっぱり Java などでの開発した方が IDE は力を発揮しやすいんではないかと思いました。</p>
<p>とりあえずほとんど設定をいじらなくても、補完やドキュメントの参照ができるのはラクです。また JS などで変数に変換してもあんまり便利さが湧きませんが、 Java の場合だと勝手に対応した型もつけてくれたりと至れり尽くせりだと思いました。</p>
<p>ゲッターセッターなども手作業でやると単純作業になりますが、⌘N で勝手に展開してくれますし、Java での開発の冗長な部分をあまり感じないで開発できるのが凄いです。</p>
<p>エミュレータなども自分が使い始めたときは既に起動も高速な感じでしたし、エミュレータの管理なども Android Studio 内で完結するのであまり迷ったりすることがないです。また初心者に優しいなと思ったのが、Activity など作るのにテンプレートがちゃんと用意されてるのであまりファイル構成が分かってない初めの頃でもちゃんと画面を作ることができるところです。</p>
<h2 id="ビルドシステムが分かりやすい">ビルドシステムが分かりやすい</h2>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170727/20170727194345.png" alt="20170727194345.png">
<p>ご存知 Gradle ですが、通常の使い方してる分には(あまり Gradle でがんばらなければ)、WebPack とかよりも書きやすいよねと思います。もちろん目的が違いますが。</p>
<p>フロントエンド開発でよく陥りがちな「設定ファイルなんだかプログラムなのか分からないくらい作られた」というような設定ファイルにはなりにくいんじゃないかと感じています。</p>
<p><code>build.gradle</code> で設定した環境変数などもソースからさっと使うことができますし、良く考えられてるなーと思いました。</p>
<p>公式のライブラリであれば、 <code>build.gradle</code> で lint としてライブラリの更新があることが分かるのも親切です。サードパーティのライブラリもこれがされてるととても嬉しいんですが、 <code>Analyze -> Run Inspection by Name -> Newer Library Versions Available</code> で見てくれるんでガマンしてます。</p>
<h2 id="view-部分の作り方がフロントエンドに似ているので取っつきやすい">View 部分の作り方がフロントエンドに似ているので取っつきやすい</h2>
<p>これは HTML / CSS を書いていた人間からすると大変に敷居が低いと思いました。</p>
<p>使うパーツは最初はどんなものがあるのか分からないのでドキュメント引いたり、 <code>Design</code> タブからパーツを選択したりして View を作ってましたが、HTML を組む感覚でガンガンと作っていけました。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170727/20170727195637.png" alt="20170727195637.png">
<p>基本的には HTML タグのなかで <code>style</code> 属性付けていくみたいな感じです。<code>margin</code> とか <code>padding</code> とかも同じですし、<code>textAlign</code> なんかもあるので初期のころは本当に助かりました。</p>
<p>もちろん <code>AppBarLayout</code> とか <code>Toolbar</code> とか <code>Theme</code> など Android 独自の概念はありますがそういった差分的なものは知識として覚えていけば良いだけなので、フロントエンドに慣れていると大変に View 作るのがラクだと感じます。</p>
<p>iOS の StoryBoard は未だにつらい。</p>
<h2 id="ググったときに古めの情報を参考にしても問題が解決できる後方互換性の高さ">ググったときに古めの情報を参考にしても問題が解決できる後方互換性の高さ</h2>
<p>ここ数年フロントエンドの技術的な流れが早すぎるというのは一般的に良く言われていますが、良い意味で Android は古い情報もちゃんと使えるというのはとても助かりました。</p>
<p>一番古いので 2011 年くらいの情報だったら使えました。(もちろん廃止されてる…というのも多いのですが)</p>
<p>初めのうちは <strong>フロントエンドだとこうやるけど Android ではどうするんだろう</strong> というような疑問が多いと思うのですが、特に View に関わるような問題などは古い記述でも問題なく使える後方互換性の高さのおかげで「ググったように書いたのに、今は使えない…」というストレスからかなり開放されます。</p>
<h1 id="つらかった部分">つらかった部分</h1>
<p><strong>ライブラリや画像を気軽に入れにくい</strong></p>
<p>これついては、フロントエンドの JS などでも別に気軽にさくさくとライブラリを入れていたわけではないですが、いざ入れようとすると Android(というよりもネイティブアプリ全般か)の場合全て APK の容量増加という自体に直結するので中々気をつかいます。</p>
<p>さすがにゲームでもないのに容量重いアプリにするとそもそもダウンロードしてもらえるかどうかも怪しくなります。パッケージする画像なども多用するような仕様にしないようにしています。</p>
<p>あと最初のころにビルドできずに困るわ…と思って必死に対応を調べた <a href="https://developer.android.com/studio/build/multidex.html">64K 参照制限</a> も一回設定してしまえば何てことはないんですが、割と初見殺しだと思います。</p>
<p>また実際にプロダクション用にビルドするとなるとフロントエンドでもおなじみのソースの難読化をするのですがその際にライブラリのなかで難読化しちゃいけないなどの指定をしないと余裕で動かなくなります。</p>
<p>ということで各ライブラリを使うときには<a href="https://www.guardsquare.com/en/proguard">ProGuard</a>の設定をしないといけないのですが、これも気軽にライブラリ使おうと中々ならない理由でした。</p>
<p><strong>バッググラウンドプロパティ関連の貧弱さ</strong></p>
<p>良い点のところで HTML / CSS と View の作りが似ているので幸せと書いたんですが、1 つだけ許せないことがありまして、それが <strong>バックグラウンドプロパティ関連のプロパティの貧弱さ</strong> というものです。</p>
<p>どういうことかというと…例えばこんな感じのラベル作るとします。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170727/20170727194414.png" alt="20170727194414.png">
<p>HTML / CSS だったら楽勝だと思います。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="html"><code><span class="line"><span style="color:#808080"><</span><span style="color:#569CD6">span</span><span style="color:#9CDCFE"> class</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"radius"</span><span style="color:#808080">></span><span style="color:#D4D4D4">診察待ち</span><span style="color:#808080"></</span><span style="color:#569CD6">span</span><span style="color:#808080">></span></span></code></pre>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="css"><code><span class="line"><span style="color:#D7BA7D">.radius</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#9CDCFE"> border-radius</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">12px</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#9CDCFE"> padding</span><span style="color:#D4D4D4">: </span><span style="color:#B5CEA8">4px</span><span style="color:#B5CEA8"> 12px</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#9CDCFE"> background-color</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">#89c8ba</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#9CDCFE"> color</span><span style="color:#D4D4D4">: </span><span style="color:#CE9178">#ffffff</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>しかし Android の場合は</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="xml"><code><span class="line"><span style="color:#808080"><</span><span style="color:#569CD6">TextView</span></span>
<span class="line"><span style="color:#9CDCFE"> android:id</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@+id/upcoming_list_status"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:textSize</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"14sp"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_width</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"100dp"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_height</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"wrap_content"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:textAlignment</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"center"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:background</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@drawable/label_status_upcoming"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:textColor</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"#FFFFFF"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:text</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"診察待ち"</span><span style="color:#808080"> /></span></span></code></pre>
<p>とテキスト作ったうえで、 <code>background</code> の指定先のベクターアセットを作ってやらないといけません。
このアセットが <code>padding</code> や <code>background-color</code> など指定されているというものになります。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="xml"><code><span class="line"><span style="color:#808080"><?</span><span style="color:#569CD6">xml</span><span style="color:#9CDCFE"> version</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"1.0"</span><span style="color:#9CDCFE"> encoding</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"utf-8"</span><span style="color:#808080">?></span></span>
<span class="line"><span style="color:#808080"><</span><span style="color:#569CD6">selector</span><span style="color:#9CDCFE"> xmlns:android</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"https://schemas.android.com/apk/res/android"</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#569CD6">item</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#569CD6">shape</span><span style="color:#9CDCFE"> android:shape</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"rectangle"</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#569CD6">stroke</span><span style="color:#9CDCFE"> android:width</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"1dp"</span><span style="color:#9CDCFE"> android:color</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@color/label_status_upcoming"</span><span style="color:#808080"> /></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#569CD6">corners</span><span style="color:#9CDCFE"> android:bottomLeftRadius</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"12dp"</span><span style="color:#9CDCFE"> android:bottomRightRadius</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"12dp"</span><span style="color:#9CDCFE"> android:topLeftRadius</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"12dp"</span><span style="color:#9CDCFE"> android:topRightRadius</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"12dp"</span><span style="color:#808080"> /></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#569CD6">solid</span><span style="color:#9CDCFE"> android:color</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@color/label_status_upcoming"</span><span style="color:#808080"> /></span></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#569CD6">padding</span><span style="color:#9CDCFE"> android:left</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"12dp"</span><span style="color:#9CDCFE"> android:right</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"12dp"</span><span style="color:#9CDCFE"> android:top</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"2dp"</span><span style="color:#9CDCFE"> android:bottom</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"2dp"</span><span style="color:#808080"> /></span></span>
<span class="line"><span style="color:#808080"> </</span><span style="color:#569CD6">shape</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"> </</span><span style="color:#569CD6">item</span><span style="color:#808080">></span></span>
<span class="line"><span style="color:#808080"></</span><span style="color:#569CD6">selector</span><span style="color:#808080">></span></span></code></pre>
<p>とても面倒です。</p>
<h2 id="リストがつらい">リストがつらい</h2>
<p>配列になっているデータを取り出してリストとして表示するという状況、良くあると思います。
フロントエンドなら <code><ul/></code> で囲んだなかの <code><li/></code> にデータを <code>forEach()</code> やら <code>map()</code> やら使って作っていくのが常道ですが、Android は手順が多いので面倒です。</p>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/dev-medley/20170727/20170727194509.png" alt="20170727194509.png">
<p>このようなお知らせのリストを作るのに…</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="java"><code><span class="line"><span style="color:#569CD6">public</span><span style="color:#569CD6"> class</span><span style="color:#4EC9B0"> NotificationItemHelper</span><span style="color:#D4D4D4"> {</span></span>
<span class="line"><span style="color:#569CD6"> private</span><span style="color:#4EC9B0"> String</span><span style="color:#9CDCFE"> message</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6"> private</span><span style="color:#4EC9B0"> String</span><span style="color:#9CDCFE"> link</span><span style="color:#D4D4D4">;</span></span>
<span class="line"><span style="color:#569CD6"> private</span><span style="color:#4EC9B0"> String</span><span style="color:#9CDCFE"> patientName</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> public</span><span style="color:#DCDCAA"> NotificationItemHelper</span><span style="color:#D4D4D4">(</span><span style="color:#4EC9B0">String</span><span style="color:#9CDCFE"> message</span><span style="color:#D4D4D4">, </span><span style="color:#4EC9B0">String</span><span style="color:#9CDCFE"> link</span><span style="color:#D4D4D4">, </span><span style="color:#4EC9B0">String</span><span style="color:#9CDCFE"> patientName</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> this</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">message</span><span style="color:#D4D4D4"> = message;</span></span>
<span class="line"><span style="color:#569CD6"> this</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">link</span><span style="color:#D4D4D4"> = link;</span></span>
<span class="line"><span style="color:#569CD6"> this</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">patientName</span><span style="color:#D4D4D4"> = patientName;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> public</span><span style="color:#4EC9B0"> String</span><span style="color:#DCDCAA"> getMessage</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4"> message;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> public</span><span style="color:#4EC9B0"> String</span><span style="color:#DCDCAA"> getLink</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4"> link;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> public</span><span style="color:#4EC9B0"> String</span><span style="color:#DCDCAA"> getPatientName</span><span style="color:#D4D4D4">() {</span></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4"> patientName;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>リストに表示するためのクラスを作ってあげて、</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="java"><code><span class="line"><span style="color:#569CD6">public</span><span style="color:#569CD6"> class</span><span style="color:#4EC9B0"> NotificationListAdapter</span><span style="color:#569CD6"> extends</span><span style="color:#4EC9B0"> ArrayAdapter</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">NotificationItemHelper</span><span style="color:#D4D4D4">> {</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> private</span><span style="color:#4EC9B0"> int</span><span style="color:#9CDCFE"> layoutResource</span><span style="color:#D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#569CD6"> public</span><span style="color:#DCDCAA"> NotificationListAdapter</span><span style="color:#D4D4D4">(</span><span style="color:#4EC9B0">Context</span><span style="color:#9CDCFE"> context</span><span style="color:#D4D4D4">, </span><span style="color:#4EC9B0">int</span><span style="color:#9CDCFE"> resource</span><span style="color:#D4D4D4">, </span><span style="color:#4EC9B0">List</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">NotificationItemHelper</span><span style="color:#D4D4D4">> </span><span style="color:#9CDCFE">objects</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#569CD6"> super</span><span style="color:#D4D4D4">(context, resource, objects);</span></span>
<span class="line"><span style="color:#569CD6"> this</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">layoutResource</span><span style="color:#D4D4D4"> = resource;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D4D4D4"> @</span><span style="color:#4EC9B0">NonNull</span></span>
<span class="line"><span style="color:#D4D4D4"> @</span><span style="color:#4EC9B0">Override</span></span>
<span class="line"><span style="color:#569CD6"> public</span><span style="color:#4EC9B0"> View</span><span style="color:#DCDCAA"> getView</span><span style="color:#D4D4D4">(</span><span style="color:#4EC9B0">int</span><span style="color:#9CDCFE"> position</span><span style="color:#D4D4D4">, </span><span style="color:#4EC9B0">View</span><span style="color:#9CDCFE"> convertView</span><span style="color:#D4D4D4">, </span><span style="color:#4EC9B0">ViewGroup</span><span style="color:#9CDCFE"> parent</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#4EC9B0"> View</span><span style="color:#9CDCFE"> view</span><span style="color:#D4D4D4"> = convertView;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (view == </span><span style="color:#569CD6">null</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#4EC9B0"> LayoutInflater</span><span style="color:#9CDCFE"> layoutInflater</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">LayoutInflater</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">from</span><span style="color:#D4D4D4">(</span><span style="color:#DCDCAA">getContext</span><span style="color:#D4D4D4">());</span></span>
<span class="line"><span style="color:#D4D4D4"> view = </span><span style="color:#9CDCFE">layoutInflater</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">inflate</span><span style="color:#D4D4D4">(layoutResource, </span><span style="color:#569CD6">null</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#4EC9B0"> NotificationItemHelper</span><span style="color:#9CDCFE"> notificationItemHelper</span><span style="color:#D4D4D4"> = </span><span style="color:#DCDCAA">getItem</span><span style="color:#D4D4D4">(position);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (notificationItemHelper != </span><span style="color:#569CD6">null</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#4EC9B0"> TextView</span><span style="color:#9CDCFE"> message</span><span style="color:#D4D4D4"> = (TextView) </span><span style="color:#9CDCFE">view</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">findViewById</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">R</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">notification_list_message</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#4EC9B0"> TextView</span><span style="color:#9CDCFE"> patientName</span><span style="color:#D4D4D4"> = (TextView) </span><span style="color:#9CDCFE">view</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">findViewById</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">R</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">notification_list_patient_name</span><span style="color:#D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (message != </span><span style="color:#569CD6">null</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#9CDCFE"> message</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setText</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">notificationItemHelper</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getMessage</span><span style="color:#D4D4D4">());</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> if</span><span style="color:#D4D4D4"> (patientName != </span><span style="color:#569CD6">null</span><span style="color:#D4D4D4">) {</span></span>
<span class="line"><span style="color:#9CDCFE"> patientName</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setText</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">notificationItemHelper</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getPatientName</span><span style="color:#D4D4D4">());</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C586C0"> return</span><span style="color:#D4D4D4"> view;</span></span>
<span class="line"><span style="color:#D4D4D4"> }</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span></code></pre>
<p>それを View に表示させるための Adapter というものを作り、</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="xml"><code><span class="line"><span style="color:#808080"><?</span><span style="color:#569CD6">xml</span><span style="color:#9CDCFE"> version</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"1.0"</span><span style="color:#9CDCFE"> encoding</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"utf-8"</span><span style="color:#808080">?></span></span>
<span class="line"><span style="color:#808080"><</span><span style="color:#569CD6">LinearLayout</span><span style="color:#9CDCFE"> xmlns:android</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"https://schemas.android.com/apk/res/android"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:id</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@+id/upcoming_list"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_width</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"match_parent"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_height</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"match_parent"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:paddingStart</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@dimen/spacing_small"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:paddingEnd</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@dimen/spacing_small"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:orientation</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"horizontal"</span><span style="color:#808080">></span></span>
<span class="line"></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#569CD6">LinearLayout</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_width</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"0dp"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_height</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"wrap_content"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_weight</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"1"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:paddingTop</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@dimen/spacing_small"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:paddingBottom</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@dimen/spacing_small"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:orientation</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"vertical"</span><span style="color:#808080">></span></span>
<span class="line"></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#569CD6">TextView</span></span>
<span class="line"><span style="color:#9CDCFE"> android:id</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@+id/notification_list_message"</span></span>
<span class="line"><span style="color:#9CDCFE"> style</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@style/TextBoldStyle"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_width</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"match_parent"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_height</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"wrap_content"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_marginBottom</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@dimen/spacing_xsmall"</span><span style="color:#808080"> /></span></span>
<span class="line"></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#569CD6">TextView</span></span>
<span class="line"><span style="color:#9CDCFE"> android:id</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@+id/notification_list_patient_name"</span></span>
<span class="line"><span style="color:#9CDCFE"> style</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@style/TextNormalStyle"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_width</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"match_parent"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_height</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"wrap_content"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_marginBottom</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@dimen/spacing_xsmall"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:textColor</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@color/text_color_secondary"</span><span style="color:#808080"> /></span></span>
<span class="line"><span style="color:#808080"> </</span><span style="color:#569CD6">LinearLayout</span><span style="color:#808080">></span></span>
<span class="line"></span>
<span class="line"><span style="color:#808080"> <</span><span style="color:#569CD6">ImageView</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_width</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"wrap_content"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_height</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"wrap_content"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_gravity</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"end|center_vertical"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:src</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@drawable/ic_keyboard_arrow_right_black_24dp"</span><span style="color:#808080"> /></span></span>
<span class="line"><span style="color:#808080"></</span><span style="color:#569CD6">LinearLayout</span><span style="color:#808080">></span></span></code></pre>
<p>実際のリストの内容を xml で作ってあげて、</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="xml"><code><span class="line"><span style="color:#808080"><</span><span style="color:#569CD6">ListView</span></span>
<span class="line"><span style="color:#9CDCFE"> android:id</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"@+id/notification_list"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_width</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"match_parent"</span></span>
<span class="line"><span style="color:#9CDCFE"> android:layout_height</span><span style="color:#D4D4D4">=</span><span style="color:#CE9178">"match_parent"</span><span style="color:#808080"> /></span></span></code></pre>
<p>リストのラッパーを指定して</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="java"><code><span class="line"><span style="color:#4EC9B0">List</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">NotificationItemHelper</span><span style="color:#D4D4D4">> </span><span style="color:#9CDCFE">notificationItemList</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">new</span><span style="color:#4EC9B0"> ArrayList</span><span style="color:#D4D4D4"><>();</span></span>
<span class="line"><span style="color:#4EC9B0">ListView</span><span style="color:#9CDCFE"> notificationListView</span><span style="color:#D4D4D4"> = </span><span style="color:#DCDCAA">findViewById</span><span style="color:#D4D4D4">(</span><span style="color:#9CDCFE">R</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">id</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">notification_list</span><span style="color:#D4D4D4">);</span></span>
<span class="line"><span style="color:#4EC9B0">List</span><span style="color:#D4D4D4"><</span><span style="color:#4EC9B0">NotificationResponse</span><span style="color:#D4D4D4">> </span><span style="color:#9CDCFE">notifications</span><span style="color:#D4D4D4"> = </span><span style="color:#9CDCFE">notificationsResponse</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getNotificationResponses</span><span style="color:#D4D4D4">();</span></span>
<span class="line"><span style="color:#C586C0">for</span><span style="color:#D4D4D4"> (</span><span style="color:#4EC9B0">Notification</span><span style="color:#9CDCFE"> notification</span><span style="color:#C586C0"> :</span><span style="color:#D4D4D4"> notifications) {</span></span>
<span class="line"><span style="color:#4EC9B0"> NotificationItemHelper</span><span style="color:#9CDCFE"> item</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">new</span><span style="color:#DCDCAA"> NotificationItemHelper</span><span style="color:#D4D4D4">(</span></span>
<span class="line"><span style="color:#9CDCFE"> notification</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getMessage</span><span style="color:#D4D4D4">(),</span></span>
<span class="line"><span style="color:#9CDCFE"> notification</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getLink</span><span style="color:#D4D4D4">(),</span></span>
<span class="line"><span style="color:#9CDCFE"> notification</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">getPatientName</span><span style="color:#D4D4D4">());</span></span>
<span class="line"><span style="color:#9CDCFE"> notificationItemList</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">add</span><span style="color:#D4D4D4">(item);</span></span>
<span class="line"><span style="color:#D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#4EC9B0">NotificationListAdapter</span><span style="color:#9CDCFE"> notificationListAdapter</span><span style="color:#D4D4D4"> = </span><span style="color:#C586C0">new</span><span style="color:#DCDCAA"> NotificationListAdapter</span><span style="color:#D4D4D4">(</span><span style="color:#569CD6">this</span><span style="color:#D4D4D4">, </span><span style="color:#9CDCFE">R</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">layout</span><span style="color:#D4D4D4">.</span><span style="color:#9CDCFE">notification_layout</span><span style="color:#D4D4D4">, notificationItemList);</span></span>
<span class="line"><span style="color:#9CDCFE">notificationListView</span><span style="color:#D4D4D4">.</span><span style="color:#DCDCAA">setAdapter</span><span style="color:#D4D4D4">(notificationListAdapter);</span></span></code></pre>
<p>ここまでやってようやくリストが表示されます。未だに面倒だなーと思います。</p>
<h1 id="最後に">最後に</h1>
<p>色々と細かい例を上げましたが、いかがでしょうか。</p>
<p>今回は書いてませんが、ライブラリも JS とは考え方違って面白いなあと思うものがあります。特に View パーツを簡単に選んだり、イベントを書くことができる<a href="https://jakewharton.github.io/butterknife/">ButterKnife</a>やバリデーションライブラリの<a href="https://github.com/ragunathjawahar/android-saripaar">Android Saripaar</a>のように Java のアノテーションを付けるだけで簡単に使える部分などは Java に触れてこなかった身としては新鮮でした。</p>
<p>全体的な印象としてはプラットフォーム特有のクセなどはもちろんありますが、フロントエンドエンジニアでもかなり取り組みやすいのではないかと感じています。何より Android 実機で自分が作ったものが動いてるのを見るとブラウザで自分が作ったサイトが動いているというのとは一味違った達成感があります。</p>
<p>もちろん開発していく内に、たとえば JS にはないスレッドの概念が立ち塞がったり Android 特有のライフサイクルに悩まされたりもしますが、公式のドキュメントも充実していますし、Stack Overflow など見て解決することがかなり多いです。</p>
<p>これを読んで Android 開発やってみようと思っていただけたら良いなと思います。
もっといろいろ知りたいという方は「<a href="https://www.wantedly.com/companies/medley">話を聞いてみたい</a>」ボタンを押してもらえれば!</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは医療業界に存在する課題に IT を駆使して取り組んでいきたいデザイナー・エンジニアを募集中です。
皆さまからのご応募お待ちしております。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/jobs/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">採用情報 | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーの採用情報はこちらからご確認ください。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/team/creator-story.html" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">メンバーのストーリー | 株式会社メドレー</div>
<div class="remark-link-card-plus__description">メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。...</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>medley
- WebPushAPI を用いたブラウザでのプッシュ通知開発 〜メドレー TechLunch〜https://developer.medley.jp/entry/2017/07/13/170000https://developer.medley.jp/entry/2017/07/13/170000開発本部の宮内です。
先日、社内勉強会「TechLunch」にて WebPushAPI についての発表を行いましたので、その紹介をさせていただきたいと思います。
WebPushAPI とは?
一般的にプロダクトにおいて、スマートフォンアプリ...Thu, 13 Jul 2017 08:00:00 GMT<p>開発本部の<a href="https://github.com/miyucy">宮内</a>です。</p>
<p>先日、社内勉強会「TechLunch」にて WebPushAPI についての発表を行いましたので、その紹介をさせていただきたいと思います。</p>
<h1 id="webpushapi-とは">WebPushAPI とは?</h1>
<p>一般的にプロダクトにおいて、<a href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A1%BC%A5%C8%A5%D5%A5%A9%A5%F3">スマートフォン</a>アプリのような「プッシュ通知」を導入しようと思った場合、いままでは専用のアプリケーションを開発する必要がありました。</p>
<p>しかし、プッシュ通知に関する<a href="https://d.hatena.ne.jp/keyword/API">API</a>が<a href="https://d.hatena.ne.jp/keyword/W3C">W3C</a>で標準化が進み(まだドラフト状態とはいえ)、<a href="https://d.hatena.ne.jp/keyword/Google%20Chrome">Google Chrome</a>のバージョン 42、<a href="https://d.hatena.ne.jp/keyword/Mozilla%20Firefox">Mozilla Firefox</a>のバージョン 44 に、WebPushAPI が導入され、元来ある Web アプリケーションに簡単にプッシュ通知を取り込むことが可能になりました。
詳しくは <a href="https://developers.google.com/web/fundamentals/getting-started/codelabs/push-notifications/">ウェブアプリへのプッシュ通知の追加 </a>、<a href="https://developer.mozilla.org/ja/docs/Web/API/Push_API/Using_the_Push_API">Using the Push API</a>や、<a href="https://gihyo.jp/magazine/wdpress/archive/2017/vol97">WEB+DB PRESS Vol.97</a> などを読むとより理解が深まるので興味ある方はぜひ読んでみてください。</p>
<h1 id="techlunch-でやったこと">TechLunch でやったこと</h1>
<p>TechLunch では、自分が実際に実装した WebPushAPI のサンプルコードを元に、動作を示しながら挙動を開発本部の全員で追っていきました。</p>
<iframe id="talk_frame_399319" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/3649c540494d48a6a95ae667b39370e8" width="710" height="596" frameborder="0" allowfullscreen="true"></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/medley/webpushapiwoshi-tutemiyou">speakerdeck.com</a></cite>
<p>具体的な手順などもスライドにありますが、公開したものだけだとちょっと分かりにくいかもですね。ごめんなさい。</p>
<p>もっと詳しく聞きたいなって人がいたら、気軽に<a href="https://www.wantedly.com/companies/medley">ここらへん</a>にある「話を聞きに行きたい」ボタンを押下してみてください。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」、オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」、医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」、口コミで探せる<a href="https://d.hatena.ne.jp/keyword/%B2%F0%B8%EE%BB%DC%C0%DF">介護施設</a>の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<p>メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="CREATOR'S STORY | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fteam%2Fcreator-story.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/team/creator-story.html">www.medley.jp</a></cite>medley
- React.js 入門について発表しましたhttps://developer.medley.jp/entry/2017/06/29/161001https://developer.medley.jp/entry/2017/06/29/161001開発本部の楊です。医療介護の求人サイト「ジョブメドレー」の開発を担当しております。うるさい 5 歳のこどもと一緒に遊ぶのが大好きです。
ジョブメドレーの求人機関向け管理画面は React.js を利用し開発しています。
他プロダクトを担当す...Thu, 29 Jun 2017 07:10:01 GMT<p>開発本部の楊です。医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」の開発を担当しております。うるさい 5 歳のこどもと一緒に遊ぶのが大好きです。</p>
<p>ジョブメドレーの求人機関向け管理画面は React.js を利用し開発しています。
他プロダクトを担当するメンバのなかには React.js について詳しくない人もいる為、TechLunch(開発本部内で定期的に開催している社内技術勉強会)で React.js 入門について話しました。</p>
<h1 id="reactjs-とは">React.js とは</h1>
<ul>
<li>UI を構築するためのライブラリ</li>
<li><a href="https://d.hatena.ne.jp/keyword/Facebook">Facebook</a>社が開発しているライブラリでコミュニティも活発で安心して使える</li>
<li>多くの<a href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>が提供されており開発が捗る(参照:<a href="https://github.com/brillout/awesome-react-components%EF%BC%89">https://github.com/brillout/awesome-react-components)</a></li>
</ul>
<p>などなど、様々な特徴があります。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170626/20170626185005.png" alt=""></p>
<h1 id="techlunch-での発表内容">TechLunch での発表内容</h1>
<p>React.js は UI を構築するためのライブラリなのでデータフローを管理する仕組みは提供していません。
開発の際にはデータフロー管理のために、Flux/Immutable.js などの考え方や技術要素を必要とします。
今回の TechLunch の発表では Flux/Immutable.js についても簡単に触れつつ発表しました。</p>
<p>目次はこちら。</p>
<ul>
<li>React.js</li>
<li>Flux</li>
<li>Immutable.js</li>
</ul>
<p>詳細はこちらをご覧ください。</p>
<h1 id="まとめ">まとめ</h1>
<ul>
<li>全ては Component、シンプルに保つことを意識する</li>
<li>小さく Component を作り、組み立てることにより UI を構築する</li>
<li>Flux と併用しデータフローを意識する</li>
<li>Immutable.js 便利</li>
</ul>
<h1 id="最後に">最後に</h1>
<p>React.js は比較的新しい技術で、仮想 DOM の使用によりブラウザの<a href="https://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>が早いなどの利点が多々あります。
必要に応じて新しめの技術も取り入れながら、ジョブメドレーをよりよいプロダクトにしていきたいと思っています。</p>
<h1 id="お知らせ">お知らせ</h1>
<p>メドレーでは、ジョブメドレーだけでなく、医師たちがつくるオンライン医療事典「<a href="https://medley.life/">MEDLEY</a>」やオンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」、口コミで探せる<a href="https://d.hatena.ne.jp/keyword/%B2%F0%B8%EE%BB%DC%C0%DF">介護施設</a>の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」などのプロダクトも提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。</p>
<p>今後ともメドレーを、よろしくお願いいたします!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="CREATOR'S STORY | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fteam%2Fcreator-story.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/team/creator-story.html">www.medley.jp</a></cite>medley
- 島根県松江市でお試し勤務してきましたhttps://developer.medley.jp/entry/2017/05/23/171859https://developer.medley.jp/entry/2017/05/23/171859こんにちは、平山です。しれっと公式ブログ初登場です。
5 月 10 日から 5 月 13 日まで、総務省の「お試しサテライトオフィス」事業を利用して島根県松江市で 3 泊 4 日のお試し勤務をしてきました。そうです、Rubyの聖地といわれる...Tue, 23 May 2017 08:18:59 GMT<p>こんにちは、<a href="https://toppa.medley.jp/">平山</a>です。しれっと公式ブログ初登場です。</p>
<p>5 月 10 日から 5 月 13 日まで、<a href="https://d.hatena.ne.jp/keyword/%C1%ED%CC%B3%BE%CA">総務省</a>の<a href="https://www.otameshi-satellite-office.jp/">「お試しサテライトオフィス」事業</a>を利用して<a href="https://d.hatena.ne.jp/keyword/%C5%E7%BA%AC%B8%A9">島根県</a><a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BB%D4">松江市</a>で 3 泊 4 日のお試し勤務をしてきました。そうです、<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の聖地といわれるあの<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BB%D4">松江市</a>です。</p>
<p>オンライン診療アプリ CLINICS が<a href="https://rubybiz.jp/">Ruby biz グランプリ</a>で受賞したことをきっかけに、<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BB%D4">松江市</a>から<a href="https://www1.city.matsue.shimane.jp/jigyousha/sangyou/kigyou/otameshi.html">お試しサテライトオフィスの提案</a>をうけていましたが、ちょうど別件で松江に行く用事ができたので、せっかくならばお試し勤務をしてみようということで開発本部の 3 名で松江まで行ってきました。</p>
<h1 id="day1---松江テルサ別館でお試し勤務">DAY1 - 松江テルサ別館でお試し勤務</h1>
<p><a href="https://d.hatena.ne.jp/keyword/%B1%A9%C5%C4%B6%F5%B9%C1">羽田空港</a>から<a href="https://d.hatena.ne.jp/keyword/%BD%D0%B1%C0%B6%F5%B9%C1">出雲空港</a>まで 1 時間半のフライト、<a href="https://d.hatena.ne.jp/keyword/%BD%D0%B1%C0%B6%F5%B9%C1">出雲空港</a>から<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BB%D4">松江市</a>街まで 40 分の空港連絡バスでの移動、東京から約 2-3 時間で<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BB%D4">松江市</a>に到着しました。思ったより近いんですね。</p>
<p>1 日目は<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%B1%D8">松江駅</a>前にある「松江テルサ別館」でのお試し勤務。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516171143.jpg" alt="f:id:medley_inc:20170516171143j:plain" title="f:id:medley_inc:20170516171143j:plain"></p>
<p>お試しオフィスの隣にはエンジニアの交流拠点「<a href="https://www1.city.matsue.shimane.jp/jigyousha/sangyou/ruby/rabo_open.html">松江オープンソースラボ</a>」があり、<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の神さま Matz さん(の看板)が我々を迎えてくれました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516171155.jpg" alt="f:id:medley_inc:20170516171155j:plain" title="f:id:medley_inc:20170516171155j:plain"></p>
<p><a href="https://d.hatena.ne.jp/keyword/%A5%AA%A1%BC%A5%D7%A5%F3%A5%BD%A1%BC%A5%B9">オープンソース</a>ラボの自販機の売上の一部は<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>アソシエーションの支援金になるようです。さすが<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a> City MATSUE ですね。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516171309.jpg" alt="f:id:medley_inc:20170516171309j:plain" title="f:id:medley_inc:20170516171309j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516171253.jpg" alt="f:id:medley_inc:20170516171253j:plain" title="f:id:medley_inc:20170516171253j:plain"></p>
<p>お昼は「<a href="https://www.yakumoan.jp/">八雲庵</a>」で<a href="https://d.hatena.ne.jp/keyword/%BD%D0%B1%C0%A4%BD%A4%D0">出雲そば</a> 。独特の食べ方で食べるそばはとてもおいしく、また庭の池には立派な鯉が泳いでいたりとたいへん風情のあるお店でした。そばを食べている時に池の鯉がどこかから飛んできた鷹に連れ去られるのを見て、松江はすごいところだなと思いました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516171325.jpg" alt="f:id:medley_inc:20170516171325j:plain" title="f:id:medley_inc:20170516171325j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516171338.jpg" alt="f:id:medley_inc:20170516171338j:plain" title="f:id:medley_inc:20170516171338j:plain"></p>
<p>お昼のあとはお試し勤務を本格的に開始(上: お試し勤務前の記念写真)。机、椅子、スタンディングデスク、<a href="https://d.hatena.ne.jp/keyword/Bluetooth">Bluetooth</a>スピーカー、ビデオ会議用カメラなど、オフィス設備はいずれも本格的なものが用意されていて、仕事をするのにまったく不自由がない環境でした(下:せっかくなので本社にいるメンバーとビデオ会議)。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516123923.jpg" alt="f:id:medley_inc:20170516123923j:plain" title="f:id:medley_inc:20170516123923j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516123922.jpg" alt="f:id:medley_inc:20170516123922j:plain" title="f:id:medley_inc:20170516123922j:plain"></p>
<p>1 日目は移動で疲れたこともあり、仕事を早めに切り上げ「<a href="https://tabelog.com/shimane/A3201/A320101/32000087/">根っこや</a>」で夜ご飯&飲み。島根の地酒の「<a href="https://www.ouroku.com/">王祿の渓(にごり)</a>」は色々な飲み方が楽しめ、美味しくいただきました。</p>
<h1 id="day2---古民家風オフィスでお試し勤務">DAY2 - 古民家風オフィスでお試し勤務</h1>
<p>2 日目は国宝<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BE%EB">松江城</a>近くに建つ趣のある「古民家風オフィス」でのお試し勤務。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516172409.jpg" alt="f:id:medley_inc:20170516172409j:plain" title="f:id:medley_inc:20170516172409j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518224844.jpg" alt="f:id:medley_inc:20170518224844j:plain" title="f:id:medley_inc:20170518224844j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516124207.jpg" alt="f:id:medley_inc:20170516124207j:plain" title="f:id:medley_inc:20170516124207j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518224938.jpg" alt="f:id:medley_inc:20170518224938j:plain" title="f:id:medley_inc:20170518224938j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518225051.jpg" alt="f:id:medley_inc:20170518225051j:plain" title="f:id:medley_inc:20170518225051j:plain"></p>
<p>オフィスの設備は 1 日目と同じものが用意され、加えて今回はひとりひとりにデスクがあり、とても快適に仕事をすることができました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516124312.jpg" alt="f:id:medley_inc:20170516124312j:plain" title="f:id:medley_inc:20170516124312j:plain"></p>
<p>この日のお昼は「<a href="https://tabelog.com/shimane/A3201/A320101/32000219/">西洋軒</a>」で洋食を食べました。古い佇まいながらも小綺麗なお店でポークカツレツがおいしかったです。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516171916.jpg" alt="f:id:medley_inc:20170516171916j:plain" title="f:id:medley_inc:20170516171916j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516172314.jpg" alt="f:id:medley_inc:20170516172314j:plain" title="f:id:medley_inc:20170516172314j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516172542.jpg" alt="f:id:medley_inc:20170516172542j:plain" title="f:id:medley_inc:20170516172542j:plain"></p>
<p>また、せっかく<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BE%EB">松江城</a>の近くにきたということで、休憩時間を利用して<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BE%EB">松江城</a>を散策しました。国宝<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BE%EB">松江城</a>はとても立派でした(下: メドレーの人文字をやろうとしてイタい感じになっているおじさんたちの図)。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516172646.jpg" alt="f:id:medley_inc:20170516172646j:plain" title="f:id:medley_inc:20170516172646j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518225152.jpg" alt="f:id:medley_inc:20170518225152j:plain" title="f:id:medley_inc:20170518225152j:plain"></p>
<p>仕事が終わった後には、オフィス近くにある「<a href="https://www.facebook.com/otsumami.lab2016/">おつまみ研究所松江殿町ラボ</a>」で軽く立ち飲み。地元クリエイターが内装やパッケージのデザインをしているようで、クリエイティブ感満載の居心地のよいお店でした。立ち飲みでは飲み足りないということで、2 日目はこのまま夜の街に繰り出しました。</p>
<h1 id="day3---ゆめっくす北陵でお試し勤務">DAY3 - ゆめっくす北陵でお試し勤務</h1>
<p>3 日目は市街地から少し離れた「ゆめっくす北陵」でお試し勤務。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516172839.jpg" alt="f:id:medley_inc:20170516172839j:plain" title="f:id:medley_inc:20170516172839j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516172830.jpg" alt="f:id:medley_inc:20170516172830j:plain" title="f:id:medley_inc:20170516172830j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516172820.jpg" alt="f:id:medley_inc:20170516172820j:plain" title="f:id:medley_inc:20170516172820j:plain"></p>
<p>ゆめっくす北陵は<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BB%D4">松江市</a>街地から少し離れた「<a href="https://www.techno-arc-shimane.jp/">テクノアークしまね</a>」という研究開発施設があつまる場所の近くにあります。緑に囲まれた静かな環境にあり開発に専念できる環境です。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518225634.jpg" alt="f:id:medley_inc:20170518225634j:plain" title="f:id:medley_inc:20170518225634j:plain"></p>
<p>こちらでも 1 日目、2 日目と同様に充実したオフィス設備が用意されていました。今までのオフィスと比較して市街地から離れた場所にあったということもあり、とても静かな環境でよりいっそう仕事に集中することができました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516172857.jpg" alt="f:id:medley_inc:20170516172857j:plain" title="f:id:medley_inc:20170516172857j:plain"></p>
<p>お昼ごはんは「<a href="https://tabelog.com/shimane/A3201/A320101/32000105/">お食事処ふの</a>」で<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BB%D4">松江市</a>民の<a href="https://d.hatena.ne.jp/keyword/%A5%BD%A5%A6%A5%EB%A5%D5%A1%BC%A5%C9">ソウルフード</a>といわれているカツライス。ボリュームが多いわりにさっぱりと食べられました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516173018.jpg" alt="f:id:medley_inc:20170516173018j:plain" title="f:id:medley_inc:20170516173018j:plain"></p>
<p>その後は松江出張の目的でもあった打ち合わせと会食を無事に終わらせ、松江のバー「<a href="https://tabelog.com/shimane/A3201/A320101/32000006/">大正倶楽部</a>」でしっぽりと飲んで最後の夜を楽しみました。</p>
<h1 id="day4---プロジェクト成功祈願">DAY4 - プロジェクト成功祈願</h1>
<p>松江お試し勤務の最終日は土曜日ということもあり、業務はせずにプロジェクトの成功祈願をしてきました。<a href="https://d.hatena.ne.jp/keyword/%C5%E7%BA%AC%C8%BE%C5%E7">島根半島</a>の東と西に位置する<a href="https://d.hatena.ne.jp/keyword/%C8%FE%CA%DD%BF%C0%BC%D2">美保神社</a>と<a href="https://d.hatena.ne.jp/keyword/%BD%D0%B1%C0%C2%E7%BC%D2">出雲大社</a>の両方にお参りすることを「<a href="https://www.mihonoseki-kankou.jp/ryoumairi/">えびすだいこく両参り</a>」といい、両参りをすることで願いが成就すると言われているようです。また、<a href="https://d.hatena.ne.jp/keyword/%BD%D0%B1%C0%C2%E7%BC%D2">出雲大社</a>の<a href="https://d.hatena.ne.jp/keyword/%BC%E7%BA%D7%BF%C0">主祭神</a><a href="https://d.hatena.ne.jp/keyword/%A5%AA%A5%AA%A5%AF%A5%CB%A5%CC%A5%B7">オオクニヌシ</a>は医療の神様ともいわれ、医療 IT<a href="https://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%E3%A1%BC">ベンチャー</a>であるメドレーが祈願をしない理由はありません。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516130322.jpg" alt="f:id:medley_inc:20170516130322j:plain" title="f:id:medley_inc:20170516130322j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516173129.jpg" alt="f:id:medley_inc:20170516173129j:plain" title="f:id:medley_inc:20170516173129j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518225924.jpg" alt="f:id:medley_inc:20170518225924j:plain" title="f:id:medley_inc:20170518225924j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518230028.jpg" alt="f:id:medley_inc:20170518230028j:plain" title="f:id:medley_inc:20170518230028j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518230044.jpg" alt="f:id:medley_inc:20170518230044j:plain" title="f:id:medley_inc:20170518230044j:plain"></p>
<p>まずは<a href="https://d.hatena.ne.jp/keyword/%C8%FE%CA%DD%BF%C0%BC%D2">美保神社</a>での参拝。巫女舞を近くでみるのは初めてで貴重な体験でしたが、厳かな雰囲気の中で商売繁盛とプロジェクトの成功を祈願しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516173505.jpg" alt="f:id:medley_inc:20170516173505j:plain" title="f:id:medley_inc:20170516173505j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518230223.jpg" alt="f:id:medley_inc:20170518230223j:plain" title="f:id:medley_inc:20170518230223j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516133037.jpg" alt="f:id:medley_inc:20170516133037j:plain" title="f:id:medley_inc:20170516133037j:plain"></p>
<p><a href="https://d.hatena.ne.jp/keyword/%C8%FE%CA%DD%BF%C0%BC%D2">美保神社</a>から<a href="https://d.hatena.ne.jp/keyword/%BD%D0%B1%C0%C2%E7%BC%D2">出雲大社</a>への移動の途中では「<a href="https://www.facebook.com/kairaku.matsue/">SUP (Stand Up Paddle)</a>」というアクティビティを体験しました。SUP は大きめのサーフボードのようなものに立って乗りパドルで漕ぐというウォータースポーツで、最近流行ってきているようです。当然のように室内で過ごすことが得意な我々にとって、ウォータースポーツはなじみの無いものでしたが、初心者でも丁寧にレクチャーしてくれて存分に楽しむことができました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170516/20170516133426.jpg" alt="f:id:medley_inc:20170516133426j:plain" title="f:id:medley_inc:20170516133426j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518230357.jpg" alt="f:id:medley_inc:20170518230357j:plain" title="f:id:medley_inc:20170518230357j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518230532.jpg" alt="f:id:medley_inc:20170518230532j:plain" title="f:id:medley_inc:20170518230532j:plain"></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170518/20170518230442.jpg" alt="f:id:medley_inc:20170518230442j:plain" title="f:id:medley_inc:20170518230442j:plain"></p>
<p>そして<a href="https://d.hatena.ne.jp/keyword/%BD%D0%B1%C0%C2%E7%BC%D2">出雲大社</a>に到着。最近メディアで取り上げられることが多く、縁結びの神様がいるということで女性観光客が多く来ていました。建築物の歴史の深さと職人のこだわりを感じながら、こちらでもプロジェクトの成功を祈願し、無事に両参りを終えることができました。</p>
<h1 id="まとめ">まとめ</h1>
<p>以上、<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BB%D4">松江市</a>でのお試し勤務の様子でした。落ち着いた環境で勤務ができたことで、仕事もはかどり良い機会となりました 。島根に対しては、東京から遠そう、市内の交通の便が悪そう、ネットワークがつながらなさそう、曇りが多そう、というネガティブな印象を失礼ながら持っていましたが、インターネットの恩恵を受けることできる環境は十分にあるので、エンジニアとして仕事をする分には大きく困ることは無いように思いました。</p>
<p>また、<a href="https://d.hatena.ne.jp/keyword/%BE%BE%B9%BE%BB%D4">松江市</a>には<a href="https://d.hatena.ne.jp/keyword/%BE%F0%CA%F3%B9%A9%B3%D8">情報工学</a>の専門課程がある大学や<a href="https://d.hatena.ne.jp/keyword/%B9%E2%C0%EC">高専</a>があったり、街をあげて<a href="https://smalruby.jp/">IT エンジニアの教育</a>に力をいれていたりと、教育の機会が十分に提供されつつあり、エンジニア採用という観点からも、<a href="https://d.hatena.ne.jp/keyword/%A5%B5%A5%C6%A5%E9%A5%A4%A5%C8%A5%AA%A5%D5%A5%A3%A5%B9">サテライトオフィス</a>の選択肢は十分検討する価値はあるなと思いました。</p>
<p>オフラインで<a href="https://d.hatena.ne.jp/keyword/%A5%E1%A5%EA%A5%CF%A5%EA">メリハリ</a>を付けて効率的にチームで仕事をしていきたいというのが自分の基本スタンスなのですが、今回のお試し勤務をふまえて、<a href="https://d.hatena.ne.jp/keyword/%A5%B5%A5%C6%A5%E9%A5%A4%A5%C8%A5%AA%A5%D5%A5%A3%A5%B9">サテライトオフィス</a>を前提とした現実的で効率的な働き方というものも模索していきたいと思います。</p>
<h1 id="さいごに">さいごに</h1>
<p>今回のお試し勤務は<a href="https://fledge.jp/article/shimaneken-matsue">松江市役所の福田さん</a>のコーディネートのもと実現しました。ホスピタリティと仕事力がとても高い方で、福田さんのおかげで今回のお試し勤務を不自由なく終えることができました。IT 企業誘致や UI ターン支援などを担当されている方なので、興味のある方は福田さんに連絡してみるとよいと思います(6 月 9 日にはお試し<a href="https://d.hatena.ne.jp/keyword/%A5%B5%A5%C6%A5%E9%A5%A4%A5%C8%A5%AA%A5%D5%A5%A3%A5%B9">サテライトオフィス</a>の<a href="https://lig.connpass.com/event/56767/">説明会</a>もあるようです)。もちろん<a href="https://www.facebook.com/sosuke.hirayama">平山宛</a>に連絡いだければお繋げします。</p>
<p>福田さんをはじめ今回お世話になった方々、本当にありがとうございました!</p>
<p>ということで、メドレーは今回のお試し勤務のように働き方についても議論できるフェーズです。そんな初期フェーズの会社で泥臭く一緒にコミットしていける人を絶賛募集しています。興味のあるエンジニア・デザイナーの方はご連絡ください!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- Web アプリケーションの遅い処理を特定する話https://developer.medley.jp/entry/2017/05/09/194020https://developer.medley.jp/entry/2017/05/09/194020こんにちは、開発本部で「ジョブメドレー」の開発を担当している稲本です。
先日、社内で行っている TechLunch という勉強会で「Web アプリケーションの遅い処理を特定する話」という話をしました。
タイトルの意味する範囲が広めなので概要...Tue, 09 May 2017 10:40:20 GMT<p>こんにちは、開発本部で「<a href="https://job-medley.com">ジョブメドレー</a>」の開発を担当している稲本です。</p>
<p>先日、社内で行っている TechLunch という勉強会で「Web アプリケーションの遅い処理を特定する話」という話をしました。</p>
<p>タイトルの意味する範囲が広めなので概要を記載すると以下の通りです。</p>
<ul>
<li>NewRelic から処理速度を見ていく</li>
<li>ChromeDeveloperTools 処理速度を見ていく</li>
<li><a href="https://d.hatena.ne.jp/keyword/RoR">RoR</a>関連のプロファイラから処理速度を見ていく</li>
</ul>
<p>上記の様に Client から Server, Application までプロファイリングを行い遅い処理を特定していく流れの話をしました。</p>
<h1 id="なぜこの話をしようと思ったのか">なぜこの話をしようと思ったのか</h1>
<p>弊社ではフロントエンドエンジニア、サーバサイドエンジニアなどのポジションが明確には分かれておらず、どのバックグラウンドを持った人間も両方開発に携わる方針のため、エンジニア同士が、お互いの得意分野を補い合いながら、各々業務や学習を通して理解を深めるようにしています。</p>
<p>ただ「<a href="https://job-medley.com">ジョブメドレー</a>」チームでは、現状パフォーマンス対策に関しては得意な人が対応する傾向にあります。</p>
<p>そのため得意では無い人へも、どのように調べていけば良いかを共有し、少しでもエンジニア間の知識のギャップを小さくできればと思いフロントエンド〜サーバサイドまでのプロファイリングというテーマで発表しました。</p>
<h1 id="話した内容">話した内容</h1>
<p>先述の取り話した内容は以下の通りです</p>
<ul>
<li>NewRelic から処理速度を見ていく</li>
<li>ChromeDeveloperTools 処理速度を見ていく</li>
<li><a href="https://d.hatena.ne.jp/keyword/RoR">RoR</a>関連のプロファイラから処理速度を見ていく</li>
</ul>
<p><strong>NewRelic の項</strong>、<strong>ChromeDeveloperTools の項</strong>では、ユーザーが Browser を介してサービスを表示するまでの間に、どのような処理にどれぐらいの時間がかかっているか、それを見る方法について説明しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170508/20170508185724.png" alt=""></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170508/20170508185842.png" alt=""></p>
<p><strong><a href="https://d.hatena.ne.jp/keyword/RoR">RoR</a>関連のプロファイラの項</strong>では、サーバーアプリケーションで実行する処理の速度の調査方法について説明しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170508/20170508190003.png" alt=""></p>
<p>※発表資料は<a href="https://speakerdeck.com/medley/webapurikesiyonfalse-chi-ichu-li-wote-ding-suruhua">こちら</a></p>
<iframe id="talk_frame_390512" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/052ded9953be4bbdb157ab9ddcdfb2e3" width="710" height="596" frameborder="0" allowfullscreen="true"></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/medley/webapurikesiyonfalse-chi-ichu-li-wote-ding-suruhua">speakerdeck.com</a></cite>
<h1 id="まとめ">まとめ</h1>
<p>今回、TechLunch で各種プロファイリング方法について紹介しました。</p>
<ul>
<li>ブラウザを経由したパフォーマンスは NewRelic, ChromeDeveloperTools などで見ることが出来る</li>
<li>アプリケーションのパフォーマンスは rack-mini-profiler, peek/rblineprof, stackprof</li>
<li>正しく問題を把握することで誤った解決方法を選択してしまうリスクを回避できる</li>
</ul>
<p>ということについて話をしていきました。
個々のツールの使い方については、調べれば良質な文書が沢山ある中で、実運用上どのように調べれば良いのかにフォーカスし、その一例を紹介出来たのではないかと思います。</p>
<p>今回は触れていませんが、これに限らずサーバーリソースから見る<a href="https://d.hatena.ne.jp/keyword/%A5%DC%A5%C8%A5%EB%A5%CD%A5%C3%A5%AF">ボトルネック</a>の調査方法や、負荷試験方法などについてもどこかで触れて行ければ良いなと考えています。</p>
<h1 id="最後に">最後に</h1>
<p>メドレーでは医療業界に存在する課題に IT を駆使して取り組んでいきたいデザイナー・エンジニアを募集中です。</p>
<p>皆さまからのご応募お待ちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- Web 技術でレガシーな医療業界に革命を!(MedNightTokyo#2 レポート)https://developer.medley.jp/entry/2017/04/21/104335https://developer.medley.jp/entry/2017/04/21/104335こんにちは、オンライン病気事典 MEDLEYの開発をしています徐です。
2017 年 4 月 19 日(水)にBASE 株式会社さんの会議室にて、エムスリー株式会社さんとの合同勉強会(MedNightTokyo)を開催しました。
BASE...Fri, 21 Apr 2017 01:43:35 GMT<p>こんにちは、<a href="https://medley.life/">オンライン病気事典 MEDLEY</a>の開発をしています徐です。</p>
<p>2017 年 4 月 19 日(水)に<a href="https://binc.jp/">BASE 株式会社</a>さんの会議室にて、エムスリー株式会社さんとの合同勉強会(<a href="https://mednight.connpass.com/">MedNightTokyo</a>)を開催しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170420/20170420165035.png" alt="f:id:yamadagenki:20170420165035p:plain" title="f:id:yamadagenki:20170420165035p:plain"></p>
<p>BASE 株式会社さんのオフィスはとてもきれいでした!</p>
<p>「Web 技術でレガシーな医療業界に革命を!エンジニア開発裏話」というテーマで、弊社からは<a href="https://www.wantedly.com/companies/medley/post_articles/42315">平木</a>と<a href="https://www.wantedly.com/companies/medley/post_articles/57252">田中</a>が<a href="https://clinics.medley.life/">オンライン診療アプリ CLINICS</a>の開発の舞台裏について発表しました。 発表内容について一部ご紹介致します。</p>
<h1 id="クラウド電子カルテを支える魂の技術-エムスリー株式会社-冨岡-純さん"><a href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a><a href="https://d.hatena.ne.jp/keyword/%C5%C5%BB%D2%A5%AB%A5%EB%A5%C6">電子カルテ</a>を支える魂の技術 (エムスリー株式会社 冨岡 純さん)</h1>
<p>トップバッターは、エムスリーの冨岡純さんによる<a href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a><a href="https://d.hatena.ne.jp/keyword/%C5%C5%BB%D2%A5%AB%A5%EB%A5%C6">電子カルテ</a>についての発表でした。</p>
<p>“カルテ”というと、いかにも”医療”という感じがしますが、これを電子化し、しかも<a href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>化し、
今現在病院で使われるシステムと連携する…などと考えると難しいことが大量にあるそうです。
まさに、“レガシー”とどう立ち向かったか、というのがわかるお話でした。</p>
<p>※ 発表資料は下記</p>
<iframe id="talk_frame_388168" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/cca6df720b814ee9949c1aa1c9715d4d" width="710" height="463" frameborder="0" allowfullscreen="true"></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/jooohn/kuraudodian-zi-karutewozhi-eruhun-falseji-shu">speakerdeck.com</a></cite>
<h1 id="clinics-を支える技術と開発体制-株式会社メドレー-田中清">CLINICS を支える技術と開発体制 (株式会社メドレー 田中清)</h1>
<p>田中の発表は、<a href="https://clinics.medley.life/">CLINICS</a>で用いられている技術と開発体制でした。
メドレーの中でもベテランエンジニアが集まる CLINICS は技術的にもレベルが高く、
また開発体制も非エンジニアも多く関わるプロダクトならではの工夫をしており、
「ベテラン(おじさん)たちの経験と知恵が詰まってるな」と深く関心してしまいました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170420/20170420164547.jpg" alt="f:id:yamadagenki:20170420164547j:plain" title="f:id:yamadagenki:20170420164547j:plain"></p>
<p>※ 発表資料は下記</p>
<iframe id="talk_frame_388121" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/632af2c7295548cd89c8b96043747277" width="710" height="596" frameborder="0" allowfullscreen="true"></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/medley/clinicsji-shu-gou-cheng-tokai-fa-ti-zhi">speakerdeck.com</a></cite>
<h1 id="非定型なレポート依頼とpythonで戦う-エムスリー株式会社-三浦-琢磨さん">非定型なレポート依頼と<a href="https://d.hatena.ne.jp/keyword/Python">Python</a>で戦う (エムスリー株式会社 三浦 琢磨さん)</h1>
<p>「医師免許を持つエンジニア」という変わった経歴をもつ三浦琢磨さんからは、
ビジネスに於ける課題をエンジニアリング(<a href="https://d.hatena.ne.jp/keyword/Python">Python</a>)の力でいかに効率化したか、というお話でした。</p>
<p>業務で繰り返される作業を課題として捉え、それを着実にエンジニアリングの力で解決していく姿勢は、
IT 企業ならではと言った印象を受け、「こういう仕事いいなぁ」という気持ちになりました。</p>
<p>※ 便利なライブラリーをそのうち公開していただけるそうで、ちょっと期待。</p>
<iframe style="border: 1px solid #CCC; border-width: 1px; margin-bottom: 5px; max-width: 100%;" src="https://www.slideshare.net/slideshow/embed_code/key/g2lhurItopt713" width="427" height="356" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" allowfullscreen></iframe>
<p><strong><a href="https://www.slideshare.net/takumamiura3/python-75184158" title="非定型なレポート依頼と Python で戦う">非定型なレポート依頼と Python で戦う</a></strong> from <strong><a href="https://www.slideshare.net/takumamiura3">琢磨 三浦</a></strong></p>
<p><cite class="hatena-citation"><a href="https://www.slideshare.net/takumamiura3/python-75184158"></a><a href="http://www.slideshare.net">www.slideshare.net</a></cite></p>
<h1 id="clinics-の変化に耐える-mithril-株式会社メドレー-平木聡">CLINICS の変化に耐える Mithril (株式会社メドレー 平木聡)</h1>
<p>平木からは、<a href="https://clinics.medley.life/">CLINICS</a>で使われている Mithril について紹介しました。
CLINICS はサービスの UI デザインだけではなく、
内部でもライブラリーのバージョンやファイル構成など変化を繰り返しており
そういった変化にどう耐えて来たのか、という CLINICS の歴史も交え発表しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170420/20170420164628.jpg" alt="f:id:yamadagenki:20170420164628j:plain" title="f:id:yamadagenki:20170420164628j:plain"></p>
<p>※ 発表資料は下記</p>
<iframe id="talk_frame_388133" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/f4202e29617940539cfc73b1a275b9f8" width="710" height="596" frameborder="0" allowfullscreen="true"></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/medley/clinicsfalsebian-hua-ninai-erumithril">speakerdeck.com</a></cite>
<h1 id="まとめ">まとめ</h1>
<p>第 2 回目となる MedNightTokyo の勉強会を行いました。</p>
<p>発表内容もさることながら、真剣に発表する発表者の様子からは、
IT の力を用いてレガシーな医療業界全体をより良くしていこうという熱い思いが感じられました。</p>
<p>こうした熱い思いを持ったエンジニアたちが集まる、この”医療”という分野で、
技術的にも切磋琢磨できるのは本当にやりがいがある仕事なのだと再認識しました。</p>
<p>来月 5/17(水)には、株式会社トレタとの合同エンジニア&デザイナーイベントを開催予定です!
弊社からは CTO の平山と、デザイナーの前田が登壇します。
ご興味ある方、ぜひご参加ください!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="5/17 メドレー×トレタ合同エンジニア・デザイナーイベントを開催します! by 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F97348" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.wantedly.com/projects/97348">www.wantedly.com</a></cite>
<p>また、このメドレーオフィシャルブログでも、勉強会情報を掲載していくので、ブログも引き続きチェックよろしくお願い致します。</p>
<p>※<a href="https://www.facebook.com/medley.jp/">メドレー公式 Facebook ページ</a>に「いいね!」していただけると、ブログの最新情報をフォローできます!!</p>
<h1 id="最後に">最後に</h1>
<p>メドレーでは医療業界に存在する課題に IT を駆使して取り組んでいきたいデザイナー・エンジニアを募集中です。</p>
<p>皆さまからのご応募お待ちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- COBOL と ORCA について学ぶお話https://developer.medley.jp/entry/2017/04/18/174918https://developer.medley.jp/entry/2017/04/18/174918TechLunch でCOBOLとORCAについて話しました
開発本部の竹内です。病気事典 MEDLEY の開発を担当しております。子どもが絶賛イヤイヤ期中です。
さて先日、TechLunch という社内勉強会にてCOBOLとORCAについ...Tue, 18 Apr 2017 08:49:18 GMT<p><strong>TechLunch で<a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>と<a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>について話しました</strong></p>
<p>開発本部の竹内です。病気事典 MEDLEY の開発を担当しております。子どもが絶賛イヤイヤ期中です。
さて先日、TechLunch という社内勉強会にて<a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>と<a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>についての発表を行いましたので、その紹介をさせていただきたいと思います。</p>
<h1 id="はじめに">はじめに</h1>
<p><a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>とは?<a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>とは?の前に、「なぜ今回このテーマを選んだか」について簡単に説明させてください。</p>
<p>株式会社メドレーでは「医療ヘルスケア分野の課題を解決する」ために各サービスの開発・運営を行っています。
しかし、「医療ヘルスケア分野」のサービス開発・運営を行っていると言っても、実際の開発現場では一般的な Web アプリケーションや<a href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A1%BC%A5%C8%A5%D5%A5%A9%A5%F3">スマートフォン</a>アプリケーションの開発を行うことが多々あります。
そのため、日々の仕事において「医療ヘルスケア分野」で業務を行っていると意識しないこともあります。
せっかく「医療 ×IT」の会社にいるわけなので、エンジニアとして医療に深く関わるソフトウェアについて勉強してみるのも面白いのではないか、と思ったことがテーマの選択につながっています。
また、医療ヘルスケア分野で<a href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>として公開されている代表的なものとして<a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>の「日医標準レセプトソフト」がある、というのもテーマ選択の理由の一つです。
どんなソフトウェアにせよそれが<a href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>であれば、コードが公開されているため勉強を始めるハードルはかなり下がるため、です。</p>
<h1 id="orcaとは"><a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>とは</h1>
<blockquote>
<p><a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>プロジェクトとは、医療情報ネットワーク推進委員会にて「医師会総合情報ネットワーク構想」(1997 年 情報化検討委員会)を構成するツールの一つとして認められた<a href="https://d.hatena.ne.jp/keyword/%C6%FC%CB%DC%B0%E5%BB%D5%B2%F1">日本医師会</a>の研究事業プロジェクトです。 <a href="https://www.orca.med.or.jp/orca/summary/outline.html">ORCA Project: ORCA プロジェクトの概要</a></p>
</blockquote>
<p><a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>とは<a href="https://d.hatena.ne.jp/keyword/%C6%FC%CB%DC%B0%E5%BB%D5%B2%F1">日本医師会</a>のプロジェクトで、国民医療を改善するために医療情報の効率化・標準化を推進するために、医師・医療関係機関が無料で利用・改良できる<a href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>を配布しています。</p>
<p>現在、診療報酬を請求するための専用コンピュータ(=レセコン)は、全国の<a href="https://d.hatena.ne.jp/keyword/%B0%E5%CE%C5%B5%A1%B4%D8">医療機関</a>の内8割以上が使用しているそうです。<a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>プロジェクトでは「日医標準レセプトソフト(=日レセ)」という<a href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>が公開されています。</p>
<p><a href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>なのでもちろんコードは公開されており、その他の技術情報も下記リンク先に詳細が掲載されています。 <a href="https://www.orca.med.or.jp/receipt/tec/">ORCA Project: 技術情報</a></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170417/20170417154614.png" alt="f:id:medley_inc:20170417154614p:plain" title="f:id:medley_inc:20170417154614p:plain"></p>
<h1 id="cobolとは"><a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>とは</h1>
<p>さて、上記リンク先を見いくと日医標準レセプトソフトのコードは<a href="https://d.hatena.ne.jp/keyword/CVS">CVS</a>で公開されており、その中を見てみると<a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>のコードがあることがあわかります。
個人的に<a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>については、「名前は聞いたことあるけど、文法など詳細は知らない割になぜか良い印象を持っていない」という状態でした。
レセコンのロジックを勉強するにあたり、ついでに未知の言語である<a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>も勉強してみよう、という軽いノリで調べはじめました。</p>
<p>勉強会では<a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>の基本的な文法にフォーカスして紹介しています。
また、今回は<a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>の実行環境として Docker を利用しています。</p>
<ul>
<li><a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>による HELLO, WORLD</li>
</ul>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="cobol"><code><span class="line"><span style="color:#569CD6">IDENTIFICATION DIVISION</span><span style="color:#D4D4D4">.</span><span style="color:#569CD6">PROGRAM-ID</span><span style="color:#D4D4D4">. </span><span style="color:#DCDCAA">HELLO_WORLD</span><span style="color:#D4D4D4">.</span></span>
<span class="line"><span style="color:#569CD6">ENVIRONMENT DIVISION</span><span style="color:#D4D4D4">.</span></span>
<span class="line"><span style="color:#569CD6">DATA DIVISION</span><span style="color:#D4D4D4">.</span></span>
<span class="line"><span style="color:#569CD6">PROCEDURE DIVISION</span><span style="color:#D4D4D4">.</span></span>
<span class="line"><span style="color:#569CD6"> DISPLAY</span><span style="color:#CE9178"> "Hello, COBOL"</span><span style="color:#D4D4D4">.</span></span>
<span class="line"><span style="color:#569CD6">STOP RUN</span><span style="color:#D4D4D4">.</span></span></code></pre>
<h1 id="orcaのコードについて"><a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>のコードについて</h1>
<p>上述の通り、<a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>(日レセ)のコードは<a href="https://d.hatena.ne.jp/keyword/CVS">CVS</a>で管理されているため、<a href="https://d.hatena.ne.jp/keyword/Mac">Mac</a>では次のようにしてコードを取得できるかと思います。</p>
<pre class="astro-code dark-plus" style="background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A9955"># 参考:https://www.orca.med.or.jp/receipt/tec/cvs-jma-receipt.html\# リポジトリ取得</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> brew</span><span style="color:#CE9178"> install</span><span style="color:#CE9178"> cvs</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> curl</span><span style="color:#CE9178"> https://ftp.orca.med.or.jp/pub/data/receipt/tec/orcacvs</span><span style="color:#D4D4D4"> > </span><span style="color:#CE9178">~/bin/orcacvs</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> chmod</span><span style="color:#CE9178"> +x</span><span style="color:#CE9178"> ~/bin/orcacvs</span></span>
<span class="line"><span style="color:#DCDCAA">$</span><span style="color:#CE9178"> cvs</span><span style="color:#569CD6"> -d</span><span style="color:#CE9178"> :ext:[email protected]:/cvs</span><span style="color:#CE9178"> co</span><span style="color:#CE9178"> jma-receipt</span></span></code></pre>
<p>実際の<a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>のコードは<a href="https://d.hatena.ne.jp/keyword/cobol">cobol</a>という<a href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リに入っています。
<a href="https://cvs.orca.med.or.jp/cgi-bin/cvsweb/jma-receipt/cobol/">https://cvs.orca.med.or.jp/cgi-bin/cvsweb/jma-receipt/cobol/</a></p>
<p>見てみると、ファイルを開いてコメントを確認するまでどれがどういう用途なのかよくわかりませんでした。
ファイル内のコメントにある「<a href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>名」と cvsweb の URL を対応付けた<a href="https://d.hatena.ne.jp/keyword/markdown">markdown</a>を作成しましたので、興味がある方は見てみてください。</p>
<div class="remark-link-card-plus__container">
<a href="https://github.com/ise/orca-jma-receipt-study/blob/master/cobol.md" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">orca-jma-receipt-study/cobol.md at master · ise/orca-jma-receipt-study</div>
<div class="remark-link-card-plus__description">Contribute to ise/orca-jma-receipt-study development by creating an account on GitHub.</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://github.githubassets.com/favicons/favicon.svg" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">github.com</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://opengraph.githubassets.com/5e0557f61a3d2aa7f9ed8e073a9f8dadd3ee8bb53f5bb79b6a0789508803d680/ise/orca-jma-receipt-study" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170417/20170417154633.png" alt="f:id:medley_inc:20170417154633p:plain" title="f:id:medley_inc:20170417154633p:plain"></p>
<h1 id="まとめ">まとめ</h1>
<p>TechLunch で<a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>と<a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>について紹介しました。
軽い気持ちで調べはじめましたが、
「レセコンが<a href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>で配布されている」ということにまず衝撃を受け、
「<a href="https://d.hatena.ne.jp/keyword/ORCA">ORCA</a>(日レセ)のコードが<a href="https://d.hatena.ne.jp/keyword/CVS">CVS</a>で公開されている」ということにも衝撃を受け、
「コードが<a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>である」ということにさらに衝撃を受けました。
が、同時に<a href="https://d.hatena.ne.jp/keyword/OSS">OSS</a>が日本の医療を支えていることに胸が熱くなりました。技術スタックが古くとも、とても素晴らしいことだと思いました。</p>
<p>また、リニューアル PJ もあるようで、今後の動向が興味深いですね。
<a href="https://www.slideshare.net/ShinjiKobayashi2/moss9-orca">ORCA 次期 FW 開発の現状</a> (少し古い資料ですが)</p>
<p><a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>についても、調べはじめるとなかなかに興味深い言語でしたが、やはり基本文法を理解しただけではレセコンのコードを読んで理解するまでには至りませんでした。。発表資料でも触れましたが、日レセを構成する<a href="https://d.hatena.ne.jp/keyword/%A5%DF%A5%C9%A5%EB%A5%A6%A5%A7%A5%A2">ミドルウェア</a>である MONTSUQI についても、ある程度理解する必要があると感じました(俺たちの戦いはこれからだ、、)</p>
<p>さて、医療に関わるソフトウェア&(個人的に)未知の技術である<a href="https://d.hatena.ne.jp/keyword/COBOL">COBOL</a>に触れ、心が豊かになったところで今回のブログを締めさせていただきたいと思います。
ここまでお読み下さり、どうもありがとうございました。</p>
<p>※勉強会資料は下記</p>
<iframe id="talk_frame_387815" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/ba72db0a5f7a47f4805636fe43894b24" width="710" height="463" frameborder="0" allowfullscreen="true"></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/medley/orcatocobolto-watashi">speakerdeck.com</a></cite>
<h1 id="最後に">最後に</h1>
<p>メドレーでは医療業界に存在する課題に IT を駆使して取り組んでいきたいデザイナー・エンジニアを募集中です。</p>
<p>皆さまからのご応募お待ちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- 社内勉強会 TechLunch で ECMAScript についての発表をしましたhttps://developer.medley.jp/entry/2017/04/11/190413https://developer.medley.jp/entry/2017/04/11/190413こんにちは、開発本部エンジニア平木です。
弊社では定例で TechLunch という社内勉強会を開いています。今回は自分が担当になったので、最近の動向も含めてECMAScriptについて話をしました。
なぜECMAScriptについて話そう...Tue, 11 Apr 2017 10:04:13 GMT<p>こんにちは、開発本部エンジニア平木です。</p>
<p>弊社では定例で TechLunch という社内勉強会を開いています。今回は自分が担当になったので、最近の動向も含めて<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>について話をしました。</p>
<h1 id="なぜecmascriptについて話そうと思ったのか">なぜ<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>について話そうと思ったのか?</h1>
<p>ご存知の方も多いでしょうが、<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>とは<a href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>の仕様になります。</p>
<p>ここからは個人的な印象になりますが、つい数年前までは特にフロントエンド開発をする人でも仕様のことを意識しなくてもあまり問題はなかったと思います。</p>
<p>しかし、Babel や TypeScript などが普及しだしてきたあたりから、ブラウザの実装に関係なく<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>で提案されている仕様が使えるようになり、段々と<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>について知っておいた方が開発効率が上がる…ということになってきたかと思います。</p>
<p>最近ですと、特に Babel を使う前提の React.js などが一般に使われてきていますので、そういった開発をするときに<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>の仕様がどのように決まって次の仕様にはどういった機能が取り込まれるのかを知っておくと開発がしやすくなるのではないかと考えています。</p>
<p>弊社でもちらほらと、ECMAScript2015 以降を使うようになってきているという事情もあり、ここらで情報が追いやすくなってきている<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>について興味を持ってもらおうという意図で題材に選びました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/L/Layzie/20170303/20170303120548.jpg" alt="f:id:Layzie:20170303120548j:plain"></p>
<h1 id="話す内容で考えたこと">話す内容で考えたこと</h1>
<p>このような思いから、題材は決まったのですが、いきなり<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>について話しだしても全然わからないよねということになります。</p>
<p>以前に<a href="https://meguroes.connpass.com/event/21510/">Meguro.es #1</a>というイベントで<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>について<a href="https://speakerdeck.com/layzie/ecma262">LT をさせてもらった</a>のですが、イベントの性質上前提をすっ飛ばして話した感じでした。</p>
<p>しかし、弊社のエンジニア陣は今でこそフロントエンドも実装していますが元々はバックエンドなどフロントエンド以外出身のエンジニアが多いので、本題のなぜ<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>の次の仕様が追いやすくなったしキャッチアップしていこう!という点がピンとこない可能性があります。</p>
<p>前提としてそもそも<a href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>の歴史や、<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>が策定される経緯などから話さないといけないかも…と色々盛り込んだ話をしたのですが、結果としてはちょっと本題がボヤけてしまった感じがあり反省しています…。</p>
<p>スライドはこちらになります。</p>
<h1 id="スライドには入れられなかった部分">スライドには入れられなかった部分</h1>
<p>時間の都合で話したかったけど入っていないことを 2 点ほど。</p>
<h2 id="babel-について">Babel について</h2>
<p>TechLunch の発表では、構成上入れていなかったのですが、弊社のプロダクトでいうと<a href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>を結局は<a href="https://babeljs.io/">Babel</a>を使ってトランスパイルするという形がほとんどです。</p>
<p>基本の ES2015 の機能については<a href="https://babeljs.io/learn-es2015/">Learn ES2015 · Babel</a>や<a href="https://github.com/yosuke-furukawa/tower-of-babel">yosuke-furukawa/tower-of-babel</a>なんかで覚えることができると思います。</p>
<p>しかし、極端な例でいうと Babel の<a href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%E9%A5%B0%A5%A4%A5%F3">プラグイン</a>の<a href="https://babeljs.io/docs/plugins/preset-stage-0/">Stage 0 preset · Babel</a>を使ったりするとさらに便利(かもしれない)機能が使えるようになります。この<a href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%E9%A5%B0%A5%A4%A5%F3">プラグイン</a>で使える機能は何か?というのを調べるとなると、やはり<a href="https://github.com/tc39/proposals">tc39/proposals</a>などを見たほうが早いということになります。</p>
<p>次にどんな機能が来るなどの情報はこの発表の内容などを知ってるとキャッチアップしやすくなると思います。</p>
<h2 id="詳しい情報源についてα">詳しい情報源について+α</h2>
<p>参考情報として載せたなかで個人的に <strong>早い・分かりやすい・詳しい</strong> を兼ねそなえているのが <em>Dr. Axel Rauschmayer</em> の <a href="https://2ality.com/index.html">2ality – JavaScript and more</a>かなと思っています。</p>
<p>英語が苦でなければこの人のブログを読んでるだけで実用上問題ないくらいに<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>の動向についていけます。</p>
<p>この方の<a href="https://exploringjs.com/">JavaScript / ECMAScript 関連の本</a>もすばらしいので、これまた英語が苦でなければぜひ読んでください。</p>
<p>ちなみにこの方は<a href="https://esnextnews.com/">ES.next News: 5 ECMAScript.next links, every week</a>という<a href="https://d.hatena.ne.jp/keyword/%A5%E1%A1%BC%A5%EA%A5%F3%A5%B0%A5%EA%A5%B9%A5%C8">メーリングリスト</a>も運営しておりこちらは粒度としては 1 週間に 1 回になるんである意味読みやすい情報かもしれません。</p>
<p>日本語ですと、<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>だけではないですがやっぱり<a href="https://twitter.com/azu_re">azu</a>さんの<a href="https://jser.info/">JSer.info</a>がよいかと思います。</p>
<h1 id="まとめ">まとめ</h1>
<p><a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>についての勉強会を開催したお話でした。メドレーでは、<a href="https://d.hatena.ne.jp/keyword/ECMAScript">ECMAScript</a>の仕様読むの大好物!というような方もぜひ来ていただきたいと思っています!!</p>
<p>また 4/19(水)に筆者が携わっている CLINICS について話すイベントを開催します。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="MedNight Tokyo #2 〜Web 技術でレガシーな医療業界に革命を!エンジニア開発裏話 (2017/04/19 20:00〜)" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmednight.connpass.com%2Fevent%2F50788%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://mednight.connpass.com/event/50788/">mednight.connpass.com</a></cite>
<p>フロントエンドのことについて話す予定ですので、ご興味あればぜひご参加ください!</p>
<h3 id="お知らせ">お知らせ</h3>
<p>メドレーでは医療業界に存在する課題に IT を駆使して取り組んでいきたいデザイナー・エンジニアを募集中です。</p>
<p>皆さまからのご応募お待ちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- AWS サービスを用いた今後のバッチ処理のお話〜開発本部・ TechLunch〜https://developer.medley.jp/entry/2017/03/21/174050https://developer.medley.jp/entry/2017/03/21/174050オンライン診療アプリ「CLINICS」を開発している田中です。
本日は、メドレー開発本部にて隔週で行われている勉強会(TechLunch)で、今後のバッチ処理構成の 1 つとして活用できそうなAWSサービスの紹介を行ったので、その一部を紹介...Tue, 21 Mar 2017 08:40:50 GMT<p>オンライン診療アプリ「<a href="https://clinics.medley.life/">CLINICS</a>」を開発している田中です。</p>
<p>本日は、メドレー開発本部にて隔週で行われている勉強会(TechLunch)で、今後の<a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>構成の 1 つとして活用できそうな<a href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>サービスの紹介を行ったので、その一部を紹介したいと思います。</p>
<h1 id="背景と勉強会の目的">背景と勉強会の目的</h1>
<p><strong>メドレーでの<a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a></strong></p>
<p>メドレーの各プロダクトは環境として主に<a href="https://aws.amazon.com/">AWS</a>を使用しており、いわゆる「<a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>」(時間起動でのデータ一括処理系)については、スケジューラとして使用している Jenkins からバッチ用 EC2<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>上の<a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>をキックする、という構成が多いです。</p>
<p>(短時間で完了し、ワークフロー自体シンプルなバッチについては、スケジューラに Lambda を利用する構成も増えてきました)</p>
<p>その他、SQS を用いたキューイング処理、<a href="https://d.hatena.ne.jp/keyword/Kinesis">Kinesis</a>/Lambda を使用したログストリーミング処理など様々な構成で動いています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170321/20170321124505.png" alt=""></p>
<h1 id="勉強会の目的">勉強会の目的</h1>
<p>現状、プロダクトを運用する上で<a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>で何か課題があるかと言えば特には無いのですが、下記を目的として勉強会を行いました。</p>
<ul>
<li>そもそも<a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>とは?という整理</li>
<li><a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>構成の新しい選択肢としてのインプット</li>
</ul>
<p>今後各プロダクトを跨ぐ共通基盤システムなどで<a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>が必要になった場合など、出来るだけ人の手間を減らしたい場合に備え、選択肢の 1 つとしてバッチ向けサービス(<a href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> Batch、Step Functions)の紹介を行いました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170321/20170321124724.png" alt=""></p>
<p>また、広義の意味での<a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>/狭義の意味での<a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>という整理で、一括系処理系、ストリーミング/キューイングなどの(ほぼ)リアルタイム系の違いやよくある構成についての説明も行いました。</p>
<h1 id="勉強会の内容">勉強会の内容</h1>
<p>メドレーでは様々なバックグラウンドをもったエンジニアが集まっており、フロントエンドやネイティブは得意だけどサーバーサイド/インフラはまだ慣れていないエンジニア向けに、そもそも<a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>とは?という内容から始めました。</p>
<ul>
<li><a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>の概要、よくあるシステム構成</li>
<li><a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>設計時のポイント</li>
<li>ここ最近のバッチ関連の流れ(リアルタイム化)</li>
<li>各プロダクトのバッチ構成の例</li>
</ul>
<p>次に、今後使えそうな<a href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>のサービスとして<a href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> Batch と Step Functions の概要について説明しました。</p>
<ul>
<li><a href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> Re:Invent 2016 で発表された<a href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>の batch 処理系に使える新サービス</li>
<li><a href="https://d.hatena.ne.jp/keyword/AWS">AWS</a> Batch
<ul>
<li>フルマネージド型の<a href="https://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>実<a href="https://d.hatena.ne.jp/keyword/%B9%D4%B4%F0">行基</a>盤</li>
<li>必要なリソース(CPU、メモリ etc)を定義すれば、<a href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>が必要に応じて ECS 上で実行(<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>タイプ、分散用に台数確保)</li>
<li>ジョブとして登録したアプリやコンテナイメージを実行</li>
</ul>
</li>
<li>Step Functions
<ul>
<li>Lamba などの複数アプリをワークフローとして定義、実行(ビジュアル化)</li>
<li>今までは Lambda to Lambda や、SQS を介すなど自分で考慮必要だったワークフロー(分岐、繰り返しなど)を<a href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>で定義しサービス化</li>
</ul>
</li>
</ul>
<p>※ 発表資料は以下</p>
<p>最後に、簡単にですが Step Functions を実際に試した内容と、個人的な感想を共有しました。</p>
<ul>
<li>
<p>Pros</p>
<ul>
<li>既存の Lambda をそのまま使用できる</li>
<li>フロー定義や実行結果がビジュアルで確認できる</li>
<li>同じ Lambda を別々の State Machine で使えるので汎用性、再利用性が高まる</li>
</ul>
</li>
<li>
<p>Cons</p>
<ul>
<li>ワークフローが複雑になると、タスクの<a href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>定義がけっこう手間(Step Functions に限った話ではないですが、、、)</li>
<li><a 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>
</ul>
</li>
</ul>
<h1 id="まとめ">まとめ</h1>
<p>メドレー開発本部の技術勉強会(TechLunch)で発表した内容の一部を紹介しました。</p>
<p>メドレーでは各技術の選択基準として、適材適所という考えを大事にしています。そのため、新技術/新サービスを使用すればいいと言う訳ではなく、今回の内容に関連して言えば、あえてベーシックにバッチ<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を立てて、という構成も適材適所であれば問題ないと考えています。</p>
<p>適材適所を効果的に行うためには、日々、新技術も情報として押さえ蓄積していく事が重要だと思うので、INPUT の一環として今後も「TechLunch」を更に活用していきたいと思います。</p>
<h3 id="お知らせ">お知らせ</h3>
<p>CTO 平山のブログが本日公開となりました。 記念すべき第一回目は、「医療 × インターネットの未来」がテーマ。 ぜひ読んでみてくださいね。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="医療×インターネットの未来 | メドレー平山の中央突破" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Ftoppa.medley.jp%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://toppa.medley.jp/">toppa.medley.jp</a></cite>
<p>また、CTO 平山とのランチ会を定期開催予定です。 ご興味ある方はぜひエントリーください!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="ランチ企画第 3 弾!3/22、CTO 平山と語りたいエンジニア・デザイナー募集 by 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F92106" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.wantedly.com/projects/92106">www.wantedly.com</a></cite>
<h3 id="求人情報">求人情報</h3>
<p>メドレーでは医療業界に存在する課題に IT を駆使して取り組んでいきたいデザイナー・エンジニアを募集中です。</p>
<p>皆さまからのご応募お待ちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- 「メタプログラミング Ruby」ことはじめ〜開発本部・ TechLunch〜https://developer.medley.jp/entry/2017/03/14/173006https://developer.medley.jp/entry/2017/03/14/173006医療介護の求人サイト「ジョブメドレー」の開発を担当している後藤です。
メドレー開発本部にて隔週で行われている勉強会(TechLunch)でメタプログラミング RubyをベースにメタプログラミングRuby入門について発表したのでその一部を紹介...Tue, 14 Mar 2017 08:30:06 GMT<p>医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」の開発を担当している後藤です。</p>
<p>メドレー開発本部にて隔週で行われている勉強会(TechLunch)で<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミング Ruby</a>をベースに<a href="https://d.hatena.ne.jp/keyword/%A5%E1%A5%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">メタプログラミング</a><a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>入門について発表したのでその一部を紹介したいと思います。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170313/20170313150345.png" alt=""></p>
<h1 id="メドレーとruby">メドレーと<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a></h1>
<p>弊社では「<a href="https://clinics.medley.life/">CLINICS</a>」、「<a href="https://job-medley.com/">ジョブメドレー</a>」、「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」と複数のプロダクトで<a href="https://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a> を利用しています。</p>
<p>スタートアップでそこまでエンジニアの数が多くないなか、エンジニアが最大限にプロダクトにコミットするために、もともとのバックグラウンドがフロントエンドエンジニアだったり、ネイティブアプリエンジニアだったりする方も<a href="https://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a>のコードを書いています。</p>
<p><a href="https://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a>はとても便利な<a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>で、周辺の便利なライブラリや解説記事が整っていることもあり、必要な機能は検索すればあまり中身を理解せずとも実装できてしまったりもします。ただ、何か問題があった時のために利用している<a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>の処理を把握しておくことはとても重要です。そして、<a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>の<a href="https://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を読み解くにはやはり<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の言語仕様の知識が重要になってきます。</p>
<p>また、<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の言語仕様、そして使っている<a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>の深い理解があることでよりメンテナンスしやすい設計・実装がしやすくなるのも事実だと思います。</p>
<p>こういった背景のもと、社内のエンジニアの<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>に対する知見を深めることはとても価値があると感じ、「<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミング Ruby</a>」をベースに<a href="https://d.hatena.ne.jp/keyword/%A5%E1%A5%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">メタプログラミング</a><a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>入門の勉強会を開催しました。</p>
<h1 id="メタプログラミングruby入門"><a href="https://d.hatena.ne.jp/keyword/%A5%E1%A5%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">メタプログラミング</a><a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>入門</h1>
<p>今回は<a href="https://d.hatena.ne.jp/keyword/%A5%E1%A5%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">メタプログラミング</a>入門ということで<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>で<a href="https://d.hatena.ne.jp/keyword/%A5%E1%A5%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">メタプログラミング</a>をする上で必要な<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の言語仕様の話と一つの例として method_missing の話を取り上げました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170313/20170313152735.png" alt=""></p>
<h2 id="rubyのオブジェクトモデル"><a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>のオブジェクトモデル</h2>
<p>この<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>のオブジェクトモデルという言葉は「<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミング Ruby</a>」から拝借しています。本の中では「このメソッドはどのクラスに所属するものなのか?」「このモジュールをインクルードしたら何が起きるのか」といった質問の答えが見つかる場所と記載しています。このオブジェクトモデルには以下のようなルールが存在します。</p>
<ol>
<li>オブジェクトは 1 種類しかない。それが通常のオブジェクトかモジュールになる。</li>
<li>モジュールは 1 種類しかない。それが通常のモジュール、クラス、特異クラスのいずれかになる。</li>
<li>メソッドは 1 種類しかない。メソッドはモジュール(大半はクラス)に住んでいる。</li>
<li>すべてのオブジェクトは(クラスも含めて)「本物のクラス」を持っている。それが通常のクラスか特異クラスである。</li>
<li>すべてのクラスは(BasicObject を除いて)ひとつの祖先(<a href="https://d.hatena.ne.jp/keyword/%A5%B9%A1%BC%A5%D1%A1%BC%A5%AF%A5%E9%A5%B9">スーパークラス</a>かモジュール)を持っている。つまり、あらゆるクラスが BasicObject に向かって 1 本の継承チェーンを持っている。</li>
<li>オブジェクトの特異クラスの<a href="https://d.hatena.ne.jp/keyword/%A5%B9%A1%BC%A5%D1%A1%BC%A5%AF%A5%E9%A5%B9">スーパークラス</a>は、オブジェクトのクラスである。クラスの特異クラスの<a href="https://d.hatena.ne.jp/keyword/%A5%B9%A1%BC%A5%D1%A1%BC%A5%AF%A5%E9%A5%B9">スーパークラス</a>はクラスの<a href="https://d.hatena.ne.jp/keyword/%A5%B9%A1%BC%A5%D1%A1%BC%A5%AF%A5%E9%A5%B9">スーパークラス</a>の特異クラスである。</li>
<li>メソッドを呼び出すときは、<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>はレシーバの本物のクラスに向かって「右へ」進み、継承チェーンを「上へ」進む。<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>のメソッド探索について知るべきことは以上だ。</li>
</ol>
<p>(<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミング Ruby 第 2 版</a> より)</p>
<h2 id="basicobjectmethod_missing">BasicObject#method_missing</h2>
<p><a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>でメソッド呼び出しを行った際のフローは以下になります。</p>
<ol>
<li><a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>はレシーバーの本物のクラスに向かってメソッド探索を始める</li>
<li>継承チェーンを BasicObject まで辿っても呼び出したメソッドが見つからなかった場合、<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>はレシーバーの method_missing を呼び出す</li>
<li>メソッド探索で method_missing が見つからなかった場合、BasicObject#method_missing が呼び出され、例外が投げられる</li>
</ol>
<p>この仕組みをうまく使って、BasicObject#method_missing が呼び出される前に自前の method_missing メソッドを呼び出すようにして動的にメソッドを定義したり動的な振る舞いをオブジェクトに加えたりすることができるのです。</p>
<h1 id="勉強会の様子">勉強会の様子</h1>
<p>ここまでお話しした内容を含め、勉強会では以下の内容を発表しました。</p>
<p>発表資料はこちら</p>
<p>発表時の個人的な裏テーマとして「あまり<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>が得意でない方に<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミング Ruby</a>を読みたくなるようにさせる」というものを設定していましたが、実際の発表後にちらほら<a href="https://www.oreilly.co.jp/books/9784873117430/">メタプログラミング Ruby</a>の話が出ていたので発表した甲斐がありました。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回はメドレー開発本部の技術勉強会(TechLunch)で発表した内容を紹介しました。</p>
<p>勉強会では、まず<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の言語要素とメソッド実行の理解を深め、その上で BasicObject#method_missing を理解し、それを使った gem のコードを読み進めました。</p>
<p>実際に<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>での<a href="https://d.hatena.ne.jp/keyword/%A5%E1%A5%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">メタプログラミング</a>に触れることで<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>への知見が深めていけたかと思います。 メドレー開発本部で実施している「TechLunch」での発表内容は今後も定期的に紹介していくので、是非メドレーブログをチェックしてみてください。</p>
<h3 id="お知らせ">お知らせ</h3>
<p>メドレーでは医療業界に存在する課題に IT を駆使して取り組んでいきたいデザイナー・エンジニアを募集中です。</p>
<p>皆さまからのご応募お待ちしております。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="募集職種 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.medley.jp%2Fjobs%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/jobs/">www.medley.jp</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- プロダクトへの「思い」を大切にし続ける開発チームを目指して〜メドレー開発本部合宿@まるも〜https://developer.medley.jp/entry/2017/02/14/180948https://developer.medley.jp/entry/2017/02/14/180948こんにちは、オンライン病気事典MEDLEYの開発をしてます徐聖博です。
先日、開発本部のメンバーで千葉県の「まるも・かぢや旅館」で合宿を行いました。
marumo.net // 『まるもの開発合宿プラン』を利用
今回は、開発本部で合宿を行...Tue, 14 Feb 2017 09:09:48 GMT<p>こんにちは、オンライン病気事典<a href="https://medley.life/">MEDLEY</a>の開発をしてます徐聖博です。
先日、開発本部のメンバーで千葉県の「<a href="https://marumo.net/gasshuku-plan001/">まるも</a>・<a href="https://www.kajiyaryokan.com/">かぢや旅館</a>」で合宿を行いました。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="開発合宿|千葉県金谷のまるもで開発合宿しませんか?" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fmarumo.net%2Fgasshuku-plan001%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://marumo.net/gasshuku-plan001/">marumo.net</a></cite> // 『まるもの開発合宿プラン』を利用
<p>今回は、開発本部で合宿を行った背景や、合宿の内容の紹介をしていきます。</p>
<h1 id="背景">背景</h1>
<h2 id="ストイックすぎる大人たち">ストイックすぎる大人たち</h2>
<p>メドレーはスタートアップ企業の中では比較的年齢層が高く、一部からは「大人の<a href="https://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%E3%A1%BC">ベンチャー</a>」と呼ばれることもあります。
中でも開発本部に関してはメンバー 17 人中、20 代は自分を含めて 3 人しかおらず、基本的に 30 代中盤の「大人(おっさん)」が集まる部署です。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="CLINICS のエンジニアリーダー田中さんに「聞いてみた」 by 平木 聡 | メドレー平木の「気になるあの人に聞いてみた」" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fmedley%2Fpost_articles%2F50054" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.wantedly.com/companies/medley/post_articles/50054">www.wantedly.com</a></cite>
<p>// 開発本部の**“ツートップ”**による対談。</p>
<p>普段はゆるく気さくな「大人」たちですが、仕事ではとことん真剣にプロダクトと向き合っています。</p>
<p>例えば、昨年 5 月に行った開発合宿では、昼に集合し途中夕食・ボーリングを挟み深夜 1 時ごろまで開発し、次の日は朝ごはんを食べて開発、昼ごはんを食べて開発。</p>
<iframe class="embed-card embed-blogcard" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" title="温泉浸かりながら開発(開発合宿@おんやど恵) - MEDLEY オフィシャルブログ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.medley.jp%2Fentry%2F2016%2F05%2F30%2F165640" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://developer.medley.jp/entry/2016/05/30/165640">develoepr.medley.jp</a></cite>
<p>// 昨年 5 月末に『おんやど恵』で行った開発合宿。</p>
<p>温泉に行ったにも関わらず 2 日間みっちり開発し、時間の限り最後までクオリティにこだわりやりきりました。</p>
<h2 id="成長してゆく組織">成長してゆく組織</h2>
<p>そんな合宿を行った昨年の 5 月末の 12 人から、メドレー開発本部には新たに 5 人のエンジニア・デザイナーが加わり合計 17 人体制になりました。 オンライン診療アプリ CLINICS の<a href="https://d.hatena.ne.jp/keyword/iOS">iOS</a>版・<a href="https://d.hatena.ne.jp/keyword/Android">Android</a>版もリリースし、プロダクトの数も増え、そして社員数が約 1.5 倍になるなど会社全体としても成長をしました。</p>
<p>会社の成長に伴い開発の業務が細分化し、「席は近いけど、やってることは全然違う」というような状況が増えました。
また、個人のプロダクトに対する思いを共有する場も日常の業務では少なくなってきました。</p>
<p>メドレーにおけるプロダクトは、一つひとつ「思い」があり、その「思い」によって生まれたプロダクトです。
そのため、同じ部署のメンバーが互いにどのような思いでそのプロダクト開発しているかを知りながら働くことは、とても大切なことだと考えています。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="MEDLEY(メドレー) オンライン病気事典" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmedley.life%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://medley.life/">medley.life</a></cite>
<p>// 正しい医療の情報を発信し、病気で不安を感じる人を一人でも減らしたい「思い」で生まれた MEDLEY</p>
<p>こうした背景の中、「リフレッシュ・コミュニケーション」をテーマに開発本部合宿を行いました。</p>
<h1 id="何をやったか">何をやったか</h1>
<p>今回、行った先は<a href="https://www.google.co.jp/maps/place/%E5%8D%83%E8%91%89%E7%9C%8C%E5%AF%8C%E6%B4%A5%E5%B8%82/@35.2490826,139.800212,12z/data=!3m1!4b1!4m5!3m4!1s0x60180e34a8ff9b05:0x18f16ebde5b12939!8m2!3d35.3041204!4d139.8571048?hl=ja">千葉県富津市</a>。</p>
<p>当日は強風に見舞われ、当初予定していた海釣りから予定変更し、鋸山へと行きました。</p>
<p>鋸山は標高 329.4m と、決して高くはありません。
しかし、登ってみると意外と階段が多く、普段おっさんたちが発しないような声色で「つらい」・「え、まだ登るの?」と音を上げながらもなんとか巡回コースを歩きました。
各々プライベートことも含め語り合いながら、そして苦労の末に山頂にたどり着き、そこで撮った集合写真はみなとてもいい笑顔で登ってよかったと思いました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170208/20170208113439.jpg" alt="f:id:yamadagenki:20170208113439j:plain" title="f:id:yamadagenki:20170208113439j:plain">
// 名物の地獄覗きを体験。撮影は高所恐怖症の<a href="https://www.wantedly.com/companies/medley/post_articles/42315">不惑エンジニア</a>) 。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170208/20170208113501.jpg" alt="f:id:yamadagenki:20170208113501j:plain" title="f:id:yamadagenki:20170208113501j:plain"> // 鋸山山頂で撮った記念写真。半分以上のメンバーは翌日筋肉痛になりました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170208/20170208113522.jpg" alt="f:id:yamadagenki:20170208113522j:plain" title="f:id:yamadagenki:20170208113522j:plain"> // おみくじを引き、今年の運勢を占ったり。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170208/20170208113559.jpg" alt="f:id:yamadagenki:20170208113559j:plain" title="f:id:yamadagenki:20170208113559j:plain"> // 男が 15 人で BBQ をすれば、当たり前のように肉の争奪戦が始まります。メシに上下関係なんてありません。</p>
<p>日々オフィスにこもりストイックに仕事をする開発本部メンバーにとって、山に登り体を動かしたり、バーベキューで肉の取り合いをすることは良いリフレッシュになったのではないかと思います。</p>
<h1 id="今後へ向けて">今後へ向けて</h1>
<p>「リフレッシュ・コミュニケーション」がテーマであるものの、ただリフレッシュし、コミュニケーションを取るだけが今回の目的ではありません。
メドレーという会社が目指す未来を実現するために、開発本部の今後のロードマップを定めることも直近のテーマです。</p>
<p>夜は、<a href="https://marumo.net/gasshuku-plan001/">まるも</a>にて「これまでの開発本部の道のり」と、そして「これからの開発本部がどうあるべき」か、
そして「株式会社メドレーが運営する 4 つのプロダクトをどういう気持ちで成長させていくか」ということに関して CTO の平山がその熱い胸の内を共有しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170208/20170208113659.jpg" alt="f:id:yamadagenki:20170208113659j:plain" title="f:id:yamadagenki:20170208113659j:plain"> // 平山の発表を真剣に聞くメンバーの様子。</p>
<p>発表は、2015 年から 2017 年の現在にいたるまでのメドレー開発本部の動きがまとめられている資料で、個人的に今までやってきたことを思い出し感慨深く感じました。
中でも、熱い思いが込められたこの一枚のスライドがとても印象に残っています。
ちょっと、内緒で拝借してご紹介します。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170207/20170207185100.png" alt="f:id:yamadagenki:20170207185100p:plain" title="f:id:yamadagenki:20170207185100p:plain">
// 平山の思いが込められたスライド。しっかりとメンバーの胸に刻み込まれました。</p>
<p>そのあとは、メンバー各々プロダクトへの思いや最新の技術の話題など、夜遅くまで真面目に、且つ面白く語り合っていました。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回はリフレッシュとコミュニケーションをテーマに、千葉県で合宿を行いました。</p>
<p>鋸山で山登りをし、<a href="https://marumo.net/gasshuku-plan001/">まるも</a>で夜遅くまでプロダクトに対する思いについて語り合い、リフレッシュもコミュニケーションもしっかりできました。
帰りは若干眠そうでしたが、みなスッキリした顔で解散し、良い合宿だったなと思いました。
今後も定期的に合宿を行い、プロダクトへ対する思いを共有し、その思いを大切にしながら開発していきたいと思いました。</p>
<h1 id="最後に">最後に</h1>
<p>今回企画してくれた新居さん、ありがとうございます!!!</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170208/20170208113720.jpg" alt="f:id:yamadagenki:20170208113720j:plain" title="f:id:yamadagenki:20170208113720j:plain"> // 恒例(にしていきたい)の到着直後の新居さんピース</p>medley
- 開発環境に Docker を導入し開発を効率化する話〜開発本部・ TechLunch〜https://developer.medley.jp/entry/2017/02/07/175125https://developer.medley.jp/entry/2017/02/07/175125口コミで探せる介護施設の検索サイト「介護のほんね」を開発している新居です。 本日は、メドレー開発本部にて隔週で行われている勉強会(TechLunch)で、Docker の発表を行ったのでその一部を紹介したいと思います。
メドレーと Doc...Tue, 07 Feb 2017 08:51:25 GMT<p><a href="https://www.kaigonohonne.com/">口コミで探せる介護施設の検索サイト「介護のほんね」</a>を開発している新居です。 本日は、メドレー開発本部にて隔週で行われている勉強会(TechLunch)で、Docker の発表を行ったのでその一部を紹介したいと思います。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20170131/20170131212540.png" alt="f:id:medley_inc:20170131212540p:plain" title="f:id:medley_inc:20170131212540p:plain"></p>
<h1 id="メドレーと-docker">メドレーと Docker</h1>
<p>弊社では<a href="https://medley.life/">オンライン病気事典 MEDLEY(メドレー)</a>の運用で Docker が使われています。 2015 年 2 月のリリース時から開発環境〜本番環境の全てで Docker が使われており、開発運用を通じてノウハウも少しずつ貯められています。</p>
<p>一方、私が以前関わっていた「<a href="https://job-medley.com/">ジョブメドレー</a>」の中で感じていた課題として、</p>
<ul>
<li>開発環境の構築で時間がとられる</li>
<li>たまに<a href="https://d.hatena.ne.jp/keyword/Mac">Mac</a>の OS(<a href="https://d.hatena.ne.jp/keyword/%A5%AA%A5%DA%A5%EC%A1%BC%A5%C6%A5%A3%A5%F3%A5%B0%A5%B7%A5%B9%A5%C6%A5%E0">オペレーティングシステム</a>)が新しいと、あるライブラリがインストールできなくなってしまう</li>
<li>本番環境で使っている nginx の設定を弄りたいがテストしにくい</li>
</ul>
<p>などがありました。</p>
<p>事業規模が大きくなっていくにつれエンジニア個々の時間の使い方も大事になっていきます。 頻度は少ないものの開発環境の構築で時間を無駄にしてしまったり、本番環境の稼働を止めてしまったりすることは事業に与える影響も大きくなってきます。</p>
<p>こうした課題意識が、自分の勉強も踏まえて「<a href="https://job-medley.com/">ジョブメドレー</a>」の開発環境を Docker 化してみるという取り組みに繋がりました。 さらにその勉強の中で得た知見を社内の勉強会でアウトプットすることで、また開発環境構築時にメンバーが Docker に触れる機会を作ることで技術の理解や底上げに繋がることを狙いました。</p>
<h1 id="docker-とは">Docker とは</h1>
<p>エンジニア界隈ではお馴染みの Docker ですが、このブログは医療従事者やエンジニア職ではない方もご覧になられていると思いますので、Docker の説明を簡単にしておきます。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Docker" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.docker.com%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.docker.com/">www.docker.com</a></cite>
<p>Docker とは、ひとことでいうと「<a href="https://d.hatena.ne.jp/keyword/Linux">Linux</a>上で動作するシンプルで使いやすい軽量コンテナ環境」です。 <a href="https://d.hatena.ne.jp/keyword/Linux">Linux</a>という OS 上にコンテナという箱を用意し、そのコンテナの中に必要なソフトウェアやライブラリ(ツール)などをインストールしてひとつのまとまりとしてパッケージングすることができます。</p>
<p>これにより、</p>
<ul>
<li><a href="https://d.hatena.ne.jp/keyword/%A5%A6%A5%A7%A5%D6%A5%B5%A1%BC%A5%D3%A5%B9">ウェブサービス</a>などが動作する環境をポータブルな環境にすることができ、開発環境や検証環境、本番環境でまったく同じ環境を作ることが容易になる</li>
<li>これまでのような開発環境と本番環境の微妙な違いによる動作の不具合や、開発環境では動いていたのに本番にデプロイしたら動かなくなりましたといった問題から解放される</li>
<li>エンジニアの精神的な不安も幾分か軽減される</li>
</ul>
<p>といったメリットを享受することができるようになります。</p>
<p>また、新しいメンバーやディレクターの PC で Docker が動く状態になっていれば開発環境を構築するのも格段に楽になります。 PC の OS の違いなどによるライブラリがインストールできないといった問題で時間を奪われることもなくなるでしょう。</p>
<p>このようにできるだけ簡単な言葉で Docker を説明してみましたが、Docker のようなコンテナ型仮想化技術というのは古くからあり、Docker を提供する Docker 社の FAQ ページではそれら(ここでは LXC)との違いが説明されています。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Docker Engine frequently asked questions (FAQ)" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.docker.com%2Fengine%2Ffaq%2F%23%2Fwhat-does-docker-technology-add-to-just-plain-lxc" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://docs.docker.com/engine/faq/#/what-does-docker-technology-add-to-just-plain-lxc">docs.docker.com</a></cite>
<h1 id="勉強会の内容">勉強会の内容</h1>
<p>冒頭でも述べましたが、弊社では MEDLEY の開発運用でのみ Docker を使っているため、自分も含め他サービスの運用メンバーで Docker に慣れていない人もいます(もちろん個人や前職で Docker を触っていたメンバーもいますが)。</p>
<p>メンバーが増えてくるにつれ個々の技術レベルにも差が生まれてくるので、そういった差を埋めるためにも今回は Docker 初心者向けに基本的な話と既存の<a href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>プロジェクトをどのように Docker 化していくかといった話をしました。</p>
<p>実際の内容としては、</p>
<ul>
<li>Docker、コンテナとは?</li>
<li>Docker for <a href="https://d.hatena.ne.jp/keyword/Mac">Mac</a>による Docker 環境の構築</li>
<li>Docker Machine</li>
<li>Docker Compose</li>
<li>xhyve</li>
<li>Kitematic</li>
<li>コンテナの作り方</li>
<li>Dockerfile の作り方</li>
<li>Docker Compose による複数コンテナの管理</li>
<li>ジョブメドレーの開発環境を Docker 化する話</li>
</ul>
<p>といった内容で発表を行いました。</p>
<p>Docker for <a href="https://d.hatena.ne.jp/keyword/Mac">Mac</a>については 2016 年 7 月に正式版がリリースされ、それまでの Docker Toolbox による Docker 環境構築に比べて幾分か楽になり、Docker Toolbox からの移行も簡単でより Docker が使いやすい状況へと進みつつあります。周辺のエコシステムが積極的に整備されているのは Docker の利点のひとつでもあります。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Get started with Docker for Mac" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.docker.com%2Fdocker-for-mac%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://docs.docker.com/docker-for-mac/">docs.docker.com</a></cite>
<p>また、Docker の内部でどういうツールが動いているのかを理解し、実際にコンテナを立てたり壊したりして試行錯誤を繰り返すことで基本的な使い方も体に染み付いていきます。</p>
<p>ここまで来れば実際のサービスの開発環境を Dockerfile と docker-compose.yml などで定義し、ビルド・実行することで Docker を使った開発環境を作ることができます。
まだ途中になっている部分もありますが、これらの過程の中で得た知見などを中心に発表を行いました。</p>
<h1 id="まとめ">まとめ</h1>
<p>メドレー開発本部の技術勉強会(TechLunch)で発表した内容の一部を紹介しました。</p>
<p>メドレーではこのような勉強会を通じてメンバーの技術の理解や底上げを促し、弊社が提供するサービスをより安全に効率良く開発・運用できるように努めています。
今回は開発環境を効率よく構築するためのツールとして Docker を取り上げ、それを開発本部内で勉強していきました。
これによって、開発本部のメンバーが開発しやすい環境を整えることを目指しました。
今後も新しくスタンダードになりつつある技術を積極的に勉強し、サービスをより良くする上で価値あるアウトプットにしていけるよう努めて行こうと思いました。</p>
<p>メドレー開発本部で実施している「TechLunch」での発表内容は今後も定期的に紹介していくので、是非メドレーブログをチェックしてみてください!</p>
<h2 id="参考">参考</h2>
<p>今回の勉強会ではこちらの本を参考にさせていただきました。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Docker 実戦活用ガイド | 吉岡 恒夫, paiza |本 | 通販 | Amazon" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.amazon.co.jp%2Fdp%2F4839959234" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.amazon.co.jp/dp/4839959234">www.amazon.co.jp</a></cite>
<p>何冊か手にとって読んでみましたが、こちらの本が基本的なことから網羅的に書かれており、また細か過ぎず読むのにも時間がかからないので、初心者の最初の 1 冊としてオススメしたいと思います。</p>
<h2 id="求人">求人</h2>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="CLINICS のエンジニアリーダー田中さんに「聞いてみた」 by 平木 聡 | メドレー平木の「気になるあの人に聞いてみた」" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fmedley%2Fpost_articles%2F50054" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.wantedly.com/companies/medley/post_articles/50054">www.wantedly.com</a></cite>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="デザイナー&エンジニア採用 | 株式会社メドレー" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.medley.jp%2Frecruit%2Fcreative.html" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.medley.jp/recruit/creative.html">www.medley.jp</a></cite>medley
- ビジネスにも役立つ学術論文を読むメリットとは〜開発本部・ TechLunch〜https://developer.medley.jp/entry/2017/01/24/182028https://developer.medley.jp/entry/2017/01/24/182028こんにちは、オンライン病気事典 MEDLEYの開発を担当しています徐聖博です。
メドレー開発本部では「TechLunch」という勉強会を隔週で開催しており、お互いのノウハウをシェアしています。今回私は、”学術論文を読む”ことについて発表しま...Tue, 24 Jan 2017 09:20:28 GMT<p>こんにちは、<a href="https://medley.life/">オンライン病気事典 MEDLEY</a>の開発を担当しています徐聖博です。
メドレー開発本部では「TechLunch」という勉強会を隔週で開催しており、お互いのノウハウをシェアしています。今回私は、”学術論文を読む”ことについて発表しました。
開発者だけではなく、技術に興味がある人、更には社会では働く全ての人が知って得する内容だと思うので、是非読んでみてください。</p>
<h1 id="なぜ徐が学術論文の読み方をお伝えするのか">なぜ徐が学術論文の読み方をお伝えするのか</h1>
<p>今では、メドレーで日々エンジニアとして Web 開発をしている私ですが、
大学院時代は<a href="https://d.hatena.ne.jp/keyword/%C5%EC%B5%FE%C2%E7%B3%D8">東京大学</a>大学院の<a href="https://www.honiden.nii.ac.jp/">本位田研究室</a>で、一流の研究者を志し”<a href="https://d.hatena.ne.jp/keyword/%BF%CD%B9%A9%C3%CE%C7%BD">人工知能</a>”の研究をしていました。</p>
<p>研究活動のひとつに”学術論文を書く”ということがあります。
自身が良い学術論文を書くためには、その分野の先行研究の”良い学術論文”に多く触れ、そこから研究内容や論文の書き方を学ぶ必要があります。
そのため、大学院時代は日々学術論文を読む生活を送っていました。
学術論文は、一定の読み方のルールやコツがあり、そこがハードルとなり読むことを挫折する人も少なくありません。
今回は「効果的な学術論文の読み方」や「どんな論文を読めば良いのか」を改めてまとめてみました。</p>
<h1 id="そもそも学術論文とは">そもそも、学術論文とは?</h1>
<p>スタートアップで働く人やエンジニアと呼ばれる職種の人々は日々”技術記事”と称される、技術関連のノウハウやベストプラクティスが詰まった記事を読んだりしていると思います。</p>
<h2 id="ノウハウやベストプラクティスが日々投稿されるサービスqiita">ノウハウやベストプラクティスが日々投稿されるサービス:Qiita</h2>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Qiita - プログラマの技術情報共有サービス - Qiita" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fqiita.com%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://qiita.com/">qiita.com</a></cite>
<h2 id="困ったときに世界中のわかる人に質問をするサービスstackoverflow">困ったときに世界中の”わかる人”に質問をするサービス:StackOverflow</h2>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Stack Overflow" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fstackoverflow.com%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://stackoverflow.com/">stackoverflow.com</a></cite>
<h2 id="その技術に精通している人が書くブログ酒と泪とrubyとrailsと">その技術に精通している人が書くブログ:酒と泪と<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>と<a href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>と</h2>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="酒と泪と Ruby と Rails と" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fmorizyun.github.io%2F" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://morizyun.github.io/">morizyun.github.io</a></cite>
<p>一方、学術論文には</p>
<ul>
<li>最先端の研究発表成果が載っている</li>
<li>第三者による査読がなされているので品質の担保がされている</li>
<li>関連した周辺の研究についても知識が増える</li>
</ul>
<p>など、学術論文を読むことならではのメリットがあると考えています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20170116/20170116165737.jpg" alt=""></p>
<h1 id="学術論文を読む意義とは">学術論文を読む意義とは</h1>
<p>学術論文を読むのは必ずしもメリットばかりではありません。
特に、学術論文を読むのは初心者にとっては一苦労です。</p>
<p>学術論文を読むデメリットを挙げると、</p>
<ul>
<li>いつ使えるかわからない技術が多い</li>
<li>読むのに時間がかかる(良い論文は英語で書かれているものが多い!)</li>
<li>高い費用がかかる</li>
</ul>
<p>特に、筆者が学生時代は毎日 1 本論文を読むことを努力目標にしていましたが、「いつ使えるかわからない技術が多い」という点で論文を読むのが本当に辛く感じていたのを今でも覚えています。</p>
<p>しかし、そんな苦痛を乗り越えた丘の向こうにはもちろん素晴らしい世界が広がっているはずです。
例えば、</p>
<ul>
<li>うまくビジネスに使いこなせたら”技術”で勝負ができる</li>
<li>ものごとを見る視点が増える</li>
<li>純粋に知らない技術を知るのが面白い</li>
</ul>
<p>“技術”で勝負したい、というのが技術者として働く者全てに共通する気持ちだと思っています。
技術記事を読むのに似ていますが、自分の知らない知識をインプットすることは純粋に楽しいことです。
また、学術論文では一つの物事を様々な角度からアプローチしているため物事を見る視点が増えます。
最終的に、まだビジネスに応用されていない技術を誰より先にビジネスに活かし、それで成功することは技術者にとってこれほど嬉しいことはないと信じています。</p>
<h1 id="勉強会の様子">勉強会の様子</h1>
<p>ここまでお話しした内容も含め、勉強会ではこんな内容を発表しました。</p>
<p>発表資料は<a href="https://speakerdeck.com/medley/medorekai-fa-bu-techlunch-xue-shu-lun-wen-wodu-ntemiyou">こちら</a></p>
<p>特に、自分が卒業した本位田研究室に伝わる論文のポイントは、
新規事業を始める際にも使える重要な視点だと思っており、CTO の平山も「うんうん」と深くうなずくき共感してもらえました。</p>
<h2 id="本位田研究室に伝わる論文のポイント">本位田研究室に伝わる論文のポイント</h2>
<ul>
<li>解くべき問題は何か?</li>
<li>なぜその問題を解く必要があるのか?</li>
<li>その問題は過去に解かれていないのか?</li>
<li>なぜ解かれていないのか?</li>
<li>アイディアの着眼点は何か?</li>
<li>具体的なアイディアは何か?</li>
<li>アイディアを具現化する際の困難さは何か?</li>
<li>何を評価しているのか?</li>
<li>問題・アイディア・評価はきちんと対応づいているか?</li>
<li>何が嬉しいのか?(有用性はなにか?)</li>
<li>アイディアはどの問題のどの範囲まで有効なのか?アイディアの限界を客観的に論じているか?</li>
<li>従来研究と公平に比較しているか?</li>
<li>比較している従来研究に漏れはないか?</li>
</ul>
<h1 id="まとめ">まとめ</h1>
<p>今回は、メドレー開発本部の技術勉強会(TechLunch)で発表した内容を紹介しました。
スタートアップで働くエンジニアには普段あまり馴染みがない学術論文ですが、技術で勝負するスタートアップの技術者にこそ学術論文を読むことは必要だと思っています。
いきなり英語で読むのも辛いという方は、日本語の学術論文から読み始めるのもありだと思いますので、これまで読んだことがない方はぜひ読んでみてください。</p>
<p>メドレー開発本部で実施している「TechLunch」での発表内容は今後も定期的に紹介していくので、是非メドレーブログをチェックしてみてください!</p>
<p>※<a href="https://www.facebook.com/medley.jp/">メドレー公式 Facebook ページ</a>に「いいね!」していただけると、ブログの最新情報をフォローできます
今後とも、よろしくお願いします!</p>medley
- 全ての働く人に知って欲しいプログラミング序論〜メドレー非エンジニア向け勉強会〜https://developer.medley.jp/entry/2017/01/17/174921https://developer.medley.jp/entry/2017/01/17/174921こんにちは、エンジニア・プロダーツプレイヤーの徐 聖博です(最近、介護施設の口コミサイト「介護のほんね」から、オンライン病気事典「MEDLEY」のチームに異動しました)。
メドレーでは、定期的にエンジニア向け勉強会はもちろんのこと、非エンジ...Tue, 17 Jan 2017 08:49:21 GMT<p>こんにちは、エンジニア・<a href="https://www.target-darts.jp/shopdetail/000000000022/ct33/page1/recommend/">プロダーツプレイヤー</a>の徐 聖博です(最近、介護施設の口コミサイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」から、オンライン病気事典「<a href="https://medley.life/">MEDLEY</a>」のチームに異動しました)。</p>
<p>メドレーでは、定期的にエンジニア向け勉強会はもちろんのこと、非エンジニア向けにも技術勉強会を開催しています。
<a href="https://info.medley.jp/entry/2016/11/02/122944">前回の非エンジニア向け Git 勉強会</a>に続き、今回は<strong>非エンジニアのためのプログラミング序論</strong>というタイトルで、全社員の希望者を対象にプログラミング入門講座を行いました。</p>
<iframe class="embed-card embed-blogcard" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" title="医師も Github にコミット!メドレーの目指す「全員で行う高速開発体制」について 〜非エンジニア向け勉強会(Git 編)〜 - MEDLEY オフィシャルブログ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.medley.jp%2Fentry%2F2016%2F11%2F02%2F122944" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://developer.medley.jp/entry/2016/11/02/122944">developer.medley.jp</a></cite>
<p>最終的に日常業務でもプログラミングを使い業務の効率化をするために、今回はプログラミングの入門編という位置づけで基礎的な内容について勉強しました。それをブログにまとめたので、ご興味ある方は是非最後まで読んでいただけると光栄です。</p>
<h1 id="でもプログラミングって難しいんでしょ">でも、プログラミングって難しいんでしょ?</h1>
<p>プログラミングというと、「なんか難しそう」・「え、私無理」と思う方が多いのではないでしょうか。
興味がわいても、<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>・<a href="https://d.hatena.ne.jp/keyword/PHP">PHP</a>・<a href="https://d.hatena.ne.jp/keyword/Java">Java</a>・<a href="https://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C 言語</a>・・・といった言語の多様さをみて「どれから勉強していいの?」と挫折してしまったという方もいるかもしれません。</p>
<p>「プログラミング」というのは限られた概念の中で、あの手この手で人の意図した処理を行っているだけなので難しいことはありません。
今回は、そのいくつかの概念を理解してしまえば簡単だという意識を持ってもらうことを目標にしました。</p>
<p>また、<a 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>も様々なものがありますがこちらも恐れることはありません。
様々な<a 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>がありますが、これは「こんにちは」という日本語を、英語で「Hello」、中国語で「你好(ニーハオ)」、フランス語で「<a href="https://d.hatena.ne.jp/keyword/Bonjour">Bonjour</a>(ボンジュール)」というように、“こんにちは”という概念を違う言葉に置き換えただけのものなのです。
さらには試験ではないので、ネットで<strong>検索し放題</strong>です。
様々な言語はありますが、基本となる概念は一緒なことが多いので恐れることはありません。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20161122/20161122151851.png" alt=""></p>
<p>メドレーで使われいる様々な言語。エンジニアは各々詳しい言語はあれども、業務において一通り触ります。</p>
<h1 id="勉強会の様子">勉強会の様子</h1>
<p>というわけで、ともに働く弁護士や医師、デザイナー、広報などなど「プログラミング触れたことないよ」という非エンジニア向けに、プログラミングについて勉強会を開催しました。
「介護のほんねってどのように動いているのか」「どういうシステムで何をプログラミングしているのか」ということから説明をはじめました。</p>
<p>そして、実際に<a 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>「<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>」を使ってもらいながら</p>
<ul>
<li>変数と代入</li>
<li>四則演算 + α</li>
<li>繰り返し</li>
<li>条件分岐</li>
<li>関数</li>
</ul>
<p>という 5 つの基本的な概念を勉強しました。</p>
<p>当日は「介護のほんねのこの部分はどんな関数で動いているの?」などの質問がどんどん飛んできたり「そうか、この動きはこういう意味だったのか!」というガッテンをいただくなど(予想以上に)盛り上がり、参加者がプログラミングを身近に感じる時間になったのではと思います。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20161212/20161212165359.jpg" alt=""></p>
<p>勉強会での様子</p>
<p>勉強会で使われた資料</p>
<h1 id="まとめ">まとめ</h1>
<p>実際に開発現場では、開発者はサーバー構成を初めとした<a 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://d.hatena.ne.jp/keyword/Ruby">Ruby</a>を使いながら学ぶ会としました。</p>
<p>プログラミングは難しいと思われがちですが、元をたどれば”ほぼ”今回勉強した概念でカバーできると考えています。
実際、「プログラミングは難しいから」と食わず嫌いをしている人は多いと思います。
「私、文系だから」「黒い画面をみるだけで無理」など様々な言葉を聞きますが、<strong>こうした「概念」だけ理解できれば特別恐れることはことはありません。</strong>
今回紹介した概念を理解することで、企画の際の作業工数見積がしやすくなったり、エンジニアからされる説明に対する理解が深まったりすると考えています。
また、エンジニアが日々何をやっているのかを理解する足がかりになればと思っています。</p>
<p>今後は、今回学んだプログラミングを基礎として、もっと実務で役立つプログラミングなどについての勉強会を行っていく予定です。
「スクレイプして営業リストを作成する」・「ボットを作って定常的なリマインドをさせる」・「定期的に発生する単純作業をプログラムで一瞬で終わらせる」など業務に役立つことを紹介していきたいと思っています。
怠惰は<a href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE%A1%BC">プログラマー</a>としての最大の美徳と言われています。このエッセンスを全ての働く人に知って頂き、日々の業務の効率化に努めていただきたいなと思っています。
プログラミングに興味がある方、非エンジニアにレクチャーしていきたいと考えている方、ぜひ感想や「もっとこうしてみては」というご意見などを頂けると嬉しいです!</p>
<p>※<a href="https://www.facebook.com/medley.jp/">メドレー公式 Facebook ページ</a>に「いいね!」していただけると、ブログの最新情報をフォローできます</p>
<p>今後とも、よろしくお願いします!</p>medley
- エムスリー・メドレー合同勉強会開催しました(MedNightTokyo#1 レポート)https://developer.medley.jp/entry/2016/12/26/183444https://developer.medley.jp/entry/2016/12/26/183444こんにちは、エンジニア兼ダーツプロの徐です。
2016 年 11 月 14 日(水)に株式会社ドリコムさんのイベントスペースをお借りして、エムスリー株式会社さんとの合同勉強会を開催しました。
「医療系サービスにおけるエンジニア運用裏話」とい...Mon, 26 Dec 2016 09:34:44 GMT<p>こんにちは、エンジニア兼<a href="https://www.target-darts.jp/shopdetail/000000000022/">ダーツプロ</a>の徐です。</p>
<p>2016 年 11 月 14 日(水)に株式会社<a href="https://d.hatena.ne.jp/keyword/%A5%C9%A5%EA%A5%B3%A5%E0">ドリコム</a>さんのイベントスペースをお借りして、<a href="https://corporate.m3.com/">エムスリー株式会社</a>さんとの合同勉強会を開催しました。
「医療系サービスにおけるエンジニア運用裏話」というテーマで、泥臭い・苦労した話やそれをどう工夫したかについて発表しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20161222/20161222141637.jpg" alt="f:id:yamadagenki:20161222141637j:plain" title="f:id:yamadagenki:20161222141637j:plain"></p>
<p>発表の様子</p>
<p>“開発”と言えば、「新規機能追加」を想像される方が多いのではないでしょうか。
しかし、実際現場で働くエンジニアは、新規開発をするのと並行して、安定したサービスを提供し続けるために日々努力しています。
大勢の人々が”当たり前に動くサービス”を実現するために、一生懸命になっているのです。
今回の勉強会はこうした各社の苦い話や知見を持ち寄った会となりました。</p>
<p>当日は各社 2 名ずつ、15 分弱の LT を行いました。 メドレーの発表内容を少しだけご紹介します。</p>
<h1 id="1-人開発体制における運用と工夫介護のほんね奮闘記">1 人開発体制における運用と工夫<del>介護のほんね奮闘記</del></h1>
<p>まずは私、徐より介護施設の口コミサイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」 を、エンジニア 1 人でどう運用しているかについてご紹介しました。
1人で運用する際の苦労は何か、そして1人で運用する過程で大事なこととは何かについて紹介していきました。
必ずしもシステム的な工夫だけではなく、周りのメンバーにうまく協力をしてもらう姿勢について、日々の心がけなどを話しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20161222/20161222141632.jpg" alt="f:id:yamadagenki:20161222141632j:plain" title="f:id:yamadagenki:20161222141632j:plain"></p>
<p>人前での発表が不慣れで緊張している私…</p>
<p>発表資料は<a href="https://speakerdeck.com/medley/1ren-kai-fa-ti-zhi-niokeruyun-yong-togong-fu-jie-hu-falsehonnefen-dou-ji">こちら</a></p>
<h1 id="オンライン病気事典-medley-の-地味だけど重要な-運用の話">オンライン病気事典 MEDLEY の 地味だけど重要な 運用の話</h1>
<p>そして、オンライン病気事典「<a href="https://medley.life/">MEDLEY</a>」を担当する<a href="https://www.wantedly.com/companies/medley/post_articles/37518">竹内</a>からは、運用の裏側として<strong>小規模チームに起きる問題</strong>とそれを解決する方法についての紹介がありました。
今回は、開発ばかりしているとつい疎かになりがちな**「運用手順書」**にスポットを当て、手順書を作成している際に気をつけているポイントについて解説しました。
発表資料は<a href="https://speakerdeck.com/medley/onrainbing-qi-shi-dian-medleyfalse-di-wei-taketozhong-yao-na-yun-yong-falsehua">こちら</a></p>
<h1 id="まとめ">まとめ</h1>
<p>今回の勉強会では、普段なかなか取り扱われない”運用”をテーマにし、苦労した点やそれを工夫した点など”地味だけど大事な”ことについて、各社のノウハウを話し合うことができました。</p>
<p>エンジニアの仕事は、新規機能開発より運用のほうが割合が高いことから、日々の業務のなかで共感できる・役に立つ話をできるのでは、と考え勉強会を企画しました。
発表中に「そうだよね」と頷く参加者の方もいらっしゃり、さらに終了後の懇親会では「すごい共感しました…」といった感想をいただくなど、参加者の方にも楽しんでいただけた企画となったのではと感じています。</p>
<p>運用は、システム的な自動化の工夫ももちろんですが、人が人力作業でやるものもあります。
エムスリーさんからも実状やノウハウを(ぶっちゃけ話も織り交ぜながら)伺えましたが、いずれも運用においてオンライン・オフラインを問わず**「コミュニケーション」(人間関係)**が大事なんだなと感じました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20161222/20161222141639.jpg" alt="f:id:yamadagenki:20161222141639j:plain" title="f:id:yamadagenki:20161222141639j:plain"></p>
<p>勉強会後の懇親会の様子。共感した・参考になったという話が多く飛び交いました。</p>
<p>今後もこうした実際の現場で役に立つ勉強会を定期的に開催していく予定です。 MedNightTokyo の Connpass グループで最新情報を発信してまいりますので、今回は残念ながら参加できなかった方も是非フォローをお願いします。</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="MedNight Tokyo" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmednight.connpass.com%2F" frameborder="0" scrolling="no"></iframe>
<p><cite class="hatena-citation"><a href="https://mednight.connpass.com/">mednight.connpass.com</a></cite></p>
<p>メドレーブログでも、勉強会情報を掲載していくので、ブログも引き続きチェックしてくださいね。</p>
<p>※<a href="https://www.facebook.com/medley.jp/">メドレー公式 Facebook ページ</a>に「いいね!」していただけると、ブログの最新情報をフォローできます</p>medley
- 医師も Github にコミット!メドレーの目指す「全員で行う高速開発体制」について 〜非エンジニア向け勉強会(Git 編)〜https://developer.medley.jp/entry/2016/11/02/122944https://developer.medley.jp/entry/2016/11/02/122944こんにちは、介護のほんねの開発担当エンジニア 兼 プロダーツプレイヤーの徐 聖博です。
メドレーでは定期的に技術勉強会を行っているのですが、この勉強会に参加するのはエンジニアばかりとは限りません。先日は非エンジニア向けに Git の勉強会を...Wed, 02 Nov 2016 03:29:44 GMT<p>こんにちは、<a href="https://www.kaigonohonne.com/">介護のほんね</a>の開発担当エンジニア 兼 <a href="https://www.wantedly.com/companies/medley/post_articles/31064">プロダーツプレイヤー</a>の徐 聖博です。</p>
<p>メドレーでは定期的に技術勉強会を行っているのですが、この勉強会に参加するのはエンジニアばかりとは限りません。先日は<strong>非エンジニア向けに Git の勉強会を開催</strong>しました。エンジニアではないメンバーも Git を使えるようにすることは、プロダクトの高速開発に大きな効果があると僕たちは考えています。今回は、その思想の背景と、実際に行った勉強会の内容についてご紹介します。</p>
<h1 id="一般的な開発フロー">一般的な開発フロー</h1>
<p>実際にサービスをコードレベルで把握・修正する人は大抵の場合エンジニア(一部デザイナー)と呼ばれる人種です。 しかし、一般的に、「サービスの運営」と一言で言っても、そこに携わる人の役割は様々です。 サービスの規模にもよりますが、ディレクターやエンジニア・デザイナーはもとより法務や広報など様々な方のサポートによって一つのサービスが成り立ちます。</p>
<p>こうした人が「使いにくい」「ここ、なんか文章間違ってる」と思った場合どうなるでしょうか。 通常は、ディレクターが意見をまとめて方針を決め、一度エンジニアと相談した上で、実装・確認の後リリースというような開発フローを経ます。(下図)</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20161031/20161031105325.png" alt=""></p>
<p>これでは改善をしようと思っていても、実際に実装が終わり改善されるまでに時間がかかりすぎてしまいます。</p>
<h1 id="メドレーが目指す開発の形">メドレーが目指す開発の形</h1>
<p>メドレーでは、こうした通常の開発における課題解決や、エンジニアリング技術を使った業務改善を目指し、定期的に非エンジニア・デザイナー向けの勉強会を開いています。</p>
<p>今回、<strong>初回として行ったのは非エンジニア向けの Git 講座</strong>です。
メドレーでは、先ほど述べたような問題を解決しリリースまでの時間を短縮できる開発フローを目指しており、その一環としてこのような勉強会を開きました。
下図のように、誤りを発見した人が直接自分で編集し、エンジニアがプルリクをレビューしてマージ・リリースすることで開発速度の向上・品質の担保を目指しています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20161031/20161031105322.png" alt=""></p>
<p>※勉強会で実際に使った資料はこちら</p>
<iframe id="talk_frame_361652" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/bfce6b87e40d47eeb38e1238e82e8efa" width="710" height="596" frameborder="0" allowfullscreen="true"></iframe>
<p><cite class="hatena-citation"><a href="https://speakerdeck.com/medley/di-1hui-medorefei-enziniaxiang-kemian-qiang-hui-fei-enziniafalsetamefalsezui-di-xian-falsegitcao-zuo">speakerdeck.com</a></cite></p>
<p>メドレーの<a href="https://d.hatena.ne.jp/keyword/Github">Github</a>には、エンジニア・ディレクターに加えて医師や営業といった様々な職種の方が開発者登録されており、全員で直接コードを編集し、日々プロダクト改善に努めています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20161102/20161102110814.png" alt=""></p>
<h1 id="まとめ">まとめ</h1>
<p>メドレーでは、課題解決・効率化を目指して、さまざまな勉強会を開催しています。非エンジニア・デザイナーに向けても、Git 講座を皮切りに今後月 1 で勉強会を開催予定です。 こうした勉強会の様子を定期的に記事にしていくので、ご興味ある方はメドレーブログをフォローいただけたらなと思っております。</p>
<p>※<a href="https://www.facebook.com/medley.jp/">メドレー公式 Facebook ページ</a>に「いいね!」していただけると、ブログの最新情報をフォローできます</p>
<p>今後とも、よろしくお願いします!</p>medley
- メドレーで初めての技術職インターンシップを実施しましたhttps://developer.medley.jp/entry/2016/10/18/170603https://developer.medley.jp/entry/2016/10/18/170603医療介護の求人サイト「ジョブメドレー」の開発を担当している新居です。 10 月になり肌寒い季節になってきましたが、メドレーでは今年の夏の 8 月から 9 月の間で技術職インターンシップ(以下、技術インターン)を実施しました。
最初に少しメド...Tue, 18 Oct 2016 08:06:03 GMT<p>医療介護の求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」の開発を担当している新居です。 10 月になり肌寒い季節になってきましたが、メドレーでは今年の夏の 8 月から 9 月の間で技術職<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3%A5%B7%A5%C3%A5%D7">インターンシップ</a>(以下、技術<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>)を実施しました。</p>
<p>最初に少しメドレーのエンジニアについて紹介すると、メドレーにはエンジニアが所属する開発本部があり、昨年 2015 年 7 月に CTO の平山が参画してから整備されてきました。弊社自体は 2009 年創業ではあるものの、正式に開発本部が立ち上がってからは 1 年弱です。開発本部には 10 月現在 14 名が所属しており、<a href="https://info.medley.jp/entry/2016/08/26/144702">こちらの記事</a>でも紹介しましたが医療 xIT という領域でプロダクトの開発をゴリゴリ進めています。</p>
<p>このようにまだまだスタートアップフェーズを走っている段階であり、教育や技術<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>開催に使える時間も限られている中ではありましたが、その中で最大限の成果を出せるよう、実施に向けて取り組むことになりました。自分自身としても<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生のメンターをするのは初でしたが、<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生とメンターである自分、そしてプロダクトを通じて社会のため(それが会社のためでもある)にしっかり価値を残せるようチャレンジしました。</p>
<p>ということで少し前置きが長くなりましたが、今回はメドレーで行った技術<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>の取り組みをひとつの事例として紹介してみようと思います。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20161017/20161017184047.jpg" alt="f:id:medley_inc:20161017184047j:plain" title="f:id:medley_inc:20161017184047j:plain"></p>
<h1 id="企画-1まずはインタビュー">【企画 1】まずはインタビュー</h1>
<p>今回の<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生は某大学の 3 年生 1 名で、まずはどういう流れで進めていくかを決めるために簡単にインタビューを行いました。 インタビューの内容は</p>
<ul>
<li>「なぜ技術<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>をやりたいのか?」</li>
<li>「大学ではどういう勉強をしてるのか?」</li>
<li>「技術スキルはどれくらいか?(どの言語を書いたことがあるかとか)」</li>
</ul>
<p>といった内容で、今回は受け入れ前提だったので技術試験などは行っていません。 インタビューの結果、細かい内容は伏せますが今回の<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生は大学の授業でプログラミングや簡単な<a href="https://d.hatena.ne.jp/keyword/UNIX">UNIX</a>コマンドに触れたことがあるというレベル感で、「将来エンジニアの道に進むかどうか悩んでいる」「開発の流れをひと通り経験してみたい」という要望を持っていました。</p>
<h1 id="企画-2ゴールと内容の大枠を決める">【企画 2】ゴールと内容の大枠を決める</h1>
<p>インタビューの結果を踏まえて、技術<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>で行う内容を決めていきます。インタビューによって<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生が求めていることがわかったので、それを満たせるようにゴール設定をします。今回だと「開発の一通りの流れを経験し、<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生本人の今後の進路決定のための判断材料やヒントを得てもらうこと」というゴールを設定しました。</p>
<p>また、冒頭でも述べましたが、スタートアップフェーズで少人数でプロダクト開発を進めている最中なので、メンターである自分の開発業務も疎かにできません。実際に開発・運用しているプロダクトでしっかりアウトプットを出し続けることが大切なので、<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生のレベル云々に関係なく、技術<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>でもどうにか実際に稼働しているプロダクトでアウトプットを出せるような企画を考えました。</p>
<p>そこで以下のような企画の大枠イメージを定義しました。</p>
<ul>
<li><a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生、自分、社会(会社)に価値を残すために、実プロダクト上でアウトプットをしっかり出す</li>
<li>実プロダクト上でアウトプットを出すことで得られる達成感や喜びを感じてもらう</li>
<li>ひと通りの開発フローを経験してもらう</li>
<li>メンターである自分にも開発業務があるので<a href="https://d.hatena.ne.jp/keyword/OJT">OJT</a>形式で行う</li>
</ul>
<h1 id="企画-3詳細な内容を決める">【企画 3】詳細な内容を決める</h1>
<p>ゴールと大枠を決めた後は、企画の詳細な内容を決めていきます。 以下のようなフェーズ 1〜4 を定義してみました。</p>
<h2 id="フェーズ-1基本的なウェブアプリケーション開発のフローを経験する">フェーズ 1「基本的なウェブアプリケーション開発のフローを経験する」</h2>
<p>今回の<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生のレベル感を踏まえて、まずは<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>と<a href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>を使った基本的なウェブアプリケーション開発のフローを経験してもらうことにしました(<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>と<a href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>は今回関わる実プロダクトで使っているため)。そこで教材として<a href="https://railstutorial.jp/">Ruby on Rails チュートリアル:実例を使って Rails を学ぼう</a>を使用しました。ご存知の方も多いと思いますが、<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>と<a href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>を使いながらウェブアプリケーション開発を体系的に学べるとても良い教材です。<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>と<a href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>はもちろんのこと、Git の使い方、デプロイ、テストといった実プロダクトの開発で必要不可欠な要素も含まれており、手厚いフォローがなくとも手を動かしながら進められるということで今回採用することになりました。</p>
<h2 id="フェーズ-2実プロダクトでウェブアプリケーション開発のフローを経験する小中規模">フェーズ 2「実プロダクトでウェブアプリケーション開発のフローを経験する(小〜中規模)」</h2>
<p>フェーズ 1 で流れを掴んだら、ここから実プロダクトの開発に関わってもらうことにしました。まずは文言の修正や追加などを通じて実プロダクト上での開発フローを経験してもらいます。</p>
<h2 id="フェーズ-3実プロダクトでウェブアプリケーション開発のフローを経験する中大規模">フェーズ 3「実プロダクトでウェブアプリケーション開発のフローを経験する(中〜大規模)」</h2>
<p>フェーズ 2 で慣れてきたらできる範囲で徐々に規模を大きくしていきます。 (小〜中、中〜大など抽象的なところはありますが、細かい定義は省略します)</p>
<h2 id="フェーズ-4難しい課題解決にチャレンジしてアウトプットを出す">フェーズ 4「難しい課題解決にチャレンジしてアウトプットを出す」</h2>
<p>フェーズ 3 の上位として実プロダクト上での難しい課題解決にチャレンジしてもらいます。ここは必須ではないですが、一応設定しておきました。</p>
<p>このような感じでフェーズに区切って定義してみました。</p>
<h1 id="企画-4ゴールと内容を共有し認識を合わせる">【企画 4】ゴールと内容を共有し認識を合わせる</h1>
<p>ゴールと内容が決まったら、それらを<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生に共有し、<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生のやりたいこととズレがないようしっかり認識を合わせました。問題がなければ開始日からどういう流れで進めていくかのスケジュールもすり合わせし、開始日を待つことになります。</p>
<h1 id="実施実際に実施した流れを紹介">【実施】実際に実施した流れを紹介</h1>
<p>いよいよ当日です。当初の計画通りにフェーズ 1 から実施しました。 実際にどういう流れで進んでいったか紹介してみます。</p>
<h2 id="初日から-4-日目">初日から 4 日目</h2>
<ul>
<li>フェーズ 1 の「<a href="https://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a> <a href="https://d.hatena.ne.jp/keyword/%A5%C1%A5%E5%A1%BC%A5%C8%A5%EA%A5%A2%A5%EB">チュートリアル</a>」を進めた</li>
<li>「<a href="https://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a> <a href="https://d.hatena.ne.jp/keyword/%A5%C1%A5%E5%A1%BC%A5%C8%A5%EA%A5%A2%A5%EB">チュートリアル</a>」の第 9 章のはじめくらいまで進めることができた</li>
<li>時間の都合もありここで「<a href="https://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a> <a href="https://d.hatena.ne.jp/keyword/%A5%C1%A5%E5%A1%BC%A5%C8%A5%EA%A5%A2%A5%EB">チュートリアル</a>」は終了した</li>
</ul>
<h2 id="5-日目">5 日目</h2>
<ul>
<li>ここから実プロダクトの開発環境構築を行った</li>
<li>早速実プロダクトの文言調整などを行った(ここでブランチ操作や PR の出し方なども経験)</li>
</ul>
<h2 id="6-日目から-9-日目">6 日目から 9 日目</h2>
<ul>
<li>実プロダクトで小さい開発業務を数件こなした</li>
<li>表示まわりの調整が中心だが、データベースから必要なデータを取得したり、条件による出し分けをしたり、プロダクト開発で考えないといけないことを経験した</li>
<li>ここまでで実プロダクト上でのひと通りの開発フローを経験した</li>
</ul>
<h2 id="10-日目から-13-日目">10 日目から 13 日目</h2>
<ul>
<li>実プロダクトの中規模な開発業務を経験した(内容はメディア系のプロダクトの新規ページの追加)</li>
<li>ルーティング、コントローラ、ビューなどのページ遷移に必要な処理を実装した</li>
<li>新規ページに表示する情報の取得、情報の整形や表示まわりの処理を実装した</li>
<li>データーベースへのデータのインサート、アップデートなども経験した</li>
</ul>
<h2 id="14-日目から-17-日目">14 日目から 17 日目</h2>
<ul>
<li>テストコードを追加した</li>
<li>普段手動で行っていたことの自動化をする<a href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>の開発をした(後回しにしていた課題などの解決)</li>
<li>日程の都合もありここで技術<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>は一区切り</li>
</ul>
<p>こんな感じで一気に紹介してみましたが、随所で適宜フォローを入れつつ短期間で実プロダクト上でアウトプットをしっかり出すことができました。もちろん<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生の成果物はしっかりコードレビューと品質レビューをした後、本番にリリースされました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20161017/20161017184012.png" alt="f:id:medley_inc:20161017184012p:plain" title="f:id:medley_inc:20161017184012p:plain"></p>
<h1 id="実施感想">【実施】感想</h1>
<p>今回の技術<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>終了後、企画していたフェーズ 3 くらいまでは到達でき、<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生からも当初の目的を満たせたという感想を頂くことができました。ひと通りの開発フローを経験できたこと、実プロダクト上で作ったものが本番環境にリリースされるドキドキや達成感を感じてもらえて、良い経験になったのではないかと思います。</p>
<p>スタートアップということで普段の開発業務と並行し、数日ガッツリ時間をとってフォローするなどはできませんでしたが、<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生の目的を満たせたこと、そして実プロダクト上で新しい機能を追加してリリースできたり、普段の自分の業務で手が回らず後回しになっていた課題なども解決することができ、<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生と自分、社会(会社)のために価値を残すことができたのではないかと思います。</p>
<h1 id="さいごに">さいごに</h1>
<p>ということで、今回はメドレーで実施した技術<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>の事例を紹介してみました。スタートアップというフェーズらしく、意思決定も柔軟で、今回のような技術<a href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>の企画・実施という突発的な業務であったり、会社の成長と共に生まれる様々な問題や課題の解決に奔走することも多々ありますが、それもまた醍醐味でしょう。働く環境の整備やエンジニアの文化形成も徐々に進められており、大変なこともありますがスタートアップフェーズでしか経験できないような濃い仕事がたくさんあります。集まっているメンバーは豊富な経験を持った 30 代が中心ですが、もちろん 20 代でも、医療 xIT で挑戦したいやる気ある方を募集中ですので、興味のある方はぜひご連絡ください!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="株式会社メドレーの採用/求人一覧 - Wantedly" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fmedley%2Fprojects" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.wantedly.com/companies/medley/projects">www.wantedly.com</a></cite>medley
- プロダクト品質向上のためのフルスタック開発〜メドレーにおける機能ベース開発について〜https://developer.medley.jp/entry/2016/08/26/144702https://developer.medley.jp/entry/2016/08/26/144702文責:徐 聖博(ダーツプロ)
こんにちは!メドレーのダーツプロこと徐です。
社会人歴・エンジニア歴 3 年目ながら、メドレーでは口コミで探せる介護施設の検索サイト「介護のほんね」の開発・運用を 1 人で担当しています。(インフラ整備からフ...Fri, 26 Aug 2016 05:47:02 GMT<p>文責:徐 聖博(<a href="https://www.target-darts.jp/shopdetail/000000000022/ct33/page1/recommend/">ダーツプロ</a>)</p>
<p>こんにちは!メドレーのダーツプロこと徐です。 </p>
<p>社会人歴・エンジニア歴 3 年目ながら、メドレーでは<strong>口コミで探せる介護施設の検索サイト「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」の開発・運用を 1 人で担当</strong>しています。(インフラ整備からフロントエンド開発まで行っています) </p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160725/20160725165814.png" alt="f:id:yamadagenki:20160725165814p:plain" title="f:id:yamadagenki:20160725165814p:plain"></p>
<p>現在筆者が一人で担当している介護施設の口コミサイトである「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」</p>
<p>前職では Web ゲームのバックエンド開発(<a href="https://d.hatena.ne.jp/keyword/PHP">PHP</a>)を担当していて、メドレーに入った時点では</p>
<ul>
<li><strong>「インフラ?経験無いです…」</strong></li>
<li><strong>「フロントエンド?<a href="https://d.hatena.ne.jp/keyword/Javascript">Javascript</a>で書くんですよね…?」</strong></li>
<li><strong>「<a href="https://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a>?<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>なら書くだけかけますけど…」</strong></li>
</ul>
<p>という状態でした。今回はそんな自分が、入社後半年で<a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EB%A5%B9%A5%BF">フルスタ</a>ックエンジニアとして<a href="https://www.kaigonohonne.com/">介護のほんね</a>を 1 人で担当できるまでに至った、メドレーの開発体制について紹介します。</p>
<h1 id="メドレーのプロダクト">メドレーのプロダクト</h1>
<p>メドレーには Web アプリケーションから<a href="https://d.hatena.ne.jp/keyword/iOS">iOS</a>のアプリケーションまで様々なプロダクトがあります。</p>
<p>サーバーサイドに<a href="https://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a>を用いるプロダクトを初め、<a href="https://d.hatena.ne.jp/keyword/PHP">PHP</a>を使ったプロダクトもあり、構成はプロダクトにより様々です。フロントエンドでは、AngularJS や Mithril の<a href="https://d.hatena.ne.jp/keyword/MVC">MVC</a>の<a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>を使用したり、<a href="https://d.hatena.ne.jp/keyword/CoffeeScript">CoffeeScript</a>や Babel なる AltJS を用いて開発しており、幅広い技術を採用しています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160610/20160610160409.png" alt="f:id:yamadagenki:20160610160409p:plain" title="f:id:yamadagenki:20160610160409p:plain"></p>
<p>サーバーサイドを<a href="https://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a>で書き、Mithril を使い SPA で作られている「<a href="https://clinics.medley.life/">CLINICS</a>」。インフラは、Heroku を採用。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160610/20160610162818.png" alt="f:id:yamadagenki:20160610162818p:plain" title="f:id:yamadagenki:20160610162818p:plain"></p>
<p><a href="https://clinics.medley.life/">CLINICS</a>では、メドレーで初となる<a href="https://d.hatena.ne.jp/keyword/iOS">iOS</a>アプリをリリース。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160610/20160610160403.png" alt="f:id:yamadagenki:20160610160403p:plain" title="f:id:yamadagenki:20160610160403p:plain"></p>
<p><a href="https://d.hatena.ne.jp/keyword/PHP">PHP</a>の爆速 Phalcon<a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>を使用したオンライン病気事典「<a href="https://medley.life/">MEDLEY</a>」</p>
<p>このように多種多様な技術を用いながら、どのプロダクトにおいてもメンバーはそれぞれインフラからフロントエンドまで幅広い開発をしています。しかし、もともと<a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EB%A5%B9%A5%BF">フルスタ</a>ックエンジニアだったという人は少なく、尖った強みを持ちながら、メドレーに入社後、徐々に対応範囲を広げてきたというケースが多いです。 </p>
<p>これは、メドレーにおける”機能ベース開発”という進め方が大きく関係しています。</p>
<h1 id="機能ベース開発がエンジニアをフルスタックにする">機能ベース開発が、エンジニアを<a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EB%A5%B9%A5%BF">フルスタ</a>ックにする</h1>
<p>メドレーでは、基本的に機能ベースで ISSUE ・タスクを切り分け、それぞれの ISSUE ・タスクを週のはじめのミーティングにおいて担当者を決めていきます。 </p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160824/20160824092401.png" alt="f:id:yamadagenki:20160824092401p:plain" title="f:id:yamadagenki:20160824092401p:plain"></p>
<p>それぞれ業務の幅が広いタスクがたくさんあり、週のはじめに担当を決めていく</p>
<p>インフラやフロントエンドの得手不得手は各々ありますが、基本的には関係なくタスクが割り振られ(もしくは挙手制で)作業を進めていきます。</p>
<p>「知識が足りない・全く経験がないタスクが回ってきた!(> <)」などという場合も、お互い協力・教えあいながらタスクをこなしていくので、経験のないチャレンジングなタスクに挙手できたりします。 </p>
<h1 id="システム全体を把握して開発することでプロダクト全体の品質向上">システム全体を把握して開発することで、プロダクト全体の品質向上</h1>
<p>このタスクベース開発体制を続けていると、システム全体の開発に携わることが多くなります。入社当時はバックエンドしか知識がなかったエンジニアも、開発する機能が増え、プロダクト全体の構成を把握する機会が増えるため、入社から半年〜1 年経つ頃には、1 つのサービスを一通りインフラからバックエンド、フロントエンドまで触ることになります。 </p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160824/20160824093922.png" alt="f:id:yamadagenki:20160824093922p:plain" title="f:id:yamadagenki:20160824093922p:plain"></p>
<p>Elasticsearch の基盤整備から、DB の<a href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>設計、バックエンド・フロントエンドの作りこみまで
全て一人で開発したジョブメドレーのトップ画面</p>
<p>こうしてシステム全体を把握することで、エンジニアたちがそれぞれ他の分野を考慮したプログラムを書けるようになり、<a href="https://d.hatena.ne.jp/keyword/API">API</a>の設計・ユーザインタラクション開発や、インフラ構成を配慮したキャッシュ機能開発など、一貫性をもった開発が可能となります。</p>
<p>もちろんもともと、特定分野に強みを持つエンジニアが集まっていますので、それぞれ得意分野を活かしつつも、お互いの領域が理解できることで、社内のプロダクトの全体的な最終的な品質向上を実現できていると感じています。</p>
<h1 id="まとめ">まとめ</h1>
<p>今回はメドレーにおける機能ベースの開発体制を紹介しました。</p>
<p>メドレーでは、このようにどのプロダクトでも個人が機能ベースで開発をしていきます。そのため、入社時点で経験がない分野でも一年ほど開発に携わることで、一通りのインフラからフロントエンドまでの開発をこなせる知識がつき、僕も一人で 1 つのプロダクトを担当できるまでになりました。</p>
<p>しかし、これは機能ベース開発だけのおかげではなく、様々な分野のとても気さくなシニアエンジニアが集まり、気軽に質問できる開発部の雰囲気も大いに関係していると思っています。僕自身この 1 年で、さまざまな分野に挑戦しましたが、各領域のプロがレクチャーしてくれることで、その分野に自信をもって取り組めるようになったと感じています。</p>
<p>また、メドレーに集まるエンジニアは皆開発が大好きです。各々が日々技術に対して貪欲で、勉強が好き・開発が好きという個人の努力により、知らない困難なタスクも進んで開発した結果<a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EB%A5%B9%A5%BF">フルスタ</a>ックエンジニアになります。これが最終的なプロダクト品質へのこだわりになり、品質の高いプロダクトができているのだと思っています。</p>
<p><a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EB%A5%B9%A5%BF">フルスタ</a>ックな開発がしたい、システム全体考えた品質の高い開発をしたいような方は是非メドレーで一緒に働いてみませんか?</p>
<p>勉強大好き、開発するのが大好き!ダーツが大好き!(?)というようなエンジニアをメドレーでは募集しています。 </p>
<p>ぜひ、よろしくお願いします!</p>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="株式会社メドレーの採用/求人一覧 - Wantedly" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fmedley%2Fprojects" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://www.wantedly.com/companies/medley/projects">www.wantedly.com</a></cite>medley
- 医療体験を再デザインする - コーポレートサイトリニューアルへの想いhttps://developer.medley.jp/entry/2016/08/18/110000https://developer.medley.jp/entry/2016/08/18/110000はじめまして、メドレーのデザイナーの波切と申します。
今年の 7 月に入社してから取り組んでいたコーポレートサイトのリニューアルを公開しました。これをきっかけに、リニューアルの経緯とメドレーのデザインのこれからについて少しお話しさせていただ...Thu, 18 Aug 2016 02:00:00 GMT<p>はじめまして、メドレーのデザイナーの波切と申します。
今年の 7 月に入社してから取り組んでいたコーポレートサイトのリニューアルを公開しました。これをきっかけに、リニューアルの経緯とメドレーのデザインのこれからについて少しお話しさせていただければと思います。</p>
<div class="remark-link-card-plus__container">
<a href="https://www.medley.jp/" target="_blank" rel="noreferrer noopener" class="remark-link-card-plus__card">
<div class="remark-link-card-plus__main">
<div class="remark-link-card-plus__content">
<div class="remark-link-card-plus__title">医療ヘルスケアの未来をつくる|株式会社メドレー</div>
<div class="remark-link-card-plus__description">メドレーは、テクノロジーを活用した事業やプロジェクトを通じて「納得できる医療」の実現を目指します。</div>
</div>
<div class="remark-link-card-plus__meta">
<img src="https://www.medley.jp/images/favicon.ico" class="remark-link-card-plus__favicon" width="14" height="14" alt="favicon">
<span class="remark-link-card-plus__url">www.medley.jp</span>
</div>
</div>
<div class="remark-link-card-plus__thumbnail">
<img src="https://www.medley.jp/img/ogimage.png" class="remark-link-card-plus__image" alt="ogImage">
</div>
</a>
</div>
<h1 id="医療体験を再デザインする">医療体験を再デザインする</h1>
<p>メドレーはインターネットを通じて医療ヘルスケア分野の課題を解決することを目指す会社です。</p>
<p>・医師がつくるオンライン病気辞典「<a href="https://medley.life/">MEDLEY</a>」
・遠隔診療ソリューション「<a href="https://clinics.medley.life/">CLINICS</a>」
・日本最大級の医療介護求人サイト「<a href="https://job-medley.com/">ジョブメドレー</a>」
・口コミで探せる介護施設の検索サービス「<a href="https://www.kaigonohonne.com/">介護のほんね</a>」</p>
<p>現在は上記の 4 つのサービスがそれぞれに医療と介護にまつわる課題解決に向けて運営を行っており、メドレーのデザインの根幹も会社のミッション同様、医療ヘルスケア分野の課題を解決し、医療体験の再デザインを行っていくことにあります。</p>
<h1 id="コーポレートサイトのリニューアルはデザインに対する意思表明">コーポレートサイトのリニューアルはデザインに対する意思表明</h1>
<p>これまでのメドレーは専任デザイナーが不在だったため、サービスごとに様々なデザイン上の課題を抱えていました。そんななかで、入社して真っ先に取り組んだプロジェクトはコーポレートサイトのリニューアルでした。</p>
<p>これは、自身が入社して感じた社内の熱気と充実したサポートチームの存在から、「サービスを利用してもらうユーザーや医療施設の方に対してはもちろん、メドレーに興味を持っている方にもどのような人間がサービスを作り運営しているかを知ってもらいたい」と感じたことがきっかけとなっています。
さらに、開発をリードする CTO の平山はデザインへの理解も深く、デザイン体制の構築が始まったタイミングで「メドレーは事業面だけでなくデザイン面でも業界をリードしていく」という意思表示を込め、まずは会社の顔であるコーポレートサイトのリニューアルに取り組もうということになりました。</p>
<p>その結果として<a href="https://takehirogoto.com/">後藤武浩</a>さんに人と社内の空気を写真と動画に収めてもらい、メドレーが持つ熱気のある力強さを掛け合わせたデザインに仕上げました。
リリースしてから間もないのでまだ評判は聞けていませんが、奇をてらわず、社内の雰囲気が見えデザインへの意識も伝えられるサイトが出来たのではないかと思っています。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20160816/20160816203803.jpg" alt="f:id:medley_inc:20160816203803j:plain" title="f:id:medley_inc:20160816203803j:plain"></p>
<h1 id="ユーザーファーストとパブリックマインド大きいデザインと小さいデザイン">ユーザーファーストとパブリックマインド、大きいデザインと小さいデザイン</h1>
<p>メドレーを「医療ヘルスケア分野のスタートアップ」として捉えていらっしゃるかもしれません。しかしメドレーは、その枠組みでは伝わらない大きな夢を持った会社です。<a href="https://d.hatena.ne.jp/keyword/%C2%E5%C9%BD%BC%E8%C4%F9%CC%F2">代表取締役</a>医師の豊田をはじめ社内には現在 7 名の医師がおり、「納得できる医療が実現できる社会づくりに貢献する」という想いを持って事業へ取り組んでいます。</p>
<p>先にお伝えしたようにメドレーは医療体験そのものをデザインし直そうとしており、UI/UX、HCD という「ユーザーが喜ぶか」という視点だけでなく、事業会社としての数値達成への視点ももちろん、「医療領域の課題解決につながるか」という社会的意義の強い視点と、多くの視点を持ってデザインに取り組んでいく必要があると感じています。</p>
<p>社会的意義が強く大きな夢を描くほど、多くの人を惹きつけるヴィジョンを見せ、実際のプロダクトも作り上げなければいけない必要があり、それらの取り組みをしていく上で「大きな視点への理解」と、人を惹きつけるための「細かい配慮と魅力あるアート(個でもあり美術でもある)」の両立がデザインの鍵と感じています。
デザインに求められることも多いですが、エンジニアとプランナーと協力しながら多くの人を幸せにできるサービスを作っていきたいと思っています。</p>
<h1 id="ブレのない一つのストーリーを">ブレのない一つのストーリーを</h1>
<p>デザイナーの大事な仕事としてサービスのデザインの他にもメドレーという会社と各サービスのブランド構築があります。</p>
<p>今回のコーポレートサイトもその一環で、ロゴのデザインやレギュレーション設計といった見た目の機能的な側面はもちろん、会社と各サービスの関係性を整理したサービス<a href="https://d.hatena.ne.jp/keyword/%A5%DD%A1%BC%A5%C8%A5%D5%A5%A9%A5%EA%A5%AA">ポートフォリオ</a>の管理、イベントや映像など様々な表現に寄与できるブランドコンセプトの管理をしていきたいと考えています。</p>
<p>幸いメドレーは創業から今までと各サービスの関係性には「医療ヘルスケア分野の課題を解決する」というブレのない一つのストーリーに集束出来ており、シンプルで強いブランド構築が出来ると感じています。
ブランド構築の手法も従来の「知ってもらうためにブランドイメージをばらまく」ようなものではなく、スタートアップらしく事業ドリブンでメディアやサービスサイトなどメドレーとの接点となる体験の質を上げて、良いブランドイメージを積み上げていくような方法をと考えています。</p>
<h1 id="メドレーは堂々と王道を歩む">メドレーは堂々と王道を歩む</h1>
<p>MEDLEY では医療が必要な人・関わる人に対して最新かつ正しい情報を提供できるよう、350 人を超える医師が匿名で多角的な検討とチェックを行う体制を築くことで、その品質を向上させています。CLINICS では領域自体がこれから作られていく遠隔診療について、法的観点の見解を明確にし、医療機関と綿密にコミュニケーションを取るなど、適切な普及に向けて貢献しています。</p>
<p>ジョブメドレーでは医療現場の人材不足解決のために、離職中の資格保有者への復職や、都心に比べ情報へのアクセスが難しい地方求人の取り扱いにも注力するほか、求職者および医療施設の双方にむけたサポート体制を充実させています。介護のほんねにおいても急な施設入居が必要になった方・十分に施設を検討したい方のために相談員による電話サポートを行っています。</p>
<p>会社としても、それぞれの事業で真摯に課題とユーザーに向き合うことができるよう、社員がやりたい・やるべきことをスピード感をもって取り組むための体制づくりを目指しています。</p>
<p>事業がどのような社会貢献を果たしているかということは、ブランドとデザインを考える上で最も重要なことです。メドレーは、ユーザーはもちろん医療領域・社会全体へ貢献するためにあらゆるサービスを提供しており、余計な手を加える必要もなく今ある姿をシンプルにデザインへ落としこめる状態にあります。</p>
<p>医療領域においても、デザインにおいても、メドレーは堂々と王道を歩むことが出来る会社であり、そのデザインを出来ること・関われることはデザイナーとしても理想的な環境だと思っています。</p>
<h1 id="未完成を楽しめるデザイナー">未完成を楽しめるデザイナー</h1>
<p>メドレーのデザインとこれからやっていきたいことについてお話しさせていただきましたが、デザイナーはとにかく人手が足りておらず、メドレーが描く大きな夢をブランドやサービスとして、抽象と具体を行き来して設計に落とし込めるデザイナーを募集しています。</p>
<p>※<a href="https://www.medley.jp/recruit/creative.html">募集概要はこちら</a></p>
<p>会社や事業としてポテンシャルが高くその部分では良い仕組みが出来てきているのに、それに続くデザイナーの体制がまだまだ構築出来ていません。裏を返せばこれだけ可能性を秘めている会社のデザイン体制を 0 から構築出来る機会なんてそうそう出会えないと思う(自身がそう思って転職した)ので、未完成であることに魅力を感じ共感していただける人であればとにかく一度オフィスへ遊びに来ていただきたいです。</p>
<h1 id="まだまだ始まったばかり">まだまだ始まったばかり</h1>
<p>今日お話しした内容も、まだまだ「こうしたい」の域であってこれからどんどん実行していかなければいけません。
メドレーには経験豊富なエンジニアチームと理解のあるディレクター・プランナーしかいない、とてもデザインに集中出来る環境が揃っているので、これから成果が出るまで愚直にやり続けていきたいと思います。後々にはデザイナーコミュニティにも貢献していければと思っていますので、ぜひこれからもメドレーの動向を気にかけていただけますと幸いです。(そしてデザイナーの方はぜひ一度遊びに来てください!)</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20160816/20160816203808.jpg" alt="f:id:medley_inc:20160816203808j:plain" title="f:id:medley_inc:20160816203808j:plain"></p>medley
- 温泉浸かりながら開発(開発合宿@おんやど恵)https://developer.medley.jp/entry/2016/05/30/165640https://developer.medley.jp/entry/2016/05/30/165640文責:徐聖博(ダーツプロ)
日常業務で後回しになっている課題を合宿で
メドレーには開発本部という、エンジニア・デザイナー全員が所属する組織があります。
蓋を開けてみると、チームに分かれていて、各々のプロダクトを担当しています。
そのため、社...Mon, 30 May 2016 07:56:40 GMT<p>文責:徐聖博(<a href="https://www.target-darts.jp/shopdetail/000000000022/">ダーツプロ</a>)</p>
<h1 id="日常業務で後回しになっている課題を合宿で">日常業務で後回しになっている課題を合宿で</h1>
<p>メドレーには開発本部という、エンジニア・デザイナー全員が所属する組織があります。</p>
<p>蓋を開けてみると、チームに分かれていて、各々のプロダクトを担当しています。</p>
<p>そのため、社内のシステム整備や、プロダクトを横断してやるようなプロジェクトは日常的にやる機会がありません。</p>
<p>今回の合宿では、各プロダクトのメンバーをシャッフルし、4 チームに別れ各々の課題を解いてもらう形式で合宿を行いました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530133300.png" alt="f:id:yamadagenki:20160530133300p:plain"></p>
<p>プロダクトメンバーをシャッフルして 4 チームに</p>
<h1 id="ということで湯河原に合宿しに来ました">ということで、湯河原に合宿しに来ました</h1>
<p>今回の合宿地は、神奈川県湯河原。</p>
<p>都内から、特急乗って 1 時間ちょっとなので、立地的には十分近い場所。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530105740.png" alt="f:id:yamadagenki:20160530105740p:plain"></p>
<p>意外と都内から近くてびっくり</p>
<h1 id="駅からバスで約-7-分そこは理想郷であった">駅からバスで約 7 分、そこは「理想郷」であった</h1>
<p>駅からバスに約 7 分乗り、「理想郷」という名のバス停で下車。</p>
<p>今回の合宿地であるおんやど恵は、バス停のすぐ目の前にありました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530110057.jpg" alt="f:id:yamadagenki:20160530110057j:plain"></p>
<p>無事に宿について喜ぶ新居さん</p>
<h1 id="早速開発開始">早速、開発開始</h1>
<p>今回の合宿は事前に課題を設定し、4 チームに分けそれぞれ開発を進めていくスタイル。</p>
<p>開発中は、普段あまり関わりがないメンバーとも和気あいあいと、チームで開発を進めました。</p>
<p>1 日目は各チーム深夜まで開発が続きました。</p>
<p>みんなさん開発好きですね。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530110939.jpg" alt="f:id:yamadagenki:20160530110939j:plain"></p>
<p>普段と違うメンバーでやる開発はとても学ぶことが多かったようです</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530130713.jpg" alt="f:id:yamadagenki:20160530130713j:plain"></p>
<p>お互いのもつ知識を共有しつつ、相談しながら開発</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530130759.jpg" alt="f:id:yamadagenki:20160530130759j:plain"></p>
<p>ホワイトボードを使ったディスカッションも白熱しました</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530130730.jpg" alt="f:id:yamadagenki:20160530130730j:plain"></p>
<p>会社ではできないこんな体勢での開発も合宿なら可能!!</p>
<h1 id="なんといっても温泉と美味しいごはん">なんといっても温泉と美味しいごはん</h1>
<p>おんやど恵にしてよかったなと思ったのは、なんと言っても温泉とご飯でした。</p>
<p>24 時間入れる足湯から、大浴場の露天風呂といい最高でした。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530111435.jpg" alt="f:id:yamadagenki:20160530111435j:plain"></p>
<p>足湯。タオルがあるので手ぶらでいけます</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530111536.jpg" alt="f:id:yamadagenki:20160530111536j:plain"></p>
<p>1日目夕食。もっとたくさん料理が出てきました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530111956.jpg" alt="f:id:yamadagenki:20160530111956j:plain"></p>
<p>2 日目朝食。とても量があって朝からお腹いっぱい!</p>
<h1 id="お楽しみイベントでボーリング">お楽しみイベントでボーリング</h1>
<p>1日目の夜には、近くにあるボーリング場でみんなでボーリングをしました。
トップのスコアは後藤さんの 178。久しぶりにやる人とは思えないスコアでした。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530113358.jpg" alt="f:id:yamadagenki:20160530113358j:plain"></p>
<p>トップのスコアは後藤さんの 178。冷静にすごい</p>
<h1 id="最終成果物発表会">最終成果物発表会</h1>
<p>2 日目は、お昼におんやど恵を出発し、<a href="https://d.hatena.ne.jp/keyword/%C5%F2%B2%CF%B8%B6%B1%D8">湯河原駅</a>前にある湯河原商工会の会議室を借りてしばらく開発した後、最終成果物を発表しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530113843.jpg" alt="f:id:yamadagenki:20160530113843j:plain"></p>
<p>湯河原<a href="https://d.hatena.ne.jp/keyword/%BE%A6%B9%A9%B2%F1%B4%DB">商工会館</a></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530131109.jpg" alt="f:id:yamadagenki:20160530131109j:plain"></p>
<p><a href="https://d.hatena.ne.jp/keyword/iOS">iOS</a>アプリチームの発表</p>
<h1 id="最後に">最後に</h1>
<p>いつも会社に引きこもってる開発部でしたが、</p>
<p>たまには社外で温泉に浸かりながら開発はいい気分転換になりました。</p>
<p>また、普段一緒に開発しないメンバーと一緒に開発することで、非常に多くの新しい学びがあり、とても良い合宿となりました。</p>
<p>是非、また温泉に行きた。。。</p>
<p>じゃなかった、開発合宿行きたい!</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamadagenki/20160530/20160530114652.jpg" alt="f:id:yamadagenki:20160530114652j:plain"></p>
<p>2016 年 5 月 28 日(土)株式会社メドレー開発本部@おんやど恵</p>medley
- マネーフォワードさん・ Sansan さんとエンジニア勉強会を開催しましたhttps://developer.medley.jp/entry/2016/05/23/101156https://developer.medley.jp/entry/2016/05/23/1011565/11(水)、マネーフォワード・ Sansan と共催で勉強会を開催しました。
120 人もの応募をいただいた本イベント、少しだけ当日の様子をレポートします!
金融、医療、HR、営業管理など、IT が十分に活用されてこなかった、ともすれ...Mon, 23 May 2016 01:11:56 GMT<p>5/11(水)、マネーフォワード・ Sansan と共催で勉強会を開催しました。</p>
<p>120 人もの応募をいただいた本イベント、少しだけ当日の様子をレポートします!</p>
<p><a href="https://connpass.com/event/29430/"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20160520/20160520175219.png" alt="f:id:medley_inc:20160520175219p:plain" title="f:id:medley_inc:20160520175219p:plain"></a></p>
<p>金融、医療、HR、営業管理など、IT が十分に活用されてこなかった、ともすれば「固い」と思われがちな業界を IT で変えていく、という共通点で集まった 3 社が、チーム作りや開発のノウハウをお話しました。</p>
<p>メドレーでは、まずは CTO の平山から弊社のサービスやチームをご紹介。医師とエンジニアが連携した独自の体制についてお話させていただきました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20160520/20160520180104.jpg" alt="f:id:medley_inc:20160520180104j:plain" title="f:id:medley_inc:20160520180104j:plain"></p>
<p>開発部の宮内からは、オンライン通院システム「CLINICS(クリニクス)」の開発の裏側についてお話しました。3 月に開催したイベントでお話した内容を受けて「前回のあらすじ」から始まる斬新なプレゼンに、会場が沸きました(笑)。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20160523/20160523100028.jpg" alt="f:id:medley_inc:20160523100028j:plain" title="f:id:medley_inc:20160523100028j:plain"></p>
<iframe id="talk_frame_341438" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/2469dc49d5ba434fb744dcc2be63d412" width="710" height="463" frameborder="0" allowfullscreen="true"></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/medley/onraintong-yuan-sisutemu-clinics-kurinikusu-falsewu-tai-li-wozhi-eruji-shu-jia">speakerdeck.com</a></cite>
<p>稲本からは、医療介護の求人サイト「ジョブメドレー」の開発についてご紹介しました。企業の採用情報のインフラとして、誰でも使える・信頼性の高いサイトをつくるための開発体制などをお話しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20160523/20160523100247.jpg" alt="f:id:medley_inc:20160523100247j:plain" title="f:id:medley_inc:20160523100247j:plain"></p>
<iframe id="talk_frame_341442" style="border: 0; padding: 0; margin: 0; background: transparent;" src="https://speakerdeck.com/player/8afd44b365574855a13531511b8a3b87" width="710" height="596" frameborder="0" allowfullscreen="true"></iframe>
<cite class="hatena-citation"><a href="https://speakerdeck.com/medley/yi-liao-jie-hu-qiu-ren-saito-ziyobumedore-falsekai-fa-shi-qing">speakerdeck.com</a></cite>
<p>マネーフォワードさんからは、<a href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>を使っている開発の日常についてご紹介があったり、Sansan さんからは<a href="https://d.hatena.ne.jp/keyword/%C6%C1%C5%E7%B8%A9">徳島県</a>の Sansan 神山ラボに山ごもりしての開発合宿の様子など、各社の特色が現れた LT が展開されました。</p>
<p>◆ マネーフォワードさんの LT はこちら</p>
<p><a href="https://speakerdeck.com/yui_knk/rubykomituta-railskomituta-gairuri-chang">“Ruby コミッター” “Rails コミッター”がいる日常 // Speaker Deck</a></p>
<p><a href="https://speakerdeck.com/toru_taniguchi/btobpurodakutochuang-riniokeruti-zhi-toda-qie-nisiteirukoto">BtoB プロダクト創りにおける体制と大切にしていること // Speaker Deck</a></p>
<p>◆Sansan さんの LT はこちら</p>
<p><a href="https://speakerdeck.com/dotrikun/sumatoshan-gomorideapuriwogao-su-kai-fa-suru">スマート山ごもりでアプリを高速開発する // Speaker Deck</a></p>
<p><a href="https://speakerdeck.com/ynakagawa33/task-force-20-percent-time-at-sansan">Task Force ~ 20 percent time@Sansan ~ // Speaker Deck</a></p>
<p><a href="https://speakerdeck.com/sigemoto/purodakutomanezimentofalseusohonto">プロダクトマネジメントのウソ・ホント // Speaker Deck</a></p>
<p>お待ちかねの懇親会!色とりどりの料理にがっつく一同…(笑)</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20160520/20160520182949.jpg" alt="f:id:medley_inc:20160520182949j:plain" title="f:id:medley_inc:20160520182949j:plain"></p>
<p>異業種 3 社が集まったイベントということもあり、参加者の属性もさまざま。参加者から登壇者にむけた LT への質問はもちろん、お互いの開発ノウハウなどを情報交換し、とても盛り上がる時間となりました。</p>
<p>メドレーでは今後も、エンジニアの勉強会・交流会を積極的に開催します。</p>
<p>開催情報は<a href="https://www.facebook.com/medley.jp/">Facebook</a>などでお知らせしておりますので、ぜひチェックしてみてください!</p>medley
- LifeTech - meetup for engineer を開催しましたhttps://developer.medley.jp/entry/2016/04/06/103931https://developer.medley.jp/entry/2016/04/06/103931メドレー広報の阿部です。
医療ヘルスケア分野を IT の力で変えていこうとする企業(メドレー ×FiNC× エムスリー)が集まり企画した「LifeTech- meetup for engineer」の第 1 回を FiNC 本社で開催しまし...Wed, 06 Apr 2016 01:39:31 GMT<p>メドレー広報の阿部です。</p>
<p>医療ヘルスケア分野を IT の力で変えていこうとする企業(メドレー ×FiNC× エムスリー)が集まり企画した「LifeTech- meetup for engineer」の第 1 回を FiNC 本社で開催しました。そのレポートをお届けします!</p>
<p>業界のエンジニア同士でノウハウ共有の機会をつくるとともに、医療ヘルスケアにおけるエンジニアの可能性を広く知ってほしいという 3 社の思いから生まれた本イベント。</p>
<p>50 人定員に対し、応募開始数日で 100 人の申し込みをいただいたほか、実施後のアンケートは「イベントに満足」という回答率が 100%となり、多くの方に医療ヘルスケア ×IT の魅力を伝えられる機会となったのではと、事務局として嬉しく感じています。</p>
<p>当日は各社 3〜4 名の CTO ・エンジニアが LT を行いました。</p>
<p>弊社からは、取締役 CTO の平山がトップバッターとして登壇。日本が抱える医療課題を示しながら、メドレーが世の中に提供していく価値をお伝えしたほか、プロダクトや開発チームの特徴を紹介しました。</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20160404/20160404151828.jpg" alt="f:id:medley_inc:20160404151828j:plain"></p>
<p>その後、開発部の平木より「オンライン通院システム『<a href="https://clinics.medley.life/">CLINICS(クリニクス)</a>』の舞台裏」、石井より「オンライン病気事典『<a href="https://www.medley.life/">MEDLEY</a>』がこだわる Performance & Automation」と題し、各プロダクトの技術選定や開発のこだわりなどをお話しました。</p>
<h2 id="資料はこちらから">資料はこちらから</h2>
<p><a href="https://speakerdeck.com/medley/onraintong-yuan-sisutemu-clinics-kurinikusu-falsewu-tai-li">オンライン通院システム「CLINICS(クリニクス)」の舞台裏 // Speaker Deck</a></p>
<p><a href="https://speakerdeck.com/medley/onrainbing-qi-shi-dian-medley-gakodawaruperformance-and-automation">オンライン病気事典「MEDLEY」がこだわる Performance & Automation // Speaker Deck</a></p>
<h2 id="エンジニア-type-さんでも紹介いただきました">エンジニア type さんでも紹介いただきました!</h2>
<iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="盛り上がる「LifeTech」普及のカギは安全性と使い勝手の融合~FiNC×エムスリー×メドレーの 3 社に聞く - エンジニア type | 転職@type" src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Ftype.jp%2Fet%2Ffeature%2F1015" frameborder="0" scrolling="no"></iframe>
<cite class="hatena-citation"><a href="https://type.jp/et/feature/1015">type.jp</a></cite>
<p>その後はお楽しみ、懇親会!</p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/medley_inc/20160404/20160404153902.jpg" alt="f:id:medley_inc:20160404153902j:plain"></p>
<p>参加者のほぼ全員が懇親会にも出席してくださり、会場はぎゅうぎゅうに…!</p>
<p>登壇者と参加者の交流も弾み、「よい出会いがありました!」と言ってお帰りいただく方もいるなど、さまざまな情報交換をできる有意義な場となったようです。</p>
<p>引き続き「LifeTech - meetup for engineer」は定期開催を予定しています^^</p>
<p>医療ヘルスケアの業界を盛り上げるべく、さまざまな情報交換ができる場にしていきたいと思っておりますので、今回抽選に漏れてしまった方も、興味が湧いた方も、ご参加お待ちしております!</p>
<p>※イベント情報はブログや<a href="https://www.facebook.com/medley.jp/">Facebook</a>、<a href="https://lifetech.connpass.com/">connpass 上のグループ</a>で発信してまいります。ぜひチェックしてみてください!</p>medley