AWS各通知の構成変更に取り組んでみた

情報システム部でAWS周りの運用保守をしている「ねぎ」です、こんにちは!

弊社ではAWSのHealth、Amazon GuardDuty、AWS SecurityHubに関する通知をSlackに連携して受け取っています。

その際のSlackチャンネルは、Health、Amazon GuardDuty、AWS SecurityHubでそれぞれ1チャンネルずつだったのですが、管理しているAWSアカウント数が増えたこともあり、通知された情報がすぐ流れて見落としてしまうようなことも発生していました。

※弊社のAWSアカウント管理については、是非こちらもご覧ください

tech-blog.yayoi-kk.co.jp

今回はそういった課題に対して取り組んだ内容をご紹介していきたいと思います。

各通知構成変更の取り組み

第一段リリース

まずは一番シンプルだったころのSlack通知の仕組みです。

Slack通知の仕組み-変更前

検知内容をEventBridgeで受け取り、ChatbotからSlackへ連携して流す、といったものです。

AWS SecurityHubについても同じ仕組みで通知を実施していました。

Slack通知の仕組み-AHA

Healthの情報については、AWS Health Awareというソリューションを使ってSlackへの連携を実施していました。

こちらのソリューションは、

  • EventBridgeで定期的にlambda関数を呼び出して

  • 各AWSアカウントのHealth情報を受け取り

  • SecretManagerから通知先の情報を取得して

  • DynamoDBにHalth情報のステータス(オープン中、アップデート、クローズ)を書き込む

といったものになります。

参考:AWS Health Aware

aws.amazon.com

Amazon GuardDuty / AWS SecurityHub、Healthの通知構成も、一目でリソース全体がぱっと把握できるくらいの量かなと思います。

続いて各チームから修正の要望が多かった内容について紹介です。

第二段リリース

管理しているAWSアカウント数が50を超えたあたりから、ちらほらと以下のような指摘をいただくことがありました。

「Slack通知にAWSアカウントIDは表示されているけど、AWSアカウント名がないからわからない。」

※AWS Health Awareについては、Slack通知時にアカウント名が表示されます

12桁のAWSアカウントIDを覚えるのは大変ですよね、はい。。。

ということで、Amazon GuardDutyとAWS SecurityHubに関するSlack通知についてAWSアカウント名も表示させるような取り組みをしてみました!

詳細はコチラ

tech-blog.yayoi-kk.co.jp

Chatbotでは通知内容の細かいカスタマイズが出来ないので、Chatbotの便利さを諦め、lambdaでアカウントIDからアカウント名を取得するような構成に変更しました。

その結果lambdaで通知内容のJSONを受け取り、Slackの形式に合わせたJSON形式へと変更するような処理を加えることになりました。

若干複雑になりましたね。

ここまで対応が行えたことで一段落していましたが、さらなる課題が出てくるのでした。

第三段リリース

今回の本題です。

管理するAWSアカウント数が100超となってきて、1つのSlackチャンネルに通知をしていると、情報が流れて見逃してしまうといった課題が出てきました。

1つのSlackチャンネルに通知しているのが限界だと感じ、ならば複数チャンネルだ!と構成変更を実施することにしました。

結論!

各種通知の変更後アーキテクチャ

このようなアーキテクチャで実現することにしました。

どうでしょうか?当初のシンプルさから、手の込んだ構成になったかと思います。

この構成のポイントは以下です。

  1. Amazon GuardDuty / AWS SecurityHubでSlack通知を行うlambda関数を1つに
  2. AuditアカウントにOrganizationsの一部権限を移譲し、Auditアカウント内のlambda関数からAWSアカウント名を取得可能に

1つ目のポイントは、lambda関数を1つにしたことで、今後Firewall Managerの検知も集約したいといった要望が出てきた場合も、同じlambda関数で使いまわしが効くようにしています。

2つ目のポイントは、Organizationsの権限を移譲したことで、Auditアカウント内でやりとりが済むようになりました。

権限移譲していない場合は、親のアカウントに対してAWSアカウント名を取得するようにアカウント跨ぎの仕組みを用意する必要がありました。

(Healthの情報は残念ながら権限移譲の仕組みが用意されていないため、アカウント跨ぎで取得するような構成としています。今後のアップデートに期待したいと思います。)

本構成で運用をしてみてまた新たな課題が出てきたら、その際には第四段リリースでブログを書きたいと思います。

あとがき

いかがでしたでしょうか。

AWSアカウントが少ないうちは、1つの通知先でも問題ないといった声も多いと思います。

しかしAWSアカウント数が増えるにつれて、情報が流れて見落としてしまうと、それがきっかけで重大な障害につながりそうだった、などヒヤリとした経験があるのではないでしょうか?

今回のブログ記事が、AWSマルチアカウント環境で日ごろ多くの通知に悩まされている方の一助となればと思います。

一緒に働く仲間を募集しています

herp.careers

スクラムマスターに俺はなる!......かもしれない

こんにちは、カトです。弥生でQAエンジニアをしています。
認定スクラムマスター(CSM)の資格を取得しました。

なぜスクラムマスター?

QAエンジニアの私がなぜ認定スクラムマスター(CSM)に手を出したのか。
3月下旬から7月中旬の4か月間の物語です。

2023年3月

弥生の開発本部は、4半期に1度、本部のキックオフを開催しています。
このキックオフの場で、「より速くお客さまに価値を提供する」というメッセージとともに、新規プロダクト開発はスクラム開発でやっていくという共有がありました。

キックオフの場と、その後スクラムの体制を確認した時に、スクラムチームに対して、以下の理解をしました。誤解はありますが、当時の理解をそのまま書きます。

  • スクラムチームは、プロダクトオーナー、スクラムマスター、開発者の3つの役割からなる
  • スクラムチームに所属する開発者はフルスタックでなければいけない

「QAエンジニア、どうすればよいのだろう?」
これが最初の疑問でした。
私はQAエンジニアで、普段の業務は、品質分析や、テスト計画・テスト設計・テスト実施などをしています。
製品に搭載できるようなコーディングのスキルは持っていませんし、UX検討を主導することもできません。私のスキルでは、スクラムチームの開発者にはなれなさそうです。

どうしよう、このままでは役立たずで捨てられてしまうかもしれない。

ダメな私は、現実から目を背けることにしました。

2023年4月

楽しく平凡に、QAエンジニアとして、品質分析し、テスト計画をしていました。
今まであらゆるラッキーだけで生き抜いてきたのだから、きっと今回も何とかなるでしょう。

youtu.be
speakerdeck.com
楽しいのが一番じゃないか。もくテクで発表もしました。



しかし、現実逃避していても、目からも耳からもスクラムの話が入ってきます。
そこで、意を決して現実と向き合うことにしました。

私には何ができるのか、未来をどうしたいのか。

私は弥生の各サービスの担当を渡り歩いてきています。弥生のサービスを広く知っていて、業務を進めるうえで社内の人脈をつくってきたと思います。

現在担当しているプロジェクトでは、がんばっているエンジニアたちのファイアウォール的な存在でありたいと考えています。
各方面からの要求や要望を整理し、調整し、チームの進捗に影響が出そうなことは事前に対処しておくことを目指しています。

