Dockerの有償プランを契約して運用するまでの話

こんにちは。弥生の内山です。
みなさまご存知の通り、2022年1月31日以降、一定以上の規模の組織でDocker Desktopを利用するには有償プランの契約が必要となります。

www.docker.com

弥生では、検討の結果、Dockerを利用する開発者全員分のライセンスを購入し運用することにしました。
しかし、実際に有償プランを使おう!となったとき、具体的に何をしたらよいのかという情報がWeb上にほとんど見つからず、手探りで作業を進めることになりました。
現時点で、ようやく運用の形が見えてきたため、この記事では契約から運用までの具体的な考慮ポイントをご紹介したいと思います。
無償利用の猶予期間終了が迫っている中、有償プランの利用について困っている方の助けになれば幸いです。

(なお、この記事は2022/01/20時点で弥生内で検討・試行した内容に基づいて作成したものであり、Docker社の提供するサービスについて正確な情報を提供することを目的としたものではありません。契約を行われる際は十分な調査を行った上でご判断ください)

なぜ有償プランを契約するのか

Docker Desktopが使いたい

有償プランを契約する目的は、開発メンバーがDocker Desktopを使えるようにすること、ほぼそれだけです。
「Docker Desktopを利用せず、無償で利用できるツールの範囲でDockerを使い続ければよいのでは?」という考え方もあります。
実際、Web上にはDocker Desktopを使わずにDockerを使い続けるための手順が多数見つかります。
そのようにして有償プランを契約せずにDockerを利用し続けるという選択はありだと思います。
ですが、我々は有償プランを契約してDocker Desktopを利用した方がよいと考えました。
理由は以下です。

Dockerまわりのセットアップやトラブルシュートが簡単であるため

Docker Desktopは、それをインストールするだけで、Dockerの利用に必要なセットアップがほとんど完了します。
また、GUIのツール上から、稼働中のコンテナやpullしたイメージを管理できるため、DockerのCUI操作に不慣れなメンバーでもトラブルシュートが比較的容易です。
実際の開発チームでは、スキルや経験が様々なメンバーが参加しているため、簡単にセットアップやトラブルシュートが可能であるというメリットは大きいと考えます。

Windowsコンテナを利用したい場合、Docker Desktopがないと厳しそうであるため

弥生の新規プロダクトの開発では、Windowsコンテナを利用して機能を実装する可能性があります。
Windows 10/11にてWindowsコンテナを利用する場合、Microsoft公式の手順では、Docker Desktopをインストールする方法が案内されています
Web上を探せば、Docker Desktopを利用せずにWindowsコンテナを利用する手順も見つかりはするのですが、多少複雑な手順となりますし、またいつまでもその手順が有効とは限りません。
前述の理由と近いですが、開発メンバーがトラブル無く開発を続けられることを重視するならば、やはり公式の手順通り、Docker DesktopをインストールしてWindowsコンテナを利用した方がよいと考えました。

契約・運用のポイント

プラン選択は"Team"プランがよさそう

2022年1月現在、Dockerの有償プランには、"Pro"、"Team"、"Business"という3つのプランがあります。
どのプランでもDocker Desktopは利用できるため、一番安い"Pro"プランを選択したくなるのですが、企業で利用する場合、"Team"プランを選択するのが現実的だと考えています。

www.docker.com

企業でソフトウェアライセンスを管理・運用する場合、「必要な分を一括で購入し、必要なメンバーに付与し、不要なメンバーからは回収する」というようなオペレーションができないと、管理が面倒すぎて運用が難しいと思います。
"Pro"プランは、個人の商用開発をターゲットとしているためか、Docker ID(=ユーザーアカウント)のそれぞれにおいて支払情報(クレカ)を紐付けて支払いを行うような設計になっているようです。
5人以下程度の組織ならばどうにか運用できるかもしれませんが、それ以上となると管理は難しいでしょう。
"Team"プランであれば、"Seats"という形でライセンスがプールされ、個別のメンバーに着脱を行うことができるため、数十人規模での運用に耐えうると思います。

f:id:ucho_yayoi:20220120201731p:plain
"Team"プランでは利用しているSeatsが表示される。Seatsの増減も可能。

”Business"プランは、SSO機能などがあるようなので、より大規模な組織での運用に適しているのではないかと思います。
ただし、"Team"プランと比較して価格が跳ね上がることと、我々としてはそこまで高度な機能を必要としていないことから、候補としてあまり検討を行いませんでした。"Business"プランは日本の代理店での取り扱いを行っているので、興味のある方はお問い合わせをしてみるとよいかもしれません。

メンバーには業務用アカウントを取得してもらう

"Team"プランでは、Organizationというグループのようなものを作成し、そこにメンバーを招待して追加していきます(追加されるとSeatsが消費されます)。
招待を行う際、Docker IDまたはメールアドレスを指定して招待を送るのですが、我々はここで社用のメールアドレス宛てに招待を送り、その社用メールアドレスを使ってDocker IDを作成してもらう運用としました。
理由は以下です。

ライセンスの利用者を明確にするため

メールアドレスを指定して招待を行うと、そのメールアドレスを紐付けたDocker IDでなければ招待元のOrganizationに参加することができないようです。
このことを利用し、招待は社用メールアドレスに対してのみ行うようにすることによって、社内の開発メンバーだけが確実に会社のOrganizationに参加するようにしました。

f:id:ucho_yayoi:20220120200954p:plain
招待されたときのDocker ID作成画面には招待を受け取ったメールアドレスを入力する欄がある

個人利用と業務利用を分けるため

個人用のDocker IDを会社のOrganizationに追加してしまうと、個人でDockerを利用している際に、誤ってOrganization内のリポジトリへの出し入れを行ってしまい、セキュリティリスクにつながる恐れがあるのではないかと考えました。
個人用と業務用のDocker IDを別にすることによって、上記のようなリスクをあらかじめ軽減できるのではないかと思います。

台帳でアカウントを管理する

