Пример прикладного проекта на F#

    Язык F# уступает в популярности C#. Вместе с тем, во многом благодаря сообществу, фаршик стал реальной альтернативой для прикладных проектов. В статье описаны ингредиенты бэкенда, фронтенда, тестов, сборки и инфраструктуры проекта, полностью написанного на F#. Исходный код прилагается.

    Диаграмма контейнеров
    Диаграмма контейнеров

    SAFe

    На выбор ингредиентов, определяющее влияние, оказал SAFe Stack. SAFe представляет собой шаблон dotnet CLI, в котором подобраны необходимые компоненты для гомогенной разработки SPA в связке с бэкэндом. Сайт проекта содержит много обучающих материалов и примеров.

    SAFе облегчает старт для новичка. На первом этапе можно ничего не менять в шаблоне и сразу переходить к разработке функционала. По мере накопления опыта, вы, скорее всего, отойдете от SAFe. 

    Первая буква акронима - ‘S’ - означает Saturn - идиоматический фреймворк над Giraffe, который, в свою очередь, функциональная обертка над Asp.net. 

    Вторая буква - ‘A’ - означает Azure. Здесь мне было сразу не по-пути с SAFe, а тем, кто использует Ажур, как платформу, пригодится библиотека Farmer, которую пилят те же люди, что и SAFe.

    Третья буква - ‘F’ - означает Fable - транспайлер из F# в JavaScript - настоящая сдобная булочка в экосистеме фарша.

    Бэкенд

    Для создания API используется библиотека Fable.Remoting. Fable.Remoting скрывает абстракции веб-сервера. Типы, определяющие контракт, помещаются в общий для бэкенда и фронтенда файл (или сборку). Реализовываете API на сервере, все остальное (создание прокси, сериализацию, обработку ошибок, логирование) делает за вас библиотека. Помимо JSON, поддерживается передача бинарных данных.

    Сейчас, имея такой удобный инструмент, как Fable.Remoting, я не вижу смысла тянуть в бэкенд колбасу Saturn - Giraffe - Asp.Net. Но, по историческим причинам, в проекте остался Giraffe.

    Если нужно использовать спецификацию OpenAPI, можно посмотреть на GiraffeGenerator.

    В качестве хранилища данных, в проекте используется NoSql база DynamoDB. За основу реализации слоя доступа к данным была взята библиотека DynamoDb.Ok. В ней используется идиоматический подход на основе монады Reader. В итоге, могу сказать, что этот подход мне не понравился. В будущем хотелось бы вообще не отвлекаться на код в слое доступа к данным. Есть идеи, возможно, об этом выйдет отдельная статья.

    C реляционными базами из F# работать не приходилось. В чате сообщества замечал нарекания на ограниченную поддержку типов F# в Entity Framework и рекомендации использовать Dapper.

    В прошлом году сообщество обогатилось серией годных статей по теме внедрения зависимостей: статья 1, статья 2статья 3. Подход, базирующийся на Flexible Types, применяется и в данном проекте.

    Для логирования используется библиотека Serilog, у которой есть расширение для Giraffe.

    Для авторизации используется самописная реализация JWT.

    Для связи с AWS частично используется дотнетовский AWSSDK, частично HTTP, так как SDK не покрывает весь функционал облака.

    Фронтенд

    Основа фронтендов на F# - Fable,  Ваш код и большая часть стандартной библиотеки переводится в JS. Можно легко взаимодействовать с библиотеками JS. Существует множество обвязок (binding) для популярных библиотек, в т.ч. React и его компонент.

    Для управления состоянием используется Elmish - реализация Elm-архитектуры. Рендеринг страницы производится с помощью Fable.React и Bulma.

    Разработка фронта в этой экосистеме доставляет.

    пример кода
    let quizView (dispatch : Msg -> unit) (settings:Settings) (quiz:QuizRecord) l10n = [
       br []
       figure [ Class "image is-128x128"; Style [Display DisplayOptions.InlineBlock] ] [ img [ Src <| Infra.urlForMediaImgSafe settings.MediaHost quiz.ImgKey ] ]
       br []
       h3 [Class "title is-3"] [str quiz.Name]
     
       div [Class "notification is-white"][
           p [Class "subtitle is-5"][
               match quiz.StartTime with
               | Some dt -> str (dt.ToString("yyyy-MM-dd HH:mm"))
               | None -> str "???"
     
               if quiz.Status = Live then
                   str " "
                   span [Class "tag is-danger is-light"][str "live"]
               br[]
           ]
     
           p [] (splitByLines quiz.Description)
     
           if quiz.EventPage <> "" then
               a[Href quiz.EventPage][str l10n.Details]
        ]
    ]

    Отличное введение в экосистему - книга The Elmish Book.

    Однако, фронтдендеры такие фронтендеры, связка Elmish + Fable.React + Boolma уже вышла из моды. В 2021 году, чтобы быть в тренде, вам нужно освоить Feliz + Fable.React.WebComponent + Material UI и рассмотреть альтернативу - Fable.Svetle. Моя бэкендер страдать.

    Для взаимодействия с Aws, а именно с брокером сообщений в AppSync, используется библиотека Aws Amplify.

    Тесты

    Для проверки нефункциональных требований были реализованы нагрузочные тесты. Без использования сторонних решений (тянуть сюда JMeter показалось перебором). 

    Модульные тесты не писались. Не могу не отметить то чувство защищенности, которое дает система типов F#. Глупые ошибки обычно отлавливаются компилятором. За все время эксплуатации проекта, словил всего один мажорный баг на проде. В других проектах использовал FsUnit и expecto. Первый проще встраивается в инструментарий, второй гибче в синтаксисе, но, как по мне, большой разницы между ними нет.

    Из прочих решений для тестирования, в моем списке на попробовать:

    • FsCheck - для тестирование на основе свойств

    • Canopy - фреймворк и DSL для тестирования UI

    • NBomber - для нагрузочных тестов

    Сборка и развертывание

    Для управления пакетами используется Paket. Для сборки используется скрипт Fake. Оба инструмента входят в шаблон SAFe. Судя по обсуждению, в настоящее время есть какие-то проблемы в запуске фейковых скриптов и, в будущем, сборка будет выполняться из консольного приложения. В таком случае, вообще не вижу смысла в фейке.

    Для обновления инфраструктуры и развертывания используется AWS Cloud Development Kit. Поддержка F# не заявлена, но идет из коробки, вслед за C#. 

    Инструментарий

    Хорошая новость. В SAFe сконфигурировано горячее обновление клиента и сервера в режиме разработки. К этому быстро привыкаешь и потом не понимаешь, как можно было работать по-другому.

    Плохая новость. Инструментарий развивается, но он уступает тому, что есть в C#. Проект разрабатывается в VSCode с расширением Ionide. Подвисания, перезагрузки, регулярная необходимость удалять временные файлы - все это по-прежнему присутствует. Есть подозрение, что, в более крупных проектах, это может сильно испортить жизнь разработчику. Альтернативой Ionide является Rider. И там и там недавно вышли обновления, будем надеяться, что ситуация улучшится.

    Помимо Ionide, используется расширение ILSpy, для того, чтобы понимать логику компилятора.

    Также, работая с F# необходимо менять привычки отладки. Меньше пошаговой отладки, больше вывода в консоль - этот подход мне зашел. А вот то, что часто приходится прибегать к операциям текстового поиска (а иногда и замены) по всем файлам проекта - это для 2020 года уже не торт. Также нужно научиться использовать такой инструмент, как FSI.

    Итого

    По состоянию на начало 2021, F# пригоден для прикладных проектов небольшого и среднего размера. Для меня, преимуществами этого языка являются:

    • экосистема фронтенд-разработки,

    • система типов,

    • компактный синтаксис.

    До тех пор, пока эти возможности не появятся в C#, при прочих равных, мой выбор будет в пользу фаршика.

    исходный код проекта

    Картинка-поощрение для тех, кто дочитал эту статью до конца.
    Картинка-поощрение для тех, кто дочитал эту статью до конца.

    UPD: изначально в статье была фраза о том, что в экосистеме Майкрософт, F# занимает место экспериментального языка. Признаю, это было некорректное утверждение, фшарп это уже как десять лет не эскпериментальный язык, огромное количество компаний юзают его в проде, а фичей крутых там настолько много что некоторые тянут другие языки такие как сишарп, джаваскрипт и скала (тыц).

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 6

      0
      А какие плюсы вы заметили при работе с F# по сравнению с С#?
      например:
      Также, работая с F# необходимо менять привычки отладки. Меньше пошаговой отладки, больше вывода в консоль. Использовать FSI.

      Это огромный минус.
      В F# коде клиента я не заметил, чтоб он был удобнее тайпскрипта и реакта, а серверная часть слишком много технического кода содержит, тот же АПИ всё в одном месте прописано.

      Сам функциональный подход мне нравиться, потому что любой метод это результат по сути одной строчки кода которая описывает все кейсы, без циклов и if/else, то-есть большую часть ошибок можно уже исключить пока пишется код, но вот дебажить эту однострочную операцию сложнее, нужно выделять куски кода в отдельные методы и уже их дебажить, но не везде у нас массивы, когда работаем с отдельными обьектами функциональный подход уже не работает.
        0
        А какие плюсы вы заметили при работе с F# по сравнению с С#?

        Немногословность, операторы, синтаксис — это как по мне. Ну а главное, конечно, преобладание декларативного программирования.

          0
          Как я писал, плюсы: cистема типов, синтаксис, фронтенды. Исследую возможности повышения продуктивности для небольшой инновационной команды. Удобно и бысто моделируется домен, субъективно меньше ошибок. Задачи такие, где UI незатейливый, но клепать его нужно бысто, где-то даже с переиспользованием серверного кода.
            0
            любой метод это результат по сути одной строчки кода которая описывает все кейсы, без циклов и if/else

            [1..100]
            |> Seq.map (function
            | x when x%5=0 && x%3=0 -> "FizzBuzz"
            | x when x%3=0 -> "Fizz"
            | x when x%5=0 -> "Buzz"
            | x -> string x)
            |> Seq.iter (printfn "%s")


            (ссылка)
            Тут, вроде, есть и циклы, и условия.
            +1
            Похоже на натягивание совы на глобус. Упитанной добротной совы на красивый глобус, но всё же.
              0
              Также, работая с F# необходимо менять привычки отладки. Меньше пошаговой отладки, больше вывода в консоль. Использовать FSI.

              Не соглашусь с этим утверждением. Кому как нравится, дебажит из эфки куда удобнее чем из того же басика. С другой стороны со временем осознаешь что удобнее использовать консольку FSI ибо она классная.

              Only users with full accounts can post comments. Log in, please.