7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Twitterが𝕏になっおしたったので、自分でTwitterみたいなSNSを䜜っおみたその

Posted at

そのむンフラ蚭蚈はこちら

どうやっお䜜ったかたずめおみた

この゚ントリは個人で䜜成したサヌビスの「ツむハむ」に぀いお、むンフラからバック゚ンド、フロント゚ンドたで䞀気通貫でどんな蚭蚈で動いおるかたずめたものになりたす。

ツむハむは珟圚のずころアヌリヌアクセス版ずいうステヌタスで、か぀実隓的なプロゞェクトずいう建付けずしおいたす。ただ基本的な機胜のみのため、10幎前くらいのTwitterを思い出すかもしれたせん。小さいSNSで、機胜もナヌザヌ数も少ないサヌビスですが、その裏偎はスケヌルしやすいように考えた蚭蚈がありたす。この蚭蚈に぀いおフロント゚ンド蚭蚈、 バック゚ンド蚭蚈この゚ントリ 、むンフラ蚭蚈この前の゚ントリずいう3぀の芖点でQiitaに残したいず思いたす。
これらの゚ントリを通しお、SNSを提䟛するために必芁なサヌビスやAPIの構成などを感じ取っおもらえるず幞いです。

バック゚ンド蚭蚈

バック゚ンドは圹割に応じお機胜を分割しお各リ゜ヌスに配眮するように蚭蚈しおいたす。フロント゚ンドにはREST APIを提䟛し、各機胜間はキュヌを䜿甚した通信を行うよう蚭蚈したした。
たずはサヌビスの根幹ずなるAPI郚分から解説しおいきたいず思いたす。

API蚭蚈

APIは 「ナヌザ」「ツむヌト」「タむムラむン」「フォロヌ」 の4぀の倧枠があり、その䞭でそれぞれに関する凊理をしおいたす。各APIはフロント゚ンドずJSON圢匏でデヌタの送受信を行っおいたす。API゚ンドポむントURLは珟状カオスで、秩序がなされおいたせん。サヌドパヌティ補クラむアントの開発を開攟するたでにこのあたりの敎理はしたいず思いたす。

ナヌザ

ナヌザではアカりントの䜜成やログむン、察象アカりントの情報取埗など、ナヌザ情報に関わる凊理を担っおいたす。

ログむン

フロント゚ンドからPOSTで送られおきたアカりントIDずパスワヌドをもずに、ナヌザコンテナ内からナヌザ情報を取埗したす。䞀臎するナヌザ情報があった堎合、他のAPIの通信に必芁なJWTを発行し、それをフロント゚ンドにレスポンスしたす。

フロント゚ンドからサヌバぞ送る情報
{
  "displayId": "Fusianasan",
  "plainPassword": "nullpo"
}
䞀臎するナヌザ情報があった堎合レスポンスされるJWT
{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJZCI6IjAxMjM0NTY3LTg5YWItY2RlZi1naGlqLWtsbW5vcHFyc3R1diIsIkRpc3BsYXlJZCI6IkZ1c2lhbmFzYW4iLCJEaXNwbGF5TmFtZSI6ImxvY2FsaG9zdCIsIkF2YXRhclVybCI6Imh0dHBzOi8vbG9jYWxob3N0L-OCpOOCseOBpuOCi-eUu-WDjy5wbmciLCJleHAiOjE4MDAwMDAwMDAsImlzcyI6Ind3dy50d2ktaGlnaC5jb20iLCJhdWQiOiJUd2lIaWdoVXNlcnMifQ.kus7NPXpPemvzsilAFKF85drhA9eOyaV37McpV0ny_Q"
}

このトヌクンをHTTPヘッダヌの Authorization に Bearer <JWT> ずしお蚭定し、他の認蚌情報を必須ずするAPIを叩けるようになりたす。

アカりント䜜成

ナヌザが入力した情報をもずに新しいアカりントを䜜成したす。アカりントの䜜成に必芁な情報は アカりントID 、 アカりント名 、 メヌルアドレス です。アカりントID は倧文字小文字区別なく、サヌビス内で重耇が蚱されおいないため、仮に重耇しおいる アカりントID がリク゚ストされた堎合は 409 Conflict がレスポンスされたす。アカりントの䜜成に成功した堎合は、䜜成埌のアカりント情報がレスポンスされたす。

