실무.log

파일 스토리지 관리 서버 구축하기(feat. 파일럿 프로젝트)

_2J 2025. 1. 15. 00:00


안정적인 서비스 운영을 위해 시스템의 구조적인 부분을
“이렇게 리팩터링 해보는 건 어떤가요?” 하고 제안했던 내용인데,
프로젝트를 진행하면서 새로운 지식을 학습하고 경험하고 느낀 것들을 공유해볼게요.
 


나: "이 파일 이렇게하면 받아지는데요? 이렇게 바꾸면 어떨까요?"
팀장님: "음.. 다시 한 번 봐보고 너가 말했던 건 만들면서 공부해봐"

 

그렇게 시작된 셀프 파일럿 프로젝트 이야기

 

 

뭐가 문제인데?

접근 불가능한 페이지인데 이렇게해서 요렇게하면 페이지에 포함된 파일을 다운 받을 수 있어요..!

 
새로운 서비스 부분을 넘겨받고 둘러보던 중 특정 그룹의 회원들만 볼 수 있는 게시물이지만 포함된 파일엔 접근이 가능했다.

물론 페이지 정보와 파일의 이름에 대한 사전정보가 필요하긴 했지만, 이게 이렇게 다운로드 받을 수 있는게 맞나..? 생각이 들었다. (게임 빈집털이 당한 느낌... ) 
 
 
 

이렇게 바꾸면 어떨까요?

이런 저런 문제에 대한 의견과 함께 개선안을 제시했다.

잡았다 시스템의 허점

파일의 접근 권한도 게시물에 맞춰 따로 관리하면 어떤가요? 회원, 게시물도 권한 그룹이 묶여 있으니까 같이 관리하면 좋을 것 같은데!! 회원 별로 파일 자체에 대한 접근 권한을 비공개로 두고 서버에서 접근 권한을 어쩌구 저쩌구...

 

그치만, 일개 사원은 서비스 정책을 생각하지 않았다.

게시물 정책에 따라 이쪽에선 오픈 저쪽에선 비공개 서비스의 정책에 따라 구성되어 있는 것이었고, 결국 파일은 누구나 접근 가능한게 맞는거였다.

 

 

요렇게 만들어봐

혼자만의 해프닝으로 끝나나 했지만 특별히 바쁜 일이 없기도 했고, 기존 서비스 구조도 파악해보고 기술 공부도 할 겸 제시했던 개선안을 기반으로 파일럿 프로젝트가 시작되었다.

 

근데 하는 김에 이것도 해봐도 됌요 ?

현재 저희 서비스가 EC2 인스턴스를 IIS 이미지 서버로 활용하고 있잖아요. 근데 유지보수와 관련된 관리 이슈가 항상 고려 대상이기도 했고, EC2 2대(서버 + 상시 백업 서버)의 서버 비용을 내는 것보다 기존 배치로 백업해 놓던 S3를 메인 이미지 서버로 사용하면 어떨까…해서 그리고 이미 운영중인 서비스들이 AWS 클라우드 위에서 운영되고 있고, AWS의 다양한 제품들과 연동하기 편리함까지!! 챙길 수 있지 않겠습니까????? (S3 써서 하고싶음)

 

결국, S3를 파일 스토리지로 사용 하기로 했다. (대가리만 큼)
 

모두 바꾸기엔 일정이 너무 오래 걸려서 루즈해질 것도 같고 작은 부분부터 점차 개선해가는 방향으로 결정하고 업로드 부분을 먼저 개선하기로 했다.

 

 


그래서 어떻게 바꿀 건데?

이번 프로젝트를 통해 개선할 주요 목표를 요약하면.

 

  • 유저 입장에서 기존의 UI, 자료 업로드 및 조회 기능이 변경되지 않고 그대로 동작해야 한다.
  • 이미지 스토리지로 AWS S3를 활용할 것(with. Assume Role)
  • 기존에 업로드 되었던 자료가 손실 되어서는 안된다.
  • 스토리지 전환 이후에도 IIS, S3 파일이 동기화 되어야 한다.
  • 부가 기능은 자유롭게 추가 가능 (검토 먼저 받아라...)

모두 바꾸기엔 일정이 너무 오래 걸려서 루즈해질 것도 같고 작은 부분부터 점차 개선해가는 방향으로 결정하고 업로드 부분을 먼저 개선하기로 했다.

 

 

지금 어떻게 되어있는데?

Frontend : Classic ASP
Backend : Spring


현재 운영 중인 메인 서비스는 크게 보면 이런 기술 스택으로 구성되어 있고, ASP + DB Procedure 호출로만 처리하던 비즈니스 로직을 API로 추출하고 서버에서 처리할 수 있도록 거의 전환이 완료된 상태다.
 

AS-IS 간소화 구조

 

페이지에서 업로드 시 ASP에서 IIS 서버로 직접 업로드하는 구조로 구성되어 있다.

 

새로운 목표를 기반으로 TO-BE 업로드 구조를 구상해 본 결과 2가지 선택지로 좁혀졌다.
 

TO-BE 업로드 구조

1안 ) 클라이언트 사이드 업로드

클라이언트 단에서 API를 통해 Pre-signed URL을 발급받아 S3에 직접 업로드 하고, 업로드 된 경로만 db에 저장하는 방식. + IIS 파일 동기화는 S3 버킷 업로드 이벤트 트리거를 활용하여 AWS Lambda로 처리
 

TO-BE (2) 클라이언트 업로드

 

이 방식은 클라이언트에서 업로드를 진행하기 때문에 서버에 부담을 주지 않는 구조이다.

서버의 메모리를 사용하지 않기 때문에 대용량, 다중 파일 업로드에 유용하다는 장점이 있지만 기존에 진행하던 MIME Type 검증을 하기가 까다로워진다. 이로 인해 악의적인 이미지의 변조 등의 보안 문제가 발생할 수 도 있고, 클라이언트 - DB - 스토리지 간의 일관성 문제가 발생할 수 있는 단점도 있는 것 같다.

 


2안 ) 서버에서 이미지 업로드

 이미지를 업로드 후 경로 저장 까지 백엔드에서 처리하는 방식. 

TO-BE (2) 서버 업로드

 

클라이언트에서 업로드 하는 방식의 단점을 보완하여 파일 유효성 검증, 양쪽 스토리지의 일관성을 유지할 수 있는 장점이 있다. 클라이언트 업로드 방식과는 반대로 서버의 리소스를 사용하여 업로드를 진행하기 때문에 서버에 부담을 주고 성능에 영향을 끼칠 수 있는 단점이 있다.

 

 

목적이 뭔데?

파일럿 프로젝트의 요구사항은 기존의 기능 유지, IIS와 S3 파일 동기화였기 때문에 

최종적으로 2개의 구조가 나왔지만 선택하는 건 그렇게 오래 걸리지도 어렵지도 않았던 것 같다.

  • 이미지 변조 등 파일의 검증이 가능하다.
  • IIS, S3 파일의 동기화가 중요하다.

서버에 부담을 주는 단점은 기존 API 서버가 아닌 이미지 서버로 새로 분리하여 구성하면 단점을 조금 더 보완할 수 있을 것 같았고, 이 요구사항을 만족하는 구조로는 2번째 서버사이드 업로드가 적합하다고 생각했다.
 

 

 

그래서 그려본 아키텍쳐

 

간소화 해서 그리긴했지만 이 인프라 아키텍쳐 기반으로 파일 스토리지 서버를 구축해볼 예정이다.

( 인프라 구성에 도움을 주신분들 정말 감사드립니다 ㅠㅠ )

 

 

 

그림을 그려보자

 

 

이제 업로드 기능 부터 구현해야한다. 먼저 기능을 어떤 플로우로 동작시킬지 간단하게 그려보았다.

 

 

 

 

 

파일 업로드 부분을 먼저 진행하기로 했기 때문에 개발 시작 전 어떻게 개발할지 간단하게 그림부터 그리고 시작하기로 했다.

 

전체적인 흐름으로 보면

  1. 파일 업로드 요청(1 ~ N개)
  2. 업로드 파일의 유효성 검사
  3. S3, IIS 파일 업로드
  4. 업로드 결과와 데이터 DB 저장
  5. 결과 응답.

 

 

 


 

 

개발 시작

  • 환경 설정 파일 구성하기
  • 확장자 체크(with. 변조된 파일)

평소 개인 프로젝트 때 사용했던게 아니었던 2가지 부분들을 위주로 전체 프로젝트를 말씀드리도록 하겠슴미다요 :)

 

잠깐, 구현한 업로드 방식을 조금 말씀드리자면, 우아한 형제들에서 작성한 기술 블로그를 참고하여 

Spring Boot에서 S3에 파일을 업로드하는 세 가지 방법 중 2번째 방법으로 소개된 MultipartFile 업로드 방식을 채택했습니다.

가장 익숙한 방식이기도 하고 빠르게 개발할 수 있는 방법이라 이 업로드 구조를 선택했답니다.

 

가보자고~~

 

환경 설정 파일 구성하기

혼자 끄적끄적 프로젝트할 때에는 aws정보가 포함된 yml 파일 따로 만들어서 세팅하고 깃에 올라가면 안되니깐 ignore 추가를 했었는데 실무에서는 Auto-scaling 환경에서의 접근 권한을 관리하기 위해 보통 instanceProfile로 관리한다고 합니다.

 

그래서 AWS의 InstanceProfile을 사용하였고 AWS STS + Assume Role을 함께 사용하여 S3를 사용할 수 있도록 설정 구성을 진행하였습니다. 

 

AWS STS와 Assume Role을 함께 사용하면 다음과 같은 장점이 있대요.

  • 프로젝트에 키를 포함하지 않기 때문에 키 노출 없이 AWS 리소스에 대한 액세스 권한 부여 가능.
  • Assume Role은 수명이 제한되어 있어서, 더 이상 필요하지 않을 때 교체하거나 명시적으로 취소할 필요가 없음.
AWS STS(Security Token Service) 란?
- AWS에서 보안 토큰을 생성하는 서비스. AWS IAM 사용자나 AWS 외부 자격 증명을 통해 엑세스 권한을 부여할 수 있다.

Assume Role 이란?
- AWS IAM에서 지원하는 기능. IAM 사용자 또는 AWS 외부 자격 증명으로 다른 AWS 계정 또는 리소스에 엑세스 할 수 있게 된다.

Reference : 
API_AssumeRole - amazon.com
AWS STS와 Assume Role 이란 - kyung123a Tistory Blog
How to assume a role - amazon.com

 

 

어쨌든 로컬환경에서도 구동하려면 배포환경과 맞춰줘야 합니다. 

일단, AWS CLI 사용할 때와 동일하게 로컬에 credentials 파일을 설정해줘야 한답니다.

Reference
Setting ip the configuration and security authentication file for AWS CLI - amazon.com

~/.aws 하위에 넣어줘요

 

 

다음, Spring 환경설정은

 

 

프로퍼티 디렉토리는 S3관련, IIS 관련 요런식으루 구성을 했고 정보들은 yml 파일에 따로 분리해 놓았습니다.

application.yml 에는 credentials 관련 정보(access-key, secret-key)를 입력하지 않아도 됩니다.

cloud:
  aws:
    credentials:
      profileName: S3
      session:
      	... // 프로필 세션 관련 설정
    s3:
      imgBucket: ...
      fileBucket: ...
      session:
        role:
          arn: ${iam arn name}
          sessionName: ${s3 upload role session name}
    region:
      static: ap-northeast-2
    stack:
      auto: false
    credentials:
      instanceProfile: true

 

 

 

S3 접근 시 AssumeRole을 사용하여 세션토큰을 사용할 것이기 때문에 STS를 통해 토큰을 발급하고 갱신하면서 사용하겠습니다. 먼저 AWSCredentialsProvider를 프로젝트에 맞는 Provider로 구현하겠습니다.

 

