๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • ์žฅ์›์ต ๊ธฐ์ˆ ๋ธ”๋กœ๊ทธ
๐Ÿ”ฌapplication/- DDD

[DDD] Entity ์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ ์ด์•ผ๊ธฐ

by Wonit 2023. 7. 15.

๋ชฉ์ฐจ

  • ์„œ๋ก 
  • entity ๋ž€ ๋ฌด์—‡์ธ๊ฐ€
    • ๋„๋ฉ”์ธ ์„ธ์ƒ ์ด์•ผ๊ธฐ
    • vo ์™€ entity ๊ฐ€ ์žˆ๋‹ค
    • ๋ฌด์—‡์ด entity ๋ฅผ ๊ฒฐ์ •ํ•˜๋Š”๊ฐ€?
  • entity ์˜ ์†์„ฑ
    • invariant
    • immutable
  • entity ์˜ ํ–‰์œ„
    • object autonomy
    • self encapsulation

 

์„œ๋ก 

 

DDD ์—์„œ๋Š” ์œ ๋น„์ฟผํ„ฐ์Šค ๋žญ๊ท€์ง€๋ฅผ ํ†ตํ•˜์—ฌ ์—ฌ๋Ÿฌ ์ดํ•ด๊ด€๊ณ„์ž๋“ค๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ์˜์—ญ์— ๋Œ€ํ•ด ํ•ฉ์˜ (Cosensus) ๋ฅผ ๋งž์ถ”๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋ฌผ๋กœ ๋„๋ฉ”์ธ์— ๋Œ€ํ•œ ๋ชจ๋ธ๋ง ์ฆ‰, domain model ์ด ์‚ฐ์ถœ๋œ๋‹ค.

 

์ด domain model ์€ ์—ฌ๋Ÿฌ ์ƒํ˜ธ์ž‘์šฉ์„ ํ†ตํ•ด ๋น„์ฆˆ๋‹ˆ์Šค๋ฅผ ์ง€ํƒฑํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, domain model ์˜ ๊ฐ€์žฅ ๊ธฐ์ดˆ๊ฐ€ ๋˜๋Š” ๋‘๊ฐ€์ง€ ํƒ€์ž…์˜ ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•œ๋‹ค.

 

  1. Entity
  2. Value Object

 

์˜ค๋Š˜์€ ์ด Entity ์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋„๋ก ํ•˜์ž

 

Entity ๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

์—”ํ‹ฐํ‹ฐ entity, spring ์„ ์ด์šฉํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋Š” entity ์— ๋Œ€ํ•ด์„œ ์ตํžˆ ๋“ค์–ด์™”๊ณ  ์‚ฌ์šฉํ•ด ์™”์„ ๊ฒƒ์ด๋‹ค.

 

๋ฐ”๋กœ Spring Data JPA ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฐœ๋… ํ˜น์€ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ, Database Table ์— ๋Œ€์‘๋˜๋Š” ORM ์˜ ํ•ต์‹ฌ์ด ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ”๋กœ Entity ๋ผ๊ณ  ํ‘œํ˜„ํ•˜๊ณ  ์ฝ”๋“œ๋ ˆ๋ฒจ์—์„  ์ด๋ฅผ @Entity ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ง€์ •ํ•œ๋‹ค.

 

DDD ์—์„œ ๋งํ•˜๋Š” Entity ์™€ JPA ์—์„œ ์ด์•ผ๊ธฐํ•˜๋Š” Entity ๊ฐ€ ํฌ๊ฒŒ ๋‹ค๋ฅผ๋ฐ”๋Š” ์—†์ง€๋งŒ, ์š”์ฆ˜ ์›น ๊ฐœ๋ฐœ์˜ ํ๋ฆ„์—์„œ ๋ณด๋ฉด ๋‹ค๋ฅด๋‹ค๋Š” ๊ฒƒ์„ ๋Š๋ผ๊ณ  ์žˆ๋‹ค.

 

๊ทธ๋Ÿผ entity ๋Š” ๋ฌด์—‡์ธ๊ฐ€?

 

Entity ์ž์ฒด์˜ ๊ฐœ๋…์€ ์˜์†์„ฑ ์žฅ์น˜์™€ ๋ฌด๊ด€ํ•˜๊ณ  DB field ๋ฅผ mapping ํ•˜๋Š” java ๊ฐ์ฒด๊ฐ€ ์•„๋‹ˆ๋‹ค.

 

์šฐ๋ฆฌ๊ฐ€ ํ•ต์‹ฌ ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ผ ๋ถ€๋ฅด๋Š” ๊ฒƒ๋“ค์€ ๋Œ€๋ถ€๋ถ„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์˜์†ํ™”๋ฅผ ํ•˜๋Š”๋ฐ, ์ด์œ ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์‚ดํŽด๋ณผ ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

 

