47 Commits
v1.1 ... dev

Author SHA1 Message Date
Ivan Paulovich
81d8a76256 ok 2017-12-18 15:16:09 -02:00
Ivan Paulovich
3a414dbf79 refactoring 2017-12-18 15:00:24 -02:00
Ivan Paulovich
19b1c4295e ok 2017-11-10 09:24:18 -02:00
Ivan Paulovich
cd9bb683c8 ok 2017-11-10 09:23:45 -02:00
Ivan Paulovich
8a58fa6d34 tests 2017-11-03 23:23:57 -02:00
Ivan Paulovich
af0f787f6a Consumer App changed to console (bug fix) 2017-10-12 16:04:57 -03:00
Ivan Paulovich
ce55972d64 SRP 2017-10-10 17:23:40 -03:00
Ivan Paulovich
6d3db24188 CQRS 2017-10-10 17:18:23 -03:00
Ivan Paulovich
63b81a62e4 palestras antigas 2017-10-10 09:05:53 -03:00
Ivan Paulovich
16bc48a491 Merge pull request #2 from ivanpaulovich/dev
Complete refactoring to DIP architecture style
2017-10-06 15:40:45 -03:00
Ivan Paulovich
be322e581b full refactoring 2017-10-06 15:39:16 -03:00
Ivan Paulovich
884c3a6f34 refactoring 2017-10-06 13:06:00 -03:00
Ivan Paulovich
261c037f27 refactoring 2017-10-06 11:11:07 -03:00
Ivan Paulovich
457285e194 refactoring 2017-10-06 11:10:22 -03:00
Ivan Paulovich
cb274449a8 refactoring 2017-10-06 10:34:41 -03:00
Ivan Paulovich
9628a4f60b refactoring 2017-10-05 17:20:07 -03:00
Ivan Paulovich
dc690a49f0 refactoring 2017-10-05 17:08:45 -03:00
Ivan Paulovich
efaeedce3f DIP 2017-10-05 16:44:58 -03:00
Ivan Paulovich
9231f5a9a6 annotations 2017-10-02 17:14:26 -03:00
Ivan Paulovich
d25db6a360 data annotations 2017-10-02 17:04:10 -03:00
Ivan Paulovich
472185f752 Merge branch 'master' of https://github.com/ivanpaulovich/jambo 2017-10-02 14:26:50 -03:00
Ivan Paulovich
92287837ec workshop 2017-10-02 14:26:35 -03:00
Ivan Paulovich
fe2d38cd12 Set theme jekyll-theme-cayman 2017-09-27 11:22:04 -03:00
Ivan Paulovich
be6b993d03 Corrigindo erro de compilacao do ServiceBus.Kafka. Tks José! 2017-09-26 21:06:26 -03:00
Ivan Paulovich
c5551d84fa apresentacao 2017-09-25 10:39:32 -03:00
Ivan Paulovich
c3c5e3a729 apresentação final 2017-09-23 11:53:08 -03:00
Ivan Paulovich
86777211a4 apresentacao 2017-09-22 16:39:26 -03:00
Ivan Paulovich
a3c98f8bd7 apresentacao 2017-09-22 16:39:06 -03:00
Ivan Paulovich
fcbd1a7294 cores 2017-09-20 21:13:08 -03:00
Ivan Paulovich
92570ff64d compose 2017-09-20 14:05:46 -03:00
Ivan Paulovich
4bf8d9d583 ES 2017-09-20 11:16:42 -03:00
Ivan Paulovich
4a353a8baf principios 2017-09-20 11:08:48 -03:00
Ivan Paulovich
3271a92164 ok 2017-09-20 09:06:28 -03:00
Ivan Paulovich
d721e13108 event sourcing 2017-09-20 09:02:58 -03:00
Ivan Paulovich
46db1f45ac event sourcing 2017-09-19 22:13:31 -03:00
Ivan Paulovich
5ef48012fb event sourcing 2017-09-19 17:45:16 -03:00
Ivan Paulovich
1241a58f3a event sourcing 2017-09-19 17:23:15 -03:00
Ivan Paulovich
5a10794876 ok 2017-09-18 16:50:22 -03:00
Ivan Paulovich
a003da0240 ok 2017-09-18 16:41:14 -03:00
Ivan Paulovich
b21559bd0d Merge branch 'master' of https://github.com/ivanpaulovich/jambo 2017-09-18 15:57:43 -03:00
Ivan Paulovich
f99dcce2e0 ES 2017-09-18 15:57:27 -03:00
Ivan Paulovich
ed3afddf21 ok 2017-09-12 16:01:09 -03:00
Ivan Paulovich
847c3ee696 Ok 2017-09-11 10:06:13 -03:00
Ivan Paulovich
7175f13b21 ok 2017-09-10 21:28:24 -03:00
Ivan Paulovich
644bcf0294 ok 2017-09-10 16:16:25 -03:00
Ivan Paulovich
d409c29c73 ok 2017-09-10 16:02:48 -03:00
Ivan Paulovich
2b691e61bf ok 2017-09-10 15:59:51 -03:00
516 changed files with 1178 additions and 50563 deletions

View File

@@ -1,7 +1,54 @@
Neste projeto experimental em Domain-Driven-Design com implementação de Aggregates + Event Sourcing + CQRS + Optimistic Concurrency a técnica de Event Sourcing é usada como ferramenta de auditoria das modificações do conteúdo do Blog. A fonte dos dados são os Domain Events registrados no Kafka, para permitir consultas de alta performance, foi criado um banco de dados MongoDB com o 'último estado já processado dos eventos'. Há um microsserviço auxiliar de autenticação. Tudo isso foi implementado em .NET Core/Standard compatível com Docker! Divirta-se!
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 Web API for authentication and JWT generation.
#### O Domínio
![Domain](https://github.com/ivanpaulovich/jambo/blob/master/images/Domain.png)
#### 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)
* [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.*
* Run the `./up-kafka-mongodb.sh` script to run Kafka and MongoDB as Docker Containers. Please wait until the ~800mb download to be complete.
```
$ ./up-kafka-mongodb.sh
Pulling mongodb (mongo:latest)...
latest: Pulling from library/mongo
Digest: sha256:2c55bcc870c269771aeade05fc3dd3657800540e0a48755876a1dc70db1e76d9
Status: Downloaded newer image for mongo:latest
Pulling kafka (spotify/kafka:latest)...
latest: Pulling from spotify/kafka
Digest: sha256:cf8f8f760b48a07fb99df24fab8201ec8b647634751e842b67103a25a388981b
Status: Downloaded newer image for spotify/kafka:latest
Creating setup_mongodb_1 ...
Creating setup_kafka_1 ...
Creating setup_mongodb_1
Creating setup_mongodb_1 ... done
```
* Check if the data layer is ready with the following commands:
```
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mongo latest d22888af0ce0 17 hours ago 361MB
spotify/kafka latest a9e0a5b8b15e 11 months ago 443MB
```
```
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
32452776153f spotify/kafka "supervisord -n" 2 days ago Up 2 days 0.0.0.0:2181->2181/tcp, 0.0.0.0:9092->9092/tcp setup_kafka_1
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`.
#### The Domain
![Domain](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Domain.png)
#### 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.
@@ -15,7 +62,7 @@ Há duas formas de iniciar a solução.
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:
![Enviando comandos](https://github.com/ivanpaulovich/jambo/blob/master/images/Docker-PS.PNG)
![Enviando comandos](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Docker-PS.PNG)
A partir daí basta acessar:
* Auth em http://localhost:32775/swagger/
@@ -28,24 +75,30 @@ Leia o [o jeito não tão fácil](https://github.com/ivanpaulovich/jambo/#2-o-je
A outra opção é inicializar aplicação por aplicação, seguindo o seguintes passos:
1. Execute o projeto **Jambo.Auth.WebAPI** e chame o método *Account/Token* com qualquer usuário e senha. *Guarde este token*.
![Auth](https://github.com/ivanpaulovich/jambo/blob/master/images/Auth.PNG)
![Auth com Token](https://github.com/ivanpaulovich/jambo/blob/master/images/Auth1.PNG)
![Auth](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Auth.PNG)
![Auth com Token](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Auth1.PNG)
3. Execute o projeto **Jambo.Producer.WebAPI** e clique no botão *Authorization* (topo direito da página).
Digite `bearer + valor_do_token` e clique em fechar. Algo assim:
![Autorizando](https://github.com/ivanpaulovich/jambo/blob/master/images/Producer.PNG)
![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/images/Producer02.PNG)
![Enviando comandos](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Producer02.PNG)
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/images/Consumer.PNG)
![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/images/Producer03.PNG)
![Queries](https://github.com/ivanpaulovich/jambo/blob/master/docs/images/Producer03.PNG)
#### Demo
*Em breve...*
* **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.
@@ -54,6 +107,7 @@ Chame os métodos para manutenção dos dados do Blog, Posts e Comentários.
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

1
docker-demo/demo-down.sh Normal file
View File

@@ -0,0 +1 @@
docker-compose down

1
docker-demo/demo-up.sh Normal file
View File

@@ -0,0 +1 @@
docker-compose up -d

Binary file not shown.

Binary file not shown.

1
docs/_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -1,14 +0,0 @@
using Jambo.Domain.ServiceBus;
using MediatR;
using System;
using System.Collections.Generic;
using System.Text;
namespace Jambo.Application.DomainEventHandlers
{
public interface IEvent<T> : IRequest<T>
where T : DomainEvent
{
}
}

View File

@@ -12,11 +12,11 @@ namespace Jambo.Auth.Application.Commands
{
[Required]
[DataMember]
public string Username { get; private set; }
public Guid UserId { get; private set; }
[Required]
[DataMember]
public string Password { get; private set; }
public Guid SchoolId { get; private set; }
public LoginCommand()
{

View File

@@ -4,13 +4,15 @@
<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" />
</ItemGroup>
<ItemGroup>
<Folder Include="CommandHandlers\" />
</ItemGroup>
</Project>

View File

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

View File

@@ -2,24 +2,16 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jambo.Auth.Application\Jambo.Auth.Application.csproj" />
<ProjectReference Include="..\Jambo.Auth.UI\Jambo.Auth.UI.csproj" />
</ItemGroup>
</Project>

View File

@@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Jambo.Producer.WebAPI
namespace Jambo.Auth.Infrastructure
{
public class Program
{

View File

@@ -3,7 +3,7 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8546/",
"applicationUrl": "http://localhost:15877/",
"sslPort": 0
}
},
@@ -16,14 +16,14 @@
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Jambo.Auth.WebAPI": {
"Jambo.Auth.Infrastructure": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/values",
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:8547/"
"applicationUrl": "http://localhost:15878/"
}
}
}
}

View File

@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Jambo.Auth.Infrastructure
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "Auth API",
Version = "v1",
Description = "The Auth API",
TermsOfService = "Terms Of Service"
});
});
services.AddScoped(
s => new UI.Config(Configuration.GetSection("Security").GetValue<string>("SecretKey"),
Configuration.GetSection("Security").GetValue<string>("Issuer")));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
app.UseSwagger()
.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
}
}

View File

@@ -1,8 +0,0 @@
using System;
namespace Jambo.Auth.IoC
{
public class Class1
{
}
}

View File

