we would like to clarify the runtime behaviour of the Inbox processing in ABP.IO v10 when multiple instances are running.
For example:
If the Inbox contains 10 or more pending items
And 2 (or more) application instances are running with Inbox processing enabled
What is the expected behavior?
Will each instance pick different Inbox items and process them in parallel?
Or does only one instance consume and process Inbox messages while the others remain idle?
If parallel processing is supported, how does ABP prevent two instances from processing the same Inbox item?
Understanding this behaviour is important for us to properly size and scale our deployment.
Thank you.
We are using ABP.IO v10 and with the built-in Inbox/Outbox pattern for message processing in a micro service approach.
We would like to understand whether it is possible to scale message consumption horizontally:
Is it supported to have multiple application instances or background workers consuming messages from the same Inbox while using the Inbox/Outbox flow?
Specifically:
Can multiple instances safely process messages from the same Inbox without the risk of duplicate processing? Does ABP v10 provide built-in concurrency control (such as row locking, leasing, or optimistic concurrency) for Inbox message consumption?
If this scenario is not supported out of the box, is there a recommended approach in ABP v10 to achieve parallel consumption (e.g., partitioning inboxes, custom locking, or integration with a message broker)?
Our goal is to scale consumers while preserving the at-least-once guarantees provided by the Inbox/Outbox pattern.
Have managed to get this working.
In the API project this registration would not trigger the read. This is still the registration on the web project.
services.Configure<AbpSystemTextJsonSerializerOptions>(options => c
{
options.JsonSerializerOptions.Converters.Add(new PaymentRequestProductExtraParameterConfigurationJsonConverter());
});
Adding the JsonConverter in the API project against the JsonOptions instead of AbpSystemTextJsonSerializerOptions resolved the issue and the read now trigger.
Configure<JsonOptions>(options =>
{
options.JsonSerializerOptions.Converters.Add(new PaymentRequestProductExtraParameterConfigurationJsonConverter());
});
This did not resolve my issue, I am still experiencing the same error.
I added the new JsonConverter to my web project.
The API is still throwing the following exception:
Deserialization of interface or abstract types is not supported. Type 'Volo.Payment.IPaymentRequestProductExtraParameterConfiguration'. Path: $.products[0].extraProperties.ZendaPaymentRequestProductExtraParameterConfiguration | LineNumber: 0 | BytePositionInLine: 213
StackTrace:
at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack& state, Utf8JsonReader& reader, Exception innerException)
at System.Text.Json.ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(JsonTypeInfo typeInfo, Utf8JsonReader& reader, ReadStack& state)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
at System.Text.Json.Serialization.JsonDictionaryConverter`3.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TDictionary& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
at System.Text.Json.Serialization.JsonCollectionConverter`2.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TCollection& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, T& value, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.ContinueDeserialize(ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack, T& value)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsync(Stream utf8Json, CancellationToken cancellationToken)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder.BindModelAsync(ModelBindingContext bindingContext)
at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value, Object container)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
I also add the same JsonConverter to the API project where the exception is being thrown.
We are building a microservice-based solution using abp.io and the ABP Payment module.
In our payment workflow, we use PaymentRequestProductCreateDto, which has an ExtraProperties property of type Dictionary<string, IPaymentRequestProductExtraParameterConfiguration>.
We need to pass custom gateway configuration data (e.g., ZendaPaymentRequestProductExtraParameterConfiguration) through this property.
public class ZendaPaymentRequestProductExtraParameterConfiguration : IPaymentRequestProductExtraParameterConfiguration
{
public string RegisterNo { get; set; }
}
However, when sending this DTO between services (e.g., via HTTP), we encounter the following error during deserialization
Deserialization of interface or abstract types is not supported. Type 'Volo.Payment.IPaymentRequestProductExtraParameterConfiguration'. Path: $.products[0].extraProperties.Zenda | LineNumber: 0 | BytePositionInLine: 165.
Expected behaviour: We would like to be able to pass custom configuration objects in ExtraProperties across microservice boundaries without serialization issues, or have guidance on the recommended ABP approach for this scenario.
Question: What is the recommended way to use ExtraProperties for gateway-specific configuration in a microservice environment, given the interface-based dictionary and serialization limitations?