私は遊軍のような存在で、チームの「こぼれタスク」に対応できる存在でありたいと考えています。
特別なスキルはいらないけれど、時間がとれない後回しにしている作業を引き受け、チームの「やりたいのにできていない。いつかやらなきゃいけない。」というストレスからの解放を目指しています。

tech-blog.yayoi-kk.co.jp

「遊軍」の詳細はこちら。



私の考えや行動は、「スクラムマスターは奉仕型リーダー」というところに通じるのではないかと考え始めました。
そこでスクラムマスターをよく知るべく、スクラムマスターの研修を探しました。そして、確認した時点で確定していた開催日のうち、都合がつく6月24日~25日の研修に申し込みをしました。
私が参加した研修はこちらです。
abi-agile.com

2023年5月

仙台に行き、JaSST '23 Tohokuに現地参加しました。
JaSSTのセッションでは、スクラムの中で活動しているQAエンジニア、スクラムの外から活動しているQAエンジニアの実践のお話しを伺いました。
QAエンジニアのままでも活動できるのではないか?という考えがよぎりました。しかし、スクラムマスターに関する知識をつけることがQAエンジニアにとってマイナスになることはないだろうと思いなおし、スクラムマスターを取り上げている書籍を読みました。

tech-blog.yayoi-kk.co.jp

JaSST '23 Tohoku参加ブログです。観光もしてきました。

2023年6月

研修に参加しました。
全体講義と、4人ずつのグループワークの時間がありました。

とても実りの多い研修でした。特に、以下の内容は、スクラムマスターにならなかったとしても、すぐに業務で活用できる内容でした。

  • ウォーターフォールかスクラムかの選択をする際には仕様変更が4%未満か以上かで判断
    • 「不確実性に対応する要素が多い対応かどうか」を一つの判断基準にすることができそう (「4%」の根拠や取得の方法については、研修では説明なし)
  • 見積り
    • 相対的なサイズで見積り、ポイント化する
    • チームがスプリントで対応できるポイント数の実績から、次のスプリントで対応できるポイント数をスプリント計画時に用いる
  • ハンドサイン
    • ミーティングの際に、次の話題に移るかもう少し深掘りするか、意見を求める際、手の動きで多数決をとる

私が参加したグループには、スクラムマスターとして活動されている人がいらっしゃいました。研修の合間に実際の業務の場面でのお話しを伺うこともできました。

2023年7月

研修の内容を復習して、認定試験を受けました。試験時間は60分で50問の択一問題です。自宅で好きな時間に受験可能です。
最初の1問目で迷ってしまって冷や汗をかきましたが、48問/50問正解で、無事に合格することができました。

初心者マークつきの認定スクラムマスターです。

スクラムマスターに関する情報は書籍やネットの情報でも得ることができます。
それでも、スクラムを体験できる研修を受講したことは、とてもよい経験だった考えています。
基礎となる知識を身につけました。知識をどう実践に活かしていくか、これが大事です。

今のスクラムに対する理解

スクラムは、プロダクトオーナー、スクラムマスター、開発者の3つの役割からなります。
開発者はなんでもできなければいけないと思い込んでいましたが、個人ですべての能力を兼ね備えている必要はありません。
チームとしてフルスタックであればよいのです。

ここで、思い浮かべたのは音楽でした。

ウォーターフォール開発

指揮者のいる合唱団のイメージです。
たくさんの歌い手(開発者)を取りまとめる、指揮者(マネージャー)がいます。
指揮者(マネージャー)の方針に意見がある場合は、演奏の最中ではなく、事前に合わせておく必要があります。 指揮者(マネージャー)の指示どおりの演奏をすれば、大きな間違いが起きることは少ないです。指揮者(マネージャー)の指示どおりに歌い進めることが大切です。
歌い手(開発者)それぞれのスキルは、合唱団の全体に影響します。

スクラム開発

バンドのイメージです。
一人でギターもベースもドラムもすべての楽器の演奏ができる必要はありませんが、チームとして揃っている必要があります。
ボーカル担当ではなくてもコーラスができたらよいですし、ギター・ベース・ドラムの他に、チームにキーボードができる人がいたら、このバンドで演奏できる範囲は広がります。
事前の擦り合わせも大事ですが、演奏しながら、お互いの目をみて、演奏を調整することも多くあります。

ウォーターフォール開発とスクラム開発のイメージってこんな感じ?

はたしてこの理解は正しいのでしょうか?
これから学習を進め、スクラムチームの実態をみながら、理解が変化した時に、また文章に残していきます。

今後の活動

スクラムマスターの研修をとおして、QAエンジニアとしてできることを考え直すきっかけになりました。
認定スクラムマスター(CSM)の資格を取得したからといって、すぐにスクラムマスターとして活動できるわけではないと思っています。
どのような役割だったとしても、「お客さまに価値を届け続ける」という目標は変わりません。私は私のできることを増やしていき、チームがより快適に働くことができること、お客さまにとって便利なサービスであり続けることを目指す活動をしていきます。

研修で学んだ「見積りのやり方」「開発者としての責任」については、すぐに実践に移すことができるので、業務に取り入れています。
このお話しはまた別の機会に。

弥生にはスクラム開発しているチームがあり、スクラムマスターたちの集いがあります。
夏の夜にスクラムマスターたちのお悩み相談を聞いてみませんか?
mokuteku.connpass.com

弥生では、スクラムマスター、品質に向き合い続けるQAエンジニアの募集をしています。
herp.careers

AWS re:Inforce 2023 reCapイベント参加で得たもの

情報システム部でAWS周りの運用保守をしている「ねぎ」です、こんにちは!

6月29日(木)にAWSさん主催のre:Inforce reCapイベントに参加しました。

aws-startup-lofts.com

本ブログ記事では、reCapイベントで得た内容について紹介していこうと思います。

まえがき

re:Inforceとは?

2023年6月13日~14日の2日間 カリフォルニア州アナハイムで開催されたAWS主催のセキュリティに特化したイベントです。

reinforce.awsevents.com

AWSを利用している全ての方がセキュリティを気にされているのではないでしょうか?

弊社もセキュリティに関しては注目しており、AWSさん主催のイベントにも参加して学んでいます。

tech-blog.yayoi-kk.co.jp

そんなセキュリティに関する大きなイベントであるre:InforceのreCapに参加して、タメになった内容をこの後紹介していきたいと思います。

イベント当日の流れとして、「AWS様からのセキュリティに関連したお話」、「サービスアップデート」、「LT」といった流れでした。

当日の流れに沿って、この後紹介していきます。

セキュリティを組織に浸透させるためには

セキュリティを組織に浸透させるのは大変、といった話が冒頭ありました(笑)

まず、セキュリティとコストは常に課題になるものである。

セキュリティを強化しようとすればコストがかかり、サービスや製品開発のスピードも落としてしまうかもしれない、といった考えがネックになり、どうしても後回しにされがちになってしまうといった紹介でした。

そんな状況の中で、どうやって浸透させるか?ですが、社内・外のプログラムでセキュリティ担当者を育成し、セキュリティのカルチャーを醸成していくといったものでした。

特に「セキュリティ」に関しても各開発チーム内で担当者を入れてく、という話があったので、この点はまだまだ実践に至ってないなと聞いていて感じました。

サービスアップデート(一部気になったもの)

各種AWSサービスのアップデートです。

いつも色んな機能がアップデートされるので、お話を聞いた中で特に気になったものだけをピックアップしています。

Amazon EC2 Instance Connect Endpoint

アップデート概要:パブリックIP不要でSSH / RDP可能

docs.aws.amazon.com

おー、ついに来たか、という感じでした。

EC2に踏み台サーバー経由で活用しているような場合は、こちらのエンドポイントを作成すれば、踏み台サーバー不要で直接EC2へ接続出来るため、良いアップデートだったのではないでしょうか?

もっと早く欲しかったな、という感想と共にECSとかSSMを利用しないポートフォワーディングの踏み台とか、そこまでアップデートされると弊社でも利用が進むなと思いました。

Amazon Inspector Code Scans for AWS Lambda

アップデート概要:インジェクション脆弱性、データ漏洩、暗号化の欠如などを検出可能

aws.amazon.com

Inspectorどんどん範囲広がるなー。EC2、ECS、そしてlambda。

残念ながら弊社では現状はそこまでInspectorを活用してないですが、AWSのサービスを活用することを優先する方針の場合は、こちらの機能が一般利用可能になったのは良いアップデートだったのではないでしょうか。

Findings Groups for Amazon Detective

アップデート概要:InspectorのNW到達可能性、ソフトウェアの脆弱性検出結果を含むように検出結果グループ機能を拡張

aws.amazon.com

Detectiveは関連性のあるリソースをビジュアライズしてくれるため、何か起きた時に調査がしやすく、とても便利だと思って重宝しています。

検出結果グループ

上記の画像ではGuardDuty検知時に関連していたリソースがグルーピングされています。

表示されているアイコンを押すと、どのリソースが該当していたのか詳細を追っていくことが可能になります。

Detectiveの検出結果グループ機能がリリースされる前に、同じようなビジュアライズをしてくれるSaaSサービスを見つけて導入しようと検討していましたが、似たような機能をリリースしてくれたので、Detectiveを活用する方針にしました。

Guard Duty Findings Summary View

アップデート概要:検出結果をダッシュボード化、最も優先度が高く、影響を受けたと思われるアカウントの調査をサポート

Guard Duty見やすくなりましたよね。

aws.amazon.com

今までは以下のように検知された一覧しか表示されませんでしたが(一部マスキングしています)

検出結果一覧

Summary Viewを表示すると、重要度の高い検出結果や、検出が多いリソースなどが表示され、非常に見やすくなりました。(一部マスキングしています)

直近30日は弊社の環境で重要度の高い検出結果が無くて安心しました(笑)

Guard Duty Findings Summary View

こちらもどんどん検出してくれる関連サービスが増えるので、重宝しています。

マルチアカウント環境への適用も簡単で、弊社では新規AWSアカウント発行時にデフォルト有効化されるよう設定済みです。

AWS Elastic Disaster RecoveryがVPC configurations recover機能を追加

アップデート概要:DR元にNW環境の変化があっても、DR先に同じ設定が可能

こちらはまだ利用したことなかったのですが、今後活用してみたいと思ったアップデートでした。

aws.amazon.com

災害対策する上で、VPCの設定をDR先と同じ設定してくれるということなので、異なるリージョンを災害対策リージョンとして活用しやすくなったのかなと思っています。

LT

何人かre:Inforceに参加された他社の担当者がLTに登壇をされていました。

その中でもこれは活用できそうだなと思ったのが、「Temporary Elevated Access Management」です。

dev.classmethod.jp

弊社では、各チームから私が所属しているインフラチームに対し、日常的にこの「ユーザ」に、この「権限」を付与して欲しいと依頼が来ます。

これはAWSをマルチアカウント環境で運用しているどの企業でも発生しているのではないかと思います。

この運用に対して承認ワークフローで一時的なアクセス権付与を実装出来るようになるのが、「Temporary Elevated Access Management」通称「TEAM」です。

権限付与に関する1件1件の対応工数は微々たるものですが、AWSアカウント数が増えるにつれて権限付与の依頼数も増える傾向にあるので、無視できないくらいの工数が積みあがっている状況です。

そのためこのような仕組みを導入してみようという気持ちが強くなってきたところ、良いLTを聞くことが出来ました。

この機能をカスタマイズして、一時的な権限付与ではなく、恒久的な権限付与を承認ワークフロー化できるように今後社内で実現しようと思います。

あとがき

いかがでしたでしょうか。

re:Inforceは海外のイベントのため、気軽に参加は難しいと思いますが、後日こうしたイベントを開催してくれるのはとてもありがたいです。

セキュリティに関連したサービスアップデートの知識を得ることが出来たので、インプットで終わらずしっかりとアウトプットしていきたいと思います。

一緒に働く仲間を募集しています

herp.careers

もくテク「ライフも!ワークも!フルリモート弥生生活の紹介」を開催しました!

こんにちは。弥生でエンジニアをしている中尾です。

この記事では、6月15日(木)に開催されたもくテクの様子をご紹介します!

イベント概要

イベントページはこちら(※開催は終了)

mokuteku.connpass.com

ライフもワークもチャレンジしている弥生メンバーのお話を聞いてみませんか。 あなたがチャレンジできる環境が弥生にあるかもしれません!!

こんな方におすすめ
・プライベートの時間も大事にしつつ、仕事や技術にもチャレンジしたい方
・お子様と暮らしていてリモートワークに興味のある方

オフィス回帰の流れが取沙汰される昨今、実は弥生の開発本部では絶賛リモートワークを継続中です。
今回のもくテクでは、そんな話題の働き方、フルリモートワークについて語り合いました。

アーカイブをYouTubeで公開しているのでぜひご覧ください!

会社紹介の様子

最初のセッションは「弥生の組織と働き方の紹介」です。
次世代プロダクト開発チームでPMをされている髙瀬さんに紹介していただきました。

会社紹介の様子
会社紹介をしつつ、
働き方紹介の様子

リモートワークでどんな働き方をしているのかを紹介しました。
家族時間を含めた弥生社員の一日のスケジュールの紹介コーナーもあります。

筆者の推しポイント

  • リモートワークでの組織づくりの参考になります
  • 弥生の働き方が自分に合っているかわかります!

気になった方におすすめの記事

note.yayoi-kk.co.jp リモートワークについて開発本部社員にアンケートを取ってみた記事です。
もくテクでは触れなかった出社派の方のお話しも紹介されています!

座談会の様子

続きましてメインの座談会となります。

PM、テックリード、エンジニアなど幅広いメンバーが集う会となりました。
遠方で働いているメンバーにも参加していただきました!

当日お話ししたのは下記のようなトークテーマです。

トークテーマ

  • 仕事と家庭とのバランスに変化はあった?
  • (首都圏以外で働いているメンバーに対して)技術のキャッチアップはどうしてる?
  • 家庭/学習含めてどんなチャレンジを始めましたか?
  • 自宅の作業環境のこだわりは?
  • 出社することはある?
  • そうはいってもリモートワークで困っていることは?

座談会の一幕

気になる質問があった方はぜひアーカイブをご覧ください。
画像の様に左上に質問を表示しています!

筆者の推しポイント

  • リモートワークを利用したチャレンジはエンジニアなら刺激になること間違いなし!
  • ここ2,3年以内に入社したメンバーがほとんどなので、他社との比較を踏まえた話が聞けます
  • トークを通じてリモートワークがほとんどの会社の人間関係や普段の雰囲気が伝わってきます
  • 髙瀬さんのファシリテートが上手い!

