Tech Racho エンジニアの「?」を「!」に。
  • 開発

週刊Railsウォッチ(20190610-1/2前編)RailsConf 2019のスライドを追う、Railsのファイル添付gem、Railsの技術的負債を返す、RuboCop 1.0間近ほか

こんにちは、hachi8833です。iTunesが終わっても特に痛みはありません。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

おかげさまで6/6の第11回公開つっつき会は無事終了いたしました。初参加の方も多く、回を重ねるごとに賑わいを増してきてうれしい限りです。
お忙しい中お集まりいただいた皆さま、どうもありがとうございました😂!

Rails: 先週の改修(Rails公式ニュースより)

今回はコミットリストから見繕いました。

マルチプルデータベースのガイドも追加されていました。


つっつきボイス:「おー、ついにドキュメントが追いついた🎉」「6.0のマルチデータベースの実装はだいぶ進んでいるようですけど、ドキュメントができたということはここからはそれほど変わらないのかも」「6.0もrc1出ましたし☺️」「database.yamlがこんな感じ↓になるのはもう割と普通というか、migrations_paths:がある以外はswitch_pointっぽいですし😆」「switch_pointやったことある人なら割とすんなりやれるかも😋」

production:
  primary:
    database: my_primary_database
    user: root
    adapter: mysql
  primary_replica:
    database: my_primary_database
    user: root_readonly
    adapter: mysql
    replica: true
  animals:
    database: my_animals_database
    user: animals_root
    adapter: mysql
    migrations_paths: db/animals_migrate
  animals_replica:
    database: my_animals_database
    user: animals_readonly
    adapter: mysql
    replica: true

はみだし: マルチプルデータベースの話題

「ところで、参加者の方でこれまでマルチプルデータベースのRailsを開発したことのある方はいらっしゃいます?: お、割と少ないですね😆」「Octopusだとゴツすぎるので☺️、Active Recordのshard_forとかいうgemをカスタマイズして使ったりとか」「お〜なるほど!」

おそらくこれかと思います↓。

「Octopusは有名なgemですね: 自分はswitch_pointが好きです❤️」「私もOctopusです☺️」「なるほど、Octopusの方そこそこいますね」

「マルチデータベースは何をしたいかにもよりますよね: 自分の場合は別の基幹システムのデータを引っ張りたいというのがあって、そういうスキーマも違うデータに横断的にアクセスするのにswitch_pointを使ったりしてました」「ふむふむ」「他にもデータベースを2個だけ扱うシンプルなgemとか、とにかくいろんなのがあります」

クロスデータベースでのJOIN

「マルチプルデータベースから少し逸れますけど、複数データベースにまたがるJOINって、一見できなさそうで実はできたりします🧐: PostgreSQLでは実際にやりましたし、MySQLでもできたと思います」「マジで!?」「一見できなさそうですけど?」「もちろんパフォーマンスやデータのローカリティの劣化とかはあると思いますけど、最近だとできるようになってたりします☺️」「知らなかった〜😳」

参考1: 4 Methods for joining data from multiple PostgreSQL databases
参考2: sql - How do I construct a cross database query in MySQL? - Stack Overflow

「ちょうど検索で出てきたQiita記事↓で説明すると、ちゃんとGRANTされていればdb_A.user_entry.idみたいに外部のデータベースとJOINできますね」「おぉ〜」「クエリがちょっとだけ冗長になりますけど、できることを知っておくといざというときに役に立つことがあるかも😆」「できれば使わずに済む方がうれしいですけど🤣」

select db_A.user_entry.id, db_A.user_entry.created_at, db_B.users.email, db_B.users.name
from db_A.user_entry
left join db_B.users
on db_A.user_entry.user_id = db_B.users.id;

MySQLで異なるDBに存在するテーブルを結合して表示する方法 - Qiitaより

