snaqme Engineers Blog

おやつ体験メーカー snaq.me エンジニアによる開発プロダクトブログ

第1回【DDD勉強会】を開催しました!🍫

こんにちは、エンジニアの兼崎です!

先日、第1回 ドメイン駆動設計(DDD)勉強会が開催されました。

スナックミーでは過去の開発における反省のもとDDDを導入していて、開発メンバーがしっかり設計に則って開発できるようにしたいという想いから、エンジニアのタク(@yamataku3831)が当勉強会を企画してくれました。

今回のテーマは、 「値オブジェクト」です。

(当記事は主に文章で構成しています。以下のスライドはサンプルコードもあり理解しやすいので、気になる方はぜひご覧ください💁)

speakerdeck.com

DDDとは何か

勉強会の主題である"ドメイン駆動設計(Domain Driven Design)"とは、「ドメインの知識に焦点を当てた設計手法」と紹介されていました。

少し詳しく書くとすれば、様々な解釈があると思いますが「プログラムが適用される領域を実際に利用している人たちの考え方や視点、彼らを取 り巻く環境を理解することを重視した設計手法」であると、この勉強会では説明されていました。

値オブジェクトについて

エンジニアの方は、「値」や「オブジェクト」に馴染みがあるかと思います。 値オブジェクトはこれらが組み合わさったもので、システム固有の値を表現するために定義されたオブジェクト と理解しました。

なぜ値オブジェクトを学ぶのかというと、integerやstringなどのプリミティブな値だけでシステムを構築しようとすると管理が難しくなるからです。

例えば、氏名を扱うシステムで姓だけを表示したい場合、氏名が文字列型だと姓だけを正確に取り出すことが難しくなります。 そこで氏名をオブジェクトとして定義すると、姓と名を属性として持たせることができ、管理が容易になります。

今回のスライドの文脈では、氏名はオブジェクトであり、値でもあるため、「値オブジェクト」として実装していました。

そもそも “値” が持つ性質とは何か

"値"には、以下のような性質があると紹介されていました。

  • 不変である: 値の変更を行うときには、変数の値そのものを変更するのではなく、変数の内容を入れ替えます。
  • 交換が可能である: 変数の値を変更するには、代入を利用した値の交換を行います。
  • 等価性によって比較される: 同一性ではなく、等価性によって比較されます。

値オブジェクトは値の性質を持つオブジェクトです。つまり、値オブジェクトを変更したい場合は属性単体での変更はせず新しい値オブジェクトを代入します。さらに比較はオブジェクト同士が持つ全ての属性を比較するメソッドを使用するように設計するとよさそうでした。

値オブジェクトを使用するメリット

値オブジェクトには以下のようなメリットがあります。

  • 表現力を増す: 値の構成や設計が明確になり、ロジックを読み取りやすくなります。
  • ロジックの分散を防ぐ: 仕様に関わる処理を値オブジェクト内に閉じ込めることで、仕様変更に伴う修正箇所が明確になります。
  • 不正な値を存在させない: 値の正当性を確認するコードを値オブジェクト内に統一することで、不正な値の存在を防ぐことができます。
  • 誤った代入を防ぐ: 値オブジェクトを使用することで、タイプ不一致エラーを検出し、予測不能なエラーを減らすことができます。

値オブジェクトとして実装すべき基準

前提として、値オブジェクトを実装する基準に正解はなく、システムのコンテキストに合わせて判断する必要があります。 とはいえ、参考になるような基準が欲しいのも事実ですよね。 今回の勉強会では以下のようなものが、基準の例として挙げられていました。

「そこにルールが存在しているか」「それ単体で取り扱いたいか」

おわりに

今回のDDD勉強会では、「値オブジェクト」について学びました。 私自身、これまで設計から考える機会はあまり無かったので、DDDという設計手法を学ぶ良い機会になっています。 今後、「このコードは値オブジェクトか?」「他に値オブジェクトとして表現できるところはないか?」という視点を持って実装できそうです😊

スナックミーでは、エンジニアを積極的に探しています。この記事を読んで興味を持ってくださった方や、おやつが好きで「おやつと世界を面白くしていきたい」とお考えの方がいらっしゃれば、ぜひ応募していただけると嬉しいです!

engineers.snaq.me

募集ポジション

