From 1d4295431368fb47087188ad199091b67e280579 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sun, 9 Jan 2022 13:08:03 +0800 Subject: [PATCH 01/60] Batch publish events from outbox to the event bus --- .../PostgreSqlDbContextEventOutbox.cs | 2 +- .../DistributedEvents/DbContextEventOutbox.cs | 11 + .../Azure/AzureDistributedEventBus.cs | 9 +- .../Kafka/KafkaDistributedEventBus.cs | 9 +- .../RabbitMq/RabbitMqDistributedEventBus.cs | 509 ++++++++++-------- .../Rebus/RebusDistributedEventBus.cs | 9 +- .../Distributed/DistributedEventBusBase.cs | 236 ++++---- .../Abp/EventBus/Distributed/IEventOutbox.cs | 2 + .../Distributed/ISupportsEventBoxes.cs | 6 + .../EventBus/Distributed/InboxProcessor.cs | 4 +- .../Abp/EventBus/Distributed/OutboxSender.cs | 21 +- .../MultipleOutgoingEventPublishResult.cs | 14 + .../MongoDbContextEventOutbox.cs | 14 + 13 files changed, 488 insertions(+), 358 deletions(-) create mode 100644 framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs index e5f2e0c4b6..7c5ef12fa7 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs @@ -13,7 +13,7 @@ public class PostgreSqlDbContextEventOutbox : DbContextEventOutbox : IDbContextEventOutbox ids) + { + var dbContext = (IHasEventOutbox)await DbContextProvider.GetDbContextAsync(); + var outgoingEvents = await dbContext.OutgoingEvents.Where(x => ids.Contains(x.Id)).ToListAsync(); + if (outgoingEvents.Any()) + { + dbContext.RemoveRange(outgoingEvents); + } + } } diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs index 49add5df84..b253164161 100644 --- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs @@ -85,12 +85,17 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen await TriggerHandlersAsync(eventType, eventData); } - public override async Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) + public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) { await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData); } - public override async Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) + public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + { + throw new NotImplementedException(); + } + + public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) { var eventType = _eventTypes.GetOrDefault(incomingEvent.EventName); if (eventType == null) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index ec1901161a..23ae188170 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -162,7 +162,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); } - protected override async Task PublishToEventBusAsync(Type eventType, object eventData) + protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { await PublishAsync( eventType, @@ -196,7 +196,12 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen ); } - public override async Task ProcessFromInboxAsync( + public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + { + throw new NotImplementedException(); + } + + public async override Task ProcessFromInboxAsync( IncomingEventInfo incomingEvent, InboxConfig inboxConfig) { diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index 1564cfd213..c65e9a60d1 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -16,225 +16,290 @@ using Volo.Abp.Threading; using Volo.Abp.Timing; using Volo.Abp.Uow; -namespace Volo.Abp.EventBus.RabbitMq; - -/* TODO: How to handle unsubscribe to unbind on RabbitMq (may not be possible for) - */ -[Dependency(ReplaceServices = true)] -[ExposeServices(typeof(IDistributedEventBus), typeof(RabbitMqDistributedEventBus))] -public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDependency +namespace Volo.Abp.EventBus.RabbitMq { - protected AbpRabbitMqEventBusOptions AbpRabbitMqEventBusOptions { get; } - protected IConnectionPool ConnectionPool { get; } - protected IRabbitMqSerializer Serializer { get; } - - //TODO: Accessing to the List may not be thread-safe! - protected ConcurrentDictionary> HandlerFactories { get; } - protected ConcurrentDictionary EventTypes { get; } - protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; } - protected IRabbitMqMessageConsumer Consumer { get; private set; } - - public RabbitMqDistributedEventBus( - IOptions options, - IConnectionPool connectionPool, - IRabbitMqSerializer serializer, - IServiceScopeFactory serviceScopeFactory, - IOptions distributedEventBusOptions, - IRabbitMqMessageConsumerFactory messageConsumerFactory, - ICurrentTenant currentTenant, - IUnitOfWorkManager unitOfWorkManager, - IGuidGenerator guidGenerator, - IClock clock) - : base( - serviceScopeFactory, - currentTenant, - unitOfWorkManager, - distributedEventBusOptions, - guidGenerator, - clock) + /* TODO: How to handle unsubscribe to unbind on RabbitMq (may not be possible for) + */ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(IDistributedEventBus), typeof(RabbitMqDistributedEventBus))] + public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDependency { - ConnectionPool = connectionPool; - Serializer = serializer; - MessageConsumerFactory = messageConsumerFactory; - AbpRabbitMqEventBusOptions = options.Value; - - HandlerFactories = new ConcurrentDictionary>(); - EventTypes = new ConcurrentDictionary(); - } + protected AbpRabbitMqEventBusOptions AbpRabbitMqEventBusOptions { get; } + protected IConnectionPool ConnectionPool { get; } + protected IRabbitMqSerializer Serializer { get; } + + //TODO: Accessing to the List may not be thread-safe! + protected ConcurrentDictionary> HandlerFactories { get; } + protected ConcurrentDictionary EventTypes { get; } + protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; } + protected IRabbitMqMessageConsumer Consumer { get; private set; } + + public RabbitMqDistributedEventBus( + IOptions options, + IConnectionPool connectionPool, + IRabbitMqSerializer serializer, + IServiceScopeFactory serviceScopeFactory, + IOptions distributedEventBusOptions, + IRabbitMqMessageConsumerFactory messageConsumerFactory, + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + IGuidGenerator guidGenerator, + IClock clock) + : base( + serviceScopeFactory, + currentTenant, + unitOfWorkManager, + distributedEventBusOptions, + guidGenerator, + clock) + { + ConnectionPool = connectionPool; + Serializer = serializer; + MessageConsumerFactory = messageConsumerFactory; + AbpRabbitMqEventBusOptions = options.Value; - public void Initialize() - { - Consumer = MessageConsumerFactory.Create( - new ExchangeDeclareConfiguration( - AbpRabbitMqEventBusOptions.ExchangeName, - type: "direct", - durable: true - ), - new QueueDeclareConfiguration( - AbpRabbitMqEventBusOptions.ClientName, - durable: true, - exclusive: false, - autoDelete: false - ), - AbpRabbitMqEventBusOptions.ConnectionName - ); - - Consumer.OnMessageReceived(ProcessEventAsync); - - SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); - } + HandlerFactories = new ConcurrentDictionary>(); + EventTypes = new ConcurrentDictionary(); + } - private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea) - { - var eventName = ea.RoutingKey; - var eventType = EventTypes.GetOrDefault(eventName); - if (eventType == null) + public void Initialize() { - return; + Consumer = MessageConsumerFactory.Create( + new ExchangeDeclareConfiguration( + AbpRabbitMqEventBusOptions.ExchangeName, + type: "direct", + durable: true + ), + new QueueDeclareConfiguration( + AbpRabbitMqEventBusOptions.ClientName, + durable: true, + exclusive: false, + autoDelete: false + ), + AbpRabbitMqEventBusOptions.ConnectionName + ); + + Consumer.OnMessageReceived(ProcessEventAsync); + + SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); } - var eventBytes = ea.Body.ToArray(); + private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea) + { + var eventName = ea.RoutingKey; + var eventType = EventTypes.GetOrDefault(eventName); + if (eventType == null) + { + return; + } + + var eventBytes = ea.Body.ToArray(); + + if (await AddToInboxAsync(ea.BasicProperties.MessageId, eventName, eventType, eventBytes)) + { + return; + } + + var eventData = Serializer.Deserialize(eventBytes, eventType); + + await TriggerHandlersAsync(eventType, eventData); + } - if (await AddToInboxAsync(ea.BasicProperties.MessageId, eventName, eventType, eventBytes)) + public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) { - return; + var handlerFactories = GetOrCreateHandlerFactories(eventType); + + if (factory.IsInFactories(handlerFactories)) + { + return NullDisposable.Instance; + } + + handlerFactories.Add(factory); + + if (handlerFactories.Count == 1) //TODO: Multi-threading! + { + Consumer.BindAsync(EventNameAttribute.GetNameOrDefault(eventType)); + } + + return new EventHandlerFactoryUnregistrar(this, eventType, factory); } - var eventData = Serializer.Deserialize(eventBytes, eventType); + /// + public override void Unsubscribe(Func action) + { + Check.NotNull(action, nameof(action)); - await TriggerHandlersAsync(eventType, eventData); - } + GetOrCreateHandlerFactories(typeof(TEvent)) + .Locking(factories => + { + factories.RemoveAll( + factory => + { + var singleInstanceFactory = factory as SingleInstanceHandlerFactory; + if (singleInstanceFactory == null) + { + return false; + } + + var actionHandler = singleInstanceFactory.HandlerInstance as ActionEventHandler; + if (actionHandler == null) + { + return false; + } + + return actionHandler.Action == action; + }); + }); + } - public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) - { - var handlerFactories = GetOrCreateHandlerFactories(eventType); + /// + public override void Unsubscribe(Type eventType, IEventHandler handler) + { + GetOrCreateHandlerFactories(eventType) + .Locking(factories => + { + factories.RemoveAll( + factory => + factory is SingleInstanceHandlerFactory && + (factory as SingleInstanceHandlerFactory).HandlerInstance == handler + ); + }); + } - if (factory.IsInFactories(handlerFactories)) + /// + public override void Unsubscribe(Type eventType, IEventHandlerFactory factory) { - return NullDisposable.Instance; + GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Remove(factory)); } - handlerFactories.Add(factory); + /// + public override void UnsubscribeAll(Type eventType) + { + GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); + } - if (handlerFactories.Count == 1) //TODO: Multi-threading! + protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { - Consumer.BindAsync(EventNameAttribute.GetNameOrDefault(eventType)); + await PublishAsync(eventType, eventData, null); } - return new EventHandlerFactoryUnregistrar(this, eventType, factory); - } + protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) + { + unitOfWork.AddOrReplaceDistributedEvent(eventRecord); + } - /// - public override void Unsubscribe(Func action) - { - Check.NotNull(action, nameof(action)); + public override Task PublishFromOutboxAsync( + OutgoingEventInfo outgoingEvent, + OutboxConfig outboxConfig) + { + return PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); + } - GetOrCreateHandlerFactories(typeof(TEvent)) - .Locking(factories => + public async override Task PublishManyFromOutboxAsync( + IEnumerable outgoingEvents, + OutboxConfig outboxConfig) + { + using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) { - factories.RemoveAll( - factory => + var outgoingEventArray = outgoingEvents.ToArray(); + channel.ConfirmSelect(); + + var pendingConfirms = new ConcurrentDictionary(); + var failures = new ConcurrentBag(); + + void CleanPendingConfirms(ulong sequenceNumber, bool multiple, bool ack) + { + if (multiple) { - var singleInstanceFactory = factory as SingleInstanceHandlerFactory; - if (singleInstanceFactory == null) + var confirmed = pendingConfirms.Where(x => x.Key <= sequenceNumber); + foreach (var entry in confirmed) { - return false; + pendingConfirms.TryRemove(entry.Key, out var eventId); + + if (!ack) + { + failures.Add(eventId); + } } + } + else + { + pendingConfirms.TryRemove(sequenceNumber, out var eventId); - var actionHandler = singleInstanceFactory.HandlerInstance as ActionEventHandler; - if (actionHandler == null) + if (!ack) { - return false; + failures.Add(eventId); } + } + } - return actionHandler.Action == action; - }); - }); - } + foreach (var outgoingEvent in outgoingEventArray) + { + pendingConfirms.TryAdd(channel.NextPublishSeqNo, outgoingEvent.Id); + await PublishInternalAsync(channel, outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); + } - /// - public override void Unsubscribe(Type eventType, IEventHandler handler) - { - GetOrCreateHandlerFactories(eventType) - .Locking(factories => - { - factories.RemoveAll( - factory => - factory is SingleInstanceHandlerFactory && - (factory as SingleInstanceHandlerFactory).HandlerInstance == handler - ); - }); - } + channel.BasicAcks += (_, ea) => CleanPendingConfirms(ea.DeliveryTag, ea.Multiple, true); + channel.BasicNacks += (_, ea) => CleanPendingConfirms(ea.DeliveryTag, ea.Multiple, false); - /// - public override void Unsubscribe(Type eventType, IEventHandlerFactory factory) - { - GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Remove(factory)); - } - - /// - public override void UnsubscribeAll(Type eventType) - { - GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); - } + channel.WaitForConfirms(); - protected override async Task PublishToEventBusAsync(Type eventType, object eventData) - { - await PublishAsync(eventType, eventData, null); - } - - protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) - { - unitOfWork.AddOrReplaceDistributedEvent(eventRecord); - } - - public override Task PublishFromOutboxAsync( - OutgoingEventInfo outgoingEvent, - OutboxConfig outboxConfig) - { - return PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); - } + return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !failures.Contains(x.Id)).ToList()); + } + } - public override async Task ProcessFromInboxAsync( - IncomingEventInfo incomingEvent, - InboxConfig inboxConfig) - { - var eventType = EventTypes.GetOrDefault(incomingEvent.EventName); - if (eventType == null) + public async override Task ProcessFromInboxAsync( + IncomingEventInfo incomingEvent, + InboxConfig inboxConfig) { - return; + var eventType = EventTypes.GetOrDefault(incomingEvent.EventName); + if (eventType == null) + { + return; + } + + var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); + var exceptions = new List(); + await TriggerHandlersAsync(eventType, eventData, exceptions, inboxConfig); + if (exceptions.Any()) + { + ThrowOriginalExceptions(eventType, exceptions); + } } - var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); - var exceptions = new List(); - await TriggerHandlersAsync(eventType, eventData, exceptions, inboxConfig); - if (exceptions.Any()) + protected override byte[] Serialize(object eventData) { - ThrowOriginalExceptions(eventType, exceptions); + return Serializer.Serialize(eventData); } - } - protected override byte[] Serialize(object eventData) - { - return Serializer.Serialize(eventData); - } + public virtual Task PublishAsync(Type eventType, object eventData, IBasicProperties properties, Dictionary headersArguments = null) + { + var eventName = EventNameAttribute.GetNameOrDefault(eventType); + var body = Serializer.Serialize(eventData); - public Task PublishAsync(Type eventType, object eventData, IBasicProperties properties, Dictionary headersArguments = null) - { - var eventName = EventNameAttribute.GetNameOrDefault(eventType); - var body = Serializer.Serialize(eventData); + return PublishAsync(eventName, body, properties, headersArguments); + } - return PublishAsync(eventName, body, properties, headersArguments); - } + protected virtual Task PublishAsync( + string eventName, + byte[] body, + IBasicProperties properties, + Dictionary headersArguments = null, + Guid? eventId = null) + { + using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) + { + return PublishInternalAsync(channel, eventName, body, properties, headersArguments, eventId); + } + } - protected Task PublishAsync( - string eventName, - byte[] body, - IBasicProperties properties, - Dictionary headersArguments = null, - Guid? eventId = null) - { - using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) + protected virtual Task PublishInternalAsync( + IModel channel, + string eventName, + byte[] body, + IBasicProperties properties, + Dictionary headersArguments = null, + Guid? eventId = null) { channel.ExchangeDeclare( AbpRabbitMqEventBusOptions.ExchangeName, @@ -262,68 +327,68 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe basicProperties: properties, body: body ); - } - return Task.CompletedTask; - } - - private void SetEventMessageHeaders(IBasicProperties properties, Dictionary headersArguments) - { - if (headersArguments == null) - { - return; + return Task.CompletedTask; } - properties.Headers ??= new Dictionary(); - - foreach (var header in headersArguments) + private void SetEventMessageHeaders(IBasicProperties properties, Dictionary headersArguments) { - properties.Headers[header.Key] = header.Value; - } - } - - private List GetOrCreateHandlerFactories(Type eventType) - { - return HandlerFactories.GetOrAdd( - eventType, - type => + if (headersArguments == null) { - var eventName = EventNameAttribute.GetNameOrDefault(type); - EventTypes[eventName] = type; - return new List(); + return; } - ); - } - protected override IEnumerable GetHandlerFactories(Type eventType) - { - var handlerFactoryList = new List(); + properties.Headers ??= new Dictionary(); - foreach (var handlerFactory in - HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) - { - handlerFactoryList.Add( - new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); + foreach (var header in headersArguments) + { + properties.Headers[header.Key] = header.Value; + } } - return handlerFactoryList.ToArray(); - } - - private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType) - { - //Should trigger same type - if (handlerEventType == targetEventType) + private List GetOrCreateHandlerFactories(Type eventType) { - return true; + return HandlerFactories.GetOrAdd( + eventType, + type => + { + var eventName = EventNameAttribute.GetNameOrDefault(type); + EventTypes[eventName] = type; + return new List(); + } + ); } - //TODO: Support inheritance? But it does not support on subscription to RabbitMq! - //Should trigger for inherited types - if (handlerEventType.IsAssignableFrom(targetEventType)) + protected override IEnumerable GetHandlerFactories(Type eventType) { - return true; + var handlerFactoryList = new List(); + + foreach (var handlerFactory in + HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) + { + handlerFactoryList.Add( + new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); + } + + return handlerFactoryList.ToArray(); } - return false; + private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType) + { + //Should trigger same type + if (handlerEventType == targetEventType) + { + return true; + } + + //TODO: Support inheritance? But it does not support on subscription to RabbitMq! + //Should trigger for inherited types + if (handlerEventType.IsAssignableFrom(targetEventType)) + { + return true; + } + + return false; + } } } diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index 0542a705eb..aad844a4d7 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -146,7 +146,7 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen await TriggerHandlersAsync(eventType, eventData); } - protected override async Task PublishToEventBusAsync(Type eventType, object eventData) + protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { await AbpRebusEventBusOptions.Publish(Rebus, eventType, eventData); } @@ -210,7 +210,12 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen return PublishToEventBusAsync(eventType, eventData); } - public override async Task ProcessFromInboxAsync( + public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + { + throw new NotImplementedException(); + } + + public async override Task ProcessFromInboxAsync( IncomingEventInfo incomingEvent, InboxConfig inboxConfig) { diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs index dee9d1f7d3..178dea1340 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs @@ -8,156 +8,162 @@ using Volo.Abp.MultiTenancy; using Volo.Abp.Timing; using Volo.Abp.Uow; -namespace Volo.Abp.EventBus.Distributed; - -public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventBus, ISupportsEventBoxes +namespace Volo.Abp.EventBus.Distributed { - protected IGuidGenerator GuidGenerator { get; } - protected IClock Clock { get; } - protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } - - protected DistributedEventBusBase( - IServiceScopeFactory serviceScopeFactory, - ICurrentTenant currentTenant, - IUnitOfWorkManager unitOfWorkManager, - IOptions abpDistributedEventBusOptions, - IGuidGenerator guidGenerator, - IClock clock - ) : base( - serviceScopeFactory, - currentTenant, - unitOfWorkManager) - { - GuidGenerator = guidGenerator; - Clock = clock; - AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; - } - - public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class + public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventBus, ISupportsEventBoxes { - return Subscribe(typeof(TEvent), handler); - } + protected IGuidGenerator GuidGenerator { get; } + protected IClock Clock { get; } + protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } + + protected DistributedEventBusBase( + IServiceScopeFactory serviceScopeFactory, + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + IOptions abpDistributedEventBusOptions, + IGuidGenerator guidGenerator, + IClock clock + ) : base( + serviceScopeFactory, + currentTenant, + unitOfWorkManager) + { + GuidGenerator = guidGenerator; + Clock = clock; + AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; + } - public override Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true) - { - return PublishAsync(eventType, eventData, onUnitOfWorkComplete, useOutbox: true); - } + public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class + { + return Subscribe(typeof(TEvent), handler); + } - public Task PublishAsync( - TEvent eventData, - bool onUnitOfWorkComplete = true, - bool useOutbox = true) - where TEvent : class - { - return PublishAsync(typeof(TEvent), eventData, onUnitOfWorkComplete, useOutbox); - } + public override Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true) + { + return PublishAsync(eventType, eventData, onUnitOfWorkComplete, useOutbox: true); + } - public async Task PublishAsync( - Type eventType, - object eventData, - bool onUnitOfWorkComplete = true, - bool useOutbox = true) - { - if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null) + public Task PublishAsync( + TEvent eventData, + bool onUnitOfWorkComplete = true, + bool useOutbox = true) + where TEvent : class { - AddToUnitOfWork( - UnitOfWorkManager.Current, - new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext(), useOutbox) - ); - return; + return PublishAsync(typeof(TEvent), eventData, onUnitOfWorkComplete, useOutbox); } - if (useOutbox) + public async Task PublishAsync( + Type eventType, + object eventData, + bool onUnitOfWorkComplete = true, + bool useOutbox = true) { - if (await AddToOutboxAsync(eventType, eventData)) + if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null) { + AddToUnitOfWork( + UnitOfWorkManager.Current, + new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext(), useOutbox) + ); return; } - } - await PublishToEventBusAsync(eventType, eventData); - } + if (useOutbox) + { + if (await AddToOutboxAsync(eventType, eventData)) + { + return; + } + } - public abstract Task PublishFromOutboxAsync( - OutgoingEventInfo outgoingEvent, - OutboxConfig outboxConfig - ); + await PublishToEventBusAsync(eventType, eventData); + } - public abstract Task ProcessFromInboxAsync( - IncomingEventInfo incomingEvent, - InboxConfig inboxConfig); + public abstract Task PublishFromOutboxAsync( + OutgoingEventInfo outgoingEvent, + OutboxConfig outboxConfig + ); - private async Task AddToOutboxAsync(Type eventType, object eventData) - { - var unitOfWork = UnitOfWorkManager.Current; - if (unitOfWork == null) - { - return false; - } + public abstract Task PublishManyFromOutboxAsync( + IEnumerable outgoingEvents, + OutboxConfig outboxConfig + ); + + public abstract Task ProcessFromInboxAsync( + IncomingEventInfo incomingEvent, + InboxConfig inboxConfig); - foreach (var outboxConfig in AbpDistributedEventBusOptions.Outboxes.Values) + private async Task AddToOutboxAsync(Type eventType, object eventData) { - if (outboxConfig.Selector == null || outboxConfig.Selector(eventType)) + var unitOfWork = UnitOfWorkManager.Current; + if (unitOfWork == null) { - var eventOutbox = (IEventOutbox)unitOfWork.ServiceProvider.GetRequiredService(outboxConfig.ImplementationType); - var eventName = EventNameAttribute.GetNameOrDefault(eventType); - await eventOutbox.EnqueueAsync( - new OutgoingEventInfo( - GuidGenerator.Create(), - eventName, - Serialize(eventData), - Clock.Now - ) - ); - return true; + return false; } - } - return false; - } + foreach (var outboxConfig in AbpDistributedEventBusOptions.Outboxes.Values) + { + if (outboxConfig.Selector == null || outboxConfig.Selector(eventType)) + { + var eventOutbox = (IEventOutbox)unitOfWork.ServiceProvider.GetRequiredService(outboxConfig.ImplementationType); + var eventName = EventNameAttribute.GetNameOrDefault(eventType); + await eventOutbox.EnqueueAsync( + new OutgoingEventInfo( + GuidGenerator.Create(), + eventName, + Serialize(eventData), + Clock.Now + ) + ); + return true; + } + } - protected async Task AddToInboxAsync( - string messageId, - string eventName, - Type eventType, - byte[] eventBytes) - { - if (AbpDistributedEventBusOptions.Inboxes.Count <= 0) - { return false; } - using (var scope = ServiceScopeFactory.CreateScope()) + protected async Task AddToInboxAsync( + string messageId, + string eventName, + Type eventType, + byte[] eventBytes) { - foreach (var inboxConfig in AbpDistributedEventBusOptions.Inboxes.Values) + if (AbpDistributedEventBusOptions.Inboxes.Count <= 0) { - if (inboxConfig.EventSelector == null || inboxConfig.EventSelector(eventType)) - { - var eventInbox = (IEventInbox)scope.ServiceProvider.GetRequiredService(inboxConfig.ImplementationType); + return false; + } - if (!messageId.IsNullOrEmpty()) + using (var scope = ServiceScopeFactory.CreateScope()) + { + foreach (var inboxConfig in AbpDistributedEventBusOptions.Inboxes.Values) + { + if (inboxConfig.EventSelector == null || inboxConfig.EventSelector(eventType)) { - if (await eventInbox.ExistsByMessageIdAsync(messageId)) + var eventInbox = (IEventInbox) scope.ServiceProvider.GetRequiredService(inboxConfig.ImplementationType); + + if (!messageId.IsNullOrEmpty()) { - continue; + if (await eventInbox.ExistsByMessageIdAsync(messageId)) + { + continue; + } } - } - await eventInbox.EnqueueAsync( - new IncomingEventInfo( - GuidGenerator.Create(), - messageId, - eventName, - eventBytes, - Clock.Now - ) - ); + await eventInbox.EnqueueAsync( + new IncomingEventInfo( + GuidGenerator.Create(), + messageId, + eventName, + eventBytes, + Clock.Now + ) + ); + } } } + + return true; } - return true; + protected abstract byte[] Serialize(object eventData); } - - protected abstract byte[] Serialize(object eventData); } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs index 6ecefcf002..018747945c 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs @@ -12,4 +12,6 @@ public interface IEventOutbox Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default); Task DeleteAsync(Guid id); + + Task DeleteManyAsync(IEnumerable ids); } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs index 37405fb58a..7c23be5ecd 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; namespace Volo.Abp.EventBus.Distributed; @@ -9,6 +10,11 @@ public interface ISupportsEventBoxes OutboxConfig outboxConfig ); + Task PublishManyFromOutboxAsync( + IEnumerable outgoingEvents, + OutboxConfig outboxConfig + ); + Task ProcessFromInboxAsync( IncomingEventInfo incomingEvent, InboxConfig inboxConfig diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs index 50c1a4e009..0d22245d13 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs @@ -98,7 +98,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency break; } - Logger.LogInformation($"Found {waitingEvents.Count} events in the inbox."); + //Logger.LogInformation($"Found {waitingEvents.Count} events in the inbox."); foreach (var waitingEvent in waitingEvents) { @@ -113,7 +113,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency await uow.CompleteAsync(); } - Logger.LogInformation($"Processed the incoming event with id = {waitingEvent.Id:N}"); + //Logger.LogInformation($"Processed the incoming event with id = {waitingEvent.Id:N}"); } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs index 6ed60ba34c..d42110e4ea 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs @@ -1,4 +1,6 @@ using System; +using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -76,25 +78,20 @@ public class OutboxSender : IOutboxSender, ITransientDependency while (true) { var waitingEvents = await Outbox.GetWaitingEventsAsync(EventBusBoxesOptions.OutboxWaitingEventMaxCount, StoppingToken); - if (waitingEvents.Count <= 0) + if (waitingEvents.Count < 1000) { break; } Logger.LogInformation($"Found {waitingEvents.Count} events in the outbox."); - foreach (var waitingEvent in waitingEvents) - { - await DistributedEventBus - .AsSupportsEventBoxes() - .PublishFromOutboxAsync( - waitingEvent, - OutboxConfig - ); + var result = await DistributedEventBus + .AsSupportsEventBoxes() + .PublishManyFromOutboxAsync(waitingEvents, OutboxConfig); - await Outbox.DeleteAsync(waitingEvent.Id); - Logger.LogInformation($"Sent the event to the message broker with id = {waitingEvent.Id:N}"); - } + await Outbox.DeleteManyAsync(result.PublishedOutgoingEvents.Select(x => x.Id).ToArray()); + + Logger.LogInformation($"Sent {result.PublishedOutgoingEvents.Count} events to message broker"); } } else diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs new file mode 100644 index 0000000000..515dec1ae6 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EventBus; + +public class MultipleOutgoingEventPublishResult +{ + public IReadOnlyList PublishedOutgoingEvents { get; } + + public MultipleOutgoingEventPublishResult(IReadOnlyList outgoingEvents) + { + PublishedOutgoingEvents = outgoingEvents; + } +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs index 1f3ae999b0..fae0709953 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs @@ -68,4 +68,18 @@ public class MongoDbContextEventOutbox : IMongoDbContextEventOu await dbContext.OutgoingEvents.DeleteOneAsync(x => x.Id.Equals(id)); } } + + [UnitOfWork] + public async Task DeleteManyAsync(IEnumerable ids) + { + var dbContext = (IHasEventOutbox)await MongoDbContextProvider.GetDbContextAsync(); + if (dbContext.SessionHandle != null) + { + await dbContext.OutgoingEvents.DeleteManyAsync(dbContext.SessionHandle, x => ids.Contains(x.Id)); + } + else + { + await dbContext.OutgoingEvents.DeleteManyAsync(x => ids.Contains(x.Id)); + } + } } From 5ee59194637c187ecfee34aab7163c801e182044 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sun, 9 Jan 2022 20:24:39 +0800 Subject: [PATCH 02/60] Batch publish events from outbox to the kafka event bus --- .../Kafka/KafkaDistributedEventBus.cs | 44 ++++++++++++++----- .../Abp/EventBus/Kafka/MessageExtensions.cs | 18 ++++++++ .../EventBus/Distributed/InboxProcessor.cs | 4 +- .../Abp/EventBus/Distributed/OutboxSender.cs | 2 +- 4 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index 23ae188170..aef7662095 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -77,12 +77,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen return; } - string messageId = null; - - if (message.Headers.TryGetLastBytes("messageId", out var messageIdBytes)) - { - messageId = System.Text.Encoding.UTF8.GetString(messageIdBytes); - } + var messageId = message.GetMessageId(); if (await AddToInboxAsync(messageId, eventName, eventType, message.Value)) { @@ -196,9 +191,38 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen ); } - public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { - throw new NotImplementedException(); + var pendingConfirms = new ConcurrentDictionary(); + var outgoingEventArray = outgoingEvents.ToArray(); + + var tasks = new List(); + foreach (var outgoingEvent in outgoingEventArray) + { + var messageId = outgoingEvent.Id.ToString("N"); + pendingConfirms.TryAdd(messageId, outgoingEvent.Id); + + var task = PublishAsync( + AbpKafkaEventBusOptions.TopicName, + outgoingEvent.EventName, + outgoingEvent.EventData, + new Headers { { "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)} }, + null + ); + + tasks.Add(task.ContinueWith(t => + { + if (!t.IsFaulted) + { + var message = t.Result.Message; + pendingConfirms.TryRemove(message.GetMessageId(), out _); + } + })); + } + + await Task.WhenAll(tasks); + + return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !pendingConfirms.Select(p => p.Value).Contains(x.Id)).ToList()); } public async override Task ProcessFromInboxAsync( @@ -244,13 +268,13 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen return PublishAsync(topicName, eventName, body, headers, headersArguments); } - private async Task PublishAsync(string topicName, string eventName, byte[] body, Headers headers, Dictionary headersArguments) + private Task> PublishAsync(string topicName, string eventName, byte[] body, Headers headers, Dictionary headersArguments) { var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); SetEventMessageHeaders(headers, headersArguments); - await producer.ProduceAsync( + return producer.ProduceAsync( topicName, new Message { diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs new file mode 100644 index 0000000000..17a80ec87c --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs @@ -0,0 +1,18 @@ +using Confluent.Kafka; + +namespace Volo.Abp.EventBus.Kafka; + +public static class MessageExtensions +{ + public static string GetMessageId(this Message message) + { + string messageId = null; + + if (message.Headers.TryGetLastBytes("messageId", out var messageIdBytes)) + { + messageId = System.Text.Encoding.UTF8.GetString(messageIdBytes); + } + + return messageId; + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs index 0d22245d13..50c1a4e009 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs @@ -98,7 +98,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency break; } - //Logger.LogInformation($"Found {waitingEvents.Count} events in the inbox."); + Logger.LogInformation($"Found {waitingEvents.Count} events in the inbox."); foreach (var waitingEvent in waitingEvents) { @@ -113,7 +113,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency await uow.CompleteAsync(); } - //Logger.LogInformation($"Processed the incoming event with id = {waitingEvent.Id:N}"); + Logger.LogInformation($"Processed the incoming event with id = {waitingEvent.Id:N}"); } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs index d42110e4ea..17dec0a810 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs @@ -78,7 +78,7 @@ public class OutboxSender : IOutboxSender, ITransientDependency while (true) { var waitingEvents = await Outbox.GetWaitingEventsAsync(EventBusBoxesOptions.OutboxWaitingEventMaxCount, StoppingToken); - if (waitingEvents.Count < 1000) + if (waitingEvents.Count <= 0) { break; } From f36c44fc78203c4f520cfce62d99fce82ecb24c4 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sun, 9 Jan 2022 20:50:17 +0800 Subject: [PATCH 03/60] Batch publish events from outbox to the azure event bus --- .../Azure/AzureDistributedEventBus.cs | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs index b253164161..a253a8ba96 100644 --- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs @@ -90,9 +90,38 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData); } - public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { - throw new NotImplementedException(); + var outgoingEventArray = outgoingEvents.ToArray(); + var failures = new List(); + + var publisher = await _publisherPool.GetAsync( + _options.TopicName, + _options.ConnectionName); + + using var messageBatch = await publisher.CreateMessageBatchAsync(); + + var failed = false; + foreach (var outgoingEvent in outgoingEventArray) + { + if (failed) + { + failures.Add(outgoingEvent.Id); + continue; + } + + var body = _serializer.Serialize(outgoingEvent.EventData); + + if (!messageBatch.TryAddMessage(new ServiceBusMessage(body) { Subject = outgoingEvent.EventName })) + { + failed = true; + failures.Add(outgoingEvent.Id); + } + } + + await publisher.SendMessagesAsync(messageBatch); + + return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !failures.Contains(x.Id)).ToList()); } public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) @@ -183,7 +212,7 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen .Locking(factories => factories.Clear()); } - protected override async Task PublishToEventBusAsync(Type eventType, object eventData) + protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { await PublishAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData); } From 9ec54780d475ca56c2c6086039a9bb4d42a4371c Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sun, 9 Jan 2022 21:18:48 +0800 Subject: [PATCH 04/60] Batch publish events from outbox to the rebus event bus --- .../EventBus/Rebus/AbpRebusEventBusOptions.cs | 13 +--------- .../Rebus/RebusDistributedEventBus.cs | 24 ++++++++++++++++--- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs index 5238cdd8bb..4c39b9bf1d 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs @@ -20,24 +20,13 @@ public class AbpRebusEventBusOptions } private Action _configurer; - [NotNull] - public Func Publish { - get => _publish; - set => _publish = Check.NotNull(value, nameof(value)); - } - private Func _publish; + public Func Publish { get; set; } public AbpRebusEventBusOptions() { - _publish = DefaultPublish; _configurer = DefaultConfigure; } - private async Task DefaultPublish(IBus bus, Type eventType, object eventData) - { - await bus.Advanced.Routing.Send(InputQueueName, eventData); - } - private void DefaultConfigure(RebusConfigurer configure) { configure.Subscriptions(s => s.StoreInMemory()); diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index aad844a4d7..fc6a0667af 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -148,7 +148,18 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { - await AbpRebusEventBusOptions.Publish(Rebus, eventType, eventData); + await PublishAsync(eventType, eventData); + } + + protected virtual async Task PublishAsync(Type eventType, object eventData) + { + if (AbpRebusEventBusOptions.Publish != null) + { + await AbpRebusEventBusOptions.Publish(Rebus, eventType, eventData); + return; + } + + await Rebus.Advanced.Routing.Send(AbpRebusEventBusOptions.InputQueueName, eventData); } protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) @@ -210,9 +221,16 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen return PublishToEventBusAsync(eventType, eventData); } - public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { - throw new NotImplementedException(); + var outgoingEventArray = outgoingEvents.ToArray(); + + foreach (var outgoingEvent in outgoingEventArray) + { + await PublishFromOutboxAsync(outgoingEvent, outboxConfig); + } + + return new MultipleOutgoingEventPublishResult(outgoingEventArray); } public async override Task ProcessFromInboxAsync( From 325f82a3d7a7448f7e7b3b73438f965810b93500 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Mon, 10 Jan 2022 17:11:06 +0800 Subject: [PATCH 05/60] Update AzureDistributedEventBus --- .../Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs index ffcf6df8c7..5e91a224c5 100644 --- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs @@ -110,9 +110,7 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen continue; } - var body = _serializer.Serialize(outgoingEvent.EventData); - - if (!messageBatch.TryAddMessage(new ServiceBusMessage(body) { Subject = outgoingEvent.EventName })) + if (!messageBatch.TryAddMessage(new ServiceBusMessage(outgoingEvent.EventData) { Subject = outgoingEvent.EventName })) { failed = true; failures.Add(outgoingEvent.Id); From 3364e7e2273b395b87a5395513a3b5585ac5ddf9 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 14 Jan 2022 14:28:30 +0800 Subject: [PATCH 06/60] Update RabbitMqDistributedEventBus.cs --- .../Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index a9cfb4dc23..a9d6a42207 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -238,7 +238,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe foreach (var outgoingEvent in outgoingEventArray) { pendingConfirms.TryAdd(channel.NextPublishSeqNo, outgoingEvent.Id); - await PublishInternalAsync(channel, outgoingEvent.EventName, outgoingEvent.EventData, null, + await PublishAsync(channel, outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); } @@ -293,11 +293,11 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe { using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) { - return PublishInternalAsync(channel, eventName, body, properties, headersArguments, eventId); + return PublishAsync(channel, eventName, body, properties, headersArguments, eventId); } } - protected virtual Task PublishInternalAsync( + protected virtual Task PublishAsync( IModel channel, string eventName, byte[] body, From 0c45dccd54f9c70b6fb2008df81b22655a5f2d20 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 25 Jan 2022 14:17:09 +0800 Subject: [PATCH 07/60] Update Rebus --- .../Abp/EventBus/Rebus/RebusDistributedEventBus.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index 81611e180a..a5cac31235 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Rebus.Bus; using Rebus.Pipeline; +using Rebus.Transport; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; using Volo.Abp.Guids; @@ -227,9 +228,14 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen { var outgoingEventArray = outgoingEvents.ToArray(); - foreach (var outgoingEvent in outgoingEventArray) + using (var scope = new RebusTransactionScope()) { - await PublishFromOutboxAsync(outgoingEvent, outboxConfig); + foreach (var outgoingEvent in outgoingEventArray) + { + await PublishFromOutboxAsync(outgoingEvent, outboxConfig); + } + + await scope.CompleteAsync(); } return new MultipleOutgoingEventPublishResult(outgoingEventArray); From 282d9ada01463503afbfce2bcbe34f68b9cb48db Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 25 Jan 2022 14:20:56 +0800 Subject: [PATCH 08/60] Update RabbitMqMessageConsumer.cs --- .../Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs index 049e8da712..8ce184295a 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs @@ -176,14 +176,6 @@ public class RabbitMqMessageConsumer : IRabbitMqMessageConsumer, ITransientDepen } catch (Exception ex) { - if (ex is OperationInterruptedException operationInterruptedException && - operationInterruptedException.ShutdownReason.ReplyCode == 406 && - operationInterruptedException.Message.Contains("arg 'x-dead-letter-exchange'")) - { - Logger.LogException(ex, LogLevel.Warning); - await ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning); - } - Logger.LogException(ex, LogLevel.Warning); await ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning); } From 7221600e75719a494447953b789e79539fb7f10e Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Thu, 17 Mar 2022 21:55:33 +0800 Subject: [PATCH 09/60] Refactor --- .../Azure/AzureDistributedEventBus.cs | 15 +--- .../Kafka/KafkaDistributedEventBus.cs | 76 ++++++++++++------- .../RabbitMq/RabbitMqDistributedEventBus.cs | 69 +++++++---------- .../Rebus/RebusDistributedEventBus.cs | 8 +- .../Distributed/AbpEventBusBoxesOptions.cs | 6 ++ .../Distributed/DistributedEventBusBase.cs | 2 +- .../Distributed/ISupportsEventBoxes.cs | 2 +- .../Abp/EventBus/Distributed/OutboxSender.cs | 46 +++++++++-- .../MultipleOutgoingEventPublishResult.cs | 14 ---- .../Volo/Abp/Kafka/ConsumerPool.cs | 1 + .../Volo/Abp/Kafka/KafkaMessageConsumer.cs | 21 +++-- .../Volo/Abp/Kafka/ProducerPool.cs | 31 +++++--- 12 files changed, 161 insertions(+), 130 deletions(-) delete mode 100644 framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs index add6670bd4..fa6e155081 100644 --- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs @@ -92,10 +92,9 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, outgoingEvent.Id); } - public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { var outgoingEventArray = outgoingEvents.ToArray(); - var failures = new List(); var publisher = await _publisherPool.GetAsync( _options.TopicName, @@ -103,25 +102,15 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen using var messageBatch = await publisher.CreateMessageBatchAsync(); - var failed = false; foreach (var outgoingEvent in outgoingEventArray) { - if (failed) - { - failures.Add(outgoingEvent.Id); - continue; - } - if (!messageBatch.TryAddMessage(new ServiceBusMessage(outgoingEvent.EventData) { Subject = outgoingEvent.EventName })) { - failed = true; - failures.Add(outgoingEvent.Id); + throw new AbpException("The message is too large to fit in the batch. Set AbpEventBusBoxesOptions.OutboxWaitingEventMaxCount to reduce the number"); } } await publisher.SendMessagesAsync(messageBatch); - - return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !failures.Contains(x.Id)).ToList()); } public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index e271ad502c..4dba728c51 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -193,38 +193,40 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen ); } - public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { - var pendingConfirms = new ConcurrentDictionary(); + var producer = ProducerPool.Get(); var outgoingEventArray = outgoingEvents.ToArray(); - - var tasks = new List(); - foreach (var outgoingEvent in outgoingEventArray) + producer.BeginTransaction(); + try { - var messageId = outgoingEvent.Id.ToString("N"); - pendingConfirms.TryAdd(messageId, outgoingEvent.Id); - - var task = PublishAsync( - AbpKafkaEventBusOptions.TopicName, - outgoingEvent.EventName, - outgoingEvent.EventData, - new Headers { { "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)} }, - null - ); - - tasks.Add(task.ContinueWith(t => - { - if (!t.IsFaulted) - { - var message = t.Result.Message; - pendingConfirms.TryRemove(message.GetMessageId(), out _); - } - })); + foreach (var outgoingEvent in outgoingEventArray) + { + var messageId = outgoingEvent.Id.ToString("N"); + var headers = new Headers + { + { "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)} + }; + + producer.Produce( + AbpKafkaEventBusOptions.TopicName, + new Message + { + Key = outgoingEvent.EventName, + Value = outgoingEvent.EventData, + Headers = headers + }); + } + + producer.CommitTransaction(); } - - await Task.WhenAll(tasks); - - return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !pendingConfirms.Select(p => p.Value).Contains(x.Id)).ToList()); + catch (Exception e) + { + producer.AbortTransaction(); + throw; + } + + return Task.CompletedTask; } public async override Task ProcessFromInboxAsync( @@ -270,10 +272,26 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen return PublishAsync(topicName, eventName, body, headers, headersArguments); } - private Task> PublishAsync(string topicName, string eventName, byte[] body, Headers headers, Dictionary headersArguments) + private Task> PublishAsync( + string topicName, + string eventName, + byte[] body, + Headers headers, + Dictionary headersArguments) { var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); + return PublishAsync(producer, topicName, eventName, body, headers, headersArguments); + } + + private Task> PublishAsync( + IProducer producer, + string topicName, + string eventName, + byte[] body, + Headers headers, + Dictionary headersArguments) + { SetEventMessageHeaders(headers, headersArguments); return producer.ProduceAsync( diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index 9b3bc5b76e..b9210a6eb8 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -34,6 +34,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; } protected IRabbitMqMessageConsumer Consumer { get; private set; } + private bool _exchangeCreated; + public RabbitMqDistributedEventBus( IOptions options, IConnectionPool connectionPool, @@ -84,6 +86,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe Consumer.OnMessageReceived(ProcessEventAsync); SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); + + } private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea) @@ -197,7 +201,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe return PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); } - public async override Task PublishManyFromOutboxAsync( + public async override Task PublishManyFromOutboxAsync( IEnumerable outgoingEvents, OutboxConfig outboxConfig) { @@ -206,48 +210,17 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe var outgoingEventArray = outgoingEvents.ToArray(); channel.ConfirmSelect(); - var pendingConfirms = new ConcurrentDictionary(); - var failures = new ConcurrentBag(); - - void CleanPendingConfirms(ulong sequenceNumber, bool multiple, bool ack) - { - if (multiple) - { - var confirmed = pendingConfirms.Where(x => x.Key <= sequenceNumber); - foreach (var entry in confirmed) - { - pendingConfirms.TryRemove(entry.Key, out var eventId); - - if (!ack) - { - failures.Add(eventId); - } - } - } - else - { - pendingConfirms.TryRemove(sequenceNumber, out var eventId); - - if (!ack) - { - failures.Add(eventId); - } - } - } - foreach (var outgoingEvent in outgoingEventArray) { - pendingConfirms.TryAdd(channel.NextPublishSeqNo, outgoingEvent.Id); - await PublishAsync(channel, outgoingEvent.EventName, outgoingEvent.EventData, null, + await PublishAsync( + channel, + outgoingEvent.EventName, + outgoingEvent.EventData, + properties: null, eventId: outgoingEvent.Id); } - channel.BasicAcks += (_, ea) => CleanPendingConfirms(ea.DeliveryTag, ea.Multiple, true); - channel.BasicNacks += (_, ea) => CleanPendingConfirms(ea.DeliveryTag, ea.Multiple, false); - - channel.WaitForConfirms(); - - return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !failures.Contains(x.Id)).ToList()); + channel.WaitForConfirmsOrDie(); } } @@ -308,11 +281,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe Dictionary headersArguments = null, Guid? eventId = null) { - channel.ExchangeDeclare( - AbpRabbitMqEventBusOptions.ExchangeName, - "direct", - durable: true - ); + EnsureExchangeExists(channel); if (properties == null) { @@ -338,6 +307,20 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe return Task.CompletedTask; } + private void EnsureExchangeExists(IModel channel) + { + if (_exchangeCreated) + { + return; + } + + channel.ExchangeDeclare( + AbpRabbitMqEventBusOptions.ExchangeName, + "direct", + durable: true + ); + } + private void SetEventMessageHeaders(IBasicProperties properties, Dictionary headersArguments) { if (headersArguments == null) diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index a5cac31235..4ccd72ba15 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -162,7 +162,7 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen return; } - await Rebus.Advanced.Routing.Send(AbpRebusEventBusOptions.InputQueueName, eventData); + await Rebus.Publish(eventData); } protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) @@ -224,7 +224,7 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen return PublishToEventBusAsync(eventType, eventData); } - public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { var outgoingEventArray = outgoingEvents.ToArray(); @@ -234,11 +234,9 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen { await PublishFromOutboxAsync(outgoingEvent, outboxConfig); } - + await scope.CompleteAsync(); } - - return new MultipleOutgoingEventPublishResult(outgoingEventArray); } public async override Task ProcessFromInboxAsync( diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs index a46fc76238..749aa3d6d2 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs @@ -34,6 +34,11 @@ public class AbpEventBusBoxesOptions /// Default: 2 hours /// public TimeSpan WaitTimeToDeleteProcessedInboxEvents { get; set; } + + /// + /// Default: false + /// + public bool OutboxPublishInBatch { get; set; } public AbpEventBusBoxesOptions() { @@ -43,5 +48,6 @@ public class AbpEventBusBoxesOptions PeriodTimeSpan = TimeSpan.FromSeconds(2); DistributedLockWaitDuration = TimeSpan.FromSeconds(15); WaitTimeToDeleteProcessedInboxEvents = TimeSpan.FromHours(2); + OutboxPublishInBatch = false; } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs index 0add75f9c4..69ae91505d 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs @@ -85,7 +85,7 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB OutboxConfig outboxConfig ); - public abstract Task PublishManyFromOutboxAsync( + public abstract Task PublishManyFromOutboxAsync( IEnumerable outgoingEvents, OutboxConfig outboxConfig ); diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs index 7c23be5ecd..73f74a68ad 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs @@ -10,7 +10,7 @@ public interface ISupportsEventBoxes OutboxConfig outboxConfig ); - Task PublishManyFromOutboxAsync( + Task PublishManyFromOutboxAsync( IEnumerable outgoingEvents, OutboxConfig outboxConfig ); diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs index 17dec0a810..ac92740e86 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; @@ -84,14 +85,15 @@ public class OutboxSender : IOutboxSender, ITransientDependency } Logger.LogInformation($"Found {waitingEvents.Count} events in the outbox."); - - var result = await DistributedEventBus - .AsSupportsEventBoxes() - .PublishManyFromOutboxAsync(waitingEvents, OutboxConfig); - - await Outbox.DeleteManyAsync(result.PublishedOutgoingEvents.Select(x => x.Id).ToArray()); - - Logger.LogInformation($"Sent {result.PublishedOutgoingEvents.Count} events to message broker"); + + if (EventBusBoxesOptions.OutboxPublishInBatch) + { + await PublishOutgoingMessagesInBatchAsync(waitingEvents); + } + else + { + await PublishOutgoingMessagesAsync(waitingEvents); + } } } else @@ -105,4 +107,32 @@ public class OutboxSender : IOutboxSender, ITransientDependency } } } + + protected virtual async Task PublishOutgoingMessagesAsync(List waitingEvents) + { + foreach (var waitingEvent in waitingEvents) + { + await DistributedEventBus + .AsSupportsEventBoxes() + .PublishFromOutboxAsync( + waitingEvent, + OutboxConfig + ); + + await Outbox.DeleteAsync(waitingEvent.Id); + + Logger.LogInformation($"Sent the event to the message broker with id = {waitingEvent.Id:N}"); + } + } + + protected virtual async Task PublishOutgoingMessagesInBatchAsync(List waitingEvents) + { + await DistributedEventBus + .AsSupportsEventBoxes() + .PublishManyFromOutboxAsync(waitingEvents, OutboxConfig); + + await Outbox.DeleteManyAsync(waitingEvents.Select(x => x.Id).ToArray()); + + Logger.LogInformation($"Sent {waitingEvents.Count} events to message broker"); + } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs deleted file mode 100644 index 515dec1ae6..0000000000 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using Volo.Abp.EventBus.Distributed; - -namespace Volo.Abp.EventBus; - -public class MultipleOutgoingEventPublishResult -{ - public IReadOnlyList PublishedOutgoingEvents { get; } - - public MultipleOutgoingEventPublishResult(IReadOnlyList outgoingEvents) - { - PublishedOutgoingEvents = outgoingEvents; - } -} diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs index 81a5c195c1..7147c81271 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs @@ -76,6 +76,7 @@ public class ConsumerPool : IConsumerPool, ISingletonDependency try { + consumer.Value.Unsubscribe(); consumer.Value.Close(); consumer.Value.Dispose(); } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs index da21f48883..704c15845f 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs @@ -80,9 +80,11 @@ public class KafkaMessageConsumer : IKafkaMessageConsumer, ITransientDependency, protected virtual async Task Timer_Elapsed(AbpAsyncTimer timer) { - await CreateTopicAsync(); - Consume(); - Timer.Stop(); + if (Consumer == null) + { + await CreateTopicAsync(); + Consume(); + } } protected virtual async Task CreateTopicAsync() @@ -164,12 +166,21 @@ public class KafkaMessageConsumer : IKafkaMessageConsumer, ITransientDependency, public virtual void Dispose() { + Timer.Stop(); if (Consumer == null) { return; } - Consumer.Close(); - Consumer.Dispose(); + try + { + Consumer.Unsubscribe(); + Consumer.Close(); + Consumer.Dispose(); + Consumer = null; + } + catch (ObjectDisposedException) + { + } } } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs index 49287a20e9..0a31a3483a 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs @@ -17,6 +17,8 @@ public class ProducerPool : IProducerPool, ISingletonDependency protected ConcurrentDictionary>> Producers { get; } protected TimeSpan TotalDisposeWaitDuration { get; set; } = TimeSpan.FromSeconds(10); + + protected TimeSpan DefaultTransactionsWaitDuration { get; set; } = TimeSpan.FromSeconds(30); public ILogger Logger { get; set; } @@ -37,11 +39,18 @@ public class ProducerPool : IProducerPool, ISingletonDependency return Producers.GetOrAdd( connectionName, connection => new Lazy>(() => { - var config = Options.Connections.GetOrDefault(connection); - - Options.ConfigureProducer?.Invoke(new ProducerConfig(config)); - - return new ProducerBuilder(config).Build(); + var producerConfig = new ProducerConfig(Options.Connections.GetOrDefault(connection)); + Options.ConfigureProducer?.Invoke(producerConfig); + + if (producerConfig.TransactionalId.IsNullOrWhiteSpace()) + { + producerConfig.TransactionalId = Guid.NewGuid().ToString(); + } + + var producer = new ProducerBuilder(producerConfig).Build(); + producer.InitTransactions(DefaultTransactionsWaitDuration); + + return producer; })).Value; } @@ -69,7 +78,7 @@ public class ProducerPool : IProducerPool, ISingletonDependency foreach (var producer in Producers.Values) { var poolItemDisposeStopwatch = Stopwatch.StartNew(); - + try { producer.Value.Dispose(); @@ -77,19 +86,19 @@ public class ProducerPool : IProducerPool, ISingletonDependency catch { } - + poolItemDisposeStopwatch.Stop(); - + remainingWaitDuration = remainingWaitDuration > poolItemDisposeStopwatch.Elapsed ? remainingWaitDuration.Subtract(poolItemDisposeStopwatch.Elapsed) : TimeSpan.Zero; } - + poolDisposeStopwatch.Stop(); - + Logger.LogInformation( $"Disposed Kafka Producer Pool ({Producers.Count} producers in {poolDisposeStopwatch.Elapsed.TotalMilliseconds:0.00} ms)."); - + if (poolDisposeStopwatch.Elapsed.TotalSeconds > 5.0) { Logger.LogWarning( From f1cbcb16c7e7fa5f4b21603d981fdbf9619dc223 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 18 Mar 2022 11:55:14 +0800 Subject: [PATCH 10/60] Update RabbitMqDistributedEventBus --- .../RabbitMq/RabbitMqDistributedEventBus.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index b9210a6eb8..73ca0cbb61 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -86,8 +86,6 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe Consumer.OnMessageReceived(ProcessEventAsync); SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); - - } private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea) @@ -313,12 +311,20 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe { return; } - - channel.ExchangeDeclare( - AbpRabbitMqEventBusOptions.ExchangeName, - "direct", - durable: true - ); + + try + { + channel.ExchangeDeclarePassive(AbpRabbitMqEventBusOptions.ExchangeName); + } + catch (Exception) + { + channel.ExchangeDeclare( + AbpRabbitMqEventBusOptions.ExchangeName, + "direct", + durable: true + ); + } + _exchangeCreated = true; } private void SetEventMessageHeaders(IBasicProperties properties, Dictionary headersArguments) From b998015760649a3d36343d9a5b820787700fd345 Mon Sep 17 00:00:00 2001 From: muhammedaltug Date: Fri, 22 Apr 2022 16:53:28 +0300 Subject: [PATCH 11/60] proxy generation api versioning imp --- .../packages/schematics/src/models/method.ts | 7 +-- .../packages/schematics/src/utils/index.ts | 1 + .../packages/schematics/src/utils/methods.ts | 4 ++ .../packages/schematics/src/utils/service.ts | 46 +++++++++++++++++-- .../packages/schematics/src/utils/text.ts | 3 ++ 5 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 npm/ng-packs/packages/schematics/src/utils/methods.ts diff --git a/npm/ng-packs/packages/schematics/src/models/method.ts b/npm/ng-packs/packages/schematics/src/models/method.ts index b6ee494089..7e6147b2d4 100644 --- a/npm/ng-packs/packages/schematics/src/models/method.ts +++ b/npm/ng-packs/packages/schematics/src/models/method.ts @@ -1,5 +1,6 @@ import { eBindingSourceId, eMethodModifier } from '../enums'; -import { camel } from '../utils/text'; +import { camel, camelizeHyphen } from '../utils/text'; +import { getParamName } from '../utils/methods'; import { ParameterInBody } from './api-definition'; import { Property } from './model'; import { Omissible } from './util'; @@ -50,12 +51,12 @@ export class Body { ? shouldQuote(paramName) ? `${descriptorName}['${paramName}']` : `${descriptorName}.${paramName}` - : nameOnMethod; + : camelizeHyphen(nameOnMethod); switch (bindingSourceId) { case eBindingSourceId.Model: case eBindingSourceId.Query: - this.params.push(paramName === value ? value : `${paramName}: ${value}`); + this.params.push(paramName === value ? value : `${getParamName(paramName)}: ${value}`); break; case eBindingSourceId.Body: this.body = value; diff --git a/npm/ng-packs/packages/schematics/src/utils/index.ts b/npm/ng-packs/packages/schematics/src/utils/index.ts index e6df05b2e2..eeb12e6d9e 100644 --- a/npm/ng-packs/packages/schematics/src/utils/index.ts +++ b/npm/ng-packs/packages/schematics/src/utils/index.ts @@ -7,6 +7,7 @@ export * from './enum'; export * from './file'; export * from './generics'; export * from './import'; +export * from './methods'; export * from './model'; export * from './namespace'; export * from './path'; diff --git a/npm/ng-packs/packages/schematics/src/utils/methods.ts b/npm/ng-packs/packages/schematics/src/utils/methods.ts new file mode 100644 index 0000000000..7b7547b9e1 --- /dev/null +++ b/npm/ng-packs/packages/schematics/src/utils/methods.ts @@ -0,0 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const shouldQuote = require('should-quote'); +export const getParamName = (paramName: string) => + shouldQuote(paramName) ? `["${paramName}"]` : paramName; diff --git a/npm/ng-packs/packages/schematics/src/utils/service.ts b/npm/ng-packs/packages/schematics/src/utils/service.ts index 70655c821d..f37384d27d 100644 --- a/npm/ng-packs/packages/schematics/src/utils/service.ts +++ b/npm/ng-packs/packages/schematics/src/utils/service.ts @@ -20,6 +20,8 @@ import { createTypesToImportsReducer, removeTypeModifiers, } from './type'; +import { eBindingSourceId } from '../enums'; +import { camelizeHyphen } from './text'; export function serializeParameters(parameters: Property[]) { return parameters.map(p => p.name + p.optional + ': ' + p.type + p.default, '').join(', '); @@ -79,8 +81,12 @@ export function createActionToSignatureMapper() { return (action: Action) => { const signature = new Signature({ name: getMethodNameFromAction(action) }); - - signature.parameters = action.parametersOnMethod.map(p => { + const versionParameter = getVersionParameter(action); + const parameters = [ + ...action.parametersOnMethod, + ...(versionParameter ? [versionParameter] : []), + ]; + signature.parameters = parameters.map(p => { const type = adaptType(p.typeSimple); const parameter = new Property({ name: p.name, type }); parameter.setDefault(p.defaultValue); @@ -93,7 +99,41 @@ export function createActionToSignatureMapper() { } function getMethodNameFromAction(action: Action): string { - return action.uniqueName.split('Async')[0]; + return action.uniqueName.replace('Async', ''); +} + +function getVersionParameter(action: Action) { + const versionParameter = action.parameters.find( + p => + (p.name == 'apiVersion' && p.bindingSourceId == eBindingSourceId.Path) || + (p.name == 'api-version' && p.bindingSourceId == eBindingSourceId.Query), + ); + const bestVersion = findBestApiVersion(action); + return versionParameter && bestVersion + ? { + ...versionParameter, + name: camelizeHyphen(versionParameter.name), + defaultValue: `"${bestVersion}"`, + } + : null; +} + +// Implementation of https://github.com/abpframework/abp/commit/c3f77c1229508279015054a9b4f5586404a88a14#diff-a4dbf6be9a1aa21d8294f11047774949363ee6b601980bf3225e8046c0748c9eR101 +function findBestApiVersion(action: Action) { + /* + TODO: Implement configuredVersion when js proxies implemented + let configuredVersion = null; + if (action.supportedVersions.includes(configuredVersion)) { + return configuredVersion; + } + */ + + if (!action.supportedVersions?.length) { + // TODO: return configuredVersion if exists or '1.0' + return '1.0'; + } + //TODO: Ensure to get the latest version! + return action.supportedVersions[action.supportedVersions.length - 1]; } function createActionToImportsReducer( diff --git a/npm/ng-packs/packages/schematics/src/utils/text.ts b/npm/ng-packs/packages/schematics/src/utils/text.ts index b7a8def0f4..6215ffaacd 100644 --- a/npm/ng-packs/packages/schematics/src/utils/text.ts +++ b/npm/ng-packs/packages/schematics/src/utils/text.ts @@ -55,3 +55,6 @@ function isUpperCase(str = '') { function toLowerCase(str = '') { return str.toLowerCase(); } +export function camelizeHyphen(str: string) { + return str.replace(/-([a-z])/g, g => g[1].toUpperCase()); +} From 7dd744137ce40f727d3fb02a25cecd4831acf6df Mon Sep 17 00:00:00 2001 From: muhammedaltug Date: Fri, 22 Apr 2022 17:08:34 +0300 Subject: [PATCH 12/60] rollback string replace - move condition from ternary op to if --- npm/ng-packs/packages/schematics/src/models/method.ts | 9 +++++---- npm/ng-packs/packages/schematics/src/utils/service.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/npm/ng-packs/packages/schematics/src/models/method.ts b/npm/ng-packs/packages/schematics/src/models/method.ts index 7e6147b2d4..d8f56e383e 100644 --- a/npm/ng-packs/packages/schematics/src/models/method.ts +++ b/npm/ng-packs/packages/schematics/src/models/method.ts @@ -47,11 +47,12 @@ export class Body { const { bindingSourceId, descriptorName, jsonName, name, nameOnMethod } = param; const camelName = camel(name); const paramName = jsonName || camelName; - const value = descriptorName - ? shouldQuote(paramName) + let value = camelizeHyphen(nameOnMethod); + if (descriptorName) { + value = shouldQuote(paramName) ? `${descriptorName}['${paramName}']` - : `${descriptorName}.${paramName}` - : camelizeHyphen(nameOnMethod); + : `${descriptorName}.${paramName}`; + } switch (bindingSourceId) { case eBindingSourceId.Model: diff --git a/npm/ng-packs/packages/schematics/src/utils/service.ts b/npm/ng-packs/packages/schematics/src/utils/service.ts index f37384d27d..4e2fdc7c02 100644 --- a/npm/ng-packs/packages/schematics/src/utils/service.ts +++ b/npm/ng-packs/packages/schematics/src/utils/service.ts @@ -99,7 +99,7 @@ export function createActionToSignatureMapper() { } function getMethodNameFromAction(action: Action): string { - return action.uniqueName.replace('Async', ''); + return action.uniqueName.split('Async')[0]; } function getVersionParameter(action: Action) { From c61abfed2ffe15c54b0c6fab1296be3b5df4be3d Mon Sep 17 00:00:00 2001 From: enisn Date: Wed, 27 Apr 2022 16:10:13 +0300 Subject: [PATCH 13/60] Add LeptonX Lite Docs --- docs/en/Themes/LeptonXLite/angular.md | 75 ++++++++++++++++ docs/en/Themes/LeptonXLite/blazor.md | 124 ++++++++++++++++++++++++++ docs/en/Themes/LeptonXLite/mvc.md | 63 +++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 docs/en/Themes/LeptonXLite/angular.md create mode 100644 docs/en/Themes/LeptonXLite/blazor.md create mode 100644 docs/en/Themes/LeptonXLite/mvc.md diff --git a/docs/en/Themes/LeptonXLite/angular.md b/docs/en/Themes/LeptonXLite/angular.md new file mode 100644 index 0000000000..431a4d07f2 --- /dev/null +++ b/docs/en/Themes/LeptonXLite/angular.md @@ -0,0 +1,75 @@ +# Client Side + +To add `LeptonX-lite` into your project, + +* Install `@abp/ng.theme.lepton-x` + +`yarn add @abp/ng.theme.lepton-x@preview` + +* Install `bootstrap-icons` + +`yarn add bootstrap-icons` + + +* Then, we need to edit the styles array in `angular.json` to replace the existing style with the new one. + +Add the following style + +```json +"node_modules/bootstrap-icons/font/bootstrap-icons.css", +``` + +* Finally, remove `ThemeBasicModule` from `app.module.ts`, and import the related modules in `app.module.ts` + +```js +import { ThemeLeptonXModule } from '@abp/ng.theme.lepton-x'; +import { SideMenuLayoutModule } from '@abp/ng.theme.lepton-x/layouts'; + +@NgModule({ + imports: [ + // ... + + // do not forget to remove ThemeBasicModule + // ThemeBasicModule.forRoot(), + ThemeLeptonXModule.forRoot(), + SideMenuLayoutModule.forRoot(), + ], + // ... +}) +export class AppModule {} +``` + +Note: If you employ [Resource Owner Password Flow](https://docs.abp.io/en/abp/latest/UI/Angular/Authorization#resource-owner-password-flow) for authorization, you should import the following module as well: + +```js +import { AccountLayoutModule } from '@abp/ng.theme.lepton-x/account'; + +@NgModule({ + // ... + imports: [ + // ... + AccountLayoutModule.forRoot(), + // ... + ], + // ... +}) +export class AppModule {} +``` + +To change the logos and brand color of the `LeptonX`, simply add the following CSS to the `styles.scss` + +```css +:root { + --lpx-logo: url('/assets/images/logo.png'); + --lpx-logo-icon: url('/assets/images/logo-icon.png'); + --lpx-brand: #edae53; +} +``` + +- `--lpx-logo` is used to place the logo in the menu. +- `--lpx-logo-icon` is a square icon used when the menu is collapsed. +- `--lpx-brand` is a color used throughout the application, especially on active elements. + +# Server Side + +In order to migrate to LeptonX on your server side projects (Host and/or IdentityServer projects), please follow [Server Side Migration](mvc.md) document. diff --git a/docs/en/Themes/LeptonXLite/blazor.md b/docs/en/Themes/LeptonXLite/blazor.md new file mode 100644 index 0000000000..a20d3436de --- /dev/null +++ b/docs/en/Themes/LeptonXLite/blazor.md @@ -0,0 +1,124 @@ +# LeptonX Lite MVC UI + +````json +//[doc-params] +{ + "UI": ["Blazor", "BlazorServer"] +} +```` + +LeptonX Lite has implementation for ABP Framework Blazor WebAssembly & Blazor Server. + +## Installation + + +## Installation + +{{if UI == "Blazor"}} + +- Add **Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXTheme** package to your **Blazor wasm** application. + ```bash + dotnet add package Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme + ``` + +- Remove old theme from **DependsOn** attribute in your module class and add **AbpAspNetCoreComponentsWebAssemblyLeptonXThemeModule** type to **DependsOn** attribute. + +```diff +[DependsOn( +- typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule), ++ typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule) +)] +``` + +- Change startup App component with LeptonX one. + +```csharp +// Make sure the 'App' comes from 'Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite' namespace. +builder.RootComponents.Add("#ApplicationContainer"); + +``` + +- Run `abp bundle` command in your **Blazor** application folder. + +{{end}} + + +{{if UI == "BlazorServer"}} + +- Complete [MVC Razor Pages Installation](mvc.md#installation) first. + +- Add **Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme** package to your **Blazor server** application. + ```bash + dotnet add package Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme + ``` + +- Remove old theme from **DependsOn** attribute in your module class and add **AbpAspNetCoreComponentsWebAssemblyLeptonXThemeModule** type to **DependsOn** attribute. + + ```diff + [DependsOn( + - typeof(AbpAspNetCoreComponentsServerBasicThemeModule), + + typeof(AbpAspNetCoreComponentsServerLeptonXLiteThemeModule) + )] + ``` + +- Update AbpBundlingOptions + ```diff + options.StyleBundles.Configure( + - BlazorBasicThemeBundles.Styles.Global, + + BlazorLeptonXLiteThemeBundles.Styles.Global, + bundle => + { + bundle.AddFiles("/blazor-global-styles.css"); + //You can remove the following line if you don't use Blazor CSS isolation for components + bundle.AddFiles("/MyProjectName.Blazor.styles.css"); + }); + ``` + +- Update `_Host.cshtml` file. _(located under **Pages** folder by default.)_ + + - Add following usings to Locate **App** and **BlazorLeptonXLiteThemeBundles** classes. + ```csharp + @using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite + @using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling + ``` + - Then replace script & style bunles as following + ```diff + - + + + ``` + + ```diff + - + + + ``` + +{{end}} + + +--- + +## Customization + +### Toolbars +LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in **LeptonXLiteToolbars** class. + +- `LeptonXLiteToolbars.Main` +- `LeptonXLiteToolbars.MainMobile` + +```csharp +public class MyProjectNameMainToolbarContributor : IToolbarContributor +{ + public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context) + { + if (context.Toolbar.Name == LeptonXLiteToolbars.Main) + { + context.Toolbar.Items.Add(new ToolbarItem(typeof(MyDesktopComponent))); + } + + if (context.Toolbar.Name == LeptonXLiteToolbars.MainMobile) + { + context.Toolbar.Items.Add(new ToolbarItem(typeof(MyMobileComponent))); + } + } +} +``` \ No newline at end of file diff --git a/docs/en/Themes/LeptonXLite/mvc.md b/docs/en/Themes/LeptonXLite/mvc.md new file mode 100644 index 0000000000..7a4519585b --- /dev/null +++ b/docs/en/Themes/LeptonXLite/mvc.md @@ -0,0 +1,63 @@ +# LeptonX Lite MVC UI +LeptonX Lite has implementation for ABP Framework Razor Pages. + +## Installation + +- Add **Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite** package to your **Web** application. + +```bash +dotnet add package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite +``` + +- Make sure old theme is removed and LeptonX is added in your Module class. + +```diff +[DependsOn( +- typeof(AbpAspNetCoreMvcUiBasicThemeModule), ++ typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule) +)] +``` + +- Update AbpBundlingOptions + +```diff +Configure(options => +{ + options.StyleBundles.Configure( +- BasicThemeBundles.Styles.Global, ++ LeptonXLiteThemeBundles.Styles.Global + bundle => + { + bundle.AddFiles("/global-styles.css"); + } + ); +}); +``` + +--- + +## Customization + +### Toolbars +LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in **LeptonXLiteToolbars** class. + +- `LeptonXLiteToolbars.Main` +- `LeptonXLiteToolbars.MainMobile` + +```csharp +public class MyProjectNameMainToolbarContributor : IToolbarContributor +{ + public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context) + { + if (context.Toolbar.Name == LeptonXLiteToolbars.Main) + { + context.Toolbar.Items.Add(new ToolbarItem(typeof(MyDesktopComponent))); + } + + if (context.Toolbar.Name == LeptonXLiteToolbars.MainMobile) + { + context.Toolbar.Items.Add(new ToolbarItem(typeof(MyMobileComponent))); + } + } +} +``` \ No newline at end of file From a9d3c032d8e8ededc286f3f2cb7f94149faaa96f Mon Sep 17 00:00:00 2001 From: enisn Date: Wed, 27 Apr 2022 16:10:30 +0300 Subject: [PATCH 14/60] Add LeptonX docs to menu --- docs/en/docs-nav.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index b2061920a8..a4d34ad292 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -704,6 +704,10 @@ { "text": "The Basic Theme", "path": "UI/AspNetCore/Basic-Theme.md" + }, + { + "text": "LeptonX Lite", + "path": "Themes/LeptonXLite/mvc.md" } ] }, @@ -817,6 +821,10 @@ "text": "The Basic Theme", "path": "UI/Blazor/Basic-Theme.md" }, + { + "text": "The Basic Theme", + "path": "Themes/LeptonXLite/blazor.md" + }, { "text": "Branding", "path": "UI/Blazor/Branding.md" @@ -1071,6 +1079,10 @@ { "text": "The Basic Theme", "path": "UI/Angular/Basic-Theme.md" + }, + { + "text": "LeptonX Lite", + "path": "Themes/LeptonXLite/angular.md" } ] }, From 8313cb5f6d44dc0f6ce87d2bbbb831ac0fb9ce56 Mon Sep 17 00:00:00 2001 From: enisn Date: Wed, 27 Apr 2022 16:47:46 +0300 Subject: [PATCH 15/60] Update blazor.md --- docs/en/Themes/LeptonXLite/blazor.md | 38 +++++++++++++++------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/en/Themes/LeptonXLite/blazor.md b/docs/en/Themes/LeptonXLite/blazor.md index a20d3436de..07d0bb68eb 100644 --- a/docs/en/Themes/LeptonXLite/blazor.md +++ b/docs/en/Themes/LeptonXLite/blazor.md @@ -1,4 +1,4 @@ -# LeptonX Lite MVC UI +# LeptonX Lite Blazor UI ````json //[doc-params] @@ -9,12 +9,10 @@ LeptonX Lite has implementation for ABP Framework Blazor WebAssembly & Blazor Server. -## Installation - - ## Installation {{if UI == "Blazor"}} +- Complete [MVC Razor Pages Installation](mvc.md#installation) for the **HttpApi.Host** application first. _If the solution is tiered/micro-service, complete MVC steps for all MVC applications such as **HttpApi.Host** and if identity server is separated, install to the **IdentityServer**_. - Add **Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXTheme** package to your **Blazor wasm** application. ```bash @@ -35,7 +33,6 @@ LeptonX Lite has implementation for ABP Framework Blazor WebAssembly & Blazor Se ```csharp // Make sure the 'App' comes from 'Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite' namespace. builder.RootComponents.Add("#ApplicationContainer"); - ``` - Run `abp bundle` command in your **Blazor** application folder. @@ -45,7 +42,7 @@ builder.RootComponents.Add("#ApplicationContainer"); {{if UI == "BlazorServer"}} -- Complete [MVC Razor Pages Installation](mvc.md#installation) first. +- Complete [MVC Razor Pages Installation](mvc.md#installation) first. _If the solution is tiered/micro-service, complete MVC steps for all MVC applications such as **HttpApi.Host** and **IdentityServer**_. - Add **Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme** package to your **Blazor server** application. ```bash @@ -106,19 +103,24 @@ LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage to - `LeptonXLiteToolbars.MainMobile` ```csharp -public class MyProjectNameMainToolbarContributor : IToolbarContributor +public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context) { - public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context) + if (context.Toolbar.Name == LeptonXLiteToolbars.Main) + { + context.Toolbar.Items.Add(new ToolbarItem(typeof(MyDesktopComponent))); + } + + if (context.Toolbar.Name == LeptonXLiteToolbars.MainMobile) { - if (context.Toolbar.Name == LeptonXLiteToolbars.Main) - { - context.Toolbar.Items.Add(new ToolbarItem(typeof(MyDesktopComponent))); - } - - if (context.Toolbar.Name == LeptonXLiteToolbars.MainMobile) - { - context.Toolbar.Items.Add(new ToolbarItem(typeof(MyMobileComponent))); - } + context.Toolbar.Items.Add(new ToolbarItem(typeof(MyMobileComponent))); } + + return Task.CompletedTask; } -``` \ No newline at end of file +``` + +{{if UI == "BlazorServer"}} + +> _You can visit the [Toolbars Documentation](https://docs.abp.io/en/abp/latest/UI/Blazor/Toolbars) for better understanding._ + +{{end}} From b3ff72681f5e657a1e1253e0a10d3280c4a16d37 Mon Sep 17 00:00:00 2001 From: enisn Date: Wed, 27 Apr 2022 16:48:14 +0300 Subject: [PATCH 16/60] Update mvc.md --- docs/en/Themes/LeptonXLite/mvc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Themes/LeptonXLite/mvc.md b/docs/en/Themes/LeptonXLite/mvc.md index 7a4519585b..74218b5381 100644 --- a/docs/en/Themes/LeptonXLite/mvc.md +++ b/docs/en/Themes/LeptonXLite/mvc.md @@ -6,7 +6,7 @@ LeptonX Lite has implementation for ABP Framework Razor Pages. - Add **Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite** package to your **Web** application. ```bash -dotnet add package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite +abp add-package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite ``` - Make sure old theme is removed and LeptonX is added in your Module class. From 5bfcf0a2bb0e0049b4dd9b201251534b39610af4 Mon Sep 17 00:00:00 2001 From: William Obando Date: Wed, 27 Apr 2022 11:36:14 -0400 Subject: [PATCH 17/60] Ignore messages without subject --- .../Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs index d8a3d1341a..0247d90be1 100644 --- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs @@ -71,6 +71,10 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen private async Task ProcessEventAsync(ServiceBusReceivedMessage message) { var eventName = message.Subject; + if (eventName == null) + { + return; + } var eventType = _eventTypes.GetOrDefault(eventName); if (eventType == null) { From 5720a882f41e562100448de87036b452249159fc Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Thu, 28 Apr 2022 09:59:19 +0300 Subject: [PATCH 18/60] Update docs/en/Themes/LeptonXLite/blazor.md Co-authored-by: Qingxiao Ren --- docs/en/Themes/LeptonXLite/blazor.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Themes/LeptonXLite/blazor.md b/docs/en/Themes/LeptonXLite/blazor.md index 07d0bb68eb..7994f080b2 100644 --- a/docs/en/Themes/LeptonXLite/blazor.md +++ b/docs/en/Themes/LeptonXLite/blazor.md @@ -14,7 +14,7 @@ LeptonX Lite has implementation for ABP Framework Blazor WebAssembly & Blazor Se {{if UI == "Blazor"}} - Complete [MVC Razor Pages Installation](mvc.md#installation) for the **HttpApi.Host** application first. _If the solution is tiered/micro-service, complete MVC steps for all MVC applications such as **HttpApi.Host** and if identity server is separated, install to the **IdentityServer**_. -- Add **Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXTheme** package to your **Blazor wasm** application. +- Add **Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme** package to your **Blazor WebAssembly** application. ```bash dotnet add package Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme ``` From cbdef0960c31ee4c779794da04c286cd7647890d Mon Sep 17 00:00:00 2001 From: Enis Necipoglu Date: Thu, 28 Apr 2022 10:00:16 +0300 Subject: [PATCH 19/60] Fix typo for LeptonXLiteTheme Co-authored-by: Qingxiao Ren --- docs/en/Themes/LeptonXLite/blazor.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Themes/LeptonXLite/blazor.md b/docs/en/Themes/LeptonXLite/blazor.md index 7994f080b2..f57f1a8268 100644 --- a/docs/en/Themes/LeptonXLite/blazor.md +++ b/docs/en/Themes/LeptonXLite/blazor.md @@ -19,7 +19,7 @@ LeptonX Lite has implementation for ABP Framework Blazor WebAssembly & Blazor Se dotnet add package Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme ``` -- Remove old theme from **DependsOn** attribute in your module class and add **AbpAspNetCoreComponentsWebAssemblyLeptonXThemeModule** type to **DependsOn** attribute. +- Remove old theme from **DependsOn** attribute in your module class and add **AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule** type to **DependsOn** attribute. ```diff [DependsOn( @@ -49,7 +49,7 @@ builder.RootComponents.Add("#ApplicationContainer"); dotnet add package Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme ``` -- Remove old theme from **DependsOn** attribute in your module class and add **AbpAspNetCoreComponentsWebAssemblyLeptonXThemeModule** type to **DependsOn** attribute. +- Remove old theme from **DependsOn** attribute in your module class and add **AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule** type to **DependsOn** attribute. ```diff [DependsOn( From 1fa455d14c15579385ea836ef4ba27e21594db72 Mon Sep 17 00:00:00 2001 From: Qingxiao Ren Date: Thu, 28 Apr 2022 15:56:31 +0800 Subject: [PATCH 20/60] remove duplicate ICurrentTenant injection --- .../Pages/Index.razor | 1 - .../MyCompanyName.MyProjectName.Blazor.Server/Pages/Index.razor | 1 - .../src/MyCompanyName.MyProjectName.Blazor/Pages/Index.razor | 1 - 3 files changed, 3 deletions(-) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Pages/Index.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Pages/Index.razor index 063434c52c..6a4c2c8d6e 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Pages/Index.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Pages/Index.razor @@ -1,7 +1,6 @@ @page "/" @using Volo.Abp.MultiTenancy @inherits MyProjectNameComponentBase -@inject ICurrentTenant CurrentTenant @inject AuthenticationStateProvider AuthenticationStateProvider
diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Pages/Index.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Pages/Index.razor index 874098dd6b..543054d1c1 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Pages/Index.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Pages/Index.razor @@ -1,7 +1,6 @@ @page "/" @using Volo.Abp.MultiTenancy @inherits MyProjectNameComponentBase -@inject ICurrentTenant CurrentTenant @inject AuthenticationStateProvider AuthenticationStateProvider
diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Pages/Index.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Pages/Index.razor index bf959ba3d0..3cd56e9cac 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Pages/Index.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Pages/Index.razor @@ -1,7 +1,6 @@ @page "/" @using Volo.Abp.MultiTenancy @inherits MyProjectNameComponentBase -@inject ICurrentTenant CurrentTenant @inject AuthenticationStateProvider AuthenticationStateProvider
From 34ec342b79fdaf121d3d2d5259fd0bf6042937e9 Mon Sep 17 00:00:00 2001 From: enisn Date: Thu, 28 Apr 2022 10:58:43 +0300 Subject: [PATCH 21/60] Add Introduction section to mvc --- docs/en/Themes/LeptonXLite/mvc.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/en/Themes/LeptonXLite/mvc.md b/docs/en/Themes/LeptonXLite/mvc.md index 74218b5381..d46c8a82d0 100644 --- a/docs/en/Themes/LeptonXLite/mvc.md +++ b/docs/en/Themes/LeptonXLite/mvc.md @@ -1,5 +1,9 @@ # LeptonX Lite MVC UI -LeptonX Lite has implementation for ABP Framework Razor Pages. +LeptonX Lite has implementation for ABP Framework Razor Pages. It's a simplified version of [LeptonX Theme](https://x.leptontheme.com/). + +> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of the [ABP Commercial](https://commercial.abp.io/). + +> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. ## Installation From 754c454c96abc8fbe4058f1b4d5ed0b64560889b Mon Sep 17 00:00:00 2001 From: enisn Date: Thu, 28 Apr 2022 11:00:48 +0300 Subject: [PATCH 22/60] Update mvc.md --- docs/en/Themes/LeptonXLite/mvc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Themes/LeptonXLite/mvc.md b/docs/en/Themes/LeptonXLite/mvc.md index d46c8a82d0..ea5843cd93 100644 --- a/docs/en/Themes/LeptonXLite/mvc.md +++ b/docs/en/Themes/LeptonXLite/mvc.md @@ -1,5 +1,5 @@ # LeptonX Lite MVC UI -LeptonX Lite has implementation for ABP Framework Razor Pages. It's a simplified version of [LeptonX Theme](https://x.leptontheme.com/). +LeptonX Lite has implementation for ABP Framework Razor Pages. It's a simplified variation of [LeptonX Theme](https://x.leptontheme.com/). > If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of the [ABP Commercial](https://commercial.abp.io/). From afbb552a83302269d62e746eff9d89c0aeb8ffa8 Mon Sep 17 00:00:00 2001 From: enisn Date: Thu, 28 Apr 2022 11:02:53 +0300 Subject: [PATCH 23/60] Add Introduction section to blazor --- docs/en/Themes/LeptonXLite/blazor.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/en/Themes/LeptonXLite/blazor.md b/docs/en/Themes/LeptonXLite/blazor.md index f57f1a8268..3461a9ae42 100644 --- a/docs/en/Themes/LeptonXLite/blazor.md +++ b/docs/en/Themes/LeptonXLite/blazor.md @@ -7,7 +7,11 @@ } ```` -LeptonX Lite has implementation for ABP Framework Blazor WebAssembly & Blazor Server. +LeptonX Lite has implementation for ABP Framework Blazor WebAssembly & Blazor Server. It's a simplified variation of [LeptonX Theme](https://x.leptontheme.com/). + +> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of the [ABP Commercial](https://commercial.abp.io/). + +> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. ## Installation From ce15f9d50ee1389b5528a11632c64627ade04815 Mon Sep 17 00:00:00 2001 From: enisn Date: Thu, 28 Apr 2022 11:13:03 +0300 Subject: [PATCH 24/60] Add Introduction section to angular --- docs/en/Themes/LeptonXLite/angular.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/en/Themes/LeptonXLite/angular.md b/docs/en/Themes/LeptonXLite/angular.md index 431a4d07f2..6610e68c8e 100644 --- a/docs/en/Themes/LeptonXLite/angular.md +++ b/docs/en/Themes/LeptonXLite/angular.md @@ -1,4 +1,11 @@ -# Client Side +# LeptonX Lite Angular UI +LeptonX Lite has implementation for ABP Framework Angular Client. It's a simplified variation of [LeptonX Theme](https://x.leptontheme.com/). + +> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of the [ABP Commercial](https://commercial.abp.io/). + +> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. + +## Installation To add `LeptonX-lite` into your project, @@ -70,6 +77,6 @@ To change the logos and brand color of the `LeptonX`, simply add the following C - `--lpx-logo-icon` is a square icon used when the menu is collapsed. - `--lpx-brand` is a color used throughout the application, especially on active elements. -# Server Side +### Server Side In order to migrate to LeptonX on your server side projects (Host and/or IdentityServer projects), please follow [Server Side Migration](mvc.md) document. From 2eba5c7e0bc4018e5cf7d946f7d1dd4e8ac44a50 Mon Sep 17 00:00:00 2001 From: Engincan VESKE Date: Thu, 28 Apr 2022 18:54:51 +0300 Subject: [PATCH 25/60] app-nolayers: Fix localization problem (on production) --- .../MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj | 1 + .../MyCompanyName.MyProjectName.Blazor.Server.csproj | 1 + .../MyCompanyName.MyProjectName.Host.Mongo.csproj | 1 + .../MyCompanyName.MyProjectName.Host.csproj | 1 + .../MyCompanyName.MyProjectName.Mvc.Mongo.csproj | 1 + .../MyCompanyName.MyProjectName.Mvc.csproj | 1 + 6 files changed, 6 insertions(+) diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj index 073f531982..0dcf5f4de7 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj @@ -3,6 +3,7 @@ net6.0 enable + true diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index e4d5596abf..30b56f9da5 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -3,6 +3,7 @@ net6.0 enable + true diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj index d0250e295c..1e7ab8d272 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj @@ -3,6 +3,7 @@ net6.0 enable + true diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj index 1ccb7d8375..1d8766fec7 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj @@ -3,6 +3,7 @@ net6.0 enable + true diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj index c7232dbc14..41033c4eab 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj @@ -3,6 +3,7 @@ net6.0 enable + true diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj index d4efe0ab2c..fd0c48af53 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj @@ -3,6 +3,7 @@ net6.0 enable + true From 4ef7e7f2d55df19f8d6ed0948df7303cf8cdec1c Mon Sep 17 00:00:00 2001 From: Hamza Albreem <94292623+braim23@users.noreply.github.com> Date: Thu, 28 Apr 2022 18:58:14 +0300 Subject: [PATCH 26/60] Typo fix in commercial.abp.io pages --- .../Commercial/Localization/Resources/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index baaa28edb0..a977671501 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -49,7 +49,7 @@ "IndexPageHeroSection": "A complete web development platformbuilt-on framework", "AbpCommercialShortDescription": "ABP Commercial provides pre-built application modules, rapid application development tooling, professional UI themes, premium support and more.", "LiveDemo": "Live Demo", - "GetLicence": "Get a Licence", + "GetLicence": "Get a License", "Application": "Application", "StartupTemplates": "Startup Templates", "Startup": "Startup", @@ -271,7 +271,7 @@ "Enterprise": "Enterprise", "Custom": "Custom", "IncludedDeveloperLicenses": "Included developer licenses", - "CustomLicenceOrAdditionalServices": "Need custom licence or additional services?", + "CustomLicenceOrAdditionalServices": "Need custom license or additional services?", "CustomOrVolumeLicense": "Custom or volume license", "LiveTrainingSupport": "Live training & support", "AndMore": "and more", From be6116727d5f3552dc3cee7bf0ac02ab1fe68648 Mon Sep 17 00:00:00 2001 From: albert <9526587+ebicoglu@users.noreply.github.com> Date: Fri, 29 Apr 2022 02:16:17 +0300 Subject: [PATCH 27/60] install-libs command is for MVC and Blazor-Server https://github.com/abpframework/abp/blob/98c42910e5dc332f9ce79baa5a00debdcb750aea/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs#L45 --- docs/en/Getting-Started-Running-Solution.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Getting-Started-Running-Solution.md b/docs/en/Getting-Started-Running-Solution.md index 281add1e2a..fe1d0de246 100644 --- a/docs/en/Getting-Started-Running-Solution.md +++ b/docs/en/Getting-Started-Running-Solution.md @@ -91,10 +91,10 @@ Right click to the `.DbMigrator` project and select **Set as StartUp Project** ## Run the Application -> Please execute the `abp install-libs` command to restore the libs required by the web project before running the application. - {{ if UI == "MVC" || UI == "BlazorServer" }} +> Before starting the application, run `abp install-libs` command in your Web directory to restore the client-side libraries. This will populate the `libs` folder. + {{ if Tiered == "Yes" }} > Tiered solutions use **Redis** as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below. From 0a8b45e09d3c594640e6d2e8e0ee8f3293c0b730 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 29 Apr 2022 10:31:30 +0800 Subject: [PATCH 28/60] Batch publish outbox events by default --- .../Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs | 6 +++--- .../Volo/Abp/EventBus/Distributed/OutboxSender.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs index 749aa3d6d2..d92c48db3a 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs @@ -36,9 +36,9 @@ public class AbpEventBusBoxesOptions public TimeSpan WaitTimeToDeleteProcessedInboxEvents { get; set; } /// - /// Default: false + /// Default: true /// - public bool OutboxPublishInBatch { get; set; } + public bool BatchPublishOutboxEvents { get; set; } public AbpEventBusBoxesOptions() { @@ -48,6 +48,6 @@ public class AbpEventBusBoxesOptions PeriodTimeSpan = TimeSpan.FromSeconds(2); DistributedLockWaitDuration = TimeSpan.FromSeconds(15); WaitTimeToDeleteProcessedInboxEvents = TimeSpan.FromHours(2); - OutboxPublishInBatch = false; + BatchPublishOutboxEvents = true; } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs index ac92740e86..851ddf3da1 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs @@ -86,7 +86,7 @@ public class OutboxSender : IOutboxSender, ITransientDependency Logger.LogInformation($"Found {waitingEvents.Count} events in the outbox."); - if (EventBusBoxesOptions.OutboxPublishInBatch) + if (EventBusBoxesOptions.BatchPublishOutboxEvents) { await PublishOutgoingMessagesInBatchAsync(waitingEvents); } From 1463d23e9f8c7a9c5d8780d1d158da560b4b9ffe Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 29 Apr 2022 10:47:08 +0800 Subject: [PATCH 29/60] Add console logging to app templates for every environment --- .../MyCompanyName.MyProjectName.Blazor.Server.Tiered/Program.cs | 2 -- .../src/MyCompanyName.MyProjectName.Blazor.Server/Program.cs | 2 -- .../src/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs | 2 -- .../MyCompanyName.MyProjectName.HttpApi.HostWithIds/Program.cs | 2 -- .../src/MyCompanyName.MyProjectName.IdentityServer/Program.cs | 2 -- .../src/MyCompanyName.MyProjectName.Web.Host/Program.cs | 2 -- .../aspnet-core/src/MyCompanyName.MyProjectName.Web/Program.cs | 2 -- .../MyCompanyName.MyProjectName.Blazor.Server.Host/Program.cs | 2 -- .../host/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs | 2 -- .../host/MyCompanyName.MyProjectName.IdentityServer/Program.cs | 2 -- .../host/MyCompanyName.MyProjectName.Web.Host/Program.cs | 2 -- .../host/MyCompanyName.MyProjectName.Web.Unified/Program.cs | 2 -- 12 files changed, 24 deletions(-) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Program.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Program.cs index d028aeafad..d4399ec884 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Program.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Program.cs @@ -22,9 +22,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Program.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Program.cs index 8cffe96c42..6e2f2085dc 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Program.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Program.cs @@ -22,9 +22,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs index b3bb9299ba..149b9ad46d 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs @@ -22,9 +22,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/Program.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/Program.cs index b3bb9299ba..149b9ad46d 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/Program.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/Program.cs @@ -22,9 +22,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/Program.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/Program.cs index 2699dc2993..756b7da688 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/Program.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/Program.cs @@ -22,9 +22,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/Program.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/Program.cs index 8c345c70a2..7894ee68b6 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/Program.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/Program.cs @@ -22,9 +22,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Program.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Program.cs index 8c345c70a2..7894ee68b6 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Program.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Program.cs @@ -22,9 +22,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Program.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Program.cs index cff77845f1..c7f18ffe4a 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Program.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Program.cs @@ -22,9 +22,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs index a315163da3..be11164285 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Program.cs @@ -22,9 +22,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Program.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Program.cs index 540e2312e2..89fa523a4d 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Program.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.IdentityServer/Program.cs @@ -22,9 +22,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/Program.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/Program.cs index 2d54ded331..e638a0176b 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/Program.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/Program.cs @@ -25,9 +25,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Program.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Program.cs index f8d7f828c5..0e0b569886 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Program.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Program.cs @@ -25,9 +25,7 @@ public class Program .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt")) -#if DEBUG .WriteTo.Async(c => c.Console()) -#endif .CreateLogger(); try From b31a4dcfca7d35ca8188c3f3929abe1eb5371977 Mon Sep 17 00:00:00 2001 From: Michel Zehnder Date: Fri, 29 Apr 2022 08:43:35 +0200 Subject: [PATCH 30/60] Fix description --- docs/en/Migration-Guides/Abp-5-0-Blazor.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Migration-Guides/Abp-5-0-Blazor.md b/docs/en/Migration-Guides/Abp-5-0-Blazor.md index 326bac55d5..09d0f8e682 100644 --- a/docs/en/Migration-Guides/Abp-5-0-Blazor.md +++ b/docs/en/Migration-Guides/Abp-5-0-Blazor.md @@ -1,6 +1,6 @@ # ABP Blazor UI v4.x to v5.0 Migration Guide -> This document is for the ABP MVC / Razor Pages UI. See also [the main migration guide](Abp-5_0.md). +> This document is for the Blazor UI. See also [the main migration guide](Abp-5_0.md). ## Upgrading to the latest Blazorise @@ -16,4 +16,4 @@ Replace `AddBootstrapProviders()` with `AddBootstrap5Providers()`. ## Update Bundle -Use `abp bundle` command to update bundles if you are using Blazor WebAssembly. \ No newline at end of file +Use `abp bundle` command to update bundles if you are using Blazor WebAssembly. From 22c5abc9bd3daf1c2c76470c53d6d39c9add8c8b Mon Sep 17 00:00:00 2001 From: Michel Zehnder Date: Fri, 29 Apr 2022 08:44:07 +0200 Subject: [PATCH 31/60] Fix description --- docs/en/Migration-Guides/Abp-5_0-Angular.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Migration-Guides/Abp-5_0-Angular.md b/docs/en/Migration-Guides/Abp-5_0-Angular.md index 8099c8acd4..b7e55dad38 100644 --- a/docs/en/Migration-Guides/Abp-5_0-Angular.md +++ b/docs/en/Migration-Guides/Abp-5_0-Angular.md @@ -1,6 +1,6 @@ # Angular UI v4.x to v5.0 Migration Guide -This document is for the ABP MVC / Razor Pages UI. See also [the main migration guide](Abp-5_0.md). +> This document is for the Angular UI. See also [the main migration guide](Abp-5_0.md). ## Overall From 793cf8e530d09fc60efc30541ed4091489eb4bca Mon Sep 17 00:00:00 2001 From: Hamza Albreem <94292623+braim23@users.noreply.github.com> Date: Fri, 29 Apr 2022 12:59:26 +0300 Subject: [PATCH 32/60] Grammar fix --- docs/en/Themes/LeptonXLite/angular.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/Themes/LeptonXLite/angular.md b/docs/en/Themes/LeptonXLite/angular.md index 6610e68c8e..7b4a4fca68 100644 --- a/docs/en/Themes/LeptonXLite/angular.md +++ b/docs/en/Themes/LeptonXLite/angular.md @@ -1,7 +1,7 @@ # LeptonX Lite Angular UI -LeptonX Lite has implementation for ABP Framework Angular Client. It's a simplified variation of [LeptonX Theme](https://x.leptontheme.com/). +LeptonX Lite has implementation for the ABP Framework Angular Client. It's a simplified variation of [LeptonX Theme](https://x.leptontheme.com/). -> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of the [ABP Commercial](https://commercial.abp.io/). +> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/). > See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. @@ -63,7 +63,7 @@ import { AccountLayoutModule } from '@abp/ng.theme.lepton-x/account'; export class AppModule {} ``` -To change the logos and brand color of the `LeptonX`, simply add the following CSS to the `styles.scss` +To change the logos and brand color of `LeptonX`, simply add the following CSS to the `styles.scss` ```css :root { @@ -79,4 +79,4 @@ To change the logos and brand color of the `LeptonX`, simply add the following C ### Server Side -In order to migrate to LeptonX on your server side projects (Host and/or IdentityServer projects), please follow [Server Side Migration](mvc.md) document. +In order to migrate to LeptonX on your server side projects (Host and/or IdentityServer projects), please follow the [Server Side Migration](mvc.md) document. From 80fad9e6de555a6bb9e153a3d6a1e92416cc205b Mon Sep 17 00:00:00 2001 From: Engincan VESKE Date: Fri, 29 Apr 2022 13:00:00 +0300 Subject: [PATCH 33/60] Add new localizations for abp.io websites --- .../Admin/Localization/Resources/en.json | 11 ++++++++++- .../Base/Localization/Resources/en.json | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index 89b7d03f5b..1dc0d7c1f7 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -384,6 +384,15 @@ "ShowBetweenDayCount": "Show Between Days", "PurchaseOrder": "Purchase Order", "ShowCreateInvoiceOfOrganization": "Create Invoice", - "ShowCreateQuotationOfOrganization": "Create Quotation" + "ShowCreateQuotationOfOrganization": "Create Quotation", + "BookDiscounts": "Book Discounts", + "Permission:BookDiscount": "Book Discount", + "Menu:BookDiscounts": "Book Discounts", + "BookType": "Book Type", + "PurchasePlatform": "Purchase Platform", + "StartTime": "Start Time", + "EndTime": "End Time", + "CreateABookDiscount": "Create a book discount", + "BookDiscountDeletionConfirmationMessage": "Are you sure you want to delete this book discount?" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json index 5d6e081085..e898619b64 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json @@ -121,6 +121,10 @@ "MasteringAbpFrameworkBook": "Book: Mastering ABP Framework", "ABPIO-CommonPreferenceDefinition": "Get the latest news about ABP Platform like new posts, events and more.", "BuiltOn": "Built-on", - "AbpFramework": "ABP Framework" + "AbpFramework": "ABP Framework", + "Volo.AbpIo.Domain:080001": "Start Time can not be greater than End Time", + "Enum:BookType:0": "Mastering ABP Framework", + "Enum:PurchasePlatform:0": "Amazon", + "Enum:PurchasePlatform:1": "Packt" } } From c306f63f3d65199ceab628156f442d2bb3d84b1d Mon Sep 17 00:00:00 2001 From: Hamza Albreem <94292623+braim23@users.noreply.github.com> Date: Fri, 29 Apr 2022 15:25:16 +0300 Subject: [PATCH 34/60] grammar and typo fix for blazor.md --- docs/en/Themes/LeptonXLite/blazor.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/en/Themes/LeptonXLite/blazor.md b/docs/en/Themes/LeptonXLite/blazor.md index 3461a9ae42..ddea346585 100644 --- a/docs/en/Themes/LeptonXLite/blazor.md +++ b/docs/en/Themes/LeptonXLite/blazor.md @@ -7,23 +7,23 @@ } ```` -LeptonX Lite has implementation for ABP Framework Blazor WebAssembly & Blazor Server. It's a simplified variation of [LeptonX Theme](https://x.leptontheme.com/). +LeptonX Lite has implementation for the ABP Framework Blazor WebAssembly & Blazor Server. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/). -> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of the [ABP Commercial](https://commercial.abp.io/). +> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/). > See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. ## Installation {{if UI == "Blazor"}} -- Complete [MVC Razor Pages Installation](mvc.md#installation) for the **HttpApi.Host** application first. _If the solution is tiered/micro-service, complete MVC steps for all MVC applications such as **HttpApi.Host** and if identity server is separated, install to the **IdentityServer**_. +- Complete the [MVC Razor Pages Installation](mvc.md#installation) for the **HttpApi.Host** application first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and if identity server is separated, install to the **IdentityServer**_. - Add **Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme** package to your **Blazor WebAssembly** application. ```bash dotnet add package Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme ``` -- Remove old theme from **DependsOn** attribute in your module class and add **AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule** type to **DependsOn** attribute. +- Remove the old theme from the **DependsOn** attribute in your module class and add the **AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule** type to the **DependsOn** attribute. ```diff [DependsOn( @@ -32,28 +32,28 @@ LeptonX Lite has implementation for ABP Framework Blazor WebAssembly & Blazor Se )] ``` -- Change startup App component with LeptonX one. +- Change startup App component with the LeptonX one. ```csharp // Make sure the 'App' comes from 'Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite' namespace. builder.RootComponents.Add("#ApplicationContainer"); ``` -- Run `abp bundle` command in your **Blazor** application folder. +- Run the `abp bundle` command in your **Blazor** application folder. {{end}} {{if UI == "BlazorServer"}} -- Complete [MVC Razor Pages Installation](mvc.md#installation) first. _If the solution is tiered/micro-service, complete MVC steps for all MVC applications such as **HttpApi.Host** and **IdentityServer**_. +- Complete the [MVC Razor Pages Installation](mvc.md#installation) first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and **IdentityServer**_. - Add **Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme** package to your **Blazor server** application. ```bash dotnet add package Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme ``` -- Remove old theme from **DependsOn** attribute in your module class and add **AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule** type to **DependsOn** attribute. +- Remove old theme from the **DependsOn** attribute in your module class and add the **AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule** type to the **DependsOn** attribute. ```diff [DependsOn( @@ -82,7 +82,7 @@ builder.RootComponents.Add("#ApplicationContainer"); @using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite @using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling ``` - - Then replace script & style bunles as following + - Then replace script & style bundles as following: ```diff - + @@ -101,7 +101,7 @@ builder.RootComponents.Add("#ApplicationContainer"); ## Customization ### Toolbars -LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in **LeptonXLiteToolbars** class. +LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class. - `LeptonXLiteToolbars.Main` - `LeptonXLiteToolbars.MainMobile` From 51e0ab5f372f4f9dee79124b3486281ac284cb88 Mon Sep 17 00:00:00 2001 From: Hamza Albreem <94292623+braim23@users.noreply.github.com> Date: Fri, 29 Apr 2022 15:25:30 +0300 Subject: [PATCH 35/60] Update angular.md --- docs/en/Themes/LeptonXLite/angular.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Themes/LeptonXLite/angular.md b/docs/en/Themes/LeptonXLite/angular.md index 7b4a4fca68..6e4cbef317 100644 --- a/docs/en/Themes/LeptonXLite/angular.md +++ b/docs/en/Themes/LeptonXLite/angular.md @@ -1,5 +1,5 @@ # LeptonX Lite Angular UI -LeptonX Lite has implementation for the ABP Framework Angular Client. It's a simplified variation of [LeptonX Theme](https://x.leptontheme.com/). +LeptonX Lite has implementation for the ABP Framework Angular Client. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/). > If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/). From 1b996d175539a952408a71d5815eaedd767c492b Mon Sep 17 00:00:00 2001 From: Hamza Albreem <94292623+braim23@users.noreply.github.com> Date: Fri, 29 Apr 2022 15:35:46 +0300 Subject: [PATCH 36/60] Grammar fix for mvc.md --- docs/en/Themes/LeptonXLite/mvc.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/en/Themes/LeptonXLite/mvc.md b/docs/en/Themes/LeptonXLite/mvc.md index ea5843cd93..b5575cba49 100644 --- a/docs/en/Themes/LeptonXLite/mvc.md +++ b/docs/en/Themes/LeptonXLite/mvc.md @@ -1,7 +1,7 @@ # LeptonX Lite MVC UI -LeptonX Lite has implementation for ABP Framework Razor Pages. It's a simplified variation of [LeptonX Theme](https://x.leptontheme.com/). +LeptonX Lite has implementation for the ABP Framework Razor Pages. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/). -> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of the [ABP Commercial](https://commercial.abp.io/). +> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/). > See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. @@ -13,7 +13,7 @@ LeptonX Lite has implementation for ABP Framework Razor Pages. It's a simplified abp add-package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite ``` -- Make sure old theme is removed and LeptonX is added in your Module class. +- Make sure the old theme is removed and LeptonX is added in your Module class. ```diff [DependsOn( @@ -43,7 +43,7 @@ Configure(options => ## Customization ### Toolbars -LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in **LeptonXLiteToolbars** class. +LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class. - `LeptonXLiteToolbars.Main` - `LeptonXLiteToolbars.MainMobile` @@ -64,4 +64,4 @@ public class MyProjectNameMainToolbarContributor : IToolbarContributor } } } -``` \ No newline at end of file +``` From 33967a7b391e5f94666c2e3eb8d63e7021702bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 29 Apr 2022 17:50:40 +0300 Subject: [PATCH 37/60] Docs: Move application modules on top of samples --- docs/en/docs-nav.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index b2061920a8..8ea0a6cb1c 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -1225,19 +1225,6 @@ "text": "Testing", "path": "Testing.md" }, - { - "text": "Samples", - "items": [ - { - "text": "All Samples", - "path": "Samples/Index.md" - }, - { - "text": "Microservice Demo", - "path": "Samples/Microservice-Demo.md" - } - ] - }, { "text": "Application Modules", "items": [ @@ -1295,6 +1282,19 @@ } ] }, + { + "text": "Samples", + "items": [ + { + "text": "All Samples", + "path": "Samples/Index.md" + }, + { + "text": "Microservice Demo", + "path": "Samples/Microservice-Demo.md" + } + ] + }, { "text": "Release Information", "items": [ From a337b1226d10de6b3689d6a5a8429e35a8b5989d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 29 Apr 2022 18:03:47 +0300 Subject: [PATCH 38/60] Added empty "Deploying to a Clustered Environment" document. --- docs/en/Deployment/Clustered-Environment.md | 3 +++ docs/en/Deployment/Index.md | 9 +++++++++ docs/en/docs-nav.json | 10 ++++++++++ 3 files changed, 22 insertions(+) create mode 100644 docs/en/Deployment/Clustered-Environment.md create mode 100644 docs/en/Deployment/Index.md diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md new file mode 100644 index 0000000000..136bdb0171 --- /dev/null +++ b/docs/en/Deployment/Clustered-Environment.md @@ -0,0 +1,3 @@ +# Deploying to a Clustered Environment + +* TODO \ No newline at end of file diff --git a/docs/en/Deployment/Index.md b/docs/en/Deployment/Index.md new file mode 100644 index 0000000000..f195019819 --- /dev/null +++ b/docs/en/Deployment/Index.md @@ -0,0 +1,9 @@ +# Deployment + +Deploying an ABP application is not different than deploying any .NET or ASP.NET Core application. You can deploy it to a cloud provider (e.g. Azure, AWS, Google Could) or on-premise server, IIS or any other web server. ABP's documentation doesn't contain much information on deployment. You can refer to your provider's documentation. + +However, there are some topics you should care when you are deploying your applications. Most of them are general software deployment considerations, but you should understand how to handle them within your ABP based applications. We've prepared guides for this purpose and we suggest you to read these guides carefully before designing your deployment configuration. + +## Guides + +* [Deploying in a clustered environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently. \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 8ea0a6cb1c..e6cb5375b0 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -1225,6 +1225,16 @@ "text": "Testing", "path": "Testing.md" }, + { + "text": "Deployment", + "path": "Deployment/Index.md", + "items": [ + { + "text": "Deploying to a Clustered Environment", + "path": "Clustered-Environment.md" + } + ] + }, { "text": "Application Modules", "items": [ From c2a6e604fcc3a0df77c176b3a2ef40cb10b25ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 29 Apr 2022 18:14:22 +0300 Subject: [PATCH 39/60] Update Index.md --- docs/en/Deployment/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Deployment/Index.md b/docs/en/Deployment/Index.md index f195019819..b11125275b 100644 --- a/docs/en/Deployment/Index.md +++ b/docs/en/Deployment/Index.md @@ -6,4 +6,4 @@ However, there are some topics you should care when you are deploying your appli ## Guides -* [Deploying in a clustered environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently. \ No newline at end of file +* [Deploying to a clustered environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently. \ No newline at end of file From ec671f5a88cd577be931fec453e35a27c978edfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 29 Apr 2022 19:31:20 +0300 Subject: [PATCH 40/60] Deploying to a Clustered Environment --- docs/en/Deployment/Clustered-Environment.md | 59 +++++++++++++++++- docs/en/_resources/Diagrams.pptx | Bin 0 -> 145782 bytes docs/en/images/deployment-clustered.png | Bin 0 -> 41488 bytes docs/en/images/deployment-single-instance.png | Bin 0 -> 30663 bytes 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 docs/en/_resources/Diagrams.pptx create mode 100644 docs/en/images/deployment-clustered.png create mode 100644 docs/en/images/deployment-single-instance.png diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md index 136bdb0171..4247fc022f 100644 --- a/docs/en/Deployment/Clustered-Environment.md +++ b/docs/en/Deployment/Clustered-Environment.md @@ -1,3 +1,60 @@ # Deploying to a Clustered Environment -* TODO \ No newline at end of file +This document introduces the topics that you should care when you are deploying your application to a clustered environment where **multiple instances of your application runs concurrently**, and explains how you can deal with these topics in your ABP based application. + +> This document is valid regardless you have a monolith application or a microservice solution. The Application term is used for a process. An application can be a monolith web application, a service in a microservice solution, a console application, or another kind of executable process. + +## Understanding the Clustered Environment + +> You can skip this section if you are already familiar with clustered deployment and load balancers. + +### Single Instance Deployment + +Consider an application deployed as a **single instance**, as illustrated in the following figure: + +![deployment-single-instance](../images/deployment-single-instance.png) + +Browsers and other client applications can directly make HTTP requests to your application. You can put a web server (e.g. IIS or NGINX) between the clients and your application, but you still have a single application instance running in a single server or container. Single-instance configuration is **limited to scale** since it runs in a single server and you are limited with the server's capacity. + +### Clustered Deployment + +**Clustered deployment** is the way of running **multiple instances** of your application **concurrently** in a single or multiple servers. In this way, different instances can serve to different users or requests and you can scale by adding new servers to the system. The following figure shows a typical implementation of clustering using a **load balancer** service: + +![deployment-clustered](../images/deployment-clustered.png) + +### Load Balancers + +[Load balancers](https://en.wikipedia.org/wiki/Load_balancing_(computing)) have a lot of features, but they fundamentally **forwards an incoming HTTP request** to an instance of your application and returns your response back to the client application. + +Load balancers can use different algorithms for selecting the application instance while determining the application instance that is used to deliver the incoming request. **Round Robin** is one of the simplest and most used algorithms. Requests are delivered to the application instances in rotation. First instance gets the first request, second instance gets the second, and so on. It returns to the first instance after all the instances are used, and the algorithm goes like that for the next requests. + +### Potential Problems + +Once multiple instances of your application runs in parallel, you should carefully consider the following topics: + +* Any **state (data) stored in memory** of your application will become a problem when you have multiple instances. A state stored in memory of an application instance may not be available in the next request since the next request will be handled by a different application instance. While there are some solutions (like sticky sessions) to overcome this problem user-basis, it is a **best practice to design your application as stateless** if you want to run it in a cluster, container or/and cloud. +* **In-memory caching** is a kind of in-memory state and should not be used in a clustered application. You should use **distributed caching** instead. +* You shouldn't store data in the **local file system** that should be available to all instances of your application. Difference application instance may run in different containers or servers and they may not be able to access to the same file system. You can use a **cloud or external storage provider** as a solution. +* If you have **background workers** or **job queue managers**, you should be careful since multiple instances may try to execute the same job or perform the same work. As a result, you may have the same work done multiple times or you may get a lot of errors while trying to access and change the same resource concurrently. + +You may have more problems with clustered deployment, but these are the most common ones. ABP has been designed to be compatible with clustered deployment scenario. The following sections explains what you should do when you are deploying your ABP based application to a clustered environment. + +## Switching to a Distributed Cache + +TODO + +## Using a Proper BLOB Storage Provider + +TODO + +## Configuring Background Jobs + +TODO + +## Implementing Background Workers + +TODO + +## Configuring a Distributed Lock Provider + +TODO \ No newline at end of file diff --git a/docs/en/_resources/Diagrams.pptx b/docs/en/_resources/Diagrams.pptx new file mode 100644 index 0000000000000000000000000000000000000000..05edf2ced786c3a436033b91c59d09d9c35ba217 GIT binary patch literal 145782 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�sC3^7!qTRL8|EqU9Hvehlix|^i=czt-9^}F`{t2$}C-K{5?0$SsU2H}fC;u)bP$%_8CsxIrhg*m;b>R}D6J>vrwy z*)F~&4jLNzkHjd8xPpX}L$w^*4n_3q*>9^c1*=v)kUvv?B`Bq&%#Xvrr>cl;=Vy&O&5^{RQ$v3t)#R*2R^W z^v|c3mxn5;>|k1ei!>#N@Wt6}7>F>V4eAH$nlA;ci8J5yv{4ZQxXy_Hb7VLDht%X0 zj6?S4%H9`Mj5JaPTXx9s4W78`GRiL}NoLF2-m}ys-79-)n}H2te9@=x2#aU$u2qTm zZYopN0JqI5T;g=&qIp5z=sVUB3x3aA*KI_E-Zwh#Z4+3Rx^^ZorDNicO&e9SHTHOU zW=FcqOi#po`@osl%AZ}wOG8iUa8pX-yul;yf5ccxHMvAjksYq5|CN&u;2Cf9uIQbb%xePB{B;95*Bkhmph+D|Z-8d`(t{c$A=35Liq*p^=nm$@^(NE1gF68#Z! zpR22@ooNjZ%}XDbLOobm|I8p``Lhdqx}Ms*&asC5_Iln5kIP-GGVfvWnMgNW@mCRs zND;>~e9pF(tKIc{_gkChhWlP0g}4RK_hRBK7K^s5u9_p7vhp^3b1uTB-`ropQZR9F zaX{=usw(8ED+Jio@8^Tt#)a3;^h?p&iUg8ugp0 zgFo>r?z3m(7~{)siNjS59F(z!pIkvr4FMf9o{e)$93OBK!StK)+lc$EDbW+9#rZFZ z-Ndb;uY&J9&v4CKZSvlj_NXM#li=&#s1h3YS>zZ$WiQh#ZrLY3%3d2CkbtNm4T&PD zTPeZ%%Y*5HOZ9saFCSN+)#~g_YTTzE4U)~L_WIo9a38a$tWx}vzCsaV+HQH_FRe^F z72NhgDHq1h-|TuM5Z_hHKE!4SF@9;uuJVn}AH=ks?!3Al$^*#eeB7L5go)jtXIMLY z%$~Yn@G`2EbuU!oi9=Ql)7vWCTSLNqHT5y>vNNtWM)oj*CG7yIl{SaWyHW1_e<(m~ zu><){PW7YBZ0fyAcRtJKI`G;jxqU^pyLLAtRM>GYjzFg{Zb06F6HX))axBlrrGe_; zBueOT5~#vRbSpia((W|0y=nNlB6}RIPW~fg-6=TQFq{=Te$GFk+@z#=rqWaldktzb zWbv%A7M{Na3G&a-UsrOjnT%3IDQW50{TGFQ9*d8BaigzEMIuMCB;JNFgL-9^H8*W} zD~@SFbd|#cWno;2D6W}APR8+rqU7|NhRG84=T?!Ol~39`DxY0nwLV{xr*zuuL(xeO zBuXO=q`$iXN}LYO(%O!vMmG7wp}U?7U#PEJF5mGU{?1Xv7PE(N=KEMVHQ;gpQKcpH zo{8%dZKvkjK|i|d*Kwu5L;Jk&$$3Gga7XN#+p{9{>ZyOGf;Vt-Vfp06oQzFdoefY8 zpeBtNZ-K(>TbH!@jKH&{2ikn}+|ZkWxK0OLdoy<9BfVNIm}^{x>MZf7&L|Pj&$lf& zvvPHJVwcXwE+WXUBL$t3z>8O;=xlVsIDJdJ-{t^a48345%jdnij8tRVmOT8Y4?eEv zf}2YNeN!BS9af|iEt*yMIhZcg96Rk=gew3urAl)UW{*x@>$l%%X=HQ>rXI_C(L3dM z_pbc=IAWoa>Qd{hMTHFZ`cA*J_cg9JNJJAs?B1P8{9|pqmNt~= ze+DI^c-{{o$b?y=O?cF&`Bc&9x2Jnw`Tu155WjD5xV_)IM8#Ib=)0thjF2XcDK`v> zy<)&S8K%yMg=n)GyqxlMSlRU8!P7dVw2RP`p_y6P?cZ>IY2tq5ivM4EiJr5Q8Pc7f zdFEN()R7(?Y2054>%zG>G4`(BD6yTh(wW|q&JQj2TEYn>V zcR93YH|c3zNC;=W^a%I*3~-LTDoml<)BcfU`bEjsW@sg^p(e`^L8;)Xi`wK*y-=sF zq4b*F;DHhhf!Vo(?UIw3=_?)J2)5Q+6PHoLN2IVQ{L>7pWgAeUBiL=?{<82@vTc4q zfW#xa)mTD(=pt~V^!>uYb!&f^!mMR*N(=hxUT83q$(L@QI0vDE?9DVaH}^-gk#@o7 zhqebi{;Iz?@xf|V1c!u>7X9S4$;2xd9NDygIa*uWrU#c`m0n!Ty6#NI-#*- zWnEl`Rz^9=wG|M>FdoL5KD<3iGThN2cv#x8?nx_l@&Nm0Io74wAHo4=h-iN&JBPM5>k!lBgZRl& zs2XFYGTKD-eD!?= zPREKs!uvXx4b?G!f9xjz`hVX-Y2u|J*7P9jJ$w*ghIlu57a~X~oc124uFFYoQ85Mq z77w4w%40?v{a*OXn0W~*dueD7OFll_HgH}t5BvLttU@%5 zBqi+cx(!Qi2UAr{W|atawx|ou=n}I>OFRSFdLe8~8=Hmdj+Nfi%w{iTy*HQx}akI&4S^1X>)_F-O7TuBTV5>PSvKBOf zMC9ErgH;R#0~LugcVRv?B5m25%-7E@m&1 zHn{Wqq7irYB3p@Lk6~9=Wb)&f_qsjqZ}q(+?f8D)0}<;42jCTK!fc3ABGk>7J#S^Z zMIPT?X>)N%*`l~7%Rt$85omS=lFSDRyff4<9s1B=kL zjQ?)%nd4Sq`NDR~2~&skefnp^3{DmEu)anKr!8OBd{dYw^3=(bUJ6AOtw&zS$ald- zLU#dHEEm}UPI;lF>er%lUVUBuO=lyQRt9=5*6D6ox@5!B&=c9H9vJ+KQHJ{n>8G<# zPuh5B6STgNpiCUDd^8dJy`_v=c0tdt0WEXx z%hg|iq!4@Hu)npN)TmO&SmgNUrT{zeOPaWYF2P!?M$4F9yo6hk$C{G1JKbCd%MUIE zT1QA>^HV%roh<=teR)mWk==Am!rZy2;I9 z`vOv?d!3@%0TQXod3vDOSVvpu%P_a7QzL!rz9n)oTP;H4Rthsr{NjYSP<$ zdKlvM3i%pX!;F8EZl67Y`K1_+m=&H{(GLA(N_}PIoS=8{V~@ncHZE04Rkt~cjYb{qr6RiSC+0FJ8b>dB#N=B zQyHE*;Zy-subhuh5lOuP7rFeH%p0;DqSbYtIjCz}ya2_rR)}dM{ZJc0xW`4YT(Xp; zO!nYs?H?bnm&XWrrRaHG?#ESOo}l=yK4;~38HS#odzV<-;ZSrd-g_UOm$m`-{+Gpv**v?}u$Vn+zr@cBa?4=x* zdIJ5aLw$=BTKT#wcavQve79p2z z>L9ktX7PayOIHy-?sj~iSHyh=aB>eIHI_I(%xpjYa`_Y}MmMwC#VemD)VmxpG1*x^ zTo#Eif%de~bC&Q^;#OBhH9;Vh0`p=4eon-%n4q!%)qQUZ;$3u*);Z~A}BOec2_)n8RyWVC;tNk7Ze-9408ST6nM_wQ;R?orl~d^p8RF~b z6De8i0{&XdW3>KQm`7`NBcfPYAKJ3U#3&k@Z9~#N88t(?nB*x#8 ztW_gR^>*TIb@k+rziX%^5kBut_Y=*BA53Omu%}L77JZH$yR6xA)v8sy1}02gFBj(7)N7exr?6ff z+O-hqkUIPiXFzFX(%6_Fu0cp|iD+sR@zu+w0qC8*p(i&`MtKhwFgW*ztNrDaEg1E; zlpEFpnb76bDU&?WcLJ=F$Oi}$U!}5(Q>~D;SR$FG3#3NH*L{ry3gfQQqw#~ha5DQ& ztrEHt&I=#43pdV=_OZtY&$>sp;`HO;$;7d6KJ+>`1SOi{6^^Z?4S4Sq3*yv7zWg zx=%6l4b0}^B6gF?G}0G~LE<`@_FfJro1ckF)qqYUi#*cn2jYsm`$x_7m)XC9YXvAB zp8t%sAN~IpLE5Qs{~%^eH>91`J&d%(JAY1`|=XE_L*o$5KOfLVd4GgKFM2Rg!B zHN<=`6ds}Sf(2wrx*?}tD|#LQV{_Q@Q9;e(C`%#;)$j;~odwUJRe|R`j`HGozV+A?LosyRTvx`Zjv8tS?g>dlDE57D+t>WY7td{X}4$x>@R zkKDi0+rm;aX06x0VJwdrytW-dlSwSU6P1`a%_F|TJ+z*4)6X|k8*+B?=JD0L&Ii$H zv!KmewAoOaSEIC9gCs0l09^F(&6}&eE*C~xl^Z)oCE$-=`+l7=D zQ&C6Y8_pP4qh9B^n41|~{Lu3H@n*2yJnnt}p~1gVH}N^-fvLrV@UaY95U0`h3Ls0fB0FZ5K@@=RTsTtt`ywqH zT6p?={^k`P!GBkEu(wewY8nn9d}HyYeMN_tWthU?fKR8ijEoVip?O4yHZ2_$7+l0o z0Y8C@=AyMJmv#6k!pEj>J%Jo2h2>R6z7mi92a^ea{ig7TBY{!$?bmsU-hnxRpWTUVmnMD2yXjWWcbq{(Tzk3L;h{>3jm~MwbFjSZxbCe6?I6WVF}S_nRwQ<;1`bBWUFxd?ZRCO zNxGMbg_g2=q85BR+fY-N`p zD3y5F@Om@vueH~3<%`^*p_e`%9XY&cLzYaN27pwaQa+!^;iz(;7oW)~|6?+G2gZn~ z14%>|hs4y`Tf{XRHNmno38lXVwn@~+^4`zN&TbbAO9lILElRVN5*1iO#KTheM#$Ot zb+8K=`0a&m`p%tNva5a*fG$-v4J3(bwaq$xE%Pskef9q7=_(DKx{j+a`x&g}YjRJ2 ztM;^>S1Uy9a=1XA)=hlwuqKRfl2EkcUMVWcd(VGsV8|0Qy<;#%C9aQDIAIrpDHN`a z2PH-U8fv2d-6&rfN_XLFeksf|?SZR^^Y7FG1R=7H=5cZmb^ljMVi+;kI*7;lpOI}n z<>`aNnOEb#;TGbt`qj5OGw<#gxVjnyX^8}3**N9BKl^%VN})$MLNOhp0I&#{pC-P_ zvacAtUctS7XzVP`h_>POLc9FDx^epvbn0(#GNo3MFrGD}4pnFhA1E8kL;B}}6Rcc? zpR-ZCV>fg*=Ggv739+U(ICAWMVJFOcMk2)QnW%H$;<2I7qmgb!B2NXENv5?Q(kf3U zxio<&TuF3uksr4FgJ}qRroVDJ(?MIetfST2|I;6mP)d{|z}1s>!(vq=ePUVj4%(Vabbz83yER52NCJT~phSOQH%;Y2Q)ky4 z$DSlyqisd91wy|sxM1#=H0yFD{<1;l65=GV8CaWbYNKGYK}ht>4YcWIkfOSaGzd#W zFxE&)NPNC_sQQ?(@k*Gc@$J7|NRfNiP=g`R+4FoM50}@3{G}-e%X1PGN}WMs;BXD; z_d5=iP$~_PYNJ{73_meBmuSypR8)Sn@by0pa+K5dbW2Dbo%`qRuKZxbr^)YOls8~n z7{R_~w$-|z$PAA0LkEu@EzXg%v!t~yAr^B4U5JpIw@-g-qsfFQ7?fFsLTr%}KPs?gb6!EMfD$w-eSMl=hGw>pOl(C951YA% z&Gm*-VW7^>UHG0#6+U+5lWv^sx#8i*-U2~W6-UroaMEraB65Nb173FPmJm>E6AS+w;aL1+ZqxAZ^y5tNFvsZ=;s2OI*}U8WA;o{|xLVo`wO+$K7U1zR zTH?v=+fRSK=bGCoa@5dpq$9(y&-cG5jz`_<`T?nbA=#D~113(MpUbXR2>;CfZWL}l zJ%1VH@S5B7%sSd~qpO|!)xlR>f+d`XqS`pBZFYc`88_x{({Krya~6!PmC2{8=l6n1 zySa_&yQ)|3=GOVJf$IvY2KZeOS7KHTmmQ5&yJ-B$V#})r634H`SB1g!WfcF^!Zyz} zJk!yhaH(G3+?2r>S~Z?H9upMBR8jYP*qU4sB!+L7kt7jS?zYu zEtR(X?Vi`eBLp%Ezl2>_#5-S8)->e9oHf-nxC=2`K`W)G3(um*3JpOij*1kd%q`|-h5$uuO0Vg z7VF^=yM$DngWc=3dg_8-2ywYrf;2>$lA-{JiCyuUg0~%}@pg!Atu> zofa7T1}NfNLTjz@Rn>J+Y4oiWk{2R~d7M=N@J@6kwkU+9`bQhAAP(AvpS$b3huRYQ zsZP~RGMUg+7HQAkkSffRAYhqZctnjah%sIVdhZk$arz~PXLkji%rpei*VQ9;SSIW7 z^OFpzV3+4c?cC)UhBG`y&^EO1NQhJ(|B*mgHSM`=MbSUTcm9`L_l^>$y>1I`eY!ev zI8vLs@)@ z{Snv3A9YS>Ws~TQ2=!FYphp{HK7KctMsPyTKYkbG zC`-l6r#J01W=bv}Zsl%v?T#^eke{EgUl(_Vw&rz%uc#%FD?=R|C>L5nOaN1p9M3-6 zc-V_26>WeCey4*@<&Cr5+XqAYwR7_6tqZCbQ!H^d+eVc2m%Cus_{*}LsVGTiC>;tw zLB_7Uq9Qi?D6|W5vT_#p=#=Z%PM40)_3xIl)L)|fD+oj%W#xp|kC!!v_J7P_=ZYvu zD6LcbbneU=Ry5K1)JsB5lS!G-JoDS_x9`%z{j2)wn~L}ehIkqSR3pA-Njr-b^)kC` ze=Onnx}Nw;r!#z5s)!}MW)7=pWsoJQ-x$2#Th@hY8?A|AlLaT=NXQo{uRnDvYGdS> zo0~j(_dA-0J$Tn}&qVpR=&Ku3MxO~-n)#(bweOdXcnF)zBD>MP4;)mx7z0=iY+%qr zixHVhy|S0o*!74{%ZNX}+hG`q^k|G2ZNDOtnl}^^5n#0K?b3vYmoqxr2(dkB*XHm6 z4YgLfd>!RBI1$9x0OJ0u?lXZIIXOnKUn}`rwd~%yZCeJ})PQO2@Wl9dz&Q-19mB_3 zu!SXb3HpihBFRE}I58I!vTLPsG7F`L+riHfCj`3^GvAn8`O|)d zBdD3aBQ{=&u;L}c&omxJPaPhfSh{2xn}e8R;-JMm`A%$E@5_KES_@a}>9@P6=p2!^ zpSO{x^srx++&-za-4dfL$k3fL-q>pfbQ6n@Wfp%uzir2X8!g?R68Yozg?%<(R6WoiwpKu8Fyx;GxLj zw=GqJU-tJ`FJJml5R35P5Ki%XcIAbvcIOMTn5E=unNE<@69TA zYow-f5$T1G8x{3vx)L$AI;{W{Xy3T;&ZMA_KZ?~**8D<6J`>64Tt;U|Y+TsGLSU8e zS5)je9^TyUn+AB&@|9WcLG(IbAz3$Q?9^3uD2gS5Hc{;e;e_d9KQ|k=T{BK2X(VXeK_IPT!IQ=`SD{IXC= zA~zT1=iiHta9hIcn-u}aYrr1 zg{JH6ya#RGZ%|B}4%tHi@X$~~_A*`i=fJ>RR=MH1xz>NHGS;3LV!!1-on{by{f_Nb zOI409h962s9=}y~ebTw(4f}n6YtNtPL|>{FxASKMiM=$_@rpR_J41_x0v4CE??o#7571Xi+|Xr9s)G{=|UBHURTa!E#wD#cpv?gwk9JkMRc_ z^>-3}IEMdNWz$f(Wf*LWwFKYGj0&Jia%r?8`-JKKpV?Ejj_WBc(f89tUEE^o-}|3F z#6D2IL~C#_jVtN?iS1(fX?t#tgsuI`GNY!Im9R@g*-x5%#r>POH2WXxYRid7Tn26WJWKD8v~^T*5qa@ zuRH)4veEo$VrrTJ8hk8!dIM`}Z@kVv3MH1;4uhWW3(gKnCGWT;i z?~g~OxV_ee&-HR!OI+u;IO)1ywdqH@XLW2lYh!z+bh(TZZ|o);=qbP2O|!NEnyV$! z=>!xr_^JZv8j*s+H_TkJN<({rX#2!))YMkp_${If&hPeRv0%g4Q3^;u_V_6)ljo!g zQo*X2_=!!r7qhkU#gv(qgye$Y^E%Z(HS+ongVbBuD zS#=}EpfVdScoT;VPEwhH2)v>5vH?SIFBn)C=?hUXRDlSi7$&9fF&DmY(C-_ncMJ$Z zf4Moja+BMA=Mnd;H(hKck#;Rip7MXw|B{7M#XMg;71T%#E?mY69OQK0V{t5Cj&~a zbQk4!1Tp_A`LLdv_>PgnJix2-oJ8Hx|Ms}FG{I``y}Kot`d_BKH@C0=)8?36Jy!(Z zKfDFsV+cJb>~6fUHRPK!mo|9?aI(ZV_(M$mL(Ap6K(X0z@|l8%y0fFBGB-s}a`}S9 zv%K2-HfHX*{gpD*1GVoRGG(h)aXV?}6|bI)C};-wRoRF!#CMig@=;P`Fya`xwLEkW zK0^GTBUeMiknIXX3ybVW3~e}%72txY!Jhfp8Ggr8$jv+F>TNiSlI!&Z=4cVF?4(oy z{`H*no=~0z!PO{!K+nLckq__8q?yPfX5fm8@3IXwFGIwy%0fo}dEtY*_8mD+O4&%s zh2^ZLqCoE>Z6w`qSD-gk`H~W|!Os}Bs(pN6eFzW|gA*sxT^t<~t+J+$+&*YU9OQbD z6Q*5#0;i0r=TDyO9PH^492xrz1q6)zLG-vRvhBnwg zd*k9`Co+e)L_b&f$ApOc1MV7c{8Jlj;ERde?CeOiT}*5t%#J`K(xi0&L4cA^lo58l zEqb(%behburbR91#+@=OeH9BVd^{li+OwlG$>ci!=izC62>Foj;mw;|OZ-<}_}KD; zfbik50|85FuEvkOgc96@InClHk58Bexs@dLqt?qzS_3*Td-;OkC`#TDhpAI|6!+0a z+FNdqsQu%O{oLPs+)jY$6G~r)m=>vg>!23G-yZIn1!Qp9?Ri9s&-ZH@d_?gIBmYxgqmuWdtUu22LL%g7A_}956v#cl`j@^#?4} ze~InWHxvjFsi_NYk8s&dOKM`O$Xjm5?qUNnS}R}XG=Jzb0oYt1&9E=w*$8yVs(ju6 zC9+5R<)m=ZKWLWVy$x&EJYpO@a^xPml!=o&zXowdMm!V1gKvm{kKO|M-fPES#flIp z37XK#kmEY+KQFq&R*@qpk1<{OK5EhD#Gi?ZZr7D_Uv{6#`EjNs9*;;pR~6NUcb?j< z&d=Jk^Q8E!Cb<3WtA-;Ir0#8kIFvzO$|Hsb#s-a3PnQH7L|v32Q3YJh>bHvxL1~A< zpbf-E-F>w$(foT)*@$c}>yRp^P+pky!~7t&@xaZIJy_3JBq`{fo~p`Sw|vsQkLzkP zjtw7RZtw7hVC$?pRJ$y25#dYi{Duhoh30jX@lZJzBd=uL?D#a%-Mge*+)&*RPr-VzjaqlqHWCzwenXP6wg*)*ByIxg0l# zl{$vEK5y@8_TvcOD67LAhWS=At{aBDZVFqsCUz!HD6yuz8+cvycF4s4qlkrYy59R} zpH(QwjlFICu}pS=GiuLGiPfpM$c%+-IQMi4LMf!7t&nVYD@68K!{6UOVePv@&9R84 zHD=or4hQeCX22RynWQ{++C(2;`*V|p_|v$0etBenLOdmr*ZZxB z>5s&{)Gv1ndS9!EP+|4~8nYo0iIXOrBiA8vLUujXY+Ri%rxZWY4@31rO^|y_mk@)Q zIXS9rzJXbCKUU-L(ZuAyYq@i-`rYhn;VIqgQ^u(#>OAkXBQ4)R@1|q zcXzF9|HfU{_4ZbJ`iWiC$lvWs)Gv-_-3wwmY@eb8?o4C(4(sZRGGqy4FkI2r%L2tk zMW3!CZdFuF&#|k3r-VYuKxLn=%)f=K+F4}ft(-z#lh|@CS{9yGCsHb(AXaa@-}Ce5 z8jqOvt)z5`jkDAVM#qlhTjE#y`uIG45In%&tzi7zb<%nopZV$#n@MZ!rONodb(OK@ z;g}Ln+oulBw&JUXM``lB|CZ@1tg@f+@rb7^rFgMY4r{$`fwUJmE;KVFq(V$dme=@f z$-$WPKF$oK_#Dx*b6Y%&9$zGx`sDEM)iSP1r{0SXB$mPrGRh$JM(DiO9jhLKp0XAT zaH8#b#F>ZYv!+JG!|q9@{RtKQBqtyC4=|Q{CqjTwCV-<(Xf10HlxL%DXXZ0Ww&Emq z0}XoCBu2H1v=9h1Ic+RR|=(>#8* z_}#zif*Sa=1+;&i(_enrmz91hi!5Hl4nrH+vV&|?!v0=ET==x0E>0<2?lqofZ{d=T z(?8pDMLm^iV6a-n3*$uYFA2%b=Fjff#iNF3}kO*{?W`16t7sH zI**8XVJm#((uqQb`Iuh-Y2Q*Jh!V5NWbYp<*Yp+SImw(za*wSb#SFZLigR?&i`G45 z?3UIbE6|nrRmplfROK~xpDEa=m;CUCwD~EGo}H!5pN$|yQiAu~e(M{kNLky^z!zeD z{qr^HStg0Fth_Hlq(v_{PdqG~n>C7lRgaNo4H=LsM+gGDO`nwI*aEQuV-4zyqXoE| ztywYW>3Rmfa+CICsh=&Pl|&W2Cqk~Le8vvSgUr5Db9Wp71mfmadjr?gt@r@{AEwR( z9P0J`{~t?8#7H5sj6K=Ql8_o@DWZh2C88pG_AR8bL|Kw-5s6HwCi{{#nJ8)OqqIn3 z$iDyYPo3}Y`k(7u*E!d@PRx9k=eeKze!pLDtE@M8>%V|XUIh{Riu9Vlf+ zvQT3pV4b2(uPP!3fK0R&;x$?tGUc)gG13cVwBa&wuPOo13$DOJ#*pQmNZ z;mK60-3*-Dxc52)0$1W!#I_xwR_5k~UR9a=6EeOiybYoSQ?&T*!}%U$t+u?g3*c8Xeg0oS7mrjPH1a?m6fUiTgpzMPfwpW({S!FD~lm!yv!;rRf(&wdpxH1zC zSGhRo0!@&oVfOLrHE_P>y1S{?y;lBBHPrsE8CqUpu1`ZlV^mOI_Wi4S5W|926ACtb zFg-p@DlK)7UD^aWY&{~Jh5cIvUEaaZ5bo@x)OO(rgVk%^ITr_V9+b2PrlzNDll}Yk zrUuJ)zlWU|wRshu&UnR$ksrVSdFGp{R-7l0odak&UR@y};CX*qKwmp?NwD;1>m_fX z-vZ)wYH$wu>`o-vDYijgGC1=R3EAnpG5Y^tR!pUHdzedG%b6h96A~in!iT)7hT}T2 zV6S^-q_yG*xh*NjzDQUXRaNmp;~@k($>tKtRj6zmgGt}_=bOf{PzSnK@cIr0HIzUS zOJ>gIe4!?`F{7}kNFVfQRaI4$@zC~56wB)b6baZvKH&`y{C%^T%4fLlDmg`_sgF~) zM|?JNAdit;`m|uGbc|Wpl6uYJ35s=ey?XTzG=8dtS+2rLt#!W5udu^?J>r^5#;2|- z=DC&io7x)_#(jE>7bmrD4G!X=RMA&qsfS6LlNR{V&qDcK06~K(rfUocAo{357?g;i z)6bq?1Lzq9$4D8Zf}&KR?Eq-2pMjFqtYZW0WhJPN)6UFHT<+yIh=o55`lUk*NFQKP zos`mxhLmGkSM@I={FSr27SwQyvg2L!+D3%OcHk%Nqza~&0lT#|Dd(J(;_kZM6=LbKPw$^1RmHHn$%;G^ZO?+AbdLEc zh+Pe^vm13!zTxtZ!B`K0YqdTu%l?_*-C6|)V>zs8%$N_|nAtdu;;aF;WI(3bNF@KHW&#gMtk@Boasrkn!<10@(5w^0VcTFFNX!ADvC%byfA z^G}vzFB56rVIU1GiTm~T(>g~W>xJzQ%rQDze+xr=#ssJ}2K1t!Letj8 zIV;SmgG{6(Wl%KTtP4Mhxtq>+=@bQI48wzVV{0#e$h)o71lJO*wZA*;d9$bL zv}I%SM8EDz?(vU0^#VQqvTgq&ds|s-n;~H^^~Ig}$2%JdN{E1Bfx#?t;G1 zLy+-^Vi#R;ppQJXP~#-%{6d*EQHoCT6IV$c~?+Y zdx51`QImPm?~VJ#MwR4_%ZUFjOesW(Y~)n(lzqGE`@vcJjL;X0XzeP9Es;lK7ne32 zowWLS8H!Y{P82J|qUti1nuwm?5D!IQsa*mfmv*+#SRc-33$~%nJE6Z(;j&9z0r%k0xV0qabbq>LFf(brO{QGA4Z6!X+-L_BKFPa%z`4p5*?)6V z$Inl7XUeX6aMyRjYmgwGS8CzX9iR7;mv~*pY&R1@vQMw$@7nR3rN@M+2mc7N)ou&{ zlDwPGLGUGo3UM?JaL|AHk)s}MjRSwmv2oB%IK7e{c?k9&+Fu03Aik!P=@q-d%IU0D zdtKdafi(B&si|wfrl&V&Kuq?@j~YyWSo24oxWoG?}kcB^6S0-@ngpHMNh#Kh|w ztJkR5Zv-C8R{($m)%NoMgCh6E`FRho4*!aV#u3?uiB!#ucl(_-MX~2W#tVUqy(1&+ z&*^E@>-n(Rp9irah&N9*6nlV35)y;dxG=`j+^n#zfWCSo8AH;y<;950qmQwxiEpZD z%GKjcKX_@~dmsVrtYC5Dz&2^l>T{rW}TC zyPI8tnfCxd!@{ySBMEBz>`2NrWwe-A!rgMD2o%FD;bzw?roy7gS zOJL2`yY6=fWS!1G;T+a&>o;=U3F?V0ex!afv-vfrH*YVz*=6PiCIJEJ7A`cgyma_9 zrEl|jBe@{ct_|$~L(JU|!?i=uxSP_&0Z!%$b?sxO_eq7h`YU|CUAr~(-#GHaZEB9| zMGi4R{oEwh#@RW23pXF+Kz8T!< z18mU&v_yE-l65$_pVCiIuFmx7_z0r{#Ihk>o{JwYAxE*MI2cqRs0)wq^AI*rg(cz= z93ugAnY}ri7*<<7Z{hiR8_yGXI5I#V7E2^Yy*7kbT6|Xo%H}qK#zvpN-rmocEh47Hm`-d*8~PB8f=?>E&z}7#2`PvX>50tY4iGUo z7!-C?BKh_5w)S`;0CdhjnsB0~8;~Hga=H#5s5@yL3&vQ;+QEj_F z+6|$`@&(2!uz!=hz8f5Y4Ogs9Axgj92#)-o`QFo@AbSyQK=RXj>we%cv^!u#n&~Z5 zx3tv@l?nS9a+cb_ljv7$(9`|;PIWZXu}&rF3v4ypLuJLn15`dq(^ZwBZYSb5cyZkw*8Jp92bPTY$i=_YKdx83==L zG(>KxqrlUWd^`ZM=k&jb`21#SDZHKv4Z<_Ka^9~B4eIl2LcNJ zG`CK3AzbNDpg6|3>HFwlF#BN+YBp>hNP}LQ-B|<8PI5PWqdWLS?9fMWopSV|Pp53f zdr0$I$xk-ActumCWv;G+-;36lQv{a)=o;y&vU;3&mi2%;oZBwO;(1KFDmTYx>l+?h_{S`Uo$ag_HIL zmv=HEYEDkLQ}}24sUQdxk*U&m6&Waf9j)SUXTDb)``5HO8S`^@K`#v=N!x<9KqYOA zK2jkMCEc!GHI+GEatEBEk~Vs9uzA24{bi|k=@Xd^ntf;dym%>pG7jIqj?4c`B=p8l zhLOK}`O)ifUt~;G@_-Y?{B#XXLgU+w{agMp^hpE-$w|%+?8KRh+%?(-)y;{HsXkAU7+JBY)}#$Qx9(BgQ^HUAw);HDXMNarFm?SOKh7EX z`Bm^gBs@&eT(npRmwq$qiaB@vB$^uMNW2LX1#Pgdd&69S5=7gIA+&M)sIL<>3Uyck z6)?oZW9f0$^vG5w+eM*F|Xi41rl1gs0xfjQaTSIFXd& z*|iUjVs~7>if2j068pGT?iqk|T*J~!{nPGTwcQHXJx{9;{)?b2TaYfGe)ua47C_c- zr019$)M>d>YnPZ-nL8f7jNExxZ)}I?uK(&)u>QHYbTfuS(kUz>beZ0JFbf_7*&ZN{ z3au1)`IXMo!qzr{GI$6-xdm$DKCq8#8FXD7f0rT2b>x_;t5b9m>uh-1ZzuBoySgpM zYnOJ>ebz(N;En3Ah+T>){LJJuE!pbH-CnqvqX!>&NTy1_O0JcAC>8_yFp=0i0^vNL zgDs?euptdg)Ga~8)&|;ClaQvioq3Q)!p7G$PbRYG#7ucP?D0wTAE1~@n5TyI1%;(A z2{tMC6w!Tcb`W()D1##&4yRnQC`jIk0X#6Dh0B%zkz!cu#Tcjajo@oIHG2 zfPCTjaeq$5J&c%Pf9<0kpI6*J@%d!-e6Bp6wFk* z^mZ>!VAo0J=;Q%q#GiOw>G&`(GV846mR$Y@BwT+hUFZSwJ%Bqr@N5oM_ z-(Xla9GVpb5(znwC5f4RVZt5K7NiYVx-L?f*CtTGyNgb_u*R5M*J6KLghB};kLlV@ z-IuvC=MER&vDS$NtS)6$leoa@K!(B1HcZ{m%pmpTj#^dVuqUdJx~oB{mrB}`|3Jny zX5MTDL~YMuL^ud6rUb&y-;+n~! zo#PF3pS8iI6Uq}dgU$n$n&Znpnbtz``!O77;?|vYK9fsxARys zy|-ak8+qp-S$msu>CqOR^c8s%?z-#4p6zv(c6JR#>4xIZgzL1-;MGk!WYj91^p;CWxyep+H}*|L*}jVznS`cj0~2gotBmr* z4JSgs{4IXJSbe6S9Xs^4#n4gv{;45HE^0`rl4zudCse1BkN=;7`>pnAWElq-Z`$t$2oWG;o;IcafY zAZ0xgGka!y_%(zALi{0&p-*{zK7DO?gHc_O@z>WiZVoDfa1l9A6c69Sam6-eu*n&| zdg^mfWK%C1-%Be4J$CFY?t`VR#{hPGsf9ubQfliKF@d|8Y-hrRYheQ z+OSZ1Q4tSeqtEu~^oxh{bgwa$!u5|iRj94V$xx#(F ziP;Xj2B~AUS`6zXW7T`-T|Y`DmiH?m^ZN@R)NHgD-_S}1+2SO5J=XLWJ1)31FK?_4 zXse#@_9Q5y4fym^Oa_C5R3}i&+%ka?p;wjl2Rqch{WxXSjvTy4`M6fj(rkU!i5@R~ zv(T+=a4-J=rF~0=n>93Fmf(0s08<2ZRmvbogz$Y@X2$5?;Ht_$%>mrJUzc8~*_*$e zg)a1ab!O0i@`kQ-+EE`%%t7<&QKom0;@sw6auC1apBsqBou$xVZrz|D+7hnCq{G#? z<6&(Ix8$d<^Pi7sH|l7}c5+p5`V!u>G0TKEoZ#pQGc2{zEjms4GH<>bC7=Dy6BL_( z0^48E%Z%a?fO21_gLBsyr$Smz7|8Drfv()Hp@3&-WaPB|w+YpT74{teu^5N~2JVZ& z{>mmf@hnK@fbDXvdTCRGV3A^ry@;F<^<43%g>W(g{;U8bgaLpXbaRpASgLK`Bi1in z@Dd$03SA}+)h*4Tb`6F*)0|D8lfL$-Z97`-RcjVf0#QtDSBWQgeEXwh3at4iKX>lbzdo zI&t4l7SHeD>5W!#-iAA}6Yk}z^!V^u+&RVJ!K`bEjLI)Q;vu)I5nqFM8Hpjtb~ILk zfC(y8CMy}fh_a^@?&0irV@=hw73}F{3SM0q*`O}fr&-noE(pTOBuIEn(6jfVwv%8t zc?~<3jg;M53Wc<5aGC_sH3CmpIFEu~dMcF`Yglx!aI=lUrb!Oteqi^{Z;Nzt7h}%2 z7d&}fEN1QXnO90QWAl^&v-L&1#YZlyKzd!=#^XKCKR6Lq>Y6P`rVWZd!QbU?>c{KC z?4%cVDmQ?C7DUEZi3<;AzqXfd!pAc2AG}ro$PtWBX^EDB&bKj2}EsS73XnN zr>Yd>9{KaX(KOiFGLR*7k_%(!7bS~U50Be|)jOvhYBczf$~d1Ey?8BGg9s4_Y)}7u zz3u)@+(AItuY^gd4<)_!Y`Vk!O$=WSBy}idp^Joiyl#NiG3& zO=45)y>Q{Xx1*3mw`WmA$m&&v*GgDPUuKfosU27F7R=Rk^R$WBuvJG1x5-b<G00u(AKQxBMlStWvbnW=oMbU!Zz2n2TbT~n_7I=O12D6|=CDfCTu37r(bAN#` zJ`M?o_8LTd@zl@UeA)jt)QZ;&QbIq-Rtz5l@&XA#U}pY^@YB>~ijBBXZo;ke+e=i1 zS{4(M)#>!&ZP3;zjJA11kw)mARi~JmFDx$ZEUe1#d-Df|(FG|9;&2$)I>HiBg^oJa zsy^!S*yOu6Z1_rQoV8brRZ>1Km-e?-w&<{`@)B=LyYm@5;d4UcTr+?a%_32p=!9K} zvS$raccJ0Q7PJiuoWO0uZnE_a!lU#mdgO%Y#cjyB#%O~%sc~xxp5MZS9m_;rLof`8 z0uDE!VM5s~1cJwIC!MoJ{Y^nMoq=)Y48?p%Y1X*Bz0nnMA zr2KODv4Fg1KqfiMZ>9GI(1Hk0+JhQEM$PF-T!OrMa7mUz2PtIMVU98wjFPG9I&(pA zwFj!lj@~hVihlmK5ZBAvoCS)OK*)tFfAQjDD^gqF%y7=iin> zdd>qvJ%o7uInIc{!7H$CDKu0oN62KK(5q`z<$q_UNwMvk1FpU>`F}x+Xo&9o6eF1} zRdvC>CDsdjF6Mw_c^r=1)5J9{;rH!T1U%hjweja|_fHuc8&i0bby`YDDh~16QQ@p( zvb>X=Vd#mL1Io-eC{Gy*&fQs`u2V%gk#HT{MJINI5!nkm)!oWZmo-{-qw=C|+UuEc zPe=_`hF^*QBFs{v`1stQYb~vO7AJWxT@#D%SD1OVnsx7qWfLwERp z1mKs%?u2;8>lZm0ufuYim$G+v_a3R9n%({&dWpUt&^kzRNvz#i916l99#9@#g;Ydi zeg`eds!`&r3+?3l9gb3hC9{uBi-*+vK79Bv>*dwZLlFhNVQ{(;ZjhG&cP?e(_H<_P zk)37DQGjEp%<+*lo5W4WE9x6Q&64(O<*5MdQ+(c&(V+dBI+XwmxbV=%zxt2X`%&N7 zATGw-C_v8u(})>f&z;CaXq}co)nGkt4T%73*ob4f2-!8=@^0WCd%LUvYA-ZoZDiCsx%Lbw^} zxG+)n%SJG<9pL?_3H|B9ml;$q?tbKiO%%*O4d z&{)!9em`Ghko#BZssi4X*?U#+vJVDnqmexi(6;s{w6)JOAja88Eaj~$72xb`#fEHg zbzhTn?VcEp?!>Sn(9#sUOV|Jr3YAKA0sgh%v6gL*P9|=ygl&$8J*%30R)Zijzh88p zhaPa+5QSO{JzHp5*vi6LG7pBq^jL{~_=gY(-69pczgc1ZF(pu&rWs{#aUfeH@19Td z&Kq!C0r2nYcBBz*c(wf>v2b?L9Z>58pFaK0`Toj)RBVJ}o1mqeP>vec7v!iUv8Q$k znBHz8dAg|oSEY)EYEJ=Vc>y>TTycLDCn9>#$khDvM)ZE?_gFJ3ympHaXexhzo7J4Y zahAZ+L$V)#O@>K-Y~Igu5QR^tK$_1*?bW>DA71J(ewu@Mlb`UIDyV(gf`{~lm;fDQ zzz3lkd;YjehDmw0=zvF?d~FLl+U7c(>)+5}QZwp0xlKy|@KJsB&%C(WQxE^DlOvg2 zFJFGBpDf`C9<N}fzRPi`qo&T zHhS`1SvKB~fo^Y8KaX9o;%!0ARmFndG`LSnGuH}}Pi4|UjMcb+@YtMZ=gX~Z7>CVu zWP!l?3P5Y;C+)WIe_o>xK^cFP-$OTc*t`MyFb?@ZMWjVXhj@?=^_SD-zpasJeG6Hl zHwk^;ys8FxtUYej!;NsAWjHljRb7ooV`Y1_%;_%v^jlo2NTjwc8u2{9#R_@ zz-S6GzLs4H-b{eM`jXZRwf+iye6wcIU`7<>vq!x6@na`|qYv}}t`<_ZU%aF^oGoX9 zprs(|w=P5sa-LCVW(DK|)NjTNZ{625KraGt`3i#~hy2x=Ac6x%5b@z;e3%j;6-(iB zm_FH(6F{Ru(>_F_Dh!t69()`NI%I3w$xZEHb$4)ZP=SOI!p|fCB#Gn!>nu+y$)fr5 z9lNx5PG;P?3veG~!RtanYnp=RLy7-MPW>Qn+zuv$>dJ=Qqa*9;Z6nzGgmX$ZF1*n~ z@LSIsjHq1geX<6(S}0WbA1z74)*lXtkX zN|!bZfF!ZflUC1dl%thpXmh!d;|eU72fcrss$c)pDO3WpDAE}z0>bfN*MrTK3r?VD z4Z=^>fTE!kKuQk_3ND%9qnZ_>z)UL7_g8YkYvFGw3VRj1C2a!|r*_<55pddk2~)lF zK3+_SfD7lBeMgeMow&WXpmu2#8lT!|e-@U1w?`bo51Gv&#sQQxekfcAMoI@`vGY`! zjw>8;JEGa&mBn5ASi96nc6T9ewe!v?yG$Lwf`J$i!3{_wcnFY&WB<+J@%|uw{Rs@} z?>;;QaMmb+d@~R&)Gm$t)3hDp>0pW76|UYfyVG{|oud*fCJjV+*)`I$Bl+v}uDqv6 zpiMEiv`hzzLMeQ*)cS@<>!@@-62&oI-#m&YnP*4ecpQdR?vJ(C{x>_Whtc07LwfX~ zR>*X$Kn-fLcwCx|Z#s;auA2D6_S4SiU<5Fl$vW-kpsno~YVNp>fpjS;A;r`V6eR(Q z=@8KOO*O64B=<}>=*!#y_x*ypR!1ZW3vIsVv=47MALW4Ae9t>4g&Da?SOQGVR`*1E zR?u|SD#W>6>e!AxY0bL+K*GHI{cY-fH}7qT2iib};XEWTsKP{q6*Fawo6w77p}qGK z0uLKdnzjm`KAl|4c)IF#K9p_~zX3g#I#cOSb)g%99h*BfF}_H6#|3p9qsuiAXFW@) zJ}Z4a3|<)Jt(BZDKJi32yP$+W!+(S(Qy)LSkEp8yv&L@0fS|%JXONckjj1(n$GtRH zYV|mB`CZ)1PW?VlXeYZ&C4X(Zt>O9Z!J&H4W52jrD;w>5(LcBqMT?TxJ95|l_(_hn4v4S81x5x^;aQ196Ju)GPu4im2J)dmSv`*NBZ7h5T zoAU~kJw7HS2pZ{7E+xd``E31P0c3bfjP7+oXNl;onhM~%^llcQ#P0!5q z&+yI}13vQ#ZKG0cQ&{Hhdmeixgh*l{9`dKX4%&L9_ydEjmQU_Dau9w;+D8sBNkj(% zwga7{q?Mqv?6S@Q>+2OT8pL9=@lxFNfH{~T_w!v;yQ=iNLG zTDLGjp%jA@sG?Q*F)`pzOCD^}00tU)4)=g>T_v89Rhd}k>}0@k{{;nRIWGoW7ekTd z98^X{1u~t)2Kt$0kZ_1BO&7(?MQ{Q#RvnTaoe7oWR=lNH1qs5Dc#cFFFX z!R=Vl&;ka^nJ7OCA`d8mrJU-~O+ORn`;Zh~Yi9nWB2Zinz`G0JYQKhRrL{BOQI-}L z@mMZ8K_@XtvNpOMmwZ*s%$~RaZki((h#|{!Ia}*Ac>a2t;y|R-J2WH{@;Aw8Y8(7V z)YaYudE|Ymf`$;K!vT*ai2bTbi=E_PJ=2%^-Ot;|QY$0&fF*W0ji2=6oZSHrBdx`# z#!R>JnmR)hxZZkT4_DIE)NByRdo;IFo3`7q45uW&jGZtw%`u==52yWOldtz2AWAp3F^T1CAW@#P%n;N+ z?mvH%?x?usm&(+^z=B?w>s&(0*pTeY;MlGi9ef+_oE`A^ftn|_f<)541Y40kW9Rha z{WY-p1&NF)68sIgb?s(B@PKnjetL&8m|xx5lV#8NK>h{s$&>dWpX`I=%CG;YYRx#` zcx|Ilf3pJQL;Uzj==(*?QFpzacNcZrFf_jjvp=6Z1W_&*h#KPh;N`TL17a5Kxn$@q zM&9rZH{ljfx92+w2~G-R9J*RT9Fa*dC|8(Ees{q&?gn584IXK$MeKCkxIMn7p|_r^l>(x%%4mTT}P|uFhvf;)bLHnS(;K;t|Qan|LWd zC;r+`;k$13gv<$l3AwDmk>D-PSC&P4}H89VU~O9c;Za4u%FGvEDp#!k7Fu9lyF1)}wj&}N1lZ>IywqsF*)sH0Belo&G0J}I%y&UePJ zF&tHz8F;4){MD3HQ&glF_kB~t*)F2hR zlQ|q3Y<8^8GrY775fHDoj?q&FFFKh!n)jcdy8TJ9!Xd)MX`D9 zfJk1H>97OPg_&K^QYv&etEN z+7;F)zCjMz*R-e#YWH@M$02HTv_Ug#pKt6sJ<{9T`vj^RKbVz=Vv{@8IS>JBDeipW z2C?w{@MkVgF|{z=w}_jEeIWo@X25JPA-0F%RAGk}naw?t==Okzrje4eweP^CzmxBl z*lBRLY{!7e;@J;Z+pyHr@b~4VoS&=^9O7SpX?cAWf3jA;NKMZ#xg7a!QI$7{g>g z9_t=O0Lu3(YQ1PHkMBI|f1>aV7!88wz6SXWpjB~tk>4*vf~pU*6;(!=vZk0`Al?KA zDu$@gR+~=>eUP4`+cFAL{!}wx22SCu=Q z9@a0OjFbNzzgw-t0S2r>Iyl30ODt!T8|I=DIF7OVeedb*=S9(=t4Ccnpzcq^&4XbW zvdirDT|Q&;ybd%lAAn(}U?y%9Wr3oDY_L@jk0DhL%}f6`BDY+9e=S2(VcJ}Zd*@k$ zebaS0-|Donv3cE)IRrkr{R2Xeolb*r+p~G+Ih=X_r%L~sAR)vW$^qY{E>63COPTA~ zM)2UQy3cU0}H55h241`i0#mQqnXuw3?hEx>h z0TBYfl72>=iv_cD{l4@2pOt|Gr5E4a9kw}lnza#}Bn{(Wv*Nh;1M*)yu!n)x*Ic*6 ztT1QWap9#{_2)Wfr+cx_6fVzDH5}qi4MMl37}Mi_UUT@LK{H zNqwOU_II``wv31;F(3uZpD-q#-;}WR4)Fn}AubC!(oQY~^gl2x$$vwgCRG-ULC7l2 zXMw$VfaTcga>lc<$4p~xUgXzc*H8@vmDsMAlpMz&pjZJ@i?KhAW06YB@p%&?$z{NC zVzMG9j+s5~Cu^i`FsctH*CwUQ1BSu>s&g#O$ZjQB#{*oKL0~opAp#y8;y9erZWv${ z_&@4r#N03~&fz|3;+Sd*`Uo@PLJr;MOSoWLmkn0shVm){#TE?fxIhVH-Cg94P+Hg3YRTl(8|c0}AcRGx$J^8@KKlE#+Z z!R7_?ctK2^OZnM#=5L_$}X_pJq5#>2haa1*-BJ+0Lu`48X~*dW z+^uu+Hafif6_nRNi2yLQEzp%gl;!N$QupD>10fI}(4STRWjfZcA4HF{WCaU5yDd@N zy05V@hxf15IAOL{;44LZG|Rl04Ui)Oivc7rh^Gl3#|C!t&b>1I1<7s$_%)!rG9b1f z=}-qr$AZ;m24@y`_v8`rKJ zv5y-X1UA10=ZW>pe^WWp#HUh+E|srWaQ~71v=r z!?1@NgTml&OpgIW=)u=P+OP2%=UuY&nX+1$w|sV|VLnK%ZI6**XOKDXg|JIG(r-c;1C6G^<1S&}{U#RQgQ1JBh6 z7UkpIx{#;n5N}!VH@z?`-5fi{KP!AA)u4!jbM_JOi2&&EnZIH%3f_vgvp3~~MPxg$ zZ<_Ax8p2)83H&83hP8k#O(OM^Ec<@Lvvzjc(dkQP45q%;<**fnK{&r z*SJSmJav-0CxX9%%c1{F>*=@(y=MwNEqzQn4`VJL!cx9_RSkgzC-+BpPDAUice!#6 z2=bmnc?{<=CvbXc-6D(-)TCkM+iAh6I6ZnJuktn)D}t3UiFN#J;}w3pA@C)Iw%36Q z4;BX^L-&~-@lQ@41!Uh)U*FOqxXt>=*_s*+B>7?(e+d3p<<44-uI@Qt`8=POG((M4 zl!&n+9N3{TrIy;laZlbZ0mlfc;!Q}{MWLdt)r~%w{V>O34$|0WYOUV zlt$T%74It0aa!QL=A6%k31EkN;AV7B_Ri{a#8H z$OPOfq2CZEBvpXgXf$aX`w{;{0+l{oHOm_sBHAEev0_J_LYuJp+DIYnOeVvE&n!F* z&F>{@Z!Y_2@!=kvOlXuyiFKU_Y$87-QHy`-NS`e>;FgZ=$8d*N&PFH+&jhEtt9-wzxAk_%37_WEzI(msD_bH2mKYZR7JZ=k3c z6O)I?K&HI_2NhKV7aKR?J_ZD0vtA}r0_~irJjkN`j}VL}^kC{Ca;U571Nq1x@1x|a z*NXZj?nYI{X80IOg*0F*BFZ+#Tq1?vBNt+R3PC};x$B|buBg|ZD)lwC$Du+-Ex0JS zbws@n5);&Nx@-I$pZ@k?v}f+{tUbP0?@k;uIOxPb#QJPguM$pX>(#fJw&^Cjq?p3V z&4uvuIN~pa(MH0}$m_XFp->;4Hz4Y?kU-W3;kho5P-3SB{-QjP0)Hyc?PpjhacB*FSTOx zw6MR<8Z2141f{FH6W@hp7QZM9fVj$Z_6Xq@V4CxCl2dL=37~bpq+GJ+O)xdMtZl@&gfY{FwZHl6(Q zoW4&N4r5IRT=(1$h#|cydi3aP4V##r!FzqbG5qBLFh; zpwW7WBL?D!yY%)v;wdkge=5NNP*}q=$F4F$(ZvxX)lJSd-G8tx3fH>>sU!67;NRZ^ z|L7!#5c-*s0F>O5P59G6BlM5g$qQd(0aPF4$0&JoN87{rE>9}9#JdYLyMRom;N9cx z3sS5aLN!mCd^?#aIQ2+s|GhYj=cAaNDW-0)3XGXOJ)i4s#O^U8MRkhL1q}CH#b{{N z-t1BbG|5$B3_(v?mm1Vceyh%-;2lno*wEZm-+@>nZ?G37RtAEAN3DdsQSw&D{?!Xe zIsfoNc)!yFgt^|6qp}dne3*AJxqtrJtn;t4mV>twPKzItQIcVa zJR1l|5{cvwNEs7m>lul3h-WPmvLkNrpDOixKc$BvvfW;^!0ZEXBS##_iXidGPS+`J zj8K?gN|sKaGPjAoEq}|tW9KWiN_knUf{+#39oY9_E{ioB=QEM+4{Me>ZajVF}! zbF5u|(F!^p!k6mRJ-?q0sh{Zf2rvHqP;=+@h_cgaxN~s-OGq4w-p9c~wgZ3&kKo;$ zY42E1+_DEmf_*aHFU>E81e{ZVe zvmfExy>mF$CX4U{OhH`jWPtdx)h>0CD5Ef*4i&iI;p6@HT=sLzUa-i29&7=*^FLD~5xr~wFGTkfEUQKdtGmtsSD`O_ZRRkm*VMg)+H&cx1do&L}@+K zZExQiXmgJaoq3it2J*wAf`Zm}*_s*IVVf&Yp<98&1&z-I{HSC*@U-;bWl)JJJ&r>q zN+#TziGkUF&uISM!qFs`~uH8h9_VSa*V4B|$akY}Z(IdHrZR5lNwiLf32KN`)x zgmmeQcX{4!z+E^2!ON(az5BaYJ}Dahn0D{zx3arU{wcYjB7>eAtMV2P$>j^V!ijV7?cI}t z0|lf|W+Y)}#03tTX4|FU|2~VtnEqpNm9{5vJUG^J@_tn{zzl8~ADL~r1qJ7?4#}J- zu*3Fn6fS8Vkjb5(^MnriXk2<}1;v&~5`^)7JK?7e{?T^yLn%ZQm`fK#3L`F4oxsCj zhVd+o2@y2H+`@=9YzI*VN~!`W0CY89fN=Y3W{m&_9ITxOJpX>B_=B{C|Ag~321S-| zxUmKQ%U+}r0_q>Ax@Yru?XVYfybxz2f*`=dG5$l{5=Bn=c8N!hoe}%z^S5`c19A~S z&d042DTkR6ZSd*?MGPvYzZ|-|Wd}}>kuoUDSerTFM`$9AaMG3B^+6jurjH;ukLm3H zJGvBRQx#B_?6G=}-|FR^3mXRr&dKnL&EX=7vgL1RzDnTOg!Z5_?iI+7v3`T^Dz$#%4 z;try(Feg*od?4=B7<;awQ#mD&0&&wh2KnYL>~M&ld)|Tzt&n}k9#m`mz^mxMBz{ItJx63U<%Bt7C>;ckZ+l6Wo?bHSsftl=bqP5KUB8Wy#LsPqA4Wk8jf8r<-n?#54A z^G3%3;c1i}0iMr!kd?&hmA;`RSQ){OfdkkPtP=XNkEC&yLk$u~qJXTOaXiz9fL<6z z`&+#72=`EG01Ye-KxmrNUj7^@4!Gc3!ZMq>xg59a;-u8S>KzXAc3m&5~^z z_jks-qgh4}LLzJRicRD2VFBdm@#D>cL@>tS>LGyUw)#zU9j0&&YZk0Y;9_rB_a`E_yz^NxyT)#n6gI zL`E_oiJl6Wr?eE)I8IM}xwN&jUZb$Ah?~JD6VV5p1*)_HQkH26Q+KBNTpW1USjUG) zkwzTuz#`i{*S|JaHlZF%s!wVkuXmzpF?zD9URxX83Z*-m$Bu1i_E2xXOBiDffk8_m zJoL%U3Yk}lnl{qAQ9}uKg8Q#5d3CUP|C`}Hn5EGlw0+^F zEj|_Qf)#oef>nD$_2@HD&#;USx5Ai+6*;-TAU4?<(fbd;prKzv_kSt{t%Gd`KH=qm zO-xjUfs8{W{9J%;=*gMq?*Eh^yMvg*#x+J2;k(Je&jNHAcv&?+o|XO|%^_z-=>-rh zI2k#kb3fwc&l<==pWaVQxeRIx&jO-@URMeLzshWzBSTr$ogepmE$HBtr!3 z!PvZyFDaI&yoW`2RigAB+oLVxB#Jh4Mz=J9=scdZ)(1E~;vqeE+q-u5 zyK+v#vO~0~*|SH^+9F;T?sbSnY~GG0g>?V}?$F^pn#loqdsx(79Z@zJr8+_RTxXX{ zs!t}kO~K*#-eF(&gOrn;E4yFS=6*pXu-6AJr2*6g&w(faf&z`6HJ+#Dfa7=@GGj?R zl+W}w;6IFnCxlBqOff~Rk~7qXYw=aa(%0j>r(c6IWTC?W1--~Otb5s6T1uBQ-L?Dj z^*!Xsfe$FRhE}h!FYXO}<93kWK`=m-LD6x7lnrJv34^Y2QByp&|D5q!3T?xsDXZyA zoAvNB%{m;qAhRW>lP?h{?JiW!=TR& zA$A7(^fTrStEs2AzC3Vwwh|0Nrw63P1GJ4Ug z58*POr2I$6ppBiq{GUEyFW12Bn`W?|;Zo*5hn$2dC}q`+xWE9S;}yfz^8Zm8@(m;3 zlqw|Q{6FOB&fj(qZU)&G_ZkM&d$*y_#LxJe50cSKSTykFGmL<`)CqY~MXWD;?x5S3Z=1+|vyoOGU)AXg^Waa|j;lq* z5I%sgZ2$te#etuwFdODu6R|SS?!-tk!sY)si9=sq>nbi9G<-v63D77}G$_(1_`~%7 zpv#mPc2K#hh1>6r-bt~)^WUgz*y);#b56ikOEGOE!+Tts5|}^`M8cVY@NgD}`v&HI z_K-ThS_YvbYLUW!WL$;7hUx>1=a10GAfsia`EIDJM|08}cy&MY)h!iTx+F@;$UqR| zh`EI$T7vao9r%Plu9ONa$9n__-qL)W64=Q5qV`~o26%QCfShc9gjm+Xhkk|jXYxhG z9u0dGphPPG$=mE%xCxEqLiO5@BjxpE(5hHRto~{w7`a)z#M+IZYddD)z;ExMmUh;xK-ZYmiz>@ z$baS<@rk;TZsKy;p1A-98`!=oz%kngAE64l4SEbCgjhvvEq;%(pw#)>n1 zyhGr|>=tFC;E%Q}5q=uuC)){^1KizdXQ4kOGuFQQ%H_*vAYkn9Td`f}ml<W-V4eX@mh0nDPX|9mBq+F?Qk<_t^Rf%CVryph?XhwoIPUn}9lqZ?QBbyWwp zqV0M27o6w(pbU~sKyO+R7ie%`s41TB0RK}jOb}!hnRLFHbM~P6sYi)gv0>m{0=5?I zdO>OgEqGi5MJF)-Rf&LWsU3jdlXauo6AbBZ%1A1j-^J+>k-U;QSQ^EXK*dgeaPPN# zs_B!I2(jorx){z6P?V*;q!=sAodq-w<(bVI9_mAjUwcqRMl#)1Yz=%(Vb`hE zf!H+uT&dXlxI!-+f_AGQh;R z4f1v%2wr+y%ujk0!9-Zr!qi=j4}g@iPk8N#y0A-d78gp@8iz?t@7q)v@g`xyI%`vx zg`fZM(-c6;pMi`5FtG-Y0hdL_=0CX@V8&5U0^XS?K<1l-2@X;P`r9%(p zu2h{D=oc|Pz|wRfZFqjuKw1~asBhM%&TP-CgsHQSUx5a`I&I@4VL1u>;0)h=MCw-; zA$m&hyg8H|QLpQ5B|QPzA#t;^x;hY=T6ynNs8^)w&h(==!bWk`Z0{cz62)D*!1m$~FobgumP<&B@8D9A7&iVU?pV3# zjO{@0B%88`*3XBLDtzgc6OS`~0wP+27X$HE-3oK#bxS$VDWQYv5Q>rv({aEqRH><- z3_fF^)L|;U2ihQpa?0MrmiCX{&|_|p_B1K5ktj5-4>|FQ@*-n9sB5FnF9yt=-aS}o zJ4%lx=7MjbU6txDn)PwpT^sZ3Ix=#bLwj8z8(eQc>z2^bsmCB0?yr&q`38Qn z8(taa@NP>NZF^!CP^|P^h$#Rl)>TY++RBDZu*Ujib28qjTe|a0@9eNDCq}~aX43Ys z63Qyf65&*O5|kFoTe>bArVy-=0l(XUc6949YC6k&^gkKMM6EE3*Uavy8#mR`;~l)q zwu*dl@Du=b=hm+85AwOq?CeD8rRa`PqRDLqRsC>t$~Y7xpsB*I?Ubo zyT}Uoyw~Zuc#0Djy3M}bVD?8IO#EO=>Vuy8JRQMb7!imidL z@_k&q>cJeu$tlNrF>2FsH?Za;5Ud=9R-d02l zPW`#mHHJK?L1Xua{sYP}CSvLiPDVT9TMZv$+at=pwy0b;eJdW~88^bsiAiF0JA8}Z z_~lv9Wtu}sJ^ z!%Wsh)P%&nq==Fw`<|2-MV4HXLaFSeg-e#QrFhTKy_P%m*5B{@^BIPZ^PT6M=Q;a% z&LfeuSVUYW8yMMvXu92|VoasQmuRO&wD)wuM&LoCBM@#$5pcJPrVm6Fa6VRY94`Wm zmt0n1fp!8HTe3mmVhh~WkAt8T$t^C?H2n*jHsX)zz}1?~Eo7{aOtF5;oOFzJ)up>I z(M!Uf_qJP=hrmFarr!fkH;V*OBgAikIZdIWL_iPvEWedZY4C{S|YY}1QZv|TF&Dh{jSUhluRgupUDT@+2>d9QD zr#nU5ff`J0Wp0NH1Re7Lw@i)|S$jL^G`vh+ybexd`>!6-zIV$@eN+J)mawv8{4UXS zkBfyYHKYUrcuWB&hheaNmZp-#@R)e}A&3xe0xqcKo=olGg_sO)ghetJ-l2he%un}E zf}3%f{ya>{_vc7feq!`7A%|j1IYs`z%26=CTtbs}N^ytpXwVfCs~g2#do)BGM(eV> zy2HCb_=7PD;Il~hswhxQ8I}l5ff!AhmxF9Q@L*Hg3V)GJvU{AB>qHY+BT)g~HdEj> zSXbx|+G1j(9bTIGRw?R;j65;4`k5mUlO1-zv*R7;w>U3GI)mZ|SGo{=ZRIC{2O$|k z!((E(tttcrkf@nwHOiaGa@MopH#RiXFpt%PYUX)hA$C4JZlTOO3$@?!D|DYvMpF~_tqBA*;6x}e)(!`8LQsOkJ%q1PP)$O7Hv{l| z?go>mxJv!Q31T5I^Irw5{6ymG4LTc$HEC>KtXWHc{(W7s=aps5@9z84M(T=%mh zBV|GI;_ANMz@-Nua8mJHf9Oc+5(uw|dD2ze4vxPYrJ0Z)1j}fk`Osqb>-oCo(@cym zKMfATxeLxoZfm5Q({SD#3&o%&$#YrB{%-NH^^ zobBz?{@Ib{OnV{+*r2j5ZO<>K>lwsgc@v?-5K2Jrbeh?gK1EP z#X*B=x?dj#KLGbbr{D40Cr}Kcdlw9V0Y>eS!^*e7zU<^(r2XXg%bkEr!%i?qJ{!d> zG6N2nH5-ZD1IwMdo>ashgwhdVcpQYaDU-W0A|hQqF1I2pwKNR&Yt81`4R{k_=2wz$ z8|C=|<((*o!rIIBPLe@yAsJk<1Y=ovvVowJ9y^Y;e<@ltT27aR&k2I4A#zA2GB?3y z-iWG5m&N!k`e9mUJU;IMYGR=B9=dYoPIe>q<^$A6JUup8jy*nEP?rur-#7^-=|*|s z7UFS{AeU0h{SlFBP^ltYFUYIlEz7w1GuJIn0`D7-WaUZx(>Tt3`6w_4U7WC203Hxn za8<()7<15$ve$$9lXe9TgRd#|Rn-#g1a2~9GI#+*C*p%1g7w_95X7Lqzz6mlz^cLS zfwq+y>6Kys1`Xgtwd@)M`jx-Hd{5vn0Vg&n8n`T)Lo(6Ht&zEOP&vVl$Jn1k{wkTK z&FO&*)tuU#^W8GHar8$lJs>marjd8VNF|o)N^)nYX&<4o!Xl-&iNFJs^h^@yX`*1@ z7U+IHkg*^^872yQ`PmD4*1Wd07}CF_DlkW`$pc$T0-1+Qfb0YGg1cX5(J$< zED*36fj@BaR+Pp8N(guq&ND)TpF2%25D%SW?O%&Jfz_-jpM@1Nnd&EdIA+>G9|%E) zdWUt3wGrWF#uSt+Kr7q8-}s?d-!8=!f_IhRMf^838Y_);bn-+V!3KIFF;W3uo^%Am zo#8UP!qqJJQP_{hnHq z&4-EE6Xv~MRdiXl3&EXE!0YqO}p@24LPQ!uD={y+(Nm77F&)+lW=Z^F@^F^UovlbJ72n7aNxXFY$ z-i%&@9o;IqqHdiP_DUw<6FkD%E+-R2cbYBCVl)=ImCywfFH3l zsfvuHE!-hmGySRH+r}#~tpc14K1_+)T>Z04wqGWE^Mq?`s-)`9bv5Yllh?g5I-q*5 z%CyWKl@vh%-@J#N~5O?KPCp1iS8UQCC0pF-ZXIV>Nx*&m&fBBCbD&=H7y$N2{< zbyRAweD0!I#8S1IT|@(oEQ6EW?ZP^S(YhydpK>l06($^S(>j*~#&%`T#wE-noVQhK zKb|+pJMX|D@jgJ2Ge+*NzDjv@}#Otok2QeO@ zm(lE@hodJirN&d=t~je#<&DtqWKxuTU+R@|s?9nY6Fv~*%aS9uSKs?Z=tz47qH!QH z_=f*d_7J0g_0AK@6Nsv8$`gf$+58{SJt-<(O+BHp@7H5 zdyFVc4aA<|{WM`pQg@eWth8K!?#jrC^=%uO3(9#_ZPLytUnH?9;me>T6TJz6fBC=t z#KhZs)XT8oCTW%!gy+4!q_>@~tXFg?-Cn<&t9r+%d4#(~JYnpC1KjJylZ*Dad1VF> zRQzGTTT_)c>5s|jm@^762pyriT0n=F&R%8@Y_hO1OBZr+W1v1X)01-9>YNxorbztR zm5Aw7L(ckqI&j8kTNAD*J!=p89CpZJG#>8%T*A)0GF{@K4|^ADkj@vCkzI?>bq0XveM{tm907KU)?&pRV?|&2g zsvI2RQ8I>kjctV9y4OX%J*ugGY~P2f;f^2-eUq0-k;H*Uwf0gIiaSQ{pIV}a@MdM{ zG+8^Y)l=>Bd)I%WB|YHPiqp!wCy_WwRKKDrJVPV9Ib-J;QB{8}-`q@N_4wwTMwYie z?9FGCrrs>+DG0T5bDVQ{a&p%v5ywEg!L%lrp|;%aG>!vW{obb+TV{AknkIf<^e%c} zmfBPhxX)mNX*#0O|8JS=wK3Jw9MvtucqXl41+j={|IoC@#m>qy@eEu%*) z_iG-BrP*pFMugt=?wXP6YUzRN>J9|dadDl>!3aM;Z5q6~FancP(P#2OCB;4@M@ZK_ zQK7B=+(*7=T+@rp4nfBv;yB5*wsY;Nh$-WdXPaRX=LoW{v=&P(acOvlN|K!9S{hbJe#*)}yuxZX>WzV3Vr$A`y>n%Ox#<)^&t1;`?1JBu*RsQ=Z3TQd z!8UjU>hJCkcKV-n_F_NUqxFOs^0wRD9~iuPxaaX}F<4(%SlFI_#w(J&}s5zFt8{LWT(#4~PzVb}DF_k~oaixbJKOPs#!eRr|(`3Du&oqv@0 zx+9{qBekzVqJ6>(yInAY#(8RTcYSKktE!lWFGm=tXN=fTyA`HBEBXxYK4=T)67n2l z#3w0pQP|L!rJib`G_A_94u!pp3L8A+RFF8t^YB0g&E&JgbAOr`Pz-TP;;!0e@hwI! z^bS=v>YR1uS#b@xmVT$2`#sz2lhFX~Yet!A_%j9iA7`^;c5y7Rh9c1yVp~lQ&o5^7 zf|#2=E@S!dZf-UeY{|sa(qegk5y9Pl3#u*0ZhgKvCH}#$#&}Bk{^+4cX;(VDHMx~a zc0A#pdUZU?NoH(z+VH^=|I4pWFH{?Wu(IDrQ)J|l(E4DJ9o0)y4I1w+bZdjfb+aM9 zv(-U)W0Bri2g^V-5@S!SRc6MU0d5P}bP50%1~n0^S#EefgYMqJ7wL(SmLh)Nn(#gS z%xPJWr~rI2%Z6kSBucY3XeEvjz=u0x-BI48KKia5!j%(zX1Emuv2G$<1Q&h~hrJk2 zcNZjP?T0KWL}G+*DDZiO;;FYHUICf5BIHOR#O2jrF@plIqJ$t~L$&_g*tLdOD}=4N zAz3Jt+eCNw0fHWoiEo_*-pz4rl4p&%n!nYIk)#>Vs*U48T5xJ>3k}BgBid>nNzzKp z7`=CbDTcfvah9?1q1(NIcE}NfMf!d(^tE3|7X6F%_-PP=epR@=8)=Y@&<31|V<7M@ z!boq(@Y~p7O1wHP01G+*UzMrCncRSM;5jONaqcvyPI)iAmGf; za0;YwZ+OL6m_UO-tj?QpuQY#-+eZplP){g)1=_-S&{S>4i5dJHr$`E?`r`3H9Kg+j zHf}Rc)A;AO{iJX{<75-XKouIGvD%EI0yBc2m7NkP9OO`483u5)|G~{z{v4-F3ipZp zV3;S+>;+(Vn=4!y{O34X8B)9-Hx2dug}iE?zezsj{43;DQ~gbHu*!8$ee4WJo(%C~iZmgu&I#=0TBkkYH)H*{~nxpVd5hjJ3Lj?aSb zhWOe%#Jh-EBX}WQ+#RLey&PSUvQlVo*R?_7I>GtRy(P(@lOVo8i?ZPpPNI?jfiN@iQ}Lt8AmVGY7-8lA5kqcWjP+6S)+N<-IXVT%R~yMK3#?lQ zyIYqn*8!!1Yk*CQm|K%?T|rwuFn)Z(hV%PYgzdBIN6(_|KBg*5sQf h+8dKsi)~2$ecEkiOhxQFAu;gfLsl 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 literal 0 HcmV?d00001 diff --git a/docs/en/images/deployment-single-instance.png b/docs/en/images/deployment-single-instance.png new file mode 100644 index 0000000000000000000000000000000000000000..8c3f8719b2432dcffb0d3eed2c5005a69ac24027 GIT binary patch literal 30663 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 literal 0 HcmV?d00001 From c929fd7289efb53585971950ac061832da8b3f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 30 Apr 2022 14:56:59 +0300 Subject: [PATCH 41/60] Minor fixes. --- docs/en/Deployment/Clustered-Environment.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md index 4247fc022f..8bb9f50c53 100644 --- a/docs/en/Deployment/Clustered-Environment.md +++ b/docs/en/Deployment/Clustered-Environment.md @@ -18,7 +18,7 @@ Browsers and other client applications can directly make HTTP requests to your a ### Clustered Deployment -**Clustered deployment** is the way of running **multiple instances** of your application **concurrently** in a single or multiple servers. In this way, different instances can serve to different users or requests and you can scale by adding new servers to the system. The following figure shows a typical implementation of clustering using a **load balancer** service: +**Clustered deployment** is the way of running **multiple instances** of your application **concurrently** in a single or multiple servers. In this way, different instances can serve to different requests and you can scale by adding new servers to the system. The following figure shows a typical implementation of clustering using a **load balancer**: ![deployment-clustered](../images/deployment-clustered.png) @@ -35,7 +35,7 @@ Once multiple instances of your application runs in parallel, you should careful * Any **state (data) stored in memory** of your application will become a problem when you have multiple instances. A state stored in memory of an application instance may not be available in the next request since the next request will be handled by a different application instance. While there are some solutions (like sticky sessions) to overcome this problem user-basis, it is a **best practice to design your application as stateless** if you want to run it in a cluster, container or/and cloud. * **In-memory caching** is a kind of in-memory state and should not be used in a clustered application. You should use **distributed caching** instead. * You shouldn't store data in the **local file system** that should be available to all instances of your application. Difference application instance may run in different containers or servers and they may not be able to access to the same file system. You can use a **cloud or external storage provider** as a solution. -* If you have **background workers** or **job queue managers**, you should be careful since multiple instances may try to execute the same job or perform the same work. As a result, you may have the same work done multiple times or you may get a lot of errors while trying to access and change the same resource concurrently. +* If you have **background workers** or **job queue managers**, you should be careful since multiple instances may try to execute the same job or perform the same work concurrently. As a result, you may have the same work done multiple times or you may get a lot of errors while trying to access and change the same resources. You may have more problems with clustered deployment, but these are the most common ones. ABP has been designed to be compatible with clustered deployment scenario. The following sections explains what you should do when you are deploying your ABP based application to a clustered environment. From 9ea480f3a0b848bea4711a569a7ae7c8f4a36917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 30 Apr 2022 15:25:21 +0300 Subject: [PATCH 42/60] Added section: Switching to a Distributed Cache --- docs/en/Deployment/Clustered-Environment.md | 12 +++++++++++- docs/en/Redis-Cache.md | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md index 8bb9f50c53..247d90acb0 100644 --- a/docs/en/Deployment/Clustered-Environment.md +++ b/docs/en/Deployment/Clustered-Environment.md @@ -41,7 +41,17 @@ You may have more problems with clustered deployment, but these are the most com ## Switching to a Distributed Cache -TODO +ASP.NET Core provides different kind of caching features. [In-memory cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory) stores your objects in the memory of the local server and only available to the application that stored the object. Non-sticky sessions in a clustered environment should use the [distributed caching](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) except some specific scenarios (for example, you can cache a local CSS file into memory. It is a read-only data and it is the same in all application instances. You can cache it in memory for performance reasons without any problem). + +[ABP's Distributed Cache](Caching.md) extends [ASP.NET Core's distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) infrastructure. It works in-memory by default. You should configure an actual distributed cache provider when you want to deploy your application to a clustered environment. + +> You should configure the cache provider for a clustered deployment, even if your application doesn't directly use `IDistributedCache`. Because ABP Framework and pre-built [application modules](../Modules/Index.md) are using the distributed cache. + +ASP.NET Core's provides multiple integrations to use as your distributed cache provider, like [Redis](https://redis.io/) and [NCache](https://www.alachisoft.com/ncache/). You can follow [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to learn how to use them in your applications. + +If you decided to use Redis as your distributed cache provider, **follow [ABP's Redis Cache Integration document](../Redis-Cache.md)** for the steps you need to follow to install it into your application and setup your Redis configuration. + +> Based on your preferences while creating a new ABP solution, Redis cache might be pre-installed in your solution. For example, if you have selected the *Tiered* option with the MVC UI, Redis cache comes as pre-installed. Because, in this case, you have two applications in your solution and they should use the same cache source to be consistent. ## Using a Proper BLOB Storage Provider diff --git a/docs/en/Redis-Cache.md b/docs/en/Redis-Cache.md index 3e11584bce..9fe8efdb27 100644 --- a/docs/en/Redis-Cache.md +++ b/docs/en/Redis-Cache.md @@ -21,7 +21,7 @@ abp add-package Volo.Abp.Caching.StackExchangeRedis ## Configuration -Volo.Abp.Caching.StackExchangeRedis package automatically gets the redis [configuration](Configuration.md) from the `IConfiguration`. So, for example, you can set your configuration inside the `appsettings.json`: +Volo.Abp.Caching.StackExchangeRedis package automatically gets the Redis [configuration](Configuration.md) from the `IConfiguration`. So, for example, you can set your configuration inside the `appsettings.json`: ````js "Redis": { From 885ed933929241e91355c6818fd490b4bb69b9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 30 Apr 2022 16:00:57 +0300 Subject: [PATCH 43/60] Added section: Using a Proper BLOB Storage Provider --- docs/en/Deployment/Clustered-Environment.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md index 247d90acb0..17fbb9a564 100644 --- a/docs/en/Deployment/Clustered-Environment.md +++ b/docs/en/Deployment/Clustered-Environment.md @@ -55,7 +55,13 @@ If you decided to use Redis as your distributed cache provider, **follow [ABP's ## Using a Proper BLOB Storage Provider -TODO +If you have used ABP's [BLOB Storing](../Blob-Storing.md) feature with the [File System provider](../Blob-Storing-File-System.md), you should use another provider in your clustered environment since the File System provider uses application's local file system. + +The [Database BLOB provider](../Blob-Storing-Database) is the easiest way since it uses your application's main database (or another database if you configure) to store BLOBs. However, you should remember that BLOBs are large objects and may quickly increase your database's size. + +> [ABP Commercial](https://commercial.abp.io/) startup solution templates come with the database BLOB provider as pre-installed, and stores BLOBs in the application's database. + +Check the [BLOB Storing](../Blob-Storing.md) document to see all available BLOG storage providers. ## Configuring Background Jobs From 9c6978737769b2cdf2a033591bb1b8a0c9ccbbeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 30 Apr 2022 17:01:28 +0300 Subject: [PATCH 44/60] Added new sections to Clustered-Environment.md document --- docs/en/Background-Jobs.md | 10 ++++++++-- docs/en/Deployment/Clustered-Environment.md | 10 ++++++++-- docs/en/Distributed-Locking.md | 4 +--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/en/Background-Jobs.md b/docs/en/Background-Jobs.md index 2e0b5d3f65..b5442ddec2 100644 --- a/docs/en/Background-Jobs.md +++ b/docs/en/Background-Jobs.md @@ -189,12 +189,18 @@ public class MyModule : AbpModule ### Data Store -The default background job manager needs a data store to save and read jobs. It defines `IBackgroundJobStore` as an abstraction. So, you can replace the implementation if you want. +The default background job manager needs a data store to save and read jobs. It defines `IBackgroundJobStore` as an abstraction to store the jobs. -Background Jobs module implements `IBackgroundJobStore` using various data access providers. See its own [documentation](Modules/Background-Jobs.md). +Background Jobs module implements `IBackgroundJobStore` using various data access providers. See its own [documentation](Modules/Background-Jobs.md). If you don't want to use this module, you should implement the `IBackgroundJobStore` interface yourself. > Background Jobs module is already installed to the startup templates by default and it works based on your ORM/data access choice. +### Clustered Deployment + +The default background job manager is compatible with [clustered environments](Deployment/Clustered-Environment.md) (where multiple instances of your application run concurrently). It uses a [distributed lock](Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance in a time. + +However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, please follow the [distributed lock](Distributed-Locking.md) document to configure a provider for your application, if it is not already configured. + ## Integrations Background job system is extensible and you can change the default background job manager with your own implementation or on of the pre-built integrations. diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md index 17fbb9a564..8fd07796f4 100644 --- a/docs/en/Deployment/Clustered-Environment.md +++ b/docs/en/Deployment/Clustered-Environment.md @@ -65,7 +65,11 @@ Check the [BLOB Storing](../Blob-Storing.md) document to see all available BLOG ## Configuring Background Jobs -TODO +ABP's [background job system](../Background-Jobs.md) is used to queue tasks to be executed in background. Background job queue is persistent and a queued task is guaranteed to be executed (it is re-tried if it fails). + +ABP's default background job manager is compatible with clustered environments. It uses a [distributed lock](../Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance in a time. See the *Configuring a Distributed Lock Provider* section below to learn how to configure a distributed lock provider for your application, so the default background job manager properly works in a clustered environment. + +> If you are using an external background job integration (e.g. [Hangfire](../Background-Workers-Hangfire.md) or [Quartz](../Background-Workers-Quartz.md)) instead of the default background job manager, then please refer your provider's documentation to learn how it should be configured for a clustered environment. ## Implementing Background Workers @@ -73,4 +77,6 @@ TODO ## Configuring a Distributed Lock Provider -TODO \ No newline at end of file +ABP provides a distributed locking abstraction with an implementation made with the [DistributedLock](https://github.com/madelson/DistributedLock) library. A distributed lock is used to control concurrent access to a shared resource by multiple applications to prevent corruption of the resource because of concurrent writes. ABP Framework and some pre-built [application modules](../Modules/Index.md) are using the distributed locking for several reasons. + +However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, please follow the [distributed lock](../Distributed-Locking.md) document to configure a provider for your application, if it is not already configured. \ No newline at end of file diff --git a/docs/en/Distributed-Locking.md b/docs/en/Distributed-Locking.md index d57fd45c83..d724bfec68 100644 --- a/docs/en/Distributed-Locking.md +++ b/docs/en/Distributed-Locking.md @@ -1,7 +1,5 @@ # Distributed Locking -Distributed locking is a technique to manage many applications that try to access the same resource. -The main purpose is to allow only one of many applications to access the same resource at the same time. -Otherwise, accessing the same object from various applications may corrupt the value of resources. +Distributed locking is a technique to manage many applications that try to access the same resource. The main purpose is to allow only one of many applications to access the same resource at the same time. Otherwise, accessing the same object from various applications may corrupt the value of resources. > ABP's current distributed locking implementation is based on the [DistributedLock](https://github.com/madelson/DistributedLock) library. From 02cd3a2eeee8374e7f91332da185920537c745a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 30 Apr 2022 17:06:00 +0300 Subject: [PATCH 45/60] Update Clustered-Environment.md --- docs/en/Deployment/Clustered-Environment.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md index 8fd07796f4..11db6c7e0f 100644 --- a/docs/en/Deployment/Clustered-Environment.md +++ b/docs/en/Deployment/Clustered-Environment.md @@ -3,6 +3,8 @@ This document introduces the topics that you should care when you are deploying your application to a clustered environment where **multiple instances of your application runs concurrently**, and explains how you can deal with these topics in your ABP based application. > This document is valid regardless you have a monolith application or a microservice solution. The Application term is used for a process. An application can be a monolith web application, a service in a microservice solution, a console application, or another kind of executable process. +> +> For example, if you are deploying your application to Kubernetes and configure so that your application or service runs in multiple pods, then your application or service runs in a clustered environment. ## Understanding the Clustered Environment From b1a9c4e19638abc07cd5a948da5b15f7435d0389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 30 Apr 2022 18:23:59 +0300 Subject: [PATCH 46/60] Added section: Implementing Background Workers --- docs/en/Background-Jobs.md | 9 +++++--- docs/en/Deployment/Clustered-Environment.md | 23 +++++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/docs/en/Background-Jobs.md b/docs/en/Background-Jobs.md index b5442ddec2..e9a1311ef2 100644 --- a/docs/en/Background-Jobs.md +++ b/docs/en/Background-Jobs.md @@ -155,8 +155,6 @@ public class MyModule : AbpModule } ```` -> Default background manager (see below) does not support multiple processes execute the same job queue. So, if you have multiple running instance of your application and you are using the default background job manager, you should only enable one application instance process the job queue. - ## Default Background Job Manager ABP framework includes a simple `IBackgroundJobManager` implementation that; @@ -199,7 +197,12 @@ Background Jobs module implements `IBackgroundJobStore` using various data acces The default background job manager is compatible with [clustered environments](Deployment/Clustered-Environment.md) (where multiple instances of your application run concurrently). It uses a [distributed lock](Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance in a time. -However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, please follow the [distributed lock](Distributed-Locking.md) document to configure a provider for your application, if it is not already configured. +However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, **please follow the [distributed lock](Distributed-Locking.md) document to configure a provider for your application**, if it is not already configured. + +If you don't want to use a distributed lock provider, you may go with the following options: + +* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false` as explained in the *Disable Job Execution* section) in all application instances except one of them, so only the single instance executes the jobs (while other application instances can still queue jobs). +* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false` as explained in the *Disable Job Execution* section) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in background) to execute all the background jobs. This can be a good option if your background jobs consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs doesn't affect your application's performance. ## Integrations diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md index 11db6c7e0f..a4f4716d75 100644 --- a/docs/en/Deployment/Clustered-Environment.md +++ b/docs/en/Deployment/Clustered-Environment.md @@ -71,14 +71,29 @@ ABP's [background job system](../Background-Jobs.md) is used to queue tasks to b ABP's default background job manager is compatible with clustered environments. It uses a [distributed lock](../Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance in a time. See the *Configuring a Distributed Lock Provider* section below to learn how to configure a distributed lock provider for your application, so the default background job manager properly works in a clustered environment. -> If you are using an external background job integration (e.g. [Hangfire](../Background-Workers-Hangfire.md) or [Quartz](../Background-Workers-Quartz.md)) instead of the default background job manager, then please refer your provider's documentation to learn how it should be configured for a clustered environment. +If you don't want to use a distributed lock provider, you may go with the following options: -## Implementing Background Workers +* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false`) in all application instances except one of them, so only the single instance executes the jobs (while other application instances can still queue jobs). +* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in background) to execute all the background jobs. This can be a good option if your background jobs consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs doesn't affect your application's performance. -TODO +> If you are using an external background job integration (e.g. [Hangfire](../Background-Workers-Hangfire.md) or [Quartz](../Background-Workers-Quartz.md)) instead of the default background job manager, then please refer your provider's documentation to learn how it should be configured for a clustered environment. ## Configuring a Distributed Lock Provider ABP provides a distributed locking abstraction with an implementation made with the [DistributedLock](https://github.com/madelson/DistributedLock) library. A distributed lock is used to control concurrent access to a shared resource by multiple applications to prevent corruption of the resource because of concurrent writes. ABP Framework and some pre-built [application modules](../Modules/Index.md) are using the distributed locking for several reasons. -However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, please follow the [distributed lock](../Distributed-Locking.md) document to configure a provider for your application, if it is not already configured. \ No newline at end of file +However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, please follow the [distributed lock](../Distributed-Locking.md) document to configure a provider for your application, if it is not already configured. + +## Implementing Background Workers + +ASP.NET Core provides [hosted services](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services) and ABP provides [background workers](../Background-Workers.md) to perform tasks in background threads in your application. + +If your application has tasks running in background, you should care on how they will behave in a clustered environment, especially if your background tasks are using the same resources. You should design your background tasks so they continue to work properly in the clustered environment. + +Assume that your background worker in your SaaS application checks user subscriptions and sends emails if their subscription renewal date approaches. If the background task run in multiple application instances, it is probable to send the same email many times to some users, which will disturb them. + +We suggest to you to use one of the following approaches to overcome the problem: + +* Implement your background workers so that they works in a clustered environment without any problem. Using the [distributed lock](../Distributed-Locking.md) to ensure concurrency control is a way of doing that. A background worker in an application instance may handle a distributed lock, so the workers in other application instances will wait the lock. In this way, only one worker does the actual work, while others wait in idle. If you implement this, your workers run safely without caring how the application is deployed. +* Stop background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers. +* Stop background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in background) to execute all the background tasks. This can be a good option if your background workers consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks doesn't affect your application's performance. \ No newline at end of file From da040e10a5a6e61aa392ad6b37f280efa06f5045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 30 Apr 2022 18:41:06 +0300 Subject: [PATCH 47/60] Completed the clustered environment document --- docs/en/Background-Workers.md | 7 ++++--- docs/en/Caching.md | 6 +++--- docs/en/Deployment/Clustered-Environment.md | 2 +- docs/en/Distributed-Locking.md | 10 +++++++--- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/en/Background-Workers.md b/docs/en/Background-Workers.md index 726a8817ab..922740f92a 100644 --- a/docs/en/Background-Workers.md +++ b/docs/en/Background-Workers.md @@ -126,10 +126,11 @@ Background workers only work if your application is running. If you host the bac Be careful if you run multiple instances of your application simultaneously in a clustered environment. In that case, every application runs the same worker which may create conflicts if your workers are running on the same resources (processing the same data, for example). -If that's a problem for your workers, you have two options; +If that's a problem for your workers, you have the following options: -* Disable the background worker system using the `AbpBackgroundWorkerOptions` described above, for all the application instances, except one of them. -* Disable the background worker system for all the application instances and create another special application that runs on a single server and execute the workers. +* Implement your background workers so that they works in a clustered environment without any problem. Using the [distributed lock](../Distributed-Locking.md) to ensure concurrency control is a way of doing that. A background worker in an application instance may handle a distributed lock, so the workers in other application instances will wait the lock. In this way, only one worker does the actual work, while others wait in idle. If you implement this, your workers run safely without caring how the application is deployed. +* Stop background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers. +* Stop background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in background) to execute all the background tasks. This can be a good option if your background workers consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks doesn't affect your application's performance. ## Integrations diff --git a/docs/en/Caching.md b/docs/en/Caching.md index 13129bbc95..064323a9c3 100644 --- a/docs/en/Caching.md +++ b/docs/en/Caching.md @@ -1,7 +1,9 @@ -# Caching +# Distributed Caching ABP Framework extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). +> **Default implementation of the `IDistributedCache` interface is the `MemoryDistributedCache` which works in-memory.** See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](Redis-Cache.md) document if you want to use the Redis as the distributed cache server. + ## Installation > This package is already installed by default with the [application startup template](Startup-Templates/Application.md). So, most of the time, you don't need to install it manually. @@ -27,8 +29,6 @@ ASP.NET Core defines the `IDistributedCache` interface to get/set the cache valu > `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications, but also available to **any type of applications**. -> Default implementation of the `IDistributedCache` interface is the `MemoryDistributedCache` which works **in-memory**. See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](Redis-Cache.md) document if you want to use the Redis as the distributed cache server. - See [ASP.NET Core's distributed caching document](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more information. ### `IDistributedCache` Interface diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md index a4f4716d75..ac7b425377 100644 --- a/docs/en/Deployment/Clustered-Environment.md +++ b/docs/en/Deployment/Clustered-Environment.md @@ -45,7 +45,7 @@ You may have more problems with clustered deployment, but these are the most com ASP.NET Core provides different kind of caching features. [In-memory cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory) stores your objects in the memory of the local server and only available to the application that stored the object. Non-sticky sessions in a clustered environment should use the [distributed caching](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) except some specific scenarios (for example, you can cache a local CSS file into memory. It is a read-only data and it is the same in all application instances. You can cache it in memory for performance reasons without any problem). -[ABP's Distributed Cache](Caching.md) extends [ASP.NET Core's distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) infrastructure. It works in-memory by default. You should configure an actual distributed cache provider when you want to deploy your application to a clustered environment. +[ABP's Distributed Cache](../Caching.md) extends [ASP.NET Core's distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) infrastructure. It works in-memory by default. You should configure an actual distributed cache provider when you want to deploy your application to a clustered environment. > You should configure the cache provider for a clustered deployment, even if your application doesn't directly use `IDistributedCache`. Because ABP Framework and pre-built [application modules](../Modules/Index.md) are using the distributed cache. diff --git a/docs/en/Distributed-Locking.md b/docs/en/Distributed-Locking.md index d724bfec68..44cdca74ac 100644 --- a/docs/en/Distributed-Locking.md +++ b/docs/en/Distributed-Locking.md @@ -13,7 +13,7 @@ abp add-package Volo.Abp.DistributedLocking This package provides the necessary API to use the distributed locking system, however, you should configure a provider before using it. -### Configuring a provider +### Configuring a Provider The [DistributedLock](https://github.com/madelson/DistributedLock) library provides [various of implementations](https://github.com/madelson/DistributedLock#implementations) for the locking, like [Redis](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md) and [ZooKeeper](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.ZooKeeper.md). @@ -59,7 +59,7 @@ This code gets the Redis connection string from the [configuration](Configuratio There are two ways to use the distributed locking API: ABP's `IAbpDistributedLock` abstraction and [DistributedLock](https://github.com/madelson/DistributedLock) library's API. -### Using the IAbpDistributedLock service +### Using the IAbpDistributedLock Service `IAbpDistributedLock` is a simple service provided by the ABP framework for simple usage of distributed locking. @@ -101,6 +101,10 @@ namespace AbpDemo * `timeout` (`TimeSpan`): A timeout value to wait to obtain the lock. Default value is `TimeSpan.Zero`, which means it doesn't wait if the lock is already owned by another application. * `cancellationToken`: A cancellation token that can be triggered later to cancel the operation. -### Using DistributedLock library's API +### Using DistributedLock Library's API ABP's `IAbpDistributedLock` service is very limited and mainly designed to be internally used by the ABP Framework. For your own applications, you can use the DistributedLock library's own API. See its [own documentation](https://github.com/madelson/DistributedLock) for details. + +## The Volo.Abp.DistributedLocking.Abstractions Package + +If you are building a reusable library or an application module, then you may not want to bring an additional dependency to your module for simple applications those run as a single instance. In this case, your library can depend on the [Volo.Abp.DistributedLocking.Abstractions](https://nuget.org/packages/Volo.Abp.DistributedLocking.Abstractions) package which defines the `IAbpDistributedLock` service and implements it as in-process (not distributed actually). In this way, your library can run properly (without a distributed lock provider dependency) in an application that runs as a single instance. If the application is deployed to a [clustered environment](Deployment/Clustered-Environment.md), then the application developer should install a real distributed provider as explained in the *Installation* section. From 0045569fa7914326d8c853d719a7a984087ed3a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 30 Apr 2022 18:48:06 +0300 Subject: [PATCH 48/60] Fix document path --- docs/en/docs-nav.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index e6cb5375b0..b81f0b6efd 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -1231,7 +1231,7 @@ "items": [ { "text": "Deploying to a Clustered Environment", - "path": "Clustered-Environment.md" + "path": "Deployment/Clustered-Environment.md" } ] }, From a915c20f94395d0016219d71f2bf434ffe6f82c2 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 2 May 2022 14:26:34 +0800 Subject: [PATCH 49/60] Add `ctor` to `ExtensibleEntityDto` base classes. --- .../Dtos/ExtensibleAuditedEntityDto.cs | 25 +++++++++++++++++++ .../ExtensibleAuditedEntityWithUserDto.cs | 25 +++++++++++++++++++ .../ExtensibleCreationAuditedEntityDto.cs | 25 +++++++++++++++++++ ...ensibleCreationAuditedEntityWithUserDto.cs | 25 +++++++++++++++++++ .../Application/Dtos/ExtensibleEntityDto.cs | 24 ++++++++++++++++++ .../Dtos/ExtensibleFullAuditedEntityDto.cs | 25 +++++++++++++++++++ .../ExtensibleFullAuditedEntityWithUserDto.cs | 25 +++++++++++++++++++ 7 files changed, 174 insertions(+) diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs index aa03b8da1d..775f629c3c 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs @@ -1,5 +1,6 @@ using System; using Volo.Abp.Auditing; +using Volo.Abp.Data; namespace Volo.Abp.Application.Dtos; @@ -16,6 +17,18 @@ public abstract class ExtensibleAuditedEntityDto : ExtensibleCreati /// public Guid? LastModifierId { get; set; } + + protected ExtensibleAuditedEntityDto() + : this(true) + { + + } + + protected ExtensibleAuditedEntityDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } /// @@ -30,4 +43,16 @@ public abstract class ExtensibleAuditedEntityDto : ExtensibleCreationAuditedEnti /// public Guid? LastModifierId { get; set; } + + protected ExtensibleAuditedEntityDto() + : this(true) + { + + } + + protected ExtensibleAuditedEntityDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs index 05193efc4a..5f6fd28628 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs @@ -1,5 +1,6 @@ using System; using Volo.Abp.Auditing; +using Volo.Abp.Data; namespace Volo.Abp.Application.Dtos; @@ -18,6 +19,18 @@ public abstract class ExtensibleAuditedEntityWithUserDto /// public TUserDto LastModifier { get; set; } + + protected ExtensibleAuditedEntityWithUserDto() + : this(true) + { + + } + + protected ExtensibleAuditedEntityWithUserDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } /// @@ -35,4 +48,16 @@ public abstract class ExtensibleAuditedEntityWithUserDto : ExtensibleA /// public TUserDto LastModifier { get; set; } + + protected ExtensibleAuditedEntityWithUserDto() + : this(true) + { + + } + + protected ExtensibleAuditedEntityWithUserDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs index ba3bec655d..9230759052 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs @@ -1,5 +1,6 @@ using System; using Volo.Abp.Auditing; +using Volo.Abp.Data; namespace Volo.Abp.Application.Dtos; @@ -16,6 +17,18 @@ public abstract class ExtensibleCreationAuditedEntityDto : Extensib /// public Guid? CreatorId { get; set; } + + protected ExtensibleCreationAuditedEntityDto() + : this(true) + { + + } + + protected ExtensibleCreationAuditedEntityDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } /// @@ -30,4 +43,16 @@ public abstract class ExtensibleCreationAuditedEntityDto : ExtensibleEntityDto, /// public Guid? CreatorId { get; set; } + + protected ExtensibleCreationAuditedEntityDto() + : this(true) + { + + } + + protected ExtensibleCreationAuditedEntityDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs index e6a31b4a68..3ea1be97ab 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs @@ -1,5 +1,6 @@ using System; using Volo.Abp.Auditing; +using Volo.Abp.Data; namespace Volo.Abp.Application.Dtos; @@ -14,6 +15,18 @@ namespace Volo.Abp.Application.Dtos; public abstract class ExtensibleCreationAuditedEntityWithUserDto : ExtensibleCreationAuditedEntityDto, ICreationAuditedObject { public TUserDto Creator { get; set; } + + protected ExtensibleCreationAuditedEntityWithUserDto() + : this(true) + { + + } + + protected ExtensibleCreationAuditedEntityWithUserDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } /// @@ -27,4 +40,16 @@ public abstract class ExtensibleCreationAuditedEntityWithUserDto : Ext ICreationAuditedObject { public TUserDto Creator { get; set; } + + protected ExtensibleCreationAuditedEntityWithUserDto() + : this(true) + { + + } + + protected ExtensibleCreationAuditedEntityWithUserDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs index 788825e842..1af46a3287 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs @@ -11,6 +11,18 @@ public abstract class ExtensibleEntityDto : ExtensibleObject, IEntityDto public TKey Id { get; set; } + protected ExtensibleEntityDto() + : this(true) + { + + } + + protected ExtensibleEntityDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } + public override string ToString() { return $"[DTO: {GetType().Name}] Id = {Id}"; @@ -20,6 +32,18 @@ public abstract class ExtensibleEntityDto : ExtensibleObject, IEntityDto : ExtensibleAu /// public DateTime? DeletionTime { get; set; } + + protected ExtensibleFullAuditedEntityDto() + : this(true) + { + + } + + protected ExtensibleFullAuditedEntityDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } /// @@ -36,4 +49,16 @@ public abstract class ExtensibleFullAuditedEntityDto : ExtensibleAuditedEntityDt /// public DateTime? DeletionTime { get; set; } + + protected ExtensibleFullAuditedEntityDto() + : this(true) + { + + } + + protected ExtensibleFullAuditedEntityDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs index bd1ee89bcb..018a84cd7a 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs @@ -1,5 +1,6 @@ using System; using Volo.Abp.Auditing; +using Volo.Abp.Data; namespace Volo.Abp.Application.Dtos; @@ -21,6 +22,18 @@ public abstract class ExtensibleFullAuditedEntityWithUserDto public TUserDto Deleter { get; set; } + + protected ExtensibleFullAuditedEntityWithUserDto() + : this(true) + { + + } + + protected ExtensibleFullAuditedEntityWithUserDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } /// @@ -41,4 +54,16 @@ public abstract class ExtensibleFullAuditedEntityWithUserDto : Extensi /// public TUserDto Deleter { get; set; } + + protected ExtensibleFullAuditedEntityWithUserDto() + : this(true) + { + + } + + protected ExtensibleFullAuditedEntityWithUserDto(bool setDefaultsForExtraProperties) + : base(setDefaultsForExtraProperties) + { + + } } From 06c678343b97f1cf11696c5e835b74a9fba160c9 Mon Sep 17 00:00:00 2001 From: hpstory <33348162+hpstory@users.noreply.github.com> Date: Wed, 4 May 2022 11:29:28 +0800 Subject: [PATCH 50/60] Create docs/zh-Hans/Deployment/Index.md --- docs/zh-Hans/Deployment/Index.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/zh-Hans/Deployment/Index.md diff --git a/docs/zh-Hans/Deployment/Index.md b/docs/zh-Hans/Deployment/Index.md new file mode 100644 index 0000000000..9df24e56f0 --- /dev/null +++ b/docs/zh-Hans/Deployment/Index.md @@ -0,0 +1,9 @@ +# 部署 + +部署ABP应用程序与部署其他.NET或ASP.NET Core应用程序并没有什么不同. 你可以将其部署到云服务提供商(例如Azure、AWS、Google)或内部部署服务器、IIS或任何其他web服务器. ABP的文档中没有太多关于部署的信息. 你可以参考提供商的文档. + +但是, 在部署应用程序时, 有些主题是你应该注意的. 其中大多数是一般的软件部署注意事项, 但你应该了解如何在基于ABP的应用程序中处理它们. 我们为此准备了指南, 建议你在设计部署配置之前仔细阅读这些指南. + +## 指南 + +* [部署到群集环境](Clustered-Environment.md): 讲解了当你希望同时运行应用程序的多个实例时, 如何来配置应用程序. From bec27ed0ba4f8be79e64cad605318a83d8d98eb2 Mon Sep 17 00:00:00 2001 From: braim23 <94292623+braim23@users.noreply.github.com> Date: Wed, 4 May 2022 07:53:56 +0300 Subject: [PATCH 51/60] Quick Grammar Fix of all the final commits It'd be better to take a look at my changes if they changed any of the context.. --- docs/en/Background-Jobs.md | 4 +- docs/en/Background-Workers.md | 6 +-- docs/en/Caching.md | 2 +- docs/en/Deployment/Clustered-Environment.md | 48 ++++++++++----------- docs/en/Deployment/Index.md | 2 +- docs/en/Distributed-Locking.md | 4 +- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/en/Background-Jobs.md b/docs/en/Background-Jobs.md index e9a1311ef2..84e746f73d 100644 --- a/docs/en/Background-Jobs.md +++ b/docs/en/Background-Jobs.md @@ -195,14 +195,14 @@ Background Jobs module implements `IBackgroundJobStore` using various data acces ### Clustered Deployment -The default background job manager is compatible with [clustered environments](Deployment/Clustered-Environment.md) (where multiple instances of your application run concurrently). It uses a [distributed lock](Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance in a time. +The default background job manager is compatible with [clustered environments](Deployment/Clustered-Environment.md) (where multiple instances of your application run concurrently). It uses a [distributed lock](Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance at a time. However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, **please follow the [distributed lock](Distributed-Locking.md) document to configure a provider for your application**, if it is not already configured. If you don't want to use a distributed lock provider, you may go with the following options: * Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false` as explained in the *Disable Job Execution* section) in all application instances except one of them, so only the single instance executes the jobs (while other application instances can still queue jobs). -* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false` as explained in the *Disable Job Execution* section) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in background) to execute all the background jobs. This can be a good option if your background jobs consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs doesn't affect your application's performance. +* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false` as explained in the *Disable Job Execution* section) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background jobs. This can be a good option if your background jobs consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs don't affect your application's performance. ## Integrations diff --git a/docs/en/Background-Workers.md b/docs/en/Background-Workers.md index 922740f92a..e1cce6ae28 100644 --- a/docs/en/Background-Workers.md +++ b/docs/en/Background-Workers.md @@ -128,9 +128,9 @@ Be careful if you run multiple instances of your application simultaneously in a If that's a problem for your workers, you have the following options: -* Implement your background workers so that they works in a clustered environment without any problem. Using the [distributed lock](../Distributed-Locking.md) to ensure concurrency control is a way of doing that. A background worker in an application instance may handle a distributed lock, so the workers in other application instances will wait the lock. In this way, only one worker does the actual work, while others wait in idle. If you implement this, your workers run safely without caring how the application is deployed. -* Stop background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers. -* Stop background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in background) to execute all the background tasks. This can be a good option if your background workers consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks doesn't affect your application's performance. +* Implement your background workers so that they work in a clustered environment without any problem. Using the [distributed lock](../Distributed-Locking.md) to ensure concurrency control is a way of doing that. A background worker in an application instance may handle a distributed lock, so the workers in other application instances will wait for the lock. In this way, only one worker does the actual work, while others wait in idle. If you implement this, your workers run safely without caring about how the application is deployed. +* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers. +* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background tasks. This can be a good option if your background workers consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks don't affect your application's performance. ## Integrations diff --git a/docs/en/Caching.md b/docs/en/Caching.md index 064323a9c3..6f1416c203 100644 --- a/docs/en/Caching.md +++ b/docs/en/Caching.md @@ -2,7 +2,7 @@ ABP Framework extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). -> **Default implementation of the `IDistributedCache` interface is the `MemoryDistributedCache` which works in-memory.** See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](Redis-Cache.md) document if you want to use the Redis as the distributed cache server. +> **Default implementation of the `IDistributedCache` interface is` MemoryDistributedCache` which works in-memory.** See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](Redis-Cache.md) document if you want to use Redis as the distributed cache server. ## Installation diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md index ac7b425377..d76a199518 100644 --- a/docs/en/Deployment/Clustered-Environment.md +++ b/docs/en/Deployment/Clustered-Environment.md @@ -1,10 +1,10 @@ # Deploying to a Clustered Environment -This document introduces the topics that you should care when you are deploying your application to a clustered environment where **multiple instances of your application runs concurrently**, and explains how you can deal with these topics in your ABP based application. +This document introduces the topics that you should consider when you are deploying your application to a clustered environment where **multiple instances of your application run concurrently**, and explains how you can deal with these topics in your ABP based application. -> This document is valid regardless you have a monolith application or a microservice solution. The Application term is used for a process. An application can be a monolith web application, a service in a microservice solution, a console application, or another kind of executable process. +> This document is valid regardless you have a monolith application or a microservice solution. The Application term is used for a process. An application can be a monolith web application, a service in a microservice solution, a console application, or another kind of an executable process. > -> For example, if you are deploying your application to Kubernetes and configure so that your application or service runs in multiple pods, then your application or service runs in a clustered environment. +> For example, if you are deploying your application to Kubernetes and configure your application or service to run in multiple pods, then your application or service runs in a clustered environment. ## Understanding the Clustered Environment @@ -20,36 +20,36 @@ Browsers and other client applications can directly make HTTP requests to your a ### Clustered Deployment -**Clustered deployment** is the way of running **multiple instances** of your application **concurrently** in a single or multiple servers. In this way, different instances can serve to different requests and you can scale by adding new servers to the system. The following figure shows a typical implementation of clustering using a **load balancer**: +**Clustered deployment** is the way of running **multiple instances** of your application **concurrently** in a single or multiple servers. In this way, different instances can serve different requests and you can scale by adding new servers to the system. The following figure shows a typical implementation of clustering using a **load balancer**: ![deployment-clustered](../images/deployment-clustered.png) ### Load Balancers -[Load balancers](https://en.wikipedia.org/wiki/Load_balancing_(computing)) have a lot of features, but they fundamentally **forwards an incoming HTTP request** to an instance of your application and returns your response back to the client application. +[Load balancers](https://en.wikipedia.org/wiki/Load_balancing_(computing)) have a lot of features, but they fundamentally **forward an incoming HTTP request** to an instance of your application and return your response back to the client application. Load balancers can use different algorithms for selecting the application instance while determining the application instance that is used to deliver the incoming request. **Round Robin** is one of the simplest and most used algorithms. Requests are delivered to the application instances in rotation. First instance gets the first request, second instance gets the second, and so on. It returns to the first instance after all the instances are used, and the algorithm goes like that for the next requests. ### Potential Problems -Once multiple instances of your application runs in parallel, you should carefully consider the following topics: +Once multiple instances of your application run in parallel, you should carefully consider the following topics: * Any **state (data) stored in memory** of your application will become a problem when you have multiple instances. A state stored in memory of an application instance may not be available in the next request since the next request will be handled by a different application instance. While there are some solutions (like sticky sessions) to overcome this problem user-basis, it is a **best practice to design your application as stateless** if you want to run it in a cluster, container or/and cloud. * **In-memory caching** is a kind of in-memory state and should not be used in a clustered application. You should use **distributed caching** instead. -* You shouldn't store data in the **local file system** that should be available to all instances of your application. Difference application instance may run in different containers or servers and they may not be able to access to the same file system. You can use a **cloud or external storage provider** as a solution. +* You shouldn't store data in the **local file system**. It should be available to all instances of your application. Different application instance may run in different containers or servers and they may not be able to have access to the same file system. You can use a **cloud or external storage provider** as a solution. * If you have **background workers** or **job queue managers**, you should be careful since multiple instances may try to execute the same job or perform the same work concurrently. As a result, you may have the same work done multiple times or you may get a lot of errors while trying to access and change the same resources. -You may have more problems with clustered deployment, but these are the most common ones. ABP has been designed to be compatible with clustered deployment scenario. The following sections explains what you should do when you are deploying your ABP based application to a clustered environment. +You may have more problems with clustered deployment, but these are the most common ones. ABP has been designed to be compatible with the clustered deployment scenario. The following sections explain what you should do when you are deploying your ABP based application to a clustered environment. ## Switching to a Distributed Cache -ASP.NET Core provides different kind of caching features. [In-memory cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory) stores your objects in the memory of the local server and only available to the application that stored the object. Non-sticky sessions in a clustered environment should use the [distributed caching](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) except some specific scenarios (for example, you can cache a local CSS file into memory. It is a read-only data and it is the same in all application instances. You can cache it in memory for performance reasons without any problem). +ASP.NET Core provides different kind of caching features. [In-memory cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory) stores your objects in the memory of the local server and is only available to the application that stored the object. Non-sticky sessions in a clustered environment should use the [distributed caching](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) except some specific scenarios (for example, you can cache a local CSS file into memory. It is read-only data and it is the same in all application instances. You can cache it in memory for performance reasons without any problem). [ABP's Distributed Cache](../Caching.md) extends [ASP.NET Core's distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) infrastructure. It works in-memory by default. You should configure an actual distributed cache provider when you want to deploy your application to a clustered environment. -> You should configure the cache provider for a clustered deployment, even if your application doesn't directly use `IDistributedCache`. Because ABP Framework and pre-built [application modules](../Modules/Index.md) are using the distributed cache. +> You should configure the cache provider for clustered deployment, even if your application doesn't directly use `IDistributedCache`. Because the ABP Framework and the pre-built [application modules](../Modules/Index.md) are using distributed cache. -ASP.NET Core's provides multiple integrations to use as your distributed cache provider, like [Redis](https://redis.io/) and [NCache](https://www.alachisoft.com/ncache/). You can follow [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to learn how to use them in your applications. +ASP.NET Core provides multiple integrations to use as your distributed cache provider, like [Redis](https://redis.io/) and [NCache](https://www.alachisoft.com/ncache/). You can follow [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to learn how to use them in your applications. If you decided to use Redis as your distributed cache provider, **follow [ABP's Redis Cache Integration document](../Redis-Cache.md)** for the steps you need to follow to install it into your application and setup your Redis configuration. @@ -57,30 +57,30 @@ If you decided to use Redis as your distributed cache provider, **follow [ABP's ## Using a Proper BLOB Storage Provider -If you have used ABP's [BLOB Storing](../Blob-Storing.md) feature with the [File System provider](../Blob-Storing-File-System.md), you should use another provider in your clustered environment since the File System provider uses application's local file system. +If you have used ABP's [BLOB Storing](../Blob-Storing.md) feature with the [File System provider](../Blob-Storing-File-System.md), you should use another provider in your clustered environment since the File System provider uses the application's local file system. The [Database BLOB provider](../Blob-Storing-Database) is the easiest way since it uses your application's main database (or another database if you configure) to store BLOBs. However, you should remember that BLOBs are large objects and may quickly increase your database's size. > [ABP Commercial](https://commercial.abp.io/) startup solution templates come with the database BLOB provider as pre-installed, and stores BLOBs in the application's database. -Check the [BLOB Storing](../Blob-Storing.md) document to see all available BLOG storage providers. +Check the [BLOB Storing](../Blob-Storing.md) document to see all the available BLOG storage providers. ## Configuring Background Jobs -ABP's [background job system](../Background-Jobs.md) is used to queue tasks to be executed in background. Background job queue is persistent and a queued task is guaranteed to be executed (it is re-tried if it fails). +ABP's [background job system](../Background-Jobs.md) is used to queue tasks to be executed in the background. Background job queue is persistent and a queued task is guaranteed to be executed (it is re-tried if it fails). -ABP's default background job manager is compatible with clustered environments. It uses a [distributed lock](../Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance in a time. See the *Configuring a Distributed Lock Provider* section below to learn how to configure a distributed lock provider for your application, so the default background job manager properly works in a clustered environment. +ABP's default background job manager is compatible with clustered environments. It uses a [distributed lock](../Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance at a time. See the *Configuring a Distributed Lock Provider* section below to learn how to configure a distributed lock provider for your application, so the default background job manager properly works in a clustered environment. If you don't want to use a distributed lock provider, you may go with the following options: * Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false`) in all application instances except one of them, so only the single instance executes the jobs (while other application instances can still queue jobs). -* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in background) to execute all the background jobs. This can be a good option if your background jobs consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs doesn't affect your application's performance. +* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background jobs. This can be a good option if your background jobs consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs don't affect your application's performance. -> If you are using an external background job integration (e.g. [Hangfire](../Background-Workers-Hangfire.md) or [Quartz](../Background-Workers-Quartz.md)) instead of the default background job manager, then please refer your provider's documentation to learn how it should be configured for a clustered environment. +> If you are using an external background job integration (e.g. [Hangfire](../Background-Workers-Hangfire.md) or [Quartz](../Background-Workers-Quartz.md)) instead of the default background job manager, then please refer to your provider's documentation to learn how it should be configured for a clustered environment. ## Configuring a Distributed Lock Provider -ABP provides a distributed locking abstraction with an implementation made with the [DistributedLock](https://github.com/madelson/DistributedLock) library. A distributed lock is used to control concurrent access to a shared resource by multiple applications to prevent corruption of the resource because of concurrent writes. ABP Framework and some pre-built [application modules](../Modules/Index.md) are using the distributed locking for several reasons. +ABP provides a distributed locking abstraction with an implementation made with the [DistributedLock](https://github.com/madelson/DistributedLock) library. A distributed lock is used to control concurrent access to a shared resource by multiple applications to prevent corruption of the resource because of concurrent writes. The ABP Framework and some pre-built [application modules](../Modules/Index.md) are using distributed locking for several reasons. However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, please follow the [distributed lock](../Distributed-Locking.md) document to configure a provider for your application, if it is not already configured. @@ -88,12 +88,12 @@ However, the distributed lock system works in-process by default. That means it ASP.NET Core provides [hosted services](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services) and ABP provides [background workers](../Background-Workers.md) to perform tasks in background threads in your application. -If your application has tasks running in background, you should care on how they will behave in a clustered environment, especially if your background tasks are using the same resources. You should design your background tasks so they continue to work properly in the clustered environment. +If your application has tasks running in the background, you should consider how they will behave in a clustered environment, especially if your background tasks are using the same resources. You should design your background tasks so that they continue to work properly in the clustered environment. -Assume that your background worker in your SaaS application checks user subscriptions and sends emails if their subscription renewal date approaches. If the background task run in multiple application instances, it is probable to send the same email many times to some users, which will disturb them. +Assume that your background worker in your SaaS application checks user subscriptions and sends emails if their subscription renewal date approaches. If the background task runs in multiple application instances, it is probable to send the same email many times to some users, which will disturb them. -We suggest to you to use one of the following approaches to overcome the problem: +We suggest you to use one of the following approaches to overcome the problem: -* Implement your background workers so that they works in a clustered environment without any problem. Using the [distributed lock](../Distributed-Locking.md) to ensure concurrency control is a way of doing that. A background worker in an application instance may handle a distributed lock, so the workers in other application instances will wait the lock. In this way, only one worker does the actual work, while others wait in idle. If you implement this, your workers run safely without caring how the application is deployed. -* Stop background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers. -* Stop background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in background) to execute all the background tasks. This can be a good option if your background workers consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks doesn't affect your application's performance. \ No newline at end of file +* Implement your background workers so that they work in a clustered environment without any problem. Using the [distributed lock](../Distributed-Locking.md) to ensure concurrency control is a way of doing that. A background worker in an application instance may handle a distributed lock, so the workers in other application instances will wait for the lock. In this way, only one worker does the actual work, while others wait in idle. If you implement this, your workers run safely without caring about how the application is deployed. +* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers. +* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background tasks. This can be a good option if your background workers consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks don't affect your application's performance. \ No newline at end of file diff --git a/docs/en/Deployment/Index.md b/docs/en/Deployment/Index.md index b11125275b..dc1cca300e 100644 --- a/docs/en/Deployment/Index.md +++ b/docs/en/Deployment/Index.md @@ -2,7 +2,7 @@ Deploying an ABP application is not different than deploying any .NET or ASP.NET Core application. You can deploy it to a cloud provider (e.g. Azure, AWS, Google Could) or on-premise server, IIS or any other web server. ABP's documentation doesn't contain much information on deployment. You can refer to your provider's documentation. -However, there are some topics you should care when you are deploying your applications. Most of them are general software deployment considerations, but you should understand how to handle them within your ABP based applications. We've prepared guides for this purpose and we suggest you to read these guides carefully before designing your deployment configuration. +However, there are some topics that you should care about when you are deploying your applications. Most of them are general software deployment considerations, but you should understand how to handle them within your ABP based applications. We've prepared guides for this purpose and we suggest you to read these guides carefully before designing your deployment configuration. ## Guides diff --git a/docs/en/Distributed-Locking.md b/docs/en/Distributed-Locking.md index 44cdca74ac..d29b60ad04 100644 --- a/docs/en/Distributed-Locking.md +++ b/docs/en/Distributed-Locking.md @@ -1,5 +1,5 @@ # Distributed Locking -Distributed locking is a technique to manage many applications that try to access the same resource. The main purpose is to allow only one of many applications to access the same resource at the same time. Otherwise, accessing the same object from various applications may corrupt the value of resources. +Distributed locking is a technique to manage many applications that try to access the same resource. The main purpose is to allow only one of many applications to access the same resource at the same time. Otherwise, accessing the same object from various applications may corrupt the value of the resources. > ABP's current distributed locking implementation is based on the [DistributedLock](https://github.com/madelson/DistributedLock) library. @@ -107,4 +107,4 @@ ABP's `IAbpDistributedLock` service is very limited and mainly designed to be in ## The Volo.Abp.DistributedLocking.Abstractions Package -If you are building a reusable library or an application module, then you may not want to bring an additional dependency to your module for simple applications those run as a single instance. In this case, your library can depend on the [Volo.Abp.DistributedLocking.Abstractions](https://nuget.org/packages/Volo.Abp.DistributedLocking.Abstractions) package which defines the `IAbpDistributedLock` service and implements it as in-process (not distributed actually). In this way, your library can run properly (without a distributed lock provider dependency) in an application that runs as a single instance. If the application is deployed to a [clustered environment](Deployment/Clustered-Environment.md), then the application developer should install a real distributed provider as explained in the *Installation* section. +If you are building a reusable library or an application module, then you may not want to bring an additional dependency to your module for simple applications that run as a single instance. In this case, your library can depend on the [Volo.Abp.DistributedLocking.Abstractions](https://nuget.org/packages/Volo.Abp.DistributedLocking.Abstractions) package which defines the `IAbpDistributedLock` service and implements it as in-process (not distributed actually). In this way, your library can run properly (without a distributed lock provider dependency) in an application that runs as a single instance. If the application is deployed to a [clustered environment](Deployment/Clustered-Environment.md), then the application developer should install a real distributed provider as explained in the *Installation* section. From 201d4081b95797821ba70b1b9641d5b2cb19f3f6 Mon Sep 17 00:00:00 2001 From: Cyril-hcj <31679398+Cyril-hcj@users.noreply.github.com> Date: Wed, 4 May 2022 17:19:49 +0800 Subject: [PATCH 52/60] fixed translation --- docs/zh-Hans/Caching.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zh-Hans/Caching.md b/docs/zh-Hans/Caching.md index a75199ce47..7e7a314dab 100644 --- a/docs/zh-Hans/Caching.md +++ b/docs/zh-Hans/Caching.md @@ -47,7 +47,7 @@ ASP.NET Core 定义了 `IDistributedCache` 接口用于 get/set 缓存值 . 但 ABP框架在[Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching/)包定义了通用的泛型 `IDistributedCache` 接口. `TCacheItem` 是存储在缓存中的对象类型. -`IDistributedCache` 接口了上述中的问题; +`IDistributedCache` 解决了上述中的问题; * 它在内部 **序列化/反序列化** 缓存对象. 默认使用 **JSON** 序列化, 但可以替换[依赖注入](Dependency-Injection.md)系统中 `IDistributedCacheSerializer` 服务的实现来覆盖默认的处理. * 它根据缓存中对象类型自动向缓存key添加 **缓存名称** 前缀. 默认缓存名是缓存对象类的全名(如果你的类名以`CacheItem` 结尾, 那么`CacheItem` 会被忽略,不应用到缓存名称上). 你也可以在缓存类上使用 `CacheName` 设置换缓存的名称. @@ -206,4 +206,4 @@ ABP的分布式缓存接口定义了以下批量操作方法,当你需要在一 ### DistributedCacheOptions -TODO \ No newline at end of file +TODO From 445ded3953fb7fb3b0aa2469df97cb3e2781a491 Mon Sep 17 00:00:00 2001 From: Cyril-hcj <31679398+Cyril-hcj@users.noreply.github.com> Date: Wed, 4 May 2022 18:53:14 +0800 Subject: [PATCH 53/60] fixed translation --- docs/zh-Hans/Settings.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/zh-Hans/Settings.md b/docs/zh-Hans/Settings.md index a4d84c91b7..73d3115b19 100644 --- a/docs/zh-Hans/Settings.md +++ b/docs/zh-Hans/Settings.md @@ -65,7 +65,7 @@ public class MySettingDefinitionProvider : SettingDefinitionProvider } ```` -> 使用常量作为设置名称是一种好习惯,ABP的包就是这样做的. `Abp.Mailing.Smtp`设置名称是在`EmailSettingNames`类(在Volo.Abp.Emailing名称空间中)定义的常量. +> 使用常量作为设置名称是一种好习惯,ABP的包就是这样做的. `Abp.Mailing.Smtp.Host`设置名称是在`EmailSettingNames`类(在Volo.Abp.Emailing命名空间中)定义的常量. ## 读取设置值 @@ -141,7 +141,7 @@ var requireDigit = abp.setting.getBoolean('Abp.Identity.Password.RequireDigit'); * `TenantSettingValueProvider`: 获取当前租户的设置值(参阅 [多租户](Multi-Tenancy.md)文档). * `UserSettingValueProvider`: 获取当前用户的设置值(参阅 [当前用户](CurrentUser.md) 文档). -> 设置回退系统从底部 (用户) 到 (默认) 方向起用用. +> 设置回退系统从底部 (用户) 到 顶部(默认) 方向起作用. 全局,租户和用户设置值提供程序使用 `ISettingStore` 从数据源读取值(参见下面的小节). @@ -222,7 +222,7 @@ Configure(options => `ISettingEncryptionService` 用于在设置定义的 `isencryption` 属性设置为 `true` 时加密/解密设置值. -你可以在依赖项入系统中替换此服务,自定义实现加密/解密过程. 默认实现 `StringEncryptionService` 使用AES算法(参见字符串[加密文档](String-Encryption.md)学习更多). +你可以在依赖注入系统中替换此服务,自定义实现加密/解密过程. 默认实现 `StringEncryptionService` 使用AES算法(参见字符串[加密文档](String-Encryption.md)学习更多). ## 设置管理模块 From 8931c8ad10bcebd914f0a2c8b3b566aa4b849122 Mon Sep 17 00:00:00 2001 From: hpstory <33348162+hpstory@users.noreply.github.com> Date: Wed, 4 May 2022 19:26:51 +0800 Subject: [PATCH 54/60] Create Clustered-Environment.md --- .../Deployment/Clustered-Environment.md | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 docs/zh-Hans/Deployment/Clustered-Environment.md diff --git a/docs/zh-Hans/Deployment/Clustered-Environment.md b/docs/zh-Hans/Deployment/Clustered-Environment.md new file mode 100644 index 0000000000..d98ef86c28 --- /dev/null +++ b/docs/zh-Hans/Deployment/Clustered-Environment.md @@ -0,0 +1,99 @@ +# 部署到群集环境 + +本文档介绍了在将应用程序部署到**多个应用程序实例同时运行**的集群环境中时应注意的内容, 并解释了如何在基于ABP的应用程序中处理这些内容. + +> 无论你使用的是整体式应用程序还是微服务解决方案, 本文档均有效. 适用于一个流程. 应用程序可以是整体式web应用程序、微服务解决方案中的服务、控制台应用程序或其他类型的可执行过程. +> +> 例如, 要将应用程序部署到Kubernetes并进行配置, 以便应用程序或服务在多个POD中运行, 那时应用程序或服务将在集群环境中运行. + +## 了解集群环境 + +> 如果你已经熟悉集群部署和负载均衡器, 可以跳过本节. + +### 单实例部署 + +考虑作为**单个实例**部署的应用程序, 如下图所示: + +![deployment-single-instance](../images/deployment-single-instance.png) + +浏览器和其他客户端应用程序可以直接向应用程序发出HTTP请求. 你可以在客户端和应用程序之间放置一个web服务器(例如IIS或NGINX), 但仍有一个应用程序实例在单个服务器或容器中运行. 单实例的配置**限于规模**, 因为它在一台服务器上运行, 并且你受到服务器容量的限制. + +### 集群部署 + +**集群部署**是在一台或多台服务器上**同时运行**应用程序**多个实例**的方式. 通过这种方式, 不同的实例可以满足不同的请求, 并且可以通过在系统中添加新服务器来扩展. 下图显示了集群使用**负载均衡器**的典型实现: + +![deployment-clustered](../images/deployment-clustered.png) + +### 负载均衡器 + +[负载均衡器](https://en.wikipedia.org/wiki/Load_balancing_(computing)) 有很多特性, 但它们基本上会将**传入的HTTP请求转发**给应用程序的实例, 并将响应返回给客户端应用程序. + +负载平衡器可以使用不同的算法来选择应用程序实例, 同时确定用于传递传入请求的应用程序实例. **循环**是最简单、最常用的算法之一. 请求被轮流传递到应用程序实例. 第一个实例得到第一个请求, 第二个实例得到第二个请求, 依此类推. 在所有实例都被使用之后, 它返回到第一个实例, 并且下一个请求的算法也是类似的. + +### 潜在问题 + +一旦应用程序的多个实例并行运行, 你应该仔细考虑以下内容: + +* 当你有多个实例时, 存储在应用程序 **内存中的任何状态(数据)** 都将成为问题. 存储在应用程序实例内存中的状态可能在下一个请求中不可用, 因为下一个请求将由不同的应用程序实例处理. 虽然有一些解决方案(比如粘性会话)可以解决这个问题, 但如果你想在集群、容器或云中运行应用程序, **最好将其设计为无状态**. +* **内存缓存** 是一种内存状态, 不应在集群应用程序中使用. 你应该使用**分布式缓存**. +* 你不应该在**本地文件系统**中存储应用程序所有实例都可以使用的数据. 不同的应用程序实例可能在不同的容器或服务器中运行, 并且它们可能无法访问同一个文件系统. 你可以使用**云或外部存储提供商**作为解决方案. +* 如果你有**后台工作者**或**作业队列管理器**, 则应小心, 因为多个实例可能会尝试执行同一作业或同时执行同一工作. 因此, 你可能会多次完成相同的工作, 或者在尝试访问和更改相同的资源时可能会出现很多错误. + +集群部署可能会有更多问题, 但这些是最常见的问题. ABP被设计为与集群部署场景兼容. 以下各节介绍了将基于ABP的应用程序部署到集群环境时应执行的操作. + +## 切换分布式缓存 + +ASP.NET Core提供了不同类型的缓存功能. [内存缓存](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory)将对象存储在本地服务器的内存中, 并且仅对存储该对象的应用程序可用. 集群环境中的非粘性会话应使用[分布式缓存](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed), 除了一些特定场景(例如, 你可以将本地CSS文件缓存到内存中. 它是只读数据, 在所有应用程序实例中都是相同的. 出于性能原因, 你可以将其缓存到内存中, 而不会出现任何问题). + +[ABP的分布式缓存](../Caching.md)扩展了[ASP.NET Core的分布式缓存](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed)的基础设施. 默认情况下, 它在内存中工作. 当你要将应用程序部署到集群环境时, 应该配置实际的分布式缓存提供程序. + +> 即使应用程序不直接使用`IDistributedCache`, 也应该为集群部署配置缓存提供程序. 因为ABP框架和预构建的[应用程序模块](../Modules/Index.md)正在使用分布式缓存. + +ASP.NET Core提供了可以用作分布式缓存提供程序的多种集成, 如[Redis](https://redis.io/)和[NCache](https://www.alachisoft.com/ncache/). 你可以按照[微软文档](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed)了解如何在应用程序中使用它们. + +如果你决定使用Redis作为分布式缓存提供程序, **请遵循[ABP的Redis缓存集成文档](../Redis-Cache.md)** 了解将其安装到应用程序并配置Redis所需遵循的步骤. + +> 根据你在创建新ABP解决方案时的偏好, Redis缓存可能会预先安装在你的解决方案中. 例如, 如果你在MVC UI中选择了*Tiered*选项, Redis缓存将进行预装. 因为, 在这种情况下, 解决方案中有两个应用程序, 它们应该使用相同的缓存源来保持一致. + +## 使用合适的BLOB存储提供程序 + +如果你在[文件系统提供程序](../Blob-Storing-File-System.md)中使用了ABP的[BLOB存储](../Blob-Storing.md)功能, 则应该在集群环境中使用另一个提供程序, 因为文件系统提供程序使用应用程序的本地文件系统. + +[数据库BLOB提供程序](../Blob-Storing-Database)是最简单的方法, 因为它使用应用程序的主数据库(或另一个数据库, 如果你配置的话)来存储BLOB. 但是, 你应该记住, BLOB是大型对象, 可能会迅速增加数据库的大小. + +> [ABP商业版](https://commercial.abp.io/)启动解决方案模板预装了数据库BLOB提供程序, 并将BLOB存储在应用程序的数据库中. + +查看[BLOB Storing](../Blob-Storing.md)文档以查看所有可用的BLOB存储提供程序. + +## 配置后台作业 + +ABP的[后台作业系统](../Background-Jobs.md)将要在后台执行的任务进行排队. 后台作业队列是持久性的, 排队的任务能够保证执行(如果失败, 将重新尝试). + +ABP的默认后台作业管理器与集群环境兼容. 它使用[分布式锁](../Distributed-Locking.md)来确保一次只能在单个应用程序实例中执行作业. 请参阅下面的*配置分布式锁提供程序*部分, 了解如何为应用程序配置分布式锁提供程序, 以便默认后台作业管理器在集群环境中正常工作. + +如果不想使用分布式锁提供程序, 可以使用以下选项: + +* 停止所有应用程序实例中的后台作业管理器(将`AbpBackgroundJobOptions.IsJobExecutionEnabled`设置为`false`)只保留其中一个应用程序实例, 以便只有单个实例执行作业(而其他应用程序实例仍可以对作业进行排队). +* 在所有应用程序实例中停止后台作业管理器(将`AbpBackgroundJobOptions.IsJobExecutionEnabled`设置为`false`), 并创建一个专用的应用程序(可能是在自己的容器中运行的控制台应用程序或在后台运行的Windows服务)来执行所有后台作业. 如果你的后台作业占用大量系统资源(CPU、RAM或磁盘), 那么这是一个不错的选择, 这样你就可以将该后台应用程序部署到专用服务器上, 并且后台作业不会影响应用程序的性能. + +> 如果你使用的是外部后台作业集成(例如[Hangfire](../Background-Workers-Hangfire.md)或[Quartz](../Background-Workers-Quartz.md))而不是默认的后台作业管理器, 请参阅提供程序的文档, 了解如何为集群环境配置它. + +## 配置分布式锁提供程序 + +ABP通过[分布式锁](https://github.com/madelson/DistributedLock)库实现了一个抽象的分布式锁. 分布式锁用于控制多个应用程序对共享资源的并发访问, 以防止由于并发写入而导致资源损坏. ABP框架和一些预构建的[应用程序模块](../Modules/Index.md)出于一些原因正在使用分布式锁. + +但是, 分布式锁系统默认在进程中工作. 这意味着它实际上不是分布式的, 除非配置分布式锁提供程序. 因此, 如果尚未配置应用程序的提供程序, 请按照[分布式锁](../Distributed-Locking.md)文档为其配置提供程序. + +## 实现后台工作者 + +ASP.NET Core[托管服务](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services), ABP提供[后台工作者](../Background-Workers.md)在应用程序的后台线程中执行任务. + +如果你的应用程序有在后台运行的任务, 你应该注意它们在集群环境中的行为, 尤其是在后台任务使用相同资源的情况下. 你应该设计后台任务, 以便它们在集群环境中继续正常工作. + +假设SaaS应用程序中的后台工作者检查用户订阅, 并在订阅续订日期临近时发送电子邮件. 如果后台任务在多个应用程序实例中运行, 可能会多次向某些用户发送同一封电子邮件, 这会影响他们. + +我们建议你使用以下方法之一来解决此问题: + +* 实现你的后台工作者, 以便他们在集群环境中工作时不会出现任何问题. 使用[分布式锁](../Distributed-Locking.md)来确保并发控制是一种方法. 应用程序实例中的后台工作者可能会处理分布式锁, 因此其他应用程序实例中的工作者将等待该锁. 这样, 只有一个工作者在实际工作, 而其他的则在等待. 如果你实现了这一点, 你的后台工作者就可以安全地运行, 不必关心应用程序是如何部署的. +* 停止所有应用程序实例中的后台工作者(将`AbpBackgroundWorkerOptions.IsEnabled`设置为`false`), 只保留其中一个应用程序实例, 因此只有单个实例运行这些后台工作者. +* 停止所有应用程序实例中的后台工作者(将`AbpBackgroundWorkerOptions.IsEnabled`设置为`false`), 并创建一个专用的应用程序(可能是在自己的容器中运行的控制台应用程序或在后台运行的Windows服务)来执行所有后台任务. 如果你的后台工作者消耗大量系统资源(CPU、RAM或磁盘), 那么这是一个不错的选择, 这样你就可以将该后台应用程序部署到专用服务器上, 并且你的后台任务不会影响应用程序的性能. From a3172543aecb98fa57491bd15d31554af0033955 Mon Sep 17 00:00:00 2001 From: hpstory <33348162+hpstory@users.noreply.github.com> Date: Wed, 4 May 2022 19:30:43 +0800 Subject: [PATCH 55/60] add images and update docs-nav.json --- docs/zh-Hans/docs-nav.json | 14 ++++++++++++++ docs/zh-Hans/images/deployment-clustered.png | Bin 0 -> 41488 bytes .../images/deployment-single-instance.png | Bin 0 -> 30663 bytes 3 files changed, 14 insertions(+) create mode 100644 docs/zh-Hans/images/deployment-clustered.png create mode 100644 docs/zh-Hans/images/deployment-single-instance.png diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index fa38a203d0..f708415979 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -262,6 +262,10 @@ "text": "种子数据", "path": "Data-Seeding.md" }, + { + "text": "分布式锁", + "path": "Distributed-Locking.md" + }, { "text": "虚拟文件系统", "path": "Virtual-File-System.md" @@ -694,6 +698,16 @@ "text": "测试", "path": "Testing.md" }, + { + "text": "部署", + "path": "Deployment/Index.md", + "items": [ + { + "text": "部署到群集环境", + "path": "Deployment/Clustered-Environment.md" + } + ] + }, { "text": "示例", "items": [ diff --git a/docs/zh-Hans/images/deployment-clustered.png b/docs/zh-Hans/images/deployment-clustered.png new file mode 100644 index 0000000000000000000000000000000000000000..74177482c5e768e71ac6d8d6e1340d06f7dbd176 GIT binary patch literal 41488 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 literal 0 HcmV?d00001 diff --git a/docs/zh-Hans/images/deployment-single-instance.png b/docs/zh-Hans/images/deployment-single-instance.png new file mode 100644 index 0000000000000000000000000000000000000000..8c3f8719b2432dcffb0d3eed2c5005a69ac24027 GIT binary patch literal 30663 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 literal 0 HcmV?d00001 From 533d9860650512489b55569771b6ed5f4a6c0819 Mon Sep 17 00:00:00 2001 From: hpstory <33348162+hpstory@users.noreply.github.com> Date: Wed, 4 May 2022 19:54:03 +0800 Subject: [PATCH 56/60] Create Distributed-Locking.md --- docs/zh-Hans/Distributed-Locking.md | 110 ++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 docs/zh-Hans/Distributed-Locking.md diff --git a/docs/zh-Hans/Distributed-Locking.md b/docs/zh-Hans/Distributed-Locking.md new file mode 100644 index 0000000000..2176230403 --- /dev/null +++ b/docs/zh-Hans/Distributed-Locking.md @@ -0,0 +1,110 @@ +# 分布式锁 +分布式锁是一种管理多个应用程序访问同一资源的技术. 主要目的是同一时间只允许多个应用程序中的一个访问资源. 否则, 从不同的应用程序访问同一对象可能会破坏资源. + +> ABP当前的分布式锁实现基于[DistributedLock](https://github.com/madelson/DistributedLock)库. + +## 安装 + +你可以打开一个命令行终端并输入以下命令来安装[Volo.Abp.DistributedLocking](https://www.nuget.org/packages/Volo.Abp.DistributedLocking)到你的项目中: + +````bash +abp add-package Volo.Abp.DistributedLocking +```` + +这个库提供了使用分布式锁系统所需的API, 但是, 在使用它之前, 你应该配置一个提供程序. + +### 配置一个提供程序 + +[DistributedLock](https://github.com/madelson/DistributedLock)库对[Redis](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md)和[ZooKeeper](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.ZooKeeper.md)提供[多种实现](https://github.com/madelson/DistributedLock#implementations). + +例如, 如果你想使用[Redis provider](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md), 你应该将[DistributedLock.Redis](https://www.nuget.org/packages/DistributedLock.Redis) NuGet包添加到项目中, 然后将以下代码添加到ABP[模块](Module-Development-Basics.md)类的`ConfigureServices`方法中: + +````csharp +using Medallion.Threading; +using Medallion.Threading.Redis; + +namespace AbpDemo +{ + [DependsOn( + typeof(AbpDistributedLockingModule) + //If you have the other dependencies, you should do here + )] + public class MyModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + context.Services.AddSingleton(sp => + { + var connection = ConnectionMultiplexer + .Connect(configuration["Redis:Configuration"]); + return new + RedisDistributedSynchronizationProvider(connection.GetDatabase()); + }); + } + } +} +```` + +此代码从[配置](Configuration.md)获取Redis连接字符串, 因此你可以将以下行添加到`appsettings.json`文件: + +````json +"Redis": { + "Configuration": "127.0.0.1" +} +```` + +## 使用 + +有两种方法可以使用分布式锁API: ABP的`IAbpDistributedLock`抽象和[DistributedLock](https://github.com/madelson/DistributedLock)库的API. + +### 使用IAbpDistributedLock服务 + +`IAbpDistributedLock`是ABP框架提供的一个用于简单使用分布式锁的服务. + +**实例: 使用`IAbpDistributedLock.TryAcquireAsync`方法** + +````csharp +using Volo.Abp.DistributedLocking; + +namespace AbpDemo +{ + public class MyService : ITransientDependency + { + private readonly IAbpDistributedLock _distributedLock; + public MyService(IAbpDistributedLock distributedLock) + { + _distributedLock = distributedLock; + } + + public async Task MyMethodAsync() + { + await using (var handle = + await _distributedLock.TryAcquireAsync("MyLockName")) + { + if (handle != null) + { + // your code that access the shared resource + } + } + } + } +} +```` + +`TryAcquireAsync`可能无法获取锁. 如果无法获取锁, 则返回`null`. 在这种情况下, 你不应该访问资源. 如果句柄不为`null`, 则表示你已获得锁, 并且可以安全地访问资源. + +`TryAcquireAsync`方法拥有以下参数: + +* `name` (`string`, 必须): 锁的唯一名称. 不同的锁命名用于访问不同的资源. +* `timeout` (`TimeSpan`): 等待获取锁的超时值. 默认值为`TimeSpan.Zero`, 这意味着如果锁已经被另一个应用程序拥有, 它不会等待. +* `cancellationToken`: 取消令牌可在触发后取消操作. + +### 使用DistributedLock库的API + +ABP的`IAbpDistributedLock`服务非常有限, 主要用于ABP框架的内部使用. 对于你自己的应用程序, 可以使用DistributedLock库自己的API. 参见[文档](https://github.com/madelson/DistributedLock)详细信息. + +## Volo.Abp.DistributedLocking.Abstractions库 + +如果你正在构建一个可重用的库或应用程序模块, 那么对于作为单个实例运行的简单应用程序, 你可能不希望为模块带来额外的依赖关系. 在这种情况下, 你的库可以依赖于[Volo.Abp.DistributedLocking.Abstractions](https://nuget.org/packages/Volo.Abp.DistributedLocking.Abstractions)库, 它定义了`IAbpDistributedLock`服务, 并将其在进程内实现(实际上不是分布式的). 通过这种方式, 你的库可以在作为单个实例运行的应用程序中正常运行(没有分布式锁提供程序依赖项). 如果应用程序部署到[集群环境](Deployment/Clustered-Environment.md), 那么应用程序开发人员应该安装一个真正的分布式提供程序, 如*安装*部分所述. From 6ce7fc871a28c4e3a19f3ad66ad2294974988153 Mon Sep 17 00:00:00 2001 From: hpstory <33348162+hpstory@users.noreply.github.com> Date: Wed, 4 May 2022 19:59:56 +0800 Subject: [PATCH 57/60] Update spelling mistake --- docs/en/Deployment/Clustered-Environment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md index ac7b425377..cb8f3dfda7 100644 --- a/docs/en/Deployment/Clustered-Environment.md +++ b/docs/en/Deployment/Clustered-Environment.md @@ -63,7 +63,7 @@ The [Database BLOB provider](../Blob-Storing-Database) is the easiest way since > [ABP Commercial](https://commercial.abp.io/) startup solution templates come with the database BLOB provider as pre-installed, and stores BLOBs in the application's database. -Check the [BLOB Storing](../Blob-Storing.md) document to see all available BLOG storage providers. +Check the [BLOB Storing](../Blob-Storing.md) document to see all available BLOB storage providers. ## Configuring Background Jobs From b779f07d8851413d94ddeb8146f17c537777bbd6 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 4 May 2022 20:03:45 +0800 Subject: [PATCH 58/60] Update Connection-Strings.md --- docs/zh-Hans/Connection-Strings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh-Hans/Connection-Strings.md b/docs/zh-Hans/Connection-Strings.md index cf3aed11fa..290cce0436 100644 --- a/docs/zh-Hans/Connection-Strings.md +++ b/docs/zh-Hans/Connection-Strings.md @@ -7,7 +7,7 @@ ABP框架的设计是[模块化](Module-Development-Basics.md), [微服务兼容 它还支持混合场景; -* 允许将模块分组到数据库 (所有的模块分组到一个共享数据库, 两个模块使用数据库A, 3个模块使用数据库B, 一个数据库使用数据库C其余的数据库使用数据库D...等.) +* 允许将模块分组到数据库 (所有的模块分组到一个共享数据库, 两个模块使用数据库A, 三个模块使用数据库B, 一个模块使用数据库C其余的模块使用数据库D...等.) * 允许将租户分组到数据库中,像模块一样. * 允许为每个租户每个模块分离数据库 (数据库过多会增加维护成本,但ABP框架支持这种需求). From f7cbe7a4e9748c719a499712c4d62f7bf41667b7 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Thu, 5 May 2022 08:56:00 +0800 Subject: [PATCH 59/60] Update Clustered-Environment.md --- docs/zh-Hans/Deployment/Clustered-Environment.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zh-Hans/Deployment/Clustered-Environment.md b/docs/zh-Hans/Deployment/Clustered-Environment.md index d98ef86c28..34547709d1 100644 --- a/docs/zh-Hans/Deployment/Clustered-Environment.md +++ b/docs/zh-Hans/Deployment/Clustered-Environment.md @@ -2,9 +2,9 @@ 本文档介绍了在将应用程序部署到**多个应用程序实例同时运行**的集群环境中时应注意的内容, 并解释了如何在基于ABP的应用程序中处理这些内容. -> 无论你使用的是整体式应用程序还是微服务解决方案, 本文档均有效. 适用于一个流程. 应用程序可以是整体式web应用程序、微服务解决方案中的服务、控制台应用程序或其他类型的可执行过程. +> 无论你使用的是单体式应用程序还是微服务解决方案, 本文档均有效. 适用于一个流程. 应用程序可以是单体式web应用程序、微服务解决方案中的服务、控制台应用程序或其他类型的可执行进程. > -> 例如, 要将应用程序部署到Kubernetes并进行配置, 以便应用程序或服务在多个POD中运行, 那时应用程序或服务将在集群环境中运行. +> 例如, 如果你将应用程序部署到Kubernetes并把应用程序或服务在多个POD中运行, 那么应用程序或服务将在集群环境中运行. ## 了解集群环境 From 271c5a923d2668708f75970935ca592f6b5e585e Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Thu, 5 May 2022 13:08:45 +0800 Subject: [PATCH 60/60] Make method DeleteManyAsync virtual --- .../Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs index fae0709953..cc6c4d42df 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs @@ -70,7 +70,7 @@ public class MongoDbContextEventOutbox : IMongoDbContextEventOu } [UnitOfWork] - public async Task DeleteManyAsync(IEnumerable ids) + public virtual async Task DeleteManyAsync(IEnumerable ids) { var dbContext = (IHasEventOutbox)await MongoDbContextProvider.GetDbContextAsync(); if (dbContext.SessionHandle != null)