Quantcast
Channel: Cybozu Inside Out | サイボウズエンジニアのブログ
Viewing all 681 articles
Browse latest View live

2019年 報奨金制度を振り返って

$
0
0

こんにちは。Cy-PSIRT(Cybozu Product Security Incident Response Team)の福永です。

本エントリでは

  • 2019年に実施した報奨金制度の結果
  • 参加者からのご要望

について、ご紹介いたします。

2019年に実施した報奨金制度の結果

定量情報

2019年の定量情報は、次の通りです。*1

着信数認定数(暫定)報奨金支払金額(暫定)
489件193件15,348,000円

2015年から2019年の着信数、認定数、報奨金支払金額

着信数が増加した要因

着信数は前年度と比較して、40%増えています。

2019年4月20日(土)、21日(日)に2年ぶりのバグハン合宿2019を開催し、そのイベントで多数報告いただいたことが、着信数の増加に繋がりました。 489件中196件がバグハン合宿でのご報告です。

認定数が増加した要因

認定数は前年度と比較して、26%増えています。

複数製品で発生する脆弱性をバグハンターの方から多数報告いただいたことが、認定数の増加に繋がりました。

一方、1件あたりの支払金額が大きい脆弱性が少なかったことで、報奨金支払金額は前年度比73%におさまっています。

2019年度報奨金獲得ランキング

総額ランキング

獲得した報奨金額の合計が最も多かったのは、米山 俊嗣(三井物産セキュアディレクション) 様でした。上位5名中4名が2018年度にもランクインされた方々です。他にもたくさんの方からご報告をいただいています。皆様ありがとうございました。

順位掲載名
1位 米山 俊嗣(三井物産セキュアディレクション) 様
2位西谷完太(イエラエセキュリティ) 様
3位東内裕二(三井物産セキュアディレクション) 様
4位馬場 将次(イエラエセキュリティ) 様
5位Tanghaifeng 様


脆弱性1件あたりの高額ランキング

報告いただいた脆弱性1件あたりの報奨金額が高額だった方のランキングです。総額ランキングに続き、米山 俊嗣(三井物産セキュアディレクション) 様に報告いただいた脆弱性が1位でした。おめでとうございます。

順位掲載名
1位 米山 俊嗣(三井物産セキュアディレクション) 様
2位西谷完太(イエラエセキュリティ) 様
3位馬場 将次(イエラエセキュリティ) 様
4位米山 俊嗣(三井物産セキュアディレクション) 様
5位Sunshine Hui(@bttthuan) 様

2019年度ポイントランキング

総獲得ポイントランキング

2019年度から、報奨金とは別にポイントを付与する施策を開始しました。ご報告いただいたもので「認定の有無にかかわらず有益な情報だった」「レポートの内容が的確であった」など、様々な視点でポイントを付与します。累計獲得ポイントに応じて感謝の気持ちとしてオリジナルTシャツなどの特典をお贈りしています。 獲得したポイントの合計が最も多かったのは、米山 俊嗣(三井物産セキュアディレクション) 様でした。

順位掲載名
1位 米山 俊嗣(三井物産セキュアディレクション) 様
2位東内裕二(三井物産セキュアディレクション) 様
3位西谷完太(イエラエセキュリティ) 様
4位Tanghaifeng 様
5位馬場 将次(イエラエセキュリティ) 様

2019年のトレンド

「CWE-79:XSS」「CWE-200:情報漏えい」「CWE-264:認可・権限・アクセス制御」が同率1位です。 例年と比べると、「CWE-200:情報漏えい」の報告が多いという結果でした。

2019年度、CWEタイプに「CWE-94:コード・インジェクション」を新たに追加しました。今まで「CWE-20:不適切な入力確認」などに分類していたコードインジェクションの脆弱性を、「CWE-94:コード・インジェクション」に分類できるようにしました。

認定された脆弱性(脆弱性タイプ別)
認定された脆弱性(脆弱性タイプ別)

参加者からのご要望

報奨金制度をより良くしていくために、 制度に参加いただいたバグハンターの方にアンケートを実施しました。皆様よりお寄せいただきましたご要望を一部ご紹介します。

運営に対するご要望

脆弱性の評価にかかる期間がまだ長い

報告が集中したり、報告いただいた内容が複雑だったりする場合には通常よりお時間をいただくケースもございます。ご了承ください。

評価大変かと思いますが頑張って下さい

あたたかいお言葉恐れ入ります。

報告用サイトに対するご要望

2019年12月に脆弱性報奨金制度の参加者向けに開設した報告用サイトに対するご要望です。

レポート記入欄にリッチエディターを利用できるようにしてほしい

早速設定しましたので、活用ください。

複数の報告事項を並列でやりとりしていたので事項ごとのステータスが把握しづらかった
報告した物の対応ステータスが分かるようになるといい

報告用サイトでは、ご自身の報告のステータスを一覧で確認できるようになっています。

メールアドレスを他の参加者に公開したくない

申し訳ございませんが、他の参加者から閲覧できても問題ないメールアドレスをお使いください。 報告用サイトにおいて、メールアドレスが他の参加者からも閲覧できるのは仕様です。

開示請求ボタンや修正ステータス欄も用意してほしい

対応予定はありません。 第三者への開示が可能かどうかは、報告用サイトのコメントにて都度ご確認いただく必要があります。 報告いただいた脆弱性を改修した際には、不具合情報公開サイトで公開していますので確認ください。

HackerOneなどの既存のプラットフォームを参考にしてほしい

他社のシステムも参考にしながら、使いやすいシステムを目指してまいります。

メールではなくてkintoneを使えばいいのにとずっと思っていた。報告用サイトには期待しかない

以前から要望いただいていた本件、ようやく実現しました。今後ともご報告お待ちしています。

2020年の取り組み

脆弱性報奨金制度 2020 始まります - Cybozu Inside Out | サイボウズエンジニアのブログでご紹介しています。

さいごに

サイボウズでは、2020年も継続して報奨金制度を運営しています。

バグハンターの方にとって、魅力的な制度であり続けたいという思いで日々対応しておりますので、「サイボウズにこのような企画や施策を実施してほしい」というご要望がありましたら、 報告用サイトやメールでproductsecurity@cybozu.co.jpまでお気軽にご連絡いただければ幸いです。

今後とも、よろしくお願いいたします。

*1:2020年5月現在の暫定値であるため、評価の変動により増減する可能性があります。


kintone開発チームに聞いてみた ── Cybozu Tech Meetup #1 の質疑応答編

$
0
0

こんにちは、Yakumoチーム兼コネクト支援チームの@ueokandeです。 先日5月12日に、Cybozu Tech Meetup 『kintone開発チーム』を開催しました。

cybozu.connpass.com

当日はYouTube Liveで配信しました。 connpassでは300名近くの方にお申し込み頂き、当日の最大視聴者数も200人を超えました。 配信の録画は以下から観ることができます。

www.youtube.com

質疑応答の時間も、運営が想定してたよりも多くの質問が寄せられて、とても盛り上がりました。 時間の都合上、当日にすべての質疑に答えられなかったので、この記事ではそれらの質問への回答を紹介したいと思います。

「kintone × リモートモブプログラミング」 by 西 大樹

Q: モブプロに選ぶタスクの基準、タイミングが知りたいです。

基本的にタスクはモブで実施します。一方で、誰がやっても同じ結果になるタスクはモブでしません。 たとえば定形作業などです。

Q: チーム構成はどうやって決められますか? 気を付けていることはありますか?

それぞれのチームに、ベテラン1名が割り振られるようにします。 それ以外のメンバーも知識が偏らないようにバランス良くチーム分けします。

Q: ソースの書き方・フォーマットについては、なにかルール化していますか?またはツールを使ってる?

チーム内でEclipse Formatterの設定ファイルで共有しています。 またspotlessというツールを使って、CI (Continuous Delivery) 上でフォーマットエラーを検知します。

Q: 共有VMの詳細を差し支えない範囲でお聞きしたいです

社内にUbuntuのサーバーを立てています。

Q: モブプロを教育の手段とする場合どれくらいモブプロは活用されていますか?

チームに人が入ったら、とりあえずモブに入ってもらいます。 モブでキャッチアップやオンボーディングを実施します。

Q: ドライバーとナビゲーターの交代時間ってどのくらいの間隔で交代してますか?

チームによりますが、25分の作業時間と5分休憩のサイクルを繰り返しています。 作業時間や残り時間を測るために、Cuckooというモブプロタイマーを使っています。

Q: モブプロしてる時の休憩ってどういう風に取っていますか?みんなで一緒に休憩する?適宜抜けたり入ったりする?

休憩時間も接続を維持したままにしていますが、メンバーは全員、音声・映像をオフにします。

「ひよっこ kintone 開発プログラマーの冒険譚」 by 中川 遼太郎

Q: フルリモートになったのはコロナの影響もありますか?

はい、そうです!

Q: 自動テスト、進んでますか?

試験設計をQAエンジニアが担当して、プログラマがそれを自動化しています。

「IT業界未経験者が kintone QA として1年間がんばった話」 by 川畑 ひとみ

Q: 回帰試験は自動化されていないんでしょうか?

自動化が技術的に難しい、不安定である等の理由がなければ、基本的には自動化しています。

Q: QAエンジニアは全て内政ですか??ニアショアオフショア等を利用されていますか??

kintone開発チームとしては社外に依頼することはほとんどありません。 セキュリティチームなどは外部監査を依頼することがあります。

Q: 海外の拠点からQAをしてもらうことはありますでしょうか?また、その際のコミュニケーションは何を使うのでしょうか?

上海とベトナムにも開発拠点があるので、海外拠点のQAがいるチームもあります。 今はいませんが、長い間kintoneチームにも上海のQAがいました。

コミュニケーションツールは拠点関係なくkintoneが主で、必要に応じてテレビ会議やチャットも使っています。 またその際の言語は、上海メンバーは全員日本語ができるので日本語で、ベトナムメンバーは英語でやり取りしています。 ベトナムにはコミュニケーターさんがいるので、テレビ会議の通訳やテキストの翻訳をお願いすることも可能です。

Q: 上司がいなくてどのようなことに困りましたか?
Q: メンターはいるが上司はいない、の具体的な苦労点が気になります

自分の進むべき方向や相談などができる人が、明確に決まっていない点です。

Q: 上流工程でQAが関与する具体例等をお聞かせ頂けますか??

要件定義からQAも交えて議論しています。 PMからの「こういう機能の追加・改善をしたい」という相談に対し PG・QAで「他の機能へ影響しそうだが問題ないか」「どういう挙動になる想定なのか」等のフィードバックを返しています。 また製品仕様書の作成・修正も、PGとQAで一緒に行っています。

Q: QAエンジニアはシステムテストや受入テスト、開発エンジニアはユニットテストや統合テストを担当、等の担当分けをされていますでしょうか??

各テストの定義が異なるかもしれませんが、以下kintoneチーム内の用語で回答します。 自動化しているテスト(受け入れ試験)については、テストケースをQAとPGで一緒に考えてPGが自動化しています。 ユニットテストについては、一部管理しているもの以外は、PGの裁量で作成してもらっています。 機能試験や自動化されていない受け入れ試験等、手動で実施するテストについてはQAがすべて担当します。

Q: バグなのか仕様なのかで、プログラマと喧嘩になりませんか?

喧嘩にはならず、PGとQAエンジニアが納得するまで議論を続けます。

Q: 実際に今の業務でイメージと違ったことや苦労していることはなんですか?

想像よりもミーティングが多いと感じました。 また自分の知識の無さに苦労しています。

Q: QAエンジニアに未経験で実際なってみて、ギャップ等はありますでしょうか??

思ってたよりも、Webアプリケーションの知識が必要だと感じました。

Q: 今後どの様なスキルがQAエンジニアに必要と考えてらっしゃいますか??

WebアプリケーションのQAエンジニアとしては、Web技術の知識が必要だと考えています。

Q: QAをやっていて楽しいことはありますか?

仕様検討のときに、QAエンジニアの立場として、考慮漏れなどを指摘できたときにやりがいを感じます。

Q: 現在、転職活動中なので、参考にさせて頂きたいのですが、現在のQAエンジニアを通して、最終的にこういうエンジニアになりたい、といったようなビジョンがもしありましたら、教えて頂けますでしょうか。

「kintoneの仕様は私に聞けばOK」という人になりたいです。

Q: これからkintoneをどうしていきたいですか?

もっと多くの人に使ってもらえるようなサービスを目指したいです。

おわりに

初めてのオンライン開催のMeetupでしたが、当日は質疑応答や実況が盛り上がりました。 ご参加いただいた皆様、本当にありがとうございました。 今後もCybozu Tech Meetupを定期的に開催します。

次回は6月中旬ごろに、『Garoon開発チーム』にフォーカスをあてたテーマで開催する予定です。 近いうちに改めてconnpassで告知するので、お楽しみください!

マージボタン1つで本番適用するための仕組み

$
0
0

こんにちは、Yakumoチーム兼コネクト支援チームの@ueokandeです。

本日はYakumoチームで構築した、デプロイパイプラインとその工夫について紹介します。 Yakumoプロジェクトはグローバル市場向けに、kintone.comをAWSから提供することを目指すプロジェクトです。 これまで日本のデータセンターから提供していたkintone.comを、現在AWS上に移行しています。 プロジェクトのもう1つのゴールとして、開発・運用体制を見直してクラウドネイティブなリリースプロセスを確立するというのがあります。

このプロジェクトは、国内向けのcybozu.comと完全に切り離されて開発がスタートしました。 ゼロからリリースフローを作るということで、これまでのインフラの経験や反省点を活かしつつ、チームの理想的なデプロイパイプラインの構築を目指しました。 そして最終的には、マージボタン1つで本番適用できるデプロイパイプラインを構築しました。 この記事ではYakumoチームのデプロイパイプラインと、それに至るまでの経緯を紹介します。

kintone.comのインフラ構成

kintone.comは異なる責務を持ったいくつかのサービスから構成されます。 たとえば、マルチテナントを実現するサービスや、非同期ジョブを処理するサービスなどです。 各サービスはKubernetes (Amazon EKS) を使ってデプロイされます。 それぞれのサービスが利用するデータベースやストレージは、AWSのマネージドサービスを利用します。

kintone.comのインフラ構成
kintone.com on AWSのインフラ構成

理想のデプロイパイプライン

インフラ構成の更新や本番オペレーションでは、運用するチームにとって負担が少ないことが理想です。 これまでの国内インフラの運用経験で、オペレーションを自動化するだけでは管理者の負担が減らないというのがわかっていました。 Yakumoプロジェクトでは、KubernetesやTerraformで採用された宣言的モデルという考えに基づいて、デプロイパイプラインを構築しました。

ソースコードは構築したいインフラの構成を表し、常に本番環境の設定はソースコードを見ればよいという状態を維持します。 そしてインフラの構成やアラートの閾値調整も、ソースコードを変更してマージするというフローにして、本番オペレーションを最小限に抑えました。

kintone.comのデプロイパイプラインの図
kintone.comのデプロイパイプライン
宣言的モデルをインフラに適用するのに必要なのが、差分検知と差分を埋めるオペレーションです。

差分検知とオペレーション

宣言的モデルでは、管理者が定義した状態を元に、実際のインフラを定義された状態に収束させます。 システムは、実際のインフラの状態と定義された状態との差分を検知し、その差分を埋めるオペレーションを実行します。

AWS CloudFormationやKubernetesは、この仕組を備えています。 ユーザーが定義したマニフェストファイルをCloudFormationやKubernetesに与えると、その定義ファイルに基づいたインフラの構築やコンテナをデプロイします。 またマニフェストファイルで何らかのパラメータを変更すると、対象リソースや関連するリソースに新しい設定を反映します。

一方でインフラレイヤーだけでなく、Yakumoチームが開発しているサービス群のデプロイもあります。 こちらもソースコードと本番環境の状態を常に一致させたいです。 サービスのソースコードを更新すると、新しいバージョンのイメージをKubernetes上にデプロイしたいです。 サービスの更新を検知するために、コンテナのイメージタグにソースコードのハッシュ値を採用しました。

ソースコードからイメージタグを生成

それぞれのサービスのイメージタグは、バージョン番号vX.Y.Zなどではなく、ソースコードから計算したハッシュ値を使います。 このソースコードには、サービス本体のコードだけでなく、コンテナに同梱するスクリプトやDockerfileも含みます。 ソースコードやDockerfileが更新されると、新しいイメージタグでコンテナレジストリに登録されます。

Kubernetesに適用するマニフェストは、レポジトリに含まれるソースコードからハッシュ値を計算して、イメージタグを埋め込みます。 ソースコードに更新があったサービスは新しいイメージタグが割り当てられ、 Kubernetesマニフェストの適用 (kubectl apply) でサービスのコンテナが更新されます。 開発者は、現在サービスのどのバージョンがデプロイされているかを考える必要がなくなり、本番環境は常にレポジトリにあるソースコードがデプロイされる事になります。

spec:containers:- name: my-service-a
      # 575C70E78FE448BC973EB46A46F4AE8Bなどのイメージタグが適用されるimage: quay.io/cybozu/my-service-a:{{ tag "my-service-a" }}

ケーススタディ: コンテナのベースイメージ更新

このしくみにより本番適用の自動化が容易になり、マージボタン1つで本番適用できるしくみができました。 ここで1つ例として、コンテナのベースイメージの更新を考えます。

例えばJVM用にビルドされたサービスの、Javaのバージョンの更新を考えます。 Yakumoチームでは管理の容易化のために、JVMサービスでは同じベースイメージを利用します。 セキュリティパッチなどで新しいJavaのバージョンがリリースされると、そのたびに本番環境のサービスを更新する必要があります。

一般的なベースイメージの更新作業は以下のとおりです。

  1. べースイメージのDockerfileを更新
  2. ベースイメージのリビルド (docker build)
  3. ベースイメージをコンテナレジストリに登録 (docker push)
  4. 各種サービスをリビルド (docker build)
  5. 各種サービスをコンテナレジストリに登録 (docker push)
  6. 各種サービスを本番環境に適用 (kubectl apply)

Yakumoチームのリリースフローでは、手順1.のDockerfileを更新してマージするだけです。

ベースイメージもまた、ハッシュ値のイメージタグを持ちます。 各サービスはベースイメージのタグを参照するため、ベースイメージが更新されると、それを利用するサービスも更新されます。 そして新しいイメージタグを持つサービスが本番環境に適用され、作業は完了です。

コンテナのベースイメージの更新と本番適用
コンテナのベースイメージの更新と本番適用

まとめ

以上がYakumoプロジェクトで取り組んだ、リリースフローとデプロイパイプラインの紹介でした。 宣言的モデルにより、「インフラやサービスの状態を確認し、影響範囲を考慮しながら、適用手順を作成する」という難しいフローも発生しません。 このリリースフローを確立したことで、本番環境への適用オペレーションというのがほぼなくなりました。 masterマージと本番適用が同義となり、切り戻しもgit revertするだけ、というフローです。

この理想のデプロイパイプラインも、一朝一夕で構築できたわけではありません。 これまでのcybozu.comの知見や、チーム内で積み重ねてきた改善活動によって現在の形に至りました。 このリリースフローで退屈な適用オペレーションを省けるだけでなく、製品の価値をより早くユーザーに届けることにも繋がります。

みなさんも、同じオペレーションを繰り返してるなと感じた時は、理想のリリースフローを探求してみてはいかがでしょうか?

styled-componentsの採用と既存資産を捨てた理由

$
0
0

こんにちは。フロントエンドエキスパートチームの@nakajmgです。

私が所属しているフロントエンドエキスパートチームでは、現在 kintone の脱レガシーの一環として React + TypeScript 化に取り組んでいます。この取組の中で Scss で定義されている既存のスタイルを styled-componentsで書き直していくという決定をしました。

今回は styled-components の採用を決定するまでの過程や、既存の Scss ファイルの扱いについて検討した内容などを紹介します。

スタイル定義方法の検討

kintone にはユーザーが JavaScript でカスタマイズできる機能があり、ユーザーが行っているカスタマイズの中には、DOM 構造や CSS のクラス名に依存しているものもあります。このようなカスタマイズはサポートの対象外ではありますが、ユーザーにできるだけ不利益を出したくないという考えから、かなり慎重な方法も検討しました。

  • React コンポーネントで DOM 構造とクラス名を再現して既存のスタイル(Scss)をそのまま使う
  • Scss ファイルからコンポーネントで使うスタイルを抜き出して、コンポーネントと対になる Scss ファイルを新規で作成する
  • 既存の Scss を使わず新たにスタイルを定義し直す

これらは検討した案の一部です。プロダクトがすでにリリースされて多くの人に使われているという状況と、どういった形でリリースしていくかを考えて、これ以外にも様々なパターンが挙げられました。

既存の Scss ファイルを捨てる

さまざまなパターンを検討した結果、スタイルについては再利用せずに書き直そうという決定になりました。

これは既存の Scss が@mixin@extendを多用していて、かなり複雑な状態になっていたこと、それが課題として認識されてたのが大きな要因です。課題を解決するより新しく書くほうがコストが低いと考えてこのタイミングで捨てることを決断しました。

また、DOM 構造についても既存のものを踏襲せずに、よりよいマークアップで作り変えていこうとなりました。

スタイルを何で定義するか

既存の Scss ファイルを使わないことが決まったので、次は React コンポーネントでスタイルを定義する方法です。

React コンポーネントのスタイルの定義方法については、次の3つの候補が挙げられました。

  • Scss ファイルで新規作成
  • Scss + CSS Modules
  • CSS in JS

Scss ファイルで新規作成

既存メンバーが慣れ親しんだ方法で行い、コンポーネントごとのスタイルを@extendなどを使わずに、現状より独立した形で捨てやすい単位で定義し直すという選択肢です。

この方法は React 化が完了していない部分のスタイルと相互に干渉しあう可能性があること、コンポーネント化の恩恵を受けづらいことを理由に採用しませんでした。既存の Scss の課題をどうにかしたいと思っていたのもあり、メンバーからは新たな課題を生み出すだけではという懸念が出ました。

CSS Modules ( with Scss )

Scss で挙げたような懸念を一部解消できる選択肢として CSS Modules(css-loader による利用) も候補に挙がりました。.scssをコンポーネントからimportして使うことで scopedなスタイルとして使えること、メンバーが持っている知識で運用できるという点では悪くない選択肢のように思いました。

しかしながら、現在 CSS Modules はメンテナンスオンリーな状態にあり、将来 deprecateとされる可能性が高く、採用するにはリスクが高いと判断しました。

また、.scssファイルを ES Modules の構文で importするのは、ビルドツールの選定において CSS Modules をサポートしていることが必要条件となってしまうので、将来的に身動きがとりづらい状況になってしまう可能性があります。

CSS in JS

CSS in JS はその名のとおり JavaScript の中で CSS を書くものです。CSS in JS を実現するためのツールは多くありますが、代表的なものとして styled-components や emotion があります。

今回 styled-components を採用することにしましたが、次のような懸念点も挙げられました。

  • JavaScript(TypeScript)の中でスタイル書いていくのつらそう
  • styled なラッパーコンポーネントとかがいっぱい作られていろいろつらそう
  • JSX に styled なコンポーネントと React コンポーネントが混ざって視認性が悪くなりそう
  • デザイナーが参加しにくくなりそう
  • 負債になりそう

検討を重ねた結果これらの懸念点のほとんどは styled-components の使い方にルールを設けることで払拭できそうだと考えました。

styled-components の使用ルールを定める

styled-components を使う上で定めたのは次のようなルールです。

  • styledはコンポーネントを引数に取ってスタイルをあてる以外の使い方をしない
  • styledは同一ファイル内で使用してファイルを分割しない

具体的には次のような書き方になります。

import React from "react";
import styled from "styled-components";

const Component = ({ className }) => (
  <div className={className}>
    <div className={`${className}__child`}>コンポーネント</div>
  </div>
);

const StyledComponent = styled(Component)`
  background-color: #fff;
  color: #000;
  &__child {
    border: 1px solid #000;
  }
`;

exportconst MyComponent = StyledComponent;

JavaScript の中でスタイルが書くのがつらそうという懸念については、IDE やエディタにプラグインを入れることで軽減できます。また、この形式であれば styled なコンポーネントと React コンポーネントがごちゃ混ぜになることはありません。React コンポーネント自体もピュアな React コンポーネントの状態を保つことができます。

デザイナーがスタイルの修正などに参加しにくくなりそうという点も、スタイルが記述されている場所が限定されることで、これまでの .scssファイルと同じように変更を加えられるようになっています。

そして将来的に負債になりそうという懸念ですが、これはどんなツールやライブラリを選んだとしても、早かれ遅かれ、多かれ少なかれ負債になるものだと考えています。その負債を小さくするために考えるべきは、スタイルを小さい範囲で書いて捨てやすい状況にすること、そして変更に耐えれる設計にできるかどうかです。この観点からすると、styled-components でも前述したルールで運用すれば大丈夫であろうと判断しました。

Scss の資産と向き合う

styled-components でやっていくにあたり、既存の Scss に定義してある変数をどうするかについて検討しました。

最終的な決定としてはこの機会に Scss の資産をすべて放棄して、styled-components に適したやり方で色などを管理していこうとなりました。styld-componets はテーマによってスタイルを変更する機能を持っているので、テーマとして扱っていくことにしました。

検討の途中で Scss の変数を抽出して JavaScript から扱えるように変換する方法を試してみました。このプロジェクトでは使わないことにしましたが、移行の途中段階などで使える状況もあるかと思いますので、方法を紹介します。

Scss の変数を JavaScript に変換する

次のようなディレクトリ構成をもとに変換方法を紹介します。

.
├── package-lock.json
├── package.json
└── src
    └── scss
        ├── _components.scss
        ├── _variables.scss
        └── entry.scss

変換には次のパッケージを使います。

npm からインストールしてください。

npm i -D sass-extract sass-extract-js rgb-hex

変換スクリプトの作成

次の内容を extractScssVariables.jsとしてプロジェクトのルートに作成します。

const fs = require("fs");
const path = require("path");
const sassExtract = require("sass-extract");
const rgbHex = require("rgb-hex");
const{ writeFile } = fs.promises;

const extract = () => {const rendered = sassExtract.renderSync(
    {
      file: path.resolve(__dirname, "./src/scss/entry.scss"),
    },
    {
      plugins: [{
          plugin: "sass-extract-js",
          options: { camelCase: false},
        },
        {
          plugin: {
            run: () => ({
              postExtract: (vars) =>
                Object.keys(vars).reduce((ret, key) => {
                  ret[`$${key}`] = `#${rgbHex(vars[key])}`;
                  return ret;
                }, {}),
            }),
          },
        },
      ],
    }
  );
  console.log(rendered.vars);
  writeFile(
    path.resolve(__dirname, "./src/variables.js"),
    `
    exportconst scssVariables = ${JSON.stringify(rendered.vars, null, 2)}
  `.trim()
  );
};

extract();

Scss の中身

今回サンプルとして使うのは次のような Scss です。

// src/scss/_variables.scss
$color-text: #333;
$color-text-hover: #666;
$color-active: #3498db;
$color-border-focus: $color-active;
$bg-button: #f7f9fa;
$bg-button-hover: $color-active;
// src/scss/_components.scss
@import "./variables";
.button {
  color: $color-text;
  background-color: $bg-button;
  &:hover {
    color: $color-text-hover;
    background-color: $bg-button-hover;
  }
  &:focus {
    border-color: $color-border-focus;
  }
}
// src/scss/entry.scss
@import "./components";

これらの Scss をもとに変換します。

変換してみる

次のコマンドをプロジェクトのルートディレクトリで実行します。

node ./extractScssVariables.js

変換結果

実行が終わると、次のようなファイルが生成されます。

// src/variables.js
export const scssVariables = {
  "$color-text": "#333333",
  "$color-text-hover": "#666666",
  "$color-active": "#3498db",
  "$color-border-focus": "#3498db",
  "$bg-button": "#f7f9fa",
  "$bg-button-hover": "#3498db",
};

TypeScript で補完を効かせたい場合には次のように as constを足してください。

writeFile(
  path.resolve(__dirname, "./src/variables.js"),
  `
  exportconst scssVariables = ${JSON.stringify(
    rendered.vars,
    null,
    2
  )} as const
`.trim()
);

これで Scss の変数が JavaScript から扱える形になりました。直接importrequireをして使ったり、styled-components のthemeで注入して使うといったことが可能になります。

今回変換対象として用意した Scss ファイルはシンプルなものでしたが、このスクリプトはエントリーとなる Scss から辿れる全ての変数を抽出してくれます。

今回紹介したスクリプトのサンプルが次のリポジトリに置いてありますので、ぜひお手元の Scss ファイルで試してみてください。

https://github.com/nakajmg/sample-scss-variables-to-js

おわりに

styled-components の採用を決めた経緯と、既存の Scss をどう扱っていくかについて紹介しました。

負債はコツコツと返済していければ良いですが、対象やタイミングによっては難しいこともあるので、思い切って捨ててしまう(作り直す)という選択も有効です。

プロジェクトの状況やチームが達成したい目標によってレガシーとの向き合い方はさまざまですが、先を見据えてじっくりと検討を重ねて、一歩づつ進んでいければと思います。

サイボウズには「社内コネクト」を支援するチームがいます

$
0
0

社内イベントの様子
2019年開催のハッカソンでの1枚
こんにちは!プロ野球がついに始まりましたね! 開発本部社内コネクトチームのhokatomoです。

今日は私が所属している「社内コネクトチーム"WASABI"」の紹介です。 このチームは「社内開発イベントを通じて、製品の垣根を超えた輪を広げていく」をミッションにし、活動をしています。

具体的な活動

  • 社内(主に、開発本部・運用本部)の繋がりを作る・深めるためのイベントの企画運営
  • 多拠点に開発・運用のメンバーがいるので、自チーム以外でもそれぞれのことを知り興味を持つきっかけを作る
  • よりよいチームワークに繋げる

を意識し、社内イベントの企画開催運営を行っています。

なんで「WASABI」なの?

もともとの名前は「社内開発イベントPJ」だったのですが、

  • 「社内開発イベントPJ」が呼びづらい
  • (WASABIのきっかけとなったイベント)フリージャンルのLTやりたい→みんなに話してもらうイベントに→寿司食べながら聞きたい→話しながら寿司を食べる(※)→話す+寿司🍣→話スシ(!)→寿司といえばワサビ→WASABIチーム誕生!

などの経緯や事情があり「WASABI」という名前になりました!

※2020年6月現在フルリモート開催なので寿司は出ていません。

WASABIチームは現在国内の3拠点、4人💪

それぞれ別のチームを兼務し、

  • モバイルエンジニア(東京)
  • フロントエンドエンジニア(松山)
  • QAエンジニア(大阪)
  • 技術広報(東京)

といったメンバー構成です。 リアルで全員集合したことは多分まだないです。

どんな社内イベントを開催しているかと言うと👀

社内LTからPodcast、成果発表会からハッカソンまで!さまざまなイベントを開催しています。

  • LTイベント(技術メイン、趣味、自己紹介等テーマはさまざま)
  • 社内座談会
  • 社内Podcast
  • 合宿
  • ハッカソン

など、普段業務で関わらないようなチームや本部、メンバーとコミュニケーションが取れるイベントを企画したり それぞれの取り組みや人となりを知れるイベントを開催しています。(今年は合宿の開催は見送りました)

開催したイベントの関連記事

blog.cybozu.ioblog.cybozu.ioblog.cybozu.io

2020年前半は合計13回開催👏👏

2月以降はすべてフルリモート・オンライン開催で、以下のイベントの企画・運営、一部運営のみなどをやりました。

  • 自己紹介系LTイベント…3回
  • 技術系LTイベント…4回
  • 趣味系LTイベント…1回
  • 社内Podcast…2回
  • その他、各チームの成果発表報告会等…3回

月2回以上は何かしらのイベントを開催していますね! 詳細な参加人数は取れていないのですが、だいたい1回につき30人〜80人はリモート参加しています。

「オンラインならでは」の社内イベント事情

情勢を考えて、2020年2月からどこにも集まらず、運営も参加者も全員オンラインで開催です。 オンラインだと拠点の格差(多く集まっている拠点だけが盛り上がってしまう)がなく、 それぞれが思い思いに盛り上がれるのがオンラインの良さだと感じました。

社内イベントの待機画面
イベント開始までの待機画面を改善したり、イベントが少しでも楽しくなるような工夫も

ただ、フルリモート・オンライン開催だと「リアクションが分かりにくい」という問題が顕著なため効果音をつけたり、他にも社内オンラインイベントの体験を少しでも改善できるよう、イベント開始前にちょっとした案内を出したり、細かな改善なども模索しています。 (効果音の付け方は下記の記事を参考にさせていただきました)

参考記事

note.com

サイボウズならでは?!の社内イベント文化

実況スレッドでイベントの感想をやりとりしている様子
社内オンラインイベントで、おすすめの本が紹介された時の実況スレの様子

自社プロダクトの「kintone」に「実況スレ」を立てて、文字通りイベントを実況したり 疑問に思ったこと、感じたことなどをそれぞれ自由に書いています。 そこから意外な話に発展したり、嬉しいリアクションをもらえたり、他本部のメンバーが興味を持ってくれたり……。

社内イベントで実況スレをやってみるの、とってもおすすめです!

終わりに

リアルイベントに限りなく近づけるのは難しいので、現在は「オンラインならでは」の良さを楽しんで運営しています。

雑談やちょっとした会話が難しい今だからこそ、 社内のこうした取り組みがオンライン上でコメントをするきっかけになったり リアルで会ったときのコミュニケーションの種になると信じて、引き続き取り組んでいきます!

AWS移行が完了したUS版kintoneと、これからの挑戦

$
0
0

こんにちは、@ueokandeです。 先日6月21日に、日本のデータセンターで運用していたUS版kintone (kintone.com) が、ついにAWSに完全移行しました。 このAWS移行プロジェクトは2018年にスタートし、2019年秋の新規顧客向けリリースを経て、この6月に全US顧客のAWS移行が完了しました。 本日はUS版kintoneのAWS移行と、これからのチャレンジについてお話します。

モノリスシステムからの独立

AWS移行プロジェクトには、2つの背景があります。

1つはUSを含めたグローバル市場の拡大です。 現地のUSリージョンで運用することで、ユーザーからのレイテンシを小さく抑えることができ、より安定したサービスを提供できます。 またこれを機に販売管理システムもUS市場向けに新規開発し、現地に合わせた販売戦略を計画できるようになりました。

2つめの背景として、クラウドネイティブ時代の、開発、運用フローの確立です。 国内に提供しているcybozu.comは、インフラシステムのモノリス化により、開発、運用面で新しいチャレンジがしにくいという課題があります (国内インフラに関してはNeco/Manekiチームによるインフラ刷新が進んでいます)。 AWS移行プロジェクトでは、国内のcybozu.comから独立して開発がスタートしました。 国内インフラの運用経験を活かして、高速なリリースフローや、コンテナアプリケーションの運用などに取り組んできました (具体的な取り組みは過去の記事を御覧ください)。

US版kintoneのこれから

移行プロジェクトが無事終了し、チームは6月いっぱいで解散しました。 しかしチームがなくなっても、サービスが終了するわけではありません。 解散後は元チームメンバーの数名がkintone開発チームに参加し、今後はkintone開発チーム全体でUS版kintoneをメンテナンスします。

開発者と運用者が同じチームに属することで、機能開発から、リリース、運用までの一連のフローが、同じチーム内で完結します。 チーム内で意思決定がしやすく、メトリクスの追加やパフォーマンスチューニングなど、新しいチャレンジへのハードルが下がります (従来のプロダクトでは、運用チームと開発チームが明確に分かれています)。

これまでkintoneの機能開発をしていたメンバーが運用に携わることで、真のサイトリライアビリティに向けた取り組みがスタートできます。 AWS移行プロジェクトではできなかった、今後の取り組みについては、以下のようなものがあります。

  • アプリケーションやユーザー視点でのSLI/SLOの検討
  • Microservicesなどの脱モノリス
  • 分散トレーシングなどのクラウドネイティブな監視の仕組み

US版kintoneの開発、運用体制は、技術的だけでなく組織的にも、社内のカナリアリリースに当たります。 そして溜まった知見は、将来のNeco/Maneki移行に活かす予定です。

もっとAWS移行について知りたい人は

7月14日(火)にMeetupを開催します! 当時のプロジェクトメンバーから、US版kintoneのアーキテクチャや、移行の裏側についてお話します。 YouTube Liveによるオンライン配信予定なので、興味のある人はぜひご視聴ください!

cybozu.connpass.com

#osc20do でサイボウズ開発本部各チームの文化とコミュニティについてお話ししました

$
0
0

登壇者 4 名が談笑している様子
「ぼく、サイボウズで優勝したよ!」で優勝している 4 人の様子

西原(@tomio2480)です.5/1(金) に入社して以降,ここに記事を書くのははじめてです.地方ITコミュニティのことを 10 年くらいやっていて,お仕事でもそこをやっていくことになりました.
 
さっそくですが,イベントの登壇報告をいたします.6/27(土) Open Source Conference 2020 Online/Hokkaido に参加してきました.タイトルにもある osc20do は本イベントで用いられたハッシュタグです.イベント名が長いので,これ以降は便宜的に osc20do と表記します.
https://event.ospn.jp/osc2020-online-do/event.ospn.jp

Open Source Conference は一般に OSC と呼ばれていて,一年とおして各地で開催されています.北海道地区オフライン開催の代替としてオンライン開催となったのが今回のイベントです.サイボウズからはセッション枠とミーティング枠の 2 枠をいただいてお話ししてきました.また,一般社団法人 LOCAL が担当する枠にも自分含めサイボウズ社員が 2 名登壇しました.
 

 
いずれも手応えを感じるいい時間になったと聞いています.特に低レイヤ相談室は延長戦に次ぐ延長戦で合計 3 時間ちかくの濃い時間を過ごすことができたとようです.私は裏でコミュニティ関連の話にちょっと出ていたり,スタッフ業だったりでまともに参加できなかったのですが,話を聞く限りでは sat さん (@satoru_takeuchi) と内田さん (@uchan_nos) の知識/経験量をなくして成り立たないコンテンツだったということでした.北海道出身として北海道関連のイベントで盛り上げていただいてありがたい限りです.
 
さて,今回はこの盛りだくさんのコンテンツから,西原が登壇したものに絞りレポートします.今回触れないコンテンツについても公開可能な物に限り,主催である OSPN の YouTube で視聴することができます.
https://www.youtube.com/channel/UCEPkrGMghsXnoG1LffgjMJg 

ぼく、サイボウズで優勝したよ!──地元ゆかりの社員が本音でサイボウズを語る

登壇の様子を Twitter 上に残しましたが,自分で撮ったのに謎の表情がスクショにおさまってしまいました.
 
今回は北海道に縁のある 3 名とサイボウズ歴の長い 1 名,合計 4 名でお話しをさせていただきました.
 
  • 今野遼太 @rkonno / 開発本部 モバイルチーム
  • 笠間保鷹 @b4h0-c4t / 開発本部 フロントエンドエキスパートチーム
  • 上岡真也 @ueokande / 開発本部 コネクト支援チーム
  • 西原翔太 @tomio2480 / 開発本部 コネクト支援チーム
  • (配信担当) 風穴江 @windhole / 開発本部 コネクト支援チーム

 
よく試された道民であればわかると思いますが,今回のタイトルは道民に愛されしフレーズを RESPECT したものになっています.実はスライドの中にもいくつかそんなフレーズを盛り込んでおきました.
 
セッションの内容は「サイボウズに入社したきっかけ」「入社前の印象と入社後のギャップ」「サイボウズ社員として北海道でやりたいこと」という 3 点の質問を基軸に「サイボウズと自分」を座談会形式でお話しさせていただきました.
 
今回のねらいは「サイボウズで働く人々を通じてサイボウズを知ってもらう」ことです.そのためにも北海道民がたくさん参加するであろう osc20do では道民の感覚を持った人たちの登壇が不可欠でした.特に北海道の OSC は学生が多いこともあって,そもそも働いている人の生態を知る機会として使って欲しい!という思いも込めておりました.
 
座談会形式でお話しすることでそれぞれの所属チームの違いや,サイボウズとの出会いの違い,出身地や住んでいた地方地域の違いを比べながら,様々な視点からの経験を伝えられたかなと感じています.ざっくり言うと「会社に入るまでの動きと共感」「チームごとに異なる仕事の発生の仕方」「専門性の生かされ方」「北海道に対する思い」この辺りが話の中心だったかな,というところです.
 
詳しくは YouTube で動画をご覧ください! www.youtube.com 

[北海道企画]北海道出身のエンジニアが学生の質問に答えます!

