AI 던전 게임
모듈 1: 모노레포 설정
섹션 제목: “모듈 1: 모노레포 설정”먼저 새로운 모노레포를 생성합니다. 원하는 디렉터리에서 다음 명령어를 실행하세요:
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=pnpm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=yarn --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=npm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@~21.0.3 dungeon-adventure --pm=bun --preset=@aws/nx-plugin --ci=skip
이 명령어는 dungeon-adventure
디렉터리 내에 NX 모노레포를 설정하며, 이후 vscode에서 열 수 있습니다. 파일 구조는 다음과 같습니다:
디렉터리.nx/
- …
디렉터리.vscode/
- …
디렉터리node_modules/
- …
디렉터리packages/ 서브 프로젝트가 위치할 폴더
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json Nx CLI 및 모노레포 기본 설정 구성
- package.json 모든 노드 의존성 정의
- pnpm-lock.yaml 또는 bun.lock, yarn.lock, package-lock.json (패키지 매니저에 따라 다름)
- pnpm-workspace.yaml (pnpm 사용 시)
- README.md
- tsconfig.base.json 모든 노드 기반 서브 프로젝트가 상속할 기본 설정
- tsconfig.json
이제 @aws/nx-plugin
을 사용하여 다양한 서브 프로젝트를 생성할 준비가 되었습니다.
게임 API
섹션 제목: “게임 API”먼저 Game API를 생성해 보겠습니다. 다음 단계에 따라 GameApi
라는 tRPC API를 생성합니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - ts#trpc-api
- 필수 매개변수 입력
- name: GameApi
- 클릭
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
파일 트리에 몇 가지 새 파일이 생성된 것을 확인할 수 있습니다.
ts#trpc-api 생성된 파일
다음은 ts#trpc-api
생성기에 의해 생성된 모든 파일 목록입니다. 파일 트리에서 강조 표시된 주요 파일을 살펴보겠습니다:
디렉터리packages/
디렉터리common/
디렉터리constructs/
디렉터리src/
디렉터리app/ 애플리케이션 특화 CDK 컨스트럭트
디렉터리apis/
- game-api.ts tRPC API를 생성하는 CDK 컨스트럭트
- index.ts
- …
- index.ts
디렉터리core/ 일반적인 CDK 컨스트럭트
디렉터리api/
- rest-api.ts API Gateway Rest API를 위한 기본 CDK 컨스트럭트
- trpc-utils.ts tRPC API CDK 컨스트럭트 유틸리티
- utils.ts API 컨스트럭트 유틸리티
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
디렉터리types/ 공유 타입
디렉터리src/
- index.ts
- runtime-config.ts CDK와 웹사이트 모두에서 사용되는 인터페이스 정의
- project.json
- …
디렉터리game-api/ tRPC API
디렉터리src/
디렉터리client/ 기계 간 통신에 사용되는 클라이언트
- index.ts
- sigv4.ts
디렉터리middleware/ Powertools 계측
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
디렉터리schema/ API 입력 및 출력 정의
- echo.ts
디렉터리procedures/ API 프로시저/라우트 구현
- echo.ts
- index.ts
- init.ts 컨텍스트 및 미들웨어 설정
- local-server.ts 로컬 tRPC 서버 실행 시 사용
- router.ts 모든 프로시저를 정의하는 람다 핸들러 진입점
- project.json
- …
- eslint.config.mjs
- vitest.workspace.ts
주요 파일 몇 가지를 살펴보겠습니다:
import { awsLambdaRequestHandler, CreateAWSLambdaContextOptions,} from '@trpc/server/adapters/aws-lambda';import { echo } from './procedures/echo.js';import { t } from './init.js';import { APIGatewayProxyEvent } from 'aws-lambda';
export const router = t.router;
export const appRouter = router({ echo,});
export const handler = awsLambdaRequestHandler({ router: appRouter, createContext: ( ctx: CreateAWSLambdaContextOptions<APIGatewayProxyEvent>, ) => ctx, responseMeta: () => ({ headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', }, }),});
export type AppRouter = typeof appRouter;
라우터는 tRPC API의 진입점을 정의하며 모든 API 메서드를 선언하는 곳입니다. 위 예시에서는 echo
메서드가 있으며, 해당 구현은 ./procedures/echo.ts
파일에 있습니다.
import { publicProcedure } from '../init.js';import { EchoInputSchema, EchoOutputSchema,} from '../schema/echo.js';
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
이 파일은 echo
메서드의 구현체이며, 입력 및 출력 데이터 구조를 강력하게 타입화합니다.
import { z } from 'zod';
export const EchoInputSchema = z.object({ message: z.string(),});
export type IEchoInput = z.TypeOf<typeof EchoInputSchema>;
export const EchoOutputSchema = z.object({ result: z.string(),});
export type IEchoOutput = z.TypeOf<typeof EchoOutputSchema>;
모든 tRPC 스키마 정의는 Zod를 사용하여 정의되며, z.TypeOf
구문을 통해 TypeScript 타입으로 내보내집니다.
import { Construct } from 'constructs';import * as url from 'url';import { Code, Runtime, Function, FunctionProps, Tracing,} from 'aws-cdk-lib/aws-lambda';import { AuthorizationType, Cors, LambdaIntegration,} from 'aws-cdk-lib/aws-apigateway';import { Duration, Stack } from 'aws-cdk-lib';import { PolicyDocument, PolicyStatement, Effect, AccountPrincipal, AnyPrincipal,} from 'aws-cdk-lib/aws-iam';import { IntegrationBuilder, RestApiIntegration,} from '../../core/api/utils.js';import { RestApi } from '../../core/api/rest-api.js';import { Procedures, routerToOperations } from '../../core/api/trpc-utils.js';import { AppRouter, appRouter } from ':dungeon-adventure/game-api';
type Operations = Procedures<AppRouter>;
export interface GameApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { integrations: TIntegrations;}
export class GameApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { public static defaultIntegrations = (scope: Construct) => { return IntegrationBuilder.rest({ operations: routerToOperations(appRouter), defaultIntegrationOptions: { runtime: Runtime.NODEJS_LATEST, handler: 'index.handler', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/game-api/bundle', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, } satisfies FunctionProps, buildDefaultIntegration: (op, props: FunctionProps) => { const handler = new Function(scope, `GameApi${op}Handler`, props); return { handler, integration: new LambdaIntegration(handler) }; }, }); };
constructor( scope: Construct, id: string, props: GameApiProps<TIntegrations>, ) { super(scope, id, { apiName: 'GameApi', defaultMethodOptions: { authorizationType: AuthorizationType.IAM, }, defaultCorsPreflightOptions: { allowOrigins: Cors.ALL_ORIGINS, allowMethods: Cors.ALL_METHODS, }, policy: new PolicyDocument({ statements: [ new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: routerToOperations(appRouter), ...props, }); }}
이 CDK 컨스트럭트는 GameApi를 정의합니다. defaultIntegrations
메서드는 tRPC API의 각 프로시저에 대해 람다 함수를 자동 생성하며, 번들된 API 구현을 가리킵니다. 이는 cdk synth
시점에 번들링이 발생하지 않음을 의미합니다(NodeJsFunction 사용과 대조적).
스토리 API
섹션 제목: “스토리 API”이제 Fast API인 StoryApi
를 생성합니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - py#fast-api
- 필수 매개변수 입력
- name: StoryApi
- moduleName: story_api
- 클릭
Generate
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
파일 트리에 새 파일이 생성된 것을 확인할 수 있습니다.
py#fast-api 생성된 파일
py#fast-api
생성기에 의해 생성된 주요 파일:
디렉터리.venv/ 모노레포 단일 가상 환경
- …
디렉터리packages/
디렉터리common/
디렉터리constructs/
디렉터리src/
디렉터리app/
디렉터리apis/
- story-api.ts Fast API CDK 컨스트럭트
- project.json story_api 빌드 의존성 추가
디렉터리types/
디렉터리src/
- runtime-config.ts StoryApi 추가
디렉터리story_api/
디렉터리story_api/
- init.py Powertools, FastAPI 및 미들웨어 설정
- main.py 모든 라우트를 포함하는 람다 진입점
디렉터리tests/
- …
- .python-version
- project.json
- pyproject.toml
- .python-version 고정된 uv 파이썬 버전
- pyproject.toml
- uv.lock
import { Construct } from 'constructs';import * as url from 'url';import { Code, Runtime, Function, FunctionProps, Tracing,} from 'aws-cdk-lib/aws-lambda';import { AuthorizationType, Cors, LambdaIntegration,} from 'aws-cdk-lib/aws-apigateway';import { Duration, Stack } from 'aws-cdk-lib';import { PolicyDocument, PolicyStatement, Effect, AccountPrincipal, AnyPrincipal,} from 'aws-cdk-lib/aws-iam';import { IntegrationBuilder, RestApiIntegration,} from '../../core/api/utils.js';import { RestApi } from '../../core/api/rest-api.js';import { OPERATION_DETAILS, Operations,} from '../../generated/story-api/metadata.gen.js';
export interface StoryApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { integrations: TIntegrations;}
export class StoryApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { public static defaultIntegrations = (scope: Construct) => { return IntegrationBuilder.rest({ operations: OPERATION_DETAILS, defaultIntegrationOptions: { runtime: Runtime.PYTHON_3_12, handler: 'story_api.main.handler', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/story_api/bundle', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, } satisfies FunctionProps, buildDefaultIntegration: (op, props: FunctionProps) => { const handler = new Function(scope, `StoryApi${op}Handler`, props); return { handler, integration: new LambdaIntegration(handler) }; }, }); };
constructor( scope: Construct, id: string, props: StoryApiProps<TIntegrations>, ) { super(scope, id, { apiName: 'StoryApi', defaultMethodOptions: { authorizationType: AuthorizationType.IAM, }, defaultCorsPreflightOptions: { allowOrigins: Cors.ALL_ORIGINS, allowMethods: Cors.ALL_METHODS, }, policy: new PolicyDocument({ statements: [ new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: OPERATION_DETAILS, ...props, }); }}
이 CDK 컨스트럭트는 StoryApi를 정의합니다. defaultIntegrations
메서드는 FastAPI의 각 작업에 대해 람다 함수를 자동 생성합니다.
export type ApiUrl = string;export interface IRuntimeConfig { apis: { GameApi: ApiUrl; StoryApi: ApiUrl; };}
생성기가 기존 코드를 보존하면서 IRuntimeConfig
정의에 StoryApi
를 추가한 예시입니다.
from .init import app, lambda_handler, tracer
handler = lambda_handler
@app.get("/")@tracer.capture_methoddef read_root(): return {"Hello": "World"}
API 메서드는 여기에 정의됩니다. Pydantic을 사용하여 입력 및 출력을 타입 안전하게 선언할 수 있습니다.
게임 UI: 웹사이트
섹션 제목: “게임 UI: 웹사이트”게임과 상호작용할 수 있는 UI를 생성합니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - ts#react-website
- 필수 매개변수 입력
- name: GameUI
- 클릭
Generate
pnpm nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
npx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
파일 트리에 새 파일이 생성됩니다.
ts#react-website 생성된 파일
디렉터리packages/
디렉터리common/
디렉터리constructs/
디렉터리src/
디렉터리app/
디렉터리static-websites/
- game-ui.ts Game UI CDK 컨스트럭트
디렉터리core/
- static-website.ts 일반 정적 웹사이트 컨스트럭트
디렉터리game-ui/
디렉터리public/
- …
디렉터리src/
디렉터리components/
디렉터리AppLayout/
- index.ts 페이지 레이아웃
- navitems.ts 사이드바 네비게이션 항목
디렉터리hooks/
- useAppLayout.tsx 알림, 페이지 스타일 동적 설정
디렉터리routes/ @tanstack/react-router 파일 기반 라우팅
- index.tsx 루트 페이지
- __root.tsx 기본 컴포넌트
디렉터리welcome/
- index.tsx
- config.ts
- main.tsx React 진입점
- routeTree.gen.ts 자동 생성 라우트 트리
- styles.css
- index.html
- project.json
- vite.config.ts
import * as url from 'url';import { Construct } from 'constructs';import { StaticWebsite } from '../../core/index.js';
export class GameUI extends StaticWebsite { constructor(scope: Construct, id: string) { super(scope, id, { websiteFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-ui/bundle', import.meta.url, ), ), }); }}
Vite 기반 UI의 번들 경로가 미리 구성된 CDK 컨스트럭트입니다.
import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';
import '@cloudscape-design/global-styles/index.css';
const router = createRouter({ routeTree });
declare module '@tanstack/react-router' { interface Register { router: typeof router; }}
const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RouterProvider router={router} /> </I18nProvider> </React.StrictMode>, );
React 진입점으로, 파일 기반 라우팅을 사용합니다.
import { ContentLayout, Header, SpaceBetween, Container,} from '@cloudscape-design/components';import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/welcome/')({ component: RouteComponent,});
function RouteComponent() { return ( <ContentLayout header={<Header>Welcome</Header>}> <SpaceBetween size="l"> <Container>Welcome to your new Cloudscape website!</Container> </SpaceBetween> </ContentLayout> );}
/welcome
라우트에 렌더링되는 컴포넌트입니다.
게임 UI: 인증
섹션 제목: “게임 UI: 인증”Amazon Cognito를 통한 인증을 구성합니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - ts#react-website#auth
- 필수 매개변수 입력
- cognitoDomain: game-ui
- project: @dungeon-adventure/game-ui
- allowSignup: true
- 클릭
Generate
pnpm nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
npx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
파일 트리가 변경됩니다.
ts#react-website#auth 생성된 파일
디렉터리packages/
디렉터리common/
디렉터리constructs/
디렉터리src/
디렉터리core/
- user-identity.ts 사용자/ID 풀 CDK 컨스트럭트
디렉터리types/
디렉터리src/
- runtime-config.ts cognitoProps 추가
디렉터리game-ui/
디렉터리src/
디렉터리components/
디렉터리AppLayout/
- index.tsx 헤더에 로그인 사용자/로그아웃 추가
디렉터리CognitoAuth/
- index.ts Cognito 로그인 관리
디렉터리RuntimeConfig/
- index.tsx runtime-config.json 가져오기
디렉터리hooks/
- useRuntimeConfig.tsx
- main.tsx Cognito 추가
import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });declare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <RouterProvider router={router} /> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
RuntimeConfigProvider
와 CognitoAuth
컴포넌트가 추가되어 인증을 처리합니다.
게임 UI: Story API 연결
섹션 제목: “게임 UI: Story API 연결”Story API 연결을 구성합니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - api-connection
- 필수 매개변수 입력
- sourceProject: @dungeon-adventure/game-ui
- targetProject: dungeon_adventure.story_api
- 클릭
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
파일 트리가 변경됩니다.
UI -> FastAPI 연결 생성된 파일
디렉터리packages/
디렉터리game-ui/
디렉터리src/
디렉터리hooks/
- useSigV4.tsx StoryApi 요청 서명
- useStoryApiClient.tsx StoryApi 클라이언트 생성
- useStoryApi.tsx TanStack Query를 사용한 StoryApi 상호작용
디렉터리components/
- QueryClientProvider.tsx TanStack Query 클라이언트 제공자
- StoryApiProvider.tsx StoryApi 훅 제공자
- main.tsx 제공자 주입
- .gitignore 생성된 클라이언트 파일 무시
- project.json OpenAPI 훅 생성 타겟 추가
디렉터리story_api/
디렉터리scripts/
- generate_open_api.py
- project.json openapi.json 생성
import { StoryApi } from '../generated/story-api/client.gen';import { useSigV4 } from './useSigV4';import { useRuntimeConfig } from './useRuntimeConfig';import { useMemo } from 'react';
export const useStoryApi = (): StoryApi => { const runtimeConfig = useRuntimeConfig(); const apiUrl = runtimeConfig.apis.StoryApi; const sigv4Client = useSigV4(); return useMemo( () => new StoryApi({ url: apiUrl, fetch: sigv4Client, }), [apiUrl, sigv4Client], );};
StoryApi에 인증된 요청을 보내는 훅입니다.
import { createContext, FC, PropsWithChildren, useMemo } from 'react';import { useStoryApiClient } from '../hooks/useStoryApiClient';import { StoryApiOptionsProxy } from '../generated/story-api/options-proxy.gen';
export const StoryApiContext = createContext<StoryApiOptionsProxy | undefined>( undefined,);
export const StoryApiProvider: FC<PropsWithChildren> = ({ children }) => { const client = useStoryApiClient(); const optionsProxy = useMemo( () => new StoryApiOptionsProxy({ client }), [client], );
return ( <StoryApiContext.Provider value={optionsProxy}> {children} </StoryApiContext.Provider> );};
TanStack Query 훅을 사용하여 FastAPI와 상호작용합니다.
게임 UI: Game API 연결
섹션 제목: “게임 UI: Game API 연결”Game API 연결을 구성합니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - api-connection
- 필수 매개변수 입력
- sourceProject: @dungeon-adventure/game-ui
- targetProject: @dungeon-adventure/game-api
- 클릭
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
파일 트리가 변경됩니다.
UI -> tRPC 연결 생성된 파일
디렉터리packages/
디렉터리game-ui/
디렉터리src/
디렉터리components/
- GameApiClientProvider.tsx GameAPI 클라이언트 설정
디렉터리hooks/
- useGameApi.tsx GameApi 호출 훅
- main.tsx trpc 클라이언트 제공자 주입
- package.json
import { GameApiTRCPContext } from '../components/GameApiClientProvider';
export const useGameApi = GameApiTRCPContext.useTRPC;
tRPC의 React Query 통합을 사용하는 훅입니다.
import GameApiClientProvider from './components/GameApiClientProvider';import QueryClientProvider from './components/QueryClientProvider';import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });declare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <QueryClientProvider> <GameApiClientProvider> <RouterProvider router={router} /> </GameApiClientProvider> </QueryClientProvider> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
tRPC 제공자가 주입된 main.tsx
파일입니다.
게임 UI: 인프라
섹션 제목: “게임 UI: 인프라”CDK 인프라를 생성합니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - ts#infra
- 필수 매개변수 입력
- name: infra
- 클릭
Generate
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
파일 트리가 변경됩니다.
ts#infra 생성된 파일
디렉터리packages/
디렉터리common/
디렉터리constructs/
디렉터리src/
디렉터리core/
디렉터리cfn-guard-rules/
- *.guard
- cfn-guard.ts
- index.ts
디렉터리infra
디렉터리src/
디렉터리stacks/
- application-stack.ts CDK 리소스 정의
- index.ts
- main.ts 모든 스택 정의
- cdk.json
- project.json
- package.json
- tsconfig.json 참조 추가
- tsconfig.base.json 별칭 추가
import { ApplicationStack } from './stacks/application-stack.js';import { App, CfnGuardValidator, RuleSet,} from ':dungeon-adventure/common-constructs';
const app = new App({ policyValidationBeta1: [new CfnGuardValidator(RuleSet.AWS_PROTOTYPING)],});
new ApplicationStack(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, }, crossRegionReferences: true,});
app.synth();
CDK 애플리케이션 진입점으로, cfn-guard
를 사용한 인프라 검증을 수행합니다.
import * as cdk from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
// 스택 리소스 정의 }}
게임 인프라를 구축할 CDK 컨스트럭트를 정의하는 파일입니다.
인프라 업데이트
섹션 제목: “인프라 업데이트”packages/infra/src/stacks/application-stack.ts
를 업데이트하여 생성된 컨스트럭트를 인스턴스화합니다:
import { GameApi, GameUI, StoryApi, UserIdentity,} from ':dungeon-adventure/common-constructs';import * as cdk from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
// The code that defines your stack goes here const userIdentity = new UserIdentity(this, 'UserIdentity');
const gameApi = new GameApi(this, 'GameApi', { integrations: GameApi.defaultIntegrations(this).build(), }); const storyApi = new StoryApi(this, 'StoryApi', { integrations: StoryApi.defaultIntegrations(this).build(), });
// grant our authenticated role access to invoke our APIs [storyApi, gameApi].forEach((api) => api.grantInvokeAccess(userIdentity.identityPool.authenticatedRole), );
// Ensure this is instantiated last so our runtime-config.json can be automatically configured new GameUI(this, 'GameUI'); }}
기본 통합을 제공하여 각 API 작업을 개별 람다 함수에 매핑합니다.
코드 빌드
섹션 제목: “코드 빌드”Nx 명령어
단일 vs 다중 타겟
섹션 제목: “단일 vs 다중 타겟”run-many
명령어는 여러 서브프로젝트에서 타겟을 실행합니다. 종속성 순서를 올바르게 처리합니다.
단일 프로젝트 빌드 예시:
pnpm nx run @dungeon-adventure/infra:build
yarn nx run @dungeon-adventure/infra:build
npx nx run @dungeon-adventure/infra:build
bunx nx run @dungeon-adventure/infra:build
의존성 시각화
섹션 제목: “의존성 시각화”pnpm nx graph
yarn nx graph
npx nx graph
bunx nx graph

캐시 무시 빌드:
pnpm nx run @dungeon-adventure/infra:build --skip-nx-cache
yarn nx run @dungeon-adventure/infra:build --skip-nx-cache
npx nx run @dungeon-adventure/infra:build --skip-nx-cache
bunx nx run @dungeon-adventure/infra:build --skip-nx-cache
캐시 초기화:
pnpm nx reset
yarn nx reset
npx nx reset
bunx nx reset
pnpm nx run-many --target build --all
yarn nx run-many --target build --all
npx nx run-many --target build --all
bunx nx run-many --target build --all
다음 프롬프트가 표시됩니다:
NX The workspace is out of sync
[@nx/js:typescript-sync]: Some TypeScript configuration files are missing project references to the projects they depend on or contain outdated project references.
This will result in an error in CI.
? Would you like to sync the identified changes to get your workspace up to date? …Yes, sync the changes and run the tasksNo, run the tasks without syncing the changes
Yes, sync the changes and run the tasks를 선택하여 tsconfig.json 참조를 자동 업데이트합니다.
빌드 아티팩트는 dist/
폴더에 생성됩니다. 모든 설정이 완료되었습니다! 🎉🎉🎉