์™œ ์šฐ๋ฆฌ๋Š” ์˜์†ํ™”๋ฅผ ํ•˜๋Š” ๊ฒƒ์ผ๊นŒ?

 

์˜์†ํ™”์˜ ๋ณธ์งˆ์€ ๋ฐ”๋กœ ์—ฐ์†์„ฑ์ด๋‹ค.

 

์–ด๋–ค ๊ฐ์ฒด๊ฐ€ ๊ฐ€์ ธ์•ผ ํ•˜๋Š” ์ฑ…์ž„์„ ์—ฐ์†์ ์œผ๋กœ ์ถ”์ ํ•˜๊ณ  ๊ด€์ฐฐํ•˜๊ณ ์‹ถ์€ ๊ฒƒ์ด๋‹ค.

 

์–ด๋Š ์ˆœ๊ฐ„์—๋Š” ๊ทธ ํ•ต์‹ฌ ๊ฐ์ฒด์—๊ฒŒ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ผ๋Š” ๋ช…๋ น(command) ๋ฅผ ํ•  ๊ฒƒ์ด๊ณ , ๊ทธ ํ–‰์œ„๋Š” ๊ฐ€๋Šฅํ•œ ์˜์›ํžˆ ์—ฐ์†์ ์ด์–ด์•ผ ํ•œ๋‹ค.

 

๊ฐ€๋Šฅํ•œ ์—ฐ์†์ ์ด๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ˆ˜๋งŽ์€ ๊ฐ์ฒด๋“ค์„ ๊ฐ๊ฐ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

 

๊ทธ๋•Œ ์šฐ๋ฆฌ๋Š” ๊ทธ ํ•ต์‹ฌ ๊ฐ์ฒด์—๊ฒŒ ์‹๋ณ„์ž ๋ผ๋Š” ๊ฒƒ์„ ํ• ๋‹นํ•˜๊ฒŒ ๋˜๊ณ , ์ด๊ฒƒ์„ ๋ฐ”๋กœ Entity ๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

 

์ด๋Ÿฌํ•œ entity ๋“ค์„ ์˜์›ํžˆ ์‹คํ–‰๋  ์ˆ˜ ์—†๋Š” ํ˜„์‹ค์˜ computing engine ์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ์œ ์ง€์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ ์šฐ๋ฆฌ๋Š” database ๋กœ ์˜์†ํ™”๋ผ๋Š” ๊ณผ์ •์„ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

์ฆ‰, entity ๋Š”, ๋„๋ฉ”์ธ ๊ฐ์ฒด๊ฐ€ ์—ฐ์†์„ฑ์„ ๊ฐ€์ง€๊ธฐ ์œ„ํ•ด์„œ ์‹๋ณ„์ž๋ฅผ ๋ถ€์—ฌ๋ฐ›์•„, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์˜์†ํ™”๊ฐ€ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๋‹ค์‹œ ๋งํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ’์„ ๊บผ๋‚ด์˜ค๊ธฐ ์œ„ํ•œ ์‹๋ณ„์ž๊ฐ€ ์žˆ๊ธฐ์— entity ๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์ด๋‹ค

 

Entity ์™€ ์‹๋ณ„์ž์— ๋Œ€ํ•ด์„œ๋Š” ํ•  ์ด์•ผ๊ธฐ๊ฐ€ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์—, Entity ์™€ Identifier(์‹๋ณ„์ž) ์— ๋Œ€ํ•œ ์ด์•ผ๊ธฐ ์—์„œ ๋”ฐ๋กœ ์ด์•ผ๊ธฐํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค.

 

์ •๋ฆฌ

  • Domain Model ์€ ํฌ๊ฒŒ 2๊ฐ€์ง€ ๊ฐ์ฒด๋กœ ๋‚˜๋‰จ
    • Entity
    • Value Object
  • Entity ์™€ Value Object ๋ฅผ ๊ตฌ๋ถ„์ง“๋Š” ๊ฒƒ์€ ์‹๋ณ„์„ฑ์ž„
    • Entity: ๋„๋ฉ”์ธ ๋ชจ๋ธ ๋‚ด์—์„œ ์‹๋ณ„์ด ๋˜์–ด์•ผ ํ•˜๋Š” ๊ฐ์ฒด
    • Value Object: ๋„๋ฉ”์ธ ๋ชจ๋ธ ๋‚ด์—์„œ ์‹๋ณ„์ด ๋˜์ง€ ์•Š์•„๋„ ๋˜๋Š” ๋‹จ์ˆœํ•œ ๊ฐ’ ๊ฐ์ฒด
  • Entity ์˜ ํ•ต์‹ฌ์€ DB Table ์˜ Relation Mapping Object ๊ฐ€ ์•„๋‹˜

 

