Привет, Хабр!
Микросервисы давно являются некой "попсой" для создания гибких, масштабируемых и отказоустойчивых систем. И естественное имеет свою реализацию в функциональном программирование.
В статье рассмотрим два языка программирования, которые выделяются своим функциональным подходом и широким применением в микросервисной архитектуре: 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. На странице курса вы можете бесплатно посмотреть записи прошедших вебинаров, а также подробно ознакомиться с программой.
dolfinus
Акторная модель ведь фича не самой Scala, а Akka