Laravel Meetup Tokyo Vol.13 Onlineで津崎がLT登壇しました

こんにちは、エンジニアの津崎( https://twitter.com/820zacky )です。

最近は燻製作りにハマっていて、よくベーコンを作っているのですが、 ベーコンの食べ過ぎでいよいよ自分が🐷ちゃんになりそうなフェーズに突入しております。

f:id:zacky2:20210308211814p:plain
ぶたちゃん

Laravel Meetup Tokyo Vol.13 Online

先日「Laravel Meetup Tokyo Vol.13 Online」というイベントに参加させていただきました。

「Laravel Meetup Tokyo」は、PHPフレームワークLaravelで開発を行うエンジニアを対象としたmeetupです。 今回およそ1年半ぶりの開催で、初のオンライン開催とのことです。

こちらのイベント詳細はこちら。 laravel-meetup-tokyo.connpass.com

今回は、以下のタイムテーブルで行われました。

f:id:zacky2:20210308212618p:plain

ytake ( https://twitter.com/ex_takezawa )さんのDDDのお話がとても白熱していたのと、 Hiroki Matsuo (https://twitter.com/handm871 )さんのLaravelあるあるネタがオーディエンスを沸かしていたが印象的でした。

Laravel Meetup Tokyoのおもひで

2019年の5月に行われた前回の「Laravel Meetup Tokyo」にも参加させていただきました。 当時は弊社に入社したばかりで、LaravelもPHPもど素人で、勉強会に参加するのもほとんど初めてという感じだったのを懐かしく思います。

スピーカーは、「吉田あひる」さん、「くまモンエンジニア」さん、「suthio」さん、「おかしょい」さん、「カンボ」さんで、 今でもLaravel界隈のイベントやTwitterでよく目にする有名な方達が登壇されていました。 今回はLT参加でしたが、同じように登壇できたことを嬉しく思います。

当時はLaravelに苦戦していた時期だったので、同じくLaravelを使っている方々とピザをつまみながら直接お話しできたのがとても楽しかったです。 コロナになってしまい、対面でお酒を飲みながら話すことができないのが、やはりちょっと寂しいですね。

発表内容

今回は「アクセス制御ライブラリ Casbinを使ってみた」というテーマでLTさせていただきました。 ご覧いただいた皆様ありがとうございました。

speakerdeck.com

最後に

エンジニア募集中です! 一緒に燻製やりましょう!

www.wantedly.com

www.wantedly.com

【AkitoTsukahara】中途入社しました。

f:id:tsukahara1991:20210226173143j:plain
 

みなさん、こんにちは!

今月よりM&Aクラウドにジョインしました塚原です。 ネット上では以下のアカウントで活動しております。よろしくお願いします!

AkitoTsukahara (akito) · GitHub
Akito.Tsukahara (@AkitoTsukahara) | Twitter

自分が入社するまでの経緯と、入社して感じたM&Aクラウドの魅力をご紹介させていただきます。

入社までの経緯

前職ではWebの受託開発の会社でWebエンジニアをしていました。
担当していたプロジェクトでは、設計・開発・保守、顧客折衝にまで及び、プロジェクトに関することは満遍なく携わってきました。
お仕事する上で、エンジニアとしてシステムを開発するだけではなく、クライアントの期待値を意識したコミュニケーションを心がけていました。

今回はCTOの荒井さんからお誘いいただき入社する運びとなりました。
お声がけいただいたきっかけは昨年のPHPerKaigi2020での登壇経験を評価いただいてのことでした。(外部に向けて発信するって大切ですね!)

入社して感じたM&Aクラウドの魅力

日々アップデートされるアーキテクチャとそれを支える環境

M&Aクラウドの魅力の1つは、入社を決めた理由にもなったM&Aクラウドプラットフォームのアーキテクチャです。前職の受託開発会社でもシステム設計・開発をしていましたが、スケジュールや予算の兼ね合いで、アーキテクチャを意識した開発や技術的負債を解消する機会作ることが難しく、歯痒い思いをしておりました。。。

一方で、M&AクラウドではDDD や CQRS などの考え方をシステムに取り入れて開発されており、ビジネスの複雑性と技術的な複雑性に応えられる設計になっています。また、現在進行中のプロジェクトでも実装方針の意見交換が活発に行われており、メンバー全員がシステムの設計・開発に高い関心を持って、開発を進めていると感じました。 システムの完成度の高さはもちろんのこと、アーキテクチャに高い関心を持つエンジニアメンバーがいることも素敵だと感じました。

エンジニアが主導する「リリース⇆計測&ヒアリング」のサイクルがある

開発チームでは毎日サービスのKPIを確認するMTGがあります。直近のリリースした機能がユーザに使われてるのか、さらに改善できるところは無いかと議論しています。
また、営業の方に同行してクライアントのアポイントメントに参加する機会があります。数値データだけでなくサービスを利用するクライアントの意見に触れる中で、サービス改善の気付きや学びを得る為の取り組みが行われています。
より良いサービスをリリースし続けていく仕組みが根付いていることもM&Aクラウドの魅力だと感じました。

これからは

M&Aは専門知識が多く、この会社の事業ドメインを理解するまでにやらなければいけないことが盛り沢山で大変ですが、新しい見識が得られる機会を楽しんでいこうと思っています。

開発チームの行動指針「全員インフルエンサー」を自分も体現できるように情報発信を増やしていきます! (自分も含めて、開発チームは全員PHPerKaigi2021に登壇しますので、ぜひご覧ください。)

引き続き、技術力をさらに磨き、事業ドメインの知識を身につけて、1日でも早くみなさまにM&Aクラウドのさらなる活躍、素敵なサービスをお届けできるように頑張って参ります!

最後に

M&Aクラウドでは、エンジニアを募集中です!!興味がありましたら、是非以下からご応募ください!

www.wantedly.com

www.wantedly.com

開発のプロジェクト管理をGitHub ProjectsからJiraに移行しました

f:id:kazuhei0108:20210219113421p:plain

こんにちは。M&Aクラウドのかずへいです。

今年に入って開発のプロジェクト管理をGitHub ProjectからJiraに移行しました。

どのような意図で今回Jiraに移行したのかを紹介させてもらえばと思います。

GitHub Projectsを利用していたときの課題

GitHub Projectsを使い始めた時はホワイトボードの物理的なカンバンの代わりとしてだったと思います。その時のエンジニア数はまだ3名で、 GitHub Projectsの機能でも特に問題は感じていませんでした。

元の運用は、

  1. issueのマイルストーンバックログを作る。マイルストーン内ではissueが優先度順に並べられる。ストーリーポイントは数字のラベルをissueにつける。
  2. マイルストーンから次のスプリントでやる分だけをスプリントマイルストーンに移動、GitHub Projectsに紐づけてカンバンに表示させる。
  3. スプリント開始!プルリクエストにissueを紐付けておくとプルリクエストがmergeされたときにissueもdoneになって便利。

というシンプルな形でした。

しかし、メンバーが増え、リポジトリが増え、管理するタスクが増えるとこの運用がつらくなってきました。

つらかった点

GitHub Projectsでの運用で辛かった点には以下の様なものがありました。

  1. マイルストーンGitHub Repositoryに紐付いているissueをまとめる機能なので、複数のRepositoryをまたいで一つのマイルストーンにissueを集めることが出来ない。
  2. issueが入れ子関係を持てないのでlabelで管理することになり、labelが増殖する
  3. 消化したストーリーポイントを数えるのがめんどくさい
  4. asigneeをつけ忘れると誰がやってるか分からない
  5. バックログの整理が大変(GitHubマイルストーンは複数のissueをドラッグアンドドロップで動かせない😢)
  6. 何がいつリリース予定なのか整理するのが大変

たまたま、エンジニア以外からもタスク管理ツールを選定して導入して欲しいという話があったので、このタイミングでJiraを全社導入し、開発チームもJiraに移行することに決めました。

Jiraの導入

今回Jiraに乗り換えたことで上記の問題が全て解決されました!

  1. Jiraのバックログリポジトリに関係無いので、複数リポジトリにまたがるストーリーも管理できる
  2. epicを親のissue、storyを子のissueとすることによって、issueを入れ子で管理
  3. スプリントのベロシティを自動で計測してくれるし、epicに属するissueのポイントの合計もすぐ表示される
  4. カンバン上の移動にルールを追加することが出来るので入力漏れがない
  5. バックログのissueは複数選択出来、簡単に動かせる。
  6. いつ何がリリースされるのか一目瞭然

いくつかピックアップして説明していきます!

スプリントのベロシティを自動で計測してくれるし、epicに属するissueのポイントの合計もすぐ表示される

f:id:kazuhei0108:20210217192440p:plain
Jiraのベロシティーチャートの画面

スプリントの機能を使って、スプリントの開始、終了を実行すると毎回の消化ポイントを自動で集計してレポートに出してくれます。 以前はissueについてるlabelのストーリーポイントを数えていましたが、そんなことをする必要はありません!

また、epicに属するissueの合計の見積もり、完了、残りのストーリーもすぐ表示されるので、大きな機能がどれくらいでリリースされそうかわかります。

f:id:kazuhei0108:20210217193332j:plain

カンバン上の移動にルールを追加することが出来るので入力漏れがない

f:id:kazuhei0108:20210217194559j:plain
左下に足りない入力項目が出ています

カンバン上でTO DOからWIPにカードを移動させようとすると、移動するためのルールを満たしているかどうかのチェックが走ります。 今は、担当者とリリース予定の日付を入力しないとWIPに移動出来ないようにしています。

バックログのissueは複数選択出来、簡単に動かせる。

f:id:kazuhei0108:20210217194921j:plain
ドラッグアンドドロップで簡単にissueを動かせます

Jiraのバックログではスプリントとバックログが同じページに表示されており、簡単に移動させることが出来ます。

いつ何がリリースされるのか一目瞭然

f:id:kazuhei0108:20210218194616j:plain
リリース機能

Jiraのリリース機能によって、リリース予定日ごとに何をリリースするかをまとめるようになりました。 GitHubのPRと連動しているので、もうマージされたのか、まだレビュー中なのかというところまで細かく分かるようになっています。

移行手段

すでにGitHubに大量のissueが積まれており(500個以上!)、それらを整理してJiraに移行する作業はどう考えても苦行に思えました。GitHubAPIでissueをダウンロードしてcsvに整形してJiraにアップロードするというのが正攻法のようでしたが、そもそも全部移行する必要は無いのではと考えたため移行したいものだけ移行できるZapをZapierで作りました。

GitHub issueのコメントにjira移行と書くとJiraのストーリーを作ってくれた上で元のGitHub issueのURLを説明文に入れてくれるというすぐれものです。

f:id:kazuhei0108:20210218194141p:plain
zapierのzap作成画面

移行してみて

Jiraに移行したことにより、これから更にチームメンバーが増えたり、Repositoryの数が増えたりしても大丈夫な体制を構築できたと自負しています。 また、想定していたわけではないのですが、開発のビジネス要件が全てJiraにまとまるようになり、PMとのコミュニケーションが円滑になりました。 元々はGitHub Issue上でやっていたことですが、GitHub上で管理すると、どうしてもコードについてのアレコレを書いてしまいがちになったり、どこのRepositoryに起票したらいいんだ、となったりしていました。 これがJiraとGitHubで使い分けられたことにより、PMはJiraのみ見れば良い状態になりました。

まだ導入して1ヶ月ほどですが、開発チームの状態に合わせてこれからカスタマイズを続けていきます!

Nuxt.js化計画vol.5

f:id:kubotak:20200317182842j:plain
Nuxt.js化計画vol.5

第5弾です。

Nuxt.js化計画の概要についてはvol.1を参照ください。 また、過去のシリーズも通してリンクしているのでぜひ御覧ください。

tech.macloud.jp

徐々に弊社アプリケーションのフロントエンドもLaravelからNuxt.jsに移行しています。
今回は成約事例一覧M&Aお役立ち資料ページがめでたくNuxt.js化されました。

実は今までNuxt.jsによる動的なルーティングは使っていませんでした。
今回のリニューアルのタイミングで個別のページはNuxt.jsによるルーティングを利用しています。

成約事例一覧

https://macloud.jp/interviews

弊社のプラットフォームであるM&Aクラウド上での成約事例をインタビュー記事として紹介しているページです。
リニューアル前は絞り込み機能はありませんでしたが、今回Nuxt.js化する際に追加されました。
記事についているタグで絞り込むことができます。

M&Aお役立ち資料

https://macloud.jp/documents

M&A時に必須である資料のフォーマットの配布や、弊社調べによる有益な資料をダウンロードすることができるページです。

NotFoundの扱いについて

ここからは少し技術的な話をしたいと思います。
今回のリニューアルから新たにNuxt.jsの動的なルーティングを利用しています。
ここで注意しなくてはならないのは存在しないページの処理についてです。
ページのコンテンツはHTTP APIによってそのリソースが存在するか確認します。
厳密に言うとリソースが存在することを確認するというよりも、リソースを取得できない場合にそのリソースが存在しなかったという結果をHTTPのステータスコードで表現することが一般的だと思います。

Nuxt.jsではaxiosというHTTPクライアントライブラリを利用してHTTP APIからデータを取得しています。
axiosではレスポンスからHTTPのステータスコードステータスコードが取得できますので、404だった場合はNuxt.jsのerror関数を実行することで404画面を表示することが出来ます。

error({ statusCode: status })

※axiosは200系以外のステータスコードをすべてaxiosErrorという例外で扱われます。適宜設定を替える必要があります。 https://github.com/axios/axios/blob/master/lib/defaults.js#L79-L81

しかし、弊社の場合は取得した値を一旦store(Vuexによるグローバルにアクセスできるデータストア)に格納して、そのstoreからデータを取得するロジックになっています。
そのため、axiosで404の場合はエラーにはせずに、storeからの取得の際にデータがない場合に独自の例外を発生させて404を表現しています。

try {
  useInterview(store).getInterview()
} catch (e) {
  if (e instanceof NotFoundError) {
    error({
      statusCode: e.statusCode
    })
  }
}

SSRCSRに跨ったシャッフル処理

Nuxt.jsはSSR(サーバーサイドレンダリング)とCSR(クライアントサイドレンダリング)によるユニバーサルなアプリケーションを作ることが出来ます。
この挙動に関してハマリポイントがあったので共有します。
それは、シャッフルを利用したDOMの操作です。

対象の箇所は以下のようなカルーセルです。 f:id:kubotak:20210212151612p:plain ここでは表示する度に要素がシャッフルされる仕様です。
配列をシャッフルしてその配列をeachで表示する至ってシンプルなロジックです。

問題になったのは、SSR時にシャッフルされてレンダリングされた結果と、表示時にCSRでシャッフルしてレンダリングが異なるため一部のレンダリングがずれた事にありました。
imgタグのsrcがCSR時に変わっていなくてタイトルと画像が食い違うという事象がありました。

そのため、シャッフル要素に関しては都度表示が変わるためSEOとしても対応不要としてCSR時のみ実行されるように変更しました。
Nuxt.jsではこの様にSSRCSR時の挙動も把握する必要があると痛感しました。

最後に

M&Aクラウドではエンジニアを絶賛募集中ですので、興味がありましたら是非以下からご応募ください!
Nuxt.jsの挙動を熟知している方は大歓迎です!

www.wantedly.com

www.wantedly.com

Vue Component をサイトの各所で別々の条件によって表示する機能を Nuxt.js + TypeScript で実装する

Nuxt.js

Nuxt-2.14 TypeScript-3.7

こんにちは。エンジニアの濱田( @hamakou108 )です。

先日、あるモーダルをサイトの各所で別々の条件によって表示する機能を Nuxt.js + TypeScript で実装する機会がありました。 このときの設計が他の場面でも応用できそうな知見としてまとめられそうだったので、この記事で紹介したいと思います。

話さないこと

  • Vue.js (Composition API), Nuxt.js, TypeScript の基本的な使い方
  • 時間計算などの細かいアルゴリズムの実装

背景

M&Aクラウドでは M&A に役立つ資料の一部のダウンロードや買い手企業様とのマッチング機能を会員登録された方向けに提供しています。 サイトのコンテンツが気になった方に少しでもリーチするため、会員登録を促すモーダルを特定のタイミングで表示しています。 勿論モーダルが何度も表示されてしまうようでは UX を損ねるため、どのページでどのような行動を行ったときにモーダルを表示するか様々な条件によって制御されています。

設計

様々な種類の条件を手続き的に実装してしまうと、保守性に乏しいコードが出来上がってしまうのは目に見えています。 そこでまず幾つかの具体的な要件を取り上げ、それらを抽象化して設計を考えてみることにしました。

今回の要件としては例えば以下のようなものがありました。

  • ページ A の訪問時、最初に訪問してから24時間経過していたらモーダルを表示する
  • パーツ B のクリック時、累計3回目のクリックであればモーダルを表示する

これらの要件を分解してみると「ページ表示やクリックなどのイベント」と「経過時間や回数などの表示条件」の2つの要素の組み合わせという形に抽象化できそうです。 このように分離しておくと、後々「パーツ B のクリック時、最初に訪問してから24時間経過していたらモーダルを表示する」のように組み合わせが異なる要件が追加されても柔軟に対応できそうです。

よって今回はイベント表示条件という2つのモジュール、そしてそれらを利用して Vue Component にモーダルの表示可否の判定や表示イベントの保存を行う手段を提供する Manager というモジュールの主に3つに分けて設計しました。

実装

Event

イベントに関するモジュール event.ts は以下のようになりました。

// ~/compositions/event.ts

export type Event = VisitEvent | ClickEvent

export type VisitEvent = {
  key: 'visit'
  page: string
}

export type ClickEvent = {
  key: 'click'
  page: string
  element: string
}

型を判定するための key やページやパーツの名前といった型定義のみを持っています。 Vue Component でイベントを定義するときや Web Storage に保存している過去のイベント情報を取得するときにこれらの型情報を参照します。

Condition

表示条件に関するモジュール condition.ts は以下のようになりました。

// ~/compositions/condition.ts

import { CustomDate, CustomDuration } from '~/foo/bar/date.ts' // 日付関連の型定義を持つ適当なファイル

export type Condition = TimeCondition | CountCondition

export type TimeCondition = {
  key: 'time'
  durationList: Array<CustomDuration>
}

export type CountCondition = {
  key: 'count'
  countList: Array<number>
}

type CurrentState = CurrentTimeState | CurrentCountState

type CurrentTimeState = {
  key: 'time'
  firstEventOccurred: CustomDate | null
  lastModalShowed: CustomDate | null
}

type CurrentCountState = {
  key: 'count'
  count: number
}

export function useCondition(condition: Condition) {
  const canShowUnderTimeCondition = (
    currentState: CurrentTimeState
  ): boolean => {
    // イベント発生からの経過時間からモーダル表示可能か判定するロジック
  }

  const canShowUnderCountCondition = (
    currentState: CurrentCountState
  ): boolean => {
    // イベントの発生回数からモーダル表示可能か判定するロジック
  }

  const canShow = (
    currentState: CurrentState
  ): boolean => {
    if (currentState.key === 'time') {
      return canShowUnderTimeCondition(currentState)
    } else if (currentState.key === 'count') {
      return canShowUnderCountCondition(currentState)
    }
  }

  return {
    canShow
  }
}

モーダルの表示条件に関する幾つかの型定義に加え、モーダルの表示可否を判定するロジック useCondition を持っています。 表示条件を定めて useCondition の引数として与えておき、 canShow の引数として現在の状態を与えると、これらの情報に基づいて表示可否を判定します。

モーダル表示可否を判定する具体的なアルゴリズムの実装もこれはこれで面白いのですが、冗長になってしまうので割愛します。

Manager

イベントと表示条件を利用して Vue Component にモーダルの表示可否の判定や表示イベントの保存を行う手段を提供するモジュール manager.ts は以下のようになりました。

// ~/compositions/manager.ts

import { useStorage } from '~/foo/bar/storage.ts' // Web Storage 関連の型定義やロジックを持つ適当なファイル
import { Event } from '~/compositions/event'
import { Condition, useCondition } from '~/compositions/condition'

export function useManager(event: Event, condition: Condition) {
  const storage = useStorage(event, condition)

  const canShow = (): boolean => {
    const currentState = storage.getCurrentState()
    return useCondition(condition).canShow(currentState)
  }

  const saveEvent = () => {
    storage.saveEvent()
  }

  return {
    canShow,
    saveEvent
  }
}

特筆すべき点はないですが、補足として useStorage はイベントの発生回数やモーダルの表示時刻などの情報を Web Storage から取得または保存する composition です。 メソッドの引数として渡された condition に応じて必要な情報を取得・保存する役割を持ちます。 詳細な説明は本筋から逸れるので割愛します。

Vue Component

モーダルを表示するページの Vue Component は以下のようになりました。

// pages/some-page.vue

<template>
  <div>
    <some-modal :can-show="canShow"></some-modal>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, onMounted, reactive, ref } from '@vue/composition-api'
import { customDuration } from '~/foo/bar/date.ts'
import { VisitEvent } from '~/compositions/event'
import { TimeCondition } from '~/compositions/condition'
import { useManager } from '~/compositions/manager'
import SomeModal from '~/components/some-modal.vue'

export default defineComponent({
  components: {
    SomeModal
  },
  setup() {
    const manager = ref<ReturnType<typeof useManager> | null>(null)

    onMounted(() => {
      // イベント: とあるページの訪問
      const event: VisitEvent = {
        page: 'some',
      }

      // 表示条件: 最初のイベント発生から24時間または72時間経過後
      const condition = reactive<TimeCondition>({
        durationList: [
          customDuration(1, 'days'),
          customDuration(3, 'days')
        ]
      })

      manager.value = useManager(event, condition)
      manager.value.saveEvent() // ページ表示イベントの発生情報を保存
    })

    const canShow = computed(() => {
      return manager.value !== null ? manager.value.canShow() : false
    })

    return {
      canShow
    }
  }
})
</script>

「とあるページの訪問時、最初に訪問してから24時間または72時間経過していたらモーダルを表示する」という条件を表現するため、 useManager にはそれぞれ対応するイベント、発生条件のオブジェクトを設定しています。 そして computed を使って manager オブジェクトが生成されたタイミングでモーダルの表示可否を判定し、子コンポーネントのモーダルに props としてその情報を伝えます。

今回のイベントはページの表示なので、ライフサイクルフック onMounted を使って useManager を呼び出しました。 クリックイベントの場合は対象のコンポーネントの emit イベントに対してコールバック関数を定義し、その中で useManager を呼び出すように実装します。

このように useManager を利用して Vue Component 各所でモーダルの表示可否を制御できるようになりました!

最後に

M&Aクラウドではエンジニアを絶賛募集中ですので、興味がありましたら是非以下からご応募ください! Nuxt.js や TypeScript に知見のある方は特に大歓迎です!!

www.wantedly.com

www.wantedly.com

「レバレッジ指向」という開発チームバリューについて

f:id:zacky2:20210126110429j:plain こんにちは、エンジニアの津崎です。

前回の記事で、「全員インフルエンサー」という開発チームのバリューについて紹介しました。 今回もその流れを汲んで開発チームのバリューの紹介をしていこうと思います。

開発チームのバリューについて

M&Aクラウドの開発チームには行動指針として以下の三つがあります。

このバリューは、今月(2021年1月)に開発チームによる合宿*1により策定されました。

今回は、「レバレッジ指向」というバリューについて紹介します。

レバレッジ指向」というバリューについて

レバレッジ指向」とは今回のバリュー策定で生まれた造語です。

レバレッジ」という言葉は、よくFXの話で、「レバレッジの倍率が〜」という使われ方すると思いますが、 このバリューでは、「小さい労力で大きな成果を出す」という意味として使っています。

スタートアップにおけるソフトウェア開発では、「価値を素早く届けること」と「スケールできること」を求められますが、このバランスはとても難しいです。 「価値を素早く届けること」を追いすぎると、技術的負債が増えて価値を素早く届けられなくなったり、「運用でカバー」的作業が増えて開発する時間を失ってしまいます。 一方で「スケールできること」を求めすぎると、機能開発するのに時間がかかり過ぎて商機を逃してしまいます。 このバランスはプロダクトの成長具合によって変化することが求められます。

弊社のプロダクトは、順調にユーザー数を伸ばしており、「スケールできること」を重視するフェーズに差し掛かってきました。 ユーザ増加だけでなく、エンジニアの増加や、プロダクトの複雑性の増大にも対応していく必要があります。 そんな背景があり、より長期的な目線を持って「レバレッジ」の効く選択をしようという方針が固まりました。

「指向」という言葉も議論がありました。 「しこう」には「指向」の他に、「思考」と「志向」がありますが、 「指向」は「方針の選択の局面でどちらの方向性でいくか」という判断の際に使いやすいワードであり、 「オブジェクト指向」といったエンジニアに馴染みのあるワードであるため選ばれました。

つまるところ、「レバレッジ指向」という言葉は、『長期的な目線を持って「レバレッジ」の効く方針を「指向」していこう。』という意味を表しています。

レバレッジ指向」の体現

レバレッジ指向」がバリューに設定されてからまだ日が浅いですが、早速レバレッジ指向を体現する開発を行っています。

コードによるコード生成

データに対応して単純なコードをたくさん作成しなくてはいけないケースにおいて、コードを生成するコードを実装することで対応しました。 コードによるコード生成を行うことで、今後、データに追加、削除があっても簡単に対応することができます。 少ない労力で大きな成果を出す、レバレッジ指向な実装ができたと思います。

この話はPHPer会議での登壇を予定していますので、そちらの方でお話しさせていただければと思います。

fortee.jp

NoCodeツールによる人力の排除

現在、開発チームではIssueの管理をGitHubからJiraへ移行している最中です。 必要なIssueだけJiraに移行するため、 GitHubのIssueに「Jira移行」とコメントすると自動でJiraに同じIssueが作成される仕組みをZapierを使って作りました。 こちらも人力作業を排除するレバレッジの効いた仕事といえます。

リリーススクリプトの改修

弊社のリリーススクリプトは最近になって特定条件でうまく動かない問題が発生していました。 運用でカバーできるものはなかなか根本修正されず、運用でカバーが常態化してしまうことはよくあるかと思います。 今回、「レバレッジ指向」がバリューに設定されたため、運用でカバーを排除する修正がすぐに行われました。

最後に

レバレッジ指向」を体現してプロダクトを爆伸びさせたいエンジニアを積極採用中です。 カジュアル面談もやっておりますので、お気軽にご連絡ください💪

www.wantedly.com

www.wantedly.com

*1:合宿とは名ばかりの泊まり込みのない長時間ミーティング

PHPerKaigi 2021 と「全員インフルエンサー」という開発チームバリュー

f:id:kazuhei0108:20210118230904p:plain

こんにちは。エンジニアの鈴木(@yamotuki)です。

昨年はPHPカンファレンス2020にチーム全員でプロポーザルを出し、4人が登壇することになりました。
今回は PHPerKaigi 2021 にまた全員でプロポーザルを出した話です。この記事を書いている時点ではまだ採択されたか分かりませんので、もし採択されたら追記します。

追記:
5人がプロポーザルを出して、3人が20分枠、2人がLTで発表することになりました。紹介タイトルの先頭に[採択]または[不採択]をつけました。
頑張って発表準備し為になる発表をしたいと思います。お楽しみに!

開発チームのバリューについて

M&Aクラウドの開発チームには行動指針として以下の三つがあります。

これらの詳細はまた別の記事で紹介されると思いますが、今回は全員インフルエンサーについて書きます。

経緯

以前から各種勉強会で活発に発表し、コミュニティの発展に貢献していた久保田さん(@kubotak_public)が弊社に2019年10月にジョインされました。

CTOの荒井はかねてからチーム作りの重要性を感じており、久保田さんジョインと同時期にこのブログを始めることになりました。

運用

エンジニアは情報発信が大事、と近年のWeb界隈ではよく言われていると思います。目的はスキルアップだったり、プレゼンス向上だったり、採用だったり、チーム技術交換だったり、色々あると思います。

しかし、実情としては10人に1人、会社によっては100人に1人くらいしかブログを書いたり登壇したりすることはないのではないでしょうか。 弊社ではより強いチームにするために「全員」が情報発信を積極的に行っていくことに決めました。

それが開発チームのバリューとして2019年11月の開発合宿(という名の長時間ミーティング)で「全員インフルエンサー」として言語化されました。

この行動指針に則って「全員毎週 qiita 記事投稿」(現在は不定期)や、PHPカンファレンス 2020への「全員プロポーザル宣言」が実現されました。 まだエンジニア5人のチームですが、これが10人100人になった時にもこの行動指針を続けられたなら唯一無二のチームになれると信じています。

PHPerKaigi 2021への全員プロポーザル

次の大きな祭りはなんだ?と問われれば、PHPer なら PHPerKaigi 2021 が思い浮かぶのではないでしょうか。 今回はCTOが明示的に「全員プロポーザル宣言」を出さなくても、当たり前のように全員が出すことになりました。ちなみに"沈黙"をしてひよっているのが私です。

f:id:yamotuki:20210115174429p:plain
全員プロポーザル宣言(自発)

さて、どのようなプロポーザルになったのか、紹介していきたいと思います。

[採択]モックの泥沼から脱却するために、あえてDBにつないでテストしている話

fortee.jp

CTOの荒井さんによる、前回PHPカンファレンスで話せなかったリベンジです。 テストを美しく、効率的に書くための工夫について紹介されています。 モックの泥沼に入るとテストのどこが何を表しているか分かりにくいですよね。 モック地獄から救ってくれた荒井さんに清き一票を。

[採択]Laravel のメール認証の内部実装を掘り下げる

fortee.jp

Vimマスターの濱田さんによるプロポーザルです。 今回はフレームワークの内部実装を掘り下げていこうという渋い内容になってます。 プロダクトが立ち上がった時には要らなくても、成長すればじきに必要になってくるメール認証。 案外幅広い開発者に関係するところだと思うので、見たい人はぜひ清き一票を。

[不採択]Casbinを用いたアクセス制御入門

fortee.jp

このプロジェクトを通して設計作業を学びたい!という積極性No.1の津崎さんによるプロポーザルです。 権限管理は自分で丸々実装すると大変かと思いますが、幅広い言語で使用可能な Casbin というライブラリを使うと比較的簡単にできるそうです。 弊社のプロダクトではサービスの管理ツールでCasbinによるアクセス制御を導入しているところです。 こちらも会社成長すると"あるある"の機能なので、ご期待ください。

追記:
このプロポーザルは残念ながら不採択でしたが、このブログが公開された後に出した以下の発表で採択されました。

fortee.jp

[採択]ある日突然、Laravel Queue Workerが壊れた

fortee.jp

「全員インフルエンサー」の始祖、久保田さんによる発表です。 年末に開発チームが襲われた Worker の障害の根本対応の想定される工数は、チーム全員でやっても1週間(辛い)。 そこで「レバレッジ指向」の久保田さんが華麗な解決策を編み出し半日で解決してしまった話です。 その発想の源が垣間見れるのか。楽しみですね。

[採択]プログラマ三大美徳を実現するデプロイフローを目指して

fortee.jp

私の発表です。 デプロイめんどくさいです。昔はもっと面倒でした。 フローなんてあってなかったようなプロダクト初期から、ミスなく楽するために徐々に改善してきた軌跡を共有します。

終わりに

いいなと思ったら、清き一票をお願いします!

弊社では一緒に「全員インフルエンサー」を実現してくれるチームメンバー募集してます。

www.wantedly.com

新年の挨拶2021

あけましておめでとうございます。M&Aクラウドのかずへいです。

去年から大変な時期が続いておりますが、M&Aクラウドは順調に成長しておりまして、 仲間の増加に合わせて、この度オフィスを八丁堀から新宿御苑に移転することとなりました。

f:id:kazuhei0108:20210112134651j:plain
新オフィスのエントランス

せっかくオフィスを移転したのですが、今はほとんどのメンバーはリモートワークをしております。

緊急事態宣言も出ておりますが、より柔軟に、仕事の進め方、コミュニケーションの仕方を変えながら、 いつかまたオフィスでみんなで集まってわいわいする時を思い描きながら頑張っていきたいと思います。

今年の抱負

私の今年の抱負は「発進・発信」です。

今年はM&Aクラウド開発チームとしても飛躍の年にしたいと思っていまして、そのためにはたくさんの新しいことにチャレンジし、 その取り組みを外部に出していくことでチーム全体の増強につなげていきたいと思っています。

また、メンバーの人数が増え、コミュニケーションが少なくなると、メンバー間のずれが起きやすくなりますが、 それを防ぐためには、常に理想を発信し、定着させていくことが必要です。

このブログを始めるときに自分が思い描いたループを意識して、ガンガン回していけたらと思います。

f:id:kazuhei0108:20191024121803p:plain
取り組みを発信して採用につなげる図

それでは本年もよろしくお願いいたいします。

【Laravel】Laravelのスケジューラー経由で実行しているコマンドのログを詳細に取る

こんにちは、M&Aクラウドの荒井です。

以前に弊社の津崎がElasticBeanstalkのワーカー環境の導入について紹介させていただきました。

tech.macloud.jp

弊社ではその後様々な定期実行のスケジュールを移行し、安定稼働に至っています!

ワーカー環境で実行されるコマンドが増えるにつれて、コマンドの実行状況をより詳細に確認したいというニーズが出てきました。

以下でどのように対応したのかを説明します。

Workerサーバーでスケジュールを実行する

弊社ではlaravel-aws-workerを利用しているので、Laravelのスケジュールの機能でElasticBeanstalk Workerからスケジュールを実行することができます。

GitHub - dusterio/laravel-aws-worker: Run Laravel (or Lumen) tasks and queue listeners inside of AWS Elastic Beanstalk workers

app/Console/Kernel.phpに以下の様に書いていくことでコマンド実行の設定ができます

<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{

    /**
     * Define the application's command schedule.
     *
     * @param \Illuminate\Console\Scheduling\Schedule $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {

        $schedule->command('some-command')
            ->weeklyOn(1 /* 月曜日 */, '10:00')
            ->withoutOverlapping()
            ->onOneServer()
            ->runInBackground();
     }
}

?>

このようにして、月曜の10時にsome-commandを実行するという設定ができます。

課題

ここで問題になるのは、このコマンド実行はHTTPリクエスト経由の実行ではなくconsoleからのphpの実行だということです。Webサーバー経由での実行ではないので、Apacheアクセスログ等は出ません。当たり前といえば当たり前なのですが…。標準出力にコマンドを実行したときのoutputが出るだけです。

しかし、これでは実際にコマンドがどのように実行されているのか細かく見ることができません。よって以下のような解決策を取っていきました。

解決策

実行時の標準出力相当のものをログに書き出す

コマンドのコードには、ログへの出力をするコードは書かれていませんでしたが、コマンドを人間が実行するためのテキスト等を出力するコードはちゃんと書かれていたのでこれを応用することにしました。command実行時の出力を同時にログファイルにも出力します。

Illuminate/Console/Command.phpを継承して以下のようなLoggableCommand.phpクラスを定義します。

<?php
declare(strict_types=1);

namespace App\Console\Commands\Base;

use Illuminate\Console\Command;
use Illuminate\Log\LogManager;

abstract class LoggableCommand extends Command
{
    public function info($string, $verbosity = null)
    {
        parent::info($string, $verbosity);
        $this->getLogger()->info($string);
    }

    public function warn($string, $verbosity = null)
    {
        parent::warn($string, $verbosity);
        $this->getLogger()->warning($string);
    }