public class STSAssumeRoleCredentialsProvider implements AWSCredentialsProvider {
    private final AWSSecurityTokenService stsClient;
    private final AwsS3Properties awsS3Properties;
    private AWSSessionCredentials sessionCredentials;
    private Date expiration;

    // 생성자
    public STSAssumeRoleCredentialsProvider(AwsS3Properties awsS3Properties) {
        this.stsClient = AWSSecurityTokenServiceClientBuilder.standard()
                .withCredentials(new ProfileCredentialsProvider(awsS3Properties.getProfileName()))
                .withRegion(awsS3Properties.getRegion())
                .build();
        this.awsS3Properties = awsS3Properties;
    }

    @Override
    public AWSCredentials getCredentials() {
        if (sessionCredentials == null || isExpired()) {
            resetCredentials(); // 필요 시 자격 증명 갱신
        }
        return sessionCredentials;
    }

    @Override
    public void refresh() {
        resetCredentials();
    }

    private boolean isExpired() {
        return expiration == null || new Date().after(expiration);
    }

    private void resetCredentials() {
        try {
            AssumeRoleResult assumeRoleResult = stsClient.assumeRole(new AssumeRoleRequest()
                    .withRoleArn(awsS3Properties.getRoleArn())
                    .withRoleSessionName(awsS3Properties.getRoleSessionName())
                    .withDurationSeconds(awsS3Properties.getDurationSeconds()));
            Credentials stsCredentials = assumeRoleResult.getCredentials();
            sessionCredentials = new BasicSessionCredentials(stsCredentials.getAccessKeyId(),
                    stsCredentials.getSecretAccessKey(), stsCredentials.getSessionToken());
            expiration = stsCredentials.getExpiration();
        } catch (Exception e) {
            // 예외 처리 로직 추가
            e.printStackTrace();
        }
    }
}

 

 

Provider 클래스 구현 코드는 Netflix의 SimianArmy의 STSAssumeRoleSessionCredentialsProvider 코드를 참고했어요!

https://github.com/Netflix/SimianArmy

 

SimianArmy/src/main/java/com/netflix/simianarmy/aws/STSAssumeRoleSessionCredentialsProvider.java at master · Netflix/SimianArmy

Tools for keeping your cloud operating in top form. Chaos Monkey is a resiliency tool that helps applications tolerate random instance failures. - Netflix/SimianArmy

github.com

 

 

이제 이걸 기반으로 S3 클라이언트를 빈으로 등록하겠습니다.

 

AwsS3Config.java

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@RequiredArgsConstructor
@Configuration
public class AwsS3Config {

    private final AwsS3Properties awsS3Properties;

    @Bean
    public AmazonS3 s3Client() {
        return AmazonS3ClientBuilder.standard()
                .withCredentials(new STSAssumeRoleCredentialsProvider(awsS3Properties))
                .withRegion(awsS3Properties.getRegion())
                .build();
    }
}

 

 

이제 세션 증명 토큰을 자동으로 갱신할 수 있는 S3Client를 사용할 준비가 되었어요.

(토큰을 발급 받은 후 유효 시간 동안은 발급받은 토큰으로 사용하고 토큰이 만료되었을 경우 토큰 갱신)

 

토큰을 발급 받고 S3와 연동할 수 있는 환경 설정은 끝이 났습니다.

 

 

파일 검증하기

 

업로드 전 먼저 검증 과정부터 소개해 드리겠습니다!

  1. 파일 용량 검증
  2. 업로드 요청 파일 갯수 검증 
  3. 파일 이름 검증 
  4. 파일 확장자 검증 

 

 

1. 파일의 용량 검증하기

 

먼저, 설정을 통해 request의 최대 크기를 제한했습니다.

각 파일은 최대 200MB까지 허용되지만, 총합은 200MB를 초과할 수 없게끔 설정하였습니다.

 

application.yml