次回のご案内

次回は7月の開催となります。

7月13日(木)開催予定:「快適なフルリモート弥生生活を支えるインフラ・ヘルプデスクの挑戦」

6月会の兄弟イベントとなっており、こちらもフルリモートワークを扱っています!

6月会と実はつながっているサムネイル
今回のイベントで紹介したリモートワーク生活を支えるインフラ、ヘルプデスクチームのみなさんにその裏側を語っていただきます!

6月会にも登壇いただいたインフラチームのテックリードの峯岸さんが再登壇です!
トークテーマ「自宅の作業環境のこだわりは?」ではインフラエンジニアならではのこだわりを語っており、次回もどんな話をしてくれるか楽しみです!

下記のイベントページで参加者募集中です。

mokuteku.connpass.com

おわりに

弥生では一緒に働く仲間を募集しています!
弥生で働くことに興味がありましたら、求人一覧をぜひご覧ください。
それではまた次のイベントで!

herp.careers

いくぜ、東北。いったよ、仙台。

こんにちは、カトです。弥生でQAエンジニアをしています。
5月26日に開催された、JaSST '23 Tohoku に現地仙台で参加しました。

仙台へは、東京駅から東北新幹線はやぶさに乗っていってきました。
東北新幹線はやぶさの停車駅は、東京 ⇒ 上野 ⇒ 大宮 ⇒ 仙台 です。
ちなみに、弥生株式会社の本社は秋葉原にあります。東京駅でJR山手線に乗ると、東京 ⇒ 神田 ⇒ 秋葉原 です。
秋葉原は(JR山手線で)東京から2駅。仙台は(東北新幹線で)東京から3駅。ほぼ一緒ですね。

JaSST '23 Tohoku

テーマはアジャイル

私は弥生で「スマート証憑管理」というサービスを担当しています。
サービスを開発するチームは、開発手法としてアジャイル開発を取り入れています。
ベータ版としてのサービス開始から1年ほどで、複数回リリースをしています。
スマート証憑管理 リリースノート| スマート証憑管理 サポート情報

試行錯誤し、たくさんの改善ができているチームではありますが、まだ課題はたくさんあります。
部門間やサービス間での共有、プロダクトやプロジェクトの全体像の把握といった課題に「ユーザーストーリーマッピング」で改善していきたいと考えていた私は、オンサイトならではのワークショップがあるという情報を見つけ、JaSST '23 Tohokuに現地参加することにしました。

Vacation Photo

「もっと対話をしなければ」「チケットやドキュメントですべてがわかるように書かなければ」と思っていた私にとって、すごく印象的な共有知のお話しでした。

参画しているプロジェクトでは、「開発本部がマーケティング本部や顧客サービス本部に伝え漏れた」「要求をあげているのになかなか実現されない」ということが発生しています。
「レビューで指摘されなかったが、実際には誤っていた」「見てもらったはずなのに伝わっていなかった」ということが改善点としてあがることもあります。
私は開発本部に所属していて、開発のメンバーとはミーティングしやすい環境です。
一方で、マーケティング本部や顧客サービス本部のメンバーとは日常的に会話できていないと感じています。
会話の少なさが問題だと思っていました。
どうやって場や時間をつくるのがよいのかを考えていて、それぞれの業務があるから日常的に会話ができる状態にするのは難しいしどうやって改善するのがよいのだろう?と迷っていました。

「チケットで作業単位で区切ってタスクをこなしているけれど、このチケットが全体像のどのパーツなのかわからないことがある」という声を聞いたこともあります。

私たちチームは、共通の"Photo"を見ることができているのだろうか?"Photo"の背景の情報を語れるだろうか?

JaSST '23 Tohokuに参加してから、チームで要求や要件の全体像を把握すること、連携するシステムを含めた仕組みを伝え共有することを意識しています。

計画実行型、自己責任、自己組織化

基調講演中、「付箋をスタッフが順に配ります。」という場面がありました。
現地にいた私は 「そうなんだ。待っている間、ツイートでもしようかな。」と思っていました。
「付箋をスタッフが順に配ります。」に対して「もっとはやく配り終える方法がないか?」という考えに至ることはありませんでした。
そのあと、「自分の分を自分で取りにきてください。」と言われ、「何かがおかしいぞ?」と思い始めました。

「配り方はどうしたらいいと思う?」と問われたら最適解を考えるかもしれません。しかし「配ります。」と言われたらそのまま受け取ってしまっていました。

「上司、リーダー、マネージャー、スクラムマスターが忙しそう。メンバーでできる仕事は、もっとメンバーに仕事をふっていいのに。」という意見を聞くこともあります。
それは一見、自発的な発言のようにも聞こえますが、誰かが計画して采配してくれるのを待っている状態ではないだろうか?と思い始めました。
「考える人」「作る人」と区切るのではなく、一緒に作業していくことが大事なのだということを感じました。
しかし、「アサインされるのを待つのではなく、仕事を取りにきてほしい。」と望んでも、チームがどこをめざしているのか、チームにどんな仕事があるのかわからなければ、取りにいくこともできないし、取りに行くタイミングも遅くなります。
情報を開示し共有すること、「自分がこのプロジェクトに参加することを選択している。そして、チームみんながそうであると信頼している。」と感じられるチームになること、これがスピードにつながっていくと信じて、一歩ずつ取り組んでいきます。

MVP

Most Valuable Playerではありません。Minimum Viable Productです。
現地では、付箋を使い、4人チームで2種類のワークショップを実施しました。

ワークでは、やることを洗い出したあとに、第一段階のリリース、第二段階のリリースと最小単位を考えていくことを体験しました。
私には、「機能が充実していないと売れない。」「あの機能も、この機能もほしい。」といろいろ詰め込んでしまって、開発やテストが大変になってしまった経験がありました。
「最低限必要な機能」をチームみんなで決めること、そして次の段階を見据えることで、今までの失敗を繰り返すことなく、MVPを意識したリリース計画ができそうです。

製品開発のプロジェクトは、私一人の意志だけで優先順位を決めることは難しかったので、勉強会もくテクのタスクを使って優先順位づけをしてみました。
開催回数を重ねるごとにタスクが増えてきて「やるべきことがたくさんある」状態でしたが、運営に関われる人数、運営対応できる工数を鑑みて、開催ごとにやるタスク・やらないタスクを選別できそうです。


最小単位は社内勉強会として開催、次に社外勉強会として最小限のやるべきことを追加、といった第一段階、第二段階に整理しました。
すべてのタスクが同列の優先度で一列に並んでいるように見えていたところから、だいぶすっきりしました。

今回は一人でタスクを整理しました。今後、チームで優先順位づけをすることで、目的の認識合わせ、作業優先度の確認、各タスクをやる理由も見えてきそうです。
別のプロジェクトでもユーザーストーリーマッピングを取り入れて、作業を整理していきます。

おまけ・仙台グルメ

JaSST '23 Tohoku へ出発前、5年前に開発者ブログへ投稿されていた記事を読みました。 tech-blog.yayoi-kk.co.jp 勉強会の現地参加は、地方グルメを満喫することもできます。
記事はしっかり読みました。紹介しているお店、1件も行けていませんが。

私が行ってきた、牛たんとずんだのお店を紹介します。

