TECH PLAY

株式会社メドレー

株式会社メドレー の技術ブログ

1359

はじめに こんにちは。技術広報・エンジニアの平木です。最近使っている Emacs を Spacemacs から Doom Emacs に変更したところ気分も新たにテキスト活動が捗るようになりました。 さて、2022/05/11 に行なわれた 「 QA Night 〜組織内で QA エンジニアがバリューを発揮し、キャリアアップするには〜 」 という QA エンジニア向けイベントで弊社 QA エンジニア米山がパネリストとして登壇しましたので、その様子をイベントレポートとしてお届けしたいと思います。 QA Night とは こちらのイベントは Showcase Gig さんが主催しているイベントである Geek Gig というエンジニアリングについての定期イベントがありまして、そのシリーズの 1 つとして運営されたものです。 今回は Showcase Gig さんも弊社もテスト自動化ツールの MagicPod を使っているというご縁からお声がけいただきイベント開催の運びとなりました。 イベントについて 今回のイベントは、パネルディスカッションとして 2 社の QA についてそれぞれ語る形式になりました。 モデレーターを Showcase Gig ソフトウェアエンジニアで VP of Technology である 菊池さん が行ない、パネリストとして Showcase Gig QA エンジニア 横田さん と、弊社 QA エンジニア米山とでざっくばらんにイベントが進行していきました。 QA エンジニアの方だけではなく、モデレーターにエンジニアの方が入ったことによりバランスが良いパネルディスカッションになっているという感想でした。 こちらのイベントは connpass で公開後、あれよあれよと 140 人まで申し込みがあり、注目度の高さが伺えました。 当日のイベントの様子は下記からご覧いただけますので、ご興味のある方はぜひ。 パネルディスカッション パネルディスカッションは大まかに以下のようなセクションに分かれて実施されました。 パネリスト・モデレーター自己紹介 各社の開発/QA プロセスの理想と現実 QA エンジニアがいなかったらどうなる? QA エンジニアのキャリア これらのセクションに合わせて、パネリストそれぞれの立場で回答をしていきましたが、途中で視聴されている方の質問などは、モデレーターの菊池さんがほぼリアルタイムで拾っていきながらの進行だったので、イベントの一体感が非常に感じられて面白かったです。 自己紹介 横田さんも米山も現職に至るまでの経歴がかなり似ていました。第三者検証会社で様々な業態の会社を顧客として、様々な経験を積み重ねてから、事業会社での QA エンジニアとして特定のプロダクトの品質を高める仕事をしていらっしゃるという点です。 第三者検証会社で様々なニーズに対応したことが、プロダクトへの貢献に活きていそうだと感じました。 各社の開発/QA プロセスの理想と現実 QA を含めた開発体制については、両社で違いがもちろんありますが大きい違いでいうと、現在のメドレーではプロダクト開発チームの一員として QA エンジニアが所属しています。他方、Showcase Gig さんでは複数の開発チームを横断して QA チームが存在しているという部分でしょうか。 メドレーの現在の開発体制では、チーム内で開発メンバーと密接にやり取りをしながら品質を高めていくというプロセスが取られており、別の開発チームのヘルプなども、もちろんありますがアドバイザー的な感じで、実際にはそのチーム内で品質を高めるように動いています。 一方で Showcase Gig さんでは横断チームとして各プロダクトを俯瞰して見れるような体制にしている印象でした。それぞれの開発チームとは要所でシンクアップすることにより会社全体での QA プロセスを統一しながら各チームに展開できるのが良さそうだなと感じました。 理想と現実について そんな両社ですが、共通する部分として事業領域の難しさがありました。 メドレーは医療領域という難しさ、Showcase Gig さんではオンラインとオフラインの両立をしなければならない部分が特に難しいとのこと。 そこから話は QA の理想と現実に移っていきます。メドレーではプロダクトのより上流部分である仕様策定部分からバグを潰せるようにしていくのを目指しつつも、ガチガチな方法や体制にならないように日々 QA を行なっています。また E2E テストの失敗が非常事態だとメンバーが焦るくらいに成功率を高めていきたいという理想がありますが、どうしてもマニュアルでのテストが必要になってくるプロダクトなので、E2E テストだけに頼らないように試行錯誤しています。 Showcase Gig さんでは品質は担保しつつ、今以上にリリース頻度を上げるためにどうしたら良いかという点を開発チームと協業で改善しようとされているそうです。MagicPod を使った E2E テストだけでリリースまでできるようにするのが理想ではありますが、その前にプロセスの制定など前段階の準備を着々としているそうです。 QA エンジニアがいなかったらどうなる? QA エンジニアのイベントで中々出てこないような設問もあり大変面白かったのですが、それがこの「QA エンジニアがいなかったらどうなる?」という設問でした。 興味深かったのはお二人とも同じような返答だったことです。両社とも「リリースは通常通り行なわれるだろうが、リリース後の不具合対応などが増えるかもしれない」ということでした。またこれも共通して「QA エンジニアがいなくても品質保証がされることが理想」というお話でした。 実際に QA エンジニアはいなくてもきちんと高い品質のプロダクトを世に出していける仕組みや体制が理想というのは、関係者に品質についての意識が根差していけるようにするという意気込みのように聞こえ感銘を受けました。 QA エンジニアのキャリア また普段お二人はどのような意識配分で日々の仕事をされているかというのを円グラフで表わす試みが良かったです。 普段どのような事に時間や意識を割いているかというのが分かると自分の仕事の比率と比べてみちゃいますね。 ここから、皆さんが気になる QA エンジニアのキャリアについてお二人が話をされました。 米山は 15 年の QA 経験を持っていますが、その中でアジャイルやテスト自動化などのトレンドには「自分でもできるかな」と思いながら導入をしていったとのことでした。そうしながら、自分の中のプライオリティはあくまでも事業・プロダクトという部分だったので、ここにコミットができる立ち位置での仕事をずっと続けてきたと言います。 先のキャリアとしては、ポジションに捕われず事業価値を高めるように動いていき、どのような状況のチームでも品質を高めるための推進ができるようにしていきたいということでした。 一方の横田さんも今までの経験を踏まえ QA のポジションを高めていけるような動きをしていきたいということでした。テストなどに興味を持つ人の裾野を広げていきたいという意欲を感じました。個人としては UI/UX なども含めて品質を高めるということができればとのことでした。 お二人とも、「自分のキャリア」に留まらず周囲の人間にも、良い影響を与えていきたいということをおっしゃっているのが非常に印象的でした。 最後に 紹介した以外にも現場に即した QA についてや、品質についての考え方の一端が分かるようなイベントでした。自分はエンジニアという立場で視聴していましたが、そうした人間にも大変に示唆に富むお話が多く、あっという間に 1 時間が経過しました。QA エンジニアの方はもちろん、その他プロダクトを作るということに関わる方はぜひ、アーカイブを視聴してみてください!
アバター
はじめに 皆さん、こんにちは。エンジニア・技術広報の平木です。 以前からご覧になっていただいていた方にはお分かりかと思いますが、3/18 に今ご覧いただいているエンジニア・デザイナーブログを「Developer Portal」という名称に変更して 2017 年以来 5 年ぶりにリニューアルをしました。今回はリニューアルについて、目指すところと若干の技術的な側面をお伝えできればと思います。 ブログトップ新旧比較 旧 新 エンジニア・デザイナーブログリニューアルの背景 まずは、 なぜリニューアルしたか? という背景についてです。弊社のエンジニア・デザイナーブログの変遷と共にお話していきたいと思います。 エンジニア・デザイナーブログの変遷 第 1 期(2016/04 ~ 2017/07) Developer Blog は 2016/04 月に会社公式ブログにエンジニア向けの記事を掲載し始めたころから、しばらくは他の会社情報とともに掲載をしていました。記事内容はエンジニアが登壇したイベント情報や、今も続いている TechLunch(全社横断のエンジニア・デザイナー向け社内勉強会) の発表レポートなどがメインとなっていました。 この頃は開発組織の人数もそこまで多くなかったのですが、メドレーの開発組織を社内の様々な部署の雰囲気と共にお伝えしようという目的がメインでした。まずは、メドレーの開発組織としてのプレゼンスを高め、プロダクトを内製で日々開発をしていることを、外部の皆さんに知ってもらおうという目的が一番大きかったように思います。 このような形で 2017 年中頃までは会社公式ブログでの更新をしていましたが、開発組織が拡大するにつれ、組織として出来ることも増えてきました。それに伴い、これまでのように「開発組織の存在のアピール」や「組織の取り組み」に加えて、純粋に技術的な側面をもっと打ち出していき「医療 x IT」というテーマにメドレーはどのように向きあっているかを知ってもらおうという機運が高まってきました。 第 2 期(2017/07 ~ 2022/03) こうした運びで新しく Medley Developer Blog を 2017/07 月に立ち上げることになりました。会社公式ブログから完全に独立したテックブログということで、目論見通りにそれまで以上に技術的な投稿もできるようになりました。編集方式もこの時から現在までエンジニア・デザイナーが主体となっています。 更新頻度も不定期だったものを月 1~2 回と増やし、コンスタントにメドレーの開発に関する話題を取り扱うようにしました。運営をしていた 5 年の間にいくつかの記事は、おかげさまではてなブックマークなどで 話題 になることもあり、第 1 期よりもさらに皆さんに読んでいただけるようなブログになりました。 この間にメドレーも様々なエンジニア・デザイナーイベントにスポンサードさせていただいたりもして、ブログと合わせて一定のプレゼンスを得ることができてきたのではないかと思っています。特に「医療ヘルスケア業界」という馴染みが無い方にはハードルが高いと思われることが多い業界でのプロダクト開発に、一般的なインターネットテクノロジーを駆使しているという点を、色々な側面からお伝えできるようになったことは大きいと感じています。 現在(2022/03 ~) そんなブログでしたが、開設から 5 年経ち以下のような課題が出てきました。 会社がまだ上場前に作られたデザインだったため、現在のコーポレートサイトなどのトーンとズレが出てきている 会社や組織規模が大きくなり、希薄になりがちな内部で働いている個人にもフォーカスが当てられるようなコンテンツが欲しい 採用的な側面として、メドレーに興味を持った方へ提供できる情報がバラバラになっている 以上の課題を解決するために、ブログのリニューアルをすることになりました。 特に今年から全社的な方針として、メドレーのソフト面での話題にフォーカスしたコンテンツを作っていくということで、改めて公式 note の更新に注力していることもあり、エンジニア・デザイナーブログもこの動きと連動する必要がありました。 以上の理由から、コンテンツとしては従来通りの ブログ記事 、様々なメディアでメンバーが露出している インタビュー記事 、イベントなどで使われた 発表スライド を全て見られるような総合的なサイトにしていくこととなりました。 インタビューはこれまでもメンバーが様々なメディアに出ていたり、スライドも以前から Speaker Deck を更新していたのですが、まとまった形での提供ができていませんでした。 今回のリニューアルで、これらの要素をまとめて皆さんにお届けできるようになったため、「Medley Developer Blog」から「Medley Developer Portal」と名称変更をしました。 これからも、メドレーの開発組織をより外部の皆さんに知ってもらうために、運営ができればと考えていますので、よろしくお願いします。 エンジニア・デザイナーブログリニューアルの技術面について さて、ここまでリニューアルの背景をお伝えしましたが、ここからは今回使った技術面について軽く触れていこうと思います。 使用技術について 旧ブログは「はてなブログ」での運用をしていました。リニューアルにあたって上記の課題を解決するためには、独自にブログを開発したほうがよいと考えました。新ブログは以下の技術を使って開発しました。 Gatsby Emotion TypeScript Netlify Gatsby に決めた理由は大きくは以下でした。 既に弊社内で Gatsby の導入実績があるため、社内のメンバーがいざとなったら触れる ブログ + α のサイトを作るのに十分な柔軟性がある 公式やコミュニティプラグインが充実しており、開発が省力化できる Gatsby は精力的にアップデートが行なわれており、早いサイクルで進化するのも魅力の 1 つでした。 Gatsby 製サイトをどのように公開するかは、悩んだのですが、結果的に事例も多くありデプロイの簡易さや機能の豊富さを考えて Netlify にしました。 はてなブログからの記事移行について 最初から旧ブログでのコンテンツは新ブログに移行することがマストだったので、まずはどのように移行ができるかということを考慮することから始めました。はてなブログは通常のエクスポートだと Movable Type 方式になるので、なんとか Markdown に変換するか…などと考えていました。 しかし、 blogsync というはてなブログの CLI クライアントを通すとローカルに Markdown ファイルとしてブログエントリを同期することが可能だったため、 blogsync で全ての旧ブログコンテンツをローカルに同期(ダウンロード) ローカルに Markdown ファイルとしてダウンロードできたブログコンテンツを Gatsby 製の新ブログにコピー gatsby-source-filesystem でコピーした Markdown ファイルを読み込みブログとして表示 という手順で既存のブログ記事を全て移行することができました。 また、ドメインについては独自ドメインをそのまま新ブログにも移行することとしたため、はてなブログと同じ URL 形式でブログが読み込めるようにルーティングをしました。 苦労した部分 今回の開発で苦労した部分をダイジェストで書いていきます。自分のニーズに合わせて調べてみても、特に深い部分に関して、あまり情報が出てこないというパターンが多かったように思います。 バージョン固有の情報や、プラグインの情報が少なかった 開発当初は Gatsby v4 が出たばかりだったので、何かに困って検索しても公式以外の情報は以前のバージョンで使えないことが多かったです。 またプラグイン関係の情報も調べる内容によっては中々目的の情報が出てこなかったりしました。 gatsby-plugin-image 周りで顕著な印象だったので、画像周りの表示に泣かされることがありました。 他にも Markdown 表示は gatsby-plugin-mdx を使用していますが、従来の gatsby-transformer-remark と情報が混在している印象がありましたので混乱することも。 ページネーションでベストなプラグインが見つからなかった 公式 ガイドを参考にページネーションを自前で作っていたのですが、 GraphQL から持ってきたデータを表示するだけではあまりユーザビリティが良くなかったため、プラグインなど探したのですがこれといったものが見つからなかったです。 公式ガイド参考に作ると延々記事が増えたらページネーションの数も増えていってしまい微妙な感じでした…。自前で制御も考えましたが、最終的には mui/pagination コンポーネントを組み合わせることで対応しました。 OGP 画像自動生成に関して情報が少ない OGP 画像は自動生成にしたいと思いましたが、案外ニーズが無いのか情報が少なかった印象です。最終的に こちら のブログを発見し、 kentaro-m/catchy-image を使って生成するようにしました。 これからやりたいこと 機能の拡充以外だと、今まではできなかった textlint での校正などをして、編集時の負荷軽減などをやっていきたいと考えています。 さいごに 以上、 Developer Portal のリニューアルについて書かせていただきました。 引き続き、情報発信などをしていきメドレー開発組織について、皆さんに知っていただければと思います。 最後になりますが、医療ヘルスケアのプロダクト開発に(このようなブログ製作も)関わっていきたい!という方はぜひ下記よりご応募お待ちしております! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに 皆さん、こんにちは。エンジニア・技術広報の平木です。 以前からご覧になっていただいていた方にはお分かりかと思いますが、3/18 に今ご覧いただいているエンジニア・デザイナーブログを「Developer Portal」という名称に変更して 2017 年以来 5 年ぶりにリニューアルをしました。今回はリニューアルについて、目指すところと若干の技術的な側面をお伝えできればと思います。 ブログトップ新旧比較 旧 新 エンジニア・デザイナーブログリニューアルの背景 まずは、 なぜリニューアルしたか? という背景についてです。弊社のエンジニア・デザイナーブログの変遷と共にお話していきたいと思います。 エンジニア・デザイナーブログの変遷 第 1 期(2016/04 ~ 2017/07) Developer Blog は 2016/04 月に会社公式ブログにエンジニア向けの記事を掲載し始めたころから、しばらくは他の会社情報とともに掲載をしていました。記事内容はエンジニアが登壇したイベント情報や、今も続いている TechLunch(全社横断のエンジニア・デザイナー向け社内勉強会) の発表レポートなどがメインとなっていました。 この頃は開発組織の人数もそこまで多くなかったのですが、メドレーの開発組織を社内の様々な部署の雰囲気と共にお伝えしようという目的がメインでした。まずは、メドレーの開発組織としてのプレゼンスを高め、プロダクトを内製で日々開発をしていることを、外部の皆さんに知ってもらおうという目的が一番大きかったように思います。 このような形で 2017 年中頃までは会社公式ブログでの更新をしていましたが、開発組織が拡大するにつれ、組織として出来ることも増えてきました。それに伴い、これまでのように「開発組織の存在のアピール」や「組織の取り組み」に加えて、純粋に技術的な側面をもっと打ち出していき「医療 x IT」というテーマにメドレーはどのように向きあっているかを知ってもらおうという機運が高まってきました。 第 2 期(2017/07 ~ 2022/03) こうした運びで新しく Medley Developer Blog を 2017/07 月に立ち上げることになりました。会社公式ブログから完全に独立したテックブログということで、目論見通りにそれまで以上に技術的な投稿もできるようになりました。編集方式もこの時から現在までエンジニア・デザイナーが主体となっています。 更新頻度も不定期だったものを月 1~2 回と増やし、コンスタントにメドレーの開発に関する話題を取り扱うようにしました。運営をしていた 5 年の間にいくつかの記事は、おかげさまではてなブックマークなどで 話題 になることもあり、第 1 期よりもさらに皆さんに読んでいただけるようなブログになりました。 この間にメドレーも様々なエンジニア・デザイナーイベントにスポンサードさせていただいたりもして、ブログと合わせて一定のプレゼンスを得ることができてきたのではないかと思っています。特に「医療ヘルスケア業界」という馴染みが無い方にはハードルが高いと思われることが多い業界でのプロダクト開発に、一般的なインターネットテクノロジーを駆使しているという点を、色々な側面からお伝えできるようになったことは大きいと感じています。 現在(2022/03 ~) そんなブログでしたが、開設から 5 年経ち以下のような課題が出てきました。 会社がまだ上場前に作られたデザインだったため、現在のコーポレートサイトなどのトーンとズレが出てきている 会社や組織規模が大きくなり、希薄になりがちな内部で働いている個人にもフォーカスが当てられるようなコンテンツが欲しい 採用的な側面として、メドレーに興味を持った方へ提供できる情報がバラバラになっている 以上の課題を解決するために、ブログのリニューアルをすることになりました。 特に今年から全社的な方針として、メドレーのソフト面での話題にフォーカスしたコンテンツを作っていくということで、改めて公式 note の更新に注力していることもあり、エンジニア・デザイナーブログもこの動きと連動する必要がありました。 以上の理由から、コンテンツとしては従来通りの ブログ記事 、様々なメディアでメンバーが露出している インタビュー記事 、イベントなどで使われた 発表スライド を全て見られるような総合的なサイトにしていくこととなりました。 インタビューはこれまでもメンバーが様々なメディアに出ていたり、スライドも以前から Speaker Deck を更新していたのですが、まとまった形での提供ができていませんでした。 今回のリニューアルで、これらの要素をまとめて皆さんにお届けできるようになったため、「Medley Developer Blog」から「Medley Developer Portal」と名称変更をしました。 これからも、メドレーの開発組織をより外部の皆さんに知ってもらうために、運営ができればと考えていますので、よろしくお願いします。 エンジニア・デザイナーブログリニューアルの技術面について さて、ここまでリニューアルの背景をお伝えしましたが、ここからは今回使った技術面について軽く触れていこうと思います。 使用技術について 旧ブログは「はてなブログ」での運用をしていました。リニューアルにあたって上記の課題を解決するためには、独自にブログを開発したほうがよいと考えました。新ブログは以下の技術を使って開発しました。 Gatsby Emotion TypeScript Netlify Gatsby に決めた理由は大きくは以下でした。 既に弊社内で Gatsby の導入実績があるため、社内のメンバーがいざとなったら触れる ブログ + α のサイトを作るのに十分な柔軟性がある 公式やコミュニティプラグインが充実しており、開発が省力化できる Gatsby は精力的にアップデートが行なわれており、早いサイクルで進化するのも魅力の 1 つでした。 Gatsby 製サイトをどのように公開するかは、悩んだのですが、結果的に事例も多くありデプロイの簡易さや機能の豊富さを考えて Netlify にしました。 はてなブログからの記事移行について 最初から旧ブログでのコンテンツは新ブログに移行することがマストだったので、まずはどのように移行ができるかということを考慮することから始めました。はてなブログは通常のエクスポートだと Movable Type 方式になるので、なんとか Markdown に変換するか…などと考えていました。 しかし、 blogsync というはてなブログの CLI クライアントを通すとローカルに Markdown ファイルとしてブログエントリを同期することが可能だったため、 blogsync で全ての旧ブログコンテンツをローカルに同期(ダウンロード) ローカルに Markdown ファイルとしてダウンロードできたブログコンテンツを Gatsby 製の新ブログにコピー gatsby-source-filesystem でコピーした Markdown ファイルを読み込みブログとして表示 という手順で既存のブログ記事を全て移行することができました。 また、ドメインについては独自ドメインをそのまま新ブログにも移行することとしたため、はてなブログと同じ URL 形式でブログが読み込めるようにルーティングをしました。 苦労した部分 今回の開発で苦労した部分をダイジェストで書いていきます。自分のニーズに合わせて調べてみても、特に深い部分に関して、あまり情報が出てこないというパターンが多かったように思います。 バージョン固有の情報や、プラグインの情報が少なかった 開発当初は Gatsby v4 が出たばかりだったので、何かに困って検索しても公式以外の情報は以前のバージョンで使えないことが多かったです。 またプラグイン関係の情報も調べる内容によっては中々目的の情報が出てこなかったりしました。 gatsby-plugin-image 周りで顕著な印象だったので、画像周りの表示に泣かされることがありました。 他にも Markdown 表示は gatsby-plugin-mdx を使用していますが、従来の gatsby-transformer-remark と情報が混在している印象がありましたので混乱することも。 ページネーションでベストなプラグインが見つからなかった 公式 ガイドを参考にページネーションを自前で作っていたのですが、 GraphQL から持ってきたデータを表示するだけではあまりユーザビリティが良くなかったため、プラグインなど探したのですがこれといったものが見つからなかったです。 公式ガイド参考に作ると延々記事が増えたらページネーションの数も増えていってしまい微妙な感じでした…。自前で制御も考えましたが、最終的には mui/pagination コンポーネントを組み合わせることで対応しました。 OGP 画像自動生成に関して情報が少ない OGP 画像は自動生成にしたいと思いましたが、案外ニーズが無いのか情報が少なかった印象です。最終的に こちら のブログを発見し、 kentaro-m/catchy-image を使って生成するようにしました。 これからやりたいこと 機能の拡充以外だと、今まではできなかった textlint での校正などをして、編集時の負荷軽減などをやっていきたいと考えています。 さいごに 以上、 Developer Portal のリニューアルについて書かせていただきました。 引き続き、情報発信などをしていきメドレー開発組織について、皆さんに知っていただければと思います。 最後になりますが、医療ヘルスケアのプロダクト開発に(このようなブログ製作も)関わっていきたい!という方はぜひ下記よりご応募お待ちしております! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに 皆さん、こんにちは。エンジニア・技術広報の平木です。 以前からご覧になっていただいていた方にはお分かりかと思いますが、3/18 に今ご覧いただいているエンジニア・デザイナーブログを「Developer Portal」という名称に変更して 2017 年以来 5 年ぶりにリニューアルをしました。今回はリニューアルについて、目指すところと若干の技術的な側面をお伝えできればと思います。 ブログトップ新旧比較 旧 新 エンジニア・デザイナーブログリニューアルの背景 まずは、 なぜリニューアルしたか? という背景についてです。弊社のエンジニア・デザイナーブログの変遷と共にお話していきたいと思います。 エンジニア・デザイナーブログの変遷 第 1 期(2016/04 ~ 2017/07) Developer Blog は 2016/04 月に会社公式ブログにエンジニア向けの記事を掲載し始めたころから、しばらくは他の会社情報とともに掲載をしていました。記事内容はエンジニアが登壇したイベント情報や、今も続いている TechLunch(全社横断のエンジニア・デザイナー向け社内勉強会) の発表レポートなどがメインとなっていました。 この頃は開発組織の人数もそこまで多くなかったのですが、メドレーの開発組織を社内の様々な部署の雰囲気と共にお伝えしようという目的がメインでした。まずは、メドレーの開発組織としてのプレゼンスを高め、プロダクトを内製で日々開発をしていることを、外部の皆さんに知ってもらおうという目的が一番大きかったように思います。 このような形で 2017 年中頃までは会社公式ブログでの更新をしていましたが、開発組織が拡大するにつれ、組織として出来ることも増えてきました。それに伴い、これまでのように「開発組織の存在のアピール」や「組織の取り組み」に加えて、純粋に技術的な側面をもっと打ち出していき「医療 x IT」というテーマにメドレーはどのように向きあっているかを知ってもらおうという機運が高まってきました。 第 2 期(2017/07 ~ 2022/03) こうした運びで新しく Medley Developer Blog を 2017/07 月に立ち上げることになりました。会社公式ブログから完全に独立したテックブログということで、目論見通りにそれまで以上に技術的な投稿もできるようになりました。編集方式もこの時から現在までエンジニア・デザイナーが主体となっています。 更新頻度も不定期だったものを月 1~2 回と増やし、コンスタントにメドレーの開発に関する話題を取り扱うようにしました。運営をしていた 5 年の間にいくつかの記事は、おかげさまではてなブックマークなどで 話題 になることもあり、第 1 期よりもさらに皆さんに読んでいただけるようなブログになりました。 この間にメドレーも様々なエンジニア・デザイナーイベントにスポンサードさせていただいたりもして、ブログと合わせて一定のプレゼンスを得ることができてきたのではないかと思っています。特に「医療ヘルスケア業界」という馴染みが無い方にはハードルが高いと思われることが多い業界でのプロダクト開発に、一般的なインターネットテクノロジーを駆使しているという点を、色々な側面からお伝えできるようになったことは大きいと感じています。 現在(2022/03 ~) そんなブログでしたが、開設から 5 年経ち以下のような課題が出てきました。 会社がまだ上場前に作られたデザインだったため、現在のコーポレートサイトなどのトーンとズレが出てきている 会社や組織規模が大きくなり、希薄になりがちな内部で働いている個人にもフォーカスが当てられるようなコンテンツが欲しい 採用的な側面として、メドレーに興味を持った方へ提供できる情報がバラバラになっている 以上の課題を解決するために、ブログのリニューアルをすることになりました。 特に今年から全社的な方針として、メドレーのソフト面での話題にフォーカスしたコンテンツを作っていくということで、改めて公式 note の更新に注力していることもあり、エンジニア・デザイナーブログもこの動きと連動する必要がありました。 以上の理由から、コンテンツとしては従来通りの ブログ記事 、様々なメディアでメンバーが露出している インタビュー記事 、イベントなどで使われた 発表スライド を全て見られるような総合的なサイトにしていくこととなりました。 インタビューはこれまでもメンバーが様々なメディアに出ていたり、スライドも以前から Speaker Deck を更新していたのですが、まとまった形での提供ができていませんでした。 今回のリニューアルで、これらの要素をまとめて皆さんにお届けできるようになったため、「Medley Developer Blog」から「Medley Developer Portal」と名称変更をしました。 これからも、メドレーの開発組織をより外部の皆さんに知ってもらうために、運営ができればと考えていますので、よろしくお願いします。 エンジニア・デザイナーブログリニューアルの技術面について さて、ここまでリニューアルの背景をお伝えしましたが、ここからは今回使った技術面について軽く触れていこうと思います。 使用技術について 旧ブログは「はてなブログ」での運用をしていました。リニューアルにあたって上記の課題を解決するためには、独自にブログを開発したほうがよいと考えました。新ブログは以下の技術を使って開発しました。 Gatsby Emotion TypeScript Netlify Gatsby に決めた理由は大きくは以下でした。 既に弊社内で Gatsby の導入実績があるため、社内のメンバーがいざとなったら触れる ブログ + α のサイトを作るのに十分な柔軟性がある 公式やコミュニティプラグインが充実しており、開発が省力化できる Gatsby は精力的にアップデートが行なわれており、早いサイクルで進化するのも魅力の 1 つでした。 Gatsby 製サイトをどのように公開するかは、悩んだのですが、結果的に事例も多くありデプロイの簡易さや機能の豊富さを考えて Netlify にしました。 はてなブログからの記事移行について 最初から旧ブログでのコンテンツは新ブログに移行することがマストだったので、まずはどのように移行ができるかということを考慮することから始めました。はてなブログは通常のエクスポートだと Movable Type 方式になるので、なんとか Markdown に変換するか…などと考えていました。 しかし、 blogsync というはてなブログの CLI クライアントを通すとローカルに Markdown ファイルとしてブログエントリを同期することが可能だったため、 blogsync で全ての旧ブログコンテンツをローカルに同期(ダウンロード) ローカルに Markdown ファイルとしてダウンロードできたブログコンテンツを Gatsby 製の新ブログにコピー gatsby-source-filesystem でコピーした Markdown ファイルを読み込みブログとして表示 という手順で既存のブログ記事を全て移行することができました。 また、ドメインについては独自ドメインをそのまま新ブログにも移行することとしたため、はてなブログと同じ URL 形式でブログが読み込めるようにルーティングをしました。 苦労した部分 今回の開発で苦労した部分をダイジェストで書いていきます。自分のニーズに合わせて調べてみても、特に深い部分に関して、あまり情報が出てこないというパターンが多かったように思います。 バージョン固有の情報や、プラグインの情報が少なかった 開発当初は Gatsby v4 が出たばかりだったので、何かに困って検索しても公式以外の情報は以前のバージョンで使えないことが多かったです。 またプラグイン関係の情報も調べる内容によっては中々目的の情報が出てこなかったりしました。 gatsby-plugin-image 周りで顕著な印象だったので、画像周りの表示に泣かされることがありました。 他にも Markdown 表示は gatsby-plugin-mdx を使用していますが、従来の gatsby-transformer-remark と情報が混在している印象がありましたので混乱することも。 ページネーションでベストなプラグインが見つからなかった 公式 ガイドを参考にページネーションを自前で作っていたのですが、 GraphQL から持ってきたデータを表示するだけではあまりユーザビリティが良くなかったため、プラグインなど探したのですがこれといったものが見つからなかったです。 公式ガイド参考に作ると延々記事が増えたらページネーションの数も増えていってしまい微妙な感じでした…。自前で制御も考えましたが、最終的には mui/pagination コンポーネントを組み合わせることで対応しました。 OGP 画像自動生成に関して情報が少ない OGP 画像は自動生成にしたいと思いましたが、案外ニーズが無いのか情報が少なかった印象です。最終的に こちら のブログを発見し、 kentaro-m/catchy-image を使って生成するようにしました。 これからやりたいこと 機能の拡充以外だと、今まではできなかった textlint での校正などをして、編集時の負荷軽減などをやっていきたいと考えています。 さいごに 以上、 Developer Portal のリニューアルについて書かせていただきました。 引き続き、情報発信などをしていきメドレー開発組織について、皆さんに知っていただければと思います。 最後になりますが、医療ヘルスケアのプロダクト開発に(このようなブログ製作も)関わっていきたい!という方はぜひ下記よりご応募お待ちしております! https://www.medley.jp/jobs/
アバター
はじめに 皆さん、こんにちは。エンジニア・技術広報の平木です。 以前からご覧になっていただいていた方にはお分かりかと思いますが、3/18 に今ご覧いただいているエンジニア・デザイナーブログを「Developer Portal」という名称に変更して 2017 年以来 5 年ぶりにリニューアルをしました。今回はリニューアルについて、目指すところと若干の技術的な側面をお伝えできればと思います。 ブログトップ新旧比較 旧 新 エンジニア・デザイナーブログリニューアルの背景 まずは、 なぜリニューアルしたか? という背景についてです。弊社のエンジニア・デザイナーブログの変遷と共にお話していきたいと思います。 エンジニア・デザイナーブログの変遷 第 1 期(2016/04 ~ 2017/07) Developer Blog は 2016/04 月に会社公式ブログにエンジニア向けの記事を掲載し始めたころから、しばらくは他の会社情報とともに掲載をしていました。記事内容はエンジニアが登壇したイベント情報や、今も続いている TechLunch(全社横断のエンジニア・デザイナー向け社内勉強会) の発表レポートなどがメインとなっていました。 この頃は開発組織の人数もそこまで多くなかったのですが、メドレーの開発組織を社内の様々な部署の雰囲気と共にお伝えしようという目的がメインでした。まずは、メドレーの開発組織としてのプレゼンスを高め、プロダクトを内製で日々開発をしていることを、外部の皆さんに知ってもらおうという目的が一番大きかったように思います。 このような形で 2017 年中頃までは会社公式ブログでの更新をしていましたが、開発組織が拡大するにつれ、組織として出来ることも増えてきました。それに伴い、これまでのように「開発組織の存在のアピール」や「組織の取り組み」に加えて、純粋に技術的な側面をもっと打ち出していき「医療 x IT」というテーマにメドレーはどのように向きあっているかを知ってもらおうという機運が高まってきました。 第 2 期(2017/07 ~ 2022/03) こうした運びで新しく Medley Developer Blog を 2017/07 月に立ち上げることになりました。会社公式ブログから完全に独立したテックブログということで、目論見通りにそれまで以上に技術的な投稿もできるようになりました。編集方式もこの時から現在までエンジニア・デザイナーが主体となっています。 更新頻度も不定期だったものを月 1~2 回と増やし、コンスタントにメドレーの開発に関する話題を取り扱うようにしました。運営をしていた 5 年の間にいくつかの記事は、おかげさまではてなブックマークなどで 話題 になることもあり、第 1 期よりもさらに皆さんに読んでいただけるようなブログになりました。 この間にメドレーも様々なエンジニア・デザイナーイベントにスポンサードさせていただいたりもして、ブログと合わせて一定のプレゼンスを得ることができてきたのではないかと思っています。特に「医療ヘルスケア業界」という馴染みが無い方にはハードルが高いと思われることが多い業界でのプロダクト開発に、一般的なインターネットテクノロジーを駆使しているという点を、色々な側面からお伝えできるようになったことは大きいと感じています。 現在(2022/03 ~) そんなブログでしたが、開設から 5 年経ち以下のような課題が出てきました。 会社がまだ上場前に作られたデザインだったため、現在のコーポレートサイトなどのトーンとズレが出てきている 会社や組織規模が大きくなり、希薄になりがちな内部で働いている個人にもフォーカスが当てられるようなコンテンツが欲しい 採用的な側面として、メドレーに興味を持った方へ提供できる情報がバラバラになっている 以上の課題を解決するために、ブログのリニューアルをすることになりました。 特に今年から全社的な方針として、メドレーのソフト面での話題にフォーカスしたコンテンツを作っていくということで、改めて公式 note の更新に注力していることもあり、エンジニア・デザイナーブログもこの動きと連動する必要がありました。 以上の理由から、コンテンツとしては従来通りの ブログ記事 、様々なメディアでメンバーが露出している インタビュー記事 、イベントなどで使われた 発表スライド を全て見られるような総合的なサイトにしていくこととなりました。 インタビューはこれまでもメンバーが様々なメディアに出ていたり、スライドも以前から Speaker Deck を更新していたのですが、まとまった形での提供ができていませんでした。 今回のリニューアルで、これらの要素をまとめて皆さんにお届けできるようになったため、「Medley Developer Blog」から「Medley Developer Portal」と名称変更をしました。 これからも、メドレーの開発組織をより外部の皆さんに知ってもらうために、運営ができればと考えていますので、よろしくお願いします。 エンジニア・デザイナーブログリニューアルの技術面について さて、ここまでリニューアルの背景をお伝えしましたが、ここからは今回使った技術面について軽く触れていこうと思います。 使用技術について 旧ブログは「はてなブログ」での運用をしていました。リニューアルにあたって上記の課題を解決するためには、独自にブログを開発したほうがよいと考えました。新ブログは以下の技術を使って開発しました。 Gatsby Emotion TypeScript Netlify Gatsby に決めた理由は大きくは以下でした。 既に弊社内で Gatsby の導入実績があるため、社内のメンバーがいざとなったら触れる ブログ + α のサイトを作るのに十分な柔軟性がある 公式やコミュニティプラグインが充実しており、開発が省力化できる Gatsby は精力的にアップデートが行なわれており、早いサイクルで進化するのも魅力の 1 つでした。 Gatsby 製サイトをどのように公開するかは、悩んだのですが、結果的に事例も多くありデプロイの簡易さや機能の豊富さを考えて Netlify にしました。 はてなブログからの記事移行について 最初から旧ブログでのコンテンツは新ブログに移行することがマストだったので、まずはどのように移行ができるかということを考慮することから始めました。はてなブログは通常のエクスポートだと Movable Type 方式になるので、なんとか Markdown に変換するか…などと考えていました。 しかし、 blogsync というはてなブログの CLI クライアントを通すとローカルに Markdown ファイルとしてブログエントリを同期することが可能だったため、 blogsync で全ての旧ブログコンテンツをローカルに同期(ダウンロード) ローカルに Markdown ファイルとしてダウンロードできたブログコンテンツを Gatsby 製の新ブログにコピー gatsby-source-filesystem でコピーした Markdown ファイルを読み込みブログとして表示 という手順で既存のブログ記事を全て移行することができました。 また、ドメインについては独自ドメインをそのまま新ブログにも移行することとしたため、はてなブログと同じ URL 形式でブログが読み込めるようにルーティングをしました。 苦労した部分 今回の開発で苦労した部分をダイジェストで書いていきます。自分のニーズに合わせて調べてみても、特に深い部分に関して、あまり情報が出てこないというパターンが多かったように思います。 バージョン固有の情報や、プラグインの情報が少なかった 開発当初は Gatsby v4 が出たばかりだったので、何かに困って検索しても公式以外の情報は以前のバージョンで使えないことが多かったです。 またプラグイン関係の情報も調べる内容によっては中々目的の情報が出てこなかったりしました。 gatsby-plugin-image 周りで顕著な印象だったので、画像周りの表示に泣かされることがありました。 他にも Markdown 表示は gatsby-plugin-mdx を使用していますが、従来の gatsby-transformer-remark と情報が混在している印象がありましたので混乱することも。 ページネーションでベストなプラグインが見つからなかった 公式 ガイドを参考にページネーションを自前で作っていたのですが、 GraphQL から持ってきたデータを表示するだけではあまりユーザビリティが良くなかったため、プラグインなど探したのですがこれといったものが見つからなかったです。 公式ガイド参考に作ると延々記事が増えたらページネーションの数も増えていってしまい微妙な感じでした…。自前で制御も考えましたが、最終的には mui/pagination コンポーネントを組み合わせることで対応しました。 OGP 画像自動生成に関して情報が少ない OGP 画像は自動生成にしたいと思いましたが、案外ニーズが無いのか情報が少なかった印象です。最終的に こちら のブログを発見し、 kentaro-m/catchy-image を使って生成するようにしました。 これからやりたいこと 機能の拡充以外だと、今まではできなかった textlint での校正などをして、編集時の負荷軽減などをやっていきたいと考えています。 さいごに 以上、 Developer Portal のリニューアルについて書かせていただきました。 引き続き、情報発信などをしていきメドレー開発組織について、皆さんに知っていただければと思います。 最後になりますが、医療ヘルスケアのプロダクト開発に(このようなブログ製作も)関わっていきたい!という方はぜひ下記よりご応募お待ちしております! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに 皆さん、こんにちは。エンジニア・技術広報の平木です。 以前からご覧になっていただいていた方にはお分かりかと思いますが、3/18 に今ご覧いただいているエンジニア・デザイナーブログを「Developer Portal」という名称に変更して 2017 年以来 5 年ぶりにリニューアルをしました。今回はリニューアルについて、目指すところと若干の技術的な側面をお伝えできればと思います。 ブログトップ新旧比較 旧 新 エンジニア・デザイナーブログリニューアルの背景 まずは、 なぜリニューアルしたか? という背景についてです。弊社のエンジニア・デザイナーブログの変遷と共にお話していきたいと思います。 エンジニア・デザイナーブログの変遷 第 1 期(2016/04 ~ 2017/07) Developer Blog は 2016/04 月に会社公式ブログにエンジニア向けの記事を掲載し始めたころから、しばらくは他の会社情報とともに掲載をしていました。記事内容はエンジニアが登壇したイベント情報や、今も続いている TechLunch(全社横断のエンジニア・デザイナー向け社内勉強会) の発表レポートなどがメインとなっていました。 この頃は開発組織の人数もそこまで多くなかったのですが、メドレーの開発組織を社内の様々な部署の雰囲気と共にお伝えしようという目的がメインでした。まずは、メドレーの開発組織としてのプレゼンスを高め、プロダクトを内製で日々開発をしていることを、外部の皆さんに知ってもらおうという目的が一番大きかったように思います。 このような形で 2017 年中頃までは会社公式ブログでの更新をしていましたが、開発組織が拡大するにつれ、組織として出来ることも増えてきました。それに伴い、これまでのように「開発組織の存在のアピール」や「組織の取り組み」に加えて、純粋に技術的な側面をもっと打ち出していき「医療 x IT」というテーマにメドレーはどのように向きあっているかを知ってもらおうという機運が高まってきました。 第 2 期(2017/07 ~ 2022/03) こうした運びで新しく Medley Developer Blog を 2017/07 月に立ち上げることになりました。会社公式ブログから完全に独立したテックブログということで、目論見通りにそれまで以上に技術的な投稿もできるようになりました。編集方式もこの時から現在までエンジニア・デザイナーが主体となっています。 更新頻度も不定期だったものを月 1~2 回と増やし、コンスタントにメドレーの開発に関する話題を取り扱うようにしました。運営をしていた 5 年の間にいくつかの記事は、おかげさまではてなブックマークなどで 話題 になることもあり、第 1 期よりもさらに皆さんに読んでいただけるようなブログになりました。 この間にメドレーも様々なエンジニア・デザイナーイベントにスポンサードさせていただいたりもして、ブログと合わせて一定のプレゼンスを得ることができてきたのではないかと思っています。特に「医療ヘルスケア業界」という馴染みが無い方にはハードルが高いと思われることが多い業界でのプロダクト開発に、一般的なインターネットテクノロジーを駆使しているという点を、色々な側面からお伝えできるようになったことは大きいと感じています。 現在(2022/03 ~) そんなブログでしたが、開設から 5 年経ち以下のような課題が出てきました。 会社がまだ上場前に作られたデザインだったため、現在のコーポレートサイトなどのトーンとズレが出てきている 会社や組織規模が大きくなり、希薄になりがちな内部で働いている個人にもフォーカスが当てられるようなコンテンツが欲しい 採用的な側面として、メドレーに興味を持った方へ提供できる情報がバラバラになっている 以上の課題を解決するために、ブログのリニューアルをすることになりました。 特に今年から全社的な方針として、メドレーのソフト面での話題にフォーカスしたコンテンツを作っていくということで、改めて公式 note の更新に注力していることもあり、エンジニア・デザイナーブログもこの動きと連動する必要がありました。 以上の理由から、コンテンツとしては従来通りの ブログ記事 、様々なメディアでメンバーが露出している インタビュー記事 、イベントなどで使われた 発表スライド を全て見られるような総合的なサイトにしていくこととなりました。 インタビューはこれまでもメンバーが様々なメディアに出ていたり、スライドも以前から Speaker Deck を更新していたのですが、まとまった形での提供ができていませんでした。 今回のリニューアルで、これらの要素をまとめて皆さんにお届けできるようになったため、「Medley Developer Blog」から「Medley Developer Portal」と名称変更をしました。 これからも、メドレーの開発組織をより外部の皆さんに知ってもらうために、運営ができればと考えていますので、よろしくお願いします。 エンジニア・デザイナーブログリニューアルの技術面について さて、ここまでリニューアルの背景をお伝えしましたが、ここからは今回使った技術面について軽く触れていこうと思います。 使用技術について 旧ブログは「はてなブログ」での運用をしていました。リニューアルにあたって上記の課題を解決するためには、独自にブログを開発したほうがよいと考えました。新ブログは以下の技術を使って開発しました。 Gatsby Emotion TypeScript Netlify Gatsby に決めた理由は大きくは以下でした。 既に弊社内で Gatsby の導入実績があるため、社内のメンバーがいざとなったら触れる ブログ + α のサイトを作るのに十分な柔軟性がある 公式やコミュニティプラグインが充実しており、開発が省力化できる Gatsby は精力的にアップデートが行なわれており、早いサイクルで進化するのも魅力の 1 つでした。 Gatsby 製サイトをどのように公開するかは、悩んだのですが、結果的に事例も多くありデプロイの簡易さや機能の豊富さを考えて Netlify にしました。 はてなブログからの記事移行について 最初から旧ブログでのコンテンツは新ブログに移行することがマストだったので、まずはどのように移行ができるかということを考慮することから始めました。はてなブログは通常のエクスポートだと Movable Type 方式になるので、なんとか Markdown に変換するか…などと考えていました。 しかし、 blogsync というはてなブログの CLI クライアントを通すとローカルに Markdown ファイルとしてブログエントリを同期することが可能だったため、 blogsync で全ての旧ブログコンテンツをローカルに同期(ダウンロード) ローカルに Markdown ファイルとしてダウンロードできたブログコンテンツを Gatsby 製の新ブログにコピー gatsby-source-filesystem でコピーした Markdown ファイルを読み込みブログとして表示 という手順で既存のブログ記事を全て移行することができました。 また、ドメインについては独自ドメインをそのまま新ブログにも移行することとしたため、はてなブログと同じ URL 形式でブログが読み込めるようにルーティングをしました。 苦労した部分 今回の開発で苦労した部分をダイジェストで書いていきます。自分のニーズに合わせて調べてみても、特に深い部分に関して、あまり情報が出てこないというパターンが多かったように思います。 バージョン固有の情報や、プラグインの情報が少なかった 開発当初は Gatsby v4 が出たばかりだったので、何かに困って検索しても公式以外の情報は以前のバージョンで使えないことが多かったです。 またプラグイン関係の情報も調べる内容によっては中々目的の情報が出てこなかったりしました。 gatsby-plugin-image 周りで顕著な印象だったので、画像周りの表示に泣かされることがありました。 他にも Markdown 表示は gatsby-plugin-mdx を使用していますが、従来の gatsby-transformer-remark と情報が混在している印象がありましたので混乱することも。 ページネーションでベストなプラグインが見つからなかった 公式 ガイドを参考にページネーションを自前で作っていたのですが、 GraphQL から持ってきたデータを表示するだけではあまりユーザビリティが良くなかったため、プラグインなど探したのですがこれといったものが見つからなかったです。 公式ガイド参考に作ると延々記事が増えたらページネーションの数も増えていってしまい微妙な感じでした…。自前で制御も考えましたが、最終的には mui/pagination コンポーネントを組み合わせることで対応しました。 OGP 画像自動生成に関して情報が少ない OGP 画像は自動生成にしたいと思いましたが、案外ニーズが無いのか情報が少なかった印象です。最終的に こちら のブログを発見し、 kentaro-m/catchy-image を使って生成するようにしました。 これからやりたいこと 機能の拡充以外だと、今まではできなかった textlint での校正などをして、編集時の負荷軽減などをやっていきたいと考えています。 さいごに 以上、 Developer Portal のリニューアルについて書かせていただきました。 引き続き、情報発信などをしていきメドレー開発組織について、皆さんに知っていただければと思います。 最後になりますが、医療ヘルスケアのプロダクト開発に(このようなブログ製作も)関わっていきたい!という方はぜひ下記よりご応募お待ちしております! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに 皆さん、こんにちは。エンジニア・技術広報の平木です。 以前からご覧になっていただいていた方にはお分かりかと思いますが、3/18 に今ご覧いただいているエンジニア・デザイナーブログを「Developer Portal」という名称に変更して 2017 年以来 5 年ぶりにリニューアルをしました。今回はリニューアルについて、目指すところと若干の技術的な側面をお伝えできればと思います。 ブログトップ新旧比較 旧 新 エンジニア・デザイナーブログリニューアルの背景 まずは、 なぜリニューアルしたか? という背景についてです。弊社のエンジニア・デザイナーブログの変遷と共にお話していきたいと思います。 エンジニア・デザイナーブログの変遷 第 1 期(2016/04 ~ 2017/07) Developer Blog は 2016/04 月に会社公式ブログにエンジニア向けの記事を掲載し始めたころから、しばらくは他の会社情報とともに掲載をしていました。記事内容はエンジニアが登壇したイベント情報や、今も続いている TechLunch(全社横断のエンジニア・デザイナー向け社内勉強会) の発表レポートなどがメインとなっていました。 この頃は開発組織の人数もそこまで多くなかったのですが、メドレーの開発組織を社内の様々な部署の雰囲気と共にお伝えしようという目的がメインでした。まずは、メドレーの開発組織としてのプレゼンスを高め、プロダクトを内製で日々開発をしていることを、外部の皆さんに知ってもらおうという目的が一番大きかったように思います。 このような形で 2017 年中頃までは会社公式ブログでの更新をしていましたが、開発組織が拡大するにつれ、組織として出来ることも増えてきました。それに伴い、これまでのように「開発組織の存在のアピール」や「組織の取り組み」に加えて、純粋に技術的な側面をもっと打ち出していき「医療 x IT」というテーマにメドレーはどのように向きあっているかを知ってもらおうという機運が高まってきました。 第 2 期(2017/07 ~ 2022/03) こうした運びで新しく Medley Developer Blog を 2017/07 月に立ち上げることになりました。会社公式ブログから完全に独立したテックブログということで、目論見通りにそれまで以上に技術的な投稿もできるようになりました。編集方式もこの時から現在までエンジニア・デザイナーが主体となっています。 更新頻度も不定期だったものを月 1~2 回と増やし、コンスタントにメドレーの開発に関する話題を取り扱うようにしました。運営をしていた 5 年の間にいくつかの記事は、おかげさまではてなブックマークなどで 話題 になることもあり、第 1 期よりもさらに皆さんに読んでいただけるようなブログになりました。 この間にメドレーも様々なエンジニア・デザイナーイベントにスポンサードさせていただいたりもして、ブログと合わせて一定のプレゼンスを得ることができてきたのではないかと思っています。特に「医療ヘルスケア業界」という馴染みが無い方にはハードルが高いと思われることが多い業界でのプロダクト開発に、一般的なインターネットテクノロジーを駆使しているという点を、色々な側面からお伝えできるようになったことは大きいと感じています。 現在(2022/03 ~) そんなブログでしたが、開設から 5 年経ち以下のような課題が出てきました。 会社がまだ上場前に作られたデザインだったため、現在のコーポレートサイトなどのトーンとズレが出てきている 会社や組織規模が大きくなり、希薄になりがちな内部で働いている個人にもフォーカスが当てられるようなコンテンツが欲しい 採用的な側面として、メドレーに興味を持った方へ提供できる情報がバラバラになっている 以上の課題を解決するために、ブログのリニューアルをすることになりました。 特に今年から全社的な方針として、メドレーのソフト面での話題にフォーカスしたコンテンツを作っていくということで、改めて公式 note の更新に注力していることもあり、エンジニア・デザイナーブログもこの動きと連動する必要がありました。 以上の理由から、コンテンツとしては従来通りの ブログ記事 、様々なメディアでメンバーが露出している インタビュー記事 、イベントなどで使われた 発表スライド を全て見られるような総合的なサイトにしていくこととなりました。 インタビューはこれまでもメンバーが様々なメディアに出ていたり、スライドも以前から Speaker Deck を更新していたのですが、まとまった形での提供ができていませんでした。 今回のリニューアルで、これらの要素をまとめて皆さんにお届けできるようになったため、「Medley Developer Blog」から「Medley Developer Portal」と名称変更をしました。 これからも、メドレーの開発組織をより外部の皆さんに知ってもらうために、運営ができればと考えていますので、よろしくお願いします。 エンジニア・デザイナーブログリニューアルの技術面について さて、ここまでリニューアルの背景をお伝えしましたが、ここからは今回使った技術面について軽く触れていこうと思います。 使用技術について 旧ブログは「はてなブログ」での運用をしていました。リニューアルにあたって上記の課題を解決するためには、独自にブログを開発したほうがよいと考えました。新ブログは以下の技術を使って開発しました。 Gatsby Emotion TypeScript Netlify Gatsby に決めた理由は大きくは以下でした。 既に弊社内で Gatsby の導入実績があるため、社内のメンバーがいざとなったら触れる ブログ + α のサイトを作るのに十分な柔軟性がある 公式やコミュニティプラグインが充実しており、開発が省力化できる Gatsby は精力的にアップデートが行なわれており、早いサイクルで進化するのも魅力の 1 つでした。 Gatsby 製サイトをどのように公開するかは、悩んだのですが、結果的に事例も多くありデプロイの簡易さや機能の豊富さを考えて Netlify にしました。 はてなブログからの記事移行について 最初から旧ブログでのコンテンツは新ブログに移行することがマストだったので、まずはどのように移行ができるかということを考慮することから始めました。はてなブログは通常のエクスポートだと Movable Type 方式になるので、なんとか Markdown に変換するか…などと考えていました。 しかし、 blogsync というはてなブログの CLI クライアントを通すとローカルに Markdown ファイルとしてブログエントリを同期することが可能だったため、 blogsync で全ての旧ブログコンテンツをローカルに同期(ダウンロード) ローカルに Markdown ファイルとしてダウンロードできたブログコンテンツを Gatsby 製の新ブログにコピー gatsby-source-filesystem でコピーした Markdown ファイルを読み込みブログとして表示 という手順で既存のブログ記事を全て移行することができました。 また、ドメインについては独自ドメインをそのまま新ブログにも移行することとしたため、はてなブログと同じ URL 形式でブログが読み込めるようにルーティングをしました。 苦労した部分 今回の開発で苦労した部分をダイジェストで書いていきます。自分のニーズに合わせて調べてみても、特に深い部分に関して、あまり情報が出てこないというパターンが多かったように思います。 バージョン固有の情報や、プラグインの情報が少なかった 開発当初は Gatsby v4 が出たばかりだったので、何かに困って検索しても公式以外の情報は以前のバージョンで使えないことが多かったです。 またプラグイン関係の情報も調べる内容によっては中々目的の情報が出てこなかったりしました。 gatsby-plugin-image 周りで顕著な印象だったので、画像周りの表示に泣かされることがありました。 他にも Markdown 表示は gatsby-plugin-mdx を使用していますが、従来の gatsby-transformer-remark と情報が混在している印象がありましたので混乱することも。 ページネーションでベストなプラグインが見つからなかった 公式 ガイドを参考にページネーションを自前で作っていたのですが、 GraphQL から持ってきたデータを表示するだけではあまりユーザビリティが良くなかったため、プラグインなど探したのですがこれといったものが見つからなかったです。 公式ガイド参考に作ると延々記事が増えたらページネーションの数も増えていってしまい微妙な感じでした…。自前で制御も考えましたが、最終的には mui/pagination コンポーネントを組み合わせることで対応しました。 OGP 画像自動生成に関して情報が少ない OGP 画像は自動生成にしたいと思いましたが、案外ニーズが無いのか情報が少なかった印象です。最終的に こちら のブログを発見し、 kentaro-m/catchy-image を使って生成するようにしました。 これからやりたいこと 機能の拡充以外だと、今まではできなかった textlint での校正などをして、編集時の負荷軽減などをやっていきたいと考えています。 さいごに 以上、 Developer Portal のリニューアルについて書かせていただきました。 引き続き、情報発信などをしていきメドレー開発組織について、皆さんに知っていただければと思います。 最後になりますが、医療ヘルスケアのプロダクト開発に(このようなブログ製作も)関わっていきたい!という方はぜひ下記よりご応募お待ちしております! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに 皆さん、こんにちは。エンジニア・技術広報の平木です。 以前からご覧になっていただいていた方にはお分かりかと思いますが、3/18 に今ご覧いただいているエンジニア・デザイナーブログを「Developer Portal」という名称に変更して 2017 年以来 5 年ぶりにリニューアルをしました。今回はリニューアルについて、目指すところと若干の技術的な側面をお伝えできればと思います。 ブログトップ新旧比較 旧 新 エンジニア・デザイナーブログリニューアルの背景 まずは、 なぜリニューアルしたか? という背景についてです。弊社のエンジニア・デザイナーブログの変遷と共にお話していきたいと思います。 エンジニア・デザイナーブログの変遷 第 1 期(2016/04 ~ 2017/07) Developer Blog は 2016/04 月に会社公式ブログにエンジニア向けの記事を掲載し始めたころから、しばらくは他の会社情報とともに掲載をしていました。記事内容はエンジニアが登壇したイベント情報や、今も続いている TechLunch(全社横断のエンジニア・デザイナー向け社内勉強会) の発表レポートなどがメインとなっていました。 この頃は開発組織の人数もそこまで多くなかったのですが、メドレーの開発組織を社内の様々な部署の雰囲気と共にお伝えしようという目的がメインでした。まずは、メドレーの開発組織としてのプレゼンスを高め、プロダクトを内製で日々開発をしていることを、外部の皆さんに知ってもらおうという目的が一番大きかったように思います。 このような形で 2017 年中頃までは会社公式ブログでの更新をしていましたが、開発組織が拡大するにつれ、組織として出来ることも増えてきました。それに伴い、これまでのように「開発組織の存在のアピール」や「組織の取り組み」に加えて、純粋に技術的な側面をもっと打ち出していき「医療 x IT」というテーマにメドレーはどのように向きあっているかを知ってもらおうという機運が高まってきました。 第 2 期(2017/07 ~ 2022/03) こうした運びで新しく Medley Developer Blog を 2017/07 月に立ち上げることになりました。会社公式ブログから完全に独立したテックブログということで、目論見通りにそれまで以上に技術的な投稿もできるようになりました。編集方式もこの時から現在までエンジニア・デザイナーが主体となっています。 更新頻度も不定期だったものを月 1~2 回と増やし、コンスタントにメドレーの開発に関する話題を取り扱うようにしました。運営をしていた 5 年の間にいくつかの記事は、おかげさまではてなブックマークなどで 話題 になることもあり、第 1 期よりもさらに皆さんに読んでいただけるようなブログになりました。 この間にメドレーも様々なエンジニア・デザイナーイベントにスポンサードさせていただいたりもして、ブログと合わせて一定のプレゼンスを得ることができてきたのではないかと思っています。特に「医療ヘルスケア業界」という馴染みが無い方にはハードルが高いと思われることが多い業界でのプロダクト開発に、一般的なインターネットテクノロジーを駆使しているという点を、色々な側面からお伝えできるようになったことは大きいと感じています。 現在(2022/03 ~) そんなブログでしたが、開設から 5 年経ち以下のような課題が出てきました。 会社がまだ上場前に作られたデザインだったため、現在のコーポレートサイトなどのトーンとズレが出てきている 会社や組織規模が大きくなり、希薄になりがちな内部で働いている個人にもフォーカスが当てられるようなコンテンツが欲しい 採用的な側面として、メドレーに興味を持った方へ提供できる情報がバラバラになっている 以上の課題を解決するために、ブログのリニューアルをすることになりました。 特に今年から全社的な方針として、メドレーのソフト面での話題にフォーカスしたコンテンツを作っていくということで、改めて公式 note の更新に注力していることもあり、エンジニア・デザイナーブログもこの動きと連動する必要がありました。 以上の理由から、コンテンツとしては従来通りの ブログ記事 、様々なメディアでメンバーが露出している インタビュー記事 、イベントなどで使われた 発表スライド を全て見られるような総合的なサイトにしていくこととなりました。 インタビューはこれまでもメンバーが様々なメディアに出ていたり、スライドも以前から Speaker Deck を更新していたのですが、まとまった形での提供ができていませんでした。 今回のリニューアルで、これらの要素をまとめて皆さんにお届けできるようになったため、「Medley Developer Blog」から「Medley Developer Portal」と名称変更をしました。 これからも、メドレーの開発組織をより外部の皆さんに知ってもらうために、運営ができればと考えていますので、よろしくお願いします。 エンジニア・デザイナーブログリニューアルの技術面について さて、ここまでリニューアルの背景をお伝えしましたが、ここからは今回使った技術面について軽く触れていこうと思います。 使用技術について 旧ブログは「はてなブログ」での運用をしていました。リニューアルにあたって上記の課題を解決するためには、独自にブログを開発したほうがよいと考えました。新ブログは以下の技術を使って開発しました。 Gatsby Emotion TypeScript Netlify Gatsby に決めた理由は大きくは以下でした。 既に弊社内で Gatsby の導入実績があるため、社内のメンバーがいざとなったら触れる ブログ + α のサイトを作るのに十分な柔軟性がある 公式やコミュニティプラグインが充実しており、開発が省力化できる Gatsby は精力的にアップデートが行なわれており、早いサイクルで進化するのも魅力の 1 つでした。 Gatsby 製サイトをどのように公開するかは、悩んだのですが、結果的に事例も多くありデプロイの簡易さや機能の豊富さを考えて Netlify にしました。 はてなブログからの記事移行について 最初から旧ブログでのコンテンツは新ブログに移行することがマストだったので、まずはどのように移行ができるかということを考慮することから始めました。はてなブログは通常のエクスポートだと Movable Type 方式になるので、なんとか Markdown に変換するか…などと考えていました。 しかし、 blogsync というはてなブログの CLI クライアントを通すとローカルに Markdown ファイルとしてブログエントリを同期することが可能だったため、 blogsync で全ての旧ブログコンテンツをローカルに同期(ダウンロード) ローカルに Markdown ファイルとしてダウンロードできたブログコンテンツを Gatsby 製の新ブログにコピー gatsby-source-filesystem でコピーした Markdown ファイルを読み込みブログとして表示 という手順で既存のブログ記事を全て移行することができました。 また、ドメインについては独自ドメインをそのまま新ブログにも移行することとしたため、はてなブログと同じ URL 形式でブログが読み込めるようにルーティングをしました。 苦労した部分 今回の開発で苦労した部分をダイジェストで書いていきます。自分のニーズに合わせて調べてみても、特に深い部分に関して、あまり情報が出てこないというパターンが多かったように思います。 バージョン固有の情報や、プラグインの情報が少なかった 開発当初は Gatsby v4 が出たばかりだったので、何かに困って検索しても公式以外の情報は以前のバージョンで使えないことが多かったです。 またプラグイン関係の情報も調べる内容によっては中々目的の情報が出てこなかったりしました。 gatsby-plugin-image 周りで顕著な印象だったので、画像周りの表示に泣かされることがありました。 他にも Markdown 表示は gatsby-plugin-mdx を使用していますが、従来の gatsby-transformer-remark と情報が混在している印象がありましたので混乱することも。 ページネーションでベストなプラグインが見つからなかった 公式 ガイドを参考にページネーションを自前で作っていたのですが、 GraphQL から持ってきたデータを表示するだけではあまりユーザビリティが良くなかったため、プラグインなど探したのですがこれといったものが見つからなかったです。 公式ガイド参考に作ると延々記事が増えたらページネーションの数も増えていってしまい微妙な感じでした…。自前で制御も考えましたが、最終的には mui/pagination コンポーネントを組み合わせることで対応しました。 OGP 画像自動生成に関して情報が少ない OGP 画像は自動生成にしたいと思いましたが、案外ニーズが無いのか情報が少なかった印象です。最終的に こちら のブログを発見し、 kentaro-m/catchy-image を使って生成するようにしました。 これからやりたいこと 機能の拡充以外だと、今まではできなかった textlint での校正などをして、編集時の負荷軽減などをやっていきたいと考えています。 さいごに 以上、 Developer Portal のリニューアルについて書かせていただきました。 引き続き、情報発信などをしていきメドレー開発組織について、皆さんに知っていただければと思います。 最後になりますが、医療ヘルスケアのプロダクト開発に(このようなブログ製作も)関わっていきたい!という方はぜひ下記よりご応募お待ちしております! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに 皆さん、こんにちは。エンジニア・技術広報の平木です。 以前からご覧になっていただいていた方にはお分かりかと思いますが、3/18 に今ご覧いただいているエンジニア・デザイナーブログを「Developer Portal」という名称に変更して 2017 年以来 5 年ぶりにリニューアルをしました。今回はリニューアルについて、目指すところと若干の技術的な側面をお伝えできればと思います。 ブログトップ新旧比較 旧 新 エンジニア・デザイナーブログリニューアルの背景 まずは、 なぜリニューアルしたか? という背景についてです。弊社のエンジニア・デザイナーブログの変遷と共にお話していきたいと思います。 エンジニア・デザイナーブログの変遷 第 1 期(2016/04 ~ 2017/07) Developer Blog は 2016/04 月に会社公式ブログにエンジニア向けの記事を掲載し始めたころから、しばらくは他の会社情報とともに掲載をしていました。記事内容はエンジニアが登壇したイベント情報や、今も続いている TechLunch(全社横断のエンジニア・デザイナー向け社内勉強会) の発表レポートなどがメインとなっていました。 この頃は開発組織の人数もそこまで多くなかったのですが、メドレーの開発組織を社内の様々な部署の雰囲気と共にお伝えしようという目的がメインでした。まずは、メドレーの開発組織としてのプレゼンスを高め、プロダクトを内製で日々開発をしていることを、外部の皆さんに知ってもらおうという目的が一番大きかったように思います。 このような形で 2017 年中頃までは会社公式ブログでの更新をしていましたが、開発組織が拡大するにつれ、組織として出来ることも増えてきました。それに伴い、これまでのように「開発組織の存在のアピール」や「組織の取り組み」に加えて、純粋に技術的な側面をもっと打ち出していき「医療 x IT」というテーマにメドレーはどのように向きあっているかを知ってもらおうという機運が高まってきました。 第 2 期(2017/07 ~ 2022/03) こうした運びで新しく Medley Developer Blog を 2017/07 月に立ち上げることになりました。会社公式ブログから完全に独立したテックブログということで、目論見通りにそれまで以上に技術的な投稿もできるようになりました。編集方式もこの時から現在までエンジニア・デザイナーが主体となっています。 更新頻度も不定期だったものを月 1~2 回と増やし、コンスタントにメドレーの開発に関する話題を取り扱うようにしました。運営をしていた 5 年の間にいくつかの記事は、おかげさまではてなブックマークなどで 話題 になることもあり、第 1 期よりもさらに皆さんに読んでいただけるようなブログになりました。 この間にメドレーも様々なエンジニア・デザイナーイベントにスポンサードさせていただいたりもして、ブログと合わせて一定のプレゼンスを得ることができてきたのではないかと思っています。特に「医療ヘルスケア業界」という馴染みが無い方にはハードルが高いと思われることが多い業界でのプロダクト開発に、一般的なインターネットテクノロジーを駆使しているという点を、色々な側面からお伝えできるようになったことは大きいと感じています。 現在(2022/03 ~) そんなブログでしたが、開設から 5 年経ち以下のような課題が出てきました。 会社がまだ上場前に作られたデザインだったため、現在のコーポレートサイトなどのトーンとズレが出てきている 会社や組織規模が大きくなり、希薄になりがちな内部で働いている個人にもフォーカスが当てられるようなコンテンツが欲しい 採用的な側面として、メドレーに興味を持った方へ提供できる情報がバラバラになっている 以上の課題を解決するために、ブログのリニューアルをすることになりました。 特に今年から全社的な方針として、メドレーのソフト面での話題にフォーカスしたコンテンツを作っていくということで、改めて公式 note の更新に注力していることもあり、エンジニア・デザイナーブログもこの動きと連動する必要がありました。 以上の理由から、コンテンツとしては従来通りの ブログ記事 、様々なメディアでメンバーが露出している インタビュー記事 、イベントなどで使われた 発表スライド を全て見られるような総合的なサイトにしていくこととなりました。 インタビューはこれまでもメンバーが様々なメディアに出ていたり、スライドも以前から Speaker Deck を更新していたのですが、まとまった形での提供ができていませんでした。 今回のリニューアルで、これらの要素をまとめて皆さんにお届けできるようになったため、「Medley Developer Blog」から「Medley Developer Portal」と名称変更をしました。 これからも、メドレーの開発組織をより外部の皆さんに知ってもらうために、運営ができればと考えていますので、よろしくお願いします。 エンジニア・デザイナーブログリニューアルの技術面について さて、ここまでリニューアルの背景をお伝えしましたが、ここからは今回使った技術面について軽く触れていこうと思います。 使用技術について 旧ブログは「はてなブログ」での運用をしていました。リニューアルにあたって上記の課題を解決するためには、独自にブログを開発したほうがよいと考えました。新ブログは以下の技術を使って開発しました。 Gatsby Emotion TypeScript Netlify Gatsby に決めた理由は大きくは以下でした。 既に弊社内で Gatsby の導入実績があるため、社内のメンバーがいざとなったら触れる ブログ + α のサイトを作るのに十分な柔軟性がある 公式やコミュニティプラグインが充実しており、開発が省力化できる Gatsby は精力的にアップデートが行なわれており、早いサイクルで進化するのも魅力の 1 つでした。 Gatsby 製サイトをどのように公開するかは、悩んだのですが、結果的に事例も多くありデプロイの簡易さや機能の豊富さを考えて Netlify にしました。 はてなブログからの記事移行について 最初から旧ブログでのコンテンツは新ブログに移行することがマストだったので、まずはどのように移行ができるかということを考慮することから始めました。はてなブログは通常のエクスポートだと Movable Type 方式になるので、なんとか Markdown に変換するか…などと考えていました。 しかし、 blogsync というはてなブログの CLI クライアントを通すとローカルに Markdown ファイルとしてブログエントリを同期することが可能だったため、 blogsync で全ての旧ブログコンテンツをローカルに同期(ダウンロード) ローカルに Markdown ファイルとしてダウンロードできたブログコンテンツを Gatsby 製の新ブログにコピー gatsby-source-filesystem でコピーした Markdown ファイルを読み込みブログとして表示 という手順で既存のブログ記事を全て移行することができました。 また、ドメインについては独自ドメインをそのまま新ブログにも移行することとしたため、はてなブログと同じ URL 形式でブログが読み込めるようにルーティングをしました。 苦労した部分 今回の開発で苦労した部分をダイジェストで書いていきます。自分のニーズに合わせて調べてみても、特に深い部分に関して、あまり情報が出てこないというパターンが多かったように思います。 バージョン固有の情報や、プラグインの情報が少なかった 開発当初は Gatsby v4 が出たばかりだったので、何かに困って検索しても公式以外の情報は以前のバージョンで使えないことが多かったです。 またプラグイン関係の情報も調べる内容によっては中々目的の情報が出てこなかったりしました。 gatsby-plugin-image 周りで顕著な印象だったので、画像周りの表示に泣かされることがありました。 他にも Markdown 表示は gatsby-plugin-mdx を使用していますが、従来の gatsby-transformer-remark と情報が混在している印象がありましたので混乱することも。 ページネーションでベストなプラグインが見つからなかった 公式 ガイドを参考にページネーションを自前で作っていたのですが、 GraphQL から持ってきたデータを表示するだけではあまりユーザビリティが良くなかったため、プラグインなど探したのですがこれといったものが見つからなかったです。 公式ガイド参考に作ると延々記事が増えたらページネーションの数も増えていってしまい微妙な感じでした…。自前で制御も考えましたが、最終的には mui/pagination コンポーネントを組み合わせることで対応しました。 OGP 画像自動生成に関して情報が少ない OGP 画像は自動生成にしたいと思いましたが、案外ニーズが無いのか情報が少なかった印象です。最終的に こちら のブログを発見し、 kentaro-m/catchy-image を使って生成するようにしました。 これからやりたいこと 機能の拡充以外だと、今まではできなかった textlint での校正などをして、編集時の負荷軽減などをやっていきたいと考えています。 さいごに 以上、 Developer Portal のリニューアルについて書かせていただきました。 引き続き、情報発信などをしていきメドレー開発組織について、皆さんに知っていただければと思います。 最後になりますが、医療ヘルスケアのプロダクト開発に(このようなブログ製作も)関わっていきたい!という方はぜひ下記よりご応募お待ちしております! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
こんにちは、医療プラットフォーム本部/プロダクト開発室/第一開発グループ所属の世嘉良です。 メドレーには 2018 年の頭に入社しており、今年で 4 年目になります。 当初はサーバーサイドを中心に開発を担当していたのですが、最近は患者エンゲージメントチームという患者様に提供するサービスを開発するチームで主に iOS の仕事を担当することが多いです。 さて去年の 12 月になりますが、 CLINICS アプリ は UI のフルリニューアル を行いました。 今回はリニューアルの裏話 (iOS について) をしていきたいと思います。 これまでの CLINICS アプリについて 本題を書く前に、CLINICS アプリの歴史を紹介します。 ファーストコミットを見てみると、アプリの開発は 2016 年 2 月ごろからスタートし、ファーストリリースが行われたのが 2016 年 5 月でした。 当初、担当していたエンジニアは iOS の経験が豊富な方はおらず、全員で試行錯誤しながら開発を進めていたようです。 しばらくの間は機能の追加などが行われていましたが、 CLINICS カルテ の開発に注力するために大きな開発はストップし、 Pharms との連携が開始される 2020 年 5 月頃まで機能や設計に関する見直しがほとんど行われてきませんでした。 iOS に詳しいエンジニアがいなかったにも関わらず、かなりのスピード感でリリースしていることはさすがの開発力という一言に尽きるのですが、Pharms との連携やお薬手帳といった機能が追加されたり、今後の開発を見据えた際に既存機能や設計の見直しを行いたいと思うようになってきました。 改善したい部分はたくさんあったのですが、対応工数を踏まえ今回のリニューアルでは以下の 2 点の改善に注力することにしました。 Storyboard による View の管理 View とロジックの分離 この 2 点についてそれぞれ詳しく説明します。 1. Storyboard による View の管理 従来の開発では Storyboard を利用して View を作成していました。 Storyboard を使うとレイアウトや画面遷移を簡単に実装することができますが、開発を続けているうちに以下のような問題が目立つようになってきました。 Storyboard が巨大化していき、複数人開発を行う際に支障がでる レイアウトに追加・変更が行われた場合に AutoLayout の再設定に時間がかかる コンポーネント自体にサイズが設定されていることがあり、コンポーネントを再利用できないことがある 課題 こちらは実際にあった Storyboard のキャプチャですが、複数の画面が 1 つの Storyboard 内に詰め込まれた状態となっていました。 Storyboard は Xcode のバージョンにより微妙な差分が発生してしまったり、AutoLayout の調整が必要になる場合があります。 こちらは古い Storyboard の画面を開いた後の差分なのですが、この変更がシステムによって加えられた変更なのか、他人の改修による変更なのかを後から見た時にわかりづらいという問題がありました。 仮に問題が発生した場合は修正を試みるのですが、独自の XML によって表現されているため、iOS の経験が浅い僕にとっては修正が非常に難しかったです。 また、複数人で並行して開発する際にもコンフリクトが起きやすくなってしまい解消に時間がかかるという問題となっていました。 さらに従来の CLINICS アプリは DLS を利用してレイアウトを作成していたのですが、コンポーネントに直接サイズが設定されているものがあり、ある画面では微妙にサイズを調整したい…といった要件に対応できず、せっかくのコンポーネントを再利用できないケースがありました。 解決案 課題に対する解決案は色々と考えられますが、弊社の開発スタイルやエンジニアのスキルを考慮し以下のような方針を立てました。 Storyboard と各画面の実装は 1 : 1 の関係とする 画面遷移の責務も Storyboard から切り離す コンポーネントにアトミックデザインを適用する Storyboard の分割は以下のようなステップで行っていました。 Refactor to Storyboard を使って巨大な Storyboard を分割する SwiftGen を利用し画面生成のコードを自動作成する 2 で生成したものを利用して画面遷移を行う 既存の Segue を削除する まず最初に Xcode 7 から利用可能となった「Refactor to Storyboard」を使って画面を分割するところから始めます。 この機能を利用すると選択した View が新しい Storyboard に切り出され、元の Storyboard には切り出した Storyboard へのリファレンスや Segue 等の接続が保持された状態になります。 次に各画面間の遷移を Segue を使わずに行うようにします。 Segue は便利なのですが、Identifier が単なる文字列であったり、Storyboard を分割してもリファレンスは保持しておく必要がある点が微妙に感じてしまい利用しないことにしました。 Segue を使わずに画面遷移を行う必要があるため、画面生成と画面遷移の方法を自前で実装する必要があります。 今回は画面生成の処理を SwiftGen を利用して自動生成可能にし、VIPER というアーキテクチャの Router を参考にして画面遷移を Storyboard から切り離すことにしました。 ※ SwiftGen の利用方法は SwiftGen#interface-builder を参照ください。 Router の実装は以下の通りです。 実装されたプロトコルを遷移元の VC に継承し、画面遷移のコードを呼び出すだけで画面遷移を行うことができます。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func presentClinic ( clinicId : String ) func pushClinic ( clinicId : String ) } extension ClinicWireframe { func presentClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : true ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) vc. modalPresentationStyle = . pageSheet let nc = UINavigationController ( rootViewController : vc) viewController. present (nc, animated : true ) } func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } 最後にコンポーネントの調整についてですが、CLINICS アプリではアトミックデザインを簡略化し、以下のような基準でコンポーネントを実装しています。 Block: UI の最小単位 (他の PJT にも持ち込めそうなレベルまで分解されたもの) Partial: CLINICS の PJT 固有のコンポーネント Layout: ヘルプ用のモーダルやエラー画面など他の画面から呼び出されることで初めて意味をなす画面など Page: 各 ViewController とそれに紐づく Storyboard この管理方法自体は 弊社の別プロダクトの開発を担当しているエンジニアによって考案されたものです。 弊社ではサーバー・フロント分け隔てなく開発を任されることも多く、厳密なアトミックデザインだとコンポーネントの分類に困ることが多かったためこのような管理方法をとっているそうです。 今回のリニューアルに際して CLINICS アプリでもこれを参考にすることにしました。 これまで DLS として管理されていたコンポーネントは Block で実装しなおし、Width / Height といったサイズの設定を行わないようにしました。 このようにコンポーネントの実装レベルを明確にしたことで、実装に一定の指針が生まれ使い回しの効くコンポーネントを作成しやすくなりました。 2. View とロジックの分離 スピード開発が求められた背景を考えると仕方のないことではあるのですが、従来の CLINICS アプリでは ViewController にロジックが記述されており、俗にいう Fat ViewController の状態になってしまっていました。 Fat ViewController の問題点については既にさまざまな方が取り上げていますが、以下の部分が問題と感じています。 UI とロジックが分離されていないため、テストを書くことが難しい 可読性が低くなりがち ロジックが切り出されていないため似たような実装が点在する場合がある 課題 こちらは実際にあった ViewController の一部です。 このコードに関しては以下の部分が問題と感じていました。 通信処理の呼び出しが ViewController の責務になっていること 通信結果を整形するロジックが ViewController の責務になっていること 画面描画のための State 管理が ViewController の責務になっていること 解決案 UI とロジックを分離するための手法はさまざまなものがありますが、弊社のアプリには以下のような特徴があります。 iOS 以外にも複数のプラットフォームをサポートしているためフロントエンドで保持するデータや複雑なロジック自体は少ない (サーバー側でなるべく担保している) 実装時や QA 時に感じた違和感について細かくディレクターと打ち合わせし、その結果次第では仕様を変更することがある これらを考慮すると、プロジェクトの期間的に初期の導入コストが低く、データフローがシンプルなものに留めたいというように考えがまとまってきました。 これらを考慮し、CLINICS アプリでは MVP というアーキテクチャを採用することにしました。 より詳細には MVP の Passive View 方式を採用しており、以下のような形で実装しています。 View は基本的にすべての入力イベントに対応した Presenter の処理を呼び出す Presenter は入力に応じて通信処理などの外部要因となる処理を呼び出し、結果を整形する プレゼンテーションロジックの結果を描画するように View に指示を出す View は Presenter の指示によってのみ描画処理を行い、自身を起点とした描画処理は行わない Model–view–presenter - Wikipedia 既に Router は導入してるため、複雑な処理フローを実装したい場合は Interactor を導入するだけで VIPER アーキテクチャへと発展させることが簡単にできる点も魅力でした。 CLINICS アプリでの ViewController / Presenter の実装を簡単にまとめたものは以下のようになっています。 final class ClinicViewController : UIViewController { private lazy var presenter: ClinicPresenterInput = { fatalError (“Failed to inject presenter”) }() override func viewDidLoad () { super . viewDidLoad () presenter?. refresh () } func inject ( presenter : ClinicPresenterInput) { self . presenter = presenter } // タップされた際に呼び出す func onTapServiceCell ( _ service : ServiceEntity, isTelemedicine : Bool ) { presenter?. didTapServiceButton (service, isTelemedicine : isTelemedicine) } } // MARK: - ClinicPresenterOutput extension ClinicViewController : ClinicPresenterOutput { func reloadData ( clinic : ClinicEntity?) { clinicViewStore. clinic = clinic } func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) // 予約へ進む } func showErrorAlert ( _ error : Error ) { // エラー表示を行う } } protocol ClinicPresenterInput : AnyObject { func refresh () func didTapServiceButton ( _ service : ServiceEntity, isTelemedicine : Bool ) } protocol ClinicPresenterOutput : AnyObject { func reloadData ( clinic : ClinicEntity?) func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) func showErrorAlert ( _ error : Error ) } final class ClinicPresenter { private let clinicRepository: ClinicRepository private var clinic: ClinicEntity? private var cancellables: Set <AnyCancellable> = . init () init ( view : ClinicPresenterOutput, container : DIContainer) { self . view = view clinicRepository = container. resolve () } private func reloadView () { view?. reloadData ( clinic : clinic) } } // MARK: - PresenterInput extension ClinicPresenter : ClinicPresenterInput { func refresh () { guard let clinicId = clinicId else { return } clinicRepository. getClinic ( clinicId : clinicId) . sink ( receiveCompletion : { [ weak self ] completion in guard let self = self else { return } guard case let . failure (error) = completion else { return } self . view ?. showErrorAlert (error) } }, receiveValue : { [ weak self ] response in guard let self = self else { return } self . clinic = response. data self . reloadView () } ) . store ( in : &cancellables) } func didTapServiceButton ( _ service : ClinicsServiceEntity, isTelemedicine : Bool ) { guard let clinic = clinic else { return } view?. openCreateAppointmentView ( clinic : clinic, service : service, isTelemedicine : isTelemedicine) } } ViewController と Presenter は以下のように Router の内部で DI するようにしています。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func pushClinic ( clinicId : String ) } extension ClinicWireframe { func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc, container : DIContainer ()) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } } これによって、もともと ViewController にあった処理は以下のように分離されました。 また、View と Presenter はインターフェースを介してしかお互いを知らないため、実装の交換が簡単にできるようになりました。 Presenter を交換することで 1 つの View で別々の処理を表現することが可能となったり、View をテストコードを交換することで Presenter の入出力値をテストすることができるようになっています。 まとめ CLINICS アプリは歴史のあるプロジェクトですが、今回のプロジェクトを通して UI だけでなく裏側の実装も刷新しています。 巨大な Storyboard を分解したことでメンテナビリティが向上し、MVP (+ Router) の導入によって View とロジックの交換が簡単になり、テストなどの実装を取り入れやすくなりました。 さいごに ブログの本編には書けなかったのですが、今回リニューアルされた画面に関しては SwiftUI を利用してフルスクラッチで実装していたり、 Asset の管理方法についても大きな見直しを行いました。 またアーキテクチャの選定にあたり、「 iOS アプリ設計パターン入門 」が大変参考になりました。iOS の開発を行う方はぜひ一読してほしいと思います。 約半年ほどかかった大きなプロジェクトでしたが、患者エンゲージメントチームのメンバー全員で取り組み無事にリリースまで漕ぎ着けることができました。まだまだ手探りな部分もありますが、今後も患者さんにとってより安心して使えるサービスとなるように開発を続けていければと思っています。 長くなりましたが、最後までお読みいただきありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
こんにちは、医療プラットフォーム本部/プロダクト開発室/第一開発グループ所属の世嘉良です。 メドレーには 2018 年の頭に入社しており、今年で 4 年目になります。 当初はサーバーサイドを中心に開発を担当していたのですが、最近は患者エンゲージメントチームという患者様に提供するサービスを開発するチームで主に iOS の仕事を担当することが多いです。 さて去年の 12 月になりますが、 CLINICS アプリ は UI のフルリニューアル を行いました。 今回はリニューアルの裏話 (iOS について) をしていきたいと思います。 これまでの CLINICS アプリについて 本題を書く前に、CLINICS アプリの歴史を紹介します。 ファーストコミットを見てみると、アプリの開発は 2016 年 2 月ごろからスタートし、ファーストリリースが行われたのが 2016 年 5 月でした。 当初、担当していたエンジニアは iOS の経験が豊富な方はおらず、全員で試行錯誤しながら開発を進めていたようです。 しばらくの間は機能の追加などが行われていましたが、 CLINICS カルテ の開発に注力するために大きな開発はストップし、 Pharms との連携が開始される 2020 年 5 月頃まで機能や設計に関する見直しがほとんど行われてきませんでした。 iOS に詳しいエンジニアがいなかったにも関わらず、かなりのスピード感でリリースしていることはさすがの開発力という一言に尽きるのですが、Pharms との連携やお薬手帳といった機能が追加されたり、今後の開発を見据えた際に既存機能や設計の見直しを行いたいと思うようになってきました。 改善したい部分はたくさんあったのですが、対応工数を踏まえ今回のリニューアルでは以下の 2 点の改善に注力することにしました。 Storyboard による View の管理 View とロジックの分離 この 2 点についてそれぞれ詳しく説明します。 1. Storyboard による View の管理 従来の開発では Storyboard を利用して View を作成していました。 Storyboard を使うとレイアウトや画面遷移を簡単に実装することができますが、開発を続けているうちに以下のような問題が目立つようになってきました。 Storyboard が巨大化していき、複数人開発を行う際に支障がでる レイアウトに追加・変更が行われた場合に AutoLayout の再設定に時間がかかる コンポーネント自体にサイズが設定されていることがあり、コンポーネントを再利用できないことがある 課題 こちらは実際にあった Storyboard のキャプチャですが、複数の画面が 1 つの Storyboard 内に詰め込まれた状態となっていました。 Storyboard は Xcode のバージョンにより微妙な差分が発生してしまったり、AutoLayout の調整が必要になる場合があります。 こちらは古い Storyboard の画面を開いた後の差分なのですが、この変更がシステムによって加えられた変更なのか、他人の改修による変更なのかを後から見た時にわかりづらいという問題がありました。 仮に問題が発生した場合は修正を試みるのですが、独自の XML によって表現されているため、iOS の経験が浅い僕にとっては修正が非常に難しかったです。 また、複数人で並行して開発する際にもコンフリクトが起きやすくなってしまい解消に時間がかかるという問題となっていました。 さらに従来の CLINICS アプリは DLS を利用してレイアウトを作成していたのですが、コンポーネントに直接サイズが設定されているものがあり、ある画面では微妙にサイズを調整したい…といった要件に対応できず、せっかくのコンポーネントを再利用できないケースがありました。 解決案 課題に対する解決案は色々と考えられますが、弊社の開発スタイルやエンジニアのスキルを考慮し以下のような方針を立てました。 Storyboard と各画面の実装は 1 : 1 の関係とする 画面遷移の責務も Storyboard から切り離す コンポーネントにアトミックデザインを適用する Storyboard の分割は以下のようなステップで行っていました。 Refactor to Storyboard を使って巨大な Storyboard を分割する SwiftGen を利用し画面生成のコードを自動作成する 2 で生成したものを利用して画面遷移を行う 既存の Segue を削除する まず最初に Xcode 7 から利用可能となった「Refactor to Storyboard」を使って画面を分割するところから始めます。 この機能を利用すると選択した View が新しい Storyboard に切り出され、元の Storyboard には切り出した Storyboard へのリファレンスや Segue 等の接続が保持された状態になります。 次に各画面間の遷移を Segue を使わずに行うようにします。 Segue は便利なのですが、Identifier が単なる文字列であったり、Storyboard を分割してもリファレンスは保持しておく必要がある点が微妙に感じてしまい利用しないことにしました。 Segue を使わずに画面遷移を行う必要があるため、画面生成と画面遷移の方法を自前で実装する必要があります。 今回は画面生成の処理を SwiftGen を利用して自動生成可能にし、VIPER というアーキテクチャの Router を参考にして画面遷移を Storyboard から切り離すことにしました。 ※ SwiftGen の利用方法は SwiftGen#interface-builder を参照ください。 Router の実装は以下の通りです。 実装されたプロトコルを遷移元の VC に継承し、画面遷移のコードを呼び出すだけで画面遷移を行うことができます。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func presentClinic ( clinicId : String ) func pushClinic ( clinicId : String ) } extension ClinicWireframe { func presentClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : true ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) vc. modalPresentationStyle = . pageSheet let nc = UINavigationController ( rootViewController : vc) viewController. present (nc, animated : true ) } func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } 最後にコンポーネントの調整についてですが、CLINICS アプリではアトミックデザインを簡略化し、以下のような基準でコンポーネントを実装しています。 Block: UI の最小単位 (他の PJT にも持ち込めそうなレベルまで分解されたもの) Partial: CLINICS の PJT 固有のコンポーネント Layout: ヘルプ用のモーダルやエラー画面など他の画面から呼び出されることで初めて意味をなす画面など Page: 各 ViewController とそれに紐づく Storyboard この管理方法自体は 弊社の別プロダクトの開発を担当しているエンジニアによって考案されたものです。 弊社ではサーバー・フロント分け隔てなく開発を任されることも多く、厳密なアトミックデザインだとコンポーネントの分類に困ることが多かったためこのような管理方法をとっているそうです。 今回のリニューアルに際して CLINICS アプリでもこれを参考にすることにしました。 これまで DLS として管理されていたコンポーネントは Block で実装しなおし、Width / Height といったサイズの設定を行わないようにしました。 このようにコンポーネントの実装レベルを明確にしたことで、実装に一定の指針が生まれ使い回しの効くコンポーネントを作成しやすくなりました。 2. View とロジックの分離 スピード開発が求められた背景を考えると仕方のないことではあるのですが、従来の CLINICS アプリでは ViewController にロジックが記述されており、俗にいう Fat ViewController の状態になってしまっていました。 Fat ViewController の問題点については既にさまざまな方が取り上げていますが、以下の部分が問題と感じています。 UI とロジックが分離されていないため、テストを書くことが難しい 可読性が低くなりがち ロジックが切り出されていないため似たような実装が点在する場合がある 課題 こちらは実際にあった ViewController の一部です。 このコードに関しては以下の部分が問題と感じていました。 通信処理の呼び出しが ViewController の責務になっていること 通信結果を整形するロジックが ViewController の責務になっていること 画面描画のための State 管理が ViewController の責務になっていること 解決案 UI とロジックを分離するための手法はさまざまなものがありますが、弊社のアプリには以下のような特徴があります。 iOS 以外にも複数のプラットフォームをサポートしているためフロントエンドで保持するデータや複雑なロジック自体は少ない (サーバー側でなるべく担保している) 実装時や QA 時に感じた違和感について細かくディレクターと打ち合わせし、その結果次第では仕様を変更することがある これらを考慮すると、プロジェクトの期間的に初期の導入コストが低く、データフローがシンプルなものに留めたいというように考えがまとまってきました。 これらを考慮し、CLINICS アプリでは MVP というアーキテクチャを採用することにしました。 より詳細には MVP の Passive View 方式を採用しており、以下のような形で実装しています。 View は基本的にすべての入力イベントに対応した Presenter の処理を呼び出す Presenter は入力に応じて通信処理などの外部要因となる処理を呼び出し、結果を整形する プレゼンテーションロジックの結果を描画するように View に指示を出す View は Presenter の指示によってのみ描画処理を行い、自身を起点とした描画処理は行わない Model–view–presenter - Wikipedia 既に Router は導入してるため、複雑な処理フローを実装したい場合は Interactor を導入するだけで VIPER アーキテクチャへと発展させることが簡単にできる点も魅力でした。 CLINICS アプリでの ViewController / Presenter の実装を簡単にまとめたものは以下のようになっています。 final class ClinicViewController : UIViewController { private lazy var presenter: ClinicPresenterInput = { fatalError (“Failed to inject presenter”) }() override func viewDidLoad () { super . viewDidLoad () presenter?. refresh () } func inject ( presenter : ClinicPresenterInput) { self . presenter = presenter } // タップされた際に呼び出す func onTapServiceCell ( _ service : ServiceEntity, isTelemedicine : Bool ) { presenter?. didTapServiceButton (service, isTelemedicine : isTelemedicine) } } // MARK: - ClinicPresenterOutput extension ClinicViewController : ClinicPresenterOutput { func reloadData ( clinic : ClinicEntity?) { clinicViewStore. clinic = clinic } func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) // 予約へ進む } func showErrorAlert ( _ error : Error ) { // エラー表示を行う } } protocol ClinicPresenterInput : AnyObject { func refresh () func didTapServiceButton ( _ service : ServiceEntity, isTelemedicine : Bool ) } protocol ClinicPresenterOutput : AnyObject { func reloadData ( clinic : ClinicEntity?) func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) func showErrorAlert ( _ error : Error ) } final class ClinicPresenter { private let clinicRepository: ClinicRepository private var clinic: ClinicEntity? private var cancellables: Set <AnyCancellable> = . init () init ( view : ClinicPresenterOutput, container : DIContainer) { self . view = view clinicRepository = container. resolve () } private func reloadView () { view?. reloadData ( clinic : clinic) } } // MARK: - PresenterInput extension ClinicPresenter : ClinicPresenterInput { func refresh () { guard let clinicId = clinicId else { return } clinicRepository. getClinic ( clinicId : clinicId) . sink ( receiveCompletion : { [ weak self ] completion in guard let self = self else { return } guard case let . failure (error) = completion else { return } self . view ?. showErrorAlert (error) } }, receiveValue : { [ weak self ] response in guard let self = self else { return } self . clinic = response. data self . reloadView () } ) . store ( in : &cancellables) } func didTapServiceButton ( _ service : ClinicsServiceEntity, isTelemedicine : Bool ) { guard let clinic = clinic else { return } view?. openCreateAppointmentView ( clinic : clinic, service : service, isTelemedicine : isTelemedicine) } } ViewController と Presenter は以下のように Router の内部で DI するようにしています。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func pushClinic ( clinicId : String ) } extension ClinicWireframe { func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc, container : DIContainer ()) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } } これによって、もともと ViewController にあった処理は以下のように分離されました。 また、View と Presenter はインターフェースを介してしかお互いを知らないため、実装の交換が簡単にできるようになりました。 Presenter を交換することで 1 つの View で別々の処理を表現することが可能となったり、View をテストコードを交換することで Presenter の入出力値をテストすることができるようになっています。 まとめ CLINICS アプリは歴史のあるプロジェクトですが、今回のプロジェクトを通して UI だけでなく裏側の実装も刷新しています。 巨大な Storyboard を分解したことでメンテナビリティが向上し、MVP (+ Router) の導入によって View とロジックの交換が簡単になり、テストなどの実装を取り入れやすくなりました。 さいごに ブログの本編には書けなかったのですが、今回リニューアルされた画面に関しては SwiftUI を利用してフルスクラッチで実装していたり、 Asset の管理方法についても大きな見直しを行いました。 またアーキテクチャの選定にあたり、「 iOS アプリ設計パターン入門 」が大変参考になりました。iOS の開発を行う方はぜひ一読してほしいと思います。 約半年ほどかかった大きなプロジェクトでしたが、患者エンゲージメントチームのメンバー全員で取り組み無事にリリースまで漕ぎ着けることができました。まだまだ手探りな部分もありますが、今後も患者さんにとってより安心して使えるサービスとなるように開発を続けていければと思っています。 長くなりましたが、最後までお読みいただきありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
こんにちは、医療プラットフォーム本部/プロダクト開発室/第一開発グループ所属の世嘉良です。 メドレーには 2018 年の頭に入社しており、今年で 4 年目になります。 当初はサーバーサイドを中心に開発を担当していたのですが、最近は患者エンゲージメントチームという患者様に提供するサービスを開発するチームで主に iOS の仕事を担当することが多いです。 さて去年の 12 月になりますが、 CLINICS アプリ は UI のフルリニューアル を行いました。 今回はリニューアルの裏話 (iOS について) をしていきたいと思います。 これまでの CLINICS アプリについて 本題を書く前に、CLINICS アプリの歴史を紹介します。 ファーストコミットを見てみると、アプリの開発は 2016 年 2 月ごろからスタートし、ファーストリリースが行われたのが 2016 年 5 月でした。 当初、担当していたエンジニアは iOS の経験が豊富な方はおらず、全員で試行錯誤しながら開発を進めていたようです。 しばらくの間は機能の追加などが行われていましたが、 CLINICS カルテ の開発に注力するために大きな開発はストップし、 Pharms との連携が開始される 2020 年 5 月頃まで機能や設計に関する見直しがほとんど行われてきませんでした。 iOS に詳しいエンジニアがいなかったにも関わらず、かなりのスピード感でリリースしていることはさすがの開発力という一言に尽きるのですが、Pharms との連携やお薬手帳といった機能が追加されたり、今後の開発を見据えた際に既存機能や設計の見直しを行いたいと思うようになってきました。 改善したい部分はたくさんあったのですが、対応工数を踏まえ今回のリニューアルでは以下の 2 点の改善に注力することにしました。 Storyboard による View の管理 View とロジックの分離 この 2 点についてそれぞれ詳しく説明します。 1. Storyboard による View の管理 従来の開発では Storyboard を利用して View を作成していました。 Storyboard を使うとレイアウトや画面遷移を簡単に実装することができますが、開発を続けているうちに以下のような問題が目立つようになってきました。 Storyboard が巨大化していき、複数人開発を行う際に支障がでる レイアウトに追加・変更が行われた場合に AutoLayout の再設定に時間がかかる コンポーネント自体にサイズが設定されていることがあり、コンポーネントを再利用できないことがある 課題 こちらは実際にあった Storyboard のキャプチャですが、複数の画面が 1 つの Storyboard 内に詰め込まれた状態となっていました。 Storyboard は Xcode のバージョンにより微妙な差分が発生してしまったり、AutoLayout の調整が必要になる場合があります。 こちらは古い Storyboard の画面を開いた後の差分なのですが、この変更がシステムによって加えられた変更なのか、他人の改修による変更なのかを後から見た時にわかりづらいという問題がありました。 仮に問題が発生した場合は修正を試みるのですが、独自の XML によって表現されているため、iOS の経験が浅い僕にとっては修正が非常に難しかったです。 また、複数人で並行して開発する際にもコンフリクトが起きやすくなってしまい解消に時間がかかるという問題となっていました。 さらに従来の CLINICS アプリは DLS を利用してレイアウトを作成していたのですが、コンポーネントに直接サイズが設定されているものがあり、ある画面では微妙にサイズを調整したい…といった要件に対応できず、せっかくのコンポーネントを再利用できないケースがありました。 解決案 課題に対する解決案は色々と考えられますが、弊社の開発スタイルやエンジニアのスキルを考慮し以下のような方針を立てました。 Storyboard と各画面の実装は 1 : 1 の関係とする 画面遷移の責務も Storyboard から切り離す コンポーネントにアトミックデザインを適用する Storyboard の分割は以下のようなステップで行っていました。 Refactor to Storyboard を使って巨大な Storyboard を分割する SwiftGen を利用し画面生成のコードを自動作成する 2 で生成したものを利用して画面遷移を行う 既存の Segue を削除する まず最初に Xcode 7 から利用可能となった「Refactor to Storyboard」を使って画面を分割するところから始めます。 この機能を利用すると選択した View が新しい Storyboard に切り出され、元の Storyboard には切り出した Storyboard へのリファレンスや Segue 等の接続が保持された状態になります。 次に各画面間の遷移を Segue を使わずに行うようにします。 Segue は便利なのですが、Identifier が単なる文字列であったり、Storyboard を分割してもリファレンスは保持しておく必要がある点が微妙に感じてしまい利用しないことにしました。 Segue を使わずに画面遷移を行う必要があるため、画面生成と画面遷移の方法を自前で実装する必要があります。 今回は画面生成の処理を SwiftGen を利用して自動生成可能にし、VIPER というアーキテクチャの Router を参考にして画面遷移を Storyboard から切り離すことにしました。 ※ SwiftGen の利用方法は SwiftGen#interface-builder を参照ください。 Router の実装は以下の通りです。 実装されたプロトコルを遷移元の VC に継承し、画面遷移のコードを呼び出すだけで画面遷移を行うことができます。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func presentClinic ( clinicId : String ) func pushClinic ( clinicId : String ) } extension ClinicWireframe { func presentClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : true ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) vc. modalPresentationStyle = . pageSheet let nc = UINavigationController ( rootViewController : vc) viewController. present (nc, animated : true ) } func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } 最後にコンポーネントの調整についてですが、CLINICS アプリではアトミックデザインを簡略化し、以下のような基準でコンポーネントを実装しています。 Block: UI の最小単位 (他の PJT にも持ち込めそうなレベルまで分解されたもの) Partial: CLINICS の PJT 固有のコンポーネント Layout: ヘルプ用のモーダルやエラー画面など他の画面から呼び出されることで初めて意味をなす画面など Page: 各 ViewController とそれに紐づく Storyboard この管理方法自体は 弊社の別プロダクトの開発を担当しているエンジニアによって考案されたものです。 弊社ではサーバー・フロント分け隔てなく開発を任されることも多く、厳密なアトミックデザインだとコンポーネントの分類に困ることが多かったためこのような管理方法をとっているそうです。 今回のリニューアルに際して CLINICS アプリでもこれを参考にすることにしました。 これまで DLS として管理されていたコンポーネントは Block で実装しなおし、Width / Height といったサイズの設定を行わないようにしました。 このようにコンポーネントの実装レベルを明確にしたことで、実装に一定の指針が生まれ使い回しの効くコンポーネントを作成しやすくなりました。 2. View とロジックの分離 スピード開発が求められた背景を考えると仕方のないことではあるのですが、従来の CLINICS アプリでは ViewController にロジックが記述されており、俗にいう Fat ViewController の状態になってしまっていました。 Fat ViewController の問題点については既にさまざまな方が取り上げていますが、以下の部分が問題と感じています。 UI とロジックが分離されていないため、テストを書くことが難しい 可読性が低くなりがち ロジックが切り出されていないため似たような実装が点在する場合がある 課題 こちらは実際にあった ViewController の一部です。 このコードに関しては以下の部分が問題と感じていました。 通信処理の呼び出しが ViewController の責務になっていること 通信結果を整形するロジックが ViewController の責務になっていること 画面描画のための State 管理が ViewController の責務になっていること 解決案 UI とロジックを分離するための手法はさまざまなものがありますが、弊社のアプリには以下のような特徴があります。 iOS 以外にも複数のプラットフォームをサポートしているためフロントエンドで保持するデータや複雑なロジック自体は少ない (サーバー側でなるべく担保している) 実装時や QA 時に感じた違和感について細かくディレクターと打ち合わせし、その結果次第では仕様を変更することがある これらを考慮すると、プロジェクトの期間的に初期の導入コストが低く、データフローがシンプルなものに留めたいというように考えがまとまってきました。 これらを考慮し、CLINICS アプリでは MVP というアーキテクチャを採用することにしました。 より詳細には MVP の Passive View 方式を採用しており、以下のような形で実装しています。 View は基本的にすべての入力イベントに対応した Presenter の処理を呼び出す Presenter は入力に応じて通信処理などの外部要因となる処理を呼び出し、結果を整形する プレゼンテーションロジックの結果を描画するように View に指示を出す View は Presenter の指示によってのみ描画処理を行い、自身を起点とした描画処理は行わない Model–view–presenter - Wikipedia 既に Router は導入してるため、複雑な処理フローを実装したい場合は Interactor を導入するだけで VIPER アーキテクチャへと発展させることが簡単にできる点も魅力でした。 CLINICS アプリでの ViewController / Presenter の実装を簡単にまとめたものは以下のようになっています。 final class ClinicViewController : UIViewController { private lazy var presenter: ClinicPresenterInput = { fatalError (“Failed to inject presenter”) }() override func viewDidLoad () { super . viewDidLoad () presenter?. refresh () } func inject ( presenter : ClinicPresenterInput) { self . presenter = presenter } // タップされた際に呼び出す func onTapServiceCell ( _ service : ServiceEntity, isTelemedicine : Bool ) { presenter?. didTapServiceButton (service, isTelemedicine : isTelemedicine) } } // MARK: - ClinicPresenterOutput extension ClinicViewController : ClinicPresenterOutput { func reloadData ( clinic : ClinicEntity?) { clinicViewStore. clinic = clinic } func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) // 予約へ進む } func showErrorAlert ( _ error : Error ) { // エラー表示を行う } } protocol ClinicPresenterInput : AnyObject { func refresh () func didTapServiceButton ( _ service : ServiceEntity, isTelemedicine : Bool ) } protocol ClinicPresenterOutput : AnyObject { func reloadData ( clinic : ClinicEntity?) func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) func showErrorAlert ( _ error : Error ) } final class ClinicPresenter { private let clinicRepository: ClinicRepository private var clinic: ClinicEntity? private var cancellables: Set <AnyCancellable> = . init () init ( view : ClinicPresenterOutput, container : DIContainer) { self . view = view clinicRepository = container. resolve () } private func reloadView () { view?. reloadData ( clinic : clinic) } } // MARK: - PresenterInput extension ClinicPresenter : ClinicPresenterInput { func refresh () { guard let clinicId = clinicId else { return } clinicRepository. getClinic ( clinicId : clinicId) . sink ( receiveCompletion : { [ weak self ] completion in guard let self = self else { return } guard case let . failure (error) = completion else { return } self . view ?. showErrorAlert (error) } }, receiveValue : { [ weak self ] response in guard let self = self else { return } self . clinic = response. data self . reloadView () } ) . store ( in : &cancellables) } func didTapServiceButton ( _ service : ClinicsServiceEntity, isTelemedicine : Bool ) { guard let clinic = clinic else { return } view?. openCreateAppointmentView ( clinic : clinic, service : service, isTelemedicine : isTelemedicine) } } ViewController と Presenter は以下のように Router の内部で DI するようにしています。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func pushClinic ( clinicId : String ) } extension ClinicWireframe { func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc, container : DIContainer ()) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } } これによって、もともと ViewController にあった処理は以下のように分離されました。 また、View と Presenter はインターフェースを介してしかお互いを知らないため、実装の交換が簡単にできるようになりました。 Presenter を交換することで 1 つの View で別々の処理を表現することが可能となったり、View をテストコードを交換することで Presenter の入出力値をテストすることができるようになっています。 まとめ CLINICS アプリは歴史のあるプロジェクトですが、今回のプロジェクトを通して UI だけでなく裏側の実装も刷新しています。 巨大な Storyboard を分解したことでメンテナビリティが向上し、MVP (+ Router) の導入によって View とロジックの交換が簡単になり、テストなどの実装を取り入れやすくなりました。 さいごに ブログの本編には書けなかったのですが、今回リニューアルされた画面に関しては SwiftUI を利用してフルスクラッチで実装していたり、 Asset の管理方法についても大きな見直しを行いました。 またアーキテクチャの選定にあたり、「 iOS アプリ設計パターン入門 」が大変参考になりました。iOS の開発を行う方はぜひ一読してほしいと思います。 約半年ほどかかった大きなプロジェクトでしたが、患者エンゲージメントチームのメンバー全員で取り組み無事にリリースまで漕ぎ着けることができました。まだまだ手探りな部分もありますが、今後も患者さんにとってより安心して使えるサービスとなるように開発を続けていければと思っています。 長くなりましたが、最後までお読みいただきありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
こんにちは、医療プラットフォーム本部/プロダクト開発室/第一開発グループ所属の世嘉良です。 メドレーには 2018 年の頭に入社しており、今年で 4 年目になります。 当初はサーバーサイドを中心に開発を担当していたのですが、最近は患者エンゲージメントチームという患者様に提供するサービスを開発するチームで主に iOS の仕事を担当することが多いです。 さて去年の 12 月になりますが、 CLINICS アプリ は UI のフルリニューアル を行いました。 今回はリニューアルの裏話 (iOS について) をしていきたいと思います。 これまでの CLINICS アプリについて 本題を書く前に、CLINICS アプリの歴史を紹介します。 ファーストコミットを見てみると、アプリの開発は 2016 年 2 月ごろからスタートし、ファーストリリースが行われたのが 2016 年 5 月でした。 当初、担当していたエンジニアは iOS の経験が豊富な方はおらず、全員で試行錯誤しながら開発を進めていたようです。 しばらくの間は機能の追加などが行われていましたが、 CLINICS カルテ の開発に注力するために大きな開発はストップし、 Pharms との連携が開始される 2020 年 5 月頃まで機能や設計に関する見直しがほとんど行われてきませんでした。 iOS に詳しいエンジニアがいなかったにも関わらず、かなりのスピード感でリリースしていることはさすがの開発力という一言に尽きるのですが、Pharms との連携やお薬手帳といった機能が追加されたり、今後の開発を見据えた際に既存機能や設計の見直しを行いたいと思うようになってきました。 改善したい部分はたくさんあったのですが、対応工数を踏まえ今回のリニューアルでは以下の 2 点の改善に注力することにしました。 Storyboard による View の管理 View とロジックの分離 この 2 点についてそれぞれ詳しく説明します。 1. Storyboard による View の管理 従来の開発では Storyboard を利用して View を作成していました。 Storyboard を使うとレイアウトや画面遷移を簡単に実装することができますが、開発を続けているうちに以下のような問題が目立つようになってきました。 Storyboard が巨大化していき、複数人開発を行う際に支障がでる レイアウトに追加・変更が行われた場合に AutoLayout の再設定に時間がかかる コンポーネント自体にサイズが設定されていることがあり、コンポーネントを再利用できないことがある 課題 こちらは実際にあった Storyboard のキャプチャですが、複数の画面が 1 つの Storyboard 内に詰め込まれた状態となっていました。 Storyboard は Xcode のバージョンにより微妙な差分が発生してしまったり、AutoLayout の調整が必要になる場合があります。 こちらは古い Storyboard の画面を開いた後の差分なのですが、この変更がシステムによって加えられた変更なのか、他人の改修による変更なのかを後から見た時にわかりづらいという問題がありました。 仮に問題が発生した場合は修正を試みるのですが、独自の XML によって表現されているため、iOS の経験が浅い僕にとっては修正が非常に難しかったです。 また、複数人で並行して開発する際にもコンフリクトが起きやすくなってしまい解消に時間がかかるという問題となっていました。 さらに従来の CLINICS アプリは DLS を利用してレイアウトを作成していたのですが、コンポーネントに直接サイズが設定されているものがあり、ある画面では微妙にサイズを調整したい…といった要件に対応できず、せっかくのコンポーネントを再利用できないケースがありました。 解決案 課題に対する解決案は色々と考えられますが、弊社の開発スタイルやエンジニアのスキルを考慮し以下のような方針を立てました。 Storyboard と各画面の実装は 1 : 1 の関係とする 画面遷移の責務も Storyboard から切り離す コンポーネントにアトミックデザインを適用する Storyboard の分割は以下のようなステップで行っていました。 Refactor to Storyboard を使って巨大な Storyboard を分割する SwiftGen を利用し画面生成のコードを自動作成する 2 で生成したものを利用して画面遷移を行う 既存の Segue を削除する まず最初に Xcode 7 から利用可能となった「Refactor to Storyboard」を使って画面を分割するところから始めます。 この機能を利用すると選択した View が新しい Storyboard に切り出され、元の Storyboard には切り出した Storyboard へのリファレンスや Segue 等の接続が保持された状態になります。 次に各画面間の遷移を Segue を使わずに行うようにします。 Segue は便利なのですが、Identifier が単なる文字列であったり、Storyboard を分割してもリファレンスは保持しておく必要がある点が微妙に感じてしまい利用しないことにしました。 Segue を使わずに画面遷移を行う必要があるため、画面生成と画面遷移の方法を自前で実装する必要があります。 今回は画面生成の処理を SwiftGen を利用して自動生成可能にし、VIPER というアーキテクチャの Router を参考にして画面遷移を Storyboard から切り離すことにしました。 ※ SwiftGen の利用方法は SwiftGen#interface-builder を参照ください。 Router の実装は以下の通りです。 実装されたプロトコルを遷移元の VC に継承し、画面遷移のコードを呼び出すだけで画面遷移を行うことができます。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func presentClinic ( clinicId : String ) func pushClinic ( clinicId : String ) } extension ClinicWireframe { func presentClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : true ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) vc. modalPresentationStyle = . pageSheet let nc = UINavigationController ( rootViewController : vc) viewController. present (nc, animated : true ) } func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } 最後にコンポーネントの調整についてですが、CLINICS アプリではアトミックデザインを簡略化し、以下のような基準でコンポーネントを実装しています。 Block: UI の最小単位 (他の PJT にも持ち込めそうなレベルまで分解されたもの) Partial: CLINICS の PJT 固有のコンポーネント Layout: ヘルプ用のモーダルやエラー画面など他の画面から呼び出されることで初めて意味をなす画面など Page: 各 ViewController とそれに紐づく Storyboard この管理方法自体は 弊社の別プロダクトの開発を担当しているエンジニアによって考案されたものです。 弊社ではサーバー・フロント分け隔てなく開発を任されることも多く、厳密なアトミックデザインだとコンポーネントの分類に困ることが多かったためこのような管理方法をとっているそうです。 今回のリニューアルに際して CLINICS アプリでもこれを参考にすることにしました。 これまで DLS として管理されていたコンポーネントは Block で実装しなおし、Width / Height といったサイズの設定を行わないようにしました。 このようにコンポーネントの実装レベルを明確にしたことで、実装に一定の指針が生まれ使い回しの効くコンポーネントを作成しやすくなりました。 2. View とロジックの分離 スピード開発が求められた背景を考えると仕方のないことではあるのですが、従来の CLINICS アプリでは ViewController にロジックが記述されており、俗にいう Fat ViewController の状態になってしまっていました。 Fat ViewController の問題点については既にさまざまな方が取り上げていますが、以下の部分が問題と感じています。 UI とロジックが分離されていないため、テストを書くことが難しい 可読性が低くなりがち ロジックが切り出されていないため似たような実装が点在する場合がある 課題 こちらは実際にあった ViewController の一部です。 このコードに関しては以下の部分が問題と感じていました。 通信処理の呼び出しが ViewController の責務になっていること 通信結果を整形するロジックが ViewController の責務になっていること 画面描画のための State 管理が ViewController の責務になっていること 解決案 UI とロジックを分離するための手法はさまざまなものがありますが、弊社のアプリには以下のような特徴があります。 iOS 以外にも複数のプラットフォームをサポートしているためフロントエンドで保持するデータや複雑なロジック自体は少ない (サーバー側でなるべく担保している) 実装時や QA 時に感じた違和感について細かくディレクターと打ち合わせし、その結果次第では仕様を変更することがある これらを考慮すると、プロジェクトの期間的に初期の導入コストが低く、データフローがシンプルなものに留めたいというように考えがまとまってきました。 これらを考慮し、CLINICS アプリでは MVP というアーキテクチャを採用することにしました。 より詳細には MVP の Passive View 方式を採用しており、以下のような形で実装しています。 View は基本的にすべての入力イベントに対応した Presenter の処理を呼び出す Presenter は入力に応じて通信処理などの外部要因となる処理を呼び出し、結果を整形する プレゼンテーションロジックの結果を描画するように View に指示を出す View は Presenter の指示によってのみ描画処理を行い、自身を起点とした描画処理は行わない Model–view–presenter - Wikipedia 既に Router は導入してるため、複雑な処理フローを実装したい場合は Interactor を導入するだけで VIPER アーキテクチャへと発展させることが簡単にできる点も魅力でした。 CLINICS アプリでの ViewController / Presenter の実装を簡単にまとめたものは以下のようになっています。 final class ClinicViewController : UIViewController { private lazy var presenter: ClinicPresenterInput = { fatalError (“Failed to inject presenter”) }() override func viewDidLoad () { super . viewDidLoad () presenter?. refresh () } func inject ( presenter : ClinicPresenterInput) { self . presenter = presenter } // タップされた際に呼び出す func onTapServiceCell ( _ service : ServiceEntity, isTelemedicine : Bool ) { presenter?. didTapServiceButton (service, isTelemedicine : isTelemedicine) } } // MARK: - ClinicPresenterOutput extension ClinicViewController : ClinicPresenterOutput { func reloadData ( clinic : ClinicEntity?) { clinicViewStore. clinic = clinic } func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) // 予約へ進む } func showErrorAlert ( _ error : Error ) { // エラー表示を行う } } protocol ClinicPresenterInput : AnyObject { func refresh () func didTapServiceButton ( _ service : ServiceEntity, isTelemedicine : Bool ) } protocol ClinicPresenterOutput : AnyObject { func reloadData ( clinic : ClinicEntity?) func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) func showErrorAlert ( _ error : Error ) } final class ClinicPresenter { private let clinicRepository: ClinicRepository private var clinic: ClinicEntity? private var cancellables: Set <AnyCancellable> = . init () init ( view : ClinicPresenterOutput, container : DIContainer) { self . view = view clinicRepository = container. resolve () } private func reloadView () { view?. reloadData ( clinic : clinic) } } // MARK: - PresenterInput extension ClinicPresenter : ClinicPresenterInput { func refresh () { guard let clinicId = clinicId else { return } clinicRepository. getClinic ( clinicId : clinicId) . sink ( receiveCompletion : { [ weak self ] completion in guard let self = self else { return } guard case let . failure (error) = completion else { return } self . view ?. showErrorAlert (error) } }, receiveValue : { [ weak self ] response in guard let self = self else { return } self . clinic = response. data self . reloadView () } ) . store ( in : &cancellables) } func didTapServiceButton ( _ service : ClinicsServiceEntity, isTelemedicine : Bool ) { guard let clinic = clinic else { return } view?. openCreateAppointmentView ( clinic : clinic, service : service, isTelemedicine : isTelemedicine) } } ViewController と Presenter は以下のように Router の内部で DI するようにしています。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func pushClinic ( clinicId : String ) } extension ClinicWireframe { func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc, container : DIContainer ()) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } } これによって、もともと ViewController にあった処理は以下のように分離されました。 また、View と Presenter はインターフェースを介してしかお互いを知らないため、実装の交換が簡単にできるようになりました。 Presenter を交換することで 1 つの View で別々の処理を表現することが可能となったり、View をテストコードを交換することで Presenter の入出力値をテストすることができるようになっています。 まとめ CLINICS アプリは歴史のあるプロジェクトですが、今回のプロジェクトを通して UI だけでなく裏側の実装も刷新しています。 巨大な Storyboard を分解したことでメンテナビリティが向上し、MVP (+ Router) の導入によって View とロジックの交換が簡単になり、テストなどの実装を取り入れやすくなりました。 さいごに ブログの本編には書けなかったのですが、今回リニューアルされた画面に関しては SwiftUI を利用してフルスクラッチで実装していたり、 Asset の管理方法についても大きな見直しを行いました。 またアーキテクチャの選定にあたり、「 iOS アプリ設計パターン入門 」が大変参考になりました。iOS の開発を行う方はぜひ一読してほしいと思います。 約半年ほどかかった大きなプロジェクトでしたが、患者エンゲージメントチームのメンバー全員で取り組み無事にリリースまで漕ぎ着けることができました。まだまだ手探りな部分もありますが、今後も患者さんにとってより安心して使えるサービスとなるように開発を続けていければと思っています。 長くなりましたが、最後までお読みいただきありがとうございました。 https://www.medley.jp/jobs/
アバター
こんにちは、医療プラットフォーム本部/プロダクト開発室/第一開発グループ所属の世嘉良です。 メドレーには 2018 年の頭に入社しており、今年で 4 年目になります。 当初はサーバーサイドを中心に開発を担当していたのですが、最近は患者エンゲージメントチームという患者様に提供するサービスを開発するチームで主に iOS の仕事を担当することが多いです。 さて去年の 12 月になりますが、 CLINICS アプリ は UI のフルリニューアル を行いました。 今回はリニューアルの裏話 (iOS について) をしていきたいと思います。 これまでの CLINICS アプリについて 本題を書く前に、CLINICS アプリの歴史を紹介します。 ファーストコミットを見てみると、アプリの開発は 2016 年 2 月ごろからスタートし、ファーストリリースが行われたのが 2016 年 5 月でした。 当初、担当していたエンジニアは iOS の経験が豊富な方はおらず、全員で試行錯誤しながら開発を進めていたようです。 しばらくの間は機能の追加などが行われていましたが、 CLINICS カルテ の開発に注力するために大きな開発はストップし、 Pharms との連携が開始される 2020 年 5 月頃まで機能や設計に関する見直しがほとんど行われてきませんでした。 iOS に詳しいエンジニアがいなかったにも関わらず、かなりのスピード感でリリースしていることはさすがの開発力という一言に尽きるのですが、Pharms との連携やお薬手帳といった機能が追加されたり、今後の開発を見据えた際に既存機能や設計の見直しを行いたいと思うようになってきました。 改善したい部分はたくさんあったのですが、対応工数を踏まえ今回のリニューアルでは以下の 2 点の改善に注力することにしました。 Storyboard による View の管理 View とロジックの分離 この 2 点についてそれぞれ詳しく説明します。 1. Storyboard による View の管理 従来の開発では Storyboard を利用して View を作成していました。 Storyboard を使うとレイアウトや画面遷移を簡単に実装することができますが、開発を続けているうちに以下のような問題が目立つようになってきました。 Storyboard が巨大化していき、複数人開発を行う際に支障がでる レイアウトに追加・変更が行われた場合に AutoLayout の再設定に時間がかかる コンポーネント自体にサイズが設定されていることがあり、コンポーネントを再利用できないことがある 課題 こちらは実際にあった Storyboard のキャプチャですが、複数の画面が 1 つの Storyboard 内に詰め込まれた状態となっていました。 Storyboard は Xcode のバージョンにより微妙な差分が発生してしまったり、AutoLayout の調整が必要になる場合があります。 こちらは古い Storyboard の画面を開いた後の差分なのですが、この変更がシステムによって加えられた変更なのか、他人の改修による変更なのかを後から見た時にわかりづらいという問題がありました。 仮に問題が発生した場合は修正を試みるのですが、独自の XML によって表現されているため、iOS の経験が浅い僕にとっては修正が非常に難しかったです。 また、複数人で並行して開発する際にもコンフリクトが起きやすくなってしまい解消に時間がかかるという問題となっていました。 さらに従来の CLINICS アプリは DLS を利用してレイアウトを作成していたのですが、コンポーネントに直接サイズが設定されているものがあり、ある画面では微妙にサイズを調整したい…といった要件に対応できず、せっかくのコンポーネントを再利用できないケースがありました。 解決案 課題に対する解決案は色々と考えられますが、弊社の開発スタイルやエンジニアのスキルを考慮し以下のような方針を立てました。 Storyboard と各画面の実装は 1 : 1 の関係とする 画面遷移の責務も Storyboard から切り離す コンポーネントにアトミックデザインを適用する Storyboard の分割は以下のようなステップで行っていました。 Refactor to Storyboard を使って巨大な Storyboard を分割する SwiftGen を利用し画面生成のコードを自動作成する 2 で生成したものを利用して画面遷移を行う 既存の Segue を削除する まず最初に Xcode 7 から利用可能となった「Refactor to Storyboard」を使って画面を分割するところから始めます。 この機能を利用すると選択した View が新しい Storyboard に切り出され、元の Storyboard には切り出した Storyboard へのリファレンスや Segue 等の接続が保持された状態になります。 次に各画面間の遷移を Segue を使わずに行うようにします。 Segue は便利なのですが、Identifier が単なる文字列であったり、Storyboard を分割してもリファレンスは保持しておく必要がある点が微妙に感じてしまい利用しないことにしました。 Segue を使わずに画面遷移を行う必要があるため、画面生成と画面遷移の方法を自前で実装する必要があります。 今回は画面生成の処理を SwiftGen を利用して自動生成可能にし、VIPER というアーキテクチャの Router を参考にして画面遷移を Storyboard から切り離すことにしました。 ※ SwiftGen の利用方法は SwiftGen#interface-builder を参照ください。 Router の実装は以下の通りです。 実装されたプロトコルを遷移元の VC に継承し、画面遷移のコードを呼び出すだけで画面遷移を行うことができます。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func presentClinic ( clinicId : String ) func pushClinic ( clinicId : String ) } extension ClinicWireframe { func presentClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : true ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) vc. modalPresentationStyle = . pageSheet let nc = UINavigationController ( rootViewController : vc) viewController. present (nc, animated : true ) } func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } 最後にコンポーネントの調整についてですが、CLINICS アプリではアトミックデザインを簡略化し、以下のような基準でコンポーネントを実装しています。 Block: UI の最小単位 (他の PJT にも持ち込めそうなレベルまで分解されたもの) Partial: CLINICS の PJT 固有のコンポーネント Layout: ヘルプ用のモーダルやエラー画面など他の画面から呼び出されることで初めて意味をなす画面など Page: 各 ViewController とそれに紐づく Storyboard この管理方法自体は 弊社の別プロダクトの開発を担当しているエンジニアによって考案されたものです。 弊社ではサーバー・フロント分け隔てなく開発を任されることも多く、厳密なアトミックデザインだとコンポーネントの分類に困ることが多かったためこのような管理方法をとっているそうです。 今回のリニューアルに際して CLINICS アプリでもこれを参考にすることにしました。 これまで DLS として管理されていたコンポーネントは Block で実装しなおし、Width / Height といったサイズの設定を行わないようにしました。 このようにコンポーネントの実装レベルを明確にしたことで、実装に一定の指針が生まれ使い回しの効くコンポーネントを作成しやすくなりました。 2. View とロジックの分離 スピード開発が求められた背景を考えると仕方のないことではあるのですが、従来の CLINICS アプリでは ViewController にロジックが記述されており、俗にいう Fat ViewController の状態になってしまっていました。 Fat ViewController の問題点については既にさまざまな方が取り上げていますが、以下の部分が問題と感じています。 UI とロジックが分離されていないため、テストを書くことが難しい 可読性が低くなりがち ロジックが切り出されていないため似たような実装が点在する場合がある 課題 こちらは実際にあった ViewController の一部です。 このコードに関しては以下の部分が問題と感じていました。 通信処理の呼び出しが ViewController の責務になっていること 通信結果を整形するロジックが ViewController の責務になっていること 画面描画のための State 管理が ViewController の責務になっていること 解決案 UI とロジックを分離するための手法はさまざまなものがありますが、弊社のアプリには以下のような特徴があります。 iOS 以外にも複数のプラットフォームをサポートしているためフロントエンドで保持するデータや複雑なロジック自体は少ない (サーバー側でなるべく担保している) 実装時や QA 時に感じた違和感について細かくディレクターと打ち合わせし、その結果次第では仕様を変更することがある これらを考慮すると、プロジェクトの期間的に初期の導入コストが低く、データフローがシンプルなものに留めたいというように考えがまとまってきました。 これらを考慮し、CLINICS アプリでは MVP というアーキテクチャを採用することにしました。 より詳細には MVP の Passive View 方式を採用しており、以下のような形で実装しています。 View は基本的にすべての入力イベントに対応した Presenter の処理を呼び出す Presenter は入力に応じて通信処理などの外部要因となる処理を呼び出し、結果を整形する プレゼンテーションロジックの結果を描画するように View に指示を出す View は Presenter の指示によってのみ描画処理を行い、自身を起点とした描画処理は行わない Model–view–presenter - Wikipedia 既に Router は導入してるため、複雑な処理フローを実装したい場合は Interactor を導入するだけで VIPER アーキテクチャへと発展させることが簡単にできる点も魅力でした。 CLINICS アプリでの ViewController / Presenter の実装を簡単にまとめたものは以下のようになっています。 final class ClinicViewController : UIViewController { private lazy var presenter: ClinicPresenterInput = { fatalError (“Failed to inject presenter”) }() override func viewDidLoad () { super . viewDidLoad () presenter?. refresh () } func inject ( presenter : ClinicPresenterInput) { self . presenter = presenter } // タップされた際に呼び出す func onTapServiceCell ( _ service : ServiceEntity, isTelemedicine : Bool ) { presenter?. didTapServiceButton (service, isTelemedicine : isTelemedicine) } } // MARK: - ClinicPresenterOutput extension ClinicViewController : ClinicPresenterOutput { func reloadData ( clinic : ClinicEntity?) { clinicViewStore. clinic = clinic } func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) // 予約へ進む } func showErrorAlert ( _ error : Error ) { // エラー表示を行う } } protocol ClinicPresenterInput : AnyObject { func refresh () func didTapServiceButton ( _ service : ServiceEntity, isTelemedicine : Bool ) } protocol ClinicPresenterOutput : AnyObject { func reloadData ( clinic : ClinicEntity?) func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) func showErrorAlert ( _ error : Error ) } final class ClinicPresenter { private let clinicRepository: ClinicRepository private var clinic: ClinicEntity? private var cancellables: Set <AnyCancellable> = . init () init ( view : ClinicPresenterOutput, container : DIContainer) { self . view = view clinicRepository = container. resolve () } private func reloadView () { view?. reloadData ( clinic : clinic) } } // MARK: - PresenterInput extension ClinicPresenter : ClinicPresenterInput { func refresh () { guard let clinicId = clinicId else { return } clinicRepository. getClinic ( clinicId : clinicId) . sink ( receiveCompletion : { [ weak self ] completion in guard let self = self else { return } guard case let . failure (error) = completion else { return } self . view ?. showErrorAlert (error) } }, receiveValue : { [ weak self ] response in guard let self = self else { return } self . clinic = response. data self . reloadView () } ) . store ( in : &cancellables) } func didTapServiceButton ( _ service : ClinicsServiceEntity, isTelemedicine : Bool ) { guard let clinic = clinic else { return } view?. openCreateAppointmentView ( clinic : clinic, service : service, isTelemedicine : isTelemedicine) } } ViewController と Presenter は以下のように Router の内部で DI するようにしています。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func pushClinic ( clinicId : String ) } extension ClinicWireframe { func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc, container : DIContainer ()) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } } これによって、もともと ViewController にあった処理は以下のように分離されました。 また、View と Presenter はインターフェースを介してしかお互いを知らないため、実装の交換が簡単にできるようになりました。 Presenter を交換することで 1 つの View で別々の処理を表現することが可能となったり、View をテストコードを交換することで Presenter の入出力値をテストすることができるようになっています。 まとめ CLINICS アプリは歴史のあるプロジェクトですが、今回のプロジェクトを通して UI だけでなく裏側の実装も刷新しています。 巨大な Storyboard を分解したことでメンテナビリティが向上し、MVP (+ Router) の導入によって View とロジックの交換が簡単になり、テストなどの実装を取り入れやすくなりました。 さいごに ブログの本編には書けなかったのですが、今回リニューアルされた画面に関しては SwiftUI を利用してフルスクラッチで実装していたり、 Asset の管理方法についても大きな見直しを行いました。 またアーキテクチャの選定にあたり、「 iOS アプリ設計パターン入門 」が大変参考になりました。iOS の開発を行う方はぜひ一読してほしいと思います。 約半年ほどかかった大きなプロジェクトでしたが、患者エンゲージメントチームのメンバー全員で取り組み無事にリリースまで漕ぎ着けることができました。まだまだ手探りな部分もありますが、今後も患者さんにとってより安心して使えるサービスとなるように開発を続けていければと思っています。 長くなりましたが、最後までお読みいただきありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
こんにちは、医療プラットフォーム本部/プロダクト開発室/第一開発グループ所属の世嘉良です。 メドレーには 2018 年の頭に入社しており、今年で 4 年目になります。 当初はサーバーサイドを中心に開発を担当していたのですが、最近は患者エンゲージメントチームという患者様に提供するサービスを開発するチームで主に iOS の仕事を担当することが多いです。 さて去年の 12 月になりますが、 CLINICS アプリ は UI のフルリニューアル を行いました。 今回はリニューアルの裏話 (iOS について) をしていきたいと思います。 これまでの CLINICS アプリについて 本題を書く前に、CLINICS アプリの歴史を紹介します。 ファーストコミットを見てみると、アプリの開発は 2016 年 2 月ごろからスタートし、ファーストリリースが行われたのが 2016 年 5 月でした。 当初、担当していたエンジニアは iOS の経験が豊富な方はおらず、全員で試行錯誤しながら開発を進めていたようです。 しばらくの間は機能の追加などが行われていましたが、 CLINICS カルテ の開発に注力するために大きな開発はストップし、 Pharms との連携が開始される 2020 年 5 月頃まで機能や設計に関する見直しがほとんど行われてきませんでした。 iOS に詳しいエンジニアがいなかったにも関わらず、かなりのスピード感でリリースしていることはさすがの開発力という一言に尽きるのですが、Pharms との連携やお薬手帳といった機能が追加されたり、今後の開発を見据えた際に既存機能や設計の見直しを行いたいと思うようになってきました。 改善したい部分はたくさんあったのですが、対応工数を踏まえ今回のリニューアルでは以下の 2 点の改善に注力することにしました。 Storyboard による View の管理 View とロジックの分離 この 2 点についてそれぞれ詳しく説明します。 1. Storyboard による View の管理 従来の開発では Storyboard を利用して View を作成していました。 Storyboard を使うとレイアウトや画面遷移を簡単に実装することができますが、開発を続けているうちに以下のような問題が目立つようになってきました。 Storyboard が巨大化していき、複数人開発を行う際に支障がでる レイアウトに追加・変更が行われた場合に AutoLayout の再設定に時間がかかる コンポーネント自体にサイズが設定されていることがあり、コンポーネントを再利用できないことがある 課題 こちらは実際にあった Storyboard のキャプチャですが、複数の画面が 1 つの Storyboard 内に詰め込まれた状態となっていました。 Storyboard は Xcode のバージョンにより微妙な差分が発生してしまったり、AutoLayout の調整が必要になる場合があります。 こちらは古い Storyboard の画面を開いた後の差分なのですが、この変更がシステムによって加えられた変更なのか、他人の改修による変更なのかを後から見た時にわかりづらいという問題がありました。 仮に問題が発生した場合は修正を試みるのですが、独自の XML によって表現されているため、iOS の経験が浅い僕にとっては修正が非常に難しかったです。 また、複数人で並行して開発する際にもコンフリクトが起きやすくなってしまい解消に時間がかかるという問題となっていました。 さらに従来の CLINICS アプリは DLS を利用してレイアウトを作成していたのですが、コンポーネントに直接サイズが設定されているものがあり、ある画面では微妙にサイズを調整したい…といった要件に対応できず、せっかくのコンポーネントを再利用できないケースがありました。 解決案 課題に対する解決案は色々と考えられますが、弊社の開発スタイルやエンジニアのスキルを考慮し以下のような方針を立てました。 Storyboard と各画面の実装は 1 : 1 の関係とする 画面遷移の責務も Storyboard から切り離す コンポーネントにアトミックデザインを適用する Storyboard の分割は以下のようなステップで行っていました。 Refactor to Storyboard を使って巨大な Storyboard を分割する SwiftGen を利用し画面生成のコードを自動作成する 2 で生成したものを利用して画面遷移を行う 既存の Segue を削除する まず最初に Xcode 7 から利用可能となった「Refactor to Storyboard」を使って画面を分割するところから始めます。 この機能を利用すると選択した View が新しい Storyboard に切り出され、元の Storyboard には切り出した Storyboard へのリファレンスや Segue 等の接続が保持された状態になります。 次に各画面間の遷移を Segue を使わずに行うようにします。 Segue は便利なのですが、Identifier が単なる文字列であったり、Storyboard を分割してもリファレンスは保持しておく必要がある点が微妙に感じてしまい利用しないことにしました。 Segue を使わずに画面遷移を行う必要があるため、画面生成と画面遷移の方法を自前で実装する必要があります。 今回は画面生成の処理を SwiftGen を利用して自動生成可能にし、VIPER というアーキテクチャの Router を参考にして画面遷移を Storyboard から切り離すことにしました。 ※ SwiftGen の利用方法は SwiftGen#interface-builder を参照ください。 Router の実装は以下の通りです。 実装されたプロトコルを遷移元の VC に継承し、画面遷移のコードを呼び出すだけで画面遷移を行うことができます。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func presentClinic ( clinicId : String ) func pushClinic ( clinicId : String ) } extension ClinicWireframe { func presentClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : true ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) vc. modalPresentationStyle = . pageSheet let nc = UINavigationController ( rootViewController : vc) viewController. present (nc, animated : true ) } func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } 最後にコンポーネントの調整についてですが、CLINICS アプリではアトミックデザインを簡略化し、以下のような基準でコンポーネントを実装しています。 Block: UI の最小単位 (他の PJT にも持ち込めそうなレベルまで分解されたもの) Partial: CLINICS の PJT 固有のコンポーネント Layout: ヘルプ用のモーダルやエラー画面など他の画面から呼び出されることで初めて意味をなす画面など Page: 各 ViewController とそれに紐づく Storyboard この管理方法自体は 弊社の別プロダクトの開発を担当しているエンジニアによって考案されたものです。 弊社ではサーバー・フロント分け隔てなく開発を任されることも多く、厳密なアトミックデザインだとコンポーネントの分類に困ることが多かったためこのような管理方法をとっているそうです。 今回のリニューアルに際して CLINICS アプリでもこれを参考にすることにしました。 これまで DLS として管理されていたコンポーネントは Block で実装しなおし、Width / Height といったサイズの設定を行わないようにしました。 このようにコンポーネントの実装レベルを明確にしたことで、実装に一定の指針が生まれ使い回しの効くコンポーネントを作成しやすくなりました。 2. View とロジックの分離 スピード開発が求められた背景を考えると仕方のないことではあるのですが、従来の CLINICS アプリでは ViewController にロジックが記述されており、俗にいう Fat ViewController の状態になってしまっていました。 Fat ViewController の問題点については既にさまざまな方が取り上げていますが、以下の部分が問題と感じています。 UI とロジックが分離されていないため、テストを書くことが難しい 可読性が低くなりがち ロジックが切り出されていないため似たような実装が点在する場合がある 課題 こちらは実際にあった ViewController の一部です。 このコードに関しては以下の部分が問題と感じていました。 通信処理の呼び出しが ViewController の責務になっていること 通信結果を整形するロジックが ViewController の責務になっていること 画面描画のための State 管理が ViewController の責務になっていること 解決案 UI とロジックを分離するための手法はさまざまなものがありますが、弊社のアプリには以下のような特徴があります。 iOS 以外にも複数のプラットフォームをサポートしているためフロントエンドで保持するデータや複雑なロジック自体は少ない (サーバー側でなるべく担保している) 実装時や QA 時に感じた違和感について細かくディレクターと打ち合わせし、その結果次第では仕様を変更することがある これらを考慮すると、プロジェクトの期間的に初期の導入コストが低く、データフローがシンプルなものに留めたいというように考えがまとまってきました。 これらを考慮し、CLINICS アプリでは MVP というアーキテクチャを採用することにしました。 より詳細には MVP の Passive View 方式を採用しており、以下のような形で実装しています。 View は基本的にすべての入力イベントに対応した Presenter の処理を呼び出す Presenter は入力に応じて通信処理などの外部要因となる処理を呼び出し、結果を整形する プレゼンテーションロジックの結果を描画するように View に指示を出す View は Presenter の指示によってのみ描画処理を行い、自身を起点とした描画処理は行わない Model–view–presenter - Wikipedia 既に Router は導入してるため、複雑な処理フローを実装したい場合は Interactor を導入するだけで VIPER アーキテクチャへと発展させることが簡単にできる点も魅力でした。 CLINICS アプリでの ViewController / Presenter の実装を簡単にまとめたものは以下のようになっています。 final class ClinicViewController : UIViewController { private lazy var presenter: ClinicPresenterInput = { fatalError (“Failed to inject presenter”) }() override func viewDidLoad () { super . viewDidLoad () presenter?. refresh () } func inject ( presenter : ClinicPresenterInput) { self . presenter = presenter } // タップされた際に呼び出す func onTapServiceCell ( _ service : ServiceEntity, isTelemedicine : Bool ) { presenter?. didTapServiceButton (service, isTelemedicine : isTelemedicine) } } // MARK: - ClinicPresenterOutput extension ClinicViewController : ClinicPresenterOutput { func reloadData ( clinic : ClinicEntity?) { clinicViewStore. clinic = clinic } func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) // 予約へ進む } func showErrorAlert ( _ error : Error ) { // エラー表示を行う } } protocol ClinicPresenterInput : AnyObject { func refresh () func didTapServiceButton ( _ service : ServiceEntity, isTelemedicine : Bool ) } protocol ClinicPresenterOutput : AnyObject { func reloadData ( clinic : ClinicEntity?) func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) func showErrorAlert ( _ error : Error ) } final class ClinicPresenter { private let clinicRepository: ClinicRepository private var clinic: ClinicEntity? private var cancellables: Set <AnyCancellable> = . init () init ( view : ClinicPresenterOutput, container : DIContainer) { self . view = view clinicRepository = container. resolve () } private func reloadView () { view?. reloadData ( clinic : clinic) } } // MARK: - PresenterInput extension ClinicPresenter : ClinicPresenterInput { func refresh () { guard let clinicId = clinicId else { return } clinicRepository. getClinic ( clinicId : clinicId) . sink ( receiveCompletion : { [ weak self ] completion in guard let self = self else { return } guard case let . failure (error) = completion else { return } self . view ?. showErrorAlert (error) } }, receiveValue : { [ weak self ] response in guard let self = self else { return } self . clinic = response. data self . reloadView () } ) . store ( in : &cancellables) } func didTapServiceButton ( _ service : ClinicsServiceEntity, isTelemedicine : Bool ) { guard let clinic = clinic else { return } view?. openCreateAppointmentView ( clinic : clinic, service : service, isTelemedicine : isTelemedicine) } } ViewController と Presenter は以下のように Router の内部で DI するようにしています。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func pushClinic ( clinicId : String ) } extension ClinicWireframe { func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc, container : DIContainer ()) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } } これによって、もともと ViewController にあった処理は以下のように分離されました。 また、View と Presenter はインターフェースを介してしかお互いを知らないため、実装の交換が簡単にできるようになりました。 Presenter を交換することで 1 つの View で別々の処理を表現することが可能となったり、View をテストコードを交換することで Presenter の入出力値をテストすることができるようになっています。 まとめ CLINICS アプリは歴史のあるプロジェクトですが、今回のプロジェクトを通して UI だけでなく裏側の実装も刷新しています。 巨大な Storyboard を分解したことでメンテナビリティが向上し、MVP (+ Router) の導入によって View とロジックの交換が簡単になり、テストなどの実装を取り入れやすくなりました。 さいごに ブログの本編には書けなかったのですが、今回リニューアルされた画面に関しては SwiftUI を利用してフルスクラッチで実装していたり、 Asset の管理方法についても大きな見直しを行いました。 またアーキテクチャの選定にあたり、「 iOS アプリ設計パターン入門 」が大変参考になりました。iOS の開発を行う方はぜひ一読してほしいと思います。 約半年ほどかかった大きなプロジェクトでしたが、患者エンゲージメントチームのメンバー全員で取り組み無事にリリースまで漕ぎ着けることができました。まだまだ手探りな部分もありますが、今後も患者さんにとってより安心して使えるサービスとなるように開発を続けていければと思っています。 長くなりましたが、最後までお読みいただきありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
こんにちは、医療プラットフォーム本部/プロダクト開発室/第一開発グループ所属の世嘉良です。 メドレーには 2018 年の頭に入社しており、今年で 4 年目になります。 当初はサーバーサイドを中心に開発を担当していたのですが、最近は患者エンゲージメントチームという患者様に提供するサービスを開発するチームで主に iOS の仕事を担当することが多いです。 さて去年の 12 月になりますが、 CLINICS アプリ は UI のフルリニューアル を行いました。 今回はリニューアルの裏話 (iOS について) をしていきたいと思います。 これまでの CLINICS アプリについて 本題を書く前に、CLINICS アプリの歴史を紹介します。 ファーストコミットを見てみると、アプリの開発は 2016 年 2 月ごろからスタートし、ファーストリリースが行われたのが 2016 年 5 月でした。 当初、担当していたエンジニアは iOS の経験が豊富な方はおらず、全員で試行錯誤しながら開発を進めていたようです。 しばらくの間は機能の追加などが行われていましたが、 CLINICS カルテ の開発に注力するために大きな開発はストップし、 Pharms との連携が開始される 2020 年 5 月頃まで機能や設計に関する見直しがほとんど行われてきませんでした。 iOS に詳しいエンジニアがいなかったにも関わらず、かなりのスピード感でリリースしていることはさすがの開発力という一言に尽きるのですが、Pharms との連携やお薬手帳といった機能が追加されたり、今後の開発を見据えた際に既存機能や設計の見直しを行いたいと思うようになってきました。 改善したい部分はたくさんあったのですが、対応工数を踏まえ今回のリニューアルでは以下の 2 点の改善に注力することにしました。 Storyboard による View の管理 View とロジックの分離 この 2 点についてそれぞれ詳しく説明します。 1. Storyboard による View の管理 従来の開発では Storyboard を利用して View を作成していました。 Storyboard を使うとレイアウトや画面遷移を簡単に実装することができますが、開発を続けているうちに以下のような問題が目立つようになってきました。 Storyboard が巨大化していき、複数人開発を行う際に支障がでる レイアウトに追加・変更が行われた場合に AutoLayout の再設定に時間がかかる コンポーネント自体にサイズが設定されていることがあり、コンポーネントを再利用できないことがある 課題 こちらは実際にあった Storyboard のキャプチャですが、複数の画面が 1 つの Storyboard 内に詰め込まれた状態となっていました。 Storyboard は Xcode のバージョンにより微妙な差分が発生してしまったり、AutoLayout の調整が必要になる場合があります。 こちらは古い Storyboard の画面を開いた後の差分なのですが、この変更がシステムによって加えられた変更なのか、他人の改修による変更なのかを後から見た時にわかりづらいという問題がありました。 仮に問題が発生した場合は修正を試みるのですが、独自の XML によって表現されているため、iOS の経験が浅い僕にとっては修正が非常に難しかったです。 また、複数人で並行して開発する際にもコンフリクトが起きやすくなってしまい解消に時間がかかるという問題となっていました。 さらに従来の CLINICS アプリは DLS を利用してレイアウトを作成していたのですが、コンポーネントに直接サイズが設定されているものがあり、ある画面では微妙にサイズを調整したい…といった要件に対応できず、せっかくのコンポーネントを再利用できないケースがありました。 解決案 課題に対する解決案は色々と考えられますが、弊社の開発スタイルやエンジニアのスキルを考慮し以下のような方針を立てました。 Storyboard と各画面の実装は 1 : 1 の関係とする 画面遷移の責務も Storyboard から切り離す コンポーネントにアトミックデザインを適用する Storyboard の分割は以下のようなステップで行っていました。 Refactor to Storyboard を使って巨大な Storyboard を分割する SwiftGen を利用し画面生成のコードを自動作成する 2 で生成したものを利用して画面遷移を行う 既存の Segue を削除する まず最初に Xcode 7 から利用可能となった「Refactor to Storyboard」を使って画面を分割するところから始めます。 この機能を利用すると選択した View が新しい Storyboard に切り出され、元の Storyboard には切り出した Storyboard へのリファレンスや Segue 等の接続が保持された状態になります。 次に各画面間の遷移を Segue を使わずに行うようにします。 Segue は便利なのですが、Identifier が単なる文字列であったり、Storyboard を分割してもリファレンスは保持しておく必要がある点が微妙に感じてしまい利用しないことにしました。 Segue を使わずに画面遷移を行う必要があるため、画面生成と画面遷移の方法を自前で実装する必要があります。 今回は画面生成の処理を SwiftGen を利用して自動生成可能にし、VIPER というアーキテクチャの Router を参考にして画面遷移を Storyboard から切り離すことにしました。 ※ SwiftGen の利用方法は SwiftGen#interface-builder を参照ください。 Router の実装は以下の通りです。 実装されたプロトコルを遷移元の VC に継承し、画面遷移のコードを呼び出すだけで画面遷移を行うことができます。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func presentClinic ( clinicId : String ) func pushClinic ( clinicId : String ) } extension ClinicWireframe { func presentClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : true ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) vc. modalPresentationStyle = . pageSheet let nc = UINavigationController ( rootViewController : vc) viewController. present (nc, animated : true ) } func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } 最後にコンポーネントの調整についてですが、CLINICS アプリではアトミックデザインを簡略化し、以下のような基準でコンポーネントを実装しています。 Block: UI の最小単位 (他の PJT にも持ち込めそうなレベルまで分解されたもの) Partial: CLINICS の PJT 固有のコンポーネント Layout: ヘルプ用のモーダルやエラー画面など他の画面から呼び出されることで初めて意味をなす画面など Page: 各 ViewController とそれに紐づく Storyboard この管理方法自体は 弊社の別プロダクトの開発を担当しているエンジニアによって考案されたものです。 弊社ではサーバー・フロント分け隔てなく開発を任されることも多く、厳密なアトミックデザインだとコンポーネントの分類に困ることが多かったためこのような管理方法をとっているそうです。 今回のリニューアルに際して CLINICS アプリでもこれを参考にすることにしました。 これまで DLS として管理されていたコンポーネントは Block で実装しなおし、Width / Height といったサイズの設定を行わないようにしました。 このようにコンポーネントの実装レベルを明確にしたことで、実装に一定の指針が生まれ使い回しの効くコンポーネントを作成しやすくなりました。 2. View とロジックの分離 スピード開発が求められた背景を考えると仕方のないことではあるのですが、従来の CLINICS アプリでは ViewController にロジックが記述されており、俗にいう Fat ViewController の状態になってしまっていました。 Fat ViewController の問題点については既にさまざまな方が取り上げていますが、以下の部分が問題と感じています。 UI とロジックが分離されていないため、テストを書くことが難しい 可読性が低くなりがち ロジックが切り出されていないため似たような実装が点在する場合がある 課題 こちらは実際にあった ViewController の一部です。 このコードに関しては以下の部分が問題と感じていました。 通信処理の呼び出しが ViewController の責務になっていること 通信結果を整形するロジックが ViewController の責務になっていること 画面描画のための State 管理が ViewController の責務になっていること 解決案 UI とロジックを分離するための手法はさまざまなものがありますが、弊社のアプリには以下のような特徴があります。 iOS 以外にも複数のプラットフォームをサポートしているためフロントエンドで保持するデータや複雑なロジック自体は少ない (サーバー側でなるべく担保している) 実装時や QA 時に感じた違和感について細かくディレクターと打ち合わせし、その結果次第では仕様を変更することがある これらを考慮すると、プロジェクトの期間的に初期の導入コストが低く、データフローがシンプルなものに留めたいというように考えがまとまってきました。 これらを考慮し、CLINICS アプリでは MVP というアーキテクチャを採用することにしました。 より詳細には MVP の Passive View 方式を採用しており、以下のような形で実装しています。 View は基本的にすべての入力イベントに対応した Presenter の処理を呼び出す Presenter は入力に応じて通信処理などの外部要因となる処理を呼び出し、結果を整形する プレゼンテーションロジックの結果を描画するように View に指示を出す View は Presenter の指示によってのみ描画処理を行い、自身を起点とした描画処理は行わない Model–view–presenter - Wikipedia 既に Router は導入してるため、複雑な処理フローを実装したい場合は Interactor を導入するだけで VIPER アーキテクチャへと発展させることが簡単にできる点も魅力でした。 CLINICS アプリでの ViewController / Presenter の実装を簡単にまとめたものは以下のようになっています。 final class ClinicViewController : UIViewController { private lazy var presenter: ClinicPresenterInput = { fatalError (“Failed to inject presenter”) }() override func viewDidLoad () { super . viewDidLoad () presenter?. refresh () } func inject ( presenter : ClinicPresenterInput) { self . presenter = presenter } // タップされた際に呼び出す func onTapServiceCell ( _ service : ServiceEntity, isTelemedicine : Bool ) { presenter?. didTapServiceButton (service, isTelemedicine : isTelemedicine) } } // MARK: - ClinicPresenterOutput extension ClinicViewController : ClinicPresenterOutput { func reloadData ( clinic : ClinicEntity?) { clinicViewStore. clinic = clinic } func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) // 予約へ進む } func showErrorAlert ( _ error : Error ) { // エラー表示を行う } } protocol ClinicPresenterInput : AnyObject { func refresh () func didTapServiceButton ( _ service : ServiceEntity, isTelemedicine : Bool ) } protocol ClinicPresenterOutput : AnyObject { func reloadData ( clinic : ClinicEntity?) func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) func showErrorAlert ( _ error : Error ) } final class ClinicPresenter { private let clinicRepository: ClinicRepository private var clinic: ClinicEntity? private var cancellables: Set <AnyCancellable> = . init () init ( view : ClinicPresenterOutput, container : DIContainer) { self . view = view clinicRepository = container. resolve () } private func reloadView () { view?. reloadData ( clinic : clinic) } } // MARK: - PresenterInput extension ClinicPresenter : ClinicPresenterInput { func refresh () { guard let clinicId = clinicId else { return } clinicRepository. getClinic ( clinicId : clinicId) . sink ( receiveCompletion : { [ weak self ] completion in guard let self = self else { return } guard case let . failure (error) = completion else { return } self . view ?. showErrorAlert (error) } }, receiveValue : { [ weak self ] response in guard let self = self else { return } self . clinic = response. data self . reloadView () } ) . store ( in : &cancellables) } func didTapServiceButton ( _ service : ClinicsServiceEntity, isTelemedicine : Bool ) { guard let clinic = clinic else { return } view?. openCreateAppointmentView ( clinic : clinic, service : service, isTelemedicine : isTelemedicine) } } ViewController と Presenter は以下のように Router の内部で DI するようにしています。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func pushClinic ( clinicId : String ) } extension ClinicWireframe { func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc, container : DIContainer ()) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } } これによって、もともと ViewController にあった処理は以下のように分離されました。 また、View と Presenter はインターフェースを介してしかお互いを知らないため、実装の交換が簡単にできるようになりました。 Presenter を交換することで 1 つの View で別々の処理を表現することが可能となったり、View をテストコードを交換することで Presenter の入出力値をテストすることができるようになっています。 まとめ CLINICS アプリは歴史のあるプロジェクトですが、今回のプロジェクトを通して UI だけでなく裏側の実装も刷新しています。 巨大な Storyboard を分解したことでメンテナビリティが向上し、MVP (+ Router) の導入によって View とロジックの交換が簡単になり、テストなどの実装を取り入れやすくなりました。 さいごに ブログの本編には書けなかったのですが、今回リニューアルされた画面に関しては SwiftUI を利用してフルスクラッチで実装していたり、 Asset の管理方法についても大きな見直しを行いました。 またアーキテクチャの選定にあたり、「 iOS アプリ設計パターン入門 」が大変参考になりました。iOS の開発を行う方はぜひ一読してほしいと思います。 約半年ほどかかった大きなプロジェクトでしたが、患者エンゲージメントチームのメンバー全員で取り組み無事にリリースまで漕ぎ着けることができました。まだまだ手探りな部分もありますが、今後も患者さんにとってより安心して使えるサービスとなるように開発を続けていければと思っています。 長くなりましたが、最後までお読みいただきありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
こんにちは、医療プラットフォーム本部/プロダクト開発室/第一開発グループ所属の世嘉良です。 メドレーには 2018 年の頭に入社しており、今年で 4 年目になります。 当初はサーバーサイドを中心に開発を担当していたのですが、最近は患者エンゲージメントチームという患者様に提供するサービスを開発するチームで主に iOS の仕事を担当することが多いです。 さて去年の 12 月になりますが、 CLINICS アプリ は UI のフルリニューアル を行いました。 今回はリニューアルの裏話 (iOS について) をしていきたいと思います。 これまでの CLINICS アプリについて 本題を書く前に、CLINICS アプリの歴史を紹介します。 ファーストコミットを見てみると、アプリの開発は 2016 年 2 月ごろからスタートし、ファーストリリースが行われたのが 2016 年 5 月でした。 当初、担当していたエンジニアは iOS の経験が豊富な方はおらず、全員で試行錯誤しながら開発を進めていたようです。 しばらくの間は機能の追加などが行われていましたが、 CLINICS カルテ の開発に注力するために大きな開発はストップし、 Pharms との連携が開始される 2020 年 5 月頃まで機能や設計に関する見直しがほとんど行われてきませんでした。 iOS に詳しいエンジニアがいなかったにも関わらず、かなりのスピード感でリリースしていることはさすがの開発力という一言に尽きるのですが、Pharms との連携やお薬手帳といった機能が追加されたり、今後の開発を見据えた際に既存機能や設計の見直しを行いたいと思うようになってきました。 改善したい部分はたくさんあったのですが、対応工数を踏まえ今回のリニューアルでは以下の 2 点の改善に注力することにしました。 Storyboard による View の管理 View とロジックの分離 この 2 点についてそれぞれ詳しく説明します。 1. Storyboard による View の管理 従来の開発では Storyboard を利用して View を作成していました。 Storyboard を使うとレイアウトや画面遷移を簡単に実装することができますが、開発を続けているうちに以下のような問題が目立つようになってきました。 Storyboard が巨大化していき、複数人開発を行う際に支障がでる レイアウトに追加・変更が行われた場合に AutoLayout の再設定に時間がかかる コンポーネント自体にサイズが設定されていることがあり、コンポーネントを再利用できないことがある 課題 こちらは実際にあった Storyboard のキャプチャですが、複数の画面が 1 つの Storyboard 内に詰め込まれた状態となっていました。 Storyboard は Xcode のバージョンにより微妙な差分が発生してしまったり、AutoLayout の調整が必要になる場合があります。 こちらは古い Storyboard の画面を開いた後の差分なのですが、この変更がシステムによって加えられた変更なのか、他人の改修による変更なのかを後から見た時にわかりづらいという問題がありました。 仮に問題が発生した場合は修正を試みるのですが、独自の XML によって表現されているため、iOS の経験が浅い僕にとっては修正が非常に難しかったです。 また、複数人で並行して開発する際にもコンフリクトが起きやすくなってしまい解消に時間がかかるという問題となっていました。 さらに従来の CLINICS アプリは DLS を利用してレイアウトを作成していたのですが、コンポーネントに直接サイズが設定されているものがあり、ある画面では微妙にサイズを調整したい…といった要件に対応できず、せっかくのコンポーネントを再利用できないケースがありました。 解決案 課題に対する解決案は色々と考えられますが、弊社の開発スタイルやエンジニアのスキルを考慮し以下のような方針を立てました。 Storyboard と各画面の実装は 1 : 1 の関係とする 画面遷移の責務も Storyboard から切り離す コンポーネントにアトミックデザインを適用する Storyboard の分割は以下のようなステップで行っていました。 Refactor to Storyboard を使って巨大な Storyboard を分割する SwiftGen を利用し画面生成のコードを自動作成する 2 で生成したものを利用して画面遷移を行う 既存の Segue を削除する まず最初に Xcode 7 から利用可能となった「Refactor to Storyboard」を使って画面を分割するところから始めます。 この機能を利用すると選択した View が新しい Storyboard に切り出され、元の Storyboard には切り出した Storyboard へのリファレンスや Segue 等の接続が保持された状態になります。 次に各画面間の遷移を Segue を使わずに行うようにします。 Segue は便利なのですが、Identifier が単なる文字列であったり、Storyboard を分割してもリファレンスは保持しておく必要がある点が微妙に感じてしまい利用しないことにしました。 Segue を使わずに画面遷移を行う必要があるため、画面生成と画面遷移の方法を自前で実装する必要があります。 今回は画面生成の処理を SwiftGen を利用して自動生成可能にし、VIPER というアーキテクチャの Router を参考にして画面遷移を Storyboard から切り離すことにしました。 ※ SwiftGen の利用方法は SwiftGen#interface-builder を参照ください。 Router の実装は以下の通りです。 実装されたプロトコルを遷移元の VC に継承し、画面遷移のコードを呼び出すだけで画面遷移を行うことができます。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func presentClinic ( clinicId : String ) func pushClinic ( clinicId : String ) } extension ClinicWireframe { func presentClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : true ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) vc. modalPresentationStyle = . pageSheet let nc = UINavigationController ( rootViewController : vc) viewController. present (nc, animated : true ) } func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } 最後にコンポーネントの調整についてですが、CLINICS アプリではアトミックデザインを簡略化し、以下のような基準でコンポーネントを実装しています。 Block: UI の最小単位 (他の PJT にも持ち込めそうなレベルまで分解されたもの) Partial: CLINICS の PJT 固有のコンポーネント Layout: ヘルプ用のモーダルやエラー画面など他の画面から呼び出されることで初めて意味をなす画面など Page: 各 ViewController とそれに紐づく Storyboard この管理方法自体は 弊社の別プロダクトの開発を担当しているエンジニアによって考案されたものです。 弊社ではサーバー・フロント分け隔てなく開発を任されることも多く、厳密なアトミックデザインだとコンポーネントの分類に困ることが多かったためこのような管理方法をとっているそうです。 今回のリニューアルに際して CLINICS アプリでもこれを参考にすることにしました。 これまで DLS として管理されていたコンポーネントは Block で実装しなおし、Width / Height といったサイズの設定を行わないようにしました。 このようにコンポーネントの実装レベルを明確にしたことで、実装に一定の指針が生まれ使い回しの効くコンポーネントを作成しやすくなりました。 2. View とロジックの分離 スピード開発が求められた背景を考えると仕方のないことではあるのですが、従来の CLINICS アプリでは ViewController にロジックが記述されており、俗にいう Fat ViewController の状態になってしまっていました。 Fat ViewController の問題点については既にさまざまな方が取り上げていますが、以下の部分が問題と感じています。 UI とロジックが分離されていないため、テストを書くことが難しい 可読性が低くなりがち ロジックが切り出されていないため似たような実装が点在する場合がある 課題 こちらは実際にあった ViewController の一部です。 このコードに関しては以下の部分が問題と感じていました。 通信処理の呼び出しが ViewController の責務になっていること 通信結果を整形するロジックが ViewController の責務になっていること 画面描画のための State 管理が ViewController の責務になっていること 解決案 UI とロジックを分離するための手法はさまざまなものがありますが、弊社のアプリには以下のような特徴があります。 iOS 以外にも複数のプラットフォームをサポートしているためフロントエンドで保持するデータや複雑なロジック自体は少ない (サーバー側でなるべく担保している) 実装時や QA 時に感じた違和感について細かくディレクターと打ち合わせし、その結果次第では仕様を変更することがある これらを考慮すると、プロジェクトの期間的に初期の導入コストが低く、データフローがシンプルなものに留めたいというように考えがまとまってきました。 これらを考慮し、CLINICS アプリでは MVP というアーキテクチャを採用することにしました。 より詳細には MVP の Passive View 方式を採用しており、以下のような形で実装しています。 View は基本的にすべての入力イベントに対応した Presenter の処理を呼び出す Presenter は入力に応じて通信処理などの外部要因となる処理を呼び出し、結果を整形する プレゼンテーションロジックの結果を描画するように View に指示を出す View は Presenter の指示によってのみ描画処理を行い、自身を起点とした描画処理は行わない Model–view–presenter - Wikipedia 既に Router は導入してるため、複雑な処理フローを実装したい場合は Interactor を導入するだけで VIPER アーキテクチャへと発展させることが簡単にできる点も魅力でした。 CLINICS アプリでの ViewController / Presenter の実装を簡単にまとめたものは以下のようになっています。 final class ClinicViewController : UIViewController { private lazy var presenter: ClinicPresenterInput = { fatalError (“Failed to inject presenter”) }() override func viewDidLoad () { super . viewDidLoad () presenter?. refresh () } func inject ( presenter : ClinicPresenterInput) { self . presenter = presenter } // タップされた際に呼び出す func onTapServiceCell ( _ service : ServiceEntity, isTelemedicine : Bool ) { presenter?. didTapServiceButton (service, isTelemedicine : isTelemedicine) } } // MARK: - ClinicPresenterOutput extension ClinicViewController : ClinicPresenterOutput { func reloadData ( clinic : ClinicEntity?) { clinicViewStore. clinic = clinic } func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) // 予約へ進む } func showErrorAlert ( _ error : Error ) { // エラー表示を行う } } protocol ClinicPresenterInput : AnyObject { func refresh () func didTapServiceButton ( _ service : ServiceEntity, isTelemedicine : Bool ) } protocol ClinicPresenterOutput : AnyObject { func reloadData ( clinic : ClinicEntity?) func openCreateAppointmentView ( clinic : ClinicEntity, service : ServiceEntity, isTelemedicine : Bool ) func showErrorAlert ( _ error : Error ) } final class ClinicPresenter { private let clinicRepository: ClinicRepository private var clinic: ClinicEntity? private var cancellables: Set <AnyCancellable> = . init () init ( view : ClinicPresenterOutput, container : DIContainer) { self . view = view clinicRepository = container. resolve () } private func reloadView () { view?. reloadData ( clinic : clinic) } } // MARK: - PresenterInput extension ClinicPresenter : ClinicPresenterInput { func refresh () { guard let clinicId = clinicId else { return } clinicRepository. getClinic ( clinicId : clinicId) . sink ( receiveCompletion : { [ weak self ] completion in guard let self = self else { return } guard case let . failure (error) = completion else { return } self . view ?. showErrorAlert (error) } }, receiveValue : { [ weak self ] response in guard let self = self else { return } self . clinic = response. data self . reloadView () } ) . store ( in : &cancellables) } func didTapServiceButton ( _ service : ClinicsServiceEntity, isTelemedicine : Bool ) { guard let clinic = clinic else { return } view?. openCreateAppointmentView ( clinic : clinic, service : service, isTelemedicine : isTelemedicine) } } ViewController と Presenter は以下のように Router の内部で DI するようにしています。 protocol ClinicWireframe : AnyObject { var viewController: UIViewController { get } func pushClinic ( clinicId : String ) } extension ClinicWireframe { func pushClinic ( clinicId : String ) { let vc = StoryboardScene. Clinic . initialScene . instantiate { ClinicViewController ( coder : $0 , isHeaderEnabled : false ) } let presenter = ClinicPresenter ( view : vc, container : DIContainer ()) presenter. setClinicId ( clinicId : clinicId) vc. inject ( presenter : presenter) viewController. navigationController ?. pushViewController (vc, animated : true ) } } これによって、もともと ViewController にあった処理は以下のように分離されました。 また、View と Presenter はインターフェースを介してしかお互いを知らないため、実装の交換が簡単にできるようになりました。 Presenter を交換することで 1 つの View で別々の処理を表現することが可能となったり、View をテストコードを交換することで Presenter の入出力値をテストすることができるようになっています。 まとめ CLINICS アプリは歴史のあるプロジェクトですが、今回のプロジェクトを通して UI だけでなく裏側の実装も刷新しています。 巨大な Storyboard を分解したことでメンテナビリティが向上し、MVP (+ Router) の導入によって View とロジックの交換が簡単になり、テストなどの実装を取り入れやすくなりました。 さいごに ブログの本編には書けなかったのですが、今回リニューアルされた画面に関しては SwiftUI を利用してフルスクラッチで実装していたり、 Asset の管理方法についても大きな見直しを行いました。 またアーキテクチャの選定にあたり、「 iOS アプリ設計パターン入門 」が大変参考になりました。iOS の開発を行う方はぜひ一読してほしいと思います。 約半年ほどかかった大きなプロジェクトでしたが、患者エンゲージメントチームのメンバー全員で取り組み無事にリリースまで漕ぎ着けることができました。まだまだ手探りな部分もありますが、今後も患者さんにとってより安心して使えるサービスとなるように開発を続けていければと思っています。 長くなりましたが、最後までお読みいただきありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに こんにちは、メドレーのコーポレート本部法務コンプライアンス部で、知的財産関連の業務を担当している鬼鞍です。 1 年ほど前に本ブログで「 特許とはなにか 」というテーマで当社における特許啓蒙活動を紹介させて頂いたのですが、思っていた以上に反響があり、「面白い」とか「わかりやすい」という嬉しいコメントも頂戴しました。 私がメドレーに入社して最初のミッションが知財部門の立ち上げだったので、次はぜひ**「ベンチャーでの知財部門の立ち上げ方」**的なテーマでブログ発信したいと思っていたのですが、当初はまだ具体的な社内事例が少なかったため、実績ができた段階で改めて本ブログで発表したいと思っていました。 それから1年以上が経過して実際に特許も取得するなど知財活動としての実績が蓄積されてきたため、これを機会に ベンチャーで知財部門を立ち上げる際にやるべきことについて、実例に触れながら具体的にまとめてみました。 かなり長いコンテンツになってしまいましたが、 フェーズごとの知財活動計画 であったり、 知財ロードマップの作り方 など、 なかなか表に出てこない知財部門立ち上げストーリーの具体的事例を惜しみなく書かせていただいております ので、ぜひご一読いただき、お役に立てれば幸いです。 知財は開発チームとの信頼関係の構築がまず第一 先ほどは知財部門を立ち上げストーリの中で、知財活動計画とか、知財ロードマップであるとか少し格好つけたような文字が羅列していましたが、実は私が知財専任として初めに配置されて最初にやったことはそんな大それたことではありませんでした。 最初になにをやったかというと、 エンジニアの方とランチに行く、あるいは飲みに行く 、ということでした。当時はまだ Covid-19 が蔓延する前だったので気軽にそういうことができました。入社して最初の 1 ヶ月くらいは週 2 回くらいのペースで誰かとランチか飲みにいくということをしていたと思います。 飲みに行く、というとふざけているとか思われるかもしれませんが、知財部門の立ち上げストーリーを語る上で、このプロセスは無視できないほど重要だと考えています。先に断っておくと私は酒好きでもなければ、家でも一切飲みません。 当たり前のことなのですが、知財というのはプロダクトを生み出す開発組織があって初めて価値を発揮するものです。知財はあくまで事業や開発をサポートするところであって、それ単体で存在意義を発揮することはほぼありません。特に特許活動については開発チームとの連携が非常に重要です。 一緒にご飯を食べることが信頼関係を構築する上で最良の方法、とまでは言いませんが、少なくともご飯を食べない人はいないわけで、時間を無駄にしている感もなければ、オフィスの外で会えば仕事以外のことを話題にし易いので互いの趣味嗜好を知ることができます。みなさんもそうだと思いますが、通常、仲の悪い人とは一緒に食事はしないと思います。 特に、この後にもご紹介する 社内のアイデアを発掘するという発明発掘活動は、話しやすいカジュアルな雰囲気で雑談のような感じでブレストするというのが非常に重要 で、このときに相手がどんな人間かを互いに知っているか、知らないかでは大きな差がでてきます。 また更に、この後にご紹介する知財計画立案や知財ロードマップを作成することも当然重要なのですが、結局仕事というのは人と人との間を思考がやりとりされて実現化されていくものなので、人間関係・信頼関係が全ての基礎であり、実は一番大切にするべきものだと思います。 これは何も知財に限ったことではなく、部門を跨いで仕事をする上で信頼関係は非常に重要ですし、知財の観点で見ても 円滑なコミュニケーションが結果的にいい発明やアイデアを生み出すための土壌づくりに貢献する ものだと考えています。 知財活動計画は、事業の成長フェーズに合わせて設定する 知財部門の設立にあたって、エンジニアとのコミュニケーションと並行して進めたのが、知財活動計画の立案でした。知財活動とは、知財サイクルをうまく事業サイクルにインストールしてサイクルを循環させることであり、更にはこのサイクルが循環するための運用基盤を構築することを指します。また、ここでいう知財サイクルとは、例えば、社内に埋もれたアイデアを発掘し(創発)→ それを知財権化し(保護)→ 権利化した知財を活用し(活用)→ 活用した知財で得られた利益や結果を創発支援に適用できる形に変換する、というものです。 このような知財サイクルを、何の計画や戦略もなしに事業サイクルへインストールしようとするとうまくいきません。なぜなら、事業というのは日々成長し変化していくもので、 事業の成長に応じて知財活動の目的や課題も変化するからです 。 例えば、子会社が増えて事業規模が拡大すると、その子会社の事業領域で既に多くの特許出願がされている場合には、発明発掘活動よりもまずは法的リスクを回避することを最優先にして、他社の特許調査をしっかりやってからプロダクトをリリースする仕組みを作ろう、ということになります。あるいは、事業の成長に伴いプロダクトが成熟してきた場面では、似たようなプロダクトが乱立する結果、尖った機能や最先端技術で差別化をするようになるため、こうした場合には先行した特許取得が重要な目的になってきます。 このように、事業の成長フェーズに応じた知財活動の計画を立案するべく、まずはメドレーのミッションおよびそのミッションを実現するためのプロダクトの性質を把握する必要がありました。 メドレーは「医療ヘルスケアの未来をつくる」というミッションを掲げ、インターネットテクノロジーによる医療のあり方の変革を目指す企業です。 このミッションと向き合いつつ、 事業プラットフォームの成長フェーズごとに知財活動の方向性・目的を3段階にわけて設定し、成長フェーズごとに知財活動計画を立案しました 。 例えば、プラットフォームの創設期においては新規ユーザ獲得が重要となり、使い勝手のいいシンプルな機能に開発の方向性が向いているため、独自性のある技術を特許化するというよりも、まずは守りを固めるべく知財権侵害のリスクを最小限にして法的安定性の確保に軸足を置いた知財活動を推進します。 このような計画は、知財の観点で一方的にできるものではありません。 知財活動の計画に際しては、開発チームを統括するマネージャにプロダクト開発の方向性をヒアリングして特許取得の判断基準を検討したり、経営層に将来の事業の方向性についてヒアリングをして長期志向で目標設定したりして、ブラッシュアップしていきました。 そういった検討や議論を重ねていくうちに自然と、その企業風土や事業ミッションにあった知財活動計画というものができあがってくるのだと思います。 知財活動ロードマップで現在位置を把握しつつ定期的に軌道修正する 長期的な方向性を可視化したら次はそれをどう実行するかという実行計画が必要になってきました。 そもそも自分たちは今何ができていて、何ができていないのか。またどのようなことを今後やっていかなければならないのかが不明確だよね、という話になり、それを具体的に俯瞰するための知財ロードマッピングというものを作成しました。 縦軸に知的財産の種類、横軸に時間軸であるフェーズを設定して、知財活動の内容に過不足ないかをチェックできるようにしました。 定期的に内容を見直してメドレーの事業規模にあった形に修正しながら自分達の現在位置を把握し、今後の方向性を見定めるためのツールとして有効でした。 このようなロードマップがあれば、知財担当者の立場からすると、実績が可視化されるため知財活動を推進していく上での達成感もありますし、逆に知財担当者を評価する立場からしても、明確な評価基準がない中で、知財担当者の評価をする際に役に立ちます。 以上のように、何もないところから他部門と関わりながら知財活動計画を立案し、ロードマップを達成していくのは、知財部門を立ち上げるという業務の面白さの1つでもあります。 初期段階から長期視点で知財素人でも回せる業務運用作りをする 1 人法務や 1 人知財担当という状況は、遅かれ早かれ属人化という問題が必ずやってきます。 属人化は組織運営上、健全ではありません。 そのため、 知財に関する知識がない人間でも、誰でもその業務を回すことができるような状態をつくる ために、知財部門立ち上げの初期段階から、自分が担当している業務でルーチン化できるものは全て仕組み化してドキュメントに落とし込むようにしていました。 例えば「怪しい特許を見つけた場合の動き方」、「発明報奨金の決定プロセス」、「他社特許調査及び発明発掘のハイブリッド調査の進め方」などなど、ありとあらゆるプロセス業務を全てフロー図や概念図を作成し、誰か見てもわかる業務ということに留意しています。 現状はまだ引き継ぎや新たな知財部員の登用という話がないので、わかりやすい効果を発揮しているわけではないのですが、他部門からの問い合わせがあった際は資料だけ渡せば理解してもらえるので、コミュニケーションの効率化には貢献していると思います。 どのような特許を取得すべきなのか 企業としてどのような特許を取得していくべきか、ということは「知財を最大限に活用する」という知財の出口から考えていく上で、重要な事項になります。 そしてどのような特許を取得するかは、事業領域や事業ミッションに沿って設定されるべきです。 メドレーは、オープンプラットフォームによる医療のあり方の変革を目指している企業なので、開発チームから上がってきたアイデアについては、 プラットフォーム事業を適切に運営していく上で必要な技術かどうか 、という観点で特許化するかどうかを判断しています。 例えば、最近特許を取得した**医療データの管理方法( 特許第 6921177 号 )**というものがあります。これは、アプリ端末から入力された患者データと、医療機関の各業務システムから入力された患者データという 2 つの類似したデータをシームレスに管理するために、両方のデータを 2 つのデータベース上で統合管理することにより、データ管理の責任分担の明確化及び厳格な情報管理を可能にするというものです。 仕組みとしてはとてもシンプルなのですが、 患者の持つ端末と、医科・歯科・調剤等の各業務システムをシームレスに連携させる仕組みが、医療プラットフォームを適切に運営していく上で必要な技術だと判断 したため、特許化しました。 また、上記事例のように実際にプロダクトに実装されている技術を特許化したものだけでなく、実際にプロダクトに実装するかわからないけれども将来を見据えた先見的な特許として、**ブロックチェーンを用いた電子処方箋の管理方法( 特許第 6936763 号 )**という特許を取得しています。 これは、ブロックチェーンを用いたアクセス権の管理機能として全体的に秘密状態を保ちながら電子処方箋を管理するためのアクセス制御に関する仕組みで、ブロックチェーンデータベース上において処方箋データと患者データとを紐づけて、医師端末からの患者レコードへの一時的なアクセス権限を付与し、会計終了時に患者端末の操作に応じて医師端末へのアクセス権限を剥奪するというものです。 このような特許を取得した背景には、 マーケットを独占して権利主張をするためというよりも、メドレーが考える未来のプロダクトビジョンであったり、開発部門の先見的な創作活動を対外的に発信したい 、という思いがあります。 メドレーは、顧客利用率の高いプロダクト、機能拡張性の高いプロダクトの開発を推進しているため、**独創的で最新技術を取り入れた尖った機能を目指すというよりはユーザにとって使いやすいシンプルさが多く取り入れられています。**また、それだけではなく、前述したブロックチェーンと電子処方箋との組み合わせ技術の先見的な特許取得のように、10 年先を見据えた未来のプロダクトのあり方についても日々追求しています。 特許を身近に感じてもらうためのユニークな話も取り入れて社内に啓蒙する いい特許を生み出すためには地道な社内の啓蒙活動も重要です。 メドレーでは、開発チーム間の技術格差の是正や、技術情報の共有による活性化を目的として、エンジニアが日々の業務で扱っている技術や取り組みについて共有するテックランチという勉強会が定期的に開催されています。 先日そのテックランチで、社内のエンジニアの方達に少しでも特許を身近に感じてもらおうと、「特許の頭体操」というコーナーを設けて、実際に頭を使って体験してもらいました。 下の例では、おたまとスプーンとの違いを考えようというお題を通して、ある物の特徴を把握する際に、 「違い」から「もの」を観察すると、その物の特徴が浮き出てくる 、というメッセージが含まれています。 おたまもスプーンも対象物をすくい上げるという機能においては共通しているのですが、おたまの先端のお皿部分とスプーンの先端のお皿部分とは、その形状が異なります。スプーンのお皿部分は楕円形ですが、おたまのお皿部分は円形になっています。 つまり、おたまの特徴は、特許的にいうと「その先端にあるお皿部分が円形の開口を有した半球状で、対象物をすくうための所定の深さを有している」ということになります。 この辺はちょっと固苦しい表現が続いたせいもあり、「難しい…」というコメントをもらいました。馴染みのない人にとってはただただ面倒な作業だと思います。 しかし、ここで言いたかったのは、 これはあくまで構造物についての話であって、ソフトウェアの特徴を把握するということはそんなに難しいことではないのだよ 、ということでした。 ソフトウェアの場合は、ちょっとした機能を追加すれば、それが従来のソフトとは異なるものとなり、比較的簡単に特許になってしまうケースが少なくありません。 つまり、エンジニアの方は日々新しい機能を実装すべく開発業務を行っているわけなので、 知財の観点から言うと、極端なことを言えば日々発明をしている ということになります。当然特許になるかどうかは別の話になりますが、日々の開発業務で自分が発明をしていることに気づいていない、という状況が多分にあるということです。 さらに、 知財担当がこれに気づかなければ、日の目を見ることない埋蔵知財が量産される ということになってしまいます。 では、どのようにしてエンジニアの方が自ら発明をしていることに気づいていないものを発掘し、社内の知的財産として認定していくのか、というのが次の話になります。 面白いアイデアは雑談から生まれる 日々の開発業務の延長で生まれてくる機能や技術を特許化するという活動が基本であることは間違いないのですが、実際にプロダクトに実装するかどうか不確定要素を多く含むアイデアを特許化するという活動は、エンジニアの開発成果物を知的財産として見える化することで、エンジニアの成果が報われる土壌を作るという意味においては大切な活動になってきます。 ただ、どの企業知財部でも発明発掘業務はされていると思いますし、「アイデアは雑談から生まれる」ということは既に周知の事実かもしれません。また、知財活動として特段新しいことをしているわけではないのですが、実際にそれを実行する際の心構えであったり、具体的に気をつけていることについて、事例を通してご紹介したいと思います。 私は、 エンジニアの方というのは、アイデアの卵をもっている という前提に立ってエンジニアの方とコミュニケーションをとるようにしています。 そうすると、「実はアイデアありますよね、なんで言ってくれないんですか〜。」という具合にカジュアルな会話をスタートすることができ、スムーズにブレストを進めることができたりします。大したことではないのですが、 意外とこういう心がけが円滑なコミュニケーションを生み出すきっかけになっている かもしれません。 一方で、エンジニアの方は暇ではありません。 日々の開発では、既存機能の修正や新規機能の開発などの案件に追われるため、頭の中にあるアイデアの卵も埋もれてしまいがちです。 以上のことを踏まえ、エンジニアからアイデアを出してもらうためのブレスト MTG では主に以下の点に気をつけています。 エンジニアの方の負担にならないように短時間に設定すること(長くて 30 分) カジュアルに話しやすい雑談ベースの雰囲気にすること そのために少人数で行うこと 出てきたアイデアを楽しむこと 実際に下図に示すような雑談ベースの会話から、「診療方式を患者と医療機関との間の距離に応じて、対面診療かオンライン診療かを自動的に切り替える」というアイデアが生まれ、実際に特許出願につながりました。 この時にエンジニアの方々からは「とても面白い!」とか「またやりたい!」という前向きなコメントをもらえて、カジュアルベースの雑談効果を実感しました。 このようなブレストを通して一番大きな気づきというのは、エンジニアの皆さん、本当によくプロダクトについて考えているということ。 エンジニアの方々の創意工夫が全て知的財産「権」になるわけではないのですが、知的財産であることには違いありません。 これからは、そういった エンジニアの方の頭の中に埋もれがちな創意・工夫というものが報われるような土壌作りを、知的財産というツールを使って構築していきたい と考えています。 このブログも「特許を対外的にどう見せるか」という実験の1つ ここまでご紹介してきた内容は、知財計画を立てて、立てた計画に沿って知財業務を運用し、運用していく中でアイデア発掘して知財化する、という話にとどまってましたが、実際に取得した知財権をどのように活用して企業価値の向上につなげるのか、ということがこれからの知財部門にとっての重要なミッションになると考えています。 従来は、特許権の独占排他権という性質を使って参入障壁として活用したり、模倣の抑止に活用されるのが常識だったと思いますが、メドレーではこれまでにない新たな活用方法を模索しています。 実はこのブログもそうなのですが、特許を対外的にどうみせるか、ということの1つの実験です。 どういうことかというと、ここまでご紹介してきた 知財活動自体も人の知的活動によって生み出された知的財産であり、このようにブログで発信しなければ単なる埋蔵知財になってしまいます 。 例えば、特許取得についてもただ特許を取ったという事実を公表するのではなく、どうしてそのような特許を取得してどのように活用しているのか、またそこに関連したメンバーは誰でどのような検討があったのか、というストーリーメイキングをすると、特許というものを中心とした1つのドラマが浮き上がってきます。 今回は知財活動のご紹介という趣旨で、特許を中心とした内容になっているため、特許に関心がない人にはあまりささらないコンテンツになっているかもしれませんが、例えば特許と広報との掛け算でストーリーメイキングをすれば 特許に関心がある人だけでなく、広報に関心がある人にも情報がリーチすることになります 。 これは 特許というものが、人材・業務ノウハウ・広報・採用活動等と同列に知的財産である がゆえになせるものです。これからは、知的財産=特許、商標という視点ではなく、知財=目に見えない創造的な活動という捉え方をした上で、企業価値に貢献していく必要があります。 知財部門が、採用、IR、広報、開発、社内 IT といった企業内にある多数の部門とコラボレーションすることでこれまで見たことのない新しい知財活用の形を模索し、 知財を最大活用することによって企業価値を向上させていきたい と考えています。 さいごに ここまで、知財活動の一部をご紹介させていただきましたが、これが知財活動の進め方として正解というわけではなく、企業風土や事業領域によって、知財活動のあり方は異なります。そして、そこを考えながら知財活動を推進していくことが仕事の面白さであり醍醐味とも言えます。新しい知財部のあり方や、新しい知的財産の活用方法を検証しながらも、建設的かつ柔軟に日々の業務を進め、メドレーの事業をしっかりとバックアップしていきたいと考えています。少しでも当社の知財活動が参考になれば幸いです。最後までお付き合い頂きありがとうございました! https://www.medley.jp/jobs/
アバター
はじめに こんにちは、メドレーのコーポレート本部法務コンプライアンス部で、知的財産関連の業務を担当している鬼鞍です。 1 年ほど前に本ブログで「 特許とはなにか 」というテーマで当社における特許啓蒙活動を紹介させて頂いたのですが、思っていた以上に反響があり、「面白い」とか「わかりやすい」という嬉しいコメントも頂戴しました。 私がメドレーに入社して最初のミッションが知財部門の立ち上げだったので、次はぜひ**「ベンチャーでの知財部門の立ち上げ方」**的なテーマでブログ発信したいと思っていたのですが、当初はまだ具体的な社内事例が少なかったため、実績ができた段階で改めて本ブログで発表したいと思っていました。 それから1年以上が経過して実際に特許も取得するなど知財活動としての実績が蓄積されてきたため、これを機会に ベンチャーで知財部門を立ち上げる際にやるべきことについて、実例に触れながら具体的にまとめてみました。 かなり長いコンテンツになってしまいましたが、 フェーズごとの知財活動計画 であったり、 知財ロードマップの作り方 など、 なかなか表に出てこない知財部門立ち上げストーリーの具体的事例を惜しみなく書かせていただいております ので、ぜひご一読いただき、お役に立てれば幸いです。 知財は開発チームとの信頼関係の構築がまず第一 先ほどは知財部門を立ち上げストーリの中で、知財活動計画とか、知財ロードマップであるとか少し格好つけたような文字が羅列していましたが、実は私が知財専任として初めに配置されて最初にやったことはそんな大それたことではありませんでした。 最初になにをやったかというと、 エンジニアの方とランチに行く、あるいは飲みに行く 、ということでした。当時はまだ Covid-19 が蔓延する前だったので気軽にそういうことができました。入社して最初の 1 ヶ月くらいは週 2 回くらいのペースで誰かとランチか飲みにいくということをしていたと思います。 飲みに行く、というとふざけているとか思われるかもしれませんが、知財部門の立ち上げストーリーを語る上で、このプロセスは無視できないほど重要だと考えています。先に断っておくと私は酒好きでもなければ、家でも一切飲みません。 当たり前のことなのですが、知財というのはプロダクトを生み出す開発組織があって初めて価値を発揮するものです。知財はあくまで事業や開発をサポートするところであって、それ単体で存在意義を発揮することはほぼありません。特に特許活動については開発チームとの連携が非常に重要です。 一緒にご飯を食べることが信頼関係を構築する上で最良の方法、とまでは言いませんが、少なくともご飯を食べない人はいないわけで、時間を無駄にしている感もなければ、オフィスの外で会えば仕事以外のことを話題にし易いので互いの趣味嗜好を知ることができます。みなさんもそうだと思いますが、通常、仲の悪い人とは一緒に食事はしないと思います。 特に、この後にもご紹介する 社内のアイデアを発掘するという発明発掘活動は、話しやすいカジュアルな雰囲気で雑談のような感じでブレストするというのが非常に重要 で、このときに相手がどんな人間かを互いに知っているか、知らないかでは大きな差がでてきます。 また更に、この後にご紹介する知財計画立案や知財ロードマップを作成することも当然重要なのですが、結局仕事というのは人と人との間を思考がやりとりされて実現化されていくものなので、人間関係・信頼関係が全ての基礎であり、実は一番大切にするべきものだと思います。 これは何も知財に限ったことではなく、部門を跨いで仕事をする上で信頼関係は非常に重要ですし、知財の観点で見ても 円滑なコミュニケーションが結果的にいい発明やアイデアを生み出すための土壌づくりに貢献する ものだと考えています。 知財活動計画は、事業の成長フェーズに合わせて設定する 知財部門の設立にあたって、エンジニアとのコミュニケーションと並行して進めたのが、知財活動計画の立案でした。知財活動とは、知財サイクルをうまく事業サイクルにインストールしてサイクルを循環させることであり、更にはこのサイクルが循環するための運用基盤を構築することを指します。また、ここでいう知財サイクルとは、例えば、社内に埋もれたアイデアを発掘し(創発)→ それを知財権化し(保護)→ 権利化した知財を活用し(活用)→ 活用した知財で得られた利益や結果を創発支援に適用できる形に変換する、というものです。 このような知財サイクルを、何の計画や戦略もなしに事業サイクルへインストールしようとするとうまくいきません。なぜなら、事業というのは日々成長し変化していくもので、 事業の成長に応じて知財活動の目的や課題も変化するからです 。 例えば、子会社が増えて事業規模が拡大すると、その子会社の事業領域で既に多くの特許出願がされている場合には、発明発掘活動よりもまずは法的リスクを回避することを最優先にして、他社の特許調査をしっかりやってからプロダクトをリリースする仕組みを作ろう、ということになります。あるいは、事業の成長に伴いプロダクトが成熟してきた場面では、似たようなプロダクトが乱立する結果、尖った機能や最先端技術で差別化をするようになるため、こうした場合には先行した特許取得が重要な目的になってきます。 このように、事業の成長フェーズに応じた知財活動の計画を立案するべく、まずはメドレーのミッションおよびそのミッションを実現するためのプロダクトの性質を把握する必要がありました。 メドレーは「医療ヘルスケアの未来をつくる」というミッションを掲げ、インターネットテクノロジーによる医療のあり方の変革を目指す企業です。 このミッションと向き合いつつ、 事業プラットフォームの成長フェーズごとに知財活動の方向性・目的を3段階にわけて設定し、成長フェーズごとに知財活動計画を立案しました 。 例えば、プラットフォームの創設期においては新規ユーザ獲得が重要となり、使い勝手のいいシンプルな機能に開発の方向性が向いているため、独自性のある技術を特許化するというよりも、まずは守りを固めるべく知財権侵害のリスクを最小限にして法的安定性の確保に軸足を置いた知財活動を推進します。 このような計画は、知財の観点で一方的にできるものではありません。 知財活動の計画に際しては、開発チームを統括するマネージャにプロダクト開発の方向性をヒアリングして特許取得の判断基準を検討したり、経営層に将来の事業の方向性についてヒアリングをして長期志向で目標設定したりして、ブラッシュアップしていきました。 そういった検討や議論を重ねていくうちに自然と、その企業風土や事業ミッションにあった知財活動計画というものができあがってくるのだと思います。 知財活動ロードマップで現在位置を把握しつつ定期的に軌道修正する 長期的な方向性を可視化したら次はそれをどう実行するかという実行計画が必要になってきました。 そもそも自分たちは今何ができていて、何ができていないのか。またどのようなことを今後やっていかなければならないのかが不明確だよね、という話になり、それを具体的に俯瞰するための知財ロードマッピングというものを作成しました。 縦軸に知的財産の種類、横軸に時間軸であるフェーズを設定して、知財活動の内容に過不足ないかをチェックできるようにしました。 定期的に内容を見直してメドレーの事業規模にあった形に修正しながら自分達の現在位置を把握し、今後の方向性を見定めるためのツールとして有効でした。 このようなロードマップがあれば、知財担当者の立場からすると、実績が可視化されるため知財活動を推進していく上での達成感もありますし、逆に知財担当者を評価する立場からしても、明確な評価基準がない中で、知財担当者の評価をする際に役に立ちます。 以上のように、何もないところから他部門と関わりながら知財活動計画を立案し、ロードマップを達成していくのは、知財部門を立ち上げるという業務の面白さの1つでもあります。 初期段階から長期視点で知財素人でも回せる業務運用作りをする 1 人法務や 1 人知財担当という状況は、遅かれ早かれ属人化という問題が必ずやってきます。 属人化は組織運営上、健全ではありません。 そのため、 知財に関する知識がない人間でも、誰でもその業務を回すことができるような状態をつくる ために、知財部門立ち上げの初期段階から、自分が担当している業務でルーチン化できるものは全て仕組み化してドキュメントに落とし込むようにしていました。 例えば「怪しい特許を見つけた場合の動き方」、「発明報奨金の決定プロセス」、「他社特許調査及び発明発掘のハイブリッド調査の進め方」などなど、ありとあらゆるプロセス業務を全てフロー図や概念図を作成し、誰か見てもわかる業務ということに留意しています。 現状はまだ引き継ぎや新たな知財部員の登用という話がないので、わかりやすい効果を発揮しているわけではないのですが、他部門からの問い合わせがあった際は資料だけ渡せば理解してもらえるので、コミュニケーションの効率化には貢献していると思います。 どのような特許を取得すべきなのか 企業としてどのような特許を取得していくべきか、ということは「知財を最大限に活用する」という知財の出口から考えていく上で、重要な事項になります。 そしてどのような特許を取得するかは、事業領域や事業ミッションに沿って設定されるべきです。 メドレーは、オープンプラットフォームによる医療のあり方の変革を目指している企業なので、開発チームから上がってきたアイデアについては、 プラットフォーム事業を適切に運営していく上で必要な技術かどうか 、という観点で特許化するかどうかを判断しています。 例えば、最近特許を取得した**医療データの管理方法( 特許第 6921177 号 )**というものがあります。これは、アプリ端末から入力された患者データと、医療機関の各業務システムから入力された患者データという 2 つの類似したデータをシームレスに管理するために、両方のデータを 2 つのデータベース上で統合管理することにより、データ管理の責任分担の明確化及び厳格な情報管理を可能にするというものです。 仕組みとしてはとてもシンプルなのですが、 患者の持つ端末と、医科・歯科・調剤等の各業務システムをシームレスに連携させる仕組みが、医療プラットフォームを適切に運営していく上で必要な技術だと判断 したため、特許化しました。 また、上記事例のように実際にプロダクトに実装されている技術を特許化したものだけでなく、実際にプロダクトに実装するかわからないけれども将来を見据えた先見的な特許として、**ブロックチェーンを用いた電子処方箋の管理方法( 特許第 6936763 号 )**という特許を取得しています。 これは、ブロックチェーンを用いたアクセス権の管理機能として全体的に秘密状態を保ちながら電子処方箋を管理するためのアクセス制御に関する仕組みで、ブロックチェーンデータベース上において処方箋データと患者データとを紐づけて、医師端末からの患者レコードへの一時的なアクセス権限を付与し、会計終了時に患者端末の操作に応じて医師端末へのアクセス権限を剥奪するというものです。 このような特許を取得した背景には、 マーケットを独占して権利主張をするためというよりも、メドレーが考える未来のプロダクトビジョンであったり、開発部門の先見的な創作活動を対外的に発信したい 、という思いがあります。 メドレーは、顧客利用率の高いプロダクト、機能拡張性の高いプロダクトの開発を推進しているため、**独創的で最新技術を取り入れた尖った機能を目指すというよりはユーザにとって使いやすいシンプルさが多く取り入れられています。**また、それだけではなく、前述したブロックチェーンと電子処方箋との組み合わせ技術の先見的な特許取得のように、10 年先を見据えた未来のプロダクトのあり方についても日々追求しています。 特許を身近に感じてもらうためのユニークな話も取り入れて社内に啓蒙する いい特許を生み出すためには地道な社内の啓蒙活動も重要です。 メドレーでは、開発チーム間の技術格差の是正や、技術情報の共有による活性化を目的として、エンジニアが日々の業務で扱っている技術や取り組みについて共有するテックランチという勉強会が定期的に開催されています。 先日そのテックランチで、社内のエンジニアの方達に少しでも特許を身近に感じてもらおうと、「特許の頭体操」というコーナーを設けて、実際に頭を使って体験してもらいました。 下の例では、おたまとスプーンとの違いを考えようというお題を通して、ある物の特徴を把握する際に、 「違い」から「もの」を観察すると、その物の特徴が浮き出てくる 、というメッセージが含まれています。 おたまもスプーンも対象物をすくい上げるという機能においては共通しているのですが、おたまの先端のお皿部分とスプーンの先端のお皿部分とは、その形状が異なります。スプーンのお皿部分は楕円形ですが、おたまのお皿部分は円形になっています。 つまり、おたまの特徴は、特許的にいうと「その先端にあるお皿部分が円形の開口を有した半球状で、対象物をすくうための所定の深さを有している」ということになります。 この辺はちょっと固苦しい表現が続いたせいもあり、「難しい…」というコメントをもらいました。馴染みのない人にとってはただただ面倒な作業だと思います。 しかし、ここで言いたかったのは、 これはあくまで構造物についての話であって、ソフトウェアの特徴を把握するということはそんなに難しいことではないのだよ 、ということでした。 ソフトウェアの場合は、ちょっとした機能を追加すれば、それが従来のソフトとは異なるものとなり、比較的簡単に特許になってしまうケースが少なくありません。 つまり、エンジニアの方は日々新しい機能を実装すべく開発業務を行っているわけなので、 知財の観点から言うと、極端なことを言えば日々発明をしている ということになります。当然特許になるかどうかは別の話になりますが、日々の開発業務で自分が発明をしていることに気づいていない、という状況が多分にあるということです。 さらに、 知財担当がこれに気づかなければ、日の目を見ることない埋蔵知財が量産される ということになってしまいます。 では、どのようにしてエンジニアの方が自ら発明をしていることに気づいていないものを発掘し、社内の知的財産として認定していくのか、というのが次の話になります。 面白いアイデアは雑談から生まれる 日々の開発業務の延長で生まれてくる機能や技術を特許化するという活動が基本であることは間違いないのですが、実際にプロダクトに実装するかどうか不確定要素を多く含むアイデアを特許化するという活動は、エンジニアの開発成果物を知的財産として見える化することで、エンジニアの成果が報われる土壌を作るという意味においては大切な活動になってきます。 ただ、どの企業知財部でも発明発掘業務はされていると思いますし、「アイデアは雑談から生まれる」ということは既に周知の事実かもしれません。また、知財活動として特段新しいことをしているわけではないのですが、実際にそれを実行する際の心構えであったり、具体的に気をつけていることについて、事例を通してご紹介したいと思います。 私は、 エンジニアの方というのは、アイデアの卵をもっている という前提に立ってエンジニアの方とコミュニケーションをとるようにしています。 そうすると、「実はアイデアありますよね、なんで言ってくれないんですか〜。」という具合にカジュアルな会話をスタートすることができ、スムーズにブレストを進めることができたりします。大したことではないのですが、 意外とこういう心がけが円滑なコミュニケーションを生み出すきっかけになっている かもしれません。 一方で、エンジニアの方は暇ではありません。 日々の開発では、既存機能の修正や新規機能の開発などの案件に追われるため、頭の中にあるアイデアの卵も埋もれてしまいがちです。 以上のことを踏まえ、エンジニアからアイデアを出してもらうためのブレスト MTG では主に以下の点に気をつけています。 エンジニアの方の負担にならないように短時間に設定すること(長くて 30 分) カジュアルに話しやすい雑談ベースの雰囲気にすること そのために少人数で行うこと 出てきたアイデアを楽しむこと 実際に下図に示すような雑談ベースの会話から、「診療方式を患者と医療機関との間の距離に応じて、対面診療かオンライン診療かを自動的に切り替える」というアイデアが生まれ、実際に特許出願につながりました。 この時にエンジニアの方々からは「とても面白い!」とか「またやりたい!」という前向きなコメントをもらえて、カジュアルベースの雑談効果を実感しました。 このようなブレストを通して一番大きな気づきというのは、エンジニアの皆さん、本当によくプロダクトについて考えているということ。 エンジニアの方々の創意工夫が全て知的財産「権」になるわけではないのですが、知的財産であることには違いありません。 これからは、そういった エンジニアの方の頭の中に埋もれがちな創意・工夫というものが報われるような土壌作りを、知的財産というツールを使って構築していきたい と考えています。 このブログも「特許を対外的にどう見せるか」という実験の1つ ここまでご紹介してきた内容は、知財計画を立てて、立てた計画に沿って知財業務を運用し、運用していく中でアイデア発掘して知財化する、という話にとどまってましたが、実際に取得した知財権をどのように活用して企業価値の向上につなげるのか、ということがこれからの知財部門にとっての重要なミッションになると考えています。 従来は、特許権の独占排他権という性質を使って参入障壁として活用したり、模倣の抑止に活用されるのが常識だったと思いますが、メドレーではこれまでにない新たな活用方法を模索しています。 実はこのブログもそうなのですが、特許を対外的にどうみせるか、ということの1つの実験です。 どういうことかというと、ここまでご紹介してきた 知財活動自体も人の知的活動によって生み出された知的財産であり、このようにブログで発信しなければ単なる埋蔵知財になってしまいます 。 例えば、特許取得についてもただ特許を取ったという事実を公表するのではなく、どうしてそのような特許を取得してどのように活用しているのか、またそこに関連したメンバーは誰でどのような検討があったのか、というストーリーメイキングをすると、特許というものを中心とした1つのドラマが浮き上がってきます。 今回は知財活動のご紹介という趣旨で、特許を中心とした内容になっているため、特許に関心がない人にはあまりささらないコンテンツになっているかもしれませんが、例えば特許と広報との掛け算でストーリーメイキングをすれば 特許に関心がある人だけでなく、広報に関心がある人にも情報がリーチすることになります 。 これは 特許というものが、人材・業務ノウハウ・広報・採用活動等と同列に知的財産である がゆえになせるものです。これからは、知的財産=特許、商標という視点ではなく、知財=目に見えない創造的な活動という捉え方をした上で、企業価値に貢献していく必要があります。 知財部門が、採用、IR、広報、開発、社内 IT といった企業内にある多数の部門とコラボレーションすることでこれまで見たことのない新しい知財活用の形を模索し、 知財を最大活用することによって企業価値を向上させていきたい と考えています。 さいごに ここまで、知財活動の一部をご紹介させていただきましたが、これが知財活動の進め方として正解というわけではなく、企業風土や事業領域によって、知財活動のあり方は異なります。そして、そこを考えながら知財活動を推進していくことが仕事の面白さであり醍醐味とも言えます。新しい知財部のあり方や、新しい知的財産の活用方法を検証しながらも、建設的かつ柔軟に日々の業務を進め、メドレーの事業をしっかりとバックアップしていきたいと考えています。少しでも当社の知財活動が参考になれば幸いです。最後までお付き合い頂きありがとうございました! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに こんにちは、メドレーのコーポレート本部法務コンプライアンス部で、知的財産関連の業務を担当している鬼鞍です。 1 年ほど前に本ブログで「 特許とはなにか 」というテーマで当社における特許啓蒙活動を紹介させて頂いたのですが、思っていた以上に反響があり、「面白い」とか「わかりやすい」という嬉しいコメントも頂戴しました。 私がメドレーに入社して最初のミッションが知財部門の立ち上げだったので、次はぜひ**「ベンチャーでの知財部門の立ち上げ方」**的なテーマでブログ発信したいと思っていたのですが、当初はまだ具体的な社内事例が少なかったため、実績ができた段階で改めて本ブログで発表したいと思っていました。 それから1年以上が経過して実際に特許も取得するなど知財活動としての実績が蓄積されてきたため、これを機会に ベンチャーで知財部門を立ち上げる際にやるべきことについて、実例に触れながら具体的にまとめてみました。 かなり長いコンテンツになってしまいましたが、 フェーズごとの知財活動計画 であったり、 知財ロードマップの作り方 など、 なかなか表に出てこない知財部門立ち上げストーリーの具体的事例を惜しみなく書かせていただいております ので、ぜひご一読いただき、お役に立てれば幸いです。 知財は開発チームとの信頼関係の構築がまず第一 先ほどは知財部門を立ち上げストーリの中で、知財活動計画とか、知財ロードマップであるとか少し格好つけたような文字が羅列していましたが、実は私が知財専任として初めに配置されて最初にやったことはそんな大それたことではありませんでした。 最初になにをやったかというと、 エンジニアの方とランチに行く、あるいは飲みに行く 、ということでした。当時はまだ Covid-19 が蔓延する前だったので気軽にそういうことができました。入社して最初の 1 ヶ月くらいは週 2 回くらいのペースで誰かとランチか飲みにいくということをしていたと思います。 飲みに行く、というとふざけているとか思われるかもしれませんが、知財部門の立ち上げストーリーを語る上で、このプロセスは無視できないほど重要だと考えています。先に断っておくと私は酒好きでもなければ、家でも一切飲みません。 当たり前のことなのですが、知財というのはプロダクトを生み出す開発組織があって初めて価値を発揮するものです。知財はあくまで事業や開発をサポートするところであって、それ単体で存在意義を発揮することはほぼありません。特に特許活動については開発チームとの連携が非常に重要です。 一緒にご飯を食べることが信頼関係を構築する上で最良の方法、とまでは言いませんが、少なくともご飯を食べない人はいないわけで、時間を無駄にしている感もなければ、オフィスの外で会えば仕事以外のことを話題にし易いので互いの趣味嗜好を知ることができます。みなさんもそうだと思いますが、通常、仲の悪い人とは一緒に食事はしないと思います。 特に、この後にもご紹介する 社内のアイデアを発掘するという発明発掘活動は、話しやすいカジュアルな雰囲気で雑談のような感じでブレストするというのが非常に重要 で、このときに相手がどんな人間かを互いに知っているか、知らないかでは大きな差がでてきます。 また更に、この後にご紹介する知財計画立案や知財ロードマップを作成することも当然重要なのですが、結局仕事というのは人と人との間を思考がやりとりされて実現化されていくものなので、人間関係・信頼関係が全ての基礎であり、実は一番大切にするべきものだと思います。 これは何も知財に限ったことではなく、部門を跨いで仕事をする上で信頼関係は非常に重要ですし、知財の観点で見ても 円滑なコミュニケーションが結果的にいい発明やアイデアを生み出すための土壌づくりに貢献する ものだと考えています。 知財活動計画は、事業の成長フェーズに合わせて設定する 知財部門の設立にあたって、エンジニアとのコミュニケーションと並行して進めたのが、知財活動計画の立案でした。知財活動とは、知財サイクルをうまく事業サイクルにインストールしてサイクルを循環させることであり、更にはこのサイクルが循環するための運用基盤を構築することを指します。また、ここでいう知財サイクルとは、例えば、社内に埋もれたアイデアを発掘し(創発)→ それを知財権化し(保護)→ 権利化した知財を活用し(活用)→ 活用した知財で得られた利益や結果を創発支援に適用できる形に変換する、というものです。 このような知財サイクルを、何の計画や戦略もなしに事業サイクルへインストールしようとするとうまくいきません。なぜなら、事業というのは日々成長し変化していくもので、 事業の成長に応じて知財活動の目的や課題も変化するからです 。 例えば、子会社が増えて事業規模が拡大すると、その子会社の事業領域で既に多くの特許出願がされている場合には、発明発掘活動よりもまずは法的リスクを回避することを最優先にして、他社の特許調査をしっかりやってからプロダクトをリリースする仕組みを作ろう、ということになります。あるいは、事業の成長に伴いプロダクトが成熟してきた場面では、似たようなプロダクトが乱立する結果、尖った機能や最先端技術で差別化をするようになるため、こうした場合には先行した特許取得が重要な目的になってきます。 このように、事業の成長フェーズに応じた知財活動の計画を立案するべく、まずはメドレーのミッションおよびそのミッションを実現するためのプロダクトの性質を把握する必要がありました。 メドレーは「医療ヘルスケアの未来をつくる」というミッションを掲げ、インターネットテクノロジーによる医療のあり方の変革を目指す企業です。 このミッションと向き合いつつ、 事業プラットフォームの成長フェーズごとに知財活動の方向性・目的を3段階にわけて設定し、成長フェーズごとに知財活動計画を立案しました 。 例えば、プラットフォームの創設期においては新規ユーザ獲得が重要となり、使い勝手のいいシンプルな機能に開発の方向性が向いているため、独自性のある技術を特許化するというよりも、まずは守りを固めるべく知財権侵害のリスクを最小限にして法的安定性の確保に軸足を置いた知財活動を推進します。 このような計画は、知財の観点で一方的にできるものではありません。 知財活動の計画に際しては、開発チームを統括するマネージャにプロダクト開発の方向性をヒアリングして特許取得の判断基準を検討したり、経営層に将来の事業の方向性についてヒアリングをして長期志向で目標設定したりして、ブラッシュアップしていきました。 そういった検討や議論を重ねていくうちに自然と、その企業風土や事業ミッションにあった知財活動計画というものができあがってくるのだと思います。 知財活動ロードマップで現在位置を把握しつつ定期的に軌道修正する 長期的な方向性を可視化したら次はそれをどう実行するかという実行計画が必要になってきました。 そもそも自分たちは今何ができていて、何ができていないのか。またどのようなことを今後やっていかなければならないのかが不明確だよね、という話になり、それを具体的に俯瞰するための知財ロードマッピングというものを作成しました。 縦軸に知的財産の種類、横軸に時間軸であるフェーズを設定して、知財活動の内容に過不足ないかをチェックできるようにしました。 定期的に内容を見直してメドレーの事業規模にあった形に修正しながら自分達の現在位置を把握し、今後の方向性を見定めるためのツールとして有効でした。 このようなロードマップがあれば、知財担当者の立場からすると、実績が可視化されるため知財活動を推進していく上での達成感もありますし、逆に知財担当者を評価する立場からしても、明確な評価基準がない中で、知財担当者の評価をする際に役に立ちます。 以上のように、何もないところから他部門と関わりながら知財活動計画を立案し、ロードマップを達成していくのは、知財部門を立ち上げるという業務の面白さの1つでもあります。 初期段階から長期視点で知財素人でも回せる業務運用作りをする 1 人法務や 1 人知財担当という状況は、遅かれ早かれ属人化という問題が必ずやってきます。 属人化は組織運営上、健全ではありません。 そのため、 知財に関する知識がない人間でも、誰でもその業務を回すことができるような状態をつくる ために、知財部門立ち上げの初期段階から、自分が担当している業務でルーチン化できるものは全て仕組み化してドキュメントに落とし込むようにしていました。 例えば「怪しい特許を見つけた場合の動き方」、「発明報奨金の決定プロセス」、「他社特許調査及び発明発掘のハイブリッド調査の進め方」などなど、ありとあらゆるプロセス業務を全てフロー図や概念図を作成し、誰か見てもわかる業務ということに留意しています。 現状はまだ引き継ぎや新たな知財部員の登用という話がないので、わかりやすい効果を発揮しているわけではないのですが、他部門からの問い合わせがあった際は資料だけ渡せば理解してもらえるので、コミュニケーションの効率化には貢献していると思います。 どのような特許を取得すべきなのか 企業としてどのような特許を取得していくべきか、ということは「知財を最大限に活用する」という知財の出口から考えていく上で、重要な事項になります。 そしてどのような特許を取得するかは、事業領域や事業ミッションに沿って設定されるべきです。 メドレーは、オープンプラットフォームによる医療のあり方の変革を目指している企業なので、開発チームから上がってきたアイデアについては、 プラットフォーム事業を適切に運営していく上で必要な技術かどうか 、という観点で特許化するかどうかを判断しています。 例えば、最近特許を取得した**医療データの管理方法( 特許第 6921177 号 )**というものがあります。これは、アプリ端末から入力された患者データと、医療機関の各業務システムから入力された患者データという 2 つの類似したデータをシームレスに管理するために、両方のデータを 2 つのデータベース上で統合管理することにより、データ管理の責任分担の明確化及び厳格な情報管理を可能にするというものです。 仕組みとしてはとてもシンプルなのですが、 患者の持つ端末と、医科・歯科・調剤等の各業務システムをシームレスに連携させる仕組みが、医療プラットフォームを適切に運営していく上で必要な技術だと判断 したため、特許化しました。 また、上記事例のように実際にプロダクトに実装されている技術を特許化したものだけでなく、実際にプロダクトに実装するかわからないけれども将来を見据えた先見的な特許として、**ブロックチェーンを用いた電子処方箋の管理方法( 特許第 6936763 号 )**という特許を取得しています。 これは、ブロックチェーンを用いたアクセス権の管理機能として全体的に秘密状態を保ちながら電子処方箋を管理するためのアクセス制御に関する仕組みで、ブロックチェーンデータベース上において処方箋データと患者データとを紐づけて、医師端末からの患者レコードへの一時的なアクセス権限を付与し、会計終了時に患者端末の操作に応じて医師端末へのアクセス権限を剥奪するというものです。 このような特許を取得した背景には、 マーケットを独占して権利主張をするためというよりも、メドレーが考える未来のプロダクトビジョンであったり、開発部門の先見的な創作活動を対外的に発信したい 、という思いがあります。 メドレーは、顧客利用率の高いプロダクト、機能拡張性の高いプロダクトの開発を推進しているため、**独創的で最新技術を取り入れた尖った機能を目指すというよりはユーザにとって使いやすいシンプルさが多く取り入れられています。**また、それだけではなく、前述したブロックチェーンと電子処方箋との組み合わせ技術の先見的な特許取得のように、10 年先を見据えた未来のプロダクトのあり方についても日々追求しています。 特許を身近に感じてもらうためのユニークな話も取り入れて社内に啓蒙する いい特許を生み出すためには地道な社内の啓蒙活動も重要です。 メドレーでは、開発チーム間の技術格差の是正や、技術情報の共有による活性化を目的として、エンジニアが日々の業務で扱っている技術や取り組みについて共有するテックランチという勉強会が定期的に開催されています。 先日そのテックランチで、社内のエンジニアの方達に少しでも特許を身近に感じてもらおうと、「特許の頭体操」というコーナーを設けて、実際に頭を使って体験してもらいました。 下の例では、おたまとスプーンとの違いを考えようというお題を通して、ある物の特徴を把握する際に、 「違い」から「もの」を観察すると、その物の特徴が浮き出てくる 、というメッセージが含まれています。 おたまもスプーンも対象物をすくい上げるという機能においては共通しているのですが、おたまの先端のお皿部分とスプーンの先端のお皿部分とは、その形状が異なります。スプーンのお皿部分は楕円形ですが、おたまのお皿部分は円形になっています。 つまり、おたまの特徴は、特許的にいうと「その先端にあるお皿部分が円形の開口を有した半球状で、対象物をすくうための所定の深さを有している」ということになります。 この辺はちょっと固苦しい表現が続いたせいもあり、「難しい…」というコメントをもらいました。馴染みのない人にとってはただただ面倒な作業だと思います。 しかし、ここで言いたかったのは、 これはあくまで構造物についての話であって、ソフトウェアの特徴を把握するということはそんなに難しいことではないのだよ 、ということでした。 ソフトウェアの場合は、ちょっとした機能を追加すれば、それが従来のソフトとは異なるものとなり、比較的簡単に特許になってしまうケースが少なくありません。 つまり、エンジニアの方は日々新しい機能を実装すべく開発業務を行っているわけなので、 知財の観点から言うと、極端なことを言えば日々発明をしている ということになります。当然特許になるかどうかは別の話になりますが、日々の開発業務で自分が発明をしていることに気づいていない、という状況が多分にあるということです。 さらに、 知財担当がこれに気づかなければ、日の目を見ることない埋蔵知財が量産される ということになってしまいます。 では、どのようにしてエンジニアの方が自ら発明をしていることに気づいていないものを発掘し、社内の知的財産として認定していくのか、というのが次の話になります。 面白いアイデアは雑談から生まれる 日々の開発業務の延長で生まれてくる機能や技術を特許化するという活動が基本であることは間違いないのですが、実際にプロダクトに実装するかどうか不確定要素を多く含むアイデアを特許化するという活動は、エンジニアの開発成果物を知的財産として見える化することで、エンジニアの成果が報われる土壌を作るという意味においては大切な活動になってきます。 ただ、どの企業知財部でも発明発掘業務はされていると思いますし、「アイデアは雑談から生まれる」ということは既に周知の事実かもしれません。また、知財活動として特段新しいことをしているわけではないのですが、実際にそれを実行する際の心構えであったり、具体的に気をつけていることについて、事例を通してご紹介したいと思います。 私は、 エンジニアの方というのは、アイデアの卵をもっている という前提に立ってエンジニアの方とコミュニケーションをとるようにしています。 そうすると、「実はアイデアありますよね、なんで言ってくれないんですか〜。」という具合にカジュアルな会話をスタートすることができ、スムーズにブレストを進めることができたりします。大したことではないのですが、 意外とこういう心がけが円滑なコミュニケーションを生み出すきっかけになっている かもしれません。 一方で、エンジニアの方は暇ではありません。 日々の開発では、既存機能の修正や新規機能の開発などの案件に追われるため、頭の中にあるアイデアの卵も埋もれてしまいがちです。 以上のことを踏まえ、エンジニアからアイデアを出してもらうためのブレスト MTG では主に以下の点に気をつけています。 エンジニアの方の負担にならないように短時間に設定すること(長くて 30 分) カジュアルに話しやすい雑談ベースの雰囲気にすること そのために少人数で行うこと 出てきたアイデアを楽しむこと 実際に下図に示すような雑談ベースの会話から、「診療方式を患者と医療機関との間の距離に応じて、対面診療かオンライン診療かを自動的に切り替える」というアイデアが生まれ、実際に特許出願につながりました。 この時にエンジニアの方々からは「とても面白い!」とか「またやりたい!」という前向きなコメントをもらえて、カジュアルベースの雑談効果を実感しました。 このようなブレストを通して一番大きな気づきというのは、エンジニアの皆さん、本当によくプロダクトについて考えているということ。 エンジニアの方々の創意工夫が全て知的財産「権」になるわけではないのですが、知的財産であることには違いありません。 これからは、そういった エンジニアの方の頭の中に埋もれがちな創意・工夫というものが報われるような土壌作りを、知的財産というツールを使って構築していきたい と考えています。 このブログも「特許を対外的にどう見せるか」という実験の1つ ここまでご紹介してきた内容は、知財計画を立てて、立てた計画に沿って知財業務を運用し、運用していく中でアイデア発掘して知財化する、という話にとどまってましたが、実際に取得した知財権をどのように活用して企業価値の向上につなげるのか、ということがこれからの知財部門にとっての重要なミッションになると考えています。 従来は、特許権の独占排他権という性質を使って参入障壁として活用したり、模倣の抑止に活用されるのが常識だったと思いますが、メドレーではこれまでにない新たな活用方法を模索しています。 実はこのブログもそうなのですが、特許を対外的にどうみせるか、ということの1つの実験です。 どういうことかというと、ここまでご紹介してきた 知財活動自体も人の知的活動によって生み出された知的財産であり、このようにブログで発信しなければ単なる埋蔵知財になってしまいます 。 例えば、特許取得についてもただ特許を取ったという事実を公表するのではなく、どうしてそのような特許を取得してどのように活用しているのか、またそこに関連したメンバーは誰でどのような検討があったのか、というストーリーメイキングをすると、特許というものを中心とした1つのドラマが浮き上がってきます。 今回は知財活動のご紹介という趣旨で、特許を中心とした内容になっているため、特許に関心がない人にはあまりささらないコンテンツになっているかもしれませんが、例えば特許と広報との掛け算でストーリーメイキングをすれば 特許に関心がある人だけでなく、広報に関心がある人にも情報がリーチすることになります 。 これは 特許というものが、人材・業務ノウハウ・広報・採用活動等と同列に知的財産である がゆえになせるものです。これからは、知的財産=特許、商標という視点ではなく、知財=目に見えない創造的な活動という捉え方をした上で、企業価値に貢献していく必要があります。 知財部門が、採用、IR、広報、開発、社内 IT といった企業内にある多数の部門とコラボレーションすることでこれまで見たことのない新しい知財活用の形を模索し、 知財を最大活用することによって企業価値を向上させていきたい と考えています。 さいごに ここまで、知財活動の一部をご紹介させていただきましたが、これが知財活動の進め方として正解というわけではなく、企業風土や事業領域によって、知財活動のあり方は異なります。そして、そこを考えながら知財活動を推進していくことが仕事の面白さであり醍醐味とも言えます。新しい知財部のあり方や、新しい知的財産の活用方法を検証しながらも、建設的かつ柔軟に日々の業務を進め、メドレーの事業をしっかりとバックアップしていきたいと考えています。少しでも当社の知財活動が参考になれば幸いです。最後までお付き合い頂きありがとうございました! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター