3. 여러 클라우드에
올라간 파일들을
통합 관리하는
앱을 만들어보자.
출처: http://www.flickr.com/photos/42402605@N07/5693120074
4. 빨리 선보이기 위한 최초 0.1 버전
• 대상 클라우드
o
드롭박스, 박스
o
각 클라우드의 목록 조회하기
각 클라우드에서 파일 다운로드 하기
• 주요 기능
o
5. 파일 정보 표현
public class FileInfo {
private CloudId cloudId;
private String name;
private long length;
… // get 메서드
}
public enum CloudId {
DROPBOX,
BOX;
}
6. 파일 목록 조회
public List<FileInfo> getFileInfos(CloudId cloudId) {
if (cloudId == CloudId.DROPBOX) {
DropboxClient dc = …;
List<DbFile> dbFiles = db.getFiles();
List<FileInfo> result = new ArrayList<>();
for (DbFile dbFile : dbFiles) {
FileInfo fi = new FileInfo();
fi.setCloudId(CloudId.DROPBOX);
…
result.add(fi);
}
return result;
} else if (cloudId == CloudId.BOX) {
BoxService boxSvc = …;
… //
}
}
7. 파일 다운로드 (계속)
public void download(FileInfo file, File localTarget) {
if (file.getCloudId() == CloudId.DROPBOX) {
DropboxClient dc = …;
FileOutputStream out = new FileOutputStream(localTarget);
dc.copy(file.getFileId(), out);
out.close();
} else if (file.getCloudId() == CloudId.BOX) {
// TODO
}
동작하도록 반영
}
public class FileInfo {
private CloudId cloudId;
private String fileId;
private String name;
...
for (DbFile dbFile : dbFiles) {
FileInfo fi = new FileInfo();
fi.setFileId(dbFile.getId());
fi.setCloudId(cloudId);
…
}
8. 파일 다운로드
public void download(FileInfo file, File localTarget) {
if (file.getCloudId() == CloudId.DROPBOX) {
...
} else if (file.getCloudId() == CloudId.BOX) {
BoxService boxSvc = …;
InputStream is = boxSvc.getInputStream(file.getId());
FileOutputStream out = new FileOutputStream(localTarget);
CopyUtil.copy(is, out);
}
}
9. 야호!
• 무사히 동작하는 0.1 버전 완성
• 0.5 버전을 향해
o
기능 확대
§ 파일 업로드
§ 파일 삭제
§ 검색
§ 이미지 미리보기
10. 0.5v 기능 추가
public FileInfo upload(File file, CloudId cid) {
if (cid == CloudId.DROPBOX) {
...
} else if (cid == CloudId.BOX) {
...
public void delete(String fileId, CloudId cid) {
}
if (cid == CloudId.DROPBOX) {
}
...
} else if (cid == CloudId.BOX) {
...
}
public List<FileInfo> search(String query, CloudId cid) {
}
if (cid == CloudId.DROPBOX) {
...
} else if (cid == CloudId.BOX) {
...
}
}
11. 음...
• 무사히 동작하는 0.5 버전 완성
• 베타 버전을 향해
o
o
클라우드 추가
§ S클라우드
§ D클라우드
§ N클라우드
§ ...
기능 추가
§ 클라우드 간 파일 복사
12. 과정
public FileInfo upload(File file, CloudId cid) {
if (cid == CloudId.DROPBOX) {
...
} else if (cid == CloudId.BOX) {
...
}
코드복사
}
public FileInfo upload(File file, CloudId cid) {
if (cid == CloudId.DROPBOX) {
...
} else if (cid == CloudId.BOX) {
...
} else if (cid == CloudId.BOX) {
...
}
}
public FileInfo upload(File file, CloudId cid) {
if (cid == CloudId.DROPBOX) {
...
} else if (cid == CloudId.BOX) {
...
} else if (cid == CloudId.SCLOUD) {
ScloudClient scClient = …;
...
구현변경
}
}
public FileInfo upload(File file, CloudId cid) {
if (cid == CloudId.DROPBOX) {
...
조건변경
} else if (cid == CloudId.BOX) {
...
} else if (cid == CloudId.SCLOUD) {
...
}
}
13. 흔한 실수
public List<FileInfo> getFileInfos(CloudId cloudId) {
if (cloudId == CloudId.DROPBOX) {
…
} else if (cloudId == CloudId.BOX) {
…
} else if (cloudId == CloudId.SCLOUD) {
…
} else if (cloudId == CloudId.DCLOUD) {
…
public void delete(String fileId, CloudId cid) {
if (cid == CloudId.DROPBOX) {
...
return result;
} else if (cloudId == CloudId.BOX) {
… //
} else if (cloudId == CloudId.SCLOUD) {
… //
alert(“파일이 존재하지 않습니다.”);
...
} else if (cloudId == CloudId.DCLOUD) {
…
fi.setCloudId(CloudId.SCLOUD);
…
} else if (cloudId == CloudId.NCLOUD) {
…
}
}
음.. 파일이
있는데,
왜 없다 그러지?
alert(“파일이 존재하지 않습니다.”);
...
} else if (cloudId == CloudId.NCLOUD) {
… //
}
}
OO;;
14. 클라우드 간 파일 복사 기능 고려사항
• URL로부터 복사해서 가져오는 클라우드 존재
o
그런데, URL을 제공하지 않는 클라우드 존재
o
그런데, InputStream을 제공하지 않는 클라우드 존재
• InputStream을 제공하면 되는 클라우드 존재
• 로컬 File을 제공해야 하는 클라우드 존재
15. 클라우드 간 파일 복사 기능 구현 ...
public FileInfo copy(FileInfo fileInfo, CloudId from, CloudId to) {
if (to == CloudId.DROPBOX) { // URL로 복사하기 지원
DropBoxClient dbClient = …;
if (from == CloudId.BOX) {
dbClient.copyFromUrl(“http://www.box.com/files/”+fileInfo.getFileId());
} else if (from == CloudId.SCLOUD) {
ScloudClient sClient = …;
InputStream is = sClient.getInputStream(fileInfo.getFileId());
dbClient.copyFromInputStream(is, fileInfo.getName());
} else if (from ==CloudId.DCLOUD) {
dbClient.copyFromUrl(“http://www.dcloud.com/getfile?fileId=”+fileInfo.getFileId());
} else if (from == CloudId.NCLOUD) {
NCloudClient nClient = …;
File temp = File.createTemp();
nClient.save(fileInfo.getFileId(), temp);
InputStream is = new FileInputStream(temp);
dbClient.copyFromInputStream(is, fileInfo.getName());
}
} else if (to == CloudId.BOX) {
// 코드 계속 ….
16. 클라우드 간 파일 복사 기능 구현 ...
} else if (to == CloudId.BOX) { // URL로 복사하기 지원
BoxService boxService = …;
if (from == CloudId.DROPBOX) {
boxService.createFileByUrl(“http://www.dropbox.com/box/files/”+fileInfo.getFileId());
} else if (from == CloudId.SCLOUD) {
ScloudClient sClient = …;
InputStream is = sClient.getInputStream(fileInfo.getFileId());
boxService.uploadFile(is, fileInfo.getName());
} else if (from ==CloudId.DCLOUD) {
boxService.createFileByUrl(“http://www.dcloud.com/getfile?fileId=”+fileInfo.getFileId());
} else if (from == CloudId.NCLOUD) {
NCloudClient nClient = …;
File temp = File.createTemp();
nClient.save(fileInfo.getFileId(), temp);
boxService.uploadFile(temp, fileInfo.getName());
}
} else if (to == CloudId.SCLOUD) {
// 코드 계속 ….
17. 클라우드 간 파일 복사 기능 구현 ...
} else if (to == CloudId.SCLOUD) {
ScloudClient sClient = …;
if (from == CloudId.DROPBOX) {
…
...
} else if (from == CloudId.BOX) {
…
...
} else if (from ==CloudId.DCLOUD) {
…
...
} else if (from == CloudId.NCLOUD) {
…
...
}
} else if (to == CloudId.DCLOUD) {
// 코드 계속 ….
18. 클라우드 간 파일 복사 기능 코드
(if-else 구조만 표시)
public FileInfo copy(FileInfo fileInfo, CloudId from, CloudId to) {
if (to == CloudId.DROPBOX) { // URL로 복사하기 지원
DropBoxClient dbClient = …;
if (from == CloudId.BOX) {
...
} else if (from == CloudId.SCLOUD) {
…
...
} else if (from ==CloudId.DCLOUD) {
…
...
} else if (from == CloudId.NCLOUD) {
…
...
}
} else if (to == CloudId.BOX) { // URL로 복사하기 지원
BoxService boxService = …;
if (from == CloudId.DROPBOX) {
…
...
} else if (from == CloudId.SCLOUD) {
…
...
} else if (from ==CloudId.DCLOUD) {
…
...
} else if (from == CloudId.NCLOUD) {
…
...
}
} else if (to == CloudId.SCLOUD) {
ScloudClient sClient = …;
if (from == CloudId.DROPBOX) {
…
...
} else if (from == CloudId.BOX) {
…
...
} else if (from ==CloudId.DCLOUD) {
…
...
} else if (from == CloudId.NCLOUD) {
…
...
}
} else if (to == CloudId.DCLOUD) {
DcloudClient sClient = …;
if (from == CloudId.DROPBOX) {
…
...
} else if (from == CloudId.BOX) {
…
...
} else if (from ==CloudId.SCLOUD) {
…
...
} else if (from == CloudId.NCLOUD) {
…
...
}
} else if (to == CloudId.NCLOUD) {
NcloudClient sClient = …;
if (from == CloudId.DROPBOX) {
…
...
} else if (from == CloudId.BOX) {
…
...
} else if (from ==CloudId.SCLOUD) {
…
...
} else if (from == CloudId.DCLOUD) {
…
...
}
}
}
.
19. 새로운 요구사항
public FileInfo copy(FileInfo fileInfo, CloudId from, CloudId to) {
if (to == CloudId.DROPBOX) { // URL로 복사하기 지원
DropBoxClient dbClient = …;
if (from == CloudId.BOX) {
...
} else if (from == CloudId.SCLOUD) {
…
...
} else if (from ==CloudId.DCLOUD) {
…
...
} else if (from == CloudId.NCLOUD) {
…
...
}
} else if (to == CloudId.BOX) { // URL로 복사하기 지원
BoxService boxService = …;
if (from == CloudId.DROPBOX) {
…
...
} else if (from == CloudId.SCLOUD) {
…
...
} else if (from ==CloudId.DCLOUD) {
…
...
} else if (from == CloudId.NCLOUD) {
…
...
}
} else if (to == CloudId.SCLOUD) {
ScloudClient sClient = …;
if (from == CloudId.DROPBOX) {
…
...
} else if (from == CloudId.BOX) {
…
...
} else if (from ==CloudId.DCLOUD) {
…
...
} else if (from == CloudId.NCLOUD) {
…
...
}
} else if (to == CloudId.DCLOUD) {
DcloudClient sClient = …;
if (from == CloudId.DROPBOX) {
…
...
} else if (from == CloudId.BOX) {
…
...
} else if (from ==CloudId.SCLOUD) {
…
...
} else if (from == CloudId.NCLOUD) {
…
...
}
} else if (to == CloudId.NCLOUD) {
NcloudClient sClient = …;
if (from == CloudId.DROPBOX) {
…
...
} else if (from == CloudId.BOX) {
…
...
} else if (from ==CloudId.SCLOUD) {
…
...
} else if (from == CloudId.DCLOUD) {
…
...
}
}
클라우드 10개 추가
}
.
22. 개발 시간 증가 이유
• 코드 구조가 길어지고 복잡해짐
o
새로운 클라우드 추가시 모든 메서드에 새로운 if 블록 추가
§ 중첩 if-else는 복잡도 배로 증가
§ if-else가 많을수록 진척 더딤 (신중 모드)
• 관련 코드가 여러 곳에 분산됨
o
한 클라우드 처리와 관련된 코드가 여러 메서드에 흩어짐
o
코드 추가에 따른 노동 시간 증가
실수하기 쉽고, 이로 인한 불필요한 디버깅 시간 증가
• 결과적으로, 코드 가독성과 분석 속도 저하
o
26. DROPBOX용 구현 (계속)
public class DropBoxFileSystem implements CloudFileSystem {
private DropBoxClient dbClient = new DropBoxClient(...);
@Override
public List<CloudFile> getFiles() {
List<DbFile> dbFiles = dbClient.getFiles();
List<CloudFile> results = new ArrayList<>(dbFiles.size());
for (DbFile file : dbFiles) {
DropBoxCloudFile cf = new DropBoxCloudFile(file, dbClient);
results.add(cf);
}
return results;
}
27. DROPBOX용 구현
public class DropBoxCloudFile
implements CloudFile {
private DropBoxClient dbClient;
private DbFile dbFile;
public DropBoxCloudFile(
DbFile dbFile, dbClient) {
this.dbFile = dbFile;
this.dbClient = dbClient;
}
public String getId() {
return dbFile.getId();
}
public boolean hasUrl() {
return true;
}
public String getUrl() {
return dbFile.getFileUrl();
}
public String getName() {
return dbFile.getFileName();
}
public InputStream getInputStream() {
return dbClient
.createStreamOfFile(dbFile);
}
public void write(OutputStream out) {
...
}
public void delete() {
dbClient.deleteFile(dbFile.getId());
}
…
}
28. v0.1의 파일 목록/다운로드 기능 구현
public List<CloudFile> getFileInfos(CloudId cloudId) {
CloudFileSystem fileSystem =
CloudFileSystemFactory.getFileSystem(cloudId);
return fileSystem.getFiles();
}
public void download(CloudFile file, File localTarget) {
file.write(new FileOutputStream(localTarget));
}
현재 드롭박스 지원만 구현 된 상태
30. v0.1의 파일 목록/다운로드 기능 구현
(다시)
public List<CloudFile> getFileInfos(CloudId cloudId) {
CloudFileSystem fileSystem =
CloudFileSystemFactory.getFileSystem(cloudId);
return fileSystem.getFiles();
}
public void download(CloudFile file, File localTarget) {
file.write(new FileOutputStream(localTarget));
}
박스 지원도 반영 (코드 그대로네… 응?)
31. 복잡했던 파일 복사 기능의 변신:
copyFrom(CloudFile file) 추가
public void copy(CloudFile file, CloudId target) {
CloudFileSystem fileSystem =
CloudFileSystemFactory.getFileSystem(target);
fileSystem.copyFrom(file);
}
32. 복잡했던 파일 복사 기능의 변신:
파일시스템 별 copyFrom 메서드
-- DropBoxFileSystem
private DropBoxClient dbClient = new DropBoxClient(...);
public void copyFrom(CloudFile file) {
if (file.hasUrl())
dbClient.copyFromUrl(file.getUrl());
else
dbClient.copyFromInputStream(file.getInputStream(), file.getName());
}
-- NcloudFileSystem
private NcloudClient nClient = new NCloudClient(...);
public void copyFrom(CloudFile file) {
File tempFile = File.createTemp();
file.write(new FileOutputStream(tempFile));
nClient.upload(tempFile, file.getName());
}
33. 추상화를 잘 했더니
public class CloudFileManeger {
public List<CloudFile> getFileInfos(CloudId cloudId) {
CloudFileSystem fileSystem = CloudFileSystemFactory.getFileSystem(cloudId);
return fileSystem.getFiles();
}
public void download(CloudFile file, File localTarget) {
file.write(new FileOutputStream(localTarget));
}
public void copy(CloudFile file, CloudId target) {
CloudFileSystem fileSystem =
CloudFileSystemFactory.getFileSystem(target);
fileSystem.copyFrom(file);
}
…
...
}
추상화된
타입으로만
핵심 기능 구현
가능
35. 이것이 바로 OCP
Open-Closed principle
Open for Extenstion
확장에 열려 있음
Closed for Modification
수정엔 닫혀 있음
36. 추상화를 잘 했더니:
기능이 만들어 짐
• URL 제공 여부 및 구성 코드가 메서드로 이동
public FileInfo copy(FileInfo fileInfo,
CloudId from, CloudId to) {
if (to == CloudId.DROPBOX) {
DropBoxClient dbClient = …;
if (from == CloudId.BOX) {
dbClient.copyFromUrl(
“http://www.box.com/files/”+
fileInfo.getFileId());
} else if (from == CloudId.SCLOUD) {
ScloudClient sClient = …;
InputStream is = sClient.getInputStream(
fileInfo.getFileId());
dbClient.copyFromInputStream(is,
fileInfo.getName());
-- DropBoxFileSystem
private DropBoxClient dbClient = ...;
public void copyFrom(CloudFile file) {
if (file.hasUrl())
dbClient.copyFromUrl(file.getUrl());
else
dbClient.copyFromInputStream(
file.getInputStream(),
file.getName());
}
37. 이것이 바로 캡슐화Encapsulation
• 내부 구현을 외부에 감춤
o
내부 구현을 변경해도 이를 사용하는 코드 영향 최소화
-- DropBoxFileSystem
private DropBoxClient dbClient = ...
URL 생성 규칙이 변경되어도
이 코드는 바뀌지 않음
public void copyFrom(CloudFile file) {
if (file.hasUrl())
dbClient.copyFromUrl(file.getUrl());
else
URL 제공 여부가 변경되어도
이 코드는 바뀌지 않음
dbClient.copyFromInputStream(
file.getInputStream(),
file.getName());
}
40. 추상화는 언제?
• 유연함이 필요한 시점(변화가 생기는 시점)
o
o
예, 1개 클라우드 지원에서 2개 클라우드로 확장
예, 물류업체 추가
• 아직 구현할 수 없는 부분이 존재할 때
o
예, 물류 업체와의 연동 프로토콜 미확정
§ 물류 시스템 연동을 추상화해서 인터페이스로 표현
• 추상화(모델링)에는 비용이 발생
o
과도한 추상화(모델링)는 불필요한 복잡도를 증가시킴
41. 정리
• 개발 시간을 줄이기 위해 필요한 것 중 하나,
o
변화를 수용할 수 있는 설계/구현
o
o
변화에 대한 유연함 제공
이는 개발 비용(즉, 시간) 절감 효과!
o
캡슐화, 추상화, 다형성, SOLID
o
좋은 코드, 패턴, 테스트 주도 개발, 리팩토링 등
• 잘 된 추상화/캡슐화가 해 주는 것
• 익혀야 할 것 → 객체 지향
• 더불어 익힐 것