Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 43 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,50 @@
## TO-DO
## 배운점

### Document
### SimpleJdbcInsertOperations, SqlParameterSource

- [ ] 결재자는 생성자에서만 추가하고 불변으로 관리해야 하지 않나? 왜 따로 add하지?
- [ ] 결재자 중복 확인이 필요할 것 같다.
- [ ] ApprovalState 필드를 가져야 하는가? DocumentApprovals에다가 물어보면 되는 거 아닌가? 연산 시간때문에 별도의 필드를 갖는게 나은가?
SQL을 하드코딩하는 것보다는 한 단계 추상화된 JDBC 기술들을 사용하는 게 더 낫다.

### DocumentApproval
- 오탈자 등 휴먼 에러로 인한 버그를 줄일 수 있다.
- 재사용성과 유지보수성이 개선된다.
- SQL을 직접 작성하면 끽해봐야 매직 넘버나 스트링을 빼는 정도로만 재사용성을 높일 수 있다.
- 반면 얘네들은 엔티티를 자동 매핑해주기도 하고... 스트링의 나열로 보는 것보다 가독성도 낫다.
- 그래도 여전히 불편하다.

- [ ] approvalOrder 필드를 가져야 하는가? 어차피 DocumentApprovals의 리스트가 순서를 다 알고 있지 않나?
## 의문점

### 왜 엔티티를 쓸 일이 없는가

DocumentApproval 엔티티를 만들지 않았는데도 API가 동작한다....? 이래도 되는 건가... 무슨 짓을 저지른 건가...

### documentService.findById()

1. 테이블별로 메서드 분리해서 각각 찾아오기
- document, user, document_approval 테이블 각각 조회...? 에반데
- 게다가 SELECT문에서 조인해서 document 가져오면 document rowMapper도 같이 수정해야됨 진짜 구리다
- 우선 response 형식에 approvers가 없으니까 document_approval 테이블은 패스
- 근데 documentDao가 user 테이블까지 접근하는건 짱별로인듯

2. 테이블 조인해서 한 번에 찾아오기
- 이럴거면 테이블별로 dao를 왜 나누냐..?

### DB 테스트 시 초기 데이터로의 의존성

현재 dao 테스트가 data.sql에서 제공하는 초기 데이터셋에 의존하고 있다.

- findById(): 문서가 이미 저장되어 있어야 find가 가능하다.
- addDocument(): pk를 검증할 수 없다. auto_increment pk 값을 모르기 때문이다.

해결책은 뭘까

1. 그냥 초기 적재 데이터에 의존한다.
- 적재 데이터가 바뀌면 그 때마다 테스트가 깨지지 않나..
- 영향 크게 안 받게 해놔도 언젠간 깨지지 않나..
2. find 하기 전에 데이터를 넣던지, add 하기 전에 pk를 미리 알아 온다.
- 이전에 수행하는 로직에 의존하지 않나..

### 테스트 실행 방식에 따른 결과 차이

DocumentH2DaoTest의 find_by_drafter_success() 결과가 독립적이지 않다.

