VO - ValueObject? DTO?

JBBยท2020๋…„ 7์›” 10์ผ
0

Object

๋ชฉ๋ก ๋ณด๊ธฐ
1/1
post-thumbnail

๐Ÿ˜ƒ ๋“ค์–ด๊ฐ€๋ฉฐ

ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ํ•˜๋ฉฐ, VO(Value Object)๋ผ๋Š” ๋‹จ์–ด๋ฅผ ๋“ค์–ด๋ณด์…จ์„๊ฒ๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ VO๋ผ๋Š” ๋‹จ์–ด๊ฐ€ ์‹ค๋ฌด์—์„œ ํ˜ผ๋ž€์Šค๋Ÿฝ๊ฒŒ ์“ฐ์ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
๊ตฌ๊ธ€์— VO๋ฅผ ๊ฒ€์ƒ‰ํ•ด๋ณด๋ฉด ์•„์ง๋„ ์—ฌ๋Ÿฌ ๊ธ€๋“ค์ด Getter/Setter๋งŒ ์žˆ๋Š”๊ฐ’์„ ์‹ค์–ด๋‚˜๋ฅด๋Š” ๊ฐ์ฒด๋ฅผ
VO๋ผ ์นญํ•˜๊ณ  ์žˆ๋Š”๋ฐ ์ด๋Š” DTO๋กœ ์นญํ•˜๋Š”๊ฒŒ ํ˜ผ๋ž€์˜ ์—ฌ์ง€๊ฐ€ ์ ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ ‡๋‹ค๋ฉด, VO์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ด…์‹œ๋‹ค

Martin Fowler์˜ Value-Object

Martin Fowler์˜ ๊ธ€์— ์˜ํ•˜๋ฉด

โœ๐ŸผI find it useful to think of two classes of object:
ย ย ย ย ย value objects and reference objects, depending on how I tell them apart

๐Ÿ”๋‚˜๋Š” ๊ตฌ๋ณ„ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋”ฐ๋ผ ๊ฐ’ ๊ฐ์ฒด์™€ ์ฐธ์กฐ ๊ฐ์ฒด,
ย ย ย ย ย ๋‘ ๊ฐ€์ง€ ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๋ฅผ ๊ณ ๋ คํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค.

โœ๐ŸผOne source of terminological confusion is that around the turn of the century
ย ย ย ย ย some J2EE literature used "value object" for Data Transfer Object.
ย ย ย ย ย That usage has mostly disappeared by now, but you might run into it.

๐Ÿ”์šฉ์–ด์˜ ํ˜ผ๋ž€์ด ์˜จ๊ฒƒ์€ ์„ธ๊ธฐ๊ฐ€ ๋ฐ”๋€” ๋ฌด๋ ต J2EE๋ฌธํ—Œ์—์„œ
ย ย ย ย ย Date Tranfer Object๋ฅผ Value Object๋ผ๊ณ  ์‚ฌ์šฉํ–ˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
ย ย ย ย ย ๊ทธ๋Ÿฌํ•œ ์‚ฌ์šฉ์€ ์ง€๊ธˆ์œผ๋กœ์„œ๋Š” ๋Œ€๋ถ€๋ถ„ ์‚ฌ๋ผ์กŒ์ง€๋งŒ, ๋„ˆ๋Š” ์—ฌ์ „ํžˆ ์šฐ์—ฐํžˆ ์ ‘ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค

๊ทธ๋ ‡๋‹ค๋ฉด, Value-Object์™€ Reference-Object๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ด…์‹œ๋‹ค.

๐Ÿง VO (Value-Object)?

Martin Fowler๊ฐ€ ์–ธ๊ธ‰ํ•œ VO์˜ ๊ฐœ์š”๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

โœ๐ŸผWhen programming, I often find it's useful to represent things as a compound.
ย ย ย ย ย A 2D coordinate consists of an x value and y value.
ย ย ย ย ย An amount of money consists of a number and a currency.
ย ย ย ย ย A date range consists of start and end dates,
ย ย ย ย ย which themselves can be compounds of year, month, and day.

๐Ÿ”ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•  ๋•Œ, ์‚ฌ๋ฌผ์„ ๋ณตํ•ฉ๋ฌผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•œ ๊ฒฝ์šฐ๊ฐ€ ์ข…์ข… ์žˆ๋‹ค.
ย ย ย ย ย ์˜ˆ๋ฅผ ๋“ค์–ด, 2์ฐจ์› ์ขŒํ‘œ๋Š” x, y๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๊ณ ,
ย ย ย ย ย ๋ˆ์ด๋‚˜ ํ†ตํ™” ๊ฐ™์€ ๊ฒฝ์šฐ ์ˆซ์ž๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๋‹ค.
ย ย ย ย ย ๋‚ ์งœ์˜ ๋ฒ”์œ„๋Š” ์‹œ์ž‘ ๋‚ ์งœ์™€ ์ข…๋ฃŒ๋‚ ์งœ๋กœ ๊ตฌ์„ฑ๋  ์ˆ˜ ์žˆ๊ณ ,
ย ย ย ย ย ์—ฐ๋„์™€ ์›”, ์ผ์˜ ๋ณตํ•ฉ๋ฌผ์ผ ์ˆ˜ ๋„ ์žˆ๋‹ค.

์ฆ‰, Value-Object ๋ž€, ํ•œ๊ฐœ ํ˜น์€ ๊ทธ ์ด์ƒ์˜ ์†์„ฑ๋“ค์„ ๋ฌถ์–ด์„œ ํŠน์ • ๊ฐ’์„ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ์ฒด๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค.
VO๋Š” ๋„๋ฉ”์ธ ๊ฐ์ฒด์˜ ์ผ์ข…์ด๋ฉฐ, ๋ณดํ†ต ๊ธฐ๋ณธํ‚ค๋กœ ์‹๋ณ„ ๊ฐ’์„ ๊ฐ–๋Š” Entity์™€ ๊ตฌ๋ณ„ํ•ด์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ ‡๋‹ค๋ฉด VO๋Š” ์–ด๋–ค ์กฐ๊ฑด๋“ค์— ์˜ํ•ด ๊ตฌ๋ถ„ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์•Œ์•„๋ด…์‹œ๋‹ค.

1. equals & hash code ๋ฉ”์„œ๋“œ ์žฌ์ •์˜

