hiroaki's blog

技術系を中心に気になったこととかいろいろと。

2023 年の振り返りと 2024 年の目標

2024 年になったので去年の振り返りと今年の目標を決めておこうと思います。

仕事について

2 年ほど続いていたプロジェクトがついに完了し、プロダクトをリリースすることができました。多くのシステムや部署が関連するプロジェクトで、その中でエンジニアチームのリーダーと横断アーキチームのリーダーを担当できたのは本当にいい経験になりました。(関係者は数百人、関連システムも十数個)

やはり関係者が増えてくると意思決定や方針の合意、コミュニケーションのコストが激烈に高く、日々方針決定とコミュニケーションで時間が過ぎていきました。プロジェクトの中盤以降はコードを書くのはメンバーに任せて自分は先回りして調整ごとやチームが動きやすい状況を作ろうとしてましたが、自分がスタックしてしまうこともあり、この規模のプロジェクトならもっと体制や役割などを分業しても良かったかなと今は反省してます。

そして兎にも角にもチームメンバーには本当に助けられました。自分だけではもちろんこんな成果は出せなかったし、無茶な方針変更にも柔軟に爆速で対応してくれて感謝しかないです。チーム開発経験はこういうところからじゃないと学べないことが多いので今後の自分のキャリアにおいてもとてもいい経験になりました。

技術的なところ

去年のコードコミットはこんな感じでした。

2023 年の Github のコミット

見事にプロジェクトが忙しい時期はコミット数が減ってますが、2022 年は 346 contribution だったのでそれは上回ることはできました。ただ、使っている技術は 2022 年とほぼ変わらず、フロントエンドがメインでバックエンドも Go が少しという感じです。プロジェクトが忙しくなかなか自分の勉強の時間を割くことができなかったのですが、プロジェクトも少し落ち着いてきたので今年は新しい分野や言語、過去に経験したけど最新化できていない分野のキャッチアップなどをやっていきたいですね。

健康と運動

2023 年は健康を意識して運動量を増やしました。コロナが蔓延してきた 2020 年からリモートワークになったこともありぐっと運動量が落ちました。もともと睡眠時間も短かったのも相まって、2021 年頃から体力もメンタルも調子が悪くなることがあり、2022 年の人間ドックでもいくつか問題がでてきました。特に気になるのは期外収縮で、幸い問題があるレベルではなかったんですが、このままではいけないと決心して健康を意識するようになりました。

ランニング

リモートワークのときは朝子どもを保育園に送ったあとにそのまま数キロ走るというルーティーンにしていました。これが意外と良く、体力的なところももちろんメンタル的にもかなりリフレッシュできて良い効果がありました。

ランニングは Nike Run Club で計測しているので 2023 年のサマリーをまとめてみました。(データエクスポートができないかつアプリで Yearly の選択ができないというバグがあるので 1 ヶ月ごと地道に計算)

  • 76 回
  • 248.46 km

1 月にコロナに罹ったり、夏場は危険な暑さで走れなかったりもしたなかでここまで継続できたのは良かったです。

テニス

テニスについてはスクールに通い始めました。学生のころはかなり本気でテニスに取り組んでいましたが社会人になってからはほとんどやらなくなってしまっていました。そんななか長男がテニススクールに通い始めたのをきっかけに自分もスクールに行くことにしました。

4 月頃から通い始め、最初は久しぶりすぎて感覚を取り戻すのに苦労しましたが、今では上級クラスまでレベルアップすることができました。副次的な効果として筋トレをする習慣も身についたし、息子とも土日に公園でテニスをしたり、テニス観戦に行ったりもしたし、こちらも良いこと尽くめでした。

2024 年の目標

2023 年は良い習慣を身につけることができたのでそれを継続していきたいです。またプロジェクトも少し落ち着いてきたので新しいことにもチャレンジしていきたいですね。

Keep

  • 毎週ランニングをする
  • 昨年のコードコミットを上回る
  • 毎日の振り返りを欠かさない

Try

  • テニスの試合に出る
  • 新しい言語もしくは分野について勉強する
  • 月に 2 冊は本を読む(2023 年は 14 冊)

2022 年の振り返りと 2023 年の目標

2022 年もあっという間に過ぎ去ってしまいました。2023 年もきっとあっという間に終わってしまうので今のうちに去年のことを振り返って、その上で今年やりたいことはちゃんと言語化しておこうと思います。(サクッと書いて終わりにしようと思って書いていたら意外と長文になってしまった)

2022 年の振り返り

仕事について

昨年に引き続きエンジニアリングリーダーとしてチームをリーディングしてました。かなり不確実性が高くて方針変更も日常茶飯事に起きるプロジェクトでしたが、その中でも柔軟にリーディングしてプロダクトを形にすることができたのは良かったところです。訳あってリリースはまだですが。

こんな感じでチームとしてプロダクトを形にすることはできたのですが、個人としてのエンジニアリングのアウトプットとしてはほとんど出せない一年でした。プロダクトの方針や要件が頻繁に変わる状況で、関係者も数十人(全体では百人は超えている)という規模になってくるとミーティングや仕様調整で日々が終わってしまうことがほとんどでした。

合間をみて自分で設計できるところやコードを書けるところは積極的に手を出していこうとしてましたが結局そこまで動けない状況だったし、そのような状況で下手に手を出して開発スピードを落としてしまうのも本末転倒です。