「たんや善治郎」牛たんとソーセージ
「村上屋餅店」胡麻・ずんだ・くるみ3種のセット

勉強会の現地参加は、勉強会の雰囲気を直接感じられるだけではなく、現地のグルメや観光もできて、おすすめです。
次はどの街にいこうか、今からわくわくしています。


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

ControlTowerランディングゾーンのバージョンアップを実施しました

情報システム部でAWS周りの運用保守をしている「ねぎ」です、こんにちは!

弥生のAWS環境を支えてくれているControlTowerについて、ランディングゾーンという各AWSアカウントのベースライン設定に関するバージョンアップを実施してみました。

docs.aws.amazon.com

今回はバージョンアップを実施した際に、つまずいた点やポイントなどを紹介していきたいと思います。

私と同じくAWS周りの運用保守を担当している「いまいずみ」さんに詳細を説明していただこうと思います。

まえがき

弥生のランディングゾーンは「2.9」のバージョンで利用を行っていました。
(2.9は2022年4月22日リリースされています)

ランディングゾーンの最新版は「3.1」で、2023年2月9日にリリースされていますので、今回のバージョンアップは約1年ぶりとなります。

docs.aws.amazon.com

2.9から3.1へバージョンアップする際の大まかな変更点は以下となり、それぞれ影響有無などを確認する必要がありました。
(早めに最新化しないとどんどん調査工数が膨らみますよね。。。)

  • Configでグローバルサービスの履歴を残すオプションがControlTowerのホームリージョンのみで有効になる

  • CloudWatch Logsの証跡が無くなる

  • ControlTower管理外のアカウントも証跡が取られる

  • リージョン拒否ガードレール(SCP)の設定

  • アクセスログ用S3バケットのサーバーアクセスログ記録の無効化

上記の各アップデート内容に追加して、中々手を出せてなかったCloudTrailの暗号化もアップデートにあわせて実施することにしました。

それでは以降、バージョンアップに関する苦労(?)話を「いまいずみ」さんからしていただこうと思います。

アップデート前影響範囲調査

  • 各アップデートでは以下のような変更点があり、弊社で変更点とその影響範囲を洗い出しました。

