Atrae Tech Blog

People Tech Companyの株式会社アトラエのテックブログです。

国税庁のインボイスWeb-APIを使って、取引先のインボイス制度登録番号を一気に取得してみた

みなさんこんにちは。GreenのCSチームの荻原です。
普段はGreenの利用企業様のサポートをしたりプロダクトを企画をしたりしているのですが、自身の仕事を効率化し業務改善をゴリゴリ進めていたら、いつの間にか社内全体の業務プロセス改善や基幹システム整備なども担当するようになっていました。

今日は社内でインボイス制度に関する対応を効率化できたのでその話をしようと思います。インボイス制度は2023年10月これから多くの会社が対応していくと思いますので、誰かのお役に立てれば嬉しいなと思い記事を投稿することにしました。

何をやったのか

まず経理チームからもらった相談は「現在の取引先のインボイス制度の登録状況を調べたいんだけど、なにか良い方法はないか?」という内容でした。実際僕もGreenのお客様からインボイス制度の登録状況に関するアンケートの入力依頼をもらうことが増えてきており、「インボイス制度もいよいよ始まるな〜」なんて思っていました。

色々と調べてみると、どうやら国税庁が「インボイスWeb-API」なるものを出しており、国税庁が発行している13桁の法人番号さえあれば、適格請求書発行事業者の登録があるかどうかを判別し、登録がある場合は登録番号を取得することができることがわかりました。ありがたい!!

https://www.invoice-kohyo.nta.go.jp/web-api/index.html

以下、実際に利用したコードを載せていますので参考にしてください。

事前準備

今回説明するやり方は事前に準備が必要なものがあります。

①登録状況を調べたい取引先の法人番号リスト
インボイスWeb-APIの利用申請

①法人番号リストについては下記の画像のように法人番号が並んでいるリストを用意してもらえればOKです。このとき、法人番号を昇順(上から小さい順)になるように並べ替えをしておいてください。(インボイスWeb-APIの仕様上、予め昇順に並べておく必要があります)

インボイスWeb-APIの利用申請については下記URLから行えます。申請が承認されるまでに1ヶ月程度かかる可能性があるとのことなので、利用したい方は早めの申請をおすすめします…

https://www.invoice-kohyo.nta.go.jp/web-api/index.html#cmspagelink2

申請が承認されるとAPIを利用するための「アプリケーションID」が発行されますので、こちらを予め取得しておいてください。

GASでAPIから情報を取得するプログラムを作成

ここはエンジニアチームに相談しながら進めました。Googleスプレッドシートの「ツール>Apps Script」からGAS(Google App Script)のエディタを開きコードを生成します。

function myFunction() { 
  get_invoice_by_hojinbango();
}

//-----------------------
// 設定
//-----------------------
const max = 1000; //最大件数
const unit = 10; //APIは1セッション10件まで
const staySecond = 1; //APIの発行間隔[秒]
const apiurl = "https://web-api.invoice-kohyo.nta.go.jp/1/num?id={アプリケーションID}&number=";
const apisuffix = "&type=11&history=0";
//-----------------------
var startRow;
var countSum = 0;
var endFlg = 0;


//-----------------------
// メイン
// ・法人番号をキーに、その登録番号("T"+法人番号)があるかAPIで検索
// ・登録番号が無い場合は、その番号はスルーされる
//   よって10件検索して未登録2件あればレスポンスは8件
// ・レスポンスは登録番号の昇順で返ってくる
//   よってシートはあらかじめ法人番号の昇順でソートしておく
//-----------------------
function get_invoice_by_hojinbango() {
  var sheet, companyinfo, companynum, companynums, count, arrs, i, j, k, countnoresp;
  sheet = SpreadsheetApp.getActiveSheet();
  companynums = [];
  count = 0;
  countnoresp = 0;
  
  //空きセル(スタート位置)を探索し、スタート位置から法人番号を全件取得していく
  var lastrow = sheet.getRange(sheet.getMaxRows(), 3).getNextDataCell(SpreadsheetApp.Direction.UP).getRow();

  for (i = 0; i < unit; i++) {
    companynum = "T" + sheet.getRange(lastrow+i, 2).getValue();
    companynums.push(companynum);
    count++; countSum++;

    // 10件までに最終行に達したら終了
    if (i+lastrow == sheet.getLastRow()) {
      count = 0;
      endFlg++;
      break;
    }
    // 設定した10件ずつ取得
    if (count >= unit) {
      count = 0;
      break;
    }
  }

  //APIから10件取得してくる
  arrs = fetch_info_ten(companynums.join());

  //シートにセット
  for (j = 1; j < companynums.length; j++) {
    //法人番号が同一のときは書き込み、無いときはbreak
    if(j - countnoresp <= arrs.length - 1) { //取得したレスポンス数がjを超えないようにする
      if(companynums[j] === arrs[j - countnoresp][1]) { //登録番号が合致したら
        for (k = 0; k < arrs[j - countnoresp].length; k++) {
          sheet.getRange(lastrow + j, 3 + k).setValue(arrs[j - countnoresp][k+1]);
        }
      } else { //登録番号が合致しなかったら「0」を書き込んで次へ
        sheet.getRange(lastrow + j, 3).setValue(0);
        countnoresp = countnoresp +1; //レスポンスの無かった件数
      }
    } else { //取得したレスポンス数がjを超えたら「0」を書き込んでbreak
      sheet.getRange(lastrow + j, 3).setValue(0);
      break;
    }
  }

  //まだレコードが残っている時は再実行
  if (countSum < max && endFlg < 1) {
    Utilities.sleep(staySecond * 1000);
    get_invoice_by_hojinbango()
  }

}

//-----------------------
// APIで取得したXMLを配列で返す
//-----------------------
function fetch_info_ten(companynums) {
  var arrs,temp;
  // フィードを取得
  var fetchurl = apiurl + companynums + apisuffix;
  const options = {
    "muteHttpExceptions" : true,
  }
  try {
    var response = UrlFetchApp.fetch(fetchurl, options);
  } catch(e) {
    // 例外エラー処理
    Logger.log('Error:')
    Logger.log(e)
  }
  var xmlitems = XmlService.parse(response.getContentText()).getRootElement().getChildren('announcement');
  arrs = [];
  for (var i = 0; i < xmlitems.length; i++) {
    arrs[i] = [];
    arrs[i].push(xmlitems[i].getChildText('sequenceNumber'));
  }
  return arrs;
}

多少勉強すればコードの内容も理解できると思いますが、正直ここはエンジニアの人に相談したほうが早いかもしれません笑

const apiurl = "https://web-api.invoice-kohyo.nta.go.jp/1/num?id={アプリケーションID}&number=";

の部分だけ変更してもらえれば動くと思います。

ちなみにこのコードは下記の記事を参考にしながら、インボイス用に改良しました! stabucky.com

プログラムを実行

「実行」ボタンを押すとプログラムが動き始めて、GASが勝手に登録状況を入力してくれます。登録がない場合には3列目に「0」が、登録がある場合には登録番号が入力されるようになります。

なおGASの実行時間が30分が限度となっており、それを超えるとシステムの実行が止まってしまいます。件数が5000件を超える場合などは、システムが止まったら再度実行をする必要があります。

以上の手順で一気に大量の会社の登録状況を調べられるので、ぜひ参考にしていただければ嬉しいです。(なお記事内のコードなどは勝手に利用頂いてOKですが、もっと良いコードの書き方はあるかもしれません。あくまで参考程度に…そしてこのやり方では個人事業主の登録状況は調べられないのでご注意ください。)

これの結果、具体的にどんなメリットがあった?

アトラエでは全取引先の登録状況と登録番号を30分くらいですべて精査できました!

取引のある会社すべてにアンケートを送って、未回答の企業に催促の連絡をして、アンケートの回答を集計して…みたいな作業がすべて不要になりました。取引先の手間を減らし、自分たちの手間も減らせて、まさにwin-winなプロジェクトでした!IT最高!!

世の中の経理担当の方にこのブログが届き、こんなやり方もあるんだと知るキッカケになれば嬉しいなと思っています。

最後に

アトラエでは一緒に働く仲間を募集しております。
アトラエでは前例の無いことにも積極的に挑戦し、常に生産性の高い会社であろうと日々挑戦を続けています。オフィス内に無料でお酒を飲めるバーもあるので、興味がある方はぜひご連絡ください〜!

speakerdeck.com

アトラエらしさと向き合った Google ドライブの設計

みなさんこんにちは。エンジニアの小倉です。権限管理してますか?(挨拶)

今回は「アトラエらしさと向き合った Google ドライブの設計」を題材にし、アトラエのビジネスや組織の考え方をどのように Google ドライブに反映させたかを説明します。細かに書きすぎるとセキュリティ的にアレなので重要な点のみを抽出しています。

なお、2023年時点で完成しているわけではなくむしろ発展途上ですので、その前提で読み進めて頂けると幸いです。

背景

アトラエでは以前からクラウドストレージサービスとして Google ドライブ(以下 ドライブ)を利用しています。 ドライブは柔軟性の高いサービスで、セキュリティで固く縛ることもできますし、逆に意図的にゆるく設計することも可能です。

ドライブを導入後、社内に統一的なポリシーがなかったため運用における問題がたくさんありました。例えば以下です。

  • Slack でドライブの URL が送られてくるが、リンクを開いてもアクセス権限がないことがあった(自身には権限がないマイドライブや共有ドライブだった 等)
  • 誤操作によってファイルやフォルダが消失していたり意図せず変更されているケースがあった
  • 社外に共有する際のルールがなかったり、チームや個人にによってその方法が異なっていたりした

後述しますが、世界中の人々を魅了する会社を創ることをビジョンに掲げるアトラエにとって情報共有がボトルネックになっていることは由々しき事態でした。

時間をかけてドライブと向き合いバージョンアップを重ねた結果、上記の課題を解決できるようになったのでその過程を詳細に書いていきます。

取り組んだこと

以下の3つを約1年の間で取り組みました。特に方針の策定に時間がかかりました。

  • 課題の整理
  • 方針の策定
  • 移行の実施と浸透

課題の整理

MECE ではなく恐縮ですが、私が観測していたことと従業員からヒアリングして得られた話などをまとめたものが以下です。

事象と原因が混在していますが、要は生産性とセキュリティレベルの低下が発生している状態でした。

これが続くと利用歴の長い既存の従業員がこれまで通り利用を続けることで、よりドライブの構成が複雑になることが予想されましたし、事業部やチームごとに個別で異なるクラウドストレージを採用したり、それによって本来意図していた全社としての情報共有が阻害されたりする未来がなんとなく見えていました。

私は Konmari 魂とともにドライブと向き合うことを決めました。

「らしさ」と情報システムの設計

閑話休題。課題の解決方法からその組織「らしさ」が見えてくると私は考えています。だからこそ組織で特定された課題へのアプローチが複数ある場合、「どのような組織にしたいか?」を考えた上で方法を選択するべきだとも考えています。

特にクラウドストレージのような組織の全員が日々触れる情報システムにおいては、より「らしさ」と向き合った設計が必要になります。どういう組織にしたいか?逆にどういう組織にしたくないか?を考えました。

「最小権限」を問いなおす

例えば「各従業員に対して適切に権限が付与されていない」という課題を情報セキュリティの定石から解決しようとすると「最小権限の原則(Principle of Least Privilege)」を採用することになります。これは本来の目的達成に必要な最低限の権限しか与えないようにすることを指し、基本的には各人にミニマムな権限を付与した上で必要な上位権限を追加的に付与していくことになります。この背景には性弱説および性悪説があり、不要なら付与しないということで不要な事故や事件を防ぐことを目指します。

しかしながら組織環境や状況に応じて「最小」が指すところは変わってきそうです。

アトラエでは、全メンバーがフラットかつ建設的に議論できるような、経営者と同じレベルの情報にアクセスできる業務環境を志向しています。当事者意識、経営者目線を阻害する理由の1つに情報量の不足が挙げられます。熱意ある変革者だとしても情報が限られていることで正しい意思決定、自信のこもった意思決定ができないのは想像に難くないはずです。

一般には最小権限の原則に従って自チームや自事業部以外の情報へのアクセスを与えないべきなのですが、アトラエでは全員にほぼ全部の情報にアクセスさせるということが是ということになります。だって経営者なら自社サービスの営業をいつでもできるように営業資料に目を通す可能性は全然ありますよね。いい意味で他事業部、他チームに首を突っ込めた方がいいと私も、みんなも考えています。

適切なガードレールを設置する

一方で全員が全情報を編集できる必要があるか?と問われるとそんなことはありません。そのため編集権限ではなく閲覧権限で十分そうです。