せっかく社用のメールアドレスを指定して招待を行ったものの、Organizationに追加されたDocker IDに紐付いているメールアドレスを見ることはできません。見ることができるのはID文字列とプロフィールのFull Nameだけです。
そのため、「誰のIDか識別できるようにするために、ID文字列やプロフィールに本名を入れるようにしよう!」などとしたくなるのですが、ID文字列とプロフィールは公開情報となるため、本名をインターネットに公開したくないメンバーに対してそのようなことを強要するべきではないと思います…。 そこで我々は、招待したメンバーから実際に作成したID文字列を教えてもらい、それを氏名やメールアドレスと合わせてスプレッドシートで台帳管理するという、アナログな解決策を採ることにしました。
ただ、結局は退社したメンバーをOrganizationから削除するなどの棚卸し作業が必要になるため、どのみち台帳管理のようなことは必要になると思います。
(もしかしたら"Business"プランでSSOを利用すれば、認証サーバー側でIDを無効化するだけでOK、となるのかもしれませんが、詳細はわかりません…)

おわりに

Dockerの有償プランを具体的にどのように運用していくかをご紹介しました。
このような有償ライセンスは、ライセンス費用そのものに加えて、運用のコストがかかるのがつらいところです。
みなさまも、運用上の工夫ポイントなどがありましたら、ぜひ共有いただけるとうれしいです。

お知らせ

弥生では一緒に働く仲間を募集しています! herp.careers

2021年に出会った障害と2022年の目標

こんにちは、カトです。弥生でQAエンジニアをしています。

2022年、新しい年になりました。目標を立てる人も多いですね。 目標は「SMARTゴールで設定」、「モチベーションがあがる目標」が良いと言います。しかし、現状を正しく認識しないと、適切な目標を立てることはできません。 ということで、私が2021年に出会った障害をふりかえり、同じことを繰り返さないように目標を立てていきます。

弥生開発者ブログを参考に、ふりかえっていきます。

障害ふりかえりに向き合う

「カレンダーがケーキに見える」

カレンダーとケーキを誤認?
安心してください。障害票のタイトルではありません。

製品・サービスの画面に、日付を指定するためのカレンダーアイコンがあります。カレンダーアイコンをクリックすることにより、カレンダーを表示し日付を選択する仕組みです。
今回の対象は、"あの"カレンダーアイコンです。

テストをしているとき、システムごとにカレンダーのアイコンが異なっていることに気がつきました。
ログを確認したところ、カレンダーアイコンを使っている1つのシステムで、カレンダーアイコンを「マンスリーカレンダー」の絵から「日めくりカレンダー」の絵に変更していました。
その変更ログに残されていたのが「カレンダーがケーキに見えるため、アイコンを変更」というメッセージでした。

なぜサービスごとにカレンダーのアイコンが異なってしまったのか、障害ふりかえりをしました。

「類似機能なのに、処理結果がちょっと違う」

似ているだけではダメ?
追加機能開発において、「既存の処理と同じように」動作するように実装することがあります。
まるっきり同じ処理であれば、コードを複製するのではなく、同じ処理を呼び出すことで対応できますが、何をどの順序で呼び出すのか?流用できるところと流用できないところはどこなのか?の見極めをしなければなりません。
闇雲なコピー&ペーストでもいけないし、安易な呼び出しでもいけません。

テスト作成時、既存の処理のテストケースを見て、入力と出力が同じ結果になるようなテストパターンを検討しました。
しかし、過去に作成していた既存の処理でのテストには漏れがありました。そのテストが漏れていたパターンにおいて、既存機能と追加機能で、同じ入力をしても出力が異なってしまうという事態が発覚しました。

なぜ予定していた記述型テストで障害検出することができなかったのか、障害ふりかえりをしました。

 運用開始後、境界値が「こんにちは」

境界値 隠れ身の術?
設計書に書いていない境界値があることは承知しています。「設計書に書いていないからテストしない」ではなく、「設計書に書かれていない情報を引き出して明確にする」ことが、テストの活動の一つだとも思っています。
境界値テストのイメージ
2値を選択するか、3値を選択するかという内容は、今回は割愛します。

今回の対象は、画面入力した文言に対し、内部でIDを採番してデータを持つ機能です。 IDの最小値は1。最大値は、手動でテストできない数となっていました。
最大値で登録できることの確認はコードレビュー・単体テストにて実施しました。手動テストでは、1つめの登録、2つめの登録を確認しました。
他のテストにおいても登録をしているため、テスト期間中に少なくても10回の登録は実施しています。
これで、この登録におけるテストに漏れはないと思っていました。

ところが…

突然、なにか出てきた!
しばらくすると、テスト環境で、「入力した文言ではなく、IDが表示される」という事象が発生しました。 テストをしたはずなのに、なぜ?と思いながら障害票を起票、エンジニアに調査をしてもらいました。

調査結果は、「IDを金額と同じような3桁区切りの情報として扱ってしまっている箇所があった。IDが1000以上の場合に、区切りなしの値と区切りありの値が不一致とみなされ、IDに紐づく文言ではなく、IDそのものを表示してしまっていた」ということでした。

なぜ予定していたテストで検出できなかったのか、障害ふりかえりをしました。

 ふりかえりをまとめる

「どのチームが作った」「その時、誰が担当していた」という情報は、弥生の製品・サービスを使っているお客さまにとっては関係のないことです。
チーム弥生として、お客さまには、「かんたん・あんしん・たよれる。」を提供し続けなければいけません。

ふりかえりから、2022年の目標

2021年の障害ふりかえりに向き合ってきました。
「ふりかえりは大事」であることは理解していますが、真剣に向き合うのは、大変です。

ふりかえりから、2022年の目標は、

製品・サービスの、現在を見る/過去を見る/周囲を見る


とします。

まだ、この記事の中で、私の目標は、SMARTゴールである、Specific(具体性)、Measurable(測定可能)、Achievable(達成可能)、Relevant(関連性)、Time-based(期限)は明確になっていません。
これは、また次の機会としましょう。
この目標を達成するためにやるべきことを落とし込んでいけば、SMARTゴールになっていくはずです。
目標のためにやるべきことを具体的にし、現実的に実践が可能なのかどうかを考えて、製品開発に取り組んでいきます。

さいごに

弥生の開発メンバーはどんな目標を立てているのでしょうか?
2022年最初のもくテクは、1月20日に開催です。テーマは、「新年の抱負を語ろう!LT」
さまざまな分野やキャリアのエンジニアの目標を聞いて、自分の目標をブラッシュアップしていける機会になります。
ぜひご参加ください。 mokuteku.connpass.com

弥生では、「かんたん・あんしん・たよれる。」を実現するために、一緒に、弥生の製品・サービスの品質を追求するメンバーを募集しています。
herp.careers

2021年 もくテクを再開し、月1回の開催を継続しました!

こんにちは、カトです。この記事は弥生 Advent Calendar 2021の25日目 最終日のエントリーです。

今日は12月25日。みなさんのお家にサンタクロースは来ましたか?
私は雪が降る地方の出身です。幼少期の12月25日の朝は、庭に残っているソリの跡を探すのを楽しみにしながらカーテンを開けていました。

f:id:yayoikato:20211213115601p:plain:w350

もくテク12月開催報告

2021年12月16日(木)にもくテク「AWS学習イベント DevAxのご紹介」をオンラインで開催しました。

f:id:yayoikato:20211201084148j:plain

今回のテーマ

AWS学習コンテンツ「DevAx::Academy Monoliths To Microservices」のご紹介

弥生が2021年6月~9月に実施したAWS学習イベント「DevAx::Academy Monoliths To Microservices」について。
弥生のエンジニア向けに100人規模で実施した大型の技術研修イベントの概要と、学んだことの具体例についてご紹介。

イベントの企画の話は別ブログで記載しています。こちらもご覧ください。
技術勉強会の企画を立てよう!(初心者編) - 弥生開発者ブログ

また、DevAxについて以下ブログで記載しています。こちらもご覧ください。
エンジニア100人でAWS学習コンテンツ<DevAx Academy MONOLITHS TO MICROSERVICES>を受けてみた(※日本初) - 弥生開発者ブログ

年末らしく、2021年もくテクをおさらい

f:id:yayoikato:20211220093909p:plain:w350
11月開催 2021年秋のLT大会「実践!ふりかえり ~チームで週次ふりかえりをやってみた~」資料より抜粋

ゴリラに煽られました。2021年もくテクのおさらい・ふりかえりやっていきましょう。
もくテクは2016年にはじまったイベントです。中断を経て、2021年5月にオンラインで再開し、運営メンバーも心機一転、がんばってまりました。

5月 確定申告ソフトの開発舞台裏(05/13開催)

f:id:yayoikato:20210515115847p:plain:w300:leftイベントページはこちら。関連ブログはこちら
再開にふさわしく、弥生といえば!の会計チームからの発表でした。
「会計ソフトの弥生」といわれる弥生をもってしても、要件が盛りだくさんで多忙を極めた確定申告について、会計プロジェクトのメンバーがお話ししました。

6月 新規開発における知見共有LT(06/17開催)

f:id:yayoikato:20210622201652j:plain:w300:leftイベントページはこちら。関連ブログはこちら
「会計ソフトだけじゃない弥生」による新規開発における知見の共有です。参加者からのLTを募りました。応募してくださる方はいるだろうかと運営メンバー一同、ドキドキしていました。

少人数で月1開催を続けるのは難しいだろうと判断し、社内で運営メンバー募集をして増員したのがこの時期です。

7月 もう戻りたくない!業務効率化のあれこれ(07/15開催)

f:id:yayoikato:20210715174753j:plain:w300:leftイベントページはこちら。関連ブログはこちら
もくテク最多となる94人のみなさまにご参加いただきました。ハード面、ソフト面、いろんな「業務効率化」に取り組むメンバーの発表で、いますぐ取り入れられるTipsもたくさんある会になりました。

8月 弥生TALK #リモートワーク #コミュニケーション(08/12開催)

f:id:yayoikato:20210803123532j:plain:w300:leftイベントページはこちら。関連ブログはこちら
もくテクでは、はじめてのパネルディスカッション。
「弥生の開発本部の取り組みがわかるような企画を」ということでリモートワークになっても組織・チームを意識できるような仕組みづくりをリーダーがディスカッションしました。

もくテク開催の目的の1つに弥生のエンジニア採用につなげることを意識しはじめたのがこの時期です。

9月 誰でもできる!オンライン勉強会のはじめかたLT(09/16開催)

f:id:yayoikato:20210816083206j:plain:w300:leftイベントページはこちら。関連ブログはこちら
「勉強会」で「勉強会」の話をするのはどうだろう?ということで、もくテク再開から5回目の会は、勉強会のはじめかたについて。普段は裏方の運営メンバーに、苦労や工夫・これからの意気込みを語ってもらいました。

10月 弥生で未経験の分野に挑戦したエンジニアたち(10/14開催)

f:id:yayoikato:20211214082420j:plain:w300:leftイベントページはこちら。関連ブログはこちら
弥生の「採用」を意識したテーマ会。未経験で弥生に入社して活躍するメンバー、弥生の開発本部内でキャリアチェンジしているメンバーが、それぞれのキャリアチェンジストーリーを語りました。

このもくテクの翌日10/15(金)年1度の社員総会が開始されました。もくテク運営チームが社長表彰の一つである「チームヤヨイ賞」を受賞し、また一段ともくテク運営メンバーの士気が上がりました。

社長表彰の一つである「チームヤヨイ賞」とは…
業務内・外を問わず活発に交流し、周囲に笑顔を広め明るくしたことをたたえる
または、弥生のビジョンや文化を理解・共感し、熱意を持って、主体的な行動のサポートを実施したことをたたえる
という賞です。

11月 2021年秋のLT大会(11/18開催)

f:id:yayoikato:20211116122636j:plain:w300:leftイベントページはこちら。関連ブログはこちら
「弥生開発本部の、たくさんの人・チーム・取り組みを紹介したい」を実現した会。
2021年4月新卒メンバー3人も発表しました。11月は新人のデビュー会定番になりつつある…かもしれない?

12月 AWS学習イベント DevAxのご紹介(12/16開催)

f:id:yayoikato:20211201084148j:plain:w300:leftイベントページはこちら
2021年6月~9月、開発本部全員を対象にして実施したAWS学習イベントの紹介、そして勉強会以降の学びを共有する会。
勉強会に参加して終わりではなく、それぞれのエンジニアが有効活用していることがわかる会になりました。

良い子はここまで?

何事も、華やかな現実の裏には、地味でちょっと辛い努力があるものなのです。

f:id:yayoikato:20211214074449p:plain:w300f:id:yayoikato:20211214074502p:plain:w300
サンタクロースのソリの跡にみる、理想と現実

千里の道も一歩から。日々どんな目標に向かってエンジニアが業務に向き合っているのか?を知ることができる機会になるもくテクは、2022年1月20日(木)開催です。
みなさんも新年の目標を考えながら、一緒に弥生のエンジニアの抱負を聞いてみませんか?
ぜひ、connpassイベントサイトより、参加お申込みください。

f:id:yayoikato:20211217172914j:plain

さいごに

