๋ฌด์์ ํ๋์?
ํฌ๋๋ฆฌ์คํธ์ ํซ ์ก์ฑ ๊ฒ์์ด ํผํฉ๋ ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์๋ค. ํฌ๋๋ฅผ ๋ฌ์ฑํ๋ฉด์ ๋ชจ์ ๊ฒ์๋จธ๋์ ๊ฒฝํ์น๋ก ํซ์ ์งํ์ํค๊ฑฐ๋ ์์ดํ ์ ๊ตฌ๋งคํ์ฌ ๋ง์ด๋ฃธ์ ๊พธ๋ฐ ์ ์๋ค. ๋, ์ธ์ฆ์ท์ ์ฌ๋ ค์ ํ ์ผ์ ํ์ธ๋ฐ๊ณ ์น๊ตฌ์ ๋ฃธ์ ๋๋ฌ๊ฐ๊ฑฐ๋ ๋ฐฉ๋ช ๋ก์ ๋จ๊ธฐ๋ ์์ ๊ธฐ๋ฅ๋ ์๋ค.
์ฑ ๊ฐ๋ฐ์ ์ํด ํฌ๋ก์คํ๋ซํผ ๊ฐ๋ฐ ํ๋ ์์ํฌ์ธ Flutter๋ฅผ ์ฌ์ฉํ๋ค. ์๋ฒ๋ NestJS, DB๋ PostgreSQL ์ฌ์ฉํ๊ณ ORM์ TypeORM์ ์ผ๋ค. ์ด์ธ๋ก๋ Socket IO, Docker, EC2 ๊ฐ ์ํคํ ์ฒ์ ํฌํจ๋์๋ค.
๋๋ง๋ฌด ์ด์ ์ฃผํน๊ธฐ ๊ธฐ๊ฐ์ Spring์ ํ๊ธฐ ๋๋ฌธ์ ์ด๋ฐ์๋ ํ๋์ ๊ธฐ๋ฅ์ ๋ํด ๋ฐฑ, ํ๋ก ํธ ๋ชจ๋ ์ฝ๋๋ฅผ ์์ฑํ์ง๋ง ํ๋ฐ๋ถ๋ก ๊ฐ์๋ก ํ๋ก ํธ์๋ง ์ง์คํ๋ ๊ฒ ๊ฐ๋ค.
Kakao OAuth ๊ธฐ๋ฐ ๋ก๊ทธ์ธ
๋ก๊ทธ์ธ ๊ฐ์ ๊ธฐ๋ฅ์ ์๊ฐ ๋ค์ด์ง ๋ง๋ผ๋ ๊ฒฝ๊ณ ์๋ ๊ฒฝ๊ณ ๋ฅผ ๋ง์ด ๋ค์์ง๋ง.. ์ด๋ฐ ์ฝ๋๋ฅผ ์๋ชป ์ง๋ ๋ฐ๋์ ์๊ฐ ์๋ชจ๊ฐ ๋ง์๋ ๊ฒ ๊ฐ๋ค. Flutter๋ฅผ ์ํ KakaoSDK๋ก ๋๋ฐ์ด์ค์ ์นด์นด์ค ์๋ฒ์์ ์ฐ๊ฒฐ๋ง์ผ๋ก ์ธ์ฆ์ ์ฒ๋ฆฌํ๋๋ก ํ๋๋ฐ, ์ด ๊ฒฐ๊ณผ ์๋ฒ์ ๋๋ฐ์ด์ค ๊ฐ์ ์ธ์ฆ ๋ก์ง์ด ๋๋ฝ๋์๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Redirection ๋ฐฉ์์ผ๋ก ์ธ์ฆ์ ์ฒ๋ฆฌํ๋๋ก ๋ค์ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
- ์ฌ์ฉ์๋ ์ฑ์์ ์นด์นด์ค ๊ณ์ ์ ๋ณด๋ฅผ ์ ๋ ฅํ๊ณ ๋ก๊ทธ์ธ ๋ฒํผ์ ๋๋ฅธ๋ค.
- ์นด์นด์ค ์๋ฒ๋ก ๊ณ์ ์ ๋ณด๊ฐ ์ ์ก๋๊ณ , ๋ก๊ทธ์ธ์ด ์ฑ๊ณตํ๋ฉด ์๋น์ค ์๋ฒ (์ฐ๋ฆฌ์ Nest์๋ฒ)๋ก ์ธ๊ฐ ์ฝ๋๊ฐ ๋ ์์จ๋ค.
- ์๋น์ค ์๋ฒ๋ ์ธ๊ฐ ์ฝ๋๋ฅผ ๋ค์ ์นด์นด์ค ์๋ฒ๋ก ๋ณด๋ด์ด ์ ์ ๋ฅผ ์๋ณํ๋ ID๊ฐ์ ์ ๋ฌ๋ฐ๋๋ค. ์ด๋ฅผ DB์์ ์กฐํํ์ฌ ์ ์ ์ธ ๊ฒฝ์ฐ JWT ํ ํฐ์ ๋ฐ๊ธ, DeepLink๋ฅผ ํตํด ์ฑ์ผ๋ก ๋ฆฌ๋๋ ์ ํ๋ค. ์ ์ ๊ฐ ์๋ ๊ฒฝ์ฐ access denied ๋ฉ์์ง๋ฅผ ์ ๋ฌํ๋ค.
- ์ฑ์ ์ ๋ฌ๋ฐ์ ํ ํฐ์ 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 ๋ก์ง๊ณผ ์ด๋ฒคํธ ๋ก์ง์ ๋ถ๋ฆฌํ ์ ์์ด ์ฝ๋ ์์ฑ์ด ๋ ํธํ๊ธฐ ๋๋ฌธ์ ์ด ๋ฐฉ๋ฒ์ ํํ๋ค. ๋, ๋์ค์๋ ์์ผ์ผ๋ก ์ค๋ ์๋ฆผ์ ๋ํ ์ด๋ฒคํธ ์ญ์ ์ด๊ณณ์์ ์ฒ๋ฆฌ๋๋๋ก ๊ตฌํํ๊ธฐ ๋๋ฌธ์ ์คํธ๋ฆผ์ด ์ ์ ํ๋ค๊ณ ์๊ฐํ๋ค.
์ด๋ฐ์ ์ด๋ฒคํธ ๊ด๋ฆฌ์ ํด๋์ค๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ๋ทฐ๋ชจ๋ธ๊ณผ ๋ทฐ๋ชจ๋ธ ๊ฐ์ ํต์ ์ ์กฐ๊ธ ๋ ์ฝ๊ฒ ํ๊ธฐ ์ํด์์๋ค. (๋น๋ก ๊ทธ๋ ๊ฒ ์ฌ์ฉ๋์ง๋ ์์๋ค.) ํ ์ผ ํ๋๋ฅผ ์ฒดํฌํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ผ์ด ๋ฒ์ด์ง๋ค.
- ์๋ฒ๋ก ํ ์ผ ์ฒดํฌ api๋ฅผ ๋ณด๋ธ๋ค.
- ์๋ฒ๋ ํด๋น ํ ์ผ์
todo_done
ํ๋๋ฅผtrue
๋ก ๋ฐ๊พธ๊ณ , ์ฒดํฌ๋ ํ ์ผ์ด ์ค๋์ ์ฒซ ํ ์ผ์ธ์ง ํ์ธํ๋ค. ์ฒซ ํ ์ผ์ธ ๊ฒฝ์ฐ ์ ์ ์ ์บ์๋ฅผ 100 ์ฌ๋ฆฌ๊ณ , ๊ทธ๋ ์ง ์์ผ๋ฉด 10 ์ฌ๋ฆฐ๋ค. ๋ณ๊ฒฝ๋ ์ ์ ์ ์บ์ ์ํ๋ฅผ ์๋ต์ผ๋ก ๋ฐํํ๋ค. - ์ฑ์
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 |