사내 서비스는 IoT 엣지 디바이스와 통신하는 Gateway 모듈을 갖고 있다.
이 gateway는 LoRa, Socket.io, MQTT 등 다양하나, 대개 지역구당 1개씩 들어가는 서버 구조이기 때문에,
보통 복수의 경우로 들어가기보다는 LoRa만, 혹은 Socket.io만, 혹은 MQTT만 (물론 아예 없는 것은 아니다) 들어간다.
다만 서버 측에서는 각 통신 방식에 대해 대응할 필요가 있다보니 각각의 Gateway 클래스를 만들어 통신하는 구조를 가지고 있었는데,
최근 Socket.io 통신 구조가 신규 IoT 디바이스가 도입되어 통신 프로토콜이 분리됨에 따라 문제에 대응할 필요가 생겼다.
1. 기존의 방식
비슷한 경우가 mqtt 통신 클래스를 구축하면서 발생했었다.
Socket.io로만 통신하는 서버인데 불필요하게 mqttClient를 생성해야 하는 경우가 생겼기 때문이다.
이때는 그나마 간단했던 것이 client 객체 자체를 생성할 때는 브로커 주소와 설정값들이 필요하기 때문에,
해당 설정을 env에 넣어두고 설정값의 유무를 검증해 객체를 생성하지 않는 방향으로 접근했다.
2. 문제 발생
문제는 Socket.io 게이트웨이 클래스가 신규 IoT 엣지 디바이스 도입에 따라 두 개의 클래스로 분리되면서 생겼다.
(기존에 엣지 디바이스의 유지보수를 고려해 abstract class는 따로 구현해두었으나,
이번에는 송수신 원리 자체가 아예 바뀌어 오히려 리스코프 치환 원칙에 위배된다고 생각해 별도 클래스로 분리하기로 하였다.)
Nest.js의 경우 Socket.io 구현을 위해 데코레이터를 지원한다.
@WebSocketGateway(8008, globalConfig)
이 경우 문제가 몇 가지 있었는데,
- 데코레이터는 configService 등을 삽입하기에 부적절하다.
- 데코레이터를 optional로 처리하는 로직은 데코레이터를 래핑하지 않는 한 어렵다.
- 해당 데코레이터는 포트를 뺀다고 해서 클래스가 생성되지 않는 것은 아니다
(서버와 동일한 포트로 동작해버려서 기존 socket.io 게이트웨이와 충돌해버린다).
때문에 클래스를 동적으로 생성하기 위한 방안을 모색했다.
클래스를 팩토리로 생성하기
기존 클래스의 구조를 최대한 바꾸지 않으면서 동적 생성 기능을 작업하기 위해 다음과 같은 전략을 취하였다.
- 우선 팩토리 함수를 따로 만든 뒤, 기존의 Socket.io 게이트웨이 클래스를 일종의 추상 클래스로 사용한다.
- 해당 팩토리 함수 내에서 유효 검증을 진행한 뒤 기존 게이트웨이 클래스를 extends한 신규 클래스를 반환하는 형태로 함수를 작성한다.
- 기존 게이트웨이 클래스에 있던 @Injectable, @WebSocketGateway를 신규 클래스에 붙인다.
- Module에서 해당 팩토리 함수를 호출하고 provider array에 추가한 뒤 @Module 데코레이터 안의 provider 인자에 넣어준다.
작업 구조는 다음과 같다.
(자세한 구조는 전부 걷어냈기 때문에 참고용으로만 보는 것을 권장)
// 이곳에 있는 데코레이터들을 전부 제거한다.
export class SocketIOGateway
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
export function maybeCreateSocketIOGateway(): Type<SocketIOGateway> | null {
const socketIOPort = Number(process.env.SOCKET_IO_PORT);
// 포트가 존재하지 않는 경우 클래스 자체를 생성하지 않음.
if (!socketIOPort) {
return null;
}
@WebSocketGateway(socketIOPort, globalConfig)
@Injectable() // 이곳에 Injectable을 넣으면 동적 관리가 가능하다
class ExGatewayImpl extends SocketIOGateway {}
return ExGatewayImpl;
}
const provider: Provider[] = [
// SocketIOGateway, // 기존에 있던 Socket.io 게이트웨이를 걷어낸다.
];
// 팩토리 함수를 사용해 Socket.io Gateway 함수를 검증해 추가한다.
// Module 데코레이터 안에선 호출이 안 되므로 이런 식으로 push 해 넣어준다.
const SocketIOGateway = maybeCreateSocketIOGateway();
if (SocketIOGateway) {
provider.push(SocketIOGateway);
}
@Module({
imports: [
// ...
],
controllers: [],
providers: provider,
})
export class GatewayModule {}
env의 속성 값 유무를 각각 실행시킨 뒤 netstat
을 사용해 해당 클래스가 동작 중인지를 검증하면 된다.
이러면 불필요한 포트 점유 없이 클래스를 동적으로 관리할 수 있게 된다.
또한 클래스 자체의 구조 변경은 없기 때문에 의존성이 있는 외부 모듈을 변경할 필요도 없다.
'DEVELOPMENT > NODEJS' 카테고리의 다른 글
Jest config (0) | 2022.11.07 |
---|---|
AES with Nest.js + BASE64 (0) | 2022.09.26 |
TypeORM where (in 0.3.x) (2) | 2022.09.25 |
NestJS :: param과 URI의 우선순위 문제 (0) | 2022.04.19 |
Node.js BASE64로 AWS S3에 이미지 업로드하기 (0) | 2022.04.17 |