APi Gateway 구현체
Service registry
Config Server(여러 api 서버, 필요에 따라 DB 연동 및 서로 요청을 매핑)
Distributed Tracing
마이크로서비스 아키텍처(MSA)로 구성되어 있는 서비스들은 각자 다른 IP와 Port를
가지고 있다
-> 이러한 서로 다른 서비스들의 IP와 Port 정보에 대해서 저장하고 관리할 필요가 있는데 이것이 Service Discovery
여러 서비스들을 운용하기 위해서 클라우드 환경에서 인스턴스를 생성하여 구축
->이때 클라우드 환경에서 인스턴스는 AutoScaling, 생성, 삭제, 확장 등을 거치면서 IP나 Port들이 동적으로 변경될 가능성이 많음
-> 그때마다 서비스 변경사항에 대해서 일일이 알아내고 수정하고 하기에는 수십 ~ 수백개의 서비스들을 일일이 관리하기 어려울 뿐더러 클라이언트가 원하는 정보를 얻기 위해 요청 할 서비스를 찾기에 어려움을 겪을 것
AutoScaling
: 사용자가 정의한 주기나 이벤트에 따라 서버를 자동으로 생성하거나 삭제
서비스 클라이언트가 Service register에서 서비스의 위치를 찾아서 호출하는 방식
장점
단점
로드 밸런서는 컴퓨터 네트워크에서 사용되는 기술로, 네트워크 트래픽을 여러 서버에 분산하는 역할
호출 되는 서비스 앞에 로드밸런서를 넣는 방식이고, 클라이언트는 로드밸런서를 호출하면 로드밸런서가 Service Register로 부터 등록된 서비스의 위치를 전달하는 방식
장점
단점
클라이언트 사이드 디스커버리 패턴에는 대표적으로 Netflix OSS(Netflix Open Source Software)에서 Client-Side discovery Pattern을 제공하는 Netflix Eureka
(service registry component)
Server-Side Discovery의 예로는 AWS Elastic Load Balancer
(ELB), Kubernetes
MultiPartConfig
@Slf4j
@Configuration
public class MutlpartConfig {
@Value("${photoapp.file.defaultPath}")
public String defaultPath ="";
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setResolveLazily(true);
multipartResolver.setMaxUploadSize(1024 * 1024 * 10); // 10메가
multipartResolver.setDefaultEncoding(StandardCharsets.UTF_8.displayName());
try {
LOG.info("path:" + defaultPath);
multipartResolver.setUploadTempDir(new FileSystemResource(defaultPath));
} catch (IOException e) {
LOG.error("init error", e);
}
return multipartResolver;
}
}
멀티파트 요청
은 인터넷상에서 서버에 여러 개의 파일을 요청하는 것
-> 웹 페이지 로딩 속도를 높이기 위해 주로 사용
CommonsMultipartResolver 클래스는 HTTP POST 요청으로 전송된 여러 일반 파일과 단일 이미지/비디오 파일을 처리하는데 사용
-> 이를 통해 쉽게 파일을 업로드 할 수 있고, 다양한 설정을 통해 업로드된 파일의 크기와 형식을 제한할 수 있다
multipartResolver.setResolveLazily
-> 멀티파트 요청을 처리할때, 각 부분을 요청할때(필요할때)까지 처리를 미루는 기능을 설정하는 메서드
@Slf4j
@Configuration
@Setter
@Getter
public class PhotoAppProperties {
@Value("${photoapp.file.defaultPath}")
private String defaultPath;
@PostConstruct
private void init() {
LOG.info("path:: {}",this.defaultPath);
}
}
@Value 어노테이션으로 인해 필드가 실행될때 스프링이 photoapp.file.defaultPath속성에서 값을 가져오 주입
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.deepLinking(true)
.displayOperationId(false)
.defaultModelExpandDepth(3)
.defaultModelExpandDepth(3)
.docExpansion(DocExpansion.LIST)
.displayRequestDuration(false)
.supportedSubmitMethods(UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS)
.validatorUrl(null)
.build();
}
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("photo api")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("photo upload api")
.description("사진 업로드 api")
.version("1.0.0")
.build();
}
}
Image File
@Getter
@Builder
public class ImageFile {
private String fileId;
private Long fileSize;
private String fileName;
private String fileType;
private String filePath;
}
ImageFile Upload Result
@Builder
@Getter
public class ImageFileUploadResult {
private String fileId;
private String fileName;
private Long fileSize;
}
File writer
@Slf4j
@Service
public class FileWriter {
@Autowired
PhotoAppProperties photoAppProperties;
public long writeFile(MultipartFile multipartFile, String filePath) {
try {
multipartFile.transferTo(new File(filePath));
} catch (IllegalStateException ile) {
throw new RuntimeException("file write error");
} catch (IOException ioe) {
throw new RuntimeException("ioe error");
}
return multipartFile.getSize();
}
public String getFilePath(String fileId, MultipartFile sourceFile) {
return photoAppProperties.getDefaultPath() +"/" + dateStr() + "/" + fileId + "." + getMimeType(sourceFile.getOriginalFilename());
}
private static String getMimeType(String filePath) {
return FilenameUtils.getExtension(filePath);
}
public static String dateStr() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
return now.format(dateTimeFormatter);
}
}
transferTo를 통해 파일을 지정된 경로에 write 하려 함
getMimeType은 확장자 정보 얻기
Controller
@RestController
public class FileUploadController {
@Autowired
FileUploadService fileUploadService;
@ApiOperation("이미지 파일 업로드")
@PostMapping("/v1.0/upload/image")
public ImageFileUploadResult transfer(
@RequestParam("userKey") String userKey,
@RequestPart("imageFile") MultipartFile multipartFile) {
ImageFile imageFile = fileUploadService.upload(multipartFile);
return ImageFileUploadResult.builder()
.fileName(imageFile.getFileName())
.fileId(imageFile.getFileId())
.fileSize(imageFile.getFileSize())
.build();
}
}
@RequestPart
-> file 객체와 VO(DB 테이블에 필드와 대응되는, 데이터를 저장하기 위한 객체) 객체를 한번에 받을 때 사용
하나의 api에서 MediaType을 지정하여 Json과 MultipartFile을 한번에 전달 받을 수 있다
Service
@Slf4j
@Service
public class FileUploadService {
@Autowired
private FileWriter fileWriter;
public ImageFile upload(MultipartFile sourceFile) {
String fileId = UUID.randomUUID().toString();
String filePath = fileWriter.getFilePath(fileId, sourceFile);
LOG.info("filePath:: {}", filePath );
fileWriter.writeFile(sourceFile, filePath);
return ImageFile.builder()
.fileName(sourceFile.getName())
.filePath(filePath)
.fileId(fileId)
.fileSize(sourceFile.getSize())
.build();
}
}