๋‚ด๊ฐ€ ๋งŒ๋“  Entity ๋Š” DTO ๊ทธ ์ž์ฒด

 

๋‚ด๊ฐ€ ์ง„ํ–‰ํ–ˆ๋˜ ๋งŽ์€ ์˜ˆ์ „ ํ”„๋กœ์ ํŠธ๋“ค์˜ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ๋ณด๋ฉด Entity ๊ฐ์ฒด๋Š” Database ์˜ Field ์™€ 1:1 ๋Œ€์‘๋˜๋Š” ๋‹จ์ˆœ DTO ๊ทธ ์ด์ƒ๋„, ์ดํ•˜๋„ ์•„๋‹ˆ์—ˆ๋‹ค.

 

@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "Username ์ด ํ•„์š”ํ•จ")
    @Size(min = 3, max = 20, message = "3 ์ด์ƒ 20 ์ดํ•˜")
    private String username;

    @NotBlank(message = "Email ์ด ํ•„์š”ํ•จ")
    @Email(message = "email ํ˜•์‹์ด ์•„๋‹˜")
    private String email;

    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime modifiedAt;

    // ํ–‰์œ„ ๋ฉ”์„œ๋“œ
}

 

DTO ๋Š” ํ–‰์œ„๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฉ”์„œ๋“œ๋„ค, ๋ญ DTO ๋Š” setter ๊ฐ€ ์—†์–ด์•ผ ํ•˜๋„ค, ์ด๋Ÿฐ ์—ฌ๋Ÿฌ ์ด์•ผ๊ธฐ๋ฅผ ๋“ค์—ˆ์ง€๋งŒ, ํ–‰์œ„๊ฐ€ ์กด์žฌํ•˜๋˜ setter ๊ฐ€ ์กด์žฌํ•˜๋˜ ์ค‘์š”ํ•œ๊ฒŒ ์•„๋‹ˆ๋‹ค.

 

๊ฐ์ฒด๋“ค ์‚ฌ์ด์—์„œ ์ถฉ๋ถ„ํ•œ ์ž์œจ์„ฑ๊ณผ ์ฑ…์ž„์„ ๊ฐ–์ง€ ๋ชปํ•œ ๊ฐ์ฒด๋Š” ๋‚˜๋Š” ๋ชจ๋‘ Value ํ˜น์€ DTO ๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

 

์ด DTO ๋ฅผ Database ์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ์‹ค์ œ ํ–‰์œ„๋Š” ๋ชจ๋‘ Service ๋ผ๊ณ  ๋ถ€๋ฅด๋Š” ์ด์ƒํ•œ ๊ฐ์ฒด์—์„œ transactional script ๋กœ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

 

ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๋ฉด ๋จผ์ € ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  Entity ๊ฐ์ฒด ๋งŒ๋“œ๋Š๋ผ ๋งŽ์€ ์‹œ๊ฐ„์„ ํ—ˆ๋น„ํ–ˆ์—ˆ๋Š”๋ฐ, ์ด๋Ÿฐ ํ–‰์œ„์™€ ์‹œ๊ฐ„ ์ž์ฒด๊ฐ€ ๊ธธ์–ด์งˆ ์ˆ˜๋ก anemic ํ•œ ๋ชจ๋ธ์ด ๋‚˜์˜ค๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

Entity ๋ฅผ ์„ค๊ณ„ํ•˜๋Š” ์‹œ์ ์—๋Š” Database ์™€ ์ „ํ˜€ ๋ฌด๊ด€ํ•˜๋‹ค.

 

Entity ์˜ ํ–‰์œ„์™€ ์†์„ฑ์„ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์—ฌ๋Ÿฌ ์ดํ•ด๊ด€๊ณ„์ž๋“ค๊ณผ์˜ ํ˜‘์˜์™€ Event Storming ์„ ๋น„๋กฏํ•œ ๋„๋ฉ”์ธ ํƒ๊ตฌ ๊ณผ์ •์„ ๊ฑฐ์ณ ํƒ„์ƒํ•˜๋Š” Ubiqutious Language ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ˆ˜๋ฐ˜๋˜์–ด์•ผ ํ•œ๋‹ค.

 

์ด๋Ÿฌํ•œ UL ์„ ๋งŒ๋“ค๊ณ , ๊ทธ ์–ธ์–ด๋“ค ์ž์ฒด๋ฅผ ์†Œ์Šค์ฝ”๋“œ์— ๋…น์ด๋Š” ๊ฒƒ์ด๋‹ค.

 