@@ -1,7 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Jambo.Auth.Application.Auth
namespace Jambo.Auth.UI
{
public class Config
{

View File

@@ -8,10 +8,9 @@ using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using System.Text;
using Jambo.Auth.Application.Auth;
using Jambo.Auth.Application.Commands;
namespace Jambo.Auth.WebAPI.Controllers
namespace Jambo.Auth.UI
{
[Route("api/[controller]")]
public class AccountController : Controller
@@ -27,7 +26,12 @@ namespace Jambo.Auth.WebAPI.Controllers
[HttpPost]
public IActionResult Token([FromBody] LoginCommand loginCommand)
{
var token = GetJwtSecurityToken(loginCommand.Username);
//
// TODO: Implementar uma verificação
// se loginCommand.Username e loginCommand.Password são válidos.
//
var token = GetJwtSecurityToken(loginCommand);
return Ok(new
{
@@ -36,25 +40,26 @@ namespace Jambo.Auth.WebAPI.Controllers
});
}
private JwtSecurityToken GetJwtSecurityToken(string user)
private JwtSecurityToken GetJwtSecurityToken(LoginCommand user)
{
return new JwtSecurityToken(
config.Issuer,
config.Issuer,
GetTokenClaims(user),
expires: DateTime.UtcNow.AddMinutes(30),
expires: DateTime.UtcNow.AddDays(1),
signingCredentials: new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey)),
SecurityAlgorithms.HmacSha256)
);
}
private static IEnumerable<Claim> GetTokenClaims(string user)
private static IEnumerable<Claim> GetTokenClaims(LoginCommand user)
{
return new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Sub, user)
new Claim(JwtRegisteredClaimNames.Jti, user.UserId.ToString()),
new Claim(JwtRegisteredClaimNames.Sub, user.UserId.ToString()),
new Claim(ClaimTypes.GroupSid, user.SchoolId.ToString()),
};
}
}

View File

@@ -0,0 +1,16 @@
<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,25 +0,0 @@
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.WebAPI
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}

View File

@@ -1,81 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Jambo.Auth.Application.Auth;
namespace Jambo.Auth.WebAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "Auth API",
Version = "v1",
Description = "The Auth API",
TermsOfService = "Terms Of Service"
});
});
services.AddScoped(
s => new Config(Configuration.GetSection("Security").GetValue<string>("SecretKey"),
Configuration.GetSection("Security").GetValue<string>("Issuer")));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration.GetSection("Security").GetValue<string>("Issuer"),
ValidAudience = Configuration.GetSection("Security").GetValue<string>("Issuer"),
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(
Configuration.GetSection("Security").GetValue<string>("SecretKey")))
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
app.UseSwagger()
.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
}
}

37
source/Jambo.Auth.sln Normal file
View File

@@ -0,0 +1,37 @@

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.Auth.Application", "Jambo.Auth.Application\Jambo.Auth.Application.csproj", "{CADA8CE3-F53F-46FE-80B1-F898AEC640E1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jambo.Auth.Infrastructure", "Jambo.Auth.Infrastructure\Jambo.Auth.Infrastructure.csproj", "{D91971DB-5A62-4BB5-B310-6BF4E9C8596F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jambo.Auth.UI", "Jambo.Auth.UI\Jambo.Auth.UI.csproj", "{4BECC370-4749-48C2-82FF-47ADE3BE0FAA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CADA8CE3-F53F-46FE-80B1-F898AEC640E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CADA8CE3-F53F-46FE-80B1-F898AEC640E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CADA8CE3-F53F-46FE-80B1-F898AEC640E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CADA8CE3-F53F-46FE-80B1-F898AEC640E1}.Release|Any CPU.Build.0 = Release|Any CPU
{D91971DB-5A62-4BB5-B310-6BF4E9C8596F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D91971DB-5A62-4BB5-B310-6BF4E9C8596F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D91971DB-5A62-4BB5-B310-6BF4E9C8596F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D91971DB-5A62-4BB5-B310-6BF4E9C8596F}.Release|Any CPU.Build.0 = Release|Any CPU
{4BECC370-4749-48C2-82FF-47ADE3BE0FAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4BECC370-4749-48C2-82FF-47ADE3BE0FAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4BECC370-4749-48C2-82FF-47ADE3BE0FAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4BECC370-4749-48C2-82FF-47ADE3BE0FAA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {54AE4BA0-6081-4892-A9B9-3F8D15D994C8}
EndGlobalSection
EndGlobal

View File

@@ -1,11 +0,0 @@
{
"MongoDB": {
"ConnectionString": "mongodb://10.0.75.1:27017",
"Database": "jambov32"
},
"ServiceBus": {
"ConnectionString": "10.0.75.1:9092",
"Topic": "jambov32"
}
}

View File

@@ -4,7 +4,7 @@ using Jambo.Domain.Model.Posts;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
namespace Jambo.Infrastructure
namespace Jambo.Consumer.Infrastructure.DataAccess
{
public class MongoContext
{
@@ -20,7 +20,7 @@ namespace Jambo.Infrastructure
public void DatabaseReset(string databaseName)
{
mongoClient.DropDatabase(databaseName);
mongoClient.DropDatabase(databaseName);
}
public IMongoCollection<Blog> Blogs

View File

@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Jambo.Infrastructure.Repositories.Blogs
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Blogs
{
public class BlogReadOnlyRepository : IBlogReadOnlyRepository
{

View File

@@ -3,7 +3,7 @@ using MongoDB.Driver;
using System;
using System.Threading.Tasks;
namespace Jambo.Infrastructure.Repositories.Blogs
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Blogs
{
public class BlogWriteOnlyRepository : IBlogWriteOnlyRepository
{

View File

@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Jambo.Infrastructure.Repositories.Posts
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Posts
{
public class PostReadOnlyRepository : IPostReadOnlyRepository
{

View File

@@ -4,7 +4,7 @@ using MongoDB.Driver;
using System;
using System.Threading.Tasks;
namespace Jambo.Infrastructure.Repositories.Posts
namespace Jambo.Consumer.Infrastructure.DataAccess.Repositories.Posts
{
public class PostWriteOnlyRepository : IPostWriteOnlyRepository
{

View File

@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;1705;NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="4.6.0" />
<PackageReference Include="Autofac.Configuration" Version="4.0.1" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.0" />
<PackageReference Include="MediatR" Version="3.0.1" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
<PackageReference Include="Confluent.Kafka" Version="0.11.0" NoWarn="NU1701" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jambo.Consumer.Application\Jambo.Consumer.Application.csproj" />
<ProjectReference Include="..\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

@@ -1,28 +1,21 @@
using Autofac;
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using Jambo.Infrastructure;
using Jambo.Infrastructure.Repositories;
using Jambo.Infrastructure.Repositories.Blogs;
using Jambo.Infrastructure.Repositories.Posts;
namespace Jambo.Consumer.IoC
namespace Jambo.Consumer.Infrastructure.Modules
{
using Autofac;
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using Jambo.Consumer.Infrastructure.DataAccess.Repositories.Blogs;
using Jambo.Consumer.Infrastructure.DataAccess.Repositories.Posts;
using Jambo.Consumer.Infrastructure.DataAccess;
public class ApplicationModule : Module
{
public readonly string connectionString;
public readonly string database;
public ApplicationModule(string connectionString, string database)
{
this.connectionString = connectionString;
this.database = database;
}
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
protected override void Load(ContainerBuilder builder)
{
MongoContext mongoContext = new MongoContext(connectionString, database);
mongoContext.DatabaseReset(database);
MongoContext mongoContext = new MongoContext(ConnectionString, DatabaseName);
mongoContext.DatabaseReset(DatabaseName);
builder.Register(c => mongoContext)
.As<MongoContext>().SingleInstance();

View File

@@ -0,0 +1,21 @@
namespace Jambo.Consumer.Infrastructure.Modules
{
using Autofac;
using Jambo.Consumer.Infrastructure.ServiceBus;
using Jambo.Domain.ServiceBus;
public class BusModule : Module
{
public string BrokerList { get; set; }
public string Topic { get; set; }
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<Bus>()
.As<ISubscriber>()
.WithParameter("brokerList", BrokerList)
.WithParameter("topic", Topic)
.SingleInstance();
}
}
}

View File

@@ -1,12 +1,12 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.IO;
namespace Jambo.Consumer.Console
namespace Jambo.Consumer.Infrastructure
{
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.IO;
class Program
{
static void Main(string[] args)
public static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
@@ -14,7 +14,6 @@ namespace Jambo.Consumer.Console
.AddEnvironmentVariables();
IConfigurationRoot configuration = builder.Build();
IServiceCollection serviceCollection = new ServiceCollection();
Startup startup = new Startup(configuration);

View File

@@ -2,6 +2,7 @@
using Confluent.Kafka.Serialization;
using Jambo.Domain.Exceptions;
using Jambo.Domain.Model;
using Jambo.Domain.ServiceBus;
using MediatR;
using Newtonsoft.Json;
using System;
@@ -9,54 +10,42 @@ using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Jambo.ServiceBus.Kafka
namespace Jambo.Consumer.Infrastructure.ServiceBus
{
public class Bus : IPublisher, ISubscriber
public class Bus : ISubscriber
{
private readonly Config config;
public readonly string brokerList;
public readonly string topic;
private readonly Producer<string, string> _producer;
private readonly Consumer<string, string> _consumer;
private readonly Consumer<string, string> consumer;
public Bus(Config config)
public Bus(string brokerList, string topic)
{
this.config = config;
this.brokerList = brokerList;
this.topic = topic;
_producer = new Producer<string, string>(
consumer = new Consumer<string, string>(
new Dictionary<string, object>()
{{
"bootstrap.servers", config.BrokerList}},
new StringSerializer(Encoding.UTF8), new StringSerializer(Encoding.UTF8));
_consumer = new Consumer<string, string>(
new Dictionary<string, object>()
{{ "group.id", "simple-csharp-consumer" },
{ "bootstrap.servers", config.BrokerList }},
new StringDeserializer(Encoding.UTF8), new StringDeserializer(Encoding.UTF8));
}
public async Task Publish(DomainEvent domainEvent)
{
string data = JsonConvert.SerializeObject(domainEvent, Formatting.Indented);
Message<string, string> message = await _producer.ProduceAsync(
config.TopicName, domainEvent.GetType().AssemblyQualifiedName, data);
{
{ "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>
consumer.Assign(new List<TopicPartitionOffset>
{
new TopicPartitionOffset(config.TopicName, 0, 0)
new TopicPartitionOffset(topic, 0, 0)
});
while (true)
{
Message<string, string> msg;
if (_consumer.Consume(out msg, TimeSpan.FromSeconds(1)))
if (consumer.Consume(out msg, TimeSpan.FromSeconds(1)))
{
try
{
@@ -74,7 +63,7 @@ namespace Jambo.ServiceBus.Kafka
mediator.Send(domainEvent).Wait();
}
catch (BlogDomainException ex)
catch (DomainException ex)
{
Console.WriteLine(ex.BusinessMessage);
}
@@ -93,17 +82,7 @@ namespace Jambo.ServiceBus.Kafka
public void Dispose()
{
_producer.Dispose();
_consumer.Dispose();
}
public async Task Publish(IEnumerable<DomainEvent> domainEvents, Header header)
{
foreach (var domainEvent in domainEvents)
{
domainEvent.SetHeader(header);
await Publish(domainEvent);
}
consumer.Dispose();
}
}
}

View File

@@ -1,21 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Jambo.ServiceBus;
using Newtonsoft.Json;
using MediatR;
using System.Reflection;
using Autofac;
using Microsoft.Extensions.DependencyInjection;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Jambo.Consumer.IoC;
using System.Threading;
using Jambo.Consumer.Application.DomainEventHandlers.Blogs;
using Jambo.Domain.Model;
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.Console
namespace Jambo.Consumer.Infrastructure
{
public class Startup
{
@@ -57,7 +52,6 @@ namespace Jambo.Consumer.Console
while (true)
{
System.Console.WriteLine(DateTime.Now.ToString());
Thread.Sleep(1000 * 60);
}
}

View File

@@ -1,26 +0,0 @@
using Autofac;
using Jambo.ServiceBus;
using Jambo.Infrastructure;
using Jambo.ServiceBus.Kafka;
using MediatR;
namespace Jambo.Consumer.IoC
{
public class BusModule : Module
{
private readonly string connectionString;
private readonly string topic;
public BusModule(string connectionString, string topic)
{
this.connectionString = connectionString;
this.topic = topic;
}
protected override void Load(ContainerBuilder builder)
{
builder.Register(c => new Config(connectionString, topic)).As<Config>().SingleInstance();
builder.RegisterType<Bus>().As<ISubscriber>().SingleInstance();
}
}
}

View File

@@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;1705;NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="4.6.0" />
<PackageReference Include="MediatR" Version="3.0.1" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jambo.Consumer.Application\Jambo.Consumer.Application.csproj" />
<ProjectReference Include="..\Jambo.Domain\Jambo.Domain.csproj" />
<ProjectReference Include="..\Jambo.Infrastructure\Jambo.Infrastructure.csproj" />
<ProjectReference Include="..\Jambo.ServiceBus.Kafka\Jambo.ServiceBus.Kafka.csproj" />
<ProjectReference Include="..\Jambo.ServiceBus\Jambo.ServiceBus.csproj" />
</ItemGroup>
</Project>

View File

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

View File

@@ -10,35 +10,25 @@
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="4.6.0" />
<PackageReference Include="Autofac.Configuration" Version="4.0.1" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.0" />
<PackageReference Include="MediatR" Version="3.0.1" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
<PackageReference Include="Confluent.Kafka" Version="0.11.0" NoWarn="NU1701" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="System.ComponentModel" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jambo.Consumer.IoC\Jambo.Consumer.IoC.csproj" />
<ProjectReference Include="..\Jambo.Consumer.Application\Jambo.Consumer.Application.csproj" />
<ProjectReference Include="..\Jambo.Domain\Jambo.Domain.csproj" />
<ProjectReference Include="..\Jambo.ServiceBus\Jambo.ServiceBus.csproj" />
</ItemGroup>
<ItemGroup>
<None Update=".dockerignore">
<DependentUpon>Dockerfile</DependentUpon>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@
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)
.AddJsonFile("autofac.json")
.AddEnvironmentVariables();
IConfigurationRoot configuration = builder.Build();
IServiceCollection serviceCollection = new ServiceCollection();
Startup startup = new Startup(configuration);
startup.ConfigureServices(serviceCollection);
startup.Run();
}
}
}

View File

@@ -0,0 +1,69 @@
namespace Jambo.Consumer.Infrastructure
{
using Autofac;
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
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
IServiceProvider serviceProvider;
public IServiceProvider ConfigureServices(IServiceCollection services)
{
LoadInfrastructureAssemblies();
services.AddMediatR(typeof(BlogCreatedEventHandler).GetTypeInfo().Assembly);
ContainerBuilder builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterModule(new ConfigurationModule(Configuration));
serviceProvider = new AutofacServiceProvider(builder.Build());
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

@@ -0,0 +1,3 @@
{
}

View File

@@ -0,0 +1,19 @@
{
"defaultAssembly": "Jambo.Consumer.Infrastructure",
"modules": [
{
"type": "Jambo.Consumer.Infrastructure.Modules.ApplicationModule",
"properties": {
"ConnectionString": "mongodb://10.0.75.1:27017",
"DatabaseName": "jambov32"
}
},
{
"type": "Jambo.Consumer.Infrastructure.Modules.BusModule",
"properties": {
"BrokerList": "10.0.75.1:9092",
"Topic": "jambov32"
}
}
]
}

49
source/Jambo.Consumer.sln Normal file
View File

@@ -0,0 +1,49 @@

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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
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
{645C9138-DA59-48C8-A15E-D720874DD148}.Release|Any CPU.Build.0 = Release|Any CPU
{E86878A7-CB0F-46A7-B918-BED5C45BA4C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {54AE4BA0-6081-4892-A9B9-3F8D15D994C8}
EndGlobalSection
EndGlobal

View File

@@ -1,10 +1,10 @@
namespace Jambo.Domain.Exceptions
{
public class BlogDomainException : JamboException
public class DomainException : JamboException
{
public string BusinessMessage { get; set; }
public BlogDomainException(string businessMessage)
public DomainException(string businessMessage)
{
BusinessMessage = businessMessage;
}

View File

@@ -33,7 +33,7 @@ namespace Jambo.Domain.Model.Blogs
{
if (enabled == false)
{
throw new BlogDomainException("The blog is disabled. Enable this before making any changes.");
throw new DomainException("The blog is disabled. Enable this before making any changes.");
}
Raise(BlogUrlUpdatedDomainEvent.Create(this, url));
@@ -43,7 +43,7 @@ namespace Jambo.Domain.Model.Blogs
{
if (enabled == true)
{
throw new BlogDomainException("The blog is already enabled.");
throw new DomainException("The blog is already enabled.");
}
Raise(BlogEnabledDomainEvent.Create(this));
@@ -53,7 +53,7 @@ namespace Jambo.Domain.Model.Blogs
{
if (enabled == false)
{
throw new BlogDomainException("The blog is already disabled.");
throw new DomainException("The blog is already disabled.");
}
Raise(BlogDisabledDomainEvent.Create(this));

View File

@@ -1,7 +1,7 @@
using System;
namespace Jambo.Domain.Model.Blogs.Events
namespace Jambo.Domain.Model.Blogs.Events
{
using System;
public class BlogEnabledDomainEvent : DomainEvent
{
public BlogEnabledDomainEvent(Guid aggregateRootId, int version,

View File

@@ -10,7 +10,7 @@ namespace Jambo.Domain.Model.Blogs
public Url(string text)
{
if (string.IsNullOrWhiteSpace(text))
throw new BlogDomainException("The url field is required");
throw new DomainException("The url field is required");
this.Text = text;
}

View File

@@ -12,7 +12,7 @@ namespace Jambo.Domain.Model.Posts
public Content(string text)
{
if (string.IsNullOrWhiteSpace(text))
throw new BlogDomainException("The content field is required");
throw new DomainException("The content field is required");
this.Text = text;
}

View File

@@ -28,3 +28,6 @@ namespace Jambo.Domain.Model.Posts.Events
}
}
}

View File

@@ -2,29 +2,29 @@
namespace Jambo.Domain.Model.Posts.Events
{
public class PostCreatedDomainEvent : DomainEvent
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)
{
public Guid BlogId { get; private set; }
public int BlogVersion { get; private set; }
BlogId = blogId;
BlogVersion = blogVersion;
}
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");
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);
PostCreatedDomainEvent domainEvent = new PostCreatedDomainEvent(
aggregateRoot.Id, aggregateRoot.Version, DateTime.UtcNow, null, blogId, blogVersion);
return domainEvent;
}
return domainEvent;
}
}
}

View File

@@ -47,7 +47,7 @@ namespace Jambo.Domain.Model.Posts
{
if (enabled == false)
{
throw new BlogDomainException("The post is already disabled.");
throw new DomainException("The post is already disabled.");
}
Raise(PostDisabledDomainEvent.Create(this));
@@ -57,12 +57,12 @@ namespace Jambo.Domain.Model.Posts
{
if (enabled == false)
{
throw new BlogDomainException("The post is disabled. Enable this before making any changes.");
throw new DomainException("The post is disabled. Enable this before making any changes.");
}
if (published == false)
{
throw new BlogDomainException("The post is already hidden.");
throw new DomainException("The post is already hidden.");
}
Raise(PostHiddenDomainEvent.Create(this));
@@ -72,7 +72,7 @@ namespace Jambo.Domain.Model.Posts
{
if (enabled == true)
{
throw new BlogDomainException("The post is already enabled.");
throw new DomainException("The post is already enabled.");
}
Raise(PostEnabledDomainEvent.Create(this));
@@ -82,7 +82,7 @@ namespace Jambo.Domain.Model.Posts
{
if (enabled == false)
{
throw new BlogDomainException("The blog is disabled. Enable this before making any changes.");
throw new DomainException("The blog is disabled. Enable this before making any changes.");
}
Raise(PostContentUpdatedDomainEvent.Create(this, title, content));
@@ -92,12 +92,12 @@ namespace Jambo.Domain.Model.Posts
{
if (enabled == false)
{
throw new BlogDomainException("The blog is disabled. Enable this before making any changes.");
throw new DomainException("The blog is disabled. Enable this before making any changes.");
}
if (published == true)
{
throw new BlogDomainException("The post is already published.");
throw new DomainException("The post is already published.");
}
Raise(PostPublishedDomainEvent.Create(this));
@@ -107,12 +107,12 @@ namespace Jambo.Domain.Model.Posts
{
if (enabled == false)
{
throw new BlogDomainException("The blog is disabled. Enable this before making any changes.");
throw new DomainException("The blog is disabled. Enable this before making any changes.");
}
if (published == true)
{
throw new BlogDomainException("The post is already hidden.");
throw new DomainException("The post is already hidden.");
}
Raise(CommentCreatedDomainEvent.Create(this, comment.Id, comment.Message));

View File

@@ -12,7 +12,7 @@ namespace Jambo.Domain.Model.Posts
public Title(string text)
{
if (string.IsNullOrWhiteSpace(text))
throw new BlogDomainException("The title field is required");
throw new DomainException("The title field is required");
this.Text = text;
}

View File

@@ -3,7 +3,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Jambo.ServiceBus
namespace Jambo.Domain.ServiceBus
{
public interface IPublisher : IDisposable
{

View File

@@ -1,7 +1,7 @@
using MediatR;
using System;
namespace Jambo.ServiceBus
namespace Jambo.Domain.ServiceBus
{
public interface ISubscriber : IDisposable
{

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jambo.Domain\Jambo.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -4,28 +4,28 @@ using Jambo.Domain.Model.Blogs;
using MediatR;
using System;
using System.Threading.Tasks;
using Jambo.ServiceBus;
using Jambo.Domain.ServiceBus;
namespace Jambo.Producer.Application.CommandHandlers.Blogs
{
public class CreatePostCommandHandler : IAsyncRequestHandler<CreateBlogCommand, Guid>
public class CreatePostCommandHandler : IAsyncRequestHandler<CreateBlogCommand, Guid>
{
private readonly IPublisher bus;
public CreatePostCommandHandler(IPublisher bus)
{
private readonly IPublisher bus;
this.bus = bus ?? throw new ArgumentNullException(nameof(bus));
}
public CreatePostCommandHandler(IPublisher bus)
{
this.bus = bus ?? throw new ArgumentNullException(nameof(bus));
}
public async Task<Guid> Handle(CreateBlogCommand command)
{
Blog blog = Blog.Create();
blog.Start();
blog.UpdateUrl(Url.Create(command.Url));
public async Task<Guid> Handle(CreateBlogCommand command)
{
Blog blog = Blog.Create();
blog.Start();
blog.UpdateUrl(Url.Create(command.Url));
await bus.Publish(blog.GetEvents(), command.Header);
await bus.Publish(blog.GetEvents(), command.Header);
return blog.Id;
}
return blog.Id;
}
}
}

View File

@@ -3,7 +3,7 @@ using Jambo.Domain.Model.Blogs;
using MediatR;
using System;
using System.Threading.Tasks;
using Jambo.ServiceBus;
using Jambo.Domain.ServiceBus;
namespace Jambo.Producer.Application.CommandHandlers.Blogs
{

View File

@@ -3,7 +3,7 @@ using System;
using System.Threading.Tasks;
using Jambo.Producer.Application.Commands.Blogs;
using Jambo.Domain.Model.Blogs;
using Jambo.ServiceBus;
using Jambo.Domain.ServiceBus;
namespace Jambo.Producer.Application.CommandHandlers.Blogs
{

View File

@@ -2,7 +2,7 @@
using System;
using System.Threading.Tasks;
using Jambo.Producer.Application.Commands.Blogs;
using Jambo.ServiceBus;
using Jambo.Domain.ServiceBus;
using Jambo.Domain.Model.Blogs;
namespace Jambo.Producer.Application.CommandHandlers.Blogs

View File

@@ -1,7 +1,7 @@
using Jambo.Producer.Application.Commands.Posts;
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using Jambo.ServiceBus;
using Jambo.Domain.ServiceBus;
using MediatR;
using System;
using System.Threading.Tasks;

View File

@@ -3,7 +3,7 @@ using Jambo.Producer.Application.Commands.Blogs;
using Jambo.Producer.Application.Commands.Posts;
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using Jambo.ServiceBus;
using Jambo.Domain.ServiceBus;
using MediatR;
using System;
using System.Threading.Tasks;

View File

@@ -3,10 +3,10 @@ using Jambo.Producer.Application.Commands.Blogs;
using Jambo.Producer.Application.Commands.Posts;
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using Jambo.ServiceBus;
using MediatR;
using System;
using System.Threading.Tasks;
using Jambo.Domain.ServiceBus;
namespace Jambo.Producer.Application.CommandHandlers.Posts
{

View File

@@ -1,9 +1,9 @@
using Jambo.Producer.Application.Commands.Posts;
using Jambo.Domain.Model.Posts;
using Jambo.ServiceBus;
using MediatR;
using System;
using System.Threading.Tasks;
using Jambo.Domain.ServiceBus;
namespace Jambo.Producer.Application.CommandHandlers.Posts
{

View File

@@ -1,9 +1,9 @@
using Jambo.Producer.Application.Commands.Posts;
using Jambo.Domain.Model.Posts;
using Jambo.ServiceBus;
using MediatR;
using System;
using System.Threading.Tasks;
using Jambo.Domain.ServiceBus;
namespace Jambo.Producer.Application.CommandHandlers.Posts
{

View File

@@ -3,10 +3,10 @@ using Jambo.Producer.Application.Commands.Blogs;
using Jambo.Producer.Application.Commands.Posts;
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using Jambo.ServiceBus;
using MediatR;
using System;
using System.Threading.Tasks;
using Jambo.Domain.ServiceBus;
namespace Jambo.Producer.Application.CommandHandlers.Posts
{

View File

@@ -3,10 +3,10 @@ using Jambo.Producer.Application.Commands.Blogs;
using Jambo.Producer.Application.Commands.Posts;
using Jambo.Domain.Model.Blogs;
using Jambo.Domain.Model.Posts;
using Jambo.ServiceBus;
using MediatR;
using System;
using System.Threading.Tasks;
using Jambo.Domain.ServiceBus;
namespace Jambo.Producer.Application.CommandHandlers.Posts
{
@@ -34,3 +34,4 @@ namespace Jambo.Producer.Application.CommandHandlers.Posts
}
}
}

View File

@@ -2,15 +2,12 @@
using System.Runtime.Serialization;
using Jambo.Producer.Application.Commands;
using System;
using System.ComponentModel.DataAnnotations;
namespace Jambo.Producer.Application.Commands.Blogs
{
[DataContract]
public class CreateBlogCommand : CommandBase, IRequest<Guid>
{
[StringLength(100, MinimumLength = 10)]
[Required]
[DataMember]
public string Url { get; private set; }

View File

@@ -4,14 +4,12 @@ using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
using Jambo.Producer.Application.Commands;
using System.ComponentModel.DataAnnotations;
namespace Jambo.Producer.Application.Commands.Blogs
{
[DataContract]
public class DisableBlogCommand : CommandBase, IRequest
{
[Required]
[DataMember]
public Guid Id { get; private set; }

View File

@@ -4,14 +4,12 @@ using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
using Jambo.Producer.Application.Commands;
using System.ComponentModel.DataAnnotations;
namespace Jambo.Producer.Application.Commands.Blogs
{
[DataContract]
public class EnableBlogCommand : CommandBase, IRequest
{
[Required]
[DataMember]
public Guid Id { get; private set; }

View File

@@ -1,6 +1,5 @@
using MediatR;
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace Jambo.Producer.Application.Commands.Blogs
@@ -8,12 +7,9 @@ namespace Jambo.Producer.Application.Commands.Blogs
[DataContract]
public class UpdateBlogUrlCommand : CommandBase, IRequest
{
[Required]
[DataMember]
public Guid Id { get; private set; }
[StringLength(100, MinimumLength = 10)]
[Required]
[DataMember]
public string Url { get; private set; }

View File

@@ -1,19 +1,15 @@
using MediatR;
using System.Runtime.Serialization;
using System;
using System.ComponentModel.DataAnnotations;
namespace Jambo.Producer.Application.Commands.Posts
{
[DataContract]
public class CreateCommentCommand : CommandBase, IRequest<Guid>
{
[Required]
[DataMember]
public Guid PostId { get; private set; }
[StringLength(100, MinimumLength = 10)]
[Required]
[DataMember]
public string Comment { get; private set; }

View File

@@ -1,24 +1,18 @@
using MediatR;
using System.Runtime.Serialization;
using System;
using System.ComponentModel.DataAnnotations;
namespace Jambo.Producer.Application.Commands.Posts
{
[DataContract]
public class CreatePostCommand : CommandBase, IRequest<Guid>
{
[Required]
[DataMember]
public Guid BlogId { get; private set; }
[StringLength(100, MinimumLength = 10)]
[Required]
[DataMember]
public string Title { get; private set; }
[StringLength(100, MinimumLength = 10)]
[Required]
[DataMember]
public string Content { get; private set; }

View File

@@ -2,14 +2,12 @@
using System.Runtime.Serialization;
using Jambo.Producer.Application.Commands;
using System;
using System.ComponentModel.DataAnnotations;
namespace Jambo.Producer.Application.Commands.Posts
{
[DataContract]
public class DisablePostCommand : CommandBase, IRequest
{
[Required]
[DataMember]
public Guid Id { get; private set; }

View File

@@ -2,14 +2,12 @@
using System.Runtime.Serialization;
using Jambo.Producer.Application.Commands;
using System;
using System.ComponentModel.DataAnnotations;
namespace Jambo.Producer.Application.Commands.Posts
{
[DataContract]
public class EnablePostCommand : CommandBase, IRequest
{
[Required]
[DataMember]
public Guid Id { get; private set; }

View File

@@ -2,14 +2,12 @@
using System.Runtime.Serialization;
using Jambo.Producer.Application.Commands;
using System;
using System.ComponentModel.DataAnnotations;
namespace Jambo.Producer.Application.Commands.Posts
{
[DataContract]
public class HidePostCommand : CommandBase, IRequest
{
[Required]
[DataMember]
public Guid Id { get; private set; }

View File

@@ -2,14 +2,12 @@
using System.Runtime.Serialization;
using Jambo.Producer.Application.Commands;
using System;
using System.ComponentModel.DataAnnotations;
namespace Jambo.Producer.Application.Commands.Posts
{
[DataContract]
public class PublishPostCommand : CommandBase, IRequest
{
[Required]
[DataMember]
public Guid Id { get; private set; }

View File

@@ -2,24 +2,18 @@
using System.Runtime.Serialization;
using Jambo.Producer.Application.Commands;
using System;
using System.ComponentModel.DataAnnotations;
namespace Jambo.Producer.Application.Commands.Posts
{
[DataContract]
public class UpdatePostContentCommand : CommandBase, IRequest
{
[Required]
[DataMember]
public Guid Id { get; private set; }
[StringLength(100, MinimumLength = 10)]
[Required]
[DataMember]
public string Title { get; private set; }
[StringLength(100, MinimumLength = 10)]
[Required]
[DataMember]
public string Content { get; private set; }

View File

@@ -6,14 +6,10 @@
<ItemGroup>
<PackageReference Include="MediatR" Version="3.0.1" />
<PackageReference Include="MongoDB.Driver" Version="2.4.4" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.4.0" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jambo.Domain\Jambo.Domain.csproj" />
<ProjectReference Include="..\Jambo.ServiceBus\Jambo.ServiceBus.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text;
using System.Threading.Tasks;
namespace Jambo.Producer.Application.Queries

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text;
using System.Threading.Tasks;
namespace Jambo.Producer.Application.Queries

View File

@@ -0,0 +1,76 @@
namespace Jambo.Producer.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;
private readonly IMongoDatabase database;
public MongoContext(string connectionString, string databaseName)
{
this.mongoClient = new MongoClient(connectionString);
this.database = mongoClient.GetDatabase(databaseName);
Map();
}
public void DatabaseReset(string databaseName)
{
mongoClient.DropDatabase(databaseName);
}
public IMongoCollection<Blog> Blogs
{
get
{
return database.GetCollection<Blog>("Blogs");
}
}
public IMongoCollection<Post> Posts
{
get
{
return database.GetCollection<Post>("Posts");
}
}
private void Map()
{
BsonClassMap.RegisterClassMap<Entity>(cm =>
{
cm.MapIdProperty(c => c.Id);
});
BsonClassMap.RegisterClassMap<AggregateRoot>(cm =>
{
cm.MapProperty(c => c.Version).SetElementName("_version");
});
BsonClassMap.RegisterClassMap<Blog>(cm =>
{
cm.MapField("url").SetElementName("url");
cm.MapField("rating").SetElementName("rating");
cm.MapField("enabled").SetElementName("enabled");
});
BsonClassMap.RegisterClassMap<Post>(cm =>
{
cm.MapField("title").SetElementName("title");
cm.MapField("content").SetElementName("content");
cm.MapField("blogId").SetElementName("blogId");
cm.MapField("enabled").SetElementName("enabled");
cm.MapField("published").SetElementName("published");
});
BsonClassMap.RegisterClassMap<Comment>(cm =>
{
cm.MapField("message").SetElementName("message");
});
}
}
}

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