SWJungle ๐Ÿ‘Š

[SWJungle] ๋‚˜๋งŒ์˜ ๋ฌด๊ธฐ ๋งŒ๋“ค๊ธฐ ๊ฐœ์ธ ํšŒ๊ณ 

cece00 2023. 12. 17. 18:22

๋ฌด์—‡์„ ํ–ˆ๋‚˜์š”?

ํˆฌ๋‘๋ฆฌ์ŠคํŠธ์™€ ํŽซ ์œก์„ฑ ๊ฒŒ์ž„์ด ํ˜ผํ•ฉ๋œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์—ˆ๋‹ค. ํˆฌ๋‘๋ฅผ ๋‹ฌ์„ฑํ•˜๋ฉด์„œ ๋ชจ์€ ๊ฒŒ์ž„๋จธ๋‹ˆ์™€ ๊ฒฝํ—˜์น˜๋กœ ํŽซ์„ ์ง„ํ™”์‹œํ‚ค๊ฑฐ๋‚˜ ์•„์ดํ…œ์„ ๊ตฌ๋งคํ•˜์—ฌ ๋งˆ์ด๋ฃธ์„ ๊พธ๋ฐ€ ์ˆ˜ ์žˆ๋‹ค. ๋˜, ์ธ์ฆ์ƒท์„ ์˜ฌ๋ ค์„œ ํ• ์ผ์„ ํ™•์ธ๋ฐ›๊ณ  ์นœ๊ตฌ์˜ ๋ฃธ์— ๋†€๋Ÿฌ๊ฐ€๊ฑฐ๋‚˜ ๋ฐฉ๋ช…๋ก์„ ๋‚จ๊ธฐ๋Š” ์†Œ์…œ ๊ธฐ๋Šฅ๋„ ์žˆ๋‹ค.

์•ฑ ๊ฐœ๋ฐœ์„ ์œ„ํ•ด ํฌ๋กœ์Šคํ”Œ๋žซํผ ๊ฐœ๋ฐœ ํ”„๋ ˆ์ž„์œ„ํฌ์ธ Flutter๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. ์„œ๋ฒ„๋Š” NestJS, DB๋Š” PostgreSQL ์‚ฌ์šฉํ–ˆ๊ณ  ORM์€ TypeORM์„ ์ผ๋‹ค. ์ด์™ธ๋กœ๋Š” Socket IO, Docker, EC2 ๊ฐ€ ์•„ํ‚คํ…์ฒ˜์— ํฌํ•จ๋˜์—ˆ๋‹ค.

๋‚˜๋งŒ๋ฌด ์ด์ „ ์ฃผํŠน๊ธฐ ๊ธฐ๊ฐ„์— Spring์„ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ดˆ๋ฐ˜์—๋Š” ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ๋ฐฑ, ํ”„๋ก ํŠธ ๋ชจ๋‘ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ์ง€๋งŒ ํ›„๋ฐ˜๋ถ€๋กœ ๊ฐˆ์ˆ˜๋ก ํ”„๋ก ํŠธ์—๋งŒ ์ง‘์ค‘ํ–ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

Kakao OAuth ๊ธฐ๋ฐ˜ ๋กœ๊ทธ์ธ

๋กœ๊ทธ์ธ ๊ฐ™์€ ๊ธฐ๋Šฅ์— ์‹œ๊ฐ„ ๋“ค์ด์ง€ ๋ง๋ผ๋Š” ๊ฒฝ๊ณ  ์•„๋‹Œ ๊ฒฝ๊ณ ๋ฅผ ๋งŽ์ด ๋“ค์—ˆ์ง€๋งŒ.. ์ดˆ๋ฐ˜ ์ฝ”๋“œ๋ฅผ ์ž˜๋ชป ์งœ๋Š” ๋ฐ”๋žŒ์— ์‹œ๊ฐ„ ์†Œ๋ชจ๊ฐ€ ๋งŽ์•˜๋˜ ๊ฒƒ ๊ฐ™๋‹ค. Flutter๋ฅผ ์œ„ํ•œ KakaoSDK๋กœ ๋””๋ฐ”์ด์Šค์™€ ์นด์นด์˜ค ์„œ๋ฒ„์™€์˜ ์—ฐ๊ฒฐ๋งŒ์œผ๋กœ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ–ˆ๋Š”๋ฐ, ์ด ๊ฒฐ๊ณผ ์„œ๋ฒ„์™€ ๋””๋ฐ”์ด์Šค ๊ฐ„์˜ ์ธ์ฆ ๋กœ์ง์ด ๋ˆ„๋ฝ๋˜์—ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Redirection ๋ฐฉ์‹์œผ๋กœ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋‹ค์‹œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

  1. ์‚ฌ์šฉ์ž๋Š” ์•ฑ์—์„œ ์นด์นด์˜ค ๊ณ„์ • ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ๋‹ค.
  2. ์นด์นด์˜ค ์„œ๋ฒ„๋กœ ๊ณ„์ • ์ •๋ณด๊ฐ€ ์ „์†ก๋˜๊ณ , ๋กœ๊ทธ์ธ์ด ์„ฑ๊ณตํ•˜๋ฉด ์„œ๋น„์Šค ์„œ๋ฒ„ (์šฐ๋ฆฌ์˜ Nest์„œ๋ฒ„)๋กœ ์ธ๊ฐ€ ์ฝ”๋“œ๊ฐ€ ๋‚ ์•„์˜จ๋‹ค.
  3. ์„œ๋น„์Šค ์„œ๋ฒ„๋Š” ์ธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์นด์นด์˜ค ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์–ด ์œ ์ €๋ฅผ ์‹๋ณ„ํ•˜๋Š” ID๊ฐ’์„ ์ „๋‹ฌ๋ฐ›๋Š”๋‹ค. ์ด๋ฅผ DB์—์„œ ์กฐํšŒํ•˜์—ฌ ์œ ์ €์ธ ๊ฒฝ์šฐ JWT ํ† ํฐ์„ ๋ฐœ๊ธ‰, DeepLink๋ฅผ ํ†ตํ•ด ์•ฑ์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•œ๋‹ค. ์œ ์ €๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ access denied ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.
  4. ์•ฑ์€ ์ „๋‹ฌ๋ฐ›์€ ํ† ํฐ์„ secured storage์— ์ €์žฅํ•œ๋‹ค. access denied์ผ ๊ฒฝ์šฐ ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋‹ค์Œ ๋กœ๊ทธ์ธ ์‹œ์—๋Š” JWT ํ† ํฐ์„ ์ด์šฉํ•ด ์„œ๋น„์Šค ์„œ๋ฒ„์™€ ์ง์ ‘ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋‹ค. (ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ) ์นด์นด์˜ค๋””๋ฒจ๋กœํผ์Šค์—์„œ ๋‘ ๋ฐฉ๋ฒ• ๋ชจ๋‘์— ๋Œ€ํ•ด ์ƒ์„ธํžˆ ๋ฌธ์„œ๋ฅผ ์ •๋ฆฌํ•ด๋‘๊ณ  ์žˆ์–ด์„œ ์ฝ”๋“œ๋ฅผ ์งœ๋Š” ๊ฒƒ ์ž์ฒด๋Š” ํฌ๊ฒŒ ์–ด๋ ต์ง€ ์•Š์•˜๋Š”๋ฐ, ์›น์—์„œ ์•ฑ์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๋Š” ๊ฒƒ์ด๋‚˜ ์นด์นด์˜ค ์„œ๋น„์Šค ์ž์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์„ค์ •ํ•ด์•ผ ํ•˜๋Š” ํ‚ค๊ฐ’ ๋“ฑ ์ž์ž˜ํ•œ ๋ถ€๋ถ„์—์„œ ๋ฏธ์ˆ™ํ•œ ์ ์ด ๋งŽ์•„ ์˜ค๋ž˜ ๊ฑธ๋ ธ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

  • ์•ˆ๋“œ๋กœ์ด๋“œ์˜ ๊ฒฝ์šฐ ์•ฑ ํ‚ค๋ฅผ ๋“ฑ๋กํ•ด์•ผ ํ•œ๋‹ค. ์—ฌ๋Ÿฌ ๋ช…์ด ๊ฐœ๋ฐœ์ค‘์ด๋ผ๋ฉด ๋ชจ๋‘์˜ ์•ฑ ํ‚ค๋ฅผ ์นด์นด์˜ค๋””๋ฒจ๋กœํผ์Šค์˜ ๋‚ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ > ํ”Œ๋žซํผ > ์•ˆ๋“œ๋กœ์ด๋“œ์— ๋“ฑ๋กํ•ด์•ผ ์ •์ƒ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. (๋””๋ฒ„๊ทธ ๋ชจ๋“œ ๊ธฐ์ค€, ๋ฆด๋ฆฌ์ฆˆ๋Š” ์•„์ง ๋ชจ๋ฅด๊ฒ ๋‹ค.)
  • iOS์˜ ๊ฒฝ์šฐ ๋ฒˆ๋“ค ์•„์ด๋”” ํ•˜๋‚˜๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์—ฌ๋Ÿฌ ๋ช…์ด ๊ฐœ๋ฐœํ•  ๊ฒฝ์šฐ ๊ฐ๊ฐ์˜ ๋ฒˆ๋“ค ์•„์ด๋””๊ฐ€ ๋‹ค๋ฅด๊ธฐ ๋–„๋ฌธ์—(๊ณ ์œ ํ•˜๋„๋ก ์• ํ”Œ์ด ์ œํ•œํ•˜๊ณ  ์žˆ๋‹ค), iOS๋Š” ํ˜„์žฌ๊นŒ์ง€ ๋‚˜์˜ ํŒ๋‹จ์œผ๋กœ๋Š” ํ•œ ๋ช…๋งŒ ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.
  • ๋”ฅ๋งํฌ๋ฅผ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” UniLink ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋“œ๋  ๋•Œ deeplink listen์„ ์‹œ์ž‘ํ•˜๊ณ , ์›น์—์„œ ์•ฑ์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜์ด ์™„๋ฃŒ๋˜๋ฉด cancelํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.

