りゅうじの学習blog

学習したことをアウトプットしていきます。

7/5 リマインドメールの施策の効果測定に関して

ある条件に該当したユーザーに、リマインドメールを自動配信する実装したのですが、このタスクを実施した上での効果測定をする必要がありました。

  • 誰に送ったか
  • いつ送ったか
  • メール内のURLのクリック率

メール内のURLのクリック率に関しては、GA4のクエリパラメータを仕込んで取得する方法を取りました。

誰に、いつに関して、私はlogHelperを使って、メールを送信する直前と直後で、ログを残すことで効果測定をする手段を思いつき、実施しようとしましたが、テックリードの方がそれはやめた方がいいと言われました。

理由をとてもわかりやすく教えてくださったので、記録を残しておきます。

logHelperを効果測定のために使用しない方がいい理由

  • ログを出す目的は、障害の追跡やパフォーマンスの分析などであり、施策の効果測定まで含めてしまうと、そういうログで埋もれてしまいログが見にくくなったり、そういうログのためにデータ形式を柔軟にしないといけなかったりで、本来の目的を達成するのに障害になってしまう。
  • インフラのログはredashと連携する想定をしていないので、ログを集約できず、分析するときに使いにくい。

7/4 log4jsの導入手順

log4jsとは

log4jsは、Node.js用のロギングライブラリで、Javaでよく使われるlog4jJavaScript向けに再設計したものです。このライブラリを使用することで、ログの出力レベル(エラー、警告、情報、デバッグなど)を柔軟に制御したり、ログの出力先(コンソール、ファイル、リモートサーバーなど)を設定したりすることができます

インストール

npm install log4js

log4jsの基本的な使い方

一例

const log4js = require('log4js');

log4js.configure({
  appenders: { cheese: { type: 'file', filename: 'cheese.log' } },
  categories: { default: { appenders: ['cheese'], level: 'error' } }
});

const logger = log4js.getLogger('cheese');
logger.trace('Entering cheese testing');
logger.debug('Got cheese.');
logger.info('Cheese is Gouda.');
logger.warn('Cheese is quite smelly.');
logger.error('Cheese is too ripe!');
logger.fatal('Cheese was breeding ground for listeria.');

この例ではまず、configureメソッドを使ってlog4jsを設定しています。appendersプロパティでログの出力先を定義し(この場合はcheese.logという名前のファイル)、categoriesプロパティでログの出力レベルを設定しています(この場合はerrorレベル以上のログが出力されます)。

次にgetLoggerメソッドを使ってロガーを取得し、そのロガーを使って各レベルのログを出力しています。

log4jsの設定

log4jsの設定はかなり柔軟で、例えば複数の出力先に異なるレベルのログを出力するといったことも可能です。

公式ドキュメントを参考に実装する

人生をゲームのようにしていく感覚を持つ

 

なぜやるか

  • 人生を重く捉えすぎたり、真面目に生きすぎる事で、自身を追い詰めてしまった
  • 休職をすることにまでなってしまった
  • そうなったのなら、それを学びにして、今後を改善したい
  • 休職になったことをきっかけに哲学・生き方・思考法を学んだものをアウトプットしたい

ゲームのような感覚を持つに至ったのは

  • 東洋哲学からの学び
  • SNSは一時的な楽しみであるという学び

東洋哲学からの学び

これからお話しする上での前提として、細かいことよりも、自身がどう捉え活かしていくかを重視しているので、誰が言ったかなど、その人の名前などは、そこまで気にしていない。

正確な情報を知りたいのであれば、一次情報をキャッチアップしてください。

私が学び、私の受け取り方をしたものをアウトプットします。

私とは認識するものである

世の中には、認識する者と認識される者の二つが存在している。

認識する者は、認識される事がない。

これは認識できるとしてしまうと、無限素行になってしまうからである。

例えとして、右目で右目を見ることはできない、鏡で見えたものは右目の像を見ているに過ぎないので、やはり右目で右目は見ることはできない。

もし、右目を見れた(認識される者)としてしまうと、それに対応する、認識する者が発生することになる。これが永遠に続いてしまうため、無限素行となる。

つまり、認識する者である「私」という存在は、何者からも認識対象にはなり得ないという特徴を持っている。

にも関わらず、私含め、人は、『私は〜〜である』という言葉を平気で使っている。

これがあらゆる不幸・苦悩を生み出している。

認識する者で、認識されない私に対して、〜〜であるという言葉は使えない。

〜〜であるという言葉は、認識される者にしか使いようがない言葉だからである。

認識されるものになり得ない私に対しては、『〜〜ではない』という文脈でしか語ることはできない。

つまり、私というものに対してのきちんとした理解がないから、そこにありもしない不幸・苦悩を背負って生きているということ。

例えるのなら、マリオというゲームをやっていて、私はコントローラーを持ってマリオを操作して、マリオ含めマリオの世界を認識する者であり、ゲームの世界は認識される者である。

