diff --git a/DashboardService.Test/DashboardService.Test.csproj b/DashboardService.Test/DashboardService.Test.csproj
new file mode 100644
index 0000000..2cc9615
--- /dev/null
+++ b/DashboardService.Test/DashboardService.Test.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netcoreapp3.1
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DashboardService.Test/UnitTest1.cs b/DashboardService.Test/UnitTest1.cs
new file mode 100644
index 0000000..6125ad3
--- /dev/null
+++ b/DashboardService.Test/UnitTest1.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Threading.Tasks;
+using DashboardService.DataAccess.Elastic;
+using DashboardService.Domain;
+using DotNet.Testcontainers.Containers.Builders;
+using DotNet.Testcontainers.Containers.Modules;
+using DotNet.Testcontainers.Containers.WaitStrategies;
+using Nest;
+using Xunit;
+
+namespace DashboardService.Test
+{
+ public class UnitTest1
+ {
+ [Fact]
+ public async Task Test1()
+ {
+ //docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 elasticsearch:7.5.1
+
+ var testContainersBuilder = new TestcontainersBuilder()
+ .WithImage("elasticsearch:6.4.0")
+ .WithName("elasticsearch-33333")
+ .WithEnvironment("discovery.type","single-node")
+ .WithPortBinding(9200, 9200)
+ .WithPortBinding(9300, 9300)
+ //.WithCleanUp(true)
+ //.WithWaitStrategy(Wait.UntilContainerIsRunning());
+ .WithWaitStrategy(Wait.UntilPortsAreAvailable(9200));
+
+ using var testContainer = testContainersBuilder.Build();
+ await testContainer.StartAsync();
+
+ var policyRepo = new ElasticPolicyRepository(CreateElasticClient());
+
+ var pol = new PolicyDocument
+ (
+ "POL1201",
+ new DateTime(2019,1,1),
+ new DateTime(2019,12,31),
+ "Jan Ziomalski",
+ "BDA",
+ 4500M,
+ "jim beam"
+ );
+
+ policyRepo.Save(pol);
+
+ var saved = policyRepo.FindByNumber("POL1201");
+
+
+ Assert.NotNull(saved);
+
+ /*
+ * Elasticsearch.Net.UnexpectedElasticsearchClientException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Int64' because the type requires a JSON primitive value (e.g. string, number, boolean, null) to deserialize correctly.
+To fix this error either change the JSON to a JSON primitive value (e.g. string, number, boolean, null) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
+Path 'hits.total.value', line 1, position 114.
+ ---> Nest.Json.JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Int64' because the type requires a JSON primitive value (e.g. string, number, boolean, null) to deserialize correctly.
+To fix this error either change the JSON to a JSON primitive value (e.g. string, number, boolean, null) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
+Path 'hits.total.value', line 1, position 114.
+ at Nest.Json.Serialization
+ */
+
+ }
+
+ private ElasticClient CreateElasticClient()
+ {
+ var connectionSettings = new ConnectionSettings()
+ .DefaultMappingFor(m=>
+ m.IndexName("policy_lab_stats").IdProperty(d=>d.Number));
+ return new ElasticClient(connectionSettings);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DashboardService/DashboardService.csproj b/DashboardService/DashboardService.csproj
index 20b9fe2..7259b09 100644
--- a/DashboardService/DashboardService.csproj
+++ b/DashboardService/DashboardService.csproj
@@ -10,10 +10,13 @@
+
+
+
diff --git a/DashboardService/DataAccess/Elastic/ElasticPolicyRepository.cs b/DashboardService/DataAccess/Elastic/ElasticPolicyRepository.cs
index 8bd30c4..9155c52 100644
--- a/DashboardService/DataAccess/Elastic/ElasticPolicyRepository.cs
+++ b/DashboardService/DataAccess/Elastic/ElasticPolicyRepository.cs
@@ -1,3 +1,4 @@
+using System;
using System.Linq;
using DashboardService.Domain;
using Nest;
@@ -8,13 +9,6 @@ public class ElasticPolicyRepository : IPolicyRepository
{
private readonly ElasticClient elasticClient;
- public ElasticPolicyRepository()
- {
- var connectionSettings = new ConnectionSettings()
- .DefaultMappingFor(m=>m.IndexName("policy_lab_stats").IdProperty(d=>d.Number));
- elasticClient = new ElasticClient(connectionSettings);
- }
-
public ElasticPolicyRepository(ElasticClient elasticClient)
{
this.elasticClient = elasticClient;
@@ -28,13 +22,18 @@ public ElasticPolicyRepository(ElasticClient elasticClient)
public void Save(PolicyDocument policy)
{
- elasticClient.Index
+ var response = elasticClient.Index
(
policy,
i => i
.Index("policy_lab_stats")
.Id(policy.Number)
);
+
+ if (!response.IsValid)
+ {
+ throw new ApplicationException("Failed to index a policy document");
+ }
}
public PolicyDocument FindByNumber(string policyNumber)
diff --git a/DashboardService/DataAccess/Elastic/NestInstaller.cs b/DashboardService/DataAccess/Elastic/NestInstaller.cs
new file mode 100644
index 0000000..9d1ae5e
--- /dev/null
+++ b/DashboardService/DataAccess/Elastic/NestInstaller.cs
@@ -0,0 +1,25 @@
+using System;
+using DashboardService.Domain;
+using Microsoft.Extensions.DependencyInjection;
+using Nest;
+
+namespace DashboardService.DataAccess.Elastic
+{
+ public static class NestInstaller
+ {
+ public static IServiceCollection AddElasticSearch(this IServiceCollection services, string cnString)
+ {
+ services.AddSingleton(typeof(ElasticClient), svc => CreateElasticClient(cnString));
+ services.AddScoped(typeof(IPolicyRepository), typeof(ElasticPolicyRepository));
+ return services;
+ }
+
+ private static ElasticClient CreateElasticClient(string cnString)
+ {
+ var connectionSettings = new ConnectionSettings(new Uri(cnString))
+ .DefaultMappingFor(m=>
+ m.IndexName("policy_lab_stats").IdProperty(d=>d.Number));
+ return new ElasticClient(connectionSettings);
+ }
+ }
+}
diff --git a/DashboardService/Listeners/PolicyCreatedHandler.cs b/DashboardService/Listeners/PolicyCreatedHandler.cs
new file mode 100644
index 0000000..8c4e845
--- /dev/null
+++ b/DashboardService/Listeners/PolicyCreatedHandler.cs
@@ -0,0 +1,36 @@
+using System.Threading;
+using System.Threading.Tasks;
+using DashboardService.Domain;
+using MediatR;
+using PolicyService.Api.Events;
+
+namespace DashboardService.Listeners
+{
+ public class PolicyCreatedHandler : INotificationHandler
+ {
+ private readonly IPolicyRepository policyRepository;
+
+ public PolicyCreatedHandler(IPolicyRepository policyRepository)
+ {
+ this.policyRepository = policyRepository;
+ }
+
+ public Task Handle(PolicyCreated notification, CancellationToken cancellationToken)
+ {
+ var policy = new PolicyDocument
+ (
+ notification.PolicyNumber,
+ notification.PolicyFrom,
+ notification.PolicyTo,
+ $"{notification.PolicyHolder.FirstName} {notification.PolicyHolder.LastName}",
+ notification.ProductCode,
+ notification.TotalPremium,
+ notification.AgentLogin
+ );
+
+ policyRepository.Save(policy);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/DashboardService/Messaging/RabbitMq/RabbitEventListener.cs b/DashboardService/Messaging/RabbitMq/RabbitEventListener.cs
new file mode 100644
index 0000000..9d4f9ed
--- /dev/null
+++ b/DashboardService/Messaging/RabbitMq/RabbitEventListener.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using MediatR;
+using Microsoft.Extensions.DependencyInjection;
+using RawRabbit;
+
+namespace DashboardService.Messaging.RabbitMq
+{
+ public class RabbitEventListener
+ {
+ private readonly IBusClient busClient;
+ private readonly IServiceProvider serviceProvider;
+
+ public RabbitEventListener(
+ IBusClient busClient,
+ IServiceProvider serviceProvider)
+ {
+ this.busClient = busClient;
+ this.serviceProvider = serviceProvider;
+ }
+
+ public void ListenTo(List eventsToSubscribe)
+ {
+ foreach (var evtType in eventsToSubscribe)
+ {
+ //add check if is INotification
+ this.GetType()
+ .GetMethod("Subscribe", System.Reflection.BindingFlags.NonPublic| System.Reflection.BindingFlags.Instance)
+ .MakeGenericMethod(evtType)
+ .Invoke(this, new object[] { });
+ }
+ }
+
+ private void Subscribe() where T : INotification
+ {
+ //TODO: move exchange name and queue prefix to cfg
+ this.busClient.SubscribeAsync(
+ async (msg) =>
+ {
+ //add logging
+ using (var scope = serviceProvider.CreateScope())
+ {
+ var internalBus = scope.ServiceProvider.GetRequiredService();
+ await internalBus.Publish(msg);
+ }
+ },
+ cfg => cfg.UseSubscribeConfiguration(
+ c => c
+ .OnDeclaredExchange(e => e
+ .WithName("lab-dotnet-micro")
+ .WithType(RawRabbit.Configuration.Exchange.ExchangeType.Topic)
+ .WithArgument("key", typeof(T).Name.ToLower()))
+ .FromDeclaredQueue(q => q.WithName("lab-dashboard-service-" + typeof(T).Name)))
+ );
+ }
+ }
+}
diff --git a/DashboardService/Messaging/RabbitMq/RabbitMqOptions.cs b/DashboardService/Messaging/RabbitMq/RabbitMqOptions.cs
new file mode 100644
index 0000000..c4b9d9d
--- /dev/null
+++ b/DashboardService/Messaging/RabbitMq/RabbitMqOptions.cs
@@ -0,0 +1,9 @@
+namespace DashboardService.Messaging.RabbitMq
+{
+ public class RabbitMqOptions
+ {
+ public string Host { get; set; }
+
+ public int Port { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/DashboardService/Messaging/RabbitMq/RawRabbitInstaller.cs b/DashboardService/Messaging/RabbitMq/RawRabbitInstaller.cs
new file mode 100644
index 0000000..7baed7a
--- /dev/null
+++ b/DashboardService/Messaging/RabbitMq/RawRabbitInstaller.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using RawRabbit;
+using RawRabbit.DependencyInjection.ServiceCollection;
+using RawRabbit.Instantiation;
+
+namespace DashboardService.Messaging.RabbitMq
+{
+ public static class RawRabbitInstaller
+ {
+ public static IServiceCollection AddRabbitListeners(this IServiceCollection services, RabbitMqOptions options)
+ {
+ services.AddRawRabbit(new RawRabbitOptions
+ {
+ ClientConfiguration = new RawRabbit.Configuration.RawRabbitConfiguration
+ {
+ Username = "guest",
+ Password = "guest",
+ VirtualHost = "/",
+ Port = options.Port,
+ Hostnames = new List { options.Host },
+ RequestTimeout = TimeSpan.FromSeconds(10),
+ PublishConfirmTimeout = TimeSpan.FromSeconds(1),
+ RecoveryInterval = TimeSpan.FromSeconds(1),
+ PersistentDeliveryMode = true,
+ AutoCloseConnection = true,
+ AutomaticRecovery = true,
+ TopologyRecovery = true,
+ Exchange = new RawRabbit.Configuration.GeneralExchangeConfiguration
+ {
+ Durable = true,
+ AutoDelete = false,
+ Type = RawRabbit.Configuration.Exchange.ExchangeType.Topic
+ },
+ Queue = new RawRabbit.Configuration.GeneralQueueConfiguration
+ {
+ Durable = true,
+ AutoDelete = false,
+ Exclusive = false
+ }
+ }
+ });
+
+ services.AddSingleton(svc => new RabbitEventListener(svc.GetRequiredService(), svc));
+
+ return services;
+ }
+ }
+
+ public static class RabbitListenersInstaller
+ {
+ public static void UseRabbitListeners(this IApplicationBuilder app, List eventTypes)
+ {
+ app.ApplicationServices.GetRequiredService().ListenTo(eventTypes);
+ }
+ }
+}
diff --git a/DashboardService/Startup.cs b/DashboardService/Startup.cs
index 402ab1c..3c98689 100644
--- a/DashboardService/Startup.cs
+++ b/DashboardService/Startup.cs
@@ -1,5 +1,8 @@
+using System;
+using System.Collections.Generic;
using DashboardService.DataAccess.Elastic;
using DashboardService.Domain;
+using DashboardService.Messaging.RabbitMq;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -7,6 +10,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using PolicyService.Api.Events;
namespace DashboardService
{
@@ -26,7 +30,9 @@ public void ConfigureServices(IServiceCollection services)
.AddNewtonsoftJson()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddMediatR();
+ services.AddElasticSearch(Configuration.GetConnectionString("ElasticSearchConnection"));
services.AddSingleton();
+ services.AddRabbitListeners(Configuration.GetSection("RabbitMqOptions").Get());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -44,6 +50,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
+
+ app.UseRabbitListeners(new List { typeof(PolicyCreated) });
}
}
}
\ No newline at end of file
diff --git a/DashboardService/appsettings.json b/DashboardService/appsettings.json
index d9d9a9b..a65d635 100644
--- a/DashboardService/appsettings.json
+++ b/DashboardService/appsettings.json
@@ -6,5 +6,12 @@
"Microsoft.Hosting.Lifetime": "Information"
}
},
- "AllowedHosts": "*"
+ "AllowedHosts": "*",
+ "RabbitMqOptions" : {
+ "Host" : "localhost",
+ "Port" : 5672
+ },
+ "ConnectionStrings": {
+ "ElasticSearchConnection": "http://localhost:9200"
+ }
}
diff --git a/DotNetMicroservicesPoc.sln b/DotNetMicroservicesPoc.sln
index fe4376c..1979240 100644
--- a/DotNetMicroservicesPoc.sln
+++ b/DotNetMicroservicesPoc.sln
@@ -45,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DashboardService.Api", "Das
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DashboardService", "DashboardService\DashboardService.csproj", "{C8B8AF36-EB9C-4C93-BB9F-0CC5D3A4411C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DashboardService.Test", "DashboardService.Test\DashboardService.Test.csproj", "{4B044FAA-8281-4936-9211-6B236890A199}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -135,6 +137,10 @@ Global
{C8B8AF36-EB9C-4C93-BB9F-0CC5D3A4411C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8B8AF36-EB9C-4C93-BB9F-0CC5D3A4411C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8B8AF36-EB9C-4C93-BB9F-0CC5D3A4411C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4B044FAA-8281-4936-9211-6B236890A199}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4B044FAA-8281-4936-9211-6B236890A199}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4B044FAA-8281-4936-9211-6B236890A199}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4B044FAA-8281-4936-9211-6B236890A199}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE