You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

六边形架构中动态加载适配器:单主机集成WCF与Owin适配器

Dynamic Adapter Loading for Hexagonal Architecture in a Single Console Host

Great question! Implementing a single console host that dynamically loads both your WCF SOAP and OWIN REST adapters aligns perfectly with Hexagonal Architecture’s goal of decoupling core logic from external interfaces. Let’s walk through a practical approach step by step.

1. Clarify Port Naming & Define the Primary Port

First, let’s get the terminology right: your "UI port" is actually a Primary (Driving) Port—it’s the interface through which external systems (like UI clients, APIs) drive your core user management logic. This port should be completely technology-agnostic, so it doesn’t depend on WCF, OWIN, or any other adapter-specific details.

Here’s how you can define the port interface in your core bounded context project:

// MyDDD.UserManagement.Core/IPrimary/UserManagementPort.cs
namespace MyDDD.UserManagement.Core.PrimaryPorts;

public interface IUserManagementPort
{
    Guid CreateNewUser(string username, string email, string passwordHash);
    void BlockUser(Guid userId);
    bool CheckIfUserExists(string usernameOrEmail);
}

Implement this interface in your core service (the "application layer" of your Hexagonal Architecture):

// MyDDD.UserManagement.Core/Services/UserManagementService.cs
namespace MyDDD.UserManagement.Core.Services;

public class UserManagementService : IUserManagementPort
{
    // Inject repositories (secondary ports) here as needed
    private readonly IUserRepository _userRepository;

    public UserManagementService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public Guid CreateNewUser(string username, string email, string passwordHash)
    {
        // Core business logic here
        var user = new User(username, email, passwordHash);
        _userRepository.Add(user);
        return user.Id;
    }

    // Implement other methods...
}

2. Build Adapter Class Libraries (WCF SOAP & OWIN REST)

Each adapter should be a separate class library that depends only on your core bounded context (specifically, the primary port interface). Each adapter will implement the primary port and expose it via its technology (WCF/OWIN), plus provide a host interface to start/stop itself.

Define a Common Adapter Host Interface

Create a shared project (or add it to your core project) with an interface for adapter hosts:

// MyDDD.UserManagement.Adapters.Shared/IAdapterHost.cs
namespace MyDDD.UserManagement.Adapters.Shared;

public interface IAdapterHost
{
    Task StartAsync(CancellationToken cancellationToken = default);
    Task StopAsync(CancellationToken cancellationToken = default);
}

WCF SOAP Adapter

Create a class library MyDDD.UserManagement.Adapters.Soap:

  1. Implement the WCF service that wraps your primary port:
// MyDDD.UserManagement.Adapters.Soap/Services/UserManagementSoapService.cs
namespace MyDDD.UserManagement.Adapters.Soap.Services;

[ServiceContract]
public class UserManagementSoapService
{
    private readonly IUserManagementPort _userManagementPort;

    public UserManagementSoapService(IUserManagementPort userManagementPort)
    {
        _userManagementPort = userManagementPort;
    }

    [OperationContract]
    public Guid CreateNewUser(string username, string email, string passwordHash)
    {
        return _userManagementPort.CreateNewUser(username, email, passwordHash);
    }

    // Expose other operations as OperationContracts...
}
  1. Implement the adapter host to start/stop the WCF service:
// MyDDD.UserManagement.Adapters.Soap/Host/SoapAdapterHost.cs
namespace MyDDD.UserManagement.Adapters.Soap.Host;

public class SoapAdapterHost : IAdapterHost
{
    private ServiceHost? _serviceHost;
    private readonly IUserManagementPort _userManagementPort;
    private readonly string _serviceAddress;

    public SoapAdapterHost(IUserManagementPort userManagementPort, IConfiguration configuration)
    {
        _userManagementPort = userManagementPort;
        _serviceAddress = configuration["Adapters:Soap:Address"] ?? "http://localhost:8080/UserManagementSoap";
    }

    public Task StartAsync(CancellationToken cancellationToken = default)
    {
        _serviceHost = new ServiceHost(typeof(UserManagementSoapService));
        var endpoint = _serviceHost.AddServiceEndpoint(
            typeof(UserManagementSoapService),
            new BasicHttpBinding(),
            _serviceAddress);
        
        _serviceHost.Open();
        Console.WriteLine($"WCF SOAP adapter started at {_serviceAddress}");
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken = default)
    {
        _serviceHost?.Close();
        Console.WriteLine("WCF SOAP adapter stopped");
        return Task.CompletedTask;
    }
}

OWIN REST Adapter

Create a class library MyDDD.UserManagement.Adapters.Rest:

  1. Use OWIN + Web API to expose REST endpoints that call the primary port:
// MyDDD.UserManagement.Adapters.Rest/Controllers/UserManagementController.cs
namespace MyDDD.UserManagement.Adapters.Rest.Controllers;

public class UserManagementController : ApiController
{
    private readonly IUserManagementPort _userManagementPort;

    public UserManagementController(IUserManagementPort userManagementPort)
    {
        _userManagementPort = userManagementPort;
    }

    [HttpPost]
    [Route("api/users")]
    public IHttpActionResult CreateUser([FromBody] CreateUserRequest request)
    {
        var userId = _userManagementPort.CreateNewUser(request.Username, request.Email, request.PasswordHash);
        return Ok(new { UserId = userId });
    }

    // Implement other endpoints...
}

public class CreateUserRequest
{
    public string Username { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string PasswordHash { get; set; } = string.Empty;
}
  1. Implement the adapter host to start/stop the OWIN web host:
// MyDDD.UserManagement.Adapters.Rest/Host/RestAdapterHost.cs
namespace MyDDD.UserManagement.Adapters.Rest.Host;

public class RestAdapterHost : IAdapterHost
{
    private IDisposable? _webApp;
    private readonly string _restAddress;

    public RestAdapterHost(IConfiguration configuration)
    {
        _restAddress = configuration["Adapters:Rest:Address"] ?? "http://localhost:8081/api";
    }

    public Task StartAsync(CancellationToken cancellationToken = default)
    {
        _webApp = WebApp.Start<Startup>(_restAddress);
        Console.WriteLine($"OWIN REST adapter started at {_restAddress}");
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken = default)
    {
        _webApp?.Dispose();
        Console.WriteLine("OWIN REST adapter stopped");
        return Task.CompletedTask;
    }
}

// Startup class for OWIN
public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var config = new HttpConfiguration();
        config.MapHttpAttributeRoutes();
        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
        app.UseWebApi(config);
    }
}

3. Build the Single Console Host

Your console application will act as the "shell" that loads the core logic and dynamically discovers/loads adapters. We’ll use dependency injection to wire everything together.

Step 1: Set Up Dependency Injection

Use Microsoft’s built-in DI (or another container like Autofac) to register core services and adapter hosts.

// MyDDD.UserManagement.Host/Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using MyDDD.UserManagement.Adapters.Shared;
using MyDDD.UserManagement.Core.PrimaryPorts;
using MyDDD.UserManagement.Core.Services;
using System.Reflection;

var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: false)
    .Build();

var services = new ServiceCollection();

// Register core services
services.AddScoped<IUserManagementPort, UserManagementService>();
// Register secondary ports (repositories) here as needed
// services.AddScoped<IUserRepository, SqlUserRepository>();

// Dynamically register adapter hosts from assemblies
var adapterAssemblies = Directory.GetFiles(AppContext.BaseDirectory, "MyDDD.UserManagement.Adapters.*.dll")
    .Select(Assembly.LoadFrom);

foreach (var assembly in adapterAssemblies)
{
    var adapterHostTypes = assembly.GetTypes()
        .Where(t => typeof(IAdapterHost).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
    
    foreach (var type in adapterHostTypes)
    {
        services.AddScoped(typeof(IAdapterHost), type);
    }
}

var serviceProvider = services.BuildServiceProvider();

Step 2: Start All Adapters

Retrieve all registered IAdapterHost instances and start them:

var adapterHosts = serviceProvider.GetServices<IAdapterHost>();
var startTasks = adapterHosts.Select(h => h.StartAsync()).ToList();
await Task.WhenAll(startTasks);

Console.WriteLine("All adapters started. Press Ctrl+C to stop.");

Step 3: Handle Shutdown Gracefully

Listen for console exit signals to stop all adapters:

var cancellationTokenSource = new CancellationTokenSource();
Console.CancelKeyPress += (sender, args) =>
{
    args.Cancel = true;
    cancellationTokenSource.Cancel();
};

try
{
    await Task.Delay(Timeout.Infinite, cancellationTokenSource.Token);
}
catch (TaskCanceledException)
{
    var stopTasks = adapterHosts.Select(h => h.StopAsync()).ToList();
    await Task.WhenAll(stopTasks);
    Console.WriteLine("All adapters stopped. Exiting...");
}

4. Key Configuration (appsettings.json)

Use a configuration file to set adapter addresses without hardcoding:

{
  "Adapters": {
    "Soap": {
      "Address": "http://localhost:8080/UserManagementSoap"
    },
    "Rest": {
      "Address": "http://localhost:8081/api"
    }
  }
}

5. Best Practices

  • Keep Adapters Light: Adapters should only handle technology-specific concerns (SOAP serialization, REST routing) and delegate all business logic to the primary port.
  • Isolate Adapter Dependencies: Each adapter should only depend on the core bounded context and its own technology stack (WCF/OWIN).
  • Add Logging: Integrate a logging framework (like Serilog or Microsoft.Extensions.Logging) into adapters and the host to track activity and debug issues.
  • Health Checks: Add health check endpoints to each adapter (e.g., a /health endpoint in the REST adapter) to monitor their status.

内容的提问来源于stack exchange,提问作者DmitriBodiu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:21:01