ソフトウェアエンジニア Backend (lead) / 株式会社スナックミー

ソフトウェアエンジニア FrontEnd (lead) / 株式会社スナックミー

ソフトウェアエンジニア Data Engineer / 株式会社スナックミー

スナックミーのエンジニアチームで直近動きたいこと (2024年1月)

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
四半期ごとにチームに共有している近期の取り組みたい内容を一部変更し、こちらのエンジニアブログに載せたいと思います。

シンプル化の追求

プロダクトの視点

2024年が始まりました。過去一年も様々な挑戦を経て、スナックミーはさらに成長を遂げました。多くの方にはおやつの定期便サービスとして認識されていますが、実は私たちは2022年4月から直営店の運営やナチュラルローソンをはじめとするコンビニへの卸事業、オフィス向けの定期便事業と、オンラインだけでなくオフラインでも接点を増やしています。「おやつ、と世界を面白く。」このパーパスのもと、お菓子を食べる楽しみだけでなく、ワクワクする体験を提供したいと考えています。

様々な接点を通じて、スナックミーのおやつとの出会いを豊かにし、プロダクトを通じてさらなる楽しみを提供していきたいと考えています。例えば:

  1. おやつの定期便を利用 -> 直営店の存在を知る -> 直営店を訪問 -> 好みのおやつを購入
  2. コンビニでスナックミーのおやつに出会う -> サービスを調べる -> ECサイトで購入 -> 定期便を始める
  3. 企業がおやつ定期便を利用 -> 様々なおやつと出会う -> 自宅用にも定期便を始める

など、出会いの循環を加速させるような動きを作っていきたいと考えています。

このような循環を加速させることが目標です。しかし、それには機能追加よりも大事ですが、迷いや不要な動きなどを減らすシンプル化が必要です。エンジニアチームとしても、この方針に基づき迅速に動いています。

prtimes.jp

office.snaq.me

開発の視点

プロダクトの理念をシステムにどう落とし込むかが重要です。サービス開始から9年が経過し、後回しにしていたリファクタリングや急ぎで開発した部分の再考が必要になり、開発速度の低下を感じています。過去3、4年の間には、以下のような取り組みを進めてきました。

labs.snaq.me

labs.snaq.me

小さく始めて、一歩ずつ改善を重ねることで、サービスを永続的に提供し、おやつ体験を向上させることが目標です。「サービス・フルサイクルエンジニアリング」を大切にしています。スナックミーは多様なシステムを通じてサービスを提供しており、事業の増加に伴い、システムの複雑性が増す可能性があります。それを防ぐためにも、シンプル化をテーマに選びました。シンプル化は「単純化」ではなく、「明快さ」を意味します。

Netflixの「フルサイクルエンジニアリング」に”リアル”を加えたスナックミーの「サービス・フルサイクルエンジニアリング」について

まとめ

シンプル化は四半期の目標に留まらず、常に意識すべきことです。スナックミーのエンジニアリングガイドラインには「Scale Out」という考えがあります。これは、会社、チーム、個人の成長に不可欠なマインドセットです。シンプル化を通じて、より良いサービスを提供し続けることが私たちの目標です。今四半期はこのテーマを特に強調しています。

おやつと、世界を面白く。」 一緒に面白くしたい仲間をお待ちしてます!!

meety.net

engineers.snaq.me

募集ポジション

ソフトウェアエンジニア Backend (lead) / 株式会社スナックミー

プロダクトマネージャー / 株式会社スナックミー

ソフトウェアエンジニア FrontEnd (lead) / 株式会社スナックミー

ソフトウェアエンジニア Data Engineer / 株式会社スナックミー

第5回【社内LT会】を開催しました🎉

こんにちは。スナックミーでエンジニアをしているタク(@yamataku3831)です。

つい先日、5回目の【社内LT会】を開催しました!今回もスナックミーの業務委託メンバーである“ぱーとなー”も多く参加してくれて、ワイワイしながら情報発信を行うことができました。

今回もスナックミーらしくおやつを楽しみながら…

今回もまたマレーシアを生活の拠点としている “ぱーとなー” が、現地で売られていたおやつを紹介してくださいました!

マレーシアのポテトのスナック菓子

