Как стать автором
Обновить
0
0

Пользователь

Отправить сообщение

Любой современный язык программирования имеет функцию форматирования числа в строку по заданному шаблону. Хранить и работать с номером нужно в нормализованном виде, а отображать в том, в каком привык пользователь конкретного региона. Очень плохая практика, когда вариант хранения диктуется UI.

Итоговый нормализованный номер будет выглядеть так: +123456789012
Теперь его можно сохранить в виде строки.

Если убрать +, то можно хранить в виде числа int64, что уменьшит размер и значительно упростит поиск.

Из разряда «дешево и сердито» я бы взял Beelink на процессоре N100. Можно до 20 т.р. найти.

установить это значение по-умолчанию как AsNoTracking

Если в коде есть разделение ответственности на команды и запросы, то можно выделить 2 интерфейса для контекста:

public interface IDbReadOnly : IDisposable, IAsyncDisposable
{
  IQueryable<Order> Orders { get; }
  IQueryable<Product> Products { get; }
  // ...

  IQueryable<T> Query<T>() where T : class, IModel;
}

public interface IDb : IDbReadOnly
{
  void Add<T>(T entry) where T : class, IModel;
  void Remove<T>(T entry) where T : class, IModel;
  void Attach<T>(T entry) where T : class, IModel;

  Task SaveAsync(CancellationToken cancellationToken = default);
}

public class AppDb : DbContext, IDb
{
  // ...
}

Соответственно, запросы используют IDbReadOnly, для которого на уровне DI контейнера включено AsNoTracking.

Очередная статья в духе «Нужно делать так, как нужно. А как не нужно, делать не нужно!»

По пунктам «Недостаточное покрытие» и «Переизбыток тестов» – уже набил оскомину этот пример с калькулятором. Для тестирования 1 строчки столько усилий. Как мне кажется, если вы не разрабатываете для космического агентства, где цена ошибки - миллиарды, то такие примитивные вещи вообще не нужно тестировать. Несколько хороших программистов и налаженная процедура приемки пул-реквестов значительно ускорят разработку. Иначе до релиза вы просто не дойдете.

Использовать генератор данных для тестов – то же опасная штука, т.к. если тесты начнут падать случайным образом, то будет больно.

«Нетестируемый код» - пример «хорошей» реализации от автора подразумевает, что у нас есть IoC-контейнер, что не всегда так. Ответственность за получение экземпляра  логгера передается на более высокий уровень, что повлечет за собой дополнительный рефакторинг кода. В принципе, в данном примере достаточно было бы сделать 2 конструктора – 1) без параметров, который создаем экземпляр ILogger по умолчанию; 2) для тестов, который принимает мок экземпляра ILogger.

«Тестирование реализации» - написание тестов после
написания кода тоже хорошая практика, т.к. она фиксирует поведение юнита (далеко
не всегда у нас четко оговорены все условия изначально, и часто, по мере
разработки, они детализируются). Кроме того, такая фиксация поведения позволяет
безопасно сделать рефакторинг внутренней реализации, ведь далеко не всегда с
первого раза все бывает оптимально.

Промахнулся, это был ответ на сообщение @Oceanshiver ".. вообще большая загадка как MediatR стал хоть сколько-то используемым .."

В заключение статьи хорошо отмечено, что «MediatR не является реализацией шаблона Посредник, а является внутренней шиной». Как мне кажется, это основной плюс данной библиотеки. Это позволяет отделить в тестах специфику web от бизнес-логики.

Так, если замокать ISender и создать клиента с помощью WebApplicationFactory, то мы можем легко протестировать, что http-запрос корректно преобразовался в Request, который отправился во внутреннюю шину и полученный из нее Response преобразован в ожидаемый http-ответ.

Бизнес-логику, реализованную в хэндлерах (как слой юзкейсов над сервисами), можно тестировать классическими unit-тестами.

Но, автор MediatR попытался превратить библиотеку в «комбайн», использование которого и вызывают такие холивары. Я предпочитаю свою light-реализацию данного функционала.

Реализация на интерфейсах – хорошая практика, если у вас небольшое приложение. Но, если у вашего Web API 100+ методов, то подход MediatR-а позволяет получить слабосвязанный код без нагромождения десятков интерфейсов.