加えて、編集権限を持っているがゆえに本来的ではない操作もできてしまいます。誤操作により業務で利用するファイルを削除してしまうとか、もしくは意図的に情報を抜き取るとかです。これらのリスクに対しては不要な機能をオフにしたり、監視体制を設置したりすることで対応します。

「どのような組織にしたいか?」が攻めの観点とした場合、「どのようにガードレールを設計できるか?」が守りの観点となります。

よいセキュリティの仕組みはガードレールに例えられることが多いです。適切なガードレールがあるからこそ余計なことを考えずに事故もなく走り抜けられるように、快適に業務ができて事故が起きにくい仕組みが情報システムにおいても必要です。

ちなみに全員でほぼ全情報を共有すると書きましたが、人事情報(マイナンバー、給与情報 等)を代表とする情報はそもそも全員がアクセスできるべきではないので、これは一部の人のみがアクセスできるようにする必要があります。

方針の策定

ここまでの観点を踏まえて、「アトラエらしさ」の表現のために一般的な情報セキュリティのベストプラクティスとは反する合理性を含めて方針を策定しました。

資産情報の可視化

インシデント対策として実施しています。やはり情報管理の一丁目一番地となるのが資産のランク付けです。全員がほぼ全てのファイルにアクセスできるからこそ、取り扱いに責任を持ってもらうことを期待してファイルの情報を可視化しました。

Google Workspace のラベル機能を利用し、資産情報の可視化を行っています。Google Workspace Enterprise では DLP 機能が利用でき、その機能の一環としてドライブ上の全てのファイルに対してラベルを自動で付与することができます(このあたりは公式ドキュメントをご覧になったりデモ的に操作したりすることをおすすめします)。

例えばアトラエでは2種類のラベルを設定することでドライブ内の資産管理を行っています。

  • 重要度(Confidentiality)
    • Higly Confidential Confidential General の3種類
    • デフォルトで Confidential が付与され、ファイル内に事前定義の機微な情報が含まれる場合は Higly Confidential に自動的に変更される
    • ファイルに含まれる情報が経営に全く影響しない場合は手動で General に手動で切り替える
  • 共有範囲(Accessibility)
    • Atrae Internal Atrae External の2種類
    • デフォルトで Atrae Internal が自動で付与される
    • 社外に共有する場合は Atrae External に手動で切り替える

ラベルが付与されたことで、グルーピングされたファイルに対してまとめて制御をかけたり、監視の対象としたりできるようになりました。

また結果的に自動で付与されるラベルを通して自分が扱っている情報資産の価値を認識する逆転現象のケースも発生しました。個人情報の正確な定義を知らない人が扱うファイルが、ふと Higly Confidential になり、不思議に思って調べたら、実はこれまで自分が入力していた情報は個人情報の類だった、みたいな感じです(※)。

※ 弊社では個人情報含む機密情報に関するトレーニングを継続的に行っておりますが、その専門ではないメンバーが非常に細かい定義を把握することは非常に難しいこともある、というニュアンスを示唆しています。メインの業務で扱う情報に関しては日々各人で理解に努めています。

共有ドライブの再設計

生産性を改善しつつインシデントが発生しにくい設計を試みました。結果、アトラエの共有フォルダは以下の5種類に大別されるようになりました。

ドキュメントを読み込まないと利用ができないドライブの構成はワークしないと思ったので、命名規則や権限管理を工夫しました。

例えば、

  • 基本的に社員が格納する場所は Employees_Only とする
  • 社外ユーザーに共有するには External_SharingPublic を利用する
  • 協業パートナーとの情報共有は With_Partners を利用する

とかです。直感的ですね。

この取り組みの結果、無秩序に乱立したフォルダはなくなりデータの格納先は統一され、結果どのフォルダに格納しても基本的に社員であれば全てのファイルや対応する URL にアクセスできるようになりました。また誤って社外の方が参加している Slack チャンネルにドライブの URL を添付してしまっても社員以外はアクセス権限がないので、情報漏えいが発生しないというセキュリティ上の効果もあります。

加えて外部共有のルールもできたため、社員が共有してはいけないデータをお客様に誤共有してしまうような事案も極めて発生しにくい構造となりました。

監視機構の導入

期待しないことが発生した場合すぐ気付けるようにするため、ドライブのログを全て SIEM に流しています。

例えば以下のようなケースです。

  • ファイルの重要度を格下げされた時
  • 一定期間にファイルが大量に削除された時
  • 怪しい拡張子がドライブに保存された時
  • 上位権限者としての操作があった時

いずれもインシデントに繋がるケースを想定して、Sumo Logic にログを収集、成形した上で Slack に通知をするような実装をしています。

また、Google Admin にあるアラートセンターなども活用し、外部共有されているファイルの数や種類なども別途確認できます。

細かい設定は他にもたくさんありますが書ききれないので、ログを利用して資産の利用状況が見えるようになりました。

移行の実施と浸透

現状を把握し、理想を伝え、実施して、邪魔なものを削除するということを小さい単位で繰り返しました。

この点についてはアトラエのみんなの協力のおかげで正直あまり困りませんでした(みんなありがとう)。

またマニュアルを作ったものの実はそこまで熟読されておりませんが(マニュアルの閲覧ログを見ています)、トラブルが発生したり運用の質問が来たりすることは現時点ではかなり少ないです。結果的に命名、権限、設定の設計に時間をかけたことは正解だったと思えました。

伸びしろ

冒頭に書いたような目立った課題こそ解決できましたが、まだまだ改善の余地があるのも事実です。

まず全部の情報にアクセスできるようになったはいいものの、必要なファイルに必要なタイミングでアクセスできているわけではない点です。アクセスできていることと、活用できているのはまた別の話です。Google は検索とレコメンドの会社なので、フォルダを掘るようにポチポチと GUI をクリックして欲しいファイルを探すのではなく、ドライブでの検索やレコメンドの最適化を積極活用するべきだと私自身は考えています。極論、それができれば共有ドライブの構成は適当でいいまであるかなとも思います。異論は認めます。人数や社内で発生するプロジェクトがもっと増えたタイミングでこの辺りを考え直そうと思います。

2つ目に外部共有についてです。アトラエからお客様に対してファイルをお送りする場合は、基本的にはドライブに格納しそのリンクをお送りするようにしています。一方でお客様の中にはドライブをはじめ、Google 周りのサービスに一切アクセスできない環境にお勤めの方もいらっしゃいます。この辺りも会社としてどのような方針を採用するか、どのようなツールを選定するかの検討の余地がありそうです。

長文にわたりやってやった感を出したブログになっていますが実際のところ課題はまだまだあります。アトラエらしさには時の流れとともに変わるものもあれば普遍的なものもあります。また見る人によって変わるものももちろんあります。なんならアトラエらしさって何なの?という問いすらも…。その前提に立つと今後クラウドストレージの立ち位置も大きく変化しそうです。いろいろな人と一緒にアトラエらしいクラウドストレージのあり方を追求し続けたいです。

最後に失敗系の学びも多かったので、それだけ共有させて下さいw

しくじり先生〜俺みたいなドライブ設計はするな!!〜

進め方、考え方において間違っていたこともありました。

完璧な設計が可能だと思っていた

クラウドストレージの設計において、承認、確認、その他の手動作業が発生するような設計になることは負けだと思っていました。ピースがよほど上手くハマらない限り、クラウドストレージで全部が自動化できることやサービスが具備している機能で全てが充足することはないはずです。

知見ある他社のご担当者の方々とディスカッションさせてもらいましたが、口を揃えて同じことを仰っていました。うまく設計できない自分の能力や情報不足が原因だと思い、設計に必要以上に時間をかけていた可能性もあります。可逆な施策であればもっとたくさん、早く回しても良かったかなと思います。

サービスの思想を理解しないまま進めてしまった

プロジェクトの早い段階でドライブや Google Workspace の設計思想を理解しておけばよかったです。

例えばマイドライブに重要な資産を保存することの是非はどうでしょうか。マイドライブは共有資産ではなく個人資産の側面が強いため、マイドライブへの保存を禁止している組織もあると思います。だってみんなの資産が「マイ」ドライブにあるの変じゃないですか。実際にアトラエでも長年、マイドライブどうするか問題に決着をつけられていませんでした。

紆余曲折あり、Google の方とディスカッションさせてもらう機会を得て、上記含めてドライブ の設計思想や目指している世界線Google 内でどのように使われているのかなどの話を聞かせてもらいました。

私の解釈が誤解を招く可能性があるのでここでは書きませんが、弊社では設定を変更した上でマイドライブを利用し続けることに自信をもつことができました。

サービス提供者が意図する方法と異なる使い方をして「このサービス全然イケてない!」というのはナンセンスで、思想を理解した上でそれができないならサービスを選定し直すべきという考えが自分の中で芽生えました。

運用を一人で行おうとしてしまった

賛否両論ありそうですが、クラウドストレージの管理者/運用者は2人以上設置したほうがいいと考えています。特に大きい組織であればなおさら。

理由は以下の通りです。

  • 上述のように、自動化しきれない(しない方がいい)運用作業が一定発生するから
  • 担当者が欠席した場合、共有や移動などのフローが滞りビジネスに影響を与えるから
  • クラウドストレージは会社で最も重要な資産なのに、監視や牽制なしに1人で管理するのはガバナンス上も望ましくないから

自分は最初1人ではじめてすぐその体制をやめました。単一障害点・ワイを回避。

Google ドライブの設定だけして要塞化した気になってしまった

要塞化の側面でドライブの運用をカスタマイズする場合、Google Workspace 全体で設定をする必要があります。

ドライブの設定だけして終わりというのがドライブ入門者あるあるです(私)。

最後に

アトラエでは一緒に働く仲間を募集しております。

「自分ならもっとイケてる構成にできる」という方、是非お待ちしております(そうではない方も、もちろんお待ちしています!w)

speakerdeck.com

強いチームを構成するエッセンス

アトラエにてデザイナーとして従事している藤澤です。

入社は2013年にGreenチームに配属され、 Greenサポートチーム(一瞬で異動)、 ほとんどはGreenUIチームのデザイナーとして働きました。

途中、新規事業チームにいったりGreenに戻ったりしながらも、 2017年Wevoxの立ち上げ時期からWevoxチームで働いています。

気がつけば長い時間をアトラエのチームと共にしてきました。 これまでの体験のなかで、強いチームを考えたり、 感じたりして過ごしたことを書いてみようと思います。

良いチーム?強いチーム?

「あなたは、どんなチームで働きたいですか?」

こう聞かれたときに、どんな答えを伝えるでしょうか。 毎日がポジティブになるような過ごしやすいチームですか? それとも、切磋琢磨しながら目的を目指すチームですか?

私自身、理想のチーム像が元々明確にあった訳ではありませんでしたが、 アトラエのメンバーと共に時間を過ごすほど、チームで働くことの意義を感じました。 可能性を信じ、仲間を信じ、時にはぶつかりあって…。 「全員がどんな組織を作りたいか?」という話題で、時には朝まで話し合う。

そんな日々を経て、もともと自分自身が持っていた「真の正しさ」へのこだわり。 そして、アトラエのコアバリュー アトラエの当事者として、  未来を他人に委ねることなく、  自らの意志と責任で理想の組織を創ろう」 と、つながっていると思っています。

私自身は「強いチームを創りたい」という想いが明確にあります。 真剣に考え、行動し、壁にぶつかり、また考えて…を、 繰り返して今日までを創ってきたのです。

強いチームとは、全員が「目指したい目標」に対して、 信念を持ち、お互いを信じあい、支えあえるチームだと私は思っています。 だからこそ、そういうチームを創り続けたいと思うのです。

平成ど根性版チーム・アトラエ

今となっては100人を超える上場企業になり、多くのステークホルダーから応援されている組織となりましたが、未上場のアトラエは、少人数かつ全員がチームと言えるほど、全員の顔が見える社内体制でした。

事業外のことですら何時間もかけて意見を出し合い、 時には叱咤が飛ぶほどの全社ミーティングがあったり、悔し涙を流す場面もありました。 自らのちからで事業を大きくしたいという想いが溢れ出てやまないくらい、 勢いがあり余った小さな組織だったことを覚えています。

当時わたしは所属していたGreenUIチームですが、アトラエを支える唯一の事業でした。 そこで任された、成長につなげるための工夫や、長い時間をかけた議論を経て、 「チームが心を一つにできれば、なんだってできるんだ」と、心の底から確信できました。

そして、アトラエチームの全員が、より強いアトラエを目指して燃えていました。 その頃のブログを読んでいただければ、 わたしが如何に活き活きと働いていたのかが伝わると思います。

事業としては追い風、組織としては向かい風

