Adapter, Strategy ve Factory Pattern ile Çoklu Dosya Yüklemek

Modern yazılımlarda dosya yüklemek genellikle Amazon S3 veya Azure Blob gibi servislerle yapılır. Ancak birçok kurum hala FTP gibi eski altyapıları kullanmaktadır. Bu yazıda, eski ve yeni dosya yükleme sistemlerini nasıl tek bir noktadan yönetebileceğimizi, Adapter, Strategy ve Factory Pattern’lerini kullanarak anlatmaya çalışacağım.

Bu senaryoda kullandığım tasarım desenleri ve görevleri şu şekilde:

  • Adapter Pattern (Eski sistemi yeni sisteme göre uyarlar)
  • Strategy Pattern (Upload stratejilerini ayrıştırır, tek sorumluluk ve open-close prensibini yerine getirir)
  • Factory Pattern (upload servislerini seçime göre oluşturup geri verir)

İlk olarak bir interface oluşturuyoruz IFileUpload. Bu interface tüm file upload işleminin en ortak metodunu barındırır ki tüm upload sınıflarında aynı metod kullanılabilsin.

namespace AdapterPatternFtpUpload.Abstracts;

public interface IFileUpload
{
    void UploadFile(string path, byte[] file);
}

Daha sonra elimizde zaten varsayıyorum ki modern yapıya uygun sınıflarımız var. Bu sınıflar Azure Blob ve Amazon’a dosya upload ediyor

using AdapterPatternFtpUpload.Abstracts;

namespace AdapterPatternFtpUpload;

public class AzureBlobUploader : IFileUpload
{
    public void UploadFile(string path, byte[] file)
    {
        Console.WriteLine($"Uploading file {path} to Azure blob.");
    }
}
using AdapterPatternFtpUpload.Abstracts;

namespace AdapterPatternFtpUpload;

public class S3Uploader : IFileUpload
{
    public void UploadFile(string path, byte[] file)
    {
        Console.WriteLine($"S3'e {path} yüklendi.");
    }
}

Bir de elimizde şöyle eski bir sınıf var. Bu sınıf ftp’ye dosya yüklüyor ama yapısal olarak çağın gerisinde kalmış.

namespace AdapterPatternFtpUpload;

public class FtpFileService
{
    public void UploadFile(string path, byte[] file)
    {
        Console.WriteLine($"Uploading file {path} to ftp");
    }
}

Şimdi bu eski yapıyı yeni yapımıza uygun hale getirmek daha da doğrusu yeni yapımızda yapımızı bozmadan bu sınıfı kullanabilmek için Adapter Pattern uyguluyoruz. FtpFileUploaderAdapter sınıfı, eski FTP sınıfını sarmalayarak IFileUpload arayüzüne uygun hale getirir. Böylece eski sistemin yapısını bozmadan modern uygulamaya entegre ederiz.

using AdapterPatternFtpUpload.Abstracts;

namespace AdapterPatternFtpUpload;

public class FtpUploaderAdapter(FtpFileService ftpService) : IFileUpload
{
    public void UploadFile(string path, byte[] file)
    {
        string filePath = $"files/{path}";
        ftpService.UploadFile(filePath, file);
    }
}

Tabi bize olmazsa olmaz bir enum lazım. Factory Pattern bize soracak, hangi provider’ı kullanmak istiyorsun? Söyle ki ona göre bir sınıf vereyim sana.

namespace AdapterPatternFtpUpload;

public enum UploadProviderEnum
{
    S3,
    AzureBlob,
    Ftp
}

Tamam, şimdi Factory Pattern uygulayabiliriz. Bu pattern sayesinde ihtiyacımız olan sınıfa erişmiz olacağız. Buradaki püf noktalarından birisi erişmek istediğimiz tüm sınıfların IFileUpload dan türemiş olması. Yapısal eşitlik bu sebepten dolayı oluşuyor.

using AdapterPatternFtpUpload.Abstracts;

namespace AdapterPatternFtpUpload;

public static class UploaderFactory
{
    public static IFileUpload CreateUploader(UploadProviderEnum provider)
    {
        return provider switch
        {
            UploadProviderEnum.AzureBlob => new AzureBlobUploader(),
            UploadProviderEnum.Ftp => new FtpUploaderAdapter(new FtpFileService()),
            UploadProviderEnum.S3 => new S3Uploader(),
            _ => throw new NotSupportedException("Unknown upload provider"),
        };
    }
}

Son olarak bir tane service yazalım ki istemci sadece tek bir katmanla bu işi görebilsin. İstemci arkada neler oluyor, ne nasıl oluşuyoru bilmek zorunda değil. Sadece ne istediğini belirtebileceği basit bir sınıfa istek atabilsin.

namespace AdapterPatternFtpUpload;

public class FileUploadService
{
    public void UploadFile(string path, byte[] file, UploadProviderEnum provider)
    {
        var uploader = UploaderFactory.CreateUploader(provider);
        uploader.UploadFile(path, file);
    }
}

İstemci buraya üç parametre, yani dosyanın adresi, dosyanın kendisi ve hangi provider kullanılacak bilgisini versin, detaylarla ilgilenmesine gerek kalmaz. Service bu parametreleri Factory Pattern ile oluşturduğumuz UploadFactory’ye provider bilgisini verip geri dönen sınıfın metodu ile yükleme işlemini tamamlıyor.

Son adım aşağıdaki şekilde istemci isteğini gönderiyor:

using AdapterPatternFtpUpload;

var uploadService = new FileUploadService();
uploadService.UploadFile("report.pdf", 
    [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09], UploadProviderEnum.S3);
    
uploadService.UploadFile("report.pdf", 
    [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09],
    UploadProviderEnum.AzureBlob);
    
uploadService.UploadFile("report.pdf", [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09], 
    UploadProviderEnum.Ftp);

Çalıştırdığımızda çıktımız şu şekilde olacak

S3'e report.pdf yüklendi.
Uploading file report.pdf to Azure blob.
Uploading file files/report.pdf to ftp

Faydalar:

  • Factory Pattern ile istemcinin detaylarla ilgilenmeden uygun yükleyiciye ulaşmasını sağladık.
  • Yeni bir servis (örneğin Google Cloud) eklemek sadece Factory’e yeni bir satır eklemek kadar kolay hale geldi.
  • Strategy Pattern ile her yükleme sınıfını ayrı sorumlulukla yönettik, test edilebilir ve genişletilebilir hale getirdik.
  • Adapter Pattern sayesinde eski FTP sistemini değiştirmeden sisteme entegre ettik.


Yorumlar

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir