Desenvolvimento-Backend.md•22.1 kB
A plataforma foi projetada para oferecer flexibilidade e robustez, permitindo que diferentes equipes trabalhem com diferentes versões, conectores personalizados e funcionalidades adaptadas sem comprometer a estabilidade ou integridade do sistema.
Aqui está um exemplo com descrição detalhada de como a plataforma funciona com relação a implementações/customizações em contextos específicos, garantindo a existência de diferentes times atuando em diferentes versões, em diferentes projetos:
### **Arquitetura da Plataforma**
1. **Serviços Baseados em Azure Functions**:
- A plataforma é composta por vários serviços, cada um implementado como Azure Functions. Esses serviços são responsáveis por diferentes áreas funcionais, como autenticação, comunicação, gerenciamento de conteúdo, armazenamento, e integração com métodos de pagamento.
<span style="width:200px">

</span>
2. **Principais entidades da plataforma**:
Uma visão geral da plataforma pode ser baseada em três **entidades** principais existente na plataforma:
>**Diretório (IDirectory):**
>
> - Representa o nível mais alto de organização, proporcionando uma separação lógica.
> - Pode conter diversos projetos.
>**Projeto (IProject):**
>
> - Representa um agrupamento menor de recursos e usuários dentro de um diretório.
> - Pode haver vários projetos dentro de um diretório.
>**Conector (IConnector):**
>
> - Abstrai determinados fluxos de trabalho realizados pelos serviços.
> - Pode estar presente no diretório, autorizando seu uso em todos os projetos do diretório, ou no projeto, limitando-se > ao contexto relacionado.
> - Um conector pode ter várias implementações.
> Exemplo: O conector IConnectorGenerativeIa possui duas implementações:
> Api.Digitalpages.connector.Vertex: utiliza as APIs do Germini do Google.
> Api.DigitalPages.connector.OpenAi: utiliza as APIs do OpenAI.
> - Um projeto ou diretório pode apresentar um ou mais conectores.
Ao utilizar as três entidades principais da plataforma de forma adequada, é possível construir diversas soluções com a mesma base, aproveitando ao máximo a abstração em sua construção. Alguns exemplos de casos de uso:
**Projeto 01**
- Foco na gestão de cursos.
- Entrega e catologação de vídeos.
- Conteúdo e feedback personalizado.
**Project 02**
- Foco na entrega de conteúdos.
- Indicação de conteúdos de interesse do usuário.
**Project 03**
- Foco na manutenção em fábricas.
- Análise de dados de equipamentos em tempo real.
A escolha correta dos conectores, com base nas diferentes necessidades, garante o sucesso da utilização da plataforma. Para cada projeto, a configuração ideal seria a seguinte:
<!--
erDiagram
"Diretório" ||--|| "Project 01" : ""
"Diretório" ||--|| "Project 02" : ""
"Diretório" ||--|| "Project 03" : ""
"Project 01" ||--|| "IConnectorLearning" : ""
"Project 01" ||--|| "IConnectorStorageContentEnrichmentV3" : ""
"Project 01" ||--|| "IConnectorGenerativeIA" : ""
"Project 02" ||--|| "IConnectorManagedContent" : ""
"Project 02" ||--|| "IConnectorInsightsUser" : ""
"Project 03" ||--|| "IConnectorManagedContent " : ""
"Project 03" ||--|| "IConnectorIot " : ""
-->
[](https://mermaid.live/edit#pako:eNqVkkFqwzAQRa9iZp1Am-y8C0kphhYKoV1pM1gTWyUalfG4UOKcKkfIxTpuQ0lJXWIJgfTRm_8Xfwdl8gQ5kKwCVoLRcWbLwSoI6fEgITnIum467TpTnyS9UqnZza2puQm2rwNmY4H5BfDL_AcolonZ5CQPhMKBq9Hg2g5WZG8l1juWUNbRbi_jM9wTk6CGdyoWZ_AFPvsbf0S2IP6UZNh9AC-4CVWtzXND8p_7_Br3bNh-gC_SGQQTiCQRg7d67foRDrSmSA76D5422G61H763r9hqWn9wCblKSxNo3zwqnToJ-Qa3jankQx_zu7Jfzd1_AkGo4eo)
Seguindo essa lógica, qualquer funcionalidade pode ser inserida ou atualizada de forma eficiente, atribuindo o desenvolvimento de conectores para times especialistas sem a necessidade de um conhecimento profundo do serviço que será utilizado, tornando o paralelismo algo realmente visível no dia a dia.
3. **Conectores e Interface Core**:
- A plataforma utiliza uma arquitetura de conectores, onde cada conector implementa funcionalidades específicas de acordo com interfaces definidas no projeto "Interface Core". Essas interfaces padronizam as operações que cada conector deve suportar, garantindo consistência na comunicação entre os serviços e os conectores.
Interface do core, representando uma integração com uma API Generativa:
[Repositório](https://dev.azure.com/digitalpages/Plataforma/_git/csharp_interface_api?path=/Connector/IConnectorGenerativeIA.cs)
~~~csharp
public interface IConnectorGenerativeIA
{
/// <summary>
/// Responde uma pergunta efetuada pelo usuário
/// </summary>
/// <param name="question">Pergunta do usuário</param>
/// <param name="restrictContext">Indicador se deve restringir o contexto da resposta</param>
/// <returns></returns>
Task<string> Answer(string question, bool restrictContext);
/// <summary>
/// Traduz um conteúdo.
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
Task<string> Translate(string content);
/// <summary>
/// Gera uma imagem com base em uma descrição.
/// </summary>
/// <param name="description"></param>
/// <returns></returns>
Task<Stream> Image(string description);
}
~~~
Implementação de um novo serviço que faz uso de API Generativa:
~~~csharp
public class MeuConector : IConnectorGenerativeIA
{
public async Task<string> Answer(string question, bool restrictContext)
{
// Lógica para responder à pergunta
}
public async Task<string> Translate(string content)
{
// Lógica para traduzir o conteúdo
}
public async Task<Stream> Image(string description)
{
// Lógica para gerar a imagem
}
}
~~~
Utilização do connector pelo serviço:
~~~csharp
//Endpoint cadastrado no serviço para entrega de conteúdo com base em uma implementação de API Generativa
//
[FunctionName(nameof(GenerateResponse))]
public async Task<IActionResult> GenerateResponse(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "v1/generative/connector/uid/{connectorUid}/response")] HttpRequest req, Guid connectorUid)
{
// Objeto representando o usuário autenticado é criado (JWTData)
// Objeto apresenta uma instância do IUser, com base nas informações do JWT
// JWT define Uid do usuário, diretório, projetos e conectores que o usuário tem acesso.
var jwt = await req.JwtInfo().ConfigureAwait(false);
// Serviço avalia se usuário tem acesso ao conector informado.
if (jwt.AvailableConnectors.Any(p => p.Uid == connectorUid) == false) return new UnauthorizedResult();
// Configuração do conector é buscado no serviço.
var config = await ConnectorLifeCycle.Get(connectorUid, new ConnectorConfigOptions()).ConfigureAwait(false);
if (config == null) return new BadRequestObjectResult(new WrapperError { Message = "Connector não encontrado." });
// Conteúdo enviado pelo usuário é lido, neste caso, sem o uso de um wrapper.
var data = await req.BodyDeserialize<Dictionary<string, string>>();
string prompt = null;
if (data.ContainsKey("question")) prompt = data["question"];
if (string.IsNullOrEmpty(prompt) && data.ContainsKey("prompt")) prompt = data["prompt"];
if (string.IsNullOrEmpty(prompt)) return new BadRequestObjectResult(new WrapperError { Message = "Parâmetro question ou prompt não encontrado" });
// Com base na configuração encontrada do conector, serviço tenta criar uma instância do conector com requisito da interface IConnectorGenerativeIA implementada.
var connectorContext = await ConnectorContextData.Populate(config, ServiceProvider, req).ConfigureAwait(false);
var spec = await SpecificationConnector.From<IConnectorGenerativeIA>(connectorContext).ConfigureAwait(false);
if (spec == null) return new NotFoundObjectResult(new WrapperError { Message = "Connector não compatível com IConnectorGenerativeIA." }); ;
// Serviço direciona informação para o conector autorizado.
var result = await spec.Answer(prompt, false);
return new OkObjectResult(new { response = result });
}
~~~
4. **Projeto Interface Core**:
- O "Interface Core" é um projeto central que contém todas as interfaces utilizadas pela plataforma. Além das interfaces, ele fornece implementações base para padronizar o desenvolvimento dos conectores. Por exemplo, a interface `ISystemConnectorBaseCrudFlow` define operações CRUD padronizadas para manipulação de modelos.
Definição da interface ISystemConnectorBaseCrudFlow:
[Repositório](https://dev.azure.com/digitalpages/Plataforma/_git/csharp_interface_api?path=/Connector/ISystemConnectorBaseCrud.cs)
~~~csharp
public interface ISystemConnectorBaseCrudFlow<TModel, TOptions>
where TModel : IBaseModelV3
where TOptions : IBaseCrudOptions
{
Task<TModel> Get(Guid uid, TOptions options);
Task<List<TModel>> Get(TOptions options);
Task<TModel> Insert(TModel model);
Task<TModel> Update(TModel model);
Task<bool> Delete(TModel model);
}
~~~
Utilizando padrões para a criação de novos conectores:
~~~csharp
public interface IConnectorUserData :
ISystemConnectorBaseCrudFlow<IUserData, UserDataQueryOptions>,
{
}
public class UserBaseQueryOptions : IBaseCrudOptions
{
public List<Guid> FilterUser { get; set; }
public List<UserDataOriginType> FilterOriginType { get; set; }
public List<ObjectType> FilterTargetsType { get; set; }
public List<ObjectType> FilterContextsType { get; set; }
public List<Guid> FilterTargets { get; set; }
public List<Guid> FilterContexts { get; set; }
}
~~~
Implementação em um conector:
~~~csharp
public class ConnectorUserData : SpecificationConnector,
IConnectorUserData
{
public async Task<IUserData> Get(Guid uid, UserDataQueryOptions options){ /*... */ }
public async Task<List<IUserData>> Get(UserDataQueryOptions options){ /*... */}
public async Task<IUserData> Insert(IUserData model) { /*... */ }
public async Task<IUserData> Update(IUserData model) { /*... */ }
public async Task<bool> Delete(IUserData model) { /*... */ }
/*
...
*/
}
~~~
### **Versionamento e Customização**
1. **Versionamento Semântico**:
- A plataforma adota o versionamento semântico, onde cada conector e serviço é versionado de forma independente. As versões seguem o padrão major.minor.patch, permitindo uma clara distinção entre mudanças que quebram compatibilidade (major), novas funcionalidades retrocompatíveis (minor), e correções de bugs (patch).
2. **Publicação de Conectores como Pacotes NuGet**:
- Cada conector é desenvolvido, versionado e publicado como um pacote NuGet no repositório privado do Azure DevOps. Isso permite que diferentes serviços consumam versões específicas dos conectores conforme necessário.
...
4. **Documentação e Comunicação**:
- Toda documentação gerada no projeto é integrada ao Wiki do Azure DevOps, que faz a leitura da documentação criada no repositório. Isso garante que a documentação esteja sempre atualizada e acessível para diferentes equipes.
- Os clientes são notificados sobre atualizações e hotfixes pelo time de relacionamento com o cliente, garantindo que estejam cientes das mudanças que possam impactar suas operações.
### **Escalabilidade e Customização**
1. **Feature Flags e Parâmetros de Configuração**:
- A plataforma permite a customização por meio de parâmetros de configuração em cada conector, que funcionam como feature flags. Isso possibilita que diferentes funcionalidades sejam ativadas ou desativadas para diferentes clientes, sem a necessidade de alterar o código-fonte.
~~~csharp
public class VertexConnector : SpecificationConnector,
IConnectorGenerativeIA
{
[Specification(Property = "json_base64", Description = "Chave de acesso para API do Google", IsRequired = false, RuntimeValue = false)]
public string JsonBase64 { get; set; }
/* ... */
}
~~~
2. **Flexibilidade para Novos Recursos**:
- A arquitetura baseada em interfaces permite que novos conectores ou funcionalidades sejam introduzidos sem modificar as interfaces principais. Por exemplo, um conector pode implementar a interface `IConnectorGenerativeIA` para fornecer funcionalidades de IA, como tradução ou geração de imagens, utilizando serviços de terceiros como OpenAI ou Gemini, de forma transparente para a plataforma.
2. ### **Flexibilidade para Novos Recursos sem Alterar a Interface Core**
- Um dos aspectos mais poderosos da plataforma é a capacidade de introduzir novas funcionalidades sem a necessidade de modificar a Interface Core. Isso é possível devido à estrutura modular baseada em conectores, onde cada conector implementa interfaces definidas na Interface Core. Essa abordagem traz várias vantagens:
1. **Isolamento de Funcionalidades**:
- Novas funcionalidades podem ser introduzidas tornando o conector como um dos responsáveis por manipular a requisição do usuário. Isso significa que a Interface Core, que define as operações e contratos principais da plataforma, pode permanecer estável e inalterada enquanto novas capacidades são adicionadas.
Redirecionamento das requisições do usuário para o conector:
~~~csharp
[FunctionName("BridgeToConnector")]
public async Task<IActionResult> BridgeToConnector(
[HttpTrigger(AuthorizationLevel.Function, new string[] { "get", "put", "post", "options", "patch", "head", "trace" }, Route = "v1.1/bridge/connector/uid/{connectorUid}/{*route}")] HttpRequest req,
Guid connectorUid,
string route,
ILogger log
)
{
var jwt = await req.JwtInfo().ConfigureAwait(false);
var userConnectorData = jwt.Model.ConnectorsData?.FirstOrDefault(p => p.SourceUid == connectorUid);
var config = await ConnectorService.Get(connectorUid, new ConnectorOptions { }).ConfigureAwait(false);
if (config == null) return new BadRequestObjectResult(new WrapperError { Message = "Conector não registrado para uso no Authorization." });
var connectorContext = await ConnectorContextData.Populate(config.Convert(), ServiceProvider, req).ConfigureAwait(false);
var spec = await SpecificationConnector.From<IConnectorDataServerRequestV2>(connectorContext);
if (spec == null) return new BadRequestObjectResult(new WrapperError { Message = "Conector não autorizado." });
IConnectorData connectorData = null;
if (userConnectorData != null)
{
connectorData = await new SdkApi().NewContext(DistributedCache).AddHeaders(await req.DefaultSystemHeaders()).Authorization.ConnectorDataInfo(userConnectorData.Uid).ConfigureAwait(false);
if (connectorData == null) return new BadRequestObjectResult(new WrapperError { Message = "Falha ao recuperar credencias de acesso." });
}
var headers = req.Headers;
var systemHeaders = await req.DefaultSystemHeaders().ConfigureAwait(false);
var automaticEvents = new AutomaticConnectorEvent(QueueMessage);
var context = await InsightsContext.From(req).ConfigureAwait(false);
context = context
.Populate(jwt.Model.Projects.Where(p => p.Connectors.FirstOrDefault(p => p.Uid == connectorUid) != null).FirstOrDefault())
.Populate(jwt.Model.Projects.SelectMany(p => p.Connectors).FirstOrDefault(p => p.Uid == connectorUid))
.Populate(jwt.Model)
.Populate(jwt.Model.Parent);
automaticEvents.Setup(context, spec);
foreach(var key in systemHeaders.Keys)
{
if (headers.ContainsKey(key)) headers.Remove(key);
headers[$"RDP-{key}"] = systemHeaders[key];
}
headers["RDP-UserUid"] = jwt.Model.Uid.ToString();
headers["RDP-Domain"] = Environment.GetEnvironmentVariable("ApiDomain");
if (req.Headers.ContainsKey("Content-Type")) headers["Content-Type"] = req.Headers["Content-Type"];
var requestInfo = new ServerRequestInfo
{
Service = route.Replace("%2F", "/"),
Method = req.Method,
Body = req.ContentLength > 0 ? req.Body : null,
Parameters = req.Query.Where(p=> p.Key != "code").ToDictionary(p => p.Key, p => (object)p.Value.FirstOrDefault()),
Headers = headers.ToDictionary(p => p.Key, p => (object)p.Value)
};
try
{
var response = await spec.Process(connectorData, requestInfo).ConfigureAwait(false);
if(response.StatusCode == System.Net.HttpStatusCode.Redirect)
{
return new RedirectResult(response.Headers.Location.ToString(), false);
}
if (response.IsSuccessStatusCode == false)
{
return new StatusCodeResult((int)response.StatusCode);
}
var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var mediaType = response?.Content?.Headers?.ContentType?.MediaType ?? "application/octet-stream";
if (response?.Content?.Headers != null)
{
var resultHeaders = req.HttpContext.Response.Headers;
foreach(var header in response.Content.Headers)
{
resultHeaders.Add(header.Key, new StringValues(header.Value.ToArray()));
}
}
await automaticEvents.Process().ConfigureAwait(false);
return new FileStreamResult(stream, mediaType);
}
catch(Exception e)
{
return new BadRequestObjectResult(new WrapperError { Message = e.Message });
}
}
~~~
Conector processa padrão estabelecido:
~~~csharp
public class ConnectorSunflower : IConnectorDataServerRequestV2
{
public async Task<HttpResponseMessage> Process(IConnectorData userData, ServerRequestInfo requestInfo)
{
HttpResponseMessage result = null;
switch(requestInfo.Service)
{
case "webhook":
{
// Fluxo personalizado.
}
default:
{
result = new HttpResponseMessage(System.Net.HttpStatusCode.NotFound);
break;
}
}
return result;
}
}
~~~
2. **Independência dos Serviços**:
- Como a plataforma consome conectores por meio de interfaces definidas, é indiferente ao serviço qual implementação específica está sendo utilizada. Por exemplo, um conector que implementa a interface `IConnectorGenerativeIA` pode usar diferentes serviços de IA (como OpenAI ou Gemini) sem exigir mudanças nos serviços principais da plataforma.
3. **Facilidade de Adaptação e Expansão**:
- Isso facilita a adaptação da plataforma a novas tecnologias ou requisitos do cliente, permitindo que diferentes conectores sejam desenvolvidos para diferentes clientes ou contextos de uso, sem a necessidade de mexer no núcleo da plataforma. A customização e evolução ocorrem de forma desacoplada, promovendo a escalabilidade e a longevidade da solução.
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
3. **Processo de Rollback**:
- <span style="color:red">O rollback de versões é possível via Azure DevOps, permitindo reverter rapidamente para uma versão anterior em caso de problemas. No entanto, as migrações de banco de dados podem ser um desafio, e é necessário um plano para lidar com essas situações.</span>
### **Gerenciamento de Equipes e Versões**
1. **Trabalho em Diferentes Versões**:
- Diferentes equipes podem trabalhar em diferentes versões dos conectores e serviços, graças ao versionamento semântico e à publicação de pacotes NuGet. Isso permite que novas funcionalidades sejam desenvolvidas em paralelo com a manutenção das versões estáveis em produção.
2. **Sincronização e Paridade de Ambientes**:
- A paridade entre os ambientes de desenvolvimento e produção é mantida para garantir que os testes realizados em desenvolvimento sejam representativos do ambiente de produção. Isso minimiza os riscos de introduzir bugs ou problemas de compatibilidade ao fazer deploys.
3. **Gerenciamento de Dependências e Compatibilidade**:
- <span style="color:red">A falta de um gerenciamento formal de dependências entre os conectores e serviços é um ponto de atenção. No entanto, o sistema utiliza o versionamento semântico e bloqueio de versões (lockfiles) para mitigar problemas de incompatibilidade. Há planos de implementar testes de compatibilidade automatizados para garantir que novas versões de conectores não causem problemas nos serviços existentes.</span>
### **Processo de Desenvolvimento e Integração**
1. **CI/CD com Azure DevOps**:
- A plataforma utiliza Azure DevOps para a integração contínua (CI) e entrega contínua (CD). Cada commit é automaticamente compilado, testado e, se aprovado, publicado em diferentes ambientes (desenvolvimento, staging e produção).
2. **Ambientes de Desenvolvimento e Produção**:
- <span style="color:red">Atualmente, a plataforma opera com ambientes de desenvolvimento e produção. A introdução de um ambiente de staging seria benéfica para testes mais completos antes de liberar as atualizações em produção.</span>
3. **Gestão de Hotfixes**:
- <span style="color:red">Hotfixes são tratados de acordo com a localização do erro:</span>
- <span style="color:red">**Erro no Serviço**: Um hotfix é aplicado diretamente no serviço, e o deploy é feito apenas para clientes na última versão ativa.</span>
- <span style="color:red">**Erro no Conector**: O conector é atualizado, seguido de um novo deploy do serviço com o fluxo corrigido.</span>