์ผ๋ฐ˜์ ์œผ๋กœ ํƒ€์ž…์ด ๊ฐ™๊ณ , ๋‚ด๋ถ€์˜ ์†์„ฑ ๊ฐ’๋„ ๊ฐ™์€ ๋‘ ๊ฐ์ฒด๊ฐ€ ์žˆ๋‹ค๋ฉด, ๋‘ ๊ฐ์ฒด๋Š” ๊ฐ™์€ ๊ฐ์ฒด๋ผ๊ณ  ์ทจ๊ธ‰ํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์˜คํžˆ๋ ค ๋‹ค๋ฅด๋‹ค๊ณ  ํ•˜๋Š”๊ฒŒ ๋” ์ด์ƒํ•˜๋‹ค๊ณ  ์ƒ๊ฐ ํ•  ์ˆ˜ ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ๋“ค๋ฉด, ๋นจ๊ฐ„์ƒ‰์ด ๋นจ๊ฐ„์ƒ‰๊ณผ ๋‹ค๋ฅด๋‹ค๊ณ  ํ•˜๋Š”๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ, ๊ฐ’์ด ๊ฐ™์€ ๋‘ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ฐ์ฒด๋ฅผ ๋น„๊ตํ•ด๋ณด๋ฉด ๋‘˜์€ ์„œ๋กœ ๋‹ค๋ฅธ ๊ฐ์ฒด๋กœ ๋น„๊ต๋ฉ๋‹ˆ๋‹ค.
์ขŒํ‘œ๋ฅผ ์˜ˆ์‹œ๋กœ ํ•œ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด,

    public class Point {
        private int x;
        private int y;

        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }

    @Test
    public void isPointEquals() {
        Point point1 = new Point(1, 2);
        Point point2 = new Point(1, 2);

        // point1 != point2
        Assertions.assertThat(point1 == point2).isFalse();
    }

ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ ๊ฐ™์€ ๊ฐ’์„ ๊ฐ€์ง„ ์ขŒํ‘œ 1๊ณผ ์ขŒํ‘œ2๋Š” ๋‹ค๋ฅธ ๊ฐ์ฒด๋ผ๋Š”๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋ถ„๋ช… ์‚ฌ๋žŒ์ด ๋ณด๊ธฐ์—” ๊ฐ™์€ ๊ฐ’์„ ๊ฐ€์ง„ ์ขŒํ‘œ๋‹ˆ ๊ฐ™์€ ์ขŒํ‘œ์ธ๋ฐ ๋‹ค๋ฅธ ์ขŒํ‘œ๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.
์ด๋Ÿฌํ•œ ์ ์€ ์šฐ๋ฆฌ๊ฐ€ ํ˜„์‹ค๊ณผ ๊ฐ™์€ ๋„๋ฉ”์ธ์„ ์„ค๊ณ„ํ•˜๊ณ  ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ ์— ์žˆ์–ด ํ˜ผ๋ž€์„ ์ค๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋™์ผ์„ฑ ๋น„๊ต์™€ ๋™๋“ฑ์„ฑ ๋น„๊ต์˜ ์ฐจ์ด์— ๋Œ€ํ•ด ์•Œ์•„์•ผํ•ฉ๋‹ˆ๋‹ค.

๋™์ผ์„ฑ ๋น„๊ต
๐Ÿ‘‰ ๋™์ผ์„ฑ ๋น„๊ต๋Š” ํ•ด๋‹น ๊ฐ์ฒด๊ฐ€ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ์ฃผ์†Œ ๊ฐ’์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
ย ย ย ย ย ย ํ•˜์ง€๋งŒ ํ˜„์žฌ ์ƒํƒœ์—์„œ Point1๊ณผ Point2๊ฐ€ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๋ฉ”๋ชจ๋ฆฌ์˜ ์ฃผ์†Œ ๊ฐ’์€ ๋‹ค๋ฅด๋ฉฐ,
ย ย ย ย ย ย ์ด ์ฃผ์†Œ ๊ฐ’์€ ์ž„์˜๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋™๋“ฑ์„ฑ ๋น„๊ต
๐Ÿ‘‰ ๋™๋“ฑ์„ฑ ๋น„๊ต๋Š” ๊ฐ์ฒด๊ฐ€ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š” ์†์„ฑ ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ๊ฐ์ฒด๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
ย ย ย ย ย ย ์ด๋Ÿฌํ•œ ๋™๋“ฑ์„ฑ ๋น„๊ต๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” equals ๋ฉ”์†Œ๋“œ๋ฅผ ์žฌ์ •์˜ํ•จ์œผ๋กœ์จ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.
ย ย ย ย ย ย equals ๋ฉ”์†Œ๋“œ ์žฌ์ •์˜ ์‹œ์—๋Š” ์–ด๋– ํ•œ ์†์„ฑ ๊ฐ’๋“ค์„ ๊ธฐ์ค€์œผ๋กœ ๋™๋“ฑ์„ฑ์„ ๋น„๊ตํ•  ๊ฒƒ์ธ์ง€ ์ •ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

์œ„์˜ Point ๊ฐ์ฒด์˜ equals ๋ฉ”์†Œ๋“œ์™€ hashCode ๋ฉ”์†Œ๋“œ๋ฅผ ์žฌ์ •์˜ ํ•ด๋ด…์‹œ๋‹ค.

...
        // equals & hashcode ์žฌ์ •์˜
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            final Point point = (Point) o;
            return x == point.x &&
                y == point.y;
        }

        @Override
        public int hashCode() {
            return Objects.hash(x, y);
        }
...

๐Ÿ”‘๊ฐ์ฒด์˜ hash code๋Š” ๊ฐ์ฒด๋ฅผ ์‹๋ณ„ํ•  ํ•˜๋‚˜์˜ ์ •์ˆ˜ ๊ฐ’์„ ๊ฐ€๋ฆฌํ‚ค๊ณ ,
ย ย ย ย ย ย ์žฌ์ •์˜ ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ ๊ฐ’์„ ์‚ฌ์šฉํ•ด์„œ ํ•ด์‰ฌ ๊ฐ’์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
ย ย ย ย ย ย ๊ฐ์ฒด์˜ ๋™๋“ฑ์„ฑ์„ ๋น„๊ตํ•  ๋•Œ๋Š” hashCode ๋ฉ”์†Œ๋“œ๋ฅผ ์žฌ์ •์˜ํ•˜์—ฌ,
ย ย ย ย ย ย ํŠน์ • ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ๊ฐ™์€ ํ•ด์‰ฌ ์ฝ”๋“œ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๊ณ ,
ย ย ย ย ย ย ์ด๋Š” ํ•ด์‰ฌ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ์ปฌ๋ ‰์…˜ ๋“ฑ์—์„œ ๊ฐ์ฒด๋ฅผ ๋น„๊ตํ•˜๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์žฌ์ •์˜ ํ•œ equals ๋ฉ”์†Œ๋“œ์™€ hashCode ๋ฉ”์†Œ๋“œ๋ฅผ Test ํ•ด๋ด…์‹œ๋‹ค.

...
    @Test
    public void isPointAttributesEquals() {
        Point point1 = new Point(1, 2);
        Point point2 = new Point(1, 2);

        // point1 == point2
        Assertions.assertThat(point1.equals(point2)).isTrue();
        // point1.hashCode() == point2.hashCode()
        Assertions.assertThat(point1.hashCode() == point2.hashCode()).isTrue();
    }
...

์ด์ฒ˜๋Ÿผ equals ๋ฉ”์†Œ๋“œ์™€ hashCode ๋ฉ”์†Œ๋“œ๋ฅผ ์žฌ์ •์˜ํ•˜๋ฉด,
VO๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์†์„ฑ๊ฐ’์ด ๊ฐ™์€ ๊ฐ์ฒด๋Š” ๊ฐ™์€ ๊ฐ์ฒด์ž„์„ ๋ณด์žฅํ•˜๋ฉด์„œ VO๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. ์ˆ˜์ •์ž(Setter)๊ฐ€ ์—†๋Š” ๋ถˆ๋ณ€(Immutable) ๊ฐ์ฒด

Entity์™€ ๊ฐ™์€ ๊ฒฝ์šฐ ๋ณ„๋„์˜ ์‹๋ณ„ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—
๋‚ด๋ถ€ ์†์„ฑ ๊ฐ’์ด ๋ณ€๊ฒฝ๋œ๋‹ค๊ณ  ํ•˜๋”๋ผ๋„ ๊ฐ™์€ ๊ฐ์ฒด๋กœ ๊ณ„์† ์ธ์‹ํ•˜๊ณ  ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ์†์„ฑ ๊ฐ’ ์ž์ฒด๊ฐ€ ์‹๋ณ„ ๊ฐ’์ธ VO๋Š” ์†์„ฑ ๊ฐ’์ด ๋ฐ”๋€Œ๊ฒŒ ๋˜๋ฉด ์‹๋ณ„ ๊ฐ’๋„ ๋ฐ”๋€Œ๊ฒŒ ๋˜์–ด ์ถ”์ ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ ,
๋ณต์‚ฌ ๋  ๋•Œ๋Š” ์˜๋„์น˜ ์•Š์€ ๊ฐ์ฒด๋“ค์ด ํ•จ๊ป˜ ๋ณ€๊ฒฝ๋˜๋Š” ๋ฌธ์ œ๋ฅผ ์œ ๋ฐœํ•ฉ๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ VO๋Š” ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” ๋ถˆ๋ณ€ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ๋ฅผ ํ†ตํ•ด ๋ถˆ๋ณ€ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์•ผํ•˜๋Š” ์ด์œ ๋ฅผ ์•Œ์•„๋ด…์‹œ๋‹ค.

    @Getter
    @Setter
    @NoArgsConstructor
    @EqualsAndHashCode
    @ToString
    public class Subsidy {
        private String country;
        private String category;
        private int familyCount;

    }

    @Test
    public void ๋ณด์กฐ๊ธˆ์ง€๊ธ‰() {

        Subsidy ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ = new Subsidy();
        ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ.setCountry("ํ•œ๊ตญ");
        ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ.setCategory("๊ทผ๋กœ์žฅ๋ ค๊ธˆ");
        ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ.setFamilyCount(1);

        System.out.println("์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ : " + ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ);
        // ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ = (country=ํ•œ๊ตญ, category=๊ทผ๋กœ์žฅ๋ ค๊ธˆ, familyCount=1)

        Subsidy ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ = ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ;

        System.out.println("๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ : " + ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ);
        // ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ = (country=ํ•œ๊ตญ, category=๊ทผ๋กœ์žฅ๋ ค๊ธˆ, familyCount=1)

        ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ.setCategory("์ž๋…€์žฅ๋ ค๊ธˆ");
        ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ.setFamilyCount(4);

        System.out.println("์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ : " + ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ);
        System.out.println("๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ : " + ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ);
        // ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ = (country=ํ•œ๊ตญ, category=์ž๋…€์žฅ๋ ค๊ธˆ, familyCount=4)
        // ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ = (country=ํ•œ๊ตญ, category=์ž๋…€์žฅ๋ ค๊ธˆ, familyCount=4)

    }

Lombok ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

