Introduction of AkKA Ecological Panorama in March, 2018 – Zhihu (zhihu.com)

How to learn Scala, Akka, play Framework? – zhihu (zhihu.com)

Refer to documentation and case codes

The basic usage of Akka has been briefly described in the previous section, see Scala+Akka for heartbeat detection of master/master nodes – Nuggets (juejin. Cn) and Scala’s Akka framework for Combat – nuggets (juejin. Cn). Refer to Akka in Action for study notes for the entire column.

Pay attention to

Akka is an open source project built by Lightbend that aims to provide a simple, single programming model — an Actor programming model based on concurrent, distributed applications. Akka’s goal is to make it easier to deploy applications in the cloud, or to develop applications on multi-core devices. In other words, Akka enables projects to scale vertically (single machine) and horizontally (cluster).

Akka has quite a few extension modules, including Akka Cloud Platform, Akka Streams, Akka Actors (Core), Akka Http, etc.

Akka is a lib, not a framework. Not all Scala developers have the need to learn Akka, see: Why Akka (Actor Model) is lukewarm in China? – Zhihu.com. If you focus on business development, it is more appropriate to directly use a mature solution like Play, Flink based on Akka. Akka fits nicely into a domain that was stateful, such as a game server (whereas most Web applications are stateless).

Akka’s support for Scala 3 is experimental, and the JDK is still based on LTE versions 8 and 11.

Akka: build concurrent, distributed, and resilient message-driven applications for Java and Scala | Akka

The github link below provides the entire code for the original book. The code in this article corresponds to chapter chap-up -and- Running of Akka in Action.

git init
git clone https://github.com/RayRoestenburg/akka-in-action.git
Copy the code

Go to the chapter up-and-running directory, open the SBT interactive mode, and run the assembly command to compile the file. Alternatively, run the following command on the terminal:

sbt assembly
Copy the code

My SBT version is 0.13.7 and the JDK version is 8U241. A message similar to this is displayed after a successful compilation:

[info] SHA-1: b7a86b2544a0d3d5ec1f8096dc55e2ffe4e4fdab [info] Packaging C: \ Users \ LJH \ Desktop \ akka \ hello_world \ akka - in - action \ chapter - up - and - running \ target \ scala 2.11 \ goticks jar... [info] Done packaging. [success] Total time: 116 s, completed 2022-2-25 23:37:20Copy the code

The compiled file is stored in the ~/chapter-up-and-running/target/ Scala-2.11 /gotick-assembly-1.0.jar directory.

java -jar goticks.jar
# INFO [Slf4jLogger]: Slf4jLogger started
# INFO [go-ticks]: RestApi bound to /0:0:0:0:0:0:0:0:5000
Copy the code

In addition to assembly packaging a.jar, we can use other SBT commands. Such as:

sbt clean compile test
Copy the code

In addition, IntelliJ and SBT compatibility is currently a bit problematic. For example, an error may occur when using IntelliJ to import an external SBT project:

Extracting structure failed, reason: not ok build status: Error (BuildMessages(Vector(),Vector(),Vector(),Vector(),Error))
Copy the code

Solution: IDEA cannot re-import SBT project-Stack Overflow

SBT Reference Manual – SBT 1.4.x Releases (SCALa-sbt.org)

SBT mirror warehouse configuration author’s old notes: Play2 / SBT operation guide – Nuggets (juejin. Cn)

SBT Introduction and Building Scala Project – Skydome 2018 – Blogpark (CNblogs.com)

Actor

The Akka system is based on actors. Many of the components in Akka provide instructions on how to use actors, configure actors, connect networks with actors, schedule, and build clusters. Actors themselves can be thought of as miniaturized message queues — thousands or millions of actors can easily be created.

Actors communicate with each other through messages. A message is a simple immutable data structure.

Execution between actors is asynchronous: when they send a message to another Actor, they don’t have to wait for an immediate response.

The overall purpose of its design can be found in the Reaction Declaration (Reactivemanifesto.org) :

  1. I/O blocking limits parallelism, so non-blocking I/O is preferred.
  2. Synchronous interactions limit parallelism, so use asynchronous interactions.
  3. Polling consumes resources, so the need to use event-driven is preferred.
  4. If one node slows down other nodes, isolation is needed to avoid losing other work.
  5. The system needs to be resilient. If there is less demand, then fewer resources should be used; If there are more demands, then more resources should be used.

The idea of the Actor model was proposed as early as 1973 by Carl Hewitt et al., and the Erlang language and its OTP have supported the Actor model since 1986. Akka’s implementation details are slightly different from Erlang’s, but are influenced by Erlang in many ways.

An Actor is a “lightweight process” that has only four basic operations: create, send, change, and supervise.

send

Actors interact with each other only through messages. In an object, any method declared public can be accessed, but the Actor does not allow outsiders to access its internal state, including the list of messages in the Actor. Messages are sent asynchronously, which is called fire and forget style.

Messages are immutable, which means they cannot be changed once they have been created. Note: Actors can receive and send any message, which means Scala’s static type checking mechanism is limited.

Message delivery is ordered between actors that send and receive messages. An Actor accepts only one message at a time. If modifying information is involved, a new copy of the message needs to be sent. If a user modifies a message multiple times, the message that the user eventually modifies is meaningful.

Creation and Supervision

An Actor can create other actors and need to oversee the ones they create.

change

State machines are powerful tools to ensure that a system performs a specific function in a specific state. Actors receive only one message at a time, and this mechanism is suitable for implementing state machines. An Actor can change the way it handles messages by exchanging its behavior. Suppose a Session Actor that processes a Session becomes closed after receiving CloseConservation. This way, any subsequent messages sent to the Session Actor are ignored.

Decoupling of three dimensions

Actors decouple in the following three dimensions:

  1. Space – An Actor does not guarantee, nor does it expect, where the physical address of another Actor is.
  2. Time – The Actor does not guarantee, nor does it expect, when its work will be completed.
  3. Interfaces – Actors have no defined interfaces because they interact with each other through RPC messages.

Actor System

All the actors that build a service make up an ActorSystem. The first step in creating an Akka application is to create ActorSystem. ActorSystem creates the top-level Actor and returns a reference to that Actor, ActorRef, rather than itself. ActorRef is the channel through which actors pass messages — especially if actors are on other machines, this makes sense.

Another way to find actors in a system is ActorPath. Each Actor has its own name (which should be unique within the same system), and the Actor hierarchy can be compared to urls.

The message is sent to the Actor’s ActorRef and passed through the Akka system’s Dispatcher. The continuously accumulated information will be sent to the Mailbox of the other Actor (equivalent to message cache queue, similar to Chan of Go), and the other Actor will take out messages from the Mailbox and process them in turn. Therefore, the essence of Actor sending messages can be summarized as follows: Actor A delivers the message to Mailbox B through ActorRef B, and then waits for the dispenser to gradually push the message to Actor B for processing.

Akka’s actors take up less space than system threads: about 2.7 million actors take up 1 Gb of space, which means you can create actors more freely than if you were using threads directly.

ActorSystem is at the heart of Actors, while other features such as remote calls and persistent logging are provided in the form of Akka extensions. That is, ActorSystem can be configured differently for specific problems.

Example: Build a miniature service using Akka HTTP

The following uses the Actor core library and Akka HTTP to implement a simple HTTP server. Here is a personal translation of Akka HTTP for reference: Akka HTTP document (unofficial Chinese) – guide – Zhihu (zhihu.com). Background: Our GoTickets server allows users to purchase tickets for a variety of services (concerts, games, whatever), each with a single ID. Administrators can create an event name and issue a specified number of tickets, or simply cancel an event; Users can purchase multiple tickets with serial numbers for the same event at one time, query activities and other basic operations.

The project structure

The interface documentation is as follows:

describe methods URL Body StateCode The instance
Create activities POST /events/{event_name} {“tickets”:250} 201 Created {“name”:”RHCP”,tickets:250}
Acquisition activities GET /events/{event_name} _ 200 OK {“name”:”RHCP”,tickets:250}
Get all activities GET /events _ 200 OK [{“name”:”RHCP”,tickets:249},{“name”:”Radio”,tickets:130}]
Buy a ticket POST /events/{event_name}/tickets {“tickets”:2} 201 Created {“event”:”RHCP”,”entries”:[{“id”:1},{“id”:2}]}
Cancel the activity DELETE /events/{event_name} _ 200 OK {“event”:”RHCP”,”tickets”:249}

The project has the following core classes:

In order to make the logic of the code look more clear, the general framework of the code is first given below, and the specific functions to be implemented are temporarily used. Method to mark.

The singleton Main, which inherits App, can be automatically recognized by the SBT tool as a program entry, and the Main program will start as an HTTP server.

  1. Read the configuration and set the listening port. (Given)
  2. Mixed withRequestTimeoutAttribute setting request timeout (given)
  3. Create Akka system and dispenser and set to context (given)
  4. Create HTTP Server (TODO)
import akka.actor.ActorSystem
import akka.util.Timeout
import com.typesafe.config.{Config.ConfigFactory}
import scala.concurrent.ExecutionContextExecutor

object Main extends App with RequestTimeout {
  // Configuration related
  val config = ConfigFactory.load()
  val host = config.getString("http.host")
  val port = config.getInt("http.port")
    
  // Akka core: ActorSystem, and message distributor
  implicit val sys: ActorSystem = ActorSystem(a)implicit val ec: ExecutionContextExecutor = sys.dispatcher

  // Start the RestApi and run it as a daemon.
  val api = new RestApi(sys = sys, timeout = requestTimeout(config))
  // TODO external listening connection, using API, sys, EC????? }// Used to detect the nature of a timeout connection.
trait RequestTimeout {  
  import scala.concurrent.duration._
  def requestTimeout(config: Config) :Timeout = {
    val t = config.getString("akka.http.server.request-timeout")
    val d = Duration(t)
    FiniteDuration(d.length, d.unit)
  }
}
Copy the code

TicketSeller takes the Event parameter and is created by BoxOffice Actor when it receives the request by calling the createTicketSeller() method (the basic implementation of which is shown below). For example, when a context.actorof (TicketSeller. Props (“RHCP”), RHCP) is created, the external HTTP server created an activity named “RCHP”.

  1. It is the actual handler of the business, including the logout business, which means the Actor will destroy itself.
  2. Each TickekSeller Actor is autonomous and maintains its own business.
package com.gotickets

import akka.actor.{Actor.Props}

object TicketSeller {
  // Used for ActorSystem to create TicketSeller
  def props(event : String) = Props(new TicketSeller(event))
}

class TicketSeller(event : String) extends Actor{
  // handle the messages sent by the BoxOfficer, and handle the ticketing business here
  override def receive: Receive=??? }Copy the code

BoxOffice is proxied by BoxOffice API, while BoxOffice API, RestRoutes, and RestApi maintain a hierarchical relationship.

  1. RestRoutesprovidesRestApiHTTP request processing function, in the form ofRestApiWith theRestRoutesCharacteristics.RestApiIt is only responsible for providing the context of ActorSystem. In addition, THE RestRoutes returns a Route type that the Akka HTTP component needs to import to provide HTTP services externally.
  2. BoxOfficeApiprovidesRestRoutesBoxOfficeIn the form ofRestRoutesWith theBoxOfficeApiCharacteristics.
  3. BoxOfficeApiResponsible for TicketSeller Actor creation and information interaction.
  4. Receiving and returning HTTP packets involves serialization and deserialization of Scala messages, and the external interaction adopts JSON data format. Data conversion work useEventMarshallingTo complete.
package com.gotickets

import akka.actor.{Actor.ActorRef.ActorSystem.Props}
import akka.util.Timeout
import spray.json.DefaultJsonProtocol
import scala.concurrent.ExecutionContext

object BoxOffice {
  def props(implicit timeout: Timeout) = Props(new BoxOffice)}class BoxOffice(implicit timeout: Timeout) extends Actor {

  // Assign messages (tasks) to TicketSeller based on the received messages
  override def receive: Receive=???// Used to create TicketSeller Actor
  def createTicketSeller(name : String) =
    context.actorOf(TicketSeller.props(name),name)
}

// BoxOfficeApi,
// Similar to BoxOfficeHandler
trait BoxOfficeApi {

  // By default, ActorSystem does the assembly
  def createBoxOffice() :ActorRef
  lazy val boxOffice: ActorRef = createBoxOffice()

  implicit def executionContext : ExecutionContext
  implicit def requestTimeout : Timeout

  // TODO takes the processed information from boxOffice and creates TicketSeller Actor????? }trait RestRoutes extends BoxOfficeApi with EventMarshalling {
  // Return some form of routing mechanism to Main
  def routes =???// TODO creates REST methods, like Spring's @requestMapping ("...") )????? }// RestApi only needs to provide context.
class RestApi(sys : ActorSystem,timeout: Timeout) extends RestRoutes with EventMarshalling {
  override def createBoxOffice() :ActorRef =  sys.actorOf(BoxOffice.props,"boxOffice")
  override implicit def executionContext: ExecutionContext = sys.dispatcher
  override implicit def requestTimeout: Timeout = timeout
}

// Note that the RestApi has the potential to receive Post requests, so it needs to provide implicit serialization of Json -> Entity.
trait EventMarshalling extends DefaultJsonProtocol {????? }Copy the code

In addition, messages between actors are expressed as the sample case Class or the sample singleton Case Object, depending on whether the message itself needs to carry parameters. All messages of the system are stored in MessageProtocols singleton objects.

package com.gotickets

object MessageProtocols {

  // For generic errors
  case class Error(msg : String)
  
  // Express an activity, a generic message for the activity list: name denotes the name of the activity, and tickets denotes the number of remaining tickets.
  case class Event(name : String,tickets : Int)
  case class Events(events : Vector[Event])

  // HTTP Request
  case object GetEvents // GET
  case class GetEvent(name : String) // POST
  case class GetTickets(event: String,tickets : Int) // POST
  case class CancelEvent(name : String) //DELETE
  // HTTP POST Request Body
  case class EventDescription(tickets : Int){require(tickets > 0)}
  case class TicketRequest(tickets : Int){require(tickets > 0)}
  
  // Initiated by RestApi and received by BoxOffice to create an activity Event.
  case class CreateEvent(name : String,ticketCounts : Int)
  
  // BoxOffice returns to RestApi whether the new Event was created successfully. EventCreated is returned on success and EventExists is returned on repetition.
  sealed trait EventResponse
  case class EventCreated(event: Event) extends EventResponse
  case object EventExists extends EventResponse

  // Seal BoxOffice <=> TicketSeller interaction protocol.
  // GetThisEvent returns the activity state managed by TicketSeller Actor itself as an Event.
  TicketSeller Actor that receives CancelThis will be destroyed after the Event message is returned.
  case object TicketSeller {
    case class Ticket(id : Int)
    case class Add(tickets : Vector[Ticket])
    case class Buy(tickets : Int)
    case class Tickets(event : String,entries : Vector[Ticket] = Vector.empty[Ticket])
    case object GetThisEvent
    case object CancelThis}}Copy the code

This article presents a “bottom-up” implementation: first implement TickekSeller to handle the core business, then BoxOffice, then RestApi, and finally complete the logic of Main.

TicketSeller

TicketSeller manages the core business code and is therefore the easiest part to understand. Each TicketSeller Actor internally maintains a list of tickets that represent information about the remaining tickets for that activity. Review the following basic uses of Akka:

  1. When a message needs to be sent back, instead of showing the ActorRef that found the sender, you just call sender().
  2. Each Actor expresses the processing of different messages by defining receive partial functions.
  3. !Indicates that the message is discarded immediately after sending. For more, see:Actor model of communication mode | tisonkun (lmlphp.com)
  4. throughselfPointer to TicketSeller ActorRef itself, not usedthisThe keyword.
  5. self ! PoisonPillDestroys its own Actor and its Ref; Only if ActorRef acceptsCancelThisMessage is executed.
class TicketSeller(event : String) extends Actor{
  import MessageProtocols.TicketSeller. _import MessageProtocols.Event

  var tickets: Vector[Ticket] = Vector.empty[Ticket]

