refactor: restructure host message handling by implementing specialized handlers and updating database schema documentation

This commit is contained in:
2026-05-11 10:50:51 +02:00
parent 6c4fabe603
commit aecf032f22
31 changed files with 99 additions and 2 deletions

View File

@@ -1,90 +0,0 @@
using System;
using System.ComponentModel;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using Unity;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Services;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.Shared.Process;
using Gebhardt.Shared.Process.ProducerConsumer;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Mapping;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Helpers;
using Gebhardt.Shared;
using Gebhardt.StoreWare.WcsWms.Constants;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms
{
internal class FromWmsBookingConsumer : Consumer<IHostMessage>
{
#region Private Fields
private readonly IUnityContainer _unityContainer;
private readonly IHostMessageFromWmsService _serviceFromWms;
#endregion Private Fields
#region Public Constructors
public FromWmsBookingConsumer(string name, int aliveTime, int queueLength, IUnityContainer unityContainer)
: base(name, aliveTime, true, queueLength)
{
_unityContainer = unityContainer;
_serviceFromWms = _unityContainer.Resolve<IHostMessageFromWmsService>();
}
#endregion Public Constructors
#region Public Methods
public override bool DoWork(IHostMessage hostMessage)
{
try
{
IHandleRecord<IHostMessage> handler = null;
try
{
//handler = _unityContainer.Resolve<IHandleRecord>(hostMessage.RecordType);
handler = (IHandleRecord<IHostMessage>)_unityContainer.Resolve(hostMessage.GetType()); //TODO test
}
catch (Exception e)
{
Log.Write(LogLevel.Error, $"IHandleRecord on UnityContainer in RegisterFromWmsServices from MessageInitializer.cs not registired. '{e.Message}'");
}
if (handler == null)
{
hostMessage.Handle();
}
else
{
handler.Handle(hostMessage);
hostMessage.SetFinished();
}
}
catch (Exception e)
{
//Log.WriteException(e);
string exceptionMessage = e.InnerException?.InnerException?.Message ?? e.InnerException?.Message ?? e.Message;
Log.Write(LogLevel.Error, $"Nachricht {hostMessage.RecordType} kann nicht verbucht werden: {exceptionMessage}");
hostMessage.SetFailed(e.Message);
}
finally
{
_serviceFromWms.Update(hostMessage);
}
return true;
}
#endregion Public Methods
}
}

View File

@@ -1,224 +0,0 @@
using System;
using System.ComponentModel;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using System.Text.RegularExpressions;
using Unity;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Services;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.Shared.Process;
using Gebhardt.Shared.Process.ProducerConsumer;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Mapping;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Helpers;
using Gebhardt.Shared;
using Gebhardt.StoreWare.WcsWms.Constants;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms
{
internal class FromWmsBookingProducer : Producer<IHostMessage>
{
#region Private Fields
private bool _firstExecution = true;
private int _consumerQueueLength;
private bool _useLoadBalancing;
private readonly IUnityContainer _unityContainer;
private readonly IHostMessageFromWmsService _serviceFromWms;
#endregion Private Fields
#region Private Methods
/// <summary>
/// Fügt im Dictionary dem angegebenen Consumer das Messagem hinzu
/// </summary>
/// <param name="dataForConsumers"></param>
/// <param name="consumer"></param>
/// <param name="hostMessage">FromWms - IHostMessage</param>
private void AddDataForConsumer(ref Dictionary<string, List<IHostMessage>> dataForConsumers, string consumer, IHostMessage hostMessage)
{
if (dataForConsumers.ContainsKey(consumer))
{
dataForConsumers[consumer].Add(hostMessage);
}
else
{
dataForConsumers.Add(consumer, new List<IHostMessage> { hostMessage });
}
}
/// <summary>
/// Bestimmt den Consumer, der für den Datensatz verantwortlich ist
/// </summary>
/// <param name="consumersWithDemand"></param>
/// <param name="criterion"></param>
/// <returns>Consumer Name, wenn ein passender Consumer in der lsite ist, null, wenn kein passender Consumer in der Liste ist</returns>
private string GetConsumerForBooking(List<string> consumersWithDemand, string criterion)
{
//Gibt es eine Letzte Stelle, sonst default
if (!criterion.IsNullOrEmptyOrDbEmpty())
{
//Wir entscheiden mit der letzten Stelle der LE, welcher LE-Consumer verbucht oder ob der default Consumer das tun muss
string endOfLe = criterion.Substring(criterion.Length - 1);
//Der Name des Consumer endet mit dem gleichen Zeichen
if (consumersWithDemand.Any(c => c.EndsWith(endOfLe)))
{
return consumersWithDemand.First(c => c.EndsWith(endOfLe));
}
//Soll dieses Message von einem speziellen Consumer bearbeitet werden? Aber dieser ist beschäftigt
else if (Regex.IsMatch(endOfLe, "[0-9]"))
{
//Message auslassen - keinem Consumer zuordnen
return null;
}
//HU endet mit einem Zeichen, dass zu keinem Consumer passt also Default oder auslassen
else
{
//Default Consumer muss mit Default enden!
return consumersWithDemand.FirstOrDefault(c => c.EndsWith("Default"));
}
}
else
{
//Default Consumer muss mit Default enden!
return consumersWithDemand.FirstOrDefault(c => c.EndsWith("Default"));
}
}
#endregion Private Methods
#region Protected Methods
protected override Dictionary<string, List<IHostMessage>> GetDataForConsumers(List<string> consumersWithDemand)
{
//TODO: jub evtl. die Messagem Stau Meldung aus KW übernehmen
Dictionary<string, List<IHostMessage>> dataForConsumers = new Dictionary<string, List<IHostMessage>>();
try
{
using HostEntities db = new HostEntitiesFactory(HostEntities.DefaultConnectionStringName).Create();
if (consumersWithDemand.Count > 0)
{
List<IHostMessage> hostMessageEntries;
//es werden maximal so viele Messages abgerufen, wie ein einzelner Consumer annehmen könnte, damit das TryAdd sicher klappt und der Status nicht fälschlich
//gesetzt wird
if (_firstExecution)
{
//beim ersten mal nach Neustart werden auch die InProgress Messages nochmal mit abgerufen
//Ref == null damit nur Kopfnachrichten gefunden werden
hostMessageEntries = _serviceFromWms.GetAllEntries(a => a.Status == "Pending" || a.Status == "InProgress", takeCount: _consumerQueueLength).OrderBy(a => a.Id).ToList();
_firstExecution = false;
}
else
{
//sonst nur Messages, die noch an keinen Consumer gegeben wurden
//Ref == null damit nur Kopfnachrichten gefunden werden
hostMessageEntries = _serviceFromWms.GetAllEntries(a => a.Status == "Pending", takeCount: _consumerQueueLength).OrderBy(a => a.Id).ToList();
}
if (hostMessageEntries.Any())
{
//ohne LoadBalancing erhält der erste Consumer alle Messages zum Verbuchen
if (!_useLoadBalancing)
{
//der Consumer muss also alle Messages verarbeiten und nicht nur loggen
dataForConsumers.Add(consumersWithDemand.FirstOrDefault() ?? string.Empty, hostMessageEntries);
//Status auf InProgress damit jede Message nur einmal abgerufen wird
hostMessageEntries.ForEach(message => message.Status = "InProgress");
Log.Write(LogLevel.Low, $"Kein LoadBalancing aktiv, {hostMessageEntries.Count} neue HostMessages für Consumer {consumersWithDemand.FirstOrDefault()} gefunden");
}
//mit LoadBalancing wird nach der letzen Ziffer der ersten HU der Message auf 10 Consumer verteilt,
//enthält die Message keine HU wird es an Consumer 11 übergeben.
else
{
Log.Write(LogLevel.Low, $"LoadBalancing aktiv, {hostMessageEntries.Count} neue HostMessages gefunden, verteile auf Consumer");
foreach (IHostMessage message in hostMessageEntries)
{
try
{
FromWms entityFromWms = HostMessageFromWmsService.HostMapper.Map<FromWms>(message);
Log.Write(LogLevel.Low, $"Producerschleife für {entityFromWms}");
string consumer;
//keine HU in der Message, dann dem Default Consumer zuordnen
if (entityFromWms.LeNo.IsNullOrEmptyOrDbEmpty())
{
consumer = GetConsumerForBooking(consumersWithDemand, null);
//wenn der Default Consumer nicht in der Liste war, Message auslassen
if (consumer != null)
{
AddDataForConsumer(ref dataForConsumers, consumer, message);
//Status auf InProgress damit die Message nur einmal abgerufen wird
entityFromWms.Status = "InProgress";
}
}
//Messages mit HU
else
{
consumer = GetConsumerForBooking(consumersWithDemand, entityFromWms.LeNo);
AddDataForConsumer(ref dataForConsumers, consumer, message);
//Status auf InProgress damit die Message nur einmal abgerufen wird
entityFromWms.Status = "InProgress";
}
}
catch (Exception ex)
{
Log.WriteException(ex);
}
}
Log.Write(LogLevel.Low, $"Producerschleife beendet");
}
//Status Updates für alle weitergereichten Messages
db.SaveChanges();
}
else
{
Log.Write(LogLevel.Debug, 60, "Keine Messages in FromWms");
}
}
else
{
Log.Write(LogLevel.Debug, 60, "Kein Consumer hat demand");
}
return dataForConsumers;
}
catch (Exception e)
{
Log.WriteException(e);
return dataForConsumers;
}
}
#endregion Protected Methods
#region Public Constructors
public FromWmsBookingProducer(int workinterval, bool useLoadBalancing, int consumerQueueLength, IUnityContainer unityContainer) : base(typeof(FromWmsBookingProducer).Name, workinterval, true)
{
_unityContainer = unityContainer;
_serviceFromWms = _unityContainer.Resolve<IHostMessageFromWmsService>();
_consumerQueueLength = consumerQueueLength;
_useLoadBalancing = useLoadBalancing;
}
#endregion Public Constructors
}
}

View File

@@ -1,17 +0,0 @@
using System;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms
{
public class FromWmsException : Exception
{
public FromWmsException(string message) : base(message)
{
}
public FromWmsException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

View File

@@ -1,33 +0,0 @@
using Unity;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementation;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Services;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms
{
public static class MessageInitializer
{
public static void RegisterFromWmsServices(this IUnityContainer unityContainer)
{
unityContainer.RegisterType<IHostMessageFromWmsService, HostMessageFromWmsService>();
}
public static void RegisterFromWmsHandlers(this IUnityContainer unityContainer)
{
unityContainer.RegisterType<IHandleRecord<TransportOrder>, TransportOrderHandler>("TransportOrder");
unityContainer.RegisterType<IHandleRecord<ShipmentTransportOrder>, ShipmentTransportOrderHandler>("ShipmentTransportOrder");
unityContainer.RegisterType<IHandleRecord<AcknowledgeTransportCompleted>, AcknowledgeTransportCompletedHandler>("AcknowledgeTransportCompleted");
unityContainer.RegisterType<IHandleRecord<DepartureNotification>, DepartureNotificationHandler>("DepartureNotification");
//unityContainer.RegisterType<IHandleRecord<RequestEmptyHuReport>, RequestEmptyHuReportHandler>("RequestEmptyHuReport");
unityContainer.RegisterType<IHandleRecord<HuChange>, HuChangeHandler>("HuChange");
}
}
}