株式会社LIFULLのブログ - TECH PLAY

TECH PLAY

株式会社LIFULL

株式会社LIFULL の技術ブログ

664

こんにちは! プロダクトエンジニアリング部の吉永です。 LIFULLには2020年8月に入社しました。 今回は入社後、早々に任せていただいた仕事について記事を書きたいと思います。 内容としては、静的サイトをホスティングしている、とあるサービスのAWS構成を刷新し、CI/CDを導入したプロジェクトについて、どんな理由でどんなことに考慮して刷新していったのかについてご紹介していきます。 アジェンダ 刷新対象サイトの旧AWS構成図 AWS構成刷新の背景 新AWS構成について Lambda@Edgeについて インフラのコード化について CI/CDについて まとめ 刷新対象サイトの旧AWS構成図 まずは今回刷新の対象となったサイトの旧AWS構成図を見てください。 静的サイトをホスティングしているだけなのですが、EC2インスタンスがいたり、S3のバケットも複数あったりで実現している機能に対して、構成がやや複雑に見えます。 この構成のポイントを要約すると下記になります。 S3のバケットが二つあり、それぞれPC用ページ、SP用ページのリソース類(html/css/jsなど)が格納されていた。 サイトにアクセスしてきたデバイスに応じて、どちらのS3からコンテンツを返却するかをEC2インスタンス内で稼働しているnginxで振り分けていた。 デバイス判定にはHTTPリクエストヘッダーのユーザーエージェントを使用。 また、上記サイトのローカル開発環境は下記のようになっていました。 サイトのローカル開発環境はRuby on Rails。 サイトの更新を行うエンジニアはローカルで更新後、Railsアプリケーションを起動し、シェルスクリプトを実行して静的コンテンツをローカルに保存、保存した静的コンテンツをS3にデプロイ、その後リポジトリを更新して、リモートへプッシュしていた。 AWS構成刷新の背景 今回、AWS構成を刷新した理由は下記2点です。 Amazon Linux AMI のサポート期間終了に伴う対応が必要だったから。 https://aws.amazon.com/jp/blogs/news/update-on-amazon-linux-ami-end-of-life/ 上記ページにもあるように、EC2インスタンスを更新するなり、何かしらの対応を2020年中に行う必要があった。 また、旧AWS構成を手動で構築したこともあり、テスト環境と本番環境の設定が微妙に異なっているなどが発生していた。 生産性の観点から、ビルドやテストだけでなくリリースプロセス全体を自動化したかったから。 必須ではなかったが、先述したようにローカルで更新したリソースをほぼ手動でデプロイしており、作業担当者がリポジトリのコミット、プッシュを忘れると、リポジトリの内容とデプロイされている静的コンテンツの内容が一致しない可能性があった。 また、ローカル開発環境が作業者PCに直接環境を構築する前提だったこともあり、環境構築手順書のメンテナンスや、引継ぎ時の環境再現コストなどがかかっており、このあたりも改善したかった。 新AWS構成について これらを踏まえ、現在使用できるAWSマネージドサービスから最適かつシンプルな組み合わせを模索して、新構成を設計しました。 AWS構成の大きな変更点は下記になります。 EC2インスタンス内で稼働していたnginxの役割をCloudFront前段で動作するLambda@Edgeに変更。 EC2インスタンスが不要になったので、前段にいたALBも一緒に削除。 S3バケットも一つにまとめ、バケット内に/pc、/spフォルダを設け、それぞれのページリソースを格納。 CloudFrontはS3の内容をそのままキャッシュするように変更。 EC2が構成からなくなったことにより、ランニングコストが事前の試算では従来の1/3くらいにまで削減できそうだということもこの構成にした大きな理由です。 Lambda@Edgeについて Lambda@EdgeはCloudFrontのビューワーリクエストをトリガーにして動作するように設定し、HTTPリクエストに含まれるリクエストパスをユーザーエージェントに応じて書き換えるようになっています。 イメージが湧きやすいように設定したLamda@Edgeソースの一部を下記に記します。 // User-AgentからPC・SP判定を行ないリクエストURIを変換する exports.handler = ( event , context, callback) => { const request = event .Records [ 0 ] .cf.request; const headers = request.headers; if (headers [ 'user-agent' ] ) { var uri = request.uri; // UA判定 if (isSpBrowser(headers [ 'user-agent' ] )) { // スマホ uri = '/sp' + uri; } else { // PC uri = '/pc' + uri; } request.uri = uri; callback( null , request); return ; } callback( null , request); } ; インフラのコード化について 旧AWS構成が手作業で構築した環境だったので、テスト環境と本番環境で微妙に差異がありましたが、今回1からインフラ構成を作ったのでCloudFormationを用いてインフラのコード化を行いました。 コード化したことにより、テスト環境で構築した構成を本番環境で再現するのが容易でしたし、今後何かしらの変更を加える際にもコードベースで管理しておけば、テストと本番で設定が異なるなどの状態には陥りにくいと思います。 ※実際には少しの変更ならAWS管理画面から直接行いたくなりますが・・・それをやってしまうとせっかくコード化した意味がないので、手動設定はなるべく排除しましょう。 CloudFormationについては別途Qiitaにて、この案件を通じて学んだ内容を備忘録代わりに投稿した記事があるので、もし興味があれば参照してみてください。 AWS CloudFormation入門 CI/CDについて 今回改善したかったもう一つの個所がローカルでサイトを更新してから、デプロイするまでの流れでした。 改善点をまとめると下記になります。 ローカル開発環境をDockerで構築し、環境再現コストを削減。 masterブランチにマージされたら、GitHub Actionsでデプロイ実行。 デプロイ作業を自動化したことでヒューマンエラーが混入しなくなり、作業者も安心してリリースできるようになりました。 また、副産物として、GitHub Actionsにてバージョン管理TAGの自動付加も行えるようにしたので、その件についてもQiitaに投稿しました。 もし興味があれば参照してみてください。 GitHub ActionsでX.Y.Z形式のバージョニング自動タグ付けを行う まとめ AWSについては前職では少し触った程度の浅い知識しかなく、入社早々任せていただいた仕事がAWS構成の刷新だったので一抹の不安はありましたが、最終的にはかなり色々なものを吸収させていただき、とても良い経験になりました。 今回の業務を通じて得られたものを下記にまとめました。 AWS構成でどこにどれくらいのコストがかかっているのか?調べる力が身についた。 AWSマネージドサービスは常に進化しており、数年前は実現できなかったことが近年ではシンプルな構成で実現できるようになっていたりするので、ベストプラクティスは常に変化していることを知れた。 インフラのコード化を初めて行ったが、テスト環境で構築した環境を本番環境に簡単に構築できるメリットを実感できた 将来的に似たような構成を作る際に今回作ったCloudFormationが流用できる可能性を感じたので、インフラのコード化は業務効率化や標準化の観点からみても非常に有益な作業であると実感できた。 CI/CDを導入することでリリース作業の負担を減らすことのメリットを実感でき、かつリリース作業を属人化することなく、だれでも簡単な操作でできるようになったので、リリース作業の心理的負担もだいぶ和らいだ。 以上となります。 最後までご覧いただき、ありがとうございました。
はじめ こんにちは、アプリケーションエンジニアとして働いてます。キム ソンジュです。 今回の記事では自分が参加したPJで利用した、インフラ構成から、CI/CD環境を利用して簡単にアプリケーション開発ができる方法について紹介しようと思います。 システム投入・設計背景 既存のレガシーシステムには、次の問題がありました。 デプロイの手順が複雑で時間かかり、面倒な作業が多い 環境ごとにミドルウェアのバージョンが異なる この問題を解決し、かつ新しい技術にチャレンジするために、チーム内で次の内容で進めるようチームで決めました。 Dockerを活用して環境ごとの差をなくす GitHubでソースコードを管理するので、CI/CDにはGitHub Actionsを採用 Dockerを利用することによる、ECRとECSを活用 入る前に 本記事で話したい内容は「このような方法で、こんなに簡単にアプリの開発からデプロイまでできる」といった紹介をするのが目的です。 案内してるAWSの各技術の細かい使い方、テストで使ってるGo言語の深い話については紹介しませんのでその点をご了承ください。 登場人物 AWS IAM AWS ECR AWS ECS AWS ALB(※1) AWS Parameter Store(※2) Github Actions Docker Go ( Go以外のアプリケーションも利用可能です) ※1. ALBはFargateを利用する時の必須技術になります。 ※2. Parameter Storeは、Dockerを起動させる時に環境変数を利用して敏感な情報をアプリで利用するため使います。 ※その他の登場人物については、上記で決めたことを元にして登場しました。 作業結果の予想図 上記の登場人物を利用する場合、下記のような構成が出ます。 作業の流れ Goを利用したアプリ作成 Dockerを利用したContainer環境構築 Local環境起動確認・テスト AWS設定 Github Actions設定 実装テスト こちらの流れで作業を進めて行く予定です。 それでは始めましょう! 1. Goを利用したアプリ作成 go-json-reset のModuleを利用します。 この記事では上記の例文をそのまま利用する予定です。 go module を利用します。 参考コード はこちらです。 2. Dockerを利用したContainer環境構築 goが設定されてるAlphine Linuxを利用します。 TimeZone設定がDefault UTCなので調整します(ログ書くときに時間がずれることを予防します) go moduleの設定と、Install作業を行います。 buildした後、実行させます。 dockerfile ## get alphine + go image ver.1.14 FROM golang:1.14.2-alpine3.11 ## pakcage update & install RUN apk add --update curl git pkgconfig curl-dev ## modified timezone RUN apk add tzdata ENV TZ=Asia/Tokyo ## setup env variables for go module ENV GO111MODULE "on" ## source code copy from host disk WORKDIR /go COPY . /go/src ## module install ## If You didn't set up go.mod in your local workspace, You can not use command go install WORKDIR /go/src RUN go install ## go build RUN go build ./main.go ## container listen port EXPOSE 8080 ## command ## start CMD [ "./main" ] ここまで来たら下記のようなディレクトリ構成になります。 . ├── Dockerfile ├── go.mod // go module 設定ファイルになります。 ├── go.sum // なくても構いません。 └── main.go 3. Local環境起動確認・テスト docker build postman, curl などを利用して、テスト docker build -t lifull_test . docker run -p 8080:8080 qiita_test:latest 自分はPostmanを利用して見ました。 アクセスログが出力されているか見てみましょう?! よし、ここまで来たら準備は完了です。 4. AWS設定 AWSの環境を設定しましょう、ALB,SGの細かい設定についてはこの記事では案内しません、必要最低限の設定で作成します IAM 設定 Github Actionsが利用するDeploy用ユーザーを作成 必要権限は ECR,ECSに関連する権限を付与します。 作成時 AccesToken、Keyを保存しましょう。 FargateのTaskが使うRoleを設定します。 ecsTaskExecutionRole この名がDefaultです。 権限は、ECR,ECSの権限に更に、CloudWatch,Parameter Storeへの利用権限が必要です。 Parameter Store 設定 アプリで利用する各種環境変数周りがあります(Laravelの場合.env周りに設定する環境変数です)こちらをGo+Dockerで利用する場合、環境変数をOSで扱うのが一般的で、こちらを実現するためにAWSでは SystemManager > Parameter Storeに設定します。 key-valueで設定します。暗号化された文字列として保存するのがより安全ですね! Security Group 作成 利用するポートを許可します。 今回の場合は8080をOpenしましょう! ALB 作成 Public Subetを設定します。 Public DNSが使える状態にします。もしくはHTTPS設定を利用して使える場合DNS設定を行います。(ACMなどが登場しますね) ECR 作成 Repositoryを作成します。(Docker-hubのPrivate版的な感じで、AWSでDockerを利用する時よく使います。) ECS 設定 ECSの構成について細かい説明は行いません。下記の設定は注意してください。 task定義が必要です。 まだRepositoryにアップしたイメージがないので適当に書いておいても構いません。   Containerに渡す環境変数の設定が必要です。上記のParameter Storeで設定したパラメーター名を環境変数:パラメーター名構成で作成します。 Container側に必要はPortをOpenする必要もあります。ここでは8080ですね。 clusterを作成します。 serviceを作成します。 5. Github Actions設定 一覧の設定が終わったら、Github Actionsを設定しましょう Github Actionsについては気になる方は以前自分が Qiitaに投稿した記事 がありますので、参考にしてください Trigger push Target Branch master doing docker build task-definition.json を利用したDeploy作業 .github/workflow/main.yml ### main.yml name : test workflow for qiita on : push : # event trigger on push branches : master jobs : build : # job id name : sjkim action # job name runs-on : ubuntu-latest # virtual os steps : - name : set up go 1.14 uses : actions/setup-go@v1 with : go-version : 1.14 - name : Checkout branch go module directory uses : actions/checkout@v2 - name : package install uses : actions/cache@v2 with : path : ~/go/pkg/mod key : ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys : | ${{ runner.os }}-go- - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ap-northeast-1 - name : Login to Amazon ECR id : login-ecr uses : aws-actions/amazon-ecr-login@v1 - name : Build, tag, and push image to Amazon ECR id : build-image env : ECR_REGISTRY : ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY : qiita-test IMAGE_TAG : ${{ github.sha }} run : | # Build a docker container and # push it to ECR so that it can # be deployed to ECS. docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" - name : Fill in the new image ID in the Amazon ECS task definition id : task-def uses : aws-actions/amazon-ecs-render-task-definition@v1 with : task-definition : task-definition.json container-name : qiita-container image : ${{ steps.build-image.outputs.image }} - name : Deploy Amazon ECS task definition uses : aws-actions/amazon-ecs-deploy-task-definition@v1 with : task-definition : ${{ steps.task-def.outputs.task-definition }} service : qiita-service cluster : qiita-test-cluster wait-for-service-stability : true Github Actions marketplaceにある ECS Deploy を元に作成してます。 Github Secretsについて Github Actions上で利用する秘密情報や、アクセストークンなどを利用する時に使います。 {{ secrets.AWS_SECRET_ACCESS_KEY }} などで利用可能です。 setting > secrets > new secrets task-definition.json { " requiresCompatibilities ": [ " FARGATE " ] , " inferenceAccelerators ": [] , " containerDefinitions ": [ { " name ": " qiita-container ", " image ": " ***/qiita-test ", " resourceRequirements ": null , " essential ": true , " portMappings ": [ { " hostPort ": 8080 , " containerPort ": " 8080 ", " protocol ": " tcp " } ] , " secrets ": [ { " name ": " ENV ", " valueFrom ": " dev " } ] , " logConfiguration ": { " logDriver ": " awslogs ", " options ": { " awslogs-group ": " /ecs/qiita-test ", " awslogs-region ": " ap-northeast-1 ", " awslogs-stream-prefix ": " qiita " } } } ] , " volumes ": [] , " networkMode ": " awsvpc ", " memory ": " 512 ", " cpu ": " 256 ", " executionRoleArn ": " ****/ecsTaskExecutionRole ", " family ": " qiita-task ", " taskRoleArn ": "", " placementConstraints ": [] } taskは上記AWSの作業手順のなか、Taskを作成したら、Task詳細タブの中にJsonて書かれてる部分があります。 その部分をコピしてLocalのPJ内に配置したらOKです。 Cloud Watchのロググループは存在してない場合、自動的に作成してくれます。(Deployユーザーに権限が無い場合Deploy中にNGになりますので注意) secretsの部分でContainerにわたす環境変数を設定します。nameは渡す環境変数名 valueFromはParameter Storeに指定した名前を入れます。 container name, task name, cluster name, service name など、先に設定しておかない、又はタイポで間違った内容を書くと、Github Actionsの動作中に落ちます。 6. テスト もうほぼ準備は完了です。 ここまでのPJの構成が下記になります。 ├── .github │   └── workflows │   └── main.yml ├── Dockerfile ├── go.mod ├── go.sum ├── main.go └── task-definition.json 実装をしてみましょう! GithubのMasterブランチへPushしたら、Github Actionsが起動されます。 その流れによって、ECSにデプロイ作業が開始されます。 作業が終わったら、AWS管理ページを確認し、EC2>ターゲットグループチェックします。 Public DNSや、利用してるDNSを利用してAPIが叩けるのか確認します! まとめ 今回このような構成でアプリケーションの開発を行ったのは初の試みです。 AWSの知識が浅かったのでかなり苦労した記憶が残ってますが、かなりいい構成かな…と思ってるのでこちらをベースにPJをどんどん拡張していきたいなと思います。 Firelensなどを利用したLog設定や、Github Actions、AWS SNSを利用したアラーム設定、Containerのリソースモニタリングなど、どんどん入れてみたいなと思ってます。 おそらくこの記事の情報だけではそんなに簡単にアプリを作るのは難しいかもしれません。 でもこの記事の内容を参考にしてもらい、皆さんのPJや、悩みへ少しでもHINTになったら嬉しいです。 本日案内したコードは ここ を参考してください。
こんにちは。LIFULL でネイティブアプリのスペシャリストをしている菊地です。 普段は LIFULL HOME'S アプリ(iOS, Android)の開発チームで Tech Lead をしています。 今回、Tech Lead としての活動が5年目となることから、LIFULL HOME’S アプリにおける Tech Lead の事例をご紹介いたします。 ご自身のキャリアパスの一つとして Tech Lead を検討されている方、Tech Lead という役割を導入したい方々の参考になれば幸いです。 はじめに Tech Lead とは Tech Lead はプロダクトに携わるエンジニアチームの技術リーダーになります。 Tech Lead は数年前から浸透してきた役割ですが、会社やチームによって Tech Lead に求められるものが異なるため、明確に「これが Tech Lead」 というものはないと言われています。 Tech Lead の役割 Tech Lead の主な役割(責任範囲)として挙げられるのは以下の3つの要素になります。 コードの品質 チームが作るソフトウェアの品質を守る チームの生産性 チームが設計・実装する上で障害となるものを事前に取り除く アーキテクチャ・設計 チームに技術的なビジョンを示して、そこに導く Tech Lead に求められるスタンス Tech Lead に求められるスタンスなどが書かれた有名な記事がありますので、こちらも参考にしてみてください。 medium.com LIFULL HOME'S アプリの Tech Lead とは LIFULLでは主力サービスである LIFULL HOME'S を iOSアプリ、Androidアプリとして提供しています。 iOS アプリと Android アプリそれぞれに対して専属の開発チームが存在しており、この二つのアプリを開発するチームをまとめてアプリ開発チームとして扱うことが多いです。 アプリ開発チームは以下の三つの職種から構成されています。 企画 デザイナー エンジニア この三つの職種から構成されている LIFULL HOME'S アプリ開発チームに対して 2016年10月に Tech Lead という役割を導入しました。 賃貸物件検索 ホームズ 部屋探し・お家探し・不動産・引越し LIFULL Co., Ltd ナビゲーション 無料 play.google.com Tech Lead を導入するまで Tech Lead を検討した理由 LIFULL HOME'S アプリ開発チームで、Tech Lead の導入を検討した理由はいくつかありますが大きな理由としては、 iOS アプリ開発チームと Android アプリ開発チームに分かれているために iOS アプリと Android アプリという OS を横断した動きというものがとりにくかった ということが挙げられます。 それぞれ普段の業務としては各OSのアプリの開発・運用となるため、各職種の活動範囲や責任範囲がどちらかのアプリに限定されてしまうことがあり、いくつかの課題が上がっていました。 技術的な窓口の分散 各OSアプリ開発チームのエンジニアにはリードエンジニアは存在していましたが、LIFULL HOME'S アプリとして全体をリードするエンジニアは存在していませんでした。 そのため、他部署や他職種(アプリ開発チーム内)からの問い合わせなどへ対応する際に各 OSのエンジニアがそれぞれ対応を行うため LIFULL HOME'S アプリとしての回答に時間がかかる 共通で検討されるべきことがどちらかのプラットフォームのみで話されてしまう といったことがありました。 サービスとしての体験の違い 各OSで開発を行う際に、影響範囲として考慮されるのが所属しているチームの担当 OS の範囲内になってしまうことがあり LIFULL HOME'S アプリとして違う体験になってしまっていることがある どちらかのアプリだけに存在している機能や仕様というものが増えてきた どちらかのプラットフォームのみを利用しているユーザーには影響はないかもしれませんが、両方のプラットフォームを利用しているユーザーやプラットフォームを乗り換えたユーザーにとっては、同じサービスなのに体験が違ってきてしまうという課題がありました。 エンジニアのスキルセット 当たり前のことですが、iOS アプリエンジニアは iOS アプリ、Android アプリエンジニアは Android アプリのスキルセットを持っています。 ですが、iOS アプリと Android アプリの両方の技術についてスキルセットとして持っているエンジニアはいませんでした。 そのため、iOS アプリと Android アプリ で共通の機能開発を行う際に どちらかの OS では実現できるが、どちらかの OS では実現できない スケジュールの都合で、どちらかの OS が先行して機能開発を行った際に、後続の OS の事が考慮されていない API 開発や仕様調整が行われる という課題がありました。 Tech Lead を導入した理由 アプリ開発チームとしてのリードエンジニアの必要性 これらの課題を抱えていたことにより、LIFULL HOME'S としてアプリ(iOS、Android)を成長させていく際に、アプリ(iOS、Android)共通の話や将来的な思想といった部分がまとめにくく、アプリ開発チーム全体としてのレベルをあげることが難しい状態でした。 そこで iOSアプリ開発チーム、Androidアプリ開発チームというOSを飛び越えて動ける存在として Tech Lead という役割を導入し、プロダクト全体としてチームのレベルをあげるために Tech Lead の導入を行いました。 Tech Lead を導入してから Tech Lead として求められたこと LIFULL HOME'S アプリの Tech Lead として求められたことは以下になります。 プロダクトチーム全体の生産性の最大化 技術的負債の管理 設計・開発の妨げになるものの排除 アプリとして共通のビジョンを提示すること OSを限定せず技術でプロダクトチームをリードすること エンジニア以外についても技術視点から意見を伝えて、起案段階から確度の高いものになるようにすること 新たな体験の提供や価値の創造 最新技術の調査・検証・プロトタイプ 最新技術をサービスに落とし込み、チームに展開する チームの技術に対する責任を持つ プロダクトが採用する技術や品質に対する責任 エンジニアの技術に対する責任 技術的な最後の砦になること いることでチームに安心感を与えること Tech Lead としてやってきたこと LIFULL HOME'S アプリの Tech Lead として求められてきたことを実現するために実際にやってきたこと、気をつけてきたことは以下になります。 最新技術の調査・設計・プロトタイピング WWDC、Google I/O などで発表される最新技術などを最新技術を使うだけではなく、アプリの機能や体験にどう落とし込むのか?を検討して、チーム(または各職種)に展開します。 時には技術資料をまとめて、時にはプロトタイプを作ったりして、職種毎にイメージしやすいように展開の仕方を変えるようにして、アプリ開発チームとしてスムーズにサービスに取り入れやすくするために様々な土台づくりをしています。 基本的には、LIFULL HOME'S アプリで提供している「かざして検索」のように事前にプロトタイピングを行った上でアプリに導入することになりますが、場合によっては LIFULL HOME'S アプリとは別アプリとして提供することもあります。別アプリとして提供した例としては Google が提供していた Tango プラットフォームでお部屋の模様替えを体験できるアプリのプロトタイピングを行い、アプリとしてリリースしたという事例もあります。 プロジェクトマネジメント Tech Lead の本来の仕事でないのですが、アプリの共通施策や大規模な機能開発、最新技術の開発といった場合にプロジェクトマネジメントも行うことがあります。 かざして検索のプロジェクトでは全体のプロジェクトマネジメントを行いながら、Android アプリ側での実装を行うといったことも行いました。 技術的負債の管理 iOS アプリ、Android アプリの双方で抱えている負債の把握を行い、将来的なアプリのあるべき姿に向けて施策などが行われる際に合わせて負債も返却できるような提案を行って、返済できるようにしています。 いま出来ることと(負債により)出来ないことを把握して、アプリ開発チームがやりたいことをやりたいときにスムーズに行えるようにするため、障害となり得るものの排除を行っています。 エンジニアの育成 Tech Lead は Enginnering Manager でないということもよく言われていることかと思います。 エンジニアのキャリア形成や人事考課、チームの方向性の評価やキャリアについては Tech Lead の範囲ではなく、技術的な観点についての育成についてについてが Tech Lead の範囲となっています。 具体的には、コードレビューについてとなりますが、普段のやりとりや流れてくる会話の中で技術的な観点でこう考えてほしいなどの助言をしたり、エンジニアとして、他職種と関わる際に意識することでチーム全体でより良い開発ができるような助言を行なっています アーキテクチャの設計 各OSアプリ毎に将来的に必要となるものを導入しやすい下地を作るため、アーキテクチャの選定や、影響の大きいライブラリの選定なども行っています。 Tech Lead が選定したものが導入されるというわけではなく、あくまで議論するための素案となります。最終的には各OSのアプリ開発チームと話し合った上で導入まで進めていきます。 これまで、iOS では Clean Architecture の導入、Android では Instant Apps 対応時の multi module 導入や、Dynamic Feature Module の導入なども行っており、チームとして話すための素案などをまとめてきました。 チームの生産性向上 Tech Lead はエンジニアチームの生産性の向上だけではなく、チーム全体の生産性の向上も求められています。 そのため、LIFULL HOME'S アプリ全体としての技術窓口として、企画やデザイナーと起案段階から技術的な観点で事前に相談を行なったり、ユーザーからの問い合わせなどについても把握しておいて相談がスムーズに行えるようにしたりなども行なってきました。 また、アプリ開発チームにおけるコミュニケーションのルーズボールを率先して拾って、適切なところに受け渡したり、対応を行なったりという一見地味なことも行なっています。 技術的な最後の砦になること Tech Lead の役割として、常に安定していてチームの最後の砦のような存在になるようにするということがよく言われています。 これは言葉としては単純ではあるんですが、とても大事なことなのに難しいなと感じています。 どんな状況、どういった問題に対しても、常に落ち着いて対応をする必要があるため、それまでの状況の把握だけでなく対応するために必要な知識や情報を常にアップデートし続ける必要がありました。 私の場合は、iOS と Android の両方について Tech Lead という役割を持っているため、カバーしなければならない範囲が iOS、Android だけではなく共通で使用する API(マイクロサービスや全社的なもの含む)やそれの裏側の設計、さらには技術的な話以外でそれぞれの業界の動向などとかなり広くなってしまいます。 そのため、Tech Lead として職種間やアプリ開発チームのやりとりをスムーズにするために、技術的な観点以外にも最新の UI / UX、業界の動向やビジネス領域についてもキャッチアップをおこない続ける必要がありました。 一見関係の無いようなものであっても新しい体験を生み出す時に組み合わせられることもあるため、そういったものについてもキャッチアップをするように心がけています。 現時点でもカバーしきれていない領域があるとは思いますが、出来る限り幅広い知見を持つことで、アプリ開発チーム内で起きる問題に対して安定して対応することが出来るように、Tech Lead に聞けばなんとかなると思ってもらえることで安心感を与えるために、常に知見を増やし続けるようにしています。 まとめ 今回、LIFULL の LIFULL HOME'S アプリにおける Tech Lead の役割についてご紹介させていただきました。 既に Tech Lead のようなことをやられている方もいるかと思いますが、Tech Lead という役割を導入することによって、その方のキャリアパスが明確になったり、チームにおける窓口が明確になることで得られるメリットなどもあるかと思います。 もしあなたのチームに Tech Lead という役割がまだないのであれば、Tech Lead の導入を検討されてみてはいかがでしょうか? LIFULLではメンバーを募集しております! カジュアル面談もありますのでご興味ある方は是非ご参加ください! https://hrmos.co/pages/lifull/jobs/010-9999 hrmos.co
こんにちは。エンジニアの加藤です。 普段はLIFULL HOME'Sの注文住宅領域にてエンジニアチームのマネジメントを担当しております。 LIFULL HOME'Sでは日々新機能の開発や機能改修を重ねておりますが、一方でレガシーコードや技術的負債も少なからず抱えており、開発速度低下や開発の幅を狭める一因となっております。 そのような状況の下、ここ数年、機能開発と同様に技術的負債の解消への取り組みにも注力し、注文住宅領域においてもシステム基盤刷新プロジェクトとして、開発効率向上やビジネス成長に耐えうる基盤づくりに取り組んでおります。 今回はそのシステム基盤刷新プロジェクトの一部であるAPIリプレイスを経て学んだ、当初の期待とそれに対するギャップ、そしてそれを踏まえた改善をお伝えしたいと思います。 システム基盤刷新プロジェクトの初期設計 システム基盤刷新プロジェクトでは最終的にはLIFULL HOME'S 注文住宅を構成するそれぞれのシステムのリプレイスを図るものとなっておりますが、領域毎フェーズを分けて実施をしております。 その中でまずは以下のように全体的なシステム設計を行った上、リプレイスに取り組みました。 LIFULL HOME'S 注文住宅には大きく分けて3つのシステムによって構成されております。 一つ目はエンドユーザ向けシステム(以降、web)、二つ目にハウスメーカーや工務店などクライアントへ向けた管理システム(以降、manager)、そして社内用管理システム(以降、client manger)となります。 マイクロサービス化を想定し、それぞれのシステムに対しフロントエンド層とそれに対応するBFF層を構成し、将来的に複数の機能別サービスに対応するAPIを呼び出す可能性を考慮した設計を行いました。 当初の設計時の期待としては以下となります。 期待 機能毎のサービスが増えた際、それらの呼び出しはBFF層で吸収しフロントエンド層をシンプルにする それぞれのAPIにて依存関係や関心の分離を図る BFFは各システムのビジネスロジックや業務ルールに関心を持つ BFFはデータの取得先やDBには関心を持たない db-apiは意味のあるまとまりでDBからデータを取得・更新する db-apiは各システムのビジネスロジックや業務ルールに関心を持たない db-apiの各エンドポイントは特定のシステムによらない設計とし、複数BFFから呼び出し可能とする 同様の処理を各システムにて重複実装することを防ぎ、開発の効率化を図る APIリプレイスを進めて見えてきたギャップ 上記設計に基づき、まずはdb-apiとclient manager-apiのリプレイスを実施致しました。 しかし、開発・運用フェーズを経ることで、徐々に期待に対しての以下のようなギャップが生じました。 ギャップ 機能毎独立したサービス(API)の必要性がない API開発のコストが増加 リリースの複雑性が生じた 一つ目のギャップはマイクロサービス化についてです。 設計当初、将来的には機能毎マイクロサービス化とし、APIを切り出すことを考慮してシステム設計を行っておりました。 しかし、注文住宅領域においてのサービス規模や少人数での開発体制を踏まえると、各機能別にチームを構成し開発を行う必要性が薄く、効率的でないという結論に至りました。 二つ目はdb-apiの再利用性や関心の分離についてです。 理想としてはdb-apiは各システムに依存せず、汎用的で複数BFFから利用可能なエンドポイントを構成するAPIとして期待をしておりました。 しかし、トランザクション等を考慮するとシステムの業務ルールに依存したエンドポイントとする必要性が生じ、汎用的なエンドポイント設計の困難さを感じました。 その結果、各システムに依存するエンドポイントが量産されることが予想され、開発コスト増大の懸念が高まりました。 また、取得処理がメインなweb、更新処理がメインなmanageの双方から利用されるAPIである性質上、ReadモデルとWriteモデルは別々で作成した方がメンテナンスしやすいというDDD的な思想のアンチパターンにも陥っており、開発する上で考慮する観点が増え、こちらも開発コストが増加する一因となっておりました。 三つ目は機能改修時のリリースの複雑性についてです。 機能改修の際は、フロントエンド層、BFF層、db-apiそれぞれに対し開発を行い、リリースの順序によりシステムの整合性を保つ必要がありました。 そのため、リリース時のシステム間での互換性の担保など考慮する部分が増え、結果として開発の複雑性の増大にも繋がりました。 ギャップを踏まえた改善 続くリプレイス対象のシステムとしてweb-apiが控えておりましたが、上記のギャップを踏まえ、当初のシステム設計を見直すこととしました。 その際、見直したポイントとしては以下となります。 見直したポイント 基本的に機能毎のAPI開発を考慮しない db-apiからのデータ取得をやめ、DBから直接データを取得 三層スキーマアーキテクチャにて、業務ルールをViewに集約 新たな設計では、db-apiの廃止や各BFF間でのエンドポイントの再利用性などを排除した代わりに、影響範囲を一つのシステムに限定する構成としました。 また、APIからのデータ取得部分においても一部変更を加えております。 DBに三層スキーマアーキテクチャを導入することで、単一データベースでの簡易的なCQRSを可能な設計としました。 その上で、webでの利用に特化して条件を絞ったViewを作成し、参照することでAPIにて発行するSQLをシンプルにする期待を見込んでおります。 改善点 新たな設計に基づきweb-apiを構築することにより、以下のようなメリットを享受することができました。 開発対象のAPIが一つとなり開発工数が削減 影響するシステムが限定されることで開発効率が向上 Viewにロジックを集約することでAPIのデータ取得ロジックが簡略化され開発コストが低下 まとめ 今回のAPIリプレイスを通じ、結果に対しての振り返りを行い、課題を受け入れ改善することの重要性を学びました。 一度決めたことを変えることはなかなかエネルギーが必要なことではありますが、状況に柔軟に対応しつつ、強い意志を持って変えること、そしてやり遂げることができるチームが強い組織であると考えております。 今回の学びを活かし、今後も技術的負債の解消に向け取り組んで参りたいと思います。
技術開発部の川合です。 この記事ではアプリケーション実行基盤開発を支える視点での取り組みについて紹介したいと思います。 キーワードはKubernetesクラスタE2Eテストと自動化です。 背景 提供機能の保証 Conformance E2Eテストの実行 カスタムE2Eテストの実行 クラスタ構築の不安を軽減する おわりに 背景 私が所属するチームではKubernetesを利用しアプリケーション実行基盤を開発しています。 エンドユーザへの安定したサービス提供はもちろんのことアプリケーション開発者が開発に集中するための様々な機能提供を行っています。 一例を紹介すると以下のような機能が提供されています。 アラート・メトリクスを可視化するGrafanaダッシュボードの自動構築 OPAを利用したmanifestがベストプラクティスに沿っているかどうかの検証 実行基盤への移行を容易にするテンプレートの開発 他にも多数の機能を提供していますがそれらをさらにシンプルに導入する取り組みとして、CRDとして定義された設定ファイルを書くだけでまとめて手に入るという仕組みを開発しています。 これが完成すればnamespaceの作成からデプロイ環境の構築、アラート・メトリクスなど多岐に渡る機能が統合的に導入できるようになります。 こちらについてはまた別の機会に紹介できればと思います。 チームでは「可観測性の向上」「アプリケーション移行支援」「コスト削減」といったテーマを決定し それぞれに求められる改善・機能開発を継続して提供することを目標としています。 こういった活動に対しスピード感を損なわず安定して価値を提供するための仕組みづくりが必要でした。 また、実行基盤(クラスタ)にはマルチテナンシーを採用しており多種多様なアプリケーションがデプロイされます。 多くの裁量を開発者へ移譲できるような思想で運用していますが、基盤自体を安定稼働させる責任としての調査・不具合等対応等が求められます。 テナントが増えることで観測範囲も増加し基盤への開発・運用はより複雑なものになっていく傾向がありました。 提供機能の保証 提供機能も増え、それを利用するアプリケーションも増え影響範囲も増加していきます。 さらにKubernetes本体や周辺エコシステムのアップデートはとても早く、さらなる価値提供のためにもいち早く適用していかなれければいけません。 そこで実行基盤開発の「守り」を強化することを目指しテストを実装しました。 Sonobuoyというツールを利用してクラスタのE2Eテストを実行しています。 github.com このテストによって機能が正しく動作することを保証し、 機能開発のデグレチェック・クラスタバージョンアップによる影響の確認が機械的に行えるようになります。 Conformance E2Eテストの実行 KubernetesにはCNCFが実施している認定プログラムがあり、 Certified Kubernetesとして認定されたアプリケーションはSonobuoyによる適合試験をクリアしていている必要があります。 ※認定済みのアプリケーションはこちらのリポジトリに登録されています。 github.com テスト実装についてはKubernetesリポジトリに格納されておりKubernetesのE2Eの実行方法や、ラベルの種類などはこちらから確認することができます。 github.com Sonobuoyはこれらのテストを便利に実行してくれるテストツールとして利用することが出来ます。 実行されるテストの一部を紹介すると以下のようにConformanceラベルがついたテストを実行していく様子が見られます。 Sonobuoy namespaceを始め独自のnamespaceが作成され実行ログやnodeの様子、テスト実行結果などはSonobuoyが提供するコマンドを通じて確認することが出来ます。 "[sig-network] DNS should provide DNS for pods for Subdomain [Conformance]" "[sig-api-machinery] AdmissionWebhook [Privileged:ClusterAdmin] listing mutating webhooks should work [Conformance]" "[sig-storage] Projected secret should be consumable from pods in volume with defaultMode set [LinuxOnly] [NodeConformance] [Conformance]" "[sig-storage] Projected downwardAPI should provide container's memory limit [NodeConformance] [Conformance]" "[sig-node] Downward API should provide pod UID as env vars [NodeConformance] [Conformance]" 認定済みのアプリケーションを利用しているわけですが組み合わせによる不具合やクラウド固有の設定による挙動変化などがあった場合にそれを検知したいという意図がありLIFULLでは構築後のクラスタでConformanceテストを実行するようにしています。 カスタムE2Eテストの実行 また、前述の通りLIFULLのアプリケーション実行基盤として多くの機能開発を行っています。それらが期待通りに動作するかについてConformanceテストとは別にE2Eテストを実装しています。 これらはKubernetes本家同様にテストフレームワークであるGinkgoとGomegaを使いSonobuoyのカスタムプラグインとして実行できるようにテストを実装しています。例として以下のようなテストを実装しています。 cluster-autoscalerを稼働させインスタンス増減が機能するかを確認する デプロイされたpodがすべてreadyとなっているかupstreamから検証する prometheusがscrapeするjobがすべて成功しているかどうか 実行基盤にデプロイされているアプリケーションにおいてSIGTERMの挙動が期待しているものとなっているかrolling restartさせてみる spinnakerのpipelineを設定したアプリケーションがすべてデプロイされているかを確認する fluentdによるログ収集が機能しているか カスタムプラグインについては公式のドキュメントが充実しています。作成手順は簡単です手順通りに実行すればすぐに導入可能と思います。 sonobuoy.io 前述した内容についてテストを実装してimageをbuildし、 Sonobuoy pluginのcommandが以下のスクリプトを参照するようにしてGinkgoを実行するようにしています。 #!/bin/bash set -x results_dir = " ${RESULTS_DIR :-/tmp/results } " mkdir -p $results_dir saveResults() { cd ${results_dir} tar czf results.tar.gz * printf ${results_dir} /results.tar.gz > ${results_dir} /done } trap saveResults EXIT ginkgo_args = () if [[ -n ${E2E_DRYRUN :- } ]] ; then ginkgo_args+ = ( " --dryRun=true " ) fi ginkgo_args+ = ( " -ginkgo.focus= ${E2E_FOCUS} " " -ginkgo.skip= ${E2E_SKIP} " " -ginkgo.noColor=true " " -ginkgo.v=false " " -ginkgo.reportFile= ${results_dir} /junit.xml " ) e2e_args = () e2e_args+ = ( " -kubeconfig= ${E2E_KUBECONFIG} " " -disable-log-dump=true " " -dump-logs-on-failure=false " ) extra_args = (${E2E_EXTRA_ARGS :- }) target = " ${E2E_TARGET :-apps } " cd /go/src/github.com/lifull/cluster/ test /e2e ginkgo $target -- " ${ginkgo_args[ @ ]} " " ${e2e_args[ @ ]} " " ${extra_args[ @ ]} " | ( tee ${results_dir} /e2e.log ) && ret = 0 || ret = $? saveResults exit ${ret} 実はこのタスクは私が今のチームにJOINした際のオンボーディングの一環として担当させてもらったタスクでした。 そういった経緯のためにクラスタ挙動の理解が浅く、運用開始当初は稼働中のクラスタでは動作するけど構築直後には動作しないといった不十分な実装になっていたりnodeの状態によって失敗するといった不安定さも多く残っていました。 例えばこのコードは安定したdeploymentにおいては稼働しますが、spotインスタンスに配置されたpodがspotのterminationによってEvictされたり、deschedulerによってPodが移動されている最中に実行されたりすると失敗してしまいます。 Context( "Deployment" , func () { var dList *appsv1.DeploymentList BeforeEach( func () { deployments, err := cs.AppsV1().Deployments( "" ).List(metav1.ListOptions{}) if err != nil { panic (err.Error()) } dList = deployments }) It( "should be fullfill specified number of replica" , func () { errList := make ([] error , 0 ) for _, d := range dList.Items { if deployment.IsFulfilledDesiredReplica(&d) != true { e := errors.New(fmt.Sprintf( "Failure deployment ns:%s name:%s check replicas (%d desired | %d updated | %d total | %d available | %d unavailable)" , d.GetNamespace(), d.GetName(), *(d.Spec.Replicas), d.Status.UpdatedReplicas, d.Status.Replicas, d.Status.AvailableReplicas, d.Status.UnavailableReplicas)) errList = append (errList, e) } } Expect(errList).To(BeEmpty()) }) }) そういったケースを考慮してpolling処理を行いながらdeploymentを満たすかどうかを判定するような処理に修正しています。 Kubernetesの挙動を知る意味でもテストを書くのは有益ですし、Kubernetes本体に実装されているテストを参考にしつつ挙動を理解していくのは学習方法としても良かったと感じています。 interval := 5 * time.Second duration := 3 * time.Minute err := wait.PollImmediate(interval, duration, func () ( bool , error ) { return deployment.IsFulfilledDesiredReplica(&d), nil }) if err == wait.ErrWaitTimeout { e := rrors.New(fmt.Sprintf( "Failure deployment ns:%s name:%s check replicas (%d desired | %d updated | %d total | %d available | %d unavailable)" , d.GetNamespace(), d.GetName(), *(d.Spec.Replicas), d.Status.UpdatedReplicas, d.Status.Replicas, d.Status.AvailableReplicas, d.Status.UnavailableReplicas)) errList = append (errList, e) } 実行したテスト結果はslackに通知し、失敗時のログが後から確認できるようにS3に保存するように設定してあります。 Sonobuoyで実行されたテストは実行前のnodeの状態や実行時のログを残すことが出来、どういう状態で失敗したかの詳細を確認することが出来ます。 こうしてテストコードで提供機能の正常稼働を保証しバージョンアップにおいてもそれらが正しく機能するかが判定できるテスト基盤が用意できました。 クラスタ構築の不安を軽減する LIFULLではいつ誰が実行しても期待するクラスタが手に入る状態を一つの目標としていました。 気軽に稼働中のクラスタのクローンが手に入り破壊的変更を試すことができるのは開発を支える視点でも優位なものとなります。 そのための材料として構築の自動化と構築後のクラスタにおいてE2EテストがGreenになることは重要でした。 現在、構築手順についてはすべてコードベースで定義されスクリプト実行に依存し手作業は一切排除されています。 さらにリソースを操作するための権限差異、OS依存の環境差異などにより構築が失敗する状態を避けるため AWS CloudFormationでAWS Systems Manager Automationを構築し固定されたマシンイメージ・権限に基づいてスクリプトが実行できる環境として整備してあります。 この構築環境を利用し「毎日、EC2インスタンスからクラスタ構築スクリプトと一緒にE2Eテストを実行する」ことにしています。 毎日構築を試すことにより構築スクリプトの健全性が定期的に保証される 構築されたクラスタはE2Eテストによって期待する動作を保証される この2つが担保されていることで構築スクリプトの風化やデグレの不安から開放されポジティブに開発に取り組むことが出来ます。 おわりに 以上、アプリケーション実行基盤開発を支える視点での取り組みについて紹介してきました。 目新しいものではありませんがこういった取り組みによりスピード感を損なわずチャレンジングな開発に取り組むことができます。 今後はテストの拡充を行うことでさらなる安心を得て、一切手作業が発生しないクラスタの完全自動切り替えなどにも挑戦して行きたいと思います。 E2Eテスト導入の参考になれば幸いです。 また、LIFULLではメンバーを募集しております! カジュアル面談もありますのでご興味ある方は是非ご参加ください! hrmos.co
どうも【 エンジニアの「市場価値」を向上する 】をキーワードに活動している @サム です。 今回はエンジニアの新しいスキル『 マーケティング × エンジニア 』について書いていきます。 昔と今のITエンジニア 一概にITエンジニアといっても、Webエンジニア、インフラ(サーバ)エンジニア、フロントエンドエンジニア等、様々です。 携わる環境や言語の違いから、必要なスキルも変わってくるため、より専門性の高さが求められていました。 しかし最近は専門性の高さよりも、汎用性の高い(メインスキル + サブスキルを持つ)エンジニアの需要が増えてきていると感じています。 例えば、ほんの数年前まではiDC(インターネットデータセンター)と契約したり自分たちで物理サーバを用意するのが当たり前でした。 用意したサーバにLAMP環境を構築したり、サーバやデータベースの保守運用も必要なので専用のエンジニアもいました。 けれども、AWSやGCPなどクラウド化が進み、インフラ面の構築がGUIベースで簡単に行えるだけでなく、保守面もCI/CDを活用する流れに変わりました。 そのため、 Webエンジニアがインフラ面も担当する 事が多くなったと思います。 そういった流れから、現状はITエンジニアは職種の掛け合わせ 「 ITエンジニア ✕ n 」が当たり前になってきています。 例えば... Webエンジニア ✕ インフラ iOS・Androidエンジニア ✕ バックエンド など... その流れはエンジニアスキルの掛け合わせだけでは収まりません。 プログラミング ✕ 数学などの統計学 = データサイエンティスト エンジニア ✕ 人事 = ジンジニア つまり、ITエンジニアは専門性を高めるだけでなく、オイラー図のように 様々な職種やスキルと掛け合わせる ことで、新しいサービスの開発や、既存サービスの成長に貢献できる人材が求められています。 サービスが使われる仕組みを作り出す「エンジニア」 サービスを世の中にリリースするにあたり重要なことな何でしょうか?それは「サービスが使われる」ことです。 どんなに良いサービスも使われなければ、それまでです。 そのため、サービスが使われる仕組みが必要になってきます。それをエンジニアスキルを使って実現するのが、今回のテーマである『 マーケティング × エンジニア 』になります。 なぜマーケティングなのか まず、マーケティングを4Pで表すと、各分野でエンジニアリングが関係する部分がありますが、LIFULL HOME'Sの事業でいうと最も大きいのはコミュニケーション(プロモーション)です。 コミュニケーションは新規客と既存客に分かれ、新規客はアドテク界隈の技術が代表的です。 そして、主に既存客へのコミュニケーションの自動化を行うもので、CRMの考え方を母体とするのが「 マーケティング・オートメーション・ツール (以下MAツール)」です。その名前のとおり、マーケティングを仕組み化するツールで、MAツールはGDPR等の第三者データの活用規制によってより一層注目度が高い分野と言えます。 MAツールを導入すればいいだけじゃないの? もちろん導入だけで見れは簡単です。しかし目的はMAツールの導入ではなく、使われるサービスの仕組み化なのです。MAツールはマーケティング全般をツールを使って実施していくため、ある程度のマーケティング知識が必要になってきます。そのため、 マーケティングの知識や経験が浅い場合は、MAツールを使いこなせないというリスク もあります。それは一般的なソフトウェアも同様ですね。 MAツールを使うのがエンジニアである必要があるの? もちろんツールを使うだけならエンジニアである必要はありません。 しかしオムニチャネル *1 を始め、ユーザーにアプローチする戦略のオートメーション化を考えると、MAツールとそれぞれのチャネル *2 との連携は欠かせません。 もちろんLINEやメールと連携できる機能を最初から提供しているMAツールも多く存在します。 しかし、既存のWebアプリケーションやすでに使われているサービスを全て1つにまとめるには、 必ずサービスとMAツールを繋げるアプリケーション開発が係わって きます。 こういった事から、マーケティングの知識を持ち、MAツールを使いこなし、サービスを横断して繋げるための開発ができるスキルを持ったエンジニアが必要になってくるのです。 マーケティング × エンジニアのスキルとは マーケティングとプログラミングの知識を持ったエンジニアのスキルはどのようなものでしょうか? そもそもマーケティングのスキル全般を収めてしまうと、それはマーケターの領域になります。 もちろんスキルはあればあるほど良いので、マーケター × エンジニアでも問題ありませんが、この場ではあくまでエンジニアとしてマーケティングに着目していきます。 上記でも述べましたが、マーケティングを一番有効・効率的に行えるのが「MAツール」です。基本的にはこのツールを使いこなすスキルは必須となり、MAツールと連携できるサービスの開発が主軸になると思います。 つまりスキルを分けると次のようになります。 マーケティングスキル ...MAツールの利用、連携(、MAツールを使うために必要なマーケティング知識) エンジニアスキル ...MAツールや既存サービスと連携させるためのAPI開発 もちろん、上記以外でもサービスを構築するためのインフラスキルや、各サービスが配信しているメールのHTMLコーディングやチャットボットなどのUI/UXなど、マイクロサービスを考慮すると必要なスキルは幅広くなってきます。 マーケティング × エンジニアの目指すべきところ マーケティング × エンジニアとは、サービスが使われる仕組みを作り出す「エンジニア」になります。 MAツールを中心として各サービスとの連携を開発し、エンドユーザに対して様々なチャネルを通じてサービスを配信していく機能を創り出すことです。 それはサービスにおけるLTV(LifeTime Value)やARPU( Average Revenue Per User)にも繋がる大切なものです。 今後のエンジニアは現状のテクニカルスキルの延長線ではなく、そのスキルで何を成し遂げたいかで学ぶスキルが変わってくる と思います。 その中でも、ビジネス寄りでサービスの成長を考えたマーケティングスキルを持ったエンジニアは、企業にとって無くてはならない存在になるはずです。 最後に これまで『 マーケティング × エンジニア 』について書きましたが、今後もこういったハイブリット型のエンジニアは増えてくると考えています。 フルスタックエンジニアがいるように、サービスを創るだけでなく、いずれはサービスそのものの成長にも新しい目線で貢献できるエンジニアになってほしいです。 告知 Ltech では、LIFULLエンジニアが中心となって皆様の技術欲を満たすよう実例を交えた勉強会を開催しています。 今後もLtechを積極的に開催していきますので、 ぜひ気になった方は、connpassでLIFULLのメンバー登録をよろしくお願いします! lifull.connpass.com *1 : オムニチャネルとは、店舗やアプリなどエンドユーザとあらゆる接点で最適な購入体験を提供すること *2 : チャネルとは、エンドユーザと接点を持つ店舗やアプリなどのサービスのこと
こんにちは、LIFULL HOME'Sの技術基盤部門に所属している戸野です。 LIFULL HOME'Sのエンジニア組織は大きく分けて2つに分かれています。 LIFULL HOME'Sに限らず、プロダクト・サービス全体の基盤システムを保守・改善する技術基盤部門と、ビジネスサイドと密接にコミュニケーションしながら、エンドユーザーが触れるプロダクトを日々開発する事業開発部門です。 下記のエントリでは、事業開発部門における技術的負債解消の取り組みが紹介されています。 www.lifull.blog 一方、技術基盤部門でも各PJでLIFULL HOME'Sの技術的負債解消に取り組んでいます。 そこで、今回は技術基盤部門での技術的負債解消の取り組みをご紹介したいと思います。大規模サービスになると、技術的負債との戦いは避けられないでしょう。私達の取り組みが少しでもお役に立てたら幸いです。 技術基盤部門の体制と取り組み 価値提供を加速させるために技術基盤部門では、技術的負債解消の文脈でバックエンド・フロントエンドともに既存アプリケーション改修を行っています フロントエンドの既存アプリケーション改修についてはこちらの記事で紹介されています。 www.lifull.blog 私がアサインされているPJでは、 バックエンドの既存アプリケーション改修 を、現在2名体制で取り組んでいます。 具体的にどのような技術的負債があったかと言いますと、 設計レベル 何重にもなっているクラスの継承関係 処理の流れがとても分かりづらい複雑な依存関係 実装レベル マジックメソッドがあらゆるところで使われているためデバッグしづらい 動的に呼ばれるクラス、メソッドが多くデバッグしづらい ビジネスロジックがテンプレートエンジンに記載されている などがあってそれぞれに対応していきました。 PJを進める上での制約 このような技術的負債に立ち向かっていくわけですが、技術基盤部門と事業開発部門が並行して開発を進めるため、当然制約がある中での開発でした。そして、具体的には次のような制約がありました。 制約💁‍♂️ 事業開発部門の人数の方が多く、並行開発による衝突のリスクが常にあるので、事業開発部門の変更をウォッチしコミュニケーションを適宜取り調整する必要があった。 事業開発部門の人数の方が多く、速度的に技術的負債の「増大スピード > 解消スピード」であるため、手当たり次第にリファクタリングを行うのではなく、効果の高い部分から対処する必要があった。 事業開発部門の開発を停止させることはできないため、バグが確実に出ないようにQCDのQ重視でテストを厚くし、後方互換性を維持したAPI設計の必要があった 技術的負債解消のために講じた具体的な取り組み これらの制約の中で私達は技術的負債の解消を進めていきました。そこで、設計実装レベル両方で技術的負債を解消するための取り組みを3つ紹介します。それぞれの取り組みの説明、メリットと大変だったことを具体的に説明しています。 全体を俯瞰して分析し本質的な問題点と解決策の洗い出し 技術基盤部門がLIFULL HOME'Sの技術的負債を解消することに取り組むメリットは、 事業開発部門が工数的にできないような、広い範囲での根本的なリファクタリングを行えることだと思います。 広い範囲での改修になるので、部分最適な設計実装にならないようにする必要があります。そこで、UMLを駆使してリバースエンジニアリングを行い、全体的にクラスの依存関係、継承関係、API呼び出しなどを調査し現状の本質的な問題と解決策を分析しました。 メリット🙆‍♂️ そもそもの設計レベルから見直すことができ、できるだけ理想に近い形の設計をすることができた 大変だったこと🤷‍♂️ 継承関係と依存関係が複雑すぎて現状のソースを理解、分析するのにかなり苦労した 古いAPIをやめて新しいAPIを呼び出すように改修 LIFULL HOME'Sでは複数のAPIが使われていて、中には古いAPIが使われている箇所があります。 古いAPIはアーキテクチャの技術的負債があったりして利用しづらいので、 できるだけ古いAPIの呼び出しをやめて、新しいAPIを呼び出す実装に修正していきました。 新しいAPIはテストコードが完備されているのとアーキテクチャの面で分かりやすいので、以前よりも保守性可読性が上がったと考えています。 メリット🙆‍♂️ (廃止予定の)古いAPIを廃止するのに1歩近づいた 大変だったこと🤷‍♂️ 古いAPIのアーキテクチャが複雑で、調査分析の工数がかなりかかった ビジネスロジックと表示ロジックの適切な分割 ビジネスロジックと表示ロジックがフロント側のシステムに混在していて、 フロント側のコードが肥大化し、かなりメンテナンスしづらい状況でした。 そこで、他PJが考えていたビジネスロジックと表示ロジックの切り分けのルールを参考にしながら、 ビジネスロジックをAPI側、表示ロジックをフロント側に実装するように切り分けていきました。 メリット🙆‍♂️ フロント側のコードのボリュームが小さくなり、以前よりも調査しやすくなった 各システムの責務が明確になり、以前よりも凝集度の高いメンテナンスしやすいコードになった 大変だったこと🤷‍♂️ 単位やprefixのような文字列をドメインとして扱うかどうかの判定で悩み、ビジネスロジックか表示ロジックかの切り分けが難しかった 最後に 技術基盤部門での技術的負債解消の取り組みを紹介させていただきました。技術的負債解消は、直接的にビジネスの価値提供をするわけではないので、どうしても優先順位を高くつけづらいところがあると思います。 しかし、技術的負債が積み重なるとビジネスの価値提供も鈍化するので、かなり重要なことだと考えています。 技術基盤部門という立場で、今後も技術的負債解消に立ち向かい、長期的に価値提供ができるシステムを構築していきたいです。
LIFULLのエンジニアの村田です。 私は2018年までLIFULL東京本社で勤務していましたが、現在は北海道の札幌拠点に勤務しています。 当時LIFULLには、全国に営業拠点はあったものの地方開発拠点はありませんでした。私が北海道に移住するタイミングで 札幌の営業拠点に開発拠点を整備することになり、LIFULLにとって初の地方開発拠点が誕生しました。 本エントリーでは、東京に長年勤務していたエンジニアが 地方開発拠点で働くようになって感じたメリット・デメリットおよび、 今後どのような働き方になっていくのか思うことをつらつらと書いていこうと思います。 内容が札幌に限定されている部分も多々ありますが コロナ禍でのリモートワークが普及する中、 地方に移住しようと考えているエンジニアの方に向けて参考になれば幸いです。 地方(札幌)拠点の良いところ 家賃が安い まず何と言ってもこれ。 都心に比べると、家賃が圧倒的に安いです。 同じ家賃で広い部屋に住める! 私の場合は、3人家族で2Kから3LDKに引っ越しましたが 東京にいた時よりもトータルで家賃が安くなったにも関わらず 広さは2倍近くになりました。 参考までにですが、私が住んでいる行政区の家賃相場と東京23区で 比較的家賃相場が低い足立区と比べてみても ▼ 札幌市豊平区の家賃相場 https://www.homes.co.jp/chintai/hokkaido/sapporo_toyohira-city/price/ ▼ 東京都足立区の家賃相場 https://www.homes.co.jp/chintai/tokyo/adachi-city/price/ およそ2倍の家賃の差があります。 リモートワークにおける仕事部屋の確保のしやすさという点では 非常に大きなメリットですね。 上記の例では札幌ですが、他の地方でも都心に比べれば 家賃が下がる可能性は高いはず。 通勤が楽になった 通勤のストレスがまったくないというわけではありませんが、 毎日満員電車に揺られていた頃に比べると、雲泥の差です。 札幌はコンパクトシティで、地下鉄の最寄駅から 中心地さっぽろ駅まで、通勤時間がだいたい10〜15分以内に収まります。 住む場所によっては、自転車で通勤することも十分可能です。 リモートワークであれば、そもそも通勤に関しては問題になりにくいかもしれませんが、 場合によっては出社する必要があるケースも出てくるので 通勤が快適であるに越したことはないですよね。 採用・勤務範囲が広がる 会社の勤務範囲が広がることは会社にとってもメリットになります。 今まではエンジニアの採用は東京勤務が必須であり、本社に通勤できる 範囲のエンジニアしか採用できませんでした。 札幌拠点ができたことで、札幌にいるエンジニアも採用の対象となり、 採用の幅が広がりました。また、北海道出身の社員もLIFULLにはたくさん働いていますので、 彼らが北海道に戻るようなケースがあった場合は 札幌拠点で働き続けるといった選択肢も用意できるようになりました。 その他メリット エンジニア視点ではありませんが、他にも ご飯が美味しい、子育てに優しい環境、夏それほど暑くない などなど、総じて言えることはQOLがかなり向上したと思います。 地方(札幌)拠点の気になるところ 勉強会の開催数が少ない。 東京にいた頃は、平日もぎっしり様々な種類の勉強会が行われていましたが 北海道の勉強会の開催数はかなり少ないです。 人口比率からして仕方ないことですが、エンジニアにとっては残念な点になるかと思います。 しかし、コロナ禍で勉強会がオンライン開催されることが多く 札幌にいながらでもこういった勉強会に参加できるようになってきたので この点のデメリットはなくなりつつあります。 情報システム部のサポートが手薄 情報システム部のメンバーが拠点に存在しない場合、 PCのキッティング等は本社の情報システム部門が行ないます。 万が一業務PCが故障した場合、本社から新しいPCが送られてくるまで 業務がまったくできない状態になってしまいます。 また、PCが不調の場合でも本社であれば、直接PCを持ち込むなどして情シスのサポートをすぐに受けられますが それが地方拠点だとできないため、このあたりのケースに備えて 予備のPCをあらかじめ用意しておく等、対策を考えておく必要があります。 冬が厳しい(北海道限定) 一年の半分は寒く、1月〜4月は雪が積もりっぱなしです。 北海道に来た年は、3-4回盛大に雪の上を滑りました。 エンジニアにとって、手は命なので雪で滑って骨折したなんてことにならないよう、 細心の注意を払う必要があります。 在宅勤務の場合は、吹雪にさらされる場面は少なくなるものの 自宅の暖房代がかさんでしまうのが辛いところです。 地方拠点で苦労したこと 私のミッションは、LIFULLの価値創造を加速させるようなエンジニア組織を 札幌拠点でも構築することでした。 本社にエンジニアが集中している中で、1人だけ物理的な場所が離れたオフィスにいる身としては 本社とどうやって連携を取っていけば良いか手探り状態でした。 その中でも特に困った事が、本社との会話におけるコミュニケーションです。 本社の会議に私だけリモートで参加するスタイルになるわけですが、 会議の音声がクリアに拾えるかどうかは会議室の設備に大きく依存します。 高価なマイク設備が設置された会議室であれば、ある程度広い範囲のメンバーの 声をクリアに聞き取れることができます。逆にマイク設備がなくメンバー一人のPCを 利用したミーティングでは、そうはいきません。 ミーティングを行う場所によっては、あらかじめ集音マイクを用意してもらうように 参加メンバーに依頼したりしていました。 設備を揃えたとしても、離れた位置にいるメンバー同士での会話はどうしても把握しづらいなど 体感値として、MTGの内容のうち6〜7割キャッチアップできれば上出来だったのではないでしょうか。 コミュニケーションに苦戦しつつも 私が強く意識していたことは 「この仕事を任せたいけど、メンバーが本社にいないなら無理かも」 といった、判断をされないように実績を積み上げていく事でした。 前例が無い状態で、成功事例を作ることは非常に重要です。 拠点のメンバーでも、本社のメンバーと問題なく連携して仕事を遂行できる ということを証明するため、基本はどのような仕事も引き受けるようにしました。 実績を積み上げることで、開発拠点の可能性を広げ 全国にどんどん開発拠点を展開していくような未来を描きたいと思っていたからです。 最終的には、全国にいるエンジニアが横断でチームを組んで PJを遂行するような働き方を実現したいと思っていました。 コロナになって変わったこと 開発拠点が設立されて1年が過ぎ、新しいメンバーが増え 実績を着実に積み上げている中、ある出来事で状況は一変しました。 そう、コロナです。 コロナによる緊急事態宣言下の中、 LIFULLでは比較的早くリモートワークが導入されました。 これがきっかけで、拠点と本社のコミュニケーションの質が劇的に改善されることになります。 札幌拠点の開発チームは2020年から、東京にいるチームと組んでPJを進めていたのですが コロナ禍以前では、彼らとのやりとりは基本はチャットベースが中心で 週1回の定例MTGで情報共有を行う程度でした。 MTGも回線や設備が悪い状態で音声が聞き取りにくいこともあり、非常にストレスでした。 現在は各メンバーがZoomで常時接続しながら仕事をしており、何かあればすぐにコミュニケーションをとれるようにしています。 画面共有によるペアプロや、オペレーションのダブルチェックといったことも苦労なくできるようになりました。 あれほど苦労していたMTGも、全員が同じ条件でWeb会議システムに参加する形式になったので 参加メンバーの声がききとりやすくなり、MTGの雰囲気も掴みやすくなりました。 拠点も本社もフラットに 今まではどんな工夫を施したとしても本社、拠点という区切りがありました。 物理的な距離が離れた拠点にいる、という事実は消すことができず 何かを判断するにもそこは意識せざるを得ません。 コロナになって、リモートワークが導入されたことで どこで働いているのか、ということはあまり問題ではなくなりました。 本社だからとか、拠点だからとか、地方だからとか、東京だからとか それらに差が生まれなくなり、働く場所における条件がフラットの状態になったのです。 拠点ならではの価値を出す事や、本社とうまく連携するためのノウハウを 溜め込んでいくといった必要性がもやは無くなりました。 こうなってくると、組織の形も変わってくるのではないでしょうか? 今までは同じ拠点の社員で組織を構成せざるを得なかったケースが多いようでしたが 拠点という概念を外して、組織を編成することが容易になりました。 少なくとも、エンジニアのグループをあえて拠点で区切る意味はもう無いように思えます。 日本全国に散らばった「働きたい場所で働くエンジニア」同士が チームを組んで、リモートでコミュニケーションをとりながら 仕事を進めていけるような、私が目指していた遠い未来にぐっと近づきました。 まとめ コロナによる新しい生活様式が確立され、リモートワークが広がりつつある今、 物理的な場所に縛られず働くことが可能になってきました。 地方拠点においても、リモートワーク浸透により 本社との連携が劇的に改善されるなど、今までのデメリットが改善され より生産性を上げて仕事ができるようになりました。 選択肢がぐっと広まったこの機会に、自身の働き方を見直してみても良いかもしれません。 何はともあれ、北海道はいいところです。
プロダクトエンジニアリング部の柴田です。 普段は LIFULL HOME'S の賃貸領域でフロントエンドの開発・設計を担当しております。 今回は LIFULL HOME'S での Web フロントエンドおけるレガシーブラウザ対応の負債解消への取り組みについてご紹介いたします。 背景 現在の LIFULL HOME'S の大枠は 2011 年ころに作られたものであり、当時の CSS3 サポート状況に差異のあるブラウザで表示を担保するには装飾的なパーツはほぼ画像で実装する必要がありました。 例えば、次に示すような枠の装飾はガイド線の外側を 8 つにスライスし、それぞれを background-image として割り当てるような実装を行います。 カードUIのスライス例 < div class = "ui-frame" > < div class = "content" > ... </ div > <!-- 左上〜右下の各フレームのスプライト設置用の空要素 --> < div class = "ui-frame__top-left" ></ div > < div class = "ui-frame__top" ></ div > < div class = "ui-frame__top-right" ></ div > < div class = "ui-frame__left" ></ div > < div class = "ui-frame__right" ></ div > < div class = "ui-frame__bottom-left" ></ div > < div class = "ui-frame__bottom" ></ div > < div class = "ui-frame__bottom-right" ></ div > </ div > 現在では css のみで実現できるデザインであっても当時はこのように手間がかかる実装だったわけですね。 LIFULL HOME'S での課題と対応 リフロー・リペイントの発生 情報をよりリッチに表現するため LIFULL HOME'S でもこの手法を広くサービス内の UI で適用してきました。 具体的には JavaScript で特定のセレクタを持つ要素に対し、各スプライトの背景を設定した空の DOM を挿入する処理を行うことで、実装時にクロスブラウザを意識することなく少ない工数でリッチな UI を実現できるようにしていたのです。 しかし、これには リフロー・リペイントを発生させ、対象要素の数が増えるごとに比例的にレンダリングコストを増やしてしまう という大きな問題がありました。 この問題は特に物件一覧ページ上が顕著で、それぞれの紹介物件ごとにこの実装を利用したパーツが使われていたため Lighthouse 上でも大きくパフォーマンススコアを下げる要因となっていました。 物件一覧ページの例。赤太枠で示した箇所でリフローを起こしており、このページ全体では約 30 箇所利用されていた。 対応 当時各ブラウザ間の CSS3 の実装足並みが揃っていなかった名残が現在の技術的負債となっていたため、これを JavaScript を利用せず、完全に CSS に置き換える対応をとりました。 私は主に賃貸領域のフロントエンド開発を担当しているのですが、このパーツは全マーケットで広く利用されていることもあり、特に以下に注意を払い対応をすすめました。 追加したプロパティ起因でレイアウト崩れが起きないこと 全マーケットを通してのデザイナー・企画職との合意 結果 この改修をリリースしたことで、ページの表示速度を大きく向上することができました。 Lighthouse で計測した具体的な効果としては Performance スコアを 5.8 ポイント向上 Speed Index において 2.6 秒 の改善 となっており、実際に体感できるレベルでの速度改善となりました。PC から LIFULL HOME'S を利用する際の快適さの向上にも貢献できたかと思います。 利用箇所が多すぎて刷新後のチェックを網羅しきれない課題がありましたが、こちらは全社的にエンジニア・デザイナーにご協力いただいたこともあってデザイン崩れなどが発生することもなくリリースすることができました。 まとめ 聖域をつくらないことが大事 今回実施した改修は技術的な観点ではさして難しくない改善であるといえます。 しかし、長年の運用によって肥大化した View の「どこで壊れるかわからない」という不安から表面的に見える課題であっても聖域化してしまう場合があることを学びました。 特に汎用的に利用される UI であればあるほど解決が難しくなってくると思います。 これについてはエンジニア・デザイナーが簡単にアクセスできるスタイルガイドの運用を開始し、解決と防止に取り組んでいます。 LIFULL HOME'Sのフロントエンド環境にSassが導入されたので色々やった話 技術負債 ≒ 改善の種 この 10 年でフロントエンド は大きく進歩しましたが、取り残されたレガシーコードが LIFULL HOME'S にはまだまだ存在します。 それらを辛さと捉えるのではなく、潰せば DX または UX を向上させられる鉱脈と捉えて着実に改善していけたらと思います。
プロダクトエンジニアリング部の中島です。 今回はフロントエンドのテンプレート部分についての負債やレガシーな機構に対する改善の取り組みについて紹介させていただきます。 背景 LIFULL社のメインサービスであるLIFULL HOME'SのメインリポジトリのサーバサイドはSymfony + Twig(※テンプレートエンジン)の構成を採用しています。 このリポジトリの歴史は古く、2011年頃から開発は行われており、今となってはレガシーな機構であったり、開発体験を損ねる負債的な記述も多くあります。 テンプレート部分で多くみられる問題のうちいくつかをピックアップすると弊社ではこのようなものが悩みのタネになっています 変数などを用いた動的な部分テンプレートの呼び出しによるgrepしやすさの低下 部分テンプレートをロードするときにスコープ制御(Twigだと only属性 )をつけ忘れてテンプレート間依存関係を不透明にしてしまう テンプレートの深すぎる継承関係 負債化してしまったTwig拡張関数の呼び出しが各所にちらばって残っている などなど 新規実装部分にだけに着目しても、レビューによるチェックでこれらを抑える努力はすれど負債コードを誤って参考にした開発は度々あり、レビューの漏れやレビュアー/実装者の経験不足等で徐々にこういった負債は増えていきます。 こういった現状を長年みていると、次第にこれらを「commit-hook等で自動検出する仕組みがほしいな」と感じるようなり、その検知のためにテンプレート(twigファイル)をパースしてTraverseしたいという思いが芽生えてきました。 もしestoolsのようにプログラムを抽象構文木(AST)に変換するParserと、それを探索するTraverser、そのASTからコードを生成するCodegenの 三種の神器 があれば、強引な正規表現に頼ることなく問題コードを検出したり、レガシーな機構をモダンな機構に変換したりすることも可能になります。 github.com こういった思いからLIFULL HOME'SでもTwigのParser/Traverser/Codegenを作成することにしました。 Parserを作成するためのステップ 私の知る限り、界隈では一般?的にこのようなステップをたどる感じになると思います。 字句解析を行う(Lexer) 字句をToken単位にまとめる(Tokenizer) TokenをNode単位にまとめる(Parser) 実コードレベルでそれぞれのステップを見てみるとこんな感じになります 元コード < div class = "sample" > {% spaceless %} < div > {{ a|some_filter(b) }} </ div > {% endspaceless %} </ div > Twigでは {% ~ %} で構文を、 {{ xxx }} で値のプリンティングを表現します。 LexicalAnalyze (字句解析してTokenごとに分解) TokenStream { tokens: [ Token { type: 0, value: '<div class="sample">\n ' }, Token { type: 1, value: '' }, Token { type: 5, value: 'spaceless' }, Token { type: 3, value: '' }, Token { type: 0, value: ' <div>\n ' }, Token { type: 2, value: '' }, Token { type: 5, value: 'a' }, Token { type: 9, value: '|' }, Token { type: 5, value: 'some_filter' }, Token { type: 9, value: '(' }, Token { type: 5, value: 'b' }, Token { type: 9, value: ')' }, Token { type: 4, value: '' }, Token { type: 0, value: '\n </div>\n ' }, Token { type: 1, value: '' }, Token { type: 5, value: 'endspaceless' }, Token { type: 3, value: '' }, Token { type: 0, value: '</div>\n' }, Token { type: -1, value: '' } ], current: 0, filename: '/path/to/twig-tools/sample/twig/017.html.twig' } Parse (TokenStreamからNodeTreeへ) Node { nodes: [ TextNode { value: '<div class="sample">\n ' }, SpacelessNode { body: Node { nodes: [ TextNode { value: ' <div>\n ' }, PrintNode { expr: FilterExpression { node: NameExpression { name: 'a' }, filter: ConstantExpression { value: 'some_filter' }, args: Node { nodes: [ NameExpression { name: 'b' } ] } } }, TextNode { value: '\n </div>\n ' } ] } }, TextNode { value: '</div>\n' } ] } ToAST (NodeTreeをASTとしてJSONフォーマットに変換する) { type: 'Node' , nodes: [ { type: 'TextNode' , value: '<div class="sample"> \n ' } , { type: 'SpacelessNode' , body: { type: 'Node' , nodes: [ { type: 'TextNode' , value: ' <div> \n ' } , { type: 'PrintNode' , expr: { type: 'FilterExpression' , node: { type: 'NameExpression' , name: 'a' } , filter: { type: 'ConstantExpression' , value: 'some_filter' } , args: { type: 'Node' , nodes: [ { type: 'NameExpression' , name: 'b' } ] } } } , { type: 'TextNode' , value: ' \n </div> \n ' } ] } } , { type: 'TextNode' , value: '</div> \n ' } ] } 実装について 上述のサンプルデータでなんとなくそれぞれの役割的なものが見えてきたかと思います。 全てのフェーズをゴリゴリと自前で実装するのは仕様把握などの面で非常に大変ですが、そもそもTwig自体の中にLexer/Tokenizer/Parserの現行実装があるわけで、それを参考にしながら書いて、それに加えて、最終的なNodeをASTとしてJSONフォーマットで吐き出させる機構・そのASTを再帰的に探索するコード、ASTからのコード生成部分だけを作ればいいだけなので実はそこまでチャレンジングな取り組みというわけでもないのです。 TwigのLexerをみてみましょう。 Lexer.php while ($this- > cursor < $this-> end) { ... switch ($this- > state) { case self::STATE_DATA: $this- > lexData(); break; case self::STATE_BLOCK: $this- > lexBlock(); break; case self::STATE_VAR: $this- > lexVar(); break; case self::STATE_STRING: $this- > lexString(); break; ... } } テンプレート文字列を先頭から順次スキャンしていって 構文部分のキーワード の出現をみながらモードを切り替え、それぞれをTokenとして分割していきます。 そうしてできたTokenの集まりをToken Streamというオブジェクト表現にして Parser でNode単位を作っていくわけです。 このNodeのSyntaxはNodeの種類によって変わるので、すべてのNodeのParserを作りきらないといけないわけなのでやや骨がおれますが、これも参考コードとしてTwigの既存のパーサがあるので明けない夜はないという感じで進められるわけです Node一覧(github/twig) また、Twig拡張でNodeを自前で増やしてる方々はそこのParserもかくことになります。 弊社の場合はSymfonyがTwigの拡張をいくつか(3~4個 )作っていたのと、自前で拡張を1~2個かいてたのでその分も書くことにしました。 かかった工数はTraverser{traverse|replace}/Codegen/Spec含めてだいたい半月程度です。 NodeとSyntaxバリエーションの一部 コード自体はまだ公開してはいないのですがこんな感じで動きます。 (TwigのバージョンによってLexer部分が変わるので公開してもあまりoss的に意味ないかなというのが本音です) const { parser, codegen, traverser } = require( '@lifull/twig-tools' ); let template = ` <div class = "sample" > { % spaceless % } <div> { % include '/path/to/partial.html.twig' % }{ # onlyをつけ忘れている # } </div> { % endspaceless % } </div> `; let ast = parser.parse(template); traverser.traverse(ast, { enter(node, parent ) { if (node.type === 'IncludeNode' && !node.only) { console.warn( 'onlyをつけ忘れのinclude nodeがありました。規約違反です!' ); console.warn( '>' , codegen.generate(node)); } } } ) 実行 % node sample.js onlyをつけ忘れのinclude nodeがありました。規約違反です! > { % include " /path/to/partial.html.twig " % } 出力を見るとうまく検知できていることがわかります。 Traverser等のインタフェースはestoolsをとくに参考にして書きました。 おわりに 上述のコードからもテンプレートをAST化してTraverseできるようにすることにより、問題コードの検知や変換等の処理をたった数行でかけるようになったことがわかります。 現在はこの三種の神器(Parser/Traverser/Codegen)を用いて問題コードの検知や、古い機構の一括変換を遂行してレガシーなテンプレートと戦略的に向き合う努力を進めています。 人力で修正する前提では何年たっても実現できないような大規模なリファクタリングであっても、仕組みを整えることで実現に近づけることもあると思います。 これからも、緩やかではありますが、着実な改善を続けていきたいです。
プロダクトエンジニアリング部のえびさわです。 この一年でLIFULL HOME’Sのメイン開発サーバーのフロントエンド環境が大きく刷新されました。 以前は使えなかったSassも導入され、モリモリとDXが向上しております😭 今回はマークアップにフォーカスし、刷新前に感じていた課題とSass導入後に行なった改善策をご紹介します。 課題 カラーコードのtypo, 種類が多い LIFULL HOME’S にはデザインガイドラインが存在し、使う色はある程度パターン化されています。 代表的なもので言うと弊社のコーポレートカラーでもある #ed6103 です。 LIFULLのコーポレートカラー #ed6103 CSSしか使えない場合、カラーコードは手打ちないしコピペするしかありません。 しかし、以下のような微妙な違いの場合はデザイナー・フロントエンドエンジニアともにレビューで見落とす可能性があります。 違いがわからない デザイン的にあえてずらしているのかtypoなのか、あとから開発している人間にはわかりません。 「すでに実装されているものだから正だな!コピペ実装したろ!」 とかやっちゃうともう地獄です。秩序が崩壊します。 また、似たようなカラーコードも多いです。 例えば以下の図はすべて同じページで使われているグレーです。 グレー激戦区 特に #fafafa ~ #f5f5f5 は激戦区かつモニタ輝度によっては真っ白にしか見えないので誤実装が発生したり、 雰囲気で色を当ててレビューまで持っていってしまうことがあります(大抵デザイナーさんにしょっぴかれます) 同じスタイルのcssが大量にある、コピペが大変 LIFULL HOME’Sはモジュールという単位で各コンポーネントを管理しており、 それぞれ HTML + CSS (+ JS) が1セットになっています。 LIFULL HOME'S 賃貸の物件ページで使われているモジュール こちらの図は実際に使われているモジュールです。 少しわかりにくいですが、 「物件の費用目安を見る」「お気に入りに追加」「シェア(LINE/メール)」 の3モジュールに分割されています。 これらはボタンの見た目は同じですがHTML/CSSは完全に別となっており、いずれかのCSSを変更してもスタイルが同期することはありません。 そのため影響範囲を最小限に止めることができ、大規模開発においては大きなメリットとなっています。 一方で似たようなデザインでコンポーネントを作成する際はCSSも丸ごとコピペする必要があります。 その際に微妙な差異を生み出してレビューを通過してしまったり、UIリニューアルの際に大量のファイルを更新しないといけないなどのデメリットもありました。 「ここで実装されているやつと同じで」がしづらい 前述の通りLIFULL HOME'Sでは殆どの場合再利用性のあるコンポーネントは作られず、独立したモジュールになっています。 似ているけれど微妙に見た目が違うモジュールが存在していたり、インタラクションが異なったりしています。 歴史の長さ・開発者の多さ故にすべてを制御しきれておれず、どれが正なのかがわかりません。 そのため、「ここで実装されているやつと同じで」と言われても他のモジュールの方が正しいのでは…?と疑心暗鬼になりがちです。 改善策 共通変数・mixinの導入 まず「カラーコードのtypo, 種類が多い」に対して共通変数を用意しました。 変数を使うことによってtypo防止・色のリスト化が行えるため、野良色の発生を抑止できます。 命名は ${$prefixColor}-{$uniqueName} としています。 カラーサークル prefixは上記のカラーサークルに近いものを選び、uniqueNameはHexColorを参考につけています。 たとえば #d8d8d8 であれば $mono-gainsboro といった具合です。 名前がユニークすぎて色が想像できない、というのがデメリットですが $red-default , $red-lighter などの命名は中間色が来た場合が辛いですし 将来的にどんな色が追加されるかもわからないので、命名被りが起こらないことを重視しています。 また、UIでよく出るElements( background , border , box-shadow )は専用のcolor aliasを用意し、実装時にブレが出ないようにしています。 map-gettersの利用も検討しましたがタイピング量が増える割には恩恵が少なかったので、 $bg-xxx など専用prefixをつける形で落ち着きました。 また、「同じスタイルのcssがたくさんある、コピペが多い」についてはmixinを作成しました。 LIFULL HOME'S 賃貸の物件ページで使われているモジュール この図のボタンについては以下のmixinで解決できます。 // mixin @mixin white { border: 1px solid #d8d8d8; border-radius: 4px; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3); font-weight: bold; } // 使用時 @use 'path/mixins/buttons' as buttons; .mod-meyasu { &__button { @include buttons.white; text-align: center; padding: 12px; } } 余白はあえて設定していません。 というのもボタン内のレイアウトパターンは無数のようにあり、強い制約も設けられていません。 使う場面によって全く異なるルールのため、mixinではスタイリングのみ提供するという形をとっています。 (引数で設定も検討しましたが、指定し始めるとキリがないのでやめました) 当初はごく一部の変数とmixinのみ提供していましたが、数ヶ月に一度のペースでアップデートを行い対応範囲を広げています。 スタイルガイドの導入 共通変数・mixinの可視化 スタイルガイド(変数一覧) スタイルガイド(mixin一覧) 共通変数・mixinを導入しても、使ったらどんな見た目になるかは自分で当ててみないとわかりません。 そこで、開発サーバー上にスタイルガイドを作成して共通変数・mixinをビジュアライズしました。 Usageに従ってコピペすればすぐに使えるため、実装時のブレも抑制することが可能です。 スタイルガイドに載せているものは実際に運用されているコンポーネントがベースになっていますので、どのスタイルが正なのかという指針にもなります。 そのため、「ここで実装されているやつと同じで」がやりやすくなります。 アクセシビリティに関する実装ガイドライン スタイルガイド(フォームアクセシビリティについて) スタイルガイド(aria-*について) また、Sass導入の文脈とは異なるのですが実装時の諸注意としてアクセシビリティに関する内容も記載しました。 LIFULL HOME'Sではアクセシビリティに関するコーディングガイドラインが存在せず、雰囲気で実装しているところがありました。 長年可動しているが故に最新の対応が出来ているところも少なく、以前の実装をそのまま流用してアクセシビリティが壊滅的……なんてこともあります。 そこで今回スタイルガイドにガイドラインを表記し、実装例を併せることで正しいアクセシビリティ対応をできるようにしました。 導入してみて 実装が爆速になった 全世界で500億回は言われていることだと思いますが、本当に実装が速くなりました。 今まで手作業で数行〜数十行書いていたものが1,2行で書けるようになったため、 ちょっとしたモジュールであれば一瞬で組めるようになりました。 変数・mixinが頭に叩き込まれているか否かで実装速度は前後しますが、 スタイルガイドの存在によって差は埋めやすくなっているとは思います。 レビューも楽になった これも5000億回は言われていると思いますが、 レビュー時にスタイリングが正しいか否かに心血を注ぐ必要がなくなり、 他の事項に集中できるようになりました。 デザイナーさんとの意思疎通がしやすくなった LIFULL HOME'Sは施策の規模によってはデザイナーさん無しで開発を進め、 フロントエンドエンジニアが組んだものをデザイナーさんにレビューしてもらいリリースする、ということがあります。 そういった場合に「この色とこのパーツで」とざっくり相談ができるので、意思疎通が楽になりました。 おわりに Sass導入前に苦しんでいた問題が緩和され、大変快適なマークアップライフを送れるようになりました☺️ 10年以上運用されている大規模サイトのためどこまでSassで対応していくかが大きな悩みでしたが、 周りのデザイナーさんやエンジニアさんに助けられ対応範囲を広げていくことができました。 この場を借りてお礼申し上げます🙇‍♂️🙇‍♂️ありがとうございました 引き続きアップデートを行い、より良いマークアップ体験を提供できるよう努めて参りたいと思います!
 こんにちは、LIFULLでData Analystとして働いている竹澤です。社外では、Mediumの Towards Data Science でContributorとして寄稿したりしています。  2020年私は主にデジタルマーケティング領域で効果検証の自働化や異常検知ロジックの開発、DXプロジェクトの立ち上げに携わってきました。  今回はデータアナリティクス/データサイエンス・プロジェクトにおけるマネジメント論の整理を試みます。あくまで私見の塊(いわゆるオレオレ)ですが。 想定読者としては、Data Analyst、Data Scientist、DXプロジェクトのPMなどデータサイエンス及び分析プロジェクトに関わる全ての人を考えています。 はじめに  突然ですが、あなたは「ビジネスにおけるデータサイエンスの本質的価値とは何か?」という問いに対してどう回答するでしょうか。  私は個人的に、以下の言葉でその価値を定義しています。 ”データサイエンスの価値は「プログラムに対する意思決定の権限委譲」である”  なぜ、意思決定を権限委譲できることをビジネスの価値と定義できるか。 その理由は、「ビジネスは、Decision Making(意思決定)とExecution(実行または製作)の2つで支配されている」という論拠がまず根底にあります。  そして抽象度を極限まで上げた場合、頭を動かして「考える」時間と手を動かして「実行」したり「作る」時間に分けられるということです。  その上で、人間にとって難易度が高いあるいは時間を要する意思決定が存在したとして、それらをアウトソースできることは十分価値があると考えています。 具体例として、製造業の検品においてディープラーニングを用いた異常検知(画像認識)はまさに人間の目視による判断をより高精度でリプレイスするケースなど。  あるいは似た言葉として、 トヨタ生産方式(TPS) で提唱されている「自働化(ニンベンのついた自動化)」のニュアンスに近い概念だと考えたりします。  以上が情報技術の専門家ではない私が、純粋に問題解決の1ソリューションとしてデータサイエンスという大きな仮説に賭けてきた背景です。 なぜ書くか  前述した通り、私はデータサイエンスの問題解決能力を信じていますが一方で現実世界では、それを否定するようなファクトが目に付きます。  つまり、現状の課題として自社を含めた日本のビジネス現場におけるデータサイエンスを用いた問題解決が思うように進んでいないと考察しています。  今回はその原因の1つしてデータサイエンスプロジェクトにおける「マネジメント手法の未整備」にあると仮定し、その解決策を検討します。  その上でまずは、データ分析プロジェクトという大きな括りの中におけるデータサイエンスプロジェクトの位置付け、各プロジェクトの進み方を確認し、プロジェクトがクローズするような潜在的リスクを検討していきます。  その他、ITプロジェクトにおけるPM論は既にある程度体系化されていますが、データサイエンスプロジェクトはそうではない現状に対する問題意識があります。  また最近は特に、データサイエンスプロジェクトの遂行過程に潜む不確実性を対処するには、モデリングの能力以外のリソースの必要性を実感しています。  以上から、過去の業務経験を抽象化することで未来のプロジェクトに応用可能な、汎用的フレームワークへと昇華することを検討してきました。  まだ全く完成系ではないですが、未来への足跡として今回は現時点での限界(一次結論)を残します。 分析プロジェクトの種類と目的の違いについて  ここでは、プロジェクト・マネージャー(以下PM)の視点から個人的な分析プロジェクトの分類の提案とマネジメント・スコープの明確化に取り組みます。  まずは「分析プロジェクトの分類」について見ていきます。  結論から言うと、私は「データアナリティクスプロジェクト」と「データサイエンスプロジェクト」の2つにデータ分析による問題解決を区別しています。 そもそもなぜ分類が必要かというと、問題解決の種類によってプロジェクトの全体像や体制、必要なリソースが大きく変わるという結論に経験上至ったからです。  以降では、両者の違いをより鮮明にしていきます。 データアナリティクスプロジェクトとは  前述した通り、私はデータサイエンスの価値を、「プログラムに対する意思決定の権限委譲」と定義しています。  よって、シンプルに上の定義域に収まると認識している問題解決はデータサイエンスプロジェクト。それ以外をデータアナリティクスプロジェクトとしています。  データアナリティクスプロジェクトの特徴は、データ分析を使ったアドホック的(一時的)な問題解決であることです。  具体例としては、統計的仮説検定によるABテストの効果検証やKPIダッシュボードの作成などが該当します。ここでは「意思決定の支援」までが責務となります。  詳細は こちらの記事 に譲りますが、「意思決定の支援」においては組織の意思決定に求められる品質には「正確性・速さ・納得感」の3つがあります。  なので、その3つのうちどれかを引き上げることが目的(価値)となります。 データサイエンスプロジェクトとは  データサイエンスプロジェクトの特徴は、継続的かつ定期的に訪れる意思決定をプログラムが肩代わりするような問題解決であることです。  具体例としては、コンテンツのレコメンドシステム開発やサーバーメトリクスに対する異常検知、画像認識技術によるモノの判別自動化などが該当します。  つまり、「誰にどのコンテンツをどの順番で見せるか?」というレコメンドTaskのように、定型的な意思決定の機会が何度もイテレートするTaskがサービスのバリューチェーン上に存在するとき、それらを代行するシステムの開発が目的です。  そして、データサイエンスプロジェクトの責務が「意思決定の代行」であることはプロジェクトの難易度の高さに直結します。  まずは、品質(Quality)の問題。「同じ時間で人間(既存機能)よりも高い精度」あるいは、「より短い時間で人間(既存機能)と同水準の精度」のどちらかを満たすタスク遂行能力がアウトプットの品質としてほぼ常に要求されます。  また、機械学習ライフサイクルのスケール化を目的とする MLOps の領域は、業界全体でベストプラクティスを探っている途中にあります。  以降ではデータアナリティクス/サイエンスプロジェクトそれぞれのプロジェクト・マネジメントにおけるMethodologyを整理していきます。 データアナリティクスプロジェクトにおけるPM論  まずは、私のようなData Analystといった職種の主な責務であろう、データアナリティクスプロジェクトにおけるマネジメントについて見ていきます。  私はLIFULLに入社してから約1年半、CausalImapctを使ったTVCMの効果測定やABテストに対する有意差検定、事業KPIをモニタリングするためのDashboard作成など多様なアナリティクスプロジェクトに伴走してきました。  本質的にはその役割を、「今回のTVCMはうまくいったのか?」や「このUIの変更はサイトKPIにポジティブだったか?」などの問いに定量的(統計的)なアングルから新たな知識を創造する、「意思決定の支援」業務と捉えています。  そのため、上記は全てアドホック的(一時的)に発生する問いに対しての問題解決なることが多く、消費期限(使用期間)が長くても半年ほどです。  以降では、データアナリティクスプロジェクトを以下3つの観点から整理します。 プロジェクトのプロセス(進み方) プロジェクト遂行に必要なリソース(スキル)と主な担当職種 プロジェクトがクローズ・振り出しに戻るリスク  なお、後述するデータサイエンスプロジェクトでも同じ整理をします。 プロジェクトのプロセス(進み方) ヒアリング : ステークホルダーにInterviewを行い、要求や解決すべき真の問題と制約条件(非機能要件)を見極めるために情報収集をする 要件定義 : 今回の問題解決の全体像を整理するため、背景・目的・課題・原因・解決策などをドキュメント化。ステークホルダーや納期もここに記載 データ抽出 : 検証や分析に必要となるデータを、DBやDWHからSQLを使って抽出。検証内容によっては、社外のデータを使用する 分析集計 : LIFULLでは、分析イシューを現状調査・課題発見・効果検証・予測判別の4つに分類し、適切な解決策(統計手法)を選定 結果説明 : TableauなどのBIによる結果の可視化と、その解釈を記載。ビジネスサイドに伝わる言葉でのStory Tellingが求められる プロジェクト遂行に必要なリソースと主な担当職種 プロジェクト管理機能 : Project Manager(Data Analyst) データ分析機能 : Data Analyst(Data Scientist) 分析レビュー機能 : Data Analyst(Data Scientist)  ただし、A/Bテストの効果検証などの比較的小規模な案件の場合、データ分析者が要件定義などの上流設計から分析まで一気通貫で行うことを想定しています。  実際にLIFULLでも、上記のアナリティクスプロジェクトと分類される案件のうち8割は、1人の人間がレビュー以外の全プロセスに実行責任を負っています。 プロジェクトがクローズ・振り出しに戻るリスク 問題設計のリスク : ステークホルダーが真に行いたい意思決定が曖昧なまま分析を進めると、付加価値のない分析に人件費をかけることになる 説明可能性のリスク : 分析結果をステークホルダーが咀嚼しきれない問題を解消するために、解釈性に優れた解決策(統計手法)の選定が求められる 役割分担のリスク : 分析の品質(正確性)を担保するために適切なレビュワーの配置や、検証内容が複数ある場合の最適な役割分担を設計する必要がある 計画進捗のリスク : 書くまでもないが納期を守るために、同時並行で進む他のプロジェクトも管理する必要がある。自分の力量にあった納期の設定が大切  上記では、あえてジェネラルな 問題解決 における落とし穴を列挙しました。  その意図は、個人的に汎用的な問題解決能力こそが分析のValueを最大化すると考えているからです。(もちろん統計などのある程度の理解は当然だとして)  また、予算が大きいTVCMの効果検証などの事業上重要かつ手持ちの統計手法で解決可能な問題を見極めるスキル、つまり イシューから始める 力も重要です。  以上で、データアナリティクスプロジェクトの進み方や、潜在的なリスクへの対処に必要となるスキルについての考察を終わります。 余談:統計的因果推論の価値について  LIFULLのData Analystたちは、特に近年注目度の高い(統計的)因果推論の領域にフォーカスし、様々なチャレンジをしてきました。  その背景には、「派手な予測モデルより、因果関係の実証の方がビジネスの現場で必要とされる頻度が高いだろう」という仮説(スタンス)があったからです。  例えばA/Bテストにおいて、p値を用いる必要がある頻度主義の仮説検定ではなく、ベイズ主義に基づく仮説検定を導入しました。  また、前後比較に対する回帰分析を用いた差分の差分法(DID)。時系列データに対するグレンジャー因果検定や CausalImpact による効果測定を行いました。  このように、Webサービス開発や事業運営で遭遇する効果検証に対して、多様な統計手法を適応することで組織への価値提供に努めてきました。 データサイエンスプロジェクトにおけるPM論  次に、Data ScientistやML Engineerといった職種の主な責務であろう、データサイエンスプロジェクトにおけるマネジメントについて見ていきます。  ここではいわゆる「機械学習プロジェクト」を想定して、整理していきます。  ただ繰り返しになりますが、私の中のデータサイエンスプロジェクトの本質は「プログラムに対する意思決定の権限委譲」にあると考えています。  なのでアルゴリズムの中身がML/DLかは必要条件であり、人間の意思決定のリプレイス(単なる「作業」の自動化ではない)に価値があることを強調します。  また筆者である私は、現時点で機械学習プロジェクトを完遂した経験(プロダクション環境におけるデプロイ・運用保守)はまだありません。  なので今回は、自社における未来のデータサイエンスプロジェクトに備えることを目的としてPMが把握すべきリスクをまとめます。  特に私が実務経験のない効果検証以降のプロセスは、 「仕事ではじめる機械学習」 の著者である有賀さんの MLOps の歩き方 などを参考にさせて頂きました。 スコープ:書かないこと  今回はあくまで、プロジェクト・マネージャー(PM)視点でのプロセスの抽象化やリスクの列挙になります。  なのでData Scientist視点でのモデリング過程の詳細や、Software Engineer視点でのエンジニアリングにまつわる技術的な諸問題は取り扱いません。  また、自社でWebサービス(アプリケーション)を持つ事業会社における、インハウス開発を想定しています。なので、QCDにおけるCDへの配慮等は手薄です。 プロジェクトのプロセス(進み方) 要件定義 : まず機械学習をしない方法も検討した上で、 Problem/Solution Fit (PSF)の理論的な妥当性を確認。またプロダクション環境での非機能要件とステークホルダーの体制の明確化、利用するローデータの確認も忘れない 概念実証(PoC) : 学習データに対するEDAの結果をもとに、前処理や特徴量選択を行い、アルゴリズム候補を選定。RMSEなどの精度指標を用いたオフライン検証によって、最終的なモデルを(複数)選択 効果検証 : プロトタイプ化した機械学習モデルを本番環境へデプロイ。本番トラフィックの一部を利用してA/Bテストなどを行い、ビジネスKPIを基準に既存手段との比較・評価を繰り返して本番適応の意思決定をする 本番開発 : リファクタリングやワークフローのパイプライン整理、またシステムデザインや各クラウドコンポーネントを最終決定。単体テストと統合テストだけでなく、入力データの分布等に変化がないかも確認が必要 保守運用 : 機械学習モデルの精度やビジネスKPIだけでなく、レスポンスタイム等のシステムメトリクスなどの指標も監視する。また、CI/CDのみならず 継続的トレーニング(CT) が実行できる環境を整備する プロジェクト遂行に必要なリソースと主な担当職種  前述の通り、以下では事業会社における機械学習プロジェクトでの理想的なプロジェクト体制について検討していきます。 プロジェクト承認機能 : Project Owner(Business Manager) プロジェクト管理機能 : Project Manager アルゴリズム開発機能 : Data Scientist(Machine Learning Engineer) 本番開発機能 : Machine Learning Engineer(Data Scientist) 保守運用機能 : Software Engineer(Machine Learning Engineer)  補足としてアルゴリズム開発以降の後半3機能は、 こちらの記事 におけるDevelop・Deploy・Driveの3プロセスの責務に対応するイメージです。  そして、とても重要だが十分にその必要性が検討されていない機能(体制)は、1番目と5番目の役割です。  「プロジェクト承認機能」とは具体的に、デプロイ先のプロダクトが生み出すビジネス成果(コンバージョンなどのKPI)に責任を持つBizDevの人です。  また「保守運用機能」とは、機械学習システムをデプロイ対象となるプロダクション環境のドメイン知識を持った開発者を指しています。  これらの人々は社内のMLチームの”外”にいる場合が多いですが、彼らの積極的合意がなければ、不確実性の高いMLモデルの本番適応は難しいです。  なので彼らをいかにプロジェクトの適切な段階で巻き込むことできるかが、小さな(大きな)失敗を許容してもらう上では非常に大切です。  その他にも、仮にビジネスKPIを改善できたとして、MLのデプロイはシステムに対してほぼ確実にさらなる技術的負債や不確実性を積むことになります。  そうなるとプロダクション側の開発者に旨味がなく、また運用保守の精度を保証する術も無いため、最終的な受け入れ拒否に繋がりかねません。  こうした問題への対処や交渉のために、2番目の「プロジェクト管理機能」の存在が重要になります(こちらも軽視されがちですが)。  総じて、スクラム的な顧客一体型の開発体制やPMの配置が、プロジェクト後半の受け入れ拒否のリスクを最小限に抑えたる上で重要かと思います。 プロジェクトがクローズ・振り出しに戻るリスク 役割分担のリスク : モデル開発やETL処理のパイプライン化、インフラの選定、本番環境への結合とそのテストなどやることと求められる知見幅が非常に広い。しかし、PoCを乗り越えることすら不確実なため、プロジェクトの立ち上げ期からそのリソース確保や体制を確立することが難しいのが実情 推定精度のリスク : いわゆるPoC死や効果検証フェーズでの敗北。新しいモデルの精度不足または既存手段を上回れないことの方が多い 非機能要件のリスク : MLチームにとっては暗黙知的な本番環境で要求されるシステムの制約や、レイテンシーやクラウドの費用、セキュリティ要件を満たせるかなどの一般的な非機能要件も早く正確に認知する必要がある データ依存のリスク : CACEの原則 。機械学習モデルの振る舞いは、入力データに依存して決まり、データ自体(分布)も時間の経過に従い変化しうる CI/CD/CTのリスク : ローンチ後にKPIや推定精度が下がった際に、どう解釈(説明)するか。常に最新のデータに適応した、最良のモデルであることを担保するための環境の整備が難しい  上記以外にも、システムデザインの選定や決定的テストの難しさなどの私の知見不足でまとめられなかった難関は他にも多数存在します。  これらに関しての解決策は、 MLOpsの歩き方 やGoogle社による こちらの記事 内でいくつか提案されています。 MLOps: 効率的な開発を阻害する要因  プロジェクトがクローズとまではいかないものの、機械学習プロジェクトに「遅延」をもたらすお馴染みの課題は上記以外に存在しています。 テスト環境と本番環境の違い : Jupyter Notebookに書いた前処理や推定のプログラムをパイプラインにまとめたり、クラス化し直す必要がある バージョン管理が必要な対象の広さ : 学習に使用したデータ・アルゴリズム・ハイパーパラメータ・評価指標をセットで管理する必要がある パイプラインのジャングル化 :MLパイプラインの乱立により、システムを占めるグルーコードの比率が肥大化し、メンテナンスが困難になる  これらを解消するために、機械学習ライフサイクルのスケーラブルな運用を可能にするMLOpsツールが日進月歩で開発されています。  ツールは大きくハイパーパラメータ管理、実験管理、パイプライン(ワークフロー)管理の3つのカテゴリーに大別でき、カテゴリーと具体的なOSSの対応については こちらの記事 が非常に参考になります。  一方で、ツールの比較・検討をプロジェクト中に行うような余裕は基本ないため、自社におけるMLOpsツールの選定をいつ行うかは工夫する必要があります。  ツール以外にもデータサイエンスプロジェクトとアジャイル開発がどう溶け合うかなど開発スタイルに関する議論も個人的には面白いトピックだと感じています。 さいごに  今回はプロジェクト管理の視点から、LIFULL発の「データサイエンスの社会実装」の成功確度を高めるアプローチを模索してきました。  改めて執筆すると、技術的な理解の甘さを痛感しました。やはりテストやCI/CDは実際に手を動かしてみないと解像度が上げられないと思いました。  LIFULLでは、キャリフルという社内副業制度があるので、エンジニアリングの職種に応募してDevOpsやマイクロサービスなどを手を動かして体感する機会をぜひ作りたいと考えています。  今後もData ScientistやML Engineerたちにとって、伴走しがいのあるパートナー(PM)になっていけるよう毎日の業務で成長していきます。  LIFULLのアナリティクスチームは、上記のデータアナリティクス/サイエンスプロジェクト両方の問題解決に貢献できるよう、これからも越境し続けます。  ご拝読、ありがとうございました。 参照: 企業におけるデータ分析プロジェクトと求められるスキル 機械学習を「社会実装」するということ ゆるふわMLOps入門 MLOps: 機械学習における継続的デリバリーと自動化のパイプライン 機械学習システムの設計パターンを公開します。 機械学習アプリケーションにおけるテストについて 機械学習:技術的負債の高金利クレジットカード マイクロサービスアプリケーションとしての機械学習 Data Science Project Flow for Startups 機械学習アプリケーションにおけるテストについて
Hi, I'm Jye Ruey . A SET(Software Engineer in Test) from LIFULL. We published an End-to-End testing framework "Bucky" at last time. www.lifull.blog In this time, an image difference detection tool "Gazo-san", which is for visual testing, is also published. github.com This article will introduce visual testing and the features of Gazo-san. What Is Visual Testing Why Visual Testing is needed Three keys in Visual Testing Capture 📷 Difference detection 🔍 Reporting 📑 Difference with End-to-End Testing E2E for funcional, Visual Testing for visual Visual Testing has no maintenance cost Visual Testing in LIFULL Capture Difference detect Report Features of Gazo-san The key point in Gazo-san Detect on parts At Last Reference What Is Visual Testing Visual testing is a word that is often heard. But it seems that visual testing doesn't have a formal definition in most testing associations. We found the definition of visual testing in an introduction article. ( https://dzone.com/articles/what-is-visual-testing-a-definitive-answer-and-app ) It says: "Visual testing is how you ensure that your app appears to the user as you intended." In other words, visual testing is a test that checks what is showing . Why Visual Testing is needed When developing in the front-end, HTML, CSS and JavaScript make the site change often. The factors which are possible to change the layout are just too many. The unexpected changes after developing, like garbling or skewing, sometimes will happen even if we don't notice that. To make sure the page looks correctly for the user, the test checking the page exactly showing in the browser, may be a proper way for testing. Three keys in Visual Testing But checking every page by our eyes is a tough test isn’t it? 😇 It needs to take the capture before and after the change on the website. Full size capture of the site may be too long to check. It also needs to check the website in mobile size. It's hard to realize the delicate difference. Tens of hundreds of cases to check will make your eyes tired. It seems checking every case by human eyes is impossible. A good tool will help you to check it easily. Summary the difficulty above. A tool with these three features will be helpful. Capture Difference detection Reporting Capture 📷 Without the capture, we have nothing to check in visual testing. In regression testing, capture before and after implementation is very important. To make sure the capture is the same as the user seen, a size designate and mobile view capture is also required. Difference detection 🔍 The function that recognizes the difference between the two images. The tool will markup the part needed to check, so we just need to check the markup rather than the whole capture. The picture below is an example of Gazo-san difference markup. The red part is the difference part. The part, which is not red, is the part without any change. We can just check the red part. Reporting 📑 Checking tens of hundreds of cases is hard work. A good report format will make the check more efficient. The management and traceability of the test cases is also the key point to a good test tool. If the tool can manage test cases on a dashboard, it will be easier for analysis and checking. Difference with End-to-End Testing Visual testing usually checks the capture before and after implementation. So it is also included in regression testing. Compared with E2E (End-to-End) testing, which is also famous in regression testing, visual testing assures a different objective with E2E. By controlling the UI, E2E testing assures the function in application to work expectedly on the page. E2E for funcional, Visual Testing for visual E2E testing always specifies the UI element by class or id, then manipulates them. Sometimes, the element may change the place on the page, this will lead the E2E testing not working well and the case may just fail. In other words, E2E testing can't really recognize the element place changing. To assure the element display in a correct place , a regression test that combines E2E testing and visual testing will be more effective. Visual Testing has no maintenance cost E2E testing will need to fix the test script when the element has been changed. Visual testing only compares the two captures, it doesn't need to maintain any test script. However, there will be cases such as dealing with application data deficient and redefining the test target. It may still cost some other effort. Visual Testing in LIFULL SET team in LIFULL uses visual testing for two reasons. To assure the visual change. To reach a broader coverage by combining visual testing with E2E testing. E2E costs the effort of implementing a test script, but visual testing can start with only the captures that want to compare. The strategy to make broader coverage is moving the implementation and checking effort into visual testing. Actually, we don't use the packaging visual testing tools, we deal with the three keys in visual testing respectively as follows. Capture We take the capture of the target page in full size by headless chrome. Difference detect We use "Gazo-san", which is an image difference tool we publish at this time. It will be introduced later in this article. Report We prepare a HTML template for the difference report in each test case. The upper form will be created before the new release. We can check the difference in red parts from each case. If the UI changes expectedly in this release it means the test is passed. Otherwise we will report the bug to developers. SET team has created the visual testing for the integration environment in system testing level, to make sure there are no side effects between each feature. Maybe the visual testing system still gets somewhere to improve, it just works pretty well. We can check 150 test cases in 10 minutes with this system. Features of Gazo-san Before introducing Gazo-san, let's talk about the type of image difference detection. There are two types of image difference detection. ・Perfect match: Compare with every pixel. Also known as Pixel perfect testing. ・Similar match: Calculate the similarity by the algorithm. (There are many kinds of algorithm) Perfect matches usually show the difference by coloring the parts on the picture. Although, similar matches only show the similar rate in percentage. Perfect match Similar match Pros Detect detail difference. 1. Works fine on pictures with different sizes. 2. Easy to recognize even though there are many differences. Cons 1. Not working well on pictures with different sizes. 2. Hard to recognize the differences when too many differences are showing. Can't show the delicate difference. The key point in Gazo-san Although Gazo-san is a kind of perfect match. It has a special structure dealing with recognizing many differences. The structure is splitting each capture in few parts and showing the difference only on the matched parts. We’ll show the key point by demo pictures. Detect on parts 1. Input two pictures for compare Before Implementation After Implementation 2. Splitting the capture into parts Before Implementation After Implementation 3. Match by parts Parts matched with no difference Parts matched with difference exist Parts unmatched 4. Create output In Output_diff, matched parts are surrounded with a red line, the difference will be colored in red. Output_diff In Output_delete, showing the disappearing parts by surrounding the green line on the after implementation capture. Output_delete In Output_add, showing the increased parts by surrounding with green line on the before implementation capture. Output_add With this structure, it becomes easier to recognize the difference even though two captures have different sizes. At Last SET team executes the visual testing before release to assure the visual view which is expected to show to the user. We had detected the degradation by visual testing many times. We feel relieved that we execute visual testing before the release. Using Gazo-san can make visual testing easier. Please give it a try. Also, there are many tools for visual testing. You can just choose the suitable tool for your team. At last, there are still some parts that can be improved, we are looking forward to getting your issue or Pull Request. Enjoy the happy visual testing time! Reference 5分でわかるVISUAL TESTING FOR HTML5 What is Visual Testing?
ソリューションアーキテクトの鈴木( @szk3 )です。 事業ドメイン知識 + クラウドサービスの知識で、自社サービス開発をサポートする設計相談サービスとして「社内ソリューションアーキテクト」サービスという取り組みをスタートし、 2017年から現在に至るまで3年以上にわたり運用してきました。 本エントリでは、この取り組みについて振り返り、まとめました。 サービスの成り立ちや考え方、どのように運用してきたのか?どんな変化があったのか?なぜクローズするのか?などなど。 自社でソリューションアーキテクトのような職種や役割を作っていこうと考えている方の参考になれば幸いです。 はじまり コトのはじまりは、 3年以上前、2017年4月 に遡ります。 当時、主幹事業のAWS移行が概ね完了し、各部署の裁量で個別にAWSアカウントを運用し始めたタイミングでした。 自分たちでインフラリソースを自由にできるようになった反面、 慣れないAWS上での設計に対する不安の声 も同時に耳にするようになりました。 当時からAWSの情報量は膨大であり、選択肢の多さや制限事項の見落としなど、従来のサービス開発とは別のコストが発生するようなシーンも顕在化しつつありました。 もちろん、能動的にキャッチアップしマネージできる部署も存在しましたが、少ない人的リソースや挑戦的なサービス開発目標などで余裕が少なく、キャッチアップに少なからず時間がかかる部署もありました。 そこで、事業ドメイン知識とクラウド上での設計知識を効率よく伝達しつつ、設計や問題の切り分けをサポートすることで調査コストや設計の差し戻しコストを減らし、サービス開発効率に間接的に貢献しようと始めたサービスが、 「社内ソリューションアーキテクト」 サービスでした。 実際どうだったの? どうやって相談するの? JIRA(課題管理ツール)にチケットを切ってもらい、相談内容を書いてもらいます。 その後、30分から1時間程度のミーティングを設け、相談内容についてディスカッションした後に、議事ログや議事フォトをチケットで共有する。 たったこれだけです。 あれこれ仕組みを整えるより、やってみてから考えるという感じでスタートしましたが、 シンプルなやり方ゆえに、特に大きな変更が必要になることもなく、当初からのスタイルを今でも継続しています。 チケット自体は1回づつクローズしますが、ひきつづき相談したい場合は何度でも相談できるようにしています。 どれくらいの件数の相談を受けたの? 3年半くらいの間で、100件を超える相談に乗ってきました。 最も多い時でも1週間に2-3本のペースだったので、比較的緩いペースだったと思います。 どれくらいの工数掛けてるの? 相談は、あくまでも発生ベースのため、一概に月に何時間とかは出しにくいのですが、平均すると業務時間の10%程度が感覚値として近いと思っています。 何人でやってるの? アーキテクトの体制は、自分一人で始めました。 もっと多様な視点で相談に乗れる体制にしたかったのと、特定のバイアスを避けるため、 設計に関心のある同僚を誘い、途中から約1年ほど2名体制で相談に乗っていました。 アーキテクト数名によるモブプロのような設計相談は、発散しきって収集できないように思われるかも知れませんが、 最終的には「やっぱりここだよね」という落ち着くべきところに落ち着くことが多かった 気がします。 しかし、残念ながら、現在はまた一人体制になってしまいました。 相談範囲とか内容は? 当初は、AWSを主軸に相談に乗ってきましたが途中からGCP周りの相談も乗るようになり、現在はクラウドアーキテクトとして幅広い相談を受けています。 相談内容は重い相談から軽い相談まで様々で、相談者の目的や課題に対する優先度もバラバラです。 カテゴリとしてまとめるとすると、バッチの設計・実装相談、CI/CDの構築相談、クラウドでのストレージ選定、システムリプレイスの相談、新機能のフィジビリ相談、その他 のような感じになります。 また、クラウド周りのトラブルシューティングにも駆り出されることが多々あります。 どんなことに気をつけてきた? 意識してきたことはたくさんありますが、ひとつ選ぶとしたら 「相談者の立ち位置を忘れない」 ということです。 相談相手によって、ドメイン知識やクラウドの知識に差があることが当然なので、全員に対して同じスタンスではなく、相談者の目線で課題解決を一緒に考えるように心がけていました。 アーキテクトとしての理想系の回答は持ちつつも、一番重要なポイント(納期、リソース利用料、運用コスト、拡張性など)をすり合わせて、トレードオフとセットで押し付けない提案をするようにしていました。 あくまでも相談者の意見を深堀りし、選択肢を広げ、相談者の選択をサポートすることを提供価値として意識的に持つようにしています。 サービス設計と同じで、顧客(相談者)の目線に立つのが大事 ですね。 なにか変わった? 個人的な変化としては、社内での認知が進み、クラウドというワードで想起してもらえるようになりました。 そのため、ありがたいことに、いろんなところからチャットで小さい雑相が飛んでくるようになったり、 海外カンファレンスなどにも参加させていただくような、 新しい機会に恵まれるようになりました。 組織的な変化としては、残念ながら、目に見える大きな変化を生み出すことはできていないと思っています。 これは当初、個別相談対応を経てソリューションカタログを作成し、共有することでの効率化を提供価値として考えていましたが、 実際にはカスタムされた相談は転用が難しいケースが多々あり、汎化しづらい問題がありました。 仮に相談時点のプラクティスをカタログ化したとしても、そのソリューションが賞味期限切れになる可能性を考慮すると、カタログのメンテナンスコストより「相談のタイミングでベストのものを一緒に考える」ことを重視していました。 しかし、この当時の判断は、完全に間違った判断だったと思います。なぜなら、カタログを実際に作ってないからです。 やってみた上でドキュメンテーションのコストがかかるなら判断のしようがありますが、机上の算盤で判断してしまったのが反省点です。 カタログの更新に責任を持ち、カタログを育てていったほうが組織に対する価値を提供できた可能性を考えると、「とにかくやってみる」をしなかったのは、今思うと反省すべき点になります。 相談内容にも変化がありました。 以前に比べ、クラウドの知識はコモディティ化してきており、一般的なユースケースはほぼ情報が出揃っています。 そのため、相談者が自身でベストプラクティスにたどり着きやすい世の中になり、特に相談に乗らなくても自身で解決できるケースが増えているように感じます。 また、 時代はクラウドからコンテナの時代へと、アーキテクトの関心も変化していっています。 その流れに迎合し、アプリケーションの実行環境はコンテナ化を推奨し、社内のKubertnetesへの載せ替えをセットで提案するようになりました。 他にも、CI/CDなどに代表される便利な外部サービスが充実してきたこともあり、クラウド上で作るパターンと、外部サービスを使うパターンの比較の相談も増加しました。 生産性向上のために外部サービスを積極的に使っていくことの敷居が下がり、それらサービスの知識や肌感も幅広く求められるようになりました。 まとめ 「社内ソリューションアーキテクト」サービスという取り組みを紹介しました。 いまさらですが、約3年前(2017年8月)にLT発表させていただいた社内ソリューションアーキテクトについての資料を公開しておきます。 【Web系ベンチャーが語るAWS利用事例】社内ソリューションアーキテクトのすすめ from LIFULL Co., Ltd. www.slideshare.net ここに書いてあるとおり、 最終目標は「サービス提供先」への貢献 にあると今でも思っており、その方向性は一貫してきました。 反省すべき点は多々ありますが、利用者からのアンケート結果では、自分でも驚くような高評価を頂くことができました。 じゃあ、なんで閉じるの?って話ですが。 現在、社内ではアーキテクトに特化した部署が出来たこともあり、 組織単位で「アーキテクト活動」として相談に乗るような取り組みが始まりました。 「アーキテクト活動」は情報のストックにも重点を置いているため、これらが活性化することで 自分が構想しつつ着手できてなかった知見のストックも促進されるのは大変喜ばしいこと だと思っています。 また、アフリカのことわざで「早く行きたければ一人で進め、遠くまで行きたければ皆で進め」とあるように、スペシャリティと多様性が組織にとても重要だと考えています。ですが、今と同じやり方や延長線上には遠くに行くための道筋が描けなかったというのが、大きな理由になります。 そこで、 もっと良い形を模索する中で、いままでの経験を生かし新しい価値提供体験を作るために、一度立ち止まることにしました。 約3年半続いた「社内ソリューションアーキテクト」サービスは、今月末でサービスとしての役割を終了させますが、自身のロールとしては引き続きクラウドアーキテクト、ソリューションアーキテクトとして活動していきます。 今後は、社内の技術的な相談に対して、また少し形を変えて取り組んで行くことになりそうなので、そちらの活動はまた別のエントリで紹介したいと思います。 自社でソリューションアーキテクトのような職種や役割を作っていこうと考えている方の参考になれば幸いです。
AI戦略室の椎橋です。LIFULLで取り組んでいる広告費配分のポートフォリオ最適化を紹介します。 LIFULLは広告宣伝費に年間100億近く使っており、決算説明会の質疑応答でも頻出なテーマで削減することが求めらています。広告にはTVCMや電車のつり革広告、リスティング広告や、リターゲティング広告など広告配信する場所やターゲットユーザー層もさまざまな種類があります。これらの広告媒体にそれぞれいくらの金額を投資すべきかというポートフォリオ最適化を計算するのが本記事のメインになります。 社内システムMAM 広告運用を自動化するためにMAMという社内システムがあります。広告を運用するマーケターが操作するためのフロントエンド、広告実績を蓄積するDB、取得・集計する定期バッチ処理などの機能をすべてまとめてMAMと呼んでいます。ポートフォリオ最適化におけるデータの流れを簡易的に図示すると以下のようになります。 ポートフォリオ最適化のためのデータの流れ簡易図 広告実績データをBigQueryに保存し、そこからデータ抽出、機械学習計算、計算結果のポートフォリオで広告入稿、その結果がBigqueryに保存される、というサイクルになっています。 アトリビューションモデル ポートフォリオ計算の前に広告を価値を定量化してサイエンスの問題に落とし込みやすくします。ユーザーはコンバージョンまでに多くの媒体に接触しており、接触した媒体はそれぞれどれくらい貢献しているかというのを計算するモデルになります。下図の接触例で、代表的なアトリビューションモデルで評価したときの広告評価値を表にまとめると以下のようになります。 コンバージョンまでの媒体接触例 さまざまなアトリビューションモデルとそのときの広告価値見積もり 弊社では独自のアトリビューションモデルを開発していて、そのモデルで各広告の売上換算価値を見積もることができます。 計算方法は今回は省略します。この価値を使ってポートフォリオ計算を行います。 ポートフォリオ最適化アルゴリズム ポートフォリオを計算するために最適化問題を解くのですが、必要なパーツをサブセクションに分けて説明します。  広告効果モデル 各広告に対していくらの金額を使うといくらの売上価値が見込めるかという回帰モデルを作ります。扱いやすいように凸の性質を持つような関数で回帰します。一般的に広告予算を増やせば増やすほどコンバージョン率の低い層にも広告配信していくようになるため、以下の図に示すように減衰効果をモデルに組み込むことには妥当性があります。 広告効果モデル(横軸はコスト、縦軸は売上) 入口出口問題 これは社内独自の問題で私はそう呼んでいます。LIFULL HOME'Sは賃貸、新築マンション、中古戸建などのセクターごとに管理する部署が異なり、予算も目標売上も各セクターごとに割り当てられています。一方で賃貸と売買のどちらにするか悩むユーザーは多くおり、賃貸想定の広告のつもりが最終的に中古マンションを購入するということは珍しくありません。なので発生したセクターごとの売上の割合で各部署で予算を出し合って広告出稿したとみなすようにしています。リスティング広告の例がわかりやすく、以下のように"東京 ほーむず マンション"という検索広告は賃貸ユーザー向きか売買ユーザー向きかはっきりしません。この広告に100万円使って売上価値が賃貸200万、新築マンションが600万、中古戸建が200万の売上になったら、それぞれの予算を20万、60万、20万使ったことにするということです。 この配分ルールによって、賃貸の予算を使おうとしても売買の予算も使ってしまうことになり、トレードオフがある中でポートフォリオを計算する必要があります。 "東京 ほーむず マンション"の検索広告 定式化 問題を簡単にするために本記事ではセクターを賃貸と売買の2セクターとし、広告効果モデルは の形式で書けるものとします。 定式化は以下のようになります。 最適化問題 ここで は大きな定数、添え字 は広告IDを表し、 は広告効果モデルのパラメータです。式7式8ではダミー変数を用いていますが、これは実運用では残り予算がマイナスになるときがあり、そのときでも実行可能解を出力するための式変形です。BUDGET_CHINTAI=100, BUDGET_BAIBAI=200は賃貸部署、売買部署の予算です。 サンプルコードを用意しました。 import cvxpy import numpy BUDGET_CHINTAI = 100 BUDGET_BAIBAI = 200 AD_MODELS = [ { "id" : 1 , "a" : 20 , "chintai_rate" : 0.2 , "baibai_rate" : 0.8 }, { "id" : 2 , "a" : 10 , "chintai_rate" : 0.3 , "baibai_rate" : 0.7 }, { "id" : 3 , "a" : 5 , "chintai_rate" : 0.5 , "baibai_rate" : 0.5 }, { "id" : 4 , "a" : 30 , "chintai_rate" : 0.9 , "baibai_rate" : 0.1 }, ] def _predict_sale_and_cost (cost, ad_model): sale = ad_model[ "a" ] * cvxpy.log(cost + 1 ) sale_chintai = sale * ad_model[ "chintai_rate" ] sale_baibai = sale * ad_model[ "baibai_rate" ] cost_chintai = cost * ad_model[ "chintai_rate" ] cost_baibai = cost * ad_model[ "baibai_rate" ] return sale_chintai, sale_baibai, cost_chintai, cost_baibai def objective_function (costs, dummys): M = 1000 sum_sale_chintai = 0 sum_sale_baibai = 0 for cost, ad_model in zip (costs, AD_MODELS): sale_chintai, sale_baibai, _, _ = _predict_sale_and_cost(cost, ad_model) sum_sale_chintai += sale_chintai sum_sale_baibai += sale_baibai return sum_sale_chintai + sum_sale_baibai - M * (dummys[ 0 ] + dummys[ 1 ]) def constraint_function (costs, dummys): const_list = [] sum_cost_chintai = 0 sum_cost_baibai = 0 for cost, ad_model in zip (costs, AD_MODELS): _, _, cost_chintai, cost_baibai = _predict_sale_and_cost(cost, ad_model) sum_cost_chintai += cost_chintai sum_cost_baibai += cost_baibai const_list.append(sum_cost_chintai <= BUDGET_CHINTAI + dummys[ 0 ]) const_list.append(sum_cost_baibai <= BUDGET_BAIBAI + dummys[ 1 ]) for cost in costs: const_list.append(cost >= 0 ) for dummy in dummys: const_list.append(dummy >= 0 ) return const_list def calc_sum_cost (costs): cost_chintai = numpy.array([c * m[ "chintai_rate" ] for c, m in zip (costs.value, AD_MODELS)]).sum() cost_baibai = numpy.array([c * m[ "baibai_rate" ] for c, m in zip (costs.value, AD_MODELS)]).sum() print ( "賃貸コスト " , cost_chintai) print ( "売買コスト " , cost_baibai) costs = cvxpy.Variable( len (AD_MODELS)) dummys = cvxpy.Variable( 2 ) prob = cvxpy.Problem(cvxpy.Maximize(objective_function(costs, dummys)), constraint_function(costs, dummys)) prob.solve(verbose= False , solver= "ECOS_BB" , mi_max_iters= 50000 ) assert prob.status == "optimal" costs.value calc_sum_cost(costs) 実行すると以下の解を得ます。広告1,2,3,4にそれぞれ155,51,14,51の予算を割り当てるという結果で、このとき賃貸部署が負担する予算は100、売買部署が負担する予算は172です。これは定式化の性質的には大域的最適解に収束しているはずなのでこれ以上多く売上を出せる解は存在しないはずです。 最適化計算結果 事業の成長に合わせて目的関数を変更できる サンプルコードでの売上最大化は一見腑に落ちる式に見えますが、売買部署の立場から見ると予算が200あるのに172しか使っていないという点で最適ではないかもしれません。利益の最大化を目的としたポートフォリオはまた別の解が出てきます。このように立場や事業の成長期によって目的が変わるという意味ではある意味多目的最適化の側面を持っており、例えば想定されるシチュエーションごとに対策を考えておくとこうなります。 目的関数のバリエーション  説明責任 このアルゴリズムの強みは細かい運用における制約を記述できることですが、説明責任を果たせることも大きなメリットです。最近の金融工学の研究ではニューラルネットワークや強化学習を用いたポートフォリオ最適化もあるのですが、ブラックボックス性の強さが安定運用の妨げになりかねません。出力値に対して説明ができず、ポートフォリオを採用してくれる社内のマーケターに安心感を与えられません。機械学習は広告効果の予測のみに使うように切り分けることで、広告効果モデルの予測が正しいと仮定すればそのあとの計算については納得してもらえます(もちろん数理最適化の説明は平易な言葉に置き換えます)。 まとめ 広告宣伝費最適化に向けた数理最適化の活用事例を紹介しました。最適化問題は非常に強力とは言えないのですが、かゆいところに手が届く技術で、業務上必要になる細かいロジックを洗練させたいときに役立ちます。 AI戦略室の事例をコンスタントに発信できるようにがんばります。ありがとうございました。
AWS利用の最適化に従事してます、鈴木( @szk3 )です。 最適化といってもいろいろありますが、ここ最近はAWSにおけるコスト削減についていろいろと行ってきました。 LIFULLのアカウント数は100を超えます。それらのアカウントに対し、約240以上のコスト削減案を立案し180件以上の施策を完了させてきました。 今回は、この新型コロナの影響で先行きが不透明な中、AWS利用費用を見直したいという方のために、 実体験に基づいたAWSコスト最適化の流れと考え方をシェアしたい と思います。 コスト削減の流れ いろいろとやってきましたが、 コストを削減する流れはどれも同じ です。 現状を知る ソリューションを選ぶ 実行する めちゃくちゃシンプル ですね。 ひとつづつ見ていきましょう。 現状を知る コストを削減するにあたり、最も重要なのはどこにコストが掛かっているかを知ることです。 「無い袖は振れない」ので、 CostExplorer を使い、どこにコストがかかっているのかを特定するのが最初の作業 です。 CostExplorerについてはこちらの資料が参考になります。 20200129 AWS Black Belt Online Seminar AWS Cost Explorer from Amazon Web Services Japan www.slideshare.net まずは、コスト高のサービスを軸にピックアップします。 この時、画面右側フィルターの「料金タイプ」から、自分たちでどうにもできないものと、コスト削減対象になりえない項目を除外しておきましょう。 具体的な項目としては、「サポート料金」、「税金」、RIやSavingsPlansなどの「前払い料金」などです。 次に、サービスごとに絞り込んで行きます。 ひとつのサービスでフィルターし使用タイプでグループ化して、サービスのどこにコストが掛かっているのかを明らかにします。 この時のポイントは、使われ方でコストが変動するサービスは最適化に時間がかかるケースが多いので、 リソースのプロビジョニング次第でコントロールできそうな使用タイプで絞り込みあたりをつけていきます。 例えば、「EC2 その他」サービスでフィルタし、「使用タイプ」でグループ化した場合、 DataTransfer-Regional-Bytesより、EBS:VolumeUsage.gp2 や EBS:SnapshotUsage のほうが、手っ取り早くコスト削減できる可能性が高いです。 また、調査内容は、どこかにチケット化(タスク化)しておくことをおすすめします。 いますぐ対応できなくても、後日対応できる可能性もあるので、ネタとしてストックしておきます。 この作業により、コスト削減候補のリストが出来あがります。 このリストは、のちのち考慮する最適化のしやすさや削減コスト想定額などを軸にして、優先順位を決定するのに役立ちます。 ソリューションを選ぶ さて、コスト削減候補のリストができあがりましたので、今度はどのように対応するかを検討していきます。 コスト削減の原理原則もシンプル です。 不要なリソースは、停止・削除する 必要なリソースは、適切に使う 適切なリソースは、コスト最適化オプションでコスト削減する これだけです。 不要なリソースは、停止・削除する 運用あるあるですが、昔から運用しているAWSアカウントには、削除漏れ・賞味期限切れのリソースが残ってしまうパターンがあります。 例えば、数年前に取得したスナップショットやEBSなど、現在利用しても要件を満たさないリソースが存在しているケースが多々あります。 また、RDSの手動バックアップと自動バックアップが重複していたりと、役割がかぶっているものなどは運用ルールを見直すことで重複リソースを削除することが可能です。 S3などは、大量のログがあるものの、特に利用されることも無いのに、標準のストレージクラスで保存していないかなどを確認しましょう。 ライフタイムを設定することで、古いオブジェクトを削除したりGlacierなどのストレージクラスに変更することでコストを抑えることが出来ます。 これらは、組織変更や担当者が変わることで、リソース管理の責務がルーズボールになっているパターンがあるので見落としがちです。 また、開発環境も意外と見落としがちです。 試行錯誤の残骸が削除されず、不要なリソースとして残っているケースがあります。 また開発環境であればリソースを24h/365dで使わないこともあります。 EC2やRDS は Instance Scheduler を使うと簡単に起動・停止設定をすることができます。 CloudFormationを使って簡単にプロビジョニングでき、インスタンスの起動や停止をタグで管理します。 仕組みもシンプルですし、 夜間・土日に停止しておくだけで、かなりのコストを削減することが可能 です。 aws.amazon.com これらのリソースの調査で、削除・停止できそうなものが見つかったら、コスト削減案としてストックしていきます。 これらの”不要そう”なリソースは、「持ち主」や「背景」を紐解くのに時間がかかる反面、不要となれば削除するだけなので 最も簡単にコストを削減 できます。 (先走って勝手に削除するのはNGですのでご注意を。) 必要なリソースは、適切に使う 使っているリソースが、「適切に使えているか?」を確認しましょう。 具体的には、リソースごとにCloudWatch メトリクスなどで、適切に利用できているかを見ていきます。 EC2であれば、適切なサイジングは AWS Compute Optimizer を利用するのがおすすめです。 過去のメトリクスを分析し、過剰にプロビジョニングされたEC2を、変更リスクとともに新しいインスタンスタイプを提案してくれます。 こちらのYoutubeで、 Compute Optimizer のイメージを確認できます。 youtu.be また、必要に応じて、カスタムメトリクスなどで、メモリやディスクも見ておきましょう。 確認した結果、改善の余地があればインスタンスタイプの変更やダウンサイジングを計画します。 EC2の場合、最新世代のインスタンスタイプに変更するが望ましいのですが、古いインスタンスの場合、ディストリビューションによっては Nitro世代に変更できないこともありえます。 さくっとNitro世代に変更できそうな場合は、このタイミングで変更してしまうのがいいでしょう。 また、AutoScalingグループを確認し、インスタンスを過剰にスケールさせてないかも合わせて確認しましょう。 これらの対応は、 地味ですがコスト最適化オプション購入の前処理として重要な作業 になります。 適切なリソースは、コスト最適化オプションでコスト削減する リソースの最適化が一通り完了したら、いよいよコスト最適化オプションでコスト削減を行います。 ただ、コスト最適化オプションと言っても様々なものがあります。 たとえば、EC2に対しては Reserved Instance(RI) や SavingsPlans など 年間利用を約束し割引する購入オプションがあります。 24h/365dで利用が固定されているEC2は、こういったオプションで最適化し、Auto Scaling などで一時的に利用するEC2には、スポットインスタンスが最適です。 いまは、1 つの Auto Scaling グループ内で、オンデマンドインスタンスとスポットインスタンスのフリートを簡単に混ぜることが出来るので、RI/SavingsPlans + Spot Instance の構成でまんべんなくコスト削減することが可能です。 docs.aws.amazon.com ちなみに、EC2のコスト最適化オプションといえば、以前はRIが主流でしたが最近のトレンドは SavingsPlans を利用することです。 SavingsPlansはRIと比較して、アーキテクチャ変更に対する柔軟性が高いのが特徴です。 20191212 新割引オプション "Savings Plans" によるコスト最適化のご提案 from Amazon Web Services Japan www.slideshare.net SavingsPlansのコミット金額は、若干取っつきづらいかもしれませんが、Cost Explorerからコミット推奨金額が参照できます。 また、RIといえば、EC2, RDS はすぐに想起できますが、ElasticSearch Service にも対応しています。他にも、ElastiCacheのリザーブドキャッシュノードや、DynamoDBのリザーブドキャパシティなど、サービスによって呼び名は違えど、年間利用を約束することでコストを削減できるオプションがありますので、忘れずに検討しましょう。 実行する 最後は、ただ実行するだけです。 ただし、自分で対応できる場合はよいのですが、自分で手を出せないアカウントに対してはアカウントの管理者にお願いして作業してもらう必要があります。 なので、お願いする側としては、費用対効果の金額試算、稟議の文章テンプレ化、進捗のヒアリング、ステークホルダーの洗い出しなど、さまざまなアプローチで作業をサポートできると良いと思います。 「こうあるべき」と正論を振りかざすのではなく、相手の立場になり同じ方向に向けて一緒に最適化していくような丁寧なコミュニケーションを心がけています。 また、残念ながら費用対効果やさまざまな制約により、現時点では最適化できないという判断になったとしても「できなかった」という判断と理由が資産になります。 全てのコスト最適化がスムーズに実行されるわけではありませんが、「あえてしなかった」という判断もひっくるめて、 ひとつづつ完了させていくことに、大きな意味がある と思います。 まとめ 以上、実体験に基づくコスト最適化の流れと考え方を紹介しました。 ただ、コスト最適化はこれが全てではありません。上記以外にも、コスト削減できる方法はいろいろ存在します。 例えば、レスポンスタイムなどを許容できるような場合であれば、利用料金が安いUS Eastリージョン利用の検討したり、 アーキテクチャの変更なども、コスト削減の可能性を大いに秘めています。 ただ、変更の振り幅とそれにかかる人的コストは比例しやすいのも事実ですので、バランスを見て判断するのが良いでしょう。 AWS Well-Architected フレームワークの 5 本の柱のひとつに「コスト最適化」があります。 こちらを参考に、自分たちにとってコスト効率のよい形を目指して行きたいですね。 wa.aws.amazon.com 今回はあえて流れや考え方にフォーカスしました。 サービスごとの具体的な手法についてはまた別の機会にシェアできればと思います。 繰り返しになりますが、コスト削減の原理原則はシンプルです。 不要なリソースは、停止・削除する 必要なリソースは、適切に使う 適切なリソースは、コスト最適化オプションでコスト削減する いまできることから着手し、費用対効果の高い順に対応していく。 当たり前だけど、 これが一番確実で素早くコスト削減に寄与する と信じています。 ご自身の環境に照らし合わせ、お役に立てば幸いです。
こんにちは、LIFULL HOME'Sの売買領域でエンジニアチームのマネジメントを担当しています、長崎です。 ここ数年、LIFULL HOME'Sでは積極的に技術的負債解消に取り組んでおり、今回は私がマネジメントするチーム内でどのような取り組みをしているかをご紹介します。 技術的負債の解消はあらゆるサービスにおいて大きな問題となっており、すでに多くの事例が紹介されていますが、同じように我々の取り組みがどなたかの参考になれば幸いです。 LIFULL HOME'Sにおける技術的負債 これまでに下記エントリでも言及している通り、LIFULL HOME'Sの現行プロダクトは9年を超えて開発されています。 www.lifull.blog 上記エントリではフロントエンドに焦点を当てていますが、これだけ長く開発・保守されているプロダクトですので、技術的負債はアプリケーションレイヤー(PHP / Ruby)、インフラレイヤーと多岐・多層に渡って存在しています。 新機能の開発や既存機能の改修を行う際に、調査・テストの工数が膨らんでおり、サービス成長の大きな阻害要因、まさに負債となってしまっています。 事業開発部門における取り組み LIFULL HOME'Sの開発部門は大きく分けて2つに分かれています。 ビジネスサイドと密接にコミュニケーションしながら、エンドユーザーが触れるプロダクトを日々開発する事業開発部門と、 LIFULL HOME'Sに限らず、プロダクト・サービス全体の基盤システムを保守・改善する技術基盤部門です。 肥大化する調査・テスト工数を削減し、開発効率の向上を図るために、私の所属する事業開発部門では 工数の10%をリファクタリングに充てる というルールを作成し、組織全体として負債解消に取り組んでいます。 私のチームはアプリケーションレイヤーの開発を担当することが多く、下記のようなコードレベルでの負債に日々悩まされています。 使われているかわからないコード 依存関係が適切に分割されていないコード(デグレしやすいコード) 複雑度が高く、テストコードがないコード 私のチームでの取り組み 私のチームでは、上記コードレベルの負債をリファクタリングにより解消するべく、3つの取り組みを行っています。 コードの静的解析に基づいたリファクタリング 日々の改修における課題を基にした大規模リファクタリング リファクタリングを行うべき領域の可視化・優先度付け それぞれどんなことをしているのか、よいところと課題はなにか、をご紹介します。 コードの静的解析に基づいたリファクタリング LIFULL HOME'Sでは、 codeclimate というサービスを導入し、コードの静的解析を行っています。 この解析結果に基づいて、複雑度の高いメソッドや、重複したブロックを発見し、リファクタリングを実施します。 よいところ コードに対し、定量的な指標を持てる 一定のフィードバックを機械的に行える 課題 メンテナンス性のために分割されたクラス・メソッドであっても重複判定されてしまう コードの利用頻度や、改修頻度に紐づかない分析のため、改善効果による優先度がつけられない 静的解析の結果がすべて!としない運用が求められますが、lintツールなどと同様に定常的に活用することで、コードのベースレベルの向上や複雑度に対する意識づけが行える点が良いと思います。 日々の改修における課題を基にした大規模リファクタリング 新規実装当初にその時点でのユースケースから作ったクラスが、長年多数のメンバによって、そして多くの場合デリバリーを優先したプロジェクトにおいて、適切に拡張されずに if 文まみれになってしまうことはよくあることかと思います。 このような状態のコードを放置していると、改修の際に想定外のページ・サービスにおいてバグが発生し、以降デグレチェックに悩まされ続けます。 これを改善するため、現在のユースケースに合わせた大規模なリファクタリングを行っています。 よいところ 普段目を背けているところに向き合う機会になる 改めて見直すことで、現時点では不要になったコードを消すことができる ビジネス要求や想定される改修の質・量が想定できる状況で設計し直せる 課題 アーキテクチャレベルや、フレームワークの負債からは逃れられない リリース手順やタイミングなど、実行計画が難しい ステークホルダーが多くなりがちで、精神的に疲弊する 進めていると往々にして、「もはや作り直したほうが早いだろコレ」という気持ちに苛まれますが、サービスの保守をしながらできることをしていかねばと思い直す日々です。 リファクタリングを行うべき領域の可視化・優先度付け なんとか技術的負債を定量的に表現したいと思い、コードの変更行数とテストケース数(テストコードの行数)の計測を行っています。 現時点での1サンプルですが、変更行数が数行にもかかわらず、デグレチェック目的で数十URLをテストすることになる場合があれば、変更行数が数十行でもテストケースは数ケース程度で済む場合もあります。 前者のような機能は早急にリファクタリングしなければ、毎回大きなテスト工数がかかったり、誰にも意図がわからないデグレチェックが口伝で残り続けてしまいます。 よいところ リファクタリングの期待効果を見定めることができる 非エンジニアに改修の難易度を定量的に伝えることができ、リファクタリングの重要性を理解してもらいやすい 課題 変更行数は計測しやすい反面、プログラミング言語やフレームワーク、設計思想に左右されやすい テストケース数やテストコードの行数は、作成者によりばらつく 現在取り組んでいる指標で目的を果たせるのか、そしてベストなのかどうかはわかりませんが、なんとかリファクタリングの重要性を定量的に表現するべくチャレンジしていきます。 最後に サービス上の不要な機能を削除したり、「今」問題がないコード・システムを直すためにはステークホルダーの理解・協力が欠かせません。 一方で、「リファクタリング」や「技術的負債の解消」という非エンジニアからしたらよくわからないけどすごく大事そうな言葉を盾に、エンジニアが手段を目的化してビジネスに貢献できないことは絶対に避けなければいけません。 今回3つの取り組みを紹介させて頂きましたが、リファクタリングが「よくわからないけどすごく大事そうな言葉」じゃなくするために、ステークホルダーにしっかりと伝え続けることが一番大事な取り組みだと思います。
釣り気味タイトルで大変申し訳ございません。 プロダクトエンジニアリング部の島村です。 総会どうしていますか? みなさまが所属する会社・組織では総会は実施されておりますでしょうか? 部署やチームの結束を高め、メンバーが同じ方向を向くためには貴重な機会となる総会。 エンジニアリングマネージャーであれば、運営を行なったことがある方も少なくないと思います。 総会って難しい そんな総会ですが、下記のような問題が生じやすいと思います。 組織の階層ごとに総会があり、総会過多な状況になる どの総会でも似たような構成になりやすい 会を重ねるごとにネタが尽きてくる 私の所属する組織でも総会の実施を考えたのですが、上記のような問題が懸念され、頭を悩ませました。 総会の目的を考える そもそも何が何でも総会を実施する必要があるわけではありません。 総会を実施する上では、目的や総会を実施することで目指したい組織の姿を定義することが大切です。 今回の我々の組織のケースでは、所属する各グループ内での交流は盛んだが、グループ感での交流の機会には乏しく、そもそもお互いのことをよく知らない等、組織全体としての結束力には課題がありました。 そのため、総会を行うことで目指す姿を下記のように定義しました。 個々人の強みや詳しい領域がわかり、それをそれぞれの組織に還元できる状態 まずは総会を行うことで上記を目指し、ゆくゆくは、 グループの垣根を超えて助け合い、相乗効果を生み出せている状態 例えば何かサービスをつくるときにお互いの力を合わせ、よりよいものを作り出せる状態 のような状態に辿りつきたいと考えました。 OSTに着想を得る 上記を目指すための総会のコンテンツを考えはじめたのですが、そんな時にコンテンツ案として着想を得たのが、OST(Open Space Technology)というディスカッションの方法論です。 www.humanvalue.co.jp OSTは会議の参加者が議題を提案し、その議題に興味のある人が参加して議論を行う会議の方法です。 議題や議論の場の作り方が参加者に委ねられるため、より参加者が主体的、かつ能動的に議論を行えるのが特徴です。 どんな感じでやったか 今回我々は上記のOSTにインスパイアされつつも、初めての試みかつ、関係性が出来上がり切っていない組織での実施であったため、事前にいくつかのテーマを用意し、参加者に選んでもらう形式で行うことにしました。 (それもうOSTじゃないじゃんというツッコミはスルーします!) テーマですが、参加者にとって親みやすい、かつ個々人にとって学びがある・持って帰られるネタがあることを重視し、下記に5テーマを用意しました。 在宅勤務 レビュー 設計・見積もり 仕事を早く終える方法 チームビルディング LIFULLも現在は在宅勤務が多くなっており、総会もzoomでの開催となりました。 上記のテーマごとの部屋を用意し、参加者が入りたいテーマの部屋に入ってトークする形式で実施しました。 やってみてどうだったか やってみて良かった点、イマイチだった点・改善点は下記です。 良かった点 普段関わりのないメンバーでの交流ができた、話が参考になったという意見が参加者からも聞くことができた テーマ選定が日常業務の範疇だったため、初対面くらいの関係性でも議論がしやすかった 自分でテーマを選ぶことができたので、話が盛り上がりやすかった イマイチだった点・改善点 テーマにより人数にバラつきがあり、人数が少なすぎて解散した部屋もあった(5、6人がちょうどよさそう) 技術的な話題のところは知識差がある場合に同じ目線で議論をするのが難しかった テーマは事前発表されていた方が準備して臨めるので議論が深まりそうだった 総会実施後にはアンケートも実施しましたが、約80%がやってよかった、約95%がまたやりたいと回答しており、おおむね好評な結果が得られました。 一方で改善点も多く見えてきております。 目指す姿に近くためには継続性が重要と考えますが、継続していくには議論の質を高めていく工夫も必要と感じています。 まとめ 今回我々の組織で実施した総会について紹介させていただきました。 タイトルは釣り気味ですが、満足度が高かったのは本当なので、今後も改善を重ねながら継続していきたいと考えています。 上記のような形式でのディスカッションは総会以外のチームビルディングにも有用と思いますので、参考になれば幸いです。
こんにちは。LIFULLでエンジニアをしている中村優太です。 2020年4月に新卒で入社して、早くも4ヶ月、配属されて2ヶ月が経過致しました。 この記事では、配属までのLIFULL新卒エンジニア研修についてご紹介したいと思います。 はじめに 研修スケジュール プログラミングの基礎 個人開発演習 その他トピック 最後に はじめに LIFULLのエンジニアは2ヶ月間の新卒研修があります。 最初の2週間は全職種合同で会社のビジョンの理解や社会人の心構え、ビジネス基礎を学び、残りの1.5ヶ月間はエンジニア研修になります。 LIFULLの新卒エンジニア研修はプログラミングの基礎の基礎から始まります。 と言うのもLIFULLはとにかく経営理念『常に革進することで、より多くの人々が心からの「安心」と「喜び」を得られる社会の仕組みを創る』を大切にしています。 採用過程では技術力を評価の軸にするのではなく、経営理念に共感する価値観を持っているか?、それに伴う行動を自発的に起こしてきたか?が評価されている(のだと思います!)。 そのため新卒には、Web開発インターンをしていた人もいれば、Web開発は未経験と言う人もいます。 それどころか同期は、大学で生物を研究してた人、機械科出身、農学部出身とバックグラウンドも様々です!(ここもLIFULLらしい) 前置きが長くなりましたが、つまりは技術差があるのです! 新卒エンジニア全員がWeb開発の基礎と全体像を理解し、実装できる状態にしてから実務に入る。 そのためにエンジニア研修は1.5ヶ月に設定されています。 そして今年はみなさんご存知の通り、新型コロナウイルスの影響により入社初日から出社できないと言う状況でした。 LIFULLでは入社式もリモートで行いました。 当然エンジニア研修もフルリモートで行われました。 そんなLIFULLの新卒エンジニア研修について紹介していきます。 このブログを見ていただいた方、特に就職活動中の学生に入社後の様子やLIFULLの人となり、空気感が少しでも伝われば幸いです。 研修スケジュール 以下がざっくりとした研修期間のスケジュールです。()内には営業日を書いています。 04/10 - 05/01(15) プログラミングの基礎 Linux HTML, CSS, JavaScript PHP PHPフレームワーク(Laravel) 05/07 & 05/08(2) ソフトウェアテスト & セキュリティ研修 05/11 - 05/19(7) 個人開発演習 5/20 成果発表会 スケジュールを改めて振り返ってみると、ゴールデンウィークなどもあって営業日だけで言うと1ヶ月分もなかったんですね。 内容については後述してますが、25日間でやる内容にしてはかなりボリューミーです。 さらに今年はオンラインでの研修ということもあり、何かと模索しながらの研修で大変でした。 講師の方は僕たちの数倍大変だったと思います。。。 あくまで僕の例ですが、研修前と研修後のスキルマップはこんな感じです。 Web開発はほとんど未経験でしたが、研修を通して非常に幅広い技術に触れたと思います。 研修の最後の方にはスキルマップには記載されていない技術にもチャレンジしたので、めちゃくちゃ詰め込み教育ですね。 プログラミングの基礎 「はじめに」でも記述したようにLIFULLのエンジニア研修は基礎の基礎から始まります。 Linuxの生い立ちや、HTML,CSS,JavaScriptを使ったDOM操作などWeb実装未経験者にも1からわかる授業です。 ちなみに研修のスタイルとして、講義内容についてすでに理解している人は自習しててOKです。 あくまで目的はエンジニア基礎スキルの向上です。 PHPの研修の最後には、課題アプリケーションを2つ作成します。 課題が与えられ制限時間内にアプリケーションを作成します。 実装が終わると、レビューの時間が設けられており、2人分のコードに対してレビューを行います。 また自分のコードには同期2人と講師の方からレビューをもらえます。 (ちなみに講師の方は全員分のコードをレビューしてくれました。大変だ。。。) 学生時代の研究では、コードに対するレビューは経験することがなかったのでとても新鮮であり、同時にレビューで他人のコードを理解することの難しさを学ぶことができます。 配属されて2ヶ月ですが、先輩方のレビューを見ていて、レビュー能力はエンジニアにとっていかに大切な能力なのかをひしひしと感じてます。 社内には、ソフトウェアテストとセキュリティを専門に扱うグループがぞれぞれあり、そのグループの先輩社員から1日ずつ講義を受けます。 ソフトウェアテストでは実際にある仕様に対するテストケースを自分で考えます。 テストケースを考える上で必要な観点が必要最低限身につきます。 セキュリティ研修では、座学もありますが、実際に研修用サーバに対してSQLインジェクションやXSSを仕掛けます。 研修の最後にはCTF(Capture The Flag)という情報セキュリティのスキルを競い合うセキュリティコンテストを行います。 2人一組で課題に取り組みます。 研修用のWebサイトに対して、いろんな攻撃手法を試して、隠されたキーワードを見つけ出します。 これがまた楽しい!がしかし、できないと悔しい。。。 個人開発演習 個人開発演習はサービス開発における「企画、スケジューリング、要件定義、設計、実装(サーバサイド、フロントサイド)、テスト」の全てを1人でやりきります。 テーマは「チャレンジ&リスペクト」。 制約は「Laravelを使うこと」くらいです。 LIFULL内の多くのサービスにPHPが用いられていることからこの制約が設けられています。 みんな各々自分の作りたいサービスを作ります。 そのため特に授業などがあるわけではありません。 個人開発演習期間の1日のスケジュールは、「朝会→ 個人開発 →昼食→ 個人開発 →夕会→ 個人開発 →終了」です。 本当に1日中個人開発しています。 しかし、講師の方の計らいにより、基本的にZoomを繋げたまま、カメラをオンの状態で個人開発を行いました。 そうすることで、少し実装でつまづいた時や困った時に気軽に相談できるようになりました。 そしてその問題に対して、みんなで考え、調べ解決していきます。 あくまで個人開発ですが、お互いに何を実装したいのかを理解し、知見共有していくことでみんながより良いプロダクトを作れるような環境ができていたと思います。 また、テーマにあるチャレンジの取り組み方も人それぞれで、研修内容で習った内容を最大限プロダクトに落とし込んでアウトプットすることはもちろんですが、自分なりの技術的チャレンジを行います。 CSSフレームワークを使わずにあえてCSSをフルスクラッチで開発する人 PREACT x redisで超軽量ネイティブアプリを作る クリーンアーキテクチャ AI技術を取り入れたアプリケーションを3つ作る 研修中にクリーンアーキテクチャが同期内でブームになりました。 個人開発演習では同期2人が実際に「Laravel x クリーンアーキテクチャ」にチャレンジしていて大変そうだったのが印象に残ってます。 (そんな同期が書いたQiita記事です。ぜひご覧ください! https://qiita.com/Shiruba/items/b0754a5815e0583aa8ce ) 僕は「Vue.jsとDocker使ってみたい」という、いかにもミーハーかつ浅はかな考えで以下のような技術選定を行いました。 時間も限られている中で、少し幅広く手を出しすぎたかなという気がしています。 しかし、いろんな技術に触れることは新鮮で楽しいですね! 研修の最終日には成果発表会があります。 成果報告会はエンジニア全社員にZoomが公開されており、配属後のメンターやグループ長だけでなく、非常に多くの人が見にきてくれました。 みなさん活発に質問してくださり、すごく盛り上がりました! ちなみにLIFULLでは2年目にもエンジニア研修があり、チーム開発でプロダクトを作り上げます(手厚い😂 )。 噂によるとそちらの成果報告は厳しい質問も飛んでくるとか。。。頑張ろう! その他トピック 毎日の研修には日直が決められており、研修の最後に「日直スピーチ」があります。 自分の好きなもの、経験してきたことを好きなように話すコーナーです。 コンテンツは - 3Dモデリング - Deep Learning - 好きなキーボードについて語る - おすすめのVSCode拡張機能 - 競技プログラミングの魅力 - Haskelの魅力 みたいな感じです。 他にも講師の方による、監視技術やボトルネック調査の方法などを講義していただき、研修内容には含まれない実践的な内容も聞けました。 そして中でも僕が印象強く残っているのは、同期が話してくれた「DNAを使ってデータを保持する」という話です。 長くなりそうなので、詳細は省略しますが、DNAはアデニン、グアニン、シトシン、チミンの4種類の塩基が含まれており、この配列によって遺伝子が表現されているみたいです。 1つの塩基対で2ビットのデータを表しているということ! つまりここから導き出される結末はDNAはわずか1グラムで215ペタバイトのデータを保持できるということらしい! 衝撃的すぎますよね! 多種多様な話が聞けるのもメンバーのバックグラウンドが様々だからです! また、研修終わりにみんなでオンライン懇親会を行い、研修では話せないプライベートな話をして盛り上がりました🍺 最後に 配属されて2ヶ月が経過しましたが、実務でも「チャレンジ&リスペクト」が大事だなと思います。チャレンジする環境があり、それを周りが全力で応援する。 LIFULLの社風と言っていいと思います。 僕は同期とやってきたプロジェクトが新規事業提案制度(Switch: https://lifull.com/company/bctw_japan/) で入賞し、業務時間の20%を新規事業創出に費やしています。 新卒でいきなり新規事業に関われるのは、とてもありがたい機会だなと感じています。 また、その新規事業は社長室と呼ばれる新規事業を専門とするエンジニアや、ビジネスのプロフェッショナルがアドバイスをくれたり、時にチームのメンバーとして事業を推進してくれます。 まさに「チャレンジ&リスペクト」です。 僕自身も配属された部署で最大限価値を提供し、さらには今行ってるプロジェクトをLIFULLの子会社化するという目標を持って突き進みたいと思います! 最後までお読みいただきありがとうございました。 ぜひ、少しでもLIFULLって面白そうだなと思っていただけたら幸いです。 さらにLIFULLに入社して、みなさんと一緒に働けるのを心から楽しみにしております! では!
技術開発部の相馬です。好きな UI フレームワークは Svelte です。 私が現在所属しているグループでは、弊社のメイン事業である LIFULL HOME'S における開発効率の改善などを行っています。 今回は、LIFULL HOME'S の Web フロントエンド(以降はフロントエンドと表記します)開発環境を、Node.js の資産を用いて近代化した話(以降は近代化と表記します)をご紹介したいと思います。 目次 はじめに 近代化として取り組んだ内容 Sass の導入 Rollup の導入 Babel の導入 PJ を進める上で立ちはだかる問題点 全ファイルのコンパイル前後における動作担保問題 文字コード問題 CSS ハックをパースできない問題 デプロイサーバー(テスト)で本番デプロイできちゃう問題 学び npm ci の必要性 npm modules の更新作業 おわりに はじめに LIFULL HOME'S は弊社でもっとも開発が盛んなアプリケーション(Repository)です。 Commit 数や Contributor 数は恐らく弊社でもっとも多く、また開発の歴史も非常に長い(9年超)です。 アプリケーションの基本構成は PHP(Symfony + Twig) + jQuery で、サーバーサイドでテンプレートを組み立て HTML を構築し、クライアントサイドでは jQuery を使って補助的に DOM の操作などを行うような形です。 近代化を行うにあたり、当時の課題点としては大きく 2 種類ありました。 CSS/JS の minify/bundle は 初回の HTTP リクエスト時に PHP 側からランタイムで生成される 初回リクエストのレスポンスが非常に遅くなってしまう CSS/JS の依存関係の解決(ファイルの読み込み)は Twig 上で行なっており、JS などを単体で見た場合に依存関係が分かりづらい CSS/JS は開発者が書いたコードがそのまま(minify/bundle を除く)ブラウザーへと出荷される CSS vendor prefix 手作業がつらい URL 関数などで参照する画像のバージョン管理(cache-buster の手動付与)がつらい Hex などのデザイン周りの値が毎回ハードコーディングでつらく、たまに間違っててつらい JS 変数スコープがつらい 3rd party ライブラリの取り扱いが煩わしい JS のみで静的な依存解決ができない 近代化として取り組んだ内容 前述した課題を解決するため、実際に取り組んだ内容としては以下の通りです。 Sass の導入 前述していた CSS に対する課題は、Sass を導入することでほぼ全て解決できましたが、画像などの cache-buster のみ、ライブラリだけではどうにもならなかったため、対象サーバに内包している画像については Sass のビルドの事前に MD5 を計算した結果をファイルへと出力しておき、 Sass の JavaScript API を使って CSS の URL 関数をオーバーライドし、MD5 の計算結果のあるものは Hash を付与して URL 関数を組み立てる ということを Sass の build 内で行うことで解決しました。 sass-lang.com この対応によって、既存のコードを修正することなく、また開発者のメンタルモデルも据え置きでハッピーだねという作戦です。関数名を別の名前(URL_USE_CB のような)にして、CSS 側をリネームするかという議論もあったのですが、オーバーライドするデメリットがほぼ存在しないことと、別の関数に切り出した後の周知徹底/実装忘れの懸念を考慮した結果、URL を上書きするという結論に至りました。 また、テンプレート(Twig)側から参照される画像についても、Twig 上に cache-buster 用のカスタムタグを作成し、その中で参照される img 要素には PHP から例の MD5 の計算結果ファイルを参照し、自動で付与されるように実装を加えました。 input {% cachebuster %} < img src = "/img/xxxx.png" > < img src = "https://s3..../foo.png" > {% endcachebuster %} output < img src = "/img/xxxx.png?v=xxxxxxxxxxxx" > < img src = "https://s3..../foo.png" > また、開発補助として StyleLint/Autoprefixer も合わせて導入しています。 Rollup の導入 JavaScript の bundler には Rollup を選択しました。 選択した理由としては、将来的に module/nomodule を採用した build プロセスを検討していたため、実装当時では ESM 形式の output の選択肢を取ることができる唯一の bundler だったということと、plugin の書きやすさなどが大きな要因です。 philipwalton.com 実装したい機能が満たされているかの確認として、bundler の機能比較はこのサイトを参考にするのが良いと思います。 bundlers.tooling.report また、弊社では長年 joo というライブラリを使ってクラス宣言のようなことを行い、その宣言単位でモジュールと呼んで開発を行なっていたのですが、そのモジュール間の依存関係の解決は global(window)の名前空間を頼りに行なっていました。 github.com イメージです // module/A.js def().as( 'myapp.module.A' ). it.provides( { method: function () { ~~~~~~~ } } ); // module/B.js def().as( 'myapp.module.B' ). it.inherits( window .myapp.module.A). it.provides( { method: function () { this ._super(); ~~~~~~~ } } ); しかし、これらの JS ファイルの読み込みは Twig 側から行なっていたため、そのページ(ルーティング)から読み込まれる Twig をまず特定し、そこから辿るようにして JS が読み込まれているかどうかを判定するしかファイル特定の術がありませんでした。また、Twig 上で依存関係(JS の読み込み及びそれらの読み込み順番)を解決しているため、ページによっては依存するモジュールを読み込んでおらずランタイムでエラーになってしまうということが発生してしまっていました。 <!-- ./somepage/head/javascript.twig --> {{ minify_js([ "module/A.js", "module/B.js" ]) }} <!-- ./somepage2/head/javascript.twig --> {{ minify_js([ "module/B.js" <!-- 依存する A を読み込めていない --> ]) }} この問題に対して、Twig 側で依存関係の解決をするのをやめ、ESM の import/export を使用して JS 上で静的に依存関係の解決を行えるようにしました。 Babel の導入 JavaScript のコンパイラーとして Babel を導入しました。 前述した joo などは Class シンタックスによって、this のスコープ問題で bind や this の再代入などを行なっている記述はアロー関数に書き換え可能です。Optional chaining や Nullish coalescing などが良い例ですが、コードの記述量に関してもモダンな構文を採用する方が少なくなる傾向にあり、コード全体として可読性の向上にも繋がると思います。 また、導入時には基本的にモダンシンタックスのダウングレードコンパイルの役割しか持たせませんでした。 というのも、Babel を利用する場合 @babel/preset-env を併用して browserlist などでサポートブラウザを指定し、未サポートのメソッドの利用があった場合に Babel が core-js などから polyfill コードを読み込むのが一般的なセットアップだと思われますが、この自動判定の仕組みには限界があり「該当するメソッド呼び出しがあった際に透過的に追加する」という問題が発生してしまいます。今回の一件で具体例を挙げると Array.prototype.find と jQuery の $.find を見分けることが出来ずに不要な polyfill コードが読み込まれてしまっていました。 この挙動によって、後述する自動テストに影響が出てしまうため @babel/preset-env のオプションで利用する polyfill は、エントリーとなるようなファイルで手動読み込みするような設定にしました。 @babel/preset-env · Babel Babel 自体は Rollup の plugin として通すようにビルドを組みました。 PJ を進める上で立ちはだかる問題点 これらのツールを適応/導入すること自体はそこまで難易度の高いものではありません。 しかし今回取り組んだ環境には 9 年を超える"重み"が存在し、PJ を進めるには同時にこれらを解決する必要がありました。 全ファイルのコンパイル前後における動作担保問題 前提として、これらの導入/移行計画は全ファイルを一括で行う必要がありました。 CSS で約 700 ファイル、JS で約 1500 ファイルほど存在していたのですが、それぞれコンパイル前後でファイル内容に差分が発生することが分かり、リリースするためには「コンパイル前後で生じた差分によって現状の期待値とずれてしまうことがない」ということを全ページの全機能で確認する必要がありました。 正攻法でいくには圧倒的にテスト工数が足りないことが分かっていたため、ブラウザ上での動作確認は念頭に置いておらず、「コンパイル前後で構文上に差分がないこと」を確認するという方針を取りました。 最終的には AST の一致を測る自動テストで 99.9 % 以上を担保することができました。Babel によってどうしても変えられてしまう if 文表記がいくつかあったので、そこだけは等価性を証明できなかったため、手動での確認を行いました。 無事何事もなく済んだのですが、リリースのためにはこの「何事も起きないはず」ということを証明する必要があったというのが最初の小話です。 文字コード問題 歴史のあるサイトのため、中には UTF-8 ではないファイルがそれなりにありました。 基本的にコンパイルされ作成されるファイルは UTF-8 として吐き出されてしまうため、日本語などでコメントが記述されている箇所を特定し、事前に文字コードと内容を修正する必要がありました。 CSS ハックをパースできない問題 いわゆる CSS ハックな書き方は、仕様上正式な構文ではないため、Sass の parser がエラーとして扱ってしまいます。 これらは全て以前サポートしていた IE 向けの記述ばかりだったのですが、幸いなことに現在弊社では IE については 11 のみをサポートしているため、これらの記述は本来不要であったため、事前に手動で削除する対応を行いました。 デプロイサーバー(テスト)で本番デプロイできちゃう問題 以前の弊社のデプロイ方法は、Jenkins からリリーススクリプトを手動実行し、魔法のシェルスクリプトによって本番サーバへと展開されていました。(現在は k8s にのっているため当時のこのフローは存在しません。詳しくは下記リンク先の記事をご覧ください) www.lifull.blog その魔法のシェルが動いている環境が、見出しで言及しているデプロイサーバで、これが非常に厄介ものでした。 歴史的な理由により、いわゆるテスト環境内で閉じた状態で動作確認を行える環境がありませんでした。 デプロイサーバ(本番)とデプロイサーバ(テスト)の環境的な差分はほぼ存在せず、デプロイサーバ(本番)で動いているスクリプトを コメントアウト/修正 して動作確認をする必要がありました。 デプロイの向き先をミスしてしまうと、テスト中のソースで本番サーバに展開されてしまう可能性があり、ここでの動作確認がこの PJ でもっとも精神的負荷のある局面でした。 PJ チーム一丸となり、円陣を組みながらシェルを叩いた(比喩表現)のが功を奏し、本番デプロイは免れ無事にテストは完了できました。 学び 今回の近代化を通して、いくつか学びがあったので最後にまとめさせていただきます。何かに役立てば幸いです。 npm ci の必要性 リリース当初、モジュール更新は npm i コマンドでセットアップしていたため、実行タイミングによっては package-lock.json に差分が生じてしまう構成になっていました。 開発に関しては、都度マージすればいいんじゃないくらいの温度感の話なのですが、ある日社内のステージング環境の一つが突然更新が途絶えたタイミングがあり、調査した結果「定期的に git pull をしてソースを最新化」しているような構成だったため、 package-lock.json に差分が生じたタイミング移行の git pull が失敗してしまっていました。 前述したリリーススクリプトでは「git 上で差分が無いこと(ステージングに何も存在しないこと)」を確認するステップがあり、こちらも大慌てで修正した次第です。 package-lock.json から忠実に再現したい場合に npm ci の必要性を認識させられました。 npm modules の更新作業 開発タイミングやブランチのマージタイミングによって、作業環境の node_moudles が最新の状態ではないことはそれなりに発生すると思いますが、その状態でビルドなどを実行した場合は必須モジュールが存在しないためにエラーとなってしまいます。 この際 Node.js への理解などがある場合は各自で npm ci を再実行してもらえば良いのですが、多くの開発者が出入りしている Reposiroty では全員がそうとは限らず、エラーの問い合わせも何度か発生していました。 この問題に対して、node_modules の更新作業を自動化するスクリプトを、開発コマンドの前に発火させることで対応しました。 イメージ " predev ": " node ./ check - module - install . js " " dev ": " run - p dev :*" スクリプトの内容としては、 package-lock.json の中身と node_modules 配下のモジュールのバージョン確認をするシンプルなスクリプトで、完全に一致していれば何もせず、差分があったタイミングで npm ci を別のプロセスで実行するような内容です。 これを Github Packages として公開し、社内の他のプロジェクトでも利用できるようにしました。 おわりに 今回の PJ のように歴史あるアプリケーションの改善やリファクタリングにおいては、技術的課題に対して導入/解決する技術選定の問題と、それらを取りまく開発環境などにまつわる技術的問題が重なるケースがあり、場合によっては後者が進行を鈍化させてしまうこともあると思います。 また、開発環境の改善をする際は「それらを利用する開発者」のことを第一に考え、導入チームのバイアスがかかり過ぎないよう全体として最善の選択になるように心がけています。 今回のプロジェクトは約 3 ヶ月の期間で開発 2 名 + QA 2 名という体制でしたが、障害なく完遂できました。サポートしてくれた優秀なメンバーや様々な調整/依頼を快く引き受けてくれた多部署のメンバーに恵まれたことも、今回のような部署を跨いだ大きめなプロジェクトを成功させるための大きな要因の一つだと思っています。 これからも、緩やかではありますが、着実な改善を続けていきたいです。