昨年はそこをある程度諦めてリーダー職に徹していましたが、個人としてはやはりエンジニアとして形あるものを作っていくのが楽しいなと思ってます。ある程度年数を重ねてくるとよくマネージャーかエンジニアかどっちに振り切るかの選択に迫られる、という状況になりがちですが、自分としてはそこはどっちもとれるようにしていきたいです。やはり技術を知らないと意思決定もチームリーディングもできないですし、実装できる人じゃないとメンバーからも信頼を得にくいと思いますし。

エンジニアリングリーダーも面白いポジションではあるので、チームリーディングをしながらどうやったら自分も手を動かしていくことができるのか、まずは仕事の内容の整理と取捨選択をしつつ、うまく工夫してやっていけるようにしていきたいですね。

そんな状況だったのでメンバーにはいろいろと負担をかけてしまったし、お前は全然コード書いてねーじゃねーか!って思われてるかもしれないですが、今年はもっと信頼してもらえるように先陣を切ってプロジェクトに突っ込んでいきたいと思います。

技術的なところについて

去年の Github のコミットはこんな感じでした。

github-commit
2022 年の Github のコミット

プライベートなところで 1 月に第二子が産まれてバタバタではあったんですが、そんな中でもこれだけのコミットを維持できたのは上出来かなと思ってます。

昨年の技術スタックはこの辺りがメインでした。

  • Next.js
  • TypeScript
  • Go

使っている技術は昨年とは特に変わっていないので、今年は新しい言語や分野にチャレンジしていきたいですね。

またちょっと特殊な環境でエンジニアとしてのアドバイザーのようなことも経験できたのは良かったです。単純に技術先行ではなくチームやプロダクトの状況によってどこまで踏み込んで対応するかが変わってくるので、その辺りの見極めや判断力については視野が広がったかなと思っています。

昨年の振り返りにも書いた設計やアーキテクチャ周りの引き出しを増やす、という目標についてはうまく進められなかったので、この辺りも今年の課題としてチャレンジしていきたいです。

プライベートについて

昨年はプライベートなイベントが満載でした。

第二子誕生

1 月に第二子が産まれました。「あーこんなタスクあったなー、身体が覚えてるわー」という感じで子育てタスクで困ることはほとんどなかったと思います。産まれてから 2 週間ほど育児休暇をとれたのも大きかったです。妻の身体的には出産してからしばらくは普通の生活をするのもつらい状況になってしまうので、そのタイミングで育児休暇をとってサポートできたのは良かったです。仕事ではいろいろと迷惑をかけてしまいましたが、この辺りの制度にも非常に理解のある職場で本当に助かりました。

そんな中で 1 人目のときとの大きな違いはリモートワークで家にいたため子育てに積極的に関われたことでした。昨年もほとんどリモートワークで在宅勤務だったため、子どもに対する時間はかなり確保することができました。今と比較すると 1 人目のときは夜のお風呂の時間に帰ってくることはほとんどできなかったし、帰ってくると寝てる時間帯だったので顔を合わせるのは朝だけという状況。これは今考えると考えられないというか、そんなに子どもとの時間が少なかったのか、と恐ろしくなりました。

そんな長男も去年は保育園から帰ってきたあとは一緒に遊んであげたり、毎日一緒に夕飯を食べたりできたので少しは還元できたのかなと思ってます。まだまだコロナは大変ですが、リモートワークで家族の時間をもっと確保したいし大切にしていきたい、という価値観に大きく変わったのはとても良かったですね。

長男の小学校受験

11 月には長男の小学校受験がありました。結果的には無事に納得の行く学校に合格できました。これに関してはほとんどのタスクを妻がこなしてくれたので本当に感謝です。また、妻が育児休暇で長男の勉強に長い時間付き添ってあげられたこと、リモートワークで親二人で子どもの面倒を見てあげられたこと、など今のいろんな状況がいい方向に作用したおかげでなんとか乗り切れることができました。(普通に両親とも働いてる家庭はどうやってやって受験を乗り切ってるんだろ...)

受験対策としてはせっかくなので色々とツールをフル活用して挑んでみました。結果それがうまくいっていたかなと思うので、これは改めてどっかでブログにまとめようと思ってます。

それにしても本当に一段落してよかった。なんだかんだ学校が決まるまでは家中がストレスでピリピリしてた感じでした。そんな中最後まで頑張った息子にもしばらくはゆっくり好きなことをさせてあげようと思ってます。

健康について

昨年は心身ともに健康について考えることが多い一年でした。

メンタル面については昨年から引き続き、脳科学、心理学、歴史を中心に本を読んだりしていました。特に面白かったのは「認知バイアス」と「宗教・哲学」の分野でした。認知バイアスについては脳科学から発展して、自分はどうやって物事を認識しているのか、どんな癖があるのかを科学的に理解することで自分を俯瞰して観察したいという目的、宗教・哲学については先人たちの考えを知ることで自分の考え方をアップデートしたいという目的で勉強していました。メンタルや考え方をうまく制御するだけじゃなく、考え方の幅が広がることでエンジニアリングにも良い影響がでてくると思うので、この辺りは分野を限定せずに色々と知識を習得していきたいですね。

フィジカル面でも大きな出来事がありました。7 月に初めて人間ドッグを受けたのですが、そこでいくつか問題が見つかりました。