昔から「アトラエに新しい事業を創るんだ!」と、社員全員で新規事業を考えるイベントを定期的に行っており、新規事業を考えていたうちに、明確に手ごたえを得たWevoxの前身「法人ドック(仮)」が生まれたのです。 しばらくしてWevoxが本格的に事業化することになりました。

Wevoxの立ち上げから少しづつ中途採用も増え、 あっという間に50人…100人と…メンバーが増えたのです。

当時はエンゲージメントという言葉すら浸透しておらず、この働きがいを組織の指標にするというコンセプトは「働き方改革」や「ストレスチェックの義務化」にという社会の波に乗り、今ではエンゲージメントといえばWevoxというくらい馴染んできたのではないでしょうか。

そんなWevoxチームはしばらく社内表彰も独占し続けるくらい、 熱量の高いチームのまま大きくなっていきます。 コロナ禍によるリモートワーク時代へのスイッチのなか、どんどんチームは大きくなっていく。 あっという間に「どうあるべきか」を、毎日顔を合わせて話をするということが難しく感じるようになる時代になってしまったのです。 ずっと隣にいたような感覚で働いていたアトラエにも、チームの変革期が訪れていました。

目的を明確にするだけで、チームは変われる

一定期間、Wevoxチームから離れていた私でしたが、 2021年10月に、再度Wevoxプロダクト改善チームに配属になりました。 その頃には70人以上の正社員メンバーだけでなく、 多くの業務委託メンバーもアトラエで働いている状態。 直近で入社したメンバーに至っては、話す機会すらないというケースもちらほら。

プロダクト改善チームに与えられた役割は、プロダクトを改善することと、依頼をこなすこと。 チーム人数は9名。社歴はほぼ5年未満。入社したばかりのメンバーが3名。 そして、社歴は長いものの令和版アトラエに全く追いついていないデザイナーのわたし。

このチームはこれまでの間、「とにかく改善をするために、目の前の業務を頑張る!」という状態で、何をどう頑張るのかが明確になっていないようでした。 そもそも依頼の量が多すぎる上に、基本受け身なので、価値を創るということに目標が置きづらい。 そして、配属の変更があり、今までチームを引っ張ってくれていたメンバーが居なくなってしまった!という状態でした。

「事業の価値を最大化したい!」と想いが溢れ出てやまないくらい熱量に満ち溢れた状態が強いチームになれるエッセンスだと信じているわたしは、この事実を受け止めつつも、まず何からはじめたら良いのだろうかと考え「全員が心から納得できる目標を絶対に達成する」という強い意志をもつことがまずは大事であると考えました。

インタビューや議論を経て「圧倒的にタスクを消化すること」をまず土台目標として掲げました。 この目標を目指すことで、まだアトラエ入社歴も浅いメンバーは、 依頼/機能実装をよりこなせるようになる上に、余白を持てるようになる。 そのために、とにかく行動量を追い、日々昨日の自分を超えていけ。そんな目標です。

この土台目標を達成したら、 全員で追える数字を掲げ、世の中に価値を届けられるインパクトの出せるチームになる。 という、メンバーが心からワクワクできるような目標を設定しました。

実務ゼロのスクラムマスター爆誕

ところでタスクの細分化とか、行動の可視化ってどう管理するのが良いのだろうか? 仕組み的には、最小単位まで分解したタスクをポイント化したら分かりやすいだろうか? そんなことを悩んでいると鶴(カタミー)の一声がありました。

「それってスクラムですよね?」

改めてスクラムを思い返してみると、そういえばそうだ。その通りだ! ここから、スクラムの道が始まりました。 スクラムの本を読み直し、スクラムマスターブックも購入し日々のバイブルとして読み込んだセクションをSlackでみんなにシェアしたり、チームにフィットするように当てはめる。 上手く機能するイメージが湧き出てきて、この作業は体験設計を考えるようで、とても楽しかったです。

そして、デザイナーという役割をほぼ放棄して、スクラムマスターを務めることになりました。 エンジニアチームにいる、ただのスクラムマスター。 自虐ではないですが、日々わたしは何もしていません…という枕詞を外せない時期もありましたw

アトラエWevox開発チームにおける、フィットしたスクラムは以下のようになりました。 毎日15分以内で終了するレベルのデイリーショートミーティング。 持っている役割・業務を達成するために、何をし、何をするのか。成し遂げる上での障害はないか。 常に「目標達成のために何をするのか」ということにメンバーの気持ちが向くように、ファシリテーションを心がけました。

また、発表内容はデイリースクラムも含め、メモをして、必要な対話の時間をセットする。 それ以外の時間では、スクラムを行う上で知っておきたいTipsを日々投稿してみたり、このチームが目標に向かって活力あふれる状態を目指しました。

その後、自分たちが持っていた依頼をこなしながらも、チームとして大事にしたい機能のリリースを役割分担して前に進めるチームとなりました。「今期は絶対に自分たちがベストチームだ!」と言えるくらい自信とやる気に満ち溢れたチームになっていたと感じました。 この経験から、難しいことや、小手先のテクニックは必要なく、やはり目標への納得感や、自分の興味関心との繋がりが、今日・明日の自分の行動に納得感を置けるきっかけなのだと確信しました。

強いチームにするために行った4つのこと

今私はWevoxでのGrowthHackersというチームにジョインさせていただいてます。 このチームは、わたしたちがもっているコンテンツやプロダクトの価値を、より多くのお客様に届けるための役割を担っています。

このチームではもうすぐ働いて1年が経ちますが、今までの経験を活かし、チームのキックオフやグラウンドルールなどにこだわり、対話や相互理解の時間を大事にしています。 振り返りも本質に向き合いながら真剣に話し合えることが、わたしにとってもやりがいを感じる時間になっているので、とてもエンゲージメント高く働けています。 特にこのチームは定量的な目標すら自分たちで見つけ出し、価値に繋げていく必要があるので、各自が当事者意識を持ち、自走できることが大事です。

意識して進めたことは以下の4つです。

1. 自分がこのチームで目指したいことを個人で発表する。

これにより、その人が目指すことや、価値観をしることができるので、 この人に相談してみようと思い出せる機会が増やせます。

2. 全メンバーで相互の期待値の共有をする

各個人の目指したいことを知った上で、お互いに期待していることを共有する。 また、逆に任せてほしいと思うことも共有する。 この相互の期待値を交換することによって、チーム内での協力体制が強まります。

3. オフラインも、オンラインも、コミュニケーションのかたちを大切にする

オフラインで顔を合わせて体験を共有するもよし、 オンラインでゲームをするもより、 どちらが良いとかではなく、どちらもやる。 この交流をすることで、確実にチーム内のコミュニケーションがスムーズになるスピードが速かったです。

特に、キャンプ・オンラインゲームというような体験を共有することで、心理的安全性も大きく高まったと思っています。 また、会社の近くにくることが可能なメンバーと毎週金曜日に出社デーにしよう!という約束をしてランチタイム含め顔をあわせるようにしています。 交流の量や質は、チームづくりに置いて非常に大切なポイントだと感じています。

4. 振り返りの時間も大切にできるようにセッティングする

Wevoxチームでは、各チームのエンゲージメントスコアを月に一度チームでの振り返りに使っています。振り返りの方法は各チームに任されており、やりかたはチームそれぞれですが、圧倒的な盛り上がりを見せています。真剣に話をする傍ら、感情あふれるスタンプやコメントをする文化が醸成されており、みんなが楽しみながら振り返りできていることが、毎月とても大きい収穫となっています。

5. 柔軟に変化する

GrowthHackersは、主に他チームと兼任しているメンバーが多く、ミーティングの時間を合わせることがなかなか難しく、どうしたらもう少しこのチームに時間を費やしてもらうことができるんだろう?と、考える時間が多かったです。 この問題に対して、先の予定をブロックしておくということは大前提として大切だと捉えています。 また、今週やりとげたいことが、使える時間があふれてしまう場合は、一人で抱え込まず、人に任せるということを全員で推奨しています。 また、もっと各チームとのハブになったほうがバリューが出せるのではないか?と感じたのなら、そうチームも変化していけばいいと考えています。

他にも紹介しきれないくらいの小さな工夫を詰んでいますが、なによりも「わたしたちは何をすべきか」ということにこだわり、必要があれば変化する、そしてお互いにそれを許容しあえる関係性づくりが大事だと、改めて感じているところです。

自分が納得できるまで、熱意をもって変えていこう

最近はプロジェクトマネジメントを担うメンバーも増えてきたアトラエ。 ですがアトラエに取ってジョブはただの役割に過ぎないのです。

一番大切なことは、「仲間と共に世界中の人々へ向けて価値を生み出し、それにより組織は強くなる」こと。 最大限の知恵や方法の中からベストを見つけて、結果に繋げていこうという考え方で全てのチームが努めています。

そのためには思っていることをお互いに開示しあえる関係性、任せられる関係性が重要です。

納期・スピード感・約束したことを守る視点だけでなく、 「これで本当に使ってもらった人はハッピーになれるのか?」を考える選択肢だって必要なときがあります。 一緒にプロダクトを成長させていくこと、そしてユーザーにフィットさせていくことは、本当にやりがいのある仕事です。 チームはそういう積み重ねで出来上がっていくもので、決してテクニックでは生れません。

そもそもの思想には、「自らがアトラエの当事者」というコアがあります。 だからこそ、「何をすればいいのか」すら、自分たちで決める。 ここが自律分散型組織の難しさでもあります。

とにかく、人に寄りかかったり、すがったりしながら成果が出せない組織なのです。 その代わり、誰よりも信念を持ち、世の中に価値を出すんだと働いた人は、やはり応援されますし、そういう人のいるチームは活力が漲っていく。 チームは創るのではなく、熱意を持って集まったチームは、ほっておいてもチームになるのです。

もちろん、プロジェクトの指針や目標はある。 でも、その指針や目標に納得感がないのであれば、課題感を持った人が勝手にそれを変えていい。 それがアトラエの将来につながりますし、そのための裁量も十分にあります。 だからこそチームで最大の成果を出して、組織をより強く盛り上げていくことに全力投球できる。 それがアトラエの働き方です。

さいごに

今日は今までの経験で考えた「強いチームになるためのエッセンス」についてまとめてみました。このブログを読まれたかたで、アトラエのメンバーになることに興味を持ってくださった方がいたら、ぜひ、以下リンクをご覧ください。 良い仲間と、良いプロダクトを創る、アトラエのメンバーを募集しています! よろしくおねがいします!

speakerdeck.com

atrae.co.jp

フロントエンドの"ちょうどいい"自動テストのはじめかた

Wevoxのフロントエンドエンジニアをしているタガミです。最近はmonorepo構成に移行中のWevoxフロントエンドのテストやデザインシステムなどをいい感じにしようとしています。

この記事では、WevoxというSaaSプロダクトのフロントエンドにおける自動テストの話をします。Wevoxはリリースから5年以上が経過し、チームのメンバーも増え、またソースコードも巨大化しています。そんな中でフロントエンドも"式年遷宮"をして、改善を繰り返しています。中にはソースコードをガラッと変えるようなリファクタもあり、担当するエンジニアにとってはデグレの心配が付き纏います。そんな日々変化するフロントエンドを支えるのが自動テストです。

Wevoxの開発チームは決して大人数ではありません。そんなチームでも品質の改善のために一歩ずつ改善しつつある経験をもとに、フロントエンドの自動テストポイントをいくつかお伝えします。

結論

まずはじめに結論から。これらは「ちょうどいい」自動テストのために必要なポイントです。

  • 重要度をもとにアプリケーションごとに書くべきテストを決める
  • テストの方針をドキュメントに起こす
  • Presentation/Logicを分離して、テストしやすさをあらかじめ担保する
  • 重要な機能のみテストを厚めに書く
  • チームのエンジニアのテスト習熟度に応じてテストツールを利用する

ちなみに、ここでいう「ちょうどいい」とはROIがプラスになり、かつチームの成熟度に適したテスト運用・実装の度合いを指します。自動テストについては「テストカバレッジ100%派」や「お金を産む新規実装の方が大事派」などさまざまな考えがありますが、それらをひっくるめて適切な粒度・優先度で自動テストを書くことがチーム開発においては重要です。

この記事の内容は、例えばTDDを採用していないようなチーム、あるいは自動テストに慣れていないチームでも自動テストを適切に書き始める際の参考になることを目指しています。

テストバランスと重要度(Severity Level)

みなさん、テストは好きですか?

自信を持って「はい!」と答える方はあまり多くないかもしれません...。自動テストを書くということは、短期的には実装工数が増えることを意味します。もちろんテストコードを書くのに慣れていたり、すでにテストコードが充実しているプロダクトでは負荷は少ないでしょう。しかし、はじめのうちは負担になりがちなテストをちょうどいいバランスで書くためには、どこにどれくらいのテストを書くべきなのでしょうか。

