Pull to refresh
1
0
Алексей @AleksejMsk

Программирую от души за деньги

Send message

Какой бред.

Играют для фанатов и получить приятное приключение.

И выполнять миссии и прочее получая перки - доя этого никаких манипуляций не надо .

Игрок сам для этого пришел в игру.

манипуляции были до момента оплаты игроком денег - далее только приключение по желанию игрока .

Какие все глупые .

Надо не 2 сети делать а 3 которая будет контролить первых 2 х если они вдруг налажают

Мы пошли дальше.

Сделали шаблон сервера с полным фаршем что нужен серверам.

Логирование, телеметрия, DDD, тесты и прочее…

https://github.com/KlestovAlexej/Wattle3.DemoServer

Поделюсь опытом реализации хранилища ключей для идемпотентных операций.
Тут полный пример как хранить в БД и в памяти 1.000.000.000 (миллиард) ключей и запускать сервер за 17 секунд с полной загрузкой ключей в память.
Всё сделано на C# как готовый компонент.
https://github.com/KlestovAlexej/Wattle3.Examples?tab=readme-ov-file#пример-старта-реестра-на-1000000000-уникальных-ключей-1712-секунд

Реестр уникальных ключей реализует :

  • Хранение ключей в оперативной памяти и в БД.

  • Ключи могут быть ассоциированы с произвольными данными, тоже хранимыми в памяти.

  • Поиск ключа или данных по ключу происходит только в оперативной памяти и не задействует БД.

  • Параллельная регистрация одного и того же уникального ключа не приводит к задвоениям или рассинхронизациям с БД.

  • Есть сборка мусора - устаревшие ключи и данные автоматически удаляются из оперативной памяти по критерию заданным программистом.

  • Адаптирован для применения партиционирования PostgreSQL таблицы ключей для изятия устаревших ключей из памяти и БД.

  • Хранение ключей и данных в оперативной памяти оптимизировано и не нагружает сборщик мусора.

  • Ключи и данные в оперативной памяти занимают места O(Число ключей * (размер ключа + размер данных)) и не расходуют память на служебную информацию CLR.

  • Регистрация нового ключа задействует БД только в момент подтверждения Unit of Work.

  • Регистрация нового ключа отменяется автоматически в аварийных ситуациях работы Unit of Work.

У меня на C# создания QR и запроса статуса оплаы.
https://github.com/KlestovAlexej/SbpPoint.Examples/blob/f09e5a64ee6b2a85b1fb5199de704f8085c1a7b3/Gateway/Examples.cs#L315

        // Создание платежа.
        var payment = await client.PaymentsAutomationDynamicQrsCreateAsync(
            ApiKey,
            new AutomationDynamicQrCreate
            {
                Amount = 1000,
                AutoCancelMinutes = 5,
                Purpose = "Тест (10 рублей)"
            });

        ShowQrImage(payment.Link);

        // Ждём завершения платежа - примерно 5 минут.
        WaitHelpers.TimeOut(
            () => client.PaymentsAutomationDynamicQrsReadAsync(ApiKey, payment.Id).SafeGetResult().Status.IsFinal,
            TimeSpan.FromMinutes(10));

        // Запрос статуса платежа.
        payment = await client.PaymentsAutomationDynamicQrsReadAsync(ApiKey, payment.Id);
        Assert.IsNotNull(payment);
        Console.WriteLine(payment.ToJsonText(true));
        Assert.IsTrue(payment.Status.IsSuccess);
        Assert.IsFalse(payment.HasRefunds);
        Assert.AreEqual(0, payment.TotalRefundedAmount);

Вставлю свои 5 копеек, но в терминах C# на примере моей демки https://github.com/KlestovAlexej/Wattle3.DemoServer

При создании UnitOfWork необязательно начинать транзакцию БД сразу и в лоб.
Так как даже работа с доменными объектами может не потребовать БД до самого момента комита UnitOfWork - и соединение к БД не будет держаться без дела паразитно.
Всё зависит от реализации UnitOfWork и той инфраструктуры на которой он живёт.

На C# я могу хоть триллион IF сделать.
Шаг №1 - Генерирую DLL на N штук IF - загружаю в память и использую
...
Шfг №M - Генерирую DLL на N штук IF - загружаю в память и использую

В итоге N * F операторов IF.

Вуаля !

Если о кэше - есть ключевые точки - обновление, удаление, выборка.
Они причины движений в кэше.
Это помимо протухания элемента кэша по TTL и есть еще принудительное автоматическое удаление элемента из кэша при подозрении на рассинхрон с БД.

Моя библиотека только в комплексе даёт профит.
Просто голый кэш мапера это недостаточно для вкусной жизни ПО.

Помимо мапера в доменной области есть стратегия решения конфликтной ситуации при комите в БД - система автоматически даёт программисту уведомление что комит успешен (данные в БД) или не успешен (данные не в БД) - даже если данные в БД попали, а их потом физически удалил.
Причём где бы не упал код до комита или даже в момент самого комита transaction.Commit().
В демо сервер есть примеры https://github.com/KlestovAlexej/Wattle3.DemoServer/blob/c9074eb2604efae8c8f8926ec843806193547651/src/DemoServer.Processing.Model/DomainObjects/DemoObjectX/DomainObjectDemoObjectX.cs#L226.
Система автоматически понимает - следит на транзакциями БД отслеживает их делает сверки с БД (у каждой БД свои маперы которые знают специфику).
Всё из коробки.

Да моя библиотека (C# https://github.com/KlestovAlexej/Wattle3.Examples) генерирует CRUD но и много сопутствующих фичей полезных на практике.

В генерированом коде реализовано понятие сокрытии записи, оптимистическая конкуренция при обновлении записи.

Помимо CRUD создаётся код с типичными запросами:

  • Выборка по альтернативному ключу (ключам).

  • Выборка по полю (полям) группировки.

  • Создание WHERE специфичного для БД (SQL Server и PostgreSQL) по запросу собраному в C# или из текста.

  • Постраничная выборка (с и без WHERE).

Все генерируемые мапперы поддерживают кэширования при выборке объектов по первичному ключу.
По мониторингу боевых серверов - попадания к кэш стремится с 99%, что сильно разгружает сеть и БД.
Рассинхрон кэша и БД решается автоматически.

Реализовано создания изначений первичных ключей на основе последовательностей БД с минимальной нагрузкой на саму БД.

Что-то подобное для продакшен использовании я на C# сделал.
Идёт генерация мапперов с полным CRUD на ADO.NET.
Поддерживается SQL Server и PostgreSQL.
Демонстрационный сервер тут https://github.com/KlestovAlexej/Wattle3.DemoServer

К примеру метод маппера GetAsync :

https://github.com/KlestovAlexej/Wattle3.DemoServer/blob/31bd12fe5e68af449b3e88f8e9c6e3b2597815b2/src/DemoServer.Processing.DataAccess.Postgresql/Generated/ShtrihM.Wattle3.CodeGeneration.Generator.Implements/ShtrihM.Wattle3.CodeGeneration.Generator.Implements.SourceGenerator/DbMappers.Implements.PostgreSql.Generated.cs#L1304

    /// <summary>
    /// Получить запись с указаным идентити.
    /// </summary>
    /// <param name="mappersSession">Сессия БД.</param>
    /// <param name="id">Идентити записи.</param>
    /// <param name="cancellationToken">Токен отмены.</param>
    /// <returns>Возвращает значение если запись существует иначе возвращает <see langword="null" /> если запись не существует.</returns>
    [SuppressMessage("ReSharper", "UseObjectOrCollectionInitializer")]
    [SuppressMessage("ReSharper", "ConvertIfStatementToConditionalTernaryExpression")]
    [SuppressMessage("ReSharper", "RedundantCast")]
    public virtual async ValueTask<IMapperDto> GetAsync(IMappersSession mappersSession, long id, CancellationToken cancellationToken = default)
    {
        if (mappersSession == null)
        {
            throw new ArgumentNullException(nameof(mappersSession));
        }

        try
        {
            var typedSession = (IPostgreSqlMappersSession)mappersSession;

            // ReSharper disable once ConvertToUsingDeclaration
            var command = await typedSession.CreateCommandAsync(false, cancellationToken).ConfigureAwait(false);
            await using (command.ConfigureAwait(false))
            {
                command.CommandType = CommandType.Text;
                command.CommandText = @"SELECT

Id
FROM ChangeTracker WHERE
(Id = @Id)";

                {
                    var parameter = new NpgsqlParameter<long>("@Id", NpgsqlDbType.Bigint) { TypedValue = id };
                    command.Parameters.Add(parameter);
                }

                var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancellationToken).ConfigureAwait(false);
                await using (reader.ConfigureAwait(false))
                {
                    if (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
                    {
                        GetColumnIndexes(
                            reader,
                            out var indexId);

                        var result = Read(
                            reader,
                            indexId);

                        return ((IMapperDto)result);
                    }
                }

                return ((IMapperDto)null);
            }
        }
        catch (Exception exception)
        {
            CatchExceptionOnGet(mappersSession, exception, id);
            CatchException(mappersSession, exception);

            var targetException = await m_exceptionPolicy.ApplyAsync(exception, cancellationToken).ConfigureAwait(false);
            if (ReferenceEquals(targetException, exception))
            {
                ExceptionDispatchInfo.Capture(exception).Throw();
            }

            throw targetException;
        }


Information

Rating
5,388-th
Location
Москва, Москва и Московская обл., Россия
Registered
Activity

Specialization

Backend Developer, Software Architect
Lead