示例#1
0
        public Task <AsyncTaskResult <CommandAddResult> > AddAsync(HandledCommand handledCommand)
        {
            var record = ConvertTo(handledCommand);

            return(_ioHelper.TryIOFuncAsync <AsyncTaskResult <CommandAddResult> >(async() =>
            {
                try
                {
                    using (var connection = GetConnection())
                    {
                        await connection.InsertAsync(record, _commandTable);
                        return new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.Success, null, CommandAddResult.Success);
                    }
                }
                catch (SqlException ex)
                {
                    if (ex.Number == 2627 && ex.Message.Contains(_primaryKeyName))
                    {
                        return new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.Success, null, CommandAddResult.DuplicateCommand);
                    }
                    return new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.IOException, ex.Message, CommandAddResult.Failed);
                }
                catch (Exception ex)
                {
                    return new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.IOException, ex.Message, CommandAddResult.Failed);
                }
            }, "AddCommandAsync"));
        }
        public CommandAddResult Add(HandledCommand handledCommand)
        {
            var record = ConvertTo(handledCommand);

            return _ioHelper.TryIOFunc(() =>
            {
                using (var connection = GetConnection())
                {
                    try
                    {
                        connection.Insert(record, _commandTable);
                        return CommandAddResult.Success;
                    }
                    catch (SqlException ex)
                    {
                        if (ex.Number == 2627)
                        {
                            if (ex.Message.Contains(_primaryKeyName))
                            {
                                return CommandAddResult.DuplicateCommand;
                            }
                        }
                        throw;
                    }
                }
            }, "AddCommand");
        }
        private void CommitChangesAsync(ProcessingCommand processingCommand, IApplicationMessage message, int retryTimes)
        {
            var command        = processingCommand.Message;
            var handledCommand = new HandledCommand(
                command,
                aggregateRootId: command.AggregateRootId,
                message: message);

            _ioHelper.TryAsyncActionRecursively <AsyncTaskResult <CommandAddResult> >("AddCommandAsync",
                                                                                      () => _commandStore.AddAsync(handledCommand),
                                                                                      currentRetryTimes => CommitChangesAsync(processingCommand, message, currentRetryTimes),
                                                                                      result =>
            {
                var commandAddResult = result.Data;
                if (commandAddResult == CommandAddResult.Success)
                {
                    PublishMessageAsync(processingCommand, message, 0);
                }
                else if (commandAddResult == CommandAddResult.DuplicateCommand)
                {
                    HandleDuplicatedCommandAsync(processingCommand, 0);
                }
                else
                {
                    NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, "Add command async failed.");
                }
            },
                                                                                      () => string.Format("[handledCommand:{0}]", handledCommand),
                                                                                      () => NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, "Add command async failed."),
                                                                                      retryTimes);
        }
        public Task <AsyncTaskResult <CommandAddResult> > AddAsync(HandledCommand handledCommand)
        {
            var record = ConvertTo(handledCommand);

            return(_ioHelper.TryIOFuncAsync(async() =>
            {
                try
                {
                    using (var connection = GetConnection())
                    {
                        await connection.InsertAsync(record, _tableName);
                        return new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.Success, null, CommandAddResult.Success);
                    }
                }
                catch (SqlException ex)
                {
                    if (ex.Number == 2601 && ex.Message.Contains(_uniqueIndexName))
                    {
                        return new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.Success, null, CommandAddResult.DuplicateCommand);
                    }
                    _logger.Error(string.Format("Add handled command has sql exception, handledCommand: {0}", handledCommand), ex);
                    throw;
                }
                catch (Exception ex)
                {
                    _logger.Error(string.Format("Add handled command has unkown exception, handledCommand: {0}", handledCommand), ex);
                    throw;
                }
            }, "AddCommandAsync"));
        }
示例#5
0
        public Task<AsyncTaskResult<CommandAddResult>> AddAsync(HandledCommand handledCommand)
        {
            var record = ConvertTo(handledCommand);

            return _ioHelper.TryIOFuncAsync<AsyncTaskResult<CommandAddResult>>(async () =>
            {
                try
                {
                    using (var connection = GetConnection())
                    {
                        await connection.InsertAsync(record, _commandTable);
                        return new AsyncTaskResult<CommandAddResult>(AsyncTaskStatus.Success, null, CommandAddResult.Success);
                    }
                }
                catch (SqlException ex)
                {
                    if (ex.Number == 2627 && ex.Message.Contains(_primaryKeyName))
                    {
                        return new AsyncTaskResult<CommandAddResult>(AsyncTaskStatus.Success, null, CommandAddResult.DuplicateCommand);
                    }
                    _logger.Error(string.Format("Add handled command has sql exception, handledCommand: {0}", handledCommand), ex);
                    return new AsyncTaskResult<CommandAddResult>(AsyncTaskStatus.IOException, ex.Message, CommandAddResult.Failed);
                }
                catch (Exception ex)
                {
                    _logger.Error(string.Format("Add handled command has unkown exception, handledCommand: {0}", handledCommand), ex);
                    return new AsyncTaskResult<CommandAddResult>(AsyncTaskStatus.Failed, ex.Message, CommandAddResult.Failed);
                }
            }, "AddCommandAsync");
        }
示例#6
0
        public CommandAddResult Add(HandledCommand handledCommand)
        {
            var record = ConvertTo(handledCommand);

            return(_ioHelper.TryIOFunc(() =>
            {
                using (var connection = GetConnection())
                {
                    try
                    {
                        connection.Insert(record, _commandTable);
                        return CommandAddResult.Success;
                    }
                    catch (SqlException ex)
                    {
                        if (ex.Number == 2627)
                        {
                            if (ex.Message.Contains(_primaryKeyName))
                            {
                                return CommandAddResult.DuplicateCommand;
                            }
                        }
                        throw;
                    }
                }
            }, "AddCommand"));
        }
示例#7
0
        private void CommitChangesAsync(ProcessingCommand processingCommand, IApplicationMessage message, int retryTimes)
        {
            var command        = processingCommand.Message;
            var handledCommand = new HandledCommand(command.Id, command.AggregateRootId, message);

            _ioHelper.TryAsyncActionRecursively <AsyncTaskResult <CommandAddResult> >("AddCommandAsync",
                                                                                      () => _commandStore.AddAsync(handledCommand),
                                                                                      currentRetryTimes => CommitChangesAsync(processingCommand, message, currentRetryTimes),
                                                                                      result =>
            {
                var commandAddResult = result.Data;
                if (commandAddResult == CommandAddResult.Success)
                {
                    PublishMessageAsync(processingCommand, message, 0);
                }
                else if (commandAddResult == CommandAddResult.DuplicateCommand)
                {
                    HandleDuplicatedCommandAsync(processingCommand, 0);
                }
                else
                {
                    _logger.ErrorFormat("Add command async failed, commandType:{0}, commandId:{1}, aggregateRootId:{2}", command.GetType().Name, command.Id, command.AggregateRootId);
                    NotifyCommandExecuted(processingCommand, CommandStatus.Failed, typeof(string).FullName, "Add command async failed.");
                }
            },
                                                                                      () => string.Format("[handledCommand:{0}]", handledCommand),
                                                                                      errorMessage => NotifyCommandExecuted(processingCommand, CommandStatus.Failed, typeof(string).Name, errorMessage ?? "Add command async failed."),
                                                                                      retryTimes);
        }