テストのバランスについては"Testing Pyramid"や"Testing Trophy"などのベストプラクティクスが存在します。

一方で、各テストには別の指標、重要度(Severity Level)も存在すると考えています。ここでいう重要度とは、「テスト対象のアプリケーションにおける機能や画面ごとに存在する、ビジネス上あるいは技術上重要な度合い」を意味します。

例を挙げて考えます。例えば、SNSアプリケーションにおいて3つの機能のうちいずれが重要度が高いでしょうか?

1. ユーザー登録機能
2. いいね!機能
3. 課金機能

さまざまな観点があると思いますが、もしここにバグがあったときにサービスに致命的である度合いと考えると「3 > 1 > 2」の順番に重要と考えられます。もしも課金機能にバグがあり、クレジットカード情報の漏洩や課金ミスが起きた場合、サービス運営に致命的なダメージになり得ます。

ここでポイントは、重要度はテストそのものとは全く分けて考えるべきということです。例えば重要度は高いが、自動テストがしづらいという機能や画面があったとしても、それはテストをしない理由にはなり得ません。インシデントを防止することは新たな機能を実装することと同じくらい、サービス運営にとっては大事なことです。

自動テストにはテストのバランスと、重要度が存在し、それらをマトリクスに起こすとこのようになります。

重要度を高い順から3~1の3つにわけ、さらにTesting Pyramidを参考にUnit Test > Integration Test > E2E Testの順で重みづけをしました。そしてそれぞれに対応する機能テストが点数が高い順に対応優先度が高いとしています。これはあくまで一例なので、それぞれのポイントは各サービスごとに異なります。

各テストへのチームの温度感を探る

現在の自分たちのプロダクトと向き合ったときに「最低限やっておきたいライン」「チャレンジングだけどやりたいライン」「後回しでもいいライン」の3つがあると思います。これはチームメンバー、環境など様々な要因があります。これはテストに限らず、品質改善という広い視点で考えられます。

例えば、「最低限やっておきたいライン」でいえば

  • eslintやprettierなどを使った静的解析(Static Test)
  • TypeScriptをstrict: trueにして、厳密な型チェックを適用する
  • Jestでの単体テスト

など。

また、「チャレンジングだけどやりたいライン」だと

  • StorybookのUIカタログ
  • Snapshotテスト
  • VRT

などチームのナレッジ、経験を超えて「やってみたい」というモチベーションを含んだ選択肢が出てくると思います。

一方で、現状を鑑みて「後回しでもいいライン」というのも各チームには存在しています。例えば小規模のMVPプロダクトにおいて「テストカバレッジ100%」などは、プロダクト開発のアジリティを落とすことになりかねません。

チームを主体として考えたときに「自分達はどこまで手を伸ばすべきなのか?」を考えることは、今後テストに対してオーナーシップを持つための打一歩として有効です。

テストの方向性をチームで考える

どのテストを、どの機能に重点的に書くべきか?を考えることはチームのテストに対する考え方にも影響します。

MVPの小さいアプリケーションの場合や自動テストを専門に書くQAエンジニアがいる場合を除いて、開発者全員が自動テストを書けることが理想です。いま自分が書いているソースコードがアプリケーションに対してどれくらいの重要度なのか?はたまた変化度合いがどれくらいありそうなのか?は開発者にしか分かりません。プルリクエストのレビューでもチェックはできますが、仕様・背景を理解している当人がテストを書くべきか?の判断ができることは、スケールする自動テストの重要なファクターのひとつです。

Wevoxのフロントエンドチームで実際に使ったドキュメントです。

どのようなケースでテストを書くべきか、またなぜテストが必要なのか?の理由、そして具体的にどのような点をテストすべきか?の3点を重要度ごとに記載しています。

自動テストに関する方針は曖昧になりやすく、属人的なため、ドキュメンテーションが重要です。テストの必要性に関する背景は特に重要で、「なんでこんなことテスト書かないといけないんだ...」という不満を抱えないよう説明あるいは議論することが、テストの方向性をそろえる第一歩だと考えています。

では、このようなドキュメントは誰が書き始めるべきでしょうか?

自動テストに慣れていない、あるいはモチベーションが高くない方が書いても意味はないでしょう。一方でテストに成熟したプロが「俺が考える最強のテスト!あれもこれもテストするんだ!」のようにちょうど良くないドキュメンテーションは、チームによっては忌避される恐れもあります。チームの成熟度に応じて適切な方針が策定できるメンバーがドラフトを作成すべきではないでしょうか。

ただし、ドキュメント化しただけでは意味はありません。文字におこしたものをチームで議論・意見を出し合って、それが適切かどうか?を考えることが重要です。また実際の開発の際に、ドキュメントの内容が適切かどうか?を書いた本人含めて振り返ることも、ドキュメントが意味あるものになっているかどうかのポイントです。

テストしやすいコード

テストをチームで始める際のポイントについてお伝えしてきました。ここからは具体的にテストを書き始める際にWevoxチームの動きをいくつかご紹介します。

1. ロジックとの分離

まずは一般的にBetter Practiceといわれているロジックと見た目の分離です。保守性の高いコードのポイントとして「テストしやすさ」が挙げられます。WevoxのフロントエンドではReactを採用しており、ここではViewとそれに関わるロジック(以下ViewModel)を分離しています。ViewModelはCustom Hooksとして定義しており、Viewつまりコンポーネントはそれを呼び出し、Custom Hooksが提供するロジックをJSXにそのまま渡しています。

Viewコンポーネントの中にはロジック(ステートや関数)は持たせていません。Custom Hooksを呼び出し、その返り値を参照しています。

// View
const UserPage = () => {
  const [userInfo, updateUserInfo, deleteUser] = useUserPageViewModel();

  return (
    // ViewModelが返す値を使ったView
  );
};

Custom Hooksの中身はこのようにステートや関数を持ち、ロジックを集約しています。

// ViewModel
const useUserPageViewModel = () => {
  const [userInfo, setUser] = useState<User | null>(null);

  useEffect(() => {
    getUser();
  }, []);

  const getUser = () => {}

  const updateUserInfo = () => {}

  const deleteUser = () => {}

  return [
    userInfo,
    updateUserInfo,
    deleteUser,
  ]
};

こうすることで、ComponentへのUnit Test、ViewModel(Custom Hooks)へのUnit Testが描きやすくなります。ViewModelをCustom Hooksにすることでテストも純粋な関数へのテストと同程度にシンプルに書くことができます。また、フロントエンドにおいてよくあるバグあるいはデグレのほとんどはロジック部分にあると感じており、そのロジックのみを狙い撃ちしてテストかけることは品質の担保にもなります。

ViewModelへのテストはこのようになります。

describe('useUserPageViewModel', () => {
  describe('when user is null', () => {
    result = renderHook(() =>
      useUserPageViewModel()
    ).result;

    it('return null', async () => {
      await waitFor(() => {
        expect(result.current.getUser()).toBe(null);
      });
    });
  });
});

Custom HooksのテストにはrenderHookを使用して、result.current.hoge()のようなかたちでそのHooksが返す関数や値をテストしています。また、Hooksの返り値だけでなく、ViewModelの内部で別のHooksや関数を読んでいること自体をテストしたいケースは以下のようにテストを書いています。

describe('useUserPageViewModel()', () => {
  const showSnackbar = jest.fn();
  (useSnackbar as jest.Mock).mockImplementation(() => ({
    showSnackbar: showSnackbar,
  }));

  describe('when user is null', () => {
    result = renderHook(() =>
      useUserPageViewModel()
    ).result;

    result.current.getUser();

    it('call other hooks function', async () => {
      await waitFor(() => {
        expect(showSnackbar).toHaveBeenCalledWith('ユーザーが見つかりません');
      });
    });
  });
});

ここでは、想定した文字列が画面に表示されること自体はテストしていません。その代わりに別のCustom Hooksが提供する関数に対して想定した文字列が引数に渡され呼ばれることだけをテストしています。これができる前提には、ViewModelのようなロジックとViewのみを扱うコンポーネント疎結合になっていることが必要です。

もしこれらが結合していると、テストへのハードルがぐんとあがり、結果的に自動テスト書く文化が薄れてしまいます。テストしやすいコードを書くことは自分だけでなく、チームのテスト文化醸成にも役立ちます。

一方で、View単体のテストやロジックと結合した場合のテストも必要になってきます。では、それらについて説明します。

2. "Pure" View Componentを作っておく

WevoxではView Componentの単体テスト自体は積極的には書いておらず、代わりにStorybookでUIカタログを作っています。またTypeScriptでコンポーネントのPropsをある程度セキュアに保つことができているため、Atomic ComponentでいうところのAtomにあたるコンポーネント単体テストは書いていません。StorybookでUIカタログを整理するメリットはカタログとしてのそれだけでなく、Storycapなどを使ってSnapshotテストやVRTを始めやすくなります。

ただ、Storybook自体も書くメリットがないとなかなかカバレッジを上げることが難しいです。

ただし上述のロジックとの分離をもう一歩進めることで純粋なView Componentを作って、Storybook駆動開発を促進することができます。先ほどのUserPageコンポーネントを例に挙げます。

const UserPage = () => {
  const [userInfo, updateUserInfo, deleteUser] = useUserPageViewModel();

  return (
    // ViewModelが返す値を使ったView
  );
};

このコンポーネントをStorybookで扱おうとすると、内部にCustom Hooksとの結合があるため少し記述が面倒です。

そこで、下記のように純粋に見た目だけを扱うコンポーネントを"同一ファイルに"記述することで、Storybookが目的とするViewのみをUIカタログに登録することができます。

// Containerから呼び出されるPresentational Component
export const UserPageTemplate = ({
  userInfo,
  updateUserInfo,
  deleteUser
}) => (
  // UserPageの中にあったView
);

// Custom Hooksと接続するContainer Component
export const UserPage = () => {
  const [userInfo, updateUserInfo, deleteUser] = useUserPageViewModel();

  return (
    <UserPageTemplate
      userInfo={userInfo}
      updateUserInfo={updateUserInfo}
      deleteUser={deleteUser}
    />
  );
};
import { UserPageTemplate } from './UserPage';

export default {
  title: 'Pages/UserPage',
  component: UserPage,
} as Meta;

const Template: ComponentStory<typeof UserPage> = (args: React.ComponentProps<typeof UserPage>) => (
  <UserPageTemplate {...args} />
);

Container/Presentational Componentの分離はコンポーネント単位では一般的に行われますが、Container Componentそれ自体もさらに2つに分離することで、Storybookへの登録がしやすくなります。さらに、Storybookに限らず、コンポーネントとしての単体テストもしやすくなるでしょう。

これらはテストを書く/書かないに限らず、実装時点で分けておくことで後からテストを追加しやすくなるという点ではどのようなチームでもできる工夫のひとつだと思います。

3. Integration TestとUnit Testを分けて考える

さて、ここまでコンポーネントやロジックの単体テストについてみてきました。Wevoxでもフロントエンドにおける結合テストは書いています。ただし、上述の通り単体 > 結合というバランスで書いているため、アプリケーションにおいて「絶対に落としてはいけないところ」を中心にテストを書いています。例えば機能Aがそれにあたり、機能Bや機能Cは比較的重要度が低い場合以下のようにテストの優先度を決めています。

重要度が高い機能Aに関しては、単体テストはもちろん、結合テスト、さらにE2Eテストも書いています。これは一見して"ちょうど良くない"過剰なテストと思われるかもしれませんが、アプリケーションにおけるテストの重要度を鑑みてこのように方針を決めています。

結合テストにもJestを利用しており、以下のようにテストを書いています。単体テストで担保しているロジックとViewを横断してチェックするようなイメージです。

describe('Important Form', () => {
  it('should show user info update form', async () => {
    renderComponent({
      userInfo: { ...mockUserInfo, text: 'test' },
    });
    const textInput = screen.getByTestId('input-form');
    expect(textInput).toHaveTextContent('test');
  });

  it('should be onChange input form', async () => {
    renderComponent();
    const textInput = screen.getByTestId('input-form');
    userEvent.type(textInput, 'my user');
    expect(onChangeInfo).toBeCalled();
  });
});

これとは別にView Component、ViewModelの単体テストを書いています。

もしかすると上記のような「ボタンを押したらonChangeHogeが動く」のようなフロントエンドの流れのみをテストする場合があるかもしれません。ただ、それだけだとonChangeの中身で何が起きているか?だったり、ボタンがどう動くか?などのケースまではテストでは担保できません。View/ロジック/ユーザー操作それぞれをテストすることで各レイヤーの仕様をテストで保証しています。