こちらの企画は一般社団法人 LOCAL が担当するイベントで,北海道の IT 系を学ぶ学生たちが所属する LOCAL 学生部の部員に向けたセッションでした.実は登壇者 5 名はいずれも LOCAL 学生部を卒業したエンジニア他で構成されています.サイボウズからは 2 名登壇しました. (ちなみに,竹内さんは LOCAL 学生部の元部長です.)
 

  • 竹内健人(けんつ) @lrf141 / 開発本部 Shash/CyDE-Cチーム
  • 西原翔太 @tomio2480 / 開発本部 コネクト支援チーム
  • 他 LOCAL 学生部 OB 3 人

 
セッションでは,LOCAL 学生部の現役メンバーから出た質問に対してそれぞれの立場から答えていきました.登壇者全員が北海道で生まれ育ち,コミュニティ運営活動の経験もあり,いずれも工業大学を出ているなど共通点は多かったのですが,回答にはそれぞれの色がでました.当然社会に出てからやってきた仕事はそれぞれ別々で,その違いが大きく出たコメントになったなぁという印象です.
 
学生からは研究と仕事の関係や,学ぶ分野を広げるべきかどうかといった学習や就職に関係する質問もあれば,北海道と関東関西の暮らし的な違いや北海道ならではの勉強会文化など北海道と本州の比較についての質問もあり,未来を想像する上でのヒントを得たい!という思いが伝わってくる質問が多かったと感じています.
 
そろそろ一回り違う子達が後輩にあたる頃合いです.彼らとは育った時代背景が違うし,疑問に感じることも違うだろうから,質問への回答も困るかもしれないと想像していました.しかし,いざ蓋を開けてみれば,自分も感じていたような疑問や悩みが出てきました.同じ北海道で生まれ育った人たちだと,時代を超えて抱く疑問も似通うのかもしれません.
 
しかし,ひとつここで思ったのは,自分たちがぶつかった課題を解決しないままに置いてきたから,同じ課題にぶつかっているのかもしれないということです.誰もが通る道とよく言いますが,それは今まで通ってきた人の怠慢でつらいままのこともあるのではないかということです.自分にできることはどんどんやっていかなきゃな,と考えさせられるセッションでした.
 
こちらもやはり詳細は動画をご覧いただければわかりやすいです.
www.youtube.com

持っている知見は惜しみなく発信しよう

以上,自分の登壇したセッションについてのレポートでした.
 
今回パネルディスカッションというか座談会というかを 2 つやってみて,登壇者同士が事前にどれだけ交流を取れるかが鍵だと感じました.サイボウズのセッションは社内向けにプレ開催をやっていて,ここでいろいろ登壇者同士で意見交換ができたのがデカかったと思います.こういった取り組みによって阿吽の呼吸感を生み出せれば,いい回答をお互いに引き出していくことができるのかなと思いました.(LOCAL のセッションも事前の顔合わせをしています.こっちは内輪感が出過ぎないように注意するぞ!という決起集会でしたが)
 
もちろん,聞き手側が何を求めているかということを知っていないと,独りよがりになってしまいます.しかし,座談会だと登壇者半分,視聴者半分で参加できるので「興味深く聞けることを引き出す視聴者」としての役割を担えます.ここを意識すると,当日も面白い流れを生むことができそうです.
 
サイボウズセッションを道産子勢で固めたのは正解でした.はじめての顔合わせでも,わかりあうための前提がある程度整っていて,視聴者とも同じ視点をわざわざ作る必要がありませんでした.これからの OSC でも各地の出身者を集めて座談会ができると,その地方の参加者に喜んでもらえる情報発信ができるのではないかと,今回の登壇を通じてより強く感じました.
 
どれだけの効果があったかは分かりませんが,こうしてサイボウズ社員の魅力を伝えられる場を設定できたこと,各地方の人とわかりあえるきっかけの場を作れたことはサイボウズに所属する一員として願ったり叶ったりです.これからもこういった視点の取り組みを続け,地方の IT エンジニアや IT エンジニアを目指す人たちに役立つ活動を展開していきたいと考えています.どうぞよろしくお願いいたします!


おまけ情報

7/18(土) 14:00 - LOCAL Developer Day Online ’20 /Security にて「地方ITコミュニティとの関わりに何を求めていたのか」というタイトルで西原が登壇します.オンライン全盛になりつつある今,地方ITコミュニティとはそもそもどんな役割を果たしていたのか,ということについて考える時間にします. local.connpass.com

CircleCIで勝手に強くなる静的解析の作り方

$
0
0

こんにちは。Garoonチームの杉山(@oogFranz)です。

以前 #PHPerKaigi 2020にて、「静的解析の育て方」というタイトルで発表いたしました。この発表ではレガシープロダクトにおいて静的解析が有効であることと、「育てる」という比喩表現で静的解析のルールを強くしていく戦略についてお話ししました。

発表後のAsk the Speakerでは様々な方から講演へのフィードバックをいただき、特に既存のプロダクトに静的解析の導入・運用する大変さやその改善方法に関して議論を深めることができました。議論に参加いただいた皆様大変ありがとうございました。

議論の中でルールを自動的に強くしていく方法のヒントをいただきました。そのヒントを元にCircleCIで勝手に強くなる静的解析が実現ができたので紹介したいと思います。

勝手に育つ静的解析の作り方

通常、レガシープロダクトにおいて静的解析を実行すると大量のエラーが報告されてしまいます。「静的解析の育て方」では、Phanと呼ばれる静的解析ツールをGaroonに適用しましたが、はじめは約4万件ものエラー(Phanではissueと呼ぶ)が発生しました。

この大量のissueを抑えるために、Garoonでははじめsuppress_issue_typeという設定項目を利用しました。この設定項目に指定されたルールは静的解析時に無視されるようになります。issueが報告されているルールの一覧を作成し、このsuppress_issue_typeに指定することで、issueが報告されない状況を作り、CIを実行できる状況を作り上げました。その後、各ルールごとに、修正するコストと修正しないリスクを測り、少しづつ修正していくという戦略をとりました。しかし、この方法には次のような問題がありました。

  • 静的解析を強くしたい場合、開発者が明示的にsuppress_issue_typeに記載されているルールを更新する必要がある。
  • 設定がプロダクト全体に適用されるため、新規作成されたファイルでもエラーが無視されてしまう。
  • 多くのルールが無視されるため、CIによってエラーが報告されることがほとんどなくなってしまう。

この問題を解決するために、

  • 静的解析のルールを強められる場合には、自動的に強くする。
  • 既存コードの問題は無視したまま、新規作成されたファイルでは強い静的解析を行う。

仕組みを作りました。次の節からその実現方法を紹介します。

勝手に強くなる静的解析の構成

この仕組みの実現には、PhanのベースラインとCircleCIのキャッシュの機能を利用しました。

ベースライン

PhanをはじめとしてPsalmPHPStanといったPHPの主要な静的解析ツールには、既存のコード上のissueを“ベースライン”として保存し、静的解析時にベースラインに記載されたコード上のissueを無視する機能が搭載されています。 ツールにもよりますが、ベースライン作成のコマンドを実行すると、各PHPファイルごとにどのようなissueがあるかがリスト形式で保存されます。

Phanでは次のような形でベースラインの保存と、ベースラインを用いた静的解析が行えます。

phan --save-baseline ~/phan_baseline.php ./ #ベースラインの保存 
phan --load-baseline ~/phan_baseline.php ./ #ベースラインを利用した解析

ベースラインの結果をレポジトリにコミットし、CIでそのベースラインを利用する、といった使い方も可能です。 ベースラインを利用する時は、保存した時と同じ設定値を使う必要があります。

CircleCIのキャッシュ

CircleCIには、過去のジョブや他のジョブのデータをキャッシュし、再利用する仕組みがあります。save_cacheでキャッシュキーとキャッシュする内容を指定でき、restore_cacheでキャッシュ内容を復元できます。一般的にはnpmcomposerといった依存関係管理ツールがインストールするライブラリなどをキャッシュし、CircleCIのジョブを高速化するために利用されます。
今回はベースラインをこのキャッシュに保存しています。

勝手に強くなる静的解析の実現方法

以下のようなCircleCIのジョブphan-load-baselinephan-save-baselineを作成しました。

  • phan-load-baseline: CircleCIのキャッシュからベースラインを取得し、取得したベースラインを利用してPhanを実行します。
  • phan-save-baseline: Phanでエラーが報告されなかった時に、ベースラインを更新します。

これにより静的解析に通ったときにのみベースラインが更新されるため、発生しなくなったissueがあれば、それ以降チェックされるようになる、 つまり静的解析が勝手に強くなるという算段です。

実際には以下のようなconfig.ymlを記述しました。(説明のため、実際に使われている設定ファイルを簡易にしています。)

...phan-load-baseline:docker:- image: docker-registory.cybozu.private/phan:X.Y.Z
  steps:- checkout
    - restore_cache:name: Restore phan baseline
        keys:- v1-phan-baseline-{{ .BRANCH }}- #(1)- v1-phan-baseline-develop-
    - run:name: Phan
        command: |
          if [[ -f ~/phan_baseline.php ]]; then #(2)
            phan ./ --load-baseline ~/phan_baseline.php
          fi

phan-save-baseline:docker:- image: docker-registory.cybozu.private/phan:X.Y.Z
  steps:- checkout
    - run:name: Save Phan baseline
        command: |
          set +e #(3)
          phan ./ --save-baseline ~/phan_baseline.php
          exit 0 #(4)- save_cache:key: v1-phan-baseline-{{ .BRANCH }}-{{ .Revision }} #(5)paths:- ~/phan_baseline.php
...

さらにphan-save-baselineジョブの依存関係として、以下のようにphan-load-baselineを指定することで、phan-load-baselineでphanの静的解析のエラーが出なかった場合のみベースラインを更新することができます。

...- phan-save-baseline:requires:- phan-load-baseline
...

追加で説明を補足していきます。

(1)はフィーチャーブランチとdevelopブランチ両方からベースラインの復元を試みています。フィーチャーブランチのベースラインが優先されますが、フィーチャーブランチが新しく作られたときには、まだベースラインが保存されていないためdevelopブランチのベースラインが利用されます。その後、フィーチャーブランチ上で静的解析を行い、エラーが報告されなければフィーチャーブランチ用のベースラインを利用するようになります。

(2)はキャッシュの読み込みに失敗した時のために記載しています。キャッシュの復元で特にヒットするものがなかった場合もCircleCIはエラーになりません。これはCircleCIのキャッシュがおもに、ジョブの高速化を目的に利用されているためです。

(3)と(4)はCircleCIのジョブを成功状態にするために追加しています。CircleCIは終了コードが0以外だと、ジョブを失敗したとみなします。Phanは、ベースラインの保存時であっても、エラーが検出されたときに0以外の終了コードを返すため、(3)のset +eと(4)のexit 0をつけてジョブが必ず成功するようにしています。

(5)では、キャッシュキーとしてv1-phan-baseline-{{ .BRANCH }}-{{ .Revision }}と、Revisionの値を利用しています。読み取り時に、Revisionの値を利用しないのであれば、書き込み時にも不要なのでは?と思う方もいらっしゃるかもしれませんが、一度保存されたキャッシュは書き換え不可なので、Revisionの値をつけて、最新の状況を更新していく必要があります。

最後に、Phanのライブラリをアップデートするなどして、報告されるissueが大きく変わった場合はどのように対処するかについても説明します。その場合はキャッシュキー名を変えることで対応できます。v1-から始まるキャッシュキーをv2-から始まるように書き換えれば、新たなベースラインがdevelopブランチに作成されるようになります。

まとめ

今回の仕組みにより、静的解析のルールは自動的に強くなっていくようになりました。これにより静的解析の設定ファイルの管理コストを下げることができました。

また、ベースラインがファイル単位でルールを指定しているため、新しく作られたファイルでは、全てのルールが適用されるようになりました。これにより、新機能の開発時には、もっとも強い静的解析のルールが適用されたコードが書かれるようになりました。レガシープロダクトでは、既存のコードが参考にされ、新たなコードが書かれることが多くありますが、この既存のコードに問題があった場合、問題のあるコードがどんどん増えていってしまいます。今回の仕組みによって、そのような心配がなくなったのは大きなメリットでした。

新規のプロダクトでは静的解析ツールを導入することが一般的になってきていますが、既存のプロダクトに静的解析を導入・運用するためには、この記事で紹介したような課題を克服していく必要があります。この記事が少しでもそういった課題を持っている方の助けになれば幸いです。


大規模データベースを安全にマイグレーションする仕組み

$
0
0

こんにちは、Yakumo兼コネクト支援チームの@ueokandeです。 サイボウズには体験入部という制度があり、数週間〜数ヶ月の期間、他チームの業務を体験できます。 自分もこの制度を使い、1ヶ月ほどGaroon開発チームを体験してきました。

自分はこの期間で、Garoonの大規模なデータベースを安全にマイグレーションするためのしくみの設計と、そのプロトタイプを実装しました。

背景

Garoonはサービスのアップデートと同時に、データベースのマイグレーションを実行します。 ここでいうマイグレーションは、主に2つの処理があります。

  • テーブルスキーマの更新。ALTER TABLEによるカラムの追加、削除など。
  • データの変換。既存レコードのデータ編集など。

Garoonはメンテナンスウィンドウを設けてバージョンアップを実施します。 このバージョンアップが毎回確実に成功すればいいのですが、実際はそれを保証するのが難しいです。

Garoonのデータベース (MySQL) は、cybozu.com全体でテラバイト級のデータサイズになります。 アップデート時にむやみにマイグレーションを実行すると、以下の理由でマイグレーションが失敗するケースがあります。

  • 巨大なテーブルに対するALTER TABLEがメンテナンスウィンドウ内で終わらない
  • 巨大なテーブルに対するデータコンバートが、メンテナンスウィンドウ内に終わらない
  • 古いデータに、コンバートに失敗するデータがある

現在はメンテナンスウィンドウ内に確実にマイグレーションが終了するように、本番環境のバックアップデータを使ったリハーサルを実施しています。 しかし、毎回のバージョンアップでテラバイト級のバックアップデータをリストアしてリハーサル環境を構築するのは大変で、リハーサル環境そのものがメンテナンス困難になってきています。 そこでGaroon開発チームでは、リハーサル環境を使わずに、安全にデータベースマイグレーションをするしくみに取り組んでいます。

この体験入部期間では、このしくみの設計とそのプロトタイプ実装をしました。

安全にマイグレーションをするしくみ

データベースのマイグレーションを確実に終わらせるために、メンテナンスウィンドウではなく、事前に裏でデータコンバートを実行します。 あらかじめコンバート先のスキーマを持つ空のテーブルを作成しておき、お客様に影響がないよう徐々にデータをコピーとコンバートを実施します。 そしてバージョンアップ時に、コンバート後のテーブルをリネームして新しいテーブルに切り替えることで、マイグレーション終了です。

もしデータコンバートが失敗するデータを含まれていたとしても、アップデートの前に知ることができます。 また格納先のテーブルはマイグレーション後のスキーマを持っているので、ALTER TABLEで時間がかかるということもありません。 以降、コンバート後の格納先のテーブルを shadowedテーブルと呼ぶことにします。

shadowedテーブルの作成から、アップデートが完了までの流れは以下のとおりです。

  1. shadowedテーブルの作成
  2. TRIGGERの作成
  3. データコンバートの開始
  4. shadowedテーブルから元テーブルに切り替え

ステップ1. 2. 3. はダウンタイムなしにバックグラウンドで実施できます。 そしでメンテナンスウィンドウでステップ4.を実行して、アップデート後のGaroonはデータコンバート済みのテーブルを参照します。

どこまでコンバートが進んだかを判断するために、コンバート対象のテーブルに 単調増加するカラムが必要です。 これはAUTO_INCREMENT属性のカラムを使います(以降は idカラムとします)。 shadowedテーブルのレコードは、変換元のデータと同じ idを持ちます。

それぞれのステップについて詳しく説明していきます。

1. shadowedテーブルの作成

shadowedテーブルは、変換後のテーブルスキーマを持ちます。 このとき元テーブルに外部キー制約があっても、この時点ではshadowedテーブルに外部キー制約を付けません (CREATE TABLE ... LIKE構文を使うと外部キー制約なしで、同じスキーマのテーブルを作成できます)。 そしてshadowedテーブルに、元データの変更があったかを示す convertedカラムを追加します。 convertedはデータ変換後は1がセットされ、元データに変更があれば0になります。

ここでは todoテーブルのタイムスタンプ created_atを、int(11)から timestamp型に変換する例を考えます。 この場合CREATE TABLE ... LIKE構文で新たなテーブルを作成できます。 そこにALTER TABLE構文で、カラムの型を変更して、convertedカラムを追加します。

Shadowedテーブルの作成
Shadowedテーブルの作成

2. TRIGGERの作成

続いて元テーブルの更新、削除時を知らせるTRIGGERの作成です。 元データの BEFORE DELETEBEFORE UPDATE時に、shadowedテーブルに converted=0をセットします。 このTRIGGERによりshadowedテーブルにあるレコードが、古いデータということに気づくことができます。

たとえばtodoテーブルに対しては、以下の構文でそれぞれのTRIGGERを作成できます。

CREATETRIGGER update_todo_replica BEFORE UPDATEONtodoFOR EACH ROWBEGINUPDATE todo_shadowed SET `converted`=0WHERE id = NEW.id;
END;

CREATETRIGGER delete_todo_replica BEFORE DELETEONtodoFOR EACH ROWBEGINUPDATE todo_shadowed SET `converted`=0WHERE id = OLD.id;
END;

3. データコンバートの開始

shadowedテーブルとTRIGGERを作成したら、データコンバートをスタートできます。 データコンバートはバックグラウンドで実行し、お客様の利用に影響が無いように少しずつ処理を進めます。 この処理では以下の2つの条件を満たすまで、コンバートを繰り返します。

  1. 元テーブルとshadowedテーブルのmax(id)が一致する
  2. shadowedテーブルからconverted=0なレコードが存在しない。

もし条件1.を満たさないときは、元テーブルに新しいデータが追加されていることになります。 その場合はshadowedテーブルの max(id)より大きなIDから、条件1.を満たすまでデータコンバートしてshadowedテーブルにINSERTし続けます。 INSERTするときは元データと同じ idを使い、converted=1に設定します。

Shadowedテーブルに差分をデータコンバートする
Shadowedテーブルに差分をデータコンバートする

もし条件2.を満たさないときは、元テーブルのデータが更新または削除されたことになります。 同じ idを持つデータを再変換してshadowedテーブルをREPLACEするか、削除されている場合はshadowedテーブルからDELETEします。 REPLACEするときは同じ idを使い、再びconverted=1をセットします。

更新、削除されたデータを再コンバート
更新、削除されたデータを再コンバート

この処理を繰り返すことで、最終的に上記の条件を満たすことになり、そのときshadowedテーブルにはデータコンバート済みのデータのみが存在します。

4. テーブルの切り替え

いよいよGaroonアップデートの時間です。 テーブルの切り替えはメンテナンスウィンドウ内で実行されます。

まずテーブルの切り替え前に、上記のコンバートを確実に終了します。 このとき残りのデータ件数は少ないので即座に終了します。 それが終わると、shadowedテーブルを元のテーブル名にリネームして、元テーブルの外部キー制約を新しいテーブルに設定します。 このときforeign_key_checksを無効化して、外部キー制約のチェックをスキップします。

foreign_key_checksが有効になっていると、他のテーブルから外部キー制約で参照されているテーブルは削除できません。 またマイグレーション対象が外部キー制約を持っていたとしても、キーそのものをコンバートしない限りはマイグレーション後も外部キー制約が守れられます。 そのためforeign_key_checksを無効化しても、安全にテーブルを切り替えることができます。

TRIGGERを削除して、新しいテーブルに切り替えると、バージョンアップ完了です。

テーブルのリネーム
テーブルのリネーム

このときconvertedカラムは残り続けますが、内部のテーブル再構築を避けるためにカラムは削除しません。

制約事項