๊ทธ๋Ÿผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋„๋ฉ”์ธ ๊ฐ์ฒด์˜ ํ–‰์œ„๊ฐ€ ๋– ์˜ค๋ฅผ ๊ฒƒ์ด๊ณ , ์ ์ ˆํ•œ ๊ฐ์ฒด์˜ field ๊ฐ€ ๋– ์˜ค๋ฅด๊ฒŒ ๋œ๋‹ค.

 

Entity ์˜ ์†์„ฑ, entity field

 

๋„๋ฉ”์ธ ๋ ˆ์ด์–ด์— ์œ„์น˜ํ•˜๋Š”, ๋„๋ฉ”์ธ ๋ชจ๋ธ์— ์กด์žฌํ•˜๋Š” Entity ๋‚˜ Value Object ๋Š” ์ž์œจ์„ฑ์„ ๊ฐ€์ง€๊ณ  ์ฑ…์ž„์„ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค.

 

์ด๋Ÿฌํ•œ Field ์˜ ํŠน์„ฑ์„ ํ•œ ๋ฒˆ ์‚ดํŽด๋ณด์ž.

 

field ํŠน์„ฑ 1. Invariant

 

๋„๋ฉ”์ธ์„ ๋ชจ๋ธ๋งํ•˜๋ฉด ์—”ํ‹ฐํ‹ฐ์˜ ์–ด๋–ค ์†์„ฑ๋“ค์€ invariant ํ•œ ํŠน์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค๋ฉด, ๋„๋ฉ”์ธ ๊ทœ์น™์ค‘ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž

  • ์œ ์ €๋Š” ๋กœ๊ทธ์ธ์„ ์œ„ํ•ด์„œ ๋Œ€ํ‘œ ์ด๋ฉ”์ผ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด
    • ์ด ๋Œ€ํ‘œ ์ด๋ฉ”์ผ์€ ํ•œ๋ฒˆ ์„ค์ •๋˜๋ฉด ๋ณ€๊ฒฝ๋  ์ˆ˜ ์—†์–ด
  • ์œ ์ €์˜ ์ด๋ฆ„์€ ์–ธ์ œ๋“ ์ง€ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์–ด

 

์—ฌ๊ธฐ์„œ ๋Œ€ํ‘œ ์ด๋ฉ”์ผ์€ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— Invariant ์ด๋‹ค. ํ•œ ๋ฒˆ ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋œ ์‹œ์ ์—์„œ ์ ˆ๋Œ€ ๋ณ€๊ฒฝ๋˜์–ด์„œ๋Š” ์•ˆ๋˜๋Š” ์ค‘์š”ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์ธ ๊ฒƒ์ด๋‹ค.

 

๋ณดํ†ต ์ด๋Ÿฐ ๋ถˆ๋ณ€์‹์ด ํ•„์š”ํ•œ ๊ฐ์ฒด๋Š” ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š” ์ƒ์ˆ˜๋กœ ์ทจ๊ธ‰ํ•ด์•ผํ•œ๋‹ค.

 

java ์—์„œ๋Š” final ํ‚ค์›Œ๋“œ๋ฅผ ํ†ตํ•ด์„œ ํŠน์ • ํ•„๋“œ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์ˆ˜๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

 

final field ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด ์ƒ์„ฑ๋‹จ๊ณ„๋ถ€ํ„ฐ ๊ฐ’์„ ํ• ๋‹นํ•˜์ง€ ์•Š์œผ๋ฉด ์ปดํŒŒ์ผ ํƒ€์ž„์˜ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ํ•ด๋‹นํ•˜๋Š” ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ๋‹ค๋Š” ๋œป์€, ๊ฐ’์ด ์กด์žฌํ•œ๋‹ค๋Š” ๋œป์œผ๋กœ ํ•ด์„ํ•  ์ˆ˜ ์žˆ๋‹ค

 

public class User {
    private final UserId id; // ์‹๋ณ„์ž ์•ˆ์ •์„ฑ
    private final Email email; // ์ƒ์ˆ˜ ์ทจ๊ธ‰
    private UserName username; // immutable ํ•œ reassign
}

 

์‹๋ณ„์ž๋„ ๋ถˆ๋ณ€์‹์ด๋‹ค.

 

์‹๋ณ„์ž ์•ˆ์ •์„ฑ

์ด๋Š” ์‹๋ณ„์ž ์•ˆ์ •์„ฑ์ด๋ผ๊ณ  ๋ถ€๋ฅด๋Š”๋ฐ, ํ•œ ๋ฒˆ ๊ฐ์ฒด๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ธ์Šคํ„ด์Šคํ™”๊ฐ€ ๋˜์—ˆ๋‹ค๋ฉด ๊ทธ ๊ฐ์ฒด๋Š” ๊ณผ๊ฑฐ๋ถ€ํ„ฐ ๋ฐœ์ƒํ–ˆ๋˜ ๋ชจ๋“  ์‚ฌ๊ฑด์˜ ์—ฐ์†์„ฑ์— ๋Œ€ํ•œ ์ด ํ•ฉ์ด๋ฉฐ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ํ‘œํ˜„ํ•œ๋‹ค

 

