Skip to content

Getting Started

Angel Sanadinov edited this page Sep 13, 2017 · 6 revisions

core3 is based on Play 2.6.x and Akka 2.5.x, so some familiarity with them is essential (especially Play). Here are some resources that can help you get started:

Before writing anything it might be a good idea to get to decide on your application's structure. Also, there are two example projects that can be used as a more hands-on starting point:

Add dependencies

libraryDependencies += "com.interelgroup" %% "core3" % "2.1.0"

Released for Scala 2.11 and 2.12

See Additional Dependencies for other libraries you may need.

Create containers

You don't have to use containers if your application does not need to store anything or if persistence is handled in a different way.

The basic approach is to define all the fields, decide on the supported DALs and implement the appropriate container definition traits:

import core3.database
import core3.database.containers._
import core3.database._
import core3.utils.Time._
import core3.utils._
import play.api.libs.json._

case class TestContainer(
  test: String,
  created: Timestamp,
  var updated: Timestamp,
  var updatedBy: String,
  id: ObjectID,
  var revision: RevisionID,
  var revisionNumber: RevisionSequenceNumber)
  extends MutableContainer {
  override val objectType: ContainerType = "TestContainer"
}

object TestContainer {
  trait BasicDefinition extends BasicContainerDefinition {
    override def getDatabaseName: String = "test-container"
  
    override def matchCustomQuery(
      queryName: String,
      queryParams: Map[String, String],
      container: Container):
    Boolean = ???
  }
  
  //with Json support
  trait JsonDefinition extends JsonContainerDefinition {
    override def toJsonData(container: Container): JsValue = {
      Json.toJson(container.asInstanceOf[Group])
    }
  
    override def fromJsonData(data: JsValue): Container = {
      data.as[Group]
    }
  }
}

Warning: Each container must have a unique objectType for your application! This means that defining containers called TransactionLog, LocalUser or Group can cause issues. You can go around that by not using transaction logs, not using local auth or not using groups.

Check out the containers page for more details.

Create workflows

You don't have to use workflows if they don't make sense for your application or if they are too restrictive; the data layer can be used directly through the DatabaseAbstractionLayer interface (see Provide DB).

The easiest way to go about it is to define a list of actions that your users will be performing (for example: create project, update article, lock user, etc) and create a workflow per action. For each workflow you will need to define and implement the following:

  • Workflow parameters (if any) - the parameters supplied by the user (or some other external entity)
  • Input data (if any) - the data retrieved from the DB
  • Action - the actual work to be done

Check out the workflows page for more details.

Provide DB

In your application's Module.scala (example with Redis):

import akka.actor.ActorSystem
import akka.util.Timeout
import com.google.inject.{AbstractModule, Provides, Singleton}
import core3.config.StaticConfig
import core3.database._
import core3.database.containers.{BasicContainerDefinition, JsonContainerDefinition, core}
import core3.database.dals.json.Redis
import core3.database.dals.{Core, DatabaseAbstractionLayer}
import core3.workflows._
import net.codingwell.scalaguice.ScalaModule

import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

class Module extends AbstractModule with ScalaModule {

  ...
    type DefsMap = Map[ContainerType, BasicContainerDefinition with JsonContainerDefinition]

    @Provides
    @Singleton
    def provideContainerDefinitions(): DefsMap = {
      val groupDefinitions = 
        new core.Group.BasicDefinition 
          with core.Group.JsonDefinition
      
      val transactionLogDefinitions = 
        new core.TransactionLog.BasicDefinition 
          with core.TransactionLog.JsonDefinition
      
      val localUserDefinitions = 
        new core.LocalUser.BasicDefinition 
          with core.LocalUser.JsonDefinition
    
      Map(
        "Group" -> groupDefinitions,
        "TransactionLog" -> transactionLogDefinitions,
        "LocalUser" -> localUserDefinitions
      )
    }
  
    @Provides
    @Singleton
    def provideDB(definitions: DefsMap)
      (implicit system: ActorSystem, ec: ExecutionContext): DatabaseAbstractionLayer = {
  
      val storeConfig = StaticConfig.get.getConfig("database.redis")
      implicit val timeout = Timeout(StaticConfig.get.getInt("database.requestTimeout").seconds)
  
      val storeActor = system.actorOf(
        Redis.props(
          storeConfig.getString("hostname"),
          storeConfig.getInt("port"),
          storeConfig.getString("secret"),
          storeConfig.getInt("connectionTimeout"),
          definitions,
          storeConfig.getInt("databaseID"),
          storeConfig.getInt("scanCount")
        )
      )
  
      new DatabaseAbstractionLayer(
        system.actorOf(
          Core.props(
            Map(
              "Group" -> Vector(storeActor),
              "TransactionLog" -> Vector(storeActor),
              "LocalUser" -> Vector(storeActor)
            )
          )
        )
      )
    }

  ...

}

Check out the databases page for more details and config.

Provide Engine

In your application's Module.scala (example with all core workflows):

import akka.actor.ActorSystem
import akka.util.Timeout
import com.google.inject.{AbstractModule, Provides, Singleton}
import core3.config.StaticConfig
import core3.database.dals.DatabaseAbstractionLayer
import core3.workflows._
import net.codingwell.scalaguice.ScalaModule

import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

class Module extends AbstractModule with ScalaModule {

  ...

  @Provides
  @Singleton
  def provideWorkflows(): Vector[WorkflowBase] = {
    Vector(
      definitions.SystemCreateGroup,
      definitions.SystemCreateLocalUser,
      definitions.SystemDeleteGroup,
      definitions.SystemDeleteLocalUser,
      definitions.SystemQueryGroups,
      definitions.SystemQueryLocalUsers,
      definitions.SystemQueryTransactionLogs,
      definitions.SystemUpdateGroup,
      definitions.SystemUpdateLocalUserMetadata,
      definitions.SystemUpdateLocalUserPassword,
      definitions.SystemUpdateLocalUserPermissions
    )
  }
  
  @Provides
  @Singleton
  def provideEngine(
    system: ActorSystem,
    workflows: Vector[WorkflowBase],
    db: DatabaseAbstractionLayer,
    definitions: DefsMap
  )(implicit ec: ExecutionContext): WorkflowEngine = {
    val engineConfig = StaticConfig.get.getConfig("engine")
    implicit val timeout = Timeout(engineConfig.getInt("requestTimeout").seconds)
  
    new WorkflowEngine(
      system.actorOf(
        WorkflowEngineComponent.props(
          workflows,
          db,
          StoreTransactionLogs.fromString(engineConfig.getString("storeLogs")),
          TransactionLogContent.WithParamsOnly,
          TransactionLogContent.WithDataAndParams
        )(ec, definitions)
      )
    )
  }

  ...
}

Check out the Workflow Engine page for more details and config.

Create controllers

You don't have to use controllers if communication and security is handled in a different way.

This will be the code that communicates with the outside world: renders pages, parses parameters, makes service requests, etc. You can start with the basics then move on to how to use and configure them.

Create UI

Play's template engine is one way to go but this part is entirely up to you.

Run It

PlayConsole

Clone this wiki locally