アドベントカレンダー

弥生 Advent Calendar 2021も本日で終了です。
毎日楽しみに読んでくださったみなさま、ありがとうございました。
まだ、読めていないというみなさま、これからでも間に合います。弥生にはどんな開発者がいるのかを知ることができます。ぜひ興味のある日をクリックして、読んでみてください。

採用のお知らせ

弥生では一緒に働く仲間を募集しています! herp.careers

ソースコード検索エンジン・OpenGrokを構築しよう

この記事は 弥生 Advent Calendar 2021 の24日目の記事です。

こんにちは。弥生でエンジニアをしている、たけです。
突然ですが、この記事を読んでくださっているみなさんは、ソースコードの検索をどのように行っていますか?

普段から道に迷いやすい私ですが、ソースコードを読んでいるときにも迷子になることが多々あります。 そのうえ極度の面倒くさがりなので、「IDEを立ち上げるのもちょっとな…」と思うことがあります。

そこで、「OpenGrok」というソースコード検索エンジンを自分の開発用PCに構築することにしました。
私は普段デスクトップアプリケーションの開発を行っているのですが、Webアプリケーションの基本の勉強になりそうだと思ったことも、OpenGrokを使ってみようと思った理由の一つです。

目次

OpenGrokとは?

oracle.github.io

  • 高速のソースコード検索・相互参照エンジンです。
    • "A wicked fast source browser" (めちゃくちゃ速いソースブラウザ)です。
  • 複数のプロジェクトのソースコードを一括で検索することができます。
  • 以下のような様々な言語に対応しています。
    • C / C# / C++ / Golang / Java / JavaScript / Json / Kotlin/ Python / Ruby / Rust など

環境構築手順

公式的な環境構築手順は以下のページに記載されていますので、詳細はこちらをご参照ください。
How to setup OpenGrok · oracle/opengrok Wiki · GitHub

このブログでは、私が実際に自分の開発用PCのWindowsに構築した手順を簡単にご紹介します。

1. 事前準備

2. Ctagsをダウンロード

※Ctags:ソースファイル・ヘッダファイルの名前のインデックスファイルを生成するためのプログラム。

  • Universal Ctags をダウンロードし任意のディレクトリに展開する。
  • Path にctags.exeのパスを追加する。

3. OpenGrokの環境作成

  • OpenGrok > Download TAR Ball から「opengrok-XXX.tar.gz」をダウンロードし任意の場所に展開する。
  • OpenGrokの展開先にある、\lib\source.warを展開する。
  • 展開したsourceの配下にある、WEB-INF\web.xmlにCONFIGURATION、SRC_ROOT、DATA_ROOTを設定する。
  • sourceを、%CATALINA_HOME%(Tomcatの展開先)\webapps\にディレクトリごとコピーする。
  • %CATALINA_HOME%\conf\server.xmlの要素内に以下の行を追加する。
 <Context path="/source" docBase="source" debug="0" />

4. ソースコードを解析

  • 「3. OpenGrokの環境作成」でweb.xmlのSRC_ROOTに指定したパスに、解析したいソースコードを配置する。
  • コマンドプロンプトでOpenGrokの展開先\libに移動し以下のコマンドを実行する。
java -jar opengrok.jar -W [configuration.xmlへのパス] -c  [ctags.exeへのパス] -P -S -v -s  [ソースツリー(SRC_ROOT)へのパス] -d  [解析結果出力先(DATA_ROOT)へのパス]

5. OpenGrok を表示

  • Tomcatを起動する。(%CATALINA_HOME%\bin\startup.bat)
  • ブラウザから http://localhost:8080/source にアクセスする。
  • 以下のようなOpenGrokのトップページが表示されていれば環境構築成功です!

f:id:yayoi_ntake:20211220103905p:plain

OpenGrokでできること

  • SRC_ROOTに配置したすべてのファイルについて、以下の表の項目から検索を行うことができます。
  • 「Projects」から検索対象のプロジェクトを絞り込むことも可能です。
検索項目 検索対象
Full Search すべてのトークン
Definition シンボルの定義
Symbol シンボルの定義と参照
File Path ファイル・ディレクトリの名前
Type ファイルの種類
  • 検索結果を選択することで、ヒットした部分が含まれるファイルを開くことができます。
  • 開いたファイルの変数などをクリックすると、その定義にジャンプすることもできます。

使いどころ

上記に記載したOpenGrokの機能を使うと、

  • 複数のプロジェクトのソースコードの検索を、一括で高速に行うことが可能です。
  • 複数の条件を組み合わせた検索を簡単に行うことができます。
  • したがって、巨大なプロジェクトのソースコードを理解するのに便利です(もう迷わない)!

感想

実際にOpenGrokを使ってみて、「やはり速い!」と感じました。
私の所属するチームでは、複数の製品の開発を行っています。
そのため、「このクラス/メソッドは他にどこで使っているんだろう?」と思ったときに、複数のプロジェクトを気軽に検索することができるのはとても助かります。

また、Webアプリケーションのごく基本的な構成について、実際に環境を構築しながら学ぶ良い機会になりました。
OpenGrokを構築する環境がWindows、かつ日本語で書かれている参考情報があまり多くなく情報収集に少し苦労しましたが、それもまた良い勉強になりました。

今後やりたいこと

GitやSVNなどのバージョン管理システムとOpenGrokを連携させることで、変更履歴や差分も見られるようになるので、そちらも対応するとより便利になりそうだと思っています。

お知らせ

弥生では一緒に働く仲間を募集しています! herp.careers

マイクロサービスのAPIをRESTからGraphQLへ段階的に移行した話

こんにちは。弥生の内山です。
この記事は弥生 Advent Calendar 2021 23日目の記事です。

qiita.com

はじめに

現在、弥生では新しいサービスの開発を行っており、今後主流となりうる技術を積極的に取り入れる方針で進めています。
本記事のタイトルにあるマイクロサービスアーキテクチャやGraphQLもそのようにして取り入れてきました。
ただし、それらの技術を実際にプロダクトに適用するのは一筋縄ではいかず、いろいろな遠回りを行った末にどうにか適用できた、というのが実際のところでした。
その過程については、前回12/16のもくテクにてお話させていただきました。

mokuteku.connpass.com

