포트 8080이 이제 Vercel Sandboxes에서 사용 가능합니다

발행: (2026년 5월 29일 AM 09:01 GMT+9)
1 분 소요

Source: Vercel Blog

포트 8080이 열려 있는 샌드박스 만들기

이 포트는 컨트롤러 포트로 사용되었으며, 이제는 포트 23456으로 이동되었습니다.

import { Sandbox } from "@vercel/sandbox";

const sandbox = await Sandbox.create({
  ports: [8080],
});

await sandbox.runCommand({
  cmd: "python3",
  args: ["-m", "http.server", "8080", "--bind", "0.0.0.0"],
  detached: true,
});

console.log(`url: ${sandbox.domain(8080)}`);
0 조회
Back to Blog

관련 글

더 보기 »

엔티티 생성과 업데이트에 단일 Builder 패턴을 사용하는 것이 좋은 관행일까?

문제 Node.js TypeScript 백엔드에서 Builder 패턴을 구현하여 Permission 객체를 인스턴스화한 뒤 이를 repository 레이어에 전달하려고 합니다. 저는 Permission 객체가 여러 선택적 속성을 가질 수 있기 때문에 Builder를 사용해 가독성을 높이고 싶었습니다. 시도한 내용 ```typescript class PermissionBuilder { private permission: Permission; constructor() { this.permission = new Permission(); } setId(id: string): this { this.permission.id = id; return this; } setUserId(userId: string): this { this.permission.userId = userId; return this; } // … 기타 setter 메서드들 … build(): Permission { return this.permission; } } ``` 위와 같이 Builder를 만든 뒤, 서비스 레이어에서 다음과 같이 사용했습니다. ```typescript const permission = new PermissionBuilder() .setId('123') .setUserId('456') .setRead(true) .setWrite(false) .build(); await permissionRepository.save(permission); ``` 하지만 테스트를 실행하면 `permissionRepository.save`가 `undefined`를 반환하고, 저장된 레코드가 데이터베이스에 나타나지 않습니다. 또한, `PermissionBuilder`를 사용하지 않고 직접 `new Permission()`으로 객체를 만들면 정상적으로 저장됩니다. 원인 분석 1. **Builder가 실제로 새로운 인스턴스를 반환하지 않음** `build()` 메서드가 현재 내부에 보관하고 있는 `this.permission` 객체를 그대로 반환하고 있습니다. 이 객체는 `new Permission()`으로 만든 초기 객체이며, 이후 setter 메서드들이 원본 객체의 속성을 직접 수정합니다. TypeScript에서는 객체가 **참조에 의해 전달**되기 때문에, `PermissionBuilder` 인스턴스가 재사용될 경우 이전에 설정한 값이 남아 있을 수 있습니다. 2. **불변성을 보장하지 않음** Builder 패턴의 일반적인 구현은 `build()` 단계에서 **새로운 복사본**을 만들어 반환합니다. 이렇게 하면 Builder 자체는 재사용 가능하고, 이미 `build()`된 객체는 더 이상 변경되지 않게 됩니다. 현재 구현은 동일한 인스턴스를 계속 반환하므로, 테스트 환경에서 여러 번 `build()`를 호출하면 이전 테스트의 상태가 섞일 위험이 있습니다. 3. **Repository 레이어가 기대하는 형태와 불일치** `permissionRepository.save`는 **완전한 엔티티**(예: TypeORM 엔티티) 인스턴스를 기대합니다. Builder가 반환하는 객체가 엔티티 메타데이터(예: `@PrimaryGeneratedColumn`, `@Column` 데코레이터)와 연결되지 않은 **plain 객체**라면, ORM이 이를 무시하거나 `undefined`를 반환할 수 있습니다. 해결 방안 ### 1. `build()`에서 새로운 객체를 반환하도록 수정 ```typescript class PermissionBuilder { private readonly data: Partial<Permission> = {}; setId(id: string): this { this.data.id = id; return this; } setUserId(userId: string): this { this.data.userId = userId; return this; } // … 기타 setter 메서드들 … build(): Permission { // Permission 엔티티의 생성자를 사용하거나 Object.assign 로 복사 return Object.assign(new Permission(), this.data); } } ``` - `Partial<Permission>`을 사용해 아직 완전하지 않은 상태를 저장하고, `build()` 시점에 **새로운 Permission 인스턴스**를 만든 뒤 속성을 복사합니다. - 이렇게 하면 Builder 자체는 상태를 유지하지만, 반환된 객체는 독립적이며 ORM이 정상적으로 인식합니다. ### 2. Builder를 **불변**하게 설계 ```typescript class PermissionBuilder { private readonly data: Readonly<Partial<Permission>>; private constructor(data: Partial<Permission> = {}) { this.data = data; } static create(): PermissionBuilder { return new PermissionBuilder(); } setId(id: string): PermissionBuilder { return new PermissionBuilder({ ...this.data, id }); } setUserId(userId: string): PermissionBuilder { return new PermissionBuilder({ ...this.data, userId }); } // … 기타 setter 메서드들 … build(): Permission { return Object.assign(new Permission(), this.data); } } ``` - 각 setter가 새로운 Builder 인스턴스를 반환하므로 **동시성 문제**와 **테스트 간 상태 누수**를 방지합니다. ### 3. Repository에 전달하기 전에 엔티티 인스턴스인지 확인 ```typescript const permission = PermissionBuilder.create() .setId('123') .setUserId('456') .setRead(true) .setWrite(false) .build(); if (!(permission instanceof Permission)) { throw new Error('Built object is not a Permission entity'); } await permissionRepository.save(permission); ``` - 타입 가드로 잘못된 객체가 전달되는 것을 사전에 차단합니다. ### 4. 테스트 환경 정리 - 각 테스트 케이스 시작 전에 **Builder 인스턴스를 새로 생성**하거나 `PermissionBuilder.create()`를 호출합니다. - 테스트 후에는 데이터베이스를 **트랜잭션 롤백**하거나 `afterEach` 훅에서 `permissionRepository.clear()`를 호출해 상태를 초기화합니다. 예시 코드 (전체 흐름) ```typescript // permission.builder.ts export class PermissionBuilder { private readonly data: Partial<Permission> = {}; setId(id: string): this { this.data.id = id; return this; } setUserId(userId: string): this { this.data.userId = userId; return this; } setRead(read: boolean): this { this.data.read = read; return this; } setWrite(write: boolean): this { this.data.write = write; return this; } // … 추가 setter … build(): Permission { return Object.assign(new Permission(), this.data); } } // permission.service.ts async function createPermission(dto: CreatePermissionDto) { const permission = new PermissionBuilder() .setId(dto.id) .setUserId(dto.userId) .setRead(dto.read) .setWrite(dto.write) .build(); return await permissionRepository.save(permission); } ``` 결론 - 현재 Builder 구현은 **같은 인스턴스를 재사용**하고 있어 ORM이 기대하는 완전한 엔티티 객체를 제공하지 못합니다. - `build()` 단계에서 **새로운 Permission 인스턴스를 반환**하도록 수정하고, 가능하면 **불변 Builder** 패턴을 적용하면 테스트 간 상태 오염을 방지하면서도 가독성을 유지할 수 있습니다. - 마지막으로, Repository에 전달하기 전에 반환 객체가 실제 엔티티인지 검증하고, 테스트 환경을 깨끗하게 정리하면 `undefined` 반환 문제를 해결할 수 있습니다.