ํ•˜์ง€๋งŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ธ์Šคํ„ด์Šคํ™”๊ฐ€ ๋œ ์—”ํ‹ฐํ‹ฐ์˜ ์‹๋ณ„์ž๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋ฐ”๋€๋‹ค๋ฉด?

 

์ด๋ฅผํ…Œ๋ฉด ๋‚ด ์ฃผ๋ฏผ๋“ฑ๋ก๋ฒˆํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ๊ทธ๊ฐ„ ์‚ด์•„์™”๋˜ ์ธ์ƒ์ด ํ•˜๋ฃจ์•„์นจ์— ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ๋˜๋Š” ๊ฒƒ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€์ด๋ฏ€๋กœ ์‹๋ณ„์ž์˜ ์•ˆ์ •์„ฑ์€ ์ค‘์š”ํ•˜๋‹ค.

 

์ด๋„ ์—ญ์‹œ invariant ํ•˜๊ฒŒ ๋ถˆ๋ณ€์‹์„ ๋ถ€์—ฌํ•˜์—ฌ ์ƒ์ˆ˜๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค

 

field ํŠน์„ฑ 2. Immutable

 

๋ฐ˜๋ฉด์— ์œ ์ € ์ด๋ฆ„์€ ์–ธ์ œ๋“ ์ง€ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ๋„๋ก ์ƒ์ˆ˜๋กœ ์ทจ๊ธ‰ํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค.

 

field ์ž์ฒด๋ฅผ ์ƒ์ˆ˜๋Š” ์•„๋‹ˆ์ง€๋งŒ ๋‹จ์ˆœ immutable ํ•œ ๊ฐ’ ๊ฐ์ฒด๋กœ ์ทจ๊ธ‰ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค. (๊ฐ’ ๊ฐ์ฒด์— ๋Œ€ํ•ด์„œ๋Š” ์ดํ›„ ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค)

 

field ๋ฅผ Immutable ํ•˜๊ฒŒ ์œ ์ง€ํ•œ๋‹ค๋ฉด ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ด์ ์ด ์žˆ๋‹ค.

 

์šฐ๋ฆฌ๋Š” ๋ฉ”๋ชจ๋ฆฌ์˜ ์ฐธ์กฐ๊ฐ’์„ ์ด์šฉํ•˜๋Š” ์—ฐ์‚ฐ์„ ์ฃผ๋กœ ํ•˜๋Š” Reference-oriented programming ์„ธ์ƒ์— ์‚ด๊ณ  ์žˆ๋‹ค.

 

๋งŒ์•ฝ ํŠน์ • field ์˜ reference ๊ฐ€ ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ๊ณณ์— ๋‚จ์•„์žˆ๊ณ , ๋‹ค๋ฅธ operation ์— ์˜ํ•ด์„œ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์„ ๋ง‰์•„์ค„ ์ˆ˜ ์žˆ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค์–ด๋ณด๋ฉด ๋ณด์ž

 

public class User {
    private Age age;

    public User(int i) {
        this.age = new Age(i);
    }
}

public class Age {
    private int age;

    void plus() {
        this.age++;
    }
}

 

์œ„ ์ฝ”๋“œ์—์„œ Getter ๋‚˜ Equals Hashcode ์žฌ์ •์˜ ์ฝ”๋“œ๋Š” ์ œ๊ฑฐํ•˜์˜€๋‹ค.

 

๋‹ค์Œ ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋Š” ์„ฑ๊ณตํ• ๊นŒ?

 

@Test
void name() {
    User user = new User(0);

    Age age = user.getAge();
    age.plus();

    assertThat(user.getAge()).isEqualTo(new Age(0));
    assertThat(age).isEqualTo(new Age(1));
}

 

์‹คํŒจํ•œ๋‹ค.

 

testcode ๋‚ด์—์„œ ์ฐธ์กฐ๋ฅผ ํ†ตํ•œ ์—ฐ์‚ฐ์ด ์ˆ˜ํ–‰๋˜์–ด user ๊ฐ€ ์†Œ์œ ํ•œ age ์˜ ๋ฉ”๋ชจ๋ฆฌ์˜ ๋ฐ์ดํ„ฐ ์ž์ฒด๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด๋ฒ„๋ ธ์„ ๊ฒƒ์ด๋‹ค.

์ด๋Ÿฐ ์ƒํ™ฉ์„ ๋Œ€๋น„ํ•˜์—ฌ Field ์˜ ๋ชจ๋“  ์—ฐ์‚ฐ์€ Immutable ํ•˜๊ฒŒ ์„ค๊ณ„ํ•œ๋‹ค๋ฉด ์˜ˆ์ƒํ•  ์ˆ˜ ์—†๋Š” side effect ์— ๋Œ€ํ•ด ์•ˆ์ „ํ•œ entity ๋ฅผ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค

์ •๋ฆฌ

  • entity ์˜ ์†์„ฑ์€ db field ์— ์˜ํ•ด ๊ฒฐ์ •๋˜์ง€ ์•Š๋Š”๋‹ค.
    • ๊ฐ์ฒด์˜ ์ฑ…์ž„๊ณผ ์—ญํ• ์— ๋”ฐ๋ผ ๊ฒฐ์ •๋œ๋‹ค
  • entity ์˜ ์†์„ฑ์˜ 2๊ฐ€์ง€ ๋Œ€ํ‘œ ํŠน์„ฑ
    1. invariant
    2. immutable

Entity ์˜ ํ–‰์œ„

 

entity ๋Š” ๋„๋ฉ”์ธ ๋ชจ๋ธ์˜ ํ•ต์‹ฌ ๊ฐ์ฒด์ด๋‹ค.

 

๊ทธ ์—ญํ• ๊ณผ ์ฑ…์ž„์ด๋ผ๋Š” ๊ฒƒ์€ ์•ž์„  ์†์„ฑ์„ ์ด์•ผ๊ธฐํ•  ๋•Œ์™€ ๋™์ผํ•˜๊ฒŒ Ubiqutious Language ๋กœ ๋ถ€ํ„ฐ ์ถœ๋ฐœํ•˜๋Š” ๊ฒƒ์ด๋‹ค

 

entity ๋Š” ํ•ต์‹ฌ์ด๋ผ๋Š” ์ด๋ฆ„์— ๊ฑธ๋งž๊ฒŒ ์ ์ ˆํ•œ ์—ญํ• ๊ณผ ์ฑ…์ž„์ด ์กด์žฌํ•ด์•ผ ํ•œ๋‹ค.

 

์ง€๊ธˆ๋ถ€ํ„ฐ ์ด์•ผ๊ธฐํ•  ๋‚ด์šฉ์€ DDD ๋ณด๋‹ค ๋” ์›์ดˆ์ ์ธ OOP ์˜ ๊ฐ์ฒด๊ฐ„ ์ƒํ˜ธ์ž‘์šฉ์„ ์ด์•ผ๊ธฐํ•˜๋Š” ๋‚ด์šฉ์ด๋‹ค.

 

Entity ํ–‰์œ„์˜ ํŠน์„ฑ 1. ์ž์œจ์ ์ธ ๊ฐ์ฒด, object autonomy

 

๊ฐ์ฒด์˜ ์ž์œจ์„ฑ์€ ๊ฐ์ฒด๊ฐ€ ์Šค์Šค๋กœ ๊ฒฐ์ •์„ ๋‚ด๋ฆฌ๊ณ  ํ–‰๋™ํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์„ ์˜๋ฏธํ•œ๋‹ค.

 

๊ฐ์ฒด๋Š” ์ž์‹ ์˜ ์†์„ฑ๊ณผ ์ƒํƒœ์— ๋Œ€ํ•ด์„œ ์บก์Šํ™”๋ฅผ ํ†ตํ•ด์„œ ์Šค์Šค๋กœ ํ–‰์œ„ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๋Š”๋ฐ, getter ์™€ setter ๋“ค๋กœ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๊ฐ์ฒด์˜ ์ž์œจ์„ฑ์„ ๋นผ์•—๋Š” ๋‚˜์˜ ๊ณผ๊ฑฐ ์ฝ”๋“œ๋ฅผ ๋ด๋ณด์ž