示例#8
0
        public Task <AsyncTaskResult <CommandAddResult> > AddAsync(HandledCommand handledCommand)
        {
            var record = ConvertTo(handledCommand);

            return(_ioHelper.TryIOFuncAsync <AsyncTaskResult <CommandAddResult> >(async() =>
            {
                try
                {
                    using (var connection = GetConnection())
                    {
                        await connection.InsertToMySqlAsync(record, _tableName);
                        return new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.Success, null, CommandAddResult.Success);
                    }
                }
                catch (DbException ex)
                {
                    //if (ex.Number == 2627 && ex.Message.Contains(_primaryKeyName))
                    //{
                    //    return new AsyncTaskResult<CommandAddResult>(AsyncTaskStatus.Success, null, CommandAddResult.DuplicateCommand);
                    //}
                    _logger.Error(string.Format("Add handled command has sql exception, handledCommand: {0}", handledCommand), ex);
                    return new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.IOException, ex.Message, CommandAddResult.Failed);
                }
                catch (Exception ex)
                {
                    _logger.Error(string.Format("Add handled command has unkown exception, handledCommand: {0}", handledCommand), ex);
                    return new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.Failed, ex.Message, CommandAddResult.Failed);
                }
            }, "AddCommandAsync"));
        }
        public Task <AsyncTaskResult <CommandAddResult> > AddAsync(HandledCommand handledCommand)
        {
            var record = ConvertTo(handledCommand);

            return(_ioHelper.TryIOFuncAsync(async() =>
            {
                try
                {
                    using (var connection = GetConnection())
                    {
                        string sql = string.Format(
                            "INSERT INTO `{0}` (CommandId,CreatedOn,AggregateRootId,MessagePayload,MessageTypeName) VALUES (@CommandId,@CreatedOn,@AggregateRootId,@MessagePayload,@MessageTypeName)",
                            _tableName);
                        await connection.ExecuteAsync(sql, record);
                        return new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.Success, null,
                                                                      CommandAddResult.Success);
                    }
                }
                catch (MySqlException ex)
                {
                    if (ex.Number == 1062 && ex.Message.Contains(_uniqueIndexName))
                    {
                        return new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.Success, null, CommandAddResult.DuplicateCommand);
                    }
                    _logger.Error(ex.InnerException);
                    _logger.Error(string.Format("Add handled command has sql exception, handledCommand: {0}", handledCommand), ex);
                    throw;
                }
                catch (Exception ex)
                {
                    _logger.Error(string.Format("Add handled command has unkown exception, handledCommand: {0}", handledCommand), ex);
                    throw;
                }
            }, "AddCommandAsync"));
        }
示例#10
0
 private CommandAddResult Add(HandledCommand handledCommand)
 {
     if (_handledCommandDict.TryAdd(handledCommand.CommandId, handledCommand))
     {
         return(CommandAddResult.Success);
     }
     return(CommandAddResult.DuplicateCommand);
 }
 public CommandAddResult Add(HandledCommand handledCommand)
 {
     if (_handledCommandDict.TryAdd(handledCommand.Command.Id, handledCommand))
     {
         return CommandAddResult.Success;
     }
     return CommandAddResult.DuplicateCommand;
 }
示例#12
0
 private CommandRecord ConvertTo(HandledCommand handledCommand)
 {
     return(new CommandRecord
     {
         CommandId = handledCommand.CommandId,
         AggregateRootId = handledCommand.AggregateRootId,
         MessagePayload = handledCommand.Message != null?_jsonSerializer.Serialize(handledCommand.Message) : null,
                              MessageTypeName = handledCommand.Message != null?_typeNameProvider.GetTypeName(handledCommand.Message.GetType()) : null,
                                                    CreatedOn = DateTime.Now,
     });
 }