この過程の中で特に苦労したのが、既にREST APIとして構築したサービス群をGraphQL APIのサービス群へ移行させることでした。
もくテクの発表では、かなりの駆け足で移行の全体をお話ししたため、このGraphQLへの移行についてあまり詳しくご説明することができませんでした。
そこで本記事では、RESTからGraphQLへ移行した過程について、少し掘り下げてご説明したいと思います。

移行前のシステム構成

移行前のシステム構成は、以下の図のようになっていました。

f:id:ucho_yayoi:20211220173234p:plain

技術構成は以下のようになっていました。

  • フロントエンド:Next.jsを使用。API RoutesにGraphQLサーバーを立ててBFFとしている。
  • バックエンド:C#(ASP.NET Core)を使用。REST APIとして各種機能をフロントエンドに提供。

この構成において、BFFは以下のような役割を持っていました。

  • フロントエンドからバックエンドのアクセスを1箇所にまとめる
  • バックエンドが提供しているREST APIを集約・変換し、GraphQL APIの形でフロントエンドへ提供する

以上の構成において特に問題となっていたのが、BFFにてREST → GraphQLへのプロトコル変換を行う処理の実装が開発スピードを落としているということでした。
そこで、この部分を見直し、今後の開発スピードを高める策を検討しました。

移行方針

GraphQLは使いたい

フロントエンドとBFFの接続にはGraphQLを使用していましたが、これはそのまま使いたいと考えました。
GraphQLのメリットは様々なところで語られていますが、クライアント側で取得するデータを決定できるという特徴が、やはりフロントエンドとの相性が良いということを実感していました。
そこで、BFFより後ろのプロトコル変換部分を見直すことにしました。

プロトコル変換は無くしたい

プロトコル変換処理を作成するのは大変ですが、APIスキーマ等から自動生成を行う等の手段によって、ある程度はその労力を削減することはできると思います。
しかし、プロトコル変換を行うレイヤーが存在すること自体が、開発のボトルネックになるのではないかと考えました。
そこで、バックエンドサービスがREST APIを提供するのをやめて、変換後のGraphQL APIを直接提供するような構成にすることにしました。

移行のための技術選定

Apollo Federation

GraphQLは、クライアント側から見えるエンドポイントを1つにするのがベストプラクティスとされています。
各バックエンドサービスがGraphQL APIを提供するのであれば、BFFの位置でそれらをまとめて1つのGraphQL APIにすることが望ましいです。

複数のGraphQL APIを束ねて1つのGraphQL APIにしたい、というときに有力な選択肢となるのが、Apollo Federation仕様です。
Apollo Federationは、Netflixが採用したことで有名になった技術で、APIを束ねるゲートウェイが各APIの関連を理解して自動的にクエリの分配・結果の統合を行ってくれます。
現時点でGraphQLのマイクロサービス構成を作るならばこれだろう、と考え、Apollo Federationを採用することにしました。

www.infoq.com

zenn.dev

Apollo Federationでは、各APIが他のAPIとどのように関連しているかをゲートウェイに示すために、独自のスキーマ拡張仕様が定義されています。
この仕様をサポートしていないとゲートウェイにぶらさがることができないため、APIの実装にはApollo Federationに対応したライブラリを使う必要があります。

C#でのGraphQL(不採用)

さて、ではC#のサーバーで実装されたREST APIを、GraphQL APIに置き換えていこうと思ったのですが、ライブラリの選定時点で頓挫してしまいました。
C#のGraphQLライブラリで最も人気があったのはgraphql-dotnetですが、以下のような理由で我々にはマッチしていないと考えました。

  • APIが難しい
    • 非常に高機能な一方で、どう書けば目的を達成できるのかを理解するのが難しかった
    • 公式のドキュメントがあまり充実しておらず、Webにも情報が少なかった
  • Federationへの対応が中途半端
    • Federation対応のAPIを書くにはSchema Firstモードを使う必要があるが、Schema Firstモードの使い方に関する情報はさらに少なく、これを調査しながら使い続けるのは現実的ではないように感じた

ちなみに、一般的にGraphQL APIを定義する方法は大きく分けてSchema First(まずスキーマを作成し、それに対応するリゾルバを実装する)とCode First(クラス定義等からスキーマが生成される)があり、世の中の多くのライブラリはSchema Firstを採用しています。
どちらの方法も一長一短ですが、API実装者がAPI定義も行う場合は、Code Firstの方がスキーマ定義と実装との不一致が起こらないため、有効だと感じます。
なお、graphql-dotnetはSchema FirstとCode Firstの両方をサポートしているライブラリです。

また、graphql-dotnet以外には実用レベルに至っていると感じられるライブラリが見つけられませんでした。
このため、C#でFederation対応のGraphQL APIを実装することは困難であると考えました。

NestJS + GraphQL

C#での実装が難しいということがわかったため、他の選択肢を検討しました。
GraphQLのサポートが強力なのはやはりJavaScript/TypeScriptベースのライブラリでした。
その中でもNestJSは、Code FirstモードでFederation対応のAPI定義をサポートしているため、我々の要求にピッタリ合うという印象でした。

docs.nestjs.com

また、NestJSを採用すれば、フロントエンドもバックエンドもTypeScriptで開発できるようになるため、1つの機能を開発する際のオーバーヘッドが削減できそうです。
以上のことから、バックエンドの各サービスはNestJSを用いて開発することにしました。

お試し移行

しかし、既にC# + RESTで開発したAPI実装を、NestJS + GraphQLのAPIに変更するということは、つまりバックエンドサービス群を新しい技術スタックでまるごと書き直すということになります。
さすがにそれをいきなり実行するのはリスクが高すぎるため、まずは新規機能のために開発するAPIサーバーを、この新しい技術スタックで作成してみることにしました。