「マルチデータベースの他のシナリオでたまにあるのは、複数の会社がハイブリッドなシステム(Railsと別のフレームワークが混在しているなど)を共同開発しているときに、お互いに影響を与えないためにデータベースサーバーを別々に立てて、間違ってもGRANTで相手のスキーマを変更しないようにしておく、というのがあったりします: そういうときに、クロスデータベースのJOINできることを知っておくと助かったりするんですよ😅」

ワーカーが早期に終了するとパラレルテストが失敗するのを修正

# activesupport/lib/active_support/testing/parallelization.rb#L30
+       def length
+         @queue.length
+       end
...

      def shutdown
        @queue_size.times { @queue << nil }
        @pool.each { |pid| Process.waitpid pid }
+
+       if @queue.length > 0
+         raise "Queue not empty, but all workers have finished. This probably means that a worker crashed and #{@queue.length} tests were missed."
+       end
      end

つっつきボイス:「なるほど、パラレルテストでありそうなバグ😆」「終了時には@queue.lengthチェックしようよということで☺️」「まだキューが残ってるのに終わってはいかんと😆」「やっぱりパラレルは難しい😅」

saveのたびにarrayが作られるのを回避

# activerecord/lib/active_record/timestamp.rb#L62
-     private
-       def timestamp_attributes_for_create_in_model
-         timestamp_attributes_for_create.select { |c| column_names.include?(c) }
-       end
+     def timestamp_attributes_for_create_in_model
+       @timestamp_attributes_for_create_in_model ||=
+         (timestamp_attributes_for_create & column_names).freeze
+     end

追記(2019/06/13): 上記コードの貼り付けミスを修正しました。


つっつきボイス:「先週見かけた@kamipoさんのツイート↑はどうやらこれのことかなと😅」「5つってありますし😆」「なるほど、selectをやめて結果をメモ化したと」「selectはRubyのメソッドの方でしたっけ?」「ですね☺️: selectの戻り値はarrayで、内部的にarrayを生成してるのか」

参考: instance method Enumerable#filter (Ruby 2.6.0) -- select

pluckのテストにorder(:id)を追加

# activerecord/test/cases/calculations_test.rb#L838
  def test_pluck_columns_with_same_name
    expected = [["The First Topic", "The Second Topic of the day"], ["The Third Topic of the day", "The Fourth Topic of the day"]]
-   actual = Topic.joins(:replies)
+   actual = Topic.joins(:replies).order(:id)
      .pluck("topics.title", "replies_topics.title")
    assert_equal expected, actual
  end

つっつきボイス:「pluckのテストが落ちることがあったのを修正したそうです」「ここだけ見てると、assert_equalでやるのがいいのか順序を問わないアサーションがいいのか、どっちだろうって思っちゃいますけど、どっちもありそう😆」

「そういえばORDERを付けなくても何となくid順に返してくれるのはMySQLぐらいでしたっけ: ぽすぐれは内部のアラインメントの絡みとかでORDERなしだと結構順序変わったりしますし😆」

マルチDBでActiveRecord::BaseApplicationRecordを"primary"に

# activerecord/lib/active_record/connection_handling.rb#L205
    def connection_specification_name
      if !defined?(@connection_specification_name) || @connection_specification_name.nil?
-       return self == Base ? "primary" : superclass.connection_specification_name
+       return primary_class? ? "primary" : superclass.connection_specification_name
      end
      @connection_specification_name
    end

+   def primary_class? # :nodoc:
+     self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
+   end

マルチDBアプリをやっているとApplicationRecordは次のような感じになる。

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to database: { writing: :primary, reading: :replica }
end

上はActiveRecord::Baseへの実際の接続を1つだけにしたい場合でも接続が2つオープンされる。connection_specification_nameが異なると、RailsがApplicationRecordをチェックするときにこれがActiveRecord::Baseではなく新しい接続だと認識するのが原因。
このPRはApplicationRecordクラスを"primary"接続と同一とみなすようにする。
同PRより大意


つっつきボイス:「おお、これはマルチDBで起きそう: 今まではActiveRecord::Baseのコネクションはシングルトンだったので問題なかったのが、マルチDBだとコネクションになるからでしょうね」「今回はマルチDB系のプルリクが多かったような気がします」「そうかもですね☺️」

その他細かな改善

# activesupport/lib/active_support/evented_file_update_checker.rb#L
    private
      def boot!
        normalize_dirs!
-       Listen.to(*@dtw, &method(:changed)).start
+
+       unless @dtw.empty?
+         Listen.to(*@dtw, &method(:changed)).start
+       end
      end

つっつきボイス:「これはわかりやすい改善」「速度的に違うでしょうね☺️」

Rails

今回はRailsConf 2019のDay 2〜Day 3から見繕いました。RubyKaigi 2019と同じスライドもちらほら見かけました。

メンタルケアに関するセッションが複数あるのが日本と違う感じですね。


つっつきボイス:「RailsConf 2019のセッションをざざーっと見てみたところ、意外に強い人の話ばかりとは限らなくて、Railsやって2年目ぐらいの話なども含めてバラエティに富んでいる印象がありました」「そういう印象ありますね: めちゃ深い話があったりそうでもなかったり☺️」「雰囲気としてはRailsdmと少し似ているところがあるのかも?」

「なるべくスライドだけでわかるものを選んでみました」「喋りの背景に徹したスライドだと見ててわからないですよね😆」

技術的負債を返済する


つっつきボイス:「技術的負債を返済する話ですが、半分ぐらい自分で作ったgemも使っているそうです」「こういう『現場であったつらみ集』は親近感が持てていいですね☺️」「興味のある人は追いかけておくとよさそう👍」

以前のウォッチでも取り上げたShopifyの「Shitlist」開発にも言及していました。

分散トレーシングによるRailsマイクロサービスのトラブルシュート

日本勢です。


つっつきボイス:「こちらはWantedlyの方によるマイクロサービスの分散トレーシングのセッションです」「マイクロサービス、成長するとだいたいこうなりますよね↓😇」「😆」「マイクロサービスはよほどキレイに設計しないと、ね☺️」

「なるほど、こういうのをやりたかったと↓」「お〜」「AWSのApp Meshなんかもそうですけど、マイクロサービスは監視がしっかりしてないと話にならないというのはよく言われますね」


「App Meshでもこの辺のグラフをLambda関数チェインなんかも含めて生成できるようになった気がするけどどうだったかな?🤔」「監視と言えばIstioも」「以前ウォッチでEnvoyとともに話題になったヤツですね(ウォッチ20190212)」

参考: AWS App Mesh(マイクロサービスをモニタリングおよびコントロールする)| AWS
参考: Istio

「思い出した、AWS X-Rayなんかがそれ」「X-Ray!?」「おととしぐらいに始まったサービスで自分もまだ使ったことはないんですが😆、EC2なんかも含めてこういうサービスグラフ↓なんかを生成できる」「ふむふむ」「X-Ray使ったことある人っています?」「まだかな〜😅」

参考: AWS X-Ray とは何ですか。 - AWS X-Ray


aws.amazon.comより

「お〜、内部も含めて全部のログが辿れるようになってるみたいですね」「ですです: マイクロサービスの間しか取れないと、結局どのリクエストがfailしたのかがわからなくなるので😆、だいたいマイクロサービス系のバックエンドでは絶対必要になる機能です」「なるほど!」

バックグラウンドジョブをサーバーレスで


つっつきボイス:「サーバーレスで重いバックグラウンドジョブを回すみたいな話だそうです」「AWS LambdaやGCPなんかも使ってる」「Railsも」「やっぱりpub/sub使いますよね😆↓」