spring:
  servlet:
    multipart:
      max-file-size: 200MB  # 최대 파일 크기 설정
      max-request-size: 200MB  # 최대 요청 크기 설정

 

파일 크기 초과: 사용자가 업로드하려는 파일의 크기가 max-file-size로 설정한 값보다 클 경우, Spring Boot는 org.springframework.web.multipart.MaxUploadSizeExceededException 예외를 발생시킵니다. 

요청 크기 초과: max-request-size를 설정한 경우, 요청의 전체 크기(파일과 다른 파라미터 포함)가 이 값보다 클 경우에도 업로드가 실패합니다. 이 경우에도 MaxUploadSizeExceededException이 발생합니다.

 

해당 설정을 요청할 수 있는 최대 크기를 설정하였고, @ControllerAdvice를 활용하여 예외처리를 진행했습니다.

@ControllerAdvice
public class ServiceExceptionHandler {

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    @ResponseStatus(HttpStatus.OK)
    public String handleMaxSizeException(MaxUploadSizeExceededException exc) {
        return ErrorResponse.fail(MAX_SIZE_LIMIT_REACHED);
    }
}

 

 

 

2. 파일의 유효성 검증 하기

업로드 파일 요청 개수는 컨트롤러단에서 검증하였고, 파일의 이름 및 확장자 체크는 클래스를 만들어 검증할 수 있도록 만들었답니다. 우선 필요한 정보들을 추출하고 프로퍼티로 설정해놓았습니다.

 

file-validation-rules.yml

allow:
  types:
    jpg: "image/jpeg"
    jpeg: "image/jpeg"
    png: "image/png"
    gif: "image/gif"
    pdf: "application/pdf"
    ...


invalid-characters:
  types:
    - "{"
    - "}"
    - "["
    - "]"
    - "\\"
    ...
public abstract class BaseProperties<T> {
    private T types;

    public T getTypes() {
        return types;
    }

    public void setTypes(T types) {
        this.types = types;
    }
}

@ConfigurationProperties(prefix = "allow")
public class AllowExtensionProperties extends BaseProperties<Map<String, String>> {}

@ConfigurationProperties(prefix = "invalid-characters")
public class NotAllowedCharactersProperties extends BaseProperties<List<String>> {}

 

 

 

 

프로퍼티 설정 후 파일 유효성 검증과 FileDto로 변환을 담당하는 클래스를 만들었습니다.

(파일 위변조 검증은 Apache Tika 라이브러리를 활용) 

public class FileDtoConverter {
    private final Tika tika;
    private final AllowExtensionProperties allowExtensionProperties;
    private final InvalidCharactersProperties invalidCharactersProperties;
    private final FilePathAndNameGenerator filePathAndNameGenerator;

    public FileDto convertValidFileDto(String fileCategory, MultipartFile file) {
        String originalFilename = validateFileName(file.getOriginalFilename());
        String extension = getFileExtension(originalFilename);
        validateFileExtension(extension);

        String mimeType = detectMimeType(file, extension);
        validateMimeType(mimeType, extension);

        return buildFileDto(fileCategory, originalFilename, extension, mimeType, file.getSize());
    }

    // 객체 생성 담당
    private FileDto buildFileDto(String fileCategory, String originalFilename, String extension, String mimeType, long fileSize) {
        return FileDto.builder()
                .fileName(filePathAndNameGenerator.generateFileName(fileCategory, originalFilename))
                .originalFileName(originalFilename)
                .fileExtension(extension)
                .fileMimeType(mimeType)
                .fileSize(fileSize)
                .filePath(filePathAndNameGenerator.generateFilePath(fileCategory, extension))
                .build();
    }

    // 파일 이름 추출 및 검증
    private String validateFileName(String filename) {
        if (!StringUtils.hasText(filename)) {
            throw new FileValidationException(INVALID_FILE_NAME);
        }

        if (invalidCharactersProperties.getTypes().stream().anyMatch(filename::contains)) {
            throw new FileValidationException(INVALID_FILE_NAME);
        }

        return filename;
    }