最初の 2 つはそれほど問題じゃないんですが、3つ目のやつは心臓に関するものなのでかなり焦りました。体中に電極を付けて(ホルター心電図っていうらしい)丸一日モニタリングした結果、特に異常な波形はなかったのでひとまずは大丈夫でした。期外収縮についてもいろいろと調べてみたのですが、どうやら自律神経の乱れでも影響が出てしまう様子。確か数年前に心電図をとったときには異常はなかったことから考えるとここ数年の生活が影響してそう、となるとやはりここ数年のメンタルの不調と不規則な生活が影響していそうです。

自分は身体は強い方で少しくらい無茶をしても大丈夫だろうと思っていたのですが、過信しすぎるのも良くないですね。人間、健康じゃないと何もできないので、今年は健康に気をつけて生活習慣を整えてしっかりと運動を継続していこうと思います。

2023 年の目標

というわけで今年はこの辺を頑張っていこうと思います。

  • 毎週ランニングを継続する(健康が第一優先)
  • 新しい言語もしくは分野について勉強する
  • 昨年のコードコミットを上回る
  • ブログへのアウトプットを増やす
  • 仕事での時間の使い方を整理する

TypeScript の Union Types について再確認してみた

TypeScript の Union Types でうまく型が認識されず、プロパティのサジェストが効かなかったので改めて仕様を確認してみた。 結論としては「Union Type でそれぞれの Type で異なるプロパティがあった場合、どちらの Type もコード上あり得るからそのままでは使用できない。」という当たり前のところだった。

Generics と絡んでいるコードだったのでそっちが原因かなと思って問題の特定に時間がかかってしまった。雰囲気で利用せずに基本的なところはちゃんと公式ドキュメントを見て理解しておきましょうという反省も込めて内容を記載しておく。

発生した問題

問題となったのは下記のようなコード。

type Props1 = {
  id: string
  title: string
}

type Props2 = {
  id: string
  title: string
  owner: {
    id: string
    name: string
  }
}

type UnionType = Props1 | Props2

export type Props<T extends UnionType> = {
  params: T
}

export const SampleComponent = <T extends UnionType>({ params }: Props<T>) => {
  console.log(params.title) // Success : これは引数の Type が解釈される
  console.log(params.owner.name) // Error : Property 'owner' does not exist on type 'Props1'.
}

SampleComponent({
  params: {
    id: "1",
    title: "sample",
    owner: { id: "2", name: "Imai" },
  },
})

コメントで書いているが、1 つ目の id は Type として認識されてサジェストされるが 2 つ目の ownerProperty 'owner' does not exist on type 'Props1'. のエラーが表示された。とある既存サービスの改修をしようとしていて対象箇所で Generics を使っていたので、Union した Type に Extends するとうまく型が解釈されないのかと予想したのだが、そもそもの Union Type の基本的なところが原因だった。

既に Deprecated なドキュメントだが TypeScript の公式ドキュメントに同様のケースが載っていた。 www.typescriptlang.org

interface Bird {
  fly(): void;
  layEggs(): void;
}
 
interface Fish {
  swim(): void;
  layEggs(): void;
}
 
declare function getSmallPet(): Fish | Bird;
 
let pet = getSmallPet();
pet.layEggs();
 
// Only available in one of the two possible types
pet.swim();
Property 'swim' does not exist on type 'Bird | Fish'.
  Property 'swim' does not exist on type 'Bird'.

Union Type なのでもともとの 2 つの型についてはどちらが入ってきても良い想定である。 となった場合に上記の例だと Bird インターフェースは swim メソッドを持っていないのでコンパイルエラーとなる。 この場合に使用できるのは「どちらのインターフェースも持っている layEggs というメソッド」のみとなる。

解決策

で、どうすれば良いかというとこちらも最新の公式ドキュメントに記載がある。 www.typescriptlang.org

要は TypeScript が型を絞り込めるようにコード内で条件分岐が必要だということ。例えばこういうコードでエラーになった場合、

function printId(id: number | string) {
  console.log(id.toUpperCase());
// Property 'toUpperCase' does not exist on type 'string | number'.
//  Property 'toUpperCase' does not exist on type 'number'.
}

下記のように条件分岐することで必要なメソッドも推論されて実行可能となる。

function printId(id: number | string) {
  if (typeof id === "string") {
    // In this branch, id is of type 'string'
    console.log(id.toUpperCase());
  } else {
    // Here, id is of type 'number'
    console.log(id);
  }
}

冒頭のコードに適用すると下記のような感じになる。type というプロパティをもたせて、それによって処理を分岐することでどちらかにしか存在しないプロパティも問題なく実行することが可能となる。

type Props1 = {
  type: "titleOnly"
  id: string
  title: string
}
type Props2 = {
  type: "hasOwner"
  id: string
  title: string
  owner: {
    id: string
    name: string
  }
}
type UnionType = Props1 | Props2

export type Props<T extends UnionType> = {
  params: T
}
export const SampleComponent = <T extends UnionType>({ params }: Props<T>) => {
  switch (params.type) {
    case "titleOnly":
      console.log(params.title)
      break
    case "hasOwner":
      console.log(params.owner.name)
      break
    default:
      console.log("Type property not found.")
  }
}

SampleComponent({
  params: {
    type: "hasOwner",
    id: "1",
    title: "sample",
    owner: { id: "2", name: "Imai" },
  },
})

公式ドキュメントにわかりやすい例えが載っていたのでここにも掲載しておく。

