Dalibor Votruba 2 лет назад
Родитель
Сommit
58c8300166

+ 50 - 5
Workbench/Processor/Dependencies/Value/Extensions/EnumerableExt.cs

@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Quadarax.Foundation.Core.Value.Extensions
+namespace Quadarax.Foundation.Core.Value.Extensions
 {
     public static class EnumerableExt
     {
@@ -45,5 +41,54 @@ namespace Quadarax.Foundation.Core.Value.Extensions
 
 
         }
+
+
+        public static IEnumerable<TElement> CompareDiff<TElement>(this IEnumerable<TElement> owner,
+            IEnumerable<TElement> other)
+        {
+            if (owner== null)
+                throw new ArgumentNullException(nameof(owner));
+            if (other == null)
+                throw new ArgumentNullException(nameof(other));
+
+            var ownerArr = (owner as TElement[] ?? owner.ToArray()).ToArray();
+            var otherArr = (other as TElement[] ?? other.ToArray()).ToArray();
+            
+            var result = new List<TElement>();
+
+            foreach (var elem in ownerArr)
+            {
+                if (!otherArr.Contains(elem)) result.Add(elem);
+            }
+            foreach (var elem in otherArr)
+            {
+                if (!ownerArr.Contains(elem)) result.Add(elem);
+            }
+            return result.Distinct();
+        }
+        
+        public static IEnumerable<TElement> CompareCommon<TElement>(this IEnumerable<TElement> owner,
+            IEnumerable<TElement> other)
+        {
+            if (owner== null)
+                throw new ArgumentNullException(nameof(owner));
+            if (other == null)
+                throw new ArgumentNullException(nameof(other));
+
+            var ownerArr = (owner as TElement[] ?? owner.ToArray()).ToArray();
+            var otherArr = (other as TElement[] ?? other.ToArray()).ToArray();
+            
+            var result = new List<TElement>();
+
+            foreach (var elem in ownerArr)
+            {
+                if (otherArr.Contains(elem)) result.Add(elem);
+            }
+            foreach (var elem in otherArr)
+            {
+                if (ownerArr.Contains(elem)) result.Add(elem);
+            }
+            return result.Distinct();
+        }
     }
 }

+ 6 - 7
Workbench/Processor/Providers/IProcessorStorageProvider.cs

@@ -25,29 +25,28 @@ namespace Quadarax.Foundation.Core.Business.Processor.Providers
         /// </summary>
         /// <param name="item">Item to create.</param>
         /// <returns>Identifier of new created item.</returns>
-        public ITaskIdentifier Create(ITaskItem item);
+        public ITaskIdentifier Create(ITaskItemData item);
         /// <summary>
         /// Get task item from storage by identifier.
         /// </summary>
         /// <param name="ids">Storage identifiers of Task item</param>
         /// <returns>Array of Task items</returns>
-        public ITaskItem[] Get(params ITaskIdentifier[] ids);
+        public ITaskItemData[] Get(params ITaskIdentifier[] ids);
         /// <summary>
         /// Saves task items in storage.
         /// </summary>
         /// <param name="items">Task items to save.</param>
-        public void Set(params ITaskItem[] items);
+        public void Set(params ITaskItemData[] items);
         /// <summary>
-        /// Deletes task items in storage (or mark it for delete).
+        /// Deletes task items physically in storage.
         /// </summary>
-        /// <param name="force">Flag is set, performs physical delete of item. Otherwise mark item to delete.</param>
         /// <param name="ids">Storage identifiers of Task item</param>
-        public void Delete(bool force, params ITaskIdentifier[] ids);
+        public void Delete(params ITaskIdentifier[] ids);
         /// <summary>
         /// Query task items from storage by status.
         /// </summary>
         /// <param name="statuses">Allowed statuses</param>
         /// <returns>Queryable collection of selected Task items.</returns>
-        public IQueryable<ITaskItem> Query(params ProcessStatusEnum[] statuses);
+        public IQueryable<ITaskItemData> Query(params ProcessStatusEnum[] statuses);
     }
 }

+ 71 - 0
Workbench/Processor/Providers/InMemoryStorageProvider.cs

@@ -0,0 +1,71 @@
+using System.Collections.Concurrent;
+using Quadarax.Foundation.Core.Business.Processor.Enums;
+using Quadarax.Foundation.Core.Business.Processor.Task;
+
+namespace Quadarax.Foundation.Core.Business.Processor.Providers
+{
+    public class InMemoryStorageProvider : ProcessorStorageProvider
+    {
+        #region *** Private fields ***
+        private readonly IDictionary<long, ITaskItemData> _cache = new ConcurrentDictionary<long, ITaskItemData>();
+        #endregion
+
+        #region *** Overrides ***
+        protected override void OnDisposing()
+        {
+            _cache.Clear();
+        }
+
+        public override IQueryable<ITaskItemData> Query(params ProcessStatusEnum[] statuses)
+        {
+            throw new NotImplementedException();
+        }
+
+        protected override void OnOpen()
+        {
+        }
+
+        protected override void OnClose()
+        {
+        }
+
+        protected override bool OnEnsureStorage()
+        {
+            return false;
+        }
+
+        protected override ITaskIdentifier OnCreate(ITaskItemData item)
+        {
+            var id = GetNextId();
+            ((TaskItem)item).Id = id;
+            _cache.TryAdd(id, item);
+            return item;
+        }
+
+        protected override ITaskItemData[] OnGet(ITaskIdentifier[] ids)
+        {
+            var result = ids.Select(id => _cache[id.Id]).ToList();
+            return result.ToArray();
+        }
+
+        protected override void OnDeleteOne(ITaskItemData item)
+        {
+            _cache.Remove(item.Id);
+        }
+
+        protected override void OnSetOne(ITaskItemData item)
+        {
+            var existing = (TaskItem) _cache[item.Id];
+            existing.CopyFrom(item);
+        }
+        #endregion
+
+        #region *** Private operations ***
+        private long GetNextId()
+        {
+            return _cache.Keys.Any() ? _cache.Keys.Max() + 1 : 1;
+        }
+        #endregion 
+
+    }
+}

+ 136 - 0
Workbench/Processor/Providers/ProcessorStorageProvider.cs

