Skip to content

Dev-TiagoCarvalho/DynamicFireMapper

Repository files navigation

AVE 2020/2021 FireMapper LI41D - G07

Geral

Este projeto foi dividido em duas fases:

  1. Trabalho Prático 1 -> Reflexão
  2. Trabalho Prático 2 -> Classes geradas dinâmicamente

O projeto foi construído da seguinte forma:

  1. Attributes:
    • FireCollection: Contém o nome da coleção de armazenamento, sendo aplicável à classe representante do domínio.
    • FireKey: Aplicável à propriedade representante da chave primária.
    • FireIgnore: Aplicável a propriedades não correspondentes ao domínio, ou seja que não são para mapear.
  2. Mappers:
    • IDataMapper: Interface representante de um mapeador de dados.
    • BaseFireMapper: Classe que implementa IDataMapper responsável por servir de base ás classes abaixo na hierarquia de tipos.
    • FireDataMapper: Classe que extende BaseFireMapper e que usa reflexão para obter os valores das propriedades.
    • DynamicFireMapper: Classe que extende BaseFireMapper e que usa código gerado dinâmicamente para obter os valores das propriedades.
  3. Wrappers:
    • IPropertyWrapper: Interface representante de uma propriedade.
    • PropertyPrimitive: Classe que implementa IPropertyWrapper responsável por abstrair propriedades primitivas.
    • PropertyComplex: Classe que implementa IPropertyWrapper responsável por abstrair propriedades complexas.
    • WrapperBuilder: Classe responsável por gerar dinâmicamente tipos que implementem IPropertyWrapper.
  4. Sources:
    • IDataSource: Interface representante de uma instância de armazenamento.
    • FireDataSource: Classe que implementa IDataSource responsável por abstrair o armazenameto no serviço FireStore da Google.
    • WeakDataSource: Classe que implementa IDataSource responsável por originar um armazenamento local.
    • NonExecutableSource: Classe que implementa IDataSource responsável por não executar operaçãoes de escrita, somente de leitura.

Trabalho Prático 1

DATA LIMITE de entrega: 18 de Abril de 2021. Fazer push a tag trab1 no repositório do grupo.

Neste trabalho pretende-se desenvolver a biblioteca FireMapper que disponibiliza uma abstração sobre uma colecção de uma base de dados documental FireStore. Em modo resumido, o FireStore é uma base de dados NoSQL que armazena colecções de documentos JSON.

https://github.com/isel-leic-ave/FireMapper

Uma base de dados FireStore é gerida a partir de um projecto FireBase. De modo a se familiarizar com a tecnologia deve em primeiro lugar seguir os passos do guião “FireStore get started para criar alguns documentos e uma pequena aplicação em C# que se liga a essa base de dados listando o seu conteúdo na consola.

O objectivo da biblioteca FireMapper é facilitar o acesso aos documentos de uma colecção por via de um IDataMapper. Esta interface especifica os métodos de acesso à coleção e que correspondem às operações CRUD.

    public interface IDataMapper
    {
        IEnumerable GetAll();
        object GetById(object keyValue);
        void Add(object obj);
        void Update(object obj);
        void Delete(object keyValue);
    }

Por cada colecção deve existir uma classe de domínio com propriedades correspondentes às propriedades de um documento. Essas classes podem ter informação complementar dada na forma de anotações, por via dos seguintes custom attributes:

  • FireCollection - aplicado a uma classe para identificar o nome da coleção Firestore.
  • FireKey - identifica a propriedade que é chave única na pesquisa de um documento através do método GetById
  • FireIgnore - propriedade a ignorar no mapeamento com um documento.

Exemplo:

[FireCollection("Students")]
public record Student(
    [property:FireKey] string Number,
    string Name, 
    [property:FireIgnore] string Classroom)  
{}

Students collection:

{
    "Name": "Zanda Cantanda",
    "Number": "72538",
    "Classroom": "TLI41D"
}

A classe FireDataMapper implementa a interface IDataMapper com um comportamento dependente da classe de domínio (e.g. Student), cujo Type é fornecido na sua instanciação.

IDataMapper studentsMapper = new FireDataMapper(typeof(Student), ...);

A implementação de FileDataMapper deve ser feita com o suporte da classe FireDataSource da biblioteca FireSource disponibilizado no respectivo projecto que integra a solução.

Enquanto a classe FireDataSource lida com dados fracamente tipificados na forma de Dictionary<string, object>, a classe FireDataMapper trata objectos de domínio, e.g. instâncias de Student.

  1. Implemente os custom attributes FireCollection, FireKey e FireIgnore.

  2. Usando a API de Reflexão implemente a classe FireDataMapper que faz o mapeamento entre objectos de domínio e dados na forma de Dictionary<string, object> manipulados por uma instância de IDataSource. Implemente os testes unitários que validem o correcto funcionamento dos métodos, incluíndo casos de excepção como por exemplo, ausência de anotações; mais que uma propriedade anotada com FireKey; etc. Garanta o máximo de cobertura observando o coverage report obtido através do procedimento descrito no README.md.

  3. Faça uma implementação alternativa de IDataSource na classe WeakDataSource que mantém os dados apenas em memória (defina a estrutura de dados ao seu critério). Valide o funcionamento com testes unitários. Note que a classe FireDataMapper pode funcionar com qualquer implementação de IDataSource especificada por parâmetro do construtor.

  4. Defina as classes de um modelo de domínio e crie uma nova base de dados para esse modelo no FireStore e teste com a sua biblioteca FireMapper. Exemplos: carros, filmes, música, desportos, jogadores de futebol, ligas de futebol, jogadores da NBA, videogames, surfistas, lutadores, séries de TV, surf spots, praias, cidades do mundo, resorts de neve, hotéis, marcas, etc. Requisitos:

    • O modelo definido deverá incluir no mínimo duas entidades (classes) com uma relação de associação.
    • Use auto id nos documentos da base de dados.
    • Veja o exemplo de associação criado nos testes do projecto FireSource.Tests e como a propriedade Classroom de um documento Student corresponde à propriedade Token de um documento Classroom semelhante ao comportamento de uma foreign key.
    • Cada grupo de trabalho deverá usar um modelo de domínio distinto.
    • Modelos de domínio mais ricos em termos de dados e relações entre si, serão valorizados.

Faça um pull request para o repositório https://github.com/isel-leic-ave/FireMapper/ para adicionar um novo ficheiro na pasta Db que identifica o modelo domínio. Cada grupo deve escolher um domínio diferente. Os domínios são atribuídos de acordo com a ordem dos pull request.

Cada pull request deve atender aos seguintes requisitos:

  • Um novo ficheiro com o nome na forma: <turma>-<grupo>-nome-do-dominio.txt, por exemplo: i41d-g07-surf-spots.txt
  • O conteúdo do ficheiro deve descrever o esquema (nomes de campo e tipo) de cada coleção. Deve ter pelo menos duas coleções distintas com uma associação (foreign key) entre elas.
  • Um exemplo dos documentos.
  1. FireDataMapper deve suportar classes de domínio com propriedades de tipo definido por outras classes de domínio. Neste caso deve criar uma outra instância de FireDataMapper auxiliar para o respectivo tipo da propriedade que permite aceder à respectiva colecção. Valide o funcionamento da associação em testes unitários.

Exemplo a classe ClassroomInfo correspondente ao tipo da propriedade Classroom:

[FireCollection("Students")]
public record Student( [property:FireKey] string Number, string Name, ClassroomInfo Classroom)  {}

[FireCollection("Classrooms")]
public record ClassroomInfo([property:FireKey] string Token, string Teacher) {}

Trabalho Prático 2

DATA LIMITE de entrega: 23 de Maio de 2021. Fazer push a tag trab3 no repositório do grupo.

Objectivos: Análise e manipulação programática de código intermédio com API de System.Reflection.Emit.

No seguimento do Trabalho 1 desenvolvido na biblioteca FireMapper pretende-se desenvolver uma nova classe DynamicDataMapper que implementa IDataMapper, mas que ao contrário de FireDataMapper NÃO usa reflexão no acesso (leitura ou escrita) das propriedades das classes de domínio. Note, que continuará a ser usada reflexão na consulta da metadata, deixando apenas de ser usada reflexão em operações como <property>.SetValue(…) ou <property>.GetValue(…). O acesso a propriedades passa a ser realizado directamente com base em código IL emitido em tempo de execução através da API de System.Reflection.Emit.

Para tal, DynamicDataMapper deve gerar em tempo de execução implementações, em que cada tipo implementa o acesso a uma determinada propriedade de uma classe de domínio.

Etapa 0 - TPC06 - System.Reflection

Ainda usando apenas a Reflection API (sem Emit) reorganize o projecto resultante do Trabalho 1 de modo a que FireDataMapper mantenha uma estrutura de dados com um tipo de elemento (interface) que define a API de acesso a uma propriedade de uma classe de domínio.

Esta interface deve ter implementações diferentes, consoante represente o acesso a uma propriedade "simples" (string ou primitivo) ou "complexa" (do tipo de outra classe de domínio).

Etapa 1 - System.Reflection.Emit

Implemente DynamicFireMapper que gera dinamicamente implementações da interface definida na Etapa 0, para cada propriedade de cada classe de domínio acedida.

Implemente testes unitários que validem o correcto funcionamento de DynamicFireMapper.

Requisito: Deve incluir nos testes unitários a utilização de entidades de domínio de tipo valor (i.e. definidas como struct).


Abordagem de desenvolvimento

Como suporte ao desenvolvimento de DynamicFireMapper deve usar as ferramentas:

  • ildasm
  • peverify

Deve desenvolver em C# uma classe dummy num projecto separado com uma implementação semelhante aquela que pretende que seja gerada através da API de System.Reflection.Emit. Compile a classe dummy e use a ferramenta ildasm para visualizar as instruções IL que servem de base ao que será emitido através da API de System.Reflection.Emit.

Grave numa DLL as classes geradas pelo DynamicFireMapper e valide através da ferramenta peverify a correcção do código IL.

Etapa 2 - Benchmarking

Implemente uma aplicação consola num outro projecto FireMapperBench da mesma solução para comparar o desempenho dos métodos entre as classes FireDataMapper e DynamicDataMapper.

Para as medições de desempenho use a abordagem apresentada nas aulas. Registe e comente os desempenhos obtidos entre as duas abordagens.

Atenção:

  • testes de desempenho NÃO são testes unitários
  • Use WeakDataSource nas medidas de desempenho.
  • Evite IO.

About

A project that uses Dynamic generated classes and reflection

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages