POJO(Plain Old JavaScript Object)를 잘 지키는 Model 만들기

poyal·2024년 9월 25일
0
post-thumbnail

POJO란?

POJO는 Plain Old Java Object 라는 명칭으로 JAVA에서 시작한 개념이다. POJO는 오래된 방식의 JAVA 객체를 뜻한다. 특정 기술이나 방식에 종속되지 않는 상태의 객체를 뜻한다.

POJO는 왜 등장 했는가?

POJO는 2000년에 마틴 파울러, 레바카 파슨, 조시 맥킨지등이 사용하기 시작한 용어이다.

당시 Java EE 등의 중량 프레임워크를 사용하게 되면서 해당 프레임워크에 종속된 무거운 객체를 만들게 된 것에 반발해서 사용하게 된 용어다. 2000년 초반 당시 IT 기술이 발전하면서, 다양한 기술, 서비스가 점차 등장하게 된 시기였다. 하지만 문제점은, 생산성 그 자체에 집중하다보니 객체지향적인 설계를 포기하고 특정 기술에 의존하여 급급하게 개발하는 방식이 늘어났다. 이는 결과적으로 유연한 확장성을 낮추는, 유지보수가 어렵게 만드는 문제를 야기했다.

이를 해결하기 위해 POJO 라는 개념이 등장했다. 본래 자바의 장점을 살리는 오래된 방식의 순수한 자바 객체의 중요성이 필요하여 등장했다. POJO 를 다시 정의하자면, 자바 언어의 규약에 의한 제한사항 외의 그 어떠한 제한사항에도 구속받지 않는 (의존성을 최소화한) 자바 객체를 뜻한다.

POJO 프로그래밍 규칙

POJO 프로그래밍이란 POJO 객체를 만들기 위한 프로그래밍 설계 기법이다. 즉, 객체지향적인 원리에 충실하면서 특정 환경과 기술에 종속되지 않고, 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 개발하는 방식이다. 이 프로그래밍 규칙을 준수하기 위해선, 애플리케이션의 핵심 로직과 기능을 담아 설계하고, 개발해야한다.

  • JAVA 스팩외에 다른 기술이나 규약에 종속되지 않아야 한다.

POJO(Plain Old JavaScript Object)는 어떻게?

JavaScript에서 POJO는 같은 의미를 사용되지만 중간의 축약어가 JAVA가 아니라 JavaScript이다. JAVA에서와 같이 추구하는바는 같으나 구현하는 방식이 다르다. JAVA에서는 원시 Class의 getter, setter 까지가 JAVA에서의 POJO라면 JavaScript의 POJO는 프로토타입이 Object.prototype이 object인 형태이다. 메서드, 내부상태를 제외한 데이터만 포함하는 객체이여야 한다.

다음에는 POJO를 확인하는 구문이다.

function isPojo(obj) {
  if (obj === null || typeof obj !== 'object') {
    return false;
  }

  return Object.getPrototypeOf(obj) === Object.prototype;
}

function Foo () {}
function Bar () {}
Bar.prototype.constructor = Object;

isPojo(function () {}) // false
isPojo([]) // false
isPojo(new Date()) // false
isPojo(true); // false
isPojo("abc"); // false
isPojo(123); // false
isPojo(new RegExp()); // false
isPojo(null); // false
isPojo(undefined); // false
isPojo(Object.create({})); // false
isPojo(new Foo()); // false
isPojo(new Bar()); // false
isPojo({constructor: Foo}); // true

객체의 프로토타입이 object인 것을 확인한다.

POJO를 왜 준수해야 하는지?

JavaScript에서는 데이터들은 여러방향으로 사용된다. 라이브러리에서 데이터를 전송하기도, 외부 저장소와 소통하기도, 내부의 상태와 소통하기도 한다. 데이터 교환에 독립적인 JSON을 기본적으로 사용한다. JSON(JavaScript Object Notation)은 Javascript 객체 문법으로 구조화된 데이터 교환 형식, python, javascript, java 등 여러 언어에서 데이터 교환형식으로 쓰이며 객체문법말고도 단순 배열, 문자열도 표현 가능하다. 이러한 데이터 교환을 할때 주로 사용하는 방식이 직렬화, 역직렬화를 사용한다.

직렬화 역직렬화

직렬화란 외부의 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터를 변환하는 기술이며 역직렬화는 반대를 의미한다. 직렬화는 JSON.stringify를 사용해서 Object를 String으로 변환한다. 역직렬화는 직렬화된 String Object에 JSON.parse를 사용해서 Object를 변환한다. 이렇게 사용해서 여려가지의 플렛폼에서 JSON을 독립적으로 사용이 가능하다.

모델의 변화(Class -> Object)

우리는 모델을 Class 모델을 사용하고 직렬화 역직렬화툴을 class-transformer를 사용하여 개발하고 있었다. Vue@2때 까지만 해도 괜찮았다. Nuxt@2를 사용하면서 이러한 메시지가 뜨기 시작했다.

 WARN  Cannot stringify arbitrary non-POJOs Add 
// model
export class Add {
  @Attribute('타이틀')
  @XssRequest()
  @IsString()
  @IsNotEmpty()
  @MaxLength(200)
  title!: string;

  @Attribute('유저 아이디')
  @IsNumber()
  @IsNotEmpty()
  userId!: number;
}

우리는 모델을 Class를 사용함에 있어서 Nuxt@2의 server-side, client-side간의 데이터 전송에 직렬화 역직렬화에 문제가 있었다.

Nuxt@3를 도입함에 있어서 이 부분을 해결하려고 했다. Class모델을 과감하게 버리고, 순수 Object를 사용함으로서 해당 부분을 해결하려고 했다.
Validator와 Transformer를 같이 해결해야 했다. 그래서 찾은게 yup이다 중요한점은 Vaildator, Transfomer, Type이 다 분리되어 있고 데이터도 순수 Object로 유지할수 있다.

// type.model.ts
export namespace TypeModel {
  export namespace Response {
    export type FindAll = InferType<typeof TypeSchema.Response.FindAll>;
    export type Id = InferType<typeof TypeSchema.Response.Id>;
    export type Item = InferType<typeof TypeSchema.Response.Item>;
  }
}

// type.schema.ts
export namespace TypeSchema {
  export namespace Response {
    export const Id = object({
      id: number('아이디')
    });

    export const Item = object({
      name: string('이름')
    }).label('아이템');

    export const FindAll = object({
      title: string('타이틀'),
      customType: enums('customType', CUSTOM_TYPE.ENUM),
      date: date('date'),
      datetime: datetime('datetime'),
      time: time('time'),
      item: TypeSchema.Response.Item,
      items: array('아이템 목록', TypeSchema.Response.Item)
    }).concat(TypeSchema.Response.Id);
  }
}

// 사용
const data: TypeModel.Response.FindAll = parse(TypeSchema.Response.FindAll, {
  id: '1',
  title: 'test',
  customType: 'TYPE_A',
  date: '2022-12-12',
  datetime: '2022-12-12T10:11:22',
  time: '10:11:22',
  item: {name: true},
  items: [{name: 'test'}]
});


/*
{
  items: [{name: 'test'}],
  item: {name: 'true'},
  time: '10:11',
  datetime: '2022-12-12T10:11:22',
  date: '2022-12-12',
  customType: 'TYPE_A',
  title: 'test',
  id: 1
};
*/

참조

0개의 댓글