Skip to content

Commit

Permalink
Merge pull request #118 from dolittle-entropy/multi-tenant-resources
Browse files Browse the repository at this point in the history
Changing how we deal with resources
  • Loading branch information
einari authored Feb 26, 2021
2 parents 68ca81c + f9787e6 commit b6a35c9
Show file tree
Hide file tree
Showing 55 changed files with 1,258 additions and 255 deletions.
19 changes: 12 additions & 7 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
"version": "0.2.0",
"configurations": [
{
"name": "Attach by Process ID",
"processId": "${command:PickProcess}",
"request": "attach",
"name": "Launch Basic Sample",
"type": "node",
"request": "launch",
"runtimeExecutable": "node",
"runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
"args": ["${workspaceFolder}/Samples/Source/Typescript/Backend/index.ts"],
"cwd": "${workspaceFolder}/Samples/Source/Typescript/Backend",
"preLaunchTask": "tsc: build - Typescript Sample",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
"<node_internals>/**",
"node_modules/**"
]
}
]
}
}
1 change: 1 addition & 0 deletions Documentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ for everyone participating in this project to see what is expected.
| [Microservice Instance](./microservice.md) | How a Vanir based Dolittle microservice is packaged and run |
| [Frontend](./frontend/index.md) | Details about the frontend support |
| [Getting started](./getting-started.md) | A quick guide on getting started |
| [TypeScript Backend](./backend/typescript/index.md) | Topics specific for TypeScript based backends |
Original file line number Diff line number Diff line change
Expand Up @@ -71,84 +71,23 @@ In the backend you would then have a starting point for the microservice; `index
Leveraging the shared backend setup.

```javascript
import path from 'path';
import { startBackend } from '@shared/backend';

import { getSchema } from './schema';
import { ProductAdded, ProductHandler } from './configuration';
import 'reflect-metadata';
import { Host } from '@dolittle/vanir-backend';

