-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
361 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Authorization |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
# Backend Architecture | ||
|
||
At first, I stuck to the usual modular architecture. After that, I decided to switch to microservices. | ||
|
||
*Microservices* are very well suited to the monorepository structure, | ||
and I've also added the CQRS pattern that the [@nestjs/cqrs](https://docs.nestjs.com/recipes/cqrs) module provides. | ||
|
||
## Kafka as a Message Broker | ||
|
||
[Apache Kafka](https://kafka.apache.org/documentation/) is often used to solve the problems of messaging and transferring data between | ||
different `microservices`. Unlike [RabbitMQ](https://www.rabbitmq.com/tutorials/tutorial-one-javascript.html), it focuses on high throughput and scalability. | ||
|
||
I used Kafka + Zookeeper to implement communication between microservices. | ||
|
||
::: code-group | ||
|
||
```yaml [docker-compose.services.yml] | ||
zookeeper: | ||
image: confluentinc/cp-zookeeper:latest | ||
container_name: zookeeper | ||
networks: | ||
- cgnet | ||
env_file: | ||
- .env | ||
- .docker/.override.env | ||
ports: | ||
- 22181:2181 | ||
|
||
kafka: | ||
image: confluentinc/cp-kafka:latest | ||
container_name: kafka | ||
hostname: kafka | ||
networks: | ||
- cgnet | ||
depends_on: | ||
- zookeeper | ||
ports: | ||
- 29092:29092 | ||
env_file: | ||
- .env | ||
- .docker/.override.env | ||
``` | ||
:::tip NOTE | ||
`.override.env` is a file to overwrite the local configuration for `docker`. It's important to add it | ||
|
||
::: | ||
|
||
## Usage with NestJS | ||
|
||
You can use the module described below to *connect* the microservice. | ||
|
||
```ts | ||
@Module({ | ||
imports: [EnvModule], | ||
providers: [KafkaService], | ||
exports: [KafkaService] | ||
}) | ||
/** | ||
* Creates a reusable Kafka configuration | ||
* @param service {Microservice} - name of your microservice | ||
*/ | ||
export class KafkaModule { | ||
static forRoot(service: Microservice): DynamicModule { | ||
const providers: Provider[] = [ | ||
{ | ||
provide: service, | ||
useFactory: (kafkaService: KafkaService) => { | ||
return ClientProxyFactory.create( | ||
kafkaService.getKafkaOptions(service) | ||
) | ||
}, | ||
inject: [KafkaService] | ||
} | ||
] | ||
return { | ||
module: KafkaModule, | ||
providers, | ||
exports: providers | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Now, it can be connected in a single line. | ||
|
||
|
||
```ts | ||
@Module({ | ||
imports: [KafkaModule.forRoot(Microservice.CODE_EXECUTOR)] | ||
}) | ||
/** | ||
* You can now use it in the module. | ||
*/ | ||
@Controller() | ||
export class ExampleController { | ||
constructor( | ||
@Inject(Microservice.CODE_EXECUTOR) private executorClient: ClientKafka | ||
) {} | ||
async onModuleInit() { | ||
this.executorClient.subscribeToResponseOf(CodeExecutorTopic.MY_TOPIC) | ||
await this.executorClient.connect() | ||
} | ||
@Get() | ||
async myRoute( | ||
@Body() args: ExampleDTO | ||
): Promise<unknown> { | ||
return this.executorClient.send(CodeExecutorTopic.MY_TOPIC, args) | ||
} | ||
} | ||
``` | ||
|
||
|
||
## CQRS Pattern | ||
|
||
Using *CQRS* in a project is no different from the cqrs documentation of the NestJS module, | ||
which can be found [here](https://docs.nestjs.com/recipes/cqrs). | ||
|
||
You can find examples of usage in the microservice `apps/server/service-code-executor`. | ||
|
||
## Gateway Pattern | ||
|
||
I'm using the `gateway` pattern for microservices. In general, it is quite popular, you can read more | ||
[here](https://microservices.io/patterns/apigateway.html). | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# GraphQL | ||
|
||
In the project, I used *GraphQL* for client-server communication. | ||
|
||
[GraphQL](https://graphql.org/learn/) - is a query language, architectural style, and toolset for creating and managing APIs. | ||
|
||
## Schema Generation | ||
|
||
I used the [@nestjs/graphql](https://docs.nestjs.com/graphql/quick-start#installation) package to autogenerate the | ||
*GraphQL schema* from typescript interfaces | ||
([Code First](https://docs.nestjs.com/graphql/quick-start#code-first)). | ||
|
||
|
||
::: warning | ||
|
||
There may be problems with webpack hot reload during autogeneration, | ||
in which case check that the `_schema.gql` file is in `.nxignore`. | ||
|
||
::: code-group | ||
```sh [.nxignore] | ||
_schema.gql | ||
``` | ||
You can read more about [.nxignore](https://nx.dev/reference/nxignore) here. | ||
|
||
::: | ||
|
||
Here's a common use case with `@nestjs/graphql`. | ||
|
||
```ts | ||
// API gateway | ||
|
||
@Resolver(() => Example) | ||
export class ExampleResolver { | ||
constructor(private authService: AuthService) {} | ||
|
||
@Mutation(() => Example) | ||
@UseGuards(GqlAuthGuard, GqlLocalAuthGuard) | ||
async signIn( | ||
@Args(graphqlArg) payload: SignIn, | ||
@WithUser() user: User | ||
): Promise<AccessToken> { | ||
return this.authService.generateToken(user.username) | ||
} | ||
} | ||
|
||
|
||
``` | ||
|
||
## Using the `@grnx-utils/apollo` package. | ||
|
||
On the frontend, you can use the [@grnx-utils/apollo](https://www.npmjs.com/package/@grnx-utils/apollo) package to | ||
reduce the number of boilerplate code. | ||
|
||
```ts | ||
import { createApolloClient } from '@grnx-utils/apollo' | ||
|
||
const apollo = createApolloClient({ | ||
url: graphqlEndpoint | ||
}) | ||
|
||
class AuthServices implements IAuthServices { | ||
async getProfile() { | ||
const [payload, error] = await apollo.request<Request, Response>( | ||
exampleQuery | ||
) | ||
|
||
if (payload) { | ||
this.state.username = payload.username | ||
this.state.isAuthorized = true | ||
} | ||
} | ||
} | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Prisma ORM | ||
|
||
I've been using [Prisma](https://www.prisma.io/docs/getting-started/quickstart) | ||
as an [ORM](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping) and in this module I'll cover some interesting concepts I've found. | ||
|
||
## Auto-generation of prisma types | ||
|
||
To avoid duplicating types, you can generate prisma types and then reuse them between the frontend and backend. | ||
|
||
::: code-group | ||
|
||
```sh [yarn] | ||
nx prisma:types gateway | ||
``` | ||
|
||
::: | ||
|
||
Under the hood, I use the [@kalissaac/prisma-typegen](https://www.npmjs.com/package/@kalissaac/prisma-typegen) module that allows me to do this. | ||
|
||
```ts | ||
// AUTO GENERATED FILE BY @kalissaac/prisma-typegen | ||
// DO NOT EDIT | ||
|
||
export interface Example { | ||
name: string | ||
} | ||
``` | ||
|
||
## Using multiple Prisma files | ||
|
||
In the project, you can group and create `.prisma` files wherever you want, in the end, | ||
this script will merge all the .prisma files into one `schema.prisma` | ||
|
||
```js | ||
// ~/gateway/prisma/concat-prisma-files.js | ||
|
||
export const concatPrismaFiles = ({ config, models, dest }) => { | ||
concat( | ||
[ | ||
resolvePrisma('config', config), | ||
...models.map((model) => resolvePrisma('models', `${model}.prisma`)) | ||
], | ||
resolvePrisma(dest), | ||
(error) => { | ||
if (error) { | ||
throw error | ||
} | ||
|
||
console.log('Prisma files merged.') | ||
} | ||
) | ||
} | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.