(웹소켓 공부 중)
참고중:
웹소켓과 Socket.IO 코드 예시 - https://velog.io/@delay100/Socket
ws 사용하기 - 이것도 좋음. https://curryyou.tistory.com/348
브라우저 렌더링 전체 과정 https://ghost4551.tistory.com/220
소켓IO - 소켓이 끊어지는 상황 리스트와 room에 관하여 https://socket.io/docs/v3/server-socket-instance/
내일 할 일:
- 스파르타 쇼핑몰 폴더에서 “누가 주문했어요” 소켓 알림창과 “2명이 함께 이 상품을 보고 있어요” 파트를 떼어내보기. 필요하다면 흐름도 작성.
- 메세지 앱에 간단 구현
- 세탁 프로젝트에 “사장님이 픽업하셨습니다” 소켓 알림창 붙여보기
- (희망) 테스트 코드 강의 마저 듣기
- (희망) Socket.IO가 정말 websockets 모듈보다 느린지를 테스트할 수 있는 코드 짜보기
- (희망) 웹소켓으로 인해 추가된 부분도 계층 분리해보기.
기억하고 싶은 오늘의 튜터 팁:
- 파이참 IDE의 네비게이션 단축키를 익혀두는 게 좋다. 코드를 독해에 필수 무기. 가능하면 마우스를 사용하지 않을 수 있도록.
- 필기구로 코드 흐름을 그려봐도 좋다. 그게 느리고 많아서 힘들다면 내가 지금 독해하는 흐름대로 console.log(”1번”) 하는 식으로 순서를 마킹해두는 것도 한 방법이다. 코드 독해는 이런 식으로 꾸준히 하는 것 외에는 특별한 연습 방법이 없다.
- 웹소켓이 원래 서버측과 프론트측을 동시에 살펴보며 약속된 “이벤트명”이 어떻게 되는지를 따져봐야 해서 코드 짜기가 힘든 작업이라고 한다. 거기다 자바스크립트도 언어 특성상 콜백 함수, async 함수 등으로 인해 엄청나게 이리저리 왔다갔다 하는 코드 흐름을 지니고.
따라서 웹소켓을 구현하려면, 이런이런 이벤트명을 사용하겠다 같은 설계를 사전에 튼튼히 해놓는 식으로 접근하자.
오늘은 그 동안 궁금했던 것들을 알아보는 시간을 가졌다.
1. head와 body에 든 script들 - 차이점과 실행 순서
1. head와 body에 든 script들 - 차이점과 실행 순서
script 태그 사이에 위치한 자바스크립트 코드를 자바스크립트 블록이라고 한다.
브라우저의 동작 순서
- HTML을 읽기 시작한다.
- HTML을 파싱한다.
- DOM 트리를 생성한다.
- Render 트리(DOM tree + CSS의 CSSOM 트리 결합)가 생성되고
- Display에 표시한다
⇒ 자바스크립트 script 태그의 해석은 2번과 3번 사이에 오게 된다.
즉, 2번 HTML 파싱 도중 script 태그를 만나면 파싱을 중단하고 JavaScript 파일을 로드하고 JavaScript 코드를 파싱한다. 파싱이 완료되면 그 후에 HTML 파싱이 계속되는 방식이다.
그래서 Display가 지연되는 것을 막고 DOM 트리가 생성되기도 전에 DOM 요소를 조작하는 것을 피하기 위해 Script 태그는 body 태그 최하단에 위치하는 게 좋다고 하는 것이다.
script 로딩 순서 제어하기
- async 속성 활용
<script async src="script.js">
- defer 속성 활용
<script defer src="script.js">
⇒ 1과 2번은 src 어트리뷰트로 외부 JS 파일을 로딩할 경우에만 사용 가능하다. (인라인 JS에서는 사용 불가)
자바스크립트 코드 내에서 로딩순서 제어하기
- onload 이벤트 활용
:
onload
내부의 코드는 문서에 포함된 모든 콘텐츠(images, script, css, ...)가 전부 로드된 후에 실행된다.즉, 4번 단계(CSS까지 파싱해서 Render 트리를 생성) 이후에 실행되는 것이다.
// 방법 1. window.onload = function() { // 실행될 코드 } // 방법 2. <body onload="실행될 코드"></body> // 이 같이 사용될 경우, window.onload 로 지정된 것은 무시된다. // 방법 3. window.addEventListener('load', function() { // 실행될 코드 });
// jQuery 방식 $(window).load(function() { // 실행될 코드 });
// window 객체 말고 원하는 객체에 연결할 수도 있다. document.getElementById('myFrame').onload = function() { // 실행될 코드 }
- DOMContentLoaded 이벤트 활용
: DOM 트리가 준비되었을 때, 즉 3번 단계(HTML과 script가 로드된 시점) 이후에 발생하는 이벤트.
window.addEventListener('DOMContentLoaded', function() { // 실행될 코드 });
// jQuery 방법 $(documenet).ready(function() { // 실행될 코드 });
참고:
https://velog.io/@takeknowledge/script-태그는-어디에-위치해야-할까요
https://jmjmjm.tistory.com/entry/JavaScript-문서의-로드시점-onload-DOMContentLoaded
여기 읽어보아도 좋을 듯 - 브라우저 렌더링 과정 전체가 잘 나와있다 :
2. websockets 모듈은 가볍고 빠르고 Socket.IO는 느리나 세밀한 사용자 조정이 가능하다는데, 이 ‘성능 비교’를 어떻게 하지?
2. websockets 모듈은 가볍고 빠르고 Socket.IO는 느리나 세밀한 사용자 조정이 가능하다는데, 이 ‘성능 비교’를 어떻게 하지?
대략 두 가지 방식을 알아내었다.
1. performance 활용하기
performance.mark('mark-1')
// 성능을 측정할 코드...........
performance.mark('mark-2')
performance.measure('test', 'mark-1', 'mark-2')
console.log(performance.getEntriesByName('test')[0].duration)
=> 이런 항목들을 확인할 수 있다:
detail: null
name: "name"
entryType: "mark"
startTime: 268528.33999999985
duration: 0
2. console.time 활용하기
console.time('test')
for (let i = 0; i < array.length; i++) {
// some code
}
console.timeEnd('test')
=> 브라우저별로 이런 결과를 확인할 수 있다:
크롬: test: 0.766845703125ms
파이어폭스: test: 2ms - timer ended
예시 - 10만 개 길이의 배열을 forEach와 for문 중 어느 것이 더 빠르게 실행할 수 있을지 알아보고 싶을 때 :
// forEach 코드
function testForEach(x) {
console.time('test-forEach')
const res = []
x.forEach((value, index) => {
res.push((value / 1.2) * 0.1)
})
console.timeEnd('test-forEach')
return res
}
// for 코
function testFor(x) {
console.time('test-for')
const res = []
for (let i = 0; i < x.length; i++) {
res.push((x[i] / 1.2) * 0.1)
}
console.timeEnd('test-for')
return res
}
// 성능 테스트 실행
const x = new Array(100000).fill(Math.random())
testForEach(x)
testFor(x)
⇒ 실행 결과:
// 크롬:
test-forEach: 5.589111328125 ms
test-forEach: 5.730712890625 ms
test-for: 4.765869140625 ms
test-for: 6.64892578125 ms
// 파이어폭스:
test-forEach: 4ms
test-forEach: 3ms
test-for: 2ms
test-for: 1ms
참고: https://yceffort.kr/2020/12/measuring-performance-of-javascript-functions#consoletime
3. 코드 성능 비교 사이트 활용하기
네이버나 구글 등에서 제공하는 자바스크립트 코드 성능 비교 사이트를 찾으려고 해보았으나 다 예전에 서비스가 중단되고, 있는 online 사이트들도 뭔가 부실하여 스킵한다.
⇒ 실제 websockets 코드로 Socket.IO 코드에 적용하는 작업을 진행해야 함.
3. Restful API가 아닌 것들은 3계층화를 어떻게 하지?
3. Restful API가 아닌 것들은 3계층화를 어떻게 하지?
예를 들어 render_template으로 반환하는 app.js 쪽의 메소드들이라든지,
이렇게 추상화한 웹소켓 코드들이라든지.
// 추상화 진행 (함수로 분리하기)
io.on("connection", (socket) => {
const { watchBuying, watchByeBye, watchPageChange } = initSocket(socket);
// => 헐 함수도 객체다 라는 사실이 여실히 드러나는 '함수 구조 분해 할당'..!!
// 구조 분해 할당을 위한 호출도 일단 호출이므로 그 안에 든 함수들이 실행된다?
watchBuying();
watchByeBye(); // => 연결되면 그냥 바로 실행해버리는 이게 어떻게 잘 동작하는거지?
watchPageChange();
});
...
// socket.on 부분을 떼어내 추상화한 함수.
// = socket.on의 역할을 맡은 추상 함수.
function watchEvent(eventName, func) {
socket.on(eventName, func);
}
// 연결된 모든 클라이언트에게 메세지를 전송하는 함수.
function sendMessageAll(eventName, data) {
io.emit(eventName, data);
}
- 이런 추상화는 계층화는 아니고, 무슨 작업이라고 하지?
- 왜 이렇게 추상화를 하는 거지? ‘함수 구조 분해 할당’으로 얻는 이점이 뭐지?
- watchBuying() 같은 메소드를 바로 실행했는데 어떻게 ‘메세지가 들어올 때마다’ 소켓 이벤트가 잘 작동되는 거지?
3계층 아키텍처
: 3계층 아키텍처는 애플리케이션을 3개의 논리적 및 물리적 컴퓨팅 계층으로 구성하는 잘 정립된 소프트웨어 애플리케이션입니다.
- 프리젠테이션 계층
- 일반 사용자가 애플리케이션과 상호작용
- 주요 목적은 정보를 표시하고 사용자로부터 정보를 수집하는 것
- 웹 프리젠테이션 계층은 일반적으로 HTML, CSS 및 JavaScript를 사용하여 개발되고, 데스크탑 프리젠테이션 계층은 다양한 언어로 작성됨.
- 웹 개발에서의 예) 사용자가 장바구니에 상품을 추가하거나 지불 정보를 추가하거나 계정을 작성하는 일 처리.
- CLI, HTTP 요청, HTML 처리 등을 담당
- • HTTP 요청 처리 및 HTML 렌더링에 대해 알고 있는 웹 계층
- • 흔히 말하는 MVC (Model / View / Controller) 도 이 계층에 속한다.
- 사용자 인터페이스, 데이터가 처리되는 애플리케이션 계층
- 프리젠테이션 계층에서 수집된 정보가 처리됨
- API 호출을 사용하여 데이터 계층과 통신함(?!)
- 웹 개발에서의 예) 인벤토리 데이터베이스를 조회하여 제품 가용성을 리턴하거나 고객 프로파일에 세부사항을 추가하는 일.
- •프리젠테이션 계층에서 받은 데이터 유효성 검사 및 계산을 포함하는 Business 논리 계층
- 애플리케이션과 연관된 데이터가 저장 및 관리되는 데이터 계층
- 데이터베이스 계층, 데이터 액세스 계층 또는 백엔드라고도 불림
- 애플리케이션이 처리하는 정보가 저장 및 관리되는 곳
- DAO 계층
- • Database / Message Queue / 외부 API와의 통신 등 처리
참고 : https://www.ibm.com/kr-ko/cloud/learn/three-tier-architecture
계층화 원칙
계층화의 핵심은 각 계층은 응집도가 높으면서, 다른 계층과는 낮은 결합도를 가지는데 있다.
이를 위해서는 기본적으로 상위 계층은 하위 계층을 사용할 수 있지만, 하위 계층은 본인의 상위 계층으로 누가 있는지 인식 못하도록 해야한다. 즉, 내가 지금 가지고 있는 클래스와 메소드들이 나를 부른 곳으로 가서 어떻게 쓰일는지 나는 몰라야 한다.
프리젠테이션 계층 분리의 예
예를 들면 기존 NodeJS 프로젝트에서 서버 템플릿 엔진으로 EJS를 사용하고 있던 와중에 React 기반의 SPA로 전환을 해야한다고 가정해보자.
이때 UI를 담당하는 EJS에 얼마나 많은 도메인 로직이 담겨있느냐에 따라 전환에 들어가는 비용이 결정된다.
계층 분리를 잘 해 놓으면 동일한 도메인 논리를 복제하지 않고도 여러 Presentation을 작성할 수 있다. 여러 Presentation은 웹 앱과 모바일 네이티브 앱, 스크립팅용 API 또는 CLI 인터페이스가 있는 웹 앱의 개별 페이지일 수 있다.
계층화를 수행해야 할 때. 나름의 결론
계층 분리를 할 때 이 코드가 도메인 로직인지 아닌지를 구분해내는 것이 실질적인 핵심이라고 할 수 있다.
좀 험하게 예를 결론을 내 보자면, 몸통인 도메인 로직을 그대로 두고 머리(프리젠테이션 계층=UI 페이지)와 다리(데이터 계층=사용 DB)를 쉽게 갈아끼울 수 있겠는가를 생각하며 코드를 나누는 것이다.
내가 작성해놓은 코드에서, ‘여기에서 머리나 다리를 다른 걸로 바꿔끼워야 한다’는 가정을 했을 때 거기에 수반되는 수정이 최대한 적어지도록 코드를 분리하는 것이다.
‘머리나 다리를 바꿔 끼운다’의 예시는 다음이 있다:
- 웹 애플리케이션이라면, CLI로 이 애플리케이션을 실행해야 한다고 가정해본다.
- RDBMS로 저장해야하는 애플리케이션이라면 RDBMS외에 데이터를 XML 파일로 저장 되는 상황도 가정해본다.
참고: https://jojoldu.tistory.com/603
⇒ 정리한 계층 나눔 예시를 참고하여 실제로 app.js에 들어가는 평범한 메소드들도 적합한 계층에 배치하기를 진행해야 함.
그밖에 소소한 코드 조각들
하나의 페이지에, 원하는 버튼에 텍스트 넣고 이벤트 달기
하나의 페이지에, 원하는 버튼에 텍스트 넣고 이벤트 달기
: $(’타켓 요소’).text( ).click( )
// '주문' 페이지의 '$00.00 결제' 버튼이라면:
<script>
$(document).ready(function () {
getSelf(function (user) {
$("#btnOrder").text(`$... 결제`)
.click(function () {
postOrder(user, order); // order[0].goods.goodsId같이 사용
// postOrder = 소켓 창을 띄우는 트리거!
});
...
})
})
</script>
Express로 HTML 파일 렌더링
Express로 HTML 파일 렌더링
(검색어: “express js html render”)
다음과 같은 폴더 구조가 있을 때
// 6.message_sending_app.js에서
app.get("/", (req, res) => {
res.sendFile(__dirname + "/6.message_sending_index.html");
// => __dirname은 프로젝트 플더를 가리킴.
// 다고 했는데 실은 이 app.js가 속한 상위 폴더를 가리키는 듯함.
});
Express로 EJS 파일 렌더링
Express로 EJS 파일 렌더링
다음과 같은 폴더 구조가 있을 때
// 6.message_sending_app.js에서
app.set("view engine", "ejs"); // 렌더링 엔진 모드를 ejs로 설정
app.set("views", __dirname + "/views"); // ejs이 있는 폴더를 지정
app.get("/", (req, res) => {
res.render("6.message_sending_index.ejs"); // index.ejs을 사용자에게 전달
});
Uploaded by N2T