먼저 만들어둔 코드를 설명하기에 앞서 spring에서는 웹 애플리케이션 계층 구조가 어떻게 되는지 알아볼 필요가 있어보였다.
아래는 코드짠 구조인데...
아무리 봐도 왜 controller, domain, repository, service라는 패키지를 만들었는지 이해를 할 수 없다. 따라서 더 깊이 파보려고 한다.
이 그림은 스프링의 웹 계층에 대해서 표현해둔것이다.
스프링의 웹 계층은 4가지로 나뉜다.
일단 먼저 웹을 설계할때 도메인을 먼저 정하는게 중요하다.
도메인 모델(객체)은 내가 개발하고자 하는 영역을 분석하고, 그 분석의 결과로 도출된 모델(객체)이라고 할 수 있다.
온라인 쇼핑몰을 예를 든다면,
로 도메인을 나눌 수 있다. 이 도메인중 또 하위 도메인으로 나눌 수 있게된다.
우리가 작성한 코드로 보자면 여러 도메인들중의 하위 도메인인, 회원 도메인으로 도메인 모델을 작성했었다.
도출한 도메인 모델은 크게 entity와 value로 구분할 수 있다.
지금까지 Domain, Entity, Value에 대해서 알아보았다.
결국 이 데이터 객체들을 db에 저장을 해야한다. 그런데 이 데이터 객체들을 저장하는 방식에 따라서 데이터 객체가 Entity로 불릴 수 도 있고, Value Object로 불릴 수 도 있다.
hello/hellospring/controller/HelloController.java
package hello.hellospring.domain;
public class Member {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
비즈니스 요구사항을 봤을때 멤버 도메인에는
이 들어가야한다.
우리가 만든 웹은 따로 db를 사용하지 않기 때문에, 아래 내용 전부가 필요하지는 않다. 그러나 나중을 위해서 한번 살펴나 보자.
hello/hellospring/repository/MemberRepository.java
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
인터페이스는 틀을 미리 짜주는 역할을 하는데(템플릿 같은 느낌), repository 를 짤때 필요한 함수들을 만들었다.
hello/hellospring/repository/MemoryMemberRepository.java
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.stereotype.Repository;
import java.util.*;
@Repository
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore(){
store.clear();
}
}
repository interface의 구현체이다.
@Repository
우리는 db에 저장을 하지 않고 메모리에 저장을 할껀데, Map을 사용할 것이다.
public Member save(Member member)
public Optional<Member> findById(Long id)
Optional.ofNullable(store.get(id))
public Optional<Member> findByName(String name)
stream 이란?
Lambda 란?
( 파라미터 ) -> { 몸체 }
filter 란?
따라서 위 세가지를 다 합쳐버린 식인 hashmap.stream.filter((param)->{})
이게 완성이 된다.
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
store의 value를 stream으로 받아서, Fileter로 필터링을 한다. 그런데 그 조건이 member의 이름이 함수 파라미터로 들어온 name과 같은 조건일때를 말한다. 하나가 찾아지면 바로 리턴을 하게된다.
public List<Member> findAll()
new ArrayList<>(store.values());
로 store에 저장되어있는 value인 member 객체를 리스트로 바꿔서 반환한다.애플리케이션 비즈니스 로직 처리와 비즈니스와 관련된 도메인 모델의 적합성 검증을 한다.
트렌젝션(DB 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한꺼번에 모두 수행되어야 할 일련의 연산들)을 관리한다.
(1) 판매처에 돈보내기, (2) 판매처에서 돈 받기
이 있다고 하면, (1)은 성공했지만 (2)가 실패를 하게되면, 작업의 실행하기 전 상태로 돌리(rollback)는것을 하는것이 트렌젝션이다.Atomicity; 원자성
: 트랜잭션 내의 작업들은 모두 성공 또는 모두 실패한다.Consistency; 일관성
: 모든 트랜잭션은 일관성 있는 DB 상태를 유지한다. (ex: DB의 무결성 제약 조건 항상 만족)Isolation; 격리성
: 동시에 실행되는 트랜잭션들은 서로 영향을 미치지 않는다. (ex: 동시에 같은 데이터 수정 X)Durability; 지속성
: 트랜잭션이 성공적으로 끝나면 그 결과는 항상 기록되어야 한다.프레젠테이션 계층과 데이터 엑세스 계층 사이를 연결하는 역할로서 두 계층이 직접적으로 통신하지 않게 한다.
Service 인터페이스와 @Service 어노테이션을 사용하여 작성된 Service 구현 클래스가 이 계층에 속한다.
나중에 Controller 가 Service를 통해 회원가입과 데이터를 가져올 수 있게 된다. (컨트롤러가 서비스를 의존하는 관계)
hello/hellospring/service/MemberService.java
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public Long join(Member member){
validateDuplicateMember(member); //중복회원 검증
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
public List<Member> findMember() {
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
}
@Service
private final MemberRepository memberRepository;
@Autowired
Constructor Dependency Injection
이다.public MemberService(MemberRepository memberRepository)
public Long join(Member member)
validateDuplicateMember(member);
를 통해 멤버가 존재하는지 확인validateDuplicateMember(member)
[RuntimeException] IllegalStateException
메소드가 요구된 처리를 하기에 적합한 상태에 있지 않을때 throw
를 사용throw new 강제시킬예외
public List<Member> findMember()
public Optional<Member> findOne(Long memberId)
package hello.hellospring.controller;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
private final MemberService memberService;
DI를 하는 방법이 2가지가 있다.
하나는 Component Scan이 있다. 아까 했던 방법이다. @Controller
@Service
@Repository
annotation을 통해 Spring 빈에 등록을 한다. 여기에 이 3가지가 @Component
가
you should always put most of the business logic into value objects. Entities in this situation would act as wrappers upon them and represent more high-level functionality.
참고
https://yadon079.github.io/2021/spring/spring-web-layer
https://yeonyeon.tistory.com/223
https://velog.io/@gentledot/ddd-domain-model
https://multifrontgarden.tistory.com/182?category=471239
https://okky.kr/articles/779150
https://mangkyu.tistory.com/70
https://engkimbs.tistory.com/646
https://futurecreator.github.io/2018/08/26/java-8-streams/
https://codechacha.com/ko/stream-filter/
오 굿 제가 찾던 내용입니다