TECH PLAY

株式会社ラクス

株式会社ラクス の技術ブログ

927

はじめに こんにちは!three_yagiです。 今回は自宅で使っている Mac と会社で使っている Windows 両方にDockerをインストールし、Docker Hubによるコンテナの共有を試してみたのでそちらの手順について説明してみようと思います。 はじめに Dockerってなに? Docker Hubってなに? WindowsでのDockerインストール 動作環境 手順 MacでのDockerインストール 動作環境 手順 Docker Hubでコンテナを共有 コンテナイメージをPushする docker build docker run docker push コンテナイメージをPullする docker pull おわりに Dockerってなに? Docker社が提供しているコンテナ型の仮想環境を作成、配布、実行するためのプラットフォームです。 コンテナを作成することでOSなどの違いに左右されずに環境を構築することができます。 Dockerやコンテナについては過去の記事で詳しく解説されていますので、ぜひそちらをご覧ください。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp Docker Hubってなに? 同じくDocker社が クラウド サービスとして提供しているDocker向けのコンテナ共有サービスです。 Docker Hubを利用することで他ユーザとのコンテナの共有を行うことができます。自分でコンテナイメージを作成する手間が省けたり、別の環境で同じコンテナを起動したりすることができます。 また、タグを利用して GitHub のようにバージョン管理を行うことも可能です。 利用にはユーザ登録が必要です。 以下の公式サイトの Sign Upからユーザを登録しておきましょう。 Docker Hub Windows でのDockerインストール まずは Windows へのインストールからです。 動作環境   Windows10 Pro (バージョン2004, OSビルド19041.572) 手順 まずは公式サイトからDocker Desktopをダウンロードしましょう。 Get Started with Docker | Docker 公式サイトからDownload for Windows を選択して インストーラ をダウンロードします。 ダウンロードした「Docker for Windows Installer」を起動すると以下の画面が表示されます。 Windows10のバージョン2004からWSL2が正式に導入され、Dockerのバックエンドとして利用できるようになりました。 以前までは Windows でDockerを利用する場合はVirtual Boxや Hyper-V を利用する必要がありましたが、WSL2を利用することでより軽量かつ高速にDockerを使用することができます。 ということで、今回はWSL2でDockerの環境を構築します。「Install required Windows components for WSL2」にチェックをいれてOKを押すとインストールが開始されます。 インストールが完了するとPCの再起動を求められます。 Close and restartをクリックして再起動します。 再起動し、晴れてDockerのインストール完了…となるはずでしたが、以下のようなアラートウインドウが表示されました。 どうやらWSL2のインストールが不完全なようです。 記載されているリンクからWSL2の Linux カーネル を最新化する更新プログラムをダウンロードしましょう。 以前のバージョンの WSL の手動インストール手順 | Microsoft Docs ダウンロードした更新プログラムを実行すると インストーラ が立ち上がるので指示に従ってインストールします。 インストール自体は数秒で完了するはずです。 インストール後、再度先程のアラートウインドウに戻り、RestartをクリックするとDocker Desktopが起動しました! 設定を確認すると、WSL2もちゃんと使われていることが確認できます。 これで Windows へのDockerインストールは完了です。 Mac でのDockerインストール 次に Mac へのDockerインストールについて説明します。 動作環境    MacOS Mojave (10.14) 手順 Windows と同様、公式サイトからDocker Desktopをダウンロードします。 Get Started with Docker | Docker ダウンロードした「Docker. dmg 」を実行すると以下のウィンドウが表示されます。DockerアイコンをApplicationフォルダにdrag and drop してDockerをアプリケーションに追加しましょう。 Launchpadに追加されたDockerアイコンをクリックするとDocker Desktopが起動します。 初回起動時にはDockerの機能がネットワークを使用することに対しての許可を求められます。"OK"を選択し、OSユーザーのパスワードを入力してこれを許可します。 Docker Desktopの初期画面が表示されました。 これで Mac へのDockerのインストールは完了です! Docker Hubでコンテナを共有 Mac とWindowにDockerがインストールできたので、Docker Hubを使って両者間でコンテナを共有してみます。 コンテナイメージをPushする まず Mac 側で共有するコンテナイメージを作成します。 GitHub の チュートリアル を参考にコンソール上に アスキーアート を表示するコンテナを作成します。 ターミナルを開いたら任意の作業 ディレクト リに移動して、git cloneでDocker リポジトリ をクローンします。 ----Mac----- $ git clone https://github.com/docker/doodle.git Cloning into 'doodle'... remote: Enumerating objects: 73, done. remote: Total 73 (delta 0), reused 0 (delta 0), pack-reused 73 Unpacking objects: 100% (73/73), done. docker build 次にコンテナイメージをビルドします。 ビルドは「docker build」コマンドで実行できます。「-t」オプションには作成するコンテナイメージのイメージ名を指定します。今回は"xxxxxxx(DockerHubアカウント名)/cheers2019"をイメージ名としてビルドしました。 ----Mac---- $ cd doodle/cheers2019 $ docker build -t xxxxxxx/cheers2019 . [+] Building 66.4s (12/12) FINISHED => [internal] load build definition from Dockerfile 0.0s 〜〜中略〜〜 => => naming to docker.io/xxxxxxx/cheers2019 docker run ビルドが完了するとコンテナを起動することができます。 「docker run」コマンドでコンテナを起動します。「-it」は対話的な操作を可能にするオプションで、「--rm」はコンテナ終了時にコンテナを自動的に削除するオプションです。 起動に成功すると"Cheer"の アスキーアート が出力されます。ぜひご自身の端末で試してみてください。 ----Mac---- $ docker run -it --rm xxxxxxx/cheers2019 docker push 作成したコンテナイメージをDocker Hubにプッシュしてみます。 「docker login」でDocker Hubにログインします。先程登録したログインIDとパスワードを入力して自分のアカウントにログインします。 「docker push」で自身のアカウントのDocker Hubのレポジトリにコンテナイメージをプッシュできます。 "Pushed"と表示されたら成功です。 ----Mac---- $ docker login Username: xxxxx Password: Email: XXXXXX@XXXXXXX Login Succeeded $ docker push xxxxxxx/cheers2019 The push refers to repository [docker.io/xxxxxxx/cheers2019] 1e2fa4a93dd1: Pushed latest: digest: sha256:7be1af3fac3de80eac34491a7e031123ba5488d92a8f2e563c2a3b057d3253b7 size: 527 ブラウザのDocker Hubにログインするとプッシュしたコンテナイメージが存在することを確認できました。 コンテナイメージをPullする ではDocker Hub上にあるコンテナイメージを Windows 側でPullしてみましょう。 docker pull コマンドプロンプト を起動し「docker login」でDocker Hubにログインします。 「docker pull」でGit Hubの レジストリ からコンテナイメージをローカルにもってきます。 "Pull complete"と表示されたら成功です。 ----Windows---- > docker pull xxxxxxx/cheers2019 Using default tag: latest latest: Pulling from xxxxxxx/cheers2019 3b5197dd02a5: Pull complete Digest: sha256:7be1af3fac3de80eac34491a7e031123ba5488d92a8f2e563c2a3b057d3253b7 Status: Downloaded newer image for xxxxxxx/cheers2019:latest docker.io/xxxxxxx/cheers2019:latest 「docker images」でローカルにあるコンテナイメージを確認できます。 Pullしたコンテナイメージがローカルにあることを確認できました。 ----Windows---- >docker images REPOSITORY TAG IMAGE ID CREATED SIZE xxxxxxx/cheers2019 latest b0d6c9e01637 37 hours ago 4.01MB 「docker run」でコンテナの起動も実行してみたところ、 Mac で起動したときと同じ結果が出力されました。 これで Mac と Windows の間でコンテナの共有ができました。簡単ですね! ----Windows---- >docker run -it --rm xxxxxxx/cheers2019 ~~結果略~~ おわりに Docker Hubを利用して Windows と Mac の両者間でコンテナを共有することができました。 Docker Hubを活用することで別PC、別OSでも簡単に同じ環境を構築できます。複数台のPCをまたいでの開発でも環境構築に頭を悩ませることなく快適に開発できそうです。もちろん他の人とも共有できるので、複数人の開発でも効力を発揮しそうですね。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com
アバター
はじめに こんにちは、新卒1年目のYoshidaMichaelです。 私の所属しているチームでは1年目に OSS -DB Silverを取得するという目標があり、この度無事取得に至りましたので私の行った学習方法とその比較、今ゼロから学習を進めるならもっとこうできたな、といった内容をご紹介していきたいと思います。 学習前の知識量は簡単な SQL くらいはわかるけれど運用の知識や細かい SQL は全くわからない程度です。 学習期間は2週間半 (時間にして55時間位?) 、得点は94点でした (合格ライン64点) 。 OSS -DB Silverのスコアレポート はじめに OSS-DB Silverとは 学習方法 学習するときに意識していたこと 学習に使った教材 学習の進め方 反省点 所感 終わりに OSS -DB Silverとは 「そもそも OSS -DB Silverとはなんぞや?」ということなんですが、 特定非営利活動法人 LPI-Japanが オープンソース データベースに関する技術力と知識を公平かつ厳正に、中立的な立場で認定してくれる試験です。Silverの他にGoldというより上位のレベルも存在します。 (とはいえSilverとGoldは全く別物という話もありますので、GoldができればSilverの範囲はマスターできているかと言われるとそれは違うのかなと思ったり) oss-db.jp 学習方法 学習するときに意識していたこと 私が学習を進める上で一番意識していたことは「どういう機能があるか、何ができて何ができないかを覚える。」です。 正直試験なので調べたら一発でわかるようなオプション周りの話とかも出るのですが、その辺りは結構無理して丸暗記しながらも調べてもわからないような「そもそも何ができるか」というところを意識して覚えるようにしていました。 資格だけ取れても使い物にならない技術だと意味ないと個人的には思いますので。 学習に使った教材 OSS 教科書 OSS -DB Silver Ver2.0対応 www.amazon.co.jp 教科書としては優秀だと思います。 会社にあったので辞書的に活用していました。 正直「問題」という点ではあまり試験に役に立ったという実感はないので他でたくさん解いたほうがいいと思います。 本の初めに付いてるチェックリストは試験直前に見返す上で役に立ちました。 サンプル問題/例題解説 oss-db.jp 公式にあるサンプル問題と例題解説です。 サンプルと言いながらもかなり問題数があり、これを2周3周するだけでも十分な力がつくと思います。 ただ、解説はそこまで丁寧ではないので、ここで間違えた問題を OSS 教科書や公式ドキュメント等で調べるのが良いかと。 Ping -t 最強WEB問題集 OSS -DB Silver (Ver2.0) ping-t.com たまたま別件で有料会員だったのでついでにと思い使っていました。 問題数が505問と豊富なこと、解説がそこそこ丁寧なこと、 スマホ 対応なことなど色々便利でした。 これだけやっていても合格はできると思いますが、得点を取るという部分にやや傾倒しているのを感じたので、 PostgreSQL の知識を深めたいという方はその他の方法と合わせたほうが良いかと思います。 学習の進め方 これらの教材を使った上で私は次のように学習を進めていきました。 後述しますが、後から思い返すと1. の部分は無くても良かったかなと思いました。 まず OSS 教科書を一通り読む (約3日) OSS -DBに関してほとんど知識がなかったので、まずは全体観を掴むために3日ほどかけて全部読みました。 この時点では知識の定着を目的とはしていなかったので、「あー、こんなのがあるんだー」くらいで流し読みしました。 サンプル問題、 Ping -tの問題をひたすら解く (約12日) 1周目の時点では OSS 教科書の内容はあまり定着しておらず、ほとんど間違えると思います。 そこで間違えた問題に対して初めて深く学習します。 Ping -t有料会員の方はその解説で学習してもいいと思いますし、そうでない方は OSS 教科書や公式ドキュメントを辞書的に使うといいと思います。 このときも、丸暗記しないといけない部分は仕方ないですが、理由がある部分はそれも合わせて覚えたほうが良いと思います。 ある程度知識が定着してきた単元については通勤中にも Ping -tで問題を解いたりしていました。こういうところは強いですね、 Ping -t。 OSS 教科書の模擬問題を解く (約半日) これはチームのルールとして模擬問題である程度 (9割) 点数が取れてから受験というものがあるので受けました。 サンプル問題で8割、 Ping -tで9割程度正答していましたが模試の結果は68%と結構凹みました。 間違った部分の復習は当然必要ですが、今までの学習がゼロになったわけではないのでもし点数が低くても前向きに学習を続ければ良いと思います。 適宜復習 (約1日) もうここまで来ればあとは各々不足している部分を補う段階かと思います。 OSS 教科書をお持ちの方ははじめについているチェックシートがさっと見直せて個人的には大変便利でした。 反省点 結果無事合格はできたのですが、今この経験を持った上でゼロからまた受験をするとしたらこうしたほうが良かったなという点がいくつかあるので挙げていきます。 はじめの OSS 教科書を一通り読む段階が不要だった どうせ定着する形で頭に入っていないためです。 目次を見て、各箇所の概要説明だけ読んですぐに問題に移行したほうが良かったなと思いました。 サンプル問題、 Ping -tで視野が狭まっていることに気が付かなかった 私の体感ですが、情報の深さは 公式ドキュメント > OSS 教科書 > サンプル問題 > Ping -t の順で深いと思いました。 サンプル問題や Ping -tの問題を解くだけであれば知らなくても良いオプション等についても OSS 教科書には載っているなんてことも何度もありました。 OSS 教科書を推すわけではありませんが、公式のドキュメントなど、より濃い情報があるものをもっと活用するべきだと感じました。 あまり手を動かさなかった しっかりと環境を用意するのが面倒で、職場以外ではあまり手を動かした学習をしていませんでした。 やはりコマンドを実際に打ち込んでその結果を見ると頭に残りますし、何より楽しいので少し時間を使ってもそのような環境は作ったほうが良いと思いました。 最近だとDockerなどを使うと楽なのかもしれないので早くDockerマスターになりたいですね。 所感 実は受験前からこのような記事を書こうと決めていたので記事に説得力を持たせるためにも高いスコアで合格したいと考えていました。 もちろん自宅での学習もかなり行ったのですが、業務時間でも学習用の時間をいただくことが出来たので本当に恵まれているなぁと感じます。 まだまだ実務経験が浅いこともあり、実際に業務に活かせるかはまた別の問題になりますが、一通り体系的な知識を得ることが出来たので良い経験となりました。 次はこの知識が少しでも今後に活かせるようアウトプットについても考えていきたいですね。 終わりに いかがでしたでしょうか? 今回は OSS -DB Silverの学習体験記とその反省について記事を書かせていただきました。 この記事が今から OSS -DB Silverを受験してみようという方の参考に少しでもなれば幸いです。 それではまた! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは、 MasaKu です。 2020年9月8日に Laravel 8 がリリースされました。 laravel.com Laravel 8 では セキュリティ周りの強化として Laravel Jetstream という新しいスキャフォールディングが注目されています。 jetstream.laravel.com しかし、レート制限の新機能にも注目です! 今回はレート制限の解説と新機能部分の説明を行いたいと思います。 レート制限とは 1. 特定のアクション(APIなど)に個別の制限をかける 2. 認証を通過したユーザとゲストユーザで制限を分ける 3. 特別なオプションを保有しているユーザだけは制限を開放する おわりに 参考サイト レート制限とは Laravel には DoS攻撃 などの大量アクセス対策として、単位時間当たりに一定以上のアクセスを検知するとアクセス拒否する機能が導入されておりました。 readouble.com 最初にこの機能を知ったときは Apache や Nginx などの Webサーバ側で制限すればいいんじゃないの? と思っていましたが、以下のようなケースではアプリケーション側で制御するほうが都合が良いことに気が付きました。 特定のアクション( API など)に個別の制限をかけたい 認証を通過したユーザとゲストユーザで制限を分けたい 特別なオプションを 保有 しているユーザだけは制限を開放したい 上記はいずれも Laravel 8 以前から実現できましたが、より直感的かつ簡潔に実装できるようになりましたので、Laravel 8 での実装方法について説明したいと思います。 1. 特定のアクション( API など)に個別の制限をかける まず、前提としてレート制限は throttle という ミドルウェア を利用して実装します。 この ミドルウェア にリミッター名と任意のレート制限を設定します。 app/Providers/RouteServiceProvider.php の configureRateLimiting メソッドに以下のような処理を追加します。 <?php // 一部省略 class RouteServiceProvider extends ServiceProvider { // 一部省略 protected function configureRateLimiting () { // リミッター名と制限値を設定 RateLimiter ::for ( 'testLimit' , function ( Request $ request ) { // 1分間に5アクセスまでの制限を追加 return Limit :: perMinute ( 5 ) ; }) ; } } 後は、ルーティングを記載している routes/web.php にレート制限をかけたいアクションに対し throttle ミドルウェア を設定するだけです。 throttle ミドルウェア には上記で設定したリミッター名を指定します。 <?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\TestController; // アクセス制限をかけたいアクションに上記で設定したリミッター名を設定 Route :: middleware ([ 'throttle:testLimit' ]) -> get ( '/test' , [ TestController :: class , 'index' ]) ; なお、 ミドルウェア は複数のアクションをグルーピングできますので、以下のように各アクションに対して同じリミッターを適応することもできます。 <?php Route :: middleware ([ 'throttle:testLimitUser' ]) -> group ( function () { Route :: get ( '/test1' , [ TestController :: class , 'index' ]) ; Route :: get ( '/test2' , [ TestController :: class , 'index' ]) ; }) ; 上記のように記載することで /test1 と /test2 それぞれへのアクセスの合計回数を1分間5回にする制限を付けることができます。 2. 認証を通過したユーザとゲストユーザで制限を分ける 次はログイン認証を通過したユーザとゲストユーザで制限を分けてみます。 先ほど修正した app/Providers/RouteServiceProvider.php の configureRateLimiting メソッドを以下の通り修正します。 <?php RateLimiter ::for ( 'testLimit' , function ( Request $ request ) { return $ request -> user () ? Limit :: none () : Limit :: perMinute ( 5 ) ; }) ; ログイン認証を通過しているユーザがアクセスした場合、Request オブジェクトにはユーザ情報がセットされます。 そのため、認証済みのユーザの場合はレートの制限が解放されますが、認証前のユーザの場合は1分間5アクセスまでの制限を付けられます。 3. 特別なオプションを 保有 しているユーザだけは制限を開放する 認証後のユーザオブジェクトは app\Models\User.php モデルが利用されますので、こちらに独自メソッドを追加することで特別なオプションを持つユーザだけをレート制限から解放することができます。 まずは、以下のようにして app\Models\User.php に独自メソッドを追加します。 <?php // 省略 class User extends Authenticatable { // 省略 public function vipCustomer (){ // rakus.co.jp のドメインで登録されているユーザのみ制限を解除する if ( strpos ( $ this -> email, '@rakus.co.jp' ) !== false ){ return true ; } return false ; } } あとは、リミッター内で上記で設定した独自関数を実行すれば特別なオプションを持つユーザのみアクセス制限を解除することができます。 <?php RateLimiter ::for ( 'testLimit' , function ( Request $ request ) { return $ request -> user () -> vipCustomer () ? Limit :: none () : Limit :: perMinute ( 5 ) ; }) ; また、上記に加えて、 by でメソッドチェインをすると特定のIPからのアクセスを制限する、などの処理を追加できます。 <?php RateLimiter ::for ( 'testLimit' , function ( Request $ request ) { return $ request -> user () -> vipCustomer () ? Limit :: none () : Limit :: perMinute ( 5 ) -> by ( $ request -> ip ()) ; }) ; おわりに いかがでしたでしょうか。 Laravel は非常に多くの機能があるため、こんなこともできるんだ!と驚くような便利な機能に気づいていないことがあります。 新機能のリリースによって、前バージョンよりも使いやすくなったりわかりやすくなることによって、いままで注目されていなかった機能にも気づいてもらいやすくなるのではないかと思います。 そのほかにも、便利な機能がリリースされておりますので、気になった方は是非ご確認ください! 参考サイト Release Notes - Laravel - The PHP Framework For Web Artisans Introduction | Laravel Jetstream ルーティング 8.x Laravel LaravelのAPIで429 Too Many Requestsが返る - suzu6の技術ブログ エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
はじめに こんにちは、 @rs_tukki です。 突然ですが、皆さんは iOS でアプリをリリースする際、以下のような表示を見たことがあるでしょうか。 輸出 コンプライアンス 情報 Appには暗号化が使用されていますか?Appに使用されているのが iOS および macOS の標準的な暗号化のみである場合も「はい」を選択します。 暗号化というと難しく聞こえますが、実は一般的な HTTPS 通信もこの暗号化に含まれています。 *1 そして、 App Store や Google Play でリリースするアプリにこの暗号化が使用されている場合、「年度末自己分類報告書」というものを毎年作成し、提出する必要があります。 今回は、その書類の書き方・出し方を調べてみました。 はじめに なぜ「年度末自己分類報告書」の記載が必要なのか? 報告書の書き方 報告書の出し方 まとめ 参考 なぜ「年度末自己分類報告書」の記載が必要なのか? そもそも何故このような報告書を記載しなければいけないのかというと、 ざっくり言ってしまえば「 アメリ カの輸出規制の法律に対応するため」です。 App Store や Google Play でリリースするアプリはいずれも アメリ カのサーバから配信されており、日本を対象としたアプリをリリースする場合、厳密には アメリ カから日本へ輸出されるもの とみなされます。 暗号化の技術を使用した商品を アメリ カ国外へ輸出する場合、それがどういうものなのかを記載し、 アメリ カ政府に報告する義務があると定められています。 報告書はその年の1月~12月までに作成された商品である場合、翌年の2月1日までに提出する必要があります。 特に スマホ アプリの場合は、全く変更がなかったとしても毎年提出しなければなりません。 報告書の書き方 アメリ カに出す報告書とあって記載内容は全て英語ですが、そこまで難しい内容ではないため身構える必要はありません。 アプリごとに、以下12の項目を csv ファイルに記載するのみです。(1行目にはこれらの項目名を記載します) PRODUCT NAME MODEL NUMBER MANUFACTURER ECCN AUTHORIZATION TYPE ITEM TYPE SUBMITTER NAME TELEPHONE NUMBER E-MAIL ADDRESS MAILING ADDRESS NON-U.S. COMPONENTS NON-U.S. MANUFACTURING LOCATIONS PRODUCT NAME 商品名。ストアで公開しているアプリ名を英語で記載すればOKです。 MODEL NUMBER モデル番号。 iOS , Android どちらもアプリ固有のIDを記載すれば問題ないはずです。 App Store の場合はこちら、 Google Play の場合はこちらに記載されています。 MANUFACTURER アプリの主な提供社名。提出者本人が主な提供者であれば「SELF」と記載します。 ECCN 輸出規制分類番号。 スマホ アプリは5D002もしくは5D992に分類されますが、 HTTPS 通信のみを行うアプリであれば5D992でOKです。 *2 AUTHORIZATION TYPE 暗号化認証タイプ識別子。ENCもしくはMMKTのどちらかですが、ECCNの項目が5D992の場合はMMKT(Mass Market)を選択するのが一般的のようです。 ITEM TYPE 商品分類。 スマホ アプリの場合は「mobility and mobile applications n.e.s.」と記載します。 SUBMITTER NAME TELEPHONE NUMBER E-MAIL ADDRESS MAILING ADDRESS この辺りはそのまま、提供者名/電話番号/メールアドレス/住所を記載すればOKです。特に理由がなければ、ストアに登録したものを流用すればよいかと思います。 NON-U.S. COMPONENTS アメリ カ以外で作成された暗号化技術を使っているかどうか。 HTTPS 通信しか使用していないのであれば「NO」でOKです。 NON-U.S. MANUFACTURING LOCATIONS アメリ カ以外で作成されたアプリの場合、その場所を記載します。「Tokyo Japan」といった粒度の記載で大丈夫です。 報告書の出し方 さて、これで完成した csv ファイルですが、特に難しい手続きを行う必要はなく、以下2種類のメールアドレスに添付して送信すればOKです。 crypt-supp8@bis.doc.gov enc@ nsa .gov Subject:Annual Self Classification Report Dear Sir/Madam, Thank you very much for your time reading this message. Please find attached our report. If you require any further information, let me know. Best Regards, まとめ 今回は、意外と気にしてない年度末自己分類報告書の書き方とその出し方について記事にしてみました。 分かってしまえば何のことはない内容ですので、ぜひこれを機に書いてみてください。 何か間違っている箇所があれば教えていただけると助かります(小声) 参考 輸出コンプライアンスの概要 - App Store Connect ヘルプ https://blog.hakoniwa.net/archives/1537 http://www009.upp.so-net.ne.jp/kgm1_ear/cipher4/Howto_file_selfClassify.htm iOSアプリ提出の輸出コンプライアンスで、通信にHTTPSを使っているだけの場合の解釈 - Qiita §742 付則 No.8 暗号品目に対する自己番号分類報告 カテゴリー5-通信及び”情報セキュリティ”/パート 2-”情報セキュリティ” アメリカ合衆国からの暗号の輸出規制 - Wikipedia app store - Which ECCN should I use for iOS app which uses HTTPS? - Stack Overflow エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com *1 : 輸出コンプライアンスの概要 - App Store Connect ヘルプ には「セキュリティで保護されたチャンネル (例: HTTPS 、 SSL など) を使って電話をかける場合」が暗号化の使用にみなされるとの記載がある。 *2 : 参考: アメリカ合衆国からの暗号の輸出規制 - Wikipedia
アバター
はじめに こんにちは、itoken1013です。すっかり秋ですね! 今回は近年エンジニアに大人気のエディタの1つである Visual Studio Code ( VSCode )のインストールと日本語表記の手順を示し、入門エンジニアがいち早く開発のスタートラインに立てるようにご説明していきたいと思います。 Visual Studio Code は ラク スの中でも支持するエンジニアが多く、実際の開発業務でも十分活用していけるツールです。 一方で Visual Studio Code は世界中で利用されているため、インストール直後では英語表記となっており、日本語のツールをメインで使う入門エンジニアには若干の使いづらさを感じさせる見た目となっています。 今回は Windows と MacOS 別に日本語表記の手順を示していきますので、どちらのユーザーの方も安心して読んでいただければと思います! tech-blog.rakus.co.jp はじめに 手順のご紹介 手順1. VSCodeのインストール Windowsの場合 Macの場合 手順2. VSCodeの日本語化 手順2-1. 「Extension」を表示する 手順2-2. Marketplaceから日本語用の拡張機能を探す 手順2-3. 日本語の表示設定に変更する 手順2-4. VSCodeを再起動する 補足. 一時的に日本語以外の表記でVSCodeを起動する方法 終わりに 手順のご紹介 それでは早速ここから Visual Studio Code ( VSCode )のインストールと日本語化のステップに分け、手順を紹介してまいります! 手順1. VSCode のインストール まずはインストールの手順からです。 お使いのOSが Windows ・ Mac かに関わらず、まずは VSCode の インストーラ およびパッケージの入手が必要となります。 ブラウザを開いて、 VSCode の作成元である Microsoft 社のページ(以下)にアクセスしてください。 ページにアクセスすると、中央に Visual Studio Code のダウンロード案内が確認できます。 visualstudio.microsoft.com Windows の場合 「 Visual Studio Code のダウンロード」の右端にある下矢印マークをクリックし、「 Windows x64」をクリックすると VSCode の インストーラ のダウンロードが開始されます。 ダウンロードの完了後、 インストーラ をクリックして起動しましょう。 最初の「使用許諾契約書の同意」を「同意する(A)」に変更する以外はデフォルトの状態で進み、インストールを実行してください。 手順を進むと最後に「 Visual Studio Code を実行する」がチェックONの状態となります。 このまま「完了」をクリックし、 VSCode が起動すればインストール完了です! Mac の場合 「 Visual Studio Code のダウンロード」の右端にある下矢印マークをクリックし、「 mac OS 」をクリックすると VSCode のパッケージがダウンロードされます。 次にFinder上でダウンロードフォルダへ移動し、「 Visual Studio Code 」をアプリケーションフォルダへドラッグしてください。 これで Mac 上で VSCode を起動する準備ができました。 あとはアプリケーションフォルダ上で「 Visual Studio Code 」をダブルクリックし、 VSCode が起動すればインストール完了です! 手順2. VSCode の日本語化 次に VSCode を日本語の表示に変更する手順を説明します。 VSCode ではインターネット上の Marketplace という場所から「 拡張機能 」をインストールし、様々な機能を追加してカスタマイズを加えていくことができます。 日本語化も 「Japanese Language Pack for Visual Studio Code 」 という 拡張機能 をインストールすることで、 VSCode の表示を日本語に変更できます。 ここから先の手順は Windows でも Mac でも共通となりますので、1つの手順として日本語化の流れを紹介していきます。 手順2-1. 「Extension」を表示する VSCode を起動して、左側のアクティビティバー一番下にある「Extension」をクリックしてください。 クリック後、インストール可能な 拡張機能 が一覧表示されます。 ※アクティビティバーが表示されていない場合には、上部にあるメニューバーから以下の手順で表示させてください。 View > Appearance > Show Activity Bar (日本語だと表示 > 外観 > アクティビティバーを表示する) 手順2-2. Marketplaceから日本語用の 拡張機能 を探す 次に表示されたテキストボックスに 「Japanese Language Pack」 と入力すると、今回のインストール対象の日本語化の 拡張機能 「Japanese Language Pack for Visual Studio Code 」が表示されます。 名称を確認の上、「Japanese Language Pack for Visual Studio Code 」の右下にある「Install」をクリックしてください。これで日本語化の 拡張機能 がインストールされます。 手順2-3. 日本語の表示設定に変更する インストールが完了すると、手順2-2では「Install」と表記されていた箇所が歯車マークに変わります。 マークの変更を確認後、次に VSCode の日本語化用に表記設定を変更します。 Windows の方は「Control + Shift + P」、 Mac の方は「Command + Shift + P」でコマンドパレットを起動し、 「Configure Display Language」 と入力してEnterキーを入力してください。 次に言語の選択が求められます。 「en」と「ja」が表示されますので、「ja」を選択してEnterキーを入力してください。 これで日本語化になるよう、 VSCode 内で設定が変更されます。 手順2-4. VSCode を再起動する この後、「A restart is ~ 」という表記のダイアログが表示されますので、「Restart」を選択してください。(「言語の切り替えには、 VSCode の再起動が必要です」という案内になります) 再起動後、無事に各箇所が日本語化されているはずです。 無事に日本語化されましたか? これで今回の作業は完了です!お疲れ様でした! 補足. 一時的に日本語以外の表記で VSCode を起動する方法 ここまで日本語に表示を切り替える設定方法を紹介してきましたが、何らかの理由で一時的に日本語以外の表記へ変更したいケースがあるかもしれません。 現在起動している Visual Studio Code だけ日本語ではない表記に変更するには、 コマンドライン から Visual Studio Code を起動し、オプションに ロケール (言語と地域の設定情報)を明示的に指定することで実現できます。 この手順も実際にやってみましょう。 まずはコマンドから Visual Studio Code を立ち上げるために、ターミナルを立ち上げましょう。 Windows であれば コマンドプロンプト や GitBash、 Mac であればターミナルでよいでしょう。 次にターミナルが立ち上がりましたら、ターミナルの コマンドライン 上に「code」と入力してEnterキーを押してください。 これだけで Visual Studio Code がオープンします。 ただしこの状態だと、画面上から Visual Studio Code を起動した場合と同様、 ツールバー などが日本語で表示されている状態となります。 いったん Visual Studio Code を終了して、再度ターミナルに戻って下さい。 次に日本語以外(ここでは英語とします)の ロケール を指定するために、 コマンドライン 上に「code --locale="en" 」と入力して、Enterキーを押してください。 あらためて Visual Studio Code が立ち上がり、 ツールバー などが英語で表示されていることが確認できるはずです。 これで日本語以外での表示を実現できました! ちなみにターミナルは Visual Studio Code にも備え付けられていますので、上記の ロケール 指定は Visual Studio Code からも実施できます。 (ターミナルは上部にあるメニューバーの ターミナル > 新しいターミナル から起動できます) Visual Studio Code のターミナル上で同様のコマンドを打てば、「現在立ち上がっている Visual Studio Code は日本語だけど、もう1つ別の言語の VSCode を立ち上げたい」ということも実現できます。 (以下の図では左が日本語、右が英語で立ち上げています) 日本語以外に切り替えるケースはなかなか実施機会は少ないかもしれませんが、日本語の表示設定を実施したからといって別の言語へ変更できないわけではないという点を覚えておいていただければと思います。 またこちらで説明した内容は、 Visual Studio Code に用意されている コマンドライン インターフェース機能( CUI )に関連しています。 コマンドライン インターフェースには ロケール の切り替え以外にも、様々な機能が用意されています。 また日本語や英語以外の表記についても、各国の ロケール を指定することで実現が可能です。 ご興味のある方はこちらをご確認の上、日本語と英語以外の言語にも切り替えてみていただければと思います。 vscode-doc-jp.github.io 終わりに いかがでしたでしょうか? 終了してしまえば簡単な作業だったかもしれませんが、何の ガイドライン もなく、いきなり日本語へ切り替えずに操作をするのは少しハードルが高く感じてしまう方が多いのではないかと思います。 VSCode には日本語への切り替え以外にも便利な 拡張機能 が豊富に用意されており、皆さんの開発を手助けする便利ツールに溢れています。 無料で使えて超便利な VSCode を使いこなし、上級者への道を進んでいただければと思います! それではまた次回の記事でお会いしましょう! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
はじめに 社会人として2年目になろうというタイミング(今年の4月)で急遽、 スマホ アプリの開発を担当することになりました。 スマホ アプリは初めての開発だったため、 Android ・ iOS それぞれの開発の学習を行いました。 iOS : Xcode /Swift Android : Android Studio /Kotlin 両方の学習を終えて振り返ってみると、 Android より Xcode を用いた iOS の開発の方が新しい発見が多く、面白く感じました。そこで今回は、 Xcode で開発した iOS アプリを 実機/ iPhone でデバック・動作確認を行うまでの流れを解説しようと思います。 はじめに そもそもX-codeとは ダウンロード・インストール 作業時の落とし穴 1. Xcodeをインストールできない場合がある 2. XcodeとSwiftのバージョンの組み合わせは決まっている プロジェクトを作成する/選択する 実機でデバックするための設定 おまけ:Provisioning Profile とは さいごに ※記事作成時の環境 MacOS :Catalina Xcode :ver.12 そもそもX-codeとは 「 Xcode 」とは、 MacOS で使用できる開発ツールの一つで、様々な Apple 製品( iOS / iPhone 、watchOS/ Apple Watch 、etc...)で使用できるアプリを作成することができます。 Xcode に記述していく プログラミング言語 である「Swift」または「 Objective-C 」を用いてコードを記述していき、実機・ エミュレーター を使った動作確認や、開発に必須なテスト・デバックも簡単に実行可能です。 「 App Store 」に様々なアプリが公開されていますが、そのほとんどが Xcode で作成されていると言っても過言ではありません。つまり、この Xcode を使いこなすことができれば、 iPhone アプリなどを自作することができるということです。 ダウンロード・インストール Xcode の一番手軽なインストール方法は MacBook の「 App Store 」からのダウンロードです。 App Store で 「 Xcode 」と検索すれば出てきます。 ※すでに Xcode がインストール済みのため「アップデート」になっていますが、未インストールであれば「インストール」が表示されます 「インストール」を押すと、 MacBook にダウンロード・インストールが開始されます。(約8Gあるので時間がかかります。) これで最新版の Xcode をインストールでき、さらに新しいバージョンの Xcode が公開されたときも、「 App Store 」から簡単にバージョンアップが可能となります。 作業時の落とし穴 「とても簡単!」と思うかもしれませんが、 実はここまでで一つ 落とし穴 があります。 これから解説する以下の点を疎かにすると危険ですので、作業前に必ずご一読ください。 1. Xcode をインストールできない場合がある お使いの MacBook の「 MacOS のバージョン」によって、インストールできる「 Xcode のバージョン」に"上限"があり、昔のバージョンの MacOS を使用していると、特定のバージョン以降の Xcode がインストールできなくなります。 2. Xcode とSwiftのバージョンの組み合わせは決まっている Xcode をインストールするとSwiftも自動的にインストールされるため、そのままSwiftのコードを記述することができます。 裏を返すと、インストールする際にSwiftのバージョンを選ぶことができません。 整理すると、 MacOS 、 Xcode 、Swift それぞれのバージョンの組み合わせに制約があるこということになります。 App Store には最新版が公開されているので、 最新の MacOS でなければインストールできない可能性が高い 最新のバージョンのSwiftがインストールされる ということになります。 「バージョンなんて気にしない/しなくても良い」という方は、 App Store での管理が一番楽だと思います。 しかし、環境の影響で MacOS を自由にアップデートできない・指定のバージョンのSwiftを使用したい といったケースは少なくないと思います。 そういった場合は、「 App Store 」からではなく、 Apple Developerサイトからから任意のバージョンの Xcode を直接ダウンロードしてください。 https://developer.apple.com/download/more/ ダウンロードしたファイルを実行することでインストールを行うことができます。 ここでも一つ注意しなければいけないことがあります。 「 Xcode のバージョン」によってデバック・動作確認することができる「実機のOSのバージョン」に 上限 があります。つまり、古い Xcode を使うと、 iPhone の iOS のバージョンによって開発したアプリをインストールできないケースがあるのです。 このパートをまとめると、 Xcode をインストールする前に以下のそれぞれのバージョンを確認し、条件にあったバージョンを選択する必要があるということです。 MacOS のバージョン 接続するデ バイス OSバージョン 使用したいSwiftのバージョン 何も意識せず、 Xcode をインストールするとハマる場合があるのでご注意ください。 ※各バージョンがどのように紐づいているかは、ググれば簡単に出てくると思います。 プロジェクトを作成する/選択する Xcode を起動すると、以下の画面が表示されます。 ※ここからの画面画像は Xcode のバージョンによって若干異なることが多々あります。 Create a new Xcode project:新規プロジェクトを作成する。新しくアプリを作成する場合はここから。 Clone an existing project:Git Repositoryからプロジェクトを作成する。 Open a project or file:ローカルにあるプロジェクトを開きます。 プロジェクトを初めて作成する場合は「Create a new Xcode project」を押下します。 Xcode11には「Get started with a playground:Swiftプログラムを記述し動作確認を行うモード」があり、練習に使ってましたがXcode12で消えてしまいました、、、(探したらあるかもしれない) 次に、以下のような画面が表示されます。 ここでは、開発するOSとそのテンプレートを選択します。 OS 開発しようとしているアプリのOSを選択してください。 テンプレート はじめて Xcode を触る方でとりあえず実機でのデバックを行いたい(Hellow Worldを表示させたい)ということであれば、「Single View app」を選択します。 作成するアプリが決まっている方は任意に選んでいただいて構いません。 次の画面です。 Product Name:プロジェクトの名前です(任意の値に設定) Team:プロジェクトを作成するアカウント・チームを選択します Xcode に設定した Apple IDで選択可能なチームから選択できます(後ほどログインに関して説明します) Noneでも開発可能ですが実機でデバックすることができません(後から変更可能) Organization Identifier:アプリを一意に識別する”Bundle Identifier”に用いられる 今回は任意の名称で構いません。アプリを公開する場合には、慎重に作成してください。 Organization Identifier + . + Product Name になる Interface:SwiftUIかstoryboardから選択できます Life Cycle:SwiftUI App か、UIKit App Delegate から選択できます Language:コーディングする際の言語を選択する(SwiftとObject-Cから選択可能) 上記の設定が完了すると、プロジェクトを作成する場所(保存先)の設定画面に進みますので、任意の場所を選択してください。 ここまでの設定が完了すると、いよいよ Xcode の開発画面が表示されます。 実機でデバックするための設定 昔は" Apple Developer Program"という有料プラン(年間 11800円)に加入していることが必須でしたが、現在は無料で実機にデバックすることができます。(ただし以下の制限があります) デバックのみ(リリースは不可能) アプリの有効期限は7日 プッシュ通知機能を使用することができない 今回は、無料の方法を説明していきます。 有料/無料の違いで、手順が異なるようです。 1: Apple ID でログインする Xcode で、 Apple のアカウントにログインする必要があります。 メニューバー > Xcode > Preferences > Accounts 画面へ進み、左下の「+」→「 Apple ID」→「Continue」の順にクリックします。 Apple IDの情報を入力し、登録処理を完了させてください。 2:証明書を作成し、キーチェーンに登録する メニューバー > Xcode > Preferences > Accounts 画面へ進み、右下の「Manage Certificates…」をクリックします。 左下の「+」ボタン →「 iOS Development」を選択 → 「Done」をクリックします。 これで、実機でデバックを行うための証明書が作成され、キーチェーンに登録されます。 3: iPhone に開発元を信用させる 上記の部分を接続している自分の iPhone を選択して実行することでデバックが可能となります。 まず、 iPhone と Mac をUSBで接続します。 そして、接続した実機を選択します。(ここで、任意の エミュレーター を選択すれば画面上で動作を確認することもできます) 再生ボタン:▶ を押下すると実機に対してインストールを行い、デバックが可能となるはずですが、このタイミングでエラーが発生することが多いです。 一例の解決方法を紹介します。 3.1:Bundle Identifierが重複している Bundle Identifierが重複しないように複雑なものに変更してください。 3.2:「Team」が指定されていない 「Team」の設定が"None"になっていたら、登録した自分の Apple ID に変更してください。(または、「Team」設定に問題がないか確認してください) 上記エラーは初めての操作の場合、必ず発生します。 以下の手順を「接続している iPhone 」で行うとアプリが実行可能になります。 1. 「設定」 > 「一般」 > 「プロファイルとデ バイス 管理」画面を開く 2. 「 デベロッパ App」の欄に表示されている「登録した Apple アカウント」をタップする 3. 再び「登録した Apple アカウント」をタップする 4. 表示されるダイアログで「信頼」をタップする ここまで完了すると、ホーム画面(アプリ一覧画面)でインストールしたアプリの動作確認ができます。 おまけ:Provisioning Profile とは Xcode を使って、実機に対してビルドする際に必要なのが、Provisioning Profileです。 Provisioning Profile とは、アプリ情報、 Apple ID、証明書、実行可能デ バイス の情報が紐づいたデータで、 Xcode 上でアプリを実機用にビルドする際に必要なものになります。 ビルド時に、Provisioning Profile の情報から以下を確認し、問題がない場合はビルドされます。 開発者として登録した証明書がキーチェーンに登録されたPCであること 開発機として登録されているデ バイス がインストール対象であること このような制限を設けることで、アプリが不正に配布されることを防いでいます。 無料でデバックする場合は、プロビジョニングファイルを意識する必要がありませんでしたが、おそらく 「実機でデバックするための設定」の1~3の手順を踏んでいる裏で 作成されているのではないかと推測します。(違っていたらごめんなさい、、、) " Apple Developer Program"に登録している場合は、 Apple DeveloperサイトにUDID(デ バイス 情報)などを登録し、同サイトで作成、 Mac へダウンロードする必要があるみたいです。(こちらも詳しく確認したかったのですが、お金に余裕ができてからにします、、、) さいごに 自作したアプリが自分の iPhone で動かせるようになると、開発のモチベーションになると思います。 私も便利なアプリを自作し、お金に余裕ができたら" Apple Developer Program"に登録しようと思います! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
はじめに 9/29(水)に行われました 『UI/UXデザイナーLT会 #uiuxdesignerslt』 のイベントレポートを発信させていただきます! 申し込みは300名の満枠、当日もたくさんの方にご参加いただけたイベントになりました! rakus.connpass.com はじめに イベント概要 発表の紹介 1. 伝わらない UI UX の 正しい伝え方 / 木村圭 2. UIを止めるな! ~UIデザインのコンサルティングで話すこと~ / yumemiさん 3. デザイナーとして入社した私がフロントエンド領域から届ける体験価値とは / Three4Cさん 4. 三次元的アクセシビリティ解釈。価値、多様性、そして時間 / masuP9さん 5. 新卒2年目のデザイナーが大事にすること / yuriさん 6. TypeScriptで解説する催眠術のかけ方 / Ouji Miyaharaさん 7. Adobe XDでつくるVUIプロトタイピング / Makiko Odaniさん 8. デザイナー採用のUXデザインに取り組み始めた話 / TomoyoHirokawaさん 9. UIデザインの呪術性 - 覚書- / yuki_sasakiさん 10. デザイナー 1 年目のわたしが考えるデザイン / uknmrさん 11. 生産性が上がる文章の書き方と、デザインと文章の関係性 / ShinyaSatoさん 12. 技術とデザインの間 / takanoripさん 13. TypeScriptではじめるUIデザイン / kgsiさん 14. Web UIの実装で考えていることと気をつけたいこと / yamanokuさん 15.3次元のカラーピッカー的なものを作ってみた / chooさん 16. BtoB SaaSのUIリニューアルの苦労 / 小林肇 おわりに イベント概要 今回はUI/UXデザイナー16名による怒涛のLT会を開催させていただきました。 UI/UXデザインをテーマとしたイベントはconnpassを探しても多くなく、16名という大人数でもあっという間にLT枠が埋まりました。 また今回が登壇へ初チャレンジでした方々には、登壇機会をご提供できた形となったことを大変嬉しく思います。 当日は各登壇者16名の個性的な発表により、最後までコメントやツイートが止まりませんでした。 Twitter のタイムラインも大変賑わい、催眠術とカレーという謎の文言の他、ご参加の方からも便利情報のリンクなどをシェアいただけました。 タイムラインを眺めるだけでも学べる情報に溢れていますので、ぜひご覧になっていただき、当日の盛り上がりを感じていただければと思います。 twitter.com ちなみに当日イベントに参加いただいた方の職種は、以下の割合でした。 UI/UXデザイナー:41% エンジニア:39% 学生:5% その他デザイン関連職種:5% その他ビジネス職種:10% 発表の紹介 それでは今回ご登壇いただいた16名の方のコンテンツを紹介させていただきます。 本当に幅広いキャリアの方々に発表をいただき、ありがとうございました! 1. 伝わらない UI UX の 正しい伝え方 / 木村圭 まずは ラク スのマネージャーである木村から、UI/UXデザイナーの立場からのプロジェクトメンバーとのコミュニケーション(説明力)について、発表させていただきました。 関係するメンバーに思うように意図を伝えられない、UI/UXの重要性が浸透しない、などの悩みを抱えている方には必見の内容です! speakerdeck.com 2. UIを止めるな! ~UIデザインの コンサルティング で話すこと~ / yumemiさん UIデザインと コンサルティング に取り組まれている yumemiさんより、日々のお仕事で考えている「デザインの使いやすさ、デザインの本来あるべき姿」を オブジェクト指向 UIの例などを交えつつ、ご紹介いただきました。 「行動」と「結果」が直接結びつかなくなった歴史を踏まえると、「UIを止めるな」の意図が深く理解できた気がします。 エンジニア目線でもUXに関わる上で必要な視点のように思えますね。 speakerdeck.com 3. デザイナーとして入社した私がフロントエンド領域から届ける体験価値とは / Three4Cさん 新卒2年目のデザイナーであるThree4Cさんが考える、デザイナーとしてのフロントエンド領域への関わり方を発表いただきました。 ご自身の経験をもとに企画・分析・開発チームと関わる上での姿勢や知識の深め方、今後のありたい姿を語っていただきました。 ご自身の現在地と置かれている状況をしっかり客観視されており、初登壇とは思えない素晴らしい発表でした。 speakerdeck.com 4. 三次元的 アクセシビリティ 解釈。価値、多様性、そして時間 / masuP9さん masuP9さんは非常に個性的に登場し、なんと その場で説明資料を作成される斬新な発表 でした! 発表は アクセシビリティ と「価値」に対するお考えが中心でしたが、その発表方法はとても衝撃的でした。 発表動画を公開できないことが残念です! また ラク スの主催イベントでのご登壇を楽しみにしています! 5. 新卒2年目のデザイナーが大事にすること / yuriさん デザイナー新卒2年目のyuriさんより、freeeさんの共通の価値観である「マジ価値」に基づき、 組織での動き方でデザイナーとして大事と考えられている点、そして B2B サービスのUIを作っていく上で大事と考えられている点を話していただきました。 司会者も話していましたが、新卒2年目とは思えない堂々としたお姿に尊敬のまなざしでした。 日々の思考と「マジ価値」への共感の積み重ねによる成果ではと感じていました。 初のLT挑戦の場として ラク スを選んでいただき、ありがとうございました。 t.co 6. TypeScriptで解説する催眠術のかけ方 / Ouji Miyaharaさん 事前に伺っていたタイトルとは一切関係ない 催眠術のかけ方 について、TypeScriptを交えながらご紹介いただきました! イベントの空気が一変したタメになるご発表、ありがとうございました! 催眠術にご興味をお持ちのデザイナーの方、ぜひこちらのスライドをご覧ください! t.co 7. Adobe XDでつくるVUIプロトタイピング / Makiko Odaniさん Makiko Odaniさんも初のLT挑戦として、 Google Home や Amazon Echo によって注目されているVUI(Voice User Interface 、音声を使って操作するインターフェース)をご紹介いただきました。 発表中には実際に作成されたプロトタイプでのデモも実施いただきました。 デザイナーの方には馴染みのある Adobe XDで手軽に作成できるそうですので、ご興味のある方はぜひお試しいただければと思います! 8. デザイナー採用のUXデザインに取り組み始めた話 / TomoyoHirokawaさん プロダクトデザイナー のTomoyoHirokawaさんには勢いでイベントにご参加いただき(直前の欠員を埋めていただき、ありがとうございました!)、初のデザイナー採用での体験設計に取り組まれた際のエピソードを語っていただきました。 初の取り組みとのことでしたが、仮説検証 → 修正 → 実行 → 振り返りのステップを着実に踏めていらっしゃる印象を受けました。 特に経営層や他職種を巻き込んでアクションが体現された点、素晴らしいです! 2周目以降の取り組みも応援しています!! 9. UIデザインの呪術性 - 覚書- / yuki _sasakiさん 呪術的思考 とUIデザインの関連について、以下の2つの補助線を用いてご説明いただきました! 一体どのようにUIデザインにつながるのか謎に感じておりましたが、最後は絶妙にUIデザインへと結び付けていただきました! 非常に深く、まるで催眠術にかかったような気分でした! クロード・ レヴィ・ストロース 『野生の思考』 ジャック・ラカン 『光点としてのまなざし』 speakerdeck.com 10. デザイナー 1 年目のわたしが考えるデザイン / uknmrさん エンジニアからデザイナーの道へと進まれたuknmrさんより、 プロダクトデザイナー 半年のご経験を踏まえてデザインとは何なのかを語っていただきました。 スライドに出てきます 『”デザイン”はデザイナーだけのものではない』 に込められた深い洞察はとても参考になりました。 あと催眠術にかかったのか、なぜか焼きそばが食べたくなりました! www.notion.so 11. 生産性が上がる文章の書き方と、デザインと文章の関係性 / ShinyaSatoさん ShinyaSatoさんからはあえてデザインをテーマとした発表ではなく、「文章の書き方」を中心にしたノウハウをご紹介いただきました。 「書く力」はデザイナーにとっても重要度が高く、とはいえ他職種よりも機会が少ないものということで、ご参加の方々からは大好評の内容でした。 在宅ワーク が本格化した今のタイミングでは、デザイナー以外のどの職種でも学ぶことばかりの貴重なお話だったと思います。 speakerdeck.com 最後にご紹介いただきましたShinyaSatoさんの個人ブログはこちらになります。 学びの宝庫です…!! https://shinya-it.com/work/sentence_of_import shinya-it.com 12. 技術とデザインの間 / takanoripさん デザイナーとエンジニアが分業体制である歴史を踏まえつつ、デザイナーが技術の仕組みを理解する重要性をご説明いただきました。 デザイナーにとって技術への理解が「なぜ必要なのか?」が明確となり、技術とデザインが切り離せないものであることを認識できた方も多いと思います。 エンジニア・デザイナー両方のご経験があるtakanoripさんの実感が強く感じられる内容でした。 speakerdeck.com 13. TypeScriptではじめるUIデザイン / kgsiさん UIの情報整理の難しさと理想を踏まえ、TypeScriptを使った方法を説明いただきました。 TypeScriptの言語仕様を踏まえ、実際にトライされたケースへの所感がとても参考になりました。 あと催眠術にかかったのか、なぜかカレーが食べたくなりました! speakerdeck.com 14. Web UIの実装で考えていることと気をつけたいこと / yamanokuさん 普段取り組まれている業務でのご経験を踏まえ、UI実装に対する深いお考えを発表いただきました。 ボタンUI1つをとっても思慮がとても深く、ぜひ全てのデザイナーの方にご覧いただきたい内容です。 LTへのご挑戦、ありがとうございました! scrapbox.io 15.3次元のカラーピッカー的なものを作ってみた / chooさん 「色は本質的に三次元(の要素で表現される)だから、二次元のUIはムリがあるのでは」という仮説に基づき、 三次元のColor Picker を紹介いただきました。 これは凄いです…私の説明よりも、ぜひこちらにアクセスいただいてお試しください!! t.co docs.google.com 16. BtoB SaaS のUIリニューアルの苦労 / 小林肇 最後は ラク スのUI開発チームの小林より、 ラク スでの17年間のサービスデザインでの歴史を語らせていただきました。 およそ半分が10年以上の歴史を持つ ラク スのサービスですが、現在に至るまで、実に多くのお客様からのお声をいただきながら成長させていただきました。 厳しいご意見からお褒めの言葉まで、お客様からの貴重なフィードバックが重要であることをあらためて考えるきっかけとなりました。 お客様にとって使いやすく、課題解決につながるサービスを目指していきたいという小林からの声で今回のLTを締めさせていただきました。 おわりに ラク ス初のUI/UXデザイナー向けのイベントでしたが、いかがでしたでしょうか? 個性的な16名の登壇者の方々、イベントを盛り上げていただいて誠にありがとうございました! また次回開催でのご登壇を楽しみにお待ちしています! さて、今月 ラク スでは フロントエンド技術への取り組み を発信させていただく予定です。 ご都合のよろしい方は是非、こちらのイベントに気軽にご参加いただけますと幸いです! rakus.connpass.com また プロジェクトマネジメント にも関わっている方には、こちらのイベントがお勧めです! アウトプットに飢えているPMの方、お待ちしています! rakus.connpass.com それではまた次回のブログで。 長文をご覧いただき、ありがとうございました! 技術広報のitoken1013でした! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com
アバター
こんにちは。技術推進課のt_okkanです。 最近業務でFlutterを使用することになり、少しずつですがFlutterを学習しています。 そこで今回はFlutterで、実機( iPhone )のカメラを利用してみました。カメラを使用するための プラグイン の追加から、カメラでの画像の撮影と保存、保存した画像の表示までを行います。 環境 Flutterについて アプリ作成〜プラグインのインストール 実装 使用できるカメラの取得 カメラ制御の初期化 カメラのプレビューの実装 画像の撮影と保存の実装 撮影した画像を表示する 全体のソースコード まとめ 参考 環境 使用した環境は以下になります。 Flutter 1.22.0 Visual Studio Code macOS Catalina 10.15.6 iOS 13.7 Flutterの環境構築は、公式HPに詳しく記載されています。 https://flutter.dev/docs/get-started/install 上記を参考に、 macOS + VSCode の環境を構築してください。 Flutterについて Flutterについて少しだけ紹介します。 Google が提供しているUIツールキットで、主に iOS ・ Android アプリの クロスプラットフォーム ツールとして利用されています。最近はWeb対応が進み、モバイル・ウェブ・デスクトップと真の クロスプラットフォーム を目指している注目のツールです。 Flutterは Dart という言語で実装されています。カメラなどのデ バイス 固有の機能にアクセスするには、各プラットフォームのネイティブの実装を必要とします。 Flutterではそのような各ネイティブの実装を必要とする プラグイン をPluginパッケージとして提供しています。 プラグイン は pub.dev で公開されています。 アプリ作成〜 プラグイン のインストール Flutterのプロジェクトを作成して、 camera の プラグイン と、カメラとマイクへのアクセス許可の確認までを設定します。また、画像を保存するために各プラットフォームのパスを取得する必要があるので、 path_provider と path プラグイン もインストールします。 まずはコマンドでFlutterのプロジェクトを作成します。 $ flutter create camera_app その後 プラグイン を追加します。Flutterでは pubspec.yaml ファイルに使用する プラグイン を記載します。今回は以下のようにします。 dependencies : camera : ^0.5.8+5 path_provider : ^1.6.14 path : ^1.7.0 flutter : sdk : flutter 次に、カメラを使用の許可を促すメッセージを表示するための設定を行います。 ios/Runner/Info.plist ファイルに以下を追加します。 <key> NSCameraUsageDescription </key> <string> Can I use the camera please? </string> <key> NSMicrophoneUsageDescription </key> <string> Can I use the mic please? </string> また Android で使用する場合は、 SDK の最低バージョンを 21 を指定する必要があります。 android/app/build.gradle に以下を追加します。 defaultConfig { // TODO : Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.camera_app" minSdkVersion 21 targetSdkVersion 29 versionCode flutterVersionCode. toInteger () versionName flutterVersionName } 準備は以上です。では実際にコードを書いていきます。 実装 まずは camera プラグイン で使用するクラスを紹介します。 CameraController 端末のカメラを制御するクラス。このクラスに制御したいカメラを渡すことで、プレビューの表示や、写真・動画の撮影を行うことができます。 CameraDescription カメラの情報(フロント・バック)を管理しているクラス。 CameraController クラスにこのクラスを渡すことで、カメラの制御を行うことができます。 CameraPreview カメラが取得している映像(静止画の連続)を画面に表示する ウィジェット 。 今回はカメラで画像を撮影・保存し表示するまでの手順を順番に説明します。最後に必要なコードを全て載せますので、先に見たい方はそちらからどうぞ。 では、以下の手順で実装していきます。 使用できるカメラの取得 カメラ制御の初期化 カメラのプレビューの実装 画像の撮影と保存の実装 撮影した画像を表示する 使用できるカメラの取得 まずは端末から使用できるカメラを取得します。 今回は取得したカメラから、背面のカメラを使用します。 // runAppが実行される前に、cameraプラグインを初期化 WidgetsFlutterBinding . ensureInitialized (); // デバイスで使用可能なカメラの一覧を取得する final cameras = await availableCameras (); // 利用可能なカメラの一覧から、指定のカメラを取得する final firstCamera = cameras.first; プラグイン の availableCameras メソッドで、端末で使用可能なカメラを配列で取得できます。 ここで取得したカメラを、表示する ウィジェット に渡すことで、カメラを使用できます。 では次に、取得したカメラを制御するための準備をします。 カメラ制御の初期化 取得したカメラの制御を行うために、 CameraController クラスを初期化する必要があります。 ここではカメラ用の画面 CameraHome の構築から、 CameraController クラスの初期化までを実装します。 // ① class CameraHome extends StatefulWidget { final CameraDescription camera; const CameraHome ({ Key key, @required this .camera}) : super (key : key); @override State < StatefulWidget > createState () => CameraHomeState (); } class CameraHomeState extends State < CameraHome > { // デバイスのカメラを制御するコントローラ CameraController _cameraController; // コントローラーに設定されたカメラを初期化する関数 Future < void > _initializeCameraController; @override void initState () { super . initState (); // ② // コントローラを初期化 _cameraController = CameraController ( // 使用するカメラをコントローラに設定 widget.camera, // 使用する解像度を設定 // low : 352x288 on iOS, 240p (320x240) on Android // medium : 480p (640x480 on iOS, 720x480 on Android) // high : 720p (1280x720) // veryHigh : 1080p (1920x1080) // ultraHigh : 2160p (3840x2160) // max : 利用可能な最大の解像度 ResolutionPreset .max); // ③ // コントローラーに設定されたカメラを初期化 _initializeCameraController = _cameraController. initialize (); } // ④ @override void dispose () { // ウィジェットが破棄されたタイミングで、カメラのコントローラを破棄する _cameraController. dispose (); super . dispose (); } @override Widget build ( BuildContext context) {} } カメラを起動する画面を構築する カメラを起動する画面として StatefulWidget を使用し、画面を構築します。 コンストラクター で CameraDescription クラスを受け取り、使用するカメラを設定します。 カメラコントローラーを初期化します CameraController の コンストラクター の第一引数に CameraDescription を指定し、制御対象のカメラを設定します。第二引数に、列挙型 ResolutionPreset からカメラの解像度を指定します。今回は使用できる最大の解像度を指定します。 カメラコントローラーに設定されたカメラを初期化します CameraController クラスに設定された端末のカメラを、 initialize メソッドを実行して初期化します。 initialize メソッドは非同期処理を行うメソッドのため、 Feature オブジェクトを返します。 ウィジェット 破棄のメソッドの追加 StatefulWidget の dispose メソッド内で、 CameraController クラスの dispose メソッドを呼び出します。 ウィジェット が破棄されたタイミングで、 CameraController クラスも破棄する用になります。 以上がカメラの初期設定になります。次は、カメラが取得した画像を画面に表示する プレビュー機能 を実装します。 カメラのプレビューの実装 camera プラグイン の CameraPreview クラスを利用して、カメラが取得した画像をプレビューとして画面に表示します。先ほどの CameraHomeState クラスの、 build メソッドに以下のコードを追加します。 @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( '' ), ), // FutureBuilderを実装 body : FutureBuilder < void > ( future : _initializeCameraController, builder : (context, snapshot) { if (snapshot.connectionState == ConnectionState .done) { // カメラの初期化が完了したら、プレビューを表示 return CameraPreview (_cameraController); } else { // カメラの初期化中はインジケーターを表示 return const Center (child : CircularProgressIndicator ()); } }, ), ); } FutureBuilder クラスは、非同期で画面を構築できます。引数の future で非同期処理を設定し、 builder で非同期処理が完了した場合と完了していない場合の処理を実装します。今回は、非同期処理としてカメラの初期化を行う initialize メソッドを実行し、カメラの初期化が完了するとプレビュー画面を、完了していない場合はインジケーターを表示するようにします。 以上がカメラのプレビューを表示するための実装です。 画像の撮影と保存の実装 次は、カメラのシャッターボタンを実装し、画像の撮影と保存を行います。カメラのシャッターボタンは FloatingActionButton で実装します。 FloatingActionButton の onPressed 引数でボタンが押下された場合の実装をします。 @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( '' ), ), body : FutureBuilder < void > ( // 省略 ), floatingActionButton : FloatingActionButton ( child : const Icon ( Icons .camera_alt), // ボタンが押下された際の処理 onPressed : () async { try { // ①画像を保存するパスを作成する final path = join ( ( await getApplicationDocumentsDirectory ()).path, ' ${DateTime.now()} .png' , ); // ②カメラで画像を撮影する await _cameraController. takePicture (path); // ③画像を表示する画面に遷移 Navigator . push ( context, MaterialPageRoute ( builder : (context) => CameraDisplay (imgPath : path), ), ); } catch (e) { print (e); } }, ), } 画像を保存するパスを作成 path プラグイン の join メソッドを使用して、画像を保存するパスを作成します。 path_provider プラグイン の getApplicationDocumentsDirectory メソッドを使用することで、アプリ専用の ディレクト リを取得できます。 写真を撮影し、作成したパスに保存 CameraController クラスの takePicture メソッドで画像の撮影を行うことができます。引数に指定したパスに画像を保存できます。 撮影した画像を表示する画面に遷移 画像を表示する画面は次で実装します。ここでは、画面の コンストラクター に先ほど画像を保存したパスを渡します。 以上が画像の撮影から保存までの実装です。 ここまでの実装で、以下のようなカメラから取得した画像を表示し、撮影用のボタンを表示する画面が出来上がります。 カメラ撮影画面 最後に撮影ボタンを押下後、保存した画像を表示する画面を作成します。 撮影した画像を表示する 最後に、撮影し保存した画像を表示する画面を作成します。 画像を表示するためには、 Image ウィジェット を使用します。 コンストラクター に表示したい画像のパスを指定することで画像を表示できます。 class CameraDisplay extends StatelessWidget { // 表示する画像のパス final String imgPath; // 画面のコンストラクタ const CameraDisplay ({ Key key, this .imgPath}) : super (key : key); @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( 'Picture' ), ), body : Column ( // Imageウィジェットで画像を表示する children : [ Expanded (child : Image . file ( File (imgPath)))], ) ); } } これで以下のような画像を表示する画面を作成できます。 画像表示 実装は以上になります。全体の ソースコード は以下になります。 全体の ソースコード 全体のソースコード import 'dart:async' ; import 'dart:io' ; import 'package:camera/camera.dart' ; import 'package:flutter/material.dart' ; import 'package:path/path.dart' show join; import 'package:path_provider/path_provider.dart' ; Future < void > main () async { // runAppが実行される前に、cameraプラグインを初期化 WidgetsFlutterBinding . ensureInitialized (); // デバイスで使用可能なカメラの一覧を取得する final cameras = await availableCameras (); // 利用可能なカメラの一覧から、指定のカメラを取得する final firstCamera = cameras.first; runApp ( MyApp (camera : firstCamera)); } class MyApp extends StatelessWidget { final CameraDescription camera; const MyApp ({ Key key, @required this .camera}) : super (key : key); // This widget is the root of your application. @override Widget build ( BuildContext context) { return MaterialApp ( title : 'Flutter Demo' , theme : ThemeData ( primarySwatch : Colors .blue, visualDensity : VisualDensity .adaptivePlatformDensity, ), home : MyHomePage ( camera : camera, ), ); } } class MyHomePage extends StatelessWidget { final CameraDescription camera; const MyHomePage ({ Key key, @required this .camera}) : super (key : key); @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( 'Camera Example' ), ), body : Wrap ( children : [ RaisedButton ( child : const Text ( 'Camera' ), onPressed : () { Navigator . push (context, MaterialPageRoute (builder : (context) { return CameraHome (camera : camera,); })); }), ], ), ); } } class CameraHome extends StatefulWidget { final CameraDescription camera; const CameraHome ({ Key key, @required this .camera}) : super (key : key); @override State < StatefulWidget > createState () => CameraHomeState (); } class CameraHomeState extends State < CameraHome > { // デバイスのカメラを制御するコントローラ CameraController _cameraController; // コントローラーに設定されたカメラを初期化する関数 Future < void > _initializeCameraController; @override void initState () { super . initState (); // コントローラを初期化 _cameraController = CameraController ( // 使用するカメラをコントローラに設定 widget.camera, // 使用する解像度を設定 // low : 352x288 on iOS, 240p (320x240) on Android // medium : 480p (640x480 on iOS, 720x480 on Android) // high : 720p (1280x720) // veryHigh : 1080p (1920x1080) // ultraHigh : 2160p (3840x2160) // max : 利用可能な最大の解像度 ResolutionPreset .max); // コントローラーに設定されたカメラを初期化 _initializeCameraController = _cameraController. initialize (); } @override void dispose () { // ウィジェットが破棄されたタイミングで、カメラのコントローラを破棄する _cameraController. dispose (); super . dispose (); } @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( '' ), ), // FutureBuilderを実装 body : FutureBuilder < void > ( future : _initializeCameraController, builder : (context, snapshot) { if (snapshot.connectionState == ConnectionState .done) { // カメラの初期化が完了したら、プレビューを表示 return CameraPreview (_cameraController); } else { // カメラの初期化中はインジケーターを表示 return const Center (child : CircularProgressIndicator ()); } }, ), floatingActionButton : FloatingActionButton ( child : const Icon ( Icons .camera_alt), // ボタンが押下された際の処理 onPressed : () async { try { // 画像を保存するパスを作成する final path = join ( ( await getApplicationDocumentsDirectory ()).path, ' ${DateTime.now()} .png' , ); // カメラで画像を撮影する await _cameraController. takePicture (path); // 画像を表示する画面に遷移 Navigator . push ( context, MaterialPageRoute ( builder : (context) => CameraDisplay (imgPath : path), ), ); } catch (e) { print (e); } }, ), ); } } class CameraDisplay extends StatelessWidget { // 表示する画像のパス final String imgPath; // 画面のコンストラクタ const CameraDisplay ({ Key key, this .imgPath}) : super (key : key); @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( 'Picture' ), ), body : Column ( // Imageウィジェットで画像を表示する children : [ Expanded (child : Image . file ( File (imgPath)))], ) ); } } まとめ Flutterでカメラ機能を実装してみました。以前、 Swiftでカメラ機能を実装したこと があるのですが、その時と比べ必要なクラスが プラグイン ですでに実装されているので、シンプルに実装することができました。 またFlutterの特徴として宣言的にUIを構築できるので、楽に開発できます。 今回はシンプルなカメラ機能だけの紹介でしたが、 GitHub にインカメの切り替えのできるコードを置いています。興味があれば確認してみてください。 参考 Take a picture using the camera | Flutter エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは、takaramです。 私が担当しているサービスでは、 RDBMS に PostgreSQL を利用しています。今回は業務で行った デッドロック の調査で知った、 PostgreSQL の仕様に関して書いていきます。 ここでは デッドロック や パーティショニング といった用語が登場しますが、今回これらの説明は割愛します。 パーティショニングについてご存じでない方は、まずはこちらの記事をお読みください。 tech-blog.rakus.co.jp qiita.com なお、この記事の内容は PostgreSQL 12.3で確認したものです。 テーブルのロック 子テーブルの作成 パーティションの追加 デッドロックが起こりそうな例 前提 デッドロックの発生 対策 まとめ テーブルのロック PostgreSQL のロックについては、以下のドキュメントに詳しく書かれています。 www.postgresql.jp これによると、テーブルレベルのロックだけで8種類が存在します。 LOCK TABLE 文でロックが獲得されるのは言うまでもありませんが、INSERT文やUPDATE文、SELECT文ですら対象のテーブルのロックを獲得します。 さらに、実は以下のような操作でもテーブルのロックが獲得されます。 子テーブルの作成 PostgreSQL の継承機能を利用し子テーブルを作成すると、親テーブルのロックが獲得されます。例として、 psql コマンドで以下のような SQL を実行してみます。 partition_test=# CREATE TABLE parent (); partition_test=# BEGIN; BEGIN partition_test=# CREATE TABLE child () INHERITS (parent); CREATE TABLE ここで別のコンソールからロックの状態を確認してみましょう。 PostgreSQL の場合、 pg_locks を参照することでロック状態が確認できます。結果を見てみると、 parent テーブルのSHARE UPDATE EXCLUSIVEロックが獲得されているのがわかります。 partition_test=# SELECT d.datname, l.locktype, l.relation::regclass, l.mode FROM pg_locks l LEFT JOIN pg_database d ON l.database = d.oid WHERE l.pid != pg_backend_pid(); datname | locktype | relation | mode ----------------+---------------+----------+-------------------------- | virtualxid | | ExclusiveLock partition_test | object | | AccessShareLock partition_test | relation | 16428 | AccessExclusiveLock | transactionid | | ExclusiveLock partition_test | relation | parent | ShareUpdateExclusiveLock (5 行) パーティション の追加 PostgreSQL 10以降では宣言的パーティショニングがサポートされていますが、 パーティション テーブルに パーティション を追加する際に、 パーティション テーブルのロックが獲得されます。これも psql コマンドで確認してみます。 partition_test=# CREATE TABLE partition (id integer) PARTITION BY LIST (id); CREATE TABLE partition_test=# BEGIN; BEGIN partition_test=# CREATE TABLE partition_1 PARTITION OF partition FOR VALUES IN (1); CREATE TABLE 別コンソールの psql で確認すると、 パーティション テーブルの ACCESS EXCLUSIVEロックが獲得されているのが確認できます。 partition_test=# SELECT d.datname, l.locktype, l.relation::regclass, l.mode FROM pg_locks l LEFT JOIN pg_database d ON l.database = d.oid WHERE l.pid != pg_backend_pid(); datname | locktype | relation | mode ----------------+---------------+-----------+--------------------- | virtualxid | | ExclusiveLock partition_test | object | | AccessShareLock partition_test | relation | 16434 | AccessExclusiveLock partition_test | relation | partition | AccessExclusiveLock | transactionid | | ExclusiveLock (5 行) デッドロック が起こりそうな例 上記のように PostgreSQL では パーティション の追加などの操作でもロックが発生します。こうした挙動を把握、意識していないと、うっかり デッドロック を引き起こしてしまう場合があります。 前提 あるアプリケーションにユーザとグループが存在し、一人のユーザは複数のグループに所属することができるとします。 これをデータベース上で表現しようとすると、ユーザとグループは多対多の関係なので、ユーザテーブル、グループテーブルと、この2つを結ぶ中間テーブルを作成することになると思います。 ユーザとグループのER図 この中間テーブルはレコード数がとても多くなりそうなので、グループごとにパーティショニングすることにしました。グループテーブルにレコードを挿入するたびに、トリガーで新しい パーティション を追加します。 以上を SQL で表現すると、以下のようになります。 CREATE TABLE users ( id serial PRIMARY KEY, name text ); CREATE TABLE groups ( id serial PRIMARY KEY, name text ); CREATE TABLE user_group ( user_id integer , group_id integer ) PARTITION BY LIST ( group_id ); CREATE FUNCTION groups_insert_trigger_func() RETURNS trigger AS $$ BEGIN EXECUTE ' CREATE TABLE user_group_g ' || NEW.id || ' PARTITION OF user_group FOR VALUES IN ( ' || NEW.id || ' ) ' ; RETURN NEW; END ; $$ LANGUAGE plpgsql; CREATE TRIGGER groups_insert_trigger AFTER INSERT ON groups FOR EACH ROW EXECUTE FUNCTION groups_insert_trigger_func(); デッドロック の発生 上記のようなテーブル、トリガーを作成した状態で、2つの トランザクション A, Bで以下のような順序で操作を行うと デッドロック が発生します。 トランザクション SQL A LOCK TABLE user_group; B INSERT INTO groups (name) VALUES ('group 1'); A LOCK TABLE groups; トランザクション BでINSERT文を実行すると、 groupsテーブルをロックし行挿入 トリガーでuser_groupの パーティション を作成 user_groupのロック待ち となり、その後で トランザクション Aがgroupsのロックを獲得しようとすると デッドロック 状態になってしまいます。 トランザクション Bでは見た目上はgroupsテーブルに挿入しているだけなので、これで デッドロック が発生するとは気づきにくそうです。 ここでは例として宣言的パーティショニングを使ったケースを挙げていますが、子テーブルの作成でもロックが発生するため、継承によるパーティショニングでも同様に デッドロック が起こり得ます。ただし宣言的パーティショニングの場合、 トランザクション Bが獲得しようとするuser_groupのロックはもっとも厳しい ACCESS EXCLUSIVEロックなので、 LOCK TABLE user_group; を SELECT * FROM user_group; などに置き換えても同じく デッドロック してしまいます。 対策 このような デッドロック を回避する方法の一つは、テーブルのロック順序に気をつけることです。 上の例でいうと、 トランザクション Aでuser_groupよりも先にgroupsをロックすると、 デッドロック が発生しなくなります。 まとめ 子テーブルの作成( CREATE TABLE ... INHERITS ... )の際、親テーブルがSHARE UPDATE EXCLUSIVEロックされる パーティション の作成( CREATE TABLE ... PARTITION OF ... )の際、大元の パーティション テーブルが ACCESS EXCLUSIVEロックされる エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは!akiponxといいます。 さて、今回のブログはメールのあれこれについて書きます。 メールというと、いつでも送信できて、いつでも受信できる。 これが当たり前ですよね。 まれにメールが届かない。という声を聴くことがあります。 今回はメールが届かない原因について触れてみたいと思います。 まずはRakusのブログではメール関連の記事があまりないので今回は仕組みの部分。イメージを付けていただければと思います。 当たり前にメールの送受信ができる裏側で何が起きているのか見ていきましょう! 1. メールとは ①手紙を作る ②郵便配達の人が頑張って宛先の住所に手紙を届けます。 ③家のポストを確認して手紙を受け取る 2. メールの送信・受信 ①手紙を作る部分:メールを作成して送信ボタンをクリック ②郵便配達の人が頑張る部分:メール送信サーバが受信サーバへメール送信 ③郵便ポストにメールを取りに行く部分:受信者がメーラで受信メールサーバに届いているか確認しに行く メール送信 メール受信 全部をマージするとこんな感じ 3. メールが届かない原因 スパムメールとは スパムメール対策 4. SPF 5. DKIM 6. DMARC 7. まとめ 1. メールとは メールとは手紙のやり取りと似たようなものです。 画像①を使って説明します。 ①手紙を作る 封筒に下記を書く 宛先の住所 差出人の名前 手紙の本文を書く ポストへ投函 ②郵便配達の人が頑張って宛先の住所に手紙を届けます。 ③家のポストを確認して手紙を受け取る メールの送信・受信もイメージは同じです。 2. メールの送信・受信 こちらについてもメールの仕組みで使った図と見比べて説明していこうと思います。 ①手紙を作る部分:メールを作成して送信ボタンをクリック メーラ( gmail など)でメールを作成 (図1の封筒にあたる部分) 宛先メールアドレスを記載 (例: hoge @ example.com ) 自動で入力されていることが多いですが、差出人アドレスを記載 (例:akiponx@rakus.co.jp) 本文を入力 (図1の本文にあたる部分) 送信ボタンを押す (図1のポストに投函にあたる部分) ②郵便配達の人が頑張る部分:メール送信サーバが受信サーバへメール送信 この時にメール送信サーバが行っている処理をざっくり説明。 宛先メールアドレスの ドメイン ( example.com )を名前解決してメール受信サーバのインターネット上の住所である IPアドレス を取得。 メール受信サーバへメールを送信 ③郵便ポストにメールを取りに行く部分:受信者がメーラで受信メールサーバに届いているか確認しに行く メール受信はメーラによってメール受信サーバにメールが届いているか確認しに行く作業。 さて。ざっくりとした流れは説明した通りですが、送信・受信についてもう少し細かく見ていきます。 メール送信 ①の部分を切り取った画像がこちら。さらに処理の内容を記載しています。後で全部マージします。 1-1. 差出人アドレスがメールを送信していいかどうか認証する( SMTP 認証と呼ばれているもの。) 1-2. 認証が通れば送信する準備に入る 続いて②を切り取った画像。 ②の中身を細かく分割するとこんな感じです。では説明していきましょう。 2-1. どこに送ればいいのか確認するため、 送信先 メールアドレスの ドメイン 部分を確認。 2-2. 確認した ドメイン の住所がどこなのか DNS レコードにMXレコードを問合せ 2-3. DNS から 送信先 アドレスの住所をもらう。 2-4. DNS からもらった住所宛にメールを送信する。 ※MXレコードとはメールをどこに送ればいいのかを教えるための、 DNS レコードの一種です。 メール受信 さて、続いてメール受信ですが、メールが受信できるようになるまでもいくつか過程があります。 メール送信の段階で②の処理が全て終わっているように思われますが、実はまだ終わっていません。 続きはメール受信サーバ側で処理がされているためこちらに続きを記載します。 2-5. 届いたメールの差出人アドレスの ドメイン を確認 2-6. DNS に SPF (または DKIM の公開鍵)を問合せ 2-7. SPF (または DKIM の公開鍵)の評価を実施 ※1 2-8. 問題なければメールを受け入れる処理を実施 2-9. メー ルボックス (イメージ的にはポスト)にメールを置く。 3-1. メーラが POP3 or IMAP でメールがあるかどうか確認しに行く ※2 3-2. 新着メールが存在していればダウンロードして表示する。 ※1  SPF (または DKIM )の評価によってはメールが拒否されることがある。 ※2  POP3 や IMAP はメールを受信する用の 通信プロトコル 。    また、POP3SやIMAPSといった SSL証明書 を利用した プロトコル もある。 SPF や DKIM については後程説明します。 全部をマージするとこんな感じ 3. メールが届かない原因 さて、メールの送信・受信についてはある程度説明できたかと思います。 続いては当たり前に届くはずのメールが届かないことがある原因ついて、考えられることを書いていきます。 スパムメール 判定がされている メールアドレスが間違っている etc... メールが届かない原因として スパムメール と評価されてしまい、拒否されているケースが多いです。 スパムメール とは スパムメール とは迷惑メールの一つで、主に広告や宣伝、ウィルス付きメールなどに使われることが多いです。 広告や宣伝に使われるならいいんじゃない?と思う方もいるかと思いますが、そうではありません。 主に詐欺やデータを取る目的で送られていることが多いです。 そもそもメール配信などはオプトイン(メルマガ送ってもいいですよ。という意志表明)が無いと送ってはいけないのがルール。 そのオプトインがない人にメールを大量に送る。迷惑ですよね。 スパムメール 対策 スパムメール を受信しないようにする対策として、 スパムフィルター を実装する 逆引きが設定されていない IPアドレス からは受信しない SPF チェックが通らないものを受信しない DKIM 署名がないものを受信しない ブラックリスト に登録されている ドメイン ( IPアドレス )からのメールは拒否する などがありますが、「メールが届かない!」ということは大きなクレームにもつながるため、 なかなか SPF や DKIM を設定していないメールは受信しない。という強気な対応に踏み切れないところがあります。 そんなこんなで続いては SPF とか何か、 DKIM とは何か。について書いていきます。 4. SPF SPF とは、 DNS のTXTレコードにあたります。 送信元 ドメイン の名前解決を実施しTXTレコードに記載がある SPF の値を確認します。 例を出すと基本的にこんな感じの SPF が設定されています。 # SPFの例 rakus.co.jp descriptive text "v=spf1 include:spf.rakus.co.jp ip4:xxx.xxx.xxx.xxx ~all" SPF の値には IPアドレス などが記載されており、送信サーバの IPアドレス が SPF に記されていればOK、記載されていなければ基本的にNGとなります。 受信メールサーバ側の設定によりますが、この SPF のチェックを通らないとメールを受信しない。という設定もできます。 もし「メールが届かない…orz」となった方は SPF をチェックしてみるのもいいかもしれないですね。 5. DKIM DKIM とは、これも DNS のTXTレコードを利用した技術です。 SPF より少し複雑。 ざっくりとした流れとしては メール送信サーバがメールヘッダーに 電子署名 を実施 メール受信サーバが DNS サーバに問い合わせを実施し、公開鍵を入手 入手した公開鍵で 電子署名 が複合化し、認証を実施。 こんな感じで DKIM は評価されます。 DKIM 署名を実装する方法などなどは今回は書きません。 6. DMARC DMARCとは SPF と DKIM の合わせ技です。 基本的にメール受信については SPF が設定されていなくても、 DKIM が設定されていなくても、 そのメールを受信するかどうかは受信メールサーバ側が決めています。 DMARCを設定することによって、メール送信者側が挙動を指定できます。 例えば、 SPF が通らなければメールを拒否(迷惑メールに振り分け)する DKIM の署名がなければメールを拒否(迷惑メールに振り分け)する 上記の設定をすることによって、自分の ドメイン を語ったメールを受信させないようにできます。 また、DMARCを設定することによって、自分の ドメイン を語ったメール( SPF や DKIM の評価が通らなかったもの)が何通送られているのか というレポートメールを受け取ることができます。 これを受け取ったから何ができるのかといわれると難しいですが、DMARCの運用を考えていかないといけないですね。 7. まとめ メールが届かない原因は今回記載した以外にもありますが、 本記事に記載されている内容を踏まえてメールが届かなった原因の調査や対策をしてみていただければと思います。 大手フリーメールの会社ではセキュリティが厳しくなってきており、迷惑メールフォルダに振り分けられてしまうこともしばしばあります。 迷惑メールフォルダに振り分けられないように、 SPF ・ DKIM の対策をしっかりしていきましょう! そのうち別のメール関連の記事も書きます。 参考にさせていただいたページ DMARK エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com
アバター
はじめに 技術広報のitoken1013です。 いつも ラク スのエンジニアブログのご購読、そしてエンジニアイベントへのご参加、ありがとうございます! 今回は9/16(水)に行われた ラク スMeetup『持続可能な大規模 SaaS 企業の開発戦略』を紹介させていただきます。 イベントにご参加いただきました方も初めてご覧になる方も、4名のエンジニアの発表内容をご覧いただけますと幸いです! rakus.connpass.com イベントテーマ概要 本日は以下の計5つの「中小企業を強くする」ための大規模 クラウド サービスに関わるアプリケーションエンジニア、インフラエンジニアから発表をさせていただきました。 メールディーラー 楽楽販売 配配メール Curumeru チャットディーラー 上記どのプロダクトもおかげさまで拡大を続けていますが、裏側ではエンジニアがプロダクトを持続的に開発・運用するために日々工夫を重ねています。 今回は ラク スの開発組織に根付く 費用対効果を考え、やるべき最適な対策をとる文化 をアウトプットすべく、各プロダクトでの取り組みを発信させていただきました。 発表の紹介 長期的に考える技術的負債マネジメント戦略 「負債を返すチャンスは来るので、そのチャンスを逃さないために 諦めずに負債の局所化や新しく作り込まないなど準備をしておくのが大事 」 今回のMeetupのコンセプトを象徴するようなコメントですが、トップバッターであるやなせ(メールディーラー担当)から出てきた言葉でした。 これまでのMeetupでも度々登場してきたメールディーラーは歴史が深く、いわゆる「技術的負債」と向き合う機会も度々あります。 今回はあらためて技術的負債とは何なのか、そしてどのように立ち向うべきかを、やなせの経験と理論を踏まえて語らせていただきました。 会計的な負債と比較した場合の見えにくさ、またビジネスの側面も踏まえての方法論は多くのエンジニアに参考にしていただけると思います。 speakerdeck.com DB容量肥大化への取り組み 次の発表は 「データの肥大化問題」 がテーマです。 Webデータベースである楽楽販売を担当する山内より、その製品の特長上、深刻となったデータ量の問題に 「Cloudian HyperStore」 というオブジェクトストレージを導入して対処したエピソードを発表させていただきました。 アプリ・インフラどちらにも多大な影響を与える一大プロジェクトでしたが、今後も持続的に楽楽販売を利用いただける環境を作り上げるため、山内をはじめとするチームは主に3つの課題と対峙しました。 データ量に関する問題は長くプロダクトを運用していく上で避けられない問題です。 現在同じ問題を抱える方には、今回の発表からヒントを得ていただけると幸いです。 speakerdeck.com 4年間の レガシーシステム 運用から学んだトラブル対策の取り組み方 運用の スペシャ リストとして配配メールとCurumeruの2プロダクトに関わる西尾より、運用面からプロダクトを支えるためのプ ラク ティスを発表させていただきました。 どんなに緻密に作ったプロダクトでもトラブルを全て未然に防ぐことは難しいものです。 特に西尾が関わるプロダクトはメール配信サービスであるが故、以下の3フェーズそれぞれでプロダクト固有の問題、またメール プロトコル や業界固有の問題を抱えています。 メールを「作る」 メールを「送る」 メールを「届く」 今回の発表では未然にトラブルを防ぐことに全力を注ぐのではなく、起こってしまったトラブルに如何に迅速に対応するかを語っています。 サービスの外側・内側それぞれで学びとなるプ ラク ティスが提供できる内容です。 speakerdeck.com Infrastructure as Codeによるインフラ構築の自動化により、オペレーションミスを防ぐ仕組みづくり 最後にインフラエンジニアである山村よりインフラエンジニア目線での技術的負債とは何か、そしてInfrastructure as Codeを中心とした技術的負債への対処方法を発表させていただきました。 チャットディーラーの運用・保守をメインに担当する山村ですが、着任より現在に至るまで様々な技術的負債に着目する中、特に「共有不足で実装内容の ブラックボックス 化による負債」には強い問題意識を持っていました。 解決策として編み出したプ ラク ティスは属人性の解消のみならず、いまや作業 工数 としても大きく寄与するソリューションとなっています。 インフラ・アプリに関わらず、どのエンジニアにも是非ご参考にしていただきたいプ ラク ティスばかりです。 speakerdeck.com おわりに 持続可能な大規模 SaaS 企業の開発戦略、いかがでしたでしょうか? ラク スは多様な SaaS を長く開発し続けることで、 費用対効果を考え、やるべき最適な対策をとるための戦略と思考 が根付いています。 今回の4名のエンジニアの発表より、その一端を感じ取っていただければ幸いです。 次回はそんな多様な SaaS から フロントエンド に特化したテーマで、10月にMeetupを開催予定です! Vue.js、Typescriptなどのモダンなフロントエンドにご興味のある方は、ぜひconnpassをウォッチいただければと思います! rakus.connpass.com rakus.connpass.com
アバター
こんにちは。 やなせたかし です。 今回は「繰り返し」について掘り下げてみようと思います。 PHP に限ったことではないですが、繰り返しはプログラミングでは基本的な操作です。たとえば、 while for など、処理を繰り返す構文です。その中でも利用頻度が高いのは for でしょうか?サンプルコードでも配列を繰り返す時に使われたりと目にすることが多いと思います。 PHP であれば、 foreach という構文もあります。これも繰り返しのようです。 この中でも、 for と foreach どちらを使うのか?が議論されたり、どちらのほうが性能がいいだの悪いだの、過去にも比較されてきました。2020年の現在、(あくまで PHP 上での)両者にどんな差があるのでしょうか。 forを眺める 以下のコードを例に考えてみます。何の変哲もない PHP コードです。 <?php $ a = [ 1 , 2 , 3 ] ; $ b = 0 ; for ( $ i = 0 ; $ i < count ( $ a ) ; $ i ++ ){ $ b += $ a [ $ i ] ; } 配列の和を計算しています。この for の終了条件は $i < count($a) です。配列の大きさまで $i が増加すると終了します。 とくに何かあるわけではないコードのようにも見えますが、よく見るとコードの関心ごとがバラバラなことに気づきます。 たとえば、 $i はただの変数で、配列そのものとの関係はない ループ内でアクセスしている配列が $a であるかはループの条件に絡まない 和をとる配列は $a でなくても $aa にしても構わない そう、 for はただのループで、「配列の操作」とは関係がないのです。 この「関係がない」というのは、コードを見てもわかることですが、どこまで関係がないのか? 改めて for を振り返ってみましょう。 まず、 for という構文について。 さきほども書いた通り、ループを表現する構文です。では構文としてはどのようなルールになっているでしょうか? ルールを正確に知るため、 PHP のパーザーを呼んで文法そのものを見てみましょう。 PHP のコードを理解している部分ですから、正確な文法がわかるはずです。 PHP そのもののソースは、以下から手に入ります。 https://github.com/php/php-src ということで PHP のパーザーでは、 for の構文は以下のように定義されています。 %token T_FOR "for (T_FOR)" statement: | T_FOR '(' for_exprs ';' for_exprs ';' for_exprs ')' for_statement { $$ = zend_ast_create(ZEND_AST_FOR, $3 , $5 , $7 , $9 ); }; for() の中に for_exprs というのが並んでいるのがわかります。そして、最後に for_statement というものがあります。 この for_exprs というものは何を表しているでしょうか。これは何もない(NULLと表します)か、カンマ区切りの expr のリストのようです。 ちなみに、 expr は式を表します。メソッド呼び出しのほか、 $a だけでも式ですし、 $i = 0 なども式にあたります。 そして zend_ast_create というものを呼び出しています。ここで ZEND_AST_FOR というものを渡しているので、ここで for文 を作っているようです。 $3,$5... はこのパーザーにマッチした要素の位置っぽいですね。 正規表現 のマッチっぽくも見えます。 さらに追っていくと、 PHP の for の定義が見えてきます。 void zend_compile_for(zend_ast *ast) { zend_ast *init_ast = ast->child[ 0 ]; // ← 1つ目のfor_exprs zend_ast *cond_ast = ast->child[ 1 ]; // ← 2つ目のfor_exprs zend_ast *loop_ast = ast->child[ 2 ]; // ← 3つ目のfor_exprs zend_ast *stmt_ast = ast->child[ 3 ]; // ← for_statement //.... } ということで、パーザーは for をこんな風に理解しています。 <?php for ( 開始状態の式; 終了条件の式; ループ式 ) for内部の文 となります。当たり前のことですが、調べてみるとしっかり書かれているものです。では、 for の命令を確認してみましょう。 以下のソースのopcodeをダンプします。 opcodeとは、 PHP で書かれたコードを コンパイル して得られるもので、Zendエンジンで直接実行されます。 <?php $ a = [ 1 , 2 , 3 ] ; $ b = 0 ; for ( $ i = 0 ; $ v = count ( $ a ) ; $ i ++ ) { $ b += $ a [ $ i ] ; } すると、以下のようなダンプが出力されます。 L0 (3): EXT_STMT L1 (3): ASSIGN CV0($a) array(...) L2 (4): EXT_STMT L3 (4): ASSIGN CV1($b) int(0) L4 (6): EXT_STMT L5 (6): ASSIGN CV2($i) int(0) L6 (6): JMP L12 L7 (7): EXT_STMT L8 (7): T6 = FETCH_DIM_R CV0($a) CV2($i) L9 (7): ASSIGN_ADD CV1($b) T6 L10 (6): T8 = POST_INC CV2($i) L11 (6): FREE T8 L12 (6): T9 = COUNT CV0($a) L13 (6): T10 = IS_SMALLER CV2($i) T9 L14 (6): EXT_STMT L15 (6): JMPNZ T10 L7 L16 (10): RETURN int(1) なんだか アセンブリ っぽいですね。 処理を見ていきましょう。L3までは for と関係ない処理です。ということで、それ以降を確認していきましょう。ループに関する個所は以下のような感じです。わりとイメージ通りの命令になっているのではないでしょうか。 行 処理 L5 $i に 0 を アサイ ン L6 L12 に飛ぶ L7 - L9 $b += $a[$i] の処理 L10 - L11 $i++ L12 T9 に count($a) の結果を保存 L13 T10 に $i < T9 の結果を保存 L15 T10 が 0 でなければ L7に飛ぶ さきほどまとめた文法と照らし合わせると、それぞれの式・文が評価されている命令の並びがわかります。初めに終了条件の式に飛ぶ以外は、普通にループしているだけですね。 要素 行 開始状態の式 L5 for内部の文 L7 - L9 ループ式 L10 - L11 終了条件の式 L12 - L15 ここで注目したいのが、 for内部の文 です。命令の L8 を見てみましょう。配列から値を取り出す処理です。 L8 (7): T6 = FETCH_DIM_R CV0($a) CV2($i) ここで使われている命令は、 FETCH_DIM_R となっています。これは配列にインデックスアクセスするときの命令です。当たり前のことを確認しているようですが、結構大事なところです。 このように実際の命令まで追っていくと、やはり for というのは「配列の操作」とは直接関係のない構文である、と言えるでしょう。 foreachを眺める 続いて、 foreach を見てみましょう。ソースは以下の通りです。やりたいことは変わりません。 <?php $ a = [ 1 , 2 , 3 ] ; $ b = 0 ; foreach ( $ a as $ v ) { $ b += $ v ; } では、まずは foreach の文法を確認しましょう。さきほどと同じように、 PHP のパーザーにある定義を確認します。 %token T_FOREACH "foreach (T_FOREACH)" statement: | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement { $$ = zend_ast_create(ZEND_AST_FOREACH, $3 , $5 , NULL, $7 ); } | T_FOREACH '(' expr T_AS foreach_variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement { $$ = zend_ast_create(ZEND_AST_FOREACH, $3 , $7 , $5 , $9 ); } どうやら foreach は2つの文法が定義されているようです。 上側は配列の value だけを、下側は T_DOUBLE_ARROW とあるように配列の key, value 両方使用するタイプですね。 ここで出てくるそれぞれの要素も、なんとなくイメージできそうです。 そして、さらに追いかけると、 foreach の定義にたどり着きます。 void zend_compile_foreach(zend_ast *ast) /* {{{ */ { zend_ast *expr_ast = ast->child[ 0 ]; // foreachで回すもの zend_ast *value_ast = ast->child[ 1 ]; // foreachで回される「値」 zend_ast *key_ast = ast->child[ 2 ]; // foreachで回される「キー」 zend_ast *stmt_ast = ast->child[ 3 ]; // 逐次処理される文 //・・・ } PHP パーザーはこのように理解しています。余談ですが、読み進めていくと foreach は「値」が参照かどうかで処理を切り替えている、ということもここから読み取れます。 今回は値を渡した時の挙動に絞って見ていきましょう。 ということで、実行されている様子を調べます。 調べるのは以下のコードです。 <?php $ a = [ 1 , 2 , 3 ] ; $ b = 0 ; foreach ( $ a as $ v ) { $ b += $ v ; } このopcodeのダンプは以下の通りです。 L0 (3): EXT_STMT L1 (3): ASSIGN CV0($a) array(...) L2 (4): EXT_STMT L3 (4): ASSIGN CV1($b) int(0) L4 (6): EXT_STMT L5 (6): V5 = FE_RESET_R CV0($a) L10 L6 (6): FE_FETCH_R V5 CV2($v) L10 L7 (7): EXT_STMT L8 (7): ASSIGN_ADD CV1($b) CV2($v) L9 (6): JMP L6 L10 (6): FE_FREE V5 L11 (10): RETURN int(1) LIVE RANGES: 5: L6 - L10 (loop) 見ての通り、 for と比べてかなり異なります。 L3までは飛ばして、 foreach 内部を見ていきましょう。 行 処理 L5 V5 に $a の イテレータ ーを保存 無ければL10へ L6 $v に V5 から値を取得し アサイ ン 終端ならL10へ L8 $b += $v L9 L6に飛ぶ L10 V5 を解放 opcodeレベルではとてもシンプルになっています。ここで注目したいのは、 L5 - L6 の部分です。 FE_RESET_R , FE_FETCH_R は、 foreach で使用する命令です。配列 $a からの値の取得自体が foreach の機能である、ということですね。 逆に for は普通の配列アクセス命令でした。こういったところにも単純な繰り返しの for と、配列を逐次処理する foreach の違いがあります。 まとめ for と foreach の両者を比較してみると、構文ごとの目的の違いがわかりました。 for は繰り返し処理のための構文 foreach は逐次処理のための構文 一見どちらも繰り返しを目的としているようですが、 foreach のほうが用途を限定されています。 foreach はしばしば for をシンプルに書けるようになったと表現されたりしますが、実際にopcodeで比較してみると、 for の シンタックス シュガーではなく、内部ではしっかり別の処理です。 何気なく使っている構文でも、調べてみると意外と奥深いものです。
アバター
はじめに はじめまして、新卒1年目のyykaoruです。 今回は私事ですが、引っ越しをした際に実家にキーボード・マウスをおいてきてしまい、キーボード・マウス・モニターがない状態でなんとかして Raspberry Pi にアクセスするお話です。 やってみたこと概要 Raspberry Pi を購入しウキウキで 開封 していた際に、あることに気が付きました。 「あれ、ノートPCはあるけど、 Raspberry Pi 用のキーボードとマウスない・・・?」 しかし、どうにかして早くさわりたい...しばらく考えた結果 「せや!ネットワークに接続された Raspberry Pi の IPアドレス (プライベート)特定して SSH 接続でなんとかしたらええんや!」 今回は Raspberry Pi をセットアップする際にノートPCはあるのですが、キーボード・マウス・モニターがない場合に Raspberry Pi の IPアドレス を特定し、 SSH を利用して Raspberry Pi にアクセスする方法を紹介しようと思います。 今回の使用機器&環境 Raspberry Pi 4 Model B(8GB) Raspberry Pi 4 Model B用電源(USB Type-c 5V 3A) ノートPC(windows10) microSD (32GB) microSD をPCに接続するためのアダプタ(USBタイプ、SDカードタイプなどお好みで!) 有線LANケーブル ネットワーク環境 環境構築 Raspberry Pi の OS インストールは ラズベリー パイ財団公式が提供している Raspberry Pi Imager を利用します。 今回は Raspberry Pi で使用するOSとして Ubuntu 20.04.1 LTSを使用します。 OSのインストールが完了した後にSDカードをもう一度マウントするとSDストレージが出現し、中身はboot ディレクト リになっています。 ここの中に新たに ssh というファイル名のファイルを作成しておきます。 これにより、初期起動時に ssh 機能が有効になります。 Raspberry Pi に電源とネットワークルータにつながった有線LANケーブルを接続します。 2~3分くらい待ちます。 これで準備は完了です。 ノートPCから Raspberry Pi に SSH で接続 ノートPCで「 windowsキー + R」をし、入力欄に「cmd」と入力します。 すると コマンドプロンプト が起動します。 そこに「 arp -a」を打ち込みます。 すると同じネットワークに接続されている IPアドレス と MACアドレス が表示されます。 表示された一覧の情報で MACアドレス が「B8:27:EB:」か「DC:A6:32:」か「E4:5F:01:」で始まるものを探します。 これは Raspberry Pi の MACアドレス のレンジがこの3つに割り当てられているのでそれを利用して Raspberry Pi を特定する為です。 詳細は以下のサイトで確認できます。 udger.com 今回紹介した MACアドレス で始まる MACアドレス が書かれた行の IPアドレス をメモしておきます。 今回私の環境では「192.168.10.110」が割り当てられていました。 これで Raspberry Pi の IPアドレス が特定することができたので、 SSH 接続を試みます。 「 ssh ubunntu@192.168.10.110」を コマンドプロンプト に入力します。 すると以下の画像のように Raspberry Pi の Ubuntu に接続することができました。 ログインの初期パスワードはOSが Ubuntu の場合「 ubuntu 」となっているので、これを入力し、 新たにログ インパス ワードの設定を行うように要求されるのでパスワードを設定します。 これで、もう一度 SSH 接続をし、設定したパスワードでログインすると、 Raspberry Pi に接続することができます。 後は SSH のセキュリティ設定をするなり、お好きなツールのインストールを行ったり好きなようにできます! 所感 今回は休日で Raspberry Pi で遊ぼうとしたところ、肝心な入力系の機器を用意し忘れてしまったことから 新たな方法でのセットアップ方法を習得することができました。 始まりはとても初歩的なミスでしたが、結果として新しい知識を得ることができたのでとても満足しています。 セットアップした Raspberry Pi はせっかくなのでお家環境モニタリングシステムにでも活用しようと思います。 そのお話はまたいつかの機会にできたらと思います! では! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
こんにちは、 id:eichisanden です。 社内の有志とDDD( ドメイン 駆動設計)関連の2冊の書籍の読書会を開催したので、ふりかえって書いていきたいと思います。 1冊目:「エリック・ エヴァ ンスの ドメイン 駆動設計」 エリック・エヴァンスのドメイン駆動設計 作者: Eric Evans 発売日: 2013/11/20 メディア: Kindle 版 最初の題材として選んだのは、DDDの原典「エリック・ エヴァ ンスの ドメイン 駆動設計」です。 長いので以後は「 エヴァ ンス本」と呼びます。 今年の1月〜6月に掛けて、計17回開催しました。 読書会をやるきっかけ ある日の社内チャットで、 エヴァ ンス本に挫折したことで盛り上がったのがきっかけでした。 章と書いてますが部の間違いです かく言う自分も Tweet をさかのぼると3年前に挫折していて、いつかリベンジしたいと思っていました。 第4部の途中まで読んだけど、途中から字面を追ってるだけになったので再読する前提で最後までキーワードだけ拾い読みした。普通に読んだら1ヶ月じゃ終わらんなこれ。 — A1 (@EichiSanden) 2017年1月31日 良い機会なのでみんなで読書会しようと言うことになり、社内で声をかけたところ10人のメンバーが参加してくれました。 本は会社経費で購入させてもらいました。技術書としても高い部類の本に入るので非常にありがたいです。 壮観ですが本当はもっとあります 読書会の形式 週に1回 各回1.5時間 エクストリームリーディング 形式 そもそも私自身が読書会を主催したことが無いため色々調べた結果、この方式に決めました。 なぜこの形式にしたかと言うと、下記のような仮説があったからです。 事前準備が必要だと参加者の負担が大きいのでは? 誰かが講師になるスタイルだとその人に掛かる負担が大きいのでは? 参加者の負担が大きいと参加が離脱していってしまうのではないか? 特に事前準備はせず(と言っても主催者の私は不安なので読みましたが)、読書会当日は数ページ程度のパートに切って、読んでは議論を数セット繰り返しました。 分からなかったことや他の人の意見を聞きたいことは付箋に書いおいて、読み終わったらホワイトボードに貼って議論しました。 やってみてどうだったか? みんなで理解を補いながら読めるから理解しやすい 参加者の中にはDDDを実践しているチームの人や、以前にこの本やIDDD本を読んで知識がある人がいたりして、 意味が分からなかったところは他の人の解釈を聞くことで理解の助けになりました。 ただし難しいところはみんなで読んでも難しい 内容の難しさに輪をかけて文章の構造が分かりにくくて意味が掴めない箇所については、 みんなで束になって掛かっても分からないこともありました。 10章しなやかな設計の「概念の輪郭」が意味わからなすぎて辛い。頭が割れるー — A1 (@EichiSanden) 2020年3月2日 理解できた方が良いんですが、みんなも分からないのだから自分だけ理解力がない訳ではないと言う変な安心感もありました(笑) これについては他の本も含め行ったりきたり何度も読むことでブレイクスルーがあるかもしれないと思いました。 もしかすると分からない文は英語の原書も併せて読むことで理解できる物もあるかもと思いました(これは実践できてませんが) 事前に読んできてもらうスタイルの方が良かったかもしれない 参加者の負担を下げたことで完走率は高くなったとも思いますが、 この本のように難易度が高い場合は、事前に読んできてもらい当日は議論に集中した方が良かったかもしれないとも思っています。 これは読書会に求める期待効果によって実施スタイルは今後は使い分ようと終わってみて思いました。 技術書を読む習慣がない人にもっと本を読んで欲しい  → 本を読む機会を作ることが目的なので当日読めば良い 本の内容を深く理解するため他の人と議論したい  → 事前に読んできてもらって議論を中心にする 2冊目:「 ドメイン 駆動設計 モデリング /実装ガイド」 booth.pm 今年の7月〜8月に掛けて、計8回開催しました。 読書会をやるきっかけ エヴァ ンス本でモデルや ユビキタス 言語など戦略的な設計はなんとなくは理解できたが、具体的なイメージが湧かなかったため評判が良かったこの本を読んでみたところ良い本でしたので読書会の題材にしました。 コロナの影響でオンラインでの開催になり場所の制約がなくなったため、他拠点のメンバーにも声をかけたところ18名ものメンバーが参加してくれました。 読書会の形式 オンライン形式(ZOOM) エクストリームリーディング形式 事前に Github のIssueに持ち回りで概要をまとめておく 当日は疑問点などをSli.doに書いてもらって読み終わったら議論した やってみてどうだったか? エヴァ ンス本に比べて本の内容がスッと頭に入ってくるので、当日読むスタイルでも無理なくやれました。 本を読んでもらった後、 Github のIssueにまとめた意訳と自分の理解のDiffをとることで更に理解が進みました。 メンバーが拡大したことでDDDを実践している人も増えて、他サービスでDDDを実践して困ってることなど生きた情報が共有されて良い読書会になりました。 番外編: モデリング 大会 参加者から発案で、去年にネット上で流行っていたチケット料金 モデリング を読書会の集大成として皆んなでやってみました。 実際に手を動かしてみると自分の理解度合いが分かりますし、コードも書いてみないとモデルを完成させるのが難しいと言うのを肌で感じることができました。 拙作ですが、モデルには「振る舞い」まで書かない方が良いかもね、と他の参加者にフィードバックもらいました。 github.com チケット料金 モデリング はお題としてちょっと難しかった気がするので、 もっとシンプルなお題でまたやってみたいと思っています。 最後に 読書会の題材としてDDDの本は向いていて、 エヴァ ンス本は複数人で助け合いながら読むのが良かったです。 エヴァ ンス本で モデリング や ユビキタス 言語の重要性を理解した後で、噛み砕いた本で理解を深めるのは順番としては悪くなかったのではと思っています。 読書会を開催する時の参考になれば幸いです。
アバター
はじめに PHP8で導入されるmatch式が導入されます。 プログラマ としてはどういった場面で使いやすいのか、バグが入りやすさはどうなのかといった点が気になるのではないかと思います。 この記事では、match式についてswitch文との違いを述べながら、構文の性質からどういった場面で役立ちそうかを私なりに考えまとめました。 はじめに match式とは matchは式。値を代入する場面で真価を発揮する 文とは 式とは breakが不要。バグが入りにくくシンプルな記述ができる switch match 条件の記入忘れにエラーで気付ける switch match 厳密な比較 switch match おまけ おわりに match式とは match式の基本的な構文と、switchとの相違点を大まかに記載します。 簡単に言ってしまうとswitchのような分岐の構文です。 $param = 1; $result = match($param) { 1 = > "1の結果", 2 = > "2の結果", } // 与えられた引数($param)に対して一致するケース(1または2)の結果(= > の右側)を返す // 結果:$result = "1の結果" 下記にswitch文との大まかな違いを記載します。 switch match 返り値 なし あり breakの記述 必要 不要 各条件の処理 ブロックで記述可能 1行でしか書けない 比較 緩やか(==) 厳密(===) どの条件にも当てはまらない場合 そのまま実行される エラーになる 次の章以降では、PHP8で導入されるmatch式がどのような場面で生きてきそうかを紹介します。 match式の詳しい仕様は下記もご参照して頂くとより分かりやすいかと思います。 参考: wiki.php.net qiita.com matchは式。値を代入する場面で真価を発揮する 比較表で返り値が、matchは「あり」、switchは「なし」と記載しました。 これは、matchが 式 であり、ifやswitchが 文 であるためです。 この章では、式と文の違いを見た上で、match 式 が役立ちそうな場面を紹介します。 文とは ざっくり説明すると、返り値を返さず、右辺に配置できないものです。 例:ifやswitchがこれにあたります。右辺に配置できないため直接値の代入はできません。 // 下記のような直接値を代入するような書き方は「文」ではできない $result = if($param == 1){… $result = switch($param){… このため、各条件での代入が必要であり、代入忘れの恐れがあります。 // ifやswitch文では各条件で代入が必要。ケースが増えると代入忘れが怖い /** if **/ if($param == 1){ $result= "1の結果"; } elseif($param == 2){ $result = "2の結果"; } /** switch **/ switch ($param) { case 1: $result = "1の結果"; break; default: $result = "defaultの結果"; } 式とは ざっくり説明すると、返り値を持ち「=」の右辺に配置できるもの。また文としても使えます 例:matchがこれにあたります。直接値の代入が可能なので、代入を各条件に記述する手間がなく、代入忘れもなくなります。 $result = match($param) { 1 = > "1の結果", 2 = > "2の結果", }; もちろん不要であれば代入しない書き方もできます。 match($param) { // $result = の部分は取り外し可能 1 = > func_hoge(), 2 = > func_huga(), }; 参考: jsprimer.net breakが不要。バグが入りにくくシンプルな記述ができる switchでは明示的にbreakの記述が必要です。breakを毎回書く必要があり書き忘れのチェックが必要なので、労力がかかります。ただし、そのまま次の条件の処理を実行(フォールスルー)する記述は簡単に書けます。 switch breakは明示的に記述が必要。フォールスルーがデフォルトであり、フォールスルーを想定した記述は簡単にできますが、その代わりにbreak忘れによるバグが入りやすい構造になっています。 switch ($param) { case1: func_hoge();// break忘れ case2: func_fuga();// case 1でヒットしても処理fugaも実行される } ただし、case1の場合はcase1,2を、case2のときはcase2のみを実行したい場合は上記のコードであっています。 breakを書かないことによりフォールスルーを簡単に実現できますが、その分「break忘れ」というバグが入り込む副作用を持っています match フォールスルーできない。その代わりbreakを明示的に記載する必要がなく、break忘れを気にしなくてよいです。 match ($param) { 1 = > func_hoge(), 2 = > func_fuga(), }; フォールスルー(case1の場合はcase1,2を、case2のときはcase2のみを実行)したい場合はmatchには向きません。 というのも、基本的にフォールスルーしない仕組みであることに加え、matchは条件の右辺に1行しか記載ができないからです。 このため、上記のswitchと同じことをしようとすると下記のようになります。 match ($param) { 1 = > func_hogefuga(), // ここに2行以上の記述はできない 2 = > func_fuga(), }; function func_hogefuga() {// 関数切り出しが必要になる func_hoge(); func_fuga(); } 条件の記入忘れにエラーで気付ける 分岐に限らず、予期せぬ値が代入される場合 や 意図せず何も代入されなかった場合は、エラーやログで気付ける方がよいです。 match式ではどの条件にもあてはまらない場合エラーになるため、誤りに気づきやすいです。 switch 条件が抜けていてもそのまま実行されます。 switchのようなエラーがでない仕組みの場合、バグが発生した時にケース忘れの可能性を考えることが必要となります。数行ならよいですが、ケースや処理が多い時は確認が大変です。 foreach ($users as $user) { switch ($user- > score) { case "good": $command = "upgrade"; break; case "bad": $command = "BAN"; break; // $user- > scoreがnormalの場合、どのケースも通らず値の代入が行われない。 // このため、次のループ処理にそのまま$commandの値が引き継がれる } $commandQue[$user- > id] = $command; } // batchキューに入れる // 日時で実行されるバッチで普通のユーザがBANされてしまう match 条件が抜けているとエラーになります。 matchのようにエラーとなる仕組みの場合、ケースの記述忘れを気にする必要がりません。 foreach ($users as $user) { $command = match ($user- > score) { "good" = > "upgrade", "bad" = > "BAN", // normalの場合はケースが無いので、エラーとなり、処理が中断される } $commandQue[$user- > id] = $command; } 厳密な比較 matchは厳密な比較のため「数値だと思っていたら文字列であり思わぬ分岐に入った」といったことががなくなります。 ただし、 PHP はそもそも厳密な比較を前提として作られているとは言えないので、厳密な比較にすることでエラーになることもあるので注意が必要です。(おまけで記載します) switch $hoge = "hoge"; switch ($hoge) { case 1: echo "1の結果"; break; case "hoge": echo "hogeの結果"; break; } // "1の結果" match $hoge = "hoge"; match ($hoge) { 1 => "1の結果", "hoge" => "hogeの結果", } // "hogeの結果" 一見すると厳密な比較の方が良さそうです。ただし、元々型を気にせず気軽に書けることが特徴でもあった PHP 。 古いコードでmatchを導入する際にはまずは、型をしっかり整備するなど対策が必要そうです。 おまけ PHP の関数が厳密な型を意識した作りでないため、思わぬエラーが発生する例を紹介します。 例:拡張子でケースを分けたい場合 下記の例では画像ファイル名の拡張子から処理を分岐させるコードです。 $img_name = "img.jpg"; /** switch_正規表現 **/ switch (true) { case preg_match('/(\.jpg)$/', $img_name): echo "jpgだよ"; break; case preg_match('/(\.png)$/', $img_name): echo "pngだよ"; break; // gif等その他の分岐が続く default: echo "defaultだよ"; } // = > "jpgだよ" /** match_正規表現 **/ echo match (true) { preg_match('/(\.jpg)$/', $img_name) = > "jpgだよ", preg_match('/(\.png)$/', $img_name) = > "pngだよ", // gif等その他の分岐が続く default = > "defaultだよ" }; // = > "defaultだよ" 実はpreg_matchは該当する場合trueでなく、1を返します。このためmatch式ではtrue===1となり、 正規表現 に該当してもそのケースに入ることはありません。 このように既存の関数の性質を正しく理解していないと思わぬバグを生む可能性があります。 おわりに この記事ではPHP8で導入されるmatch式について、switchと比較しながら、有効そうな場面やバグの入りにくさについて記載しました。 今後導入を検討している方やmatch式について知りたい方の一助になっていれば幸いです。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
はじめに こんにちは!技術広報チームの itoken1013です。 2020年に入ってから、 ラク スはオンラインイベント開催に積極的に取り組んでいます! 今回はその中でも多くの方に参加をいただき大好評である、 エンジニアの勉強法ハックLT を紹介します。 rakus.connpass.com はじめに イベント概要 LTの紹介 1. ゲームで学ぶマネジメント/白柳隆司さん 2. 私にとっての学び/ariakiさん 3. esaを使ったインクリメンタル勉強法/Eiichi Horiuchiさん 4. 面倒臭がりだってキャッチアップしたい - RE:Bot から始めるものぐさ生活/みのるさん 5. 三日坊主でも勉強がしたい/脱脂綿さん 6. 自己学習を支える Inoreader + Notion/Logyさん 7. やはり俺のLT登壇はまちがっている。/moomooyaさん おわりに & 次回予告 イベント概要 先日9/9(水)で2回目の開催となりました『エンジニアの勉強法ハックLT』は名前の通り、エンジニアの勉強法や情報収集のノウハウを気軽に5分間のLTで紹介しあうイベントです。 今回は計8名からLT枠のご参加、またエンジニア志望の学生さん、新人・ベテランエンジニア、デザイナーさんと幅広い層の方々にご来場いただきました。 Twitter もZoomのチャットも賑やかにしていただき、中の人である私も大忙しのイベントでした…! togetter.com LTの紹介 1. ゲームで学ぶマネジメント/白柳隆司さん いつも ラク スの主催イベントにご参加いただいている白柳さんより、 5種類のゲームから学べるマネジ メントス キルをご紹介いただきました。 おざなりにしがちなマネジメントを、気楽に楽しめるゲームの中で学ぶ発想に驚きました。 動画化も密かに楽しみにしています。 speakerdeck.com 2. 私にとっての学び/ariakiさん Zoomフル活用の双方向LTを展開いただいたariakiさんには、「学ぶこと」のそもそも論を語っていただきました。 原動力となる欲求に素直に従うことで、「やらされている感」で学習をしている人は悪循環から脱却できるのではと共感していました。 私もいつか日本酒の席に同席したいです。 docs.google.com 3. esa を使ったインクリメンタル勉強法/Eiichi Horiuchiさん 時間のない中で、どのように学習を継続的に実施していくかを esa を使った事例と共に語っていただきました。 学習計画をマークダウンで手軽に作成したスケジュールに落とし込み、週次で消化していくというものでしたが、同じく esa を使っている私からは驚きのクオリティでした。 そして編み出した方法論もさることながら、継続を支える知識欲への情熱とセルフマネジメント力には感服でした! esa.io 4. 面倒臭がりだってキャッチアップしたい - RE: Bot から始めるものぐさ生活/みのるさん みのるさんが取り組んだ情報収集の カイゼン をご紹介いただききました。 特徴的だったのは、自発的に探すのではなく Bot で自動化を目指した点、そして自動化を実現するまでの作業を楽しんで取り組まれた点でした。 日常生活を改善している感覚、楽しいですよね。 面倒臭がりでもキャッチアップしたい- RE:Bot から始めるものぐさ生活 - from MinoruIto3 www.slideshare.net 5. 三日坊主でも勉強がしたい/脱脂綿さん 脱脂綿さんには「やる気に頼らずに」続けるための勉強方法の実践例を3つ教えていただきました。 個人的にずっと気になっていたお風呂用の防水カバーを紹介いただき、 私も購入して湯舟読書に取り組みたいと思いました。 読書家の多い ラク スの社員でも、欲しいと思ってくれる人がいるはず…! speakerdeck.com 6. 自己学習を支える Inoreader + Notion/Logyさん 膨大な情報を収集して学習に活用するためのフローを、Logyさんから紹介いただきました。 噂に聞いていた2つのツールを組み合わせており、私もさっそくアプリをインストールしてみました。 チラッと映りましたタグがとても緻密で驚きました…! 取得漏れの少ない RSSリーダー Inoreader biz-notion.northsand.co.jp メモ、プロジェクト管理、タスク管理の All-in-One ツール Notion www.notion.so speakerdeck.com 7. やはり俺のLT登壇はまちがっている。/moomooyaさん 主催者のmoomooyaさんには今回のイベントも含めたアウトプット駆動学習とLT中毒について熱く語っていただきました。 全国の登壇者を喚起し、アウトプットに役立つ内容に溢れています。 moomooyaさんには今後も適度なボリュームのLT会をどんどんドライブしていっていただきたいです。 speakerdeck.com おわりに & 次回予告 今までになく Twitter にはコメントが多く、私たちも楽しく学ばせていただきながら運営を行うことができました。 LTにご登録いただきました方々、賑やかしていただいた皆様、ご参加ありがとうございました! 3回目の開催も近々行われるかもしれませんので、どうぞお楽しみに。 さて、10月以降も ラク スではイベント開催を加速させていきます! ITエンジニア的はじめての経験 を1分で語りたい方、お早めにご応募ください! rakus.connpass.com その他のイベントへのご参加もお待ちしています! rakus.connpass.com
アバター
こんにちは、west-cです。 業務にて要件定義を行う機会があり、その成果物である要求仕様書の書き方を学ぶために『 【改訂第2版】[入門+実践]要求を仕様化する技術・表現する技術 』という書籍を読みました。今回はその内容をご紹介します。 【改訂第2版】[入門+実践]要求を仕様化する技術・表現する技術 ~仕様が書けていますか? 作者: 清水 吉男 技術評論社 Amazon ◆ 関連ブログ 仕様書 とは 【まとめ】 おすすめの読者層 要求仕様書の目的・ゴールが曖昧な方 自身が作成した仕様書において、仕様漏れや仕様の衝突が後工程で発生したことがある or 発生しないか不安を抱えている方 依頼者から要求を引き出す方法の糸口を掴みたい方 要求仕様書とは 書籍では、要求仕様書を「 要求について、関係者がその内容について認識を特定できている文章 」と定義しています。 要求 (今回の機能で実現したいこと)は曖昧なものを含んでいるため、具体的な振る舞いや制限として 仕様 化することが要求仕様書の役割となります。 要求仕様にまつわる問題 多くの現場における要求仕様にまつわる問題として以下が挙げられています。 多くの現場では、設計の様子が見えるような具体的なレベルの仕様は、設計工程の中で掘り下げるものと考えられている。 そのような考えで書かれた要求仕様書は設計や実装のイメージにはつながらず、設計工程や実装工程で具体化するなかで仕様の漏れや衝突に気づくことになる。 これは私にとっては耳の痛い話で、はじめて仕様書作成を担当した機能において具体的な仕様に落とし込めていない仕様書を作成し、実装工程になってから実装担当者から質問が頻発する、という苦い経験がありました。 要求仕様書というと上流工程に位置することから細かい仕様は後工程で決める印象を持ちがちですが、少なくとも仕様の衝突が発生しない程度には具体化する必要があると改めて感じました。 仕様化のテクニック 要求仕様書では、依頼者から引き出した要求を仕様に落とし込む必要があります *1 。 仕様へ落とし込む方法について、書籍では以下に基づいたテクニックが紹介されています。 要求という振る舞いの中に含まれている動き(=動詞)をすべて表現してしまえば、それぞれの動詞および目的語について必要な仕様をもれなく記述できる。 例えば、以下のような要求があったとします。 印刷途中で、用紙やトナーの不足が分かった時点で担当者にメールで知らせる。 この文章において直接見えている動詞は以下の通りです。 不足が"分かる" メールで"知らせる" しかし、この要求に含まれる動詞はこれだけではなく、よく読むと以下の動詞も見えてきます。 用紙やトナーの量を"知る" メールを"組み立てる" 担当者のメールアドレスを"読み出す" これらの動詞をもとに仕様化を行う、というのが書籍で紹介されている仕様化のテクニックです。それぞれの動詞に対して仕様化すべき内容としては以下のようなものがあるでしょう。 用紙やトナーの量を知る 用紙・トナーの残量をどのように入手するか 不足が分かる 用紙・トナーの残量がどれくらいであれば不足と判断するか メールを組み立てる メッセージの構成をどうするか 担当者のメールアドレスを読み出す メールアドレスをどこから読み出すか メールで知らせる どのように通知するか 再送方法をどうするか 「よく読むと見えてくる動詞」は慣れないと洗い出しが難しそうですが、個人的には、その要求を実現するためのプロセスをイメージすれば、そのプロセス一つひとつが動詞と対応付くのではと感じました。設計や実装のイメージを持ちながら要求仕様書の作成を行う考え方は、ここに直結するのだと思います。 実践してみた 書籍にはこの他にも要求仕様書の作成に関するテクニックが紹介されており、筆者が推奨するフォーマットも提示されています。そこで、過去に自分が要件定義を行った機能について、改めて書籍の手法を用いて仕様化を行ってみました。 ちなみに書籍で紹介されている要求仕様書の作成手法は USDM という名前が付いているようです。調べると資料も見つかりますので、詳しく知りたい方は書籍と併せてそちらもどうぞ。 作成した要求仕様書 書籍で紹介されているフォーマットに厳密には従っていませんが、一つひとつの要求を仕様にブレークダウンする書き方は踏襲しています。また、要求に複数の動詞が含まれる場合は、要求をもう一段階層化して動詞を分解しています。 実践した感想 (良くも悪くも)考えるスコープを絞り込める 一つの動詞+目的語に絞り込まれた要求を仕様に落とし込んでいくため、考えるべきスコープを絞って集中的に仕様化できることを実感しました。これまでの私は思いつくままに仕様化していましたが、全体から詳細にブレークダウンしていくことでより網羅的に仕様の洗い出しができている感覚がありました。 一方、考えるスコープが絞られるということはより近視眼的になることでもあります。仕様化を進める中でも、複数の要求にまたがるような仕様が漏れそうという不安がありました。定期的に要求レベルに立ち返って全体を俯瞰するなど、鳥の目・虫の目の視点の切り替えが肝になると感じました。 要求と理由をセットで記述するのは効果あり 書籍では、「なぜこの要求を実現したいのか」という依頼者の思いを明らかにするため、要求は理由とセットで記述するよう紹介されています。 実際に理由を添えて記述することで、「そういえばこの要求はなぜ必要なのだろう?」と初心に立ち返りながら考えることができました。背景や理由を明文化することで、要求を実現するためのよりよい解に至ることもできるのではと思いました。 また、仕様の経緯を辿れるようにする意味でも、背景や理由をドキュメントに残しておく意義はあると思います。 おわりに 試しに実践してみたことで、書籍のテクニックが仕様漏れ防止の一助になると感じました。このフォーマットをいきなり既存の要求仕様書に置き換えるのはハードルが高そうなため、まずは抜け漏れ防止のクロスチェック的な立ち位置で部分的に取り入れたいです。 また当然ではありますが、いくら漏れなく仕様化できたとしても、そもそもの要求の認識が依頼者とズレていたのであれば元も子もありません。今後は要求の引き出し方についても深く学んでいきたいです。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com *1 : この記事では割愛していますが、書籍内には依頼者から要求を ヒアリ ングする方法も紹介されています。
アバター
はじめに はじめまして、新卒1年目のyykaoruです。 今回はDockerを勉強しようと思い、Dockerで PHP の実行環境を構築してみたお話です。 前提 Docker、docker-composeがインストールされていること やってみること概要 今回行うことは、 php コンテナにローカルストレージのsrcをマウントし、nginxコンテナからアクセスして確認、という流れです。 ファイル構成と ソースコード を順番に説明し、最後に実行してみたいと思います。 はじめに 前提 やってみること概要 ファイル構成 ソースコード docker-compose.yml default.conf Dockerfile php.ini index.php 実行 最後に ファイル構成 今回Dockerで PHP の実行環境を作成する上でのファイル構成は以下の通りです。 nginx Dockerコンテナ起動時、nginx内の設定ファイルに書かれている設定に上書きします。 なぜDockerファイルがないかは後に記述します。 php PHP コンテナを建てるために必要なDockerファイルと、 PHP コンテナの中に配置する PHP の環境設定ファイルとして php .iniを作成しておきます。 src 実行する php ファイルを配置しておきます。 このsrc配下のファイルがnginxで配信されます。 docker-compose.yml このファイルでnginx、 php コンテナをまとめてインストールできます。ホント便利。 │ docker-compose.yml │ ├─nginx │ default.conf │ ├─php │ Dockerfile │ php.ini │ └─src index.php ソースコード それでは今回使用するファイルの ソースコード を紹介していきます。 docker-compose.yml version: '3' services: nginx: image: nginx:stable-alpine ports: - "8080:80" volumes: - ./src:/var/www/html - ./nginx/default.conf:/etc/nginx/conf.d/default.conf php: build: ./php volumes: - ./src:/var/www/html docker-compose.ymlには、nginx, php コンテナのインストール内容が書かれています。 それぞれのコマンド名を簡単に説明していきます。 version: composeファイルのフォーマットのバージョンを明記しています。 services: コンテナのインストール内容が書かれます。 nginx: サービス「nginx」のインストールを示しています。 php : サービス「 php 」のインストール内容を示しています。 image: Dockerでは後述するDockerfileでコンテナのイメージ名を明記してインストールするのが基本ですが、imageでインストールイメージを指定することもできます。 今回はnginxコンテナはcomposeファイルでインストール、 php コンテナはDockerfileでインストールしています。 ports : ここではコンテナ側のポートとローカル側のポートをつなぐ役割をしています。 nginxの場合は「- "8080:80"」と書かれていますが、ローカル側の8080番をコンテナ側の80番とつなぐという意味を表しています。 volumes: ここではローカル側の ディレクト リ内容をコンテナにマウントする設定が書かれています。 今回はnginx配下で「- ./src:/var/www/html」と示していますが、これはローカル側の./srcをコンテナ側の/var/www/htmlにマウントするという意味を表しています。 build: 作成したDockerfileの場所を指定することで、指定したDockerfileの内容に従ってコンテナがインストールされます。 ※以前までは links: というものを書いてコンテナ間のネットワークに参加させる必要があったのですが、現在は書かなくてもデフォルトで network: というものが働く様になったようで、これにより各コンテナはそのネットワークに参加した状態になるということのようです。 default.conf server { listen 80; root /var/www/html; index index.php index.html; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; location / { try_files $uri $uri/ /index.php$is_args$args; } location ~ \.php$ { fastcgi_pass php:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } default.confにはnginxの設定内容を記述します。 よくハマってしまう点としては「 fastcgi _pass」の設定です。 デフォルトの fastcgi _passは 127.0.0.1 :9000を指定しており、これはnginxコンテナの localhost を指定してしまうために502エラーを返してしまいます。(BadGateway) このエラーを解決するためには php コンテナを指定する必要があり、「 fastcgi _pass php :9000;」と明記することでサービス名( php )で php コンテナを指定することができます。 Dockerfile FROM php:fpm COPY php.ini /usr/local/etc/php/ Dockerfileでは php のコンテナイメージのインストール、 php 設定ファイルの上書きを行っています。 FROM php :fpmと指定することでDocker公式が提供している PHPイメージ をインストールします。 COPY ローカルにある php .iniをインストールした php .iniにコピー、上書きします。 php .ini [Date] date.timezone = "Asia/Tokyo" [mbstring] mbstring.internal_encoding = "UTF-8" mbstring.language = "Japanese" php .iniには php 実行環境の設定を記述します。 必要に応じて設定を追加します。 index. php <?php echo "Hello World!"; phpinfo(); ここでは php スクリプト を書いています。 内容はお好みでどうぞ! 実行 以上のファイルが用意できたら、Docker、Docker-composeがインストールされている環境で以下のコマンドを入力します。 docker-compose build コマンド入力後、dockerコンテナのビルドが走ります。 ビルドが正常に終了したことが確認できたら、以下のコマンドでコンテナを立ち上げます。 docker-compose up コンテナが立ち上がった後に localhost:8080 にアクセスすると以下のような画面が表示されます。 これで php の実行環境は完成です! 後は、src内で php ファイルを変更したりしてみましょう! 最後に 今回はDockerを利用して PHP の実行環境を構築してみました。 今回は必要最小限の構成なので、次回記事を書く機会には PHP の フレームワーク であるLaravel開発環境をDockerを利用して構築したお話をしたいと思います。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
id:radiocat です。2020年8月27日に開催された Developers Summit 2020 KANSAIに登壇させていただきました。今回はそのレポート記事です。 セッションの様子 Developers Summit 2020 KANSAI について 「 Developers Summit 」通称「 デブサミ 」は 翔泳社 さん主催の IT技術 者向けイベントです。近年は東京で夏と冬、そして関西と福岡で年1回開催されています。今回は関西で開催されて記念すべき10周年となったイベント「 デブサミ 関西」で登壇の機会を頂きました。 event.shoeisha.jp 今回のテーマは「The Future Has Come」ということで、少し先の未来の「変化」の到来に関するチャレンジをシェアしようという思いが込められたテーマでした。私も大阪オフィスで9年間勤務して様々な変化にチャレンジしてきました。貴重な機会を頂いたので、9年間の取り組みを可能な限り盛り込んで発表させて頂きました。 関西的なノリで変化の波をノリこなすチームの取り組み speakerdeck.com チームでの変化へのチャレンジのこれまでの経験を振り返ってみて、大切な要素のひとつが大阪のチームならではの「関西的なノリ」なのではないか?と考えたことが今回のテーマのきっかけです。もちろん、関西のノリがなければ変化に適応できないという意味ではありません。「関西のノリの中に変化に適応していくヒントがあるのではないか?」、「過去の経験を振り返ってそのヒントを整理してみたい!」という思いが、 デブサミ 関西に向かううえでの私自身のチャレンジでした。 基本的に不確実な変化の波を乗りこなすのは難しい そもそも変化の波を乗りこなすのは簡単ではありません。大阪では2018年に 地震 、豪雨、台風と、1ヶ月おきに災害が発生して、出勤できないなどで仕事の予定を調整せざるを得ない経験をしました。私自身、 地震 が発生した日は当時担当していたサービスのリリース当日で、たまたま出勤できたメンバーとリモートでやり取りしながら対応した経験があります。 2018年の関西 不確実な波を乗りこなすために、世の中に溢れている アジャイル の手法やマネジメントの事例を取り入れようとしますが、それもなかなかうまくいきません。変化の波を乗りこなすつもりが、気づいたら情報の波の乗りこなそうとしてしまい、溢れる情報のより複雑な渦に飲み込まれてしまうこともあります。 情報の波は複雑性の渦を呼び込む ノリで変化の波をノリこなす 不確実な波に抗わず、困難な状況に向き合って起きたことに対処するためには、「状況の洞察」と「ポジティブな姿勢」が大切だと考えました。これこそが今回のテーマである「ノリ」です。 ノリは「状況の洞察」と「ポジティブな姿勢」 変化の波をノリこなす3つのステップ ノリで変化の波を乗りこなし続けるには、3つのステップでノリを広げていくことが大切です。この3つのステップをこれまで経験してきた事例を交えながらご紹介しました。 変化の波をチームでノリこなす3つのステップ 自分らしさのリーダーシップ まず最初のステップが、チームで働く土台として一人ひとりが自分らしさのリーダーシップを持つことです。これは近年のリーダーシップ論で「オーセンティック・リーダーシップ」と呼ばれています。 オーセンティック・リーダーシップ チームの中で互いに自分らしさを持ちつつ、他者を理解しあう取り組みの事例として、以前このブログにも投稿したリモートワーク時の取り組みを「自己開示と安心・安全な仕組みに身を預ける」という2つのポイントでご紹介しました。 tech-blog.rakus.co.jp 自己開示と安心・安全な仕組みに身を預ける取り組み チームで経験から学ぶ 2つ目のステップの「チームで経験から学ぶ」については「 アジャイル の原則」「 カイゼン 文化」「ありたい姿」の3つを軸にすることをお話しました。 経験から学ぶチームのモデル これは Scrum Fest Osaka 2020で発表させていただいた内容 を引用していますが、今回は大阪のチームで過去に経験した開発プロジェクトを一通り振り返ってお話しました。 大阪のチームで経験した開発プロジェクト 過去の開発プロジェクトの反省点を突き詰めれば アジャイル の原則に向かうことは認めつつも、予測できない様々な変化の波を乗りこなしていくためには アジャイル の手法だけにとらわれず、チームが経験から学ぶ方向に向かうことが変化を乗りこなすことにつながると考えています。 イテレーティブなプロセスを繰り返して経験から学ぶ 枠を超えてつながる 3つ目のステップの「枠を超えてつながる」では、小さく始めた社内勉強会が組織や会社全体を巻き込んだイベントへと発展した事例をご紹介しました。 勉強会イベント主催の様子 変化の波の中で自分たちも変化していくために、少しずつ枠の外に向かっていくことが変化の波を乗りこなすことにつながるという内容です。 枠を超えてつながるノリの大切さ 枠を超えることよにって、つながった人たち同士で継続的な相互アップデートを生み出すことができました。 枠を超えたつながりによって得られたこと 以上の3つのステップを外側に向けて広げることが様々な変化の波を乗りこなすことにつながると私は考えています。 外側に向けてステップを広げる 関西人はノリこなすのが得意? 突然のリモートワークへの移行の中で「大変だけど、これはこれでオモロそう」と言ったメンバーがいました。この「オモロそう」が変化の波を乗りこなす「ノリ」のヒントだと私は考えました。 大変だけど「オモロそう」 「オモロそう」は、過去にやったことが無いけど、これからやる価値がありそうでポジティブなことです。つまり、「オモロそう」には「状況の洞察」と「ポジティブな姿勢」のノリで対処するための要素が含まれています。 「オモロそう」とは そして、関西人は突然怖い人が借金を取りに来てもノリで対処する喜劇やどんな無理難題でもノリで調査・報告する探偵番組など、普段からノリでノリこなす文化の中で生きています。 某喜劇のイメージ 関西人らしく「オモロそう」を引き寄せて外側へステップを進めていくことが、変化の波をノリこなすことにつながるのではないかと私は考えています。 「オモロそう」引き寄せて変化の波をノリこなす デブサミ も変化の波を乗りこなす 今回はオンライン開催ということでしたが、セッション内容はリアルタイム配信ではなく事前撮影方式でした。テレビ撮影のようにカメラを向けられてマイクで音声を拾ってもらいながら行うセッションは初めてで、とても貴重な経験をさせて頂きました。 デブサミ 自体も新たな変化に向けてチャレンジしていることを改めて実感できる体験でした。 セッション撮影の様子 枠を超えてつながりたい 今回の発表をきっかけに、これまで以上にたくさんの方々と枠を超えてつながることができればと思っています。今月も様々なイベントが計画されていますので、興味を持たれましたらぜひご参加ください。 rakus.connpass.com
アバター
はじめに こんにちは。kkystです。 開発を担当しているプロダクトではpg_bigmを利用して 全文検索 機能を提供しています。 今回、その 全文検索 を行っているテーブルにINSERTを行う一部の処理で、 応答時間 が増えていることを検知しました。 そこでその原因を調査していったところ、GINインデックスのGIN高速更新手法にたどり着き、待機リストの有無による 応答時間 の検証を行いました。 その結果として、GIN高速更新手法の有効性を確認することができたので、検証の記録を残しておきたいと思います。 はじめに 概要 GIN高速更新手法とは 検証環境 検証内容 検証結果 おわりに 概要 GIN高速更新手法とは GINインデックスは 全文検索 向けのインデックスで文書中の単語の位置を保持しているため、使用することで特定の単語の検索を効率的に行えるようになります。 基本的には英文(英単語)を意識したものとなっていますが、pg_bigmなどのモジュールを導入することで日本語でも利用できます。 GINインデックスの高速更新手法については、 PostgreSQL の リファレンス には下記のように記載されています。 1つのヒープ行の挿入または更新によりインデックスへの挿入が多く発生するという、 転置インデックス の本質的な性質のためGINインデックスの更新は低速になりがちです。 (各キー用のヒープ行はインデックス付けされた項目から取り出されます。) PostgreSQL 8.4からGINは、新しいタプルを一時的なソートされていない、待機中の項目リストに挿入することにより、この作業の大部分を遅延させることができるようになりました。 (中略) この手法の大きな欠点は、検索時に通常のインデックス検索に加え待機中の項目リストのスキャンを行わなければならない点です。 このため、待機中の項目リストが大きくなると検索が顕著に遅くなります。 他の欠点は、ほとんどの更新は高速ですが、待機中の項目リストが「大きくなりすぎる」きっかけとなった更新は即時の整理処理を招くことになり、他の更新に比べ大きく低速になります。 自動バキュームを適切に使用することで、これらの両方の問題を最小化することができます。 「整理処理を行う更新処理が大きく低速となる」という部分が実際にどれくらい低速となるの?という疑問が浮んだので、この部分に着目して実際の処理時間を計測することにしました。 検証環境 ローカル環境:Windows10 Intel (R) Core(TM) i7-8550 CPU @1.8GHz RAM:16GB SSD 512GB Docker for Windows version 2.3.0.3 PostgreSQLの公式Dockerイメージ(v12.4) テーブル構造 create table sample_table(id integer, note text); create index sample_table_ix1 on sample_table using gin (note gin_bigm_ops); ※待機リストを利用しない場合は、WITH (FASTUPDATE = OFF)を付与してインデックス再作成を実施。 検証内容 以下の状態でASCIIコードのランダム1000バイトの文字列データを1件挿入し、処理時間を計測する。 初期データなし 待機リストあり(空) 待機リストあり(最大≒gin_pending_list_limit(4MB):1000バイト * 170件) 待機リストを利用しない 初期データ100万件(各行1000バイト) 待機リストあり(空) 待機リストあり(最大≒gin_pending_list_limit(4MB):1000バイト * 170件) 待機リストを利用しない 検証結果 待機リスト(空) 待機リスト(最大) 待機リストなし 初期データなし 89.034ms 97.462ms 83.937ms 初期データあり 70.088ms 863.200ms 827.526ms この結果より、インデックスへの整理処理そのものに時間がかかり、整理する対象のデータ件数による差はほとんどありませんでした。 また、今回用意したパターンであれば、その整理処理自体も1秒を切っているので、他の処理との兼ね合いもありますがオンラインで利用しても許容できる速度であることがわかりました。 このことから、大半の更新処理が高速になるGIN高速更新手法は非常に有効であり、特別な要件がない限りは有効にしておくべきものだということがわかりました。 検証結果の詳細を展開 1.1. 初期データなし-待機リストあり(空) dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 2 | 1 | 0 | 0 | 2 dev=# INSERT INTO sample_table SELECT 1000000, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 89.034 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 2 | 4 | 5680 | 3 | 1 | 2 | 1 | 0 | 0 | 2 1.2. 初期データなし-待機リストあり(最大) dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 347 | 4 | 5660 | 177 | 59 | 2 | 1 | 0 | 0 | 2 dev=# INSERT INTO sample_table SELECT 1000000, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 97.462 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 2 | 4 | 5660 | 3 | 1 | 2 | 1 | 0 | 0 | 2 1.3. 初期データなし-待機リストを利用しない dev=# create index sample_table_ix1 on sample_table using gin (note gin_bigm_ops) WITH (FASTUPDATE = OFF); CREATE INDEX 時間: 43.528 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 2 | 1 | 0 | 0 | 2 dev=# INSERT INTO sample_table SELECT 1000000, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 83.937 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 2 | 1 | 0 | 0 | 2 2.1. 初期データあり-待機リストあり(空) dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 212649 | 2764 | 209881 | 7224 | 2 dev=# INSERT INTO sample_table SELECT 1000001, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 70.088 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 212646 | 212648 | 5740 | 3 | 1 | 212649 | 2764 | 209881 | 7224 | 2 2.2. 初期データあり-待機リストあり(最大) dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 212646 | 213158 | 5940 | 513 | 171 | 212649 | 2764 | 209881 | 7224 | 2 dev=# INSERT INTO sample_table SELECT 1000001, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 863.200 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 212649 | 2764 | 209881 | 7224 | 2 2.3. 初期データあり-を利用しない dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 212649 | 2764 | 209881 | 7224 | 2 (1 行) 時間: 63.233 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 212651 | 2764 | 209886 | 772968 | 2 dev=# INSERT INTO sample_table SELECT 1000001, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 827.526 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 212651 | 2764 | 209886 | 772968 | 2 おわりに 今回は、GIN高速更新手法の待機リストの整理処理による低速化に着目して検証し、GIN高速更新手法の有効性を実感することができました。 時間の都合もあり現時点における検証はここまでですが、gin_pending_list_limitなどの適切な設定値やデータ量や偏りとの関連などもありそうなので、今後もこのGIN高速更新手法について別な切り口で検証していきたいと思います。
アバター