    // 파일 확장자 추출
    private String getFileExtension(String filename) {
        int lastIndexOfDot = filename.lastIndexOf('.');
        if (lastIndexOfDot == -1 || lastIndexOfDot == filename.length() - 1) {
            return ""; // 확장자가 없는 경우
        }
        return filename.substring(lastIndexOfDot + 1).toLowerCase();
    }

    // 파일 확장자 검증
    private void validateFileExtension(String extension) {
        if (!allowExtensionProperties.getTypes().containsKey(extension)) {
            throw new FileValidationException(NOT_SUPPORTED_FILE_EXTENSION);
        }
    }

    // MIME 타입 추출
    private String detectMimeType(MultipartFile file, String extension) {
        try (InputStream inputStream = file.getInputStream()) {
            return tika.detect(inputStream).toLowerCase(); // MIME 타입을 반환
        } catch (IOException e) {
            throw new FileValidationException(INVALID_FILE);
        }
    }

    // MIME 타입 검증
    private void validateMimeType(String mimeType, String extension) {
        String allowContentType = allowExtensionProperties.getTypes().get(extension);
        if (!allowContentType.equalsIgnoreCase(mimeType)) {
            throw new FileValidationException(INVALID_FILE_ABNORMAL_TYPE);
        }
    }
}

 

 

FileDtoConverter를 사용하는 입장에서는 convertValidDto() 함수를 통해 업로드 요청받은 파일을 검증하고 이후 로직에 필요한 서비스 DTO로의 변환을 할 수 있게 됩니다. 서비스 정책상 업로드 요청 카테고리에 맞는 파일 경로와 업로드될 파일명이 다르게 저장되어야 하기 때문에 FilePathAndNameGenerator 를 통해 생성할 수 있도록 분리해두었습니다.

 

 

 

 

파일 업로드 하기

 

업로드 부분은 기록하려고 했던 부분 이외의 내용이라 간단하게 소개만 해드리겠습니다~!

로드 파일은 그룹 단위로 묶이며 한 개의 요청에 담긴 업로드 파일들은 하나의 그룹 ID를 가질 수 있도록 했습니다.

 

 

파일 롤백 관련하여 배치를 통한 고아파일 삭제, 업로드 재시도, 즉시 삭제 보상 트랜잭션..  등 여러가지 고민해 보았지만 시간 관계상, 스케줄러를 통해 업로드 실패된 그룹 파일들을 삭제처리하는 방향으로 진행하였습니다. 그래서 업로드는 S3, IIS 동시에 진행하고 두 스토리지 중 하나라도 실패하면 업로드 실패로 남겨둘 계획입니다.

 

 

간단하게 그려본 클래스 다이어그램 대충 어떻게 흘러갈지 보이시나요..? 너무 대충그려서 설명을 덧붙이자면 크게 5단계로 진행될 예정입니다요.

  1. FileService의 upload 함수 실행
  2. 만들어 둔 FileDtoConverter를 통해 검증하고 FileDto로 변환
  3. StorageService의 upload 함수 호출
  4. StorageService는 StorageClient를 통해 S3, IIS에 각각 업로드 후 업로드 여부 반환
  5. DB 저장 후 결과 응답.

 

@Getter
@Builder
public class FileDto {
    private String fileId;
    private String fileName;
    private String originalFileName;
    private String fileExtension;
    private String fileMimeType;
    private String filePath;
    private long fileSize;
    private boolean uploaded;

    public void updateUploadStatus(boolean uploaded) {
        this.uploaded = uploaded;
    }
}

 

업로드 로직에 사용될 파일 DTO입니다.

  • 파일의 메타정보
  • 업로드 경로
  • 업로드 파일명
  • 각 스토리지에 업로드 상태
public class FileServiceImpl implements FileService {
    private final FileDao fileDao;
    private final FileDtoConverter fileDtoConverter;
    private final StorageService storageService;