バージョン3.0 変更点

  • 証跡出力のS3パス変更
    • version 3.0 以前はメンバーアカウントにアカウントレベルでの証跡として作成されていたが、version 3.0 から組織レベルの証跡として管理されます。
      アカウントレベルの証跡のパス : `/organization id/...`
      組織レベルの証跡のパス : `/<organization id>/AWSLogs/<organization id>/...`
    • 証跡パスの変更に伴い、弊社のAWS環境で利用しているSIEM on Amazon OpenSearch Serviceに影響は特にありませんでした。
      • 設定変更は必要ありませんでした。(AWSのサポートから回答いただきました。)
    • アップデートを実施した日からS3のパスが変わるので、もし他に利用する場合は注意が必要と考えられます。
  • AWS ControlTowerのログ保存期間(最大15年)
    • ControlTowerアップデートの際に新しく設定できます。(オプション機能)
    • 弊社セキュリティポリシーに合わせ変更いたしました。
  • Security Hub Config.1の違反(既知のバグ
    • グローバルリソースの記録がホームコントロールタワーリージョンでのみ行われるConfigの変更が原因で発生します。
    • 対応が難しいので、アップデートで改善を期待。
  • 現状、弊社環境に影響のないアップデートは以下となります。
    • 各アカウントにあったCloudWatch Logsの証跡が無くなりました。
    • ControlTower管理外のアカウントも証跡が取られるようになります。
    • リージョン拒否ガードレールの設定(こちらについては今後検討予定です。)

バージョン3.1 変更点

  • ログ記録アカウントの Access Logging バケットにおけるサーバーアクセスログ記録を非アクティブ化するように更新
    • SecurityHubのAWS 基礎セキュリティのベストプラクティス v1.0.0のS3.9が失敗となります。

    AWS ControlTower アクセスログバケットのサーバーアクセスログを無効にすると、Security Hub は Log Archive アカウントのアクセスログバケットの結果を作成します。これは、[S3.9] S3 バケットのサーバーアクセスログを有効にする必要があるという AWS Security Hub ルールによるものです。Security Hub に従い、このルールの Security Hub の説明に記載されているように、この特定の結果を非表示にすることをお勧めします。追加情報については、「非表示の結果に関する情報」を参照してください。 (AWS公式docsから抜粋)

    • 抑制済みとすることを推奨しているが、100アカウントもあると途方もないので断念。。。。
    • 今後のアップデートで改善を期待。

CloudTrailログKMS暗号化による変更点

  • 事前に作業が必要になるのは暗号化による変更が大半でした。
  • S3暗号化により以下のサービスに影響が出ることが予想されました。
    • レプリケーションバケット
    • SIEM on Amazon OpenSearch Service
    • Quick Sight + Athena

弊社の環境

  • CloudTrailの分析ツールとして以下を利用しています。
  • CloudTrailログを保存しているS3をデータソースとして指定しており、暗号化の影響があるリソースとなります。
    • SIEM on Amazon OpenSearch Service

      SIEM on Amazon OpenSearch Serviceの構成図

    • Quick Sight + Athena

      Quick Sight + Athenaを使った弥生の環境

バージョンアップ前の準備

  • 前提条件
    • 弊社ControlTowerではCloudTrailのログはLog Archiveアカウントに保存され、SIEMやOpenSearchで利用するS3へレプリケーションを取っているような構成となります。

  • 暗号化の際の考慮事項
    • KMS暗号化をする際の各ポリシーの関係は以下の図のようになっております。
      各ポリシーの関係図
    • KMS
      • キーポリシーにLog ArchiveアカウントとAuditアカウントに対する権限付与が必要となります。
    • IAM
      • KMSを利用する必要のあるサービスへIAMポリシーでKMSの暗号化復号化権限が必要になります。
      • 今回利用しているサービスでKMSの権限を追加したサービスは以下となります。
        • ログ集約用S3からSIEM取り込み用S3へのレプリケーションで利用しているIAMロール
        • SIEM取り込み用S3からOpenSearchへ取り込むためのLambdaのIAMロール
        • QuickSightに付与されているIAMロール
    • S3
      • 今回KMSの権限を追加したIAMロールはすでにバケットポリシーを追加しているため、特に変更は実施いたしませんでした。
  • 準備手順
    1. 親アカウントでKMSを発行
      • kmsはキータイプを対称、キーの使用を暗号化および復号化で作成
        暗号化キーの指定
      • キーポリシーではCloudTrail, Config, LogArchiveアカウントとAuditアカウントの利用を許可するポリシーを記載
        {
            "Version": "2012-10-17",
            "Statement": [
                {
                "Sid": "Allow CloudTrail and AWS Config to encrypt/decrypt logs",
                "Effect": "Allow",
                "Principal": {
                    "Service": [
                        "config.amazonaws.com",
                        "cloudtrail.amazonaws.com"
                    ]
                },
                "Action": [
                    "kms:GenerateDataKey",
                    "kms:Decrypt"
                ],
                "Resource": "*"
                },
                {
                    "Sid": "Enable IAM User Permissions",
                    "Effect": "Allow",
                    "Principal": {
                        "AWS": "arn:aws:iam::[親アカウントID]:root"
                    },
                    "Action": "kms:*",
                    "Resource": "*"
                },
                {
                    "Sid": "Allow use of the key",
                    "Effect": "Allow",
                    "Principal": {
                        "AWS": [
                            "arn:aws:iam::[Log ArchiveアカウントID]:root",
                            "arn:aws:iam::[AuditアカウントID]:root"
                        ]
                    },
                    "Action": [
                        "kms:Encrypt",
                        "kms:Decrypt",
                        "kms:ReEncrypt*",
                        "kms:GenerateDataKey*",
                        "kms:DescribeKey"
                    ],
                    "Resource": "*"
                },
                {
                    "Sid": "Allow attachment of persistent resources",
                    "Effect": "Allow",
                    "Principal": {
                        "AWS": [
                            "arn:aws:iam::[Log ArchiveアカウントID]:root",
                            "arn:aws:iam::[AuditアカウントID]:root"
                        ]
                    },
                    "Action": [
                        "kms:CreateGrant",
                        "kms:ListGrants",
                        "kms:RevokeGrant"
                    ],
                    "Resource": "*",
                    "Condition": {
                        "Bool": {
                            "kms:GrantIsForAWSResource": "true"
                        }
                    }
                }
            ]
        }
      
    2. Log ArchiveアカウントとAuditアカウントへKMSのアクセス権限を追加
    3. Log ArchiveのCloudTrailログ用S3バケットからSIEM用S3バケットへのレプリケーションを実施しているため、該当のIAMロールへKMSのdecrypt/encrypt権限を追加
    4. SIEM on OpenSearchで利用しているLambdaのIAMロールへ作成したKMSの権限を付与

バージョンアップ実施

バージョンアップ後に発覚した内容

  • S3レプリケーションの設定で暗号化されているオブジェクトをレプリケーションしない設定となっていました。(恥ずかしながら設定あることも知りませんでした。。。)

    • 以下設定を変更すれば問題ありません。
    • 暗号化するためのKMSキーは親アカウントで作成したキーのarnを指定しています。
      暗号化されたオブジェクトをレプリケートする設定前
      暗号化されたオブジェクトをレプリケートする設定後
  • QuickSightでもCloudTrailを利用していることがエラーから発覚いたしました。

    • QuickSightでは作成時にIAMロールが自動的に作成されることもあり、権限については意識することは少ない。
    • 切り戻しとしてはIAMロールにKMSの復号化権限を付与して完了です。

あとがき

「いまいずみ」さんのバージョンアップ体験談、いかがでしたでしょうか。

バージョンアップをしたくても中々踏み出せない方がいらっしゃるのではないかと思います。

ControlTowerのランディングゾーンバージョンアップに向けて、本ブログが一助になれば幸いです。

引き続きAWS関連の情報を発信していけるよう、がんばっていきます。

一緒に働く仲間を募集しています

herp.careers

2023/07/13(木)開催の もくテク でインフラのお話しをしますので、こちらもぜひ。 mokuteku.connpass.com

Flutterで作るMisocaクライアント

Flutterで作るmisocaクライアント

こんにちは、弥生モバイルチームのtijinsです。
公式MisocaアプリはAndroid用iOS用がありますが、それぞれ別のコードベースで開発されています。
今回はFlutterで開発するとAndroid/iOSの両方で動作するという噂が本当なのか確認してみました。

Flutterについて

docs.flutter.dev

iOS/Androidの両方で動作する事を確認したいので、macOS用のSDKをインストールしました。
Flutterはクロスプラットフォーム開発が可能ですが、iOS用のパッケージを作成するにはmacOS(Xcode)が必要です。

Dart

FlutterアプリはDart言語で開発します。
インタプリタや中間言語でプラットフォーム互換を実現している訳ではなく、ネイティブコードに変換されているようです。

画面レイアウト

ネイティブアプリの開発と一番大きな違いは、画面レイアウトの作成だと感じました。
Flutterの画面レイアウトはDartのコードで行います。
Composeで開発した経験があれば違和感は少ないです。

Scaffold

一般的なAndroidアプリのレイアウトはScaffoldを使うと簡単です。 ScaffoldにはAppBar、 左上アイコン、メニューの表示等が含まれます。

レイアウトの基本

Flutterのレイアウトは各Widgetが持つConstraint(制約)により配置が決定されます。
Constraintは幅・高さの範囲を保持するオブジェクトです。

Widgetツリーのルートから末端まで下記を繰り返しレイアウトを決定します。

  1. 子Widgetが無い時、親から指定されたConstraintの範囲内で自身のサイズを決定します。
  2. 子Widgetがある時、親から受け取ったConstraintにPadding等を考慮したConstraintを子Widgetに通知します。
  3. 子Widgetからサイズ・位置を受け取り、子Widetのサイズを考慮し、自身のサイズを決定します。

Widgetのサイズは種類により決定方法が異なります。

子が1つのレイアウト

Widget 説明 自身のサイズ 子のサイズ
Container 基本のレイアウト 子が無い時は親と同じ。子が有る時は子を包含する最小サイズ child, alignの指定により大きさが変わる 0〜自身と同じ, constraintが指定されいる場合は固定値となる
Align,Center 右寄せ、左寄せ等ができる 最大化 0〜自身と同じ
Padding,ColoredBox パディングを与える 最小化 0〜自身と同じ
SizedBox 自身、子のサイズを固定 固定値を指定 自身と同じ(固定)
OverflowBox 親からはみ出して配置 任意の値を指定可能 0〜自身のサイズ
LimitedBox サイズを制限する ListView内などサイズの制約が無い場合のみ、サイズを指定できる。サイズが指定されている場合は親の制約を引き継ぐ 0〜自身のサイズ
SingleChildScrollView スクロールを可能にする 最小化 幅:自身と同じ、高さ:0〜無限大)

子が複数のレイアウト

Widget 説明 自身のサイズ 子のサイズ
ListView 子が複数のレイアウト 最大化 幅:自身と同じ、高さ:0〜無限大
Column 複数の子を縦に並べる 幅:最小化、高さ:MainAxisSizeで指定 幅:自身と同じ、高さ:0〜無限大
Row 複数の子を横に並べる 幅:MainAxisSizeで指定、高さ:最小化 幅:0〜無限大、高さ:自身と同じ
Expanded Column又はRowの内側で使用する Column内で使用する時: 高さ方向が最大になる。Row内で使用する時: 幅方向が最大になる。 自身と同じ
Flexible Column又はRowの内側で使用する Column内で使用する時: 高さ方向が0〜最大になる。Row内で使用する時: 幅方向が0〜最大になる。 0〜自身と同じ

Constraintsの動作

  • 最小化 包含する子要素を包む最小サイズになる

  • 最大化 親要素と同じ大きさになる

  • 無限大 double.infinityが指定されている場合を無限大として扱う。 例えばListView内に配置される要素の制約は、縦方向が無限大になっている(スクロールできる為)

LayoutBuilder

レイアウトツリーが動的に変わる場合に使います。

ConstraintLayout

組み込みWidgetではありませんが、ネイティブのConstraintLayoutと同等のレイアウトが可能なライブラリがあります。flutter_constraintlayout

ユーザー入力を受け取るWidget

Widget 機能
InkWell Clickable, Focusableにする マテリアルデザイン対応のレイアウト内で利用可能です。
GestureDetector タップやスワイプを検出する

マテリアルデザイン

