Multiple Org - Loan Example¶
A 2-organization consortium example
Table of Contents¶
Background¶
An SME client (Org1) wishes to application business loan from a bank (Org2).
Org1 SME Client provides document. She wants to keep detailed document private, and able to manage the access control of detailed document content, because of data residency requirement.
Org2 Bank accepts loan application. Loan application details is bank-internal information, which is partially shared to client.
Design¶
- 2 organization network
- Each organization has both public and private data.
Org1:
gw-org1 (gateway)
⎿ admin (service)
⎿ document (service)
⎿ docContents (service)
⎿ loan (service)
⎿ loanDetails (service)
queryhandler (service)
Org2:
gw-org2 (gateway)
⎿ admin (service)
⎿ document (service)
⎿ docContents (service)
⎿ loan (service)
⎿ loanDetails (service)
queryhandler (service)
Domain models¶
1. Data-graph services under gw-org1
| Service-name | Models | Type | Read/Write |
|---|---|---|---|
| document | Document | public | R/W |
| docContents | Document DocContents |
public private |
R/W R/W |
| loan | Loan | public | R/W |
| loanDetails | Loan LoanDetails |
public remote |
R/W R |
Document, Loan are public on-chain data. DocContents is Org1’s private data. LoanDetails fetch
data from Org2.
2. Data-graph services under gw-org2
| Service-name | Models | Type | Read/Write |
|---|---|---|---|
| document | Document | public | R/W |
| docContents | Document DocContents |
public remote |
R/W R |
| loan | Loan | public | R/W |
| loanDetails | Loan LoanDetails |
public private |
R/W R/W |
LoanDetails is Org2’s private data. DocContents fetch data from Org1.
Note
Private data uses Implicit Collection. The remote data service fetches data from another organization’s Federated Gateway; and is READ only.
How federation works¶
Document -> DocContents¶
The data graphs are federated unidirectionally, from Document to DocContents. You
need define both schema and resolvers at DocContents, not Document.
### DocContents schema ###
### packages/model-document/src/doc-contents/service/schema.ts
type DocContents @key(fields: "documentId") {
documentId: String!
document: Document
# ...
}
extend type Document @key(fields: "documentId") {
documentId: String! @external
contents: [DocContents]
}
It defines two links:
document: Documentadd document fields inDocContent.external type Document@key(fields "documentId") {extends the source modelDocument, to the target modelDocContents.
Correspondingly, the resolvers implements these two links in both Document and DocContents resolvers.
Link 1
// DocContents Resolvers
// packages/model-document/src/doc-contents/service/resolvers.ts
DocContents: {
document: ({ documentId }) => ({ __typename: 'Document', documentId }),
},
Link 2
// DocContents Reolvers
// packages/model-document/src/doc-contents/service/resolvers.ts
Document: {
contents: catchResolverErrors(
async ({ documentId }, { token }, context) =>
// ...
This is a declarative approach, which you do not deal with how the data are physically meshed together. Apollo Federation does the works for you.
Loan -> Document¶
Loan depends on Document unidirectionally.
### Document schema ###
### packages/model-document/src/document/service/schema.ts
type Document @key(fields: "documentId") {
documentId: String!
loan: Loan
# ...
}
extend type Loan @key(fields: "loanId") {
loanId: String! @external
documents: [Document]
}
Similarly, it defines two links:
loan: Loanadd loan fields inDocument.external type Loan@key(fields "loanId") {extends the source modelLoan, to the target modelDocument.
The resolvers as follow:
Link 1
// Document Resolvers
// packages/model-document/src/document/service/resolvers.ts
Document: {
/* ... */
loan: ({ loanId }: { loanId: string }) => ({ __typename: 'Loan', loanId }),
},
Link 2
// Document Resolvers
// packages/model-document/src/document/service/resolvers.ts
Loan: {
documents: catchResolverErrors(
async (
{ loanId }: { loanId: string },
// ...
Loan -> LoanDetails¶
Loan depends on LoanDetails unidirectionally.
### LoanDetails schema ###
### packages/model-loan/src/document/service/schema.ts
type LoanDetails @key(fields: "loanId") {
loanId: String!
loan: Loan
# ...
}
extend type Loan @key(fields: "loanId") {
loanId: String! @external
details: [LoanDetails]
}
Similarly, it defines two links:
loan: Loanadd loan fields inLoanDetails.external type Loan@key(fields "loanId") {extends the source modelLoan, to the target modelLoanDetails.
The resolvers as follow:
Link 1
// LoanDetails Resolvers
// packages/model-loan/src/loan-details/service/resolvers.ts
LoanDetails: {
loan: ({ loanId }) => ({ __typename: 'Loan', loanId }),
},
Link 2
// LoanDetails Resolvers
// packages/model-loan/src/loan-details/service/resolvers.ts
Loan: {
details: catchResolverErrors(
async ({ loanId }, { token }, context) =>
// ...
Here omits the details of domain models. If interested, you may explore packages/model-loan and packages/model-document.
How remote data works¶
TODO
How to build federated gateway¶
There are two federated gateway, gw-org1 and gw-org2; and each are very similar structure.
gw-org1 Federated Gateway will compose the api from all underlying service. And, you need to provide packages/gw-org1/connection/connection-org1.yaml; configured via .env.
See .env.example.
You need to start all services successfully, before launching gateway. The gw-org1 can
be used, via http://localhost:4001/graphql, Apollo Playground.
Hint
Each federated service consumes at least 150MB memory.
Tester¶
TODO