Ну, если у вас простейшая задача, то только поддерживаю… 

Но в статье целью была заявлено:

... написать консольное приложение без использования IHost, но при этом иметь удобства IoC, поддержку конфигурационных файлов и переменных окружающей среды ...

Это обычно требуется в сложных приложениях, где зависимости прописывается в  IoC через extension methods или иным способом (как, например, модули autofac) и проще использовать готовое решение.

Под .net 6 и выше эти задачи реализуются весьма просто.

Краткий пример
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;

// Setup
// ---------------------

var configuration = new ConfigurationBuilder()
  .AddJsonFile("appsettings.json", false, false)
  .AddJsonFile("appsettings.Development.json", true, false)
  .Build();

Log.Logger = new LoggerConfiguration()
  .WriteTo.Console()
  .CreateLogger();

var services = new ServiceCollection();

services.AddLogging(l => l.ClearProviders().AddSerilog());
services.AddSingleton<IConfiguration>(configuration);
services.AddTransient<WorkerService>();

var provider = services.BuildServiceProvider();

// Runtime
// ---------------------

await provider.GetRequiredService<WorkerService>()
  .ExecuteAsync();

// Types
// ---------------------

public class WorkerService
{
  private readonly IConfiguration _cfg;
  private readonly ILogger<WorkerService> _logger;

  public WorkerService(ILogger<WorkerService> logger, IConfiguration cfg)
  {
    _logger = logger;
    _cfg = cfg;
  }

  public Task ExecuteAsync()
  {
    _logger.LogInformation("Hi!");

    return Task.CompletedTask;
  }
}

Удивительно, про AutoMocker не знал. Использовал подобный самописный «велосипед».

А по поводу использования контейнера зависимостей в unit-тестах – всегда был противником такого решения. DI-контейнер – это инфраструктурная вещь, которая реализуется на уровне платформы/framework-а приложения, но не бизнес-логики. В тесте сервиса стоит четко описать требуемые зависимости. Если зависимостей много, то стоит задуматься над рефакторингом для разделения ответственности. Использование контейнера и костыля в виде вызова RegisterEverything() делает тест хрупким.

Из-за попытки прокинуть контейнер зависимостей в тесты возникает и проблема, которая описана в разделе «Тестирование уровня проекта». Если уйти от этого, то будет достаточно сделать unit-тесты для реализации конкретных сервисов + единственный тест на проект для статического метода ContainerConfig.RegisterDomainServices(), проверяющий, что все реализуемые проектом интерфейсы добавлены в контейнер.

Если Вы имеете в виду Алису от Яндекса, то тут сплошное разочарование. Достаточно пяти вопросов, что бы понять, что на бэкэнде она триггерится на ключевые слова и о поддержке контексте беседы даже речи не идет.

А читая такие новости про ChatGPT иногда ловлю себя на мысли, я не являемся ли мы свидетелями рождения ИИ… (полноценного, а не того что под этим сейчас маркетологи понимают)

Я тоже не использую AutoMapper предпочитая явное неявному. Но пример автора, как мне кажется, неудачный, стоило упростить код, оставив только то, что относиться к заявленной теме.

Было бы достаточно следующего:

public class Projection<TSource, TResult>
{
    private readonly Lazy<Func<TSource, TResult>> _lazyDelegate;

    public Projection(Expression<Func<TSource, TResult>> expression)
    {
        Expression = expression;
        _lazyDelegate = new Lazy<Func<TSource, TResult>>(Expression.Compile, LazyThreadSafetyMode.PublicationOnly);
    }

    internal Expression<Func<TSource, TResult>> Expression { get; }

    internal Func<TSource, TResult> Delegate => _lazyDelegate.Value;

    public TResult Map(TSource source) => Delegate(source);
}

public static class ProjectionExtensions
{
    public static IQueryable<TDestination> Projection<TSource, TDestination>(
      this IQueryable<TSource> queryable, Projection<TSource, TDestination> projection)
    {
        return queryable.Select(projection.Expression);
    }