このしくみにもいくつかの制限があります。 現段階では設計や実装の容易化のために、以下の条件は制約事項としました。

  • 外部キー自体のコンバートはできません。shadowedテーブルには外部キー制約を設定しないため、たとえば外部キー制約が設定されているuser_idの値が変わる場合は、参照先のデータがあるか保証できません。
  • MySQL 8.0未満はサポートしません。MySQLにはサーバー再起動時にAUTO_INCREMENTがリセットされる不具合(#199)があり、その場合は正常にコンバートできません。この不具合はMySQL 8.0で修正されました。

おわりに

このしくみがうまく動くと、安全にデータコンバートを実行できるだけでなく、ゼロダウンタイムにもつなげる事ができます。 今回はプロトタイプ実装だけで終わりましたが、本番データのコンバート時間や、データベースの負荷計測などの課題も残っています。 そこは未来のGaroon開発チームに託して、自分は一旦Yakumoチームに戻ります。

以上で自分のGaroon体験入部の報告は終わりです。 体験入部ではGaroonという製品に向き合えただけでなく、Garoon開発チームの文化や開発フローについても知る、貴重な体験となりました。 Garoon開発チームの皆さんありがとうございました!

フロントエンドエキスパートチームが選ぶ web.dev Live2020 オススメセッション

$
0
0

こんにちは、フロントエンドエキスパートチームです。

フロントエンドエキスパートチームでは、フロントエンドに関する情報共有会を社内向けに行っています。
2017年から2020年までに10回開催しており、「Web ページのパフォーマンス」、「React の今とこれから」、「Google I/O セッションまとめ」などフロントエンドに関する情報を社内に共有してきました。

今回行ったのは、2020年6月30日から7月2日にかけて3日間オンラインで開催された web.dev Live2020のセッションについての情報共有です。各セッションはYouTubeにて視聴できます。

今回は各チームメンバーが視聴したセッションの中から、それぞれ2つのセッションを選んで内容と感想を紹介します。

まだ web.dev Live2020 を見ていない方が興味を持つきっかけになれば幸いです。

BaHoのオススメ

BaHoです。kintoneのDX向上やフロントエンドの技術改善に取り組んでいます。

[Sign-in form best practice]

https://youtu.be/alGcULGtiv8

サインインフォームを作る上でのベストプラクティスについてのセッションです。 シンプルなサインインフォームの実装を例に解説しています。

要点をまとめると、次の3つのことに気を使うべきだと解釈しました。

  • セマンティックなマークアップをすること
  • モバイルでも扱いやすいスタイリングをすること
  • パスワードの表示非表示やパスワード忘れ等に対応したリッチな機能を追加すること

このセッションでは、HTMLやCSS、JavaScriptのコードを例示しながら解説しているため、閲覧後に自身で実践しやすいようになっています。 また、やりがちなバッドプラクティスから納得できる改善策までをセッション内で指摘しているので、自身が関わっているプロダクトと照らし合わせると面白いかもしれないです。

昨今のプロダクトではユーザ認証を求めるものがほとんどだと思いますので、多くの開発者にとって一見の余地があるセッションだと感じました。

[How to Define your install Strategy]

https://youtu.be/6R9pupbDXYw

ネイティブアプリ・PWA・ブラウザアプリを対象とするユーザによって使い分ける戦略についてのセッションです。

発表者の方の主張によると、アプリケーション利用時にユーザに最も良い体験を提供できるのはネイティブアプリだと紹介しています。 一方で、動作端末のスペックや回線の帯域によってはPWAやブラウザアプリの方がユーザにとってふさわしい場合があるとのことです。 これを踏まえて、動作環境やアプリケーションの使われ方によって最も適したインストール方法を提案する手法について解説しています。

1つ目は、初回利用時にネイティブアプリのインストールを促し、その後Cookie等でトラッキングを行い、ブラウザで利用し続けていると判断した場合はPWAのインストールを促す手法です。ユーザに選択権を与えることで本人が最も利用を望んでいる環境を提供できますが、選択をさせる前に、その方法の価値を明確化させておく必要があります。

2つ目は、初回利用時に動作端末のスペックを確認し、ネイティブアプリの利用が難しい場合はPWAのインストールを提案する手法です。利用時の導線にユーザには選択権がないため、ユーザにとっては初回利用時のコストが低いですが、すでにネイティブアプリがインストールされていないかをOS提供のAPIを利用して確かめておく必要があります。

最近はネイティブアプリとブラウザアプリの両方を提供するサービスが多いため、それぞれのサービスがどんな手法を使っているのか着目してみると面白いかもしれないです。

koba04のオススメ

koba04です。kintone/js-sdkにあるようなツール開発など色々やってます

What’s new in speed tooling

https://youtu.be/yDHfrhCGFQw

Web Performance の計測をどのように、どういったツールを用いて行うのかを解説したセッションです。

Google が推奨している Core Web Vitals についての簡単な説明と、それを計測するためのツールや Lab(用意された環境) と Field(実ユーザー環境) の違いなどがわかりやすく紹介しています。

Web Performance に取り組みたいがどこから始めたらいいのかわからないという人にオススメのセッションです。

Writing build plugins

https://youtu.be/mr67QkDfkoQ

同じ機能を持つ webpack と Rollup のプラグインを作ってみることで、それぞれの作り方やアプローチの差異を紹介しているセッションです。

それぞれのプラグインを 15 分ずつくらいで順番にコードを紹介しながら作り上げていく構成になっているため、プラグインの作り方を知らない人でもどうやって作ればいいのかをイメージできる構成になっています。

Rollup の方が webpack に比べて簡単にプラグインが作れますが、webpack の場合は SourceMap が正しく生成できるといったメリットがあるなど、デザイン上のトレードオフについても紹介しています。

webpack や Rollup のプラグインに興味があったり作ってみたい人にはオススメのセッションです。35 分と少し長いセッションですが、2 つのバンドラーのプラグインについて実際のコードを紹介しているので、飽きずに楽しめる構成になっています。

nakajmgのオススメ

じまぐです。kintoneやGaroonのレガシーに立ち向かっています。

10 modern layouts in 1 line of CSS

https://youtu.be/qm0IfG1GyZU

あるあるなレイアウトを 1 ラインのCSS で実現する方法を紹介するセッションです。

紹介しているのは display: gridを使ったものがほとんどで、place-items: centerの利用を中心にしてよくあるレイアウトを少ないコードで実現しています。1linelayouts.glitch.meではセッション内で紹介しているコードをブラウザ上でインタラクティブに変更して確認できます。

紹介しているコードは IE11 には対応していないものがほとんとですが、使える場面では積極的に使っていきたいなと思うものが多かったです。place-items: centerがとにかく便利そうです。なんでもかんでも grid でスタイリングすればいいとは思いませんが、テクニックとして引き出しに入れておきたいなと思いました。

Storage for the web

https://youtu.be/NNuTV-gjlZQ

ブラウザが持っているストレージについてのセッションです。

各ブラウザの保存容量の制限値や、制限値を越えたときの例外処理、現在の使用量や空き容量を Storage Manager API で取得する方法などを紹介しています。各ブラウザが空き容量不足に対応するために持っている仕組みなどについても紹介しています。

Chrome がディスク容量の 80%まで使えるようになっているという話は知らなかったので驚きました。

pirosikickのオススメ

@pirosikickです。最近は設計の勉強が好きです。

Just the data you need

https://www.youtube.com/watch?v=f0YY0o2OAKA

User AgentやReferrerについてのセッションです。

UserAgentやReferrerはユーザーをトラッキングできたりセンシティブな情報を含む場合があるので、「必要なデータだけ」を扱うのがよいとのことです。 UserAgentやReferrerを扱う場合の考え方のフレームワークとして、次の3つを考慮するとよいと紹介しています。

  • 必要なデータか?
    • 不要なら扱わないようにする
  • 代替手段はないか?
    • User Agentを判定して機能を切り替える代わりに、Progressive EnhancementやResponsive Designで代替できないか検討する
    • Referrerではなく、OriginヘッダーやSec-Fetch-Siteヘッダーで代替できないか検討する
  • 扱う場合はセキュアな方法か?
    • User-Agent Client Hintsで必要な情報だけを取得する
    • Referrer-Policyを設定し、完全なReferrerを必要な範囲にだけ取得可能にする

シンプルな図やコードで解説していて、わかりやすいセッションでした。

Progressively enhancing like it’s 2003

https://www.youtube.com/watch?v=NXCT3htg9nk

Project FuguのAPIを使って作られているGreeting Cardを作るアプリを題材に、Project FuguのAPI紹介とProgressive Enhancementする方法について紹介するセッションです。

Progressive Enhancementについては、次のようにDynamic Importを使えば機能に必要なファイルだけロードできると紹介しています。

if ('新し目のAPI'inwindow) {import('./新し目のAPIを使って書かれている.js');
}else{import('./新し目のAPIがない場合の代替の処理.js');
}

題材となっているアプリはすごくシンプルなのですが、Project FuguのAPIを幅広く適切なユースケースで紹介しており、どのように活用すればよいのかがイメージしやすかったです。

sakitoのオススメ

sakitoです。

以前styled-componentsの採用と既存資産を捨てた理由でも紹介があったkintoneのフロントエンドモダン化を中心にフロントエンドの支援に取り組んでいます。 最近はパフォーマンスへの取り組みも考えていきたいと思っています。

Optimize for Core Web Vitals

https://youtu.be/AQqFZ5t8uNc

Googleが5月頃新たに提唱したパフォーマンス指標の Core Web Vitalsについてのセッションです。

Core Web Vitalsには次の3つの指標があります。

セッションでは実際のファッションブランドサイトchloeを例に、この 3 つの指標に対して問題がある点を取りあげ、どうやって改善するか実装と合わせて解説しています。

Core Web Vitals の概要から具体的な対応策まで網羅的に説明しているので、これから Core Web Vitals をもとにパフォーマンス改善を行う方の入門としてぴったりなセッションだと思います。

Core Web Vitals in the DevTools timeline

https://youtu.be/t8YBZLjL-KU

このセッションでは Chrome チームのメンバーが Chrome DevTools 内の Core Web Vitals に関する情報を中心にトークを繰り広げていきます。

1つ目に紹介した Optimize for Core Web Vitals と異なり、Core Web Vitals の3つの指標であるLCP, FID, CLS について DevTools の Performance タブを使いながら、具体的に解説しています。

devtoolのperformanceタブの内容
devtoolのperformanceタブの内容

例えば次の画像にあるように Performance タブを使用して、LCP のコンテンツの特定を行う方法を紹介しています。

performanceタブでLCPコンテンツを特定した様子
performanceタブでLCPコンテンツを特定

Optimize for Core Web Vitalsのセッションを見たあとに、Core Web Vitals についてより深く知りたくなった方にオススメです。 Performance タブの使い方を動画で体系的に学べるのも個人的にはとてもよかったです。

shisamaのオススメ

shisamaです。

主に kintone のカスタマイズやプラグイン開発のための JavaScript の支援ツールを開発しています。
Web と JavaScript が好きです。

Prevent Info leaks and enable powerful features: COOP and COEP

https://youtu.be/XLNJYhjA-0c

Cross-Origin-Opener-Policy (COOP) と Cross-Origin-Embedder-Policy (COEP) という新しい HTTP ヘッダーについて紹介するセッションです。

Spectreの影響により SharedArrayBufferなどは一時的に利用が制限されていましたが、COOP と COEP の登場によりそれらの API は再度利用可能になりました。
COOP と COEP がどのように Spectre の脅威を防ぐのか、その仕組みについて説明しています。

Web のセキュリティモデルや Spectre の脅威に関しても簡単におさらいできるようになっています。
今回紹介している COEP を適用するためには CDN など配信側でも対応する必要がありますが、強力なセキュリティ対策なので対応を進めていってほしいです。
また、CSP のように Report-only モードがあるので Web サービスを開発している側も、レポートから徐々に適用できるので広く使われるようになってほしいです。

Find and fix problems with the Chrome DevTools Issues tab

https://youtu.be/1TbkSxQb4bI

Chrome 84 から DevTools に追加された Issues タブについて紹介するセッションです。

Issues タブはページ上で問題が発生したリソースとその問題の修正方法を次のように表示します。

Image from Gyazo

Issues タブでは現状次の 3 種類の問題を検知できます。

  • Cookie に関する問題
  • mixed content(混合コンテンツ)に関する問題
  • Cross-Origin-Embedder-Policy に関する問題

DEMO ページが用意されているので、Chrome 84 (or higher) から DevTools を開いて、More Tools > Issues から Issues タブを開いて確認してみてください。

前述の COEP で問題のあるリソースを確認できるだけでなく、Cookie の SameSite に関する問題を確認できるのは嬉しいところです。
今後 Issues タブで検知できる問題が増えると、より一層強力なツールとして広く使われると感じました。

toshi-tomaオススメ

toshi-tomaです。業務では、古くなったライブラリのアップデートやモダンなスタックへの置き換えプロジェクトなどに注力しています。

Advanced PWA patterns

https://youtu.be/fhqCwDP69PI

企業の事例にそって、「高速で快適なWebの体験」を提供するパターンを4つ紹介するセッションです。 また、Workboxというライブラリを利用したコード例も紹介しています。

紹介しているのは、次の4つのパターンです。

  • Resilient search experiences (パターン1)
    • ユーザーが検索時にオフラインだった場合、復帰時にPush通知を送信して、検索結果のページへ遷移することを可能にします
    • Background Sync APIを活用した例です
  • Adaptive loading with service workers (パターン2)
    • 2Gや3Gなど低速なネットワークを利用するユーザーには、サイズが小さい画像やAMPバージョンのページを提供します
    • Network Information APIを活用した例です
  • Instant navigation experiences (パターン3)
    • メディアサイトにおいて、人気の記事や次のページをPrefetchしておくことで、高速なナビゲーションを提供します
    • Resource Hintsを活用した例です
  • App-shell UX with Service Worker (パターン4)
    • Multi Page Applicationで、SPAのような体験を提供します
    • パーシャルやService Worker、Streams APIを活用した例です

このセッションから複数の事例を知れたことで、ユーザーに快適な体験を提供するための工夫を日々考えていかなければと改めて思いました。また、パターン4(App-shell UX with Service Worker)のように既存のアーキテクチャを大きく変えずに、より良い体験を提供する工夫が開発者には求められていると感じました。

Shipping a PWA as an Android app

https://youtu.be/QJlbMfW3jPc

PWAをAndroidアプリとして作成する Trusted Web Activity (TWA) の紹介と、TWAを構築する際に便利な CLIツール Bubblewrapを紹介するセッションです。

WebアプリをAndroidなどのネイティブアプリとして提供する場合、WebViewを用いるのが主な選択肢となります。しかし、Webの新しいAPIが利用できないなど、WebViewは様々な問題を抱えています。 WebViewの問題を解決するアプローチとして、PWAをAndroidのネイティブアプリとして提供するTWAが昨年のGoogle I/Oで発表されました。

TWA構築には Java/Kotlin や Android Studio、Gradle などネイティブアプリの開発知識が必要になりますが、NodeJSプロジェクトの Bubblewrapを使うことでTWA構築を簡単にすることができます。セッションでは用意したPWAをTWAにする流れを紹介しています。

TWAはWebとネイティブをうまく統合する非常に魅力的な技術だと思い注目しています。 Webアプリのエラーの一部をネイティブのクラッシュに委譲するという発表がありましたが、今後ますますTWAにより、Webとネイティブアプリで実現できることが増えるのではないかとワクワクしています。

@zaki___yama が選ぶおすすめ 2 本

@zaki___yamaです。kintoneのDX向上というテーマでSDK開発などをやっています。

Building better in the world of build tools!

https://youtu.be/vsMJiNtQWvw

webpack, Parcel, Rollup などのビルドツールの特徴や長所・短所を紹介するセッションです。
日本語字幕もあります。

ParcelはWorker間で共通するコードもうまくchunkとして扱ってくれるとのことですが、そのきっかけとなったエピソード(セッション7:00あたり)が面白かったです。 また最後に紹介している Tooling.Reportというサイトは、各ビルドツールでできることできないことを確認する際に参考になりそうです。

Zoom on Web: getting connected with advanced web technology

https://youtu.be/nhTxJBgTywc

ビデオ会議サービス Zoom の web クライアントの体験を良くするための技術として、現在ブラウザに実装中もしくは仕様策定中の先進的な API を紹介するセッションです。 紹介しているのは次の3つです。

  • WebAssembly SIMD
  • WebTransport
  • WebCodecs

セッションの冒頭でWebRTCについても触れていましたが、WebRTCは高機能な分Zoomのように独自のプロトコルやアーキテクチャを使用しているところに導入するのは難しく、より低レベルなAPIを探していたそうです。
動画まわりの技術には明るくないですが、なるほどこんなAPIが今後実装されるのかという気持ちで観れました。

さいごに

フロントエンドエキスパートチームでは、今後もチームの活動として、フロントエンドに関するトピックを社内外へ積極的に共有していきます。

チームの活動内容をまとめた資料も作成しましたので、ぜひご覧ください! https://speakerdeck.com/cybozuinsideout/frontendexpert-team

社外への情報共有の場としてCybozu Frontend Monthlyを開催しています。フロントエンドエキスパートチームのメンバーが、その月に気になったトピックをわいわいしながら紹介する形式のイベントです。

毎月最後の火曜日に開催を予定していますので、お時間が合えばぜひ参加してみてください。

Cybozu Frontend Monthly #2 - 2020/08/25Cybozu Frontend Monthly #1 - 2020/07/28

イベントの様子は#サイボウズフロントエンドマンスリーにて実況していますので、こちらもチェックしてみてください。

サイボウズサマーインターン2020 報告 〜 OSSへの貢献を通して学ぶKubernetes基盤開発

$
0
0

こんにちは。Necoチームの池添とsatです。

サイボウズは毎年サマーインターンシップを開催しています。

今年は新型コロナウイルス(COVID-19)の影響で一時開催が危ぶまれました。 しかし、この状況で学生の皆さんの就業体験の機会が失われてしまうのもよくないと考え、フルリモートでインターンシップを実施することとしました。

blog.cybozu.io

Kubernetes基盤開発コースは8月と9月の2回開催で、1回目の8月3日~14日(9日間)の日程には4人の学生が参加してくれました。

Kubernetes基盤開発コース担当のNecoチームはサイボウズの中でも、もっともOSSを活用しているチームです。 そこでこのコースでは、Necoチームが取り組んでいるKubernetes基盤開発について、OSSへの貢献を通して知ってもらう機会にしました。単に開発をするだけではなく、サイボウズがどういう意図で自社プロダクトをOSSとして公開しているか、また、既存のOSSの開発に参加しているのかについても学んでもらいました。

具体的には、2人ずつのチームに分かれて下記のような課題に取り組んでもらうこととしました。

1つめのチームはKubernetesプログラミングの手法を学び、新しいOSSプロダクトを開発するという課題です。

2つめのチームはOSSの作法を学び、メンテナと英語でやり取りしながらRook(後述)にコントリビュートするという課題です。

リモートインターンシップのための取り組み

今回、Necoチームではフルリモートでのインターンシップ受け入れに挑戦しました。 フルリモートであることがデメリットとならないように様々な取り組みを実施しましたので、いくつか紹介したいと思います。

開発環境

個人の環境の違いによる問題がでないように、開発環境としてはGCP上のインスタンスを利用してもらいました。

朝会/夕会、振り返り

朝会や振り返りの時間には、実際にNecoチームが普段実施しているミーティングに参加してもらいました。 実際のミーティングに参加することで、Necoチームが普段どのような仕事をしているのか、どのように改善を進めているのかを知ってもらうことができたと思います。

勉強会

課題に取り組む前に必要な知識を学んでもらうため、いくつかの勉強会を実施し、GoのContextの使い方のような基礎的な内容から、RookやTopoLVMの概要、OSS開発の作法などを学んでもらいました。

Kubernetesのカスタムコントローラ開発に関しては下記のような資料を用意して、手を動かしながら理解してもらいました。

zoetrope.github.io

これらを一通り終えた後に、どちらの課題に取り組むかをインターン生たちに選択してもらいました。

リモートモブプログラミング

課題はリモートモブプログラミング形式で取り組んでもらいました。

これはチームごとにメンターとなる社員2~3名と学生2名が常時Zoomで接続し、誰か1人の画面を共有しながらプログラミングを進めていくというものです。

モブプログラミングでは、常時ナビゲータからの指示が入るため分からないところでハマることがほとんどありません。 また、コードは書いたそばからレビューされることになり、エディタの使い方や様々なテクニックを共有することもでき、非常に密度の濃い時間を経験できたのではないかと思います。

成果発表会

成果発表会も、もちろんリモートで開催しました。Zoom で手軽に参加できるということもあり、40名を超える社員が発表を聴きに来てくれました。

リモートでのプレゼン発表は聴講者からの反応が少なくてやりにくいことも多いと思うのですが、今回の成果発表会ではたくさんの社員が適時チャットでコメントしてくれたので、非常に盛り上がった成果発表会となりました。

TopoLVMチームの課題

ここでは、TopoLVMチームが取り組んだ課題について具体的に紹介したいと思います。

TopoLVMはサイボウズが開発するCSI (Container Storage Interface) プラグインで、Kubernetesでローカルストレージを利用する際に便利なOSSとなっています。 最近では海外の利用ユーザーも増えてきています。

blog.cybozu.io

TopoLVMチームでは、TopoLVMに関する3つの課題に取り組んでもらいました。

Kubectlプラグインの開発

github.com

手始めにKubernetesプログラミングに慣れてもらうために、kubectlプラグインの開発に取り組んでもらいました。

この課題ではkubectlプラグインの作り方、KubernetesのAPIサーバーとやり取りする方法、envtestを利用したテストの書き方などを学んでもらいました。 特にプラグインの実装よりも自動テストに時間をかけていることを体験してもらい、我々がいかにテストを重視しているのかを知ってもらえたのではないかと思います。

TopoLVMの不具合修正

github.com

TopoLVMはサイボウズだけが利用しているものではなく、何人もの社外のユーザーが利用してくれています。

インターンシップ期間中に社外のユーザーから不具合が報告されたので、課題として不具合を修正し、新しいバージョンをリリースするところまで体験してもらいました。

pvc-autoresizerの開発

github.com

メイン課題としてpvc-autoresizerの実装に取り組んでもらいました。 pvc-autoresizerは、Podが利用している永続化ボリューム(PV: PersistenVolume)の空き容量が少なくなってきたら、自動的にサイズを拡張するというソフトウェアです。

この課題では英語でデザインドキュメントを書くところから開始し、基本的な機能の実装とテスト、チームメンバーによるレビューと、我々が普段の開発で実施している内容とほとんど同じ体験をしてもらいました。

なお、pvc-autoresizerはインターンシップの課題として開発したものですが、TopoLVMだけでなく様々なCSIプラグインで利用できる汎用的なソフトウェアとなっています。 今後、ドキュメントやサンプル等を整備して、広く利用されるOSSに育てていきたいと考えています。

Rookチームの課題

ここでは、Rookチームが取り組んだ課題について紹介します。

まずはこの課題に取り組む背景としてRook、およびRookとNecoの関係について説明をしておきます。RookはKubernetes上で動作する各種ストレージソフトウェアのオーケストレーションです。Necoではストレージ基盤としてCephを使うことを決めており、かつ、その管理にRookを使うことになったので開発に取り組んでいるというわけです。詳細については以下の記事をごらんください。

blog.cybozu.io

これらを踏まえた上で、Rookチームでは実際にupstreamのRookの開発に参加してもらいました。

Rookは大きなプロジェクトなので、実際にPRを投げる前に覚えるべきことがたくさんあります。このため、まずは全体構造をつかむためにRookにおけるKubernetesとCephの機能の対応、ローカル環境でのビルド/テスト方法、PR投稿時のルールなどを学びました。

upstream Rookへの修正の取り込み

前節において述べた準備が終わった後は、次のように簡単なPR投稿からはじめてインクリメンタルに難易度を上げていくという進め方をしました。

  1. ドキュメント修正
  2. コード修正: 実装方針が既に決まっているもの
  3. コード修正: 実装方針を自分で考えるもの

おふたりとも試行錯誤しながら順調に進み、最終的にはわずか二週間弱の間に5個のPRをマージというすばらしい成果をあげました。

github.com

github.com

github.com

github.com

github.com

マージされなかったPR

投稿したもののインターン期間中にマージされなかった修正も3つありました。これらはインターン終了後にNecoのメンバに引継いで継続中です。

1つ目は、同時期に出されたPRにおいて同様の修正がされていたので不要になったというものです。これは開発が活発なOSSにおいてよくあることです。

github.com

残る2つは、PRそのものがどうというより、Rookに現在発生しているCIの不具合によってマージができなかったというものです。

github.com

github.com

これらについてはCIの問題が解決すればマージされる見込みです。

インターン生たちが得たもの

上述したPRの投稿、マージといったわかりやすい成果以外にも、おふたりは様々なことを学びました。

まずはアクティブなOSSの開発におけるスピード感です。とくにPR5969については、日本時間の昼間に投稿したらアメリカ中部に住んでいるはずのメンテナに7分後にマージされました。さすがに彼らもこれには「マージが滅茶苦茶速い!あとこの人はいつ寝てるんだ!?」と、驚いたようです。

続いてOSSへの貢献に抵抗がなくなりました。OSSへの最初の貢献には、コードを書く以外にも「自分がやっていいのだろうか」「反対されたらどうしよう」などの心理的な障壁があるためにためらいがちなのですが、おふたりは見事その障壁を乗り越えました。メンテナからの質問にも臆することなく答えられるようになりました。今後本人たちが望めば、さまざまなOSSの開発に問題なく参加できることでしょう。

まとめ

今回のインターンシップはフルリモートでの開催ということで運営が難しかった面もありますが、非常に充実した内容で実施することができました。

この経験が学生の皆さんにとってKubernetesやインフラにより強く興味を持つきっかけとなり、彼らの進路を決定する助けになったり、Kubernetes界隈を盛り上げる一助となれば幸いです。

生産性向上チームの紹介

$
0
0

こんにちは、生産性向上チームです。今回は、私たち「生産性向上チーム」について紹介いたします。

生産性向上チームとは

サイボウズの技術領域と生産性向上チームの立ち位置
サイボウズの技術領域と生産性向上チームの立ち位置

生産性向上チームは主に、次のような業務を行っています。

  • チームを横断した開発効率を高める基盤の整備
  • 開発チームの業務の自動化や効率化の支援
  • 最新の生産性向上に関わる技術のキャッチアップ、探求

などなど、一言でいうと「サイボウズのエンジニアがつらいと思っている部分を最高にしていく」ために活動しているチームです!

メンバー

生産性向上チームメンバーで記念撮影
生産性向上チームメンバーで記念撮影

生産性向上チームのメンバー数は現在5名です。そのうち3名は他のチームと兼務しており、そのつながりを活かして業務に取り組んでいます。

各メンバーについて、簡単に紹介いたします。

宮田

  • Twitter: @miyajan
  • 出身:東京
  • 得意なこと、好きなこと
    • CI/CD とか自動化系全般が好きです
  • 入社:2009/04
  • ひとこと
    • 最近は GitHub Actions にハマってます
  • 拠点:東京
  • 登壇資料一覧

五十嵐

  • Twitter: @ganta0087
  • 出身: 神奈川県
  • 得意なこと、好きなこと
    • ソフトウェア設計、リファクタリングが得意です
    • CIや開発環境をスマートに整えるのが好きです
    • ドットファイルをいじり始めると止まりません
    • 何よりコードを書くのが好きです
  • 入社した年月: 2019/02
  • ひとこと
    • オカメインコはかわいい🐤🐤
  • 拠点: 東京(WFHメイン)
  • 登壇資料一覧

小山

  • Twitter: @akihisa1210
  • 出身: 島根県
  • 得意なこと、好きなこと:
    • ソフトウェアの品質にかかわる活動
    • 自動化(特に、環境構築の自動化)
  • 入社した年月: 2016/4
  • ひとこと
    • Go 言語とラテン語を勉強中です📕
  • 拠点: 東京(WFHメイン)
  • 登壇資料一覧

川畑

  • Twitter: @n1wat0n
  • 出身: 千葉県
  • 得意なこと、好きなこと:
    • 業務を自動化するのが好きです
    • 最近はDatadogのダッシュボードのカスタマイズが楽しいです
  • 入社した年月: 2017/4
  • ひとこと
    • 身体の19%軽量化に成功
  • 拠点: 東京(WFHメイン)
  • 登壇資料一覧

平木場

  • Twitter: @shitimi_613
  • 出身: 鹿児島県姶良市
  • 得意なこと、好きなこと
    • CI/CDパイプライン、Dockerfileをいじったり
    • 最近はAWSでシステムを構築してるのが楽しい
    • とにかくみんなの生産性をあげたい
  • 入社した年月: 2020/04
  • ひとこと
    • スーツ👔と辛麺🍜とドライブ🚗が好きです
  • 拠点: 東京
  • 登壇資料一覧

生産性向上チーム誕生の経緯

2015年から2020年で人数が5倍の5人になった様子
5年で5倍になりました

生産性向上チームは2015年に誕生しました。

サイボウズではそれまで、各プロダクトチームで改善活動(デプロイパイプラインの構築や自動テスト)を行っていました。改善の仕組みはうまく回っていたのですが、開発チームで活動が閉じてしまっており、チーム横断の開発基盤を整える必要性を感じていました。

例えば、当時は複数の開発チームが一つのJenkinsを共同で利用しており、各チームが自由にそのJenkinsを変更していました。このため、あるチームが新しいプラグインをインストールすると、他のチームのCI実行が失敗するようになってしまうという問題が起きていました。また、厳密に管理している人がいなかったためバックアップも存在しておらず、Jenkinsが稼働しているマシンに何か問題が生じると、二度と復旧できなくなるリスクを抱えていました。

また、あるチームで培った改善のノウハウが他のチームに共有される機会がなかったため、自動テストをCI上でどんどん実行できているチームがある一方、他のチームではテストの自動化がまったく進まないという状況が発生していました。

開発基盤や改善の取り組みはプロダクトの進化につながり、お客様に価値を提供していくためには重要です。しかし、そこに時間を割けている人はいませんでした。そういった問題意識から、@miyajanが新たに生産性向上チームを立ち上げるに至りました。

立ち上げから4年間は1人チームとして各プロダクトが抱える問題をヒアリングし、解決してきました。そんな中、@ganta0087の入社に続き、Garoonをもっとよくしていきたいという思いを持ったGaroon開発チームの @akihisa1210@n1wat0nが兼務という形で参加したことにより、より多くの問題に対して取り組めるようになりました。さらに、今年の6月には新卒の@shitimi_613が専任メンバーとして加わり、5名体制となりました。

生産性向上チームの活動内容

ここからは、生産性向上チームが普段どのような活動をしているのかを紹介します。

日々の業務サイクル

探求タイム [毎日]

午前中に、個人で自由に探求する時間を設けています。特にテーマに縛りを設けず、知見を広げることによって、よりよい方法で問題解決ができるようにすることがねらいです。各メンバーが取り組んでいる内容はkintoneのスレッド上で共有するようにしています。

デイリーミーティング [毎日]

昼前に、前日に行ったタスク、今日行うタスクを簡単に共有し、各メンバーのスケジュールを確認します。兼務メンバーが多く予定が不定なため、モブプロの時間はこのデイリーミーティングで予定を入れ、作業可能な時間を把握するようにしています。

モブプログラミング [毎日]

モブプログラミングとは、複数人で同じ画面を見てプログラミングを行うことです。ドライバーと呼ばれる人が操作をし、ナビゲーターと呼ばれる人がドライバーに指示を与えます。

生産性向上チームでは、タスクをモブプログラミングで進めます。作業が完了するとその場でPull Requestを出し、CIによるテスト通過後に、生産性向上チームで判断できる場合はマージまで行います。
また、チームメンバーの働く場所がそれぞれ異なるため、コミュニケーションロスが無いように、各自の席でウェブ会議ツール(Zoom)の画面共有機能を利用してモブプログラミングしています。25分作業→5分休憩→ドライバー交代を繰り返します。

各メンバーがオンラインでモブプロをしています
各メンバーがオンラインでモブプロをしています

Productivity Weekly [毎週]

毎週水曜日に生産性向上に関する記事や、GitHub、AWS、CircleCIなどの更新情報などを見ていく会です。参加メンバーは自由なので、さまざまなチームの人たちでワイワイやっています。事前にみんなが気になった情報をkintone上に共有し、生産性向上チームが持ち回りで進行を行います。ここで得た知見はTwitterの#cybozu_productivity_weeklyハッシュタグで発信しています。

ふりかえり&プランニング [毎週]

毎週金曜日に、その週のふりかえりと翌週のプランニングを実施しています。対応したタスクを振り返ったり、KPTで気づきを共有したり、チームの今後について話し合ったりしています。

もくもく会 [毎月]

毎月最終月曜日に、他チームともくもく会の時間を設けています。もくもく会とは、普段の業務から一歩離れて技術を探求したり、業務の延長で自由に改善活動を行ったりする会です。最後は全員で何をやったか共有します。

もくもく開発が終わったら乾杯をして成果報告会!(COVID-19前の様子です)
もくもく開発が終わったら乾杯をして成果報告会!(写真はCOVID-19前のものです)

その他のタスク

開発チームの支援活動

開発チームから相談を受け、知見を共有したり、実装を手伝ったりします。

具体的な支援内容に興味のある方はこちらをご覧ください。 youtu.be

開発基盤の構築・運用

生産性向上チームでは、さまざまな開発基盤の構築・運用を行っています。主に次のようなものがあります。

CircleCI Serverの管理

サイボウズではクラウド版とオンプレ版のCircleCIを利用しています。 オンプレ版であるCircleCI Serverのメンテナンスを生産性向上チームで行っています。

AWSアカウントの管理

社員が利用するAWSアカウントの管理を行っています。また、マルチアカウント構成によるシングルサインオンとチームに委譲できる権限管理のしくみを開発・運用しています。詳しくは以下の記事をご覧ください。 blog.cybozu.io

リリース管理システムの開発・運用

各プロダクトのリリースを管理するシステムの開発・運用を行っています。

勉強会への参加・登壇

勉強会への参加・登壇も業務として扱います。登壇資料の作成なども業務中に行い、チームでレビューします。

We're hiring!

生産性を上げることが好きな方をお待ちしております!Zoomでのコミュニケーションがメインなので、働く場所は関係ありません!どこでも歓迎です!

詳しい採用情報はこちら!!

2020年のエンジニア新人研修の講義資料を公開しました

$
0
0

こんにちは。コネクト支援チームの@tignyaxです。 みなさま、夏はどう過ごされたでしょうか? 私は、夏が好きなのに今年は夏らしいことが出来なくて寂しいなぁとなっています。。。

さて、今年2020年もエンジニア新人研修を行いましたので、その紹介と講義資料を公開いたします。

2020年のエンジニア新人研修について

基本的には2019年と同じ形*1での実施となりました。 最初の1週間で必修講義をしたあと、新人の皆さんには2週間ずつ3チームを体験してもらいました。 チーム体験のコンセプトは、新人に「興味のあるチームで実際に業務を体験し、配属希望を決める参考になった。」と言ってもらうことです。 各チーム体験では座学や研修を中心にするのではなく、業務体験が中心です。 チーム体験を通して、配属先を検討する材料にしたり、いろんなチーム/人/業務を知ってもらえる機会となります。

  • 必修講義
    • 誰に: 開発/運用本部に配属される新入社員​
    • 何を: どのチームに行っても必要となる基礎的な知識/技術/ツールを学び、体験できた
  • 選択講義
    • 誰に: 学びたい人が​(=新入社員に限らず)
    • 何を: 興味があることを学べた
  • チーム体験(2週間 * 3チーム)
    • 誰に: 開発/運用本部に配属される新入社員
    • 何を: 興味のあるチームで実際に業務を体験し、配属希望を決める参考になった

去年との大きな違いとしては、受講者、講師含めてフルリモートの開催となったことです。 COVID-19の影響で基本的に全社員リモートワークとなっています。 新人の皆さんは入社時からフルリモートのため、エンジニア新人研修がフルリモートで可能なのか、チーム体験は受け入れ可能なのかなど不安点も多かったようです。 例年であれば先輩社員と、オフラインでの懇親会が開かれたり、オフィスでふらっと会話をすることができたのですが、今年はコロナ禍でできない状態でした。 そのため、オンラインであっても雑談の時間などを明確に設けたりして不安を払拭できるようにしました。 研修を受けるに当たって、リモートワークの環境を良くするためにモニタなどの支給も早めに行いました。

スケジュール

  • 4/27〜5/8(1週目)
    • 必修の講義や演習が中心
    • チーム体験先の紹介など
  • 5/11〜6/19(2-7週目)
    • チーム体験と選択講義
  • 6/22〜6/26(8週目)
    • RFC を読んで HTTP サーバを作る研修

講義一覧

必修講義

Webアプリケーション基礎

HTTP/DNS

ソフトウェアライセンス

スクラムトレーニング

品質保証活動に関するテスト全般からテスト自動化まで

HTTPサーバー開発

選択講義

デザインの役割と関わりかた

セキュリティ

アクセシビリティ

Chrome Developer Toolsの使い方

正規表現

Docker

CI/CD

モブに早く慣れたい人のためのガイド

Kubernetes を使った開発入門

Introduction to Kubernetes

(日本語話者向け)ベトナム語超入門

最後に

2020年の研修資料も、各社のエンジニア新人研修や駆け出しエンジニアの皆様のお役に立てれば幸いです。 「弊社の研修はこんな感じですよ」というのが伝わって興味を持っていただければ嬉しいです。

2020年9月14日にはサイボウズの新人研修について、YouTube LiveによるオンラインMeetupも行います。 サイボウズのエンジニア新人研修の歴史、今年の研修内容の取り組み、フルリモートならではの苦労や工夫などを、運営と受講者のディスカッション形式で共有する予定です。 みなさまのご参加お待ちしております!

Cybozu Tech Meetup #6 サイボウズのエンジニア新人研修 2020

今回紹介した研修資料以外にも、各種勉強会やカンファレンスでサイボウズのエンジニアが発表した資料もエンジニアサイト(Cybozu Tech)で公開しています。 よろしければこちらもご覧ください!

Slides | Cybozu Tech

新卒採用およびキャリア採用もやっています!

サイボウズ | 採用情報(新卒・キャリア)

新人研修やMeetup、採用などで聞きたいことあればお気軽にDMください。 それではまた!

*1:2019年の新人研修についての記事はこちら https://blog.cybozu.io/entry/2019/09/05/080000

ドキュメントの文章校正には、textlintが便利

$
0
0

こんにちは! 開発部 テクニカルコミュニケーションチーム(以下、TCチーム)の原嶋です。

さてさて。
みなさん、ドキュメントの文章校正(以下、校正)ってどんな風にやっていますか?

目視チェックでバッチリだぜ!という方も もちろんいると思うのですが、チェックポイントが多いと指摘が漏れてしまいがちですよね。そして、会社の公式文書となれば、チェックポイントはあれもこれもと山のようになります。

TCチームでも長年 校正に頭をかかえていましたが、textlint と+αのツール を使って、校正の悩みを解決しました。
今回はその経緯をお話させてください。

校正って確認する項目がたくさん

TCチームでは、サイボウズ製品のユーザーサポートコンテンツ(ヘルプやリリースノートなど)を作成しているのですが、
それらのコンテンツは会社の公式文書なので、毎回の校正でチェックする項目が多々あるんです。
校正おわったーと思ったら「わ!ここにもミスが😫」と、1回で校正が完了する方が珍しい状態でした。

チェック項目の例:

  • 製品名/サービス名/機能名が正しいか
  • てにをは
  • 常用漢字
  • カテゴリ vs カテゴリー
  • 誤字脱字の有無
  • ですます調 vs である調
  • 同じ接続詞や同じ助詞を連続使用していないか
  • 二重否定の有無
  • 半角カナの有無
  • 読点の有無 etc...

もちろん、これにプラスして、
「文章の分かりやすさや簡潔さ」「機能説明の正確性」などを考慮したチェックもあります。

うん、つらい。。
注意散漫な私は一度に全部ムリー!目がつぶれるーという感じでした👀

校正支援ツールを探す

何度も校正を繰り返すのはライターもレビュワーも疲弊してしまいます。 また、目視に頼るチェックは効率的ではないので、ツールに頼りたいなとチームで模索していました。

色々な校正支援ツールを試しては断念というのを繰り返して、TCチームに合う校正支援ツールのポイントが見えてきました。

TCチームに合う校正支援ツール

  • 自動校正ができる

    Just Right!といった校正支援ツールも使っていたのですが、毎回コピペで校正にかけるのが手間で断念。
    ドキュメントの分量が多いとコピペも大変なんです。
    同様の理由で、ファイル読み込みなどの操作があるツールも断念しました。

  • セキュアに利用できる

    リリース前の機能に関する記事を書くので、校正時に記事内容が流出する事態は避けなければいけません。
    校正ツールに読み込んだデータが別用途に利用されることはないか、利用規約などはつぶさに確認していました。

  • シンプルな校正機能でよい

    文章の分かりやすさや簡潔さ、機能説明の正確性などは、人間によるチェックが的確です。 また、ヘルプ記事ではウィットに富んだ表現はないので、ツールでは「日本語の正確さ」や「表現の統一」に関する校正を頼ることにしました。

  • サイボウズ独自のチェック項目を校正ルールに追加できる

    • 基本は常用漢字を使用するが、ひらがなにする方が読み手に不利益となりそうな漢字は利用できるようにしたい。
      例:「紐」「幌」「〇」「稟」「罫」

    • 康煕部首と漢文記号の利用を禁止したい
      PDFから文章をコピペした際に流入しがちでした。
      No More 文字化け!

    • よくある表記揺れを防ぎたい
      初めて / 始めて
      3か月 / 3ヵ月 / 3ヶ月
      etc..

  • マークダウンファイルに対応している
    ヘルプサイトの運用基盤をGithub・Hugo・Netlifyの組み合わせに変更したことに伴い、記事がマークダウンファイルに変わったためです。
    その時の経緯は↓を参照してください。
    blog.cybozu.io

「Circle CI」と「textlint」の組み合わせが最強だった

ヘルプサイトの運用基盤を変えたことで、更新記事をGitHubにコミットしてからサイトデプロイまでが自動化されました。
この自動化の流れに校正支援ツールも組み込みたい!
色々と調べて、上記をカバーできるツール組み合わせが「Circle CI *1」と「textlint」であることにたどり着きました💡

textlintとは

textlintは、自然言語を対象にファイルをチェックし、ルールに沿っていない書き方を指摘して、修正を促すツールです。マークダウンファイルやテキストファイルに対応しています。

textlintには、文章タイプにあわせたプリセット(=ルール集)と単体のルールが用意されています。
TCチームでは下記を採用しました。

TCチームが採用したルール

Circle CIにtextlintを組み込むとどうなる?

ヘルプ記事(=マークダウンファイル)をGitHubにコミットするたびに、Circle CIが走り、textlintによるチェックが実行されます。
下記図の(2)・(3)・(4)がこの一連の動きを指しています。

ヘルプコンテンツの運用基盤と運用サイクルを説明した図
GitHubに記事をコミットしてからサイトデプロイまでが自動化されていることを示す図

(4)の校正結果は次のように表示されます。

textlintの校正結果
textlintの校正結果の表示例

人手のレビューを通さず、サイトデプロイの流れの一環として校正が実施されるんです!
しかも、記事を更新しても、GitHubへのコミットごとにCircle CIが走るので、その時自分が書いた記事に対する校正結果が瞬時に返ってきます。レビュー結果を待たなくていいんです。
なんて良い子なんでしょう!!!! 良い子すぎて、TCチームでは愛称として「コロスケ」と呼んでます。
そう、コロッケ大好きなコロスケです。Circle CI → bot → 🤖 → コロスケ という発想での愛称です。

コロスケの効果がこんなところにも

コロスケの活躍は、校正負担を軽減しただけではありませんでした。
ライターとレビュワーの心理負担も軽減してくれています。

たとえば、こんなやりとり身に覚えありませんか?

ライター : 記事できました!レビューお願いします。  
レビュワー: はーい。このあたりの説明を手厚くした方がいいかも。
      そして、「てにをは」が変な個所あったからマークしてるよ  
ライター  : 書き直しました。どうでしょう?  
レビュワー: 説明は良くなってるね。あ、でもここ、常用漢字じゃないね。 製品名もカタカナになってるよ。  
ライター : わーごめんなさい!再度修正しました!  
レビュワー: あともう一息。ここ修正もれ。。  
ライター :  。。。。


最初のうちは気軽に間違いを指摘しあえていても、回数が重なるとすさんできますよね💦
この文章上で最初に潰しておいて欲しいミスをコロスケが全部指摘してくれます。それも指摘がさりげないんです。
コロスケが教えてくれた!修正しなきゃという感じで、ライターは校正時のストレスを感じずに自分の記事を修正できます。
おかげで、ライターもレビュワーも記事内容をよりよくするためのレビューや改善にフォーカスできる状態になりました。

おわりに

今回は、ヘルプサイトの校正ツールについてお話しました。
今のコロスケも良い子なのですが、使ってみると、こういう機能があったらいいな、こういう部分も校正してくれると嬉しいなというのが出てきます。
そういった場合に、機能の拡張がしやすいのもtextlintのいいところ。コロスケのパワーアップが一段落した際には、その成長過程についてもお話させてください。

*1:Circle CIは、ソフトウェアのビルド、テスト、デプロイを自動化するツールです。
GitHubと連携すると、コミット時に指定されたツールをCircle CIを使って自動的に実行できるようになります。

#osc20hi でたちまちサイボウズ広島のはなしをしました

$
0
0

開発本部 コネクト支援チーム の西原(@tomio2480)です.
 
9/17(土) に開催されたオープンソースカンファレンス2020 Online/Hiroshimaで「たちまちサイボウズ広島!(訳:とりあえずサイボウズ広島事情話します)」という題でセミナーを行いました.今回の登壇者はいずれも広島にゆかりのある開発本部所属の社員で,組織運営チームの水戸(広島所属),フロントエンドエキスパートチームのsakito(東京所属),kintone開発チームのにしみね(広島所属),コネクト支援チームの西原(東京所属)の 4 名でお送りいたしました.主だった話題の 3 点に沿って報告をいたします.
 
※ 参考 : オープンソースカンファレンスについて → https://www.ospn.jp/
 

当日は登壇者の都合が合わなかったので,録画配信対応となりました.当日流した動画は OSPN から YouTube に投稿されていますのでこちらをご覧ください.

www.youtube.com

拠点が分散した中でのリモートワーク

新しい生活様式を求められてそろそろ半年が経過します.サイボウズ開発本部では何か影響はあったのでしょうか,という観点で,今のサイボウズの開発本部ではどのように業務を進めているのかを中心にお話ししました.ざっとまとめると以下のとおりです.

  • 地域拠点ごとに担当製品や部署を分けていないため,そもそも支障がなかった.
  • 会議室問題,移動時間問題,どこにいて何で連絡すればいいかわからない問題が解消.
    • 集まれない理由が一つ減った.(「会議室があいてない」との決別)
    • 多数の現地と少数のオンラインのようなコミュニケーション差がなくなった.
    • Zoom で呼び出せばほぼ 100 % 応じてもらえる.
  • コミュニケーションの質を補うためのオフライン集合ができないのはつらい.
    • 各拠点や働いている現場を見て何かを感じる機会が失われる.

プラスに働いたという意見が多数でした.今までも"可能"だったオンライン業務に挑戦してきた流れがあり,その土台の上に立てていることが大きいようです.その上で"可能"から"標準"になることでより洗練され,新しい業務の形が馴染みつつある,といった現状が垣間見えました.
 
一方でより深くわかりあうためには,オフラインでの顔合わせが効果的だと感じていることもわかりました.特にフロントエンドエキスパートチームは,別拠点の観光とリフレッシュも兼ねた打ち合わせを設定していたものの,このコロナ禍で移動が禁じられてしまい苦しいとのこと.この点は今までに戻りたい思いがあるとのことでした.オンラインだけで代替できない要素はまだ数多く,これを以前のように補いたい気持ちがもやもやと残っている状態は早く脱したいものです.

広島,東京に住む/広島,東京で働く

こういった座談会でよく触れられる「地方と東京」の広島版です.広島ならでは,東京ならではの生活スタイルやエンジニアとしての刺激の具合についてのお話しが主だったところでした.単身かそうでないか,自分の技術分野に関する盛り上がりがどうか,などざっとまとめると以下のとおりです.

  • 生活
    • 小さいこどもがいると車生活のほうが気を使わずに住む(広島)
    • 比較的短時間で海や山などの自然にアクセスできる(東京でも行けるけど混む)
    • 家賃を考えると若いうちは別の投資ができてよい(広島)
    • より都会を求めるのであれば東京,ほどよい都市を感じるならば広島
    • 東京や福岡などに新幹線でのアクセスが可能.駅前の発展に期待がある(広島)
  • 仕事と技術
    • 地方ではビッグゲストが来ます!みたいな人が東京だとそのへんで出会える(東京)
    • 地方の勉強会でのほうが懇親できる(東京だと待ち行列が消化しきれず終わる等)
    • 東京は情報収集のやりやすさ,広島(地方)はコミュニティに入りやすい
    • 給与は地方か東京だからということは関係なく設定されている(広島)

生活と仕事とのバランスをより意識せざるを得ない状況となった昨今の状況を鑑みるに,お出かけのしやすさや気兼ねない移動のしやすさが生活面でのストレス軽減にもつながっているようです.また,家賃など生活基盤に割く予算を小さくできる点は,実は新卒など若い層にとっては大きな利点なのでは,という話も頷けます.勉強会関係については次の話題でも触れられたので,そちらでまとめて報告します.

広島とサイボウズとこれから

春先に計画していた広島でのイベントが流れてしまった.というお話しもあったので,広島のエンジニア文化とどう関わっていきたいか,今後の展望や妄想についてお話ししました.ざっとまとめると以下のとおりです.

  • 勉強会にもっと参加し,広島がもっと活発になれるよう活動したい.
  • 中四国のハブとなる都市の広島で活動することで,全体の盛り上がりに寄与したい.
    • 広島に限らず中四国全体のエンジニア文化を理解したい.
    • 大きめのイベントが開かれる機会も多く,多くの方が訪れる機会を大事にしたい.
  • 最新技術の輸入的な活動を行って,レベルアップの場をつくりたい.
  • 地元が好きな人がエンジニアとしての刺激を理由に離れることが減るといい.

話の中で福岡の盛り上がりについて触れられました.広島からほど近い都市であり,かつ技術系のイベントや文化の盛り上がりが大きな福岡のような例があるのならば,広島でもできるはず.それをどのようにやっていくかは模索したいということでした.エンジニアとしての刺激,という観点で地方を離れざるを得ない状況は別の都市でも抱えている課題かと思います.小さな町村ではなく,周りから集まってくる側の都市として,こうした刺激の機能も持っていると,より東京ではないところでの人生の可能性が広がってくるものと感じました.
 
また,この話の後,広島オフィスでの採用は可能なのか?という話題にもなりました.受け入れ体制が整っているチームばかりではないものの,全員リモートを前提とした流れから,今年の新入社員は全てリモートでの研修となっています.来年度の研修体制が固まっているわけではありませんが,広島オフィスでの入社も可能ということです.参考までに,今年度の研修についてのブログ記事を載せておきます.

blog.cybozu.io

最後に

たちまちサイボウズ広島について語る 4 人
たちまちサイボウズ広島について語る 4 人
45 分の予定が 50 分と 5 分オーバーでしたが,まだまだ話し足りないことや,引き出しきれなかったこともあったなぁという司会としての感想/反省です.一方で,こういったお話しをきちんと聞ける機会に恵まれたことは,大変ありがたかったなと感じています.
 
オープンソースカンファレンス2020 Online/Hokkaido では北海道にゆかりのある開発本部社員での座談会を実施しましたが,北海道には開発のオフィスがなく,こういった話をすることができませんでした.これからあらゆるところでの業務の可能性が広がっていくことを考えると,先行事例としての広島の話は大変興味深いものでした.自分のこれからにとっても実りある時間となりました.
 
実はこの広島オフィスにまつわるお話しは,来る 10/7(水) 17:00 - 18:00 開催の「Cybozu Tech Meetup #7 サイボウズと広島ゆかりのエンジニア」でも触れられるようです.ぜひご参加ください!

cybozu.connpass.com

以上,osc20hi「たちまちサイボウズ広島!」の報告とさせていただきます.


Node.js Dual Packages (CommonJS/ES Modules) に対応した npm パッケージの開発

$
0
0

こんにちは。フロントエンドエキスパートの平野(@shisama_)です。

フロントエンドエキスパートチームでは業務時間の 30 % の時間で技術探究を行っています。
今回は探究した技術の中から Node.js の ES Modules(以下 ESM)についてと Dual Package (CommonJS/ES Modules) に対応した npm パッケージの開発について紹介します。

ES Modules の特徴

まず ESM の概要を簡単に紹介します。

ESM は ES2015 から仕様に入ったモジュールシステムです。 仕様は ECMAScript の 15.2 Modulesに記載されています。

ESM では次のようにモジュールを読み込むことができます。

import{ KintoneAPIClient } from "@kintone/rest-api-client";

しかし、Node.js では古くから require関数を使ってモジュールを読み込む CommonJS Modules(以下 CJS) を採用していました。

const{ KintoneAPIClient } = require("@kintone/rest-api-client");

CJS と ESM はモジュールを読み込むという面で似ていますが、比べると ESM には次のような特徴があります。

ESM (import) CJS (require)
ブラウザ互換 Node.js でしか動かない
厳格(Strict モード) 厳格ではない
非同期 同期
静的解析可能 静的解析が不可能

ESM はブラウザ互換

ESM はブラウザでも使うことができます。

caniuseのES Modulesの対応表

https://caniuse.com/es6-module

ESM に限らず Node.js のコアモジュールはブラウザとの互換性を重視しています。(互換性のないものもいくつかありますが...)
Node.js の ESM の仕様も ECMAScript の仕様に合わせるようにすることで、ブラウザとの互換性を保つようにしています。

ESM は Strict モード

JavaScript はファイルの先頭に 'use strict';と記載することで Strict モードになりますが、ESM では Strict モードがデフォルトで有効になります。 Strict モード に関しては MDN のページをご確認ください。
Strict モード - JavaScript | MDN

ESM は非同期

ESM では各モジュールのローディングとパースの処理が非同期に並列で行われます。

requireは同期関数なので、1 つのモジュールの処理が終わるまで他のモジュールの処理は開始されません。ローディングは実行時に順次行われます。

ESM は静的解析可能

require関数は実行するまで解析できないことがあります。これは require関数がファイルのローディングを実行時に行うことに起因しています。
一方、ESM は V8 など JS エンジンがパースするときに importするモジュールを解析します。また、importするモジュールがさらに importしているモジュールの解析まで非同期で行います。なので、importするファイルのパスに誤りがあった場合、パースの時点でエラーになるので早めに気づくことができます。

Node.js の ESM 対応について

実は Node.js v8 から ESM を使うことが出来ます。v8 ~ v12 までは実行時に --experimental-modulesフラグをつける必要がありましたが、v12.17.0 からはフラグなしでも実行できます。
https://nodejs.org/en/blog/release/v12.17.0/

まだドキュメント上は Experimental となっていますが、Node.js v14 がメンテナンスされている間に警告も消せるように Node.js のモジュールチーム主体で取り組んでいます。
https://github.com/nodejs/modules/blob/master/doc/meetings/2020-03-11.md#stability-and-flags

また、著名な npm パッケージも ESM に関する対応や議論が行われています。 以下はその一例です。

今後、ESM に対応したパッケージは増え続けると予想しています。

Dual Package(CJS/ESM)に対応した npm パッケージの開発

ここからは Dual Package に対応した npm パッケージの開発方法について解説します。 ESM と CJS に対応したパッケージのことを Dual Package と呼びます。

Conditional Exports によるファイルの指定

古いバージョンの Node.js は ESM のファイルを実行できません。CJS からも使えるように互換性を維持しつつ ESM としもパッケージ配布する仕組みが用意されています。

例えば、以下のディレクトリ構成のパッケージがあったとします。

node_modules/sample-package
├── package.json
└── lib
    ├── cjs.js
    ├── esm.mjs
    └── utils

上記の例では CJS のエントリポイントとして cjs.jsを、ESM のエントリポイントとして esm.mjsを用意しています。 ユーザーは以下のようにエントリポイントのファイルを直接指定することで読み込むことができます。

// ESMimport{ run } from "sample-package/lib/esm.mjs";

// CJSconst{ run } = require("sample-package/lib/cjs.js");

しかし、ユーザーにパッケージ内部のファイルを参照してもらう必要があり、ユーザーにとっては使いにくいです。

Conditional Exports という機能を使うことで、内部のファイルを参照せずに ESM、CJS 両方からパッケージを使うことができるようになります。

https://nodejs.org/api/packages.html#packages_conditional_exports

配布するパッケージの package.json に "exports"フィールドを追加します。"exports"内に "import""require"フィールドを定義し ESM と CJS のエントリポイントを指定します。 フォールバック先として "default"、CJS と ESM のどちらのファイルでも設定できる Node.js 用の "node"もあります。

{"exports": {"import": "./lib/esm.mjs",
    "require": "./lib/cjs.js",
    "node": "./lib/esm.mjs",
    "default": "./lib/cjs.js"
  }}

上記のようにパッケージ側が "exports"を定義しておけばユーザー側は ESM と CJS の両形式で使用できます。

// CJS から読み込む場合は ./node_modules/sample-package/lib/cjs.js が読み込まれるconst foo = require("foo");

// ESM から読み込む場合は ./node_modules/sample-package/lib/esm.mjs が読み込まれるimport foo from "foo";

この Conditional Exports は Dual Package 対応以外にも次のようにパスのエイリアスを指定することも可能です。
また、ブラウザや Electron など実行環境ごとに読み込むファイルを変更するために使うことも考えられています。
https://nodejs.org/api/esm.html#esm_conditional_exports

{"main": "./main.js",
  "exports": {".": {"node": "./index-node.js",
      "browser": "./index-browser.js",
      "default": "./index.js"
    },
    "./feature": {"node": "./feature-node.js",
      "browser": "./feature-browser.js",
      "default": "./feature.js"
    }}}

webpack も v5 からこの "exports"フィールドを参照するようになります。

github.com

.mjs と .cjs

Node.js では ESM と CJS のファイルの判定を拡張子で行っています。

拡張子を .mjsとした場合 ESM のファイルとして読み込まれます。.jsはこれまで通り CJS として読み込まれます。また、.cjsという拡張子も CJS として読み込まれます。
しかし、ESM のファイルの拡張子に .mjsよりも .jsを使いたいという声は多く Node.js のメンテナー間でも議論されました。結果としては package.json に "type": "module"を指定することで .js拡張子のファイルを ESM として実行にすることができようになりました。ただし CJS は拡張子を .cjsにする必要があります。
反対に .jsファイルを CJS として読み込ませることを明示するために "type": "commonjs"とすることもできます。

{"type": "module",
  "exports": {"import": "./lib/esm.js",
    "require": "./lib/index.cjs"
  }}

もう 1 点注意すべきなのは、TypeScript や Babel などを使って開発しているとトランスパイル後のファイルの拡張子が .jsになることです。

前述の通り、ESM として importする場合 .mjsの拡張子にする必要があります。ですが、TypeScript は .mjsでの出力には現在対応していません。
issue は作られているので将来的には対応されるかもしれません。

github.com

そのため、TypeScript を使った開発の場合、.js拡張子を ESM として読み込む必要があるので、package.json の "type"フィールドを "module"にするか、拡張子を .mjsにする変換処理などの対応が必要になります。
これについては後述の方法でも解決できます。

require など CJS 特有の機能を使う

ESM では requireexportsmodule.exports, __filename, __dirnameといった Node.js 特有のグローバル変数や関数は ESM の仕様上使えません。 しかし、互換性のためにも Node.js ではこれらを使うための API を提供しています。

次のように module.createRequire関数を使うことで require関数を生成できます。
ただし、CJS の require関数と同じく同期関数なので、ESM 内で使われていても requireの処理は終わるまで待ちます。

import module from "module";
const require = module.createRequire(import.meta.url);

const packageJson = require("../package.json");

__filename__dirnameは以下のように fileURLToPath関数や dirname関数を使って生成できます。

import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

https://nodejs.org/api/esm.html#esm_no_require_exports_module_exports_filename_dirname

ESMから CJS ファイルを require する

前述のとおり ESM でも require関数を生成できるので、 CJS で書いたコードをそのまま利用できます。
なので少しずつ CJS から ESM へ移行することも可能です。

// foo.cjs
module.exports.getBar = (id) => {...};
// index.mjsimport module from "module";
const require = module.createRequire(import.meta.url);

const{ getBar } = require("./foo");

拡張子の問題

TypeScript や Babel で CJS にトランスパイルする場合は以下のように importのファイルパスの拡張子を省略していても正常に動作します。

// トランスパイル前import foo from"./foo";// トランスパイル後 (CJS)const foo = require("./foo");

しかし、ESM ではファイルパスを明記する仕様になっているため拡張子を省くことはできません。Node.js もこの仕様に従っているため CJS のように拡張子を省略することはできません。

import foo from "./foo"; // 実行時にエラーになる

ファイルパスまで明記する仕様により読み込むファイルを必ず一意に決めることができます。

もし、 ESM でも拡張子が省略できた場合、拡張子が違う同じ名前のファイルが存在したらどちらが優先されて読み込まれるかはコードからは読み取れません。どのファイルが読み込まれるかはランタイムによって変わる可能性があります。ESM ではファイルパスを拡張子まで書く仕様になっているためこういった問題は起きません。

// もし ESM でも拡張子を省略できたら...import foo from "foo";

// どちらが読み込まれるかはわからない(ランタイム次第)├── foo.js
└── foo.json

CJS では Node.js の仕様により読み込まれるファイルの優先順は決まっています。Node.js 特有の仕様のためブラウザとの互換性はありません。
https://nodejs.org/api/modules.html#modules_all_together

ここまでファイルの拡張子まで正確に書く必要があると説明してきましたが、例外があります。

Node.js では ESM でも node_modules配下のパッケージや fshttpなど Node.js のコアモジュールについては拡張子や相対パスの指定は不要です。

// Node.js のコアモジュールの読み込みimport fs from "fs";
// node_modules 内のパッケージの読み込みimport _ from "lodash";

既存の CJS を require して拡張子解決する

前述の「ESM 内で require 関数を使う方法」で紹介したように ESM でも require関数を使うことができます。

require関数の引数のファイルパスは拡張子を省略できるので、その仕組みを利用して既存の CJS ファイルを読み込んで ESM で export するファイルを作成します。

たとえば次のような構造のパッケージにするとします。

node_modules/sample-package
├── package.json
└── lib
    ├── cjs.js  // 既存のCJSファイル
    ├── esm.mjs // ESM で書かれたファイル
    └── api.js  // cjs.js から読み込まれるファイル

esm.mjsは次のように ./cjs.jsrequireして exportするだけの ESM ファイルを作り、Conditional Exports で ESM として配布します。

// esm.mjsimport module from "module";
const require = module.createRequire(import.meta.url);
exportconst{ Foo } = require("./cjs");
{"exports": {"import": "./lib/esm.mjs",
    "require": "./lib/cjs.js"
  }}

この方法は既存の CJS をそのまま活用できるため非常に簡単に ESM 対応ができます。

モジュールバンドラーによる拡張子解決

importしているモジュールのコードを 1 つのファイルにバンドルしてしまえば、実行ファイルから import文を削除できます。

次に Rollup を使って esm.mjsというファイルにバンドルする例を載せます。

exportdefault{
  input: "./src/main.js",
  output: {
    file: "./lib/esm.mjs",
    format: "esm",
  },
  plugins: [resolve(), commonjs()],
};

このような設定で 2 ファイルをバンドルしてみます。

// main.jsimport{ random } from './maths.js';

exportconst randomSelect = (arr) => {return arr[random(arr.length)];
}
// maths.jsexportconst random = (size) => {return Math.floor((Math.random() * size)); 
}