For example, if we had a room of tall people wearing hats, and another room of Spanish speakers wearing hats, after combining those rooms, the only thing we know about every person is that they must be wearing a hat.

たとえば、背の高い人が帽子をかぶっている部屋と、スペイン語を話す人が帽子をかぶっている部屋がある場合、それらの部屋を組み合わせた後、すべての人について知っているのは、帽子をかぶっていなければならないということだけです。(Google 翻訳)

まとめ

  • Union Types を使用した場合 Union として定義されるのは結合する Type の共通部分のみ
  • どちらかにしか存在しないメソッドやプロパティを使用する場合は条件分岐することで使用可能
  • 実装していると雰囲気で使ってしまうこともあるが、基本的なところは公式ドキュメントをちゃんと読んで理解してから使うようにする。
  • エラーが発生したときは問題をシンプルにして原因を特定する。(Generics 関係なかった)

2020 年の振り返りと 2021 年の目標

あけましておめでとうございます。明日から仕事が始まるのでその前に 2020 年の振り返りと 2021 年の目標を書いておこうと思います。

2020 年の振り返り

一つ前のブログにも書いたのですが、2020 年は年の初めにコロナが発生して生活環境が大きく変わってしまいました。 なかなかここまで環境が変わるってことは今までの人生ではなかったんじゃないかなと思います。 ワクチンが開発されるまでは今の状況は変わらなそうですし、しばらくはうまく付き合って生活していくしかなさそうですね。

仕事関連

コロナでリモートワークが基本になりました。最初の頃は多少の混乱はあったけど Web エンジニアということもあり仕事内容は基本的に全てフルリモートに移行して特に支障がない状態です。 ただ、いくつか技術ブログは書いたものの社内外でのイベント自体も自粛でなかったこともあり、オフィシャルなアウトプットという面ではほとんで成果がありませんでした。 自分の理解を助けるという意味でもアウトプットは重要なので今年は特にそのあたりを意識してアウトプットの量を増やしていきたいと思います。

  • Next.js + TypeScript を使用したプロダクト開発

5 月くらいに社内でかなり重要な短期プロジェクトが発足してそこにサポートとして 2 ヶ月弱参加してました。

プロジェクト自体は死ぬほど忙しくて大変だったんですが、今思えばものすごいいい経験だったし刺激的なメンバーで自分も成長できたかなと思ってます。 技術的にもそれなりの規模のサービスを Next.js + TypeScript で開発できた経験はとても良かったですね。

12 月に社内向けの AWS GameDay というイベントがあったので参加してみました。 細かく内容は書くことはできないのですがイベント自体はめちゃくちゃ楽しくてインフラスキルを見直すいい機会になりました。

前職ではインフラエンジニアもやってたんですが独自クラウドインフラ構築がメインでしたし、今のサービスでは AWS を使っているのですがいろいろと知らないことだらけで結構悔しかったのでインフラ面も改めて一から勉強し直していこうと思っています。

  • 1人 Daily / Weekly Sprint を始めてみた

8 月くらいから 1 人で日次、週次のプランニングと振り返りを継続してます。

といってもそんなに仰々しいものではなく、その日にやることをメモってちゃんと予定通りできたかとか、週次でよかったことを書き出してみたりとか今後のキャリアを考える時間を作るとかそのくらいの内容です。 こんな感じで緩く始めてみたんですがそれが功を奏したのか自分には結構合っていたみたいで半年くらい継続できています。

最初は自分の計画の制度を上げることが目的だったんですが、あとから振り返ってみると行動のログも残るし日記的に自分の気持ちも書いていたりするのでとても良かった取り組みでした。これは 2021 年も継続していきたいと思います。

  • 1 ヶ月のお休み

これは体調不良とかでは全然なく、会社の制度で 3 年以上勤務すると 1 ヶ月連続して休めるという制度があるのでそれを取得してみました。 社会人になってから 1 ヶ月も休めるなんてことは想像もしてなかったのでかなり新鮮な 1 ヶ月でした。

もともとは海外に F1 観戦しに行ったりしたいなと思ってたんですがコロナで実現できず。 せっかく GoTo トラベルもあるので(もちろん感染対策はしっかりした上で)伊勢神宮へ一人旅したりしてました。

自分は働くこと自体は大好きなんですが、今回こういう機会を会社からもらったことで自分を見つめ直すいいきっかけになったので 今後も休むときはしっかり休んでたまには頭をリフレッシュしていきたいなと思いました。

マネージメント的なところ

仕事的にはメインとしていたプロダクトが変わりました。 行き先は 3 年前くらいに担当していたプロダクトなんですが、やはりコロナ禍でリーダーとしてマネジメントしていくのは難易度が高いですね。

自分はチームマネジメントでは「メンバーと信頼を築いていく」ということが重要だと思ってます。 去年のブログにも書いてるんですが、ただメンバーを管理するだけではなく下記のようなところまで考えていくことでメンバーとの信頼感も生まれるし仕事もうまく回っていくんだと思ってます。

  • 目的を正確に伝えてメンバーに納得感を持って対応してもらう
  • メンバーが自分の成長につながるイメージを持ってもらう
  • 何かあればリーダー自ら対応する方針を示す

この辺の進め方がリモートになることで最高難易度業務になってるとは思うんですが、メンバーに正面からぶつかっていくことでなんとかうまく走り出せているのかなと感じてます。