    public static IEnumerable<TDestination> Projection<TSource, TDestination>(
      this IEnumerable<TSource> enumerable, Projection<TSource, TDestination> projection)
    {
        return enumerable.Select(projection.Delegate);
    }
}

Полный код с рабочим примером:
https://gist.github.com/Zerg903/007967724a9d37f19856b083a1b6bf6e

Полностью согласен с @VanKrock. Мне кажется, что хэндлер нужно воспринимать как некий Transaction Script, который вызывается в одном конкретном месте.

Если логика простая и не периспользуемая, то пишем ее в самом хэндлере (делать одноразовый сервис доменной логики как-то оверхэд).

Если же появляется повторяющаяся задача, то выносим ее в отдельный сервис бизнесс-логики. Вроде бы по классике все…

Да, с record код стал проще, но насчет "сравнивать" - есть нюансы:

private record Dto(string Name, List<int> Values);

public static void Main()
{
  var dto1 = new Dto("name", new List<int>() { 1 });
  var dto2 = new Dto("name", new List<int>() { 1 });

  Console.WriteLine(dto1 == dto2); // false !!!
}

Чуда нет… и зная .net понимаешь, почему так происходит (для ссылочных типов, если не переопределен метод Equals(), то сравнение делается по ReferenceEquals()). Но все же хотелось бы получить иной результат, раз уж везде пишут о том, как удобно сравнивать record-ы.

Я решил это покупкой smart-приставки к TV. Plex стоит на Raspberry Pi 4, в настройках отключено транскодирование и файл отдатается как есть. Современым приставкам вполне хватает мощности адаптировать видео под требуемый формат, главное что бы роутер справлялся с потоком.

Нет, не пробовал. Мне кажется, что не должно быть заметной разницы от OS.


Больше зависимость от типа файловой системы. Наблюдал, что драйверы NTFS при копировании отнимали немалую часть ресурсов процессора.

M.2 SSD, подключённый по USB3 — 33,94 Мб/с

Какие-то маленькие скорости у вас получается. Проделали ли вы рекомендуемые манипуляции?


У меня RPi 4 используется как файловый сервер (SMB), торрент качалка (Transmission), и медиа-сервер (Plex Server). К USB3 подключен старенький HDD WD Passport 1Tb.


Стояла Raspberry Pi OS (x32) и WD Passport отформатированный в NTFS. На самом устройстве скорость чтения не замерял, но по SMB скорость копирования была в районе 35 Мб/с.


Скорость меня не устраивала, и недавно я решил поставить Ubuntu Server 20.04 (x64) + отформатировать WD Passport в EXT4.


В результате скорость копирований по SMB стала 85-100 Мб/с. Правда, это на чистом HDD, По мере его заполнения скорость начала падать. Сейчас при наполненности на ~50% скорость в районе 65 Мб/с. Но, думаю, это уже виноват не USB3, а сам HDD.


Думал подключить SSD, но пока цены на >1Tb высоковаты. Про CM4 – узнал от вас. Наверное, когда появится, тогда и продолжу эксперименты.

Как мне кажется, в данном случае нужно не плодить методы getProducts***(), а использовать классический шаблон Спецификация.


Не сказал бы, что он полностью избавит от возможной ошибки, но при грамотном подходе значительно снизит вероятность ее появления.

Утверждать, что исключение это плохо – не правильно. Все зависит от контекста.

Например: при реализации Web API если по userId не найден пользователь и нужно просто отдать в ответ http-код 404. В таком варианте выброс UserNotFoundException и перехват его в фильтре с преобразованием в HttpNotFoundResult — будет самым правильным вариантом. Вам не придется пробрасывать результат через весь колстек.

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

Просто выбрано неудачное название Mayby… Обычно такую реализацию я делаю в виде дженерика OperationResult.
Так я и не утверждаю, что автор открыл что-то новое. Решение является композицией известных шаблонов проектирования Proxy и Chain of Responsibility.

Мой пост был о том, что на слова автора «смотрите как можно решить задачу», он словил минусов с пожеланиями пересмотра архитектуры всего приложения (на мой взгляд, для замены IoC-конетйнера или внедрение AOP на уже работающем в проде приложении должно быть очень веское обоснование). Добрее надо быть…
1

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность