-
홈페이지 FileSystem 구조 개선 (DI)HelloJkw 개발 2024. 10. 2. 02:03
내 홈페이지는 총 4가지 파일 시스템을 사용할 수 있다.
테스트를 위한 Local file system, InMemory file system
서비스를 위한 Azure file system, Dropbox file system.
설정 파일 appsettings를 읽어서 어떤 파일 시스템을 쓸지 할지 결정한다.
각 서비스마다 각자 다른 파일시스템을 설정할 수 있다.
설정은 이렇게 생겼다.
이번 개선에서 설정 파일을 변경하는 것은 아니다.
{ // FileSystem은 한 번만 설정하는 값이다. // 연결 정보만 저장한다. FileSystem: { Dropbox: { ClientId: "", ClientSecret: "" }, Azure: { ConnectionString: "" } } DiaryService: { // Path는 각 서비스마다 정의한다. // 각 타입마다 각각 지정한다. (경로가 너무 달라서 이렇게 하기로 정했다.) Path: { Dropbox: { DiaryListPath: "/hellojkw/db/diary/diary-info" }, Azure: { DiaryListPath: "diary::/diary-info", } } } }
문제점1: 종속성 문제
위에도 써 있지만, 설정을 보고 사용할 파일 시스템을 고르는 if문이 코드에 그대로 있었다.
이 부분이 홈페이지 서비스와 파일 시스템의 종속성을 높이는 부분이라고 생각한다.
// FileSystemService 내부 Dictionary<FileSystemType, IFileSystem> CreateFileSystems() { var fsDic = new Dictionary<FileSystemType, IFileSystem>(); if (options.FileSystemType == "Dropbox") { fsDic.Add(FileSystemType.Dropbox, new DropboxFileSystem()); } else if (options.FileSystemType == "Azure") { fsDic.Add(FileSystemType.Azure, new AzureFileSystem()); } // add local, in-memory file system return fsDic }
파일 시스템 타입을 dropbox, azure를 비교하는 코드가 FileSystemService안에 없었으면 좋겠다.
FileSystemService가 FileSystemType이 4가지 종류라는 것을 몰랐으면 좋겠다.
FileSystemService의 역할은 필요한 타입의 FileSystem을 제공하는 것일 뿐,
FileSystem이 몇 종류나 있는지 알 필요는 없다고 생각한다.
해결
각 FileSystem은 각자 자신을 등록한다.
FileSystemService는 등록되어 있는 FileSystem목록을 받아올 뿐이다.
FileSystem을 요청하면 타입에 맞는 것을 찾아 반환한다.
만약 AWS S3를 사용하게 된다면 FileSystemType.S3와 program.cs에 AddS3FileSystem함수를 추가하면 된다.
// Program.cs builder.Services.AddInMemoryFileSystem(); builder.Services.AddLocalFileSystem(); if (fsOption.Dropbox != null) { builder.Services.AddDropboxFileSystem(fsOption.Dropbox); } if (fsOption.Azure != null) { builder.Services.AddAzureFileSystem(fsOption.Azure); } // FileSystemService // FileSystemType을 인자로 받고 있지만, 그 종류가 무엇인지는 모른다. public interface IFileSystemService { IFileSystem GetFileSystem(FileSystemSelectOption fileSystemSelectOption, PathMap pathOption); } public class FileSystemService : IFileSystemService { private readonly IEnumerable<IFileSystemBuilder> _fileSystems; public FileSystemService(IEnumerable<IFileSystemBuilder> fileSystems) { _fileSystems = fileSystems; } public IFileSystem GetFileSystem(FileSystemSelectOption fileSystemSelectOption, PathMap pathMap) { return GetFileSystemBuilder(fileSystemSelectOption.FileSystemType) .Build(pathMap); } private IFileSystemBuilder GetFileSystemBuilder(FileSystemType fsType) { var fileSystem = _fileSystems.FirstOrDefault(fs => fs.FileSystemType == fsType); if (fileSystem == null) { throw new ArgumentException(); } return fileSystem; } }
문제점2: 각 서비스에서 FileSystem이 아닌 FileSystemService를 DI 하는 문제
아래 예제를 보면 DiaryService에서 IFileSystem을 받아오지 않고, FileSystemService를 받아오고 있다.
의미상 DiaryService는 본인이 사용하는 FileSystem에만 관심이 있어야 한다.
FileSystemService를 굳이 알 필요가 없다.
(테스트 코드도 길어진다)
class DiaryService { readonly IFileSystem _fileSystem; public DiaryService( DiaryOption option, FileSystemService fsService ) { // IFileSystem을 바로 받아와도 되는데, // FileSystemService를 받아와서 fileSystem을 생성하고 있다. _fileSystem = fsService.GetFileSystem(option.FileSystemSelect, option.Paths); } }
해결
서비스 관련 DI를 등록할 때 IFileSystem을 함께 등록하면 된다.
Singleton객채에 구분자를 지정할 수 있다. AddKeyedSingleton 함수를 사용한다.
services.AddKeyedSingleton<IFileSystem>(nameof(DiaryService), (provider, key) => { var option = provider.GetRequiredService<DiaryOption>(); var fileSystemService = provider.GetRequiredService<IFileSystemService>(); var fileSystem = fileSystemService.GetFileSystem(option.FileSystemSelect, option.Path); return fileSystem; }); services.AddKeyedSingleton<IFileSystem>(nameof(DiarySearchService), (provider, key) => { var option = provider.GetRequiredService<DiaryOption>(); var fileSystemService = provider.GetRequiredService<IFileSystemService>(); var fileSystem = fileSystemService.GetFileSystem(option.SearchEngineFileSystem, option.Path); return fileSystem; });
public class DiaryService : IDiaryService { private readonly IFileSystem _fs; public DiaryService( [FromKeyedServices(nameof(DiaryService))] IFileSystem fileSystem) { _fs = fileSystem; } } public class DiarySearchService : IDiarySearchService { private readonly IFileSystem _fs; public DiarySearchService( [FromKeyedServices(nameof(DiarySearchService))] IFileSystem fileSystem ) { _fs = fileSystem; } }
끝!
사실 처음 만들 때 부터 구조가 좋지 않다고 생각했다.
동작하는데 별 문제가 몇 년 동안 그대로 두다가 이제야 변경하였다...
'HelloJkw 개발' 카테고리의 다른 글
[양팔저울] 게임 설명서 (기본) (0) 2023.11.13 일기장 검색 관련 변화 (0) 2023.06.25 Blazor에서 typescript 디버깅 (0) 2022.03.06 Blazor에서 카카오 지도 API 적용하기 (0) 2022.03.05 Blazor에서 typescript 사용하기 (0) 2022.03.04