3-4. MVC 패턴

해피데빙·2022년 3월 10일
0

TIL

목록 보기
30/45
post-thumbnail

Node.js mysql 관계 정리
https://www.w3schools.com/nodejs/nodejs_mysql_delete.asp

mysql 시작하기, sql문 정리
https://gist.github.com/livelikeabel/909d5dc35e96e3f0bed0cd28cddcdeaf

MVC

만들어지게 된 배경

: 코드가 많아지면 많아질수록
1)코드를 파악하기 힘들어지고 2)기능 수정 시 갈아엎어야 함
-> 즉 유지보수가 힘듬

그러므로 코드를 각각 데이터 관리, 중재자, 보여주기 등 기능에 따라 정리
=> 보다 효율적인 유지보수가 가능한 MVC 패턴 탄생

mvc 구성

  • 모델 : 데이터와 관련 부분
  • 콘트롤러 : 모델과 뷰의 중개자
  • 뷰 : 사용자에게 보여지는 부분

mvc 패턴의 예시

  1. UI (client)에서 data 요청
  2. controller에서 라우팅, 요청 model로 넘긴다
  3. model이 db에서 정보를 가져와서 controller에 넘긴다 : db.query(sql, callback)
  4. controller에서 응답을 보낸다
  5. view에서 받아서 UI로 보여준다

요청 : UI > controller > model > db
응답 : model > controller > UI

mvc를 지키면서 코딩하는 방법

  1. 모델은 컨트롤러와 뷰에 의존하지 않아야 한다
    : 모델 내부에 컨트롤러와 뷰에 관련된 코드가 있으면 안된다
    : 즉 컨트롤러와 뷰에서 코드를 import해 오면 안된다 (각각 독립적으로, 모델은 데이터 관련 코드만)

  2. view는 model에만 의존해야 하고 controller에 의존하면 안된다
    (view 내부에는 model 관련 코드만 있어야지 controller 관련 코드는 있으면 안된다)

  3. view가 model로부터 데이터를 받을 때는 사용자마다 다르게 보여주어야하는 데이터에 대해서만 받아야 한다
    ex. 배달의 민족 어플에서 사용자마다 다르게 보여줘야 하는 부분들, 일관되게 똑같이 보여줘야 하는 부분들
    view는 사용자 ui(레이아웃) + model로부터 받은 데이터 = view

4.controller는 model과 view에 의존해도 된다(controller 내부에는 model과 view의 코드가 있을 수 있다 : 중개자니까!)

  1. view가 model로부터 데이터를 받을 때 반드시 controller에서 받아야 한다
    : controller 코드 내에서만 받아야 한다

Sprint 주요 과제

  1. items 테이블에서 데이터 get
  2. 주문 관련 테이블에 주문한 내용 post
  3. 주문 관련 테이블에서 주문한 내용 get
  1. 모델은 컨트롤러와 뷰에 의존하지 않아야 한다
    : model은 db관련 코드만 있고 컨트롤러를 통해서 뷰에 db에서 받은 정보를 응답으로 전송한다

  2. view는 model에만 의존해야 하고 controller에 의존하면 안된다
    : view는 controller를 통해 model에서 받은 정보를 조작한다
    (model관련 코드도 없지만 controller 관련 코드는 없다??)

  3. view가 model로부터 데이터를 받을 때는 사용자마다 다르게 보여주어야하는 데이터에 대해서만 받아야 한다
    : 사용자마다 다를 사용자의 주문 내역을 받아온다

4.controller는 model과 view에 의존해도 된다
: view에서 request를 받아서 model의 result를 조작해 view에 다시 전송하낟

  1. view가 model로부터 데이터를 받을 때 반드시 controller에서 받아야 한다

Sprint

itemList.js

ShoppingCart.js

OrderList.js

폴더/파일

client/src

client/src/pages

server

1.server/config

dotenv 변수에 dotenv 모듈을 require해서 넣는다
dotenv.config()하면 process.env에 env파일에 저장해둔 환경변수들에 접근할 수 있다

config변수에 node.js로
process.env를 이용해서 저장해둔 비밀번호를 불러오고 export를 해서 사용

mysql에 접속하기 위해 내 user정보가 필요하다
그러므로 config 사용

2. server/db

mysql 모듈 : mysql과 node.js를 연결할 때 사용
require로 받아온 뒤
mysql.createConnetcion({mysql 접속하기 위해 필요한 user정보})를 넣으면 연결이 된다
con.connect를 통해 접속한다

cf. 1번에서 설명했듯이 config 객체를 import해서 config['development']를 통해 객체 안의 development 키에 대한 값을 잡는다
안에 비밀번호 등이 들어있다

config[process.env.NODE_ENV]는 환경변수를 통해 config 객체 안의 값ㅇ르 잡는건데 아마 env파일 안에서 NODE_ENV를 설정해주면 해당 값을 키로 사용했을 것 같다. 일단 여기서는 undefined이기 때문에 development를 키로 사용한다

3. server/schema.sql

mysql에서 create database cmarket;

CLI(터미널) 환경에서 레포지토리에 진입하여 커맨드를 입력

mysql -u root -p < server/schema.sql -Dcmarket

미리 구성되어 있는 Cmarket 스키마를 기반으로 MySQL에 cmarket 데이터베이스의 테이블을 생성합니다.
다음 명령어로 cmarket 데이터베이스에 테이블을 생성합니다.

#CLI(터미널) 환경에서 레포지토리에 진입하여 커맨드를 입력

mysql -u root -p < server/schema.sql -Dcmarket

위에서 생성한 테이블에 기반이 되는 데이터를 다음 명령어로 저장합니다.

#CLI(터미널) 환경에서 레포지토리에 진입하여 커맨드를 입력

mysql -u root -p < server/seed.sql -Dcmarket

4. server/app.js

express로 서버 만들기에 대한 자세한 내용은 여기로
https://velog.io/@nikki/ServerSprint-Statesairline-Server

app.use(cors())
app.use로 모든 루트에 대해 cors() 사용

app.use(express.json())
app.use로 모든 요청 객체화, 응답 json화

app.use('/', indexRouter)
app.use로 루트 디렉토리에 대해서 indexRouter사용

app.listen(port, () => {
console.log()
})
localhost:4000에서 listen하고 있다
이게 없으면 실행이 안된다

서버의 기본 설정을 살펴봤다
이제 라우팅이 어떻게 되어 있는지 보기 위해
const indexRouter = require('./routes')
의 route파일을 보자

5. server/routes/index.js

express.Router를 export

  • 5-1.itemsRouter는 items 파일에서
  • 5-2.usersRouter는 users 파일에서

router.use(path, router);
앞에 있는 path에 대해서 모두 뒤에 있는 router로

  • /items로 오는 모든 요청은 itemsRouter로
  • /users로 오는 모든 요청은 usersRouter로

5-1.server/routes/items.js

router.get('/, controller.items.get)
'/items/'path 로 오는 get요청에 대해 controller.items.get의 콜백함수로 준다

5-1.server/routes/users.js

router.get('/:usersId/orders', controller.orders.get)
router.post('/:usersId/orders', controller.orders.post)

localhost:4000/orders/?/orders
?에 들어가는 값이 req.params.usersId = ?로 들어간다
즉 request 객체의 params의 usersId의 값으로 들어간다

localhost:4000/orders/?/orders
이 주소로 get요청을 하면 controller 객체의 orders 객체의 get함수를 콜백함수로 주고
이 주소로 post요청을 하면 controller 객체의 orders 객체의 post함수를 콜백함수로 준다

[server 정리]

각각 다른 이름의 router에 path에 따라 불리는 콜백함수가 저장된다

  • (db -> model):
    가장 말단은 db로부터 db.query(sql, callback)을 해서
    sql의 result를 callback의 result인자로 받는다

  • (model -> controller -> view):
    result 인자는 controller에서 콜백함수에 불려져 res.send(result)를 통해 클라이언트에게 전송된다

router에 저장된 path별 router

controller 구조

controller = { 
 	items: { 
	 get: (req, res) => { 
 	models.items.get((err, result)=> {
    //models 객체의 items 객체의 get 값 함수의 인자 콜백함수
    	res.send(result)
   	 	}
 		}
	 } 
 
 orders: { 
  get: (req, res) => { 
  models.orders.get((err, result)=> { 
    	res.send(result)
    }}
 post: (req, res) => { 
 models.orders.post((err, result)=> { 
    	res.send(result)
    }}
 
 }
} 

model

model = {
	items : {
      get: (callback) => { 
      //controller에서 callback 함수를 받아서 err, result를 인자로 넣는다 
      //이때 result에 db.query로 sql을 보낸 결과 result를 넣는다 
      }
    }
    orders : { 
    get: (callback) => { }
    post: (callback) => { }
    }


}

0.클라이언트에서 온 요청을
1.app.js : app.use를 통해 모든 '/'에 대해 indexRouter로 전달
2. route/index.js : router를 통해
/items, /users에 대해 itemsRouter, usersRouter로 보낸다
3. route/items.js, route/users.js :
itemsRouter와 usersRouter를 통해 각각 path에 대한 콜백함수 설정,
각각 controller 객체의 items와 orders 키의 get, post 함수를 콜백함수로 갖는다

  • itemRouter는 router.get(/items, controller.items.get)
  • usersRouter는
    - router.get(/:userId/orders, controller.order.get)
    - router.post(/:userId/orders, controller.order.post)
  1. controller/index.js :
    controller 객체의 item, orders 키에서 get, post함수는 req, res를 인자로 받아서
    model 객체의 item. orders 키의 get, post 함수에 callback 함수를 넘긴다
    이 callback함수는
    1)model 안의 함수에게 req를 조작해서 model 안의 함수의 인자로 보내고
    2)model 안의 함수에서 err,result를 인자로 받아서
    - err로 error 핸들링을 하고
    - result로 res.json(result)로 응답으로 보낸다
    => 즉 클라이언트와 서버의 요청과 응답을 관할한다
  2. model/index.js:
    1)model 객체 안의 item, order라는 객체의 get, post라는 함수에서
    controller의 callback 함수를 인자로 받는다
    이 callback 함수는 req를 받아 model 안의 함수에게 인자로 넘겨서 그 결과물을 err, result로 받아서 res로 보낸다
    2)model이기 때문에 db와 상호작용한다
    db.query(sql문, (err, result) => {
    callback(err, result)
    })
    //db(node.js와 mysql을 연결하는 변수, db/index.js 파일에 있다.)
    //첫번째 인자는 sql문, 두번째 인자는 db에 보낸 sql문에 대한 result를 받는 콜백함수다

6.controller/index.js

  • req를 받아서 userId를 뽑아내서 models.orders.get함수의 인자라로 넣는다

callback 함수

  • models 객체 안의 items, orders의 get, post함수에서
    (err, result)=> {} 함수를 callback 함수로 받는다
  • models 객체의 인자로 준 callback함수는 models 안에 있는 함수의 error, result를 인자로 받는다

상태코드

  • 그러므로 error가 일어나면 models의 error이므로 server error인 500 상태코드로 응답한다

  • error가 없으면 200 또는 201로 응답한다

    controller.post함수에서,
    -userId, orders, totalPrice 등 요청에서 있어야 하는 params의 내용 또는 payload의 내용이 없다면 models에서는 인자로 undefined를 받게 된다
    -그러므로 이런 경우에는 아예 models의 함수가 실행되지 않도록 이전에 controller.post함수를 끝내기 위해 return문을 넣는다

7.Model/index.js

db.query를 통해 sql문을 보내면 두번째 인자인 함수에 result가 담긴다

이번 Sprint에서 가장 관건은 sql문이었는데
sql문에 따라서 db에서 돌려주는 result가 다르다

models.items.get
목표 : items 테이블에서 모든 정보 가져오기
저장되어 있는 물품들의 이름, 이미지, 가격 등 칼럼별 정보들을 모두 가져온다

SELECT * FROM items;

models.orders.get
목표 : 주문한 물품들의 id, 총 가격, 주문번호, 물품번호, 개수 등을 가져와서 주문 내역 페이지에서 보여준다
관건 : orders, order_items, items 세개의 다른 테이블에서 정보 찾기
해결방법 : 모두 join을 해버린다

Select 찾고 싶은 정보들 모두(테이블명.칼럼명)형식으로 적고
FROM 테이블명
JOIN 합칠 테이블명
ON 둘이 공유하고 있는 데이터 즉 합치는 기준이 되는 칼럼
(ex. users테이블의 id를 orders테이블에서 users_id라는 Foreign key로 참조하고 있으면
ON orders.users_id = users.id라고 하면 된다)
JOIN...ON... (반복)
WHERE orders.user_id = ${userId}

models.orders.post
목표 : 주문 시 같은 주문번호, 사용자, 물품 이름, 개수, 총 가격 등 정보를 주문 관련 테이블에 저장한다.

관건1: orders테이블에 insert를 하자마자 생기는 default인 id를
바로 다음에 다른 정보들을 orders_items 테이블에 insert할 때 order_id로 넣어야 하는 것이었다
(order_id는 order테이블의 id를 참조하는 foreign key)

해결방법 : INSERT INTO 쿼리문의 result 객체 중 insertId를 사용한다

쿼리문마다 db에서 돌려주는 result 객체의 형태가 다르다

SELECT 칼럼 FROM 테이블 : 선택한 칼럼에 맞는 정보를 반환한다
INSERT, UPDATE, DELETE : 테이블 내용을 바꾸는 쿼리문은 기존 테이블이 어떻게 바뀌었는지에 대한 정보를 반환한다

자세한 건 여기로!
Node.js mysql 관계 정리
https://www.w3schools.com/nodejs/nodejs_mysql_delete.asp
왼쪽 메뉴를 통해 query마다 어떤 result를 받는지 찾아볼 수 있다

그러므로 orders테이블에 insert를 한 뒤 두번째 인자로 들어오는 result에서 result.insertId를 order_items 테이블에 insert할 때 order_id로 넣으면 된다

하지만 db.query()는 비동기
그러므로 result가 db.query()밖에서는 undefined
그러므로 이중 query()를 써줘야한다

관건2: 주문 내역이 배열일 때 안에 있는 요소들을 어떻게 하나하나 db에 넣을지
해결방법 : bulk insert + placeholder

  • placeholder '?'
    : 쿼리문을 쓸 때 ?를 넣으면 placeholder로 인식된다
    : db.query()의 두번째 인자로 []안에 ?에 넣을 값을 넣으면 순서대로 들어간다
    => 꼭 [] 안에 넣어야 ?와 일대일 매칭을 한다

  • bulk insertINSERT INTO Table (칼럼1, 칼럼2) VALUES ?;
    params : 칼럼1과 칼럼2에 들어갈 내용이 하나의 배열로 들어가 있는 이중 배열
    con.query(sql, [params], function(){})
    query()의 두번째 인자로 ?에 들어갈 내용을 넣는다
    이때 ? 때문에 꼭 []안에 params를 넣어줘야 한다
    만약 []안에 안 넣어주면 ?과 params가 일대일 매칭이 안된다

Sprint Review

  1. MVC 패턴
    node.js 상에서 어떻게 쿼리문을 날릴 수 있는지
    model view controller
    3 티어 : client - server - database
    => client는 FE
    => server는 BE

1 티어 아키텍쳐에서는 서버에서 클라이언트까지 다 만들어서 보내는 거 (ssr)

db 영역까지 만약에 서버에서 넣으면

이렇게 너무 코드가 많으면 독립적이지 않고 유지보수하기 어렵다
cf. db.query()는 비동기 : 그러므로 await필요

이렇게 서버에서 모든 것을 다 관리하면 유지보수가 어려우니까 디자인 패턴(하나의 규칙)을 만들었다
정해진 패턴들이 있으므로 맥락을 패턴으로 대신할 수 있다
SW design patterns
: client - server(요청을 보내면 응답을 받는 패턴)
: flux 패턴 - action, dispatch, store
(ex. 이 패턴으로 만든 상태관리 라이브러리 : redux)

MVC
Model : DB
View : UI
Controller

MVVM 등은 MVC에 바탕을 두고 있다

사용자가 바로 model을 거칠 수 있을까?
: no

controller를 꼭 거쳐야 한다
controller로 가기 전에 router를 통해 분기되어야 한다
그 이유 : user가 보내는 요청의 종류가 엄청 다양하기 때문에 router로 나눠주는 거 필요

controller는 비즈니스 로직을 처리한다
비즈니스 로직 : 프로그램이 동작하는 방식을 부르는 말
개발자는 문제 파악, 해결하는 방법 연구
-> 프로그램을 통해 어떤 문제를 해결할 것인가 ? -> 어떻게 하면 문제를 풀 수 있을아 글로 나열 -> 그 ㄹ하나하나가 기능 -> 기능이 비즈니스 로직이 되어 -> 로직은 컨트롤러에 들어간다

cmarket sprint의 과제
1. db에서 아이템 가져오기 [controller]
: get 요청이 들어옴
: router를 통해 items 컨트롤러로 분기
: db에서 테이블 조회 [model]
: 성공 시 데이터를 json으로 전송

  1. db에 주문내역 저장하기
    -POST 요청 들어옴
    -router 통해 orders 컨트롤러로 분기
    -새로운 레코드 만듦 [model]
    : url 파라미터
    : post 요청의 body
    -상태코드 201

  2. db에서 주문내역 가져오기
    -get요청이 들어옴
    -router를 통해 orders 컨트롤러로 분기
    -db에서 테이블 조회 [model]
    : url 파라미터
    -성공 시 데이터를 json으로 전송

코드로 만들기

routes/users.js
const controller = require('./../controllers');
router.get('/:userId/orders', controller.orders.post);
router.post('/:userId/orders', controller.orders.post);

routes/index.js
const usersRouter = require('./users')
router.use('/users', usersRouter)

conrollers/index.js
get : (req, res) => {
const userId = req.params.userId;
res.end();
},
post: (req, res) => {
const userId = req.params.userId;
const {orders, totalPrice} = req.body;

if(!orders || !totalPrice){
return res.sendStatus(400);
}

models.orders.post(
userId,
orders,
totalPrice,
(err, result) => {
if(err){
return res.sendStatus(500);
}else{
return res.sendStatus(201);
)
}
응답이 끝나도록 res.end()를 끝에 넣어준다
내용을 함께 보낼거면 res.json(result)식으로 json()으로 보낸다

models/index.js
orders: {

get : (userId, callback) => {

const queryString = SELECT o.id, o.created_at, o.total_price, i.name, i.price, i.image, oi,order_quantity FROM orders AS o JOIN order_items AS oi ON oi.order_id = o.id JOIN items AS i ON i.id = oi.item_id WHERE o.user_id = ?
const params = [userId]
db.query(queryString, params, (err, result) => {
return callback(err, result)
}
}

post: (userId, orders, totalPrice, callback) => {
//orders PK(id)를 0_i에서 FK로 저장해야 한다
//주문내역 : orders, order_items 활용
//req로 온 user_id, total_price는

삼중이 아니면 변수가 5개라고 생각한다
?에 변수 params 하나를 넣는 거니까 []를 붙인다

const queryString = `
INSERT INTO orders(user_id, total_price) VALUES (?,?)
`;
const params = [userId, totalPrice]
db.query(queryString, params, (err, result) => { 
if(err){ 
	return callback(err)
}else{ 
	const queryString = `
    INSERT INTO order_items(order_id, item_id, order_quantity)
    VALUES ?
    `
    const params = orders.map((order) => [
    result.insertId, 
    order.itemId, 
    order.quantity]) 
    
    //params가 하나의 변수로 인식이 되어야 하므로 [params]로 두번째 인자에 넣어준다
    
    
    //item_id = orders 안에 itemId, order_quantity = orders 안의 quantity 
    order_id는 insert into 했을 때 result 객체 안의 insertId로 얻어올 수 있다 
    : 해당 테이블에 어떤 영향을 주었는지를 result 객체를 통해 살펴볼 수 있다 
    db.query(queryString, [params], (err, result) => { 
    
    }
	
}
}
callback() 

}
}

placeholder ?

: 배열의 파라미터를 받는다
: 배열 안에 있는 순서대로 ?에 넣는다
: ${}형태로 대체해도 되지만 되도록 통일하는 것이 좋다

ex. const query = VALUES (?,?)
//여러 placeholder 쓰는 경우에는 괄호로 감싸야
//이중

const params = [userId, totalPrice]
db.query(queryString, params, (err, result) => {
//?자리에 userId, totalPrice가 들어간다
callback(err, result);
})

삼중배열인데 parameter 자체를 배열로 보내줘야 하기 때문에 []를 씌워서 보냄
?와 []안의 요소의 개수가 같아야
그래서 param이 아니라 [params]로 보내야 한다

실무 관련 책들
1. 클린코드
2. 클린 아키텍쳐
3. 리팩토링

  1. Cmarket Database
profile
노션 : https://garrulous-gander-3f2.notion.site/c488d337791c4c4cb6d93cb9fcc26f17

0개의 댓글