728x90
반응형
프로젝트 구성
도메인을 생성할 때 클래스는 한가지 기능만을 하도록 하는 SRP를 지켰고,
관심사를 분리하여 DIP를 지키기 위해 생각을 하며 코딩했습니다.
SRP, DIP에 대한 자세한 설명
[Todo.java]
package hooyn.todo.domain;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Todo {
@Id @GeneratedValue
@Column(name = "todo_id")
private Long id;
private String title;
private String content;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "date", column = @Column(name = "deadline_date")),
@AttributeOverride(name = "time", column = @Column(name = "deadline_time"))
})
private Deadline deadline;
private LocalDateTime create_time;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "uuid")
private Member member;
//연간관계 편의 메서드
void setMember(Member member){
this.member = member;
member.getTodos().add(this);
}
//변경감지를 위한 set 메서드
public void setTitle(String title){
this.title = title;
}
public void setDeadline(Deadline deadline){
this.deadline = deadline;
}
public void setContent(String content) {
this.content = content;
}
//생성 매서드
}
투두 기한을 위한 클래스(Deadline)를 생성하여 SRP를 지켰습니다.
그리고 데이베이스에 저장될 column name도 @AttributeOverrides를 통해 지정해주었습니다.
또한 Member도 다대일 매핑관계를 추가해주었고 엔티티 조회 시 Member는 직접 접근(Get메서드)을
하지 않으면 프록시 객체로 가져와 Member 엔티티를 가져오지 않게 LAZY로 설정하였습니다.
그리고 Todo를 저장할 때 Member와의 연관관계를 설정해주기 위해
연관관계 편의 메서드도 생성해주었습니다.
[Update Todo.java]
package hooyn.todo.domain;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Todo {
@Id @GeneratedValue
@Column(name = "todo_id")
private Long id;
private String title;
private String content;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "date", column = @Column(name = "deadline_date")),
@AttributeOverride(name = "time", column = @Column(name = "deadline_time"))
})
private Deadline deadline;
private LocalDateTime create_time;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "uuid")
private Member member;
//연간관계 편의 메서드
private void setMember(Member member){
this.member = member;
member.getTodos().add(this);
}
//변경감지를 위한 set 메서드
public void changeTitle(String title){
this.title = title;
}
public void changeDeadline(Deadline deadline){
this.deadline = deadline;
}
public void changeContent(String content) {
this.content = content;
}
//빌더를 통한 생성 매서드
public static Todo createTodo(String title, String content, Deadline deadline, Member member){
Todo todo = Todo.builder()
.title(title)
.content(content)
.deadline(deadline)
.build();
todo.setMember(member);
return todo;
}
@Builder
private Todo(String title, String content, Deadline deadline){
this.title = title;
this.content = content;
this.deadline = deadline;
this.create_time = LocalDateTime.now();
}
}
변경감지를 위한 메서드의 이름을 Set보다는 Change로 하는 것이 좋다고 하여 수정하였습니다.
또한 투두의 무분별한 생성을 막기위해서 빌더를 통해 생성메서드를 구현하였습니다.
빌더를 사용하는 이유
1. 필요한 데이터만 설정할 수 있습니다.
2. 유연성을 확보할 수 있습니다.
3. 가독성을 높일 수 있습니다.
4. 불변성을 확보할 수 있습니다.
[Deadline.java]
package hooyn.todo.domain;
import lombok.Getter;
import javax.persistence.Embeddable;
@Embeddable
@Getter
public class Deadline {
private String date;
private String time;
}
[TodoRepository.java]
package hooyn.todo.repository;
import hooyn.todo.domain.Deadline;
import hooyn.todo.domain.Todo;
import java.util.List;
public interface TodoRepository {
//투두 작성
Long save(Todo todo);
//투두 엔티티 검색
Todo findById(Long id);
//투두 UUID에 따른 엔티티 검색
List<Todo> findByUUID(String uuid);
//투두 Deadline에 따른 엔티티 검색 + 페이징 변수 추가
List<Todo> findByDeadline(String uuid, Deadline deadLine, Integer page);
//투두 콘텐츠 키워드에 따른 엔티티 검색 + 페이징 변수 추가
List<Todo> findByContent(String uuid, String content, Integer page);
//투두 수정, 삭제 권한 확인
boolean checkAuthorization(String uuid, Long id);
//투두 삭제
Long delete(Long id);
}
[TodoRepositoryImpl.java]
package hooyn.todo.repository;
import com.querydsl.jpa.impl.JPAQueryFactory;
import hooyn.todo.domain.Deadline;
import hooyn.todo.domain.QTodo;
import hooyn.todo.domain.Todo;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.UUID;
import static hooyn.todo.domain.QTodo.todo;
@Repository
@RequiredArgsConstructor
@Primary
public class TodoRepositoryImpl implements TodoRepository {
private final EntityManager em;
private final JPAQueryFactory queryFactory;
/**
* 투두 작성
*/
@Override
public Long save(Todo todo) {
em.persist(todo);
return todo.getId();
}
/**
* 투두_아이디에 따른 투두 조회
*/
@Override
public Todo findById(Long id) {
return em.find(Todo.class, id);
}
/**
* UUID에 따른 투두 조회
*/
@Override
public List<Todo> findByUUID(String uuid) {
return queryFactory
.selectFrom(todo)
.where(todo.member.uuid.eq(UUID.fromString(uuid)))
.fetch();
}
/**
* 기한에 따른 투두 조회
*/
@Override
public List<Todo> findByDeadline(String uuid, Deadline deadLine, Integer page) {
return queryFactory
.selectFrom(todo)
.where(todo.member.uuid.eq(UUID.fromString(uuid))
.and(todo.deadline.date.eq(deadLine.getDate())))
.orderBy(todo.create_time.desc()) //생성한 날 기준 내림차순
.offset(0+((page-1)*10)) //페이징 10개 불러오기
.limit(10)
.fetch();
}
/**
* content가 포함된 투두 조회
*/
@Override
public List<Todo> findByContent(String uuid, String content, Integer page) {
return queryFactory
.selectFrom(todo)
.where(todo.member.uuid.eq(UUID.fromString(uuid))
.and(todo.content.like("%"+content+"%")))
.orderBy(todo.create_time.desc()) //생성한 날 기준 내림차순
.offset(0+((page-1)*10)) //페이징 10개 불러오기
.limit(10)
.fetch();
}
/**
* 권한 확인
*/
@Override
public boolean checkAuthorization(String uuid, Long id) {
List<Todo> fetch = queryFactory
.selectFrom(todo)
.where(todo.member.uuid.eq(UUID.fromString(uuid))
.and(todo.id.eq(id)))
.fetch();
if(fetch.size()>0){
return true;
} else {
return false;
}
}
/**
* 투두 삭제
*/
@Override
public Long delete(Long id) {
queryFactory
.delete(todo)
.where(todo.id.eq(id))
.execute();
return id;
}
}
[FindTodoDto.java]
package hooyn.todo.dto;
import hooyn.todo.domain.Deadline;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Getter
@NoArgsConstructor
public class FindTodoDto {
private Long id;
private String title;
private String content;
private Deadline deadline;
private LocalDateTime create_time;
public FindTodoDto(Long id, String title, String content, Deadline deadline, LocalDateTime create_time) {
this.id = id;
this.title = title;
this.content = content;
this.deadline = deadline;
this.create_time = create_time;
}
}
[TodoService.java]
package hooyn.todo.service;
import hooyn.todo.domain.Deadline;
import hooyn.todo.domain.Todo;
import hooyn.todo.dto.FindTodoDto;
import java.util.List;
public interface TodoService {
//투두 작성
Long writeTodo(Todo todo);
//투두 조회 (todo_id)
Todo findTodoById(Long todo_id);
//투두 조회 (Deadline) //Dto를 통해서 데이터를 클라이언트에 반환 + 페이지 변수 추가
List<FindTodoDto> findTodoByDeadline(String uuid, Deadline deadline, Integer page);
//투두 조회 (Content) //Dto를 통해서 데이터를 클라이언트에 반환 + 페이지 변수 추가
List<FindTodoDto> findTodoByContent(String uuid, String content, Integer page);
//투두 수정
Long updateTodo(Long todo_id, String title, String content, Deadline deadline);
//투두 삭제
Long deleteTodo(Long todo_id);
//권한 확인
boolean checkAuthorization(String uuid, Long todo_id);
}
[TodoServiceImpl.java]
package hooyn.todo.service;
import hooyn.todo.domain.Deadline;
import hooyn.todo.domain.Todo;
import hooyn.todo.dto.FindTodoDto;
import hooyn.todo.repository.TodoRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Primary
public class TodoServiceImpl implements TodoService {
private final TodoRepository todoRepository;
/**
* 투두 작성
*/
@Override
@Transactional
public Long writeTodo(Todo todo) {
return todoRepository.save(todo);
}
/**
* 투두 조회 (id)
*/
@Override
@Transactional(readOnly = true)
public Todo findTodoById(Long todo_id) {
return todoRepository.findById(todo_id);
}
/**
* 투두 조회 (deadline)
*/
@Override
@Transactional(readOnly = true)
public List<FindTodoDto> findTodoByDeadline(String uuid, Deadline deadline, Integer page) {
List<Todo> todos = todoRepository.findByDeadline(uuid, deadline, page);
return todos
.stream()
.map(todo -> new FindTodoDto( //DTO로 데이터를 받아서 저장하고 반환
todo.getId(),
todo.getTitle(),
todo.getContent(),
todo.getDeadline(),
todo.getCreate_time()))
.collect(Collectors.toList());
}
/**
* 투두 조회 (content)
*/
@Override
@Transactional(readOnly = true)
public List<FindTodoDto> findTodoByContent(String uuid, String content, Integer page) {
List<Todo> todos = todoRepository.findByContent(uuid, content, page);
return todos
.stream()
.map(todo -> new FindTodoDto( //DTO로 데이터를 받아서 저장하고 반환
todo.getId(),
todo.getTitle(),
todo.getContent(),
todo.getDeadline(),
todo.getCreate_time()))
.collect(Collectors.toList());
}
/**
* 투두 삭제
*/
@Override
@Transactional
public Long deleteTodo(Long todo_id) {
return todoRepository.delete(todo_id);
}
/**
* 투두 업데이트 (변경감지 사용)
*/
@Override
@Transactional
public Long updateTodo(Long todo_id, String title, String content, Deadline deadline) {
Todo todo = todoRepository.findById(todo_id);
if(!title.isBlank()){
todo.changeTitle(title);
}
if(!content.isBlank()){
todo.changeContent(content);
}
if(deadline!=null || !deadline.getDate().isBlank()){
todo.changeDeadline(deadline);
}
return todo_id;
}
/**
* 투두 권한 확인
*/
@Override
public boolean checkAuthorization(String uuid, Long todo_id) {
return todoRepository.checkAuthorization(uuid, todo_id);
}
}
투두에 대한 도메인과 Repository, Service 코드를 구현하였습니다.
이번 프로젝트 부터 SRP와 DIP에 대한 개념을 이해하고
이를 적용하면서 프로젝트를 진행해보고 있습니다.
728x90
반응형
'Project > Todo' 카테고리의 다른 글
[Spring Boot] Todo 서비스 구현 (3) (2) | 2022.07.18 |
---|---|
[Spring Boot] Todo 서비스 구현 (2) (0) | 2022.07.18 |
[Refactoring] 관심사 분리를 통한 DIP 지키기 (0) | 2022.07.15 |
[Spring Boot] Member 서비스 구현 (2) (0) | 2022.07.13 |
[AWS EC2 ubuntu] MySQL 데이터베이스 구축, 스프링 배포 파일 빌드, 서버 데이터베이스를 이용한 Member API 테스트 (2) | 2022.07.12 |