Notice
Recent Posts
Recent Comments
Link
«   2025/09   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

코딩 이래요래

Sprint Mission Part 3 File*Repository Refactoring 본문

Refactoring

Sprint Mission Part 3 File*Repository Refactoring

강범호 2025. 4. 30. 17:19

File I/O를 이용한 Repository의 저장 및 로드 메소드 리팩토링

☑️ 기존 FileRepository 저장 및 로드 메소드

@Repository
@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file")
public class FileChannelRepository implements ChannelRepository {

    private final Map<UUID, Channel> channels;

    public Map<UUID, Channel> loadFromFile() {
        File file = new File(filePath);
        if (!file.exists()) return new HashMap<>();

        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
            return (Map<UUID, Channel>) ois.readObject();
        } catch (IOException e) {
            throw new RuntimeException("파일 읽기 실패: " + filePath, e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("데이터 형식 오류: " + filePath, e);
        }
    }

    public void saveToFile(Map<UUID, Channel> data) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
            oos.writeObject(data);
        } catch (IOException e) {
            throw new RuntimeException("파일 저장 실패 : ", e);
        }
    }
}
  • 각 FileRepository마다 loadFromFile(), saveToFile()의 메소드가 중복되어 선언되어 있음
  • 만약, 파일 저장 방식이 바뀌거나 예외 처리 로직이 변경되면 모든 Repository를 하나하나 수정해야 함
  • 유지보수가 어렵고, 코드 중복

✅ AbstractFileRepository 추상 클래스로 리팩토링

public abstract class AbstractFileRepository<K, V> {

    private final String filePath;

    protected AbstractFileRepository(@Value("${discodeit.repository.file-directory}")String basePath, String path) {
        this.filePath = basePath + path;
    }

    public Map<K, V> loadFromFile() {
        File file = new File(filePath);
        if (!file.exists()) return new HashMap<>();

        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
            return (Map<K, V>) ois.readObject();
        } catch (IOException e) {
            throw new RuntimeException("파일 읽기 실패: " + filePath, e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("데이터 형식 오류: " + filePath, e);
        }
    }

    public void saveToFile(Map<K, V> data) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
            oos.writeObject(data);
        } catch (IOException e) {
            throw new RuntimeException("파일 저장 실패 : ", e);
        }
    }
}
  • 다양한 타입에 대응 가능한 제너릭 K, V 구조 설계
  • 생성자를 통해 저장 path를 파라미터로 전달 받아 filePath 초기화
  • loadFromFile(), saveToFile() 메소드 정의
  • 추후 save, delete, find 등 추상 메소드로 정의 후 클래스에서 확장 가능

👍 기존 FileRepository 수정

@Repository
@ConditionalOnProperty(name = "discodeit.repository.type", havingValue = "file")
public class FileChannelRepository extends AbstractFileRepository<UUID, Channel> implements ChannelRepository {

    public FileChannelRepository(@Value("${discodeit.repository.file-directory}") String filePath) {
        super(filePath, "/channel.ser");
    }
    
    @Override
    public Channel createChannel(ChannelCreateDto channelCreateDto) {
        try {
            Channel channel = new Channel(
                    channelCreateDto.getAdmin(),
                    channelCreateDto.getName(),
                    channelCreateDto.getDescription()
            );
            return save(channel.getId(), channel);
        } catch (RuntimeException e) {
            throw new RuntimeException("채널 생성 실패: " + e.getMessage(), e);
        }
    }
    
    @Override
    public Optional<Channel> getChannel(GetPublicChannelRequestDto getPublicChannelRequestDto) {
        Map<UUID, Channel> channels = loadFromFile();
        return channels.get(getPublicChannelRequestDto.getChannelId()) == null ? Optional.empty() : Optional.of(channels.get(getPublicChannelRequestDto.getChannelId()));
    }
}
  • 더이상 중복된 I/O 메소드를 작성할 필요 없이 상속받은 loadFromFile(), saveToFile() 메소드를 사용하면 됨

💡 리팩토링을 통해 느낀 점

  • 공통 기능은 최대한 추상화하여 중복을 제거하자
  • 제너릭을 통해 유연한 설계 방법
  • 미션을 진행하면서 작은 기능 하나를 추가했을 뿐인데도, 관련된 여러 부분을 함께 수정해야 하는 상황이 반복됐고, 이 과정에서 유연한 설계의 중요성과 유지보수의 부담을 체감했고, 초기 설계 단계의 중요성을 뼈저리게 느꼈음
  • 앞으로 미션, 프로젝트를 진행할 때에는 기능 구현만 급하게 생각할 것이 아닌, 지금보다 나중을 염두에 두고 설계할 수 있도록 많은 연습이 필요해 보임