作成してみて、これまでの技術スタック(C# + REST、BFF内でGraphQLへ変換)で開発していたときと比較し、機能が完成するまでのスピードが明らかに早くなったと感じました。
単純に記述するコードの量が減りましたし、言語やプロトコルを頭の中で切り替える必要がなくなったので、作業効率もアップしたのだと思います。
この新しいGraphQL APIの実装方法に手応えを感じたため、これまでに作成してきた機能についても、新しい技術スタックへの置き換えを進めることにしました。

APIのマージ

さて、新しくGraphQL APIを立てたものの、このAPIを既存のシステムにうまく接合することができていませんでした。
この新しいAPIもBFFにぶらさげる形としたかったのですが、以下のような理由によりそれができませんでした。

  • Apollo ServerはREST API等の複数のデータソースを変換して1つのGraphQL APIにまとめることはできるが、GraphQLのデータソースはサポートしていなさそう
  • Apollo Gatewayは、複数のGraphQL APIをまとめることはできるが、GraphQL以外のAPIをまとめることはできなさそう

そのため、実は新しいAPIはフロントエンドから旧APIと別に呼んで、フロントエンドで結果をマージするという、かなり乱暴な暫定処置を行っていたのでした。
(新しいAPIとこれまでのAPIがそれほど結びつきが強くないということも幸いし、この処置でどうにかなったのでした)

しかし、このままではフロントエンドが複数のAPIエンドポイントを呼ぶ形になってしまい、複雑さが高くなってしまうのは明白です。
また、これまで作成したREST API群をGraphQL APIに置き換えていくときに、全てのAPIを一気にGraphQLにするのではなく、APIサーバーを順番にGraphQLに書き直してシステムに接合していけるような方法が採れないと、移行のリスクが高くなってしまうと考えました。
そこで思いついたのが、「REST APIをまとめてGraphQL APIにするサーバー」と、「各GraphQL APIをまとめるサーバー」の2段構成にするアイデアでした。

移行ステップ

以下のようなステップを経て、REST API群をGraphQL APIへ置き換えていきました。

1. REST APIを集約する部分を切り出す

今まではNext.js上のAPI RoutesにApollo Serverを配置し、これがREST APIを集約してGraphQL APIを提供していました。
このApollo ServerをBFFから切り離し、独立したサービス(API Gateway)として動作させるようにしました。

f:id:ucho_yayoi:20211220173237p:plain

2. GraphQLをまとめる

システム上に、2つのGraphQL APIサーバー(API Gatewayと、新規機能開発で作成したGraphQL APIサーバー)が存在する状態になるので、これらをApollo Gatewayを使って集約し、1つのGraphQL APIにします。
Apollo Gatewayは、いったんAPI Routesに配置します。
これによって、フロントエンド側からは1つのGraphQL APIだけが見える状態になります。
(新APIがフロントエンドに直結されてしまう問題もこれで解決できます)

f:id:ucho_yayoi:20211220173239p:plain

3. REST APIサービスをGraphQL APIサービスに移行する

この状態であれば、REST APIサーバーの1つをGraphQL APIサーバーに書き直しても、API RoutesのApollo Gatewayの下にぶらさげれば、フロントエンドから見たGraphQL APIとしては変化しないということになります。
これによって、REST APIサーバーをひとつずつGraphQL APIサーバーに書き直して、順次システムに接合する、ということが可能になります。

f:id:ucho_yayoi:20211220173242p:plain

4. API Gatewayを切り出す

REST APIサーバーをすべてGraphQL APIサーバーに書き直した後は、REST APIを集約していたApollo Serverが必要なくなります。
また、各Next.jsアプリのAPI Routesに置いたApollo Gatewayがほとんど同じことをやっているため、これらを1つにまとめたいです。
そこで、API GatewayにApollo Gateway(名前が紛らわしいですね)を配置するようにします。
ここまで実施すれば、GraphQLなマイクロサービス構成への移行が完了です。

f:id:ucho_yayoi:20211220173231p:plain

まとめ

こうして振り返ってみると、なんだか非常に遠回りをしたような気がします…。
しかし、結果としてすっきりした技術構成へ移行することができましたし、移行過程でGraphQLまわりに関する知見を深めることができたと思います。
この記事が、GraphQLへの移行を試みる方の参考になればうれしいです。

お知らせ

弥生では一緒に働く仲間を募集しています! herp.careers

コロナ禍入社のエンジニアが技術イベントを頑張る話

この記事は弥生 Advent Calendar 2021の22日目の記事です。

情報システム部エンジニアの飯田です。昨年に引き続きAdvent Calendarの記事を書いていきたいと思います!テーマは弥生の技術イベント「もくテク」を頑張っているよというものです。

もくテクについてはAdvent Calendarの8日と9日にもエントリーがありますので、興味のある方はそちらもぜひ見てください!

qiita.com

自己紹介とこれまでのお話

昨年5月、コロナ禍でちょうどリモートワークへ完全移行した後に弥生へ中途入社しました。これまでと全く異なる働き方に戸惑いつつも社員の皆さんのサポートのおかげで仕事にも慣れ、今では要件定義からリリース作業に保守・運用まで幅広く担当させてもらっています。

それに加えて今年は全社的な活動に関わる機会も増え、前回投稿した部署の全体会でのLT川本さんが書かれたBacklog移行プロジェクト、そして今回のテーマであるもくテクの登壇および運営と、自分なりに色々と動いてみました。

ちなみに全体会でのLTの話を投稿した4月の時点で3回だった出社回数は8回に増えていますが、そのほとんどはオフィスリニューアルのための荷物の梱包&開梱など普段の業務とは異なる理由でした。私の周りは自宅の作業環境も整備され、リモートワークがすっかり定着しているように思われます。(ただ、せっかくリニューアルして快適な空間となったので、オフィスも何かで活用したいなと思っています。)

きっかけ

私がもくテクに関わるようになったきっかけは、今年の6月頃にチームリーダーから登壇者の募集があったことでした。テーマは業務効率化で、ちょうど私がプロジェクトで取り組んでいたことをネタにできそうだったこともあり立候補しました(実際に登壇した会のイベントページはこちらです)。同時にもくテクの運営メンバーも募集していたのでそちらにも手を挙げました。(ここだけの話ですが、私は最初、「もくもく会」のイメージもあって「もくテク」の「もく」は「黙」だと思っていました。正しくは「木曜日」の「もく」です。)

運営については前職で社内の勉強会の活動に携わっていたのでその経験を活かせそうだと思ったのですが、登壇については実は初めてでした。自分でもどちらかというと裏方が向いており、発表も決して得意ではないと認知しています。それでも登壇者に立候補してみたのにはこれから書くような考えがあったからでした。

立候補した理由その1:弥生の技術をアピールしたい!

時は遡って昨年の3月、私が転職活動のために転職エージェントとの面談を受けた日のことでした。私のこれまでの経験や今後の方向性などを伝えた後、エージェントさんから勧められた企業の中に弥生株式会社があったのですが、その時私の頭に浮かんだのは「弥生会計の会社…かな?」でした。これ以外のことは思い浮かびませんでしたし、同じように思う人も多いのではないかと思います。

しかしそこから面接で弥生のビジョンや事業内容などに惹かれて入社を決め、実際の業務を見てみると、技術の会社として弥生はとても面白いということに気付きました。

まず、弥生会計を含む「弥生シリーズ」は30年以上の歴史を持つデスクトップアプリケーションで、これだけ長く続くソフトウェアは珍しいと思います。もちろんメンテナンスの大変さなどの負の側面もありますが、お客さまからの要望を取り入れた様々なこだわりがあり、技術の活かしどころなのではないかと思っています。(こういったこだわりの話は社内教育の他、もくテクで知ったものもあります。)

次にクラウド(オンライン)アプリケーションです。プラットフォームは以前から使用しているMicrosoft Azureに加え、佐々木さんが書かれたように最近はAWSの利用も広がっています。私が所属している新課金チーム(弥生製品の契約や請求を管理するシステムを開発・運用しています)とも深い繋がりがあり、また私も前職でAWSを使ってきていたので、個人的にこの流れは嬉しいです。(これからは私の時代だな!)

他にもYAYOI SMART CONNECTでは現在ホットなトピックである機械学習を扱っていますし、将来へ向けて最新の技術を使った次世代のサービスの開発も進んでいます。

これだけ幅広い領域を1つの会社で扱っており、しかも全て自社の製品やサービスです。弥生というと会計ソフトの老舗ということもあってお堅いイメージを持たれがちかも知れませんが、そんなことはなくて色んな技術を使っているんだよというのを多くの方に知っていただきたいと思っています。(そして私たちと一緒に働くメンバーを増やしていきたい!(笑))

立候補した理由その2:アウトプットの楽しさを広めたい!

もう1つの理由は内部向けのものでもあります。

発表は得意ではないと書いた私ですが、7月の最初の登壇以降、運営に携わりつつ9月と12月にも登壇させてもらっています(12月の会については後日開催報告がありますのでお楽しみを!)。発表自体は5分や10分と短いものですが、それでも準備にはそれなりの時間と手間がかかりますし、発表本番は緊張するものなので、ハードルは決して低いものではありません。

ただ、やり遂げた後の達成感は大きいですし、自分の普段の漠然とした考えを具体化することによって理解が深まったり気付きがあったりして、仕事の楽しさやモチベーションの向上にも繋がると感じています。

個人的な考えですが、イラストレーターは絵を描くことによって、俳優は役を演じることによって自己表現しますが、エンジニアは記事を書くことや登壇することによって自己表現ができるのではないかと思っています。(ちなみに私は大学のサークルで演劇をやっていました。最初に書いた通り表舞台には向いておらず役者は2回だけしかやりませんでしたが。)

登壇も経験した運営メンバーとして、これからは自身の経験を活かして登壇者をサポートし、アウトプットすることの楽しさを広めると共にもくテクを盛り上げていきたいと思っています。

次回もくテクのご案内

ということで、次回2022年1月のもくテクは私がメインの運営を担当します!

mokuteku.connpass.com

テーマは「新年の抱負を語ろう!LT」で、色んなチームのメンバーに2022年の抱負を語ってもらいます。周りのエンジニアがどんなことを考えどんな目標を立てているのか、また弥生がどんな技術を扱っているのかを知っていただける内容となっていますので、ぜひお気軽にご参加ください!

お知らせ

弥生では一緒に働く仲間を募集しています! チャレンジしたいことがあれば応援してくれる環境があるので、興味のある方はお気軽にお問い合わせください!

herp.careers

音声系システム担当のカスタマーセンター移転で得た学びの話

この記事は 弥生 Advent Calendar 2021 の21日目の記事です。

はじめに

弥生株式会社の石原です。
情報システム部のCRMチームに所属しています。

弥生は、札幌と大阪にカスタマーセンター(コールセンター)があり、CRMチームはカスタマーセンターの業務システムの導入支援・開発・運用保守を主に担当しています。
私は、CRMチームの中で主に音声系(テレフォニー・自動応答系)システムの担当をしています。

ちょうど1年前の年末年始期間中(2020年~2021年)に「お客さまへの安定したサポート体制と従業員が働きやすいオフィス環境づくりを目指す」というコンセプトのもと、札幌オフィスを移転しました。

今回は、音声系システム担当としてオフィス移転に携わり、普段と違った業務の中で、大変だったこと、学びになったことをお話します。

今回移転対象となった札幌カスタマーセンターは、400席を超える規模の大きいカスタマーセンターです。
私は、音声系システム担当としてアプリケーション側の業務を主に担当していて、オフィス移転の実務は未経験でした。
(大阪カスタマーセンターは2016年5月にオフィス移転をしていますが、私は参画していませんでした。)

オフィス移転プロジェクト開始前、私は漠然と以下のような移転作業を行うイメージでした。
1. 旧オフィスで業務が終了したら機器の電源を落として新オフィスに運ぶ
2. 新オフィスでは回線開通工事をしておいて、運んだ機器を設置する
3. 設置した機器の電源入れてテストしておわり!

ですが、実際やってみるとそんな簡単な話ではありませんでした。
様々な点で考慮する必要がありましたので、その点をお話していきます。

移転作業の方針・プロジェクトとして考慮すること

今回の移転プロジェクトでは音声系システムに限らず、移転プロジェクト全体で以下の2点を考慮する必要がありました。

  • 移転作業が年末年始期間中であること
    年末年始期間中のため、引越業者やシステム担当ベンダーの稼働日が限られ、移転作業も出来ることが限られました。移転作業は、ただ引越作業をするだけでなく、何か問題が発生したときの対処も含みます。
    そのため、移転先の新オフィスに「可能な限りシステム環境を整えて事前にテストを行う」という方針で進めることが決まりました。
    事前テストは2020年12月上旬開始ということも決まり、音声系システムも事前テストに合わせて環境を整えることになりました。

  • 新オフィスの引き渡しから営業開始日までの期間が3か月であること
    新オフィスの引き渡しは2020年10月で、新オフィスの営業開始日は2021年1月です。
    電話回線の開通工事に必要な、電源やサーバーラック設置などの電気設備工事はすべて、新オフィス引き渡し後の10月から着手で、完了が事前テスト開始直前の予定でした。
    そのため、事前テスト開始直前は、音声系システムだけでなくシステム系すべての回線開通工事とサーバー機器設置工事が立て込むことが想定され、各システム担当で密に連携をとりながら工事日を調整する必要がありました。
    システム工事以外である什器搬入や内装工事、空調設備工事も新オフィス引き渡し後の10月からの開始なので、移転プロジェクトはすべての作業が非常にタイトなスケジュールで進んでいきました。

私のタスク

今回のオフィス移転で、私は以下の4つのタスクを担当しました。

  • 電話回線の移転契約
  • PBX(電話交換機)の構成と番号設定の設計
  • 通話録音環境の構成と録音設計
  • 電話機・ヘッドセットの機器選定

また、オフィス移転はシステム設計課題の改善や機器更改のチャンスなので、課題の改善を意識しながら作業を進めました。

電話回線の移転契約

まずは旧オフィスの電話回線契約内容と実機の確認を行い、旧オフィスの電話回線の契約内容と機器構成を完全に理解するところから始めました。
その上で新オフィスに必要な環境と、旧オフィスの回線契約をどうすべきかを考えました。
「可能な限りシステム環境を整えて事前にテストを行う」ため、新オフィスで必要な回線をすべて新規契約で開通させて、旧オフィスの業務終了後に番号ポータビリティを行いました。

PBXの構成と番号設定の設計

ハードウェアの更改と、旧オフィスの複数回の増床により煩雑になっていた社内の内線番号と転送設定の最適化を行いました。
担当ベンダーとの密な連携で特に大きな問題もなく作業を進めることができました。

通話録音環境の構成と録音設計

旧オフィスは、通話録音の際に電話機と電話機設置エリアに依存関係がある課題がありました。
オフィス移転のタイミングでどの電話機をどのエリアに設置しても通話録音ができるように改善しました。

電話機・ヘッドセットの機器選定

旧オフィスの増床のたびに追加購入していたので機種の統一ができておらず、古いものも継続して利用している状態でした。
利用する側は席によって電話機が違うため不便さがあり、私のように機器管理する側は統一出来ていないことによる保守性の低さが課題としてありました。
「従業員が働きやすいオフィス環境づくりを目指す」というコンセプトのもと、新オフィスでは気持ち良く仕事ができるように400席分新品購入することとしました。

大変だったこと

移転プロジェクトが未経験で、一つ一つが挑戦で大変ではあったのですが、その中でも特に印象に残っている大変だったことが3つありました。

電話回線契約の取りまとめが完了し発注目前の段階で見積金額に誤りがあり、費用が増額することが発覚したこと

電話回線契約の内容を決めるまでに時間がかかっていたため、事前テストに間に合うように電話回線開通するための発注期限が迫っている状況でした。 発注期限が迫っている状況と予算の範囲もあって焦ってしまい、費用を抑えることができる別の契約内容を再検討することも考えましたが、「お客さまへの安定したサポート体制」と「可能な限りシステム環境を整えて事前にテストを行う」という方針のもと契約内容を変更せず費用増額で社内調整を実施しました。調整の結果、費用増額での発注も承認され、契約内容を変更せずに進めることができました。

発注した400台の電話機が、納品前に突然販売停止となり、在庫もない、追加製造予定もないことが発覚したこと

製造メーカーに在庫も追加製造予定もない以上、発注した電話機と同じく、ヘッドセットとの互換性や必要機能を満たす別機種を再発注する以外に手はありませんでした。
今回はその別機種の在庫があり、無事400席分の新品購入を実現できました。

ルーター/ゲートウェイの設置工事日に、サーバールームの電源・サーバーラック設置工事が完了していないことが発覚したこと

電話回線は、開通工事日までにアクセス工事・ルーター/ゲートウェイの設置工事をする必要があります。
開通工事日の調整に気をとられすぎて、電源工事とサーバーラック設置工事の完了前に、ルーター/ゲートウェイの設置工事を行うスケジュールになってしまいました。
後続スケジュールのこともあり、開通工事日は延期できないため、キャリア側に相談をしたところ、電源は仮設電源、機器設置はラックがある想定でケーブル長を調整する形で工事を実施していただけることになりました。
工事日当日は、延長コードと電源タップを駆使した仮設電源を何とか用意し、工事を予定通り完了させることができました。

学んだこと

移転プロジェクトを通じて、音声系システムの契約内容と構成を完全に理解できたことも良かったのですが、前述の大変だったことから普段の業務では学べないことを学ぶことができました。

見積の内容確認の際は、金額に誤りがないことの再確認の依頼もするようにする

見積の工事単価に誤りはないだろうという思い込みがありました。
未経験なこともあり、見積内容をキャリア側と対面で認識合わせをして、決定した電話回線環境が構築できるか、工事内容に認識違いや過不足がないかの確認ができて満足していました。
今後は、工事単価の見積金額にも誤りがないか確認することを学びました。

発注時の在庫の有無はしっかり確認する・万が一の回避策も用意しておく

見積を受け取り、発注もできた製品なので、在庫はあるだろうという思い込みがありました。
今回は、ヘッドセットとの互換性や必要機能を満たす別機種があったから良かったです。
別機種の在庫がなかったら、旧オフィスの電話機を継続して使用するしかないため課題解決できず「従業員が働きやすいオフィス環境づくりを目指す」ということが実現できないところでした。 今後は、見積と発注のタイミングで確実に納品できる在庫があるのか確認すること、万が一の回避策となる第2案も用意しておくことを学びました。

自分が実施する作業の前提条件をしっかりと確認する

自分が実施する作業と、その前提条件の洗い出しができていませんでした。
実施する作業と前提条件を洗い出した上で全体スケジュールに反映しないと、プロジェクトの進捗管理も正しくできず、プロジェクト全体に迷惑がかかることになります。
今後は実施する作業と前提条件を洗い出した上で、全体スケジュールにはめ込んで、工程管理していくことを学びました。

おわりに

迎えた札幌カスタマーセンター移転後最初の営業日である2021年1月4日。 無事にお客さまからの電話が着信していることを見届け、一安心したと同時にとても大きな充実感がありました。
オフィスが一からでき上がっていく過程に携われたことも、なかなか得られない非常に恵まれた経験をすることができたと思っています。

ふりかえると、学びになったことは「自身の思い込みや考慮不足により問題が発生すると後々になって大変になってしまう」ということで、今現在も良い教訓となっています。

f:id:taku_ishihara:20211210101812j:plain
2020/10/05 新オフィス引き渡し直後
f:id:taku_ishihara:20211210102524j:plain
2020/12/21 移転作業を待つだけの新オフィス

お知らせ

弥生では一緒に働く仲間を募集しています!
https://herp.careers/v1/yayoi/s2GGF_cUe8qt