個人的には年末の振り返りで「心理的安全性のあるチームづくりをしようとしている気概を感じる」というコメントがメンバーから出てきたのが嬉しかったですね。 まだリーダーとして 3 ヶ月くらいしか経っていないのにそれを感じてくれたのは自分がやろうとしていたマネジメントがちゃんと伝わっていたと少しホッとしました。

ただ、これで満足することなくこれからもメンバーと信頼関係を持って進めていけるよう改めて気を引き締めていかないとですね。

副業関連

  • とあるサービスのお手伝い

とあるエンタメ系サービスの開発をお手伝いしてました。3 月から忙しくなってしまったので少しの期間だけでしたが。 ビジネスモデルがとても面白かったので参画させていただきましたが、予想通り自社のサービスでは経験できないようなモデルだったので非常に参考になりました。 技術的には割とレガシーな環境だったんですが、そこで開発をしてみて改めて今の技術スタックは便利だなーと実感することができました。(やっぱり jQuery は厳しい...)

  • 新規サービスの PoC 検証

過去に一緒に仕事をしていた人から誘われて新しいサービスの PoC 検証みたいなことをやってました。 具体的なことはまだ言えないんですが、一から新規サービスを検討して形にしていくということができたのは非常に面白かったです。 エンジニアは自分だけなので色々と技術を試してみようということで、Next.js と Vercel をメインにいろいろとチャレンジできたのが良かったです。 この活動は今後も続いていくので、ビジネス的な成功を目指しつつ技術的なところもうまく試していける場にしていければ良いなと思ってます。

2021 年の目標

  • 去年以上にコードを書く

2020 年の GitHub はこんな状況でした。

f:id:hiro14aki:20210103225604p:plain

Write Code Every Day までは全然行かなかったし数をこなせば良いということでもないんですが、 いろんなコードを読んで書いたほうが自分の引き出しが多くなることは間違いないので今年も継続していきたいと思います。(単純にコード書くの楽しいですしね。)

  • 毎日運動を継続する

コロナ禍で基本的には家にいることが多くなったので、それと反比例して運動する機会が減っていきました。 去年は jump one というトランポリンジムにも通い始めてたのにコロナの影響で退会してしまいました。 当然体力が減っていくのでなんとなく体調が良くないというタイミングも増えたし、体調が良くないとメンタルもそれに引っ張られて気持ちが落ち込むことが多かったです。 しばらくはコロナも続きそうですし家でのワークアウトとかを継続して健康に気をつけて生活していきたいと思います。

  • 余裕を持って生活する

これは去年からの継続目標なんですが、人間余裕がないと正常な判断が難しくなってくるなと思いました。 数値化できない目標ではあるんですが、ある程度余裕をもって先回りして考えることで仕事自体もうまくいくことが多いですし、気持ちに余裕が出ることで QOL も上がってくることが多いです。 リモートで何かとストレスが多くなりがちですが、うまく乗りこなして生活していけるようにしたいです。

まとめ

余裕をもって健康的に、という落ち着いた目標になってしまいましたが、自分のマインドとして「ある程度まで追い込まれないと人間成長しない」というスローガンみたいなのは常に持っています。

ドラゴンボールでも一度死にかけて仙豆で復活すると強くなりますし、ヒロアカのエンデヴァーもハイエンドにギリギリまで追い込まれながらも PLUS ULTRA で撃退して No.1 の威厳を示してましたし、そういう覚悟みたいなのは結構重要だなーと思ってます。

そういうわけで今年も自分をどんどん追い込んで行こうと思っているので皆さんよろしくお願いします。 (あくまでも追い込むのは自分向けなのでご安心ください)

ちなみに

去年のブログで目標に設定してた「キングダムハーツシリーズ全制覇」は無事に達成できました。 ユニオンクロスとダークロードはまだ完結していないので WIP にしてますが、11 月に発売されたメロディオブメモリーもクリア済みです。 続編も楽しみだけどしばらくは無さそうな気がするので今年はポケモンを復活していこうかな。

2020年に買ったもの

2020 年もあっという間に終わりですね。 今年は年明け早々にコロナの影響でリモートワークになったので季節感を感じないままいつの間にか年末になってました。

ほとんどの仕事を家でやるようになったのでそれに合わせて家の環境も色々とアップデートしました。 年末ですしせっかくなので何を買ったのかざっとまとめておこうと思います。

作業環境まわり

今まではリビングのテーブルで仕事することが多かったんですが、リモートワークをきっかけにちゃんと仕事スペースを作りました。

  • デスク
    ちょっと小さめですが仕事するのに最低限の広さは確保しました。27 インチのモニターがピッタリ置けます。 www.creema.jp

  • モニター台
    目線を下げないようにするのと机が狭いのでキーボードを収納できるように購入しました。 www.nitori-net.jp

  • HermanMiller Sayl Chairs
    アーロンチェアまでは金額的に手が届かなかったので同じハーマンミラー社のセイルチェアを購入。やっぱりいい椅子は全然疲れないですね。もともと結構肩こりも酷い方なんですがこの椅子にしてからは肩こりを感じることもほとんどなくなりました。 www.hermanmiller.com

  • SANWA SUPPLY SNC-CAST3 大型ウレタンチェアキャスター
    セイルチェアのオリジナルのキャスターだとフローリングに傷が付きやすいということことでこちらを購入。音も静かになりました。
    Amazon | SANWA SUPPLY SNC-CAST3 大型ウレタンチェアキャスター | キャスター・固定脚 | 文房具・オフィス用品

  • Dell プロフェッショナルシリーズ P2419HC 23.8インチ ワイド USBーC モニター(メルカリで売却)
    4 月のリモートワークをきっかけに購入しました。スペック的には割と満足してたんですが、会社のモニターが EIZO だったこともあり使うにつれて物足りなさが出てきてしまいました。最終的にこちらはメルカリで売却して新しいモニター購入の資金にしました。 www.dell.com

  • EIZO FlexScan EV2785
    半年くらい使っていた Dell のモニターから乗り換えました。4K モニターなので一つ解像度を落として使ってますが、それでも表示できる情報が格段に増えました。EIZO クオリティもあって目も疲れにくくなったので最高です。 www.eizo.co.jp

リモート会議まわり

コロナで出社も自粛するようになってリモート会議の出番が増えてきました。正直 iPhone のイヤホンでも問題ないんですが、リモート会議にかける時間もかなり長くなってくるのでできるだけ快適に会議ができるように色々と買い揃えていました。

ガジェット系

こちらはリモートワークは関係なく自分の趣味で買ったものがほとんどですね。

  • HHKB Professional HYBRID Type-S
    今までは Apple の Magic Keyboard を使っていたんですが、HHKB がどんなものか試してみたくなって思い切って購入。使ってみた結果、やっぱり良いものは良く、長時間タイピングしていても指が疲れにくくなりました。打感も気持ちよくてどんどんタイピングしたくなってきますね。 happyhackingkb.com

  • iPhone 12 Pro
    当初は買うつもりはなかったんですが、Apple の Event をオンラインで見ていたら欲しくなって購入。iPhone のデザインはこの角張ったデザインのほうが好きですね。 www.apple.com

  • ホワイトボード
    ちょっとしたメモ書きをするために購入。子供のお絵かきにも使えるしなかなか便利です。 www.pilot.co.jp

運動系

リモートワークになったことでほとんど運動をしなくなってしまい、これはまずいということで色々と運動系のグッズも購入してました。

  • シェイプキューブ
    家の中でできるオシャレトランポリンです。以前はトランポリンジムに行ってたのですがコロナで出社もしなくなったので解約してしまいました。そんなときに見つけたのがこちら。ちょっと高いけどデザインもオシャレだし音も響かないし満足です。 www.amepla.jp

  • OASIS×東急ハンズ ダンベル 1kg
    普通のダンベルです。これを持ちながら YouTube のボクササイズ動画をやるとかなり負荷がかかっていい感じです。 hands.net

  • La.VIE のび~るフィットネススーパーハード グリーン
    仕事中に背中が疲れたとき用に購入。一番ハードなモデルにしたんですが、もう少し柔らかめでも良かったかも。 hands.net

まとめ

ここまでざっとまとめてきましたが、2020 年に購入したものベスト 3 をあえて挙げるならこの 3 つですね。

  1. HermanMiller Sayl Chairs
  2. HHKB Professional HYBRID Type-S
  3. EIZO FlexScan EV2785

もともと副業などで家で仕事する事が多かったので、コロナをきっかけに色々と買い揃えられたのは結果的に良かったです。 来年もしばらくリモートワークが続きそうですし、意識的に運動を継続しながら健康に気をつけて生活できれば良いですね。

まずは一刻も早くコロナが落ち着くことを祈って。

GraphQL を Apollo を使ってキャッチアップしてみた

直近で GraphQL を使いそうな気配がしているので今更ながらキャッチアップしてみた。 最後に書いているが実践で使用できるような細かい設定は引き続き調査して試していくところではあるが、現時点で CRUD 機能をもつ簡易的なアプリケーションを作成するところまでは試してみたので今後の自分用のメモとして初期インストールから実装まででやったことを残しておくこととする。

なお、今回のコードは全て GitHub にアップしているので、もしコメントなどがあれば是非意見をいただきたい。 github.com

GraphQL とは

GraphQL とは API のための問い合わせ言語であり、クライアント/サーバー間通信のための言語仕様である。

今までは API を構築するとなると REST の設計に基づいて実装することが多かったが、取得できるデータが URL と結びついて理解しやすくなった反面、リソースが分離していることで逆に取得するデータが過剰になってしまったりリクエストが増えてしまったり、という問題が起きることがあった。

GraphQL ではクライアントが必要なデータを定義し、サーバー側もそれに従ってスキーマを構築していくことになるため上記のような問題が発生しにくくなる。また、明確にリクエストデータとスキーマで型を定義することになるため、クライアント/サーバー間でどのようなデータをやり取りするのかが開発者に分かりやすくなるというメリットもある。

アプリケーションとリポジトリの構成

今回は JavaScript(TypeScript)を使用し、GraphQL のサーバー/クライアントともに Apollo を使用して実装していくこととする。サーバーとクライアント両方を作成していくので同じリポジトリ内にディレクトリを分けて構成していく。

.
├── README.md
├── client
│   ├── README.md
│   ├── node_modules
│   ├── package.json
│   ├── prettier.config.js
│   ├── public
│   ├── src
│   ├── tsconfig.json
│   └── yarn.lock
└── server
    ├── index.js
    ├── node_modules
    ├── package-lock.json
    └── package.json

GraphQL Server を構築する

GraphQL リクエストを受けるサーバーを構築していく。

初期セットアップ

まずは Node.js のプロジェクトとしてセットアップする。

