Skip to content

Commit

Permalink
refactor: Extract EntityAndClassIriService and introduce ActorToZioBr…
Browse files Browse the repository at this point in the history
…idge and ActorDeps

*Extract EntityAndClassIriService
* Introduce ActorDeps
* Introduce ActorToZioBridge
* Remove methods on abstract Responder and use iriService in Responder directly
* Remove commented out code
* Move handleUnexpectedMessage method into Responder and make protected
* Expose ProjectsResponder as ProjectsService to ProjectRouteZ
* Add scaladoc
* Formatting
  • Loading branch information
seakayone committed Dec 23, 2022
1 parent 86a19ab commit 1a8daea
Show file tree
Hide file tree
Showing 39 changed files with 526 additions and 361 deletions.
3 changes: 2 additions & 1 deletion project/Dependencies.scala
Expand Up @@ -44,6 +44,7 @@ object Dependencies {
// zio-test and friends
val zioTest = "dev.zio" %% "zio-test" % ZioVersion
val zioTestSbt = "dev.zio" %% "zio-test-sbt" % ZioVersion
val zioMock = "dev.zio" %% "zio-mock" % "1.0.0-RC9"

// akka
val akkaActor = "com.typesafe.akka" %% "akka-actor" % AkkaActorVersion // Scala 3 compatible
Expand Down Expand Up @@ -127,7 +128,7 @@ object Dependencies {
zioTestSbt
).map(_ % IntegrationTest)

val webapiTestDependencies = Seq(zioTest, zioTestSbt).map(_ % Test)
val webapiTestDependencies = Seq(zioTest, zioTestSbt, zioMock).map(_ % Test)

val webapiDependencies = Seq(
akkaActor,
Expand Down
10 changes: 5 additions & 5 deletions webapi/src/it/scala/org/knora/webapi/CoreSpec.scala
Expand Up @@ -9,22 +9,23 @@ import akka.actor
import akka.testkit.ImplicitSender
import akka.testkit.TestKitBase
import com.typesafe.scalalogging.Logger

import org.knora.webapi.config.AppConfig
import org.knora.webapi.core.AppRouter
import org.knora.webapi.core.AppServer
import org.knora.webapi.core.TestStartupUtils
import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject
import org.knora.webapi.messages.util.ResponderData
import org.knora.webapi.store.cache.settings.CacheServiceSettings
import org.knora.webapi.util.LogAspect
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import zio._
import zio.logging.backend.SLF4J

import scala.concurrent.ExecutionContext

import org.knora.webapi.responders.ActorDeps

abstract class CoreSpec
extends AnyWordSpec
with TestKitBase
Expand Down Expand Up @@ -87,9 +88,8 @@ abstract class CoreSpec
val appActor = router.ref

// needed by some tests
val appConfig = config
val cacheServiceSettings = new CacheServiceSettings(appConfig)
val responderData = ResponderData(system, appActor, appConfig, cacheServiceSettings)
val appConfig = config
val responderData = ResponderData(ActorDeps(system, appActor, appConfig.defaultTimeoutAsDuration), appConfig)

final override def beforeAll(): Unit =
/* Here we start our app and initialize the repository before each suit runs */
Expand Down
6 changes: 6 additions & 0 deletions webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala
Expand Up @@ -11,6 +11,9 @@ import zio.ZLayer
import org.knora.webapi.auth.JWTService
import org.knora.webapi.config.AppConfig
import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.responders.ActorDeps
import org.knora.webapi.responders.ActorToZioBridge
import org.knora.webapi.responders.admin.ProjectsService
import org.knora.webapi.routing.ApiRoutes
import org.knora.webapi.routing.admin.ProjectsRouteZ
import org.knora.webapi.slice.resourceinfo.api.ResourceInfoRoute
Expand Down Expand Up @@ -54,7 +57,9 @@ object LayersLive {
*/
val dspLayersLive: ULayer[DspEnvironmentLive] =
ZLayer.make[DspEnvironmentLive](
ActorDeps.layer,
ActorSystem.layer,
ActorToZioBridge.live,
ApiRoutes.layer,
AppConfig.live,
AppRouter.layer,
Expand All @@ -67,6 +72,7 @@ object LayersLive {
IriConverter.layer,
JWTService.layer,
ProjectsRouteZ.layer,
ProjectsService.layer,
RepositoryUpdater.layer,
ResourceInfoRepo.layer,
ResourceInfoRoute.layer,
Expand Down
Expand Up @@ -6,7 +6,6 @@
package org.knora.webapi.core.actors

import akka.actor.Actor
import akka.actor.ActorSystem
import com.typesafe.scalalogging.Logger

import scala.concurrent.ExecutionContext
Expand Down Expand Up @@ -39,6 +38,7 @@ import org.knora.webapi.messages.v2.responder.resourcemessages.ResourcesResponde
import org.knora.webapi.messages.v2.responder.searchmessages.SearchResponderRequestV2
import org.knora.webapi.messages.v2.responder.standoffmessages.StandoffResponderRequestV2
import org.knora.webapi.messages.v2.responder.valuemessages.ValuesResponderRequestV2
import org.knora.webapi.responders.ActorDeps
import org.knora.webapi.responders.admin.GroupsResponderADM
import org.knora.webapi.responders.admin.ListsResponderADM
import org.knora.webapi.responders.admin.PermissionsResponderADM
Expand Down Expand Up @@ -67,59 +67,47 @@ import org.knora.webapi.store.iiif.IIIFServiceManager
import org.knora.webapi.store.triplestore.TriplestoreServiceManager
import org.knora.webapi.util.ActorUtil

class RoutingActor(
final case class RoutingActor(
cacheServiceManager: CacheServiceManager,
iiifServiceManager: IIIFServiceManager,
triplestoreManager: TriplestoreServiceManager,
appConfig: AppConfig,
runtime: zio.Runtime[Any]
) extends Actor {

implicit val system: ActorSystem = context.system
val log: Logger = Logger(this.getClass)

/**
* The Cache Service's configuration.
*/
implicit val cacheServiceSettings: CacheServiceSettings = new CacheServiceSettings(appConfig)

/**
* Provides the default global execution context
*/
implicit val executionContext: ExecutionContext = context.dispatcher

/**
* Data used in responders.
*/
val responderData: ResponderData = ResponderData(system, self, appConfig, cacheServiceSettings)
private val log: Logger = Logger(this.getClass)
private val actorDeps: ActorDeps = ActorDeps(context.system, self, appConfig.defaultTimeoutAsDuration)
private val cacheServiceSettings: CacheServiceSettings = new CacheServiceSettings(appConfig)
private val responderData: ResponderData = ResponderData(actorDeps, appConfig)
private implicit val executionContext: ExecutionContext = actorDeps.executionContext

// V1 responders
val ckanResponderV1: CkanResponderV1 = new CkanResponderV1(responderData)
val resourcesResponderV1: ResourcesResponderV1 = new ResourcesResponderV1(responderData)
val valuesResponderV1: ValuesResponderV1 = new ValuesResponderV1(responderData)
val standoffResponderV1: StandoffResponderV1 = new StandoffResponderV1(responderData)
val usersResponderV1: UsersResponderV1 = new UsersResponderV1(responderData)
val listsResponderV1: ListsResponderV1 = new ListsResponderV1(responderData)
val searchResponderV1: SearchResponderV1 = new SearchResponderV1(responderData)
val ontologyResponderV1: OntologyResponderV1 = new OntologyResponderV1(responderData)
val projectsResponderV1: ProjectsResponderV1 = new ProjectsResponderV1(responderData)
private val ckanResponderV1: CkanResponderV1 = new CkanResponderV1(responderData)
private val resourcesResponderV1: ResourcesResponderV1 = new ResourcesResponderV1(responderData)
private val valuesResponderV1: ValuesResponderV1 = new ValuesResponderV1(responderData)
private val standoffResponderV1: StandoffResponderV1 = new StandoffResponderV1(responderData)
private val usersResponderV1: UsersResponderV1 = new UsersResponderV1(responderData)
private val listsResponderV1: ListsResponderV1 = new ListsResponderV1(responderData)
private val searchResponderV1: SearchResponderV1 = new SearchResponderV1(responderData)
private val ontologyResponderV1: OntologyResponderV1 = new OntologyResponderV1(responderData)
private val projectsResponderV1: ProjectsResponderV1 = ProjectsResponderV1(actorDeps)

// V2 responders
val ontologiesResponderV2: OntologyResponderV2 = new OntologyResponderV2(responderData)
val searchResponderV2: SearchResponderV2 = new SearchResponderV2(responderData)
val resourcesResponderV2: ResourcesResponderV2 = new ResourcesResponderV2(responderData)
val valuesResponderV2: ValuesResponderV2 = new ValuesResponderV2(responderData)
val standoffResponderV2: StandoffResponderV2 = new StandoffResponderV2(responderData)
val listsResponderV2: ListsResponderV2 = new ListsResponderV2(responderData)
private val ontologiesResponderV2: OntologyResponderV2 = new OntologyResponderV2(responderData)
private val searchResponderV2: SearchResponderV2 = new SearchResponderV2(responderData)
private val resourcesResponderV2: ResourcesResponderV2 = new ResourcesResponderV2(responderData)
private val valuesResponderV2: ValuesResponderV2 = new ValuesResponderV2(responderData)
private val standoffResponderV2: StandoffResponderV2 = new StandoffResponderV2(responderData)
private val listsResponderV2: ListsResponderV2 = new ListsResponderV2(responderData)

// Admin responders
val groupsResponderADM: GroupsResponderADM = new GroupsResponderADM(responderData)
val listsResponderADM: ListsResponderADM = new ListsResponderADM(responderData)
val permissionsResponderADM: PermissionsResponderADM = new PermissionsResponderADM(responderData)
val projectsResponderADM: ProjectsResponderADM = new ProjectsResponderADM(responderData)
val storeResponderADM: StoresResponderADM = new StoresResponderADM(responderData)
val usersResponderADM: UsersResponderADM = new UsersResponderADM(responderData)
val sipiRouterADM: SipiResponderADM = new SipiResponderADM(responderData)
private val groupsResponderADM: GroupsResponderADM = new GroupsResponderADM(responderData)
private val listsResponderADM: ListsResponderADM = new ListsResponderADM(responderData)
private val permissionsResponderADM: PermissionsResponderADM = new PermissionsResponderADM(responderData)
private val projectsResponderADM: ProjectsResponderADM = ProjectsResponderADM(actorDeps, cacheServiceSettings)
private val storeResponderADM: StoresResponderADM = new StoresResponderADM(responderData)
private val usersResponderADM: UsersResponderADM = new UsersResponderADM(responderData)
private val sipiRouterADM: SipiResponderADM = new SipiResponderADM(responderData)

def receive: Receive = {

Expand Down Expand Up @@ -183,5 +171,4 @@ class RoutingActor(
s"RoutingActor received an unexpected message $other of type ${other.getClass.getCanonicalName}"
)
}

}
Expand Up @@ -54,14 +54,6 @@ trait InstrumentationSupport {
}
}

// def counter(name: String) = Kamon.metrics.counter(name)
// def minMaxCounter(name: String) = Kamon.metrics.minMaxCounter(name)
// def time[A](name: String)(thunk: => A) = Latency.measure(Kamon.metrics.histogram(name))(thunk)
// def traceFuture[A](name:String)(future: => Future[A]):Future[A] =
// Tracer.withContext(Kamon.tracer.newContext(name)) {
// future.andThen { case completed ⇒ Tracer.currentContext.finish() }(SameThreadExecutionContext)
// }

/**
* Based on the current class name, create a logger with the name in the
* form 'M-ClassName', e.g., 'M-RedisManager'.
Expand Down
Expand Up @@ -7,20 +7,25 @@ package org.knora.webapi.messages.util

import akka.actor.ActorRef
import akka.actor.ActorSystem
import akka.util.Timeout

import scala.concurrent.ExecutionContext

import org.knora.webapi.config.AppConfig
import org.knora.webapi.responders.ActorDeps
import org.knora.webapi.store.cache.settings.CacheServiceSettings

/**
* Data needed to be passed to each responder.
*
* @param system the actor system.
* @param appActor the main application actor.
* @param cacheServiceSettings the cache service part of the settings.
* @param actorDeps all dependencies necessary for interacting with the [[org.knora.webapi.core.actors.RoutingActor]]
* @param appConfig the application configuration for creating the [[CacheServiceSettings]]
*/
case class ResponderData(
system: ActorSystem,
appActor: ActorRef,
appConfig: AppConfig,
cacheServiceSettings: CacheServiceSettings
)
case class ResponderData(actorDeps: ActorDeps, appConfig: AppConfig) {
val cacheServiceSettings: CacheServiceSettings = new CacheServiceSettings(appConfig)

val appActor: ActorRef = actorDeps.appActor
val executionContext: ExecutionContext = actorDeps.executionContext
val system: ActorSystem = actorDeps.system
val timeout: Timeout = actorDeps.timeout
}
Expand Up @@ -7,7 +7,6 @@ package org.knora.webapi.messages.util.search.gravsearch.types

import akka.actor.ActorRef

import scala.concurrent.ExecutionContext
import scala.concurrent.Future

import dsp.errors.GravsearchException
Expand All @@ -16,7 +15,6 @@ import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.messages.admin.responder.usersmessages.UserADM
import org.knora.webapi.messages.util.ResponderData
import org.knora.webapi.messages.util.search._
import org.knora.webapi.settings.KnoraDispatchers

/**
* Runs Gravsearch type inspection using one or more type inspector implementations.
Expand All @@ -30,8 +28,7 @@ class GravsearchTypeInspectionRunner(
responderData: ResponderData,
inferTypes: Boolean = true
) {
private implicit val executionContext: ExecutionContext =
responderData.system.dispatchers.lookup(KnoraDispatchers.KnoraActorDispatcher)
private implicit val executionContext = responderData.actorDeps.executionContext

// If inference was requested, construct an inferring type inspector.
private val maybeInferringTypeInspector: Option[GravsearchTypeInspector] = if (inferTypes) {
Expand Down
Expand Up @@ -14,7 +14,6 @@ import scala.concurrent.Future
import org.knora.webapi.messages.admin.responder.usersmessages.UserADM
import org.knora.webapi.messages.util.ResponderData
import org.knora.webapi.messages.util.search.WhereClause
import org.knora.webapi.settings.KnoraDispatchers

/**
* An trait whose implementations can get type information from a parsed Gravsearch query in different ways.
Expand All @@ -29,10 +28,9 @@ abstract class GravsearchTypeInspector(
responderData: ResponderData
) {

protected val system: ActorSystem = responderData.system
protected implicit val executionContext: ExecutionContext =
system.dispatchers.lookup(KnoraDispatchers.KnoraActorDispatcher)
protected implicit val timeout: Timeout = responderData.appConfig.defaultTimeoutAsDuration
protected val system: ActorSystem = responderData.system
protected implicit val executionContext: ExecutionContext = responderData.executionContext
protected implicit val timeout: Timeout = responderData.timeout

/**
* Given the WHERE clause from a parsed Gravsearch query, returns information about the types found
Expand Down
54 changes: 54 additions & 0 deletions webapi/src/main/scala/org/knora/webapi/responders/ActorDeps.scala
@@ -0,0 +1,54 @@
/*
* Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.responders
import akka.actor.ActorRef
import akka.actor.ActorSystem
import akka.util.Timeout
import zio.ZIO
import zio.ZLayer

import scala.concurrent.ExecutionContext

import org.knora.webapi.config.AppConfig
import org.knora.webapi.core.AppRouter
import org.knora.webapi.settings.KnoraDispatchers

/**
* Class encapsulating all Akka dependencies necessary to interact with the [[org.knora.webapi.core.actors.RoutingActor]] aka. "appActor"
*
* When using this class in a service depending on the routing actor this will provide the necessary implicit dependencies for using the ask pattern
* whilst making the dependency explicit for ZIO layers.
*
* @example Usage in client code:
* {{{
* final case class YourService(actorDeps: ActorDeps){
* private implicit val ec: ExecutionContext = actorDeps.executionContext
* private implicit val timeout: Timeout = actorDeps.timeout
*
* private val appActor: ActorRef = actorDeps.appActor
*
* def someMethod = appActor.ask(SomeMessage())...
* }
* }}}
*
* @param system the akka.core.ActorSystem - used to extract the [[ExecutionContext]] from
* @param appActor a reference to the [[org.knora.webapi.core.actors.RoutingActor]]
* @param timeout the timeout needed for the ask pattern
*/
final case class ActorDeps(system: ActorSystem, appActor: ActorRef, timeout: Timeout) {
val executionContext: ExecutionContext = system.dispatchers.lookup(KnoraDispatchers.KnoraActorDispatcher)
}

object ActorDeps {
val layer: ZLayer[AppConfig with AppRouter, Nothing, ActorDeps] = ZLayer.fromZIO {
for {
router <- ZIO.service[AppRouter]
system = router.system
appActor = router.ref
timeout <- ZIO.service[AppConfig].map(_.defaultTimeoutAsDuration)
} yield ActorDeps(system, appActor, timeout)
}
}
@@ -0,0 +1,47 @@
package org.knora.webapi.responders
import akka.actor.Actor
import akka.actor.ActorRef
import akka.pattern.ask
import akka.util.Timeout
import zio.Tag
import zio.Task
import zio.URLayer
import zio.ZIO
import zio.ZLayer

import scala.reflect.ClassTag

import org.knora.webapi.messages.ResponderRequest

/**
* This trait encapsulates the [[akka.pattern.ask]] into the zio world
*/
trait ActorToZioBridge {

/**
* Sends a message to the "appActor" [[org.knora.webapi.core.actors.RoutingActor]] using the [[akka.pattern.ask]],
* casts and returns the response to the expected return type `R` as [[Task]].
*
* @param message The message sent to the actor
* @param tag implicit proof that the result type `R` has a [[ClassTag]]
*
* @tparam R The type of the expected success value
* @return A Task containing either the success `R` or the failure [[Throwable]],
* will fail during runtime with a [[ClassCastException]] if the `R` does not correspond
* to the response of the message being sent due to the untyped nature of the ask pattern
*/
def askAppActor[R: Tag](message: ResponderRequest)(implicit tag: ClassTag[R]): Task[R]

}

final case class ActorToZioBridgeLive(actorDeps: ActorDeps) extends ActorToZioBridge {
private implicit val timeout: Timeout = actorDeps.timeout
private val appActor: ActorRef = actorDeps.appActor

override def askAppActor[R: Tag](message: ResponderRequest)(implicit tag: ClassTag[R]): Task[R] =
ZIO.fromFuture(_ => appActor.ask(message, Actor.noSender).mapTo[R])
}

object ActorToZioBridge {
val live: URLayer[ActorDeps, ActorToZioBridgeLive] = ZLayer.fromFunction(ActorToZioBridgeLive.apply _)
}

0 comments on commit 1a8daea

Please sign in to comment.