「自分達のサービスではどこを手厚くテスト書くべきだろうか?」をエンジニア、その他の職種のメンバー含めて議論することをおすすめします。そして、手厚く書くべき機能に関しては結合/単体をそれぞれ担保するように自動テストを徐々に増やすことを目指します。

WevoxではさらにE2Eテストも利用しています。ただ、これにはcypressやseleniumなどのコードベースのものではなく、ブラウザ上でテストを設定するAutifyというサービスを利用しています。これについて説明します。

4. チームのテスト成熟度に応じてツールを利用する

バックエンド含めて元々単体テストはいくつかあったものの、ブラウザ上での一貫した挙動確認は人力でテストしていました。ただし、開発メンバーが増え、リリース頻度が高くなるなかで、アジリティを落とすことなく、バグ・デグレを減らす方法としてE2Eテストの導入を検討していました。

ただし、Wevoxのサービスの特性上、日にちをまたぐユーザー操作などのテストができることが求められ、またE2Eテストの実装経験があるメンバーがほとんどいないという状況でした。そんな中で国内外のE2Eテストツールを検討し、最終的にAutifyに決定し、1年以上利用しています。

ここではAutifyの素晴らしさは詳しくは述べませんが(また別の記事で書きます!)、チームのテスト成熟度次第ではこのような「ショートカット」の利用は有効です。

テスト経験があまりないチームで無理にテストを書いたり、その環境構築をして、メンバーからテストに対する嫌悪感を抱かれるケースをよく聞きます...。その結果テスト環境はあるものの、誰も自動テストを書かない、結果としてかたちだけの「雰囲気自動テストが回っている」というような状況になっていきます。

そのような事態を避けるためにも、Autifyのようなツールを利用してテストへのハードルを下げていくほうがいいでしょう。さらにAutifyの場合、クロスブラウザテストやモバイルアプリのE2Eテストなどにも対応しています。クロスプラットフォーム前提のアプリケーションの場合にはなおさら有効な手段だと思います。

最後に

ここまでWevoxのケースを交えながら"ちょうどいい"自動テストのはじめかたについてお伝えしました。

「はじめかた」と言っても、ほとんどの方はすでに各々の方法で自動テストを書いていることと思います。ただ、それを「チームの文化」として浸透させられているケースは少ないと思います。TDDのような手法をルール化したり、ペアプロするなど様々な方法はあると思いますが、最終的にはモチベーションが高いエンジニアが旗振りをする、それに限ると思います。多少なりとも手間になる自動テストを、いかに「プロダクトを改善するためにやるべきこと」として浸透させられるかは、チームのためにアクションできる方、またそれを応援してくれるメンバーがいるからこそだと思います。

弊社アトラエではエンジニア誰しもがそのように「チームづくり」に日々コミットしています。またプロダクトをより良くするために、自分がやるべきと思ったことを手を上げて進められる文化があります。テスト大好きな方も、そうではない方も、カジュアル面談などでお話ししましょう!

こちらのスライドではより詳しくアトラエについて知ることができます!

speakerdeck.com

atrae.co.jp

Yenta Android版にJetpack Composeを導入しました

こんにちは!ビジネス版マッチングアプリYentaのエンジニアをしている@bull です。

私は2021年7月にアトラエにジョインし、YentaのAndroid版の開発・運用・保守を担当しています。

YentaのAndroid版では2021年11月から各画面を少しずつJetpack Composeで書き換えています。

そこで導入に至った経緯について説明していこうと思います!

※導入から暫く経ちますが、新機能との兼ね合いもあり、最近は着手ができていないです

Jetpack Compose導入に至った経緯

Yentaの機能が増えていくにつれ、フラグメントの切り替え周りが複雑になっていたり、Viewの変更が複雑になってきているという状況でした。

そこで、保守の観点でリファクタリングを行わないといけないと判断しました。

その1つとしてJetpack Composeを導入するかどうかという話が出てきました。

アトラエではWebのプロダクトが多いため、ReactやFlutterなどの宣言型UIに慣れた人の割合が多くいます。

Greenでは、より生産性の高い技術組織になることを考え、Webフロントとモバイルアプリの実装を行き来できるようにモバイルアプリの開発にFlutterを導入しました。

atraetech.hatenablog.com

Yentaでも同様に、お互いの行き来がしやすく、Greenのように生産性の高いエンジニア組織になるため、WebフロントとAndroidの開発を両方とも宣言的UIで行えるよう、このタイミングでJetpack Composeの導入を決断しました。

導入してみてよかったこと

Viewの切り替えが楽

これまでは以下のように画面(Fragment)の表示・非表示でremoveしてaddするという操作を行っていました。

   private fun switchFragment(state: PageState) {
        val transaction = childFragmentManager.beginTransaction()

        var mainFragment = childFragmentManager.findFragmentByTag(MainFragment.TAG) as? MainFragment
        if (state == PageState.MAIN) {
            if (mainFragment == null) mainFragment = MainFragment.newInstance()
            transaction.add(R.id.fragment_container, mainFragment, MainFragment.TAG)
        } else mainFragment?.let {
            transaction.remove(it)
        }

        var signUpFragment =
            childFragmentManager.findFragmentByTag(SignUpFragment.TAG) as? SignUpFragment
        if (state == PageState.SIGN_UP) {
            if (signUpFragment == null) signUpFragment = SignUpFragment.newInstance()
            transaction.add(R.id.fragment_container, signUpFragment, SignUpFragment.TAG)
        } else signUpFragment?.let {
            transaction.remove(it)
        }

        var loginFragment = childFragmentManager.findFragmentByTag(LoginFragment.TAG) as? LoginFragment
        if (state == PageState.LOGIN) {
            if (loginFragment == null) loginFragment = LoginFragment.newInstance()
            transaction.add(R.id.fragment_container, loginFragment, LoginFragment.TAG)
        } else loginFragment?.let {
            transaction.remove(it)
        }
    }

これをJetpack Composeでは、removeしなくても呼び出したいComposableを呼び出すだけで良いので直感的な画面の切り替えが可能になりました。

when (screen) {
    PageState.MAIN -> {
        MainScreen(
            …
        )
    }
    PageState.SIGN_UP -> {
        SignUpScreen(
            …
        )
    }
    PageState.SIGNED_IN -> {
        LogInScreen(
            …
        )
    }
}

Preview、LiveEditが便利

Preview、LiveEditはとても便利です。

これまでViewを確認をしたい場合は基本的にアプリをBuildしてViewの確認をしていたと思います。

xmlでも確認できると思いますが、xmlによっては確認できないものもあるでしょう。

Preview機能を使えば、アプリ全体をbuildしなくてもUIの確認ができるのも魅力の一つです。 1つのファイル内でいくつものPreviewをつけることも可能なので、様々なパターンを網羅したい場合にも役に立ちます。

最近ではLive LiteralsからLiveEditに変更になりました。 これらはbuildせずとも変更を即座に適応してくれるホットリロード機能です。

Live Literalsでは文字の大きさ変更、テキスト変更くらいしか対応していなかったのですが、Live Editでは、コンポーネントを配置した時にも反映されるようになりました。 これを使うことにより、効率良いUIの構築が可能になるでしょう。

備考:Android Developers, Twitter

Viewが以前より隠蔽された

これまではActivity、Fragmentごとにパーツをそれぞれ作っており、ViewBindingを用いてActivity、Fragment内で値をセットしていました。

この場合、Viewを直接いじれてしまうので、どのプロパティをいじるのかView側で制御できませんでした。 Jetpack Composeに変換すれば、引数でPropsを渡すことが可能になるため、引数でどのプロパティをいじりたいか指定することが可能になります。

それにより、Activity、Fragmentからむやみにコンポーネントを変形することができない状態となり、保守性が担保されるようになります。

つまり、Activity、Fragmentからは引数を渡してあげるだけでコンポーネントを作成できるのです。

@Composable
fun SignUpLayout(
    onClickTermDetail: (uri: String) -> Unit,
    onClickPolicyDetail: (uri: String) -> Unit,
    buttonContents: @Composable () -> Unit
) {
    WelcomeDescription(stringId = R.string....)
    HeightSpacer(dp = 24.dp)
    buttonContents()
    HeightSpacer(dp = 24.dp)
    LoginHelpDescription(onClickTermDetail, onClickPolicyDetail)
    HeightSpacer(dp = 36.dp)
}

また、Atomic Designのように階層に分けてコンポーネントを分けて依存関係を片方向にすることで、一つ当たりのコンポーネントの責務も少なくなりました。

その結果、Activityが知ることは必然的に少なくなり、コンポーネントの記述量も減って開発しやすくなりました。

ネストを考える必要がない

Compose ではネストされたレイアウトを効率的に処理できるため、このようなレイアウトを利用した複雑な UI 設計が可能になります。Android View ではパフォーマンス上の理由でネストされたレイアウトを避ける必要がありましたが、Compose ではこの点が改善されています。

引用:AndroidDevelopersより

この文章より、これまでxmlを用いていた時はパフォーマンスのことを考え、ConstraintLayout、RecyclerViewなどの重たいレイアウトのネストを避けて実装していましたが、その懸念がなくなるのは助かります。

備考:パフォーマンスとビュー階層 | Android デベロッパー

結果的にパフォーマンスも少しずつですが改善してきています。

苦労したところ

ドキュメントが少ない

やはり、xmlと比べてドキュメントの数が少ないので苦労しました。

Android Developersにない情報は、Twitterを駆使して探したり、Youtubeで動画を探したりもしました。 大抵の場合、海外の英語ドキュメントに書かれているので、日本語の検索でヒットしなかった時は英語で調べてみることをお勧めします。

コンポーネント再生成のタイミング

どのタイミングでコンポーネントが再生成されるのかといった再コンポーズの理解を深めていかないと、無駄な処理が走ってしまってJetpack Composeの良さを活かせないです。

これまでの考え方でいくと親のコンポーネントで更新処理が走ると、子のコンポーネントも更新処理が走ると一見思うでしょう。

でも実際は更新が行われたコンポーネントのみ更新されるため、子のコンポーネントの再コンポーズはスキップされます。 そして、親のコンポーネントで変更した値を引数として子に渡している場合は再コンポーズされます。

例えば、以下のような親と子のコンポーネントがあるとします。

fun NameBox(){
    var name by remember { mutableStateOf("")}
    var description by remember { mutableStateOf("")}
    var birthday by remember { mutableStateOf("")}

    Button1(onClick = {name += "Name "})
    Button2(onClick = {birthday += "BirthDay "})

    NameText(text = name)
    DescriptionText(text =description)
}

NameBox()を呼び出すと全てのコンポーザブルが再コンポーズされると思いきや、 変更が加わったコンポーネントのみが再コンポーズされます。

詳細を言うと、nameに変更が加わった場合は、Button1とNameTextが再コンポーズされ、 descriptionに変更があった場合、DescriptionTextだけが再コンポーズされます。

このように無駄なところで再コンポーズをし、パフォーマンスを下げないようにするという部分に気を遣いました。

つい最近では、再コンポーズまたはスキップした頻度をLayout Inspectorから確認できるようになったので有効活用しようと思います。 興味ある方は以下からキャッチアップしてみてください。

備考:Get recomposition counts

細かい部分

やはり、実装していて何度も気づくことがたくさんありました。 最初にAndroid Developersなどを熟読するべきだったと後悔しました。

以下は最初から気づいておけば良かったと思った一部です。

  • Element関数はModifier型の引数を持ち、名前をmodifierで、最初のオプショナルパラメータでなければならない
  • 再コンポーズは可能な限りスキップする
  • StateとEventを明確に分ける
  • UI ロジックと UI 要素の状態を管理する状態ホルダーを明確に分ける
  • ViewModel インスタンスを他のコンポーザブルに渡してはいけない
  • 拡張性を備えた最小単位のコンポーネント設計

Jetpack Composeの学習について

Jetpack Composeの学習としてどうすれば良いか迷う人がいると思います。 そこで一例として、私の勉強方法を紹介します。

私は以下の手順で勉強しました。

  • Android Developersを見る
  • Sampleアプリに目を通す
  • 作ってみる
  • 課題にぶつかったらググる

※あくまで私の勉強方法です

Android Developersを見る

やはり、公式のAndroid Developersはとても分かりやすいです。 Jetpack Composeの知識としては以下のサイトを参考にしました。

最近ではCodelabも始まったので目を通してみると良いかもしれません。

Jetpack Compose CodeLab

Sampleアプリに目を通す

Sampleアプリについては有名どころで言うと以下になると思います。

作ってみる

