Introdução ao Spring com Akka

Introdução ao Spring com Akka

1. Introdução

Neste artigo, vamos nos concentrar na integração da Akka com o Spring Framework - para permitir a injeção de serviços baseados em Spring em atores Akka.

Antes de ler este artigo, um conhecimento prévio dos fundamentos da Akka é recomendado.

Leitura adicional:

Introdução aos atores Akka em Java

Aprenda a criar aplicativos concorrentes e distribuídos usando o Akka Actors em Java.

Read more

Guia para córregos Akka

Um guia rápido e prático para transformações de fluxo de dados em Java usando a biblioteca Akka Streams.

Read more

Bota de Primavera e Kotlin

Aprenda a usar o Kotlin junto com o Spring Boot 2.x.

Read more

2. Injeção de dependência em Akka

Akka é uma estrutura de aplicativo poderosa baseada no modelo de simultaneidade do ator. A estrutura está escrita em Scala, que, naturalmente, também é totalmente utilizável em aplicativos baseados em Java. E entãoit’s very often we will want to integrate Akka with an existing Spring-based application ou simplesmente use o Spring para conectar os beans aos atores.

O problema com a integração Spring / Akka está na diferença entre o gerenciamento de beans no Spring e o gerenciamento de atores em Akka:actors have a specific lifecycle that differs from typical Spring bean lifecycle.

Além disso, os atores são divididos em um ator em si (que é um detalhe interno da implementação e não pode ser gerenciado pelo Spring) e uma referência de ator, acessível por um código de cliente, além de serializável e portátil entre diferentes tempos de execução do Akka.

Felizmente, o Akka fornece um mecanismo, a saberAkka extensions, que torna o uso de frameworks de injeção de dependência externa uma tarefa bastante fácil.

3. Dependências do Maven

Para demonstrar o uso de Akka em nosso projeto Spring, precisaremos de uma dependência mínima do Spring - a bibliotecaspring-context e também a bibliotecaakka-actor. As versões da biblioteca podem ser extraídas para a seção<properties> depom:


    4.3.1.RELEASE
    2.4.8



    
        org.springframework
        spring-context
        ${spring.version}
    

    
        com.typesafe.akka
        akka-actor_2.11
        ${akka.version}
    

Certifique-se de verificar o Maven Central para as versões mais recentes das dependênciasspring-contexteakka-actor.

E observe como, que a dependênciaakka-actor tem um postfix_2.11 em seu nome, o que significa que esta versão do framework Akka foi construída com base no Scala versão 2.11. A versão correspondente da biblioteca Scala será incluída transitivamente na sua compilação.

4. Injetando Spring Beans em Atores Akka

Vamos criar um aplicativo Spring / Akka simples que consiste em um único ator que pode responder pelo nome de uma pessoa enviando uma saudação a essa pessoa. A lógica da saudação será extraída para um serviço separado. Desejamos conectar esse serviço automaticamente a uma instância de ator. A integração da primavera nos ajudará nessa tarefa.

4.1. Definindo um ator e um serviço

Para demonstrar a injeção de um serviço em um ator, vamos criar uma classe simplesGreetingActor definida como um ator não tipado (estendendo a classe baseUntypedActor de Akka). O principal método de cada ator Akka é o métodoonReceive, que recebe uma mensagem e a processa de acordo com alguma lógica especificada.

Em nosso caso, a implementação deGreetingActor verifica se a mensagem é de um tipo predefinidoGreet, em seguida, pega o nome da pessoa da instânciaGreet e usa oGreetingService para receber uma saudação para essa pessoa e responder ao remetente com a sequência de saudação recebida. Se a mensagem for de algum outro tipo desconhecido, ela é passada para o métodounhandled predefinido do ator.

Vamos dar uma olhada:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class GreetingActor extends UntypedActor {

    private GreetingService greetingService;

    // constructor

    @Override
    public void onReceive(Object message) throws Throwable {
        if (message instanceof Greet) {
            String name = ((Greet) message).getName();
            getSender().tell(greetingService.greet(name), getSelf());
        } else {
            unhandled(message);
        }
    }

    public static class Greet {

        private String name;

        // standard constructors/getters

    }
}

Observe que o tipo de mensagemGreet é definido como uma classe interna estática dentro desse ator, o que é considerado uma boa prática. Os tipos de mensagem aceitos devem ser definidos o mais próximo possível de um ator, para evitar confusão sobre quais tipos de mensagem esse ator pode processar.

Also notice the Spring annotations @Component and @Scope - define a classe como um bean gerenciado por Spring com o escopoprototype.

O escopo é muito importante, porque cada solicitação de recuperação de bean deve resultar em uma instância recém-criada, pois esse comportamento corresponde ao ciclo de vida do ator de Akka. Se você implementar esse bean com algum outro escopo, o caso típico de reiniciar atores no Akka provavelmente funcionará incorretamente.

Finalmente, observe que não precisamos explicitamente@Autowire a instânciaGreetingService - isso é possível devido ao novo recurso do Spring 4.3 chamadoImplicit Constructor Injection.

A implementação deGreeterService é bastante direta, observe que o definimos como um bean gerenciado por Spring adicionando a anotação@Component a ele (com o escopo padrãosingleton):

@Component
public class GreetingService {

    public String greet(String name) {
        return "Hello, " + name;
    }
}

4.2. Adicionando Suporte Spring via Extensão Akka

A maneira mais fácil de integrar o Spring ao Akka é através de uma extensão do Akka.

An extension is a singleton instance created per actor system. Consiste em uma classe de extensão própria, que implementa a interface de marcadorExtension, e uma classe de id de extensão que geralmente herdaAbstractExtensionId.

Como essas duas classes estão fortemente acopladas, faz sentido implementar a classeExtension aninhada na classeExtensionId:

public class SpringExtension
  extends AbstractExtensionId {

    public static final SpringExtension SPRING_EXTENSION_PROVIDER
      = new SpringExtension();

    @Override
    public SpringExt createExtension(ExtendedActorSystem system) {
        return new SpringExt();
    }

    public static class SpringExt implements Extension {
        private volatile ApplicationContext applicationContext;

        public void initialize(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        public Props props(String actorBeanName) {
            return Props.create(
              SpringActorProducer.class, applicationContext, actorBeanName);
        }
    }
}

First -SpringExtension implementa um único métodocreateExtension da classeAbstractExtensionId - que é responsável pela criação de uma instância de extensão, o objetoSpringExt.

A classeSpringExtension também possui um campo estáticoSPRING_EXTENSION_PROVIDER que contém uma referência para sua única instância. Muitas vezes faz sentido adicionar um construtor privado para declarar explicitamente queSpringExtention é suposto ser uma classe singleton, mas vamos omiti-lo para maior clareza.

Secondly, a classe interna estáticaSpringExt é a própria extensão. ComoExtension é simplesmente uma interface de marcador, podemos definir o conteúdo desta classe como acharmos adequado.

Em nosso caso, vamos precisar do métodoinitialize para manter uma instância SpringApplicationContext - este método será chamado apenas uma vez por inicialização da extensão.

Além disso, exigiremos o métodoprops para criar um objetoProps. A instânciaProps é um projeto para um ator e, em nosso caso, o métodoProps.create recebe uma classeSpringActorProducere argumentos do construtor para esta classe. Esses são os argumentos com os quais o construtor dessa classe será chamado.

O métodoprops será executado sempre que precisarmos de uma referência de ator gerenciado por Spring.

The thirde a última peça do quebra-cabeça é a classeSpringActorProducer. Ele implementa a interfaceIndirectActorProducer da Akka, que permite substituir o processo de instanciação para um ator implementando os métodosproduceeactorClass.

Como você provavelmente já adivinhou,instead of direct instantiation, it will always retrieve an actor instance from the Spring’s ApplicationContext. Como fizemos do ator um bean com escopoprototype, cada chamada ao métodoproduce retornará uma nova instância do ator:

public class SpringActorProducer implements IndirectActorProducer {

    private ApplicationContext applicationContext;

    private String beanActorName;

    public SpringActorProducer(ApplicationContext applicationContext,
      String beanActorName) {
        this.applicationContext = applicationContext;
        this.beanActorName = beanActorName;
    }

    @Override
    public Actor produce() {
        return (Actor) applicationContext.getBean(beanActorName);
    }

    @Override
    public Class actorClass() {
        return (Class) applicationContext
          .getType(beanActorName);
    }
}

4.3. Juntando tudo

A única coisa que resta a fazer é criar uma classe de configuração Spring (marcada com a anotação@Configuration) que dirá ao Spring para escanear o pacote atual junto com todos os pacotes aninhados (isto é garantido pela anotação@ComponentScan ) e criar um contêiner Spring.

Precisamos apenas adicionar um único bean adicional - a instânciaActorSystem - e inicializar a extensão Spring nesteActorSystem:

@Configuration
@ComponentScan
public class AppConfiguration {

    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public ActorSystem actorSystem() {
        ActorSystem system = ActorSystem.create("akka-spring-demo");
        SPRING_EXTENSION_PROVIDER.get(system)
          .initialize(applicationContext);
        return system;
    }
}

4.4. Recuperando Atores Spring-Wired

Para testar se tudo funciona corretamente, podemos injetar a instânciaActorSystem em nosso código (algum código de aplicativo gerenciado por Spring ou um teste baseado em Spring), criar um objetoProps para um ator usando nosso extensão, recupere uma referência a um ator por meio do objetoProps e tente cumprimentar alguém:

ActorRef greeter = system.actorOf(SPRING_EXTENSION_PROVIDER.get(system)
  .props("greetingActor"), "greeter");

FiniteDuration duration = FiniteDuration.create(1, TimeUnit.SECONDS);
Timeout timeout = Timeout.durationToTimeout(duration);

Future result = ask(greeter, new Greet("John"), timeout);

Assert.assertEquals("Hello, John", Await.result(result, duration));


Aqui usamos o padrãoakka.pattern.Patterns.ask típico que retorna uma instânciaFuture do Scala. Assim que o cálculo for concluído, oFuture é resolvido com um valor que retornamos em nosso métodoGreetingActor.onMessasge.

Podemos esperar pelo resultado aplicando o métodoAwait.result do Scala aoFuture ou, mais preferivelmente, construir o aplicativo inteiro com padrões assíncronos.

5. Conclusão

Neste artigo, mostramos como integrar o Spring Framework ao Akka e aos feixes de arame automático nos atores.

O código-fonte do artigo está disponívelon GitHub.