public class AbcService {
    //...
    public void changeUserName(long userId, String newUsername) {
        User user = repository.findBy(userId);

        UserName username = user.getName();

        if (!username.length() > 0) {
            throw new IllegalArgumentException("์ด๋ฆ„์€ 0์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค")
        } else if (!username.length() <= 6) {
            throw new IllegalArgumentException("์ด๋ฆ„์€ 6์ž ์ดํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
        }

        user.setUsername(newUsername);

        repository.save(user);
    }
    //...
}

ํ•ด์•ผํ•˜๋Š” ์ผ์ด ์ผ์ข…์˜ ์ˆœ์„œ๋„์ฒ˜๋Ÿผ ๋‚˜์—ด๋˜์–ด์žˆ๊ณ , ๊ฐ์ฒด ์Šค์Šค๋กœ๊ฐ€ ๋ฌด์–ธ๊ฐ€๋ฅผ ์ „ํ˜€ ํ•˜์ง€ ๋ชปํ•œ๋‹ค.

 

Entity ์Šค์Šค๋กœ ํ–‰์œ„๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž์œจ์„ฑ์„ ๋ถ€์—ฌํ•œ๋‹ค๋Š” ๊ฒƒ์€ ๊ฐ„๋‹จํ•˜๋‹ค.

 

์•ž์„  ํ–‰์œ„๋“ค์„ Entity ๊ฐ€ ์Šค์Šค๋กœ ํ•  ์ˆ˜ ์žˆ๋„๋ก public interface ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

 

public class AbcService {
    //...
    public void changeUserName(long userId, String newUsername) {
        User user = repository.findBy(userId);

        user.changeUsername(newUsername);

        repository.save(user);
    }
    //...
}

 

์ด๋ ‡๊ฒŒ ์ž์œจ์„ฑ์„ ๋†’์ด๋ฉด ๊ฐ์ฒด ์Šค์Šค๋กœ๊ฐ€ ์ผ๊ด€์„ฑ์— ๋Œ€ํ•ด์„œ ์ฑ…์ž„์งˆ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

์ด๋ ‡๊ฒŒ Entity ์—๊ฒŒ ํ–‰์œ„๋ฅผ ๋ถ€์—ฌํ•˜๋ฉด ๋ถ€์—ฌํ• ์ˆ˜๋ก, ์ฆ‰ ์ž์œจ์„ฑ์„ ๋†’์ผ์ˆ˜๋ก Service Layer ์˜ ์ฝ”๋“œ๊ฐ€ ์ค„์–ด๋“œ๋Š” ๋˜๋‹ค๋ฅธ ๋ถ€์ˆ˜ํšจ๊ณผ๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

 

Entity ํ–‰์œ„์˜ ํŠน์„ฑ 2. ์ž๊ธฐ ์บก์Šํ™”, self encapsulation

 

martin fowler ๋Š” ๋ณธ์ธ์˜ ๋ธ”๋กœ๊ทธ์— Self Encapsulation ์ด๋ผ๋Š” ๊ธ€์„ ์ผ๋‹ค.

 

bliki: SelfEncapsulation

Self-encapsulation is the practice of using only indirect access to an object's data, even from inside the object.

martinfowler.com

 

์ฆ‰, ๊ฐ์ฒด์˜ ์ƒํƒœ์™€ ํ–‰์œ„๋ฅผ ์บก์Šํ™”ํ•˜์—ฌ ์™ธ๋ถ€์— ๋…ธ์ถœ๋˜๋Š” ๊ฒƒ์„ ์ตœ์†Œํ™”ํ•˜์—ฌ ๊ฐ์ฒด ์Šค์Šค๋กœ๊ฐ€ ์ฑ…์ž„์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์›์น™์ด๋‹ค.

 

์•ž์„  ์ž์œจ์„ฑ๊ณผ ๋น„์Šทํ•œ ๋งฅ๋ฝ์ด์ง€๋งŒ ์ค‘์š”ํ•œ ๊ฒƒ์€ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถ˜๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

 

public class AbcService {
    //...
    public void changeUserName(long userId, String newUsername) {
        User user = repository.findBy(userId);

        UserName username = user.getName();

        if (!username.length() > 0) {
            throw new IllegalArgumentException("์ด๋ฆ„์€ 0์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค")
        } else if (!username.length() <= 6) {
            throw new IllegalArgumentException("์ด๋ฆ„์€ 6์ž ์ดํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
        }

        user.setUsername(newUsername);

        repository.save(user);
    }
    //...
}

 

์ด ์ฝ”๋“œ์—์„œ AbcService ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ import ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๊ฒƒ์ด๋‹ค.

 

import com.github.dhslrl321.User.UserName;

 

์ด ๋ง์€ Service ๊ฐ€ UserName ์— ์˜์กดํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์ฆ‰, UserName ์ด ๋ณ€๊ฒฝ๋˜๋ฉด Service ๊นŒ์ง€ ๊ทธ ๋ณ€๊ฒฝ์ด ์ „ํŒŒ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋œป์ด๋‹ค.

 

๋‹ค์Œ๊ณผ ๊ฐ™์ด User ๊ฐ€ Self Encapsulation ์„ ํ†ตํ•ด์„œ ์˜์กด ๊ด€๊ณ„๋ฅผ ์ฐจ๋‹จํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

 

public class User implements Entity {

    // ...
    private UserName username;

    // ๊ฐ์ฒด์˜ ํ–‰์œ„, self encapsulation
    public void changeUserName(String target) {
        username = username.change(target);
    }

    // ...
}

public class UserName {

    String value;

    public UserName change(String target) {
        validate(target.length() > 0, IllegalArgumentException.class);
        validate(target.length() <= 6, IllegalArgumentException.class);

        return new UserName(target);
    }
}

 

์ด๋Š” entity ๋งŒ์˜ ํŠน์„ฑ์ด๋ผ๊ธฐ ๋ณด๋‹ค Value Object ์—๋„ ํ•ด๋‹น๋  ์ˆ˜ ์žˆ๋Š” ๋„๋ฉ”์ธ ๋ชจ๋ธ ๊ฐ์ฒด์˜ ํŠน์„ฑ์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ด๋ ‡๊ฒŒ ํ˜‘๋ ฅํ•˜๋Š” ๊ฐ์ฒด๋“ค์˜ ๊ณต๋™์ฒด๋ฅผ ์ž˜ ๋งŒ๋“ค๋ฉด rich domain model ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

๊ฐ์ฒด๋ฅผ ๋”์ด์ƒ DTO ๋กœ ์‚ฌ์šฉํ•˜์ง€ ๋ง์ž. ๊ฐ์ฒด๊ฐ„์˜ ํ–‰์œ„๋ฅผ ์ž˜ ๋ถ„์„ํ•˜๊ณ  ์ฑ…์ž„์„ ๋ถ€์—ฌํ•˜๋ฉด, ๊ทธ๋“ค๊ฐ„์˜ ํ˜‘๋ ฅ์„ ํ†ตํ•ด์„œ transactinal script ๋ฐฉ์‹์„ ๋ฒ—์–ด๋‚  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

์ •๋ฆฌ

  • entity ์˜ ํ–‰์œ„ ์—ญ์‹œ domain modeling ๊ณผ ubiqutious language ์— ์˜ํ•ด ๊ฒฐ์ •๋œ๋‹ค.
  • entity ์˜ ๋‘๊ฐ€์ง€ ํ–‰์œ„ ํŠน์„ฑ (์ด๋Š” entity ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ value object ์—์„œ๋„ ๋™์ผํ•˜๋‹ค)
    • object autonomy: ๊ฐ์ฒด์˜ ๋™์ž‘์— ์ง‘์ค‘
    • self encapsulation: ๊ฐ์ฒด์˜ ์ •๋ณด ์€๋‹‰๊ณผ ์บก์Šํ™”์— ์ง‘์ค‘

 

๋งˆ์น˜๋ฉฐ

 

์ด๋ ‡๊ฒŒ ์˜ค๋Š˜์€ Entity ์— ๋Œ€ํ•ด์„œ ์ด์•ผ๊ธฐํ•ด๋ณด๋Š” ์‹œ๊ฐ„์„ ๊ฐ€์กŒ๋‹ค.

 

Entity ๋Š” db table ์— ์˜์กดํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

entity ๋Š” ์‚ด์•„์žˆ๋Š” ๋„๋ฉ”์ธ ๊ฐ์ฒด ๊ทธ ์ž์ฒด๋ฅผ ์˜๋ฏธํ•˜๊ณ , ์‹๋ณ„์„ ํ†ตํ•ด ์—ฐ์†์„ฑ์„ ๊ฐ€์ง€๊ฒŒ ๋œ๋‹ค.

 

์‹๋ณ„์— ๋Œ€ํ•ด์„œ๋Š” ํ•  ์ด์•ผ๊ธฐ๊ฐ€ ๋งŽ๋‹ค.

 

์‹๋ณ„์ž๊ฐ€ ๋ฌด์—‡์ด๊ณ  ์–ด๋–ค ํŠน์„ฑ์„ ๊ฐ€์ ธ์•ผ ํ•˜๋Š”์ง€ ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์–ธ์ œ ์ƒ์„ฑํ•˜๋Š๋ƒ ๋ˆ„๊ฐ€ ์ƒ์„ฑํ•˜๋Š๋ƒ๊นŒ์ง€.

 

์ด์™€ ๊ด€๋ จ๋œ ์ด์•ผ๊ธฐ๋Š” ๋‹ค์Œ ์‹œ๊ฐ„์— ์ง„ํ–‰ํ•ด๋ณด๋ ค ํ•œ๋‹ค.

 

Entity ์™ธ Value Object ์— ๋Œ€ํ•ด์„œ๋„ ์ฐจ์ด์ ์ด ๋ช…ํ™•ํ•˜์ง€ ์•Š์€๋ฐ, ์ดํ›„์— ๋˜ ์ด์•ผ๊ธฐ๋ฅผ ํ•˜๋Š” ์‹œ๊ฐ„์„ ๊ฐ€์ ธ๋ณผ ๊ฒƒ์ด๋‹ค

๋Œ“๊ธ€