  // sender() sends a message back to the message sender
  // PoisonPill; Meaning "poison", make Actor logout itself
  override def receive: Receive = {
    case Add(newTickets) => tickets = tickets ++ newTickets
    // If there are not enough votes left, return null.
    case Buy(nums) =>
      val entries: Vector[Ticket] = tickets.take(nums)
      if(entries.size >= nums){
        sender() ! Tickets(event,entries)
        tickets = tickets.drop(nums)
      }else sender() ! Tickets(event)
    case GetThisEvent => sender() ! Some(Event(event,tickets.size))
    case CancelThis => sender() ! Some(Event(event,tickets.size)); self !PoisonPill}}Copy the code

BoxOffice

BoxOffice itself does not deal with the detailed logic of “selling tickets”, it only creates, or finds, the corresponding TicketSeller ActorRef and forwards the task (the task is the message). The following points need to be noted:

  1. askRepresents asynchronous request content (with the option of synchronous wait), equivalent to?. It’s disposable!There is a difference. The return value of this request is oneFuture[T]Because the message sender is not sure when the result will be available.
  2. forwardmessage-indicatingforwarding. For example, if the RestApi sends a message to BoxOffice, which forwards the message to TicketSeller, then TicketSeller’ssender()Will point to the RestApi.
  3. throughcontext.child(name)Retrieves the child Actor(Ref)s managed by the current Actor.
  4. context.child(event)Returns aOption[ActorRef]Because the context is not guaranteedActorRefThere must be. In order tocontext.child(event).fold(create())(_ => sender() ! EventExists)Call as an example, indicating: If returnedOption[ActorRef]NoneThe callcreate()Method (this is a named call and will delay execution), otherwise, subsequent methods need to be passed in and executed, similarlyEmpty or callThe logic.
  5. Sources inpipe ... to(orpipeToMethod) can be obtained in the current ActorRefFuture[T]The results are then forwarded to another ActorRef.
  6. sequenceIs a common generalization of the expression “rollover” in functional programming. See:Scala: Exception Handling in Functional Programming – Nuggets (juejin. Cn)In theOptionEitherUnderstanding.
class BoxOffice(implicit timeout: Timeout) extends Actor {

  import MessageProtocols.TicketSeller. _import com.gotickets.MessageProtocols. {Event.GetEvents}
  import context._

  // TODO allocates messages (tasks) to TicketSeller based on the message received
  override def receive: Receive = {
    case CreateEvent(name, tickets) => 
      def create() :Unit = {
        val thisNewEvent: ActorRef = createTicketSeller(name)
        // By default, Tickets are created from id: 1.
        // Ticket in map is the constructor, equivalent to id => Ticket(id).
        val newTickets: Vector[Ticket] = (1 to tickets).map {
          Ticket
        }.toVector
        thisNewEvent ! Add(newTickets)
        sender() ! EventCreated(Event(name, tickets))
      }
      // Context. child represents the child Actor that the current Actor is looking for.
      // It will only be created if it cannot be found.
      // The left side of the fold is a named call. The right-hand side accepts an Actor => B function
      context.child(name).fold(create())(_ => sender() ! EventExists)

    case GetTickets(event, tickets) =>
      def notFound() :Unit = sender() ! Tickets(event)
      // Forward This results in the sender() of TicketSeller being the RestApi rather than BoxOffice.
      def buy(child: ActorRef) :Unit = child.forward(Buy(tickets))
      context.child(event).fold(notFound())(buy)

    case GetEvent(event) =>
      def notFound() :Unit = sender() ! None
      def getEvent(child: ActorRef) :Unit = child.forward(GetThisEvent)
      context.child(event).fold(notFound())(getEvent)

    case GetEvents= >// Create a broadcast mechanism. This is equivalent to constantly polling yourself for the Event.
      def getEvents: 可迭代[Future[Option[Event]]] = context.children.map {
        child => self.ask(GetEvent(child.path.name)).mapTo[Option[Event]]}// Sequence is A common "flip" operation in FP, used to flip A[B] to B[A].
      // For example, in Option, the sequence method can flip Option[List[_]] to List[Option[_]}.
      def convertToEvents(f: Future[可迭代[Option[Event]]]) :Future[Events] = {
        // import akka.actor.TypedActor.dispatcher
        // Future { Iterable[Option[Event]] => Iterable[Event] => Events(Vector[Event]) }
        f.map(optionSeqs => optionSeqs.flatten).map(l => Events(l.toVector))
      }

      // Forward a Future[T] to another ActorRef.
      pipe(convertToEvents(Future.sequence(getEvents))) to sender()

    case CancelEvent(event) =>
      def notFound() :Unit = sender() ! None
      def cancelEvent(child: ActorRef) :Unit = child.forward(CancelThis)
      context.child(event).fold(notFound())(cancelEvent)
  }

  // Used to create TicketSeller Actor
  def createTicketSeller(name: String) :ActorRef =
    context.actorOf(TicketSeller.props(name), name)
}
Copy the code

BoxOffice API acts as a BoxOffice agent, holding a reference to a corresponding BoxOffice ActorRef that encapsulates the internal interaction with BoxOffice. The createBoxOffice method, which is the abstract method defined by RestRoutes, is implemented.

trait BoxOfficeApi {

  import MessageProtocols. _// By default, ActorSystem does the assembly
  def createBoxOffice() :ActorRef

  lazy val boxOffice: ActorRef = createBoxOffice()

  implicit def executionContext: ExecutionContext

  implicit def requestTimeout: Timeout

  // TODO takes the processed information from boxOffice and creates TicketSeller Actor
  // Send GetEvents asynchronously to boxOffice(Ref) to accept Events messages.
  def getEvents: Future[Events] = boxOffice.ask(GetEvents).mapTo[Events]

  def createEvent(event: String, ticketCounts: Int) :Future[EventResponse] = boxOffice.ask(CreateEvent(event, ticketCounts)).mapTo[EventResponse]

  def getEvent(event: String) :Future[Option[Event]] = boxOffice.ask(GetEvent(event)).mapTo[Option[Event]]

  def cancelEvent(event: String) :Future[Option[Event]] = boxOffice.ask(CancelEvent(event)).mapTo[Option[Event]]
    
  def getTickets(event: String, tickets: Int) :Future[TicketSeller.Tickets] = boxOffice.ask(GetTickets(event, tickets)).mapTo[TicketSeller.Tickets]}Copy the code

RestApi

RestRoutes provides a routing mechanism that instructs Akka HTTP how to handle received requests. This section of logic is similar to Controller in the Java SpringBoot project (although it is clear that the Akka HTTP DSL follows a very different message processing mechanism from SpringBoot), On the inside, the result is obtained through the BoxOfficeApi agent.

OnSuccess (getEvents) (the same goes for other code) is more similar to: getEvents onSuccess {events => complete(OK,events)}. Here, so you need to import the akka. HTTP. Scaladsl. Marshallers. Sparyjson. SprayJsonSupport. _ (OK, events) automatic encapsulation for the HTTP Response body.

trait RestRoutes extends BoxOfficeApi with EventMarshalling {

  import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport. _// Create a routing mechanism
  def routes: Route = eventsRoute~eventRoute~ticketsRoute

  // TODO creates REST methods
  def eventsRoute: Route = pathPrefix("events") {
    // pathEndOrSingleSlash: accept events or events/
    pathEndOrSingleSlash {
      get {
        // GET /events
        // getEvents comes from the BoxOfficeApi feature, same as below.
        onSuccess(getEvents) {
          // An implicit conversion is used to wrap (OK,events) into JSON.
          / / you need to import the akka. HTTP. Scaladsl. Marshallers. Sprayjson. SprayJsonSupport. _
          events => complete(OK, events)
        }
      }
    }
  }

