Naviary Logo

브리딩 관리 시스템 개발기 #1: TypeScript를 선택한 이유

브리딩 시스템 개발을 위한 언어 선택 과정. 세 가지 기준으로 언어를 검토하고 선택한 과정을 정리했습니다.

n
naviary
10 min read
TypeScript언어 선택DDD기술 스택

들어가며

브리딩 관리 시스템 개발을 시작하며 가장 먼저 결정해야 할 것은 언어 선택이었다.

당시 TypeScript를 4년간 사용했지만, 새로운 언어를 탐색하고 싶었다. Go, Kotlin, Erlang 계열을 검토했고, 결국 TypeScript로 돌아왔다.

언어 선택 기준

브리딩 센터는 초기 투자 비용이 크다. 서버 비용을 최소화해야 하는 상황에서 언어 선택 기준은 다음과 같았다:

  1. 가벼움 - 서버 비용 최소화
  2. 개발 생산성 - 1인 개발
  3. 도메인 표현력 - 복잡한 브리딩 로직의 명확한 표현

후보군 검토

Go

언어적인 면에서

Go는 가볍고 빠르며 배포가 간단하다. 하지만 Go는 가벼운 네트워크 I/O 시스템에 적합하다고 생각했다. 함수형 표현이나 객체지향 프로그래밍 지원이 부족해서, 앵무새 브리딩이라는 복잡한 현실 세계 문제를 명확한 도메인 로직으로 표현하기에는 내 실력이 부족하다고 판단했다.

go
type Gender string
const (
  Male Gender = "MALE"
  Female Gender = "FEMALE"
  UnknownGender Gender = "UNKNOWN"
)

type ParrotStatus string
const (
  Feeding ParrotStatus = "FEEDING"
  Weaned ParrotStatus = "WEANED"
  Breeder ParrotStatus = "BREEDER"
)

type Parrot struct {
  Aggregate // audit column이 있는 추상클래스 같은 느낌을 쓰려면 이런식으로 embed해야한다.
  ID string
  Name string
  Gender Gender
  Status ParrotStatus
  ImportedAt time.Time
}

func (parrot *Parrot) wean() error {
  if parrot.Status != Feeding {
    return fmt.Errorf("parrot is not feeding")
  }
  parrot.Status = Weaned
  return nil
}

go로 간단하게 구현해본 Parrot model

ORM

Go의 ORM은 대부분 쿼리 빌더나 로우 쿼리 중심이다. 물론 Gorm의 경우 그런 문제에서는 벗어나다고는 하나, 지원되는 기능이 여태까지 내가 사용했던 ORM들에 비해 다소 부족하다고 느꼈다.

브리딩 시스템은 현실세계의 도메인들간 복잡한 관계가 많아서 생산성 측면에서 go의 ORM은 나에게는 부적절하다고 판단했다.


Kotlin

언어적인 면에서

Kotlin은 세련된 언어라고 생각한다. 표현력이 뛰어나고 문법이 현대적이다. 비록 내가 자바와는 친하지 않지만 kotlin으로 프로젝트는 한두번 해봤기 때문에 익숙하기도 하고 여러 언어들을 봐도 코틀린은 잘 만든 언어라고 생각한다. 물론 Typescript와는 다르게 조금 더 객체지향적인 면이 있기 때문에 빠르게 구현하는 생산성 측면에서는 조금 떨어진다고 생각한다.

kotlin
enum class Gender {
    MALE, FEMALE, UNKNOWN
}

enum class ParrotStatus {
    FEEDING, WEANED, BREEDER
}

class Parrot(
    val id: String,
    val name: String,
    val gender: Gender,
    var status: ParrotStatus,
    val importedAt: LocalDateTime
) : Aggregate() {
    fun wean() {
        if (status != ParrotStatus.FEEDING) {
            throw IllegalStateException("parrot is not feeding")
        }
        status = ParrotStatus.WEANED
    }
}

kotlin으로 간단하게 구현해본 Parrot model

JVM

JVM은 메모리를 많이 사용한다. 빌드타임도 느리고 작은 서버에서 이런저런 다른 도구들과 같이 실행하기엔 부담스럽다. 초기 비용이 많이드는 브리딩 센터에서 언어 문제로 인해 서버를 키우는 문제는 겪고싶지 않았다. Spring 생태계도 나에게는 조금 어려웠다. 물론 쓰는데에는 스프링처럼 정해져있는 것이 좋을지는 모르겠으나, 추상화가 너무 잘 되어있어 내가 원리를 이해하기에는 어렵다고 생각했다. 물론 브리딩 센터를 운영하게되면 밤에 시간이 꽤 남기 때문에 그 시간에 공부를 하면 모르겠지만... :)


Gleam

언어적인 면에서

Erlang VM은 견고한 시스템을 만들기 좋다고 알려져 있다. 실제로 Gleam으로 간단한 프로젝트를 만들어봤을 때 간단한 문법과 함수형 언어의 특성을 가지고있어 개발 생산성이 좋다고 생각했다. 특히 패턴 매칭과 함수형 프로그래밍 스타일이 마음에 들었다. 타입스크립트를 주로 사용하던 나에게 gleam의 패턴매칭은 혁신적으로 보였다.

gleam
pub type Gender {
  Male
  Female
  Unknown
}

pub type ParrotStatus {
  Feeding
  Weaned
  Breeder
}

pub type Parrot {
  Parrot(
    id: String,
    name: String,
    gender: Gender,
    status: ParrotStatus,
    imported_at: DateTime,
  )
}

pub fn wean(parrot: Parrot) -> Result(Parrot, String) {
  case parrot.status {
    Feeding -> Ok(Parrot(..parrot, status: Weaned))
    _ -> Error("parrot is not feeding")
  }
}

gleam으로 구현해본 Parrot model

함수형 언어

함수형 언어라는 것이 개발생산성에 도움은 되었지만 브리딩 시스템은 유전 추적, 근친 계수 계산 같은 복잡한 로직이 있고 나는 객체지향적인 언어들을 주로 사용해왔었기 때문에 함수형 프로그래밍으로 그런 복잡한 로직을 명확하게 표현할 수 있을지 확신이 서지 않았다. 또한, Gleam의 경우 생태계가 아직 너무 미숙하다고 생각했다. 실제로 나온지 얼마 되지 않은 언어기 때문에 그런부분에서 레퍼런스 삼을 것도 많지 않고 해서 일단은 포기했다.


TypeScript

돌고 돌아 TypeScript

내가 취업하기 전 웹개발을 처음 시작했을때부터 다뤄왔던 언어이며 풀스택 개발자로 살던 삶에서 역시 주 언어로 계속해서 사용해왔다. 타입스크립트의 장점은 역시 어느것 하나 빠지지 않고 두루두루 다 갖춘 언어라는 것이었다. 도메인 표현력 역시 객체지향과 함수형을 적절히 섞어서 좋았고, 타입시스템이 다른 강타입 언어들보다는 약하지만 약한만큼 생산력을 올려줄 수 있었고, 그렇다고 문제가 될만큼 약하지만은 않다고 생각했다. 생태계 역시 전세계에서 가장 큰 생태계 중 하나를 가지고 있기 때문에 생산성을 올릴 수 있었다.

typescript
export const gender = ["male", "female", "unknown"] as const;
export type Gender = (typeof gender)[number];

export const parrotStatus = ["feeding", "weaned", "breeder"] as const;
export type ParrotStatus = (typeof parrotStatus)[number];

export class Parrot extends Aggregate {
  id: string;
  name: string;
  gender: Gender;
  status: ParrotStatus;
  importedAt: Date;

  wean() {
    if (this.status !== "feeding") {
      throw new Error("parrot is not feeding");
    }
    this.status = "weaned";
  }
}

typescript로 구현해본 Parrot model

마무리

결국 TypeScript로 돌아왔다. 사업 시작 전부터 새로운 언어를 배우며 그곳의 철학이나, 원리를 알아가며 배우는 것도 많았기 때문에 그런 즐거움을 놓치는 것은 살짝 아쉽긴 했다.

하지만 나는 이제 개발자로서가 아닌 사업가로서 브리딩 센터를 운영할 예정이다. 기술 탐험보다는 빠른 개발과 안정적인 운영이 우선이다. 익숙한 도구를 선택하는 게 합리적이었다. 그리고 TypeScript에서도 아직 배울 게 많다. 타입 시스템의 고급 기능, 성능 최적화 등 탐구할 영역은 여전히 넓다.

새로운 언어들은 또 그에 맞춰 해볼만한 프로젝트가 있다면 해볼 생각이다. 현재는 이미지 처리 서버를 Go로 따로 구축해서 사용 중이다. TypeScript만 고집하는 건 아니다. 각 언어의 강점을 살릴 수 있는 부분에서 활용하고 있다. 이 부분은 나중에 별도로 다루겠다.

다음 글에서는 런타임과 프레임워크 선택에 대해 다뤄보려고 한다.