    @Override
    public List<FileUploadResponse> upload(String fileCategory, List<MultipartFile> files) {
        List<FileDto> uploadedFiles = files.stream()
                .map(file -> {
                    // 파일 유효성 검사 및 파일 정보 추출
                    FileDto fileDto = fileDtoConverter.convertValidFileDto(fileCategory, file);
                    // 파일 업로드
                    return storageService.upload(fileDto, file);
                })
                .collect(Collectors.toList());

        // File groupID 생성
        String fileGroupId = UUID.randomUUID().toString();
        
        // DB 저장
        fileDao.save(fileGroupId, uploadedFiles);
        
        return FileUploadResponse.builder()
                .fileGroupId(fileGroupId)
                .uploadedFiles(uploadedFiles)
                .build();
    }
}

 

 

업로드 코드 부분입니다. 간단하죵?

 

 

 

환경설정부터 유효성 검증 부분까지 소개 끝!

 

주어진 3주동안 파일 업로드, 다운로드, 조회, 삭제까지 구현하고 추가부분도 개발해야지~~ 했지만 기본적인 요구사항 끝내기도 급급했던 것 같습니다.. 다시 코드를 볼 때마다 뭔가 허전하고 찝찝한 이 느낌.. 아무래도 테스트할 시간이 부족해 충분한 테스트를 못해본게 많은 아쉬움이 남는 것 같아요..  소개해 드린 코드 이외에 다른 기능들은 의미 없이 내용만 더 길어질 것 같아 과감히 생략했습니다. 더 개선할 시간이 생긴다면... 파일별 권한 관리, 객체별 스토리지 클래스 관리 도 고려하여 추가 내용 소개해보도록 할게요!

 

 

 

 

마치며

우선 그냥 지나갈 수 있는 말임에도 불구하고 파일럿 프로젝트를 진행할 수 있는 기회를 주신 팀장님 너무 감사드립니다ㅎㅎ 인프라 구성에 도움을 주신 분들 또한 감사의 말씀을 전하고 싶습니다:) 프로젝트를 진행하면서 생각하지 못했던 부분에 대해 많이 알려주시고, 설계와 구현 관점에서 더 고민하고 성장할 수 있는 정말 뜻 깊은 시간이었던 것 같습니다. 실장님이 해주신 리뷰중 기억에 남는 조언에 대한 느낀점만 적고 마치도록 하겠습니다

 

 

세상에 완벽한 프로그램은 없다.

 

"각 서비스에 맞는 기술스택, 정책에 맞춘 코드들이 있고 성능, 보안등을 고려한 코드는 각각 장단점이 있다. 그래서 완벽한 프로그램보다는 서비스에 맞는 정책을 잡고 이 정책 내에서는 오류 없이 동작하는 프로그램을 만든다. 실제로 새로운 프로젝트 개발이나 운영 업무를 할 때에도 모든 경우는 아니어도 최소한 정책에 맞춘 부분들은 오류 없이 동작할 수 있게끔 개발해야 한다."

 

실용주의 프로그래머 책에서 본 내용이었는데 실제로 개발자 선배님께 들으니 색다르게 다가왔다.

이 말을 듣고나니 또 생각나는 비둘기짤

 

 

효율적이지 않은 코드는 잘못된 코드일까요? 이 질문은 진짜 아무리 생각해도 잘 모르겠다. 사실 서비스에 맞춰서 정책에 맞는 코드를 작성하고 테스트해봐도, 그 결과가 만족스러운지 확신이 안 서는 경우가 많거덩요.

그래도 실무를 하면서 느낀 건, 예전처럼 효율성과 멋부림만 신경 쓰던 코드에서 이제는 효율성, 가독성, 유지보수성, 코드 컨벤션 같은 다양한 요소를 고려하면서 코드를 작성하게 됐다는 겁니다! 이건 분명히 제가 성장한 부분이라고 생각합니다.

"왜?"라는 질문을 항상 머릿속에 두고 고민하는 게 개발자로서 성장하는 데 많은 도움이 된 것 같아요. 이런 생각을 하면서, 더 나은 코드를 위해 고민하는 개발자가 되겠다고 다시 다짐하게 됩니다. 앞으로도 화이팅!