{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/saas-ontology-1/",
    "result": {"data":{"ghostPost":{"id":"Ghost__Post__6a74b8ae781e37000184edec","title":"SaaSを題材にしたオントロジーを作ってみようシリーズ #1 テナント","slug":"saas-ontology-1","featured":false,"feature_image":"https://ghost.tech.anti-pattern.co.jp/content/images/2026/08/----------2026-08-19-17.27.06.png","excerpt":"\n0. このシリーズの目的\n昨今、セマンティックレイヤー・オントロジーなどの記事がそこそこ盛り上がっている気がします。生成AIの時代になってきて、どちらも重要かつ実用的になってきたからかと思います。\n\nしかし！いざ、オントロジーをやってみよう！と思っても、かなり内容が難しく、結局どうして良いのかわからない、これで正しいのかわからない、などハマりがちかと思います。少なくともぼくはハマりまくってます。\n\nそこで！実際に自分たちが常に触れている、「B2B\nSaaS」を題材にしてオントロジーを作ってみて、使ってみて、理解を深めよう！というテーマで連載をしていきたいと思います。だいたい、10回くらいでまとめられればと思っておりますが、もっと多くなってしまうかもしれません。。。\n\nそして、この連載が終わるころには、「SaaSっていったら、ベースとしてのオントロジーってこれだろ」っていうのがそこそこできているはずです。はずです。それを、「SaaSオントロジーv0」と呼ぼうと思います。そのうえで、v0を実際のみなさまのSaaSに当てはめて拡張して、おのおののSaaSのv1を作って活用していこう！という","custom_excerpt":null,"visibility":"public","created_at_pretty":"06 August, 2026","published_at_pretty":"19 August, 2026","updated_at_pretty":"19 August, 2026","created_at":"2026-08-07T01:39:10.000+09:00","published_at":"2026-08-19T14:17:14.000+09:00","updated_at":"2026-08-19T17:44:17.000+09:00","meta_title":null,"meta_description":null,"og_description":null,"og_image":null,"og_title":null,"twitter_description":null,"twitter_image":null,"twitter_title":null,"authors":[{"name":"Akihiro YAGASAKI","slug":"akihiro","bio":null,"profile_image":"https://ghost.tech.anti-pattern.co.jp/content/images/2022/04/yagasaki--2-.jpeg","twitter":null,"facebook":null,"website":null}],"primary_author":{"name":"Akihiro YAGASAKI","slug":"akihiro","bio":null,"profile_image":"https://ghost.tech.anti-pattern.co.jp/content/images/2022/04/yagasaki--2-.jpeg","twitter":null,"facebook":null,"website":null},"primary_tag":{"name":"Ontology","slug":"ontology","description":null,"feature_image":null,"meta_description":null,"meta_title":null,"visibility":"public"},"tags":[{"name":"Ontology","slug":"ontology","description":null,"feature_image":null,"meta_description":null,"meta_title":null,"visibility":"public"}],"plaintext":"\n0. このシリーズの目的\n昨今、セマンティックレイヤー・オントロジーなどの記事がそこそこ盛り上がっている気がします。生成AIの時代になってきて、どちらも重要かつ実用的になってきたからかと思います。\n\nしかし！いざ、オントロジーをやってみよう！と思っても、かなり内容が難しく、結局どうして良いのかわからない、これで正しいのかわからない、などハマりがちかと思います。少なくともぼくはハマりまくってます。\n\nそこで！実際に自分たちが常に触れている、「B2B\nSaaS」を題材にしてオントロジーを作ってみて、使ってみて、理解を深めよう！というテーマで連載をしていきたいと思います。だいたい、10回くらいでまとめられればと思っておりますが、もっと多くなってしまうかもしれません。。。\n\nそして、この連載が終わるころには、「SaaSっていったら、ベースとしてのオントロジーってこれだろ」っていうのがそこそこできているはずです。はずです。それを、「SaaSオントロジーv0」と呼ぼうと思います。そのうえで、v0を実際のみなさまのSaaSに当てはめて拡張して、おのおののSaaSのv1を作って活用していこう！というコンセプトです。\n\n現在、ある程度まで書き進めているのですが、このSaaSオントロジー構築をやっていくと、とってもSaaSの理解が深まります。なので、オントロジーに興味が無くても、ディープなSaaSの世界をよりダイブして理解したい方々にはオススメ！と言える内容にしていきたいと思いますので、ぜひご一緒にオントロジー入門してみていただけますと幸いです。\n\n※途中で、 RDF/Turtle を利用したオントロジーを表現したファイルが出てきます。ここで、うわ！ってなった方は、 こちらの記事で RDF/Turtle\nとは？\n[https://tech.anti-pattern.co.jp/rdfrdfsowlturtlenowei-iwozheng-li-site-huairudeli-jie-suruontoroziru-men/] \nというのを確認してみていただければと思います！\n\nRDF・RDFS・OWL・Turtleの違いを整理して、ファイルで理解するオントロジー入門\n「オントロジーを勉強し始めたけれど、RDF、RDFS、OWL、Turtleの違いが分からない」\nこれは、たぶんオントロジー初心者が最初につまずきやすいポイントです。少なくとも、ぼくはこれらが出てきたときに思考が止まりました。。。なので、これらだけをザックリ解説する記事になっております。\n特に混乱しやすいのが、次のような疑問です。 * RDFとOWLは別のファイル形式なのか * .ttlファイルはRDFなのかOWLなのか *\nex:とは何を意味するのか * URIはどのように決めればよいのか * RDF/XMLでは、なぜURIの書き方が場所によって違うのか この記事では、社…\nAnti-Pattern Inc. Engineering BlogAkihiro YAGASAKI\n[https://tech.anti-pattern.co.jp/rdfrdfsowlturtlenowei-iwozheng-li-site-huairudeli-jie-suruontoroziru-men/]\n1. このオントロジーの目的\nB2B\nSaaSには、業種や機能が違っていても、かなり共通した構造があります。たとえば、会計SaaS、CRM、勤怠管理、セキュリティ管理、プロジェクト管理のいずれであっても、一般的に次のような概念が登場します。\n\n * SaaSを利用する単位\n * SaaSにログインする人\n * 人がどの利用単位に所属しているか\n * その人に何が許可されているか\n * どのプランを契約しているか\n * SaaS上で作成・管理されるデータ\n\nこれらを、特定のSaaSに依存しない形で表現するのが、このオントロジーの目的です。\n\n最初のバージョンでは、次の8つだけを中心概念とします。\n\n 1. Tenant\n 2. User\n 3. Membership\n 4. Role\n 5. Permission\n 6. Subscription\n 7. Plan\n 8. Resource\n\n\n--------------------------------------------------------------------------------\n\n2. 全体像\n\nclassDiagram\n    class Tenant {\n        tenantId\n        name\n        status\n    }\n\n    class User {\n        userId\n        displayName\n        status\n    }\n\n    class Membership {\n        membershipId\n        status\n        joinedAt\n    }\n\n    class Role {\n        roleId\n        name\n    }\n\n    class Permission {\n        permissionId\n        action\n    }\n\n    class Subscription {\n        subscriptionId\n        status\n        startedAt\n        endedAt\n    }\n\n    class Plan {\n        planId\n        name\n    }\n\n    class Resource {\n        resourceId\n        resourceType\n        name\n    }\n\n    User \"1\" --> \"0..*\" Membership : hasMembership\n    Tenant \"1\" --> \"0..*\" Membership : hasMember\n    Membership \"0..*\" --> \"0..*\" Role : assignedRole\n    Role \"0..*\" --> \"0..*\" Permission : grants\n    Tenant \"1\" --> \"0..*\" Subscription : hasSubscription\n    Subscription \"0..*\" --> \"1\" Plan : basedOn\n    Tenant \"1\" --> \"0..*\" Resource : owns\n    User \"0..1\" --> \"0..*\" Resource : created\n\n\nこのモデルの中心は、名前のとおり Tenant です。\n\nTenantを中心として、\n\n * 誰が参加しているか\n * 誰がどの権限を持つか\n * どのプランを契約しているか\n * どの業務データを所有しているか\n\nを表現します。\n\n\n--------------------------------------------------------------------------------\n\n3. 最重要概念：Tenant\n3.1 Tenantとは何か\nTenantは、SaaSにおける論理的な利用境界です。\n\nTenant\n＝ ユーザー、権限、契約、データを分離する単位\n\n\n典型的には、次のようなものがTenantになります。\n\n * 企業\n * 部署\n * 支店\n * プロジェクト\n * 顧客専用環境\n * 学校\n * 医療法人\n * 自治体\n * 販売代理店\n * グループ会社\n\n重要なのは、\n\nTenant ＝ 会社\n\n\nとは限らないことです。\n\n3.2 会社とTenantを同一視しない\nたとえば、株式会社ABCがSaaSを利用しているとしても、Tenantの作り方には複数の可能性があります。\n\nパターンA：会社全体で1テナント\n株式会社ABC\n└── Tenant ABC\n\n\nパターンB：部署ごとに別テナント\n株式会社ABC\n├── 営業部Tenant\n├── 開発部Tenant\n└── 管理部Tenant\n\n\nパターンC：会社と子会社で別テナント\nABCグループ\n├── 株式会社ABC Tenant\n├── ABC販売 Tenant\n└── ABCシステムズ Tenant\n\n\nしたがって、Tenantは「法人」ではなく、あくまでSaaS上の利用境界として定義します。\n\n3.3 Tenantの基本属性\nTenant\n├── tenantId\n├── name\n├── status\n├── createdAt\n└── updatedAt\n\n\nstatusには、たとえば次の値が入ります。\n\nactive\nsuspended\nclosed\n\n\n\n--------------------------------------------------------------------------------\n\n4. UserとMembershipを分離する\nこのオントロジーで特に重要なのが、UserとMembershipを別の概念にすることです。\n\n4.1 Userとは何か\nUserは、SaaSを利用する人、またはログイン主体です。\n\nUser\n＝ SaaS全体で識別される利用主体\n\n\nUserは通常、次のような情報を持ちます。\n\nUser\n├── userId\n├── displayName\n├── emailAddress\n├── status\n├── createdAt\n└── updatedAt\n\n\nただし、Userが存在するだけでは、特定のTenantに所属しているとは限りません。\n\n4.2 Membershipとは何か\nMembershipは、UserとTenantの間に存在する「所属関係」です。\n\nMembership\n＝ あるUserが、あるTenantに参加しているという事実\n\n\n関係としては、次の形になります。\n\nUser\n  ↓\nMembership\n  ↓\nTenant\n\n\n一見すると、単純に次のようにしてもよさそうに見えます。\n\nUser belongsTo Tenant\n\n\nしかし、これでは複数テナントへの所属をうまく扱えません。\n\n4.3 同じUserが複数Tenantに所属できる\nたとえば山田太郎さんが、次の2つのTenantに参加しているとします。\n\nUser: 山田太郎\n├── Tenant Aでは「管理者」\n└── Tenant Bでは「閲覧者」\n\n\nこの場合、User自体は同じですが、所属状態や役割が異なります。\n\n\ngraph LR\n    U[User\n山田太郎]\n\n    M1[Membership A]\n    M2[Membership B]\n\n    T1[Tenant A]\n    T2[Tenant B]\n\n    R1[管理者Role]\n    R2[閲覧者Role]\n\n    U --> M1\n    U --> M2\n    M1 --> T1\n    M2 --> T2\n    M1 --> R1\n    M2 --> R2\n\n\n\nしたがって、Roleは原則としてUserに直接付けるのではなく、Membershipに付けます。\n\n推奨：\n\nMembership assignedRole Role\n\n\n単純化しすぎたモデルでは、次のようになりがちです。\n\n非推奨：\n\nUser hasRole Role\n\n\nUserに直接Roleを付けてしまうと、「どのTenantでのRoleなのか」が分からなくなります。\n\n4.4 Membershipの基本属性\nMembership\n├── membershipId\n├── status\n├── joinedAt\n├── leftAt\n└── invitationStatus\n\n\nstatusの例は次のとおりです。\n\ninvited\nactive\nsuspended\nremoved\n\n\n\n--------------------------------------------------------------------------------\n\n5. RoleとPermission\n5.1 Roleとは何か\nRoleは、権限をまとめたものです。\n\nRole\n＝ Permissionの集合\n\n\nたとえば、次のようなRoleが考えられます。\n\nTenant Administrator\nManager\nEditor\nViewer\nBilling Administrator\n\n\n日本語では、次のような名称になるでしょう。\n\nテナント管理者\n部門管理者\n編集者\n閲覧者\n請求管理者\n\n\n5.2 Permissionとは何か\nPermissionは、何らかの操作を許可する最小単位です。\n\nPermission\n＝ 何に対して、何をしてよいか\n\n\n最初は単純に、操作を文字列で表現できます。\n\ntenant.read\ntenant.update\nmember.read\nmember.invite\nmember.remove\nresource.read\nresource.create\nresource.update\nresource.delete\nbilling.read\nbilling.update\n\n\nたとえば「管理者Role」は、次のPermissionを持ちます。\n\nRole: Tenant Administrator\n├── tenant.read\n├── tenant.update\n├── member.read\n├── member.invite\n├── member.remove\n├── resource.read\n├── resource.create\n├── resource.update\n└── resource.delete\n\n\n「閲覧者Role」は、次のようになります。\n\nRole: Viewer\n├── tenant.read\n├── member.read\n└── resource.read\n\n\n5.3 権限判定の流れ\nあるUserが操作できるかどうかは、次の順番で判定できます。\n\nUser\n→ Membership\n→ Role\n→ Permission\n\n\nたとえば、山田太郎さんがTenant Aのデータを削除できるかを確認する場合は、次のように考えます。\n\n1. 山田太郎のUserを特定する\n2. Tenant Aに対するMembershipを取得する\n3. Membershipに割り当てられたRoleを取得する\n4. Roleが resource.delete を持っているか確認する\n\n\n概念的には、次のような条件です。\n\nUserがTenant内で操作可能\n＝\n有効なMembershipが存在する\nかつ\nMembershipのRoleが必要なPermissionを持つ\n\n\n5.4 Roleの種類\nRoleには大きく2種類あります。\n\nシステム定義Role\nSaaS提供者が最初から用意するRoleです。\n\nAdministrator\nEditor\nViewer\n\n\nテナント定義Role\nTenantの管理者が独自に作るRoleです。\n\n東日本支店管理者\n監査担当者\n外部委託先\n承認のみ可能な担当者\n\n\nいまのところは、どちらも同じRoleとして扱い、必要になったら次の属性を追加します。\n\nRole\n├── roleType: system | tenant\n└── definedByTenant\n\n\n\n--------------------------------------------------------------------------------\n\n6. SubscriptionとPlan\n6.1 Planとは何か\nPlanは、SaaS提供者が定義する商品・提供条件です。\n\nPlan\n＝ SaaSとして販売される標準的なサービス構成\n\n\n例として、次のようなPlanがあります。\n\nFree\nStandard\nProfessional\nEnterprise\n\n\nPlanには次のような情報が含まれます。\n\nPlan\n├── planId\n├── name\n├── description\n├── status\n└── billingCycle\n\n\nただし、Planは「契約そのもの」ではありません。\n\n6.2 Subscriptionとは何か\nSubscriptionは、TenantがPlanを利用しているという契約・購読関係です。\n\nSubscription\n＝ TenantによるPlanの利用契約\n\n\n関係は次のようになります。\n\nTenant\n→ Subscription\n→ Plan\n\n\nたとえば、\n\nTenant: 株式会社ABC\nSubscription: 2026年度契約\nPlan: Enterprise\n\n\nという形です。\n\n6.3 PlanとSubscriptionを分ける理由\nPlanに直接Tenantを接続すると契約固有の情報を表現できず、契約にはTenantごとに次の違いがあります。\n\n * 契約開始日\n * 契約終了日\n * 無料トライアル期間\n * 自動更新の有無\n * 個別割引\n * 契約ユーザー数\n * 契約ストレージ容量\n * 契約状態\n\nこれらはPlanではなくSubscriptionに属します。\n\nPlan\n＝ 標準商品\n\nSubscription\n＝ 個別契約\n\n\n6.4 Subscriptionの基本属性\nSubscription\n├── subscriptionId\n├── status\n├── startedAt\n├── endedAt\n├── trialEndsAt\n├── autoRenew\n└── contractedQuantity\n\n\nstatusの例は次のとおりです。\n\ntrial\nactive\npast_due\nsuspended\ncancelled\nexpired\n\n\n\n--------------------------------------------------------------------------------\n\n7. Resource\n7.1 Resourceとは何か\nResourceは、そのSaaSで管理される業務データや機能上の対象です。\n\nResource\n＝ TenantがSaaS上で所有・管理する対象\n\nResourceは抽象的な概念であり、SaaSの種類によって具体的なResourceは変わります。\n\nSaaSの種類Resourceの例CRM顧客、商談、活動履歴会計SaaS仕訳、請求書、勘定科目プロジェクト管理プロジェクト、タスク、コメント勤怠管理\n勤務記録、申請、承認セキュリティSaaSアラート、検出結果、ポリシーファイル共有ファイル、フォルダチャットチャンネル、メッセージ\n共通オントロジーでは、それらをすべてResourceの一種として扱います。\n\nCustomer is-a Resource\nInvoice is-a Resource\nProject is-a Resource\nTask is-a Resource\nSecurityAlert is-a Resource\n\nここで注意していただきたいのは、CustomerやTaskは共通オントロジーの語彙ではないということです。共通オントロジーが定義するのはResource\nまでで、その下にどんなクラスを作るかは、各SaaSが自分で決めます。\n\n# 共通オントロジー側（saas:）が定義するのはここまで\nsaas:Resource a owl:Class .\n\n# 各SaaSが、自分のドメインに合わせて継承する\ncrm:Customer rdfs:subClassOf saas:Resource .\ncrm:Deal     rdfs:subClassOf saas:Resource .\n\nつまりResourceは、各SaaSが拡張するための接続点です。第1回で決めた8つの中心概念のうち、Resource\nだけが「中身が空っぽ」なのは、そこが各社の個性が入る場所だからです。\n\n以降の説明では、9章と同じCRM SaaSを例にして、Customer（顧客）を使って進めます。\n\n7.2 ResourceはTenantに所有される\n最も重要な関係は次のものです。\n\nTenant ownsResource Resource\n\nこの関係は、逆向きから見ることもできます。\n\nResource belongsToTenant Tenant\n\nこの2つは同じ1本の関係を、どちら側から見ているかの違いでしかありません。オントロジーでは、これを逆関係として明示的に宣言しておきます。\n\nsaas:ownsResource a owl:ObjectProperty ;\n    rdfs:domain saas:Tenant ;\n    rdfs:range  saas:Resource .\n\nsaas:belongsToTenant a owl:ObjectProperty ;\n    owl:inverseOf saas:ownsResource .\n\nこう書いておくと、Tenant → Resource\nのどちらか一方を記録するだけで、推論エンジンがもう一方を自動的に導いてくれます。「このTenantが持つResourceは？」と「このResourceの持ち主は？」の両方を、データを二重に持たずに引けるということです。\n\nそして原則として、すべての業務データに所有Tenantが存在します。\n\nTenant AのUser\n→ Tenant AのResourceにはアクセス可能\n\nTenant AのUser\n→ Tenant BのResourceには原則アクセス不可\n\nマルチテナントSaaSでは、このTenantとの関係がデータ分離の根拠になります。\n\nなお、Resourceの中には特定のTenantに属さないもの（国コードや通貨コードなど、SaaS全体で共有されるマスターデータ）もあります。この例外の扱いは10.2で整理します。\n\nResourceが持つもの\nResourceの構成要素は、次の2種類に分けて考えます。\n\n他の概念との関係として表現するもの\n\nbelongsToTenant  → どのTenantのものか\ncreatedBy        → 誰が作ったか\nクラス（型）      → 何のResourceか（Customer / Deal / Task …）\n\nResource自身が持つ値（リテラル）\n\nresourceId\nname\ncreatedAt\nupdatedAt\n\n「何のResourceか」を、resourceType: \"Customer\"のような文字列ではなくクラスで表す\nのがポイントです。文字列だと、それがCustomerであることをオントロジーは理解できません。クラスにしておけば、crm:Customer\nと書いた時点で「これはResourceでもある」と推論されますし、crm:Customer\nだけに成り立つルール（たとえば「顧客は必ずメールアドレスを持つ」）を後から追加できます。\n\nこの「関係にするか、値にするか」という切り分けが、次の7.3のテーマです。\n\n7.3 tenant_idを「カラム」ではなく「関係」として表現する\nデータベース設計では、多くの場合、Resourceにtenant_idを持たせます。\n\ncustomers\n├── customer_id\n├── tenant_id\n├── name\n└── status\n\ntenant_idは、RDBの世界では「customersテーブルの1カラム」でしかありません。値としては\"tenant-abc\"\nという文字列やUUIDが入っているだけで、それがTenantを指していることは、アプリケーションのコードや開発者の頭の中にしか存在しません。\n\nオントロジーでは、これをTenantという実体そのものへのリンクとして表現します。\n\nCustomer belongsToTenant Tenant\n\n実際のデータで書くと、次のようになります。\n\nex:customer-xyz a crm:Customer ;\n    saas:belongsToTenant ex:tenant-abc ;\n    saas:createdBy ex:user-yamada ;\n    saas:name \"株式会社XYZ\" .\n\nsaas:belongsToTenant ex:tenant-abcのex:tenant-abcは、文字列ではなくex:tenant-abcという\nTenantの実体を指しています。だから、この1行を起点にして、そのTenantの契約情報にも、所属メンバーにも、他の所有データにも辿っていけます。\n\nそして7.2で逆関係を宣言してあるので、Tenant側から見ることもできます。\n\nex:tenant-abc saas:ownsResource ex:customer-xyz\n（上のトリプルから自動的に導出される）\n\nこの関係を明示すると何が嬉しいのか\ntenant_idをカラムではなく関係として書くことで、次のようなことがたどれるようになります。\n\n * 誰がデータを所有するのかex:customer-xyz → ex:tenant-abc → 株式会社ABCテナント\n * どの権限体系が適用されるのかex:tenant-abcのMembershipを持つUserの、そのRoleのPermissionが適用される\n * どの契約制限が適用されるのかex:tenant-abc → Subscription → Plan → そのPlanの上限値\n * どのデータ保持ポリシーが適用されるのかex:tenant-abcに紐づく保持期間ルール（第◯回で追加予定）\n\nRDBでこれをやろうとすると、テーブルを何段もJOINするクエリを書くことになります。オントロジーでは、これらはすべて「リンクをたどる」という同じ操作になります。しかも、\nex:customer-xyzがcrm:Customerであり、crm:Customerがsaas:Resource\nのサブクラスであることも宣言済みなので、「Tenant Aが所有するすべてのResource」を聞けば、CustomerもDealも、あとから追加した\nActivityも、まとめて返ってきます。\n\nこれが、7.1でResourceを「中身が空っぽの接続点」にしておいた理由です。\n\n\n--------------------------------------------------------------------------------\n\n8. 最小の関係一覧\nこのオントロジーの最小関係は、次のとおりです。\n\n主語関係目的語意味UserhasMembershipMembershipUserが所属関係を持つMembershipbelongsToUserUser\nMembershipの対象UserMembershipbelongsToTenantTenantMembershipの所属先Membership\nassignedRoleRole所属内でRoleを持つRolegrantsPermissionPermissionRoleが操作を許可するTenant\nhasSubscriptionSubscriptionTenantが契約を持つSubscriptionbasedOnPlanPlan契約が利用するPlan\nTenantownsResourceResourceTenantがデータを所有するResourcecreatedByUserResourceを作成したUser\nResourcebelongsToTenantTenantResourceの所属先Tenant関係を図にすると、次のようになります。\n\n\ngraph TD\n    Tenant[Tenant]\n\n    Membership[Membership]\n    User[User]\n    Role[Role]\n    Permission[Permission]\n\n    Subscription[Subscription]\n    Plan[Plan]\n\n    Resource[Resource]\n\n    User -->|hasMembership| Membership\n    Membership -->|belongsToTenant| Tenant\n    Membership -->|assignedRole| Role\n    Role -->|grantsPermission| Permission\n\n    Tenant -->|hasSubscription| Subscription\n    Subscription -->|basedOnPlan| Plan\n\n    Tenant -->|ownsResource| Resource\n    Resource -->|createdBy| User\n\n\n\n\n--------------------------------------------------------------------------------\n\n9. 具体例\n9.1 登場するインスタンス\n次のようなCRM SaaSを考えます。\n\nTenant\n- 株式会社ABC\n\nUser\n- 山田太郎\n- 佐藤花子\n\nMembership\n- 山田太郎の株式会社ABCへの所属\n- 佐藤花子の株式会社ABCへの所属\n\nRole\n- 管理者\n- 営業担当者\n\nPermission\n- customer.read\n- customer.create\n- customer.update\n- customer.delete\n- member.invite\n\nPlan\n- Professional Plan\n\nSubscription\n- 株式会社ABCのProfessional契約\n\nResource\n- 顧客「株式会社XYZ」\n- 商談「基幹システム刷新案件」\n\n\n関係は次のようになります。\n\n山田太郎\n→ 株式会社ABCへのMembershipを持つ\n→ 管理者Roleを持つ\n\n佐藤花子\n→ 株式会社ABCへのMembershipを持つ\n→ 営業担当者Roleを持つ\n\n管理者Role\n→ customer.read\n→ customer.create\n→ customer.update\n→ customer.delete\n→ member.invite\n\n営業担当者Role\n→ customer.read\n→ customer.create\n→ customer.update\n\n株式会社ABC\n→ Professional PlanのSubscriptionを持つ\n\n株式会社ABC\n→ 顧客「株式会社XYZ」を所有する\n→ 商談「基幹システム刷新案件」を所有する\n\n\n9.2 インスタンス図\n\ngraph TD\n    T[株式会社ABC\nTenant]\n\n    U1[山田太郎\nUser]\n    U2[佐藤花子\nUser]\n\n    M1[Membership 001]\n    M2[Membership 002]\n\n    R1[管理者\nRole]\n    R2[営業担当者\nRole]\n\n    S[Professional契約\nSubscription]\n    P[Professional\nPlan]\n\n    C[株式会社XYZ\nCustomer Resource]\n    D[基幹システム刷新案件\nDeal Resource]\n\n    U1 --> M1\n    M1 --> T\n    M1 --> R1\n\n    U2 --> M2\n    M2 --> T\n    M2 --> R2\n\n    T --> S\n    S --> P\n\n    T --> C\n    T --> D\n\n\n\n--------------------------------------------------------------------------------\n\n10. 最低限のルール\nオントロジーを単なる用語集で終わらせず、意味のあるモデルにするため、最低限の制約を定義します。\n\n10.1 Membershipのルール\nMembershipは、必ず1つのUserに属する。\nMembershipは、必ず1つのTenantに属する。\n\n\nさらに、基本的には次の組み合わせを一意にします。\n\nUser × Tenantごとに、有効なMembershipは最大1つ\n\n\n同じUserが同じTenantに、重複して所属するのを防ぐためです。\n\n10.2 Resourceのルール\nTenant固有Resourceは、必ず1つのTenantに属する。\n\n\nつまり、通常の業務データについては、次を必須とします。\n\nResource belongsToTenant exactly 1 Tenant\n\n\nただし、SaaS全体で共有されるマスターデータは例外で、たとえば次のようなものが挙げられます。\n\n * 国コード\n * 通貨コード\n * SaaS提供者が定義したテンプレート\n * システム共通Role\n * システム共通Plan\n\nなどは、特定のTenantに属さないことがあります。\n\n10.3 Subscriptionのルール\nSubscriptionは、必ず1つのTenantに属する。\nSubscriptionは、必ず1つのPlanを参照する。\n\n\nただし、1つのTenantが複数のSubscriptionを持つ場合もあります。\n\nたとえば、\n\n基本プランのSubscription\n追加ストレージのSubscription\nAI機能追加のSubscription\n監査ログ追加のSubscription\n\n\nなどです。\n\n10.4 RoleとPermissionのルール\nRoleは、0個以上のPermissionを持つ。\nMembershipは、0個以上のRoleを持つ。\n\n\n最初は1 Membershipにつき1 Roleでも構いませんが、将来的には次のような複数Roleが必要になる可能性があります。\n\n山田太郎\n├── 営業担当者Role\n├── 請求閲覧者Role\n└── セキュリティ監査者Role\n\n\nそのため、オントロジー上は多対多にしておく方が拡張しやすくなります。\n\n\n--------------------------------------------------------------------------------\n\n11. RDF/Turtleによる最小表現\nオントロジーとして実装する場合の、非常に単純化したTurtle表現です。\n\n@prefix saas: <https://example.com/ontology/saas#> .\n@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n@prefix owl:  <http://www.w3.org/2002/07/owl#> .\n\nsaas:Tenant a owl:Class .\nsaas:User a owl:Class .\nsaas:Membership a owl:Class .\nsaas:Role a owl:Class .\nsaas:Permission a owl:Class .\nsaas:Subscription a owl:Class .\nsaas:Plan a owl:Class .\nsaas:Resource a owl:Class .\n\nsaas:belongsToUser a owl:ObjectProperty ;\n    rdfs:domain saas:Membership ;\n    rdfs:range saas:User .\n\nsaas:belongsToTenant a owl:ObjectProperty ;\n    rdfs:domain saas:Membership ;\n    rdfs:range saas:Tenant .\n\nsaas:assignedRole a owl:ObjectProperty ;\n    rdfs:domain saas:Membership ;\n    rdfs:range saas:Role .\n\nsaas:grantsPermission a owl:ObjectProperty ;\n    rdfs:domain saas:Role ;\n    rdfs:range saas:Permission .\n\nsaas:hasSubscription a owl:ObjectProperty ;\n    rdfs:domain saas:Tenant ;\n    rdfs:range saas:Subscription .\n\nsaas:basedOnPlan a owl:ObjectProperty ;\n    rdfs:domain saas:Subscription ;\n    rdfs:range saas:Plan .\n\nsaas:ownsResource a owl:ObjectProperty ;\n    rdfs:domain saas:Tenant ;\n    rdfs:range saas:Resource .\n\nsaas:createdBy a owl:ObjectProperty ;\n    rdfs:domain saas:Resource ;\n    rdfs:range saas:User .\n\n\n\n--------------------------------------------------------------------------------\n\n12. 具体的なデータのTurtle表現\n先ほどの株式会社ABCの例を表すと、次のようになります。\n\n@prefix saas: <https://example.com/ontology/saas#> .\n@prefix ex:   <https://example.com/data/> .\n\nex:tenant-abc a saas:Tenant ;\n    saas:name \"株式会社ABCテナント\" ;\n    saas:hasSubscription ex:subscription-abc-professional ;\n    saas:ownsResource ex:customer-xyz .\n\nex:user-yamada a saas:User ;\n    saas:displayName \"山田太郎\" .\n\nex:membership-yamada-abc a saas:Membership ;\n    saas:belongsToUser ex:user-yamada ;\n    saas:belongsToTenant ex:tenant-abc ;\n    saas:assignedRole ex:role-admin .\n\nex:role-admin a saas:Role ;\n    saas:name \"テナント管理者\" ;\n    saas:grantsPermission\n        ex:permission-customer-read,\n        ex:permission-customer-create,\n        ex:permission-customer-update,\n        ex:permission-customer-delete .\n\nex:plan-professional a saas:Plan ;\n    saas:name \"Professional\" .\n\nex:subscription-abc-professional a saas:Subscription ;\n    saas:basedOnPlan ex:plan-professional ;\n    saas:status \"active\" .\n\nex:customer-xyz a saas:Resource ;\n    saas:name \"株式会社XYZ\" ;\n    saas:resourceType \"Customer\" ;\n    saas:createdBy ex:user-yamada .\n\n\n\n--------------------------------------------------------------------------------\n\n13. このモデルで答えられる質問\nオントロジーを作る目的は、単にデータを保存することではなく、意味をたどって質問に答えられるようにすることです。この最小モデルでも、次のような質問に答えられます。\n\nTenantに関する質問\nこのUserは、どのTenantに所属しているか。\nこのTenantには、何人のUserが参加しているか。\nこのTenantが所有しているResourceは何か。\n\n\n権限に関する質問\nこのUserは、Tenant AでどのRoleを持っているか。\nこのUserは、顧客データを削除できるか。\nmember.inviteを持つMembershipはどれか。\n管理者権限を持つUserは誰か。\n\n\n契約に関する質問\nこのTenantは、どのPlanを利用しているか。\nProfessional Planを利用しているTenantはどれか。\n契約が停止中のTenantはどれか。\n\n\nResourceに関する質問\nこのResourceは、どのTenantのものか。\nこのUserが作成したResourceは何か。\nTenant AのすべてのResourceは何か。\n\n\n\n--------------------------------------------------------------------------------\n\n14. あえて含めていない概念\n最初のバージョンをシンプルにするため、たとえば次の概念などはまだ入れていません。\n\nOrganization\nOrganizationUnit\nWorkspace\nGroup\nTeam\nCustomerAccount\nInvoice\nPayment\nEntitlement\nFeature\nUsage\nQuota\nAuthenticationIdentity\nAPIClient\nServiceAccount\nAuditEvent\nPolicy\nDataRetentionRule\nPartner\nReseller\n\n\nこれらは重要ですが、最初から入れるとTenant、会社、部署、Workspace、契約者などの違いが分かりにくくなるため、まずは次の骨格を固定するのがよいでしょう。\n\nTenant\n├── Membership\n│   ├── User\n│   └── Role\n│       └── Permission\n│\n├── Subscription\n│   └── Plan\n│\n└── Resource\n\n\n\n--------------------------------------------------------------------------------\n\n15. このオントロジーのポイント\nこのモデルで最も重要なのは、次の4点です。\n\n15.1 Tenantは会社ではなく境界である\nTenant\n＝ SaaS上でユーザー、権限、契約、データを分離する境界\n\n\n15.2 Userと所属は別である\nUser\n＝ 人またはログイン主体\n\nMembership\n＝ Userが特定Tenantに参加している関係\n\n\n15.3 Planと契約は別である\nPlan\n＝ SaaS提供者が定義した商品\n\nSubscription\n＝ Tenantごとの個別契約\n\n\n15.4 業務データはResourceとして抽象化する\nResource\n＝ 各SaaS固有の業務オブジェクトの共通上位概念\n\n\n\n--------------------------------------------------------------------------------\n\n16. 最終的な最小モデル\n最も単純に表すと、このB2B SaaSオントロジーは次の文章に集約できます。\n\nTenantは、SaaSの論理的な利用境界である。\n\nUserは、Membershipを通じてTenantに所属する。\n\nMembershipにはRoleが割り当てられる。\n\nRoleはPermissionを付与する。\n\nTenantはSubscriptionを通じてPlanを利用する。\n\nTenantはResourceを所有する。\n\n\n図にすると、次の形です。\n\n\ngraph LR\n    User --> Membership\n    Membership --> Tenant\n    Membership --> Role\n    Role --> Permission\n\n    Tenant --> Subscription\n    Subscription --> Plan\n\n    Tenant --> Resource\n\n\n\n\n\nこれを、B2B\nSaaS共通オントロジーの最初のベースとし、SaaSオントロジーv0にむけての最初のバージョン、SaaSオントロジーv.1とでも呼んでおきましょう。\n\n第２回は、 Tenantは会社なのか？ Organization／Workspace／Teamを追加して「会社・契約単位・作業空間・部署」の違いを整理する \nで行きたいと思います\n\nアディオス！","html":"<h1></h1><h2 id=\"0-%E3%81%93%E3%81%AE%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA%E3%81%AE%E7%9B%AE%E7%9A%84\">0. このシリーズの目的</h2><p>昨今、セマンティックレイヤー・オントロジーなどの記事がそこそこ盛り上がっている気がします。生成AIの時代になってきて、どちらも重要かつ実用的になってきたからかと思います。</p><p>しかし！いざ、オントロジーをやってみよう！と思っても、かなり内容が難しく、結局どうして良いのかわからない、これで正しいのかわからない、などハマりがちかと思います。少なくともぼくはハマりまくってます。</p><p>そこで！実際に自分たちが常に触れている、「B2B SaaS」を題材にしてオントロジーを作ってみて、使ってみて、理解を深めよう！というテーマで連載をしていきたいと思います。だいたい、10回くらいでまとめられればと思っておりますが、もっと多くなってしまうかもしれません。。。</p><p>そして、この連載が終わるころには、「SaaSっていったら、ベースとしてのオントロジーってこれだろ」っていうのがそこそこできているはずです。はずです。それを、「SaaSオントロジーv0」と呼ぼうと思います。そのうえで、v0を実際のみなさまのSaaSに当てはめて拡張して、おのおののSaaSのv1を作って活用していこう！というコンセプトです。</p><p>現在、ある程度まで書き進めているのですが、このSaaSオントロジー構築をやっていくと、とってもSaaSの理解が深まります。なので、オントロジーに興味が無くても、ディープなSaaSの世界をよりダイブして理解したい方々にはオススメ！と言える内容にしていきたいと思いますので、ぜひご一緒にオントロジー入門してみていただけますと幸いです。</p><p>※途中で、 RDF/Turtle を利用したオントロジーを表現したファイルが出てきます。ここで、うわ！ってなった方は、 <a href=\"https://tech.anti-pattern.co.jp/rdfrdfsowlturtlenowei-iwozheng-li-site-huairudeli-jie-suruontoroziru-men/\">こちらの記事で RDF/Turtle とは？</a> というのを確認してみていただければと思います！</p><figure class=\"kg-card kg-bookmark-card\"><a class=\"kg-bookmark-container\" href=\"https://tech.anti-pattern.co.jp/rdfrdfsowlturtlenowei-iwozheng-li-site-huairudeli-jie-suruontoroziru-men/\"><div class=\"kg-bookmark-content\"><div class=\"kg-bookmark-title\">RDF・RDFS・OWL・Turtleの違いを整理して、ファイルで理解するオントロジー入門</div><div class=\"kg-bookmark-description\">「オントロジーを勉強し始めたけれど、RDF、RDFS、OWL、Turtleの違いが分からない」 これは、たぶんオントロジー初心者が最初につまずきやすいポイントです。少なくとも、ぼくはこれらが出てきたときに思考が止まりました。。。なので、これらだけをザックリ解説する記事になっております。 特に混乱しやすいのが、次のような疑問です。 * RDFとOWLは別のファイル形式なのか * .ttlファイルはRDFなのかOWLなのか * ex:とは何を意味するのか * URIはどのように決めればよいのか * RDF/XMLでは、なぜURIの書き方が場所によって違うのか この記事では、社…</div><div class=\"kg-bookmark-metadata\"><img class=\"kg-bookmark-icon\" src=\"https://tech.anti-pattern.co.jp/icons/icon-512x512.png\"><span class=\"kg-bookmark-author\">Anti-Pattern Inc. Engineering Blog</span><span class=\"kg-bookmark-publisher\">Akihiro YAGASAKI</span></div></div><div class=\"kg-bookmark-thumbnail\"><img src=\"https://ghost.tech.anti-pattern.co.jp/content/images/2026/08/ontology-viewer-overview.png\"></div></a></figure><h2 id=\"1-%E3%81%93%E3%81%AE%E3%82%AA%E3%83%B3%E3%83%88%E3%83%AD%E3%82%B8%E3%83%BC%E3%81%AE%E7%9B%AE%E7%9A%84\">1. このオントロジーの目的</h2><p>B2B SaaSには、業種や機能が違っていても、かなり共通した構造があります。たとえば、会計SaaS、CRM、勤怠管理、セキュリティ管理、プロジェクト管理のいずれであっても、一般的に次のような概念が登場します。</p><ul><li>SaaSを利用する単位</li><li>SaaSにログインする人</li><li>人がどの利用単位に所属しているか</li><li>その人に何が許可されているか</li><li>どのプランを契約しているか</li><li>SaaS上で作成・管理されるデータ</li></ul><p>これらを、特定のSaaSに依存しない形で表現するのが、このオントロジーの目的です。</p><p>最初のバージョンでは、次の8つだけを中心概念とします。</p><ol><li>Tenant</li><li>User</li><li>Membership</li><li>Role</li><li>Permission</li><li>Subscription</li><li>Plan</li><li>Resource</li></ol><hr><h1 id=\"2-%E5%85%A8%E4%BD%93%E5%83%8F\">2. 全体像</h1><!--kg-card-begin: html--><pre class=\"mermaid\">\nclassDiagram\n    class Tenant {\n        tenantId\n        name\n        status\n    }\n\n    class User {\n        userId\n        displayName\n        status\n    }\n\n    class Membership {\n        membershipId\n        status\n        joinedAt\n    }\n\n    class Role {\n        roleId\n        name\n    }\n\n    class Permission {\n        permissionId\n        action\n    }\n\n    class Subscription {\n        subscriptionId\n        status\n        startedAt\n        endedAt\n    }\n\n    class Plan {\n        planId\n        name\n    }\n\n    class Resource {\n        resourceId\n        resourceType\n        name\n    }\n\n    User \"1\" --> \"0..*\" Membership : hasMembership\n    Tenant \"1\" --> \"0..*\" Membership : hasMember\n    Membership \"0..*\" --> \"0..*\" Role : assignedRole\n    Role \"0..*\" --> \"0..*\" Permission : grants\n    Tenant \"1\" --> \"0..*\" Subscription : hasSubscription\n    Subscription \"0..*\" --> \"1\" Plan : basedOn\n    Tenant \"1\" --> \"0..*\" Resource : owns\n    User \"0..1\" --> \"0..*\" Resource : created\n</pre><!--kg-card-end: html--><p>このモデルの中心は、名前のとおり <strong>Tenant</strong> です。</p><p>Tenantを中心として、</p><ul><li>誰が参加しているか</li><li>誰がどの権限を持つか</li><li>どのプランを契約しているか</li><li>どの業務データを所有しているか</li></ul><p>を表現します。</p><hr><h1 id=\"3-%E6%9C%80%E9%87%8D%E8%A6%81%E6%A6%82%E5%BF%B5%EF%BC%9Atenant\">3. 最重要概念：Tenant</h1><h2 id=\"31-tenant%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B\">3.1 Tenantとは何か</h2><p>Tenantは、SaaSにおける<strong>論理的な利用境界</strong>です。</p><pre><code class=\"language-text\">Tenant\n＝ ユーザー、権限、契約、データを分離する単位\n</code></pre><p>典型的には、次のようなものがTenantになります。</p><ul><li>企業</li><li>部署</li><li>支店</li><li>プロジェクト</li><li>顧客専用環境</li><li>学校</li><li>医療法人</li><li>自治体</li><li>販売代理店</li><li>グループ会社</li></ul><p>重要なのは、</p><pre><code class=\"language-text\">Tenant ＝ 会社\n</code></pre><p>とは限らないことです。</p><h2 id=\"32-%E4%BC%9A%E7%A4%BE%E3%81%A8tenant%E3%82%92%E5%90%8C%E4%B8%80%E8%A6%96%E3%81%97%E3%81%AA%E3%81%84\">3.2 会社とTenantを同一視しない</h2><p>たとえば、株式会社ABCがSaaSを利用しているとしても、Tenantの作り方には複数の可能性があります。</p><h3 id=\"%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3a%EF%BC%9A%E4%BC%9A%E7%A4%BE%E5%85%A8%E4%BD%93%E3%81%A71%E3%83%86%E3%83%8A%E3%83%B3%E3%83%88\">パターンA：会社全体で1テナント</h3><pre><code class=\"language-text\">株式会社ABC\n└── Tenant ABC\n</code></pre><h3 id=\"%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3b%EF%BC%9A%E9%83%A8%E7%BD%B2%E3%81%94%E3%81%A8%E3%81%AB%E5%88%A5%E3%83%86%E3%83%8A%E3%83%B3%E3%83%88\">パターンB：部署ごとに別テナント</h3><pre><code class=\"language-text\">株式会社ABC\n├── 営業部Tenant\n├── 開発部Tenant\n└── 管理部Tenant\n</code></pre><h3 id=\"%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3c%EF%BC%9A%E4%BC%9A%E7%A4%BE%E3%81%A8%E5%AD%90%E4%BC%9A%E7%A4%BE%E3%81%A7%E5%88%A5%E3%83%86%E3%83%8A%E3%83%B3%E3%83%88\">パターンC：会社と子会社で別テナント</h3><pre><code class=\"language-text\">ABCグループ\n├── 株式会社ABC Tenant\n├── ABC販売 Tenant\n└── ABCシステムズ Tenant\n</code></pre><p>したがって、Tenantは「法人」ではなく、あくまでSaaS上の利用境界として定義します。</p><h2 id=\"33-tenant%E3%81%AE%E5%9F%BA%E6%9C%AC%E5%B1%9E%E6%80%A7\">3.3 Tenantの基本属性</h2><pre><code class=\"language-text\">Tenant\n├── tenantId\n├── name\n├── status\n├── createdAt\n└── updatedAt\n</code></pre><p><code>status</code>には、たとえば次の値が入ります。</p><pre><code class=\"language-text\">active\nsuspended\nclosed\n</code></pre><hr><h1 id=\"4-user%E3%81%A8membership%E3%82%92%E5%88%86%E9%9B%A2%E3%81%99%E3%82%8B\">4. UserとMembershipを分離する</h1><p>このオントロジーで特に重要なのが、<strong>UserとMembershipを別の概念にすること</strong>です。</p><h2 id=\"41-user%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B\">4.1 Userとは何か</h2><p>Userは、SaaSを利用する人、またはログイン主体です。</p><pre><code class=\"language-text\">User\n＝ SaaS全体で識別される利用主体\n</code></pre><p>Userは通常、次のような情報を持ちます。</p><pre><code class=\"language-text\">User\n├── userId\n├── displayName\n├── emailAddress\n├── status\n├── createdAt\n└── updatedAt\n</code></pre><p>ただし、Userが存在するだけでは、特定のTenantに所属しているとは限りません。</p><h2 id=\"42-membership%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B\">4.2 Membershipとは何か</h2><p>Membershipは、UserとTenantの間に存在する「所属関係」です。</p><pre><code class=\"language-text\">Membership\n＝ あるUserが、あるTenantに参加しているという事実\n</code></pre><p>関係としては、次の形になります。</p><pre><code class=\"language-text\">User\n  ↓\nMembership\n  ↓\nTenant\n</code></pre><p>一見すると、単純に次のようにしてもよさそうに見えます。</p><pre><code class=\"language-text\">User belongsTo Tenant\n</code></pre><p>しかし、これでは複数テナントへの所属をうまく扱えません。</p><h2 id=\"43-%E5%90%8C%E3%81%98user%E3%81%8C%E8%A4%87%E6%95%B0tenant%E3%81%AB%E6%89%80%E5%B1%9E%E3%81%A7%E3%81%8D%E3%82%8B\">4.3 同じUserが複数Tenantに所属できる</h2><p>たとえば山田太郎さんが、次の2つのTenantに参加しているとします。</p><pre><code class=\"language-text\">User: 山田太郎\n├── Tenant Aでは「管理者」\n└── Tenant Bでは「閲覧者」\n</code></pre><p>この場合、User自体は同じですが、所属状態や役割が異なります。</p><!--kg-card-begin: html--><pre class=\"mermaid\">\ngraph LR\n    U[User<br>山田太郎]\n\n    M1[Membership A]\n    M2[Membership B]\n\n    T1[Tenant A]\n    T2[Tenant B]\n\n    R1[管理者Role]\n    R2[閲覧者Role]\n\n    U --> M1\n    U --> M2\n    M1 --> T1\n    M2 --> T2\n    M1 --> R1\n    M2 --> R2\n\n</pre><!--kg-card-end: html--><p>したがって、Roleは原則としてUserに直接付けるのではなく、Membershipに付けます。</p><pre><code class=\"language-text\">推奨：\n\nMembership assignedRole Role\n</code></pre><p>単純化しすぎたモデルでは、次のようになりがちです。</p><pre><code class=\"language-text\">非推奨：\n\nUser hasRole Role\n</code></pre><p>Userに直接Roleを付けてしまうと、「どのTenantでのRoleなのか」が分からなくなります。</p><h2 id=\"44-membership%E3%81%AE%E5%9F%BA%E6%9C%AC%E5%B1%9E%E6%80%A7\">4.4 Membershipの基本属性</h2><pre><code class=\"language-text\">Membership\n├── membershipId\n├── status\n├── joinedAt\n├── leftAt\n└── invitationStatus\n</code></pre><p><code>status</code>の例は次のとおりです。</p><pre><code class=\"language-text\">invited\nactive\nsuspended\nremoved\n</code></pre><hr><h1 id=\"5-role%E3%81%A8permission\">5. RoleとPermission</h1><h2 id=\"51-role%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B\">5.1 Roleとは何か</h2><p>Roleは、権限をまとめたものです。</p><pre><code class=\"language-text\">Role\n＝ Permissionの集合\n</code></pre><p>たとえば、次のようなRoleが考えられます。</p><pre><code class=\"language-text\">Tenant Administrator\nManager\nEditor\nViewer\nBilling Administrator\n</code></pre><p>日本語では、次のような名称になるでしょう。</p><pre><code class=\"language-text\">テナント管理者\n部門管理者\n編集者\n閲覧者\n請求管理者\n</code></pre><h2 id=\"52-permission%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B\">5.2 Permissionとは何か</h2><p>Permissionは、何らかの操作を許可する最小単位です。</p><pre><code class=\"language-text\">Permission\n＝ 何に対して、何をしてよいか\n</code></pre><p>最初は単純に、操作を文字列で表現できます。</p><pre><code class=\"language-text\">tenant.read\ntenant.update\nmember.read\nmember.invite\nmember.remove\nresource.read\nresource.create\nresource.update\nresource.delete\nbilling.read\nbilling.update\n</code></pre><p>たとえば「管理者Role」は、次のPermissionを持ちます。</p><pre><code class=\"language-text\">Role: Tenant Administrator\n├── tenant.read\n├── tenant.update\n├── member.read\n├── member.invite\n├── member.remove\n├── resource.read\n├── resource.create\n├── resource.update\n└── resource.delete\n</code></pre><p>「閲覧者Role」は、次のようになります。</p><pre><code class=\"language-text\">Role: Viewer\n├── tenant.read\n├── member.read\n└── resource.read\n</code></pre><h2 id=\"53-%E6%A8%A9%E9%99%90%E5%88%A4%E5%AE%9A%E3%81%AE%E6%B5%81%E3%82%8C\">5.3 権限判定の流れ</h2><p>あるUserが操作できるかどうかは、次の順番で判定できます。</p><pre><code class=\"language-text\">User\n→ Membership\n→ Role\n→ Permission\n</code></pre><p>たとえば、山田太郎さんがTenant Aのデータを削除できるかを確認する場合は、次のように考えます。</p><pre><code class=\"language-text\">1. 山田太郎のUserを特定する\n2. Tenant Aに対するMembershipを取得する\n3. Membershipに割り当てられたRoleを取得する\n4. Roleが resource.delete を持っているか確認する\n</code></pre><p>概念的には、次のような条件です。</p><pre><code class=\"language-text\">UserがTenant内で操作可能\n＝\n有効なMembershipが存在する\nかつ\nMembershipのRoleが必要なPermissionを持つ\n</code></pre><h2 id=\"54-role%E3%81%AE%E7%A8%AE%E9%A1%9E\">5.4 Roleの種類</h2><p>Roleには大きく2種類あります。</p><h3 id=\"%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E5%AE%9A%E7%BE%A9role\">システム定義Role</h3><p>SaaS提供者が最初から用意するRoleです。</p><pre><code class=\"language-text\">Administrator\nEditor\nViewer\n</code></pre><h3 id=\"%E3%83%86%E3%83%8A%E3%83%B3%E3%83%88%E5%AE%9A%E7%BE%A9role\">テナント定義Role</h3><p>Tenantの管理者が独自に作るRoleです。</p><pre><code class=\"language-text\">東日本支店管理者\n監査担当者\n外部委託先\n承認のみ可能な担当者\n</code></pre><p>いまのところは、どちらも同じRoleとして扱い、必要になったら次の属性を追加します。</p><pre><code class=\"language-text\">Role\n├── roleType: system | tenant\n└── definedByTenant\n</code></pre><hr><h1 id=\"6-subscription%E3%81%A8plan\">6. SubscriptionとPlan</h1><h2 id=\"61-plan%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B\">6.1 Planとは何か</h2><p>Planは、SaaS提供者が定義する商品・提供条件です。</p><pre><code class=\"language-text\">Plan\n＝ SaaSとして販売される標準的なサービス構成\n</code></pre><p>例として、次のようなPlanがあります。</p><pre><code class=\"language-text\">Free\nStandard\nProfessional\nEnterprise\n</code></pre><p>Planには次のような情報が含まれます。</p><pre><code class=\"language-text\">Plan\n├── planId\n├── name\n├── description\n├── status\n└── billingCycle\n</code></pre><p>ただし、Planは「契約そのもの」ではありません。</p><h2 id=\"62-subscription%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B\">6.2 Subscriptionとは何か</h2><p>Subscriptionは、TenantがPlanを利用しているという契約・購読関係です。</p><pre><code class=\"language-text\">Subscription\n＝ TenantによるPlanの利用契約\n</code></pre><p>関係は次のようになります。</p><pre><code class=\"language-text\">Tenant\n→ Subscription\n→ Plan\n</code></pre><p>たとえば、</p><pre><code class=\"language-text\">Tenant: 株式会社ABC\nSubscription: 2026年度契約\nPlan: Enterprise\n</code></pre><p>という形です。</p><h2 id=\"63-plan%E3%81%A8subscription%E3%82%92%E5%88%86%E3%81%91%E3%82%8B%E7%90%86%E7%94%B1\">6.3 PlanとSubscriptionを分ける理由</h2><p>Planに直接Tenantを接続すると契約固有の情報を表現できず、契約にはTenantごとに次の違いがあります。</p><ul><li>契約開始日</li><li>契約終了日</li><li>無料トライアル期間</li><li>自動更新の有無</li><li>個別割引</li><li>契約ユーザー数</li><li>契約ストレージ容量</li><li>契約状態</li></ul><p>これらはPlanではなくSubscriptionに属します。</p><pre><code class=\"language-text\">Plan\n＝ 標準商品\n\nSubscription\n＝ 個別契約\n</code></pre><h2 id=\"64-subscription%E3%81%AE%E5%9F%BA%E6%9C%AC%E5%B1%9E%E6%80%A7\">6.4 Subscriptionの基本属性</h2><pre><code class=\"language-text\">Subscription\n├── subscriptionId\n├── status\n├── startedAt\n├── endedAt\n├── trialEndsAt\n├── autoRenew\n└── contractedQuantity\n</code></pre><p><code>status</code>の例は次のとおりです。</p><pre><code class=\"language-text\">trial\nactive\npast_due\nsuspended\ncancelled\nexpired\n</code></pre><hr><h1 id=\"7-resource\">7. Resource</h1><h2 id=\"71-resource%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B\">7.1 Resourceとは何か</h2><p>Resourceは、そのSaaSで管理される業務データや機能上の対象です。</p><pre><code class=\"language-text\">Resource\n＝ TenantがSaaS上で所有・管理する対象</code></pre><p>Resourceは抽象的な概念であり、SaaSの種類によって具体的なResourceは変わります。</p><!--kg-card-begin: html--><table><thead><tr><th scope=\"col\">SaaSの種類</th><th scope=\"col\">Resourceの例</th></tr></thead><tbody><tr><td>CRM</td><td>顧客、商談、活動履歴</td></tr><tr><td>会計SaaS</td><td>仕訳、請求書、勘定科目</td></tr><tr><td>プロジェクト管理</td><td>プロジェクト、タスク、コメント</td></tr><tr><td>勤怠管理</td><td>勤務記録、申請、承認</td></tr><tr><td>セキュリティSaaS</td><td>アラート、検出結果、ポリシー</td></tr><tr><td>ファイル共有</td><td>ファイル、フォルダ</td></tr><tr><td>チャット</td><td>チャンネル、メッセージ</td></tr></tbody></table><!--kg-card-end: html--><p>共通オントロジーでは、それらをすべてResourceの一種として扱います。</p><pre><code class=\"language-text\">Customer is-a Resource\nInvoice is-a Resource\nProject is-a Resource\nTask is-a Resource\nSecurityAlert is-a Resource</code></pre><p>ここで注意していただきたいのは、<strong><code>Customer</code>や<code>Task</code>は共通オントロジーの語彙ではない</strong>ということです。共通オントロジーが定義するのは<code>Resource</code>までで、その下にどんなクラスを作るかは、各SaaSが自分で決めます。</p><pre><code class=\"language-turtle\"># 共通オントロジー側（saas:）が定義するのはここまで\nsaas:Resource a owl:Class .\n\n# 各SaaSが、自分のドメインに合わせて継承する\ncrm:Customer rdfs:subClassOf saas:Resource .\ncrm:Deal     rdfs:subClassOf saas:Resource .</code></pre><p>つまり<code>Resource</code>は、<strong>各SaaSが拡張するための接続点</strong>です。第1回で決めた8つの中心概念のうち、<code>Resource</code>だけが「中身が空っぽ」なのは、そこが各社の個性が入る場所だからです。</p><p>以降の説明では、9章と同じCRM SaaSを例にして、<code>Customer</code>（顧客）を使って進めます。</p><h2 id=\"72-resource%E3%81%AFtenant%E3%81%AB%E6%89%80%E6%9C%89%E3%81%95%E3%82%8C%E3%82%8B\">7.2 ResourceはTenantに所有される</h2><p>最も重要な関係は次のものです。</p><pre><code class=\"language-text\">Tenant ownsResource Resource</code></pre><p>この関係は、逆向きから見ることもできます。</p><pre><code class=\"language-text\">Resource belongsToTenant Tenant</code></pre><p>この2つは<strong>同じ1本の関係を、どちら側から見ているかの違い</strong>でしかありません。オントロジーでは、これを逆関係として明示的に宣言しておきます。</p><pre><code class=\"language-turtle\">saas:ownsResource a owl:ObjectProperty ;\n    rdfs:domain saas:Tenant ;\n    rdfs:range  saas:Resource .\n\nsaas:belongsToTenant a owl:ObjectProperty ;\n    owl:inverseOf saas:ownsResource .</code></pre><p>こう書いておくと、<code>Tenant → Resource</code>のどちらか一方を記録するだけで、推論エンジンがもう一方を自動的に導いてくれます。「このTenantが持つResourceは？」と「このResourceの持ち主は？」の両方を、データを二重に持たずに引けるということです。</p><p>そして原則として、すべての業務データに所有Tenantが存在します。</p><pre><code class=\"language-text\">Tenant AのUser\n→ Tenant AのResourceにはアクセス可能\n\nTenant AのUser\n→ Tenant BのResourceには原則アクセス不可</code></pre><p>マルチテナントSaaSでは、このTenantとの関係がデータ分離の根拠になります。</p><p>なお、Resourceの中には特定のTenantに属さないもの（国コードや通貨コードなど、SaaS全体で共有されるマスターデータ）もあります。この例外の扱いは10.2で整理します。</p><h3 id=\"resource%E3%81%8C%E6%8C%81%E3%81%A4%E3%82%82%E3%81%AE\">Resourceが持つもの</h3><p>Resourceの構成要素は、次の2種類に分けて考えます。</p><p><strong>他の概念との関係として表現するもの</strong></p><pre><code class=\"language-text\">belongsToTenant  → どのTenantのものか\ncreatedBy        → 誰が作ったか\nクラス（型）      → 何のResourceか（Customer / Deal / Task …）</code></pre><p><strong>Resource自身が持つ値（リテラル）</strong></p><pre><code class=\"language-text\">resourceId\nname\ncreatedAt\nupdatedAt</code></pre><p>「何のResourceか」を、<code>resourceType: \"Customer\"</code>のような<strong>文字列ではなくクラスで表す</strong>のがポイントです。文字列だと、それがCustomerであることをオントロジーは理解できません。クラスにしておけば、<code>crm:Customer</code>と書いた時点で「これはResourceでもある」と推論されますし、<code>crm:Customer</code>だけに成り立つルール（たとえば「顧客は必ずメールアドレスを持つ」）を後から追加できます。</p><p>この「関係にするか、値にするか」という切り分けが、次の7.3のテーマです。</p><h2 id=\"73-tenantid%E3%82%92%E3%80%8C%E3%82%AB%E3%83%A9%E3%83%A0%E3%80%8D%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%8F%E3%80%8C%E9%96%A2%E4%BF%82%E3%80%8D%E3%81%A8%E3%81%97%E3%81%A6%E8%A1%A8%E7%8F%BE%E3%81%99%E3%82%8B\">7.3 tenant_idを「カラム」ではなく「関係」として表現する</h2><p>データベース設計では、多くの場合、Resourceに<code>tenant_id</code>を持たせます。</p><pre><code class=\"language-text\">customers\n├── customer_id\n├── tenant_id\n├── name\n└── status</code></pre><p><code>tenant_id</code>は、RDBの世界では「customersテーブルの1カラム」でしかありません。値としては<code>\"tenant-abc\"</code>という文字列やUUIDが入っているだけで、それがTenantを指していることは、アプリケーションのコードや開発者の頭の中にしか存在しません。</p><p>オントロジーでは、これを<strong>Tenantという実体そのものへのリンク</strong>として表現します。</p><pre><code class=\"language-text\">Customer belongsToTenant Tenant</code></pre><p>実際のデータで書くと、次のようになります。</p><pre><code class=\"language-turtle\">ex:customer-xyz a crm:Customer ;\n    saas:belongsToTenant ex:tenant-abc ;\n    saas:createdBy ex:user-yamada ;\n    saas:name \"株式会社XYZ\" .</code></pre><p><code>saas:belongsToTenant ex:tenant-abc</code>の<code>ex:tenant-abc</code>は、文字列ではなく<code>ex:tenant-abc</code>という<strong>Tenantの実体</strong>を指しています。だから、この1行を起点にして、そのTenantの契約情報にも、所属メンバーにも、他の所有データにも辿っていけます。</p><p>そして7.2で逆関係を宣言してあるので、Tenant側から見ることもできます。</p><pre><code class=\"language-text\">ex:tenant-abc saas:ownsResource ex:customer-xyz\n（上のトリプルから自動的に導出される）</code></pre><h3 id=\"%E3%81%93%E3%81%AE%E9%96%A2%E4%BF%82%E3%82%92%E6%98%8E%E7%A4%BA%E3%81%99%E3%82%8B%E3%81%A8%E4%BD%95%E3%81%8C%E5%AC%89%E3%81%97%E3%81%84%E3%81%AE%E3%81%8B\">この関係を明示すると何が嬉しいのか</h3><p><code>tenant_id</code>をカラムではなく関係として書くことで、次のようなことが<strong>たどれるようになります</strong>。</p><ul><li><strong>誰がデータを所有するのか</strong><code>ex:customer-xyz</code> → <code>ex:tenant-abc</code> → 株式会社ABCテナント</li><li><strong>どの権限体系が適用されるのか</strong><code>ex:tenant-abc</code>のMembershipを持つUserの、そのRoleのPermissionが適用される</li><li><strong>どの契約制限が適用されるのか</strong><code>ex:tenant-abc</code> → Subscription → Plan → そのPlanの上限値</li><li><strong>どのデータ保持ポリシーが適用されるのか</strong><code>ex:tenant-abc</code>に紐づく保持期間ルール（第◯回で追加予定）</li></ul><p>RDBでこれをやろうとすると、テーブルを何段もJOINするクエリを書くことになります。オントロジーでは、これらはすべて「リンクをたどる」という同じ操作になります。しかも、<code>ex:customer-xyz</code>が<code>crm:Customer</code>であり、<code>crm:Customer</code>が<code>saas:Resource</code>のサブクラスであることも宣言済みなので、「Tenant Aが所有するすべてのResource」を聞けば、<code>Customer</code>も<code>Deal</code>も、あとから追加した<code>Activity</code>も、まとめて返ってきます。</p><p>これが、7.1で<code>Resource</code>を「中身が空っぽの接続点」にしておいた理由です。</p><hr><h1 id=\"8-%E6%9C%80%E5%B0%8F%E3%81%AE%E9%96%A2%E4%BF%82%E4%B8%80%E8%A6%A7\">8. 最小の関係一覧</h1><p>このオントロジーの最小関係は、次のとおりです。</p><!--kg-card-begin: html--><table data-line=\"683\" class=\"code-line\" dir=\"auto\" style=\"border-collapse: collapse; margin-bottom: 0.7em;\"><thead data-line=\"683\" class=\"code-line\" dir=\"auto\"><tr data-line=\"683\" class=\"code-line\" dir=\"auto\"><th style=\"text-align: left; border-bottom: 1px solid rgba(255, 255, 255, 0.69); padding: 5px 10px; border-top-color: rgba(255, 255, 255, 0.69); border-right-color: rgba(255, 255, 255, 0.69); border-left-color: rgba(255, 255, 255, 0.69);\">主語</th><th style=\"text-align: left; border-bottom: 1px solid rgba(255, 255, 255, 0.69); padding: 5px 10px; border-top-color: rgba(255, 255, 255, 0.69); border-right-color: rgba(255, 255, 255, 0.69); border-left-color: rgba(255, 255, 255, 0.69);\">関係</th><th style=\"text-align: left; border-bottom: 1px solid rgba(255, 255, 255, 0.69); padding: 5px 10px; border-top-color: rgba(255, 255, 255, 0.69); border-right-color: rgba(255, 255, 255, 0.69); border-left-color: rgba(255, 255, 255, 0.69);\">目的語</th><th style=\"text-align: left; border-bottom: 1px solid rgba(255, 255, 255, 0.69); padding: 5px 10px; border-top-color: rgba(255, 255, 255, 0.69); border-right-color: rgba(255, 255, 255, 0.69); border-left-color: rgba(255, 255, 255, 0.69);\">意味</th></tr></thead><tbody data-line=\"685\" class=\"code-line\" dir=\"auto\"><tr data-line=\"685\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-color: rgba(255, 255, 255, 0.18);\">User</td><td style=\"padding: 5px 10px; border-color: rgba(255, 255, 255, 0.18);\">hasMembership</td><td style=\"padding: 5px 10px; border-color: rgba(255, 255, 255, 0.18);\">Membership</td><td style=\"padding: 5px 10px; border-color: rgba(255, 255, 255, 0.18);\">Userが所属関係を持つ</td></tr><tr data-line=\"686\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Membership</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">belongsToUser</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">User</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Membershipの対象User</td></tr><tr data-line=\"687\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Membership</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">belongsToTenant</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Tenant</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Membershipの所属先</td></tr><tr data-line=\"688\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Membership</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">assignedRole</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Role</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">所属内でRoleを持つ</td></tr><tr data-line=\"689\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Role</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">grantsPermission</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Permission</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Roleが操作を許可する</td></tr><tr data-line=\"690\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Tenant</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">hasSubscription</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Subscription</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Tenantが契約を持つ</td></tr><tr data-line=\"691\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Subscription</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">basedOnPlan</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Plan</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">契約が利用するPlan</td></tr><tr data-line=\"692\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Tenant</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">ownsResource</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Resource</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Tenantがデータを所有する</td></tr><tr data-line=\"693\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Resource</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">createdBy</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">User</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Resourceを作成したUser</td></tr><tr data-line=\"694\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Resource</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">belongsToTenant</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Tenant</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Resourceの所属先Tenant</td></tr></tbody></table><!--kg-card-end: html--><p>関係を図にすると、次のようになります。</p><!--kg-card-begin: html--><pre class=\"mermaid\">\ngraph TD\n    Tenant[Tenant]\n\n    Membership[Membership]\n    User[User]\n    Role[Role]\n    Permission[Permission]\n\n    Subscription[Subscription]\n    Plan[Plan]\n\n    Resource[Resource]\n\n    User -->|hasMembership| Membership\n    Membership -->|belongsToTenant| Tenant\n    Membership -->|assignedRole| Role\n    Role -->|grantsPermission| Permission\n\n    Tenant -->|hasSubscription| Subscription\n    Subscription -->|basedOnPlan| Plan\n\n    Tenant -->|ownsResource| Resource\n    Resource -->|createdBy| User\n\n</pre><!--kg-card-end: html--><hr><h1 id=\"9-%E5%85%B7%E4%BD%93%E4%BE%8B\">9. 具体例</h1><h2 id=\"91-%E7%99%BB%E5%A0%B4%E3%81%99%E3%82%8B%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9\">9.1 登場するインスタンス</h2><p>次のようなCRM SaaSを考えます。</p><pre><code class=\"language-text\">Tenant\n- 株式会社ABC\n\nUser\n- 山田太郎\n- 佐藤花子\n\nMembership\n- 山田太郎の株式会社ABCへの所属\n- 佐藤花子の株式会社ABCへの所属\n\nRole\n- 管理者\n- 営業担当者\n\nPermission\n- customer.read\n- customer.create\n- customer.update\n- customer.delete\n- member.invite\n\nPlan\n- Professional Plan\n\nSubscription\n- 株式会社ABCのProfessional契約\n\nResource\n- 顧客「株式会社XYZ」\n- 商談「基幹システム刷新案件」\n</code></pre><p>関係は次のようになります。</p><pre><code class=\"language-text\">山田太郎\n→ 株式会社ABCへのMembershipを持つ\n→ 管理者Roleを持つ\n\n佐藤花子\n→ 株式会社ABCへのMembershipを持つ\n→ 営業担当者Roleを持つ\n\n管理者Role\n→ customer.read\n→ customer.create\n→ customer.update\n→ customer.delete\n→ member.invite\n\n営業担当者Role\n→ customer.read\n→ customer.create\n→ customer.update\n\n株式会社ABC\n→ Professional PlanのSubscriptionを持つ\n\n株式会社ABC\n→ 顧客「株式会社XYZ」を所有する\n→ 商談「基幹システム刷新案件」を所有する\n</code></pre><h2 id=\"92-%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E5%9B%B3\">9.2 インスタンス図</h2><!--kg-card-begin: html--><pre class=\"mermaid\">\ngraph TD\n    T[株式会社ABC<br>Tenant]\n\n    U1[山田太郎<br>User]\n    U2[佐藤花子<br>User]\n\n    M1[Membership 001]\n    M2[Membership 002]\n\n    R1[管理者<br>Role]\n    R2[営業担当者<br>Role]\n\n    S[Professional契約<br>Subscription]\n    P[Professional<br>Plan]\n\n    C[株式会社XYZ<br>Customer Resource]\n    D[基幹システム刷新案件<br>Deal Resource]\n\n    U1 --> M1\n    M1 --> T\n    M1 --> R1\n\n    U2 --> M2\n    M2 --> T\n    M2 --> R2\n\n    T --> S\n    S --> P\n\n    T --> C\n    T --> D\n</pre>\n<!--kg-card-end: html--><hr><h1 id=\"10-%E6%9C%80%E4%BD%8E%E9%99%90%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%AB\">10. 最低限のルール</h1><p>オントロジーを単なる用語集で終わらせず、意味のあるモデルにするため、最低限の制約を定義します。</p><h2 id=\"101-membership%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%AB\">10.1 Membershipのルール</h2><pre><code class=\"language-text\">Membershipは、必ず1つのUserに属する。\nMembershipは、必ず1つのTenantに属する。\n</code></pre><p>さらに、基本的には次の組み合わせを一意にします。</p><pre><code class=\"language-text\">User × Tenantごとに、有効なMembershipは最大1つ\n</code></pre><p>同じUserが同じTenantに、重複して所属するのを防ぐためです。</p><h2 id=\"102-resource%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%AB\">10.2 Resourceのルール</h2><pre><code class=\"language-text\">Tenant固有Resourceは、必ず1つのTenantに属する。\n</code></pre><p>つまり、通常の業務データについては、次を必須とします。</p><pre><code class=\"language-text\">Resource belongsToTenant exactly 1 Tenant\n</code></pre><p>ただし、SaaS全体で共有されるマスターデータは例外で、たとえば次のようなものが挙げられます。</p><ul><li>国コード</li><li>通貨コード</li><li>SaaS提供者が定義したテンプレート</li><li>システム共通Role</li><li>システム共通Plan</li></ul><p>などは、特定のTenantに属さないことがあります。</p><h2 id=\"103-subscription%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%AB\">10.3 Subscriptionのルール</h2><pre><code class=\"language-text\">Subscriptionは、必ず1つのTenantに属する。\nSubscriptionは、必ず1つのPlanを参照する。\n</code></pre><p>ただし、1つのTenantが複数のSubscriptionを持つ場合もあります。</p><p>たとえば、</p><pre><code class=\"language-text\">基本プランのSubscription\n追加ストレージのSubscription\nAI機能追加のSubscription\n監査ログ追加のSubscription\n</code></pre><p>などです。</p><h2 id=\"104-role%E3%81%A8permission%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%AB\">10.4 RoleとPermissionのルール</h2><pre><code class=\"language-text\">Roleは、0個以上のPermissionを持つ。\nMembershipは、0個以上のRoleを持つ。\n</code></pre><p>最初は1 Membershipにつき1 Roleでも構いませんが、将来的には次のような複数Roleが必要になる可能性があります。</p><pre><code class=\"language-text\">山田太郎\n├── 営業担当者Role\n├── 請求閲覧者Role\n└── セキュリティ監査者Role\n</code></pre><p>そのため、オントロジー上は多対多にしておく方が拡張しやすくなります。</p><hr><h1 id=\"11-rdfturtle%E3%81%AB%E3%82%88%E3%82%8B%E6%9C%80%E5%B0%8F%E8%A1%A8%E7%8F%BE\">11. RDF/Turtleによる最小表現</h1><p>オントロジーとして実装する場合の、非常に単純化したTurtle表現です。</p><pre><code class=\"language-turtle\">@prefix saas: &lt;https://example.com/ontology/saas#&gt; .\n@prefix rdf:  &lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&gt; .\n@prefix rdfs: &lt;http://www.w3.org/2000/01/rdf-schema#&gt; .\n@prefix owl:  &lt;http://www.w3.org/2002/07/owl#&gt; .\n\nsaas:Tenant a owl:Class .\nsaas:User a owl:Class .\nsaas:Membership a owl:Class .\nsaas:Role a owl:Class .\nsaas:Permission a owl:Class .\nsaas:Subscription a owl:Class .\nsaas:Plan a owl:Class .\nsaas:Resource a owl:Class .\n\nsaas:belongsToUser a owl:ObjectProperty ;\n    rdfs:domain saas:Membership ;\n    rdfs:range saas:User .\n\nsaas:belongsToTenant a owl:ObjectProperty ;\n    rdfs:domain saas:Membership ;\n    rdfs:range saas:Tenant .\n\nsaas:assignedRole a owl:ObjectProperty ;\n    rdfs:domain saas:Membership ;\n    rdfs:range saas:Role .\n\nsaas:grantsPermission a owl:ObjectProperty ;\n    rdfs:domain saas:Role ;\n    rdfs:range saas:Permission .\n\nsaas:hasSubscription a owl:ObjectProperty ;\n    rdfs:domain saas:Tenant ;\n    rdfs:range saas:Subscription .\n\nsaas:basedOnPlan a owl:ObjectProperty ;\n    rdfs:domain saas:Subscription ;\n    rdfs:range saas:Plan .\n\nsaas:ownsResource a owl:ObjectProperty ;\n    rdfs:domain saas:Tenant ;\n    rdfs:range saas:Resource .\n\nsaas:createdBy a owl:ObjectProperty ;\n    rdfs:domain saas:Resource ;\n    rdfs:range saas:User .\n</code></pre><hr><h1 id=\"12-%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AA%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AEturtle%E8%A1%A8%E7%8F%BE\">12. 具体的なデータのTurtle表現</h1><p>先ほどの株式会社ABCの例を表すと、次のようになります。</p><pre><code class=\"language-turtle\">@prefix saas: &lt;https://example.com/ontology/saas#&gt; .\n@prefix ex:   &lt;https://example.com/data/&gt; .\n\nex:tenant-abc a saas:Tenant ;\n    saas:name \"株式会社ABCテナント\" ;\n    saas:hasSubscription ex:subscription-abc-professional ;\n    saas:ownsResource ex:customer-xyz .\n\nex:user-yamada a saas:User ;\n    saas:displayName \"山田太郎\" .\n\nex:membership-yamada-abc a saas:Membership ;\n    saas:belongsToUser ex:user-yamada ;\n    saas:belongsToTenant ex:tenant-abc ;\n    saas:assignedRole ex:role-admin .\n\nex:role-admin a saas:Role ;\n    saas:name \"テナント管理者\" ;\n    saas:grantsPermission\n        ex:permission-customer-read,\n        ex:permission-customer-create,\n        ex:permission-customer-update,\n        ex:permission-customer-delete .\n\nex:plan-professional a saas:Plan ;\n    saas:name \"Professional\" .\n\nex:subscription-abc-professional a saas:Subscription ;\n    saas:basedOnPlan ex:plan-professional ;\n    saas:status \"active\" .\n\nex:customer-xyz a saas:Resource ;\n    saas:name \"株式会社XYZ\" ;\n    saas:resourceType \"Customer\" ;\n    saas:createdBy ex:user-yamada .\n</code></pre><hr><h1 id=\"13-%E3%81%93%E3%81%AE%E3%83%A2%E3%83%87%E3%83%AB%E3%81%A7%E7%AD%94%E3%81%88%E3%82%89%E3%82%8C%E3%82%8B%E8%B3%AA%E5%95%8F\">13. このモデルで答えられる質問</h1><p>オントロジーを作る目的は、単にデータを保存することではなく、意味をたどって質問に答えられるようにすることです。この最小モデルでも、次のような質問に答えられます。</p><h2 id=\"tenant%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%B3%AA%E5%95%8F\">Tenantに関する質問</h2><pre><code class=\"language-text\">このUserは、どのTenantに所属しているか。\nこのTenantには、何人のUserが参加しているか。\nこのTenantが所有しているResourceは何か。\n</code></pre><h2 id=\"%E6%A8%A9%E9%99%90%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%B3%AA%E5%95%8F\">権限に関する質問</h2><pre><code class=\"language-text\">このUserは、Tenant AでどのRoleを持っているか。\nこのUserは、顧客データを削除できるか。\nmember.inviteを持つMembershipはどれか。\n管理者権限を持つUserは誰か。\n</code></pre><h2 id=\"%E5%A5%91%E7%B4%84%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%B3%AA%E5%95%8F\">契約に関する質問</h2><pre><code class=\"language-text\">このTenantは、どのPlanを利用しているか。\nProfessional Planを利用しているTenantはどれか。\n契約が停止中のTenantはどれか。\n</code></pre><h2 id=\"resource%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%B3%AA%E5%95%8F\">Resourceに関する質問</h2><pre><code class=\"language-text\">このResourceは、どのTenantのものか。\nこのUserが作成したResourceは何か。\nTenant AのすべてのResourceは何か。\n</code></pre><hr><h1 id=\"14-%E3%81%82%E3%81%88%E3%81%A6%E5%90%AB%E3%82%81%E3%81%A6%E3%81%84%E3%81%AA%E3%81%84%E6%A6%82%E5%BF%B5\">14. あえて含めていない概念</h1><p>最初のバージョンをシンプルにするため、たとえば次の概念などはまだ入れていません。</p><pre><code class=\"language-text\">Organization\nOrganizationUnit\nWorkspace\nGroup\nTeam\nCustomerAccount\nInvoice\nPayment\nEntitlement\nFeature\nUsage\nQuota\nAuthenticationIdentity\nAPIClient\nServiceAccount\nAuditEvent\nPolicy\nDataRetentionRule\nPartner\nReseller\n</code></pre><p>これらは重要ですが、最初から入れるとTenant、会社、部署、Workspace、契約者などの違いが分かりにくくなるため、まずは次の骨格を固定するのがよいでしょう。</p><pre><code class=\"language-text\">Tenant\n├── Membership\n│   ├── User\n│   └── Role\n│       └── Permission\n│\n├── Subscription\n│   └── Plan\n│\n└── Resource\n</code></pre><hr><h1 id=\"15-%E3%81%93%E3%81%AE%E3%82%AA%E3%83%B3%E3%83%88%E3%83%AD%E3%82%B8%E3%83%BC%E3%81%AE%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88\">15. このオントロジーのポイント</h1><p>このモデルで最も重要なのは、次の4点です。</p><h2 id=\"151-tenant%E3%81%AF%E4%BC%9A%E7%A4%BE%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%8F%E5%A2%83%E7%95%8C%E3%81%A7%E3%81%82%E3%82%8B\">15.1 Tenantは会社ではなく境界である</h2><pre><code class=\"language-text\">Tenant\n＝ SaaS上でユーザー、権限、契約、データを分離する境界\n</code></pre><h2 id=\"152-user%E3%81%A8%E6%89%80%E5%B1%9E%E3%81%AF%E5%88%A5%E3%81%A7%E3%81%82%E3%82%8B\">15.2 Userと所属は別である</h2><pre><code class=\"language-text\">User\n＝ 人またはログイン主体\n\nMembership\n＝ Userが特定Tenantに参加している関係\n</code></pre><h2 id=\"153-plan%E3%81%A8%E5%A5%91%E7%B4%84%E3%81%AF%E5%88%A5%E3%81%A7%E3%81%82%E3%82%8B\">15.3 Planと契約は別である</h2><pre><code class=\"language-text\">Plan\n＝ SaaS提供者が定義した商品\n\nSubscription\n＝ Tenantごとの個別契約\n</code></pre><h2 id=\"154-%E6%A5%AD%E5%8B%99%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AFresource%E3%81%A8%E3%81%97%E3%81%A6%E6%8A%BD%E8%B1%A1%E5%8C%96%E3%81%99%E3%82%8B\">15.4 業務データはResourceとして抽象化する</h2><pre><code class=\"language-text\">Resource\n＝ 各SaaS固有の業務オブジェクトの共通上位概念\n</code></pre><hr><h1 id=\"16-%E6%9C%80%E7%B5%82%E7%9A%84%E3%81%AA%E6%9C%80%E5%B0%8F%E3%83%A2%E3%83%87%E3%83%AB\">16. 最終的な最小モデル</h1><p>最も単純に表すと、このB2B SaaSオントロジーは次の文章に集約できます。</p><pre><code class=\"language-text\">Tenantは、SaaSの論理的な利用境界である。\n\nUserは、Membershipを通じてTenantに所属する。\n\nMembershipにはRoleが割り当てられる。\n\nRoleはPermissionを付与する。\n\nTenantはSubscriptionを通じてPlanを利用する。\n\nTenantはResourceを所有する。\n</code></pre><p>図にすると、次の形です。</p><!--kg-card-begin: html--><pre class=\"mermaid\">\ngraph LR\n    User --> Membership\n    Membership --> Tenant\n    Membership --> Role\n    Role --> Permission\n\n    Tenant --> Subscription\n    Subscription --> Plan\n\n    Tenant --> Resource\n\n</pre><!--kg-card-end: html--><p></p><p>これを、B2B SaaS共通オントロジーの最初のベースとし、SaaSオントロジーv0にむけての最初のバージョン、SaaSオントロジーv.1とでも呼んでおきましょう。</p><p><strong>第２回は、 Tenantは会社なのか？ Organization／Workspace／Teamを追加して「会社・契約単位・作業空間・部署」の違いを整理する</strong> で行きたいと思います</p><p>アディオス！</p>","url":"https://ghost.tech.anti-pattern.co.jp/saas-ontology-1/","canonical_url":null,"uuid":"2fab20e7-2039-4229-8464-2e2ea855912f","page":null,"codeinjection_foot":null,"codeinjection_head":null,"codeinjection_styles":null,"comment_id":"6a74b8ae781e37000184edec","reading_time":15}},"pageContext":{"slug":"saas-ontology-1"}},
    "staticQueryHashes": ["176528973","2358152166","2561578252","2731221146","4145280475"]}