DDD Sunflower

Quando se começa a estudar algum assunto, e principalmente quando falamos de nossa área de TI, o “estudo orientado a exemplos” é muito forte. Sempre buscamos qual é a maneira que foi feita, para vermos o passo a passo, comparamos com o que já sabemos e, se for o caso, adaptarmos o que já temos à esse novo conhecimento. Depois de algum tempo praticando, passamos a ter um maior conhecimento sobre esse novo assunto e passamos a arriscar modificações para customizar o procedimento aprendido à nossa realidade.

Acredito que esse cenário descrito seja familiar para todos, pois é um caminho natural de pesquisar e absorver conhecimento e agilizar aprendizado. Claro que buscar estudar, mesmo, sobre o conceito, e não apenas estudar baseado em exemplo, é fundamental, senão pode-se vira o que chamamos de Programador de Busca, pois só consegue gerar código se tiver um exemplo na web a seguir.

Desde que comecei minha reciclagem, em 2012, e parei com projetos ASP.NET WebForms e migrei para ASP.NET MVC, sempre senti que o que eu fazia não era o ideal. Funcionava, mas batia um feeling que dava para fazer melhor. Com isso eu sempre lia muitos artigos, via muitos vídeos no YouTube e fiz alguns cursos com personagens que são referências na área de desenvolvimento web e arquitetura de software com tecnologias Microsoft (aka MVP ASP.NET/IIS).

Recentemente já me sentindo mais à vontade com as novas tecnologias e patterns, resolvi usar o que aprendi até o momentos para criar um modelo de arquitetura para que outros desenvolvedores possam estudar e se basear para criar seus próprios projetos.

Implementando DDD, TDD, SOLIC, DI, IOC

Usei o famoso projeto MVC Musica Store, publicado pela própria Microsoft no GitHub, para implementar esses patterns. Assim, quando a Microsoft atualizar este modelo, eu vou, na medida do possível, atualizar esse meu modelo na mesma velocidade. Neste modelo, será implementado os seguintes patterns de arquitetura:

  • DDD – Domain Driven Design
  • TDD – Test Drive Design
  • SOLID – OOP Principles
  • DI – Dependency Injection
  • IOC – Inversion of Control
  • Cross Cutting
  • Self Validation Domain

Mais patterns serão implementados de acordo com o que eu for estudando ou de acordo com a colaboração da comunidade. Então, sinta-se convidado a implementar mais recursos. Neste projeto também faço uso das seguintes ferramentas:

  • ASP.NET MVC 5
  • Entity Framework 6
  • AutoMapper
  • Dapper
  • Service Locator
  • Ninject

Importante reforçar que não utilizo WebAPI, pois o projeto original também não utiliza, e a ideia inicial é de apenas implementar os patterns acima.

Links do Projeto

Colaboradores e Influenciadores

Cursos Recomendados

[mc4wp_form id="3070"]

55 Comments

Roberto Hermes · 20/05/2015 at 17:50

Interessante! Usou alguma ferramenta para geração automática das entidades (assim como para a geração de EqualityCompare, Specifications e Validations)?

Roberto Hermes · 20/05/2015 at 17:50

Interessante! Usou alguma ferramenta para geração automática das entidades (assim como para a geração de EqualityCompare, Specifications e Validations)?

Felipe · 22/05/2015 at 09:54

Fala Thiago, muito bom esse material, pra quem está começando como eu é sempre bom ter material assim para estudos. Só fiquei com uma dúvida, na verdade, não consegui fazer funcionar foi o ValidationResult, na camada Domain em Service.cs existe o método Add que retorna ValidationResult e esse método recebe uma entidade, existe a seguinte verificação

if (!ValidationResult.IsValid)
return ValidationResult;

Porém, esse ValidationResult é uma propriedade criada na própria classe e que no construtor receber new ValidationResult(), ou seja, sempre aqui vai ser verdadeiro e nunca vai entrar no return, sempre o IsValid vai vir como true.

Só que na entidade que está vindo por parâmetro o ValidationResult.IsValid é false, deveria validar esse ValidationResult da entidade, não a propriedade que sempre recebe New.

Não sei se consegui me expressar bem, mas é isso, mais uma vez ótimo material.

Abraço.

    Thiago Lunardi · 25/05/2015 at 12:56

    Olá Felipe!
    Vamos lá, o ValidationResult que vem da entidade, valida a Entidade. O ValidationResult do serviço de domínio irá validar os processos desse método.
    Ex:
    Na entrada esse método, vc pode validar a entidade (Entity.IsValid), se for válida, vc continua, senão retorna o ValidationResult da entidade – que irá transportar as mensagens de validação.
    Então, vc faz o que o método deve fazer – adicionar uns dados, atualiza outros, etc. Se der algum erro, vc adicionar a mensagem de erro na ValidationResult do método.
    Fique tranquilo que não é sempre que o ValidationResult.IsValid será true. Pois imagine se vc chama mais de um método dentro do mesmo serviço, exemplo: vc está movendo dados, então você cria uma informação usando médoto Add() e remove usando o Delete(), ok? Se ocorrer um problema no método Add() – dado duplicado, talvez – o Delete já pode validar se o procedimento que está sendo executado ainda é valido, senão ele nem executada nada e já retorna os erros já acumulados.
    Espero ter sido claro, senão responde aí de novo que tendo explicar de outra maneira. 🙂

      Wendel · 28/09/2015 at 10:06

      Thiago tudo bem?

      Estudando o código fonte tive a mesma dúvida que o Felipe. Entretanto após ler sua explicação fiz a leitura do código novamente, porém não encontrei em que momento a entidade é validada.

      Poderia dar maiores detalhes?
      Obrigado…

        Thiago Lunardi · 28/09/2015 at 10:39

        Putz, eu esqueci de consumir os validadores. E vc implementou do jeito que faço.
        Já atualizei no git, valeu.

Felipe · 22/05/2015 at 09:54

Fala Thiago, muito bom esse material, pra quem está começando como eu é sempre bom ter material assim para estudos. Só fiquei com uma dúvida, na verdade, não consegui fazer funcionar foi o ValidationResult, na camada Domain em Service.cs existe o método Add que retorna ValidationResult e esse método recebe uma entidade, existe a seguinte verificação

if (!ValidationResult.IsValid)
return ValidationResult;

Porém, esse ValidationResult é uma propriedade criada na própria classe e que no construtor receber new ValidationResult(), ou seja, sempre aqui vai ser verdadeiro e nunca vai entrar no return, sempre o IsValid vai vir como true.

Só que na entidade que está vindo por parâmetro o ValidationResult.IsValid é false, deveria validar esse ValidationResult da entidade, não a propriedade que sempre recebe New.

Não sei se consegui me expressar bem, mas é isso, mais uma vez ótimo material.

Abraço.

    Thiago Lunardi · 25/05/2015 at 12:56

    Olá Felipe!
    Vamos lá, o ValidationResult que vem da entidade, valida a Entidade. O ValidationResult do serviço de domínio irá validar os processos desse método.
    Ex:
    Na entrada esse método, vc pode validar a entidade (Entity.IsValid), se for válida, vc continua, senão retorna o ValidationResult da entidade – que irá transportar as mensagens de validação.
    Então, vc faz o que o método deve fazer – adicionar uns dados, atualiza outros, etc. Se der algum erro, vc adicionar a mensagem de erro na ValidationResult do método.
    Fique tranquilo que não é sempre que o ValidationResult.IsValid será true. Pois imagine se vc chama mais de um método dentro do mesmo serviço, exemplo: vc está movendo dados, então você cria uma informação usando médoto Add() e remove usando o Delete(), ok? Se ocorrer um problema no método Add() – dado duplicado, talvez – o Delete já pode validar se o procedimento que está sendo executado ainda é valido, senão ele nem executada nada e já retorna os erros já acumulados.
    Espero ter sido claro, senão responde aí de novo que tendo explicar de outra maneira. 🙂

      Wendel · 28/09/2015 at 10:06

      Thiago tudo bem?

      Estudando o código fonte tive a mesma dúvida que o Felipe. Entretanto após ler sua explicação fiz a leitura do código novamente, porém não encontrei em que momento a entidade é validada.

      Poderia dar maiores detalhes?
      Obrigado…

        Thiago Lunardi · 28/09/2015 at 10:39

        Putz, eu esqueci de consumir os validadores. E vc implementou do jeito que faço.
        Já atualizei no git, valeu.

Rodrigo · 20/06/2015 at 10:16

Bacana a implementação, reparei que usaram tudo muito atual, mas não usaram o asp.net identity ? Por que? Se usaram, aonde ficou as classes de manager do identity?

Parabéns pelo projeto, ótimo trabalho e um ótimo ponto de referencia sobre algumas técnicas.

PS: Não gosto de DDD com repository ;(, o próprio ef já implementa o pattern necessário, tirando isso, show de bola!

    Thiago Lunardi · 22/06/2015 at 08:45

    Olá, Rodrigo! Obrigado pelo feedback!

    Esse projeto eu peguei do sample da Microsoft, do codeplex ainda, e eles não tinham implementado Identity.

    Como está no GitHub, e se vc estiver interessado, você mesmo pode fazer a implementação, e isolamento do mesmo no CrossCutting, que eu aceito a alteração e já fica lá para outros poderem consumir.

    Quanto ao respository, é uma camada abstrata. Em projetos maiores, vc mantém a repository redireciona os dados para EF, ou outro ORM – NHibernate, Dapper -, ou arquivo disco (serial ou binário como imagens ou documentos), ou webservice, etc. Para sua camada de serviços (App ou Domain), tanto faz para eles onde os dados serão persistidos ou de onde eles veem. Ele apenas requisitam o repositório por recuperar ou armazenar informações, e o repositório quem destina onde vai.

    Se implementar EF direto, vc perde essa abstração. Eu mesmo trabalho com EF, Dapper, WebServices, FileServices e AD em um projeto. O acesso à esses caras ficam na camada de repositório. Os serviços fazem as mesmas requisições, independente de onde, ou por onde, as informações estão.

      Rodrigo · 12/01/2016 at 22:00

      Opa, nem tinha visto a resposta, quanto tempo kkkkk

      CrossCutting ? De onde surgiu essa nomeclatura? kkkk Usuário/Permissões, pra mim é domain também.

      O repository faz até sentido em alguns casos, só não fez sentido de você fazer 1 repository de dapper e outro de EF e ambos para o mesmo model, assim como você fez…faria de dapper se EF não desse conta do recado hehe, e no mesmo repository poderia ter EF/Dapper 😀

      No mais ficou bacana, só vale ressaltar, que há pessoas que acham que separar em N layers uma aplicação é DDD, os conceitos de DDD por exemplo se quiser aplicar, pode ser aplicado até em 2 Layer (web/library)…obviamente também podemos organizar melhor, dar melhor permissão e ter um ciclo de vida melhor…
      flw

        Thiago Lunardi · 13/01/2016 at 07:21

        Olá Rodrigo!

        CrossCutting é uma camada que “corre por fora” do projeto e não parte do fluxo de dados. Por exemplo, classes do injetor de dependencias, as classes do Identity, etc.

        A nomenclatura surgiu dos processos de produção de vídeos, vc pode achar mais no Wikipedia.

        O Repository serve para tornar a base de dados indiferente para o projeto. Vc define as interface e pluga o repositorio que quiser nele, seja EF ou outro ORM, ADO.NET, webservice, api, arquivo xml ou texto, etc.

Rodrigo · 20/06/2015 at 10:16

Bacana a implementação, reparei que usaram tudo muito atual, mas não usaram o asp.net identity ? Por que? Se usaram, aonde ficou as classes de manager do identity?

Parabéns pelo projeto, ótimo trabalho e um ótimo ponto de referencia sobre algumas técnicas.

PS: Não gosto de DDD com repository ;(, o próprio ef já implementa o pattern necessário, tirando isso, show de bola!

    Thiago Lunardi · 22/06/2015 at 08:45

    Olá, Rodrigo! Obrigado pelo feedback!

    Esse projeto eu peguei do sample da Microsoft, do codeplex ainda, e eles não tinham implementado Identity.

    Como está no GitHub, e se vc estiver interessado, você mesmo pode fazer a implementação, e isolamento do mesmo no CrossCutting, que eu aceito a alteração e já fica lá para outros poderem consumir.

    Quanto ao respository, é uma camada abstrata. Em projetos maiores, vc mantém a repository redireciona os dados para EF, ou outro ORM – NHibernate, Dapper -, ou arquivo disco (serial ou binário como imagens ou documentos), ou webservice, etc. Para sua camada de serviços (App ou Domain), tanto faz para eles onde os dados serão persistidos ou de onde eles veem. Ele apenas requisitam o repositório por recuperar ou armazenar informações, e o repositório quem destina onde vai.

    Se implementar EF direto, vc perde essa abstração. Eu mesmo trabalho com EF, Dapper, WebServices, FileServices e AD em um projeto. O acesso à esses caras ficam na camada de repositório. Os serviços fazem as mesmas requisições, independente de onde, ou por onde, as informações estão.

      Rodrigo · 12/01/2016 at 22:00

      Opa, nem tinha visto a resposta, quanto tempo kkkkk

      CrossCutting ? De onde surgiu essa nomeclatura? kkkk Usuário/Permissões, pra mim é domain também.

      O repository faz até sentido em alguns casos, só não fez sentido de você fazer 1 repository de dapper e outro de EF e ambos para o mesmo model, assim como você fez…faria de dapper se EF não desse conta do recado hehe, e no mesmo repository poderia ter EF/Dapper 😀

      No mais ficou bacana, só vale ressaltar, que há pessoas que acham que separar em N layers uma aplicação é DDD, os conceitos de DDD por exemplo se quiser aplicar, pode ser aplicado até em 2 Layer (web/library)…obviamente também podemos organizar melhor, dar melhor permissão e ter um ciclo de vida melhor…
      flw

        Thiago Lunardi · 13/01/2016 at 07:21

        Olá Rodrigo!

        CrossCutting é uma camada que “corre por fora” do projeto e não parte do fluxo de dados. Por exemplo, classes do injetor de dependencias, as classes do Identity, etc.

        A nomenclatura surgiu dos processos de produção de vídeos, vc pode achar mais no Wikipedia.

        O Repository serve para tornar a base de dados indiferente para o projeto. Vc define as interface e pluga o repositorio que quiser nele, seja EF ou outro ORM, ADO.NET, webservice, api, arquivo xml ou texto, etc.

Samuel Diogo · 08/07/2015 at 19:24

Tiago!! Muito obrigado cara! muito bom!! Sei nem o que dizer.. hahahaha, obrigado mesmo!

Erick Rezende · 16/07/2015 at 21:05

Muito bom Thiago! Parabéns!

Erick Rezende · 16/07/2015 at 21:05

Muito bom Thiago! Parabéns!

Júnior Pacheco · 17/07/2015 at 22:35

Vixi agora a parada ficou séria! É estudar e pronto manão! Valews demais!

Júnior Pacheco · 17/07/2015 at 22:35

Vixi agora a parada ficou séria! É estudar e pronto manão! Valews demais!

Roberto Hermes · 23/07/2015 at 15:32

Oi Thiago, tava dando uma estudada no projeto e fiquei com a seguinte dúvida: a conexão com banco de dados se dá através da classe MusicStoreContext, que por sua vez, faz referência a sua respectiva chave no Web.Config. Se, por acaso, for necessário acessar também tabelas de outra base de dados, criaria mais uma classe (por exemplo “OutroBdContext”) e sua respectiva chave no Web.Config. Até esse ponto, ok. Mas como fazer a classe Repository diferenciar a classe de contexto, ou seja, saber qual BD acessar?

    Thiago Lunardi · 23/07/2015 at 17:58

    Olá Roberto, obrigado pelo contato!
    Quem define onde seu repositória irá persistir os dados é a sua camada de Aplicação. Nela que vc irá fazer esse binding entre contexto e repositório. Vide AlbumAppAplication em https://github.com/thiagolunardi/MvcMusicStoreDDD/blob/master/MvcMusicStore.Application/AlbumAppService.cs
    Se vc tiver outro contexto para conectar, quando for criar o AppService dele, basta passar sob qual contexto essa aplicação irá persistir.

      Roberto Hermes · 23/07/2015 at 18:19

      Olá Thiago, obrigado pela resposta.
      Eu faço exatamente da forma que você mencionou, mas funciona até certo ponto. Na classe ContextManager, o método GetContext verifica que já há um contexto instanciado (por exemplo, o MusicStoreContext), mas não instancia um segundo contexto. E eu preciso ter dois contextos, pois tenho que acessar dois BDs distintos.

Roberto Hermes · 23/07/2015 at 15:32

Oi Thiago, tava dando uma estudada no projeto e fiquei com a seguinte dúvida: a conexão com banco de dados se dá através da classe MusicStoreContext, que por sua vez, faz referência a sua respectiva chave no Web.Config. Se, por acaso, for necessário acessar também tabelas de outra base de dados, criaria mais uma classe (por exemplo “OutroBdContext”) e sua respectiva chave no Web.Config. Até esse ponto, ok. Mas como fazer a classe Repository diferenciar a classe de contexto, ou seja, saber qual BD acessar?

    Thiago Lunardi · 23/07/2015 at 17:58

    Olá Roberto, obrigado pelo contato!
    Quem define onde seu repositória irá persistir os dados é a sua camada de Aplicação. Nela que vc irá fazer esse binding entre contexto e repositório. Vide AlbumAppAplication em https://github.com/thiagolunardi/MvcMusicStoreDDD/blob/master/MvcMusicStore.Application/AlbumAppService.cs
    Se vc tiver outro contexto para conectar, quando for criar o AppService dele, basta passar sob qual contexto essa aplicação irá persistir.

      Roberto Hermes · 23/07/2015 at 18:19

      Olá Thiago, obrigado pela resposta.
      Eu faço exatamente da forma que você mencionou, mas funciona até certo ponto. Na classe ContextManager, o método GetContext verifica que já há um contexto instanciado (por exemplo, o MusicStoreContext), mas não instancia um segundo contexto. E eu preciso ter dois contextos, pois tenho que acessar dois BDs distintos.

Roberto Hermes · 07/08/2015 at 09:54

Oi Thiago, tudo bem?
Dê uma olhada nas classes de mapeamento Domain/ViewModel, pois estão com os nomes trocados:
– DomainToViewModelMappingProfile.cs está com o nome de classe ViewModelToDomainMappingProfile
– ViewModelToDomainMappingProfile.cs está com o nome de classe DomainToViewModelMappingProfile

    Thiago Lunardi · 10/08/2015 at 09:09

    Hehehe… já me peguei fazendo isso outras vezes. Vou corrigir no código, obrigado pelo aviso.

Roberto Hermes · 07/08/2015 at 09:54

Oi Thiago, tudo bem?
Dê uma olhada nas classes de mapeamento Domain/ViewModel, pois estão com os nomes trocados:
– DomainToViewModelMappingProfile.cs está com o nome de classe ViewModelToDomainMappingProfile
– ViewModelToDomainMappingProfile.cs está com o nome de classe DomainToViewModelMappingProfile

    Thiago Lunardi · 10/08/2015 at 09:09

    Hehehe… já me peguei fazendo isso outras vezes. Vou corrigir no código, obrigado pelo aviso.

geovani · 25/09/2015 at 13:20

Olá. Parabéns pelo projeto.

Tenho uma dúvida referente ao método abaixo, na classe UnitOfWork:

public void BeginTransaction()
{
_disposed = false;
}

Isso realmente cria uma transação? Se sim, por favor, poderia explicar como?

Geovani

    Thiago Lunardi · 25/09/2015 at 13:43

    Valeu Geovani!
    De forma alguma apenas isso irá criar um controle transacional, aí é apenas um ponto para se iniciar uma implementação.
    Do jeito que está, a transação se dá na abertura do contexto, e os commits vão pontuando.
    Isso é apenas para indicar para o GC: cara, não remove isso não que eu to usando, blz?
    Se quiser fazer uma implementação, forka o projeto, implementar e me faz um pull request. Eu aceito na boa e vamos evoluindo o projeto para outros devs consumirem.

geovani · 25/09/2015 at 13:20

Olá. Parabéns pelo projeto.

Tenho uma dúvida referente ao método abaixo, na classe UnitOfWork:

public void BeginTransaction()
{
_disposed = false;
}

Isso realmente cria uma transação? Se sim, por favor, poderia explicar como?

Geovani

    Thiago Lunardi · 25/09/2015 at 13:43

    Valeu Geovani!
    De forma alguma apenas isso irá criar um controle transacional, aí é apenas um ponto para se iniciar uma implementação.
    Do jeito que está, a transação se dá na abertura do contexto, e os commits vão pontuando.
    Isso é apenas para indicar para o GC: cara, não remove isso não que eu to usando, blz?
    Se quiser fazer uma implementação, forka o projeto, implementar e me faz um pull request. Eu aceito na boa e vamos evoluindo o projeto para outros devs consumirem.

Marcos Dallagnelo · 28/09/2015 at 02:08

Olá, muito bom o projeto, parabéns.
Utilizo nhibernate, quero fazer a implementação nesse projeto, porém me confunde um pouco essa camada de contexto. No caso teria uma camada session e implemento os repostiórios?

    Thiago Lunardi · 28/09/2015 at 10:17

    Olá Marcos!
    Confesso que nunca trabalhei com NHibernate, então não conheço sua arquitetura. Mas como é um ORM, não deve fugir muito de como o EF funciona.
    Vc terá que substituir o MusicStoreContext para poder consumir NHibernate, e sua configurações. Mas o importante é que é apenas nesta camada que vc irá mexer.

    Thiago Lunardi · 28/09/2015 at 10:34

    Ah, e sobre Session, dá um lida nesse artigo do Eduardo Pires.
    http://eduardopires.net.br/2015/04/pense-duas-vezes-antes-de-utilizar-sessions/

      Marcos Dallagnelo · 28/09/2015 at 11:17

      Thiago, a session que comentei seria do NHibernate, comparando com o EF é o mesmo que DbContext, estou estudando para implementar.
      Sobre a aplication layer definir onde o repositório persiste, gostaria de me aprofundar no assunto, o que você indica como estudo? Sempre deixei isso para o repositório fazer, sem as camadas de cima se envolver.

        Thiago Lunardi · 28/09/2015 at 12:09

        Sua camada de aplicação precisa passar sob qual contexto ela está trabalhando para o ioc. É a aplicação que informa o ioc para fazer commit ou não. E o ioc depende de um contexto para confirmar ou não alterações.

Marcos Dallagnelo · 28/09/2015 at 02:08

Olá, muito bom o projeto, parabéns.
Utilizo nhibernate, quero fazer a implementação nesse projeto, porém me confunde um pouco essa camada de contexto. No caso teria uma camada session e implemento os repostiórios?

    Thiago Lunardi · 28/09/2015 at 10:17

    Olá Marcos!
    Confesso que nunca trabalhei com NHibernate, então não conheço sua arquitetura. Mas como é um ORM, não deve fugir muito de como o EF funciona.
    Vc terá que substituir o MusicStoreContext para poder consumir NHibernate, e sua configurações. Mas o importante é que é apenas nesta camada que vc irá mexer.

    Thiago Lunardi · 28/09/2015 at 10:34

    Ah, e sobre Session, dá um lida nesse artigo do Eduardo Pires.
    http://eduardopires.net.br/2015/04/pense-duas-vezes-antes-de-utilizar-sessions/

      Marcos Dallagnelo · 28/09/2015 at 11:17

      Thiago, a session que comentei seria do NHibernate, comparando com o EF é o mesmo que DbContext, estou estudando para implementar.
      Sobre a aplication layer definir onde o repositório persiste, gostaria de me aprofundar no assunto, o que você indica como estudo? Sempre deixei isso para o repositório fazer, sem as camadas de cima se envolver.

        Thiago Lunardi · 28/09/2015 at 12:09

        Sua camada de aplicação precisa passar sob qual contexto ela está trabalhando para o ioc. É a aplicação que informa o ioc para fazer commit ou não. E o ioc depende de um contexto para confirmar ou não alterações.

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts

ASP.NET

ASP.NET Core WebApi sem MVC

Ja precisou criar, no ASP.NET Core, um projeto WebAPI e percebeu que vem o incoveniente do Razor? Veja aqui como criar um projeto ASP.NET Core WebApi sem MVC, apenas com controllers, e nada mais. Por Read more…

ASP.NET

Publicando site ASP.NET no Windows Azure via Visual Studio Online

Integração e agilidade são as palavres da vez no meio de desenvolvedores de software. Ainda mais se estivermos falando de serviço, e não mais de software de prateleira. Isso porquê esse conceito de software de Read more…

ASP.NET

ASP.NET Webservice retornando JSON

O ASP.NET Webservice é uma excelente solução para se criar comunicação entre sistemas de diferentes plataformas. Com ele torna-se possível enviar e receber dados utilizando uma linguagem universal, o XML. Porém o XML vem sendo Read more…