RIT Tech Blog

株式会社RITのエンジニアが知見を共有する技術ブログです。

Headless CMSのStrapiをECSで運用してみる(前半)

WordPressのスケーリングとか考えるの辛いしフロントは自由に作りたいですよね?

今回はHeadless CMSのStrapiをECSで運用した話(の前半、ローカルのDockerで動かすまで)をしたいと思います。

環境

  • Windows 10 Pro
  • Node.js 12.13.0
  • Yarn 1.19.1
  • Docker 19.03.4
  • Strapi 3.0.0-beta.17.2
    • 最新版だとstrapi buildコマンドが動かなかったのでちょっと前のバージョン使ってます

StrapiのProjectを作成する

strapi.io

公式のドキュメントに従って進めてみましょう。 Project生成用のコマンドが用意されてるのでyarnかnpxで実行します(今回はyarn使いました)

PS C:\Users\tetsu\dev> yarn create strapi-app new_blog
yarn create v1.19.1
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...

success Installed "create-strapi-app@3.0.0-beta.17.5" with binaries:
      - create-strapi-app
'C:\Program' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
error Command failed.
Exit code: 1
Command: C:\Program Files (x86)\Nodist\bin\bin\create-strapi-app
Arguments: new_blog
Directory: C:\Users\tetsu\dev
Output:

info Visit https://yarnpkg.com/en/docs/cli/create for documentation about this command.

うまくいかない… npxで再チャレンジ

PS C:\Users\tetsu\dev> npx create-strapi-app new_blog
npx: installed 84 in 12.995s
Creating a new Strapi application at C:\Users\tetsu\dev\new_blog.

? Choose your installation type (Use arrow keys)
? Choose your installation type Quickstart (recommended)
Creating a quickstart project.
Creating files.
Dependencies installed successfully.

Your application was created at C:\Users\tetsu\dev\new_blog.

Available commands in your project:

  yarn develop
  Start Strapi in watch mode.

  yarn start
  Start Strapi without watch mode.

  yarn build
  Build Strapi admin panel.

  yarn strapi
  Display all available commands.

You can start by doing:

  cd C:\Users\tetsu\dev\new_blog
  yarn develop


> new_blog@0.1.0 build C:\Users\tetsu\dev\new_blog
> strapi build "--no-optimization"

Building your admin UI with development configuration ...

√ Webpack
  Compiled successfully in 12.32s

Running your Strapi application.

> new_blog@0.1.0 develop C:\Users\tetsu\dev\new_blog
> strapi develop

[2019-11-15T09:28:19.454Z] info File created: C:\Users\tetsu\dev\new_blog\extensions\users-permissions\config\jwt.json

 Project information

┌────────────────────┬──────────────────────────────────────────────────┐
│ Time               │ Fri Nov 15 2019 18:28:21 GMT+0900 (GMT+09:00)    │
│ Launched in        │ 6508 ms                                          │
│ Environment        │ development                                      │
│ Process PID        │ 26676                                            │
│ Version            │ 3.0.0-beta.17.5 (node v12.13.0)                  │
└────────────────────┴──────────────────────────────────────────────────┘

 Actions available

One more thing...
Create your first administrator 💻 by going to the administration panel at:

┌─────────────────────────────┐
│ http://localhost:1337/admin │
└─────────────────────────────┘

[2019-11-15T09:28:21.602Z] debug HEAD index.html (10 ms) 200
[2019-11-15T09:28:21.604Z] info ⏳ Opening the admin panel...
[2019-11-15T09:28:21.763Z] debug GET index.html (3 ms) 200
[2019-11-15T09:28:21.780Z] debug GET runtime~main.c1955456.js (2 ms) 200
[2019-11-15T09:28:21.781Z] debug GET main.9cb7efe0.chunk.js (2 ms) 200
[2019-11-15T09:28:22.114Z] debug GET /favicon.ico (4 ms) 200
[2019-11-15T09:28:22.116Z] debug GET /users-permissions/init (11 ms) 200
[2019-11-15T09:28:22.121Z] debug GET /admin/init (2 ms) 200
[2019-11-15T09:28:22.164Z] debug GET 6301a48360d263198461152504dcd42b.svg (2 ms) 200
[2019-11-15T09:28:22.194Z] debug GET 2ff0049a00e47b56bffc059daf9be78b.png (2 ms) 200
[2019-11-15T09:28:22.204Z] debug GET bd03a2cc277bbbc338d464e679fe9942.woff2 (6 ms) 200
[2019-11-15T09:28:22.204Z] debug GET cccb897485813c7c256901dbca54ecf2.woff2 (5 ms) 200
[2019-11-15T09:28:22.205Z] debug GET 8b4f872c5de19974857328d06d3fe48f.woff2 (5 ms) 200
[2019-11-15T09:28:22.205Z] debug GET af7ae505a9eed503f8b8e6982036873e.woff2 (4 ms) 200
[2019-11-15T09:28:22.206Z] debug GET 33d5f0d956f3fc30bc51f81047a2c47d.woff2 (4 ms) 200
[2019-11-15T09:28:22.210Z] debug GET 4c4e7d0d5ebd40343f6e1281f0bd9438.ico (3 ms) 200

strapiが起動して管理者の登録画面が開くはずなので適当に登録しましょう (1337番portが別のプロセスですでに使われてて一度失敗してます)

f:id:rit-inc:20191115182946p:plain

登録が完了したらダッシュボードに遷移します

f:id:rit-inc:20191115183220p:plain

Dockerで動かす

一旦この状態をDockerで動かせるようにしましょう 公式がDocker Imageを提供してますが今回は手動で構築してみます

github.com

yarnしてbuildしてstartするだけのやつ

Dockerfile
FROM node:12

COPY . /strapi
WORKDIR /strapi

RUN yarn
RUN yarn build

EXPOSE 1337

CMD ["yarn", "start"]

ローカルで生成されたファイルと衝突しちゃうので.dockerignoreも作成しておきましょう

node_modules
build
.cache
.tmp
public

buildして

PS C:\Users\tetsu\dev\new_blog> docker build . -t new_blog
Sending build context to Docker daemon  527.4kB
Step 1/7 : FROM node:12
 ---> 11e92fc50c4a
Step 2/7 : COPY . /strapi
 ---> d12f81986c35
Step 3/7 : WORKDIR /strapi
 ---> Running in 41a2b9cb2e88
Removing intermediate container 41a2b9cb2e88
 ---> 55776038dbb7
Step 4/7 : RUN yarn
 ---> Running in 89413fd02861
yarn install v1.19.1
[1/5] Validating package.json...
[2/5] Resolving packages...
[3/5] Fetching packages...
info fsevents@1.2.9: The platform "linux" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
info fsevents@2.1.2: The platform "linux" is incompatible with this module.
info "fsevents@2.1.2" is an optional dependency and failed compatibility check. Excluding it from installation.
[4/5] Linking dependencies...
warning "strapi-admin > bootstrap@4.3.1" has unmet peer dependency "jquery@1.9.1 - 3".
warning "strapi-admin > bootstrap@4.3.1" has unmet peer dependency "popper.js@^1.14.7".
warning "strapi-admin > @buffetjs/styles > react-dates@21.5.0" has unmet peer dependency "react-with-direction@^1.3.1".
warning "strapi-plugin-users-permissions > grant-koa@4.6.4" has unmet peer dependency "koa@>=0.x.x".
warning "strapi-hook-bookshelf > bookshelf@0.15.1" has incorrect peer dependency "knex@>=0.13.0 <0.18.0".
[5/5] Building fresh packages...
Done in 53.53s.
Removing intermediate container 89413fd02861
 ---> f4e982ccbbc1
Step 5/7 : RUN yarn build
 ---> Running in 92f0d4414e4b
yarn run v1.19.1
$ strapi build
Building your admin UI with development configuration ...
ℹ Compiling Webpack
✔ Webpack: Compiled successfully in 24.15s
Done in 25.48s.
Removing intermediate container 92f0d4414e4b
 ---> e986f88a755d
Step 6/7 : EXPOSE 8000
 ---> Running in e8c7cc8fd917
Removing intermediate container e8c7cc8fd917
 ---> bc4357547272
Step 7/7 : CMD ["yarn", "start"]
 ---> Running in da37576c9adf
Removing intermediate container da37576c9adf
 ---> 3e5b72ab37a0
Successfully built 3e5b72ab37a0
Successfully tagged new_blog:latest
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.

runしてみる

PS C:\Users\tetsu\dev\new_blog> docker run -p 1337:1337 new_blog
yarn run v1.19.1
$ strapi start

 Project information

┌────────────────────┬──────────────────────────────────────────────────┐
│ Time               │ Fri Nov 15 2019 10:20:05 GMT+0000 (Coordinated … │
│ Launched in        │ 1795 ms                                          │
│ Environment        │ development                                      │
│ Process PID        │ 28                                               │
│ Version            │ 3.0.0-beta.17.5 (node v12.13.0)                  │
└────────────────────┴──────────────────────────────────────────────────┘

 Actions available

Welcome back!
To manage your project 🚀, go to the administration panel at:
http://localhost:1337/admin

To access the server ⚡️, go to:
http://localhost:1337

localhost:1337にアクセスしてみましょう

f:id:rit-inc:20191115192124p:plain

ローカルで実行したときと同じように登録画面が出るはず

ECSで動かす

ちょっと長くなってきたので今回はここまで 次回はECRにpushしてECSで動かすとこまでやります!

React Hooks使ってリファクタしてみた話

CTOの福田です

React 16.8 リリースされましたね

新機能のReact Hooksを使って自社サービスのコードをリファクタしてみたので、全体的な使用感とかハマったポイントを挙げていこうと思います。

前提

  • Reduxは使ってません
  • statefulなコンポーネントを多用してます
  • React + React RouterのSPA
  • TypeScript

ざっくり

  • 〇 Class ComponentがFunction Componentにできてネストが一段減って行数も微妙に削減されてうれしい
  • useState使うとsetState({hoge: 'fuga'})setHoge('fuga')みたいにできてうれしい
  • useEffectの第二引数が直感的じゃない?

詳しく

Class ComponentがFunction Componentにできる & setStateがシンプルに

何かすごいメリットがあるかって言われると微妙なんですが、単純にネストと行数が削減されるのでよいとおもいます

単純なカウンターの例を書いてみると

class HelloComponent extends React.Component<{}, {count: number}> {
    state = {
        count: 0;
    };

    render() {
        return (
            <button onClick={() => this.setState({count: this.state.count + 1})}>{this.state.count}</button>
        );
    }
}

function HelloComponent() {
    const [ count, setCount ] = useState(0);
    return (
        <button onClick={() => setCount(count + 1)}>{count}</button>
    );
}

になります

useEffectの第二引数

componentDidMountajaxしたりcomponentDidUpdateでrefからdomを操作したい場合はuseEffectの第一引数にコールバック関数渡してその中で処理するんですが、asyncなコールバックが渡せなかったり何も考えずにasyncな関数呼び出してその中でsetStateしてると無限ループしちゃうのでちゃんとドキュメント読みましょう

メッセージ一覧を取得して表示する画面で最初書いてたコード(サンプル用に改変済み

import React, { useState, useEffect } from 'react';
import { Message } from '../entities';
import { MessageRepository } from '../repositories';

export default function Messenger() {
    const [ messages, setMessages ] = useState(new Array<Message>());

    const fetchMessages = async () => {
        setMessages(await MessageRepository.getAll());
    };

    useEffect(() => {
        fetchMessages();
    });

    return (
        <ul>
            {messages.map(m => <li key={m.id}>{m.text}</li>)}
        </ul>
    );
}

useEffectはstateやpropsが変更される度に呼び出されるので、この例ではsetMessagesを呼び出すたびにfetchMessagesが呼び出されて無限ループしてしまいますが、 useEffectの第二引数を使うことによって特定の値が変更された場合のみコールバックを実行するように制御できます

なので、ここに空の配列を渡してやることで初回だけ実行されるコールバックが実現できます

useEffect(() => {
    fetchMessages();
}, []);

これで初回だけ実行されるので、componentDidMount相当になります

最後に

ハマりポイント何個か書こうと思ったんですがほとんどハマらなかったので書くことがありませんでした

現状複雑な状態や副作用を持ってるコンポーネントだと大幅な改修が必要になっちゃうんですが基本的にコード量は減るので、新しいコンポーネント書くときには積極的に使うといいんじゃないでしょうか

RITでは新規事業に携わりたい方を様々な職種で募集しているので、ご興味ある方はぜひオフィスに遊びに来てください!

https://rit-inc.co.jp

昨年の振り返りと今年の目標

新年明けましておめでとうございます。CTOの福田です。

年始なので昨年の振り返りと今年の目標でも書いてみようと思います。

昨年の振り返り

マッチングサービス多いですね。 この他にも以前受託したプロダクトの改修案件や仕込み中の案件もあり、エンジニア3人(うち一人デザイナー兼務)で割とスピード感持って開発できたのではないでしょうか。

よかったこと

  • スピード感持って開発できた
    • だいたい1サービス2~3ヶ月でリリースできた
    • VIRECが最初で、そのマッチングサービスのノウハウで他サービスの基盤構築を短縮できた
  • 技術レベルの底上げができた
    • ほぼ新卒で未経験のエンジニアを採用したけど半年程度である程度RailsとReactが触れるようになってる(インフラはまだちょっと怪しい)

改善したいこと

  • マッチングサービス多すぎ
    • 中間マージンを排除するという世の流れもあるけどマッチングサービス以外も作りたい
  • 技術の幅を広げたい
    • 基本的にRails + React + AWS(Beanstalk)で固定されてしまっているので、どんどん新しい言語とかフレームワーク試してさらなる高速化に繋げたい

今年の目標

社内に蓄積されたナレッジで、サービス立ち上げを通じてチャレンジしやすい環境が整ってきたので、 様々なサービス立ち上げに対応できるような組織づくりを推進します。 具体的な目標は以下の通り

  • エンジニアとデザイナー増員
    • 技術の幅を広げて開発するサービスの幅も広げるために増員したい
    • 少なくとも今年2~3人は増やしたい
  • 開発部隊の年収15%以上アップ
    • 教育にちゃんとコストかけて外の世界でも魅力的な人材になってもらうとともに、それに見合った給与を支払えるように売り上げも作る
  • 6サービス以上リリース
    • 開発ラインを増やして前年比1.5倍で
  • イケてる技術をプロダクトに導入したい
    • GraphQL(AWS AppSync)とかFlutter興味あります

最後に

RITでは新しいサービスや技術が好きで、新サービスの立ち上げを通して成長したい方を募集しています。

まずはオフィスまで遊びに来てみませんか?

ActionMailerでdeliver_laterしてるテストが頻繁に止まるようになった話

内容は薄いですが結構ハマったのでメモ

結論

非同期処理内でDBからデータ取ってくるときはassert_enqueued_jobsとかassert_enqueued_emailsで囲んどこう

経緯

弊社のプロダクトではRuby on Railsを利用していてMinitestでテストを書いているんですが、最近になって頻繁にテストが失敗するようになりました。 成功する時もあれば失敗する時もあり、docker-composeでコンテナ立ち上げすぎて色々足りてないのかなーぐらいに思ってたのですが、大体止まったタイミイングのログを見るとActionMailer周りの処理だったので、非同期処理が何か悪さしてるのかと思って調査をはじめました。

予想

最近入れたactive_elastic_jobが悪いんじゃないか

最近非同期処理に影響を与えそうな改修をしたのはこれだったので一番怪しんでたのですが冤罪でした

そもそもtestのqueue_adapterにactive_elastic_job使ってなかったので関係ない

testのRollback先に走ってjobのdeserialize失敗してるんじゃないか

基本的にはログ出さずに止まってるだけだったのですが、たまにdeserializeでエラーログ出ることがあったので怪しいと思い調査 queueを処理してからテスト終わるようにしたらうまくいくんじゃないかと思ったので↓みたいにassert_enqueued_emailsで囲んでみたところ

class SampleTest < ActiveSupport::TestCase
  include ActionMailer::TestHelper

  test 'sample test' do
    assert_enqueued_emails 1 do
      Sample.test_method
    end
  end
end

ActiomMailer起因で失敗することがなくなりました

絶対失敗するわけじゃなかったり最近失敗が増えてきたのはコンテナの立てすぎでテストの処理が重くなってJobの処理する前にRollbackしちゃうケースが増えちゃったって感じなんですかね

JS/TSで特定ディレクトリ以下のモジュールをまとめてexport/importしたい

CTOの福田です

JSって書いてますがTSの話しかしませんごめんなさい

app.ts
├ modules
│  ├ a.ts
│  └ b.ts
└ ...

こんな感じのファイル構成があったときに、app.tsでmodules/aとmodules/bを使おうとすると

// modules/a.ts
export default class A {
}

// modules/b.ts
export default class B {
}


// app.ts
import A from './modules/a';
import B from './modules/b';

class App {
}

AとBを別々にimportする必要がある 2つぐらいだと問題ないけど増えてくると面倒

// app.ts
import { A, B } from './modules';

って書けると嬉しい

とりあえず一つのファイルでimportしてそこからexportしてみる

// modules/modules.ts
import A from './a';
import B from './b';

export { A, B };

// app.ts
import { A, B } from './modules/modules';

これだとmodules/modulesがダサい

index.(js|ts)ってファイルを置いておくと、親ディレクトリがimportされたときにそのファイルがimportされる(なんの仕様かはちゃんと調べてない)

// modules/index.ts
import A from './a';
import B from './b';

export { A, B };

// app.ts
import { A, B } from './modules';

index.tsでimportするモジュールをいちいち書かなきゃいけないのが面倒

// modules/a.ts
export class A {
}

// modules/b.ts
export class B {
}

// modules/index.ts
export * from './a';
export * from './b';

export * from 'xxx'でそのモジュールでexportしてるモジュールを全てexportできる ただ、default exportしてると動いてくれないのでexportにしてる

僕がやれたのはここまでなので、誰かJSとかTSのすごい人がいたら

import { A, B } from './modules';

import A from './modules/a';

ができてindex.tsを書くのが面倒じゃない良い方法あれば教えてください!

ActiveStorageでattachできるものについて調べてみた

福田です

ActiveStorage便利ですよね

railsのmodelにファイルを簡単に紐づけることができるんですが、form postされたファイルだけじゃなくてdirect uploadで発行された謎トークンとかもattachできて混乱しちゃったので、結局何がattachできるのかコード読んで調べてみました

↓はhas_oneなattachのコードです

# Associates a given attachment with the current record, saving it to the database.
#
#   person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
#   person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
#   person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
#   person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
def attach(attachable)
    blob_was = blob if attached?
    blob = create_blob_from(attachable)

    unless blob == blob_was
        transaction do
            detach
            write_attachment build_attachment(blob: blob)
        end
        blob_was.purge_later if blob_was && dependent == :purge_later
    end
end

どうやらattachは四種類の引数を受け取るようです

  • ActionDispatch::Http::UploadedFile
    • form postされたfile
  • signed_blob_id
    • ファイルそのものではなくてdirect uploadでアップロードされたファイルを表すID?
  • io
  • ActiveStorage::Blob
    • active_storage_blobsかな?
    • すでにattachされてるファイルを別のmodelに紐づけたりできそう

IOクラスのインスタンスを直接attachできるのは初めて知ったので試してみましょう

User model作って

class User < ApplicationRecord
    has_one_attached :avatar
end

適当に置いといたpngをattachしてみる

irb(main):012:0> f = File.open(Rails.root.join('storage', 'sample_avatar.png').to_s)
=> #<File:/app/storage/sample_avatar.png>
irb(main):013:0> User.first.avatar.attach(io: f, filename: 'sample_avatar.png', content_type: 'image/png')

urlを取得してみる

irb(main):023:0* include Rails.application.routes.url_helpers
=> Object
irb(main):024:0> default_url_options[:host] = "localhost:3000"
=> "localhost:3000"
irb(main):025:0> url_for User.first.avatar
  User Load (0.3ms)  SELECT  `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
  ActiveStorage::Attachment Load (1.9ms)  SELECT  `active_storage_attachments`.* FROM `active_storage_attachments` WHERE `active_storage_attachments`.`record_id` = 1 AND `active_storage_attachments`.`record_type` = 'User' AND `active_storage_attachments`.`name` = 'avatar' LIMIT 1
  ActiveStorage::Blob Load (0.4ms)  SELECT  `active_storage_blobs`.* FROM `active_storage_blobs` WHERE `active_storage_blobs`.`id` = 1 LIMIT 1
=> "http://localhost:3000/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--c0c210c1622d0f9c9d208352011ee63a9aa8d5f1/sample_avatar.png"

ちゃんとattachできてる

IOのインスタンスってことはopen-uriとか使ってweb上の画像とかも直接attachできるので、SNS連携で登録された時のデフォルトアバターとか設定するのに使えそうですね

tech blogはじめました & VIRECの裏側

初めまして、RIT CTOの福田(@gendaihyousyou)です。

RITにより多くの人から興味を持ってもらうために、エンジニアやデザイナーから知見を発信するtech blogを開設しました。

WEB系の技術を取り扱うことが多くなると思いますが、たまにコンサル系の記事も混じってくるかもしれません。

今回は初回ということでRITの紹介少しと現在開発中のサービスの裏側について書きます。

RITについて

会社の概要はホームページを見てもらうのが早いですが、実際にどんな仕事をしてるのかが想像しづらいかもしれないので補足してみます。

人材と仕事内容

2018年3月現在の社員数は8名で、大半がコンサルタントとして社外に常駐しています。

このブログを見てる方はおそらくエンジニアのはずで、コンサルタントの業務についてよく知らない方が多いと思う(自分が知らなかっただけかも)ので簡単に説明しておくと、

  • 何か課題を抱えている企業がコンサルタントを雇って課題の解決をお願いする
  • コンサルタントが関係各所にヒアリングを行って課題を洗い出す
  • 自身の持っている知見を駆使して解決方法を提案する

って事をやってるみたいです。知見というのは具体的には"電話での問い合わせに問題を抱えていればAmazon Connectで解決した事例がある"みたいなのですね。

なのでコンサルタントは領域によってはエンジニアよりもAWSやAzureのサービスに詳しかったりします。

エンジニアとデザイナーは基本的に自社のオフィスに常駐していて、コンサルタントが提案したソリューションを受託開発したり、社内で提案された新サービスを開発しています。

出社していたほうがコミュニケーション取りやすいので基本的には出社してますが、体調不良や家で荷物受け取らなきゃいけないみたいな時はリモートでも作業をしてます。

技術スタック

  • インフラ
  • サーバサイド
  • フロントエンド
    • TypeScript, Webpack, React(no Redux), Bootstrap, Sass
  • その他利用ツール

がメインですが、高速に開発する事を第一としているので、より良い選択肢があれば常にそれを試せるようしています。

現在開発中のサービスの裏側

現在RITではVIRECという、フリーのコンサルタントとクライアント企業をマッチングするサービスを開発しています。

普通のWEBアプリケーションなのですが、開発の特徴としては

  • Elastic Beanstalkにもろもろお任せ
  • だいたい使える限り最新のバージョンを使う
    • MySQL 5.7
    • Ruby 2.4
      • Beanstalkのプラットフォームで2.5系がまだ選べなかったので仕方なく
    • Rails 5.2
      • ユーザに紐づいた動画とか扱うのにActiveStorageをすごく使いたかったのでbeta版から使ってる
    • TypeScript 2.7
    • React 16.2
    • Webpack 4.0
  • 開発環境はdocker-composeにお任せ
  • DDD, TDDを一部採用

みたいな感じです。

バージョンに関してはこれで固定ではなく、可能な限りstable版が出ると同時ぐらいのタイミングで各々入れ替えていく事を目標にしています。

また、利用している技術スタックに関しても同様で、より良い方法があればフットワーク軽めに色々試してみる予定です。

最後に

VIRECの新機能開発やその他新サービスの開発など、開発するものは色々あるのにエンジニアの手が足りない状況です。

もしRITで働く事に興味があれば、ぜひ一度オフィスに遊びに来てください!

会社の問い合わせ用のアドレスにメール送ってもらうか僕のアカウントに連絡してもらえれば対応します!