8 Commits
dev ... master

Author SHA1 Message Date
Ivan Paulovich
15ac2de003 docs 2018-03-23 19:10:45 -03:00
Ivan Paulovich
9334511f78 Unit Tests 2018-01-25 17:19:31 -02:00
Ivan Paulovich
4b5800e3fb Unit Tests 2018-01-25 14:38:28 -02:00
Ivan Paulovich
27c8075092 Docs 2017-12-27 09:22:32 -02:00
Ivan Paulovich
74b5c8e3a1 Set theme jekyll-theme-cayman 2017-12-22 11:15:21 -02:00
Ivan Paulovich
823a8c9d1b Consumer with pause/resume support 2017-12-21 17:46:59 -02:00
Ivan Paulovich
fc74b10766 Spliting projects 2017-12-21 15:59:03 -02:00
Ivan Paulovich
ffdc36dbd2 Merge pull request #4 from ivanpaulovich/dev
refactoring
2017-12-18 15:19:56 -02:00
163 changed files with 638 additions and 659 deletions

BIN
Producer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

142
README.md
View File

@@ -1,18 +1,20 @@
A solution for Blogging based on a Event-Driven architecture with DDD and CQRS. The full solution contains three applications.
* A Web API which receives Commands to produces Domain Events also receives Queries to return JSON.
* A Consumer App that reads the Event Stream and do a projection to a MongoDB database.
A solution based on a Event-Driven architecture with DDD and CQRS. The solution contains the following applications.
* A Producer Web API which receives Commands to produce Domain Events. This one also receives Queries and returns JSON.
* A Consumer Console App that reads the Event Stream and do a projection to a MongoDB database.
* A Web API for authentication and JWT generation.
[Checkout the Source Code on GitHub](https://github.com/ivanpaulovich/jambo)
#### Requirements
* [Visual Studio 2017 + Update 3](https://www.visualstudio.com/en-us/news/releasenotes/vs2017-relnotes)
* [.NET SDK 2.0](https://www.microsoft.com/net/download/core)
* [.NET CORE SDK 2.0](https://www.microsoft.com/net/download/core)
* [Docker](https://docs.docker.com/docker-for-windows/install/)
#### Environment setup
*If you already have valid connections strings for Kafka and MongoDB you could skip this topic and go to the Running the applications topic.*
*If you already have valid connections for Kafka and MongoDB you could skip this step and go to Running the applications step.*
* Run the `./up-kafka-mongodb.sh` script to run Kafka and MongoDB as Docker Containers. Please wait until the ~800mb download to be complete.
* Run the `./up-kafka-mongodb.sh` bash script to run Kafka and MongoDB as Docker Containers. Please wait until the ~800mb download to be complete.
```
$ ./up-kafka-mongodb.sh
@@ -45,88 +47,78 @@ CONTAINER ID IMAGE COMMAND CREATED
ba28cf144478 mongo "docker-entrypoint..." 2 days ago Up 2 days 0.0.0.0:27017->27017/tcp setup_mongodb_1
```
If Kafka is running good it will be working with the `10.0.75.1:9092` connection string and if MongoDB is running good it will be working at `mongodb://10.0.75.1:27017`.
If Kafka is running well it will be working with the `10.0.75.1:9092` connection string.
if MongoDB is running well it will be working at `mongodb://10.0.75.1:27017` connection string.
#### The Domain
![Domain](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Domain.png)
## Running the applications
#### As Aplicações desta Solução
* **Producer**: Web API que recebe os comandos de edição de conteúdo, produz Eventos de Domínio e publica as mensagens em um tópico no Kafka.
* **Consumer**: Aplicativo Console que consome as mensagens do Kafka, deserializa em Eventos de Domínio e aplica nas agregações persistindo no MongoDB o novo estado.
* **Auth**: Web API que gera tokens de autenticação para acesso ao WebAPI.
You have two options to run the applications, one is by opening with Visual Studio 2017 and the other is by running dotnet core commands.
#### Por onde começar?
Há duas formas de iniciar a solução.
### Option 1 - Running with Visual Studio 2017
##### 1. O jeito fácil
Open the three solutions on three Visual Studios them run the following projects.
Resolver os [pré-requisitos](https://github.com/ivanpaulovich/jambo/#prerequisitos), definir o projeto inicial como sendo o `docker-compose` e então apertar `Ctrl+F5` para executar todas as aplicações. Se tudo estiver correto, digite `docker ps` no seu terminal para verificar em quais portas cada aplicação está executando. Será algo assim:
* `Jambo.Auth.UI`.
* `Jambo.Consumer.UI`.
* `Jambo.Producer.UI`.
![Enviando comandos](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Docker-PS.PNG)
### Option 2 - Running with dotnet commands
A partir daí basta acessar:
* Auth em http://localhost:32775/swagger/
* Producer em http://localhost:32776/swagger/
#### How to run the Bearer Authencation API
Leia o [o jeito não tão fácil](https://github.com/ivanpaulovich/jambo/#2-o-jeito-não-tão-fácil) para entender como criar um Token no Auth API para consumir os serviços do Producer API via swagger.
1. Run the command `dotnet run` at `source\Auth\Jambo.Auth.UI` folder.
```
$ dotnet run
Using launch settings from C:\git\jambo\source\Auth\Jambo.Auth.UI\Properties\launchSettings.json...
Hosting environment: Development
Content root path: C:\git\jambo\source\Auth\Jambo.Auth.UI
Now listening on: http://localhost:16024
Application started. Press Ctrl+C to shut down.
```
2. Navigate to the Swagger UI at (eg. http://localhost:16024/swagger).
3. Post the following credentials:
```
{
"username": "ivanpaulovich",
"password": "mysecret"
}
```
4. __Store the Bearer Token__ because you will need the token value to log on Producer API.
```
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJhYzA4MmE3OS1lMWY3LTQ4MTktYmU1Mi1hOTQwMTBkM2VjZTciLCJzdWIiOiJzdHJpbmciLCJleHAiOjE1MTI0Nzg5ODgsImlzcyI6Imh0dHA6Ly9teWFjY291bnRhcGkiLCJhdWQiOiJodHRwOi8vbXlhY2NvdW50YXBpIn0.9YKGmKaptLBDcExHhPOQ3_j9TsdbkcRf8ZtvIkdq8Go",
"expiration": "2017-12-05T13:03:08Z"
}
```
#### How to run the Consumer API
##### 2. O jeito não tão fácil
1. Run the command `dotnet run` at `source\Consumer\Jambo.Consumer.UI` folder
A outra opção é inicializar aplicação por aplicação, seguindo o seguintes passos:
```
$ dotnet run
11/5/2017 11:17:20 AM Waiting for events..
11/5/2017 11:18:20 AM Waiting for events..
11/5/2017 11:19:20 AM Waiting for events..
11/5/2017 11:20:20 AM Waiting for events..
11/5/2017 11:21:20 AM Waiting for events..
11/5/2017 11:22:20 AM Waiting for events..
```
1. Execute o projeto **Jambo.Auth.WebAPI** e chame o método *Account/Token* com qualquer usuário e senha. *Guarde este token*.
3. __Attention:__ keep the Console App running for events processing.
![Auth](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Auth.PNG)
#### How to run the Producer API
![Auth com Token](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Auth1.PNG)
![Authorization](https://github.com/ivanpaulovich/jambo/blob/master/Producer.png)
3. Execute o projeto **Jambo.Producer.WebAPI** e clique no botão *Authorization* (topo direito da página).
1. Run the command `dotnet run` at the `source\Producer\Jambo.Producer.UI` folder.
Digite `bearer + valor_do_token` e clique em fechar. Algo assim:
![Autorizando](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Producer.PNG)
Chame os métodos para manutenção dos dados do Blog, Posts e Comentários.
![Enviando comandos](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Producer02.PNG)
```
$ dotnet run
Using launch settings from C:\git\jambo\source\Producer\Jambo.Producer.UI\Properties\launchSettings.json...
Hosting environment: Development
Content root path: C:\git\jambo\source\Producer\Jambo.Producer.UI
Now listening on: http://localhost:16959
Application started. Press Ctrl+C to shut down.
```
2. Execute o projeto **Jambo.Consumer.Console** e garante que ele **contínua em execução**.
![Comsumer em execução](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Consumer.PNG)
4. Visualize suas modificações
![Queries](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Producer03.PNG)
#### Demo
* **Auth API**: http://jambo.westus.cloudapp.azure.com:7070/swagger/.
* **Producer**: http://jambo.westus.cloudapp.azure.com:7080/swagger/.
* **Consumer**: Executa em background neste servidor.
#### Próximos passos?
1. Publicar os containers no Azure.
2. Criar um CI/CD para atualizar os containers a cada commit.
3. Criar testes de unidade, testes automatizados.
4. Consumir serviços externos.
5. Implementação alternativa de barramento: Azure Event Hubs
6. Implementação alternativa de snapshot: Azure Cosmos DB
7. Implementar um HealthCheck
#### Pré-requisitos
* [Visual Studio 2017 + Update 3](https://www.visualstudio.com/en-us/news/releasenotes/vs2017-relnotes)
* [.NET SDK 2.0](https://www.microsoft.com/net/download/core)
* [Docker](https://docs.docker.com/docker-for-windows/install/) (Opcional)
* [Robomongo](https://robomongo.org/) (Opcional)
#### Agradecimentos
Obrigado aos amigos que me estimularam a criar este projeto e estão sempre contribuindo e dando feedback.
* [Vinicius Baldotto](https://github.com/Baldotto)
* [André Paulovich](https://github.com/andrepaulovich)
* André Mendes
Obrigado de verdade!
#### Deixe o seu feedback
Agradeço todo comentário sobre o projeto. Envie suas dúvidas e sugestões no [Fórum](https://github.com/ivanpaulovich/jambo/issues).
#### Histórico de Versões
* 10/set/2017:
[![release](https://img.shields.io/github/release/ivanpaulovich/nixy.svg?style=flat-square)](https://github.com/ivanpaulovich/jambo/releases/latest)
2. Navigate to the Swagger UI (eg. http://localhost:14398/swagger).

View File

@@ -1,54 +0,0 @@
version: '2'
services:
jambo.auth.webapi:
image: ivanpaulovich/jambo:auth.webapi
links:
- "mongodb:mongo"
depends_on:
- mongodb
ports:
- 7070:80
jambo.producer.webapi:
image: ivanpaulovich/jambo:producer.webapi
ports:
- 7080:80
links:
- "mongodb:mongo"
depends_on:
- mongodb
- kafka
environment:
- ASPNETCORE_URLS=http://*:80
- ServiceBus__ConnectionString=kafka:9092
- ServiceBus__Topic=jambov32
- MongoDB__ConnectionString=mongodb://mongodb:27017
- MongoDB__Database=jambov32
jambo.consumer.console:
image: ivanpaulovich/jambo:consumer.console
links:
- "mongodb:mongo"
depends_on:
- mongodb
- kafka
environment:
- ASPNETCORE_URLS=http://*:80
- ServiceBus__ConnectionString=kafka:9092
- ServiceBus__Topic=jambov32
- MongoDB__ConnectionString=mongodb://mongodb:27017
- MongoDB__Database=jambov32
mongodb:
image: mongo
container_name: "mongodb"
ports:
- 27017:27017
kafka:
image: landoop/fast-data-dev:latest
ports:
- 2181:2181
- 3030:3030
- 8081-8083:8081-8083
- 9581-9585:9581-9585
- 9092:9092
environment:
- ADV_HOST=kafka

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -6,9 +6,10 @@ services:
ports:
- 27017:27017
kafka:
image: landoop/fast-data-dev:latest
image: spotify/kafka
ports:
- 3030:3030
- 2181:2181
- 9092:9092
environment:
- ADV_HOST=10.0.75.1
- ADVERTISED_HOST=10.0.75.1
- ADVERTISED_PORT=9092

View File

@@ -0,0 +1,17 @@
namespace Jambo.Auth.Application.Commands
{
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
[DataContract]
public class LoginCommand
{
[Required]
[DataMember]
public string Username { get; set; }
[Required]
[DataMember]
public string Password { get; set; }
}
}

View File

@@ -4,12 +4,6 @@
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Remove="CommandHandlers\**" />
<EmbeddedResource Remove="CommandHandlers\**" />
<None Remove="CommandHandlers\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Mediatr" Version="3.0.1" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.4.0" />

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;1705;NU1701;CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Jambo.Auth.Application\Jambo.Auth.Application.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,17 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using System.Text;
using Jambo.Auth.Application.Commands;
namespace Jambo.Auth.UI
namespace Jambo.Auth.UI
{
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using System.Text;
using Jambo.Auth.Application.Commands;
[Route("api/[controller]")]
public class AccountController : Controller
{
@@ -46,7 +44,7 @@ namespace Jambo.Auth.UI
config.Issuer,
config.Issuer,
GetTokenClaims(user),
expires: DateTime.UtcNow.AddDays(1),
expires: DateTime.UtcNow.AddDays(7),
signingCredentials: new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey)),
SecurityAlgorithms.HmacSha256)
@@ -57,9 +55,8 @@ namespace Jambo.Auth.UI
{
return new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Jti, user.UserId.ToString()),
new Claim(JwtRegisteredClaimNames.Sub, user.UserId.ToString()),
new Claim(ClaimTypes.GroupSid, user.SchoolId.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, user.Username),
new Claim(JwtRegisteredClaimNames.Sub, user.Password),
};
}
}

View File

@@ -3,4 +3,4 @@ ARG source
WORKDIR /app
EXPOSE 80
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", "Jambo.Auth.Infrastructure.dll"]
ENTRYPOINT ["dotnet", "Jambo.Auth.UI.dll"]

View File

@@ -4,6 +4,16 @@
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;1705;NU1701</NoWarn>
<DocumentationFile>bin\Debug\netcoreapp2.0\Jambo.Auth.UI.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<None Include="appsettings.Development.json" />
<None Include="appsettings.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
@@ -11,7 +21,6 @@
<ItemGroup>
<ProjectReference Include="..\Jambo.Auth.Application\Jambo.Auth.Application.csproj" />
<ProjectReference Include="..\Jambo.Auth.UI\Jambo.Auth.UI.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,15 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Jambo.Auth.Infrastructure
namespace Jambo.Auth.UI
{
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
public class Program
{
public static void Main(string[] args)

View File

@@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:16023/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Jambo.Auth.UI": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:16024/"
}
}
}

View File

@@ -1,10 +1,10 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Jambo.Auth.Infrastructure
namespace Jambo.Auth.UI
{
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public class Startup
{
public Startup(IConfiguration configuration)

View File

@@ -12,7 +12,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jambo.Domain\Jambo.Domain.csproj" />
<ProjectReference Include="..\..\Shared\Jambo.Domain\Jambo.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,11 +1,11 @@
using Jambo.Domain.Model;
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
namespace Jambo.Consumer.Infrastructure.DataAccess
namespace Jambo.Consumer.Infrastructure.DataAccess
{
using Jambo.Domain.Model;
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
public class MongoContext
{
private readonly MongoClient mongoClient;

View File

@@ -1,12 +1,11 @@
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Blogs
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Blogs
{
using Jambo.Domain.Model.Blogs;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
public class BlogReadOnlyRepository : IBlogReadOnlyRepository
{
private readonly MongoContext _mongoContext;

View File

@@ -1,10 +1,10 @@
using Jambo.Domain.Model.Blogs;
using MongoDB.Driver;
using System;
using System.Threading.Tasks;
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Blogs
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Blogs
{
using Jambo.Domain.Model.Blogs;
using MongoDB.Driver;
using System;
using System.Threading.Tasks;
public class BlogWriteOnlyRepository : IBlogWriteOnlyRepository
{
private readonly MongoContext mongoContext;

View File

@@ -1,12 +1,11 @@
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Posts
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Posts
{
using Jambo.Domain.Model.Posts;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
public class PostReadOnlyRepository : IPostReadOnlyRepository
{
private readonly MongoContext _mongoContext;

View File

@@ -1,11 +1,10 @@
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using MongoDB.Driver;
using System;
using System.Threading.Tasks;
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Posts
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Posts
{
using Jambo.Domain.Model.Posts;
using MongoDB.Driver;
using System;
using System.Threading.Tasks;
public class PostWriteOnlyRepository : IPostWriteOnlyRepository
{
private readonly MongoContext _mongoContext;

View File

@@ -27,11 +27,7 @@
<ItemGroup>
<ProjectReference Include="..\Jambo.Consumer.Application\Jambo.Consumer.Application.csproj" />
<ProjectReference Include="..\Jambo.Domain\Jambo.Domain.csproj" />
<ProjectReference Include="..\..\Shared\Jambo.Domain\Jambo.Domain.csproj" />
</ItemGroup>
<Target Name="CopyFiles" AfterTargets="build">
<Copy DestinationFolder="..\Jambo.Consumer.UI\bin\Debug\netcoreapp2.0" SourceFiles="$(OutputPath)\Jambo.Consumer.Infrastructure.dll" SkipUnchangedFiles="false" />
<Copy DestinationFolder="..\Jambo.Consumer.UI\bin\Debug\netcoreapp2.0" SourceFiles="$(OutputPath)\Jambo.Consumer.Infrastructure.pdb" SkipUnchangedFiles="false" />
</Target>
</Project>

View File

@@ -0,0 +1,38 @@
namespace Jambo.Consumer.Infrastructure.Modules
{
using Autofac;
using Autofac.Features.Variance;
using Jambo.Consumer.Application.DomainEventHandlers.Blogs;
using MediatR;
using System.Collections.Generic;
using System.Reflection;
public class MediatRModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterSource(new ContravariantRegistrationSource());
builder
.RegisterType<Mediator>()
.As<IMediator>()
.InstancePerLifetimeScope();
builder
.Register<SingleInstanceFactory>(ctx => {
var c = ctx.Resolve<IComponentContext>();
return t => { object o; return c.TryResolve(t, out o) ? o : null; };
})
.InstancePerLifetimeScope();
builder
.Register<MultiInstanceFactory>(ctx => {
var c = ctx.Resolve<IComponentContext>();
return t => (IEnumerable<object>)c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
})
.InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(typeof(BlogCreatedEventHandler).GetTypeInfo().Assembly).AsImplementedInterfaces(); // via assembly scan
}
}
}

View File

@@ -0,0 +1,121 @@
namespace Jambo.Consumer.Infrastructure.ServiceBus
{
using Confluent.Kafka;
using Confluent.Kafka.Serialization;
using Jambo.Domain.Exceptions;
using Jambo.Domain.Model;
using Jambo.Domain.ServiceBus;
using MediatR;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
public class Bus : ISubscriber
{
public readonly string brokerList;
public readonly string topic;
private static Dictionary<string, object> constructConfig(string brokerList, bool enableAutoCommit) =>
new Dictionary<string, object>
{
{ "group.id", "jambo-consumer" },
{ "enable.auto.commit", enableAutoCommit },
{ "auto.commit.interval.ms", 5000 },
{ "statistics.interval.ms", 60000 },
{ "bootstrap.servers", brokerList },
{ "default.topic.config", new Dictionary<string, object>()
{
{ "auto.offset.reset", "smallest" }
}
}
};
public Bus(string brokerList, string topic)
{
this.brokerList = brokerList;
this.topic = topic;
}
public void Listen(IMediator mediator)
{
using (var consumer = new Consumer<string, string>(constructConfig(brokerList, true), new StringDeserializer(Encoding.UTF8), new StringDeserializer(Encoding.UTF8)))
{
consumer.OnPartitionEOF += (_, end)
=> Console.WriteLine($"Reached end of topic {end.Topic} partition {end.Partition}, next message will be at offset {end.Offset}");
consumer.OnError += (_, error)
=> Console.WriteLine($"Error: {error}");
consumer.OnConsumeError += (_, msg)
=> Console.WriteLine($"Error consuming from topic/partition/offset {msg.Topic}/{msg.Partition}/{msg.Offset}: {msg.Error}");
consumer.OnOffsetsCommitted += (_, commit) =>
{
Console.WriteLine($"[{string.Join(", ", commit.Offsets)}]");
if (commit.Error)
{
Console.WriteLine($"Failed to commit offsets: {commit.Error}");
}
Console.WriteLine($"Successfully committed offsets: [{string.Join(", ", commit.Offsets)}]");
};
consumer.OnPartitionsAssigned += (_, partitions) =>
{
Console.WriteLine($"Assigned partitions: [{string.Join(", ", partitions)}], member id: {consumer.MemberId}");
consumer.Assign(partitions);
};
consumer.OnPartitionsRevoked += (_, partitions) =>
{
Console.WriteLine($"Revoked partitions: [{string.Join(", ", partitions)}]");
consumer.Unassign();
};
consumer.OnStatistics += (_, json)
=> Console.WriteLine($"Statistics: {json}");
consumer.Subscribe(this.topic);
Console.WriteLine($"Subscribed to: [{string.Join(", ", consumer.Subscription)}]");
var cancelled = false;
Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true; // prevent the process from terminating.
cancelled = true;
};
Console.WriteLine("Ctrl-C to exit.");
while (!cancelled)
{
Message<string, string> msg;
if (consumer.Consume(out msg, TimeSpan.FromSeconds(1)))
{
Console.WriteLine($"Topic: {msg.Topic} Partition: {msg.Partition} Offset: {msg.Offset} {msg.Value}");
try
{
Type eventType = Type.GetType(msg.Key);
DomainEvent domainEvent = (DomainEvent)JsonConvert.DeserializeObject(msg.Value, eventType);
mediator.Send(domainEvent).Wait();
}
catch (DomainException ex)
{
Console.WriteLine(ex.BusinessMessage);
}
catch (TransactionConflictException ex)
{
Console.WriteLine(ex.DomainEvent.ToString());
}
catch (JamboException ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
}
}
}

View File

@@ -28,7 +28,8 @@
<ItemGroup>
<ProjectReference Include="..\Jambo.Consumer.Application\Jambo.Consumer.Application.csproj" />
<ProjectReference Include="..\Jambo.Domain\Jambo.Domain.csproj" />
<ProjectReference Include="..\..\Shared\Jambo.Domain\Jambo.Domain.csproj" />
<ProjectReference Include="..\Jambo.Consumer.Infrastructure\Jambo.Consumer.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,19 +1,13 @@
namespace Jambo.Consumer.Infrastructure
{
using Autofac;
using Autofac.Configuration;
using Autofac.Extensions.DependencyInjection;
using Jambo.Domain.ServiceBus;
using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Reflection;
using System.Threading;
using System.IO;
using System.Linq;
using System.Runtime.Loader;
using Autofac.Configuration;
using Jambo.Consumer.Application.DomainEventHandlers.Blogs;
public class Startup
{
@@ -29,10 +23,6 @@
public IServiceProvider ConfigureServices(IServiceCollection services)
{
LoadInfrastructureAssemblies();
services.AddMediatR(typeof(BlogCreatedEventHandler).GetTypeInfo().Assembly);
ContainerBuilder builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterModule(new ConfigurationModule(Configuration));
@@ -42,28 +32,12 @@
return serviceProvider;
}
private void LoadInfrastructureAssemblies()
{
string[] fileNames = Directory.EnumerateFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.TopDirectoryOnly)
.Where(filePath => Path.GetFileName(filePath).StartsWith("Jambo.Consumer.Infrastructure", StringComparison.OrdinalIgnoreCase))
.ToArray();
foreach (string file in fileNames)
AssemblyLoadContext.Default.LoadFromAssemblyPath(file);
}
public void Run()
{
IMediator mediator = serviceProvider.GetService<IMediator>();
ISubscriber subscriber = serviceProvider.GetService<ISubscriber>();
subscriber.Listen(mediator);
while (true)
{
Console.WriteLine($"{DateTime.Now.ToString()} Waiting for events..");
Thread.Sleep(1000 * 60);
}
}
}
}

View File

@@ -14,6 +14,11 @@
"BrokerList": "10.0.75.1:9092",
"Topic": "jambov32"
}
},
{
"type": "Jambo.Consumer.Infrastructure.Modules.MediatRModule",
"properties": {
}
}
]
}

View File

@@ -3,15 +3,15 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jambo.Domain", "Jambo.Domain\Jambo.Domain.csproj", "{ED9F252F-10E5-4A65-ADA0-122D61D655A4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jambo.Consumer.Application", "Jambo.Consumer.Application\Jambo.Consumer.Application.csproj", "{601F90B1-4BE4-462E-8595-AAAA49B80405}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jambo.Domain.UnitTests", "Jambo.Domain.UnitTests\Jambo.Domain.UnitTests.csproj", "{B59AD81D-93F9-425F-8F87-DF13561BE424}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jambo.Consumer.Infrastructure", "Jambo.Consumer.Infrastructure\Jambo.Consumer.Infrastructure.csproj", "{645C9138-DA59-48C8-A15E-D720874DD148}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jambo.Consumer.UI", "Jambo.Consumer.UI\Jambo.Consumer.UI.csproj", "{E86878A7-CB0F-46A7-B918-BED5C45BA4C5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jambo.Consumer.UI", "Jambo.Consumer.UI\Jambo.Consumer.UI.csproj", "{E86878A7-CB0F-46A7-B918-BED5C45BA4C5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jambo.Domain", "..\Shared\Jambo.Domain\Jambo.Domain.csproj", "{B0475864-44AC-4A6A-9970-CC5EEA01C2CF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jambo.Domain.UnitTests", "..\Shared\Jambo.Domain.UnitTests\Jambo.Domain.UnitTests.csproj", "{04732413-3F55-4194-B406-2F84666B1F6C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -19,18 +19,10 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{ED9F252F-10E5-4A65-ADA0-122D61D655A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED9F252F-10E5-4A65-ADA0-122D61D655A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED9F252F-10E5-4A65-ADA0-122D61D655A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED9F252F-10E5-4A65-ADA0-122D61D655A4}.Release|Any CPU.Build.0 = Release|Any CPU
{601F90B1-4BE4-462E-8595-AAAA49B80405}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{601F90B1-4BE4-462E-8595-AAAA49B80405}.Debug|Any CPU.Build.0 = Debug|Any CPU
{601F90B1-4BE4-462E-8595-AAAA49B80405}.Release|Any CPU.ActiveCfg = Release|Any CPU
{601F90B1-4BE4-462E-8595-AAAA49B80405}.Release|Any CPU.Build.0 = Release|Any CPU
{B59AD81D-93F9-425F-8F87-DF13561BE424}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B59AD81D-93F9-425F-8F87-DF13561BE424}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B59AD81D-93F9-425F-8F87-DF13561BE424}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B59AD81D-93F9-425F-8F87-DF13561BE424}.Release|Any CPU.Build.0 = Release|Any CPU
{645C9138-DA59-48C8-A15E-D720874DD148}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{645C9138-DA59-48C8-A15E-D720874DD148}.Debug|Any CPU.Build.0 = Debug|Any CPU
{645C9138-DA59-48C8-A15E-D720874DD148}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -39,6 +31,14 @@ Global
{E86878A7-CB0F-46A7-B918-BED5C45BA4C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E86878A7-CB0F-46A7-B918-BED5C45BA4C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E86878A7-CB0F-46A7-B918-BED5C45BA4C5}.Release|Any CPU.Build.0 = Release|Any CPU
{B0475864-44AC-4A6A-9970-CC5EEA01C2CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0475864-44AC-4A6A-9970-CC5EEA01C2CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0475864-44AC-4A6A-9970-CC5EEA01C2CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0475864-44AC-4A6A-9970-CC5EEA01C2CF}.Release|Any CPU.Build.0 = Release|Any CPU
{04732413-3F55-4194-B406-2F84666B1F6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04732413-3F55-4194-B406-2F84666B1F6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04732413-3F55-4194-B406-2F84666B1F6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04732413-3F55-4194-B406-2F84666B1F6C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,26 +0,0 @@
using MediatR;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using System.Text;
namespace Jambo.Auth.Application.Commands
{
[DataContract]
public class LoginCommand : IRequest
{
[Required]
[DataMember]
public Guid UserId { get; private set; }
[Required]
[DataMember]
public Guid SchoolId { get; private set; }
public LoginCommand()
{
}
}
}

View File

@@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jambo.Auth.Application\Jambo.Auth.Application.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,24 +0,0 @@
namespace Jambo.Consumer.Infrastructure
{
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.IO;
class Program
{
public static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
IConfigurationRoot configuration = builder.Build();
IServiceCollection serviceCollection = new ServiceCollection();
Startup startup = new Startup(configuration);
startup.ConfigureServices(serviceCollection);
startup.Run();
}
}
}

View File

@@ -1,88 +0,0 @@
using Confluent.Kafka;
using Confluent.Kafka.Serialization;
using Jambo.Domain.Exceptions;
using Jambo.Domain.Model;
using Jambo.Domain.ServiceBus;
using MediatR;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Jambo.Consumer.Infrastructure.ServiceBus
{
public class Bus : ISubscriber
{
public readonly string brokerList;
public readonly string topic;
private readonly Consumer<string, string> consumer;
public Bus(string brokerList, string topic)
{
this.brokerList = brokerList;
this.topic = topic;
consumer = new Consumer<string, string>(
new Dictionary<string, object>()
{
{ "group.id", "consumer" },
{ "bootstrap.servers", brokerList }
}, new StringDeserializer(Encoding.UTF8), new StringDeserializer(Encoding.UTF8));
}
public void Listen(IMediator mediator)
{
Task.Run(() =>
{
consumer.Assign(new List<TopicPartitionOffset>
{
new TopicPartitionOffset(topic, 0, 0)
});
while (true)
{
Message<string, string> msg;
if (consumer.Consume(out msg, TimeSpan.FromSeconds(1)))
{
try
{
Type eventType = Type.GetType(msg.Key);
DomainEvent domainEvent = (DomainEvent)JsonConvert.DeserializeObject(msg.Value, eventType);
Console.WriteLine($"CorrelationId: {domainEvent.Header.CorrelationId}");
Console.WriteLine($"UserName: {domainEvent.Header.UserName}");
Console.WriteLine($"CreatedDate: {domainEvent.CreatedDate}");
Console.WriteLine($"Type: {msg.Key}");
Console.WriteLine($"AggregateRootId: {domainEvent.AggregateRootId}");
Console.WriteLine($"Version: {domainEvent.Version}");
Console.WriteLine($"Raw: {msg.Value}");
Console.WriteLine();
mediator.Send(domainEvent).Wait();
}
catch (DomainException ex)
{
Console.WriteLine(ex.BusinessMessage);
}
catch (TransactionConflictException ex)
{
Console.WriteLine(ex.AggregateRoot.ToString() + ex.DomainEvent.ToString());
}
catch (JamboException ex)
{
Console.WriteLine(ex.Message);
}
}
}
});
}
public void Dispose()
{
consumer.Dispose();
}
}
}

View File

@@ -1,59 +0,0 @@
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Jambo.Consumer.Application.DomainEventHandlers.Blogs;
using Jambo.Consumer.Infrastructure.Modules;
using Jambo.Domain.ServiceBus;
using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Reflection;
using System.Threading;
namespace Jambo.Consumer.Infrastructure
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
IServiceProvider serviceProvider;
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMediatR(typeof(BlogCreatedEventHandler).GetTypeInfo().Assembly);
ContainerBuilder container = new ContainerBuilder();
container.Populate(services);
container.RegisterModule(new ApplicationModule(
Configuration.GetSection("MongoDB").GetValue<string>("ConnectionString"),
Configuration.GetSection("MongoDB").GetValue<string>("Database")));
container.RegisterModule(new BusModule(
Configuration.GetSection("ServiceBus").GetValue<string>("ConnectionString"),
Configuration.GetSection("ServiceBus").GetValue<string>("Topic")));
serviceProvider = new AutofacServiceProvider(container.Build());
return serviceProvider;
}
public void Run()
{
IMediator mediator = serviceProvider.GetService<IMediator>();
ISubscriber subscriber = serviceProvider.GetService<ISubscriber>();
subscriber.Listen(mediator);
while (true)
{
Thread.Sleep(1000 * 60);
}
}
}
}

View File

@@ -1,14 +0,0 @@
using System;
using Xunit;
namespace Jambo.Domain.UnitTests
{
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}
}

View File

@@ -1,30 +0,0 @@
using System;
namespace Jambo.Domain.Model.Posts.Events
{
public class PostCreatedDomainEvent : DomainEvent
{
public Guid BlogId { get; private set; }
public int BlogVersion { get; private set; }
public PostCreatedDomainEvent(Guid aggregateRootId, int version,
DateTime createdDate, Header header, Guid blogId, int blogVersion)
: base(aggregateRootId, version, createdDate, header)
{
BlogId = blogId;
BlogVersion = blogVersion;
}
public static PostCreatedDomainEvent Create(AggregateRoot aggregateRoot,
Guid blogId, int blogVersion)
{
if (aggregateRoot == null)
throw new ArgumentNullException("aggregateRoot");
PostCreatedDomainEvent domainEvent = new PostCreatedDomainEvent(
aggregateRoot.Id, aggregateRoot.Version, DateTime.UtcNow, null, blogId, blogVersion);
return domainEvent;
}
}
}

View File

@@ -1,10 +0,0 @@
using MediatR;
using System;
namespace Jambo.Domain.ServiceBus
{
public interface ISubscriber : IDisposable
{
void Listen(IMediator mediator);
}
}

View File

@@ -9,7 +9,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jambo.Domain\Jambo.Domain.csproj" />
<ProjectReference Include="..\..\Shared\Jambo.Domain\Jambo.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jambo.Domain\Jambo.Domain.csproj" />
<ProjectReference Include="..\..\Shared\Jambo.Domain\Jambo.Domain.csproj" />
<ProjectReference Include="..\Jambo.Producer.Application\Jambo.Producer.Application.csproj" />
</ItemGroup>

Some files were not shown because too many files have changed in this diff Show More