Salted Egg Yolk Potato Crisps」というポテトのスナック菓子で、卵黄とチリパウダーがコーティングされている、やみつきになるお菓子とのことでした!さらに全て手作りで作られているらしく、商品によってちょっと違いがあったりするみたいです。なんだかちょっと心まで温かくなるようなお菓子ですね。食べてみたい!

発表内容

ボックスバランス

発表者:野澤

野澤 - 発表資料の一部切り抜き

野澤は主にデータ基盤構築やデータ分析を担当してくれているインターンメンバーです。最近はユーザーの「飽き」に対して改善できないかと、弊社のエンジニアである兼崎と二人三脚で日々様々なアプローチで分析・仮説・検証を繰り返してくれています。

解約時の「飽き」コメントが気になったことをきっかけに、チャーンユーザーとアクティブユーザーのボックス内容に違いがあるのではないかと SHAP 分析したところ、「8つのおやつのうち、”とあるカテゴリのおやつ” がひとつ入っていると飽きにくい」という考察を得られたとのこと。

そして、上記の仮説を検証するためにボックスに含まれる “とあるカテゴリのおやつ” の割合で A/B テストを行なったところ、ボックスに “とあるカテゴリのおやつ” がひとつ入っているとチャーンレートが低くなるという結果を得られました!

これまでカテゴリによるボックスバランスの調整を行ったことがなかったこともあり、この検証結果は今後の仮説検証を推進する大きな一歩となりました。インターンメンバーの活躍がきっかけで、今後のユーザー満足度に大きな影響を与えそうな、そんなワクワクする発表内容でした!

Rails 経験者が FastAPI 本を読んで感じたこと

発表者:タク

speakerdeck.com

私は前職で Ruby on Rails を使って6年間開発をしてきました。弊社も同じフレームワークで実装しているシステムもあるのですが、FastAPI での開発がメインになりつつあるため、エンジニア採用を担当する身として自社の技術に関して、キャッチアップしておきたいなと思うようになりました。

そこで「動かして学ぶ!Python FastAPI 開発入門」を手に取り、実際に手を動かしながら勉強しました。これまでサーバーサイドは Ruby on Rails でしか開発したことがなかったので、FastAPI が魅力的に感じる部分や、新鮮に感じる部分もあったので、それを忘れないよう言語化してみました!

スライドもありますが、テックブログにも文章でまとめていますので、興味のある方は併せてご覧ください。

labs.snaq.me

おわりに

今回で5回目の社内LT会でしたが、社会人がいる前で初めて発表してくれたインターンのメンバーも、みんなからのポジティブなフィードバックが嬉しかったのか、とっても嬉しそうな笑顔を見せてくれました。

チームメンバーが情報発信をしやすい土壌を整えたくて始めた社内 LT 会なので、メンバーのそういった嬉しそうな笑顔を見るととっても嬉しく感じます。これからもこの活動は続けていこうと思います!

そんなスナックミーでは、エンジニアを積極的に探しています!この記事を読んで興味を持ってくださった方や、おやつが大好きで「おやつと世界を面白くしていきたい」とお考えの方がいらっしゃれば、ぜひ応募していただけると嬉しいです。

engineers.snaq.me

募集ポジション

ソフトウェアエンジニア Backend (lead) / 株式会社スナックミー

ソフトウェアエンジニア FrontEnd (lead) / 株式会社スナックミー

ソフトウェアエンジニア Data Engineer / 株式会社スナックミー

FastAPI 0.100 (Pydantic v2)にアップグレードしました

こんにちは スナックミー CTO の三好 (@miyoshihayato) です

はじめに

スナックミーはマイページのAPI開発をFastAPIで開発し、フロントはReactで開発しています。

詳しくは FastAPIについての採用背景

labs.snaq.me

フロントの取り組み

labs.snaq.me

こちらをご覧ください。

背景・課題

以前は、品質とスピードの間にトレードオフがあるという思い込みにより、プロダクト開発を進めていました。その結果、アップグレードへの対応力が低く、アップグレード作業には多大な労力が必要でした。

上記込みで様々な課題を解決するために 2021年にPHPからFastAPIのリプレイスすると決めて動き始めました。 課題は先ほどのFastAPIの採用背景をご覧ください。要約すると以下が要因になります。

* 高学習コスト
* コードの質
* 心理的安全性

今年7月、FastAPIのアップグレードが発表され、これを機に変更容易性を意識したアップグレードに取り組みました。(今は 2023/12/7 現在 0.104.1 が動いてます)

FastAPIの対応

主に対応したところのリスト 主に Pydantic V2に関するアップグレードがメイン

  • dict() から model_dump() への移行
  • __root__ から RootModel への移行
  • __fields__ から model_fields への移行
  • class Config から model_config = ConfigDict() への移行
  • @validation から @field_validator@model_validator への移行
  • Optional の挙動の整理
  • Fieldexampleexamples への変更
  • deprecatedjson_schema_extra への変更

非推奨から推奨方法へ

  • dict() から model_dump()
  • __root__ から RootModel
  • __fields__ から model_fields
  • class Config から model_config = ConfigDict()

必須対応

  • Optional の挙動の整理
  • Fieldexampleexamples への変更
  • deprecatedjson_schema_extra への変更

Optional 挙動の整理

v1

class UserName(BaseModel):
    name: Optional[str] = Field(title="ユーザー名", example="須那田 クミ子")

以前は、nameがNoneの場合、Noneとして扱われましたが、v2ではエラーになるため、以下のようにFieldにNoneを宣言する必要があります。

v2

class UserName(BaseModel):
    name: Optional[str] = Field(None, title="ユーザー名", example="須那田 クミ子")

Fieldの仕様変更

  • example から examples
  • deprecated から json_schema_extra

v1

class UserName(BaseModel):
    name: Optional[str] = Field(
        None,
        title="ユーザー名",
        example="須那田 クミ子",
        deprecated=True
    )

v2では、exampleexamples に変更し、deprecatedjson_schema_extra で表現します。

v2

class UserName(BaseModel):
    name: Optional[str] = Field(
        None,
        title="ユーザー名",
        examples=["須那田 クミ子"],
        json_schema_extra={"deprecated": True}
    )

Python 3.11の対応

Python 3.11へのアップデートは、Pythonのバージョンを3.8から3.11にすることによるもので、主に以下の点に対応しました。

  • Dict, List, Optional, Tuple, Union の基本廃止
  • インポート量の削減
  • コードのシンプル化

3.8

class SnaqmeInfo:
    def __init__(self) -> None:
        self.__name = str
        self.__items: List[Dict[str, int]] = []
        self.__status: Optional[Union[int, List[int]]] = None

3.11

class SnaqmeInfo:
    def __init__(self) -> None:
        self.__name = str
        self.__items: list[dict[str, int]] = []
        self.__status: int | list[int] | None = None

まとめ

今回のアップグレードは、時間的にも問題的にもスムーズに本番環境へデプロイできました。時間は合計で10時間ほどで20時間はかかってないと思います。 その成功要因としては、

  • テストのカバレッジが高い(95%以上)
  • ステージング環境での事前テスト
  • 異常検知システムの有効活用

などが挙げられます。新しい取り組みではなく、基本をしっかり押さえることがスケールアップにおいて重要であると改めて実感しました。弊社は全てを完璧に押さえることは難しいかもしれませんが、常に意識して取り組むことで、現状と将来への最善のバランスを図りながら、チームとしての開発を進めていきたいと考えています。

また、Pydantic V2へのアップグレードにより、型の取り扱いがより厳密になったと感じています。これにより、以前は曖昧だった部分をより明示的に表現しやすくなったという印象を持っています。

engineers.snaq.me

募集ポジション

ソフトウェアエンジニア Backend (lead) / 株式会社スナックミー

ソフトウェアエンジニア FrontEnd (lead) / 株式会社スナックミー

ソフトウェアエンジニア Data Engineer / 株式会社スナックミー

Rails 経験者が FastAPI 本を読んで感じたこと

こんにちは。スナックミーでエンジニアをしているタク(@yamataku3831)です。

私は前職で6年間 Ruby on Rails で Web サービスを開発してきました。スナックミーでも Ruby on Rails で開発しているシステムがあるのですが、コンシューマー向けの Web サービスに関しては FastAPI と React を使って開発をしています。

エンジニア採用を担当する身として、自分たちが使っている技術についてもしっかり伝えられるようになりたいと思い、「動かして学ぶ!Python FastAPI開発入門」という本を手に取りました。

www.shoeisha.co.jp

サーバーサイドは Ruby on Rails でしか開発してこなかったため、この本を読んで FastAPI が魅力的に感じたり、新鮮さを感じたりする部分がありました。今回はそれらを言語化したいと思います。

私と同じように Ruby on Rails で開発をしていて、FastAPI に興味を持っている方々の判断材料になれば嬉しいです。

FastAPI に魅力を感じたところ

FastAPI 自体に魅力を感じたところや、この本の中で紹介されているものに興味をそそられたものがあったので、3つにまとめてみました。

不具合を型定義により未然に防ぐ

そもそも Python は動的型付け言語でありながらも、昨今の型を重視するトレンドに例に漏れず、「型ヒント」の仕組みが導入されています。「型ヒント」を使用することで⁠コードの可読性を向上⁠したり⁠、⁠IDEによるコード補完を充実⁠させたりすることができます。

一方で、通常「型ヒント」は実行時に影響を及ぼさない仕様になっていることが多いようです。そのため、以下のように指定した型とは異なる値を変数に格納しても、Python ではエラーを発しません。

>>> order: int = 1
>>> order = ‘string’

しかし、FastAPI では Pydantic というライブラリの力によって、API の入出力のバリデーションを行う仕様となっています。 それにより、想定していない値を返却すると以下のようなエラーが発生するようになっていて、フロントエンドとバックエンドを接続して動作確認をする前に不具合を検知することができるというメリットがあります。

pydantic.error_wrappers.ValidationError: 1 validation error for Task
number
  value could not be parsed to a integer (type=type_error.integer)

Ruby 3.0 で Ruby にも型定義情報を提供する RBS という仕組みが導入され、Ruby on Rails でのアプリケーション開発でも型を使った開発が可能になっているようです。私は当時型を使用してプロダクト開発をしていなかったため、開発効率を向上する上で非常に強力な仕組みだと感じました。

特に TypeScript の普及によって型を使用した開発が一般的になってきたフロントエンド開発との相性が良さそうという印象を受けました。

フロントエンドとの協業をスムーズにする仕組み

昨今のプロダクト開発において、フロントエンドとバックエンドを分離して開発する場合が多く見られるような気がします。FastAPI はそういった開発において本領を発揮するフレームワークであるとも感じました。

FastAPI では Router にパスオペレーション関数を定義し、リクエストとレスポンスのスキーマを定義した段階で、Swagger UIのドキュメントと API モックが自動的に提供されます。

swagger.io

つまり、ビジネスロジックなど詳細な実装に手をつける前に、フロントエンドの実装に着手することができます。また継続したプロダクト開発において起こりがちな、ドキュメントの更新漏れやそれに伴って起きる実装の手戻りなどを、普段の開発の流れで解消することができる仕組みにもなっています。

以前 Ruby on Rails で開発していた時は、API を追加したり更新した場合は「忘れずに Apipie-rails のドキュメント部分も更新しましょう」といった呼びかけをしていました。そういった中で更新が漏れたりすると「やってしまった」とネガティブな気持ちになりがちだったことを思い出しました。

そういったことがこの仕組みによって改善され、精神衛生を保つことにもつながり、モチベーションとパフォーマンスを高く保ちながら開発できるひとつのソリューションになりそうだと感じました。

保守性を高めやすいアーキテクチャ

これは FastAPI 自体の魅力というよりかは、この本の中で紹介されていたアーキテクチャーに関する魅力なのですが、MVC モデルで問題になりがちな Controller や Model の肥大化を、極力避けれるような設計がなされています。

これは FastAPI のフレームワーク自体がもつ高い自由度だからこそ為せる業になりますが、この本では CRUD の処理が Router(MVC における Controller)から切り離されていました。これによって、処理やロジックをカプセル化できるようになるため、再利用性が向上することで、テスタビリティも向上することができます。

Ruby on Rails で開発している時は、Controller の中で CRUD の処理を書いていました。ただでさえ条件分岐が多くなりがちな Controller で、条件付きのデータアクセスを実装すると、その分だけテストケースが増えてしまいます。その条件分岐やそれを網羅するためのテストを Controller の外でかけるのは、非常にありがたいなと感じました。

FastAPI に新鮮さを感じたところ

自前でエクセプションを返す必要がある

この本では、データが見つからなかったときに 404 の HTTP エクセプションを返す処理を自身で実装していました。

