ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [HOW] Node.js 모듈 시스템
    Server/Node.js 2022. 10. 30. 13:15

    [이전 글 참고]

    1. [WHAT] Node.js란 무엇인가?  : https://growth-msleeffice.tistory.com/51

    2. [WHY] Node.js 기반으로 API 서버를 구축하면 좋은 이유 : https://growth-msleeffice.tistory.com/52

     

    3. [How] Node.js 모듈 시스템

    3.1 Custom 모듈 만들기

    모듈도 LEGO 블록과 마찬가지로 코드의 조각으로, 여러 모듈이 조합되어 하나의 소프트웨어를 이루게 됩니다. 즉, 프로그램을 만들 때 코드를 잘 모듈화해서 만들게 되면, 유지보수하기 쉬운 구조로 시스템을 만들 수 있습니다.

    결국에는 모듈화가 잘 된 코드는 재사용성과 확장성을 높여 새로운 기능을 개발하거나 유지보수 할 때, 전체적인 비용을 감소시키고 개발팀의 생산성을 증대시킬 수 있습니다. 그만큼 모듈화는 지속 가능한 소프트웨어 개발에 있어서 중요한 부분이였습니다.

    But, 초기에 자바스크립트는 언어 차원에서 모듈을 위한 명시적인 키워드나 패키징 정책을 지원하지 않았기 때문에 모듈화와는 거리가 먼 언어였다. 물론 ES6가 등장하면서는 자바스크립트의 모듈화가 가능해졌다.

     

    CommonJS 라는 JavaScript를 브라우저에서뿐만 아니라, 서버사이드 애플리케이션이나 데스크톱 애플리케이션에서도 사용하려고 조직한 자발적 워킹 그룹에서 가장 의미있는 결과를 만들어 냈습니다.

    이 그룹은 JavaScript를 범용적으로 사용하기 위해 필요한 '명세(Specification)'를 만들었고, Node.js 모듈 시스템도 CommonJS의 Module specification을 따르고 있습니다.

    • CommonJS
      • 참고 : https://velog.io/@leobit/CommonJS
      • 어떻게 모듈을 정의할 것인가, 어떻게 사용할 것인가에 대한 것이 모듈 명세이다.
      • 모듈화에 필요한 세 가지
      1. 스코프 : 첫 번째는 모든 모듈은 자신만의 독립적인 실행 영역이 있어야 한다는 것입니다. 이 말은 전역변수(Global variable)와 지역변수(Local variable)를 분리하는 것을 의미합니다. 서버 사이드 JavaScript의 경우에는 파일마다 독립적인 파일 스코프가 있기 때문에 파일 하나에 모듈 하나를 작성하는 방법으로 독립적인 실행 영역을 만듭니다.
      2. 정의 : 두 번째는 모듈을 외부에서 사용할 수 있도록 공개하는 것인데, Node.js (CommonJS의 모듈 명세를 따르는)에서는 module.exports 또는 exports라는 전역 객체를 이용해서 정의합니다.
      3. 사용 : 마지막으로 모듈을 사용하는 영역에서는 require('모듈이름') 함수를 이용해서 모듈을 사용합니다. node_modules에 저장되어 있는 모듈을 불러올 수 있습니다.
      4. 다른 파일을 불러오기 위해 require를 사용할 수도 있다.
    // 모듈 불러오기
    const fs = require('fs')
    
    // 다른 js 파일 불러오기
    
    // script1.js
    const module2 = require('./script2.js')
    
    // script2.js
    console.log('this is module 2');

    3.1.1 exports 객체를 사용하여 만드는 방법

    // [exports 사용 시]
    // script1.js
    const module2 = require('./script2.js');
    console.log(module2.introduce());
    
    // script2.js
    exports.introduce = 'this is module2'
    
    
    // [module.exports 사용 시]
    // script1.js
    const module2 = require('./script2.js');
    console.log(module2); // 'this is module2'
    
    // script2.js
    const introduce = 'this is module2';
    module.exports = {introduce}

    - exports 사용 시 : script2 모듈에 exports 객체의 메소드로 introduce를 정의, script1 파일에서 require 함수 호출 결과 script2 모듈은 객체 형태로 반환되어 module2 변수에 담긴다.

     

    - module.exports 사용 시 : exports 객체는 여러 개의 속성과 메소드를 정의할 수 있지만 module.exports는 하나의 객체만 할당할 수 있다. 

     

    exports는 module.exports를 참조하고 있기 때문에 exports는 module.exports와 같다고 보아도 무방합니다. 공식문서에서도 exports는 module.exports의 축약형(shortcut)이라고 언급되어 있습니다. 그래도 혼란스럽다면, module.exports를 사용합니다.

    3.2 File system 모듈 사용하기

    file system 모듈은 Node.js에서 파일을 읽고 쓸 수 있는 기능이다. require 할수에서 fs 모듈을 호출하면 fs 객체에 우리가 사용할 수 있는 많은 함수들이 담겨 반환된다. 반환된 객체를 변수에 담아서 데이터를 읽고, 쓰는 기능이 필요할 때 마다 사용할 수 있다.

    3.2.1 동기적 방식(Sychronously)

    //privateInfomation.txt
    
    Name : Harry Potter
    Age : 22
    
    // CASE 1. Blocking, Synchronous
    // syncFileSystem.js
    
    const fileSystem = require("fs");
    
    //Read File (Before)
    const privateInformation = fileSystem.readFileSync("./privateInformation.txt", "utf-8");
    console.log(`===== Before written ===== \n${privateInformation}`);
    
    //Write File
    const additionalInfo = `${privateInformation}PhoneNumber : 010-7777-0000\nModified at : ${Date.now()}`;
    fileSystem.writeFileSync("./privateInformation.txt", additionalInfo);
    console.log("===== Successfully File written! ===== \n");
    
    //Read File (After)
    const updatedPrivateInformation = fileSystem.readFileSync("./privateInformation.txt", "utf-8");
    console.log(`===== After written ===== \n${updatedPrivateInformation}`);
    
    //추가적으로 실행되어야 하는 코드 (동기적 실행을 이해하기 위해서 추가된 코드 입니다.) (1)
    console.log("추가적으로 실행되어야 하는 코드 1"); 
    console.log("추가적으로 실행되어야 하는 코드 2");
    console.log("추가적으로 실행되어야 하는 코드 3");
    console.log("추가적으로 실행되어야 하는 코드 4");

    - readFileSync("파일의 경로", "인코딩 방식") : 파일에 접근하여 데이터를 읽는 메소드

    - writeFileSync("파일의 경로", "인코딩 방식") : 파일에 접근하여 데이터를 쓰는 메소드

     

    $ node syncFileSystem.js
    
    ===== Before written =====
    Name : Harry Potter
    Age  : 22
    
    ===== Successfully File written! =====
    
    ===== After written =====
    Name        : Harry Potter
    Age         : 22
    PhoneNumber : 010-7777-0000
    Modified at : 1653178357269
    
    ===== 추가적으로 실행되어야 하는 코드 =====
    추가적으로 실행되어야 하는 코드 1
    추가적으로 실행되어야 하는 코드 2
    추가적으로 실행되어야 하는 코드 3
    추가적으로 실행되어야 하는 코드 4

    여기서 Sync라는 용어가 등장하는데 이는 synchronic(동기)을 의미합니다. 즉 readFileSync() 함수는 동기적으로 실행된다는 의미입니다. 그렇기 때문에 함수가 호출되어 실행된 후에 값을 반환할 때까지 기다린 후에 다음 코드를 실행하게 됩니다. 위와 같이 코드가 순차적으로 실행되는 것을 확인할 수 있습니다.

     

    또한 privateInfomation.txt에 핸드폰 번호와 수정 시간이 새롭게 추가된 것을 확인할 수 있습니다.

    이처럼 Node.js는

    브라우저와 다르게 운영체제에서 기본적으로 제공하는 주된 서비스(ex. 파일 I/O 작업)들에 바인딩할 수 있는 기능을 제공해준다

    는 것을 확인했습니다.

     

    하지만, 동기적 실행으로 파일 시스템에 접근해서 파일을 읽어오는 방식은 일반적으로 좋지 않습니다. 

    그 이유는 만약에 위에 예제와 같이

    파일을 읽은 후에 추가적으로 실행되야 하는 코드들이 있다면, 파일을 읽는 작업 동안 실행이 지연되기 때문입니다.

     

    그래서, 앞에서 살펴보았던 것처럼(Node.js 기반으로 API 서버를 구축하면 좋은 이유) 

     

    Node.js는 이러한 블로킹 문제를 해결해주기 위해

    libuv 오픈소스를 활용해 비동기 I/O 작업이 가능하도록 되어있다고 했습니다.

     

    3.2.2 비동기(Asynchronously)

    // CASE 2. Non-blocking, Asynchronous
    // asyncFileSystem.js
    
    const fileSystem = require("fs");
    
    // Read File (Before)
    fileSystem.readFile("./privateInformation.txt", "utf-8", (err, data1) => {
      console.log(`===== Before written ===== \n${data1}`);
    
      const additionalInfo = `${data1}PhoneNumber : 010-7777-0000\nModified at : ${Date.now()}`;
    
      // Write File
      fileSystem.writeFile("./privateInformation.txt", additionalInfo, "utf-8", (err, data2) => {
          console.log("===== Your file has been written =====");
    
          // Read File (After)
          fileSystem.readFile("./privateInformation.txt", "utf-8", (err, data3) => {
            console.log(`===== After written ===== \n${data3}`);
          });
        }
      );
    });
    
    //추가적으로 실행되어야 하는 코드
    console.log("\n===== 추가적으로 실행되어야 하는 코드 =====");
    console.log("추가적으로 실행되어야 하는 코드 1");
    console.log("추가적으로 실행되어야 하는 코드 2");
    console.log("추가적으로 실행되어야 하는 코드 3");
    console.log("추가적으로 실행되어야 하는 코드 4");

     

    - readFile("파일의 경로", "인코딩 방식", callback) : 비동기적으로 파일에 접근하여 데이터를 읽는 메소드, 콜백함수는 비동기 실행이 완료 되고 호출된다.

    - writeFile("파일의 경로", "인코딩 방식", callback) : 비동기적으로 파일에 접근하여 데이터를 쓰는 메소드, 콜백함수는 비동기 실행이 완료 되고 호출된다.

     

    //zsh
    $ node asyncFileSystem.js
    
    ===== 추가적으로 실행되어야 하는 코드 =====
    추가적으로 실행되어야 하는 코드 1
    추가적으로 실행되어야 하는 코드 2
    추가적으로 실행되어야 하는 코드 3
    추가적으로 실행되어야 하는 코드 4
    
    ===== Before written =====
    Name : Harry Potter
    Age  : 22
    
    ===== Your file has been written =====
    
    ===== After written =====
    Name        : Harry Potter
    Age         : 22
    PhoneNumber : 010-7777-0000
    Modified at : 1653192110477

    비동기 함수가 실행되면 작업이 끝날때까지 기다리는 것이 아니라 백그라운드에 비동기 작업을 위임해주기 때문에 File I/O 작업이 오래 걸리더라도 추가적으로 실행되어야 하는 코드들에 영향을 주지 않는다.

    'Server > Node.js' 카테고리의 다른 글

    Error Handling  (0) 2022.11.20
    [WHY] Node.js 기반으로 API 서버를 구축하면 좋은 이유  (0) 2022.10.30
    [WHAT] Node.js란 무엇인가?  (0) 2022.10.30

    댓글