π₯ MSA ꡬ쑰λ₯Ό graphql λ‘ λ§λ€μ΄λ³΄μ π₯
κ·Έμ μ μκ°ν΄λ³΄μ...
grapqhl μ μλν¬μΈνΈκ° νλμΈλ°, MSA λ₯Ό μ΄λ»κ² ꡬνν΄μΌ νμ§...? λκ° msa λ‘ ν μ μμ κ² κ°μΌλ©΄μλ... μ΄κ² restapi μ²λΌ msa λ₯Ό κ΅¬μ± κ°λ₯ν κΉ...? μ¬λ¬κ°μ§ μκ°μ΄ λ λ€. μ...κ° app λ³λ‘ tcp μλ²λ₯Ό μ΄μ΄μΌ νλ? tcp ν΅μ μ΄ νμνκ°? μ΄μ°¨νΌ μλν¬μΈνΈ νλμΈλ°..?
νλ² ν΄λ³΄μ.
κ° λλ©μΈμ λ°λ₯Έ app μ λ§λ λ€
nest g app gateway
, nest g app account
, nest g app chat
μμ±ν μ±μ rest κΈ°λ°μΌλ‘ λ§λ€κΈ° λλ¬Έμ graphql μ μν resolver μμ±μ΄ νμνλ€.
nest g resource
gateway λ₯Ό μ μΈν λ€λ₯Έ app λ€μ κ°μ μ νν΄μ resolver λ₯Ό μμ±νλ©΄ κΈ°λ³Έ ꡬ쑰(resolver, service, dto, entity...) λ° λ‘μ§(CRUD)μ λ§λ€μ΄μ€λ€.
μ΄μ gateway μμ κ° μ±λ€μ νλλ‘ μ°κ²°μ μμΌμ€μΌ νλ€.
μ¬κΈ°μ μ€μν κ°λ μ΄ μλ€.
Federation(μ§μνλ©΄ μ°λ°©
)μ μλ―Έλ graphql μλ²λ₯Ό κ° microservice λ‘ μ£Όμ
μ ν΄μ€λ€λΌλ μλ―Έμ΄λ€.
μμ²ν 쿼리λ₯Ό νλμ 리쑸λ²μμ λͺ¨λ ν΄κ²°νλ κ²μ΄ μλ, κ° subgraph μμ μ€ν€λ§λ₯Ό μ²λ¦¬ν΄μ μ²λ¦¬λ schema λ₯Ό superGraph μμ μ·¨ν©ν΄μ νλ‘ νΈλ‘ λμ Έμ€λ€.
Federation μ ꡬννκΈ° μν΄μλ νλμ gateway μ νκ° μ΄μμ microservice(federated microservice) κ° νμνλ€.
μ΄λ gateway λ₯Ό supergraph λΌκ³ νκ³ , κ°κ°μ service λ€μ subgraph λΌκ³ μ΄ν΄νλ©΄ νΈνλ€.
federated microservice(subgraph) λ κ°κ°μ schema λ₯Ό κ°μ§κ³ μκ³ , gateway(supergraph) λ κ·Έ schema λ€μ ν κ³³μΌλ‘ λͺ¨μ μ μλ€.
π₯ λ¬Όλ‘ νλμ resolver μμ λͺ¨λ λ‘μ§μ μ²λ¦¬ν μλ μμ§λ§, κ·Έλ κ² νλ©΄ μ¬λ¬ ν
μ΄λΈμ join μ κ±Έμ΄μΌ νκ³ κ·Έλ κ² λλ©΄ graphql μ μ¬μ©νκ³ λ overFetching μ ν μ λ°μ μλ€.
κ·Έλ κΈ° λλ¬Έμ μνλ κ°λ§ μ‘°ννκΈ° μν΄ κ° entityλ₯Ό λ΄λΉνλ resolver μμ κ°κ° μ²λ¦¬νκ³ superGraph μμ ν΄λΉ entity λ€μ λͺ¨μμ λ°ννλ κ²μ΄λ€.
κ·Έλ¬λ©΄ κ°κ°μ subgraph λ₯Ό μ΄λ»κ² νλ²μ λͺ¨μΌλμ§ μλ μ½λλ₯Ό ν΅ν΄ νμΈν΄λ³΄μ.
// gateway.module.ts
import { Module } from '@nestjs/common';
import { GatewayService } from './gateway.service';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloGatewayDriver, ApolloGatewayDriverConfig } from '@nestjs/apollo';
import { IntrospectAndCompose } from '@apollo/gateway';
@Module({
imports: [
GraphQLModule.forRoot<ApolloGatewayDriverConfig>({
driver: ApolloGatewayDriver,
gateway: {
supergraphSdl: new IntrospectAndCompose({
// κ°κ°μ subGraph λ₯Ό νλλ‘ μ°κ²° μμΌμ€λ€.
subgraphs: [
{
name: 'account',
url: 'http://localhost:3001/graphql',
},
{
name: 'talk',
url: 'http://localhost:3002/graphql',
},
],
}),
},
}),
],
providers: [GatewayService],
})
export class GatewayModule {}
μ½λλ κ°λ¨νλ€. ApolloGatewayDriver, IntrospectAndCompose class
λ₯Ό μ΄μ©ν΄μ κ°κ°μ subgrapqh λ₯Ό ν΅ν©ν΄μ€ μ μλ€.
κ°κ°μ federated microservice μ schema μ€ @ObjectType
κ°μ relation μ νμ©νλ©΄ νλμ query μμ λ°μ΄ν°λ₯Ό κ°μ Έμ€λ κ²μ΄ κ°λ₯νλ€. κ·Έλ¬κΈ° μν΄μλ directive μ§μμ΄
κ° νμνλ€.
graphql μμλ λμΌν μ΄λ¦μ @ObjectType
μ΄ μ‘΄μ¬ νλ©΄ μλλ€
νμ§λ§ μ¬λ¬ μ±μμ λμΌν λͺ¨λΈμ μ°κ³ μΆμ μ μλ€. μλ₯Ό λ€μ΄, κ³μ νμ΄μ§μμ κ³ κ°μ μ 보λ₯Ό 보μ¬μ€ μλ μκ³ , μ±ν
λ°©μμ κ³ κ° μ 보λ₯Ό 보μ¬μ€ μλ μλ€. μ±ν
λ°©μμλ μ±ν
μ 보μ κ³ κ°μ μ 보λ₯Ό κ°μ΄ 보μ¬μ€μΌ νλ κ²½μ°κ° μκΈ΄λ€.
MSA ꡬ쑰μ λ°λΌμ κ° μ±μμ κ° λͺ¨λΈμ κ΅μ°¨ν΄μ μ’
μμν€λ건 μ³μ§ μμ λ°©λ²μ΄κΈ° λλ¬Έμ, κ° μ±μμ κ° λͺ¨λΈμ λλ€ μ μΈμ ν΄μ€λ€.
// chat > chat.model.ts
import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';
@ObjectType()
@Directive('@key(fields: "id")')
export class AccountModel {
@Field(() => ID)
id: string;
}
// account > account.model.ts
import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';
@ObjectType()
@Directive('@key(fields: "id")')
// λμΌ μ΄λ¦μ objectType μ΄ μμΌλ©΄ μλλ€
// -> λμΌν objectType μ΄ μλ€λ©΄ λ°λμ @Directive('@key(fields: "id")') λ‘ obejct μ pk λ₯Ό μ€μ ν΄μ€μΌ ν¨
export class AccountModel {
@Field(() => ID)
id: string;
@Field(() => String)
name: string;
}
μ΄λ κ² λ κ²½μ° schema μ κ°μ μ΄λ¦μ λͺ¨λΈμ΄ μ겨λ²λ¦¬κΈ° λλ¬Έμ graphql μ² νμ μλ°°λλ€. μ΄λ, @Directive('@key(fields: "id")')
λ‘ λ¬Έμ λ₯Ό νμ΄μ€λ€.
μ λ°μ½λ μ΄ν°λ μ€λ³΅μ νμ©ν΄μ€λ€λ μλ―Έλ₯Ό ν¬ν¨νκ³ μλ€. λν, μμ μλ @Key λ λͺ¨λΈμλ ν
μ΄λΈμ²λΌ PK λ₯Ό μ§μ ν΄μ£Όλ κ²μ΄λ€.
μ PKλ‘ μ ν id κ° κ°μ λͺ¨λΈμ΄ μμΌλ©΄ λκ°λ κ°μκ²μΌλ‘ νλ¨ν΄μ νλλ‘ λ¬Άμ΄μ€λ€.
κ²°κ΅
@ObjectType()
export class AccountModel {
@Field(() => ID)
id: string;
@Field(() => String)
name: string;
}
// schema.gql
type AccountModel {
id: ID!
name: String!
}
λ€μκ³Ό κ°μ νλμ λͺ¨λΈμ΄ λλ κ²μ΄λ€.
μ²μμ μ΄κ±° λλ¬Έμ μ λ₯Ό μ©μλ€.
μ무리 ꡬκΈλ§ν΄μ λμ€λ μ€λͺ
μ λ΄λ μ΄ν΄κ° μλκ³ , λμ μ μ΄κ±°λ₯Ό μ°λ©΄ dataLoader λμκΈ° λμκΈ°... κ·Έλμ μ νλΈ μμ λ° μ§μΈλ€μ ν΅ν΄μ μμλλ€.
ResolveField... μΌλ¨ Field λ€. resolve... ν΄κ²°νλ€?
μμνλ©΄ νλλ₯Ό νλ μ΄μ΄μ£Όλ κ²μ΄λ€.
μ¦ ResolveField κ° μν΄μλ 리쑸λ²μμ λ°ννλ λͺ¨λΈμ νλλ₯Ό νλ λ μΆκ°νλ νμλ€.
// chat.resolver.ts
import {
Resolver,
Query,
Args,
Int,
ResolveField,
Parent,
} from '@nestjs/graphql';
import { ChatService } from './chat.service';
import { AccountModel, ChatModel } from './entities';
@Resolver(() => ChatModel)
// μ΄ resolver λ chatModel μ λ½μλ΄κΈ° μν 리쑸λ²μ΄λ€
export class ChatResolver {
constructor(private readonly chatService: ChatService) {}
@Query(() => ChatModel)
async getChatInfo(
@Args('roomId', { type: () => Int }) roomId: number,
): Promise<ChatModel> {
return {
chatId: roomId,
};
}
@ResolveField(() => AccountModel)
user(@Parent() chat: ChatModel) {
return { __typename: 'UserEntity', id: chat.chatId };
}
}
// chat.model.ts
@ObjectType()
export class ChatModel {
@Field(() => Number)
chatId: number;
}
μ 리쑸λ²λ ChatModelμ λ°ννλ 리쑸λ²μ΄λ€. @ResolveField λ₯Ό ν΅ν΄μ ChatModel μ user
λΌλ νλλ₯Ό μΆκ°μ μΌλ‘ λ°ννκ² λ€λΌλ λ»μ΄λ€.
κ·Όλ° User λ AccountModel μ΄λ€. AccountModel μ Chat μ±μ΄ μλλΌ, Account μ±μ μ’
μλμ΄μλ€. μ΄λ»κ² κ°μ Έμ€μ§?
μ΄λ @Directive
κ° μ°μΈλ€!!!
@Query() getChatInfo
쿼리λ ChatModel μ λ°ννλ€.
κ·Έλ¬λ©΄ @Parent()
κ° ChatModel μΈ user @ResolveField()
κ° μ΄λ₯Ό μΊμΉνλ€. user @ResolveField()
κ° { __typename: 'UserEntity', id: chat.chatId }
λ₯Ό λ°ννλλ°, μ΄λ₯Ό λꡬνν
λ°ννλκ°..?
β
μμ λ§νλ―μ΄ graphql μμλ λμΌν ObjectTypeμ΄ μμ μ μλ€.
chat app μ μ’
μλμ΄ μλ Account Model μ
Account app μ μ’
μλμ΄ μλ Account Model μ΄
@Directive key λ‘ μ°κ²°λμ΄ μκΈ° λλ¬Έμ,
AccountModel μ μ’
μνκ³ μλ Account app μμ μ΄λ₯Ό μμ νλ€.
λ§μ½μ λλ€λ₯Έ app μμλ Account Model μ μ¬μ©νκ³ μλ€λ©΄? μ΄λμͺ½μμ μμ ν κΉ?
μ΄λ° κ³ λ―Όμ μλͺ»λλ€.
μ μ΄μ Account Model μ Account app μμ λ€λ£¨λκ²μ΄ μ³μ λ°©λ²μ΄λ€. (κ΄μ¬μ¬μ λΆλ¦¬κ° μ μ©λλ€)
κ·Έλ κΈ° λλ¬Έμ μ μ΄μ λ λ€λ₯Έ app μμλ@ResolveReference()
μ΄ μλλΌ,@ResolveField() user
λ₯Ό μ¬μ©νλ κ²μ΄ λ§λ€.
μ¦, Account Model μ Account app μμλ§ λ‘μ§μ νμ΄κ°μ!
// account.resolver.ts
/**
*
* @description chat resolver μμ μ²λ¦¬νμ§ λͺ»νλ 쿼리λ₯Ό μ¬κΈ°μ μ²λ¦¬ν¨ (find account)
* resolverField λ₯Ό μ¬κΈ°μ μμ ν΄μ μ²λ¦¬νλ€.
*/
@ResolveReference()
resolveReference(reference: {
__typename: string;
id: string;
}): AccountModel {
return {
id: reference.id,
name: `${reference.id} + name`,
};
}
μ΄λ κ² ResolveField -> ResolveReference -> ResolveField -> ResolveReference -> ... κ³μ νλ©΄ κ°μ₯ λ§μ§λ§μ μμ νλ 리쑸λ²λ₯Ό μλ§κ°μ§ μμ²μ μ²λ¦¬ν΄μΌ νλ κ²½μ°κ° μκΈ΄λ€.
μ΄λ₯Ό N+1 λ¬Έμ
λΌκ³ νλλ°, λ€μμ λ€λ€λ³΄μ.
https://blog.doctor-cha.com/integrating-graphql-services-with-graphql-federation
https://www.apollographql.com/docs/federation/