๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ›๏ธ Architecture

[์ด๋ฒคํŠธ ์†Œ์‹ฑ๊ณผ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜] CH.2 ๊ฐ์ฒด์ง€ํ–ฅ ์„ค๊ณ„ ์›์น™

by GroovyArea 2025. 2. 24.

๊ฐ์ฒด ์ง€ํ–ฅ์˜ ๋Œ€๋ช…์‚ฌ์ธ Java๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ ๊ฐœ๋ฐœ์ž๋“ค, ์ฆ‰ ์šฐ๋ฆฌ๋“ค์€ ์ ˆ์ฐจ ์ง€ํ–ฅ์ ์ธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ๊ฐ€ ๋งŽ๋‹ค.

์ฒ˜์Œ ๊ฐ์ฒด ์ง€ํ–ฅ์„ ๊ณต๋ถ€ํ•˜๋˜ ๋•Œ, ๊ฐ์ฒด๋ผ๋Š” ๊ฐœ๋…์ด ๊ทธ๋ฆฌ๋„ ๋‚ฏ์„ค์—ˆ๋‹ค.

C์–ธ์–ด๋กœ ์ฝ”๋”ฉ ์ž…๋ฌธ์„ ํ–ˆ๊ธฐ์—, ์ ˆ์ฐจ์ ์ธ ์ฝ”๋“œ์™€ ๋ช…๋ น์—๋งŒ ์ต์ˆ™ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ฐ์ฒด ์ง€ํ–ฅ ๊ธฐ๋ฒ•์„ ๊ณต๋ถ€ํ•˜๋ฉด์„œ, ํด๋ž˜์Šค๋ฅผ ์‚ฌ๋žŒ์œผ๋กœ ๋ฐ”๋ผ๋ณด๋ ค ๋…ธ๋ ฅํ–ˆ๊ณ  ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ฑ…๊ณผ ๊ฐ์ฒด ์ง€ํ–ฅ ์ƒํ™œ ์ฒด์กฐ ์›์น™ ๋“ฑ์˜ ๊ฐœ๋…์„ ์ˆ™์ง€ํ•˜๋‹ˆ,

์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉฐ ํ›จ์”ฌ Java์Šค๋Ÿฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ ๊ฒƒ ๊ฐ™๋‹ค.

 

ํ•˜์ง€๋งŒ, ์•„์ง๋„ ๊ฐ์ฒด ์ฆ‰ ํด๋ž˜์Šค๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ๋‚˜๋ˆ„๋Š” ๊ฑด ํž˜๋“  ์ผ์ด๋ผ ์ƒ๊ฐํ•œ๋‹ค.

๊ทธ๊ฒƒ์€ ๊ณง ์„ค๊ณ„์— ๋Œ€ํ•œ ์˜์—ญ์ด๋ผ ์ƒ๊ฐ์ด ๋“œ๋Š”๋ฐ, ์ตœ๊ทผ์— LinkedIn ์—์„œ ๋ณธ ํ† ๋น„ ๋‹˜์˜ ๊ธ€์ฒ˜๋Ÿผ ๊ฐœ๋ฐœ์ž๋“ค์€ ์„ค๊ณ„๋ฅผ ์ง์ ‘ ๋‹ค๋ฃจ๋ฉฐ ๊ณต๋ถ€ํ•  ์ผ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋ผ๋Š” ์ƒ๊ฐ๋„ ๋“ ๋‹ค.

 

์ •๋ง ๊ธฐ๋ณธ์ด ๋˜๋Š” ๋‚ด์šฉ์ด์ง€๋งŒ ๋‹ค์‹œ ํ•œ๋ฒˆ ์ •๋ฆฌํ•ด ๋ณด์ž~!

 

---

 

์ด ์ฑ•ํ„ฐ์—์„œ๋Š” ์„ค๋ฌธ์กฐ์‚ฌ ๊ธฐ์—…์˜ ์˜ˆ๋ฅผ ๋“ ๋‹ค.

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

๊ฐ์ฒด ์ง€ํ–ฅ ์„ค๊ณ„ ๊ธฐ๋ฒ•์„ ์ ์šฉํ•˜๋ฉฐ, ๋ฆฌํŒฉํ† ๋ง์„ ํ•ด๋‚˜ ๊ฐ€๋ณด์ž.

 

์ฑ…์ž„ ์ฃผ๋„ ์„ค๊ณ„.

์ฑ…์ž„, ์—ญํ• , ํ˜‘๋ ฅ์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ์ ‘๊ทผ ๋ฐฉ๋ฒ•์˜ ํ•œ ๋ถ„์•ผ.

๊ฐ์ฒด์— ์ฑ…์ž„์„ ๋ถ€์—ฌํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

๊ฐ์ฒด์˜ ์ฑ…์ž„์€ ๊ณง "์•„๋Š” ๊ฒƒ", "ํ•˜๋Š” ๊ฒƒ"์ด๋‹ค.

์•„๋Š” ๊ฒƒ์€ ํด๋ž˜์Šค์˜ ์†์„ฑ(ํ•„๋“œ)์ด๋ฉฐ, ํ•˜๋Š” ๊ฒƒ์€ ๋ฉ”์„œ๋“œ์ด๋‹ค. ์ •๋ง ๊ธฐ๋ณธ๊ธฐ~

 

๊ฐ์ฒด ์ง€ํ–ฅ ์ƒํ™œ ์ฒด์กฐ ์›์น™์—์„œ๋„ ๋‹ค๋ฃจ ๋“ฏ์ด,

๊ฐ์ฒด์— ๋ฌป์ง€ ๋ง๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๋ผ (Tell Dont Ask)

๋””๋ฏธํ„ฐ์˜ ๋ฒ•์น™ "Don't talk to stranger"(๋‚ฏ์„  ์‚ฌ๋žŒ๊ณผ ๋Œ€ํ™”ํ•˜์ง€ ๋งˆ๋ผ)

private String secondUncle = me.family.father.family.father.sons[1];
^ ์š”๊ฒƒ ๋ณด๋‹ค๋Š” (์ฒด์ด๋‹์ด ๋„ˆ๋ฌด ์‹ฌํ•˜์ž–์•„, ๋ฌป์ง€ ๋ง๊ตฌ..)

private String secondUncle = me.secondUncle;
^ ์š”๋ ‡๊ฒŒ ํ•˜๋ฉด ํ›จ์”ฌ ๋‚ซ์ง€ ์•Š์„๊นŒ

 

๋‹จ์ผ ์ฑ…์ž„ ์›์น™. (Simple Responsibility Principle)

ํด๋ž˜์Šค๋Š” ํ•œ ๊ฐ€์ง€ ์ผ๋งŒ ์ˆ˜ํ–‰, ํ•œ ๊ฐ€์ง€ ์ด์œ ์— ์˜ํ•ด์„œ๋งŒ ๋ณ€๊ฒฝ๋˜์–ด์•ผ ํ•œ๋‹ค.

๋งŒ์•ฝ ํด๋ž˜์Šค๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ์ด์œ ๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ๋ผ๋ฉด SRP๋ฅผ ์œ„๋ฐ˜ํ•œ ๊ฒƒ.

์—ฌ๋Ÿฌ ์ฑ…์ž„์„ ๊ฐ€์ง„ ํด๋ž˜์Šค๋กœ ๋‚˜๋ˆ„๊ณ , ์—ฐ๊ด€๋œ ์†์„ฑ์„ ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๋กœ ํ†ตํ•ฉํ•˜๋ผ!

public class Assign {
	private Customer customer;
	private Surveyor surveyor;
}

 

์œ„์™€ ๊ฐ™์ด ํ†ตํ•ฉํ•ด์„œ~

 

๊ฐœ๋ฐฉ/ํ์‡„ ์›์น™. (Open/Closed Principle)

์ฝ”๋“œ๊ฐ€ ์ž์œ ๋กœ์šฐ๋ฉด์„œ๋„ ์ œํ•œ์ ์ด์–ด์•ผ ํ•œ๋‹ค.

์†Œํ”„ํŠธ์›จ์–ด ์—”ํ‹ฐํ‹ฐ๋Š” ๊ฒฐ๊ตญ ํ™•์žฅ์— ๋Œ€ํ•ด์„œ ๊ฐœ๋ฐฉ์ ์ด๋‚˜, ์ˆ˜์ •์— ๋Œ€ํ•ด์„œ ํ์‡„์ ์ด์–ด์•ผ ํ•œ๋‹ค.

 

์ผ๋ก€๋กœ WAS์ธ tomcat์„ ์˜ˆ๋กœ ๋“ ๋‹ค. Java Web Application์„ warํŒŒ์ผ๋กœ ๋งŒ๋“ค๊ณ  tomcat์— ๋ฐฐํฌํ•˜๋Š”๋ฐ, warํŒŒ์ผ์€ ํ†ฐ์บฃ ๊ณ ์œ  ๊ธฐ๋Šฅ์— ์˜ํ–ฅ์„ ์ฃผ๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ณ€๊ฒฝ์— ํ์‡„์ ์ด๋‹ค. ๋ฐ˜๋Œ€๋กœ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฏ€๋กœ ํ™•์žฅ์— ๊ฐœ๋ฐฉ๋˜์–ด ์žˆ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

์ฝ”๋“œ์—์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ด๋‹ค. ์ธํ„ฐํŽ˜์ด์Šค๋‚˜ ์ƒ์œ„ ํด๋ž˜์Šค์— ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์„ ๋‘๊ณ  ๊ตฌํ˜„ ํด๋ž˜์Šค๋‚˜ ์ƒ์† ํด๋ž˜์Šค์—์„œ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•œ๋‹ค.

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

interface Filter {
	boolean isSatisfied(Customer customer);
}

class ContactFilter extends Filter {
	//...
}

class AgeFilter implements Filter {
	// ...
}

class GenderFilter implements Filter {
	// ...
}

์ด์ฒ˜๋Ÿผ ๊ตฌํ˜„์ฒด๋Š” ์„ฑ๊ฒฉ์— ๋งž๊ฒŒ ํ•ด๋‹น ์ถ”์ƒ์ฒด๋ฅผ ํ™•์žฅํ•ด ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.

 

๋ฆฌ์Šค์ฝ”ํ”„ ์น˜ํ™˜ ์›์น™. (Liskov Substitution Principle)

ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ƒ์† ํด๋ž˜์Šค๋ฅผ ์•ˆ์ •์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•˜๋Š”๋ฐ ๋„์›€์„ ์ค€๋‹ค.

ํด๋ผ์ด์–ธํŠธ๋Š” ํด๋ž˜์Šค์˜ ์ƒ์† ๊ตฌ์กฐ์— ๊ด€๊ณ„์—†์ด ์ƒ์œ„ ํด๋ž˜์Šค๋งŒ ์ดํ•ดํ•˜๊ณ  ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

S๊ฐ€ ๊ธฐ๋ฐ˜ํƒ€์ž… T์˜ ์„œ๋ธŒํƒ€์ž…์ด๋ฉด T ํƒ€์ž…์˜ ๊ฐ์ฒด๋Š” ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰์— ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ค์ง€ ์•Š๊ณ  S ํƒ€์ž…์˜ ๊ฐ์ฒด๋กœ ์น˜ํ™˜์ด ๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค.

 

๊ธฐ๋ฐ˜ ํƒ€์ž… : ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ํƒ€์ž…, ํด๋ผ์ด์–ธํŠธ๋Š” ๊ธฐ๋ฐ˜ํƒ€์ž…์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœ

์„œ๋ธŒ ํƒ€์ž… : ๊ธฐ๋ฐ˜ ํƒ€์ž…์„ ์ƒ์†ํ•œ ๋ชจ๋“  ์ข…๋ฅ˜์˜ ํด๋ž˜์Šค

๋ฌธ๋งฅ : ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ธฐ๋ฐ˜ ํƒ€์ž…์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด ์„œ๋ธŒ ํƒ€์ž…์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์กฐ๊ฑด

public interface Distributable {
	public List<Assign> distribute(List<Customer> customers, List<Surveyor> surveyors);
}

public class RoundRobinDistributor implements Distributable {
	
    @Override
    public List<Assign> distribute(List<Customer> customers, List<Surveyor> surveyors) {
    	// round robin ๋ฐฉ์‹ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„
    }
}

public class PerformanceDistributor implements Distributable {
	
    @Override
    public List<Assign> distribute(List<Customer> customers, List<Surveyor> surveyors) {
    	// ์„ฑ๊ณผ๊ฐ€ ์ข‹์€ ์„ค๋ฌธ ์กฐ์‚ฌ์›์—๊ฒŒ ๋ฐฐ์ •ํ•˜๋Š” ๋ฐฉ์‹ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„
    }
}

๊ฑฐ์˜ ๋น„์Šทํ•œ ๋“ฏ~~ ๋ฌธ๋งฅ๋งŒ ์ดํ•ดํ•˜๋ฉด ๋œ๋‹ค.

 

์ธํ„ฐํŽ˜์ด์Šค ๋ถ„๋ฆฌ ์›์น™. (Interface Segregation Principle)

๋„ˆ๋ฌด ๋งŽ๊ฑฐ๋‚˜, ๊ด€๊ณ„์—†๋Š” ์˜คํผ๋ ˆ์ด์…˜์„ ์ œ๊ณตํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ง€์–‘ํ•˜์ž.