ゲームの世界やマリオという登場人物、クリボーという外敵は、私からしたら"それ"を認識することはできるが、"それ"からは認識されようがないし、触れられようもないのである。

マリオがダメージを受ければ、マリオのHPはもちろん減るのだが、私のHPが減ることはない。

なのにも関わらず、マリオがダメージを受けたら、私も『いたっ』と声を出して、本当に痛がっている。

先述した、私というものに対してのきちんとした理解がないから、そこにありもしない不幸・苦悩を背負って生きているというのは、この状況だという事である。

まだまだたくさんのことを学び、興味深かったのですが、ここでのアウトプットはこの辺で終わりとします。

SNSは一時的な楽しみである

 

私はYouTubeやその他SNSをなるべく見ないための工夫をしてきました。

それは、それをしているときは楽しいのですが、後で振り返ったときに『無駄な時間を過ごしてしまったな』と感じるからです。

これは、真の意味での"楽しみ"にはなり得ていないからです。

それをなるべくやらない・見ないという方針で考えていましたが、あるYouTube動画を見ていて気づきました。

要約すると、真の"楽しみ"に身を投じていれば、自ずと、真の楽しみではないことに使う時間は無くなっていくというものです。

まとめ

主に二つの学びを通して

人生を楽しむためにゲーム感覚を持つ工夫をしていくのがいいかなと思いました。
真の意味で、マリオという人生を楽しむのなら、同じ場所に立ち止まってスマホを見ていることでなく、冒険を進めること。

仕事もプライベートでやりたいことも、全てゲーム感覚を持つ工夫をして、道中である冒険を楽しむことだと思います。

6/27 TCP/IP Chapter1 part5

新しいネットワークの形

1-6-2 CDNとは

Content Delivery Networkの略称で、画像・動画・HTML・CSSなど、webコンテンツで使用される、色々なファイルを大量配信するために最適化されたインターネット上のサーバーネットワークのこと。

オリジナルのwebコンテンツを持っているオリジンサーバーとキャッシュを持つエッジサーバーで構成されている。

ユーザーは距離が近いエッジサーバーにアクセスし、キャッシュがなかったり、有効期限が切れていた場合に、オリジンサーバーにアクセスする。

なぜ使うか

CDNの主な目的は、ウェブサイトのパフォーマンスを向上させ、遅延を減らすことです。

使用用途の例

あるウェブサイトのサーバーがアメリカにあるとします。日本のユーザーがそのウェブサイトにアクセスすると、リクエストは地球の半分を旅してサーバーに到達し、レスポンス(ウェブページのコンテンツ)は同じ距離を戻る必要があります。これには時間がかかり、ウェブサイトのロード時間が遅くなります。

しかし、そのウェブサイトがCDNを使用している場合、ウェブページのコンテンツは日本に近いCDNのサーバー(エッジサーバーとも呼ばれます)にキャッシュされます。したがって、日本のユーザーがウェブサイトにアクセスすると、コンテンツは地元のCDNサーバーから迅速に配信され、ウェブページのロード時間が大幅に短縮されます。

さらに、CDNはウェブサイトの可用性と信頼性を向上させる役割も果たします。一部のサーバーがダウンしても、他のサーバーが引き続きコンテンツを提供できます。また、大量のトラフィックがある場合でも、CDNは負荷を分散してウェブサイトのパフォーマンスを維持します。

参考

仕組み・動作が見てわかる 図解入門 TCP/IP

6/27 ドメイン駆動設計 集約 [復習]

集約とは

ドメイン駆動設計において、一つ以上のエンティティを束ね、単一のトランザクションの枠内で整合性を保つための仕組みです。 例えば、銀行口座とそれに紐づく取引履歴エンティティがある場合、これらを銀行口座集約として束ね、取引が行われた場合には必ず銀行口座集約単位で整合性を保つようにします。

なぜ使うか

オブジェクトのグループの不変条件(ある処理の間、その真偽値が真のまま変化しない述語のこと)を維持するために秩序を作るため

どのように使うか

集約ルート作る

外部からのアクセスはすべて集約ルートを経由して行う

例: Userという集約があった場合、Userという集約ルートがあり、UserName UserIdが存在するとする。この場合、外部からUserName UserIdを操作してはならず、集約ルートであるUserを経由する必要がある

const userName = new UserName('newName');

//NG
user.name = userName;

//OK
user.changeName(userName);
  • UserにはchangeNameメソッドがあるという前提
  • UserNameに直接アクセスして代入するのは禁止
  • 集約ルートであるUserを経由(changeNameメソッドを使用)するやり方をしなくてはならない、不変条件を維持する秩序をもたらすため

参考

ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本

6/26 mswと Redux Toolkitを組み合わせて使う

MSW (Mock Service Worker)

MSW はブラウザのネットワークリクエストをインターセプトし、モックのレスポンスを返すことができるツールです。これにより、実際のサーバーと通信することなく、API のレスポンスをシミュレートすることができます。

  • コンポーネントが Redux の非同期アクションをディスパッチします(例:ユーザー情報を取得する fetchUser アクション)。
  • fetchUser アクションは API リクエストを行います。
  • MSW がこの API リクエストをインターセプトし、モックのレスポンスを返します。
  • fetchUser アクションはこのレスポンスを受け取り、Redux の状態を更新します。

