Matt Can Code  

Setup rabbitmq using Docker 

[root@szsit148 ~]# docker run --name rabbitmq -d -p 5672:5672 -p 15672:15672 --h ostname rabbitmq rabbitmq:3-management

[root@szsit148 ~]# docker ps

CONTAINER ID IMAGE                           COMMAND                  CREATED     STATUS       PORTS                                                                                                                                       NAMES
8b27776fa3eb rabbitmq:3-management "docker-entrypoint.s…" 11 hours ago Up 11 hours 4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 15671/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp rabbitmq

[root@szsit148 ~]# docker rm rabbitmq -f

Setup rabbitmq, redis, using Docker Compose

[root@szsit148 ~]# docker-compose -f mongo-rabbit-redis.yml up -d

version: "3.5"

services:
  mongo:
    image: mongo:4
    container_name: mongo
    ports:
      - 27017:27017
    networks:
      - dshop
    # network_mode: host
    volumes:
      - mongo:/data/db

  rabbitmq:
    image: rabbitmq:3-management
    container_name: rabbitmq
    ports:
      - 5672:5672
      - 15672:15672
    networks:
      - dshop
    # network_mode: host
    volumes: 
      - rabbitmq:/var/lib/rabbitmq

  redis:
    image: redis
    container_name: redis
    ports:
      - 6379:6379
    networks:
      - dshop
    # network_mode: host
    volumes: 
      - redis:/data

networks:
  dshop:
    name: dshop-network

volumes:
  mongo:
    driver: local
  rabbitmq:
    driver: local
  redis:
    driver: local
mango-rabbit-redis

 [root@szsit148 compose]# docker inspect dshop-network

[
{
"Name": "dshop-network",
"Id": "1545e967cc479ed40e859c1b82a8477b6ff90ccd7ff868b91c2788f9e15e2ae2",
"Created": "2019-01-29T08:38:48.739641213+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": true,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"7cdfdf302de6c7fea656670e3076f1b36309791409ff2f9db510013630897068": {
"Name": "mongo",
"EndpointID": "5cc3bb708e274be58d2fb2450aa1a6054848d89738e653d62f90cc1bdeffca58",
"MacAddress": "02:42:ac:12:00:04",
"IPv4Address": "172.18.0.4/16",
"IPv6Address": ""
},
"9a66c04a4171791121bfb319d5f3dbaef8c326e89a1384239d485bc72b93364b": {
"Name": "redis",
"EndpointID": "4ab30409c9a3b786dd566cc424b4257edc5664a2383e4ed768ba51afeed14e84",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
},
"c2f328b8896ac1c53701dfc15b1f563e63bd3818dedfb87ad03bc95eb14e238e": {
"Name": "rabbitmq",
"EndpointID": "103ce313867ac5069f7416ba713879329fead30233ebed39005c355402acb769",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {
"com.docker.compose.network": "dshop-network",
"com.docker.compose.project": "compose",
"com.docker.compose.version": "1.22.0-rc1"
}
}
View Result

Start all applicaiton up using start-all.sh

#!/bin/bash
export ASPNETCORE_ENVIRONMENT=local
DOTNET_RUN=./scripts/dotnet-run.sh
PREFIX=DNC-DShop
SERVICE=$PREFIX.Services
REPOSITORIES=($PREFIX.Api $SERVICE.Customers $SERVICE.Identity $SERVICE.Operations $SERVICE.Orders $SERVICE.Products $SERVICE.Signalr)

for REPOSITORY in ${REPOSITORIES[*]}
do
     echo ========================================================
     echo Starting a service: $REPOSITORY
     echo ========================================================
     cd $REPOSITORY
     $DOTNET_RUN &
     cd ..
done
Use git bash to run

 

 MangoDB tool

 

 

Conditional Resolving multiple implementation from generic interface base on types

articles

Dependency injection is applied where there is only one implementation to the interface, DI via constructor is mostly seen.

but if a interface has multiple implementation, resolving the right one can be done in runtime and determined by the paramterized type variables

 

   public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            var builder = new ContainerBuilder();
            builder.RegisterAssemblyTypes(Assembly.GetEntryAssembly())
                .AsImplementedInterfaces();
            builder.Populate(services);
            Container = builder.Build();
            
            return new AutofacServiceProvider(Container);
        }
Use Autofac as DI framework at startup.cs
[HttpPost]
        public IActionResult GetAll([FromBody] GetAllQuery query)
        {
            var handleType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(List<DemoModel>));
            dynamic QueryHandler = _context.Resolve(handleType);
            return new JsonResult(QueryHandler.execute((dynamic)query));
              }

