서비스와 컨트롤러

Taesoo Kim·2024년 6월 10일

그리고 왜 서비스에 DI, IoC 이런 개념이 들어왔을까??
먼저 서비스가 의존성 주입을 통해 만들어지는지 확인해 보자.

결합도가 높다?

보통의 디자인 패턴은 결합도를 낱추고 유지보수를 편하게 하기 위해서 등장한다고 한다.
근데 도대체 결합도가 높은 코드가 뭐길래 이게 이렇게 중요한 것인가?

하나의 변경이 수정의 파도를 몰고온다.

프로젝트를 진행하면서 꼭 겪었던 일이다. 이 코드를 수정하면, 저 코드도 무조건 수정해야 하는 경우가 종종 있었다. 우린 이런 경우를 '결합도가 높다' 라고 표현을 한다.

예를들자면,

class RealEmailService { 
	sendEmail(email: string, content: string): void { 
	console.log(`Sending email to ${email} with API key ${this.apiKey}: ${content}`); 
	} 
}

이런 서비스 클래스를 일전에 만들었었다.

그런데 이 클래스에서 예를 들어 하나의 변수가 필요해 진 상황이다.

그럼 우리는 당연히,

class RealEmailService { 
	private apiKey: string; 
	constructor(apiKey: string) {
		this.apiKey = apiKey; 
	} 
	sendEmail(email: string, content: string): void { 
	console.log(`Sending email to ${email} with API key ${this.apiKey}: ${content}`); 
	} 
}

이런 코드를 짤텐데, 평소와 같이 아무생각 없이 코드를 짜면 이런 형태로 짜게 될 것이다.

class AuthService { 
	private emailService: RealEmailService; 
	constructor() { 
		this.emailService = new RealEmailService('my-api-key'); // 생성자 인자 추가 
	} 
	login(email: string): void { 
		this.emailService.sendEmail(email, 'Login successful!'); 
	} 
}

이런식으로 말이다. 문제점이 조금 보이나? 매번 새로운 생성자에 필요한 인자들이 변경된다면, 우리는 일일히 코드를 쫒아다니면서 수정을 해야한다. 이런 상태를 결합도가 높다라고 표현을 한다.

그럼 이것을 어떻게 줄일까?

import { Controller, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';
import { EmailService } from './email.service';

@Controller('auth')
export class AuthController {
  constructor(
    private readonly userService: UserService,
    private readonly emailService: EmailService,
  ) {}
}
import { Controller, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';
import { EmailService } from './email.service';

@Controller('auth')
export class AuthController {
  constructor(
    private readonly userService: UserService,
    private readonly emailService: EmailService,
  ) {}

  @Post('login')
  async login(@Body('email') email: string): Promise<string> {
    const user = this.userService.getUser(email);
    this.emailService.sendEmail(user.email, 'Login successful!');
    return 'Login email sent';
  }
}

이렇게 컨트롤러의 생성자의 인자로 서비스를 넘겨주고, 하나의 모듈에서 서비스 생성자에 필요한 인자들을 통합해 관리하면 결합도를 낮추면서, 코드의 유지보수성을 올릴 수 있게 된다.

그렇담 서비스에 담아야 하는게 무엇인가?

가장 중요한건 컨트롤러와의 차이일 것이다. 가장 헷갈리는 부분이 왜 컨트롤러와 서비스를 나누어 개발하냐인데, 우리는 이것을 Seperation fo Concerns라고 부른다. 즉, 같은 요청에도 컨트롤러와, 서비스가 하는 일을 나누어 생각하는 것이다.

그래서, 보통 우리가 컨트롤러를 만들때 고려하는 점은,

  • 클라이언트로부터 들어오는 요청을 처리한다.
  • 해당 요청을 처리하기위한 서비스 메서드를 적절히 호출한다.
  • 서비스로부터 받은 결과를 보낸다.

정도가 있을 것 같다. 즉, 컨트롤러는 어떤 요청을 비즈니스 로직과 연결시키는 다리라고 생각하면 된다.

반대로, 서비스는 우리의 비즈니스 로직을 실현하는 곳이다. 주로

  • 데이터베이스와 상호작용을 하고
  • 데이터 처리, 계산, 로직을 수행하고
  • 통신과 잡다한 내용은 제외하고, 순수하게 우리 서비스에 필요한 로직을 작성

하는 곳이다.

profile
뭔 생각을 해. 그냥 하는 거지 뭐

0개의 댓글