これにより、実際のサーバーと通信することなく、API リクエストの結果に基づいた Redux の状態の変化をシミュレートすることができます。

// mocks/handlers.js

import { rest } from 'msw';

export const handlers = [
  rest.get('/api/user', (req, res, ctx) => {
    return res(
      ctx.json({
        id: 1,
        name: 'John Doe',
      })
    );
  }),
];

mswの設定です

// features/user/userSlice.js

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

export const fetchUser = createAsyncThunk('user/fetchUser', async () => {
  const response = await fetch('/api/user');
  const data = await response.json();
  return data;
});

const userSlice = createSlice({
  name: 'user',
  initialState: { id: null, name: '' },
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchUser.fulfilled, (state, action) => {
      state.id = action.payload.id;
      state.name = action.payload.name;
    });
  },
});

export const userReducer = userSlice.reducer;

createAsyncThunkで非同期アクションをします

// components/User.js

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUser } from '../features/user/userSlice';

export const User = () => {
  const dispatch = useDispatch();
  const user = useSelector((state) => state.user);

  useEffect(() => {
    dispatch(fetchUser());
  }, [dispatch]);

  return (
    <div>
      <h1>User</h1>
      <p>ID: {user.id}</p>
      <p>Name: {user.name}</p>
    </div>
  );
};

コンポーネントでdispatchします

// .storybook/preview.js

import { setupWorker } from 'msw';
import { handlers } from '../mocks/handlers';

const worker = setupWorker(...handlers);
worker.start();

// 他のStorybookの設定...

storybookの設定でmswを使用します

コメント

流れが分かってなかったので、まとめました。複雑になるとよくわからなくなる箇所がまだあるので、理解が浅いところを埋めていこうと思います。

6/24 DockerでReactを動かしてみる

DockerとDocker Composeのインストール

DockerとDocker Composeを使用するために、まずはこれらのツールをあなたのマシンにインストールします。これらのツールはDockerの公式ウェブサイトからダウンロードできます。

Dockerfileの作成

ルートディレクトリにDockerfileを作成して設定する

Dockerfile

# ベースイメージとしてNode.jsを指定 (開発ステージ)
FROM node:18 AS dev

# 作業ディレクトリを指定
WORKDIR /app

# package.jsonとpackage-lock.jsonをコピー
COPY package*.json ./

# パッケージのインストール
RUN npm install

# 開発サーバーの起動
CMD [ "npm", "start" ]

# プロジェクトの全ファイルをコピー (本番ステージ)
FROM node:18 as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

# ビルドの実行
RUN npm run build

# ビルドした結果をNginxでホストするためのステージ
FROM nginx:1.17.1-alpine

# ビルド結果をNginxのhtmlディレクトリにコピー
COPY --from=builder /app/build /usr/share/nginx/html

# Nginxを起動
CMD ["nginx", "-g", "daemon off;"]
  • 開発環境では、通常の npm start で開発しているときと同様にホットリロードをしたいのでその設定
  • 本番環境では、npm run buildをしてからビルド結果をnginxで使用するための設定

docker-compose.ymlの作成

Docker Composeは、複数のDockerコンテナを管理するためのツールです。

開発環境と本番環境で異なる設定を使用するため、それぞれの環境用のdocker-compose.ymlを作成します。

docker-compose.yml

# 開発環境用
version: "3.7"
services:
  app:
    container_name: your-app-dev
    build:
      context: .
      dockerfile: Dockerfile
      target: dev
    volumes:
      - ".:/app"
      - "/app/node_modules"
    ports:
      - "3000:3000"
    environment:
      - CHOKIDAR_USEPOLLING=true

docker-compose.prov.yml

# 本番環境用
version: "3.7"
services:
  app:
    container_name: your-app-prod
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "80:80"

portsの番号について

portsはDockerコンテナ内のポートをホストマシンのポートにマッピング(つなげる)設定です。

設定値の"3000:3000"は「ホストマシンの3000番ポートをコンテナの3000番ポートにつなげる(マッピングする)」という意味になります。

本番環境はなぜ"80:80"なのかというと、それはNginxサーバー(コンテナ内で動作するWebサーバー)がデフォルトで80番ポートを使用するためです。

一方、もしReactの開発サーバー(通常npm startで起動する)をコンテナ内で動かしている場合は、その開発サーバーはデフォルトで3000番ポートを使います。そのため、その場合は"3000:3000"と設定します。

Dockerイメージのビルドとコンテナの起動

Docker Composeを使用してDockerイメージをビルドし、コンテナを起動します。

開発環境ではdocker-compose up --buildを、本番環境ではdocker-compose -f docker-compose.prod.yml up --buildを実行します。

これで、docker環境でReactアプリーケーション開発ができます。