๋ชฉ์ ์— ๋งž๊ฒŒ!! ์ž˜๊ฒŒ ๋‚˜๋ˆ„์ž~~

 

Spring Data JPA Interface ๊ตฌ์กฐ๋ฅผ ๋ณด๋ฉด,

์ถœ์ฒ˜ : https://velog.io/@hoyun7443/JPA%EC%9D%98-%EA%B3%B5%ED%86%B5-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-JpaRepository%EC%9D%98-%EA%B8%B0%EB%8A%A5%EA%B3%BC-%EA%B5%AC%EC%A1%B0

 

์œ„์™€ ๊ฐ™์ด ์ฑ…์ž„์— ๋งž๊ฒŒ ์—ฌ๋Ÿฌ interface๋“ค์ด ํ•˜์œ„๋กœ ์ƒ์†ํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜๊ฐ€ ์žˆ๋‹ค.

JPA ์„ธ๋ถ€ ๊ตฌํ˜„์ด ํ•„์š” ์—†๋‹ค๋ฉด, CrudRepository ๋งŒ ์ƒ์†ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์ผ๋ถ€ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

์˜์กด์„ฑ ์—ญ์ „ ์›์น™. (Dependency Inversion Principle)

์‚ฌ์‹ค ์•ž์— ๋‚˜์˜จ ๊ฐœ๋…๋“ค๊ณผ ๊ฑฐ์˜ ๋น„์Šทํ•œ ๊ฐœ๋….

๊ตฌ์ฒด์ ์ธ (ํ˜น์€ ์ž์ฃผ ๋ณ€ํ•  ์ˆ˜ ์žˆ๋Š”) ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ง์ ‘ ์•Œ์ง€ ๋ชปํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•˜๋ผ.

LSP ์˜ˆ์ œ ์ฝ”๋“œ์—์„œ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋“ฏ์ด, ํด๋ผ์ด์–ธํŠธ๋Š” Distributable ์ธํ„ฐํŽ˜์ด์Šค๋งŒ ์•Œ๋ฉด ๋œ๋‹ค. ์„ธ๋ถ€ ๊ตฌํ˜„์€ ์•Œ ํ•„์š”๊ฐ€ ์—†๋‹ค.

๋ง์ด ์–ด๋ ค์šธ ์ˆ˜๋„ ์žˆ์ง€๋งŒ ๊ฒฐ๊ตญ, ์˜์กด์„ฑ์„ interface๋กœ ์—ญ์ „์‹œํ‚จ ๊ฒƒ์ด๋‹ค. ๊ตฌํ˜„๋ถ€๋ฅผ ์˜์กดํ•˜์ง€ ์•Š๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด!!

 

์ด๋ฅผ ํ…Œ๋ฉด,

interface UserReader {
	fun findBy(userId: Long) : User?
}

class DBUserReader(
	private val jpaUserDao: JpaUserDao
): UserReader {

	override fun findBy(userId: Long): User? {
		return jpaUserDao.findByIdOrNull(userId).toEntity()
	}
}

class HttpUserReader(
	private val userClient: UserClient
): UserReader {

	override fun findBy(userId: Long): User? {
		return runCatching {
			userClient.getUser(userId)
		}.onFailure {
			logger.error(it.message())
		}.getOrNull()
	}
}

// ๋น„์ฆˆ๋‹ˆ์Šค์—์„œ..

fun usecase(
	private val dbUserReader: UserReader
) {
	
	// ๋กœ์ง ๊ตฌํ˜„    
}
	
}

์š”๋ ‡๊ฒŒ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

---

 

๊ฐ์ฒด ์ง€ํ–ฅ ์›์น™์€ ๊ณต๋ถ€๋งŒ ํ–ˆ์ง€ ๊ธ€๋กœ ์ฒ˜์Œ ์ •๋ฆฌํ•ด ๋ณธ๋‹ค ใ…Žใ…Ž

๋น„์ฆˆ๋‹ˆ์Šค๋ฅผ ์น˜๋ฉด์„œ, ์ข€ ๋” ์œ ๋…ํ•˜๊ณ  ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•˜๋ ค ์˜์‹ํ•ด์•ผ ํ•œ๋‹ค!! ๊ทธ๋ž˜์•ผ ๋Š˜์ง€~~

 

๋‹ค์Œ ์žฅ์€ ๋“œ๋””์–ด ์ด๋ฒคํŠธ ์†Œ์‹ฑ์ด๋‹ค!!

 

๋ฐ˜์‘ํ˜•