$ npm init

次に GraphQL のサーバー関連のパッケージをインストールする。

$ npm install apollo-server graphql

GraphQL リクエストを受けるサーバーの構築

インストールが完了したらサーバー側のコードを書いていく。 まずは apollo-server から必要なパッケージを読み込む。

const { ApolloServer, gql } = require("apollo-server");

次に GraphQL でリクエストを受ける際のスキーマ定義を記述していく。

const typeDefs = gql`
  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
  }

  input InputBook {
    title: String
    author: String
  }

  type Mutation {
    addBook(input: InputBook): Int
  }
`;

type Query は特別な記述でアプリケーションのデータを取得する際に使用する。クライアントからくるデータ取得用のクエリを全て記述しておくことでサーバー側で受けるリクエストを定義できる。ここでの type Book のような感じで Query の定義を別出しにして、クエリのオブジェクトをネストして定義することもできる。

アプリケーションのデータに変更を加える場合は type Mutation を使用してミューテーションとして定義する必要がある。また Mutation に使用している変数の型は 入力型 と呼ばれ、input として定義を記載していく。この入力側の定義は引数に対してのみ使用することができる。ページングの情報やフィルタリングなど、実行するアクションは異なるが渡すパラメータが同じ、といった場合にこのような入力型で統一した定義を使用してユーザーが理解しやすくメンテナンスもしやすくなってくる。

引数ありのリクエストは下記のようなフォーマットで受けることができる。

const typeDefs = gql`
  type Book {
    title: String
    author: String
  }

  type Query {
    books(author: String): [Book]
  }
`;

後述するがこれに対してクライアント側で下記のようなリクエストを送ることで値を受け取れるようになる。

// Client
query {
  books(
    author: "Hiroaki Imai" // ここが引数としてサーバー側に渡される
  ) {
    title
    author
  }
}

次にリクエストに対するレスポンスを記述しておく。今回は動作確認が目的なので DB は用意していないが、実際のサービスなどでは DB から取得した値をレスポンスにセットするため、今回のような固定値を配列で用意しておくような処理は不要となる。

// 今回は一時的に Array の変数として準備しておく
let books = [
  {
    id: "4371b53e-e8e0-489e-9d92-fc243bc9dc48",
    title: "The Awakening",
    author: "Kate Chopin",
  },
  {
    id: "f267b324-6dc5-4b01-8d33-7e6dbcd60710",
    title: "City of Glass",
    author: "Paul Auster",
  },
  {
    id: "eb9e1b15-37cb-4f6e-a3de-e3ffbe3a499e",
    title: "Apollo server example.",
    author: "Hiroaki Imai",
  },
];

Apollo Server は、クライアントから呼び出された際にクライアントから要求されたクエリに対してどのようなレスポンスを返す必要があるのがを知っておく必要がある。これを実現するために、リゾルバを使用する。

ゾルバはスキーマの特定のフィールドのデータを返却する関数である。バックエンドデータベースやサードパーティAPI からデータをフェッチするなど、データを取得して定義されたスキーマに従ってクライアントにレスポンスを返す。

下記の例ではリクエストとして渡ってきた値をもとに定義された Array を filter した結果をレスポンスとして返している。

const resolvers = {
  Query: {
    books: (parent, args, context, info) => {
      return books.filter((value) => value.author === args.author);
    },
  },
  Mutation: {
    addBook: (parent, args, context, info) => {
      const requestData = args.input;
      if (requestData.title !== "" && requestData.author !== "") {
        const id = uuidv4();
        books.push({ ...requestData, id });
      }
      return books.length;
    },
  },
};

resolvers の中では parent, args, context, info というパラメータが引数として定義されている。Query の箇所で定義した引数は resolvers 内で args として受けることができる。(parent、context、info に関しては今回の記事では説明を省略)

次に Apollo サーバーにスキーマ定義である typeDefs とそれに対するデータ入力フィールドを定義した resolvers を渡して初期化する。

const server = new ApolloServer({ typeDefs, resolvers });

最後にインスタンス化した Apollo サーバーでリクエストを待ち受ける処理を記述する。

server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`);
});

ここまでで Apollo サーバーの記述は以上となる。この状態で下記のコマンドを実行すれば GraphQL のリクエストを処理する Web サーバーが起動する。

$ node server/index.js
🚀  Server ready at http://localhost:4000/

サーバーの hot reload

開発中の効率を上げるためにサーバーの hot reload を設定する。 nodemon を使用することでサーバー側のコードを変更した際に自動でリロードされるようになる。

$ npm install --save-dev nodemon
$ npx nodemon index.js

GraphQL Client を構築する

初期セットアップ

次にクライアント側を作成していく。今回は React を使用して作成していくこととするため、create-react-app を使用する。(今回は create-react-app 自体の説明は省略する。)TypeScript を使用するためオプションに --template typescript を指定する。

npx create-react-app . --template typescript

クライアント作成に必要なパッケージも合わせてインストールしていく。

npm install @apollo/client graphql --save

データ取得用のリクエストクエリ作成

まずはページ表示時のクエリを作成していく。

下記の用に apollo-client をインポートし、ページの初回表示時にリストの情報を取得するクエリを作成する。

GraphQL ではデータの取得には query オペレーションを使用する。query オペレーションの定義には変数として使用する値の定義も合わせて記述する。ページ初回表示時には存在している全てのデータを取得してくる動きとなるが、ここではデータの検索も想定して特定のパラメータを渡すことができるようにしておく。

今回の例では実際の開発時を想定して QueryData.ts という外部ファイルを作成し、そこにリクエスト用のクエリを記述していくこととする。

// data/QueryData.ts
import { gql } from '@apollo/client'

export const FETCH_BOOK_LIST = gql`
  query FetchBookListQuery($text: String!) {
    books(author: $text) {
      id
      title
      author
    }
  }

