Goで異なる型の構造体をディープコピーする

Goで異なる型の構造体をディープコピーする

システムのリファクタリングで、階層ごとのエンティティ変換に苦労しました。ビュー層の商品VO、ドメイン層のentity、永続化層のPOは似ていても、型のわずかな違いで直接変換できず、ディープコピーが面倒になります。そこでリフレクションを使った汎用的な変換処理を実装し、大量のassemblerメソッドを減らして、保守性と柔軟性を高めることにしました。

最近はシステムのリファクタリングで大忙しです。ブログもすっかり放置してしまいました。

その中で、かなり厄介な問題に出会いました。レイヤードアーキテクチャにおける、階層ごとのエンティティの相互変換です。商品を例にすると、ビュー層には商品VO、ドメイン層には商品entity、あるいはDO(domain object)、永続化層にはデータベースのエンティティに対応する商品POがある、といった具合です。

これらの構造は大抵よく似ていて、ほぼ同じ、あるいは完全に同じものもあります。ただ、あるフィールドが一方ではポインター型で、もう一方では非ポインター型という微妙な違いがあると、直接キャストできません。そこで変換用のassemblerメソッドを大量に書くことになりますが、複雑な構造になるとまさに地獄です。本質的にはディープコピーなのに、型が違うというだけで処理できないわけです。

この特殊なケースを少し汎用的に扱える変換処理を、リフレクションで作れないかと考えました。そうして午後いっぱいかけて生まれたのが、次のコードです。

中心となるのはcopyRecursiveです。構造体、Slice、Mapのディープコピーに対応し、ポインター型から非ポインター型、その逆のコピーもできます。唯一の条件は、構造体をコピーするとき、コピー先の全フィールドに対して、同名かつ基になる型が同じフィールドがコピー元に存在することです。これで再帰的なディープコピーができます。

コードの仕組みを細かく説明するつもりはありませんが、これだけは言わせてください。

リフレクション、マジですごい。


20220820 update:コピー先に、コピー元にはないフィールドが含まれる場合にも対応しました。一致するフィールドがない場合、コピー先のフィールドにはゼロ値を設定します。ポインターならnil、構造体なら空の構造体です。

失敗に終わったプロジェクト:楽しくなかった春節の7日間
Javaのthisキーワードでコンパイル時の定数伝播が効かなくなる問題