โš™๏ธ๊ตญ๊ฐ€์˜ ๋ณด์กฐ๊ธˆ์„ ์ •ํ•˜๋Š” ๊ตญ๊ฐ€์ด๋ฆ„, ๋ณด์กฐ๊ธˆ ๋ฒ”์ฃผ, ๊ฐ€์กฑ ์ˆ˜๋ฅผ ๊ฐ’์œผ๋กœ ๊ฐ–๋Š” Subsidy ๋ผ๋Š” VO๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
ย ย ย ย ย ๋‘ ๊ฐ€๊ตฌ์˜ ๋ณด์กฐ๊ธˆ ์‹ ์ฒญ์ด ๋“ค์–ด์™”๊ณ , ๊ฐ™์€ 1์ธ๊ฐ€๊ตฌ ๊ทผ๋กœ์žฅ๋ ค๊ธˆ์„ ์‹ ์ฒญํ•˜์—ฌ ๊ฐ์ฒด๋ฅผ ๋ณต์‚ฌํ•ด์„œ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
ย ย ย ย ย ์–ด์ฐจํ”ผ ๊ฐ™์€ ๋ณด์กฐ๊ธˆ์ด๊ณ  ๊ฐ€์กฑ ์ˆ˜๊ฐ€ ๊ฐ™์œผ๋‹ˆ ๋ฌธ์ œ๋  ๊ฒŒ ์—†๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿงจ๊ทธ๋Ÿฐ๋ฐ ์ด ๋•Œ, ๋‘ ๋ฒˆ์งธ ๊ฐ€๊ตฌ์˜ ์‹ฌ์‚ฌ ๊ฒฐ๊ณผ๊ฐ€ 4์ธ๊ฐ€๊ตฌ์˜ ์ž๋…€์žฅ๋ ค๊ธˆ ๋Œ€์ƒ์ž๋กœ ๋‚˜์™”์Šต๋‹ˆ๋‹ค.
ย ย ย ย ย ๋ถ„๋ช… ๋‘ ๋ฒˆ์งธ ๊ฐ€๊ตฌ์˜ ๋‚ด์šฉ๋งŒ ๋ณ€๊ฒฝํ–ˆ์„ ๋ฟ์ธ๋ฐ, ์ถœ๋ ฅ๋œ ๊ฒฐ๊ณผ๋ฅผ ์‚ดํŽด๋ณด๋ฉด,
ย ย ย ย ย ์ฒซ ๋ฒˆ์งธ ๊ฐ€๊ตฌ๊นŒ์ง€ 4์ธ ๊ฐ€๊ตฌ ์ž๋…€์žฅ๋ ค๊ธˆ์œผ๋กœ ๋ณด์กฐ๊ธˆ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”๋ฌธ์ œ์˜ ์‹œ์ž‘์€ ์‚ฌ์šฉํ•  ๊ฐ’์ด ๊ฐ™๋‹ค๊ณ  ํ•ด์„œ ์ฒซ ๋ฒˆ์งธ ๊ฐ€๊ตฌ ๊ฐ’์„ ๋‘ ๋ฒˆ์งธ ๊ฐ€๊ตฌ์— ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•œ ๊ณณ ์ž…๋‹ˆ๋‹ค.
ย ย ย ย ย ํ˜„์žฌ ๋‘ ๋ฒˆ์งธ ๊ฐ€๊ตฌ๋Š” ์ฒซ ๋ฒˆ์งธ ๊ฐ€๊ตฌ ๊ฐ’์„ ๋ณต์‚ฌํ•œ ๊ฒƒ์ด ์•„๋‹Œ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ๋ฅผ ๋ณต์‚ฌํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—
ย ย ย ย ย ๋ณด์กฐ๊ธˆ ๋‚ด์šฉ์ด ๋ฐ”๋€Œ๋ฉด ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์— ์ €์žฅ๋œ ์‹ค์ œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.
ย ย ย ย ย ๋‹น์—ฐํžˆ ๊ฐ™์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ์ฒซ ๋ฒˆ์งธ ๊ฐ€๊ตฌ์˜ ๋‚ด์šฉ๋„ ๋ณ€๊ฒฝ๋œ ๊ฐ’์„ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ๋˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์น˜๋ช…์ ์ธ ์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ VO๋Š” ํ•œ๋ฒˆ ์„ค์ •๋œ ๊ฐ’์ด ๋ณ€ํ•˜์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ฆ‰, ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ •์ž(Setter)๊ฐ€ ์—†์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿผ ์ˆ˜์ •์ž ์—†์ด ์–ด๋–ป๊ฒŒ VO์— ๊ฐ’์„ ์„ค์ •ํ• ๊นŒ์š”?
์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ, ๊ฐ’์ด ํ•œ ๋ฒˆ๋งŒ ํ• ๋‹น๋˜๊ณ  ์ดํ›„๋กœ๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋„๋ก ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด์„œ Subsidy ๊ฐ์ฒด๋ฅผ ๋ถˆ๋ณ€(Immutable)๋กœ ๋งŒ๋“ค๋ฉด ์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ๋ถˆ๋ณ€ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค.

    @Getter
    @AllArgsConstructor
    @EqualsAndHashCode
    @ToString
    public class Subsidy {
        private String country;
        private String category;
        private int familyCount;

    }

    @Test
    public void ๋ณด์กฐ๊ธˆ์ง€๊ธ‰() {

        Subsidy ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ = new Subsidy("ํ•œ๊ตญ", "๊ทผ๋กœ์žฅ๋ ค๊ธˆ", 1);
        System.out.println("์ฒซ๋ฒˆ์งธ ๊ฐ€๊ตฌ : " + ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ);
        // ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ = (country=ํ•œ๊ตญ, category=๊ทผ๋กœ์žฅ๋ ค๊ธˆ, familyCount=1)

        Subsidy ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ = new Subsidy("ํ•œ๊ตญ", "๊ทผ๋กœ์žฅ๋ ค๊ธˆ", 1);
        System.out.println("๋‘๋ฒˆ์งธ ๊ฐ€๊ตฌ : " + ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ);
        // ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ = (country=ํ•œ๊ตญ, category=๊ทผ๋กœ์žฅ๋ ค๊ธˆ, familyCount=1)

        // ์‹ฌ์‚ฌ๋กœ ์ธํ•œ ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ ๋ณด์กฐ๊ธˆ ๋ณ€๊ฒฝ
        ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ = new Subsidy("ํ•œ๊ตญ", "์ž๋…€์žฅ๋ ค๊ธˆ", 4);
        System.out.println("์ฒซ๋ฒˆ์งธ ๊ฐ€๊ตฌ : " + ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ);
        System.out.println("๋‘๋ฒˆ์งธ ๊ฐ€๊ตฌ : " + ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ);
        // ์ฒซ๋ฒˆ์งธ๊ฐ€๊ตฌ = (country=ํ•œ๊ตญ, category=๊ทผ๋กœ์žฅ๋ ค๊ธˆ, familyCount=1)
        // ๋‘๋ฒˆ์งธ๊ฐ€๊ตฌ = (country=ํ•œ๊ตญ, category=์ž๋…€์žฅ๋ ค๊ธˆ, familyCount=4)

    }