やはり実践あるのみです。 やらなければ分からないことも多いと思うので、試しにアプリを作成した方が近道だと思います。 ただし、やる前に基本的な部分は押さえておきたいので、先ほど説明したこの2つは読んでおくとスムーズかと思います。

課題にぶつかったらググる

最近では情報は増えてきましたが、やはり情報量は少ない方です。 特に日本語ですと記事の量はかなり少ないので、検索する際は英語で調べることをオススメします。

初心者あるあるとして

  • ググるにもどう調べれば良いかが分からない
  • 言いたいことを言葉にできない

このようなことがあると思います。 そんな時の私の解決方法は以下の通りです。

  • 何を実行したいのかを詳細に記述して検索
  • 実現したいものをクラス名(パーツ)とJetpack Composeをセットにして検索
    • 例: BottomNavigationViewのJetpack Composeはあるのか
  • Logcatを読んでエラーの原因の説明文を検索
  • stack overflow、TwitterGitHubのIssueで検索

ぜひ参考にしてみてください。

まとめ

現在のyentaでは新しい施策が多く控えているため、時間がある時に少しずつ移行を進めています。

既存機能の改修では、xmlでレイアウトが組まれている部分が多く、そこをJetpack Composeへリプレイスすると、複雑な仕様の理解しなければならず、またコード量が純粋に多くなってしまうため、新規で作る画面やシンプルな画面での実装しています。

いきなり全画面の導入は難しいかもしれませんが、今後新規でアプリを作る場合は全画面導入を視野に入れているので、少しずつ移行を進めていこうと思います。

Jetpack Composeの移行には私が紹介した他にもまだまだ価値があります。 小さな画面でも良いので徐々に進めていくのもアリかと思いますので、 まだCompose化を進めていない方は是非検討してみてください!

最後までお読みいただきありがとうございました!

チーム内勉強会を経て、個々人のレベルアップと開発効率の両方を得た話

はじめまして。
求人メディアGreenのフロントエンドエンジニアをしている河内と申します!
Atrea Tech Blogは初投稿です!

今回は、今年の4月からGreenのフロントエンドメンバーで週1回やっている勉強会についてご紹介したいと思います。
初めて3ヶ月、今の所いい感じにワークしているので、なぜやろうと思ったか、どこが良かったか、どういうふうに行なっているかを中心にご紹介できればと思います!

勉強会をするきっかけ

ことの発端は今年の3月に、Greenのフロントエンドエンジニアとして「どうなりたいか・今後どういうことに挑戦したいか」をメンバー間で話し合ったところから始まります。
その中で、

  • 事業を伸ばすために、まだまだ個々人のレベルアップが必要
  • 最近、自発的なInput/Outputがあまりできておらず、エンジニアとしての成長ができていない気がする
  • フレームワークや言語への理解をもっと深めたい

という話題になり、エンジニアとしてレベルアップをするために、チームで勉強会を定期的にやっていこうということになりました。

チーム内勉強会のメリット

レビューの指摘がしやすくなる

実務的な観点で、大きなメリットを感じられたのはレビューがしやすくなったことです。

例えば、

  • 「あの本のこのページにもっと良いやり方が書いてありました」
  • 「○○の根拠として、あの本にこう書いてあることを参考にXXと思いました...」

などの指摘がとてもしやすくなりました。

チームで同じ本や記事を題材に勉強することによって、認識のレベルが一定揃っている状態になります。
ですので、レビューを受ける側もすんなり入ってきますし、逆にもっとこうした方が良いなどの意見があった際にもスムーズに議論が進むようになりました。

新しく挑戦したいことへの合意がその場で取れる

技術的インプットをしている最中に、「これを今のプロダクト開発に取り入れてみたい!」と思うことがよくあります。
チーム内勉強会をやる前だと、その説得をする場合には

  1. 背景などをなるべく詳しく伝える
  2. その上で、チーム内で取り入れるかの議論を行う
  3. 良さそうであれば導入

という流れで意思決定がされていました。

チーム内勉強会をやるようになってからは、同じ本や記事などを読んだメンバーと意思決定を行うため、上記で言うと①にあたる背景などを詳しく伝えるコストが圧倒的に少なく、また、議論する際も前提の知識がある程度揃っているので、とても意思決定がしやすくなりました。
実際、勉強会の中ですぐに取り入れて、そのまま実装まで持っていくことも多々ありました。

実際に取り入れたこと(抜粋)

アウトプットの場を強制的に作れる

これは私個人の問題かもしれないですが、普段インプットは行うのですが、アウトプットがなかなかできていませんでした(やりたいなとは思っていたのですが...)。
後述しますが、勉強会では、インプットした上で取り入れたいことや興味を持ったことを発表する場を設けていたので、アウトプットが強制的にできたのは非常に良かったです。

このおかげもあって、最近では徐々にアウトプットする癖がついてきました。
(このブログ投稿もその癖のおかげです。笑)

どのような形式で行なっているか

頻度

週一回ランチの時間(1時間)を使って勉強会を行なっています。

形式

勉強会の場はアウトプットする場として利用するために、インプットは各自、1週間の中で行なっていくスタイルをとっています。
お題(1週間で○○本の何章まで読むなど)を決め、そのお題に対するインプットを行います。
そして、全員が集まる場で、取り入れたいことや興味を持ったことを発表します。

お題の決め方

お題はその時々、ホットな話題や、チームとして課題に感じている箇所などを中心に話しあいながら決めています。
一緒に本屋に行き、「この本を勉強会でやったらいいかもしれないね」などの話をしながら、決めることもありました。

これまでやったお題(抜粋)

気をつけたこと

色々な事情によって、個々人のインプットの時間があまり取れない週もありました。
その時に気をつけたことは、「最低限ここまではやろう(例えば第何章のみやるなど)」と決め、勉強会自体は毎週開催したことです。

個人的には、毎週開催したことが、3ヶ月続けられた理由の一つでもあると思っているので、忙しくてもスコープを絞って開催し続けたことは良かったのかなと思います。

メンバーの声

  • チームとしての課題に対してピンポイントに勉強できるため、学んだことがすぐ実践できるし、課題の対処にも繋がるところが良かった
  • 同じ話題をメンバーで議論できるのが良かった
  • 定期的に予定が入っているため、毎週続けられたのが良かった

まとめ

簡単にですが、Greenのフロントエンドメンバーの勉強会について紹介させていただきました!
タイトルにも書いた通り、やっみて3ヶ月、個々人のスキルアップはもちろん、チームとしての開発効率も以前と比べて上がったように感じています。
まだまだやりたいお題はたくさんあるので、今後も継続してやっていければと思います!
皆さんも是非やってみてはいかがでしょうか...!

最後に

アトラエでは一緒に働く仲間を募集しています。
少しでもご興味ある方は是非スライドと採用ページもご覧ください!

speakerdeck.com

atrae.co.jp

アトラエの技術・アーキテクチャ・文化を紹介します

アトラエにある4つの事業、Green、Yenta、Wevox、Inowのエンジニアが集まりAtrae Engieer Meetupを開催しました。リモートワークの影響もあり、普段なかなか一堂に会する機会のないメンバーもオフライン/オンラインで集まり、各事業の技術やチームの話などを発表しました。

そこで本記事では、「アトラエのエンジニア組織ってどんなところ?」「どういう技術使ってるの?」という方へ向けて、アトラエの技術・組織について紹介します。もっと詳しく知りたい!という方は記事最後にある採用ピッチを是非ご覧ください。

アトラエのエンジニアってこんな感じ

本Meetup開催前にWevoxのカスタムサーベイという機能を使って、アトラエ内のエンジニアメンバーに、好きなエディタや言語などに関するアンケートをとりました。その中からいくつか特徴的な結果をご紹介します。

エンジニア歴は3~5年が最も多い

アトラエのエンジニア組織は新卒メンバーも多い一方、ここ最近では中途メンバーも増えてきて、結果的に社内のエンジニア歴は多様にわたります。

アトラエでは新卒採用メンバーが入社前にインターンをしたり、業務未経験エンジニアの方もいることから、1年未満、1~3年のメンバーも一定数います。また、エンジニア経験が長いメンバーがメンターなどになって、インターンのメンバーに教えることも多くあります。とはいえ、経験に応じて役職が決まるわけではなく、あくまで会社・事業にどれだけ価値を生み出したか?が評価の軸になっているため、全員がフラットにコミュニケーションをとっているのが特徴です。

好きなエディタはVSCodeが最も多い

エディタはVSCodeが圧倒的に人気な一方、各言語・プラットフォームごとのエディタ(Rubymine, Xcode etc)を使っている人も多いようです。

好きな言語は、Ruby、TypeScriptが多い

アトラエのサービスの多くはバックエンドにRuby on Rails、フロントエンドにはTypeScriptを採用しています。そのためRubyやTypeScript人気が高いようです。また、データサイエンスチームが中心となって社内勉強会を行なっている影響もあってか、Pythonの人気も高く、非エンジニアメンバーも日々Pythonでコードを書いたりしています。

作業する環境は「どこでも」という方が最も多い

アトラエでは、全員が無駄なストレスなく働けるように、原則いつでもどこでも働くことができます。オフィスにいたいときはオフィス、自宅で集中したいときは自宅という文化が根付いていて、どこでも働ける方が多いという結果に影響しているようです。ただ最近では対面でのコミュニケーションも重視して、定期的に出社しているメンバーも増えています。

さて、ここからは各事業の技術スペックをご紹介します。

Green

https://www.green-japan.com/

「Green」は求人メディアのサービスで、アトラエのサービスの中でも最も歴史が古く、またアプリケーションも巨大です。そのため、新規機能開発と並行してリファクタ及びリアーキテクチャを推進しています。

現在、Ruby on Railsが担っているサーバーサイドをGoで書き直し、またフロントエンドもNext.js + Reactに移行をしています。

また、ユーザーへのメール配信配信基盤、分析基盤の構築も行っています。具体的には、SendGrid + fluentd + BigQueryでプッシュ通知・配信メールのA/Bテストがよりやりやすいよう基盤の刷新をしてきました。メールテンプレートも、コードベースのものから、ローコード化し、誰でも内容を編集できるようにしています。

一方、巨大アプリケーションならではの課題も多く残っています。これらを一つ一つ解決することもエンジニアチームのロードマップに含まれていて、順次対応しています。リファクタ・移行に際して、不要な機能あるいは優先度が低い機能に関してはビジネスサイドと相談の上機能を削除をしてスムーズに移行できるようチーム全体で工夫しています。こういったリファクタやリアーキテクチャは直接的なユーザー価値を生むわけではないため、一般的には事業的優先度は低いと見做されがちです。しかしGreenチームでは、今後の成長に際して重要なファクターであると認識し、チームの一定のリソースを割いてでも取り組むことにしています。

Yenta

https://page.yenta-app.com/jp

「Yenta」はビジネスパーソン向けのマッチングサービスで、iOSAndroidアプリ、またWebアプリケーションと多岐にわたるプラットフォームで展開されています。iOSはSwift、AndroidはKotlinで、サーバーサイドAPIRuby on Railsで書かれています。またWebはNext + ReactでSPAとして構築されています。

またサーバーサイドアーキテクチャはこのようになっています。

元々スマホアプリ向けにAPIが細分化されており、それを後発のWebアプリケーションにも適用しやすくなっています。

Yentaはビジネス向けマッチングアプリという特性上、一度に大量のリクエストが起きる可能性があるサービスです。そのため、クエリごとにパフォーマンス改善を図ったり、オートスケールするようにしたりと、様々な工夫をしています。

またYentaはマッチング&コミュニケーション機能をベースとしながらも、Q&A機能や特性診断など、マッチングとは異なる分野の機能も多くリリースしています。そのため画面の種類、数がいっきに増えることが多いのですが、スマホアプリ向けのデザインだけでなく、Web側のデザイン・実装も行う必要があります。チームとしては少ない人数ながら、ビジネスサイド/エンジニアが一丸となってコミュニケーションとりながら、スピーディに開発ができているのが特徴的です。

https://web.yenta-app.com/personality/top

小さなチームで質の高いデザインを生み出すために、UIデザイナーとPdM、さらにエンジニアもデザインにコミットしています。Figmaのデザインカンプをデザイナー <=> PdM, エンジニア間で何度もお互いにフィードバックし、最終的にはFigma上でデザイン及び細かい画面の挙動をpx単位で詰めています。一般的には、PdMやデザイナーが決めたものを上から下に伝えることもありますが、Yentaチームではチームで意思決定を図って、クオリティの高いものをアウトプットしています。

Wevox

https://wevox.io/

