TIL - 0704

java-ims

다운로드 기능 만들기

  • 업로드 했던 파일을 반대로 다운로드 하는 기능을 만들어야함
    • 특정 이슈에 특정 유저가 올린 특정 파일을 다운로드
    • 업로드 했을 때 AttachmentRepository에 업로드 정보를 저장함 : 이슈 id, 사용자 id, 첨부파일 uuid(식별자 - 런타임 시에 고정된 전략이라서 static 메소드를 사용했지만 AttachmentNameConverter도 런타임 시에 다양한 전략을 사용하게끔 한다면 DI로 변경해도됨)와 origin name 등
public class AttachmentService {

    public Issue upload(User loginUser, Issue issue, MultipartFile file) throws IOException {
        String savedFileName = fileSaver.save(file, AttachmentNameConverter.convertName(file.getOriginalFileName()));
        Attachment attachment = new Attachment()
                .uploadBy(loginUser)
                .toIssue(issue)
                .setOriginName(file.getOriginalFilename())
                .setManageName(savedFileName);
        attachmentRepo.save(attachment);
        return issue;
    }
}
  • 다운로드 URI는 GET 메소드 맵핑(attachment id값에 의한 식별) -> 파일 정보 찾고, HTTP 리스폰스 메세지 작성 후 리턴
    • PathResource : 로컬에 저장된 파일을 다루는 API(현재 사용하고 있는 FileSaver의 구현체가 로컬에 저장하는 LocalFileSaver)
    • HttpHeader 작성 역할 분리 필요 : HttpHeader 작성하는 것도 Controller에서 작성할 필요없이 PathResource만 Service로부터 리턴을 받은 후 HttpHeader작성 역할에 요청해서 Header를 만들어내도록 하는 것이 좋음
public class AttachmentController {
        
    @GetMapping("/{id}")
    public ResponseEntity<PathResource> download(@LoginUser User loginUser, @PathVariable Long issueId, @PathVariable Long id) throws IOException {
        Attachment attachment = attachmentService.findById(id);
        PathResource pathResource = attachmentService.download(loginUser, issueService.findById(issueId), id);
        
        HttpHeaders header = new HttpHeaders();
        header.setContentType(MediaType.MULTIPART_FORM_DATA);
        header.setContentLength(pathResource.contentLength());
        header.set(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s", attachment.getOriginName)));
        return new ResponseEntity<>(pathResource, header, HttpStatus.OK)        
    }    
}

업로드 위치 관리 외부에서 해보기

  • LocalFileSaver를 비롯한 FileSaver에서 사용하는 파일 업로드 위치를 외부에서 관리하기
    • springboot.properties + @Value 사용해서 인젝션 하기
    • 설정값을 한 곳(springboot.properties)에서 관리한다는 장점이
  • 구현하기 : 상태값 없이 저장전략 각각 구현하도록하는 기존 인터페이스 구조와 상태값(uploadPath)을 가질 수 밖에 없는 구조 간에 충돌이 생김
    • 각각 구현하되 상태값을 공통적으로 가지면서 공통적인 메소드로 묶는 방법은 추상클래스
/* application.properties */
file.save.local=/Users/imjinbro/Desktop/codesquad/workspace/jwp/java-ims/src/main/resources/upload/

/* FileSaver */
public abstract class FileSaver {
    private String uploadPath;

    public FileSaver(String uploadPath) {
        this.uploadPath = uploadPath;
    }

    public abstract String save(MultipartFile file, String fileManageName) throws IOException;

    Path getPath(String fileManageName) {
        return Paths.get(uploadPath + fileManageName);
    }

    protected String getSavePath() {
        return uploadPath;
    }
}


/* LocalFileSaver */
@Component(value = "localFileSaver")
public class LocalFileSaver extends FileSaver {

    public LocalFileSaver(@Value("${file.save.local}") String uploadPath) {
        super(uploadPath);
    }

    @Override
    public String save(MultipartFile file, String fileManageName) throws IOException {
        File saveFile = new File(getSavePath() + fileManageName);
        file.transferTo(saveFile);
        return fileManageName;
    }
}
  • 업로드 통합테스트 결과 해당 구조만 변경하고 테스트 통과 : 컨트롤러, 서비스 코드 변경없음

git

  • git add .만 사용해서 staging area로 옮기지 말 것 : 불필요한 파일 또한 관리 대상이 됨
    • 아예 불필요한 파일은 git 관리 범위 바깥에 저장해두거나 git add . 와 같은 모든 파일을 한꺼번에 관리 대상으로 지정하는 명령어 쓰지않기

자바

  • 추상클래스 vs 인터페이스 : 상태값을 가지는가 가지지않느냐의 차이, 상태값에 따라 다르게 동작하는가, 단순히 공통된 동작이나 구현체마다 다르게 동작하는가

데이터베이스

비용도 생각을 해보자

  • 자원을 무한정 사용할 수 있으면 좋지만, 현실은 한정된 자원 내에서 최대한의 효율을 뽑아내야함
    • 초기비용(라이센스 비용)과 운영비용(기술 지원 등을 포함한 비용, 서브스크립션 비용 - aws)을 고려해야함
  • 에디션에 따라 기술 지원, 편의 기능(보안, 로그, 버젼 관리 - 무조건적으로 최신버젼을 도입하면 안정적이지 않기때문에 그러면 안됨 등)의 차이가 있음
  • PaaS(Platform as a Service) : aws의 RDB 서비스 처럼 미들웨어(DBMS와 같은 소프트웨어)를 포함한 클라우드 서비스 임대 모델을 가리키는 말 차이