Привет, Хабр!
Микросервисы давно являются некой "попсой" для создания гибких, масштабируемых и отказоустойчивых систем. И естественное имеет свою реализацию в функциональном программирование.
В статье рассмотрим два языка программирования, которые выделяются своим функциональным подходом и широким применением в микросервисной архитектуре: Scala и Erlang.
Scala
Scala - это достаточной мощный ЯП, который сочетает в себе функциональные и объектно-ориентированные подходы.
Одной из ключевых фич Scala заключается в его акторной модели, которая (очевидно) основана на концепции акторов. Акторы в Scala представляют собой небольшие вычислительные сущности, которые общаются друг с другом посредством отправки и получения сообщений. Такой подход к параллельному и распределенному программированию обеспечивает высокую степень отказоустойчивости и масштабируемости, что для микросервисов, как мы знаем - очень важно.
ФП в Scala поддерживается мощными функциональными конструкциями, такими как функции высшего порядка, неизменяемые структуры данных и паттерн матчинга.
Кроме того, Scala обладает большой экосистемой библиотек и фреймворков, спецом разработанных для создания микросервисов. Например, Akka — это популярный фреймворк, основанный на акторной модели Scala.
Еще важно отметить, что Scala также имеет преимущества в интеграции с существующим Java-кодом, что делает его привлекательным выбором для компаний, уже имеющих код на Java.
Пример создания микросервиса на Scala
Прежде всего, установим Scala и необходимые зависимости. Также воспользуемся Akka HTTP для обработки HTTP запросов:
// build.sbt name := "microservice-example" version := "1.0" scalaVersion := "2.13.8" libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-actor-typed" % "2.6.17", "com.typesafe.akka" %% "akka-stream" % "2.6.17", "com.typesafe.akka" %% "akka-http" % "10.2.8", "com.typesafe.akka" %% "akka-http-spray-json" % "10.2.8" )
Создадим актор для обработки запросов:
import akka.actor.typed.ActorSystem import akka.actor.typed.scaladsl.Behaviors object Main extends App { val system = ActorSystem(Behaviors.empty, "microservice") // define actors and behavior here }
Создадим просто�� маршрут для API:
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Route object Routes { def helloRoute: Route = path("hello") { get { complete("Hello, Habr!") } } }
Настроим HTTP сервер с использованием Akka HTTP:
import akka.http.scaladsl.Http import akka.actor.typed.scaladsl.adapter._ object Main extends App { val system = ActorSystem(Behaviors.empty, "microservice") val routes = Routes.helloRouter Http().bindAndHandle(routes, "localhost", 8080)(system.toClassic) }
Запускаем:
object Main extends App { val system = ActorSystem(Behaviors.empty, "microservice") val routes = Routes.helloRoute // Add more routes as needed Http().bindAndHandle(routes, "localhost", 8080)(system.toClassic) println("Server online at http://localhost:8080/") }
Это базовый пример.
Scala также позволяет легко выполнять асинхронные HTTP запросы к внешним сервисам или микросервисам. Пример использования Akka HTTP Client для отправки GET запроса:
import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.model._ import scala.concurrent.Future import scala.util.{Success, Failure} import akka.stream.ActorMaterializer import scala.concurrent.ExecutionContext.Implicits.global object HttpClientExample { def main(args: Array[String]): Unit = { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = "http://example.com")) responseFuture.onComplete { case Success(response) => println(s"Request successful: $response") // handle response case Failure(ex) => println(s"Request failed: $ex") } } }
Scala позволяет создавать гибкие маршруты для обработки HTTP запросов с помощью Akka HTTP. Пример маршрута, который принимает POST запросы на путь /api/data и фильтрует запросы по определенным условиям:
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Route object Routes { def dataRoute: Route = pathPrefix("api") { path("data") { post { entity(as[String]) { requestData => // process the request data complete("Data received: " + requestData) } } } } }
Часто есть необходимость в управлении состоянием. Есть различные подходы к этой задаче, включая использование акторов для изоляции состояния. Пример:
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.{ActorRef, ActorSystem, Behavior} object StateManagement { // define messages sealed trait Command case class UpdateState(data: String) extends Command case class GetState(replyTo: ActorRef[String]) extends Command // define actor behavior def stateManager(state: String): Behavior[Command] = Behaviors.receiveMessage { case UpdateState(data) => stateManager(data) case GetState(replyTo) => replyTo ! state Behaviors.same } def main(args: Array[String]): Unit = { val system = ActorSystem(stateManager("Initial state"), "state-manager") val stateManagerRef = system .unsafeUpcast[StateManagement.Command] .narrow stateManagerRef ! GetState(System.out) } }
Erlang
Фича Erlang также как и в Scala заключается в его акторной модели. Также есть механизм supervision, который позволяет строить отказоустойчивые системы. Каждый процесс в Erlang имеет надзорный процесс, который отвечает за его состояние. В случае сбоя процесса, надзорный процесс автоматом перезапускает его
Также Erlang обладает возможностью хот-свопа кода. Т.е можно обновлять приложение в реал тайме без простоев. Новая версия кода может быть внедрена в работающую систему без перезапуска.
И самое главное - Erlang изначально разработан для построения распределенных систем.
Реализация
GenServer является базой для создания микросервисов на Erlang. Он обеспечивает асинхронное взаимодействие и управление состоянием:
-module(example_service). -behaviour(gen_server). %% API -export([start_link/0, get_state/1]). %% Callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% State -record(state, {data = []}). %% API functions start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). get_state(Pid) -> gen_server:call(Pid, get_state). %% Callback functions init([]) -> {ok, #state{}}. handle_call(get_state, _From, State) -> {reply, State#state.data, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}.
Supervisor обеспечивает отказоустойчивость и перезапуск микросервисов в случае сбоев:
-module(example_supervisor). -behaviour(supervisor). %% API -export([start_link/0]). %% Callbacks -export([init/1]). %% Init function start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% Callback function init([]) -> {ok, {{one_for_one, 5, 10}, [{example_service, {example_service, start_link, []}, permanent, 5000, worker, [example_service]}]}}.
Развернем на кластере:
%% запуска микросервиса на удаленном узле start_remote_service(Node) -> {ok, _} = net_kernel:start([example@hostname]), {ok, _} = rpc:call(Node, example_service, start_link, []).
Масштабирование может быть достигнуто путем запуска доп. экземпляров микросервисов на разных узлах кластера:
%% запуск дополнительных экземпляров микросервиса на разных узлах кластера start_additional_instances(NumInstances, Node) -> lists:foreach(fun(_) -> start_remote_service(Node) end, lists:seq(1, NumInstances)).
Определяем функцию start_additional_instances, которая принимает два аргумента: NumInstances, представляющий собой количество дополнительных экземпляров микросервиса, которые хотим запустить, и Node, представляющий собой имя узла кластера, на котором мы хотим развернуть эти экземпляры.
Затем используем функцию lists:seq/2, чтобы сгенерировать список чисел от 1 до NumInstances. Далее используем функцию lists:foreach/2, чтобы выполнить функцию start_remote_service для каждого элемента списка.
Статья подготовлена в преддверии старта курса Microservice Architecture. На странице курса вы можете бесплатно посмотреть записи прошедших вебинаров, а также подробно ознакомиться с программой.