示例#13
0
 private CommandRecord ConvertTo(HandledCommand handledCommand)
 {
     return(new CommandRecord
     {
         CommandId = handledCommand.Command.Id,
         CommandTypeCode = _typeCodeProvider.GetTypeCode(handledCommand.Command.GetType()),
         AggregateRootId = handledCommand.AggregateRootId,
         AggregateRootTypeCode = handledCommand.AggregateRootTypeCode,
         Timestamp = DateTime.Now,
         Payload = _jsonSerializer.Serialize(handledCommand.Command),
         Message = handledCommand.Message != null?_jsonSerializer.Serialize(handledCommand.Message) : null,
                       MessageTypeCode = handledCommand.Message != null?_typeCodeProvider.GetTypeCode(handledCommand.Message.GetType()) : 0,
     });
 }
        private void CommitAggregateChanges(ProcessingCommand processingCommand)
        {
            var command = processingCommand.Message;
            var context = processingCommand.CommandExecuteContext;
            var trackedAggregateRoots     = context.GetTrackedAggregateRoots();
            var dirtyAggregateRootChanges = trackedAggregateRoots.ToDictionary(x => x, x => x.GetChanges()).Where(x => x.Value.Any());
            var dirtyAggregateRootCount   = dirtyAggregateRootChanges.Count();

            //如果当前command没有对任何聚合根做修改,则认为当前command已经处理结束,返回command的结果为NothingChanged
            if (dirtyAggregateRootCount == 0)
            {
                _logger.DebugFormat("No aggregate created or modified by command. commandType:{0}, commandId:{1}", command.GetType().Name, command.Id);
                NotifyCommandExecuted(processingCommand, CommandStatus.NothingChanged, null, null);
                return;
            }
            //如果被创建或修改的聚合根多于一个,则认为当前command处理失败,一个command只能创建或修改一个聚合根;
            else if (dirtyAggregateRootCount > 1)
            {
                var dirtyAggregateTypes = string.Join("|", dirtyAggregateRootChanges.Select(x => x.Key.GetType().Name));
                var errorMessage        = string.Format("Detected more than one aggregate created or modified by command. commandType:{0}, commandId:{1}, dirty aggregate types:{2}",
                                                        command.GetType().Name,
                                                        command.Id,
                                                        dirtyAggregateTypes);
                _logger.ErrorFormat(errorMessage);
                NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, errorMessage);
                return;
            }

            //获取当前被修改的聚合根
            var dirtyAggregateRootChange = dirtyAggregateRootChanges.Single();
            var dirtyAggregateRoot       = dirtyAggregateRootChange.Key;
            var changedEvents            = dirtyAggregateRootChange.Value;

            //构造出一个事件流对象
            var eventStream = BuildDomainEventStream(dirtyAggregateRoot, changedEvents, processingCommand);

            //如果当前command处于并发冲突的重试中,则直接提交该command产生的事件,因为该command肯定已经在command store中了
            if (processingCommand.ConcurrentRetriedCount > 0)
            {
                _eventService.CommitDomainEventAsync(new EventCommittingContext(dirtyAggregateRoot, eventStream, processingCommand));
                return;
            }

            var handledAggregateCommand = new HandledCommand(
                command,
                eventStream.AggregateRootId,
                eventStream.AggregateRootTypeCode);

            CommitAggregateChangesAsync(processingCommand, dirtyAggregateRoot, eventStream, handledAggregateCommand, 0);
        }
        private void CommitChangesAsync(ProcessingCommand processingCommand, bool success, IApplicationMessage message, string errorMessage, int retryTimes)
        {
            var command        = processingCommand.Message;
            var handledCommand = new HandledCommand(command.Id, command.AggregateRootId, message);

            _ioHelper.TryAsyncActionRecursively("AddCommandAsync",
                                                () => _commandStore.AddAsync(handledCommand),
                                                currentRetryTimes => CommitChangesAsync(processingCommand, success, message, errorMessage, currentRetryTimes),
                                                result =>
            {
                var commandAddResult = result.Data;
                if (commandAddResult == CommandAddResult.Success)
                {
                    if (success)
                    {
                        if (message != null)
                        {
                            PublishMessageAsync(processingCommand, message, 0);
                        }
                        else
                        {
                            CompleteCommand(processingCommand, CommandStatus.Success, null, null);
                        }
                    }
                    else
                    {
                        CompleteCommand(processingCommand, CommandStatus.Failed, typeof(string).FullName, errorMessage);
                    }
                }
                else if (commandAddResult == CommandAddResult.DuplicateCommand)
                {
                    HandleDuplicatedCommandAsync(processingCommand, 0);
                }
            },
                                                () => string.Format("[handledCommand:{0}]", handledCommand),
                                                error =>
            {
                _logger.Fatal(string.Format("Add command has unknown exception, the code should not be run to here, errorMessage: {0}", error));
            },
                                                retryTimes, true);
        }