UI๊ฐ€ ๊ทธ๋ ค์ง€๊ธฐ ์ด์ „์— ๋กœ๊ทธ์ธ ๊ณผ์ •์ด ์ˆ˜ํ–‰๋˜๊ณ , ๋กœ๊ทธ์ธ ์ƒํƒœ์— ๋”ฐ๋ผ ๋ฉ”์ธํ™”๋ฉด, ๋กœ๊ทธ์ธ ํ™”๋ฉด, ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด ๋“ฑ ์„œ๋กœ ๋‹ค๋ฅธ ํ™”๋ฉด์„ ๊ทธ๋ ค์•ผ ํ–ˆ๊ธฐ์— AuthService๋ผ๋Š” ChangeNotifier ์ƒ์† ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ–ˆ๋‹ค. Provider ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์ธ๋ฐ, ํŠน์ •๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด ๊ด€๋ จ UI๊ฐ€ ๋ชจ๋‘ ์žฌ๋นŒ๋“œ๋  ์ˆ˜ ์žˆ๋„๋ก notifyํ•œ๋‹ค.

MainPage & Navigator

์ƒ๋‹จ appbar์™€ ํ•˜๋‹จ navbar๋กœ ์ด๋ฃจ์–ด์ ธ ํด๋ฆญ ์‹œ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ๋‚ด๋น„๊ฒŒ์ด์…˜๋˜๋Š” ๋ ˆ์ด์•„์›ƒ์„ ๋งŒ๋“ค์—ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” ํ• ์ผ, ์ƒ์ , ๋งˆ์ด๋ฃธ ๋“ฑ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฐ”๋ฅผ ์ด๋ฃจ๋Š” ๊ฐ ํŽ˜์ด์ง€๋งˆ๋‹ค Scaffold ์œ„์ ฏ์œผ๋กœ ๋งŒ๋“ค์—ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ ํŽ˜์ด์ง€์˜ ์ž…์žฅ์—์„œ๋Š” '์ž์‹ ์ด ๋ช‡ ๋ฒˆ์งธ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฉ”๋‰ด์ธ์ง€๋ฅผ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—' ํ˜„์žฌ ํŽ˜์ด์ง€์— ํ•ด๋‹นํ•˜๋Š” ๋ฉ”๋‰ด์˜ ์ƒ‰์„ ํ•˜์ด๋ผ์ดํŒ…ํ•˜์ง€ ๋ชปํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ๋ฌผ๋ก , ํŽ˜์ด์ง€๋งˆ๋‹ค ํ•˜์ด๋ผ์ดํŒ…์ด ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฐ”๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค. ํ• ์ผ ํŽ˜์ด์ง€์˜ navbar์—๋Š” 'ํ• ์ผ' ์•„์ด์ฝ˜์„ ํ•˜์ด๋ผ์ดํŠธํ•˜๊ณ , ๋งˆ์ด๋ฃธ ํŽ˜์ด์ง€์˜ navbar๋Š” '๋งˆ์ด๋ฃธ'์„ ํ•˜์ด๋ผ์ดํŠธํ•œ๋‹ค๋˜์ง€.

ํ•˜์ง€๋งŒ ์ „์—ญ์ ์œผ๋กœ ํ˜„์žฌ ์–ด๋–ค ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋Š”์ง€ ์ธ๋ฑ์Šค๊ฐ’ ํ•˜๋‚˜๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉด ์ด๋ฅผ ๊ธฐ์ค€์œผ๋กœ navbar ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ƒ๋‹จ appbar์™€ ํ•˜๋‹จ navbar๋ฅผ ๋‘๊ณ , ๋‚ด๋ถ€ body์— ํ•ด๋‹นํ•˜๋Š” ํŽ˜์ด์ง€๋งŒ ์ƒํƒœ๋กœ ๊ด€๋ฆฌํ•˜๋ฉฐ ๊ฐˆ์•„๋ผ์šฐ๋Š” ๋ฐฉ๋ฒ•์„ ํƒํ–ˆ๋‹ค.

class _MainPageState extends State<MainPage> {

    // ....

    @override
    Widget build(BuildContext context) {
    AppNavigator nav = context.watch<AppNavigator>();
    final List<AppPage> bottoms = nav.BOTTOM_PAGES;
    final List<AppPage> appbars = nav.APPBAR_PAGES;

    final AppPage curr = nav.current;

    // * ==== Trigger Login Event ==== * //
    context.read<UserInfo>().initEvents();

    return Scaffold(
        resizeToAvoidBottomInset: false,
        // * ======= APPBAR ======= * //
        appBar: MyAppBar(
        leading: nav.pushback,
        title: Text(nav.title ?? curr.label),
        actions: appbars
            .map(
                (each) => each.toAppbarWidget(
                // ์•ฑ ๋ฐ” ์•„์ด์ฝ˜ ํด๋ฆญ์‹œ ํ•ด๋‹น ์ธ๋ฑ์Šค๋กœ ๋ณ€๊ฒฝ
                onPressed: () => nav.push(each.idx),
                child: each.icon,
                ),
            )
            .toList(),
        ),
        // * ======= BODY ======= * //
        body: Builder(
          // ๋นŒ๋” ํ•จ์ˆ˜ ์ฝœ
          builder: (context) => curr.builder(context),
        ),
        bottomNavigationBar: nav.isBottomSheetPage
            ? // ... navbar ์œ„์ ฏ
            : null,
        );
    }
}

๋ฉ”์ธ ํŽ˜์ด์ง€์˜ ๋นŒ๋“œ ๋ฉ”์†Œ๋“œ๋Š” ์œ„์™€ ๊ฐ™๋‹ค. ๊ฐ ํŽ˜์ด์ง€์˜ ์•„์ด์ฝ˜, ์ด๋ฆ„, ํŽ˜์ด์ง€ ๋นŒ๋” ํ•จ์ˆ˜๋ฅผ ์ผ์ข…์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋กœ ๋ฌถ์–ด์„œ ๋‚ด๋น„๊ฒŒ์ด์…˜์„ ๋‹ด๋‹นํ•˜๋Š” ์ƒํƒœ ๊ด€๋ฆฌ ํด๋ž˜์Šค๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ฐ€, ์‚ฌ์šฉ์ž์˜ ์•ก์…˜์— ๋”ฐ๋ผ ์„œ๋กœ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฉ์‹์ด๋‹ค.

final AppPage _todo = AppPage(
    idx: AppRoute.todo,
    label: "ํ• ์ผ",
    icon: Icon(Icons.check_box_outlined),
    builder: (context) => TodoPage(),
);
class AppNavigator extends ChangeNotifier {

    AppNavigator() {
        // ์ดˆ๊ธฐ ๋‚ด๋น„๊ฒŒ์ด์…˜ ์ƒํƒœ
        _stack.add(_idx);
    }

    // * ====== navigation status ====== * //
    List<int> _stack = [];
    int _idx = 2; // ํ™ˆ ํŽ˜์ด์ง€

    // ํ˜„์žฌ ํŽ˜์ด์ง€ ์ œ๋ชฉ์„ ์ง€์ •
    String? _title;
    String? get title => _title;
    set title(String? val) => _title = val;

    AppPage get current => ALL_PAGES[_idx];
    bool get isBottomSheetPage => _idx < 5 && _stack.length < 2;
    int get idx => _idx;

    String? _argument;
    String? get arg => _argument;
    set arg(String? val) {
        _argument = val;
        notifyListeners();
    }


    // * ====== navigator methods ====== * //
    void navigate(AppRoute route, {String? argument, String? title}) {
        _argument = argument;
        _title = title;

        if (route.index != _idx) {
            _stack = [];
            _idx = route.index;
            _stack.add(route.index);
        }
        notifyListeners();
    }

    void push(AppRoute route, {String? argument, String? title}) {
        _argument = argument;
        _title = title;

        if (route.index != _idx) {
            _idx = route.index;
            _stack.add(route.index);
        }
        notifyListeners();
    }

    void pop({String? argument, String? title}) {
        _stack.removeLast();
        _idx = _stack.isEmpty ? 2 : _stack.last;
        _argument = argument;
        _title = title;
        notifyListeners();
    }
}

๋‹จ์ˆœํžˆ ์ด๋™๋งŒ ํ•˜๋Š” ํŽ˜์ด์ง€๋„ ์žˆ์—ˆ๋˜ ๋ฐ˜๋ฉด, ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ด์ „ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜์™€์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์—ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด stack์„ ๋‘์–ด์„œ push, pop ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค. ๋‹ค๋งŒ ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” ์ผ€์ด์Šค๋ฅผ ๋งŽ์ด ํ…Œ์ŠคํŠธํ•ด๋ณธ ๊ฒƒ์€ ์•„๋‹ˆ๋ผ์„œ.. ๊ณผ์—ฐ ํ˜„์žฌ ์ฝ”๋“œ๋กœ ๋ชจ๋“  ๋‚ด๋น„๊ฒŒ์ด์…˜์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š”์ง€, ๋˜ ์ฒ˜๋ฆฌ๋˜์ง€ ๋ชปํ•œ ์˜ˆ์™ธ ์ผ€์ด์Šค๋“ค์€ ์—†๋Š”์ง€ ์ถฉ๋ถ„ํžˆ ์•Œ๊ธฐ ์–ด๋ ค์› ๋‹ค๋Š” ์•„์‰ฌ์›€์ด ์žˆ๋‹ค. ์‹œ๊ฐ„์  ์—ฌ์œ ๋งŒ ์žˆ๋‹ค๋ฉด push, pop์— ํ•ด๋‹นํ•˜๋Š” UI ๋ชจ์…˜๊นŒ์ง€๋„ ๊ตฌํ˜„ํ•ด ๋ณด๊ณ  ์‹ถ์€ ๋ถ€๋ถ„์ด๊ธฐ๋„ ํ•˜๋‹ค. (+ Navigator 2.0)

App Event Service

