본문 바로가기
프로그래밍/Spring

[Spring + Jackson] Spring Boot에서는 왜 FAIL_ON_UNKNOWN_PROPERTIES default 옵션을 false로 사용하는가 ?

by 사바라다 2022. 10. 3.

안녕하세요. 이전 포스팅 [Spring + Jackson] Spring Boot에서 default ObjectMapper의 configuration을 알아보도록 하자에서 Spring Boot에서는 jackson을 사용할 때 default ObjectMapper를 기본적으로 사용하지 않고 커스텀하여 사용한다고 말씀드렸습니다.

관련하여 왜 이런 결정을 내렸는지에 대한 이야기를 전달받아서 이를 추가 포스팅으로 작성해 보았습니다.

Spring Boot에서 FAIL_ON_UNKNOWN_PROPERTIES 설정값을 false로 기본 설정

해당 결정에 대한 Issue가 Spring-Framework의 github Issue로 2014년 6월 20일 등록 되어있었습니다. 해당 이슈에 대한 논의는 지금으로부터 8년전에 이루어졌던 것입니다. 해당 문서의 내용은 아래와 같습니다.

Jackson's ObjectMapper has a weird default setting to completely fail the deserialization if a single unknown property is encountered. This violates Postel's law as the server could still successfully create an instance of the expected type. Actually this kind of resilient behavior is why people might choose JSON over the rather schema dominated XML world.
I've filed a ticket for that issue in Jackson itself but I thought it might be worth taking the lead here. I know changing defaults is kind of a risky thing to do but as Brian Clozel indicated in the comments of said ticket, it would actually increase resilience of already existing code.

 

위의 내용을 추려서 요약하자면 아래와 같습니다.

  • Jackson ObjectMapper의 기본 세팅중 deserialize시 알지 못하는 property가 오면 실패하는 것은 이상하다.
  • 왜냐하면 이는 견고함의 원칙(또는 포스텔의 법칙)의 기본을 지키지 않는 것이기 때문이다.
  • 스키마를 강제하는 XML과 다르게 JSON은 schema의 유연함을 지향하고 있고 이로 인해서 사람들이 JSON을 택하고 사용하는 것이다.

이러한 이유로 Spring Boot에서는 좀 더 유연한 Schema를 보장하기 위해서 ObjectMapper의 설정 중으로 FAIL_ON_UNKNOWN_PROPERTIES의 옵션을 false 처리하여 deserialize시 알지 못하는 property가 오더라도 실패하지 않도록 처리한 것입니다.

견고함의 원칙(Robustness Principle)

위에서 deserialize시 알지 못하는 property가 오면 실패하는 것은 견고함의 원칙을 지키지 못하기 때문에 FAIL_ON_UNKNOWN_PROPERTIES는 false으로 두는 것으로 Spring Boot에서는 기본 원칙으로 하고 있다고 했습니다. 여기서 나오는 견고함의 원칙에 대해서 좀 더 알아보도록 하겠습니다.

견고함의 원칙이란 소프트웨어 개발의 기본 설계 원칙으로 위키피디아에서는 당신이 하는 일은 엄하게, 남의 것을 받아들일 때는 너그럽게. (종종 “보내는 것은 엄하게, 받는 것은 너그럽게”로도 일컬어진다.)라고 말하고 있습니다. 이 법칙은 여러 상황에서 사용 되어지는데 개발에서도, API를 설계할 때도 적용할 수 있는 원칙으로 사용할 수 있습니다.

개발을 할 때 문서를 만들고 전달합니다. 그리고 클라이언트가 문서를 토대로 API 요청을 보냅니다. 문서를 100명의 사람이 보았을 때 과연 100명 모두 동일한 API 스펙으로 전송할 수 있을까요? 문서라는 것은 사람에 따라서 해석의 여지가 있을 수 있기때문에 그렇지 않을 수 있습니다. 따라서 여기에 견고함의 원칙을 적용한다면 요청을 받아들이는 서버에서는 어느정도 유연하게 Request를 받아들일 수 있어야한다는 것이됩니다.

이 견고함의 원칙은 RFC로 채택되어있는 원칙이기도 합니다. RFC1122

이러한 원칙을 근거로하여 Spring Boot에서는 ObjectMapper를 그대로 사용하지 않고 커스터마이징하여 사용합니다.

jackson에 issue 제기

그렇다면 왜 jackson에서는 이 이슈에 대해서 대응하지 않는 것일까요 ? 이에 대한 논의는 jackson-databind github issue에서 찾아볼 수 있었습니다. 위와 같은 논리로 문제제기를 했고 이에 부정적인 의견을 남긴 것을 가져와보면 아래와 같습니다.

I disagree. I leave that feature to the default because I prefer my code to document exactly what properties I can ignore (with JsonIgnoreProperties). This document both the code and the json representation I'm working with and clearly state my expectations about it. Every defaults in the Jackson feature set ensure a strict conformance to the JSON specification and to the classes being mapped.
A "law" shouldn't be followed blindly; one of the best example of this "law" being wrongly applied is HTML: it's less of a problem now, but earlier some browsers were more tolerant then others, so you'd get compatibility problems because some HTML pages could only be read by specific browser.

 

위의 내용을 요약하자면 아래와 같습니다.

  • 견고함의 원칙을 무조건 따를 필요는 없습니다.
  • API는 문서와 정확하게 동일해야합니다. 그래야 문서는 API를 통한 기대 요소를 정확하게 전달할 수 있습니다.
  • 이는 호환성 문제를 유발할 수 있습니다.

그리고 jackson 그룹에서 추가로 아래와 같은 이야기를 하고 있습니다.

Yes, I do think it's right by default to fail: you should be very strict about what you accept by default, then gradually relax your constraint depending in the situation. If you change this default, should JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES be changed to true too to be "robust" to older Json generator? And what about DeserializationFeature.FAIL_ON_INVALID_SUBTYPE? These features are all configured by default to be very strict about what Json is accepted. Then, application code can relax those constraints.
With the specific case of ignoring properties, you can do it with different scope: be very specific JsonIgnoreProperties("foo") on a class, then a little bit larger with JsonIgnoreProperties(ignoreUnknown=true) on a class, then very wide with the feature to true. I think this ties in very well with how Java defined its access modifier: the "default" modifier is very strict (package-private), but it's also probably the modifier used the less.
We clearly don't develop the same kind of system and perhaps in your case it makes sense to always accept unknown properties. And from your twitter "survey", it seems that your circle also thinks the same thing, which is fine. I also enabled this feature for some cases I had, but I don't think Jackson defaults should cater to the majority, simply because the majority changes over time and who you talk to. The defaults should offer a consistent and strict definition of what is valid.

 

길게 추가로 이야기했지만 요약하면 아래와 같습니다.

  • FAIL_ON_UNKNOWN_PROPERTIES 뿐만 아니라 그러면 어느정도까지 확장성을 기본으로 열어주어야할까요 ?
  • 많은 사용자들이 FAIL_ON_UNKNOWN_PROPERTIES 옵션을 기본으로 열어주는 것이 좋겠다라고 하지만 이것은 jackson의 철학과는 맞지않습니다.
  • 따라서 해당 옵션은 변경되지 않습니다.

결론

위 이야기를 종합해보면

Spring Boot 진영에서는 API의 견고함의 원칙을 지니는 것을 중요시합니다.

Jackson 진영에서는 API는 명확해야하고 문서와 일치되어야함을 중요시합니다.

마무리

여러분은 어느쪽의 의견이 좀 더 스스로와 맞다고 생각하실까요 ?

찾아보며 흥미로웠던것 같습니다. :)

오늘은 여기까지 알아보도록 하겠습니다.

감사합니다.

참조

[1] https://github.com/spring-projects/spring-framework/issues/16510

[2] https://en.wikipedia.org/wiki/Robustness_principle

[3] https://jira.spring.io/browse/SPR-11891?redirect=false

[4] https://github.com/FasterXML/jackson-databind/issues/493

[6] https://www.rfc-editor.org/rfc/rfc1122#page-12

[7] https://groups.google.com/g/jackson-user/c/5TT1IpakGHU

댓글