マテリアルデザイン対応のWidgetは以下です。 https://docs.flutter.dev/development/ui/widgets/material

ElevatedButtonやCard等がマテリアルデザイン対応のWidgetです。
Container等、非マテリアルデザインWidgetにelevationを付ける場合はMaterial()で囲みます。

画面遷移

Named routesを定義し、名前ベースのナビゲーションを行います。
ネイティブアプリのActivityスタックや、Navigationと似た機能が利用可能です。
ナビゲーションのスタックを制御する事で、前画面に戻れなくする事も可能です。

  • pushNamed ナビゲーションスタックに遷移先の画面を追加します。
    バックキーで現在の画面に戻れます。

  • pushReplacementNamed 現在の画面を破棄してから、遷移します。 バックキーを使用しても、現在の画面には戻れません。

  • pushNamedAndRemoveUntil 第2引数に指定するラムダがtrueを返すまでナビゲーションのスタックを削除してから遷移します。
    第2引数に(_) => falseを指定するとスタックがクリアされます。

AppBarのホームボタン

ナビゲーションのスタックに2件以上の画面が積まれている場合、自動的に戻るボタンが表示されます。
スタックが空になると、戻るボタンは消えます。

引数の指定

pushNamed()等を実行する際argumentsに引数を指定可能です。

    // 引数を指定
    Navigator.of(context).pushNamed("InvoiceDetailPage", arguments: {"invoice": invoice});
   // 引数を参照
    var args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;

サンプルアプリの実装

https://github.com/standfirm/misoca-sample-flutter

サンプルアプリを動作させる為には、後述するアプリの登録と、クライアントID、シークレット、コールバックURLの設定が必要です。

使用したライブラリ

その他はpubspec.yamlを参照

プロジェクト構成

View → ViewModel(StateNotifier) → UseCase → Repository(APIやファイルにアクセスする)の構成にしています。

画面構成

  • ログイン画面
  • 請求書一覧
  • 請求書詳細
  • 請求書プレビュー

オプションメニュー等は公式アプリに合わせていますが、編集等は機能しません。 Flutterの調査用に作ったものなので、税率表示等は端折っています。

ログイン画面の作成

ログイン画面のスクリーンショット

ログイン画面では、Oauth2で認証してアクセストークンをSharedPreferencesに保存します

公式アプリに合わせてメールアドレス、パスワード入力フォームを配置していますが、APIv3では使用不可です。

TextFormFieldにInputDecorationを指定することで、ネイティブのTextInputLayoutを再現可能です。

InputDecorationにsuffixIconを指定することで、パスワードトグルを再現可能です。

Misoca API v3

MisocaはRESTful形式のAPIを公開しています。
APIは無料プランでも利用可能です。(ユーザー登録が必要です)

アプリの登録

APIの利用にはアプリケーションID、シークレットが必要です。 Misocaにログイン後、アプリケーション管理画面を表示して、アプリを登録します。

  • 名称は任意です。
  • コールバックURLは認証完了時にフックされるURLです。 AndroidManifest.xmlのIntent-Filterで捕捉可能なURLを指定します。 サンプルアプリではscheme部の一致で起動しています。

登録するとクライアントID、シークレットが払い出されます。

OAuth2認証

公式アプリはパスワードで認証していますが、APIv3はパスワード認証に対応していない為、一般ユーザーはOAuth2で認証します。

OAuth2の認証フロー(認可コードによる認証)は以下のようになっているので、アプリからブラウザの起動と、ブラウザからアプリの起動に対応する必要があります。

sequenceDiagram
  FlutterApp ->> WebBrowser: ブラウザを起動(クライアントID、コードバックURI、スコープ)
  WebBrowser ->> Misoca: ブラウザでアクセス https://app.misoca.jp/oauth2/authorize
  Misoca ->> WebBrowser: 認証画面を表示
  WebBrowser ->> Misoca: ユーザーが許可を選択
  Misoca ->> WebBrowser: 認可コード
  WebBrowser ->> FlutterApp: Intent-Filterによりアプリが起動(認可コード)
  FlutterApp ->> Misoca: https://app.misoca.jp/oauth2/token(認可コード)
  Misoca ->> FlutterApp: アクセストークン、リフレッシュトークン

ブラウザの起動

oauthの認可画面

url_launcherを使用してブラウザを起動します。 http://app.misoca.jp/oauth2/authorize に対し、クエリパラメータで以下を指定します。

ブラウザで承認をクリックすると、再びアプリに戻ります。

name value
response_type "code" 認可コードフローで認証する
scope "write" または "read"
client_id アプリ登録時に払い出されたID
redirect_uri アプリ登録時に指定したURL

認可コードの取得(URLからアプリの起動)

uni_linksを使用してコールバックURLから認可コードを取得します。
コールバックURLからアプリを起動するには、OSの機能を利用する必要がある為、Android、iOSで個別の設定が必要になります。 - Android

AndroidManifest.xmlにintent-filterを設定します。
AndroidManifestはFlutterアプリプロジェクトの以下にあります。 ./android/app/src/main/AndroidManifest.xml

サンプルアプリではschemeを利用して起動しますが、ホスト名等も利用可能です。

<application>
    <activity>
          <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="misocasample" />
            </intent-filter>
    </activity>
</application>
  • iOS

Info.plistにCFBundleURLTypesを指定します。
Info.plistはFlutterアプリプロジェクトの以下にあります。

./ios/Runner/Info.plist

<plist>
<dict>
<key>CFBundleURLTypes</key>
<array>
    <dict>
      <key>CFBundleTypeRole</key>
      <string>Editor</string>
      <key>CFBundleURLName</key>
      <string>jp.misoca.fluttersample</string>
      <key>CFBundleURLSchemes</key>
      <array>
        <string>misocasample</string>
      </array>
    </dict>
</array>
</dict>
</plist>

コールバックURLからクエリパラメータを取得する

https://pub.dev/packages/uni_links#usage アプリが停止している時と、起動済みの時で、コールバックURLの通知を受けるイベントが異なります。
アプリからブラウザに遷移したタイミングでアプリが停止される可能性がある為、両方で受け取れるようにします。

認可コードは、コールバックURLのクエリパラメータ部(name="code")に指定されています。

将来的に多目的のDeepLinkを処理できるように、ログイン画面ではなくMaterialAppのルートにDeepLinkを取得するコードを配置しています

アクセストークンの取得

認可コードを指定してアクセストークンを取得します。

通信(dio)

flutterには組み込みのhttp通信クラスがありますが、多機能なdioを利用します。 retrofitを使用するとGET, POST等の通信を簡単に実装可能です。

サンプルアプリでは、dioはシングルトンにしてアプリ全体で共有しています。 APIのBaseUrl等は個別にも指定できますが、dioに対して行う事でアプリ全体に適用されます。

アクセストークンの取得

POST http://app.misoca.jp/oauth2/token からアクセストークンを取得します。 BodyのFormデータに以下を指定します。

name value
client_id アプリ登録時に払い出されたID
client_secret アプリ登録時に払い出されたシークレット
grant_type "authorization_code"
redirect_uri アプリ登録時に指定したコールバックURL
code コールバックURLで受け取ったcode

レスポンスにはアクセストークン、リフレッシュトークンが含まれます

name value
access_token アクセストークン
refresh_token リフレッシュトークン

シリアライズ・デシリアライズ