私はこれまで Ruby on Rails で開発してきて、Active Record や Action Controllerといった仕組みの恩恵に与ってきていたこともあり、自身でエクセプションを返さなくても自動的に 404 エラーを返せていました。

そういったこともあり、実装者自ら明示的にコーディングしている点に新鮮さを感じました。忘れないよう都度実装したり、スナックミーのテックブログでも紹介させていただいているようにモジュール化したりする必要がありそうです。

labs.snaq.me

自由度の高い設計が可能

Ruby on Rails の設計理念のひとつに「CoC(Convention over Configuration: 設定より規約)」というものがあります。前職では可能な限り Ruby on Rails の規約に則り、Rails Way に沿ったプログラミングをしていたので、特にチーム開発において余計なコミュニケーションコストやドキュメント管理コストなどが発生しないよう意識しながら開発していました。

rubyonrails.org

一方で FastAPI は Ruby on Rails と比べると自由度が高いため、チームでどういった設計で実装していくのか、しっかり認識を合わせた上で開発していく必要があると感じました。現に弊社では DDD を意識して開発していることもあり、domain というディレクトリを作って、そこに各ドメインに依存したロジックなどをまとめていたりします。

おわりに

開発言語が異なる環境に転職をして、実装で苦労する部分などはありますが、様々なフレームワークについて触れることで、それぞれの良さや違いを感じることができました。またそれらを言語化することによって、開発する上で何に注意するべきかなどを把握する助けにもなると実感しました。

スナックミーは FastAPI をかなり早い段階からプロダクションで開発してきたので、今後も様々なチャレンジをして、情報を発信できるように頑張っていきたいと思います!

そんなスナックミーでは、エンジニアを積極的に探しています!この記事を読んで興味を持ってくださった方や、おやつが大好きで「おやつと世界を面白くしていきたい」とお考えの方がいらっしゃれば、ぜひ応募していただけると嬉しいです。

engineers.snaq.me

募集ポジション

ソフトウェアエンジニア Backend (lead) / 株式会社スナックミー

ソフトウェアエンジニア FrontEnd (lead) / 株式会社スナックミー

ソフトウェアエンジニア Data Engineer / 株式会社スナックミー

第4回【社内LT会】を開催しました🎉

こんにちは。スナックミーでエンジニアをしているタク(@yamataku3831)です。

つい先日、4回目の【社内LT会】を開催しました!今回もスナックミーの業務委託メンバーである“ぱーとなー”も多く参加してくれて、ワイワイしながら情報発信を行うことができました。

今回もスナックミーらしくおやつを楽しみながら…

今回は、先月韓国に帰省したときに私がスーパーで買ったおやつを紹介しました!韓国の돼지바(テジバー)というアイスクリームと、食べたことがある方もきっと多い CRUNKY がコラボしてできたお菓子です。

韓国のチョコ菓子!

ちょっとスナックミーっぽくないので、紹介しようか迷いました(笑)。ただこういう商品からインスピレーションをもらって、スナックミーらしい商品として開発につなげることもできるかも?と思ったので、今回持ってきてみた次第です。

発表内容

MSX の昔と未来

発表者:大場

大場 - 発表資料の一部切り抜き

大場はマレーシアを拠点とし、スナックミーの開発に携わっている”ぱーとなー”です。彼が「MSX」という機械をきっかけにプログラミングと出会ったのは、小学生の頃。ブラウン管と MSX を繋いでプログラミングを行い、ゲームを作って遊んでいたんだとか。

一般の人たちが MSX で作ったゲームのソースコードを集めた雑誌もあったみたいで、そのソースコードを見ながら MSX に直打ちして、同じファンの人が作ったゲームを楽しんでいたようです。なんだかエモい気持ちになりますね…!

MSX は現在も開発が続いていて、IoTプロダクトや Watch なども作られているみたいです。スナックミーとしても「ファンに愛されるプロダクト」を目指していきたいなと改めて実感しました。

フロントエンドのベースライン

発表者:小林

小林 - 発表資料の一部切り抜き

小林はフロントエンド開発に携わっている”ぱーとなー”です。今回はブラウザやモバイル OS における市場シェアや、各ブラウザにおける標準仕様の比較、iOSAndroid のCPU パフォーマンスなどを調査してくれました。

