これは、2018 年初頭に着手し、2022 年に完成させた、コンテンツ クリエーター向けのソーシャル メディア プラットフォームである、私のソロ開発プロジェクト、の完全な公開です。
大規模なプロジェクトを開始しようとしている、またはプロジェクトを構築している最中で、継続するためのモチベーションが必要な人へのガイドとして役立つことを願っています。
さかのぼる 2018 年の初めに、かすかな声が出始めました。人々は、ウェブやソーシャル メディア全般の状態について意見を表明し始めました。
Facebookとそのユーザーのプライバシーに関するポリシーに不満を持っている人もいれば、YouTubeとその収益化の慣行に不満を持っている人もいれば、Medium.comのような意見を表明しているプラットフォームそのものに反対する人もいます.
しかし、これらすべてに代わるものはどのようなものでしょうか?
ソーシャル メディア プラットフォームを構築するというアイデアは、コンテンツ クリエーターに、個人としてコンテンツを作成するだけでなく、コミュニティと関わり、コンテンツ作成プロセスに貢献してもらいながら、その努力を収益化できる能力を与えたいという願望から生まれました。 ;すべてが 1 か所にあります。
プラットフォームは無料で使用でき、誰でも参加できる必要があります。言うまでもなく、世界中の人々を教育し、楽しませる仕事に時間を費やすクリエイターも、好きなことで生計を立てられるべきです。
これらのいずれかで、今日オンラインで直面しているすべての問題を解決できますか?いいえ、しかしそれは代替手段であり、それが重要です.それは種であり、時間の経過とともに美しいものに変わる可能性があります.
また、技術者として、これらすべてを 1 人でできるのではないかと考えました。
実際に 1 人の人間がこの課題に取り組み、人々が使いたくなる堅牢なエンタープライズ レベルのプラットフォームを提供することは可能でしょうか? また、可能であれば、そのようなシステムを提供するには何が必要でしょうか?
正直なところ、これらの質問に対する答えはありませんでしたが、挑戦することにしました。
2018 年の初めの頃、私はソフトウェア開発者として 6 年ほどの実務経験しかありませんでした。そのほとんどはバックエンド .NET で、フロントエンドでは WPF の一部でした。
私も東京に住んでいて、引っ越してから 3 年足らずで、残業が当たり前でした。控えめに言っても、すべての意図と目的のために、追加の作業を行うという考えは不合理でした.
できるだけ多くの人が利用できるようにするためには、プラットフォーム自体を Web サイトにする必要がありますが、Web 開発の経験がほとんどないため、これを実現することは不可能に思えました。
それが不可能に思えたという事実は、まさに私が遅かれ早かれ始めることにした理由でした.これはすべて、膨大な時間の無駄でしょうか?時間だけが教えてくれますが、始めるのが早ければ早いほど、答えを見つけるのも早くなります。
そして、 2018年2月1日、企画をスタート。
私が最初にやろうと決めたのは、これが簡単ではないだろうと考えるのは世紀の控えめな表現になるだろうと自分自身に言い聞かせることでした.私はこれまでこのようなことをしたことがなかったので、自分が何に夢中になっているのか文字通りわかりませんでした.
これは、これが完了するまで、趣味や生活に似たものがないことを意味しました。フルタイムの仕事で残業して、家に帰ってこのプロジェクトに取り組むことが、新しい標準になりつつあります。
休暇を取って、ある時点で旅行しますか?はい、しかし、私はこれらの休暇をほとんど仕事にも費やさなければなりません。
これまでの経験から学んだことがあるとすれば、それは、組織化することがプロジェクトの成否を左右し、順調に進むということです。前もって準備して設計すればするほど、後で行う必要のある試行錯誤的なタイプの作業が少なくなります。
考えて計画することは、確かに構築するよりも簡単で、時間もかからないため、計画フェーズで多くのことを行えば行うほど、実際の開発フェーズで発見する必要が少なくなり、時間的に多くのリソースが必要になります。
リソースが足りなかったので、企画やデザインで補わなければなりませんでした。また、Web は行き詰まったときに行く場所であることも知っていましたが、このプロジェクトでは Stack Overflow を使用せず、新しい質問をすることはないと決めました。
この決定の背後にある理由は非常に単純でした。ここで学ぶことがたくさんあるので、誰かにすべての問題を解決してもらうだけでは、経験を積むことはできません。
プロジェクトが進めば進めば進むほど難しくなり、自分で取り組むための経験が得られなくなります。
したがって、既存の回答をウェブで検索するだけで、問題を解決するために新しい質問をすることはしないことにしました。プロジェクトが完了したら、再びスタック オーバーフローに関与することができますが、この特定の開発目標のためには立ち入り禁止です。
構築したいシステムを設計するためにアプローチを利用します。システムは、各部分がどのように機能し、システムの他の部分とどのように相互作用するかを教えてくれます。さらに、後でコードに実装するビジネス ルールを抽出します。
それから私はメモを取り始め、私が集中しなければならない 2 つの主なポイントがあることに気付きました。
1 日は 24 時間しかなく、そのほとんどが仕事や旅行に費やされることを知っていたので、慎重に時間を最適化する必要がありました。
プロジェクト設計の部分では、時間を最大限に活用するために、すでにうまく機能している製品を調べて、それらをインスピレーションの源として使用することにしました。
明らかに、システムは高速で誰もがアクセスできる必要があり、複数のプラットフォーム用のコードを書く時間がないため、Web がその答えでした。
その後デザインに転向。広く受け入れられているデザイン プラクティスで有名な Apple は、すぐにインスピレーションの源となりました。次に、Pinterest に目を向け、これがうまく機能する最もシンプルなデザインであると判断しました。私の決断の背後にある考え方は、古いことわざでした。
コンテンツは王様です。 — ビル・ゲイツ、1996 年。
これにより、不要なデザインの詳細を可能な限り削除し、コンテンツの提示に集中するというアイデアが生まれました.考えてみれば、デザインがいいから一度はサイトに来てくれますが、コンテンツが良くなければ再訪はありません。
これにより、フロントエンドの設計に必要な時間が短縮されました。
システム自体はシンプルでなければなりませんでした。すべてのユーザーは、独自のコミュニティを作成して所有できる必要があります。他のユーザーは、メンバーとして参加し、記事を書いてこのコミュニティにコンテンツを投稿できる必要があります。この機能は、多くのユーザーが同じ記事を編集できるウィキペディアから着想を得ています。
多くの人が特定のトピックについて一緒に取り組んでいる場合、これは、私たちが手元に持っているのはコミュニティであり、残りのコミュニティから分離する必要があることを示しています.
機能に関しては、ウィキペディアのように通常の記事を書いてそれらをつなげるだけでなく、別の種類の記事が必要になるレビューも書くことができる必要があります。
したがって、通常の記事は「インタレスト」と呼ばれ、本質的に事実と情報を提供し、誰でも編集できます。
一方、「レビュー」はそれぞれの興味に基づいており、レビューの作成者のみが編集できます。
要するに、人々は協力して映画、たとえば「マトリックス」について書き、いつでもその記事を編集することができます。彼らは、必要な記事に事実情報を追加することができました。
その後、各ユーザーはその映画の独自のレビューを書くことができます。当然、元の記事には、この映画について書かれたすべてのレビューのリストが表示されます。
さらに 2 つの重要な機能を追加する必要があることは明らかでした。 1 つ目は、各コミュニティの記事を検索する検索オプションです。もう 1 つの機能はレコメンデーションで、ユーザーが記事の最後までスクロールすると、気に入ったものに基づいて提供されます。
また、各ユーザーは自分のプロフィールに簡単な更新や「アクティビティ」を投稿できる必要があります。これは、Facebook のウォールとして機能します。これは、実際にプロジェクトを提供するために使用できるテクノロジについて考え始める前に作成した基本的な概要です。
次に注目したのはテック スタックです。 Web ベースのプロジェクトを構築することをすでに決めていたので、当時最も一般的に使用されていた最新の人気のあるテクノロジをすべて書き留めることにしました。次に、新しいテクノロジーの学習に費やす時間をできるだけ少なくするために、自分のキャリアですでに使用しているものに技術的に近いものを選びました。
さらに、このフェーズで最も頭を悩ませたアイデアは、できるだけコードを書かなくても済むような設計上の決定を行い、時間を節約することでした。
広範な調査の結果、次の主要な技術スタックに落ち着きました。
さらに、SaaS サービスを選択することで、これらの機能を自分で実装する必要がなくなるため、さらに時間を節約できます。私が決めたサービスは以下の通りです。
これらは、プロジェクトに取り組み始めるために、できるだけ早く習得しなければならなかった主なテクノロジでした。他のすべては、途中で学ぶ必要があります。
この時点で、私は新しい技術を学び始めました。主なものと最も重要なものについては、次の本を読み始めました。
日中はフルタイムの仕事をすることにしましたが、夜は公平なゲームでした。家に帰ったら、寝るまで勉強できました。睡眠自体に関して言えば、勉強する時間を増やすために6時間に短縮されます.
私は当初、このプロジェクトを構築するのに 1 年しかかからないと予測していました。それから、ほぼ 1 年間新しいテクノロジを学習した後、2018 年 12 月になり、ちょうど 0 行のコードを書きながら、主要な読み物を読み終えました。
2018年12月、ようやく開発に取り掛かりました。 Visual Studio Code をセットアップし、開発環境の構築を開始しました。
このような大規模なプロジェクトに必要なすべてのサーバーを維持できないことは、初日から明らかでした。技術的な観点だけでなく、予算面からも。したがって、私は解決策を見つけなければなりませんでした。
幸いなことに、 という形のソリューションと、バックエンド インフラストラクチャへのサーバーレス アプローチを見つけました。
これらのアプローチが今日のように広く普及する前でさえ、何かをコードで簡潔に記述することができれば、それを自動化して時間とリソースを節約できることはすぐにわかりました。
DevOps では、フロントエンドとバックエンドの両方の開発を統合して自動化し、サーバーレスではサーバーのメンテナンスの必要性を取り除き、運用コストも削減します。
この哲学は明らかに私の考え方に沿っており、最初にセットアップすることに決めたのは、Terraform を使用した CI/CD パイプラインでした。
設計は、開発、UAT、および生産の 3 つのブランチで構成されていました。私は毎日働き、作業が完了したら、変更を AWS CodeCommit の Development ブランチにコミットするだけで、AWS CodeBuild がトリガーされてプロジェクトがビルドされました。
ローカル テスト用の macOS 用と、CodeBuild でのビルド用の Linux ベースの実行可能ファイルの両方で、Terraform 実行可能ファイルをリポジトリに保持します。
ビルド プロセスが開始されると、Terraform 実行可能ファイルが CodeBuild 内で呼び出され、Terraform コード ファイルが取得され、AWS でインフラストラクチャが構築されます。
次に、これらのすべての部分を接続して自動化する必要があります。これは AWS CodePipeline を使用して行いました。これにより、コミットを行うたびにコードが CodeCommit から CodeBuild に移動されます。
これは、コードとインフラストラクチャの同期を維持するのに役立ちます。前進する準備ができたら、開発ブランチを UAT にマージするか、UAT を本番環境にマージして、他の環境を同期します。
バックエンドの CI/CD パイプラインが完成したら、実際の Web サイトをローカルでセットアップして、フロントエンドの開発を開始します。
フロントエンドを設定する最初のステップは、Aurelia ベースのプロジェクトを作成することでした。 React を使用せずに Aurelia を使用するという決定の背景にあるのは、React です。React は JavaScript フレームワークの最も一般的な選択肢であり、 パターンが原因でした。
MVVM パターンは、私が経験した WPF デスクトップ アプリで顕著に使用されています。したがって、React を学ぶには、既に知っていることを単純に構築するよりも時間がかかります。
一方、Angular や Vue ではなく Aurelia を使用するという決定は、フレームワークを邪魔にならないようにするという Aurelia の背後にある哲学に基づいていました。つまり、アウレリアにはアウレリアがいない。
Aurelia での開発中に基本的に使用しているのは、HTML、JavaScript、および CSS であり、属性へのデータ バインディングなどのいくつかの機能が追加されています。
したがって、決定は最終的なものでした。次に、静的に型付けされる C# の世界から来て、JavaScript ではなくを使用することにしました。
次は WebPack です。遅延読み込みを容易にするために、アプリケーションをチャンクに分割する明確な必要性がありました。アプリに多くの機能があることはすでにわかっていたので、必要に応じてパーツをロードできる SPA としてアプリを構築することが不可欠でした。
そうしないと、ユーザー エクスペリエンスが受け入れられなくなります。
WebPack 構成の処理を簡単にするために、 をミックスに追加し、その流暢なインターフェースを使用して WebPack をセットアップすることにしました。
2019 年になっても、モバイル Web ブラウジングが増加していることはよく知られていました。最新の Web に備えるために、開発アプローチは次のように定義されました。
これは、 と PWA という 2 つの主要なテクノロジによって促進されました。スタイリングに関しては、システムをさらに簡素化するために、Tailwind CSS を追加しました。これは、デフォルトでモバイル ファーストであるため、私が行った最良の決定の 1 つであることが判明しました。
また、ユーティリティベースなので再利用性が高く、まさに求めていたものでした。
さらに、私はネイティブのようなエクスペリエンスを提供しようとしていましたが、ネイティブ アプリを作成する時間がなかったため、次善の策、つまりブラウザーから直接インストールしてオフラインで作業できるアプリを選ぶことにしました。
これはプログレッシブ Web アプリ (PWA) がユーザーに提供することを目的としていますが、手動でセットアップするとエラーが発生しやすく、時間がかかるため、Service Worker のインストール、オフライン、およびキャッシュ機能を備えたを使用することにしました。内蔵。
コアのセットアップが完了したら、オンラインで試乗してみましょう。 Web サイトはいつでも誰でもアクセスできる必要があることは明らかでした。停止は最新のシステムには当てはまりません。そのため、次の方法でシステムをセットアップすることにしました。
まず、HTML および JS ファイルが AWS S3 バケットから提供されます。グローバルに低レイテンシーで利用できるようにするために、AWS CloudFront CDN Network がフロントになります。
この場合のわずかな後退は、Terraform よりも関数のセットアップが簡単だったため、も使用することを決定したときでした。そのため、私が世話をしなければならなかった新しいテクノロジーを導入しました。
セットアップが完了したら、AWS Route 53 でドメインを購入しました。これは、テストに使用したものであり、運用ドメイン – です。
名前の背後にあるアイデアは、同様の名前のコミュニティベースのネットワークであるに由来しています。 「没入型」という言葉を選んだのは、その当時、Google で非常に流行り始めたからです。
「immersive.networks」と「immersive.community」はすでに使用されていたので、「immersive.community」に落ち着きました。
フロントエンドを起動したので、データベースの作業を開始する時が来ました。以前は SQL ベースのリレーショナル データベースに慣れていましたが、この特定のプロジェクトには明らかに遅すぎたため、NoSQL データベースを使用することにしました。私が AWS DynamoDB を選んだ理由は、そのサーバーレス製品です。
データベースにアクセスするために、マネージド GraphQL 実装であり、サーバーレスでもある AWS AppSync を選択しました。
この時点で、私が直面した最大の問題の 1 つを解決し始める時が来ました。
ユーザーが複数のコミュニティに参加できるようにする方法は?
この問題を解決する最も簡単な方法は、複数のデータベースを作成することですが、ある時点で作成できるデータベースが不足するため、これには明らかな制限があります。
各 AWS アカウントには、作成できるリソースの数に制限があることが判明したため、これは実行可能なソリューションではありませんでした。
次に、DynamoDB データベースの各エントリに type 列を割り当てることで、この問題を最終的に解決します。各ユーザーのタイプは「user」に設定され、各コミュニティは単純に「web」に設定されます。
次に、この行のキーが「user#web_user#web」として指定されている新しい行を追加して、ユーザーがコミュニティに参加したことを示します。ユーザーとコミュニティ名が一意になるため、このキーも一意になり、ユーザーはコミュニティに複数回参加できなくなります。
ユーザーがコミュニティに参加している場合にのみ実行できるアクションを実行したい場合は、DynamoDB で複数の行をクエリできる AppSync が提供するパイプライン関数を使用するだけです。
次に、ユーザーがコミュニティのメンバーであるかどうかを照会して確認し、メンバーである場合にのみ、ユーザーがアクションを実行できるようにします。
これにより、の問題は解決されましたが、解決すべき最大の問題の 1 つがすぐそこにありました。
言うまでもなく、エンタープライズ レベルのシステムは、フォールト トレランスと高可用性を念頭に置いて構築されています。これにより、一部のコンポーネントに障害が発生した場合でも、ユーザーはシステムを使用し続けることができます。
そのようなシステムを実装したい場合は、冗長性を念頭に置いて実装する必要があります。
私の調査により、この場合の最適なソリューション、つまりアーキテクチャが導き出されました。 AWS のほとんどのサービスはすでに高可用性であることがわかりましたが、AppSync 自体はそうではありません。したがって、私は独自の実装を作成することにしました。
Web はこの問題の解決策を提供していなかったので、自分で作成する必要がありました。グローバルに考え始めました。つまり、訪問者はさまざまな地域から来ており、AppSync を米国に配置すると、アジアの訪問者のレイテンシーが高くなります。
待ち時間と高可用性の問題は、次の方法で解決しました。その時点で、利用可能なすべてのリージョンで 10 の異なる AppSync API を作成することにしました。現在、API は米国、アジア、およびヨーロッパにあります。
さらに、各 API は、同じリージョンにある対応する DynamoDB データベースに接続する必要があります。したがって、さらに 10 個の追加の DynamoDB テーブルを作成しました。
幸いなことに、DynamoDB には、接続された DynamoDB テーブル間でデータをコピーして同期を保つグローバル テーブル機能があります。
これで、ユーザーがデータベースに書き込む場所に関係なく、データが同期された後、別の地域のユーザーが同じ情報を読み取ることができます。
今出てきた疑問は次のようなものでした。
ユーザーはどのようにして最も近い API にルーティングされますか?それだけでなく、1 つの API が失敗した場合、次の使用可能な API に呼び出しをすぐにルーティングするにはどうすればよいでしょうか?
このソリューションは、CloudFront と関数の形で生まれました。これは、呼び出し元が配置されているリージョンで Lambda@Edge 関数をトリガーできる CloudFront の素晴らしい機能です。
ユーザーがどこにいるかがわかれば、呼び出し元に基づいて Lambda@Edge 関数内で API を選択できることは明らかです。
さらに、実行中の Lambda@Edge 関数のリージョンも取得できるため、同じリージョンで AppSync API を選択できます。このソリューションを実装するための最初のステップは、CloudFront を介して AppSync 呼び出しをプロキシすることでした。
したがって、呼び出しは AppSync ではなく CloudFront に対して直接行われます。
次に、Lambda@Edge 関数内の CloudFront パラメーターから HTTP 呼び出しを抽出する必要がありました。 CloudFront パラメーターから抽出されたリージョンと AppSync クエリを取得したら、対応する AppSync API に対して新しい HTTP 呼び出しを行います。
データが返されたら、Lambda@Edge 関数を介して CloudFront に戻すだけです。その後、ユーザーは要求されたデータを取得します。
しかし、アクティブ-アクティブ要件はまだ解決していません。目標は、API が利用できなくなった時点を検出し、別の API に切り替えることでした。 AppSync 呼び出しの結果を確認することで、この問題を解決しました。 HTTP 200 応答でない場合、呼び出しは明らかに失敗しています。
次に、使用可能なすべてのリージョンのリストから別のリージョンを選択し、そのリージョンで次の AppSync API を呼び出します。呼び出しが成功した場合は結果を返し、失敗した場合は成功するまで次のリージョンを試します。
最後のリージョンも失敗した場合は、単に失敗した結果を返します。
これは、アクティブ-アクティブの高可用性アーキテクチャの単純な実装です。このシステムが整ったことで、実際に次の 3 つの機能が実装されました。
各グローバル ユーザーは、API の呼び出し元である最も近いリージョンにルーティングされるため、平均して明らかに低レイテンシです。ユーザーは地域内の複数の API にルーティングされるため、地域ベースの負荷分散も行っています。
最後に、ユーザーが次に利用可能な API にルーティングされるため、一部の API またはデータベースに障害が発生した場合でも、システムは機能し続けるため、アクティブ-アクティブ高可用性があります。
実際には、API の高可用性を処理するだけでは十分ではありません。 CloudFront から提供された HTML および JavaScript ファイルを含む、すべてのリソースに対してそれを使用したかったのです。
今回も同じアプローチを使用しましたが、16 個の AWS S3 バケットを作成しました。各バケットは同じファイルを提供しますが、異なるリージョンに配置されます。
この場合、ユーザーが Web サイトにアクセスすると、ブラウザは HTML、JS、JSON、または画像ファイルに対して複数の HTTP 呼び出しを行います。この場合、Lambda@Edge は、現在呼び出されている URL を抽出する必要があります。
URL を取得したら、このファイルのファイル タイプを特定し、リージョン内の対応する S3 バケットに対して新しい HTTP 呼び出しを行う必要があります。
言うまでもなく、呼び出しが成功した場合はファイルを返し、失敗した場合は以前と同じルーティング システムを使用して、アクティブ-アクティブの高可用性システムも提供します。
このシステムを導入したことで、私たちは別のマイルストーンに到達し、エンタープライズ レベルのインフラストラクチャの基礎を築くことができました。これは開発が最も困難なシステムであり、完成までに 3 か月かかりました。
結局のところ、解決すべき問題は他にもありましたが、このシステムが再び有用であることが証明されました。
PWA は驚くべき Web テクノロジーであり、時間が経つにつれてより多くの Web サイトで使用されるようになりますが、2019 年の時点ではまだ始まったばかりです。
各コミュニティに個別のサブドメインでサービスを提供することにしたので、適切なタイトルとアプリ アイコンを使用して、ブランド化された PWA をユーザーがインストールできるようにしたいと考えました。
結局のところ、これらすべての機能を定義する PWA マニフェスト ファイルは、サブドメインに基づいて機能しません。サービス提供元のドメインに基づいて、値のセットを 1 つだけ定義できます。
CloudFront と Lambda@Edge を使用して既に HTTP 呼び出しをプロキシできたという事実は、ここでも役に立ちました。
ここでの目標は、manifest.json ファイルへの各呼び出しをプロキシすることでした。次に、呼び出し元のサブドメインに応じて、対応するコミュニティ データ (アプリのアイコン、タイトルなど) を取得するために、manifest.json にこれらの値を動的に入力します。
次に、ファイルがブラウザに提供され、コミュニティが新しい PWA アプリとしてユーザーのデバイスにインストールされます。
これらの重要なステップを理解したら、フロントエンドの作業を開始する時が来ました。以前のサブドメイン ベースの要件に沿って、サブドメインに基づいて別のコミュニティとそのデータを読み込む方法も理解する必要がありました。
これには、各コミュニティで使用されるさまざまな Web サイト レイアウトの読み込みも必要になります。
たとえば、ホームページでは利用可能なすべてのコミュニティを一覧表示する必要がありますが、他のサブドメインではそれらの各コミュニティの記事を一覧表示する必要があります。
言うまでもなく、この問題を解決するために、単純に複数の異なる Web サイトをゼロから構築することはできません。これではスケーリングできないため、代わりに、できるだけ多くのコントロールと機能を再利用する必要があります。
これらの機能は、これら 2 つのコミュニティ タイプ間で共有され、必要な場合にのみ読み込まれます。コードの再利用性を最大限にするために、すべてのコントロールを 4 つの異なるタイプとして定義しました。
<button> や <input> などの最小のカスタム HTML 要素は、コンポーネントとして定義されました。次に、これらの小さな要素のセットであるコントロールでこれらのコンポーネントを再利用できます。たとえば、プロファイル情報コントロールは、ユーザーのプロファイル画像、ユーザー名、フォロワーなどを表示します。
この場合も、これらの要素をより高いレベルの要素 (この場合はページ) で再利用できます。
各ページは基本的にルートを表します。たとえば、すべてのアクティビティを表示できる [トレンド] ページや、実際の記事のテキストが表示される [関心] ページなどです。次に、これらの小さなコントロールから各ページを構成します。
最後に、最高レベルの要素は、そのタイプに基づいてコミュニティで定義されます。各コミュニティ要素は、必要なすべての下位レベルのページを定義するだけです。
この場合、ルートを動的にロードする方法として、Aurelia Router が役に立ちました。実装は次の方法で処理されました。
サブドメインに関係なく、ウェブサイトの読み込みが開始されると、Aurelia コンポーネントとして実装される 2 つのメイン ブランチを登録します。これらは、2 つの異なるコミュニティ タイプを表しています。次に、2 つの異なる Web タイプまたはレイアウトを定義しました。
「メイン」タイプは、ユーザーがメインのページにアクセスしたときに読み込まれる Web サイトのレイアウトを単純に表します。ここでは、対応するすべてのコントロールを含むすべてのコミュニティを表示します。
一方、ユーザーがサブドメインに移動すると、別のレイアウトをロードする必要があります。つまり、コミュニティの代わりに、記事とそれに対応する機能とルートをロードします。たとえば、記事を公開および編集する機能です。
これにより、私たちがいるコミュニティの種類に基づいて、特定のルートが有効または無効になります。
Aurelia と WebPack のセットアップでは、JavaScript を適切なチャンクに分割するため、不要なルートと機能がまったく読み込まれないため、速度が向上し、帯域幅が節約されます。
この時点で、自分がいるサブドメインを特定したら、コミュニティとこの特定のコミュニティのユーザー データを読み込み、ソリューションを正常に実装しました。
デザインをできるだけシンプルに保つべきだというのが私の推論でした。したがって、ユーザーはコンテンツを求めて Web サイトにアクセスするため、二次的な機能ではなく、コンテンツの表示に重点を置きます。
記事はリストに表示する必要がありますが、古いように見えるべきではありません。したがって、各記事は単純に次のように構成することにしました。
記事のリストが古くならないようにするための主な方法は、記事の各カバー写真の縦横比をユーザーが選択できるようにすることでした。 Pinterest のピンの表示方法から着想を得たので、各記事の縦横比も異なります。
これには、CSS グリッドまたは FlexBox ですぐに選択できない石積みレイアウトを実装する必要がありました。
幸いなことに、私が試してレイアウトに使用した便利なオープンソースの実装がいくつかあります。ページ分割されたデータの読み込みや画面サイズに合わせたスケーリングなど、いくつかの改善を加える必要がありました。
その後…
2019 年 11 月、 の最初の兆候が現れ始めました。世界はすぐに混乱に陥り、何が起こっているのか誰にもわかりませんでしたが、それは世界を変え、誰も想像できない方法で私たちがお互いにどのように相互作用するかを変えました.
その後すぐに、私たちは在宅勤務を開始しました。仕事に行く必要がなくなったので、これは私の開発プロセスに大きな影響を与えるでしょう.皮肉なことに、私が必要な休憩を取っている間に、世界は崩壊しました!
開発の世界に戻ると、イマーシブ コミュニティに関する記事を書く背後にあるアイデアは、コラボレーションに基づいていました。この目的のために、共同作業の基礎としてウィキペディアを使用しました。
また、 やなどのコミュニティ Web サイトや、ブログ Web サイトも同様に役割を果たしました。
一人でブログ記事を書くのは良いスタートですが、一緒に記事を書いてもらうことでそれを超えることができます.
テキストにハイパーリンクを追加し、さまざまな人が書いたこれらの記事を接続すると、基本的に、人々が興味のあるトピックに参加するためのコミュニティが作成されます。
私は 2 種類の記事を定義することにしました。つまり、
興味は短い記事です。人が持つ可能性のある特定の興味を実際に説明する 5000 文字の長さの記事。次に、各ユーザーは、この特定の関心の評価を付けてレビューを書くことができます。
メインの関心ページには、この特定の関心のために書かれたすべてのレビューへの参照が保持されます。主な違いは、誰でも Interests を編集できるが、Review を作成した人だけがそれを編集できるため、各記事に個人的なタッチを追加できることです。
ここで、CloudFront を使用して AppSync 呼び出しをプロキシするという以前の決定が、私たちを苦しめました。 ことが判明したため、それより長いデータを保存することはできません。
各記事に関する限り、それぞれの興味は好きでコメントすることができます.したがって、ユーザーは集まって、各記事の作成方法と編集方法について話し合うことができます。さらに、すばやくアクセスできるように、各興味をユーザーのプロファイル ページに追加することもできます。
これらすべての機能が整うと、年末がやってきました。状況は良さそうで、プロジェクトは来年完了すると確信していました。控えめに言っても、この仮定は正確ではありませんでした。
今年は多かれ少なかれ順調にスタートしました。経済はまだいくらか持ちこたえていましたが、しばらくすると下降し始めました。市場はパンデミックに対応し始め、同様に価格が上昇し始めました。
2020 年の初めは、私が多くの作業を行った年でしたが、本当に機能する製品はありませんでした。まだまだやるべきことはたくさんありましたが、結果に自信があったので、前進し続けました。
私の本業では、勤務時間も延長され、通常よりも早く締め切りに間に合わなければなりませんでした。言うまでもなく、スケジュールを再編成する必要があり、さらに時間を節約する唯一の方法は、毎晩 4 時間だけ寝ることでした。
アイデアは、午後 6 時か 7 時までに帰宅して、すぐにプロジェクトに取り掛かるというものでした。その後、午前 3 時か 4 時まで働き、それから寝ることができました。その後、午前 7 時頃に起きて、すぐに本業に取り掛かる必要がありました。
もちろん、これは毎晩十分な睡眠ではありませんが、週末に12時間寝ることでその時間を補うことができると考えました.また、すべての休暇日と祝日も仕事のためにスケジュールしました。
新しいシステムがセットアップされ、計画どおりに進みました。
記事を書く Web サイトには、使いやすいテキスト エディターが必要であることは言うまでもありません。 2020 年初頭、 はテキストを書くための非常に人気のある方法として登場しました。 Immersive Communities はすぐに使用できるようにする必要があると判断しました。
これには、Markdown を記述するだけでなく、HTML として表示することも必要です。 ライブラリは、Markdown を HTML に変換するために使用されます。ただし、追加の要件があったため、表示する必要があるさまざまなメディアの完全なリストは次のとおりです。
さらに、画像や動画は、ユーザーが Instagram のように画像をスワイプできるスライダーとして表示する必要があります。これには、Markdown とその他の HTML 要素を組み合わせる必要があります。
エディターは、テキスト フィールドとメディア フィールドの 2 種類の入力があるいくつかの部分に分割されます。エディターの各フィールドは上下に移動でき、 を使用して実装するのは非常に簡単でした。
入力フィールドに関して言えば、Markdown フィールドは <textarea> 要素で作成できるほどシンプルでした。エディターは、入力中のテキストにタイプライターの外観を与えるも読み込みます。
さらに、実際にテキストのスタイルを設定するために、テキストに Markdown を追加するバーが実装されました。 を使用してキーボード ショートカットを使用して同じことを行いました。これで、Control+B などを使用して ** Markdown タグの形式で太字のテキストを簡単に追加できます。
入力中、テキストの量が増えると <textarea> 要素が拡張されるのは自然なことなので、 ライブラリを使用してこの機能を実装しました。
メディア フィールドは、埋め込まれた Web サイトを含む画像、動画、または iframe のいずれかを表示できます。メディア フィールドのタイプは、メディア自体のタイプに基づいて切り替わります。 を使用して、画像間のスワイプを実装しました。
ビデオ コンポーネントは、 ライブラリを使用して実装されました。
実際にメディアをアップロードするときに問題が発生し始めました。画像に関する限り、ブラウザーのを使用してデバイスから写真やビデオを読み込むのは簡単でした。次にやらなければならなかったことは、最初に、HEIC 形式だった可能性のある画像を JPEG に変換することでした。
次に、バックエンドにアップロードする前に圧縮します。幸いなことに、 およびライブラリがこの目的を十分に果たしました。
アップロードする前に、正しい画像の縦横比を選択してトリミングする必要があったときに、別の問題が発生しました。これはを使用して実装されましたが、残念ながら、Safari ブラウザーではそのままでは機能しませんでした。
画像がコンテナーからオーバーフローしないように、適切な CSS を設定するのにかなりの時間を費やしました。最終的に、ユーザーは自分のデバイスから画像を簡単にロードし、ズームインおよびズームアウトし、アップロードする前に画像をトリミングすることもできます。
すべてが完了すると、メディアは、メディア ファイルを管理するためのサービスである Cloudinary にアップロードされます。
これらすべてをまとめて、記事の形でユーザーに表示するときが来ました。幸運なことに、Aurelia には HTML を動的にロードできる <compose> 要素があります。
したがって、入力の種類に応じて、HTML に変換されるメディア要素または Markdown 要素のいずれかを読み込みます。
次に、この HTML を CSS でスタイル設定する必要があります。特に、画面サイズに応じて変換する HTML テーブルです。大きな画面ではテーブルは通常の水平レイアウトで表示され、小さな画面では垂直レイアウトで表示されます。
これには、画面サイズがいつどのように変化するかを知らせるイベント駆動型のアプローチが必要になります。この場合に使用する最適なライブラリは、「サイズ変更」イベントを処理するであり、それに応じてテーブルをフォーマットすることができました。
その後、記事に戻りました。複数の人が同時に記事を変更する可能性があるため、記事をデータベースに保存する方法を変更する必要がありました。
次に、新しい記事を最初の記事タイプとして保存しますが、各記事の実際のデータはバージョンとして保存します。その後、どのユーザーがいつ各記事を変更したかを追跡できます。
これにより、ユーザーが最初に最新バージョンをロードしなかった場合に、新しいバージョンが保存されないようにすることができました。また、特定の更新が不適切である場合、その更新が無効になり、以前のバージョンが再び表示される可能性があります。各記事の下書きも同様に保存されます。
実際のデータ入力に関しては、ポップアップとして実装することにしました。ポップアップ自体は単に画面に表示されるのではなく、下から上にスライドします。さらに、ポップアップ内でスワイプすることも可能です。
この目的のために、Swiper.Js ライブラリを再利用しましたが、他のすべてのアニメーションはライブラリを使用して行われました。
ポップアップは、画面サイズに合わせてスケーリングする必要があったため、実装が簡単ではありませんでした。したがって、大きい画面では画面幅の 50% を占め、小さい画面では幅の 100% を占めます。
さらに、場合によっては、フォロワーのリストのように、スクロールをポップアップ内に含めるように実装しました。これは、スクロールしていたリストが一番上で止まらず、スクロールすると消えてしまうことを意味します。
また、スタイルをさらに追加し、背景を薄暗くし、ポップアップの外側でスクロールまたはクリックを無効にしました。一方、記事編集システムのプレビューポップアップは画面と一緒に動きます。
これは、Apple のショートカット アプリとそのポップアップの表示方法に触発されたもので、要素の上のピル ボタンとタイトルにも当てはまります。
私が実装した最も重要な UI 機能の 1 つは、さらに iPhone から着想を得たもので、そのナビゲーション バーです。私は、ほとんどすべてのモバイル アプリにかなり基本的なナビゲーション バーがあり、シンプルで小さなアイコンがアプリケーションの全体的なデザインに実際には収まらないことに気付きました。
iOS バーを単純に複製し、それを Web サイト全体で使用することにしました。言うまでもなく、常に表示されている必要はありませんが、下にスクロールすると消え、上にスクロールすると表示されます。
ユーザーが下にスクロールしているとき、彼はコンテンツに興味を持っており、現在のページから移動しないと想定されるため、バーを非表示にすることができます。
一方、ユーザーが上にスクロールしている場合、ユーザーはページを離れる方法を探している可能性があるため、バーを再度表示することもできます。
バーには 4 つのボタンがあり、ユーザーは Web サイトの 4 つの主要部分に移動できます。ホーム ボタンは、各コミュニティのホームページに移動します。 [トレンド] ボタンをクリックすると、他のユーザーが投稿した最近のアクティビティをすべて表示できる [トレンド] ページに移動します。
次のボタンは、コミュニティが提供するすべての機能と設定のリストに移動する [参加] ボタンです。最後に、[プロフィール] ボタンをクリックすると、プロフィール ページに移動します。
大画面化も視野に入れる必要があったので、実際に大画面で表示するとバーが画面右側に移動してしまいます。その時点でベタベタしてどこにも動かない。
フロントエンドで最も重要な作業が完了したら、もう一度バックエンドにアクセスします。システムのこの部分は、実装が最も複雑な部分の 1 つであることが証明されますが、最終的には非常に重要であり、他の機能への移行も非常に簡単になります。
にはの概念が存在します。この概念では、関数をシンプルに保ち、関数が本来の目的である 1 つのことだけを実行できるようにします。
さらに、の考え方は、ビジネス ロジックを他の分野横断的な関心事から分離する必要がある、特に関心事の分離に関するものです。
たとえば、ユーザーをデータベースに保存すると、ユーザーの保存が処理されている間、当然ながらログが記録されます。ただし、これら 2 つの機能のコードは別々にしておく必要があります。
この推論を全面的に適用し、ユーザーにとって重要ではない UI から可能な限り多くの機能を抽出し、それらをバックエンドに移動することにしました。
私たちの場合、コミュニティ、記事、コメント、いいねなどに関連するデータをデータベースに保存することに主に関心があります。
記事のいいね数を追跡したい場合は、各記事のいいね数をすべてカウントし、定期的に更新するプロセスを作成できます。
ここでは、データベースに格納されている大量のデータと、データベースに絶えず流れている可能性のある大量のデータを扱っているため、この状況を処理するには、リアルタイムのデータ処理を採用する必要があります。
このタスクには AWS Kinesis を選択しました。 Kinesis は大量のリアルタイム データを取り込むことができ、SQL クエリを記述してこのデータをほぼリアルタイムでクエリおよびバッチ処理することもできます。デフォルトでは、Kinesis は 60 秒間、またはバッチが 5 MB に達するまで、データをバッチ処理します。
したがって、私たちの場合、受信データ、意味、新しいコミュニティの作成、記事、ユーザー、アクティビティなどの追加または削除をクエリし、新しいデータで毎分データベースを更新します。現在発生している問題は、そもそもデータをどうやって Kinesis に取り込むかということです。
私たちが選んだデータベースである DynamoDB は、実際には、データが追加、削除、または変更されるたびに、Lambda 関数の形式で呼び出されるトリガーを定義できます。次に、このデータをキャッチし、Kinesis に送信して処理します。
1 つのデータベースを扱っているのではなく、実際には 10 のデータベースを扱っているため、以前の決定の 1 つにより、このプロセスの実装が少し難しくなります。
したがって、データが追加されると、Lambda 関数は 1 回ではなく 10 回呼び出されますが、それぞれのケースを処理する必要があります。データは異なるリージョンにあるため、任意のデータベースから取得される可能性があるからです。
ユーザーがデータベースに追加した元のデータではなく、コピーされたデータを除外することで、この問題を解決しました。
「aws:rep:updateregion」列はこの情報を提供し、データが挿入されたリージョンのデータを扱っているのか、それともコピーされたデータを表しているのかを判断できます。
この問題が解決したら、新しいデータの追加または削除のいずれかを単純にフィルタリングします。さらに、そのタイプに基づいてデータをフィルタリングします。つまり、コミュニティ、記事、コメントなどを表すデータを扱っています。
次に、このデータを収集し、「INSERT」または「DELETE」としてマークして、Kinesis に渡します。アプローチからのこれらのアイデアはドメイン イベントと呼ばれ、どのアクションが発生したかを判断し、それに応じてデータベースを更新することができます。
次に、Kinesis に注目します。ここでは、システムの 3 つの主要部分を定義する必要がありました。
Kinesis Streams を使用すると、大量のデータをリアルタイムで取り込むことができます。 Kinesis Analytics は、このデータを実際にバッチでクエリし、ローリング タイム ウィンドウに基づいて集計できるシステムです。
データが集約されると、各結果をさらに Kinesis Firehose にプッシュします。これにより、大量のデータを処理して宛先サービス (この場合は JSON 形式の S3 バケット) に保存できます。
データが S3 バケットに到達したら、別の Lambda 関数をトリガーし、このデータを処理して DynamoDB データベースを更新します。
たとえば、最後の 1 分間に 5 人のユーザーが興味を気に入った場合、JSON ファイルでこのデータを見つけることができます。次に、この Interest のいいね数を更新し、いいね数を増減します。この場合、いいねが 5 つ増えるだけです。
このシステムを使用すると、すべてのコミュニティの統計が 1 分以内に最新の状態に保たれます。
さらに、正確な結果が各レコードの高速 DynamoDB データベースに保存されるため、集計データを表示する必要があるときに複雑なクエリを作成して実行する必要がなくなり、各レコードのクエリ速度が向上します。
この改善は、データの局所性の考え方に基づいています。
必要な機能を処理するサードパーティ サービスの実装を開始する時が来ましたが、自分で構築するよりもサブスクリプションを購入する方が簡単でした。最初に実装したサービスは、メディア管理のサービスである Cloudinary でした。
Cloudinary のすべてのプリセットをセットアップして、次のレスポンシブ スクリーン ブレークポイントの画像を熱心に変換しました。
これらは、携帯電話、小型タブレット、大型タブレット、およびコンピューター モニターのさまざまな画面サイズに Web サイトが適合する Tailwind CSS に設定されたブレークポイントにもなります。
次に、現在の画面サイズに応じて、<image> 要素のscrcset属性を使用して Cloudinary から熱心に作成された画像を適切に呼び出します。
これにより、帯域幅を節約し、モバイル デバイスで画像を読み込む時間を短縮できます。
動画機能に関しては、実装後、Cloudinary での動画の価格が高すぎたため、削除することにしました。そのため、コードは存在しますが、その機能は現在使用されていませんが、後で使用可能になる可能性があります。
これにより、将来的に AWS でカスタム システムを構築する必要があります。
Embed.ly を使用して、Twitter や YouTube などの人気のある Web サイトからコンテンツを埋め込むことにしました。
残念ながら、これは問題なく機能しませんでした。Facebook と Twitter のスクリプトを Web サイトから手動で削除するには、いくつかの手法を使用する必要がありました。埋め込みコンテンツが複数回読み込まれると、これらのスクリプトが干渉する可能性があるからです。
検索に関しては、Algolia を選択し、コミュニティ、アクティビティ、記事、およびユーザーの検索を実装しました。フロントエンドの実装は十分に単純でした。
クリックするとアプリケーションの残りの部分が非表示になり、入力中には現在閲覧している特定のサブドメインの結果が表示される検索バーを作成しただけです。
「Enter」を押すと、ホームページの組積造がクエリに適合する記事を表示します。言うまでもなく、Pinterest のルック アンド フィールを模倣するために、結果を段階的に読み込むページネーションも実装する必要がありました。
問題が発生したのは、テキスト全体を Algolia に保存しない限り、アクティビティを実際に検索する方法がないことに気付いたときでした。これは回避したかったのです。したがって、各アクティビティに関連するタグのみを保存することにしましたが、問題は、各アクティビティから関連するタグをどのように抽出するかでした。
その答えは、 との形でもたらされました。データベースに追加される項目の量が多く、このデータを Algolia に追加したいので、各レコードを個別に追加すると、API が過負荷になる可能性があります。
代わりに、それらをリアルタイムでバッチで処理したいので、Kinesis をソリューションとして再度採用します。
この場合、データベースに新しいアイテムが追加されるたびに、そのデータを Kinesis Data Streams に送信する Lambda 関数がトリガーされ、次にそのデータが Kinesis Firehose に送信され (今回は Analytics は必要ありません)、さらに保存されます。 S3 バケットで。
データが安全に保存されたら、それを Algolia に送信する Lambda 関数をトリガーしますが、その前に、このデータを処理する必要があります。
特に、 ライブラリを使用してマークダウン テキストを削除するアクティビティを処理する必要があります。その後、平文が残ります。実際のテキストを取得したら、検索に使用される関連タグの抽出に進むことができます。
これは AWS Comprehend サービスを使用して簡単に実行できますが、問題は、一部の言語しかサポートしていないことです。したがって、ユーザーがサポートされていない言語で書いている場合、タグを抽出することはできません。
この場合、AWS Translate を使用してテキストを英語に翻訳するだけです。次に、タグを抽出し、元の言語に翻訳します。
これで、意図したとおりにタグを Algolia に格納するだけです。
Pinterest の最も重要な機能の 1 つは、そのレコメンデーション エンジンです。ユーザーがピンをクリックすると、すぐにそのピンのフルサイズの画像が表示されます。画像の下には、現在のピンに基づいて、ユーザーが好むと思われる推奨事項が表示されます。
これは、ユーザーの維持率を高め、ウェブサイトの閲覧を継続させるための非常に優れた方法です。私の場合、同様の記事をユーザーに表示する必要があるこの機能を実装するために、Recombee を選択しました。これは、レコメンデーション エンジン SaaS です。
同じ原則を再利用したため、今回の実装は Algolia とは対照的に簡単でした。ユーザーによって作成された新しいアイテムごとに、コミュニティ、記事、およびアクティビティをどのように推奨する必要があるかを見て、Kinesis を使用してこれらのアイテムをバッチ処理し、Recombee に送信します。
推奨プロセスはビューに基づいています。つまり、ユーザーが記事を見るたびに、この特定のユーザーと記事のこのビューを Recombee に送信します。
ユーザーがアイテムとどのようにやり取りするかに基づいて、Recombee のアイテムに他のアクションを割り当てることもできます。たとえば、新しい興味を書くと、その興味のカート追加にマップされます。ユーザーが関心を気に入った場合、これは評価に追加されます。
ユーザーがコミュニティに参加すると、これはこのコミュニティのブックマークにマップされます。
このデータに基づいて、Recombee はユーザー向けのレコメンデーションを作成します。
フロント エンドでは、ユーザーが現在読んでいる記事を取得し、この特定の記事とユーザーのレコメンデーション データを取得します。これは、ページ付けされた組積造リストとして、各記事の下部に表示されます。
これにより、ユーザーが興味を持ちそうな記事のリストが表示されます。
ウェブサイトが最初からグローバルな視聴者を対象としている様子を見て、ローカリゼーションも実装する必要がありました。最初のリリースでは、10 言語を使用することに決め、 に基づいて実装された Locize という SaaS サービスに落ち着きました。
単数または複数を意味する量に基づいて単語をローカライズする必要があり、時間もローカライズする必要があります。各記事が作成された時刻または最後に更新された時刻をどのように表示しているかを見てみましょう。
デフォルトの言語として英語を選択し、Google 翻訳を使用してすべての単語をドイツ語、日本語などの他の言語に翻訳しました。Aurelia がローカリゼーションもサポートしていることは非常に便利です。
すべての翻訳が完了したら、翻訳された JSON ファイルをアプリケーションにインポートし、コミュニティ タイプに基づいて分割して、使用されない不要なテキストを読み込まないようにしました。
次に、Aurelia を使用すると、テキストを自動的に翻訳するテンプレートとバインディングを簡単に使用できます。しかし、実際の日付を表示するのではなく、記事が書かれてからの経過時間を表示するために、時刻をフォーマットする Value Converters も使用しました。
さらに、数値もフォーマットする必要があったため、数値 1000 を表示する代わりに、1K を表示しました。これらの機能はすべてやなどのライブラリによって処理されました。
コミュニティ Web サイトにはコミュニケーションが必要ですが、公の場だけではありません。プライベートなコミュニケーションも必要です。これは、リアルタイムのプライベート チャットも提供する必要があることを意味していました。この機能は、Twilio Programmable Chat サービスを使用して実装されました。
すべてのユーザーは、特定のコミュニティ内の他のユーザーとプライベート チャットを行うことができます。バックエンドの実装は、Twilio ライブラリを使用して簡単に実装できました。フロントエンドに関しては、きれいでシンプルなデザインだったので、Instagramをベースにチャットをスタイルすることにしました.
また、Web サイトを事前にレンダリングして、検索エンジンのクローラーが利用できるようにするために、 というサービスを選択しました。価格が気になるかもしれないことに気付いた後、実際にプリレンダリング システムを自分で構築することにしました。
この目的のために、ヘッドレス Chrome API であるというライブラリを見つけました。
このライブラリを使用して、Web サイトをプログラムでロードし、JavaScript を実行して生成された HTML を返すことができましたが、当時の検索クローラーはこれを行いませんでした。この実装では、Web サイトをロードしてレンダリングし、HTML を返す Lambda 関数で Puppeteer をロードします。
Lambda@Edge を使用して、ユーザーが実際にクローラーであることを検出し、それをレンダリング前の Lambda に渡します。これは、CloudFront パラメータの「user-agent」属性を検出するだけで簡単に実行できました。実際には、Lambda は Puppeteer ライブラリが大きすぎるためにロードできなかったことが判明しました。
その後、 ライブラリを見つけたので、これはショーストッパーではありませんでした。このライブラリは、このすべての作業をすぐに実行でき、私の目的に必要な Puppeteer コアのみを使用するため、はるかに小さくなります。
システムが完成すると、検索エンジンはすでに十分に強力になり、JavaScript も実行し始めました。したがって、この機能を完成させたにもかかわらず、それをオフにして、検索エンジンが自分のウェブサイトを勝手にクロールできるようにしました。
イマーシブ コミュニティのコア機能の 1 つは、ユーザーがメンバーのサブスクリプションと広告収入の 50% を共有する収益分配スキームです。
前述のように、クリエイターがコンテンツを作成するだけでなく、収益化できるようにする必要があります。問題は、このシステムをどのように実装するかでした。言うまでもなく、デフォルトの選択は Stripe だったので、次のように進めました。
各コミュニティに基づいて収益分配システムを設計することにしました。このようにして、ユーザーは複数のコミュニティを作成し、各コミュニティに基づいて収入を得ることができます。各コミュニティの収益は 2 つのソースから得られます。
メンバー サブスクリプションは、実装が最も簡単でした。メンバー サブスクリプションには、毎月 5 ドル、10 ドル、15 ドルの 3 つの価格設定を作成します。各コミュニティのメンバーは、コミュニティの所有者を毎月サポートすることができ、その代わりに広告は表示されません。
広告システムは、同じ月額サブスクリプションに基づいていましたが、月額 $100 から $1000 の範囲でした。特定のコミュニティに広告を掲載したい企業は、毎月の支払い額を選択し、広告バナーを設定するだけです。
1 つのコミュニティに複数の広告主がいると仮定すると、広告は、ページの読み込みやルートの変更ごとにランダムに選択されます。毎月の支払い額を増やすことで、広告主が他の広告主と比較して広告を表示する頻度を増やす方法。
また、広告主に広告のパフォーマンスを示す必要があるため、ビューとクリックの両方を測定するために Kinesis セットアップを再度採用しました。その後、このシステムは通常どおり統計を更新し、Brite Charts ライブラリを使用して統計を表示しました。
最も重要な部分は、実際の収益分配機能でした。これは、 機能によって簡単に実装されました。ユーザーは自分の銀行口座を追加して Stripe Express に接続するだけで、支払いの送信に必要なすべての情報がシステムに提供されます。
次に、すべてのユーザーを毎日取得してトランザクションを更新し、各トランザクション (メンバーのサブスクリプションまたは広告の支払い) の 50% が、支払いが行われるコミュニティの所有者に転送されるようにスケジュールされた Lambda システムを用意します。作る。
最後に実装する必要があったサービスは、ユーザー認証に役立つた。いくつかの調査の後、SMS メッセージに基づいて、パスワードのない設定を行うことにしました。
現在、モバイル ファーストの世界にいることを考えると、パスワードを忘れて、誰もが既に持っているもの、つまり携帯電話に基づいて認証を行うことは理にかなっています。
パスワードレス認証の Auth0 実装は、毎回 Web サイトにリダイレクトされ、避けたかった URL パラメーターに基づいているため、最適ではないことが判明しました。
価格もソーシャル ネットワークのようなものには対応できないため、AWS Cognito を使用して独自の実装を構築することにしました。
Cognito には、認証をトリガーするために使用した Lambda 関数に接続できるトリガーがあることが非常に便利でした。 Lambda 関数は、サインアップ中にユーザー データを収集するために使用されます。
この時点で、ユーザーは自分の電話番号とユーザー名を入力するだけで登録できます。
ログイン手順中に、Lambda 関数はユーザーの電話番号を収集し、 を使用して、検証コードを含む SMS メッセージをユーザーに送信します。
ユーザーはこのコードを入力するだけで Cognito を介して確認され、自分のプロファイル ページにリダイレクトされます。
もちろん、ユーザーが承認され、検証データがフロントエンドに返されたら、データを保存する前に暗号化する必要があります。バックエンドに保存される前に、同じ承認データが暗号化されます。
また、サインアップとログインのたびに、ユーザーの IP を保存します。
後で、ユーザーが実際に携帯電話番号を教えることで問題を抱えていることが判明したため、SMS を電子メール メッセージに置き換えることにしました。
を使おうとしたらメッセージが重複する問題があったので、ユーザーにメールを送るために Twilio のに切り替えました。
このシステムが完成したことで年が明け、私が 2 年前に開始したプロジェクトは完成にはほど遠い状態でした。作業を続行し、できるだけ早く完了するようにする以外に選択肢はありませんでした。最大の課題がまだ来ていないことを、私はほとんど知りませんでした。
ここですべてがうまくいく必要がありましたが、これほど長い間フィードバックなしでソロ開発者として働いていると、プロジェクトの方向性に疑問を抱くようになります.
現在同じ場所にいる開発者なら誰でも、次の質問を自問するかもしれません。
終わりが見えなくても、どうすればやる気を維持し、続けることができるでしょうか。
答えは非常に簡単です。
プロジェクトについて現在どのように感じているかに関係なく、自分の決定に疑問を呈するべきではありません。現在の感情的な状態で、自分がどのように行動するかを決定することはできません。
今は続けたくないと思うかもしれませんが、後でやりたいと思うかもしれません。
したがって、もしやめてしまったら、もうプロジェクトはなくなり、すべての作業は無駄になります。ですから、何が起ころうとも、ただ前に進み続けるしかありません。
現時点で心に留めておくべき唯一のことは、提供されるすべての機能、キーボードを押すたびに、目標に近づくということです。
このプロジェクトの間、私は実際に 3 回転職しましたが、そのたびにかなりの負担がかかりましたが、就職の面接に行かなければならないにもかかわらず、家に帰って机の後ろに座って、プロジェクトに取り組み続けました。
もしあなたがやる気に欠けているなら、あなたが自問しなければならないことは次のとおりです。
今辞めたらどこに行くの?辞めた後にできる唯一の方法は、元いた場所に戻ることです。しかし、あなたはすでにそこに何があるかを知っています。あなたはそれがどのようなものかをすでに知っていて、それが気に入らなかったので、そもそもこの旅に出たのです。だから、あなたは今、どこにも戻ることができないという事実を知っています.あなたが進むことができる唯一の方法は、前進です。そして前進する唯一の方法は、ただ働き続けることです。
すでに述べたように、これがこのプロジェクトで得られたすべての動機であり、それはそれであるか、以前の場所に戻ることであったため、前進し続けることにしました.
物事をまとめ始める時が来ました。まず、各コミュニティを維持するために使用される管理システムを実装することにしました。各コミュニティ所有者は、コミュニティ内のコンテンツの削除について決定を下すことができます。
これは、広告、記事、および活動を無効にし、ユーザーの行動が行動規則に沿っていない場合、ユーザーを禁止できることを意味します。
各コミュニティの所有者は、他のユーザーにも管理者権限を与えることができます。ただし、メイン コミュニティの管理者が他のすべてのコミュニティを管理できるようにする必要もあります。
さらに、これらの管理者は、すべてのコミュニティの他のユーザーを完全に無効にしたり、コミュニティ全体を無効にしたりすることもできます。
管理者が仕事をしやすくするために、各項目を管理者に報告できるフラグ システムを導入しました。ユーザーは、ウェブサイト上で不適切と思われるものを報告できるようになりました。
各ユーザーの権限の実際の検証は、バックエンドで決定されます。各リクエストを検証する各 AppSync 呼び出し内で呼び出される Lambda 関数を作成するだけです。
さらに、フロントエンドは、Aurelia が提供するルーティングベースの承認を使用します。現在のユーザーが特定のルートに進むことを許可または禁止するルールを定義するだけです。
たとえば、特定のコミュニティから禁止されている場合、自分のプロフィールを見ることができません。しかし、このシステムを使用して、ログインしていないユーザーがプロファイル ページに移動するのを防ぎ、代わりにログイン ページにリダイレクトすることもできます。
ユーザーにとって便利なもう 1 つの機能は、Analytics ダッシュボード ページです。各コミュニティ所有者は、自分のコミュニティで発生しているインタラクションの正確な量を示すグラフを見ることができます。
この特定のケースでは、Kinesis によって集計されたデータを再利用し、 ライブラリを使用してグラフで表示します。
さらに、Stripe データも取得して、このコミュニティの加入者数、広告主数、および総収益を表示します。
解決しなければならなかった唯一の問題はレスポンシブ デザインでした。これは、小さな画面と大きな画面の両方にグラフを表示する方法を意味します。ここでも、RxJ を使用して「サイズ変更」イベントを検出し、Tailwind CSS で定義された画面ブレークポイントに基づいてスタイルを適用しました。
追加レベルのセキュリティもロードマップにあり、CloudFront ディストリビューションの前に WAF を実装することにしました。
私は AWS Marketplace を使用し、 システムにサブスクライブしました。これはトラフィックをプロキシし、安全であると検証されたトラフィックのみを許可するようにします。
このソリューションは非常に簡単に実装できましたが、最初の月が終わると、請求額が多すぎて処理しきれなくなったため、システムを切断し、CloudFront がデフォルトで提供するものに頼ることにしました.
この時点で、これまでに行ったことをすべて確認し、まだ残っている小さな問題の修正を開始する必要がありました。まだ多くのことを磨く必要がありましたが、変更しなければならなかった最大のことは、DynamoDB データベースのセットアップでした。
私の初期設定は、現在使用しているものではなく、うまくスケーリングできなかったことがわかりました。これが、完全に再設計し、「#」セパレーターを使用してレコードの識別子の分岐を示すことにした理由です。
以前は、個別のレコードを作成し、AppSync パイプラインを使用して関連する各レコードを見つけていましたが、これは明らかに持続不可能でした。これは、Kinesis および Algolia や Recombee などのサードパーティ サービスのセットアップにも影響を与えました。
その結果、システムが適切に機能するように完全に再設計するのに 3 か月かかりました。これが完了すると、新しい機能を再び使用できるようになります。
東京の夏は高温多湿です。特に 7 月と 8 月は、自分がしていることをすべて把握するのは非常に困難です。
その間、東京ではオリンピックが開催されており、8月7日にと報じられました。
言うまでもなく、電車で仕事に行くのはもはや意味がありません。天気があまりにも疲れ果てて、疲れ果てて夕方に仕事をすることができなくなるからです。代わりにタクシーで通勤することで、もう少し時間を節約する必要があることに気付きました。
これにより、眠る時間が増え、家に帰ってから疲れすぎて仕事ができなくなりました。
PWA は優れたテクノロジであり、プッシュ通知を使用してユーザーに通知を送信する方法を提供してくれます。これも必要とされるシステムだと判断し、導入を進めました。
通知システムは、フォローされているユーザーに基づいて実装されます。ユーザーをフォローしている場合、そのユーザーが新しいアクティビティや記事を作成したときに、通知を受ける必要があります。
現在、プッシュ通知に関する唯一の問題は、この記事の執筆時点では、iOS デバイスの Safari ブラウザーではまだサポートされていないことです。ネイティブのプッシュ通知の代わりに、ブラウザーのを使用することにしました。
バックエンドでは、 の新しいインスタンスを作成し、リアルタイム データを処理するようにセットアップしました。
フロントエンドでは、 を使用して API Gateway に接続します。フォローされているユーザーが新しい記事を公開すると、このデータが Kinesis に送信されます。ここでも、バッチ処理を使用して、作成者をフォローしているすべてのユーザーを取得し、API ゲートウェイを使用して通知をフロントエンドに送信します。
フロント エンドでは、WebSocket 接続がトリガーされ、これを使用してブラウザーの通知 API を呼び出し、通知を表示します。
さらに、ユーザーが各記事に書き込むことができるコメントに関しては、ユーザーが現在議論している場所を追跡して表示する必要があります。
また、どのコメント セクションにユーザーがまだ読んでいない新しいコメントがあるかを示す未読インジケーターも実装しました。
これは、ユーザーが AppSync 呼び出しを呼び出すときにawaitキーワードを使用せずにアプリケーションをロードするときにチェックされます。これにより、実行が呼び出しの完了を待たずに、より重要なデータが最初にロードされるようになります。
呼び出しが返ってきたら、UI を更新してユーザーに通知を表示するだけです。
また、ポップアップの形式で通知を使用して、アクションが正常に完了したかどうかをユーザーに知らせます。
たとえば、記事の更新に失敗したかどうかをユーザーに知らせるポップアップ メッセージを作成します。
バックエンドの検証がどのように完了したかを見て、ユーザーに迅速なフィードバックを提供するためにフロントエンドに検証を実装することで、ユーザーにさらに優れたエクスペリエンスを提供する必要がありました.
ありがたいことに、Aurelia には検証プラグインがあり、流動的なインターフェイスで適切に実装されています。これにより、たとえば、ユーザーが記事名の <input> フィールドに入力できる文字数を制限するビジネス ルールの作成が非常に簡単になりました。
Aurelia プロパティ バインディング システムを使用して検証メッセージを収集し、UI に表示します。さらに、これをローカリゼーション システムに組み込み、メッセージが正しい言語で表示されるようにする必要があります。
今年の残りは、細かい部分の作業でした。プレースホルダーのロードなどを作成する必要がありました。特に、読み込み中のプレースホルダーを個別の画面要素として表示したくないと決めました。
代わりに、要素が読み込まれていることをユーザーに示したかったのです。そのため、ロード中の要素のアウトラインを使用し、代わりに透明なロード アニメーションを使用しました。これは、同じように機能する Netflix モバイル アプリに触発されたものです。
この時点で年末になり、メインのホームページに取り組んでいました。このページには、現在所有しているすべてのコミュニティのみが表示されます。幸いなことに、以前に作成したコンポーネント ベースのシステムにより、作成したコードのほとんどを非常に簡単に再利用できるようになったため、タスクはすぐに完了しました。
やっとその年が終わり、やり遂げた仕事に満足しました。プロジェクトはまだ完了していませんが、成功は手の届くところにあることはわかっていました。
今年は最終年になる予定でした。やりたいことを実際にすべて実装できるかどうかはわかりませんでしたが、何が起こっても実行しなければならないことはわかっていました。
昨年よりもさらに暑くなる可能性が高かったので、昨年のように夏の間に作業を繰り返したくありませんでした。
私の予想は的中ことが判明しました。
ランディングページのデザインから始めました。質問は以下でした。
ユーザーがランディング ページにアクセスしたときにどのように感じてもらいたいですか?
私は、これがウェブサイトにとって深刻すぎるとユーザーに感じさせたくはありませんでしたが、むしろ友好的で協力的なコミュニティでした.
最近、ランディング ページに実在の人物の写真ではなくイラストが掲載されていることに気付きました。というわけで、 で購入したイラスト集に決めました。
ランディング ページはシンプルである必要があり、ウェブサイトが提供するものすべてをすばやく説明する必要があります。これもローカライズする必要があったので、ローカライズ機能を使って、表示されているランディング ページのタイトルと字幕をすべて翻訳しました。
克服しなければならなかった唯一の技術的な問題は、テキスト内に色を導入する方法でした。幸いなことに、翻訳定義内でスタイリング機能を使用し、Markdown を使用して、ランディング ページに表示される HTML を動的に生成することができました。
「プライバシー ポリシー」や「利用規約」などの必要なデータはオンラインで購入し、Google 翻訳を使用して複数の言語に翻訳しました。
すべてのルーズエンドを結び付ける時が来たので、残りの時間をバックエンドのすべてのラムダ関数にログが存在することを確認することに費やしました.これは、問題が発生した場合に何が起こっているのかを確認するのに役立ちます.
私が書き終えた頃にはが始まっていました。これにより、世界経済の不確実性が再び高まりましたが、私は仕事を続け、最終的な目標に集中し続けました.
PWA の実装を最新の状態に保っていなかったので、すべての機能が機能していることを確認する必要がありました。そのため、JavaScript と画像キャッシュを改善するためにさらに開発が必要でした。
オフライン機能がついにオンになり、アプリケーションはオフライン アプリとして適切に動作するようになりました。
また、バックエンドで行った変更を移動し、実際に AppSync で行った変更を他のリージョンに広める必要がありました。開発中にそれを行うのは面倒なので、開発を開始して以来、他のリージョンには変更を加えていません。
環境についても同様です。 3 つの環境すべてを常に構築するのは時間がかかりすぎたので、最終的にすべての環境を同期し、コードを UAT と本番環境に移行するのに時間がかかりました。
最後に、//immersive.community ドメインを実装する必要がありました。これは、「www」サブドメインなしで機能し、ホームページに正しくリダイレクトする必要があります。
この時点で、私たちは2022 年 4 月 25 日の早朝にいました。私の4年間のプロジェクトがついに終わりました。ウェブサイトで最初の投稿を作成し、眠りについた。私はついに成功したことを知っていました。やろうとしていたことをやり遂げただけでなく、夏が来る前にやり遂げました。
皮肉なことに、私の冒険の最後の言葉は、これは終わりではなく、始まりにすぎないということです.システムが稼働した今、作成する必要があるコンテンツと、ブランドの認知度に必要なプロモーションと広告は、まったく新しい冒険になります。
しかし、実際にこの演習から何を学んだのでしょうか?
まあ、かなりたくさん。まず、二度とやらないと自信を持って言えます。
結果に満足していないわけではありませんが、非常に満足していますが、これは一生に一度のことです。もっと上手にできるように。
個人の開発者としてエンタープライズ レベルのシステムを構築できるかどうかを知りたかったのですが、私たちが自由に使える技術スタックを使用して構築できることを示しました。
何よりも、これはサイド プロジェクトに取り組んでいる、またはサイド プロジェクトを開始しようと考えているすべての開発者への声明です。
このアプローチを他の開発者に勧めますか?絶対。それが最適な方法だからというわけではありません。
行き詰まったときに助けを求めないことは、問題を解決するための最速の方法ではありませんが、自分の限界を発見するのに役立ちます.
このようなことをやろうと決心して成功すると、その後にやろうと決心した他のすべてのことを達成するのがより簡単になることがわかります.
私の話は、あなたがどのように感じているかに関係なく、あなたが始めたことを終わらせる動機になると信じています. .
この話が刺激的だと思ったら、私のに登録してください。没入型コミュニティを構築するために使用したすべての技術について詳しく説明する、高度な「フル スタック開発」プログラミング コースを開始する予定です。
この記事で触れなかったのは、私が各問題にアプローチした方法の哲学的基盤と正当化、および各問題の解決策を分析および設計するために使用した手法です。
これは、テクノロジーやその使い方を知るだけではなく、さらに重要な要素でした。問題へのアプローチ方法と、解決策に至るまでの思考プロセスについては、YouTube 動画で詳しく説明します。
これは、開発者が実際のシステムを作成し、知識を共有する準備ができている人からプログラミングを学ぶ素晴らしい方法です。 YouTubeでお会いしましょう!
も掲載