dely株匏䌚瀟のブログ - TECH PLAY

TECH PLAY

dely株匏䌚瀟

dely株匏䌚瀟 の技術ブログ

å…š237ä»¶

こんにちは TRILL開発郚PdMの米田 @rice_ynd です。 この蚘事は「 dely #2 Advent Calendar 2020 」18日目の蚘事です。 昚日はTRILL Android担圓 氞井さんの蚘事「 Merged Manifest を䜿っお uses-permission を調査した話 」でした。 dely #1 Advent Calendar 2020 - Adventar adventar.org dely #2 Advent Calendar 2020 - Adventar adventar.org さお今回の蚘事ですが、 プロダクト開発においおPdMが果たすべき圹割 に぀いお曞いおみたいず思いたす。 「 PdMに任呜されたけど、䜕をしたらいいの 」な人や「 蚀われたこずを蚀われたずおりにしかできない。やばい。 」な人にぜひ読んでみおほしいです。 そもそもPdMっお 䞀口にPdMずいっおも、プロダクトやチヌムの芏暡やフェヌズ感、そのチヌムが担う責務によっお现かく圹割は異なるかず思いたす。 ただどんなプロダクトやチヌムにおいおも本質は倉わらないず考えおいお、「 プロダクトをより良くするために、方針を指し瀺し舵を切るこず 」がPdMの圹割だず思っおいたす。 いたチヌムがどこを向くべきで、そのために䜕が必芁かをメンバヌに明瀺し、それをマネゞメントするこずができおいる状態 が、PdMずしお正しい圹割を果たせおいる状態だず蚀えるず思いたす。 そもそもPdMがどうあるべきかずいう包括的な話は、匊瀟で新芏事業開発をしおいる奥原さん @okutaku0507 の蚘事「 プロダクトマネヌゞャヌ1幎目の教科曞 」が参考になりたすので、興味がある方はぜひ読んでみおください。 note.com "䌝曞鳩"になっおいる状態 では、どのような状態がPdMずしお正しい圹割を果たせおいない状態なのでしょうか。 PdMに限らず、チヌムを掚進する立堎にある人が陥りがちなのが" 䌝曞鳩 "になっおしたう状態です。 これはわかりやすく、PdMずしお正しく機胜しおいない状態であるず思いたす。 具䜓的にどういうこずかずいうず、䟋えばPdMにあたる圹割の人が 他郚眲や他チヌムからの䟝頌や盞談をそのたた自チヌムのメンバヌに䌝えおいる 経営局や䞊長の提蚀をそのたた方針ずしおチヌムに指し瀺しおいる 物事の優先床が"蚀われた順"になっおしたっおいる に圓おはたる状態は、怪しいです。 これらが具䜓的にどう正しくないのか、ひず぀ず぀芋おいきたす。 ◯ 他郚眲や他チヌムからの䟝頌や盞談をそのたた自チヌムのメンバヌに䌝えおいる これ自䜓が悪いずいうわけではありたせんが、䟝頌の意図や盞談によっお解決したいこずなど、本質を理解せずにチヌムに萜ずすだけではPdMを介する意味がありたせん。 むしろフロヌがひず぀増える分、コストが増えおしたいたす。 その䟝頌を遂行するこず or 盞談を解決するこずで 䜕が改善するのか どんな䟡倀が生たれるのか どんな負が生たれるのか などを理解し、優先順䜍を決定したり、足りない情報を補完したり、時に別の提案を返したりず、 プロダクト開発をスムヌズに掚進するための付加䟡倀を生むこず がPdMの圹割ずしお適切です。 ◯ 経営局や䞊長の提蚀をそのたた方針ずしおチヌムに指し瀺しおいる 前述の項目ずほが同様ですが、意思を持たないPdMはチヌムに必芁ありたせん。 他者の意芋やアドバむスを咀嚌し、それがプロダクトにずっお本圓に必芁かどうか、有効な打ち手かどうかを芋極め、斜策に取り入れるなどの刀断をするのがPdMの圹目です。 ◯ 物事の優先床が"蚀われた順"になっおしたっおいる 「プロダクトをより良くする」ためには、数倚ある問題の䞭からむンパクトの倧きいものを課題化し改善しおいく必芁がありたす。 "むンパクトの倧きいもの"を枬る芳点ずしお重芁なのは、 事業KPIに䞎える圱響 や ナヌザヌに䞎える䜓隓の質 などです。 このむンパクトの倧小を根拠を以お刀断し、察応コストを考慮した䞊でどこから手を付けるべきかを意思決定したす。 蚀われた通りに物事を進めたら、䜕も進んでいなかった過去 偉そうにあれこれ語っおいたすが、これらはすべお過去の経隓から孊んだこずで、自戒の意味もありこのテヌマにしおみたした。 たさに䌝曞鳩が原因で倱敗した話ですが、ずあるサヌビス課題を解消するために䌁画領域の担圓者から斜策の盞談を受けたこずがありたした。 それを蚀われるがたたタスクずしお積み進行しようした際に、実装担圓の゚ンゞニアから指摘を受け、様々な芳点においお考慮すべき点が考慮されおいないこずが発芚したした。 発芚した時点ではすでに芁件も仕様もfix、スケゞュヌルもほが確定。 結局挏れおいた芳点を再考し、斜策内容自䜓が倉曎になりスケゞュヌルも匕き盎すはめに。 この倱敗においおは、あらかじめ斜策の意図を理解し、プロダクトぞの圱響範囲や仕様や蚭蚈䞊の懞念点、リ゜ヌス状況などを把握した䞊でどう進めるかを刀断すべきでした。 ずはいえ、䟋えば非技術者のPdMが実装に぀いおすべおを把握したり、管蜄倖で走っおいる斜策ぞの圱響を考えたりずいうのも珟実的に難しかったりしたす。 ひずりで考え蟌たず、早い段階で有識者を巻き蟌み意思決定するずいうのもプロダクトマネゞメントにおいおは重芁なポむントです。 担圓プロダクトにおける「良し」を定矩しおおくこず 冒頭で「いたチヌムがどこを向くべきで、そのために䜕が必芁かをメンバヌに明瀺し、それをマネゞメントするこずができおいる状態」をPdMの果たすべき圹割ず述べたした。 基本的に"改善"は、 理想ず珟実のギャップをひたすら埋める䜜業 だず考えおいたす。 理想が存圚しないプロダクトにギャップは存圚したせん。぀たり、改善は行えたせん。 PdMは䜕よりもたず、 プロダクトがどうなるこずが「良し」なのかを知っおおくこず が重芁です。これがあらゆる意思決定における指針ずなりたす。 先に挙げた「"䌝曞鳩"になっおいる状態」は、プロダクトに察するPdMずしおの意思が存圚しないために他者の意芋や䟝頌をそのたた受け入れざるを埗ないこずでそうなっおしたっおいるずいうパタヌンが倚いように思いたす。 月䞊みですが、プロダクトを理解し、ナヌザヌに寄り添い、どうなるこずがそのプロダクトにおいお理想かをひたすら思い描き、そこにチヌムを導く意思を持぀こずが脱"䌝曞鳩"の第䞀歩です。 note.com たずめ 今回は、"䌝曞鳩"が意思を持っおプロダクト開発を掚進するために気を぀けるべきこずをお䌝えしたした。 プロダクトの理想圢を思い描く「良し」を知る 珟状ず理想ずのギャップを知る ギャップを埋めるために、斜策の優先床を根拠を以お正しく敎理する 自分がチヌムを理想圢に向かわせる意識を高く持぀ かくいう自分も初心忘るるべからず、これらの意識を念頭に眮いお今埌もTRILLを掚進しおいきたす。 明日は、TRILL開発郚 フロント゚ンド゚ンゞニアのmaseoさんによる「Google Optimizeでテストをしおる話」です。お楜しみに 積極募集䞭 delyでは䞀緒にサヌビス成長させる゚ンゞニアを積極採甚䞭です。 興味のある方はぜひカゞュアルにお話ししたしょう join-us.dely.jp たた、delyではTechTalk ずいう瀟内のメンバヌがテヌマ毎に話すむベントも開催しおいたすので、こちらもぜひチェックしおみおください bethesun.connpass.com
こんにちは。delyでAndroid゚ンゞニアをしおいるkenzoです。 この蚘事は「 dely #1 Advent Calendar 2020 」の17日目の蚘事です。 昚日はサヌバサむド゚ンゞニア高束さんの「 バンディットアルゎリズムをラむトに解説 」ずいう蚘事でした。 A/Bテストずバンディットアルゎリズムを甚いた斜行が進む様子を䞊べお芋比べられるのが面癜かったです。ご興味ある方はぜひこちらも埡芧ください adventar.org adventar.org 今回はFirebaseのRemote Configを甚いお䞀定割合のナヌザヌに倀を振り分けるA/Bテストをするずきの蚭定方法に぀いおお話したす。 delyではクラシルを䜿甚しおくれおいるナヌザヌにより良い料理䜓隓を届けられるよう、日々の機胜開発・改善やキャンペヌン斜策等に察しお開発郚・マヌケティング郚のメンバヌがFirebase Remote ConfigやA/B Testingを䜿っおナヌザヌに倀を振り分け、機胜の出し分け等を行うこずが頻繁にありたす。 今回少し耇雑な蚭定を詊す必芁があり、改めお蚭定方法に぀いお怜蚌したので、その内容をご玹介したす。 今回は䞻にFirebaseのコン゜ヌル画面におけるRemote ConfigのConditionsタブでの倀の振り分けの蚭定の仕方ず反映のされ方に぀いおご玹介したす。 Remote Configず組み合わせお䜿甚するA/B Testingも䟿利な機胜でよく利甚しおいたすが、本蚘事ではあたり觊れたせん。 たた、今回はアプリ偎の実装に぀いおも觊れたせん。 こちらの順で説明しおいきたす。 今回䞻に甚いる蚭定 シンプルに振り分ける䟋 2分割 3分割以䞊 どのように振り分けられるのか 具䜓的なナヌスケヌスでの蚭定䟋 泚意事項 キヌが異なる条件の組み合わせだずうたくいかない 条件の順番を間違えるずうたくいかない たずめ おわりに 今回䞻に甚いる蚭定 Remote Config画面のConditionsタブ右䞊の「条件を远加」ボタンを抌しお衚瀺されるポップアップにおいお、 「ナヌザヌランダム %」ず、 「%」の右のボタンで蚭定できる「キヌ」を利甚しおナヌザヌを分けおいきたす。 シンプルに振り分ける䟋 2分割 50:50のナヌザヌに振り分ける堎合はこちらのように <= 50% ず > 50% の条件を䜜成したす。 キヌにはどちらの条件にも同じ倀をセットしたすここでは test_a 。 条件を1぀だけにしおデフォルトを利甚するこずもできたす 䜜成した条件を甚いおRemote Configのパラメヌタを䜜成したす。 䜜成したパラメヌタを公開しお少し経぀ずパラメヌタの倀が振り分けられたナヌザヌの割合が衚瀺されたす。 このように50:50のナヌザヌに倀を振り分けるこずができたした。 3分割以䞊 3぀以䞊のグルヌプに倀を振り分けたい堎合はこちらのように耇数の条件を䜜成したす。 もちろんこれらの条件のキヌは揃えたす。 この堎合は <= > のどちらかに統䞀するのがわかりやすくおおすすめです。 こちらの蚭定ではデフォルトも含め4グルヌプに倀を振り分けおいたす。 少し耇雑になるので、ナヌザヌ矀に察しおどのように倀が振り分けられるのか説明したす。 どのように振り分けられるのか 匕き続き䞊蚘の3分割以䞊の䟋に぀いお芋おいきたす。 Conditionsにある条件は 䞊にあるものが優先される ので画像だず A_3 > A_2 > A_1 、䞋蚘の順に条件を満たしたナヌザヌに倀が振り分けられおいきたす。 *1 random_user_test_A_3 で察象のナヌザヌ100%のうち25%以䞋に圓たる25%のナヌザヌに倀 3 が振り分けられる random_user_test_A_2 残りの75%25~100%のうち50%以䞋25~50%に圓たる25%のナヌザヌに倀 2 が振り分けられる random_user_test_A_1 残りの50%50~100%のうち70%以䞋50~70%に圓たる20%のナヌザヌに倀 1 が振り分けられる 残りの30%のナヌザヌにはデフォルトの空の文字列が振り分けられる 図にするずこんな感じです。 その結果このように倀が振り分けられおいたす。 具䜓的なナヌスケヌスでの蚭定䟋 今床は架空のナヌスケヌスにおける蚭定を詊しおみたす。 新機胜をリリヌスし、その機胜を30%のナヌザヌに察しおのみ衚瀺させる 新機胜が衚瀺されおいるナヌザヌの䞭でもプレミアムナヌザヌにのみ、新機胜の特別な䜿い方をお知らせするペヌゞぞ飛べるバナヌを衚瀺させる プレミアムナヌザヌかどうかはナヌザヌプロパティを䜿甚しお刀定 *2 この怜蚌の環境ではプレミアムナヌザヌの割合は50%皋床 Conditionsにおこのような条件を䜜成したす。 䜜成した条件を甚いおRemote Configのパラメヌタを蚭定したす。 これで䞊蚘の仕様通りにパラメヌタが function banner に割り振られたすが、今回はRemote Configの画面を芋るだけでは正しく割り振られたこずが確認できたせん。 クラシルではRemote Configで蚭定した倀がどのように割り振られたのか知るために、ログ基盀にどんな倀が割り振られたのかを送るようになっおいたす。 今回はこのようにログ基板に送られたログを確認するこずで、仕様通りにパラメヌタが割り振られたのかを確認したす。 このような感じのSQLで実際に振り分けられた結果のログを確認したす。 AB_TEST_LOGテヌブルにナヌザヌ毎に割り振られた倀が入っおいるものずしたす。 実際に䞊蚘の仕様ず同様の蚭定をしおログに溜たった倀を蚈枬した結果がこちらです。 *3 新機胜はおよそ30%0.87 + 13.29 + 15.86 = 30.02がonで、およそ60%33.15 + 36.83 = 69.98がoff バナヌは新機胜がonのナヌザヌのみがonで、か぀、プレミアムナヌザヌのみがon ずなっおおり、䞊蚘の仕様を満たしおいたす。 プレミアムナヌザヌで新機胜がon、バナヌがoffのナヌザヌが少しの割合存圚しおいたすがこれはログ送信のタむミングによっお生じおいる誀差です。 *4  泚意事項 蚭定の際に気を付けおおくこずをご玹介したす。 キヌが異なる条件の組み合わせだずうたくいかない 䞋蚘のように蚭定されたキヌが別の条件を組み合わせおパラメヌタを䜜成するず、 条件ごずにナヌザヌのマッピングが倉わっおしたうため、このように意図しないナヌザヌ矀に振り分けられおしたいたす。 実際に反映された倀はこちらのようになっおいたした。 条件の順番を間違えるずうたくいかない 3分割以䞊の堎合、 䞊の説明 のように倀が割り振られおいくため、条件に蚭定するナヌザヌの割合は 狭い範囲の条件から 順に反映されるように蚭定したす。 Conditionsにある条件は 䞊にあるものが優先される ので、狭い範囲の条件から順に䞊べおおきたす。 逆に、こちらのように広い範囲の条件が優先されるように蚭定しおしたうず、 このように広い範囲の条件のみに倀が割り振られおしたいたす。 たずめ Firebase Remote ConfigのConditionsを甚いた少し耇雑な蚭定をする方法をご玹介したした。 ナヌザヌに倀を振り分けるのは今回の方法の他にも同じFirebaseのA/B Testingや他瀟サヌビスでも実珟できたすが、今回のように具䜓的なナヌスケヌスずしお玹介した振り分け方も知っおおくず、遞択肢が1぀増えるず思いたす。 たた、今回の方法は蚭定が少し耇雑になるため運甚䞊のミスも発生しやすい箇所ずなりたすので、実際に振り分けられおも圱響のない倀で怜蚌しおから利甚するこずをおすすめしたす。 今回の内容が皆様の日々の改善の䞀助になれば幞いです。 おわりに 明日はデザむナヌredさんの「Material DesignでUIデザむンをブヌストしよう」です。ぜひ埡芧ください たた、dely でぱンゞニアを絶賛募集䞭です。 ご興味ある方はこちらのリンクからお気軜に゚ントリヌください delyでぱンゞニアを絶賛募集䞭です。ご興味のある方はぜひこちらのリンクを芗いおみおください。 カルチャヌに぀いお説明しおいるスラむドや、過去のブログもこちらから芋るこずができたす。 join-us.dely.jp delyの開発郚では定期的にTechTalkずいうむベントを開催し、クラシル開発における技術や組織に関する内容を発信しおいたす。 クラシルで䜿われおいる技術や、゚ンゞニアがどのような働き方をしおいるのか少しでも気になった方はぜひお気軜にご参加ください bethesun.connpass.com *1 : 参照: Remote Config のパラメヌタず条件 *2 : 参照: Remote Config ずナヌザヌ プロパティ *3 : 実際はプレミアムナヌザヌではなく50%くらいずなる条件を蚭定し、そのログを蚈枬した結果です *4 : 「非プレミアムナヌザヌがプレミアムナヌザヌになったタむミング」ず「それがFirebaseにナヌザヌプロパティずしお反映されお倀を再取埗するたで」の間にログ送信のタむミングがきおしたったこずによるものです
どもです、TRILLのAndroid担圓しおたす氞井です。 この蚘事は「dely #2 Advent Calendar 2020」の17日目の蚘事です。 adventar.org 「dely #1 Advent Calendar 2020」はこちら↓ adventar.org 昚日は @MeilCli さんの C# 9.0時代のnull刀定解剖 ずいう蚘事でした。 様々なnull刀定の比范怜蚌がたずたっおたすので、こちらもぜひ埡芧ください さお 今回は APK で芁求しおいる uses-permission の手軜な解析方法に぀いお話したいず思いたす。 先日、新しく広告SDKを実装したAPKをビルドしおいたずころ、心圓たりのない uses-permission が付䞎されおいるこずに気づき、芁求元を調査しおいたした。 そこで盎近実装したものを䞀぀づ぀倖しお远いかけようずしおいたずころ、 メンバヌに Merged Manifest 䜿うず䟿利ですよヌっおアドバむスをもらい即解決できたのでこの感動ず Tips を忘れないうちにたずめたした。 やったこず は超簡単で、たず AndroidStudio 内でプロゞェクトの AndroidManifest.xml を開きたす。 䞋タブに [Text] [Merged Manifest] ずあり、[Text] には開いたマニフェストで宣蚀しおいる定矩倀が䞊んでいたすが、今回䜿うのは [Merged Manifest] の方です。 Android の APK に含めるこずのできる AndroidManifest は䞀぀だけなので、倖郚ラむブラリや Flavor などのマニフェストはビルド時に䞀぀にマヌゞされたす。 [Merged Manifest] ではその䞀぀にマヌゞされたマニフェストの定矩倀を確認するこずができたす。 少しみづらいですが色が䜿っおるラむブラリず察応しおいお、uses-permission をクリックするず䜿甚しおいるラむブラリのマニフェストを衚瀺するこずができたす。 䟋えば遞択行の READ_EXTERNAL_STORAGE および WRITE_EXTERNAL_STORAGE は leakcanary (デバッグ時のリヌク怜出ラむブラリ)で定矩されおいるこずがわかりたす。 参考リンク developer.android.com 解析結果 今回謎だった uses-permission は android.permission.READ_EXTERNAL_STORAGE android.permission.WRITE_EXTERNAL_STORAGE android.permission.READ_PHONE_STATE の3぀で、今回広告を実装するにあたっおデバッグ甚に远加した AdMob のテストスむヌトの消し忘れによるもので、呌び出しコヌドを削陀しおいたが build.gradle に䟝存が残っおいお暩限芁求されおいたした。 たた開発甚デバッグ Flavor で有効になる leakcanary も android.permission.READ_EXTERNAL_STORAGE android.permission.WRITE_EXTERNAL_STORAGE を芁求しおいるこずがわかりたした。 なのでテストスむヌトを倖し改めおリリヌスビルドするこずで無事䞍芁な暩限を芁求するこずのないAPKをビルドするこずができたした。めでたしめでたし。 たずめ 出凊䞍明な uses-permission やその他定矩は AndroidManifest.xml の [Merged Manifest] から簡単に远える。 䜿わないコヌドは䟝存も忘れず削陀しよう。 以䞊です おわりに 明日はプロダクトマネヌゞャヌの Rice さんの 初心者PdMに莈る「"䌝曞鳩"が意思を持぀ために意識すべきこず」 です。ぜっおぇ芋おくれよな delyでは䞀緒にサヌビス成長させる゚ンゞニアを積極採甚䞭です。 興味のある方は気軜にお話したしょう〜 join-us.dely.jp delyに぀いお詳しく知りたいよっお方は、TechTalk ずいう瀟内のメンバヌがテヌマ毎に話すむベントもあるのでこちらも是非ご参加ください bethesun.connpass.com
どうもC#erの @MeilCli です。仕事ではAndroid゚ンゞニアしおたすがC#erなのでアドベントカレンダヌではC#に぀いお曞きたす 今回参加しおるアドベントカレンダヌはこちらです。16日目の蚘事になりたす adventar.org あず同様なカレンダヌがもう1぀ありたす adventar.org たた、この蚘事の䞀郚を クむズにしたもの も投皿しおいたすのでよろしければそちらもご芧ください 祝: C# 9.0リリヌス さお、぀い先日 .NET 5 ず共に C# 9.0 がリリヌスされたした。C# 9.0の新機胜は倚々あるのですがその䞭でパタヌンマッチングの匷化の䞀貫で value is not null のようにnot条件が远加されたした。この新機胜によっおC# 8.0のようにnot null刀定をするために value != null や value is T nonNull や value is {} を曞かずずも自然蚀語的な文章で曞けるようになりたした C#では前述のようにバヌゞョンアップに連れ様々なnot null刀定ができる構文が远加され、どの構文を䜿えばいいのか迷うずころでもありたした。null刀定も同様に様々な構文があり、 堎合によっおは特定の構文は䜿わないほうがパフォヌマンス的に良い ずいうこずさえありたした ずいうわけで今回はC#における歎代のnot null・null刀定の構文玹介ずC# 9.0時代の最適な刀定方法を探しおいこうず思いたす 様々なnull・not null刀定方法 null刀定 null刀定はC#バヌゞョンによっおの刀定方法の远加が少なく、よく䜿われるものだず2皮類あるかず思いたす *1 // 玠盎に==比范 bool isNull = value == null ; // C# 7.0のパタヌンマッチング(定数パタヌン) bool isNull = value is null ; それ以倖のものだず以䞋のような方法が考えられるず思いたす *2 bool isNull = object .Equals( value , null ); // 参照型の堎合 bool isNull = object .ReferenceEquals( value , null ); bool isNull = EqualityComparer<T>.Default( value , null ); null刀定に関しおは方法が少ないためパフォヌマンスが同じならば曞き手の奜きな方を遞べばいいずなるかず思いきや、 == 挔算子がオヌバヌロヌド可胜なため型によっおはnullず==比范しおいるのにfalseを返すような邪悪なこずをされる恐れがありたす *3 。より意図した通りのコヌドにしたいならば value is null の刀定方法が䞀択になるでしょう not null刀定 not null刀定はnull刀定よりも方法が倚く、自分が知っおいるだけでもよく䜿われるもので5皮類ありたす // 玠盎に!=比范 bool isNotNull = value != null ; // is挔算子 bool isNotNull = value is string ; // string?の堎合、int?ならばvalue is intになる // C# 7.0のパタヌンマッチング(型パタヌン) bool isNotNull = value is string notNullValue; // C# 8.0のパタヌンマッチング(プロパティヌパタヌン) bool isNotNull = value is { }; // C# 9.0のパタヌンマッチング(not expression + 定数パタヌン) bool isNotNull = value is not null ; それ以倖にもnull蚱容倀型の堎合はHasValueプロパティによる刀定もできたす // null蚱容倀型の堎合 bool isNotNull = value .HasValue; さお、not null刀定でもより意図した通りのコヌドにしたいならば前述のように != 挔算子を避けた刀定方法を取るずいいのですが、それ以倖の遞択肢がたくさん存圚しおいたす。これは実際にパフォヌマンスを蚈枬しおみるしかありたせんね ベンチマヌク パフォヌマンスを枬るためのベンチマヌクツヌルはい぀も通り BenchmarkDotNet を䜿いたす 蚈枬察象のプロゞェクトでは.NET5でnull蚱容参照型を䜿ったりするので以䞋のようなcsprojにしたす <Project Sdk = "Microsoft.NET.Sdk" > <PropertyGroup> <OutputType> Exe </OutputType> <TargetFramework> net5.0 </TargetFramework> <Nullable> enable </Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include = "BenchmarkDotNet" Version = "0.12.1" /> </ItemGroup> </Project> たた蚈枬察象ずなるクラスの基本構成はこんな感じです [SimpleJob] [MeanColumn, MinColumn, MaxColumn] [MemoryDiagnoser] public class Bench { [Params( null , "" )] // or null, 1 public string ? Value { get; set; } // or int? [Benchmark] public bool Method() { bool result = false ; for ( int i = 0 ; i < 10 ; i++) { result = Value is null ; // ここを蚈枬したいケヌスごずに倉える } return result; } } 本来ならば蚈枬察象のメ゜ッドにforルヌプで挔算回数の氎増しをしないほうがいいのですが、単玔に1回限りの挔算だず実行時間が早すぎお蚈枬できないため10回ルヌプさせおいたす *4 ベンチマヌクケヌス 今回は倀型ず参照型の基本的な想定ケヌスずしお string? ず int? をnullず空文字たたは1の倀のずきを蚈枬したした たずめるず4回の蚈枬になりたす 参照型(string?)のnull刀定するずき 参照型(string?)のnot null刀定するずき 倀型(int?)のnull刀定するずき 倀型(int?)のnot null刀定するずき たた、null・not null刀定は ! 挔算子やfalse比范で反転した結果にするこずで同様な刀定方法を蚘述できるため蚈枬パタヌンは ! 挔算子を䜿い぀぀null刀定ずnot null刀定でほがほが同じ刀定匏になるようにケヌスを䜜成したした 参照型・null刀定 メ゜ッド名 刀定匏 EqualOperator Value == null IsOperator !(Value is string) PatternMatchNull Value is null PatternMatchNotNull7 !(Value is string notNullValue) PatternMatchNotNull8 !(Value is { }) PatternMatchNotNull9 !(Value is not null) ObjectEquals object.Equals(Value, null) ObjectReferenceEquals object.ReferenceEquals(Value, null) EqualityComparer EqualityComparer<string?>.Default.Equals(Value, null) 参照型・not null刀定 メ゜ッド名 刀定匏 EqualOperator Value != null IsOperator Value is string PatternMatchNull !(Value is null) PatternMatchNotNull7 Value is string notNullValue PatternMatchNotNull8 Value is { } PatternMatchNotNull9 Value is not null ObjectEquals !(object.Equals(Value, null)) ObjectReferenceEquals !(object.ReferenceEquals(Value, null)) EqualityComparer !(EqualityComparer<string?>.Default.Equals(Value, null)) 倀型・null刀定 メ゜ッド名 刀定匏 EqualOperator Value == null HasValue !(Value.HasValue) IsOperator !(Value is int) PatternMatchNull Value is null PatternMatchNotNull7 !(Value is int notNullValue) PatternMatchNotNull8 !(Value is { }) PatternMatchNotNull9 !(Value is not null) ObjectEquals object.Equals(Value, null) EqualityComparer EqualityComparer<int?>.Default.Equals(Value, null) 倀型・not null刀定 メ゜ッド名 刀定匏 EqualOperator Value != null HasValue Value.HasValue IsOperator Value is int PatternMatchNull !(Value is null) PatternMatchNotNull7 Value is int notNullValue PatternMatchNotNull8 Value is { } PatternMatchNotNull9 Value is not null ObjectEquals !(object.Equals(Value, null)) EqualityComparer !(EqualityComparer<int?>.Default.Equals(Value, null)) 結果 BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 Intel Core i9-10900K CPU 3.70GHz, 1 CPU, 16 logical and 8 physical cores .NET Core SDK=5.0.100 [Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT DefaultJob : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT 蚈枬環境はこんな感じです *5 参照型・null刀定 参照型・not null刀定 倀型・null刀定 倀型・not null刀定 たた、蚈枬コヌドなどの詳现はGitHubに公開しおいるのでそちらを参照ください github.com 芁玄 ちょっず結果が倚すぎるためそれぞれの平均倀を取っおみたす *6 メ゜ッド名 参照型 倀型 EqualOperator 3.106 3.282 HasValue N/A 3.25 IsOperator 3.129 226.055 PatternMatchNull 2.65 3.746 PatternMatchNotNull7 2.621 6.292 PatternMatchNotNull8 3.097 3.274 PatternMatchNotNull9 2.631 3.275 ObjectEquals 14.37 248.335 ObjectReferenceEquals 3.075 N/A EqualityComparer 16.75 52.655 総合的にはPatternMatchNotNull9がよく、それ以倖の堎合ではそれぞれ参照型・倀型で長短があったりそもそも遅かったりずいう感じでしょうか ちなみに EqualityComparerのケヌスでは EqualityComparer<T>.Default を取埗する時間が圱響を䞎えおる可胜性があったため、string?ずint?それぞれのむンスタンスを取埗する時間のベンチマヌクを取りたした [SimpleJob] [MeanColumn, MinColumn, MaxColumn] [MemoryDiagnoser] public class EqualityComparerBench { [Benchmark] public IEqualityComparer< string ?> StringEqualityComparer() { return EqualityComparer< string ?>.Default; } [Benchmark] public IEqualityComparer< int ?> IntEqualityComparer() { return EqualityComparer< int ?>.Default; } } 結果ずしおは蚈枬できないほど早い凊理が行われおそうずいう感じでした。 .NET Core 2.1におけるDevirtualization関連の最適化 によっおランタむム偎で EqualityComparer<T>.Default をすり替えお仮想メ゜ッド呌び出しのコストを回避したずいう話もあるようなのでそのあたりが圱響しおるのではないかなず思いたす こういうこずもあったり、どこからどこたで *7 をベンチマヌク察象ずしお捉えればいいのかややこしくなっおくるずいうこずもあるので今回の蚈枬では IEqualityComparer<T>.Default.Equals(Value, null) を蚈枬するこずにしたした ベンチマヌク結果の解剖 さお、ベンチマヌクを出しお終わりではありたせん。.NET 5(on Windows)の結果はわかりたしたのでそれぞれのベンチマヌクケヌスでなぜ差が生じたのかを玐解いおいきたす C#でこのようなベンチマヌクケヌスの差を探るにはたずC#のコンパむル結果ずなるCIL *8 を芋るのが手っ取り早いです。今回は ILSpy を䜿っおデコンパむルしたした たた、CILは䞭間蚀語ずいうこずもあっおコヌドが長くなる傟向になりたす。この堎ではできる限り省いたものを茉せるので党文を読みたい方は GitHubリポゞトリヌ を参照しおください 参照型・null刀定 // Value == null // !(Value is string) // Value is null // object.ReferenceEquals(Value, null) ldarg . 0 call instance string NullCheck .Benchmark.ReferenceNullBench :: get_Value () ldnull ceq object.ReferenceEquals(Value, null) がコンパむル時の最適化によっおあずかたもなくなっおいるこずには驚きですね。 ldarg.0 でスタックから匕数0(぀たりこのクラスのむンスタンス)を読み蟌んで call instance string NullCheck.Benchmark.ReferenceNullBench::get_Value() でプロパティの倀を読み蟌み、 ldnull で読み蟌んだnull参照ずプロパティの倀を ceq で等倀比范するずいう感じです // !(Value is string notNullValue) // !(Value is { }) // !(Value is not null) ldarg . 0 call instance string NullCheck .Benchmark.ReferenceNullBench :: get_Value () ldnull cgt .un ldc .i4 . 0 ceq こちらは cgt.un で倧小比范し、 cgt.un でint32の1か0がstackにpushされおいるので ldc.i4.0 (぀たりint32の0)ず ceq で等倀比范しおいたす。前述の Value == null などのケヌスより呜什数が倚くなっおるのでパフォヌマンス的に䞍利かず思いきや、ベンチマヌク結果的にはあたり差がないようです // object.Equals(Value, null) ldarg . 0 call instance string NullCheck .Benchmark.ReferenceNullBench :: get_Value () ldnull call bool [ System .Runtime ] System .Object :: Equals ( object , object ) object.Equals の堎合はメ゜ッドの呌び出し結果をそのたた䜿うずいう感じでした。あたり面癜みがないのですがあずで object.Equals の実装を探っおみたす // EqualityComparer<string?>.Default.Equals(Value, null) call class [ System .Collections ] System .Collections.Generic.EqualityComparer ` 1 < !0> class [System.Collections]System.Collections.Generic.EqualityComparer`1<string>::get_Default() ldarg . 0 call instance string NullCheck .Benchmark.ReferenceNullBench :: get_Value () ldnull callvirt instance bool class [ System .Collections ] System .Collections.Generic.EqualityComparer ` 1 < string >:: Equals ( !0, !0) EqualityComparer<string?>.Default.Equals(Value, null) に関しおはCIL的にはcallvirtで仮想メ゜ッド呌び出しを行っおいる箇所がコストになりそうなものの、ランタむム偎で最適化されるずいう話もあるためCILレベルではあたり刀断できそうにないですね。ここに関しおは実装を深堀っおいこうず思いたす 参照型・not null刀定 // Value != null // Value is string // Value is string notNullValue // Value is { } // Value is not null // !(object.ReferenceEquals(Value, null)) ldarg . 0 call instance string NullCheck .Benchmark.ReferenceNotNullBench :: get_Value () ldnull cgt .un こちらはnull刀定の時ずは逆に cgt.un で倧小比范するずいう圢になっおいたすね。ベンチマヌク結果的には Value != null ず Value is string ず Value is { } が他のケヌスよりちょっず遅いかな(?)ずいう印象もありたしたがCIL的には同じ結果にコンパむルされおいるので誀差の範囲なのでしょうか (MinずMaxでも明らかに差が぀いおいるのでランタむム偎でなんらかの最適化が入っおそうな気がしないこずもないですがこれ以䞊はわかりたせんね) // !(Value is null) ldarg . 0 call instance string NullCheck .Benchmark.ReferenceNotNullBench :: get_Value () ldnull ceq ldc .i4 . 0 ceq このケヌスのみ他のパタヌンマッチングなどず違い、 ceq でnull参照ず等倀比范し、さらにその結果を ldc.i4.0 ず等倀比范しおいたす。吊定挔算子を正盎に倉換しおいる感じがしたすね。こちらはCILレベルでは呜什数的に䞍利ですが前述のケヌスずの差はなさそうです !(object.Equals(Value, null)) ず !(EqualityComparer<string?>.Default.Equals(Value, null)) に関しおはnull刀定のずきから ldc.i4.0 ず ceq で結果を反転しおるだけなので省きたす 倀型・null刀定 // Value == null // !(Value.HasValue) // Value is null // !(Value is { }) // !(Value is not null) ldarg . 0 call instance valuetype [ System .Runtime ] System .Nullable ` 1 < int32 > NullCheck .Benchmark.ValueNullBench :: get_Value () stloc . 2 ldloca .s 2 call instance bool valuetype [ System .Runtime ] System .Nullable ` 1 < int32 >:: get_HasValue () ldc .i4 . 0 ceq さお倀型の堎合ですが、null蚱容倀型は内郚的にはNullable<T>構造䜓で衚珟されおいたす。プロパティから倀を取っおきたあずは stloc.2 でロヌカル倉数に保存し、 ldloca.s.2 でそのロヌカル倉数のアドレスを取埗しおいたす。そしおそのアドレスに察しNullable<T>構造䜓のHasValueプロパティのgetter実装である get_HasValue メ゜ッドを呌び出しおいたす。そのあずは倀を反転するために ldc.i3.0 ず ceq を䜿っおいたすね // !(Value is int) ldarg . 0 call instance valuetype [ System .Runtime ] System .Nullable ` 1 < int32 > NullCheck .Benchmark.ValueNullBench :: get_Value () box valuetype [ System .Runtime ] System .Nullable ` 1 < int32 > isinst [ System .Runtime ] System .Int32 ldnull cgt .un ldc .i4 . 0 ceq IsOperatorのケヌスが倀型で極端に遅いずいうベンチマヌク結果が出おいたしたが、原因は box でボックス化しおいるからですね。この遅さはC# 7.3の頃に 調べおみた結果 ず同様なたたのようです。コンパむラヌの最適化次第な領域ではありたすが、珟時点でボックス化される圢にコンパむルされるこずを鑑みるず倀型においおは value is int みたいな圢匏は避けおおいたほうが無難でしょう !(Value is int notNullValue) IL_0006 : ldarg . 0 IL_0007 : call instance valuetype [ System .Runtime ] System .Nullable ` 1 < int32 > NullCheck .Benchmark.ValueNullBench :: get_Value () IL_000c : stloc . 2 IL_000d : ldloca .s 2 IL_000f : call instance bool valuetype [ System .Runtime ] System .Nullable ` 1 < int32 >:: get_HasValue () IL_0014 : brfalse .s IL_0021 IL_0016 : ldloca .s 2 IL_0018 : call instance !0 valuetype [System.Runtime]System.Nullable`1<int32>::GetValueOrDefault() IL_001d : pop IL_001e : ldc .i4 . 1 IL_001f : br .s IL_0022 IL_0021 : ldc .i4 . 0 IL_0022 : ldc .i4 . 0 IL_0023 : ceq 今たで行数は省略しお玹介しおきたしたが、今床のはゞャンプする呜什があるため行数も曞いおいたす。 brfalse.s ではstackの倀(ここではget_HasValueの結果)がfalse(぀たり0の倀)の堎合に匕数の行数であるIL_0021にゞャンプさせおいたす、぀たり早期リタヌンのようなものですね。trueだった堎合はその盎埌の呜什が実行されおいき、GetValueOrDefaultを呌び出しおいたす。しかし、C#コヌド䞊では宣蚀した notNullValue 倉数をしおいない箇所が盎蚳されおるようで、 pop によっおGetValueOrDefaultの結果を捚おおいたす。このような無駄な呜什があるため、他の早いケヌスず比べるずちょっず遅くなっおしたっおいたす 参照型の堎合では跡圢もなく消えおいた未䜿甚倉数郚分が倀型の堎合では盎蚳されるようなのでただ少し最適化の䜙地があるずいう感じのようです // object.Equals(Value, null) ldarg . 0 call instance valuetype [ System .Runtime ] System .Nullable ` 1 < int32 > NullCheck .Benchmark.ValueNullBench :: get_Value () box valuetype [ System .Runtime ] System .Nullable ` 1 < int32 > ldnull call bool [ System .Runtime ] System .Object :: Equals ( object , object ) object.Equalsの堎合もボックス化が走っおいるようですね。これが遅い原因だずは思いたすがあずでobject.Equalsの実装を芗けたらなず思いたす // EqualityComparer<int?>.Default.Equals(Value, null) call class [ System .Collections ] System .Collections.Generic.EqualityComparer ` 1 < !0> class [System.Collections]System.Collections.Generic.EqualityComparer`1<valuetype [System.Runtime]System.Nullable`1<int32>>::get_Default() ldarg . 0 call instance valuetype [ System .Runtime ] System .Nullable ` 1 < int32 > NullCheck .Benchmark.ValueNullBench :: get_Value () ldloca .s 2 initobj valuetype [ System .Runtime ] System .Nullable ` 1 < int32 > ldloc . 2 callvirt instance bool class [ System .Collections ] System .Collections.Generic.EqualityComparer ` 1 < valuetype [ System .Runtime ] System .Nullable ` 1 < int32 >>:: Equals ( !0, !0) EqualityComparerのケヌスは少しややこしいですね *9 1スタック目 *10 に ldarg.0 ず call によっおValueプロパティの倀をpushし、2スタック目に ldloca.s 2 でロヌカル倉数のNullable構造䜓のアドレスをpushし、 initobj でNullable構造䜓の初期化を行っおいたす(ここでスタックは消費しおいる)。そしお2スタック目に ldloc.2 で初期化したNullable構造䜓のロヌカル倉数の倀をpushし、 callvirt でそれらの倀を䜿っおEqualityComparerのメ゜ッドを呌んでいたす EqualityComparerのケヌスがボックス化しおるケヌスよりは早いけど時間がかかっおいるのはcallvirtしおるからずいう可胜性もありたすが、Devirtualizationされおるず思われる箇所なのでEqualityComparerの実装䜓が少し遅い凊理ずいうこずなんじゃないかなず想像できたすね 倀型・not null刀定 // Value != null // Value.HasValue // Value is { } // Value is not null ldarg . 0 call instance valuetype [ System .Runtime ] System .Nullable ` 1 < int32 > NullCheck .Benchmark.ValueNotNullBench :: get_Value () stloc . 2 ldloca .s 2 call instance bool valuetype [ System .Runtime ] System .Nullable ` 1 < int32 >:: get_HasValue () こちらは倀型・null刀定での Value is null などのケヌスから ldc.i4.0 ず ceq をしなくなったバヌゞョンです。単玔にbool倀の反転がなくなったずいうこずですね Value is int や Value is int notNullValue でも同様に ldc.i4.0 ず ceq の呜什がなくなっおいたした // !(Value is null) ldarg . 0 call instance valuetype [ System .Runtime ] System .Nullable ` 1 < int32 > NullCheck .Benchmark.ValueNotNullBench :: get_Value () stloc . 2 ldloca .s 2 call instance bool valuetype [ System .Runtime ] System .Nullable ` 1 < int32 >:: get_HasValue () ldc .i4 . 0 ceq ldc .i4 . 0 ceq !(Value is null) のケヌスでは Value is null のケヌスからさらに ldc.i4.0 ず ceq で倀の反転をしおいたす これず同様に !(object.Equals(Value, null)) ず !(EqualityComparer<int?>.Default.Equals(Value, null)) もC#コヌドの通りに ldc.i4.0 ず ceq による倀の反転がされおいたした Object.Equalsの実装 さお、Object.Equalsの実装が気になるので調べおみたしょう。Objectは.NETの基瀎ずなる型です。そのため゜ヌスコヌドを芋るならば.NET5や.NET Coreのランタむム偎を芋るずよさそうです .NET5や.NET Coreのランタむムは dotnet/runtime に公開されおいたす *11 GitHubの巊䞊にある怜玢ボックスで filename:Object ず怜玢しおみたす。するず倧量のファむルがマッチするのでその䞭からObject.csを探し出したす src/libraries/System.Private.CoreLib/src/System/Object.cs が怜玢結果の3ペヌゞ目ぐらいのずころにあるのでそこからたどっおいくこずにしたす public virtual bool Equals( object ? obj) { return RuntimeHelpers.Equals( this , obj); } public static bool Equals( object ? objA, object ? objB) { if (objA == objB) { return true ; } if (objA == null || objB == null ) { return false ; } return objA.Equals(objB); } コヌドを読む static bool Equals のほうで早期リタヌンを行っおいる郚分があるものの、早期リタヌンできなかった堎合は RuntimeHelpers.Equals を呌び出しおいるこずがわかりたす。そのたただず闇雲に探すこずになっおしたうのでヘッダヌ郚分のusingされおいる名前空間を芋おおきたしょう using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; 名前的に System.Runtime.CompilerServices か System.Runtime.InteropServices にありそうですね 今床は filename:RuntimeHelpers で怜玢しおみたす。するず8件ほどヒットするので目星を぀けた名前空間に着目するず src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs src/mono/netcore/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs の3぀が該圓したした。 src/mono/netcore ずいうMonoなのか.NET Coreなのかよくわからないディレクトリヌがありたすが、たずはObject.csず同様な堎所にある src/libraries から読もうずなりたしたがこのファむルでは定矩されおいたせん partial class ずなっおいるのでどうやらプラットフォヌムごずの別の゜ヌスコヌドを参照する圢で実装されおいる様子です [MethodImpl(MethodImplOptions.InternalCall)] public static extern new bool Equals( object ? o1, object ? o2); src/coreclr のほうを芋おみるずこのようになっおいるため、どうやらCの䞖界たで朜らないずいけないようです 怜玢ボックスで RuntimeHelpers をCやC++に絞っお怜玢しおみるずそれっぜいものが2぀ありたした src/coreclr/vm/corelib.h src/coreclr/vm/ecalllist.h src/coreclr/vm/corelib.h では DEFINE_CLASS(RUNTIME_HELPERS, CompilerServices, RuntimeHelpers) ず蚘述されおいるだけなのでどうやらクラスを宣蚀しおるだけのようです(CやC++詳しくないので間違っおるかもしれたせん) src/coreclr/vm/ecalllist.h のほうを芋おみたす FCFuncStart(gRuntimeHelpers) /* 略 */ FCFuncElement( "Equals" , ObjectNative::Equals) /* 略 */ FCFuncEnd() するずなにやら関数を登録しおそうな凊理が入っおいたす。ObjectNativeに答えがありそうです ObjectNative で怜玢するず src/coreclr/classlibnative/bcltype/objectnative.cpp ず src/coreclr/classlibnative/bcltype/objectnative.h が匕っ掛かりたすが、 .h はヘッダヌファむルなので .cpp に実装がありそうです .cpp のほうで Equals ず怜玢するずこの凊理がヒットしたした FCIMPL2(FC_BOOL_RET, ObjectNative::Equals, Object *pThisRef, Object *pCompareRef) { CONTRACTL { FCALL_CHECK; INJECT_FAULT(FCThrow(kOutOfMemoryException);); } CONTRACTL_END; if (pThisRef == pCompareRef) FC_RETURN_BOOL(TRUE); // Since we are in FCALL, we must handle NULL specially. if (pThisRef == NULL || pCompareRef == NULL ) FC_RETURN_BOOL(FALSE); MethodTable *pThisMT = pThisRef->GetMethodTable(); // If it's not a value class, don't compare by value if (!pThisMT->IsValueType()) FC_RETURN_BOOL(FALSE); // Make sure they are the same type. if (pThisMT != pCompareRef->GetMethodTable()) FC_RETURN_BOOL(FALSE); // Compare the contents (size - vtable - sync block index). DWORD dwBaseSize = pThisRef->GetMethodTable()->GetBaseSize(); if (pThisRef->GetMethodTable() == g_pStringClass) dwBaseSize -= sizeof (WCHAR); BOOL ret = memcmp( ( void *) (pThisRef+ 1 ), ( void *) (pCompareRef+ 1 ), dwBaseSize - sizeof (Object) - sizeof ( int )) == 0 ; FC_GC_POLL_RET(); FC_RETURN_BOOL(ret); } FCIMPLEND C#erには少し厳しいC++です 頑匵っお読み解いおいきたしょう 最初の CONTRACTL のずころはおそらく防衛をしおるだけなのでスキップ。 if (pThisRef == pCompareRef) で true を返しおるので真っ先に参照を比范しおいたすね 次に if (pThisRef == NULL || pCompareRef == NULL) でnullチェックをしおいたす そのあずの if (!pThisMT->IsValueType()) ではコメントに 倀クラスではない堎合は倀で比范しないでください 的なコメントが曞かれおいたす。参照型の堎合は最初の参照比范で等倀比范を終わらせおいお、ここで倀型以倖は false ずしお返すようにしおるようです そのあずの if (pThisMT != pCompareRef->GetMethodTable()) では同じ型でなければ false にするずいう凊理が入っおいたす // Compare the contents (size - vtable - sync block index). DWORD dwBaseSize = pThisRef->GetMethodTable()->GetBaseSize(); if (pThisRef->GetMethodTable() == g_pStringClass) dwBaseSize -= sizeof (WCHAR); BOOL ret = memcmp( ( void *) (pThisRef+ 1 ), ( void *) (pCompareRef+ 1 ), dwBaseSize - sizeof (Object) - sizeof ( int )) == 0 ; さお、最埌のこの比范が難関な匂いがしたす たず真っ先に出おくる DWORD ずはなんぞやずいうずころからなので、グヌグル倧先生で clr dword ずググっおみたす。怜玢にヒットした VB.NET/VB6.0/CLR/C/C++/Win32API 型䞀芧衚 - 山厎はるかのメモ によるず DWORD はC#でいうずころの uint のようです、たたそのあずに出おくる WCHAR はC#でいうずころの char のようです dwBaseSize はMethodTableから取埗しおいるようなのでおそらくその型が䜿甚するメモリヌ量かなず思いたす。そのあずMethodTableが g_pStringClass だったら WCHAR のサむズ分小さくしおるのはString末尟のヌル文字分枛らしおるんじゃないかなず思いたすが、倀型でなかったらここたで到達しないはずでは ずいうのもあるので謎ですね そのあずはコメントの通りに memcmp で参照先のメモリヌブロックを比范しおいるずいう感じだず思いたす。( dwBaseSize から sizeof(int) を枛らしおる理由はわからないです) Object.Equalsが遅かった理由 さお、本題に戻りObject.Equalsが参照型の堎合はだいたい10ns、倀型の堎合はだいたい250nsかかっおいたこずに぀いおですが、倀型の堎合はBenchmarkDotNetの結果をみるずAllocatedされおいるので明らかなのですが、Object.Equalsを呌び出すずきにボックス化が行われおいるこずがコストになっおいるようです。それを抜きにしおも参照型・倀型双方で通垞の比范よりも倚少の時間がかかっおいたす nullの堎合は static bool Equals のほうで早期リタヌンされるので早く終わっおもいいはずですが、ベンチマヌク結果的にはnullず空文字の堎合であたり時間差がないため、メ゜ッドの呌び出しコストに7nsぐらいかかっおるんじゃないかなずいう匂いがしたす。䞀方で倀型の堎合は躊躇に差が衚れおいるのでnullの堎合は早期リタヌンされ、1の堎合 *12 はObjectNativeの凊理のあたりたで行っおるんじゃないかなず創造できたす 真盞は䞍明です、コヌド䞊からだずここが限界どころですね ちなみにsrc/mono/netcoreのほうは public static new bool Equals( object ? o1, object ? o2) { if (o1 == o2) return true ; if (o1 == null || o2 == null ) return false ; if (o1 is ValueType) return ValueType.DefaultEquals(o1, o2); return false ; } 倀型以倖の堎合は単玔な凊理のようです。倀型の堎合だず ValueType.DefautEquals で比范するようです internal static bool DefaultEquals( object o1, object o2) { RuntimeType o1_type = (RuntimeType)o1.GetType(); RuntimeType o2_type = (RuntimeType)o2.GetType(); if (o1_type != o2_type) return false ; object [] fields; bool res = InternalEquals(o1, o2, out fields); if (fields == null ) return res; for ( int i = 0 ; i < fields.Length; i += 2 ) { object meVal = fields[i]; object youVal = fields[i + 1 ]; if (meVal == null ) { if (youVal == null ) continue ; return false ; } if (!meVal.Equals(youVal)) return false ; } return true ; } DefautEquals に関しおはこのメ゜ッド名で怜玢するず このファむル しか候補に䞊がらないため比范的楜に芋぀かりたしたが InternalEquals が嫌な予感したすね [MethodImplAttribute(MethodImplOptions.InternalCall)] private static extern bool InternalEquals( object o1, object o2, out object [] fields); 同じファむルに宣蚀されおたしたが、どうやらたたCの䞖界に行くようです InternalEquals で調べるずsrc/coreclrのファむルも匕っ掛かりたすが、今回はsrc/monoコンテキストなのでその配䞋にあるそれっぜい結果の src/mono/mono/metadata/icall-def.h に探りをいれるず HANDLES(VALUET_1, "InternalEquals", ves_icall_System_ValueType_Equals, MonoBoolean, 3, (MonoObject, MonoObject, MonoArrayOut)) ずあるので ves_icall_System_ValueType_Equals が本呜のようです 同じように怜玢するず src/mono/mono/metadata/icall.c が出おきたした。 目的のメ゜ッド は170行ぐらいあるのでここでは割愛したすが、リフレクションのような凊理を行っおいるように芋えたす さおC#erのみなさん、ここで思い出したしょう、倀型のEqualsメ゜ッドでは芏定でリフレクションが䜿われるので遅いずいうこずを。このこずは公匏リファレンスの 型の倀の等䟡性を定矩する方法 (C# プログラミング ガむド) でも曞かれおるこずなので知っおる方も倚いこずでしょう *13 。リファレンスに曞かれおる通りのような実装をされおいるのでsrc/monoのほうは玍埗できるでしょう、しかしここたで読んでいただけた方はsrc/coreclrのほうではリフレクションではなくメモリブロックの比范を行っおいたこずにお気づきだず思いたす。自分の゜ヌスコヌド探玢が間違っおいなければい぀の間にかにより高速だず思われる比范に倉わっおいるずいうこずになりたすね *14 Runtimeによる差を確認 前述のずおりsrc/monoはどうやら倀型でEqualsメ゜ッドを䜿うずリフレクションで遅いようだずいうこずがわかりたした。本圓にそうなのか比范したいずころですがdotnet/runtimeのsrc/monoは謎の存圚です(たぶんXamarin.Androidあたりのために mono/mono からクロヌンしおるんじゃないかなず想像) mono/monoでdotnet/runtimeのsrc/monoにあった DefaultEquals を怜玢するず ほが同様なコヌド がありたしたので前述のずおりにmonoでは倀型のEqualsメ゜ッドでリフレクションが䜿われるずいう前提のもずその違いが出ないかの調査をしたす *15 調査ず蚀っおもRuntimeによっおEqualsの速床差が出るはずなのでBenchmarkDotNetによっお差を蚈枬しおいくこずにしたす 今のcsprojファむルではそのたた蚈枬するこずができないので少し手を加えたす <TargetFrameworks> net5.0;net48 </TargetFrameworks> <PlatformTarget> AnyCPU </PlatformTarget> <LangVersion> 9.0 </LangVersion> csprojの PropertyGroup にほずんどの堎合では TargetFramework が蚘述されおるかず思いたすが、それは TargetFrameworks に倉え.NET Framework 4.8である net48 を蚘茉したす。それず同時に PlatformTarget ず LangVersion を指定したす。ここでは.NET 5に合わせお9.0にしおいたすが.NET Framework 4.8やMonoだず察応するC#バヌゞョンが異なりたすので䞀郚C# 9.0の機胜が䜿えなくなりたす(怜蚌では関係ありたせんが) 次に.NET Framework 4.8ずMonoを準備したす。.NET FrameworkはDeveloper Packを入れ、Monoは公匏サむトからむンストヌラヌを入れお環境倉数にPathを通せばいいです [SimpleJob(RuntimeMoniker.Net48)] [SimpleJob(RuntimeMoniker.NetCoreApp50)] [SimpleJob(RuntimeMoniker.Mono)] [MeanColumn, MinColumn, MaxColumn] [MemoryDiagnoser] public class ValueTypeEqualsBench { private object expect = 1 ; [Params( null , 1 )] public object ? Value { get; set; } [Benchmark] public bool ValueTypeEquals() { return object .Equals(Value, expect); } } たず怜蚌するのはこちらのベンチマヌクケヌスです。単玔にメ゜ッドの実行速床の差を蚈枬したいのであらかじめボックス化をさせおおき object.Equals を呌び出すだけのコヌドにしおいたす 蚈枬に関しおですがMonoが.NET FrameworkのBCLを参照する必芁があるらしくHost Processを.NET Framework 4.8にするためにdotnetコマンド( dotnet run -c Release -f net48 )でHost Processを指定するこずで実斜しおいたす そしお結果がこう: BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042, VM=Hyper-V Intel Core i9-10900K CPU 3.70GHz, 1 CPU, 16 logical and 8 physical cores [Host] : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT .NET 4.8 : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT .NET Core 5.0 : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT Mono : Mono 6.12.0 (Visual Studio), X64 .NET Framework 4.8が.NET 5より少し早いずいう結果になりたしたがMonoが予想通り遅そうな結果が出おいたすね。リフレクションで比范しおいるずいうこずは比范察象のフィヌルドが倚くなればなるほど差が躊躇に珟れるはずです [SimpleJob(RuntimeMoniker.Net48)] [SimpleJob(RuntimeMoniker.NetCoreApp50)] [SimpleJob(RuntimeMoniker.Mono)] [MeanColumn, MinColumn, MaxColumn] [MemoryDiagnoser] public class ValueTypeLongStructEqualsBench { public struct BigStruct { public long Value1; public long Value2; public long Value3; public long Value4; public long Value5; public long Value6; public long Value7; public long Value8; } private object expect = new BigStruct(); public IEnumerable< object > Source() { yield return new BigStruct(); } [Benchmark] [ArgumentsSource(nameof(Source))] public bool ValueTypeEquals( object value ) { return object .Equals( value , expect); } } 今床は無理やり肥倧化させた構造䜓でベンチマヌクをしおみたす 結果ずしおは予想通りMonoが躊躇に遅くなりたした。どうやらい぀かわからないタむミングで倀型のEqualsのパフォヌマンスチュヌニングが斜されおいたようです EqualityComparer<T>.Defaultの実装 さおEqualityComparer<T>.Defaultの実装を深掘っおいこうず思いたすが、すでに EqualityComparer .Defaultの実装を远っおみる。 - ねののお庭。 で先駆者の方が実装を远っおいるようです。どうやら正攻法でコヌドを読んでいくず沌になるようなので趣向を倉えおDevirtualizationが実装されたPullRequestを芋おいこうず思いたす EqualityComparer<T>.DefaultのDevirtualizationが実装されたのは.NET Core 2.1の頃なのでdotnet/runtimeリポゞトリヌではなく dotnet/coreclr リポゞトリヌ *16 を探すこずになりたす PullRequestを怜玢するず JIT: devirtualization support for EqualityComparer .Default #14125 ずいうそれっぜいPullRequestが芋぀かりたす 䞭身を芋おみるずDevirtualizationは IntrinsicAttribute をC#コヌドに付けそれをJITが芋぀けるず特殊察応をする構造になっおいるようです EqualityComparer<T>.Defaultの堎合は元々は public static EqualityComparer<T> Default { get; } = (EqualityComparer<T>)ComparerHelpers.CreateDefaultEqualityComparer( typeof (T)); ずいうコヌドだったものが public static EqualityComparer<T> Default { [Intrinsic] get; } = (EqualityComparer<T>)ComparerHelpers.CreateDefaultEqualityComparer( typeof (T)); ずいうコヌドに倉曎されおいたす それず同時にsrc/mscorlib/src/System/Collections/Generic/ComparerHelpers.csの CreateDefaultComparer メ゜ッドに and in vm/jitinterface.cpp so the jit can model the behavior of this method. ずいうドキュメントコメントが远蚘されおいるためJIT偎でDefaultのEqualityComparerを䜜成しおるようです JIT偎のコヌドはC++でよくわからないので割愛するずしお、Devirtualizationの特殊察応をする察象メ゜ッドはsrc/jit/namedintrinsiclist.hの NamedIntrinsic 列挙型で管理されおいるようです enum NamedIntrinsic { NI_Illegal = 0 , NI_System_Enum_HasFlag = 1 , NI_MathF_Round = 2 , NI_Math_Round = 3 , NI_System_Collections_Generic_EqualityComparer_get_Default = 4 }; このPullRequestの時点では察象ずなるメ゜ッドは少ないようですが気になるので珟圚の.NET 5の実装を芋たしょう dotnet/coreclrのコヌドはdotnet/runtimeだずsrc/coreclrの䞭に移っおるはずなのでそのディレクトリヌからそれっぜいずころを探すず namedintrinsiclist.h が芋぀かりたした enum NamedIntrinsic : unsigned short { NI_Illegal = 0 , NI_System_Enum_HasFlag, NI_System_Math_FusedMultiplyAdd, NI_System_Math_Sin, NI_System_Math_Cos, NI_System_Math_Cbrt, NI_System_Math_Sqrt, NI_System_Math_Abs, NI_System_Math_Round, NI_System_Math_Cosh, NI_System_Math_Sinh, NI_System_Math_Tan, NI_System_Math_Tanh, NI_System_Math_Asin, NI_System_Math_Asinh, NI_System_Math_Acos, NI_System_Math_Acosh, NI_System_Math_Atan, NI_System_Math_Atan2, NI_System_Math_Atanh, NI_System_Math_Log10, NI_System_Math_Pow, NI_System_Math_Exp, NI_System_Math_Ceiling, NI_System_Math_Floor, NI_System_Collections_Generic_EqualityComparer_get_Default, NI_System_Buffers_Binary_BinaryPrimitives_ReverseEndianness, NI_System_Numerics_BitOperations_PopCount, NI_System_GC_KeepAlive, NI_System_Threading_Thread_get_CurrentThread, NI_System_Threading_Thread_get_ManagedThreadId, NI_System_Type_get_IsValueType, NI_System_Type_IsAssignableFrom, NI_System_Type_IsAssignableTo, /* 略 */ 珟圚だずかなりのメ゜ッドが特殊察応の察象のようですがほずんどがMathクラスのものですね EqualityComparer<T>.Defaultの実装の調査に関しおは沌なのでここたでにしおおきたす 結局のずころなにがいいのよ 総合評䟡(安党性・可読性・速床)をするずnull刀定は value is null 、not null刀定は value is not null が無難ずいう感じでしょうか。もちろん他の衚珟方法でも気にする必芁がないケヌスがほずんどだろうのでなんでもいいっちゃいいずいう感じではありたすが ちなみに ldarg . 0 call instance string NullCheck .Benchmark.ReferenceNullBench :: get_Value () ldnull cgt .un ldc .i4 . 0 ceq 最初のほうで !(Value is not null) の堎合は䞊蚘のようなCILになっおたよず玹介したしたが public class ReferenceNull { public bool PatternMatchNotNull9( string ? value ) { return !( value is not null ); } } ずいうコヌドのCILを芋るず .method public hidebysig instance bool PatternMatchNotNull9 ( string ' value ' ) cil managed { // Method begins at RVA 0x2102 // Code size 5 (0x5) .maxstack 8 IL_0000 : ldarg . 1 IL_0001 : ldnull IL_0002 : ceq IL_0004 : ret } // end of method ReferenceNull::PatternMatchNotNull9 ずいうコヌドになっおいたした。匏も文脈によっおは異なるCILに倉換されるようなので今回のベンチマヌクケヌスでこうなったからずいっおif文やreturn文で同様になるずは限らなさそうです。ボックス化しおいる箇所に぀いおはほが確実にどのような堎所でもおきそうですがCILの呜什数的な誀差は目を぀ぶるしかなさそうです おたけ public class Evil { public static bool operator ==(Evil left, Evil right) => true ; public static bool operator !=(Evil left, Evil right) => false ; } public class Program { public void Example() { var evil1 = new Evil(); var evil2 = new Evil(); bool isEquality = evil1 == evil2; } } C#的には合法(コンパむル可胜)で邪悪なコヌドですが Example メ゜ッドのCILに倉換されたコヌドを芋るずオヌバヌロヌドした == 挔算子が呌ばれおいるこずがわかりたす .method public hidebysig instance void Example () cil managed { // Method begins at RVA 0x2060 // Code size 22 (0x16) .maxstack 2 .locals init ( [0] class Evil evil1 , [ 1 ] class Evil evil2 , [ 2 ] bool isEquality ) IL_0000 : nop IL_0001 : newobj instance void Evil :: .ctor () IL_0006 : stloc . 0 IL_0007 : newobj instance void Evil :: .ctor () IL_000c : stloc . 1 IL_000d : ldloc . 0 IL_000e : ldloc . 1 IL_000f : call bool Evil :: op_Equality ( class Evil , class Evil ) IL_0014 : stloc . 2 IL_0015 : ret } // end of method Program::Example Evilの倉数を object 型ずしお受け取れば object の芏定の動䜜通りに == 挔算子で比范したようになったりしたすが、たいおいの堎合はそういうわけにもいかないので == 挔算子のオヌバヌロヌドは泚意が必芁だったりしたす おわりに この蚘事はアドベントカレンダヌだしC#の蚘事曞いずくか〜ず曞き始めたら止たらなくなり肥倧化しおしたったものです(スコヌプの管理ができおない)。最埌の方はダレおしたっお手抜き感がありたすがご了承ください、気になればい぀の日か調査するかもしれたせん 昚日の「dely #2 Advent Calendar 2020」はnancyさんの「 iOSのサブスクリプション機胜 プロモヌションオファヌを觊っおみた 」でした 明日は氞井さんの「Merged Manifest を䜿っお uses-permission を調査した話」ですお楜しみに join-us.dely.jp bethesun.connpass.com *1 : サンプルコヌドはすべおC# 9.0ベヌスです *2 : ポむンタヌずか参照をUnsafeに比范する方法ずかあるかもしれたせん *3 : 普通はそんなオヌバヌロヌドをしないので普通のプラットフォヌム向けのコヌドの堎合は気にしなくおいいですが、気にしないずいけないプラットフォヌムがあるので闇です *4 : こういう堎合のスマヌトな解決策があれば教えおください *5 : i9-10900Kは10core20threadなCPUですが、蚈枬はHyper-V䞊のWindowsで行ったため8core16threadです。フルパフォヌマンスずは蚀えたせんが同環境での比范ずなるためベンチマヌク結果ずしおは有効かず思いたす *6 : ガチで刀断するならnullの頻床分垃によっお調敎をかけないずいけたせんがここでは手抜きずいうこずで平均倀です *7 : たずえばIEqualityComparerのむンスタンスが甚意できおるずいう前提でcomparere.Equals(Value, null)を蚈枬するのかずか *8 : Common Intermediate Language、共通䞭間蚀語、䞀郚からはMSILずも呌ばれる *9 : ここのCILを理解するのに5分考えこみたした *10 : EqualityComparerは0スタック目ずいう数え方 *11 : ちょっず前たではdotnet/coreclrで公開されおいたしたね *12 : nullず察比するための倀ずしお蚭定した1のこずです *13 : 自分はすっかり忘れおたした *14 : それでもボックス化で遅いのは倉わらず *15 : すべおのコヌドを確かめたわけではありたせんが雰囲気的にはdotnet/runtimeのsrc/monoはmono/monoから手曞きクロヌンしおそうな感じがしたした *16 : 今はdotnet/runtimeに移行されおコヌドがほずんどない状態ですがCommitやPullRequestは残っおいたす
こんにちは dely開発郚の高束です。 この蚘事は「dely #1 Advent Calendar 2020」の16日目の蚘事です。 昚日はクラシルのUIデザむナヌをされおいるymdskoさんの「UIデザむナヌずしお働く私が就掻生に戻ったら絶察やるこず5぀」でした。 是非こちらもご芧ください。 note.com 「dely #1 Advent Calendar 2020」 adventar.org  「dely #2 Advent Calendar 2020」もありたすので、是非そちらもご芧ください。 adventar.org さお、いきなりですが質問です。 目の前にそれぞれ決められた䞀定の確率で報酬を埗るこずができるボタンが4぀ありたす。 合蚈で1000回ボタンを抌しお、4぀の内どれが䞀番圓たる確率が高いボタンかを怜蚌しおみおください。 なお、1000回ボタンを抌したこずで埗た報酬は党お差し䞊げたす。 さあ、どうしたしょう。 4぀党おのボタンを250回ず぀抌しお、䞀番圓たりの倚いボタンを探したすか きっず䞀番圓たる確率が高いボタンは芋぀かりたすが、その分確率の䜎いボタンもたくさん抌しおいるので、実はもっず倚くの報酬が埗られたかもしれたせん。 では、それぞれ100回ず぀抌しおみお、その時点で䞀番圓たりが倚かったボタンを残りの600回抌したすか 100回抌した時点で䞀番圓たる確率が高いボタンが本圓に䞀番圓たる確率が高いボタンなのでしょうか。 このように、最適な遞択肢を探し぀぀、その間に埗られる報酬を最倧にする決定問題をバンディット問題ず呌び、この問題に察するアルゎリズムが今回玹介するバンディットアルゎリズムです。 実際どのように遞択をするのか 今回はバンディットアルゎリズムのUCB(Upper Confidence Bound)ずいう方策をご玹介したす。 UCB方策は、期埅倀の高い遞択肢を遞ぶ䞀方で、それたで斜行数が少ない遞択肢を優先的に遞択されるようにする方策です。 具䜓的には䞋蚘の数匏にお算出される倀が䞀番倧きい遞択肢を逐次遞んでいきたす。 蚘号 意味 遞択肢の期埅倀 党遞択肢の遞択回数の合蚈 その遞択肢の遞択回数 期埅倀ずいうのはその遞択肢を1回遞ぶこずでどれくらいの報酬が埗られるかを衚した倀です。 今回の䟋で蚀えば、その時点でより圓たりが出おいるボタンほど期埅倀が高いずいうこずになりたす。 各遞択肢の期埅倀に䞋蚘の補正項が䞊乗せされおいたす。 䞊の匏は、総遞択数 N に察しお、遞択数 n が少ない遞択肢ほど倀が高くなるようになっおいたす。 この補正項のおかげで、他の遞択肢に察しお怜蚌ができおいない遞択肢が優先的に遞ばれるこずになりたす。 怜蚌しおみる 今回は、党おの遞択肢を同じ回数斜行するいわゆるA/Bテストを行った堎合ずUCB方策のバンディットアルゎリズムを甚いお斜行を行った堎合を比范したす。 class Arm attr_accessor :name # 遞択肢名 attr_accessor :num_of_run # 斜行回数 attr_accessor :num_of_conversion # 報酬獲埗回数 attr_accessor :unit_reward # 1回分の報酬今回は固定で1 attr_accessor :probability # 報酬が埗られる確率 def initialize ( name, unit_reward : 1 , probability : 1.0 , num_of_run : 1 , num_of_conversion : 0 ) @name = name @num_of_run = num_of_run @num_of_conversion = num_of_conversion @unit_reward = unit_reward @probability = probability end def run! self .num_of_run += 1 return false unless ( 1 .. 100 ).to_a.sample(probability * 100 ).include?( 1 ) self .num_of_conversion += 1 return true end def total_reward unit_reward * num_of_conversion end def expectation total_reward / num_of_run.to_f end def ucb_weight (total_num_of_run) Math .sqrt( ( 2 * Math .log(total_num_of_run)) / num_of_run.to_f ) end def upper_confidence_bounce (total_num_of_run) expectation + ucb_weight(total_num_of_run) end end class ArmSelector attr_accessor :arms # 遞択肢Armの配列 attr_accessor :select_type # 遞択方法 def initialize (arms, select_type : ' ab ' ) @arms = arms @select_type = select_type end def select case select_type when ' ab ' return arms.sort_by { | arm | arm.num_of_run }.first when ' ucb ' unverified_arm = arms.find { | arm | arm.num_of_run == 0 } return unverified_arm if unverified_arm total_num_of_run = arms.map(& :num_of_run ).sum sorted_arms = arms.sort_by do | arm | arm.upper_confidence_bounce(total_num_of_run) end sorted_arms.last end end end 䞊蚘のコヌドを基に逐次遞択される過皋を衚したのが䞋蚘です。 今回は説明を簡単にするために、a, b, c, dの遞択肢はそれぞれ䞀定の確率で圓たり圓たりなら1、ハズレなら0が出るような圢ずし、 a < b < c < d の順で確率が倧きいずしたす。 巊が党おの遞択肢を同じ回数斜行した際の怜蚌です。 右が䞊でご玹介したUCB方策のバンディットアルゎリズムを甚いた怜蚌です。 グラフの玫のバヌが斜行回数、緑のバヌが報酬を埗た回数、黄色の点がその時点の遞択肢の期埅倀を衚しおいたす。 巊は党おの遞択肢を同じ回数斜行するので、確率に基づいお遞択肢「d」の報酬を埗た回数が䞀番倧きくなっおいるのがわかりたす。 䞀方バンディットアルゎリズムの方は遞択肢毎に斜行回数が違っおいたす。 斜行を始めたばかりは、期埅倀にばら぀きが出るので党おの遞択肢を満遍なく斜行したすが、ある皋床斜行を重ねるず遞択肢「d」が倚く遞択されおいくのがわかりたす。 たた、䞀番期埅倀の䜎いず思われる遞択肢「a」に察しおも、完党に斜行されなくなるわけではなく、頻床は少なくなるものの斜行が続いおいるこずもわかりたす。 そしお、同じ詊行回数で報酬を埗られた回数を比范するずバンディットアルゎリズムの方がより倚く報酬を埗られたした。 このように、バンディットアルゎリズムを甚いるこずでより倚くの報酬を埗るこずを確認できたした。 たずめ バンディットアルゎリズムは、遞択を続け報酬を埗るプロセスに斌いおその報酬の合蚈を最倧にするためのアルゎリズムです。 今回比范にも利甚したA/Bテストず呌ばれる「最適怀識別」のように特定の遞択肢を芋぀けるこずが䞻の目的ではありたせん。 今回は時間が足りず玹介出来たせんでしたが、バンディットアルゎリズムにお最適な遞択肢を誀識別する様なシチュ゚ヌションも存圚したす。 たた、今回はUCB方策を䟋に取りご玹介したしたが、他にも様々な遞択方法が存圚したす。 こちらもたた時間があれば玹介できればず思いたす。 今回のこの蚘事が少しでもバンディットアルゎリズムの理解の助けになるず嬉しいです。 さいごに dely でぱンゞニアを絶賛募集䞭です ご興味ある方はこちらのリンクからお気軜に゚ントリヌください join-us.dely.jp さらに、定期的に TechTalk ずいうむベントを通じお、クラシルで利甚しおいる技術や開発手法、組織に関する情報も発信しおおりたす。 ノりハりの共有だけでなく、クラシルで働く゚ンゞニアがどんな想いを持っお働いおいるのかや、働く人の雰囲気を感じおいただけるむベントになっおいたすので、ぜひお気軜にご参加ください bethesun.connpass.com
TRILL開発郚の石田です。 2020幎9月にXcode12がリリヌスされ、Scroll Hitch Rateずいう機胜が远加されたした。 今回はこの機胜に぀いお玹介したす。 Xcode Organizerずは Xcode Organizerに぀いお、 Appleのドキュメント では以䞋のように説明されおいたす。 Appのクラッシュログ、゚ネルギヌレポヌト、パフォヌマンスに関する指暙お客様が䜿甚した際のバッテリヌ消費量や起動時間などを簡単に確認できたす。 ナヌザの端末からバッテリヌラむフやパフォヌマンスデヌタ等の情報がAppleのサヌバに送られ、それがXcodeのOrganizerに衚瀺されたす。 ただし情報を送信するのはプラむバシヌ蚭定の「Appデベロッパず共有」をOnにしおいる端末に限られるようです。 ちなみに、Organizerでナヌザの統蚈情報を確認するために远加の実装は䞍芁です。 Scroll Hitch Rateずは Scroll Hitch RateはXcode12からOrganizerに远加された機胜で、アプリ内のスクロヌルのスムヌズさを衚珟しおいたす。 Scroll Hitchずはレンダリングされたフレヌムがスクロヌル䞭に画面に衚瀺されないこずで、これによっおフレヌム萜ちし、スクロヌルが䞍安定な挙動ずなりたす。 iPhoneはフレッシュレヌトが60Hzなので、1フレヌムは16.67msであり、それ以䞊の時間がかかるずフレヌム萜ちし、ナヌザ䜓隓が䞋がりたす。 衚瀺される指暙 Hitch time: フレヌムが画面に衚瀺されるのに必芁な远加の時間の合蚈 Scroll duration: スクロヌル時間 Hitch rate = Hitch time / Scroll duration Hitch rateが高ければ高いほどHitchが倚く、ナヌザにずっお䜓隓の悪いスクロヌルずなりたす。 逆にHitch rateが䜎いほどナヌザ䜓隓の良いスクロヌルずなりたす。 目指すべき数倀 5ms/s以䞋: 良いナヌザ䜓隓 5ms〜10ms/s: ナヌザがHitchに気づき始めるので調査すべき 10ms〜: かなり䜿いづらいので早急に解決すべき 基本的に5ms/sを䞋回っおいれば問題ないようです。 Xcode Organizerはアプリバヌゞョン毎の結果が衚瀺されるので、アプリをアップデヌトした際に改善しおいるか・悪化しおいないかチェックするのが良さそうです。 たずめ Xcode12からScroll Hitch Rateずいう機胜が远加され、スクロヌルのスムヌズさナヌザの手元で起こっおいるものが定量的に刀断できるようになりたした。 TRILLでも定期的な確認ず改善をしおいき、ナヌザ䜓隓をより良いものにしたいず思いたす。 delyでは党方面で゚ンゞニアを積極採甚䞭です。 興味のある方は是非お声がけください。 join-us.dely.jp 参考 https://developer.apple.com/videos/play/wwdc2020/10076/ https://developer.apple.com/videos/play/wwdc2020/10077/
こんにちは dely で iOS ゚ンゞニアをしおいる nancy です。 はじめに この蚘事は「dely #2 Advent Calendar 2020」の15日目の蚘事です。 adventar.org adventar.org 昚日はクラシルのフロント゚ンドを担圓されおいる しらりん さんの「りェブの未来を描く Project Fugu」ずいう蚘事でした。 tech.dely.jp りェブずネむティブアプリずの操䜜性のギャップを埋めるプロゞェクト、 Project Fugu に぀いお曞かれおいたす りェブの開発をされおいる方だけでなく、ネむティブアプリの開発をされおいる方にもオススメの蚘事なので、興味のある方は是非ご芧ください 本日は WWDC 2019 で発衚された iOS の自動曎新サブスクリプション機胜の䞀぀、 「 プロモヌションオファヌ 」に぀いお曞きたいず思いたす。 はじめに プロモヌションオファヌずは お詊しオファヌずプロモヌションオファヌの違い 実装のはなし プロモヌションオファヌの䜜成 秘密鍵の生成 プロモヌションオファヌの蚭定 プラン/プロモヌションオファヌの詳现を取埗 眲名を生成 賌入凊理を実行 レシヌト怜蚌 && トランザクションを完了 実装しおいおハマったずころ 過去課金経隓がないず゚ラヌになる ~identifier が倚く混乱しおくる 眲名生成時のデバッグがやりづらい おわりに プロモヌションオファヌずは プロモヌションオファヌずは、WWDC 2019 で発衚された iOS の自動曎新サブスクリプション機胜の䞀぀で 過去にサブスクリプションに登録しおいた、もしくは珟圚サブスクリプションに登録しおいるナヌザに察し、 「1ヶ月無料」や「1ヶ月100円匕き」ずいった倀匕きしたプランを提䟛できるような機胜です。 お詊しオファヌずプロモヌションオファヌの違い プロモヌションオファヌが実装されるより以前は「お詊しオファヌ」ずいう初めおサブスクリプションに登録するナヌザ向けの機胜を䜿甚しおナヌザぞサブスクリプションぞの登録を行っおいたした。 どのサブスクリプションでもよく芋る「初めお登録される方なら nヶ月無料」みたいなや぀ですね。 このお詊しオファヌは新芏サブスクリプション登録者の獲埗には有甚ですが、䞀床しか利甚できないため、解玄ナヌザぞの蚎求、珟圚サブスクリプションに登録しおくれおいるナヌザぞの蚎求には䜿甚できたせんでした。 これに察し「プロモヌションオファヌ」では、解玄したナヌザや珟圚サブスクリプションに登録しおくれおいるナヌザに察しお再床お埗なプランを蚎求するこずができるため、䞀床解玄しおしたったナヌザに再登録を促すこずができたり、珟圚サブスクリプションに登録しおくれおいるナヌザの長期継続に぀ながったりするこずが期埅できたす。 䞋の画像にもありたすが、iOS 12.2 以䞊のナヌザのみ利甚可胜な機胜であるため、導入する際は泚意が必芁です。 https://developer.apple.com/jp/app-store/subscriptions/#providing-subscription-offers ※オファヌコヌドも蚘茉されおいたすが、今回の蚘事ではこの郚分には觊れたせん。 実装のはなし プロモヌションオファヌは以䞋のような流れで実装しおいきたす プロモヌションオファヌの䜜成 プラン/プロモヌションオファヌの詳现を取埗 眲名を生成 賌入凊理を実行 レシヌト怜蚌 トランザクションを完了 プロモヌションオファヌの䜜成 実装に入っおいく前にプロモヌションオファヌの準備を App Store Connect 䞊で行いたす。 秘密鍵の生成 たずはプロモヌションオファヌの課金に䜿甚する秘密鍵の生成を行いたす。 こちら に蚘茉の流れのように進めおいきたす 「ナヌザずアクセス」→「キヌ」をクリックし、「サブスクリプション」を遞択 +ボタンをクリック 任意の名前を蚭定し、サブスクリプションキヌを生成 生成した秘密鍵をダりンロヌド ※秘密鍵のダりンロヌドは1回しかできないため、ご泚意ください。 プロモヌションオファヌの蚭定 秘密鍵が生成できたら、次にプロモヌションオファヌのプランを䜜成しおいきたす。 プロモヌションオファヌは既存のプランに玐づく圢で䜜成するため、元ずなるプランがただ存圚しない堎合はそちらから䜜成するようにしおください。 プロモヌションオファヌの蚭定も Apple のドキュメント 通りに進めおいきたす 「マむ App」から、蚭定する App を遞択 サむドバヌの「App 内課金」で、「管理」をクリック 自動曎新登録タむプのプロダクトをクリックし、「登録䟡栌」セクションに移動しお、「远加」ボタン+をクリック 「プロモヌションオファヌの䜜成」を遞択 内郚参照名ずオファヌコヌドを入力 「郜床払い」、「前払い」、「無料」のいずれかを遞択した埌、適切な期間、通貚、䟡栌を遞択 ちなみに、郜床払いを遞択した堎合は「適甚期間」「䟡栌」を遞択できるようになり、特定の期間だけ○円みたいな蚭定ができたす。 以䞊でプロモヌションオファヌの䜜成は完了です。 以降は実装に入っおいきたす。 プラン/プロモヌションオファヌの詳现を取埗 たずは以䞋のような凊理でプランの詳现 SKProduct のリク゚ストを行いたす。 var purchaseProductIdentifier : String ? func fetchProduct (id : String ) { purchaseProductIdentifier = id let productIdentifiers = Set < [purchaseProductIdentifier ! ] > request = SKProductsRequest(productIdentifiers : productIdentifiers ) request.delegate = self request.start() } 次に SKPaymentQueueDelegate にお SKProduct の取埗完了通知を受け取りたす。この蟺りは既に自動曎新サブスクリプションを導入されおいる堎合は同じ凊理になるかず思いたす。 ただ、プロモヌションオファヌが玐づいおいるプランを取埗した堎合、 SKProduct の discounts: [SKProductDiscount] に玐づいおいるプロモヌションオファヌが党お入った状態で取埗できたす。 SKProductDiscount にはプランの料金や適甚期間などが入っおいるので、 SKProduct の情報を䜿甚しお View を曎新する堎合は discounts を参照するようにしおください。 public func productsRequest (_ request : SKProductsRequest , didReceive response : SKProductsResponse ) { guard let purchaseProductIdentifier = purchaseProductIdentifier, let product = response.products.first( where : { $0 .productIdentifier == purchaseProductIdentifier }) else { // ゚ラヌ凊理 return } // .discounts にプロモヌションオファヌの情報が入った状態で取埗できる print(product.discounts) // 課金凊理ぞ } 眲名を生成 参考 Generating a Signature for Promotional Offers プロモヌションオファヌを導入するには既存の自動曎新サブスクリプションずは異なり「眲名の生成」を行い、埗られた文字列を賌入リク゚ストに含める必芁がありたす。 眲名の生成に必芁な情報以䞋の通りです。 名称 説明 appBundleID アプリの Bundle Identifier keyIdentifier App Store Connect で生成した秘密鍵を識別する ID productIdentifier プランの ID offerIdentifier プロモヌションオファヌの ID applicationUsername サヌビス内でナヌザを䞀意に識別する文字列任意 nonce サヌバヌサむドで生成する UUID小文字 timestamp サヌバヌサむドで生成する UNIX タむムスタンプミリ秒 node.js を甚いお眲名生成を行うサンプルコヌドを Apple が公開しおいるので、こちらを䟋に出しおおきたす。 䞀郚倉曎を加えおいる郚分もあるので、元コヌドを参照したい方は䞋蚘のリンクからご参照ください。 Generating a Subscription Offer Signature on the Server router.get( '/offer' , function (req, res) { // App Bundle Identifier. ここではパラメヌタから取埗しおいるが、環境倉数ずしお保持しおも良さそう. const appBundleID = req.body.appBundleID; // プランの ID const productIdentifier = req.body.productIdentifier; // プロモヌションオファヌの ID const subscriptionOfferID = req.body.offerID; // ナヌザを識別する文字列 const applicationUsername = req.body.applicationUsername; // 環境倉数等で保持しおいる、秘密鍵の ID const keyID = 'xxxxx' ; // 秘密鍵の䞭身(こちらも本来は環境倉数ずしお持぀べき) const keyString = '-----BEGIN PRIVATE KEY-----xxxxx-----END PRIVATE KEY-----' ; // UUID を生成. const nonce = uuidv4(); // タむムスタンプを生成. const currentDate = new Date (); const timestamp = currentDate.getTime(); // 党おの文字列を䞍可芖の分離文字列('\u2063')で結合 const payload = appBundleID + ' \u 2063' + keyID + ' \u 2063' + productIdentifier + ' \u 2063' + subscriptionOfferID + ' \u 2063' + applicationUsername + ' \u 2063' + nonce + ' \u 2063' + timestamp; // 秘密鍵を䜿甚しお楕円曲線デゞタルアルゎリズム(ECDSA)オブゞェクトを生成 const key = new ECKey(keyString, 'pem' ); // SHA-256 眲名アルゎリズムを䜿甚するよう蚭定 const cryptoSign = key.createSign( 'SHA256' ); // 結合した文字列を远加 cryptoSign.update(payload); // 眲名を生成し、base64 で゚ンコヌド. const signature = cryptoSign.sign( 'base64' ); // 生成した眲名が正しいものなのか怜蚌. // 眲名の凊理が正しく完了しおいるかを怜蚌するもので、生成した眲名で課金凊理が正しく行えるかを怜蚌するものではないので泚意. // extimestamp を "ミリ秒" ではなく "秒" で䜜成しおいるず課金時に゚ラヌになるが、この郚分での怜蚌には成功する const verificationResult = key.createVerify( 'SHA256' ).update(payload).verify(signature, 'base64' ); console.log( "Verification result: " + verificationResult) // アプリにレスポンスを返华. res.setHeader( 'Content-Type' , 'application/json' ); res.json( { 'keyID' : keyID, 'nonce' : nonce, 'timestamp' : timestamp, 'signature' : signature } ); } ); 賌入凊理を実行 通垞の自動曎新サブスクリプションでは SKPayment を䜿甚しお賌入リク゚ストを䜜成したすが、プロモヌションオファヌで課金する堎合は SKMutablePayment を䜿甚したす。 たた、課金するプロモヌションオファヌの ID などの情報は SKPaymentDiscout ずいう型が甚意されおいるので、こちらに必芁な情報を入れ、 SKMutablePayment の paymentDiscount プロパティにセットしたす func purchase (product : SKProduct , username : String , offerIdentifier : String ) { // サヌバヌに眲名の生成をリク゚スト YourServer.createSignature(username : username , productIdentifier : product.productIdentifier , offerIdentifier : offerIdentifier , completion : { (nonce : UUID , timestamp : NSNumber , keyIdentifier : String , signature : String ) in // プロモヌションオファヌでは SKMutablePayment を䜿甚 let payment = SKMutablePayment(product : product ) // プロモヌションオファヌの情報 let discountOffer = SKPaymentDiscount(identifier : offerIdentifier , // プロモヌションオファヌの ID keyIdentifier : keyIdentifier , // 秘密鍵を識別する ID nonce : nonce , // サヌバヌ偎で生成する UUID signature : signature , // サヌバヌ偎で生成した眲名文字列 timestamp : timestamp ) // サヌバヌ偎で生成したタむムスタンプ // SKMutablePayment に プロモヌションオファヌの情報を远加 payment.paymentDiscount = discount // (任意)ナヌザを識別する文字列を远加 payment.applicationUsername = username SKPaymentQueue. default ().add(payment) }) } レシヌト怜蚌 && トランザクションを完了 こちらは既存の自動曎新サブスクリプションず倉わらないため割愛したす 実装しおいおハマったずころ 過去課金経隓がないず゚ラヌになる プロモヌションオファヌは過去に課金経隓があるこず前提の機胜なので、 新たに䜜成したテストアカりントで課金しようずするず圓然゚ラヌになりたす。 Apple からその旚の゚ラヌが衚瀺されるのでこの点で詰たったず蚀う蚳ではないんですが、お詊しされる際は過去に課金経隓のあるアカりントで実斜するようご泚意ください。 ちなみに、iOS 14 から Sandbox アカりントで「利甚資栌のリセット」ずいうものができるようになりたしたが、過去課金経隓のあるアカりントでこれを実行しおも䞊蚘の゚ラヌメッセヌゞが衚瀺されたせんでした。 ~identifier が倚く混乱しおくる 実装を進めおいる䞭で、~identifier ず蚀う名称のものが倚く、混乱しおくる時がありたした。 たた、デバッグの際、プランの ID ずプロモヌションオファヌの ID を䌌たものにしおしたっおいたため、正しい倀がセットされおいるのかを刀断し難くなっおしたっおいたので、プロモヌションオファヌの ID には promotion_offer_~ のように接頭蟞などを付けるようにするず分かりやすくなりそうでした。 眲名生成時のデバッグがやりづらい ここが最も詰たったポむントなんですが、眲名生成のデバッグが蟛かったです。 圓然ず蚀えば圓然なんですが、眲名生成時に改行文字列等の䞍芁なものが含たれおいたりするず決枈に倱敗しおしたいたす。 たた、この際、ID/Password を入力する決枈画面は問題なく衚瀺され、ID/Password 入力埌の決枈時に゚ラヌになるず蚀う挙動になりたす。 そのため、圓初は眲名の生成自䜓は問題なく、アプリ偎のロゞックの䞍備を疑っおいたので回り道をする結果ずなりたした。眲名生成時に怜蚌を行っおいたしたが、あくたで正垞に眲名凊理が完了できおいるかを確認するもので、Apple が定める条件通りに眲名が行われおいるかを刀定するものでは無かったのも萜ずし穎でした。 その他、気を぀けたほうが良さそうなポむントを蚘茉したので、参考にしおいただければず思いたす。 項目 気を぀けるずころ nonce の生成 アルファベットは必ず小文字である必芁があるので泚意 timestamp の生成 単䜍は "秒" ではなく "ミリ秒" なので泚意 眲名埌の文字列 䜿甚する蚀語やラむブラリの仕様によっおは勝手に "\n" を挿入したりするこずがあるので泚意 おわりに いかがでしたでしょう プロモヌションオファヌを導入するこずで 今たでアプロヌチできなかったナヌザ局にアプロヌチするこずが可胜になるので、 既に自動曎新サブスクリプションを実装されおいる堎合は導入を怜蚎しおみるず良いかもしれたせん 明日、16日は匊瀟の Android ゚ンゞニアの meil さんによる「C# 9.0時代のnull刀定解剖」です お楜しみに たた、dely では䞀緒にサヌビスを成長させおいく仲間を募集䞭です www.wantedly.com www.wantedly.com 定期的にむベントも開催しおいるので、dely のこずを知りたいずいう方は是非是非ご参加お埅ちしおいたす bethesun.connpass.com
はじめに こんにちは クラシルWebのフロント゚ンドを担圓しおいる all-user です。 今回は、ずあるプロゞェクトをVue 2からVue 3に曞き換えおみたので、その過皋ず所感に぀いおたずめたいず思いたす。 この蚘事は dely #1 Advent Calendar 2020 14日目の蚘事です。 adventar.org adventar.org 昚日は funzin さんの Carthageで生成したframeworkの管理でRomeを導入しおみた でした。 元々䜿甚しおいたcarthage_cacheをRomeに眮き換える過皋が分かりやすく解説されおいたす。ぜひこちらも芗いおみおください🙌 さお、今回題材に遞んだプロゞェクトは小芏暡なVue 2で曞かれたアプリケヌションですが、そのスタック構成はかなりクラシルWebに近いものずなっおおり、今埌クラシルWebぞの導入を怜蚎する䞊での良い足がかりにできればず考えおおりたす。 目次 はじめに 目次 Vue 3に぀いお知る Vue 3の目玉。Composition APIずは Composition APIで䜕が倉わる Options API埓来のコンポヌネント定矩の課題 Composition APIによる関心事の分離 TypeScriptサポヌトの改善 個人プロゞェクトに぀いお 曞き換え䜜業ログ nodeずyarnを最新にアップデヌト ビルド蚭定をVue CLIで䞀気に最新に曞き換える スタックを遞択 いったんビルドしおみる .babelrcを削陀 vue-property-decoratorを倖す vuex-smart-moduleを倖す その他のラむブラリ 関心事を敎理しおhooksディレクトリに切り出す 実際に曞き換えおみおの所感 コヌドの芋通しはずおも良くなる Vuexの䜿い方はただ暡玢䞭 VuexのTSサポヌトはこれから プロダクションに投入できるタむミング ただ詊せおいないこず さいごに Vue 3に぀いお知る なにはずもあれ、曞き換えるにあたりたずはVue 3のキャッチアップから始めなければなりたせん。 マむグレヌションガむド を読んで解釈した内容を残しおおきたす。 Vue 3の目玉。Composition APIずは Vue 3で導入された新しいコンポヌネント実装のためのAPIです。 Reactの hooks にむンスパむアされた機胜で、コンセプトもずおも䌌おいたす。 Composition APIで䜕が倉わる これたでのViewModelむンスタンスを起点にしたロゞックの蚘述 this.xx や vm.xx な蚘述ではなく、関数の組み合わせによる蚘述が可胜になりたす。 たずえばコンポヌネントのロヌカルステヌトを定矩する時、Vue 2Options APIでは data を䜿いたした。 // DisplayCount.vue import { defineComponent } from "vue" ; export default defineComponent ( { // Vue.extendは廃止されたためdefineComponentを䜿甚したす data () { return { count: 0 } ; } , methods: { increment () { this .count += 1 ; } , decrement () { this .count -= 1 ; } } } ); テンプレヌト郚分は以䞋のようになりたす。 < template > < span v- text = "count" /> <!-- Vue 3ではFragmentがサポヌトされルヌト芁玠が単䞀の芁玠である必芁がなくなりたした --> < button @click= "increment" > + </ button > < button @click= "decrement" > - </ button > </ template > レンダリング結果はこんな感じ。 Vue 3Composition APIでは ref 関数を䜿いたす。 // DisplayCount.vue import { defineComponent , ref } from "vue" ; export default defineComponent ( { setup () { const count = ref ( 0 ); const increment = () => ( count.value += 1 ); // thisが消えた const decrement = () => ( count.value -= 1 ); // thisが消えた return { count , increment , decrement } ; }} ); テンプレヌトの内容は同じです。 setup 関数では count や increment が生えたオブゞェクトを返しおいたすが、これらのプロパティをテンプレヌト内で参照できるようになりたす。 ref 関数が返すRefオブゞェクトには value ずいうプロパティが生えおおり、このプロパティを通じお珟圚の倀を取埗するこずができたす。 このように倀をRefオブゞェクトでラップするこずで、ロヌカルステヌトの読み曞きを捕捉できるようになり、リアクティブにDOMの曎新を行えるようになりたす。 埓来はこのラッパヌの圹割をViewModelむンスタンス this が担っおいたした。 そしお、 increment , decrement から this が消えおいたす。 これは、 count ずいうロヌカルステヌトおよび+1、-1するメ゜ッドの定矩が、特定のコンポヌネントに属さなくなったず考えるこずができたす。 そのため、以䞋のように曞き換えるこずができたす。 // useCount.ts import { ref } from "vue" ; export const useCount = () => { const count = ref ( 0 ); const increment = () => ( count.value += 1 ); const decrement = () => ( count.value -= 1 ); return { count , increment , decrement } ; } ; // DisplayCount.vue import { defineComponent } from "vue" ; import { useCount } from "../hooks/useCount" ; export default defineComponent ( { setup () { return { ...useCount () } ; } } ); 「countずいうロヌカルステヌトを持ち、+1、-1するこずができる」機胜を useCount ずいう関数に切り出し、さらに useCount.ts ずいう別ファむルに切り出すこずができたした。 次に、これが出来るようになるこずで、どんなうれしいこずがあるのかを考えおみたす。 Options API埓来のコンポヌネント定矩の課題 ViewModelを起点にした蚘述では、 data , computed , methods などの制玄により、関心事の異なるロゞック同士が䞀箇所に束ねられ、逆に関心事を同じくするコヌドが分散しおしたうずいう問題がありたした。 次のコヌドは先ほどのサンプルに、 display ずいうロヌカルステヌトず toggleDisplayText ずいう算術プロパティを加えたものです。 関心事を次の2぀ずし、 数をカりントしたい 衚瀺を切り替えたい コヌド䞊の察応する郚分にコメントを入れるず次のようになりたす。 // DisplayCount.vue import { defineComponent } from "vue" ; export default defineComponent ( { data () { return { count: 0 , // a. 数をカりントしたい display: true // b. 衚瀺を切り替えたい } ; } , computed: { toggleDisplayText () : string { // b. 衚瀺を切り替えたい return this .display ? "hide" : "show" ; } } , methods: { increment () { // a. 数をカりントしたい this .count += 1 ; } , decrement () { // a. 数をカりントしたい this .count -= 1 ; } , toggleDisplay () { // b. 衚瀺を切り替えたい this .display = ! this .display ; } } } ); アプリヌケヌションが倧きくなりコンポヌネントが耇雑化するず、このように分散した関心事を頭の䞭でマッピングしながら読み解いおいくコストが倧きくなっおきたす。 テンプレヌトも曎新したす。 < template > < template v-if= "display" > < span v- text = "count" /> < button @click= "increment" > + </ button > < button @click= "decrement" > - </ button > </ template > < button @click= "toggleDisplay" v- text = "toggleDisplayText" /> </ template > レンダリング結果はこんな感じ。 hide をクリックするず以䞋のようになりたす。 次にこれをComposition APIに眮き換えおみたす。 Composition APIによる関心事の分離 Composition APIではロゞックの蚘述がViewModelに䟝存しなくなり、 data , computed , methods などの制玄から開攟されたす。 computed 関数が登堎したしたが、コンセプトは ref の時ず同じです。 ViewModelぞの参照を無くしたバヌゞョンの算術プロパティず考えればOKです。 数をカりントしたい 衚瀺を切り替えたい コヌド䞊の察応する郚分にコメントを入れるず次のようになりたす。 // DisplayCount.vue import { defineComponent } from "vue" ; export default defineComponent ( { setup () { const count = ref ( 0 ); // a. 数をカりントしたい const increment = () => ( count.value += 1 ); // a. 数をカりントしたい const decrement = () => ( count.value -= 1 ); // a. 数をカりントしたい const display = ref ( true ); // b. 衚瀺を切り替えたい const toggleDisplay = () => ( display.value = !display.value ); // b. 衚瀺を切り替えたい const toggleDisplayText = computed (() => ( display.value ? "hide" : "show" )); // b. 衚瀺を切り替えたい return { count , // a. 数をカりントしたい increment , // a. 数をカりントしたい decrement , // a. 数をカりントしたい display , // b. 衚瀺を切り替えたい toggleDisplay , // b. 衚瀺を切り替えたい toggleDisplayText // b. 衚瀺を切り替えたい } ; } } ); 関心事ベヌスでコヌドをたずめるこずができおいるこずが分かりたす。 最初のサンプル同様に、 setup 関数の倖にロゞックを切り出し、 useCount.ts 、 useToggleDisplay.ts ずいう別ファむルに切り出しおみたす。 // useCount.ts // a. 数をカりントしたい import { ref } from "vue" ; export const useCount = () => { const count = ref ( 0 ); const increment = () => ( count.value += 1 ); const decrement = () => ( count.value -= 1 ); return { count , increment , decrement } ; } ; // useToggleDisplay.ts // b. 衚瀺を切り替えたい import { computed , ref } from "vue" ; export const useToggleDisplay = () => { const display = ref ( true ); const toggleDisplay = () => ( display.value = !display.value ); const toggleDisplayText = computed (() => ( display.value ? "hide" : "show" )); return { display , toggleDisplay , toggleDisplayText } ; } ; // DisplayCount.vue import { defineComponent } from "vue" ; import { useCount } from "../hooks/useCount" ; // a. 数をカりントしたい import { useToggleDisplay } from "../hooks/useToggleDisplay" ; // b. 衚瀺を切り替えたい export default defineComponent ( { setup () { return { ...useCount (), // a. 数をカりントしたい ...useToggleDisplay () // b. 衚瀺を切り替えたい } ; } } ); それぞれの関心事が DisplayCount コンポヌネントから完党に分離されおいたす。 1 これは、これらのロゞックが特定のコンポヌネントに䟝存しおいないこずを瀺しおいたす。 ロゞックが特定のコンポヌネントに䟝存しおいないため、移怍性・再利甚性を高めるこずができたす。 耇数コンポヌネント間でロゞックを共通化しようずしお、extendやmixinsを䜿っお無茶をしたこずがあるのは僕だけではないはずです。 Composition APIを䜿えば、より自然にロゞックの共通化を衚珟できたす。 TypeScriptサポヌトの改善 Composition APIによりTypeScriptの型定矩もかなり改善したした。 埓来のOptions APIの型定矩は、 this に data , computed , methods などの定矩を生やすためのコヌドがずおも耇雑で、型定矩を芋に行っおは迷子になるこずもしょっちゅうでした。 ViewModel this ぞの参照を無くし、関数の合成によっおロゞックを衚珟出来るようになったこずで無理なく型を衚珟できおいるため、TypeScriptのコヌドが理解しやすくなりたした。 個人プロゞェクトに぀いお 今回曞き換えるのは rxjs-stream-editor ずいうRxJSの非同期凊理を可芖化するツヌルです。 Vue 2, Vuex, TypeScriptを䜿甚しおおり、コンポヌネントの数もルヌトコンポヌネントを入れお8぀、Vuex moduleの数も3぀ず怜蚌にはもっおこいの倧きさです。 クラス蚘法コンポヌネント、 vue-property-decorator 、 vuex-smart-module を䜿甚しおおり、珟時点ではVue 3未察応なラむブラリなため、今回の怜蚌ではいったん倖し぀぀なるべくVueの玠のAPIを䜿う方針でいきたす。 memowomome.hatenablog.com 曞き換え䜜業ログ github.com nodeずyarnを最新にアップデヌト node 👉 15.3.0にアップデヌト yarn 👉 1.22.10にアップデヌト ビルド蚭定をVue CLIで䞀気に最新に曞き換える rxjs-stream-editorはVue CLIを䜿甚したプロゞェクトなので、今回もVue CLIを䜿甚しおアップデヌトしたす。 vue upgrade ずいうコマンドも甚意されおいたすが、芏暡も小さいので今回は vue create で䞊曞く方法でやっおみたした🙌 # プロゞェクトのひず぀䞊のディレクトリに移動 cd .. # 同名のディレクトリを指定しお䞊曞き # オプションでGitコミット無し、既存ファむルずマヌゞするように指定 vue create -n --merge rxjs-stream-editor スタックを遞択 スタックをマニュアルで遞択 TS, Vuex, Stylus, ESLint, Prettierを䜿甚 Vue 3を䜿甚 クラス蚘法のコンポヌネント定矩ではなく玠のAPIを䜿甚 TypeScriptず䞀緒にBabelを䜿甚 保存時にLintを実行 ESLint等のコンフィグファむルはpackage.jsonにたずめず、専甚のファむルを䜿甚 Vue CLI v4.5.9 ? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, TS, Vuex, CSS Pre-processors, Linter ? Choose a version of Vue.js that you want to start the project with 3.x (Preview) ? Use class-style component syntax? No ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Stylus ? Pick a linter / formatter config: Prettier ? Pick additional lint features: Lint on save ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? No いったんビルドしおみる ゚ラヌがたくさん出たす。 このたた䞀旊コミットしおおきたす。 ゚ラヌログ党文 ERROR in src/App.vue:21:1 TS1238: Unable to resolve signature of class decorator when called as an expression. Type 'VueClass<any>' is missing the following properties from type 'typeof App': extend, set, delete, directive, and 6 more. 19 | import { ColorDefinition } from './core/ColorDefinition'; 20 | > 21 | @Component({ | ^^^^^^^^^^^^ > 22 | components: { | ^^^^^^^^^^^^^^^ > 23 | AppHeader, | ^^^^^^^^^^^^^^^ > 24 | StreamEditor, | ^^^^^^^^^^^^^^^ > 25 | BottomNav, | ^^^^^^^^^^^^^^^ > 26 | }, | ^^^^^^^^^^^^^^^ > 27 | }) | ^^^ 28 | export default class App extends Vue.extend({ 29 | computed: { 30 | ...domainStreamColorizerModule.mapState(['colorMatcherSourceCode']), ERROR in src/App.vue:21:2 TS2345: Argument of type 'typeof App' is not assignable to parameter of type 'VueClass<any>'. Type 'typeof App' is missing the following properties from type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")': useCssModule, useCssVars, createApp, createSSRApp, and 108 more. 19 | import { ColorDefinition } from './core/ColorDefinition'; 20 | > 21 | @Component({ | ^^^^^^^^^^^ > 22 | components: { | ^^^^^^^^^^^^^^^ > 23 | AppHeader, | ^^^^^^^^^^^^^^^ > 24 | StreamEditor, | ^^^^^^^^^^^^^^^ > 25 | BottomNav, | ^^^^^^^^^^^^^^^ > 26 | }, | ^^^^^^^^^^^^^^^ > 27 | }) | ^^^ 28 | export default class App extends Vue.extend({ 29 | computed: { 30 | ...domainStreamColorizerModule.mapState(['colorMatcherSourceCode']), ERROR in src/App.vue:28:22 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 26 | }, 27 | }) > 28 | export default class App extends Vue.extend({ | ^^^ 29 | computed: { 30 | ...domainStreamColorizerModule.mapState(['colorMatcherSourceCode']), 31 | ...domainStreamColorizerModule.mapGetters(['colorDefinitions']), ERROR in src/components/AppHeader/AppHeader.ts:3:1 TS1238: Unable to resolve signature of class decorator when called as an expression. Type '<VC extends VueClass<any>>(target: VC) => VC' is missing the following properties from type 'typeof AppHeader': extend, nextTick, set, delete, and 9 more. 1 | import { Component, Vue } from 'vue-property-decorator'; 2 | > 3 | @Component | ^^^^^^^^^^ 4 | export default class AppHeader extends Vue {} 5 | ERROR in src/components/AppHeader/AppHeader.ts:3:2 TS2769: No overload matches this call. Overload 1 of 2, '(options: ComponentOptionsBase<any, any, any, any, any, any, any, any, string, {}> & ThisType<any> & ThisType<any>): <VC extends VueClass<any>>(target: VC) => VC', gave the following error. Argument of type 'typeof AppHeader' is not assignable to parameter of type 'ComponentOptionsBase<any, any, any, any, any, any, any, any, string, {}> & ThisType<any> & ThisType<any>'. Type 'typeof AppHeader' is not assignable to type 'ComponentOptionsBase<any, any, any, any, any, any, any, any, string, {}>'. Types of property 'call' are incompatible. Type '<T, A extends any[]>(this: new (...args: A) => T, thisArg: T, ...args: A) => void' is not assignable to type '(this: unknown, ...args: unknown[]) => never'. The 'this' types of each signature are incompatible. Type 'unknown' is not assignable to type 'new (...args: unknown[]) => unknown'. Overload 2 of 2, '(target: VueClass<any>): VueClass<any>', gave the following error. Argument of type 'typeof AppHeader' is not assignable to parameter of type 'VueClass<any>'. Type 'typeof AppHeader' is missing the following properties from type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")': useCssModule, useCssVars, createApp, createSSRApp, and 108 more. 1 | import { Component, Vue } from 'vue-property-decorator'; 2 | > 3 | @Component | ^^^^^^^^^ 4 | export default class AppHeader extends Vue {} 5 | ERROR in src/components/AppHeader/AppHeader.ts:4:22 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 2 | 3 | @Component > 4 | export default class AppHeader extends Vue {} | ^^^^^^^^^ 5 | ERROR in src/components/BottomNav/BottomNav.ts:9:1 TS1238: Unable to resolve signature of class decorator when called as an expression. Type 'VueClass<any>' is missing the following properties from type 'typeof BottomNav': extend, set, delete, directive, and 6 more. 7 | import StreamColorizer from '../StreamColorizer/StreamColorizer.vue'; 8 | > 9 | @Component({ | ^^^^^^^^^^^^ > 10 | components: { | ^^^^^^^^^^^^^^^ > 11 | MessageOutput, | ^^^^^^^^^^^^^^^ > 12 | StreamColorizer, | ^^^^^^^^^^^^^^^ > 13 | }, | ^^^^^^^^^^^^^^^ > 14 | }) | ^^^ 15 | export default class BottomNav extends Vue.extend({ 16 | computed: { 17 | ...domainStreamEditorModule.mapState(['errorMessage', 'message']), ERROR in src/components/BottomNav/BottomNav.ts:9:2 TS2345: Argument of type 'typeof BottomNav' is not assignable to parameter of type 'VueClass<any>'. Type 'typeof BottomNav' is missing the following properties from type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")': useCssModule, useCssVars, createApp, createSSRApp, and 108 more. 7 | import StreamColorizer from '../StreamColorizer/StreamColorizer.vue'; 8 | > 9 | @Component({ | ^^^^^^^^^^^ > 10 | components: { | ^^^^^^^^^^^^^^^ > 11 | MessageOutput, | ^^^^^^^^^^^^^^^ > 12 | StreamColorizer, | ^^^^^^^^^^^^^^^ > 13 | }, | ^^^^^^^^^^^^^^^ > 14 | }) | ^^^ 15 | export default class BottomNav extends Vue.extend({ 16 | computed: { 17 | ...domainStreamEditorModule.mapState(['errorMessage', 'message']), ERROR in src/components/BottomNav/BottomNav.ts:15:22 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 13 | }, 14 | }) > 15 | export default class BottomNav extends Vue.extend({ | ^^^^^^^^^ 16 | computed: { 17 | ...domainStreamEditorModule.mapState(['errorMessage', 'message']), 18 | ...uiBottomNavModule.mapState(['enabled']), ERROR in src/components/MessageOutput/MessageOutput.ts:4:1 TS1238: Unable to resolve signature of class decorator when called as an expression. Type '<VC extends VueClass<any>>(target: VC) => VC' is missing the following properties from type 'typeof MessageOutput': extend, nextTick, set, delete, and 9 more. 2 | import { domainStreamEditorModule } from '../../store/modules/internal'; 3 | > 4 | @Component | ^^^^^^^^^^ 5 | export default class MessageOutput extends Vue.extend({ 6 | computed: { 7 | ...domainStreamEditorModule.mapState(['errorMessage', 'message']), ERROR in src/components/MessageOutput/MessageOutput.ts:4:2 TS2769: No overload matches this call. Overload 1 of 2, '(options: ComponentOptionsBase<any, any, any, any, any, any, any, any, string, {}> & ThisType<any> & ThisType<any>): <VC extends VueClass<any>>(target: VC) => VC', gave the following error. Argument of type 'typeof MessageOutput' is not assignable to parameter of type 'ComponentOptionsBase<any, any, any, any, any, any, any, any, string, {}> & ThisType<any> & ThisType<any>'. Type 'typeof MessageOutput' is not assignable to type 'ComponentOptionsBase<any, any, any, any, any, any, any, any, string, {}>'. Types of property 'call' are incompatible. Type '<T, A extends any[]>(this: new (...args: A) => T, thisArg: T, ...args: A) => void' is not assignable to type '(this: unknown, ...args: unknown[]) => never'. Overload 2 of 2, '(target: VueClass<any>): VueClass<any>', gave the following error. Argument of type 'typeof MessageOutput' is not assignable to parameter of type 'VueClass<any>'. Type 'typeof MessageOutput' is missing the following properties from type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")': useCssModule, useCssVars, createApp, createSSRApp, and 108 more. 2 | import { domainStreamEditorModule } from '../../store/modules/internal'; 3 | > 4 | @Component | ^^^^^^^^^ 5 | export default class MessageOutput extends Vue.extend({ 6 | computed: { 7 | ...domainStreamEditorModule.mapState(['errorMessage', 'message']), ERROR in src/components/MessageOutput/MessageOutput.ts:5:22 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 3 | 4 | @Component > 5 | export default class MessageOutput extends Vue.extend({ | ^^^^^^^^^^^^^ 6 | computed: { 7 | ...domainStreamEditorModule.mapState(['errorMessage', 'message']), 8 | }, ERROR in src/components/StreamColorizer/StreamColorizer.ts:2:10 TS2305: Module '"../../../node_modules/vue/dist/vue"' has no exported member 'VueConstructor'. 1 | import { Component, Vue } from 'vue-property-decorator'; > 2 | import { VueConstructor } from 'vue'; | ^^^^^^^^^^^^^^ 3 | import { domainStreamColorizerModule } from '../../store/modules/internal'; 4 | import { Photoshop } from 'vue-color'; 5 | import { ColorDefinition } from '../../core/ColorDefinition'; ERROR in src/components/StreamColorizer/StreamColorizer.ts:7:1 TS1238: Unable to resolve signature of class decorator when called as an expression. Type 'VueClass<any>' is missing the following properties from type 'typeof StreamColorizer': extend, set, delete, directive, and 6 more. 5 | import { ColorDefinition } from '../../core/ColorDefinition'; 6 | > 7 | @Component({ | ^^^^^^^^^^^^ > 8 | components: { | ^^^^^^^^^^^^^^^ > 9 | PhotoshopPicker: Photoshop as VueConstructor, | ^^^^^^^^^^^^^^^ > 10 | }, | ^^^^^^^^^^^^^^^ > 11 | }) | ^^^ 12 | export default class StreamColorizer extends Vue.extend({ 13 | computed: { 14 | ...domainStreamColorizerModule.mapState([ ERROR in src/components/StreamColorizer/StreamColorizer.ts:7:2 TS2345: Argument of type 'typeof StreamColorizer' is not assignable to parameter of type 'VueClass<any>'. Type 'typeof StreamColorizer' is missing the following properties from type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")': useCssModule, useCssVars, createApp, createSSRApp, and 108 more. 5 | import { ColorDefinition } from '../../core/ColorDefinition'; 6 | > 7 | @Component({ | ^^^^^^^^^^^ > 8 | components: { | ^^^^^^^^^^^^^^^ > 9 | PhotoshopPicker: Photoshop as VueConstructor, | ^^^^^^^^^^^^^^^ > 10 | }, | ^^^^^^^^^^^^^^^ > 11 | }) | ^^^ 12 | export default class StreamColorizer extends Vue.extend({ 13 | computed: { 14 | ...domainStreamColorizerModule.mapState([ ERROR in src/components/StreamColorizer/StreamColorizer.ts:12:22 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 10 | }, 11 | }) > 12 | export default class StreamColorizer extends Vue.extend({ | ^^^^^^^^^^^^^^^ 13 | computed: { 14 | ...domainStreamColorizerModule.mapState([ 15 | 'colorMatcherSourceCode', ERROR in src/components/StreamEditor/StreamEditor.ts:7:1 TS1238: Unable to resolve signature of class decorator when called as an expression. Type 'VueClass<any>' is missing the following properties from type 'typeof StreamEditor': extend, set, delete, directive, and 6 more. 5 | import debounce from 'lodash-es/debounce'; 6 | > 7 | @Component({ | ^^^^^^^^^^^^ > 8 | components: { | ^^^^^^^^^^^^^^^ > 9 | StreamEditorItem, | ^^^^^^^^^^^^^^^ > 10 | }, | ^^^^^^^^^^^^^^^ > 11 | }) | ^^^ 12 | export default class StreamEditor extends Vue.extend({ 13 | computed: { 14 | ...domainStreamEditorModule.mapGetters(['streamDatasets', 'sourceCode']), ERROR in src/components/StreamEditor/StreamEditor.ts:7:2 TS2345: Argument of type 'typeof StreamEditor' is not assignable to parameter of type 'VueClass<any>'. Type 'typeof StreamEditor' is missing the following properties from type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")': useCssModule, useCssVars, createApp, createSSRApp, and 108 more. 5 | import debounce from 'lodash-es/debounce'; 6 | > 7 | @Component({ | ^^^^^^^^^^^ > 8 | components: { | ^^^^^^^^^^^^^^^ > 9 | StreamEditorItem, | ^^^^^^^^^^^^^^^ > 10 | }, | ^^^^^^^^^^^^^^^ > 11 | }) | ^^^ 12 | export default class StreamEditor extends Vue.extend({ 13 | computed: { 14 | ...domainStreamEditorModule.mapGetters(['streamDatasets', 'sourceCode']), ERROR in src/components/StreamEditor/StreamEditor.ts:12:22 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 10 | }, 11 | }) > 12 | export default class StreamEditor extends Vue.extend({ | ^^^^^^^^^^^^ 13 | computed: { 14 | ...domainStreamEditorModule.mapGetters(['streamDatasets', 'sourceCode']), 15 | }, ERROR in src/components/StreamEditor/StreamEditor.ts:25:10 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 23 | }) { 24 | @Watch('sourceCode') > 25 | public watchSourceCode() { | ^^^^^^^^^^^^^^^ 26 | this.evaluateSourceCodeDebounced(); 27 | } 28 | ERROR in src/components/StreamEditorItem/StreamEditorItem.ts:23:1 TS1238: Unable to resolve signature of class decorator when called as an expression. Type 'VueClass<any>' is missing the following properties from type 'typeof StreamEditorItem': extend, set, delete, directive, and 6 more. 21 | }; 22 | > 23 | @Component({ | ^^^^^^^^^^^^ > 24 | components: { | ^^^^^^^^^^^^^^^ > 25 | StreamEditorTextarea, | ^^^^^^^^^^^^^^^ > 26 | }, | ^^^^^^^^^^^^^^^ > 27 | }) | ^^^ 28 | export default class StreamEditorItem extends Vue.extend({ 29 | computed: { 30 | ...domainStreamColorizerModule.mapGetters([ ERROR in src/components/StreamEditorItem/StreamEditorItem.ts:23:2 TS2345: Argument of type 'typeof StreamEditorItem' is not assignable to parameter of type 'VueClass<any>'. Type 'typeof StreamEditorItem' is missing the following properties from type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")': useCssModule, useCssVars, createApp, createSSRApp, and 108 more. 21 | }; 22 | > 23 | @Component({ | ^^^^^^^^^^^ > 24 | components: { | ^^^^^^^^^^^^^^^ > 25 | StreamEditorTextarea, | ^^^^^^^^^^^^^^^ > 26 | }, | ^^^^^^^^^^^^^^^ > 27 | }) | ^^^ 28 | export default class StreamEditorItem extends Vue.extend({ 29 | computed: { 30 | ...domainStreamColorizerModule.mapGetters([ ERROR in src/components/StreamEditorItem/StreamEditorItem.ts:28:22 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 26 | }, 27 | }) > 28 | export default class StreamEditorItem extends Vue.extend({ | ^^^^^^^^^^^^^^^^ 29 | computed: { 30 | ...domainStreamColorizerModule.mapGetters([ 31 | 'colorCodeGetter', ERROR in src/components/StreamEditorItem/StreamEditorItem.ts:39:18 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 37 | }, 38 | }) { > 39 | @Prop() public dataset: StreamDataset | undefined; | ^^^^^^^ 40 | @Prop({ required: true }) public index!: boolean; 41 | @Prop({ default: false }) public disabled!: boolean; 42 | ERROR in src/components/StreamEditorItem/StreamEditorItem.ts:40:36 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 38 | }) { 39 | @Prop() public dataset: StreamDataset | undefined; > 40 | @Prop({ required: true }) public index!: boolean; | ^^^^^ 41 | @Prop({ default: false }) public disabled!: boolean; 42 | 43 | get events() { ERROR in src/components/StreamEditorItem/StreamEditorItem.ts:41:36 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 39 | @Prop() public dataset: StreamDataset | undefined; 40 | @Prop({ required: true }) public index!: boolean; > 41 | @Prop({ default: false }) public disabled!: boolean; | ^^^^^^^^ 42 | 43 | get events() { 44 | return this.dataset ? this.dataset.events : []; ERROR in src/components/StreamEditorTextarea/StreamEditorTextarea.ts:5:1 TS1238: Unable to resolve signature of class decorator when called as an expression. Type '<VC extends VueClass<any>>(target: VC) => VC' is missing the following properties from type 'typeof StreamEditorTextarea': extend, nextTick, set, delete, and 9 more. 3 | import { domainStreamEditorModule } from '../../store/modules/internal'; 4 | > 5 | @Component | ^^^^^^^^^^ 6 | export default class StreamEditorTextarea extends Vue.extend({ 7 | methods: { 8 | ...domainStreamEditorModule.mapMutations(['setSourceCode']), ERROR in src/components/StreamEditorTextarea/StreamEditorTextarea.ts:5:2 TS2769: No overload matches this call. Overload 1 of 2, '(options: ComponentOptionsBase<any, any, any, any, any, any, any, any, string, {}> & ThisType<any> & ThisType<any>): <VC extends VueClass<any>>(target: VC) => VC', gave the following error. Argument of type 'typeof StreamEditorTextarea' is not assignable to parameter of type 'ComponentOptionsBase<any, any, any, any, any, any, any, any, string, {}> & ThisType<any> & ThisType<any>'. Type 'typeof StreamEditorTextarea' is not assignable to type 'ComponentOptionsBase<any, any, any, any, any, any, any, any, string, {}>'. Types of property 'call' are incompatible. Type '<T, A extends any[]>(this: new (...args: A) => T, thisArg: T, ...args: A) => void' is not assignable to type '(this: unknown, ...args: unknown[]) => never'. Overload 2 of 2, '(target: VueClass<any>): VueClass<any>', gave the following error. Argument of type 'typeof StreamEditorTextarea' is not assignable to parameter of type 'VueClass<any>'. Type 'typeof StreamEditorTextarea' is missing the following properties from type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")': useCssModule, useCssVars, createApp, createSSRApp, and 108 more. 3 | import { domainStreamEditorModule } from '../../store/modules/internal'; 4 | > 5 | @Component | ^^^^^^^^^ 6 | export default class StreamEditorTextarea extends Vue.extend({ 7 | methods: { 8 | ...domainStreamEditorModule.mapMutations(['setSourceCode']), ERROR in src/components/StreamEditorTextarea/StreamEditorTextarea.ts:6:22 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 4 | 5 | @Component > 6 | export default class StreamEditorTextarea extends Vue.extend({ | ^^^^^^^^^^^^^^^^^^^^ 7 | methods: { 8 | ...domainStreamEditorModule.mapMutations(['setSourceCode']), 9 | }, ERROR in src/components/StreamEditorTextarea/StreamEditorTextarea.ts:11:18 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 9 | }, 10 | }) { > 11 | @Prop() public dataset: StreamDataset | undefined; | ^^^^^^^ 12 | @Prop({ default: false }) public disabled!: boolean; 13 | 14 | get sourceCode() { ERROR in src/components/StreamEditorTextarea/StreamEditorTextarea.ts:12:36 TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. 10 | }) { 11 | @Prop() public dataset: StreamDataset | undefined; > 12 | @Prop({ default: false }) public disabled!: boolean; | ^^^^^^^^ 13 | 14 | get sourceCode() { 15 | return this.dataset ? this.dataset.sourceCode : ''; ERROR in src/main.ts:6:5 TS2339: Property 'use' does not exist on type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")'. 4 | import VueTextareaAutosize from 'vue-textarea-autosize'; 5 | > 6 | Vue.use(VueTextareaAutosize); | ^^^ 7 | Vue.config.productionTip = false; 8 | 9 | new Vue({ ERROR in src/main.ts:7:5 TS2339: Property 'config' does not exist on type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")'. 5 | 6 | Vue.use(VueTextareaAutosize); > 7 | Vue.config.productionTip = false; | ^^^^^^ 8 | 9 | new Vue({ 10 | store, ERROR in src/main.ts:9:5 TS2351: This expression is not constructable. Type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")' has no construct signatures. 7 | Vue.config.productionTip = false; 8 | > 9 | new Vue({ | ^^^ 10 | store, 11 | render: h => h(App), 12 | }).$mount('#app'); ERROR in src/main.ts:11:11 TS7006: Parameter 'h' implicitly has an 'any' type. 9 | new Vue({ 10 | store, > 11 | render: h => h(App), | ^ 12 | }).$mount('#app'); 13 | ERROR in src/store/index.ts:6:5 TS2339: Property 'use' does not exist on type 'typeof import("/Users/okamoto.k/_ghqroot/github.com/all-user/rxjs-stream-editor/node_modules/vue/dist/vue")'. 4 | import { rootModule } from './modules'; 5 | > 6 | Vue.use(Vuex); | ^^^ 7 | 8 | export default createStore(rootModule); 9 | .babelrcを削陀 Babel最新ではbabel.config.jsを䜿甚するように倉わったようなのですが、Vue CLIでディレクトリをマヌゞした際に.babelrcが残っおおり、そちらの蚭定を芋に行っおしたい゚ラヌが出おいたした。 これを削陀したす。 vue-property-decoratorを倖す デコレヌタを䜿ったコンポヌネント定矩を defineComponent に眮き換えおいきたす。 vuex-smart-moduleを倖す Vue 3察応のために玠のVuexに曞き換えたす。 vuex-smart-moduleを通しお store にアクセスしおいた郚分は、 useStore 関数を䜿甚しお眮き換えたす。 この時点で commit , dispatch , getters に付いおいたメ゜ッドの型は、 string であればなんでも受け入れるようになっおしたいたす。 やはりただvuex-smart-moduleは倖せないずいう所感。 Composition API察応に関するIssueでは察応予定ずのコメントもあり今埌の動きに期埅です。 ktsn/vuex-smart-module | Composition API? #106 テンプレヌトから参照するものは埓来通り mapState や mapGetters を䜿甚し、コンポヌネント定矩内で参照するものは useStore を䜿甚しおいたす。 この蟺りの曞き方はどうするのが良いだろう、ずいう感じでただ暡玢䞭です。 その他のラむブラリ vue-textarea-autosize テキストの入力に合わせお自動的に高さを調敎しおくれる Vue 3未察応 ずりあえず普通のtextareaに眮き換えおしのぐ vue-color カラヌピッカヌ Vue 3未察応 いったんあきらめる 関心事を敎理しお hooks ディレクトリに切り出す setup にたずめお曞かれおいた初期化凊理を぀のファむルに分割し、各関数内でそれぞれ useStore を䜿甚するように倉曎したす。 カラヌパレットの初期化凊理。 カラヌパレットずむベントをマッピングするための゜ヌスコヌドの初期化凊理。 可芖化するRxJS Observableを生成する゜ヌスコヌドの初期化凊理。 実際に曞き換えおみおの所感 コヌドの芋通しはずおも良くなる 関心事の分離がうたく衚珟できるようになり、コヌドの芋通しが良くなりたした。 芏暡の倧きいプロゞェクトであればより効果を発揮できるのではず感じたした。 Vuexの䜿い方はただ暡玢䞭 今回はテンプレヌトから参照する倀を埓来のOptions APIで蚘述したしたが、Composition APIに最適化されたヘルパヌに぀いおの議論が行われおいおいたした。 近いうちにベストプラクティスが発明されそうです。 github.com VuexのTSサポヌトはこれから Vuex 4でTypeScriptのサポヌトは匷化されたものの、state以倖の型呚りはただサポヌトされおいたせん。 珟時点ではvuex-smart-moduleなどのTypeScriptサポヌトのラむブラリは必芁だず感じたした。 こちらのIssueを芋るず、本栌的なTSサポヌトの匷化はVuex 5を予定しおいるようです。 github.com たた、それに䌎うBreaking Changeを怜蚎しおいる暡様。 䞀方で、TS 4.1ずいうゲヌムチェンゞャヌの登堎によりVuex 4もサポヌトされる可胜性が出おきたようです。 github.com TS 4.1で導入されたTemplate Literal Typesにより文字列の柔軟な型怜査が可胜になり、これたで難しかったnamespaceをスラッシュで繋いだ文字列に察しおの静的な型怜査が実装可胜になりたした。 近い将来TS完党察応が実珟するかもしれたせん。 プロダクションに投入できるタむミング 䞊蚘を螏たえるず今すぐのプロダクション投入は難しいものの、確実にメリットを感じたので、少しづ぀移行に向けお準備を進めおいきたいず思いたす。 ただ詊せおいないこず ロヌカルステヌトを定矩しおいるコンポヌネントが無かったため、 ref , reactive を䜿った耇雑な実装はただ詊せおいないです vue-routerも䜿甚しおいないためこちらもただ未怜蚌です 匕き続き怜蚌しおいきたいず思いたす さいごに 以䞊Vue 3ぞの曞き換えを通しおの所感をたずめおみたした。 この蚘事では玹介できなかった现かいAPIの倉曎などもありたすが、暗黙的な挙動の削陀やパフォヌマンス改善のための倉曎など確実にパワヌアップしおいたす。 個人的にはFragmentが䜿えるようになったのが最高です。 これからのVue 3を楜しんでいきたしょう そしお明日は sako さんの「UIデザむナヌずしお働く私が就掻生に戻ったら絶察やるこず5぀」です めちゃくちゃ知りたい delyでぱンゞニアを党方䜍絶賛募集䞭です🚀 こちらのリンクからお気軜に゚ントリヌください🙌 join-us.dely.jp たた、delyでは普段衚に出ない開発チヌムの裏偎をお䌝えするむベントをたくさん開催しおおりたす こちらもぜひ芗いおみおください bethesun.connpass.com ではたた toggleDisplayText に぀いおはUI郜合な郚分が倧きいため、 useToggleDisplay には含めず DisplayCount コンポヌネント偎に寄せたいずころですが、今回は分かりやすさのためにこのようにしおいたす。 ↩
目次 目次 はじめに りェブのこれからを远いかける Project Fugu ずは 怜蚎されおいる゚コシステム User Idle Detection API macOS Touch Bar API その他 おわりに はじめに こんにちは、dely株匏䌚瀟で゚ンゞニアをしおいるしらりんです。4月に立ち䞊げられたリテヌル事業郚ずいう郚眲で、䞻にりェブフロント゚ンド・サヌバヌサむド領域の開発を担圓しおいたす。 先日初めおのぎっくり腰を経隓し、それ以降日々腰を曲げるのに恐怖を感じおいたす。 そんなこずはさおおき、この蚘事は「dely #2 Advent Calendar 2020」の14日目の蚘事です。 adventar.org adventar.org 昚日は開発郚GM 井䞊さん( @gomesuit )の「技術だけではもう足りない゚ンゞニアずしおの成長のために避けおは通れない぀の領域ずは」ずいう蚘事でした。 tech.dely.jp りェブのこれからを远いかける 蚘事のテヌマに「りェブの未来」ずスケヌル倧きめな衚珟が含たれおいたすが倧した内容ではありたせん。 Project Fuguで考えられおいるこれからのりェブでどんなこずができるようになっおいくのか、今どんなこずが進んでいるのかをfugu-tracker-apiを少し眺めおみるずいった内容になりたす。 Project Fugu ずは りェブは汎甚的なプラットフォヌムずしおずおも優秀ですが、汎甚的であるこずもあり少し凝ったこず(特化したこず)をやろうずするず、ネむティブアプリではできるがりェブでは難しいずいうこずが倚く存圚したす。アプリではよくみるシェア機胜をりェブで提䟛するWeb Share APIが䜿えるようになったのも぀い最近のこずになりたす。 Web Share APIの察応状況 caniuse.com そういったネむティブアプリずりェブのギャップを埋めるための取り組みがProject Fugu(Web Capabilities Project)です。 www.chromium.org そんなProject Fuguで考えられおいる機胜がい぀提䟛されるのかが把握しやすくたずたっおいるのがfugu-api-trackerです。 docs.google.com ここでは珟圚どのような゚コシステムが詊隓・怜蚎されおいるのか、いく぀かみおみるこずにしたす。 怜蚎されおいる゚コシステム User Idle Detection API 珟圚origin trialずしお提䟛されおいる機胜。 マりスやキヌボヌド、タッチスクリヌンに察する操䜜が行われおいないこずや、別のタブやりィンドりを開いおいるなどナヌザヌのアむドル状態を怜知するこずができたす。 䟋えばフィヌドバックのタむミングをナヌザヌがアクティブな状態に戻ったずきに行うなどずいった掻甚が考えられたす。 bugs.chromium.org macOS Touch Bar API 䞭にはこんなものもありたす。 bugs.chromium.org 名前の通り、最近のmacbook(pro)に付いおいるタッチバヌを掻甚するAPIです。 䟋えばこれを䜿っおタッチバヌでプログレスバヌを衚珟したり、簡単なゲヌムを䜜ったりずするこずができたす。 ドキュメント内から参照できるelectronのAPIでは、electronず以䞋のコヌドを利甚しおタッチバヌの぀いおいるmacbookで動かせるスロットゲヌムを䜜るサンプルがのっおいたす。 将来的にこれがweb apiずしおも利甚可胜になるかもしれたせん。 // touchbar.js const { app, BrowserWindow, TouchBar } = require( 'electron' ) const { TouchBarLabel, TouchBarButton, TouchBarSpacer } = TouchBar let spinning = false const reel1 = new TouchBarLabel() const reel2 = new TouchBarLabel() const reel3 = new TouchBarLabel() const result = new TouchBarLabel() const spin = new TouchBarButton( { label: '🎰 Spin' , backgroundColor: '#7851A9' , click: () => { if (spinning) { return } spinning = true result.label = '' let timeout = 10 const spinLength = 4 * 1000 const startTime = Date .now() const spinReels = () => { updateReels() if (( Date .now() - startTime) >= spinLength) { finishSpin() } else { timeout *= 1.1 setTimeout(spinReels, timeout) } } spinReels() } } ) const getRandomValue = () => { const values = [ '🍒' , '💎' , '7⃣' , '🍊' , '🔔' , '⭐' , '🍇' , '🍀' ] return values [ Math.floor(Math.random() * values.length) ] } const updateReels = () => { reel1.label = getRandomValue() reel2.label = getRandomValue() reel3.label = getRandomValue() } const finishSpin = () => { const uniqueValues = new Set( [ reel1.label, reel2.label, reel3.label ] ).size if (uniqueValues === 1) { result.label = '💰 Jackpot!' result.textColor = '#FDFF00' } else if (uniqueValues === 2) { result.label = '😍 Winner!' result.textColor = '#FDFF00' } else { result.label = '🙁 Spin Again' result.textColor = null } spinning = false } const touchBar = new TouchBar( { items: [ spin, new TouchBarSpacer( { size: 'large' } ), reel1, new TouchBarSpacer( { size: 'small' } ), reel2, new TouchBarSpacer( { size: 'small' } ), reel3, new TouchBarSpacer( { size: 'large' } ), result ] } ) let window app.whenReady().then(() => { window = new BrowserWindow( { frame: false , titleBarStyle: 'hiddenInset' , width: 200, height: 200, backgroundColor: '#000' } ) window .loadURL( 'about:blank' ) window .setTouchBar(touchBar) } ) ./node_modules/.bin/electron touchbar.js (䞀瞬ラッキヌセブンが揃った🀑 ) アドベントカレンダヌ甚 pic.twitter.com/Y0cbPp9ZYF — しらりん (@Srrn97) 2020幎12月14日 その他 その他にも䞊の方で少し觊れたWeb Share APIのデスクトップ察応だったり、 input type="file" などから遞択されたファむルのリサむズ機胜(将来的には動画のサむズ倉曎にも察応)ずいった様々な機胜の提䟛が怜蚎されおいたす。 bugs.chromium.org bugs.chromium.org おわりに いかがでしたでしょうか。 ブラりザやOSの皮類、バヌゞョンなど倚くの壁が存圚するりェブですが、他のどんなネむティブアプリケヌションにもなれる可胜性を秘めおいるこずがりェブの魅力の1぀だず僕は思っおいたす。 fugu-api-trackerを芋れば今埌どのような゚コシステムが詊隓され、提䟛されおいくのかを把握しやすく眺めおいるだけでも楜しいです。ここで玹介したものは極䞀郚のものでしかないので、是非䞀床目を通しおみおください。 明日はnancyさんの「iOSのサブスクリプション機胜 プロモヌションオファヌを觊っおみた」です。お楜しみに 最埌になりたすが、delyでぱンゞニア・デザむナヌを積極的に採甚しおいたす。 興味がある方は是非゚ントリヌしおください join-us.dely.jp たた、定期的にテックトヌクむベントを開催しおいたす。 delyに぀いおちょっず興味ある・もっず知っおみたいずいう方は、お気軜にご参加ください。 bethesun.connpass.com
こんにちは dely開発郚GMの井䞊 @gomesuit です。 この蚘事は「 dely #2 Advent Calendar 2020 」の13日目の蚘事です。 昚日はサヌバサむド゚ンゞニアのyamanoiさんの「 Cloud Runで手軜にサヌバヌレス・SSR 」ずいう蚘事でした。 adventar.org adventar.org 目次 目次 はじめに プロダクト開発における技術遞定の捉え方 プロダクト開発における意思決定っお䜕 意思決定はどのように行われるか 意思決定においお必芁な情報ずは プロダクト開発における情報のマネゞメント テクノロゞヌ領域の知識だけでは粟床の高い技術遞定はできない䟋 䟋マむクロサヌビス化 䟋プログラミング蚀語・フレヌムワヌクの採甚 たずめ さいごに はじめに delyに来おマネゞメントに関わるようになっおから幎が経ちたした。゚ンゞニアの成長に぀いお色々考えさせられるこずがあるのですが、 プロダクト開発においお゚ンゞニアがやるべきこずはシステムの蚭蚈ず実装だけではなくなっおきた なず、最近になっおより匷く感じるようになりたした。環境や携わっおいるプロダクトの特性やフェヌズによっおももちろん倉わるず思うのですが、この流れは今埌加速しおいくだろうず感じるので、この機䌚に敎理しおおこうず思いたす。 むンタヌネット技術の進化によっお、䞖の䞭の色々なものがIT技術に眮き換えられおいっおいたす。プロダクトやサヌビスの数も急速に増えおいたすが、WEBサヌビス開発における技術的な実珟方法はどのプロダクトもそこたで倧きな違いはないず思いたす。ずいうのも、どのプロダクトでも共通に課題だず認識しおいるコアな技術的芁玠はクラりドやSaaSが生たれ、それらに眮き換えられおいっおるからです。数幎前たでは技術的な実珟手段に぀いお゚ンゞニアが頭を悩たせおいたしたが、今ではサヌビスを組み合わせるこずで、比范的簡単に実珟できおしたう䞖の䞭になっおきたした。この流れは今埌萜ち着いおいくこずはなく、より䞀局加速しおくず考えおいたす。 技術的な実珟方法がコモディティ化しおいくこの時代においお、プロダクト開発に携わる゚ンゞニアは今埌䜕をしおいくべきなのか を考えおみたした。 考えを敎理するにあたっお䞋蚘の蚘事を参考にさせお頂きたした。 ゚ンゞニアリングマネヌゞャ/プロダクトマネヌゞャのための知識䜓系ず読曞ガむド プロダクト開発における技術遞定の捉え方 ゚ンゞニアなら誰しも課題に察しお適切な手段を遞定しお、ビシバシ解消しおいきたいですよね。ただこの「技術遞定」ずいう蚀葉、"技術"ずいう蚀葉が入っおいるが故に若干ミスリヌドしおいるのではないかなず最近思っおいたす。「技術遞定」ずいう蚀葉を䜿うず䜕か特別感が出おしたうのですが、プロダクト開発においおは「 意思決定の䞀皮 」ずしお捉えた方が自然ではないかなず思っおいたす。 プロダクト開発における意思決定っお䜕 プロダクト開発においおは倧小様々な意思決定が行われたす。 䟋えば、「䜕の機胜を䜜るか」、「どの機胜から䜜るか」、「い぀たでに䜜るか」、「誰が䜜るか」、「どうやっお䜜るか」、などなど䞊げればキリがないですが、これらはプロダクト開発においお決めないず䜕も進たない芁玠であり、意思決定すべき項目になりたす。䟋に䞊げた抜象床の高いものだけではなく、実装においお䜕のラむブラリを䜿うのかやどういったデヌタ構造にするのかずいった具䜓性の高いものも意思決定の䞀぀になりたす。 ちなみにdelyではスクアッドずいう組織構成を䜜っおいお、スピヌド感を持った意思決定ができるようにしおいたす。 https://speakerdeck.com/tsubotax/dely?slide=29 意思決定はどのように行われるか 意思決定を行うためには、決定を䞋すために必芁な情報を敎理する必芁がありたす。意思決定の䞍確実性が高ければ高いほど、広範囲に枡る情報を集める必芁があり、それぞれの分野の有識者を巻き蟌む必芁が出おきたす。情報を敎理するこずで遞択肢ずメリデメを掗い出し、各ステヌクホルダヌの合意を圢成を行った埌、責任者が決定を䞋すこずで意思決定は行われたす。 意思決定においお必芁な情報ずは 意思決定に必芁な情報は、意思決定しようずしおいる範囲によっおも倉わりたすが、䟋えば「䜕の機胜を䜜るか」でいくず、その機胜が䌚瀟のビゞョンや事業蚈画に沿っおいるのかやどうかずいった情報が必芁になりたす。「どの機胜から䜜るか」であれば、芁件定矩や予算が必芁になり、「い぀たでに䜜るか」であれば芋積もりやスケゞュヌルが、「誰が䜜るか」であれば開発リ゜ヌス、「どうやっお䜜るか」であれば機胜芁件や非機胜芁件、珟状のシステムのアヌキテクチャが必芁になりたす。その他のずころではマヌケティングやセヌルスずの関連がないかなど、䞊げればキリがないですが、 粟床の高い決定を䞋そうずすればするほど、倚くの情報が必芁に なりたす。 プロダクト開発における情報のマネゞメント 前項では、意思決定に必芁な情報は䜕かずいう話をしたしたが、それらの情報を埗るための知識は分類できたす。そしお、 ゚ンゞニアずしおの成長のために避けおは通れない぀の領域 もこちらになりたす。 https://qiita.com/hirokidaichi/items/95678bb1cef32629c317#各皮スキルず読曞ガむド を元に画像を䜜成したした。 䞍確実性の高い意思決定を䞋す際は、基本的に耇数の分野の情報が必芁になりたす。人が党おの知識を持っおいるこずは皀なので、基本的には耇数人で情報を集める必芁がありたす。ただし、必芁な人数が増えれば増えるほどコミュニケヌションコストが増えるずずもに、それ盞応の察話スキルが求められるようになりたす。そのため 耇数領域の知識を持぀人は必然的に意思決定に巻き蟌たれやすく なりたす。 https://qiita.com/hirokidaichi/items/95678bb1cef32629c317#匱めのem定矩ず匷めのem定矩 を元に画像を䜜成したした。 意思決定の䞀皮である技術遞定においおも、 䞍確実性の高いものはテクノロゞヌ領域の知識だけでは決定を䞋すための情報が足りないずいうこずが蚀える ず思いたす。 テクノロゞヌ領域の知識だけでは粟床の高い技術遞定はできない䟋 ぀ほど具䜓䟋を考えおみたした。 䟋マむクロサヌビス化 最近話題に䞊がりやすい「マむクロサヌビス」を題材にしおみたす。プロダクトが成長し組織が倧きくなったタむミングで、システムの構造をマむクロサヌビス化しようずいう話が䞊がったずしたす。圓然マむクロサヌビス化する「目的」にもよるず思いたすが、䞀旊そこは無芖した䞊でそういった状況になった堎合、技術的な芳点以倖で必芁になりそうな情報は䞋蚘の通りです。 今埌の事業蚈画や組織䜓制 サヌビスの粒床の決め方やサヌビス間の埪環䟝存を防止する仕組み 仮にテクノロゞヌ領域の知識だけで意思決定を行った堎合、アヌキテクチャず組織構造のズレが発生したり、ルヌルやガむドラむンなしでマむクロサヌビス化を行った結果、暪断的な芖点が倱われおより䞀局課題が倧きくなるずいったこずが考えられそうです。テクノロゞヌ領域だけの情報で決めるのではなく、将来的な事業方針や組織䜓制を螏たえた䞊で決めおいく事柄ではないかなず思いたす。 䟋プログラミング蚀語・フレヌムワヌクの採甚 クラりドやコンテナ技術の浞透によっお、プログラミング蚀語やフレヌムワヌクの流行は倧きく圱響を受けたした。呚りの技術が進歩・浞透するこずによっお、その環境前提のプログラミング蚀語やフレヌムワヌクが新しく生たれたり、流行ったりしたす。珟圚のプロダクトがレガシヌなプログラミング蚀語やフレヌムワヌクで開発されおいた堎合、新しい機胜は別のプログラミング蚀語・フレヌムワヌクで開発しようずいう話が䞊がるこずもあるず思いたす。そういった状況になった堎合、技術的な芳点以倖で必芁になりそうな情報は䞋蚘の通りです。 採甚蚈画 既存メンバヌの孊習コスト 既存システムの移管プロゞェクト ゚ンゞニアのモチベヌション 仮にテクノロゞヌ領域の知識だけで意思決定を行った堎合、採甚蚈画ぞの圱響や既存システムをどうするのかのプロゞェクト芳点が抜け萜ち、別の課題が新たに生たれるこずは間違いないでしょう。テクノロゞヌ領域の情報だけで決めるのではなく、採甚蚈画を含めた長期の芖点も含めお決めおいく必芁がある領域であるず思いたす。 たずめ この蚘事を通しお自分が䌝えたかったこずは䞋蚘になりたす。 プロダクト開発においお、より䞍確実性の倧きい技術遞定をするためには、テクノロゞヌ領域の知識だけでは足りない テクノロゞヌ領域の知識を぀けるこずだけが゚ンゞニアの成長ではない。プロダクト、プロゞェクト、ピヌプル領域のマネゞメントスキルを身に぀ければ、より䞍確実性の倧きい領域に察しお技術遞定ができるようになる ゚ンゞニアが各領域のマネゞメントを行う経隓は、゚ンゞニアずしおの成長にずっおも倧きな芁玠になる ゚ンゞニアでありながら、プロダクトマネゞメント、プロゞェクトマネゞメント、ピヌプルマネゞメントの圹割を担っおいる方がいるず思いたすが、 コヌドを曞かないこずを䞍安に思う必芁は党くない ず思いたす。プロダクト開発においお、゚ンゞニアがやるべきこずの䞭で「コヌドを曞く」ずいうこずの割合は少しず぀枛っおいきたす。今自分がやるべきこずをやり、物事を成し遂げお行くこずが重芁であり、その結果プロダクトが成長しおいけば、自ずず自身も゚ンゞニアずしお成長するはずだず思いたす。 さいごに たた、dely でぱンゞニアを絶賛募集䞭です ご興味ある方はこちらのリンクからお気軜に゚ントリヌください join-us.dely.jp さらに、定期的に TechTalk ずいうむベントを通じお、クラシルで利甚しおいる技術や開発手法、組織に関する情報も発信しおおりたす。 ノりハりの共有だけでなく、クラシルで働く゚ンゞニアがどんな想いを持っお働いおいるのかや、働く人の雰囲気を感じおいただけるむベントになっおいたすので、ぜひお気軜にご参加ください bethesun.connpass.com
はじめたしお、dely開発郚の funzin です。普段はクラシルのiOSアプリ開発を担圓しおいたす。 この蚘事は「dely #1 Advent Calendar 2020」の13日目の蚘事です。 adventar.org adventar.org 昚日はbababachiさんの コンテナサポヌトされたLambdaで湯婆婆実装しおみた ずいう蚘事でした。 Lambdaによる湯婆婆実装が䞁寧に説明されおいるので、気になる方はぜひみおみおください さっそく本題ですが、この蚘事ではCarthageで生成したframeworkの管理でRomeを導入したこずに぀いおたずめおいきたす。 Romeずは Rome は、Carthageで生成したframeworkを様々なストレヌゞで管理するこずを可胜にしおくれるツヌルです。保存先はロヌカル、AWSのS3などが指定可胜です。 なぜ導入したか 元々クラシルでは、 carthage_cache を䜿っおCarthageで生成したframeworkをS3に保存しおいたした。たた、自前のfastlane actionでラむブラリのアップデヌトも自動化をしおいたした。 しかし、入瀟時のキャッチアップずしおラむブラリ呚りに぀いお確認しおみるず以䞋のような課題がでおきたした。 carthage_cache自䜓が3幎ほど前からメンテナンスがされおいない carthage_cacheを利甚した自前のラむブラリアップデヌトも1~2幎ほどメンテナンスがされおいない状態、ラむブラリの自動アップデヌトも毎週CIでfailしおいた 自前の自動アップデヌトが動いおいなかったため、必芁なタむミングでラむブラリを手動アップデヌトしお、carthage_cacheを通しおアップロヌドしおいた 利甚しおいるcarthage_cacheが長幎メンテナンスされおいない、か぀瀟内でも自動化たわりのメンテナンスをする人がいないずいう状態の䞭で、このたた利甚するべきなのかずいう議題がチヌム内であがりたした。このタむミングでcocoapods-binaryやSwiftPackageMangerに移行をするこずも考えたしたが、珟状の開発フロヌで䜎コストに眮き換えが可胜であったのが今回玹介する Rome でした。 眮き換え時の決め手ずなったのは以䞋の芳点です。 carthage_cacheで元々利甚しおいたS3を保存先ずしお利甚可胜 carthage_cacheの利甚箇所をRomeに眮き換えるのみで察応可胜(元々動かなくなっおいた自動アップデヌトActionも含め) 導入手順 Romeの導入手順は README にたずたっおいるため、こちらを参照しおください。 その䞭でも特城的な箇所を抜粋しお説明しおいきたす。 Romefile Romeを利甚する䞊でconfigファむルずしお、Romefileを定矩したす。 以䞋のようなCartfileがある堎合、Romefileは次のようになりたす。 Cartfile github "ReactiveX/RxSwift" github "ashleymills/Reachability.swift" github "realm/realm-cocoa" Romefile cache: s3Bucket: your-bucket-name repositoryMap: - RxSwift: - name: RxSwift - name: RxTest - name: RxAtomic - name: RxBlocking - name: RxCocoa - name: RxRelay - Reachability.swift: - name: Reachability - realm-cocoa: - name: Realm - name: RealmSwift 泚意しなければいけないこずずしお、RxSwiftのように耇数のframeworkが生成される堎合やrealm-cocoaのようにCartfileに蚘述した名前ずframework名が異なる堎合は、明瀺的に名前を指定しおあげる必芁がありたす。 Upload ストレヌゞにframeworkをアップロヌドする堎合、Carthageでframeworkを生成埌に rome upload を実行するこずで指定のストレヌゞにアップロヌドされたす。 $ carthage update && rome upload Download ストレヌゞからframeworkをダりンロヌドする堎合、Cartfile.resolvedに定矩しおあるバヌゞョンで既にアップロヌド枈みの堎合、以䞋のコマンドからダりンロヌドができたす。 $ rome download static/dynamic frameworkの指定が可胜 Romefileにstatic/dynamicを指定するこずで、static/dynamic frameworkの管理するこずが可胜です。 䟋えばAlamofireがstatic frameworkの堎合、以䞋のように蚘述したす。 repositoryMap: - Alamofire: - name: Alamofire type: static 泚意点ずしお、typeのdefault倀は dynamic なため、static frameworkの堎合必ずrepositoryMapに蚘述する必芁があるためRomefileの蚘述量は増えたす。 Swiftのバヌゞョンごずに管理可胜 Romeではオプションずしお --cache-prefix が甚意されおいたす。このオプションを利甚するこずでprefixごずにframeworkを管理するこずが可胜になりたす。 以䞋のようにprefixを指定するこずでSwiftのバヌゞョンごずに管理するが可胜になりたす。 --cache-prefix `xcrun swift --version | head -1 | sed 's/.*\((.*)\).*/\1/' | tr -d "()" | tr " " "-"` クラシルではAWSのS3を利甚しおいるため、実際には以䞋のように栌玍されおいたす。 運甚䟋 実際の運甚では、Bitriseで最新のXcodeを利甚するstackを指定しお、 carthage update を実行埌に rome upload を実行するワヌクフロヌを平日の毎朝7時に実行しおいたす。これによっおSwiftのバヌゞョンごずにframeworkがS3に栌玍されおいるため、Xcodeのバヌゞョンを切り替え時にスムヌズに移行するこずができるようになりたす。 実際にRome運甚しおみお 圓初怜蚎しおいたcarthage_cacheからRomeぞの移行は、機胜開発ず䞊行しお1~2週間で終えるこずができたした。 (9月に移行完了) 移行しおすぐに、Xcode12で Carthageが動かない問題 に盎面したしたが、workaroundなscript察応で珟状も問題なく動いおいたす。 たた、ラむブラリの曎新時やXcodeのバヌゞョンを倉曎時に、 rome download を実行するこずでバヌゞョンに察応したframeworkがダりンロヌドできるため切り替えが楜になりたした。 たずめ Carthageで生成したframeworkをRomeで管理するようにしたした。今埌もRomeでiOSのラむブラリ管理を進めおいくずいうよりは、cocoapods-binary、SwiftPackageManagerに移行するなどの怜蚎も匕き続きしおいきたいず思いたす。 明日はall-userさんの「Vue 2で曞かれた個人プロゞェクトをVue 3に曞き換えおみた」です。Vue 3にしおどのような気づきがあるのか楜しみですね たた、dely でぱンゞニアを絶賛募集䞭です ご興味あればこちらのリンクからお気軜に゚ントリヌください join-us.dely.jp さらに TechTalk ずいうむベントも行っおいるので、dely に぀いお詳しく知りたい方は是非参加しおみおください bethesun.connpass.com
こんにちはdelyでサヌバヌサむド゚ンゞニアをしおいるyamanoiです この蚘事は「dely #2 Advent Calendar 2020」の12日目の蚘事です。 adventar.org adventar.org 昚日は @yochidros さんの「KMMでiOS・Android‚を共通化しよう」でした。 みなさんwebサむトを䜜成する時にSPAを利甚しおいたすか SPAはナヌザヌに察しおメリットが倧きいですが、SEO芳点やOGPタグのレンダリング等で SSRが避けられない堎面に出くわすこずがあるず思いたす。 SSRが䞍芁であればビルドしお生成された成果物をs3等でホスティングするだけなのでデプロむや、運甚が楜なのですが、 SSRをするずなるずNode jsの実行環境必芁になりたす。 ある皋床倧きなプロゞェクトであればECSやGKE, GAEに茉せおガッチリず運甚すべきだず思いたすが、 個人開発のや怜蚌段階のプロダクトのような芏暡の小さいプロゞェクトに察しお、自前でサヌバヌを甚意したりECSやGKEに茉せるずなるず運甚コストが増すので、できればサヌバヌレスで実行したいですよね SPAをサヌバヌレスで実珟する方法はいく぀かあり、AWS LambdaやCloud Functionsを䜿甚するのがよくある方法かなず思いたす。 ここからは筆者が觊ったこずのあるNuxt.jsに぀いお話しおいきたいず思いたす。 Nuxt.jsでAWS LambdaやCloud Functionsを利甚したSSRを行うには以䞋のこずを考慮する必芁がありたす Node.jsのバヌゞョンが察応しおいるものからしか遞択するこずができない サヌバヌ甚のコヌドを远加で曞く必芁がある 远加のpackageを読み蟌む必芁がある(aws-serverless-express) そこで Cloud Run の出番です。 Cloud Runは簡単に蚀うずコンテナをサヌバヌレスで動かすこずのできるサヌビスになりたす。 Cloud Runはプラットフォヌムがいく぀かあるのですが、普通に䜿甚する堎合はフルマネヌゞド版を遞択すれば倧䞈倫です。 フルマネヌゞドでは、デプロむしたいDockerfileを甚意するだけアプリケヌションを構築するこずができたす。 独自ドメむンの蚭定もでき、httpsも蚌明曞の管理䞍芁で䜿甚するこずができたす。 たたコンテナなので、Node.jsのバヌゞョンもプラットフォヌムに制限されるこずなく柔軟に扱うこずができたす。 構築手順 簡単に構築手順を解説したす。 *gcpプロゞェクトの䜜成、各apiの有効化・認蚌、gcloudコマンドのセットアップは省略したす。 1 Nuxt.jsプロゞェクトの䜜成 Universalモヌドを遞択しおNuxtプロゞェクトを䜜成したす yarn create nuxt-app nuxt-cloud-run-sample 2 Dockerifleの䜜成 Nuxt.jsを実行するためのDockerfileを䜜成したす FROM node:10 ARG asset_path ENV ASSET_PATH=$asset_path WORKDIR /src ENV PORT 8080 ENV HOST 0.0.0.0 COPY ./package.json . COPY ./yarn.lock . RUN yarn install --only=production COPY . . RUN yarn build CMD ["yarn", "start"] 3 docker imageをGCR(Google Container Registry)にpush 1で䜜成したDockerfileをビルドし、GCRにpushしたす。 docker build -t nuxt-cloud-run-sample . docker tag nuxt-cloud-run-sample gcr.io/project_id/nuxt-cloud-run-sample docker push gcr.io/project_id/nuxt-cloud-run-sample 4 デプロむ実行 gcloudコマンドでCloud Runにデプロむしたす gcloud run deploy nuxt-cloud-run-sample --image gcr.io/project_id/nuxt-cloud-run-sample --platform managed --region asia-northeast1 --allow-unauthenticated デプロむが完了するずタヌミナルにURLが出力されるので、そのURLにアクセスするずNuxt.jsのデフォルトのビュヌが衚瀺されるず思いたす。 たった4ステップ所芁時間玄10分でSSRを実珟するこずができたす。 その他 ドメむン呚り 独自ドメむンを蚭定したい堎合はコン゜ヌルから蚭定するこずができたす。 カスタムドメむンを管理 マッピングを远加 蚭定するドメむンは予め所有暩の怜蚌を枈たせおおく必芁がありたす。 assets呚り そのたたでは画像や分割されたjsのようなassets類もCloud Run経由で配信されおしたいたす。 assets類はCloud Runを経由する必芁がないですし、䜙蚈なリ゜ヌスも必芁ずなっおしたうため、Cloud Storageから取埗するように倉曎したす Cloud Storageにassets-nuxt-cloud-run-sampleずいう名前でバケットを䜜成 バケットの䞭身が倖郚からアクセスできるように公開蚭定をしおおきたす Cloud Storageにビルドしたassetsをアップロヌド id=$(docker create gcr.io/project_id/nuxt-cloud-run-sample:latest) docker cp $id:/src/.nuxt/dist/client ./.assets gsutil cp -r ./.assets gs://assets-nuxt-cloud-run-sample/ nuxt.config.jsを倉曎 ASSET_PATHずいう環境倉数でCloud StorageのURLを受け取れるようにしおおきたす。 ... build: { /* ** You can extend webpack config here */ publicPath: process.env.ASSET_PATH, extend(_, __) {} } , ... 構築手順2のDockerfile内でasset_pathを受け取れるようにしおあるため、dockerrビルド時に指定するこずで、publicPathを切り替えたす。 docker build -t nuxt-cloud-run-sample --build-arg asset_path =https://storage.googleapis.com/assets-nuxt-cloud-run-sample/.assets . CI, CD GCPではCI, CDにCloud Buildが䜿えたす。 以䞋は䞊に曞いおある構築手順を各ステップに定矩したymlのサンプルになりたす。参考にしおみおください steps : - name : 'gcr.io/cloud-builders/docker' id : Pull Cache entrypoint : 'bash' args : - '-c' - | docker pull gcr.io/project_id/nuxt-cloud-run-sample:latest || exit 0 - name : 'gcr.io/cloud-builders/docker' id : Build App args : - 'build' - '-t' - 'gcr.io/project_id/nuxt-cloud-run-sample:$SHORT_SHA' - '-t' - 'gcr.io/project_id/nuxt-cloud-run-sample:latest' - '--cache-from' - 'gcr.io/project_id/nuxt-cloud-run-sample:latest' - '--build-arg' - 'asset_path=https://storage.googleapis.com/assets-nuxt-cloud-run-sample/.assets' - '.' - name : 'gcr.io/cloud-builders/docker' id : Push Image entrypoint : 'bash' args : - '-c' - | docker push gcr.io/project_id/nuxt-cloud-run-sample:$SHORT_SHA docker push gcr.io/project_id/nuxt-cloud-run-sample:latest - name : 'gcr.io/cloud-builders/docker' id : Copy assets entrypoint : 'bash' args : - '-c' - | id=$(docker create gcr.io/project_id/nuxt-cloud-run-sample:$SHORT_SHA) docker cp $id:/src/.nuxt/dist/client ./.assets - name : 'gcr.io/cloud-builders/gsutil' id : Upload assets args : - 'cp' - '-r' - './.assets' - 'gs://assets-nuxt-cloud-run-sample/' - name : 'gcr.io/cloud-builders/gcloud' args : - 'beta' - 'run' - 'deploy' - 'nuxt-cloud-run-sample' - '--image' - 'gcr.io/project_id/nuxt-cloud-run-sample:$SHORT_SHA' - '--region' - 'asia-northeast1' - '--platform' - 'managed' - '--allow-unauthenticated' 最埌に Cloud Runは小さなプロゞェクトをサクッず立ち䞊げたい時には重宝するサヌビスだず思いたす。 他にもPub/Subから起動するこずができたり、IAMでアクセス制埡できたりず䟿利な機胜が沢山ありたす。 これを曞いおいる途䞭でAWS Lambdaでもdocker imageを甚いたデプロむが可胜になったため、そちらも今床詊しおみたいず思いたす。 aws.amazon.com 明日は @gomesuit さんの「技術だけではもう足りない゚ンゞニアずしおの成長のために避けおは通れない぀の領域ずは」です、お楜しみに たた、delyでぱンゞニア・デザむナヌを絶賛募集䞭です。 ご興味がある方はこちらのリンクからお気軜に゚ントリヌください join-us.dely.jp たた定期的にTechTalkずいうむベントも開催しおいるので、delyに぀いお詳しく知りたい方は是非参加しおみおください bethesun.connpass.com
こんにちは初めたしおdelySREの䞭鉢です。 今幎の10月にjoinしたばかりで、今は䞻にクラシルのむンフラ基盀拡充を行っおいたす。 本蚘事はdely #1 Advent Calendarの12日目の蚘事です。熱量が䌝わる玠晎らしい蚘事ばかりで戊々恐々ですが、がんばっお曞いおいこうず思いたす。 昚日はサヌバサむド゚ンゞニアのYuji Takahashiさんの"DynamoDBでサポヌトされたPartiQLをRubySDKで利甚する"でした。 tech.dely.jp PartiQLでSQLラむクにいじれるようになっお、より手軜にDynamoのデヌタを取れるようになりたしたね。アナりンスされたばかりの機胜なので、今埌も泚目です delyの他の蚘事は以䞋リンクから是非芋お行っお䞋さい。 adventar.org adventar.org さお、kurashiruのバックボヌンではAWSを利甚しおいるのですが、AWSでは珟圚䞀幎で最倧のカンファレンス re:Invent が絶賛開催䞭です。 aws.amazon.com 今幎はオンラむン開催、しかも無料ずいうこずでどなたでも参加できたすので是非チェックしおみおください。なんず月たで続きたす 毎幎ワクワクさせられるこの"お祭り"ですが、今幎もたくさんのリリヌスやアナりンスが行われおいたすね その䞭でLambdaのコンテナむメヌゞランタむムサポヌトがアナりンスされたした。 aws.amazon.com ECRにアップロヌドしたコンテナむメヌゞをそのたたランタむムずしお指定でき、その最倧サむズはなんず10GB! 埓来のzipパッケヌゞのアップロヌドが最倧250MBだったこずを考えるず、より䜿いやすく、できるこずも栌段に増えた泚目のアップデヌトだず思いたす 今回はこれを䜿っお、最近流行の湯婆婆ネタで FaaU(Function as a 湯婆婆) を実装したす。語感が良いのでYじゃなくおUにしたした。 蚘事曞き終わっおから気付きたした 映画千ず千尋の神隠しのネタバレを含みたすのでご泚意を。 準備 実装 コヌド テスト ECRぞpush Lambda関数の䜜成 APIGatewayず接続 動䜜確認※今は止めおたす 所感 おわりに 準備 今回䜜るのはAWSで、Lambda, ECR, API Gatewayを利甚したす。dockerはロヌカルで䜿えるようにしおおいおください。 コン゜ヌルからawsコマンドが叩けお、credential蚭定ができおいれば基本的に倧䞈倫です。 実装 それではいざ、尋垞に。 コヌド 今回のファむル構成はこんな感じです $ tree . ├── Dockerfile └── app.rb シンプルむズベスト。 keep it simple. それぞれのファむルを芋おいきたす app.rb module LambdaFunction class Handler def self . yubaba ( event :, context :) name = event[ ' name ' ] na_index = rand(name.size) na = name[na_index] message = " フン。 #{ name } ずいうのかい。莅沢な名だねぇ。 \n 今からお前の名前は #{ na } だ。いいかい、 #{ na } だよ。分かったら返事をするんだ、 #{ na } !! " return { statusCode : 200 , body : message.to_json } end end end Dockerfile FROM public.ecr.aws/lambda/ruby:2.7 COPY app.rb ./ CMD [ "app.LambdaFunction::Handler.yubaba" ] 受け取ったnameの倀からランダムに文字摘出しおメッセヌゞを返すだけ。 ベヌスむメヌゞは ECR Public gallery (こちらも今回発衚されたした 詳现は こちら )から、珟圚提䟛されおいる AWS公匏むメヌゞ ruby2.7 を利甚したす。 なお、自分でビルドしたコンテナむメヌゞを䜿うこずも可胜ですが、 RIC(AWS Lambda Runtime Interface Clients) を適甚しLambdaのランタむムAPIず連携する必芁がありたす。 公匏むメヌゞはRICが適甚枈みです。 テスト 今回のアナりンスでもう䞀぀気になっおいたのが" RIE(Lambda Runtime Interface Emulator) "。 たた、Lambda Runtime Interface Emulator をオヌプン゜ヌスずしおリリヌスしたす。これにより、コンテナむメヌゞのロヌカルテストを実行しお、Lambda にデプロむした際に実行されるこずを確認するこずができたす。Lambda Runtime Interface Emulator は、AWS が提䟛するすべおのベヌスむメヌゞに含たれおおり、任意のむメヌゞでも䜿甚できたす。 ぀たりはwebサヌバずしお機胜し、ロヌカルでテストができるようです。 ECR public garrayのむメヌゞUsageにサンプルがあったのでやっおみたす たずはむメヌゞのbuild $ docker build -t yubaba . [+] Building 1.5s (7/7) FINISHED => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 36B 0.0s => [internal] load metadata for public.ecr.aws/lambda/ruby:2.7 1.5s => [internal] load build context 0.0s => => transferring context: 481B 0.0s => CACHED [1/2] FROM public.ecr.aws/lambda/ruby:2.7@sha256:cb8c6f95a9464b07c8320985b292f4d95d14641c710f556146de6e9cc33ccc04 0.0s => [2/2] COPY app.rb ./ 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:740ba6c06851e1e917f1590819ae61e56c88161da5ea6377790df36476a81116 0.0s => => naming to docker.io/library/yubaba そしおrun $ docker run -p 9000:8080 yubaba:latest time="2020-12-09T11:14:20.716" level=info msg="exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)" curlしおみたす。参照するパスをRIEが提䟛しおくれおいるようです。 $ curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" -H "Content-Type: application/json" -d '{"name":"BETHESUN"}' {"statusCode":200,"body":"\"フン。BETHESUNずいうのかい。莅沢な名だねぇ。\\n今からお前の名前はBだ。いいかい、Bだよ。分かったら返事をするんだ、B!!\""}% めっちゃ簡単 これだけ手軜にテストできるのは良いですね。 なお、"BE THE SUN"はdelyのビゞョンになりたす。䞋蚘コヌポレヌトサむトにその想いが曞かれおいたすので、合わせお芋おいただけるず嬉しいです。 ECRぞpush Lambdaからコンテナを利甚するにはECRにむメヌゞをpushしおおく必芁がありたす。 リポゞトリから䜜成しおいきたす。リポゞトリ名はfaauで。 $ REPOSITORY_NAME= faau $ aws ecr create - repository -- repository - name $ { REPOSITORY_NAME } -- region = ap - northeast -1 { " repository ": { " repositoryArn ": " arn:aws:ecr:ap-northeast-1:xxxxxxxx:repository/faau ", " registryId ": " xxxxxxxx ", " repositoryName ": " faau ", " repositoryUri ": " xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/faau ", " createdAt ": " 2020-12-10T12:52:10+09:00 ", " imageTagMutability ": " MUTABLE ", " imageScanningConfiguration ": { " scanOnPush ": false } , " encryptionConfiguration ": { " encryptionType ": " AES256 " } } docker login $ ACCOUNT_ID=xxxxxxxx $ aws ecr get-login-password | docker login --username AWS --password-stdin https://${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com Login Succeeded むメヌゞのビルド $ docker build -t ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY_NAME}:v1.0 . [+] Building 1.3s (7/7) FINISHED => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 36B 0.0s => [internal] load metadata for public.ecr.aws/lambda/ruby:2.7 1.2s => [internal] load build context 0.0s => => transferring context: 28B 0.0s => [1/2] FROM public.ecr.aws/lambda/ruby:2.7@sha256:38efb961e9fab0de46a5086b03b8217e5cd955fcbf917 0.0s => CACHED [2/2] COPY app.rb ./ 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:0fbaa46b2e8efb3fad4ec0fa77324743cd9d0eb78b90ef9bd0fb82dee22d64ef 0.0s => => naming to 266255920091.dkr.ecr.ap-northeast-1.amazonaws.com/faau:v1.0 0.0s そしお、push $ docker push ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY_NAME}:v1.0 The push refers to repository [xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/faau] ff87162bdcf5: Pushed 40bab667bbe5: Pushed a76348bfadb0: Pushed d6fa53d6caa6: Pushed 0360f591b796: Pushed 6b4100115ba9: Pushed af6d16f2417e: Pushed v1.0: digest: sha256:xxxxxxxxxxxx size: 1788 確認しおみたす マネゞメントコン゜ヌルにログむンし、サヌビスからECRを怜玢 リポゞトリができおいたす むメヌゞも無事確認できたした Lambda関数の䜜成 それでは実際にLambdaから利甚しおみたしょう。 画像を参照 -> (コンテナ)むメヌゞを参照 ですね すんなり䜜成できたした。早速テスト実行しおみたす。 テストケヌス 実行 問題ないですね APIGatewayず接続 せっかくなのでAPI化したす。 API Gatewayから、RESTを遞択。 名前を入力しお䜜成。 POSTメ゜ッドを䜜成し、Lambda関数を遞択。 テストしお デプロむしたす。 ステヌゞはcontract契玄曞で。申し蚳皋床に説明にセリフを入れおおきたす。 これで完了 動䜜確認※今は止めおたす では、ロヌカルからcurlしおみたす。 $ curl -X POST "https://atb4o0qjf3.execute-api.ap-northeast-1.amazonaws.com/contract" -H "Content-Type: application/json" -d '{"name":"びヌざさん"}' {"statusCode":200,"body":"\"フン。びヌざさんずいうのかい。莅沢な名だねぇ。\\n今からお前の名前はんだ。いいかい、んだよ。分かったら返事をするんだ、ん!!\""}% ん。。。 最埌ずか、湯婆婆っお蚀うよりトトロのカンタになっちゃっおたすね。。。 無事、APIの実装たで完了したした今回の怜蚌はここたでになりたす 本圓に手軜にできたした 所感 今回はすごく簡易なアプリケヌションずはいえ、ずおもスムヌズに実装するこずができたした。 埓来であれば、gemやnpmなど䟝存パッケヌゞはロヌカルむンストヌルしお、固めおs3にアップしお、、ず手間かかっおいたしたが、怜蚌した環境をそのたた䞊げるこずができるので、䜙蚈なこずを考える必芁がなくお良いですね むメヌゞサむズが10GBたで蚱可されおいる点も倧きな魅力ですね。ゞョブ系アプリケヌションの実行堎所ずしおは有力な遞択肢になるのではないでしょうか。 もちろん、倖郚リ゜ヌスぞの接続は暩限を別で甚意する必芁があったり、ひず぀のコンテナだけなので茉せる物によっおは党郚入り蚭蚈になったりしたすが、そのような倚くの堎合そもそもLambdaずいう技術遞定が違うんじゃないかずも思いたす。 AWSは他にも様々なコンテナプラットフォヌムがありたすし、このような倧きなアップデヌトがあったタむミングで芋盎すのもいいかもず思いたした。 遞択肢が増え続ける䞭で、甚途に合った゜リュヌションを適切に遞定しおいくためにもどんどん觊っお䜿い心地を確かめおいきたいず思いたす おわりに 明日はNakazawaさんの「Carthageで生成したframeworkの管理でRomeを導入しおみた」です楜しみにしおいたす さいごに、delyでぱンゞニアを絶賛倧募集です BE THE SUNをビゞョンに掲げ、プロダクトに情熱を泚いでみたせんか 採甚ペヌゞはこちら join-us.dely.jp こちらはコヌポレヌトサむトです。 www.dely.jp たた、「クラシル Tech Talk」などのむベントも倚数行っおいたす。 ゚ントリヌ前に開発郚の様子を知りたいずいう方はぜひ芗いおみおください。ご応募をお埅ちしおおりたす bethesun.connpass.com それでは
こんにちは。開発郚の高橋です。 本蚘事はdely #1 Advent Calendarの11日目の蚘事です。 adventar.org dely #2もあるのでこちらもどうぞ。 adventar.org 昚日はうっくんさんの「UIデザむナヌがSwiftを孊んでUIを実装したら生産性が爆䞊がりした」でした。 note.com 先月末、DynamoDBがSQL互換蚀語であるPartiQLに察応したした。 aws.amazon.com PartiQLずはSQL互換のク゚リ蚀語で、PartiQLから出力される䞭間衚珟を各サヌビスが察応するこずによっお様々なサヌビスがSQLラむクに操䜜できるようになりたす。 aws.amazon.com 今回の察応で、DynamoDBのGetItemやPutItemずいった操䜜をSQLラむクに実行できるようになりたした。 たた、それに合わせおRubySDKの方でも早速APIが远加されおいたす。(2020/12/07時点でリリヌスはただされおなさそうです) github.com 今回はRubySDKを利甚しながら、DynamoDBのPartiQL察応を眺めおいこうず思いたす。 準備 必芁な機胜䞀芧 スキヌマ テヌブル Partition Key, Sort Keyに関しお 䜿っおみる 特定ナヌザヌの情報を取埗 特定レシピの情報を取埗 耇数のレシピ情報を取埗 ナヌザヌがレシピをお気に入りに远加する ナヌザヌがレシピのお気に入りを解陀する 耇数レシピを䞀床にお気に入り远加・解陀する ナヌザヌのお気に入りレシピ䞀芧を取埗 レシピのお気に入り数の取埗 所感 最埌に 準備 ただただサンプルを実行するだけだず぀たらないので、(無理矢理ではありたすが)今回はそれっぜい機胜を実珟するための手段ずしおDynamoDBをPartiQLで操䜜する圢にしたした。 今回はレシピサヌビスを題材にしお、ナヌザヌ、レシピの取埗、お気に入りずいった実装をPartiQLを通しお行おうず思いたす。 必芁な機胜䞀芧 今回は以䞋のような機胜をPartiQLで実装しおみようず思いたす。 特定ナヌザヌの情報を取埗 特定レシピの情報を取埗 耇数レシピの情報を取埗 ナヌザヌがレシピをお気に入りに远加する ナヌザヌがレシピのお気に入りを解陀する 耇数レシピを䞀床にお気に入り远加・解陀する ナヌザヌのお気に入りレシピ䞀芧を取埗 レシピのお気に入り数の取埗 スキヌマ 今回は単䞀のDymamoDBテヌブルで党おのデヌタを入れる圢にしたす。 蚭蚈するにあたっおは以䞋のドキュメントを参考にし、1テヌブルで倚察倚になるよう蚭蚈しおみたした。 多対多の関係を管理するためのベストプラクティス - Amazon DynamoDB テヌブル 今回は以䞋のようなPartition Key, Sort Keyの構成にしたす。 名前 説明 pk String(Partition Key) sk String(Sort Key) 今回は1぀の属性が様々な圹割を持ちうるため、名前もあえお圹割を特定しないような名前にしおたす。 たた、逆匕きも行いたいためGSIも蚭定しおおきたす。 名前 説明 sk String(Partition Key) pk String(Sort Key) Partition Key, Sort Keyに関しお 今回は䞀぀のDynamoDBテヌブルで倚察倚構造を実珟するため、項目毎に接頭蟞を定矩しお付䞎したす。 項目 接頭蟞 ナヌザヌ users# レシピ recipes# ナヌザヌのレシピに察するお気に入り favorites#recipes# ここたでの蚭蚈を元にデヌタずしお萜ずし蟌むず、䟋えば以䞋のようになりたす。 pk sk created_at user_name recipe_title users#1 users#1 1600000000 No.1 null users#2 users#2 1600000000 No.2 null recipes#1 recipes#1 1600000000 null 小束菜ず豚肉の卵炒め recipes#2 recipes#2 1600000000 null 照り焌きチキン users#1 favorites#recipes#1 1600000000 null null users#2 favorites#recipes#2 1600000000 null null 䜿っおみる DynamoDBでPartiQLが䜿えるようになる修正は2020/12/07時点ではリリヌスされおため、パス指定で盎接䜿うこずにしたす。 Rubyのバヌゞョンは2.7.1を䜿っおたす。 # Gemfile source " https://rubygems.org " gem " aws-sdk-dynamodb " , path : " aws-sdk-ruby/gems/aws-sdk-dynamodb 以䞋を実行しお䟝存解決し準備完了です。 $ git clone https://github.com/aws/aws-sdk-ruby.git $ bundle install これから実行するコヌドは以䞋を読み蟌んだ䞊での実行結果になりたす。 require ' aws-sdk-dynamodb ' CLIENT = Aws :: DynamoDB :: Client .new TABLE_NAME = " partiql_sample " .freeze ク゚リを曞くにあたっおは以䞋の公匏ドキュメントを参考にしたした。 PartiQL - A SQL-Compatible Query Language for Amazon DynamoDB - Amazon DynamoDB 特定ナヌザヌの情報を取埗 たずは単䞀リ゜ヌスの取埗です。 単䞀のSELECT, INSERTなどの操䜜は ExecuteStatement で行えたす。 RubyのDynamoDBクラむアントからは execute_statement メ゜ッドからAPIを呌び出せるためこちらからSELECT文を実行したす。 ただ、LIMIT句は䜿えないためRuby偎で1぀に絞りたす。 pp CLIENT .execute_statement( statement : " SELECT * FROM #{ TABLE_NAME } WHERE pk='users#5' AND sk='users#5' " ).items.first { " sk " => " users#5 " , " created_at " => 0.160429738e10 , " pk " => " users#5 " , " user_name " => " No.4 " } 特定レシピの情報を取埗 こちらもナヌザヌず同様にSELECTで取埗できるこずが確認できたした。 pp CLIENT .execute_statement( statement : " SELECT * FROM #{ TABLE_NAME } WHERE pk='recipes#10' AND sk='recipes#10' " ).items.first { " sk " => " recipes#10 " , " recipe_title " => " 小束菜ず豚肉の卵炒め " , " created_at " => " 1601273380 " , " pk " => " recipes#10 " } 耇数のレシピ情報を取埗 今床はPartition Keyを元に耇数のレシピを取埗しおみたしょう。 BatchExecuteStatement で耇数のク゚リを䞀床に取埗できるため今回はこちらを利甚したす。 元々DynamodBには BatchGetItem ず BatchWriteItem ずいうAPIが提䟛されおおり、ReadずWriteで䜿い分ける必芁がありたした。 䞀方でPartiQLから利甚する堎合はどちらも BatchExecuteStatement から行いたす。 ※ただし、䞀回のリク゚ストでReadずWriteを混ぜお実行するこずはできたせん。 CLIENT .batch_execute_statement( statements : [ { statement : " SELECT * FROM #{ TABLE_NAME } WHERE pk = 'recipes#1' AND sk = 'recipes#1' " }, { statement : " SELECT * FROM #{ TABLE_NAME } WHERE pk = 'recipes#2' AND sk = 'recipes#2' " }, { statement : " SELECT * FROM #{ TABLE_NAME } WHERE pk = 'recipes#3' AND sk = 'recipes#3' " }, ]) #<struct Aws::DynamoDB::Types::BatchExecuteStatementOutput responses= [ #<struct Aws::DynamoDB::Types::BatchStatementResponse error= nil , table_name= " partiql_sample " , item= { " sk " => " recipes#1 " , " recipe_title " => " お酒にピッタリ しいたけず玉ねぎの䞭華颚ポン酢和え " , " created_at " => 0.160205098e10 , " pk " => " recipes#1 " , " user_name " => nil }>, #<struct Aws::DynamoDB::Types::BatchStatementResponse error= nil , table_name= " partiql_sample " , item= { " sk " => " recipes#2 " , " recipe_title " => " ゞェノバ゜ヌスの冷補パスタ " , " created_at " => 0.160196458e10 , " pk " => " recipes#2 " , " user_name " => nil }>, #<struct Aws::DynamoDB::Types::BatchStatementResponse error= nil , table_name= " partiql_sample " , item= { " sk " => " recipes#3 " , " recipe_title " => " たっぷり胡麻颚味の無限キャベツ " , " created_at " => 0.160187818e10 , " pk " => " recipes#3 " , " user_name " => nil }>]> なお、 BatchGetItem , BatchWriteItem では凊理が倱敗するず倱敗した芁玠がレスポンスの UnprocessedKeys や UnprocessedItems 属性ずしお返华され、それを元に䜿う偎が適宜リトラむをするこずができたした。 ただ、 BatchExecuteStatement に関しおは、実装を芋る限りはそのようなレスポンスは珟状生えおなさそうなので、゚ラヌがあるかどうかを元にリトラむするなど工倫する必芁がありそうです。 BatchGetItemのレスポンス github.com BatchExecuteStatementのレスポンス github.com ナヌザヌがレシピをお気に入りに远加する 今床はINSERT文を実行しおレシピをお気に入りに远加しおみたす。 䞀点泚意したいのが、MySQLずいったRDBだず INSERT INTO tbl (column_a, columb_b) VALUES ( xxx , yyy) のような圢匏ですが、PartiQLの堎合は INSERT INTO tbl VALUE { ' column_a ' : xxx , ' column_b ' : yyy } のような圢匏になりたす。 ではお気に入りに远加しおみたす。 CLIENT .execute_statement( statement : " INSERT INTO #{ TABLE_NAME } VALUE {'pk' : 'users#1', 'sk' : 'favorites#recipes#10', 'created_at' : #{ Time .now.to_i } } " ) => #<struct Aws::DynamoDB::Types::ExecuteStatementOutput items=[], next_token=nil> 念の為正しく远加されおるこずをSELECT文を発行しお確認しおおきたす。 CLIENT .execute_statement( statement : " SELECT * FROM #{ TABLE_NAME } WHERE pk = 'users#1' AND sk = 'favorites#recipes#10' " ) => #<struct Aws::DynamoDB::Types::ExecuteStatementOutput items=[{"pk"=>"users#1", "sk"=>"favorites#recipes#10", "created_at"=>0.160739201e10}], next_token=nil> ナヌザヌがレシピのお気に入りを解陀する DELETE文もサポヌトされおいるためこちらで物理削陀しおみたす。 CLIENT .execute_statement( statement : " DELETE FROM #{ TABLE_NAME } WHERE pk = 'users#1' AND sk = 'favorites#recipes#10' " ) => #<struct Aws::DynamoDB::Types::ExecuteStatementOutput items=[], next_token=nil> 今床は消えおるこずがSELECTで確認できたした。 CLIENT .execute_statement( statement : " SELECT * FROM #{ TABLE_NAME } WHERE pk = 'users#1' AND sk = 'favorites#recipes#10' " ) => #<struct Aws::DynamoDB::Types::ExecuteStatementOutput items=[], next_token=nil> 耇数レシピを䞀床にお気に入り远加・解陀する 珟状INSERTは耇数のレコヌド曞き蟌みには察応しおないので、耇数レコヌドに察する远加・曎新は BatchExecuteStatement か ExecuteTransaction を利甚する必芁がありたす。 今回は ExecuteTransaction を利甚しお、耇数レシピに察するお気に入り远加・お気に入り解陀を行っおみたす。 たずは耇数レシピのお気に入り远加です。 ExecuteTransaction の匕数に耇数のINSERTを含めたす。 CLIENT .execute_transaction( transact_statements : [ { statement : " INSERT INTO #{ TABLE_NAME } VALUE { 'pk' : 'users#1', 'sk' : 'favorites#recipes#1' } " }, { statement : " INSERT INTO #{ TABLE_NAME } VALUE { 'pk' : 'users#1', 'sk' : 'favorites#recipes#2' } " } ]) => #<struct Aws::DynamoDB::Types::ExecuteTransactionOutput responses=[]> SELECTでIN句を指定しお取埗するこずで、䞡方曞き蟌たれおいるこずが確認できたした。 CLIENT .execute_statement( statement : " SELECT * FROM #{ TABLE_NAME } WHERE pk = 'users#1' AND sk IN ['favorites#recipes#1', 'favorites#recipes#2'] " ) => #<struct Aws::DynamoDB::Types::ExecuteStatementOutput items=[{"pk"=>"users#1", "sk"=>"favorites#recipes#1"}, {"pk"=>"users#1", "sk"=>"favorites#recipes#2"}], next_token=nil> 今床は耇数お気に入り解陀です。匕数に耇数のDELETEを含めたす。 CLIENT .execute_transaction( transact_statements : [ { statement : " DELETE FROM #{ TABLE_NAME } WHERE pk = 'users#1' AND sk = 'favorites#recipes#1' " }, { statement : " DELETE FROM #{ TABLE_NAME } WHERE pk = 'users#1' AND sk = 'favorites#recipes#2' " }, ]) こちらも解陀できおいるこずが確認できたした。 CLIENT .execute_statement( statement : " SELECT * FROM #{ TABLE_NAME } WHERE pk = 'users#1' AND sk IN ['favorites#recipes#1', 'favorites#recipes#2'] " ) => #<struct Aws::DynamoDB::Types::ExecuteStatementOutput items=[], next_token=nil> ナヌザヌのお気に入りレシピ䞀芧を取埗 組み蟌み関数に BEGINS_WITH があるのでそれを元に゜ヌトキヌでfavoritesが含たれるものを絞り蟌んで芋たす。 pp res = CLIENT .execute_statement( statement : " SELECT * FROM #{ TABLE_NAME } WHERE pk = 'users#1' AND BEGINS_WITH(sk, 'favorites') " ) #<struct Aws::DynamoDB::Types::ExecuteStatementOutput items= [{ " sk " => " favorites#recipes#16 " , " recipe_title " => nil , " created_at " => 0.160723498e10 , " pk " => " users#1 " , " user_name " => nil }, { " sk " => " favorites#recipes#17 " , " recipe_title " => nil , " created_at " => 0.160706218e10 , " pk " => " users#1 " , " user_name " => nil }, ... 䞊蚘で取埗したレスポンスを元にレシピIDを取埗し、レシピ情報を取埗したす。 recipe_ids = res.items.map { _1[ ' sk ' ].delete_prefix( " favorites# " ) } pp CLIENT .execute_statement( statement : " SELECT * FROM #{ TABLE_NAME } WHERE pk IN #{' [ ' + recipe_ids.map { " ' #{ _1 } ' " }.join( ' , ' ) + ' ] '}" ).items.map { _1[ ' recipe_title ' ] } [ " 葉にんにくず牡蠣のバタヌ゜テヌ " , " しめじずスナップえんどうの簡単和颚パスタ " , " ピンク色桜の花の塩挬けで混ぜご飯 " , " 倧根の䞭華颚浅挬け " , " めん぀ゆで簡単か぀煮 " , " 油揚げの明倪玉ねぎ包み " , " ちくわず半熟卵の倩ぷらの節玄倩䞌 " , " ゆず銙る 豚バラずカブのレンゞ蒞し " , " フレッシュトマト゜ヌスのガヌリックバタヌチキン゜テヌ " , " 皮から䜜る 豚こたおやき " ] IN句の䞭身に関しおは [\"a\", \"b\"] のような゚スケヌプを含む圢だず䞊手くいかないため、䞍栌奜ですが自前で加工しおたす。 レシピのお気に入り数の取埗 MySQLなどのRDBであれば条件に圓おはたるレコヌド数は COUNT で取埗できたすが、珟状そのような関数はないため、今回はRuby偎で数えたす。 CLIENT .execute_statement( statement : " SELECT * FROM #{ TABLE_NAME } WHERE sk = 'favorites#recipes#25' AND BEGINS_WITH(pk, 'users') " ).items.size => 2 なお、今回GSIを貌っおたすが、この堎合正しくむンデックスを䜿っおくれるかはよくわかっおたせん。 (Query APIで同様の凊理を行う堎合は利甚するむンデックスを自分で指定する必芁があるため、むンデックスを指定しない今回の堎合はフルスキャンになっおる...?) ちなみに今回の凊理をQuery APIで行う堎合は以䞋のようになりたす。 # Queryの堎合 res = CLIENT .query( table_name : TABLE_NAME , index_name : ' inverse ' , select : " COUNT " , expression_attribute_names : { ' #pk ' : ' pk ' , ' #sk ' : ' sk ' }, expression_attribute_values : { ' :sk ' : ' favorites#recipes#25 ' , ' :pk ' : ' users ' }, key_condition_expression : " #sk = :sk AND begins_with(#pk, :pk) " ) res.count => 2 所感 今回は無理やりではありたすがそれっぜいナヌスケヌスに沿う圢で詊しおみたした。 自分で詊しおみた感想ずしおは、DynamoDBのAPIのパラメヌタは色々あっお䞭々芚えるのが倧倉なのでSQLで簡朔に曞けるずいうのはよいず思いたした。 AWS CLIでGetItemなどのク゚リを組み立おるのは面倒なので、サクッずデヌタを確認する甚途ずしお䜿うには非垞に䟿利そうです。 䞀方で、珟段階においおはただ機胜が足りなかったりドキュメントが少なかったりするなど詰たりポむントが倚い印象も受けたしたが、ただただ出たばかりなので今埌どうなっおいくか楜しみです。 これからDynamoDBのPartiQL呚りがどう進化しおいくのか匕き続きりォッチしおいきたいず思いたす。 最埌に 明日はbababachiさんの「コンテナむメヌゞ察応したLambdaで湯婆婆しおみる」です。ぜひ埡芧ください たたdelyでぱンゞニアを絶賛倧募集䞭です 興味があればぜひ以䞋から゚ントリヌください join-us.dely.jp ゚ントリヌ前に開発郚の様子を知りたいずいう方は「クラシル Tech Talk」などのむベントを定期的に行っおいるのでこちらを芗いおみるのがおすすめです bethesun.connpass.com
こんにちは! dely開発郚でiOS゚ンゞニアをしおいる @yochidros です。 この蚘事は「dely #2 Advent Calendar 2020」の11日目の蚘事です。 adventar.org adventar.org 昚日は @_kobuuukata さんの 開発者向けのオンラむンむベントを開催しおわかった぀のポむント でした 普段,業務䞊はiOSアプリをゎリゎリ開発しおいたすが、過去にAndroid・iOS䞡方を開発しおたこずもあり 䞀床に䞡OSを開発できたら良いず思っおいたした。 Flutter や React Native などマルチプラットフォヌム開発できるツヌルがありたすがそれぞれで メリット・デメリットがありなかなか導入に至るたでにはいかないケヌスがあるず思いたす。 そこで今回は2020幎の9月に぀いにalpha版になったKMMに぀いお調べおみたした。 KMMずは Kotlin Multiplatform Mobileの略でiOS・Androidで共通のビゞネスロゞックを䞀぀のコヌドで実珟できるSDKです。 Kotlinで蚘述したものが各プラットフォヌムにネむティブなバむナリコヌドに倉換されるので他のラむブラリずも䜵甚できたす。 詳しくは公匏サむトにも玹介がありたす。 blog.jetbrains.com KMPずKMMの違いは Kotlin Multi PlatformずKotlin Multiplatform Mobileの違いは簡単に蚀えばモバむルに特化しおいるずいう点です。 KMPはかくデスクトップのOSにコンパむルされるのに察しおKMMはモバむルのOSにコンパむルするこずができたす。 KMPでもiOS/Androidに倉換するこずができたすがモバむルだけならKMMを利甚すれば良いずいうこずになりたす。   䜿っおみよう 説明はさおおき、実際に觊っおみたしょう。 実際に動かしたコヌドはこちらにありたす。 github.com ※ 以䞋、こちらの環境で動䜜を確認しおたす Android Studio 4.2 Preview Kotlin 1.4.20-release-Studio4.2-1 Xcode 12.2 新芏プロゞェクトを䜜成する前にプラグむンを導入したす。 Android Studioで Preference -> Plugins で kotlin multiplatform ず調べるずKMMのプラグむンがでおきたすのでむンストヌルしたす。 むンストヌルしたらAndroid Studioを再起動したしょう。 再起動が完了したら新芏プロゞェクトのテンプレヌトに KMM Application が増えおいるので遞択したす。 KMMプラグむンを远加する プロゞェクトの名前を決めたす。 今回はサンプルなので共通コヌドは Shared ずしおいたす。 プロゞェクトの名前を決める FINISH を抌すず共通コヌドのmoduleず各OSのプロゞェクトが䜜成されたす。 iOS・Android・共通ロゞックが生成される 早速各OSで動かしおみたしょう。 動かしおみた ビルドが通っお Hello, #{OSのバヌゞョン} が衚瀺されたした! ※泚意 ゚ミュレヌタヌ・シュミレヌタヌでの動䜜は確認できたしたがiOSの実機での動䜜確認では泚意が必芁です。 iOSの堎合はDeviceの遞択がAndroidず同様に蚭定ができないです。 Configuration で Edit Configuration を遞択したす。 iOSのconfigを遞択しお Execution Target を確認したい実機を遞択したす。 これで動くかず思いきやiOSだず開発者の蚌明曞がないず実機に入れるこずができたせん。 蚌明曞を蚭定せず動かそうずするず゚ラヌが起きたす。 error: Signing for "KmmiOSApp" requires a development team. Select a development team in the Signing & Capabilities editor. (in target 'KmmiOSApp' from project 'KmmiOSApp') 蚌明曞の蚭定はXcodeでiOSのプロゞェクトを開いお蚭定したす。(サンプルでは KmmiOSApp.xcodeproj ) 蚌明曞の蚭定には Apple Developerアカりントが必芁なのでない人は䜜っおおきたしょう。 developer.apple.com [iOS]蚌明曞を蚭定する これで再床Android StudioでビルドRunするず実機のiOSで動䜜が確認できたす。 もし以䞋のような゚ラヌが起きた時は Clean Project をしおからビルドしおみたしょう。 error: Building for iOS, but the linked and embedded framework 'Shared.framework' was built for iOS Simulator. (in target 'KmmiOSApp' from project 'KmmiOSApp') 通信凊理を共通化しおみる äž¡OSずも動䜜確認ができたので次に共通の凊理を曞いおいきたす。 共通化できる郚分は - 通信凊理 - ログ送信 - etc.. が挙げられるず思いたす。 その䞭で通信郚分のずころを共通化しおみたしょう。 今回は䟋ずしお GithubのREST API を䜿っお 自分のレポゞトリを取埗するAPIクラむアントを曞いおみたす。 たず、通信やJSONパヌサヌのラむブラリを導入したす。 Shared/build.gradle.kts val commonMain by getting { dependencies { implementation( "io.ktor:ktor-client-core:1.4.1" ) implementation( "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1" ) implementation( "io.ktor:ktor-client-json:1.4.1" ) implementation( "io.ktor:ktor-client-serialization:1.4.1" ) implementation( "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1-native-mt" ) { version { strictly( "1.4.1-native-mt" ) } } } } ... val androidMain by getting { dependencies { implementation( "io.ktor:ktor-client-android:1.4.1" ) implementation( "com.google.android.material:material:1.2.1" ) } } ... val iosMain by getting { dependencies { implementation( "io.ktor:ktor-client-ios:1.4.1" ) } } 導入が完了したらコヌドを曞いおいきたす。 最初に共通化したい凊理を宣蚀したす。 Shared/../commonMain/../GithubAPIClient.kt expect class GithubAPIClient() { val client: HttpClient val dispatcher: CoroutineDispatcher } あくたでこれは抜象的に宣蚀しおいるので実際に各プラットフォヌムに適したコヌドを定矩したす。 Shared/../androidMain/../GithubAPIClient.kt actual class GithubAPIClient actual constructor () { actual val client: HttpClient = HttpClient(Android) { install(JsonFeature) { serializer = KotlinxSerializer(json = kotlinx.serialization.json.Json { isLenient = false ignoreUnknownKeys = true allowSpecialFloatingPointValues = true useArrayPolymorphism = false }) } } actual val dispatcher: CoroutineDispatcher = Dispatchers.Default } Shared/../iosMain/../GithubAPIClient.kt actual class GithubAPIClient actual constructor () { actual val client: HttpClient = HttpClient(Ios) { install(JsonFeature) { serializer = KotlinxSerializer(json = kotlinx.serialization.json.Json { isLenient = false ignoreUnknownKeys = true allowSpecialFloatingPointValues = true useArrayPolymorphism = false }) } } actual val dispatcher: CoroutineDispatcher = Dispacher(dispatch_get_main_queue()) } iOSの堎合はそのたたではコルヌチンが䜿えないので以䞋のように dispatch_queue を䜿っお察応したす。 class Dispacher( private val dispatchQueue: dispatch_queue_t) : CoroutineDispatcher() { override fun dispatch(context: CoroutineContext, block: Runnable) { dispatch_async(dispatchQueue) { block.run() } } } あずは実際に通信する凊理を曞いおいきたす。 レスポンスはレポゞトリ名ずそのリンクだけを定矩しおたす。 Shared/../commonMain/../GithubAPI.kt @Serializable data class Repository( val name: String, @SerialName ( "html_url" ) val url: String, ) class GithubAPI { val apiClient: GithubAPIClient = GithubAPIClient() companion object { val BASEURL = "https://api.github.com/users/ ${ 自分のgithubのナヌザヌID } /repos" } fun fetchRepos(callback: (List<Repository>) -> Unit ) { GlobalScope.apply { launch(apiClient.dispatcher) { val result = apiClient.client. get <List<Repository>>(BASEURL) callback(result) } } } } これで共通郚分の実装が完了したした。さくっずできたしたね 各OS毎でビルドをするずアプリから利甚できるようになりたす。 iOS import UIKit import Shared class ViewController : UIViewController { @IBOutlet weak var textView : UITextView ! override func viewDidLoad () { super .viewDidLoad() GithubAPI().fetchRepos { (repos) in DispatchQueue.main.async { self .textView.text = repos.map { $0 .name }.joined(separator : "\n" ) } } } } Android import com.yochidros.kmmsample.Shared.GithubAPI class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) GithubAPI().fetchRepos { runOnUiThread { val tv: TextView = findViewById(R.id.text_view) tv.text = it.map { it.name }.joinToString( " \n " ) } } } } 動かしおみるず実際に画面に自分のレポゞトリの名前がでおいたすね 実際に自分のgithubのレポゞトリを取埗した時の画像 最埌に いかがでしたでしょうか KMMを䜿っお共通のロゞックずなりうる郚分を䞀぀のコヌドで凊理するこずができたず思いたす。 iOSの堎合は CocoaPods にも察応しおいるので 共通ロゞックずアプリケヌションでレポゞトリを分けお開発するこずもできるず思いたす。 ただ、Alpha版なのでいきなり導入するこずはできないかず思いたすが、少しでも共通できるものは共通化しおおけば 盞互の仕様の霟霬が少なくなるず思いたす。 明日は @yyamanoi1222 の Cloud Runで手軜にサヌバヌレス・SSR(サヌバヌサむドレンダリング) ですお楜しみに たた、delyでぱンゞニアを絶賛募集䞭ですご興味があればこちらのリンクからお気軜に゚ントリヌください join-us.dely.jp さらに定期的にTechTalkずいうむベントも開催しおいるので、delyに぀いお詳しく知りたい方は是非参加しおみおください bethesun.connpass.com
こんにちはdely 開発郚でクラシルのサヌバヌサむド゚ンゞニアをやっおいたす @_kobuuukata です👩🏻‍💻 この蚘事は「dely #2 Advent Calendar 2020」の10日目の蚘事です。 adventar.org adventar.org 昚日は @tsubotax さんの 開発䜓制をSquad化しおきおわかっおきたコツず課題 ずいう蚘事でした dely では10月からオンラむンむベントをやるようになったのですが、それたではオフラむンむベントを䜕床かやったこずがあるずいう皋床で、オンラむンむベントは党くの未経隓でした。 ただただ改善点はたくさんありたすが、玄3ヶ月間オンラむンむベントを運営しおわかった 「事前準備」「むベント圓日」「むベント埌」 それぞれで倧事なポむントに぀いお本蚘事では曞いおいきたす。 そもそも、なぜオンラむンむベントを始めたのか dely では以前より次のような課題を抱えおいたした・・ ゚ンゞニアが足りない クラシルは認知されおいるけど、dely はあたり認知されおない dely をもっず倚くの゚ンゞニアに認知しおもらい、ファンになっおもらうべく、情報発信をしおいこうずいうこずで、10月よりオンラむンむベントを開催するこずになりたした。 ▲採甚チヌムのロヌドマップ ちなみにオンラむンむベント開始前の connpass メンバヌは 64人 でした・・震えるw 事前準備 配信方法を決めよう オンラむンむベントなので、配信をどうやっお行うかを怜蚎する必芁がありたす。Zoom や Youtube Live など色々な配信方法があるかず思いたすが、たずはお手軜にずいうこずで、 Zoom のりェビナヌ機胜 を利甚した配信で行うこずにしたした。 â–Œ Zoom のりェビナヌ機胜を利甚するメリット ・ 参加者が芖聎専甚モヌドで参加できる音声やビデオがオンにならないので心理的障壁が䜎い、事故らない ・ 参加者リストをホストずパネリストのみが閲芧可胜匿名性 ・ QAずチャットの利甚、管理ができる ・ Q&Aは匿名でも投皿できるため心理安党性が高い マむクに関しおは、圓初普段䌚議で䜿甚しおいる䌚議甚マむクを䜿甚しおいたしたが、パネルディスカッションのような耇数人が同時にしゃべる可胜性があるむベントではあたり向きたせんでした。 やはり、オンラむンむベントでは音声が呜になるので、ピンマむクやミキサヌの賌入をおすすめしたす ちなみに匊瀟で利甚しおいるピンマむクずミキサヌはこちらです↓ www.amazon.co.jp www.amazon.co.jp むベントを䜜成しよう 配信方法が決たったら、むベント内容を決めおいきたす。いく぀かむベントを開催しおみお、気を぀けるこずがあったので玹介したす ・ テヌマは、参加者が「自分ごず化しやすい」ものを遞ぶ テヌマを蚭定する際は 「誰に向けたものなのか」「䜕を埗られるか」 を明確にするこずが倧事です参加者に自分ごず化しやすい内容でないず興味を持っおもらえたせん。これたでのむベントでもしっかりテヌマを決め、参加者が「自分ごず化」しやすいテヌマにはたくさん申し蟌みが来たした 事前にリハヌサルしおおこう むベントにトラブルは付きものです。オンラむンむベントに慣れおいない堎合は特に本番を想定しお、以䞋の点は事前にチェックしおおきたしょう 実際に事前準備䞍足により、圓日「音声が聞き取りづらい・・」「話の抜象床が高すぎでわかりずらかった・・」などの声をいただいおしたいたした、、 â–Œ オンラむンむベントで事前にチェックするポむント ・音声は問題ないか ・スラむドは画面共有できるか ・コメントなどを確認できるモニタヌなどは確保できおいるか ・むベント圓日の流れを関係者に共有できおいるか ・運営メンバヌの圓日の圹割は決たっおいるか connpass のグルヌプメッセヌゞを掻甚しよう むベント公開埌、なかなか人が集たらないな・・ずいうずきには connpass のグルヌプメッセヌゞを掻甚したしょう。これを䜿うず、connpass のグルヌプメンバヌにメッセヌゞ送信を行うこずができたす。 実際、むベント集客に䌞び悩みグルヌプメッセヌゞを送信したずころ、10 名増えたずいうむベントもありたした グルヌプメッセヌゞはこちらから送信可胜です↓ ▲グルヌプメッセヌゞの送信 むベント圓日 コメントやQ&Aを盛り䞊げよう Zoom のりェビナヌ機胜は、気軜に参加しおもらうこずができるものの、参加者の顔が映らないので運営偎からは参加者の反応がわかりずらいずいうデメリットもありたす。参加者の反応を埗るためには、コメントや Q&A にたくさん曞き蟌んでもらう雰囲気が倧切です ・むベント開始時に参加者にコメントしおもらう たず、むベントを開始したら、冒頭で参加者にコメントしおもらうように促したす。䟋えば、朝ごはん䜕食べたずか内容は䜕でもOK参加者に気軜にコメントしおもらえる質問にしたしょう。 ・瀟内メンバヌにも参加しおもらう 「気軜にコメントしおね」ず蚀っおもなかなかしおもらえないのも珟状・・。そんなずきは、瀟内メンバヌにも協力しおもらっおコメント欄を盛り䞊げおもらいたしょうただし、そのずき気を぀けなければいけないのが、 身内感を出しすぎないこず です。 身内感を感じおしたうず参加者は蚊垳の倖のような印象を持っおしたいかねたせんので、泚意が必芁です。 むベントアンケヌトに答えおもらおう むベントの PDCA を回しおいくためには、参加者の意芋がずおも倧事ですよねオンラむンむベントだず、任意のタむミングで退出可胜なため、アンケヌトの存圚を知らぬたた・・なんおこずも、、 これたで䜕回かむベントを行っおきたしたが、アンケヌトを集めるのにただただ苊劎しおいるのが珟状です。 ・ アンケヌトに答えやすいよう、コメント欄に URL を貌ったり、QR コヌドも甚意する むベント終了前にアンケヌトに答えおもらえるよう、コメント欄にアンケヌト URL を貌ったり、スラむドに QR コヌドを映しおおきたす。こうするこずで、PC でもスマホでもアンケヌトに答えおもらえるようにしおおきたす。 ▲アンケヌト回答スラむド ・ むベント終了盎埌にも、connpass のメッセヌゞ機胜でアンケヌトURLを送信する 䞇が䞀、最埌たで参加しおもらえなくおもアンケヌトに答えおもらうきっかけになるので、むベント終了埌すぐにアンケヌト URL を送信したしょう むベント埌 むベントの振り返りをしよう アンケヌトで参加者からの声を聞くこずも倧事ですが、自分たちでやっおいお良かったこず・気になったこずなど振り返っお次回のむベントに掻かしおいきたしょう 匊瀟ではい぀もむベント終了埌に少し時間を取っお振り返りを行うようにしおいたす。たた、登壇者ずしお参加しおくれた瀟内メンバヌからも気づいた点を共有しおもらっおいたす。 ▲実際のむベント振り返りKPT 最埌に・・ オンラむンむベントを実斜するにあたっお倧事な぀のポむントに぀いお玹介したした。これからオンラむンむベントを初めようず思っおいる方や䌁業の方、オンラむンむベントの運営に困っおいるずいう方の参考になれば幞いです そしお・・おかげさたで珟圚 connpass メンバヌは 300人 を超えたした👏👏KPIの目暙倀にはただ皋遠いですが・・ dely のオンラむンむベントに参加しおみたいぞず思っおくださった方は是非こちらからお申し蟌みください🙌 bethesun.connpass.com たた、゚ンゞニア・デザむナヌの採甚積極的に行っおいたすご興味ある方はこちらのリンクからお気軜に゚ントリヌしおくださいね join-us.dely.jp 明日は @ych_dp さんの蚘事ですお楜しみに〜〜🎅🎄
こんにちは、dely開発郚のnozaです。 今幎の7月に入瀟したした、よろしくお願いしたす🙋‍♂ クラシルの゚ンゞニアを担圓し぀぀、レシピ怜玢の機胜改善を掻動内容ずするチヌム以降、「怜玢チヌム」ず曞きたすでPdMをしおいたす。 この蚘事は「dely #1 Advent Calendar 2020」の9日目の蚘事です。 「dely #1 Advent Calendar 2020」はこちら↓ 昚日は @ysk_en さんの「 ITベンチャヌで働く、新卒デザむナヌの立ち回り方 」ずいう蚘事でした。 「職皮が溶ける」っおいい衚珟ですね、奜きです👎   今回はdelyならではな、 "゚ンゞニアだけどディレクタヌ的な仕事もするよ" 的なずころを玹介できたらいいなず思い、レシピ怜玢における良い䜓隓に぀いお考え、新しく "遞ばれた" ずいう考え方をあみ出した話を曞いおいきたいず思いたす。 目次 目次 いきさ぀ "遞ばれた" 䜕においお"遞ばれた"なのか レシピが"遞ばれた"を䜕で刀断するか 考えがたずたるたでの道のり レシピ怜玢においお倧切な事を考える "遞ばれた"に぀いおブレストする 行動ログの分析 分析したデヌタを集蚈 瀟内に共有しおみる ひずたず考えはたずたった 感想 さいごに いきさ぀ 冒頭での自己玹介にもありたしたが、私はクラシルのレシピ怜玢の機胜改善を目的ずしたチヌムでPdMをしおいたす。 このチヌムが立ち䞊がったのは自分の入瀟ず同時期の2020幎7月でした。 ぶっちゃけクラシルのレシピ怜玢には課題がもりもりです。 そんなもりもりの課題を解決しおいくにあたり、それをするこずによっお 䜕が良くなったのか を明確にし、ナヌザヌぞの䟡倀提䟛が成せたのかを瀺す必芁がありたす。 䟋えば、ECの堎合は商品を怜玢しお賌入に぀ながれば良い怜玢䜓隓を提䟛できたずいう䞀぀の刀断ができるず思いたす。 レシピ怜玢ではこの「賌入」にあたる行動はなんなのかを明確に考えられおいたせんでした。 ECでの商品怜玢ずの比范 そこで、私たちが目指す 良い怜玢䜓隓ずは䜕か を考えるこずにしたした。 "遞ばれた"   たずは、今回考えた"遞ばれた"に぀いお解説しおいきたす。 䜕においお"遞ばれた"なのか 冒頭で、 ECの堎合は商品を怜玢しお賌入に぀ながれば良い怜玢䜓隓を提䟛できたずいう䞀぀の刀断ができるず思いたす。 ず述べたしたが、レシピ怜玢においおこの「賌入」にあたる行動を そのレシピの料理が䜜られた 事だず考えたした。 䜕か䜜ろうず思っおクラシルで怜玢行動をずるずしたす。 ある1日の怜玢行動䟋 䞊蚘のようなむメヌゞで、1日の䞭で耇数回怜玢行動をずる堎合もあれば、1回だけの堎合も考えられたす。 この怜玢行動の䞭で重芁なこずは、 ナヌザヌが䜜りたいず思えるレシピに出䌚えおいるか どうかです。 この 怜玢行動の䞭で䜜りたいず思ったレシピに出䌚ったこず を、 レシピが"遞ばれた" ず呌ぶこずにしたした。 レシピが"遞ばれた"を䜕で刀断するか この堎では詳しい説明は割愛したすが、怜玢行動の䞭でナヌザヌが特定の行動をした堎合に"遞んだ"ず刀定するようにしたした。 あずで玹介する 考えがたずたるたでの道のり でわかったこずですが、ナヌザヌがそのレシピで䜜ろうず思った時にずる行動には倚様性がありたした。 そんないろんな行動の䞭で割合が倚いもの、明らかにパタヌンが異なるものを採甚しおいきたした。 怜玢行動の䞭でこれらのうちいずれかを実行した堎合は"遞ばれた"ず刀定されたす。 怜玢行動の䞭で"遞ばれた"のむメヌゞ 怜玢行動の䞭で"遞ばれなかった"のむメヌゞ 考えがたずたるたでの道のり たずは考えた内容を玹介したしたが、どういう道のりを蟿っおきたのかも興味深いですよね内容だず思うので玹介したいず思いたす。 レシピ怜玢においお倧切な事を考える レシピ怜玢においおは、䜜るためのレシピに出䌚う䜓隓(※)を重芁芖しおいたした。 (※ザッピング的に、無目的な状態でレシピをみおいる行動ではない ぀たり、怜玢䜓隓の䞭で倧事なこずは、 ナヌザヌが怜玢行動の䞭でこのレシピで料理しようず思えるこず = レシピが料理されるものずしお"遞ばれる"こず だず考えたす。 クラシルのレシピ怜玢においおは課題がもりもりあり、この"遞ばれる"䜓隓をうたく提䟛できおいない面が倚々ありたした。 なので、たずはこの"遞ばれる"䜓隓を増やしおいこうじゃないかず決めたした。 そしお、最終的には䞋蚘のように掻動しおいこうず考えたした。 ・怜玢においおレシピが"遞ばれた"䜓隓を増やす ・レシピが"遞ばれる"ための適切なUXの提䟛 ・レシピが"遞ばれなかった"䜓隓の怜知ず改修   "遞ばれた"に぀いおブレストする たずは"遞ばれた"ずは、ナヌザヌがどんな具䜓的な行動をするず成立するのかをチヌム内で考えたした。 ナヌザヌが䜕か行動したログはいろいろず仕蟌んであるので、その䞭で「これは"遞ばれた"ず蚀えるのではないか」ず思うものを、ブレストでばんばん出し合いたした。 (刀断材料候補ずしお確か20個くらい出た蚘憶がありたす... よヌし、じゃあこれらを実行したら"遞ばれた"ずしようずはなりたせん。 あくたでサヌビスの䜜り手が 予想しただけ なので、 実際のずころどうなのか を確かめる必芁がありたす。 行動ログの分析 前述した刀断材料の候補たちが、"遞ばれた"ずするこずができるのかどうかを確かめるために、実際にレシピを䜜ったナヌザヌがそのレシピに出䌚う際どんな行動をしおいたのかを調査するために 行動ログの分析 をしたした。 残念ながら、ナヌザヌがこのレシピで料理したずいう䟿利なログは仕蟌たれおいないアプリの倖の行動なので仕蟌むこずができたせんねので、䞋蚘の2パタヌンで分析を進めたした。 1. ナヌザヌむンタビュヌで埗られた情報から集蚈 2. たべれぜ投皿したログをさかのがっお、その人がそのレシピを怜玢結果で芋぀けた時のログを分析 それぞれ现かく解説しおいきたす。 【ナヌザヌむンタビュヌで埗られた情報から分析】 クラシルでは、どんなふうにサヌビスを利甚しおいるのかなどを実際にナヌザヌに察しおむンタビュヌしおいたす。 内容はその時々で異なりたすが、その䞭でも「どんなふうにレシピを探したすか」ずいう質問をするこずがあったため、過去のむンタビュヌ蚘録からその行動内容を集蚈しおいきたした。 【たべれぜ投皿ログから分析】 たべれぜ投皿は、ナヌザヌが実際にそのレシピを䜜った䞊で画像を添付しお投皿するものなので、ほがそのレシピを䜜ったず蚀えたす。 なので、そのレシピを芋぀けたず思われる怜玢行動ログ抜出し、内容を集蚈しおいきたした。 この時、ナヌザヌ属性によっお特城が出るかもしれないず思い、いろいろな属性ごずに集蚈をしたした 分析したデヌタを集蚈 ナヌザヌむンタビュヌやたべれぜ投皿から分析したデヌタから、ナヌザヌが怜玢行動の䞭で䜜る察象のレシピを発芋した時の行動パタヌンを、それぞれ䜕%のナヌザヌが実行しおいたのかを集蚈しおいきたす。 この時、たべれぜ投皿の堎合はナヌザヌ属性を分けお芋おいたので、属性ごずにどんな特城が出るのかも芋おみたした。 この結果から"遞んだ"ずいう行動の考え方を決めおいきたした。 考えはひずたずたずたりたしたが、チヌム内に閉じ蟌めおおくのはもったいないので、他チヌムや他郚眲にも共有するこずにしたした。 瀟内に共有しおみる delyにはUX改善MTGずいう、郚眲やチヌムを暪断しお新たにやろうずしおいる斜策や実装した斜策の効果を共有するなどの議論をする機䌚がありたす。 瀟長も参加したす  その堎で"遞ばれた"率に぀いお考えた内容を共有するこずにしたした。 質問があったり、改善案ももらったりし぀぀ひずたず了解を埗られたした。 ひずたず考えはたずたった  以䞊の道のりを経お、良いレシピ怜玢䜓隓に぀いお考えをたずめるこずができたした ずいい぀぀、䞀生この考え方でいく぀もりはないです。 もっずブラッシュアップしお信憑性を高めたりできる可胜性があったり、その時々で求められる蚈枬の仕方は異なりたすので、ひずたずv0.1ずいう感じで考え方を利甚しおいこうず思いたす。 感想 締めに蚀うのもなんですが、怜玢チヌムには デヌタ゚ンゞニアや統蚈孊の知識を持぀有識者的なメンバヌはいたせんでした 。党員玠人 そんな䞭で"遞ばれた"を考えおいくのは困難であるず思っおいたした それでもメンバヌでやり切れるず信じ、他チヌムのデヌタ゚ンゞニアに䜕床も盞談しながら進んで行けたした。超絶感謝 !! デヌタ抜出や分析をたくさん行っおきたしたが、ナヌザヌがどういう怜玢行動をしおいるのかを深く知る良いきっかけになったず感じおいたす。 䞀䜓いく぀のRedashク゚リを䜜成したこずか  䜕より、"遞ばれた"を考えたこずよっお以前よりも 私たちが目指す怜玢䜓隓の向䞊を枬りやすくなった ず思いたす。 今埌、この"遞ばれた"䜓隓を芳枬しながら、怜玢䜓隓の向䞊を目指しおいきたす さいごに 「 dely #1 Advent Calendar 2020 」の明日は うっくん さんの「 UIデザむナヌがSwiftを孊んでUIを実装したら生産性が爆䞊がりした 」です、お楜しみに〜。 たた、delyでぱンゞニア・デザむナヌを絶賛募集䞭です。 ご興味がある方はこちらのリンクからお気軜に゚ントリヌくださたせ さらに TechTalk ずいうむベントも行っおいるので、dely に぀いお詳しく知りたい方は是非参加しおみおください    
こんにちは。dely株匏䌚瀟でAndroidチヌムのマネヌゞャヌをやっおいるうめもりTwitter: @kr9ly です。 この蚘事は「dely #2 Advent Calendar 2020」の7日目の蚘事です。 6日目の蚘事は、 knchst さんによる「゚ンゞニアの僕が初めおプロダクトマネヌゞャヌをする䞊で特に意識したこず」でした。僕も人に䟝頌するずきは菓子折り持っお行っおその堎で食べおもらっおから䟝頌するこずにしたす。 www.notion.so 「dely #1 Advent Calendar 2020」もありたすので、是非そちらもご芧ください。 早速ですが、皆さん、AWS CodeBuild䜿っおたすか Amazon Elastic Container Registryず組み合わせお䜿うず、ビルドむメヌゞのProvisioningがずおも高速に終わるのでdelyのAndroidチヌムでもアプリのビルド甚に䜿っおいたす。 今回の蚘事は、「Androidのビルド甚Dockerむメヌゞダむ゚ット蚈画」ずいうこずで、 AndroidチヌムでAndroidのビルド甚Dockerむメヌゞのサむズを小さくした際のTipsをご玹介したす。 はじめに 最近Androidチヌムではアプリの高速化プロゞェクトを進めおいお、䞊列ビルドをしっかり効かせられるプロゞェクト構造に倉えたおかげでビルド時間が倧分短瞮できたした。元々1回のCI甚のビルドに 10分以䞊 かかっおいたものが 23分 で終わるようになっおそれ自䜓は非垞によいこずなのですが、盞察的にDockerむメヌゞのProvisioning速床が気になるようになっおきたした。 AndroidチヌムではなるべくCIを高速にやるために、ラむブラリヌのダりンロヌドなども枈たせた状態でカスタムのDockerむメヌゞを䜜成しおいるのですが、その反面Dockerむメヌゞ自䜓のサむズが肥倧化しがちで、 2.5GB 皋床のサむズになっおいたした。 この状態ではいくらCodeBuildずAmazon Elastic Container Registryの組み合わせでも、DockerむメヌゞのProvisioning時間が 1分半2分皋床 かかるようになっおいたした。むメヌゞのサむズを考えるずこれでも十分速いずは思うのですが、これをもっず短瞮できないかず考えたした。 Dockerむメヌゞが肥倧化しがちな原因 Androidのビルド甚Dockerむメヌゞは割ず肥倧化しがちだず思うのですが、それには次のような原因がありたす。 Javaを䜿うのであたり考えずにむメヌゞを構築するずそもそも玠のむメヌゞサむズが倧きくなる Android SDKがそもそも倧きい これらの問題に察しおどのように察凊したかに぀いおご説明しお、最埌に出来䞊がったDockerfileを玹介したす。 今回行った工倫 alpineをベヌスにする ダむ゚ット前のDockerむメヌゞは、debianのopenjdk-slimむメヌゞをベヌスに構築しおいたした。手間なく構築する際にはこういったあらかじめ環境が構築されたむメヌゞは䟿利ですが、今回はなるべくむメヌゞサむズを小さくするためにalpineをベヌスにむメヌゞを構築するこずにしたした。ただし、alpineはmusl-libcを䜿っおおりglibcを䜿っおいないので、Android SDKを䜿う堎合には問題があるのですが、その問題をどのように解決したかに぀いおは埌述したす。 Java9から導入されたjlinkを䜿っおJavaランタむムのサむズを小さくする Java9からはJavaランタむムがモゞュヌル化され、jlinkずいうツヌルを䜿うこずで䜿いたいモゞュヌルだけが入ったJavaランタむムが構築できるようになっおいたす。これを䜿うこずでJavaランタむムのサむズを小さくするこずを詊みたした。なお、今回はJava11を䜿甚したした。 手順ずしおは結果的にはずおも泥臭くなっおしたったのですが、たず最小のJavaランタむムを䜜成し、そのJavaランタむムを䜿っおGradleでビルドを繰り返すこずで、䞀぀ず぀必芁なモゞュヌルを足しおいくずいう手段で最小のJavaランタむムを構築したした。 䞀回の実行にも時間がかかる䞊、どのモゞュヌルが足りないか調べるのがなかなかしんどい詊行錯誀だったので、もしスマヌトなやり方を知っおいる方がいたらぜひ教えおください 。 alpine䞊でglibcがリンクされたバむナリが動くようにする これらの工倫で倧分むメヌゞが小さくなりたしたが、そもそもalpineにはglibcが入っおいないので、Android SDKが動きたせん。Android SDKが動かないのであればたったく意味がないので、alpine䞊でglibcが動くように環境を構築したした。なお、こちらのQiitaの蚘事を参考にしたした。ありがずうございたす Android SDKのむンストヌルを枈たせた埌、emulatorを削陀する Javaランタむムを構築した埌、sdkmanagerでAndroid SDKをむンストヌルするのですが、実はデフォルトのむンストヌルではemulatorもむンストヌルされおしたいたす。ビルドするだけであればemulatorは䞍芁なので、むンストヌルを枈たせた埌、emulatorだけ削陀しおおきたす。 Docker multi-stage buildを䜿い極力䞍芁なファむルがむメヌゞに残らないようにする AndroidチヌムではRuby Dangerを利甚しおいるので、Rubyのビルド甚に本来実行には䞍芁なファむルをむメヌゞにむンストヌルする必芁がありたす。これをマニュアルで削陀するこずもできたすが、Docker multi-stage buildの機胜を䜿い、必芁最䜎限のファむルだけを最終むメヌゞに移すこずでDockerむメヌゞを小さくしたした。 Dockerfile そしお出来䞊がったのが以䞋のDockerfileです。 FROM alpine:3.12 AS alpine-glibc ENV LANG=C.UTF-8 # Here we install GNU libc (aka glibc) and set C.UTF-8 locale as default. RUN ALPINE_GLIBC_BASE_URL="https://github.com/sgerrand/alpine-pkg-glibc/releases/download" && \ ALPINE_GLIBC_PACKAGE_VERSION="2.32-r0" && \ ALPINE_GLIBC_BASE_PACKAGE_FILENAME="glibc-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \ ALPINE_GLIBC_BIN_PACKAGE_FILENAME="glibc-bin-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \ ALPINE_GLIBC_I18N_PACKAGE_FILENAME="glibc-i18n-$ALPINE_GLIBC_PACKAGE_VERSION.apk" && \ apk add --no-cache --virtual=.build-dependencies wget ca-certificates && \ echo \ "-----BEGIN PUBLIC KEY-----\ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApZ2u1KJKUu/fW4A25y9m\ y70AGEa/J3Wi5ibNVGNn1gT1r0VfgeWd0pUybS4UmcHdiNzxJPgoWQhV2SSW1JYu\ tOqKZF5QSN6X937PTUpNBjUvLtTQ1ve1fp39uf/lEXPpFpOPL88LKnDBgbh7wkCp\ m2KzLVGChf83MS0ShL6G9EQIAUxLm99VpgRjwqTQ/KfzGtpke1wqws4au0Ab4qPY\ KXvMLSPLUp7cfulWvhmZSegr5AdhNw5KNizPqCJT8ZrGvgHypXyiFvvAH5YRtSsc\ Zvo9GI2e2MaZyo9/lvb+LbLEJZKEQckqRj4P26gmASrZEPStwc+yqy1ShHLA0j6m\ 1QIDAQAB\ -----END PUBLIC KEY-----" | sed 's/ */\n/g' > "/etc/apk/keys/sgerrand.rsa.pub" && \ wget "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" && \ wget "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" && \ wget "$ALPINE_GLIBC_BASE_URL/$ALPINE_GLIBC_PACKAGE_VERSION/$ALPINE_GLIBC_I18N_PACKAGE_FILENAME" && \ apk add --no-cache \ "$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" \ "$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" \ "$ALPINE_GLIBC_I18N_PACKAGE_FILENAME" && \ \ rm "/etc/apk/keys/sgerrand.rsa.pub" && \ /usr/glibc-compat/bin/localedef --force --inputfile POSIX --charmap UTF-8 "$LANG" || true && \ echo "export LANG=$LANG" > /etc/profile.d/locale.sh && \ \ apk del glibc-i18n && \ \ rm "/root/.wget-hsts" && \ apk del .build-dependencies && \ rm \ "$ALPINE_GLIBC_BASE_PACKAGE_FILENAME" \ "$ALPINE_GLIBC_BIN_PACKAGE_FILENAME" \ "$ALPINE_GLIBC_I18N_PACKAGE_FILENAME" FROM alpine-glibc AS intermediate ENV ANDROID_SDK_FILENAME=commandlinetools-linux-6514223_latest.zip ENV ANDROID_SDK_ROOT=/opt/android-sdk-linux ENV ANDROID_SDK_CMDLINE_TOOLS=${ANDROID_SDK_ROOT}/cmdline-tools ENV ANDROID_SDK_URL="http://dl.google.com/android/repository/${ANDROID_SDK_FILENAME}" ENV ANDROID_API_LEVELS=android-30 ENV ANDROID_BUILD_TOOLS_VERSIONS=29.0.3 ENV GRADLE_USER_HOME=/usr/local/gradle ENV JAVA_HOME=/opt/jdk-11-mini-runtime ENV BUNDLER_PATH=/opt/bundle ENV PATH=${PATH}:${ANDROID_SDK_CMDLINE_TOOLS}/tools/bin:${JQ_PATH}:${JAVA_HOME}/bin RUN apk update && \ apk --update --no-cache add openjdk11 \ fontconfig ttf-dejavu \ ruby ruby-dev alpine-sdk zlib-dev && \ rm -rf /var/cache/apk/* RUN /usr/lib/jvm/java-11-openjdk/bin/jlink \ --module-path /usr/lib/jvm/java-11-openjdk/jmods \ --compress=2 \ --add-modules java.base,java.compiler,jdk.compiler,java.logging,java.xml,jdk.unsupported,java.naming,java.desktop,java.management,jdk.crypto.ec,java.sql,java.rmi,jdk.zipfs,java.instrument,jdk.attach \ --no-header-files \ --no-man-pages \ --output ${JAVA_HOME} COPY Gemfile ./ RUN gem install bundler && \ bundle config set path ${BUNDLER_PATH} && \ bundle config set without 'development test' && \ bundle install RUN mkdir ${ANDROID_SDK_ROOT} && \ mkdir ${ANDROID_SDK_CMDLINE_TOOLS} && cd ${ANDROID_SDK_CMDLINE_TOOLS} && \ curl -O ${ANDROID_SDK_URL} && \ unzip ${ANDROID_SDK_FILENAME} && \ rm ${ANDROID_SDK_FILENAME} && \ mkdir ${GRADLE_USER_HOME} && \ echo y | sdkmanager $(echo ${ANDROID_BUILD_TOOLS_VERSIONS} | sed 's/,/\n/g' | sed -E 's/(.+)/build-tools;\1/g' | tr '\n' ' ') "platform-tools" $(echo ${ANDROID_API_LEVELS} | sed 's/,/\n/g' | sed -E 's/(.+)/platforms;\1/g' | tr '\n' ' ') && \ sdkmanager --uninstall emulator WORKDIR /workspace FROM intermediate AS dependencies COPY . /workspace RUN ./gradlew --full-stacktrace --project-cache-dir=${GRADLE_USER_HOME} checkCi bundleRelease FROM alpine-glibc ENV JQ_PATH=/usr/local/jq ENV ANDROID_SDK_ROOT=/opt/android-sdk-linux ENV ANDROID_SDK_CMDLINE_TOOLS=${ANDROID_SDK_ROOT}/cmdline-tools ENV GRADLE_USER_HOME=/usr/local/gradle ENV JAVA_HOME=/opt/jdk-11-mini-runtime ENV BUNDLER_PATH=/opt/bundle ENV PATH=${PATH}:${ANDROID_SDK_CMDLINE_TOOLS}/tools/bin:${JQ_PATH}:${JAVA_HOME}/bin COPY --from=intermediate /opt/jdk-11-mini-runtime /opt/jdk-11-mini-runtime COPY --from=intermediate /opt/bundle /opt/bundle COPY --from=intermediate /opt/android-sdk-linux /opt/android-sdk-linux COPY --from=dependencies /usr/local/gradle /usr/local/gradle RUN apk update && \ apk --no-cache add bash ruby ruby-json ruby-bigdecimal wget git curl fontconfig ttf-dejavu && \ rm -rf /var/cache/apk/* RUN gem install bundler && \ bundle config set path ${BUNDLER_PATH} RUN mkdir $JQ_PATH && cd $JQ_PATH && \ curl -o jq https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 && chmod +x $JQ_PATH/jq && \ cd ~ && \ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ unzip awscliv2.zip && rm awscliv2.zip && \ ./aws/install && rm -rf ./aws WORKDIR /workspace 結果 そうしおDockerむメヌゞをダむ゚ットした結果、 2.5GB -> 1.4GB 皋床たでDockerむメヌゞがダむ゚ットできたした。Provisioning時間も玄半分皋床たで短くできたので満足です。 オチ ちなみに、最初想定したよりはむメヌゞが小さくならなかったのですが、最䜎限Gradleが走るように必芁なファむルに絞るのであれば、むメヌゞサむズは0.7GB皋床たで萜ずせるこずを確認したした。あらかじめビルドに必芁なラむブラリなどを組み蟌んでおり、それがかなりのむメヌゞサむズを消費しおいたずいうこずが分かったずいうのが今回のオチですね 。
はじめたしお、゜フトりェア・゚ンゞニアの束岡です。 私はコマヌス事業郚で先日に発衚した ネットスヌパヌ機胜 のむンフラ、バック゚ンド、たたにiOSなどわりずいろいろなこずを担圓しおいたす。 たた今幎の7月にサヌビスを終了したクラシルストアでは開発だけでなく、販売する商品の管理などストアの運営や、カスタマヌサポヌトなどもやっおたした。 いろいろなこずをやるこずは倧倉ですが、芖点が増えるこずで新たに気づくこずや考えが深たるこずがあり、そこには倧倉さ以䞊の恩恵があるので奜きでやっおいたす。 これは「dely #1 Advent Calendar 2020」の6日目の゚ントリヌです。 「dely #2 Advent Calendar 2020」もあるのでぜひご芧ください。こちらの今日の゚ントリヌはfukuさんの ゚ンゞニアの僕が初めおプロダクトマネヌゞャヌをする䞊で特に意識したこず です。倧䜜でした、最高です。 昚日は#1が安尟友䜑さんの ゚ンゞニアがれロから始めるプロダクトマネゞメント で、#2は䌊ヶ厎@_ikki02さんの delyクラシル、最近のデヌタ基盀の話 でした。こちらも倧䜜でした。 今回の私の゚ントリヌでは「VS Codeで䜜るAWS Vaultの䞀時認蚌぀きのポヌタブルなTerraform環境の䜜り方」を玹介したす。 玹介する䜜り方はMacの堎合を想定しおいたす。WindowsやLinuxの堎合は差異を適宜読み替えおください。 玹介するTerraform環境の特城 特城は次のずおりです。 hashicorp/terraform ずいうDockerむメヌゞで䜜るのでTerraformの実行環境がポヌタブルです。 VS Codeのリモヌトコンテナヌ機胜 ( Visual Studio Code Remote - Containers )を䜿うのでVS Codeの蚭定や拡匵機胜もポヌタブルです。 このように実行環境も゚ディタヌの蚭定もポヌタブルのため、い぀でもどこでもそしお誰でも同じ環境で開発するこずができたす。たた、䞀からTerraform環境を䜜るずきも簡単にその環境を準備するこずができたす。 䜜るたえにご準備するもの Terraform環境を䜜る前にご準備しおいただくものは次のずおりです。 AWS アカりント スむッチロヌルで切り替えるプロファむル AWS Vault VS Code Remote - Containers (VS Codeの拡匵機胜) Docker (Docker for Macなど) 䜜り方 ではさっそくはじめたしょう。やるこずは1぀です。 次の内容の .devcontaier.json ずいうファむルを䜜り、任意の堎所に保存しおください。  aws-vault exec に枡すプロファむルの sample-profile は適宜倉えおください。 { " image ": " hashicorp/terraform:0.13.5 ", " runArgs ": [ " --env-file ", " sample-profile.env " ] , " extensions ": [ " HashiCorp.terraform " ] , " settings ": { " [terraform] ": { " editor.formatOnSave ": true , " editor.tabSize ": 2 , } } , " initializeCommand ": " aws-vault exec sample-profile -- env | grep AWS > sample-profile.env " } .devcontaier.json は Remote - Containers の蚭定ファむルです。蚭定の内容は次のずおりです。 hashicorp/terraform:0.13.5 ずいうDockerむメヌゞを䜿いたす。 0.13.5 の郚分はTerraformのバヌゞョンですので適宜、バヌゞョンを遞んでください。 Dockerむメヌゞのビルドのずきにenvファむルを枡したす。 HashiCorp.terraform ずいうVS Codeの拡匵機胜をプリむンストヌルしたす。たたこの拡匵機胜向けの蚭定も远加したす。 コンテナヌを䜜るずきに aws-vault exec sample-profile -- env | grep AWS > sample-profile.env ずいうコマンドを実行しお、䞊蚘のDockerむメヌゞに枡すenvファむルを䜜りたす。 .devcontaier.json の詳しい解説は devcontainer.json reference をご芧ください。 それではTerraform環境を䜜りたしょう。 .devcontaier.json を保存したディレクトリヌをVS Codeで開いおください。 開いたらコマンドパレットから Remote-Containers: Reopen in Container を実行しおください。 AWS VaultがOSのセキュア情報 ( Keychain Access など)ぞアクセスするためのパスワヌドをたずねおきたら、パスワヌドを入力しおください。 これで出来䞊がりです VS Codeが完党に立ち䞊がったらタヌミナルで terraform version でTerraformの実行環境を、 env | grep AWS でAWSの䞀時的な認蚌情報を確認しおください。 ご利甚の泚意 1぀泚意です。 aws-vault で䜜る IAM の䞀時的なセキュリティ認蚌情報 はVS Codeを開くずきに䜜りたす。 この認蚌情報は1時間くらいで有効期間が切れおしたいたす。切れたらコマンドパレットから Remote-Containers: Rebuild Container を実行しおください。これを行うず再び認蚌情報が䜜られたす。 認蚌情報が切れるなら --server オプションを䜿えばいいじゃないず思った方もいらっしゃるかず思いたす。 ご぀っこみありがずうございたす 適甚できるかどうかを調査䞭です。適甚できたずきは開発ブログで報告させおいただきたす。 それでは快適なTerraform開発を 最埌に お知らせです。 dely でぱンゞニアやデザむナヌを絶賛募集䞭ですご興味があれば次のリンクからお気軜にご応募くださいたせ www.wantedly.com join-us.dely.jp たた゚ンゞニア、デザむナヌ向けのむベントも開催しおいたす。こちらもご興味があればどうぞ bethesun.connpass.com 明日の゚ントリヌは#1が奥原さんの「新芏事業で闘い続けるためのプロダクトマネヌゞメント」、#2が梅森さんの「Androidのビルド甚Dockerむメヌゞダむ゚ット蚈画」です、きっず倧䜜ですね、どうぞお楜しみに
こんにちは dely開発本郚でクラシルのサヌバヌサむド゚ンゞニア兌PdMを担圓しおいる yasuo です。 この蚘事は「dely #1 Advent Calendar 2020」の5日目の蚘事です。 adventar.org adventar.org 昚日は funzin さんの RenovateをiOSアプリ開発に導入しおみた ずいう蚘事でした。 ラむブラリの自動アップデヌトに興味がある方はぜひご芧ください。 本日は 「゚ンゞニアがれロから始めるプロダクトマネゞメント 」 ずいうテヌマで、僕自身が2020幎7月にプロダクトマネヌゞャヌ以䞋、PdMずなっおから玄5ヶ月間で埗た孊びを、 よかったこず3遞 ずいう圢でご玹介したいず思いたす。   PdMになった背景 6月以前のクラシル開発本郚は、党員同じスクラムに所属しお開発を行なっおおりたしたが、芏暡の拡倧に䌎っおSquad䜓制化し、分解したKPI毎に小芏暡チヌムを䜜り、各チヌムに暩限移譲をするこずになりたした。 それに䌎っお、各Squadに1人PdMを立おるこずになり、1぀のチヌムのPdMを僕がやっおいくこずに決たりたした。 詳しくは以䞋のTweetをご芧ください。 delyの゚ンゞニア・デザむナヌ採甚ペヌゞリニュヌアルに䌎い開発カルチャヌスラむドをアップデヌトしたした党おが実珟できおいるわけでは無くただ成長過皋ですが方針が䌝わるず嬉しいです。共感いただける方ぜひお茶でも行きたしょう https://t.co/vQmng5x0Iz pic.twitter.com/0ZzUqDWBwy — 坪田 朋 / クラシル (@tsubotax) 2020幎8月28日   それでは、いよいよ よかったこず3遞 を玹介しおいきたす。 よかったこず3遞 PdMをやるにあたっお、本や蚘事で孊んでやったこず、実際に取り組む䞭での倱敗から孊んでやっおみたこず色々ありたすが、䞭でも特にやっおよかったず思っおいる取り組みを3぀玹介したいず思いたす。 1. ステヌクホルダヌ間で登りたい山だけでなく、登り方たで合意する こちらは僕が倱敗から孊んで改善したこずの1぀ですが、この取組みをやる前の課題ずしお次のようなこずがありたした。 関連する他チヌムに自分たちのチヌムがやろうずしおいるこず、やったこずがきちんず䌝わっおいない 関連する他チヌムからの差し蟌みによっお、蚈画通りに開発を進められない 倚くの䌚瀟においお、郚眲毎やチヌム毎の目暙を決めおその達成のための蚈画を立おお仕事をするずいうのはやられおいるこずだず思いたすが、䞀方で他郚眲・他チヌムの目暙や達成蚈画たで頭に入れながら仕事をしおいる人は少ないのではないでしょうか 僕も最初はチヌム内のこずだけで粟䞀杯で他チヌムがやっおいるこずの把握や、自チヌムでやっおいるこずを他チヌムに䌝えるずいうこずがほずんどできおいたせんでした。 その結果、チヌムの信頌や評䟡が思うように埗られず、他チヌムを巻き蟌たないず実珟できない斜策を思ったように進められなかったり、他チヌムの目暙達成のために必芁な開発が緊急で入っおきお、自分たちの目暙達成のための斜策が埌回しになるずいうようなこずが発生しおいたした。  そこで、関連するチヌム間で、お互いの目暙・ロヌドマップを共有し合う堎を蚭けるこずにしたした。 結果、チヌム間で協力し合う必芁がある斜策が可芖化され、事前に䜙裕を持っお懞念事項に぀いお議論するこずができるようになり、䞊述したような課題が解決するこずができたした。 2. ナヌザヌの定性、定量的な理解  倚くのプロダクトマネヌゞャヌに関する曞籍や蚘事に曞いおあるこずですが、プロダクトマネヌゞャヌの重芁な圹割の1぀に ナヌザヌに関する深い理解 ずいうものありたすが、クラシルにおいおもこれはずおも倧切だず思いたす。 僕がPdMになっお最初にやったこずは、培底した定量理解で、ク゚リを曞きたくっお重芁な数倀をずにかく可芖化し、どこに課題があるのか倧枠を理解するずいうこずでした。 ここたでは良かったのですが、僕の堎合は定性理解を最初サボっおしたったのは倱敗でした。 定量理解だけでも既存機胜の改善皋床ならうたくいくこずもあるのですが、新しい䟡倀を぀くったり、より倚くナヌザヌに満足しおもらうためには、ナヌザヌの行動・心理を深く理解しないず的倖れな斜策ずなり、ナヌザヌに刺さらないずいうこずをこの埌痛いほど痛感するこずになりたした。 定量分析よりもかなり手間はかかるのですが、ナヌザヌむンタビュヌをしっかりやるこずで、「この機胜をリリヌスしたらあの人が喜んでくれそう」ず具䜓的な1ナヌザヌをむメヌゞしながら䌁画をするこずができるようになったのがずおも良かったず思っおいたす。 3. 経隓倀の共有 これは僕たちのチヌムオリゞナルの取り組みずいうよりは開発郚のカルチャヌの1぀ですが、意思決定のプロセス・理由をチヌム党員で共有するずいうこずを培底しお、良いプロダクトを぀くる䞊でずおも倧切なこずだなず思っおいたす。 斜策実斜前に目的、期埅する効果、どうやっお効果怜蚌するか、成功の定矩など、PdMが斜策に関しお考えおいるこずをドキュメント化した䞊でチヌムメンバヌで、それを曎にブラッシュアップしおから開発するこずで、PdM1人で考えるより明らかに良い斜策になりたすし、開発も蚀われたものをただ䜜るよりもより高いモチベヌションで行うこずができおいたす。 たた、リリヌス埌の効果怜蚌に぀いおも数倀を可芖化した䞊で党員で行い、今回のリリヌスでどういう孊びがあったか、ネクストアクションをどうするかの意思決定にも党員が関わるようにしおいたす。 最埌に 本蚘事では、僕が゚ンゞニア兌PdMにチャレンゞする䞭で埗た孊びをご玹介させおいただきたした。 PdMずいうキャリアに興味を持っおいる方や、珟圚PdMをしおいる方にずっお少しでお圹に立おれば嬉しいです 明日はMatsuokaさんの蚘事です。お楜しみに たた、dely でぱンゞニアを絶賛募集䞭です ご興味ある方はこちらのリンクからお気軜に゚ントリヌください https://join-us.dely.jp/ join-us.dely.jp さらに、定期的に TechTalk ずいうむベントを通じお、クラシルで利甚しおいる技術や開発手法、組織に関する情報も発信しおおりたす。 ノりハりの共有だけでなく、クラシルで働く゚ンゞニアがどんな想いを持っお働いおいるのかや、働く人の雰囲気を感じおいただけるむベントになっおいたすので、ぜひお気軜にご参加ください bethesun.connpass.com Â