Keri sisuni

Kolmekihiline arhitektuur

Kuigi tarkvara arhitektuur ja arhitektuurimudelid ei ole otseselt selle aine teema, peame siiski põgusa peatuse tegema ning tutvuma arhitektuurimudeliga, mida me grupitöödes kasutame. Arhitektuur on tarkvara kirjutamisel oluline teema, sest selles sõltub suuremas osas kogu edasise koodi ülesehitus ja keerukus.

Kui süsteemile on korra arhitektuurimudel valitud, siis enamasti seda välja ei vahetata, sest mida enam lisandub koodi, seda mahukam oleks liikumine mõnele teisele mudelile.

Mis on kolmekihiline arhitektuur

Kõige klassikalisem arhitektuurimudel on kolmekihiline arhitektuur.

Kolmekihiline arhitektuur

Kolmekihiline arhitektuur koosneb järgmistest osadest:

  • Esitluskiht (Presentation Layer (PL)) - programmi kasutusliides - näiteks vaade mobiili- või veebirakenduses, vorm või aken töölauarakenduses jne (kõik, mida kasutaja saab ise vaadata ja torkida).
  • Äriloogikakiht (Business Logic Layer (PL)) - selles kihis asuvad klassid ja töövood, mis kannavad süsteemi tööloogikat, sõltuvalt arhitektuurimudelist on tegemist kas teenuste klassidega või töökäskudega. Selles aines kasutame teenuste klasse.
  • Andmekiht (Data Access Layer (DAL)) - andmekihis asuvad klassid, mis oskavad suhelda andmehoidlaga (andmete pärimine, lisamine, salvestamine, kustutamine). Andmehoidlaks on tavaliselt kas mõni SQL või NoSQL andmebaas, kuid võib olla ka fail, kus rakendus andmeid hoiab.

Eeltoodud joonisel mängivad rolli ka noolte suunad - iga kiht oskab suhelda ainult endale vahetult järgneva kihiga. Teistpidiseid seoseid see arhitektuurimudel ei luba, sest vastasel korral moodustuks muidu hästi eraldatud kihtidest raskesti hallatav monoliit.

Eelised

Kolmekihilisel arhitektuuril on mitmeid eeliseid:

  • Paindlikkus - kuna rakenduse erinevate kihtide vahel toimub ainult minimaalne vajalik suhtlus, saame me erinevates kihtides teha mitmeid lähtekoodi muudatusi selliselt, et see muid kihte ei mõjuta.
  • Lihtsamini hallatav kood - kuna kihid teineteist igas suvalises suunas ei tunne, siis tegeleb igas kihis olev lähtekood ainult antud kihi küsimustega; ära jäävad liigsed seosed üle kihtide, mis muudavad aja jooksul lähtekoodi väga raskesti kasutatavaks,.
  • Kihtide asendatavus - kui soovime mõne kihi välja vahetada uue vastu, siis puudutab antud muudatus just seda konkreetset kihti ja mitte kogu rakendust.

Kolmekihiline arhitektuur ASP.NET Core rakendustes

Ka ASP.NET Core rakendusi võime ehitada kolmekihilist arhitektuuri kasutades. Kihid oleksid realiseeritud järgmiselt:

  • Esitluskiht - ASP.NET Core kontrollerid, mudelid ja vaated.
  • Äriloogikakiht - teenuste klassid, kus on meetodid erinevateks süsteemi poolt pakutavateks toiminguteks.
  • Andmekiht - repository klassid (nendega tegeleme edaspidi, praegu mõelge selliselt, et nende klasside abil saame suhelda andmebaasiga).

Projekti struktuur

Päris rakendustes hoitakse tavaliselt teenuste ja repositoryde klassid eraldi Visual Studio projektides, et neid saaks maksimaalselt korduvkasutada, Näiteks võib meil samu klasse vaja minna taustateenuste ehitamisel ja taustateenustele terve veebirakenduse külge võtmine oleks mõeldamatu. Käesolevas aines püsime me rangelt veebirakenduse piirides ja seega me mitut projekti ei loo.

Teenuste klassid

Teenuste klassid on koht, kus asub meie rakenduste äriloogika. Siin on defineeritud kõik toimingud, mida süsteem kasutajatele pakub.

Vaatleme näitena lihtsat arvete teenuse klassi InvoiceService, mis kasutab interface-i IInvoiceService. Teenuste klassidele interface-ide defineerimine on tavaline praktika, sest see muudab meie süsteemi paindlikumaks ning selliselt on näha klassidest ainult see osa, mille interface defineerib.

public interface IInvoiceService
{
    Task IList<Invoice> List();
    Task Invoice Get(int id);

    // Ülejäänud meetodid
}

public class InvoiceService : IInvoiceService
{
    private readonly ApplicationDbContext _dataContext;

    public InvoiceService(ApplicationDbContext dataContext)
    {
        _dataContext = dataContext;
    }

    public async Task<IList<Invoice>> List()
    {
        return await _dataContext.Invoices.Include(invoice => invoice.Customer)
                                          .OrderByDescending(invoice => invoice.Date)
                                          .ToListAsync();
    }

    public async Task<Invoice> GetById(int id)
    {
        return await _dataContext.Invoices.Include(invoice => invoice.Customer)
                                          .Include(invoice => invoice.Lines)
                                          .Where(invoice => invoice.Id == id)
                                          .FirstOrDefaultAsync();
    }

    // Ülejäänud meetodid
}

Hoiatus

Ülaltoodud teenuse klass on näitlik ning aine raames me viime hiljem andmete päringud teenuse klassist välja.

Eeltoodud klassi saame kasutada ASP.NET Core kontrollerites.

public class InvoicesController : Controller
{
    private readonly IInvoiceService _invoiceService;

    public InvoicesController(IInvoiceService invoiceService)
    {
        _invoiceService = invoiceService;
    }

    public async Task<IActionResult> Index()
    {
        var invoices = await _invoiceService.List();

        return View(invoices);
    }

    // Ülejäänud kontrolleri aktsioonid
}

Siit paistab kohe silma ka kolmekihilise arhitektuuri üks eelistest. Kui mõtleme tagasi sellele, millised olid Visual Studio poolt genereeritud kontrollerid, siis kõik andmetega seotud tegevused toimusid kontolleri aktsioonides. See viiks meid edasi uute probleemideni:

  1. Kui me lisaksime rakendusele Web API, siis oleks seal vaja praktiliselt samasi aktsioone, kuid seal me tagastame andmeid, mitte vaateid. Me peaksime võtma InvoicesControllerist meetode ning kopeerima neid üle Wev API kontrollerisse. See tähendaks dubleeritud koodi, mida me püüame vältida.
  2. Kontrollerid, mis tegelevad ka muude ülesannetega peale kasutaja ja süsteemi vahelise suhtluse koordineerimise, ei vasta MVC disainimustrile. Kontrolleril oli ainult koordineeriv ülesanne. See tähendab, et kontroller ei oska andmeid andmebaasist pärida, kontroller ei oska andmeid salvestada ega äriprotsesse läbi viia.

Teenuse klassi kasutav InvoicesController on nendest pattudest puhas. See võtab kasutaja käest pöördumisi ja sisendit vastu, käivitab vastavaid toiminguid vastu teenust ja tagastab andmed brauserile.

Paks ja õhuke kontroller

Kontrollereid, mis tegelevad andmete salvestamisega või kannavad endas rakenduse äriloogikat, nimetatakse paksudeks kontrolleriteks. Pakse kontrollereid püütakse vältida, sest need lähevad vastuollu MVC disainimustri ja kolmekihilise arhitektuuri ideedega.