見出し画像

bubbleで◯◯作るなら、データベースはこれでいい[完全保存版]

どうも、bubble特化ノーコーダーのけいです(https://twitter.com/NoCoder_K)

僕のとあるツイート。

かなり多くの方に「これは欲しい!」とのコメントをいただいきました。

データベースの設計は、エンジニアではない多くのノーコーダーがつまずく所でありながらも、勉強しようにも何をすればいいか分からない題材ですよね。

実際、 NoCodeCamp内の質問のある一定数は、「どうデータを作ればいいのか分からない」とか、「リレーションってなんなの?」「Listの使い方は?」というものが多いです。


この記事でのポイントは、あえて1発で正解を示さないようにすることです。つまり、まずこんな感じかな、と構築してみて、でも「つじつま合わないよな?」という疑問とともに、再構築し直していく様子をまるで実際に目の前で作っているような書き方をしています。

こうすることで、僕がどうやって正しいデータベースの構築をしていき、どうやって考えているのかが学べるようになり、今後ご自身で考えていける教材となっています。

今回、大きく分けて5つのサービスをbubbleで構築するためのデータベースの構造テンプレートを考えてみました。括弧内は参考サービス。

考えるサービスは

・SNS
・EC、マーケットプレイス
・チャットツール

この中でコアとなる機能について解説します。

各サービスについては、bubbleのエディターも公開するので参照ください。

なお、この記事ではそれらのデータ構造をどのようにrepeating groupなどで表示するかや、新規データ作成をするかなどの解説はせず、あくまでデータベースの型を作るところまでを解説します。

また、データベースに正解はありません。紹介する構造以外にもサービスとして動くように構築する方は可能です。大まかな構造の作り方を学んで、あとはご自身のサービスに最適なものを選んで作り替えていくこともできるようになりましょう。

基本的な考え方はどれも同じで、これを理解すれば
・いいね
・フォロー
・カートボックス
などは全て同じ全く同じ構造をしていることが分かります。
そうすれば今後似たような機能が実装したい場合も自身で考えて構築していけるようになります。


0.1データ型とは?

まず実例紹介の前に、データの型という概念を解説します。非エンジニアで、データの型について理解できていない方は必ず読み飛ばさずにチェックしてください。

bubbleに限らず、プログラミングで扱うデータには型という考えがあります。

スクリーンショット 2020-09-07 10.31.50

データのfieldを作成する際に指定するこれのことです。

ここで、予めこれから扱うデータの型を"宣言"する必要があります。上から順に軽く解説します。

・text・・・文字型。'あ'、's'、'1234+6'、'0120-000-000'、など。

・number・・・数字型。0、123、4.2、-100、など。ここで、text型の'0120'とnumber型の 0120 は違うものとして扱われるということを明示しておきます。text型の場合、'0120'が塊として左端の"0"も文字として出力されるのに対して、numberで 0120とすると、これは123と同じ意味なので、画面上で表示すると 123として現れます。また、textの'0120'は、文字なのでこれに対して足し算、引き算などの計算を行うことはできません。なので、値段などで価格を合計したい場合などはnumber型で価格を宣言する必要があり、例のように電話番号のように先頭が'0'で始まる文字列などについてはtext型で扱います。

・numeric range・・・数字の範囲。下限と上限の範囲を指定した2つの値が指定されます。それぞれの下限と上限の値は、numberとして扱われます。特に使ったことはありません。

・date・・・日付(年、月、日、時間、分、秒)。date型は、年だけや、月だけ、を入力とすることはできません。必ず上記の年、月、日、時間、分、秒の全てがセットとなった形式で入力する必要があります。

その方法は一般によく使われるinputエレメントではなく、Date/Time Pickerを用いてユーザーに入力をしてもらうと自動で型の条件を満たした状態でデータを保存することができます。

スクリーンショット 2020-09-07 10.49.45

・date range・・・日付範囲。numeric rangeの日付バージョンです。特に使ったことはありません。

・date interval・・・間隔。dateなので日付感覚なのかと思いきや、単位は'秒'です。特に使ったことはありません。

・yes / no・・・フラグ。今まで紹介したものと少し概念が違います。使用用途は、例えば何かのプロジェクトで完了 or 未完了を判定するために yes / noを利用します。注意点は、この yesはtext型の'yes'とは全く別ということです。あくまで判定のためなので、文字列としての入力はしません。

スクリーンショット 2020-09-07 11.00.28

create new thingやmake changeの場合などは、このように候補として出てくるのでどちらかをクリックして選択して設定しましょう。

・file・・・ファイル型。画像、動画、csv、zipなど。1つのアプロードでは50MBが上限

・image・・・画像型。画像を扱う場合の型はfileとしてアップロードするのも可能ですが、一般的にはこちらを使用します。

・geographic address・・・地理的アドレス。世界基準でのアドレスの型です。この型を使えば、国が違っても同じようにgoogle mapにピンで表示できたり、緯度経度を算出できるようになります。入力の際は、普通に「東京都渋谷区・・・・」と入力するのはおすすめせず、bubbleのチュートリアルの1つ目で使用される search boxで自動保管される機能を使用することをおすすめします。


0.2 Listとは?

以上で各データの型を解説しました。では、下の写真の"This field is a list(multiple entries)とはなんでしょうか?

スクリーンショット 2020-09-07 11.11.12

これまで、1つのデータの箱には1つのデータしか保存できませんでした。

この場合、例えばTwitterのようなTweetに関する情報があるデータで言えば、呟いた人は1人なのでOK。呟いた時間は1つしかないのでこれもOK。じゃあ、いいねした人やリプライを送った人のデータはどうやって扱うのでしょうか?

ここでListを使用します。1つのTweetのデータに対して、複数のユーザーがいいねした情報を入れ込むことができます。

スクリーンショット 2020-09-07 11.47.40

このように、各同じ型のデータに対してもlistにするのかそうでないのかを指定します。

また、bubbleの公式リファレスでは、1つのデータに保存できるlistの数は1万までです。上の写真の例で言うと、いいねした人の like_listは1万人まで保存できます。

1万人を超える場合が想定される場合は、別の方法を考える必要があるので後ほど紹介します。

0.3 リレーションとは?

最後。データの型について見てきました。もう一つ、データベースを理解するために必要な概念があります。それがリレーションです。

リレーションとは、各テーブル同士の繋がりをリンクします。

例えば、bubbleでは標準でUserテーブルがあります。

Userテーブルには、アプリを使うユーザーの名前、アドレス、パスワード、プロフィール写真、などを保存しています。

では、そのユーザーがツイートを投稿した時、新たに作成されるTweetテーブルの中には、Tweet本文、画像、呟いた日時、いいねした人、コメントした人、そして最後に、誰が呟いたか。などが必要になってきます。

ここで、”誰が”の部分を、TweetテーブルにUserのテーブルをリンクさせるのがリレーションです。

リレーションを使わなかった場合、このTweetテーブルには、さらに呟いた人の名前、呟いた人のアドレス、呟いた人パスワード、呟いた人プロフィール写真というように、Userテーブルで保存されているデータをまたもう一度全て保存していく必要があります。

リレーションによって、特定のTweetの呟いた人(ここまでがTweetテーブル)の、名前(ここはUserテーブル)というような表現をすることができます。


ここまでが、これからの実例紹介の上で理解しておく必要のある3つのデータベースのルールです。今はパッとこなくても、実例を見ながら理解していきましょう。


1. 一般的なSNSのデータベース

まずはSNSから見ていきます。機能としては、
・ユーザー情報
・フォロー
・いいね
・投稿
・リプライ

です。フォローおよびいいねは、list型を使った方法と、そうでない方法の2種類考えられます。考え方は実はどちらも同じなので、今回はいいねはlist型。フォローは別の方法(リレーション)で紹介します。


まずは用意するテーブル(bubbleではtypeというらしい)

テーブルを設定するには、①Data → ②Data typesを開きます。

スクリーンショット 2020-09-07 12.03.03

今回用意するのは、以上の3つです。

bubbleでは、一番下のUserテーブルは標準で自動で作成されるので、
・Follow
・Reply
・Tweet
を新規で作成します。

作成するには、赤枠の下の 「New types」にテーブル名を入力してエンターもしくは「Create」ボタンをクリックします。

各テーブルには、それぞれ次のようなデータ構造を作成していきます。

「User」

スクリーンショット 2020-09-07 12.10.49

・profile_description・・・text型
・profile_image・・・   image型
・username・・・      text型

ちなみに、上の写真で赤く囲った部分は、defaultと書かれてあります。このスペースは、新しくテーブルのデータが作成された際にデフォルトとしてここに入力したものが保存されますという設定です。

例えば、新しくユーザーが新規登録された時に、profile_imageのdefaultを

画像8

このようなアイコンを指定しておけば、ユーザーがオリジナルのプロフィール画像を保存するまで、こちらがユーザーのアイコンとして使用されるようになります。適宜、サービスの快適性を確保するためにdefault valueも利用してみてください。

ここでいう上記の3つのprofile_description、profile_image、usernameは、bubbleでは「field」と呼ばれます。

つまり、Userというテーブル(bubbleではtypeともいう)の中には、3つのfieldがある。という構造になっています。

このfieldは、1つのテーブル内で同じ名前を作成するとエラーの原因になりますので避けるようにしてください。また、一目で何の情報が保存されているか分かるような名前をつけることも大切です。

下の写真は、実際にデータを保存してみたものです。データベースでいうと1行分にあたります。

スクリーンショット 2020-09-07 12.29.49

App dataに移動して、All Usersを見てみると、このように1ユーザー分の1行が保存されていきます。この”行”を、データベースではよく recordと呼ぶこともあります。


「Tweet」

スクリーンショット 2020-09-07 12.16.54

次は、Tweetテーブルです。
・draft・・・   yes / no (defaultは yes)
・image・・・    image型 (list)
・liked_user・・・   User型 (list)  ←  リレーション
・reply・・・   Reply型(list)  ←  リレーション(次に作成)
・tweet・・・     text型

draftは、下書きかどうかを判定します。作成された段階で、draftをyes、つまり下書きとして、最後に投稿するボタンが押されたらdraftをnoにして、タイムラインに表示できるようにします。

replyは、次に作成するReplyテーブルをリレーションさせるので、この段階では作成することはできません。

liked_userは、いいねした人を保存するためのものです。

ここで、どのテーブルにも必ず追加される重要な項目を紹介します。それは、created userというfieldです。一見、Tweetテーブルには、”誰が”呟いたのか、というUser情報が無いように見えます。しかしbubbleでは、そのデータ行(つまりrecord)を作った人、ということで、created userという情報が自動で与えられます。この自動で作成されるfieldがあるため、created userの名前、などといったようにリレーションが作ることができます。

この点を頭に入れておいてください。

「Reply」

スクリーンショット 2020-09-07 12.23.37

・comment・・・    text
・image・・・        image
・reply_to_reply・・・   Reply(list)  ←  リレーション

Replyテーブルには、上の3つです。文章にあたるcommentと、画像(1枚しか添付できないのでlist型ではない)、そして、このリプライに対するリプライを保存するためのfieldがlist型で指定されています。


「Follow」

スクリーンショット 2020-09-07 12.33.46

・from・・・User  ←  リレーション
・to・・・  User  ←  リレーション

ここは少し解説が必要です。

0.2のリストの章で解説したんですが、フォローしている人の情報を、Userテーブルにlistとして保存する方法も考えれれます。つまり、

スクリーンショット 2020-09-07 12.37.14

このように、Userテーブルに、followというfieldを作成し、Userのlist型をリレーションします。

実はこの方法でも上手くフォロー機能は使えるんですが、前にも書いたように1行(record)のlistの中に保存できるlistの数は、1万までという上限があります。

スクリーンショット 2020-09-07 11.47.40

一度紹介した写真ですが、これはTweetにいいねした人を保存するlistで、like_listのfieldには今3人が保存されています。実際のTwitterでは、万を超えるいいねやフォロワーを抱えるアカウントが多くあって、このlist型では実現できないという壁にあたります。

そこで、listとして保存するのではなく、別のFollowというテーブルを別で保存して、「誰が」「誰を」フォローしているのかが記録されるものを作成しました。こうすることで、行の制限は特にbubbleに設けられていないので

・「Aさんが」「https://twitter.com/NoCoder_K さん」をフォロー
・「Bさんが」「https://twitter.com/NoCoder_K さん」をフォロー
・「Cさんが」「https://twitter.com/NoCoder_K さん」をフォロー
・・・
・「AAAAAAAさんが」「https://twitter.com/NoCoder_K さん」をフォロー

といった感じで、何行でもいくらでも保存することができるようになります。

listの数が1万など大きな数にならない場合は、Tweetテーブルのliked_userのようにlistで保存してもいいですが、1万を超えるようなlistになる可能性があるものは、このように新しくテーブルを用意して、リレーションで繋ぐ方法を採用する必要があります。

今回は、説明のためにTweetテーブルにliked_userをlistで保存しました。

しかし、もっとユーザー数が増えて1万いいね以上が可能性として考えられるのであれば、正確にはFollowと同じように、「like」という別テーブルを用意して、fieldを「which_tweet (Tweet型 ← リレーション)」と、「liked_user(User型 ← リレーション)」という2つを保存したものを用意することで、特定のツイートに対するいいねの数や、いいねした人の情報をリレーションで調べることができます。

これらを図解すると、

スクリーンショット 2020-09-08 4.25.19

このように可視化することもできます。各線は、リレーションを示しています。

2. EC

機能としては、
・ユーザー情報
・フォロー
・お気に入り
・出品
・取引情報
・レビュー
・カート機能

基本的な構造はこのように作成できますね。多くの機能は、すでにSNS編で解説したものと同じです。また、ECと言っても、メルカリのような C to Cの場合や、アマゾンのようなもの(マルチベンダー)もあり、細かい箇所(在庫数やカートの考え方)は違いますが、基本的にこの機能があればECサイトを作れるというデータの構造を紹介します。

メッセージ機能については、次の章で解説するので、商品ページなどのチャットについては省きます。

この章の解説は、どのようなECを作るかによって、データの構造は変わります。

例えばAmazonと楽天では、データベースの構造は大きく異なります。

Amazonの場合、1つの商品に対して1つのページだけが存在し、その中に複数の出品者が出品しています。(相乗り出品という)

一方楽天は、同じ商品でも、複数の販売者が、それぞれ販売ページを作成しており、独自で商品ページのタイトルや写真、解説文を作成しています。

ヤフーショッピング、メルカリもこのパターンですね。一般的にはECサイトはこれが多いので、今回の事例ではメルカリを参考に解説していこうと思います。

まずはテーブルから。

スクリーンショット 2020-09-07 23.33.59

FollowはSNS編で紹介したのと同じですね。

Goodは、グッズです。「いい」ではないのでご注意。Orderは注文情報、Reviewはレビューです。今回は、テーブルではなく、Option setsも使ってみましょう。

スクリーンショット 2020-09-07 23.43.24

今回はカテゴリーというOptino setを用意してみました。Option setと、先ほどまで説明してきたテーブルの違いをまず解説します。

これまで、テーブルにはfieldがあり、それに対する値がセットで作成されてきたのを見てきました。Userのnameというfieldに、"けい"という値が保存されます。そして、work flowでデータを追加したり削除するのも簡単にできます。

一方Option setの場合は、先の例で言うと”値しかない”という状態ですね。使用用途としては、drop down inputで選択肢として使用するなどです。

スクリーンショット 2020-09-07 23.57.25

複数のページでこのように毎回選択肢を入力していくのは面倒なので↑

スクリーンショット 2020-09-07 23.58.13

このように動的にoption setからデータを表示することができます。

この場合、例えば カテゴリーに新しいものが追加された場合や変更された場合、全て個別で指定していると全てのページのを変更する必要があります。メンテナンス性でも不便なので、こういった場合はoption setを利用します。


では、1つづつ見ていきましょう。

「Good」

スクリーンショット 2020-09-08 3.42.02

・category・・・  category (option set)
・description・・・ text
・draft・・・    yes/ no
・image・・・       image (list)
・liked_user・・・   User (list)  ←  リレーション
・stock・・・     number
・Title・・・     text

特に言及するところはありませんね。stockは在庫数です。これとcategory以外は、SNS編のTweetテーブルと考え方は同じです。下書き機能をする仮に、メルカリではなく楽天・ヤフーショッピング・Amazonのようなカート機能を実装したい場合は、どうすればいいでしょうか?考えてみてください。





答えは2種類あります。このGoodテーブルに、cartのfield (Userのlist型)を追加して、いいねと全く同じように扱うか、もしくはUserテーブルに、cartのfiled(Goodのlist型)を作成して、ユーザーに対して複数の商品を持たせていくか。とできます。

ただ、落とし穴があります。

実はこのだと、上手く行かなくなります。なぜなら、カートに追加する個数は1つとは限らないからです。例えば歯ブラシでいうと、家族分の5本を全てカートに入れておきたい場合があります。その場合、

1. Goodテーブルにcart(user of list )を追加する場合:その商品をカートに追加しているユーザーは特定できます。何人がカートに追加しているかも分かります。では、Aさんがその商品を何個カートに追加したのかは分かりません。

2. Userテーブルにcart(good of list)を追加する場合:そのユーザーがどの商品をカートに入れているかが特定できます。でも、それぞれのカートを何個づつカートに入れるのかという情報は持てません。


ということで、これを解決するのは、実はもう一つテーブルを増やす必要があります。

そのテーブルは、User情報(リレーション)、商品情報(リレーション)、個数が必要になりますね。では、追加した完成形がこちら

「Cart」

スクリーンショット 2020-09-08 3.34.01

User情報は、created user (Tweet編のTweetテーブルで解説済み)を使用します。このcartテーブルのおかげで、誰が、どの商品を、何個キープしている(カートに入れている)のかを格納することができます。


「Order」

スクリーンショット 2020-09-08 3.40.57

・buyer・・・    User  ←  リレーション
・done・・・     yes/ no
・seller・・・     User  ←  リレーション
・shipping_company・・・     text
・transaction_number・・・   text
・which_good・・・Good  ←  リレーション

のようになります。doneは、注文の発送が完了したかの判定に使います。shipping_company及びtransaction_numberは、日本語で言うと運送会社と追跡番号です。販売者が発送を行うときに入力して、購入者に知らせるためのものですね。

このデータ構造、メルカリのように、1注文=1商品&1個の場合は機能しますが、1注文=複数商品&複数個の場合は実は。別のデータベースの構造にする必要がありますね。最後の"which_good"のfieldをlistにすれば解決します。

。。。。。。。。。。?

解決しませんね。これだと、1注文=複数商品にしか対応していません。各商品が何個なのか、というのは保存できません。

では、やはり今回も、商品と個数が紐づいた新しいテーブルが必要になりそうです。少し強引ではありますが、注文=カートに入っている商品を全て買うこと、と定義してみると、"which_good"のfieldは、"good_info"などとして、Cartのlistをリレーションする。とすることもできます。

ユーザーが注文ボタン押すと、Cartのcreated by Current User全ての情報が、Orderテーブルに紐付けえられます。こうすることで、Aの商品3個、Dの商品が5個、そしてそれぞれの価格も分かる。合計できる。というデータ構造が出来上がります。

この辺り、とても難しくなってきたと思うので、実際に手を動かしてデータベースを作ってみてください。

と、ここで完結したいのですが、もう一つ問題があります。

それは、運送会社と追跡番号の問題です。現在の構造だと、1つの注文に対して、1つの運送会社&追跡番号した収納できません。あらら。販売者が1社(1人)の場合はこれで済むかもしれませんが、複数の販売者から1つの注文として扱うことができません。

ということで、作っていきましょう。配送情報を格納するための、Shippingテーブルです。

このテーブルに必要な情報は、運送会社と追跡番号、そして、Order (リレーション)ですね。これで、特定の注文に紐づけられた配送情報として複数あった場合でも対応できます。

「Shpping」

スクリーンショット 2020-09-08 4.01.09

ということで、Orderテーブルはこのように書き換えます。

スクリーンショット 2020-09-08 4.04.42

配送情報を、Orderに直接紐付ける必要はなく、配送情報側にどのOrderなのか、を保存しています。ここで、sellerの情報は削除しました。sellerの情報は、注文情報に直接必要ではなく、Good_info内のCart内のGoodのcreated userが、その商品の販売者にあたりますので、そのようにして参照していきます。

さて、次が最後。

「Review」

スクリーンショット 2020-09-08 4.16.26

・commnt・・・     text
・rating・・・    number
・seller・・・     User  ←  リレーション
・which_order・・・ Order  ←  リレーション

ここでは、sellerが必要です。仮にsellerが無い場合、どの注文に対する評価なのか、は特定できますが、一般的にはレビューというのは販売者に対して行うものです。特定の販売者との、特定の注文取引という情報が必要になるため、このような構成になります。sellerはなくても、OrderのGood_info内のCart内のGoodのcreated userが間接的にsellerにはなるんですが、意味合いとして、評価はsellerに対して行うものであるので、それを強調するためにも、今回はsellerをReviewテーブルに直でリレーションさせました。


ということで、以上がECサイトのデータベースの構造の1つの例です。

これらを図解すると、

画像26

このように可視化することもできます。各線は、リレーションを示しています。

3. チャットツール

さて、最後です。

と言っても、チャット。つまりメッセージの機能は、すでにノーコードラボさんの記事で解説されているので、あまり詳しい解説はしません。必ず、この記事を読んで、実際に手を動かしてメッセージ機能を実装できるようになってからこの先に進んでください。でないと全く意味わからないと思います。

1点違う点は、既読機能を追加した、という点で解説していきます。

ここから解説する95%の部分はラボさんの記事を参照してください。

また、この解説では複数人のグループでも1対1のチャットでも関係なく動作します。

スクリーンショット 2020-09-08 4.53.04

テーブルはこれだけ。はじめに言っておくと、これ以上は増えません。(笑)

前回のEC編が重すぎました。今回は最も軽いです。

「Message」

スクリーンショット 2020-09-08 5.05.37

・image・・・         image
・message・・・    text
・unread_user・・・   User(list)  ←  リレーション

unread_userは、メッセージを読んでいないユーザーです。メッセージを送信するときに、create new messageとします。その際、次に解説するRoomテーブルに含まれるユーザーを追加し、自分だけを除外する。とします。

そして、別のユーザーがチャットを開くと、そのユーザーを一人ずつunread_userから除外していきます。

unread_userに含まれるユーザーは、まだそのメッセージを読んでいないので、通知(未読の表示)を実装する。ということが可能になります。


「Room」

スクリーンショット 2020-09-08 4.56.53

・member・・・  User(list)  ←  リレーション 
・messag・・・  Message(list)  ←  リレーション

ラボさんの例と同じです。

これらを図解すると、

スクリーンショット 2020-09-08 5.08.07

このように可視化することもできます。各線は、リレーションを示しています。


この記事では、3つの事例をもとに、bubbleで〇〇を作るなら、というテーマでデータベースの構造テンプレートを紹介しました。教科書的に初めから正解を出すのではなく、どうやって考えて、どういう手順で構築していくのかを学べる教材になるように作成しました。

今後他のサービス(マッチングサイト・タスク管理・メモアプリ)などを作っていく時も、基本的にはこの記事で解説したような考え方をしていきます。

まずは型。list。リレーションを理解。そして、大雑把に必要になる機能を洗い出して、その機能のためにはどんなデータ(field)が必要なのか?どうやってリレーションするのか?どこかに矛盾はないか?

を少しずつ考えていきます。


データベースの構築に必要なのは、「これならいける」ではなく、「これならうまくいかない」ということが理解できる状態です。

ECサイトで、何度も構築し直していた僕の思考を手を動かしながら理解するようにしてください。

そして、データベースの構造に正解はなく、別のアプローチをすることでも同じ機能を実装することもできます。いろんな考えのもと、自分なりの構築をしていってもらえたらと思います。



最後になりましたが、この記事では、構築したデータベースをもとに、いつ、どのようにcreate new recordをして、いつどのrecordに対して mack changeをし、UIをどう構築するのかは記載していません。

これらは今後同じように教材化してみたいとも考えていますが、実は現在運用中のサービスのKoremoというものを、登録してくれた方を限定でエディター公開をしています。

ぜひ、ここで学んだデータベースのノウハウを、どうやって自分のサービスに盛り込んでいくのか、Workflowはどうするのか、UIはどう作るのか、を確認してみてください。

また、YouTubeでもbubbleについて動画も上げましたので、ぜひご覧ください。

知らなかった!という方は高評価していただけると嬉しいです!

また、この記事が役に立ったと思った方はぜひいいねとシェアで感想をしてもらえたらと思います。

今後も、エンジニア・非エンジニアにかぎらずbubbleに関する役立つ情報を発信していきます。

それでは!

https://twitter.com/NoCoder_K

この記事が気に入ったらサポートをしてみませんか?