「Wevox」は従業員の方々のエンゲージメントを測るためのサーベイ及びその可視化を行うtoB SaaSアプリケーションです。エンゲージメントサーベイという主要機能を軸としながら、関連する様々な機能を高頻度でリリースしています。そこで、チームのアジリティを損なうことなく開発を続けるために、マイクロサービス化を進めているところです。

技術スタックは以下の通りです。

サービス開始当初はReact + jQueryだったフロントエンドも、その後Reactのみになり、TypeScriptの導入を行い、また現在ではNext.jsを導入したりとフロントエンドの開発効率を追求しています。サービス特性上、「サーベイの結果をどう見せたら、ユーザーの価値につながるか?」という観点での改善や機能追加が多いため、フロントエンドの改修頻度が高く、保守性を考慮して技術選定及びリアーキテクチャを行なっています。最近ではNxを使ったmonorepo構成に移行しており、チームがサービス単位で分かれても、疎結合にコミュニケーション・実装ができるよう工夫しています。

またバックエンドはBFF層にRuby on Railsを採用しており、また各サービスの処理をGoで記述しています。企業の全従業員の回答という大量のデータを処理するために、実行処理効率を重視してGoを採用しています。

Wevoxにはアプリケーション開発以外に、データサイエンス(DS)のチームもあります。技術スタックは以下の通りです。

アプリケーションのサーバーサイドがRuby, Goで書かれているのに対しDSチームではPythonを採用しています。一度に大量のデータを処理する速度や、ML/DL関連ライブラリが豊富なことなどからPythonを使っています。分析・モデリングにはJupyter Notebook、またハイパーパラメーターの管理にHydraを使っています。また計算・分析結果などをカスタマーサクセスのメンバーが参照する部分に関してはFlaskで書かれています。

事業・プロダクトが大きくなり、チームが分散化するなかで、日々のコミュニケーションやチームづくりにも注力しています。

具体的には、サービス・PJ単位のチームだけでなく、各技術(フロントエンド、サーバーサイド、インフラ ...etc)ごとの横断チームも存在しており、各技術におけるIssueについて話し合ったり、解決に向けてサブタスクとして取り組んでいます。その背景として、マイクロサービス化を進める中で、不必要な分断が起こっていたことがありました。例えば、同じ課題意識を持っているメンバーたちが、チームが分かれてしまうことでその解決に向けて動き出しにくかったり、ノウハウの共有が進めにくかったりしていました。そうした状況を改善するために横串チームをつくり、メインPJと並行してサブタスク的にアクションを促しています。また、月1,2回の頻度でプロダクトチーム全員での対話会を実施し、普段コミュニケーションを取らないメンバー数名で話し合う機会を設けています。

Inow

Inowはベテラン人材向けのジョブ型マッチングサービスで、2021年にローンチされたアトラエの中で最も新しいサービスです。(執筆現在ではβ版の提供のみ)

https://get.inow.jp/

技術スペックは下記の通りです。Inowはまだβ版ということもあり、事業として高速で価値を検証していくために、アトラエの既存技術を踏襲して技術選定をしています。

フロントエンドはReact、TypeScriptにMaterial UIを採用し、またバックエンドはGo一本でサーバーを立てています。またWevoxでも使っているArgo(上図右上のタコのロゴのサービス)は、Kubernetes向けにWorkflow(Argo Workflow)やCD(Argo CD)などのツールセットを提供するサービスです。

アトラエ全社のエンジニア文化・制度

最後に、全社的なエンジニアの文化・制度をいくつかご紹介します。

フラット/オープンコミュニケーション

アトラエはホラクラシー組織と呼ばれる組織形態を採っており、各メンバーに役職がありません。そのため上下関係もなく、基本全員がフラットな関係で事業運営を行なっています。エンジニアも同様に、シニアメンバーからジュニアメンバー、新卒/中途関係なく、プロダクト開発に取り組んでいます。例えば、「先輩エンジニアのコードレビューだから直さないと...」みたいなことは起きえないですし、むしろ若手メンバーからもレビューや提案がたくさん飛び交う組織です。

また、Slackの運用ルールとして「原則、DMは禁止」となっていることから、様々なコミュニケーションは会社全体にオープンにされています。また、社員であれば他事業部のチャンネルに入ったりするのも自由なので、他チームがどんなコード書いてるかなどを見ることも可能です。また、個人で分報など自由につぶやけるtimesチャンネルを作る方も多く、仕事以外のことも含めて自由にコミュニケーションをとっています。

「20%ルール」

Googleが採用して話題になっていた「20%ルール」に近いことをアトラエも取り入れています。ただ、「20%を別のことに充てないといけない」というわけではなく、あくまでメインPJ・業務意外に20%程度なら個人の判断でサブプロジェクトに従事しても良いというものです。例えば、メインはWevoxのエンジニアをしながら、全体の20%程度をエンジニア採用業務に充てているメンバーもいます。

アトラエには「アトラエスタンダード(通称、アトスタ)」というものがあり、その中の一つに「Atae is Me」という、アトラエを自分事として捉えて、当事者意識をもって企業活動を行おうというものがあります。このAtrae is Meに則って、自分がやるべき、あるいややりたい!と思ったものがあれば手を挙げてそこに20%程度コミットすることができます。

勉強会

定期的に勉強会や輪読会なども開催されています。これまでには、『エリック・エヴァンスのドメイン駆動設計』、『Clean Architecture』などの設計本、またセキュリティに関する書籍の輪読会などがありました。また、これらはエンジニア以外でもおこなわれていて、インプットに積極的なメンバーが多くいます。

最近ではデータサイエンスの勉強会が全社向けに行われ、半年ごとに数名が挙手制で参加し、機械学習やデータサイエンスについて学んでいます。

もっとアトラエのことを知りたい!という方は是非こちらのスライドをご覧ください。

speakerdeck.com

atrae.co.jp

求人メディアGreenのFlutterアプリを1年間運用した話

求人メディアGreenのFlutterアプリを1年間運用した話

はじめに

こんにちは!求人メディアGreenのエンジニアをしている@risafj です。

Greenでは、2021年2月にFlutterで開発されたAndroidアプリがリリースされた後、6月にiOSアプリも従来のSwift版からFlutter版に移行しました。私はその6月頃からアプリ開発に参画し、以来開発・運用を担当しています。今回は、その1年間でチームで行ってきたことを書いてみようと思います。

Flutterの人気は高まっているためネット上に記事がたくさんありますが、「長期に渡ってFlutterアプリをメンテするのってどんな感じ?趣味や個人開発ではなく、中規模サービスでFlutterを使うのってどう?」といった情報は意外に少なそうだったので、興味があればぜひご一読ください!

(ちなみに、現在のGreen開発チームとして日常的にFlutterで開発するのは私含めて2人で、あとは施策ベースで時折ほかのメンバーが実装に入ります。)

1年間で行ったこと

印象的だった出来事を挙げてみます。

※主に運用系のタスクを挙げていますが、事業として必要な機能開発や不具合修正も通年で行ってきました。

Flutter v2 へのアップデート

2021年にFlutter界で起きた最大の変更といえば、null safetyだったのではないでしょうか。

2021年3月のFlutter v2のリリース(正確にはDart 2.12のリリース)によりnull safetyが導入されました。 Greenでは、各ライブラリの対応を待ったうえで、8月頃にv1.22.4 → v2.2.3のアップデートを行いました。 当初は、アプリ担当の2人総出で何日も戦うことになるとは思っていませんでした…!

具体的に行った変更は、大きく分けて以下の通りでした。

関連ライブラリのバージョンアップデート

Flutterをアップデートしたのに伴い、その他のライブラリもnull safetyに対応したバージョンにアップデートする必要があったため、pubspec.yaml の全てのライブラリが一斉にアップデートされました。 なかにはbreaking changesが紛れているものもあり、特にHTTPライブラリのdioは詳細なchangelogやupgrade guideがない中で、なんとか問題を特定してGitHub Issueを漁って修正した記憶があります。

Null safety対応

APIのリクエストパラメータやレスポンスなどを一個一個見直していくので、膨大なコードの変更量になりました。

またGreenは性質上、nullableなデータが多いです。Null safety導入前は、final String jobName などと変数を定義した上で、使用時に if (jobName != null) {} のようにnullチェックをしていました。v2では、 jobName がnullになり得る場合は final String? jobName のように変更する必要があります。最初はnullableではないと思っていたデータについて後でnullableなエッジケースが見つかって再修正するなど、色々と手戻りがありました。

ビルドコマンドの変更

詳細は割愛しますが、ビルド時に --dart-define を使ってビルドの種類(本番用、Staging用など)や環境変数を指定していたのがそのままでは使えなくなり、修正方法の調査に苦戦を強いられました。

今後もFlutterのメジャーアップデートには時間と覚悟が必要かもしれないと感じています…!

ちなみに、ローカル開発環境でのFlutterバージョン管理にはFVMがおすすめです。アップデート作業中に差し込みで他の開発が入っても、簡単にFlutterバージョンを切り替えられます。

GitHub Actionsを使ったCI/CDの整備

GitHub Actionsを使ってPull RequestのLintを行う設定を追加したほか、ポチッとボタンを押したらApp StoreGoogle Play Store・Firebase App Distributionへのデプロイを行えるようにしました。

App Storeへのデプロイに関しては内部的にFastlaneを使っています。BitriseやCodemagicといったツールも検討しましたが、アトラエでは使用していなかったため、導入のコストなども考えて最終的にGitHub Actionsを選択しました。

デプロイのGitHub Actionsを載せると説明も長くなってしまうので、Lintの設定だけご参考までに載せておきます。

name: Lint

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  lint:
    runs-on: ubuntu-latest
    timeout-minutes: 5

    steps:
      - name: "Checkout 🛎️"
        uses: actions/checkout@v2

      - name: "Flutter setting"
        uses: subosito/flutter-action@v1
        with:
          channel: stable
          flutter-version: "2.5.3"

      - name: "Flutter pub get 👽"
        run: flutter pub get

      - name: "Flutter analyze ✅"
        run: flutter analyze

Firebase Crashlyticsによるエラー検知

不具合を素早く検知して修正するため、Firebase Crashlyticsを導入しました。ユーザが使っているアプリがクラッシュすると、次回アプリを起動した際にログを送ってくれる仕組みになっています。

「どのリリース以来、どんなエラーが何件発生しているのか」といった情報を教えてくれるため、とても重宝しています。今は主にリリース後などに定期的に確認して、気になるクラッシュログが上がってきたら調査するような運用にしています。

たまにStack traceを見てもエラーの発生条件が分からず、再現や修正ができない場合があるのが玉に瑕で、もっと詳しい情報を取得できないか検討したいところです。

Integration Testの導入

私事ですが、自分は今回ネイティブアプリ担当になるまではウェブ開発しかしていなかったので、アプリの「バグをリリースしてしまった場合、審査も含めて最低1日間は修正を出せない」というのが慣れるまでストレスでした。

リリース前に変更箇所や主要な機能を中心に動作確認していましたが、iOSAndroidの両方で毎回手動テストするのはかなり時間がかかるため、Flutter標準のintegration_testを使って自動テストを導入しました。 現在は、アカウント登録・ログイン・求人の検索・応募・メッセージ送信など、主要な機能については自動テストが実装されています。

まだ完璧に運用できているとはいえず、一旦はローカル開発環境で実行して確認していますが、本当はPull Requestを上げたらテストが実行されたり、デプロイ前に実行したりを実現したいと考えています!

感想

GreenでFlutterを採用したのは英断だったと思っています!

Flutterの開発体験が良いなどの話はさておき、なんといってもiOSAndroidアプリの両方の運用を主に2人で回せているのは、別々に開発するよりかなり開発コストが抑えられているはずです。また、単一コードベースということはロジック部分は同じなので、プラットフォーム間で仕様がずれる恐れがほとんどないのも良いです。

ぼやきとしては、成熟したフレームワークに比べれば絶対的な正解がない場合が多いと感じることがあります。例えば、状態管理に関するドキュメントには10個以上のライブラリが列挙してあり、公式に推奨されている方法はありません。

また当然かもしれませんが、Flutterで開発するとはいえ、iOSAndroid開発のことが分からないと苦戦する場面はあるなと感じています。例えば、iOSで画像付きのプッシュ通知を送信する実装を行っていたときは、「service extensionって何ぞや???」と思いながらXCodeでNotification Service Extensionを追加して、よく分かっていないためトラブルシューティングも大変だった思い出があります。

これからやりたいこと

色々ありますが、運用系のタスクでいくつか挙げるとこんな感じです。 今後も事業に必要な機能開発を進めるだけでなく、運用タスクや開発者体験の改善も行っていけたらと思っています!

  • テストの拡充・運用方法の改善
  • 継続的なFlutterのアップデート
  • その他パッケージアップデートの定期実行・自動化
  • パフォーマンスチューニング

さいごに

以上、Flutterアプリの開発・運用の1年間でした。イメージが湧きましたら幸いです。

アトラエでは一緒に働く仲間を募集しています。少しでもご興味ある方は是非スライドと採用ページもご覧ください!

speakerdeck.com

atrae.co.jp

アトラエで私がPMを担当する時に大事にしている5つのポイント

はじめまして。大谷木です。
2021年10月にアトラエに入社し、現在、Wevox事業部でプロジェクトマネージャー(PM)兼バックエンドエンジニアをしております。

突然ですが、今、あなたが参画しているプロジェクトは順調ですか?
私は先日まで、Wevox カスタムサーベイのリニューアルプロジェクトのPMを担当していましたが、正直な話、終始何のトラブルもなくとはいきませんでした。 prtimes.jp

このプロジェクトには途中から参画したのですが、参画当初、実際に発生していたのは、

  • 進捗が思わしくない、スケジュールがずるずる伸びていく
  • 次にどういったタスクに着手すべきかわからない

と言った問題でした。

ただ、こういった問題はこのプロジェクト特有の問題ではなく、私自身過去何度もそういった場面に出くわしていることからも、よくある問題であると考えています。
今日はこういったあるあるを回避するために、私がPMを担当する時に大事にしている5つのポイントについて書きたいと思います。

PMを担当する時に大事にしている5つのポイント

1. プロジェクトのゴールを明確にする

プロジェクトを進めていく中で、こんな問題に直面したことはありませんでしょうか?

  • 何を達成したら良いのかわからない
  • 進捗が思わしくない、スケジュールがずるずる伸びていく
  • これって今やるべきことなの?

こういった問題に直面する一つの要因として考えられるのは、「プロジェクトのゴールが定まっていない。あるいは、ゴールが明確でないためにmust/want要件の区別ができない」ケースです。

プロジェクトに携わっていると、プロジェクト開始〜終了まで、もっとこうした方がいいのではないか、ああした方がいいのではないかと様々な要件が上がってくると思います。
上がってくる要件全てを叶えようとするとなかなかプロジェクトは終息しません。
そもそも、全ての要件を叶えることが果たしてみんなが望んでいることなのでしょうか?
中には最低限の要件で良いから早くリリースして欲しいという人もいるのではないでしょうか?

プロジェクトのゴールが明確でないと、「各要件に対してmust/wantの判断が困難=全ての要件を叶えざるを得ない状況」になってしまい、結果、上記のような問題に直面する可能性が高くなります。
もし、今携わっているプロジェクトのゴールが明確でないなと思った方は、関係者と話し、早めに設定することをお勧めします。

2. 設計書はしっかりと作る

Photo by Firmbee.com on Unsplash

小規模プロジェクトの場合は設計書を作っている期間で実際に動作するものができてしまうので、わざわざ設計書を作らないといったケースをたまに見かけます。
それが悪いといったわけではありませんが、私はどんなに小規模なプロジェクトでも以下の目的のために設計書を用意するようにしています。

  • 実装の手戻りを防ぐ
  • テストの材料にする
  • 不具合っぽいものが見つかった時に、仕様としてあえて意図してこう実装しているのか、それともバグなのか判断できるようにする
  • エンジニア以外の人でも仕様が把握できるようにする(=ソースコードを読まなくても仕様がわかる)

ただし、何でもかんでも設計書に起こすのかというと、そういったわけではありません。
何でもかんでも設計書に起こしていたら、なかなか実装に入れないのも事実です。
あくまで私の意見ではありますが、以下3つを最低限作るようにしています。

  • ユーザーストーリー
  • 画面仕様書
  • API設計書

では、なぜこれらの設計書を作るようにしているか。
私は、プロジェクトに途中から参画することが経験的に多いのですが、時々、設計書が存在しておらず、仕様を把握するためには、ソースコードを読んだり、動くものを触ってみたり、人に聞くしかないといったケースがあります。
正直、困ります(笑)

特に困るのが、上にも記載していますが、
「不具合っぽいものが見つかった時に、これは仕様(あえて意図してこう実装しているのか)なのか?それともバグなのかわからない」ケースです。
この場合、設計・実装した人を見つけてヒアリングするのですが、時々、数年前に設計・実装したからわからないと言われてしまうこともあったりします。
自身が関わっているプロジェクトでは、未来でこういった事態を引き起こしたくないので、設計書をしっかりと作るようにしています。

3. テストは必要十分。無理のない範囲で実施する

時々、テストが目的になっているのではないかというプロジェクトを見かけます。
テストはあくまで、品質の良いものを出すための手段です。
やりすぎもやらなさすぎも良くないので、私は、最低限のテストは実施しつつ、それ以外のテストについてはプロジェクトメンバーのレベルや状況に合わせて決めるようにしています。

私が言う最低限のテストというのは、「ユーザーストーリー」を満たしているのかを指しています。
ユーザーストーリーというのは言い換えれば要求事項ですので、これを満たしているか否かをテストすることは必須であると考えています。

それ以外のテストについてどういう状況でどう判断したかについては、過去に2つ両極端なプロジェクトに関わったことがあるので、それを例に紹介したいと思います。

[設計書やテスト項目書が存在しないが、大きな不具合や致命的な問題が頻発していない]
補足:このプロジェクトには、初期リリース完了後、サービスとして拡張、改善している時に参画しました。
このプロジェクトに参画した時にポイントとしたのは、上記にもあるとおり、「今まで設計書やテスト項目書をほとんど用意していないにもかかわらず大きな不具合や致命的な問題が発生してしていない」という点です。言い換えれば、これは、一人一人が自身の責任範囲(≒単体レベル)においては品質担保しようと心がけており、懸念部分があるとすれば結合部分以降なのではないかと考えました。
そのため、実際にサービスの拡張・改善した際は、単体レベルのテストは各自の判断に任せ、結合以降はしっかりと項目書を作ってより品質を上げるようにし、結果、リリース後も特に大きな問題は発生することはありませんでした。

[設計書の通り開発が終わったとのことだが、プログラムが起動しない]
補足:このプロジェクトには、これから結合して初期リリースというタイミングで参画しました。
開発は終わってこれから結合というタイミングだったため、結合テスト項目書を私の方で用意しテストを実施したのですが、そもそもプログラムが起動せず......非常に驚いたことを数年たった今でも鮮明に覚えています。
まずは状況の整理をと思い、単体テストの結果を教えて欲しいと開発者に聞いたところ、単体テストは実施していない結合テストでテストするつもりだったと言われたので、これは、メソッド単位でも動作が怪しいと思い、メソッド単位で意図したものになっているかレビュー、改修、テストを実施するように舵を切り、なんとかリリースまでこじつけました。

このように、関わるメンバーや状況に応じて必要なレベルは変わって来ます。
最低限のテストは実施しつつどこまでテストを行うかは状況に応じて臨機応変に判断する必要があると考えます。

4. タスクを見える化する

Photo by Parabol on Unsplash

あなたが参画しているプロジェクトではタスクは見える化されていますでしょうか。

見える化は、カンバンボードやWBSを準備、メンテナンスするためにある程度コストがかかりますが、
「PMに聞かなくても、何がどこまで終わっているのか、今何をすべきか、あと何をしなければならないのかがわかる」
状況を生み出すことができます。
これにより、指示待ちやAタスクが終わらないとBタスクに着手できないといったことを最小限にすることができますので、コストをかけてでもやる価値のあるものだと私は考えています。

タスクを見える化する際によく私が用いるのは以下2つです。

カンバンボードは最近はmondayのカンバンビューをよく利用します。 サブアイテムがカンバンビューに出てこないのが少し残念ではありますが、アイテムのカラム追加の自由度が高く、WBSビューなど様々なビューやアプリが用意されており便利です。
カンバンボードを提供しているツールの大多数は数ユーザーであれば無料で使えますので、是非色々試し、自分に合ったツールを見つけ、見える化を進めていただければと思います。

5. 独りよがりにならない

例え話をしたいと思います。
あなたはA社から依頼されているシステムを開発しているエンジニアです。
ある日、PMが、
「今日A社さんと打ち合わせしてきたんだけど、Xっていう機能もあった方が良いですねって話になったからXの機能開発を追加で受注してきたからよろしく!! 費用は追加で〇〇いただけるようになって、期日も2週間伸びているから。」
と言ってきました。
あなたはどう思うでしょうか。
一見すると、A社さんは機能追加の要望が叶いますし、あなたの会社は売り上げが上がるしwin-winのようにも見えます。
ただ、このXが2週間では到底実装できない機能だったらどうでしょうか。
このシステムについて一番詳しいのは恐らくPMではなく現場のエンジニアです。
「話が上がったときに一旦持ち帰って見積りさせてよ・・・」とあなたは思うことでしょう。

PMと聞くと、何やら強い権限を持っているかのようにも見受けられますが、
PMはあくまで、「プロジェクトの計画・遂行に責任を負う管理者。また、そのような職位や職能(IT用語辞典より)」です。プロジェクトはPMのものではなく、プロジェクトに関わっている全ての人のものなのです。
上記は極端な例ではありますが、もしあなたがPMを担うことになったら、独りよがりにならず、プロジェクトに参画しているみんなで意見を出し合い、みんなで作り上げていっていただけたらと思います。

最後に

今回は、私がPMを担当する時に大事にしている5つのポイントについて書かせていただきました。
読んでいる人の中には、当たり前のことしか書いていないじゃないかと思う人もいらっしゃるかと思います。 ただ、過去に数多くのプロジェクトに参画して来ましたが、割とその当たり前が当たり前にできていないために上手くいっていないといったプロジェクトも実際に見て来ています。
当たり前だからといって蔑ろにせず、当たり前だからこそ大事にしていただければと思います。

Yenta WebにChakra UI を採用した話

f:id:atrae_tech:20211126112459j:plain

Yentaのweb版を作るにあたってChakra UIを導入しました。

Chakra UI とは

Chakra UIはユーティリティーファーストなUIコンポーネントライブラリです。 Chakra UIの利点は、デザインのニーズに合わせて簡単にスタイルを調整できます。

例えば、下記のwidth="380px"のようにスタイルpropsで渡すことができます。

 <Box width="380px">
   <Text fontSize="sm" fontWeight="bold">Hello World!</Text>
   <Button>Click me</Button>
 </Box>

Chakraのデフォルトテーマを拡張したい場合は テーマを設定して、自分のプロダクトのデザインスタイルに変更することができます。

import { extendTheme } from '@chakra-ui/react';

export const theme = extendTheme({
  fontSizes: {
    xs: '12px',
    sm: '14px',
    md: '16px',
    lg: '18px',
    xl: '20px',
  },
  fontWeights: {
    normal: 400,
    medium: 500,
    bold: 700,
  },
});

Chakra UIを選んだ理由

UIライブラリを使うことにした理由

自前でコンポーネントを一から作るとなると時間がかかるのでUIライブラリを使うことにしました。 その理由として下記が挙げられます。

  • Yentaのwebを作るにあたってスタイルを組めるメンバーが少数しかいなかったこと
  • 開発工数も長くはない中での開発だったのでスピード感良く開発がしたかった
  • class名を考えたり、コンポーネントを作る時間を削減したかった

Material UIなどの有名ライブラリもありますが、別のUIライブラリのChakraを使うことにしました。

Chakra UIとMaterial UI(MUI)の違い

MUIはChakraよりもコンポーネントの数も多く、DatePickerなどの高機能なコンポーネントもあります。 しかしMUIはマテリアルデザインガイドラインに基づいています。 MUIもカスタマイズは可能ですがコンポーネント自体に特定のスタイルがあるので調節をするのに多少なりとも時間がかかってしまいます。 Chakraよりもスタイルの拡張性は高くありません。

なぜChakra UIを選んだのか

前述の通り、MUIにもメリットは多々ありますが、学習コストが高くデザイナーもエンジニアもMUIを知っておかないといけないというデメリットもあります。また、MUIはデザインガイドラインがしっかりしていることもあり、デザインをMUIに寄せる必要性があります。Yentaは既にアプリをリリースしており、わざわざMUIにデザインを寄せる必要性がなかったこと、デザイナーにFigmaで直感的なデザインをしてもらいたかったこと、スピード感を持ってプロジェクトを進める必要があったことなどもあり、拡張性が高くデザインファーストなChakraUIの方がYentaに合っていると思いChakraUIを使う運びとなりました。

感想

Chakra UIのドキュメントにはナルトが出てきます。 多分ですが開発者はナルトが好きなんだと思います。 chakra-ui.com

みんな使ってくれってばよ!!!

参考

Chakra UI
Material UI