๊ธฐ๊ฐ
- 2023.03~2023.07
ํ๋ก์ ํธ ์ธ์ ( ์ด 2๋ช )
- ํ๋ก ํธ์๋ : 1๋ช
- ๋ฐฑ์๋ : 1๋ช (์ ์์)
-
ํด๋น ๋ฌธ์๋ ๋ฐฑ์๋์ ๋ํด์๋ง ์์ ๋์์ต๋๋ค. *
-
ํด๋น ๋ฌธ์์ ์ฒจ๋ถ๋ ๋ชจ๋ ์ด๋ฏธ์ง๋ ์ง์ ์ ์ํ ์๋ฃ์ ๋๋ค. *
-
Notion ์ผ๋ก ๋ ํธํ๊ฒ ๋ณด๊ธฐ => https://uiseok.notion.site/OBO-d6313b5578404684892def01e41dbf45?pvs=4 *
OBO ํ๋ก์ ํธ : One by One ์ ์ฝ์๋ก, ํ๋ฃจํ๋ฃจ๋ฅผ ๊ณํ์ ์ผ๋ก ๋ณด๋ด๊ธฐ ์ํ ํ๋๋ ์น ์๋น์ค์ด๋ค.
- ๊ธฐ๋ฅ์ ๋ชฉํ
- ๋ง์ ํด๋ฆญ๊ณผ ์ ๋ ฅ์ด ํ์ํ๋ ๊ธฐ์กด์ ํ๋๋์ ๋ฌ๋ฆฌ, ๋จ์ํ ๋์์ผ๋ก ํ๋ฃจ ๊ณํ์ ์ฝ๊ฒ ์์ฑํ ์ ์๋ค.
- ํฐ ํ (์๊ฐ, ์ฃผ๊ฐ) ์์ ์์ฑํ ์ผ์ ์ ๋ชจ์, ์์ ํ (์ผ์ผ๊ณํ)์ ์์ธํ ์์ฑํ๋ ํํ๋ก ๊ตฌํํ๋ค.
- ๊ธฐ์ ์ ๋ชฉํ
- ํ๋ก์ ํธ์ ์ํคํ ์ณ๋ฅผ ์ ์ฉํด ๋ณธ๋ค.
- ํ๋ก์ ํธ์ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ํจ๋ฌ๋ค์์ ์ ์ฉํด ๋ณธ๋ค.
- ์๋น์ค์ ์ ํฉํ๊ณ ํจ์จ์ ์ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ค๊ณํ๋ค.
- ํ ์คํธ ์ผ์ด์ค๋ฅผ ํ์ฉํ์ฌ ๊ธฐ๋ฅ์ ๊ฒ์ฆํ๋ค.
- User
- ์ผ๋ฐ ํ์๊ฐ์ ๊ณผ ์์ ํ์๊ฐ์ ์ด ๊ฐ๋ฅํด์ผ ํ๋ค.
- Readme
- ๊ฐ์ธ์ ์๊ฐํ๋ ๊ธ์ ์์ฑํ ์ ์๋ค.
- ๊ฐ์ธ๊ณผ ์ฐ๊ด๋ ํด์ํ๊ทธ๋ฅผ ๋ฑ๋กํ ์ ์๋ค.
- Todo
- ๊ฐ์ธ์ ํ ์ผ์ ๋ ์ง๋ณ๋ก ์์ฑ ๋ฐ ์กฐํ๊ฐ ๊ฐ๋ฅํด์ผ ํ๋ค.
- ์๋ฃ์ฌ๋ถ๋ฅผ ํ์ํ ์ ์๋ค.
- Routine
- ๋ฐ๋ณต์ ์ธ ์ผ๊ณผ๋ฅผ ๋ฑ๋กํ์ฌ ์๋์ผ๋ก ์ผ์ผ๊ณํํ์ ํ์๋์ด์ผ ํ๋ค.
- ๋ ์ง ๋ณ๋ก ์๋ฃ์ฌ๋ถ๋ฅผ ํ์ํ ์ ์๋ค.
- ๋ฃจํด์ด ์ญ์ ๋์ด๋, ์ด์ ๋ ์ง์ ๊ธฐ๋ก์ ์ง์์ง์ง ์์์ผ ํ๋ค.
- Circle
- ๊ณตํต์ ์ธ ์ ๋ฌด๋ฅผ ํ๋ ์ ์ ๋ค๋ผ๋ฆฌ ๋ชจ์ฌ ๊ทธ๋ฃน์ ์์ฑํ๋ค.
- ๋ฉค๋ฒ์ ๋ฑ๊ธ์ ๋ถ์ฌํ์ฌ, ์ ๋ฌด์ ์์ฑ๊ณผ ์ญ์ ๊ถํ์ ํ ๋นํ ์ ์๋ค.
- Work
- ์ํด์ ๊ตฌ์ฑํ ์ธ์๋ค๋ผ๋ฆฌ ์ ๋ฌด์ ์ผ์ ๊ณผ ๋ด์ฉ์ ๊ณต์ ํ ์ ์๋ค.
- ๋ฉค๋ฒ ๋ณ๋ก ์๋ฃ ์ฌ๋ถ๋ฅผ ํ์ํ ์ ์๋ค.
- ์๋ฃํ ๋ฉค๋ฒ๋ฅผ ์นด์ดํธํ์ฌ ์ง์ฒ๋๋ฅผ ํ์ํ ์ ์๋ค.
- ์ํคํ
์ณ์ ๋ถ์ฌ
- ์ํคํ ์ณ์ ๋ถ์ฌ๋ก ์ธํ์ฌ, ์์ ์ ์ฝ๋๊ฐ ์ด๋๋ฒ์๊น์ง ์ํฅ์ ๋ฏธ์น๋์ง ์ง์ ๋์ ๋ฒ์๋ฅผ ํ์ธํด์ผ ํจ
- ์ฝ๋์ ๋ช ํํ ์ญํ ๋ถ๋ฆฌ์ ์ ์ง๋ณด์์ฑ์ ํฅ์์ ์ํ์ฌ ์ํคํ ์ณ์ ์ ์ฉ์ด ํ์
- ํจ๋ฌ๋ค์์ ๋ถ์ฌ
- ์ฝ๋๊ฐ ์ ์ธํ์ผ๋ก ์์ฑ๋ ๊ฒฝ์ฐ, ๋ค์ํ ๋ณ์๊ฐ ์ด๋์์ ์ฌ์ฉ๋๋์ง ํ์ธํด์ผ ํ๋ฏ๋ก ๊ฐ๋ ์ฑ์ด ๋จ์ด์ง
- ๋งค์๋๊ฐ ์ด๋ป๊ฒ ๊ธฐ๋ฅ์ ํ๋ ์ง ํ ๋์ ์์๋ณด๊ธฐ ์ด๋ ค์
- ๋ค์ํ ๋ฉ์๋๊ฐ ๋ถ์ํจ๊ณผ๋ฅผ ์ผ์ผํค๋ฏ๋ก, ์ด๋ฅผ ์ ์ดํ ์ ์๋ ํจ๋ฌ๋ค์์ด ํ์
- ์์ฐ์ค๋ฌ์ด ํ๋ฆ์ผ๋ก ์ดํด๊ฐ ๊ฐ๋ฅํ ์ฝ๋ํํ๋ก ๊ฐ์ ์ด ํ์
- ํจ์ํ ํ๋ก๊ทธ๋๋ฐ
- ์์ํจ์๋ค์ ์กฐํฉํ์ฌ, ๋ถ์ํจ๊ณผ๋ฅผ ์ ์ดํ๋ ํ๋ก๊ทธ๋๋ฐ ํํ
- ๋ฉ์๋์ ๋ํด ์ํฅ์ ๋ผ์น๋ ๋ฒ์๋ฅผ ํจ์๋ด๋ถ๋ก ์ ํํ๊ณ , ์ด๋ฐ ํํ์ ํจ์๋ค์ ์ฎ์ด ์ญํ ๊ณผ ๋จ๊ณ๊ฐ ๋ช ํํด ์ง๋๋ก ์ ์ฉํจ.
- Fxts
- ์ดํฐ๋ ์ดํฐ๋ฅผ ํ์ฉํ์ฌ ์ง์ฐํ๊ฐ๋ฅผ ๊ตฌํํ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ํจ๋ฌ๋ค์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฃผ์ ๋งค์๋์ ๊ตฌํ์ ์์ธํ ์ฐ๊ตฌํ์ฌ ํ์ฉ
ObO ํ๋ก์ ํธ์์๋ ์ํคํ ์ณ๋ก โํฌํธ ์ค ์ด๋ํฐโ ๋ผ๊ณ ๋ถ๋ฆฌ๋ โํฅ์ฌ๊ณ ๋ ์ํคํ ์ณโ๋ฅผ ์ ์ฉํ๋ค. ์ด ์ํคํ ์ณ๋ฅผ ์ ์ฉํ๊ธฐ ์ํด ๋ ์ด์ด๋ ์ํคํ ์ณ์ ๋น๊ตํ๊ณ , NestJS์์ ๊ตฌํํ ์ ์๋๋ก ์ธํฐํ์ด์ค๋ฅผ ํ์ฉํ๋ค.
-
๋ ์ด์ด๋ ์ํคํ ์ณ์ ํฅ์ฌ๊ณ ๋ ์ํคํ ์ณ์ ๋น๊ต

