베이직 250107
https://teamsparta.notion.site/8-Layered-Architecture-Pattern-e2bc4a1a218142a888bb0d45fddfc77a
8주차 Layered Architecture Pattern
하승우 튜터님
패턴, 디자인 패턴 : 코딩 방법론. 이렇게 하니까 괜찮아서 유명해진 것. 코딩의 틀을 넣ㅂ혀 간다는 생각으로 공부하기
싱글톤
레이어드 아키텍처
등
클래스 = 공장(틀만 존재)
class GameCharacter {} // 빵틀, 설계도. 클래스
const myCharaceter = new GameCharacter("멋쟁이마법사", 9, 8, 10, 10); // 제품 생성
conosle.log(myCharaceter.str); // 9// 요소 가져오기
method : 기능
공격 메서드
// 게임 캐릭터 설계도 (클래스)
class GameCharacter {
constructor(name, str, dex, int, luk) {
this.name = name; // 캐릭터 이름
this.str = str;
this.dex = dex;
this.int = int;
this.luk = luk;
}
attack() {
console.log(`${this.str}의 힘으로 공격합니다!`);
}
}
const myCharacter = new GameCharaceter("똥캐", 10, 10, 10, 10);
myCharaceter.attack(); // 10의 힘으로 공격합니다!
예시2
// 게임 캐릭터 설계도 (클래스)
class GameCharacter {
constructor(name, str, dex, int, luk) {
this.name = name; // 캐릭터 이름
this.str = str;
this.dex = dex;
this.int = int;
this.luk = luk;
}
attack() {
const randomNumber = Math.random(); // Math.random은 0~1 사이의 수를 랜덤으로 만들어요.
const damage = randomNumber * (this.str * 10)
console.log(`${damage}를 입힙니다!`);
}
levelUp() {
this.str = this.str + 1;
this.dex = this.dex + 1;
this.int += 1;
this.luk++;
console.log('레벨업을 완료하였습니다.');
}
}
const myCharacter = new GameCharaceter("똥캐", 10, 10, 10, 10);
myCharaceter.levelUp();
Class === “붕어빵 틀”
Instance === “붕어빵”
ex) 인스턴스 던전. 플레이어가 들어갈 때 마다 새로 생성됨
◆ 계층형 아키텍처 패턴 (Layered Architecture Pattern)
시스템을 여러 계층으로 분리하여 관리하는 아키텍처 패턴
단순하고 대중적이면서 비용도 적게 들어 사실상 모든 어플리케이션의 표준 아키텍처입니다.
계층형 아키텍처 패턴은 각 계층을 명확하게 분리해서 유지하고, 각 계층이 자신의 바로 아래 계층에만 의존하게 만드는 것이 목표입니다.
계층 : 비슷한 것들을 개념적으로 모아놓은 것
클래스를 사용해서 층을 나눔
class PresentationLayer{}
이미지
cf. https://www.oreilly.com/library/view/software-architecture-patterns/9781491971437/ch01.html
3계층 아키텍처에서 구성되는 각각의 계층(Layer)는 아래와 같습니다.
- 프레젠테이션 계층 (Presentation Layer)
- 비즈니스 로직 계층 (Business Logic Layer)
- 데이터 엑세스 계층 (Data Access Layer) | 영속 계층(Persistence Layer)
) 계층형 아키텍처 패턴의 장점
관심사를 분리하여 현재 구현하려하는 코드를 명확하게 인지할 수 있습니다.
모듈을 교체하더라도 코드 수정이 용이
단위 테스트를 작성할 수 있어 테스트 코드를 조금 더 용이하게 구성할 수 있습니다.
3계층 아키텍처 (3-Layered Architecture)
주로 아래의 3가지 계층으로 구성됩니다.
- 컨트롤러(Controller) : 어플리케이션의 가장 바깥 부분, 요청/응답을 처리
클라이언트의 요청(Request)을 수신 한 후 서버에서 처리된 결과를 반환(Response)해주는 역할을 담당합니다.
- 서비스(Service) : 어플리케이션의 중간 부분, API의 핵심적인 동작이 많이 일어나는 부분
아키텍처의 가장 핵심적인 비즈니스 로직이 수행되는 부분입니다.
- 저장소(Repository) : 어플리케이션의 가장 안쪽 부분, 데이터베이스와 맞닿아 있음.
실제 데이터베이스와 통신하는 계층입니다.
웹 브라우저와 비슷한 방식
3-Layered Architecture에서는 아래의 플로우를 기반으로 로직이 수행됩니다.
1⃣ 클라이언트(Client)가 어플리케이션에 요청(Request)을 보냅니다.
2⃣ 요청(Request)을 URL에 알맞은 컨트롤러(Controller)가 수신 받습니다.
3⃣ 컨트롤러(Controller)는 요청을 처리하기 위해 서비스(Service)를 호출합니다.
4⃣ 서비스(Service)는 필요한 데이터를 가져오기 위해 저장소(Repository)에게 데이터를 요청합니다.
5⃣ 서비스(Service)는 저장소(Repository)에서 가져온 데이터를 가공하여 컨트롤러(Controller)에게 데이터를 전달합니다.
6⃣ 컨트롤러(Controller)는 서비스(Service)의 결과물(Response)을 클라이언트(Client)에게 전달해줍니다.
서버 개발자들은 서버에서의 처리과정이 대부분 유사하다는 사실을 깨닫고, Controller, Service, Repository 라는 3개의 계층으로 분리하였습니다.
컨트롤러(Controller)
클라이언트의 요청(Request)을 받습니다.
요청에 대한 처리는 서비스에게 위임합니다.
클라이언트에게 응답(Response)을 반환합니다.
서비스(Service)
사용자의 요구사항을 처리하는 실세 중에 실세!!!
현업에서는 서비스 코드가 계속 확장되는 문제가 발생할 수 있습니다.
DB 정보가 필요할 때는 Repository에게 요청합니다.
저장소(Repository)
데이터베이스의 CRUD 작업을 처리합니다.
데이터베이스 관리 (연결, 해제, 자원 관리) 역할을 담당합니다.
뒤에서부터(레퍼지토리 만들고 서비스 만들고 등..)만들면 테스트와 구현이 쉬워짐
repositorie 분리하기 ( 외부에서 접근할 수 없도록)
repositories
// 게시글 생성
router.post('/posts', async (req, res) => {
const { title, content, password } = req.body;
const post = await prisma.posts.create({
data: {
title,
content,
password,
},
});
return res.status(201).json({ data: post });
});
위 코드에서 repository 역할을 하는 코드는?
정답
await prisma.posts.create({
data: {
title,
content,
password,
},
});
repositories/posts.repository.js 파일 생성
PostRepository class 생성
// prisma client 인스턴스 가져옴
import prisma from '../prisma/prisma.js';
class PostsRepository {
// orm는 오직 PostsService만 접근하도록 private(#) 설정
#orm;
// PostsRepository가 생성될 때 사용할 Orm을 받게 함.
constructor(orm) {
this.#orm = orm;
}
// 게시글 생성
createPost = async ({ title, content, password }) => {
// 생성될 때 받은 orm을 이용하여 db 접근
return await this.#orm.posts.create({
data: { title, content, password },
});
}
}
PostRepository Instance 반환( export default )
import prisma from '../prisma/prisma.js';
class PostsRepository {
#orm;
constructor(orm) {
this.#orm = orm;
}
createPost = async ({ title, content, password }) => {
return await this.#orm.posts.create({
data: { title, content, password },
});
}
// getAllPosts = async..
}
// 우리는 orm으로 prisma를 사용할꺼니까 생성자에 prisma를 전달하여
// PostsRepository 인스턴스 생성 후 반환
export default new PostsRepository(prisma); // 이 인스턴스는 서비스가 사용
// 사용은 import from~ 하고
// PostsRepository.createPost로 사용
// 2줄설명 찾아넣기
services
// 게시글 생성
router.post('/posts', async (req, res) => {
const { title, content, password } = req.body;
const post = await prisma.posts.create({
data: {
title,
content,
password,
},
});
return res.status(201).json({ data: post });
});
위 코드에서 Service 역할을 하는 코드는?
받아서 가공까지가 service
정답
const post = ...// 받아서
{ data: post } // 가공까지
코드 분리
services/posts.service.js 파일 생성
PostsService class 생성
// PostsRepository : posts.repository.js 에서 생성해서 반환 된 인스턴스
import PostsRepository from '../repositories/posts.repository.js'; // 레파지도리의 인스턴스. 어떤 레퍼지토리를 쓸 지는 생성 후 하겠다.(여기 가 맞나?)
class PostsService {
// repository는 오직 PostsService만 접근하도록 private(#) 설정
#repository;
// PostsService가 생성될 때 PostsRepository를 받게 함.
constructor(repository) {
this.#repository = repository;
}
// 게시글 생성
createPost = async (postData) => {
// PostsService가 생성될 때 받은 #repository를 활용하여 요청
return await this.#repository.createPost(postData);
}
}
▶ PostsService Instance 반환( export default )
import PostsRepository from '../repositories/posts.repository.js';
class PostsService {
#repository;
constructor(repository) {
this.#repository = repository;
}
createPost = async (postData) => {
return await this.#repository.createPost(postData);
}
}
// 생성 시 PostService가 사용할 Repository(PostsRepository)를 넣어서 생성
// PostsService 인스턴스 생성 후 반환
export default new PostsService(PostsRepository); // 이 인스턴스는 컨트롤러가 사용
controllers
// 게시글 생성
router.post('/posts', async (req, res) => {
const { title, content, password } = req.body;
const post = await prisma.posts.create({
data: {
title,
content,
password,
},
});
return res.status(201).json({ data: post });
});
위 코드에서 Controller 역할을 하는 코드는?
정답 : 어싱크부터 바디랑 리턴부분
async (req, res) => {
const { title, content, password } = req.body;
.(이 사이는 서비스랑 레이어드임)..
return res.status(201).json(...);
}
응집도를 낮춤(서로의 연결성을 낮춤)
코드 분리
controllers/posts.controller.js 파일 생성
PostsController class 생성
// PostsService : posts.service.js 에서 생성해서 반환 된 인스턴스
import PostsService from "../services/Posts.service.js";
class PostsController{
// service는 오직 PostsController만 접근하도록 private(#) 설정
#service;
// PostsController가 생성될 때 PostsService를 받게 함.
constructor(service) {
this.#service = service;
}
// 게시글 생성
createPost = async (req, res) => {
// Client로 부터 받은 데이터를 가공
const { title, content, password } = req.body;
// PostService를 이용하여 게시글 생성 요청
const post = await this.#service.createPost({ title, content, password });
// PostService가 반환한 결과를 Client에게 전달
return res.status(201).json({ data: post });
}
}
▶PostsController Instance 반환( export default )
import PostsService from "../services/Posts.service.js";
class PostsController{
#service;
constructor(service) {
this.#service = service;
}
// 게시글 생성
createPost = async (req, res) => {
const { title, content, password } = req.body;
const post = await this.#service.createPost({ title, content, password });
return res.status(201).json({ data: post });
}
}
// 생성 시 PostsController가 사용할 Service(PostsService)를 넣어서 생성
// PostsController 인스턴스 생성 후 반환
export default new PostsController(PostsService);
클라이언트 ->라우터 -> 컨드롤러 -> 서비스 -> ㄹ
routes
// 게시글 생성
router.post('/posts', async (req, res) => {
const { title, content, password } = req.body;
const post = await prisma.posts.create({
data: {
title,
content,
password,
},
});
return res.status(201).json({ data: post });
});
위 코드에서 routes 역할을 하는 코드는?
정답 : url
router.post('/posts', ...);
코드 분리
routes/posts.routes.js 파일 생성
router 생성 및 연결
// routes에서 연결할 controller 가져옴
import PostsController from "../controllers/posts.controller.js";
import express from "express";
// router 생성
const router = express.Router();
// controller의 method를 알맞는 router path와 연결
router.post("/", PostsController.createPost);
router.get("/", PostsController.getAllPosts);
router.get("/:postId", PostsController.getPostById);
export default router;
_____________________________________________________________________
▶app.js
import express from 'express';
// postsRoutes가져옴
import postsRoutes from './routes/posts.routes.js'
const app = express();
const PORT = 3017;
app.use(express.json());
// postsRoutes를 /api/posts Path에 연결
app.use('/api/posts', postsRoutes);
app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});
_________________________________________________
5) 숙제
이력서 프로젝트를 3-Layered Architecture로 변경
https://github.com/modolee-sparta-node05/node-advanced-template
'내일배움캠프_게임서버(202410) > 분반 수업 Basic-A' 카테고리의 다른 글
베이직 250121 타입스크립트 (0) | 2025.01.21 |
---|---|
9주차 Jest - 테스트(작성중) (0) | 2025.01.14 |
(진행)7주차 - Acces Token / Refresh Token, API, Insomnia (0) | 2025.01.01 |
숙제하기 - 6주차 인증 (Session / Cookie / JWT) (0) | 2024.12.24 |
교육과정 틀기 (0) | 2024.12.19 |