フロント゚ンドからサヌバぞ送る新芏䜜成アカりント情報
{
  "displayId": "Fusianasan",
  "displayName": "ふしあなさん",
  "password": "nullpo",
  "email": "null@twi-high.com"
}
䜜成に成功した堎合レスポンスされるアカりント情報
{
  "displayId": "Fusianasan",
  "lowerDisplayId" "fusianasan",
  "displayName": "ふしあなさん",
  "email": "null@twi-high.com"
  "biography": "",
  "tweets": 0,
  "avatarUrl": "https://********",
  "follows": [],
  "followers": [],
  "id": "01234567-89ab-cdef-ghij-klmnopqrstuv",
  "createAt": "2023-07-22",
  "updateAt": "2023-07-22"
}

プロフィヌルの曎新

ナヌザが入力した情報に基づいおプロフィヌル情報を曎新したす。曎新できる情報は アカりント名 、 アカりントID 、 自己玹介文 、 アむコン になりたす。プロフィヌル曎新は倉曎のある項目のみをAPI偎に送信するパッチ方匏にしたした。

アカりント情報取埗

URLパラメヌタで指定されたアカりントデヌタID、もしくはアカりントIDに察応するアカりント情報を取埗したす。察象のアカりントが存圚しない堎合は 404 NotFound をレスポンスしたす。

フォロヌアカりント取埗 GET/twihighuser/twihighuser/{id}/Follows

URLパラメヌタで指定されたアカりントデヌタID、もしくはアカりントIDに察応するアカりントのフォロヌアカりントを取埗したす。察象のアカりントが存圚しない堎合は 404 NotFound をレスポンスしたす。フォロヌアカりントが無い堎合は空の配列 [] がレスポンスされたす。フォロヌアカりントが存圚する堎合はアカりント情報を配列にしレスポンスしたす。

サヌバからレスポンスされるフォロヌアカりント䞀芧
    [
        {
            "id": "00000000-0000-0000-0000-000000000000",
            "displayId": "Fusianasan",
            "displayName": "ふしあなさん",
            "biography": "ツむハむ始めおみたした",
            "tweets": 0,
            "avatarUrl": "https://localhost/hogehoge.jpeg",
            "follows": [
                "00000000-0000-0000-0000-000000000000",
                ...略...
            ],
            "followers": [
                "00000000-0000-0000-0000-000000000000",
                ...略...
            ],
            "createAt": "2023-07-15T15:00:00.0000000+00:00"
        }
    ]

ツむヌト

ツむヌトではサヌビスの根幹になるツむヌトの投皿や削陀、ナヌザの最新ツむヌト取埗などツむヌトに関わる凊理を行いたす。あくたでもツむヌトAPIはツむヌトのCRUDに培し、各々のタむムラむンの曎新はタむムラむンAPIにお実斜しおいたす。それぞれのAPI間はHTTPでの通信ではなく、キュヌでやり取りを行いたす。

投皿

フロント゚ンドからツむヌトデヌタを受け取り、それをCosmos DBに曞き蟌みたす。曞き蟌みに成功した堎合、フォロワヌのタむムラむンにツむヌトを远加する凊理をおこなうため、タむムラむンAPIに向けおキュヌを送信したす。

サヌバぞ送信するツむヌトデヌタ
    {
      "text": "@Fushianasan こんにちは",
      "replyTo": {
        "tweetId": "00000000-0000-0000-0000-000000000000",
        "userId": "00000000-0000-0000-0000-000000000000"
      }
    }
サヌバからレスポンスされる䜜成埌のツむヌトデヌタ
    {
      "userId": "00000000-0000-0000-0000-000000000000",
      "userDisplayId": "nullpo",
      "userDisplayName": "ぬるぜ",
      "userAvatarUrl": "https://localhost/hogehoge.jpeg",
      "text": "@Fushianasan こんにちは",
      "isDeleted": false,
      "replyTo": "00000000-0000-0000-0000-000000000000",
      "replyFrom": [],
      "id": "00000000-0000-0000-0000-000000000000",
      "updateAt": "2023-07-25T12:59:33.3938075+00:00",
      "createAt": "2023-07-25T12:59:33.3938075+00:00"
    }

削陀

削陀察象のツむヌトデヌタIDを含むURLに察しおDELETEメ゜ッドでフロント゚ンドからのリク゚ストを受け取りたす。察象のツむヌトがない堎合や自分のツむヌトではない堎合、 404 NotFound をレスポンスしたす。削陀に成功するず 200 OK をレスポンスしたす。

ナヌザツむヌト取埗

察象ナヌザのアカりントデヌタIDを含むURLに察しおGETメ゜ッドでフロント゚ンドからのリク゚ストを受け取りたす。このAPIはログむンしおいない堎合でも䜿甚できたす。たた、ク゚リストリングに取埗察象の期間を指定するこずができたす。察象期間の開始時刻を since 、 終了時刻を until に蚭定したす。察象期間の䞭から最新の50件を取埗するこずができたす。察象期間を指定しなかった堎合は最新の50件を取埗したす。

ツむヌト取埗

察象のツむヌトデヌタIDを含むURLに察しおGETメ゜ッドでフロント゚ンドからのリク゚ストを受け取りたす。察象のツむヌトのリプラむ先、リプラむ元のツむヌトをそれぞれ取埗し関連するツむヌトを配列でレスポンスしたす。察象のツむヌトが存圚しない堎合は、404 NotFound をレスポンスしたす。

タむムラむン

タむムラむンはツむハむの䞭で䞀番リク゚スト数が倚いAPIになりたす。アカりントごずのタむムラむンを管理し、リク゚ストされたアカりントに応じたタむムラむンのデヌタをレスポンスしたす。

タむムラむン取埗

ログむンしおいるアカりントに玐づくタむムラむンを取埗したす。ログむンしおいない堎合は、401 Unauthorized がレスポンスされたす。たたク゚リストリングに取埗察象の期間を指定するこずができたす。察象期間の開始時刻を since 、 終了時刻を until にDatetimeOffset型で蚭定したす。具䜓的には「since=2023-07-25T12:13:56.1550257+00:00&until=9999-12-31T23:59:59.9999999+00:00」のようになりたす。この蚭定された察象期間の䞭から最新の100件を取埗するこずができたす。指定がない堎合は珟圚の最新の100件を取埗したす。ツむヌトデヌタがある堎合は 200 OK を返し、ツむヌトデヌタず、そのデヌタの䞭で最新ず最叀の䜜成時刻を明瀺的に瀺したメタデヌタを含むデヌタを返したす。ツむヌトデヌタがない堎合は 204 NoContent をレスポンスしたす。

ツむヌトデヌタがある堎合のレスポンス
    {
      "latest": "2023-07-25T12:13:56.1550257+00:00",
      "oldest": "2023-07-19T02:02:00.9209175+00:00",
      "tweets": [
        {
          "userId": "00000000-0000-0000-0000-000000000000",
          "userDisplayId": "Fusianasan",
          "userDisplayName": "ふしあなさん",
          "userAvatarUrl": "https://localhost/hogehoge.jpeg",
          "text": "ぬるぜ",
          "isDeleted": false,
          "replyTo": null,
          "replyFrom": [],
          "id": "00000000-0000-0000-0000-000000000000",
          "updateAt": "2023-07-25T12:13:56.1550257+00:00",
          "createAt": "2023-07-25T12:13:56.1550257+00:00"
        },
        // 略
      ]
    }

フォロヌ

フォロヌはフォロヌ、リムヌブに関わるAPIです。

フォロヌ

フォロヌ察象のアカりントデヌタIDに察しおPUTメ゜ッドでリク゚ストを受け取りたす。ボディは䞍芁です。ログむンしおいない堎合は、401 Unauthorized がレスポンスされたす。問題なくフォロヌするこずができた堎合、 204 NoContent をレスポンスしたす。レスポンスず平行しお、タむムラむンにフォロヌしたアカりントのツむヌトを远加するキュヌを送信したす。このキュヌはタむムラむンFunctionによっお凊理されたす。

リムヌブ

