Keri sisuni

IFileClient liides

IFileClient pole küll disainimuster, kuid abiks on see rakenduste juures, mis võivad kasutada failide hoidmiseks nii kohalikku failisüsteemi kui ka erinevaid failihoidlaid pilves (Azure, Amazon, Google jne). IFileClient abil saame üldistada juurdepääsu erinevatele failihoidlatele ning viia rakenduse funktsionaalsest koodist välja failisüsteemiga suhtlemise loogika. Sel teel võime me rakenduse kasutatavat failihoidlat lihtsati välja vahetada ilma, et peaksime rakenduse enda koodis muudatusi tegema.

Kohaliku failid pilveteenustes

Kuigi pilves majutatud veebirakendused võivad kasutajate poolt üleslaetud faile hoida sama masina kataloogides, pole see soovitatav. Sõltuvalt teenusest ei pruugi need failid olla jäävad. Kui veebirakendust jooksutav konteiner või teenuse instants kokku jookseb, siis võib pilveteenus selle asendada automaatselt uuega. Et kasutajate poolt üleslaetud failid ei ole osa veebirakendusest, siis on need failid jäädavalt kadunud.

Programmi koodis näeb IFileClient välja selline:

public interface IFileClient
{
    Task<Stream> Get(string container, string fileName);
    Task Save(string container, string fileName, Stream fileStream);
    Task Delete(string container, string fileName);
    Task<bool> Exists(string container, string fileName);
    Task<IList<string>> List(string container, string prefix);
}

Failikonteinerid

IFileClient definitsiooni vaadates tekib kohe küsimus - mis asi on kõikides meetodites esinev container? Kõik failihoidlad ei toeta hierarhilisi kaustapuid. Need on lubatud kohalikus failisüsteemis, kus saame ühe kausta alla luua teise ja selle alla kolmanda, kuid see ei pruugi võimalik olla kõikides failihoidla teenustes.

Mida failihoidlad pea alati pakuvad, on konteinerid, milles failid asuvad. Siin kasutame lähenemist, et meil on olemas ainult esimese taseme kaust ehk konteiner. Iga konteiner sisaldab omakorda faile.

Konteinerite nimed peame andma stringina, mis tähendab ohtu, et arendajad hakkavad kirjutama konteinerite nimesid otse meetode kutsuvasse koodi.

// ...

var contract = await _fileClient.Get("contracts","1383-jaan-kask.pdf");
var photo = await _fileClient.Get("profile-photos","jaan-kask.jpg");

// ...

Selliselt on lihtne teha näpuvigu, mida kiiruga ei märka. IFileClient implementatsioone see ei pruugi häirida kui need oskavad puuduvaid konteinereid ise luua.

Näpuvigade vältimiseks loome staatilise klassi, kus on konteinerite nimed.

public static class FileContainerNames
{
    public const string Contracts = "contracts";
    public const string ProfilePhotos = "profile-photos";
}

Nüüd on meil failikonteinerite nimed ühes kohas koos ja mujal koodis kasutame FileContainerNames klassi.

// ...

var contract = await _fileClient.Get(FileContainerNames.Contracts,"1383-jaan-kask.pdf");
var photo = await _fileClient.Get(FileContainerNames.ProfilePhotos,"jaan-kask.jpg");

// ...

See kood on küll pikem kui eelmine versioon, kuid see-eest pole konteinerite nimed enam otse koodi kirjutatud ja me ei pea enam muretsema näpuvigade pärast konteinerite nimedes.

Lokaalse failisüsteemi klient

Esimese IFileClient implementatsioonina vaatame LocalFileClient klassi, mis suhtleb kohaliku failisüsteemiga. Kood on lihtne, sest peamiselt kasutab see System.IO nimeruumist klasse File, Directory ja Path.

public class LocalFileClient : IFileClient
{
    private const string RootPath = "wwwroot\\uploads";

    public void Delete(string container, string fileName)
    {
        var path = GetPath(container, fileName);

        if(!File.Exists(path))
        {
            return;
        }

        File.Delete(path);
    }

    public bool Exists(string container, string fileName)
    {
        var path = GetPath(container, fileName);

        return File.Exists(path);
    }

    public Stream Get(string container, string fileName)
    {         
        if (!Exists(container, fileName))
        {
            return null;
        }
        var path = GetPath(container, fileName);

        return File.OpenRead(path);
    }

    public IList<string> List(string container, string prefix)
    {
        var path = Path.Combine(RootPath, container);

        return Directory.EnumerateFiles(path).ToList();
    }

    public void Save(string container, string fileName, Stream inputStream)
    {
        Delete(container, fileName);

        var path = GetPath(container, fileName);
        using(var outputStream = new FileStream(path, FileMode.CreateNew))
        {
            inputStream.CopyTo(outputStream);
        }
    }

    private string GetPath(string container, string fileName)
    {
        return Path.Combine(RootPath, container, fileName);
    }
}

See klient kasutab System.IO nimeruumi klasse File, Directory ja Path. Antud klient on mõeldud küll kasutamiseks ASP.NET Core veebirakendustes, kuid seda võib kasutada ka muud tüüpi rakendustega. Ära tuleb muuta vaid juurkataloogi asukoht klassi alguses.