<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>Богдан Стефанюк</title>
<link>https://stefaniuk.website/</link>
<description>Всім привіт! Я — Богдан, фулстек розробник з Києва. Пишу в основному про програмування та штуки які вивчаю. Час від часу публікую підбірки фоток, зроблених на плівку, розказую про подорожі та цікаві речі, що оточують мене.</description>
<author></author>
<language>uk</language>
<generator>Aegea 11.2 (v4116)</generator>

<itunes:subtitle>Всім привіт! Я — Богдан, фулстек розробник з Києва. Пишу в основному про програмування та штуки які вивчаю. Час від часу публікую підбірки фоток, зроблених на плівку, розказую про подорожі та цікаві речі, що оточують мене.</itunes:subtitle>
<itunes:image href="" />
<itunes:explicit></itunes:explicit>

<item>
<title>Push Notification via ASP.NET Core</title>
<guid isPermaLink="false">277</guid>
<link>https://stefaniuk.website/all/push-notification-via-asp-net-core/</link>
<pubDate>Thu, 08 Jun 2023 19:43:17 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/push-notification-via-asp-net-core/</comments>
<description>
&lt;p&gt;Недавно запустили на роботі новий функціонал інбоксів. Якщо коротко, то це універсальний месенджер з підтримкою різних платформ та каналів що дозволяє вести комунікацію з кінцевими клієнтами в одному місці.&lt;/p&gt;
&lt;p&gt;Для кращого UX треба було добавити пуш нотифікації. На цей момент у нас уже були підключені веб сокети по яким ганяли різні дані, в тому числі і про нові повідомлення. Але такі сповіщення можна відобразити тільки коли відкрита сторінка в браузері.&lt;/p&gt;
&lt;p&gt;Тому вирішили добавити підтримку браузерних пушів. Саме про те як їх підключити та використовувати в звʼязці з ASP.NET Core я хочу розказати.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;Як це працює?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;Процес роботи технології пуш нотифікацій у браузері досить простий. Спочатку ми запрошуємо права на отримання сповіщень у користувача. Браузер покаже йому модалку із запитанням «Do you want to receive push notifications?».&lt;/p&gt;
&lt;p&gt;Після того як він підтвердить вибір нам потрібно підписати користувача на отримання нотифікацій використовуючи Push API браузера. Під капотом браузер робить запит на спеціальний push service та повертає нам обʼєкт PushSubscription в якому є вся необхідна інформація для відправки пушів:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{
  &amp;quot;endpoint&amp;quot;: &amp;quot;https://fcm.googleapis.com/fcm/send/c1KrmpTuRm…&amp;quot;,
  &amp;quot;expirationTime&amp;quot;: null,
  &amp;quot;keys&amp;quot;: {
    &amp;quot;p256dh&amp;quot;: &amp;quot;BGyyVt9FFV…&amp;quot;,
    &amp;quot;auth&amp;quot;: &amp;quot;R9sidzkcdf…&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Далі ми берем цей обʼєкт і відправляємо на наш сервер, де кладемо його у базу даних або зберігаємо іншим способом щоб використовувати його в подальшому.&lt;/p&gt;
&lt;p&gt;Якщо придивитися до структури PushSubscription, можна помітити адрес ендпоінта. Суть в тому, що наш сервер не відправляє сповіщення напряму в браузер. Для цього використовується спеціальний push service, який у кожного розробника браузера свій.&lt;/p&gt;
&lt;div style="max-width: 740px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/push-notifications-structure.png" width="1761" height="646" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Нам повезло, що браузери змогли домовитись і стандартизувати протокол push сервіса, тому нам можна не паритись за формат даних для кожного вендора. Усі вони приймають один формат даних, який містить:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Контент повідомлення&lt;/li&gt;
&lt;li&gt;Кому його відправити&lt;/li&gt;
&lt;li&gt;Як саме його відправити, з яким пріорітетом, в який топік та з яким таймаутом.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Кожне повідомлення шифрується за допомогою ключів, що були створені при генерації підписки. Крім цього, потрібно також підписувати повідомлення за допомогою приватного ключа, щоб підтвердити, що це саме наш сервер відправляє повідомлення. Виглядає це ось так:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Генеруємо пару публічного та приватного ключа, які ще називають VAPID ключами. Цей крок треба зробити всього один раз. В інтернеті купа онлайн сервісів де можна згенерувати ключі.&lt;/li&gt;
&lt;li&gt;Публічний ключ використовуємо при створенні підписки на стороні браузера. Дальше цей ключ асоціюється з ендпоінтом для конкретної підписки.&lt;/li&gt;
&lt;li&gt;Приватний ключ використовуємо для підписки повідомлення на стороні нашого сервера.&lt;/li&gt;
&lt;li&gt;Дальше за допомогою публічного ключа ми можемо перевірити що повідомлення було відправлено саме нашим сервером а не кимось іншим.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Структура VAPID ключа:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{
    &amp;quot;sub&amp;quot;: &amp;quot;mailto:bohdan@stefaniuk.io&amp;quot;,
    &amp;quot;public_key&amp;quot;: &amp;quot;sdgdklsnvi2f0m-12dgsg...&amp;quot;,
    &amp;quot;private_key&amp;quot;: &amp;quot;1fn0ASFfnaksf...&amp;quot;
 }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Але, як в тому жарті, є нюанс. Subject в VAPID ключі є опціональним і його не обовʼязково відправляти разом з запитом на push сервіс. Проте Сафарі так не вважає, ще й вимагає специфічний формат. Коли я генерував ключі, то мав ось такий sub &lt;i&gt;mailto: &amp;lt;bohdan@stefaniuk.io&amp;gt;&lt;/i&gt;.&lt;/p&gt;
&lt;p&gt;В хромі все працювало чудово, а сафарі кидав помилку, що запит невалідний. Виявилось, що вони не підтримують пробіли та кутові лапки, замінив sub на &lt;i&gt;mailto:bohdan@stefaniuk.io&lt;/i&gt; і все запрацювало.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;Реалізація бекенду&lt;/b&gt;&lt;/h2&gt;
&lt;p class="note-md"&gt;Якщо у вас macOS, то ви не зможете локально протестувати сповіщення. Проблема в тому що .NET не підтримує шифрування AesGcm на маках, тому при відправці вилетить ексепшен. Поки я не знайшов як це обійти.&lt;/p&gt;
&lt;p&gt;На стороні сервера будемо використовувати бібліотеку &lt;i&gt;Lib.Net.Http.WebPush&lt;/i&gt; яка під капотом буде шифрувати, підписувати та відправляти наші сповіщення. Для цього вона дає декілька базових модельок та один клас для відправки пушів.&lt;/p&gt;
&lt;p&gt;Почнемо з реалізації створення та видалення підписок та зберігання їх в БД. Для цього добавимо клас ApplicationPushSubscription, який описує структуру таблиці та підключимо його в Entity Framework контекст:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;public class ApplicationPushSubscription
{
    [Key]
    public string P256Dh { get; set; }
    public string Endpoint { get; set; }
    public string Auth { get; set; }
    public Guid UserId { get; set } // Id користувача в нашій системі
}

public class DatabaseContext : DbContext
{
    public DbSet&amp;lt;ApplicationPushSubscription&amp;gt; PushSubscriptions { get; set; }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Тепер створимо клас PushNotificationsService в який добавимо методи для створення та видалення підписок. Метод створення буде приймати екземпляр класу PushSubscription з бібліотеки Lib.Net.Http.WebPush.&lt;/p&gt;
&lt;p&gt;Його можна було б змінити на свій клас, щоб логіка бібліотеки не просочувалась в інші модулі але задля простоти залишимо так як є.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;public class PushNotificationsService
{
    private readonly DatabaseContext _context;
    private readonly PushServiceClient _pushClient;
    
    public PushNotificationsService (DatabaseContext context, PushServiceClient pushClient)
    {
        _context = context;
        _pushClient = pushClient;
    }
    
    public async Task CreateSubscription(Guid userId, PushSubscription pushSubscription)
    {
        var subscription = new ApplicationPushSubscription()
        {
            UserId = userId,
            AccountId = currentUser.AccountId,
            Endpoint = subscription.Endpoint,
            P256Dh = subscription.GetKey(PushEncryptionKeyName.P256DH),
            Auth = subscription.GetKey(PushEncryptionKeyName.Auth)
        };
        
        if (await _context.PushSubscriptions.AnyAsync(x =&amp;gt; x.P256Dh == subscription.P256Dh))
        {
            return;
        }
        
        _context.PushSubscriptions.Add(subscription);
    
        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateException)
        {
            var alreadyExists = await _context.PushSubscriptions.AnyAsync(x =&amp;gt; x.P256Dh == subscription.P256Dh);
            if (!alreadyExists)
            {
                throw;
            }
        }
    }
    
    public async Task DeleteSubscription(Guid userId, string endpoint)
    {
        var subscription = await _context.PushSubscriptions
            .FirstOrDefaultAsync(x =&amp;gt; x.UserId == userId &amp;amp;&amp;amp; x.Endpoint == endpoint);
        
        if (subscription == null)
        {
            return;
        }
        
        _context.PushSubscriptions.Remove(subscription);
        await _context.SaveChangesAsync();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Реалізовуємо метод для відправки сповіщень нашим користувачам. Він буде приймати Id користувача якому ми відправляємо повідомлення і модель з даними.&lt;/p&gt;
&lt;p&gt;Нам залишається отримати підписку з БД, серіалізувати повідомлення в JSON і скормити це все бібліотеці.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;public async Task SendNotificationAsync(Guid userId, PushNotificationModel model)
{
    try
    {
        var applicationPushSubscriptions = await _context.PushNotifications
            .Where(x =&amp;gt; x.UserId == userId)
            .ToList();
            
        foreach(var applicationPushSubscription in applicationPushSubscriptions)
        {
            var pushSubscription = new PushSubscription();
            pushSubscription.Endpoint = applicationPushSubscription.Endpoint;
            pushSubscription.SetKey(PushEncryptionKeyName.P256DH, applicationPushSubscription.P256Dh);
            pushSubscription.SetKey(PushEncryptionKeyName.Auth, applicationPushSubscription.Auth);
            
            var settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
            var payload = JsonConvert.SerializeObject(model, settings);
    
            var pushMessage = new PushMessage(payload);
            await _pushClient.RequestPushMessageDeliveryAsync(
                pushSubscription,
                pushMessage,
                _pushClient.DefaultAuthentication,
                VapidAuthenticationScheme.WebPush);
        }
    }
    catch (PushServiceClientException exception)
    {
        if (exception.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Gone)
        {
            _context.PushSubscriptions.Remove(subscription);
            await _context.SaveChangesAsync();
        }
        else
        {
            // Re-Throw exception or log
        }
    }
    catch (Exception exception)
    {
        // Re-Throw exception or log
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Структура класу &lt;i&gt;PushNotificationModel&lt;/i&gt; досить проста та універсальна. Я передаю заголовок сповіщення, текст та додаткові дані:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;public class PushNotificationModel
{
    public string Title { get; set; }
    public string Body { get; set; }
    public object AdditionalData { get; set; }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Підключаємо наш сервіс в ендопінт:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;[Route(&amp;quot;api/push-notifications&amp;quot;)]
[Authorize]
[ApiController]
public class PushNotificationsController : ControllerBase
{
    private readonly PushNotificationService _pushNotificationService;

    public PushNotificationsController(PushNotificationService pushNotificationService)
    {
        _pushNotificationService = pushNotificationService;
    }
    
    [HttpPost(&amp;quot;subscriptions&amp;quot;)]
    public async Task&amp;lt;IActionResult&amp;gt; CreateSubscription([FromBody] PushSubscription subscription)
    {
        var user = HttpContext.GetCurrentUser(); // Our custom method to get current logged-in user
        await _pushNotificationService.CreateSubscription(user.Id, subscription);
        return Ok();
    }

    [HttpDelete(&amp;quot;subscriptions&amp;quot;)]
    public async Task&amp;lt;IActionResult&amp;gt; DeleteSubscription([FromQuery] string endpoint)
    {
        var user = HttpContext.GetCurrentUser();
        await _pushNotificationService.DeleteSubscription(user.Id, endpoint);
        return Ok();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Все що залишається — підключити наші класи в DI і все готово. Для простоти підключення можемо добавити ще один пакет &lt;i&gt;Lib.AspNetCore.WebPush&lt;/i&gt;.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;services.AddTransient&amp;lt;PushNotificationService&amp;gt;();
services.AddMemoryVapidTokenCache();
services.AddPushServiceClient(options =&amp;gt;
{
    var clientOptions = configuration.GetSection(nameof(PushServiceClient));
    options.Subject = clientOptions.GetValue&amp;lt;string&amp;gt;(nameof(options.Subject));
    options.PublicKey = clientOptions.GetValue&amp;lt;string&amp;gt;(nameof(options.PublicKey));
    options.PrivateKey = clientOptions.GetValue&amp;lt;string&amp;gt;(nameof(options.PrivateKey));
});&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;&lt;b&gt;Реалізація фронтенда&lt;/b&gt;&lt;/h2&gt;
&lt;p class="note-md"&gt;Підтримка технології Web Push була додана в Сафарі тільки в версії MacOS 13 та вище.&lt;/p&gt;
&lt;p&gt;Перше, що потрібно реалізувати на стороні клієнта — це запит прав на отримання сповіщень. Для цього використовуємо обʼєкт &lt;i&gt;Notification&lt;/i&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;// Запит у браузера прав на підключення пушей
async function askPermission() {
    const permissionPromiseResult = await new Promise(function (resolve, reject) {
        const permissionResult = Notification.requestPermission(function (result) {
            resolve(result);
        });

        if (permissionResult) {
            permissionResult.then(resolve, reject);
        }
    });
    
    if (permissionPromiseResult !== 'granted') {
        alert(&amp;quot;We weren't granted permission.&amp;quot;);
    } else {
        await subscribeUserToPush();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Після того, як отримали права, нам потрібно зареєструвати service worker в якому буде логіка відображення сповіщень, створити підписку та відправити її на наш сервер.&lt;/p&gt;
&lt;p&gt;При створенні підписки необхідно передати наш публічний ключ. Але він повинен бути в форматі масиву байтів, тому треба спочатку його декодувати і вже тоді передавати.&lt;/p&gt;
&lt;p&gt;В результаті отримуємо обʼєкт &lt;i&gt;pushSubscription&lt;/i&gt;, який ми відправляємо на створений раніше ендпоінт. Крім цього, ще відправляємо &lt;i&gt;Bearer&lt;/i&gt; токен, щоб розуміти до якого користувача привʼязати підписку.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;const publicKey = &amp;quot;vapid-public-key&amp;quot;;

async function subscribeUserToPush() {
    const registration = await navigator.serviceWorker.register('/service-worker.js');
    const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(publicKey),
    };

    var pushSubscription = await registration.pushManager.subscribe(subscribeOptions);
   
    fetch('http://localhost:5000/api/push-notifications/subscriptions', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer 1245'
        },
        body: JSON.stringify(pushSubscription)
    }).then(function (response) {
        if (response.ok) {
            alert('Successfully subscribed for Push Notifications');
        } else {
            alert('Failed to store the Push Notifications subscription on server');
        }
    }).catch(function (error) {
        console.log('Failed to store the Push Notifications subscription on server: ' + error);
    });
    
    return pushSubscription;
}

function urlBase64ToUint8Array(base64String) {
    var padding = '='.repeat((4 - (base64String.length % 4)) % 4);
    var base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');

    var rawData = window.atob(base64);
    var outputArray = new Uint8Array(rawData.length);

    for (var i = 0; i &amp;lt; rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Наш сервіс воркер буде максимально простим. Для початку додамо логіку відображення пушей та передачі в них додаткових даних з сервера.&lt;/p&gt;
&lt;p&gt;Також необхідно додати логіку обробки кліку по сповіщенню. Ми будемо перевіряти чи відкритий сайт в браузері. Якщо ні, то відкривати його в новій вкладці. А якщо у нас вже є вкладка з сайтом, то переключаємось на неї і робимо редірект на потрібну сторінку.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;self.addEventListener('push', function (event) {
    var data = event.data.json();
    // {
    //     &amp;quot;title&amp;quot;: &amp;quot;New Notification&amp;quot;,
    //     &amp;quot;body&amp;quot;: &amp;quot;This is the body of the notification&amp;quot;
    //     &amp;quot;additionalData&amp;quot;: {
    //         &amp;quot;pageId&amp;quot;: &amp;quot;12512515&amp;quot;
    //     }
    // }

    event.waitUntil(self.registration.showNotification(data.title, {
        body: data.body,
        icon: '/logo.png',
        data: {
            pageId: data.additionalData?.pageId,
        },
    }));
});

self.addEventListener('notificationclick', function (event) {
    var notification = event.notification;

    event.waitUntil(
        self.clients.matchAll().then(function (allClients) {
            if (allClients.length === 0) {
                self.clients.openWindow(
                    `${self.location.origin}/page/${notification?.data?.pageId}`,
                );
                notification.close();
                return;
            }

            allClients[0]?.navigate(`/page/${notification?.data?.pageId}`);
            allClients[0]?.focus();
            notification.close();
        }),
    );
});&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В результаті всіх цих маніпуляцій у нас є сервер, який вміє відправляти сповіщення та клієнт, який вміє створювати підписки, відображати сповіщення та обробляти кліки.&lt;/p&gt;
&lt;p&gt;Окремо ще можна поговорити про топіки та пріоритети сповіщень, але це виходить за рамки цієї статті. Можливо, розкажу про це пізніше :)&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;Ресурси&lt;/b&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.dev/push-notifications-overview/"&gt;Оглад того як працюють пуші&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tpeczek.com/2017/12/push-notifications-and-aspnet-core-part.html"&gt;Стаття з прикладом реалізації пушів за допомогою ASP.NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tpeczek/Demo.AspNetCore.PushNotifications"&gt;Репозиторій з прикладами використання бібліотеки&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
</item>

<item>
<title>Пам’ять про минуле Землі, Liu Cixin</title>
<guid isPermaLink="false">276</guid>
<link>https://stefaniuk.website/all/remembrance-of-earths-past/</link>
<pubDate>Fri, 12 May 2023 17:04:09 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/remembrance-of-earths-past/</comments>
<description>
&lt;div style="max-width: 700px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/remembrance-of-earths-past.png" width="1200" height="626" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Недавно я захотів почитати якусь фантастику. Чекнув свій список книг, там була «Загадна трьох тіл» від Liu Cixin. Багато хто її рекомендував і після прочитання було зрозуміло чому. Тоді я ще не знав, що наступні одинадцять днів шукатиму будь-яку можливість прочитати бодай декілька сторінок.&lt;/p&gt;
&lt;p&gt;До цього мене тільки двічі так втягувало в книги, це були «Мартен Іден» Джека Лондона та «На західному фронті без змін» Ремарка. Я не міг спокійно ходити в магазин, гуляти та навіть в приймати душ. Читав в транспорті, в чергах, коли йшов по вулиці, перед сном і після сну. Тобто всюди, де була така можливість.&lt;/p&gt;
&lt;p&gt;В цей період я дуже зацінив синхронізацію Kindle, коли читаєш дома з рідера, а вже в метро продовжуєш з того ж місця, але в телефоні.&lt;/p&gt;
&lt;p&gt;Щодо самої трилогії, то її дії починаються десь в 60-х роках минулого століття під час культурної революції в Китаї. В центрі історії донька відомого вченого, яка пішла по слідам свого батька після того, як його вбили за антиреволюційні погляди. Далі книга декілька разів змінює головних героїв, а закінчується все через десятки мільйонів років.&lt;/p&gt;
&lt;p&gt;В книзі широко використовується велика кількість концепцій з сучасної науки, такі як астрономія, квантова фізика та математика. Це сприяє більшому зануренню в історію, адже ти починаєш вірити у реальну правдивість історії.&lt;/p&gt;
&lt;p&gt;Єдиною проблемою для мене стали персонажі. Їх в якийсь момент з’явилось дуже багато і майже всі з китайськими іменами. На початку було важко тримати в голові хто є хто і що зробив, але згодом в контексті стає зрозуміло.&lt;/p&gt;
&lt;p&gt;Також я для себе підмітив цікаві аналогії з сучасними подіями, а саме відношення звичайних громадян ЄС та США до війни в Україні. Досить точно передані люди, для яких війна це щось далеке, про що не хочеться думати. І, звісно, зображені прямі наслідки такої «позиції».&lt;/p&gt;
&lt;p&gt;Післясмак для мене залишився доволі приємним, та головне — відкривається інше бачення події, проблем та їх масштабу.&lt;/p&gt;
&lt;p&gt;Однозначно рекомендую!&lt;/p&gt;
</description>
</item>

<item>
<title>EDC</title>
<guid isPermaLink="false">275</guid>
<link>https://stefaniuk.website/all/edc/</link>
<pubDate>Fri, 05 May 2023 09:13:23 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/edc/</comments>
<description>
&lt;p&gt;Every Day Carry це рух, учасники якого постійно діляться речами які вони носять з собою кожного дня, радять один одному що купити, колекціонують різні штуки, пишуть гайди і т. д.&lt;/p&gt;
&lt;p&gt;Я досить довго спостерігав за цією тусовкою і з часом сам вирішив в неї влитися. Все починається з простих та зручних наборів і чим глибше поринаєш в цю тему, тим більше ти відкриваєш для себе скільки всього люди з собою носять. Там цілі набори для виживання в міському, та не тільки, середовищі. В них і пили, і ножі, і великі аптечки, і запаси їжі, і десятки метрів паракорду, і рації, і набори для рибалки тощо&lt;/p&gt;
&lt;p&gt;Це все виглядає дуже круто та цікаво, але я не уявляю як це все можна носити з собою кожного дня. Тому мій набір виглядає досить скромно, в ньому тільки необхідний мінімум речей. На фото мій максимальний комплект, який я можу брати з собою, але в основному ношу тільки частину цього.&lt;/p&gt;
&lt;div style="max-width: 1000px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/edc-1.jpeg" width="1400" height="928" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;На фото:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bellroy Ventura Sling 6L — бананка на 6 літрів в якій можна носити все необхідне.&lt;/li&gt;
&lt;li&gt;Olympus Superzoom 70G — проста та надійна плівкова мильниця.&lt;/li&gt;
&lt;li&gt;Bellroy Flip Case 2 — мінімалістичний гаманець з правами, декількома карточками та мінімальною кількістю кеша.&lt;/li&gt;
&lt;li&gt;Nitecore TINI 2 — мініатюрний фонарик з максимальною потужністю 500 люмен.&lt;/li&gt;
&lt;li&gt;Мікро аптечка з пластирами, дезінфікуючими салфетками та таблетками від голови.&lt;/li&gt;
&lt;li&gt;Apple AirPod 3&lt;/li&gt;
&lt;li&gt;Apple AirTag&lt;/li&gt;
&lt;li&gt;Victorinox Climber — швейцарський ніж, зручно відкривати пиво, вино чи щось підзірати.&lt;/li&gt;
&lt;li&gt;Пару сухих серветок та серветки для виведення плям.&lt;/li&gt;
&lt;li&gt;Звичайні сонцезахисні окуляри.&lt;/li&gt;
&lt;li&gt;Універсальний кабель inCharge X з всіма можливими розʼємами.&lt;/li&gt;
&lt;li&gt;Блокнот Moleskine яким я рідко користуюсь, тому, скоріше за все, перестану його носити.&lt;/li&gt;
&lt;li&gt;iPhone 13 Mini&lt;/li&gt;
&lt;li&gt;Годинник Casio — мій основний годинник, ношу його частіше ніж Apple Watch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;В поїздки я завжди кидаю в рюкзак свій мультитул Leatherman Rebar. Це такий собі кишеньковий ящик з інструментами яким можна відремонтувати все що захочеш. Я ним пилив невелику ялинку на новий рік, підкручував меблі, знімав розряджений акумулятор в машині, а потім встановлював його назад. І багато чого ще робив.&lt;/p&gt;
&lt;div style="max-width: 500px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/edc-2.jpeg" width="1400" height="1400" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Також в будь-якій поїздці зі мною якась із плівкових камер, тому разом з нею я беру ось такий невеликий контейнер на три катушки. Купив його в Берліні.&lt;/p&gt;
&lt;div style="max-width: 500px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/edc-3.jpeg" width="1400" height="1050" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Щоб заряджати все своє електронне барахло в мене є GaN зарядка UGREEN Nexode 65W з набором змінних вилок під різні розетки. Нею можна одночасно заряджати до трьох пристроїв, мені вона замінила зарядку для ноутбука.&lt;/p&gt;
&lt;div style="max-width: 500px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/edc-4.jpeg" width="1400" height="1400" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Це в прицнипі все, на останок залишу пару посилань по темі:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/EDC/"&gt;Reddit: Everyday Carry. What essentials do you carry on a daily basis?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLZUB6PLjHyZihDVVOgh78tRb8e0YxRxBA"&gt;Плейлист It’s a Good Trip про EDC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Найкращий відос по темі який можна знайти: &lt;a href="https://www.youtube.com/watch?v=GP1oEEYpry4"&gt;EDC набор для города 2022. Переосмысление&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
</item>

<item>
<title>Про нотатки</title>
<guid isPermaLink="false">274</guid>
<link>https://stefaniuk.website/all/about-notes/</link>
<pubDate>Mon, 27 Feb 2023 17:47:50 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/about-notes/</comments>
<description>
&lt;div style="max-width: 740px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/notes-27-02-2023.jpg" width="2560" height="1440" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Софт який пройшов через мене&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Не так давно з’явився великий тренд на ведення нотаток. Майже кожен місяць, якщо не частіше, виходять нові програми чи нові методики для ведення, групування та організації нотаток.&lt;/p&gt;
&lt;p&gt;В ютубі сотні відосів де всі хвалять той чи інший підхід: зетелькастен, evergreen notes, PARA, другий мозок, персональна вікі, сотні їх. Це стало новою модою. Популярність настільки велика, що їй важко чинити опір. Тож і я залюбки став пасажиром цього хайп-трейну.&lt;/p&gt;
&lt;p&gt;Як тільки виходила нова апка, то я обов’язково качав її, дивився відгуки й переносив все своє добро в нове місце. Витрачаючи велику кількість сил та часу, щоб все по новій організувати. Ця погоня за ідеальним блокнотом замінила собою саму суть створення нотаток.&lt;/p&gt;
&lt;p&gt;В якийсь момент починається автоматичне створення нотаток заради нотаток. І все тільки для того, щоб це красиво виглядало і вписувалось у нову модель організації. В мене було з сотню нотаток про C#, хоча вся ця інформація легко гуглиться і лежить в документації. Тому я ними не користувався, а одразу йшов в гугл.&lt;/p&gt;
&lt;p&gt;Складність інструментів та методик, з часом, приводили до того, що пропадало будь-яке бажання робити записи. Кожен раз, відкриваючи апку, я замість створення нотатки думав над тим яка в неї повинна бути структура, куди її треба покласти, щоб це відповідало методиці.&lt;/p&gt;
&lt;p&gt;Також, за час постійних переїздів, вискочила ще одна проблема — проприєтарний софт. Кожен другий стартап старається завʼязати вас на свою екосистему без можливості вивантажити свої дані, а якщо така можливість є, то це зроблено настільки погано, що просто немає бажання влазити в це. Коли ж мова йде про нотатки, то не хочеться, щоб вони пропали разом зі збанкрутілою компанією.&lt;/p&gt;
&lt;p&gt;Тому я на довго забив та вів записи як попало. Проте десь пів року назад я вирішив спробувати заново, але кардинально змінивши підхід.&lt;/p&gt;
&lt;p&gt;Першим ділом під видалення попали майже всі нотатки, що стосувалися програмування. Я вибрав найпростіший інструмент, який був мені доступним із коробки — Apple Notes. Коли у вас вся екосистема від Apple, то створювати та синхронізувати нотатки стає набагато легше.&lt;/p&gt;
&lt;p&gt;Наступним кроком була відмова від бездумного накопичування нотаток. Додаю тільки ту інформацію, яка рідко змінюється або важко гуглиться. Сюди можна віднести різні концепції із computer science чи system design. Зазвичай це важко нагуглити або інформація розкидана по різних книжках чи статтях. В такому випадку буде корисним зібрати все до купи й описати це зрозумілою для себе мовою.&lt;/p&gt;
&lt;p&gt;Все, що стосується мов програмування, бібліотек, функцій та методів зазвичай дуже легко гуглиться і немає сенсу додавати це до себе.&lt;/p&gt;
&lt;p&gt;Проте, одного разу мені стало тісно в Apple Notes, тоді під руку трапилась програма Nota. Власне кажучи, це звичайний редактор Markdown файлів без наворотів. Тому все, що стосується програмування та IT, переїхало туди.&lt;/p&gt;
&lt;p&gt;І тут я хочу підмітити одну важливу думку: не кидайтесь на першу-ліпшу програму. Почніть з самого простого і розвиньте звичку просто вести нотатки. Візьміть за правило не змінювати інструмент раніше шести місяців. Користуйтесь та відмічайте те, чого вам не вистачає. Вже потім шукайте інструмент який зможе закрити саме ваші проблеми, а не навʼязані.&lt;/p&gt;
&lt;p&gt;Зараз мій сетап виглядає максимально просто. Всі персональні нотатки живуть в Apple Notes, а все що стосується ІТ живе в Nota.&lt;/p&gt;
&lt;p&gt;Я не проти Obsidian, Roam чи іншого подібного софта. Для когось вони дійсно ідеальні та закривають всі потреби. Я лише хочу застерегти вас від хайпу та навʼязування вам проблем чи потреб, яких ви не мали. Можливо, зрештою, найкомфортнішою програмою саме для вас стане Obsidian, але це буде чітке та кайфове рішення, яке закриватиме необхідні потреби.&lt;/p&gt;
&lt;p&gt;Найцікавіше, що це стосується не тільки блокнотів, але і софта для todo-списків. Я мав ті ж самі проблеми, що описав. В якийсь момент був модним GTD і я намагався максимально інтегрувати його у своє життя. Правда потім дивувався чому система не працює, а насправді вона просто не була потрібна мені. Я міг обійтись звичайним текстовим файлом, але піддався хайпу і пробував натягнути сову на глобус.&lt;/p&gt;
&lt;p&gt;Як підсумок, бажаю вам знайти ідеальні для вас інструменти, які будуть закривати ваші потреби та допомагатимуть кожного дня.&lt;/p&gt;
</description>
</item>

<item>
<title>Фото-нотатки за літо</title>
<guid isPermaLink="false">272</guid>
<link>https://stefaniuk.website/all/photo-notes-09-2022/</link>
<pubDate>Sat, 12 Nov 2022 05:36:19 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/photo-notes-09-2022/</comments>
<description>
&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-1-1.jpg" width="1661" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-2.jpg" width="1658" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-3.jpg" width="1697" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-4.jpg" width="1697" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-5.jpg" width="1697" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-6.jpg" width="1697" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-7.jpg" width="2560" height="1697" alt="" /&gt;
&lt;/div&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-8.jpg" width="1800" height="2251" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-9.jpg" width="1800" height="2260" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-12.jpg" width="1697" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-13.jpg" width="1697" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-14.jpg" width="1697" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-15.jpg" width="1697" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-16.jpg" width="1664" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-17.jpg" width="1664" height="2560" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-10.jpg" width="1800" height="2251" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/september-photos-11.jpg" width="1800" height="2251" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;style&gt;
.category-block { width: 49%;  display: inline-block; vertical-align: top; margin-right: 1%; margin-bottom: 20px;}
@media (max-width: 900px) {
.category-block { width: 100%; margin-right: unset;}
}
&lt;/style&gt;
</description>
</item>

<item>
<title>Очередь с приоритетом</title>
<guid isPermaLink="false">271</guid>
<link>https://stefaniuk.website/all/ordered-queue-and-heap/</link>
<pubDate>Tue, 05 Jul 2022 15:29:29 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/ordered-queue-and-heap/</comments>
<description>
&lt;p&gt;Представим что нужно спроектировать список задач. У каждой задачи есть приоритет, это значит, что вначале должны быть самые приоритетные задачи. Сами задачи можно обрабатывать только по одной, мы не можем взять задачу с центра списка.&lt;/p&gt;
&lt;div style="max-width: 450px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/heap-pic-1.png" width="628" height="240" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Такой список достаточно просто реализовать с помощью обычного массива. Задачи будем хранить в случайном порядке, а в момент получения текущей задачи будем искать самую приоритетную. В таком случае добавление новой задачи будет иметь сложность O(1), а вот получение O(n), потому что каждый раз нужно искать элемент с самым большим приоритетом.&lt;/p&gt;
&lt;p&gt;Второй вариант тоже использует обычный массив, только элементы в нем хранятся уже в отсортированном виде. В таком случае сложность меняется зеркально. Добавление нового элемента будет занимать O(n), а получение O(1).&lt;/p&gt;
&lt;p&gt;Оба варианта не совсем эффективны, особенно когда очень много данных. Здесь на помощь приходят очереди с приоритетом.&lt;/p&gt;
&lt;p&gt;Очереди с приоритетом строятся на базе такой структуры данных как куча. Поэтому важно рассмотреть ее устройство. Куч есть большое множество, но нас сейчас интересует только двоичная куча.&lt;/p&gt;
&lt;h2&gt;Двоичная куча&lt;/h2&gt;
&lt;p&gt;Двоичная куча представляет из себя двоичное дерево где каждый элемент имеет два дочерних элемента и хранит значение не меньшее чем в дочерних. Они также делятся на два типа: максимальная и минимальная. В первой хранятся элементы в порядке убывания, а во второй в порядке возрастания.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/heap-max-min-2.png" width="1587" height="712" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Для хранения данных кучи удобно использовать обычные массивы. Такой подход занимает мало места и сам по себе достаточно элегантный. На как определить какой элемент массива на какой ссылается? Для этого есть две простые формулы.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2i + 1 — позволяет найти позицию левого дочернего элемента&lt;/li&gt;
&lt;li&gt;2i + 2 — позволяет найти позицию правого дочернего элемента&lt;/li&gt;
&lt;/ul&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/heap-array-structure-3.png" width="1500" height="552" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2&gt;Сортировка дерева&lt;/h2&gt;
&lt;div style="max-width: 760px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/heap-sorting-4.png" width="2104" height="695" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Когда добавляем или удаляем элемент из очереди, свойства кучи могут нарушиться. В таком случае нужно привести ее в порядок. Представим, что есть метод MakeHeap, который принимает на вход индекс элемента и он сортирует его поддерево.&lt;/p&gt;
&lt;p&gt;Берем элемент по текущему индексу и сравниваем его с дочерними элементами. Если какой-то дочерний элемент больше текущего, меняем его местами с текущим и спускаемся дальше, чтобы проверить поддерево. Таким образом, мы доходим до конца изначального поддерева сортируя все его поддеревья.&lt;/p&gt;
&lt;p&gt;Как это выглядит в коде:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;private void SortHeap(int i)
{
var size = values.Count;
var left = 2 * i + 1;
var right = 2 * i + 2;
var largest = i;

if (left &amp;lt; size &amp;amp;&amp;amp; values[left] &amp;gt; values[largest])
largest = left;

if (right &amp;lt; size &amp;amp;&amp;amp; values[right] &amp;gt; values[largest])
largest = right;

if (largest != i)
{
var temp = values[i];
values[i] = values[largest];
values[largest] = temp;
SortHeap(largest);
}
}&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Делаем кучу из обычного массива&lt;/h2&gt;
&lt;p&gt;Мы умеем сортировать элементы в поддеревьях, теперь применим этот метод для того, чтобы с обычного массива сделать кучу. Так как потомки гарантированно есть у первых (n/2 — 1) элементов, то достаточно только для них вызвать метод SortHeap.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;private void MakeHeap()
{
for (int i = values.Count / 2 - 1; i &amp;gt;= 0; i--)
{
SortHeap(i);
}
}&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Добавление нового элемента&lt;/h2&gt;
&lt;div style="max-width: 760px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/heap-insert-5.png" width="2225" height="650" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Новый элемент сначала добавляется в конец массива, после чего запускаем сортировку дерева. Алгоритм получается достаточно простым:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;if (values.Count == 0)
{
// Просто добавляем в элемент в массив, если он пустой
values.Add(value);
}
else
{
// Добавляем элемент в конец массива и запускаем сортировку дерева
values.Add(value);
for (int i = values.Count / 2 - 1; i &amp;gt;= 0; i--)
{
SortHeap(i);
}
}&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Удаление элемента&lt;/h2&gt;
&lt;div style="max-width: 760px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/heap-remove-5.png" width="2183" height="652" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Чтобы удалить элемент нужно поменять его с последним элементов в куче, потом удалить и запустить сортировку кучи. Так как мы делаем очередь, то будем всегда удалять первый (корневой) элемент кучи.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;var size = values.Count;

var last = values.Count - 1;
var temp = values[0];
values[0] = values[last];
values[last] = temp;
values.RemoveAt(last);

for (int i = size / 2 - 1; i &amp;gt;= 0; i--)
SortHeap(i);

return temp;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Итого&lt;/h2&gt;
&lt;p&gt;Куча обладает необходимыми свойствами для создания очереди с приоритетом. Она хранит данные в отсортированном виде, работает быстрее чем обычный массив. Достаточно реализовать такие же методы как в обычной очереди и у нас получиться очередь с приоритетом:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enqueue — обычная вставка в кучу, ничего даже менять не нужно.&lt;/li&gt;
&lt;li&gt;Dequeue — обычное удаление элемента с кучи, только удаляем мы всегда элемент с 0 индексов.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Посмотреть полную реализацию можно на &lt;a href="https://dotnetfiddle.net/Smd2bH"&gt;dotnetfiddle&lt;/a&gt;.&lt;br /&gt;
 &lt;/p&gt;
</description>
</item>

<item>
<title>Последнее прочитанное</title>
<guid isPermaLink="false">270</guid>
<link>https://stefaniuk.website/all/last-read/</link>
<pubDate>Wed, 22 Jun 2022 03:28:39 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/last-read/</comments>
<description>
&lt;div style="max-width: 740px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/last-read-20-06-2022.jpeg" width="1305" height="456" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Редко пишу о книгах, которые читал или читаю. В этот раз решил рассказать сразу о нескольких прочитанных книгах. Они из разных жанров, но каждая чем-то меня зацепила.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Стив Джобс&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Пока что это самая интересная биография из всех, которые я читал. В ней описана вся история Джобса начиная со школьных времён и заканчивая смертью. Больше всего мне понравилось описания принципов, которыми он руководствовался при создании продуктов. Очень интересно было прочитать про создание Macintosh, iPod и iPhone. Книгу прочитал с большим удовольствие и думаю что еще перечитаю спустя какое-то время.&lt;/p&gt;
&lt;p&gt;Книга большая, за один раз не проглотить. Читал в бумажном виде и держал на столе, чтобы читать в свободную минуту. Определенно 5/5.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Твой лучший друг, желудок&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Встречал эту книгу очень часто в разных блогах и статьях про питание, да и вообще про здоровый образ жизни. Спустя два года я все-таки решил ее прочитать.&lt;/p&gt;
&lt;p&gt;В книге подробно рассказывается про то, как работает вся наша система пищеварения, за что отвечает каждый орган, как вообще пища влияет на жизнь. Вся эта информация подкреплена современными научными исследованиями, на которые много ссылок. Мне больше всего понравилась критика современных диет, БАДов и прочее. Также интересно было узнать про влияние пищи на долголетие.&lt;/p&gt;
&lt;p&gt;Заметил только один минус — иногда встречается слишком много медицинской терминологии. Но я понимаю, что без нее никуда, да и в принципе на ней можно не заострять внимание. Главное это понять идеи, которые там заложены и общее понимание работы организма. Советую прочитать тем, кто худеет (как я) и тем, кто переживает по поводу еды.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Твой первый трек&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;В конце прошлого года увлёкся электронной музыкой, прошел курс, отыграл небольшой сет и даже выпустил один трек. Но мне хотелось иметь под рукой какой-то конспект, в который можно подглядывать, когда нужно.&lt;/p&gt;
&lt;p&gt;Для меня таким конспектом и гайдом стала книга «Твой первый трек». В ней полностью разбирается процесс написание музыки, от наброска простого бита до мастеринга и психологической составляющей творчества.&lt;/p&gt;
&lt;p&gt;В книге просто тонна иллюстраций. Весь материал очень понятно расписан, собственно это книга для тех, кто хочет себя попробовать в музыке и написать свой первый трек. Для меня она тоже оказалась очень полезной. Постоянно в нее заглядываю, чтобы вспомнить какие-то вещи, особенно что касается аранжировки, сведения и мастеринга.&lt;/p&gt;
&lt;p&gt;Статья о том как создавалась книга: &lt;a href="https://maximilyahov.ru/blog/all/maskeliade-design/"&gt;https://maximilyahov.ru/blog/all/maskeliade-design/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Грокаем алгоритмы&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Это классика, это знать надо. Лучшая книга по алгоритмам и структурам данных. Пока читал, несколько раз словил «Aha moment». Каждая идея, каждый концепт очень подробно расписан и приправлен иллюстрациями. Такого качества материала я еще не встречал.&lt;/p&gt;
&lt;p&gt;Подойдет вообще любому программисту любого уровня. Тем, кто уже понимает работу базовых алгоритмов, поможет разложить все по полочкам. Новичкам же поможет изучить все основные идеи и базовые структуры и алгоритмы.&lt;/p&gt;
</description>
</item>

<item>
<title>Бинарный поиск и бинарное дерево поиска</title>
<guid isPermaLink="false">269</guid>
<link>https://stefaniuk.website/all/binary-search-and-binary-search-tree/</link>
<pubDate>Sat, 11 Jun 2022 13:00:56 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/binary-search-and-binary-search-tree/</comments>
<description>
&lt;p&gt;Бинарный поиск один из самых популярных и простых алгоритмов поиска. Он активно используется во многих системах, например в базах данных для быстрого поиска и индексирования.&lt;/p&gt;
&lt;p&gt;Первое что нужно знать — алгоритм работает только на отсортированных данных. Поэтому перед поиском нужно отсортировать массив.&lt;/p&gt;
&lt;p&gt;Начинается алгоритм с того что берем элемент из середины массива и сравниваем с искомым значением. Если искомый элемент больше среднего, тогда искомое значение лежит в правой части массива. Идем в правую часть массива, опять берем элемент из середины и сравниваем его с искомым. В этот раз искомый элемент меньше среднего, тогда он скорее всего находится в левой части этого под массива.&lt;/p&gt;
&lt;p&gt;Таким образом мы постоянно сравниваем искомое значение со значением из середины массива. После этого откидываем половину значений. Это здорово ускоряет поиск, потому что не нужно проверять все элементы массива. На каждом этапе отбрасывается половина элементов.&lt;/p&gt;
&lt;p&gt;В итоге всех операций наступает момент когда искать приходиться в массиве из одного элемента. В таком случае просто сравниваем с искомым, если равны, тогда мы нашли элемент, если не, тогда не нашли.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/binary-search-1.png" width="524" height="215" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;А теперь живой пример. Есть отсортированный массив и нужно найти число 9. Берем элемент из середины — 8, сравниваем, 9 больше 8, значит нужно продолжить поиск в правой части.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/binary-search-2.png" width="497" height="200" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Снова берем элемент из середины — 11, 9 меньше 11, значит нужно искать в левой части нашего подмассива.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/binary-search-3.png" width="740" height="200" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Берем элемент из середины — 9, сравниваем, элементы равны, число 9 есть в массиве и храниться по индексу 4. Мы нашли нужный элемент всего за 3 шага.&lt;/p&gt;
&lt;p&gt;Если пробовать оценить скорость работы алгоритма, то получается что она равна O(log n). Для сравнения, обычный поиск перебором имеет скорость O(n), это значит что нужно перебрать все элементы массива. Что это значит на практике?&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/binary-search-speed.png" width="968" height="170" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Посмотреть реализацию на C# можно на &lt;a href="https://dotnetfiddle.net/LjCl90"&gt;dotnetfiddle&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Как оптимизировать сортировку?&lt;/h2&gt;
&lt;p&gt;Массив всегда должен быть отсортированным чтобы поиск работал, а это значит что при каждом добавлении нового элемента нужно запускать сортировку. Возникает вопрос, а что если сразу вставлять новый элемент в правильное место? Тогда у нас всегда будет отсортированный массив и не нужно каждый раз прогонять сортировку.&lt;/p&gt;
&lt;p&gt;Так же подумали умные ребята и придумали бинарное дерево поиска. Это структура данных, которая основывается на работе бинарного поиска. По сути это реализация бинарного поиска в виде структуры данных.&lt;/p&gt;
&lt;p&gt;Структура дерева достаточно простая: каждый элемент может иметь только два дочерних элемента. Их еще часто называют левым и правым. В левом дочернем элементе храниться значение, меньше чем в родительском, а в правом — больше.&lt;/p&gt;
&lt;h2&gt;Как работает поиск в дереве?&lt;/h2&gt;
&lt;p&gt;Сравниваем искомое значение со значением в корневом элементе. Если оно больше, тогда мы идем к правому дочернему элементу и повторяем процедуру. Таким образом мы начинаем с самого верха и плавно опускаемся пока не найдем элемент с таким же значением.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/binary-search-tree-1.png" width="399" height="324" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Вставка нового элемента работает так же как и поиск. Сравниваем новый элемент с текущим и опускаемся по дереву вниз пока не найдем подходящее место. В итоге при каждом добавлении у нас будет отсортированный набор данных.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/binary-search-tree-2.png" width="468" height="410" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Но что будет если всегда добавлять элементы с все большим и большим значением? Наше дерево будет выглядеть вот так:&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/binary-search-tree-3.png" width="562" height="512" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;По сути для поиска значения на нужно обойти все элементы дерева, а это значит что скорость работы упадет до O(n). Получается что бинарное дерево поиска в среднем работает со скоростью O(log n), а в худшем O(n).&lt;/p&gt;
&lt;p&gt;Такие деревья еще называют не сбалансированными. Чтобы исправить ситуацию существуют специальные алгоритмы балансировки деревьев, которые приводят их в нормальный вид. Но рассматривать здесь я их не буду.&lt;/p&gt;
&lt;h2&gt;Итог&lt;/h2&gt;
&lt;p&gt;Бинарный поиск один из самых простых и элегантных алгоритмов поиска, который привел к созданию новой структуры данных — бинарного дерева поиска. Данная структура является очень популярной и используется по многих системах. Особенно активно в базах данных для создания индексов и оптимизации выборки данных.&lt;/p&gt;
&lt;p&gt;Если тема интересная, то можно копнуть дальше и разобраться с:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Красно черными деревьями&lt;/li&gt;
&lt;li&gt;Кучами&lt;/li&gt;
&lt;li&gt;B-деревьями&lt;/li&gt;
&lt;li&gt;и т. д.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Бонус&lt;/h2&gt;
&lt;p&gt;На сайте &lt;a href="https://idea-instructions.com"&gt;&lt;a href="https://idea-instructions.com"&gt;https://idea-instructions.com&lt;/a&gt;&lt;/a&gt; есть объяснения разных алгоритмов в стиле инструкций с икеи. Например &lt;a href="https://idea-instructions.com/avl-tree/"&gt;AVL дерево&lt;/a&gt;&lt;/p&gt;
</description>
</item>

<item>
<title>Kindle</title>
<guid isPermaLink="false">268</guid>
<link>https://stefaniuk.website/all/kindle/</link>
<pubDate>Sun, 15 May 2022 06:51:32 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/kindle/</comments>
<description>
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/amazon-kindle-review-1.jpeg" width="1240" height="1240" alt="" /&gt;
&lt;/div&gt;
&lt;div&gt;&lt;p&gt;Недавно у меня был День Рождения и друзья подарили электронную книгу Amazon Kindle Paperwhite. До этого я уже пробовал использовать читалку, но впечатления были смешанными. Самым неприятным были размеры книги, когда больший корпус, но экран занимает всего половину плоскости, а в некоторых книгах еще и полноценная клавиатура была.&lt;/p&gt;
&lt;p&gt;В большинстве случаев читаю книги на телефона, технические — на компьютере. Киндл позволил отлипнуть от телефона и без отвлечений читать. Это хорошо вписывается в мои текущие настроения по уменьшению потребляемой информации, но это отдельная история.&lt;/p&gt;
&lt;p&gt;Что мне понравилось в Kindle? Небольшой размер, думал купить большую читалку, но понял что маленький размер намного оптимальнее. Ее удобно держать одной рукой и при этом она не устаёт. Также она помещается в карманы куртки или в бананке.&lt;/p&gt;
&lt;p&gt;Подсветка, спасает в темное время суток. Тут Амазон постарался и сделал качественно, минимальная яркость не выжигает глаза ночью, как в некоторых других ридерах.&lt;/p&gt;
&lt;p&gt;Минусы. Заметил только один — собственный формат mobi. Если покупать книги онлайн, то проблемы с форматами не будет, почти все магазины позволяют скачать книгу в любом удобном формате. Если же качать книги с других сайтов, то могут быть проблемы, потому что не все есть в формате mobi.&lt;/p&gt;
&lt;p&gt;На днях амазон анонсировал что будет уходить от mobi в сторону epub, но я пока не пробовал как оно работает. Обновление еще не пришло.&lt;/p&gt;
&lt;p&gt;В общем электронная книга это крутая альтернатива тупежу в телефон. С ней реально хочется больше читать.&lt;/p&gt;
</description>
</item>

<item>
<title>Транзакции в базах данных и ACID</title>
<guid isPermaLink="false">265</guid>
<link>https://stefaniuk.website/all/transactions-and-acid/</link>
<pubDate>Fri, 06 May 2022 09:20:27 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/transactions-and-acid/</comments>
<description>
&lt;p&gt;Транзакции поддерживают все реляционные базы данных и некоторые NoSQL базы. Представляют они из себя простой набор команд, который должен быть выполнен как одно целое. В большинстве случает транзакция представляет некую бизнес операцию.&lt;/p&gt;
&lt;p&gt;Базы данных гарантируют что все запросы в рамках транзакции выполняться как одно целое или не выполняться вообще. Отталкиваясь от этой концепции был придуман набор требований к транзакциям и системам, которые их используют. Эти правила гарантируют надежную работу транзакций. Такой набор требований называется ACID.&lt;/p&gt;
&lt;p&gt;ACID гарантирует что данные в БД будут целостные независимо от любых сбоев.&lt;/p&gt;
&lt;p&gt;Всего есть четыре свойства у транзакций:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Atomicity (атомарность)&lt;/b&gt; — команды внутри транзакции буду выполнены все вместе или ни одной. То есть транзакция это атомарная команда. Достигается это за счет системы откатов и журнала транзакций. Если внутри транзакции какой-то запрос выполнился с ошибкой, то все изменения, сделанные в рамках этой транзакции откатываются.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Сonsistency (консистентность)&lt;/b&gt; — данные должны быть консистентными после выполнения транзакции. Это значит что в них нету логических и технических противоречий. Например: суммарный баланс счетов должен оставаться неизменным, это логическая целостность. Записи в таблицах не ссылаются на удаленные идентификаторы в другой таблице — техническая целостность.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Isolation (изолированность)&lt;/b&gt; — транзакции зачастую обрабатываются параллельно, это значит что они не должны влиять друг на друга. Так если два человека одновременно пересылают деньги третьему, то одна транзакция может переписать данные другой и деньги потеряются. На практике изоляция достигается за счет уровней изоляции.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Durability (стойкость)&lt;/b&gt; — если транзакция завершена успешно, то она не может быть отменена даже при авариях, внезапном отключении света в датацентре и проблем в сети. В этом случае база данных должна сама восстановить последние изменения.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Отдельно стоит упомянуть уровни изоляции, потому что их понимание позволяет находить баги в коде, который работает с базой. Самих уровней существую большое множество, но рассмотрим четыре основных:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;read uncommited&lt;/b&gt; — позволяет избежать потерянных обновлений, когда две транзакции изменяют одни и те же данные. Для этого одна транзакция блокирует данные, которые хочет изменить для других UPDATE  операций в других транзакциях.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;read committed&lt;/b&gt; — решает проблему грязного чтения, когда вычитываем данные во время их обновления. Это может привести к тому что мы получим частично обновленные данные. В таком случае UPDATE операции в транзакции блокируют UPDATE и SELECT операции в других транзакциях. Именно этот уровень изоляции используется по умолчанию в большинстве БД.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;repeatable read&lt;/b&gt; — внутри транзакции может быть несколько операций SELECT, которые читают одни и те же данные. Repeatable read гарантирует что это операции внутри одной транзакции будут возвращать одинаковые данные. Даже если другие транзакции хотят их удалить или изменить. В таком случает транзакция блокирует все строчки, которые затрагивают операции UPDATE и SELECT.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;serializable&lt;/b&gt; — исключает проблему «фантомных чтений», которые возникают при вставке новой строки между двумя операциями SELECT внутри одной транзакции. Больше всего эта проблема влияет на агрегационные запросы. Такой уровень изоляции является самым строгим, все транзакции выполняются так, будто других параллельных транзакций не существует.&lt;/li&gt;
&lt;/ul&gt;
</description>
</item>

<item>
<title>Folder by Type и Folder by Feature</title>
<guid isPermaLink="false">266</guid>
<link>https://stefaniuk.website/all/folder-by-type-and-folder-by-feature/</link>
<pubDate>Sat, 30 Apr 2022 08:59:29 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/folder-by-type-and-folder-by-feature/</comments>
<description>
&lt;p&gt;ASP.NET, как и большинство других фреймворков, предлагают использовать свою структуру организации файлов в проекте. Почти всегда используется folder by type подход. Он предполагает разделение файлов по типу. Так все контроллеры лежать в одной папке, вьюхи в другой и т. д.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;com.example
├── Domain
│    ├── User.cs
│    └── Pet.cs
├── Controllers
│    ├── UserController.cs
│    └── PetController.cs
├── Repositories
│    ├── UserRepository.cs
│    └── PetRepository.cs
├── Services
│    ├── UserService.cs
│    └── PetService.cs
│   // and everything else in the project
└── Startup.cs&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Такой подход неплохо работает на небольших проектах, когда файлов немного и фичи состоят из ограниченного количества классов. Проблемы возникают при росте проекта. Когда разрабатываешь большую фичу нужно постоянно держать в голове что в какой папке лежит, постоянно переключаясь между ними. Частично эту проблему решают IDE, но только частично.&lt;/p&gt;
&lt;p&gt;Альтернативой является folder by feature подход, при котором все файлы одной фичи лежат в отдельной папке. В таком случае все необходимое для конкретной фичи лежит под рукой и нет ничего лишнего.&lt;/p&gt;
&lt;p&gt;Это позволяет:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Упростить навигацию в проекте.&lt;/li&gt;
&lt;li&gt;Построить высокоуровневую абстракцию — открываешь проект и сразу понятно что он из себя представляет, из чего состоит.&lt;/li&gt;
&lt;li&gt;Выделить вертикальные слои.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;com.example
├── Pet
│    ├── Pet.cs
│    ├── PetController.cs
│    ├── PetRepository.cs
│    └── PetService.cs
├── User
│    ├── User.cs
│    ├── UserController.cs
│    ├── UserRepository.cs
│    └── UserService.cs
│   // and everything else in the project
└── Startup.cs&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Обычно проблемы возникают когда появляется общий код, который нужно использовать в нескольких частях приложения. Но, в принципе, это известная проблема и ее можно решить грамотной нарезкой на вертикальные слои. Вторая проблема достаточно банальная — привычки, большинство программистов привыкли к классической компоновки.&lt;/p&gt;
&lt;p&gt;Ссылки:&lt;br /&gt;
&lt;a href="https://softwareengineering.stackexchange.com/questions/338597/folder-by-type-or-folder-by-feature"&gt;Folder-by-type or Folder-by-feature&lt;/a&gt;&lt;/p&gt;
</description>
</item>

<item>
<title>Интересные статьи</title>
<guid isPermaLink="false">267</guid>
<link>https://stefaniuk.website/all/links-24-04-2022/</link>
<pubDate>Sun, 24 Apr 2022 04:39:07 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/links-24-04-2022/</comments>
<description>
&lt;p&gt;Решил вернуться к своему блогу после столь долгой паузы. Начну с небольшой подборки статей, которые я недавно прочитал и которые мне очень понравились.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://boringtechnology.club"&gt;Choose Boring Technology&lt;/a&gt; — статья-презентация от одного из первых разработчиков Etsy. Рассказывает про выбор технологий для проекта. Основная идея в том чтобы выбирать «скучные», а если быть точнее, то хорошо изученные и устоявшиеся технологии. Горячо советую почитать.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://lethain.com/introduction-to-architecting-systems-for-scale/"&gt;Introduction to architecting systems for scale&lt;/a&gt; — кратко о том из чего состоят приложения, которые рассчитаны для масштабирование.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://jvns.ca/blog/2022/03/13/celebrate-tiny-learning-milestones/"&gt;Celebrate tiny learning milestones&lt;/a&gt; — не совсем техническая статья. Вместо того чтобы придумывать себе большие цели на год, лучше придумать набор небольших целей «майлстоунов» и идти по ним.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://jvns.ca/blog/brag-documents"&gt;Get your work recognized: write a brag document&lt;/a&gt; — очень частая проблема это распознавание того что ты сделал на проекте. Когда приходит перформанс ревью, то бывает сложно вспомнить все что ты сделал за год. Автор советует завести документ в который нужно записывать результаты своей работы, а именно то, что было сделано и к чему это привело.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://henrikwarne.com/2020/07/23/good-logging/"&gt;Good Logging&lt;/a&gt; — коротко о том какими должны быть логи чтобы ими было удобно пользоваться. Ну и сразу еще две статьи от этого же автора: &lt;a href="https://henrikwarne.com/2013/05/05/great-programmers-write-debuggable-code/"&gt;Great Programmers Write Debuggable Code&lt;/a&gt; и &lt;a href="https://henrikwarne.com/2014/01/01/finding-bugs-debugger-versus-logging/"&gt;Finding Bugs: Debugger versus Logging&lt;/a&gt;&lt;/p&gt;
</description>
</item>

<item>
<title>Война в Украине</title>
<guid isPermaLink="false">264</guid>
<link>https://stefaniuk.website/all/war-in-ukraine/</link>
<pubDate>Wed, 02 Mar 2022 06:21:56 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/war-in-ukraine/</comments>
<description>
&lt;p&gt;Никогда не мог подумать что война прийдет ко мне в дом. Сегодня сидишь себе спокойно, работаешь, гуляешь по улице, а уже завтра просыпаешься от взрывов в 6 утра, собираешь вещи, едешь на вокзал под звуки истребителей над головой и уезжаешь в другую часть страны.&lt;/p&gt;
&lt;p&gt;Но больше всего меня поражает та жестокость с которой ведаться эта война. Это война не за какую-то проведу или еще что-то. Это война на уничтожение. Война на уничтожение Украины. Страны в которой я вырос и в которой я живу. Которую я очень люблю.&lt;/p&gt;
&lt;p&gt;Хочу обратиться к читателям с белоруссии и россии. Чтобы вам там не говорили по телевизору, здесь настоящая война, здесь настоящий ад на земле. И это дело рук ваших «доблестных» солдат. В первой версии этого поста я просил выйти вас на улицу, поднимать шум. Сейчас я понимаю что это бессмысленно. Часть вас просто боится, а второй части пофиг. Попрошу вторых забыть этот блог и никогда на него не заходить.&lt;/p&gt;
&lt;p&gt;Ниже просто подборка фото и видео, напоминание об этой войне. О том что здесь происходит.&lt;/p&gt;
&lt;div style="max-width: 720px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;div class="fotorama" data-width="1200" data-ratio="1"&gt;
&lt;img src="https://stefaniuk.website/pictures/IMG_7001.JPG" width="1200" height="1200" alt="" /&gt;
&lt;img src="https://stefaniuk.website/pictures/IMG_7002.JPG" width="1200" height="1200" alt="" /&gt;
&lt;img src="https://stefaniuk.website/pictures/IMG_7003.JPG" width="1200" height="1200" alt="" /&gt;
&lt;img src="https://stefaniuk.website/pictures/IMG_7004.JPG" width="1200" height="1200" alt="" /&gt;
&lt;img src="https://stefaniuk.website/pictures/IMG_7005.JPG" width="1200" height="1200" alt="" /&gt;
&lt;img src="https://stefaniuk.website/pictures/IMG_7006.JPG" width="1200" height="1200" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="max-width: 720px"&gt;&lt;div class="e2-text-video"&gt;
&lt;video src="https://stefaniuk.website/video/telegram-cloud-document-5-6116446583883039737.mp4#t=0.001" width="1280" height="720" controls alt="" /&gt;

&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Победа будет за нами! Слава Україні!&lt;/p&gt;
</description>
</item>

<item>
<title>Электронная музыка</title>
<guid isPermaLink="false">263</guid>
<link>https://stefaniuk.website/all/electronic-music/</link>
<pubDate>Sun, 26 Dec 2021 17:15:18 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/electronic-music/</comments>
<description>
&lt;p&gt;В этом году получилось осуществить своё давнее желание — научиться писать электронную музыку.&lt;/p&gt;
&lt;p&gt;Эта история началась в школьные годы, тогда я очень любил смотреть видео как люди делают музыку. Тогда же я познакомился с FL Studio. Скачал, открыл и непонимающе смотрел на все это. После начал читать какие-то гайды, но все свелось к тому что я просто сидел во всяких синтезаторах, крутил случайные лучки и слушал, что получается. Приблизительно в тоже время я познакомился с программированием, и оно затянуло меня по полной.&lt;/p&gt;
&lt;p&gt;Потом я поступил в лицей, где познакомился с моим хорошим другом. Он тоже любил электронную музыку и также интересовался как ее делают. Часто смотрели всякие видосы о музыке, особенно доставляли те на которых ребята играют на «железках». Особенно удивил Launchpad от Novation.&lt;/p&gt;
&lt;p&gt;Потом был универ, там я уже по полной влился в программирование, но желание делать музыку не покидало. Так что я как-то раз скачал Ableton и пробовал в нем что-то делать, но все равно далеко я не продвинулся.&lt;/p&gt;
&lt;p&gt;В начале этого года я чисто случайно наткнулся на рекламу &lt;a href="https://module.live"&gt;школы электронной музыки&lt;/a&gt; в инстаграме. Сразу зацепило то, что занятия проходят в офлайне. Особенно после года удалённой работы сильно хотелось пообщаться с новыми людьми вживую. Также заинтересовали преподаватели, они достаточно известные ребята на украинской электронной сцене. Несколько раз даже был у них на выступлениях. Ну и последним интересным для меня пунктом стало живое выступление в конце курса на выпускной вечеринке.&lt;/p&gt;
&lt;div style=""&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/music-hobbie-school-1.jpg" width="1920" height="2560" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Помещение школы&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="category-block"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/music-hobbie-school-2.jpeg" width="960" height="1280" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Учебный класс, на столе у каждого есть Ableton Push 2&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Летом увидел, что школа также проводит публичные джемы, на которых ученики и другие ребята играют совместно музыку. Сходил, послушал, очень проникся атмосферой и уже на 90% был готов записаться, осталось только выбрать когда. Потом сходил на ещё один джем, после которого уже точно решил, что буду у них учиться.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;div class="fotorama" data-width="1400" data-ratio="1.5086206896552"&gt;
&lt;img src="https://stefaniuk.website/pictures/music-hobbie-jam-1.jpeg" width="1400" height="928" alt="" /&gt;
&lt;img src="https://stefaniuk.website/pictures/music-hobbie-jam-2.jpeg" width="1400" height="928" alt="" /&gt;
&lt;img src="https://stefaniuk.website/pictures/music-hobbie-jam-3.jpeg" width="1400" height="928" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;В августе начался курс, длился полтора месяца, 18 занятий три раза в неделю. По воскресеньям после занятий был коворкинг на котором можно поработать над своим музлом и посоветоваться с преподавателями.&lt;/p&gt;
&lt;p&gt;Учеба была очень плотной и насыщенной в плане информации. Все обучение происходило в Ableton, основы которого рассказывают в самом начале. Были занятия по базовой теории музыки, истории электронной музыки, синтезу звука и ударным. Также было много лекций по работе с семплами, на одной из которых делали трек из одного семпла.&lt;/p&gt;
&lt;p&gt;Еще одной приятной фишкой стало большое комьюнити. После окончания курсов тебя добавляют в чатик в телеге, где есть все выпускники и преподаватели. Можно общаться на разные темы, собираться и делать что-то вместе. Школа регулярно проводит джемы участие в которых бесплатно.&lt;/p&gt;
&lt;h2&gt;Первое выступление&lt;/h2&gt;
&lt;p&gt;В конце курса меня ждало выступление на выпускном. Это оказался самый сложный этап для меня, потому что нужно было написать хотя бы 30 минут материала.&lt;/p&gt;
&lt;div style="max-width: 400px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/music-hobbie-poster.jpeg" width="1024" height="1280" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Постер выступления&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Вначале не мог никак определиться с жанром, в котором буду писать. Начал с техно, не то чтобы часто его слушаю, так что фантазии не хватило написать что-то вменяемое. В итоге где-то две недели я просто ничего не делал и думал, что же написать. В какой-то момент чисто случайно вспомнил про один из любимых жанром: drum &amp; bass.&lt;/p&gt;
&lt;p&gt;В итоге получилось написать восемь треков в стиле драм энд бейс и джангл. Прогнав репетицию получил 40 минут выступления.&lt;/p&gt;
&lt;p&gt;Стоит немного отойти в сторону и рассказать о живых выступлениях и dj сетах. Обычно живое выступление подразумевает, что музыкант играет свою музыку, часть партий которых играет автоматически, а часть исполняется вживую. Зачастую используют всякие железные синтезаторы, также есть полностью «железные» выступления, в которых не используется компьютер.&lt;/p&gt;
&lt;p&gt;DJ сет состоит из готовых треков, которые музыкант выбирает перед выступлением и там бывает не только собственная музыка. Задача артиста сводить треки во время выступления чтобы все звучало плавно и гармонично.&lt;/p&gt;
&lt;p&gt;Итог моего обучения можно послушать на саундклауде в виде записи моего выступления:&lt;/p&gt;
&lt;div style="max-width: 750px;"&gt;&lt;iframe width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/1174254646&amp;color=%23ff5500&amp;auto_play=false&amp;hide_related=false&amp;show_comments=true&amp;show_user=true&amp;show_reposts=false&amp;show_teaser=true"&gt;&lt;/iframe&gt;
&lt;div style="font-size: 10px; color: #cccccc;line-break: anywhere;word-break: normal;overflow: hidden;white-space: nowrap;text-overflow: ellipsis; font-family: Interstate,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Garuda,Verdana,Tahoma,sans-serif;font-weight: 100;"&gt;&lt;p&gt;&lt;a href="https://soundcloud.com/stefaniuk" title="STEFANIUK" target="_blank" style="color: #cccccc; text-decoration: none;"&gt;STEFANIUK&lt;/a&gt; · &lt;a href="https://soundcloud.com/stefaniuk/module-exchange-graducation-live-set" title="Module Exchange - Graducation Live Set" target="_blank" style="color: #cccccc; text-decoration: none;"&gt;Module Exchange — Graducation Live Set&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;Железо&lt;/h2&gt;
&lt;p&gt;Параллельно с учебой я также увлекся музыкальным оборудованием: разными драм машинами, синтезаторами, педалями и прочим. В какой-то момент показалось, что мне нужно железо, чтобы моя музыка стала крутой и начала всем нравиться. Забегая на перед, скажу, что это заблуждение.&lt;/p&gt;
&lt;p&gt;В общем, решил начать с малого и купить себе какой-то небольшой синт, в итоге остановился на Korg Volca Bass и не успел моргнуть, как скупил почти всю линейку Volca. И тут я понял, что сделал ошибку. Во-первых, за стоимость этих четырёх коробочек я мог взять один нормальный хороший синтезатор. Во-вторых, на изучение всего этого зоопарка просто не хватало времени и желания. Да и позже оказалось, что они мне не сильно нравятся, потому что неудобные.&lt;/p&gt;
&lt;div style="max-width: 720px;"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/music-hobbie-setup-1.jpeg" width="1400" height="928" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Хорошо что я покупал их б/у и продал по той же цене, что и покупал. Решил приобрести одно устройство, над которым можно сконцентрироваться и хорошо изучить. Остановился на коробочках от Elektron. Меня подцепил красивый дизайн, удобство и мощный сэквенсор.&lt;/p&gt;
&lt;p&gt;Купил FM синтезатор Elektron Model:Cycles и потом докупил еще Model:Samples. Получился достаточно сбалансированный сэтап, с ним я понял, что хочу делать. Но оказалось, что Model:Cycles не сильно подходит под мои задачи, а вот samples напротив очень понравился.&lt;/p&gt;
&lt;p&gt;Вообще, история с железом очень сложная, особенно когда ты начинающий. Возникают мысли, что чтобы написать крутой трек обязательно нужно иметь крутой аналоговый синт и прочие примочки. Но главное это скилл, потому что он позволит даже с самого простого синта выжать крутой звук. Да и софтверных синтов хватает с головой.&lt;/p&gt;
&lt;p&gt;На эту тему есть просто шикарнейший доклад Максима Ильяхова — &lt;a href="https://www.youtube.com/watch?v=_Uv5lS7UM9Y"&gt;о демонах начинающих музыкантов и аналоговых синтах&lt;/a&gt;. После него я более спокойно стал относиться к железкам и сейчас они играют роль крутых игрушек для взрослых, с которыми приятно поиграть после работы или просто когда хочется отдохнуть от компьютера. Буду ли я покупать себе железо? Скорее всего, да. Если покупать б/у то можно неплохо сэкономить и потом продать за те же деньги. Оно не сделает меня крутым музыкантом, я просто люблю железо.&lt;/p&gt;
&lt;h2&gt;Семплы&lt;/h2&gt;
&lt;p&gt;Вторая проблема, с которой я столкнулся это гигабайты семплов, которые я накачал из интернета. В какой-то момент у меня было около 1000 бочек. И вместо того чтобы писать музыку ты бесконечно сидишь и выбираешь нужный звук, за это время идея уходит, ты устаешь и забиваешь на это дело.&lt;/p&gt;
&lt;p&gt;Однажды я просто взял и отфильтровал все семплы, после чего большую часть выкинул. И хочу проделать это еще раз. Это позволило не заморачиваться над конкретным звуком, а просто наслаждаться процессом. Да и когда уже есть какой-то набросок то найти более подходящие звуки намного проще.&lt;/p&gt;
&lt;p&gt;Так что ограничения рулят. Об этом Сергей Король написал отличный пост: &lt;a href="https://sergeykorol.ru/blog/self-limitations/"&gt;Самоограничения&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Интересные доклады&lt;/h2&gt;
&lt;p&gt;Ну и напоследок поделюсь несколькими видео, которые мне очень сильно понравились и помогли в написании музыки:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;«&lt;a href="https://www.youtube.com/watch?v=SmOGXzG_c8I"&gt;Как придумывать музыку&lt;/a&gt;» от Глеба Raumskaya&lt;/li&gt;
&lt;li&gt;«&lt;a href="https://www.youtube.com/watch?v=7NRCA5aT0sw&amp;t=1582s"&gt;Как записать альбом&lt;/a&gt;» от Сергея Бурухина&lt;/li&gt;
&lt;/ul&gt;
&lt;style&gt;
.category-block { width: 49%;  display: inline-block; vertical-align: top; margin-right: 1%; margin-bottom: 20px;}
@media (max-width: 900px) {
.category-block { width: 100%; margin-right: unset;}
}
&lt;/style&gt;
</description>
</item>

<item>
<title>Онлайн документация на базе WordPress и IdentityServer</title>
<guid isPermaLink="false">262</guid>
<link>https://stefaniuk.website/all/app-manuals-by-wordpress-and-identityserver/</link>
<pubDate>Wed, 22 Dec 2021 02:55:03 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/app-manuals-by-wordpress-and-identityserver/</comments>
<description>
&lt;p&gt;Как-то раз на работе появилась задача по созданию онлайн документации для нашего приложения. Документация должна быть не технической, а в виде мануалов для пользователей.&lt;/p&gt;
&lt;p&gt;Одним из главных требований была авторизация пользователей с помощью текущего логина и пароля от основного приложения. Доступ к материалам должен определяться на основании текущих ролей пользователя.&lt;/p&gt;
&lt;p&gt;Изначально хотели использовать готовое решение в виде SaaS сервиса с которым можно интегрироваться и спокойно пользоваться. Идея самим все написать с нуля хоть и возникала, но вернуться к ней решили только в крайнем случае.&lt;/p&gt;
&lt;p&gt;Первым кандидатом был GitBook. У него приятный интерфейс, много опций по редактированию и оформлению текстов. Необходимый нам функционал также был, но доступен только в Enterprise версии. Цена оказалась слишком большой, пришлось искать дальше.&lt;/p&gt;
&lt;p&gt;Подобных сервисов и так не очень много, а с необходимыми нам фичами так вообще почти нету. В какой-то момент я даже решил попробовать что-то собрать на основе статического генератора сайтов, но получилось слишком монструозно.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Wordpress&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Оставался только один вариант — написать все самим, чего очень не хотелось делать. В какой-то момент я чисто случайно вспомнил про Wordpress. Не то что бы я им раньше пользовался, но много слышал о нем.&lt;/p&gt;
&lt;p&gt;В итоге решили делать мануалы на базе Wordpress. Вообще, Wordpress это такая штука, с которой при должном умении можно слепить все что угодно. В нашем случае даже код не пришлось писать (почти).&lt;/p&gt;
&lt;p&gt;За основу взяли готовую тему и немного допилили под наши нужды с помощью CSS. Правда в одном месте пришлось немного изменить логику отображения с помощью PHP, но это была достаточно тривиальная задача.&lt;/p&gt;
&lt;p&gt;Весь функционал реализовали с помощью пачки плагинов. Главным среди них стал OAuth Single Sign On by miniOrange. Он позволяет логиниться в вордпесс с помощью сторонних сервисов (Google, Facebook и т. д.). Бесплатная версия покрывает почти все кейсы. На же пришлось покупать лицензию, потому что нужен был мапинг наших ролей на роли вордпреса.&lt;/p&gt;
&lt;p&gt;За работу с пользователями в нашем приложении отвечает IdentityServer. Сделали отдельную страницу логина для SSO чтобы получить нужную куку. Также включили флаг AlwaysIncludeUserClaimsInIdToken в настройках клиента, чтобы клеймы ролей были в ID токене и их смог увидеть Wordpress.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Хостинг&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;С хостингом тоже все оказалось достаточно просто. Изначально хотели использовать AWS Lightsail, но основная проблема мы в отсутствии возможности перемещать снапшоты диска между аккаунтами. В итоге развернули все на EC2 с помощью готового бесплатного решения из AWS Marketplace.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Итого&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Эта задача очень показательна, она позволяет понять насколько готовые решения позволяют сэкономить время и деньги. Если писать такое приложение с нуля, то это может занять месяц работы, а также время на тестирование и отлов типичных ошибок. Ну и пользователей нужно было бы учить как всем этим пользоваться.&lt;/p&gt;
&lt;p&gt;В нашем случае настройка Wordpress заняла приблезительно неделю не очень активной работы. Также популярность Wordpress позволяет легко нанять человека, который сможет спокойно с ним работать.&lt;/p&gt;
&lt;p&gt;Ну и стоимость порадовала. Все это обошлось в ~570$, в то время как типичный SaaS сервис берез 5$ в месяц за одного пользователя. Хостинг тоже вышел не очень дорогим (~10-15$ в месяц).&lt;/p&gt;
</description>
</item>

<item>
<title>Где захостить свое приложение?</title>
<guid isPermaLink="false">261</guid>
<link>https://stefaniuk.website/all/where-and-how-to-host-apps/</link>
<pubDate>Wed, 15 Dec 2021 03:34:45 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/where-and-how-to-host-apps/</comments>
<description>
&lt;p&gt;Как и многие другие разработчики я часто делаю разные небольшие приложения или пет-проекты. И очень часто возникает вопрос: где захостить приложение и его компоненты и как это сделать максимально дешево?&lt;/p&gt;
&lt;p&gt;Я старался подобрать сервисы, которые позволяют максимально просто и дешёво запускать все необходимые для работы приложения ресурсы. В основном большинство приложений состоит из бекенда (в моем случае .NET), фронтенда и базы данных. Поэтому рассмотрим варианты развертывания каждого компонента.&lt;/p&gt;
&lt;h2&gt;Типы сервисов&lt;/h2&gt;
&lt;p&gt;Для начала стоит кратко пройтись по моделям предоставления сервисов в облаке. Эти модели описывают зоны ответственности пользователя и облачного провайдера. В разрезе данной статьи нас интересуют следующие модели:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;IaaS (Infrastructure as a Service)&lt;/b&gt; — облачный провайдер предоставляет минимально необходимую инфраструктуру в виде виртуальных машин, сети и хранилища, а также отвечает за работоспособность. Дальнейшая настройка, в том числе и ОС, лежит на стороне пользователя. По сути это основные строительные блоки в облаке с помощью которых можно сделать все что захотим.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PaaS (Platform as a Service)&lt;/b&gt; представляет из себя готовую инфраструктуру для разработки и запуска приложений. При такой модели мы не думаем про настройку и управление серверами, операционными системами и т. д. Примером такого сервиса являются управляемые БД (managed databases). Облачный провайдер сам отвечает за правильную настройку сервера, ОС, сохранность данных, бекапах и бесперебойную работу. В основном пользователь только платит деньги и пользуется сервисом.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Serverless&lt;/b&gt;, самая свежая модель, которая очень похожа на PaaS. Только при такой модели облако само выделяет ресурсы на основании текущей нагрузки. Все настройки и планирование ресурсов скрыто от пользователя. Ему остается только загрузить свой код, а все остальное сделает облако. Также в serverless мире обычно оперируют понятием &lt;i&gt;облачная функция&lt;/i&gt; это код, который умеет обрабатывать только одни конкретный запрос.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style="max-width: 750px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/host-apps-cloud-models.png" width="1694" height="1162" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Наглядный пример зон ответственности в разных моделях предоставления сервисов.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2&gt;Где захостить .NET бекенд?&lt;/h2&gt;
&lt;p&gt;&lt;b&gt;AWS/Azure/GCP&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Три самых крупных облачных провайдера, очень похожи между собой и предоставляют во многом одни и те же сервисы. Так в каждом из них можно арендовать виртуальные машины, создать управляемую БД или работать с облачными функциями. Мне больше всего нравиться AWS, но для .NET приложения Azure будет более интересным, потому что это родная для дотнета среда.&lt;/p&gt;
&lt;p&gt;Больше всего нас интересую сервисы:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;AWS EC2 (IaaS)&lt;/b&gt; обычные виртуальные машины. Есть маркетплейс на котором можно найти огромное множество готовых AMI образов с предустановленным софтом и нужными настройками. Например можно в один клик развернуть виртуалку с Wordpress и всем необходимым.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Azure App Service (PaaS)&lt;/b&gt; самый простой и нативный способ захостить .NET приложения. App Service сам разворачивает приложение из репозитория, настраивает все необходимое для работы и предоставляет полезные метрики.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AWS Lightsail (IaaS/PaaS)&lt;/b&gt; упрощенная версия AWS для тех кто с ним не знаком. Можно очень дешево арендовать виртуальную машину, поднять докер контейнер, развернуть БД и хранилище для файлов. Все это делается буквально в пару кликов мышкой. Первые три месяца бесплатные.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AWS Elastic Beanstalk (PaaS)&lt;/b&gt; умеет поднимать необходимые для работы приложения компоненты в AWS. По сути мы можем все это сделать руками, но Beanstalk автоматизирует всю рутину. Под капотом он представляет из себя набор разных CloudFormation скриптов, которые поднимают необходимые сервисы и настраивают их.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;DigitalOcean&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Самый простой в использовании сервис. Раньше в DO можно было арендовать только виртуальные машины, сейчас же можно создавать управляемы БД, балансировщики и т. д. На текущий момент почти все мои проекты крутяться в DigitalOcean.&lt;/p&gt;
&lt;p&gt;Нам интересны следующие сервисы:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Droplets&lt;/b&gt; — виртуальная машина, есть много готовых образов на маркетплейсе под любой рантайм.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;App Platform&lt;/b&gt; — позволяет нативно запускать  приложения написанные на Python, Nodejs, Go, php или любое другое приложение в виде контейнера.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Heroku&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Честно говоря я им ни разу не пользовался, но друзья очень часто советуют. Из коробки не поддерживает .NET приложения, но вроде как есть кастомные билд паки. В основном хероку позволяет развернуть приложения на Nodejs, Go, Python и т. д. Из приятных фишек: достаточно запушить код в репозиторий, все остальное сделает Heroku.&lt;/p&gt;
&lt;p&gt;Есть бесплатный тариф, в нем приложение останавливается после пол часа без активности. Так что первые запросы к приложению могут отрабатывать немного дольше.&lt;/p&gt;
&lt;div style="max-width: 750px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/host-apps-backend.png" width="1538" height="512" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2&gt;Где захостить фронтенд?&lt;/h2&gt;
&lt;p&gt;С фронтендом дела обстоят намного проще, есть огромное количество сервисов, которые позволяют бесплатно захостить фронт.&lt;/p&gt;
&lt;p&gt;Мой личный фаворит — Vercel. Бесплатный план включает такие фишки как HTTPS, автоматически деплой с репозитория, собственный домен. Также он умеет разворачивать приложение под каждый PR, что позволяет протестировать изменения до того как влить код в основную ветку.&lt;/p&gt;
&lt;p&gt;Самым же популярным сервисом является Github Pages. В нем очень просто поднять статический сайт прямо из репозитория. Бесплатный и работает сразу из коробки. В основном используется для хостинга документации к коду, блогов или резюме.&lt;/p&gt;
&lt;p&gt;App Platform от Digital Ocean я уже упоминал выше. Можно поднять три статических сайта бесплатно, последующие за 3$ в месяц. Есть автоматический деплой, бесплатный HTTPS, возможность подключения своего домена. Сейчас в нем крутиться мое онлайн резюме. Сервис прикольный тем что в нем можно запустить и бек и фронт.&lt;/p&gt;
&lt;p&gt;Последним хочу отметить AWS S3. По сути это объектное хранилище (хранилище для файлов) в котором есть встроенная поддержка сайтов. Для этого нужно загрузить HTML/CSS/JS файлы и включить соответствующую опцию в настройках бакета. Часто встречает в продакшене связку CloudFront + S3.&lt;/p&gt;
&lt;div style="max-width: 750px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/host-apps-frontend.png" width="1524" height="298" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2&gt;Где развернуть базу данных?&lt;/h2&gt;
&lt;p&gt;С базами данных ситуация сложнее, в основном предлагают развернуть БД за большие деньги. Такой вариант не подходит если мы хотим захостить небольшое приложение или просто его потестировать без установки БД локально. Вот несколько из вариантов:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ElephantSQL, можно развернуть PostreSQL базу данных любого размера. Бесплатный план дает базу с 20 Мб и 5 параллельными подключениями.&lt;/li&gt;
&lt;li&gt;Mongo Atlas подойдет если нужно развернуть кластер MongoDB. Есть достаточно жирный бесплатный план.&lt;/li&gt;
&lt;li&gt;В DigitalOcean и AWS Lightsail начиная с 15 долларов в месяц можно развернуть достаточно неплохой сервер БД, который не нужно настраивать и работает из коробки. В Lightsail первые 3 месяца бесплатно.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style="max-width: 750px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/host-apps-databases.png" width="1570" height="288" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2&gt;Полезные ссылки&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://oblako.kz/iaas-blog/samye-populjarnye-oblachnye-servisy-v-mire"&gt;Виды облачных ресурсов&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/HostingWebsiteOnS3Setup.html"&gt;Хостинг веб сайта в AWS S3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04"&gt;Отличный туториал по тому как настроить SSL сертификат на Ubuntu&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
</item>

<item>
<title>Pentax 6x7</title>
<guid isPermaLink="false">260</guid>
<link>https://stefaniuk.website/all/pentax-6x7/</link>
<pubDate>Mon, 16 Aug 2021 12:28:49 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/pentax-6x7/</comments>
<description>
&lt;p&gt;Сегодня расскажу о своей первой среднеформатной камере — Pentax 6x7. Идея попробовать средний формат возникла где-то полтора года назад, с тех пор я время от времени заходил на OLX (сайт с объявлениями) и просматривал что там есть. Изначально я смотрел в сторону советских фотоаппаратов таких как Киев-60 и Киев-88. Однажды я чисто случайно наткнулся на объявление по продаже Pentax. Цена была вполне вменяема да и состояние вроде как хорошее. Попросил у продавца дополнительных фото и оказалось что камера в отличном состоянии. Решил что стоит взять.&lt;/p&gt;
&lt;div style="max-width: 720px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-main.jpeg" width="828" height="466" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Впервые камера была представлена в 1969 компанией Asahi Pentax и производилась до 2000-х. Главная особенность камеры — форм-фактор, по сути это сильно увеличенная 35 мм камера. Но Pentax не является первой камерой в таком форма-факторе. С середины 1950-х годов в Германии выпускались камеры Pentacon Six.&lt;/p&gt;
&lt;p&gt;Камеры выпускались в трех модификациях: Pentax 6x7 (мой экземпляр), Pentax 67 с добавленной функцией приподнятия зеркала и Pentax 67II. Последняя версия стала легче за счет добавления пластиковых деталей. Также в нее добавили экран и кнопки, которые позволяют настраивать камеру. Но и цена за нее в среднем в два раза больше предыдущего поколения.&lt;/p&gt;
&lt;p&gt;Вторая отличительная черта камеры это размер и вес. Она получилась очень большой и тяжелой. Вместе с моим 150 мм. объективом она весит более 2.8 кг. Чтобы немного улучшить ситуацию вместе с камерой выпускалась деревянная ручка, которая немного спасала ситуацию, она чем-то напоминает рукоять старых станковых пулеметов.&lt;/p&gt;
&lt;p&gt;Из других особенностей можно отметить:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Камера не является полностью механической. Спуском управляет небольшой механизм, который питается от батареек 4SR44.&lt;/li&gt;
&lt;li&gt;Камера имеет встроенный в пентапризму экспонометр, который соединен с телом камеры с помощью цепного механизма, который легко повредить. Поэтому при разборе камеры нужно сначала снять объектив и только потом пентапризму.&lt;/li&gt;
&lt;li&gt;Звук зеркала при спуске очень громкий, иногда люди оглядываются по сторонам.&lt;/li&gt;
&lt;li&gt;Большое зеркало также порождает большую вибрацию, которая мешает делать кадры на выдержках длиннее 1/60.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style="max-width: 720px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-00001.jpeg" width="1600" height="1277" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-00002.jpeg" width="1600" height="1278" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-00003.jpeg" width="1600" height="2004" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-00004.jpeg" width="1600" height="2005" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-00005.jpeg" width="1600" height="1277" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-00006.jpeg" width="1600" height="1277" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-00007.jpeg" width="1600" height="2001" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-00008.jpeg" width="1600" height="2004" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-00009.jpeg" width="1600" height="2004" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-00010.jpeg" width="1600" height="2004" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/pentax-67-00011.jpeg" width="1600" height="2004" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
</item>

<item>
<title>Веб аналитика на коленке с помощью AWS</title>
<guid isPermaLink="false">259</guid>
<link>https://stefaniuk.website/all/web-analytics-with-aws-lambda/</link>
<pubDate>Wed, 04 Aug 2021 02:58:43 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/web-analytics-with-aws-lambda/</comments>
<description>
&lt;p&gt;Время от времени нам с друзьями приходят идеи различных проектов, которые было бы круто запустить. Но чтобы убедиться в жизнеспособности идеи нужно ее как-то проверить. Обычно для этого создаётся лендинг, на который нагоняется трафик и потом в аналитике смотрим, как ведут себя пользователи.&lt;/p&gt;
&lt;p&gt;В один из таких дней мы решили проверить очередную идею. Но в этот раз пользователь мог сделать много разных действий на сайте и их нужно было как-то отследить и проанализировать.&lt;/p&gt;
&lt;p&gt;Это не какая-то уникальная проблема, обычно для таких задач берут Google Tag Manager. Но никто из нас не умел им пользоваться да и желания изучать особо не было.&lt;/p&gt;
&lt;p&gt;И тут внезапно пришла идея как это сделать. Можно взять AWS Lambda, набросать на коленке пару строк кода, которые будут получать событие и куда-то их складывать  для дальнейшего анализа. Для места хранения метрик выбрал CloudWatch. Он как раз умеет анализировать разные метрики/логи и строить красивые дашборды.&lt;/p&gt;
&lt;p&gt;Также хотелось получать письма на почту с информацией про самые важные события. Для этого взяли SNS.&lt;/p&gt;
&lt;p&gt;В итоге пользователь заходит на сайт, делает какое-то действие, оно летит в лямбду, которая просто кладет информацию в логи и отправляет сообщение в SNS. Дальше идем в CloudWatch и смотрим на дашборды и анализируем полученную информацию.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/New-screenshot.png" width="2268" height="1488" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Актуальная аналитика&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;В итоге получилась относительно простая и даже гибкая аналитика, которую можно использовать сразу на нескольких проектах. Сейчас она обходиться в 0$, нам хватает одного миллиона бесплатных вызовов лямбды в месяц.&lt;/p&gt;
&lt;p&gt;Но я все же советую изучить и использовать готовые инструменты вместо того чтобы сидеть и делать свои велосипеды. Ведь это очень скользкий путь, в какой-то момент можно забыть для чего это делалось и бесконечно улучшать и допиливать, вместо того чтобы решать задачу.&lt;/p&gt;
</description>
</item>

<item>
<title>Паттерн «Репозиторий»</title>
<guid isPermaLink="false">256</guid>
<link>https://stefaniuk.website/all/repository-pattern/</link>
<pubDate>Thu, 08 Jul 2021 07:50:13 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/repository-pattern/</comments>
<description>
&lt;p&gt;Репозиторий один из самых популярных паттернов для доступа к данным. Он используется для абстрагирования от конкретной реализации и нюансов работы с источником данных, внешних сервисов, файловой системой и т. д.&lt;/p&gt;
&lt;p&gt;С одной стороны, это очень простой паттерн, который позволяет скрыть сложность работы с БД. Но с другой стороны, спросите 10 программистов описать этот шаблон и вы получите 10 разных реализаций.&lt;/p&gt;
&lt;p&gt;Самая большая проблема этого шаблона — множество вопросов, которые возникают при его реализации и дальнейшей поддержки.&lt;/p&gt;
&lt;p&gt;Изначально хотел сам расписать проблемы, но за меня это уже сделали, поэтому настоятельно советую прочитать статью: &lt;a href="https://blog.byndyu.ru/2011/01/domain-driven-design-repository.html"&gt;«Проблемы паттерна Репозиторий».&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Если кратно, то:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Что делать если репозиториям нужно использовать закрытые методы друг друга?&lt;/li&gt;
&lt;li&gt;Можно ли использовать один репозиторий на весь проект или делать репозиторий на каждую сущность?&lt;/li&gt;
&lt;li&gt;Нужно ли дублировать методы репозитория в сервис или мы можем напрямую использовать репозиторий в контроллерах?&lt;/li&gt;
&lt;li&gt;Нужно ли возвращать IQueryable и как это повлияет на дизайн системы в случае с .NET кодом? Если нет, то как правильно изменять сущности без использования Change Tracking?&lt;/li&gt;
&lt;li&gt;Как правильно объединить репозиторий и UoW?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Отдельно также хочу отметить доклад: &lt;a href="https://www.youtube.com/watch?v=3yPpL1rEK9o&amp;t=1208s"&gt;Денис Цветцих «Repository и UnitOfWork в 2020 году, must have или антипаттерн?»&lt;/a&gt;. В нем также поднимаются проблемы репозитория, способы их решения и варианты замены этот шаблона на другой.&lt;/p&gt;
&lt;p&gt;Репозиторий — хороший паттерн, который должен быть в арсенале любого программиста, но стоит использовать его с умом и учитывать проблемы, которые он может вызвать.&lt;/p&gt;
&lt;p&gt;Для себя я решил что репозиторий хорошо подходит, когда вся логика приложения вписывается в CRUD модель. Но если логика более сложная или приложение подразумевает Task Base UI, тогда лучше прибегнуть к подходу CQRS. Он позволяет разбить сложную бизнес логику включая репозитории на независимые объекты, каждый из которых выполняет только одну бизнес задачу или use case.&lt;/p&gt;
&lt;div style="max-width: 750px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/repository-pattern-orm-db.png" width="1200" height="579" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Я кроме все прочего не люблю репозитории за:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ограничение функциональности ORM, большенство специфических операций недоступны. В зависимости от реализации можем потерять Change Tracking.&lt;/li&gt;
&lt;li&gt;Дополнительный мапинг из доменных объектов в DTO.&lt;/li&gt;
&lt;li&gt;Дополнительный слой абстракции, который зачастую просто не нужен. Особенно если речь идет о небольших сервисах где логику нужно делать максимально простой и незамысловатой.&lt;/li&gt;
&lt;li&gt;Работает только в простых CRUD сценариях.&lt;/li&gt;
&lt;/ul&gt;
</description>
</item>

<item>
<title>Аренда велосипедов BikeNow</title>
<guid isPermaLink="false">255</guid>
<link>https://stefaniuk.website/all/arenda-velosipedov-bikenow/</link>
<pubDate>Mon, 05 Jul 2021 02:19:09 -0500</pubDate>
<author></author>
<comments>https://stefaniuk.website/all/arenda-velosipedov-bikenow/</comments>
<description>
&lt;p&gt;В прошлом году захотел начать кататься на велосипеде, а у родителей как раз долгое время без дела лежал велосипед на балконе. В итоге он достался мне. Думал, что буду активно ездить, но спустя десяток поездок лопнула цепь. Начал ходить по ремонтам, почти везде свободное место было только через неделю/две. В итоге я успешно забил на всё это дело, оставив велик в подъезде.&lt;/p&gt;
&lt;p&gt;Но проблема была не только в цепи. По сути, это самый обычный велосипед, без особенностей. Из-за долгого стояния на балконе у него барахлили задние тормоза, передачи еле переключались, а некоторые вообще не работают. Но больше всего неудобств было из-за сидения после часа езды на котором дня два болела жопа.&lt;/p&gt;
&lt;p&gt;В этом году решил попробовать арендовать велик, благо их очень много по Киеву. Друзья уже активно ими пользовались и советовали попробовать.&lt;/p&gt;
&lt;p&gt;Одним субботним утром решили взять велики и часик покататься по району. В итоге объехали половину Киева, за 5 часов и намотав чуть больше 35 км.&lt;/p&gt;
&lt;div style="max-width: 450px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/bike-now-map.jpg" width="826" height="922" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Первое что заметил — удобное сиденье, после езды вообще не чувствуется дискомфорт. Каждый велик имеет небольшую корзину спереди, в которую можно сложить вещи. Раньше мне казалось что это неудобно пока сам не попробовал.&lt;/p&gt;
&lt;div style="max-width: 760px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/bike-now-bicycle.png" width="1149" height="839" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Для меня основной киллер фичей стала возможность оставлять его где захочется. Не нужно тащить домой, выделять под него место, заниматься обслуживанием и т. д. Просто подошел к нему, отсканировал QR код, сел и поехал.&lt;/p&gt;
&lt;div style="max-width: 600px"&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://stefaniuk.website/pictures/bike-now-app-over.png" width="681" height="687" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Официальное приложение BikeNow.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Из минусов, приложение, которое имеет очень большую погрешность при подсчете расстояния. Приложение показывает 95 км, в то время как Apple Watch с модулем GPS показывают 133 км.&lt;/p&gt;
&lt;p&gt;Цены очень даже адекватные, особенно если сравнивать с арендой электро-самокатов. Взять велосипед стоит 5 гривен, минута 50 копеек. Есть возможность купить подписку на 150 дней за 2500 грн, которая позволяет кататься неограниченное количество раз длинной до 30 минут.&lt;/p&gt;
&lt;p&gt;Итог.&lt;br /&gt;
На текущий момент я проехал на них больше 100 км и планирую дальше ими пользоваться. Что делать со своим великом пока не решил, возможно, дойдут руки и я займусь его ремонтом, а может просто продам как есть.&lt;/p&gt;
&lt;p&gt;Короче, советую!&lt;/p&gt;
</description>
</item>


</channel>
</rss>