Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pw:laboratoare:05 [2023/02/24 14:34]
ciprian.dobre [Suportul de laborator]
pw:laboratoare:05 [2023/04/24 22:36] (current)
ciprian.dobre [React Query]
Line 146: Line 146:
 === Uilizare React Query === === Uilizare React Query ===
  
-  - Pentru a începe să utilizați React Query, trebuie să îl instalați mai întâi. Puteți face acest lucru utilizând comanda de instalare npm: <​code>​npm install react-query</​code>​+  - Pentru a începe să utilizați React Query, trebuie să îl instalați mai întâi. Puteți face acest lucru utilizând comanda de instalare npm: <​code>​npm install ​@tanstack/react-query</​code>​
   - După ce ați instalat React Query, trebuie să îl importați și să îl configurați în aplicația voastră. React.js. În mod tipic, acest lucru se face în fișierul App.js. Iată un exemplu simplu: <​code>​import React from '​react';​   - După ce ați instalat React Query, trebuie să îl importați și să îl configurați în aplicația voastră. React.js. În mod tipic, acest lucru se face în fișierul App.js. Iată un exemplu simplu: <​code>​import React from '​react';​
 import { QueryClient,​ QueryClientProvider } from '​react-query';​ import { QueryClient,​ QueryClientProvider } from '​react-query';​
Line 196: Line 196:
  
  
-==== Teams ====+===== Sarcini de laborator =====
  
-Asa cum am spus la inceputul anuluiaveti un canal special dedicat pentru **Discutii ​de laborator**Orice intrebare aveti cu privire la laboratorva rog sa o puneti acolo, pentru ​putea sa va lamurim. +  - Creați un nou proiect de BE, așa cum a fost prezentat în Laboratorul 1 
-===== Ce este un backend? ======+  - Extindeți funcționalitatea din WeatherForecastController adăugând o metoda care întoarce prognoza pentru o data primita că parametru. Trebuie să întoarcă prognoza doar pentru următoarele 7 zile. În cazul în care este ceruta o data mai îndepărtatătrebuie întors ​un cod de eroare. 
 +  - Creați un nou proiect de FE, așa cum a fost prezentat în Laboratorul 4 
 +  - Instalați Material UI și React Query în proiectul nou creat 
 +  - Mapati endpointurile din BE folosind OpenAPI Generator 
 +  - Accesați endpointurile folosind componente de Material UI, React Query și metodele generate de OpenAPI Generator 
 +===== Linkuri Utile =====
  
-Multa lume considera ca backendul este pur si simplu o colectie de rute expuse de un server, prin care acesta primeste informatii si furnieaza informatii. Pe scurt, un blackbox. 
  
-<note important>​ +  * Documentație Materia UI: [[https://​mui.com/​material-ui/​getting-started/​overview/​]] 
-Aceasta definitie nu este gresita, dar nu este nici complet corecta, deoarece supra-simplifica rolul unui backend.</note>+  * OpenAPI Generator [[https://​openapi-generator.tech/​]] 
 +  * React Query [[https://​react-query-v3.tanstack.com/]]
  
-Un backend este acea componenta software dintr-o aplicatie care coordoneaza **business logic**-ul unei aplicatii. Chiar daca utilizatorii interactioneaza cu frontendul, frontendul poate efectua doar //actiunile care sunt permise de backend//. Asadar, backendul reprezinta **nucleul logicii de domeniu** a oricarei aplicatii. 
- 
-Backendurile pot fi scrise in multe moduri. Noi ne vom axa pe clasicul **Web API REST**. 
- 
-<note tip>Un Web API este un server web ce expune rute prin care clientii pot interactiona cu sistemul.</​note>​ 
-<note tip>Un Web API REST (Representational State Transfer) este un Web API care comunica peste HTTP.</​note>​ 
- 
- 
-===== Structura Backendului ====== 
- 
-Cea mai simpla structura pe care puteam sa o folosim este **3-tier architecture**. Asa cum a fost prezentat la curs, este cea mai simpla de folosit, dar si cea mai simpla de transformat in dezastru. 
- 
-Am ales sa construim codul folosind o combinatie intre [[https://​docs.microsoft.com/​en-us/​dotnet/​architecture/​modern-web-apps-azure/​common-web-application-architectures|Clean Architecture]],​ [[https://​docs.microsoft.com/​en-us/​azure/​architecture/​microservices/​model/​tactical-ddd|Tactical Domain Driven Design]] si [[https://​www.youtube.com/​watch?​v=cVVMbuKmNes&​ab_channel=CodeOpinion|Vertical Slicing]]. Motivatia pentru fiecare alegere este urmatoarea: 
- 
-  * **Clean** - ofera o separatie clara intre nivele si, cu toate astea, dependentele sunt curate. Nivelul **core** nu are depedenta fata de nimeni. Nivelul **infrastructure** are dependenta de **core**. Nivelul **application** are depdendenta de **core** si **infrastructure**. 
-  * **Tactical Domain Driven Design** - deoarece folosim deja Clean, logica de domeniu este scrisa in Core. Am utilizat sabloane expuse de Tactical DDD (precum agregate), pentru a reflecta ce am discutat pana acum. 
-  * **Vertical Slicing** - ofera un grad de readability foarte mare codului. Cu toate astea, am utilizat Vertical Slicing doar in nivelul **application**,​ pentru a segrega cat mai bine functionalitatile. 
- 
-<note important>​Pentru acest backend, care este simplu, arhitectura pe care noi am propus-o este **overkill**. Cu toate astea, am luat aceasta decizie cu scopul de a va oferi un material didactic cat mai cuprinzator.</​note>​ 
- 
-===== Organizarea codului ===== 
- 
-Fiecare nivel din **Clean** este reprezentat de cate un proiect de .NET **(.csproj)**. Proiectele sunt toate scrise sub umbrela unei solutii .NET **(.sln)**. 
- 
-{{:​pw:​laboratoare:​solutionexplorer.png?​800|}} 
- 
-<note tip>​Crearea unei solutii noi este triviala</​note>​ 
- 
-Pentru a adauga un proiect nou intr-o solutie existenta, click dreapta pe solutie -> Add New Project 
- 
-{{:​pw:​laboratoare:​solutionexploreraddnewproject.png?​800|}} 
-==== Organizarea Nivelului Core ==== 
- 
-Nivelul **Core** este cel mai important, deoarece descrie logica de domeniu din proiect. Aici reprezentam toate deciziile de business care se iau in aplicatia noastra. Este primul nivel pe care l-am scris. 
- 
-{{:​pw:​laboratoare:​solutionexplorercore.png?​800|}} 
- 
-Observati ca avem 3 foldere principale: 
-  * **DataModel** - contine clasele care descriu **Entitatile** sistemului. Aceste clase sunt folosite atat in **Domain**, cat si in nivelul **Infrastructure** 
-  * **Domain** - contine logica de business, impartita intre cele 2 agregate, **Book** si **UserRentals**. Aici avem definite urmatoarele:​ 
-    * clasele de domeniu (de ex: //​BooksDomain.cs//​) 
-    * interfetele pentru repository-uri (de ex: //​IBooksRepository.cs//​) 
-    * comenzi (de ex: //​InsertBookInLibraryCommand.cs//​) 
-    * evenimente (de ex: //​UserRentedBookEvent.cs//​) 
-    * exceptii de domeniu (de ex: //​RentalNotFoundException.cs//​) 
-  * **[[https://​martinfowler.com/​bliki/​Seedwork.html|SeedWork]]** - contine interfete si clase reutilizabile,​ utile pentru "​standardizarea"​ codului. 
- 
-=== Seed Work === 
- 
-Clasele si interfatele scrise in **SeedWork** sunt folosite pentru a //limita libertatea programatorului//​ si pentru a reduce //​duplicarea codului//. Chiar daca are conotatie negativa, acest lucru aduce beneficii, deoarece standardieaza modul in care cod nou este adaugat in aplicatie. Deoarece C# este un limbaj OOP, am profitat la maxim de capabilitatile de mostenire si genericitate pe care ni le ofera. 
- 
-{{:​pw:​laboratoare:​solutionexplorerseedwork.png?​500|}} 
- 
-**Entity** reprezinta o clasa de baza pentru entitatile din baza de date. In aceasta clasa avem definite cheia primara (Id), si 2 campuri pentru audit: createdAt si updatedAt. 
- 
-<code c#> 
-public abstract class Entity 
-{ 
-  public int Id { get; set; } 
-  public DateTime CreatedAt { get; set; } 
-  public DateTime UpdatedAt { get; set; } 
- 
-</​code>​ 
- 
-<note important>​Entity Framework entities, nu DDD entities.</​note>​ 
-<note tip>​[[https://​docs.microsoft.com/​en-us/​ef/​|Entity Framework]] este un framework de gestiune si interactiune a bazelor de date, pentru aplicatiile scrise in .NET. O sa vedeti mai multe detalii la nivelul **Infrastructure**.</​note>​ 
- 
-**IAggregateRoot** este o interfata goala, cu rol de **[[https://​en.wikipedia.org/​wiki/​Marker_interface_pattern|marker]]**. Aceasta va marca obiectele care sunt AggregateRoot (Tactical DDD). 
- 
-<code c#> 
-public interface IAggregateRoot 
-{ 
-} 
-</​code>​ 
- 
-**DomainOfAggregate** este o clasa abstracta, care primeste ca argument generic un IAggregateRoot. Rolul sau este sa fie un **blueprint** pentru cele doua obiecte de domeniu cu care lucram, **BooksDomain** si **UsersRentalsDomain**. 
-<code c#> 
-public abstract class DomainOfAggregate<​TAggregate>​ where TAggregate : Entity, IAggregateRoot 
-{ 
-  private protected readonly TAggregate aggregate; 
- 
-  public DomainOfAggregate(TAggregate aggregate) 
-  { 
-    this.aggregate = aggregate; 
-  } 
-} 
-</​code>​ 
-<note tip>Dupa cum observati, aceasta clasa abstracta doar primeste, in constructor,​ ca parametru, ceva de tipul argumentului generic si il retine pentru acces ulterior.</​note>​ 
- 
-**ICreateAggregateComand** este o interfata **marker** pentru comenzile care creaza obiecte de domeniu. 
-<code c#> 
-public interface ICreateAggregateCommand 
-{ 
-} 
-</​code>​ 
- 
-**IRepositoryOfAggregate** este o interfata generica, ce accepta ca argumente un **IAggregateRoot** si o **ICreateAggregateCommand**. Aceasta interfata expune cele 3 metode de care avem nevoie in repository: 
- 
-  * sa cream un agregat 
-  * sa returnam un agregat 
-  * sa salvam starea sistemului 
- 
-<code c#> 
-public interface IRepositoryOfAggregate<​TAggregate,​ TAggregateCreateCommand> ​ 
-  where TAggregate : Entity, IAggregateRoot 
-  where TAggregateCreateCommand : ICreateAggregateCommand 
-{ 
-  Task AddAsync(TAggregateCreateCommand command, CancellationToken cancellationToken);​ 
-  Task<​DomainOfAggregate<​TAggregate>?>​ GetAsync(int aggregateId,​ CancellationToken cancellationToken);​ 
-  Task SaveAsync(CancellationToken cancellationToken);​ 
- 
-</​code>​ 
- 
-<note tip>​Observati cum functionalitatea acestei interfete este conturata de Agregatul oferit si de Comanda oferita.</​note>​ 
-<note tip>In aplicatiile care nu folosesc DDD, o sa vedeti ca repository-urile au mult mai multe metode. De fapt, fiecare acces la baza de date este mediat de un apel al repository. In cazul nostru, nu avem nevoie de asa ceva, deoarece obiectul de domeniu are acces la entitate si ii poate modifica starea. Are nevoie doar sa salveze starea sistemului.</​note>​ 
-<note tip>Task reprezinta o actiune asincrona, care se intampla pe un thread. Este o abstractizare a lucrului cu threaduri. Un task poate fi asteptat (**await**). Mai multe informatii, [[https://​docs.microsoft.com/​en-us/​dotnet/​api/​system.threading.tasks.task?​view=net-6.0|gasiti aici.]]</​note>​ 
- 
-=== Data Model === 
- 
-Aici am definit clasele care reprezinta datele din sistemul nostru. Aceste clase sunt simple, fara functionalitate. Ele vor fi folosite de sistemul de configurare al **EntityFramework**. 
- 
-In plus, entitatile DDD mostenesc clasa abstracta **Entity**, in timp ce radacinile de agregat mostenesc si interfata **IAggregateRoot**. ​ 
- 
-<code c#> 
-public class Books : Entity, IAggregateRoot 
-{ 
-  public Books(string name, string author, string genre, int maximumDaysForRental,​ bool isBooked = false) 
-  { 
-    Name = name; 
-    Author = author; 
-    Genre = genre; 
-    MaximumDaysForRental = maximumDaysForRental;​ 
-    IsBooked = isBooked; 
-  } 
- 
-  public string Name { get; set; } 
-  public string Author { get; set; } 
-  public string Genre { get; set; } 
-  public int MaximumDaysForRental { get; set; } 
-  public bool IsBooked { get; set; } 
-  public virtual ICollection<​Keywords>​ Keywords { get; set; } = new List<​Keywords>​();​ 
-} 
-</​code>​ 
- 
-Avem si un value object, si anume Keywords. Odata cu .NET 6, putem folosi tipul **record** pentru a descrie value objects in Entity Framework. Diferenta intre **class** si **record** este ca **record** este imutabil. 
- 
-<code c#> 
-public record Keywords 
-{ 
-  public Keywords(string name, string description) 
-  { 
-    Name = name; 
-    Description = description;​ 
-  } 
- 
-  public string Name { get; init; } 
-  public string Description { get; init; } 
-} 
-</​code>​ 
- 
-<note tip>​Observati diferenta intre **set** si **init**. Set implica faptul ca acea proprietate poate avea valoarea schimbata in timp (mutabiltiate). Init implica faptul ca acea proprietate nu poate avea valoarea schimbata in timp (imutabilitate).</​note>​ 
- 
-=== Domain === 
- 
-Cel mai important folder, aici avem business logic-ul aplicatiei impartit intre cele doua radicini de agregat, **books** si **users rentals**. 
- 
-Fiecare dintre cele doua sub foldere are toate informatiile necesare pentru desfasurarea business logic-ului aferent. Cel mai simplu, books, are doar 3 clase: 
- 
-  * Obiectul de domeniu - BooksDomain.cs 
- 
-<code c#> 
-public class BooksDomain : DomainOfAggregate<​Books>​ 
-{ 
-  public BooksDomain(Books aggregate) : base(aggregate) 
-  { 
-  } 
- 
-  public void UpdateBook(string name, string author, string genre, int maximumDaysForRental,​ ICollection<​Keywords>​ keywords) 
-  { 
-    aggregate.Name = name; 
-    aggregate.Author = author; 
-    aggregate.Genre = genre; 
-    aggregate.MaximumDaysForRental = maximumDaysForRental;​ 
-    aggregate.Keywords = keywords; 
-   } 
- 
-   ​public bool BookCanBeRented(int rentalDays) => !aggregate.IsBooked && rentalDays <= aggregate.MaximumDaysForRental;​ 
-   ​public void MarkBookAsRented() => aggregate.IsBooked = true; 
-   ​public void MarkBookAsAvailable() => aggregate.IsBooked = false; 
-} 
-</​code>​ 
-<note tip>​Observati utilizarea clasei abstracte **DomainOfAggregate**</​note>​ 
- 
-  * Interfata pentru repository - IBooksRepository.cs 
-<code c#> 
-public interface IBooksRepository : IRepositoryOfAggregate<​Books,​ InsertBookInLibraryCommand>​ 
-{ 
-  public Task DeleteBookAsync(Books book, CancellationToken cancellationToken);​ 
-} 
-</​code>​ 
- 
-<note tip>Pe langa metodele de baza din IRepositoryOfAggregate,​ am mai adaugat si o metoda de stergere a agregatei.</​note>​ 
- 
-  * Comanda de adaugare o carte in sistem - InsertBookInLibraryCommand.cs 
-<code c#> 
-public record InsertBookInLibraryCommand : ICreateAggregateCommand 
-{ 
-  public string Name { get; init; } 
-  public string Author { get; init; } 
-  public string Genre { get; init; } 
-  public int MaximumDaysForRental { get; init; } 
-  public IEnumerable<​KeyWordDto>​ Keywords { get; init; } = new List<​KeyWordDto>​();​ 
- 
-  public InsertBookInLibraryCommand(string name, string author, string genre, int maximumDaysForRental,​ IEnumerable<​KeyWordDto>​ keywords) 
-  { 
-     Name = name; 
-     ​Author = author; 
-     Genre = genre; 
-     ​MaximumDaysForRental = maximumDaysForRental;​ 
-     ​Keywords = keywords; 
-   } 
-} 
- 
-public record KeyWordDto (string Name, string Description);​ 
-</​code>​ 
- 
-Acest fisier are definit cele doua **records** care reprezinta comanda efectiva si [[https://​en.wikipedia.org/​wiki/​Data_transfer_object|DTO]]-ul pentru Keywords. 
- 
-<note tip>​Observati utilizarea ICreateAggregateCommand</​note>​ 
- 
-<note tip>In folderul pentru **UsersRentals** avem mai multe clase definite, deoare logica de business este putin mai complexa. Clasele care sunt in plus reprezinta exceptii si evenimente.</​note> ​ 
- 
-==== Organizarea nivelului Infrastructure ==== 
- 
-Dupa ce am terminat de scris nivelul core, am scris nivelul de infrastructura. Rolul acestui nivel este sa se ocupe de infrastructura proiectului,​ precum baze de date, sisteme de logging, etc.... 
- 
-In cadrul proiectului nostru, in acest nivel ne ocupam doar de baza de date, adica de configurarea entitatilor (de EF) si de implementarea celor 2 repositories. 
- 
-Asa cum am spus initial, singura depedenta pe care o are nivelul de infrastructura este asupra nivelului Core: 
-  * Entitatile (de EF, nu DDD) declarate 
-  * Interefetele pentru repositories 
- 
-{{:​pw:​laboratoare:​solutionexplorerinfrastructure.png?​600|}} 
- 
-=== BookLibraryContext === 
- 
-Cel mai important fisier din acest nivel, reprezinta o implementare a **DbContext**,​ care reprezinta clasa esentiala de functionare a **EntityFramework**. 
- 
-<code c#> 
-public class BookLibraryContext : DbContext 
-{ 
-  public BookLibraryContext(DbContextOptions<​BookLibraryContext>​ options) : base(options) ​ 
-  { 
-  } 
- 
-  protected override void OnModelCreating(ModelBuilder modelBuilder) 
-  { 
-    modelBuilder.ApplyConfigurationsFromAssembly(typeof(BooksConfiguration).Assembly);​ 
-  } 
- 
-  public DbSet<​Users>​ Users => Set<​Users>​();​ 
-  public DbSet<​Books>​ Books => Set<​Books>​();​ 
-  public DbSet<​Rentals>​ Rentals => Set<​Rentals>​();​ 
-} 
-</​code>​ 
- 
-Contextul va fi folosit in nivelul **Api** pentru a interactiona cu baza de date in cazul citirilor, cat si nivelul **infrastructure** in implementarea repository-urilor. 
- 
-<note tip>​Observati cum clasele pe care le-am marcat in **Core** ca Entities sunt transformate aici in **DbSet**. DbSet este o clasa a EntityFramework.</​note>​ 
- 
-=== Entity Configurations === 
- 
-Aici am definit configuratiile pentru baza de date, sub forma de clase de configurare. Aceste clase de configurare sunt interpretate de **EntityFramework**,​ datorita <code c#>​modelBuilder.ApplyConfigurationsFromAssembly(typeof(BooksConfiguration).Assembly);</​code>​ scris in context. 
- 
-O clasa de configurare trebuie sa mosteneasca **IEntityTypeConfiguration** pentru o anumita entitate si sa implementeze metoda **Configure**. 
- 
-<note tip>​Acest mod de configurare poarta numele de **Fluent API**. Mai multe informatii puteti gasi [[https://​www.learnentityframeworkcore.com/​configuration/​fluent-api|aici]].</​note>​ 
- 
-<code c#> 
-internal class BooksConfiguration : EntityConfiguration<​Books>​ 
-{ 
-  public override void Configure(EntityTypeBuilder<​Books>​ builder) 
-  { 
-    builder 
-      .Property(x => x.Name) 
-      .IsRequired();​ 
- 
-    builder 
-      .Property(x => x.Genre) 
-      .IsRequired();​ 
- 
-    builder 
-      .Property(x => x.Author) 
-      .IsRequired();​ 
- 
-    builder 
-      .OwnsMany(x => x.Keywords);​ 
- 
-    base.Configure(builder);​ 
-        } 
-    } 
-</​code>​ 
- 
-<note tip>​Deoarece trebuie sa configuram si clasa de baza **Entity**, definita in SeedWork, configuratiile noastre mostenesc, de fapt,  
-**EntityConfiguration**,​ care este o clasa scrisa de noi</​note>​ 
-<code c#> 
-internal abstract class EntityConfiguration<​TEntity>​ : IEntityTypeConfiguration<​TEntity>​ 
-        where TEntity : Entity 
-{ 
-  public virtual void Configure(EntityTypeBuilder<​TEntity>​ builder) 
-  { 
-    builder 
-      .Property(x => x.CreatedAt) 
-      .ValueGeneratedOnAdd();​ 
- 
-    builder 
-      .Property(x => x.UpdatedAt) 
-      .ValueGeneratedOnAddOrUpdate();​ 
-   } 
-} 
-</​code>​ 
-<note tip>​Observati cum am folosit IEntityTypeConfiguration impreuna cu clasa noastra Entity aici, pentru genericitate.</​note>​ 
- 
-=== Repositories === 
- 
-Aici am definit **implementarile** interfetelor de repository, declarate in **Core**. 
- 
-Asa cum am spus mai sus, repository-urile noastre doar adauga un agregat in sistem, il returneaza, si salveaza starea sistemului. 
- 
-<code c#> 
-public class BooksRepository : IBooksRepository 
-    { 
-        private readonly BookLibraryContext context; 
- 
-        public BooksRepository(BookLibraryContext context) 
-        { 
-            this.context = context; 
-        } 
- 
-        public async Task AddAsync(InsertBookInLibraryCommand command, CancellationToken cancellationToken) 
-        { 
-            var keywords = command.Keywords.Select(x => new Keywords(x.Name,​ x.Description)).ToList();​ 
- 
-            var book = new Books(command.Name,​ command.Author,​ command.Genre,​ command.MaximumDaysForRental);​ 
-            book.Keywords = keywords; 
- 
-            await context.Books.AddAsync(book,​ cancellationToken);​ 
-            await SaveAsync(cancellationToken);​ 
-        } 
- 
-        public async Task<​DomainOfAggregate<​Books>?>​ GetAsync(int aggregateId,​ CancellationToken cancellationToken) 
-        { 
-            var book = await context.Books 
-                .FirstOrDefaultAsync(x => x.Id == aggregateId,​ cancellationToken);​ 
- 
-            if (book == null) 
-            { 
-                return null; 
-            } 
- 
-            return new BooksDomain(book);​ 
-        } 
- 
-        public async Task DeleteBookAsync(Books book, CancellationToken cancellationToken) 
-        { 
-            context.Books.Remove(book);​ 
- 
-            await SaveAsync(cancellationToken);​ 
-        } 
- 
-        public Task SaveAsync(CancellationToken cancellationToken) 
-        { 
-            return context.SaveChangesAsync(cancellationToken);​ 
-        } 
-    } 
-</​code>​ 
-<note tip>​Observati utilizarea clasei de **context**.</​note>​ 
-<note tip>​Observati utilizarea claselor si interfetelor generice definite in **SeedWork**.</​note>​ 
-<note tip>​Observati utilizarea async/​await. Pentru a face **await** la un task, trebuie ca metoda sa fie decorata cu **async**.</​note>​ 
- 
-=== Migrations === 
- 
-Migrarile reprezinta un snapshot al bazei de date pe baza configurarii din cod (context + entity configurations). Vom discuta la un laborator urmator despre asta. 
pw/laboratoare/05.1677242040.txt.gz · Last modified: 2023/02/24 14:34 by ciprian.dobre
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0