์‚ฌ์šฉ์ž์˜ ์•ก์…˜์— ๋”ฐ๋ผ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ , ์ŠคํŠธ๋ฆผ์„ ํ†ตํ•ด ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ์ด๋ฒคํŠธ ๊ด€๋ฆฌ์ž ํด๋ž˜์Šค๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค. ์•ฑ ์‹คํ–‰ ์‹œ ํ•˜๋‚˜๋งŒ ์žˆ์œผ๋ฉด ๋  ๊ฒƒ์ด๋ผ๋Š” ํŒ๋‹จ์œผ๋กœ static ํด๋ž˜์Šค๋กœ ๊ตฌํ˜„ํ–ˆ๊ณ , ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ง€์†์ ์œผ๋กœ ์ด๋ฒคํŠธ ๋ฐœ์ƒ์„ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•˜๋ฏ€๋กœ stream์„ ํ™œ์šฉํ–ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” ChangeNotifier + List<Event> ์œผ๋กœ๋„ ๊ตฌํ˜„ํ• ๊นŒ ์‹ถ์—ˆ๋Š”๋ฐ, ์ŠคํŠธ๋ฆผ์ด ๋ฉ”์ธ UI ๋กœ์ง๊ณผ ์ด๋ฒคํŠธ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด ์ฝ”๋“œ ์ž‘์„ฑ์ด ๋” ํŽธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ฐฉ๋ฒ•์„ ํƒํ–ˆ๋‹ค. ๋˜, ๋‚˜์ค‘์—๋Š” ์†Œ์ผ“์œผ๋กœ ์˜ค๋Š” ์•Œ๋ฆผ์— ๋Œ€ํ•œ ์ด๋ฒคํŠธ ์—ญ์‹œ ์ด๊ณณ์—์„œ ์ฒ˜๋ฆฌ๋˜๋„๋ก ๊ตฌํ˜„ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ŠคํŠธ๋ฆผ์ด ์ ์ ˆํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์ดˆ๋ฐ˜์— ์ด๋ฒคํŠธ ๊ด€๋ฆฌ์ž ํด๋ž˜์Šค๋ฅผ ๊ตฌํ˜„ํ–ˆ๋˜ ๊ฒƒ์€ ๋ทฐ๋ชจ๋ธ๊ณผ ๋ทฐ๋ชจ๋ธ ๊ฐ„์˜ ํ†ต์‹ ์„ ์กฐ๊ธˆ ๋” ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ์˜€๋‹ค. (๋น„๋ก ๊ทธ๋ ‡๊ฒŒ ์‚ฌ์šฉ๋˜์ง€๋Š” ์•Š์•˜๋‹ค.) ํ• ์ผ ํ•˜๋‚˜๋ฅผ ์ฒดํฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ผ์ด ๋ฒŒ์–ด์ง„๋‹ค.

  1. ์„œ๋ฒ„๋กœ ํ• ์ผ ์ฒดํฌ api๋ฅผ ๋ณด๋‚ธ๋‹ค.
  2. ์„œ๋ฒ„๋Š” ํ•ด๋‹น ํ• ์ผ์˜ todo_done ํ•„๋“œ๋ฅผ true๋กœ ๋ฐ”๊พธ๊ณ , ์ฒดํฌ๋œ ํ• ์ผ์ด ์˜ค๋Š˜์˜ ์ฒซ ํ• ์ผ์ธ์ง€ ํ™•์ธํ•œ๋‹ค. ์ฒซ ํ• ์ผ์ธ ๊ฒฝ์šฐ ์œ ์ €์˜ ์บ์‹œ๋ฅผ 100 ์˜ฌ๋ฆฌ๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด 10 ์˜ฌ๋ฆฐ๋‹ค. ๋ณ€๊ฒฝ๋œ ์œ ์ €์˜ ์บ์‹œ ์ƒํƒœ๋ฅผ ์‘๋‹ต์œผ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  3. ์•ฑ์˜ TodoViewModel์€ ์‘๋‹ต์„ ๋ฐ›์•„ List<Todo>์˜ ํ•ด๋‹น Todo ๊ฐ์ฒด์˜ todoDone๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ณ  notifyListeners();๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๊ด€๋ จ ์œ„์ ฏ์„ ์žฌ๋นŒ๋“œํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ์ƒํƒœ๊ด€๋ฆฌ ํด๋ž˜์Šค์ธ UserInfo์˜ userCash ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.

์ด ๊ณผ์ • ์ค‘ TodoViewModel๊ณผ UserInfo๊ฐ„์˜ ํ†ต์‹ ์ด ์ด๋ฒคํŠธ๋ฅผ ํ†ตํ•ด ์ด๋ฃจ์–ด์งˆ ์ˆ˜ ์žˆ์ง€ ์•Š์„๊นŒ? ๋ผ๋Š” ์ƒ๊ฐ์ด ์žˆ์—ˆ๋‹ค. UserInfo ์—ญ์‹œ ์‚ฌ์‹ค์ƒ ์œ ์ € ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ๋ทฐ๋ชจ๋ธ๋กœ๋„ ๋ณผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ ํด๋ž˜์Šค๊ฐ€ ์ƒ์„ฑ๋  ์‹œ ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ์„ ๊ตฌ๋…์‹œ์ผœ๋‘๊ณ  ํ†ต์‹ ํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™์•˜๋‹ค. ํ•˜์ง€๋งŒ ๋ทฐ๋ชจ๋ธ๋ผ๋ฆฌ์˜ ๊ฒฐํ•ฉ๋„๊ฐ€ ์ฆ๊ฐ€ํ•˜๊ณ , MVVM ์•„ํ‚คํ…์ฒ˜ ์›์น™์ƒ ๋ทฐ๋ชจ๋ธ๊ณผ ๋ทฐ๋ชจ๋ธ ๊ฐ„์˜ ์ง์ ‘์ ์ธ ํ†ต์‹ ์€ ์ง€์–‘ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฒฐ๊ตญ ํ•˜์œ„ ๋ทฐ์— ํ•ด๋‹นํ•˜๋Š” TodoPage์—์„œ ์–‘์ชฝ์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ชฝ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค.

๋Œ€์‹ , ๋ชจ๋‹ฌ์„ ๋„์šฐ๊ธฐ ์œ„ํ•ด ์ด๋ฒคํŠธ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. ๋งŒ์•ฝ ์ฒซ ํ• ์ผ์„ ๋‹ฌ์„ฑํ•œ ๊ฒฝ์šฐ ์œ„ ๊ณผ์ •์ด ๋งˆ๋ฌด๋ฆฌ๋˜๊ณ  ๋‚œ ํ›„ '์ฒซ ํ• ์ผ ๋‹ฌ์„ฑ!' ๋ชจ๋‹ฌ์ด ๋– ์•ผ ํ–ˆ๋‹ค. ์ด๋ฅผ TodoPage์˜ ๋ทฐ์—์„œ ๋‹ด๋‹นํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๋งŒ์•ฝ ๊ทธ ์‚ฌ์ด์— ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ๋„˜์–ด๊ฐ€๋ฉด ๋ชจ๋‹ฌ์ด ๋œจ์ง€ ์•Š๊ฒŒ ๋œ๋‹ค. ํšจ๊ณผ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด, ์œ ์ € ์บ์‹œ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•œ ํ›„ UserInfo ์ชฝ์—์„œ FirstTodoDone ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๊ณ , ์ด๋ฅผ MainPage ํด๋ž˜์Šค์—์„œ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ๋‹ค๊ฐ€ ๋ชจ๋‹ฌ์„ ๋„์šฐ๋„๋ก ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

class _MainPageState extends State<MainPage> {

    StreamSubscription<Event>? sub;
    final Queue<Event> _eventq = Queue<Event>();
    bool _waiting = false;

    @override
    void initState() {
        super.initState();
        requestNotificationPermission();

        // * ==== Event Listener ==== * //
        sub = EventService.stream.listen((event) async {
            String? message = event.message;
            EventType type = event.type;

            if (type.target == 'socket') {
                // ์†Œ์ผ“์˜ ๊ฒฝ์šฐ ์ฆ‰์‹œ
                type.run(context, message: message);
            } else if (type.target == 'ui') {
                // UI๋ผ๋ฉด ํ์— ์Œ“๊ธฐ
                _eventq.add(event);
                if (!_waiting) {
                    await _nextEvent();
                }
            }
        });
    }

    Future<void> _nextEvent() async {
        while (_eventq.isNotEmpty) {
            _waiting = true;
            Event event = _eventq.removeFirst();
            String? message = event.message;
            EventType type = event.type;

            bool? completed = await type.show(context, message: message) as bool?;
            await Future.delayed(Duration(seconds: 1)); // ์ด๋ฒคํŠธ ์‚ฌ์ด ๊ฐ„๊ฒฉ ์กฐ์ •
            _waiting = false;
        }
    }

    @override
    void dispose() {
        if (sub != null) sub!.cancel();
        super.dispose();
    }
}

// user_info.viewmodel.dart
// ์ฒซ ํˆฌ๋‘ ์ฒดํฌ ์ด๋ฒคํŠธ
void _onTodoReward(int prevUserCash) {
    int reward = _userCash - prevUserCash;
    if (reward == RewardService.FIRST_TODO_REWARD) {
        EventService.publish(Event(
            type: EventType.onFirstTodoDone,
        ));
    }
}

์‹ค์ œ๋กœ ์ด๋ฒคํŠธ๋ฅผ run, show ํ•˜๋Š” ๋ถ€๋ถ„์€ enum ํด๋ž˜์Šค์ธ EventType์˜ extension์„ ๋งŒ๋“ค์–ด ์ฒ˜๋ฆฌํ–ˆ๋Š”๋ฐ, Event ๊ฐ์ฒด ์•ˆ์— Future<bool> ํƒ€์ž…์œผ๋กœ ์‹คํ–‰๋  ํ•จ์ˆ˜๋ฅผ ๋„ฃ์–ด ๋‘์—ˆ์œผ๋ฉด (AppPage ๊ฐ์ฒด ์•ˆ์— ๋นŒ๋” ํ•จ์ˆ˜๋ฅผ ๋„ฃ์–ด ๋‘” ๊ฒƒ์ฒ˜๋Ÿผ) ์กฐ๊ธˆ ๋” ๊น”๋”ํ•œ ์ฝ”๋“œ ์ž‘์„ฑ์ด ๋˜์—ˆ์„ ๊ฒƒ ๊ฐ™๋‹ค. ํ”Œ๋Ÿฌํ„ฐ, ์•ฑ๊ฐœ๋ฐœ, MVVM ๋ชจ๋‘ ๋ฏธ์ˆ™ํ•œ ๋ถ€๋ถ„์ด ๋งŽ์•„ ๋ณ€์ˆ˜๋ช…์ด๋‚˜ ํด๋”๊ตฌ์กฐ๊ฐ€ ๋น„์ผ๊ด€์ ์ธ ๋ถ€๋ถ„๋„ ๋งŽ๊ณ , ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๋„ ๋งŽ์•˜๋‹ค. ์กฐ๊ธˆ ๋” ๊ผผ๊ผผํ•˜๊ฒŒ ์ƒ๊ฐํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•ด๋ณด๋ฉด์„œ ์ฝ”๋“œ๋ฅผ ์งœ ๋ณด์ž.

๋ฌด์—‡์„ ๋Š๊ผˆ๋‚˜์š”?

Frontend & App

  • ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋ฏธ๋ฆฌ ์ ์šฉํ•˜์ง€ ๋ง์ž. ์ •๊ธ€ ์ด์ „ ํ–ˆ์—ˆ๋˜ ์ฝ”ํ‹€๋ฆฐ ๊ฐœ๋ฐœ์—์„œ MVVM์„ ์‹œ๋„ํ–ˆ๋‹ค๊ฐ€ ์–ด๋ ค์›€์„ ๊ฒช์€ ์ ์ด ์žˆ์—ˆ๋‹ค. ์ด๋ฒˆ์—๋Š” ์ž˜ ์จ๋ณด๊ณ  ์‹ถ๋‹จ ์š•์‹ฌ์— ์ดˆ๊ธฐ์— ๋„ˆ๋ฌด ์ปค๋‹ค๋ž€ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์—ผ๋‘์— ๋‘๊ณ  ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•œ ๊ฒƒ์ด ๋‚˜๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ํŒ€์˜ ์ƒ์‚ฐ์„ฑ์„ ๋„๋ฆฌ์–ด ์ €ํ•˜์‹œํ‚ค๋Š” ์š”์ธ์ด ๋˜์ง€ ์•Š์•˜๋‚˜ ์‹ถ๋‹ค. ์ด๊ฒƒ์ €๊ฒƒ ํ•„์š”ํ•˜๊ฒ ์ง€ ์‹ถ์–ด ํด๋ž˜์Šค๋ฅผ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด๋‘์—ˆ๋Š”๋ฐ, ์‹ค์ œ๋กœ๋Š” ๊ทธ์‚ฌ์ด์— ์š”๊ตฌ์‚ฌํ•ญ์ด ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜, ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„ ์“ฐ์ง€ ์•Š๊ฒŒ ๋œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜๋‹ค. ๋ฏธ๋ฆฌ ์งœ๋‘์ง€ ๋ง์ž. ์šฐ์„  ๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜๊ณ  ์‰ฌ์šด ๋ฐฉ์‹์œผ๋กœ ๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœํ•˜๊ณ , ์ฒœ์ฒœํžˆ ๋ถ™์—ฌ๋‚˜๊ฐ€๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š”, ํ™”๋ฉด์˜ UI๋นŒ๋“œ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๋ทฐ, ์ „์—ญ ์ƒํƒœ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ํ•œํ•ด ChangeNotifier ์ƒ์†ํ•œ ๋ณ„๋„ ๋ทฐ๋ชจ๋ธ, ์„œ๋ฒ„ API ์ „์†ก์‹œ URI, Header๊ฐ’ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” static class, ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๋‹ด๋Š” Model ์ •๋„๋ฉด ์ข‹์•˜๋˜ ๊ฒƒ ๊ฐ™๋‹ค.
  • ์•ฑ๊ฐœ๋ฐœ๊ณผ ๋ชจ๋ฐ”์ผ UI/UX์— ๋Œ€ํ•ด ์กฐ๊ธˆ ๋” ๊ณต๋ถ€ํ•ด๋ณด์ž. ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ ์˜์™ธ์˜€๋˜ ๋ถ€๋ถ„์€ ํ”„๋ก ํŠธ ๊ฐœ๋ฐœ์ด ์žฌ๋ฏธ์žˆ๋‹ค๋Š” ์ ์ด์—ˆ๋‹ค. ์›น์ด ์•„๋‹ˆ๋ผ ์•ฑ์ด๋ผ์„œ ๋”์šฑ ๊ทธ๋žฌ๋˜ ๊ฒƒ ๊ฐ™๋‹ค. ์–ด๋–ป๊ฒŒ ๊ธฐ๋Šฅ์„ ์ค‘์•™์—์„œ ๊ด€๋ฆฌํ•  ๊ฒƒ์ธ์ง€, ์ฝ”๋“œ๋ฅผ ์ž˜ ์งค ๊ฒƒ์ธ์ง€์— ๋Œ€ํ•œ ๊ณ ๋ฏผ๋„ ์žฌ๋ฏธ์žˆ์—ˆ๊ณ , ์‚ฌ์šฉ์ž์—๊ฒŒ ํŽธํ•œ ๊ฒฝํ—˜์„ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•  ๊ฒƒ์ธ์ง€ ์ƒ๊ฐํ•˜๋Š” ๊ณผ์ •๋„ ๊ทธ๋žฌ๋‹ค. UI/UX ๋ ˆํผ๋Ÿฐ์Šค๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ์•ฑ์„ ์ฐพ์•„๋ณด์•˜๋Š”๋ฐ, ๋ชจ๋ฐ”์ผ์€ ์›น ๋Œ€๋น„ ํ™”๋ฉด์˜ ๋‹ค์–‘์„ฑ์ด ๋” ์ ์€ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ๋Š๊ผˆ๋‹ค. ์•„๋ฌด๋ž˜๋„ ํ™”๋ฉด ์ž์ฒด๊ฐ€ ์ž‘๋‹ค๋ณด๋‹ˆ ํ•œ๋ฒˆ์— ๋ณด์—ฌ์ง€๋Š” ์ •๋ณด์˜ ๋‹จ์œ„๊ฐ€ ํ›จ์”ฌ ์ž‘์•˜๋‹ค. ํšŒ์›๊ฐ€์ž…๋งŒ ํ•ด๋„ ์›น์—์„œ๋Š” ํ•œ๋ฒˆ์— ๋ณด์—ฌ์ง€๋Š” ํ™”๋ฉด์ด, ๋ชจ๋ฐ”์ผ์—์„œ๋Š” '๋‹ค์Œ'/'์ด์ „' ๋ฒ„ํŠผ์„ ํ†ตํ•ด ์—ฌ๋Ÿฌ ๋‹จ๊ณ„์˜ ํ™”๋ฉด์œผ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค. ๋˜ ์ƒ๋‹จ appbar๋‚˜ ํ•˜๋‹จ navbar, ์ขŒ์šฐ drawer ๋“ฑ๊ณผ ๊ฐ™์ด ๋ชจ๋ฐ”์ผ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ด๋ฏธ ๊ด€์Šต์ฒ˜๋Ÿผ ์ž๋ฆฌ์žก์€ UI/UX๋“ค์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด ์ด๊ฒƒ์„ ์ž˜ ์•Œ๊ณ  ์ ์žฌ์ ์†Œ์— ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹จ ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

Backend

  • ์ž์›์„ ์ผ์œผ๋ฉด! ๋ฐ˜ํ™˜ํ•˜์ž! ํ•€ํ† ์Šค OOM๋•Œ๋„ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๊ด€๋ จํ•ด์„œ ์ด๋ฏธ ํ•œ๋ฐ”๋‹ฅ ๋ฐ˜์„ฑ๋ฌธ์„ ์ ์—ˆ์—ˆ๋Š”๋ฐ ์ด๋ฒˆ์—๋„ ๋น„์Šทํ•œ ์‹ค์ˆ˜๋ฅผ ํ•˜๊ณ  ๋ง์•˜๋‹ค. ํŠธ๋žœ์žญ์…˜์„ ์œ„ํ•ด TypeORM api ์ค‘ queryRunner๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ, ๋ช…์‹œ์ ์œผ๋กœ commit, rollback, release๋ฅผ ํ•ด์•ผ ํ–ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ try ... catch ๋ฌธ ์•ˆ์—์„œ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋ฉด์„œ release ์—†์ด ์ฒ˜๋ฆฌํ•œ ๊ฒƒ์ด ๋ฌธ์ œ๊ฐ€ ๋˜์—ˆ๋‹ค. ํ•จ์ˆ˜๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ฆฌํ„ด๋˜๋Š” ๊ฒฝ์šฐ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์˜ˆ์™ธ๋กœ ๋น ์ ธ๋‚˜๊ฐ€๋Š” ๊ฒฝ์šฐ์— ํŠนํžˆ ์ž์› ๋ฐ˜ํ™˜์„ ์ž˜ ํ•˜์ž๊ณ  ์ด๋ฏธ 9์ฃผ์ „์— ์ ์—ˆ์—ˆ๋Š”๋ฐ ์—ฌ์ „ํžˆ ์‰ฝ์ง€ ์•Š์€ ๋ถ€๋ถ„.. ๋‹คํ–‰ํžˆ ์„œ๋ฒ„์— ์š”์ฒญ ๋กœ๊ทธ๊ฐ€ ์ฐํžˆ๋„๋ก ํŒ€์›๋ถ„๊ป˜์„œ ๋ฏธ๋ฆฌ ์„ธํŒ…์„ ํ•ด ๋‘์…”์„œ, ๋‚ด ์ฝ”๋“œ๊ฐ€ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚จ๋‹ค๋Š” ๊ฒƒ์„ ๋น ๋ฅด๊ฒŒ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋กœ๊ทธ๋‚˜ ํŠธ๋ ˆ์ด์Šค๋ฅผ ๋‚จ๊ธฐ๋Š” ๊ฒƒ์˜ ์ค‘์š”์„ฑ๋„ ์•Œ๊ฒŒ ๋œ ๊ฒฝํ—˜์ด์—ˆ๋‹ค. ๊ตฌํ˜„ํ•˜์‹  ํŒ€์›๋ถ„์˜ ๋ง์”€์— ๋”ฐ๋ฅด๋ฉด ์‹ค์ œ ์—…๋ฌด์—์„œ๋Š” ์ด๋ฏธ ํŒ€์ด ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๋กœ๊ทธ๋‚˜ ๊ณตํ†ต์ ์ธ ๋ชจ๋“ˆ์ด ๋‹ค ์žˆ๋Š” ์ƒํƒœ์—์„œ ํ•ฉ๋ฅ˜ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฐ ์‚ฌ์ „์ž‘์—…์„ ํ•ด๋ณด๊ณ  ์‹ถ๋‹ค๊ณ  ํ•˜์…จ๋‹ค. ๊ทธ ๋ง์— ๋”ฐ๋ฅด๋ฉด ์•„๋งˆ ์ด๋Ÿฐ ๋ถ€๋ถ„์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋  ์ผ์€ ํฌ๊ฒŒ ์—†์„ ์ˆ˜๋„ ์žˆ๊ฒ ์ง€๋งŒ, ๊ทธ๋ž˜์„œ ๋”์šฑ ์ด๊ฒŒ ์ค‘์š”ํ•˜๊ณ  ์‹ค๋ฌด์™€ ๋งž๋‹ฟ์•„ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ๊ฐ€์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค.
  • ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ 5๊ฐœ ํ–ˆ์œผ๋ฉด 5๊ฐœ ํ…Œ์ŠคํŠธ ๋‹ค ํ•ด๋ณด๋Š” ๊ฒŒ ๋‹น์—ฐํ•˜๋‹ค. ์œ„์™€๋„ ๋งž๋‹ฟ์•„ ์žˆ๋Š” ์ด์•ผ๊ธฐ์ธ๋ฐ, ๋งŒ์•ฝ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ๋ณด๊ณ , ๋ฌธ์ œ ์ƒํ™ฉ์„ ๋” ๋นจ๋ฆฌ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค๋ฉด ์ข‹์•˜์„ ๊ฒƒ์ด๋‹ค. ๋‚˜๋ฆ„ ํ…Œ์ŠคํŠธ๋ฅผ ํ•œ๋‹ค๊ณ ๋Š” ํ–ˆ์ง€๋งŒ ๋ชจ๋“  ์ผ€์ด์Šค๋ฅผ ๋‹ค ๊ผผ๊ผผํžˆ ํ…Œ์ŠคํŠธํ•˜์ง€ ์•Š๊ณ  main์— merge ํ–ˆ๋˜ ๊ฒƒ์ด ์น˜๋ช…์ ์ธ ์‹ค์ˆ˜์˜€๋˜ ๊ฒƒ ๊ฐ™๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ๋•Œ๋Š” ๋จผ์ € ์ผ€์ด์Šค์— ๋„˜๋ฒ„๋งํ•˜๊ณ , postman์—์„œ ์š”์ฒญ, ์‘๋‹ต์„ ์ €์žฅํ•ด์„œ ํ…Œ์ŠคํŠธ ๋ฆฌํฌํŠธ๋ฅผ ์ค€๋น„ํ•ด๋‘์ž. ๊ธฐ๋กํ•˜์ง€ ์•Š์œผ๋ฉด ํ…Œ์ŠคํŠธ ๋œ ์ฝ”๋“œ์ธ์ง€ ์•„๋‹Œ์ง€๋„ ์žŠ์–ด๋ฒ„๋ฆฌ๊ธฐ ๋งˆ๋ จ์ด๋‹ค. ํŠนํžˆ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๋Š” ๊ผผ๊ผผํ•จ๊ณผ ๊ธฐ๋ก์ด ํ•„์ˆ˜์ ์ธ ์—ญ๋Ÿ‰์ธ ๊ฒƒ ๊ฐ™๋‹ค.
  • ์˜๋ฏธ ์—†๋Š” ๊ฐ’ ์“ฐ์ง€ ๋ง์ž. ํŠนํžˆ ์ˆซ์ž ๊ฐ™์€ ๊ฒƒ์€ enum์œผ๋กœ ๋บ„ ์ˆ˜ ์žˆ์œผ๋ฉด ๋นผ์ž. ๋ณด๋Š” ์‚ฌ๋žŒ์˜ ์ž…์žฅ์—์„œ๋Š” ์ด๊ฒŒ ๋ฌด์Šจ ๊ฐ’์ธ์ง€ ์•Œ๊ธฐ ์–ด๋ ต๋‹ค.

