Skip to main content

Command Palette

Search for a command to run...

Value Object를 통한 안전한 데이터 사용

IoT기기의 펌웨어 변경으로 발생한 문제를 해결하며 공부한 내용 정리

Updated
4 min read

개요

최근 BLE 프로토콜을 통해서 IOT기기와 통신을 하는 애플리케이션을 개발할 일이 있었는데 펌웨어 업데이트 이후에 갑자기 통신이 안 되는 문제가 발생을 했다. 원인을 찾아보니 일부 기기의 unique key를 advertising 하는 로직이 바뀌어 포맷은 동일한데 일부 자릿수가 16진수로 표기될 때 앞의 0을 빼먹고 전달이 된 것과 대소문자의 차이로 이러한 문제가 발생을 했다는 것을 알게 됐다.

이때 단순하게 IoT device를 나타내는 클래스의 getter를 통해서 수정을 할까 하다가 코드가 너무 길어져서 가독성, 유지보수성이 떨어지기도 해서 value object로 분리하여 validation의 책임을 분리시키기로 했다. 오늘은 이때 공부했던 내용을 예시와 함께 정리를 해보려고 한다.

Primitive Obsession

Primitive Obsession, 원시타입 집착은 복잡한 개념을 표현할 때 기본 자료형을 과도하게 사용하는 것을 말한다. 예를 들어, 2차원 좌표, 기간, 전화번호와 같은 복잡한 데이터 타입을 특정 클래스나 구조체를 만들지 않고 프리미티브로 표현하는 것이 여기에 포함된다.

전화번호는 문자열만을 이용해서 충분히 나타낼 수 있다. 하지만 문자열이 가지고 있는 모든 속성을 전화번호가 가지고 있지는 않다. 예를 들어, "문자열이 지원하는 더하기 연산을 전화번호가 지원을 해야 할까?"라고 생각한다면 그 대답은 "아니요"가 될 것이다.

Primitive Obsession는 이처럼 도메인적으로 해당 값이 나타내는 의미를 직관적으로 판단할 수 없게 만든다. 그뿐만 아니라 데이터와 관련된 검증 및 조작 로직을 코드 전체에 분산시켜 유지 관리를 어렵게 만들고 IDE의 도움을 받기 어렵기 때문에 실수가 일어나기도 쉽다.

Value Object가 바로 이러한 Primitive Obsession을 피하면서 값이 도메인 내에서 가지는 의미를 명확하게 전달할 수 있게 해주는 장치이다.

Value Object(VO)란?

Value Object는 도메인 주도 설계(domain-driven development, DDD)의 핵심적인 개념 중 하나로 도메인의 한 측면을 나타내는 객체이다. 보통 한 개 또는 그 이상의 값들을 묶어서 특정 값을 나타내는 객체로 대표적인 예시로는 x, y 좌표로 구성되는 2차원 좌표나 시작 날짜와 끝 날짜로 구성되는 기간 등이 있다.

Value object가 가져야 하는 주요 특징은 값 기반 동등성 검사와 불변성, 자기 유효성 검사가 있다.

Value Object의 특징

값 기반 동등성 검사

동일성(identity)과 동등성(equality)의 차이를 간략하게 설명하면 동일성은 비교 대상인 두 객체의 메모리 주소가 같음을 의미하고 동등성은 두 객체가 논리적으로 동일한 값을 나타내고 있음을 의미한다.

const point1 = { x: 1, y: 10 };
const point2 = { x: 1, y: 10 };

예를 들어, 2차원 좌표를 표현하는 값을 가지고 있는 두 변수 point1, point2가 있을때 두 값은 내부의 x, y 좌표값이 같기 때문에 동등하지만 서로 다른 변수이기 때문에 동일하지는 않다고 볼 수 있다.

동등성 검사는 언어마다 다르게 이루어진다. 예를 들어 Java에서는 equals, hashcode를 재정의해야 하는 반면에 functional programming 언어인 Haskell이나 Clojure에서는 기본적으로 속성을 기준으로 한 동등성 검사를 지원한다.

불변성 (Immutability)

Value Object는 그 자체로 값인 객체이고 이 값이 불변이어야 한다는 원칙이 있다. 그렇기 때문에 수정자를 가지고 있지 않아야 하며 만약에 값을 바꾸고 싶다면 새로운 객체를 만들어서 값을 할당하는 방법 뿐이다.

이러한 특징 덕분에 side effect 발생을 방지할 수 있어 의도하지 않은 곳에서 이 값이 수정되어 발생하는 Aliasing Bug 걱정 없이 사용할 수 있다.

자기 유효성 검사 (Self-validation)

만약에 원시타입을 사용하여 복잡한 값을 표현한다면 값의 유효성을 사용하는 측에서 검사를 해야 한다. Value Object의 유효성 검사는 생성 시간에 이루어져서 항상 유효한 상태를 유지할 수 있도록 해야 하며 유효하지 않는 값이 들어왔을 때는 Value Object를 생성할 수 없어야한다.

Value Object의 불변성과 자기 유효성 검사를 통해 항상 유효한 상태 유지를 보장할 수 있어 클라이언트 쪽에서 도메인 규칙이 깨질 염려를 하지 않을 수 있다.

Device의 unique key를 Value Object로 표현

class DeviceUniqueKey {
  final String _uniqueKey;

  DeviceUniqueKey(String uniqueKey)
      : assert(uniqueKey.isNotEmpty),
        assert(
          uniqueKey
              .split(":")
              .map((e) => int.tryParse(e, radix: 16) != null)
              .every((element) => element),
        ),
        _uniqueKey = uniqueKey.toUpperCase();

  @override
  bool operator ==(Object other) {
    return other.hashCode == hashCode;
  }

  @override
  int get hashCode => _uniqueKey.hashCode;
}

위의 dart로 작성된 unique key를 나타내는 value object을 살펴보면 앞서 본 Value Object의 특징처럼 생성 시점에 validation을 수행하여 특정한 포맷을 만족하도록 강제하며 자기 유효성 검사를 수행하는 것을 볼 수 있다. 그리고 불변성을 보장하기 위해 내부의 값을 수정할 수 없게 만들었으며 == 연산자와 hashCode를 재정의하여 값을 통한 동등성 검사를 지원하고 있다.

결론

Value Object가 가지는 불변성, 자기 유효성 검사 등 클라이언트가 안심하고 사용할 수 있게 해주는 특성들은 협업 상황에서 더욱 빛을 발하는 것 같다. 특히 내가 만들어둔 객체가 의도한 상태대로 있게 해준다는 것은 커뮤니케이션 과정에서 발생할 수 있는 오류들을 줄일 수 있다는 것이 굉장히 큰 매력으로 느껴졌다. 도메인이 더 복잡해질수록 Value Object가 가져다주는 코드의 명확성, 유지보수성이 더 큰 장점으로 다가올 것 같다.

앞으로는 혼자서 개발을 할 때도 꽤 오래 전에 만들어 둔 로직들은 까먹을 수도 있기 때문에 앞으로 validation 이 필요한 값의 경우에는 Value Object를 통해서 처리를 하는 습관을 들여야겠다.

다음에는 Value Object를 공부하면서 정리를 하고 있었던 일급 컬렉션에 대한 포스팅을 할 것이다.

References

  • https://martinfowler.com/bliki/ValueObject.html

  • https://en.wikipedia.org/wiki/Value_object

  • https://hudi.blog/value-object/

  • https://hudi.blog/identity-vs-equality/

  • https://ksh-coding.tistory.com/83

  • https://velog.io/@livenow/Java-VOValue-Object%EB%9E%80

  • https://medium.com/@nicolopigna/value-objects-like-a-pro-f1bfc1548c72

  • https://medium.com/fistkim101/ddd-%EC%84%B8%EB%A0%88%EB%82%98%EB%8D%B0-3-%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A3%BC%EB%8F%84-%EC%84%A4%EA%B3%84-%EA%B8%B0%EB%B3%B8-%EC%9A%94%EC%86%8C-99eead8e96f3

More from this blog

오픈소스 기여모임 10기 후기 - 첫 Pr을 올리기까지

개발자라면 누구나 한 번쯤 오픈소스 기여에 대한 환상을 가져본 적 있을 거다. 하지만 막상 시작하려면 어디서부터 해야 할지 막막하고, 괜히 대단한 걸 해야 할 것 같은 부담감에 선뜻 시작하기는 어려운 것 같다. 나 또한 해보고 싶다는 마음만 가지고 계속 미뤄왔다. 그러다 2025년 말 쯤에 오픈채팅방과 글또 슬랙 채널에서 "오픈소스 기여모임" 10기 모집글을 봤다. 2년 넘게 500명 이상의 참가자와 함께 1000개 이상의 PR을 만들어온 커뮤...

Feb 5, 20265 min read

😢 글또 10기 활동 회고 — “글또야, 가지 마…”

들어가며 드디어 글또 10기 활동 회고를 정리해본다.6개월간의 여정을 뒤돌아보니 정말 많은 일들이 있었다. 글또라는 커뮤니티를 8기가 한창 진행되고 있을 때 알았는데 이름부터 인상이 강렬했다. "글쓰는 또라이가 세상을 바꾼다." 유쾌하고 독특한 문구에 피식 웃으며, '여긴 도대체 어떤 사람들이 모이는 곳이지?' 하고 넘겼었다. 재밌는 건 결국, 나도 그 "또라이들" 중 한 명이 되었다는 것이다. 😌 글또는 개발자들이 2주에 한 번 글을 ...

Jul 31, 20255 min read
😢 글또 10기 활동 회고 — “글또야, 가지 마…”

Serverless 환경에서 배포 전 환경변수 검증 자동화하기: TypeBox와 Bitbucket Pipeline 활용기

들어가며 배포 직후, 환경변수가 제대로 설정되지 않아 여러 API가 제대로 작동하지 않는 일이 있었습니다. 다행히 밤에 사용자가 없을 때 문제가 있었던 거라 영향도는 크지 않았지만 앞으로도 계속해서 발생할 수 있는 문제이기 때문에 해결해야 겠다고 생각했습니다. 개발 단계에서 문제가 발견되면 가장 좋겠지만, 현재 팀 상황에서는 백엔드 개발을 혼자 담당하고 있어 코드 리뷰나 검증 프로세스를 갖추기가 쉽지 않았습니다. 그래서 최소한 배포 전에 자동으...

Mar 16, 20254 min read

Cloudflare Tunnel로 포트포워딩 없이 홈서버 운영하기

이 글에서 다루는 내용 포트포워딩이 안 되는 이유 (CGNAT 환경 이해) CGNAT 우회 방법들의 장단점 비교 Cloudflare Tunnel 설정 방법 (MacOS 기준) 외부에서 내 PC로 접근할 수 있도록 허용하는 방법을 생각하면 포트포워딩이 가장 먼저 떠오릅니다. 공유기에서 특정 포트를 열어 외부에서 서버에 접속할 수 있도록 설정하는 방식으로, 마인크래프트 멀티를 해보셨던 분이라면 분명 해보셨을 방법입니다. 😊 작년에 저는 홈서...

Mar 2, 20256 min read
Cloudflare Tunnel로 포트포워딩 없이 홈서버 운영하기

구름고래 공방

48 posts