TECH PLAY

電通総研

電通総研 の技術ブログ

822

これは 電通国際情報サービス アドベントカレンダー の 20日 目の記事です。 こんにちは、 電通国際情報サービス (ISID)の上羽優貴です。 ISIDは今年に新しくスマートシティや 自治 体DX、脱炭素化などの社会課題対応型ビジネスの推進を目的とした スマートソサエティセンター という新組織を立ち上げました。私はここに所属し、スマートシティ関連のビジネスを推進しています。本記事では、スマートシティを実現するための基盤ソフトウェアであるFIWARE(ファイウェア)について解説します。 スマートシティと都市OS 近年、日本におけるスマートシティへの取り組みが盛んになっています。 トヨタ が推進する ウーブンシティ は有名ですね。スマートシティを実現するうえで欠かせないのがICTやデータの活用です。その活用の分野は防災や安全、モビリティ、教育・医療など多岐にわたります。今現在も様々な都市においてICTやデータを活用した住民サービスの提供がなされています。 しかし、日本でのスマートシティの実現に向けては、これまで大きく3つの課題がありました。 1. サービスの再利用・横展開 分野や組織ごとに個別にシステムを構築することが多く、他の地域での再利用や横展開が困難 2. 分野間データ利活用 分野や組織ごとにデータが独立し、分野や組織を横断してデータを活用することが困難 3. 拡張性の低さ 個別にシステムが構築されるため独自の仕様を持ち、機能拡張にかかるコストが大きく 、サービスを拡張させることが困難 そこで、近年注目を集めているのが都市OSというソフトウェア基盤です。都市OSは以下のような特徴を持ち、これまで課題だったサービスの再利用や、分野間のデータ利活用を促進する効果を期待されています。 1. 相互運用(つながる) 共通の機能や標準的なインタフェース( API )を具備して外部に公開する仕組みを持ち、分野や組織をまたがるサービス連携や横展開を可能にします。 2. データ流通(ながれる) スマートシティに利用するデータを仲介する機能を持ち、分野や組織を超えたデータ活用を可能にします。 3. 拡張容易(つづけられる) 機能間の 疎結合 なシステム構成を持ち、システムの拡張を容易にします。 都市OSを構築するためのソフトウェアの1つとして、この記事で紹介するFIWAREが挙げられます。 ※ スマートシティガイドブック(本文)第2章1 p.33より引用 FIWAREとは FIWARE とはFI(Future Internet)WARE(SOFTWARE)の略で、 自治 体や企業などの業種を超えたデータ利活用やサービス連携を促すために開発されたソフトウェア群です。ソフトウェア群と表現した通り、FIWAREは1つのソフトウェアのことではなく、いくつかのソフトウェアの総称です。 OSS として GitHub に公開されています。 FIWAREは欧州のFI-PPPという官民連携プログラムで開発されました(FI-PPPについては こちら を参照ください)。その後、FIWAREを民間主導で推進するための FIWARE Foundation が設立されました。現在はTelefonica( GitHubリポジトリ でFIWAREの コンポーネント を公開)や NEC ( 加古川市 や 高松市 にFIWAREを導入)などが中心メンバーとなって推進しています。 FIWAREは都市OSを構築するために利用されます。都市OSとして利用されるのは、前述の都市OSの特徴を持つためです。具体的には以下のとおりです。 1. 相互運用(つながる) NGSI(Next Generation Service Interfaces)と呼ばれる API 標準規格を採用しています。標準規格の API により、分野や組織間のサービス連携やデータの利活用を可能にします。NGSIについては後述します。 2. データ流通(ながれる) FIWAREのコア コンポーネント である FIWARE Orion がデータを分散管理したり、データを中継したり機能を持ちます。 3. 拡張容易(つづけられる) 下図の通り、FIWAREを構成する コンポーネント はそれぞれが独立して動作する「マイクロサービス アーキテクチャ 」を採用しています。必要な機能を持つ コンポーネント を取捨選択することにより、システムの拡張を容易にします。 ※ GitHubより引用 FIWAREの コンポーネント FIWAREは多くの コンポーネント を持ち、それぞれを組み合わせて利用します。FIWAREを構成する コンポーネント はGeneric Enablers(GE)と呼ばれます。ここではGEのうち、代表的なものを挙げて簡単に説明します。 FIWARE Orion FIWAREのコア(必須)となる コンポーネント です。コンテキストブローカーとしてデータを保管・管理するための機能を持ち、NGSIでデータを公開できます。MongoDBがデータストアとして利用されます。Orionに保管されるデータのライフサイクルは基本的に短く、最新のデータにより上書き更新されます。例えばビルの電力データをOrionで管理することを考えたときに、Orionに保管されるのは現在のビルの電力のみで、その履歴は保管されません。 FIWARE Cygnus (Cygnus) Orionに登録されたデータを蓄積するための コンポーネント です。データストアとして PostgreSQL 、ElasticSearch、 AWS のDynamoDBなど幅広く対応しています。 Orionに登録されたデータの変更をNGSIで受け取り、その変更履歴をデータストアに登録します。 IoT Agent For Json (IoT Agent) 名前の通り、IoTのデータ収集にかかわるコンポ―ネントです。RabbitMQなどのMQTTブローカーに集められたデータを収集してNGSIに変換し、Orionに登録する機能を持ちます。 WireCloud データを可視化するための コンポーネント です。OrionからNGSIでデータを受け取り、地図やグラフなどを活用した ダッシュ ボードを通じてデータを閲覧できます。 開発ツール Dockerイメージ や Helmチャート が公開されています。 AWS やAzureといった パブリッククラウド のコンテナプラットフォームを利用してFIWAREをホストすることが可能になります。 実際にFIWAREを使ってみる ここからは実際にFIWAREを使ったデータ登録、データ取得といった手順を試してみます。この手順で利用するFIWAREの コンポーネント はOrionのみです。Orionを利用するにあたり、事前にNGSIの説明します。 NGSIとは NGSIは、 NEC を中心にOMA(Open Mobile Alliance)によって標準化された API の規格です。アップデートが継続的に行われ、NGSI-LD(Linked Data)と呼ばれる規格が最新です。ただし、FIWAREに実装されているNGSIのバージョンはNGSIv2(NGSI-LDの以前のバージョン)であり、 NGSI-LDは開発中 になります。そのため、ここではNGSIv2に関して説明します。 まずはNGSIv2のデータモデルについて、以下の json を例に説明します。これはNGSIv2の規格に則り表現された json です。 { " id ":" urn:ngsi-ld:Vehicle:A4567 ", " type ":" Vehicle ", " brandName ": { " type ":" Property ", " value ":" Mercedes " } , " isParked ": { " type ":" Relationship ", " value ":" urn:ngsi-ld:OffStreetParking:Downtown1 ", " metadata ": { " observedAt ": { " value ":" 2017-07-29T12:00:04 ", " type ":" DateTime " } , " providedBy ": { " type ":" Relationship ", " value ":" urn:ngsi-ld:Person:Bob " } } } } この json は Vehicle という車のオブジェクトを表しています。NGSIv2において、このオブジェクトのことをエンティティ(Entity)と呼びます。このEntityは、どのブランドであるかを示す brandName と、どこに駐車されているかを示す isParked という属性を持ちます。これら属性のことを、 アトリビュート (Attribute)と呼びます。さらに isParked 属性は metadata と呼ばれるその属性に付随する情報を持ちます。 observedAt というそのデータが変更された時間や、 providedBy というそのデータが誰によって変更された、といったような情報です。この情報のことを メタデータ (Metadata) このようにNGSIv2でのデータモデルはEntity・Attribute・Metadataの3つにより構成される下図のようなモデルです。 ※ こちらより引用 NGSIでは API Specification も定められています。 Orionでのデータ管理を試す こちら で公開されている手順を参考に、Orionでのデータ管理を試してみます。実行にあたり、 Docker 、 Docker Compose が必要になりますので事前にインストールお願いします。なお、コマンドの実行環境は bash になります。 まずは チュートリアル 用の リポジトリ からファイルを取得し、 環境変数 を設定してます。 環境変数 を設定後、コンテナを起動します。 # リポジトリのクローン git clone https://github.com/FIWARE/tutorials.Getting-Started.git cd tutorials.Getting-Started git checkout NGSI-v2 # 環境変数の設定 export $( cat .env | grep " # " -v ) # コンテナを起動 docker compose -p fiware up -d docker-compose.yml の中を確認すると、Orionのコンテナと、MongoDBのコンテナを起動することが確認できます。前述の通り、MongoDBはOrionのデータストアとして利用されるためです。これがFIWAREを始めるにあたっての最小構成となります。 次に、起動したOrionへ以下のリク エス トを投げてみてください。 curl ではなくても、Postmanなどの GUI ソフトウェアを利用しても構いません。 curl -iX POST \ ' http://localhost:1026/v2/entities ' \ -H ' Content-Type: application/json ' \ -d ' { "id": "urn:ngsi-ld:Store:001", "type": "Store", "address": { "type": "PostalAddress", "value": { "streetAddress": "Bornholmer Straße 65", "addressRegion": "Berlin", "addressLocality": "Prenzlauer Berg", "postalCode": "10439" }, "metadata": { "verified": { "value": true, "type": "Boolean" } } }, "location": { "type": "geo:json", "value": { "type": "Point", "coordinates": [13.3986, 52.5547] } }, "name": { "type": "Text", "value": "Bösebrücke Einkauf" } } ' NGSIv2のEntityを作成する API ( /v2/entities )に対してリク エス トを送信していることがわかります。このリク エス トにより、Orionには Store Entityが作成されます。 次に登録したEntityを取得する API を実行してみます。以下のコマンドを実行してください。 curl -G -X GET \ ' http://localhost:1026/v2/entities/urn:ngsi-ld:Store:001 ' \ -d ' options=keyValues ' NGSIv2のEntityを取得する API ( /v2entities/<Entity ID> )に対してリク エス トを送信していることがわかります。このリク エス トにより、Orionに登録されたEntityを取得します。なお、 options=keyValues というクエリが指定されていますが、これはAttributeの type と metadata を省略した形でレスポンスを得るためのオプションです。レスポンスとして、登録したEntityを取得できていることが確認できます。 { " id ": " urn:ngsi-ld:Store:001 ", " type ": " Store ", " address ": { " streetAddress ": " Bornholmer Straße 65 ", " addressRegion ": " Berlin ", " addressLocality ": " Prenzlauer Berg ", " postalCode ": " 10439 " } , " location ": { " type ": " Point ", " coordinates ": [ 13.3986 , 52.5547 ] } , " name ": " Bösebrücke Einkauf " } このように基本的な CRUD 操作のほか、都市OSの特徴である「データ流通(ながれる)」を実現するための機能を持ちます。Orionのデータが変更されたときにPush型で情報を通知する Subscription と呼ばれる機能や、外部のデータを仲介する Registration と呼ばれる機能などがそれに該当します。本記事では割愛しますが、リンク先で手順が公開されていますのでぜひ試してください。 おわりに 本記事ではスマートシティを実現する都市OSについて、また都市OSを構築するためのFIWAREについて紹介しました。 日本におけるFIWAREの状況として、 NEC 中心にコミュニティが形成され、日本語の記事も増えてきています。またいくつかの 自治 体にも導入され、今後ますます認知度も高まり、発展していくことが予想されます。そんな中、ISIDとしても個人としても、日本のスマートシティの分野に貢献できるよう情報発信を続けていきたいなと考えています。 最後までお読みいただきありがとうございました。 参考文献 スマートシティガイドブック スマートシティリファレンスアーキテクチャ FIWARE FIWARE GitHubリポジトリ Let's FIWARE https://www.slideshare.net/fisuda/fiware-context-information-management https://www.slideshare.net/fisuda/orioncontextbroker-ngsiv2-overview-for-developers-that-already-know-ngsiv1-20180615 執筆: @ueba.yuki 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
これは 電通国際情報サービス アドベントカレンダー の 20日 目の記事です。 こんにちは、 電通国際情報サービス (ISID)の上羽優貴です。 ISIDは今年に新しくスマートシティや 自治 体DX、脱炭素化などの社会課題対応型ビジネスの推進を目的とした スマートソサエティセンター という新組織を立ち上げました。私はここに所属し、スマートシティ関連のビジネスを推進しています。本記事では、スマートシティを実現するための基盤ソフトウェアであるFIWARE(ファイウェア)について解説します。 スマートシティと都市OS 近年、日本におけるスマートシティへの取り組みが盛んになっています。 トヨタ が推進する ウーブンシティ は有名ですね。スマートシティを実現するうえで欠かせないのがICTやデータの活用です。その活用の分野は防災や安全、モビリティ、教育・医療など多岐にわたります。今現在も様々な都市においてICTやデータを活用した住民サービスの提供がなされています。 しかし、日本でのスマートシティの実現に向けては、これまで大きく3つの課題がありました。 1. サービスの再利用・横展開 分野や組織ごとに個別にシステムを構築することが多く、他の地域での再利用や横展開が困難 2. 分野間データ利活用 分野や組織ごとにデータが独立し、分野や組織を横断してデータを活用することが困難 3. 拡張性の低さ 個別にシステムが構築されるため独自の仕様を持ち、機能拡張にかかるコストが大きく 、サービスを拡張させることが困難 そこで、近年注目を集めているのが都市OSというソフトウェア基盤です。都市OSは以下のような特徴を持ち、これまで課題だったサービスの再利用や、分野間のデータ利活用を促進する効果を期待されています。 1. 相互運用(つながる) 共通の機能や標準的なインタフェース( API )を具備して外部に公開する仕組みを持ち、分野や組織をまたがるサービス連携や横展開を可能にします。 2. データ流通(ながれる) スマートシティに利用するデータを仲介する機能を持ち、分野や組織を超えたデータ活用を可能にします。 3. 拡張容易(つづけられる) 機能間の 疎結合 なシステム構成を持ち、システムの拡張を容易にします。 都市OSを構築するためのソフトウェアの1つとして、この記事で紹介するFIWAREが挙げられます。 ※ スマートシティガイドブック(本文)第2章1 p.33より引用 FIWAREとは FIWARE とはFI(Future Internet)WARE(SOFTWARE)の略で、 自治 体や企業などの業種を超えたデータ利活用やサービス連携を促すために開発されたソフトウェア群です。ソフトウェア群と表現した通り、FIWAREは1つのソフトウェアのことではなく、いくつかのソフトウェアの総称です。 OSS として GitHub に公開されています。 FIWAREは欧州のFI-PPPという官民連携プログラムで開発されました(FI-PPPについては こちら を参照ください)。その後、FIWAREを民間主導で推進するための FIWARE Foundation が設立されました。現在はTelefonica( GitHubリポジトリ でFIWAREの コンポーネント を公開)や NEC ( 加古川市 や 高松市 にFIWAREを導入)などが中心メンバーとなって推進しています。 FIWAREは都市OSを構築するために利用されます。都市OSとして利用されるのは、前述の都市OSの特徴を持つためです。具体的には以下のとおりです。 1. 相互運用(つながる) NGSI(Next Generation Service Interfaces)と呼ばれる API 標準規格を採用しています。標準規格の API により、分野や組織間のサービス連携やデータの利活用を可能にします。NGSIについては後述します。 2. データ流通(ながれる) FIWAREのコア コンポーネント である FIWARE Orion がデータを分散管理したり、データを中継したり機能を持ちます。 3. 拡張容易(つづけられる) 下図の通り、FIWAREを構成する コンポーネント はそれぞれが独立して動作する「マイクロサービス アーキテクチャ 」を採用しています。必要な機能を持つ コンポーネント を取捨選択することにより、システムの拡張を容易にします。 ※ GitHubより引用 FIWAREの コンポーネント FIWAREは多くの コンポーネント を持ち、それぞれを組み合わせて利用します。FIWAREを構成する コンポーネント はGeneric Enablers(GE)と呼ばれます。ここではGEのうち、代表的なものを挙げて簡単に説明します。 FIWARE Orion FIWAREのコア(必須)となる コンポーネント です。コンテキストブローカーとしてデータを保管・管理するための機能を持ち、NGSIでデータを公開できます。MongoDBがデータストアとして利用されます。Orionに保管されるデータのライフサイクルは基本的に短く、最新のデータにより上書き更新されます。例えばビルの電力データをOrionで管理することを考えたときに、Orionに保管されるのは現在のビルの電力のみで、その履歴は保管されません。 FIWARE Cygnus (Cygnus) Orionに登録されたデータを蓄積するための コンポーネント です。データストアとして PostgreSQL 、ElasticSearch、 AWS のDynamoDBなど幅広く対応しています。 Orionに登録されたデータの変更をNGSIで受け取り、その変更履歴をデータストアに登録します。 IoT Agent For Json (IoT Agent) 名前の通り、IoTのデータ収集にかかわるコンポ―ネントです。RabbitMQなどのMQTTブローカーに集められたデータを収集してNGSIに変換し、Orionに登録する機能を持ちます。 WireCloud データを可視化するための コンポーネント です。OrionからNGSIでデータを受け取り、地図やグラフなどを活用した ダッシュ ボードを通じてデータを閲覧できます。 開発ツール Dockerイメージ や Helmチャート が公開されています。 AWS やAzureといった パブリッククラウド のコンテナプラットフォームを利用してFIWAREをホストすることが可能になります。 実際にFIWAREを使ってみる ここからは実際にFIWAREを使ったデータ登録、データ取得といった手順を試してみます。この手順で利用するFIWAREの コンポーネント はOrionのみです。Orionを利用するにあたり、事前にNGSIの説明します。 NGSIとは NGSIは、 NEC を中心にOMA(Open Mobile Alliance)によって標準化された API の規格です。アップデートが継続的に行われ、NGSI-LD(Linked Data)と呼ばれる規格が最新です。ただし、FIWAREに実装されているNGSIのバージョンはNGSIv2(NGSI-LDの以前のバージョン)であり、 NGSI-LDは開発中 になります。そのため、ここではNGSIv2に関して説明します。 まずはNGSIv2のデータモデルについて、以下の json を例に説明します。これはNGSIv2の規格に則り表現された json です。 { " id ":" urn:ngsi-ld:Vehicle:A4567 ", " type ":" Vehicle ", " brandName ": { " type ":" Property ", " value ":" Mercedes " } , " isParked ": { " type ":" Relationship ", " value ":" urn:ngsi-ld:OffStreetParking:Downtown1 ", " metadata ": { " observedAt ": { " value ":" 2017-07-29T12:00:04 ", " type ":" DateTime " } , " providedBy ": { " type ":" Relationship ", " value ":" urn:ngsi-ld:Person:Bob " } } } } この json は Vehicle という車のオブジェクトを表しています。NGSIv2において、このオブジェクトのことをエンティティ(Entity)と呼びます。このEntityは、どのブランドであるかを示す brandName と、どこに駐車されているかを示す isParked という属性を持ちます。これら属性のことを、 アトリビュート (Attribute)と呼びます。さらに isParked 属性は metadata と呼ばれるその属性に付随する情報を持ちます。 observedAt というそのデータが変更された時間や、 providedBy というそのデータが誰によって変更された、といったような情報です。この情報のことを メタデータ (Metadata) このようにNGSIv2でのデータモデルはEntity・Attribute・Metadataの3つにより構成される下図のようなモデルです。 ※ こちらより引用 NGSIでは API Specification も定められています。 Orionでのデータ管理を試す こちら で公開されている手順を参考に、Orionでのデータ管理を試してみます。実行にあたり、 Docker 、 Docker Compose が必要になりますので事前にインストールお願いします。なお、コマンドの実行環境は bash になります。 まずは チュートリアル 用の リポジトリ からファイルを取得し、 環境変数 を設定してます。 環境変数 を設定後、コンテナを起動します。 # リポジトリのクローン git clone https://github.com/FIWARE/tutorials.Getting-Started.git cd tutorials.Getting-Started git checkout NGSI-v2 # 環境変数の設定 export $( cat .env | grep " # " -v ) # コンテナを起動 docker compose -p fiware up -d docker-compose.yml の中を確認すると、Orionのコンテナと、MongoDBのコンテナを起動することが確認できます。前述の通り、MongoDBはOrionのデータストアとして利用されるためです。これがFIWAREを始めるにあたっての最小構成となります。 次に、起動したOrionへ以下のリク エス トを投げてみてください。 curl ではなくても、Postmanなどの GUI ソフトウェアを利用しても構いません。 curl -iX POST \ ' http://localhost:1026/v2/entities ' \ -H ' Content-Type: application/json ' \ -d ' { "id": "urn:ngsi-ld:Store:001", "type": "Store", "address": { "type": "PostalAddress", "value": { "streetAddress": "Bornholmer Straße 65", "addressRegion": "Berlin", "addressLocality": "Prenzlauer Berg", "postalCode": "10439" }, "metadata": { "verified": { "value": true, "type": "Boolean" } } }, "location": { "type": "geo:json", "value": { "type": "Point", "coordinates": [13.3986, 52.5547] } }, "name": { "type": "Text", "value": "Bösebrücke Einkauf" } } ' NGSIv2のEntityを作成する API ( /v2/entities )に対してリク エス トを送信していることがわかります。このリク エス トにより、Orionには Store Entityが作成されます。 次に登録したEntityを取得する API を実行してみます。以下のコマンドを実行してください。 curl -G -X GET \ ' http://localhost:1026/v2/entities/urn:ngsi-ld:Store:001 ' \ -d ' options=keyValues ' NGSIv2のEntityを取得する API ( /v2entities/<Entity ID> )に対してリク エス トを送信していることがわかります。このリク エス トにより、Orionに登録されたEntityを取得します。なお、 options=keyValues というクエリが指定されていますが、これはAttributeの type と metadata を省略した形でレスポンスを得るためのオプションです。レスポンスとして、登録したEntityを取得できていることが確認できます。 { " id ": " urn:ngsi-ld:Store:001 ", " type ": " Store ", " address ": { " streetAddress ": " Bornholmer Straße 65 ", " addressRegion ": " Berlin ", " addressLocality ": " Prenzlauer Berg ", " postalCode ": " 10439 " } , " location ": { " type ": " Point ", " coordinates ": [ 13.3986 , 52.5547 ] } , " name ": " Bösebrücke Einkauf " } このように基本的な CRUD 操作のほか、都市OSの特徴である「データ流通(ながれる)」を実現するための機能を持ちます。Orionのデータが変更されたときにPush型で情報を通知する Subscription と呼ばれる機能や、外部のデータを仲介する Registration と呼ばれる機能などがそれに該当します。本記事では割愛しますが、リンク先で手順が公開されていますのでぜひ試してください。 おわりに 本記事ではスマートシティを実現する都市OSについて、また都市OSを構築するためのFIWAREについて紹介しました。 日本におけるFIWAREの状況として、 NEC 中心にコミュニティが形成され、日本語の記事も増えてきています。またいくつかの 自治 体にも導入され、今後ますます認知度も高まり、発展していくことが予想されます。そんな中、ISIDとしても個人としても、日本のスマートシティの分野に貢献できるよう情報発信を続けていきたいなと考えています。 最後までお読みいただきありがとうございました。 参考文献 スマートシティガイドブック スマートシティリファレンスアーキテクチャ FIWARE FIWARE GitHubリポジトリ Let's FIWARE https://www.slideshare.net/fisuda/fiware-context-information-management https://www.slideshare.net/fisuda/orioncontextbroker-ngsiv2-overview-for-developers-that-already-know-ngsiv1-20180615 執筆: @ueba.yuki 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
ISID X(クロス) イノベーション 本部 の三浦です。本記事は 電通国際情報サービス Advent Calendar 2021 の19日目のポストです。 本記事では、Azure Web Appsでは簡単にモダンな開発ができることをキャプチャーを多めで説明します。 GUI からポチポチ設定でここまで簡単にできるということをお伝えするのが目的です。 (あまりインフラに詳しくない アプリ開発 者でも簡単にインフラ構築、運用ができるかもしれないといった内容です) 触れる技術要素 今回の模擬システムの説明 今回の環境でできること OpenID Connectによる認証(Office 365のユーザーでお手軽認証) の構築 webappsのディプロイスロットによる、ブルーグリーンディプロイ GitHub Actionによる継続的デリバリー まとめ 触れる技術要素 Azure Web Apps OpenID Connectによる認証(Office 365のユーザーでお手軽Single Sign On) ディプロイスロットによるブルーグリーンディプロイ GitHub ActionによるContinuous Delivery 今回の模擬システムの説明 Azure Web Apps + cognitive searchのシンプルなシステムです。 Azure Web Appsの機能で、 OpenID ConnectでSingle Sign Onし、 GitHub から、ステージング環境へ GitHub Actionで継続的デリバリーし、 GUI 上の操作で簡易にブルーグリーンディプロイをします。また、app insightsによる APM 監視、および、log analytics によるログ管理をしています。 今回のエントリでは用いてる技術のうち「 OpenID Connectによる認証」「ディプロイスロットによるブルーグリーンディプロイ」「 GitHub Actionによる継続的デリバリー」を紹介します。 今回の環境でできること Azure Web Apps の OpenID Connect認証による簡単な Office 365ユーザーで Single Sign On(AzureAD) ブルーグリーンディプロイによる停止時間の短縮、障害時の高速切り戻し GitHub acitionによる継続的デリバリー OpenID Connectによる認証(Office 365のユーザーでお手軽認証) の構築 下記のように GUI 操作していくだけで、簡易にOffice 365のユーザーで認証が可能となります。 ここの設定を間違えると、認証してないユーザー、全世界のAzureADのユーザーにこのシステムが公開されてしまうので注意です。 このように設定すると、webapp側にRelying Party としての設定が完了、AzureAD側に OpenID Provider としてのリソースが作成されています。 では、AzureADに遷移し、 OpenID Providerとしての追加設定をします。 まず、プロパティを開き下記の項目を設定します。 デフォルトの「いいえ」の場合、そのテナントの全てのユーザーがwebapps にアクセス可能となります。「はい」の場合は後述の手順により、webapps単位でアクセスするユーザーを制限できます。 例えば、マルチテナント的に複数の顧客をゲストユーザーとして登録しているテナントで、「いいえ」で設定してしまうと、他社向けのシステムが見えてしまうといったことが起こります。 次に、このアプリケーションへのアクセス権を与えるユーザーを追加してます。 ここでポイントなのは、AzureADのゲストユーザーも追加可能であるということです。これは、他社のOffice 365ユーザーに対して、他社の管理者の作業なしにSingle Sign Onが可能だということです。顧客が普段使ってるOffice 365のユーザーでのSingle Sign Onが数分で構築できる強力な機能です。 また、社内向けシステムを検討する際にも、煩雑な情シスへのIDP関連の申請を省略して普段使っているOffice 365のID、MFAを用いたシステムが提供可能となります。 Single Sign Onの設定は開発者、ID管理側、双方に一定の知識が必要でトラブリやすい作業です。しかし、Azure Web Apps + azureADのゲストユーザーを用いることでこの作業を大幅に簡易化できます。 webappsのディプロイスロットによる、ブルーグリーンディプロイ では、次に、ブルーグリーンディプロイを設定します。Azure Web Apps には、ブルーグリーンディプロイを実現するための ディプロイスロット という仕組みがあります。 これを作成します。 「スロットの追加」を選択し、「次から設定を複製」で元のスロットを選ぶだけで、アプリケーション設定がコピーされます( 環境変数 、 java バージョン、 tomcat バージョンなど)。 また、待機スロット用のURL、サーバが作成されます。 できあがったディプロイスロットを入れ替えるには スワップ するだけです。 これにより、待機スロットで Java や Tomcat をバージョンアップし、動作確認が取れたら スワップ するといったことも可能になります。 スワップ 時には、下記のように変更差分をわかりやすく表示してくれます。 また、待機スロットに GitHub Actionから継続的デリバリーし、動作確認してから スワップ で本番環境にアプリケーションを展開することもできます。 なお、今回の手順で待機スロットを使うためには、『 OpenID Connectによる認証』の再作成が必要です。スロットの設定を複製するときに認証設定も複製されているのですが、この認証設定はURLと結びついているためこのままでは動作しません。再作成で、待機スロット用の認証を構成しましょう。 GitHub Actionによる継続的デリバリー ここまでの作業で認証付きの待機スロットが準備でき、安全にアプリケーションをディプロイできる環境が整いました。では、 GitHub Actionによる継続的デリバリーを設定します。 継続的デリバリーは待機スロットにし、待機スロットでディプロイ後アプリケーションの動作確認して、 スワップ で運用スロットに展開するといったながれとなります。 「ディ プロイセン ター」で、 GitHub を選択し、レポジトリ、ブランチ情報を入力します。 そうすると、mvnを前提とした GitHub acitonの設定ファイルが自動的に GitHub にコミットされます。 が、本プロジェクトではgraldeを使用していたので、適宜修正をします。 これだけで継続的デリバリーが構成されます。 まとめ Azureでは、このように、 GUI によりポチポチするだけでこのような環境を作成できます。 とある案件では、今回の手順をキャプチャーベースでDevのメンバーに展開することにより 「Devで簡単に巻き取れそうなので横展開は今後Devでやっていきます」 というありがたいお言葉をいただくことができました。 Azureをうまく使うことにより、 Ops の関与を非常に減らした運用ができるのではないか?と思います。 Ops に負担をかけずに運用したいDevの方、構築・リリース作業で手一杯になっている Ops の方は、Azure Web Appsを使った環境構築を試してみてはいかがでしょうか 執筆: @miura.toshihiko 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
ISID X(クロス) イノベーション 本部 の三浦です。本記事は 電通国際情報サービス Advent Calendar 2021 の19日目のポストです。 本記事では、Azure Web Appsでは簡単にモダンな開発ができることをキャプチャーを多めで説明します。 GUI からポチポチ設定でここまで簡単にできるということをお伝えするのが目的です。 (あまりインフラに詳しくない アプリ開発 者でも簡単にインフラ構築、運用ができるかもしれないといった内容です) 触れる技術要素 今回の模擬システムの説明 今回の環境でできること OpenID Connectによる認証(Office 365のユーザーでお手軽認証) の構築 webappsのディプロイスロットによる、ブルーグリーンディプロイ GitHub Actionによる継続的デリバリー まとめ 触れる技術要素 Azure Web Apps OpenID Connectによる認証(Office 365のユーザーでお手軽Single Sign On) ディプロイスロットによるブルーグリーンディプロイ GitHub ActionによるContinuous Delivery 今回の模擬システムの説明 Azure Web Apps + cognitive searchのシンプルなシステムです。 Azure Web Appsの機能で、 OpenID ConnectでSingle Sign Onし、 GitHub から、ステージング環境へ GitHub Actionで継続的デリバリーし、 GUI 上の操作で簡易にブルーグリーンディプロイをします。また、app insightsによる APM 監視、および、log analytics によるログ管理をしています。 今回のエントリでは用いてる技術のうち「 OpenID Connectによる認証」「ディプロイスロットによるブルーグリーンディプロイ」「 GitHub Actionによる継続的デリバリー」を紹介します。 今回の環境でできること Azure Web Apps の OpenID Connect認証による簡単な Office 365ユーザーで Single Sign On(AzureAD) ブルーグリーンディプロイによる停止時間の短縮、障害時の高速切り戻し GitHub acitionによる継続的デリバリー OpenID Connectによる認証(Office 365のユーザーでお手軽認証) の構築 下記のように GUI 操作していくだけで、簡易にOffice 365のユーザーで認証が可能となります。 ここの設定を間違えると、認証してないユーザー、全世界のAzureADのユーザーにこのシステムが公開されてしまうので注意です。 このように設定すると、webapp側にRelying Party としての設定が完了、AzureAD側に OpenID Provider としてのリソースが作成されています。 では、AzureADに遷移し、 OpenID Providerとしての追加設定をします。 まず、プロパティを開き下記の項目を設定します。 デフォルトの「いいえ」の場合、そのテナントの全てのユーザーがwebapps にアクセス可能となります。「はい」の場合は後述の手順により、webapps単位でアクセスするユーザーを制限できます。 例えば、マルチテナント的に複数の顧客をゲストユーザーとして登録しているテナントで、「いいえ」で設定してしまうと、他社向けのシステムが見えてしまうといったことが起こります。 次に、このアプリケーションへのアクセス権を与えるユーザーを追加してます。 ここでポイントなのは、AzureADのゲストユーザーも追加可能であるということです。これは、他社のOffice 365ユーザーに対して、他社の管理者の作業なしにSingle Sign Onが可能だということです。顧客が普段使ってるOffice 365のユーザーでのSingle Sign Onが数分で構築できる強力な機能です。 また、社内向けシステムを検討する際にも、煩雑な情シスへのIDP関連の申請を省略して普段使っているOffice 365のID、MFAを用いたシステムが提供可能となります。 Single Sign Onの設定は開発者、ID管理側、双方に一定の知識が必要でトラブリやすい作業です。しかし、Azure Web Apps + azureADのゲストユーザーを用いることでこの作業を大幅に簡易化できます。 webappsのディプロイスロットによる、ブルーグリーンディプロイ では、次に、ブルーグリーンディプロイを設定します。Azure Web Apps には、ブルーグリーンディプロイを実現するための ディプロイスロット という仕組みがあります。 これを作成します。 「スロットの追加」を選択し、「次から設定を複製」で元のスロットを選ぶだけで、アプリケーション設定がコピーされます( 環境変数 、 java バージョン、 tomcat バージョンなど)。 また、待機スロット用のURL、サーバが作成されます。 できあがったディプロイスロットを入れ替えるには スワップ するだけです。 これにより、待機スロットで Java や Tomcat をバージョンアップし、動作確認が取れたら スワップ するといったことも可能になります。 スワップ 時には、下記のように変更差分をわかりやすく表示してくれます。 また、待機スロットに GitHub Actionから継続的デリバリーし、動作確認してから スワップ で本番環境にアプリケーションを展開することもできます。 なお、今回の手順で待機スロットを使うためには、『 OpenID Connectによる認証』の再作成が必要です。スロットの設定を複製するときに認証設定も複製されているのですが、この認証設定はURLと結びついているためこのままでは動作しません。再作成で、待機スロット用の認証を構成しましょう。 GitHub Actionによる継続的デリバリー ここまでの作業で認証付きの待機スロットが準備でき、安全にアプリケーションをディプロイできる環境が整いました。では、 GitHub Actionによる継続的デリバリーを設定します。 継続的デリバリーは待機スロットにし、待機スロットでディプロイ後アプリケーションの動作確認して、 スワップ で運用スロットに展開するといったながれとなります。 「ディ プロイセン ター」で、 GitHub を選択し、レポジトリ、ブランチ情報を入力します。 そうすると、mvnを前提とした GitHub acitonの設定ファイルが自動的に GitHub にコミットされます。 が、本プロジェクトではgraldeを使用していたので、適宜修正をします。 これだけで継続的デリバリーが構成されます。 まとめ Azureでは、このように、 GUI によりポチポチするだけでこのような環境を作成できます。 とある案件では、今回の手順をキャプチャーベースでDevのメンバーに展開することにより 「Devで簡単に巻き取れそうなので横展開は今後Devでやっていきます」 というありがたいお言葉をいただくことができました。 Azureをうまく使うことにより、 Ops の関与を非常に減らした運用ができるのではないか?と思います。 Ops に負担をかけずに運用したいDevの方、構築・リリース作業で手一杯になっている Ops の方は、Azure Web Appsを使った環境構築を試してみてはいかがでしょうか 執筆: @miura.toshihiko 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
本記事は 電通国際情報サービス Advent Calendar 2021 の18日目のポストです。 (本記事の太字部分は、 GitHub Copilotで補完された箇所です。その仕組みや感想などは記事の後半で説明します。) こんにちは。ISID X イノベーション 本部の半田です。普段は 社内向けの構成管理サービスの運用 をやっています。 今年の6月末、 GitHub は「関数名とコメントから、関数の ソースコード を補完する」機能、 GitHub Copilot のテクニカルプレビューを開始しました。 このCopilot、私もここ2ヶ月くらい使用しているのですが、最近 ソースコード だけではなく日本語も補完してくれることに気づきました。 そこで今日は、この GitHub Copilotにブログの記事を書いてみてもらおう と思います。 Copilotで使われている技術などの内容はありませんが、気休め程度にお付き合いください。 ※なお、以下のパラグラフでは GitHub Copilotによる補完をそのまま掲載します。以下の文章には事実関係の誤りや、日本語の揺れがあることをご容赦 ください。 GitHub Copilotに日本語を書いてもらった その1 まずは GitHub Copilotに、 GitHub Copilot自身の説明をしてもらいましょう。あくまで補完のためのツールなので、テーマや書き出しの文は私から与える必要があります。 はじめまして。今日は、 GitHub Copilotに日本語を書いてもらったのです。 GitHub Copilotは、 GitHub の補完機能を日本語で使えるようにしたものです。 みなさんは、 GitHub Copilotを使っているのですか?私は、 GitHub の補完機能を使っているのですが、その補完機能は日本語を使えるようになったのです。 ところで、 GitHub Copilotは、 GitHub の補完機能を使っているのですが、その補完機能は日本語を使えるようになったのです。 ということで、 GitHub Copilotに日本語を書いてもらったのです。 ありがとうございます。 GitHub Copilotによる補完部分は、以下の太字の部分でした。 はじめ まして。 今日は 、 GitHub Copilotに日本語を書いてもらったのです。 G itHub Copilotは、 GitHub の補完機能を日本語で使えるようにしたものです。 みなさん は、 GitHub Copilotを使っているのですか? 私は 、 GitHub の補完機能を使っているのですが、その補完機能は日本語を使えるようになったのです。 ところで 、 GitHub Copilotは、 GitHub の補完機能を使っているのですが、その補完機能は日本語を使えるようになったのです。 という ことで、 GitHub Copilotに日本語を書いてもらったのです。 あ りがとうございます。 私が与えた文章はとても少ないですが、なんだかそれっぽい日本語が生成されてきました。ただ、内容はとても薄いですし、なんだか日本語の補完ツールだという説明になっていますね。この記事の前段から学習したことを、ほとんどそのまま言っているようです。 一方で、文章のつながりはとても自然で驚きました。接続詞は私が与えていますが、例えば「ところで」に続くセンテンスとして「○○ですが、」という言葉がきていますね。ほかにも、「という(ことで)」に続くセンテンスとして要点を簡潔に 表現しているのです。 ちなみに補完はこのように表示されます。 その2 さすがに接続詞だけだと、文章の中身としてはほとんど空っぽですね。 次はもう少し意図を持って文章を与えてみましょう。普段書いている プログラミング言語 を紹介したいと思います。 こんにちは。 みなさんは、普段どのような プログラミング言語 を使っていますか? 私は、 Python とTypeScriptを使っています。主にWebアプリケーションを作っています。 ほかにも、 JavaScript や PHP なども使っています。 普段の仕事を便利にするための自動化もしています。 例えば、 GitHub の監査ログを収集して、 Google BigQueryに保存しています。 この収集では、RxJSを使っています。RxJSは、 JavaScript のPromiseをラップして、非同期処理を楽にするためのライブラリです。 ほかにも、社内システムから出力される 工数 の実績データを Google Sheetsに保存して、 Google Data Studioで統計を表示しています。 メンバーがどのような業務をしているのかを知りたいときなどに使います。 このような簡単な処理を自動化するのに、 Python は非常に便利です。 こちらも答え合わせです。 こんにちは。 みなさんは、普段どのような プログラミング言語 を使っていますか? 私は Python とType Scriptを使っています。 主にWeb アプリケーションを作っています。 ほかにも、 JavaScript や PHP なども使っています。 普段の仕事を便利にするための自動化 もしています。 例えば、 GitHub の監査ログを収集して、 Google B igQueryに保存しています。 この収集では、R xJSを使っています。 R xJSは、 JavaScript のPromiseをラップして、非同期処理を楽にするためのライブラリです。 ほかにも、社内システムから出力される 工数 の実績データを Google Sheetsに保存して、 Google D ata Studioで統計を表示しています。 メンバーがどのような 業務をしているのかを知りたいときなどに使います。 このような簡単な処理を自動化するのに、 Python は 非常に便利です。 いかがでしょうか。私は PHP をあまり書きませんし、RxJSを「Promiseをラップして、非同期処理を楽にするためのライブラリ」と説明するのは多少危険な気がします。それでもずいぶん正確に文章を構成できていますね。 例えば「自動化も しています 」という部分では、まるで前半の文章に情報を並立していることを理解しているかのように「も」という助詞を使用していますね。 GitHub Copilotに日本語を書いてもらって感じたこと GitHub Copilotの「クセ」 最初は「へえ、日本語も補完するんだ」という驚きをモチベーションに記事を書き始めましたが、ここまで書いてみて GitHub Copilotにはいくつかの癖があるように感じました。少しまとめます。 ファイル内の文章も学習する ソースコード もファイル内のコンテンツを学習しているそうですが、日本語も同じように学習しているようですね。文章の前半より後半の方が、補完の精度は高くなっていきます。 試しに新しいファイルを開いて同じ文章を入力してみましたが、得られる結果は異なるものでした。 文章内にあるキーワードや自分やチームが持っている書き方の「クセ」を、ちゃんと学習してくれているようです。 一文を短く終わらせる傾向にある どの文章も、表示される補完は一文が短く終わっているなと感じました。主語と述語を簡潔に結んでいるような印象です。 「傾向にある」と書いたのは、まれにとてつもなく長い補完を表示したり、同じ文章が何度もループして表示されたりする事があったためです。 私は一文を長く書いてしまう傾向にあるので、この補完を見て「そろそろ文章を終わらせなきゃ」と焦ることもしばしばありました。 与えられた接続詞に対して、適切な助詞を補ったセンテンスを補完している…? これは驚きましたね。接続詞にどのような文章を続けるかというのは、時に人間であっても難しいことがあります。 もちろん完全に正しいセンテンスが表示されるわけではないのですが、どうやって意味の通る文章を生成しているのかはとても興味深いですね。AIってすごいです。 どう付き合っていくか? 今回は日本語の補完を題材にしましたが、 ソースコード の補完であっても、今回の補完に感じた違和感や誤りと同じようなものがあると思われます。 言わずもがなではありますが、 GitHub Copilotによる補完は意味的に正しいものを確実に補完してくれるわけではないことを理解する必要があります。 日本語であればパッと見てその意味があっているかどうかを確認できることも多いですが、例えば ソースコード で条件分岐の式を書くときなど、正しく補完されているかを意識して確認した方が良さそうです。 また、補完は基本的に過去のコードを学習して、それらしきものを表示してくれるものです。同じようなコードを書くような状況と、まったく新しいコードを書くような状況では、精度も異なるはずです。 これらのことから、基本的には「stackoverflowやQiitaなどの ソースコード をコピー&ペースト」する時と同じように扱うと良いのかなと考えています。 そのコードは正常に コンパイル できるのか、ランタイムエラーはでないのか、要件は満たせているのかなどを丁寧に確認できるようであれば、 GitHub Copilotの補完は強力な相棒になってくれるでしょう。 日本語の補完に使うときは、文末を補完してもらうのが良いかなと思います。 文頭からの補完でセンテンスの意味的な整合性を確保するのは難しいですが、文末を少し補完してもらえるだけでも結構便利です。文末につける言葉のバリエーションに悩んだときも、ア イデア をもらえてありがたいですよ。 あるときは驚くような高い精度の補完を、またあるときはクスッと笑えるような補完を提示してくれる GitHub Copilotですが、引き続き Microsoft Office の「イルカ」のようにかわいがってみようかなと思いました。 あとがき 記事を書いている途中、 GitHub Copilotからは 「そうですね、日本語も補完するのは面倒だ」という苦痛を感じました。 という補完を出してくれました。タイミングが良すぎて笑っちゃいました。 公式ドキュメント では、日本語を学習しているとは明記されていません。もし万が一にも GitHub Copilotで日本語を書きたい時は、どうぞ自己責任でお願いします。 執筆: @handa.kenta 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
本記事は 電通国際情報サービス Advent Calendar 2021 の18日目のポストです。 (本記事の太字部分は、 GitHub Copilotで補完された箇所です。その仕組みや感想などは記事の後半で説明します。) こんにちは。ISID X イノベーション 本部の半田です。普段は 社内向けの構成管理サービスの運用 をやっています。 今年の6月末、 GitHub は「関数名とコメントから、関数の ソースコード を補完する」機能、 GitHub Copilot のテクニカルプレビューを開始しました。 このCopilot、私もここ2ヶ月くらい使用しているのですが、最近 ソースコード だけではなく日本語も補完してくれることに気づきました。 そこで今日は、この GitHub Copilotにブログの記事を書いてみてもらおう と思います。 Copilotで使われている技術などの内容はありませんが、気休め程度にお付き合いください。 ※なお、以下のパラグラフでは GitHub Copilotによる補完をそのまま掲載します。以下の文章には事実関係の誤りや、日本語の揺れがあることをご容赦 ください。 GitHub Copilotに日本語を書いてもらった その1 まずは GitHub Copilotに、 GitHub Copilot自身の説明をしてもらいましょう。あくまで補完のためのツールなので、テーマや書き出しの文は私から与える必要があります。 はじめまして。今日は、 GitHub Copilotに日本語を書いてもらったのです。 GitHub Copilotは、 GitHub の補完機能を日本語で使えるようにしたものです。 みなさんは、 GitHub Copilotを使っているのですか?私は、 GitHub の補完機能を使っているのですが、その補完機能は日本語を使えるようになったのです。 ところで、 GitHub Copilotは、 GitHub の補完機能を使っているのですが、その補完機能は日本語を使えるようになったのです。 ということで、 GitHub Copilotに日本語を書いてもらったのです。 ありがとうございます。 GitHub Copilotによる補完部分は、以下の太字の部分でした。 はじめ まして。 今日は 、 GitHub Copilotに日本語を書いてもらったのです。 G itHub Copilotは、 GitHub の補完機能を日本語で使えるようにしたものです。 みなさん は、 GitHub Copilotを使っているのですか? 私は 、 GitHub の補完機能を使っているのですが、その補完機能は日本語を使えるようになったのです。 ところで 、 GitHub Copilotは、 GitHub の補完機能を使っているのですが、その補完機能は日本語を使えるようになったのです。 という ことで、 GitHub Copilotに日本語を書いてもらったのです。 あ りがとうございます。 私が与えた文章はとても少ないですが、なんだかそれっぽい日本語が生成されてきました。ただ、内容はとても薄いですし、なんだか日本語の補完ツールだという説明になっていますね。この記事の前段から学習したことを、ほとんどそのまま言っているようです。 一方で、文章のつながりはとても自然で驚きました。接続詞は私が与えていますが、例えば「ところで」に続くセンテンスとして「○○ですが、」という言葉がきていますね。ほかにも、「という(ことで)」に続くセンテンスとして要点を簡潔に 表現しているのです。 ちなみに補完はこのように表示されます。 その2 さすがに接続詞だけだと、文章の中身としてはほとんど空っぽですね。 次はもう少し意図を持って文章を与えてみましょう。普段書いている プログラミング言語 を紹介したいと思います。 こんにちは。 みなさんは、普段どのような プログラミング言語 を使っていますか? 私は、 Python とTypeScriptを使っています。主にWebアプリケーションを作っています。 ほかにも、 JavaScript や PHP なども使っています。 普段の仕事を便利にするための自動化もしています。 例えば、 GitHub の監査ログを収集して、 Google BigQueryに保存しています。 この収集では、RxJSを使っています。RxJSは、 JavaScript のPromiseをラップして、非同期処理を楽にするためのライブラリです。 ほかにも、社内システムから出力される 工数 の実績データを Google Sheetsに保存して、 Google Data Studioで統計を表示しています。 メンバーがどのような業務をしているのかを知りたいときなどに使います。 このような簡単な処理を自動化するのに、 Python は非常に便利です。 こちらも答え合わせです。 こんにちは。 みなさんは、普段どのような プログラミング言語 を使っていますか? 私は Python とType Scriptを使っています。 主にWeb アプリケーションを作っています。 ほかにも、 JavaScript や PHP なども使っています。 普段の仕事を便利にするための自動化 もしています。 例えば、 GitHub の監査ログを収集して、 Google B igQueryに保存しています。 この収集では、R xJSを使っています。 R xJSは、 JavaScript のPromiseをラップして、非同期処理を楽にするためのライブラリです。 ほかにも、社内システムから出力される 工数 の実績データを Google Sheetsに保存して、 Google D ata Studioで統計を表示しています。 メンバーがどのような 業務をしているのかを知りたいときなどに使います。 このような簡単な処理を自動化するのに、 Python は 非常に便利です。 いかがでしょうか。私は PHP をあまり書きませんし、RxJSを「Promiseをラップして、非同期処理を楽にするためのライブラリ」と説明するのは多少危険な気がします。それでもずいぶん正確に文章を構成できていますね。 例えば「自動化も しています 」という部分では、まるで前半の文章に情報を並立していることを理解しているかのように「も」という助詞を使用していますね。 GitHub Copilotに日本語を書いてもらって感じたこと GitHub Copilotの「クセ」 最初は「へえ、日本語も補完するんだ」という驚きをモチベーションに記事を書き始めましたが、ここまで書いてみて GitHub Copilotにはいくつかの癖があるように感じました。少しまとめます。 ファイル内の文章も学習する ソースコード もファイル内のコンテンツを学習しているそうですが、日本語も同じように学習しているようですね。文章の前半より後半の方が、補完の精度は高くなっていきます。 試しに新しいファイルを開いて同じ文章を入力してみましたが、得られる結果は異なるものでした。 文章内にあるキーワードや自分やチームが持っている書き方の「クセ」を、ちゃんと学習してくれているようです。 一文を短く終わらせる傾向にある どの文章も、表示される補完は一文が短く終わっているなと感じました。主語と述語を簡潔に結んでいるような印象です。 「傾向にある」と書いたのは、まれにとてつもなく長い補完を表示したり、同じ文章が何度もループして表示されたりする事があったためです。 私は一文を長く書いてしまう傾向にあるので、この補完を見て「そろそろ文章を終わらせなきゃ」と焦ることもしばしばありました。 与えられた接続詞に対して、適切な助詞を補ったセンテンスを補完している…? これは驚きましたね。接続詞にどのような文章を続けるかというのは、時に人間であっても難しいことがあります。 もちろん完全に正しいセンテンスが表示されるわけではないのですが、どうやって意味の通る文章を生成しているのかはとても興味深いですね。AIってすごいです。 どう付き合っていくか? 今回は日本語の補完を題材にしましたが、 ソースコード の補完であっても、今回の補完に感じた違和感や誤りと同じようなものがあると思われます。 言わずもがなではありますが、 GitHub Copilotによる補完は意味的に正しいものを確実に補完してくれるわけではないことを理解する必要があります。 日本語であればパッと見てその意味があっているかどうかを確認できることも多いですが、例えば ソースコード で条件分岐の式を書くときなど、正しく補完されているかを意識して確認した方が良さそうです。 また、補完は基本的に過去のコードを学習して、それらしきものを表示してくれるものです。同じようなコードを書くような状況と、まったく新しいコードを書くような状況では、精度も異なるはずです。 これらのことから、基本的には「stackoverflowやQiitaなどの ソースコード をコピー&ペースト」する時と同じように扱うと良いのかなと考えています。 そのコードは正常に コンパイル できるのか、ランタイムエラーはでないのか、要件は満たせているのかなどを丁寧に確認できるようであれば、 GitHub Copilotの補完は強力な相棒になってくれるでしょう。 日本語の補完に使うときは、文末を補完してもらうのが良いかなと思います。 文頭からの補完でセンテンスの意味的な整合性を確保するのは難しいですが、文末を少し補完してもらえるだけでも結構便利です。文末につける言葉のバリエーションに悩んだときも、ア イデア をもらえてありがたいですよ。 あるときは驚くような高い精度の補完を、またあるときはクスッと笑えるような補完を提示してくれる GitHub Copilotですが、引き続き Microsoft Office の「イルカ」のようにかわいがってみようかなと思いました。 あとがき 記事を書いている途中、 GitHub Copilotからは 「そうですね、日本語も補完するのは面倒だ」という苦痛を感じました。 という補完を出してくれました。タイミングが良すぎて笑っちゃいました。 公式ドキュメント では、日本語を学習しているとは明記されていません。もし万が一にも GitHub Copilotで日本語を書きたい時は、どうぞ自己責任でお願いします。 執筆: @handa.kenta 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
これは 電通国際情報サービス アドベントカレンダー の17日目の記事です。 はじめに 開発環境 ValueObject(値オブジェクト)とは? 実装方針のアプローチ 構造体として実装し、フィールドに値を保持し、必要な機能をメソッドで外部公開する defined typeとして実装し、必要な機能をメソッドで外部公開する 両アプローチの考察 データベースへの永続化 Value関数 Scan関数 実際にRDBに永続化してみる 値オブジェクトの実装 DB永続化処理 終わりに はじめに みなさんこんにちは。 電通国際情報サービス (ISID) 金融ソリューション事業部の水野です。 今回は、値オブジェクトをgo言語でどのように実装したかをご紹介します。 値の生成から振る舞いの実装、データベースへの永続化の際に如何に透過的に扱うかまでを見ていきます。 開発環境 Visual Studio Code 1.62.2 go 1.17 Docker Desktop 4.2.0 (内包されているDocker CLI を利用) 1 ValueObject(値オブジェクト)とは? Martin Fowlerさんの エンタープライズ アプリケーションアーキテクチャパターン で紹介されたのが最初だと理解しています。 「値オブジェクトとは何か」についての深堀りも非常に興味深いテーマですが、今回は以下と定義します。 イミュータブルである 不変条件が定義されており、条件を満たさない値では生成できない 特定の属性で等価性が定義される 値だけでなく、自身に属する機能を公開する 実装方針のアプローチ 以下の2つのアプローチを検討しました。 構造体 として実装し、フィールドに値を保持し、必要な機能をメソッドで外部公開する defined type で実装し、必要な機能をメソッドで外部公開する 上記どちらのアプローチでも、共通する戦略は以下です。 生成時にバリデーションを行い、エラー時は値を生成せずにerrorを返す 真偽値を返す等価性判定関数を実装する 自身が保持する値を使った何らかの操作を、メソッドで外部公開する イミュータブルに実装する方法は、構造体の場合はフィールドをエクスポートせず、値取得用のメソッドを設けて実現します。 defined typeとして実装する場合、基底にする型がイミュータブルかどうかに依存します。 構造体の場合、ボイラープレートとして値取得メソッドが毎回必要になるため、割り切ってフィールドをエクスポートしても良いと考えます。 その他の違いとしては、構造体だと複数の値を保持しやすいですが、defined typeだと単一の値の取り扱いが基本になります。 defined typeでも別の変数を保持することは可能ですが、やはりstringのような型として扱いたくなるでしょう。 例として、算術演算や税率算出などの機能を持つ「価格」という値オブジェクトを1.と2.で考えてみます。 構造体として実装し、フィールドに値を保持し、必要な機能をメソッドで外部公開する type Price struct { value int64 } func NewPrice(v int64 ) (Price, error ) { if err := isValidPrice(v); err != nil { ・・・エラーの場合の処理 } func isValidPrice(v int64 ) error { ...値の範囲チェックなど } func (price Price) Equal(other Price) bool { return price.value == other.value } func (price Price) CalcTax(rate uint ) Tax { ...省略} func (price Price) calcInternal(rate uint ) Tax { ...省略} // 内部処理用 value という構造体のフィールドに値を格納します。 defined typeとして実装し、必要な機能をメソッドで外部公開する type Price int64 func NewPrice(v int64 ) (Price, error ) { if err := isValidPrice(v); err != nil { ・・・エラーの場合の処理 } func isValidPrice(v int64 ) error { ...値の範囲チェックなど } func (price Price) Equal(other Price) bool { return int64 (price) == int64 (other) } func (price Price) CalcTax(rate uint ) Tax { ...省略} func (price Price) calcInternal(rate uint ) Tax { ...省略} // 内部処理用 両アプローチの考察 構造体による実装だと、Entityのような他の構造体のフィールドとして使用すると、構造体がネストすることになります。 今回は、 RDB への永続化時のシンプルさや、 JSON フィールドとして用いる際の取りまわしやすさから、defined typeで実装し必要な機能をメソッドで外部公開する方式を選択しました。 また、ValueObjectとして等価判定メソッドを実装する上では、以下のようなインタフェースを導入しても良いでしょう。 type ValueObject interface { Equal(other interface {}) bool } go 1.17の時点では ジェネリクス が未導入のため、上記のインタフェースでは毎回キャストする必要があります。 func (price Price) Equal(other interface {}) bool { val, ok := other.(Price) return ok && int64 (price) == int64 (val) } 余談ですが、レシーバの変数名は https://github.com/golang/go/wiki/CodeReviewComments#receiver-names では「It can be very short」と、非常に短い略称が理由と共に推奨されています。 ですが、レシーバ変数は関数内のローカル変数のスコープとしては最も広いものとなります。 また、ループカウンタの i や j と重複して意図せぬ シャドウイング を誘発したりと、短い変数名がゆえの弊害もあります。 そのため、レシーバの変数名には1, 2文字の略称ではなく、意味のある名称を割り当てています。 データベースへの永続化 defined type として実装する場合、基底型によっては意図した永続化が出来ず、動作をカスタムしたくなるケースがあります。 その場合、 Valuer インタフェースと Scanner インタフェースを実装します。 Valuerインタフェースの Value 関数は、ドライバから呼び出され、DBカラム型に対応する型に変換する役割を担います。 ScannerインタフェースのScan関数は、DBから取得したカラムの値を自身型に変換するための関数で、定められた特定の型から、ValueObject型への変換を実装します。 では、具体的に見ていきましょう。 Value 関数 シグニチャ は Value() (Value, error) と、非常にシンプルです。 前出のPrice型で実装するなら以下となります。 func (price Price) Value() (driver.Value, error ) { val := int64 (price) if val == - 1 { return NaNPrice, エラーオブジェクト } return int64 (price), nil } 上述のような実装にする必要はありませんが、int64のようなビルトイン型ではなく、固定小数10進表現を利用するケースなども考えられるため、例として無効な値が入っていた場合に無効値とerrorを返す疑似コードにしています。 Scan関数 Scan関数は若干特殊な仕組みで、ポインタを介して値をやり取りします。 そのため、ポインタレシーバとして実装する必要があります。 func (price *Price) Scan(value interface {}) error { switch v := value.( type ) { case int64 : if p, err := NewPrice(v); err == nil { *price = p return nil } else { return err } default : return errInvalidPrice } } 実際に RDB に永続化してみる RDB はPostgresSQLを使います。 今回は、Docker Desktopに内包されているDocker CLI で、最新の公式イメージでホストしつつ、デフォルトのpostgres スキーマ を使いました。 実際の開発では、アプリケーション用の スキーマ を別途用意する方が良いでしょう。 詳細は割愛しますが、 docker pull でイメージをダウンロードした後、 docker run するだけですぐに使えます。 PostgreSQL に接続し、非常にシンプルな以下の単価テーブルを作成します。 CREATE TABLE unit_price ( id serial PRIMARY KEY , lower_price bigint NOT NULL , upper_price bigint NOT NULL ); これで準備が整いました。サンプルなので、テーブル物理設計の妥当性は無視します。 PostgresSQLに永続化するにあたって、ドライバには pgx を利用します。 他のドライバに lib/pq がありますが、現在メンテナンスモードに入っており、公式ドキュメントではアクティブにメンテナンスされているpgxの利用が推奨( 2 )されています。 PostgreSQL を使うなら、今はpgxを使うのが良いでしょう。 実装した値オブジェクト Price を永続化するコードサンプルです。 税の算出などの機能を持たせていますが、紙面の都合上 RateやTaxの実装は省略しています。 値オブジェクトの実装 const ( minPrice, maxPrice = 0 , 9999999999999 NaNPrice = Price(- 1 ) ) type Price int64 func NewPrice(v int64 ) (Price, error ) { if err := isValidPrice(v); err != nil { return NaNPrice, err } return Price(v), nil } var ( errOutOfRangePrice = errors.New( "Price is out of range" ) errInvalidPrice = errors.New( "invalid Price" ) ) func isValidPrice(v int64 ) error { if v < minPrice && maxPrice < v { return fmt.Errorf( "Price must be between %d and %d, but [%d]: %w" , minPrice, maxPrice, v, errOutOfRangePrice) } return nil } func (price Price) Equal(other Price) bool { return int64 (price) == int64 (other) } func (price Price) String() string { return strconv.FormatInt( int64 (price), 10 ) } func (price Price) CalcTax(rate Rate) Tax { tax := float64 (price) * rate.AsPercentage() return NewTax(math.Floor(tax)) } func (price Price) Value() (driver.Value, error ) { if err := isValidPrice( int64 (price)); err != nil { return NaNPrice, err } return int64 (price), nil } func (price *Price) Scan(value interface {}) error { switch v := value.( type ) { case int64 : if p, err := NewPrice(v); err == nil { *price = p return nil } else { return fmt.Errorf( "invalid value [%v]: %w" , v, errOutOfRangePrice) } default : return errInvalidPrice } } func (price Price) EncodeBinary(ci *pgtype.ConnInfo, buf [] byte ) ([] byte , error ) { var numeric pgtype.Int8 if err := numeric.Set( int64 (price)); err != nil { return nil , err } return numeric.EncodeBinary(ci, buf) } Equal、String、CalcTaxというメソッドを定義しているのが分かりますね。 以下、実装におけるポイントを記します。 CalcTaxが ドメイン 固有処理 Value 、Scanはデータベースアクセスのために必要なメソッド pgx でカスタムされた型を使用するにはEncodeBinaryが必要 ( 3 参考) なため実装している pgxドライバを使うために、 github .com/jackc/pgx/v4と github .com/jackc/pgtypeを go getしている DB永続化処理 値オブジェクトを PostgreSQL に永続化する実装です。(エラー処理は意図的に省略しています) func main() { ctx := context.TODO() conn, _ := Connect(ctx, "postgres://postgres:testpass1@localhost:5432/postgres" ) defer func () { conn.Close(ctx) }() argLower, _ := value.NewPrice( 1000 ) argUpper, _ := value.NewPrice( 2500 ) conn.Exec(ctx, "INSERT INTO unit_price (lower_price, upper_price) VALUES ($1, $2)" , argLower, argUpper) // (1)挿入 rows, _ := conn.Query(ctx, "SELECT lower_price, upper_price from unit_price" ) // (2)選択 defer func () { rows.Close() }() for rows.Next() { var lower, upper value.Price rows.Scan(&lower, &upper) // (3)変数へ読み込み fmt.Printf( "lowerPrice=[%s], upperPrice=[%s]" , lower.String(), upper.String()) } } func Connect(ctx context.Context, connString string ) (*pgx.Conn, error ) { if conn, err := pgx.Connect(ctx, connString); err != nil { return nil , err } else { return conn, nil } } コード中の(1)で、値オブジェクトを引数にデータベースにINSERT文を発行しています。 デバッグ すると、以下のようにEncodeBinaryメソッドが呼び出されることが分かります。 (2)では、データベースから値を取得し、(3)でPrice値オブジェクトに読み込んでいます。 ここでは、Scanメソッドがドライバから呼び出されます。 構造体と結果セットを マッピング するライブラリを使えば、Entity構造体として以下のような実装も可能になります。 type UnitPrice struct { Id string `db:"id"` LowerPrice value.Price `db:"lower_price"` UpperPrice value.Price `db:"upper_price"` } 終わりに go言語で値オブジェクトを実装するための、一つの方法を紹介させていただきました。 全てstring, intなどのbuiltin型でいく戦略もあるので、これが正解と言うものではありません。 これからも試行錯誤しつつ、goでいろいろなアプリケーションを実装していきたいと思います。 最後までご覧になっていただき、誠にありがとうございました。 執筆: @mizuno.kazuhiro 、レビュー: @sato.taichi ( Shodo で執筆されました ) 個人利用は問題ありませんが、条件によっては2022年2月1日以降、有料になります。 https://www.docker.com/blog/updating-product-subscriptions/ ↩ https://github.com/lib/pq/blob/master/README.md#status ↩ https://pkg.go.dev/github.com/jackc/pgtype#example-package-CustomCompositeTypes ↩
アバター
これは 電通国際情報サービス アドベントカレンダー の17日目の記事です。 はじめに 開発環境 ValueObject(値オブジェクト)とは? 実装方針のアプローチ 構造体として実装し、フィールドに値を保持し、必要な機能をメソッドで外部公開する defined typeとして実装し、必要な機能をメソッドで外部公開する 両アプローチの考察 データベースへの永続化 Value関数 Scan関数 実際にRDBに永続化してみる 値オブジェクトの実装 DB永続化処理 終わりに はじめに みなさんこんにちは。 電通国際情報サービス (ISID) 金融ソリューション事業部の水野です。 今回は、値オブジェクトをgo言語でどのように実装したかをご紹介します。 値の生成から振る舞いの実装、データベースへの永続化の際に如何に透過的に扱うかまでを見ていきます。 開発環境 Visual Studio Code 1.62.2 go 1.17 Docker Desktop 4.2.0 (内包されているDocker CLI を利用) 1 ValueObject(値オブジェクト)とは? Martin Fowlerさんの エンタープライズ アプリケーションアーキテクチャパターン で紹介されたのが最初だと理解しています。 「値オブジェクトとは何か」についての深堀りも非常に興味深いテーマですが、今回は以下と定義します。 イミュータブルである 不変条件が定義されており、条件を満たさない値では生成できない 特定の属性で等価性が定義される 値だけでなく、自身に属する機能を公開する 実装方針のアプローチ 以下の2つのアプローチを検討しました。 構造体 として実装し、フィールドに値を保持し、必要な機能をメソッドで外部公開する defined type で実装し、必要な機能をメソッドで外部公開する 上記どちらのアプローチでも、共通する戦略は以下です。 生成時にバリデーションを行い、エラー時は値を生成せずにerrorを返す 真偽値を返す等価性判定関数を実装する 自身が保持する値を使った何らかの操作を、メソッドで外部公開する イミュータブルに実装する方法は、構造体の場合はフィールドをエクスポートせず、値取得用のメソッドを設けて実現します。 defined typeとして実装する場合、基底にする型がイミュータブルかどうかに依存します。 構造体の場合、ボイラープレートとして値取得メソッドが毎回必要になるため、割り切ってフィールドをエクスポートしても良いと考えます。 その他の違いとしては、構造体だと複数の値を保持しやすいですが、defined typeだと単一の値の取り扱いが基本になります。 defined typeでも別の変数を保持することは可能ですが、やはりstringのような型として扱いたくなるでしょう。 例として、算術演算や税率算出などの機能を持つ「価格」という値オブジェクトを1.と2.で考えてみます。 構造体として実装し、フィールドに値を保持し、必要な機能をメソッドで外部公開する type Price struct { value int64 } func NewPrice(v int64 ) (Price, error ) { if err := isValidPrice(v); err != nil { ・・・エラーの場合の処理 } func isValidPrice(v int64 ) error { ...値の範囲チェックなど } func (price Price) Equal(other Price) bool { return price.value == other.value } func (price Price) CalcTax(rate uint ) Tax { ...省略} func (price Price) calcInternal(rate uint ) Tax { ...省略} // 内部処理用 value という構造体のフィールドに値を格納します。 defined typeとして実装し、必要な機能をメソッドで外部公開する type Price int64 func NewPrice(v int64 ) (Price, error ) { if err := isValidPrice(v); err != nil { ・・・エラーの場合の処理 } func isValidPrice(v int64 ) error { ...値の範囲チェックなど } func (price Price) Equal(other Price) bool { return int64 (price) == int64 (other) } func (price Price) CalcTax(rate uint ) Tax { ...省略} func (price Price) calcInternal(rate uint ) Tax { ...省略} // 内部処理用 両アプローチの考察 構造体による実装だと、Entityのような他の構造体のフィールドとして使用すると、構造体がネストすることになります。 今回は、 RDB への永続化時のシンプルさや、 JSON フィールドとして用いる際の取りまわしやすさから、defined typeで実装し必要な機能をメソッドで外部公開する方式を選択しました。 また、ValueObjectとして等価判定メソッドを実装する上では、以下のようなインタフェースを導入しても良いでしょう。 type ValueObject interface { Equal(other interface {}) bool } go 1.17の時点では ジェネリクス が未導入のため、上記のインタフェースでは毎回キャストする必要があります。 func (price Price) Equal(other interface {}) bool { val, ok := other.(Price) return ok && int64 (price) == int64 (val) } 余談ですが、レシーバの変数名は https://github.com/golang/go/wiki/CodeReviewComments#receiver-names では「It can be very short」と、非常に短い略称が理由と共に推奨されています。 ですが、レシーバ変数は関数内のローカル変数のスコープとしては最も広いものとなります。 また、ループカウンタの i や j と重複して意図せぬ シャドウイング を誘発したりと、短い変数名がゆえの弊害もあります。 そのため、レシーバの変数名には1, 2文字の略称ではなく、意味のある名称を割り当てています。 データベースへの永続化 defined type として実装する場合、基底型によっては意図した永続化が出来ず、動作をカスタムしたくなるケースがあります。 その場合、 Valuer インタフェースと Scanner インタフェースを実装します。 Valuerインタフェースの Value 関数は、ドライバから呼び出され、DBカラム型に対応する型に変換する役割を担います。 ScannerインタフェースのScan関数は、DBから取得したカラムの値を自身型に変換するための関数で、定められた特定の型から、ValueObject型への変換を実装します。 では、具体的に見ていきましょう。 Value 関数 シグニチャ は Value() (Value, error) と、非常にシンプルです。 前出のPrice型で実装するなら以下となります。 func (price Price) Value() (driver.Value, error ) { val := int64 (price) if val == - 1 { return NaNPrice, エラーオブジェクト } return int64 (price), nil } 上述のような実装にする必要はありませんが、int64のようなビルトイン型ではなく、固定小数10進表現を利用するケースなども考えられるため、例として無効な値が入っていた場合に無効値とerrorを返す疑似コードにしています。 Scan関数 Scan関数は若干特殊な仕組みで、ポインタを介して値をやり取りします。 そのため、ポインタレシーバとして実装する必要があります。 func (price *Price) Scan(value interface {}) error { switch v := value.( type ) { case int64 : if p, err := NewPrice(v); err == nil { *price = p return nil } else { return err } default : return errInvalidPrice } } 実際に RDB に永続化してみる RDB はPostgresSQLを使います。 今回は、Docker Desktopに内包されているDocker CLI で、最新の公式イメージでホストしつつ、デフォルトのpostgres スキーマ を使いました。 実際の開発では、アプリケーション用の スキーマ を別途用意する方が良いでしょう。 詳細は割愛しますが、 docker pull でイメージをダウンロードした後、 docker run するだけですぐに使えます。 PostgreSQL に接続し、非常にシンプルな以下の単価テーブルを作成します。 CREATE TABLE unit_price ( id serial PRIMARY KEY , lower_price bigint NOT NULL , upper_price bigint NOT NULL ); これで準備が整いました。サンプルなので、テーブル物理設計の妥当性は無視します。 PostgresSQLに永続化するにあたって、ドライバには pgx を利用します。 他のドライバに lib/pq がありますが、現在メンテナンスモードに入っており、公式ドキュメントではアクティブにメンテナンスされているpgxの利用が推奨( 2 )されています。 PostgreSQL を使うなら、今はpgxを使うのが良いでしょう。 実装した値オブジェクト Price を永続化するコードサンプルです。 税の算出などの機能を持たせていますが、紙面の都合上 RateやTaxの実装は省略しています。 値オブジェクトの実装 const ( minPrice, maxPrice = 0 , 9999999999999 NaNPrice = Price(- 1 ) ) type Price int64 func NewPrice(v int64 ) (Price, error ) { if err := isValidPrice(v); err != nil { return NaNPrice, err } return Price(v), nil } var ( errOutOfRangePrice = errors.New( "Price is out of range" ) errInvalidPrice = errors.New( "invalid Price" ) ) func isValidPrice(v int64 ) error { if v < minPrice && maxPrice < v { return fmt.Errorf( "Price must be between %d and %d, but [%d]: %w" , minPrice, maxPrice, v, errOutOfRangePrice) } return nil } func (price Price) Equal(other Price) bool { return int64 (price) == int64 (other) } func (price Price) String() string { return strconv.FormatInt( int64 (price), 10 ) } func (price Price) CalcTax(rate Rate) Tax { tax := float64 (price) * rate.AsPercentage() return NewTax(math.Floor(tax)) } func (price Price) Value() (driver.Value, error ) { if err := isValidPrice( int64 (price)); err != nil { return NaNPrice, err } return int64 (price), nil } func (price *Price) Scan(value interface {}) error { switch v := value.( type ) { case int64 : if p, err := NewPrice(v); err == nil { *price = p return nil } else { return fmt.Errorf( "invalid value [%v]: %w" , v, errOutOfRangePrice) } default : return errInvalidPrice } } func (price Price) EncodeBinary(ci *pgtype.ConnInfo, buf [] byte ) ([] byte , error ) { var numeric pgtype.Int8 if err := numeric.Set( int64 (price)); err != nil { return nil , err } return numeric.EncodeBinary(ci, buf) } Equal、String、CalcTaxというメソッドを定義しているのが分かりますね。 以下、実装におけるポイントを記します。 CalcTaxが ドメイン 固有処理 Value 、Scanはデータベースアクセスのために必要なメソッド pgx でカスタムされた型を使用するにはEncodeBinaryが必要 ( 3 参考) なため実装している pgxドライバを使うために、 github .com/jackc/pgx/v4と github .com/jackc/pgtypeを go getしている DB永続化処理 値オブジェクトを PostgreSQL に永続化する実装です。(エラー処理は意図的に省略しています) func main() { ctx := context.TODO() conn, _ := Connect(ctx, "postgres://postgres:testpass1@localhost:5432/postgres" ) defer func () { conn.Close(ctx) }() argLower, _ := value.NewPrice( 1000 ) argUpper, _ := value.NewPrice( 2500 ) conn.Exec(ctx, "INSERT INTO unit_price (lower_price, upper_price) VALUES ($1, $2)" , argLower, argUpper) // (1)挿入 rows, _ := conn.Query(ctx, "SELECT lower_price, upper_price from unit_price" ) // (2)選択 defer func () { rows.Close() }() for rows.Next() { var lower, upper value.Price rows.Scan(&lower, &upper) // (3)変数へ読み込み fmt.Printf( "lowerPrice=[%s], upperPrice=[%s]" , lower.String(), upper.String()) } } func Connect(ctx context.Context, connString string ) (*pgx.Conn, error ) { if conn, err := pgx.Connect(ctx, connString); err != nil { return nil , err } else { return conn, nil } } コード中の(1)で、値オブジェクトを引数にデータベースにINSERT文を発行しています。 デバッグ すると、以下のようにEncodeBinaryメソッドが呼び出されることが分かります。 (2)では、データベースから値を取得し、(3)でPrice値オブジェクトに読み込んでいます。 ここでは、Scanメソッドがドライバから呼び出されます。 構造体と結果セットを マッピング するライブラリを使えば、Entity構造体として以下のような実装も可能になります。 type UnitPrice struct { Id string `db:"id"` LowerPrice value.Price `db:"lower_price"` UpperPrice value.Price `db:"upper_price"` } 終わりに go言語で値オブジェクトを実装するための、一つの方法を紹介させていただきました。 全てstring, intなどのbuiltin型でいく戦略もあるので、これが正解と言うものではありません。 これからも試行錯誤しつつ、goでいろいろなアプリケーションを実装していきたいと思います。 最後までご覧になっていただき、誠にありがとうございました。 執筆: @mizuno.kazuhiro 、レビュー: @sato.taichi ( Shodo で執筆されました ) 個人利用は問題ありませんが、条件によっては2022年2月1日以降、有料になります。 https://www.docker.com/blog/updating-product-subscriptions/ ↩ https://github.com/lib/pq/blob/master/README.md#status ↩ https://pkg.go.dev/github.com/jackc/pgtype#example-package-CustomCompositeTypes ↩
アバター
こんにちは、 電通国際情報サービス デジタル イノベーション 部の加世です。 昨今は、コンテナアプリケーション開発が主流であり、コンテナ オーケストレーション を理解することは非常に重要であると考えています。 そこで、コンテナ オーケストレーション ツールである「 Kubernetes 」をマネージド・サービス化した「 Amazon EKS」を題材として、 Amazon EKSの クラスタ ー認証・認可について理解します。 Kubernetes は、カスタマイズ性の高いコンテナ オーケストレーション ツールです。 正しく設計すれば「マイクロサービス化」「リソース最適化」「リリース最適化」「耐障害性に関わる制御の最適化」などを1つの統合された仕組みで実現できます。 これは、非常に魅力的です。 また、アプリケーション開発者が主体となって、サービス構成変更・運用を進められる点も素晴らしいです。 下記のガートナープレスリリースのとおり、コンテナー化の増加とともにコンテナー管理の需要が高まると考えています。 Kubernetes のようなコンテナ オーケストレーション ツールを始めるなら「今」です。 ■ガートナー プレスリリース https://www.gartner.com/en/newsroom/press-releases/2020-06-25-gartner-forecasts-strong-revenue-growth-for-global-co https://www.gartner.co.jp/ja/newsroom/press-releases/pr-20211118 はじめに Amazon EKSのクラスター認証・認可の概要について 認証 認可 検証環境の準備 Cognito構築 CloudShellの事前準備 EKS構築 「ClusterRoleBinding」によるRBAC認可の設定 OIDC認証で取得したトークンを利用したAPI操作 まとめ はじめに Amazon EKS クラスタ ー認証には「IAMによる認証(本記事では便宜上この方式を aws -auth 方式と呼びます)」「OIDCプロバイダーによる認証」の2つの方式があります。 本記事は、このうち以下の内容を記載します。 Amazon EKSの クラスタ ー認証・認可の概要を説明します。 Amazon EKSの「 OpenID Connect(OIDC)」認証の検証環境を構築します。 「OIDC」認証後に Kubernetes の API 操作ができることを確認します。 Amazon EKSの クラスタ ー認証・認可の概要について Amazon EKSの クラスタ ー認証・認可の概要を説明します。 Kubernetes コンポーネント と認証・認可の基礎は、下記リンク先を参照してください。 ■ Kubernetes の コンポーネント https://kubernetes.io/ja/docs/concepts/overview/components/ ■認証 https://kubernetes.io/ja/docs/reference/access-authn-authz/authentication ■RBAC認可を使用する https://kubernetes.io/ja/docs/reference/access-authn-authz/rbac 認証 Amazon EKS クラスタ ー認証は、「 aws -auth」「OIDC」の2つの方式があります。 IAMによる認証( aws -auth) ■ クラスタ ーのユーザーまたは IAM ロールの管理 https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/add-user-role.html Amazon EKSは、デフォルト設定でIAMユーザー/IAMロールに基づく認証機能が組み込まれています。 機能仕様は、 aws eks get-token コマンドで取得した トーク ンを「kube-apiserver」に渡して認証します。 ※Auditログを確認した限りでは、 ブラックボックス 化されたコン トロール プレーン内の「 kubernetes - aws -authenticator」プロセスが トーク ンを検証しているものと推察しています。なお、 AWS マニュアルに詳細な仕様は公開されていません。 OIDCプロバイダーによる認証 ■ OpenID Connect ID プロバイダーから クラスタ ーのユーザーを認証する https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/authenticate-oidc-identity-provider.html OIDCプロバイダーを用意して、「kube-apiserver」自体のOIDC認証機能(標準機能)を利用します。 複数 AWS アカウントで Amazon EKSを含めたサービスを運用する場合は、一元的なID統制・管理を目指すことによるメリットが大きくなるためこちらを検討します。 ※ Kubernetes 専用のOIDCプロバイダーになってしまう場合は、今後のID統合方針や運用コストなどを考慮して採用を検討します。 認可 認証が完了したら、「kube-apiserver」は次に Kubernetes の API 操作を許可するかをRBACで判断します。 その際に使用されるのが「Role/ClusterRole」「RoleBinding/ClusterRoleBinding」です。 なお、 Amazon EKSの認可は「 Kubernetes の認可」と同様です。 そのため、本記事では説明を省きます。 検証環境の準備 では、ここからOIDCを利用した Amazon EKS クラスタ ー認証を実際に試してみたいと思います。 検証環境は、下記の AWS Blogを参考に構築します。 なお、 AWS サービス(東京リージョン内)のみで検証を完了できるように構成しています。 ■ AWS Blog https://aws.amazon.com/jp/blogs/containers/introducing-oidc-identity-provider-authentication-amazon-eks 検証環境の構成 ※最低限の コンポーネント のみを示しています。 AWS サービス Amazon VPC 事前に、任意の VPC ネットワークを作成した状態を前提とします。 AWS IAM 事前に、EKSを初期作成するためのユーザー作成と権限付与をした状態を前提とします。 Amazon Cognito (User Pool) 補足情報を後述します。 Amazon EKS (ver 1.21) 補足情報を後述します。 ※ワーカーノードは不要です。 CloudShell (EKS API 操作用) 補足情報を後述します。 ※最近 AWS 管理コンソールのホーム画面のインタフェースアップデートがあり、ホーム画面の右上のアイコンからすぐに起動できます。 Cognito構築 AWS のマネジメントコンソールから、OIDCプロバイダーとなるCognitoユーザープールを作成します。 ※「Cognitoフェデレーティッド IDプール」の整備は不要です。 サインインエクス ペリエ ンスを設定 「Cognito ユーザープールのサインインオプション」は「Eメール」のみを指定します。 セキュリティ要件を設定 検証のため、「パスワードポリシー」「多要素認証」は任意設定で問題ありません。 検証のため、「ユーザーアカウントの復旧」は、デフォルト設定で問題ありません。 サインアップエクス ペリエ ンスを設定 検証のため、「自己登録」は無効化します。 検証のため「Cognito が検証と確認のためにメッセージを自動的に送信することを許可」の設定は無効化します。 必須の属性は自動的に「email」が設定されるため、追加の必須属性・カスタム属性は設定不要です。 メッセージ配信を設定 「Cognito で E メールを送信」を設定して、デフォルト設定で問題ありません。 アプリケーションを統合 「ユーザープール名」は、任意設定で問題ありません。 「ホストされた認証ページ」は、「Cognito のホストされた UI を使用」「Cognito ドメイン を使用する」を有効化しつつ、任意の ドメイン 名を登録します。 「最初のアプリケーションクライアント」は、検証のため「パブリッククライアント(クライアントシークレットなし)」を設定します。 ※「秘密クライアント(クライアントシークレットあり)」を選択した場合は、OIDC認証時にクライアントシークレット情報が必要となり、後述する手順では情報不足になりますが本記事では割愛します。 「アプリケーションクライアント名」は、任意設定で問題ありません。 「許可されているコールバックURL」は、検証のため「 http://localhost 」を設定します。 「高度なアプリケーションクライアントの設定」は、検証のため以下の「認証フロー」「 OpenID 接続スコープ」を設定します。 認証フロー ALLOW_REFRESH_TOKEN_AUTH(自動設定) ALLOW_ADMIN_USER_PASSWORD_AUTH ALLOW_USER_PASSWORD_AUTH OpenID 接続スコープ E メール(email) OpenID ( openid ) プロファイル(profile) ※それ以外は、検証のためデフォルト設定で問題ありません。 Cognito管理コンソールから作成された「ユーザープールID」「クライアントID」を控えます。 txt プールID : ap-northeast-1_XXXXXXXXX クライアントID : 1abc7efgh6xxxxxxxxxxx11ab1 CognitoユーザープールのOpen IDプロバイダー設定情報( OpenID Provider Configuration Information)を確認します。 ※本記事ではCognitoの「issuer」の情報のみ利用しますが、すぐに特定できるため確認はスキップ可能です。 ※Cognito以外のOpen IDプロバイダーを利用する場合は、Open IDプロバイダー設定情報を確認することが多いです。 txt https://cognito-idp.ap-northeast-1.amazonaws.com/<ユーザープールID>/.well-known/openid-configuration issuer txt https://cognito-idp.ap-northeast-1.amazonaws.com/<ユーザープールID>/ Cognitoグループ Cognito管理コンソールから「admin」グループを作成します。 ※cognitoグループに対して、IAMロールの設定は不要です。 Cognitoユーザ Eメールを受信可能なメールアドレスを利用して、ユーザーを登録します。 その際に、「E メールで招待を送信」を選択した場合は、登録したEメールにメッセージが送信されます。 Cognitoグループにユーザーを追加 [4.]の手順で作成したCognitoグループに[5.]で作成したユーザーを追加します。 Cognito管理コンソール上から「アプリケーションクライアント」の設定を表示し、「ホストされた UI」から「ホストされた UIを表示」ボタンを押下してCognito専用のログイン画面を表示します。 Cognito専用ログイン画面が表示されるため、作成したCognitoユーザーでログインして初期パスワードを変更します。 認証後はCallBack URLで指定した「locahost」のURLが表示され、「Not Found」になります。 検証はこの結果で問題なく、パスワード変更と認証完了を確認したことになります。 CloudShellの事前準備 CloudShellは、 Amazon Linux2ベースのクライアントとして利用可能です。 いくつか利用上の注意点はあります。 最低限の条件 IAMユーザー(または、スイッチ先のIAMロール)にcloudshellの操作権限が必要です。 セッション数に制限はありますが、無料です。 「/home/<ユーザ>」配下が永続データ領域となり、1GB制限があります。 必要なモジュールのインストール ■kubectl https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/install-kubectl.html こちらのマニュアルを参照して「/home/<ユーザ>」配下にkubectlを導入します。 ※ AWS CLI 含めて、いくつか必要なモジュールはデフォルトで使用可能です。 AWS CLI 経由でCognitoのユーザー認証& トーク ン取得 OIDCプロバイダーとなるCognitoで認証しつつ、リフレッシュ トーク ン・ID トーク ンを取得します。 この トーク ンは、 Kubernetes の「kube-apiserver」に連携するものです。 また、ID トーク ン(ヘッダー、 ペイロード 、署名が Base64 化されたもの)の ペイロード からユーザーの属性情報を確認することがポイントです。 特に、本記事では所属グループとなるcognito:groupsの「admin」に属していることを確認します。 OIDCに関する情報 sh ACCOUNT_ID="123456789123" REGION="ap-northeast-1" USERPOOL_ID="ap-northeast-1_XXXXXXXXX" ISSUER_URL="https://cognito-idp.${REGION}.amazonaws.com/${USERPOOL_ID}" CLIENT_ID="5abc7ufem9hpagvidrafo11ce1" USERNAME="kase.teruyoshi@isid.co.jp" PASSWORD="InitPassword" OIDC認証でリフレッシュ トーク ン、ID トーク ンを取得 ``sh tokenInfo= aws cognito-idp --region ${REGION} admin-initiate-auth --auth-flow ADMIN_USER_PASSWORD_AUTH --client-id ${CLIENT_ID} --user-pool-id ${USERPOOL_ID} --auth-parameters USERNAME=${USERNAME},PASSWORD=${PASSWORD} --query 'AuthenticationResult.{RefreshToken:RefreshToken, IdToken:IdToken}' refreshToken= echo ${tokenInfo} | jq -r .RefreshToken idToken= echo ${tokenInfo} | jq -r .IdToken` echo $refreshToken echo $idToken ``` ID トーク ンから ペイロード を確認 コマンド ``sh idTokenHeader= echo $idToken | cut -f 1 -d. | base64 --decode idTokenPayload= echo $idToken | cut -f 2 -d. | base64 --decode` echo $idTokenHeader | jq echo $idTokenPayload | jq ``` 実行結果 ``` [cloudshell-user@ip-10-0-107-185 ~]$ echo $idTokenHeader | jq { "kid": "lxZZOgtZffh16NGpS8a6IM9J5zu3KmkcbLnkDQ9/MCY=", "alg": "RS256" } [cloudshell-user@ip-10-0-107-185 ~]$ echo $idTokenPayload | jq { "sub": "abc4d541-e6f1-45g0-h6c1-184b36f52cf6", "cognito:groups": [ "admin" ], "email_verified": true, " iss ": " https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX ", "cognito:username": "abc4d541-e6f1-45g0-h6c1-184b36f52cf6", "origin_jti": "5abc2d90-e3f4-455g-94h8-6cd7909f2059", "aud": "5abc7ufem9hpagvidrafo11ce1", "event_id": "8765f9f9-999c-10e5-8a13-c44a68c8ed33", "token_use": "id", "auth_time": 1637655335, "name": "kase.teruyoshi@isid.co.jp", "exp": 1637658935, "iat": 1637655335, "jti": "568d2c50-544c-4fa8-92d0-9dc9cac2d2cb", "email": "kase.teruyoshi@isid.co.jp" } ``` EKS構築 EKS クラスタ ー「poc-eks-cluster-dev」を作成します。 検証環境のため、パブリック API エンドポイントは作成します。 Cloudshellを使用する都合で、パブリック API エンドポイントに対するアクセス元の制限はしません。 EKSを作成したIAMユーザー(またはIAMロール)で「 aws -auth」認証が登録されます。 今まで得た情報を元にOIDCプロバイダーを Amazon EKSの クラスタ ーに関連付けます。 ■ AWS Blog https://aws.amazon.com/jp/blogs/containers/introducing-oidc-identity-provider-authentication-amazon-eks こちらを参考に設定します。 なお、追加設定で20~40分程度かかることと、変更は再作成となるためおよそ2倍の時間が必要になります。 これにより、 Amazon EKS内の「kube-apiserver」にOIDCプロバイダー情報が登録されます。 これで、「kube-apiserver」側で トーク ン検証できる準備が整います。 「ClusterRoleBinding」によるRBAC認可の設定 CloudShell上でkubectlコマンドを実行して、ClusterRoleBinding「admin-role-binding」を作成します。 この段階では、「 aws -auth」認証を使用します。 AWS Blogでは「secret-reader」に対応するClusterRoleを作成しています。 本記事は、簡略化のため既にデフォルトで作成されているClusterRole「cluster-admin」を利用します。 ※「cluster-admin」は、操作権限の範囲が大きいため運用時は注意してください。 また、subjectセクションは、Cognitoユーザーが属するグループ「admin」を設定します。 この際、「kube-apiserver」にOIDCプロバイダーを登録したGroup Prefix「 gid :」を考慮して「 gid :admin」とします。 --- apiVersion : rbac.authorization.k8s.io/v1 kind : ClusterRoleBinding metadata : name : admin-role-binding namespace : default roleRef : apiGroup : rbac.authorization.k8s.io kind : ClusterRole name : cluster-admin subjects : - apiGroup : rbac.authorization.k8s.io kind : Group name : "gid:admin" OIDC認証で取得した トーク ンを利用した API 操作 「CloudShellの事前準備」の章の「OIDC認証でリフレッシュ トーク ン、ID トーク ンを取得」を実施して、再度 トーク ンを取得します。 その後、以下のとおりkubectl側で「kube-apiserver」に トーク ンを渡す準備をしたあと、自由にkubectlコマンドを実行してみてください。 ※今回、RBAC認可に使用した「cluster-admin」は強力な権限のため、ほとんどの操作ができます。 kube-config初期設定 sh aws eks --region ap-northeast-1 update-kubeconfig --name poc-eks-cluster-dev kubectl config get-contexts kubectl トーク ン設定 credentials設定 sh kubectl config set-credentials cognito-user --auth-provider=oidc --auth-provider-arg=idp-issuer-url=${ISSUER_URL} --auth-provider-arg=client-id=${CLIENT_ID} --auth-provider-arg=refresh-token=${refreshToken} --auth-provider-arg=id-token=${idToken} context設定 sh kubectl config set-context cognito-user-login --cluster arn:aws:eks:ap-northeast-1:${ACCOUNT_ID}:cluster/poc-eks-cluster-dev --user cognito-user context切替 sh kubectl config use-context cognito-user-login kubectl config get-contexts kubectlコマンドで API 実行結果 Amazon EKSにデフォルトで設定されている「ConfigMap」の情報を API で取得します。 sh [cloudshell-user@ip-10-0-107-185 ~]$ kubectl get configmap -n kube-system NAME DATA AGE aws-auth 1 6d6h   まとめ 本記事では、 Amazon EKSのOIDC認証を AWS サービスのみで検証しました。 OIDCプロバイダーは、一般的には SaaS サービス( Google 、Azure AD、Okta...etc)でID統合することになります。 本記事では触れていませんが、様々なクライアントツールを活用してログイン認証画面(本記事では「Cognito のホストされた UI 」)を経由して認証をさせることもできます。 また、本日使用した「cluster-admin」はデフォルトのClusterRoleであり、 API 操作の許可範囲が大きいものになります。 そのため、操作可能な 名前空間 ・リソース・アクションなどを正しく設計して、ID管理されたユーザー(または、ユーザー属性情報)に対して適切に付与することが重要になります。 執筆: @kase.teruyoshi 、レビュー: @shibata.takao ( Shodo で執筆されました )
アバター
こんにちは、 電通国際情報サービス デジタル イノベーション 部の加世です。 昨今は、コンテナアプリケーション開発が主流であり、コンテナ オーケストレーション を理解することは非常に重要であると考えています。 そこで、コンテナ オーケストレーション ツールである「 Kubernetes 」をマネージド・サービス化した「 Amazon EKS」を題材として、 Amazon EKSの クラスタ ー認証・認可について理解します。 Kubernetes は、カスタマイズ性の高いコンテナ オーケストレーション ツールです。 正しく設計すれば「マイクロサービス化」「リソース最適化」「リリース最適化」「耐障害性に関わる制御の最適化」などを1つの統合された仕組みで実現できます。 これは、非常に魅力的です。 また、アプリケーション開発者が主体となって、サービス構成変更・運用を進められる点も素晴らしいです。 下記のガートナープレスリリースのとおり、コンテナー化の増加とともにコンテナー管理の需要が高まると考えています。 Kubernetes のようなコンテナ オーケストレーション ツールを始めるなら「今」です。 ■ガートナー プレスリリース https://www.gartner.com/en/newsroom/press-releases/2020-06-25-gartner-forecasts-strong-revenue-growth-for-global-co https://www.gartner.co.jp/ja/newsroom/press-releases/pr-20211118 はじめに Amazon EKSのクラスター認証・認可の概要について 認証 認可 検証環境の準備 Cognito構築 CloudShellの事前準備 EKS構築 「ClusterRoleBinding」によるRBAC認可の設定 OIDC認証で取得したトークンを利用したAPI操作 まとめ はじめに Amazon EKS クラスタ ー認証には「IAMによる認証(本記事では便宜上この方式を aws -auth 方式と呼びます)」「OIDCプロバイダーによる認証」の2つの方式があります。 本記事は、このうち以下の内容を記載します。 Amazon EKSの クラスタ ー認証・認可の概要を説明します。 Amazon EKSの「 OpenID Connect(OIDC)」認証の検証環境を構築します。 「OIDC」認証後に Kubernetes の API 操作ができることを確認します。 Amazon EKSの クラスタ ー認証・認可の概要について Amazon EKSの クラスタ ー認証・認可の概要を説明します。 Kubernetes コンポーネント と認証・認可の基礎は、下記リンク先を参照してください。 ■ Kubernetes の コンポーネント https://kubernetes.io/ja/docs/concepts/overview/components/ ■認証 https://kubernetes.io/ja/docs/reference/access-authn-authz/authentication ■RBAC認可を使用する https://kubernetes.io/ja/docs/reference/access-authn-authz/rbac 認証 Amazon EKS クラスタ ー認証は、「 aws -auth」「OIDC」の2つの方式があります。 IAMによる認証( aws -auth) ■ クラスタ ーのユーザーまたは IAM ロールの管理 https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/add-user-role.html Amazon EKSは、デフォルト設定でIAMユーザー/IAMロールに基づく認証機能が組み込まれています。 機能仕様は、 aws eks get-token コマンドで取得した トーク ンを「kube-apiserver」に渡して認証します。 ※Auditログを確認した限りでは、 ブラックボックス 化されたコン トロール プレーン内の「 kubernetes - aws -authenticator」プロセスが トーク ンを検証しているものと推察しています。なお、 AWS マニュアルに詳細な仕様は公開されていません。 OIDCプロバイダーによる認証 ■ OpenID Connect ID プロバイダーから クラスタ ーのユーザーを認証する https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/authenticate-oidc-identity-provider.html OIDCプロバイダーを用意して、「kube-apiserver」自体のOIDC認証機能(標準機能)を利用します。 複数 AWS アカウントで Amazon EKSを含めたサービスを運用する場合は、一元的なID統制・管理を目指すことによるメリットが大きくなるためこちらを検討します。 ※ Kubernetes 専用のOIDCプロバイダーになってしまう場合は、今後のID統合方針や運用コストなどを考慮して採用を検討します。 認可 認証が完了したら、「kube-apiserver」は次に Kubernetes の API 操作を許可するかをRBACで判断します。 その際に使用されるのが「Role/ClusterRole」「RoleBinding/ClusterRoleBinding」です。 なお、 Amazon EKSの認可は「 Kubernetes の認可」と同様です。 そのため、本記事では説明を省きます。 検証環境の準備 では、ここからOIDCを利用した Amazon EKS クラスタ ー認証を実際に試してみたいと思います。 検証環境は、下記の AWS Blogを参考に構築します。 なお、 AWS サービス(東京リージョン内)のみで検証を完了できるように構成しています。 ■ AWS Blog https://aws.amazon.com/jp/blogs/containers/introducing-oidc-identity-provider-authentication-amazon-eks 検証環境の構成 ※最低限の コンポーネント のみを示しています。 AWS サービス Amazon VPC 事前に、任意の VPC ネットワークを作成した状態を前提とします。 AWS IAM 事前に、EKSを初期作成するためのユーザー作成と権限付与をした状態を前提とします。 Amazon Cognito (User Pool) 補足情報を後述します。 Amazon EKS (ver 1.21) 補足情報を後述します。 ※ワーカーノードは不要です。 CloudShell (EKS API 操作用) 補足情報を後述します。 ※最近 AWS 管理コンソールのホーム画面のインタフェースアップデートがあり、ホーム画面の右上のアイコンからすぐに起動できます。 Cognito構築 AWS のマネジメントコンソールから、OIDCプロバイダーとなるCognitoユーザープールを作成します。 ※「Cognitoフェデレーティッド IDプール」の整備は不要です。 サインインエクス ペリエ ンスを設定 「Cognito ユーザープールのサインインオプション」は「Eメール」のみを指定します。 セキュリティ要件を設定 検証のため、「パスワードポリシー」「多要素認証」は任意設定で問題ありません。 検証のため、「ユーザーアカウントの復旧」は、デフォルト設定で問題ありません。 サインアップエクス ペリエ ンスを設定 検証のため、「自己登録」は無効化します。 検証のため「Cognito が検証と確認のためにメッセージを自動的に送信することを許可」の設定は無効化します。 必須の属性は自動的に「email」が設定されるため、追加の必須属性・カスタム属性は設定不要です。 メッセージ配信を設定 「Cognito で E メールを送信」を設定して、デフォルト設定で問題ありません。 アプリケーションを統合 「ユーザープール名」は、任意設定で問題ありません。 「ホストされた認証ページ」は、「Cognito のホストされた UI を使用」「Cognito ドメイン を使用する」を有効化しつつ、任意の ドメイン 名を登録します。 「最初のアプリケーションクライアント」は、検証のため「パブリッククライアント(クライアントシークレットなし)」を設定します。 ※「秘密クライアント(クライアントシークレットあり)」を選択した場合は、OIDC認証時にクライアントシークレット情報が必要となり、後述する手順では情報不足になりますが本記事では割愛します。 「アプリケーションクライアント名」は、任意設定で問題ありません。 「許可されているコールバックURL」は、検証のため「 http://localhost 」を設定します。 「高度なアプリケーションクライアントの設定」は、検証のため以下の「認証フロー」「 OpenID 接続スコープ」を設定します。 認証フロー ALLOW_REFRESH_TOKEN_AUTH(自動設定) ALLOW_ADMIN_USER_PASSWORD_AUTH ALLOW_USER_PASSWORD_AUTH OpenID 接続スコープ E メール(email) OpenID ( openid ) プロファイル(profile) ※それ以外は、検証のためデフォルト設定で問題ありません。 Cognito管理コンソールから作成された「ユーザープールID」「クライアントID」を控えます。 txt プールID : ap-northeast-1_XXXXXXXXX クライアントID : 1abc7efgh6xxxxxxxxxxx11ab1 CognitoユーザープールのOpen IDプロバイダー設定情報( OpenID Provider Configuration Information)を確認します。 ※本記事ではCognitoの「issuer」の情報のみ利用しますが、すぐに特定できるため確認はスキップ可能です。 ※Cognito以外のOpen IDプロバイダーを利用する場合は、Open IDプロバイダー設定情報を確認することが多いです。 txt https://cognito-idp.ap-northeast-1.amazonaws.com/<ユーザープールID>/.well-known/openid-configuration issuer txt https://cognito-idp.ap-northeast-1.amazonaws.com/<ユーザープールID>/ Cognitoグループ Cognito管理コンソールから「admin」グループを作成します。 ※cognitoグループに対して、IAMロールの設定は不要です。 Cognitoユーザ Eメールを受信可能なメールアドレスを利用して、ユーザーを登録します。 その際に、「E メールで招待を送信」を選択した場合は、登録したEメールにメッセージが送信されます。 Cognitoグループにユーザーを追加 [4.]の手順で作成したCognitoグループに[5.]で作成したユーザーを追加します。 Cognito管理コンソール上から「アプリケーションクライアント」の設定を表示し、「ホストされた UI」から「ホストされた UIを表示」ボタンを押下してCognito専用のログイン画面を表示します。 Cognito専用ログイン画面が表示されるため、作成したCognitoユーザーでログインして初期パスワードを変更します。 認証後はCallBack URLで指定した「locahost」のURLが表示され、「Not Found」になります。 検証はこの結果で問題なく、パスワード変更と認証完了を確認したことになります。 CloudShellの事前準備 CloudShellは、 Amazon Linux2ベースのクライアントとして利用可能です。 いくつか利用上の注意点はあります。 最低限の条件 IAMユーザー(または、スイッチ先のIAMロール)にcloudshellの操作権限が必要です。 セッション数に制限はありますが、無料です。 「/home/<ユーザ>」配下が永続データ領域となり、1GB制限があります。 必要なモジュールのインストール ■kubectl https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/install-kubectl.html こちらのマニュアルを参照して「/home/<ユーザ>」配下にkubectlを導入します。 ※ AWS CLI 含めて、いくつか必要なモジュールはデフォルトで使用可能です。 AWS CLI 経由でCognitoのユーザー認証& トーク ン取得 OIDCプロバイダーとなるCognitoで認証しつつ、リフレッシュ トーク ン・ID トーク ンを取得します。 この トーク ンは、 Kubernetes の「kube-apiserver」に連携するものです。 また、ID トーク ン(ヘッダー、 ペイロード 、署名が Base64 化されたもの)の ペイロード からユーザーの属性情報を確認することがポイントです。 特に、本記事では所属グループとなるcognito:groupsの「admin」に属していることを確認します。 OIDCに関する情報 sh ACCOUNT_ID="123456789123" REGION="ap-northeast-1" USERPOOL_ID="ap-northeast-1_XXXXXXXXX" ISSUER_URL="https://cognito-idp.${REGION}.amazonaws.com/${USERPOOL_ID}" CLIENT_ID="5abc7ufem9hpagvidrafo11ce1" USERNAME="kase.teruyoshi@isid.co.jp" PASSWORD="InitPassword" OIDC認証でリフレッシュ トーク ン、ID トーク ンを取得 ``sh tokenInfo= aws cognito-idp --region ${REGION} admin-initiate-auth --auth-flow ADMIN_USER_PASSWORD_AUTH --client-id ${CLIENT_ID} --user-pool-id ${USERPOOL_ID} --auth-parameters USERNAME=${USERNAME},PASSWORD=${PASSWORD} --query 'AuthenticationResult.{RefreshToken:RefreshToken, IdToken:IdToken}' refreshToken= echo ${tokenInfo} | jq -r .RefreshToken idToken= echo ${tokenInfo} | jq -r .IdToken` echo $refreshToken echo $idToken ``` ID トーク ンから ペイロード を確認 コマンド ``sh idTokenHeader= echo $idToken | cut -f 1 -d. | base64 --decode idTokenPayload= echo $idToken | cut -f 2 -d. | base64 --decode` echo $idTokenHeader | jq echo $idTokenPayload | jq ``` 実行結果 ``` [cloudshell-user@ip-10-0-107-185 ~]$ echo $idTokenHeader | jq { "kid": "lxZZOgtZffh16NGpS8a6IM9J5zu3KmkcbLnkDQ9/MCY=", "alg": "RS256" } [cloudshell-user@ip-10-0-107-185 ~]$ echo $idTokenPayload | jq { "sub": "abc4d541-e6f1-45g0-h6c1-184b36f52cf6", "cognito:groups": [ "admin" ], "email_verified": true, " iss ": " https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXX ", "cognito:username": "abc4d541-e6f1-45g0-h6c1-184b36f52cf6", "origin_jti": "5abc2d90-e3f4-455g-94h8-6cd7909f2059", "aud": "5abc7ufem9hpagvidrafo11ce1", "event_id": "8765f9f9-999c-10e5-8a13-c44a68c8ed33", "token_use": "id", "auth_time": 1637655335, "name": "kase.teruyoshi@isid.co.jp", "exp": 1637658935, "iat": 1637655335, "jti": "568d2c50-544c-4fa8-92d0-9dc9cac2d2cb", "email": "kase.teruyoshi@isid.co.jp" } ``` EKS構築 EKS クラスタ ー「poc-eks-cluster-dev」を作成します。 検証環境のため、パブリック API エンドポイントは作成します。 Cloudshellを使用する都合で、パブリック API エンドポイントに対するアクセス元の制限はしません。 EKSを作成したIAMユーザー(またはIAMロール)で「 aws -auth」認証が登録されます。 今まで得た情報を元にOIDCプロバイダーを Amazon EKSの クラスタ ーに関連付けます。 ■ AWS Blog https://aws.amazon.com/jp/blogs/containers/introducing-oidc-identity-provider-authentication-amazon-eks こちらを参考に設定します。 なお、追加設定で20~40分程度かかることと、変更は再作成となるためおよそ2倍の時間が必要になります。 これにより、 Amazon EKS内の「kube-apiserver」にOIDCプロバイダー情報が登録されます。 これで、「kube-apiserver」側で トーク ン検証できる準備が整います。 「ClusterRoleBinding」によるRBAC認可の設定 CloudShell上でkubectlコマンドを実行して、ClusterRoleBinding「admin-role-binding」を作成します。 この段階では、「 aws -auth」認証を使用します。 AWS Blogでは「secret-reader」に対応するClusterRoleを作成しています。 本記事は、簡略化のため既にデフォルトで作成されているClusterRole「cluster-admin」を利用します。 ※「cluster-admin」は、操作権限の範囲が大きいため運用時は注意してください。 また、subjectセクションは、Cognitoユーザーが属するグループ「admin」を設定します。 この際、「kube-apiserver」にOIDCプロバイダーを登録したGroup Prefix「 gid :」を考慮して「 gid :admin」とします。 --- apiVersion : rbac.authorization.k8s.io/v1 kind : ClusterRoleBinding metadata : name : admin-role-binding namespace : default roleRef : apiGroup : rbac.authorization.k8s.io kind : ClusterRole name : cluster-admin subjects : - apiGroup : rbac.authorization.k8s.io kind : Group name : "gid:admin" OIDC認証で取得した トーク ンを利用した API 操作 「CloudShellの事前準備」の章の「OIDC認証でリフレッシュ トーク ン、ID トーク ンを取得」を実施して、再度 トーク ンを取得します。 その後、以下のとおりkubectl側で「kube-apiserver」に トーク ンを渡す準備をしたあと、自由にkubectlコマンドを実行してみてください。 ※今回、RBAC認可に使用した「cluster-admin」は強力な権限のため、ほとんどの操作ができます。 kube-config初期設定 sh aws eks --region ap-northeast-1 update-kubeconfig --name poc-eks-cluster-dev kubectl config get-contexts kubectl トーク ン設定 credentials設定 sh kubectl config set-credentials cognito-user --auth-provider=oidc --auth-provider-arg=idp-issuer-url=${ISSUER_URL} --auth-provider-arg=client-id=${CLIENT_ID} --auth-provider-arg=refresh-token=${refreshToken} --auth-provider-arg=id-token=${idToken} context設定 sh kubectl config set-context cognito-user-login --cluster arn:aws:eks:ap-northeast-1:${ACCOUNT_ID}:cluster/poc-eks-cluster-dev --user cognito-user context切替 sh kubectl config use-context cognito-user-login kubectl config get-contexts kubectlコマンドで API 実行結果 Amazon EKSにデフォルトで設定されている「ConfigMap」の情報を API で取得します。 sh [cloudshell-user@ip-10-0-107-185 ~]$ kubectl get configmap -n kube-system NAME DATA AGE aws-auth 1 6d6h   まとめ 本記事では、 Amazon EKSのOIDC認証を AWS サービスのみで検証しました。 OIDCプロバイダーは、一般的には SaaS サービス( Google 、Azure AD、Okta...etc)でID統合することになります。 本記事では触れていませんが、様々なクライアントツールを活用してログイン認証画面(本記事では「Cognito のホストされた UI 」)を経由して認証をさせることもできます。 また、本日使用した「cluster-admin」はデフォルトのClusterRoleであり、 API 操作の許可範囲が大きいものになります。 そのため、操作可能な 名前空間 ・リソース・アクションなどを正しく設計して、ID管理されたユーザー(または、ユーザー属性情報)に対して適切に付与することが重要になります。 執筆: @kase.teruyoshi 、レビュー: @shibata.takao ( Shodo で執筆されました )
アバター
みなさんこんにちは! ISID UXデザインセンターのマネージャーをしています、児玉と申します。 アドベントカレンダー も、残すところ10日。 濃厚な技術の話が続いておりますが、今日はISIDのエンジニアとタッグを組んで システム開発 を時にはサポートし、時にはリードしていく デザイナー たちの紹介です。 深~い技術 トーク の間に挟まる一服の清涼剤、あるいは シウマイ弁当 のアンズのつもりで書いています。ISIDにはこんな人たちもいるんだなぁ、くらいの気持ちでご覧になってください。 そもそもISIDにデザイナーっているの? よくこんな質問を社外から、時に社内からすら受けることもありましたが、ISIDにはデザイナーがいるんです! 2021年の1月に、 UXデザインセンター という横断型デザイン組織が立ち上がりました。 ISID UXデザインセンター 近年、さまざまなSI企業やコンサル企業が次々とデザインファームを買収したり、自社で立ち上げるなど、業界全体で「デザイン」重視の動きが広がっています。コンシューマー向けのサービスだけでなく、ユーザーが限られる業務システムであっても、きちんとユーザーの インサイト をとらえ、使いやすく価値のあるシステムを開発しようという潮流は年々強くなっています。 この流れを受け、さまざまな部署やプロダクト担当に散らばっていたデザイナーを集結させる形で、UXデザインセンターという社内ブティックが組成されました。基本的には現在のUXデザインの主流となっている人間中心設計(Human Centric Design)の考え方にもとづいており、 人間中心設計機構(HCD-Net) に企業として協賛、認定資格を持つデザイナーも多数所属していたり、資格取得支援もしています。 ISIDのデザイナーって何してるの? ISIDのデザイナーの仕事は、大きく分けて以下の4つです。 顧客向けにUXデザイン~UIデザインの提供 自社製品におけるUX向上のための活動 新規事業、研究開発におけるデザインプロセスの実践 社内に向けたデザインノウハウの浸透 一つずつ見ていきますね。 1.顧客向けにUXデザイン~UIデザインの提供 ISIDが主事業とする主に業務向け システム開発 (SI)案件において、デザイナーがエンジニアとともに参画。UI(インターフェース、見た目)にとどまらず、インタビューやアンケートなどのリサーチ、ワークショップ等によるコンセプト策定と要件定義、などのいわゆる デザイン思考 にもとづいたHCDプロセスを提供しています。これがUXデザインセンターの活動の半分近くを占めています。 さらに最近は、「業務に課題はあるが、何をしたら/どういったシステムを作りたいかわからない」と、 システム開発 の手前で相談いただくケースが増えています。そこでもデザイン思考のプロセスを活用し、課題抽出や解決方針の策定などを行うビジネスデザインの仕事も始まっています。 この領域では 電通 グループとの協業 も積極的ですね。 電通 グループのさまざまなチームにアクセスし、従来の SIer と全く違うサービスが提供できるのはUXデザインセンターの面白さです。直近ですと、 電通 の事業開発 コンサルティング 組織である 「電通ビジネスデザインスクエア」 と協業した組織開発支援なども行いました。 2.自社製品におけるUX向上のための活動 ISIDは、幅広い業種・業務向けの自社ソリューションを 保有 しています。UIのトレンドやデ バイス ・インフラの進化などによりUX設計やUIにもバージョンアップが求められます。そこでUXDCのデザイナーが開発メンバーの一員として参加し、リサーチによって改善方針の策定したり、UIの改善をしています。 リサーチからデザイン、リリースまで足の長い仕事になることも多いですが、その分ユーザーの反応にダイレクトで触れられるのはとても充実感があります。 3.新規事業、研究開発におけるデザインプロセスの実践 ISIDは受託や製品開発だけでなく、新規事業創出を継続的に行っています。新規事業チームに加わり、アイディエーションを支援したり、事業のコンセプトを固めたり、プロトタイプのUIをつくったり……とここでもデザイナーが活躍しています。 受託や自社商品だけでなく、自分たちで新規事業に取り組み、形にしていく。デザイナーとしてそのプロセスに触れることができるのも、UXデザインセンターの面白さですね。 4.社内に向けたデザインノウハウの浸透 私たちは頑張って日々いろいろなデザインをしていますが、それでもISIDの全案件のデザインを担当することはできません。デザインの力を企業の競争力にするためには、「非デザイナーのデザイン力」がとても重要だと感じています。 特にグローバルテック企業では、エンジニアとデザイナーの比率が10:1~8:1程度を目標としており、ISIDでそれを実践しようとするとデザイナーは100名ほど必要な計算となります。足りなすぎるよ! https://jp.techcrunch.com/2017/06/02/20170531here-are-some-reasons-behind-techs-design-shortage/ そこでUXデザインセンターでは、定期的に社内で勉強会を開催したり、初心者向けの教育コンテンツを用意したり、デザインに関する情報をTeamsで流通させるなど、非デザイナーのデザイン力を高める取り組みも行っています。(デザインの輪をひろげていく、という言い方をしています) と、ざっとお話ししましたが、案件の種類だけでも顧客案件(SI)・自社案件・新規事業と分かれ、それぞれに×業種数があり、さらに提供サービスもUIデザインからリサーチ、ビジネスデザインまで多様で、しかも社内人材育成も行っていて……。とにかく デザインと名の付くあらゆる角度の相談が入り続け、対応し続けている とても幸せな状況です。 ありがたいなぁ。 (いま手元で2021年に対応した案件を数えたら82案件ありました……) UXデザインセンターの雰囲気 UXデザインセンターはISID唯一のデザイン組織ということで、独自の雰囲気をもっています。愛と偏見を込めて、いくつかご紹介します。 多種多様なバックボーン 現在15名程度のデザイナーやデザインディレクター、プロデューサー、UXリサーチャーが所属していますが、 中途採用 のメンバーがほとんどで、それぞれにバックボーンが多様です。 新卒でエンジニアから転身したもの、事業部で長いキャリアを積んできたもの、デザイン会社からより広い業務経験を求めて転職してきたもの、C向けサービスの企画会社から来たもの、元スタートアップのデザイナー、ゴリゴリの営業から デザインプロデューサー になったもの、そして 電通 から来ているもの(私)など…… 得意領域がそれぞれ異なるため、悩むことがあったら「とりあえず、あの人に聞いてみよう」となりやすいです。とにかく多様な案件があるので、同じくらい多様な人材がいることで新しい課題に対する学習コストが低く済みますし、何より話していて楽しいのでこれからも多様性を大切にしていきたい。 ちなみに数少ない共通点としては、楽器ができる人が多いです。なぜだろう…… フラットな組織 また組織づくりにおいてはフラットさと透明性を最重視しています。 デザインのプロセスはそもそも、リサーチからコンセプトづくり、UIデザインまでひとつながりのものです。組織においても、大まかな個人の特技・適性はありますが、「この人は〇〇担当」と枠にはめることはしていません。クライアントワークから新規事業、UIデザインからビジネスデザインまで、希望に沿って多様な案件に触れられる動的な アサイ ンを行っています。カチッとした「先輩後輩」ではなく、それぞれのメンバーがそれぞれの得意領域を認識し、プロとして尊重しながら協力しているイメージです。 (と書くと、どんなビジネス本を開いても最初に書いてある当たり前のような話ですが、特に大企業において正面から取り組んでいる組織はあまり多くないように感じます) ISIDに馴染みすぎていない(良い意味で) 戦略的他者性、とでも言えるでしょうか。 SI業界、特に技術ありきで進むプロジェクトでよくあるのですが、受託案件も自社製品でも時として技術視点で盛り上がってしまいますよね。あれもできる、これもやってみたい、とか……。そしてその結果、いつのまにか「つくりたいもの/つくれるもの」で要件や仕様が固まっていき、ユーザーが置き去りになってしまうことも、稀によくありますよね。 私たちデザイナーはそんな状況に対するアンチテーゼとして、常に「でもそれってユーザーからするとさ……」と言い続ける存在でなくてはいけません。幸いにしてISIDの外や、非テック企業でのキャリアが長い人間も多いため、客観的な視点でアド バイス ができるのは私たちの強みです。 もちろん私たちもテク ノロ ジー 大好きですし、まだ見ぬ何かを見たい! という欲はありますが、同じくらい人間が好き。ユーザーを見つめて、そこにスッと馴染むものづくりをしたい。 理解はするが、馴染まない。 そんな強さのあるメンバーが集っています。 学習意欲が高い ひとりひとりが新しいスキルを身に着けることに対して非常に貪欲です。 業務系のUIデザインの次はWEBサイトに挑戦したい、リサーチと ファシリテーション 力を高めたい、デザインディレクターとしての立ち回りを身に着けたい、などの会話がメンバー間でよくされています。マネージャーとしてはできるだけそのリク エス トに応えられる案件を アサイ ンできるよう工夫していますし(これはとても大変です)、手ごろな案件がなければ近くの部署の活動に自分から声をかけ、体を突っ込んでいくこともあります。 表面上はクールで「チルい」雰囲気の方が多いですが、その裏には「次はあれをやりたい、これを身に着けたい」という熱意がほとばしっているようです。 まとめると、 - 個々の多様なキャリアを踏まえ、リスペクトしつつ - できるだけフラットな関係性で - 技術の会社であるISIDを理解し、しかし馴染みすぎることなく - 常に向上心を持って - とにかく多種多様なデザインに、全力で取り組んでいる そんなデザイン組織が、私たちUXデザインセンターです。 UXデザインセンターが気になったあなたへ noteもあるので見てください! https://note.com/isid_design 今回は組織のご紹介でしたが、いずれ気が向いたら、ISIDのデザインへのこだわりや方法論などをお話しさせていただくかもしれません。それでは! 執筆: @g-uxdc 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
みなさんこんにちは! ISID UXデザインセンターのマネージャーをしています、児玉と申します。 アドベントカレンダー も、残すところ10日。 濃厚な技術の話が続いておりますが、今日はISIDのエンジニアとタッグを組んで システム開発 を時にはサポートし、時にはリードしていく デザイナー たちの紹介です。 深~い技術 トーク の間に挟まる一服の清涼剤、あるいは シウマイ弁当 のアンズのつもりで書いています。ISIDにはこんな人たちもいるんだなぁ、くらいの気持ちでご覧になってください。 そもそもISIDにデザイナーっているの? よくこんな質問を社外から、時に社内からすら受けることもありましたが、ISIDにはデザイナーがいるんです! 2021年の1月に、 UXデザインセンター という横断型デザイン組織が立ち上がりました。 ISID UXデザインセンター 近年、さまざまなSI企業やコンサル企業が次々とデザインファームを買収したり、自社で立ち上げるなど、業界全体で「デザイン」重視の動きが広がっています。コンシューマー向けのサービスだけでなく、ユーザーが限られる業務システムであっても、きちんとユーザーの インサイト をとらえ、使いやすく価値のあるシステムを開発しようという潮流は年々強くなっています。 この流れを受け、さまざまな部署やプロダクト担当に散らばっていたデザイナーを集結させる形で、UXデザインセンターという社内ブティックが組成されました。基本的には現在のUXデザインの主流となっている人間中心設計(Human Centric Design)の考え方にもとづいており、 人間中心設計機構(HCD-Net) に企業として協賛、認定資格を持つデザイナーも多数所属していたり、資格取得支援もしています。 ISIDのデザイナーって何してるの? ISIDのデザイナーの仕事は、大きく分けて以下の4つです。 顧客向けにUXデザイン~UIデザインの提供 自社製品におけるUX向上のための活動 新規事業、研究開発におけるデザインプロセスの実践 社内に向けたデザインノウハウの浸透 一つずつ見ていきますね。 1.顧客向けにUXデザイン~UIデザインの提供 ISIDが主事業とする主に業務向け システム開発 (SI)案件において、デザイナーがエンジニアとともに参画。UI(インターフェース、見た目)にとどまらず、インタビューやアンケートなどのリサーチ、ワークショップ等によるコンセプト策定と要件定義、などのいわゆる デザイン思考 にもとづいたHCDプロセスを提供しています。これがUXデザインセンターの活動の半分近くを占めています。 さらに最近は、「業務に課題はあるが、何をしたら/どういったシステムを作りたいかわからない」と、 システム開発 の手前で相談いただくケースが増えています。そこでもデザイン思考のプロセスを活用し、課題抽出や解決方針の策定などを行うビジネスデザインの仕事も始まっています。 この領域では 電通 グループとの協業 も積極的ですね。 電通 グループのさまざまなチームにアクセスし、従来の SIer と全く違うサービスが提供できるのはUXデザインセンターの面白さです。直近ですと、 電通 の事業開発 コンサルティング 組織である 「電通ビジネスデザインスクエア」 と協業した組織開発支援なども行いました。 2.自社製品におけるUX向上のための活動 ISIDは、幅広い業種・業務向けの自社ソリューションを 保有 しています。UIのトレンドやデ バイス ・インフラの進化などによりUX設計やUIにもバージョンアップが求められます。そこでUXDCのデザイナーが開発メンバーの一員として参加し、リサーチによって改善方針の策定したり、UIの改善をしています。 リサーチからデザイン、リリースまで足の長い仕事になることも多いですが、その分ユーザーの反応にダイレクトで触れられるのはとても充実感があります。 3.新規事業、研究開発におけるデザインプロセスの実践 ISIDは受託や製品開発だけでなく、新規事業創出を継続的に行っています。新規事業チームに加わり、アイディエーションを支援したり、事業のコンセプトを固めたり、プロトタイプのUIをつくったり……とここでもデザイナーが活躍しています。 受託や自社商品だけでなく、自分たちで新規事業に取り組み、形にしていく。デザイナーとしてそのプロセスに触れることができるのも、UXデザインセンターの面白さですね。 4.社内に向けたデザインノウハウの浸透 私たちは頑張って日々いろいろなデザインをしていますが、それでもISIDの全案件のデザインを担当することはできません。デザインの力を企業の競争力にするためには、「非デザイナーのデザイン力」がとても重要だと感じています。 特にグローバルテック企業では、エンジニアとデザイナーの比率が10:1~8:1程度を目標としており、ISIDでそれを実践しようとするとデザイナーは100名ほど必要な計算となります。足りなすぎるよ! https://jp.techcrunch.com/2017/06/02/20170531here-are-some-reasons-behind-techs-design-shortage/ そこでUXデザインセンターでは、定期的に社内で勉強会を開催したり、初心者向けの教育コンテンツを用意したり、デザインに関する情報をTeamsで流通させるなど、非デザイナーのデザイン力を高める取り組みも行っています。(デザインの輪をひろげていく、という言い方をしています) と、ざっとお話ししましたが、案件の種類だけでも顧客案件(SI)・自社案件・新規事業と分かれ、それぞれに×業種数があり、さらに提供サービスもUIデザインからリサーチ、ビジネスデザインまで多様で、しかも社内人材育成も行っていて……。とにかく デザインと名の付くあらゆる角度の相談が入り続け、対応し続けている とても幸せな状況です。 ありがたいなぁ。 (いま手元で2021年に対応した案件を数えたら82案件ありました……) UXデザインセンターの雰囲気 UXデザインセンターはISID唯一のデザイン組織ということで、独自の雰囲気をもっています。愛と偏見を込めて、いくつかご紹介します。 多種多様なバックボーン 現在15名程度のデザイナーやデザインディレクター、プロデューサー、UXリサーチャーが所属していますが、 中途採用 のメンバーがほとんどで、それぞれにバックボーンが多様です。 新卒でエンジニアから転身したもの、事業部で長いキャリアを積んできたもの、デザイン会社からより広い業務経験を求めて転職してきたもの、C向けサービスの企画会社から来たもの、元スタートアップのデザイナー、ゴリゴリの営業から デザインプロデューサー になったもの、そして 電通 から来ているもの(私)など…… 得意領域がそれぞれ異なるため、悩むことがあったら「とりあえず、あの人に聞いてみよう」となりやすいです。とにかく多様な案件があるので、同じくらい多様な人材がいることで新しい課題に対する学習コストが低く済みますし、何より話していて楽しいのでこれからも多様性を大切にしていきたい。 ちなみに数少ない共通点としては、楽器ができる人が多いです。なぜだろう…… フラットな組織 また組織づくりにおいてはフラットさと透明性を最重視しています。 デザインのプロセスはそもそも、リサーチからコンセプトづくり、UIデザインまでひとつながりのものです。組織においても、大まかな個人の特技・適性はありますが、「この人は〇〇担当」と枠にはめることはしていません。クライアントワークから新規事業、UIデザインからビジネスデザインまで、希望に沿って多様な案件に触れられる動的な アサイ ンを行っています。カチッとした「先輩後輩」ではなく、それぞれのメンバーがそれぞれの得意領域を認識し、プロとして尊重しながら協力しているイメージです。 (と書くと、どんなビジネス本を開いても最初に書いてある当たり前のような話ですが、特に大企業において正面から取り組んでいる組織はあまり多くないように感じます) ISIDに馴染みすぎていない(良い意味で) 戦略的他者性、とでも言えるでしょうか。 SI業界、特に技術ありきで進むプロジェクトでよくあるのですが、受託案件も自社製品でも時として技術視点で盛り上がってしまいますよね。あれもできる、これもやってみたい、とか……。そしてその結果、いつのまにか「つくりたいもの/つくれるもの」で要件や仕様が固まっていき、ユーザーが置き去りになってしまうことも、稀によくありますよね。 私たちデザイナーはそんな状況に対するアンチテーゼとして、常に「でもそれってユーザーからするとさ……」と言い続ける存在でなくてはいけません。幸いにしてISIDの外や、非テック企業でのキャリアが長い人間も多いため、客観的な視点でアド バイス ができるのは私たちの強みです。 もちろん私たちもテク ノロ ジー 大好きですし、まだ見ぬ何かを見たい! という欲はありますが、同じくらい人間が好き。ユーザーを見つめて、そこにスッと馴染むものづくりをしたい。 理解はするが、馴染まない。 そんな強さのあるメンバーが集っています。 学習意欲が高い ひとりひとりが新しいスキルを身に着けることに対して非常に貪欲です。 業務系のUIデザインの次はWEBサイトに挑戦したい、リサーチと ファシリテーション 力を高めたい、デザインディレクターとしての立ち回りを身に着けたい、などの会話がメンバー間でよくされています。マネージャーとしてはできるだけそのリク エス トに応えられる案件を アサイ ンできるよう工夫していますし(これはとても大変です)、手ごろな案件がなければ近くの部署の活動に自分から声をかけ、体を突っ込んでいくこともあります。 表面上はクールで「チルい」雰囲気の方が多いですが、その裏には「次はあれをやりたい、これを身に着けたい」という熱意がほとばしっているようです。 まとめると、 - 個々の多様なキャリアを踏まえ、リスペクトしつつ - できるだけフラットな関係性で - 技術の会社であるISIDを理解し、しかし馴染みすぎることなく - 常に向上心を持って - とにかく多種多様なデザインに、全力で取り組んでいる そんなデザイン組織が、私たちUXデザインセンターです。 UXデザインセンターが気になったあなたへ noteもあるので見てください! note.com 今回は組織のご紹介でしたが、いずれ気が向いたら、ISIDのデザインへのこだわりや方法論などをお話しさせていただくかもしれません。それでは! 執筆: @g-uxdc 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
これは 電通国際情報サービス アドベントカレンダー の14日目の記事です。 はじめに Komapperとは クエリの分解 分解したクエリの再利用 利用しているKotlinの機能 おわりに はじめに X(クロス) イノベーション 本部 アドバンストテク ノロ ジー 部の中村です。 最近、サーバサイドの プログラミング言語 にKotlinを使うという話を耳にするようになってきました。Kotlinが便利な言語機能や API を持ち、 Java との相互運用性が高いということもあって、 システム開発 の プログラミング言語 選定において魅力的な選択肢の1つになっていると感じます。 私は Java もKotlinも大好きなのですが、体感的にKotlinの生産性は Java に比べて数倍高いと感じています。もし私が技術選定をする立場ならKotlinは最初に検討したい言語です。 今回は、Kotlinの言語機能を最大限活用することを目指して開発を始めたデータベースアクセスライブラリ Komapper の紹介をしたいと思います。 Komapperとは Komapperは、 Apache License 2.0の下で私が個人的に開発している オープンソース ソフトウェアです。私は、 Doma という Java で作られたデータベースアクセスライブラリの開発を10年以上続けていて、この領域のノウハウをある程度蓄積できたと感じています。Komapperにはそのノウハウを存分に注ぎ込みました。現時点のKomapper開発は個人的な取り組みですが、今後改善を加え、業務で開発・活用していく予定です。 さて、Komapperは、以下のような強みを持つサーバサイドKotlinのためのデータベースアクセスライブラリです。 JDBC と R2DBC のサポート コンパイル 時のコード生成 不変で合成可能なクエリ Value Classのサポート Spring Bootのサポート Komapperでは、 SQL を表現したクエリと呼ばれるオブジェクトを構築した後、そのクエリを実行することで実際に SQL を発行します。下記のサンプルコードをみてください。 val e = Meta.employee val query = QueryDsl.from(e). where { e.salary greaterEq BigDecimal( 5_000 ) }.orderBy(e.employeeId) val employees = db.runQuery { query } どうでしょう? SQL が分かる方にとっては何をしているか伝わりやすいコードではないでしょうか? 1行目は従業員テーブルを変数に確保しています。2行目では、「給料が5,000ドル以上の従業員」の一覧をIDでソートして取得する SQL を組み立てています。3行目で db.runQuery 関数を実行すると下記の SQL が発行されます。 select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NAME, t0_.SALARY, t0_.DEPARTMENT_ID from EMPLOYEE as t0_ where t0_.SALARY >= ? order by t0_.EMPLOYEE_ID asc このように SQL に似たクエリを宣言的に構築し実行できるのがKomapperの特徴となっています。 しかし、見た目が SQL っぽいコードを書けるだけであれば、特段目新しさは感じないかもしれません。そのようなライブラリはKotlinや Java にも、それからその他の プログラミング言語 にもたくさんあります。 クエリの分解 Komapperのクエリは分解できます。先ほどの例で示したクエリを3つの部品に分解して書いてみます。 val employeeQuery = QueryDsl.from(e) val isHighPerformer = where { e.salary greaterEq BigDecimal( 5_000 ) } val orderById = orderBy(e.employeeId) SQL で言うところのFROM句、WHERE句、ORDER BY句に分解できていますね。 分解したら名前づけも必要です。「給与が5,000ドル以上の従業員」は「ハイ パフォーマー 」と定義し、この検索条件は isHighPerformer という名前にしましょう。 分解してできた3つの部品からは容易に1つのクエリを組み立てられます。 val query = employeeQuery. where (isHighPerformer).orderBy(orderById) db.runQuery 関数を実行すると最初のクエリを1つで書いた例と全く同じ SQL が発行されます。 分解したクエリの再利用 クエリを分解できることの利点はなんでしょうか?それは 再利用できる ことです。 例えば、「SALESの部署に所属」する「ハイ パフォーマー 」を検索したい場合、先ほど分解して作成した employeeQuery や isHighPerformer や orderById を使って次のように書けます。 val e = Meta.employee val d = Meta.department val query = employeeQuery.innerJoin(d) { e.departmentId eq d.departmentId }. where { d.departmentName eq "SALES" and(isHighPerformer) }.orderBy(orderById) 1行目で部署テーブルを変数に確保し、2行目以降では従業員テーブルと部署テーブルを結合してクエリを組み立てています。 db.runQuery 関数で実行すると下記の SQL が発行されます。検索条件やソート条件が再利用されているのが分かります。 select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NAME, t0_.SALARY, t0_.DEPARTMENT_ID from EMPLOYEE as t0_ inner join DEPARTMENT as t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID) where t1_.DEPARTMENT_NAME = ? and (t0_.SALARY >= ?) order by t0_.EMPLOYEE_ID asc さらに、更新時にも検索条件を再利用できます。例えば、「ハイ パフォーマー 」の給料を一律1,000ドルupする更新クエリを作ってみましょう。 val e = Meta.employee val query = QueryDsl.update(e). set { e.salary set e.salary + BigDecimal( 1_000 ) }. where (isHighPerformer) db.runQuery 関数で実行すると下記の SQL が発行されます。こちらも検索条件が再利用されているのが分かります。 update EMPLOYEE as t0_ set SALARY = (t0_.SALARY + ?) where t0_.SALARY >= ? 複数のクエリに同じ検索条件が登場するのは良くあることだと思います。よく使う条件をあらかじめ定義しておけば変更を加えた場合に修正漏れが発生しないという利点もあります。 利用しているKotlinの機能 Kotlinを活用することで、 SQL に似たクエリを宣言的に記述したり、分解されたクエリを組み合わせたりできることを示しました。このようなことを実現するのに以下のようなKotlinの言語機能が役立っています。 Function literals with receiver Infix notation Operator overloading Properties では、もしKomapperが Java で書かれたライブラリだったらどうでしょうか? Java では上述の機能が使えないため、 API に違いが出てきます。この記事の最初に登場したクエリと比較してみます。 Kotlinで書かれたKomapperをKotlinから使う例(この記事最初のクエリの再掲): val query = QueryDsl.from(e). where { e.salary greaterEq BigDecimal( 5_000 ) }.orderBy(e.employeeId) Java で書かれたKomapperをKotlinから使う例: val query = QueryDsl.from(e). where { it.greaterEq(e.salary(), BigDecimal( 5_000 )) }.orderBy(e.employeeId) 前者のKotlin版Komapperを使った場合に比べて、後者の Java 版Komapperを使った場合では where 関数の ラムダ式 の中においていくつか違いがあります。 パラメーターの it が登場した greaterEq が 中置記法 の 演算子 から関数になった e.salary がプロパティから関数になった 関数が増えた分 ( や ) の括弧も増えた どのように思われますか?一見、大した差ではないように感じると思います。 しかし、 SQL との比較で考えると、いずれも SQL の シンタックス とは離れる方向の違いが生まれています。これはクエリを読み書きする際の思考を邪魔します。単純なクエリの場合はおそらく気にならなくても、分量が増えたり、クエリが複雑になると無視できない違いになると考えています。 おわりに クエリの宣言と分解したクエリの再利用に焦点を絞って私が開発しているKomapperを紹介しました。 Kotlinは Java のライブラリをシームレスに呼び出せます。そのため Java ライブラリを活用するのは良い戦略です。しかし、宣言的な記述をしたい場合は Java ライブラリよりもKotlinライブラリに優位性があると思います。Kotlinのドキュメントに Type-safe builders の章がありますが、KomapperもType-safe builderの一種と言えます。Kotlinのライブラリを作成する人にとっても選定する人とっても、Type-safe builderをどう捉えるかがKotlinを活用する上での1つのポイントかもしれません。 Komapperは、現在開発中で、本記事公開時点のバージョンはv0.24.0です。 感想を述べてくれる方、実際に使ってみてくれる方、一緒に開発してくれる方など歓迎いたします。どのような形でも構わないので興味を持っていただけたら嬉しいです。Komapperを使ってみたい場合は、 Quickstart や Examples をご覧ください。今回紹介したサンプルコードそのものではありませんが、同様のサンプルコードは repository-pattern-jdbc 以下にあります。 ありがとうございました。 執筆: @nakamura.toshihiro 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
これは 電通国際情報サービス アドベントカレンダー の14日目の記事です。 はじめに Komapperとは クエリの分解 分解したクエリの再利用 利用しているKotlinの機能 おわりに はじめに X(クロス) イノベーション 本部 アドバンストテク ノロ ジー 部の中村です。 最近、サーバサイドの プログラミング言語 にKotlinを使うという話を耳にするようになってきました。Kotlinが便利な言語機能や API を持ち、 Java との相互運用性が高いということもあって、 システム開発 の プログラミング言語 選定において魅力的な選択肢の1つになっていると感じます。 私は Java もKotlinも大好きなのですが、体感的にKotlinの生産性は Java に比べて数倍高いと感じています。もし私が技術選定をする立場ならKotlinは最初に検討したい言語です。 今回は、Kotlinの言語機能を最大限活用することを目指して開発を始めたデータベースアクセスライブラリ Komapper の紹介をしたいと思います。 Komapperとは Komapperは、 Apache License 2.0の下で私が個人的に開発している オープンソース ソフトウェアです。私は、 Doma という Java で作られたデータベースアクセスライブラリの開発を10年以上続けていて、この領域のノウハウをある程度蓄積できたと感じています。Komapperにはそのノウハウを存分に注ぎ込みました。現時点のKomapper開発は個人的な取り組みですが、今後改善を加え、業務で開発・活用していく予定です。 さて、Komapperは、以下のような強みを持つサーバサイドKotlinのためのデータベースアクセスライブラリです。 JDBC と R2DBC のサポート コンパイル 時のコード生成 不変で合成可能なクエリ Value Classのサポート Spring Bootのサポート Komapperでは、 SQL を表現したクエリと呼ばれるオブジェクトを構築した後、そのクエリを実行することで実際に SQL を発行します。下記のサンプルコードをみてください。 val e = Meta.employee val query = QueryDsl.from(e). where { e.salary greaterEq BigDecimal( 5_000 ) }.orderBy(e.employeeId) val employees = db.runQuery { query } どうでしょう? SQL が分かる方にとっては何をしているか伝わりやすいコードではないでしょうか? 1行目は従業員テーブルを変数に確保しています。2行目では、「給料が5,000ドル以上の従業員」の一覧をIDでソートして取得する SQL を組み立てています。3行目で db.runQuery 関数を実行すると下記の SQL が発行されます。 select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NAME, t0_.SALARY, t0_.DEPARTMENT_ID from EMPLOYEE as t0_ where t0_.SALARY >= ? order by t0_.EMPLOYEE_ID asc このように SQL に似たクエリを宣言的に構築し実行できるのがKomapperの特徴となっています。 しかし、見た目が SQL っぽいコードを書けるだけであれば、特段目新しさは感じないかもしれません。そのようなライブラリはKotlinや Java にも、それからその他の プログラミング言語 にもたくさんあります。 クエリの分解 Komapperのクエリは分解できます。先ほどの例で示したクエリを3つの部品に分解して書いてみます。 val employeeQuery = QueryDsl.from(e) val isHighPerformer = where { e.salary greaterEq BigDecimal( 5_000 ) } val orderById = orderBy(e.employeeId) SQL で言うところのFROM句、WHERE句、ORDER BY句に分解できていますね。 分解したら名前づけも必要です。「給与が5,000ドル以上の従業員」は「ハイ パフォーマー 」と定義し、この検索条件は isHighPerformer という名前にしましょう。 分解してできた3つの部品からは容易に1つのクエリを組み立てられます。 val query = employeeQuery. where (isHighPerformer).orderBy(orderById) db.runQuery 関数を実行すると最初のクエリを1つで書いた例と全く同じ SQL が発行されます。 分解したクエリの再利用 クエリを分解できることの利点はなんでしょうか?それは 再利用できる ことです。 例えば、「SALESの部署に所属」する「ハイ パフォーマー 」を検索したい場合、先ほど分解して作成した employeeQuery や isHighPerformer や orderById を使って次のように書けます。 val e = Meta.employee val d = Meta.department val query = employeeQuery.innerJoin(d) { e.departmentId eq d.departmentId }. where { d.departmentName eq "SALES" and(isHighPerformer) }.orderBy(orderById) 1行目で部署テーブルを変数に確保し、2行目以降では従業員テーブルと部署テーブルを結合してクエリを組み立てています。 db.runQuery 関数で実行すると下記の SQL が発行されます。検索条件やソート条件が再利用されているのが分かります。 select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NAME, t0_.SALARY, t0_.DEPARTMENT_ID from EMPLOYEE as t0_ inner join DEPARTMENT as t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID) where t1_.DEPARTMENT_NAME = ? and (t0_.SALARY >= ?) order by t0_.EMPLOYEE_ID asc さらに、更新時にも検索条件を再利用できます。例えば、「ハイ パフォーマー 」の給料を一律1,000ドルupする更新クエリを作ってみましょう。 val e = Meta.employee val query = QueryDsl.update(e). set { e.salary set e.salary + BigDecimal( 1_000 ) }. where (isHighPerformer) db.runQuery 関数で実行すると下記の SQL が発行されます。こちらも検索条件が再利用されているのが分かります。 update EMPLOYEE as t0_ set SALARY = (t0_.SALARY + ?) where t0_.SALARY >= ? 複数のクエリに同じ検索条件が登場するのは良くあることだと思います。よく使う条件をあらかじめ定義しておけば変更を加えた場合に修正漏れが発生しないという利点もあります。 利用しているKotlinの機能 Kotlinを活用することで、 SQL に似たクエリを宣言的に記述したり、分解されたクエリを組み合わせたりできることを示しました。このようなことを実現するのに以下のようなKotlinの言語機能が役立っています。 Function literals with receiver Infix notation Operator overloading Properties では、もしKomapperが Java で書かれたライブラリだったらどうでしょうか? Java では上述の機能が使えないため、 API に違いが出てきます。この記事の最初に登場したクエリと比較してみます。 Kotlinで書かれたKomapperをKotlinから使う例(この記事最初のクエリの再掲): val query = QueryDsl.from(e). where { e.salary greaterEq BigDecimal( 5_000 ) }.orderBy(e.employeeId) Java で書かれたKomapperをKotlinから使う例: val query = QueryDsl.from(e). where { it.greaterEq(e.salary(), BigDecimal( 5_000 )) }.orderBy(e.employeeId) 前者のKotlin版Komapperを使った場合に比べて、後者の Java 版Komapperを使った場合では where 関数の ラムダ式 の中においていくつか違いがあります。 パラメーターの it が登場した greaterEq が 中置記法 の 演算子 から関数になった e.salary がプロパティから関数になった 関数が増えた分 ( や ) の括弧も増えた どのように思われますか?一見、大した差ではないように感じると思います。 しかし、 SQL との比較で考えると、いずれも SQL の シンタックス とは離れる方向の違いが生まれています。これはクエリを読み書きする際の思考を邪魔します。単純なクエリの場合はおそらく気にならなくても、分量が増えたり、クエリが複雑になると無視できない違いになると考えています。 おわりに クエリの宣言と分解したクエリの再利用に焦点を絞って私が開発しているKomapperを紹介しました。 Kotlinは Java のライブラリをシームレスに呼び出せます。そのため Java ライブラリを活用するのは良い戦略です。しかし、宣言的な記述をしたい場合は Java ライブラリよりもKotlinライブラリに優位性があると思います。Kotlinのドキュメントに Type-safe builders の章がありますが、KomapperもType-safe builderの一種と言えます。Kotlinのライブラリを作成する人にとっても選定する人とっても、Type-safe builderをどう捉えるかがKotlinを活用する上での1つのポイントかもしれません。 Komapperは、現在開発中で、本記事公開時点のバージョンはv0.24.0です。 感想を述べてくれる方、実際に使ってみてくれる方、一緒に開発してくれる方など歓迎いたします。どのような形でも構わないので興味を持っていただけたら嬉しいです。Komapperを使ってみたい場合は、 Quickstart や Examples をご覧ください。今回紹介したサンプルコードそのものではありませんが、同様のサンプルコードは repository-pattern-jdbc 以下にあります。 ありがとうございました。 執筆: @nakamura.toshihiro 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
本記事は 電通国際情報サービス Advent Calendar 2021 の 13 日目の記事です。 執筆者は 2021 年新卒入社の XI 本部 AI トランスフォーメンションセンター所属の山田です。 はじめに React Hooks とは useState useState を使うユースケース useEffect useEffect を使うユースケース useContext useContext を使うユースケース useReducer useReducer を使うユースケース useMemo useMemo を使うユースケース useCallback React.memo と useCallback useCallback を使うユースケース React Hooks を正しく使うために おわりに はじめに 本記事では React Hooks の代表的なフックについて、その使い方と ユースケース をサンプルコードとともに紹介します。 サンプルコードは TypeScript で記述しています。 React Hooks とは React Hooks は React 16.8(2019 年 2 月リリース)で追加された機能です。 2021 年現在において React でアプリケーションを構築するためには理解が必須の機能といっても過言ではないでしょう。 React Hooks を使うことによって React の関数型 コンポーネント で状態(state)を持つことや コンポーネント のライフサイクルに応じた処理を記述できます。 以下では、 React で提供される基本的な React Hooks をその ユースケース とともに紹介します。 useState useEffect useContext useReducer useMemo useCallback useState useState は関数型 コンポーネント で状態(state)を扱うためのフックです。 以下は useState を利用する場合の基本的なコードです。 // 返り値はstateの変数とstateを更新するための関数 const [ state , setState ] = useState < T >( initStateValue ); useState は状態(state)の変数と状態(state)を更新するための関数を返します。 状態(state)の更新をする際は必ず更新用の関数を介して行う必要があります。 useState を使う ユースケース useState が必要となるのは、利用者と インタラクティブ にやり取りをする値を保持する必要がある場合です。 利用者と インタラクティブ にやり取りをするという場面の最も典型的な例はフォームです。 ここではログインフォームを題材にしてコードを紹介します。 作成するログインフォームは画像のように input 要素としてユーザー ID とパスワードを持つものを想定します。 初めにログインフォームで扱うデータの型(SampleLoginForm)を定義しておきます。 今回の例では userId と password だけをプロパティに持つオブジェクトとします。 interface SampleLoginForm { userId: string ; password: string ; } 作った SampleLoginForm 型の変数 formData を useState を使って定義します。 const [ formData , setFormData ] = useState < SampleLoginForm >( { userId: "" , password: "" , } ); あとは input 要素の value 属性に対応する formData の変数を渡します。 さらに input 要素の onChange イベントから setFormData を呼び出して formData の状態を更新します。 < div > < label htmlFor = "userId" > ユーザーID < /label > < input id = "userId" type= "text" name = "userId" placeholder = "ユーザーID" value = { formData.userId } onChange = { ( e ) => setFormData ( { ...formData , userId: e.target.value } ) } / > < /div > これにより、ユーザーがフォームに入力した文字(値)を変数 formData に保持できます。 ▶︎ クリックしてコード全文を見る // components/LoginForm.tsx import React , { useState } from "react" ; interface SampleLoginForm { userId: string ; password: string ; } export default function LoginForm () : JSX. Element { const [ formData , setFormData ] = useState < SampleLoginForm >( { userId: "" , password: "" , } ); const submitHandler = ( e: FormEvent < HTMLFormElement >) => { e.preventDefault (); console .log ( "ログインボタン押下" , formData ); } ; return ( < form onSubmit = { submitHandler } > < div > < label htmlFor = "userId" > ユーザーID < /label > < input id = "userId" type= "text" name = "userId" placeholder = "ユーザーID" value = { formData.userId } onChange = { ( e ) => setFormData ( { ...formData , userId: e.target.value } ) } / > < /div > < div > < label htmlFor = "password" > パスワード < /label > < input id = "password" type= "password" name = "password" placeholder = "パスワード" value = { formData.password } onChange = { ( e ) => setFormData ( { ...formData , password: e.target.value } ) } / > < /div > < div > < button type= "submit" > ログイン < /button > < /div > < /form > ); } useEffect useEffect は関数型 コンポーネント で副作用を実行するためのフックです。 副作用と聞くと仰々しいですが コンポーネント 内での「外部データの取得」「DOM の手動での更新」などの処理を、React では副作用と呼びます。 useEffect を使うための基本的なコードは以下のとおりです。 // 副作用を含む処理を記述した関数を記述する useEffect (() => { // 副作用処理 // … return () => { // クリーンアップ処理 } ; } , [] ); useEffect では副作用となる処理を関数内で記述します。 return で関数を返すことによってクリーンアップ処理を記述できます。 通常、 useEffect による副作用処理は コンポーネント の レンダリング 毎に実行されます。 副作用処理を毎回行わないためには、第2引数の依存配列によって制御できます。 useEffect の詳しい説明については以下の参考リンクをご覧ください。 副作用フックの利用法 https://ja.reactjs.org/docs/hooks-effect.html useEffect完全ガイド https://overreacted.io/ja/a-complete-guide-to-useeffect/ useEffect を使う ユースケース useEffect が必要となる代表的な ユースケース としては コンポーネント を呼び出したタイミングで外部 API からリソースを取得したい場合などです。 ここではサンプルの外部 API として JSONPlaceholder を利用して コンポーネント を呼び出したタイミングでデータを取得してみましょう。 JSONPlaceholder, https://jsonplaceholder.typicode.com/ 少し JSONPlaceholder について補足します。 JSONPlaceholder は 6 種類の構造のダミーデータを取得できます。 今回はタスク管理アプリケーションで一般的な ToDo リスト形式のデータを取得します。 取得したデータは先ほど紹介した setState を使って保持します。 まず取得する ToDo リストの型を定義しておきます。 interface ToDo { id: number ; userId: number ; title: string ; completed: boolean ; } そして先ほどの useState フックを使って取得する ToDo リスト形式を状態管理します。 const [ todoItemss , setToDos ] = useState < ToDo [] >( [] ); そして useEffect を使って実際に外部 API を呼び出し、状態を更新します。 外部 API の呼び出しには fetch を利用します。 useEffect (() => { const f = async () => { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); setToDos ( json ); } ; f (); } , [] ); 注意点として useEffect に渡す関数は同期的です。 そのため非同期関数(async/await)を使うには関数内で定義する必要があります。 補足ですが、次期アップデートの React v18 より React.Suspense を使った非同期のデータ取得がサポートされます。 アップデート後はこちらがベストプ ラク ティスになっていく可能性も高いため、公式ドキュメントの「React.Suspense」と「サスペンスを使ったデータ取得」についても、ぜひチェックをしてみてください。 React の最上位 API - React.Suspense, https://ja.reactjs.org/docs/react-api.html#reactsuspense サスペンスを使ったデータ取得(実験的機能), https://ja.reactjs.org/docs/concurrent-mode-suspense.html 取得した ToDo リスト形式のデータはスタイルを少し当てて Array.prototype.map() を使えば以下のように描画できます。 ▶︎ クリックしてコード全文を見る // components/ToDoList.tsx import React , { useEffect , useState } from "react" ; interface ToDo { id: number ; userId: number ; title: string ; completed: boolean ; } export default function ToDoList () : JSX. Element { const [ todoItems , setToDos ] = useState < ToDo [] >( [] ); useEffect (() => { const f = async () => { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); setToDos ( json ); } ; f (); } , [] ); return ( < div style = {{ textAlign: "left" }} > { todoItems.map (( todoItem ) => ( < div key = { todoItem.id } style = {{ width: "250px" , border: "solid" , margin: "8px" , padding: "8px" , }} > < h4 > { todoItem.title } < /h4 > < p style = {{ textAlign: "right" }} > { todoItem.completed ? "✅ 完了" : "未実施" } < /p > < /div > )) } < /div > ); } useContext useContext は コンポーネント 間で横断的に利用したい状態を管理するためのフックです。 通常、 コンポーネント では状態(データ)を props を通して親から子に渡します。 これを図に起こすと以下のようになります。 一方、コンテキストを使うと以下のように props を通さずにデータをやり取りできます。 コンテキストでは Context.Provider コンポーネント を通して横断的に利用したい状態を配信します。 そして必要な コンポーネント で useContext を使うことによって状態を購読します。 useContext を使って コンポーネント 内でコンテキストから配信される値を購読する基本的なコードは以下のようになります。 // 返り値はコンテキストから配信される値 // useContextの第1引数には`React.createContext`によって作成したコンテキストオブジェクトを渡す const value = useContext ( MyContext ); useContext では購読するコンテキストのオブジェクトを渡し、コンテキストから配信される値を受け取ります。 useContext を使う ユースケース ここまでで述べてきたように useContext を使うのは コンポーネント 間で横断的に利用したい状態がある場面です。 代表的な場面として認証情報の管理などがあります。 ここではコンテキストを使ってユーザー ID を管理することを例に説明します。 管理するユーザー ID は useState を用いて宣言し、その状態と更新用の関数をコンテキストを使って配信します。 // コンテキストで配信する値 const [ userId , setUserId ] = useState < number >( -1 ); 配信する値が決まったので、コンテキストで配信する値の型を定義します。 interface Context { userId: number ; setUserId: Dispatch < SetStateAction < number >>; } createContext を使ってコンテキストオブジェクトを作成します。型引数には先ほど定義した型を指定し、第 1 引数には初期値を与えます。 const AuthContext = createContext < Context >( { userId: -1 , setUserId: () => {} , } ); 次に Context の Provider を作成します。 Provider の value プロパティにコンテキストで配信する値を指定します。 const AuthProvider: React.FC = ( { children } ) => { // コンテキストで配信する値 const [ userId , setUserId ] = useState < number >( -1 ); return ( < AuthContext.Provider value = {{ userId , setUserId }} > { children } < /AuthContext.Provider > ); } ; // コンテキストオブジェクトとProviderをexportする export { AuthContext , AuthProvider } ; createContext で作成した AuthContext と AuthProvider を外部に公開(export)することでコンテキストを利用しやすくしています。 ▶︎ クリックしてコード全文を見る // contexts/auth.tsx import React , { createContext , Dispatch , SetStateAction , useState , } from "react" ; interface Context { userId: number ; setUserId: Dispatch < SetStateAction < number >>; } const AuthContext = createContext < Context >( { userId: -1 , setUserId: () => {} , } ); const AuthProvider: React.FC = ( { children } ) => { const [ userId , setUserId ] = useState < number >( -1 ); return ( < AuthContext.Provider value = {{ userId , setUserId }} > { children } < /AuthContext.Provider > ); } ; // コンテキストオブジェクトとProviderをexportする export { AuthContext , AuthProvider } ; 作成した AuthProvider を App.tsx に記述します。 これによりアプリケーション内のどの コンポーネント でも useContext を使って AuthContext から値を購読できます。 // App.tsx import React from "react" ; import { AuthProvider } from "./contexts/auth" ; import LoginForm from "./components/LoginForm" ; import ToDoList from "./components/ToDoList" ; export default function App () : JSX. Element { return ( < AuthProvider > < div style = {{ padding: "8px" , textAlign: "center" }} > < LoginForm / > < ToDoList / > < /div > < /AuthProvider > ); } 実際に LoginForm と ToDoList コンポーネント でコンテキストを使ってみましょう。 まず LoginForm コンポーネント 内でフォーム送信時にコンテキストの userId を更新してみます。 // AuthContextからuserIdを更新する関数setUserIdを購読 const { setUserId } = useContext ( AuthContext ); // form要素のsubmitイベントを処理する関数 const submitHandler = ( e: FormEvent < HTMLFormElement >) => { e.preventDefault (); console .log ( "ログインボタン押下" , formData ); // AuthContextで配信される値userIdを更新 setUserId ( 1 ); } ; 次に ToDoList コンポーネント でコンテキストから userId を購読します。 そして useEffect で userId の状態を監視し、初期値(-1)でない場合に外部 API からリソースを取得するようにします。 // AuthContextからuserIdを購読 const { userId } = useContext ( AuthContext ); useEffect (() => { const f = async () => { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); setToDos ( json ); } ; // userId が初期値でない場合に外部APIコール if ( userId !== -1 ) { f (); } } , [ userId ] ); 以上でログインフォームのログインボタンを押下することで AuthContext の userId を更新し、その変更を検知して ToDo リストの情報を外部 API から取得する処理が実現できます。 useReducer useReducer は useState よりも複雑な状態を管理するためのフックです。 公式ドキュメントでは「 useState の代替品」として位置づけられています。 useReducer を使うための基本的なコードは以下のとおりです。 // `useState`の代替品。返り値はstateの変数とstateを更新するためのDispatch関数 const [ state , dispatch ] = useReducer ( reducer , initialArg , init ); useReducer を理解するためには 4 つの要素を理解する必要があります。 State … 状態 Reducer … State を更新するための関数 Action … State を更新するのに必要なデータ Dispatch … Action を Reducer に届ける関数 この 4 つの要素は図のような関係になります。 useReducer を使う ユースケース アプリケーション開発を進めていくと処理が複雑になるにつれて、管理しなければならない状態(state)が増えていきます。 また実際には、相互に関連する状態を更新しなければならない場面も増えます。 そのような場面で力を発揮するのが useReducer フックです。 例えば、先ほどの useEffect フックでを使った外部 API からのリソース取得を例に考えてみましょう。 外部リソースの取得では取得までに時間を要しますので読み込み中か否かを isLoading のような形で状態管理する必要があるでしょう。 さらにデータ取得時のエラーハンドリングを考えるとエラーが発生したかを error のような変数で状態管理する必要があります。 これらを useState フックで管理する場合は以下のようになります。 const [ todos , setToDos ] = useState < ToDo [] >( [] ); const [ isLoading , setIsLoading ] = useState < boolean >( true ); const [ error , setError ] = useState < boolean >( false ); このように複数の値に関連する状態を管理する場面で useReducer を使うことを考えます。 まず useReducer で管理する状態の型とその状態の初期値を定義します。 // 管理する状態の型 interface State { todoItems: ToDo [] ; isLoading: boolean ; error: boolean ; } // 状態の初期値 const initState: State = { todoItems: [] , isLoading: true , error: false , } ; 次に状態を更新するためのデータとなるアクションの型を定義します。 今回は状態を更新する操作として以下の 2 種類を考えます。 SET_TODOS … ToDo リストにアイテムをセットする操作。アクションは ToDo リストにセットするデータを含む。 SET_ERROR … エラーが発生した際にエ ラーフラ グを True にする操作。アクションはデータを持たない。 これらを型に起こします。 // アクションの種類 type ActionType = "SET_TODOS" | "SET_ERROR" ; // アクションの型 interface Action { type : ActionType ; payload?: ToDo [] ; } 上で定義した型を使って reducer 関数を作成します。 import { Reducer } from "react" ; const reducer: Reducer < State , Action > = ( state , action ) => { switch ( action. type) { case "SET_TODOS" : if ( ! action.payload ) { // payloadが含まれていなければエラー扱いにする return { ...state , error: true , isLoading: false , } ; } return { ...state , ...action.payload , isLoading: false , } ; case "SET_ERROR" : return { ...state , error: true , isLoading: false , } ; } } ; この reducer 関数と状態の初期値を使って useReducer を宣言します。 const [{ todoItems , error , isLoading } , dispatch ] = useReducer ( reducer , initState ); そして先程の useEffect 内で状態を更新していた部分を dispatch にアクションを渡すことで状態を更新するように書き換えます。 useEffect (() => { const f = async () => { try { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); dispatch ( { type : "SET_TODOS" , payload: { todoItems: json } } ); } catch ( e ) { console .log ( e ); dispatch ( { type : "SET_ERROR" } ); } } ; f (); } , [] ); この例だともともとがそこまで複雑な状態管理ではなかったため、 useReducer を使った記述が冗長だと感じるかもしれません。 どのタイミングで useReducer を使うのかは、個人/チーム次第ではありますが、うまく使うことで状態管理をわかりやすくできます。 ▶︎ クリックしてコード全文を見る // components/ToDoList.tsx import React , { Reducer , useEffect , useReducer } from "react" ; interface ToDo { id: number ; userId: number ; title: string ; completed: boolean ; } interface State { todoItems: ToDo [] ; isLoading: boolean ; error: boolean ; } const initState: State = { todoItems: [] , isLoading: true , error: false , } ; type ActionType = "SET_TODOS" | "SET_ERROR" ; interface Action { type : ActionType ; payload?: Partial < State >; } const reducer: Reducer < State , Action > = ( state , action ) => { switch ( action. type) { case "SET_TODOS" : if ( ! action.payload ) { // payloadが含まれていなければエラー扱いにする return { ...state , error: true , isLoading: false , } ; } return { ...state , ...action.payload.todoItems , isLoading: false , } ; case "SET_ERROR" : return { ...state , error: true , isLoading: false , } ; } } ; export default function ToDoList () : JSX. Element { const [{ todoItems , error , isLoading } , dispatch ] = useReducer ( reducer , initState ); useEffect (() => { const f = async () => { try { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); dispatch ( { type : "SET_TODOS" , payload: { todoItems: json } } ); } catch ( e ) { console .log ( e ); dispatch ( { type : "SET_ERROR" } ); } } ; f (); } , [] ); return ( <> { isLoading ? ( < p > ロード中です… < /p > ) : error ? ( < p > エラーが発生しました。 < /p > ) : ( < div style = {{ textAlign: "left" }} > { todoItems.map (( todoItem ) => ( < div key = { todoItem.id } style = {{ width: "250px" , border: "solid" , margin: "8px" , padding: "8px" , }} > < h4 > { todoItem.title } < /h4 > < p style = {{ textAlign: "right" }} > { todoItem.completed ? "✅ 完了" : "未実施" } < /p > < /div > )) } < /div > ) } < / > ); } useMemo useMemo は関数の返り値をメモ化するフックです。 メモ化はプログラムの最適化技法の 1 つで、計算結果を再利用するために保持して、再計算を防ぐものです。 そのため useMemo は最適化のためのフックという位置付けです。 useMemo を使うための基本的なコードは以下のとおりです。 // 返り値は関数の計算結果をメモ化した値 // 第2引数の依存配列に含まれる値が変更された時に再計算される const memoizedValue = useMemo < T >(() => computeExpensiveValue ( a , b ), [ a , b ] ); useMemo を使う ユースケース 基本的には最適化のためのフックですが、例えば配列を保持する state で配列を走査する処理が頻繁に必要な場合などに役立ちます。 ToDo リストの例で、一覧から完了済みのアイテムを useMemo によって取得することを考えてみましょう。 const [ todoItems , setToDos ] = useState < ToDo [] >( [] ); const completedItems = useMemo < ToDo [] >(() => { return todoItems.filter (( todoItem ) => todoItem.completed ); } , [ todos ] ); useMemo では依存配列に渡された state が更新された時にメモ化していた値を再計算します。 useCallback useCallback は関数をメモ化するフックです。 useCallback は最適化のためのフックという位置付けです。 そして useCallback を利用する場合は、基本的に React.memo と併用する必要があります。 React.memo と useCallback useCallback の話をする前に、 React.memo について簡単に説明します。 React の最上位 API - React.memo, https://ja.reactjs.org/docs/react-api.html#reactmemo すでに述べた通り、React では親 コンポーネント から子 コンポーネント に props を通してデータを渡します。 通常では、図中の点線で示した子 コンポーネント は親 コンポーネント が再描画されるタイミングで常に再描画されます。 React.memo はこの親 コンポーネント が再描画されるタイミングでの子 コンポーネント の再描画を最適化するものです。 React.memo では子 コンポーネント において、親 コンポーネント から受け取る props が再描画前の props と等価であれば、再描画をスキップします。つまり親 コンポーネント から子 コンポーネント に渡す props とその等価性が重要になります。 useCallback は props に渡す関数が等価であることを保証するためのフックです。 useCallback を使うための基本的なコードは以下のとおりです。 // 返り値はメモ化された関数 // 第2引数の依存配列に含まれる値が変更された時に再計算される const memoizedCallback = useCallback (() => { doSomething ( a , b ); } , [ a , b ] ); これにより子 コンポーネント では、props 受け取った関数が useCallback の第2引数の依存配列に含まれる値が変更されていない限りは等価なものとして扱えます。 useCallback を使う ユースケース ここまで説明した通り、 useCallback は最適化の流れで、子 コンポーネント の props に関数を渡す必要が生じた際に利用します。 React.memo は使用しませんが、props に関数を渡す場面を先ほどのログインフォームの例で見てみましょう。 まずフォームの状態を useState を使って定義していました。 const [ formData , setFormData ] = useState < SampleLoginForm >( { userId: "" , password: "" , } ); そして input 要素の onChange プロパティに関数を記述し formData の値を更新していました。 < input id = "userId" type= "text" name = "userId" placeholder = "ユーザーID" value = { formData.userId } onChange = { ( e ) => setFormData ( { ...formData , userId: e.target.value } ) } / > この onChange プロパティに渡す関数を useCallback で記述すると以下のようになります。 // inputタグのonChangeイベントを処理する関数 const onChangeHandler = useCallback (( e: ChangeEvent < HTMLInputElement >) => { setFormData (( prev: SampleLoginForm ) => { return { ...prev , [ e.target.name ] : e.target.value } ; } ); } , [] ); useCallback では第 2 引数の依存配列に含まれる値が変更されたタイミングで再度メモ化されるため、依存配列に含まれる値が少なくなるように意識する必要があります。 この例では、更新時に formData を参照せず、 setFormData 関数内で直前の formData の値を受けることよって依存配列が空になるようにしています。 これにより onChangeHandler 関数はメモ化が働き、 React.memo と併用した最適化ができます。 React Hooks を正しく使うために フックは一見すると JavaScript の関数ですが、正しく使う際には、ルールに従う必要があります。 特に useEffect や useMemo 、 useCallback といった依存配列を含むフックの使用では、依存関係の漏れによってバグを混入する恐れがあります。 フックを正しく利用するために、ESLint の eslint-plugin-react-hooks プラグイン を導入しておくことがお勧めです。 exhaustive-deps ルールを有効にすれば、依存配列が正しく記述されていない場合に警告を出すこともできます。 eslint-plugin-react-hooks https://www.npmjs.com/package/eslint-plugin-react-hooks おわりに 本記事では、React で提供される基本的な React Hooks をその ユースケース とともに紹介しました。 ここでは紹介できなかった React Hooks やカスタムフック、テスト方法なども今後、紹介できればと思います。 明日(12/14)は Toshihiro Nakamura さんから「Kotlinでデータベースアクセス」の記事が公開される予定です。 そちらもぜひご覧ください。 執筆: @yamada.y 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
本記事は 電通国際情報サービス Advent Calendar 2021 の 13 日目の記事です。 執筆者は 2021 年新卒入社の XI 本部 AI トランスフォーメンションセンター所属の山田です。 はじめに React Hooks とは useState useState を使うユースケース useEffect useEffect を使うユースケース useContext useContext を使うユースケース useReducer useReducer を使うユースケース useMemo useMemo を使うユースケース useCallback React.memo と useCallback useCallback を使うユースケース React Hooks を正しく使うために おわりに はじめに 本記事では React Hooks の代表的なフックについて、その使い方と ユースケース をサンプルコードとともに紹介します。 サンプルコードは TypeScript で記述しています。 React Hooks とは React Hooks は React 16.8(2019 年 2 月リリース)で追加された機能です。 2021 年現在において React でアプリケーションを構築するためには理解が必須の機能といっても過言ではないでしょう。 React Hooks を使うことによって React の関数型 コンポーネント で状態(state)を持つことや コンポーネント のライフサイクルに応じた処理を記述できます。 以下では、 React で提供される基本的な React Hooks をその ユースケース とともに紹介します。 useState useEffect useContext useReducer useMemo useCallback useState useState は関数型 コンポーネント で状態(state)を扱うためのフックです。 以下は useState を利用する場合の基本的なコードです。 // 返り値はstateの変数とstateを更新するための関数 const [ state , setState ] = useState < T >( initStateValue ); useState は状態(state)の変数と状態(state)を更新するための関数を返します。 状態(state)の更新をする際は必ず更新用の関数を介して行う必要があります。 useState を使う ユースケース useState が必要となるのは、利用者と インタラクティブ にやり取りをする値を保持する必要がある場合です。 利用者と インタラクティブ にやり取りをするという場面の最も典型的な例はフォームです。 ここではログインフォームを題材にしてコードを紹介します。 作成するログインフォームは画像のように input 要素としてユーザー ID とパスワードを持つものを想定します。 初めにログインフォームで扱うデータの型(SampleLoginForm)を定義しておきます。 今回の例では userId と password だけをプロパティに持つオブジェクトとします。 interface SampleLoginForm { userId: string ; password: string ; } 作った SampleLoginForm 型の変数 formData を useState を使って定義します。 const [ formData , setFormData ] = useState < SampleLoginForm >( { userId: "" , password: "" , } ); あとは input 要素の value 属性に対応する formData の変数を渡します。 さらに input 要素の onChange イベントから setFormData を呼び出して formData の状態を更新します。 < div > < label htmlFor = "userId" > ユーザーID < /label > < input id = "userId" type= "text" name = "userId" placeholder = "ユーザーID" value = { formData.userId } onChange = { ( e ) => setFormData ( { ...formData , userId: e.target.value } ) } / > < /div > これにより、ユーザーがフォームに入力した文字(値)を変数 formData に保持できます。 ▶︎ クリックしてコード全文を見る // components/LoginForm.tsx import React , { useState } from "react" ; interface SampleLoginForm { userId: string ; password: string ; } export default function LoginForm () : JSX. Element { const [ formData , setFormData ] = useState < SampleLoginForm >( { userId: "" , password: "" , } ); const submitHandler = ( e: FormEvent < HTMLFormElement >) => { e.preventDefault (); console .log ( "ログインボタン押下" , formData ); } ; return ( < form onSubmit = { submitHandler } > < div > < label htmlFor = "userId" > ユーザーID < /label > < input id = "userId" type= "text" name = "userId" placeholder = "ユーザーID" value = { formData.userId } onChange = { ( e ) => setFormData ( { ...formData , userId: e.target.value } ) } / > < /div > < div > < label htmlFor = "password" > パスワード < /label > < input id = "password" type= "password" name = "password" placeholder = "パスワード" value = { formData.password } onChange = { ( e ) => setFormData ( { ...formData , password: e.target.value } ) } / > < /div > < div > < button type= "submit" > ログイン < /button > < /div > < /form > ); } useEffect useEffect は関数型 コンポーネント で副作用を実行するためのフックです。 副作用と聞くと仰々しいですが コンポーネント 内での「外部データの取得」「DOM の手動での更新」などの処理を、React では副作用と呼びます。 useEffect を使うための基本的なコードは以下のとおりです。 // 副作用を含む処理を記述した関数を記述する useEffect (() => { // 副作用処理 // … return () => { // クリーンアップ処理 } ; } , [] ); useEffect では副作用となる処理を関数内で記述します。 return で関数を返すことによってクリーンアップ処理を記述できます。 通常、 useEffect による副作用処理は コンポーネント の レンダリング 毎に実行されます。 副作用処理を毎回行わないためには、第2引数の依存配列によって制御できます。 useEffect の詳しい説明については以下の参考リンクをご覧ください。 副作用フックの利用法 https://ja.reactjs.org/docs/hooks-effect.html useEffect完全ガイド https://overreacted.io/ja/a-complete-guide-to-useeffect/ useEffect を使う ユースケース useEffect が必要となる代表的な ユースケース としては コンポーネント を呼び出したタイミングで外部 API からリソースを取得したい場合などです。 ここではサンプルの外部 API として JSONPlaceholder を利用して コンポーネント を呼び出したタイミングでデータを取得してみましょう。 JSONPlaceholder, https://jsonplaceholder.typicode.com/ 少し JSONPlaceholder について補足します。 JSONPlaceholder は 6 種類の構造のダミーデータを取得できます。 今回はタスク管理アプリケーションで一般的な ToDo リスト形式のデータを取得します。 取得したデータは先ほど紹介した setState を使って保持します。 まず取得する ToDo リストの型を定義しておきます。 interface ToDo { id: number ; userId: number ; title: string ; completed: boolean ; } そして先ほどの useState フックを使って取得する ToDo リスト形式を状態管理します。 const [ todoItemss , setToDos ] = useState < ToDo [] >( [] ); そして useEffect を使って実際に外部 API を呼び出し、状態を更新します。 外部 API の呼び出しには fetch を利用します。 useEffect (() => { const f = async () => { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); setToDos ( json ); } ; f (); } , [] ); 注意点として useEffect に渡す関数は同期的です。 そのため非同期関数(async/await)を使うには関数内で定義する必要があります。 補足ですが、次期アップデートの React v18 より React.Suspense を使った非同期のデータ取得がサポートされます。 アップデート後はこちらがベストプ ラク ティスになっていく可能性も高いため、公式ドキュメントの「React.Suspense」と「サスペンスを使ったデータ取得」についても、ぜひチェックをしてみてください。 React の最上位 API - React.Suspense, https://ja.reactjs.org/docs/react-api.html#reactsuspense サスペンスを使ったデータ取得(実験的機能), https://ja.reactjs.org/docs/concurrent-mode-suspense.html 取得した ToDo リスト形式のデータはスタイルを少し当てて Array.prototype.map() を使えば以下のように描画できます。 ▶︎ クリックしてコード全文を見る // components/ToDoList.tsx import React , { useEffect , useState } from "react" ; interface ToDo { id: number ; userId: number ; title: string ; completed: boolean ; } export default function ToDoList () : JSX. Element { const [ todoItems , setToDos ] = useState < ToDo [] >( [] ); useEffect (() => { const f = async () => { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); setToDos ( json ); } ; f (); } , [] ); return ( < div style = {{ textAlign: "left" }} > { todoItems.map (( todoItem ) => ( < div key = { todoItem.id } style = {{ width: "250px" , border: "solid" , margin: "8px" , padding: "8px" , }} > < h4 > { todoItem.title } < /h4 > < p style = {{ textAlign: "right" }} > { todoItem.completed ? "✅ 完了" : "未実施" } < /p > < /div > )) } < /div > ); } useContext useContext は コンポーネント 間で横断的に利用したい状態を管理するためのフックです。 通常、 コンポーネント では状態(データ)を props を通して親から子に渡します。 これを図に起こすと以下のようになります。 一方、コンテキストを使うと以下のように props を通さずにデータをやり取りできます。 コンテキストでは Context.Provider コンポーネント を通して横断的に利用したい状態を配信します。 そして必要な コンポーネント で useContext を使うことによって状態を購読します。 useContext を使って コンポーネント 内でコンテキストから配信される値を購読する基本的なコードは以下のようになります。 // 返り値はコンテキストから配信される値 // useContextの第1引数には`React.createContext`によって作成したコンテキストオブジェクトを渡す const value = useContext ( MyContext ); useContext では購読するコンテキストのオブジェクトを渡し、コンテキストから配信される値を受け取ります。 useContext を使う ユースケース ここまでで述べてきたように useContext を使うのは コンポーネント 間で横断的に利用したい状態がある場面です。 代表的な場面として認証情報の管理などがあります。 ここではコンテキストを使ってユーザー ID を管理することを例に説明します。 管理するユーザー ID は useState を用いて宣言し、その状態と更新用の関数をコンテキストを使って配信します。 // コンテキストで配信する値 const [ userId , setUserId ] = useState < number >( -1 ); 配信する値が決まったので、コンテキストで配信する値の型を定義します。 interface Context { userId: number ; setUserId: Dispatch < SetStateAction < number >>; } createContext を使ってコンテキストオブジェクトを作成します。型引数には先ほど定義した型を指定し、第 1 引数には初期値を与えます。 const AuthContext = createContext < Context >( { userId: -1 , setUserId: () => {} , } ); 次に Context の Provider を作成します。 Provider の value プロパティにコンテキストで配信する値を指定します。 const AuthProvider: React.FC = ( { children } ) => { // コンテキストで配信する値 const [ userId , setUserId ] = useState < number >( -1 ); return ( < AuthContext.Provider value = {{ userId , setUserId }} > { children } < /AuthContext.Provider > ); } ; // コンテキストオブジェクトとProviderをexportする export { AuthContext , AuthProvider } ; createContext で作成した AuthContext と AuthProvider を外部に公開(export)することでコンテキストを利用しやすくしています。 ▶︎ クリックしてコード全文を見る // contexts/auth.tsx import React , { createContext , Dispatch , SetStateAction , useState , } from "react" ; interface Context { userId: number ; setUserId: Dispatch < SetStateAction < number >>; } const AuthContext = createContext < Context >( { userId: -1 , setUserId: () => {} , } ); const AuthProvider: React.FC = ( { children } ) => { const [ userId , setUserId ] = useState < number >( -1 ); return ( < AuthContext.Provider value = {{ userId , setUserId }} > { children } < /AuthContext.Provider > ); } ; // コンテキストオブジェクトとProviderをexportする export { AuthContext , AuthProvider } ; 作成した AuthProvider を App.tsx に記述します。 これによりアプリケーション内のどの コンポーネント でも useContext を使って AuthContext から値を購読できます。 // App.tsx import React from "react" ; import { AuthProvider } from "./contexts/auth" ; import LoginForm from "./components/LoginForm" ; import ToDoList from "./components/ToDoList" ; export default function App () : JSX. Element { return ( < AuthProvider > < div style = {{ padding: "8px" , textAlign: "center" }} > < LoginForm / > < ToDoList / > < /div > < /AuthProvider > ); } 実際に LoginForm と ToDoList コンポーネント でコンテキストを使ってみましょう。 まず LoginForm コンポーネント 内でフォーム送信時にコンテキストの userId を更新してみます。 // AuthContextからuserIdを更新する関数setUserIdを購読 const { setUserId } = useContext ( AuthContext ); // form要素のsubmitイベントを処理する関数 const submitHandler = ( e: FormEvent < HTMLFormElement >) => { e.preventDefault (); console .log ( "ログインボタン押下" , formData ); // AuthContextで配信される値userIdを更新 setUserId ( 1 ); } ; 次に ToDoList コンポーネント でコンテキストから userId を購読します。 そして useEffect で userId の状態を監視し、初期値(-1)でない場合に外部 API からリソースを取得するようにします。 // AuthContextからuserIdを購読 const { userId } = useContext ( AuthContext ); useEffect (() => { const f = async () => { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); setToDos ( json ); } ; // userId が初期値でない場合に外部APIコール if ( userId !== -1 ) { f (); } } , [ userId ] ); 以上でログインフォームのログインボタンを押下することで AuthContext の userId を更新し、その変更を検知して ToDo リストの情報を外部 API から取得する処理が実現できます。 useReducer useReducer は useState よりも複雑な状態を管理するためのフックです。 公式ドキュメントでは「 useState の代替品」として位置づけられています。 useReducer を使うための基本的なコードは以下のとおりです。 // `useState`の代替品。返り値はstateの変数とstateを更新するためのDispatch関数 const [ state , dispatch ] = useReducer ( reducer , initialArg , init ); useReducer を理解するためには 4 つの要素を理解する必要があります。 State … 状態 Reducer … State を更新するための関数 Action … State を更新するのに必要なデータ Dispatch … Action を Reducer に届ける関数 この 4 つの要素は図のような関係になります。 useReducer を使う ユースケース アプリケーション開発を進めていくと処理が複雑になるにつれて、管理しなければならない状態(state)が増えていきます。 また実際には、相互に関連する状態を更新しなければならない場面も増えます。 そのような場面で力を発揮するのが useReducer フックです。 例えば、先ほどの useEffect フックでを使った外部 API からのリソース取得を例に考えてみましょう。 外部リソースの取得では取得までに時間を要しますので読み込み中か否かを isLoading のような形で状態管理する必要があるでしょう。 さらにデータ取得時のエラーハンドリングを考えるとエラーが発生したかを error のような変数で状態管理する必要があります。 これらを useState フックで管理する場合は以下のようになります。 const [ todos , setToDos ] = useState < ToDo [] >( [] ); const [ isLoading , setIsLoading ] = useState < boolean >( true ); const [ error , setError ] = useState < boolean >( false ); このように複数の値に関連する状態を管理する場面で useReducer を使うことを考えます。 まず useReducer で管理する状態の型とその状態の初期値を定義します。 // 管理する状態の型 interface State { todoItems: ToDo [] ; isLoading: boolean ; error: boolean ; } // 状態の初期値 const initState: State = { todoItems: [] , isLoading: true , error: false , } ; 次に状態を更新するためのデータとなるアクションの型を定義します。 今回は状態を更新する操作として以下の 2 種類を考えます。 SET_TODOS … ToDo リストにアイテムをセットする操作。アクションは ToDo リストにセットするデータを含む。 SET_ERROR … エラーが発生した際にエ ラーフラ グを True にする操作。アクションはデータを持たない。 これらを型に起こします。 // アクションの種類 type ActionType = "SET_TODOS" | "SET_ERROR" ; // アクションの型 interface Action { type : ActionType ; payload?: ToDo [] ; } 上で定義した型を使って reducer 関数を作成します。 import { Reducer } from "react" ; const reducer: Reducer < State , Action > = ( state , action ) => { switch ( action. type) { case "SET_TODOS" : if ( ! action.payload ) { // payloadが含まれていなければエラー扱いにする return { ...state , error: true , isLoading: false , } ; } return { ...state , ...action.payload , isLoading: false , } ; case "SET_ERROR" : return { ...state , error: true , isLoading: false , } ; } } ; この reducer 関数と状態の初期値を使って useReducer を宣言します。 const [{ todoItems , error , isLoading } , dispatch ] = useReducer ( reducer , initState ); そして先程の useEffect 内で状態を更新していた部分を dispatch にアクションを渡すことで状態を更新するように書き換えます。 useEffect (() => { const f = async () => { try { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); dispatch ( { type : "SET_TODOS" , payload: { todoItems: json } } ); } catch ( e ) { console .log ( e ); dispatch ( { type : "SET_ERROR" } ); } } ; f (); } , [] ); この例だともともとがそこまで複雑な状態管理ではなかったため、 useReducer を使った記述が冗長だと感じるかもしれません。 どのタイミングで useReducer を使うのかは、個人/チーム次第ではありますが、うまく使うことで状態管理をわかりやすくできます。 ▶︎ クリックしてコード全文を見る // components/ToDoList.tsx import React , { Reducer , useEffect , useReducer } from "react" ; interface ToDo { id: number ; userId: number ; title: string ; completed: boolean ; } interface State { todoItems: ToDo [] ; isLoading: boolean ; error: boolean ; } const initState: State = { todoItems: [] , isLoading: true , error: false , } ; type ActionType = "SET_TODOS" | "SET_ERROR" ; interface Action { type : ActionType ; payload?: Partial < State >; } const reducer: Reducer < State , Action > = ( state , action ) => { switch ( action. type) { case "SET_TODOS" : if ( ! action.payload ) { // payloadが含まれていなければエラー扱いにする return { ...state , error: true , isLoading: false , } ; } return { ...state , ...action.payload.todoItems , isLoading: false , } ; case "SET_ERROR" : return { ...state , error: true , isLoading: false , } ; } } ; export default function ToDoList () : JSX. Element { const [{ todoItems , error , isLoading } , dispatch ] = useReducer ( reducer , initState ); useEffect (() => { const f = async () => { try { const res: Response = await fetch ( "https://jsonplaceholder.typicode.com/todos" ); const json: ToDo [] = await res.json (); dispatch ( { type : "SET_TODOS" , payload: { todoItems: json } } ); } catch ( e ) { console .log ( e ); dispatch ( { type : "SET_ERROR" } ); } } ; f (); } , [] ); return ( <> { isLoading ? ( < p > ロード中です… < /p > ) : error ? ( < p > エラーが発生しました。 < /p > ) : ( < div style = {{ textAlign: "left" }} > { todoItems.map (( todoItem ) => ( < div key = { todoItem.id } style = {{ width: "250px" , border: "solid" , margin: "8px" , padding: "8px" , }} > < h4 > { todoItem.title } < /h4 > < p style = {{ textAlign: "right" }} > { todoItem.completed ? "✅ 完了" : "未実施" } < /p > < /div > )) } < /div > ) } < / > ); } useMemo useMemo は関数の返り値をメモ化するフックです。 メモ化はプログラムの最適化技法の 1 つで、計算結果を再利用するために保持して、再計算を防ぐものです。 そのため useMemo は最適化のためのフックという位置付けです。 useMemo を使うための基本的なコードは以下のとおりです。 // 返り値は関数の計算結果をメモ化した値 // 第2引数の依存配列に含まれる値が変更された時に再計算される const memoizedValue = useMemo < T >(() => computeExpensiveValue ( a , b ), [ a , b ] ); useMemo を使う ユースケース 基本的には最適化のためのフックですが、例えば配列を保持する state で配列を走査する処理が頻繁に必要な場合などに役立ちます。 ToDo リストの例で、一覧から完了済みのアイテムを useMemo によって取得することを考えてみましょう。 const [ todoItems , setToDos ] = useState < ToDo [] >( [] ); const completedItems = useMemo < ToDo [] >(() => { return todoItems.filter (( todoItem ) => todoItem.completed ); } , [ todos ] ); useMemo では依存配列に渡された state が更新された時にメモ化していた値を再計算します。 useCallback useCallback は関数をメモ化するフックです。 useCallback は最適化のためのフックという位置付けです。 そして useCallback を利用する場合は、基本的に React.memo と併用する必要があります。 React.memo と useCallback useCallback の話をする前に、 React.memo について簡単に説明します。 React の最上位 API - React.memo, https://ja.reactjs.org/docs/react-api.html#reactmemo すでに述べた通り、React では親 コンポーネント から子 コンポーネント に props を通してデータを渡します。 通常では、図中の点線で示した子 コンポーネント は親 コンポーネント が再描画されるタイミングで常に再描画されます。 React.memo はこの親 コンポーネント が再描画されるタイミングでの子 コンポーネント の再描画を最適化するものです。 React.memo では子 コンポーネント において、親 コンポーネント から受け取る props が再描画前の props と等価であれば、再描画をスキップします。つまり親 コンポーネント から子 コンポーネント に渡す props とその等価性が重要になります。 useCallback は props に渡す関数が等価であることを保証するためのフックです。 useCallback を使うための基本的なコードは以下のとおりです。 // 返り値はメモ化された関数 // 第2引数の依存配列に含まれる値が変更された時に再計算される const memoizedCallback = useCallback (() => { doSomething ( a , b ); } , [ a , b ] ); これにより子 コンポーネント では、props 受け取った関数が useCallback の第2引数の依存配列に含まれる値が変更されていない限りは等価なものとして扱えます。 useCallback を使う ユースケース ここまで説明した通り、 useCallback は最適化の流れで、子 コンポーネント の props に関数を渡す必要が生じた際に利用します。 React.memo は使用しませんが、props に関数を渡す場面を先ほどのログインフォームの例で見てみましょう。 まずフォームの状態を useState を使って定義していました。 const [ formData , setFormData ] = useState < SampleLoginForm >( { userId: "" , password: "" , } ); そして input 要素の onChange プロパティに関数を記述し formData の値を更新していました。 < input id = "userId" type= "text" name = "userId" placeholder = "ユーザーID" value = { formData.userId } onChange = { ( e ) => setFormData ( { ...formData , userId: e.target.value } ) } / > この onChange プロパティに渡す関数を useCallback で記述すると以下のようになります。 // inputタグのonChangeイベントを処理する関数 const onChangeHandler = useCallback (( e: ChangeEvent < HTMLInputElement >) => { setFormData (( prev: SampleLoginForm ) => { return { ...prev , [ e.target.name ] : e.target.value } ; } ); } , [] ); useCallback では第 2 引数の依存配列に含まれる値が変更されたタイミングで再度メモ化されるため、依存配列に含まれる値が少なくなるように意識する必要があります。 この例では、更新時に formData を参照せず、 setFormData 関数内で直前の formData の値を受けることよって依存配列が空になるようにしています。 これにより onChangeHandler 関数はメモ化が働き、 React.memo と併用した最適化ができます。 React Hooks を正しく使うために フックは一見すると JavaScript の関数ですが、正しく使う際には、ルールに従う必要があります。 特に useEffect や useMemo 、 useCallback といった依存配列を含むフックの使用では、依存関係の漏れによってバグを混入する恐れがあります。 フックを正しく利用するために、ESLint の eslint-plugin-react-hooks プラグイン を導入しておくことがお勧めです。 exhaustive-deps ルールを有効にすれば、依存配列が正しく記述されていない場合に警告を出すこともできます。 eslint-plugin-react-hooks https://www.npmjs.com/package/eslint-plugin-react-hooks おわりに 本記事では、React で提供される基本的な React Hooks をその ユースケース とともに紹介しました。 ここでは紹介できなかった React Hooks やカスタムフック、テスト方法なども今後、紹介できればと思います。 明日(12/14)は Toshihiro Nakamura さんから「Kotlinでデータベースアクセス」の記事が公開される予定です。 そちらもぜひご覧ください。 執筆: @yamada.y 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
これは 電通国際情報サービス アドベントカレンダー の12日目の記事です。 こんにちは。 電通国際情報サービス 金融ソリューション事業部 石沢です。 本記事は当部門で5年ほど前から継続している組織としてのPostmortem(ポストモーテム)活動「失敗学」をご紹介します。様々な用語で類似の活動をされている組織も多いと思いますが、良き システム開発 を実施するためのヒントとなれば幸いです。 Postmortem(トラブル事後分析)文化について  Postmortemは直訳すると検死解剖ですが、 システム開発 の文脈では現在「トラブルの事後分析」という意味で使われています。有名なところでは サイトリライアビリティエンジニアリング The DevOps ハンドブック 理論・原則・実践のすべて 他多数の書籍で紹介されている概念です。 簡単にいうと、 システムやサービスにおいて発生したインシデント(障害等)の対応が完了した後に、 その内容や解消のために行われたアクション、 再発を防止するためのフォローアップについて、 文書化しレビュー、 共有すること を、 システム開発 の現場ではPostmortemと呼んでいます。障害報告書と似ていますが、顧客や上司に対する説明文書ではなく組織やチームが学習するために作成するものです。日本語だと ドリコムさんがポストモーテム例を公開 しているので、こちらを見るとより理解が深まるかもしれません。  特に近年、ITシステムそのものが複雑になったこと、およびITシステムを取り巻くビジネス環境も複雑になってきたことからPostmortemのような事後検証の重要性は高まっています。われわれは専門家として様々な技術・技法だけでなく、失敗からも学ばなければいけないというわけです。  Postmortemについてより詳しく学びたい場合は、 PagerDutyの公開しているガイド がオススメです。本文は英語ですが 機械翻訳 でもけっこう読めます。 SIer 組織におけるPostmortemの難しさ  「発生した問題から学ぶ」と言うと反対する人はほとんどいません。しかし、これを実行し定着させていくのはなかなか難しいものです。特に弊社のような システムインテグレーター 色の強い組織・部門では開発チームを取り巻くコンテキストが千差万別であるという課題があります。 受託開発、製品開発、サービス開発、研究開発など 顧客、 ドメイン も異なる 短期プロジェクト、長期プロジェクト、無期限のプロダクト開発など期間もバラバラ アジャイル から ウォーターフォール までバラエティに富んだ 開発プロセス 利用技術、言語なども様々  もちろんチーム別にPostmortemをやっていくのであれば問題はありません。ただそうすると、組織全体の学びの範囲がだいぶ狭まってしまいます。一方で当部門はお客様のミッション・クリティカルな システム開発 を担当することも多いため、特定のプロジェクトで発生した問題はできる限り広く横連携したいという想いもあります。事業会社の開発チームでPostmortemを行うのとは、ちょっと異なる悩みがありました。 勝手連 事故調(ジコチョー)モデル  様々なコンテキストが詰め込まれた システムインテグレーター 組織でPostmortemを行うことは難しいということを説明しました。この解決策として、当部門では「失敗学」で有名な畑村 洋太郎先生の提唱する「 勝手連 事故調」モデルを(勝手に)取り込ませていただいています。「 勝手連 事故調」は書籍「 失敗学実践講義 」で紹介されている、事故災害が発生した際に公的機関や特定企業が主体となって設立される 事故調査委員会 (事故調)とは異なる、 手弁当 で何の誓約も受けずに好き勝手に行う調査委員会のことです。「 勝手連 事故調」は「責任追及のための調査」ではない「原因究明のための科学的調査」を行います。この活動をモデルとして、当部門では題材となるシステム障害や開発時の失敗などについて、当事者とは異なる第 三者 を中心としたチームでPostmortemを行うようにしています。   勝手連 事故調モデルのメリットは次のようなものがあります。 責任追及のための調査となりにくい。第 三者 が中心に調査を行うので、当事者や利害関係者が深く関与すると働きがちな「チームの論理」(責任問題や、問題原因を人に帰属させがち)を排除できる 第 三者 が検討するので、当事者が非難されることはない 第 三者 の検討によって、当事者が気づきにくい組織課題や真因に近づきやすくなる(場合によっては、当事者が導出した結論と異なる分析結果を出すこともできる) 第 三者 が抽象化・一般化することを通じて、当事者チーム以外のメンバーでも理解できる教訓が抽出、共有できる 勝手連 事故調に情報が集約されることで、当事者に対して他のプロジェクトの対応事例などもアド バイス が可能  私の所属する部門では、この 勝手連 事故調モデルによるPostmortem活動(内部的には「失敗学分科会」)を運用しています。 現状の運用について  現在シニアからジュニアまで多様なメンバーで構成されたチームにて、以下のような運用を実施しています。 部門で発生した題材事象(システム障害事例や開発トラブルなど)を定期的に収集する 第 三者 の分析チームにて、題材を見ながらどのような論点があるか議論する(通称:味見) 第 三者 チームの中で担当を決めて、担当が ヒアリ ングと調査を行う 得られた情報を分析して、分析する。組織として学ぶべき教訓を抽出する 第 三者 チームで分析結果と教訓についてレビューする(通称:味わい) 得られた結果を取りまとめて、部門に広く共有する  そこそこ安定して運用できていますが、活動当初はいろいろありました。畑村 洋太郎先生が著書「失敗学のすすめ」等でも述べていますが、日本人は「失敗は恥」と考える文化があります。活動初期、発生トラブルの ヒアリ ングに行こうとすると担当者の上司から「あれは失敗ではなかったので、 ヒアリ ングはしないでほしい」とか「本人たちは十分に反省しているので、そっとしておいてほしい」などという意見を受けることもありました。これらはまさに「失敗は恥」という文化に起因するものでしょう。  活動継続により、この問題については組織として乗り越えることができました。いまはプロジェクトでトラブルが発生すると「おっ、これは失敗学行きだね」「しっかりと教訓にしていこう」という前向き(?)な会話も増えています。地道な活動によっていくらかは「失敗は恥」から「失敗は学びのチャンス」に変えられたと思います。  またプロジェクト横断の問題事例共有をしていく中で、トレンドの分析もできるようになりました。複数のプロジェクトで同じようなトラブルが発生していれば、それは組織としての教育施策やサポート体制の不足かもしれないと疑うことができます。必要に応じて臨時的に部署内で勉強会などを開催して補強するようになりました。  加えて、当初は想定しなかった意外な効果として、 勝手連 事故調である第 三者 チームが「学習の場」としても有効であることがわかりました。第 三者 チームの年次的にもスキル的にも多様なメンバーで、フレッシュなトラブルを題材に「どうやって分析するか」といった議論に始まり、「そういえば昔はこういう事がよくあったなぁ」「過去にこういった対応を実施したことがあった」などという組織の 暗黙知 の交換まで行われるようになりました。経験値の継承という意味でも良い場になっていると考えています。 今後の課題  現時点では大きな問題もなく運営出来ているのですが、中期的には第 三者 チームのメンバーが固定化しないようなローテーションは実施してく必要があります。あとは良い感じにデータベース化出来ていないのが悩みです。うまくやれば、イイ感じに解析や類推検索できるようになったりする気もするんですけどね。  本記事では失敗学を使ったPostmortem文化の定着の試みを紹介させていただきました。皆さんの組織ではどのような工夫をされていますか? ぜひ紹介いただければと思います。   電通国際情報サービス アドベントカレンダー はまだまだ続きます。明日のポストもぜひお楽しみください! 執筆: Ishizawa Kento (@kent) 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
これは 電通国際情報サービス アドベントカレンダー の12日目の記事です。 こんにちは。 電通国際情報サービス 金融ソリューション事業部 石沢です。 本記事は当部門で5年ほど前から継続している組織としてのPostmortem(ポストモーテム)活動「失敗学」をご紹介します。様々な用語で類似の活動をされている組織も多いと思いますが、良き システム開発 を実施するためのヒントとなれば幸いです。 Postmortem(トラブル事後分析)文化について  Postmortemは直訳すると検死解剖ですが、 システム開発 の文脈では現在「トラブルの事後分析」という意味で使われています。有名なところでは サイトリライアビリティエンジニアリング The DevOps ハンドブック 理論・原則・実践のすべて 他多数の書籍で紹介されている概念です。 簡単にいうと、 システムやサービスにおいて発生したインシデント(障害等)の対応が完了した後に、 その内容や解消のために行われたアクション、 再発を防止するためのフォローアップについて、 文書化しレビュー、 共有すること を、 システム開発 の現場ではPostmortemと呼んでいます。障害報告書と似ていますが、顧客や上司に対する説明文書ではなく組織やチームが学習するために作成するものです。日本語だと ドリコムさんがポストモーテム例を公開 しているので、こちらを見るとより理解が深まるかもしれません。  特に近年、ITシステムそのものが複雑になったこと、およびITシステムを取り巻くビジネス環境も複雑になってきたことからPostmortemのような事後検証の重要性は高まっています。われわれは専門家として様々な技術・技法だけでなく、失敗からも学ばなければいけないというわけです。  Postmortemについてより詳しく学びたい場合は、 PagerDutyの公開しているガイド がオススメです。本文は英語ですが 機械翻訳 でもけっこう読めます。 SIer 組織におけるPostmortemの難しさ  「発生した問題から学ぶ」と言うと反対する人はほとんどいません。しかし、これを実行し定着させていくのはなかなか難しいものです。特に弊社のような システムインテグレーター 色の強い組織・部門では開発チームを取り巻くコンテキストが千差万別であるという課題があります。 受託開発、製品開発、サービス開発、研究開発など 顧客、 ドメイン も異なる 短期プロジェクト、長期プロジェクト、無期限のプロダクト開発など期間もバラバラ アジャイル から ウォーターフォール までバラエティに富んだ 開発プロセス 利用技術、言語なども様々  もちろんチーム別にPostmortemをやっていくのであれば問題はありません。ただそうすると、組織全体の学びの範囲がだいぶ狭まってしまいます。一方で当部門はお客様のミッション・クリティカルな システム開発 を担当することも多いため、特定のプロジェクトで発生した問題はできる限り広く横連携したいという想いもあります。事業会社の開発チームでPostmortemを行うのとは、ちょっと異なる悩みがありました。 勝手連 事故調(ジコチョー)モデル  様々なコンテキストが詰め込まれた システムインテグレーター 組織でPostmortemを行うことは難しいということを説明しました。この解決策として、当部門では「失敗学」で有名な畑村 洋太郎先生の提唱する「 勝手連 事故調」モデルを(勝手に)取り込ませていただいています。「 勝手連 事故調」は書籍「 失敗学実践講義 」で紹介されている、事故災害が発生した際に公的機関や特定企業が主体となって設立される 事故調査委員会 (事故調)とは異なる、 手弁当 で何の誓約も受けずに好き勝手に行う調査委員会のことです。「 勝手連 事故調」は「責任追及のための調査」ではない「原因究明のための科学的調査」を行います。この活動をモデルとして、当部門では題材となるシステム障害や開発時の失敗などについて、当事者とは異なる第 三者 を中心としたチームでPostmortemを行うようにしています。   勝手連 事故調モデルのメリットは次のようなものがあります。 責任追及のための調査となりにくい。第 三者 が中心に調査を行うので、当事者や利害関係者が深く関与すると働きがちな「チームの論理」(責任問題や、問題原因を人に帰属させがち)を排除できる 第 三者 が検討するので、当事者が非難されることはない 第 三者 の検討によって、当事者が気づきにくい組織課題や真因に近づきやすくなる(場合によっては、当事者が導出した結論と異なる分析結果を出すこともできる) 第 三者 が抽象化・一般化することを通じて、当事者チーム以外のメンバーでも理解できる教訓が抽出、共有できる 勝手連 事故調に情報が集約されることで、当事者に対して他のプロジェクトの対応事例などもアド バイス が可能  私の所属する部門では、この 勝手連 事故調モデルによるPostmortem活動(内部的には「失敗学分科会」)を運用しています。 現状の運用について  現在シニアからジュニアまで多様なメンバーで構成されたチームにて、以下のような運用を実施しています。 部門で発生した題材事象(システム障害事例や開発トラブルなど)を定期的に収集する 第 三者 の分析チームにて、題材を見ながらどのような論点があるか議論する(通称:味見) 第 三者 チームの中で担当を決めて、担当が ヒアリ ングと調査を行う 得られた情報を分析して、分析する。組織として学ぶべき教訓を抽出する 第 三者 チームで分析結果と教訓についてレビューする(通称:味わい) 得られた結果を取りまとめて、部門に広く共有する  そこそこ安定して運用できていますが、活動当初はいろいろありました。畑村 洋太郎先生が著書「失敗学のすすめ」等でも述べていますが、日本人は「失敗は恥」と考える文化があります。活動初期、発生トラブルの ヒアリ ングに行こうとすると担当者の上司から「あれは失敗ではなかったので、 ヒアリ ングはしないでほしい」とか「本人たちは十分に反省しているので、そっとしておいてほしい」などという意見を受けることもありました。これらはまさに「失敗は恥」という文化に起因するものでしょう。  活動継続により、この問題については組織として乗り越えることができました。いまはプロジェクトでトラブルが発生すると「おっ、これは失敗学行きだね」「しっかりと教訓にしていこう」という前向き(?)な会話も増えています。地道な活動によっていくらかは「失敗は恥」から「失敗は学びのチャンス」に変えられたと思います。  またプロジェクト横断の問題事例共有をしていく中で、トレンドの分析もできるようになりました。複数のプロジェクトで同じようなトラブルが発生していれば、それは組織としての教育施策やサポート体制の不足かもしれないと疑うことができます。必要に応じて臨時的に部署内で勉強会などを開催して補強するようになりました。  加えて、当初は想定しなかった意外な効果として、 勝手連 事故調である第 三者 チームが「学習の場」としても有効であることがわかりました。第 三者 チームの年次的にもスキル的にも多様なメンバーで、フレッシュなトラブルを題材に「どうやって分析するか」といった議論に始まり、「そういえば昔はこういう事がよくあったなぁ」「過去にこういった対応を実施したことがあった」などという組織の 暗黙知 の交換まで行われるようになりました。経験値の継承という意味でも良い場になっていると考えています。 今後の課題  現時点では大きな問題もなく運営出来ているのですが、中期的には第 三者 チームのメンバーが固定化しないようなローテーションは実施してく必要があります。あとは良い感じにデータベース化出来ていないのが悩みです。うまくやれば、イイ感じに解析や類推検索できるようになったりする気もするんですけどね。  本記事では失敗学を使ったPostmortem文化の定着の試みを紹介させていただきました。皆さんの組織ではどのような工夫をされていますか? ぜひ紹介いただければと思います。   電通国際情報サービス アドベントカレンダー はまだまだ続きます。明日のポストもぜひお楽しみください! 執筆: Ishizawa Kento (@kent) 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
ISID X イノベーション 本部の山下です。 このポストは 電通国際情報サービス Advent Calendar 2021 の11日目のポストです。 中途入社したばかりで右も左も分からないのですが、楽しそうな企画ですので参加させていただくことにしました! 今回は自分が普段よく使っている プログラミング言語 「 Common Lisp 」の紹介記事を書かせていただきます。 はじめに 皆さん、 Common Lisp という プログラミング言語 をご存じでしょうか? 「括弧が多い」と有名な Lisp の方言の一つです。 情報系の学部などでは習ったことがあるかたもいらっしゃるかもしれません。 何はともあれ、 Common Lisp で書かれたHello, Worldを見てみましょう。 ( format t "Hello, World~%" ) ちょっと独特な感じの書き方になっていますね。 Common Lisp や Lisp 系の言語では、S式と呼ばれるリストを使ってプログラムを記述します。 関数呼び出しはもちろん、すべての構文がこのS式と呼ばれるリストで記述されます。 なお、 Common Lisp では、 ; 以降はコメントになります。 ;; これはコメントです。`;` 以降の文字はすべてコメントになります ;; 関数呼び出し ( 関数名 引数1 引数2 引数3 ) ;; if式 ;; 最初の式 (equal var1 0)の評価結果が真であるなら ;; (format t "var1 is zero")が実行されて ;; そうでないなら ;; (format t "var1 is not zero")が実行されます ( if ( equal var1 0 ) ( format t "var1 is zero" ) ( format t "var1 is not zero" )) 関数呼び出しやifなどの特殊な構文(special formと呼びます)でも基本的に同じ形のS式になっています。 ;; Common Lispでは基本的に以下のような式だけでプログラムが記述できます ( 関数名か構文を示す文字 引数1 引数2 .... ) 最初はとっつきづらいかもしれません。しかしS式の書き方は1種類しかないので些末な文法で迷うことなくなります。 このため慣れるとロジックに注力してプログラムを書けるようになってきます。 また Lisp で本当に実用的なプログラムが動いているのを見たことがないという声もたまに伺います。 そこで、今回はこの Common Lisp を使って実用的なプログラムの例として、 REST API を書く例をご紹介しようかと思います。 実用的なプログラムって書けるの? では、早速ですが、 REST API サーバを Common Lisp で記述してみましょう。 ゼロから全部実装するのは大変なので、他の言語と同じようにライブラリを使って実装してみます。 ( ql:quickload '( :ningle :clack )) quickloadという、 Python のpip、 Java の Maven のようなライブラリマネージャを利用してWebサーバ関連のライブラリを読み込みます。 ;; サーバの実体をグローバル変数として宣言、作成する ( defvar *app* ( make-instance ' ningle:<app> )) ;; ;; "/"にGETアクセスすると、Hello, World!が返ってくるAPI ;; ;; - setfは代入を実行する構文です ;; - (ningle:route *app* "/" :method :GET) はwebフレームワークのningleの提供する ;; アクセスがあった際に呼ばれるコールバック関数です ;; - lambda式は無名関数を宣言する構文です ;; 以下の式は、setf式を使って、"/"にGETアクセスがあった際に呼ばれるコールバック関数を、 ;; lambda式を用いて宣言した無名関数で上書き代入するということになります。 ( setf ( ningle:route *app* "/" :method :GET ) # '( lambda ( params ) "Hello, World!" )) ;; ;; "/hello"にPOSTアクセスした際のAPI実装 ;; このAPIは、usernameというパラメータを与えて実行すると ;; "hello, 与えたusername" と返し ;; パラメータがない場合には、 ;; hello, somebody と返すような内容となっています ;; ;; 以下の式は、上で作成した"/"に対するAPIと同様に、"/hello"に対するPOSTアクセスが行われた際の ;; コールバック関数の上書きを行っています。 ;; ここでは、POST時の引数の処理を行うために、無名関数の宣言(lambda式)の中で ;; let式という変数を作成する構文を利用しています。 ;; ( setf ( ningle:route *app* "/hello" :method :POST ) # '( lambda ( params ) ;; let式を用いて、usernameという変数を宣言し、 ;; POST時のパラメータ(username)の値で初期化しています。 ( let (( username ( cdr ( assoc "username" params :test #'string= )))) ( if ( not ( null username )) ( format nil "Hello, ~A" username ) "Hello, somebody." )))) ( clack:clackup *app* ) これで完成です! 簡単ですね。 では早速アクセスしてみます。 まずはGETで"/"(ルート)にアクセスしてみます。 $ curl -s http://localhost:5000/ Hello, World! ちゃんとHello, Worldと返ってきましたね。 今度はPOSTで"/hello"にアクセスしてみましょう。 $ curl -s -XPOST -d "username=Yamashita" localhost:5000/hello Hello, Yamashita $ curl -s -XPOST localhost:5000/hello Hello, somebody. ちゃんと与えたパラメータを元に応答を返してくれました! REST API として正しく動作してそうですね。 IDE などについて Emacs で開発するのが一般的なのですが、VisualStudioCodeの プラグイン なども開発が進んでいるようです。 手元で試した限りだと小規模な開発では十分実用になりそうです。 https://marketplace.visualstudio.com/items?itemName=ailisp.commonlisp-vscode Emacs を利用する場合はSLIMEという定番の IDE 環境があります。 Emacs の キーバインド が苦手でないかたはこちらをお勧めします。 https://common-lisp.net/project/slime/ まとめ 今回は Common Lisp の簡単な紹介と、ライブラリを利用して REST API サーバの簡単な実装例を紹介してみました。 どうでしょうか、とっつきづらそうな Common Lisp ですが、簡単な読み方を覚えてしまえばそんなに括弧が気にならなくなったのではないでしょうか? また、 REST API なども実装できる十分に実用的な プログラミング言語 であるということも紹介できたのではないかと思います。 この記事で、 Common Lisp のことを括弧が多いだけの言語じゃなく、身近な問題を解決できる普通の プログラミング言語 だと思っていただければ幸いです。 お勧めの参考書など 最後に、 Common Lisp を学習するためのお勧めの書籍を紹介して終わろうかと思います。 Land of Lisp Common Lisp の入門から始まった一通りの使い方が学べる内容となっています。 挿絵の Lisp Alien がかわいいですね。 実践 Common Lisp タイトルの通り実践的な話に関して一通り書かれている書籍になります。 例えば、ファイルの入出力といった実際のプログラムを書く上では必要となるような情報がまとまっています。 このあたりの書籍が Common Lisp の入門にはお勧めです。 執筆: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
ISID X イノベーション 本部の山下です。 このポストは 電通国際情報サービス Advent Calendar 2021 の11日目のポストです。 中途入社したばかりで右も左も分からないのですが、楽しそうな企画ですので参加させていただくことにしました! 今回は自分が普段よく使っている プログラミング言語 「 Common Lisp 」の紹介記事を書かせていただきます。 はじめに 皆さん、 Common Lisp という プログラミング言語 をご存じでしょうか? 「括弧が多い」と有名な Lisp の方言の一つです。 情報系の学部などでは習ったことがあるかたもいらっしゃるかもしれません。 何はともあれ、 Common Lisp で書かれたHello, Worldを見てみましょう。 ( format t "Hello, World~%" ) ちょっと独特な感じの書き方になっていますね。 Common Lisp や Lisp 系の言語では、S式と呼ばれるリストを使ってプログラムを記述します。 関数呼び出しはもちろん、すべての構文がこのS式と呼ばれるリストで記述されます。 なお、 Common Lisp では、 ; 以降はコメントになります。 ;; これはコメントです。`;` 以降の文字はすべてコメントになります ;; 関数呼び出し ( 関数名 引数1 引数2 引数3 ) ;; if式 ;; 最初の式 (equal var1 0)の評価結果が真であるなら ;; (format t "var1 is zero")が実行されて ;; そうでないなら ;; (format t "var1 is not zero")が実行されます ( if ( equal var1 0 ) ( format t "var1 is zero" ) ( format t "var1 is not zero" )) 関数呼び出しやifなどの特殊な構文(special formと呼びます)でも基本的に同じ形のS式になっています。 ;; Common Lispでは基本的に以下のような式だけでプログラムが記述できます ( 関数名か構文を示す文字 引数1 引数2 .... ) 最初はとっつきづらいかもしれません。しかしS式の書き方は1種類しかないので些末な文法で迷うことなくなります。 このため慣れるとロジックに注力してプログラムを書けるようになってきます。 また Lisp で本当に実用的なプログラムが動いているのを見たことがないという声もたまに伺います。 そこで、今回はこの Common Lisp を使って実用的なプログラムの例として、 REST API を書く例をご紹介しようかと思います。 実用的なプログラムって書けるの? では、早速ですが、 REST API サーバを Common Lisp で記述してみましょう。 ゼロから全部実装するのは大変なので、他の言語と同じようにライブラリを使って実装してみます。 ( ql:quickload '( :ningle :clack )) quickloadという、 Python のpip、 Java の Maven のようなライブラリマネージャを利用してWebサーバ関連のライブラリを読み込みます。 ;; サーバの実体をグローバル変数として宣言、作成する ( defvar *app* ( make-instance ' ningle:<app> )) ;; ;; "/"にGETアクセスすると、Hello, World!が返ってくるAPI ;; ;; - setfは代入を実行する構文です ;; - (ningle:route *app* "/" :method :GET) はwebフレームワークのningleの提供する ;; アクセスがあった際に呼ばれるコールバック関数です ;; - lambda式は無名関数を宣言する構文です ;; 以下の式は、setf式を使って、"/"にGETアクセスがあった際に呼ばれるコールバック関数を、 ;; lambda式を用いて宣言した無名関数で上書き代入するということになります。 ( setf ( ningle:route *app* "/" :method :GET ) # '( lambda ( params ) "Hello, World!" )) ;; ;; "/hello"にPOSTアクセスした際のAPI実装 ;; このAPIは、usernameというパラメータを与えて実行すると ;; "hello, 与えたusername" と返し ;; パラメータがない場合には、 ;; hello, somebody と返すような内容となっています ;; ;; 以下の式は、上で作成した"/"に対するAPIと同様に、"/hello"に対するPOSTアクセスが行われた際の ;; コールバック関数の上書きを行っています。 ;; ここでは、POST時の引数の処理を行うために、無名関数の宣言(lambda式)の中で ;; let式という変数を作成する構文を利用しています。 ;; ( setf ( ningle:route *app* "/hello" :method :POST ) # '( lambda ( params ) ;; let式を用いて、usernameという変数を宣言し、 ;; POST時のパラメータ(username)の値で初期化しています。 ( let (( username ( cdr ( assoc "username" params :test #'string= )))) ( if ( not ( null username )) ( format nil "Hello, ~A" username ) "Hello, somebody." )))) ( clack:clackup *app* ) これで完成です! 簡単ですね。 では早速アクセスしてみます。 まずはGETで"/"(ルート)にアクセスしてみます。 $ curl -s http://localhost:5000/ Hello, World! ちゃんとHello, Worldと返ってきましたね。 今度はPOSTで"/hello"にアクセスしてみましょう。 $ curl -s -XPOST -d "username=Yamashita" localhost:5000/hello Hello, Yamashita $ curl -s -XPOST localhost:5000/hello Hello, somebody. ちゃんと与えたパラメータを元に応答を返してくれました! REST API として正しく動作してそうですね。 IDE などについて Emacs で開発するのが一般的なのですが、VisualStudioCodeの プラグイン なども開発が進んでいるようです。 手元で試した限りだと小規模な開発では十分実用になりそうです。 https://marketplace.visualstudio.com/items?itemName=ailisp.commonlisp-vscode Emacs を利用する場合はSLIMEという定番の IDE 環境があります。 Emacs の キーバインド が苦手でないかたはこちらをお勧めします。 https://common-lisp.net/project/slime/ まとめ 今回は Common Lisp の簡単な紹介と、ライブラリを利用して REST API サーバの簡単な実装例を紹介してみました。 どうでしょうか、とっつきづらそうな Common Lisp ですが、簡単な読み方を覚えてしまえばそんなに括弧が気にならなくなったのではないでしょうか? また、 REST API なども実装できる十分に実用的な プログラミング言語 であるということも紹介できたのではないかと思います。 この記事で、 Common Lisp のことを括弧が多いだけの言語じゃなく、身近な問題を解決できる普通の プログラミング言語 だと思っていただければ幸いです。 お勧めの参考書など 最後に、 Common Lisp を学習するためのお勧めの書籍を紹介して終わろうかと思います。 Land of Lisp Common Lisp の入門から始まった一通りの使い方が学べる内容となっています。 挿絵の Lisp Alien がかわいいですね。 実践 Common Lisp タイトルの通り実践的な話に関して一通り書かれている書籍になります。 例えば、ファイルの入出力といった実際のプログラムを書く上では必要となるような情報がまとまっています。 このあたりの書籍が Common Lisp の入門にはお勧めです。 執筆: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター