Wiz テックブログ

Wizは、最新のIoTやICTサービスをお客様に届ける「ITの総合商社」です。

Nuxt Bridgeを使ってみて使用法、所感まとめ

こんにちは、フロントエンドエンジニア小玉です。

先日Nuxt3ベータ版のリリースが発表されましたね。 Nuxt3ではVue3Viteのサポートに加えて。新しいサーバエンジンが搭載されるそうです。 いくつか新しい機能やアップデートがなされた中で、今回はNuxt Bridgeについて試してみたいと思います。

Nuxt Bridge

こちらは端的に言うと、Nuxt2を使用しているプロジェクトをよりスムーズにアップグレードするためのシステムです。

公式には以下のように書かれてます

Bridge is a forward-compatibility layer that allows you to experience many of new Nuxt 3 features by simply installing and enabling a Nuxt module.

(Bridgeは、Nuxtモジュールをインストールして有効にするだけで、Nuxt3の新機能の多くを体験できる上位互換性レイヤーです。:google翻訳

主な機能として

  • Nitroサーバーが使用できる
  • CompositionAPIが使用できる(Nuxt3と同じ)
  • 新しいCLIとDevtoolsが使用できる

などなど。 これまでNuxt2を使用していたプロジェクトをアップグレードするためにぜひ活用したいサービスですね。

使用手順

では早速公式に則ってNuxt Bridgeを使用してNuxt2プロジェクトをアップグレードしてみたいと思います。

Nuxt Bridgeのインストール

$ yarn add --dev @nuxt/bridge@npm:@nuxt/bridge-edge
or
$ npm install -D @nuxt/bridge@npm:@nuxt/bridge-edge

※余談ですが、僕はnodeのバージョンが古くてインストールに一度失敗しました。

The engine "node" is incompatible with this module. Expected version "^14.16.0 || ^16.11.0 || ^17.0.0". Got "15.8.0"

nodeのバージョンは上げておきましょう…。

スクリプトの更新

Nuxt3では新しくnuxiというCLIが導入されました。 そちらを使用するためにpackage.jsonを以下のように更新します。

  "scripts": {
-   "dev": "nuxt-ts",
+   "dev": "nuxi dev", //nuxi のみではダメ
-   "build": "nuxt-ts build",
+   "build": "nuxi build",
+   "start": "node .output/server/index.mjs",
-   "generate": "nuxt-ts generate",
+   "generate": "nuxi generate",
  },
"dependencies": {
-   "nuxt": "^2.15.7"
+   "nuxt-edge": "latest"
  },

nuxt.config.js

module.exportsrequireなど、Common.jsがサポートされなくなるそうなので、 nuxt.config.jsを以下のように書き換える必要があります。

export default {
  ssr: false,
 ......
}
import { defineNuxtConfig } from '@nuxt/bridge'

export default defineNuxtConfig({
  ssr: false,
 ......
})

以上で設定は完了です。

まとめ

今回は先日リリースされたNuxt Bridgeを試してみると言うことで、 自分はNuxt2を使用したいくつかのプロジェクトをNuxt Bridgeでアップグレードしてみました。 大方問題なくアップグレードができましたが、もちろんベータ版ということもあり、

  • tailwindcss が非対応
  • @nuxt/content(1.x)がサポートされない、(2.x)に関しては書き換えが必要

とのことでした。 自分は@nuxt/contenttailwindcssを使用したブログも作っていたのでそれはうまくアップグレードできませんでした。

未だ対応していないモジュールもありますが、Nuxt3ではNitroエンジンの搭載やTypescriptのサポート、Auto Importなど恩恵は数々あります。正式版のリリースが期待できますね。

v3.nuxtjs.org

最後に

Wizではエンジニアを募集しております。
興味のある方、ぜひご覧下さい!

careers.012grp.co.jp

MySQLの実行計画について

こんにちはバックエンドエンジニアの小室です。

業務では主にLaravelを使って実装しています。現在担当している案件の検索機能の実装が複雑でクエリビルダーでは事足りず生SQLで実装する必要があり、改めてSQLの重要性を実感しました。そこで最近学んだSQLの実行計画について簡単にまとめてみました。

実行計画

実行計画とは、テーブルに対して検索をかけた際、どういった手順を踏んでアクセスしたかを示す実行手順書のようなものになります。

データ量や統計情報(オプティマイザ統計)などの情報をもとに、最適な実行計画がされますが、 同じSQLであればいつも同じ実行計画が作成されるとは限らず、データ量が大きく変更されたときや、統計情報が古いままだと適切な実行計画が作成されず、パフォーマンスが低下する場合もあります。

実行計画の確認方法

mysqlで実行計画を確認するにはselect文の先頭に「EXPLAIN」をつければ確認できます。

EXPLAIN 
    SELECT departments.id, departments.name d_name, companies.name c_name 
    FROM departments 
    INNER JOIN companies
    ON departments.company_id = companies.id

以下の様な表が表示されます。

id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE departments ALL departments_company_id_foreign 9 100.0
1 SIMPLE companies eq_ref PRIMARY PRIMARY 8 practice_db.departments.company_id 1 100.0

DBeaverというツールを使えば、実行計画をツリー構造で表示する事もできます。「https://dbeaver.io/」からインストールできます。 ほぼ全てのDBに対応しており、実行計画の表示から、ER図の自動作成など便利な機能が豊富です。

f:id:shuto_komuro:20211106152701p:plain
DBeaver-実行計画

各カラムの説明

  • id

SELECT 識別子を表し、クエリー内の SELECT の連番になります。

  • select_type

SELECTの種類を表し、SIMPLE,PRIMARY,UNIONなどがあります。

  • table

出力の行で参照しているテーブルの名前を示します。

  • partitions

クエリーでレコードが照合されるパーティションを示します。

  • type

結合のタイプを表し、ALL,CONST,eq_refなどがあります。

  • possible_keys

テーブル内の行の検索に使用するために選択できるインデックスを示します。

  • key

実際に使用することを決定したキー (インデックス) を示します。

  • key_len

実際に使用することを決定したキーの長さを示します。

  • ref

テーブルから行を選択するために、key カラムに指定されたインデックスに対して比較されるカラムまたは定数を示します。

  • rows

クエリーを実行するために調査する行数を示します。(推定数)

  • filtered

テーブル条件によってフィルタ処理されるテーブル行の推定の割合を示します。

  • Extra

クエリーを解決する方法に関する追加情報が含まれます。(where句など)

より詳しい説明は以下の公式リファレンスを参照してください。 MySQL :: MySQL 5.6 リファレンスマニュアル :: 8.8.2 EXPLAIN 出力フォーマット

チューニングサンプル

unionとcase分

以下のような都市別、男女別の人口を示すpopulationsテーブル(table1)があるとします。

このテーブルから、都市別に性別を1レコードにまとめた結果(table2)を

出力したいとします。

table1

id city_name sex population
1 都市1 1 63
2 都市1 2 99
3 都市2 1 39
4 都市2 2 93
5 都市3 1 42
6 都市3 2 32
7 都市4 1 38
8 都市4 2 67
9 都市5 1 79
10 都市5 2 59

table2

city_name p_men p_wom
都市1 63 99
都市2 39 93
都市3 42 32
都市4 38 67
都市5 79 59

unionを使った解

都道府県別に男性の合計値を求めた後、都道府県別に女性の合計値を求め それらの結果をマージするという手順になると思います。

sqlは以下の様になります。

SELECT city_name, sum(p_men) AS p_men, sum(p_wom) AS p_wom 
FROM (
    SELECT city_name, population AS p_men, NULL AS p_wom 
    FROM populations WHERE sex = 1
    UNION 
    SELECT city_name, null AS p_men, population AS p_wom 
    FROM populations WHERE sex = 2
) tmp 
GROUP BY city_name

以下の実行計画が出力されます。

id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 PRIMARY ALL 4 100.0 Using temporary
2 DERIVED populations ALL 10 10.0 Using where
3 UNION populations ALL 10 10.0 Using where
UNION RESULT <union2,3> ALL Using temporary

populationsテーブルに対してフルスキャンが2回実行されていることがわかります。

UNIONを使えば、問題を小さなサブ問題に分割して考えることができますが、

内部的に複数のSELECT文を実行する実行計画として解釈されるためI/Oコストが膨らみませす。

CASE式を使った解

CASE式を使えばアクセスを1回に減らしコスト改善が可能です。

CASE式を集約関数内に収め、男性だけの人口と女性だけの人口の列を作る方法です。

sqlは以下の様になります。

SELECT
    city_name,
    sum(CASE WHEN sex = 1 THEN population ELSE 0 end) AS p_men, 
    sum(CASE WHEN sex = 2 THEN population ELSE 0 end) AS p_wom
 FROM populations
GROUP BY city_name

以下の実行計画が出力されます。

id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE populations ALL 10 100.0 Using temporary

populationsテーブルに対してフルスキャンが1回のみとなり、UNIONを使った解に比べ1/2のI/Oコストで済みました。

このように、実行計画を通しアクセスパスを確認する事で冗長なSQL文を改善することができました。

今まで、フレームワークのクエリビルダに頼りきりでしたが、

SQLを遅延させないためにも、実行計画を意識する習慣をつけていけたら良いなと思います。

最後に

Wizではエンジニアを募集しております。
興味のある方、ぜひご覧下さい!

careers.012grp.co.jp

Next.jsのmiddlewareを使ってbasic認証を実装する

こんにちは、フロントエンドエンジニアの髙橋です。

先日Next.js ConfでNext.js 12は発表されましたね!

色々な新機能が追加されましたが、新機能のMiddlewareが気になって試してみたのでその使い方などを書いてみようと思います。

middlewareとは?

公式には以下のように書かれております!

Middleware enables you to use code over configuration. This gives you full flexibility in Next.js because you can run code before a request is completed.

リクエスト完了前に特定のコードを実行できる機能のようです。

*公式ドキュメントはこちら

例えば、こういった場合↓に使用することができるそうです。 f:id:sotq17:20211105150022p:plain

何を試したか

今回は1番に例に上がっているAuthentication(認証)を試してみました。 具体的に言えば、Basic認証をNext.jsにつける、という作業です。

f:id:sotq17:20211105152843g:plain

Next.jsで作ったサイトをVercelにあげる場合、コンテンツに制限をかけることは通常有料となってしまいます。

ライブラリを導入して、Basic認証をつけることも可能なようですが、公式の機能でできればそれが一番なのでは無いかと思っております!

そんなわけで、早速試してみたいと思います!

実装

プロジェクト/ファイル作成

npx create-next-app@latest --ts

create-next-appで雛形を作ったあと…

f:id:sotq17:20211105153303p:plain

pages/_middleware.tsを作成します。 こちらがmiddlewareを扱うファイルとなります。

認証処理を書いていく

早速ですが、以下のコードを貼り付けるだけでOKです! (解説はコード内に記載します)

import { NextRequest, NextResponse } from 'next/server'

export const middleware = (req: NextRequest) => {
  const basicAuth = req.headers.get('authorization')
  //HeaderにAuthorizationが定義されているかをチェック
  if (basicAuth) {
    const auth = basicAuth.split(' ')[1]
    const [user, pwd] = Buffer.from(auth, 'base64').toString().split(':')

    // basic認証のUser/Passが、envファイルにある値と同じかをチェック
    if (user ===  process.env.NEXT_PUBLIC_USER && pwd === process.env.NEXT_PUBLIC_PASS) {
      return NextResponse.next()
    }
  }

  // 同じでなければエラーを返す
  return new Response('Auth required', {
    status: 401,
    headers: {
      'WWW-Authenticate': 'Basic realm="Secure Area"',
    },
  })
}
// .env.local
NEXT_PUBLIC_USER=XXXXX
NEXT_PUBLIC_PASS=XXXXX

これでローカルを立ち上げればBasic認証がかかるはずです!

公開する

当たり前ではありますが、Vercel上にenvファイルは置けないので環境変数を設定します。

以下の通りに設定すればlocal同様にBasic認証がかかります。

f:id:sotq17:20211105154918p:plain

まとめ

実際に作ってみて、想像の何倍も簡単に実装することができたと思っています。

相変わらずのVercel依存はありますが、ここまで便利になるなら使わない手はないのでは?と最近考えるようになりました。

ちなみにこちらがサンプルのGitHubです!

Next.js 12ではmiddlewareの他にすごい機能がたくさんあります。(SWCめっちゃ早いです…!)

気になる方は他の機能もチェックしてみてはいかがでしょうか。

最後に


Wizではエンジニアとして一緒に働く仲間を絶賛募集しております。

ご興味のある方、是非ご覧下さい..!!

careers.012grp.co.jp

Mock Service Workerを使ってOpenAPIに寄り添ったテストを行う。

Tech事業部プロダクトチームの仲本です。

10月から、フロントエンドチームからプロダクトチームになりました。

今回は、現在開発しているプロダクトに、テストを導入するにあたって、Mock Service Workerを導入し、OpenAPIと組みわせてMockAPIを作成しテストを作成しました。

今回の記事は導入記録/所感的なものを書いています。

テストを導入する経緯

  • フロントエンドのテストがまずできていなかったこと
  • 社内業務案件で、リリースのたびにチームで手作業でテストを行っていたこと
  • 今後規模が大きくなると、手作業でのテストが辛くなる

こういった経験のもと、テストを導入し安全性を確保したいということになりました。

jest.mockによるmock化がかなり多くなる話

実際にjestを導入して、テストを書いていくとmock化しないと通らないテストがあること気づきました。

apiを実行する際のfetchの処理などもmock化させると、実際の動きと遠くなるなるのではないかという懸念があり調べているとMock Service Workerに出会いました。

Mock Service Workerとは

Service WorkerAPIを使用して実際のリクエストをインターセプトするAPIモックライブラリです。

ユーザーログイン画面のテスト実装

今回はログイン画面のテスト実装していきます。

Login画面で表示するコンポーネントが以下になります。

import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { jsx } from '@emotion/react'
import { useForm, UseFormReturn } from "react-hook-form"
import { BrowserRouter as Router, useHistory } from 'react-router-dom'
import { hot } from 'react-hot-loader'

import {
  fetchAsyncLogin,
  selectError,
} from '../../../stores/slices/authSlice'


// component
import { Button } from '../../components/atoms/Button'

// style
import { LoginBox, LoginTitle } from '../../../style/pages/Login'
import { Form, FormLabel, FormInput, FormButton, FormValidButton, FormValidTxt } from '../../../style/components/block/Form'


//type
import { AppDispatch } from '../../../stores';
import { LoginFormInput } from '../../../../types/user';

const Login = () => {
  const methods: UseFormReturn<LoginFormInput> = useForm<LoginFormInput>();
  const { register, handleSubmit, formState: { errors }, reset } = methods
  const dispatch: AppDispatch = useDispatch()
  const history = useHistory()

  const onSubmit = async (data: LoginFormInput) => {
    const result = await dispatch(fetchAsyncLogin(data))
    if (fetchAsyncLogin.fulfilled.match(result)) {
      if (result.payload.status == 200) {
        history.push('/')
      }
    }
    reset()
  }

  return (
    <div>
      <div css={LoginBox}>
        <form css={Form} onSubmit={handleSubmit(onSubmit)}>
          <label css={FormLabel}>
            メールアドレス
            <input
              autoComplete="email"
              type="email"
              aria-invalid={errors.mailAddress ? "true" : "false"}
              {...register("mailAddress", { required: true, pattern: /^([a-zA-Z0-9])+([a-zA-Z0-9\._+-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/ })}
              placeholder="メールアドレスを入力してください"
              css={FormInput}
            />
          </label>
          <label css={FormLabel}>
            パスワード
            <input
              autoComplete="password"
              type="password"
              {...register("passWord", { required: true, pattern: /^[a-z\d]{1,100}$/i })} aria-invalid={errors.passWord ? "true" : "false"} placeholder="パスワードを入力してください"
              css={FormInput}
            />
          </label>
          <Button name="ログイン" cssStyle={FormButton} onClick={handleSubmit(onSubmit)} dataTestId="test-2-submit-btn"  />
        </form>
      </div>
    </div>
  )
}
export default hot(module)(Login)

要件としては

  • メールアドレスが入力できる

  • パスワードが入力できる

  • ログインボタンを押すとログイン用APIにPOSTする

流れになります。

レスポンスデータをOpenAPI定義から取得する

現在携わっているプロジェクトでは、OpenAPIを使用してAPIのやりとり/認識合わせを行なっています。 OpenAPI仕様が記載されたjsonファイルを使用し、Mock Service Workerの設定を行います。

そうすることで、より使用されているAPIを忠実に再現できることや、もし仕様の変更があった際もしっかり新しい情報の入ったOpenAPIを取り込めていたら気付けるのではないかと思い使用しました。

以下がOpenAPIで定義している内容になります。

// openapi.json

"paths": {
  "/api/login": {
// ~~省略~~
  "responses": {
    "200": {
      "description": "HTTP OK",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
// ~~省略~~
            "examples": { // <= 今回レスポンスで使用するデータ
              "default": {
                "value": {
                  "status": 200,
                  "response_time": 1.1535649299621582,
                  "message": "処理が正常終了しました。",
                    "data": {
                      "login_id": "test@hoge.co.jp",
                      "token": "123|njGYLOG9EuZuIrSv83dUvnIWzFLbo6Ri5mUOLm4q",
                    }
                 }
              }
            }

上記のjsonファイルをimportし、examplesを以下のファイルでimportします。

この設定をすることにより、jsonファイルに変更があった際に変更に対応してくれます。

import schema from '../../openapi.json'

const components = {
  LoginUser: schema.paths['/api/login'].post.responses[200].content['application/json'].examples.default.value,
}
export default components 

Mock Service Workerでhandlerを設定

次にmockを作成していきます。

mswからrestをimportしgetやpostの設定を行います。

今回はpostの設定を行います。

参考: mswjs.io

以下ファイルで定義している内容としては

  • login_idがtest@hoge.co.jpかつpasswordがtest1234@

  • 上の条件を満たしていた時ステータス200と、先ほど定義したexamplesを返す設定をしています。

import { rest } from 'msw'
import components from './components'

const handlers = [
  rest.post<Record<string, any>>('http://localhost:3000/api/login', (req, res, ctx) => {
    const { login_id, password } = req.body
    if (login_id === 'test@hoge.co.jp' && password === 'test1234@') {
      return res(
        ctx.status(200),
        ctx.json(components.LoginUser)
      )
    } else {
      return res(
        ctx.status(403),
        ctx.json({
          error: 'error: invalid username or password'
        })
      )
    }
  }),
]

export { handlers } 

jest.setup.js

jest.setup.jsというファイルで、先程設定をしたmockを動かすためのserverの設定と

node環境だと、window.fetchが使えないので node-fetchをinstallして設定を行います。

github.com

import server from './src/test/lib/msw/server'
import { cleanup } from '@testing-library/react'
import fetch from "node-fetch"

beforeAll(() => server.listen())
afterEach(async () => {
  server.resetHandlers()
})
afterAll(() => server.close())

if (!globalThis.fetch) {
  globalThis.fetch = fetch
}

実際のテストコード

import React from 'react';
import '@testing-library/jest-dom/extend-expect'
import { fireEvent, getAllByText, render, screen, waitFor } from '@testing-library/react'
import { Provider } from 'react-redux';
import { act } from 'react-dom/test-utils';

import store from '../tsx/stores';

import Login from '../tsx/views/pages/login/Login'


const LoginComponent =
  <Provider store={store}>
    <Login />
  </Provider>

const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
  useHistory: () => ({
    push: mockHistoryPush, // pushメソッドをダミー関数で上書きする。
  }),
}));

〜〜省略〜〜
describe('test 2 ログイン処理', () => {
  it('ログイン確認', async () => {
    const { getByTestId } = render(LoginComponent)

    await act(async () => {
      fireEvent.change(screen.getByLabelText(/メールアドレス/i), {
        target: { value: 'test@hoge.co.jp' },
      });

      fireEvent.change(screen.getByLabelText(/パスワード/i), {
        target: { value: 'test1234@' },
      })
    });

    await act(async () => {
      fireEvent.submit(getByTestId('test-2-submit-btn'))
    });

    await waitFor(() => {
      expect(mockHistoryPush).toBeCalledWith('/');
    })
  });
}) 

ログインボタンを押した時のAPI処理のみのテストを表示しています。

ログインボタンを押すと先程handlerで設定したMockを実行するようになっています。

以上がユーザーログイン画面のテスト実装でした。

今後考えていきたいこと

  • どの範囲をテストしていくべきかを明確にする
  • 別のプロジェクトに導入するために、どういった開発手法がいいかを考えていく
  • まだ導入したてなので色々実装していきながらベストを探していきたい

上記をまず、明確にできるように頑張っていきたいと思います。

参考記事

OpenAPI定義をmswに活用してお手軽モック

Mock Service Worker で jest.mock を使わず非同期リクエストのテストを書く

最後に

Wizではエンジニアを募集しております。
興味のある方、ぜひご覧下さい!

careers.012grp.co.jp

【Laravel】DDDでセッションを取り扱う際の妥協した実装

こんにちは、バックエンドエンジニアの青山です。
先日LaravelでDDDするときのセッション取り扱いに困った末、ある程度いい感じに妥協した実装に辿り着きました。今回はそちらについて書こうと思います。

はじめに

「LaravelでDDD〜」と書きましたが、本記事の内容は正確には「Laravelでレイヤードアーキテクチャを採用する際のセッションの取り扱いの一例について」です。 DDDを行う中で出会った問題に対する解決策の一例なのでタイトルは上記のようにしました。

仕様

セッションの取り扱いと一言に言っても、その用途は多岐にわたります。今回は「Webサイトの多言語化」という仕様を例に実装します↓

  • サイト上にドロップダウンメニューがあり、そこから使用言語(日本語 / 英語 / 中国語 / ベトナム語 / ポルトガル語など)を選択できる
  • 使用言語を選択したらサイト上の一部のテキストが選択した言語に翻訳されて表示される
  • サイトには管理者用ページとユーザー用ページがあり、翻訳はユーザー用ページ全体でのみ行う

実装

タイトルにある通り今回の趣旨は妥協です。困ったらLaravelの機能を利用したりして、いい感じに手を抜きます。
Laravelにもともと実装されている多言語化機能はこちらを参照してください。

前提

レイヤー

既存の部分は Domain層(Domain modelやFactoryクラス、Repositoryのinterfaceなど)
Infrastructure層(RepositoryやQueryServiceの実装クラスなど)
UseCase層(UseCaseクラス、QueryServiceのinterfaceなど)
Presentation層(ControllerやFormRequest、Middlewareなど)
の4層に分割して実装されています(その前段階で文脈ごとに大きく分割されています。詳しくは以降のディレクトリ図参照)。

今回の設計では依存の向きは所謂レイヤードアーキテクチャのようにInfrastructure層に向かうものではなく、図の矢印で示すようにDomain層へ向かうものとなっています。 多言語化のためのセッションの取り扱いも、既存の作りと同じように層を分けて実装します。

全体の流れ

1.サイトのトップページのメニューから使用言語を選択
2./lang/:localeにGETリクエスト送信。localeには"ja"や"en"など文字列のパラメーターが入る想定
3.セッションにlocale値設定
4.もとのページにリダイレクト(このときmiddlewareでlocale設定)

もう少し詳細に書くと
リクエスト -> Controllerでパラメーター取得 -> UseCaseクラスに渡して色々する(この"色々"の内部でセッションに値を保存したりする)
みたいな感じです。

ディレクトリ構成

localeはユーザー側ページ全体で取り回される共通のものなので、どこに置くべきか迷いました。
現在以下のように分割されています

│   ├── Packages
│   │   ├── AdminPage //管理者用ページ
│   │   ├── FrontPage //ユーザー用ページ
│   │   │   ├── Appointment
│   │   │   ├── Contact
│   │   │   ├── Home
│   │   │   └── StaticPages
│   │   └── Shared

考えた結果、FrontPage全体で共通のものということでSharedというディレクトリを作成しました。
もはや何も考えていないのと同じですが、いい感じに妥協していくのが今回の趣旨です。どんどん妥協しましょう。

│   ├── Packages
│   │   ├── AdminPage
│   │   ├── FrontPage
│   │   │   ├── Appointment
│   │   │   ├── Contact
│   │   │   ├── Home
│   │   │   ├── Shared //<-追加
│   │   │   │   ├── Domain
│   │   │   │   ├── Infrastructure
│   │   │   │   ├── Presentation
│   │   │   │   └── UseCase
│   │   │   └── StaticPages
│   │   └── Shared

Domain model

今回の場合localeを司るモデルを作るのがいいかなと判断しました。ということでlocale用のDomain modelを作成します。以下のようにモデリングしました。

gistc00482616b954c51babb350dc151ad9d

│── Shared
├── Domain
│   └── Models
│       └── Locale.php
├── Infrastructure
├── Presentation
└── UseCase

セッションを取り扱うクラス

このクラスをShared以下のどの層に配置するかを考えます。そもそもどういうクラスなのかというと

  • localeの値をセッションに保存する
  • セッションに保存されているlocaleの値を確認する

この2つを行うクラスです。localeの値とセッションを使ってxxをする、という役割なのでUseCase層が適切かと思います。interfaceを作成してUseCase層に置きます。

gist5eb0257e22b7ceca7a2af9df60f1d2dc

│── Shared
├── Domain
│   └── Models
│       └── Locale.php
├── Infrastructure
├── Presentation
└── UseCase
   └── LocaleSessionInterface.php

次にこのinterfaceの実装クラスを作成し、Presentation層に配置します。

gist1150419297fcfac1fdc7c5d5c1728fed

getLocaleSession()から返すDTOは以下のようになっています。

gistfbe39c56e70a119b97fb9faad28038df

├── Shared //<-追加
│   ├── Domain
│   │   └── Models
│   │       └── Locale.php
│   ├── Infrastructure
│   ├── Presentation
│   │   ├── Dto
│   │   │   └── GetLocaleSessionResponse.php
│   │   └── LocaleSession.php
│   └── UseCase
│      └── LocaleSessionInterface.php

interfaceと実装クラスはAppServiceProviderでbindするのを忘れないでください。

実装クラスをPresentation層に置く理由

最初はinterfaceと同じUseCase層に置こうと考えました。しかし実装クラスではLaravelのsession()ヘルパーを利用して「具体的にどうするか」を記述しています。UseCase層にそこまで具体的なものを置くのはよくなさそうです。
残るはDomain、Infrastructure、Presentationの三つの層ですが、そもそも何のためにセッションにlocaleの値を保存したりするのかというと、サイト上に表示されるテキストをlocaleに応じて変更するためでした。それを考えるとPresentation層に実装クラスを置くことが適切だと思われます。

locale値を変更するUseCaseクラス

Controllerから呼び出されるクラスです。リクエストがきたらControllerからこのクラスにパラメーターを渡して後続の処理を行います。 パラメーターからLocaleのインスタンスを作成し、パラメーターの値をセッションに保存します。セッション保存の具体的な処理は上記のLocaleSessionクラスで行われるので、ここには抽象的な手続きのみ記述します。

gist05956c45ab6f0dcd4531d471c5fc6d28

こちらのクラスはトップページから使用言語を選択した時のUseCaseなので、Shared以下ではなく該当するコンテキストのディレクトリ以下に配置します。

├── Home
│   ├── Domain
│   ├── Infrastructure
│   ├── Presentation
│   └── UseCase
│       └── SetLocaleUseCase.php

Controller

こちらは特筆すべき部分はありません。素直に実装します。

gist2a5bade46210839f30a9c987b32f1ff0

├── Home
│   ├── Domain
│   ├── Infrastructure
│   ├── Presentation
│   │   ├── Controllers
│   │       └── LocaleController.php
│   └── UseCase
│       └── SetLocaleUseCase.php

現在のlocaleを設定するmiddleware

Laravelのlocale設定は

app()->setLocale('locale_value')

で実現できます(Facadeを使用する方法もあります)。この部分はLaravelのmiddlewareで行い、ユーザー側の全ページに対して適用します。

gist860ff2f0cdde0dc7d3d2433b83decf6e

app/Http/Kernel.phpの$routeMiddleware配列に忘れず追加しておきます。

['set.locale' => \App\Packages\FrontPage\Home\Presentation\Middleware\SetLocaleMiddleware::class,]
├── Home
│   ├── Domain
│   ├── Infrastructure
│   ├── Presentation
│   │   ├── Controllers
│   │    │    └── LocaleController.php
│   │   ├── Middleware
│   │         └── SetLocaleMiddleware.php
│   └── UseCase
│       └── SetLocaleUseCase.php

ルーティング

gistfd3a74cc91d52ece76a6a23a2fa23c83

これで一通りの実装が完了しました。後は公式ドキュメントに従って言語ファイルを作成すれば、@langディレクティブや__()ヘルパーを使用して翻訳文字列が取得可能です。

メリット

この実装のメリットは、「ある文脈におけるセッションの取り扱い方法が限定されているので、コード内に散乱しがちなヘルパーやFacadeの乱用を抑制できたり、セッションのキーにする文字列の管理がしやすくなる」ということだと考えています。また、文脈ごとに区切ってセッションの取り扱いを限定するという実装は、セッションを取り扱う他の様々な場面でも応用できるのではと思います。粒度も場合によりけりだと思いますので、もっとスコープを広くして汎用的なセッション取り扱いクラスを作ってもいいかもしれません。

最後に

Wizではエンジニアを募集しております。
興味のある方、ぜひご覧下さい!

careers.012grp.co.jp

参考図書: ドメイン駆動設計 モデリング/実装ガイド - little-hands - BOOTH

現代を支配する究極のエンジニア像

f:id:daigo2895:20211012163521j:plain はじめまして!フロントエンドの久保です。

今回は前回の【営業部 → エンジニア】へジョブチェンジした話に続き、
「理想のエンジニア像」についてお話します。
目指すエンジニア像は人それぞれなので一つの参考になれば嬉しいです。

まずは結論から

プログラムが書けて、デザインが出来て、マーケティングも出来るエンジニアです!
・・・なんじゃそれ(笑)

実現させるには

そんな野球少年がプロ野球選手になりたい!と言ってるわけではないのです。
実はきちんとそういうポジションがあります。
それを説明させてください。

その名もグロースハッカー

Growth(成長)Hackerといい、
急激な成長を遂げた世界的なウェブサービスFacebookTwitterDropboxなどの成長を支えたと言われています。

業務内容としては、端的にいうと最低限のコストでユーザーをよりエンゲージメント(顧客・ユーザーの愛着度)の高い状態に持っていく事です。
言い換えると、トライアンドエラーを繰り返しながら新たな成果を作り出すことだと言えます。
そして、優秀なグロースハッカーほど、広告費やマーケティング費をかけずに任務を遂行するといわれています。

用語自体は2010年に出来た言葉ですが、近年多様なサービスや類似のサービスが増えてきてる中で 顧客に向き合い、商品・サービスに最も合った戦略を自ら模索していく姿が求められることから需要が急激に伸びてきています。

なぜグロースハッカーを目指すのか

私は営業部時代に様々なサービスを取り扱ってきました。
そこで見たものは、クオリティに関係なくユーザーにいい見せ方をしているサービスが数字を伸ばすという悲しい現実です。
逆に同業他社と比べて優位性のあるものも何故か競り負けている状態です。

それを改善するにはマーケティングの見直し、そして実装をするエンジニアが必要なことがわかりました。
しかし現状自社にはその2つが出来る人材はおらず、他業種とのコミュニケーションにおいてもなかなかもどかしい経験をしました。

その経験から私は、
おぼろげながらもその課題を解決出来るような人材になりたいと思うようになり、
そこから現在、グロースハッカーという明確な目標が生まれました。

目指すのは一つ、
「本当に良いサービスが正しくユーザーに届くようにしたい」です。

必要なスキル

グロースハッカーとは具体的に何が出来る人なのかざっくり2つに分けて説明します。

① データ解析能力

サービスを成長させるためにアクセス分析は勿論、カスタマー分析、事業分析、マーケティング分析など総合的に行います。
また、グロースハッカーが打ち出す施策は、勘やこれまでの経験、定性的な情報では行わず、
定量的(数値化出来るもの)なデータを分析した結果に基づいて行われます。
そのため、分析ツールを使いこなす知識や統計学に関する知識も求められます。

② 問題点を把握し、仮設を立て、解決する能力

①で提案したものを自らが実装出来るのがグロースハッカーの強みだと考えます。
これによって従来よりもより細やかなトライアンドエラーが実行出来ることとなり、サービスとしての力強さが実現するのです。

これはシンプルな仕事に思えますが、辛抱強く膨大なデータを検証し、徹底したA/Bテストを実施することが非常に重要なのです。
グロースハックのために打ち出す施策の75%〜90%は失敗に終わると言われています。
その中で地道に素早いサイクルで実行し続ける「不屈の精神」も必要となってきます。

5段階のフレームワーク「AARRR」

グロースハックには「AARRR(アー)」というフレームワークがあります。
具体的には、下記となります。

① Acquisition(ユーザー獲得)
② Activation(活性化)
③ Retention(継続)
④ Referral(紹介)
⑤ Revenue(収益化)

これが実行出来る人材になれば市場での価値は勿論のこと、
自分が思い描くエンジニア像を実現出来ると考えます。

WEBでの集客、交流が主流をなっている今、
1人でも実行出来る人材が増えたらもっともっとエンジニア市場の価値が上がり、報酬も上がっていくことでしょう。

求められる資質「ABCDE」

グロースハッカーに求められる資質を紹介します。

① Analyticity(分析力に長けている)
② Broad interest(好奇心に富んでいる)
③ Creative(創造性が豊かである)
④ Discipline(自らを律し、地道に取り組める)
⑤ Empathy(ユーザーの気持ちに共感できる)

これを見ると、いかにユーザーを想い地道に分析出来るかが重要となってくることが わかります。
ちなみに現時点では「B,D,E」を持ち合わせていると思っています!
色々な経験をし、分析力や創造性を高めていけるように努力したいと思います。

参考

グロースハッカーとは?シリコンバレーでも注目される最先端のWEBマーケティング職 | 株式会社EXIDEA

グロースハックとは?〜サービスを急成長させる方法と実践のための6ステップ〜 | ProSharing Consulting(プロシェアリングコンサルティング)

最後に

いかがだったでしょうか。 この記事でグロースハッカーを知ったという人もいるのではないでしょうか。 まだまだ書ききれなかったことも多くありますので少しでも興味を持った方は一度ググってみるのも良いかもしれません。

営業からエンジニアにジョブチェンジし半年が経過しようとしています。 まだまだ出来ることは少ないですが今までの経験を生かし、やるからには希少なエンジニアになるべく努力をしていきます。

そして…

Wizではエンジニアを募集しております。
興味のある方、ぜひご覧下さい!

careers.012grp.co.jp

EMトライアングルから見るWizのエンジニアリングマネージャーの現役割と、さらなる成長に向けて組織再編した話

こんにちは、株式会社Wizでエンジニアリングマネージャー(以下EM)をやっている上野です。

バックエンドエンジニアとして活動したのちEMとなり、数年経過しました。

今回、WizのEMがどんな活動を行っているのかを振り返った上で、 さらなるエンジニア組織の前進のために、10月から実施した組織再編について紹介します。

(前提)そもそもエンジニア組織はなにやっているの?

前提として、Wizのエンジニア組織は大きく2つの役割を担っており、すべてが自社開発となります。

  • 各種Webメディアを開発し集客する広告的役割
  • 社内外を業務改善したり、営業価値を最大化するようなWebプロダクト開発

WizのEMの現役割

現役割の整理には、「エンジニアリングマネジメントトライアングル」を利用させていただきます。 先人の方々の知見が詰まった非常にありがたいモデルです。

「テクノロジー」「プロダクト」「チーム」の軸の間をそれぞれ埋める空白領域が、EMの役割だという考え方です。

弊社において、役割として担っている部分に色をつけたものが以下です。

f:id:uehi1206:20211008162609j:plain

弊社のEMとしては、赤部分を担うことが主な役割です。

一方で、青部分のような特定のEMのみ担っているものもあり、 これは、各EMのやりたい領域や得意領域に合わせて遂行している部分です。 弊社の「言ったもの勝ち」「やったもの勝ち」の文化が出ている部分ではないでしょうか。

また、全体として「チーム」に寄っていると思いますが、これも文化として「人の成長」「何をやるかより誰とやるか」を大切にしている結果かもしれません。

具体的には主に以下のような活動を行っています。

  • アサインやメンバーのリソース/進捗マネジメント
    • メンバーのやりたいこと、できることを考慮したアサインと、完遂までの支援、負荷調整など。
  • メンバーのキャリア開発・成長支援
    • 1on1、コミュニケーションをベースとした関係構築
  • 組織としてのビジョン・ミッションの共有と評価・フィードバック
    • 組織のやりたいと個人のやりたいをマッチングさせ目標を定め、取り組みをフィードバック。
  • 採用活動、面接

さらなる成長に向けての組織再編

まずは結論から。このような Before → After にしました。

f:id:uehi1206:20211008162907j:plain

f:id:uehi1206:20211008162921j:plain

なぜこのような再編をやったかについては、以下のような課題解決の考えからです。

「ミッション」に、よりフォーカスする

EMトライアングルの現状でも出ていますが、「プロダクト」寄りの役割がまだまだ弱いと感じていました。 これは、ミッションが弱いということにもつながり、ここをもっと担っていきたいという課題がありました。

そこで、Beforeのような「フロントエンド」「バックエンド」といった職能別組織をやめ、 「メディア」「プロダクト」といったミッションの大枠で組織を定義しました。 そこには、各人が「フロントエンドだけ」「バックエンドだけ」を学習・業務遂行すればよいわけではなく、 ミッション達成のために得意分野を重ね合わせ、協力していってほしいというメッセージです。

曖昧な部分を、役割として明確化し、整備したい。

エンジニアのメイン業務は開発することですが、それだけでは強いエンジニア組織になりません。 例えば本ブログのような社外向けの発信といったものや、採用にまつわるイベント・カジュアル面談といったもの、 教育体制を作ったり、コミュニケーション改善のための施策などなど、開発体制をつくるための周辺業務があります。

もちろん全社的には採用や教育の事業部は存在しますが、エンジニア組織に適用するにはマッチしない部分もあるため、 これまでエンジニア組織内の有志のメンバーやEMが推進してくれていました。 これが、上記EMトライアングルの青部分「特定のEMのみ担っているもの」であったり、 赤部分「EM全員が担うもの」でもEMによって得意領域の違いから取り組みに濃淡がありました。

そのため今回、「本部」を組織構造として明言した、ということになります。

以上が、組織再編の思いです。

おわりに

このように、エンジニア組織は、EMとメンバーがともに 様々な試行錯誤を繰り返しながら、日々成長していっています。

一緒に切磋琢磨したい方、エンジニアもEMも募集していますので、 興味のある方、ぜひご覧下さい。

careers.012grp.co.jp