TECH PLAY

株式会社ラクス

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

935

楽楽勤怠開発1課のy_konnoです。 先日開催された JJUG CCC 2022 Fall に登壇しましたので、今回はそのレポートになります。 プロポーザル 登壇資料 登壇を振り返って Q:競合ライブラリは検討したか? Q:楽楽勤怠の事例においてミドルウェアではなくあえてJava側でやった理由は? Q:タイムアウトなどの値はどう設定しているか? まとめ プロポーザル マイクロサービスにおいては一部のサービスが障害を起こして応答が 不能 になったり、ネットワーク的にリク エス トが到達 不能 になることがあります。その際に無対策だと連鎖的に各サービスがダウンし、 アーキテクチャ ー全体が機能不全に陥ってしまいます。 対策としてはサーキットブレーカーの適用が挙げられます。サーキットブレーカーは局所的な障害が アーキテクチャ ー全体に波及しないように障害を分離する機構です。しかし、自力で実装するのはコストが高いのが難点です。 Resilience4jは Java でフォールトトレランスを実現するためのライブラリです。数多くの機能を備えますが、その中の一つにサーキットブレーカーがあります。本セッションでは以下内容について解説したいと思います。 Resilience4jの概要 Resilience4jを利用したサーキットブレーカーの実現方法の解説 Resilience4jを利用したリトライの実現方法の解説 弊社サービスである「楽楽勤怠」での適用例 fortee.jp 登壇資料 speakerdeck.com 登壇を振り返って 今回はセッション本体が動画配信で、質疑応答がリアルタイムという形式でした。動画は15分という制限時間があったので、なるべくシンプルかつ利点がストレートに伝わるように、Resilience4jの適用方法を3ステップに分けて解説してみました。 Resilience4j自体あまりメジャーではないので、テーマ被りの心配はしていませんでしたが、やっぱり被らずに済みましたw 表題としてはサーキットブレーカーをメインに据えていますが、個人的にResilience4jを使っていて最もありがたいポイントはリトライをものすごく簡単に実装できるところですね。質疑応答前に運営の方とも話をしたんですが、リトライのコードを自前で書いてつらいという話は各所で出ているのでこういったライブラリはありがたいとおっしゃってましたね。 以下は頂いた質問に対する回答をいくつか Q:競合ライブラリは検討したか? A:正直なところ、Resilience4jが真っ先に出る上にメンテされてるので、一択なところがありました。かつては Netflix がサーキットブレーカー用のライブラリを出していたんですが、それが ディスコン になっているのも影響はでかいと思います Q:楽楽勤怠の事例において ミドルウェア ではなくあえて Java 側でやった理由は? A:リトライを併せて使いたかったことと、インフラ構成を増やしたくなかったのが理由になりますね。あとは打刻アプリケーションは様々な顧客向けにリク エス トを飛ばす関係上、振り分けのロジックを管理するのが Java でやるほうが都合が良かったというのもあります Q: タイムアウト などの値はどう設定しているか? A:正直、手探り感があるところです。リトライでウェイトする秒数などはサービスで応答目標としている秒数があるのでそちらを下回るように計算しています。他方、サーキットブレーカーにおけるリングバッファのサイズなどは暫定で決めています。 まとめ マイナーなネタなので質問が全く来ないことを危惧しましたが、なんとか関心を集めることはできたようで安心しました。 リトライ目的からスタートしてResilience4jを導入する人が増えてくれると嬉しいですね。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
はじめに 本記事では、Spockの基礎について解説します。 「そもそもSpockって何?」「コードをテストするってどうやるの?」という方がSpockでテストを書く助けになれば幸いです。 目次 はじめに 目次 Spockとは テストコードを書いてみよう expectブロック whereブロック when, thenブロック given(setup)ブロック、cleanupブロック 予約メソッド(Fixture methods) Mockでテストを分割しよう おわりに 参考資料 Spockとは Spockとは、 Java コードのテストができる フレームワーク で、Groovyという言語で記述します。 Groovyを知らないという方でも、書き方が Java に近いのでほとんど同じ感覚で書くことができます。 同じく Java のコードをテストする フレームワーク に JUnit があり、よく比較されます。 どちらを使うかは好みですが、個人的にはSpockの方が直感的にわかりやすいと思っています。 JUnit とSpockを比較した記事があるので JUnit についても知りたいという方はこちらの記事もおすすめです。 tech-blog.rakus.co.jp テストコードを書いてみよう expectブロック では、さっそくSpockを用いてテストコードを書いていきましょう。 テストするコードはこちらです。 説明するまでもないですが、数字の足し算結果を返すだけのメソッドです。 テストするコード public static int add( int x, int y) { return x + y; } テスト class AddSpec extends Specification { def "add関数のテスト" () { expect : add( 2 , 3 ) == 5 } } テストするコードがシンプルなので、テストもシンプルですね。 では、上から順にみていきましょう。 Spockを使うには、Specificationを継承します。 私も中身は知らないので おまじないだと思ってとりあえず書いておいてください。また、慣例でテストクラス名は メソッド名+Spec にすることが多いです。 次に、メソッドの宣言です。 Java とは少し違い、 def 名前() {} のように書きます。 名前の部分は、例えば def addTest() {} のように書くことも、例のように""で囲んで日本語を使うこともできます。 では、メソッドの中を見てみましょう。 expect はGroovyではなく、Spock特有の構文です。 expect の他に given ( setup )、 when 、 then 、 where 、 clearnup ブロックがあり、それぞれ役割があります。(後述) expect はテストを書くブロックで、 add(2, 3) の結果が 5 だと予測(expect)することを表します。 whereブロック 次に、whereブロックを使ってみましょう。 where は、同じメソッドを色々なパターンで試すときに使用するブロックです。1つのメソッドを1パターンだけテストすることはほぼないので頻繫に使用することになると思います。 では、例を見てみましょう。テストするのは先程と同じ、足し算するメソッドです。 テスト class AddSpec extends Specification { def "add関数のテスト" () { expect : add(x, y) == result where : x | y || result 2 | 3 || 5 Integer .MAX_VALUE | 1 || 2147483648 Integer .MIN_VALUE | - 1 || - 2147483649 } } 境界値のテストを追加してみました。 このように、1行目に変数、2行目以降にパターンのように書きます。 変数( x , y , result )はメソッド内で使うことができ、例では add メソッドの引数と結果を変数にしています。 | は見た通り変数の区切りです。 | の数は1個でも2個でも同じ意味ですが、入力と出力などで区切ると見やすさがアップします。 when, thenブロック 実行結果1 add(x, y) == result | | | | | | | 1 | 2147483648 | | false | 2147483647 -2147483648 実行結果2 add(x, y) == result | | | | | | | -1 | -2147483649 | | false | -2147483648 2147483647 テストを実行したら失敗しました。 実行結果を見る限りオーバーフローが起きてしまったみたいです。 オーバーフローが起きたときはちゃんと例外を投げるように修正し、例外になるかどうかのテストも追加しましょう。 テストするコード public static int add( int x, int y) { return Math.addExact(x, y); } テスト class AddSpec extends Specification { def "add関数のテスト" () { expect : add( 2 , 3 ) == 5 } def "add関数のテスト_例外" () { when : add( Integer .MAX_VALUE, 1 ) then : thrown( ArithmeticException ) } } 例外になるかをテストするために when 、 then ブロックを使います。 when と then は必ずセットで使うブロックで、 when に実行する内容、 then に結果を書きます。例は add(Integer.MAX_VALUE, 1) を実行したときに ArithmeticException が投げられるという意味です。 given(setup)ブロック、cleanupブロック 残りのブロックについても簡単に説明しておきます。 given ( setup )はテストをするための準備、 cleanup はテストの後処理をするブロックです。 テスト class AddSpec extends Specification { def "readFileのテスト" () { given : File file = new File( "./.txt" ) BufferedReader br = new BufferedReader( new FileReader(file)) expect : def result = readFile(br) cleanup : br.close() } } ファイルを読むメソッドのテストを書いてみました。 given で引数に必要な BufferedReader を準備して cleanup で解放しています。このように、複雑なクラスを引数に取るときやメモリ解放が必要な時にこれらのブロックを使います。 ちなみに、他のブロックとは違い given ( setup )ブロックは省略することができます。 逆にそれ以外のブロックは付け忘れるとビルドエラーになるので注意しましょう。 予約メソッド(Fixture methods) Spockでは、4つの特殊なメソッドが用意されており、特定のタイミングで勝手に実行されます。 def setupSpec() {} ・・・テストクラスの最初に実行する def setup() {} ・・・各テストメソッドの最初に実行する def cleanup() {} ・・・各テストメソッドの最後に実行する def cleanupSpec() {} ・・・テストクラスの最後に実行する 以上をまとめると、下図のような実行順になります。 予約メソッドは使わなくても書けますが、使いこなせればより簡潔にテストメソッドを作成することができます。 Mockでテストを分割しよう ここまでは、1つだけで完結するメソッドのテストをしてきました。ですが、実際はメソッドの中でメソッドを呼ぶことが多く、一つのメソッドで階層深くまでテストするのは現実的ではありません。 そこで呼び出したメソッドだけテストするために、メソッド内のメソッドを「モック化」します。 モック( モックアップ )は見た目だけのハリボテのことで、主にシステムを作る前にお客様に「こんな感じのを作りますよ~」という見本で使われます。 Spockにおいてモックは、呼ばれたら実際には実行せず指定された結果を返すことを意味します。 実際にコードを見たほうがわかると思うので、さっそく例に行きましょう。 テストするコード private Calculator calc; public static List<Integer> FourArithmetic( int x, int y) { List<Integer> results = new ArrayList<>(); results.add(calc.add(x, y)); results.add(calc.sub(x, y)); results.add(calc.mul(x, y)); results.add(calc.div(x, y)); return results; } テスト def "FourArithmeticのテスト" () { def calcMock = Mock(Calculator) calcMock.add( 2 , 3 ) >> 5 calcMock.sub( 2 , 3 ) >> - 1 calcMock.mul( 2 , 3 ) >> 6 calcMock. div ( 2 , 3 ) >> 0 when : def results = FourArithmetic( 2 , 3 ) then : results == List .of( 5 , - 1 , 6 , 0 ) } 例は、 FourArithmetic メソッドのテストです。メソッド内で Calculator クラスの add 、 sub 、 mul 、 div を呼び出していますね。 次にテストを見ていきます。 まず Mock(Calculator) で Calculator クラスをモック化します。 次に、モック化すると何を実行しても null になってしまうので、下の行でメソッドを書き換えます。 例では4つのメソッドを、引数が (2, 3) で呼ばれた時だけ右側の値を返すように書き換えました。( (_, _) のようにすることで、どんな引数でも指定の戻り値を戻すよう書き換えることもできます) テストが成功すれば、 Calculator の中身を無視して、 FourArithmetic のリスト操作や引数指定だけがテストできたことになります。 また、今回は紹介しませんが Spy や Stub も、 Mock と同じようにメソッドをモック化することができます。 前述した Mock 、 Spy 、 Stub などはSpockの機能ですが、Mockitoという全く別の、モックのための フレームワーク があります。 Spockでのモック化と同じことができるだけでなく、staticメソッドをモック化したり(Mockito.mockStatic)モック化したメソッドの引数をチェックしたり(Captor)できる便利な機能があるので、興味のある方はそちらも調べてみてください。 おわりに 本記事では、Spockの使い方について解説しました。 本記事は公式ドキュメントを参考に作成しています。より正確な内容を学習したい方は 公式ドキュメント を読んでみてください。 では、最後までお読みいただきありがとうございました。 参考資料 tech-blog.rakus.co.jp qiita.com spockframework.org エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
こんにちは、あるいはこんばんは。すぱ..すぱらしいサーバサイドのエンジニアの( @taclose )です☆ インフラエンジニアもサーバサイドエンジニアも ssh コマンドを叩かない日はないんじゃないでしょうかっ gitコマンドなんかでも ssh の設定が有効だったりもしますのでエンジニアにとって重要な アーティファクト と言えるでしょう。 今日は私も毎日お世話になってる ssh コマンドの話を 実践で使えるノウハウやオススメの設定をまとめて公開 していきます! ※記事の内容はインフラエンジニア1年目でも読みやすく、かつ実践を意識して記載しています。深掘りより読破重視! 目次 目次 sshとは? sshのパスワード入力を省略しよう!(公開鍵認証の話) sshのセキュリティ対策をしよう!(sshdの設定の話) sshのポート番号を変更しよう! sshdでport 20022を追加 SELinuxの設定を確認しよう! ファイアウォールを確認しよう! ssh port 22を閉じよう!!! セキュリティ設定に関するまとめ sshのコマンド入力を楽にしよう!(ssh configの話) Point1: Host __* で共通設定を書こう Point2 : gitサーバとかのHost名はHostnameと同じにしよう そしてこうなる!!! 最後に 参考文献 ssh とは? えー!そこから!?って思う方もいるでしょうが、概要だと思って聞いて下さいねっ 「 ssh とは?」なんの略かというと、 " Secure shell " を略したものです。 Wiki とかでも「暗号や認証の技術を利用して、安全にリモートコンピュータと通信するための プロトコル 」と説明されていて、安全にサーバ間通信する最も一般的な方法と言えます。 「いやいやtacloseさん、 FTP 通信もかなり主流でしょうに」という方もいらっしゃるかもしれませんが、 FTP はファイル転送 プロトコル として有名ではありますが通信が暗号化されておらず危険なため現在はファイル転送も ssh を利用したSFTPやSCPを使う事が多いですね。 ここではそんな ssh をより安全に便利に使ってもらうためのノウハウを詰め込んで説明しています。 とはいえ port変更とか必須じゃないものもあるので、やりたい所だけチョイスしてやってもらえれば良い かなと思います。 という事で、 ssh の重要性がわかった所でLet's ssh !! ssh のパスワード入力を省略しよう!(公開鍵認証の話) 公開鍵認証を使えば、安全・パスワード不要が実現出来ます。 私は以下のコマンドを Windows のWSL上で実行していますが、やりたい事は、 どこかで鍵を作成 秘密鍵 (id_ rsa )をローカルPCに配置 公開鍵(id_ rsa .pub)をサーバ側に配置 サーバ側の ssh の設定で公開鍵認証を許可 すればおしまいです。 以下は実際のコマンドです。 # username : 各自用意したサーバのuser名に置き換えてね! # hostname : 各自用意したサーバのdomain名やIPアドレスに置き換えてね! // 鍵を作成 $ ssh-keygen -t rsa -b 4096 (何も打たずにEnter2回。パスワード設定したらめんどいもんね!) // 鍵がある事を確認 $ ls -al ~/.ssh/ -rw------- 1 root root 1675 3 月 3 2022 id_rsa -rwxr-xr-x 1 root root 404 3 月 3 2022 id_rsa.pub // 公開鍵をサーバにアップロード $ ssh-copy-id username@hostname // 公開鍵がサーバに上がったかを確認してみる $ ssh username@hostname " ls -al ~/.ssh/authorized_keys " // 試しにアクセスできるか試してみる(失敗しても焦らないでネ!) $ ssh username@hostname これでパスワード入力の地獄からは解放されました!おめでとう! 少し補足説明すると、 ~/.ssh/authorized_keys というのはサーバ側の ssh の設定で標準的な公開鍵の置き場所 ssh-copy-id というコマンドはローカルにある ~/.ssh/id_rsa.pub をサーバ上の ~/.ssh/authorized_keys にアクセス権含めて良しなにアップロードしてくれるコマンド ssh username@hostname って 秘密鍵 指定してないけど!? ~/.ssh/id_rsa を使うのは標準なため省略が可能 という感じになっています。 もしかしたらログイン出来なかったなんて人いるかもしれませんが、次のセキュリティ対策の設定らへんが原因かもしれませんね。 次にいきましょう! もっとここ詳しくって方はこちらも読んでみてね! SSHは公開鍵・秘密鍵を使って楽にする - RAKUS Developers Blog | ラクス エンジニアブログ ssh のセキュリティ対策をしよう!( sshd の設定の話) 先に言います。 外部に晒されてないサーバなら正直port変更はやらなくてもまぁいいかな って思います。 飛ばしてもらってもOKです!やっておく方が良いですが無理に頑張らなくても良いかなと。 でも外部からのアクセスが許可されてるサーバであれば、いつか攻撃を受けますので設定しておく事をおすすめします。 rootアカウントでのログイン拒否 パスワードでのログイン拒否 port 22でのアクセス拒否 ここらへんは最低限やっておくべきかとは思います。 さて、本題に戻ろうっ... 既に誰かが用意してくれたサーバの場合、 ssh の設定が色々と違ってるかもしれませんので確認しながら進めましょう! 尚、 ポート開放や SELinux についてはCentOS7のやり方で書いてますので、 Ubuntu とかの人はfirewalldコマンドに置き換えて実施してくださいね ! ssh のポート番号を変更しよう! ファイアウォール とか SELinux とか確認すべき事はありますが、手順としては sshd で20022 portを許可! ファイアウォール も SELinux も 20022 portを許可! ssh で20022 port使えるか確認! ssh でport 22を閉じる!(port 20022で接続してないと接続切れちゃうよ!!) の順番なので、焦らず焦らずとりあえず許可していきましょう! sshd でport 20022を追加 sshd とは?という方のために補足すると、" sshd = ssh daemon = ssh 常駐型プログラムを指すサーバサイド側のソフトウェア" を指しています。 普段 ssh でport X番に通信飛ばして接続して何か出来るのは、この sshd が受け答えしてくれてるんですね!感謝! 先にも記載しましたが、 ssh のportが22のままだと攻撃の機会を与えてしまいますので、変更すべきです。 では以下手順に沿ってLet’s port 解放☆ // まずは設定を確認! $ grep -E " ^Port |^PermitRootLogin |^PasswordAuthentication " /etc/ssh/sshd_config // PermitRootLogin yes : rootでのログインを許可するのは危険です。可能なら no にしよう! PermitRootLogin yes // PasswordAuthentication yes : パスワード認証を許可するのは危険です。公開鍵認証の設定はしましたよね?だったらこれも no にしよう! PasswordAuthentication yes // Portの列はない: #Port 22ってコメントアウトされてる列はあるはず。Port 20022を後で追加しましょう! // 設定変更する $ vim /etc/ssh/sshd_config // 設定変更後こうなるようにしてね! $ grep -E " ^Port |^PermitRootLogin |^PasswordAuthentication " /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no Port 22 Port 20022 // 変更を反映させるため再起動っ $ systemctl restart sshd これで sshd としては20022を許可しましたが、みんなのサーバには SELinux やfirewalldが邪魔してるはずです。 また、社内ネットワークによってはローカルPC→ ファイアウォール →開発サーバとなっていて、インフラチームにport 20022の許可を求める必要もあるかもしれません。 なので、ケースバイケースによっては無理せずPort 22のままにしましょう。 SELinux の設定を確認しよう! 先輩社員に言われたかもしれない。 「 SELinux ?ヤツは四天王の中でも最弱...ヤツは無効でいいよ」 この話をすると必ずアンチが生まれ論争となり、すぱ...すぱらしいエンジニアが場を収める必要があるため、ここでは議論しない。 なので、この章では「無効な事を確認して終わり」もしくは「有効ならアクセス権を追加して終わり」という事に留める。 私も無効とする事が多いし、世の中無効が多数派かと思います。おっと、これ以上は危険か。 以下、手順です。 // SELinuxの確認。Disabled:無効, Enforcing: 有効, Permissive : 無効だが警告は出る $ getenforce Disabled # - - - - - # // 案1:SELinux無効にする方法 // SELinux無効でいいじゃん派なら以下のコマンドで無効化させておしまいっ $ setenforce 0 # - - - - - # // 案2:SELinux有効のままで、ちゃんとsshのためにport 20022 を開ける方法 // まずはsshのportが 22 で開いている事を確認。 $ semanage port --list | grep ssh ssh_port_t tcp 22 // port: 20022 を開ける $ semanage port --add --type ssh_port_t --proto tcp 20022 // sshのportが 20022 も開いた事を確認。 $ semanage port --list | grep ssh ssh_port_t tcp 20022 , 22 今は深く理解出来てないって人もとりあえずOK!!次いきましょう! ファイアウォール を確認しよう! ファイアウォール と言っても CentOS 上で動いてるfirewalldとは別にインフラチームが管理してる機器もあります。そっちはインフラチームに任せましょう。 ここではfirewalldについて説明します。 // まずはファイアウォールがinactiveかactiveか確認。activeじゃないなら読み飛ばしてOK $ systemctl status firewalld Active: inactive ( dead ) // ←firewalldが動いていない例です # - - - - - # // 案1:firewalldを無効化にして終わらせる方法 // 常駐化 OFF (サーバ再起動しても自動起動しなくなります) $ systemctl disable firewalld.service // ファイアウォールの停止コマンド $ systemctl stop firewalld.service # - - - - - # // 案2:firewalldでちゃんとport 20022 をssh用に開ける方法 // firewalldにport 20022 のssh設定を追加する $ cp /usr/lib/firewalld/services/ssh.xml /etc/firewalld/services/ssh-20022.xml # firewallの設定をコピー $ vim /etc/firewalld/services/ssh-20022.xml # 書き換える port="22"をport="20022"にする $ diff /usr/lib/firewalld/services/ssh.xml /etc/firewalld/services/ssh-20022.xml // 差分はこうなるはず 5c5 < < port protocol = " tcp " port = " 22 " / > --- > < port protocol = " tcp " port = " 20022 " / > // 追加したport設定を読み込ませる $ firewall-cmd --add-service=ssh-20022 --permanent // 反映するために再起動 $ firewall-cmd --reload ssh port 22を閉じよう!!! 当然ながらここまでの説明で飛ばした所はここも関係ない所飛ばしつつ設定してくださいね! 以下、手順です。 // まずはローカルから 20022 で接続できるかを確認しましょう。 $ ssh username@hostname -p 20022 // sshdからPort 22 の設定を消す $ vim /etc/ssh/sshd_config Port 22 // ←この記述を消しましょう // sshd再起動! $ systemctl restart sshd.service // SELinuxからPort 22 のアクセスを拒否させる $ semanage port --delete --type ssh_port_t --proto tcp 22 // SELinuxでPort 22 が禁止されて、 20022 だけになった事を確認 $ semanage port --list | grep ssh ssh_port_t tcp 20022 // firewalldからPort 22 のアクセスを拒否させる $ firewall-cmd --permanent --remove-service=ssh // firewalldの再起動 $ systemctl restart firewalld.service // firewalldからsshのサービス設定が消えた事を確認 $ firewall-cmd --list-all | grep ssh services: dhcpv6-client ssh-20022 // ← sshというのがなくなってる。 // 最後に念のためPort 22 でログイン試みてみてもいいかもね!絶対無理だけどね!! $ ssh username@hostname -p 20022 セキュリティ設定に関するまとめ うん、すごく面倒だったけど頑張りましたね!えらい!エンジニア歴浅い人は一度 Ubuntu のサーバとか1から設定してみると良いですよ! まずは firewall で ssh 以外のポート全部閉じて、必要な所だけ開いていって、あ~なんか無駄なものがないって気持ちいいってなればいいなと思います! ssh のコマンド入力を楽にしよう!( ssh configの話) これ系はconfigファイルの書き方次第でプログラムでいう継承関係とかも実現出来るので、こだわって色々アレンジしてみてほしいです。 サンプルで私のものを一部公開しますね! ※IPとかUser名はブログ用に修正してます。サンプルです! $ cat .ssh/config Host __* ServerAliveInterval 60 User taclose Host __taclose.develop Hostname 192 . 168 . 100 . 32 IdentityFile ~/.ssh/id_rsa Port 20022 Host __taclose.staging Hostname 192 . 168 . 100 . 32 IdentityFile ~/.ssh/id_rsa Port 20022 Host git. local .server Hostname git. local .server IdentityFile ~/.ssh/id_rsa.git Host __log_server Hostname office.log-server.jp User maeda この設定ファイルの置き場所は .ssh/config ここ固定です。各自ないなら作りましょう! 指定できるパラメータ次第では ssh のProxy設定であったり、Portだったり色々指定できます。そこらへんは詳しくまとめてくれてある記事があるので、こちらを参照してください。 ~/.ssh/configを使ってSSH接続を楽にする - RAKUS Developers Blog | ラクス エンジニアブログ ここでは設定のノウハウだけ以下に書いておきますね。 Point1: Host __* で共通設定を書こう $ yum install -y bash-completion サラッと大事な事上に書いてますが bash -completionを入れておけば入力補完が効いて便利です! そして、このHost指定に __と入れる事で ssh コマンドを打つ際に余計な入力候補が出なくなります。 やっておかないと、/etc/hosts的な場所に定義してあるものとか余計なものまで候補に出て探しにくいのです。 そして、 アスタリスク * を使う事で 正規表現 指定が可能なので、「__から始まるHost指定全部にこの設定ね!」と出来ます。 なので、「共通設定は接続切れないようにしたいのと、だいたいユーザ名はtacloseだな」とかを定義しておけばOK!! Point2 : gitサーバとかのHost名はHostnameと同じにしよう これは git clone とかする際にも公開鍵認証の設定が有効なためです。そしてgitのサーバには ssh コマンドで入らないので__は不要かなと... そしてこうなる!!! ssh コマンドの入力が非常に楽!パスワード不要! ん~ ssh の入力補完も無駄な候補がないし、指定も楽ですね! ssh のパスワード不要も素敵! 最後に セキュリティ絡みが長くなりましたが、重要な事なので皆さんも一度設定してみてサーバの知識をより深めてもらえればいいんじゃないかなって思います。 AlmaLinuxへの移行とか検討されてる方にしても、 CentOS と同様に RHEL のクローンOSです。 つまり、各種設定は CentOS の知識がそのまま使えます。 AlmaLinuxのサーバ構築する際には是非、 firewall で全部のポート閉じて必要なポートだけ開けてあげてください。 きっと秘密基地にのぞき穴だけ開いてるそんな居心地の良いサーバになります。 やってみると楽しいですよ! 参考文献 CentOS 7 で sshd のポート番号を変更する方法 | ~/.ssh/configを使ってSSH接続を楽にする - RAKUS Developers Blog | ラクス エンジニアブログ SSHは公開鍵・秘密鍵を使って楽にする - RAKUS Developers Blog | ラクス エンジニアブログ エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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年目の koki _matsuraです。 本日は前回記事の「【Recoil】Reactの状態管理ライブラリ基礎学習 ~第三部~」で作成したToDoアプリの Recoil部分をリファクタする とともに パフォーマンスを上げるためにどうするか についてご紹介させていただきます。 もし、前回の記事を読んでいない方は下記のリンクから読んでいただけると嬉しいです。 tech-blog.rakus.co.jp アジェンダ は以下の通りになっています。 Recoilを用いたToDoアプリの問題点 複数人でRecoilを使うときに意図しない状態変更が行われる可能性 無駄な部分まで走る再レンダリング 解決方法 「複数人でRecoilを使うときに意図しない状態変更が行われる可能性」の解決方法 「無駄な部分まで走る再レンダリング」の解決方法 コード修正 TodoStateの修正 Atomの正規化 変更系カスタムフックの作成 取得系カスタムフックの作成 ContainerとPresenterの修正 入力部と出力部の分割 ToDoを表示するコンポーネントの作成 Recoilと接続 パフォーマンスのチェック 終わりに Recoilを用いたToDoアプリの問題点 前回作成したToDoアプリはRecoilの基礎を抑えるために重要な部分を切り落としたものになっていました。 仕様通りではありましたが、前回のToDoアプリには大きく2つの問題点があると思いました。  ・ 複数人でRecoilを使うときに意図しない状態変更が行われる可能性  ・ 無駄な部分まで走る再 レンダリング それぞれについて解説します。 複数人でRecoilを使うときに意図しない状態変更が行われる可能性 Recoilの状態管理の仕組みは以下のようになっています。 RecoilはReduxのように状態の加工から管理までをするのではなく、状態の管理のみを担当し、Hooks API を使って コンポーネント 側から値を更新できるのが特徴だと思います。 とてもシンプルな仕組みで使いやすく強力な状態管理ライブラリですが、このシンプルさが規模が大きくなってきたときに弱みになる可能性があります。 今回のようなToDoアプリの場合は規模感としてはかなり小さいですし、使っているのも自分一人だけでした。これが大きなアプリになって、一人だけでなく、複数人で開発していく場合、Recoilは下記のような状態になると考えられます。 おそらく、実際に複数人で使うとなると図よりももっと複雑になると思います。 色々な コンポーネント から利用され、状態を更新されるため、Aさんが担当した コンポーネント で更新した状態はCさんが担当した コンポーネント では意図した状態でない可能性があります。 これは後々、大きな問題点になると思われます。 無駄な部分まで走る再 レンダリング 前回、作成したToDoアプリは一つのContainer、Presenterで構成されています。 なので、フォームに文字を入力したり、追加、削除、完了・未完了の切り替えボタンを押すたびに全てに再 レンダリング がかかってしまいます。 色のついた枠は再 レンダリング されている コンポーネント です。何か操作をするたびに全てが レンダリング されていることがわかると思います。 今の時点では表示するToDoリストの数が少ないためそれほど重たい処理に感じることはありませんが、リストが数百件単位になると、かなり重くなるだろうと思われます。 変化していない部分を再 レンダリング するのは無駄ですし、パフォーマンス的に問題があると言えます。 解決方法 「複数人でRecoilを使うときに意図しない状態変更が行われる可能性」の解決方法 この問題は他の コンポーネント が Atom (状態)を好きに変更できることにより発生するものです。なので解決策としては単純ですが、 Atom に対して行える操作を指定すれば良いです。 具体的にはカスタムフックを使用して、 Atom を直接変更できないようにします。 変更も取得もカスタムフック越しに行うようにします。これで意図しない変更が行われる可能性は大きく下がると思います。 また、この問題点とはあまり関係ないのですが、一つの Atom に持たせる状態の大きさにも気をつけます。深いネストを持った状態を一つの Atom に持たせるのは変更がとても複雑になってしまいます。できる限り正規化を意識した方が良いです。 現状のToDoアプリは一つの Atom にToDoのオブジェクトを配列で持たせているので、これを Atom 一つに対してToDo一つのように変更します。そうすれば、操作用カスタムフック内のコードがシンプルになります。 Reduxのスタイルガイドですが状態の正規化に関しては ここ に書かれています。 「無駄な部分まで走る再 レンダリング 」の解決方法 現在は入力部、出力部を一つの コンポーネント で記述しているため、どこかで更新が起きると全てが再 レンダリング されてしまいます。 まず、入力部と出力部を分ければ、お互いの影響で再 レンダリング されることは無くなります。 ですが、最もパフォーマンス面に影響するのは出力部にあるToDoリストの レンダリング だと思います。 例えば、新しいToDoを追加したときに既存のToDoリスト内のToDoは何も変わらないのに全てを再 レンダリング するのはあまりにも無駄です。削除や、完了・未完了の切り替えに関しても同じことが言えます。 また、ToDoを表示する コンポーネント を出力部の子 コンポーネント として新規作成し、分割をしても再 レンダリング を防ぐことはできません。なぜなら、 コンポーネント は以下の条件に当てはまったときに再 レンダリング されるためです。  Stateが更新されたか  Propsが更新されたか  親 コンポーネント が再 レンダリング されたか 出力部のPropsが更新されて、再 レンダリング されてしまうと子 コンポーネント へのPropsに変更がなくても強制的に子 コンポーネント は再 レンダリング されます。 ですが、React.memoを使うことで、Propsが変更された場合にだけ再 レンダリング するようにできます。 React.memoとは、下図のように送られてきたPropsが前回のものと比べて同じかチェックして再 レンダリング するべきか判断してくれるようにするものです。 メモ化することで、追加などの操作をしても関係のないToDoが再 レンダリング されることがなくなり、かなりパフォーマンスが上がることが期待できます。 では、これから実際に前回のToDoアプリのコードを編集し、問題点を解決していきます。 コード修正 TodoStateの修正 まずは、状態管理部分の修正をします。 コンポーネント 側では直接、 Atom を変更できないようにし、カスタムフック越しに変更や取得ができるようにします。また、 Atom 一つにToDoリストを管理させるのではなく、 Atom 一つに対し、一つのToDoを管理させるようにします。 手順は以下のようになります。 1. Atom の正規化 2.変更系カスタムフックの作成 3.取得系カスタムフックの作成 Atom の正規化 現時点のToDoリストの Atom は以下のようになっています。 export const todosState = atom < Todo [] >( { key: AtomKeys.TODOS_STATE , default : [ { id: 1 , title: "テスト1" , content: "テスト1の内容" , isCompleted: false } , { id: 2 , title: "テスト2" , content: "テスト2の内容" , isCompleted: false } ] , } ); これを正規化していきます。ですが、ToDo一つに対し、 Atom 一つで管理するために Atom をその数だけ作るのは非効率です。「atomFamily」を使います。 atomFamilyは同じ型の情報をもつ atom の集合体のようなものです。atomFamilyにはパラメータを設定でき、パラメータにToDoのIDを指定することで特定の情報を抜き出すことが可能になります。 実際にtodosStateを次のように変更しましょう。第一引数には atom の型を、第二引数には特定の atom を指定するための値が入ります。IDで指定するのでnumber型にします。 また、 コンポーネント 側から直接操作できないように「export」は消しておきます。 // todoState.ts const todoState = atomFamily < Todo| null , number >( { key: AtomKeys.TODOS_STATE , default : null } ) ToDoを複数管理することができましたが、IDの管理をしていないため、ある時点で最大のIDはいくつなのか、特定のIDは存在しているのかがわからなくなってしまいます。よって、次の Atom を加え、ToDoのIDを管理します。 //todoState.ts const todoIdState = atom < number [] >( { key: AtomKeys.TODOID_STATE , default : [] } ) keyは「recoilKeys.ts」に新たに作成します。 //recoilKeys.ts export const AtomKeys = { "TODOS_STATE" : "todosState" , "TODOID_STATE" : "todoIdState" , } 正規化が完了しました。これで、IDを指定し、atomFamilyから特定のToDoを取り出せるようになりました。 変更系カスタムフックの作成 Atom を変更するためのカスタムフックを作成していきます。 変更系のカスタムフックを作成する目的は Atom に対して行える操作を特定の範囲内に抑えるためです。ToDoアプリならば、追加と削除、完了・未完了の切り替えのみの操作に抑えるべきです。 「TodoContainer. tsx 」内に記述した変更処理とRecoilに関わるものを削除し、「todoState.ts」で下記のようにカスタムフックにします。 // todoState.ts let id = 1 ; const getId = () => { return id ++; } export const useTodoAction = () => { /** 追加処理 */ const addTodo = useRecoilCallback (( { set } ) => ( title: string , content: string ) => { const newTodo: Todo = { id : getId (), title : title , content : content , isCompleted: false } set( todoIdState , prev => [ ...prev , newTodo.id ] ); set( todosState ( newTodo.id ), newTodo ); } , [] ) /** 削除処理 */ const removeTodo = useRecoilCallback (( { set, reset } ) => ( targetId : number ) => { set( todoIdState , prev => prev.filter ( id => id !== targetId )); reset ( todosState ( targetId )) } , [] ) /** 完了の切り替え */ const toggleComplete = useRecoilCallback (( { set } ) => ( targetId: number ) => { set( todosState ( targetId ), todo => { if ( ! todo ) return null return { ...todo , isCompleted: ! todo.isCompleted } } ) } , [] ) return { addTodo , removeTodo , toggleComplete , } } 「useTodoAction」がカスタムフックです。ポイントなのは処理をする関数を囲む形で使う「useRecoilCallback」です。 useRecoilCallbackを使わないでこのカスタムフックの関数をPropsに渡すと コンポーネント で更新が起きるたびに新しい関数を生み出してしまい、余計な再 レンダリング を起こしてしまいます。 この再 レンダリング を抑えるのがuseRecoilCallbackです。関数をメモ化してくれるものと思えば簡単です。 引数には、「snapShot」「gotoSnapshot」「set」「reset」「refresh」「transact_UNSTABLE」が入れられます。 大体の場合はsetやresetで十分かと思われます。 「set」は特定の Atom やSelectorに値を設定するのに使います。 「reset」は特定の Atom やSelectorの値をdefault値にします。削除処理で用いていますが、この場合は対象の Atom の値がnullになるということです。 atomFamilyを用いたことにより対象以外のToDoを読み込むことなく、対象のToDoのみを扱うことが可能になりました。その分、処理も簡単に書けることがわかります。 取得系カスタムフックの作成 次は特定のToDoとIDの全件を取得するカスタムフックの作成をします。 「todoState.ts」に次のコードを加えます。 const getTodo = selectorFamily < Todo| null , number >( { key: SelectorKeys.GET_TODO , get : todoId => ( { get } ) => get( todosState ( todoId )) } ) export const useGetTodoAction = () => { const todoIds = useRecoilValue ( todoIdState ); const useGetTodo = ( todoId: number ) => useRecoilValue ( getTodo ( todoId )); return { todoIds , useGetTodo } } 「useGetTodoAction」がカスタムフックです。中身は単純で、useRecoilValueでtodoIdStateとgetTodoの値を取得しています。 getTodoは、引数にIDを入れることでそのIDと合致するToDoを返すSelectorFamilyです。 これで、「todoState.ts」の修正が完了しました。 コンポーネント 側の修正をしていきましょう。 ContainerとPresenterの修正 コンポーネント のパフォーマンス部分の修正をしますが、その前にもう一度、 コンポーネント 側の問題点と解決方法を振り返ります。 現時点では入力部、出力部を一つの コンポーネント で記述しており、更新が起きると全てが再 レンダリング されてしまいます。なので、入力部と出力部を分割します。 次に レンダリング コストの高いToDoリスト部分が問題になります。変更が起きていないToDoまで再 レンダリング されるのは無駄なので、出力部にToDoを出力する子 コンポーネント を作成し、その コンポーネント をメモ化をすることでPropsに変更がない限りは再 レンダリング されないようにしましょう。 手順は以下のようになります。 入力部と出力部の分割 ToDoを表示する コンポーネント の作成 Recoilと接続 入力部と出力部の分割 まずは入力部と出力部の分割です。 入力部のContainer・Presenterと出力部のContainer・Presenterをそれぞれ作成します。下記のような構成にしましょう。 変更点は以下のようになってます。 TodoPresenter. tsx の削除 TodoContainer. tsx の削除 container ディレクト リの新規作成 TodosContainer. tsx を新規作成 TodosInputContainer. tsx の新規作成 TodosOutputContainer. tsx の新規作成 presenter ディレクト リの新規作成 TodosInputPresenter. tsx の新規作成 TodosOutputPresenter. tsx の新規作成 ※「TodoPresenter. tsx 」を削除する際に、中身をどこかに保管しておくといいかもしれないです。 「TodosContainer. tsx 」は「TodosInputContainer. tsx 」「TodosOutputContainer. tsx 」を返すようにします。 //TodosContainer.tsx export const TodosContainer = () => { return( <> < TodosInputContainer / > < TodosOutputContainer / > < / > ) } 「TodosInputContainer. tsx 」は一旦、「TodosInputPresenter. tsx 」を返すだけにします。「TodosOutputContainer. tsx 」も同様です。 //TodosInputContainer.tsx export const TodosInputContainer = () => { return < TodosInputPresenter / > } //TodosOutputContainer.tsx export const TodosOutputContainer = () => { return < TodosOutputPresenter / > } 「TodosInputPresenter. tsx 」には、入力部のコードを書きます。addTodo関数はエラーが出てしまいますが、そのままにしておきます。 //TodosInputPresenter.tsx export const TodosInputPresenter = () => { const [ title , setTitle ] = useState ( "" ); const [ content , setContent ] = useState ( "" ); const sendTodo = () => { addTodo ( title , content ) setTitle ( "" ) setContent ( "" ) } return ( < form > < label > タイトル: < input type= "text" value = { title } onChange = { e => setTitle ( e.target.value ) } / > < /label > < label > 内容: < input type= "text" value = { content } onChange = { e => setContent ( e.target.value ) } / > < /label > < button type= "button" onClick = { () => sendTodo () } > 送信 < /button > < /form > ) } 「TodosOutputPresenter. tsx 」には、出力部のコードを書きます。todosがないのでエラーが出ますが、これもそのままにしておきます。 //TodosOutputPresenter.tsx export const TodosOutputPresenter = () => { return ( <> < div >------------------------ - < /div > < h1 > Todoリスト < /h1 > { todos.map (( todo : Todo )=> { return ( < React.Fragment key = { todo.id } > < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' onClick = { () => toggleComplete ( todo.id ) } > { todo.isCompleted ? "戻す" : "完了" } < /button > < button type= 'button' onClick = { () => removeTodo ( todo.id ) } > 削除 < /button > < /React.Fragment > ) } ) } < / > ) } 所々、エラーは出ていますが、入力部と出力部を分割することができました。 ToDoを表示する コンポーネント の作成 出力部の子 コンポーネント として、ToDoを表示する コンポーネント を作成します。 container ディレクト リ下に「TodoOutputContainer. tsx 」を、presenter ディレクト リの下に「TodoOutputContainer. tsx 」を新規作成します。 これを「TodosOutputPresenter. tsx 」のToDoリストを表示している部分に入れます。現時点では何もPropsで与えていませんが、一旦これで置いておきます。 次のように書き換えましょう。 //TodosOutputPresenter export const TodosOutputPresenter = () => { return ( <> < div >------------------------ - < /div > < h1 > Todoリスト < /h1 > { todos.map (( todo : Todo )=> < TodoOutputContainer / > ) } < / > ) } 「TodoOutputContainer. tsx 」は「TodoOutputPresenter. tsx 」を返すのみにしておきます。 //TodoOutputContainer.tsx export const TodoOutputContainer = () => { return < TodoOutputPresenter / > } そして、「TodoOutputPresenter. tsx 」でToDoを表示するようにします。引数を入れるところから最後までを「memo」で囲むことでメモ化することができます。 todoや、その他の操作でエラーが出ていますが一旦、そのままで問題ありません。 //TodoOutputPresenter export const TodoOutputPresenter = memo (() => { return ( <> < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' onClick = { () => toggleComplete ( todo.id ) } > { todo.isCompleted ? "戻す" : "完了" } < /button > < button type= 'button' onClick = { () => removeTodo ( todo.id ) } > 削除 < /button > < / > ) } ); Recoilと接続 コンポーネント の分割が終わったので、あとは「todoState.ts」で作成したカスタムフックを コンポーネント 側で使うだけです。 まずは入力部から繋いでいきます。「TodosInputContainer. tsx 」では、Todoを追加するaddTodoを使います。 //TodosInputContainer.tsx export const TodosInputContainer = () => { const { addTodo } = useTodoAction (); return < TodosInputPresenter addTodo = { addTodo } / > } 「TodosInputPresenter. tsx 」にPropsでaddTodoを渡します。 //TodosInputPresenter.tsx type TodosInputPresenterProps = { addTodo : ( title: string , content: string ) => void } export const TodosInputPresenter : React.FC < TodosInputPresenterProps > = ( { addTodo } ) => { const [ title , setTitle ] = useState ( "" ); const [ content , setContent ] = useState ( "" ); const sendTodo = () => { addTodo ( title , content ); setTitle ( "" ) setContent ( "" ) } /* 省略 */ これで入力部は大丈夫です。 出力部の「TodosOutputContainer. tsx 」にはIDのリストを、ToDoを表示する「TodoOutputContainer. tsx 」にはIDからToDoを取り出す操作、削除の操作、完了・未完了の切り替えの操作をカスタムフックから取得します。 「TodosOutputContainer. tsx 」を編集します。 //TodosOutputContainer.tsx export const TodosOutputContainer = () => { const { todoIds } = useGetTodoAction (); return < TodosOutputPresenter todoIds = { todoIds } / > } 「TodosOutputPresenter. tsx 」を編集します。この コンポーネント はメモ化します。todoIdsに更新がない限りは再 レンダリング がされません。つまり、完了・未完了の切り替えの時は レンダリング がされません。 「TodoOutputContainer. tsx 」でIDが必要になるので、Propsで渡します。 //TodosOutputPresenter.tsx type TodosOutputPresenterProps = { todoIds : number [] } export const TodosOutputPresenter : React.FC < TodosOutputPresenterProps > = memo (( { todoIds } ) => { return ( <> < div >------------------------ - < /div > < h1 > Todoリスト < /h1 > { todoIds.map ( todoId => < TodoOutputContainer todoId = { todoId } / > ) } < / > ) } ) 「TodoOutputContainer. tsx 」では、親からきたIDを受け取ります。 「TodoOutputPresenter. tsx 」にはIDからToDoを取り出す関数ではなく、取り出した後のToDoと削除、切り替えの関数を渡します。 ToDoを取り出す関数を渡さない理由としては、useGetTodo関数はメモ化されていないためです。この関数をPropsで渡してしまうと、何か更新が走るたびにReactは新しい関数としてuseGetTodoを受け取ってしまい、全てのToDoが再 レンダリング されてしまいます。 //TodoOutputContainer.tsx type TodoOutputContainerProps = { todoId : number } export const TodoOutputContainer : React.FC < TodoOutputContainerProps > = ( { todoId } ) => { const { useGetTodo } = useGetTodoAction (); const { removeTodo , toggleComplete } = useTodoAction (); const todo = useGetTodo ( todoId ); const args = { todo , removeTodo , toggleComplete } return < TodoOutputPresenter { ...args } / > } 最後に「TodoOutputPresenter. tsx 」を編集します。 //TodoOutputPresenter.tsx type TodoOutputPresenterProps = { todo: Todo | null , removeTodo: ( id: number ) => void , toggleComplete: ( id: number ) => void } export const TodoOutputPresenter : React.FC < TodoOutputPresenterProps >= memo (( { todo , removeTodo , toggleComplete } ) => { if( ! todo ) return <>< / >; return ( <> < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' onClick = { () => toggleComplete ( todo.id ) } > { todo.isCompleted ? "戻す" : "完了" } < /button > < button type= 'button' onClick = { () => removeTodo ( todo.id ) } > 削除 < /button > < / > ) } ); パフォーマンスのチェック メモやカスタムフックを用いて、多くの編集をしてきました。実際にどのくらいパフォーマンスが変わるかチェックしてみます。 注目する点は 入力部と出力部がお互いの影響で再 レンダリング されていないか 変更のないToDoが無駄に再 レンダリング されていないか まずは入力部と出力部がお互いの影響で再 レンダリング されていないかをチェックします。入力部・出力部・ToDoの コンポーネント 部分にconsole.logを仕込みました。 入力部は文字を打ち込むたびに変更が起きてしまうので再 レンダリング が頻繁に起こってしまっています。ですが、出力部には全く影響が出ていないことが確認できます。しっかりと コンポーネント を分割したおかげです。 また、今回はしませんでしたが、入力部もさらにタイトル部分、コンテンツ部分、ボタンで コンポーネント を分けることでさらに再 レンダリング の範囲を狭めることが可能です。 次は、変更のないToDoが無駄に再 レンダリング されていないかをチェックします。入力部がかなりログを吐いてしまうので入力部のログは消しています。 ToDo追加時に状態に更新があるのは、ToDoのIDリストと、追加されたToDoのみなので、出力部と追加されたToDoのみが レンダリング されています。一見全てが再 レンダリング されているように見えますが、もしToDoが レンダリング されていれば、「ID:○の レンダリング 」とログを出すはずなので、しっかりとメモ化されていることがわかります。 削除時は出力部のみ再 レンダリング されています。変更のないToDoは追加と同様にメモ化されたものが使われています。 更新時はToDoのIDリストには変更がないため対象のToDoのみが再 レンダリング されていることがわかります。 コンポーネント のメモ化と、関数のメモ化により、変更のないToDoには全く影響がないようになっています。しっかりとパフォーマンスが上がったかと思われます。 終わりに 今回の記事では、前回作成したRecoilを用いたToDoアプリの問題点を例にRecoilの安全な運用方法と、無駄な再 レンダリング の削減方法について書かせていただきました。 Recoilにはまだまだ他にも機能があるのでご興味があれば公式サイトを調べてみてください! recoiljs.org また、良ければRecoil以外にもRedux、Redux-Toolkitについて書いた記事もあるので読んでいただけると嬉しいです。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
勤怠サービスの開発チームに所属しているkarabishです。 テストに関するある課題を解決するために API テストの自動化ツールを調査しました。まだチーム内に展開していないのですが、調査結果のうちツールの選定に関する部分を備忘録として残しておこうと思います。 なぜAPIテストを自動化するのか ツールの選定方針 調査したツールたち 調査方法 調査結果 Tavern テストシナリオ テスト実行 scenarigo テストシナリオ テスト実行 runn テストシナリオ テスト実行 karate テストシナリオ テスト実行 stepci テストシナリオ テスト実行 調査しなかったツールたち まとめ なぜ API テストを自動化するのか 36協定の計算などの負荷が重たい処理はpub/sub アーキテクチャ を利用して非同期で処理していました。ただ、publish側とsubscribe側それぞれの ユニットテスト は存在していたのですが、全体に関するテストは自動化されていませんでした。そのため、pub/sub全体に関するテスト観点を API テストで自動化しようと目論んだのが発端となります。 ツールの選定方針 選定方針を挙げるとすればこの3つになるのかと思います。 CIとの親和性が高いこと dockerで扱いやすいこと yaml などのテキストに定義するだけでテストができること なぜこの3点なのかというと、 API テスト自体はCI上で実行する予定のため「CIとの親和性」が重要で、CIは Gitlab CI ( executor はdockerを利用)のためdockerで実行できる必要があり、dockerで実行するとなるとコードを書かずに yaml などで定義できればありがたい、という背景になります。 調査したツールたち 調査したツールたちは以下の5つです。調査はしませんでしたが候補としてあがったツールたちは参考までに 調査しなかったツールたち にまとめてあります。 ツール URL ライセンス 実装する言語 対応している プロトコル dockerイメージ Tavern 公式サイト , Github MIT license yaml , python http, MQTT ? scenarigo Github Apache-2.0 license yaml , golang http, gRPC ? runn Github MIT license yaml , golang http, gRPC, DB, Chrome DevTools Protocol, SSH /Local command ghcr karate 公式サイト , Github MIT license Gherkin http, GraphQL ? stepci 公式サイト , Github MPL-2.0 license yaml REST, GraphQL, gRPC, tRPC, SOAP ghcr 調査方法 OpenAPIのpetstore.yaml を利用したモック環境を用意する GET /pet/1 リク エス トに対して成功した場合(200 OKを期待値とする)を実施する % docker run --name openapi -d -p 4010:4010 stoplight/prism:3 mock -d -h 0.0.0.0 https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/3_0/petstore.yaml 9b6c556320adfacd9b3a497af35df5774b64abd406f022478254e1cc7ec42600 # cURLで実行した場合のレスポンス % curl -sS -H 'api_key: special-key' -H "Accept: application/json" http://localhost:8080/pet/1 | jq . { "name": "officia magna", "photoUrls": [ "Duis ex incididunt", "sit" ], "id": 1824362991613808600, "category": { "id": -6773680898015056000, "name": "zq8QbcVd.U6hh9W0Pl01Dpq" }, "tags": [ { "id": 623842466761203700, "name": "dolor est occaecat ea adipisicing" } ], "status": "available" } 調査結果 Tavern テストシナリオ % cat api-test.tavern.yaml test_name: sample stages: - name: GET /pet/1 request: url: http://ローカルのIPアドレス:8080/pet/1 method: GET headers: accept: application/json api_key: special-key response: status_code: 200 テスト実行 公式のdockerイメージが見つからなかったため、 chatworkさん提供のイメージ を利用させていただいた % docker run -it --rm -v ${PWD}:/tavern chatwork/tavern:1.7.0 /tavern/api-test.tavern.yaml . ------------------------------------------------------------------------------ Ran 1 tests in 0.25s OK scenarigo テストシナリオ # cat scenarios/api-test.yaml title: sample steps: - title: GET /pet/1 protocol: http request: method: GET url: 'http://ローカルのIPアドレス:8080/pet/1' header: accept: application/json api_key: special-key expect: code: 200 テスト実行 golangのdockerイメージ を実行し、scenarigoを go install でインストールする % docker run -it --rm -v ${PWD}:/tests golang:1.19.3-bullseye bash # go install github.com/zoncoen/scenarigo/cmd/scenarigo@v0.12.8 scenarigoの設定ファイルを作成する。 scenarigo config init でテンプレートは作成可能 # cat scenarigo.yaml schemaVersion: config/v1 scenarios: # Specify test scenario files and directories. # ↓このディレクトリにシナリオを配置する - scenarios pluginDirectory: ./gen # Specify the root directory of plugins. # plugins: # Specify configurations to build plugins. # plugin.so: # Map keys specify plugin output file path from the root directory of plugins. # src: ./path/to/plugin # Specify the source file, directory, or "go gettable" module path of the plugin. output: verbose: false # Enable verbose output. # colored: false # Enable colored output with ANSI color escape codes. It is enabled by default but disabled when a NO_COLOR environment variable is set (regardless of its value). # report: # json: # filename: ./report.json # Specify a filename for test report output in JSON. # junit: # filename: ./junit.xml # Specify a filename for test report output in JUnit XML format. scenarigo run でテストを実行する # cd /tests/ # scenarigo run ok scenarios/api-test.yaml 0.036s scenarigo. yaml の output.verbose を true にすることでリク エス トとレスポンスが標準出力される # scenarigo run === RUN scenarios/api-test.yaml === RUN scenarios/api-test.yaml/sample === PAUSE scenarios/api-test.yaml/sample === CONT scenarios/api-test.yaml/sample === RUN scenarios/api-test.yaml/sample/GET_/pet/1 --- PASS: scenarios/api-test.yaml (0.03s) --- PASS: scenarios/api-test.yaml/sample (0.03s) --- PASS: scenarios/api-test.yaml/sample/GET_/pet/1 (0.03s) [0] send request request: method: GET url: http://ローカルのIPアドレス:8080/pet/1 header: Accept: - application/json Api_key: - special-key User-Agent: - scenarigo/v0.12.8 response: header: Access-Control-Allow-Credentials: - "true" Access-Control-Allow-Headers: - "*" Access-Control-Allow-Origin: - "*" Access-Control-Expose-Headers: - "*" Connection: - keep-alive Content-Length: - "222" Content-Type: - application/json Date: - **** body: category: id: "1575230569806000000" name: 9O7c1pJOPktof id: "8291010298486120000" name: consequat nulla sint photoUrls: - consequ status: pending tags: - id: "1005964459763441700" name: dolor Lorem sunt elapsed time: 0.027939 sec PASS ok scenarios/api-test.yaml 0.033s runn テストシナリオ % cat api-test.yaml desc: sample runners: req: http://ローカルのIPアドレス:8080 steps: getPet: req: /pet/1: get: headers: accept: "application/json" api_key: "special-key" test: steps.getPet.res.status == 200 テスト実行 用意されているdockerイメージ を利用することで実行することができる % docker run -it --rm --name runn -v $PWD:/books ghcr.io/k1low/runn:v0.52.3-slim run /books/api-test.yaml sample ... ok 1 scenario, 0 skipped, 0 failures --debug オプションを指定することでリク エス トとレスポンスの内容が標準出力される % docker run -it --rm --name runn -v $PWD:/books ghcr.io/k1low/runn:v0.52.3-slim run --debug /books/api-test.yaml Run 'req' on 'sample'.steps.getPet -----START HTTP REQUEST----- GET /pet/1 HTTP/1.1 Host: ローカルのIPアドレス:8080 Accept: application/json Api_key: special-key -----END HTTP REQUEST----- -----START HTTP RESPONSE----- HTTP/1.1 200 OK Content-Length: 452 Access-Control-Allow-Credentials: true Access-Control-Allow-Headers: * Access-Control-Allow-Origin: * Access-Control-Expose-Headers: * Connection: keep-alive Content-Type: application/json Date: **** {"name":"adipisicing eiusmod Excepteur nostrud","photoUrls":["ipsum dolor"],"id":4648156526148719000,"category":{"id":-1107853279573229600,"name":"8PbcHZGjikgwWWr"},"tags":[{"id":-3453614735764951000,"name":"dolore"},{"id":-6507718611499348000,"name":"ex Excepteur laboris mollit occaecat"},{"id":2671625534111551500,"name":"mollit"},{"id":-9126589410744205000,"name":"id Ut Lorem aliqua"},{"id":2174855446376759300,"name":"sed enim"}],"status":"sold"} -----END HTTP RESPONSE----- Run 'test' on 'sample'.steps.getPet sample ... ok 1 scenario, 0 skipped, 0 failures karate テストシナリオ % cat api-test.feature Feature: sample Background: * def host = 'localhost:8080' * def httpHeaders = { accept: 'application/json', api_key: 'special-key' } Scenario: GET /pet/{id} Given url 'http://' + host + '/pet/1' And configure headers = httpHeaders When method get Then status 200 テスト実行 Github からjarファイルをダウンロードしておく java -jar ${ダウンロードしたjarファイル} {テストシナリオ} で実行することができる % java -jar karate-1.3.0.jar api-test.feature 00:00:00.000 [main] INFO com.intuit.karate - Karate version: 1.3.0 00:00:00.000 [main] INFO com.intuit.karate.Suite - backed up existing 'target/karate-reports' dir to: target/karate-reports_1669956006172 00:00:00.000 [main] DEBUG com.intuit.karate - request: 1 > GET http://localhost:8080/pet/1 1 > accept: application/json 1 > api_key: special-key 1 > Host: localhost:8080 1 > Connection: Keep-Alive 1 > User-Agent: Apache-HttpClient/4.5.13 (Java/11.0.11) 1 > Accept-Encoding: gzip,deflate 00:00:00.000 [main] DEBUG com.intuit.karate - response time in milliseconds: 49 1 < 200 1 < Access-Control-Allow-Origin: * 1 < Access-Control-Allow-Headers: * 1 < Access-Control-Allow-Credentials: true 1 < Access-Control-Expose-Headers: * 1 < Content-type: application/json 1 < Content-Length: 315 1 < Date: **** 1 < Connection: keep-alive {"name":"laboris Ut","photoUrls":["elit ven","magna sint fugiat in occaecat","sit velit irure proident"],"id":6533262285796651000,"category":{"id":-5086239707500454000,"name":"jDU6rd4mMXrFOzsdIBp"},"tags":[{"id":-4193826791138492400,"name":"Duis anim"},{"id":-7590066944733139000,"name":"elit Ut"}],"status":"sold"} --------------------------------------------------------- feature: api-test.feature scenarios: 1 | passed: 1 | failed: 0 | time: 0.3456 --------------------------------------------------------- 00:00:00.000 [main] INFO com.intuit.karate.Suite - <<pass>> feature 1 of 1 (0 remaining) api-test.feature Karate version: 1.3.0 ====================================================== elapsed: 1.57 | threads: 1 | thread time: 0.35 features: 1 | skipped: 0 | efficiency: 0.22 scenarios: 1 | passed: 1 | failed: 0 ====================================================== HTML report: (paste into browser to view) | Karate version: 1.3.0 file:///${PWD}/target/karate-reports/karate-summary.html =================================================================== stepci テストシナリオ % cat api-test.yaml version: "1.1" name: sample env: host: ローカルのIPアドレス:8080 tests: getPet: steps: - name: GET /pet/1 http: url: http://${{env.host}}/pet/1 method: GET headers: accept: application/json api_key: special-key check: # http statusが200であることをチェックする status: 200 テスト実行 用意されているdockerイメージ を利用することで実行することができる Privacy に記載されている通り利用状況の収集を無効化するため STEPCI_DISABLE_ANALYTICS を指定している % docker run -it --rm -e STEPCI_DISABLE_ANALYTICS=yes -v ${PWD}:/tests ghcr.io/stepci/stepci:2.5.6 tests/api-test.yaml PASS getPet Tests: 0 failed, 1 passed, 1 total Steps: 0 failed, 0 skipped, 1 passed, 1 total Time: 0.216s, estimated 0s Workflow passed after 0.216s Give us your feedback on https://step.ci/feedback 調査しなかったツールたち 候補としては上がったが調査しなかったツールたちです。 ツール URL ライセンス cURL 公式サイト , Github ? HTTPie 公式サイト , Github BSD-3-Clause license Postman + Newman Postmanの公式サイト , NewmanのGithub NewmanはApache License 2.0 insomnia 公式サイト - api fortress 公式サイト - Assertible 公式サイト - speedscale 公式サイト - Datadog 公式サイト - Frisby 公式サイト BSD 3-Clause SuperTest Github MIT license Chakram 公式サイト , Github MIT license REST Assured Github Apache-2.0 license Pact 公式サイト , Github pact-goはMIT license Dredd 公式サイト , Github MIT license まとめ API テストの自動化ツールを調査してみました。運用していないのでどういう問題が発生するかわからないのですが、導入するのはすんなりいけそうなツールがそこそこあるなという印象です。また、冒頭に記載した通りまだチームメンバーに展開していないのでどれを採用するのかはわからないのですが、複雑なことをする予定はないので個人的にはシンプルで簡単にできそうな stepci がいいのではと思っています。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
はじめに JJUG CCC とは 発表内容 資料 テーマの選定理由 さいごに はじめに JJUG CCC とは 日本Javaユーザーグループ(Japan Java User Group/JJUG) が主催として、年2回(春と秋)に開催する1日カンファレンスです。 今回はオンラインで開催され、各セッションは事前録画したビデオの放映とライブQ&Aという構成でした。 このイベントは過去にも弊社のエンジニアが数名登壇したことがあり、ちょうど発表できそうなネタもあったので、今回登壇にチャレンジしました。 あと弊社はロゴスポンサーもやってました(終わってから言っても遅い気がしますが...) ccc2022fall.java-users.jp 発表内容 資料 speakerdeck.com JJUG の YouTube チャンネル でも公開されています。他の方の発表も見返せるので、ご興味があれば是非ご覧ください! テーマの選定理由 「何か新しい技術/概念」を期待されていた方には申し訳ないのですが、今回の発表はモダンな開発をすることによるメリットを、 脆弱性 対応という観点から改めて整理したものになります。(モダンといってもめちゃくちゃ新しいわけではないですが…) 発表のポイントになる「Docker / コンテナ技術」や「テストコードを書くこと」は、今から開発を始めるなら当たり前と言って良いのではないかと思います。しかしそれ以前ではアプリケーションがサーバ上で直接動いているため依存ライブラリの更新に手間がかかったり、テストコードがなく何をやるにしても手動でテストするしか品質を担保できないこともありました。そういった状況下では、発表でも触れたように、 脆弱性 対応が億劫になってしまい「積極的にはやりたくない…」という意識になりがちです。 そうではなく、もう少しポジティブというかシンプルに「 脆弱性 が出たならバージョンアップすればいい」くらいの考えでやっていければ、みんながちょっとずつ幸せになれるのではと思い、このテーマを選定しました。 さいごに 最後に今回の登壇を振り返って締めようと思います。 弊社では技術イベントに登壇する際は、会社からのバックアップを受けることができます。もちろん今回もバックアップを受けており、登壇が実は初めてだった私にとっては非常にありがたかったです。CfPの作成からビデオ録画までの段取りなど、本当に助かったという思いでいっぱいです。 また、イベント当日は「ビデオは提出したし、出番まで他の人の発表聞いてゆっくりしようか」とでも思っていたのですが、いざ出番が近づいてくると結構緊張してドキドキしていました。とはいえホントの直前には「今からビデオを再収録できるわけでもないか」と一周回って落ち着くことが出来ました。ライブでの出番がQ&Aだけということに一番安堵した瞬間でした。 登壇に向けてやることが多くて大変でしたが、その分いい経験になりました。またいつか機会があればチャレンジしてみても良いかもしれません。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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年目の koki _matsuraです。 本日は、 Recoilの基本的な状態管理や仕組み をTodoアプリ作成を通して、ご紹介させていただきます。 こちらの記事は 「Reactの状態管理ライブラリ基礎学習」の3部目 です。 「Redux編」「Redux-Toolkit編」もあるので、下記のリンクから読んでいただけると嬉しいです。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp Reactの状態管理ライブラリを勉強している方、状態管理ライブラリについて簡単に知りたい方などのお役に立てればなと書かせていただきました。 アジェンダ は以下の通りです。 Recoilとは 概要 構成図 Todoアプリ作成 仕様説明 プロジェクト作成 初期設定 ディレクトリ構成 Todo型の定義 Keyの定義 Atomの定義 TodoContainer.tsxの定義 TodoPresenter.tsxの定義 RecoilRootの定義 アプリの起動 Selectorの定義 Todoの追加機能 Todoの削除機能 完了・未完了の切り替え機能 終わりに Recoilとは 概要 RecoilはContextAPIが抱える レンダリング やコード分割の問題を解決するために提唱された、現時点では実験的な状態管理ライブラリであり、アプリケーションの状態を Atom とSelectorの集まりで管理します。 Atom はStateの単位で一意のキーとデータで管理、Selectorは Atom や他Selectorを受け取る純粋な関数です。 Atom を加工して取得したりする目的で用いられます。 Hooks API を使って、状態管理を行う点がRecoilの特徴です。 構成図 Recoilは下図のような仕組みの状態管理をしています。 コンポーネント からHooks API を使用することで簡単に Atom やSelectorの値を取得、変更、更新することができます。 Reduxを使ってから、Recoilを使ってみると、かなり単純で使いやすいことに気づきます。 Todoアプリ作成 仕様説明 Todoアプリを作成する前にTodoアプリの仕様と構成を説明します。 構成は以下の画像のようになります。 入力フォームと送信ボタン、Todoのリストを載せる部分で構成されます。 また、それぞれのTodoには内容に加え、完了ボタン、削除ボタンがあります。 仕様を説明します。 ・Todoの追加 画像上部のタイトル・内容の入力フォームに適当なテキストを入力し、送信ボタンを押すことでTodoリストに入力したTodoが追加されます。 ・Todoリストの表示 画像下部のTodoリストは古いもの(ID昇順)から順に表示されます。最も新しいものは最後尾に表示されます。 ・Todoの完了 それぞれのTodoについている完了ボタンを押すと、該当するTodoが未完了から完了に変化します。 また、完了しているTodoには「戻す」ボタンが表示されており、これは完了ボタンの逆の働きをします。 ・Todoの削除 それぞれのTodoについている削除ボタンを押すと、該当するTodoがリストから削除され、表示からも消えます。 以上が今回作成していくTodoアプリの仕様になっています。 プロジェクト作成 プロジェクトの作成は下記のコマンドを入力します。 私はプロジェクト名を「recoil-todo」としましたが、お好きなプロジェクト名をつけていただいて問題ありません。 npx create-react-app [プロジェクト名] --template typescript 初期設定 プロジェクト作成後、下記のコマンドでプロジェクトに移動して、Recoilを使えるようにします。 cd recoil-todo npm i recoil ディレクト リ構成 Recoilを用いるときの ディレクト リ構成は以下のようにします。 common ディレクト リ、features ディレクト リ、features ディレクト リの中にtodos ディレクト リを作成します。 common ディレクト リ recoilKeys.tsの新規作成 todo.type.tsの新規作成 「recoilKeys.ts」にはRecoilの状態管理で必要となる Atom やSelectorのユニークなキーを格納します。キーについては後ほど詳しく書かせていただきます。 「todo.type.ts」は今回のTodoアプリで出てくるTodoのタイプを定義します。 features/todos ディレクト リ TodoContainer. tsx の新規作成 TodoPresenter. tsx の新規作成 todoState.tsの新規作成 「TodoContainer. tsx 」はTodoアプリのロジック部分を、「TodoPresenter. tsx 」の表示部分を担当します。 「todoState.ts」には状態を管理する Atom と Atom を加工するSelectorを定義します。 Todo型の定義 Todo型を下記のように定義します。 //todo.type.ts export type Todo = { id: number , title: string , content: string , isCompleted: boolean } Keyの定義 Keyは Atom やSelectorに必須です。これは、 Atom やSelectorにおける特定の高度な API に使用されるため、複数の Atom が同じキーを持つことは禁止されています。 今回の場合はTodoを管理する Atom が一つ、それを加工するSelectorが一つのため、Keyが重複する心配はそれほどありませんが、今後、状態が増えたりする可能性がある場合に重複させないためにもKeyを一元管理させます。 「recoilKeys.ts」に Atom のKeyを下記のように一つ定義します。 //recoilKeys.ts export const AtomKeys = { "TODOS_STATE" : "todosState" } Atom の定義 Keyを定義できたので、「todoState.ts」に Atom を定義します。 Atom で定義するものはKeyとStateのみです。Reducerのようなものは書きません。 //todoState.ts export const todosState = atom ( { key: AtomKeys.TODOS_STATE , default : [ { id: 1 , title: "テスト1" , content: "テスト1の内容" , isCompleted: false } , { id: 2 , title: "テスト2" , content: "テスト2の内容" , isCompleted: false } ] as Todo [] , } ); TodoContainer. tsx の定義 このファイルではTodoアプリのロジック部分を担当します。 RecoilではStoreのようなものはなく、 Atom 単位で取得してきます。 Atom の取得だけしたいときには「useRecoilValue」を、 Atom の変更だけしたいときには「useSetRecoilState」を使います。 また、useStateのように取得と変更を両方したいときには、「useRecoilState」を使います。 今回は一旦、Todoの表示だけを実装したいので「useRecoilValue」を使います。後にTodoの追加機能の際、書き換えます。 TodoPresenterはまだ定義していないのでエラーが出ていても問題ありません。 //TodoContainer.tsx import { useRecoilValue } from "recoil" import { todosState } from "./todoState" export const TodoContainer = () => { const todos = useRecoilValue ( todosState ); const args = { todos , } return < TodoPresenter { ...args } / > } TodoPresenter. tsx の定義 このアプリではTodoアプリの表示部分を担当します。 Todoリストを表示します。 まずは、色々な機能を作る前に基盤を作りたいので下記のようなコードにします。 //TodoPresenter.tsx import React , { useState } from "react" import { Todo } from "../../common/todo.type" type TodoPresenterProps = { todos : Todo [] } export const TodoPresenter : React.FC < TodoPresenterProps > = ( { todos , } ) => { const [ title , setTitle ] = useState ( "" ); const [ content , setContent ] = useState ( "" ); return ( <> < form > < label > タイトル: < input type= "text" value = { title } onChange = { e => setTitle ( e.target.value ) } / > < /label > < label > 内容: < input type= "text" value = { content } onChange = { e => setContent ( e.target.value ) } / > < /label > < button type= "button" > 送信 < /button > < /form > < div >------------------------ - < /div > < h1 > Todoリスト < /h1 > { todos.map (( todo : Todo )=> { return ( < React.Fragment key = { todo.id } > < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' > { todo.isCompleted ? "戻す" : "完了" } < /button > < button type= 'button' > 削除 < /button > < /React.Fragment > ) } ) } < / > ) } 入力部にはタイトルと内容の入力フォームとまだ機能のついていない送信ボタンを配置しています。 出力部にはTodoリストをmap関数で出力しています。それぞれのTodoにつくボタンも現時点では機能がついていません。 一旦、これで置いておきます。 RecoilRootの定義 Stateを共有したい コンポーネント をRecoilRootで囲むことで簡単にその コンポーネント をルート コンポーネント にしてStateを共有できます。 「App. tsx 」を書き換えます。 //App.tsx import React from 'react' ; import { RecoilRoot } from 'recoil' ; import { TodoContainer } from './features/todos/TodoContainer' ; function App () { return ( < div className = "App" > < RecoilRoot > < TodoContainer / > < /RecoilRoot > < /div > ); } export default App ; アプリの起動 下記のコマンドで起動してみましょう。 npm start 自動で開くと思いますが、開かない方は「 http://localhost:3000/ 」にアクセスしてください。 次のようにStateを定義したときに入れたサンプルデータが2件分、表示されていればうまくいっています。 Selectorの定義 新しいTodoを作成するときに必要となるIDはその時点のtodosが持つTodoの最大のIDにプラス1した値を割り当てます。 Selectorを使って、最大のIDを取り出します。 まずは、「recoilKeys.ts」にSelectorのKeyを定義します。 //recoilKeys.ts export const SelectorKeys = { "TODO_MAXID" : "todoMaxId" } 次に、「todoState.ts」に最大IDを取得するSelectorを定義します。 //todoState.ts export const maxIDSelector = selector < number >( { key: SelectorKeys.TODO_MAXID , get : ( { get } ) => { return get( todosState ) .length ? get( todosState ) .slice ( -1 ) [ 0 ] .id : 0 } } ) Selectorは Atom を加工して取得する以外に、 Atom の値を変更することも可能です。 Todoの追加機能 送信ボタンを押すと、Todoを追加できるようにします。 手順を説明します。 Container内のuseRecoilValueをuseRecoilStateに変更 Containerで Atom にTodoを追加する関数を作成 Presenterで送信ボタン押下時に2で作成した関数を実行する 「todoContainer. tsx 」でuseRecoilValueを取得と変更を共にできるuseRecoilStateに変更します。 useStateと同じ書き方です。 //TodoContainer.tsx const [ todos , setTodos ] = useRecoilState ( todosState ) Atom にTodoを追加する関数「addTodo」を作成します。また、この際に先ほど作成したmaxIDSelectorを使います。 下記のコードを追加します。 //TodoContainer.tsx const maxID = useRecoilValue ( maxIDSelector ); const addTodo = ( title: string , content: string ) => { const newTodo: Todo = { id : maxID+ 1 , title: title , content: content , isCompleted: false } setTodos ( [ ...todos , newTodo ] ) } Selectorの使い方は Atom と同じです。今回は取得のみなので、useRecoilValueにしました。 今までの状態管理ライブラリでは、dispatchでReducerにActionを送っていましたが、RecoilはsetTodosに新しい状態を格納するだけで更新できます。 argsにaddTodo関数を追加して、「TodoPresenter. tsx 」に渡しましょう。 「TodoPresenter. tsx 」では、送信ボタンを押下時にaddTodo関数を実行するようにしたいです。 なので、addTodo関数を実行し、その後に入力内容を空にするsendTodo関数を作成します。その関数を送信ボタン押下時に実行させるように下記のコードを「TodoPresenter. tsx 」に追加します。 //TodoPresenter.tsx const sendTodo = () => { addTodo ( title , content ); setTitle ( "" ); setContent ( "" ); } //省略 < button type= "button" onClick = { () => addTodo ( title , content ) } > 送信 < /button > Todoを追加できるようになっていれば問題ありません。 Todoの削除機能 それぞれのTodoについている削除ボタンを押すと、リストから削除されるようにします。 Atom からTodoを削除する関数「addTodo」を作成します。 下記のコードを追加します。 //TodoContainer.tsx const removeTodo = ( id: number ) => { setTodos ( todos.filter (( todos ) => todos.id !== id )) } フィルターを用いて、対象のIDをもつTodoだけを弾いた新たなtodosを格納させるコードにしました。 argsにremoveTodo関数を渡して、「TodoPresenter. tsx 」では、削除ボタンを押したときに削除したいTodoのidを引数にしてremoveTodo関数を実行するようにします。 下記のように「TodoPresenter. tsx 」の削除ボタンを変更してください。 //TodoPresenter.tsx < button type= 'button' onClick = { () => removeTodo ( todo.id ) } > 削除 < /button > 削除ボタンを押すことでTodoを削除できるようになっているかと思います。 完了・未完了の切り替え機能 それぞれのTodoについている完了ボタンを押すと、タイトルの横の「未完了」テキストが「完了」テキストになるようにします。また、完了ボタンは「戻る」というテキストのボタンに変化します。 この戻るボタンを押すと、完了ボタンとは逆の操作をします。 「TodoPresenter. tsx 」の完了ボタンとタイトル横のテキストのコードを見てみると、todo.isCompletedで切り替えられることがわかります。 なので、isCompletedを切り替えられる関数を作りましょう。 //TodoPresenter.tsx < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' > { todo.isCompleted ? "戻す" : "完了" } < /button > 「todoContainer. tsx 」に完了・未完了を切り替える関数「toggleComplete」を作成します。 下記のコードを追加します。 //TodoContainer.tsx const toggleComplete = ( id: number ) => { const newTodos = todos.map ( todo => todo.id === id ? { ...todo , isCompleted: ! todo.isCompleted } : todo ) setTodos ( newTodos ) } argsにtoggleComplete関数を渡して、「TodoPresenter. tsx 」では、完了ボタンを押したときに対象のTodoのidを引数にしてtoggleComplete関数を実行するようにします。 下記のように「TodoPresenter. tsx 」の完了ボタンを変更してください。 //TodoPresenter.tsx < button type= 'button' onClick = { () => toggleComplete ( todo.id ) } > { todo.isCompleted ? "戻す" : "完了" } < /button > 完了ボタンを押すと、それぞれのTodoタイトルの横の「未完了」が「完了」に切り替わることが確認できると思います。 終わりに Recoilを用いたTodoアプリの作成を通して、基本的な使い方や仕組みをご紹介させていただきました。 Reduxのように一箇所に状態を集めて管理する方法ではなく、 Atom やSelectorという単位で状態を管理することで更新のたびにアプリケーション全体の状態を上書きする必要がなくなりました。 また、状態の操作をReducerではなく、Hooks API を使って行うのでState側で定義することがかなり減ったと思います。 ただし、懸念点として小規模なアプリケーションではかなり使いやすいですが、大規模なアプリケーションになるとContainer側で状態を操作できるというのは意図しない状態更新を行うことを可能にしてしまうということです。 そのため、直接 Atom やSelectorを操作するのではなく、カスタムフックを用いて操作するなどの対策をとることで大規模なアプリケーションでも安全に使えるのかなと思います。 このような問題点をリファクタ編として、解決策とともに下記の記事にまとめさせていただいたので、お読みいただけると嬉しいです。 tech-blog.rakus.co.jp ここまで読んでいただきありがとうございました。 この記事がRecoilを使いたい方や、Reactの状態管理ライブラリについて知りたい方の助けになれれば幸いです。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
こんにちは!2022年度新卒で楽楽精算開発課に配属されましたtarayamaaaと申します。 今回は私たち 楽楽精算開発課に配属された新卒が実務に入る前に行う学習メニュー について紹介させていただこうと思います。 こちらの学習メニューの一部は、新卒だけでなく中途の方も行う内容となっております。 そのため、学生や新卒の方だけでなく中途の方にも参考になりましたら幸いです。 目次 目次 ラクスにおける新卒研修について 楽楽精算開発課の学習メニュー 主な学習の流れ 学習の具体的な内容 その他の学習メニュー(抜粋) 実際に取り組んでみて 終わりに ラク スにおける新卒研修について 基本的に ラク スに入社した新卒は、4月から6月まで東京と大阪を含めた新卒全員で本社で研修を受けることになります。 おおよそ4月の中旬までビジネスマナーや商材の研修を受講し、その後6月末まで Java などの基本的な技術の研修を受講する流れとなっています。 ※ ラク スの新卒研修の詳細については、以下の記事をご覧ください。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp 楽楽精算開発課の学習メニュー 上記の研修を受講した後はいよいよそれぞれの部署に配属され、配属先で使う技術についての学習メニューを受講後実務に入るといった形になっています。 配属先によって使用する言語であったり技術スタックが微妙に異なるため、そのあたりの知識や業務のための ドメイン 理解に近い内容を学ぶことになります。 楽楽精算開発課では主に以下のような流れの学習メニューを導入しています。 主な学習の流れ 楽楽精算にどのような機能があるのかを知る 楽楽精算の何が楽なのかを体験する 楽楽精算の開発を疑似体験する 学習の具体的な内容 楽楽精算にどのような機能があるのかを知る サポートサイト学習 楽楽精算のサポートサイトと楽楽精算の検証環境をもとに演習形式で楽楽精算にどのような機能があるのか、楽楽精算のたくさんあるオプションも含めた機能で様々な会社のケースに対応するにはどのように設定や機能を使用すると手間なく精算・ 経理 作業を行うことができるのかといったことを学びます。 最終的に用意されたテストを9割解けるようになるまで学習することになるのですが、ほぼ確実に楽楽精算のサポートサイトで存在するほぼすべてのページを読んで理解することになります。 (ただし、ただ読むだけではなく実際に検証環境があるため、自分の感じた疑問点を実際に動かして確認しながら進めていくことができます。) 楽楽精算の何が楽なのかを体験する 楽楽精算の意義理解 製品説明資料作成 楽楽精算の意義理解とは、その名の通り楽楽精算の意義の理解を目的として、楽楽精算を使用した場合と使用しなかった場合の両方を体験し、どのような点で楽楽精算が精算・ 経理 作業が楽になるように貢献しているのかを学びます。 実際に会議室でメンターの先輩方にご協力いただいてお題として用意された仮の顧客訪問をもとに精 算額 の算出から仕訳・確定作業までを楽楽精算を使用せずに行い、 その後楽楽精算で同じ作業を行うことによって、サポートサイトで学んだ機能によって誰がどのように楽になるのかといった点を顧客視点で理解します。 また、最後に学びのまとめとして製品の説明資料を5時間で作成し、課の先輩方に10分間の発表を行います。 ここでは理解の確認だけでなく、先輩方から質問も受けるため理解の甘い点についても考えを深めることができます。 楽楽精算の開発を疑似体験する 開発の疑似体験 既存の楽楽精算に実際に機能を追加し、 JUnit を用いたテストコードの作成や 単体テスト までを疑似的に体験します。 同様の機能のコードや仕様書を参考に実装し、楽楽精算の権限管理やクラスの 命名規則 といった開発のルールや仕組みの理解とともに、実際に今後所属するチーム内でどのような流れで開発が行われているのかを習得することを目的としています。 機能の追加後はメンターの先輩にマージリク エス トベースでコードレビューをしていただき、考慮漏れがないように確認をもらいOKが出れば 単体テスト に取り掛かります。 単体テスト 項目書は個人ごとに書き方などに差がでやすいものであるため、配属されるチームの手法に合わせるために因子水準の抽出からテストパターンの作成・テストの実行を逐次レビューをいただきながら行います。 このようなメニューを行うことで、実務に入った後にスムーズに仕事の流れをつかむことができるようになります。 上記以外にも新卒配属では、以下のような Java や PostgreSQL の資格の取得など様々な項目について学びます。 その他の学習メニュー(抜粋) 資格取得 Java Silver OSS-DB Silver 技術スキル Webアプリケーション( TCP/IP / HTTP / SSL / Cookie 等) Webサーバ / フレームワーク (楽楽精算の アーキテクチャ 関連) セキュリティ Linux Jenkins Git JUnit Selenium など ヒューマンスキル 課題図書 入社1年目の教科書 以下のうち1冊 3分でわかるロジカル・シンキングの基本 問題解決力を鍛えるト レーニン グブック エンジニアのための伝わる書き方講座 など 実際に取り組んでみて サポートサイト課題 楽楽精算のサポートサイトのほぼすべてのページを読むことになるので、読み終えるまでものすごく時間がかかりました。 時間はかかりますが、読む前と読んだあとではその後の作業の際に初めて使う機能がうまく動かないといった場面でもなんとなくどこの設定が足りないのか、どこを設定すれば使えるようになるのかといった点が以前に比べて推測できるようになったため、製品理解を深めることができました。 細かなオプション機能の使い方などはまだ一部知識が甘いですが、この学習メニューのおかげで通常の精算作業であれば基本的に問題なく行えるようになったと思います。 楽楽精算の意義理解 & 楽楽精算製品説明資料作成・発表 入社して以降楽楽精算でしか精算処理を行ったことがないことと、 経理 作業を実際に行ったことがなかったため、なんとなく頭で楽楽精算を使わないと不便ということは理解していてもどのような点でどのような不便を解決しているのかといった詳細なポイントまでをきちんと理解していませんでした。 そのため、この学習メニューを通してどのような箇所でどの程度時間がかかるのかであったり、ヒューマンエラーがどのポイントで発生しやすいのかといった知識を吸収できた点で非常に面白い体験でした。 楽楽精算の開発の疑似体験 楽楽精算がどのように権限管理をしているのかであったり、複雑なクラス構造などを実務に入ってぶっつけ本番で学ぶのではなく、実務の前に練習として取り組めたという点でとてもありがたい内容でした。 実際に取り組んでみてミスに対する リカバリ ーに時間がかかりすぎてしまったり、テスト項目書を作成する際に不要な因子を追加することによって大量のテストケースになってしまったりしたのですが、実務に入る前にインプットだけでなくアウトプットとして学べてよかったです。 終わりに 以上が楽楽精算開発課における配属後の学習メニューの紹介になります。 体系的にメニューが組まれており、非常にスムーズに楽楽精算の製品理解を進めることができました。 また、楽楽精算開発課では新卒にはメインとサブの2人のメンターについていただけるので、詰まるところや質問があるとすぐ対応していただけてとてもありがたく感じました。 配属されてから学んできたことを今後の実務に活かすとともに、昨年の先輩がもっと時間をかけて学びたかった!という観点でおととしの内容からアップデートされたように、私たち今年度の新卒が感じた点をを来年、再来年の後輩のためにもっと改善していけたら良いなと思います。 ありがとうございました。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
はじめに こんにちわ。cappy_potterです。 MailDealer と ChatDeaeler という弊社サービスのインフラ運用チームのリーダを担当しています。 前回、 こちらの記事 で、 『チームとして障害対応時間削減に向けて取り組んだこと』 について 紹介させていただきました。 tech-blog.rakus.co.jp その際、記事の中で、取り組み実施後に同様の障害が発生したことについて触れ、取り組み実施前に比べて 関係者への情報共有の時間をおよそ半分にできたと記載しました。 ( 以前は同様の障害で「42分」かかっていたものが、「22分」に短縮できた。 ) あれからさらに半年が経過したところで、再度大きな障害が発生したのですが、今回については 効果的に動くことができず、 サービス復旧までに多大な時間を要してしまいました 。 ( 関係者への情報共有についても、障害発生から「 30分 」かかってしまいました。 ) なぜ今回の障害については迅速に動くことができなかったのか?ということと、今後どうすればよいのか ということについて、チーム内で振り返りを行った結果を中心にお話させていただきたいと思います。 はじめに 発生した障害の内容について 対応に時間がかかった要因 要因への対策 【要因①への対策】 【要因②への対策】 その他の対応について 発生した障害の内容について 平日の朝8時前に、外部からの DDoS攻撃 を防御するためのセキュリティ機器が 突如誤作動を起こし、正常な通信をブロックしてしまうという事象が発生しました。 これにより、弊社がサービス提供を行っている多数のサーバについて、外部からアクセスしづらい 状態が発生してしまいました。 また、このような状況が起こっているということを把握し、 サービス復旧のための対処を 行うまでに「 86分 」という時間を要してしまいました。  ※サービス復旧の方法としては、誤作動を起こしていたDDoS対策用機器の電源を落とし、   異常なブロックが発生しないようにする、というものでした。 対応に時間がかかった要因 以前、障害対応時間削減のための取り組みを実施し、実際、その後に発生した同様の障害に ついては、比較的迅速に対応できていたのですが、なぜ今回は時間を要してしまったのか、 チーム内で振り返りを行いました。 その結果、以下のようなことが要因として挙げられました。   ①現状、どこまで対応が進んでいるのか、把握しづらい状況だった    ∟ 誰が何をしているのか、あと何の対応をしなければいけないのか、よくわからない状態だった。    ∟ 後から対応に参加した人が現状を把握できない状態だった。   ②アラート検知しているサーバの共通項の絞込みに時間がかかり、障害箇所の特定に時間がかかった    ∟ どの仮想基盤上で稼働しているか、どのネットワーク機器を経由しているか、など。   ③コミュニケーションツール(Zoom)の準備が遅かった    ∟ 障害発生の時間が、出勤前・出勤中の時間帯(平日の朝8時前)であったことから、      まずはチャットベースでやり取りを開始しており、そのままの流れでずっと対応してしまっていた。     (テキストベースのツールだと、やり取りに時間がかかる)   ④障害対応の司令塔が情報共有・報告者を兼ねていて、メンバへの指示が後手後手になっていた。    ∟司令塔自身が、関係者への情報共有のための文章を考え、入力するのに時間を取られていた。   ⑤他部署の関係者が障害対応メンバの手を止めてしまっていた    ∟ 情報共有が適切にできていなかったため、他部署の関係者が障害対応を行っているメンバに対して      直接状況確認を行おうとて、対応の手を一時的に止めてしまう状況が発生していた。   ⑥役割分担の際、2人に対して同じ役割を分担したことにより、混乱が生じてしまった    ∟ 役割としては、ざっくりとしたものになっているため、具体的な実施内容については      2人で相談した上で進めてほしかったが、うまくいかなかった。   ⑦指示されたことと異なる対応を行っている者がいた    ∟ 似たような役割(「障害発生箇所調査」と「影響範囲調査」)があることにより、     見間違えが発生していた。 要因への対策 前項にて、対応に時間がかかった要因の洗い出しを行いましたが、全てに対応しようとすると 対策実施までに時間がかかりそうなため、ポイントを絞って対応することとします。 具体的には、要因①②に対し、以下のように対応する予定です。 なお、以下の対策を実施することにより、要因④~⑦についても、いくぶん改善できると 見込んでいます。 【要因①への対策】  ・具体的にやるべきこと、確認すべきことなどを箇条書きにしたリストを作成する。    ∟ 基本的に、上から順に実施していくものとする。    ∟ リストには「対応者」欄、「実施状況」欄を設け、リアルタイムに更新していく。    ∟ リストは スプレッドシート で作成することにより、複数人での同時編集を可能とする。    ∟ 障害対応開始時、まずこのリストを関係者で共有する。     (これを見れば、どこまで進んでいるのかがわかるようにする。)  ・調査結果の保存場所も、上記リストに記載しておくことにより、調査結果(ログなど)を   どこに格納するかを迷う時間や、パスを共有する時間を削減する。 【要因②への対策】  ・現状、各サーバごとのOSや ミドルウェア のバージョン、 IPアドレス 等の情報を管理するための   データベースがあるため、「どの仮想基盤上で稼働しているか」「どのネットワーク機器を   経由しているか」などの情報を記載するための項目を追加し、絞込みが行えるようにする。    ∟ これにより、障害発生箇所の推測を早める。   ※上記データベースは、弊社の 楽楽販売 を利用しています。 その他の対応について 前回の記事の中で、サービス復旧を早めるための取り組みとして、以下の2点について 実施予定であると記載しましたが、こちらの状況について報告します。 ●各機器への疎通・ステータス確認、サーバの正常性確認の自動化   → 弊社で稼動中のjenkinsサーバにて、あらかじめ以下を登録しておき、ワンクリックで     確認できるようにしました。    ・主要機器に対する Ping 疎通確認用ジョブ    ・ Firewall のログ確認用ジョブ(エラーログ確認用)    ・スイッチのステータス確認用ジョブ    ・サービス提供用サーバのWeb管理画面ログインテスト用ジョブ ●アラート検知を契機とした自動復旧の仕組み作り   → 一部のサーバについて、Zabbixサーバでサービス停止を検知した際に自動的に     サービス再起動コマンドが実行されるようにしました。 以上、最後までお読みいただき、ありがとうございました。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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年目の koki _matsuraです。 本日は、 Redux-Toolkitの基本的な状態管理や仕組み をTodoアプリ作成を通して、ご紹介させていただきます。 こちらの記事は 「Reactの状態管理ライブラリ基礎学習」の2部目 です。 前回の「Redux編」を読んでいない方は下記のリンクからお読みいただけると嬉しいです。 Reduxの仕組みを知ることでよりRedux-Toolkitの使いやすさが理解できると思います。 tech-blog.rakus.co.jp Reactの状態管理ライブラリを勉強している方、状態管理ライブラリについて簡単に知りたい方などのお役に立てればなと書かせていただきました。 アジェンダ は以下の通りです。 Redux-Toolkitとは 概要 構成図 Todoアプリ作成 仕様説明 プロジェクト作成 初期設定 ディレクトリ構成 Todo型の定義 Sliceの定義 Storeの定義 TodoContainer.tsxの定義 TodoPresenter.tsxの定義 Providerの定義 アプリの起動 Todoの追加機能 Todoの削除機能 完了・未完了の切り替え機能 終わりに Redux-Toolkitとは 概要 名前の通り、Reduxを用いた開発を効率的に行うためのツールキットです。 Reduxと比べて、最大のメリットはコード量が減ることです。詳しくは下の構成図で説明させていただきます。他にも、可読性が上がることやTypeScriptとの相性がいいこともメリットです。 Reduxの公式はRedux-Toolkitの記述法を標準にしてほしく、使用することを強く勧めています。 今後、Reduxを導入したい方はRedux-Toolkitで始めると簡単に状態管理できると思われます。 構成図 Redux-Toolkitでは下図のように状態管理をしています。 Reduxと比べると、少しシンプルになっているのがわかります。具体的にいうと、ActionCreatorがなくなりました。実際になくなっている訳ではないのですが、ユーザが意識する必要がなくなりました。 また、それぞれの機能をSliceという単位で切り分けます。Sliceの中には機能ごとのState、Reducerを管理することができるので、Reduxと比べて、管理する状態が増えてもコードの見通しが悪くなりにくいです。 Sliceの中にActionCreatorもあるのですが、先ほども書いた通り、意識する必要がなくなるので省いています。 Redux-Toolkitの良さはこれだけでも十分なのですが、個人的に一番メリットに感じているのはStateのイミュータブル性を意識しなくていいことです。ReduxではStateの更新方法が直接変更するのではなく、新しいStateを作り出して返すというもので、コードで書いてみると躓きやすいです。ですが、イミュータブル性を意識しなくていいのでStateを直接変更するような方法で更新することが可能になります。 これに関しては、実際にコードを書くと有り難みが身に沁みます...。 Todoアプリ作成 仕様説明 Todoアプリを作成する前にTodoアプリの仕様と構成を説明します。 構成は以下の画像のようになります。 入力フォームと送信ボタン、Todoのリストを載せる部分で構成されます。 また、それぞれのTodoには内容に加え、完了ボタン、削除ボタンがあります。 仕様を説明します。 ・Todoの追加 画像上部のタイトル・内容の入力フォームに適当なテキストを入力し、送信ボタンを押すことでTodoリストに入力したTodoが追加されます。 ・Todoリストの表示 画像下部のTodoリストは古いもの(ID昇順)から順に表示されます。最も新しいものは最後尾に表示されます。 ・Todoの完了 それぞれのTodoについている完了ボタンを押すと、該当するTodoが未完了から完了に変化します。 また、完了しているTodoには「戻す」ボタンが表示されており、これは完了ボタンの逆の働きをします。 ・Todoの削除 それぞれのTodoについている削除ボタンを押すと、該当するTodoがリストから削除され、表示からも消えます。 以上が今回作成していくTodoアプリの仕様になっています。 プロジェクト作成 プロジェクトの作成は下記のコマンドを入力します。 私はプロジェクト名を「redux-toolkit-todo」としましたが、お好きなプロジェクト名をつけていただいて問題ありません。 npx create-react-app [プロジェクト名] --template typescript 初期設定 Redux-Toolkitを用いて、開発するには「react-redux」「@reduxjs/toolkit」を入れなければなりません。 下記のコマンドでプロジェクトに入り、それらのライブラリを入れます。 cd redux-toolkit-todo npm i react-redux @reduxjs/toolkit ディレクト リ構成 Redux-Toolkitを用いた時のsrcは以下のような ディレクト リ構成にします。 app ディレクト リとcommon ディレクト リ、features ディレクト リ、features ディレクト リの中にtodos ディレクト リを作成します。 app ディレクト リ App. tsx を移動 store.tsを新規作成 「App. tsx 」を移動させた理由として、ReduxのStoreにアクセスできるのはProviderで囲われた コンポーネント だけで、「App. tsx 」の中身を囲って、Todoアプリ全体で状態を共有したかったからです。同じ ディレクト リに移動させることでどの コンポーネント でProviderが使われているか分かりやすくなります。 common ディレクト リ todo.type.tsの新規作成 rootState.type.tsの新規作成 「todo.type.ts」は今回のTodoアプリで出てくるTodoのタイプを定義し、「rootState.type.ts」には現在のStateのタイプを定義しています。色々なファイルから使われると思われるのでcommon ディレクト リに作成しました。 features/todos ディレクト リ todoSliceの新規作成 TodoContainer. tsx の新規作成 TodoPresenter. tsx の新規作成 Reduxと違う構成をしているのはtodos ディレクト リ内だけです。 「todoSlice.ts」はReduxで言うと、State・Reducer・Actionを一つにまとめたようなものです。 「TodoContainer. tsx 」はTodoアプリのロジック部分を、「TodoPresenter. tsx 」は表示部分を担当します。 Todo型の定義 「todo.type.ts」にTodo型を記述します。 //todo.type.ts export type Todo = { id : number , title : string , content : string , isCompleted : boolean } Sliceの定義 Sliceを定義していきます。 Sliceの中にはState、Reducer、Actionを記述します。 Stateには適当なデータを2つ入れておきます。 基本的な書き方は以下のようになります。 //todoSlice.ts import { createSlice } from "@reduxjs/toolkit" ; import { Todo } from "../../common/todo.type" ; const state = { todos: [ { id: 1 , title: "テスト1" , content: "テスト1の内容" , isCompleted: false } , { id: 2 , title: "テスト2" , content: "テスト2の内容" , isCompleted: false } ] as Todo [] } export const todoSlice = createSlice ( { name: 'todoSlice' , initialState: state , reducers: { //Actionを記述する } } ) createSlice関数に、「name」、「initialState(State)」、「reducer」をオブジェクトにして渡しています。 「name」というのは、Reduxでは出てこなかったのですが、Sliceの名前を示します。また、Actionのタイプのprefixとして用いられます。 なので、Redux-ToolkitではあまりActionのタイプを意識する必要がなくなるのです。 これで最も基本的なSliceを定義できます。 Storeの定義 Sliceを定義できたので、次はStoreを定義していきます。 Storeの定義方法もReduxとは少し変わってきます。 次のようにして、作成できます。 //store.ts import { configureStore } from "@reduxjs/toolkit" import { todoSlice } from "../features/todos/todoSlice" export const store = configureStore ( { reducer : todoSlice.reducer } ) configureStore関数の中でreducerにtodoSlice内のReducerを渡すことで登録できます。 configureStore関数に登録するReducerが単数の場合は、それがStoreのルートリデューサーとなります。 複数の場合は、combineReducersでReducerをまとめてから登録することをお勧めします。 また、configureStore関数にはreducer以外にも、middleware、devTools、preloadedState、enhancersもオプションとしてあります。 TodoContainer. tsx の定義 Slice側は仮ではありますが実装できたので、TodoContainer. tsx を定義します。 このファイルではTodoアプリのロジック部分を担当します。 RootState型とTodoPresenterはまだ定義していないのでエラーが出ていても問題ありません。 //TodoContainer.tsx import { useSelector } from "react-redux" export const TodoContainer = () => { const todos = useSelector (( state : RootState ) => state.todos ) const args = { todos , } return < TodoPresenter { ...args } / > } 「rootState.type.ts」に下記のようにRootState型を定義します。 //rootState.type.ts import { store } from "../app/store" ; export type RootState = ReturnType <typeof store.getState > 「store.getState」はインポートしたStoreから全てのStateを取得できます。その型をRootStateに入れています。 今回の場合はtodosのみを管理しているためToDoのリスト型でも問題はなかったのですが、管理する状態が複数になった時のためにこのような型を紹介させていただきました。 この型を「TodoContainer. tsx 」にインポートすれば、RootStateのエラーは消えます。 TodoPresenter. tsx の定義 このアプリではTodoアプリの表示部分を担当します。 Todoリストを表示します。 まずは、色々な機能を作る前に基盤を作りたいので下記のようなコードにします。 //TodoPresenter.tsx import React , { useState } from "react" import { Todo } from "../../common/todo.type" type TodoPresenterProps = { todos : Todo [] } export const TodoPresenter : React.FC < TodoPresenterProps > = ( { todos , } ) => { const [ title , setTitle ] = useState ( "" ); const [ content , setContent ] = useState ( "" ); return ( <> < form > < label > タイトル: < input type= "text" value = { title } onChange = { e => setTitle ( e.target.value ) } / > < /label > < label > 内容: < input type= "text" value = { content } onChange = { e => setContent ( e.target.value ) } / > < /label > < button type= "button" > 送信 < /button > < /form > < div >------------------------ - < /div > < h1 > Todoリスト < /h1 > { todos.map (( todo : Todo )=> { return ( < React.Fragment key = { todo.id } > < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' > { todo.isCompleted ? "戻す" : "完了" } < /button > < button type= 'button' > 削除 < /button > < /React.Fragment > ) } ) } < / > ) } 入力部にはタイトルと内容の入力フォームとまだ機能のついていない送信ボタンを配置しています。 出力部にはTodoリストをmap関数で出力しています。それぞれのTodoにつくボタンも現時点では機能がついていません。 一旦、これで置いておきます。 Providerの定義 Stateを使いたいルート コンポーネント を囲う形で使います。 TodoContainerをルート コンポーネント にStateを使いたいので、「App. tsx 」を次のように書き換えます。 //App.tsx import { Provider } from "react-redux" ; import { TodoContainer } from "../features/todos/TodoContainer" ; import { store } from "./store" ; function App () { return ( < div className = "App" > < Provider store = { store } > < TodoContainer / > < /Provider > < /div > ); } export default App ; アプリの起動 下記のコマンドで起動してみましょう。 npm start 自動で開くと思いますが、開かない方は「 http://localhost:3000/ 」にアクセスしてください。 次のようにStateを定義したときに入れたサンプルデータが2件分、表示されていればうまくいっています。 Todoの追加機能 送信ボタンを押すと、Todoを追加できるようにします。 手順を説明します。 SliceでTodo追加ActionをReducersに加え、そのActionをエクスポート ContainerでTodo追加Actionをインポートし、そのActionに追加したいTodoを入れて、Sliceに流す関数を作成 Presenterで送信ボタン押下時に2で作成した関数を実行する 早速、実装していきます。 Todo追加Actionは「add」という名前にします。 「todoSlice.ts」のsliceのreducersを下記のように書き換えてください。 //todoSlice.ts reducers: { add: ( state , action: PayloadAction < Todo >) => { state.todos.push ( action.payload ) } } Reduxとかなり違った書き方をしたと思いますが、ReduxのReducerとの大きな違いは以下2つが挙げられます。 ・ Switch文による分岐 Reduxではdispatchにより送られてくるActionのタイプをSwitch文で分岐させていたのですが、Redux-ToolkitではSwitch文を書かなくても問題ありません。 ・ イミュータブル性 Reduxは原則としてStateの値は変更してはならず、前のStateにActionを施したオブジェクトを返す仕組みでした。今回のようなものだとそれほど苦労しませんが、ネストが深いオブジェクトの場合はかなり苦労します。 ですが、Redux-ToolkitではImmerというライブラリが変更をイミュータブルにしてくれるので、直接変更するような書き方で問題ありません。 addActionを作成できたので、エクスポートします。「todoSlice.ts」の最後尾に次のコードを追加します。 //todoSlice.ts export const { add } = todoSlice.actions Sliceで追加する処理は書けたので、Containerでの処理を書いていきます。 「todoContainer. tsx 」でエクスポートしたaddActionをインポートし、addActionに追加したいTodoを加えて、Sliceに流す関数を作成します。 todosとargsの間に加えてください。 //todoContainer.tsx const maxID = todos.length ? todos.slice ( -1 ) [ 0 ] .id : 0 ; const dispatch = useDispatch (); const addTodo = ( title: string , content: string ) => { const newTodo : Todo = { id: maxID+ 1 , title: title , content: content , isCompleted: false , } dispatch ( add ( newTodo )) } maxIDはTodoリストの最大のIDを取得してきます。もし、Todoが0個の場合は0を返すようにします。 argsにaddTodo関数を追加して、「TodoPresenter. tsx 」に渡しましょう。 「TodoPresenter. tsx 」では、送信ボタンを押下時にaddTodo関数を実行するようにしたいです。 なので、addTodo関数を実行し、その後に入力内容を空にするsendTodo関数を作成します。その関数を送信ボタン押下時に実行させるように下記のコードを「TodoPresenter. tsx 」に追加します。 //TodoPresenter.tsx const sendTodo = () => { addTodo ( title , content ); setTitle ( "" ); setContent ( "" ); } //省略 < button type= "button" onClick = { () => addTodo ( title , content ) } > 送信 < /button > 送信ボタンを押すことでTodoを追加できるようになっているかと思います。 Todoの削除機能 追加処理と仕組みは同じです。 それぞれのTodoについている削除ボタンを押すと、リストから削除されるようにします。 Todo削除Actionは「remove」という名前にします。 「todoSlice.ts」のsliceのreducersにremoveActionを書き加え、そのActionをエクスポートします。 //todoSlice.ts remove: ( state , action: PayloadAction < number >) => { state.todos = state.todos.filter (( todo ) => todo.id !== action.payload ) } //省略 export const { add , remove } = todoSlice.actions 「TodoContainer. tsx 」にaddTodo関数と同様にremoveActionをインポートし、このActionをSliceに流すremoveTodo関数を作成します。 //TodoContainer.tsx const removeTodo = ( id: number ) => { dispatch ( remove ( id )) } argsにremoveTodo関数を渡して、「TodoPresenter. tsx 」では、削除ボタンを押したときに削除したいTodoのidを引数にしてremoveTodo関数を実行するようにします。 下記のように「TodoPresenter. tsx 」の削除ボタンを変更してください。 //TodoPresenter.tsx < button type= 'button' onClick = { () => removeTodo ( todo.id ) } > 削除 < /button > 削除ボタンを押すことでTodoを削除できるようになっているかと思います。 完了・未完了の切り替え機能 それぞれのTodoについている完了ボタンを押すと、タイトルの横の「未完了」テキストが「完了」テキストになるようにします。また、完了ボタンは「戻る」というテキストのボタンに変化します。 この戻るボタンを押すと、完了ボタンとは逆の操作をします。 今回も手順は同じです。まずは、Sliceのreducersに完了・未完了切り替えActionを作ります。 「updateComplete」という名前にします。Container側から対象のTodoのIDが送られてくることを想定して下記のようにします。 また、エクスポートもしておきます。 //todoSlice.ts updateComplete: ( state , action: PayloadAction < number >) => { state.todos = state.todos.map (( todo ) => todo.id === action.payload ? { ...todo , isCompleted: ! todo.isCompleted } : todo ) } //省略 export const { add , remove , updateComplete } = todoSlice.actions 「TodoContainer. tsx 」にtoggleCompleteActionをインポートし、このActionをSliceに流すtoggleComplete関数を作成します。 //TodoContainer.tsx const toggleComplete = ( id: number ) => { dispatch ( updateComplete ( id )); } argsにtoggleComplete関数を渡して、「TodoPresenter. tsx 」では、完了ボタンを押したときに対象のTodoのidを引数にしてtoggleComplete関数を実行するようにします。 下記のように「TodoPresenter. tsx 」の完了ボタンを変更してください。 //TodoPresenter.tsx < button type= 'button' onClick = { () => toggleComplete ( todo.id ) } > { todo.isCompleted ? "戻す" : "完了" } < /button > 完了ボタンを押すと、それぞれのTodoタイトルの横の「未完了」が「完了」に切り替わることが確認できると思います。 終わりに Redux-Toolkitを用いたTodoアプリの作成を通して、基本的な使い方や仕組みをご紹介させていただきました。 Reduxと比べると、State・Reducer・ActionをSliceで管理するというのが特徴的だったと思います。また、そのおかげでファイル数も少なく、記述量も少なくなりました。 Stateの更新もミュータブルにできるので単純で分かりやすい印象を受けたのではないでしょうか。 ここまで読んでいただきありがとうございました。 第3部ではRecoilの基礎について同じような形でまとめたので、一緒に読んでいただけると嬉しいです。 tech-blog.rakus.co.jp エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
はじめに こんにちは。 開発課のmoryosukeです。 OSS -DBの勉強をする過程で第二、第三正規形ってどんなことするんだっけ?そもそも部分関数従属、推移関数従属ってなんだっけ?と混乱することが多くありました。同じような方がいらっしゃいましたらぜひ参考にしてみてください。 目次 はじめに 目次 正規形とは 非正規形 第一正規形 第二正規形 部分関数従属 第三正規形 推移関数従属 まとめ 正規形とは データの重複をなくし整合的にデータを取り扱えるようにデータベースを設計することを、 データベースの正規化 と呼びます。 正規化を行っておくと、データの追加・更新・削除などに伴うデータの不整合や喪失が起きるのを防ぎ、メンテナンスの効率を高めることができます。 正規化には第一正規形から第五正規形がありますが、ほとんどの場合は第三正規化まで行えば、実務上は問題ないとされています。 そのため、ここでは、第三正規形までを紹介いたします。 非正規形 非正規系は正規化されていないデータです。 非正規形 注文番号 顧客ID 顧客名 電話番号 商品ID 商品名 単価 数量 金額 0001 0001 田中 000-0000-0000 0001 マルゲリータ 1,200 2 2,400 0002 てりやき 1,000 1 1,000 第一正規形 第一正規化は、非正規形のテーブルに次の作業を行います。 主キーを設定する 繰り返し現れる列のデータをグループ化して、別のテーブルに切り離す 導出項目(他の属性から算出できる項目)を削除する 今回の場合 注文番号を主キーとして設定します。 非正規系のテーブルから繰り返し現れる列を切り離し、下記のような「注文明細テーブル」にします。 金額は、単価✕数量から算出できる導出項目であるため、削除します。 注文テーブル 注文番号 (主キー) 顧客ID 顧客名 電話番号 0001 0001 田中 000-0000-0000 注文明細テーブル 注文番号 (主キー) 商品ID (主キー) 商品名 単価 数量 0001 0001 マルゲリータ 1,200 2 0001 0002 てりやき 1,000 1 第二正規形 第二正規形は、第一正規型のテーブルから部分関数従属属性であるものを除きます。 部分関数従属 まず、関数従属とは「ある属性(列)の値Xが決まると、別の属性の値Yが自動的に決まる」という関係です。 そして、部分関数従属は、「XがABからなる場合、AまたはBが決まるとYが決まる」という関係です。 今回の場合、注文明細テーブルの複合主キーの一部である商品IDが決まれば商品名と単価が決まる、部分関数従属となっています。 つまり、複合主キーの一部の列の値から導き出せる列があれば、それらを別のテーブルに分割するということです。 よって、以下の手順で第二正規化が行なえます。 商品名と単価は、第一正規形の注文明細テーブルの複合主キーの一部である商品IDにより決まる部分関数従属であるため、商品IDだけを主キーとして「商品ID→商品名、単価」となるように、「商品テーブル」に分割する 「商品テーブル」と「注文明細テーブル」を関連付けられるように、「注文明細テーブル」のIDは、「商品テーブル」を参照する外部キーとして設定する 注文テーブル 注文番号 (主キー) 顧客ID 顧客名 電話番号 0001 0001 田中 000-0000-0000 注文明細テーブル 注文番号 (主キー) 商品ID (主キー・外部キー) 数量 0001 0001 2 0001 0002 1 商品テーブル 商品ID (主キー) 商品名 単価 0001 マルゲリータ 1,200 0002 てりやき 1,000 第三正規形 第三正規化は、第二正規形から推移従属属性であるものを除きます。 推移関数従属 主キー以外の項目に従属する関係のことで、 「主キーXが決まるとYが決まり、Yが決まるとZが決まる」という関係です。 今回の場合、注文テーブルの非キー属性である顧客IDが決まると、顧客名、電話番号が決まるという関係を指します。 つまり、上記 推移関数従属部分を、顧客IDを主キーとした顧客テーブルに分割することによって、第三正規形となります。顧客テーブルと注文テーブルを関連付けられるように、注文テーブルの顧客IDは、顧客テーブルを参照する外部キーとして設定します。 注文明細テーブルと商品テーブルは、第二正規化からそのままです。 注文テーブル 注文番号 (主キー) 顧客ID (外部キー) 0001 0001 顧客テーブル 顧客ID (主キー) 顧客名 電話番号 0001 田中 000-0000-0000 まとめ 当初1つのテーブルだったものを正規化を行うことで以下の4つのテーブルに整理されました。 注文明細テーブル 注文番号 (主キー) 商品ID (主キー・外部キー) 数量 0001 0001 2 0001 0002 1 商品テーブル 商品ID (主キー) 商品名 単価 0001 マルゲリータ 1,200 0002 てりやき 1,000 注文テーブル 注文番号 (主キー) 顧客ID (外部キー) 0001 0001 顧客テーブル 顧客ID (主キー) 顧客名 電話番号 0001 田中 000-0000-0000 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
こんにちは~nanchiuです。なんなん 私は前職(某SIベンダー)で新卒配属されてから1年半ほど VMware vSphereのテクニカルサポートをしていました。 その時の経験を活かして初心者でもわかるように VMware vSphereの主な機能や少しだけ仕組みに突っ込んだ話をしたいと思います。 この記事の対象者 VMware って何なん!?って感じの初心者 VMware vSphereの機能の概要は知っているけどもう少し仕組みを知りたい人 目次 この記事の対象者 目次 はじめに VMwareとは? 仮想化とは? VMware vSphereのメリット VMware vSphereの主なコンポーネント VMware vSphereの機能 ESXiの主な機能 スナップショット thinディスク オーバーコミット vCenter Serverを利用した主な機能 vMotion Storage vMotion vMotionおよびStorage vMotionの同時実行 仮想マシンテンプレート、クローン High Availability(HA) vSphere Fault Tolerance(FT) Hotadd DRS storage DRS vSAN   はじめに VMware とは? 読み方は ヴイエムウェア です。 VMware は仮想化を実現するための製品、もしくはその開発元である企業名を指します。 VMware の製品は様々なものがありますが、ここでは VMware の主要製品である VMware vSphereの機能や仕組みについてご紹介します。 仮想化とは? 機能紹介の前に、そもそも仮想化とはソフトウェアを利用してサーバなどのハードウェアリソース(CPU、メモリ、HDD)を、 論理的に統合や分割することができる技術のことです。 例えば1台のハードウェア(PCやサーバー)で複数のOSを動作させたりできます。 ソフトウェアによる仮想化は以下の種類があります。 用語について 物理HW:物理的なハードウェア。 ホストOS:ホストとなるOS。物理HWの上で動作する。 仮想HW:ソフトウェアによって作りだされた仮想的なハードウェア。 ゲストOS:仮想HWの上で動作するOS。 仮想マシン :仮想HW、ゲストOSをセットで 仮想マシン と呼びます。英語ではvirtual machineなので VM と呼ぶことも多いです。 ホスト型: Windows や Mac などのOSに専用のソフトウェア( VMware Workstation Player、 VirtualBox など)を入れ、その上でゲストとなるOSを動作させることができます。 ハイパーバイザー型: ハードウェア上にハイパーバイザーと呼ばれるソフトウェアを導入し、その上でOSを動作させることができます。 ホスト型との違いはホストとなるOSがないことです。 これによりホスト型と比べてホストOS分のオーバーヘッドがなくなります。 要するにその分処理が速いということになります。 コンテナ型: 近年よく聞きますよね。上記二つは仮想化ソフトウェアの上でゲストOSを動作させていますが、 コンテナはアプリとそのアプリが必要な ミドルウェア 、ライブラリ等がセットになったもの(コンテナ)を動作させています。 OSを動作させるにはハードウェアリソースを多く消費しますが、コンテナはアプリに必要なリソースしか消費しません。 ここだけ読むとコンテナを使えばええやん?と思うかもしれませんが、それぞれメリデメがありますし、用途によって選択肢が変わってきます。 実は VMware vSphereでコンテナを動かすこともできるのですが、この辺のネタだけでブログが書けそうなので今回はあまり触れないことにします。 VMware vSphereのメリット VMware の主要製品である VMware vSphereは上述したようにハイパーバイザー型の仮想化製品です。 一般的にはサーバーの仮想化として利用します。 サーバーの仮想化では以下のメリットがあります。 1台の物理サーバー上に複数の 仮想マシン を稼働させることができるため物理サーバーの台数が減ります。 このため、物理サーバーの管理コストやラック費用などの設備費用を低減させることができます。 ビジネスの変化に迅速に対応できるようになります。 仮想マシン に対して柔軟にリソースを追加できますし、 ハードウェアの調達を待つことなく新しく 仮想マシン を作ることができます。 レガシーなシステムを延命することができます。古いOSが最新のハードウェアで対応していない場合も 仮想化することで利用できるケースがあります。 さらに、 VMware vSphereが提供する機能で様々な業務の効率化、可用性の向上、コスト削減が見込めます。 利用できる機能については後述したいと思います。 VMware vSphereの主な コンポーネント 機能を紹介する前に前提知識として主な コンポーネント を紹介します。 主な コンポーネント vCenter Server : 管理 コンポーネント 。後述するHAやvMotionを実現するために必要です。 通常、ユーザはブラウザからvCenter Serverに接続してオペレーションを行います。 vCenter ServerはESXi上の 仮想マシン として作成することも可能です。 ESXi : VMware vSphereにおけるハイパーバイザです。主に 仮想マシン への動的なリソース割り当てを制御しています。 ※ストレージはローカルディスクを利用する、共有ストレージ装置を利用する、 VMware vSphereの機能であるvSANを利用する方法があります。 後述しますが、それぞれ利用できる機能が異なります。 VMware vSphereの機能 ESXiの主な機能 vSphereの主要な機能はvCenter Serverを導入することで利用できますが、なしでも使える便利機能を紹介します。 スナップショット スナップショット取得時の 仮想マシン の状態を保存することができます。 ゲストOS内で設定を変えた場合など、以前の状態に戻したい時に便利です。 thinディスク 仮想マシン のディスクをthinで作成しておくと、利用した分だけの容量が消費されます。 例えば、仮想ディスクを100GBで作成しておいても実際に利用している領域が20GBであればその分しか消費されません。 オーバーコミット CPUやメモリを物理サーバーのリソースのキャパシティを超えて 仮想マシン に割り当てることができます。 ただし、状況によっては 仮想マシン の性能が著しく劣化するため設計・運用には注意が必要です。 CPUのオーバーコミット 物理CPU 1コアを1仮想CPUとして マッピング (ハイパースレッディング有効時は論理スレッド) 。 仮想CPUの合計がESXiホストの搭載CPUを超える場合、非常に短い時間単位で交代しながら割り当てられます。 この時、よりCPU負荷の高い 仮想マシン が優先されますが制限や予約、優先度を 仮想マシン 単位でチューニング可能です。 メモリのオーバーコミット メモリもオーバーコミットが可能です。 以下の仕組みでメモリをやりくりしています。 透過的ページ共有(TPS) 仮想マシン 間の同一内容のメモリページを共有します。 ただし、セキュリティ的な懸念からデフォルト無効になっています。 バルーニング ゲストOSのインアクティブなメモリを強制 スワップ アウトさせます。 そうして空いたメモリ領域を別の VM で利用します。 メモリ圧縮 メモリを圧縮します。ディスクに スワップ するよりは1,000倍高速らしいです。 スワップ ディスクにメモリを スワップ します。 ディスクへのアクセスになるためメモリに比べるとめちゃくちゃ遅くなります。 また、メモリも制限や予約、優先度を 仮想マシン 単位で設定できます。 vCenter Serverを利用した主な機能 vCenter Serverを導入することで利用できるvSphereの目玉機能を紹介します。 ※ライセンスによって利用できる機能は変わってくるためその辺は公式サイトなどを参照してください。 vMotion 仮想マシン を起動したままダウンタイムなしで別のESXiホストに移動できる機能です。 いわゆるライブ マイグレーション のことです。 メモリの情報をコピーするのでOS上の処理もそのままの状態で移動できます。 物理的に違うホストに移動するのになぜダウンタイムなしで移動できるの!?と思ったかもしれませんが大まかな仕組みは以下です。 前提として、仮想ディスクは各ESXiホストからアクセス可能なストレージ(共有ストレージやvSAN)に格納されている場合の動作になります。 ①メモリをNW経由でコピー 移行元が稼働した状態ですので、都度メモリの更新が入りますが移行元と移行先で差分がなくなるまで転送します。 当然、ネットワークが遅かったり、移行元のメモリが頻繁に更新される場合はvMoitonの時間が長くなったり最悪失敗したりします。 ②移行先ESXiで対象 仮想マシン のファイルをロック 仮想マシン は カプセル化 (ファイル化)されています。 複数のESXiで更新しないよう単一のESXiで 仮想マシン のファイルはロックされており、ここでそれが移動先のESXiに切り替わります。 ③ RARP をL2スイッチに送信し、 MACアドレス テーブルを更新 NW経路の変更は TCP セッションより下の階層で処理されるため切断されません。 Storage vMotion 仮想マシン を停止することなく、ストレージデータ(仮想ディスク)を移動することが可能です。 ストレージのタイプに依存しないためローカルディスク、ストレージ装置間の移行もできます。 例えばストレージ装置のメンテナンス時や移行に利用できます。 vMotionおよびStorage vMotionの同時実行 共有ストレージ不要で別のESXiホストへオンラインのまま 仮想マシン とそのデータを移動できます。 仮想マシン テンプレート、クローン テンプレート: 仮想マシン からテンプレートイメージを作成し、複数の 仮想マシン に展開できます。 クローン: 仮想マシン をクローン(複製)できます。起動状態でも可能です。 同等の構成のサーバを複数作成したい場合はこれらの機能が役立つかと思います。 High Availability(HA) 物理サーバの障害で 仮想マシン が停止しても、リソースの空いているサーバで自動的に 仮想マシン を再起動(無停止ではない)します。 ダウンタイムを最小にすることが目的の機能です。 HAの設定にはvCenter Serverが必要ですが、ESXi間でハートビートにより死活監視をしているためvCenter Server障害時でも HAの機能は発動します。 vSphere Fault Tolerance(FT) 本番環境として稼働している 仮想マシン と同じ環境を、別のESXi上に復旧用の 仮想マシン としてコピーしています。 メモリの情報もフルsyncしているのでハードウェア障害時にも一切のダウンタイムなく、 仮想マシン をフェールオーバーする事が可能です。 Hotadd 仮想マシン を停止せずにデ バイス を追加認識させる機能です。 起動状態のままCPUなど追加できますが、ゲストOSが機能に対応している必要があります。 DRS 特定のESXiに負荷が集中した際、自動的に 仮想マシン を別のリソースの空いているESXiにvMotionで移動させ、 全体のロードバランスを行います。 移動対象はポリシーで設定できるため、例えばこの 仮想マシン とこの 仮想マシン は同一ESXiで稼働させない といった制御も可能です。 storage DRS I/O負荷に応じて最適なストレージへ 仮想マシン の元データを移動し、負荷を分散します。 vSAN 図は論理的なイメージです。 ローカルストレージを仮想化し複数の筐体で単一(共有)のデータストアを作成できます。 なお、vSAN自体はESXiに機能が含まれていますが、利用するにはvSAN用のライセンスが別途必要です。 柔軟に拡張でき、共有ストレージ装置を構築する必要がない。(vSANのみで共有ストレージ必須のHAなどの機能が利用可能) 必要なデータを他のESXサーバへ多重コピーするので RAID は不要。 vSANを利用すると 仮想マシン はESX1で動作しているが、 仮想マシン のデータはESXi2とESXi3に保存されているといった 構成も普通です。 データが自身のホストのローカルディスクにない場合、IOが遅くならないか?という懸念がでるかと思いますが 主に以下の構成により高速なIOを実現しています。 ディスクがキャッシュディスクとキャパシティディスクに分かれています。データの書き込みは高速なキャッシュディスクに。 後から容量の大きなキャパシティディスクに書き込まれます。 10Gbps以上のネットワーク。物理筐体を跨いで書き込み先のESXiのローカルディスクに書き込みを行うためネットワーク帯域は 10Gbps以上である必要があります。 以上で主要機能の説明は終わりです。 どんな機能があるか、どういった仕組みで実現されているのかなんとなくイメージがつきましたでしょうか。 参考になれば幸いです。なんなん   エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 https://rakus.hubspotpagebuilder.com/visit_engineer/ rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
アバター
技術広報の飯野です。 いつも ラク スエンジニアブログをお読みいただき、ありがとうございます! 先日(2022/11/9)開催となりました、今年度5回目の ラク スMeetup。 今回は、 カイゼン /チームビルディング/プロジェクトマネジメント をテーマに開催! 各プロダクト開発に携わる弊社のバックエンドエンジニアの3名が登壇しました。 なお、本イベントは以下のような方にオススメとなっております。 ・ プロダクトライフサイクル に合わせた課題と事例を聞きたい方 ・ SaaS 開発の品質や生産性の向上を目指している方 ・ローンチしたばかりのプロダクトのチームビルディングに悩んでいる方 ・新機能開発で手戻りを防ぎたい方 ・プロジェクトマネジメントの具体的な事例を知りたい方 ・ ラク スのプロダクト、組織に興味がある方 ・ SaaS 開発に携わるエンジニアの話が聞いてみたい方 イベント内容の詳細は以下をご確認ください。 rakus.connpass.com 発表内容のご紹介 新サービスのプロジェクト推進に向けた、トライ&エラー 楽楽精算の開発課題から学ぶ、改善取り組み 大規模案件における手戻りを防ぐ要件定義・開発事例 次回のラクスMeetupは? 当日のタイムテーブル 申し込み方法 ラクスのエンジニア/デザイナーと話をしてみたい方へ 終わりに 発表内容のご紹介 新サービスのプロジェクト推進に向けた、トライ&エラー 登壇:川﨑 智彦 [所属:楽楽明細開発2課/担当プロダクト: 楽楽電子保存 ] speakerdeck.com 発表内容 2022年1月、電子帳簿保存システム「楽楽電子保存」をローンチしました。 私が入社した2022年5月段階では、楽楽電子保存開発は立ち上がってから間もないため手探りで システム開発 を進めている状況でした。 まだまだ試行錯誤中ですが、プロジェクト全体で取り組んできた内容についてお話しします。 フロントエンド/バックエンド/デザイナの役割 チーム間のコミュニケーション タスクや進捗状況の 見える化 オフショアチームへの作業分担 楽楽精算の開発課題から学ぶ、改善取り組み 登壇:坂田 光 [所属:楽楽精算開発課/担当プロダクト: 楽楽精算 ] speakerdeck.com 発表内容 楽楽精算開発チームはお客様により良いサービスをお届けすべく機能開発に取り組むとともに、品質や生産性の向上を目指して日々改善を行っています。 本発表では以下内容を中心にご紹介します。 直近の開発事例で浮き彫りになった課題 チーム全体の課題 息が長いサービスであるが故のレガシーな課題 それぞれに対する改善事例 大規模案件における手戻りを防ぐ要件定義・開発事例 登壇:西角 知佳 [所属:楽楽勤怠開発1課/担当プロダクト: 楽楽勤怠 ] speakerdeck.com 発表内容 楽楽勤怠では7月に「 工数 管理」という機能をリリースしました。 工数 管理はそれだけで1つのサービスになるほど作り込みの幅の広い機能であり、限られた期間の中で何を実現して何を実現しないかのスコープの決定が重要となりました。 また、スコープ調整した結果の開発期間は6ヶ月と大規模なものとなり、ビジネスサイドからの実現希望時期に応えるうえで大きな手戻りは許されない状況下での開発となりました。 このような状況で期日内でのリリースを無事完遂した事例を、要件定義者 兼 実装者の視点から紹介します。 イベント当日はたくさんの方にご視聴、そしてコメントやご質問をいただきました。 お申し込み、ご参加いただいた皆さま本当にありがとうございました! 次回の ラク スMeetupは? 次回の ラク スMeetupは、2022/12/7(水)に開催します。 タイトルは 『システムを”楽”に運用したい!〜自動化, CI/CDの道〜』 です。 弊社インフラ開発部のエンジニア3名が登壇します。 当日のタイムテーブル 当日のタイムテーブルは以下の通りです。 1つでもご興味のある内容がございましたら、お気軽にご参加ください。 内容 所属:登壇者 18:50 入室開始(途中参加OK!) 19:00 オープニング 19:10 SRE課が開発中システムのCI/CDで取り組んでいるGitOpsの話 SRE課:今本 光 19:35 ラク スサービスを支えるAnsible活用のこれまでとこれから 大阪インフラ開発課:上畑 圭史 20:00 メール配信サービス「blastmail」の M&A 後の軌跡 ~初めてのシステムに向き合う~ 東京インフラ開発2課/課長:柏木 達仁 20:25 クロージング 申し込み方法 以下3つのメディアをご用意しております。 ◆自社申し込みサイト career-recruit.rakus.co.jp ◆connpass rakus.connpass.com ◆TECHPLAY techplay.jp 皆様のご参加、お待ちしております! ラク スのエンジニア/デザイナーと話をしてみたい方へ 弊社では、一緒に働くエンジニア/デザイナーを積極的に募集しております! 現在募集している職種は、以下サイトよりご確認ください。 career-recruit.rakus.co.jp 「まだ応募する段階では…」 という方は、是非 カジュアル面談 もご検討ください。 【こんな方におすすめ】 ポジションが経験にマッチするか確認したい 働き方/環境・体制/事業・プロダクト/文化/制度を詳しく知りたい 応募前に選考の概要を聞きたい(人物像、基準など) エンジニア・デザイナーの人となりを知りたい 以下申し込みフォームとなります。 rakus.hubspotpagebuilder.com 「イベントに登壇していた社員と話してみたい」 といったご要望がございましたらその旨をご記入の上、ぜひお気軽にお申し込みください。 終わりに ラク スMeetupでは現場最前線のエンジニア/デザイナーから ラク スの SaaS 開発ならではの技術・運用ノウハウや、 新しい取り組みの成果や失敗談、プロダクト開発/運用で得た知見等の技術情報をお届けしております。 今後も様々なイベントを計画しております、ぜひご参加ください。 最後までお読みいただきありがとうございました! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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年目の koki _matsuraです。 本日は、Reduxの基本的な状態管理や仕組みをTodoアプリ作成を通して、ご紹介させていただきます。 この記事は 「Reactの状態管理ライブラリ基礎学習」全3部作の1部目 です。 Reactの状態管理ライブラリを勉強している方、状態管理ライブラリについて簡単に知りたい方などのお役に立てればなと書かせていただきました。 アジェンダ は以下の通りです。 Reduxとは 概要 構成図 Todoアプリ作成 仕様説明 プロジェクト作成 初期設定 ディレクトリ構成 Stateの定義 Reducerの定義 Storeの定義 ActionCreatorの定義 TodoContainer.tsxの定義 TodoPresenter.tsxの定義 Providerの設定 アプリの起動 Todoの追加機能 Todoの削除機能 完了・未完了の切り替え機能 終わりに Reduxとは 概要 JavaScript によるSPAは複雑化し続けており、Reactが導入され、Viewとロジック部分を切り離せはしましたが、State(状態)の管理は開発者に委ねられています。 Reduxでは、このStateの問題に下記の3原則を取り入れ、状態変化の流れを制限することで解決します。 - Single source of truth (ソースは一つだけ) アプリケーションの状態は一つのStore内に一つのオブジェクトでツリー型で格納されます。 状態が一つのStoreで管理されるため、 デバッグ や開発が簡単になります。 - State is read-only (状態は読み取り専用) 状態を変更できるのはActionを持ったオブジェクトのみです。つまり、ビューやコールバックが状態を直接的に変更することはできません。 - Changes are made with pure functions (変更は純粋関数で行われる) アクションがどのようにStateを変更するかはReducerに記述されます。 Reducerは前のStateとActionより、次の状態を返す、副作用のない純粋な関数です。注意点として、状態を変更しているのではなく、新しい状態を返しています。 また、開発の際にはアプリケーションで一つのReducerを用意しておき、巨大化してくればReducerを分割することもできます。ただ、分割方法はユーザで決めなければならないのが欠点かもしれません。 構成図 下図はReduxがどのように状態管理をしているかを簡単に示したものになっています。本来であれば、ComponentとReducerの間には API などの処理を行うMiddlewaresが挟まりますが、今回は省かせていただきました。 ComponentはユーザーのイベントからActionCreatorにActionの生成を依頼し、生成されたActionをReducerに対し、dispatchします。Reducerは前回のStateとdispatchされたActionから新たなStateを作り出し、それをStateに返します。StateはComponentに対して、更新を通知し、新しいStateを取得するという流れになっています。 また、Reduxが参考にしているFluxという デザインパターン ではActionCreatorがActionの生成・Actionのdispatchまでを担当するのが一般的なのですが、ReduxではテストのしやすさからActionCreatorはActionを生成するだけの役割の方がいいかもしれません。 Todoアプリ作成 仕様説明 Todoアプリを作成する前にTodoアプリの仕様と構成を説明します。 構成は以下の画像のようになります。 入力フォームと送信ボタン、Todoのリストを載せる部分で構成されます。 また、それぞれのTodoには内容に加え、完了ボタン、削除ボタンがあります。 仕様を説明します。 ・Todoの追加 画像上部のタイトル・内容の入力フォームに適当なテキストを入力し、送信ボタンを押すことでTodoリストに入力したTodoが追加されます。 ・Todoリストの表示 画像下部のTodoリストは古いもの(ID昇順)から順に表示されます。最も新しいものは最後尾に表示されます。 ・Todoの完了 それぞれのTodoについている完了ボタンを押すと、該当するTodoが未完了から完了に変化します。 また、完了しているTodoには「戻す」ボタンが表示されており、これは完了ボタンの逆の働きをします。 ・Todoの削除 それぞれのTodoについている削除ボタンを押すと、該当するTodoがリストから削除され、表示からも消えます。 以上が今回作成していくTodoアプリの仕様になっています。 プロジェクト作成 プロジェクトの作成は下記のコマンドを入力します。 私はプロジェクト名を「redux-todo」としましたが、お好きなプロジェクト名をつけていただいて問題ありません。 npx create-react-app [プロジェクト名] --template typescript 初期設定 プロジェクト作成後、Reduxを使うために必要になるので、下記のコマンドで作成したプロジェクトに移動して、reduxとreact-reduxをインストールします。 cd redux-todo npm i redux react-redux ディレクト リ構成 Reduxを使う準備もできましたので、次は ディレクト リを構成します。 Reduxにおける ディレクト リ構成は様々ありますが、今回は役割がわかりやすいように store action(actionCreator) state reducer に分けた構成にします。 「src」 ディレクト リ以外は特に触らないので、「src」以下の画像を載せます。 app ディレクト リとcommon ディレクト リ、features ディレクト リ、features ディレクト リの中にtodos ディレクト リを作成します。 app ディレクト リ App. tsx を移動 store.tsを新規作成 App. tsx を移動させた理由として、ReduxのStoreにアクセスできるのはProviderで囲われた コンポーネント だけで、「App. tsx 」の中身を囲って、Todoアプリ全体で状態を共有したかったからです。同じ ディレクト リに移動させることでどの コンポーネント でProviderが使われているか分かりやすくなります。 common ディレクト リ todo.type.tsの新規作成 rootState.type.tsの新規作成 「todo.type.ts」は今回のTodoアプリで出てくるTodoのタイプを定義し、「rootState.type.ts」には管理している全てのStateのタイプを定義しています。色々なファイルから使われると思われるのでcommon ディレクト リに作成しました。 features/todos ディレクト リ todoAction.tsの新規作成 todoReducer.tsの新規作成 todoState.tsの新規作成 TodoContainer. tsx の新規作成 TodoPresenter. tsx の新規作成 「todoAction.ts」は構成図で表すと、ActionCreatorの役割を果たします。 「todoState.ts」は状態の定義、「todoReducer.ts」はActionを受けて、状態を変更する役割を果たします。 「TodoContainer. tsx 」はTodoアプリのロジック部分を、「TodoPresenter. tsx 」は表示部分を担当します。 Stateの定義 最初はStateを定義します。 Stateは状態のことです。 StateはTodoのリストを管理するので、Todo型の配列を初期値にします。 Todo型はまだ定義していないので、「todo.type.ts」に下記のように定義します。 //todo.type.ts export type Todo = { id : number ; title : string ; content : string ; isCompleted : boolean ; } それぞれのTodoは「id」「title」「content」「isCompleted」を持ちます。 Todo型を定義できたので、Stateを「todoState.ts」に下記のように定義します。 適当なデータを2つ入れておきます。 //todoState.ts import { Todo } from "../../common/todo.type" ; export const state = {[ { id: 1 , title: "テスト1" , content: "テスト1の内容" , isCompleted: false } , { id: 2 , title: "テスト2" , content: "テスト2の内容" , isCompleted: false } ] as Todo []} Reducerの定義 Stateが定義できたので、Reducerを定義します。 今回のTodoアプリの仕様では、「追加」「削除」「完了・未完了のスイッチ」の機能が必要ですが、一旦、何もしないReducerにしておきます。 //todoReducer.ts import { state as initialState } from "./todoState" ; export const todosReducer = ( state = initialState , action: any ) => { return state } Reducerでは、第一引数に前のState、第二引数にActionを受け取ります。 ActionはActionCreatorで作成されるもので「type」を必ず持っており、必要に応じて、「payload」を持ちます。 Storeの定義 状態管理の元となるStoreを定義します。「store.ts」に下記のように書きます。 //store.ts import { legacy_createStore as createStore } from 'redux' import { todosReducer } from '../features/todos/todoReducer' export const store = createStore ( todosReducer ) createStoreに引数でReducerを入れることでstoreが出来上がります。 注意点 : 現在、createStoreは公式から推奨されていないので、1文目のインポート文を入れないとエラーが起きます。 ActionCreatorの定義 ActionCreatorは名前の通り、Actionを作る役割をします。 Actionを作る役割と言っても、typeとpayloadをオブジェクトで返すだけです。 typeの名前とpayloadの型がReducerのものと合わせる必要がありますが、まだReducerの方で処理を書いていないので、こちらを基準にしていきます。 下記のようにしましょう。 //todoAction.ts import { Todo } from "../../common/todo.type" ; /** Todoを加えるアクションを返す */ export const addTodoAction = ( newTodo : Todo ) => { return { type : "ADD" , payload: newTodo } } /** Todoを変更するアクションを返す */ export const toggleCompleteAciton = ( id : number ) => { return { type : "TOGGLE_COMPLETE" , payload: id } } /** Todoを削除するアクションを返す */ export const removeTodoAction = ( id : number ) => { return { type : "REMOVE" , payload: id } } TodoContainer. tsx の定義 このファイルではTodoアプリのロジック部分を担当します。 useSelectorを使うことでStateを取得できます。Stateの型はRootStateという名前にします。 RootStateとTodoPresenterはまだ定義していないためエラーが出ていても問題ありません。 //TodoContainer.tsx import { useSelector } from "react-redux" export const TodoContainer = () => { const todos = useSelector (( state: RootState ) => state.todos ) const args = { todos , } return < TodoPresenter { ...args } / > } 「rootState.type.ts」に下記のようにRootState型を定義します。 //rootState.type.ts import { store } from "../app/store" ; export type RootState = ReturnType <typeof store.getState > 「store.getState」はインポートしたStoreから全てのStateを取得できます。その型をRootStateに入れています。 今回の場合はtodosのみを管理しているためToDoのリスト型でも問題はなかったのですが、管理する状態が複数になった時のためにこのような型を紹介させていただきました。 このRootState型をTodoContainerにインポートすれば、型エラーはなくなります。 TodoPresenter. tsx の定義 このアプリではTodoアプリの表示部分を担当します。 Todoリストを表示します。 まずは、色々な機能を作る前に基盤を作りたいので下記のようなコードにします。 //TodoPresenter.tsx import React , { useState } from "react" import { Todo } from "../../common/todo.type" type TodoPresenterProps = { todos : Todo [] } export const TodoPresenter : React.FC < TodoPresenterProps > = ( { todos , } ) => { const [ title , setTitle ] = useState ( "" ); const [ content , setContent ] = useState ( "" ); return ( <> < form > < label > タイトル: < input type= "text" value = { title } onChange = { e => setTitle ( e.target.value ) } / > < /label > < label > 内容: < input type= "text" value = { content } onChange = { e => setContent ( e.target.value ) } / > < /label > < button type= "button" > 送信 < /button > < /form > < div >------------------------ - < /div > < h1 > Todoリスト < /h1 > { todos.map (( todo : Todo )=> { return ( < React.Fragment key = { todo.id } > < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' > { todo.isCompleted ? "戻す" : "完了" } < /button > < button type= 'button' > 削除 < /button > < /React.Fragment > ) } ) } < / > ) } 入力部にはタイトルと内容の入力フォームとまだ機能のついていない送信ボタンを配置しています。 出力部にはTodoリストをmap関数で出力しています。それぞれのTodoにつくボタンも現時点では機能がついていません。 一旦、これで置いておきます。 Providerの設定 必要なファイルはすべて完了しました。しかし、これだけでは状態を管理できません。 ReduxではStateを共有したい コンポーネント をProvdierで囲むことで機能します。 今回の場合では、TodoContainer内だけでStateを共有したいです。 なので、「App. tsx 」の元のコードを消して、次のようなコードに変えてください。 //App.tsx import React from 'react' ; import { Provider } from 'react-redux' import { store } from "./store" import { TodoContainer } from '../features/todos/TodoContainer' ; function App () { return ( < div className = "App" > < Provider store = { store } > < TodoContainer / > < /Provider > < /div > ); } export default App ; これでTodoContainer内でStateの情報を共有できるようになりました。 アプリの起動 下記のコマンドで起動してみましょう。 npm start 自動で開くと思いますが、開かない方は「 http://localhost:3000/ 」にアクセスしてください。 次のようにStateを定義したときに入れたサンプルデータが2件分、表示されていればうまくいっています。 Todoの追加機能 送信ボタンを押すと、Todoを追加できるようにします。 手順を説明します。 ContainerでTodoを加えるActionをReducerに流す関数を作成 Presenterで送信ボタン押下時に1で作成した関数を実行するコードを加える ReducerでActionに応じた処理をするコードを加える Todoを加えるActionをReducerに流す関数の名前は「addTodo」にします。次のコードを「TodoContainer. tsx 」のtodosとargsの間に加えてください。 //TodoContainer.tsx const maxID = todos.length ? todos.slice ( -1 ) [ 0 ] .id : 0 ; const dispatch = useDispatch (); const addTodo = ( title: string , content: string ) => { const newTodo : Todo = { id: maxID+ 1 , title: title , content: content , isCompleted: false } dispatch ( addTodoAction ( newTodo )); } maxIDはTodoリストの最大のIDを取得してきます。もし、Todoが0個の場合は0を返すようにします。 argsにaddTodo関数を追加して、「TodoPresenter. tsx 」に渡しましょう。 「TodoPresenter. tsx 」では、送信ボタンを押下時にaddTodo関数を実行するようにしたいです。 なので、addTodo関数を実行し、その後に入力内容を空にするsendTodo関数を作成します。その関数を送信ボタン押下時に実行させるように下記のコードを「TodoPresenter. tsx 」に追加します。 //TodoPresenter.tsx const sendTodo = () => { addTodo ( title , content ); setTitle ( "" ); setContent ( "" );   } //省略 < button type= "button" onClick = { () => addTodo ( title , content ) } > 送信 < /button > これで、送信ボタンを押下時に、addTodo関数を実行できます。 ReducerでこのAction(type: "ADD", payload: newTodo)に合う処理を書きます。 「todoReducer.ts」を次のように書き換えます。 //todoReducer.ts export const todosReducer = ( state = initialState , action : any ) => { switch ( action. type) { case "ADD" : return { todos: [ ...state.todos , action.payload ] } default : return state ; } } Reducerでは、action.typeを見て、処理を変えます。 action.typeは「todoAction.ts」で定義したものと一致させないといけません。 また、注意点としてReduxの原則にも書いてありますが、Stateを直接変更するのではなく、前のStateとActionから新しいStateを作り出すようにします。これが個人的に少し躓きやすい点かなと思います。 これで、追加の処理が書けました。実際に、入力部にタイトルと内容を入力して送信ボタンを押すと、既存のリストの下に追加されていることが確認できると思います。 Todoの削除機能 追加処理と仕組みは同じです。 それぞれのTodoについている削除ボタンを押すと、リストから削除されるようにします。 「TodoContainer. tsx 」にaddTodo関数と同様にremoveTodo関数を次のように作ります。 //TodoContainer.tsx const removeTodo = ( id: number ) => { dispatch ( removeTodoAction ( id )) } argsにremoveTodo関数を渡して、「TodoPresenter. tsx 」では、削除ボタンを押したときに削除したいTodoのidを引数にしてremoveTodo関数を実行するようにします。 下記のように「TodoPresenter. tsx 」の削除ボタンを変更してください。 //TodoPresenter.tsx < button type= 'button' onClick = { () => removeTodo ( todo.id ) } > 削除 < /button > 削除ボタン押下時に、removeTodoが実行されるようになったので、ReducerでこのAction(type: REMOVE, payload: id)に合う処理を書きます。 次のようにswitch分にcaseを増やすような形で書いてください。 //todoReducer.ts case "REMOVE" : return { todos : state.todos.filter (( todo ) => todo.id !== action.payload ) } これで、削除ボタンを押すと、該当のTodoがリストから消えるようになります。 完了・未完了の切り替え機能 それぞれのTodoについている完了ボタンを押すと、タイトルの横の「未完了」テキストが「完了」テキストになるようにします。また、完了ボタンは「戻る」というテキストのボタンに変化します。 この戻るボタンを押すと、完了ボタンとは逆の操作をします。 「TodoPresenter. tsx 」の完了ボタンとタイトル横のテキストのコードを見てみると、todo.isCompletedで切り替えられることがわかります。 なので、isCompletedを切り替えられる関数を作りましょう。 //TodoPresenter.tsx < div > { todo.title } : { todo.isCompleted ? "完了" : "未完了" } < /div > < div > 内容: { todo.content } < /div > < button type= 'button' > { todo.isCompleted ? "戻す" : "完了" } < /button > まずは、「TodoContainer. tsx 」にtoggleComplete関数を次のように作ります。 //TodoContainer.tsx const toggleComplete = ( id: number ) => { dispatch ( toggleCompleteAciton ( id )) } argsにtoggleComplete関数を追加し、下記のように「TodoPresenter. tsx 」の完了ボタンを押下時にtoggleComplete関数が実行するようにします。 //TodoPresenter.tsx < button type= 'button' onClick = { () => toggleComplete ( todo.id ) } > { todo.isCompleted ? "戻す" : "完了" } < /button > 最後はReducerでActionを受け取り、isCompletedを切り替える処理を書きましょう。 次のコードをswitch文のcaseとして追加することで実装できます。 //todoReducer.ts case "TOGGLE_COMPLETE" : return { todos: state.todos.map (( todo ) => { if ( todo.id !== action.payload ) return todo return { ...todo , isCompleted : ! todo.isCompleted } } ) } 完了ボタンを押すと、それぞれのTodoタイトルの横の「未完了」が「完了」に切り替わることが確認できると思います。 終わりに これで仕様通りのTodoアプリをReduxを使って作成できました。 かなり定義するものが多く、ファイル数が多いなと思われたのではないでしょうか。 私自身も、最初使った時はそのように感じました。 Todoアプリのように小さい規模のものだとReduxは少し冗長的で面倒に感じるのですが、大きな規模のアプリになっていくと、それぞれの役割に細かく分け、単方向なデータフローで状態を管理する構成の恩恵を受けやすくなるのかもしれません。 また、今回はそれぞれの役割が分かりやすくなるようにわざとファイルを細かく分けていたのですが、StateとAction、Reducerは密な関係になることが多いので、一つのファイルで管理することもあります。 今回はTodoアプリを通して、Reduxの基本的な仕組みや特徴を紹介させていただきました。 第2部ではReduxを使いやすくしたRedux-Toolkitについて、第3部では実験段階の状態管理ライブラリRecoilについて同じようにTodoアプリ作成を例に紹介させていただいています。良ければ下記のリンクから「Redux-Toolkit編」「Recoil編」も読んでいただけると嬉しいです。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
はじめに こんにちは C2ZTAk6 です。 日々管理しているシステムで、サービス停止が発生した際、原因究明を行う前に、サービスの自動復旧が出来る仕組みはないかと考えたことはないでしょうか。 今回は、 Zabbixという オープンソース ・ソフトウェア( OSS )のアクションという設定を活用し、サービス停止が発生した際に自動で対象のサービスを復旧出来る仕組み について、お話させて頂ければと思います。 目次 はじめに 目次 Zabbixとは Zabbixのアクションとは Zabbix自動復旧について メリット デメリット Zabbixアクションの設定をしてみよう! 前提条件 sudo権限を付与 アクション設定 テスト 概要 最後に Zabbixとは Zabbixとは、 オープンソース ・ソフトウェア( OSS )の統合監視ソフトウェア です。 サーバの死活監視、リソース(Cpu/Memory/Disk)監視、プロセス監視、ネットワーク監視、ログ監視だけでなく、各種 ミドルウェア まで幅広い監視に対応します。 Zabbixのアクションとは アクションとは、名前の通り「行動」のことを指し、アクションの設定では、主に障害発生時の「異常」をトリガーとして、「自動的に何かをさせる」という設定を行うことが出来ます。 例:「 httpd サービス停止が発生した場合に、アラートメールを運用担当者へ通知する」 Zabbix自動復旧について メリット ①サーバログイン不要! Zabbixアクションを活用し、サービス停止のアラートが発報されたことをトリガーにサービス再起動の仕組みを導入することで、夜間や休日に障害が発生しても、いちいち商用サーバにログインして、復旧手順を元にコマンドを実行し、サービス復旧をさせる必要がなくなる。 ②運用コスト削減! Zabbixアクションで障害が自動的に復旧するので、運用担当者の運用コストを減らすことが出来る。 ③属人化防止! 障害復旧手順が複雑で、障害を解消できる人が属人化していた場合はZabbixアクションの自動復旧の仕組みを導入することで、属人化を防ぐことも出来る。 デメリット ①初期費用発生 Zabbixアクションを活用した自動復旧の仕組みを導入する際は、初期導入のプロジェクト 工数 などの費用がかかる。 ※導入時は、担当者の 工数 が発生し費用はかかるが、導入が完了すれば、以降の運用 工数 削減が見込める。 Zabbixアクションの設定をしてみよう! Zabbixアクションを活用し、 httpd プロセス自動復旧の設定をしていきます! 前提条件 監視サーバ OS: Red Hat 系 ミドルウェア :Zabbixがインストールされていること 監視設定: httpd プロセス監視のアイテム、トリガーが設定されていること 監視対象サーバ OS: Red Hat 系 ミドルウェア :ZabbixAgent、 apache がインストールされていること sudo権限を付与 ・監視対象サーバ側でコマンドを実行 [ root@bweb11 ~ ] # chmod 660 /etc/sudoers [ root@bweb11 ~ ] # echo " zabbix ALL=NOPASSWD: ALL " >> /etc/sudoers [ root@bweb11 ~ ] # echo " zabbix ALL=NOPASSWD: /etc/systemd/system/httpd.service " >> /etc/sudoers [ root@bweb11 ~ ] # chmod 440 /etc/sudoers [ root@bweb11 ~ ] # cat /etc/sudoers | tail -n 2 zabbix ALL =NOPASSWD: ALL zabbix ALL =NOPASSWD: /etc/systemd/system/httpd.service ※最後尾に設定が追加されていることを確認してください [ root@bweb11 ~ ] # cat /etc/zabbix/zabbix_agentd.conf | grep EnableRemoteCommands = 1 EnableRemoteCommands = 1 ※リモートコマンドが実行出来るように、 1 になっていることを確認する  設定が 1 になっていなければ、修正のうえ、zabbix-agentを再起動してください。 アクション設定 ・監視サーバ側でアクションの設定を行っていきます ①設定 > アクション > アクションの作成 を押下。 ②アクション:以下項目を入力 項目 名前: auto restart httpd service ※ 命名規則 については、分かりやすい名前で入力してください。 実行条件:ラベルA、名前:トリガー名含む Apache processes is down ※トリガーとして設定されている情報を入力する必要がある。今回は既にトリガーとして、設定済みとなる「 Apache processes is down 」を入力させて頂きます。 ③実行内容 > 追加を押下。 ④実行内容の詳細:以下項目を入力後、Addを押下。 項目 実行内容のタイプ:リモートコマンド ターゲットリスト: bweb11.mdomain ※上記はテスト機です。こちらについては、各自変更してください。 タイプ:カスタム スクリプト 次で実行:Zabbixエージェント コマンド: sudo systemctl restart httpd ⑤設定完了後、追加を押下。 テスト 概要 監視対象サーバにログインして、 httpd プロセスを手動で停止し、3分後の監視で httpd プロセスが停止したことによるトリガー(「 Apache processes is down 」)発報後、Zabbixアクションが動作し、監視対象サーバの httpd プロセスが起動されていることを確認する。 ①監視対象サーバ側でコマンドを実行 手動で httpd プロセスを停止する [ root@bweb11 ~ ] # systemctl status httpd ● httpd.service - The Apache HTTP Server Loaded: loaded ( /etc/systemd/system/httpd.service ; enabled ; vendor preset: disabled ) Active: active ( running ) since Mon 2022-10-31 17:59:41 JST; 1min 24s ago Process: 1984892 ExecStop =/usr/local/vanguard/apache/bin/apachectl stop ( code =exited , status = 0 /SUCCESS ) Process: 1985084 ExecStart =/usr/local/vanguard/apache/bin/apachectl start ( code =exited , status = 0 /SUCCESS ) Main PID: 1985088 ( httpd ) Tasks: 9 ( limit: 10580 ) Memory: 13 .6M CGroup: /system.slice/httpd.service tq1985088 /usr/local/vanguard/apache/bin/httpd -k start tq1985090 /usr/local/vanguard/apache/bin/httpd -k start tq1985091 /usr/local/vanguard/apache/bin/httpd -k start tq1985092 /usr/local/vanguard/apache/bin/httpd -k start tq1985093 /usr/local/vanguard/apache/bin/httpd -k start tq1985094 /usr/local/vanguard/apache/bin/httpd -k start tq1985095 /usr/local/vanguard/apache/bin/httpd -k start tq1985096 /usr/local/vanguard/apache/bin/httpd -k start mq1985097 /usr/local/vanguard/apache/bin/httpd -k start 10 月 31 17:59:41 bweb11.mdomain systemd [ 1 ] : Starting The Apache HTTP Server... 10 月 31 17:59:41 bweb11.mdomain systemd [ 1 ] : Started The Apache HTTP Server. [ root@bweb11 ~ ] # systemctl stop httpd [ root@bweb11 ~ ] # systemctl status httpd ● httpd.service - The Apache HTTP Server Loaded: loaded ( /etc/systemd/system/httpd.service ; enabled ; vendor preset: disabled ) Active: inactive ( dead ) since Mon 2022-10-31 18:04:37 JST; 1s ago Process: 1985282 ExecStop =/usr/local/vanguard/apache/bin/apachectl stop ( code =exited , status = 0 /SUCCESS ) Process: 1985178 ExecStart =/usr/local/vanguard/apache/bin/apachectl start ( code =exited , status = 0 /SUCCESS ) Main PID: 1985182 ( code =exited , status = 0 /SUCCESS ) 10 月 31 18:01:18 bweb11.mdomain systemd [ 1 ] : Starting The Apache HTTP Server... 10 月 31 18:01:18 bweb11.mdomain systemd [ 1 ] : Started The Apache HTTP Server. 10 月 31 18:04:36 bweb11.mdomain systemd [ 1 ] : Stopping The Apache HTTP Server... 10 月 31 18:04:37 bweb11.mdomain systemd [ 1 ] : httpd.service: Succeeded. 10 月 31 18:04:37 bweb11.mdomain systemd [ 1 ] : Stopped The Apache HTTP Server. ②監視サーバ側で確認 ダッシュ ボードから、設定したアクションが動作したことを確認する ③監視対象サーバ側でコマンドを実行 httpd プロセスが、自動で復旧していることを確認する [ root@bweb11 ~ ] # systemctl status httpd ● httpd.service - The Apache HTTP Server Loaded: loaded ( /etc/systemd/system/httpd.service ; enabled ; vendor preset: disabled ) Active: active ( running ) since Mon 2022-10-31 18:05:18 JST; 31s ago Process: 1985282 ExecStop =/usr/local/vanguard/apache/bin/apachectl stop ( code =exited , status = 0 /SUCCESS ) Process: 1985363 ExecStart =/usr/local/vanguard/apache/bin/apachectl start ( code =exited , status = 0 /SUCCESS ) Main PID: 1985367 ( httpd ) Tasks: 9 ( limit: 10580 ) Memory: 13 .7M CGroup: /system.slice/httpd.service tq1985367 /usr/local/vanguard/apache/bin/httpd -k start tq1985370 /usr/local/vanguard/apache/bin/httpd -k start tq1985371 /usr/local/vanguard/apache/bin/httpd -k start tq1985372 /usr/local/vanguard/apache/bin/httpd -k start tq1985373 /usr/local/vanguard/apache/bin/httpd -k start tq1985374 /usr/local/vanguard/apache/bin/httpd -k start tq1985375 /usr/local/vanguard/apache/bin/httpd -k start tq1985376 /usr/local/vanguard/apache/bin/httpd -k start mq1985377 /usr/local/vanguard/apache/bin/httpd -k start 10 月 31 18:05:18 bweb11.mdomain systemd [ 1 ] : Starting The Apache HTTP Server... 10 月 31 18:05:18 bweb11.mdomain systemd [ 1 ] : Started The Apache HTTP Server. 最後に いかがでしたでしょうか。 今回はZabbixアクションを活用し、サービス自動復旧の仕組みについて、ご紹介させて頂きました。 本記事がITを学ぶ方にとって、少しでも助けになれば幸いです。 最後までお読みいただきありがとうございました。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
はじめに TextField RadioGroup SelectForm CheckboxGroup DatePicker コンポーネント使用側実装例 おわりに 本記事を執筆するにあたって、 マナリンク Tech Blog運営 さんの React Hook Form(v7)を使ったコンポーネント設計案 piyoko さんの MUI v5 + React Hook Form v7 で、よく使うコンポーネント達を連携してみる という記事を参考にさせていただきました。いつも非常にわかりやすい記事をありがとうございます。 はじめに こんにちは、 ラク スフロントエンド開発課の斉藤です。 React Hook Form v7 + MUI v5 + zod v3を使ったよく使う コンポーネント の実装例を調査しており、 こちらの記事 を参考に実装を進めてみました。しかし RadioGroup や DatePicker を atom 化しようとすると何点かハマりポイントがあったので、どなたかの参考になればと思い本記事を執筆するに至りました。 実装例を紹介する前に各 コンポーネント を実装するにあたって考慮したことをまとめておきます。React Hook Form(以下RHF)を用いたうえで扱いやすい コンポーネント はどういったものかを考えたとき、以下の条件を満たすと良いのではないかと考えました。 コンポーネント とバリデーションロジックが切り離されている マナリンクさんの記事のように コンポーネント がview層とロジック層に分かれている コンポーネント を使用する側はnameとRHFのcontrolプロパティを渡すだけで値を管理できる CheckboxGroupはチェックされたCheckboxの値をstring型の配列で返すようにする RadioGroup、SelectFormは選択された値をstring型で返すようにする DatePickerは値をDate型で返す これらを満たすように各 コンポーネント を実装してみたので、紹介していきます。 今回紹介する環境はcreate viteで作っています。 yarn create vite yarn add react-hook-form @hookform/resolvers @mui/material @emotion/react @emotion/styled zod TextField TextField に関しては 参考記事 とほぼ同じ実装です。 import { FormHelperText , TextField as MuiTextField , TextFieldProps as MuiTextFieldProps , } from "@mui/material" ; export type TextFieldProps = MuiTextFieldProps & { inputRef?: MuiTextFieldProps [ "ref" ] ; errorMessage?: string ; } ; export const TextField: React.FC < TextFieldProps > = ( { inputRef , errorMessage , ...rest } ) => { return ( <> < MuiTextField ref = { inputRef } error = { !! errorMessage } { ...rest } / > { !! errorMessage && < FormHelperText error > { errorMessage } < /FormHelperText > } < / > ); } ; import { DeepMap , FieldError , FieldValues , useController , UseControllerProps , } from "react-hook-form" ; import { TextField , TextFieldProps } from "./TextField" ; export type RhfTextFieldProps < T extends FieldValues > = TextFieldProps & UseControllerProps < T >; export const RhfTextField = < T extends FieldValues >( props: RhfTextFieldProps < T > ) => { const { name , control } = props ; const { field: { ref , ...rest } , fieldState: { error } , } = useController < T >( { name , control } ); return ( < TextField inputRef = { ref } { ...rest } { ...props } errorMessage = { ( error && error.message ) || props.errorMessage } / > ); } ; RadioGroup RadioGroup にはRadioPropsListとして value とlabelをプロパティに持つ配列を渡せるようにします。labelが画面に表示される ラジオボタン 横の文言で、 value が実際にRHFが受け取る値になります。例えば ラジオボタン で「りんご」を選択したとき、バックエンド側には「 apple 」と英名で情報を送信したい場合が良くあるのでこのように分けています。 import { FormControl , FormControlLabel , FormHelperText , Radio , RadioGroup as MuiRadioGroup , } from "@mui/material" ; import type { RadioGroupProps as MuiRadioGroupProps } from "@mui/material" ; type RadioProps = { value: string ; label: string ; } ; export type RadioGroupProps = MuiRadioGroupProps & { inputRef?: MuiRadioGroupProps [ "ref" ] ; errorMessage?: string ; radioPropsList: RadioProps [] ; } ; export const RadioGroup: React.FC < RadioGroupProps > = ( { inputRef , radioPropsList , errorMessage , ...rest } ) => { return ( < div > < FormControl error = { !! errorMessage } > < MuiRadioGroup ref = { inputRef } { ...rest } > { radioPropsList.map (( el ) => ( < FormControlLabel key = { el.value } value = { el.value } label = { el.label } control = { < Radio / > } / > )) } < /MuiRadioGroup > < /FormControl > { !! errorMessage && < FormHelperText error > { errorMessage } < /FormHelperText > } < /div > ); } ; import { useController } from "react-hook-form" ; import type { FieldValues , UseControllerProps , DeepMap , FieldError , } from "react-hook-form" ; import { RadioGroup , RadioGroupProps } from "./RadioGroup" ; export type RhfRadioGroupProps < T extends FieldValues > = RadioGroupProps & UseControllerProps < T >; export const RhfRadioGroup = < T extends FieldValues >( props: RhfRadioGroupProps < T > ) : JSX. Element => { const { name , control , ...rest } = props ; const { field: { ref , ...restControllerProps } , } = useController < T >( { name , control } ); return < RadioGroup inputRef = { ref } { ...restControllerProps } { ...rest } / >; } ; SelectForm SelectForm は機能としてはRadioGroupに近いのでほぼ同じ実装になっています。ただselectedValueをプロパティとして渡せるようにしないと、現在選択されている値をフォーム上に表示することができないので渡しています。RHFを使うと「現在選択されている値」の情報はuseControllerから持ってこれるので、そのままview層に渡します。 またMuiの Select コンポーネント をスタイリングせずにそのまま使うと、以下のgifのようにwidthが極端に小さいコンポートとなってしまいました。実際に使うときにはお好みのスタイリング手法でwidthを設定したほうが良いでしょう。ただMuiの コンポーネント にスタイルを当てたい場合は公式に用意されている styled() を使うのがオススメです。 import { FormControl , FormHelperText , InputLabel , MenuItem , Select , } from "@mui/material" ; import type { SelectProps as MuiSelectProps } from "@mui/material" ; type SelectProps = { label: string ; value: string ; } ; export type SelectFormProps = MuiSelectProps & { inputRef?: MuiSelectProps [ "ref" ] ; errorMessage?: string ; selectPropsList: SelectProps [] ; selectedValue: string ; } ; export const SelectForm: React.FC < SelectFormProps > = ( { inputRef , errorMessage , selectPropsList , selectedValue , label , ...rest } ) => { return ( < div > < FormControl > < InputLabel > { label } < /InputLabel > < Select ref = { inputRef } value = { selectedValue } label = { label } { ...rest } > { selectPropsList.map (( props ) => ( < MenuItem key = { props.value } value = { props.value } > { props.label } < /MenuItem > )) } < /Select > < /FormControl > { !! errorMessage && < FormHelperText error > { errorMessage } < /FormHelperText > } < /div > ); } ; import { useController } from "react-hook-form" ; import type { FieldValues , UseControllerProps , DeepMap , FieldError , } from "react-hook-form" ; import { SelectForm , SelectFormProps } from "./SelectForm" ; export type RhfSelectFormProps < T extends FieldValues > = Omit < SelectFormProps , "selectedValue" > & UseControllerProps < T >; export const RhfSelectForm = < T extends FieldValues >( props: RhfSelectFormProps < T > ) : JSX. Element => { const { name , control } = props ; const { field: { ref , onChange , value: selectedValue , ...rest } , fieldState: { error } , } = useController < T >( { name , control } ); return ( < SelectForm inputRef = { ref } onChange = { ( e ) => onChange ( e ) } { ...rest } { ...props } selectedValue = { selectedValue } errorMessage = { ( error && error.message ) || props.errorMessage } / > ); } ; CheckboxGroup CheckboxGroup にはチェックした値が配列として返ってくるようにしています。例えば「りんご」「ばなな」をチェックすると ["apple", "banana"] が返ってきます。 ロジックとしては RhfCheckboxGroup の handleChange とRHFの onChange 関数を使って実現しています。 handleChange でチェックした値の配列を作り、 onChange 関数の引数に渡すことでRHFに作成した配列の情報を渡すことができます。 import React from "react" ; import { Checkbox , FormControlLabel , FormGroup , FormHelperText , } from "@mui/material" ; import type { FormGroupProps } from "@mui/material" ; type CheckboxProps = { value: string ; label: string ; } ; export type CheckboxGroupProps = FormGroupProps & { inputRef?: FormGroupProps [ "ref" ] ; errorMessage?: string ; checkBoxPropsList: CheckboxProps [] ; checkedValues: string [] ; } ; export const CheckboxGroup: React.FC < CheckboxGroupProps > = ( { inputRef , checkBoxPropsList , checkedValues , errorMessage , ...rest } ) => { return ( < div > < FormGroup ref = { inputRef } { ...rest } > { checkBoxPropsList.map (( props ) => ( < FormControlLabel key = { props.value } control = { < Checkbox value = { props.value } checked = { checkedValues.includes ( props.value ) } / > } label = { props.label } / > )) } < /FormGroup > { !! errorMessage && < FormHelperText error > { errorMessage } < /FormHelperText > } < /div > ); } ; import React from "react" ; import { DeepMap , FieldError , useController } from "react-hook-form" ; import type { FieldValues , UseControllerProps } from "react-hook-form" ; import { CheckboxGroup , CheckboxGroupProps } from "./CheckboxGroup" ; export type RhfCheckboxGroupProps < T extends FieldValues > = Omit < CheckboxGroupProps , "checkedValues" > & UseControllerProps < T >; export const RhfCheckboxGroup = < T extends FieldValues >( props: RhfCheckboxGroupProps < T > ) : JSX. Element => { const { name , control } = props ; const { field: { ref , onChange , value: checkedValues , ...rest } , fieldState: { error } , } = useController < T >( { name , control } ); const handleChange = ( e: React.ChangeEvent < HTMLInputElement >) => { let newCheckedValueList: string [] = [] ; if ( e.target.checked ) { // チェックボックスがチェックされた時、チェックされた値を重複値の無い配列に追加 newCheckedValueList = [ ... new Set ( [ ...checkedValues , e.target.value ] ) ] ; } else { // チェックボックスが外された時は、チェックが外された値を配列から削除 newCheckedValueList = [ ...checkedValues ] .filter ( ( value ) => value !== e.target.value ); } return newCheckedValueList ; } ; return ( < CheckboxGroup inputRef = { ref } onChange = { ( e: React.ChangeEvent < HTMLInputElement >) => onChange ( handleChange ( e )) } { ...rest } checkBoxPropsList = { props.checkBoxPropsList } checkedValues = {[ ...checkedValues ]} errorMessage = { ( error && error.message ) || props.errorMessage } / > ); } ; DatePicker DatePicker はMuiの構成上view層とロジック層を分けることができませんでした。renderInputでRhfTextFieldを直接渡すことでRHFに対応させるようにしています。またMuiのデフォルトだと DatePicker のplaceholderに y/mm/dd と表示されてしまうので、inputPropsからplaceholderを設定するようにしています。また defaultValue={undefined} を指定しないと型エラーが出てしまうので設定しています。 Muiの DatePicker はカレンダーアイコンから日付を選択することもできるし、TextFieldに直接日付を入力することもできます。直接日付を入力した際は文字列をDate型としてparseしたいのでdate-fnsのparse関数を用いています。また数値以外の文字列を入力できるとinvalid dateとなってしまうので、onChange内で 正規表現 を用いて入力できないようにしています。 またMuiの DatePicker はバックスペースなどで入力した日付を全て消すと値としてはnullが入るのでzod側ではnullを許容するようにしています。 import { DatePicker } from "@mui/x-date-pickers" ; import { parse } from "date-fns" ; import { useController } from "react-hook-form" ; import type { FieldValues , UseControllerProps } from "react-hook-form" ; import { RhfTextField } from "./RhfTextField" ; /** 日付フォーマットyyyy/MM/ddを文字列とみなした時の長さは10 */ const DATE_FORMAT_LENGTH = 10 ; export type RhfDatePickerProps < T extends FieldValues > = UseControllerProps < T >; export const RhfDatePicker = < T extends FieldValues >( props: RhfDatePickerProps < T > ) => { const { name , control } = props ; const { field: { onChange , value } , } = useController < T >( { name , control } ); const onSelectDate = ( e: Date | null ) => { onChange ( e ); } ; const onChangeText = ( value: string ) => { // MUIのDatePickerはデフォルトで10文字より多く入力できてしまうため、10文字を超えた分は省略する // ex) yyyy/MM/dd{任意の文字}のように入力できてしまう if ( value.length > DATE_FORMAT_LENGTH ) { onChange ( parse ( value.slice ( 0 , DATE_FORMAT_LENGTH ), "yyyy/MM/dd" , new Date ()) ); return; } onChange ( parse ( value , "yyyy/MM/dd" , new Date ())); } ; return ( < DatePicker value = { value || null } onChange = { ( e: Date | null ) => onSelectDate ( e ) } renderInput = { ( params ) => ( < RhfTextField { ...params } inputProps = {{ ...params.inputProps , placeholder: "yyyy/MM/dd" , }} error = { !! errors [ name ]} onChange = { ( e ) => { // 数値以外を弾く if ( !/^\d*$/ .test ( e.target.value )) return; onChangeText ( e.target.value ); }} defaultValue = { undefined } name = { name } control = { control } / > ) } / > ); } ; コンポーネント 使用側実装例 参考までにこれまでに紹介した コンポーネント の使用側実装例を掲載しておきます。 import "./App.css" ; import { styled , Button } from "@mui/material" ; import { z } from "zod" ; import { useForm , SubmitHandler } from "react-hook-form" ; import { zodResolver } from "@hookform/resolvers/zod" ; import { RhfTextField } from "./components/RhfTextField" ; import { RhfRadioGroup } from "./components/RhfRadioGroup" ; import { RhfSelectForm } from "./components/RhfSelectForm" ; import { RhfCheckboxGroup } from "./components/RhfCheckboxGroup" ; import { RhfDatePicker } from "./components/RhfDatePicker" ; const Form = styled ( "form" )( { display: "flex" , flexDirection: "column" , gap: "16px" , alignItems: "center" , width: "100%" , padding: "16px" , } ); const Flex = styled ( "div" )( { display: "flex" , gap: "16px" , } ); const schema = z. object ( { text: z. string () .min ( 1 , { message: "Required" } ), radio: z. string () .min ( 1 , { message: "Required" } ), select: z. string () .min ( 1 , { message: "Required" } ), checkbox: z. string () .array () .min ( 1 , { message: "Required" } ), date: z .date () .nullable () .refine (( date ) => date !== null , "Required" ), } ); type Inputs = z.infer <typeof schema >; const defaultValues: Inputs = { text: "" , radio: "" , select: "" , checkbox: [] , date: null , } ; const props = [ { label: "りんご" , value: "apple" , } , { label: "みかん" , value: "orange" , } , { label: "ばなな" , value: "banana" , } , ] ; function App () { const { control , handleSubmit , reset } = useForm < Inputs >( { defaultValues: defaultValues , resolver: zodResolver ( schema ), } ); const onSubmit : SubmitHandler < Inputs > = ( data ) => console .log ( data ); return ( < Form onSubmit = { handleSubmit ( onSubmit ) } > < RhfTextField label = "Text" name = "text" control = { control } / > < RhfRadioGroup name = "radio" control = { control } radioPropsList = { props } / > < RhfSelectForm label = "Select" name = "select" control = { control } selectPropsList = { props } / > < RhfCheckboxGroup name = "checkbox" control = { control } checkBoxPropsList = { props } / > < RhfDatePicker name = "date" control = { control } / > < Flex > < Button type= "submit" > 送信 < /Button > < Button onClick = { () => reset () } > リセット < /Button > < /Flex > < /Form > ); } export default App ; おわりに よく使う コンポーネント をRHF化して子 コンポーネント として作成することができました。これらの コンポーネント を組み合わせ、zodと連携することで様々なバリデーション機能を持ったフォームを作成することができると思います。 DatePicker に関しては若干ゴリ押しの実装になってしまった感が否めませんが自分の実力ではこれが限界でした...。 未だにRHFの底が見えていないのでどんどん使い倒してマスターできるようになっていきたいです。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
はじめに PhpStormとは 料金 ダウンロード・初期設定 ダウンロード 初期設定 コードスタイルの設定 フォントの設定 日本語化 その他のプラグイン 基本操作 編集 検索 ナビゲーション コード補完 基本的な補完 ステートメント補完 後置コード補完 その他の補完 DBクライアント 設定方法 操作方法 Gitクライアント 設定方法 操作方法 Xdebugとの連携 設定方法 ステップデバッグの手順 デバッグ接続を有効にする 処理を止めたい任意の行をクリックして、ブレークポイントをセットします。 ステップデバッグでできること デバッグセッション再開 ステップオーバー(F8) ステップイン(F7) ステップアウト(Shift + F8) 強制ステップイン 変数の確認・更新 おわりに はじめに こんにちは、ryo479です。 みなさんは普段の開発において、どういった IDE (エディタ)を使用されているでしょうか? VSCode は勢いがあり、 Vim の人気は根強いですが、 PHP で開発されているのであれば PhpStorm もおすすめです。 以下では、PhpStormについての概要、基本的な使用方法や、便利な機能などについて解説します。 PhpStormとは PhpStormはJetBrains社製の、 PHP 開発者に向けて設計された IDE ( 統合開発環境 )です。 統合開発環境 とはその名の通り、開発に便利な様々なツールが統合された環境で、PhpStormでは例えば以下のような機能が含まれています。 強力なコード補完付きのエディタ 様々な リファクタリング 機能 DBクライアント Gitクライアント デバッガとの連携機能 ■公式サイト www.jetbrains.com 料金 PhpStormは有料です。 30日間の無料体験版が準備されているので、一度試しに使ってみるのがおすすめです。 また、PhpStormを安く使用できるいくつかの「特別オファー」があるので、確認してみて下さい。 購入 PhpStorm:価格とライセンス、割引 - JetBrains Toolboxサブスクリプション ダウンロード・初期設定 ダウンロード PhpStormの インストーラ ーは以下のページからダウンロードできます。 使用しているOSに合った インストーラ ーをダウンロードしましょう。 ダウンロード PhpStorm:超高速でスマートなPHP IDE ダウンロードができたら インストーラ ーを起動し、手順に沿って進めるだけです。 PhpStormのインストールが完了した旨の表示がされればOKです。 初期設定 起動後にプロジェクトを作成し、開発していくことになりますが、その前にいくつかの便利な設定を紹介しておきます。 コードスタイルの設定 PhpStormには、設定されたコードスタイルに従ってコードをフォーマットする機能が備わっています。 事前にコードスタイルを設定しておきましょう。 「Files > Settings > Editor > Code Style > PHP 」を選択します。 「Set from...」をクリックし、選択肢からコードスタイルを設定できます。 PHP コーディング規約(PSR)の最新版であるPSR12を選択するのがおすすめです。 コードフォーマットは以下で実行できます。 選択範囲をフォーマットする場合 ⇒「Code > Reformat Code」をクリック(もしくは「Ctrl + Alt + L」) ファイル全体をフォーマットする場合 ⇒「Code > Reformat File」をクリック(もしくは「Ctrl + Alt + Shift + L」) フォントの設定 「Files > Settings > Editor > Font」を選択。 フォントの種類やサイズ、行間のスペースを設定できます。 好みのフォントに設定しておきましょう。 日本語化 「Files > Settings > Plugins」を選択し、 プラグイン を「japanese」で検索しましょう。 「Japanese Language Pack」 プラグイン をインストールし、PhpStormを再起動すれば日本語化されています。 その他の プラグイン 使用する環境に合わせて、 プラグイン を追加しておきましょう。 例えば、以下のような プラグイン が存在します。 Laravel .env files support BashSupport など 基本操作 開発において頻繁に使用するであろう、おすすめの基本操作を記載しておきます。 ショートカットコマンドの一覧は以下をご確認ください。 https://pleiades.io/sites/willbrains.jp/keymap/pdf/shortcut_phpstorm_windows.pdf 編集 説明 ショートカット コードの生成(Getter, Setter, コンストラクター など) Alt + Insert コメント化/コメント解除 Ctrl + / 自動インデント Ctrl + Alt + L 選択範囲のコードをフォーマット Ctrl + Alt + L ファイル全体をフォーマット Ctrl + Alt + Shift + L 検索 説明 ショートカット どこでも検索 Shift2回 ファイル名で検索 Ctrl + Shift + N クラス名で検索 Ctrl + N ファイル内検索・置換 Ctrl + F / Ctrl + R プロジェクト内検索・置換 Ctrl + Shift + F / Ctrl + Shift + R 使用箇所を検索 Alt + F7 / Ctrl + F7 ナビゲーション 説明 ショートカット クラス、変数、メソッドの定義元にジャンプ Ctrl + B / Ctrl + Click 前のカーソル位置に移動 Ctrl + Alt + ⇐ 次のカーソル位置に移動 Ctrl + Alt + ⇒ 行番号を指定して移動 Ctrl + G コードブロックの最初・最後に移動 Ctrl + ] / Ctrl + [ 最近使用したファイルに切替 Ctrl + Tab 最近使用したファイルを表示 Ctrl + E コード補完 基本的な補完 PhpStormは自動補完の機能が備わっており、入力中に自動で候補を出してくれます。 自動補完の設定は「Files > Settings > Editor > General > Code Completion」で確認できます。 「Show suggestions as you type」にチェックが入っていれば有効です。 手動で補完を行いたい場合は「Ctrl+Space」を押下してください。 ステートメント 補完 「Ctrl+Shift + Enter」でメソッド宣言やif文のブロックなどを補完してくれます。 if ↓ if () { } 後置コード補完 例えば以下のように書いて補完を実行(Tabを押下)すると、 function m(arg) { arg.if } if文が完成します。 function m(arg) { if (arg) { } } .if 以外にも事前に定義されたテンプレートが複数存在します。 後置コード補完の設定は「Files > Settings > Editor > General > Postfix Completion」から確認できます。 その他の補完 その他にも便利な補完機能がありますので、ぜひドキュメントを確認してみて下さい。 コード補完 | PhpStorm DBクライアント PhpStormにはDBクライアントの機能も備わっています。 PhpStormから直接DBを確認したり操作したりすることができるため便利です。 設定方法 「View > Tool Windows > Database」を選択します。 設定画面が開くので「+ボタン > Data Source」より、使用するDBを選択してください。 DBの接続情報入力画面が表示されます。 情報を入力して「Test Connection」で接続テストを実施しましょう。 緑のチェックマークが出れば接続成功です。 「OK」をクリックして設定を終わります。 操作方法 DatabaseタブからDBを選択し、内容を確認できます。 また、consoleに SQL を入力し、実行ボタンを押下することで SQL を実行できます。 Gitクライアント PhpStormにはGitのクライアント機能も備わっています。 PhpStormからGitを操作できます。 設定方法 GitのパスをPhpStormに設定します。 「Files > Settings > Version Controll > Git」を選択します。 設定画面が表示されるので、「Path to Git executable」にGitのPathを設定します。 操作方法 画面の右下にGitのブランチ名が表示されています。 クリックすると新しいブランチの作成や、ブランチの切り替えなどの操作が実行できます。 より詳しい操作は、以下の記事などを参考にして下さい。 tech-blog.rakus.co.jp Xdebug との連携 設定方法 設定については、開発環境によって方法が異なります。(ローカル デバッグ 、リモード デバッグ など) 公式ドキュメントなどの資料をご確認いただき、環境に合った設定をしてください。 Debug with PhpStorm: Ultimate Guide | PhpStorm ステップ デバッグ の手順 ステップ デバッグ とは、コードの任意の行で処理を止めて行う デバッグ のことです。 PhpStormにおけるステップ デバッグ の手順を簡単に記載します。 デバッグ 接続を有効にする まずは受話器のマークをクリックして、 デバッグ 接続を有効にしてください。 処理を止めたい任意の行をクリックして、 ブレークポイント をセットします。 アプリケーションを動作させると、 ブレークポイント を設定した箇所で処理が停止します。 ステップ デバッグ でできること ステップ デバッグ 実行中に使用できる機能について一部を紹介します。 デバッグ セッション再開 次の ブレークポイント へ処理が進みます。 ステップオーバー(F8) 次の行に処理が進みます。 ステップイン(F7) 停止している行のメソッド内に入ります。 ステップアウト(Shift + F8) 現在のメソッドから抜け、呼び出し元のメソッドに移動します。 強制ステップイン ステップインでは入ることのできない サードパーティー ライブラリのメソッドへも入ることができます。 変数の確認・更新 停止している時点における変数の値を確認できます。 値の編集も可能です。 おわりに PhpStormの基本的な機能についてざっくりと記載しました。 PhpStormには他にも様々な機能が備わっていますので、ドキュメントを確認してみて下さい。 ちなみにPhpStormの名称は「PHPStorm」ではなく「PhpStorm」だそうです。 speakerdeck.com エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
こんにちは。インフラエンジニアの gumamon です! 近年、 Kubernetes 等の登場により、アプリケーションのスケールアウトはとても簡単になりました。対して、データベース(DB)のスケールアウトは依然として困難です。 「 RDBMS 」⇒ データの一貫性は保てるが、スケールアウトが難しい 「NoSQL」⇒ データの一貫性を保てないが、スケールアウトが容易 DBのスケールアウトを考えるとこの2択に行きつく、というのが今までの常識だったかと思いますが、 『どっちも!』が出来てしまう第3の選択肢が登場しました。 データの一貫性を保て、且つスケールアウト容易な『NewSQL』! 最近、NewSQLの一つである yugabyteDB の検証をする機会がありましたので、 アーキテクチャ と検証結果を紹介します。 目次 目次 ここがすごいぞ yugabyteDB! yugabyteDBのアーキテクチャ キーコンセプト NewSQLの肝①: Raft (分散合意アルゴリズム) とは? NewSQLの肝②: Table Shardingとは? yugabyteDBにおける Raft , Table Sharding yugabyteDBの検証結果 PostgreSQLとの互換性 データ移設 可用性・分断耐性 まとめ ここがすごいぞ yugabyteDB! はじめに、個人的なyugabyteDBの推しポイントを紹介します。 どれもインフラエンジニア目線では、垂涎の機能ばかりです。 ◆ PostgreSQL 完全互換である 内部構造的には全く別物でありながら、PostgreSQL11と(ほぼ)完全な互換性があります。 psql コマンドでアクセスできますし、APPの接続先を PostgreSQL からyugabyteDBに変更するだけで利用可能です。 ◆全ノード Read/Write 可能 yugabyteDBでは全てのノードに対してRead/Write可能です。 RDBMS におけるActive,Standbyのようなノード間の上下関係が存在しません。ノード構成時に レプリケーション の構成を考える必要もありません。 ◆ノード故障に伴うダウンタイムゼロ yugabyteDBは レプリケーション 係数(以下 rep)という変数を持ち、その変数に応じてデータを複製します。例えば、rep=5 と設定した場合、データは5重に複製され、2ノードまでのノード故障に耐えることができます。さらに ノード故障から規定時間(デフォルトでは15分)を経過すると、生存しているノードのデータが別ノードに再度複製され、冗長性が回復します。つまり、99ノード(物理機)で クラスタ を構築したとしてもノードの故障率を考慮する必要があまり無い、と言えます。 ◆ノード追加・削除のダウンタイムゼロ yugabyteDBは無停止で クラスタ 構成の変更が可能です。 ノード追加 ⇒ 追加ノードでサービスを起動すると自動で クラスタ に参加。データが クラスタ 全体で再配置(平坦化)されます。 ノード削除 ⇒ 削除予定 ノードでBlackListコマンドを打つと、データが他ノードに移動します。後は空になったノードを停止するだけです。 ◆無料です Apache2.0 ライセンスの OSS です。これだけの機能を持ちながら完全無料! ※有償のマネージドサービスもあります 。 yugabyteDBの アーキテクチャ つぎに、yugabyteDBがどのように前述の機能を実現しているかを紹介していきます。全てを書き起こすと果てしない長さになってしまうので、今回はyugabyteDBの概要と、NewSQLの「肝」にフォーカスしてご紹介します。 詳しい情報は 公式ドキュメント をご参照ください。読みやすいです! キーコンセプト yugabyteDBの アーキテクチャ は階層化された設計になっています(2層構造)。 yugabyteDB構造図 ◆Yugabyte Query Layer クライアントへの応答を返す上位層です。 YSQL API ⇒ PostgreSQL11 互換の応答を返す API YCQL API ⇒ Casandora 互換の応答を返す API 特徴的なのは Pluggable QueryEngine です。これは API をどんどん追加できるように設計されており、今後も新たな API が追加予定とのことです。(実際、初期のyugabyteDBはYCQLのみで、後からYSQLが追加されています。) RDBMS 、NoSQLの インターフェイス を併せ持つDBというのは中々無いのでは?と思います。 ◆DocDB Document Store 分散ドキュ メントス トアーです。 Raft Consensus Replication ⇒分散合意 アルゴリズム を用いた レプリケーション (後述。NewSQLの肝①) Sharding & Load Balancing ⇒テーブルシャーディング(後述。NewSQLの肝②) DistributedTransactionManager&MVCC ⇒データの一貫性を担保。ACID特性を持つ Custom RocksDB Storage Engine ⇒ Facebook にも採用されているKey- Value ストア データ保管場所の実体はKey- Value ストアですが、これに3つの コンポーネント を追加することで、一貫性、可用性、分断耐性を併せ持つドキュ メントス トアとして機能しています。 NewSQLの肝①: Raft (分散合意 アルゴリズム ) とは? ひらたく言うと、「柔軟に多数決を採決し、一貫した結論を出す アルゴリズム 」と言えるかと思います。 Kubernetes を構成する コンポーネント etcd でも利用されており、「一貫性、可用性、分断耐性を併せ持つことはできない」とする「CAP定理」を打破することが出来ます。Raftで ググる と「よくわからない・・」という記事が沢山ヒットしてしまうのですが、やっていることは中々にシンプルです。ただ、静止画で説明をするのが難しいので詳しく知りたい方は こちらのサイト をご参照頂ければと思います。とても分かりやすいです! 以下では アルゴリズム の肝になる部分のみ、かいつまんで説明してみます。 Raftの概要 クラスタ ー構成 Leader x1 ⇒リーダー選挙に当選したnode。意思決定権を持つ Follower x n ⇒リーダー選挙に落選したnode。意思決定権を持たない 主要な動作 リーダー選挙 ⇒Leader不在の クラスタ ーで発生。Leaderを選出 ログ複製   ⇒Leaderの意思決定をWAL(Write Ahead Log)でFollowerに伝達(複製) ※重要なこと クラスタ ー参加nodeは全nodeに対して疎通確認(HeartBeat)している リーダー選挙時、 過半数 のnodeと疎通が取れない場合、自分はLeaderに立候補しない。もしLeaderであれば辞任(Followerに降格)する。 ログ複製時、 過半数 のFollowerからLeaderにレスポンスが返ってきたらLeaderはクライアントにCOMMITを返す NewSQLの肝②: Table Shardingとは? ひらたく言うと、大きなテーブルを「 メタデータ +テーブルxn」に「行」分割する機能と言えるかと思います。テーブルシャーディングを行うと、小分けにしたテーブルは検索性が向上します。また、異なるnodeに配置することで、ハードウェアリソースの負荷を分散することもできます。 Sharding テーブル分割は「分散キー」を元に、実行されます。yugabyteDBの場合、デフォルトではPRIMARY KEYをHASH化したものが「分散キー」となり、PRIMARY KEYの値に関係なくランダムに分割・配置します。特定のルールに従って分割・配置することも可能ですが、その場合は特定のnodeにアクセスが集中する「 ホットスポット 」が生まれやすくなるため、注意が必要です。 yugabyteDBでは、分割したテーブルのことを Tablet ( タブレット )と呼びます。 yugabyteDBにおける Raft , Table Sharding yugabyteDBを rep=3 , node=4 で構成した例で説明をします。 Document Store 上図はyugabyteDB内のあるテーブルです。テーブルのデータ(緑)が3つの タブレット に分割され、 メタデータ (青)で管理されています。各 タブレット は rep=3に従い、3重に複製されています。また、複製されたデータは各々Raftを形成しており、何れか1つの タブレット のみがLeaderとなっています。上図はyugabyteDBのDocument Storeの動作イメージです。この上位層に(4nodeにまたがる形で)Query Layerが展開されており、各nodeの何れかがクエリを受け取ると、Query Layerは対応する タブレット のLeaderにRead/Write命令を投げる、という仕組みになっています。nodeが増えるとクエリが分散される。nodeが増えずとも タブレット が違えばクエリの並列実行が可能で、且つTransaction Managerによりデータの一貫性も担保される、という仕組みになっているようです。 yugabyteDBの検証結果 今回、とある弊社商材のDBを PostgreSQL からyugabyteDBに置換してみました(既存データの移設も含む)。サマリーですが、その結果を紹介してみたいと思います。 PostgreSQL との互換性 完全互換と謳うだけあって、リリースレベルのテストを行ってもほぼ問題が発生しませんでした。ただ、実態は PostgreSQL を模した API であり、一部本家 PostgreSQL と挙動が異なったのでご紹介です。 テーブルロック PostgreSQL には多様なロックモードがありますが、yugabyteDBで現在実装されているのは ACCESS SHARE のみでした。 その他ロックモードは開発中 とのこと。 トランザクション 競合時の挙動 PostgreSQL11で トランザクション が競合した場合、後発 トランザクション は先行 トランザクション が完了するまで待ってからCOMMITする動きをする(後勝ちとなる)のですが、yugabyteDBにおいては後発 トランザクション にエラーが返ります。 こちらも現在開発中 とのこと。 データ移設 COPYコマンドですんなり移設できました。 前述したPRIMARY KEYのHASHが自動実行され、 PostgreSQL 上の元テーブル(1000万行)はバラバラの タブレット に分割されました。複合キーを使っていたテーブルについても問題なし。 可用性・分断耐性 この検証結果は素晴らしかったです!推しポイントにも書きましたが、 node故障、追加、削除、ネットワークの分断(※)の発生において、ヒトが行うべき作業はほぼ何もありません 。やるべきことは サービスの起動・停止 それだけです。 ※yugabyteDBはAZ(Availability Zone)の概念を持っており、どのnodeがどのAZに所属しているかを設定する必要があります。これはネットワークの分断に備える為に重要なことです。例えばrep=3 , node=6の クラスタ を3つのAZに2nodeずつ配置したとします。この際AZの設定をyugabyteDBにしていないと、node1,node2,node3でRaftを組むような タブレット が出て来て、ネットワーク分断が発生した時、一部の タブレット のみ孤立しているAZ側で多数決を可決してしまう状況が発生する可能性があります。 まとめ yugabyteDBの アーキテクチャ 、検証結果について紹介させて頂きました! yugabyteDBは RDBMS +NoSQLの良いとこ取りDB (NewSQL) (ほぼ) PostgreSQL11完全互換 可用性・分断耐性は素晴らしい。データの再配置までしてくれる yugabyteDBの 知名度 はまだまだ(?)なのですが、NewSQLであるCloudSpanner( GCP ) やTiDB( OSS / MySQL 互換)は国内でもちらほら導入事例を聞くようになって来ました。 CloudSpannerは ドラクエ ウォーク等、巨大なリソースを必要とする(?)サービスにも採用されているようです。NewSQLのスケーラビリティ、可用性には未来を感じる・・!と個人的には思っています。当記事をご覧になり、気になった方はぜひ使ってみてください。今回はOS上に直接構築をしましたが、 Kubernetes の知見がある方はHelmChartが出ているので、そちらを使った方がスムーズかなと思います。 以上、最後までお読み頂きありがとうございました! 参考: yugabyteDB 公式ドキュメント エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
技術広報の yayawowo です。 いつも ラク スのエンジニアブログをお読みいただき、ありがとうございます! 今年度4回目となる ラク スMeetupは、 『 アーキテクチャカイゼンで課題解決に挑む、エンジニア達 』 でした! テーマは『 アーキテクチャ 』です。 各プロダクトの アーキテクチャ カイゼン に挑んだ弊社のバックエンドエンジニアの3名が登壇しました! なお、本イベントは以下のような方にオススメとなっております。 ◆ こんな方にオススメ! ・ ラク スのプロダクト、組織に興味がある方 ・マイクロサービス、DDDに興味がある方 ・長期プロダクトの リファクタリング を検討している方 ・ アーキテクチャ の カイゼン を検討している方 ・プロダクトのルール作りに興味がある方 ・ SaaS 開発に携わるエンジニアの話が聞いてみたい方 発表の紹介 レガシーになりゆくシステムとの向き合い方 アプリアーキテクチャを明文化しチームの開発効率をアップ ドメインの異なる新機能開発におけるアーキテクチャ検討 次回のラクスMeetupは? 当日のタイムテーブル 申込方法 ラクスのエンジニア/デザイナーと話をしてみたい方へ 終わりに 発表の紹介 それではここから各発表内容と資料を共有させていただきます! イベントの詳細は以下をご確認ください。 rakus.connpass.com レガシーになりゆくシステムとの向き合い方 登壇:井上 大輔 [所属:楽楽勤怠開発1課/担当プロダクト: 楽楽勤怠 ] speakerdeck.com トップバッターは、楽楽勤怠開発1課に所属する井上さんの発表です。 ローンチ2年が過ぎた、楽楽勤怠との向き合い方を発表頂きました! ◆ 発表内容 私が担当している「楽楽勤怠」は2019年度にチームが発足し、今期で4年目になる比較的新しいサービスです。 現在 PMF 達成に向けて新機能の開発に邁進している一方で、システムの複雑化が進み保守性や変更容易性が低くなっており、長期的な目線に立つと生産性の低下を招いたり、障害発生率が高まってしまいます。 そのような事態にならないよう自分たちを守るために、チームとして取り組んでいることを紹介いたします。 アプリ アーキテクチャ を明文化しチームの開発効率をアップ 登壇:佐藤 晶彦 [所属:楽楽精算モバイル開発課/担当プロダクト: 楽楽精算 ] speakerdeck.com 続いて、楽楽精算モバイル開発課に所属する佐藤さんの発表です。 アーキテクチャ ガイドライン として具体的に明文化した内容を発表頂きました! ◆ 発表内容 弊社のモバイル開発チームは、2020年に組織化された新しいチームであり、解決すべき以下の課題を抱えております。   ・技術力向上 ・開発スタイルの習得 ・学習・実装コストの削減   特に優先度が高い技術的課題へのアプローチとして「アプリ アーキテクチャ の明文化」を行い、現在開発を進めております。   一方で弊社の Android 開発では基本 アーキテクチャ として MVVM を採用していますが、モバイル アーキテクチャ は現在も進化しています。 そのため、弊社では オブジェクト指向 や ドメイン 駆動設計等のトレンドも踏まえて、これまでのMVVM アーキテクチャ を進化させた アーキテクチャ の検討を進めております。   本セッションでは、 ・ アーキテクチャ ガイドライン として具体的に明文化した内容 ・弊社で採用した オブジェクト指向 型の アーキテクチャ   を中心に、 アーキテクチャ を明文化して実際の開発に臨んだ話を紹介いたします。 ドメイン の異なる新機能開発における アーキテクチャ 検討 登壇:川上 正博 [所属:楽楽明細開発2課/担当プロダクト: 楽楽明細 ] speakerdeck.com ラストは、楽楽明細開発2課に所属する川上さんの発表です。 楽楽明細の新機能追加に伴う、 アーキテクチャ カイゼン の取り組みをお話しいただきました! ◆ 発表内容 楽楽明細は現在、モノリシックな構成で作成されています。 近年著しく成長しているサービスですが、最近大きな機能の追加開発案件が発生しました。 業務領域( ドメイン )の異なる機能となるため、楽楽明細本体とは分割されたシステム構成としての アーキテクチャ 検討が必要となりました。 マイクロサービスとしてシステム分割を検討したいところですが、さまざまな課題があり、定められた開発期間内で対応するのも困難な状況です。 この新規機能追加に対しての、 アーキテクチャ を軸とした取り組みのお話をします。 次回の ラク スMeetupは? 次回の ラク スMeetupは、2022/11/9(水)に 『【 ラク スMeetup】開発戦略/チームビルディング/新機能開発』 を開催します! テーマは、『開発戦略/チームビルディング/新機能開発』です。 各プロダクトの開発に携わる弊社のバックエンドエンジニアの3名が登壇します! 当日のタイムテーブル 当日のタイムテーブルは以下の通りです。 1つでもご興味のある内容がございましたら、お気軽にご参加ください。 時間 内容 登壇者 18:50 入室開始(途中参加OK!) 19:00 オープニング 主催者 19:10 新サービスのプロジェクト推進に向けた、トライ&エラー 川﨑 智彦 19:35 楽楽精算の開発課題から学ぶ、改善取り組み 坂田 光 20:00 大規模案件における手戻りを防ぐ要件定義・開発事例 西角 知佳 20:25 クロージング 主催者 申込方法 申込ページは以下3つございますので、どれか1つからご選択ください。 ◆ 自社申込ページ career-recruit.rakus.co.jp ◆ connpass rakus.connpass.com ◆ TECHPLAY techplay.jp 皆様のご参加、お待ちしております😊 ラク スのエンジニア/デザイナーと話をしてみたい方へ 当社では、一緒に働くエンジニア/デザイナーを積極的に募集しております! 現在募集している職種は、以下サイトよりご確認ください。 career-recruit.rakus.co.jp 「まだ応募する段階では…」 という方は、是非 カジュアル面談 もご検討ください! 【こんな方におすすめ】 ポジションが経験にマッチするか確認したい 働き方/環境・体制/事業・プロダクト/文化/制度を詳しく知りたい 応募前に選考の概要を聞きたい(人物像、基準など) エンジニア・デザイナーの人となりを知りたい 以下申込フォームとなります。 rakus.hubspotpagebuilder.com 「イベントで登壇していた●●さんと話してみたい・・・」 などご要望がありましたらその旨をご記入の上、お申込みください! お気軽にどうぞ 😊 終わりに 『 アーキテクチャ カイゼン で課題解決に挑む、エンジニア達』はいかがでしたでしょうか? 最前線で活躍しているバックエンドエンジニア3名から、各プロダクトの アーキテクチャ カイゼン に挑んだ技術取り組みを発表させていただきました。 本発表が SaaS 開発に携わるエンジニア/デザイナーの皆様にとって、一つでもご参考になれば幸いです。 最後までお読みいただきありがとうございました! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター
インストール リポジトリ RPMのインストール PostgreSQLの設定変更 TimescaleDB拡張機能を有効にする 参考資料 こんにちは、ヤマウチです。 前回は、TimescaleDBについて紹介しました。 tech-blog.rakus.co.jp 今回は、TimescaleDBのインストールとTimescaleDB 拡張機能 を有効にするまでの手順について紹介します。 インストール TimescaleDBのインストールには ① RPM でインストールする方法 ② ソースコード からインストールする方法 がありますが、今回は「① RPM で CentOS にインストールする方法」を紹介します。 リポジトリ RPM でインストールする場合は、以下2つの リポジトリ を使用します。 PostgreSQL の リポジトリ https://download.postgresql.org/pub/repos/yum/ TimescaleDBの リポジトリ https://packagecloud.io/timescale/timescaledb [注意点] 1. PostgreSQL の リポジトリ にもTimescaleDBのパッケージ(timescaledb_12-2.7.2-1.rhel7. x86 _64. rpm )がありますが、このブログを書いている(2022-10-06)時点では Apache -2機能のみでCommunity機能を使えないパッケージになっています。ご注意ください。 圧縮機能はCommunity機能でないと使えないため、圧縮機能を試したい場合は 2.TimescaleDBの リポジトリ のパッケージを使用してください。 RPM のインストール PostgreSQL の リポジトリ 設定 yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL- $( rpm -E %{centos} ) -x86_64/pgdg-redhat-repo-latest.noarch. rpm TimescaleDBの リポジトリ 設定 tee /etc/yum.repos.d/timescale_timescaledb.repo <<EOL [timescale_timescaledb] name=timescale_timescaledb baseurl=https://packagecloud.io/timescale/timescaledb/el/ $( rpm -E %{rhel} ) / \$ basearch repo_gpgcheck=1 gpgcheck=0 enabled=1 gpgkey=https://packagecloud.io/timescale/timescaledb/gpgkey sslverify=1 sslcacert=/etc/pki/tls/certs/ca-bundle.crt metadata_expire=300 EOL PostgreSQL とTimeScaleDBの RPM をインストール yum install timescaledb-2-postgresql-12 ※ PostgreSQL14をインストールする場合は、 yum install timescaledb-2-postgresql-14 のように最後のバージョン番号を変えてください。 PostgreSQL の設定変更 postgresql.conf に設定を追加 vi postgresql.conf shared_preload_libraries を以下のように編集する。 shared_preload_libraries = ' timescaledb ' # 対象行を書き換える。すでに設定がある場合は , 区切りで追加する。 timescaledb.telemetry_level = basic # TimescaleDBに情報を送信する場合は basic(デフォルト)、送信しない場合はoffに設定する PostgreSQL を再起動して設定を反映させる systemctl restart pgsql TimescaleDB 拡張機能 を有効にする 拡張をインストールするDBに接続 psql -U postgresql -p 5433 DB名 拡張をインストール DB名 = # CREATE EXTENSION IF NOT EXISTS timescaledb; インストールされた拡張を確認 DB名 = # \dx インストール済みの拡張一覧 名前 | バージョン | スキーマ | 説明 -------------+------------+------------+------------------------------------------------------------------- timescaledb | 2 . 7 . 2 | public | Enables scalable inserts and complex queries for time-series data ( 1 行 ) ※timescaledb の行が表示されていれば、TimeScaleDB 拡張機能 が有効になっています。 これでTimescaleDBを利用する準備が完了しました。 次回以降、圧縮機能も検証する予定です。 参考資料 RHEL/CentOS | Timescale Docs Telemetry | Timescale Docs エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
アバター