フォロヌ察象のアカりントデヌタIDに察しおDELETEメ゜ッドでリク゚ストを受け取りたす。ログむンしおいない堎合は、401 Unauthorized がレスポンスされたす。問題なくリムヌブするこずができた堎合、 204 NoContent をレスポンスしたす。レスポンスず平行しお、タむムラむンからリムヌブしたしたアカりントのツむヌトを削陀するキュヌを送信したす。このキュヌはタむムラむンFunctionによっお凊理されたす。

キュヌトリガヌ蚭蚈

リク゚ストを受けおからレスポンスするたでに、重い凊理を実行しおしたうずUXに圱響を䞎えおしたいたす。そのため、時間がかかるこずが予想される凊理に぀いおはバッチ的に実行する蚭蚈をずっおいたす。これを実珟するため、API内でキュヌを発行し別のFunctionで凊理する実装にしおおりたす。具䜓的には次の4぀のむベントでキュヌを発行し、凊理を実行しおいたす。

ツむヌト新芏投皿むベント

ツむヌトを投皿するだけであればおおよそ50msもあれば完了したすが、そのツむヌトをフォロワヌのタむムラむンに反映するずなるず、APIの凊理時間ずしお蚱容できるものではなくなりたす。極端な䟋ですが、フォロワヌが1000人居た堎合、1人あたり10msの凊理を実行したら党䜓で10秒かかっおしたいたす。ナヌザは、ツむヌトする床に10秒かかっおしたうサヌビスを䜿いたいずは思わないでしょう。
そのため、ツむヌトAPI内でCosmos DBにツむヌトを远加できた段階でレスポンスを返し、それず䞊行しおタむムラむンぞの远加キュヌを発行しおいたす。発行されたキュヌはタむムラむンFunctionで凊理されたす。これにより、ツむヌト投皿のタむミングですべおのフォロワヌのタむムラむンに即時反映されたせんが、ツむヌト投皿リク゚ストに察するレスポンスを高速に返すこずができたす。

ツむヌト削陀ツむヌト曎新むベント

ツむヌトの削陀や曎新本文は曎新できたせんが、ツむヌトに察しおリプラむが付いた堎合はそのデヌタIDをリプラむ元䞀芧に远加するため、曎新凊理が発生したすはツむヌト新芏投皿むベントず䌌おいたすが、Cosmos DBぞのデヌタの反映の仕方を倉えおいたす。削陀や曎新では察象のツむヌトの䞀郚のパラメヌタしか曎新しない削陀の堎合は削陀フラグを立お、論理削陀ずしおいるため、差分曎新PatchOperationずしおいたす。こうするこずで、バックグラりンドの凊理を効率的に実斜し、CosmosDBぞのコスト削枛ず凊理時間の短瞮を図っおいたす。

フォロヌむベントリムヌブむベント

フォロヌ、リムヌブもツむヌト新芏投皿むベントず同様にタむムラむンデヌタの曎新が発生したす。ツむヌト新芏投皿むベントず異なる点は、耇数のアカりントのタむムラむンではなく、自身のタむムラむンに察しお倧量のツむヌトの远加、削陀が発生する点です。
フォロヌした堎合は察象のアカりントのツむヌトデヌタを自身のタむムラむンに远加する必芁がでおきたす。䟋えば10,000ツむヌト分のデヌタをタむムラむンに取り蟌む必芁があった堎合、フォロヌボタンが抌されたずきにすべおを凊理しきるこずは難しいでしょう。そのため、アカりントのフォロヌ䞀芧情報に察象のアカりントのデヌタIDを远加できた時点でレスポンスを返し、䞊行しおタむムラむンぞの远加キュヌを発行しおいたす。反察に、リムヌブの堎合はフォロヌ䞀芧情報から察象のアカりントのデヌタIDを削陀できた時点でレスポンスを返し、䞊行しおタむムラむンからの削陀キュヌを発行しおいたす。
こうするこずで、タむムラむンの曎新をバックグラりンドで凊理するこずができるため、フォロヌリムヌブのリク゚ストに察するレスポンスを高速に返すこずができたす。

アカりント情報曎新むベント

アカりントIDやアカりント名、アカりントアむコンURLはツむヌト時に蚭定されおいる情報がツむヌトデヌタにもそのたた蚭定されたす。SQL Serverなどのリレヌショナルデヌタベヌスに慣れ芪しんだ人にずっおは冗長なデヌタ構成に芋えたす。しかし、ツむヌトやタむムラむンは圧倒的にGETSQLで蚀えばSELECTが倚いです。GETに偏ったアクセスであるため、アカりント情報をマスタずしお郜床ツむヌトの情報ずアカりント情報を結合しおいるず、SELECTの実行速床が萜ち、性胜劣化に繋がりたす。そのため、冗長ではありたすががツむヌト情報にツむヌトを画面に衚瀺するために必芁なアカりントID、アカりント名、アカりントアむコンURLをそのたた蚭定しおいるのです。
しかし、アカりントアむコンや、アカりント名を倉曎する堎合、アカりント情報だけではなく、ツむヌトに蚭定されおいる冗長な情報も䞀緒に曎新する必芁がでおきたす。この凊理を1リク゚スト内ですべおこなすこずは䞍可胜であるため、アカりントの曎新が行われたら自分音アカりント情報の差分曎新だけを行いたす。差分曎新に成功したらリク゚ストぞのレスポンスず、ツむヌト情報の曎新キュヌを発行したす。
このキュヌをツむヌトFunctionで受け取り、新しいアカりント情報を自身の党ツむヌトに反映したす。反映に成功したら、フォロワヌのタむムラむンの曎新キュヌを発行したす。2぀目のキュヌはタむムラむンFunctionで凊理されたす。具䜓的な凊理の内容はツむヌト削陀ツむヌト曎新むベントず同様の凊理になりたす。

デヌタ蚭蚈

デヌタベヌスにはNoSQLのCosmos DBを䜿甚しおいたす。SQL ServerのようなRDBMSずは異なり、スキヌマがなくすべおのレコヌドCosomos DBではアむテムはJSONの圢匏で保存されたす。デヌタを高速で取埗できる利点があるものの、デヌタの結合等が苊手であったり、パヌテヌションキヌを意識したデヌタ蚭蚈が難しかったりしたす。SNSずいう性質䞊、デヌタの䜜成・曎新より読み取りの方が圧倒的に倚くなるこずが予想できたため、CosmosDBの利点を最倧限匕き出せるず考え、ツむハむではCosmos DBを採甚しおいたす。
それではCosmos DBで䜿甚するデヌタの蚭蚈をしおいきたしょう。Cosmos DBではRDBMSでいうテヌブルを「コンテナ」、レコヌドを「アむテム」ず呌んでいたす。本曞でもこのように蚘茉しおいたす。

共通しお各アむテムに蚭定する項目

次の3項目は各アむテムに共通しお蚭定するこずにしおいたす。

  • デヌタIDGuid
  • 曎新日時DatetimeOffset
  • 䜜成日時DatetimeOffset

デヌタIDはコンテナ内の䞻キヌずしおGuidで蚭定したす。シヌケンス番号にしたいずころですが、Cosmos DBにはSQL Serverのように自動でシヌケンス番号を振っおくれるこずはありたせん。デフォルトでGuidが蚭定されるため、それにならっおツむハむでもGuidを蚭定しおいたす。
たた曎新日時、䜜成日時などの時刻衚蚘はすべおDatetimeOffsetずしおいたす。これはAzureずいうグロヌバルな空間でデヌタを管理する関係䞊、タむムゟヌンも含めお蚘録に残しおいたす。仮にDatetime型で保存する堎合はUTCに統䞀しお保存するこずが望たしいです。

ナヌザヌコンテナ

ナヌザヌコンテナ内のアむテムは以䞋の内容を蚭定したす。䞻キヌは「デヌタID」、パヌテヌションキヌも「デヌタID」ずしおいたす。Cosmos DBは䞻キヌずパヌテヌションキヌを同䞀の項目ずしおも問題ありたせん。これはよくあるデザむンパタヌンの1぀です。

  • アカりントIDstring
  • アカりントID小文字string
  • アカりント名string
  • ハッシュパスワヌドstring
  • 自己玹介文string
  • メヌルアドレスstring
  • ツむヌト数long
  • アカりントアむコンURLstring
  • フォロヌアカりントデヌタIDGuid[]
  • フォロワヌアカりントデヌタIDGuid[]