create-react-app にて App.tsx というファイルが作成されているので下記のように修正していく。

// App.tsx
import React, { ChangeEvent, useCallback, useEffect, useState } from 'react'
import { ApolloClient, InMemoryCache } from '@apollo/client'
import { FETCH_BOOK_LIST } from './data/QueryData'
import { ADD_BOOK, DELETE_BOOK, UPDATE_BOOK } from './data/MutateData'
import 'reset-css'
import './App.css'

type BookList = {
  id: string
  title: string
  author: string
}[]

const client = new ApolloClient({
  uri: 'http://localhost:4000/',
  cache: new InMemoryCache(),
})

function App() {
  const [bookList, setBookList] = useState<BookList>([
    { id: '', title: '', author: '' },
  ])

まずは apollo-client をインポートし、クライアントの初期設定を行っていく。今回はローカルで試すことのみを想定しているため、url は先程 GraphQL サーバーを作成した際に設定した http://localhost:4000/ を指定する。

ページ表示時に実行されるリクエスト処理を作成していく。まずは先程作成したクエリをインポートし、リクエスト用のオブジェクトを作成する。そのオブジェクトを初期化したクライアントの query オペレーションにセットし実行する。query オペレーションは非同期処理で Promise を返すため、then でリクエストが完了したあとの処理を記載しておく。

const fetchList = (text: string = '', forceRefresh: boolean = false) => {
  const requestQuery = {
    query: FETCH_BOOK_LIST,
    variables: { text },
  }
  client
    .query(
      forceRefresh
        ? {
            ...requestQuery,
            fetchPolicy: 'no-cache',
          }
        : requestQuery
    )
    .then((result) => {
      if (result.errors) {
        console.log('Failed to fetch data.')
      } else {
        setBookList(result.data.books)
      }
    })
}

次に fetchList アクションをページ表示時に 1 度のみ実行されるように useEffect を使用して記述しておく。

// For initial rendering.
useEffect(() => {
  fetchList();
}, []);

取得したデータを Reactコンポーネント内で使用してレンダリングすることで、GraphQL で取得したデータを画面で表示できるようになる。

  return (
    // 省略
    {bookList.map((book, index) => {
      const isModifyTarget =
        modifyBookInfo.modify && book.id === modifyBookInfo.id
      return (
        <tr key={index}>
          <td className={'data'}>{index + 1}</td>
    // 省略
  )

パラメータを必要とするクエリの組み立て

ページ表示時のフェッチ処理の部分で説明を省いてしまったが、検索アクションを実行する場合はこのアクションの中で検索用のパラメータを GraphQL のクエリにセットする必要がある。ここでは variables という機能を使用して変数をクエリに渡している。

Template String を使用して GraphQL のクエリ文字列にパラメータを直接展開することももちろんできるが、その場合は好きな文字列を渡すことができてしまうため SQL インジェクションのような攻撃を受けてしまうことが想定される。variables を使って型定義を記述し、入力される変数の型チェックを実施した上で実行クエリに変数をバインディングすることで、セキュリティホールを作らずに安全にリクエストを送信することができるようになる。

データの更新時のリクエストクエリ作成

ここではデータ取得以外のリクエスト方法について説明していく。GraphQL ではデータの「追加、更新、削除」といったデータの更新が必要なリクエストに関しては Mutation オペレーションを使用する。

実際に対象データを追加していくクエリを作成していく。今回は MutateData.ts という名前で外部ファイルを作成してそこに「追加、更新、削除」用のクエリを記載していく構成としている。

import { gql } from "@apollo/client";

export const ADD_BOOK = gql`
  mutation AddBookQuery($input: InputBook) {
    addBook(input: $input) {
      id
      title
      author
    }
  }
`

Apollo client からリクエストする際のメソッドが mutate になっているくらいのみで、基本的にはデータ取得時のクエリとほとんど構成は変わらない。

const addBook = useCallback(() => {
  client
    .mutate({
      mutation: ADD_BOOK,
      variables: { input: { title, author } },
    })
    .then((result) => {
      if (result.errors) {
        console.log('Failed to add data.')
      } else {
        fetchList(searchText, true)
      }
    })
}, [title, author, searchText])

リクエストに関しては Apollo Server の構築の際に Mutation として定義していた addBook が呼び出され、実際にデータが追加されるという流れになっている。

今回のまとめ

今回は導入編ということで環境のセットアップから Apollo というライブラリを使用して、GraphQL のサーバーとクライアントを作成するところまでを説明した。上記では初回の読み込みとデータの追加しか説明していないが、更新と削除まで実装したものを Github にアップしているので詳細はそちらを参照していただきたい。

github.com

また実践導入にあたっては下記のポイントの考慮も必要になってくるため、こちらも色々と試しつつ別途ブログにまとめていきたいと思う。

  • 細かいキャッシュの設定
  • ファイルアップロード(multipart/form-data の扱い)
  • サブスクリプションの活用方法
  • エラーハンドリング

参考