「時間のかかる処理の場合、AWSだとイベントをチェインしたりして最終的にS3に置く、みたいなピタゴラスイッチを組み立てられますね😆」「😆」「あと最近ちょっと使い始めているAWS Step Functionsでもやれますし」「お〜」

参考: AWS Step Functions(分散アプリケーションの調整)| AWS

AWS Step FunctionsとAWS Batch

「たとえばLambdaである処理が終わったら次はこの処理を実行したいというときに、Lambda関数の最後で次の関数を呼び出すことも可能ではあるんですが、関数が10個ぐらいチェインするとどんどんわけわからなくなってくるので、Step Functionでそうした部分を作れます」「なるほど!」「Step Functionsは分岐や待ち合わせなんかもできます😋」

「Step Functionsは最近Lambdaに加えてAWS Batchなんかにも接続できるようになってますので、たとえば最初はLambdaあたりで始めて、EC2インスタンスベースで作ったバッチをAWS Batchで一気に実行させて、それが終わったら別のLambdaを実行する、みたいなこともできます」「へぇ〜!」

AWS Batch – 簡単に使えて効率的なバッチコンピューティング機能 – AWS

「と言いつつ、Step Functionsを実際に使った人の話によると、ドキュメントサービスの紹介文には書かれていてもドキュメントを見ると『まだ使えない』と書かれている機能があったりするそうです🤣」「だっはっは🤣」「聞いた話では、成功/失敗部分の取得周りで欲しい機能がまだ足りないんですって🤣」「何というずっこけ🤣」「なのでそのあたりは自分が人柱になるか誰かに人柱になってもらってから導入を決める方がいいかも✞」「皆さまの貴重な人柱が明日を作ると😆」

「まあ最終的にはピタゴラスイッチを自分で作ればいいんですけどっ😆: その場合Step Functionでは管理できない部分になってしまいますけど、Terraformとかで管理する手もあるので、その辺はよしなにやってもらえれば☺️」「な〜るほど」「スライドをちら見してからだいぶ話が逸れましたが、そういう感じのスライドなんでしょう、きっと😆」

参考: Terraform by HashiCorp

PaperclipからActive Storageに移行したお


つっつきボイス:「thoughtbotのPaperclip、懐かし〜😭」「Paperclip、今は開発止まっちゃったんですよね: 移行手順が公式に一応示されてますけど↓」

Rails: PaperclipからActiveStorageへの移行ガイド by thoughtbot(翻訳)

「PaperclipってRails 3の初期ぐらいからあるとても古いgemですけど、ご存じない方はいらっしゃいます?」「名前しか聞いたことなくて😅: 自分はShrineでやってます」

参考: shrinerb/shrine: File Attachment toolkit for Ruby applications

Railsのファイル添付gemたち

「Railsでファイル添付やるときにどれ使います? Active Storage、Shrine、Paperclip、CarrierWaveと、あとひとつ何かあった気がするけど😅」

参考: carrierwaveuploader/carrierwave: Classier solution for file uploads for Rails, Sinatra and other Ruby web frameworks
参考: refile/refile: Ruby file uploads, take 3 -- これかなと思いました

「CarrierWaveの人...そこそこいる」「PaperclipとActive Storage...少なっ😆」「人柱足りない😆」「Shrine...はもちろんいますね」「後は、まさかスクラッチで実装?😆...さすがにいないか」「可能ですけどね☺️」

「自分はShrineが好き❤️: ファイル添付系のgemとしてはかなりよくできていて、一番多機能で一番何でもできると思ってます」「へぇ〜!」「たしかAWS S3のマルチプルアップロードにも対応しているので、数十GBのファイルをアップロードしてもサーバーが落ちない限りたぶん処理できる」「何だかすげ〜」

「Active Storageは新しくRailsアプリを作るときには検討すると思いますけど、まだ発展途上っぽい分、既存のものから移行ってあんまりしないんじゃないかなって😆」「既存のものが動いているなら😆」

