Разрабатываемое с вами облако нельзя назвать production ready. Мы его напишем строго в учебных целях, чтобы понять, что любой может написать такую сложную на первый взгляд инфраструктурную систему. Если требуется мощное решение, можно воспользоваться NextCloud или поднять FreeNAS.  Облачные сервисы уже давно стали неотъемлемой частью нашей жизни. На данный момент существует большое количество сервисов от разных компаний. Так давайте разберемся в принципах работы простейшего облачного сервиса, подтянем навыки проектирования систем. Данный проект можно постоянно развивать на протяжении длительного времени, и может стать отличным pet project. Созданное облако может пригодится для управления домашними файлами, достаточно его развернуть в локальной сети и с легкостью получать доступ к файлам с разных устройств.   Самое время определить задачи, которые нам необходимо решить, чтобы реализовать систему. Система должна соответствовать минимальному CRUD приложению: Просматривать содержимое файловой системы облачной папки; Реагировать на изменения в файловой системе; Загружать файлы в облако; Редактировать файлы (переименовывать или перемещать); Искать по названию; Постараться сделать это все максимально оптимально. Для решения этих задач логично выбрать клиент-серверную архитектуру. Реализация наших задач будет состоять из фронт и бек части.  Начнем с бекенда. Язык: Elixir Технология: Phoenix Почему Elixir, а не %любой_другой_язык_для_описания_API%? Я решил выбрать Elixir из-за лёгкого вхождения в мир функционального программирования, схожести языковых конструкций с Python, неплохой скорости работы и быстроты прототипирования. Сравнение API фреймворков по производительности. ASP.NET (C#) Быстрый Обладает сильной строгой типизацией; Для учебного проекта разработка на нем пойдет слишком долго. Flask (Python) Из-за особенностей Python, работает не слишком шустро; Скорость  написания кода высокая. Phoenix (Elixir) Относительно Flask быстро работает; Похож на Python, писать просто; Для первого функционального языка достаточно прост в изучении.  Cowboy или Plug (Elixir) Предоставляет слишком много свободы и не диктует разработчику архитектуру, но эта система была моим первым опытом работы на Elixir, поэтому было принято решение взять уже готовый инструмент. Yesod (Haskell) Обладает сильной строгой типизацией; Для данного проекта разработка пойдет слишком долго, т.к. я знаю только базовые элементы синтаксиса. В статье далее я не буду подробно рассказывать, что такое монады или объяснять другие сложные и не очень особенности функционального программирования, но мы вместе с вами попробуем разобраться в интересных функциональных подходах, вроде пайплайна, композиции или каррирования, которые нам полезно знать для реализации приложения на функциональном языке. В приведенном ниже коде я буду описывать в основном ядро системы, и, возможно, некоторые детали коммуникации с клиентом, если это представляет интерес. Важно понимать, что большинство рутинного кода будет опущено, чтобы не перепечатывать репозиторий.  Backend На клиенте необходимо получить информацию о файлах в текущей директории, поэтому реализуем серверные эндпойнты для решения этой задачи. Создадим новый модуль. Для легкости восприятия, его можно считать пространством имен. Создадим тип для нашего представления файла. Для работы с ним на фронтенде нам бы хотелось знать, является ли этот файл папкой хотя все - это файл. Без поля filename мы бы могли обойтись, но тогда нам бы пришлось вызывать path.basename (или аналог) на фронтенде для определения имени каждого файла. children нам будет необходим, если мы будем рекурсивно обходить директорию, но на начальном этапе это нам не нужно, пусть в нем всегда лежит nil. Приступим к написанию алгоритма. Будем отдавать файлы страницами, используя таким образом механизм виртуализации, потому что это даем нам такое преимущество как то, что пользователю не нужно ожидать загрузки информации о всех файлах, достаточно 2 * количество файлов на экране в данный момент. Виртуализация выгодна, когда у нас простые легкие ноды, как сейчас.  Это оператор пайплайна, он делает композицию функций, т.е. передает результат выполнения выражения первым параметром в следующее выражение. Если явных аргументов у функции не осталось, то скобки для вызова функции можно опустить. @spec - это определения типа функции, в Haskell оно оформляется похожим образом. В этой функции мы вызвали iterator get, предлагаю его определить сейчас. Фронтенд было решено разрабатывать на React + TypeScript. Для менеджера состояния использую MobX. Структура DTO для контроллера. Название поля Тип данных Значение по умолчанию Пояснение directory string? . Относительный путь до папки, где корень - наша dataDir с файлами page number? 0 Если требуется пагинация, и мы дробим список файлов на страницы, то мы передаем сюда страницу. page_size number? Количество файлов в директории Размер страницы, по умолчанию равен длине списка. is_recursive boolean? false Если требуется получить не только текущую директорию в результате запроса, а еще и все вложенные, то можно подать сюда true. Может пригодиться, если захотим интегрировать SotrableTree. При каждом переходе во внутреннюю директорию нужно менять url, в зависимости от которого нужно делать запросы за информацией о файлах в этой директории. Для этого необходимо заполнить поле directory. Напишем основной store с содержимым директорий.   Phoenix предлагает свой способ поднятия фронтенд сервиса. Это дает возможность поднять за одну команду два сервиса.  config.dev.exs Если запускать облако только на Linux, то можно указать скрипты из package.json, но на Windows будет ошибка с разрешениями. Теперь нам нужно сделать реакцию на изменение в файловой системе. Для этого необходимо определить FileWatcher, который будет смотреть за файлами, если изменение произойдет, он отправит сообщение всем подписанным клиентам. В нашем случае подписчиком будет выступать канал (аналог Stream из Dart), открытый по протоколу WebSocket. В данной реализации мы можем и использовать Server Sent Events, но предпочтем двухсторонний канал с заделом на будущее. ElCloudWeb.FileChannel - это модуль, в котором мы override’им некоторые методы, вроде join - подсоединение нового пользователя к каналу, send_message - отправка сообщения в канал, handle_info - поймает события с типом action. Этот модуль будет обрабатывать подключения к нашему бекенду и отвечать за отправку сообщений всем подписанным клиентам. На клиенте нужно создать инстанс сокета, подключиться к нужному каналу, и подписаться на получение сообщений об изменении. Для поиска нам нужно всего лишь рекурсивно пройтись по поданной директории, и постараться найти файл.  К сожалению, это будет работать достаточно долго. Как вариант, чтобы избежать этой проблемы, можно сделать индексацию всей файловой системы.  Например, это можно реализовать, если завести базу данных, записать данные о файловой структуре в плоском виде. На каждое изменение структуры файловой системы обновлять соответствующие записи в БД. И тогда при поиске не придется каждый раз обращаться к медленной файловой системе, и поиск будет работать значительно быстрее. Это далеко не самый хороший вариант, но этого уже достаточно для получения ускорения не индексированного поиска. Для реализации скачивания файла на фронтенде достаточно внести изменения, создав корректную ссылку на файл и добавив атрибут download на тег ссылки.  На бекенде нужно отправлять файл на клиент, исходя из переданного пути до файла. Для отправки файла на сервер необходимо создать объект FormData, в данном  случае еще важно учесть директорию, куда будем загружать файл. Также нужно выставить заголовок, что мы отправляем multipart содержимое запроса. На сервере же просто ловим путь из temp директории, и копируем файл в директорию с файлами.   Теперь освоим такой подход как pattern matching, который может под капотом понять, какой тип мы подаем в функцию и вызвать подходящее предопределенное определение. Таким подходом удобно можно заменить ветвление на один вызов функции и N определений функций, в которых мы положим действие из ветвления. Например, так мы можем переименовать файл. Несмотря на то, что часто строят пайплайн в другом порядке, передавая вниз по пайпу conn, в этом случае я бы сделал по-другому, и отправлял реальные часто изменяемые данные внутри пайпа. На данной итерации разработки приложения мы не будем прикручивать функциональность корзины,  поэтому удаление будет самой примитивной логикой, мы просто вызовем стандартный метод удаления файла. На уровне контроллера можно реализовать это таким образом, используя конвейер. Мы: Разработали клиент-серверное приложение на Elixir и React; Поняли, что разрабатывать на функциональном языке не так сложно, как кажется на первый взгляд; Разобрались в простых функциональных конструкциях. Полный исходный код можно найти в репозитории проекта. Студент Computer Science