    public function error($string, $verbosity = null)
    {
        parent::error($string, $verbosity);
        $this->getLogger()->error($string);
    }

    /** コマンド実行時までLoggerが生成されないので、ログに必要なときに都度生成 */
    protected function getLogger()
    {
        $application = $this->getLaravel();
        return $application->make(LogManager::class)->channel('command_log');
    }
}

?>

このクラスを継承することにより、コマンドを実行するとログファイルにコマンドの標準出力に出ているものと同じものがLogのformatにwrapされて書き出されます。 ログは以下のようにcommand_logのchannelに出力されます。

time:2020-11-24 21:37:15 level:INFO  message:START some-command params:[]

Exceptionをキャッチする

コマンド実行時に発生するExceptionは何も設定しないとWebサーバー経由で起動されているPHPのerror_logと同じ箇所に出力されてしまいますが、先程のエラーと同様にcommand_logのchannelにコマンドログは統一したいです。

これにはapp/Exception/Handler.phpをカスタマイズすることで対応します。

<?php

namespace App\Exceptions;

class Handler extends ExceptionHandler
{

    public function renderForConsole($output, Exception $e)
    {
        // コンソールコマンドはスケジューラーから実行される前提
        $logger = $this->container->make(LogManager::class)->channel('command_log');
        $logger->error("{$e->getMessage()} | {$e->getTraceAsString()}");
        parent::renderForConsole($output, $e);
    }

}