  def eventRoute: Route = pathPrefix("events" / Segment) {

    event => {
      // Event corresponds to the argument for POST
      pathEndOrSingleSlash {
        post {
          // POST /events/{event}
          // Body {"tickets":250}
          entity(as[EventDescription]) {
            ed =>
              onSuccess(createEvent(event, ed.tickets)) {
                case EventCreated(event) => complete(Created, event)
                case EventExists= >val err: MessageProtocols.Error = MessageProtocols.Error(s"$event exists already.")
                  complete(BadRequest, err)
              }
          }
        } ~ get {
          // GET /events/{event}
          onSuccess(getEvent(event)) { maybeEvent =>
            // maybeEvent is an Option[Event] type, which can be expressed directly as fold:
            maybeEvent.fold(complete(NotFound))(event => complete(OK,event))
          }
        } ~ delete {
          onSuccess(cancelEvent(event)) {
            // It is also possible to pass a pattern match for Option[Event], with the same semantics.
            case Some(event) => complete(OK,event)
            case None => complete(NotFound)}}}}}def ticketsRoute : Route = pathPrefix("events" / Segment / "tickets") {
    event => {
      pathEndOrSingleSlash {
        post {
          entity(as[TicketRequest]){
            ticketRequest => {
              onSuccess(getTickets(event,ticketRequest.tickets)) {
                case tickets if tickets.entries.isEmpty => complete(NotFound)
                case tickets => complete(Created,tickets)
              }
            }
          }
        }
      }
    }
  }
}
Copy the code

The RestApi mixes RestRoutes with the message sequence/deserialization attribute EventMarshalling, which essentially describes all the message routing and processing schemes for the project. In addition, it provides the ActorSystem and message distributor created by Main as contexts:

class RestApi(sys: ActorSystem, timeout: Timeout) extends RestRoutes {
  override def createBoxOffice() :ActorRef = sys.actorOf(BoxOffice.props, "boxOffice")

  override implicit def executionContext: ExecutionContext = sys.dispatcher

  override implicit def requestTimeout: Timeout = timeout
}
Copy the code

Main

Http().bindandHandler (API,host,port) causes the main program to start as an Http server. ActorMaterializer is an implicit context that must be created in Akka. Stream, because AKka HTTP treats incoming HTTP requests as message flows, I presume. The parameter API is routes, the routing mechanism returned by the RestApi instance. In the updated version, ActorMaterializer no longer needs to be explicitly defined: Migration Guide to and within Akka HTTP 10.2.x • Akka HTTP.

object Main extends App with RequestTimeout {
  val config = ConfigFactory.load()
  val host = config.getString("http.host")
  val port = config.getInt("http.port")

  implicit val sys: ActorSystem = ActorSystem(a)implicit val ec: ExecutionContextExecutor = sys.dispatcher
  val api = new RestApi(sys = sys, timeout = requestTimeout(config)).routes

  // TODO external listening connection
  implicit val materializer =  ActorMaterializer(a)val bindingFuture = Http().bindAndHandle(api,host,port) // Start HTTP server

  val log = Logging(sys.eventStream,"go-tickets")
  bindingFuture.map {
    serverBinding =>
      log.info(s"Rest Api bound to ${serverBinding.localAddress}")
  }.onFailure {
    case ex : Exception =>
      log.error(ex,"Failed to bind to {}:{}",host,port)
      sys.terminate()
  }
}
Copy the code

Finally, create the application.conf file in the project folder and do the following basic configuration:

akka {
  http {
    server {
      request-timeout = 8s
    }
  }
}

http {
  host = "0.0.0.0"
  port = 4396
}
Copy the code

*. Conf configuration is written in a hierarchy similar to *. Yml.

Run and package

Log in to the SBT interactive terminal and run the run command to start the program.

[info] [info] [02/28/2022 13:28:39.037] [default-akka.actor. Default-dispatcher-5] [go-tickets] Rest Api bound to / 0:0:0:0:0:0:0:0:43 96Copy the code

You can use the Postman tool to send messages to ports on the native machine and test them. In this instance, all the data is in memory, so every time the project is restarted, the data is lost.

If you want to package the source code as a JAR, you can use SBT’s Assembly integration plug-in. Add this plugin to /project/plugins.sbt:

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
Copy the code

Configure the assembly option in the build.sbt root directory (the in syntax is marked out of date in newer versions of the SBT tool, but still works fine) :

mainClass in assembly := Some("com.gotickets.Main")
assemblyJarName in assembly := "go-ticks.jar"
Copy the code

In SBT, refresh the configuration with the reload command, then execute assembly, and the compiled JAR package can be found under the target/ Scala-x.x/in the project root directory.

java -jar ~\go-ticks.jar
Copy the code