Easy Understanding

Web Framework(2) - 웹 프레임워크의 핵심 구성요소들 본문

Dev

Web Framework(2) - 웹 프레임워크의 핵심 구성요소들

appleg1226 2021. 7. 14. 12:57

저번 포스팅에 이어서 웹 프레임워크의 핵심 구성 요소들에 대해서 알아보려고 한다.

 

이 요소들은 프레임워크들마다 다르게 구현되어 있다.

언어에 영향을 받기도 하고, 내부적인 구현 기술에 영향을 받기도 하기 때문에 다양한 모습들을 가지고 있다.

 

앞으로 프레임워크를 사용할 때 다음의 특징들을 중점적으로 확인한다면,

새로운 프레임워크를 이해하는 데에 있어서 도움이 될 것이다. 

 


1. Bootstrap과 설정(Configuration)

Bootstrap을 찾아보면 보통 Front-end Framework 인 'Bootstrap' 이 검색될 것이다.

나도 이게 가장 익숙했지만, 사실 이 단어의 더 중요한 사용처는 따로 있다.

 

단어의 뜻을 찾아보니 "일반적으로 한 번 시작되면 알아서 진행되는 일련의 과정" 이라고도 한다.

이런 뜻과 완벽히 일치하는지는 모르겠지만,

소프트웨어에서는 보통 '프로그램이 동작하도록 시작하는 과정'을 Bootstrap 이라고 한다.

윈도우를 켤 때 부팅(Booting)한다고 하는데, 이게 Bootstrap의 줄임말이다.

 

모든 웹 프레임워크는 프로그램을 작동시키면,

각종 사용자의 개인 설정을 프레임워크 내부에 초기화 시키는 과정을 거친 뒤,

요청들을 'Listening' 하는 상태가 되며 마무리 된다.

 

프레임워크 별 예제 코드

- Spring(Java)

public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
}

 

- Nest.js(Typescript)

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

 

- Express.js(Javascript)

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

 

- Gin(Go)

func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.JSON(200, gin.H{
      "message": "pong",
    })
  })
  r.Run()
}

 

이 과정들이 굉장히 단순해 보이지만, 

사실상 프레임워크의 굉장히 많은 부분을 건드리는 핵심 코드이다.

 

프레임워크를 시작하는 메서드에 Debugger를 통해서 내부 과정을 들여다보면

정말 긴 과정을 거쳐서 프레임워크가 작동되는 것을 확인할 수 있다.

 

아마 그 과정만 다 이해하더라도 프레임워크의 구조, 설계 등을 반 이상은 이해했다고도 볼 수 있을 것 같다.

 

프레임워크 별 차이

프레임워크마다 Bootstrap 과정에서 다른 점들이 있다면 세부 설정을 반영하는 방식일 것이다.

 

Express.js와 Gin 등의 경우에는 Bootstrap 과정에 Routing까지 포함되어

코드가 길어지는 것을 확인할 수 있지만,

Spring과 Nest.js 의 경우에는 해당 Bootstrap 과정에 설정하는 부분이 적거나 거의 없는 것을 확인할 수 있다.

 

Spring Boot의 경우에는 외부 파일에서 설정을 읽어오는 방식을 선호한다.

아예 설정 파일을 별도의 'applicaiton.yaml or application.properties' 에 작성한 다음

File Reader를 통해서 읽어온다.

 

또한 프로젝트에 선언된 클래스들도 따로 Bootstrap 과정에 불러오지 않는다.

Spring은 ComponentScan 이라는 클래스를 이용하여,

@Component가 붙은 클래스들을 classpath에서 자동으로 불러온다.

 

반면 Express.js, Gin 같은 경우에는,

Bootstrap이 이루어지는 파일에 외부의 클래스/함수들을 불러온 뒤 내부적으로 사용해 주어야 한다.

본인이 원한다면 해당 index.js 파일에 모든 설정 들을 다 넣는 것도 자유롭게 가능하다.

 


2. Network를 담당하는 내부 구현체

대부분의 웹 프레임워크는 자신들이 직접 http 요청을 처리하지는 않는다.

공식 문서, 의존성, 로그 등을 확인해보면 각자 네트워크 요청에 특화된 라이브러리/프레임워크를 사용하는 것을 확인할 수 있다.

 

네트워크 프로그래밍은 굉장히 저수준인 데다가, 추상화의 레벨이 낮아 직접 사용하기가 어렵고 복잡하다.

각 프레임워크들은 이런 복잡한 것들을 우리가 흔히 사용하는 router와 handler 몇 줄만 작성하면 사용할 수 있도록 만들어 준다.

 

보통 웹 프레임워크의 성능이 내부 기술에 종속되는 편이기 때문에, 내부 기술에 대한 이해도 중요한 편이다.

그래서 해당 프레임워크를 사용한다면, 내부적으로 사용된 Network 라이브러리/프레임워크를 확인해보면 좋을 것이다.

- Spring Web: Tomcat(기본), Jetty, Undertow

- Spring Webflux: Netty(기본), Tomcat, Jetty, Undertow, and Servlet 3.1+ containers

- Armeria: Netty

- Nest.js: Express.js 또는 Fastify

 


3. Handler 메서드 적용 방식

위의 1번과 비슷한 내용이지만, 이번에는 Handler의 관점에서 한 번 더 설명해보려고 한다.

 

Spring, Nest.js는 아예 Routing / Handling 부분이 메인 코드와 분리가 되어 있다.

따로 Controller라는 클래스에 선언이 되며,

Routing과 Handling 이 @(어노테이션/데코레이터)를 이용해서 이루어진다.

 

코드를 보면 단순히 선언함으로서 Routing과 Handling을 동시에 이룰 수가 있다. 

직접 Request를 열어서 무엇인가를 할 필요가 없기 때문에, 비즈니스 로직에만 집중할 수 있게 된다.

@Controller 
public class TestController { 
    @GetMapping("/hello/{id}")
    public String hello(@PathVariable("id")string id){ 
        return "hello"; 
    } 
}

 

반면 Express.js 에서는 router 부분에 직접 외부 함수를 import해서 사용해야 한다.

게다가 직접 req, res를 건드려야 하기 때문에 비즈니스 로직과 네트워크 로직을 잘 분리해서 개발해야 한다.

app.post('/', function (req, res) {
  const result = someFunction(req);
  res.send('Got a POST request');
});

 


4. Security

대부분의 웹 프레임워크는 각자 보안과 관련된 기능들을 내장하고 있다.

CORS, Authentication, Authorization 같은 기본적인 기능부터, 다양한 기능들을 제공해 준다.

 

Security 관련된 내용은 웹 어플리케이션에서는 꽤 큰 부분을 차지하기 때문에,

이 부분은 거의 모든 어플리케이션에 반드시 구현이 되어야만 한다.

 

그런데 사실 모든 프레임워크에 이 기능이 들어있는 것은 아니다.

사실 Express.js에는 Security 관련된 기능이 없기 때문에,

Middleware에 다른 보안 라이브러리들을 사용하여 Security와 관련된 로직을 적용해야 한다.

 


5. 기타 추가 기능들

미니멀리즘을 추구하는 프레임워크들에는 내장 기능들이 굉장히 적을 것이다.

그렇지만 많은 프레임워크들은 각자의 목적에 맞게 다양한 추가 기능들을 가지고 있다.

또한 특정 기능들을 다른 프레임워크들과의 차별점으로 내세우기도 한다.

 

Spring은 방대한 호환성을 갖고 있으며,

Django는 자동으로 생성되는 Admin Page가 장점이다.

Nest.js에서는 GraphQL을 지원하며,

Gin은 언어가 Golang이라는 점(?),

Armeria는 gRPC, thrift를 지원하거나, Spring과 함께 사용할 수 있다는 특징이 있다.

 

이런 특징들을 염두해두고 프레임워크를 사용하면 될 것이다.