День 2429. #ЗаметкиНаПолях
Управляем Временем Жизни DbContext. Начало
DbContext — это сердце EF Core, но его легко использовать неправильно.
Основные правила:
- DbContext представляет собой единицу работы и должен существовать недолго.
- Он не является потокобезопасным; никогда не используйте один экземпляр совместно для параллельных операций.
- В ASP.NET Core стандартным и, как правило, правильным выбором является DbContext с ограниченной (scoped) областью действия для каждого запроса. Для задач вне области действия запроса (фоновые сервисы, одиночные объекты, UI-приложения) используйте фабрику для создания новых контекстов по требованию.
Рассмотрим, как правильно подключать DbContext, когда выбирать каждый вид регистрации и каких ловушек следует избегать.
Виды регистрации
1. AddDbContext (Scoped – по умолчанию, для каждого запроса)
Когда использовать: контроллеры MVC/минимальные API, Razor Pages, хабы SignalR — всё, что находится внутри веб-запроса.
Почему: Вы автоматически получаете отдельный контекст на каждый запрос; EF Core эффективно обрабатывает использование подключений.
2. AddDbContextFactory (Transient - фабрика для контекстов по требованию)
Использование:
Когда использовать:
- Фоновые сервисы (IHostedService, BackgroundService),
- Любые синглтон-сервисы, которым требуется DbContext,
- Десктопные/Blazor-приложения, где требуется свежий контекст для каждой операции.
Почему: Фабрики создают чистые, кратковременные контексты, не полагаясь на внешние области видимости.
3. AddDbContextPool (Scoped с пулингом)
Когда использовать: Высокопроизводительные API, когда конфигурация контекста стабильна и не сохраняет состояние.
Зачем: Повторное использование экземпляров DbContext из пула для снижения затрат на выделение ресурсов.
Внимание: не сохраняйте состояние запросов в контексте; экземпляры в пуле сбрасываются и используются повторно.
Замечания по производительности
- Пулинг уменьшает выделение памяти при высокой нагрузке; оцените свою рабочую нагрузку.
- Проверки потокобезопасности: EF Core может обнаруживать некоторые злоупотребления многопоточностью; вы можете отключить проверки для повышения производительности, но только если вы абсолютно уверены, что в одном контексте нет параллельных запросов. Обычно отключать проверки не рекомендуется.
Окончание следует…
Источник: https://thecodeman.net/posts/managing-ef-core-dbcontext-lifetime
Управляем Временем Жизни DbContext. Начало
DbContext — это сердце EF Core, но его легко использовать неправильно.
Основные правила:
- DbContext представляет собой единицу работы и должен существовать недолго.
- Он не является потокобезопасным; никогда не используйте один экземпляр совместно для параллельных операций.
- В ASP.NET Core стандартным и, как правило, правильным выбором является DbContext с ограниченной (scoped) областью действия для каждого запроса. Для задач вне области действия запроса (фоновые сервисы, одиночные объекты, UI-приложения) используйте фабрику для создания новых контекстов по требованию.
Рассмотрим, как правильно подключать DbContext, когда выбирать каждый вид регистрации и каких ловушек следует избегать.
Виды регистрации
1. AddDbContext (Scoped – по умолчанию, для каждого запроса)
// Program.cs
builder.Services
.AddDbContext<AppDbContext>(o =>
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default")
));
Когда использовать: контроллеры MVC/минимальные API, Razor Pages, хабы SignalR — всё, что находится внутри веб-запроса.
Почему: Вы автоматически получаете отдельный контекст на каждый запрос; EF Core эффективно обрабатывает использование подключений.
2. AddDbContextFactory (Transient - фабрика для контекстов по требованию)
builder.Services
.AddDbContextFactory<AppDbContext>(o =>
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default")
));
Использование:
public sealed class ReportService(
IDbContextFactory<AppDbContext> factory)
{
public async Task<IReadOnlyList<OrderDto>>
GetAsync(CancellationToken ct)
{
await using var db = await
factory.CreateDbContextAsync(ct);
return await db.Orders
.Where(…)
.Select(…)
.ToListAsync(ct);
}
}
Когда использовать:
- Фоновые сервисы (IHostedService, BackgroundService),
- Любые синглтон-сервисы, которым требуется DbContext,
- Десктопные/Blazor-приложения, где требуется свежий контекст для каждой операции.
Почему: Фабрики создают чистые, кратковременные контексты, не полагаясь на внешние области видимости.
3. AddDbContextPool (Scoped с пулингом)
builder.Services
.AddDbContextPool<AppDbContext>(o =>
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default")
));
Когда использовать: Высокопроизводительные API, когда конфигурация контекста стабильна и не сохраняет состояние.
Зачем: Повторное использование экземпляров DbContext из пула для снижения затрат на выделение ресурсов.
Внимание: не сохраняйте состояние запросов в контексте; экземпляры в пуле сбрасываются и используются повторно.
Замечания по производительности
- Пулинг уменьшает выделение памяти при высокой нагрузке; оцените свою рабочую нагрузку.
- Проверки потокобезопасности: EF Core может обнаруживать некоторые злоупотребления многопоточностью; вы можете отключить проверки для повышения производительности, но только если вы абсолютно уверены, что в одном контексте нет параллельных запросов. Обычно отключать проверки не рекомендуется.
Окончание следует…
Источник: https://thecodeman.net/posts/managing-ef-core-dbcontext-lifetime
1👍36
День 2430. #ЗаметкиНаПолях
Управляем Временем Жизни DbContext. Окончание
Начало
Примеры из реального мира
1. Контроллер / Минимальные API (Scoped)
DI даёт вам scoped-контекст, привязанный к запросу. Одна единица работы, никаких утечек:
2. Фоновые сервисы
Фоновые сервисы не имеют области видимости запроса, вы должны создать область видимости:
Либо использовать фабрику:
3. Синглтон-сервис, которому нужна БД
Синглтоны не должны захватывать DbContext. Создание нового при каждом вызове помогает избежать проблем:
4. Высокопроизводительные APIs (пулинг)
Совет: Пулинг улучшает пропускную способность. Он не делает DbContext потокобезопасным. По-прежнему один контекст на запрос.
Распространённые ошибки
1. Совместное использование одного DbContext между потоками
Симптом: "A second operation started on this context before a previous operation was completed." (Вторая операция началась в этом контексте до завершения предыдущей операции).
Решение: один контекст на единицу работы; не выполняйте параллельные запросы в одном и том же контексте.
2. Внедрение DbContext в синглтоны
Решение: внедрение IDbContextFactory или IServiceScopeFactory и создание контекстов по требованию.
3. Долгоживущие контексты
Решение: делайте контексты короткоживущими, иначе раздуваются трекеры изменений и удерживаются соединения.
4. Использование пула с состоянием запросов
Решение: не добавляйте пользовательское состояние к контексту (например, поле CurrentUserId). Контексты в пуле переиспользуются.
5. Попытка «ускорения» путём одновременного выполнения нескольких запросов в одном контексте.
Решение: либо сериализуйте работу, либо создайте несколько контекстов. DbContext не является потокобезопасным.
Итого
Думайте о DbContext как о листке-черновике: вы берёте лист, используете для коротких записей, и выбрасываете его, когда закончите. Не передавайте его по всему офису и не пытайтесь писать на нём двумя ручками одновременно.
Источник: https://thecodeman.net/posts/managing-ef-core-dbcontext-lifetime
Управляем Временем Жизни DbContext. Окончание
Начало
Примеры из реального мира
1. Контроллер / Минимальные API (Scoped)
DI даёт вам scoped-контекст, привязанный к запросу. Одна единица работы, никаких утечек:
app.MapGet("/orders/{id:int}",
async (int id,
AppDbContext db,
CancellationToken ct) =>
{
var order = await db
.Orders.FindAsync([id], ct);
return order is null
? Results.NotFound()
: Results.Ok(order);
});2. Фоновые сервисы
Фоновые сервисы не имеют области видимости запроса, вы должны создать область видимости:
public sealed class CleanupService(
IServiceScopeFactory scopes,
ILogger<CleanupService> log)
: BackgroundService
{
protected override async Task
ExecuteAsync(CancellationToken stop)
{
while (!stop.IsCancellationRequested)
{
using var scope = scopes.CreateScope();
var db = scope
.ServiceProvider
.GetRequiredService<AppDbContext>();
// Одна короткоживущая единица работы
// … используем db …
await db.SaveChangesAsync(stop);
await Task.Delay(TimeSpan.FromHours(6), stop);
}
}
}
Либо использовать фабрику:
public sealed class CleanupService(
IDbContextFactory<AppDbContext> factory,
ILogger<CleanupService> log)
: BackgroundService
{
protected override async Task
ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await using var db = await
factory.CreateDbContextAsync(ct);
// … та же логика, что и выше …
}
}
}
3. Синглтон-сервис, которому нужна БД
Синглтоны не должны захватывать DbContext. Создание нового при каждом вызове помогает избежать проблем:
csharp
public sealed class PricingCache(
IDbContextFactory<AppDbContext> factory,
IMemoryCache cache)
{
public async Task<decimal>
GetPriceAsync(int id, CancellationToken ct)
{
if (cache.TryGetValue(id, out var price))
return price;
await using var db = await
factory.CreateDbContextAsync(ct);
price = await db.Products
.Where(p => p.Id == id)
.Select(p => p.Price)
.FirstAsync(ct);
cache.Set(id, price, TimeSpan.FromMinutes(10));
return price;
}
}
4. Высокопроизводительные APIs (пулинг)
builder.Services
.AddDbContextPool<AppDbContext>(o =>
{
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default"));
// Не используйте состояние в опциях
// Избегайте изменяемого состояния
});
Совет: Пулинг улучшает пропускную способность. Он не делает DbContext потокобезопасным. По-прежнему один контекст на запрос.
Распространённые ошибки
1. Совместное использование одного DbContext между потоками
Симптом: "A second operation started on this context before a previous operation was completed." (Вторая операция началась в этом контексте до завершения предыдущей операции).
Решение: один контекст на единицу работы; не выполняйте параллельные запросы в одном и том же контексте.
2. Внедрение DbContext в синглтоны
Решение: внедрение IDbContextFactory или IServiceScopeFactory и создание контекстов по требованию.
3. Долгоживущие контексты
Решение: делайте контексты короткоживущими, иначе раздуваются трекеры изменений и удерживаются соединения.
4. Использование пула с состоянием запросов
Решение: не добавляйте пользовательское состояние к контексту (например, поле CurrentUserId). Контексты в пуле переиспользуются.
5. Попытка «ускорения» путём одновременного выполнения нескольких запросов в одном контексте.
Решение: либо сериализуйте работу, либо создайте несколько контекстов. DbContext не является потокобезопасным.
Итого
Думайте о DbContext как о листке-черновике: вы берёте лист, используете для коротких записей, и выбрасываете его, когда закончите. Не передавайте его по всему офису и не пытайтесь писать на нём двумя ручками одновременно.
Источник: https://thecodeman.net/posts/managing-ef-core-dbcontext-lifetime
👍27
День 2431. #ЗаметкиНаПолях
Экономим Память, Как Профессионалы. 5 Продвинутых Техник. Начало
Если вы уже какое-то время работаете в мире .NET, то знаете, что эта среда выполнения полна скрытых жемчужин. Некоторые из них отточены и дружелюбны, разработаны для того, чтобы новички чувствовали себя в безопасности. Другие — сырые, но мощные инструменты, которым «не обучают в школе», но они дают вам невероятную мощь, если вы знаете, когда и как их использовать. Разберём 5 малоизвестных API, которые могут помочь вам выжать гораздо больше производительности из кода, интенсивно использующего память. Эти функции имеют реальное значение в высокопроизводительных сервисах, конвейерах с малой задержкой и любых приложениях, которые используют много памяти.
1. CollectionsMarshal.AsSpan() для доступа к списку
Обычно, работая с List<T>, вы представляете его себе как удобную и безопасную абстракцию над массивом. Добавляете данные, проходите по списку. Но что, если вам нужен прямой доступ к базовому массиву без копирования?
Стандартный способ:
ToArray() создаёт полную копию содержимого списка, тем самым удваивая размер используемой памяти.
Способ лучше:
Почему: здесь мы работаем непосредственно с внутренним хранилищем списка. Никакого копирования, никаких лишних выделений памяти. Есть одна проблема: если список изменит размер (например, при добавлении нового элемента), span станет недействительным. Именно поэтому этот API находится в System.Runtime.InteropServices.
Когда использовать: при обработке больших буферов или выполнении коротких циклов. Это избавляет от ненужных копий, снижает нагрузку на сборщик мусора и повышает производительность.
Выигрыш: в 2 раза по памяти.
2. CollectionsMarshal.GetValueRefOrNullRef для доступа к словарю
Словари повсюду в приложениях .NET. И чаще всего мы просто делаем что-то вроде этого:
Выглядит безобидно, но мы ищем ключ дважды: в TryGetValue и в индексаторе.
Способ лучше:
Почему: С помощью GetValueRefOrNullRef вы получаете ссылку прямо на хранилище словаря. Без повторного поиска, без избыточного вычисления хэша и без лишних записей.
Когда использовать: при работе с большими словарями или циклами, критически важными для производительности (например, с таблицей символов компилятора или кэшем), эти ненужные поиски накапливаются. Этот подход быстрее, но, опять же, слово Unsafe разработчиками языка используется не просто так. Обращайтесь с ref’ами осторожно.
Выигрыш: примерно в 2 раза.
См. также Альтернативный Доступ к Коллекциям в C#13
Окончание следует…
Источник: https://blog.stackademic.com/stop-wasting-memory-5-advanced-net-tricks-nobody-teaches-you-47376c1339e7
Экономим Память, Как Профессионалы. 5 Продвинутых Техник. Начало
Если вы уже какое-то время работаете в мире .NET, то знаете, что эта среда выполнения полна скрытых жемчужин. Некоторые из них отточены и дружелюбны, разработаны для того, чтобы новички чувствовали себя в безопасности. Другие — сырые, но мощные инструменты, которым «не обучают в школе», но они дают вам невероятную мощь, если вы знаете, когда и как их использовать. Разберём 5 малоизвестных API, которые могут помочь вам выжать гораздо больше производительности из кода, интенсивно использующего память. Эти функции имеют реальное значение в высокопроизводительных сервисах, конвейерах с малой задержкой и любых приложениях, которые используют много памяти.
1. CollectionsMarshal.AsSpan() для доступа к списку
Обычно, работая с List<T>, вы представляете его себе как удобную и безопасную абстракцию над массивом. Добавляете данные, проходите по списку. Но что, если вам нужен прямой доступ к базовому массиву без копирования?
Стандартный способ:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Нужен Span? Придётся делать копию
Span<int> span = numbers.ToArray().AsSpan();
// вариант 2, но всё равно копия
Span<int> span1 = [.. numbers];
// … работа со span’омToArray() создаёт полную копию содержимого списка, тем самым удваивая размер используемой памяти.
Способ лучше:
using System.Runtime.InteropServices;
…
// Span напрямую над массивом
Span<int> span = CollectionsMarshal.AsSpan(numbers);
…
Почему: здесь мы работаем непосредственно с внутренним хранилищем списка. Никакого копирования, никаких лишних выделений памяти. Есть одна проблема: если список изменит размер (например, при добавлении нового элемента), span станет недействительным. Именно поэтому этот API находится в System.Runtime.InteropServices.
Когда использовать: при обработке больших буферов или выполнении коротких циклов. Это избавляет от ненужных копий, снижает нагрузку на сборщик мусора и повышает производительность.
Выигрыш: в 2 раза по памяти.
2. CollectionsMarshal.GetValueRefOrNullRef для доступа к словарю
Словари повсюду в приложениях .NET. И чаще всего мы просто делаем что-то вроде этого:
if (dict.TryGetValue("foo", out var value))
{
value++;
dict["foo"] = value;
}Выглядит безобидно, но мы ищем ключ дважды: в TryGetValue и в индексаторе.
Способ лучше:
using System.Runtime.InteropServices;
…
ref int valueRef =
ref CollectionsMarshal
.GetValueRefOrNullRef(dict, "foo");
if (!Unsafe.IsNullRef(ref valueRef))
valueRef++;
Почему: С помощью GetValueRefOrNullRef вы получаете ссылку прямо на хранилище словаря. Без повторного поиска, без избыточного вычисления хэша и без лишних записей.
Когда использовать: при работе с большими словарями или циклами, критически важными для производительности (например, с таблицей символов компилятора или кэшем), эти ненужные поиски накапливаются. Этот подход быстрее, но, опять же, слово Unsafe разработчиками языка используется не просто так. Обращайтесь с ref’ами осторожно.
Выигрыш: примерно в 2 раза.
См. также Альтернативный Доступ к Коллекциям в C#13
Окончание следует…
Источник: https://blog.stackademic.com/stop-wasting-memory-5-advanced-net-tricks-nobody-teaches-you-47376c1339e7
1👍33
День 2432. #ЗаметкиНаПолях
Экономим Память, Как Профессионалы. 5 Продвинутых Техник. Окончание
Начало
3. GC.AllocateUninitializedArray<T> для более быстрой работы с массивами
При создании нового массива в .NET среда выполнения обнуляет его. Обычно это хорошо, предсказуемое поведение, никаких мусорных данных. Но что, если вам не нужно изначальное содержимое, ведь вы всё равно перезапишете каждый элемент? Обнуление — пустая трата времени.
Стандартный способ:
Способ лучше:
Почему: здесь среда выполнения пропускает очистку памяти. Это ускоряет выделение памяти, особенно для больших массивов.
Когда использовать: требуется дисциплина. Если вы читаете массив перед записью, вы получите мусорные значения. Но в таких сценариях, как сериализация, повторное использование буфера или краткие циклы с вычислениями, это может сэкономить время.
Выигрыш: ~15% для 1000 элементов.
4. ArrayPool<T>.Shared и IMemoryOwner
Если вы многократно выделяете большие массивы в цикле, сборщик мусора вас возненавидит. Именно здесь на помощь приходит ArrayPool<T> — общий пул массивов, который можно арендовать и вернуть.
Стандартный способ:
Это приведёт к тысяче аллокаций, которые захламят кучу.
Способ лучше:
Почему: мы арендуем буфер из пула, используем его и возвращаем при уничтожении IMemoryOwner<T>. Никакие выделения памяти не перегружают сборщик мусора.
Когда использовать: особенно актуально в сценариях с высокой пропускной способностью, таких как сетевые серверы, конвейеры или обработка видео. Кроме того, это безопаснее, чем просто арендовать и возвращать массивы, поскольку IMemoryOwner<T> гарантирует корректную очистку.
Выигрыш: более чем в 2 раза быстрее и 1 аллокация вместо 1000.
5. ObjectPool<T> для дорогостоящих объектов
Создание некоторых объектов требует больших затрат. Если вы постоянно создаёте и уничтожаете их, вы тратите не только память, но и ресурсы процессора. Поможет ObjectPool<T>.
Без пулинга:
Это создаст 100 экземпляров StringBuilder. Они будут собраны GC, но зачем создавать столько?
Пулинг:
Почему: мы переиспользуем небольшой пул объектов StringBuilder. Выделение памяти значительно сокращается, и мы избегаем постоянного обращения к GC.
Когда использовать: фреймворки логирования, библиотеки сериализации или любые другие рабочие нагрузки, требующие создания множества временных объектов.
Выигрыш: примерно в 4 раза быстрее и в 50 раз меньше по памяти.
Итого
Если вы когда-нибудь заглядывали в свой профилировщик и задавались вопросом, почему так много времени тратится на сборку мусора или копирование памяти, эти приёмы помогут вам это исправить. Они подходят не для всех ситуаций, но, когда вы гонитесь за реальной производительностью, именно эти инструменты отличают «неплохое» приложение от действительно эффективного.
Источник: https://blog.stackademic.com/stop-wasting-memory-5-advanced-net-tricks-nobody-teaches-you-47376c1339e7
Экономим Память, Как Профессионалы. 5 Продвинутых Техник. Окончание
Начало
3. GC.AllocateUninitializedArray<T> для более быстрой работы с массивами
При создании нового массива в .NET среда выполнения обнуляет его. Обычно это хорошо, предсказуемое поведение, никаких мусорных данных. Но что, если вам не нужно изначальное содержимое, ведь вы всё равно перезапишете каждый элемент? Обнуление — пустая трата времени.
Стандартный способ:
// Автоматически обнуляется
int[] arr = new int[1000];
for (int i = 0; i < arr.Length; i++)
arr[i] = i; // Перезаписываем всё
Способ лучше:
int[] arr = GC.AllocateUninitializedArray<int>(1000);
for (int i = 0; i < arr.Length; i++)
arr[i] = i;
Почему: здесь среда выполнения пропускает очистку памяти. Это ускоряет выделение памяти, особенно для больших массивов.
Когда использовать: требуется дисциплина. Если вы читаете массив перед записью, вы получите мусорные значения. Но в таких сценариях, как сериализация, повторное использование буфера или краткие циклы с вычислениями, это может сэкономить время.
Выигрыш: ~15% для 1000 элементов.
4. ArrayPool<T>.Shared и IMemoryOwner
Если вы многократно выделяете большие массивы в цикле, сборщик мусора вас возненавидит. Именно здесь на помощь приходит ArrayPool<T> — общий пул массивов, который можно арендовать и вернуть.
Стандартный способ:
for (int i = 0; i < 1000; i++)
{
// аллокации каждый раз
var buffer = new byte[1024];
DoSomething(buffer);
}
Это приведёт к тысяче аллокаций, которые захламят кучу.
Способ лучше:
using System.Buffers;
for (int i = 0; i < 1000; i++)
{
using IMemoryOwner<byte> owner =
MemoryPool<byte>.Shared.Rent(1024);
var memory = owner.Memory;
DoSomething(memory.Span);
}
Почему: мы арендуем буфер из пула, используем его и возвращаем при уничтожении IMemoryOwner<T>. Никакие выделения памяти не перегружают сборщик мусора.
Когда использовать: особенно актуально в сценариях с высокой пропускной способностью, таких как сетевые серверы, конвейеры или обработка видео. Кроме того, это безопаснее, чем просто арендовать и возвращать массивы, поскольку IMemoryOwner<T> гарантирует корректную очистку.
Выигрыш: более чем в 2 раза быстрее и 1 аллокация вместо 1000.
5. ObjectPool<T> для дорогостоящих объектов
Создание некоторых объектов требует больших затрат. Если вы постоянно создаёте и уничтожаете их, вы тратите не только память, но и ресурсы процессора. Поможет ObjectPool<T>.
Без пулинга:
for (int i = 0; i < 100; i++)
{
var sb = new StringBuilder(1024);
sb.Append("Hello ").Append(i);
Console.WriteLine(sb.ToString());
}
Это создаст 100 экземпляров StringBuilder. Они будут собраны GC, но зачем создавать столько?
Пулинг:
using Microsoft.Extensions.ObjectPool;
var provider = new DefaultObjectPoolProvider();
var pool = provider.CreateStringBuilderPool();
for (int i = 0; i < 100; i++)
{
var sb = pool.Get();
sb.Clear();
sb.Append("Hello ").Append(i);
Console.WriteLine(sb.ToString());
pool.Return(sb);
}
Почему: мы переиспользуем небольшой пул объектов StringBuilder. Выделение памяти значительно сокращается, и мы избегаем постоянного обращения к GC.
Когда использовать: фреймворки логирования, библиотеки сериализации или любые другие рабочие нагрузки, требующие создания множества временных объектов.
Выигрыш: примерно в 4 раза быстрее и в 50 раз меньше по памяти.
Итого
Если вы когда-нибудь заглядывали в свой профилировщик и задавались вопросом, почему так много времени тратится на сборку мусора или копирование памяти, эти приёмы помогут вам это исправить. Они подходят не для всех ситуаций, но, когда вы гонитесь за реальной производительностью, именно эти инструменты отличают «неплохое» приложение от действительно эффективного.
Источник: https://blog.stackademic.com/stop-wasting-memory-5-advanced-net-tricks-nobody-teaches-you-47376c1339e7
👍48
День 2433. #SystemDesign101 #Microservices
Как Выглядит Типичная Микросервисная Архитектура?
Балансировщик нагрузки: устройство или приложение, которое распределяет сетевой или прикладной трафик между несколькими серверами.
CDN (Сеть Доставки Контента): группа географически распределённых серверов, которые обеспечивают быструю доставку статического и динамического контента. С CDN пользователям не нужно загружать контент (музыку, видео, файлы, изображения и т. д.) с исходного сервера. Вместо этого контент кэшируется на узлах CDN по всему миру, и пользователи могут загружать его с ближайших узлов CDN.
API-шлюз: обрабатывает входящие запросы и направляет их соответствующим сервисам. Он взаимодействует с провайдером идентификации и выполняет обнаружение сервисов. См. подробнее.
Провайдер идентификации: отвечает за аутентификацию и авторизацию пользователей.
Регистрация и обнаружение сервисов (Service Registry и Service Discovery): Service Registry — это база данных, которая хранит информацию о сервисах и их экземплярах, а Service Discovery — это механизм, использующий этот реестр для автоматического обнаружения, регистрации и отслеживания доступности сервисов в распределенной системе, что особенно важно для микросервисных архитектур, где сервисы могут динамически масштабироваться. API-шлюз ищет соответствующие сервисы в этом компоненте для взаимодействия с ними.
Менеджмент: этот компонент отвечает за мониторинг сервисов.
Микросервисы: микросервисы разрабатываются и развёртываются в разных доменах. Каждый домен имеет свою собственную базу данных. API-шлюз взаимодействует с микросервисами через REST API или другие протоколы, а микросервисы в пределах одного домена взаимодействуют друг с другом с помощью RPC (удалённого вызова процедур).
Преимущества микросервисов
- Их можно быстро проектировать, развёртывать и горизонтально масштабировать.
- Каждый домен может независимо поддерживаться выделенной командой.
- Бизнес-требования можно настраивать в каждом домене, что обеспечивает лучшую поддержку.
Источник: https://blog.bytebytego.com
Как Выглядит Типичная Микросервисная Архитектура?
Балансировщик нагрузки: устройство или приложение, которое распределяет сетевой или прикладной трафик между несколькими серверами.
CDN (Сеть Доставки Контента): группа географически распределённых серверов, которые обеспечивают быструю доставку статического и динамического контента. С CDN пользователям не нужно загружать контент (музыку, видео, файлы, изображения и т. д.) с исходного сервера. Вместо этого контент кэшируется на узлах CDN по всему миру, и пользователи могут загружать его с ближайших узлов CDN.
API-шлюз: обрабатывает входящие запросы и направляет их соответствующим сервисам. Он взаимодействует с провайдером идентификации и выполняет обнаружение сервисов. См. подробнее.
Провайдер идентификации: отвечает за аутентификацию и авторизацию пользователей.
Регистрация и обнаружение сервисов (Service Registry и Service Discovery): Service Registry — это база данных, которая хранит информацию о сервисах и их экземплярах, а Service Discovery — это механизм, использующий этот реестр для автоматического обнаружения, регистрации и отслеживания доступности сервисов в распределенной системе, что особенно важно для микросервисных архитектур, где сервисы могут динамически масштабироваться. API-шлюз ищет соответствующие сервисы в этом компоненте для взаимодействия с ними.
Менеджмент: этот компонент отвечает за мониторинг сервисов.
Микросервисы: микросервисы разрабатываются и развёртываются в разных доменах. Каждый домен имеет свою собственную базу данных. API-шлюз взаимодействует с микросервисами через REST API или другие протоколы, а микросервисы в пределах одного домена взаимодействуют друг с другом с помощью RPC (удалённого вызова процедур).
Преимущества микросервисов
- Их можно быстро проектировать, развёртывать и горизонтально масштабировать.
- Каждый домен может независимо поддерживаться выделенной командой.
- Бизнес-требования можно настраивать в каждом домене, что обеспечивает лучшую поддержку.
Источник: https://blog.bytebytego.com
👍8
День 2434. #ЧтоНовенького
Срок Поддержки STS Релизов Продлён до 24 месяцев
Microsoft объявили о продлении срока поддержки релизов .NET Standard Term Support с 18 до 24 месяцев. Изменение политики, вступающее в силу с .NET 9, поддержка которого продлена до 10 ноября 2026 года, совпадает с датой окончания поддержки .NET 8, версии с долгосрочной поддержкой (Long Term Support, LTS).
Как заявили в Microsoft, компания будет придерживаться установленного ежегодного графика выпуска релизов каждый ноябрь. Чётные релизы продолжат получать статус долгосрочной поддержки на 3 года, в то время как нечётные релизы обозначены как версии со стандартной поддержкой (STS). Ранее релизы STS получали обновления в течение 18 месяцев, и прекращали поддерживаться через 6 месяцев после выпуска следующей версии.
Основной причиной этого изменения названы проблемы управления зависимостями. Для таких релизов, как .NET Aspire, Microsoft.Extensions.AI и C# Dev Kit, иногда требуются обновлённые версии пакетов из более новых ежегодных выпусков. Эта ситуация создавала сложности для компаний, придерживающихся политики «только-LTS», поскольку они могли непреднамеренно включить компоненты STS при установке этих релизов, что потенциально сокращало сроки их поддержки. Т.е., когда организации, использующие LTS-релизы, устанавливали компоненты, требующие более новых версий пакетов, они непреднамеренно переводили части своей среды выполнения из статуса LTS в статус STS. Продлённый период поддержки решает эту проблему, обеспечивая поддержку пакетов из .NET 9 до той же даты, что и компонентов из .NET 8.
Что касается отзывов сообщества разработчиков, они выявили различные точки зрения на объявление. Некоторые отметили, что продлённый период поддержки повышает жизнеспособность релизов STS в производственных средах. Например, это изменение затрудняет обоснование отказа от запуска новых проектов на последней версии, независимо от её статуса STS или LTS.
Другие предложили альтернативные решения. Несколько разработчиков предложили Microsoft рассмотреть возможность продления цикла выпуска до 18 месяцев вместо ежегодного. Как отметил один из комментаторов, зрелость фреймворка снизила влияние новых функций, сделав более длинные циклы выпуска более приемлемыми для команд разработчиков.
Изменение политики особенно выгодно компаниям со строгими требованиями к развертыванию «только-LTS», одновременно способствуя внедрению новых возможностей .NET. Как заявили в Microsoft, организациям, планирующим миграцию с .NET 9 на .NET 10, следует продолжить придерживаться текущего графика обновлений, поскольку новые релизы обеспечивают повышение производительности и дополнительные функции.
Создатели библиотек продолжат поддерживать несколько версий одновременно в рамках новой политики. Расширенный жизненный цикл STS может упростить планирование совместимости версий для таких разработчиков. Как сообщалось, это изменение также помогает изменить мнение некоторых разработчиков, которые ошибочно воспринимали выпуски STS как предварительные версии или бета-версии, несмотря на их статус готовности к производству.
Изменённая модель поддержки вступила в силу немедленно с релизом .NET 9. Microsoft подтвердили, что сохранят существующее трёхлетнее обязательство по поддержке для всех выпусков с долгосрочной поддержкой.
Источник: https://www.infoq.com/news/2025/09/microsoft-extends-dotnet-sts/
Срок Поддержки STS Релизов Продлён до 24 месяцев
Microsoft объявили о продлении срока поддержки релизов .NET Standard Term Support с 18 до 24 месяцев. Изменение политики, вступающее в силу с .NET 9, поддержка которого продлена до 10 ноября 2026 года, совпадает с датой окончания поддержки .NET 8, версии с долгосрочной поддержкой (Long Term Support, LTS).
Как заявили в Microsoft, компания будет придерживаться установленного ежегодного графика выпуска релизов каждый ноябрь. Чётные релизы продолжат получать статус долгосрочной поддержки на 3 года, в то время как нечётные релизы обозначены как версии со стандартной поддержкой (STS). Ранее релизы STS получали обновления в течение 18 месяцев, и прекращали поддерживаться через 6 месяцев после выпуска следующей версии.
Основной причиной этого изменения названы проблемы управления зависимостями. Для таких релизов, как .NET Aspire, Microsoft.Extensions.AI и C# Dev Kit, иногда требуются обновлённые версии пакетов из более новых ежегодных выпусков. Эта ситуация создавала сложности для компаний, придерживающихся политики «только-LTS», поскольку они могли непреднамеренно включить компоненты STS при установке этих релизов, что потенциально сокращало сроки их поддержки. Т.е., когда организации, использующие LTS-релизы, устанавливали компоненты, требующие более новых версий пакетов, они непреднамеренно переводили части своей среды выполнения из статуса LTS в статус STS. Продлённый период поддержки решает эту проблему, обеспечивая поддержку пакетов из .NET 9 до той же даты, что и компонентов из .NET 8.
Что касается отзывов сообщества разработчиков, они выявили различные точки зрения на объявление. Некоторые отметили, что продлённый период поддержки повышает жизнеспособность релизов STS в производственных средах. Например, это изменение затрудняет обоснование отказа от запуска новых проектов на последней версии, независимо от её статуса STS или LTS.
Другие предложили альтернативные решения. Несколько разработчиков предложили Microsoft рассмотреть возможность продления цикла выпуска до 18 месяцев вместо ежегодного. Как отметил один из комментаторов, зрелость фреймворка снизила влияние новых функций, сделав более длинные циклы выпуска более приемлемыми для команд разработчиков.
Изменение политики особенно выгодно компаниям со строгими требованиями к развертыванию «только-LTS», одновременно способствуя внедрению новых возможностей .NET. Как заявили в Microsoft, организациям, планирующим миграцию с .NET 9 на .NET 10, следует продолжить придерживаться текущего графика обновлений, поскольку новые релизы обеспечивают повышение производительности и дополнительные функции.
Создатели библиотек продолжат поддерживать несколько версий одновременно в рамках новой политики. Расширенный жизненный цикл STS может упростить планирование совместимости версий для таких разработчиков. Как сообщалось, это изменение также помогает изменить мнение некоторых разработчиков, которые ошибочно воспринимали выпуски STS как предварительные версии или бета-версии, несмотря на их статус готовности к производству.
Изменённая модель поддержки вступила в силу немедленно с релизом .NET 9. Microsoft подтвердили, что сохранят существующее трёхлетнее обязательство по поддержке для всех выпусков с долгосрочной поддержкой.
Источник: https://www.infoq.com/news/2025/09/microsoft-extends-dotnet-sts/
👍11
День 2435. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Начало
LLM, такие как GhatGPT и Claude, изменили наше взаимодействие с компьютерами. Однако они сталкиваются с фундаментальными ограничениями, которые не позволяют им быть немедленно полезными во многих бизнес-контекстах.
Некоторые ограничения:
- Знания LLM часто заморожены на момент окончания обучения. Если мы спросим GPT-4 о событиях, произошедших после сбора обучающих данных, он не узнает об этом, если не подключится к Интернету для сбора информации.
- LLM не имеют доступа к закрытым данным компании. Когда сотрудники задают вопросы о политике компании, а клиенты интересуются конкретными продуктами, стандартный LLM может давать только общие ответы, основанные на общих закономерностях, которые он, возможно, изучил из общедоступных интернет-данных.
- LLM страдают от галлюцинаций (генерируют правдоподобно звучащую, но неверную информацию). Они могут уверенно приводить выдуманные факты, придумывать цитаты или создавать вымышленные ситуации.
- Даже если у LLM есть соответствующие знания, их ответы могут быть скорее общими, чем конкретными для требуемого контекста.
RAG решает эти проблемы, предоставляя ИИ доступ к конкретным документам и данным.
Что это?
По сути, RAG (Retrieval Augmented Generation) — это метод, объединяющий два различных процесса в одну систему:
- Извлечение релевантной информации из коллекции документов.
- Создание точного ответа на основе этой информации.
Представьте, что вы заходите в библиотеку и задаёте библиотекарю конкретный вопрос о местном налоговом кодексе. Обычный библиотекарь может поделиться общими знаниями о налогах, но библиотекарь, имеющий доступ к конкретным налоговым документам города, может подойти к нужной полке, вытащить соответствующее руководство, прочитать нужный раздел и дать точный ответ, основанный на этих официальных документах. Так работает RAG.
Когда мы спрашиваем стандартную LLM, например, о политике компании в отношении отпусков, она может ответить общей информацией о типичных правилах, с которыми она ознакомилась во время обучения, вроде: «Многие компании предлагают 2 раза по 2 недели оплачиваемого отпуска», потому что это распространённая практика. При использовании RAG система сначала извлекает справочник, находит раздел о политике отпусков, а затем генерирует ответ на основе этого документа. Ответ будет следующим: «Согласно справочнику «…», штатные сотрудники могут планировать отпуска по своему усмотрению, но не более 28 оплачиваемых дней отпуска в год».
Упрощённый принцип работы RAG показан на рисунке ниже.
RAG более полезен, чем стандартный LLM в следующих случаях:
1. Когда сценарий использования включает в себя часто меняющуюся информацию, например, данные о товарных запасах, ценах или новостях.
2. При работе с личной или конфиденциальной информацией, которая не была частью обучающих данных модели, например, внутренней документацией, записями клиентов или конфиденциальными исследованиями.
3. Когда точность критически важна, а галлюцинации недопустимы, например, в юридических, медицинских или финансовых приложениях.
4. Когда важно предоставить ссылки или доказать источник информации. Система может указать на конкретные документы и отрывки, обеспечивая прозрачность и контролируемость, которые невозможны при использовании стандартных ответов LLM.
5. Когда приложению необходимо обрабатывать большие коллекции документов, которые было бы непрактично включать в каждое сообщение.
С другой стороны, RAG не нужен для вопросов общего знания, с которыми LLM и так может справиться достаточно хорошо, например, объяснение общих концепций, выполнение базовых рассуждений или задания по творческому письму.
Продолжение следует…
Источник: https://substack.com/home/post/p-174052561
Как RAG Позволяет Использовать ИИ для Ваших Данных. Начало
LLM, такие как GhatGPT и Claude, изменили наше взаимодействие с компьютерами. Однако они сталкиваются с фундаментальными ограничениями, которые не позволяют им быть немедленно полезными во многих бизнес-контекстах.
Некоторые ограничения:
- Знания LLM часто заморожены на момент окончания обучения. Если мы спросим GPT-4 о событиях, произошедших после сбора обучающих данных, он не узнает об этом, если не подключится к Интернету для сбора информации.
- LLM не имеют доступа к закрытым данным компании. Когда сотрудники задают вопросы о политике компании, а клиенты интересуются конкретными продуктами, стандартный LLM может давать только общие ответы, основанные на общих закономерностях, которые он, возможно, изучил из общедоступных интернет-данных.
- LLM страдают от галлюцинаций (генерируют правдоподобно звучащую, но неверную информацию). Они могут уверенно приводить выдуманные факты, придумывать цитаты или создавать вымышленные ситуации.
- Даже если у LLM есть соответствующие знания, их ответы могут быть скорее общими, чем конкретными для требуемого контекста.
RAG решает эти проблемы, предоставляя ИИ доступ к конкретным документам и данным.
Что это?
По сути, RAG (Retrieval Augmented Generation) — это метод, объединяющий два различных процесса в одну систему:
- Извлечение релевантной информации из коллекции документов.
- Создание точного ответа на основе этой информации.
Представьте, что вы заходите в библиотеку и задаёте библиотекарю конкретный вопрос о местном налоговом кодексе. Обычный библиотекарь может поделиться общими знаниями о налогах, но библиотекарь, имеющий доступ к конкретным налоговым документам города, может подойти к нужной полке, вытащить соответствующее руководство, прочитать нужный раздел и дать точный ответ, основанный на этих официальных документах. Так работает RAG.
Когда мы спрашиваем стандартную LLM, например, о политике компании в отношении отпусков, она может ответить общей информацией о типичных правилах, с которыми она ознакомилась во время обучения, вроде: «Многие компании предлагают 2 раза по 2 недели оплачиваемого отпуска», потому что это распространённая практика. При использовании RAG система сначала извлекает справочник, находит раздел о политике отпусков, а затем генерирует ответ на основе этого документа. Ответ будет следующим: «Согласно справочнику «…», штатные сотрудники могут планировать отпуска по своему усмотрению, но не более 28 оплачиваемых дней отпуска в год».
Упрощённый принцип работы RAG показан на рисунке ниже.
RAG более полезен, чем стандартный LLM в следующих случаях:
1. Когда сценарий использования включает в себя часто меняющуюся информацию, например, данные о товарных запасах, ценах или новостях.
2. При работе с личной или конфиденциальной информацией, которая не была частью обучающих данных модели, например, внутренней документацией, записями клиентов или конфиденциальными исследованиями.
3. Когда точность критически важна, а галлюцинации недопустимы, например, в юридических, медицинских или финансовых приложениях.
4. Когда важно предоставить ссылки или доказать источник информации. Система может указать на конкретные документы и отрывки, обеспечивая прозрачность и контролируемость, которые невозможны при использовании стандартных ответов LLM.
5. Когда приложению необходимо обрабатывать большие коллекции документов, которые было бы непрактично включать в каждое сообщение.
С другой стороны, RAG не нужен для вопросов общего знания, с которыми LLM и так может справиться достаточно хорошо, например, объяснение общих концепций, выполнение базовых рассуждений или задания по творческому письму.
Продолжение следует…
Источник: https://substack.com/home/post/p-174052561
👍8
День 2436. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Продолжение
Начало
Как работает RAG
Работа проходит в 2 этапа:
1. Подготовка документа - один раз при настройке системы или позже, при добавлении в систему новых документов или источников информации.
2. Обработка запроса - в режиме реального времени каждый раз, когда пользователь задаёт вопрос.
Этот двухэтапный подход эффективен благодаря разделению задач между этапом подготовки документа, требующим больших вычислительных мощностей, и этапом выполнения запроса, чувствительным к задержкам.
1. Подготовка (индексация)
Эта основополагающая работа выполняется до поступления пользовательских запросов и включает несколько важных этапов. См. схему 1 ниже.
1) Сбор и обработка документов. Каждый документ (PDF, Word, веб-страница или запись БД) должен быть преобразован в обычный текст. Процесс обрабатывает различные форматы и гарантирует чёткое отделение фактического содержимого от форматирования и метаданных.
2) Разделение текста на мелкие фрагменты. Документы обычно слишком длинные для обработки как единое целое. Размер фрагментов имеет значение: слишком маленький — они теряют контекст, слишком большой — они становятся менее точными. Большинство систем используют фрагменты по 500–1000 слов, часто с некоторым перекрытием между последовательными фрагментами для сохранения контекста.
3) Фрагменты преобразуются в числовые представления – эмбеддинги - список чисел, отражающих его семантическое значение, например, [0,23, -0,45, 0,67, …] с сотнями или тысячами измерений. Числа кодируют смысл текста так, что это позволяет проводить математические сравнения. Схожие понятия порождают схожие числовые шаблоны, что позволяет системе находить связанный контент даже при использовании разных слов.
4) Эмбеддинги вместе с исходными текстовыми фрагментами и их метаданными сохраняются в векторной БД. Она оптимизирована для поиска похожих векторов и индексирует вложения так, что обеспечивает быстрый поиск сходства среди миллионов фрагментов. Метаданные, хранящиеся вместе с каждым фрагментом, обычно включают исходный документ, номера страниц, временные метки и любую другую релевантную информацию, которая может быть полезна для фильтрации или атрибуции.
2. Обработка запросов
Этот этап должен быть быстрым и эффективным, поскольку пользователи ожидают быстрых ответов. См. схему 2 ниже.
1) Вопрос пользователя попадает в систему и проходит тот же с использованием той же модели эмбеддинга, которая использовалась для обработки документов.
2) Система ищет в векторной БД наиболее похожие фрагменты документов. Этот поиск быстр, т.к. использует математические операции, а не сравнение текстов. Обычно система извлекает от 3 до 10 наиболее релевантных фрагментов.
3) Извлечённые фрагменты подготавливаются для языковой модели. Система объединяет их в контекст, часто ранжируя по релевантности и фильтруя на основе метаданных или бизнес-правил.
4) Языковая модель получает исходный вопрос пользователя и извлеченный контекст. Промпт может содержать:
- Предоставленные документы контекста.
- Конкретный вопрос пользователя.
- Инструкции по ответу на основе предоставленного контекста.
- Рекомендации по обработке информации, отсутствующей в контексте.
5) Языковая модель обрабатывает расширенный запрос и генерирует ответ. Поскольку контекст содержит конкретную, релевантную информацию, ответ может быть точным и подробным. Модель может цитировать непосредственно из извлеченных документов, синтезировать информацию из нескольких фрагментов и предоставлять конкретные ответы, основанные на исходном материале.
6) Ответ часто проходит постобработку: добавление ссылок на исходные документы, форматирование ответа для лучшей читаемости или проверку на соответствие заданному вопросу. Некоторые системы также логируют запрос, извлечённые документы и ответ для аналитики и улучшения.
Продолжение следует…
Источник: https://substack.com/home/post/p-174052561
Как RAG Позволяет Использовать ИИ для Ваших Данных. Продолжение
Начало
Как работает RAG
Работа проходит в 2 этапа:
1. Подготовка документа - один раз при настройке системы или позже, при добавлении в систему новых документов или источников информации.
2. Обработка запроса - в режиме реального времени каждый раз, когда пользователь задаёт вопрос.
Этот двухэтапный подход эффективен благодаря разделению задач между этапом подготовки документа, требующим больших вычислительных мощностей, и этапом выполнения запроса, чувствительным к задержкам.
1. Подготовка (индексация)
Эта основополагающая работа выполняется до поступления пользовательских запросов и включает несколько важных этапов. См. схему 1 ниже.
1) Сбор и обработка документов. Каждый документ (PDF, Word, веб-страница или запись БД) должен быть преобразован в обычный текст. Процесс обрабатывает различные форматы и гарантирует чёткое отделение фактического содержимого от форматирования и метаданных.
2) Разделение текста на мелкие фрагменты. Документы обычно слишком длинные для обработки как единое целое. Размер фрагментов имеет значение: слишком маленький — они теряют контекст, слишком большой — они становятся менее точными. Большинство систем используют фрагменты по 500–1000 слов, часто с некоторым перекрытием между последовательными фрагментами для сохранения контекста.
3) Фрагменты преобразуются в числовые представления – эмбеддинги - список чисел, отражающих его семантическое значение, например, [0,23, -0,45, 0,67, …] с сотнями или тысячами измерений. Числа кодируют смысл текста так, что это позволяет проводить математические сравнения. Схожие понятия порождают схожие числовые шаблоны, что позволяет системе находить связанный контент даже при использовании разных слов.
4) Эмбеддинги вместе с исходными текстовыми фрагментами и их метаданными сохраняются в векторной БД. Она оптимизирована для поиска похожих векторов и индексирует вложения так, что обеспечивает быстрый поиск сходства среди миллионов фрагментов. Метаданные, хранящиеся вместе с каждым фрагментом, обычно включают исходный документ, номера страниц, временные метки и любую другую релевантную информацию, которая может быть полезна для фильтрации или атрибуции.
2. Обработка запросов
Этот этап должен быть быстрым и эффективным, поскольку пользователи ожидают быстрых ответов. См. схему 2 ниже.
1) Вопрос пользователя попадает в систему и проходит тот же с использованием той же модели эмбеддинга, которая использовалась для обработки документов.
2) Система ищет в векторной БД наиболее похожие фрагменты документов. Этот поиск быстр, т.к. использует математические операции, а не сравнение текстов. Обычно система извлекает от 3 до 10 наиболее релевантных фрагментов.
3) Извлечённые фрагменты подготавливаются для языковой модели. Система объединяет их в контекст, часто ранжируя по релевантности и фильтруя на основе метаданных или бизнес-правил.
4) Языковая модель получает исходный вопрос пользователя и извлеченный контекст. Промпт может содержать:
- Предоставленные документы контекста.
- Конкретный вопрос пользователя.
- Инструкции по ответу на основе предоставленного контекста.
- Рекомендации по обработке информации, отсутствующей в контексте.
5) Языковая модель обрабатывает расширенный запрос и генерирует ответ. Поскольку контекст содержит конкретную, релевантную информацию, ответ может быть точным и подробным. Модель может цитировать непосредственно из извлеченных документов, синтезировать информацию из нескольких фрагментов и предоставлять конкретные ответы, основанные на исходном материале.
6) Ответ часто проходит постобработку: добавление ссылок на исходные документы, форматирование ответа для лучшей читаемости или проверку на соответствие заданному вопросу. Некоторые системы также логируют запрос, извлечённые документы и ответ для аналитики и улучшения.
Продолжение следует…
Источник: https://substack.com/home/post/p-174052561
👍11
День 2437. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Продолжение
Что такое RAG
Как работает RAG
Эмбеддинги — сердце RAG
Основная проблема поиска информации в том, что люди выражают одни и те же идеи бесчисленным множеством разных способов. Традиционный поиск по ключевым словам, ищущий точные совпадения слов, не учитывает эти вариации.
Представьте себе разнообразие способов задать вопрос о проблеме с компьютером: «ноутбук не запускается», «компьютер не загружается», «система не включается» или «ПК не работает». В этих фразах практически нет общих слов, но все они описывают одну и ту же проблему. Система, основанная на ключевых словах, воспримет их как совершенно разные запросы и пропустит руководства по устранению неполадок, использующие другую терминологию.
Эмбеддинги решают эту проблему, фиксируя семантическое значение (что текст фактически означает), а не простые совпадения слов. При преобразовании текста в эмбеддинги полученные числа представляют концепции и идеи, содержащиеся в этом тексте. Предложения о проблемах с компьютером в итоге имеют схожие числовые шаблоны, независимо от того, используется ли в них слово «ноутбук», «компьютер» или «ПК».
Процесс преобразования текста в числа:
- Модель эмбеддинга считывает текст и выводит список чисел, обычно сотни или тысячи.
- Эти числа позиционируют текст как точку в многомерном пространстве.
- Точка подобна координатам на карте, но вместо двух измерений (широта и долгота) эмбеддинги используют сотни измерений для передачи множества нюансов смысла.
См. схему ниже.
Это числовое представление позволяет выполнять математические операции. Мы можем вычислить расстояние между двумя эмбеддингами, чтобы оценить степень схожести их значений. Например, тексты о «ремонте ноутбуков» и «починке компьютеров» будут иметь эмбеддинги, расположенные близко друг к другу в этом пространстве, в то время как «ремонт ноутбуков» и «кулинарные рецепты» будут далеко друг от друга. Этот расчёт расстояния выполняется с помощью простых математических вычислений, что делает его чрезвычайно быстрым даже для миллионов документов.
Причина, по которой схожие значения создают схожие числовые закономерности, кроется в том, как обучаются модели эмбеддинга. В процессе обучения модель просматривает миллионы примеров текста и распознаёт, что определённые слова и фразы встречаются в схожих контекстах. Например, «врач» и «терапевт», встречаются в похожих предложениях, используются взаимозаменяемо и относятся к одним и тем же понятиям. Модель учится присваивать им схожие числовые закономерности. Это обучение происходит автоматически благодаря разбору огромных объемов текста, без явного программирования этих взаимосвязей кем-то.
Интересно, что мы не до конца понимаем, что представляет собой каждое измерение. Когда модель встраивания выдаёт 768 чисел для фрагмента текста, мы не можем просто сказать, что измерение 1 представляет «формальность», а измерение 547 — «техническую сложность». Эти измерения возникают естественным образом в процессе обучения, когда модель определяет закономерности, которые ей необходимо отслеживать для эффективного понимания языка. Некоторые измерения могут слабо коррелировать с человеческими понятиями, такими как тональность или тема, а многие отражают абстрактные закономерности, которые не соответствуют ни одному понятию, для которого у нас есть слова.
Модели эмбеддинга и большие языковые модели (LLM) служат разным целям в системе RAG. Модель эмбеддинга небольшая, специализирована для одной задачи: преобразования текста в числа. Модели LLM массивные, предназначены для понимания и генерации текста, похожего на человеческий. В то же время они дорогие. Поэтому системы RAG используют две отдельные модели. Модель эмбеддинга эффективно преобразует все документы и запросы в векторы, обеспечивая быстрый поиск по семантическому сходству. Затем LLM берёт найденные релевантные документы и генерирует интеллектуальные, контекстные ответы.
Окончание следует…
Источник: https://substack.com/home/post/p-174052561
Как RAG Позволяет Использовать ИИ для Ваших Данных. Продолжение
Что такое RAG
Как работает RAG
Эмбеддинги — сердце RAG
Основная проблема поиска информации в том, что люди выражают одни и те же идеи бесчисленным множеством разных способов. Традиционный поиск по ключевым словам, ищущий точные совпадения слов, не учитывает эти вариации.
Представьте себе разнообразие способов задать вопрос о проблеме с компьютером: «ноутбук не запускается», «компьютер не загружается», «система не включается» или «ПК не работает». В этих фразах практически нет общих слов, но все они описывают одну и ту же проблему. Система, основанная на ключевых словах, воспримет их как совершенно разные запросы и пропустит руководства по устранению неполадок, использующие другую терминологию.
Эмбеддинги решают эту проблему, фиксируя семантическое значение (что текст фактически означает), а не простые совпадения слов. При преобразовании текста в эмбеддинги полученные числа представляют концепции и идеи, содержащиеся в этом тексте. Предложения о проблемах с компьютером в итоге имеют схожие числовые шаблоны, независимо от того, используется ли в них слово «ноутбук», «компьютер» или «ПК».
Процесс преобразования текста в числа:
- Модель эмбеддинга считывает текст и выводит список чисел, обычно сотни или тысячи.
- Эти числа позиционируют текст как точку в многомерном пространстве.
- Точка подобна координатам на карте, но вместо двух измерений (широта и долгота) эмбеддинги используют сотни измерений для передачи множества нюансов смысла.
См. схему ниже.
Это числовое представление позволяет выполнять математические операции. Мы можем вычислить расстояние между двумя эмбеддингами, чтобы оценить степень схожести их значений. Например, тексты о «ремонте ноутбуков» и «починке компьютеров» будут иметь эмбеддинги, расположенные близко друг к другу в этом пространстве, в то время как «ремонт ноутбуков» и «кулинарные рецепты» будут далеко друг от друга. Этот расчёт расстояния выполняется с помощью простых математических вычислений, что делает его чрезвычайно быстрым даже для миллионов документов.
Причина, по которой схожие значения создают схожие числовые закономерности, кроется в том, как обучаются модели эмбеддинга. В процессе обучения модель просматривает миллионы примеров текста и распознаёт, что определённые слова и фразы встречаются в схожих контекстах. Например, «врач» и «терапевт», встречаются в похожих предложениях, используются взаимозаменяемо и относятся к одним и тем же понятиям. Модель учится присваивать им схожие числовые закономерности. Это обучение происходит автоматически благодаря разбору огромных объемов текста, без явного программирования этих взаимосвязей кем-то.
Интересно, что мы не до конца понимаем, что представляет собой каждое измерение. Когда модель встраивания выдаёт 768 чисел для фрагмента текста, мы не можем просто сказать, что измерение 1 представляет «формальность», а измерение 547 — «техническую сложность». Эти измерения возникают естественным образом в процессе обучения, когда модель определяет закономерности, которые ей необходимо отслеживать для эффективного понимания языка. Некоторые измерения могут слабо коррелировать с человеческими понятиями, такими как тональность или тема, а многие отражают абстрактные закономерности, которые не соответствуют ни одному понятию, для которого у нас есть слова.
Модели эмбеддинга и большие языковые модели (LLM) служат разным целям в системе RAG. Модель эмбеддинга небольшая, специализирована для одной задачи: преобразования текста в числа. Модели LLM массивные, предназначены для понимания и генерации текста, похожего на человеческий. В то же время они дорогие. Поэтому системы RAG используют две отдельные модели. Модель эмбеддинга эффективно преобразует все документы и запросы в векторы, обеспечивая быстрый поиск по семантическому сходству. Затем LLM берёт найденные релевантные документы и генерирует интеллектуальные, контекстные ответы.
Окончание следует…
Источник: https://substack.com/home/post/p-174052561
👍13
День 2438. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Окончание
Что такое RAG
Как работает RAG
Эмбеддинги
Создание RAG-системы
1. Понимание требований.
Разрабатывается ли система для внутренних сотрудников, для которых важна точность и надежность? Или для внешних клиентов, для которых важнее скорость и взаимодействие с UI?
2. Структура документов.
Масштаб имеет значение. Различные объёмы требуют разных стратегий хранения и поиска. Возможные типы контента определяют конвейеры загрузки и предварительной обработки.
3. Шаблоны запросов.
Являются ли большинство запросов простыми поисковыми запросами, например, «Какова наша политика в отношении отпусков?», или требуют сложных рассуждений, например, «Сравните стратегии продаж в III и IV кварталах»? Ожидают ли пользователи точных цитат или просто ответов в разговорном формате? Это определяет, насколько сложной должна быть система.
4. Технологический стек.
Вот некоторые популярные инструменты и технологии.
- LLM. С закрытым исходным кодом (GPT-4 от OpenAI, Claude от Anthropic, Gemini от Google), которые легко внедряются и обеспечивают высокую производительность, но привязаны к поставщику и имеют проблемы конфиденциальности данных. Модели с открытым исходным кодом (Llama 3, Mistral, или специализированные BioBERT для медицины и FinBERT для финансов), обеспечивают больший контроль и гибкость, но требуют инфраструктуры графических процессоров и наличия собственных специалистов для масштабирования.
- Модель эмбеддинга. Распространенные варианты: text-embedding-3 от OpenAI или embed-v3 от Cohere или бесплатные альтернативы с открытым исходным кодом. Специализированные модели, такие как E5 или Instructor, могут дополнительно повысить точность, специфичную для предметной области. Модели LLM и эмбеддинга не обязательно должны предоставляться одним и тем же поставщиком.
- Векторная БД, в которой хранятся и ищутся эмбеддинги: Pinecone, Weaviate Cloud или Qdrant Cloud, отлично подходят для быстрого начала работы и плавного масштабирования, хотя и стоят дороже. Решения с собственным хостингом, такие как ChromaDB, Milvus, Elasticsearch с поиском векторов или расширение PostgreSQL pgvector, обеспечивают больший контроль и могут быть дешевле в долгосрочной перспективе, но требуют инвестиций в DevOps. Правильный выбор зависит от объёма данных (сотни тысяч против миллиардов векторов), нагрузки запросов (десятки против десятков тысяч запросов в секунду) и бюджета.
- Фреймворк для оркестровки. Немногие команды разрабатывают всё с нуля. LangChain — самый популярный фреймворк с широкой экосистемой и абстракциями практически для каждого компонента, хотя в простых случаях он может показаться слишком громоздким. LlamaIndex разработан специально для приложений RAG с большим объёмом документов и предлагает чистый приём данных и конвейеры запросов. Haystack ориентирован на производство и обладает мощной поддержкой сложных рабочих процессов.
Итого
Понимание основных концепций RAG помогает принимать обоснованные решения о том, подходит ли он для конкретного сценария использования. Если нам нужен ИИ, способный получать доступ к закрытой информации компании, предоставлять актуальные обновления, цитировать источники или поддерживать строгую точность, RAG, вероятно, является решением. Двухэтапная архитектура подготовки документов и обработки запросов делает её масштабируемой и эффективной, а использование встраивания гарантирует, что пользователи найдут релевантную информацию независимо от формулировки своих вопросов.
Область RAG продолжает стремительно развиваться, совершенствуя методы поиска, улучшая модели эмбеддинга и разрабатывая более сложные стратегии генерации. Однако основополагающие принципы, изложенные здесь, остаются неизменными.
Источник: https://substack.com/home/post/p-174052561
Как RAG Позволяет Использовать ИИ для Ваших Данных. Окончание
Что такое RAG
Как работает RAG
Эмбеддинги
Создание RAG-системы
1. Понимание требований.
Разрабатывается ли система для внутренних сотрудников, для которых важна точность и надежность? Или для внешних клиентов, для которых важнее скорость и взаимодействие с UI?
2. Структура документов.
Масштаб имеет значение. Различные объёмы требуют разных стратегий хранения и поиска. Возможные типы контента определяют конвейеры загрузки и предварительной обработки.
3. Шаблоны запросов.
Являются ли большинство запросов простыми поисковыми запросами, например, «Какова наша политика в отношении отпусков?», или требуют сложных рассуждений, например, «Сравните стратегии продаж в III и IV кварталах»? Ожидают ли пользователи точных цитат или просто ответов в разговорном формате? Это определяет, насколько сложной должна быть система.
4. Технологический стек.
Вот некоторые популярные инструменты и технологии.
- LLM. С закрытым исходным кодом (GPT-4 от OpenAI, Claude от Anthropic, Gemini от Google), которые легко внедряются и обеспечивают высокую производительность, но привязаны к поставщику и имеют проблемы конфиденциальности данных. Модели с открытым исходным кодом (Llama 3, Mistral, или специализированные BioBERT для медицины и FinBERT для финансов), обеспечивают больший контроль и гибкость, но требуют инфраструктуры графических процессоров и наличия собственных специалистов для масштабирования.
- Модель эмбеддинга. Распространенные варианты: text-embedding-3 от OpenAI или embed-v3 от Cohere или бесплатные альтернативы с открытым исходным кодом. Специализированные модели, такие как E5 или Instructor, могут дополнительно повысить точность, специфичную для предметной области. Модели LLM и эмбеддинга не обязательно должны предоставляться одним и тем же поставщиком.
- Векторная БД, в которой хранятся и ищутся эмбеддинги: Pinecone, Weaviate Cloud или Qdrant Cloud, отлично подходят для быстрого начала работы и плавного масштабирования, хотя и стоят дороже. Решения с собственным хостингом, такие как ChromaDB, Milvus, Elasticsearch с поиском векторов или расширение PostgreSQL pgvector, обеспечивают больший контроль и могут быть дешевле в долгосрочной перспективе, но требуют инвестиций в DevOps. Правильный выбор зависит от объёма данных (сотни тысяч против миллиардов векторов), нагрузки запросов (десятки против десятков тысяч запросов в секунду) и бюджета.
- Фреймворк для оркестровки. Немногие команды разрабатывают всё с нуля. LangChain — самый популярный фреймворк с широкой экосистемой и абстракциями практически для каждого компонента, хотя в простых случаях он может показаться слишком громоздким. LlamaIndex разработан специально для приложений RAG с большим объёмом документов и предлагает чистый приём данных и конвейеры запросов. Haystack ориентирован на производство и обладает мощной поддержкой сложных рабочих процессов.
Итого
Понимание основных концепций RAG помогает принимать обоснованные решения о том, подходит ли он для конкретного сценария использования. Если нам нужен ИИ, способный получать доступ к закрытой информации компании, предоставлять актуальные обновления, цитировать источники или поддерживать строгую точность, RAG, вероятно, является решением. Двухэтапная архитектура подготовки документов и обработки запросов делает её масштабируемой и эффективной, а использование встраивания гарантирует, что пользователи найдут релевантную информацию независимо от формулировки своих вопросов.
Область RAG продолжает стремительно развиваться, совершенствуя методы поиска, улучшая модели эмбеддинга и разрабатывая более сложные стратегии генерации. Однако основополагающие принципы, изложенные здесь, остаются неизменными.
Источник: https://substack.com/home/post/p-174052561
👍4
День 2439. #ЗаметкиНаПолях
Используем insteadOf в Git для Замены HTTPS-адресов на SSH
При работе с Git-репозиториями вы часто сталкиваетесь с URL-адресами в формате HTTPS, особенно при клонировании из GitHub, GitLab или других хостинг-провайдеров. Однако, если вы предпочитаете использовать SSH для аутентификации (что часто удобнее с аутентификацией по ключам), ручное изменение URL-адресов может быть утомительным. Кроме того, это может быть ещё сложнее при работе с субмодулями.
Конфигурационная опция Git insteadOf предлагает элегантное решение, автоматически переписывая URL-адреса «на лету»:
Вы также можете настроить более конкретную проверку и выбрать только определённые репозитории или сервисы. Например, если вы хотите заменить HTTPS-адреса некоторых репозиториев, вы можете сделать следующее:
После настройки Git будет автоматически переписывать URL:
Перезапись происходит прозрачно. Вы по-прежнему можете использовать HTTPS-адреса в командах, документации или при копировании из веб-интерфейсов.
Ещё один вариант использования insteadOf — добавление аутентификации к URL. Например, если вы хотите использовать определённого пользователя для всех запросов Git, вы можете сделать следующее:
Источник: https://www.meziantou.net/using-git-insteadof-to-automatically-replace-https-urls-with-ssh.htm
Используем insteadOf в Git для Замены HTTPS-адресов на SSH
При работе с Git-репозиториями вы часто сталкиваетесь с URL-адресами в формате HTTPS, особенно при клонировании из GitHub, GitLab или других хостинг-провайдеров. Однако, если вы предпочитаете использовать SSH для аутентификации (что часто удобнее с аутентификацией по ключам), ручное изменение URL-адресов может быть утомительным. Кроме того, это может быть ещё сложнее при работе с субмодулями.
Конфигурационная опция Git insteadOf предлагает элегантное решение, автоматически переписывая URL-адреса «на лету»:
git config --global url."[email protected]:".insteadOf "https://github.com/"
git config --global url."[email protected]:".insteadOf "https://gitlab.com/"
git config --global url."[email protected]:".insteadOf "https://bitbucket.org/"
Вы также можете настроить более конкретную проверку и выбрать только определённые репозитории или сервисы. Например, если вы хотите заменить HTTPS-адреса некоторых репозиториев, вы можете сделать следующее:
git config --global url."[email protected]:username/".insteadOf "https://github.com/username/"
После настройки Git будет автоматически переписывать URL:
# Следующая команда
git clone https://github.com/user/repo.git
# Станет эквивалентной
git clone [email protected]:user/repo.git
Перезапись происходит прозрачно. Вы по-прежнему можете использовать HTTPS-адреса в командах, документации или при копировании из веб-интерфейсов.
Ещё один вариант использования insteadOf — добавление аутентификации к URL. Например, если вы хотите использовать определённого пользователя для всех запросов Git, вы можете сделать следующее:
git config --global url."https://<token>@github.com/".insteadOf "https://github.com/"
Источник: https://www.meziantou.net/using-git-insteadof-to-automatically-replace-https-urls-with-ssh.htm
👍16
День 2440. #Книги
«.NET 8: инструменты и навыки. Лучшие практики и паттерны проектирования, отладки и тестирования.» (Прайс М. — Астана: «Спринт Бук», 2025).
Ещё одну книгу издательство «Питер» прислали (точнее, передали на DotNext) мне на обзор.
В обзоре предыдущей книги я упоминал, что она вторая из серии книг Марка Прайса. Сегодня напишу про третью. В третей части 800 страниц, и, в отличие от второй, где сделан упор на обзор технологий, существующих в экосистеме .NET, в третей рассказывается о лучших практиках, которые можно применять как при создании приложений, так и для продвижения по карьерной лестнице. Вот некоторые темы:
- Эффективная работа с IDE;
- Работа с Git;
- Отладка;
- Ведение журналов, метрик и трассировок;
- Документирование кода и API;
- Рефлексия и динамическая загрузка сборок;
- Криптография;
- Создание ИИ-чатбота;
- Внедрение зависимостей;
- Тестирование: модульное, интеграционное, нагрузочное, функциональное;
- Контейнеризация;
- .NET Aspire;
- Паттерны и принципы проектирования;
- Основы архитектуры ПО;
- Командная работа и собеседования.
Как и в предыдущей книге, автор на практических примерах показывает наиболее интересные на его взгляд приёмы, которые используют многие .NET разработчики: горячие клавиши и функции IDE, команды Git, стратегии отладки, настройки телеметрии, тесты и т.п. Автор попытался охватить ВСЕ лучшие практики. Конечно, подробно в рамках одной книги это сделать нереально, поэтому ко всем перечисленным выше темам стоило бы добавить слово «Основы …». Но информации по каждой теме вполне достаточно, чтобы создать прочный базис, и автор не скупится на ссылки с дополнительной информацией. Кстати, плюсик в карму издательству: где это возможно, ссылки ведут на русскоязычные версии страниц документации.
Кроме того, как и в предыдущей книге, в конце каждой главы автор даёт несколько заданий для закрепления материала: ссылки на подробное изучение, практическое задание и вопросы для проверки знаний. Поэтому, если вы уверены, что знаете какую-то тему, можете начать с того, чтобы попытаться ответить на вопросы в конце главы. В конце книги есть ответы на все эти вопросы.
Очень интересная 19я глава про работу в команде и карьеру. В ней описаны хард- и софт-скилы, которых ждут от кандидатов, специализации, которые есть в современных вакансиях, роли в командах разработки. Далее довольно подробно описан процесс поиска работы, составления резюме и подготовки к собеседованию (ещё один плюсик издательству – добавили информацию об особенностях российского рынка разработки). Причём, описаны пути не только для новичков, но и для претендентов на старшие и руководящие должности. Описаны различные сценарии проведения собеседований, рассматриваются частые вопросы, которые задают кандидатам, в том числе поведенческие вопросы.
Интересный факт, одним из научных редакторов книги был «широко известный в узких кругах» Милан Йованович.
Ещё раз спасибо за подарок издательству «Питер». Присылайте что-нибудь ещё 😊
«.NET 8: инструменты и навыки. Лучшие практики и паттерны проектирования, отладки и тестирования.» (Прайс М. — Астана: «Спринт Бук», 2025).
Ещё одну книгу издательство «Питер» прислали (точнее, передали на DotNext) мне на обзор.
В обзоре предыдущей книги я упоминал, что она вторая из серии книг Марка Прайса. Сегодня напишу про третью. В третей части 800 страниц, и, в отличие от второй, где сделан упор на обзор технологий, существующих в экосистеме .NET, в третей рассказывается о лучших практиках, которые можно применять как при создании приложений, так и для продвижения по карьерной лестнице. Вот некоторые темы:
- Эффективная работа с IDE;
- Работа с Git;
- Отладка;
- Ведение журналов, метрик и трассировок;
- Документирование кода и API;
- Рефлексия и динамическая загрузка сборок;
- Криптография;
- Создание ИИ-чатбота;
- Внедрение зависимостей;
- Тестирование: модульное, интеграционное, нагрузочное, функциональное;
- Контейнеризация;
- .NET Aspire;
- Паттерны и принципы проектирования;
- Основы архитектуры ПО;
- Командная работа и собеседования.
Как и в предыдущей книге, автор на практических примерах показывает наиболее интересные на его взгляд приёмы, которые используют многие .NET разработчики: горячие клавиши и функции IDE, команды Git, стратегии отладки, настройки телеметрии, тесты и т.п. Автор попытался охватить ВСЕ лучшие практики. Конечно, подробно в рамках одной книги это сделать нереально, поэтому ко всем перечисленным выше темам стоило бы добавить слово «Основы …». Но информации по каждой теме вполне достаточно, чтобы создать прочный базис, и автор не скупится на ссылки с дополнительной информацией. Кстати, плюсик в карму издательству: где это возможно, ссылки ведут на русскоязычные версии страниц документации.
Кроме того, как и в предыдущей книге, в конце каждой главы автор даёт несколько заданий для закрепления материала: ссылки на подробное изучение, практическое задание и вопросы для проверки знаний. Поэтому, если вы уверены, что знаете какую-то тему, можете начать с того, чтобы попытаться ответить на вопросы в конце главы. В конце книги есть ответы на все эти вопросы.
Очень интересная 19я глава про работу в команде и карьеру. В ней описаны хард- и софт-скилы, которых ждут от кандидатов, специализации, которые есть в современных вакансиях, роли в командах разработки. Далее довольно подробно описан процесс поиска работы, составления резюме и подготовки к собеседованию (ещё один плюсик издательству – добавили информацию об особенностях российского рынка разработки). Причём, описаны пути не только для новичков, но и для претендентов на старшие и руководящие должности. Описаны различные сценарии проведения собеседований, рассматриваются частые вопросы, которые задают кандидатам, в том числе поведенческие вопросы.
Интересный факт, одним из научных редакторов книги был «широко известный в узких кругах» Милан Йованович.
Ещё раз спасибо за подарок издательству «Питер». Присылайте что-нибудь ещё 😊
👍26
День 2441. #ЗаметкиНаПолях
Используем Хранимые Процедуры в EF Core. Начало
Необходимость в хранимых процедурах в БД может возникнуть по разным причинам:
- сложный отчёт, объединяющий несколько таблиц с помощью агрегаций и оконных функций, когда LINQ-запрос генерирует неоптимальный SQL;
- нужно обновить таблицу, используя правильные блокировки, чтобы предотвратить состояние гонки.
В разных источниках об использовании хранимых процедур в EF Core можно встретить противоречивые советы: от «избегать использования чистого SQL любой ценой» до «полностью отказаться от EF и использовать ADO.NET». Ни то, ни другое не кажется правильным. EF Core отлично работает с функциями и процедурами БД.
В примерах далее будем использовать PostgreSQL, но те же принципы применимы и к другим реляционным БД.
Когда стоит использовать чистый SQL?
В большинстве случаев LINQ вполне подходит. EF Core преобразует ваш код C# в качественный SQL, а вы получаете типобезопасность и поддержку рефакторинга. Но есть исключения:
1. Нужна производительность, которую невозможно получить от LINQ. Сложные агрегации с несколькими соединениями, оконными функциями или запросы к отчётам часто выполняются быстрее, если написаны непосредственно на SQL. Вы можете протестировать и настроить запрос в инструменте работы с БД, прежде чем добавлять его в код.
2. Специфичные для БД функции. В PostgreSQL есть полнотекстовый поиск запросы к JSON и общие табличные выражения (CTE), которые не всегда имеют понятные эквиваленты в LINQ.
3. Есть существующая логика в БД (хранимые процедуры и функции, например, из унаследованной системы). Их прямой вызов лучше, чем переписывание всего на C#.
4. Нужны атомарные операции с корректной блокировкой. Хранимая процедура, которая координирует несколько обновлений с помощью блокировок FOR UPDATE, проще и безопаснее, чем пытаться управлять этим из кода приложения.
5. Нужно сократить количество запросов. Один вызов функции, агрегирующий данные из пяти таблиц, эффективнее пяти отдельных LINQ-запросов.
Рассмотрим, как это сделать.
1. Простая скалярная функция
Вот простая функция, которая показывает, сколько билетов осталось:
Ничего необычного, просто запрос, обёрнутый в функцию. Вызвать её в EF Core просто:
Обратите внимание на алиас
Синтаксис интерполированной строки (
Продолжение следует…
Источник: https://www.milanjovanovic.tech/blog/using-stored-procedures-and-functions-with-ef-core-and-postgresql
Используем Хранимые Процедуры в EF Core. Начало
Необходимость в хранимых процедурах в БД может возникнуть по разным причинам:
- сложный отчёт, объединяющий несколько таблиц с помощью агрегаций и оконных функций, когда LINQ-запрос генерирует неоптимальный SQL;
- нужно обновить таблицу, используя правильные блокировки, чтобы предотвратить состояние гонки.
В разных источниках об использовании хранимых процедур в EF Core можно встретить противоречивые советы: от «избегать использования чистого SQL любой ценой» до «полностью отказаться от EF и использовать ADO.NET». Ни то, ни другое не кажется правильным. EF Core отлично работает с функциями и процедурами БД.
В примерах далее будем использовать PostgreSQL, но те же принципы применимы и к другим реляционным БД.
Когда стоит использовать чистый SQL?
В большинстве случаев LINQ вполне подходит. EF Core преобразует ваш код C# в качественный SQL, а вы получаете типобезопасность и поддержку рефакторинга. Но есть исключения:
1. Нужна производительность, которую невозможно получить от LINQ. Сложные агрегации с несколькими соединениями, оконными функциями или запросы к отчётам часто выполняются быстрее, если написаны непосредственно на SQL. Вы можете протестировать и настроить запрос в инструменте работы с БД, прежде чем добавлять его в код.
2. Специфичные для БД функции. В PostgreSQL есть полнотекстовый поиск запросы к JSON и общие табличные выражения (CTE), которые не всегда имеют понятные эквиваленты в LINQ.
3. Есть существующая логика в БД (хранимые процедуры и функции, например, из унаследованной системы). Их прямой вызов лучше, чем переписывание всего на C#.
4. Нужны атомарные операции с корректной блокировкой. Хранимая процедура, которая координирует несколько обновлений с помощью блокировок FOR UPDATE, проще и безопаснее, чем пытаться управлять этим из кода приложения.
5. Нужно сократить количество запросов. Один вызов функции, агрегирующий данные из пяти таблиц, эффективнее пяти отдельных LINQ-запросов.
Рассмотрим, как это сделать.
1. Простая скалярная функция
Вот простая функция, которая показывает, сколько билетов осталось:
CREATE OR REPLACE FUNCTION
tt.tickets_left(ticket_type_id uuid)
RETURNS numeric LANGUAGE sql
AS $$
SELECT tt.available_quantity
FROM ticketing.ticket_types tt
WHERE tt.id = ticket_type_id
$$;
Ничего необычного, просто запрос, обёрнутый в функцию. Вызвать её в EF Core просто:
var result = await dbContext.Database.SqlQuery<int>(
$"""
SELECT tt.tickets_left({ticketTypeId}) AS "Value"
""")
.FirstAsync();
Обратите внимание на алиас
AS "Value". Когда EF Core сопоставляется с примитивным типом, он ожидает свойство с именем Value. Кавычки сохраняют точный регистр (PostgreSQL по умолчанию преобразует идентификаторы без кавычек в нижний регистр).Синтаксис интерполированной строки (
$"{ticketTypeId}") может показаться опасным и подверженным SQL-инъекциям, но EF Core автоматически преобразует его в параметризованный запрос. Это всего лишь удобный способ написания параметризованных запросов. Причина в том, что мы передаём методу SqlQuery не строку, а FormattableString. Это специальный тип, который сохраняет формат и аргументы отдельно, позволяя EF Core обрабатывать параметры.Продолжение следует…
Источник: https://www.milanjovanovic.tech/blog/using-stored-procedures-and-functions-with-ef-core-and-postgresql
👍16
День 2442. #ЗаметкиНаПолях
Используем Хранимые Процедуры в EF Core. Продолжение
Начало
2. Функция, возвращающая таблицу
Функции могут возвращать целые наборы результатов:
Эта функция объединяет заказы с их товарами, суммирует количество и возвращает несколько строк. Вы можете написать это на LINQ, но SQL понятнее, и вы можете протестировать его непосредственно в инструменте для работы с БД. Чтобы использовать её из C#, создайте DTO, соответствующий выходным данным функции:
Затем запросите функцию, как и любую другую таблицу:
Ключевым моментом является сопоставление имён столбцов со свойствами DTO с помощью алиасов. EF Core обрабатывает всё остальное автоматически.
Это простой случай без объединений, но вы можете использовать этот шаблон и в более сложных запросах. Однако вам придётся вручную проецировать данные в DTO, поскольку EF Core не может преобразовать объединения в чистом SQL в графы сущностей. Обычно вы в любом случае возвращаете плоскую структуру из функций, а затем при необходимости преобразуете её в более сложные модели на C#.
Понимание функций и процедур PostgreSQL
PostgreSQL различает функции и процедуры:
- Функции предназначены для возврата значений. Они могут возвращать скалярные значения, таблицы или даже сложные JSON-объекты. Они вызываются внутри транзакции с помощью SELECT и могут использоваться в запросах, предложениях WHERE, объединениях и других контекстах запросов.
- Процедуры предназначены для побочных эффектов. Они не возвращают значения напрямую, но могут изменять данные и иметь OUT-параметры. Вызываются с помощью CALL и идеально подходят для сложных операций, требующих явного управления транзакциями или выполнения нескольких связанных обновлений.
Используйте функции, когда нужно вернуть данные, используйте процедуры, когда нужно что-то изменить.
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/using-stored-procedures-and-functions-with-ef-core-and-postgresql
Используем Хранимые Процедуры в EF Core. Продолжение
Начало
2. Функция, возвращающая таблицу
Функции могут возвращать целые наборы результатов:
CREATE OR REPLACE FUNCTION
tt.customer_order_summary(customer_id uuid)
RETURNS TABLE (
order_id uuid,
created_at timestamptz,
total numeric,
currency text,
qty numeric
)
LANGUAGE sql
AS $$
SELECT
o.id, o.created_at, o.total, o.currency,
COALESCE(SUM(oi.qty), 0) AS qty
FROM tt.orders o
LEFT JOIN tt.order_items oi ON oi.order_id = o.id
WHERE o.customer_id = customer_id
GROUP BY o.id, o.created_at, o.total, o.currency
ORDER BY o.created_at DESC
$$;
Эта функция объединяет заказы с их товарами, суммирует количество и возвращает несколько строк. Вы можете написать это на LINQ, но SQL понятнее, и вы можете протестировать его непосредственно в инструменте для работы с БД. Чтобы использовать её из C#, создайте DTO, соответствующий выходным данным функции:
public class OrderSummaryDto
{
public Guid OrderId { get; set; }
public DateTime CreatedAt { get; set; }
public decimal Total { get; set; }
public string Currency { get; set; }
public int Quantity { get; set; }
}
Затем запросите функцию, как и любую другую таблицу:
var orders =
await dbContext.Database
.SqlQuery<OrderSummaryDto>(
$"""
SELECT order_id AS OrderId, created_at AS CreatedAt, total AS Total, currency AS Currency, qty AS Quantity
FROM tt.customer_order_summary({customerId})
""")
.ToListAsync();
Ключевым моментом является сопоставление имён столбцов со свойствами DTO с помощью алиасов. EF Core обрабатывает всё остальное автоматически.
Это простой случай без объединений, но вы можете использовать этот шаблон и в более сложных запросах. Однако вам придётся вручную проецировать данные в DTO, поскольку EF Core не может преобразовать объединения в чистом SQL в графы сущностей. Обычно вы в любом случае возвращаете плоскую структуру из функций, а затем при необходимости преобразуете её в более сложные модели на C#.
Понимание функций и процедур PostgreSQL
PostgreSQL различает функции и процедуры:
- Функции предназначены для возврата значений. Они могут возвращать скалярные значения, таблицы или даже сложные JSON-объекты. Они вызываются внутри транзакции с помощью SELECT и могут использоваться в запросах, предложениях WHERE, объединениях и других контекстах запросов.
- Процедуры предназначены для побочных эффектов. Они не возвращают значения напрямую, но могут изменять данные и иметь OUT-параметры. Вызываются с помощью CALL и идеально подходят для сложных операций, требующих явного управления транзакциями или выполнения нескольких связанных обновлений.
Используйте функции, когда нужно вернуть данные, используйте процедуры, когда нужно что-то изменить.
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/using-stored-procedures-and-functions-with-ef-core-and-postgresql
👍8
День 2443. #ЗаметкиНаПолях
Используем Хранимые Процедуры в EF Core. Окончание
Начало
Продолжение
3. Хранимая процедура с валидацией
Допустим, вам нужно изменить количество доступных билетов, но вы хотите предотвратить состояния гонки и проверить операцию:
Процедура делает несколько важных вещей:
- «Запирает» строку для обновления (FOR UPDATE), чтобы другая транзакция не могла её обновить, пока не завершится хранимая процедура;
- Проверяет бизнес-правила перед внесением изменений;
- Выводит понятные сообщения об ошибках при возникновении проблем;
- Сохраняет всё атомарно в одном запросе к БД.
Всё это можно было бы реализовать на C# с ручным управлением транзакциями и явной блокировкой, но это сложнее и подвержено ошибкам. Позвольте БД делать то, в чём она хороша. Вот как это можно вызвать из EF Core:
Процедура не возвращает значение, но, если она выбрасывает исключение (с помощью RAISE EXCEPTION), PostgreSQL передаст его в ваш код C#. Вы можете перехватить его и вернуть корректный ответ об ошибке.
Представления (view)
Представления БД подобны функциям без параметров. Это сохранённые запросы, к которым можно обращаться по имени. Можно выполнять запросы к ним, используя SqlQuery<T>, как к функциям:
Или можно сопоставлять их с типами сущностей в DbContext для полной поддержки LINQ. Представления отлично подходят для часто используемых запросов, не требующих параметров. Их также можно добавить в DbContext. Функции же обеспечивают гибкость параметризации.
Итого
EF Core не заставляет вас выбирать между LINQ и чистым SQL. Вы можете использовать и то, и другое. Используйте функции, когда нужно вернуть данные, процедуры, когда нужно изменить данные со сложной логикой, и чистые SQL-запросы, когда LINQ не может эффективно удовлетворить ваши требования. Сочетание удобства EF Core и мощности БД даёт гибкость в выборе подходящего инструмента для каждого сценария.
Источник: https://www.milanjovanovic.tech/blog/using-stored-procedures-and-functions-with-ef-core-and-postgresql
Используем Хранимые Процедуры в EF Core. Окончание
Начало
Продолжение
3. Хранимая процедура с валидацией
Допустим, вам нужно изменить количество доступных билетов, но вы хотите предотвратить состояния гонки и проверить операцию:
CREATE OR REPLACE PROCEDURE
tt.adjust_available_quantity(
ticket_type_id uuid,
delta numeric
)
LANGUAGE plpgsql AS $$
DECLARE
v_qty numeric;
v_avail numeric;
v_new_avail numeric;
BEGIN
SELECT quantity, available_quantity
INTO v_qty, v_avail
FROM tt.ticket_types
WHERE id = ticket_type_id
FOR UPDATE;
IF NOT FOUND THEN
RAISE EXCEPTION 'Тип билета % не найден', ticket_type_id;
END IF;
v_new_avail := v_avail + delta;
IF v_new_avail < 0 THEN
RAISE EXCEPTION 'Нельзя уменьшить < 0';
END IF;
IF v_new_avail > v_qty THEN
RAISE EXCEPTION 'Нельзя превышать общее количество';
END IF;
UPDATE tt.ticket_types
SET available = v_new_avail
WHERE id = ticket_type_id;
END;
$$;
Процедура делает несколько важных вещей:
- «Запирает» строку для обновления (FOR UPDATE), чтобы другая транзакция не могла её обновить, пока не завершится хранимая процедура;
- Проверяет бизнес-правила перед внесением изменений;
- Выводит понятные сообщения об ошибках при возникновении проблем;
- Сохраняет всё атомарно в одном запросе к БД.
Всё это можно было бы реализовать на C# с ручным управлением транзакциями и явной блокировкой, но это сложнее и подвержено ошибкам. Позвольте БД делать то, в чём она хороша. Вот как это можно вызвать из EF Core:
try
{
await dbContext.Database.ExecuteSqlAsync(
$"""
CALL tt.adjust_available_quantity({ticketTypeId},{quantity})
""");
}
catch (Exception e)
{
// обработка ошибки
}
Процедура не возвращает значение, но, если она выбрасывает исключение (с помощью RAISE EXCEPTION), PostgreSQL передаст его в ваш код C#. Вы можете перехватить его и вернуть корректный ответ об ошибке.
Представления (view)
Представления БД подобны функциям без параметров. Это сохранённые запросы, к которым можно обращаться по имени. Можно выполнять запросы к ним, используя SqlQuery<T>, как к функциям:
var results = await dbContext.Database
.SqlQuery<ActiveCustomerDto>(
$"SELECT * FROM tt.active_customers")
.ToListAsync();
Или можно сопоставлять их с типами сущностей в DbContext для полной поддержки LINQ. Представления отлично подходят для часто используемых запросов, не требующих параметров. Их также можно добавить в DbContext. Функции же обеспечивают гибкость параметризации.
Итого
EF Core не заставляет вас выбирать между LINQ и чистым SQL. Вы можете использовать и то, и другое. Используйте функции, когда нужно вернуть данные, процедуры, когда нужно изменить данные со сложной логикой, и чистые SQL-запросы, когда LINQ не может эффективно удовлетворить ваши требования. Сочетание удобства EF Core и мощности БД даёт гибкость в выборе подходящего инструмента для каждого сценария.
Источник: https://www.milanjovanovic.tech/blog/using-stored-procedures-and-functions-with-ef-core-and-postgresql
👍9
День 2444. #TipsAndTricks
Сеньорские Приёмы в C#
Некоторые функции C# настолько фундаментальны для чистой, современной разработки .NET, что их игнорирование выдаёт неопытность. Это не яркие новые игрушки, а практичные инструменты, которые мгновенно отличают код начинающего разработчика от отточенного кода опытного.
1. Раннее выявление ошибок внедрения зависимостей
Одна из самых серьёзных ошибок в приложениях .NET — это когда сервис успешно разрешается при запуске, но позже падает из-за недопустимых значений времени жизни или отсутствующих зависимостей. Этого можно избежать.
Код начинающего:
Вы не узнаете, что что-то неправильно настроено до того, как это не отвалится при обработке запроса.
Код опытного:
Эти две строки гарантируют обнаружение захвата зависимостей при запуске приложения, а не при его работе. ValidateScopes обнаруживает недопустимые времена жизни (например, внедрение scoped-сервиса в синглтон), а ValidateOnBuild заставляет контейнер попытаться создать сервисы при построении контейнера. Если что-то сломается, вы узнаете об этом ещё до запуска приложения.
2. Понимание и создание областей действия сервисов
Многие разработчики помещают все сервисы в Transient, не понимая, что это значит. Время жизни — это не просто шаблонный код; это модель памяти вашего приложения.
Код начинающего:
Ни контекста, ни объяснения, ни обдумывания, когда объект должен жить.
Ход мыслей опытного:
- Transient – новый экземпляр каждый раз,
- Scoped – один экземпляр на запрос/скоуп DI,
- Singleton – один экземпляр на всё время жизни приложения.
Создавая свою область действия, вы можете вручную изолировать контексты:
Опытный разработчик знает, когда ему нужна изоляция сервиса, например, для каждого пользователя, для каждого запроса или для каждого фонового задания.
3. Использование DateTimeOffset
Часовые пояса, летнее время и работа с UTC — вот где начинающие разработчики чаще всего терпят неудачу. Хуже всего то, что ваше приложение может работать… но потом перестаёт.
Ошибка начинающего:
Это сломается, как только код попадёт в другой часовой пояс (например, при размещении в облаке).
Подход опытного:
DateTimeOffset включает в себя как временную метку, так и её смещение. Это идеально подходит для реальных сценариев, таких как журналы, транзакции и планирование. Храните в формате UTC, отображайте по местному времени.
4. Использование алиасов пространств имён и типов
Загромождённые пространства имён и глубоко вложенные типы затрудняют чтение и рефакторинг кода. Тем не менее, начинающие разработчики редко используют алиасы:
Более чистый подход:
Алиасы — это не просто про сокращение имён; они проясняют суть. Они особенно полезны при рефакторинге или замене внешних библиотек, поскольку вы меняете всего одну строку — алиас.
Источник: https://blog.stackademic.com/5-c-features-that-instantly-expose-junior-devs-b126030c132e
Сеньорские Приёмы в C#
Некоторые функции C# настолько фундаментальны для чистой, современной разработки .NET, что их игнорирование выдаёт неопытность. Это не яркие новые игрушки, а практичные инструменты, которые мгновенно отличают код начинающего разработчика от отточенного кода опытного.
1. Раннее выявление ошибок внедрения зависимостей
Одна из самых серьёзных ошибок в приложениях .NET — это когда сервис успешно разрешается при запуске, но позже падает из-за недопустимых значений времени жизни или отсутствующих зависимостей. Этого можно избежать.
Код начинающего:
builder.Services.AddScoped<IMyService, MyService>();
// Нет настройки валидации
Вы не узнаете, что что-то неправильно настроено до того, как это не отвалится при обработке запроса.
Код опытного:
var builder = Program.CreateHostBuilder(args);
builder.Services.AddScoped<IMyService, MyService>();
builder.Host.UseDefaultServiceProvider(
(context, options) =>
{
options.ValidateOnBuild = true;
options.ValidateScopes = true;
});
Эти две строки гарантируют обнаружение захвата зависимостей при запуске приложения, а не при его работе. ValidateScopes обнаруживает недопустимые времена жизни (например, внедрение scoped-сервиса в синглтон), а ValidateOnBuild заставляет контейнер попытаться создать сервисы при построении контейнера. Если что-то сломается, вы узнаете об этом ещё до запуска приложения.
2. Понимание и создание областей действия сервисов
Многие разработчики помещают все сервисы в Transient, не понимая, что это значит. Время жизни — это не просто шаблонный код; это модель памяти вашего приложения.
Код начинающего:
builder.Services.AddTransient<UserSession>();
Ни контекста, ни объяснения, ни обдумывания, когда объект должен жить.
Ход мыслей опытного:
- Transient – новый экземпляр каждый раз,
- Scoped – один экземпляр на запрос/скоуп DI,
- Singleton – один экземпляр на всё время жизни приложения.
Создавая свою область действия, вы можете вручную изолировать контексты:
using var scope = serviceProvider.CreateScope();
var scopedService = scope
.ServiceProvider
.GetRequiredService<IScopedThing>();
Опытный разработчик знает, когда ему нужна изоляция сервиса, например, для каждого пользователя, для каждого запроса или для каждого фонового задания.
3. Использование DateTimeOffset
Часовые пояса, летнее время и работа с UTC — вот где начинающие разработчики чаще всего терпят неудачу. Хуже всего то, что ваше приложение может работать… но потом перестаёт.
Ошибка начинающего:
var orderTime = DateTime.Now; // локальное время
Это сломается, как только код попадёт в другой часовой пояс (например, при размещении в облаке).
Подход опытного:
var orderTime = DateTimeOffset.Now;
DateTimeOffset включает в себя как временную метку, так и её смещение. Это идеально подходит для реальных сценариев, таких как журналы, транзакции и планирование. Храните в формате UTC, отображайте по местному времени.
4. Использование алиасов пространств имён и типов
Загромождённые пространства имён и глубоко вложенные типы затрудняют чтение и рефакторинг кода. Тем не менее, начинающие разработчики редко используют алиасы:
System.Collections.Generic.Dictionary<System.Tuple<string, int>, List<MyNamespace.Models.ComplexThing>> myMap;
Более чистый подход:
using ComplexMap = System.Collections.Generic.Dictionary<(string, int), List<ComplexThing>>;
Алиасы — это не просто про сокращение имён; они проясняют суть. Они особенно полезны при рефакторинге или замене внешних библиотек, поскольку вы меняете всего одну строку — алиас.
Источник: https://blog.stackademic.com/5-c-features-that-instantly-expose-junior-devs-b126030c132e
👍26👎5
День 2445. #ВопросыНаСобеседовании
Давно не было на канале вопросов с собеседований. Смотрите все посты на эту тему по хэштэгу выше. Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы). Я решил разобрать их тут. Начнём с 4го, поскольку первые 3 приведены в его книге, обзор на которую я недавно выкладывал.
4. Интерфейсы и абстрактные классы
«Когда в .NET следует использовать интерфейс, а когда — абстрактный класс? Приведите примеры ситуаций, в которых один вариант будет более подходящим, чем другой».
Хороший ответ
«В .NET и интерфейсы, и абстрактные классы используются для определения контрактов в коде, но их применение различается в зависимости от необходимости наследования и типа полиморфизма.
Интерфейсы лучше всего использовать, когда нужно определить контракт для того, что делает класс, не навязывая ему, как именно это должно быть реализовано. Интерфейсы идеально подходят, когда требуется, чтобы несколько несвязанных классов реализовали один и тот же набор методов, гарантируя, что все реализующие классы будут придерживаться определённого поведения. Например, можно использовать интерфейс для определения стандартной структуры для различных типов платежей, где каждый тип платежа, такой как CreditCard, PayPal, Bitcoin, реализует методы обработки платежей, определённые интерфейсом.
Абстрактные классы используются, когда требуется общий код для нескольких тесно связанных классов. Абстрактный класс может предоставлять определённое поведение по умолчанию, а также определять абстрактное поведение, которое должно быть реализовано подклассами. Например, если вы создаёте приложение, моделирующее геометрические фигуры, можно использовать абстрактный класс для определения методов по умолчанию для вычисления площади и периметра, требуя при этом, чтобы подклассы, такие как Circle, Rectangle и Triangle, реализовали свои специфические вычисления.
Выбор между использованием интерфейса и абстрактного класса часто сводится к тому, нужно ли вам совместно использовать код (использовать абстрактный класс) или просто обеспечить общий интерфейс, не заботясь об иерархии (использовать интерфейс)».
Часто встречающийся неудачный ответ
«Я использую абстрактный класс всякий раз, когда мне нужно определить методы, которые не следует изменять, а интерфейсы — когда мне нужно просто реализовать множество различных методов. Поэтому, если у меня есть методы, которые не следует переопределять, я помещаю их в абстрактный класс».
Этот ответ демонстрирует непонимание фундаментального назначения и полезности абстрактных классов и интерфейсов:
- Непонимание абстрактных классов: Абстрактные классы предназначены не только для определения методов, которые «не следует изменять». Хотя абстрактный класс действительно может содержать конкретные методы, их основное предназначение — служить базовым классом для расширения другими классами, предоставляя общий код и определяя абстрактные методы, которые должны быть реализованы подклассами. Тот факт, что они могут включать непереопределяемые методы (с помощью ключевого слова sealed в C#), является второстепенным по сравнению с их основной функцией.
- Непонимание интерфейсов: Утверждение, что интерфейсы используются «только для реализации набора различных методов», слишком упрощает их роль. Интерфейсы определяют контракт, которому следуют классы, что критически важно для разработки систем, в которых различные классы могут использоваться взаимозаменяемо, не зная их конкретных реализаций.
Неточность здесь проистекает из непонимания полиморфизма, повторного использования кода и замысла проектирования интерфейсов и абстрактных классов, что является фундаментальным аспектом объектно-ориентированного проектирования.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
Давно не было на канале вопросов с собеседований. Смотрите все посты на эту тему по хэштэгу выше. Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы). Я решил разобрать их тут. Начнём с 4го, поскольку первые 3 приведены в его книге, обзор на которую я недавно выкладывал.
4. Интерфейсы и абстрактные классы
«Когда в .NET следует использовать интерфейс, а когда — абстрактный класс? Приведите примеры ситуаций, в которых один вариант будет более подходящим, чем другой».
Хороший ответ
«В .NET и интерфейсы, и абстрактные классы используются для определения контрактов в коде, но их применение различается в зависимости от необходимости наследования и типа полиморфизма.
Интерфейсы лучше всего использовать, когда нужно определить контракт для того, что делает класс, не навязывая ему, как именно это должно быть реализовано. Интерфейсы идеально подходят, когда требуется, чтобы несколько несвязанных классов реализовали один и тот же набор методов, гарантируя, что все реализующие классы будут придерживаться определённого поведения. Например, можно использовать интерфейс для определения стандартной структуры для различных типов платежей, где каждый тип платежа, такой как CreditCard, PayPal, Bitcoin, реализует методы обработки платежей, определённые интерфейсом.
Абстрактные классы используются, когда требуется общий код для нескольких тесно связанных классов. Абстрактный класс может предоставлять определённое поведение по умолчанию, а также определять абстрактное поведение, которое должно быть реализовано подклассами. Например, если вы создаёте приложение, моделирующее геометрические фигуры, можно использовать абстрактный класс для определения методов по умолчанию для вычисления площади и периметра, требуя при этом, чтобы подклассы, такие как Circle, Rectangle и Triangle, реализовали свои специфические вычисления.
Выбор между использованием интерфейса и абстрактного класса часто сводится к тому, нужно ли вам совместно использовать код (использовать абстрактный класс) или просто обеспечить общий интерфейс, не заботясь об иерархии (использовать интерфейс)».
Часто встречающийся неудачный ответ
«Я использую абстрактный класс всякий раз, когда мне нужно определить методы, которые не следует изменять, а интерфейсы — когда мне нужно просто реализовать множество различных методов. Поэтому, если у меня есть методы, которые не следует переопределять, я помещаю их в абстрактный класс».
Этот ответ демонстрирует непонимание фундаментального назначения и полезности абстрактных классов и интерфейсов:
- Непонимание абстрактных классов: Абстрактные классы предназначены не только для определения методов, которые «не следует изменять». Хотя абстрактный класс действительно может содержать конкретные методы, их основное предназначение — служить базовым классом для расширения другими классами, предоставляя общий код и определяя абстрактные методы, которые должны быть реализованы подклассами. Тот факт, что они могут включать непереопределяемые методы (с помощью ключевого слова sealed в C#), является второстепенным по сравнению с их основной функцией.
- Непонимание интерфейсов: Утверждение, что интерфейсы используются «только для реализации набора различных методов», слишком упрощает их роль. Интерфейсы определяют контракт, которому следуют классы, что критически важно для разработки систем, в которых различные классы могут использоваться взаимозаменяемо, не зная их конкретных реализаций.
Неточность здесь проистекает из непонимания полиморфизма, повторного использования кода и замысла проектирования интерфейсов и абстрактных классов, что является фундаментальным аспектом объектно-ориентированного проектирования.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍21👎2
День 2446. #Карьера
Не Только Код: Что Делает Тебя Сеньором. Начало
Вы замечали, что на собеседованиях на старшие должности вас всё меньше гоняют по техническим вопросам? Вместо этого спрашивают что-то вроде: «Что такое технический долг?» Или «Что вы делаете, когда сроки срываются? Как расставляете приоритеты в задачах?» Быть сеньором не значит освоить каждый протокол или запомнить каждый алгоритм. Это рассудительность, работа с людьми, принятие решений, долгосрочное мышление. За годы опыта вы совершаете ошибки, и хорошо, если извлекаете из них уроки. Рассмотрим некоторые наиболее важные уроки на пути к сеньорской должности. Будем использовать вымышленного персонажа Эдди.
1. Рассуждения важнее правил
Однажды нам нужно было добавить новые поля в таблицу DynamoDB в рабочей среде. Эдди, блестящий джун, предложил тяжёлую миграцию: реплицировать таблицу, синхронизировать её с потоками, а затем, после недели тестирования, выполнить переход. План казался исчерпывающим, но он не рассматривал более простые варианты. Почему не обновить существующие записи с помощью скрипта? «Это опасно. Нельзя обновлять данные БД в проде», - воскликнул Эдди, без каких-либо обоснований или данных. Но в этой системе БД всего лишь обеспечивала работу офлайн-заданий, поэтому риск был невелик.
Подход Эдди отражал распространённую ловушку: воспринимать «лучшие практики» как абсолютную истину. «Никогда не трогайте прод». «Микросервисы всегда лучше монолита». Правила кажутся безопасными, но они зависят от контекста. Опытный разработчик - не тот, у которого в арсенале обширный набор правил. Он всегда старается объяснить, почему решение имеет смысл в данной ситуации, и взвешивает риски и компромиссы.
2. Не делайте предположений, проверяйте
Когда системы выходят из строя, легко сделать поспешные выводы: «Должно быть, дело в БД», «Возможно, это новое развёртывание». Но предположения отнимают драгоценные часы, а когда прод лежит, каждая минута на счету. Лучшие инженеры не полагаются на догадки. Они доверяют данным и используют свой опыт, чтобы определить, где искать в первую очередь, но всегда проверяют. Настоящее мастерство не в «правильной догадке», а в дисциплине, позволяющей проверять логи, воспроизводить проблемы и просить коллег проверить работоспособность. Проверка решает проблемы быстрее и укрепляет доверие. Ничто не подрывает доверие быстрее, чем самоуверенное обвинение в чём-то неправильном.
3. Сомневайтесь в хороших новостях
Однажды Эдди на радостях воскликнул: «API работает на 20% быстрее после моих изменений!». Странно, его просили просто отрефакторить код. Это выглядело как победа, но, когда что-то выглядит слишком хорошо, чтобы быть правдой, так оно и есть. После анализа мы выяснили: он случайно удалил логику повторных попыток в наших нисходящих вызовах. За блестящими цифрами копились скрытые сбои.
Всегда относитесь к внезапным чудесам скептически. Когда показатели внезапно улучшаются, первым вопросом должен быть: «Что мы сломали?» Здоровый скептицизм не даёт праздновать ложные победы. Он подкрепляет истину: данные ценны, только когда вы понимаете, почему они возникли.
4. Механизм важнее благих намерений
«Мы просто не забудем сделать X» - это всегда исходит из благих намерений, но они не защищают системы. Люди отвлекаются, устают или поджимают сроки. Однажды критический инцидент произошёл из-за того, что кто-то забыл запустить скрипт после развёртывания. Дело не в невнимательности, а в хрупкости процесса. Самые сильные команды не полагаются на память или обещания. Автоматизированные проверки, конвейеры CI/CD, флаги функций, ревью кода — это не «приятные мелочи», а защитные барьеры. Вместо того, чтобы доверять кому-то ручной запуск скрипта, вы делаете скрипт частью процесса развёртывания.
Ведущие инженеры проектируют системы, где безопасный путь — это также и самый простой. Ошибки неизбежны. Сильную инженерную культуру определяет способность системы выявлять эти ошибки раньше, чем это сделают ваши клиенты.
Окончание следует…
Источник: https://levelup.gitconnected.com/beyond-the-code-lessons-that-make-you-senior-1ba44469aa42
Не Только Код: Что Делает Тебя Сеньором. Начало
Вы замечали, что на собеседованиях на старшие должности вас всё меньше гоняют по техническим вопросам? Вместо этого спрашивают что-то вроде: «Что такое технический долг?» Или «Что вы делаете, когда сроки срываются? Как расставляете приоритеты в задачах?» Быть сеньором не значит освоить каждый протокол или запомнить каждый алгоритм. Это рассудительность, работа с людьми, принятие решений, долгосрочное мышление. За годы опыта вы совершаете ошибки, и хорошо, если извлекаете из них уроки. Рассмотрим некоторые наиболее важные уроки на пути к сеньорской должности. Будем использовать вымышленного персонажа Эдди.
1. Рассуждения важнее правил
Однажды нам нужно было добавить новые поля в таблицу DynamoDB в рабочей среде. Эдди, блестящий джун, предложил тяжёлую миграцию: реплицировать таблицу, синхронизировать её с потоками, а затем, после недели тестирования, выполнить переход. План казался исчерпывающим, но он не рассматривал более простые варианты. Почему не обновить существующие записи с помощью скрипта? «Это опасно. Нельзя обновлять данные БД в проде», - воскликнул Эдди, без каких-либо обоснований или данных. Но в этой системе БД всего лишь обеспечивала работу офлайн-заданий, поэтому риск был невелик.
Подход Эдди отражал распространённую ловушку: воспринимать «лучшие практики» как абсолютную истину. «Никогда не трогайте прод». «Микросервисы всегда лучше монолита». Правила кажутся безопасными, но они зависят от контекста. Опытный разработчик - не тот, у которого в арсенале обширный набор правил. Он всегда старается объяснить, почему решение имеет смысл в данной ситуации, и взвешивает риски и компромиссы.
2. Не делайте предположений, проверяйте
Когда системы выходят из строя, легко сделать поспешные выводы: «Должно быть, дело в БД», «Возможно, это новое развёртывание». Но предположения отнимают драгоценные часы, а когда прод лежит, каждая минута на счету. Лучшие инженеры не полагаются на догадки. Они доверяют данным и используют свой опыт, чтобы определить, где искать в первую очередь, но всегда проверяют. Настоящее мастерство не в «правильной догадке», а в дисциплине, позволяющей проверять логи, воспроизводить проблемы и просить коллег проверить работоспособность. Проверка решает проблемы быстрее и укрепляет доверие. Ничто не подрывает доверие быстрее, чем самоуверенное обвинение в чём-то неправильном.
3. Сомневайтесь в хороших новостях
Однажды Эдди на радостях воскликнул: «API работает на 20% быстрее после моих изменений!». Странно, его просили просто отрефакторить код. Это выглядело как победа, но, когда что-то выглядит слишком хорошо, чтобы быть правдой, так оно и есть. После анализа мы выяснили: он случайно удалил логику повторных попыток в наших нисходящих вызовах. За блестящими цифрами копились скрытые сбои.
Всегда относитесь к внезапным чудесам скептически. Когда показатели внезапно улучшаются, первым вопросом должен быть: «Что мы сломали?» Здоровый скептицизм не даёт праздновать ложные победы. Он подкрепляет истину: данные ценны, только когда вы понимаете, почему они возникли.
4. Механизм важнее благих намерений
«Мы просто не забудем сделать X» - это всегда исходит из благих намерений, но они не защищают системы. Люди отвлекаются, устают или поджимают сроки. Однажды критический инцидент произошёл из-за того, что кто-то забыл запустить скрипт после развёртывания. Дело не в невнимательности, а в хрупкости процесса. Самые сильные команды не полагаются на память или обещания. Автоматизированные проверки, конвейеры CI/CD, флаги функций, ревью кода — это не «приятные мелочи», а защитные барьеры. Вместо того, чтобы доверять кому-то ручной запуск скрипта, вы делаете скрипт частью процесса развёртывания.
Ведущие инженеры проектируют системы, где безопасный путь — это также и самый простой. Ошибки неизбежны. Сильную инженерную культуру определяет способность системы выявлять эти ошибки раньше, чем это сделают ваши клиенты.
Окончание следует…
Источник: https://levelup.gitconnected.com/beyond-the-code-lessons-that-make-you-senior-1ba44469aa42
👍31
День 2447. #Карьера
Не Только Код: Что Делает Тебя Сеньором. Окончание
Начало
5. Дисциплина отказа
Однажды Эдди был на встрече с одним из клиентов. Они использовали наши API и хотели внедрить новую логику фильтрации. Вместо того, чтобы реализовывать её на своей стороне, они предложили сделать это на нашем бэкенде. Они убедили Эдди согласиться. Но когда он поделился решением с командой, все ведущие инженеры выразили несогласие. У клиентов не было веских причин так делать, они просто хотели переложить всю сложность на нас.
Это один из самых сложных уроков в карьере. Сеньоры не из тех, кто на всё отвечает «да». Они защищают свои команды от ненужной сложности, отвергают бессмысленные компромиссы, и понимают, что не каждый запрос заслуживает одобрения.
6. Рост начинается с ответственности
В начале карьеры мы во многом полагаемся на старших коллег. Но инженерия полна неопределённости. Старшие коллеги — не те, кто всегда знает правильный ответ; они принимают обоснованные решения, взвешивают компромиссы и берут на себя ответственность. Когда эти решения оказываются неправильными, они не перекладывают вину на других, а принимают последствия, учатся и корректируют свои действия.
7. От защитника к наставнику
Естественным инстинктом является оградить младших коллег от неудач. Оставлять длинные комментарии, объяснять каждую деталь или даже исправлять их код. Это может предотвратить сиюминутные трудности, но также лишает их возможности извлекать уроки, которые можно извлечь только из личного опыта.
Роль сеньора не в предотвращении ошибок, а в создании безопасного пространства, где ошибки могут происходить без катастрофических последствий. Настоящий рост достигается благодаря ошибкам, восстановлению и дальнейшему развитию. Высший показатель лидерства — способность команды процветать без вас. Если вы создали культуру, в которой люди учатся на безопасных ошибках, то вы выполнили свою задачу наставника.
8. Простота масштабируется, сложность ломается
Сделать просто почти всегда лучше, чем заумно. Иногда возникает соблазн проектировать систему с учётом всех возможных сценариев, «закладывая фундамент» в будущее с помощью абстракций и расширяемости. Но чаще всего эти дополнительные уровни превращаются в мёртвый груз. Старайтесь избегать преждевременной сложности любой ценой. Простые решения не только проще писать, но и легче тестировать, поддерживать и адаптировать к ним новых разработчиков.
9. Каждая система рано или поздно ломается, будьте готовы
Ни одна система не является по-настоящему отказоустойчивой. Код живой: он развивается, интегрируется с новыми зависимостями и адаптируется к меняющимся требованиям. Каждое изменение, каким бы небольшим и хорошо протестированным оно ни было, увеличивает вероятность возникновения сбоев. Сеньоры предвидят это, чётко соблюдая гарантии, тщательно контролируя версии API и настраивая мониторинг и т.п. Тесты ценны, но не являются панацеей. При достаточном времени, достаточном трафике и достаточном количестве изменений любая система выйдет из строя. Вопрос - когда и насколько вы готовы к этому.
10. Принимайте изменения и адаптируйтесь
Изменения постоянны, и их темп только ускоряется. Сейчас LLM уже стали частью повседневных рабочих процессов. Есть ли вокруг них хайп? Да. Но было бы ошибкой предполагать, что они просто исчезнут, а мы станем работать по старинке. Они продолжат совершенствоваться и постепенно брать на себя всё больше ответственности.
Мы ещё в начале пути к пониманию долгосрочного влияния LLM. Но уже понятно, что они не заменят инженерное суждение, а при разумном использовании могут ускорить процесс.
Поэтому принятие изменений не означает слепого доверия к коду, сгенерированному ИИ, и не предполагает, что он решит все проблемы. Это означает быть в курсе событий, осторожно экспериментировать и интегрировать то, что действительно приносит пользу, учитывая риски.
Источник: https://levelup.gitconnected.com/beyond-the-code-lessons-that-make-you-senior-1ba44469aa42
Не Только Код: Что Делает Тебя Сеньором. Окончание
Начало
5. Дисциплина отказа
Однажды Эдди был на встрече с одним из клиентов. Они использовали наши API и хотели внедрить новую логику фильтрации. Вместо того, чтобы реализовывать её на своей стороне, они предложили сделать это на нашем бэкенде. Они убедили Эдди согласиться. Но когда он поделился решением с командой, все ведущие инженеры выразили несогласие. У клиентов не было веских причин так делать, они просто хотели переложить всю сложность на нас.
Это один из самых сложных уроков в карьере. Сеньоры не из тех, кто на всё отвечает «да». Они защищают свои команды от ненужной сложности, отвергают бессмысленные компромиссы, и понимают, что не каждый запрос заслуживает одобрения.
6. Рост начинается с ответственности
В начале карьеры мы во многом полагаемся на старших коллег. Но инженерия полна неопределённости. Старшие коллеги — не те, кто всегда знает правильный ответ; они принимают обоснованные решения, взвешивают компромиссы и берут на себя ответственность. Когда эти решения оказываются неправильными, они не перекладывают вину на других, а принимают последствия, учатся и корректируют свои действия.
7. От защитника к наставнику
Естественным инстинктом является оградить младших коллег от неудач. Оставлять длинные комментарии, объяснять каждую деталь или даже исправлять их код. Это может предотвратить сиюминутные трудности, но также лишает их возможности извлекать уроки, которые можно извлечь только из личного опыта.
Роль сеньора не в предотвращении ошибок, а в создании безопасного пространства, где ошибки могут происходить без катастрофических последствий. Настоящий рост достигается благодаря ошибкам, восстановлению и дальнейшему развитию. Высший показатель лидерства — способность команды процветать без вас. Если вы создали культуру, в которой люди учатся на безопасных ошибках, то вы выполнили свою задачу наставника.
8. Простота масштабируется, сложность ломается
Сделать просто почти всегда лучше, чем заумно. Иногда возникает соблазн проектировать систему с учётом всех возможных сценариев, «закладывая фундамент» в будущее с помощью абстракций и расширяемости. Но чаще всего эти дополнительные уровни превращаются в мёртвый груз. Старайтесь избегать преждевременной сложности любой ценой. Простые решения не только проще писать, но и легче тестировать, поддерживать и адаптировать к ним новых разработчиков.
9. Каждая система рано или поздно ломается, будьте готовы
Ни одна система не является по-настоящему отказоустойчивой. Код живой: он развивается, интегрируется с новыми зависимостями и адаптируется к меняющимся требованиям. Каждое изменение, каким бы небольшим и хорошо протестированным оно ни было, увеличивает вероятность возникновения сбоев. Сеньоры предвидят это, чётко соблюдая гарантии, тщательно контролируя версии API и настраивая мониторинг и т.п. Тесты ценны, но не являются панацеей. При достаточном времени, достаточном трафике и достаточном количестве изменений любая система выйдет из строя. Вопрос - когда и насколько вы готовы к этому.
10. Принимайте изменения и адаптируйтесь
Изменения постоянны, и их темп только ускоряется. Сейчас LLM уже стали частью повседневных рабочих процессов. Есть ли вокруг них хайп? Да. Но было бы ошибкой предполагать, что они просто исчезнут, а мы станем работать по старинке. Они продолжат совершенствоваться и постепенно брать на себя всё больше ответственности.
Мы ещё в начале пути к пониманию долгосрочного влияния LLM. Но уже понятно, что они не заменят инженерное суждение, а при разумном использовании могут ускорить процесс.
Поэтому принятие изменений не означает слепого доверия к коду, сгенерированному ИИ, и не предполагает, что он решит все проблемы. Это означает быть в курсе событий, осторожно экспериментировать и интегрировать то, что действительно приносит пользу, учитывая риски.
Источник: https://levelup.gitconnected.com/beyond-the-code-lessons-that-make-you-senior-1ba44469aa42
👍32