namespace DynamicDI.Query
{
    public interface IQuery<TResult>
    {
    }
}

namespace DynamicDI.Query
{
    public class GetAllQuery : IQuery<List<DemoModel>> 
    {
        public int Id { get; set; }
    }
}

namespace DynamicDI.QueryHandler
{
    interface IQueryHandler<IQuery, TResult>
    {
        TResult execute(IQuery query);
    }
}


namespace DynamicDI.QueryHandler
{
    public class GetAllQueryHandler : IQueryHandler<GetAllQuery, List<DemoModel>>
    {
        public GetAllQueryHandler()
        {
            //Here you can put repository implmentation at the constructor
        }
        public List<DemoModel> execute(GetAllQuery query)
        {
            //repo.getall
            return new List<DemoModel>
            {
                new DemoModel{Id=0, Name="Matt"},
                new DemoModel{Id=1, Name="Yang"}
             };
        }
    }
}
View Code

 Spin up docker compose for Consul, 

[root@szsit148 ~]# docker-compose -f consul-fabio-vault.yml up -d

version: "3.5"

services:
  consul:
    image: consul
    container_name: consul
    ports:
      - 6500:6500
    networks:
      - dshop
    # network_mode: host
    volumes:
      - consul:/consul/data

  fabio:
    image: fabiolb/fabio
    container_name: fabio
    environment:
    - FABIO_REGISTRY_CONSUL_ADDR=consul:6500
    networks:
      - dshop
    # network_mode: host
    ports:
    - 9998:9998
    - 9999:9999

  vault:
    image: vault
    container_name: vault
    ports:
      - 8200:8200
    networks:
      - dshop
    # network_mode: host
    environment:
      - VAULT_ADDR=http://127.0.0.1:8200
      - VAULT_DEV_ROOT_TOKEN_ID=secret
    cap_add:
      - IPC_LOCK

networks:
  dshop:
    name: dshop-network
    external: true

volumes:
  consul:
    driver: local
View Code

Access  http://10.89.24.148:8500

Add a common library and create a extension method to take servicecollection and register the consul configuration

Add microsoft.extensions.dependencyinjection and configuration

microsoft.extensions.dependencyinjection is the namespace for IServiceCollection 

Add 3 import type to transcient service

  • ConsulClient - component of Consul Library provided by Nuget, responsible for sending request to consul and getting the service info in return
  • ConsulServiceRegistry - which takes the ConsulClient at constructor and leverage it to get the AgentService(Consul type) which has the service name and its host name and port
  • ConsultHttpClient - a wrapper class of HttpClient that aggregate ConsulServiceRegistry via ConsulServiceDiscoveryMessageHandler

Service A register to Consul

client.Agent.ServiceRegister(new AgentServiceRegistration{servicename="ServiceA"});

using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;

namespace Common.Consul
{
    public static class ConsulExtensions
    {
        public static IServiceCollection AddConsul(this IServiceCollection services)
        {
            IConfiguration configuration;

            using (var servicebuilder = services.BuildServiceProvider())
            {
                configuration = servicebuilder.GetService<IConfiguration>();
            }

            var options = new ConsulOptions();
            configuration.GetSection("Consul").Bind(options);
            services.Configure<ConsulOptions>(configuration.GetSection("consul"));
            services.AddTransient<IConsulServicesRegistry, ConsulServiceRegistry>();
            services.AddTransient<ConsulServiceDiscoveryMessageHandler>();
            services.AddHttpClient<IConsulHttpClient, ConsulHttpClient>()
                .AddHttpMessageHandler<ConsulServiceDiscoveryMessageHandler>();

            return services.AddSingleton<IConsulClient>(c =>
            new ConsulClient(cfg => cfg.Address = new System.Uri(options.url)));
            

        }

        public static string UseConsul(this IApplicationBuilder app)
        {
            using (var scope = app.ApplicationServices.CreateScope())
            {
                var consulOptions = scope.ServiceProvider.GetService<IOptions<ConsulOptions>>();
               // var fabioOptions = scope.ServiceProvider.GetService<IOptions<FabioOptions>>();
                var enabled = consulOptions.Value.enabled;
                var consulEnabled = Environment.GetEnvironmentVariable("CONSUL_ENABLED")?.ToLowerInvariant();
                if (!string.IsNullOrWhiteSpace(consulEnabled))
                {
                    enabled = consulEnabled == "true" || consulEnabled == "1";
                }

                if (!enabled)
                {
                    return string.Empty;
                }


                var address = consulOptions.Value.address;
                if (string.IsNullOrWhiteSpace(address))
                {
                    throw new ArgumentException("Consul address can not be empty.",
                        nameof(consulOptions.Value.pingEndpoint));
                }

                var uniqueId = Guid.NewGuid();
                var client = scope.ServiceProvider.GetService<IConsulClient>();
                var serviceName = consulOptions.Value.service;
                var serviceId = $"{serviceName}:{uniqueId}";
                var port = consulOptions.Value.port;
                var pingEndpoint = consulOptions.Value.pingEndpoint;
                var pingInterval = consulOptions.Value.pingInterval <= 0 ? 5 : consulOptions.Value.pingInterval;
                var removeAfterInterval =
                    consulOptions.Value.removeAfterInterval <= 0 ? 10 : consulOptions.Value.removeAfterInterval;
                var registration = new AgentServiceRegistration
                {
                    Name = serviceName,
                    ID = serviceId,
                    Address = address,
                    Port = port,
                    Tags = null
                    //Tags = fabioOptions.Value.Enabled ? GetFabioTags(serviceName, fabioOptions.Value.Service) : null
                };
                if (consulOptions.Value.pingEnabled )//|| fabioOptions.Value.Enabled)
                {
                    var scheme = address.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)
                        ? string.Empty
                        : "http://";
                    var check = new AgentServiceCheck
                    {
                        Interval = TimeSpan.FromSeconds(pingInterval),
                        DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(removeAfterInterval),
                        HTTP = $"{scheme}{address}{(port > 0 ? $":{port}" : string.Empty)}/{pingEndpoint}"
                    };
                    registration.Checks = new[] { check };
                }

                client.Agent.ServiceRegister(registration);

                return serviceId;
            }
        }

    }
}
ConsulExtension

 

Service B user service discovery

only aware the SERVICE NAME for Service A and reach out to Consul to resolve the name into host name and forward the rquest to the Service A 

 Unregister consul when applicaiton exit

applicationLifetime.ApplicationStopped.Register(() =>
{
client.Agent.ServiceDeregister(consulServiceId);
// Container.Dispose();
});

Add config section consul

{
  "consul": {
    "enabled": true,
    "url": "http://localhost:8500",
    "service": "conditionalinjection-service",
    "address": "localhost",
    "port": "5000",
    "pingEnabled": true,
    "pingEndpoint": "ping",
    "pingInterval": 5,
    "removeAfterInterval": 10,
    "requestRetries": 3
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

 Fabio LoadBalancing 

Fabio's unqiueness is unlike Nginx which needs to manually set up the reseverse proxied ip address.

Fabio pair with Consul to provide service. Consul does not provide LB features

Fabio collect the ipaddress from Consul where IP are binded during runtime. and re-route the traffice to registered instance address

 

 

 

 

posted on 2026-01-17 18:55  Matt Yeung  阅读(30)  评论(0)    收藏  举报