@@ -0,0 +1,136 @@
+
+using Quadarax.Foundation.Core.Business.Processor.Enums;
+using Quadarax.Foundation.Core.Business.Processor.Task;
+using Quadarax.Foundation.Core.Logging;
+using Quadarax.Foundation.Core.Object;
+
+namespace Quadarax.Foundation.Core.Business.Processor.Providers;
+public abstract class ProcessorStorageProvider : DisposableObject, IProcessorStorageProvider
+{
+    #region *** Properties ***
+    public bool IsOpen { get; private set; }
+    #endregion
+
+    #region *** Fields ***
+    private readonly ILog _log;
+    #endregion
+
+    #region *** Constructors ***
+
+    protected ProcessorStorageProvider()
+    {
+        _log = new ConsoleLogger().GetLogger(GetType());
+    }
+    #endregion
+
+    public void Open()
+    {
+        if (IsOpen) throw new InvalidOperationException("Storage provider has been already opened.");
+
+        OnOpen();
+
+        IsOpen = true;
+        Log(LogSeverityEnum.Debug, "Storage provider has been opened.");
+    }
+    
+    public void Close()
+    {
+        if (!IsOpen) throw new InvalidOperationException("Storage provider has been already closed.");
+
+        OnClose();
+
+        IsOpen = false;
+        Log(LogSeverityEnum.Debug, "Storage provider has been closed.");
+    }
+
+    public bool EnsureStorage()
+    {
+        var result = OnEnsureStorage();
+        Log(LogSeverityEnum.Debug, $"Storage provider has been ensured with result '{result}'.");
+        return result;
+    }
+
+    public ITaskIdentifier Create(ITaskItemData item)
+    {
+        if (item == null) throw new ArgumentNullException(nameof(item));
+        EnsureOpen();
+        var result = OnCreate(item);
+        Log(LogSeverityEnum.Debug, $"Task item '{result}' has been created in storage.");
+        return result;
+    }
+
+    public ITaskItemData[] Get(params ITaskIdentifier[] ids)
+    {
+        if (ids == null) throw new ArgumentNullException(nameof(ids));
+        if (ids.Length > 0) throw new ArgumentOutOfRangeException(nameof(ids), "must have at least one element in array.");
+        EnsureOpen();
+        var result = OnGet(ids);
+        Log(LogSeverityEnum.Debug, $"Gets {result.Length} items by {ids.Length} identifiers from storage.");
+        return result;
+    }
+    
+    public void Set(params ITaskItemData[] items)
+    {
+        if (items == null) throw new ArgumentNullException(nameof(items));
+        if (items.Length > 0) throw new ArgumentOutOfRangeException(nameof(items), "must have at least one element in array.");
+        EnsureOpen();
+        var storedItems = OnGet(items.Select(x => x as ITaskIdentifier).ToArray());
+        var cntUpdated = 0;
+        var cntSkipped = 0;
+        foreach (var storedItem in storedItems)
+        {
+            var item = items.First(x => x.Id == storedItem.Id);
+            if (item != storedItem)
+            {
+                OnSetOne(item);
+                cntUpdated++;
+                Log(LogSeverityEnum.Debug, $"Task item '{item}' has been updated in storage.");
+            }
+            else
+            {
+                cntSkipped++;
+                Log(LogSeverityEnum.Debug, $"Task item '{item}' skip to update. No relevant changes detected.");
+            }
+        }
+
+        Log(LogSeverityEnum.Debug, $"Task items has been updated ({cntUpdated} or {items.Length}) in storage (skipped {cntSkipped} of {items.Length}).");
+    }
+
+    
+
+    public void Delete(params ITaskIdentifier[] ids)
+    {
+        if (ids == null) throw new ArgumentNullException(nameof(ids));
+        if (ids.Length > 0) throw new ArgumentOutOfRangeException(nameof(ids), "must have at least one element in array.");
+        EnsureOpen();
+        var items = OnGet(ids);
+        foreach (var item in items)
+        {
+            OnDeleteOne(item);
+            Log(LogSeverityEnum.Debug, $"Task item '{item}' has been deleted from storage.");
+        }
+    }
+
+    public abstract IQueryable<ITaskItemData> Query(params ProcessStatusEnum[] statuses);
+
+    #region *** Private Operations & Virtuals ***
+    protected virtual void Log(LogSeverityEnum type, string message, Exception e = null)
+    {
+        _log.Log(type, message, e);
+    }
+
+    protected void EnsureOpen()
+    {
+        if (!IsOpen) Open();
+    }
+
+    protected abstract void OnOpen();
+    protected abstract void OnClose();
+    protected abstract bool OnEnsureStorage();
+    protected abstract ITaskIdentifier OnCreate(ITaskItemData item);
+    protected abstract ITaskItemData[] OnGet(ITaskIdentifier[] ids);
+    protected abstract void OnDeleteOne(ITaskItemData item);
+
+    protected abstract void OnSetOne(ITaskItemData item);
+    #endregion
+}

+ 29 - 0
Workbench/Processor/Queue/CallbackDelegates.cs

@@ -0,0 +1,29 @@
+using Quadarax.Foundation.Core.Business.Processor.Task;
+
+namespace Quadarax.Foundation.Core.Business.Processor.Queue
+{
+    #region *** Callback delegates ***
+    public delegate void ItemProcessedCallback(ITaskItem item);
+    public delegate void ItemAddedCallback(ITaskItem item);
+    public delegate void ProcessorStartedCallback(ITaskItem item);
+    public delegate void ProcessorStoppedCallback(ITaskItem item);
+    public delegate void ProcessorErrorCallback(string message, Exception e);
+    #endregion
+
+    public class CallbackDelegates
+    {
+        #region *** Public properties ***
+        public ItemProcessedCallback OnItemProcessed { get; set; }
+        public ItemAddedCallback OnItemAdded { get; set; }
+        public ProcessorStartedCallback OnProcessorStarted { get; set; }
+        public ProcessorStoppedCallback OnProcessorStopped { get; set; }
+        public ProcessorErrorCallback OnProcessorError { get; set; }
+        #endregion
+
+        #region *** Constructors ***
+        public CallbackDelegates()
+        {
+        }
+        #endregion
+    }
+}

+ 76 - 10
Workbench/Processor/Queue/ProcessQueue.cs

@@ -8,6 +8,7 @@ using Quadarax.Foundation.Core.Business.Processor.Providers;
 using Quadarax.Foundation.Core.Business.Processor.Task;
 using Quadarax.Foundation.Core.Logging;
 using Quadarax.Foundation.Core.Object;
+using Quadarax.Foundation.Core.Value.Extensions;
 
 namespace Quadarax.Foundation.Core.Business.Processor.Queue
 {
@@ -19,19 +20,26 @@ namespace Quadarax.Foundation.Core.Business.Processor.Queue
         public string QueueIdentifier { get; private set; }
         #endregion
 
+        #region *** Private fields ***
+        private readonly CallbackDelegates _delegates;
+        private readonly ILog _log;
+        private readonly ProcessQueueConfiguration _configuration;
+        private readonly IProcessorStorageProvider _storageProvider;
+        private bool _isTransient;
+        #endregion
+
         #region *** Constructors ***
-        public ProcessQueue(string processQueueIdentifierToken, IProcessorStorageProvider storage, ProcessQueueConfiguration configuration, CallbackDelegates delegates = null)
+        public ProcessQueue(string processQueueIdentifierToken, IProcessorStorageProvider storage, ProcessQueueConfiguration configuration, CallbackDelegates? delegates = null)
         {
             if (string.IsNullOrEmpty(processQueueIdentifierToken)) throw new ArgumentNullException(nameof(processQueueIdentifierToken));
-            IdentifierToken = processQueueIdentifierToken;
+            QueueIdentifier = processQueueIdentifierToken;
             _delegates = delegates ?? new CallbackDelegates();
 
-            _log = new QLogger().GetLogger(GetType());
+            _log = new ConsoleLogger().GetLogger(GetType());
             _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
             _storageProvider = storage ?? throw new ArgumentNullException(nameof(storage));
 
-            _configuration.Validate();		
-            _processingItems = new ProcessQueueItem[_configuration.WorkersCount];
+            _configuration.Validate();
         }
 
 
@@ -40,25 +48,73 @@ namespace Quadarax.Foundation.Core.Business.Processor.Queue
         #region *** Public Operations ***
         public void Start()
         {
-            throw new NotImplementedException();
+            if (IsRunning || _isTransient)
+            {
+                Log(LogSeverityEnum.Warn, $"ProcessQueue [{QueueIdentifier}] already running! Skip start.");
+                return;
+            }
+            _isTransient = true;
+            var now = DateTime.Now;
+            Log(LogSeverityEnum.Debug, $"ProcessQueue [{QueueIdentifier}] starting...");
+
+            //TODO: start mechanism here
+
+            Log(LogSeverityEnum.Info, $"ProcessQueue [{QueueIdentifier}] started @{DateTime.Now.Subtract(now).ToReadableString()}");
+            _isTransient = false;
+            IsRunning = true;
         }
 
         public void Stop()
         {
-            throw new NotImplementedException();
+            if (!IsRunning || _isTransient)
+            {
+                Log(LogSeverityEnum.Warn, $"ProcessQueue [{QueueIdentifier}] already stopped! Skip stop.");
+                return;
+            }
+            _isTransient = true;
+            var now = DateTime.Now;
+            Log(LogSeverityEnum.Debug, $"ProcessQueue [{QueueIdentifier}] stopping...");
+
+            //TODO: start mechanism here
+
+            Log(LogSeverityEnum.Info, $"ProcessQueue [{QueueIdentifier}] stopped @{DateTime.Now.Subtract(now).ToReadableString()}");
+            _isTransient = false;
+            IsRunning = false;
         }
 
-        public ITaskIdentifier CreateTask()
+        public ITaskIdentifier CreateTask(string reference, string providerClass, TypedArgument[] initialArguments, byte priority, int initialAttempts, TimeSpan attemptTimeout, DateTime validFrom, DateTime? validTo)
         {
-
+            try
+            {
+                var task = new TaskItem(this,reference, providerClass,initialArguments,priority,initialAttempts,attemptTimeout,validFrom,validTo, null);
+                var newId = _storageProvider.Create(task);
+                task.Id = newId.Id;
+                return task;
+            }
+            catch (Exception e)
+            {
+                var message = $"Error creating task [Ref:{reference}, Provider:{providerClass}] for queue [{QueueIdentifier}]";
+                Log(LogSeverityEnum.Error, message, e);
+                throw new Exception(message, e);
+            }
         }
+
         public ITaskItem GetTask(ITaskIdentifier id)
         {
-
+            if (id == null) throw new ArgumentNullException(nameof(id));
+            var result = _storageProvider.Get(id).FirstOrDefault();
+            return result == null ? throw new Exception($"Task [{id.Id}] not found!") : (ITaskItem)result;
         }
+
         public ITaskItem[] GetTask(ITaskIdentifier[] ids)
         {
+            var result = _storageProvider.Get(ids);
+            var diff = result.Select(x => x.Id).CompareDiff(ids.Select(x => x.Id));
+
+            if (diff.Any())
+                throw new Exception($"Tasks [{string.Join(",", diff)}] not found!");
 
+            return (ITaskItem[])result;
         }
 
         public ITaskIdentifier[] QueryTasks(ProcessStatusEnum[] states, bool onlyValid = true, bool includeDeleted = false)
@@ -69,6 +125,16 @@ namespace Quadarax.Foundation.Core.Business.Processor.Queue
         #endregion
 
         #region *** Private Operations ***
+
+        protected void Log(LogSeverityEnum type, string message, Exception e = null)
+        {
+            if (type == LogSeverityEnum.Error || type == LogSeverityEnum.Fatal)
+            {
+                _delegates.OnProcessorError?.Invoke(message, e);
+            }
+            _log.Log(type, message,e);
+        }
+
         protected override void OnDisposing()
         {
             throw new NotImplementedException();

+ 1 - 4
Workbench/Processor/Task/ITaskIdentifier.cs

@@ -8,8 +8,5 @@ public interface ITaskIdentifier
 	/// Primary key of the task.
 	/// </summary>
 	long Id {get;}
-	/// <summary>
-	/// Queue type of the task where task is stored or belongs to.
-	/// </summary>
-	ProcessQueueTypeEnum QueueType {get;}
+	
 }

+ 1 - 57
Workbench/Processor/Task/ITaskItem.cs

@@ -3,64 +3,8 @@ using Quadarax.Foundation.Core.Business.Processor.Exceptons;
 
 namespace Quadarax.Foundation.Core.Business.Processor.Task;
 
-public interface ITaskItem : ITaskIdentifier, ITimeTracked
+public interface ITaskItem : ITaskItemData
 {
-	#region *** Properties ***
-	/// <summary>
-	/// Contains the affinity token which is used to identify owning/locking queue.
-	/// </summary>
-	IAffinityToken AffinityToken { get; }
-	/// <summary>
-	/// External reference of the task.
-	/// </summary>
-	public string Reference { get; }
-	/// <summary>
-	/// Status of the task.
-	/// </summary>
-    public ProcessStatusEnum Status { get; }
-	/// <summary>
-	/// Assembly qualified name of the provider class responsible to process task.
-	/// </summary>
-	public string ProviderClass { get; }
-	/// <summary>
-	/// Task priority value (0-maximal, 255-minimal / default 100).
-	/// </summary>
-	public byte Priority { get; }
-	/// <summary>
-	/// Time when task can be processed.
-	/// </summary>
-	public DateTime ProcessingValidFrom { get; }
-    /// <summary>
-    /// Time when task will be expired.
-    /// </summary>
-    public DateTime? ProcessingValidTo { get; }
-	/// <summary>
-	/// Defines single attempt timeout. If null, timeout is not used.
-	/// </summary>
-    public TimeSpan? AttemptTimeout { get; }
-	/// <summary>
-	/// Decremental counter of attempts to try process task.
-	/// </summary>
-	public int AttemptsLeft { get; }
-	/// <summary>
-	/// Statistical timestamp when task begins to process.
-	/// </summary>
-	public DateTime StatStarted { get; }
-	/// <summary>
-	/// Statistical timestamp when task finished.
-	/// </summary>
-    public DateTime StatFinished { get; }
-	/// <summary>
-	/// Latest attempt duration
-	/// </summary>
-    public TimeSpan StatLastAttemptDuration { get; }
-
-	/// <summary>
-	/// Collection of task status changes history log items. Ordered by <see cref="ITaskStatusLogItem.Created"/> descending.
-	/// </summary>
-	public ITaskStatusLogItem[] StatusLog { get; }
-    #endregion
-
 	#region *** Operations ***
 	/// <summary>
 	/// Re-Schedules inactive task to future and sets <see cref="Status"/> to <see cref="ProcessStatusEnum.Pending"/> (affects <see cref="ProcessingValidFrom"/> and <see cref="ProcessingValidTo"/>).

+ 69 - 0
Workbench/Processor/Task/ITaskItemData.cs

@@ -0,0 +1,69 @@
+using Quadarax.Foundation.Core.Business.Processor.Enums;
+using Quadarax.Foundation.Core.Object;
+
+namespace Quadarax.Foundation.Core.Business.Processor.Task
+{
+    public interface ITaskItemData : ITaskIdentifier, ITimeTracked, IEquatable<ITaskItemData>
+    {
+        #region *** Properties ***
+        /// <summary>
+        /// Contains the affinity token which is used to identify owning/locking queue.
+        /// </summary>
+        IAffinityToken AffinityToken { get; }
+        /// <summary>
+        /// External reference of the task.
+        /// </summary>
+        public string Reference { get; }
+        /// <summary>
+        /// Status of the task.
+        /// </summary>
+        public ProcessStatusEnum Status { get; }
+        /// <summary>
+        /// Assembly qualified name of the provider class responsible to process task.
+        /// </summary>
+        public string ProviderClass { get; }
+        /// <summary>
+        /// Task priority value (0-maximal, 255-minimal / default 100).
+        /// </summary>
+        public byte Priority { get; }
+        /// <summary>
+        /// Time when task can be processed.
+        /// </summary>
+        public DateTime ProcessingValidFrom { get; }
+        /// <summary>
+        /// Time when task will be expired.
+        /// </summary>
+        public DateTime? ProcessingValidTo { get; }
+        /// <summary>
+        /// Defines single attempt timeout. If null, timeout is not used.
+        /// </summary>
+        public TimeSpan? AttemptTimeout { get; }
+        /// <summary>
+        /// Decremental counter of attempts to try process task.
+        /// </summary>
+        public int AttemptsLeft { get; }
+        /// <summary>
+        /// Statistical timestamp when task begins to process.
+        /// </summary>
+        public DateTime StatStarted { get; }
+        /// <summary>
+        /// Statistical timestamp when task finished.
+        /// </summary>
+        public DateTime StatFinished { get; }
+        /// <summary>
+        /// Latest attempt duration
+        /// </summary>
+        public TimeSpan StatLastAttemptDuration { get; }
+
+        /// <summary>
+        /// Collection of task status changes history log items. Ordered by <see cref="ITaskStatusLogItem.Created"/> descending.
+        /// </summary>
+        public ITaskStatusLogItem[] StatusLog { get; }
+
+        /// <summary>
+        /// Collection of task arguments.
+        /// </summary>
+        public TypedArgument[] Arguments { get; }
+        #endregion
+    }
+}

+ 12 - 0
Workbench/Processor/Task/Identifier.cs

@@ -0,0 +1,12 @@
+namespace Quadarax.Foundation.Core.Business.Processor.Task
+{
+    public class Identifier : ITaskIdentifier
+    {
+        public long Id { get; }
+
+        public Identifier(long id)
+        {
+            Id = id;
+        }
+    }
+}

+ 72 - 16
Workbench/Processor/Task/TaskItem.cs

@@ -13,67 +13,67 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
         /// <summary>
         /// <inheritdoc cref="ITaskItem.Id"/>
         /// </summary>
-        public long Id { get; }
+        public long Id { get; internal set; }
         /// <summary>
         /// <inheritdoc cref="ITaskItem.QueueType"/>
         /// </summary>
-        public ProcessQueueTypeEnum QueueType { get; }
+        public ProcessQueueTypeEnum QueueType { get;private set; }
         /// <summary>
         /// <inheritdoc cref="ITaskItem.Created"/>
         /// </summary>
-        public DateTime Created { get; }
+        public DateTime Created { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.Changed"/>
         /// </summary>
-        public DateTime? Changed { get; }
+        public DateTime? Changed { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.AffinityToken"/>
         /// </summary>
-        public IAffinityToken AffinityToken { get; }
+        public IAffinityToken AffinityToken { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.Reference"/>
         /// </summary>
-        public string Reference { get; }
+        public string Reference { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.Status"/>
         /// </summary>
-        public ProcessStatusEnum Status { get; }
+        public ProcessStatusEnum Status { get; private set; }
         /// <summary>
         /// <inheritdoc cref="ITaskItem.ProviderClass"/>
         /// </summary>
-        public string ProviderClass { get; }
+        public string ProviderClass { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.Priority"/>
         /// </summary>
-        public byte Priority { get; }
+        public byte Priority { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.ProcessingValidFrom"/>
         /// </summary>
-        public DateTime ProcessingValidFrom { get; }
+        public DateTime ProcessingValidFrom { get;private set; }
         /// <summary>
         /// <inheritdoc cref="ITaskItem.ProcessingValidTo"/>
         /// </summary>
-        public DateTime? ProcessingValidTo { get; }
+        public DateTime? ProcessingValidTo { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.AttemptsLeft"/>
         /// </summary>
-        public int AttemptsLeft { get; }
+        public int AttemptsLeft { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.AttemptTimeout"/>
         /// </summary>
-        public TimeSpan? AttemptTimeout { get; }
+        public TimeSpan? AttemptTimeout { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.StatStarted"/>
         /// </summary>
-        public DateTime StatStarted { get; }
+        public DateTime StatStarted { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.StatFinished"/>
         /// </summary>
-        public DateTime StatFinished { get; }
+        public DateTime StatFinished { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.StatLastAttemptDuration"/>
         /// </summary>
-        public TimeSpan StatLastAttemptDuration { get; }
+        public TimeSpan StatLastAttemptDuration { get; private set;}
         /// <summary>
         /// <inheritdoc cref="ITaskItem.StatusLog"/>
         /// </summary>
@@ -137,6 +137,62 @@ namespace Quadarax.Foundation.Core.Business.Processor.Task
         {
             throw new NotImplementedException();
         }
+        public bool Euqals(ITaskItemData data)
+        {
+            return ((ITaskItemData)this).Equals(data);
+        }
+
+        #endregion
+
+        #region *** Internal Operations ***
+
+        internal void CopyFrom(ITaskItemData data)
+        {
+            Id = data.Id;
+            Status = data.Status;
+            Changed = data.Changed;
+            Reference = data.Reference;
+            ProviderClass = data.ProviderClass;
+            Priority = data.Priority;
+            ProcessingValidFrom = data.ProcessingValidFrom;
+            ProcessingValidTo = data.ProcessingValidTo;
+            Created = data.Created;
+            Changed = data.Changed;
+            StatStarted = data.StatStarted;
+            StatFinished = data.StatFinished;
+            StatLastAttemptDuration = data.StatLastAttemptDuration;
+            AttemptsLeft = data.AttemptsLeft;
+            AttemptTimeout = data.AttemptTimeout;
+            _statusLog.Clear();
+            _statusLog.AddRange(data.StatusLog);
+            _arguments.Clear();
+            _arguments.AddRange(data.Arguments);
+        }
+
+        bool IEquatable<ITaskItemData>.Equals(ITaskItemData? other)
+        {
+            if (other == null)
+                return false;
+
+            var result = Id == other.Id &&
+                Status == other.Status &&
+                Changed == other.Changed &&
+                Reference == other.Reference &&
+                ProviderClass == other.ProviderClass &&
+                Priority == other.Priority &&
+                ProcessingValidFrom == other.ProcessingValidFrom &&
+                ProcessingValidTo == other.ProcessingValidTo &&
+                Created == other.Created &&
+                Changed == other.Changed &&
+                StatStarted == other.StatStarted &&
+                StatFinished == other.StatFinished &&
+                StatLastAttemptDuration == other.StatLastAttemptDuration &&
+                AttemptsLeft == other.AttemptsLeft &&
+                AttemptTimeout == other.AttemptTimeout &&
+                _statusLog.SequenceEqual(other.StatusLog) &&
+                _arguments.SequenceEqual(other.Arguments);
+            return result;
+        }
         #endregion
     }
 }