티스토리 뷰

Java

JSR354 JavaMoney

LichKing 2025. 4. 25. 19:55

객체에 분류와 구현에 대해 공부하다보면 VO(Value Object)라는 용어를 알게되고, VO를 공부하면 대표적으로 등장하는 예시가 Money 클래스이다. long이나 BigDecimal 같은 숫자를 다루는 타입으로 통화를 표현하게 되는데 이런 타입을 사용하지 말고 직접 통화를 의미하는 타입을 만들어서 표현력을 올리고, 통화의 책임을 다루라는 의미다. 테스트주도개발이라는 책에서도 Money 클래스를 TDD로 만들어가는걸로 책을 시작한다.
 
java에서는 JDK 안에 time 패키지를 제공함으로써 날짜에 대한 표준 구현 클래스들을 제공한다. 언어표준이기 때문에 대부분의 라이브러리에서도 지원하고 있어 날짜나 시간을 직접 구현하거나 String같은 타입으로 표현하는 경우는 드물다.
 
미처 몰랐는데 통화에 대해서도 java 표준 스펙이 있다는걸 알게되어 소개해보려한다. jackson 2.19의 release note를 보다가 javax.money를 지원한다는 내용을 보고, 이게 뭐지? 하면서 알아본 내용인데 더 알아보니 이미 추가된지 10년은 된 스펙이다.

 

# JavaMoney

JSR354에 추가된 스펙인데 이 스펙은 JDK 내장으로 채택되지 않았기에 사용하려면 별도 의존성 추가가 필요하다.

// 의존성 추가
implementation("javax.money:money-api:1.1")

// javax.money 패키지 사용
MonetaryAmountFactory<?> factory = Monetary.getDefaultAmountFactory();
MonetaryAmount usd = factory.setCurrency("KRW")                       
        .setNumber(500)                                               
        .create();

 
의존성을 추가하면 javax.money 패키지를 이용할 수 있게되고 위와 같은 팩토리를 사용할 수 있게된다. 위 코드를 그대로 실행하면 예외가 발생하는데 money-api는 인터페이스만 제공하는 모듈이기 때문이다. 레퍼런스 구현인 moneta 의존성도 추가해야한다. JSR310에 추가된 스펙인 time 패키지가 JDK 내장으로 들어와있기에 별도 의존성 추가 없이 구현체까지 사용할 수 있는걸 생각해보면 둘의 차이나 사용법을 비교해보는 것도 재미있다.

 

참고로 moneta는 공식 구현이 아니라 레퍼런스 구현이라는 표현을 사용하고 있는데, javax.money가 인터페이스들이니만큼 누구나 구현할 수 있고, 이때 기준이 되는 구현일 뿐 time 패키지의 LocalDate와 같은 공식 구현은 아니라는 의미다.

implementation("org.javamoney:moneta:1.4.5")

구현 의존성을 추가해주면 예외는 발생하지 않는다. 그리고 아래와 같은 테스트 코드도 성공한다.

CurrencyUnit currency = Monetary.getCurrency("KRW");                   
MonetaryAmountFactory<?> factory = Monetary.getDefaultAmountFactory(); 
MonetaryAmount krw = factory.setCurrency(currency)                     
        .setNumber(500)                                                
        .create();                                                     
                                                                       
MonetaryAmount addedKrw = krw.add(Money.of(300, currency));            
                                                                       
assertThat(addedKrw).isEqualTo(Money.of(800, currency));

금액이 달라지거나 통화가 달라지면 테스트는 실패한다.
 

# Jackson

처음 JavaMoney에 대해 알게해준 jackson을 이용해서 json화 시켜보자

MonetaryAmount krw = Money.of(300, Monetary.getCurrency("KRW"));  
                                                                  
ObjectMapper objectMapper = new ObjectMapper();                   
String json = objectMapper.writeValueAsString(krw);               
System.out.println(json);

// 출력결과
{"currency":{"context":{"providerName":"java.util.Currency","empty":false},"currencyCode":"KRW","defaultFractionDigits":0,"numericCode":410},"number":300,"factory":{"defaultMonetaryContext":{"fixedScale":false,"amountType":"org.javamoney.moneta.Money","maxScale":63,"precision":0,"providerName":null,"empty":false},"maxNumber":null,"minNumber":null,"amountType":"org.javamoney.moneta.Money","maximalMonetaryContext":{"fixedScale":false,"amountType":"org.javamoney.moneta.Money","maxScale":-1,"precision":0,"providerName":null,"empty":false}},"context":{"fixedScale":false,"amountType":"org.javamoney.moneta.Money","maxScale":-1,"precision":256,"providerName":null,"empty":false},"zero":false,"negative":false,"positive":true,"numberStripped":3E+2,"negativeOrZero":false,"positiveOrZero":true,"scale":0,"precision":3}

읽을 순 있지만 일반적으로 원하는 모양은 아니다. 계속 비교군으로 언급되는 time 패키지의 LocalDate와 같은 타입도 원하는 모양이 아닌 형태로 json화 되는걸 경험해본 경험이 있을 것이다.

implementation("com.fasterxml.jackson.core:jackson-databind:2.19.0")
// javax.money 지원
implementation("com.fasterxml.jackson.datatype:jackson-datatype-javax-money:2.19.0")
// 구현체인 moneta 지원
implementation("com.fasterxml.jackson.datatype:jackson-datatype-moneta:2.19.0")

// 둘중 하나를 추가
objectMapper.registerModule(new JavaxMoneyModule());
objectMapper.registerModule(new MonetaMoneyModule());

// 출력결과
{"amount":300,"currency":"KRW"}

jackson은 두 모듈을 지원하고 있는데, 예제차원으로 MonetaryAmount 타입을 json화하는 정도에선 자바 표준을 지원하고 있는 javax-money 모듈로도 충분하고, moneta에 대한 더 구체적인 지원이 필요할때 moneta 모듈을 추가하는게 좋아보인다. 둘중 하나를 추가하고 다시 코드를 실행하면 원하는 형태의 json이 출력됨을 알 수 있다.
 

# 마무리

개요 수준으로 간략하게 살펴봤다. moneta는 구현클래스인 Money외에도 FastMoney와 같은 구현체도 제공하고 있으니 도입을 고려한다면 더 심도깊게 알아보고 사용하자. 새로운 스펙인줄알고 신나게 찾아봤는데 이미 10년이나 된 스펙이라 김이 좀 샜지만 돈을 다루는 프로젝트에서는 도입을 검토해보면 좋겠다.
 

# 참고자료

https://javamoney.github.io
https://javamoney.github.io/api.html
https://github.com/JavaMoney/jsr354-ri/blob/master/moneta-core/src/main/asciidoc/userguide.adoc
 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함