조금더 강력해진 직렬화에 대한 견해(Serializable,Parcelable)
과거의 나의 글을 보다보면 이렇게 조잡할수가 없다. 하지만 나의 성장 과정이니 애정을 가지고 업데이트를 해보려한다.
https://mccoy-devloper.tistory.com/29
기존에 직렬화에 대한 글이다 직렬화가 뭔지모른다면 한번쯤 읽어보면 내가 쪽팔리니까 읽지말고 다른블로그로 공부하자
원래 직렬화에 대한 학습을 아주 얕고 간단하게 했었는데 계속 코딩을 해오면서 생각이 변했었고 우테코 미션을 진행하다보니
도메인 분리에 더욱더 집착하게 되었고 그에대한 고찰을 정리해봤다.
직렬화
자 직렬화는 진짜 오랫동안 사용하고 고민했다고 생각했는데 레이어의 분리가 명확한 우테코에서 새로운 관점이 생겼다.
그리고 그에 관해서 새로운 학습도 많이 하였고 미션을 계속하면서 갈대처럼 Parcelize랑 Serializable 를 교차하면서 쓰면서 장단점을
피부로 느껴봤다.
일단 직렬화가 뭔지부터 알아보고 각각의 장단과 결국 내가 뭘 쓸건지에 대해서 결론을 내보려고 한다.
직렬화란 무엇인가?
직렬화란?
- 직렬화는 메모리 내에 존재하는 정보를 보다 쉽게 전송 및 전달하기 위해 byte 코드 형태로 나열하는 것이다. 여기서 메모리 내에 존재하는 정보는 즉 객체를 말한다.
- 자바 직렬화란 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터 변환하는 기술과 바이트로 변환된 데이터를 다시 객체로 변환하는 기술(역직렬화)을 아울러서 이야기합니다.
즉 간단하게 이야기해서 그냥 객체자체를 주고받기 힘드니 byte code 형태로 변환하여 주고받기 쉬운 형태로 변환하는것이다.
그렇다면 안드로이드에서는 직렬화 하는 방법이 뭐가있을까?
세부적으로는 3개 결론적으로는 2가지가 있다.
각각을 자세히 살펴 보도록 하자
1.Serializable
우선 Serializabel 에 관한 좋은글 하나를 첨부한다.
이글을 간단히 한줄요약하자면 Serializable 은 자바에서 제공하는 인터페이스고
해당클래스가 직렬화 대상이라고 알려주기만 하는 마커 인터페이스 이기 때문에 별다른 함수를 작성 해줄 필요가 없고 편리하긴 하지만
Reflection을 통해서 동작하기 때문에 처리 과정중에 많은 추가 객체를 생성하고 -> 이 객체들은 GC의 타겟이 되고 GC가 많이 동작하게 됨으로 성능저하 및 배터리 소모를 촉진한다고 한다.
하지만 자동으로 처리되는 직렬화 프로세스를 사용자가 구현한다면 Parcelable 과 비슷하거나 더 빠른 속도를 낼수 있다.
data class MovieUIModel(
@DrawableRes var posterImage: Int = R.drawable.harrypotter_poster,
var title: String,
var screeningStartDay: LocalDate,
var screeningEndDay: LocalDate,
var runningTime: Int,
var description: String = ""
) : Serializable {
@Throws(IOException::class)
private fun writeObject(out: ObjectOutputStream) {
out.apply {
writeInt(posterImage)
writeUTF(title)
writeObject(screeningStartDay)
writeObject(screeningEndDay)
writeInt(runningTime)
writeUTF(description)
}
Log.d("멧돼지", "직렬화 되고있음")
}
@Throws(IOException::class, ClassCastException::class)
private fun readObject(`in`: ObjectInputStream) {
`in`.run {
posterImage = readInt()
title = readUTF()
screeningStartDay = readObject() as LocalDate
screeningEndDay = readObject() as LocalDate
runningTime = readInt()
description = readUTF()
}
Log.d("멧돼지", "역직렬화 되고있음")
}
}
실제로 이렇게 직렬화와 역직렬화 될때마다 불리는것을 확인할수 있다.
이렇게 직렬화와 역직렬화 프로세스인 readObject와 writeObject를 작성해 준다면 성능이 꼭필요한 곳에서는 성능을 끌어올릴수 있는 방법도 있는것이다.(물론 보일러플레이트 코드가 너무심해서 얻는것보다 잃는것이 더 크다고 생각한다 -> 이 정도도 못쓰는 폰은 이제 보내줄때가 된것 아닌가?)
장점
Serializable의 장점으로는 자바 언어차원에서 지원하기 때문에 의존성을 갖지않고 도메인 모듈을 순수 자바 코틀린 모듈을 사용하더라도 일단 쓸수는 있다.
언어차원에서 지원한다는것이 아주 강력한 사용이유중 하나이다.(도메인 레이어에서 사용에 자유롭다.)
처음에 Serializable을 도메인모델에 사용하여 따로 UI layer의 모델을 만들지 않았었다.
하지만 이에대해 리뷰어 분들의 리뷰는 과연 직렬화가 도메인 모델의 책임인가에 대해서 생각해 보라는 말이 많이 달렸는데
이에 대해서 나는 여러가지 의문점이 들었다.
그 내용은 이러했는데 결국 어딘가에 의존하는것도 아니고 기능상 문제가 없는데 객체에 역할에 비해 조금더 큰 책임이 있는건 그냥 객체의 특성일뿐 문제가 없다고 생각했다. 또한 이 부분에서는 아직도 생각이 같다.-> 객체 지향적인 측면에서는 전혀 문제가 없다고 생각한다.
하지만 이런의문이 풀리지않아 수업시간에도 질문하며 토론한 결과 레아의 말이 결국 layer별로 model 을 분리하고 도메인에서 직렬화의 책임을 가져가서는 안된다는 결론을 내리게 했다.
레아: 만약 도메인에서 Serializable을 구현하고 있다면 그것을 사용하는 관련 UI layer에서도 연쇄적으로 다 Serializable를 사용하게 될것이다.
하지만 회사에서 Parcelable를 도입하자 결정이 나거나 아님 직렬화를 아주 기깔나게 하는 더 좋은 방법이 나온다고 가정해보면 가장 깊은 뿌리인 도메인에서 Serializable이 고정되어있기 때문에 그것을 들어내지 않는이상 다른 방법으로 변화할수가 없다.
그래서 UI layer의 관심사가 변화했다고 domain layer의 코드또한 영향을 받는것은 잘못되었기 때문에 정석적으로 코딩을한다면 도메인에서 직렬화를 하면 안된다는 결론을 내렸다.
결론적으로 이론상 그리고 이상적인 코드는 각 layer의 모델이 다 분리되어야하고 직렬화의 책임은 사용되고있는 UI layer이 가져가는 것이 맞다.
하지만 이는 결론적으로 아주 큰 보일러 플레이트 코드를 가져오므로 팀원과의 합의점을 찾는다면 직렬화 정도는 tradeOff의 영역으로 도메인에서 책임을 가져가는 편법을 써도 되지않는가라는 생각이 스쳐지나간다.
팀원과의 레이어와 도메인,UI의 책임과 보일러 플레이트 코드에 대해서 전쟁을 치루고 이겨서 UI에서 모델을 생성하지 않고 도메인에 Serializable을 도입한다면 Serializable을 추천한다.
하지만 UI와 도메인의 모델을 나누게 되었다면 Parcelable을 사용하는것을 더 추천한다.(이유는 Parcelable를 보면 알게된다.)
2.Parcelable(Parcelize)
Parcelable 또한 직렬화를 위한 하나의 방법인데 표준 자바가 제공하는것이 아닌 Android SDK 에서 제공하는 인터페이스이다.
여기서 Android SDK 에서 제공 이 당연히 신경쓰이지 않는가?
당연히 도메인에서 사용금자라는 말이 떠오를것이다.
뭐 딱히 도메인이 아니더라도 그냥 어느 플렛폼에 의존성으로 묶여버리는건 아주 불편할수있다.
그래서 모든것들이 안드로이드와 엮여있는 안드로이드 관련 모듈에서만 사용하게 될것이다.(테스트도 느려지겠지 안드로이드 관련 테스트니까)
근데 웃긴건 사실 안드로이드 의존성있는데 유닛테스트에서 잘돌아감 (이게 맞음?) 근데 원칙상 안드로이드 의존성이있는것은 유닛테스트에 끌고가면 안되니까 안드로이드 테스트로 해야할것이다 이러면 테스트 느려지니 슬플것이다.
그래서 근데 그럼 전지전능한 언어차원에서 제공하는 Serializable을 사용하지 왜 의존성이 붙어있는 Parcelable을 이여기하지? 라는 의문이 들것이다.
또 근데 Parcelable 만의 매력이있다.
우선 Parcelable은 많은 오버라이딩이 필요했지만 그대신 빠르고 효율적으로 작동한다(리플렉션 없이 작동하기 때문)
또한 안드로이드에서 좀더 편의성을 위하여 @Parcelize를 제공하기 때문에 사실상 보일러 플레이트 코드도 없어진다.
@Parcelize
kotlin-parcelize 플러그인은 Parcelable 구현을 자동으로 해준다.
새로운 클래스를 생성도 필요 없고 @Parcelize 어노테이션을 추가하는 것만으로 직접 Parcelable 관련 코드를 작성 한 것과 같이 동작한다. 컴파일타임에 바이트 코드 변조를 하기 때문에 추가되는 메서드 및 런타임시 오버헤드 비용도 발생하지 않는다. 그리고 무엇보다 이 플러그인은 구글과 JetBrains가 협업하여 만든 플러그인이기 때문에 다른 3rd-party 라이브러리와는 다르게 추후 계속 유지보수 될 것이라 기대한다.
나는 코드상에서 이런걸 해결해볼려고 Serilizable을 붙들고 씨름을 했는데 역시 그냥 코드만으로는 답이없었나보다. -> 컴파일타임에 바이트 코드 변조로 작업을 해준다고 한다.
어쩃든 이렇게 빠르고 좋은데다가 어짜피 UI layer를 깔끔하게 분리하고 모델을 구분해준다면 ->Parcelize를 사용하지 않을 이유가없다.
(물론 안드로이드 의존성 때문에 원칙상 안드로이드 테스트에서 동작해야하지만 사실상 유닛테스트에서 돌아가니까 사용하는데 별로 신경 안써도된다.)
이렇게 자동완성을 봐도 극명하게 Parcelable 를 써야할 이유가 있는데
Parcelable를 리스트형태로 담아서 옮기고싶을떄 ParcelabelArrayListExtra 같은 것들이있는데
Serializable은 이렇게 리스트 형태로 옮길수있도록 지원하는 형태가 없다.
즉 다 포장해서 Serilazble 을 찍고 다녀야한다는 소리다.
이런 편의성을 많이 제공하니 레이어를 완벽하게 나눈다면 Parcelable(Parcelize)를 쓰는것이 더 편리하다고 생각한다.
그래서 결론적으로는 나라면 도메인에다가 직렬화를 전가한다면 Serializable를 사용
layer간 분리를 완벽히 한다면 Parcelable(Parcelize) 를 사용할것같다.