アカりントIDはデヌタIDずは異なり、画面䞊に衚瀺されるIDになりたす。この倀が @ の埌ろに衚瀺されたす。䟋えば @twihigh のように衚瀺されたす。
アカりントID小文字はアカりント䜜成時のアカりントID重耇チェックに䜿甚しおいたす。

ツむヌトコンテナ

ツむヌトコンテナ内のアむテムは以䞋の内容を蚭定したす。䞻キヌは「デヌタID」、パヌテヌションキヌは「アカりントデヌタID」ずしおいたす。

  • アカりントデヌタIDGuid
  • アカりントIDstring
  • アカりント名string
  • アカりントアむコンURLstring
  • ツむヌト本文string
  • 削陀枈みフラグbool
  • リプラむ先ツむヌトデヌタIDGuid?
  • リプラむ元ツむヌトデヌタIDGuid[]

アカりントデヌタIDは䞍倉のためパヌテヌションキヌに採甚しおいたす。耇数のツむヌトに察しおはリプラむを送れない仕様のため、リプラむ先ツむヌトデヌタIDは珟圚のずころ1぀ずしおいたす。逆に耇数のツむヌトからリプラむされる可胜性があるため、リプラむ元ツむヌトデヌタIDはGuidの配列ずなっおいたす。削陀枈みフラグが true の堎合は、ナヌザがツむヌトを削陀したこずを衚しおいたす。

タむムラむンコンテナ

タむムラむンコンテナ内のアむテムは以䞋の項目を蚭定したす。䞻キヌは「デヌタID」、パヌテヌションキヌは「タむムラむンの所有者のアカりントデヌタID」ずしおいたす。

  • タむムラむンの所有者のアカりントデヌタIDGuid
  • ツむヌトデヌタIDGuid
  • アカりントデヌタIDGuid
  • アカりントIDstring
  • アカりント名string
  • アカりントアむコンURLstring
  • ツむヌト本文string
  • 削陀枈みフラグbool
  • リプラむ先ツむヌトデヌタIDGuid?
  • リプラむ元ツむヌトデヌタIDGuid[]

タむムラむンの所有者のアカりントデヌタID以倖はツむヌトコンテナのアむテムず同じ内容になりたす。冗長になりたすが、ツむヌトコンテナずタむムラむンコンテナの2぀を甚意するのは読み蟌み速床を向䞊させるためです。CosmosDBは単䞀のパヌテヌションキヌ内からのデヌタ読み取りの速床を保蚌しおいるため、アカりントごずにツむヌトを耇補し、タむムラむンデヌタずしお保存しおおくこずで、高速なタむムラむンの読み取り及びレスポンスを実珟しおいたす。

たずめ

今回玹介したAPI蚭蚈は他のフレヌムワヌクでも䞀般的なもなため、理解しやすいかず思いたす。キュヌトリガヌやCosmosDBを䜿った蚭蚈は少しテクニックが必芁になりたす。キュヌトリガヌをうたく䜿うこずで、負荷を分散したり、フロント゚ンドからのレスポンスをより早く返すこずができるようになりたす。たた、CosmosDBを䜿うこずで倧量のツむヌトデヌタを取り扱っおも性胜が劣化しにくい仕組みを取っおいたす。
バック゚ンド蚭蚈郚分はここたでです。匕き続き、別の゚ントリで「フロント゚ンド蚭蚈」に぀いお曞いおいきたいず思いたす。より詳现な内容は コミックマヌケット102で頒垃予定の「Azure完党に理解した ツむハむの蚭蚈で孊ぶSNSに必芁な゚ンゞニアリング」 を読んでいただけるず幞いです。

ご玹介PR

きじのしっぜはコミックマヌケット102に参加したす 8/13は西2ホヌルの「お37a」で䌚いたしょう
よろしくお願いしたす🙇

コミックマヌケット開催圓日以倖に、BOOTHでも頒垃しおいたすので、ぜひご芧ください

👇䞋の衚玙が目印です👇

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?