「ただActive Storageでひとついいなと思うのがクライアントからのダイレクトアップロード機能: これはS3などのオブジェクトストレージで一時的に使えるトークンを発行するなどして、サーバーを経由せずにブラウザからストレージに直接アップロードできるヤツですね」「なるほど!」「こういうのって自分で書きたくないですし(やれますけど)🤣、これができないと数十GBのファイルをアップロードしたときにサーバーのワーカーが止まっちゃうので😇、Active Storageのそういう機能はうらやましいですね」

参考: ActiveStorageでS3にダイレクトアップロードする - Rails Webook

approval: Railsの承認フローgem

# 同リポジトリより
staff = User.find_or_create_by(email: "staff@example.com")

record  = Book.new(name: "Ruby Way", price: 2980)
request = staff.request_for_create(record, reason: "something")
request.save # Created Approval::Request record.

records = 10.times.map {|n| Book.new(name: "my_book_#{n}", price: 300) }
request = staff.request_for_create(records, reason: "something")
request.save!

つっつきボイス:「Railsdmを主催してた平野さんのgemですね」「元カルパスさん🐩」「実は自分もこのgemを使ったことがあって、そのときは欲しい機能がちょっとだけなかったので自分でカスタマイズしました😆」「そうでしたか!」

「このgemは、たとえばデータの更新リクエストを作っておいて、approve何とかというメソッドを実行すると初めて更新が実行される、みたいなことができます」「ふむふむ」「更新内容をモデレートしたいときとか、adminでものすごく権限の強い操作があるときなんかに、approveしないと更新できないようにするのに使いますね」

「approve gemは必要なメソッドもひととおり揃っていますし、コードも短くてすぐ読めますし、発想もいいと思います☺️」「これはRailsのモデルレベルで行うんでしょうか?」「はい、モデルの拡張です🧐」

「このgemは、あくまで1個のテーブルに閉じていることが前提なので、1個の更新で複数テーブルを更新しないといけない場合はちょっと頑張らないといけない」「あ〜」「自分が使ったときは、複数テーブルに対応するために、たしか更新処理自体を抽象化したテーブルを作って、そこにイベントをJSONとかで埋め込んだような覚えがあります☺️: でapproveするとその中のコードを適当にパースして実行するみたいなことをやりました😋」「お〜なるほど!」

前任メンテナからのメッセージ

つっつきボイス:「この@kamipoさんのツイートは?」「前任のメンテナってたぶんあの帽子かぶった@sgrifさんのことかなと」

「ツイートの方は...あ〜そういうことか: id = ?で直接arrayを打ち込める場合の話」「おぉ?」「これは何の話かというと、データベース側のプレースホルダprepared statement(及びquery plan)のキャッシュにヒットさせたいということです」

訂正(2019/06/10)上の修正に合わせて以下のパラグラフを更新しました🙇。

「データベース側の?というプレースホルダは、あくまで?というプレースホルダの状態でprepared statementで持っているので、1つでも違うとprepared statementキャッシュにヒットしなくなります: 逆にprepared statementの内容がまったく同じであればprepared statementのキャッシュが効きます」「ふむふむ」「で、id IN (?,?,?,...)みたいにプレースホルダーの数が可変であってもid = ?で受け止められるのであればキャッシュのヒット率が上がるよね、ということですね」「なるほど〜」「この短いツイートだけでよくそこまで😳」「prepared statementキャッシュの話はたまに出ます☺️」

その他Rails

Ruby

Ruby 2.7.0-preview1(Ruby公式ニュースより)

先週つっつきの後に出てきたので、主な新機能をメモします。

  • コンパクションGCの導入(メモリ空間をデフラグできる)
  • パターンマッチング(experimental)
  • REPLの改良(複数行の編集やインタラクティブハイライトなど)
  • メソッド参照演算子.:(experimental)
  • 開始値なしのrange(ary[..3]など)(experimental)
  • Enumerable#tally(要素ごとの個数をハッシュで返す)
  • JITのパフォーマンス改善