?>

renderForConsoleを実装することによってConsoleでのエラーのみこちらの関数が呼ばれるようにできます。

まとめ

Laravelのスケジューラー経由で実行されるコマンドのログを詳細に取る方法を説明しました。

  • コマンドの標準出力に出力するとともにログに出力することで簡単にログへの書き込みが実装できました。
  • app/Exception/Handler.phpをカスタマイズすることでExceptionが発生した場合にもログ出力することができました。

これでスケジューラーから実行されるコマンドの内容も詳細にログに記録することができます。

最後に

M&Aクラウドでは、エンジニアを募集中です!!興味がありましたら、是非以下からご応募ください!

www.wantedly.com

www.wantedly.com

Nuxt.js化計画vol.4

こんにちは、こんばんは、kubotak(@kubotak_public)です。

早いものでシリーズも第4回となりました。 前回の記事は以下です

tech.macloud.jp

今回はログイン周りの仕様変更があり、それに伴いNuxt.js化を行いました。

新規登録の仕様変更

新規登録に関して仕様変更しました。
今までは会社を売りたい・資金調達したいユーザーは弊社サービスに登録する際に会社情報の入力を行う必要がありました。
この問題として、例えば「直近では売却意欲は低いがとりあえず最新情報の通知だけ受け取っておこう」といったカジュアルなニーズに答えることができません。

新たに仮登録というワンステップ置いた状態が追加されました。
まず会員登録すると仮登録という状態のアカウントが発行されます。
仮登録はメールアドレスとパスワード、またはFacebook認証で登録が可能となり、登録する際の敷居が下がりました。
続いて本登録(今までの会社情報入力フロー)を行うことで弊社サービスの機能をすべて利用可能になります。

登録・ログイン周りの変更

今まではLaravelで作られていた登録周りのフロントエンドを今回の改修でNuxt.js化しました。

以前から紹介しているようにフォームの各パーツはそれぞれコンポーネント化しているのである程度の形までデザイナーを介さずに実装することが出来ました。
また、これらのインプットパーツはVeeValidateを利用したインタラクティブなバリデーションが行なわれます。

ログインフォーム

登録・ログインのPOST

弊社のログインの仕組みはLaravelの標準のAuthを利用したCookieによるログイン判定となります。
これを提供するためにはブラウザにLaravelで発行したCookieを付与する必要があります。 LaravelとNuxt.jsで分離されていると、このCookieの付与ができません。

そのため、Nuxt.jsのフォームからは通常のPOSTフォームを設置しLaravelのPOSTエンドポイントに遷移して、そこでCookieを付与するようにしています。
つまり、新規登録やログイン時はSPAではなく通常のページ遷移となります。

f:id:kubotak:20201112110347p:plain

サーバーエラーやバリデーションエラーの挙動

これまで実装してきたパターンではフォームからの情報はHTTP APIにより通信し、その結果をNuxt.jsでハンドリングしていました。
しかし、登録・ログイン周りでは先述の通り従来のページ遷移となるため、サーバー側(Laravel)のエラーをフロント側(Nuxt.js)に通知する必要があります。
従来のLaravelのアプリケーションであればセッションフラッシュを利用してページを戻した先でバリデーションエラーを表示できるのですが、アプリケーションが分断されているのでこの方法は利用できません。
そのため、弊社ではエラーをCookieとして扱って状態を引き継ぐ実装をしています。
Laravel側ではエラーになった場合はCookieにエラー情報を入れて元のページに戻します。
Nuxt側ではエラーのCookieがブラウザにある場合はエラーのトーストを表示してそのCookieを削除します。これでセッションフラッシュで行っていたような挙動を再現しています。

f:id:kubotak:20201112110401p:plain

最後に

シリーズでお届けしているNuxt.js化についてはPHP Conference 2020の私のセッションで発表させていただきますのでぜひご視聴ください。 fortee.jp

また、一緒にフロントエンドを開発してくれる仲間も募集中です。
興味がある方は以下よりご応募ください。 www.wantedly.com