その調査結果から、Safari の標準仕様に合わせて実装することや、低スペックな Android 端末にも対応できるようパフォーマンスを考えて実装することの大切さが理解できるように。

突き詰めるとユーザーのことを考えて画面を実装することに繋がりますが、ブラウザの仕様やモバイル端末のスペックとも戦いながら実装していると知れて、改めてメンバーに対して尊敬の念を抱くよいきっかけとなりました。

おわりに

今回で4回目の社内LT会でしたが、発表してくれたメンバーからも「貴重な体験になりました!また機会があればぜひ!」と嬉しい言葉をもらえるようになってきました。

チームメンバーが情報発信をしやすい土壌を整えたくて始めた社内 LT 会なので、メンバーからそういった言葉をかけてもらうととっても嬉しく感じます。これからもこの活動は続けていこうと思います!

そんなスナックミーでは、エンジニアを積極的に探しています!この記事を読んで興味を持ってくださった方や、おやつが大好きで「おやつと世界を面白くしていきたい」とお考えの方がいらっしゃれば、ぜひ応募していただけると嬉しいです。

engineers.snaq.me

募集ポジション

ソフトウェアエンジニア Backend (lead) / 株式会社スナックミー

ソフトウェアエンジニア FrontEnd (lead) / 株式会社スナックミー

ソフトウェアエンジニア Data Engineer / 株式会社スナックミー

Framer Motion でアニメーションを実装する

こんにちは。スナックミーでフロントエンドをやっている前田です。
今回は、 snaq.me のマイページで使用している Framer Motion についてご紹介したいと思います。

Framer Motion とは

Framer Motion は複雑なアニメーションを簡単に実装できる React のライブラリです。
シンプルな例では、下記の記述で左右に 100px ずつ跳ねながら移動する円を描画できます。

<motion.div
  style={{
    width: 30,
    height: 30,
    background: "red",
    borderRadius: 30
  }}
  initial={{ translateX: 0 }}
  animate={{ translateX: 100 }}
  transition={{
    type: "spring",
    bounce: 0.5,
    repeatType: "mirror",
    duration: 2,
    repeat: Infinity
  }}
/>

デフォルトでバネのようなエフェクトが使用され、 bounce のオプションに 0~1 の少数値を指定することにより、その動きを制御できます。

bounce: 1 を使用する事はほとんどありませんが、ユーザーにしっかり気づいて欲しいインタラクションには高め(0.6 ぐらい)、自然に振る舞って欲しいときには未指定(デフォルトが 0.2)を使用する事が多いです。

なぜ Framer Motion を使うのか

Web でアニメーションを表現するには、多くの手法が存在します。

  1. gif や スプライト画像でアニメーション
  2. Lottie を使用して JSON ファイルで管理
  3. CSS アニメーション
  4. JavaScript で DOM やスタイルなどを操作

それぞれ一長一短あり、 1 や 2 はデザイナーとの協業が必要となることなどから、 UI デザインに携わらないフロントエンド実装者が主導する場合は 3, 4 の手法を取ることが多いのではないでしょうか。
しかし、どちらも少し手間がかかり、 実装時にアニメーションを定義することを習慣づけるのは難しいと考えています。

どのような手間が必要となるか、例として簡単なフェードアニメーションを実装する場合を見てみましょう。
styled-components では以下のように書けます。

const Block = styled.div<{ isShow: boolean }>`
  opacity: ${({ isShow }) => isShow ? 1 : 0};
  transition: opacity 0.2s ease;
`;

これを Props を受け取らず、レンダリング後にフェードインするように変更するには keyframes を使用します。

const fadeIn = keyframes`
  to { opacity: 1 }
`;

const Block = styled.div`
  opacity: 0;
  animation: ${fadeIn} 0.2s ease-in-out forwards;
`;

opacity の定義が分断されたことにより、少し複雑さが増してしまいました。
さて、このコンポーネントを何らかの UI 操作などでフェードアウトさせたい場合はどうなるでしょう。

const fadeIn = keyframes`
  to { opacity: 1 }
`;

const fadeOut = keyframes`
  from { opacity: 1 }
  to { opacity: 0 }
`;

const Block = styled.div<{ isShow: boolean }>`
  opacity: 0;
  animation: ${({ isShow }) => (isShow ? fadeIn : fadeOut)} 0.2s ease-in-out forwards;
`;

