[Web] ์์ทจ๋ง๋ ํ๋ก์ ํธ
ํด๋น ํ๋ก์ ํธ GitHub Repository
https://github.com/2024-Hansung-Capstone/ProjectJ-Backend
GitHub - 2024-Hansung-Capstone/ProjectJ-Backend
Contribute to 2024-Hansung-Capstone/ProjectJ-Backend development by creating an account on GitHub.
github.com
1. ์ฃผ์ ์ค๋ช
ํ์ฑ๋ํ๊ต ์กธ์ ์ํ ํ๋ก์ ํธ์ ๋๋ค.
์กธ์ ์ํ์ธ ๋งํผ ์ฃผ์ ์ ๋ํ ๊ณ ๋ฏผ์ด ๊ฝค ๊ธธ์๊ณ , ์ฃผ์ ๋ฅผ ์ ์ฃผ๋ณ์์ ์ฐพ๋ ๊ฒ์ด ๊ด์ฐฎ์ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์์ต๋๋ค.
๊ทธ๋ฌ๋ค๊ฐ ์ฌ์ด๋์์ด ์์ทจ๋ฅผ ์์ํ๋ฉด์ ์์ทจ์ ๋ํ ๊ณ ๋ฏผ์ด ๊ฝค ๋ง๋ค๋ ๊ฒ์ ์๊ฒ ๋์๊ณ , ์กฐ๊ธ์ด๋๋ง ๋์์ด ๋ ์ ์์ง ์์๊น ํด์ ์์ทจ์์ ์ํ ์น์ฌ์ดํธ๋ก ์ฃผ์ ๋ฅผ ์ ์ ํ๊ฒ ๋์์ต๋๋ค.
์ฐ์ , ์์ทจํ ๋ ์ฌ์ฉ์๋ค์ด ์ด๋ค ์์์ ํด ๋จน์์ง ๊ณ ๋ฏผ์ ๋ง์ด ํ ๊ฒ ๊ฐ์์ AI ๊ธฐ๋ฅ์ด ํ์ฌ๋ ์๋ฆฌ ์ถ์ฒ ์๋น์ค๋ฅผ ๊ตฌํํ๊ฒ ๋์๊ณ , ์์ทจ๋ฅผ ์ด์ ๋ง ์์ํ๋ ์ฌ์ฉ์๋ค์ ์ํด์ ์๋ฃธ ์์น์ ์ ๋ณด๋ฅผ ์ง๋์์ ํ๋์ ๋ณผ ์ ์๊ฒ ๊ตฌํํ๊ฒ ๋์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ , ์์ทจํ๋ฉด์ ํ์ํ ๋ฌผํ์ ์ฌ์ฉ์๋ค๋ผ๋ฆฌ ๊ฑฐ๋ํ ์ ์๋๋ก ์ค๊ณ ๋ง์ผ ์๋น์ค๋ฅผ ๊ตฌํํ๊ฒ ๋์์ต๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก, ์์ทจ์๋ค์๊ฒ ํ์ํ ๊ธฐ๋ฅ์ด ๋ชจ๋ ๊ฐ์ถฐ์ง ์น์ฌ์ดํธ๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ๋ชฉํ๋ก ํ์ฌ ํ๋ก์ ํธ๋ฅผ ์งํํ์ต๋๋ค.
2. ๊ฐ๋ฐ ๊ธฐ๊ฐ
2024.01.25 ~ 2024.05.30 (์ฝ 4๊ฐ์)
3. ์ญํ
๋ฐฑ์๋ ์ด๊ด, ๋ฐฐํฌ ์๋ฒ ๊ด๋ฆฌ
- ์ค๊ณ
- ERD ์ค๊ณ
- ๋ก๊ทธ์ธ ๊ณผ์ ์ค๊ณ
- ํ์ ๊ธฐ๊ด ์ฝ๋ ์ค๊ณ
- ์ฌ์ฉ์ ๋ฑ๊ธ ๊ตฌ์ฑ
- ๊ตฌํ
- ๋ก๊ทธ์ธ ๊ธฐ๋ฅ ๊ตฌํ
- ์๋ฆฌ CRUD ๊ตฌํ, ์๋ฆฌ AI ๊ตฌํ
- ์ชฝ์ง ๊ธฐ๋ฅ ๊ตฌํ
- ์๋ฆผ ๊ธฐ๋ฅ ๊ตฌํ
- ํฌ์ธํธ ๊ธฐ๋ฅ ๊ตฌํ
- ๋ฐฐํฌ
- AWS EC2 ์ธ์คํด์ค ๋ฐฐํฌ
- ์ด๋ฏธ์ง ์ ๋ก๋ ๊ธฐ๋ฅ S3 ์ฌ์ฉ
4. ๊ธฐ์ ์คํ
- ์ธ์ด: TypeScript
- ํ๋ ์์ํฌ: NestJS
- ๋ฐ์ดํฐ๋ฒ ์ด์ค: MySQL
- ๋ฐฐํฌ: AWS EC2, S3, Elastic IP
- ๋๊ตฌ: Docker, GraphQL, OpenAI API, CoolSMS
5. ERD
6. ์ ์ฒด ๊ตฌ์กฐ
7. ์์ธ ๊ตฌํ ๋ด์ฉ
์ด๋ฒ ํ๋ก์ ํธ์ ํต์ฌ ๊ธฐ์ ์คํ์ NestJS์ GraphQL๋ก ์ ํ์ต๋๋ค.
GraphQL์ ์ฌ์ฉํ ์ด์
ํ๊ต์์๋ RestAPI ๋ฐฉ์๋ง์ ๋ฐฐ์ ๋๋ฐ ์ ํฌ ํ์๊ฒ ์์ํ GraphQL์ ๋์ ํ๊ฒ ๋ ์ด์ ๋ ๋ ๊ฐ์ง๊ฐ ์์ต๋๋ค.
์ฒซ ๋ฒ์งธ๋ก, ์ํ๋ ๋ฐ์ดํฐ๋ง์ ํด๋ผ์ด์ธํธ๊ฐ ์ฟผ๋ฆฌ๋ฅผ ํตํด ๊ฐ์ ธ์ฌ ์ ์๋ค๋ ์ด์ ์ด ์์๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ ๋ฒ์งธ๋ก, ํ๊ต์์ ์ ํด์ค ๊ธฐ๊ฐ์ด ์ด๋ฐํด์ Swagger์ ๊ฐ์ ๋ฌธ์ํ ์ฒ๋ฆฌ๋ฅผ ํ๊ธฐ์ ์๊ฐ์ด ๋ถ์กฑํ ์๋ ์๋ค๊ณ ์๊ฐ์ ํ์ฌ ๋ค๋ฅธ ๋ฐฉ์์ ์๊ฐ์ ํด ๋ณด๊ฒ ๋์์ต๋๋ค.
๊ทธ๋ฌ๋ค๊ฐ GraphQL ๋ฐฉ์์ Playground๋ฅผ ํตํด Api๊ฐ ์๋์ผ๋ก ๋ฌธ์ํ๋๊ณ , ํ๋ก ํธ์๋๊ฐ ์ฌ์ฉํด ๋ณผ ์ ์๋ ํ๊ฒฝ์ด ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณต๋๋ ์ด์ ์ด ์๋ค๋ ๊ฒ์ ์๊ฒ ๋๋ฉด์ ๋์ ํ๊ฒ ๋์์ต๋๋ค.
7-1. ํ์ ๊ด๋ฆฌ
- JWT๋ฅผ ์ด์ฉํ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ
์ฌ์ฉ์์ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ์ ๋ก๊ทธ์ธ ์ ์ง๋ฅผ ์ํด์ JWT ๊ธฐ๋ฅ์ ๋์ ํ์ต๋๋ค.
JWT ํ ํฐ์ accessToken๊ณผ restoreToken์ผ๋ก ๊ตฌ์ฑํ์๊ณ , accessToken์ ๋ง๋ฃ ์๊ฐ์ 10๋ถ, restoreToken์ ๋ง๋ฃ ์๊ฐ์ 2์ฃผ ์ ๋๋ก ์ก์์ต๋๋ค.
๋ฐ๋ผ์, accessToken์ด ๋ง๋ฃ๋์ด๋ restoreToken์ ํตํด accessToken ์ฌ๋ฐ๊ธ์ด ๊ฐ๋ฅํ๊ฒ ํด์ ์ฌ์ฉ์์ ๋ก๊ทธ์ธ ์ ์ง ์๊ฐ์ ์์ ์ ์ผ๋ก ์ฆ๊ฐ์์ผฐ์ต๋๋ค.
[accessToken ๋ฐ๊ธ]
/**
* JWT ํ ํฐ ์์ฑ ์๋น์ค ๋ฉ์๋
* @param email ์ฌ์ฉ์ ์ด๋ฉ์ผ
* @param password ์ฌ์ฉ์ ๋น๋ฐ๋ฒํธ
* @returns accessToken
*/
getAccessToken(user: User | IUserContext['user']): string {
return this.jwtService.sign(
{ sub: user.id },
{ secret: process.env.JWT_ACCESS_SECRET, expiresIn: '100m' },
);
}
[restoreToken์ ํตํ accessToken ์ฌ๋ฐ๊ธ]
/**
* JWT ํ ํฐ ๋ง๋ฃ ๋๋น refresh ํ ํฐ ์์ฑ ์๋น์ค ๋ฉ์๋
* @param user ์ฌ์ฉ์ ์ ๋ณด
* @param context context
*/
getRefreshToken(user: User, context: IContext): void {
const refreshToken = this.jwtService.sign(
{ sub: user.id },
{ secret: process.env.JWT_REFRESH_SECRET, expiresIn: '2w' },
);
context.res.setHeader(
'set-cookie',
`refreshToken=${refreshToken}; path=/;`,
);
}
- ๋ก๊ทธ์ธ์ด ํ์ํ Resolver ๊ธฐ๋ฅ ๋ณดํธ
ํ์๊ฐ์ , ๋ก๊ทธ์ธ, ๊ฒ์๊ธ ์กฐํ์ ๊ฐ์ ๊ธฐ๋ฅ์ ๋ก๊ทธ์ธ ํ์ง ์์ ์ฌ์ฉ์๋ ์ธ ์ ์๋ ๊ธฐ๋ฅ์ด์ง๋ง ํ์ ์ ๋ณด ์์ , ๊ฒ์๊ธ ์์ /์ญ์ ๋ฑ์ ๊ธฐ๋ฅ์ ์ธ์ฆ์ด ๋ ์ฌ์ฉ์๋ง ํธ์ถ์ด ๊ฐ๋ฅํ๋๋ก ์ค์ ํด์ค์ผ ํ์ต๋๋ค.
๋ฐ๋ผ์, ๋ณดํธ๊ฐ ํ์ํ ๊ธฐ๋ฅ์๊ฒ `@UseGuards` ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ๋ถํ์คฌ๊ณ , `AuthGuard('jwt')`๋ฅผ ์ธ์๋ก ๋ฃ์ด์ ํด๊ฒฐํ๋ ค ํ์ง๋ง ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.
Cannot read properties of undefined (reading 'headers') ์ค๋ฅ๊ฐ ๋ฐํํ ์ด์
์ ์๋๋์ง ์์ ์ด์ ๋ GraphQL ํ๊ฒฝ์์ request ๊ฐ์ฒด๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณต๋์ง ์์์ request ๋ด์ ์๋ header ์ ๋ณด๋ฅผ ํ์ธํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ฐ๋ผ์, GraphQL์์ UseGuards๋ฅผ ์ฌ์ฉํ ๋ header์ ๊ฐ์ ์ ์์ ์ผ๋ก ๋ถ๋ฌ์ค๊ธฐ ์ํด AuthGuard๋ฅผ extendํ๋ ํด๋์ค๋ฅผ ๋ฐ๋ก ๋ง๋ค์ด์ฃผ์ด ํด๊ฒฐํ์ต๋๋ค.
[GraphQL ํ๊ฒฝ์ ์ํด ์์ฑํ AuthGuard]
export class gqlAuthAccessGuard extends AuthGuard('heoga') {
getRequest(context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
return gqlContext.getContext().req;
}
}
๊ทธ๋ฆฌ๊ณ , 'heoga'๋ผ๊ณ ์ด๋ฆ์ ์ง์ PassportStrategy๋ฅผ ๊ตฌํํ์ต๋๋ค.
export default class JwtAccessStrategy extends PassportStrategy(Strategy, 'heoga') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_ACCESS_SECRET,
});
}
validate(payload) {
console.log(payload);
return {
id: payload.sub,
};
}
}
์ด์ @UseGuard()์ ์ธ์๋ก ์์์ ๋ง๋ GqlAuthAccessGuard๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ํด๋น ๊ธฐ๋ฅ์ GraphQL ํ๊ฒฝ์์๋ JWT Access Token ์ธ์ฆ์ด ๋ ์ํ์์๋ง ์ฌ์ฉํ ์ ์๊ฒ ๋์์ต๋๋ค.
@UseGuards(gqlAccessGuard)
@Mutation(() => User, {
description: 'ํ์ฌ ๋ก๊ทธ์ธ ๋ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์์ ํ๋ ๊ธฐ๋ฅ์
๋๋ค.',
})
async updateUser(
...
}
7-2. Open AI ๋์
์๋ฆฌ ๋ ์ํผ ์๋ ์์ฑ์ ์ํด์๋ AI ๊ธฐ๋ฅ ๋์ ์ด ํ์ํ์ต๋๋ค.
๋ฐ๋ผ์, ์ ํฌ ํ๋ก์ ํธ์์๋ Open AI API๋ฅผ ์ฌ์ฉํ์ฌ ๋ ์ํผ๋ฅผ ์๋ ์์ฑํ์ต๋๋ค.
์ ํฌ๊ฐ AI๋ฅผ ํ์ฉํด์ ๋ง๋ค์ด์ผ ํ๋ ๊ธฐ๋ฅ์ ํ๋ฆ์ ์๋์ ๊ฐ์ต๋๋ค.
์ฌ์ฉ์์๊ฒ ์์ฌ๋ฃ ์
๋ ฅ๋ฐ๊ธฐ -> ์์ฌ๋ฃ ์ ๋ณด Open AI์๊ฒ ์ ๋ฌ -> Open AI๊ฐ ํด๋น ์์ฌ๋ฃ๋ก ๋ง๋ค ์ ์๋ ์๋ฆฌ ์ ์ -> ํด๋น ์๋ฆฌ์ ํ์ ์ฌ๋ฃ์ ์กฐ๋ฆฌ๋ฐฉ๋ฒ ๋ฑ์ JSON์ผ๋ก ์์ฑ -> ํ๋ก ํธ์๋์๊ฒ GraphQL Query๋ก ๋๊ฒจ์ค
ํด๋น ๊ธฐ๋ฅ์ ์ํํ๊ธฐ ์ํด Open AI์์ ์ง์ํ๋ ๊ธฐ๋ฅ ์ค assistant ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ต๋๋ค.
Open AI Assistant
Open AI๊ฐ ์ง์ํ๋ ๊ธฐ๋ฅ ์ค ํ๋๋ก, ํด๋น assistant์๊ฒ ๋ช ๋ นํ ๋ช ๋ น์ด๋ฅผ ์น์์ ๋ฏธ๋ฆฌ ์ง์ ํด๋์ ์ ์์ต๋๋ค.
๊ทธ ์ดํ์ ํด๋น assistant์ id๋ฅผ ํตํด API๋ฅผ ํธ์ถํ์ฌ ๋ฏธ๋ฆฌ ์ง์ ํด๋จ๋ ๋ช ๋ น์ ์ํํ๊ณ ๊ฒฐ๊ณผ๊ฐ์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
๋งค๋ฒ ๋์ผํ ๋ช ๋ น์ ์ํํด์ผ ํ๊ณ , assistant์์ ์ง์ํ๋ JSON ๋ฐฉ์์ return์ด ์ ์ฉํ ์ํฉ์ด์๋ ์ ํฌ ํ๋ก์ ํธ์ ๋์ ํ๊ธฐ ์๋ง์ ๊ธฐ๋ฅ์ด๋ผ๊ณ ์๊ฐํ์ต๋๋ค.
[์์ฌ๋ฃ ์ ๋ณด๋ฅผ AI์๊ฒ ์ ๋ฌํ์ฌ ๋ ์ํผ ์ ๋ณด๋ฅผ ๋ฐ์์ค๋ Service]
async getRecipes(user_id: string) {
const myIngredients = await this.findIngredientByUserId(user_id);
const ingredientsInfo = myIngredients.map((ingredient) => ({
name: ingredient.name,
volume: ingredient.volume,
volume_unit: ingredient.volume_unit,
}));
const headers = {
Authorization: `Bearer ${process.env.OPENAI_SECRET}`,
'Content-Type': 'application/json',
'OpenAI-Beta': 'assistants=v2',
};
const run = await this.httpService
.post(
`https://api.openai.com/v1/threads/runs`,
{
assistant_id: 'asst_YFQJAwlpvKdljoQuYnZZHBDt',
thread: {
messages: [
{
role: 'user',
content: JSON.stringify(ingredientsInfo),
},
],
},
},
{ headers: headers },
)
.pipe(map((response) => response.data))
.toPromise();
let result;
do {
await new Promise((resolve) => setTimeout(resolve, 5000));
result = await this.httpService
.get(
`https://api.openai.com/v1/threads/${run.thread_id}/runs/${run.id}`,
{
headers: headers,
},
)
.toPromise();
} while (result.data.status !== 'completed');
const response = await this.httpService
.get(`https://api.openai.com/v1/threads/${run.thread_id}/messages`, {
headers: headers,
})
.toPromise();
return JSON.parse(response.data.data[0].content[0].text.value).recipes;
}
7-3. ์ง์ญ ์ฝ๋ ๊ด๋ฆฌ
์ฌ์ฉ์ ์ง์ญ ๊ด๋ฆฌ๋ฅผ ์ํด ํ์ ์์ ๋ถ์์ 2024๋ 2์ 1์ผ ๊ธฐ์ค์ ํ์ ๋ ์ฝ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
ํ์ ๊ธฐ๊ด(ํ์ ๋) ๋ฐ ๊ดํ ๊ตฌ์ญ(๋ฒ์ ๋) ๋ณ๊ฒฝ๋ด์ญ(2024.2.1. ์ํ) | ํ์ ์์ ๋ถ> ์ ๋ฌด์๋ด> ์ฐจ๊ด๋ณด>
ํ์ ์์ ๋ถ ํํ์ด์ง์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค.
www.mois.go.kr
์/๋, ์/๊ตฐ/๊ตฌ, ์/๋ฉด/๋ ๋จ์๋ก ์ฝ๋๊ฐ ๋๋์ด์ง ๊ฒ์ ํ์ ํ๊ณ , ์๋์ ๊ฐ์ ๊ตฌ์กฐ๋ก ์ฝ๋๊ฐ ๊ตฌ์ฑ๋์ด ์์์ต๋๋ค.
ex) ํํ๋
1 1 / 1 1 0 / 6 5 0 0 0
--- ----- ---------
์๋ ์๊ตฐ๊ตฌ ํ์ ๋
๊ฒฐ๊ณผ์ ์ผ๋ก, ๊ฐ๊ฐ ํ ์ด๋ธ๋ก ๋ง๋ค์ด ๊ฐ ํ์ ๋จ๊ณ์ ์ง์ญ ๊ธฐ์ค ํ ์ด๋ธ๊ณผ One To Many ๊ด๊ณ๋ฅผ ์ด๋ฃจ์ด ์ง์ญ์ฝ๋๋ฅผ ๊ด๋ฆฌํ์ต๋๋ค.
7-4. ์๋ฆผ ๊ธฐ๋ฅ ๊ตฌํ
์๋ฆผ์ด ์์ฑ๋๋ ๋ค์ํ ์ํฉ๋ค์ด ์์ด ์ฐ์ ํด๋น ์ํฉ๋ค์ ์ ๋ฆฌํ์ต๋๋ค.
- ์ ๊ท ๊ฐ์ ์
- ๋ด ์ ํ์ ์ฐ์ด ์๊ธธ ์
- ๋ด๊ฐ ์ฐํ ์ ํ์ ๊ฐ๊ฒฉ์ด ๋ณ๊ฒฝ๋ ์
- ๋ด ๊ฒ์๊ธ์ ์ข์์๊ฐ ์๊ธธ ์
- ๋ด ๋๊ธ์ ์ข์์๊ฐ ์๊ธธ ์
- ๋ด ๊ฒ์๊ธ์ ๋๊ธ์ด ๋ฌ๋ฆด ์
- ์ชฝ์ง๊ฐ ์ฌ ์
์ด ์ธ์๋ ์ดํ ๋ค์ํ ์๋ฆผ์ด ์ถ๊ฐ๋ ์ ์์ด์ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋ ๊ตฌ์กฐ๊ฐ ํ์ํ์ต๋๋ค.
๊ทธ๋์, HTTP์ Status Code ๊ด๋ฆฌ ๋ฐฉ์๊ณผ ๋น์ทํ๊ฒ ์๋ฆผ ์ํฉ๋ค์ ๊ด๋ฆฌํ์ต๋๋ค.
- 1XX : ์ฌ์ฉ์ ๊ด๋ จ
- 2XX : ์ข์์/์ฐ ๊ด๋ จ
- 3XX : ๊ฒ์๊ธ ๊ด๋ จ
- 4XX : ์ชฝ์ง ๊ด๋ จ
๊ฒฐ๊ณผ์ ์ผ๋ก, ์๋ฆผ ์ํฉ ๋ณ ๋ฉ์์ง๋ฅผ ๊ด๋ฆฌํ๋ ํด๋์ค๋ฅผ ๋ฐ๋ก ๋ง๋ค์๊ณ , ์ฌ๊ธฐ์์ ์ํฉ ๋ณ๋ก ์ฝ๋๋ฅผ ๋ถ์ฌํ์ต๋๋ค.
๋ํ, ์ฝ๋ ๋ณ๋ก ํด๋น ์ฝ๋์ ์ํฉ์ ๋ง๋ message๋ฅผ ๊ฐ๋๋ก ๊ตฌํํ์ต๋๋ค.
[์๋ฆผ ์ํฉ ๋ณ message ๊ด๋ฆฌ ํด๋์ค]
export class NotificationMessages {
/**
* ์๋ฆผ ๋ฉ์์ง ์์ฑ ๋ฉ์๋
* ์๋ฆผ ์ฝ๋์ ๋ฐ๋ผ ์๋ฆผ ๋ฉ์์ง๋ฅผ ์์ฑํฉ๋๋ค.
* @param notification ๋ฉ์์ง๋ฅผ ์กฐํํ ์๋ฆผ ์ ๋ณด
* @returns ์์ฑ๋ ์๋ฆผ ๋ฉ์์ง
*/
async getMessage(notification: Notification): Promise<string> {
let message = '';
try {
switch (notification.code) {
case '100':
message = `${notification.user.name}๋์ ์ ๊ท ํ์๊ฐ์
์ ํ์ํฉ๋๋ค!`;
break;
case '200':
message = `${notification.like.user.name}๋์ด '${notification.used_product.title}' ์ ํ์ ์ฐํ์์ต๋๋ค.`;
break;
case '201':
message = `๋ด๊ฐ ์ฐํ '${notification.used_product.title}'์ ๊ฐ๊ฒฉ์ด ${notification.used_product.price}์์ผ๋ก ๋ณ๋๋์์ต๋๋ค.`;
break;
case '202':
message = `๊ฒ์๊ธ '${notification.board.title}'์ ${notification.like.user.name}๋์ด ์ข์์๋ฅผ ๋๋ ์ต๋๋ค.`;
break;
case '203':
message = `'${notification.board.title}' ๊ฒ์๊ธ์ ๋จ ๋ด ๋๊ธ์ ${notification.like.user.name}๋์ด ์ข์์๋ฅผ ๋๋ ์ต๋๋ค.`;
break;
case '300':
message = `๊ฒ์๊ธ '${notification.board.title}'์ ${notification.reply.user.name}๋์ด ๋๊ธ์ ๋จ๊ฒผ์ต๋๋ค.`;
break;
case '400':
message = `'${notification.letter.sender.name}๋์ด ${notification.letter.category} ์นดํ
๊ณ ๋ฆฌ์์ ์ชฝ์ง๋ฅผ ๋ณด๋์ต๋๋ค.`;
break;
default:
message = '์ ์ ์๋ ์๋ฆผ์
๋๋ค.';
break;
}
return message;
} catch (e) {
console.error(e);
throw e;
}
}
}
๊ทธ๋ฆฌ๊ณ , ์๋ฆผ ์ ๋ณด๊ฐ ์๋ ์ํฐํฐ์์ ํด๋น ์๋ฆผ์ด ์ฌ์ฉ์ ๊ด๋ จ ์ผ ์๋ ์๊ณ , ๊ฒ์๊ธ์ด๋ ๋๊ธ, ์ข์์ ํน์ ์ชฝ์ง ๊ด๋ จ ์๋ฆผ ์ผ ์๋ ์๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ์ ๋ณด๊ฐ ๋ค์ด์ฌ ์ ์๋๋ก ๊ตฌ์ฑํ๊ณ ํด๋น ์๋ฆผ๊ณผ ๊ด๋ จ ์๋ ํ๋๋ NULL์ ๋ฃ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ , Strategy ๊ธฐ๋ฒ์ ์ฌ์ฉํ์ฌ ์๋ฆผ ๋ณ๋ก ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ ๋งค์นญํ๊ณ ๋ฉ์์ง๋ฅผ ๊ตฌ์ฑํ์ฌ ์๋ฆผ์ ์์ฑํ ์ ์๋๋ก ํ์ต๋๋ค.
[์ชฝ์ง ๊ด๋ จ์ธ 400๋ฒ๋ ์๋ฆผ์ผ ๋ ์ ์ฉํ strategy]
export class LetterNotificationStrategy implements NotificationStrategy {
constructor(
private letterService: LetterService,
private notificationRepository: Repository<Notification>,
) {}
async createNotification(
entity_id: string,
code: string,
): Promise<Notification> {
const letter = await this.letterService.findById(entity_id);
return await this.notificationRepository.save({
user: letter.receiver,
code: code,
letter: letter,
});
}
}
์ต์ข ์ ์ผ๋ก ํด๋น ์๋ฆผ์ ์์ฑ์ํฌ ์ํฉ์ service์์ ์๋ฆผ์ ์์ฑํ๋ service๋ฅผ ๋ถ๋ฌ์ ์๋ฆผ ๊ตฌํ์ ์ฑ๊ณตํ ์ ์์์ต๋๋ค.
7-5. ๋ฐฐํฌ๋ฅผ ์ํ ํจํค์ง
๋ก์ปฌ์ด ์๋ aws์ EC2 ์ธ์คํด์ค ํ๊ฒฝ์์ ์์ํ ๋ฐฐํฌ๋ฅผ ์ํ์ฌ Docker๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
Docker์ ์ฌ์ฉ์ผ๋ก ๋ค๋ฅธ ์ด์์ฒด์ ํ๊ฒฝ์์๋ ๋์ผํ ํ๋ก์ ํธ ์คํ ํ๊ฒฝ์ ๊ฐ์ถ์ด ์์ ์ ์ธ ์คํ์ด ๊ฐ๋ฅํ์ต๋๋ค.
[Dockerfile]
# 1. ์ด์์ฒด์ ์ค์น(node ์ต์ ๋ฒ์ ๊ณผ npm๊ณผ yarn์ด ๋ชจ๋ ์ค์น๋์ด์๋ ๋ฆฌ๋
์ค)
FROM node:latest
# 2. ๋ด ์ปดํจํฐ์ ์๋ ํด๋๋ ํ์ผ์ ๋์ปค ์ปดํจํฐ ์์ผ๋ก ๋ณต์ฌํ๊ธฐ
COPY ./package.json /ProjectJ-Backend/
COPY ./yarn.lock /ProjectJ-Backend/
WORKDIR /ProjectJ-Backend/
RUN yarn install
ENV NODE_OPTIONS="--max-old-space-size=1024"
COPY . /ProjectJ-Backend/
# 3. ๋์ปค์์์ index.js ์คํ์ํค๊ธฐ
CMD yarn start:dev
ENV NODE_OPTIONS="--max-old-space-size=1024"๋ฅผ ๋ฃ์ ์ด์
AWS์์ Free Tier๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ ์ฅ ๊ณต๊ฐ๊ณผ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํ์ ์ ์ด์์ต๋๋ค.
Swap ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํตํด ์ด๋ ์ ๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ๋ ธ์ง๋ง, JavaScript heap out of memory ์ค๋ฅ๊ฐ ๋ฐ์ํ์ฌ์ ์ต๋ ํ ๋ฉ๋ชจ๋ฆฌ ์ ํ์ ์ํด ํด๋น ์ต์ ์ ์ถ๊ฐํ์ต๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค์ธ MySQL ํ๊ฒฝ๋ ํ์ํ์๋๋ฐ, EC2 ์ธ์คํด์ค์์ MySQL์ ์ค์นํ๋ ๋์ ์ Docker์ ์ปจํ ์ด๋๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐํธํ ๊ฒ ๊ฐ์์ต๋๋ค.
๋ฐ๋ผ์, ๋ ํ๊ฒฝ์ ํ๋๋ก ๊ด๋ฆฌํ๊ธฐ ์ํด docker-compose๋ฅผ ๋์ ํ์์ต๋๋ค.
[docker-compose.yaml]
version: '3.7'
services:
backend:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./src:/ProjectJ-Backend/src
ports:
- '${SERVER_EXTERNAL_PORT}:${SERVER_INTERNAL_PORT}'
env_file:
- ./.env.docker
database:
image: mysql:latest
environment:
MYSQL_DATABASE: ${DATABASE_DATABASE}
MYSQL_ROOT_PASSWORD: ${DATABASE_PASSWORD}
ports:
- '${DATABASE_EXTERNAL_PORT}:${DATABASE_INTERNAL_PORT}'
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
volume์ ์ง์ ํ์ฌ docker-compose๊ฐ down ๋๋๋ผ๋ ๋ฐ์ดํฐ๊ฐ ์ฌ๋ผ์ง์ง ์๋๋ก ์ค์ ํ์ต๋๋ค.
7-6. ๋ฐฐํฌ
๋ฐฐํฌ๋ฅผ ์ํด EC2 ์ธ์คํด์ค๋ฅผ Free Tier ์ฑ๋ฅ์ผ๋ก ์์ฑํ์์ต๋๋ค.
- ์ด์์ฒด์ : ubuntu
- ์ธ์คํด์ค ์ ํ: t2.micro
- ์ง์ญ: ap-northeast-2
SSH๋ฅผ ํตํด ์ธ์คํด์ค์ ์ ์ํ์์ผ๋ฉฐ, ํธ๋ฆฌํ ์ ์์ ์ํด ์๋์ ๊ฐ์ด ์ค์ ์ ํ์์ต๋๋ค.
[ .ssh/config ]
Host project
HostName 54.180.182.40
User ec2-user
IdentityFile ~/key/ssh/project_key.pem
24์๊ฐ ๋ฌด์ค๋จ ๋ฐฐํฌ๋ฅผ ํด์ผ ํ๋๋ฐ, Docker๋ฅผ ์ฌ์ฉํ๊ณ ์๋์ ๊ฐ์ ๋ช ๋ น์ด๋ฅผ ํตํด ๊ฐ๋จํ ๋ฐฐํฌํ์์ต๋๋ค.
[๋ฐฑ๊ทธ๋ผ์ด๋ ์คํ]
docker-compose up --build -d
[์ข ๋ฃ]
docker-compose down
8. ์คํ ์๋๋ฆฌ์ค
8-1. ํ์๊ฐ์ , ๋ก๊ทธ์ธ
- ํ์๊ฐ์
: ์ด๋ฆ, ์์ด๋, ๋น๋ฐ๋ฒํธ, ์ด๋ฉ์ผ, ์ ํ๋ฒํธ, ์ฑ๋ณ, ์๋
์์ผ์ ์
๋ ฅํด์ผ ํ์๊ฐ์
์ด ๊ฐ๋ฅํฉ๋๋ค.
- ์ธ์ฆ๋ฒํธ ๋ฐ์ก์ ํตํด์ ์์ ์ฑ์ ํ๋ณดํด์ค๋๋ค.
- ๋ก๊ทธ์ธ: ํ์๊ฐ์ ํ, ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ๋ฉด ๋ก๊ทธ์ธ์ด ๋ฉ๋๋ค.
8-2. ์๋ฆฌ
- ์์ทจ์๋ค์ด ๋์ฅ๊ณ ์ ์๋ ์ฌ๋ฃ๋ฅผ ์ ๋ ฅํ๋ฉด AI๊ฐ ๋ ์ํผ๋ฅผ ์ถ์ฒํด์ค๋๋ค.
- AI๊ฐ ์ถ์ฒํด์ค ๋ ์ํผ๋ฅผ ์ฌ์ง๊ณผ ํจ๊ป ์๋ฆฌ ๊ฒ์ํ์ ์ฌ๋ ค ๋ค๋ฅธ ์์ทจ์๋ค๊ณผ ๊ณต์ ํ ์ ์๊ณ , ์กฐํ์๊ฐ ๊ฐ์ฅ ๋์ ๊ฒ์๊ธ์ ์ธ๊ธฐ ๋ ์ํผ BEST์ ๋ณด์ฌ์ค๋๋ค.
- ๋ํ, ์ฌ๋ฃ/์๋ฆฌ๋ฅผ ๊ฒ์ํ๋ฉด ํํฐ๋ง ๋์ด์ ๋ณด์ฌ์ค๋๋ค.
8-3. ์๋ฃธ
- ์๋ฃธ api๋ฅผ ์ด์ฉํ์ฌ ๋ด ์ฃผ๋ณ ์๋ฃธ๊ณผ ๊ทธ์ ๋ํ ์ ๋ณด๋ฅผ ์ง๋๋ก ํ์ธ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ๋ํ, ์กฐํ์๊ฐ ๋ง์ ์์ผ๋ก ๊ทธ ์ง์ญ์ ์ธ๊ธฐ ์๋ฃธ Best 3๋ฅผ ๋ณด์ฌ์ค๋๋ค.
8-4. ์์ทจ์ ๋ฉ์ดํธ
- ๋๋ค ์์ทจ์ ์น๊ตฌ๋ฅผ ๋ง๋ค ์ ์๋ ํ์ด์ง์ด๋ฉฐ, ํ์๊ฐ์ ๋ ์ฌ์ฉ์์๊ฒ ๋ฐ์ ์ ๋ ฅ๊ฐ์ ๋ฐํ์ผ๋ก ์น๊ตฌ๋ฅผ ์ถ์ฒํ์ฌ ์ง์ญ ์ปค๋ฎค๋ํฐ๋ฅผ ํ์ฑํํฉ๋๋ค.
- ์ถ์ฒ ์์ทจ์ ๋ฉ์ดํธ ์ธ์๋ ๋ณธ์ธ์ด ์ํ๋ ์กฐ๊ฑด์ ํํฐ๋งํ์ฌ ์ถ์ฒ ๋ฉ์ดํธ์ ๋จ์ง ์๋ ์น๊ตฌ์๋ ์ฐ๋ฝ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ๋ณธ์ธ์ด ์ํ๋ ์์ทจ์ ์น๊ตฌ๋ฅผ ์ฐพ์์ผ๋ฉด ์ชฝ์ง๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์์ต๋๋ค.
8-5. ์ค๊ณ ๋ง์ผ
- ์ฌ์ฉ์์ ๊ฐ์ ์ง์ญ์ ๊ฑฐ์ฃผํ๋ ์ ์ ๊ฐ ์ฌ๋ฆฐ ์ค๊ณ ๋ฌผํ์ ๋ณผ ์ ์๊ณ , ์ชฝ์ง๋ฅผ ํตํด ๊ฑฐ๋๋ฅผ ํ ์ ์์ต๋๋ค.
- ๋ํ, ๊ฒ์์ ํตํด ํ์ํ ์ค๊ณ ๋ฌผํ์ ์ฐพ์ ์๋ ์์ต๋๋ค.
- ์ฌ๋ฆฐ ๊ฒ์๊ธ์ ์์ , ์ญ์ ํ ์ ์๊ณ ์ฐํ๊ธฐ๋ฅผ ํตํด ์ํ๋ ์ค๊ณ ๋ฌผํ์ ์ฐํด๋์ ์ ์์ต๋๋ค.
8-6. ์ปค๋ฎค๋ํฐ
- ์์ทจ์๋ค์๊ฒ ํ์ํ ์ ๋ณด๋ค์ ๋ค๋ฅธ ์์ทจ์๋ค๊ณผ ๊ณต์ ํ ์ ์์ต๋๋ค.
- ๊ฒ์๊ธ์ ์ฌ์ง๊ณผ ํจ๊ป ์ฌ๋ฆฌ๋ฉด ์ฌ๋ฆฐ ์๊ฐ, ์กฐํ์ ๋ฑ์ ๋ณผ ์ ์๊ณ , ์กฐํ์๊ฐ ๊ฐ์ฅ ๋ง์ ๊ฒ์๊ธ์ ์๋จ์์ ๋ณผ ์ ์์ต๋๋ค.
- ๋ํ, ๊ณต๊ฐํ๋ ๊ฒ์๊ธ์ ์ข์์์ ๋๊ธ, ๋ต๊ธ, ๋๊ธ ์ข์์๋ฅผ ๋จ๊ธธ ์ ์๊ณ , ์ ๋ชฉ์ด๋ ๋ด์ฉ์ ๊ฒ์ํด์ ํด๋นํ๋ ๊ฒ์๊ธ์ ์ฐพ์ ์๋ ์์ต๋๋ค.
8-7. ์ชฝ์ง
- ์ค๊ณ ๋ง์ผ๊ณผ ์์ทจ์ ๋ฉ์ดํธ ์นดํ ๊ณ ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ์ค์ ๋ค๋ฅธ ์ฌ์ฉ์์ ์ชฝ์ง๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์๊ณ , ์๋๋ฐฉ์ด ๋ด ์ชฝ์ง๋ฅผ ์ฝ์๋์ง๋ ํ์ธ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ์นดํ ๊ณ ๋ฆฌ๋ณ๋ก ์ชฝ์ง๊ฐ ๋๋ ์ ธ์ ๋์ค๊ณ , ์ก์ ํจ๊ณผ ์์ ํจ ๊ฐ๊ฐ ํ์ธ์ด ๊ฐ๋ฅํฉ๋๋ค.
8-8. ์๋ฆผ
- ์ข์์์ ๋๊ธ, ์ชฝ์ง ๋ฑ์ ๋ฐ๊ฑฐ๋ ๋ด๊ฐ ์ฐํ ๋ฌผํ์ ๊ฐ๊ฒฉ์ด ๋ณ๋๋๋ ๋ฑ์ ์ํฉ์ผ ๋, ์๋ฆผ์ ํตํด ์ฌ์ฉ์์๊ฒ ์ ๋ณด๋ฅผ ์ ๋ฌํฉ๋๋ค.
- ์๋ฆผ์ ์ค๋๋ ์์ผ๋ก ๋ฐ์์๋ถํฐ ๋ฐฐ์ด๋๊ณ , ๊ฐ๋ณ ์ญ์ ์ ์ ์ฒด ์ญ์ ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
8-9. ํฌ์ธํธ
- ์ฌ์ฉ์๋ค์ ์ปค๋ฎค๋ํฐ ํ๋์ ๋๊ธฐ๋ถ์ฌ๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด ํฌ์ธํธ ์๋น์ค๋ฅผ ๋ง๋ค๊ฒ ๋์๊ณ , ๊ฒ์๊ธ์ ์์ฑํ๋ ๋ฑ์ ํ๋์ ํ ๊ฒฝ์ฐ์ ํฌ์ธํธ๋ฅผ ์ ๊ณตํ๋๋ก ๊ตฌ์ฑํ์ต๋๋ค.
- ํฌ์ธํธ ๋ณ๋ก ๋ฑ๊ธ์ ๋งค๊ฒจ ์ฌ์ดํธ ์ด์ฉ์ ์ฌ๋ฏธ๋ฅผ ๋ํ์ต๋๋ค.