ホーム ブログ ページ 18

ローカル環境で’LOADING Redis is loading the dataset in memory’が頻発する

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

表題の件についてのメモ

背景

  • Rails アプリケーションのローカル開発環境を Docker で構築
  • その環境には Sidekiq の worker が稼働する、 worker コンテナ、Sidekiqがバックエンドとして使用している Redis が稼働している redis コンテナが存在する

現象

docker-compose によるコンテナの立ち上げ時、以下のエラーが発生して worker コンテナが落ちる

worker           | LOADING Redis is loading the dataset in memory
worker           | /app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.3/lib/redis/client.rb:124:in `call'
worker           | /app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.3/lib/redis/client.rb:107:in `block in connect'
(略)
worker           | /app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.3/lib/redis.rb:278:in `info'
worker           | /app/vendor/bundle/ruby/2.5.0/gems/sidekiq-5.2.3/lib/sidekiq.rb:113:in `block in redis_info'
worker           | /app/vendor/bundle/ruby/2.5.0/gems/sidekiq-5.2.3/lib/sidekiq.rb:95:in `block in redis'

原因

redisコンテナ起動時に --appendonly yes が指定されていた。

LOADING Redis is loading the dataset in memory というエラーは、 AOF(Append-only File)からデータをメモリ上に展開している間に更新(SET)などのコマンドを実行しようとした際に発生するらしい。

RDB/AOFファイルをローディングしている間、SETコマンドのようなものを実行すれば “(error)LOADING Redis is loading the dataset in memory”このようなエラーを出すが、infoコマンドは実行されます。 したがって、infoコマンドでローディング中なのか確認することができます。
REDIS INFO [section] コマンド

対処その1

Append-only File は、プロセスの予期しない終了等からデータを保護するための機構であるが、ローカル開発環境においてはそのような考慮は不要(消えても構わない)ので、 --amendonly yes オプションを取り除く。

   redis:
     image: redis:latest
     ports:
       - 6379:6379
     volumes:
       - redis:/data
-    command: redis-server --appendonly yes
+    command: redis-server

対処その2

あるいは、ローカルで保持しているRedis内の情報が消えても構わないのであれば一旦 redis コンテナに割り当てている Volume を削除してあげるという方法もある。

ただし、一時的なものなので AOF が肥大化するにつれていずれ問題が再発する。(と思われる)

ボリュームを削除する手順は以下(コンテナを停止している状態で実施する)

<ボリュームの一覧確認>
docker volume ls

<ボリューム削除>
docker volume rm xxxxxx_redis

CloudFront + API Gateway + Lambdaの環境で生きた無属性のペアとそのこと

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

API Gateway のエラー時のレスポンスのメッセージは必ずしもその原因と直結するような内容となっていないことが多々あり、原因の特定に時間がかかりがちなのでメモっておく。

※随時追加予定

※「レスポンス」の内容は、curl -vでのリクエストに対するレスポンスを多少加工して掲載している。

401 ‘Unauthorized’

レスポンス

< HTTP/2 401
< date: Wed, 22 May 2019 08:43:33 GMT
< content-type: application/json
< content-length: 26
< x-amzn-requestid: xxxxxxxxxxxxxxxx
< x-amzn-errortype: UnauthorizedException
< x-amz-apigw-id: xxxxxxxxxxxxxxxxxxx

{"message":"Unauthorized"}

原因

  • カスタムオーソライザに必要な情報(特定のリクエストヘッダ等)がリクエストに含まれていない
    • 例えば Cookiesession_idを使ってAPIへのアクセスを認可するオーソライザを設定している場合、Cookieヘッダがリクエストに含まれていない場合に前述のリクエストが返ってくる

403 ‘Missing Authentication Token’

レスポンス

< HTTP/2 403
< date: Wed, 22 May 2019 08:31:54 GMT
< content-type: application/json
< content-length: 42
< x-amzn-requestid: xxxxxxxxxxxxxxxxx
< x-amzn-errortype: MissingAuthenticationTokenException
< x-amz-apigw-id: xxxxxxxxxxxxxx

{"message":"Missing Authentication Token"}

原因

  • API Gateway のエンドポイントのパスが間違っている
    • 例えば正しいエンドポイントのURLが以下であった場合
      • https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/stage/api/v1/{+proxy}
    • 以下のように間違っている(/apiが抜けている)と前述のようなレスポンスが返ってくる
      • https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/stage/v1/xxxxxx

403 Forbidden

(思い出したら書く)

403 ‘User is not authorized to access this resource with an explicit deny’

レスポンス

< HTTP/1.1 403 Forbidden
< Date: Wed, 22 May 2019 08:56:27 GMT
< Content-Type: application/json
< Content-Length: 82
< Connection: keep-alive
< x-amzn-RequestId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
< x-amzn-ErrorType: AccessDeniedException
< x-amz-apigw-id: xxxxxxxxxxx

{"Message":"User is not authorized to access this resource with an explicit deny"}

原因

  • カスタムオーソライザから DENY のポリシー文書が返却された場合に返ってくるレスポンス
    • カスタムオーソライザの呼び出し自体には成功しているので、大体のケースにおいては正常動作と言える(正常でない場合はカスタムオーソライザをデバッグすることになる)

503 (HTML形式で) ‘ERROR: The request could not be satisfied’

レスポンス

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>502 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
CloudFront attempted to establish a connection with the origin, but either the attempt failed or the origin closed the connection.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>

原因

  • オリジン(API Gateway)に対して http でアクセスしている
    • CloudFront のオリジンの設定(Origin Protocol Policy)が HTTP Only のようになっていないか確認する
    • API Gateway は HTTPS しかサポートしていないので、 HTTPS Only とするべき

nginx の proxy_pass の転送先が API Gateway の場合に `SSL_do_handshake() failed` と言われた

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

ローカル環境のnginxからAPI Gateway で用意したAPIのエンドポイントに proxy_pass 設定したときにハマった件

背景

  • ローカル環境に nginx を立てて、その nginx で特定の path (例えば /api/v1/xxx) に対して proxy_pass を設定して、API Gateway で作成したエンドポイントに対してアクセスを転送したい

現象

  • 該当 path にアクセスすると nginx から 502 Bad Gateway のエラーが返ってくる
  • nginx のエラーログを見ると SSL_do_handshake() failed というエラーが発生していた

原因

proxy_pass の対象となるエンドポイントが SNI を使用している場合発生するっぽい(API Gateway がデフォルトで用意するエンドポイントは SNI 使ってる)

参考: Nginx reverse proxy error:14077438:SSL SSL_do_handshake() failed – Stack Overflow

対処

以下のように proxy_ssl_server_name on の設定を追加してあげたらOK

location /api/ {
  proxy_pass https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Dev/api/;
  proxy_ssl_server_name on;
}

ボックスガチャの実装をしよう(ソーシャルゲームにおけるサーバーサイド) 7

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

どうして世の中は世知辛いのだろう。

前回のあらすじ

前回
 ボックスガチャの実装が完了しました。
 しかし、そこで完了にするのではなくコードレビューや仕様の確認をして、この先に改修が入ったりしても、誰かにクソコードと喚かれたり、見直して自分でクソコード……と呟いてしまうような、また仕様書に無い仕様が実際には入っていたり、逆に実は仕様が欠けていたり……みたいな悲劇が起こらないようにしましょう。

仕様確認をしよう

くらいあんとえんじにあ「クライアントもアセット組み込んで実装終わったから、仕様確認するぞー」

仕様書を見直すと、

・ボックスガチャは通常ガチャと同様、1連と10連で引ける
・ボックスガチャで引けるものは通常ガチャと同等
・10連を選択した時にボックスの中身が9個以下だった場合、消費される対象はその中身分のみとなり、全てを自動で排出する
・リセットする度に上限までボックスの中身は変わる
・リセットは自由に可能。そのステージのボックスガチャを1回も引かなくともリセットが出来る
+ 排出されるキャラクターの中で最高レアリティのキャラはボックスの中身を1/4以上引いていない時しか出ない
+ ボックスを引き切ると別のマスタで定義してあるおまけが貰える

さーばーえんじにあ「(追加された仕様に関しても実装したし)ここあたりは問題ないですよね。マスタ定義書とかも更新してあるのは見ましたし」

※開発途中に仕様が追加変更される事は多分良くある

くらいあんとえんじにあ「あ、いや、 ボックスを引き切ると別のマスタで定義してあるおまけが貰える ってUI上の表示は無くて良いんですっけ。こっちとしては特に大して聞いてないんですけど」
ぷらんな「現状大した物出さないし、最後まで引ききって遊んでくれてありがとう的な、そんな小さなものだから取り敢えず今回は無しね」
くらいあんとえんじにあ「りょーかい。一応追記しておいてくれると助かります」
ぷらんな「わかっタスマニアデビル」
でざいなー「UIの遷移図、仮画像のままなので本画像に差し替えておいてくださインパラ」
ぷらんな「りょーか犬」
さーばーえんじにあ「サーバー側としては他に特にないです猫」
くらいあんとえんじにあ「……クライアントとしても同じです」
でざいなー「……」
ぷらんな「……」
さーばーえんじにあ「……」
くらいあんとえんじにあ「……やらないよ?」

コードレビューをしよう

さーばーえんじにあ「実装の規模でかいんで部屋取ってコードレビューの時間取ろうと思うんですけど、良いですか?」
れびゅわー「この時間に取っておいて。1時間あれば足りる?」
さーばーえんじにあ「まあ、基本的に新規実装なので、既存コードの改修でごっちゃごっちゃそこまでやってないの考えるとその位で多分大丈夫です」

さーばーえんじにあ「実装は大まかに言うと、既存コードをごねごねせずに、引用して来る感じで行いました。
 で、ボックスガチャの実装で普通のガチャと共通の部分は継承してそのまんま使って、この部分とこの部分だけ違うので、こんな感じでオーバーライドして……みたいな感じです」
れびゅあー「設計はまあ事前に聞いてたし特に問題ないと思う」
さーばーえんじにあ「わかりましたー」
れびゅあー「で、一つ一つ改修したコード見ていこうか」

れびゅあー「データ更新の部分さ、この作りだと10連だと10回update叩かないといけないけどさ、一括で何とか出来なかった?」
さーばーえんじにあ「DBのクラスタ設定とか考慮してやると結構複雑になりそうだったので、一旦やってないです。後、ちゃんとindexとか張ってありますし、不要なデータも基本的に即削除してるので、そんなに問題ないかと」
れびゅあー「分かった。まあ、最大10回なら問題ないかな。パーティションは切ってあって、ガチャ期間が過ぎてN日経ったら削除ね……。そこあたりは何か根拠でも?」
さーばーえんじにあ「ガチャで取ってあるログと同じに一旦してあります」
れびゅあー「そうね。多分無いと思うけど、同じボックスガチャを前に引いていた人はその状態からで復刻してやるとか、そんな事がないかとか、ちゃんとプランナーとかと合意取っといて」
さーばーえんじにあ「分かりましたー」

れびゅあー「じゃあ、言ったところ確認とったりとかしたら本ブランチにマージするね」
さーばーえんじにあ「分かりましたー」

タスク完了

上記が終わり次第、 ボックスガチャの実装 というタスクはやっと、しかしながら 基本的にのみ 完了となります。
QAチェックなどで、コードレビューやテストコードもすり抜けて不具合が発覚する可能性なども多々ありますので、その都度修正などをしていきましょう。


……。
…………。
…………こんな風に実装出来たら良かったなぁ……。

ボックスガチャの実装をしよう(ソーシャルゲームにおけるサーバーサイド) 6

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

ロースザブトンもやしのナムルミスジテールスープサーロインカルビ石焼ビビンバタンサガリ米ハチノスチヂミシマチョウたまごスープハツサンチュニンニクのホイル焼きレバートントロ焼き野菜ミドガルズオルム

前回までのあらすじ

前回

 ボックスガチャのサーバーサイドのコードの実装が完了しました。
 また、テストコードの実装及びに、新規マスタのインポート処理も完了し、残るはクライアントとの繋ぎ込みのみです。

クライアントとの繋ぎこみ

くらいあんとえんじにあ「追加されるAPIのパスとかはもう共有されてるけど、まだパラメータとレスポンスの形ちゃんと貰ってないよ」
さーばーえんじにあ「あ、忘れてました……。纏めて返します」

ボックスガチャを引く POST: gachas/draw
parameter
{
  gacha_code: integer, #対象ガチャコード
  draw_count: integer  #引く回数
}

response
{
  characters: [ #引いたキャラクター達の情報
    {
      character_id: integer, #キャラクターID
      character_code: integer, #キャラクターコード
      level: integer, #レベル
      .....
    }
  ]
}

パラメータ、レスポンスは既存から変更なし
現在のボックスの中身を確認 GET: gachas/current_box_contents
parameter (クエリパラメータ)
{
  gacha_code: integer # 対象のガチャコード
}

response
{
  stage: integer, # 現在のボックスガチャのステージ
  total_count: integer, # 現在のボックスガチャの中身の総数
  remain_count: integer, # 現在のボックスガチャの残りの総数
  contents: [ # ボックスの中身
    {
      character_code: integer, # キャラクターコード
      total_count: integer, # 中身の総数
      remain_count: integer # 残りの総数
    }
  ]
}
ボックスガチャのリセット POST gachas/reset_box
......
各ステージでのボックスの中身の確認 GET: gachas/all_box_contents
......

さーばーえんじにあ「取り敢えずこんな感じですけど問題ありますか?」
くらいあんとえんじにあ「うーん、まあ、現状のUIで表示するものだと、これとこれとこれが足りないから追加して」
さーばーえんじにあ「了解です、改修終わったらAPI定義書に纏めておきますね」


※ APIのパラメータなどに限らず、仕様書とは別に定義書があると何かを調べる時にとても役立ちます。改修の度にきちんと更新されている事を前提として。


1時間後

さーばーえんじにあ「で、ソースと定義書の更新終わったからまたテストして……開発環境に反映!」
くらいあんとえんじにあ「不具合あったら報告するね」
さーばーえんじにあ「入念にテストしたから基本的に大丈夫だと思うけど、まあ、完璧な自信は無いので」

数時間後

くらいあんとえんじにあ「ボックスガチャだったら、引いた後の中身のレスポンスも同時に返してくれない? 続けて引く時にさ、ガチャ結果からクライアントが判断してボックスの中身を減算するとかするより、サーバーからその時点の結果一緒に貰っちゃった方が良いと思うんだ」
さーばーえんじにあ「ボックスガチャの時だけそういうレスポンスを返すって感じですか?」
くらいあんとえんじにあ「そういう事」
さーばーえんじにあ「ボックスガチャでない時は空配列を返す形で良いですか?」
くらいあんとえんじにあ「おk」
さーばーえんじにあ「分かりました(バグは起きてないっぽいかな。良かった)」
くらいあんとえんじにあ「あ、Internal Server Error」
さーばーえんじにあ「ふぇっ!?」

その後、クライアントとの擦り合わせやバグの解消をして

くらいあんとえんじにあ「一通り見ましたが、これで基本的に問題ないです」
さーばーえんじにあ「確認ありがとうございますー」

実装は完了しましたがまだ終わりではありません

さーばーえんじにあ「じゃあ、後はコードレビューとか仕様の認識合わせとかして終わりか。レビューする前に自分のコード一回見直そう。
 ……変数名ちょっと分かりづらいやここ。直しておこう。あ、後、ここのレスポンスの追加、定義書に書いてないや。こうして……」

※ コードレビューは大事。動くだけの汚いコードは後々の全てに悪影響を与えます。ただ、そのレビューをする前にちゃんと自分のコードは見直して、気付いた点は直しておきましょう。そしてその修正でエラーが発生しないようにしましょう。
※ また、実装が完了した後での仕様の認識合わせも重要です。口答だけで追加された仕様などが抜け落ちている可能性などをしっかりと排除しておきましょう。

次(最後)

ボックスガチャの実装をしよう(ソーシャルゲームにおけるサーバーサイド) 4

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

ど゛う゛じ゛で゛

前回までのあらすじ

前回

ボックスガチャを作る事になりました。
改修、追加すべきAPIは以下で、

・ボックスガチャを引く POST: gachas/draw
・現在のボックスの中身を確認 GET: gachas/current_box_contents
・各ステージでのボックスの中身の確認 GET: gachas/all_box_contents
・ボックスガチャのリセット POST gachas/reset_box

それらの処理の為に、GachaBoxContentManagerという、ユーザーのボックスガチャの状態を一括管理するクラスを作成しました。

そうすると、

・現在のボックスの中身を確認 GET: gachas/current_box_contents
・ボックスガチャのリセット POST gachas/reset_box

この二つのAPIの実装は大体終わったも同然であり、また、

・各ステージでのボックスの中身の確認 GET: gachas/all_box_contents

このAPIの実装もマスタデータを返すだけなのですぐに終わりました。
残りは、

・ボックスガチャを引く POST: gachas/draw

のみです。

ボックスガチャを引く処理を作る

通常ガチャを引くコードは大まかに以下のように分けられていました。

APIが叩かれる
 受け取ったパラメータを元にガチャのプロセスを初期化
 ガチャのプロセスを実行する
  アサーション
  ガチャを指定回数だけ引く
  ガチャの結果からユーザーデータを更新
  ログの出力
 実行後、レスポンスを返す

さーばーえんじにあ「このコードでの一番の問題は、1回ガチャを引く毎に中身のコンテンツが変わる事を考慮していないという事だ。
 ……どうしようかなこれ。
 ボックスガチャを引く為に大きく改修すべき部分は、
 ・ガチャを指定回数だけ引く
 ・ガチャの結果からユーザーデータを更新
 基本的にこの三つか。それ以外の部分は大きく変えたりは多分しなくて良い。
 A. ガチャのプロセスを継承してその部分だけを変えるか
 B. ガチャのプロセスそのものを拡張するか
 どっちが良いかな。
 うーん、例えば、他にも色んなガチャを実装するとしたらどうなるかな……。
 Aだと、それぞれのガチャの特性に合わせた部分だけを変更した新しいプロセスを作る必要が出てくる。でも、他の部分は基本のガチャに準じている訳だからまあ、見やすいかな。
 Bだと、一つのガチャのプロセスの中に色んなガチャに応じた分岐がどんどん増えていく事になる。完ッ璧に見辛いな。更に、改修を加える度に他のガチャに影響が出る可能性も否定出来ない。
 Aだな、うん。そうしよう」
 そうして、以下のように実装が進みました。

APIが叩かれる
 (受け取ったパラメータを元にガチャのプロセスを初期化 <= ガチャ種が増えた事によって削除)
 受け取ったパラメータと、そのガチャの種類に応じたプロセスの初期化を行う <= new!
 プロセスを実行する
 実行後レスポンスを返す
通常ガチャプロセス
 初期化
 アサーション
 ガチャを指定回数だけ引く
 ガチャの結果からユーザーデータを更新
 ログの出力

ボックスガチャプロセス <= new!
 初期化 <= GachaBoxContentManagerの初期化もする
 アサーション
 ガチャの指定回数だけ
  ガチャを引く
  ボックスの中身を減算する
 ガチャの結果からユーザーデータを更新 <= GachaBoxContentManagerからボックスの中身も更新する
 ログの出力

さーばーえんじにあ「こんな感じか。
 既存の通常ガチャへの影響はほぼ無し。じゃ、APIも作り終えたから、後はテストコード書いて……マスタのインポート処理とかをマスタが来次第作成、クライアントとの繋ぎこみをその後、か。
 あれ、まだまだやる事結構あるんか。
 ちょっと面倒だな……」

ボックスガチャの実装をしよう(ソーシャルゲームにおけるサーバーサイド) 2

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

この予算内で引けなかったら諦める

前回までのあらすじ

前回

ボックスガチャを作る事になりました。

必要な機能:
・ボックスガチャを引く
・現在のボックスの中身を確認
・各ステージでのボックスの中身の確認
・ボックスガチャのリセット
必要な追加データ:
・ユーザーのボックスの現在状態
・ステージ毎のボックスの中身を定義するマスタ
マスタデータ
 ┣キャラクターマスタ CharacterMaster
 ┃┣キャラスキルマスタ CharacterSkillMaster
 ┃┣キャラ成長マスタ CharacterExpMaster
 ┃┃┗キャラパラメータマスタ CharacterParameterMaster
 ┃...
  ...
 ┣ガチャの親マスタ GachaMaster
 ┃┣ガチャの中身マスタ GachaContentMaster
 ┃┗ボックスガチャの中身マスタ <= new!
 ...


ユーザーデータ
 ┣所持キャラクター群 Characters
 ...
 ┣現在のボックスガチャの状態データ群 <= new!
 ...

仕様書が来ました

読み込むと実装に足りるだけの詳細がありました。

・ボックスガチャは通常ガチャと同様、1連と10連で引ける
・ボックスガチャで引けるものは通常ガチャと同等
・10連を選択した時にボックスの中身が9個以下だった場合、消費される対象はその中身分のみとなり、全てを自動で排出する
・リセットする度に上限までボックスの中身は変わる
・リセットは自由に可能。そのステージのボックスガチャを1回も引かなくともリセットが出来る

さーばーえんじにあ「設計に関しても承認貰ったから実装していこう」

まずはテーブルを作りましょう

さーばーえんじにあ「新規テーブルは
・ボックスガチャの中身マスタ
・現在のボックスガチャの状態データ群
か。
名前はどうしようかな」

 ┣キャラクターマスタ CharacterMaster
 ┃┣キャラスキルマスタ CharacterSkillMaster
 ┃┣キャラ成長マスタ CharacterExpMaster
 ┃┃┗キャラパラメータマスタ CharacterParameterMaster

 ┣ガチャの親マスタ GachaMaster
 ┃┣ガチャの中身マスタ GachaContentMaster
 ┃┗ボックスガチャの中身マスタ

 ┣所持キャラクター群 Character
 ...
 ┣現在のボックスガチャの状態データ群

さーばーえんじにあ「ガチャのマスタは先頭にGachaってついていた方がエディタとかで開いた時、ガチャ関連の定義ファイルが一箇所に集中していて分かりやすいよなあ。
 だと、ボックスガチャの中身マスタはGachaBoxContentMasterとかになるかな?
 で、現在のボックスガチャの状態テータ群もそれに倣う形でGachaBoxContentとかになるかなあ。
 ボックスなのにGachaBoxって逆転させるの気持ち悪いけど……でも、データの並び順序考えるとこの方が良いかな」
さーばーえんじにあ「そんなこんなで、マスタ名、GachaBoxContentMasterにしました。通常ガチャとは別にステージ毎の中身の情報をここにお願いします。
 カラムは、通常のガチャの中身を示すGachaContentMasterの一部を変更、追加する形で、
 確率がweight: integerで定義されているのに対して、こちらが示すものは中身の数量なので、quantity: integerにします。
 後は、どのステージかを示すstage: integerをカラムとして追加してあります。
 問題ないですか?」
ぷらんな「ok」

API名を先に定義しておきましょう

既存のAPIとして以下が既に定義されていました。

・ガチャトップ画面 GET: gachas
・ガチャを引く POST: gachas/draw
・ガチャの確率を確認する GET: gachas/rate_list

さーばーえんじにあ「ガチャを引くAPIはこのままでいっか。引くものは確率確認は、ボックスガチャで各ステージでのボックスの中身の確認、としても良い気がする。
 ……でも、レスポンスの形もUIも変わるよなぁ……分けた方が良いかな?」
くらいあんとえんじにあ「あ、分けて欲しいな」
さーばーえんじにあ「分かりましたー」
さーばーえんじにあ「じゃあ、追加する機能に対してのAPIはこんな感じになるかな」

・ボックスガチャを引く POST: gachas/draw
・現在のボックスの中身を確認 GET: gachas/current_box_contents
・各ステージでのボックスの中身の確認 GET: gachas/all_box_contents
・ボックスガチャのリセット POST gachas/reset_box

さーばーえんじにあ「じゃあ、実装かな」

ボックスガチャの実装をしよう(ソーシャルゲームにおけるサーバーサイド) 1

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

この無料分で引けなかったら諦める

ガチャから派生させてボックスガチャを作る事になりました

ぷらんなー「このゲーム、普通のガチャはあるけどさ、ボックスガチャやりたいから作って」
さーばーえんじにあ「ボックスガチャか」
ぷらんなー「必要な機能は大まかに言うと、
・ボックスガチャを引く
・現在のボックスの中身を確認
・各ステージでのボックスの中身の確認
・ボックスガチャのリセット
ね。リセット回数はガチャを定義する親マスタで制限かけるから。
 細々とした仕様は後で仕様書が完成したら共有するけど、取り敢えず確定事項です」
さーばーえんじにあ「りょーかい」

ボックスガチャってなぁに?

 例えるならば、ガチャポンです。
 中身が有限なガチャです。
 排出率が1%のレアキャラが出るガチャだったら、通常は100回引いても大体63%の確率でしか引けませんが、ボックスガチャならば中身が有限なので、中身を全て引けば100%出す事が出来ます。
 やったね!!

ガチャから派生させてボックスガチャを実装するにはどういうAPIとどういう追加データが必要だろう

さーばーえんじにあ「必要なAPIとしては、まあ、必要な機能の通りで、
・ボックスガチャを引く
・現在のボックスの中身を確認
・各ステージでのボックスの中身の確認
・ボックスガチャのリセット
で大丈夫だよな」
さーばーえんじにあ「で、サーバー側で持つべきデータとしてはまず、
・ユーザーのボックスの現在状態
が必要だよな。で、えっと? 機能に 各ステージでのボックスの中身の確認 なんて項目があるって事は、リセットする度にボックスの中身変わるのかな?」
ぷらんな「そうだよ。仕様書もうそろそろ出来るから、ちょっと待ってて」
さーばーえんじにあ「あ、はい。なら、
・ステージ毎のボックスの中身を定義するマスタ
を別に作った方がいいか。通常のガチャの中身を定義するマスタとは段々かけ離れてくるし。
……その位か」

必要な機能:
・ボックスガチャを引く
・現在のボックスの中身を確認
・各ステージでのボックスの中身の確認
・ボックスガチャのリセット
必要な追加データ:
・ユーザーのボックスの現在状態
・ステージ毎のボックスの中身を定義するマスタ

さーばーえんじにあ「で、ユーザーのボックスの現在状態をどうやって保存するかちょっと悩むな。二つ案あるんだけど」

案1:
中身のコンテンツ一種類毎にレコードを作る
利点: 
・構造が簡単。ユーザー動向の調査の為の検索や緊急対応の為の一括更新などがし易い。
欠点:
・ユーザー一人辺りのデータが多くなる。総レコード数は簡単に考えて、(ユーザー数×ボックスガチャの数×ボックスガチャの平均コンテンツ種別量)となる
・10連でガチャったりする場合、SQLの更新回数が多い(O(n))
案2:
ボックスガチャ一つに対し、データをJSONなどで一括管理。
利点:
・ユーザー一人辺りのデータが少ない。総レコード数は簡単に考えて(ユーザー数×ボックスガチャの数)となる。
・10連だろうと1連だろうとSQLの更新回数は1回(O(1))
欠点:
・ユーザー動向の為の調査の為などの検索、緊急対応の為の一括更新がし辛い。JSONを生で保存するならlike検索などは辛うじて出来るが、圧縮してバイナリなどにした場合、全く出来なくなる
・管理、更新の為に毎回JSONなどを展開、圧縮などする必要がある為、やや面倒

さーばーえんじにあ「……1だな! 2の欠点が、結構でかいからなぁ。ユーザー動向の調査とかはともかく、何か緊急対応しなきゃいけなくなった時、一括更新とか出来なくてユーザー一人一人に対して処理掛ける必要が出てきて、時間が酷く掛かったりとか、そんないやーな予感がする。
 データベース容量は結構余裕あるし、古いボックスガチャのデータとか、引き切ったボックスのデータとかはパーティション区切れば一括削除出来るし、ユーザー単位でそんな数百件もデータ保持しないだろ!
 それに、更新の為の計算量はO(n)だけど、最大10連とかだったら、1回での更新は最大10回だし、ちゃんとインデックス張れば大丈夫でしょ!
 ……まあ、設計書とかちゃんと作るとして」

テーブル概要書

マスタデータ
 ┣キャラクターマスタ CharacterMaster
 ┃┣キャラスキルマスタ CharacterSkillMaster
 ┃┣キャラ成長マスタ CharacterExpMaster
 ┃┃┗キャラパラメータマスタ CharacterParameterMaster
 ┃...
  ...
 ┣ガチャの親マスタ GachaMaster
 ┃┣ガチャの中身マスタ GachaContentMaster
 ┃┗ボックスガチャの中身マスタ <= new!
 ...


ユーザーデータ
 ┣所持キャラクター群 Characters
 ...
 ┣現在のボックスガチャの状態データ群 <= new!
 ...

ボックスガチャの実装をしよう(ソーシャルゲームにおけるサーバーサイド) 3

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

ここまで来て引き下がる訳にはいかない!

前回までのあらすじ

前回
ボックスガチャを作る事になりました。
改修、追加するべきAPIは以下に纏められました。

・ボックスガチャを引く POST: gachas/draw
・現在のボックスの中身を確認 GET: gachas/current_box_contents
・各ステージでのボックスの中身の確認 GET: gachas/all_box_contents
・ボックスガチャのリセット POST gachas/reset_box

実装する前に

さーばーえんじにあ「これ……全てのAPIに対して、ボックスの中身を確認変更する操作が必要になるな。
 で、ボックスの中身を確認やら変更やらするのは、レコードの仕組み上、複数のレコードを常に触る事になる。
 ちょっと、一つ一つ実装する前にボックスの中身に対する操作をクラスで一括で行う事にして、そこから触った方が良いな……」

 必要な操作を纏めたところ、以下のようになりました。

・ボックスの中身を初期化する
・ボックスの中身を確認する
・ボックスの中身を引く
・ボックスの中身をリセットする(次のステージに進ませる)
+各種デバッグ機能

さーばーえんじにあ「これらをクラスにして、名前、どうしよっかな。ボックスの中身に対する処理を全てやるんだから、GachaBoxContentManagerとかでいっか。で、そこから全てのボックスに対する操作を一括して行う、と」

クラス GachaBoxContentManager
 コンストラクタ : ユーザーとガチャマスタを受け取り、ユーザーに対するボックスが無かったら初期状態で作る(ガチャマスタがボックスガチャ対応でなかったらエラー)
 確認メソッド : ボックスの中身を用途に合った形に整形して渡す
 引くメソッド : ボックスの中身を更新する
 リセットメソッド : ボックスを次のステージへと移行させる
 デバッグ機能 : 取り敢えず強制初期化とかはデバッグで必要になりそうだけど保留

さーばーえんじにあ「じゃあ、今度こそ実装に入るか……って、ボックスの中身を確認するAPIって大体実装終わったも同然じゃんこれ」

ボックスの中身を確認するAPI gachas/current_box_contents
  # GachaBoxContentManagerを対象ユーザーでインスタンス生成
  # インスタンスメソッドから中身を受け取る
  # 整形して返す

さーばーえんじにあ「リセットも」

ボックスのリセットAPI gachas/reset_box
  GachaBoxContentManagerを対象ユーザーでインスタンス生成
  インスタンスメソッドからリセットを実行
  リセット出来たらレスポンスを、既にリセット回数の上限に達しているなどでリセット出来なかったらエラーを返す

さーばーえんじにあ「レスポンスとかはまあクライアント次第だけど、各ステージでのボックスの中身の確認も後はマスタのデータそのまま返すだけだし……

各ステージでのボックスの中身の確認API gachas/all_box_contents
 対象のガチャのマスタデータ取得
 整形して返す

 あら、後はボックスガチャそのものの実装だけみたいなもんじゃん。
 あれま」

味噌カツで例えるソーシャルゲームのサーバーサイドにおける新規実装の流れ 4

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

そしてソースカツ丼となった

前回までのあらすじ

前回

以下のゲームで、

牧場経営ソーシャルゲーム:
ユーザーは牧場を経営する。また、牧場などで育てたものを調理し、食べる事が出来る。
ユーザーにはステータスが存在し、様々な行動によってステータスが変動する。ある一定のステータスを伸ばすと出来る事が増える。
ステータスは割合で決定されており、どれかのステータスを上げると他のステータスが落ちる。
一度得た能力は消えない。

味噌カツを作る事が決定しました。
提示された仕様書を読み込み、それに対して曖昧な点や改善すべき点を会話して解消、サーバーエンジニアがすべき事が明確になった後に仮実装、暫定での実機確認が完了。その後に仮実装で適当に作っていた部分をちゃんと作り込み、テストコードも作り、実装を一旦完了させました。

コーディング完了後にすべき事

 コーディング完了してそれで全て終わりにしてしまうと、後々負の遺産が溜まっていく事になります。
 そうしてしまうと、そのコードを他者が弄る事になった時に、「ク・ソ・コォォォォドゥォォォォォォォォオオオオオオ」とかその人に叫ばれたりとか、ずーっと誰にも気づかれないまま潜んでいたバグが唐突に本番環境で発生したりとか、そんな事が起きてしまう可能性がとても高くなっていきます。
※クソコードによる影響はどこかしこに記事やブログとして載っているのでここでは書きません。ただ、クソコードはクソだからクソコードと呼ばれるのです。クソコードはクソだからクソコードと呼ばれるのです。

 そんな事を極力防ぐ為に、例えば、以下のような事をしましょう。

実装完了後の仕様書の読み合わせ

 味噌カツ作りを実装がサーバー、クライアント、デザイナー、プランナー全ての担当が完了し、繋ぎ込みも一旦終わった後に、仕様に対して漏れや差異がないかなどを再確認しましょう。


くらいあんとえんじにあ「追加するって言ってたこの仕様、仕様書から漏れてるよ」
ぷらんなー「えっ」
さーばーえんじにあ「あ、後から追加されたこの仕様に対して実装するの忘れてた……」
くらいあんとえんじにあ「えっ、あっ、ここデフォルトデータしか入ってないじゃん……」
でざいなー「UI出来たので本画像渡します、仕様書のここの仮画像、本画像に差し替えておいて下さい」
ぷらんなー「りょーかい」
さーばーえんじにあ「……実装してから気づいたけど、仕様書通りだと、これこれこういう場合にこんな挙動になるけど、これで良いの?」
ぷらんなー「あー、これ、駄目だな、仕様から直します」
くらいあんとえんじにあ「影響はサーバーだけ?」
さーばーえんじにあ「そですね」


 要するに、実装の上で認識のズレが生じていないかの確認が出来ます。
 その認識のズレは基本的にQAチェックなどを行えばある程度はバグとして弾く事が出来ますが、QAもバグを100%弾ける訳ではありません。
 そして、こういう事をしないでQAチェックまでそのまま通って本番に反映されてしまうと、


ぷらんなー「何か変な挙動するんだけど」
さーばーえんじにあ「えっ……あー、これは、仕様です」
ぷらんなー「バグっぽいんだけど」
さーばーえんじにあ「仕様書通りの仕様です」


 みたいな事が起きたりします。確認を何度も挟んだとしても起きる可能性はありますが、チェックを複数段階で行なう事でその可能性を減らす事は出来ます。

影響範囲を纏める

 基本的に新規実装となるとQAチェックもその新規実装に対して全面的にチェックする、という簡単な構造になるのですが、既存コードへの修正はテストコードも改修して通ったとしても、ある程度はQAチェックを挟む必要があります。
 また、新規実装の為に既存コードにも手を加えた、という事に関しては仕様書への記載としてはSV側の改修点として見えない時もあります。
 なので既存コードに対して修正を入れたならば、それもきちんと纏め、QAチェックを依頼しましょう。

デバッグツールを作る

 QAが確認する際に、既存のデバッグツールでは時間が掛かる、などと言った苦情が来る可能性があります。
 余裕があったら必要そうなものを先んじて作っておいても良いでしょう(こういう事こまめにやっておくと好感度UPするんじゃないかな……)。
さーばーえんじにあ「特定の味噌カツのレシピを作れる素材を一括付与するとか、作っといたら便利かな?」

コードレビューを行なう

 第三者を招いてコードレビューを行いましょう(今更ですが、実装が複雑だったり、実装者が初心者だったりするのならば実装する前に設計レビューも設けた方がいいです、書くの忘れた……)。

 コードレビューの際には、コードを直接見せるのとは別途、追加したデータ構造などを記載したエクセルなども用意しておくと分かりやすくなります。


さーばーえんじにあ「今回は味噌カツ作りの実装を行いました。
 まずは、新規追加したモデルの構造から話します。
 マスタに関してですが、味噌カツ作りの為に新規マスタを二つ追加してあります。
 まず一つ目は、味噌カツの親マスタで……、
 次にユーザーデータは、ログが一つです、このログはユーザーの味噌カツ作成履歴を保存、更新しており……、
 ……インデックスは、ユーザーIDとレシピコードでユニークなのでそれでユニークインデックスを張ってあります……。
 ……です」
れびゅあー「マスタインポート時のチェックはどの位やってる?」
さーばーえんじにあ「指定された素材の存在確認くらいですね」
れびゅあー「explain取った?」
さーばーえんじにあ「マスタはメモ化してあるのでクエリは初回しか行われませんし、インデックス張ってある親コード単位とかでしか取ってきてないのでしてないです……。ユーザーデータもそんな感じですね……、問題無いとは思うのですが、余裕があったらやっておきます」
れびゅあー「問題ないと思うが、やっといてね」


さーばーえんじにあ「次に今回の基幹となる、指定された素材群からレシピを確定するというロジックの説明をします。
ここのロジックですが、これこれこういう風に作られるレシピを確定しています」
れびゅあー「そこ、計算量がO(n2 )で、マスタの数が大幅に増えたら計算量ヤバくならない? 改善出来ないかなぁ……」
さーばーえんじにあ「nはマスタの数ですよね、マスタそんなに今は数多くないので現状は大丈夫だと思いますが……でも改善出来そうなのでやっておきますね」
れびゅあー「わかった、その時はまたレビュー頼む」
さーばーえんじにあ「わかりましたー」
れびゅあー「あ、後、もうちょっとコメントあった方が良いと思う。コード自体に問題なくても、仕様がちょっと複雑だから」
さーばーえんじにあ「はーい」


 自分自身の観点からだと、クソコードもクソコードだと気づけない場合が良くあります。クソコードでなくとも、ぱっと見では分からない複雑さになっていたりとか。
 文章とかでもそうですが、一旦時間を置いてまた読み直してみると、前には気付けなかった欠点などにも気付けたりします。

コーディング後のチェックが完了したら

 そうして、自分のコードに対する影響や、後々に対するコスト、QA確認の為の事柄などを鑑みた上で他者からのお墨付きを貰う事で、最終的に味噌カツ作成のサーバー実装が完了となります。

 上記以外にも、バグや後々のリスクなどを防ぐ為に出来る事は色々とあると思いますが、時間も有限ですし、出来る事柄をやったとして、それら全てをやって全てに効果があるかもやや不明でしょう。
 確認行為などを行うにせよ、それをマニュアル的に行うのではなく、どういう目的の為に行うのか、それを念頭に入れて行いましょう。

より良い実装の為に(まとめ)

 サーバーに限らず、クライアントも同じ事だと思いますが、
 実装の最終目標は、仕様に沿った動くコードを作りあげ、追加/新規機能を実現する事です。しかしながら実装=コーディングではありません。
 ただ動くコードを作り上げるのではなく、後々のコストが肥大化しない為に、シンプル且つ柔軟で拡張性のあるコードを作り上げる事。認識の齟齬から後々の不具合を生み出さない事。既存部分への影響を鑑み、それに対する問題がないかの確認。そんな将来に向けた配慮が必須となります。

 上記の実装に対する例は自分が実装をしている時はこんな感じで進めているというようなものですが、これでも不完全な部分はあるでしょうし、改善出来る部分はしていきたいと思っています。
 
さーばーえんじにあ「腹減ったし、今日はソースカツ丼でも食べるか」
ぷらんなー「は?」
さーばーえんじにあ「昨日味噌カツ食べたので」
ぷらんなー「よーしよしよしよしよし」

多分終わり

味噌カツで例えるソーシャルゲームのサーバーサイドにおける新規実装の流れ 3

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

もはや味噌カツではない

前回までのあらすじ

前回

以下のゲームで、

牧場経営ソーシャルゲーム:
ユーザーは牧場を経営する。また、牧場などで育てたものを調理し、食べる事が出来る。
ユーザーにはステータスが存在し、様々な行動によってステータスが変動する。ある一定のステータスを伸ばすと出来る事が増える。
ステータスは割合で決定されており、どれかのステータスを上げると他のステータスが落ちる。
一度得た能力は消えない。

味噌カツを作る事が決定しました。
提示された仕様書を読み込み、それに対して曖昧な点や改善すべき点を会話して解消した後に、サーバーエンジニアがすべき事は以下のように明確になりました。

・味噌カツを消費する事でユーザーのステータスのナゴヤ成分が上昇する、は既存のコードで対応可能か確認する
 =>変わらず
・味噌カツを作るAPIは既存の調理APIとは別として新規に作成する
 =>既存の調理APIとはかけ離れた部分が多くなった為
・味噌カツトップ画面の為のAPIを作成する
 =>クライアント側で持っていない情報を表示する必要が確定した為
 返すものは
  ・味噌カツを作成する為に必要な素材種別群

※実装する事が複雑でなくとも設計書として纏めて共有しておくとベターです。

 仮実装までの想定日数は2日と定めました。

仮実装

 実装に対して何から始めていくのかはどのような開発手法を取っているかによって違ってくるかと思いますが、ゲーム開発においての最優先事項は実機で確認出来る状態を作る、という事だと思いますので、それに沿って仮実装を行います。

さーばーえんじにあ「まあ、軽いやつからやっていくか」

味噌カツを消費する事でユーザーのステータスのナゴヤ成分が上昇する、は既存のコードで対応可能か確認する

さーばーえんじにあ「料理を消費する事でユーザーのステータスが上昇する仕組みはもうあって、料理には出来の良さの値も保存されていて、それに依存してステータスの上昇度合いが変動する仕組みもある。
 だから、それが味噌カツだろうと味噌カツじゃなかろうと、特に何もする必要はやっぱり無いな」

味噌カツトップ画面の為のAPIを作成する

さーばーえんじにあ「やるべき事は、あれ……これ、調理トップ画面もいじらないといけないか。
 味噌カツ以外にもそういう風に調理できるものが増えた場合、調理トップ画面からの遷移先が複数になるかもしれない。その複数の遷移先を判別出来る情報を調理トップ画面でも渡さなきゃいけないか。
 クライアントエンジニアさん、そんな感じで良いですか?」
くらいあんとえんじにあ「りょーかい。今回は取り敢えず遷移先1個だけだけど、レスポンスの形は、今回は配列じゃなくて良いかな?
 遷移先が複数になったらUIもそういう風に対応しなきゃいけないだろうからさ」
さーばーえんじにあ「分かりましたー」

さーばーえんじにあ「じゃ、味噌カツトップ画面では、クライアントから受け取った値を参照して、対応するマスタの情報を返す。
 レスポンスの形はクライアントエンジニアと相談する必要あるな」
くらいあんとえんじにあ「素材の種類が最大6つって決まっているなら、配列じゃなくて、それぞれ別のキーとして返して欲しい」
さーばーえんじにあ「りょーかい。現状だとそれだけだか。すぐ終わる」

味噌カツ作成のAPIを作成する

さーばーえんじにあ「えーっと、普通の調理APIをコピって、違う部分を変えていけばいいな。
 パラメータは味噌カツの素材群と味噌カツを作るという宣言が必要。
 通常の調理の時の素材存在確認とかのアサーションに加えて、本当にその素材で味噌カツが作れるかを確認。
 マスタを参照してどの味噌カツが作られるかを決定。ユーザーのステータスに加えて、そのマスタのデータを基にして味噌カツの出来の良さを決定。
 …………。
 まあ、取り敢えずそこあたりは仮実装だから適当にやっておこう。また後で必要なアサーションをリスト化したりしよう。
 レスポンスは作られた味噌カツでいいのかな?」
くらいあんとえんじにあ「そーだな、調理完了演出はそんなに普通の調理と変わらないから、多分それで事足りる」
さーばーえんじにあ「おっけ。後は必要なマスタのテーブルを作る処理とか、CSVからインポートする処理とか入れて……。
 明日には仮実装反映出来そうです」
くらいあんとえんじにあ「わかりました」
さーばーえんじにあ「APIのURLと必要なパラメータ、返されるレスポンスはこっちに書いておきました。後、調理APIでの追加パラメータはこんな感じです。これでいいですか?」
くらいあんとえんじにあ「……多分」
さーばーえんじにあ「追加で欲しいものあったら言ってください」
さーばーえんじにあ「で、後はマスタ待ちか。
 適当に作ってあるところしっかりと作ったり、テストコード書いたり、影響範囲纏めたり、そういう事しておこう」

仮マスタが来ました

ぷらんなー「マスタ、仮で出来たよ」
さーばーえんじにあ「入れてみますー。あ、ミスった、直して……ん? ちょっといいですか?」
ぷらんなー「ん?」
さーばーえんじにあ「何でも良い部分の素材アイテムコードって、0を入れるんですっけ?」
ぷらんなー「あ、それじゃまずい?」
さーばーえんじにあ「まー、どっちでも良いです。えーっと、既存コードに合わせるとして、他の部分どうだったっけ……基本的に0だけどnullの部分もある……あー、うー、えー……。0の方が多いか……。こっちが対応します」
※既存のクソコードは影響範囲が大きくなるのならばそっとしておいた方が良いかもしれません
ぷらんなー「わかった」
さーばーえんじにあ「後は問題なさそうですね」
ぷらんなー「りょうかい」
さーばーえんじにあ「APIの確認もして……あ、getだとパラメータは全部文字列か……直して……出来た、反映出来る」

仮実装反映後〜補強して本実装

実機確認

さーばーえんじにあ「開発環境に反映しました」
くらいあんとえんじにあ「りょーかい、確認しておく」
くらいあんとえんじにあ「なんかInternal Server Error起きた」
さーばーえんじにあ「えっ、確認します」
さーばーえんじにあ「あー……さっきの確認の時、この条件の時の事漏れてたな……」
さーばーえんじにあ「修正反映しました」
※仮実装でもこういう事を頻発させるとエンジニアとしての信頼度が落ちていきます
くらいあんとえんじにあ「りょーかい……大丈夫、取り敢えず後は問題無さそう」
さーばーえんじにあ「りょーかいです」
くらいあんとえんじにあ「こっちも後で確認出来る状態のもの上げておくから、上げたらそっちでも確認してみてね」
さーばーえんじにあ「わかりましたー」

仮実装から本実装へ

さーばーえんじにあ「さて、アサーションとかちょっと適当にやってた部分ちゃんとやるか。
 今回でやるべきなのは……普通の調理と比較した場合……まあ、この位だな。
 コード修正、書き加えて、その分のテストコード書いて。
 味噌カツ作りが失敗する。
  条件1:味噌カツなのに豚肉ではなく牛肉を選んだ
  条件2:味噌カツなのに味噌が無い
  条件3:味噌カツを作る為のお金が無い
  条件4:味噌カツを作る為に選んだ素材が既に存在しない
  条件5:味噌が賞味期限切れ
  ……
 味噌カツ作りが成功する。
  判定1:味噌カツを作ったので豚肉、味噌、パン粉等が消費される
  判定2:味噌カツを作ったので味噌カツが増えている
  判定3:味噌カツを作ったのでお金が減っている
  判定4:この材料で味噌カツを作ると名古屋コーチンという名の味噌カツが作られ、また、出来栄えは乱数を鑑みた上でこの範囲内に絶対に収まり、その作成情報がログとして保存される
  判定5:この材料で味噌カツを作るとマスタ内のどのレコードの条件も満たせないので、平凡な味噌カツが出来る
  判定6:味噌カツを合計1000個作るとそのアチーブメント”最早名古屋”が達成される
  判定7:神聖なる新生味噌カツ新星を作るとアチーブメント”新しい名古屋”が達成される
  ……
 ……え、単純に箇条書きしたら20件ほど必要なテストが出てきたんだけど、これ全部テストコード書くの?」
※そうです。書かないと改修が必要になった時の既存コードの保証が出来ません。
さーばーえんじにあ「ダミーユーザー作ってダミーマスタ作ってダミーユーザーデータ作って……今日の昼はコ●ダで味噌カツサンド食べよう……。
 マスタの準備さえ出来れば後は似たようなコードコピーして判定部分だけ変えていけばいいはずだから……」

数時間後

さーばーえんじにあ「あー、終わった。まあ、ちょっとバグも直せたし、これで一旦大丈夫か……じゃあ後は、影響範囲纏めて、QAに提出する確認すべき範囲も纏めて、コードレビューして、終わり、か。
 ちょっと遅くなったけどコ●ダ行こう」
くらいあんとえんじにあ「ちょっと良い?」
さーばーえんじにあ「えっ」

次で多分最後

味噌カツで例えるソーシャルゲームのサーバーサイドにおける新規実装の流れ 2

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

味噌カツは進化する

前回までのあらすじ

前回

以下のゲームで、

牧場経営ソーシャルゲーム:
ユーザーは牧場を経営する。また、牧場などで育てたものを調理し、食べる事が出来る。
ユーザーにはステータスが存在し、様々な行動によってステータスが変動する。ある一定のステータスを伸ばすと出来る事が増える。
ステータスは割合で決定されており、どれかのステータスを上げると他のステータスが落ちる。
一度得た能力は消えない。

以下の仕様にて味噌カツを作る事が決定し、

仕様概要
 ・既存の調理機能にて味噌カツページを作成し、ユーザーの所持する特定のアイテム群を消費する事によって、味噌カツを作れるように機能拡張

味噌カツについて
 ・味噌カツを消費するとユーザーのステータスのナゴヤ成分が上昇する。
  ・上昇度合いは味噌カツの出来の良さによって決定される。
   ・味噌カツの出来の良さは、既存の調理機能では、ユーザーのステータスだけで決定されていたが、味噌カツはそれに加え、消費するアイテムによっても変動する
    ・どのような味噌カツが出来るかはMisokatsuMasterで定義する

ナゴヤ成分
 ・ナゴヤ成分とはユーザーのステータスの新しい種類である
  ・ナゴヤ成分の上昇に伴う作用
   ・ナゴヤ成分の割合により新たなレシピを作れるようになる(例: 小倉トースト、抹茶スパ、名古屋コーチン)
   ・新たなアチーブメントの追加
 ※ナゴヤ成分の追加による事柄は全て既存マスタへのデータ追加で対応可能

ユーザーインターフェース要件(以下ユーザーインターフェースはUIと略す)
 ・調理トップ画面に味噌カツトップ画面への遷移ボタンを追加
  ・味噌カツトップ画面で表示するものは以下
   ・ユーザーの所持する味噌、豚肉、その他味噌カツを作成する為のアイテム一覧を表示、選択出来る
   ・ユーザーがこれまで作成した味噌カツを表示、消費出来る
   ・味噌カツ作成ボタン

仕様書を読み込んだらサーバーエンジニアが行うべき事は以下に纏められました。

・味噌カツを消費する事でユーザーのステータスのナゴヤ成分が上昇する、は既存のコードで対応可能か確認する
・味噌カツ作りは既存機能では対応出来ないので、既存機能の拡張、または専用機能の追加を行う
・味噌カツトップ画面で返すべき情報があるのならば、返すAPIを作成する

実装に入る前に考えよう

 さて、ここまで分かったなら実装に入りたいところですが、その前に一旦立ち止まりましょう。
 これをこのまま文言通りに実装して運用していったら、将来どういう事柄が起きるのかなどを想定してみると、色々と困る事が浮かんできます。

 例えば、今回の拡張が好調だったから、今度は博多ラーメンでハカタ成分の上昇とかやりたい! と言われたらどうなるでしょう。
さーばーえんじにあ「えっ、そうなるとMisokatsuMasterに博多ラーメンを入れる必要があるんだけど、MisokatsuにHakataを入れるの? そのテーブルに作る大元のものが味噌カツか博多ラーメンか区分するカラムも追加しなきゃいけないし、変数名もmisokatsuとか使ってるし、え、えぇ……」
 他にも、
・パン粉も追加で素材として選べるようにしたい!
・今まで作った味噌カツ履歴を更に詳細に表示出来るようにしたい!
・味噌カツよりソースカツの方が美味しい!
・抹茶スパの必要性
 などが浮かんでくるでしょう。
 要するに汎用性に欠けた作りにしてしまったりすると、後から改修を入れるとなった時に、手を加えるべき範囲が広くなってしまうという事になります。
 そうなると実装コストの増大、確認するべき場所が多岐に渡る、テストコードの大幅改修などなど、対応に掛かる時間及びに改修へのリスクの増加というとても面倒臭い事になってしまいます。
 そんな事を避ける為に、この味噌カツ作成に対してはこれから先、どこまでの拡張が想定されるのか、などをしっかり考えた上で実装に対して臨まなくてはいけません。
 じゃあ、それを誰に聞くのか。
 勿論、仕様を考えた人、プランナーに聞きましょう。


さーばーえんじにあ「仕様に関して質問があるんですけど」
ぷらんな「何でしょう」
さーばーえんじにあ「今回は味噌カツに対してそういう機能を追加するっていう実装ですけど、今後味噌カツ以外で何か博多ラーメンでハカタ成分を向上させたりとか、レモン牛乳でトチギ成分を向上させたり、海藻でチョウシ成分を向上させたりとか、そんな事は有り得ますか?」
ぷらんな「海藻……? まあ、無いとは言い切れないかな」
さーばーえんじにあ「じゃあ、味噌カツに特化した作りじゃちょっと困りますね。特にマスタ名とか、MisokatsuMasterに博多ラーメンとかサーターアンダギーとか野沢菜漬けとかそんなもの入れる事になっちゃうので、別名でお願いします」
ぷらんな「えー、あー、うー、……はい」

さーばーえんじにあ「後、ソースカツの方が美味しいと思います」
ぷらんな「は?」


 曖昧な仕様や今後拡張されたらこのままの仕様だとマズいとか、そういうものはプランナーやエンジニア、デザイナーとも話し合ったりして、実装する前にしっかりと固めていきましょう。
 話し合ったとしても、実装する途中、後でやっと発覚する事などもあったり、またどんな形での拡張も柔軟に対応出来る作りにするというのは流石に出来ませんが、早め早めの対応で将来的なコストは出来るだけ少なくなるように心掛けた方が吉となる事は確かです。
 その結果、以下のようになりました。

・味噌カツに特化した作りにはしない
 =>えんじにあ「芋煮作る時困るよ」
・味噌カツなどの素材を選べるレシピに対し、素材の数は最大6とする
 =>でざいなー「素材の種類の最大数が決まってないとUIのデザインが決めづらいよ」
・作成履歴を保持。作れる味噌カツの種類単位で最良の出来となったものを1個ずつ。
 =>えんじにあ「味噌カツ履歴とか将来的に表示する可能性あるなら、保持しといた方が良いよね」
  ぷらんな「可能性は無きにしも非ず」 
・抹茶スパは必要
 =>ぷらんな「ナゴヤ成分が100%の時のみ強力なバフアイテムとして作用するのさ」

 仕様書も更新され、マスタの形などもしっかりと固まりました。
 
 サーバーエンジニアがすべき事も、これからの拡張を鑑みた柔軟な作りをベースに実装できるようになりました。
 そしてすべき事も、更に明確になりました。

・味噌カツを消費する事でユーザーのステータスのナゴヤ成分が上昇する、は既存のコードで対応可能か確認する
 =>変わらず
・味噌カツを作るAPIは既存の調理APIとは別として新規に作成する
 =>既存の調理APIとはかけ離れた部分が多くなった為
・味噌カツトップ画面の為のAPIを作成する
 =>クライアント側で持っていない情報を表示する必要が確定した為
 返すものは
  ・味噌カツを作成する為に必要な素材種別群

さーばーえんじにあ「まあ、やる事はそんなに複雑じゃないし、大体新規実装になったから既存コードの調査とかあんまり要らないし、実装の為の新規マスタの形も整ったし、テストコード含めなければそんなに長い時間は必要じゃないかな」
さーばーえんじにあ「マスタが間に合うなら、仮実装で2日くらいで開発環境に反映、確認できるようになると思います」
くらいあんとえんじにあ「りょーかい」

まだまだ続く

味噌カツで例えるソーシャルゲームのサーバーサイドにおける新規実装の流れ 1

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

味噌カツ

こんなソーシャルゲームを作っているとします

牧場経営ソーシャルゲーム:
ユーザーは牧場を経営する。また、牧場などで育てたものを調理し、食べる事が出来る。
ユーザーにはステータスが存在し、様々な行動によってステータスが変動する。ある一定のステータスを伸ばすと出来る事が増える。
ステータスは割合で決定されており、どれかのステータスを上げると他のステータスが落ちる。
一度得た能力は消えない。

そして、このソーシャルゲームの運用はサーバーエンジニア、クライアントエンジニア、プランナー、デザイナー、プロジェクトマネージャー、ディレクターで運用されています。

サーバーエンジニア: メインの事柄は、ユーザーデータを管理。
クライアントが味噌カツを作りたいと要求してきたら、本当に味噌カツを作れるかどうか確認して、味噌カツを作る処理を行い、ユーザーデータを編集する。
味噌カツでいうと味噌カツを置く為の机や食べる人が座る為の椅子。土台。

クライアントエンジニア: ゲームの実部分。ユーザーが実際にゲームを遊ぶ部分の実装。
ユーザーが味噌カツを作る為のUIなどの表示、ユーザーが味噌カツを作るとボタンをタップしたら、サーバーに味噌カツを作ると要求を飛ばし、作れたらその旨の表示をする。
味噌カツでいうと味噌カツを食べる為の箸や味噌カツを載せる為の皿。

プランナー: ゲームの内容を決定する。
味噌カツを作ると決定する。味噌カツの味やどのような味噌カツを作れるのかなどを決定する。
味噌カツでいうと、味噌カツのレシピやメニュー。

デザイナー: ゲームの絵やUIを作成する。
味噌カツを描く。味噌カツ作成の為のUIなどを作成する。
味噌カツでいうと味噌カツ。

プロジェクトマネージャー: ソーシャルゲームのプロジェクトのマネジメントをする。
味噌カツでいうと、味噌カツを作る人を支える人。

ディレクター: ソーシャルゲームのディレクションをする
味噌カツでいうと、味噌カツの方向性を決定する人。

新規実装が決まりました

ぷらんな「先日の話し合いにより、納期までに使えるリソースを鑑みた上で、次のバージョン、ver味噌.カ.2にて、ウチの出している牧場ゲームに味噌カツを作る実装が入りました」
さーばーえんじにあ「わかりました」
ぷらんな「仕様書はまだざっくりとですが、こちらを参考にお願いします」

仕様書

仕様概要
 ・既存の調理機能にて味噌カツページを作成し、ユーザーの所持する特定のアイテム群を消費する事によって、味噌カツを作れるように機能拡張

味噌カツについて
 ・味噌カツを消費するとユーザーのステータスのナゴヤ成分が上昇する。
  ・上昇度合いは味噌カツの出来の良さによって決定される。
   ・味噌カツの出来の良さは、既存の調理機能では、ユーザーのステータスだけで決定されていたが、味噌カツはそれに加え、消費するアイテムによっても変動する
    ・どのような味噌カツが出来るかはMisokatsuMasterで定義する

ナゴヤ成分
 ・ナゴヤ成分とはユーザーのステータスの新しい種類である
  ・ナゴヤ成分の上昇に伴う作用
   ・ナゴヤ成分の割合により新たなレシピを作れるようになる(例: 小倉トースト、抹茶スパ、名古屋コーチン)
   ・新たなアチーブメントの追加
 ※ナゴヤ成分の追加による事柄は全て既存マスタへのデータ追加で対応可能

ユーザーインターフェース要件(以下ユーザーインターフェースはUIと略す)
 ・調理トップ画面に味噌カツトップ画面への遷移ボタンを追加
  ・味噌カツトップ画面で表示するものは以下
   ・ユーザーの所持する味噌、豚肉、その他味噌カツを作成する為のアイテム一覧を表示、選択出来る
   ・ユーザーがこれまで作成した味噌カツを表示、消費出来る
   ・味噌カツ作成ボタン

ぷらんな「細かい点は追って記載しますが、現状は上記が確定事項です」

サーバーエンジニアがまず考えなければいけない事

納期までに使えるリソースを鑑みた上で、と前置きされているとは言え、サーバーエンジニアはまず最初に実装を完成させなければいけません。
そうでなければ、クライアントエンジニアはサーバーに味噌カツを作りたいと要求が出来ずに、ダミー味噌カツを使っての仮確認程度しか出来ないのですから。
なので、
・ 実装までにどれだけの時間が掛かりそうなのかをまずは計算したい
です。
その為にはまず、
・味噌カツ作りを実装する為に、サーバーはどのような事を実装しなければいけないのか
をリスト化しましょう。
それが分からなければ、実装コストも分かりません。

味噌カツ作りを実装する為に、サーバーはどのような事を実装しなければいけないのか

仕様書を見ると、味噌カツ作りの実装には3つの要素があります。
・味噌カツを作る事
・ナゴヤ成分の追加
・味噌カツの為のUIの追加
しかしながら、ナゴヤ成分の追加に関しては仕様書にも書いてある通り、既存マスタへのデータの追加で事足りると言うので、サーバーエンジニアが行う事は味噌カツを作る事及びに、味噌カツの為のUIの追加となります。


それではまず、仕様書の味噌カツについての部分を読み込んでいきましょう。

味噌カツについて
 ・味噌カツを消費するとユーザーの特性のナゴヤ成分が上昇する。
  ・上昇度合いは味噌カツの出来の良さによって決定される。
   ・味噌カツの出来の良さは、既存の調理機能では、ユーザーのステータスだけで決定されていたが、味噌カツはそれに加え、消費するアイテムによっても変動する
    ・どのような味噌カツが出来るかはMisokatsuMasterで定義する

その中で、トップに来ている以下の一文を見てみましょう。

・味噌カツを消費するとユーザーのステータスのナゴヤ成分が上昇する。

特定のアイテムを消費するとユーザーの特定のステータスが上昇する。これは、 ユーザーにはステータスが存在し、様々な行動によってステータスが変動する。 というゲームの特性上、普通にマスタの追加などで、サーバーの改修は必要とせずに完了しそうですね(食べるという行為をする事でステータスの上昇を今までは想定していなかったとか、そういう事がない限り、それで出来なかったらそのコードはクソです)。
まあ、上記を満たす為に行うべき事は、本当にサーバーの改修が必要でないか、の確認程度になりそうです。
プロジェクトに参画している期間にも依るでしょうが、そう時間は掛からないでしょう。

TODO: 味噌カツを消費する事でユーザーのステータスのナゴヤ成分が上昇する、は既存のコードで対応可能か確認する

とすると、残りは以下になります。

・上昇度合いは味噌カツの出来の良さによって決定される。
 ・味噌カツの出来の良さは、既存の調理機能では、ユーザーのステータスだけで決定されていたが、味噌カツはそれに加え、消費するアイテムによっても変動する
  ・どのような味噌カツが出来るかはMisokatsuMasterで定義する

 これらは既存の機能では対応できないので、既存の調理機能の改修または、味噌カツを作る為の専用機能の追加、で対応する事になりそうです。

TODO: 味噌カツ作りは既存機能では対応出来ないので、既存機能の拡張、または専用機能の追加を行う


 次に、味噌カツUIの追加に関して見ていきましょう。

 UIの実装に対してはサーバーエンジニアは余り影響しないような気もしなくもありませんが、そのUIを表示する為に必要な情報をクライアントが全て常に持っているとは限りません(データ漏洩などの観点から、基本的にクライアントには必要最低限の情報しか渡さないようにすべきです)。
 なので、味噌カツUIを表示する為にクライアントが必要な情報をAPIを作成して適切に返してあげたりする必要があります。

UI要件
 ・調理トップ画面に味噌カツトップ画面への遷移ボタンを追加
  ・味噌カツトップ画面で表示するものは以下
   ・ユーザーの所持する味噌、豚肉、その他味噌カツを作成する為のアイテム一覧を表示、選択出来る
   ・ユーザーがこれまで作成した味噌カツを表示、消費出来る
   ・味噌カツ作成ボタン

 この要件から鑑みるに、味噌カツトップ画面でサーバーがクライアントに渡す必要がありそうな情報は、
・ユーザーの所持する味噌、豚肉、その他味噌カツを作成する為のアイテム一覧
・ユーザーがこれまで作成した味噌カツ
の二つです。
 これを実際味噌カツトップ画面を表示する上で渡す必要が出てくるのならば、その情報を返すAPIを作らなくてはいけません。
 クライアントエンジニアと相談になりますね。

TODO: 味噌カツトップ画面で渡すべき情報があるのならば、それ用のAPIを作成する


 さて、これでサーバー側が実装すべき事は纏まりました。

・味噌カツを消費する事でユーザーのステータスのナゴヤ成分が上昇する、は既存のコードで対応可能か確認する
・味噌カツ作りは既存機能では対応出来ないので、既存機能の拡張、または専用機能の追加を行う
・味噌カツトップ画面で返すべき情報があるのならば、返すAPIを作成する

 ここまで分かれば、実装に必要な期間も明確になってくるでしょう。それを共有し、スケジュールなどを組み立てましょう。
 しかしながら、ここまでが分かっても、まだ実装に移るのは早いです。

次回に続く

魔王を説き落とす前に説き落とした後の事を考えよう(負荷テストをしよう)

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

魔王を説き落とした(リリースを迎えた)からと言ってその後の事はちゃんと想定しておきましょう。

魔王を説き落とす(リリース)前に

ゆうしゃ「信頼できる仲間(コンテンツ)がいる! 設備(マスタ)も揃えた! 商品(リソース)も十分! 魔王と交渉に(リリース)行くぞ!」
おうさま「ちょっと待ってね」
ゆうしゃ「え? あの、おうさま、なんでこんなところに? この辺り一人で居ると、まだ色々と危険なのですが」
おうさま「この体、分身体だから。それはそれとして、君と交わした契約には魔王との交渉(リリース)だけじゃなくて、その後の町の運営(運営)まで任せているよね?」
ゆうしゃ「え、あ、うん? ええ、はい。一応町作ってありますよ? 機能もしっかりしてます。有志を住まわせてちょっとテスト(QAチェック)もしてもらいました」
おうさま「どのくらい住民が来るとか考えた? ちゃんとその人数想定(DAU)で機能するか見た?」
ゆうしゃ「え、いや、あんまり……」
おうさま「じゃあ、ホムンクルスを人数分だけ貸しておくからさ、テストしておいて」
ゆうしゃ「ホムンクルス」
おうさま「材料は安いからね」
ゆうしゃ「安い」

ホムンクルスに行動設定をしよう(負荷テストを作ろう)

ゆうしゃ「ホムンクルスが十万人も居ると凄いなあ」
ホムンクルス「あー」
ホムンクルス「びぇー」
ホムンクルス「ふぇふぇふぇふぇふぇふぇふぇふぇ」
ホムンクルス「ふるぅつ」
ゆうしゃ「えーっと、取り敢えず、ホムンクルスに命令を書き込めば後は勝手に動いてくれるんだよな(負荷テスト用のシナリオをJMeterとかで作りましょう)。一通りの動作を確認しておかなきゃいけないから、朝に起床してからご飯を作って、バスに乗って勤め先に行って、お仕事をして、お昼休憩、昼からも仕事をして、……残業設定とかどうしようか、ある程度ランダムに動かしておくか…………ここ辺りもランダムにしておかないとちゃんとテスト出来ないよな…………、えっと休日の設定? 図書館と警察署と運動場と劇場に色んな機能チェックしておかなきゃいけないよね(API単位で出来るだけ網羅しつつ、実際の行動を想定して作りましょう)、うん、えっと、魔王との交渉いつだったっけ、2ヶ月後? え? 間に合う?」

1週間後(シナリオテストの作成にかかる時間は規模に応じます)

ゆうしゃ「書き終えた……、眠いよ……さてテスト」
ホムンクルス「ばぎゃむみぇぁらべぽぶー」
ゆうしゃ「壊れちゃった。書き直して」
ホムンクルス「あみゃめれごみばるぶぶぶぶぶー」
ゆうしゃ「別の部分壊れてる。書き直して」
ホムンクルス「おぎゃああああああああああああああああああ」
ゆうしゃ「おぎゃああああああああああああああああああああああああああ」

2日後(バグ取りは頑張って下さい)

ゆうしゃ「……」
ホムンクルス「おはようございます。朝ごはん作ります。マンドラゴラと満月大根のスライム煮を作ります。食べ終えてからはスーツに着替えてバスに乗り会社に向かいます。会社に向かったらパソコンの前に3時間座った後お昼ご飯を食べます。お昼ご飯は8割の確率でコンビニ、1.5割の確率でラーメン、0.5割の確率で洒落たランチを食べます。お昼ご飯を食べ終えた後は……」
ホムンクルス「……夜ご飯を食べた後は日付が奇数ならばお風呂に入ります。日付が3の倍数ならばゲームをプレイし、また5割の確率でドラゴンウイスキーを飲みます。また、ドラゴンウイスキーを飲んだ時、3割の確率で明日に寝坊イベントが発生します。本日は12月25日ですので、クリスマスイベントが優先され、恋人がいる場合は今から外出します。私には現在恋人が居ませんのでクリスマスイベントは安売りのコカトリスターキーを食べる事になりました」
ゆうしゃ「……できた? できた! やったああああああああああああああああ(シナリオテストを作るのはとても大変です)」

実際に近い状況を作ろう(テストデータをデータベースに入れよう)

ゆうしゃ「まだ終わっちゃいない、終わっちゃいないんだ。次はちゃんと施設を仮でも実際の環境に近くしなきゃ……。図書館には本を入れて警察署には武器を用意して、刑務所には囚人を用意してスーパーには食材を会社にはパソコンを……。本は中身なんてどうでもいいとして、銃器も実際に発砲するイベントは起きないレベルだから起きないとしちゃって、うん、囚人は人形でいいや、うん、うん(テストデータはそこまできちんとしている必要は余りありません。入っている事、それ自体が結構重要な事です)」

3日後(テストデータを作成するソースを作る時間も規模に応じます)

ゆうしゃ「紛い物だらけだけど、形になった、残された猶予は後1ヶ月ちょっと、ホムンクルス達、動かすぞ!!!!!!!! やっと、やっと始まるんだ!」

一つの町でテストをしよう(単体APIサーバーでの性能を確認しよう)

ゆうしゃ「まずは軽く走らせてみよう。ホムンクルス起動!! スイッチオーン!」
ホムンクルスA「おはようございます。私は身長3m、体重272kgです。私は寝坊イベントが発生したので寝坊します」
ホムンクルスB「おはようございます。私は身長50cm、体重10kgです。私は歩いて会社へと向かいます。途中バスに乗ります」
……
ホムンクルスK「建設業として建設途中の建物の前で3時間立ちました。お昼ご飯に向かいます。抽選の結果本日はコンビニで食事を買います」
ホムンクルスPX「公務員として役所の中で4時間座りました。朝のイベントで弁当を作成したのでお昼ご飯イベントは存在しません」
ホムンクルスJJ「タクシー運転手として一定の道路を3時間周回しました。お昼ご飯に向かいます。抽選の結果突発的タスクの発生の為、お昼ご飯イベントは抹消されました」
……
ホムンクルスGA「おぎゃああああああああああああああああああ」
ホムンクルスPT「仕事を終了しました。夜イベントは無趣味の設定の為存在しません。直帰します」
ホムンクルスRR「残業イベントが発生した為仕事を終了出来ませんでした。もう2時間立ち続けます」
ホムンクルスAE「仕事を終了しました。夜イベントは遊び人の為抽選が行われます。本日はギロッポンでレッツパーリィします」
……
……
ゆうしゃ「ちょっとバグ起きたから直さなきゃ……(沢山動かすと発生するバグも良くあります)」
ゆうしゃ「でも、簡単なデータは取れたぞ。えーっと? 図書館で本を借りるイベントと警察署から処刑場にまで行くイベントが何か遅いなあ。何だこれ、調べてみよう(調査ツールを入れておくとボトルネックが分かりやすいです)」
ゆうしゃ「図書館で遅い原因は索引が無いからか。ちゃんと入れよう(データが沢山入っているとデータベースのindexの漏れなどが分かりやすくなります)。
 で、警察署から処刑場までは、これ、そもそも回り道ばっかり使っているじゃないか。ちょっと一本で行ける道を作ろう(ロジックが重かったりするとそれも重い原因になります。O(n2 )とかになってしまっているロジックはしっかり見直しておきましょう)」
……
……
ゆうしゃ「バグ取って、明確に遅い部分もちゃんと取り除いた。もう一度」
ホムンクルスA「おはようございます。本日は昨晩の残りの囚人ステーキがある為、それを朝食とします」
ホムンクルスB「おはようございます。風邪イベントが発生した為本日は自宅で何もしません」
ホムンクルスC「おはようございます。昨晩にご飯を炊き忘れるイベントを発生させた為、本日の朝ごはんはありません」
……
ホムンクルスTN「本日はイベントに来ています。現在の所持金が27301円である為、20000円の写真集を購入します」
ホムンクルスAS「先日に失業イベントが発生した為、ハローワークに来ています。面接イベントが20%で発生します。発生しませんでした。帰宅します」
ホムンクルスRK「本日昇給イベントが発生した為、75%の確率でディナーイベントが発生します。発生しました。ディナーイベントの為お金が7000円消費され、現在の所持金が-2988円となりましたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたたた」
ホムンクルスPI「本日宝くじイベントが発生した為、1%の確率で20億円が当たります。当選しました。現在の所持金は1928309576 + 2000000000 = -1780825929となりりりりりりりりりりりりりぃぃぃぃぃぃぃぃぃぃぃぃぃぃ」

ゆうしゃ「まだちょっとバグあるなぁ……所持金周り見直さないと」

3日後(バグ取りは頑張って下さい)

ゆうしゃ「バグもわかってる限りは直した。明らかに重い処理も潰した。さて、やっと性能を測れる(性能を測るのはバグや取り除ける重い処理を潰してからにしましょう)」
ゆうしゃ「えーっと、取り敢えず今の街の性能でどの位のホムンクルスを捌けるか見てみようかな」

ゆうしゃ「5000人は簡単に捌ける」
ゆうしゃ「10000人も捌けた、でもちょっと危ないかな……」
おうさま「進捗はどうかね」
ゆうしゃ「あ、おうさま。一つの街にどれだけの人が快適に住めるか確かめているところです。現状の結果がこれです」
おうさま「なるほど……。一つ聞くがね、食事というのは皆が一斉に摂るものではなかろう。全てのホムンクルスが1時に昼食を採るように設定されているが」
ゆうしゃ「あ、そうですね……」
おうさま「そこが一番先に限界値が訪れそうだが、実際の限界値はもう少し先になるのではないかね?」
ゆうしゃ「そうですね、少しブレを出してみます(重いAPIなどが一斉に同時に叩かれないように負荷試験は一斉の実行はしないようにしましょう。例えば、JMeterならばRampUpやsleepなどの値を操作しましょう)」
おうさま「頼んだぞ。あ、後、魔王との交渉は45日後までには行っておくように。そうでないと食用の死刑囚達の管理費が赤字になる」
ゆうしゃ「……分かりました」

2日後

ゆうしゃ「大体15000人が一つの街で捌けるかな……? これ以上増やすと渋滞とかが多発して良いことにはならないな……」
おうさま「結果が出たのかね」
ゆうしゃ「あ、はい。これです」
おうさま「ふむ……、これならば今の性能の街を想定人数まで捌けるように増やしていけば予算内に収まりそうだ」
ゆうしゃ「ですね(ほっと胸を撫で下ろす)」
おうさま「それで、一番先に限界が訪れるのは交通かね?」
ゆうしゃ「はい」
おうさま「そうした場合、電車やバスの本数を増やせば対応が可能か?」
ゆうしゃ「可能です(最も先に限界が来る要素を確認し、もし到達してしまった場合にどの部分を優先的にスペックアップすれば良いのか確認しておきましょう)」
おうさま「また、ホムンクルスを使って擬似的に街の機能をいろいろ使っているが、実際に機能は動いているのかね?」
ゆうしゃ「ホムンクルスの所持する金額やスーパーの商品の増減、処刑場に建てられた墓の数から問題ありません(DBのデータなどを確認して、実際の処理がきちんと行なわれているかを確認しましょう)」
おうさま「では、想定人数である75000人を実際に5つの街を作って確認してみようか」
ゆうしゃ「え、間に合いませんよ?」
おうさま「この街をそっくりそのままコピーすればいいのだろう? それなら構成情報をそっくりそのままコピーして移せばいいだけだ」
ゆうしゃ「?????」
おうさま「2日後までには対応しておく。待っておけ」
ゆうしゃ「???????????」

実際の想定で街を動かしてみよう(実際のDAU想定で負荷試験を行おう)

二日後

おうさま「できたぞ」
ゆうしゃ「ありがとうございます??(マジで作り上げたよこの人)」

ゆうしゃ「さて、まあ、取り敢えず、動かしてみるか。街をベッドタウンとオフィス街にそれぞれ分割して、1つのベッドタウンから5つのオフィス街にそれぞれ出勤させるようにして、と(ロードバランサを介してアクセスを複数のサーバーに分割させましょう)」
ゆうしゃ「実行」
ホムンクルスA「おはようございます、オフィスAへ出勤します」
ホムンクルスB「おはようございます、オフィスBへ出勤します」
ホムンクルスC「おはようございます、オフィスCへ出勤します」

1日後

ゆうしゃ「おかしい……」
おうさま「どうかしたのかね?」
ゆうしゃ「75000/5 = 15000ですよね?」
おうさま「そうだな」
ゆうしゃ「75000人を5つのオフィス街で働かせているのに、15000人を1つのオフィス街で働かせているより悪い結果が出るんです」
おうさま「結果は……時々、ところどころで詰まるようだな……」
ゆうしゃ「何故……?」
おうさま「うん? あー、ほれ、ここを見てみろ」
ゆうしゃ「はい? あ、あー……残業イベントがオフィス街Cで殆ど起きていない……だから、定時で帰る人が想定よりも多いから、その定時時刻での交通がパンクする……」
おうさま「一つの街でテストしていた時は、常に事象の発生確率が一定に保たれていたが、複数の街ではそれが偏る場合があるという事だな(ロードバランサはリソースを見て空いている場所にリクエストを投げるとか、そんな事は基本的にしてくれません)」
おうさま「幸い、予算はまだ十分だ。街をもう一つ二つ増やして試してみよう(余裕を持ちましょう)」

2日後

ゆうしゃ「問題……なし! 終わった、終わったよ、おうさま。これが結果レポートです」
おうさま「ふむ……問題ないな。それではホムンクルスを地に戻すか」
ゆうしゃ「え?」
おうさま「維持にも金が掛かるからな」
ゆうしゃ「あ、はい」
おうさま「cd path/to/homunculuses; sudo killall」
ホムンクルス「みゃぁぁぁぁぁぁぁ」
ホムンクルス「にゃぁぁぁぁぁぁぁ」
ホムンクルス「私は生きていぃぃぃぃぃぃぃ」
ホムンクルス「だずげでぇ゛ぇ゛ぇ゛……」
ゆうしゃ「…………(何か聞こえたような気がするけど、聞かなかった事にしよう)」

魔王を倒そう!(やっとリリース)

ゆうしゃ「魔王! こちらが我が王国が提示する、魔族と人間が共存する新モデルケースでございます」
魔王「トイレある。バスルームある。清潔好きな奴意外と多いからな。ベッドの質は十分。様々な形態の我が国民が住まうのに適している。そして重要なのは刑務所だ。人間も魔族も美味いからな。しっかり脱獄出来ないように作られているか? よし、大丈夫だな。そしてビジネス形態に変なところなし。……まあ、大丈夫だろう」
ゆうしゃ「やっだあ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛」

数日後

ゆうしゃ「満員電車とかは多少あるけど、想定範囲内。トラブルは多少あるけど、この街はちゃんと動いている。多分」
魔王「おい、勇者」
ゆうしゃ「なんでしょう?」
魔王「浄水場に食用スライムが入り込んで、その酸性の体のせいでぶっ壊れた」
ゆうしゃ「えっ」
魔王「早急に直してくれ」

僕達の戦いはこれからだ!!(バグは起こるよ。)

サイト内検索のインターフェースガイドライン

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

検索をデザインする上で意識すべきインターフェース

有益なフィードバックを提供すること

・レスポンス速度が速いこと
・サイトに適した別の検索キーワードの提案すること
 →ユーザーがサイトや商品に適したキーワードで検索してくれるとは限らないので、より具体的な結果になるよう提示する必要がある
・シンプルは表示項目
 →表示項目が多いと見づらく、少ないと詳細遷移すべきか分からないので、ユーザーの判断材料となる項目を適切に表示する必要がある

ユーザーのコントロールと自動的な制御のバランスがいいこと

検索結果一覧を表示する際、人気のアイテムを上位に出すことは大事だが、人気アイテムは確認済のリピートユーザーや、安い順に見たいユーザーなどのために並び順変更できることは大事。
・価格や並び順での並び替え

短期記憶の負荷を減少させること

ユーザーが検索を行う際必ず、確認したい目的のイメージを持っている。
検索画面でつまずいてしまうと、検索することに集中してしまい、目的のイメージへの意欲が薄くなってしまうので、ユーザーが詳細遷移するまでの負荷を減らす必要がある
・自動補完(サジェスト)
・絞り込み条件の表示
・履歴の表示

ショートカットを提供する

過去に訪問したページに再度訪問したいユーザーや、過去に行った検索条件で新しいアイテムを確認したいユーザーが、キーワード入力や検索を行わず目的を達成できるようにする必要がある。
・検索履歴
・お気に入り
・検索条件の記録

0件ヒットを減らす

誤字や絞り込みすぎによる0件ヒットを防ぐことが大事
・選択した絞り込み条件でのヒット件数の表示
・もしかして?の表示
・同義語対応

細部のレイアウトが重要

・検索窓が長いと入力されるキーワードも長くなる
・商品一覧の上部の文章は見られにくい
・検索に関係ないバナーなどを表示するのは良くない

美的意識も重要

いまいちなUIに比べUIが美しいと、検索結果0件の場合でも再検索を行ってくれるユーザーが多くなる

参考

情報検索のためのユーザインタフェース

カスタマーサクセスドリブン組織作り方のレポート JSCS#7

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

カスタマーサクセスとは?(おさらい)

顧客を成功させるために提供サービスの価値を最大に引き出せるよう支援すること。

登場人物

◆登壇者
 Repro株式会社 佐々木 翼
◆パネリスト
 HiCustomer株式会社 高橋 歩
 ベルフェイス株式会社 吉本 猛
◆モデレーター
 ベルフェイス株式会社 小林 泰己
◆内容
 地獄系CSの、組織のあり方や連携の仕方の解説


プロダクトとCSの関わり

顧客から要望を聞き出し、CSがユースケースを特定する。そして、顧客と機能の認識合わせを行った開発を行う。
CSは顧客からの声をチャットツールでダイレクトに収集し、使われない機能を開発させないことに注力する。
また、顧客の要望リストを管理し優先度を敏感に調整することが重要。

目的別に機能をジャンル分けしている。
・新規顧客開拓
・チャーン防止
・UX改善
・社内運用
etc

セールスとCSの連携(境界線?)

・CSは新規顧客のクロージングに参加し、顧客の期待値調整を行う。
・また既存顧客の商談を発見し商談を行う。
・CSと営業の仕事が重複するため、KPIをそれぞれ調整する必要がある。
 ※新規顧客獲得時のKPI
  CSも関わっているがCSは顧客獲得以外にも注力する必要があるため営業のKPIとしている。

部署全体のKPIの設定

KPIをツリー構造にしてどの項目を誰が達成するかを明確にするのは大事。
しかし、部門ごとで切り分けすぎると部分最適化してしまうため、KPIを共有することも大事。

CSの目的

カスタマーサクセスは、目的を持って1つづつクリアしていくのが重要。
そのために
①カスタマ―サクセス活動のゴールを考える
②事業KPIとCS活動を一致させること
が大事。

CSの業務範囲は企業担当制?役割担当制?どちらでやるべき?

・最初は企業担当制。その後、役割で切出せたり、メンバーで増えたりしてきたら役割担当制にする。
・最初はCSの役割を見つけることが大事。

オンボーディングとは?

・明確に決まったものはない
・お客さんのゴールを定義し達成すること

日本でもっとカスタマーサクセスを盛り上げよう!!の年末イベント

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

様々な会社のカスタマーサクセスを行っている人が登壇し、これまで経験してきた失敗談などをディスカッションしていく

日本でもっと”カスタマーサクセス”を盛り上げよう!!
俺たちの屍を超えていけ!!~実はこんなこと失敗してきました~

のセミナーに行ってきたのでどんな内容だったかを記載します。
食事とお酒も出て、笑いながらもとてもためになる内容でした。

◆モデレーター
 ベルフェイス株式会社
◆スポンサー
 JCSC
◆登壇者
 Sansan株式会社 田中二郎
 HiCustomer株式会社 高橋歩
 株式会社ビズリーチ 鈴木雄太
 ベルフェイス株式会社 小林昭宏、小林泰己(モデレーター)
 弁護士ドットコム株式会社 岩熊勇斗
◆内容
 過去を振り替えりこんな失敗をしてきたという話を登壇者達がディスカッションしていく

【失敗談】

◆顧客情報管理

顧客情報が散在
請求情報を参照するためにいろいろな情報を参照する必要があり、顧客がおおくなってくるとその分負荷が上昇していった。
データ分析正確にできず、何となくあっているだろうという不確かな状態であった。
スプレットシート管理などは最初から行わず、セールスフォースなどを利用するなどしたほうがよい
少なくとも、金額にかかわる部分の情報はまとめて管理しないといけない。

◆再現性

良いことでもあるが、顧客に入っていく対応を行っていた。
できる人は好いが、個々の顧客に対して入っていく対応を行いなおかつ多くの客の対応を行うことは難しい。
CSを俗人的にしないためにも、CSレベルを下げてでも誰にでもできるような対応を目指すことも重要
ただし、最初からCSレベルを下げるのではなく、いろいろ試し、サービスに合ったCSを探っていくことが重要

◆忖度

営業からエンジニアへのフィードバックで良かった点のみが伝えられたが、エンジニアですら問題点と思っている個所も上がってこず、営業に対して疑問を感じるようになることがあった。
フィードバックする際に情報を取捨選択するのもよいが、そのまま伝えたほうが良いこともある

◆ユーザー会

ユーザー会とは、ユーザーの言葉をエンジニアに伝える場であるが、エンジニアを参加させたところで、現場の話をするのが主で、コンテンツの話はせず、ユーザー会の目的を達成できていなかった。
ユーザー会という名の飲み会を行うと会話の敷居が下がり、何気ない会話の中からユーザーの言葉をエンジニアに伝えることができる場合もある

◆戦略なき戦術

すぐ目の前の顧客対応に集中しサービス全体の今後を見たうえでの対応ができていなかった。
いろいろな施策を無作為に行うのではなく、コンセプトを決め、一貫性をもって戦術を実施することが重要

【Q&A】

Q:BtoBで顧客対応者が辞めると顧客が離れるがどうしたらよいか?

A:契約時もしくは定期的に、契約の意思決定者は誰かを把握し、会社に対して担当者を複数人あてること

Q:カスタマーの業務範囲は?

A:顧客が活用してよかったと思う箇所すべてが業務範囲

Q:再現性を高めるための具体的な方法は?

A:マニュアル化

Q:カスタマー作成の立ち上げに何をすればいい?

A:・一人のお客さんを幸せにするにはどうすればいいか検討
・顧客と会い徹底的に話す
・とりあえずいろいろな対策をやりまくる
・オンボーディング

【Rails】I18nの言語データをDBから取得【i18n/i18n-active_record】

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

I18nの言語データをymlではなくDBから取得する方法を記述します。

準備

1.Gemfileに追記してbundle install

Gemfile

gem 'i18n-active_record', :require => 'i18n/active_record'

2.migrateファイル作成

$ bundle exec rails g migrations CreateTranslations locale:string key:string value:text interpolations:text is_proc:boolean
class CreateTranslations < ActiveRecord::Migration
  def self.up
    create_table :translations do |t|
      t.string :locale
      t.string :key
      t.text   :value
      t.text   :interpolations
      t.boolean :is_proc, :default => false

      t.timestamps
    end
  end
end

3.config/initializers/i18n_active_record.rbの作成

require 'i18n/backend/active_record'

Translation = I18n::Backend::ActiveRecord::Translation

if Translation.table_exists?
  I18n.backend = I18n::Backend::ActiveRecord.new

  I18n::Backend::ActiveRecord.send(:include, I18n::Backend::Flatten)
  I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)

  # 一度呼んだkey,valueをインスタンス変数に保存する場合に使用。
  # 毎回SQLを実行しなくて済むがI18n.reload!しないとDBの値が書き換わっても前回の値が表示される
  # マルチテナント非対応
  # I18n::Backend::ActiveRecord.send(:include, I18n::Backend::Memoize)
  # I18n::Backend::Simple.send(:include, I18n::Backend::Memoize)

  I18n.backend = I18n::Backend::Chain.new(I18n.backend, I18n::Backend::Simple.new)

  # I18n.t()でDBでもファイルでも言語がヒットしなかった場合、その時のkeyやoptions等の情報ををDBに保存したい時に使用
  # 後でレコードやlocaleファイルを追加・更新する時に便利になるが毎度saveが走るためパフォーマンス低下の恐れあり
  # I18n::Backend::Chain.send(:include, I18n::Backend::ActiveRecord::Missing)
  # I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n::Backend::Simple.new)
end

使い方

1. 通常

config/locales/ja.yml

ja:
  user:
    name: ymlのユーザー名
$ bundle exec rails c

[1] pry(main)> I18n.t('user.name')
=> 'ymlのユーザー名'

[2] pry(main)> Translation.create(locale: :ja, key: 'user.name', value: 'DBのユーザー名')
=> #<I18n::Backend::ActiveRecord::Translation...........

[3] pry(main)> I18n.t('user.name')
=> 'DBのユーザー名'

2. proc

$ bundle exec rails c

[1] pry(main)> Translation.create(locale: :ja, key: 'test.proc', value: '1+2', is_proc: true)
=> #<I18n::Backend::ActiveRecord::Translation...........

[2] pry(main)> I18n.t('test.proc')
=> 3

Kernel.eval(value)してるだけみたい。ソース

3. I18n::Backend::ActiveRecord::Missingの使用時

config/initializers/i18n_active_record.rb

# I18n::Backend::Chain.send(:include, I18n::Backend::ActiveRecord::Missing)
# I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n::Backend::Simple.new)

↓コメントアウトを外す

I18n::Backend::Chain.send(:include, I18n::Backend::ActiveRecord::Missing)
I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n::Backend::Simple.new)
$ bundle exec rails c

[1] pry(main)>  I18n.t('user.name', name: 'なまえ', append: 'です')
=> "translation missing: ja.user.name"

[2] pry(main)> Translation.last
=> #<...Translation... id: 1, locale: :ja, key: 'user.name', value: nil, interpolations: [:name, :append], ....

DBに入っていないものを参照したときにレコードが自動的に作られる。
interpolationsカラムにその時の引数の名前が入る。

注意事項

このままではI18n.tを呼ぶたびに毎回DBアクセスが走るのでキャッシュを使用する方法も次回記述します。
次回の記事

【Rails】I18nの言語データをDBから取得 / キャッシュ使用【i18n/i18n-active_record】

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

前回「I18nの言語データをDBから取得」では毎回DBアクセスが走りパフォーマンスに不安があるためキャッシュを使用する方法を記述します。

前回の記事

3.config/initializers/i18n/cacheable.rbの作成

i18nのオープンクラスを使用

module I18n
  module Base
    def translate(*args)
      options  = args.last.is_a?(Hash) ? args.pop.dup : {}
      key      = args.shift
      backend  = config.backend
      locale   = options.delete(:locale) || config.locale
      handling = options.delete(:throw) && :throw || options.delete(:raise) && :raise # TODO: deprecate :raise
      translate_result(key, backend, locale, handling, options)
    end
    alias t translate

    def translate_result(key, backend, locale, handling, options)
      cache_key = translate_cache_key(locale, key, options)
      # キャッシュから取得orキャッシュを作成
      Rails.cache.fetch(cache_key) do
        enforce_available_locales!(locale)
        result = catch(:exception) do
          if key.is_a?(Array)
            key.map { |k| backend.translate(locale, k, options) }
          else
            backend.translate(locale, key, options)
          end
        end
        result.is_a?(MissingTranslation) ? handle_exception(handling, result, locale, key, options) : result
      end
    end

    def translate_cache_key(locale, key, options = {})
      File.join(locale.to_s, key.to_s, options.presence.to_s)
    end
  end
end

3.config/initializers/translation/cacheable.rbの作成

レコードとキャッシュを一緒に作成、更新、削除する

Translation.class_eval do
  after_create :create_i18n_translate_cache
  after_destroy :delete_i18n_translate_cache
  after_update :reset_i18n_translate_cache

  private

  def create_i18n_translate_cache
    I18n.t(key, locale: locale) # 一回呼んでキャッシュ作成
  end

  def delete_i18n_translate_cache
    Rails.cache.delete(delete_cache_key)
  end

  def reset_i18n_translate_cache
    delete_i18n_translate_cache
    create_i18n_translate_cache
  end

  def delete_cache_key
    I18n.translate_cache_key(locale_was, key_was)
  end
end

【jQuery】開閉パネルを最初から開いた状態にする

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Javascriptが苦手なWebデザイナーです。基礎的なことから学んでいる状態なので、簡単なことでもつまずいてしまうという方向けの内容です。

◆Javascript

$(function(){
  $(".toggle").show();
    $(".accordion dt").on("click", function() {
      $(this).next().slideToggle();
      $(this).toggleClass("active");
    });
    $(".accordion dt").mouseover(function(){
      $(this).addClass("over");   
      });
    $(".accordion dt").mouseout(function(){
      $(this).removeClass("over");    
  });
});

◆CSS

.accordion {
    clear: both;
    width: 100%;
    padding: 0;
}
/* dt */
.accordion dt {
    background: url(img/arrow.png) no-repeat 98% 50% #aab6ba;
    background-size: 15px;
    padding: 10px;
    color: #fff;
    cursor: pointer;
    margin: 0;
}
    .accordion dt.active {
        background-image: url(img/arrow.png);
    }
    .accordion dt.over {
        background-color: #c8cfd2;
    }
/* dd */
.accordion dd {
    width: 100%;
    border: 1px solid #aab6ba;
    box-sizing: border-box;
    padding: 10px;
    margin: 0;
}

◆html

<dl class="accordion">
  <dt>開閉スイッチ</dt>
  <dd>
    <ul class="toggle">
      <li>内容</li>
      <li>内容</li>
      <li>内容</li>
    </ul>
  </dd>
</dl>

解説
$(".toggle").show(); で最初は開いている状態にします。
.accordion dt をクリックすると、パネルが開閉するという仕組みになってます。


あとがき
最初から表示させつつ開閉バーをマウスオーバーしたら色が変わって矢印をくるってさせる方法がわからず解決までに時間がかかってしまったので、備忘がてらまとめました。
案件や要望によってコロコロとしようが変わるので、臨機応変に対応していく力が必要です。
Javascriptが苦手なWebデザイナーさんのお役に立てれば嬉しいです。


参考サイト
開閉パネルを最初から開いた状態にする

【超初心者向け】Javascriptの確認ダイアログを複数設置する方法

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

業務で複数の確認ダイアログを表示させたいことがあり、1つのhtml内に複数設置する方法がわからなかったので備忘します。 検索してもなかなか思った通りの解決方法が見つからず、試行錯誤の上、解決方法を発見しました。

javascript

function sample01() {
    if(window.confirm('サンプル1のコメントです。よろしいですか?')){
    }
}

function sample02() {
    if(window.confirm('サンプル2のコメントです。よろしいですか?')){
    }
}

function sample03() {
    if(window.confirm('サンプル3のコメントです。よろしいですか?')){
    }
}

html

<head>
  <script src="sample.js"></script>
  <script src="jquery.js"></script>
</head>

<body>
  <a onClick="sample01" href="#">サンプル1リンク</a>
  <a onClick="sample02" href="#">サンプル2リンク</a>
  <a onClick="sample03" href="#">サンプル3リンク</a>
</body>

解説
確認ダイアログで表示されるコメントを記述したjavascriptファイルを、html内の <head> ~ </head> 内に記載します。(ここではsample.jsとします。)
javascriptの sample01 とhtmlに記載する onClick="sample01" を同じ名前にすることがポイントです。
たったこれだけで、1つのhtml内に複数の確認ダイアログを設置することが可能です。


あとがき
この方法がもうわからず、数ヶ月後に謎が解けました。
謎が解けてみると簡単ですが、わからない人にはわからないんです。
エンジニアさんはきっとこんなことで悩まないだろうけど、Javascript知識のないデザイナーにはこれだけでも一苦労なんです!

Webデザイナーの方で、Javascriptが得意ではなく、きっと同じような悩みを持った方がいると思うので、参考になればいいなと思います。


参考サイト
確認ダイアログを表示する


検証ブラウザ
Chrome / Firefox / Safari / Edge / IE10
※全て最新版

最近人気な記事