やり方はいろいろあるかと思いますが、今回は animation-name を Props で切り替える仕様にしてみました。
keyframes の定義が増え、fadeOut は from の記述が必須(fadeIn は不要)となるなどさらに複雑性が増したように感じます。
このような手間が必要となれば、つい isShow && <Block /> と書いてしまいたくなるのも頷けるのではないでしょうか。

今度は同様の仕様を Framer Motion で記述してみましょう。

<motion.div
  initial={{ opacity: 0 }}
  animate={{ opacity: isShow ? 1 : 0 }}
/>

だいぶすっきりしましたね。 フェードだけだと描画エリアを確保してしまうので、要素ごと消す場合には以下のように書きます。

<AnimatePresence>
  {isShow && (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    />
  )}
</AnimatePresence>

このように、インタラクションの定義を自然に取り込める記述性の高さと、type: "spring" による目を引くエフェクトの実装しやすさから、 snaq.me では Framer Motion を採用しています。
ここまで小さなサンプルコードで紹介してきましたが、やや実践的なサンプルとして snaq.me で良く使うエフェクトもご紹介します。

Framer Motion を使う場合に考慮すべき点

Framer Motion の良いところばかりを紹介してきましたが、いくつか注意すべき点も存在します。

バンドルサイズが大きい

アニメーションのための機能が数多く提供されている Framer Motion ですが、バンドルサイズが膨らみがちな点が大きなデメリットであると考えています。
Bundlephobia によると、 minified された状態でおよそ 126KB ものファイルサイズになるようです。

bundlephobia.com

この問題については Framer Motion のドキュメントに記載されているので、目的に合わせて設定することでバンドルサイズを減らす事ができます。

www.framer.com

他のライブラリと記法が競合する

Framer Motion では styled(motion.div) などと書くことにより、初期状態のスタイルを styled-components で書くことが可能です。 しかし、 animateexit で指定するプロパティを initial 以外で指定していると正しく動作しないため、 競合しないように管理する必要があります。

// 上手く動かない
<Block animate={{ translateX: 0 }}  />

const Block = styled(motion.div)`
  transform: translateX(100);
`;

これにより、スタイルで JSX の宣言が肥大化して読みづらくなったり、事前に styled-components で定義されているスタイルにアニメーションを付けようとして上手くいかなかったりなどの問題が発生しています。 弊社では Framer Motion を使うときは style を使用するようにして回避していますが、オブジェクト形式で CSS を書くのに抵抗がある方や、デフォルトのスタイルを styled-components に寄せたい場合には問題となる可能性があります。

JSX からスタイルの定義をを分離する手法としては styled-components の attrs を使用する事でも可能ですが、 attrs の generic に MotionProps を渡す手間が発生します。

const Block = styled(motion.div).attrs<unknown, MotionProps>(() => ({
  initial: { opacity: 0 },
  animate: { opacity: 1 },
}))``;

SVG を埋め込みすぎるとバンドルサイズが大きくなる

強力で便利な Framer Motion ですが、 SVG でアニメーションで多用しすぎるとバンドルサイズを大きくする要因となります。 Framer Motion では SVG の path, g, rect, circle などのタグを motion.path, motion.g, motion.g, motion.g などに変えることで個別にアニメーションを指定可能ですが、 d 属性なども同時にバンドルされてしまうため、配信される JS ファイルのサイズに影響を及ぼします。
ファイルサイズの大きい SVG には

  • Dynamic import を使用する
  • ラフ加工されてアンカーポイントの多い SVGPNG にする
  • フロントエンドでのアニメーションは諦める(GIF や Lottie などを使う)

など、状況に合わせた対策が必要です。

まとめ

snaq.me で導入してから2年ほどが経過しましたが、今では snaq.me のマイページの多くの場所で使用しています。
その中でも特に多用しているのが snaq.me の eGift で、オンラインメッセージカードのインタラクションは Framer Motion を使用して実装しています。
近日中に、りっす(弊社の公式タレント)のかわいいクリスマス eGift カードが公開となるので是非覗いてくださいね。
(画像は実装途中の物です)

余談ですが、りっす氏は顔の作りがシンプルでデータ量が少なく、アニメーションを付けやすいのも推しポイントです。