[Daily morning study] BFF (Backend for Frontend) ํจํด
#daily morning study
BFF ํจํด์ด๋
BFF(Backend for Frontend)๋ ๊ฐ ํด๋ผ์ด์ธํธ ์ ํ์ ๋ง๋ ์ ์ฉ ๋ฐฑ์๋ ๋ ์ด์ด๋ฅผ ๋ณ๋๋ก ๋๋ ์ํคํ ์ฒ ํจํด์ด๋ค. ์น, ๋ชจ๋ฐ์ผ ์ฑ, IoT ๋๋ฐ์ด์ค ๋ฑ ํด๋ผ์ด์ธํธ๋ง๋ค ๋ฐ์ดํฐ ์๊ตฌ์ฌํญ์ด ๋ค๋ฅผ ๋ ๋จ์ผ API๋ก ๋ชจ๋๋ฅผ ๋ง์กฑ์ํค๊ธฐ ์ด๋ ต๋ค๋ ๋ฌธ์ ์์ ์ถ๋ฐํ๋ค.
Sam Newman์ด 2015๋ ๋ง์ดํฌ๋ก์๋น์ค ๋งฅ๋ฝ์์ ์ฒ์ ์ ์ํ ํจํด์ด๋ค.
๋ฑ์ฅ ๋ฐฐ๊ฒฝ
๊ธฐ์กด ๋จ์ผ ๋ฐฑ์๋ API๋ ๋ชจ๋ ํด๋ผ์ด์ธํธ๊ฐ ๊ณต์ ํ๋ค.
๋ชจ๋ฐ์ผ ์ฑ โโโ
์น ์ฑ โโโผโโโ ๋จ์ผ API โโโ ์ฌ๋ฌ ๋ง์ดํฌ๋ก์๋น์ค๋ค
๋ฐ์คํฌํ โโโ
์ด ๊ตฌ์กฐ์์ ๋ฐ์ํ๋ ๋ฌธ์ ๋ค:
- Over-fetching: ๋ชจ๋ฐ์ผ์ ์ด๋ฆ๊ณผ ์๋ฐํ๋ง ํ์ํ๋ฐ ์ ์ฒด ์ฌ์ฉ์ ๊ฐ์ฒด๋ฅผ ๋ฐ๋๋ค
- Under-fetching: ๋์๋ณด๋๋ฅผ ๊ทธ๋ฆฌ๋ ค๋ฉด ์ฌ๋ฌ ์๋ํฌ์ธํธ๋ฅผ ์์ฐจ ํธ์ถํด์ผ ํ๋ค
- ํด๋ผ์ด์ธํธ ๊ฐ ์ถฉ๋: ๋ชจ๋ฐ์ผ์ ์ํ ๋ณ๊ฒฝ์ด ์น ํด๋ผ์ด์ธํธ๋ฅผ ๋ง๊ฐ๋จ๋ฆด ์ ์๋ค
- ํ ์์กด์ฑ: ํ๋ก ํธ์๋ ํ์ด ๋ฐฑ์๋ ํ์ ์ผ์ ์ ์ข ์๋๋ค
BFF ๊ตฌ์กฐ
๊ฐ ํด๋ผ์ด์ธํธ ์ฑ๋๋ง๋ค ๋ณ๋์ BFF๋ฅผ ๋๋ค.
๋ชจ๋ฐ์ผ ์ฑ โโโ Mobile BFF โโโ
์น ์ฑ โโโ Web BFF โโโผโโโ ๋ด๋ถ ๋ง์ดํฌ๋ก์๋น์ค๋ค
ํํธ๋ API โโโ Partner BFF โโ
BFF์ ์ฃผ์ ์ญํ :
- ์ฌ๋ฌ ๋ง์ดํฌ๋ก์๋น์ค ํธ์ถ์ aggregationํ๋ค
- ํด๋ผ์ด์ธํธ ํํ์ ๋ง๊ฒ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๋ค
- ๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ํํฐ๋งํ๋ค
์ฝ๋ ์์
๋ชจ๋ฐ์ผ BFF โ ๋คํธ์ํฌ ์ต์ํ, ์ต์ ๋ฐ์ดํฐ
// ๋ชจ๋ฐ์ผ: ์ฌ์ฉ์ ํ๋กํ + ์ต๊ทผ ์ฃผ๋ฌธ 3๊ฐ๋ง ํ์
app.get('/mobile/user-summary', async (req, res) => {
const [user, orders] = await Promise.all([
userService.getUser(req.userId),
orderService.getRecentOrders(req.userId, { limit: 3 })
]);
res.json({
name: user.name,
avatarUrl: user.avatarUrl,
recentOrders: orders.map(o => ({
id: o.id,
status: o.status,
total: o.totalAmount
}))
});
});
์น BFF โ ๋ ํ๋ถํ ๋ฐ์ดํฐ
// ์น: ๋์๋ณด๋์ ์ ์ฒด ์ฃผ๋ฌธ ์ด๋ ฅ + ์ถ์ฒ ์ํ + ์๋ฆผ ํ์
app.get('/web/dashboard', async (req, res) => {
const [user, orders, recommendations, notifications] = await Promise.all([
userService.getUser(req.userId),
orderService.getAllOrders(req.userId),
recommendationService.get(req.userId),
notificationService.getUnread(req.userId)
]);
res.json({
user: {
name: user.name,
email: user.email,
preferences: user.preferences,
memberSince: user.createdAt
},
orders: orders.map(mapOrderDetail),
recommendations,
notifications
});
});
ํ ์๋ํฌ์ธํธ ํธ์ถ๋ก ๋์๋ณด๋ ๋ ๋๋ง์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ๋ฐ๋๋ค. ํด๋ผ์ด์ธํธ๋ N๋ฒ ํธ์ถํ์ง ์์๋ ๋๋ค.
API Gateway์์ ์ฐจ์ด
BFF์ API Gateway๋ ๋น์ทํด ๋ณด์ด์ง๋ง ์ญํ ์ด ๋ค๋ฅด๋ค.
| ํญ๋ชฉ | API Gateway | BFF |
|---|---|---|
| ๋ชฉ์ | ๊ณตํต ํก๋จ ๊ด์ฌ์ฌ ์ฒ๋ฆฌ | ํด๋ผ์ด์ธํธ๋ณ ๋ฐ์ดํฐ ์ต์ ํ |
| ๋ก์ง | ๊ฑฐ์ ์์(๋ผ์ฐํ , ์ธ์ฆ) | ๋น์ฆ๋์ค ๋ก์ง ํฌํจ ๊ฐ๋ฅ |
| ๋ฒ์ | ๋ชจ๋ ํด๋ผ์ด์ธํธ ๊ณต์ | ํด๋ผ์ด์ธํธ ์ ํ๋ณ ๋ถ๋ฆฌ |
| ์ฃผ์ฒด | ํ๋ซํผ ํ | ํ๋ก ํธ์๋ ํ |
์ค์ ๋ก ๋์ ํจ๊ป ์ฐ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
ํด๋ผ์ด์ธํธ โโโ API Gateway (์ธ์ฆ/์๋์ ํ/TLS) โโโ BFF (aggregation/๋ณํ)
์ฅ๋จ์
์ฅ์
- ํด๋ผ์ด์ธํธ ์ต์ ํ: ๊ฐ ํด๋ผ์ด์ธํธ๊ฐ ๋ฑ ํ์ํ ๋ฐ์ดํฐ๋ง ๋ฐ๋๋ค
- ํ ์์จ์ฑ: ํ๋ก ํธ์๋ ํ์ด BFF๋ฅผ ์ง์ ์์ ํ๊ณ ๋ณ๊ฒฝํ ์ ์๋ค
- ๋ ๋ฆฝ ๋ฐฐํฌ: BFF ๋ณ๊ฒฝ์ด ๋ค๋ฅธ ํด๋ผ์ด์ธํธ์ ์ํฅ์ ์ฃผ์ง ์๋๋ค
- ์ฅ์ ๊ฒฉ๋ฆฌ: ์น BFF ์ฅ์ ๊ฐ ๋ชจ๋ฐ์ผ BFF์ ์ ํ๋์ง ์๋๋ค
๋จ์
- ์ฝ๋ ์ค๋ณต: ์ฌ๋ฌ BFF์์ ๋น์ทํ ๋น์ฆ๋์ค ๋ก์ง์ด ์ค๋ณต๋ ์ ์๋ค
- ์ด์ ๋ณต์ก๋: ๊ด๋ฆฌํด์ผ ํ๋ ์๋น์ค ์๊ฐ ๋์ด๋๋ค
- ํ ๊ฒฝ๊ณ ๋ถ๋ช ํ: โ์ด๋ ํ์ด BFF๋ฅผ ์์ ํ๋๊ฐโ๋ฅผ ๋ช ํํ ํด์ผ ํ๋ค
GraphQL๊ณผ์ ๊ด๊ณ
GraphQL์ BFF์ ๋์์ผ๋ก ์์ฃผ ์ธ๊ธ๋๋ค. ํด๋ผ์ด์ธํธ๊ฐ ํ์ํ ํ๋๋ฅผ ์ฟผ๋ฆฌ์ ์ง์ ๋ช ์ํ๊ธฐ ๋๋ฌธ์ over-fetching, under-fetching ๋ฌธ์ ๋ฅผ ํด์ํ ์ ์๋ค.
# ๋ชจ๋ฐ์ผ: ํ์ํ ํ๋๋ง ์์ฒญ
query MobileProfile {
user(id: "123") {
name
avatarUrl
recentOrders(limit: 3) {
id
status
total
}
}
}
๋จ, GraphQL์ด ํญ์ BFF๋ฅผ ๋์ฒดํ์ง ์๋๋ค:
- ๊ธฐ์กด REST ์ธํ๋ผ๋ฅผ ์ ์งํด์ผ ํ๋ ๊ฒฝ์ฐ
- ํด๋ผ์ด์ธํธ๋ณ ๋ณต์กํ aggregation ๋ก์ง์ด ํ์ํ ๊ฒฝ์ฐ
- GraphQL์ ํ์ต ๊ณก์ ์ด ๋ถ๋ด์ธ ๊ฒฝ์ฐ
GraphQL BFF ์กฐํฉ๋ ์กด์ฌํ๋ค โ GraphQL ์คํค๋ง๋ฅผ ํด๋ผ์ด์ธํธ๋ณ๋ก ๋๋๋ ๋ฐฉ์์ด๋ค.
์ธ์ BFF๋ฅผ ์ ํํ๋
BFF๊ฐ ์ ํฉํ ์ํฉ:
- ํด๋ผ์ด์ธํธ ์ ํ์ด ๋ค์ํ๊ณ ๊ฐ๊ฐ ๋ค๋ฅธ ๋ฐ์ดํฐ ๋ชจ๋ธ์ ์๊ตฌํ ๋
- ํ๋ก ํธ์๋ ํ์ด ๋ฐฑ์๋ ์์กด์ฑ ์์ด ๋น ๋ฅด๊ฒ ๋ฐ๋ณตํ๊ณ ์ถ์ ๋
- ๋ง์ดํฌ๋ก์๋น์ค ํ๊ฒฝ์์ ํด๋ผ์ด์ธํธ๊ฐ ์ฌ๋ฌ ์๋น์ค๋ฅผ ์ง์ ํธ์ถํ๋ ๋ณต์ก๋๋ฅผ ์ค์ด๊ณ ์ถ์ ๋
BFF๊ฐ ๊ณผํ ์ ์๋ ์ํฉ:
- ํด๋ผ์ด์ธํธ๊ฐ ํ ์ข ๋ฅ๋ฟ์ผ ๋
- ์๋น์ค ์ด๊ธฐ ๋จ๊ณ๋ก ์ค๋ฒํค๋๊ฐ ๋ถ๋ด๋ ๋
- ํ์ด ์์์ BFF๋ฅผ ๋ณ๋๋ก ์ด์ํ๊ธฐ ์ด๋ ค์ธ ๋