2-3-1) 3-layer ์ํคํ ์ณ
- ๋ ์ด์ด๋ ์ํคํ ์ณ๋ ๊ฐ ๊ณ์ธต์ด ์ญํ ๋ณ๋ก ๋ถ๋ฆฌ๋์ด ์์ง๋ง, ์์ ๊ณ์ธต์ด ํ์๊ณ์ธต์ ๋งค์ฐ ์์กด์ ์ด๋ค.
- nestJS์์๋ ์์กด์ฑ ์ฃผ์ ์ ํตํด ๋ ์ด์ด๋ ์ํคํ ์ณ๋ฅผ ๊ตฌํํ๋๋ฐ, ์ด๋ ํด๋์ค๋ค์ ๊ฒฐ์์ ๊ฐํ๊ฒ ๋ง๋ ๋ค.
2-3-2) ํฅ์ฌ๊ณ ๋ ์ํคํ ์ณ
- ํฅ์ฌ๊ณ ๋ ์ํคํ ์ณ๋ ๋ ์ด์ด๋ ์ํคํ ์ณ์ ์๋น์ค ๋ถ๋ถ์ ํด๋นํ๋ ๋น์ฆ๋์ค ๋ก์ง์ ์ค์ฌ์ผ๋ก ์ค๊ณํ๋ค.
- ๋น์ฆ๋์ค ๋ก์ง์ ํฌํธ๋ฅผ ํตํด ์ธ๋ถ์ ์ฐ๊ฒฐ๋๋ค. ๋ฐ๋ผ์ ๊ฐ ๊ณ์ธต์ ๊ฒฐํฉ๋์ด ์์ง ์๊ธฐ์, ๋ค์ํ ํํ์ ์ธ๋ถ์์์ ์ฐ๊ฒฐ์ด ๊ฐ๋ฅํ๋ค.
- ๋น์ฆ๋์ค๋ก์ง์ด ์ธ๋ถ๊ณ์ธต๊ณผ ๋ถ๋ฆฌ๋์ด์๊ณ , ์์กด์ ์ด์ง ์๊ธฐ ๋๋ฌธ์ ์ ์ฐํ ์ค๊ณ์ ์ ์ง๋ณด์๊ฐ ์ฉ์ดํ๋ค.
-
๊ตฌํ ๋ฐฉ๋ฒ
2-3-3) ์ธํฐํ์ด์ค๋ก ๊ตฌํํ ํฅ์ฌ๊ณ ๋ ์ํคํ ์ณ
- ํฅ์ฌ๊ณ ๋ ์ํคํ ์ณ๋ฅผ nestJS๋ฅผ ํตํด ๊ตฌํํ๋ ค๋ฉด ์์ ๊ฐ์ ํํ๋ก ๊ตฌ์ฑํ ์ ์๊ฒ ๋ค.
- ๊ฐ ๊ณ์ธต์ ํฌํธ์ ํด๋นํ๋ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์ฌ ์์๊ณ์ธต์ ์ฃผ์ ๋๋ค.
- ๋ฐ๋ผ์ ๊ฐ ๊ณ์ธต์ ํ ๊ณ์ธต์ ์ ๊ฒฝ์ธ ํ์ ์์ด, ์์ ์๊ฒ ์ฐ๊ฒฐ๋ ์ธํฐํ์ด์ค๋ง ์ ๊ฒฝ์ฐ๋ฉด ๋๋ค.
- ์ถํ ๋น์ฆ๋์ค๋ก์ง์ ์ฐ๊ฒฐํด์ผํ DB๊ฐ ๋ณ๊ฒฝ๋๋ค๋ฉด, ๋น์ฆ๋์ค ๋ก์ง์ ๊ทธ๋๋ก ๋๊ณ ์ธํฐํ์ด์ค๋ฅผ ์ถฉ์กฑ์ํค๋ DB๋ฅผ ์ฐ๊ฒฐํ๋ฉด ๋๋ ๊ฒ์ด๋ค. ๋ฐ๋ผ์ ๋น์ฆ๋์ค ๋ก์ง์ ๋์ฑ ์ง์คํ ์ ์๋ค.
-
ERD ๋ค์ด์ด๊ทธ๋จ
3-1-1) ERD ๋ค์ด์ด๊ทธ๋จ. ์ฝ 20์ฌ๊ฐ์ ํ ์ด๋ธ์ด ์ฐ๊ฒฐ๋์ด ์๋ค. (ํด๋ฆญ ์ ํฌ๊ฒ๋ณผ ์ ์์ต๋๋ค.)
- ์ ๊ทํ
- ๊ฐ๊ฐ์ ํ
์ด๋ธ์ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ์๋ค.
-
๊ธฐ๋ฅ๋ณ๋ก ๊ธฐ์คํ ์ด๋ธ์ ์์ฑ. ( Users, Todos, Routines, Circles, Worksโฆ)
-
๊ธฐ์คํ ์ด๋ธ์์ ํ์ํ ๋ชจ๋ ์ปฌ๋ผ์ ๋์ด
์) Routine :
id, user, name, updated_at, description, startTime, endTime, is_active, date.. -
ํค๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ฐ ํํ์ด ๊ณ ์ ํ ์ ์๋๋ก ์ปฌ๋ผ๋ค์ ๋ถ๋ฆฌ (์ ๊ทํ)
์) Routine :
- routine (๋ฃจํด ์์ฒด์ ์ ๋ณด) : id, user, name, description
- routine_histories (์ ์ ๊ฐ ๋ฃจํด์ ์์ ํ ๊ธฐ๋ก) : routine, updated_at, start_Time, end_Time, is_active
- routine_records (์ ์ ๊ฐ ๋ฃจํด์ ์ดํํ๋์ง ๊ธฐ๋ก) : routine, date
-
์ธ๋ฑ์ฑ์ ๊ณ ๋ คํ์ฌ ํ์ํ ์ปฌ๋ผ์ ํ ์ด๋ธ์ ์ถ๊ฐ
์) routine_histories๋ routine๊ณผ joinํ๋ฉด user๋ฅผ ์ ์ ์์ง๋ง, user ๋ณ๋ก ์ ๋ ฌ๋์ด ์์ง ์์ผ๋ฉด ์ ์ ๋ก ์ฐ๊ด๋ ๋ฐ์ดํฐ๋ค์ ์ฐพ๋๋ฐ ์ค๋๊ฑธ๋ฆฐ๋ค. ๋ฐ๋ผ์ routine_histories์ user๋ฅผ ์ถ๊ฐํ์ฌ ์ธ๋ฑ์ฑ์ ํ๊ณ , ์ฟผ๋ฆฌ ์์์๊ฐ์ ์ค์ด๊ณ ์ ํ๋ค.
-
- ๊ฐ๊ฐ์ ํ
์ด๋ธ์ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ์๋ค.
-
PostgreSQL
- DBMS ์ ๊ฒฝํ์ด ํ๋ถํ์ง ์์ ์ํ์์ ์ ํฉํ RDMS ๋ฅผ ๊ณ ๋ฅด๋ ๊ฒ์ ๋งค์ฐ ์ด๋ ค์ด ์ผ์ด๋ค. ๋ฐ๋ผ์, ๊ธฐ์กด์ ์ฌ์ฉํด ๋ณธ MongoDB์ ๊ฐ์ด JSON ๋ฐ์ดํฐ๋ฅผ ์์ ํ ์ง์ํ๊ณ , ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํํ๋ฅผ ์ง๋ ๋ฌด๋ฃ์ PostgreSQL์ ์ ์ ํ๊ฒ ๋์๋ค. ( ๊ทธ๋ฌ๋ ์ค์ ๋ก JSON ๋ฐ์ดํฐ๋ฅผ ์ง์ DBMS์ ๋ฃ์ด ์ฌ์ฉํ๋ ์ผ์ ์์์ผ๋ฉฐ, ORM ์ ํตํด ๊ด๋ฆฌํ์๋ค. ) ๋ํ PostgreSQL์ ์คํ์์ค DBMS์ด๋ฏ๋ก, ์ปค๋ฎค๋ํฐ์ ๋์๊ณผ ์ด์์ ๋น ๋ฅธํด๊ฒฐ์ ๊ธฐ๋ํ ์ ์๋ค๋ ๋ถ๋ถ์ ์ฐธ๊ณ ํ๋ค.
-
MikroORM
- NestJS ์๋ฒ ๋ด์์ ์ ์ฌ์ฉํ๊ธฐ ์ํด, ๊ณต์๋ฌธ์ ์์ ์๋ ORM ์ค ํ๋๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ํ๋ค.
- TypeORM์ ์ธ๊ธฐ์๋ ORM์ผ๋ก ๊ด๋ จ์๋ฃ๋ ๋ง์ง๋ง, ๋ฉ์ด์ ๋ฒ์ ์ด ๋์ค์ง ์์ ์ํ๋ก ๋ฉ์ธํ ์ด๋๊ฐ ๋ ์ด์ ์๊ฐ์ ํฌ์ํ์ง ๋ชปํ๋ ์ด์๊ฐ ์ฌ๋ผ ์จ ์ ์ด ์์๋ค. Prisma๋ ์ผ๋ถ ์ ํ๋ ๊ธฐ๋ฅ์ ๋ํด ๋ฌด๋ฃ๋ก ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค. ๋ฐ๋ผ์ ๊ณต์๋ฌธ์๊ฐ ๋ณด๊ธฐ ์ฝ๊ณ , ๋ฌด๋ฃ๋ฒ์ ์ธ MikroORM์ ์ฑํํ๋ค.
- ๊ฐ๋ฐ ๊ณผ์ ์์ MikroORM์์๋ Entity Manager๋ผ๋ ๊ธฐ๋ฅ์ ์ง์ํ๋๋ฐ, ์ด๋ ์ํฐํฐ๋ณ Repository๋ฅผ ์์ฑํ์ฌ ์ธ์คํด์คํ ํ์ง ์์๋, Entity Manager ์ธ์คํด์ค ํ๋๋ง์ผ๋ก ๋ชจ๋ ์ํฐํฐ๋ฅผ ์ ์ฒด์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์๋ค. ๋ํ NestJS์์์ ์ฑ๊ธํค ํจํด์ ์ง์ํ๋ค. (์๋ก ๋ค๋ฅธ ํด๋์ค์ ์ฃผ์ ๋ em ์ธ์คํด์ค์ ์ฃผ์๊ฐ ์ผ์นํจ์ ํ์ธ)
-
Entity Generator
-
PostgreSQL ์์ ์ง์ํ๋ ๊ด๋ฆฌํ๋ก๊ทธ๋จ์ธ PgAdmin4 ๋ฅผ ์ฌ์ฉํ์ฌ ํ ์ด๋ธ(์ํฐํฐ)๋ฅผ ์ ์ํ๋ค.
-
์ด์ด์ MikroORM์์ ์ง์ํ๋ generate ๊ธฐ๋ฅ์ ํ์ฉํ์ฌ ์๋ฒ ๋ด ์ํฐํฐ๋ฅผ ์์ฑํ๋ค.
npx mikro-orm generate-entities --save --path=./src/database/entities
-
-
Guard์ ๊ตฌํ
-
NestJS ๊ณต์๋ฌธ์์์๋ ๊ฐ Strategy ๋ณ ๊ฐ๋๋ฅผ ์ฝ๊ฒ ์์ฑํ ์ ์๋๋ก ๋๋๋ค.
export class JwtAuthGuard extends AuthGuard('jwt') export class JwtAuthGuard extends AuthGuard(['strategy_1','...']) { ... }
-
๊ทธ๋ฌ๋ ์์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก๋ง ์ฌ์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ์ ์ด ์๋ค.
- ๋จ์ผ ์ ๋ต์ ์ฌ์ฉํ๋ Guard๋ฅผ ์์ฑํ ๊ฒฝ์ฐ, ๊ฐ ๊ฐ๋ ๋ฐ์ฝ๋ ์ดํฐ๊ฐ ๋ถ๋ api๋ฅผ ๊ฐ๊ฐ ๋ง๋ค์ด ์ฃผ์ด์ผํ๋ค.
- 1๋ฒ์ ๋จ์ ์ ๊ทน๋ณตํ๊ธฐ ์ํด ์ ๋ต๋ฐฐ์ด์ ํ์ฉํ ์ ์์ง๋ง, ๊ฐ ์์ ๋ก๊ทธ์ธ๋ง๋ค ์ง์ํ๋ ๊ธฐ๋ฅ์ด ์ด๋ป๊ฒ ๋ค๋ฅธ ์ง ์ ์ ์์ผ๋ฏ๋ก ์ถ๊ฐ์ ์ธ ์์ ์์๊ฐ ์์๋๋ค.
-
๋ฐ๋ผ์ ๋จ์ผ ๊ฐ๋๋ก ๊ตฌํํ๋, ๊ฐ๋๋ฅผ ๊ตฌํํ๋ CanActivate๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉ ํ์ฌ ๋์ ์ธ โ๋ค์ด๋๋ฏน ๊ฐ๋โ ๋ฅผ ๋ง๋ค์๋ค.
-
-
๋ค์ด๋๋ฏน ๊ฐ๋์ ํํ
-
๋ค์ด๋๋ฏน ๊ฐ๋์ ๊ตฌํ
@Injectable() export class DynamicAuthGuard implements CanActivate { constructor( private localAuthGuard: AuthLocalGuard, private googleAuthGuard: AuthGoogleGuard, ) {} canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const guard = this.getGuard(context); return guard ? guard.canActivate(context) : false; } private getGuard(context: ExecutionContext): CanActivate { const request = context.switchToHttp().getRequest(); switch (request.path.split('/').pop()) { case 'local': return this.localAuthGuard; case 'google': return this.googleAuthGuard; default: return null; } } }
-
๋ฆฌํ๋ ์ ํ ํฐ
- ๋ฆฌํ๋ ์ ํ ํฐ์ ๋ก๊ทธ์ธ์ ๋ฆฌํ๋ ์ ํ ํฐ์ ์์ฑํ์ฌ DB์ ์ ์ฅํ๊ณ , ๋ก๊ทธ์์ ์ ๋ฆฌํ๋ ์ ํ ํฐ์ DB์์ ์ญ์ ํ๋๋ก ๊ตฌํํ๋ค.
- ํ๋ก ํธ์๋ฒ์์ ์ก์ธ์ค ํ ํฐ์ด ๋ง๋ฃ๋์ด ๋ฆฌํ๋ ์ ํ ํฐ์ ๋ฐฑ์๋ ์๋ฒ์ ์ ์ํ์ ๊ฒฝ์ฐ ๋ฆฌํ๋ ์ ํ ํฐ์ ๋ฐ๊ณ ์๋ ํ๋ ค๋ api ์์ฒญ์ ๋ฐ๋ณตํ์ง ์๋๋ก, ํ ๋ฒ์ ์์ฒญ์ผ๋ก ๋ฆฌํ๋ ์ ํ ํฐ ๋ฐ๊ธ๊ณผ api ์๋ต์ ์ ์กํ ์ ์๋๋ก ๊ตฌํํ๋ค.
// AuthJwtGuard.ts export class AuthJwtGuard extends AuthGuard('jwt') implements CanActivate { constructor() { super(); } async canActivate(context: ExecutionContext): Promise<boolean> { const isValid = (await super.canActivate(context)) as boolean; const request = context.switchToHttp().getRequest(); const response = context.switchToHttp().getResponse(); const newAccessToken = request.user.newAccessToken; const userId: string = request.user.userId; if (newAccessToken) { response.setHeader('Authorization', `Bearer ${newAccessToken}`); } request.user = userId; return isValid; } }
// AuthJwtStrategy.ts async validate( payload: AuthJwtValidateInboundPortInputDto, ): Promise<AuthJwtValidateInboundPortOutputDto> { const { userId, tokenType, iat, exp } = payload; switch (tokenType) { case 'ACCESS': return { undefined, userId }; case 'REFRESH': const foundUserToken = await this.authFindRefreshTokenOutboundPort.execute({ userId: userId, }); if (!foundUserToken) { throw new HttpException( '๋ค์ ๋ก๊ทธ์ธํ์ฌ ์ฃผ์ญ์์ค.', HttpStatus.BAD_REQUEST, ); } const savedUser = foundUserToken.user.id; const savedToken = foundUserToken.token; const validateUser = userId == savedUser; const decodedToken = this.jwtService.verify(savedToken); const validateToken = iat == decodedToken.iat && exp == decodedToken.exp; if (validateUser && validateToken) { const newAccessToken = this.jwtService.sign( { userId: userId, tokenType: 'ACCESS' }, { expiresIn: '30m' }, ); return { newAccessToken, userId }; } else { throw new HttpException( '๋น์ ์์ ์ธ ์ ๊ทผ์ ๋๋ค.', HttpStatus.BAD_REQUEST, ); } } }
๊ทธ ์ค ์ ์ ์ ๋ณด, User, Readme, Todo๋ ๋จ์ CRUD ์ ๊ธฐ๋ฅ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฏ๋ก,
์ฌ๊ธฐ์๋ ์ด๋์ ๋ ์๋น์ค ๋ก์ง์ด ๊ตฌํ๋ Routine๊ณผ Circle์ ๋ํด ์ค๋ช
ํ๋ค.
-
๋ฃจํด์ ๊ธฐ๋ฅ
- ์ ์ ๊ฐ ๋งค์ผ ํน์ ์๊ฐ์ ๋ฐ๋ณตํ๋ ํ ์ผ์ ์์ฑํ๋ค.
- ์์ฑ๋ ๋ฃจํดํญ๋ชฉ์ ์ผ์ผํ์ด์ง์ ์๊ฐ๋์ ๋ง์ถฐ ์๋์ผ๋ก ํ๊ธฐ๋๋ค.
- ๋ ๋ง๋ค ๋ฃจํด์ ์ค์ ํ๋์ง ์ฌ๋ถ๋ฅผ ํ๊ธฐํ ์ ์๋ค.
- ๋ฃจํด์ ์ดํํด ์ค๋ค๊ฐ ์ด๋ ๋ ์๊ฐ๋๋ฅผ ์์ ํด๋, ์ด์ ๋ ์ ์ํ๊ธฐ๋ก ๋ฐ ์๊ฐ์ ๋ฐ๋์ง ์๋๋ค.
- ๋ฃจํด์ ์ดํํด ์ค๋ค๊ฐ ์ด๋ ๋ ์ญ์ ํด๋, ์ด์ ๋ ์ ์ํ๊ธฐ๋ก์ ์ญ์ ๋์ง ์๋๋ค.
-
๊ณ ๋ ค์ฌํญ
- ๋ฃจํด์ ๋งค์ผ ์์ฑํ๋ ์ผ๊ณผ ์ด์ง๋ง, ์๋์ ์ผ๋ก ์ผ์ผ๋ฐ์ดํฐ๋ฅผ ์์ฑํ์ฌ ์ํ์ฌ๋ถ๋ฅผ boolean์ผ๋ก ์ฒ๋ฆฌํ๋ฉด DB๊ฐ ๋งค์ฐ ๋ญ๋น๋ ๊ฒ์ด๋ค. ( ๋ฐฉ์นํ ๋ฃจํด๋ ๋งค์ผ ์๋ฏธ์๋ ๋ฐ์ดํฐ๋ฅผ ์์ฐํ๊ฒ ๋๋ฏ๋ก )
- ๋ฃจํด์ ์์ ํด๋ ์ด์ ๋ฐ์ดํฐ๊ฐ ๋ฐ๋๋ฉด ์ ๋๋ ๊ฒ์, ๊ทธ ๋์์ ์์ ๋ด์ญ์ ์ ์ฅํด์ผ ํจ์ ์๋ฏธํ๋ค.
- ํน์ ๋ ์ง์ ๋ฃจํด ์ดํ์ฌ๋ถ๋ฅผ ํ์ธํ๋ ค๋ฉด, ๊ฐ๊ฐ ์์ ์์ ์ด ๋ค๋ฅธ ๋ฃจํด์ ์ ๋ณด๋ฅผ ๋ฐ์์์ผ ํ๋ค. ( A๋ฃจํด์ 3์ 1์ผ, B ๋ฃจํด์ 5์ 14์ผ์ ์์ ํ ์ํ์์ 4์ 30์ผ์ ์ดํ์ฌ๋ถ๋ฅผ ํ์ธํ๋ ค๋ฉด, A๋ ์์ ๋ ํ์ ์ ๋ณด, B๋ ์์ ๋๊ธฐ ์ ์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์์ผ ํจ. )
-
์ํฐํฐ ์ค๊ณ
5-1-1) ๋ฃจํด์ ํ ์ด๋ธ
Routines: ๋ฃจํด์ ์ด๋ฆ๊ณผ ๋ด์ฉ์ด ๊ธฐ๋ก. ๋ฃจํด ์์ฒด์ด๋ฏ๋ก id๋ก ๊ตฌ๋ณ ๊ฐ๋ฅ.RoutineHistory: ์ด์ ์ ์์ ๋ด์ญ์ ์ ์ฅ. ์ธ์ ์์ ์ด ์ด๋ฃจ์ด ์ก๋์ง๋ก ๊ตฌ๋ถ.RoutineRecords: ์ผ์ผ ์ดํ์ฌ๋ถ๋ฅผ ๊ธฐ๋ก. ์ํ์ฌ๋ถ๋ ๋ฃจํด-๋ ์ง ์์ด ์กด์ฌํ๋ ์ง ์ฌ๋ถ๋ก ํ์ธ.
-
์ธ๋ฑ์ฑ
RoutineHistory: user ์ปฌ๋ผ์ ๋ํด b-tree๋ก ์์ฑ- ์ ์ ๊ฐ ์์ ์ ๋ฃจํด์ ๊ฒ์ํ ๋, ๋ฃจํด์ ๋ ์ง ๊ธฐ์ค์ผ๋ก ์ต์ ์ ๋ณด๋ฅผ ์ฐพ์์์ผ ํ๋ค. ๊ทธ๋ฐ๋ฐ ๋ฃจํด์ด id๋ก ์ ๋ ฌ๋์ด ์๋ค๋ฉด, ์์ ์ ๋ฃจํด์ ์ฐพ๊ธฐ์ํด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ชจ๋ ํํ์ ์ฐพ์๋ด์ผ ํ ๊ฒ์ด๋ค. ๋นํจ์จ์ ์ธ ๊ฒ์์ ๋ฐฉ์งํ๊ธฐ ์ํด, user ๋ณ๋ก ๊ด๋ฆฌํ ์ ์๋๋ก ์ธ๋ฑ์ค๋ฅผ ์ง์ ํ์๋ค.
Routine: ์ธ๋ฑ์ค ์ถ๊ฐ X- ๋ฃจํด์ ๊ฒ์์ ๋ ์ง๋ณ๋ก ๊ทธ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ค. ๋ฐ๋ผ์
RoutineHistory๋ก ๊ฒ์์ด ๋จผ์ ๋ ํ,RoutineHistory.routine์ธ๋ํค๋ฅผ ํตํด[Routine.id](http://Routine.id)๋ก ํ์ํ๊ฒ ๋๋ฏ๋ก, โuserโ ๋ฑ ๋ค๋ฅธ ์ปฌ๋ผ์ ์ธ๋ฑ์ค๊ฐ ํ์ํ์ง ์์๋ค.
- ๋ฃจํด์ ๊ฒ์์ ๋ ์ง๋ณ๋ก ๊ทธ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ค. ๋ฐ๋ผ์
-
๋ชจ๋ ๊ตฌ์กฐ ( ํฅ์ฌ๊ณ ๋ ์ํคํ ์ณ )
5-1-2) ํฅ์ฌ๊ณ ๋ ์ํคํ ์ณ๋ก ๊ณ์ธต ๊ฐ ์์กด์ฑ์ ๋ถ๋ฆฌํ Routine Module ์ ํํ
5-1-3) ๊ฐ ๊ณ์ธต์ด ์์๊ณ์ธต์ ์ฃผ์ ๋, ์์กด์ฑ์ด ๋งค์ฐ ๋์ ํํ์ ๊ตฌ์กฐ๊ฐ ์๋๋ค.
- ํฅ์ฌ๊ณ ๋ ์ํคํ ์ณ๋ฅผ ์ธํฐํ์ด์ค๋ฅผ ํ์ฉํ์ฌ Routine ๋ชจ๋์ ๊ทธ๋ฆผ 5-1-2 ๊ณผ ๊ฐ์ด ๊ตฌํํ๋ค.
- ์๋น์ค ๋ก์ง์์ ๋ณด๋ฉด, Repository์ ๋ํ ๋ด์ฉ์ ์ง์ ์ ์ผ๋ก ์ ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ์ ํ์๋ ์๋ค. ํ๋์์ผ๋ก ํ์ํ ์ธํฐํ์ด์ค์ธ outbound-port ๋ง ์๊ณ ์์ผ๋ฉฐ, ์ด๋ฐ ํํ์ ์ธ์คํด์ค๊ฐ โ์๋ค ์น๊ณ โ ๋์ํ๋ฏ๋ก, ์๋น์ค ๋ก์ง์ ํ์๊ณ์ธต์์ ๋ถ๋ฆฌํ์ฌ ์ถ์์ ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค.
-
๊ตฌํ ์ฝ๋
โ๏ธ routine.read-by-date.service.tsexport class RoutineReadByDateService implements RoutineReadByDateInboundPort { constructor( // ์๋น์ค๋ ์ธํฐํ์ด์ค๋ฅผ ์ฃผ์ ๋ฐ์ ์ถ์์ ์ผ๋ก ํ์ฉํ๋ค. // ์ค์ ๊ตฌํ์ฒด์ ์ฃผ์ ์ ์๋ฒ๊ฐ ์คํ ๋ ๋, ๋ชจ๋์ ์ํ์ฌ ํ ํฐ์ ํด๋นํ๋ ํด๋์ค์ ์ธ์คํด์ค๊ฐ ์ฃผ์ ๋๋ค. @Inject(ROUTINE_READ_BY_DATE_OUTBOUND_PORT) private readonly routineReadByDateOutboundPort: RoutineReadByDateOutboundPort, @Inject(ROUTINE_READ_RECORD_BY_DATE_OUTBOUND_PORT) private readonly routineReadRecordByDateOutboundPort: RoutineReadRecordByDateOutboundPort, ) {} async execute( params: RoutineReadByDateInboundPortInputDto, ): Promise<RoutineReadByDateInboundPortOutputDto> { // ํจ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ Fxts์ pipe, map, filter, flat, toArray ์ฌ์ฉ return await pipe( [params], toAsync, map((param) => { return this.routineReadByDateOutboundPort.readByDate(param); }), flat, filter((routine) => routine.is_active == true), map(async (userRoutine) => { return { routine: userRoutine.routine, name: userRoutine.name, startTime: userRoutine.start_time, endTime: userRoutine.end_time, description: userRoutine.description, done: await this.routineReadRecordByDateOutboundPort.execute({ routineId: userRoutine.routine, date: params.date, }), }; }), toArray, ); } }
์ ์ฝ๋๋ ํน์ ๋ ์ง์ ๋ฃจํด๊ณผ ์ดํ์ฌ๋ถ๋ฅผ ์ฐพ์ ๋ฐํํ๋ ์๋น์ค๋ก์ง
RoutineReadByDateService๋ค.- ํฅ์ฌ๊ณ ๋ ์ํคํ
์ณ์ ๊ตฌํ
- ์๋น์ค๋ก์ง์ ๋ค์ด์จ input์ธ
params์ ๋ํด, ์ฃผ์ด์ง ๋ ์ง๋ฅผ ๊ธฐ์ค์ ๋ง๋ ๋ฃจํด์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๊ณ , ํ์ฑํ ๋์ด ์๋ (์ญ์ ๋์ง ์์) ๋ฃจํด์ ๋ํด์๋ง ๊ฐ๊ฐ์ ์ ๋ณด์ ์ดํ์ฌ๋ถ๋ฅผ ์ฐพ์ ๋ฐฐ์ด์ ๋ด์ ๋ฐํํ๋ค. - ๊ทธ๋ฆผ 5-1-2 ์ ๋ณด๋ฉด,
RoutineReadByDateService์๋read-by-date์read-record-by-dateRepository๋ฅผ ์ฌ์ฉํ๊ณ ์์ง๋ง, ์ฝ๋์์๋ repository ๊ด๋ จ ์ด๋ค ์ฝ๋๋ ์ฐพ์๋ณผ ์ ์๋ค. ๋ชจ๋ ์ธํฐํ์ด์ค๋ก ์ถ์ํ ๋์ด ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ด๋ค.
- ์๋น์ค๋ก์ง์ ๋ค์ด์จ input์ธ
- ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ Fxts ์ฌ์ฉ
**pipe**: ์ฒซ ์ธ์์ ํจ์๋ค์ ๋ฐ์, ์ธ์๊ฐ ํจ์๋ฅผ ์ฐจ๋ก๋๋ก ํต๊ณผํ๋๋ก ๋ง๋ฌ. ๋ง์ฝ map, filter์ ๊ฐ์ด ์ดํฐ๋ ์ดํฐ๋ฅผ ํ์ฉํ๋ ๋งค์๋๊ฐ ํจ์๋ก ๋ค์ด์ฌ ๊ฒฝ์ฐ, ์ฒซ ์ธ์๋ ์ดํฐ๋ฌ๋ธํ ๊ฐ์ฒด๊ฐ ํ์ํ๋ฉฐ, ์ดํฐ๋ ์ดํฐ์ ๊ฐ์ ํ๋์ฉ ๋๊ธฐ๋ ๊ฒ์ด ์๋๋ผ ์ดํฐ๋ ์ดํฐ ์์ฒด๋ฅผ ํจ์๋ก ๋๊ธด๋ค.[params]: fxts๋ ์ง์ฐํ๊ฐ ๋ฉ์๋์ธ map, filter ๋ฑ์ ๊ตฌํ์ ์ดํฐ๋ ์ดํฐ๋ฅผ ํตํด ๊ตฌํํ๋ค. ๋ฐ๋ผ์ ๋ค์ด์จ input ๊ธธ์ด๊ฐ 1 ์์๋ ๋ถ๊ตฌํ๊ณ , map, filter ๋ฑ์ ์ฌ์ฉํ ์ ์๋๋ก ๋ฐฐ์ด๋ก ๋ค๋ฌ์๋ค.toAsync: map๊ณผ filter ๋ด๋ถ์ ๋น๋๊ธฐ ๋์์ด ๊ตฌํ ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ๋น๋๊ธฐ ๋์์ผ๋ก ๋ณํํ๋ค. (ํ๊ตฌ ๊ณผ์ : https://velog.io/@uiseok0514/Fxts-AsyncIterable)flat: ์ค์ฒฉ๋ ๋ฐฐ์ด์ ํ ์ฐจ์ ํ์ด ํํํ๊ฒ ๋ง๋ ๋ค. ์ฌ๊ธฐ์๋ DB๋ก ๋ถํฐ ์ฐพ์์จ ๋ฃจํด์ด ํ๋์ ๋ฐฐ์ด์ ๋ด๊ฒจ์์ด, ์ด๋ฅผ ์ดํฐ๋ ์ดํฐ๋ก ์ํํ๊ธฐ ์ํด ํํํ ํ๋ค.filter: ํน์ ์กฐ๊ฑด์ ๋ง์กฑํ๋ ์์๋ง ์ดํฐ๋ ์ดํฐ์ next()๋ก ๋ฐํํ๋ค. ์ฌ๊ธฐ์๋ ํ์ฑํ ๋ ๋ฃจํด๋ง ํํฐ๋งํ๋๋ก ์ค์ ํ๋ค. ( * ๋ฃจํด์ด ์์ /์ญ์ ๋์ด๋ ์ด์ ์ ๋ณด๋ ์ ์ง๋์ด์ผ ํ๊ธฐ ๋๋ฌธ์, ์ ์ ๊ฐ ๋ฃจํด์ ์ญ์ ํด๋ ์ค์ ๋ก๋ ์ญ์ ๋์ง ์๊ณ ๋นํ์ฑํ๋์ด ๋ณด์ด์ง ์๋๋ค.)map: ์ดํฐ๋ ์ดํฐ์ ๊ฐ ์์๋ง๋ค ์ ํด์ง ํจ์๋ฅผ ์คํํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฐ์ด๋ก ๋ฐํํ๋ค. ์ฌ๊ธฐ์๋ ๋ฃจํด์ ์ํ์ฌ๋ถ๋ฅผ ํ์ธํ๊ณ , service ์ outputDTO ์ ๋ง์ถ๋ ์์ ์ ํ๋ค.toArray: pipe๋ฅผ ํต๊ณผํ ์ดํฐ๋ ์ดํฐ๋ก ๋ถํฐ ์ต์ข ๊ฒฐ๊ณผ๊ฐ์ ๋ฐ์๋ด๋ ์์ ์ด ํ์ํ๋ค. toArray๋ pipe๋ฅผ ํต๊ณผํ ๊ฒฐ๊ณผ๊ฐ์ ๋ชจ์ ๋ฐฐ์ด์ ๋ด์ ๋ฐํํ๋ค.
- ํฅ์ฌ๊ณ ๋ ์ํคํ
์ณ์ ๊ตฌํ
-
์ํด & ์ํฌ์ ๊ธฐ๋ฅ
- ์ํด(Circle) ์ ๊ธฐ๋ฅ
- ๋์ผํ ๊ด์ฌ์ฌ ๋๋ ๊ฐ์ ์ผ์ ์ ๊ณต์ ํด์ผํ๋ ์ ์ ๋ค ๋ผ๋ฆฌ ๊ทธ๋ฃน์ ๋ง๋ค ์ ์๋ค.
- ๊ฐ ์ํด์๋ ์ํด์ฅ(owner)๊ณผ ์คํํ(staff) ๋ฉค๋ฒ(member)๊ฐ ์กด์ฌํ๋ค.
- ์ํด์ ๊ฐ์ ์ owner ๋๋ staff๊ฐ ์น์ธํด์ผ ์ด๋ฃจ์ด์ง๋ค.
- ์ํฌ(Work) ์ ๊ธฐ๋ฅ
- ์ํด์ Todo ๊ธฐ๋ฅ์ธ ์ํฌ(work)๋ owner ๋๋ staff ๋ฑ๊ธ์ ์ ์ ๊ฐ ์์ฑํ ์ ์๋ค.
- ์ํฌ๋ ์ํด์ ํฌํจ๋ ๋ชจ๋ ์ ์ ๊ฐ ๋ณผ ์ ์๋ค.
- ์ํฌ์ ์์ ์ ๋ณธ์ธ๋ง ๊ฐ๋ฅํ๋ฉฐ, ์ญ์ ๋ ๋ณธ์ธ ๋๋ owner๊ฐ ๊ฐ๋ฅํ๋ค.
- ์ํด์ ์ ์ ๋ ์ด๋ค work ์ ๋ํด ์์ ์ ์ผ์ผ ํ๋ ํ๋ฉด์์ ์๋ฃ์ฌ๋ถ๋ฅผ ํ์ํ ์ ์๋ค.
- ์ํฌ ์กฐํ ์, ํด๋น ์ํฌ๋ฅผ ์๋ฃํ ์ ์ ๋น์จ์ ํ์ธํ ์ ์๋ค.
- ์ํด(Circle) ์ ๊ธฐ๋ฅ
-
๊ณ ๋ ค์ฌํญ
-
์ธ๋ถ์ ์ธ ๊ธฐ๋ฅ์ด ๋ง์ด ํ์ํ๋ฏ๋ก, ์ด๋ค ๋งฅ๋ฝ์ผ๋ก ๊ธฐ๋ฅ๋ค์ ๊ตฌ๋ถํ ์ง ๊ณ ๋ คํด์ผํ๋ค.
โ ์ํด ๊ด๋ฆฌ / ๋ฉค๋ฒ ๊ด๋ฆฌ / ์ํฌ ์ด 3๊ฐ์ง ๋ชจ๋๋ก ๋ถ๋ฆฌ
-
์ํด๋ณ / ๋ฑ๊ธ๋ณ ๋ก ๊ฐ๋ฅํ ๋์์ด ๊ตฌ๋ถ๋์ด ์์ผ๋ฏ๋ก, ๊ถํ์ ์ค์ํ ์ ์๋ ์ฅ์น๊ฐ ํ์
( ์ํด์ ๊ฐ์ ํ์ง ์์๋๋ฐ, ์ํฌ๋ฅผ ์์ฑํ๊ฑฐ๋, member ๋ฑ๊ธ์ธ๋ฐ owner๋ฅผ ๊ฐํด์๊ธฐ๊ธฐ ๋ฑ )
-
-
ํ ์ด๋ธ ์ค๊ณ
5-2-1) ์ํด๊ณผ ์ํฌ์ ํ ์ด๋ธ
circle: ์ํด์ ๋ํ ์ ๋ณด. โis_Openโ ์ผ๋ก ์ํด๊ฐ์ ์ ์ด๊ณ ๋ซ์ ์ ์์ผ๋ฉฐ, โmembersโ ๋ ๋ฉค๋ฒ ์ด ์ธ์ ์๋ฅผ ์ ์ฅcircle_application: ์ํด ๊ฐ์ ์ ์ฒญ ๋ฐ์ดํฐ. ์ ์ -์ํด ์์ ์์ฑํ์ฌ ์ ์ฒญ์ฌ๋ถ๋ฅผ ํ์ธ ํจ.circle_grades: ์ํด ๋ฉค๋ฒ์ ๋ํ ๋ฑ๊ธ. 1-owner ~ 3-member ๊น์ง๋ก ์ค์ user_circles: ์ ์ ๊ฐ ๊ฐ์ ํ ์ํด ๋ฐ์ดํฐ. ์ ์ -์ํด ์์ ์์ฑํ์ฌ ๊ฐ์ ์ฌ๋ถ๋ฅผ ํ์ธ ํจ.work: ์ํด์ todo ์ธ ์ํฌ์ ๋ํ ์ ๋ณด.work_records: ์ด๋ค ์ํฌ์ ๋ํด ๋๊ฐ ์๋ฃํ๋์ง๋ฅผ ๊ธฐ๋กํ๋ ๋ฐ์ดํฐ. ์ปฌ๋ผ์ circle์ ์ถ๊ฐํ ์ด์ ๋ ์ธ๋ฑ์ฑ์ ์ํด์ ์ถ๊ฐํจ.work_priority: ์ํฌ์ ๋ํ ์ค์๋ ๋ฐ์ดํฐ. 1~5 ๋ฑ๊ธ์ผ๋ก ๊ตฌ๋ถ
-
์ธ๋ฑ์ฑ
circle: name ์ผ๋ก ์ธ๋ฑ์ค ์ถ๊ฐ- ์ํด ๊ฒ์ ์, ์๊ฐ์ ์ต์ํ ํ๊ธฐ ์ํ ์กฐ์น
user_circles: circle๋ก ์ธ๋ฑ์ค ์ถ๊ฐ- ๊ธฐ๋ณธ์ ์ผ๋ก user์ ๋ํด ์ธ๋ฑ์ค๊ฐ ์์ฑ๋์ด ์ ์ ๊ฐ ์์ ์ด ๊ฐ์ ํ ์ํด์ ๊ฒ์ํ ๋ ๋น ๋ฅธ ์๋๋ก ์ฐพ์ ์ ์๋ค. ๊ทธ๋ฌ๋ ์ํด์ ๊ฐ์ ํ ๋ฉค๋ฒ๋ค์ ์กฐํํ ๋ ํ ์ด๋ธ์ ๋ชจ๋ ํ์ํด์ผ ํ๋ฏ๋ก, ์ํด ๋ณ๋ก ์ธ๋ฑ์ฑ์ ์ถ๊ฐํ ํ์๊ฐ ์๋ค.
work: circle๋ก ์ธ๋ฑ์ค ์ถ๊ฐ- ์ํฌ๋ ์ํด๋ณ๋ก ์กฐํํ๋ฏ๋ก, id ๊ธฐ๋ณธ ์ธ๋ฑ์ค ์ด์ธ์ circle๋ก ์ถ๊ฐ ์ธ๋ฑ์ค๊ฐ ํ์ํ๋ค.
work_records: ์ธ๋ฑ์ค ์ถ๊ฐ X- ์ผ์ผํ๋๋์์ ์ํฌ๋ ์ ์ -๊ฐ์ ๋์ํด-์ํฌ ์์ผ๋ก ๊ฒ์๋๋ฏ๋ก, work id ๋ก ์ถฉ๋ถํ ๊ฒ์์ด ๊ฐ๋ฅํ์ฌ ๋ณ๋ ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํ์ง ์์๋ค.
-
๋ชจ๋ ๊ตฌ์กฐ ( ํฅ์ฌ๊ณ ๋ ์ํคํ ์ณ )
- ํํ๋ 5-1-2 ๋ฃจํด๊ธฐ๋ฅ์ ์ํคํ ์ณ ๊ตฌ์กฐ์ ๋์ผํ๋ค. ์ฌ๊ธฐ์๋ ์ค์ ์ฝ๋ ํ์ผ์ด ์ด๋ป๊ฒ ๊ตฌ์ฑ๋์ด ์๊ณ , ๋ชจ๋์์ ๊ฐ ์ธํฐํ์ด์ค๋ฅผ ์ด๋ป๊ฒ ์ ์ดํ๊ณ ์๋ ์ง ์ค๋ช ํ๋ค.
5-2-2) circle ์ management ๋ชจ๋.
5-2-3) circle.management.module ์ ์ค์ .
-
๊ฐ ๊ณ์ธต์ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ์ ํํ๋ก ๊ตฌ์ฑ๋์ด ์๋ค
module-controller-inboundPort-service-outboundPort-repository -
๊ฐ ๊ณ์ธต์์๋ ์ธ๋ถ๋ก๋ถํฐ ์ฃผ์ ๋๋ ์ธ์คํด์ค๋ฅผ ์ธํฐํ์ด์ค๋ฅผ ํตํด ์ถ์์ ์ผ๋ก ๋ค๋ฃจ๊ธฐ ๋๋ฌธ์, ๋ฐํ์ ์ ์ค์ ๋ก ์ด๋ค ํด๋์ค๋ฅผ ์ฌ์ฉํ ์ง ์ง์ ํด ์ฃผ์ด์ผ ํ๋ค.
-
๊ฐ ๊ณ์ธต์๋ ์ธํฐํ์ด์ค์ ํ ํฐ์ด ์ฃผ์ ๋์ด ์์ผ๋ฉฐ, ํ ํฐ์ด ๋ฌด์์ ์ง์นญํ๋์ง๋ module์์ 5-2-3 ์ฌ์ง์ฒ๋ผ ์ค์ ํ๋ค.
{ provide: CIRCLE_MANAGEMENT_CREATE_INBOUND_PORT, // ์ด ํ ํฐ์ด ์ฃผ์ ๋ ๊ฒ์ useClass: CircleManagementCreateService, // ์ค์ ๋ก๋ ์ด ํด๋์ค๋ฅผ ์ฌ์ฉํฉ๋๋ค. }
5-2-4) ๊ฐ ๊ณ์ธต์๋ ํ ํฐ์ด ์ฃผ์ ๋๊ณ , ์ฃผ์ ๋๋ ์ธ์คํด์ค๋ฅผ ์ธํฐํ์ด์ค๋ฅผ ํตํด ์ถ์์ ์ผ๋ก ๋ค๋ฃฌ๋ค.
-
์ด๋ ๊ฐ ๊ณ์ธต์ด ์ธํฐํ์ด์ค๋ก ํ ๊ณ์ธต์ ์ถ์์ ์ผ๋ก ๋ค๋ฃจ๊ณ ์๊ธฐ ๋๋ฌธ์ ๊ฐ๋ฅํ๋ค. ๋ํ ๊ณ์ธต์ด ๋ณ๋ ๋๋๋ผ๋, ์ธํฐํ์ด์ค๋ฅผ ๋ง์กฑํ๋ ๋ค๋ฅธ ๊ณ์ธต์ ๋ชจ๋์์ ํ ํฐ์ผ๋ก ์ฐ๊ฒฐํ๋ฉด ๋๋ฏ๋ก ๊ด๋ฆฌ๊ฐ ์ฉ์ดํ๋ค.
- ์ํคํ
์ณ๊ฐ ์ฃผ๋ ํ
์คํธ์ผ์ด์ค์ ์ด์
- ํฅ์ฌ๊ณ ๋ ์ํคํ ์ณ์ ์ ์ฉ์, ์ธํฐํ์ด์ค๋ฅผ ํตํด ๊ฐ ๊ณ์ธต์ ์์กด์ฑ์ ๋ถ๋ฆฌ ํ ์ ์์๋ค.
- ํ ์คํธ ๊ด์ ์์๋ ๊ฐ ๊ณ์ธต์ ์์กด์ฑ ๋ถ๋ฆฌ๋ ํด๋น๊ณ์ธต์ ํ ์คํธ์ ์ง์คํ ์ ์๋ ํ๊ฒฝ์ ์ ๊ณตํ๋ค.
- ๋ชจํนํ๋ ์ธ์คํด์ค๊ฐ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ฉฐ, ์ค์ ์ ํด๋์ค ํํ๋ฅผ ์ค์ํ๋ค.
-
๋ฆฌํฌ์งํ ๋ฆฌ ํ ์คํธ
-
๋ ํฌ์งํ ๋ฆฌ ํ ์คํธ์ ์ค์ : ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํต์ ํ๊ณ ์ ์ ํ ๋ฐ์ดํฐ์ ์ ์ถ๋ ฅ์ด ์ด๋ฃจ์ด์ง๋๊ฐ?
-
๋น์ด์๋ ํ ์คํธ์ฉ ๋ฐ์ดํฐ๋ฒ ์ด์ค์, ์ํฐํฐ ๋งค๋์ ๊ฐ create, upsert ๋ฑ์ ์ ๋ฐ์ํ๋์ง ์ฒดํฌํ๋ค.
-
ํ ์คํธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ๊ฒฝ ๊ตฌ์ถ ( ํ ์ด๋ธ์ ์ค๋ฆฌ์ง๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋์ผํ๊ฒ ๊ตฌ์ถํ๊ธฐ )
const orm = await MikroORM.init(testConfig); // ํ ์คํธ์ฉ DB์ฃผ์๊ฐ ์ ๋ ฅ๋ ์ค์ ์ฌ์ฉ const generator = orm.getSchemaGenerator(); // ์คํค๋ง ์์ฑ๊ธฐ๋ฅผ ๊ฐ์ ธ์ ์คํ await generator.updateSchema(); -
์๋๋ ์ ์ ๋ฅผ ์์ฑํ๋
UserCreateRepositoryํด๋์ค์ ๋ํ ์ฝ๋์ ํ ์คํธ ์ผ์ด์ค์ด๋ค.โ๏ธ user.create.repository.tsexport class UserCreateRepository implements UserCreateOutboundPort { constructor(private readonly em: EntityManager) {} async execute( params: UserCreateOutboundPortInputDto, ): Promise<UserCreateOutboundPortOutputDto> { const newUser = this.em.create(Users, params); await this.em.persistAndFlush(newUser); return newUser; } }
โ๏ธ user.create.repository.spec.tsdescribe('UserCreateRepository Spec', () => { let userCreateRepository: UserCreateRepository; let em: EntityManager; let orm: MikroORM; beforeAll(async () => { orm = await MikroORM.init({ ...testConfig, driver: PostgreSqlDriver }); em = orm.em; userCreateRepository = new UserCreateRepository(em); }); ... test('ํ์๊ฐ์ (๋ก์ปฌ) : ๋ก์ปฌ์ ์ ๋ฅผ ์์ฑํฉ๋๋ค.', async () => { const params = { id: '123', // ์์์ง์ (์๋๋ ์ํ์ค ์ฌ์ฉ) email: 'createLocalTester@obo.com', nickname: 'oboNewbie', password: '1q2w3e4r', authMethod: 'LOCAL', }; const result: UserCreateOutboundPortOutputDto = await userCreateRepository.execute(params); expect(result).toEqual({ userId: '123', email: 'createLocalTester@obo.com', nickname: 'oboNewbie', }); }); ... }
-
-
์๋น์ค ํ ์คํธ
- ์๋น์ค ํ ์คํธ๋ ์ค์ ๋ ํฌ์งํ ๋ฆฌ๊ฐ ์๋ Mock ๊ฐ์ฒด๊ฐ ์ฃผ์ ๋๋ค.
- mock์ ์๋น์ค๊ฐ ์ฌ์ฉํ๋ outboundPort ์ธํฐํ์ด์ค๋ฅผ ์ค์ํ๋ค.
- ๋ฐ๋ผ์ ์๋น์ค๋ ์ ํด์ง mock์ ๋ฐ๊ธฐ ์ํ ์ฌ๋ฐ๋ฅธ ์ ๋ ฅ๊ณผ, ์ ์ ํ ์ต์ข ๊ฐ์ด ์ถ๋ ฅ ๋๋์ง ํ ์คํธ ํ๋ค.
โ๏ธ user.sign-up.service.tsexport class UserSignUpService implements UserSignUpInboundPort { constructor( @Inject(USER_READ_OUTBOUND_PORT) private readonly userReadOutboundPort: UserReadOutboundPort, @Inject(USER_CREATE_OUTBOUND_PORT) private readonly userCreateOutboundPort: UserCreateOutboundPort, ) {} async execute( params: UserSignUpInboundPortInputDto, ): Promise<UserSignUpInboundPortOutputDto> { // ์ด๋ฉ์ผ์ด ์กด์ฌํ๋๊ฐ // ์ ํจํ ๋น๋ฐ๋ฒํธ์ธ๊ฐ // ๋น๋ฐ๋ฒํธ๋ฅผ ํด์ฌํ // ์ ์ ์์ฑํ๊ธฐ // ์ ์ ๋ฅผ ์ฐพ์ DB์์ ๋ฐํ ํ๊ธฐ return await pipe( [params], toAsync, filter(async (params) => { if (!(await this.userReadOutboundPort.execute({email: params.email}))) { return true; } else { throw new HttpException( '์ด๋ฏธ ๊ฐ์ ๋ ์ด๋ฉ์ผ์ ๋๋ค.', HttpStatus.BAD_REQUEST, ); } }), filter((params) => { if (params.password) { return true } else { throw new HttpException( '๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ์ธ์.', HttpStatus.BAD_REQUEST, ); } }), map(async (params) => { return { ...params, password: await bcrypt.hash(params.password, 10), }; }), map((params) => this.userCreateOutboundPort.execute(params)), map((user) => { return { userId: user.id, email: user.email, nickname: user.nickname, }; }), head, ); } }
โ๏ธ user.sign-up.service.spec.tsdescribe('UserSignUpService Spec', () => { let userSignUpService: UserSignUpService; //-- Mocking -- class MockUserReadOutboundPort implements UserReadOutboundPort { constructor(private readonly params: UserReadOutboundPortOutputDto) {} async execute( _: UserReadOutboundPortInputDto, ): Promise<UserReadOutboundPortOutputDto> { return this.params; } } class MockUserCreateOutboundPort implements UserCreateOutboundPort { constructor(private readonly params: UserCreateOutboundPortOutputDto) {} async execute( _: UserCreateOutboundPortInputDto, ): Promise<UserCreateOutboundPortOutputDto> { return this.params; } } //-- testing -- test('ํ์๊ฐ์ (๋ก์ปฌ) : ์ ์์ ์ธ ํ์๊ฐ์ ', async () => { userSignUpService = new UserSignUpService( new MockUserReadOutboundPort(null), new MockUserCreateOutboundPort({ id: '1', email: 'createLocalTester@obo.com', nickname: 'oboNewbie', authMethod: 'LOCAL', }), ); const params = { email: 'createLocalTester@obo.com', password: '1q2w3e4r', nickname: 'oboNewbie', authMethod: 'LOCAL', }; const result: UserSignUpInboundPortOutputDto = await userSignUpService.execute(params); expect(result).toEqual({ userId: '1', email: 'createLocalTester@obo.com', nickname: 'oboNewbie', }); }); //-------------------------------------------------- test('ํ์๊ฐ์ (๋ก์ปฌ) : ์ค๋ณต๋ ์ด๋ฉ์ผ๋ก ํ์๊ฐ์ ์๋', async () => { userSignUpService = new UserSignUpService( new MockUserReadOutboundPort({ id: '1', email: 'existentUsers@obo.com', nickname: 'oboUser', authMethod: 'LOCAL', }), new MockUserCreateOutboundPort(null), ); const params = { email: 'existentUsers@obo.com', // ์ค๋ณต๋ ์ด๋ฉ์ผ password: '1q2w3e4r', nickname: 'oboNewbie', authMethod: 'LOCAL', }; expect(async () => await userSignUpService.execute(params)).rejects.toThrow( new HttpException('์ด๋ฏธ ๊ฐ์ ๋ ์ด๋ฉ์ผ์ ๋๋ค.', HttpStatus.BAD_REQUEST), ); }); //-------------------------------------------------- test('ํ์๊ฐ์ (๋ก์ปฌ) : ๋น๋ฐ๋ฒํธ ์์ด ํ์๊ฐ์ ์๋', async () => { userSignUpService = new UserSignUpService( new MockUserReadOutboundPort(null), new MockUserCreateOutboundPort({ id: '1', email: 'createLocalTester@obo.com', nickname: 'oboNewbie', authMethod: 'LOCAL', }), ); const params = { email: 'createLocalTester@obo.com', password: null, // ๋ก์ปฌ ๊ฐ์ ์ธ๋ฐ ๋น๋ฐ๋ฒํธ ๋ฏธ ์ ๋ ฅ nickname: 'oboNewbie', authMethod: 'LOCAL', }; expect(async () => await userSignUpService.execute(params)).rejects.toThrow( new HttpException('๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ์ธ์.', HttpStatus.BAD_REQUEST), ); }); });
-
ObO ํ๋ก์ ํธ์ ์๋ฏธ
- OBO ํ๋ก์ ํธ์ ์ผ์ ๊ด๋ฆฌ ์๋น์ค๋ ๋ด๊ฐ ์ค์ ๋ก ํ์ํ ๊ธฐ๋ฅ์ ๋ด์ ๋ง๋ ํ๋ก์ ํธ์ด๋ค.
- ๊ทธ๋์ ํด๋ผ์ด์ธํธ ๊ด์ ์์ ๋ฐ๋ผ๋ณด๋ฉฐ, ์ด๊ฒ์ด ๋ฐฑ์๋์์๋ ์ด๋ป๊ฒ ๊ตฌํ๋๋์ง ์๊ฐํด ๋ณผ ์ ์์๋ค.
- ๊ทธ ์ ๊น์ง์ ํ๋ก์ ํธ๋ค์ ๋จ์ ๊ตฌํ์ ๋จธ๋ฌผ๋ ์ง๋ง, ObO๋ ์ํคํ ์ณ์ ํจ๋ฌ๋ค์์ ์ ์ฉ๊ณผ ์๋ก์ด ๊ธฐ์ ์ ์ฌ์ฉ์ด ์ด๋ฃจ์ด์ก๊ธฐ์ ๋์ฑ ์ฑ์ฅํ ์ ์๋ ํ๋ก์ ํธ์๋ค.
- ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉฐ ๋ง์ฃผํ ์์ํ ๋ฌธ์ ๋ค์ ๊ธฐ๋กํ์ง ์์๋ค. ๋๋ถ๋ถ์ด ๋จ์ ์ง์๋ถ์กฑ์ด๊ธฐ์ ๊ธฐ๋กํ ํ์๊ฐ ์๋ค๊ณ ๋๊ผ๊ธฐ ๋๋ฌธ์ด๋ค. ๋ฐ๋ฉด์ ์๋ก ์๊ฒ๋๊ณ ์ดํด๊ฐ ์ ๋ฆฌ๋ ๋ด์ฉ์ ๊ธฐ๋กํ๋ค.
-
๋๋ฆฌ์ง๋ง ํ์คํ๊ฒ
- ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ์ดํด๊ฐ ๋ถ์กฑํ๋ค๊ณ ๋๋๋ถ๋ถ์ ํ๋ก์ ํธ๋ฅผ ์ค๋จํ๊ณ ๊ฐ๋ ์ ์ดํดํ๊ณ ์ค๊ธฐ๋ ํ๋ค. ํนํ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ด ๊ทธ๋ฌ๋ค. (https://velog.io/@uiseok0514/FP-deepdive)
- ๊ณต๋ถ์ ๊ฐ๋ฐ์ ๋ฐ๋ณตํ๋ค๋ณด๋ ์์ฑํ ์ง ์กฐ๊ธ ์ง๋ ์ฝ๋๋ค์ ์ด์ ์ ๋ณด์ด์ง ์๋ ์๋ชป๋ ์ ๊ณผ ๋จ์ ๋ค์ด ๋ณด์ด๊ธฐ ์์ํ๋ค. ํนํ ์ด๋ฐ์ ๊ฐ๋ฐํ user์ todo ํํธ๋ ๊ฑฐ์ 3๋ฒ์ ๋ ๊ฐ์ ์์๋ค.
- ํ ์คํธ ์ผ์ด์ค๋ ์ ๋ฐ ์ด์ ๋ชจ๋์ ์์ฑํ๋ค๊ฐ, ์๋ชป๋ ๋ถ๋ถ์ ๊นจ๋ซ๊ณ ๋ชจ๋ ์ญ์ ํ๋ค. ํ์ฌ ๋ค์ ํ ์คํธ์ผ์ด์ค๋ฅผ ์์ฑ ์ค์ด๋ค. ( ์ด ํ๋ก์ ํธ๋ TDD๋ ์๋๊ธฐ ๋๋ฌธ์ ํ ์คํธ์ผ์ด์ค๋ฅผ ๋จผ์ ์์ฑํ์ง ์์๋ค. )
-
ํ์ง๋ง ์์ง๋ ๋ถ์กฑํ๋ค
- ํ๋ก์ ํธ๋ฅผ ํตํด ๋ค์ํ ๊ธฐ์ ๊ณผ ๊ฐ๋ ์ ์๋ก ์ ํ์ง๋ง, ์์ง ๋ฐฐ์ฐ๊ณ ์ตํ์ผ ํ ๊ฒ๋ค์ด ๋์ณ๋๋ค.
- ์๋ฅผ ๋ค๋ฉด, ๋๋ DB์ฟผ๋ฆฌ๋ฅผ mikroORM์ entityManager๋ฅผ ํตํด ์์ฑํ์ง๋ง, SQL ์ฟผ๋ฆฌ๋ฌธ์ผ๋ก ์์ฑํ์ง ์์๋ค. ORM์ด ๊ฐ์ง ํ๊ณ์ ์ ๋ถ๋ช ํ๊ธฐ์, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ต์ ํ ๋๊ณ ๋ณต์กํ ์ฟผ๋ฆฌ๋ฅผ ์ํํ๊ธฐ ์ํด์๋ SQL๋ฌธ ์์ฑ ๋ฅ๋ ฅ์ด ํ์ํ๋ค.
- ์ง๊ธ์ ์๋ฒ๋ ๋จ์ผ์๋ฒ, ๋จ์ผDB ์ด๋ค. ๋ง์ผ ์๋ฒ์ ์ ์ ๊ฐ ๋งค์ฐ ๋ง๊ณ ์์ฒญ๋๋ ์ฟผ๋ฆฌ์ ์ ๋ณด๊ฐ ํ ๋์ ์๋ฒ ๋ง์ผ๋ก ์ปค๋ฒ๋ ์ ์์ ๊ฒ์ด๊ณ , ์์ง ๋๋ ์ํ์ ํ์ฅ์ด ์ด๋ฃจ์ด์ ธ์ผ ํ๋ค. ์ฌ๊ธฐ์ ๋ํด์๋ ์ถ๊ฐ์ ์ผ๋ก ๊ณต๋ถ๊ฐ ํ์ํ๋ค.
- ์ข์ ํ๋ก๊ทธ๋จ์ด๋ ๋ฌด์์ผ๊น? ์ข์ ์ฝ๋๋ ๋ฌด์์ผ๊น? ๋ผ๋ ์ง๋ฌธ์ ๋ํด ๊นจ๋ฌ์์ ์ป๊ธฐ์ํด ์ฝ์ผ๋ ค๊ณ ์์๋ ์ฑ ์ด ์์ง ๋ง๋ค. ๊ฒฝํ์ด ๋ถ์กฑํด์ ์ฑ ์ ๋ด์ฉ์ ๋ค ์ดํด ๋ชปํ ๋๋ ๋ง๊ณ , ์๊ฐ์ด ๋ถ์กฑํด ์ฝ์ง ๋ชปํ ์ฑ ๋ ์๋ค. ๊พธ์คํ ์ฝ๊ณ ์๊ฐํ๋ฉฐ ๊ณ ์ฐฐํด์ผ ํ ๊ฒ์ด๋ค.