์ˆ˜์ •์ž(Setter)๋ฅผ ์ œ๊ฑฐํ–ˆ์œผ๋ฏ€๋กœ ์‹ฌ์‚ฌ๋กœ ์ธํ•ด ๋ณด์กฐ๊ธˆ์ด ๋ณ€๊ฒฝ๋œ๋‹ค๋ฉด,
์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜๊ณ  ์žฌํ• ๋‹น ํ•ด์ฃผ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์†์„ฑ ๊ฐ’ ์ž์ฒด๊ฐ€ ์‹๋ณ„ ๊ฐ’์˜ ์—ญํ• ์„ ํ•˜๋Š” VO์˜ ์ •์ฒด์„ฑ์„ ์ง€ํ‚ค๋ฉด์„œ,
๋ฌด์—‡๋ณด๋‹ค๋„ ์˜๋„์น˜ ์•Š์€ ๋ณ€๊ฒฝ์„ ๋ง‰์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์œ ์ง€๋ณด์ˆ˜์—๋„ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.

๐Ÿ’ก VO๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ์ด์ 

VO๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ ๋„ ์ถฉ๋ถ„ํžˆ ์›์‹œ ํƒ€์ž…์˜ ๊ฐ’๋งŒ ๊ฐ€์ง€๊ณ  ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ VO๋ฅผ ํ†ตํ•ด ๋„๋ฉ”์ธ์„ ์„ค๊ณ„ํ•œ๋‹ค๋ฉด, ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ํ•ด๋‹น ๊ฐ์ฒด์•ˆ์— ์ œ์•ฝ์‚ฌํ•ญ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ ์ƒ์„ฑ๋  ์ธ์Šคํ„ด์Šค๊ฐ€ ์ •ํ•ด์ ธ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š” ๋ฏธ๋ฆฌ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ด๋†“๊ณ 
์บ์‹ฑํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๋†’์ด๋Š” ๋ฐฉ๋ฒ•๋„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, Entity์˜ ์›์‹œ ๊ฐ’๋“ค์„ VO๋กœ ํฌ์žฅํ•˜๋ฉด
Entity๊ฐ€ ์ง€๋‚˜์น˜๊ฒŒ ๊ฑฐ๋Œ€ํ•ด์ง€๋Š” ๊ฒƒ์„ ๋ง‰์„ ์ˆ˜ ์žˆ์–ด์„œ,
ํ…Œ์ด๋ธ” ๊ด€์ ์ด ์•„๋‹Œ ๊ฐ์ฒด ์ง€ํ–ฅ์ ์ธ ๊ด€์ ์œผ๋กœ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ปฌ๋ ‰์…˜๋„ VO์˜ ์—ญํ• ์„ ํ•œ๋‹ค๋ฉด, ์ผ๊ธ‰ ์ปฌ๋ ‰์…˜๊ณผ ๊ฐ™์€ ๋ถˆ๋ณ€๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿค” DTO?

๊ทธ๋ ‡๋‹ค๋ฉด DTO๋Š” ๋ฌด์—‡์ผ๊นŒ์š”?
Martin Fowler์˜ ๊ธ€์„ ์ฝ์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Martin Fowler์˜ Data-Transfer-Object

โœ๐ŸผAn object that carries data between processes in order to reduce the number of method calls.

๐Ÿ”๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ˆ˜๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ํ”„๋กœ์„ธ์Šค ๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋Š” ๊ฐœ์ฒด

โœ๐ŸผAlthough the main reason for using a Data Transfer Object is to batch up what would be multiple
ย ย ย ย ย remote calls into a single call, it's worth mentioning that another advantage is to encapsulate the
ย ย ย ย ย serialization mechanism for transferring data over the wire. By encapsulating the serialization
ย ย ย ย ย like this, the DTOs keep this logic out of the rest of the code and also provide a clear point to
ย ย ย ย ย change serialization should you wish.

๐Ÿ”๋ฐ์ดํ„ฐ ์ „์†ก ๊ฐœ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฃผ๋œ ์ด์œ ๋Š” ์—ฌ๋Ÿฌ ์›๊ฒฉ ํ˜ธ์ถœ์„ ๋‹จ์ผ ํ˜ธ์ถœ๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ
ย ย ย ย ย ์œ ์„ ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๊ธฐ์œ„ํ•œ ์ง๋ ฌํ™” ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์บก์Šํ™”ํ•˜๋Š” ๊ฒƒ์ด ๋˜ ๋‹ค๋ฅธ ์žฅ์ ์ž…๋‹ˆ๋‹ค.
ย ย ย ย ย ์ด์™€ ๊ฐ™์ด ์ง๋ ฌํ™”๋ฅผ ์บก์Šํ™”ํ•จ์œผ๋กœ์จ DTO๋Š” ์ด ๋…ผ๋ฆฌ๋ฅผ ๋‚˜๋จธ์ง€ ์ฝ”๋“œ์—์„œ ์ œ์™ธํ•˜๊ณ  ์›ํ•˜๋Š” ๊ฒฝ์šฐ
ย ย ย ย ย ์ง๋ ฌํ™”๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜์žˆ๋Š” ๋ช…ํ™•ํ•œ ์ง€์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ“š ์ฐธ๊ณ ์ž๋ฃŒ

1๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2021๋…„ 1์›” 7์ผ

๊ธ€ ์ž˜์ฝ์—ˆ์Šต๋‹ˆ๋‹ค. VO์˜ ๋ถˆ๋ณ€๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์ง€๋Š” ์ด์œ ์ค‘ ์ถ”์ ์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ์ ์„ ๋“ค์–ด์ฃผ์…จ๋Š”๋ฐ ์ถ”์ ์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฒƒ๊ณผ ๋ถˆ๋ณ€์€ ์–ด๋–ค ๊ด€๊ณ„๊ฐ€ ์žˆ๋Š” ์ง€ ์ดํ•ด๊ฐ€ ์•ˆ๊ฐ€์„œ ์˜ˆ์‹œ๋กœ ์„ค๋ช… ๊ฐ€๋Šฅํ• ๊นŒ์š”?

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