[Spring MVC] ์๋น์ค ๊ณ์ธต
๐ ์๋น์ค ๊ณ์ธต
API ๊ณ์ธต์์ ์ ๋ฌ๋ฐ์ ํด๋ผ์ด์ธํธ์ ์์ฒญ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ค์ง์ ์ธ ๋น์ฆ๋์ค ์๊ตฌ์ฌํญ์ ์ฒ๋ฆฌํ๋ ๊ณ์ธต
๋ชฉํ
์คํ๋ง์ DI(Dependency Injection)๋ฅผ ์ด์ฉํ์ฌ API ๊ณ์ธต๊ณผ ๋น์ฆ๋์ค ๊ณ์ธต์ ์ฐ๋ํ๊ณ ,
API ๊ณ์ธต์์ ์ ๋ฌ๋ฐ์ DTO ๊ฐ์ฒด๋ฅผ ๋น์ฆ๋์ค ๊ณ์ธต์ ๋๋ฉ์ธ ์ํฐํฐ(Entity) ๊ฐ์ฒด๋ก ๋ณํํด์ ์ ๋ฌํด๋ณด์.
"API ๊ณ์ธต๊ณผ ์๋น์ค ๊ณ์ธต์ ์ฐ๋ํ๋ค๋ ์๋ฏธ๋ ๋ฌด์์ผ๊น?"
API ๊ณ์ธต์์ ๊ตฌํํ Controller ํด๋์ค๊ฐ ์๋น์ค ๊ณ์ธต์ Service ํด๋์ค์ ๋ฉ์๋ ํธ์ถ์ ํตํด ์ํธ์์ฉํ๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค.
๐ Service์ ์๋ฏธ
- ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ด์ ์ผ๋ก ๋ณด๋ฉด ๋๋ฉ์ธ ์ ๋ฌด ์์ญ์ ๊ตฌํํ๋ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๊ด๋ จ์ด ์๋ค.
- ์ ํ๋ฆฌ์ผ์ด์ ์ ๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๊ธฐ ์ํ ์๋น์ค ๊ณ์ธต์ ๋๋ถ๋ถ ๋๋ฉ์ธ ๋ชจ๋ธ์ ํฌํจํ๊ณ ์๋ค.
- ๋๋ฉ์ธ ๋ชจ๋ธ์ ๋น์ฝํ ๋๋ฉ์ธ ๋ชจ๋ธ/ํ๋ถํ ๋๋ฉ์ธ ๋ชจ๋ธ๋ก ๊ตฌ๋ถ, DDD(๋๋ฉ์ธ ์ฃผ๋ ์ค๊ณ, Domain Driven Design)์ ๊ด๋ จ์ด ์๋ค.
๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๋ Service ํด๋์ค
โ MemberController ํด๋์ค ์ดํด๋ณด๊ธฐ
@RestController
@RequestMapping("/v1/members")
@Validated
public class MemberController {
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
return new ResponseEntity<>(memberDto, HttpStatus.CREATED);
}
@PatchMapping("/{member-id}")
public ResponseEntity patchMember(
@PathVariable("member-id") @Positive long memberId,
@Valid @RequestBody MemberPatchDto memberPatchDto) {
memberPatchDto.setMemberId(memberId);
// No need Business logic
return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
}
@GetMapping("/{member-id}")
public ResponseEntity getMember(
@PathVariable("member-id") @Positive long memberId) {
System.out.println("# memberId: " + memberId);
// not implementation
return new ResponseEntity<>(HttpStatus.OK);
}
@GetMapping
public ResponseEntity getMembers() {
System.out.println("# get Members");
// not implementation
return new ResponseEntity<>(HttpStatus.OK);
}
@DeleteMapping("/{member-id}")
public ResponseEntity deleteMember(
@PathVariable("member-id") @Positive long memberId) {
System.out.println("# memberId: " + memberId);
// No need business logic
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
}
์ฌ๊ธฐ์ ๋ค์ฏ ๊ฐ์ ํธ๋ค๋ฌ ๋ฉ์๋๋ฅผ ๋ฝ์๋ณผ ์ ์๋ค.
- postMember() : 1๋ช ์ ํ์ ๋ฑ๋ก์ ์ํ ์์ฒญ์ ์ ๋ฌ๋ฐ๋๋ค. <-> createMember()
- patchMember() : 1๋ช ์ ํ์ ์์ ์ ์ํ ์์ฒญ์ ์ ๋ฌ๋ฐ๋๋ค. <-> updateMember()
- getMember() : 1๋ช ์ ํ์ ์ ๋ณด ์กฐํ๋ฅผ ์ํ ์์ฒญ์ ์ ๋ฌ๋ฐ๋๋ค. <-> findMember()
- getMembers() : N๋ช ์ ํ์ ์ ๋ณด ์กฐํ๋ฅผ ์ํ ์์ฒญ์ ์ ๋ฌ๋ฐ๋๋ค. <-> findMembers()
- deleteMember() : 1๋ช ์ ํ์ ์ ๋ณด ์ญ์ ๋ฅผ ์ํ ์์ฒญ์ ์ ๋ฌ๋ฐ๋๋ค. <-> deleteMember()
โ MemberService ๊ธฐ๋ณธ ๊ตฌ์กฐ
public class MemberService {
public Member createMember(Member member) {
// TODO should business logic
// TODO member ๊ฐ์ฒด๋ ๋์ค์ DB์ ์ ์ฅ ํ, ๋๋๋ ค ๋ฐ๋ ๊ฒ์ผ๋ก ๋ณ๊ฒฝ ํ์.
Member createdMember = member;
return createdMember;
}
public Member updateMember(Member member) {
// TODO should business logic
// member ๊ฐ์ฒด๋ ๋์ค์ DB์ ์
๋ฐ์ดํธ ํ, ๋๋๋ ค ๋ฐ๋ ๊ฒ์ผ๋ก ๋ณ๊ฒฝ ํ์.
Member updatedMember = member;
return updatedMember;
}
public Member findMember(long memberId) {
// TODO should business logic
// TODO member ๊ฐ์ฒด๋ ๋์ค์ DB์์ ์กฐํ ํ๋ ๊ฒ์ผ๋ก ๋ณ๊ฒฝ ํ์.
Member member =
new Member(memberId, "hgd@gmail.com", "ํ๊ธธ๋", "010-1234-5678");
return member;
}
public List<Member> findMembers() {
// TODO should business logic
// TODO member ๊ฐ์ฒด๋ ๋์ค์ DB์์ ์กฐํํ๋ ๊ฒ์ผ๋ก ๋ณ๊ฒฝ ํ์.
List<Member> members = List.of(
new Member(1, "hgd@gmail.com", "ํ๊ธธ๋", "010-1234-5678"),
new Member(2, "lml@gmail.com", "์ด๋ชฝ๋ฃก", "010-1111-2222")
);
return members;
}
public void deleteMember(long memberId) {
// TODO should business logic
}
}
- createMember(), updateMember(): ๋จ์ํ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ๋ฐ์ Member ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ๋ฆฌํดํ๊ณ ์๋ค.
- findMember(), findMembers(): Stub ๋ฐ์ดํฐ๋ฅผ ๋๊ฒจ์ค๋ค.
๐คทโ๏ธ Stub ๋ฐ์ดํฐ๋?
- ํ๋ก๊ทธ๋๋ฐ์์ ์ฌ์ฉ๋๋ ๊ฐ๋ฒผ์ด ๋ฐ์ดํฐ๋ก, ์ค์ ๋ฐ์ดํฐ์ ์ ์ฌํ ํํ๋ฅผ ๊ฐ์ง์ง๋ง ์ค์ ๋ฐ์ดํฐ์ ํฌ๊ธฐ์ ๋ณต์ก์ฑ์ ๊ฐ์ง์ง ์์
- ์ฃผ๋ก ๊ฐ๋ฐ ์ด๊ธฐ๋จ๊ณ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์ธ๋ถ ์๋น์ค์ ์ฐ๊ฒฐ๋์ง ์์ ์ํ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋์์ ํ ์คํธํ๊ธฐ ์ํด ์ฌ์ฉ
โ Member ํด๋์ค
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {
private long memberId;
private String email;
private String name;
private String phone;
}
- @AllArgsConstructor: ํ์ฌ Member ํด๋์ค์ ์ถ๊ฐ๋ ๋ชจ๋ ๋ฉค๋ฒ ๋ณ์๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๊ฐ๋ Member ์์ฑ์๋ฅผ ์๋์ผ๋ก ์์ฑํด ์ค๋๋ค.
- @NoArgsConstructor: ํ๋ผ๋ฏธํฐ๊ฐ ์๋ ๊ธฐ๋ณธ ์์ฑ์๋ฅผ ์๋์ผ๋ก ์์ฑํด์ค๋ค.
DI(Dependency Injecion) ์์ด ๋น์ฆ๋์ค ๊ณ์ธต๊ณผ API ๊ณ์ธต ์ฐ๋
@RestController
@RequestMapping("/v2/members")
@Validated
public class MemberController {
private final MemberService memberService;
public MemberController() {
this.memberService = new MemberService(); // (1)
}
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
// (2)
Member member = new Member();
member.setEmail(memberDto.getEmail());
member.setName(memberDto.getName());
member.setPhone(memberDto.getPhone());
// (3)
Member response = memberService.createMember(member);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
@PatchMapping("/{member-id}")
public ResponseEntity patchMember(
@PathVariable("member-id") @Positive long memberId,
@Valid @RequestBody MemberPatchDto memberPatchDto) {
memberPatchDto.setMemberId(memberId);
// (4)
Member member = new Member();
member.setMemberId(memberPatchDto.getMemberId());
member.setName(memberPatchDto.getName());
member.setPhone(memberPatchDto.getPhone());
// (5)
Member response = memberService.updateMember(member);
return new ResponseEntity<>(response, HttpStatus.OK);
}
@GetMapping("/{member-id}")
public ResponseEntity getMember(
@PathVariable("member-id") @Positive long memberId) {
// (6)
Member response = memberService.findMember(memberId);
return new ResponseEntity<>(response, HttpStatus.OK);
}
@GetMapping
public ResponseEntity getMembers() {
// (7)
List<Member> response = memberService.findMembers();
return new ResponseEntity<>(response, HttpStatus.OK);
}
@DeleteMapping("/{member-id}")
public ResponseEntity deleteMember(
@PathVariable("member-id") @Positive long memberId) {
System.out.println("# delete member");
// (8)
memberService.deleteMember(memberId);
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
}
(1) MemberService ํด๋์ค๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด new ํค์๋๋ฅผ ์ด์ฉํ์ฌ MemberService ํด๋์ค์ ๊ฐ์ฒด๋ฅผ ์์ฑ
(2) ํด๋ผ์ด์ธํธ์์ ์ ๋ฌ๋ฐ์ DTO ํด๋์ค์ ์ ๋ณด => MemberService์ createMember() ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ
- MemberPostDto ํด๋์ค์ ์ ๋ณด => Member ํด๋์ค์ ์ฑ์ ๋ฃ์
โญ ์๋น์ค ๊ณ์ธต๊ณผ์ ์ฐ๊ฒฐ ์ง์
(3) ํ์ ์ ๋ณด ๋ฑ๋ก์ ์ํด MemberService ํด๋์ค์ createMember() ๋ฉ์๋๋ฅผ ํธ์ถ
(4) ํด๋ผ์ด์ธํธ์์ ์ ๋ฌ๋ฐ์ DTO ํด๋์ค์ ์ ๋ณด => MemberService์ updateMember() ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ
- MemberPatchDto ํด๋์ค์ ์ ๋ณด => Member ํด๋์ค์ ์ฑ์ ๋ฃ์
โญ ์๋น์ค ๊ณ์ธต๊ณผ์ ์ฐ๊ฒฐ ์ง์
(5) ํ์ ์ ๋ณด ์์ ์ ์ํด MemberService ํด๋์ค์ updateMember() ๋ฉ์๋๋ฅผ ํธ์ถ
โญ ์๋น์ค ๊ณ์ธต๊ณผ์ ์ฐ๊ฒฐ ์ง์
(6) ํ ๋ช
์ ํ์ ์ ๋ณด ์กฐํ๋ฅผ ์ํด MemberService ํด๋์ค์ findMember() ๋ฉ์๋๋ฅผ ํธ์ถ
- ํน์ ํ์์ ์ ๋ณด๋ฅผ ์กฐํํ๋ ๊ธฐ์ค์ธ memberId๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ค
(7) ๋ชจ๋ ํ์์ ์ ๋ณด๋ฅผ ์กฐํํ๊ธฐ ์ํด MemberService ํด๋์ค์ findMembers() ๋ฉ์๋๋ฅผ ํธ์ถ
(8) ํ ๋ช ์ ํ์ ์ ๋ณด๋ฅผ ์ญ์ ํ๊ธฐ ์ํด MemberService ํด๋์ค์ deleteMember() ๋ฉ์๋๋ฅผ ํธ์ถ
- ํน์ ํ์์ ์ ๋ณด๋ฅผ ์ญ์ ํ๋ ๊ธฐ์ค์ธ memberId๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ค
ํด๋์ค ๊ฐ์ ๋์จํ ๊ฒฐํฉ(Loose Couping)์ ์ํด Spring์ DI๋ฅผ ์ฌ์ฉํด๋ณด์.
โ DI ์ ์ฉํ MemberController ํด๋์ค
@RestController
@RequestMapping("/v3/members")
@Validated
public class MemberController {
private final MemberService memberService;
// (1) MemberController์ ๋ณ๊ฒฝ ํฌ์ธํธ
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
...
...
}
๐คทโ๏ธ MemberService ๊ฐ์ฒด๋ฅผ ์ด๋ป๊ฒ, ๋๊ฐ ์ฃผ์ ํ๋?
์ด๋ป๊ฒ ? MemberController์ ์์ฑ์ ํ๋ผ๋ฏธํฐ๋ก MemberService์ ๊ฐ์ฒด๋ฅผ ์ฃผ์ ๋ฐ์๋ค.
๋๊ฐ? Spring ์ ํ๋ฆฌ์ผ์ด์ ๋ก๋ ์, ApplicationContext์ MemberService ๊ฐ์ฒด๋ฅผ ์ฃผ์ ํ์๋ค.
โ Spring Bean์ด ๋๊ธฐ ์ํด @Service ์ ๋ํ ์ด์ ์ ์ถ๊ฐํ MemberService
@Service
public class MemberService {
// ๋ด๋ถ ์ฝ๋๋ [์ฝ๋ 3-45]์ ๋์ผ.
...
...
}
๐ฌ ์์ ์ฝ๋์์ ํด๊ฒฐ๋์ง ์์ ๋ฌธ์ ์
1. MemberController์ ํธ๋ค๋ฌ ๋ฉ์๋๊ฐ DTO ํด๋์ค๋ฅผ ์ํฐํฐ(Entity) ํด๋์ค๋ก ๋ณํํ๋ ์์ ๊น์ง ๋๋งก์ ํ๊ณ ์์
2. ์ํฐํฐ(Entity) ํด๋์ค์ ๊ฐ์ฒด๋ฅผ ํด๋ผ์ด์ธํธ์ ์๋ต์ผ๋ก ์ ์กํจ์ผ๋ก์จ ๊ณ์ธต ๊ฐ์ ์ญํ ๋ถ๋ฆฌ๊ฐ ์ด๋ฃจ์ด์ง์ง ์์
๐ฃ ํด๊ฒฐ์ฑ ์?
1. DTO ํด๋์ค๋ฅผ ์ํฐํฐ ํด๋์ค๋ก ๋ณํํ๋ ์์ ์ ํธ๋ค๋ฌ ๋ฉ์๋๊ฐ ํ์ง ์๊ณ => ๋ค๋ฅธ ํด๋์ค์์ธ ๋ณํํด๋ฌ๋ผ๊ณ ์์ฒญํ๋ค.
2. ์ํฐํฐ ํด๋์ค์ ๊ฐ์ฒด๋ฅผ DTO ํด๋์ค์ ๊ฐ์ฒด๋ก ๋ค์ ๋ฐ๊ฟ์ค๋ค.
๋งคํผ(Mapper)๋ฅผ ์ด์ฉํ DTO ํด๋์ค <-> ์ํฐํฐ(Entity) ํด๋์ค ๋งคํ
@Component // (1)
public class MemberMapper {
// (2) MemberPostDto๋ฅผ Member๋ก ๋ณํ
public Member memberPostDtoToMember(MemberPostDto memberPostDto) {
return new Member(0L,
memberPostDto.getEmail(),
memberPostDto.getName(),
memberPostDto.getPhone());
}
// (3) MemberPatchDto๋ฅผ Member๋ก ๋ณํ
public Member memberPatchDtoToMember(MemberPatchDto memberPatchDto) {
return new Member(memberPatchDto.getMemberId(),
null,
memberPatchDto.getName(),
memberPatchDto.getPhone());
}
// (4) Member๋ฅผ MemberResponseDto๋ก ๋ณํ
public MemberResponseDto memberToMemberResponseDto(Member member) {
return new MemberResponseDto(member.getMemberId(),
member.getEmail(),
member.getName(),
member.getPhone());
}
}
โ ๋งคํผ(Mapper) ํด๋์ค๊ฐ ํ๋ ์ผ
- DTO ํด๋์ค์ ์ํฐํฐ(Entity) ํด๋์ค๋ฅผ ์๋ก ๋ณํํด์ฃผ๋ ํด๋์ค
โ MemberMapper ์ฝ๋ ์ค๋ช
(1) MemberMapper๋ฅผ Spring์ Bean์ผ๋ก ๋ฑ๋กํ๊ธฐ ์ํด @Component ์ ๋ํ ์ด์ ์ถ๊ฐ
=> ๋ฑ๋ก๋ Bean์ MemberController์์ ์ฌ์ฉ
(2) MemberPostDto ํด๋์ค => Member ํด๋์ค๋ก ๋ณํ
(3) MemberPatchDto ํด๋์ค => Member ํด๋์ค๋ก ๋ณํ
(4) Member ํด๋์ค => MemberResponseDto ํด๋์ค๋ก ๋ณํ