示例#16
0
        public Task <AsyncTaskResult <CommandAddResult> > AddAsync(HandledCommand handledCommand)
        {
            if (_currentFailedCount < _expectAddFailedCount)
            {
                _currentFailedCount++;

                if (_failedType == FailedType.UnKnownException)
                {
                    throw new Exception("AddCommandAsyncUnKnownException" + _currentFailedCount);
                }
                else if (_failedType == FailedType.IOException)
                {
                    throw new IOException("AddCommandAsyncIOException" + _currentFailedCount);
                }
                else if (_failedType == FailedType.TaskIOException)
                {
                    return(Task.FromResult(new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.Failed, "AddCommandAsyncError" + _currentFailedCount)));
                }
            }
            return(Task.FromResult(new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.Success, null, Add(handledCommand))));
        }
示例#17
0
 public Task <AsyncTaskResult <CommandAddResult> > AddAsync(HandledCommand handledCommand)
 {
     return(Task.FromResult <AsyncTaskResult <CommandAddResult> >(new AsyncTaskResult <CommandAddResult>(AsyncTaskStatus.Success, null, Add(handledCommand))));
 }
        private void TryToRetryCommandForExceptionAsync(ProcessingCommand processingCommand, HandledCommand existingHandledCommand, ICommandHandlerProxy commandHandler, Exception exception, int retryTimes)
        {
            var command = processingCommand.Message;

            //到这里,说明当前command执行遇到异常,然后该command在commandStore中存在,
            //但是在eventStore中不存在,此时可以理解为该command还未被成功执行,此时做如下操作:
            //1.将command从commandStore中移除
            //2.根据eventStore里的事件刷新缓存,目的是为了还原聚合根到最新状态,因为该聚合根的状态有可能已经被污染
            //3.重试该command
            _ioHelper.TryAsyncActionRecursively <AsyncTaskResult>("RemoveCommandAsync",
                                                                  () => _commandStore.RemoveAsync(command.Id),
                                                                  currentRetryTimes => TryToRetryCommandForExceptionAsync(processingCommand, existingHandledCommand, commandHandler, exception, currentRetryTimes),
                                                                  result =>
            {
                _memoryCache.RefreshAggregateFromEventStore(existingHandledCommand.AggregateRootTypeCode, existingHandledCommand.AggregateRootId);
                RetryCommand(processingCommand);
            },
                                                                  () => string.Format("[commandId:{0}]", command.Id),
                                                                  () => NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, "Remove command async failed."),
                                                                  retryTimes);
        }
        private void HandleExistingHandledCommandForExceptionAsync(ProcessingCommand processingCommand, HandledCommand existingHandledCommand, ICommandHandlerProxy commandHandler, Exception exception, int retryTimes)
        {
            var command         = processingCommand.Message;
            var aggregateRootId = existingHandledCommand.AggregateRootId;

            _ioHelper.TryAsyncActionRecursively <AsyncTaskResult <DomainEventStream> >("FindEventStreamByCommandIdAsync",
                                                                                       () => _eventStore.FindAsync(aggregateRootId, command.Id),
                                                                                       currentRetryTimes => HandleExistingHandledCommandForExceptionAsync(processingCommand, existingHandledCommand, commandHandler, exception, currentRetryTimes),
                                                                                       result =>
            {
                var existingEventStream = result.Data;
                if (existingEventStream != null)
                {
                    _eventService.PublishDomainEventAsync(processingCommand, existingEventStream);
                }
                else
                {
                    LogCommandExecuteException(processingCommand, commandHandler, exception);
                    TryToRetryCommandForExceptionAsync(processingCommand, existingHandledCommand, commandHandler, exception, 0);
                }
            },
                                                                                       () => string.Format("[aggregateRootId:{0}, commandId:{1}]", aggregateRootId, command.Id),
                                                                                       () => NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, "Find event stream by command id async failed."),
                                                                                       retryTimes);
        }
        private void HandleExistingHandledAggregateAsync(ProcessingCommand processingCommand, IAggregateRoot dirtyAggregateRoot, DomainEventStream eventStream, HandledCommand existingHandledCommand, int retryTimes)
        {
            var command = processingCommand.Message;

            _ioHelper.TryAsyncActionRecursively <AsyncTaskResult <DomainEventStream> >("FindEventStreamByCommandIdAsync",
                                                                                       () => _eventStore.FindAsync(existingHandledCommand.AggregateRootId, command.Id),
                                                                                       currentRetryTimes => HandleExistingHandledAggregateAsync(processingCommand, dirtyAggregateRoot, eventStream, existingHandledCommand, currentRetryTimes),
                                                                                       result =>
            {
                var existingEventStream = result.Data;
                if (existingEventStream != null)
                {
                    //如果当前command已经被持久化过了,且该command产生的事件也已经被持久化了,则只要再做一遍发布事件的操作
                    _eventService.PublishDomainEventAsync(processingCommand, existingEventStream);
                }
                else
                {
                    //如果当前command已经被持久化过了,但事件没有被持久化,则需要重新提交当前command所产生的事件;
                    _eventService.CommitDomainEventAsync(new EventCommittingContext(dirtyAggregateRoot, eventStream, processingCommand));
                }
            },
                                                                                       () => string.Format("[aggregateRootId:{0}, commandId:{1}]", existingHandledCommand.AggregateRootId, command.Id),
                                                                                       () => NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, "Find event stream by command id async failed."),
                                                                                       retryTimes);
        }
 private void CommitAggregateChangesAsync(ProcessingCommand processingCommand, IAggregateRoot dirtyAggregateRoot, DomainEventStream eventStream, HandledCommand handledCommand, int retryTimes)
 {
     _ioHelper.TryAsyncActionRecursively <AsyncTaskResult <CommandAddResult> >("AddCommandAsync",
                                                                               () => _commandStore.AddAsync(handledCommand),
                                                                               currentRetryTimes => CommitAggregateChangesAsync(processingCommand, dirtyAggregateRoot, eventStream, handledCommand, currentRetryTimes),
                                                                               result =>
     {
         var commandAddResult = result.Data;
         if (commandAddResult == CommandAddResult.Success)
         {
             _eventService.CommitDomainEventAsync(new EventCommittingContext(dirtyAggregateRoot, eventStream, processingCommand));
         }
         else if (commandAddResult == CommandAddResult.DuplicateCommand)
         {
             HandleAggregateDuplicatedCommandAsync(processingCommand, dirtyAggregateRoot, eventStream, 0);
         }
         else
         {
             NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, "Add command async failed.");
         }
     },
                                                                               () => string.Format("[handledCommand:{0}]", handledCommand),
                                                                               () => NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, "Add command async failed."),
                                                                               retryTimes);
 }
 public Task<AsyncTaskResult<CommandAddResult>> AddAsync(HandledCommand handledCommand)
 {
     return Task.FromResult<AsyncTaskResult<CommandAddResult>>(new AsyncTaskResult<CommandAddResult>(AsyncTaskStatus.Success, null, Add(handledCommand)));
 }
示例#23
0
 private CommandRecord ConvertTo(HandledCommand handledCommand)
 {
     return new CommandRecord
     {
         CommandId = handledCommand.CommandId,
         AggregateRootId = handledCommand.AggregateRootId,
         MessagePayload = handledCommand.Message != null ? _jsonSerializer.Serialize(handledCommand.Message) : null,
         MessageTypeName = handledCommand.Message != null ? _typeNameProvider.GetTypeName(handledCommand.Message.GetType()) : null,
         CreatedOn = DateTime.Now,
     };
 }
示例#24
0
 private CommandRecord ConvertTo(HandledCommand handledCommand)
 {
     return new CommandRecord
     {
         CommandId = handledCommand.CommandId,
         AggregateRootId = handledCommand.AggregateRootId,
         Message = handledCommand.Message != null ? _jsonSerializer.Serialize(handledCommand.Message) : null,
         MessageTypeCode = handledCommand.Message != null ? _typeCodeProvider.GetTypeCode(handledCommand.Message.GetType()) : 0,
         Timestamp = DateTime.Now,
     };
 }