Rollup で ESM 形式でバンドルすると次のように出力されます。

// esm.mjsconst random = (size) => {return Math.floor((Math.random() * size)); 
};

const randomSelect = (members) => {return members[random(members.length)];
};

export{ randomSelect };

バンドルしたファイルを Conditional Export を使って ESM として配布することでユーザーは ESM からパッケージを importすることができます。

{"exports": {"import": "./lib/esm.mjs",
    "require": "./lib/cjs.js"
  }}

Preact も同様に developit/microbundleを使って 1 つの .mjsファイルにバンドルして ESM ファイルを配布しています。

preact/package.json at master · preactjs/preact · GitHub

CJS を直接 import する

実はこれまで紹介した ESM 対応の方法を使わなくても CJS で配布されているパッケージを ESM から直接 importすることができます。

import mod from "cjs-module";
const{ someFunc } = mod; // named exports されている場合

Node.js v14.13 からは named exports も直接 importできるようになるので、すべての CJS を ESM から直接 import可能になります。

import{ someFunc } from "cjs-module";

github.com

ESM から使われることを Node.js v14.13 以上に限定するのであれば、今回の記事で紹介した ESM 対応は行う必要はありません。

今後の動向

ESM に関して Node.js のメンテナー間でも日々議論されており、ユーザーにとってもっと使いやすい仕様を考えたり実装しています。