- 하나만 따로 돌리면 성공하는데, 전체 테스트를 한 번에 돌리면 fail한다.
- 이유를 모르겠다.... 머냐...
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ dependencies {
testImplementation('org.springframework.boot:spring-boot-starter-test')

// JDBC
// implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'

// JPA
// implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package playground.learning;
package learning;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
Expand All @@ -21,7 +21,7 @@ public ApprovalState approve() {
if (this == CANCELED) {
throw new IllegalArgumentException("이미 결재자로부터 거절된 문서입니다.");
}

return APPROVED;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package playground.learning;
package learning;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package playground.learning;
package learning;

import lombok.Builder;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package playground.learning;
package learning;

import lombok.Builder;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package playground.learning;
package learning;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -20,8 +20,8 @@ public static DocumentApprovals empty() {
public void addApprovals(List<User> approvers) {
List<DocumentApproval> documentApprovals =
IntStream.range(0, approvers.size())
.mapToObj(index -> DocumentApproval.of(approvers.get(index), index + 1))
.collect(Collectors.toList());
.mapToObj(index -> DocumentApproval.of(approvers.get(index), index + 1))
.collect(Collectors.toList());

this.approvals.addAll(documentApprovals);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package playground.learning;
package learning;

import lombok.Builder;
import lombok.EqualsAndHashCode;
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/playground/domain/document/DocumentController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package playground.domain.document;

import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import playground.domain.document.dto.AddDocumentRequest;
import playground.domain.document.dto.BoxDocument;
import playground.domain.document.dto.SingleDocument;

import java.util.List;

@RequestMapping(path = "/api/documents",
produces = MediaType.APPLICATION_JSON_VALUE)
public interface DocumentController {

ResponseEntity<SingleDocument> findDocument(Long id);

ResponseEntity<Long> addDocument(AddDocumentRequest addDocumentRequest);

ResponseEntity<List<BoxDocument>> findOutbox(Long drafterId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package playground.domain.document;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import playground.domain.document.dto.AddDocumentRequest;
import playground.domain.document.dto.BoxDocument;
import playground.domain.document.dto.OutBox;
import playground.domain.document.dto.SingleDocument;

import java.util.List;

@RestController
@RequiredArgsConstructor
public class DocumentControllerImpl implements DocumentController {

private final DocumentService documentService;

@Override
@GetMapping("/{id}")
public ResponseEntity<SingleDocument> findDocument(@PathVariable Long id) {
SingleDocument result = documentService.findById(id);

return ResponseEntity
.status(HttpStatus.OK)
.body(result);
}

@Override
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Long> addDocument(@RequestBody AddDocumentRequest addDocumentRequest) {
Long documentId = documentService.addDocument(addDocumentRequest);

return ResponseEntity
.status(HttpStatus.ACCEPTED)
.body(documentId);
}

@Override
@GetMapping("/outbox")
public ResponseEntity<List<BoxDocument>> findOutbox(@RequestParam Long drafterId) {
OutBox outbox = documentService.findOutboxOf(drafterId);

return ResponseEntity
.status(HttpStatus.OK)
.body(outbox.getElements());
}
}
14 changes: 14 additions & 0 deletions src/main/java/playground/domain/document/DocumentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package playground.domain.document;

import playground.domain.document.dto.AddDocumentRequest;
import playground.domain.document.dto.OutBox;
import playground.domain.document.dto.SingleDocument;

public interface DocumentService {

SingleDocument findById(Long id);

Long addDocument(AddDocumentRequest addDocumentRequest);

OutBox findOutboxOf(Long drafterId);
}
68 changes: 68 additions & 0 deletions src/main/java/playground/domain/document/DocumentServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package playground.domain.document;

import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.stereotype.Service;
import playground.domain.document.dao.DocumentApprovalDao;
import playground.domain.document.dao.DocumentDao;
import playground.domain.document.dto.AddDocumentRequest;
import playground.domain.document.dto.OutBox;
import playground.domain.document.dto.SingleDocument;
import playground.domain.document.dto.param.AddDocumentApprovalParam;
import playground.domain.document.dto.param.AddDocumentParam;
import playground.domain.document.entity.Document;
import playground.domain.document.entity.Documents;
import playground.domain.user.dao.UserDao;
import playground.domain.user.entity.User;

import java.util.List;
import java.util.NoSuchElementException;

@Service
@RequiredArgsConstructor
public class DocumentServiceImpl implements DocumentService {

private final DocumentDao documentDao;
private final DocumentApprovalDao documentApprovalDao;
private final UserDao userDao;

@Override
public SingleDocument findById(Long id) {
try {
Document document = documentDao.findById(id);
User drafter = userDao.findDrafterOf(document);

return SingleDocument.of(document, drafter);
} catch (IncorrectResultSizeDataAccessException e) {
throw new NoSuchElementException("id에 맞는 document가 없음");
}
}

@Override
public Long addDocument(AddDocumentRequest addDocumentRequest) {
AddDocumentParam addDocumentParam = AddDocumentParam.of(addDocumentRequest);
Long documentId = documentDao.addDocument(addDocumentParam);

AddDocumentApprovalParam addDocumentApprovalParam = AddDocumentApprovalParam.of(documentId, addDocumentRequest);
addDocumentApproval(addDocumentApprovalParam);

return documentId;
}

@Override
public OutBox findOutboxOf(Long drafterId) {
List<Document> elements = documentDao.findByDrafter(drafterId);
Documents documents = new Documents(elements);

return OutBox.of(documents);
}

private void addDocumentApproval(AddDocumentApprovalParam addDocumentApprovalParam) {
try {
documentApprovalDao.addApprovals(addDocumentApprovalParam);
} catch (DataIntegrityViolationException e) {
throw new IllegalArgumentException("존재하지 않는 유저를 결재자로 등록함");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package playground.domain.document.dao;

import playground.domain.document.dto.param.AddDocumentApprovalParam;

public interface DocumentApprovalDao {

void addApprovals(AddDocumentApprovalParam addDocumentApprovalParam);
}
15 changes: 15 additions & 0 deletions src/main/java/playground/domain/document/dao/DocumentDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package playground.domain.document.dao;

import playground.domain.document.dto.param.AddDocumentParam;
import playground.domain.document.entity.Document;

import java.util.List;

public interface DocumentDao {

Document findById(Long id);

Long addDocument(AddDocumentParam addDocumentParam);

List<Document> findByDrafter(Long drafterId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package playground.domain.document.dao;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;
import playground.domain.document.entity.ApprovalState;
import playground.domain.document.entity.Category;
import playground.domain.document.entity.Document;
import playground.domain.user.entity.User;

import java.sql.ResultSet;
import java.sql.SQLException;

@Component
public class DocumentRowMapper implements RowMapper<Document> {

@Override
public Document mapRow(ResultSet rs, int rowNum) throws SQLException {
return Document.builder()
.id(rs.getLong("id"))
.title(rs.getString("title"))
.contents(rs.getString("contents"))
.drafter(User.builder()
.id(rs.getLong("drafter_id"))
.build())
.category(Category.valueOf(rs.getString("category")))
.approvalState(ApprovalState.valueOf(rs.getString("approval_state")))
.createdAt(rs.getTimestamp("created_at"))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package playground.domain.document.dao.h2;

import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.jdbc.core.simple.SimpleJdbcInsertOperations;
import org.springframework.stereotype.Repository;
import playground.domain.document.dao.DocumentApprovalDao;
import playground.domain.document.dto.param.AddDocumentApprovalParam;
import playground.domain.document.dto.param.sql.AddDocumentApprovalSqlParam;

import java.util.List;

@Repository
@RequiredArgsConstructor
public class DocumentApprovalH2Dao implements DocumentApprovalDao {

private final JdbcTemplate jdbcTemplate;

@Override
public void addApprovals(AddDocumentApprovalParam addDocumentApprovalParam) throws DataIntegrityViolationException {
Long documentId = addDocumentApprovalParam.getDocumentId();
List<Long> approversId = addDocumentApprovalParam.getApproversId();

SimpleJdbcInsertOperations insertOperations = new SimpleJdbcInsert(jdbcTemplate)
.withTableName("DOCUMENT_APPROVAL");

approversId.stream()
.map(approverId -> AddDocumentApprovalSqlParam.of(documentId, approverId))
.forEach(insertOperations::execute);
}
}
Loading