アクセストークンはJson形式なので、エンティティクラスを作成してデシリアライズします。 Dartクラス⇔Jsonの変換はfreezedで行います。

OauthTokenのEntityクラス

Dartにはリフレクションが無いため、シリアライズ処理がビルド時に生成されます。

flutter pub run build_runner watch

をバックグラウンドで実行しておくと、ファイルの変更を検知してシリアライズ処理を自動で生成してくれます。
エンティティクラスに対応するコードが生成される前はコンパイルエラーが出た状態になっているので、早めに生成しておく方がよいです。

アクセストークンの永続化

shared_preferencesを使用してアクセストークン、リフレッシュトークンをファイルに保存します。

アクセストークンの更新

アクセストークンには有効期限がある為、有効期限切れ時には、アクセストークンを更新する必要があります。

POST http://app.misoca.jp/oauth2/token から更新されたアクセストークンを取得します。 - Headerに以下を指定します。

name value
Authorization "{アプリケーションID}:{シークレット}"をBase64エンコードしたもの
Request-Type "refresh"
  • BodyのFormデータに以下を指定します。
name value
grant_type "refresh_token"
refresh_token 認証時に取得したリフレッシュトークン

AuthorizationInterceptor

サンプルアプリではDioのInterceptorを利用して、アクセストークンの有効期限が切れた時に、更新しています。

AuthorizationInterceptor

認証完了時の処理

通信など非同期処理の結果を利用する実装ではStateNotifierが便利です。

StateNotifierクラスにあるメソッドの実行

ref.read()を使用するとstateNotifierのメソッドを実行可能です。

ref.read(stateNotifierProvider.notifier).getData();

StateNotifierをObserveして非同期処理の完了を監視する

ref.watch(), またはref.listen()を使用して状態の変更を監視します。

https://github.com/standfirm/misoca-sample-flutter/blob/main/lib/main/main_app.dart#L46

      ref.listen(loginProvider, (previous, next) {
        if (next.status == NetworkStatus.success) {
          Navigator.of(context).pushReplacementNamed(InvoiceListPage.routeName);
        }
      });

StateNotifierの値が更新されるとlistenに指定したコールバックが実行されます。

請求書一覧画面への遷移

Navigatorクラスを使用して、画面遷移します。

          Navigator.of(context).pushReplacementNamed(InvoiceListPage.routeName);

画面更新のコンフリクト

画面更新中に非同期処理の完了が重なった場合に、画面更新がコンフリクトして例外が発生する場合があります。 以下のコードで、処理を描画完了後にスケジューリング可能です

    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      // post process
    });

請求書一覧画面の作成

請求書一覧画面

請求書一覧の取得

アクセストークンの指定

Misocaの各APIの実行にはアクセストークンの指定が必要です。
サンプルアプリではDioのInterceptorを使用してAuthorizationヘッダーを指定しています。

AuthorizationInterceptor
Interceptorの登録

GET /api/v3/invoices

GET http://app.misoca.jp/api/v3/invoices で請求書一覧を取得できます。

サンプルアプリではInvoiceのEntityクラスを作成してデシリアライズしています。
ネストされたクラスも問題なくデシリアライズされます。

通信結果の表示(ListView.builder)

ListView.builder()を使用します
ListView(Widget[])では、行数分のWidgetインスタンスが作成される為、数千行〜数万行あるような画面だと、非常に重くなってしまいます。
ListView.builder()は画面表示されている行のインスタンスのみが生成される為、重くなりません。

一覧画面の作成にはflutter_constraintlayoutを使用しました。Column,Rowで表現し難い画面だとConstraintLayoutは便利です。

区切り線

区切り線は decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.black, width: 1))で指定します。

ListViewのクリックで画面遷移

FlutterのListViewにはonItemClickのようなイベントがありません。
行に対してonClickイベントを設定するには、行のルートとなるWidgetにイベントを指定します。(ネイティブアプリのRecyclerViewと同じです)

ルート要素はContainerになる事が多いと思いますが、ContainerはonClickイベントに指定していない為、InkWellWidgetを行のルートにして、InkWellにonTapイベントを指定します。
InkWellで囲んだ要素は自動的にfocusableにもなり、キーボード操作による選択・実行も可能になります。
InkWellはタップやフォーカスによる背景色の制御を行う為、子要素の背景色は指定しないようにします。

請求書詳細画面の作成

請求書詳細画面

  • 品目部分が増減する為ListViewにしています。(タイトルや請求先名が表示されている上部〜品目がListViewです)
  • 合計金額が表示される下部はMaterialを使用してelavationを付けています。
  • サムネイルにはPDFファイルを表示しています。

PDFの表示

flutter_pdfviewを使用してサムネイルの表示を行います。
flutter_pdfviewでは、ファイルをダウンロードし、ローカルのファイルを指定することでPDFが表示されます。

PDFファイルのダウンロード

Retrofitで大きなファイルをダウンロードする場合、ファイル全体をメモリ上に保持する必要があった為Dioのダウンロードを使用しました。

downloadメソッドはoptionでreceiveDataWhenStatusErrorをfalseにしておかないと、ErrorBodyが無い場合にクラッシュします。

サンプルの実装ではトークン更新後のリトライ時にもoptionが引き継がれるようにしています。

PDFファイルの共有

share_plusを使用して共有します。
共有機能はAndroid/iOSのネイティブ機能が実行されます。
APIリファレンスには記載されていませんがmimeTypeの指定も可能です。

Share.shareFiles(
  ["/file/to/invoice.pdf"],
  text: "${invoice.subject}",
  mimeType:"application/pdf"
);

Android/iOSでネイティブの共有が実行されます

BottomSheetDialogの表示

ネイティブのBottomSheetDialogに相当するものがFlutterにもあります。
builderで任意のWidgetを指定するとモーダルで表示されます。
ダイアログで行った結果(Ok,Cancelなど)は非同期の戻り値として受け取れます。

  void openIssueDialog(BuildContext context) async {
    var result = await showModalBottomSheet(context: context, builder: (_) => InvoiceBottomSheet());
    if (result != null) {
      Fluttertoast.showToast(
        msg: "$resultを選択しました",
        toastLength: Toast.LENGTH_SHORT,
      );
    }
  }

モーダルダイアログはScaffoldのツリーに属さない為、Materialデザインに対応する場合はルート要素をMaterialにする必要があります。

ログアウト

Navigatorによる画面遷移を行う差異、請求書詳細から一覧には戻れますが、ログイン・ログアウトした場合、元の画面には戻れなくしています

サンプルアプリでは、Navigatorの指定を以下のように使い分けています。

  • ログイン→請求書一覧: pushReplacementNamed
  • 請求書一覧→請求書詳細: pushNamed
  • 請求書詳細→請求書プレビュー: pushNamed
  • ログアウト: pushNamedAndRemoveUntil

感想

Misocaアプリの主要な機能はFlutterでも実装できそうです。
ネイティブと比べて開発効率が悪い事もないので、Androidアプリ開発の選択肢に入れられる気がしました。
FlutterはWeb, Windows, Linux等にも対応している事になっているのですが、ライブラリはAndroid/iOSのみ対応というのも多い印象です。

終わりに

Misoca APIはFlutter以外のフレームワークからも利用可能です。
オリジナルのMisocaクライアント開発に挑戦してみてくださいね。




herp.careers