今後の動向については Node.js コアの ES Modulesラベルやモジュールチームのリポジトリをウォッチするとキャッチアップできます。

github.com

github.com

まとめ

今回は Node.js で使えるようになった ESM の特徴や Dual Package(ESM/CJS)に対応する npm パッケージの作り方を紹介しました。

サイボウズでは OSS として npm パッケージをいくつか公開しています。今回紹介した ESM での配布も考えています。

github.com

サイボウズではプロダクトの開発だけではなく、プロダクトを支えるツールの開発も行っています。OSS の開発やプラットフォームのエコシステム開発にご興味ある方はぜひ以下の採用ページからご応募ください。

cybozu.co.jp

サイボウズの「開発・運用組織」で働く環境 @ 2020

$
0
0

こんにちは〜!リングフィットアドベンチャーを6ヶ月ほどやっているのですが体重の変化がない開発本部所属の hokatomoです💪💪

2016年に ymmt さんが「サイボウズエンジニアの職場環境 @ 2016」を公開しました。 あれから変わった面があるのと、2019年1月に中途入社した者として「こういう制度や文化があるんだ!」と驚いた部分があったので、忘れないうちに2020年度版を書きます:)

この記事で、「サイボウズの開発や運用で働くってどんな感じ?」と気になってくれている方に、少しでも様子が伝わったら嬉しいです。 筆者である私はエンジニアではなく、開発本部所属で社内コミュニケーションの部分を担当しているので、その目線で書いています。

目次

  1. 働く場所・時間は自分で決める、コアタイムなし
  2. 書籍購入、勉強会参加支援、コミュニケーション支援他さまざまな制度
  3. 開発組織を横断して支える、たくさんのチームやメンバー
  4. 学びあい、それぞれを尊重する文化・風土
  5. 技術領域とOSSへの考え方
  6. 新型コロナウイルスの影響で出来た制度や取り組み

※全て詳細に記載するのは難しいので、関連記事を紹介しています☺

1.働く場所・時間は自分で決める、コアタイムなし

2020年10月現在、開発・運用本部のほとんどのメンバーはフレックスタイム制で働いています。

  • どの時間帯に
  • どこで(自宅、または各拠点への出社)
  • どれくらい(週1〜週5)
  • 残業、出張への考え方

以上を自分自身で決めて「私はこんな働き方にします」と宣言をします。なお、深夜勤務は手当や労務観点で申請が必要です。 コアタイムは一切なく「会議だから必ず出勤」みたいなことはありません。 時間が合わない場合は、チームでコミュニケーションしていきましょうというスタンスです。

cybozushiki.cybozu.co.jp

働く場所の紹介、現在の日本国内拠点・海外の連結子会社について

  • 日本国内:東京* 大阪* 松山* 広島* 仙台 福岡* 名古屋
  • 海外(連結子会社):US* ベトナム* 上海* 台湾 オーストラリア

以上にあり、*のある場所に開発チームがいます。 なお、拠点への出社は必須ではなく、自分で決めた働き方次第です。 在宅勤務を取り入れていたり、在宅勤務を中心としている人もいます。

ちなみに、2019年に広島と福岡に開発拠点が出来ました🎊

blog.cybozu.ioblog.cybozu.io

2.書籍購入、勉強会参加支援、コミュニケーション支援などさまざまな制度

「この本買ってください」で書籍が届く、電子書籍も可

自己学習の書籍を上長の承認不要で購入できます。 購入手続きは、アシスタントチームの方に「この本買ってください!」とお願いするだけ。 すぐに購入手続きをしてくれ、経費精算は同チームがやってくれるので、申請者自身での対応は不要です。

以前からこの制度はありましたが、現在はオフィスで本を受け取るのが難しいので、個人のアカウントに紐づく形で電子書籍の購入(※)ができるようになりました。

※電子書籍購入について財務経理チームからの補足💰

サイボウズでは、業務上必要な技術書を個人のアカウントに紐づく電子書籍の形で購入することをOKとしています。

この場合、電子書籍が現物給与に該当しますが、給与課税については、下記のタックスアンサーを参照の上、顧問税理士や所管の税務署と相談しながら処理をしています。 https://www.nta.go.jp/taxes/shiraberu/taxanswer/gensen/2508.htmhttps://www.nta.go.jp/taxes/shiraberu/taxanswer/gensen/2601.htm

各社での実際の税務処理にあたっては、顧問税理士や所管の税務署とご相談ください。

最高のパフォーマンスを出すために、PCスペックや周辺機器の貸与は惜しみなく

エンジニア向けPCはメモリ32GBを積んでいたり、PCの標準機は6種類から選べ、さらにカスタマイズも可能です。

また、業務効率化のため、4Kディスプレイやキーボード、さまざまな周辺機器の貸与があります。 周辺機器は価格上限なしでマウスやキーボード、モニター等さまざまなものを情シスが手配してくれます。

なお、貸与の内容は全て公開されているので、誰がどんな機器を貸与されているのかも分かります。 私は、Adobe製品がさくさく動くようにMacBook Proをカスタマイズしていて、在宅用のモニターやUSB Type Cのハブ、PCスタンド、ヘッドセット等を支給してもらいました。

cybozushiki.cybozu.co.jp

外部の勉強会・イベントの参加は業務扱い、費用は会社負担

  • 業務に関するものは業務時間として参加可能
  • 開催日が休日の場合は、別日に代休が取得可能
  • 参加のために発生する費用(チケット代、交通費や宿泊費)は会社負担

なので、積極的に外部での勉強会やカンファレンスの参加が可能です。

だいたいの交流で補助が出る(誕生日会、Welcomeランチ、成果発表会に飲食提供、海外出張者との飲食等)

社内イベントで用意した飲食
社内イベントで用意した飲食
社内のコミュニケーション促進のため、多種多様な補助の制度があります。チーム内外のちょっとした集まりから飲食の補助が出来るので、個人の負担が少なくコミュニケーションを取れるのが嬉しいですね。

関連の制度はたくさんあるので、下記ページの「社内コミュニケーション活性化のための取り組み」を参考にどうぞ!

cybozu.co.jp

3.開発組織を横断して支える、たくさんのチームやメンバー

組織横断型の支援系チームがいくつもあり、さまざまな面からサポートしています。

経費精算等各種手続きを全部まるっとやってくれるチーム

開発、QA、ライティング等それぞれの専門に集中するため 購買や経費精算、出張手続き等各種庶務業務をまるっと引き受けてくれるアシスタントチームがいます。

それはもう神のような存在です🙏🙏🙏🙏

開発チームの「生産性向上」を技術的に支援する「生産性向上チーム」

生産性向上チームは横断的に各プロダクトを支援していて、プロダクトチームが普段手の回らない部分まで見ています💪 このチームがいるおかげで、さらにノウハウの蓄積、共有も進んでいると思います。

blog.cybozu.io

フロントエンドで困ったら「フロントエンドエキスパートチーム」

プロダクトチームが開発に集中し、より多くの価値をユーザーに届けるために、各プロダクトが抱えているフロントエンドでの辛みや問題などを解決するフロントエンド専門のチームがいます。

blog.cybozu.io

MySQLで困ったらMySQLエキスパートの yoku0825 さん

2019年11月に、GMOメディア株式会社とコンサルティング業務委託契約を締結し、MySQLエキスパートの yoku0825さんに相談ができるようになりました。 端的に言うと、MySQLで困ったら yoku0825 さんに聞けたり調査をお願いできます👏

blog.cybozu.io

その他にも👀

  • 外部登壇や自主開催のイベントをやりたいときに支援してくれる「コネクト支援」チーム
  • 仕事の進め方やチームでの困りごとがあったら「アジャイルコーチ」チーム
  • 東京オフィスの開発・運用エリアを改善していく「Kaiun Office Improvement」チーム

などなど、横断型の支援系チームがたくさんいます。 相談しやすいのはもちろんですが、積極的に「それ相談乗るよ!」と前のめりで来てくれるのが特徴です。

4.学びあい、それぞれを尊重する文化・風土

コーポレートサイトで文化や風土が紹介されていますが、以下がサイボウズに転職して特に驚いた点です。

男性も育休が当たり前に取れる

素晴らしいなと思ったのが、男性も数ヶ月育休を取られていること。 これはパートナーの方も嬉しいし、心強いですよね。

当たり前のように、チームメンバーを育休に送り出していく同僚たちも素敵です👏👏

お互いが違っても、まず理解しようとする

私は子どもがいるので、天候の都合で保育園の送迎が遅れますとか、子供の体調で勤怠が…みたいなことが日常的に発生します。 また、緊急事態宣言中は家庭内保育で勤怠が乱れがちだったので稼働量が減ってしまいました。

そのときは「じゃあやれる範囲でやってみよう」「無理だったら我慢せず休もう」みたいな形で、チーム内でライフステージが違えど「なるほど」と理解しようとするスタンスなのは、とてもありがたかったです。

オンラインで社内勉強会や成果発表会が毎日何かある

以前からも社内勉強会やイベント、成果発表会は頻繁にありましたが、2020年2月以降はフルリモート・オンライン開催でより活性化していると感じます。 そのほとんどが「誰でも参加可能」で、オンライン開催でさらに参加しやすくなっています。

ちなみに、 WASABI というチームが開発・運用系のイベントや勉強会を支援しています。

blog.cybozu.io

開発本部には「部長」がいない、チームに権限が委譲されている

  • チームの方針
  • 採用に関すること

などなど、さまざまなことがチームに任されています。 決める力がないと難しいな〜というのを日々痛感しているので大変さはもちろんあります。

ただ、自分たちで責任を持って決めるというのを何度も経験できるのはなかなかないな、と思います。 ちなみにこの取り組みは開発本部のみです。

blog.cybozu.io

5.技術領域とOSSへの考え方

技術領域

サイボウズの技術領域
サイボウズの技術領域

複数のプロダクトがあり複雑なため、ざっと図で紹介すると上記のような形です。

開発・運用にあたってのコミュニケーションツール・情報共有

色々ありますが、主に

  • kintone(自社プロダクト)
  • Garoon(自社プロダクト)
  • Zoom
  • Slack

などを中心に使っています。 自社プロダクトを日常的に触っているので、ドックフーディングがしやすいのが特徴です。

OSSの考え方や取り組み

サイボウズには「OSS推進チーム」があります。法務を含めたOSSに関係する各部門のメンバーで構成されており、主な活動内容は次のとおりです。

  • サイボウズのメンバーがOSS関連の活動を行いやすくするためのサポート
  • OSSプロジェクトへの寄付・コミュニティイベントへの協賛

以下のページで

  • OSSポリシーの公開
  • OSSプロジェクトへの寄付一覧

などを公開しています。どんな考え方なのか気になる方はぜひご覧いただけると嬉しいです。

ちなみに寄付は「投資」ではなく、そのOSSを利用したことで事業が成長できた分を還元する「フィードバック」という考え方です。 この考え方、個人的に好きです😊

blog.cybozu.iotech.cybozu.ioblog.cybozu.io

6.新型コロナウイルスの影響で出来た制度や取り組み

長くなりましたが、最後に新型コロナウイルスの影響で変わった点です。

自宅の環境整備・維持のため、在宅手当が出ている

在宅勤務を選ぶ人が増え、設備投資費や在宅環境整備として、在宅手当が毎月5,000円がでています(2020年9月より) 勤務日数や勤務時間に関わらず正社員・契約社員は一律上記の金額が支給されています。

警戒レベルと行動指針ができた

それぞれの地域の感染状況を見て、

  • 各自が安心して働けるように
  • 社外の関係者の皆さんにも安心していただけるように

行政等が出している情報を元にサイボウズでも警戒レベルを設定、行動指針ができました。 (会議開催や来訪者対応、イベント開催・参加などをする際に指針とするもの) topics.cybozu.co.jp

休校・休園に伴う日数無制限の特別休暇がある

子どもがいる家庭で、新型コロナウイルスの影響で休校・休園になり休まざる追えない場合は、日数無制限の特別休暇(有給休暇)が付与されています。

実際に、自分も緊急事態宣言中に特別休暇を使いました。 もともと付与されている有給休暇の残日数を気にせず、家庭内保育のために特別休暇が取得できたのは精神的にだいぶ楽でしたね。

意思決定のスピードが早かったり、実際の制度運用開始が早く、実際に運用している労務や財務経理のメンバーには感謝しかありません…!!

www.businessinsider.jp


た、大変長くなってしまいました……。 ここまで読んでくださった方、途中まで読んでくださった方もありがとうございます!

以上、「サイボウズの「開発・運用組織」で働く環境 @ 2020」でした。

こちらを読んで「サイボウズ気になるな〜」という方は、以下で開発や運用のメンバーを募集しておりますので、ぜひご覧ください🙏 cybozu.co.jp

フロントエンドエキスパートチームでWEB+DB PRESS Vol.119の特集記事を執筆しました

$
0
0

WEB+DB PRESS Vol.119の表示画像
WEB+DB PRESS Vol.119の表示画像

こんにちは!フロントエンドエキスパートチームです!
私たちフロントエンドエキスパートチームの一部メンバーは、2020年10月24日発売のWEB+DB PRESS Vol.119に「フロントエンド脱レガシー 長く愛されるプロダクトをさらに改善していくために」というタイトルで特集を執筆しました。

gihyo.jp

今回は担当したメンバーから各章の紹介をします。

各章の概要

1章 脱レガシーの進め方

書いた人 : @shisama_

第1章では次のトピックを扱っています。

  • 問題点と改善のメリット
  • サイボウズのレガシー事例
  • 脱レガシーの進め方

はじめの章なので脱レガシーの概要や進め方といった部分にフォーカスしています。
サイボウズで起きたレガシーの問題や事例を紹介し、なぜ脱レガシーを行うのかを説明しています。 また、運用中の大規模なプロダクトをどのように脱レガシーしていくか進め方や事前に行ったことも紹介します。 技術的な内容は少ない章ですが、脱レガシーの足がかりとして参考にしていただけると幸いです。

2章 技術選定

書いた人 : @nakajmg

第2章では次のようなトピックを扱っています。

  • 技術選定における評価軸
  • 技術選定に必要な視点
  • サイボウズでの技術選定の事例

脱レガシーにおいてフロントエンドの技術選定をする際の評価軸や、必要な視点について紹介しています。また、脱レガシーの技術選定と新規プロジェクトの技術選定の違いや、サイボウズでどのような評価軸で技術選定をしたかという事例も紹介しています。この章がプロダクトに適した技術選定をするのに少しでもお役に立てれば幸いです。

3章 テスト

書いた人 : @__sakito__

第3章では前半でテストについての全般的な知識、手法を説明し、後半では実際にサンプルのアプリケーションに対してテストを加えつつ解説しています。
レガシーなプロジェクトへの予防として、フロントエンドの領域でもテストを書いていく大切さをお伝えしたい思いで書きました。
これからテストコード書いていく参考になれば幸いです。

4章 脱レガシーコード

書いた人:@pirosikick,@toshi__toma

4章ではコードレベルの具体的な改善について書きました。また、大規模なプロダクトの改善で必要になる新旧コード間の連携方法や、コードの自動変換ツールについて紹介しています。 サイボウズで行った改善、または、進行中の改善の中でよく考えることなどを切り出して紹介したので、参考になる点があれば幸いです。

5章 次のレガシーを生まないために

書いた人:@pirosikick,@toshi__toma

脱レガシーは1度取り組んで終わりではありません。日々の開発の中で改善を繰り返すなど、いま書いているコードがレガシーにならない為の取り組みが大切です。

5章では、レガシーを生まないために、普段の開発で意識すると良いこと、ツールを活用したOSSのアップデート、新しいライブラリやパラダイムを取り入れるための情報収集について紹介しています。 また、私達が脱レガシーに取り組んでみての反省点や改善点、今後の課題についても触れています。

さいごに

ぜひ感想をTwitterのハッシュタグ #wdpressに呟いてください! お待ちしています!

gihyo.jp

Rookのメンテナに就任しました: これまでとこれからの取り組み

$
0
0

はじめに

こんにちは、Necoプロジェクトsatです。先週CNCFのgraduatedプロジェクトであるRookのメンテナに就任しました。本記事ではRookがどういうソフトウェアであるかについて軽く触れた後に、何をしてきたことによってメンテナになったのか、今後どうしていくのかについて述べます。

RookはKubernetes上で動作する様々なストレージソフトウェアのオーケストレータです。主なターゲットは分散ストレージCephであり、開発者はCephを使った製品を作っているベンダやサイボウズをはじめとしたCephのユーザが主です。

サイボウズは国内向けクラウドサービスcybozu.comの次期ストレージ基盤としてRook/Cephを使うことを決めています。詳細については以下の記事をごらんください。

blog.cybozu.io

メンテナになるまでの道のり

我々はRook/Cephクラスタの開発中に遭遇した機能不足やバグによる不具合などの課題を、他の開発者たちと協力しながら自分達でPRを出して改善してきました。とくにPVC-basedクラスタという新しいタイプのクラスタをプロダクション環境で使うための諸機能については主要な貢献をしてきたという自負があります。定量的なデータを出すと、ここ半年ほどの期間ではサイボウズのコミット数が会社別で世界二位となっています。

我々が具体的にどういう取り組みをしてきたかについては以下のスライドをごらんください。

speakerdeck.com

ここまでは我々が直接困っている課題の解決でした。これらによって技術者としての能力は示せたわけですが、メンテナになるにはこれだけでは不十分です。

メンテナになるには、コミュニティ全体を俯瞰して、コミュニティ全体の利益になることをしていると示す必要があります。これを示すために、我々が直近では困っていなくても、長期的な品質向上やコミュニティの活性化のために以下のような取り組みをしてきました。

  • テストの改善。カバレッジ向上やCIがランダムに失敗してしまう不具合の修正など
  • issueやSlackにおける他のユーザのサポート。使い方や技術的な質問に対する回答やバグ修正など
  • 自分が詳しい分野についてのコードレビュー

とくにテストの改善については多くの課題があると問題提起して、定例ミーティングの議題として取り上げてもらいました。その後は集中的にCI改善に取り組み、ランダムに発生するテストの失敗確率を数十%減らしました。

これらの活動を一年以上にわたって続けてきたことが総合的に評価されたため、Steering Committee全員の賛成によって今回のメンテナ就任となりました。

今後どうするのか

わたしはメンテナ就任時に、とくに品質管理に興味があると他の開発者たちに申し出ました。その一環として、テストの改善に継続的に取り組む予定です。他にもユーザサポートやレビューなども、これまで以上にやっていきます。

サイボウズの開発者としての活動についても書いておきます。現在のRookは我々に必要な機能がおおよそ実装されたという状態になっています。今後は以下のような機能を評価して、これまでと同じく、遭遇した課題を他のコミュニティメンバと協力しながら自ら解決していくつもりです。

  • メトリクスやログの監視、通知
  • オペレーションの自動化
  • ブロックデバイスやオブジェクトストレージのリモートレプリケーション

OSSのメンテナになることによる影響

Rookに限らず、OSSのメンテナになると開発の全体方針の決定に関われるようになります。その一方で、ユーザサポートやコードレビューなどをは当然するものだと期待されます*1。Rookについては公式ドキュメントに具体的にメンテナに期待されることが書いています。このような理由によって、会社をはじめとする組織の一員がメンテナになると、その人の人的リソースを少なからず割くことになります。メンテナ就任を打診された場合は、組織としてこの点を踏まえた上で受けるかどうかを決めるといいと思います。

私の場合は、我々がRook/Cephをストレージシステムに使うと決めたときから、私をupstream Rook開発の専任技術者にして最初からメンテナになるつもりで活動すると決めていたため、判断には困らなかったです。なぜそこまでコミットする必要があるかというと、サイボウズにとってお客様のデータは何よりも重要であり、他のソフトウェアに比べて事業へのインパクトが非常に大きいからです。このため、データ消失/破壊につながるような問題の予防、発生時の可及的速やかな修正提供には多くのリソースを投入したというわけです*2

メンテナになるならないに限らず、組織としてOSSにコミットすることについて興味のあるかたは、以下の記事をごらんください。

blog.cybozu.io

おわりに

最後になりますが、私のメンテナ就任は私一人ではなく私が所属しているNecoプロジェクトが一丸となって、組織としてRookに貢献してきたことが評価された結果です。この場を借りてチームメンバに深く感謝いたします。

サイボウズはこれからもRookに深くコミットしていきます。

*1:OSSなので法的な意味での責任はありませんが

*2:自分達が使っているあらゆるソフトウェアについて同じ戦略をとるわけではありません

TypeScript による Isomorphic な API Client 開発

$
0
0

こんにちは、フロントエンドエキスパートチームの @koba04です。

本記事では、kintone の REST API を使うためのクライアントである @kintone/rest-api-client (以下 rest-api-client) の構成や工夫した点について紹介します。

本記事は rest-api-client の 1.6.0のバージョンに基づいています。

@kintone/rest-api-client とは

rest-api-client とは、kintone が提供する REST API を利用するためのクライアントライブラリです。 GitHub 上は kintone/js-sdkの Monorepo の 1 パッケージとして開発されています。

kintone/js-sdkでの Monorepo 開発については下記の記事を参照してください。

https://blog.cybozu.io/entry/2020/04/21/080000

rest-api-client は Isomorphic、つまりブラウザ環境と Node.js 環境どちらでも動作するように作られています。

全体の構成

パッケージ全体のディレクトリ構成は下記の通りです。

➜  tree -L 2 packages/rest-api-client/src
packages/rest-api-client/src
├── KintoneFields
│   ├── exportTypes
│   └── types
├── KintoneRequestConfigBuilder.ts
├── KintoneResponseHandler.ts
├── KintoneRestAPIClient.ts
├── __tests__
│   ├── KintoneRequestConfigBuilder.test.ts
│   ├── KintoneResponseHandler.test.ts
│   ├── KintoneRestAPIClient.test.ts
│   ├── setup.ts
│   └── url.test.ts
├── client
│   ├── AppClient.ts
│   ├── BulkRequestClient.ts
│   ├── FileClient.ts
│   ├── RecordClient.ts
│   ├── __tests__
│   └── types
├── error
│   ├── KintoneAbortSearchError.ts
│   ├── KintoneAllRecordsError.ts
│   ├── KintoneRestAPIError.ts
│   └── __tests__
├── http
│   ├── AxiosClient.ts
│   ├── HttpClientInterface.ts
│   ├── MockClient.ts
│   └── index.ts
├── index.browser.ts
├── index.ts
├── platform
│   ├── UnsupportedPlatformError.ts
│   ├── __tests__
│   ├── browser.ts
│   ├── index.ts
│   └── node.ts
└── url.ts

今回は上記の中でも client/http/platform/ディレクトリについて解説していきます。

依存関係の制御

前述した通り、rest-api-client はブラウザ環境でも Node.js 環境でも動作するように作られています。 ブラウザ環境と Node.js 環境固有の処理が混在しないように、環境毎の依存を抽象化出来るように設計されています。

HTTP Client

rest-api-client では HTTP Client として axiosを利用しています。 axios 自体がブラウザ環境でも Node.js でも動作するように作られているため、直接使ってもブラウザ、Node.js 環境で動作するクライアントを作ることは可能です。 しかし、rest-api-client では HTTP Client のインターフェイスを定義して抽象化した上で利用しています。

下記が HttpClientInterfaceです。

import FormData from"form-data";exportinterface HttpClient {
  get: <T extends object>(path: string, params: object)=> Promise<T>;
  getData: (path: string, params: object)=> Promise<ArrayBuffer>;
  post: <T extends object>(path: string, params: object)=> Promise<T>;
  postData: <T extends object>(path: string, params: FormData)=> Promise<T>;
  put: <T extends object>(path: string, params: object)=> Promise<T>;delete: <T extends object>(path: string, params: object)=> Promise<T>;}exporttype ErrorResponse<T =any>={
  data: T;status: number;
  statusText: string;
  headers: any;};exporttype Response<T =any>={
  data: T;
  headers: any;};exporttype HttpMethod ="get" | "post" | "put" | "delete";exporttype Params ={[key: string]: unknown };exporttype ProxyConfig ={
  host: string;
  port: number;
  auth?: {
    username: string;
    password: string;};};exportinterface HttpClientError<T = ErrorResponse>extendsError{
  response?: T;}exportinterface ResponseHandler {
  handle: <T =any>(response: Promise<Response<T>>)=> Promise<T>;}exporttype RequestConfig ={
  method: HttpMethod;
  url: string;
  headers: any;
  httpsAgent?: any;
  data?: any;
  proxy?: ProxyConfig;};exportinterface RequestConfigBuilder {
  build: (
    method: HttpMethod,
    path: string,
    params: Params | FormData,
    options?: { responseType: "arraybuffer"})=> Promise<RequestConfig>;}

httpのレイヤーの外では、上記の Interface のみに依存しており、詳細である実装には依存していません。 HttpClientInterfaceのインターフェイスを axiosをベースに実装したものが、AxiosClientです。

// HttpClient Interface を実装するexportclass AxiosClient implements HttpClient {private responseHandler: ResponseHandler;private requestConfigBuilder: RequestConfigBuilder;constructor({
    responseHandler,
    requestConfigBuilder,}: {
    responseHandler: ResponseHandler;
    requestConfigBuilder: RequestConfigBuilder;}){this.responseHandler = responseHandler;this.requestConfigBuilder = requestConfigBuilder;}publicasync get(path: string, params: any){// kintone の REST API を実行するための形式に組み立てるconst requestConfig =awaitthis.requestConfigBuilder.build("get",
      path,
      params
    );returnthis.sendRequest(requestConfig);}publicasync post(path: string, params: any){// kintone の REST API を実行するための形式に組み立てるconst requestConfig =awaitthis.requestConfigBuilder.build("post",
      path,
      params
    );returnthis.sendRequest(requestConfig);}// 省略private sendRequest(requestConfig: RequestConfig){// Axios が返す Promise を constructor で受け取った Response を処理するハンドラーに渡すreturnthis.responseHandler.handle(// eslint-disable-next-line new-cap
      Axios({
        ...requestConfig,// NOTE: For defining the max size of the http request content, `maxBodyLength` will be used after version 0.20.0.// `maxContentLength` will be still needed for defining the max size of the http response content.// ref: https://github.com/axios/axios/pull/2781/files// maxBodyLength: Infinity,

        maxContentLength: Infinity,}));}}

HTTP Client 自体のインターフェイスだけでなくレスポンスについても httpのレイヤーにインターフェイスとして定義しているため、axiosから別の HTTP Client に変えたい場合にも httpのレイヤーの中で対応できます。 現状は axiosを使うことで Node.js 環境での Proxy やクライアント証明書対応が簡単に行えるようになっていますが、将来的にブラウザ環境と Node.js 環境それぞれで異なる HTTP Client を使いたいという場合にも対応可能な設計になってます。

また、AxiosClient以外の HttpClientInterfaceの実装として、MockClientを用意しています。 これは主に単体テスト時に利用することを想定した HTTP Client で、HttpClientInterfaceの実装に加えて、任意のレスポンスを返したりリクエストのログを記録する機能を持っています。 これにより、HTTP のレイヤーをモックしたい場合にもモックライブラリを使うことなく、HTTP リクエストを伴う処理に対してテストを書くことができます。

let mockClient: MockClient;let recordClient: RecordClient;
  beforeEach(()=>{const requestConfigBuilder =new KintoneRequestConfigBuilder({
      baseUrl: "https://example.cybozu.com",
      auth: {type: "apiToken", apiToken: "foo"},});// MockClient を HTTP Client として設定
    mockClient = buildMockClient(requestConfigBuilder);const bulkRequestClient =new BulkRequestClient(mockClient);
    recordClient =new RecordClient(mockClient, bulkRequestClient);});
  describe("addRecords",()=>{const params ={ app: APP_ID, records: [record]};const mockResponse ={
      ids: ["10","20","30"],
      revisions: ["1","2","3"],};let response: any;
    beforeEach(async()=>{// モックのレスポンスを指定
      mockClient.mockResponse(mockResponse);
      response =await recordClient.addRecords(params);});
    it("should pass the path to the http client",()=>{
      expect(mockClient.getLogs()[0].path).toBe("/k/v1/records.json");});
    it("should send a post request",()=>{
      expect(mockClient.getLogs()[0].method).toBe("post");});
    it("should pass app and records to the http client",()=>{
      expect(mockClient.getLogs()[0].params).toEqual(params);});
    it("should return a response having ids, revisions, and records",()=>{
      expect(response).toEqual({
        ...mockResponse,
        records: [{ id: "10", revision: "1"},{ id: "20", revision: "2"},{ id: "30", revision: "3"},],});});});

HttpClientInterface以外では、エラー情報である HttpClientErrorやリクエスト・レスポンスを処理するための RequestConfigRequestConfigBuilderResponseHandlerを提供しています。

RequestConfigBuilderResponseHandlerはそれぞれリクエスト・レスポンス時に、HTTP Client のレイヤーで kintone のドメインに関する処理を行うためのモジュールです。 例えば、RequestConfigBuilderは GET リクエストの URI の長さが一定値を超えた場合に X-HTTP-Method-Overrideを使った POST リクエストに切り替えるといった処理を行っています。 これらは kintone のドメインに関する処理であり、httpのレイヤーに書く処理としては不適切なため、外側から渡す形にしています。

KintoneResponseHandlerでは、レスポンスに対するエラーハンドリングが行われています。

環境毎の依存

上記は HTTP Client のレイヤーの話ですが、他にも認証方法などブラウザと Node.js 環境での違いがあります。 それを処理するのが platformディレクトリの役割です。

Platform のレイヤーでは下記のようなインターフェイスを用いて抽象化を行っています。

type PlatformDeps ={
  readFileFromPath: (
    filePath: string)=> Promise<{ name: string; data: unknown }>;
  getRequestToken: ()=> Promise<string>;
  getDefaultAuth: ()=> DiscriminatedAuth;
  buildPlatformDependentConfig: (params: object)=> object;
  buildHeaders: ()=> Record<string,string>;
  buildFormDataValue: (data: unknown)=> unknown;
  buildBaseUrl: (baseUrl?: string)=>string;
  getVersion: ()=>string;};

これらの処理を platform/browser.tsplatform/node.tsで実装しています。 一方の環境でしかサポートしていない場合は UnsupportedPlatformErrorという専用のエラーを投げるようになっています。

exportconst readFileFromPath =async(filePath: string)=>{const data =await readFile(filePath);const name = basename(filePath);return{ data, name };};exportconst getRequestToken =()=>{// この関数はブラウザ環境のみthrownew UnsupportedPlatformError("Node.js");};exportconst getDefaultAuth =()=>{// この関数はブラウザ環境のみthrownew UnsupportedPlatformError("Node.js");};exportconst buildPlatformDependentConfig =(params: {
  clientCertAuth?:
    | {
        pfx: Buffer;
        password: string;}
    | {
        pfxFilePath: string;
        password: string;};})=>{const clientCertAuth = params.clientCertAuth;// クライアント証明書対応if(clientCertAuth){const pfx ="pfx"in clientCertAuth
        ? clientCertAuth.pfx
        : fs.readFileSync(clientCertAuth.pfxFilePath);const httpsAgent =new https.Agent({
      pfx,
      passphrase: clientCertAuth.password,});return{ httpsAgent };}return{};};exportconst buildHeaders =()=>{return{"User-Agent": `Node.js/${process.version}(${os.type()}) ${      packageJson.name    }@${packageJson.version}`,};};exportconst buildFormDataValue =(data: unknown)=>{return data;};exportconst buildBaseUrl =(baseUrl: string | undefined)=>{if(typeof baseUrl ==="undefined"){thrownewError("in Node.js environment, baseUrl is required");}return baseUrl;};exportconst getVersion =()=>{return packageJson.version;};

ブラウザと Node.js 環境での依存を切り替える方法ですが、実行時に動的に切り替えようとすると webpack などのモジュールバンドラによるビルド時に Node.js 環境用の依存パッケージがブラウザ用のビルドに含まれてしまいます。 rest-api-client 自体も Rollup による UMD ビルドを提供しており、不要なパッケージやポリフィルによるファイルサイズが大きくなることは避けたいと考えています。

そのため、rest-api-client では、エントリーポイントをブラウザと Node.js 環境で分離し、エントリーポイントで依存を挿入する形で対応しました。

import{ injectPlatformDeps }from"./platform/";import * as browserDeps from"./platform/browser";

injectPlatformDeps(browserDeps);export{ KintoneRestAPIClient }from"./KintoneRestAPIClient";
import{ injectPlatformDeps }from"./platform/";import * as nodeDeps from"./platform/node";

injectPlatformDeps(nodeDeps);export{ KintoneRestAPIClient }from"./KintoneRestAPIClient";export * as KintoneRecordField from"./KintoneFields/exportTypes/field";export * as KintoneFormLayout from"./KintoneFields/exportTypes/layout";export * as KintoneFormFieldProperty from"./KintoneFields/exportTypes/property";

エントリーポイントで設定されたプラットフォーム依存の処理は、下記のように利用します。

exportclass KintoneRestAPIClient {
  record: RecordClient;
  app: AppClient;
  file: FileClient;private bulkRequest_: BulkRequestClient;private baseUrl?: string;constructor(options: Options ={}){this.baseUrl = platformDeps.buildBaseUrl(options.baseUrl);// 以下略

これにより単体テストでも、プラットフォーム依存の処理を置き換えたり、ブラウザ環境、Node.js 環境を想定したテストも書けるようになっています。

Client

rest-api-client が提供する機能はいくつかのカテゴリに分類出来るため、それぞれのカテゴリ毎に Clientを作成しています。

これらの Client を作成する際に上記で作成した HTTP Client を渡す形になっているため Client をテストする際は、HTTP Client として MockClientを渡すことで実際の API にアクセスすることなくテストしています。

現状は MockClientが返すデータは都度テスト内で準備しているため、将来的にはモック API を提供する仕組みも検討したいと考えています。

その他の工夫

Feature Flags による機能追加

プロダクトで利用しているライブラリが破壊的変更を行うことはメジャーバージョンアップであっても利用者に負担を与えます。 そのため rest-api-client では、破壊的変更を行う際には可能な限り段階的なアップデートプランを提供したいと考えています。 そのための仕組みとして、Feature Flag の仕組みを取り入れています。

参考: https://github.com/kintone/js-sdk/pull/304

これにより、Feature Flag をデフォルトではオフにした状態でマイナーバージョンとしてリリースし、opt-in により新機能を試せる状態にしています。 その後デフォルトで Feature Flag をオンにする際には、そのオプション自体は残したままメジャーバージョンとしてリリースして、opt-out で無効化出来るようにします。

場合によっては、Feature Flag を適用できない場合も想定されますが、その場合でも warning メッセージを出すなど可能な限り破壊的変更時にもプロダクトが壊れないようにと考えています。

ToDo から実装する

rest-api-client は単体テストフレームワークとして、Jest を利用しています。 Jest は it.todo("should be bar");のように Todo として先にテストケースを書くことができます。 rest-api-client ではこの機能を利用して、仕様を Todo として記述していき、Todo となっているテストを実装しながら進めるという方法を取りました。

これにより、仕様を考えることと実装することがプロセスとして分離され、議論がしやすくなりました。

また、出入力がはっきりしている純粋関数に対しては、test.eachを利用して一覧性の高いテストを書いています。

test.each`  endpointName | guestSpaceId | preview      | expected  ${"record"}  | ${undefined} | ${undefined} | ${"/k/v1/record.json"}  ${"record"}  | ${undefined} | ${false}     | ${"/k/v1/record.json"}  ${"record"}  | ${undefined} | ${true}      | ${"/k/v1/preview/record.json"}  ${"record"}  | ${3}         | ${undefined} | ${"/k/guest/3/v1/record.json"}  ${"record"}  | ${3}         | ${false}     | ${"/k/guest/3/v1/record.json"}  ${"record"}  | ${3}         | ${true}      | ${"/k/guest/3/v1/preview/record.json"}  ${"record"}  | ${"3"}       | ${undefined} | ${"/k/guest/3/v1/record.json"}  ${"record"}  | ${"3"}       | ${false}     | ${"/k/guest/3/v1/record.json"}  ${"record"}  | ${"3"}       | ${true}      | ${"/k/guest/3/v1/preview/record.json"}`("buildPath",({ endpointName, guestSpaceId, preview, expected })=>{
  expect(buildPath({ endpointName, guestSpaceId, preview })).toBe(expected);});

まとめ

このようにサイボウズでは、プロダクト本体だけでなくサイボウズ外の開発者の開発体験を向上させるためのツール開発も行っています。 このような設計は、個人で行うだけでなく普段のモブプログラミングを通じて複数人で議論しながら進めています。

サイボウズでは、Web アプリケーションのフロントエンドだけでなく、プラットフォームとして開発体験を向上させるためのツール開発を OSS でやりたいというエンジニアも絶賛募集中です!

https://cybozu.co.jp/company/job/recruitment/list/front_end_expert.html

Viewing all 681 articles
Browse latest View live