(async () => {
const schema = await getSchema(); <-- This is the exposed GraphQL schema exposed

await startBackend({
microserviceId: '08fe9d6d-874e-45d5-b4f6-b31a099645a3', <-- Unique identifier that will be used for the microservice configuration in general
prefix: '/_/<microservice>', <-- URL prefix for frontend, APIs and GraphQL endpoints - each microservice has their own unique
publicPath: './public', <-- The path to where the static web pages are served from (if any)
port: 3003, <-- Development port the backend will be served from - must be unique per Microservice
dolittleRuntimePort: 50057, <-- The Dolittle runtime port to connect to in the local development environment
graphQLSchema: schema, <-- Pass in the GraphQL schema generated
defaultDatabaseName: '<microservice>', <-- Default name for database - unique per microservice
defaultEventStoreDatabaseName: <microservice-event-store>, <-- The default name for the event store database - same as in the resources.json for the microservice in environments.
expressCallback: _ => {
/* _ is the Express app instance */
await Host.start({
graphQLResolvers: [] // Array of graphql resolvers
expressCallback: (app) => {
},
dolittleCallback: _ => _
/*
_ is the Dolittle client builder instance
this is where you'd start registering things like
events, projections and more.
*/
});
dolittleCallback: (dolittle) => {
}
});
})();
```

For the GraphQL schema, add a file called `schema.ts`. The code above relies on this to be there.
A starting point would be to put it something that doesn't add any specific model or queries.

> The GraphQL system requires something, so one can't pass an empty schema.
Add the "empty" schema:

```javascript
import { buildSchema, Field, ObjectType, Query, Resolver, ResolverData } from 'type-graphql';
import { modelOptions, Severity } from '@typegoose/typegoose';
import { guid, GuidScalar } from '@shared/backend/data';
import { Guid } from '@dolittle/rudiments';
import { GraphQLSchema } from 'graphql';
import { container } from 'tsyringe';

@ObjectType()
@modelOptions({ options: { allowMixed: Severity.ALLOW } })
class Nothing {
@Field({ name: 'id' })
@guid()
_id?: Guid;
}

@Resolver(Nothing)
class NoQueries {
@Query(returns => [Nothing])
async noresults() {
return [];
}
}
Any GraphQL resolvers can then be added to the array of graphql resolvers, be it queries or mutations.

export async function getSchema(): Promise<GraphQLSchema> {
const schema = await buildSchema({
resolvers: [NoQueries],
container: {
get(someClass: any, resolverData: ResolverData<any>): any | Promise<any> {
return container.resolve(someClass);
}
},
scalarsMap: [
{ type: Guid, scalar: GuidScalar }
]
});
return schema;
}
```
> Apollo crashes if you add mutations here and no query - if all you have are mutations, you should add a query that doesn't do anything.
## APIs

Expand Down Expand Up @@ -194,47 +133,25 @@ import { RegisterRoutes } from './routes';
import * as swaggerDoc from './swagger.json';
```

Within the `startBackend()` call block, you can now add the swagger doc into it:
Within the `Host.start()` call block, you can now add the swagger doc into it:

```javascript
import path from 'path';
import { startBackend } from '@shared/backend';

import { getSchema } from './schema';
import { ProductAdded, ProductHandler } from './configuration';

import 'reflect-metadata';
import { Host } from '@dolittle/vanir-backend';
import { RegisterRoutes } from './routes';
const swaggerDoc = require('./swagger.json');
import * as swaggerDoc from './swagger.json';

(async () => {
const schema = await getSchema();

await startBackend({
microserviceId: '08fe9d6d-874e-45d5-b4f6-b31a099645a3',
prefix: '/_/<microservice>',
publicPath: './public',
port: 3003,
dolittleRuntimePort: 50057,
graphQLSchema: schema,
defaultDatabaseName: '<microservice>',
defaultEventStoreDatabaseName: <microservice-event-store>,
swaggerDoc, // This is the swagger doc.
expressCallback: _ => {
/* _ is the Express app instance */
},
dolittleCallback: _ => _
/*
_ is the Dolittle client builder instance
this is where you'd start registering things like
events, projections and more.
*/
});
await Host.start({
swaggerDoc, // This is the swagger doc.
expressCallback: (app) => {
RegisterRoutes(app); // Register the routes
}
});
})();
```

With this, you'll now have a new swagger endpoint and all your APIs accessible, prefixed with what you have set as prefix.
You should therefor be able to navigate to the URL e.g. http://localhost:3003/_/mymicroservice/api/swagger.

Read more about TSOA and concrete samples [here](https://tsoa-community.github.io/docs/examples.html).

36 changes: 36 additions & 0 deletions Documentation/backend/typescript/context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Context

As part of every web request coming into the system, there is an object that is set up that
can be retrieved anywhere as part of the request. This holds the following:

```typescript
{
userId: string;
tenantId: TenantId;
cookies: string;
}
```

To access it, all you need to do is use the `getCurrentContext()` function from the context.

```typescript
import { getCurrentContext } from '@dolittle/vanir-backend/dist/web';


const context = getCurrentContext();
```

## Express Middleware

The setting of this context is done through an express middleware.
It looks at the HTTP header and sets up the context for the current request coming in.
The default setup gets set up with this, but if you have your own setup and want to leverage
this functionality - you can do so through the following:

```typescript
import express from 'express';
importContextMiddleware } from '@dolittle/vanir-backend/dist/web';

const app = express();
app.use(ContextMiddleware);
```
16 changes: 16 additions & 0 deletions Documentation/backend/typescript/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# TypeScript Backend

## Table of contents

| Title | Description |
| ----- | ----------- |
| [Anatomy](./anatomy-of-a-backend.md) | The anatomy of a TypeScript based backend |
| [IoC](./ioc.md) | How to leverage the configured IoC container |
| [Resources](./resources.md) | How to use the resource system in your backend |
| [MongoDB](./mongodb.md) | How to use MongoDB in your backend |
| [GraphQL](./graphql.md) | How to use GraphQL in your backend |
| [Context](./context.md) | The context that is for the current web request |
| [Logging](./logging.md) | How to use structured logging in your backend |
| [Debugging](./debugging.md) | How to use debug your solution |
| [Shared Projects](./shared-projects.md) | What are the shared projects from the template? |
| [WebPack](./webpack.md) | How webpack works for your backend |
Loading

0 comments on commit b6a35c9

Please sign in to comment.