Cooperation

  • ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ ์ฝ”๋“œ๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ๋ง์ž. ๊ฑด๋“œ๋ ค์•ผ ํ•  ๊ฒฝ์šฐ ์ผ๋‹จ ๋ฌผ์–ด๋ณด๊ณ , ๋ถ€ํƒํ•˜์ž. ํ”„๋ก ํŠธ ์ฝ”๋“œ์— ๊ธฐ๋Šฅ์„ ๋‹ฌ ์ผ์ด ์žˆ์—ˆ๋Š”๋ฐ, ๋ณ„๋„ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์ง€ ์•Š์€ ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•  ๊ฒฝ์šฐ ๋‚ด ์ž…์žฅ์—์„œ ๋” ํŽธํ•˜๊ฒŒ ์ฝ”๋“œ๋ฅผ ์“ธ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์•„๋ฌด ์ƒ๊ฐ ์—†์ด ๋ถ„๋ฆฌํ•ด์„œ ์žฌ์ž‘์„ฑํ–ˆ๋Š”๋ฐ ๊ทธ ํ›„๋กœ ์ƒํƒœ ๊ฐฑ์‹ ์ด ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ์ž‘์„ฑ๋˜์–ด ์žˆ๋Š” ์ฝ”๋“œ๊ฐ€ ์–ด๋Š ์ •๋„๊นŒ์ง€์˜ ๊ธฐ๋Šฅ ์™„์„ฑ์„ ์ปค๋ฒ„ํ•˜๊ณ  ์žˆ๋Š”์ง€ ์›์ž‘์ž ์™ธ์˜ ์‚ฌ๋žŒ์€ ์•Œ๊ธฐ ์–ด๋ ต๋‹ค. ๊ทธ๋Ÿฐ ์ƒํ™ฉ์—์„œ ๋‚ด๊ฐ€ ํŽธํ•˜์ž๊ณ  ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด 1. ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ ์‚ฌ๋žŒ๋„, ์ˆ˜์ •ํ•œ ์‚ฌ๋žŒ๋„ ์˜จ์ „ํžˆ ์•Œ์ง€ ๋ชปํ•˜๋Š” ๊ตฌ์—ญ์ด ๋งŒ๋“ค์–ด์ง€๊ณ  2. ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ด ๊ฐœ๋ฐœ ์†๋„๊ฐ€ ๋”๋ŽŒ์งˆ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง„๋‹ค. ์ด๋ฒˆ ๋‚˜๋งŒ๋ฌด ํ”„๋กœ์ ํŠธ๋ฅผ ํ†ตํ‹€์–ด ๊ฐ€์žฅ ํฌ๊ฒŒ ๋ฐ˜์„ฑํ•˜๊ณ  ๋ฐฐ์› ๋˜ ์ ์ด ์•„๋‹๊นŒ ์‹ถ๋‹ค. ์•ž์œผ๋กœ๋Š” ์ ˆ๋Œ€ ํ•ด์„œ๋Š” ์•ˆ ๋  ํ–‰๋™์ด๋‹ˆ ์กฐ์‹ฌํ•˜์ž.
  • ๊ตฌ๋‘๋กœ ์š”์ฒญํ•˜๊ธฐ๋ณด๋‹ค๋Š” ํ˜‘์—…ํˆด์„ ํ†ตํ•˜์ž. ๋งค์ผ ์Šคํฌ๋Ÿผ์„ ์ง„ํ–‰ํ–ˆ๋Š”๋ฐ, ๊ธ‰ํ•œ ๋งˆ์Œ์— ๋‚˜๋„ ๋ชจ๋ฅด๊ฒŒ ์ด๊ฒƒ์ €๊ฒƒ ๋ง๋กœ ์š”์ฒญํ•˜๊ฑฐ๋‚˜ ๋ฌผ์–ด๋ดค๋˜ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜๋‹ค. ์‹ค์ œ ํŒ€์ด ๊ตฌ๋‘๋กœ ์š”์ฒญํ•˜๊ธฐ๋กœ ๊ทœ์น™์„ ์ •ํ–ˆ๋‹ค๋ฉด ๋ชจ๋ฅผ๊นŒ, jira, notion, slack์ด ๋ฒ„์ “์ด ์žˆ๋Š” ์ƒํ™ฉ์—์„œ ๊ตฌ๋‘ ์š”์ฒญ๊นŒ์ง€ ๋“ฃ๋Š” ์ผ์€ ์ƒ๋Œ€๋ฐฉ์˜ ์ž…์žฅ์—์„œ ์‹ ๊ฒฝ ์“ธ ์ฑ„๋„์„ ํ•˜๋‚˜ ๋” ๋Š˜๋ฆฌ๋Š” ๊ฒƒ์ด๋‹ค. ๋‚ด ๋งˆ์Œ ๊ธ‰ํ•˜๋‹ค๊ณ  ๊ทœ์น™ ๊ฑฐ์Šค๋ฅด์ง€ ๋ง๊ณ , jira ์“ฐ๋Š” ๋ฒ•์—๋„ ๋” ์ต์ˆ™ํ•ด์ง€์ž.

์•ž์œผ๋กœ ๋ญ˜ ํ• ๊ฑด๊ฐ€์š”?

ํ”„๋กœ์ ํŠธ์—์„œ ์ˆ˜์ •ํ•˜๊ณ  ์‹ถ์€ ๋ถ€๋ถ„์ด ๋งŽ์€๋ฐ, ๋ณด์™„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๊ฐ€ ๋œ๋‹ค๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค. ๊ฐœ์ธ์ ์œผ๋กœ๋Š”, ์ด๋ฒˆ ๊ฐœ๋ฐœ๊ณผ ๋™์ผํ•œ ์•„ํ‚คํ…์ฒ˜๋กœ ๊ธฐ๋Šฅ์€ ๋งŽ์ด ์ถ•์†Œํ•ด์„œ ํ”Œ๋Ÿฌํ„ฐ ์•ฑ๊ฐœ๋ฐœ, nestJS ์„œ๋ฒ„๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•ด๋ณด๊ณ  ์‹ถ๋‹ค. ํŠนํžˆ ํ”Œ๋Ÿฌํ„ฐ๋Š” ๊ถ๊ธˆํ–ˆ๋˜ ๋งŒํผ ๋‹ค ๋ชป ์จ ๋ณธ๊ฒŒ ๋งŽ๋‹ค. provider ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„ ์ข€ ๊ณต๋ถ€ํ•ด๋ณด๊ณ ์‹ถ๊ณ . ์•ฑ๊ฐœ๋ฐœ์— ๊ด€์‹ฌ์ด ๋งŽ์ด ์ƒ๊ธด ๊ฒƒ ๊ฐ™๋‹ค.


์งง๋‹ค๋ฉด ์งง๊ณ  ๊ธธ๋‹ค๋ฉด ๊ธด ๋‚˜๋งŒ์˜ ๋ฌด๊ธฐ ๋งŒ๋“ค๊ธฐ๊ฐ€ ๋๋‚ฌ๋‹ค. ๋ฐœํ‘œ ๋•Œ ํ™”๋ฉด ์—ฐ๊ฒฐ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋˜ ๊ฒƒ๋งŒ ๋นผ๋ฉด ๋งˆ๋ฌด๋ฆฌ๋„ ์ž˜ ๋˜์—ˆ๊ณ . ์ œ๋Œ€๋กœ ๋œ ํŒ€ ํ”„๋กœ์ ํŠธ ๊ฒฝํ—˜๋„ ์—†์—ˆ๋˜ ๋‚ด๊ฒŒ๋Š” ์ •๋ง ํฐ ๋„์›€์ด ๋˜์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค. ์ •๊ธ€์— ๋“ค์–ด์˜ค๊ฒŒ ๋œ ์ด์œ ๊ฐ€ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, ํ•˜๋‚˜๋Š” ๋ถ€์กฑํ•œ C์–ธ์–ด์™€ OS ์ฑ„์šฐ๊ธฐ์˜€๊ณ  ๋‘ ๋ฒˆ์งธ๋Š” ํŒ€ ํ”„๋กœ์ ํŠธ ๊ฒฝํ—˜์„ ์Œ“๋Š” ๊ฒƒ์ด์—ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ๋ชฉํ‘œ ์—ญ์‹œ ์ž˜ ์ฑ„์šด ๊ฒƒ ๊ฐ™๋‹ค. ์ข‹์€ ํŒ€์„ ๋งŒ๋‚˜ ์•ฑ๊ฐœ๋ฐœ, Jira ์“ฐ๊ธฐ, ์„œ๋ฒ„๊ฐœ๋ฐœ .. ๋ชจ๋‘ ๊ฒฝํ—˜ํ•ด๋ณผ ์ˆ˜ ์žˆ์—ˆ๊ณ , ํŒ€์›๋ถ„๋“ค๋กœ๋ถ€ํ„ฐ ๋ฐฐ์šด ์ ๋„ ๋งŽ์•˜๋‹ค. ์™„์„ฑ๋˜์–ด์žˆ๋Š” ํŒ€์— ์˜ˆ์ƒ์น˜ ๋ชปํ•˜๊ฒŒ ํ•ฉ๋ฅ˜ํ•˜๊ฒŒ ๋˜์–ด ์ดˆ๋ฐ˜์—” ๊ฑฑ์ •๋„ ์กฐ๊ธˆ ์žˆ์—ˆ๋Š”๋ฐ ๋‹คํ–‰์Šค๋Ÿฝ๊ฒŒ๋„ ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ํ•˜๊ณ  ๋‚˜์„œ๋ถ€ํ„ฐ๋Š” ์›ํŒ€์œผ๋กœ ๊ฐœ๋ฐœ์—๋งŒ ๋ชฐ๋‘ํ–ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค. ์„ฑ๊ธ‰ํ•œ ๋งˆ์Œ์— ์‹ค์ˆ˜๊ฐ€ ๋งŽ์•˜์ง€๋งŒ ํŒ€์›๋ถ„๋“ค์ด ๋งŽ์ด ์ดํ•ดํ•ด์ฃผ์…”์„œ ๋ฏธ์•ˆํ•˜๊ณ , ๋˜ ๊ฐ์‚ฌํ•˜๊ณ . ์•ž์œผ๋กœ ๋” ๋ฐœ์ „ํ•˜์ž๋Š” ๋งˆ์Œ์œผ๋กœ ๋…ธ๋ ฅํ•ด์•ผ๊ฒ ๋‹ค. ํž˜๋“ค๊ณ  ์ง€์น˜๊ธฐ๋„ ํ–ˆ์ง€๋งŒ, ๊ทธ ์ด์ƒ์œผ๋กœ ํ–‰๋ณตํ–ˆ๊ณ  ์žฌ๋ฏธ์žˆ์—ˆ๋˜ ๋‚˜๋งŒ๋ฌด ํšŒ๊ณ  ๋ ๐Ÿ€

'SWJungle ๐Ÿ‘Š' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[SWJUNGLE WEEK00] 0์ฃผ์ฐจ ํ”„๋กœ์ ํŠธ ํšŒ๊ณ   (0) 2023.08.13
[SWJUGLE WEEK00] Before Jungle  (0) 2023.08.12