その他の2.6以降の主な変更:

  • ブロック付きで呼ばれたメソッド内のブロックなしProc.newprocでwarningを表示
  • ブロック付きで呼ばれたメソッド内のブロックなしlambdaをエラーに
  • Unicodeと絵文字のバージョンを12.0.0に
  • Unicode 12.1.0の令和文字対応

参考: ガベージコレクション - Wikipedia

trace_location: ソースコードの位置をトレースするgem


つっつきボイス:「上のツイートがこのtrace_locationを指してるのかどうかわからなかったのですが😅、構わず拾ってみました」「ヒューマンリーダブルにコールチェインをログ出力してくれるのかな?🤔」「markdownCSVでも出せるみたいですね」

サンプルログ: trace_location/result.log at master · yhirano55/trace_location

Logged by TraceLocation gem at 2019-06-08 01:10:24 +0900
https://github.com/yhirano55/trace_location

[Tracing events] C: Call, R: Return

C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_handling.rb:49 [ActiveRecord::ConnectionHandling.establish_connection]
  C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/core.rb:59 [ActiveRecord::Base.configurations]
  R vendor/bundle/gems/activerecord-5.2.3/lib/active_record/core.rb:61 [ActiveRecord::Base.configurations]
  C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:121 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#initialize]
  R vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:123 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#initialize]
  C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:141 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve]
    C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:238 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve_connection]
      C vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:268 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve_hash_connection]
      R vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:274 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve_hash_connection]
    R vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:247 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve_connection]
  R vendor/bundle/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb:149 [ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver#resolve]
  C vendor/bundle/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/keys.rb:56 [Hash#symbolize_keys]
  R vendor/bundle/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/keys.rb:58 [Hash#symbolize_keys]
...

「これ、呼ばれてる場所を実行した順に追えるのが結構便利そうですね😋」「Railsってコードを見ててもどんな順序で動いてるのか割とわからないし😆」「ソースコードだとマジわからないし😆」

「trace_locationはRubyのどのバージョンから使えるんだろう?TracePointを使っているなら新しめのRubyじゃないと動かないだろうし」「それもそうか」「gemspecを見ると2.5.0以降ですね」「たぶん処理的には相当重いと思われ😆」

RuboCop入門(銀座Rails#9)

BPSのWebチームで銀座Railsを見に行った方がいたので。以下はSlackのメモから。

もうすぐ1.0が出るよ!
- 1.0に盛り込まれる目玉的なものはスライド71ページ
koicさん「デフォルトが厳しすぎるからカスタマイズして使っていいと個人的には思ってる。」


つっつきボイス:「RuboCop 1.0がもうすぐ出るそうです」「あ、まだ出てないのか」「例のRuboCopを分割する話とかも含めてやるんでしょうね」

「ちなみにRuboCopを使ってる人は?」「やはり多い😆」「じゃデフォルトのrubocop.yamlを使ってる人は?さすがにいないか😆」「RuboCopのデフォルト厳しいっすよね😇」「何をやってもABCサイズがどうとかline lengthがこうとか言われますし🤣」「koicさんもそうおっしゃってくれていますし😆」

Ruby trunkより

CGI.escapeHTMLの最適化


つっつきボイス:「mattnさんがRubyにコミットしたと聞いて」「CGIライブラリって今使ってる人どのぐらいいるんだろ😆」「CGIってあのCGI?」「ですね、RailsとかではないRubyのライブラリ」「しかもC言語のコードだし😳」「じゃ速そう😆」

参考: library cgi (Ruby 2.6.0)


前編は以上です。

バックナンバー(2019年度第2四半期)

週刊Railsウォッチ(20190604-2/2後編)Cloudflare Workers KVの可能性、PostgreSQL 12 Beta 1、Bootstrap 5でjQuery廃止ほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSなど)です。

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。