Home Developer Codacy Architecture: Strongly Typed Actors

Codacy Architecture: Strongly Typed Actors

Author

Date

Category

We glimpse at Codacy architecture structure in this blog from Code Reading Wednesdays at Codacy (http://www.codacy.com).  The event helps code reviews become easier and automatic.

Codacy Overview

Being a code centered company, one thing we are consistently focused is on keeping code with high cohesion and low coupling.
As we previously mentioned, Codacy is built using Scala and Play, both of which have proved to be great decisions.
While building Codacy, to keep the code clean and logically contained, we decided to break our play application into several specialized components. This is a small glimpse of how our architecture is structured.

Components

A component is no more than a cohesive set of functionality that can be developed and compiled independently of other components.
We then let each component transmit a public API which will become the only coupling to the outside world (the remaining components).
To accomplish this, we used the sbt sub-project functionality and configured play to depend on these subprojects.

This is great for various reasons:

  • It makes for self-tested and smaller bundles of features
  • It reduces the amount of class dependencies hence reducing the compilation time
  • It creates greater organization and we can easily point out where to head next or what needs to be improved

Component Architecture

This is an example diagram of the component driven architecture (with some examples taken from our own architecture):

Note: The PlayWebsite component has a dependency on all components, not shown here for simplicity’s sake

We can see several components that are dependent of each other and connect to other outside sub systems (in this case Git and our DB).

The Git system represents our 3rd party execution of binaries (since we rely on it heavily to communicate with our users’ repositories).

Each project components was only responsible for one system task, relying on the framework for utilities like logging and system configuration settings, injected directly from the PlayWebsite configuration.

Unfortunately, as the project grew, we found the need to add more components, sometimes having inter-dependencies.

To solve this, we implemented a simple factory, that all components could depend on for object creation. (As a side note, the reason why we steered away from standard and vanilla Dependency Injection is enough for a new blog post by itself).
We wanted however to make this simple factory to be configurable and as non-binding as possible to our code.
Hence we created a register based dependency injection where components ask our ComponentFactory for instances of a type. This factory, in turn, returns the registered implementation for the requested type.

This is a final view of how this simple structure ended up impacting the architecture.

Implementation

Since we use Scala, we’re providing our Scala implementation (that depends somehow on Play 2).

To start, all components should first implement a trait that would be used for requesting a object creation.
As an example, let’s create an Engine class that extends an IEngine trait:

class Engine extends IEngine

To request an instance of the engine, a component would simply call:

val engine = ComponentFactory.getComponent[IEngine]

In order to work, all required components would need to be registered on application start. We started by configuring and registering all components (Play2 specific):

object Global extends GlobalSettings {
  override def onStart(app: Application) {
      ...
      ComponentFactory.setComponent[IEngine](classTag[Engine])
      ...
  }
}

Because we wanted to have different types of registered components for different executing environments (such as development, debug and production), we started setting the components by reading the registrations from a config file:

codacy.components.IEngine.interface="codacy.framework.component.IEngine"
codacy.components.IEngine.class="codacy.Engine"

This allowed us to quickly change the implementing classes without having to restart or recompile the application, only having to deploy a new jar and changing the configuration to use the new class.
Furthermore, this structure allows us to have the benefits of dependency injection without having to pollute our injected classes with cross concerning code (subcut).

Although this system worked for a development environment, we had to make sure the website would not have the blocked waiting for a component to finish some operation, especially as some I/O operations could potentially take a long time (eg, reading from a remote hosting provider). Hence we came with the idea of creating actors that were responsible for each component. So, we started using Akka and its actor system.
This is also the main reason why we didn’t choose guice: we wanted greater control on the execution and distribution of our components.

Actors with Type safety

Because we wanted our interface to be completely type checked, we used Typed Actors.
This way we achieve a truly object oriented approach which transforms message passing to method invocations and we use the compiler as much as possible.

For this, we extended the ComponentFactory to, instead of instantiating simple classes that implemented those interfaces, create them as strongly typed akka actors that implemented the exact same interface but performing these actions with all the features and safety that akka actors provide. So, instead of a simple class factory:

def getComponent[T <: AnyRef](implicit m: Manifest[T]): T = {
  val className = this.classCache.get(m.runtimeClass.getName)
  Class.forName(className)
       .getConstructor()
       .newInstance()
       .asInstanceOf[T]
}

We started providing strongly typed actors:

private[dependencyBuilder] def getComponentActor[T <: AnyRef](implicit m: Manifest[T]): T = {
  val className = this.classCache.get(m.runtimeClass.getName)
  TypedActor(system).typedActorOf(
      TypedProps(Reflect(system).actorClassFor(className.get)
  )
}

classCache is a simple SynchronizedMap holding class names for all configured traits Note: If trying to use this code snipped, be sure to check this Stackoverflow answer.

However intimidating at first, this code is quite clever and concise.
While this required absolutely no code changes in the rest of the application, we started having a system that worked on the shoulders of the awesome Akka toolkit. Pretty cool!

With this, we have a system that is fully message driven, with non blocking calls.

Next step is to have several instances of each component, with messages being delivered with the help of a Router.
That’ll be the subject of my next blog post.

Thanks for reading. Reach me on Twitter for more on this.


Edit: We just published an ebook: “The Ultimate Guide to Code Review” based on a survey of 680+ developers. Enjoy!


About Codacy

Codacy is used by thousands of developers to analyze billions of lines of code every day!

Getting started is easy – and free! Just use your  GitHub, Bitbucket or Google account to sign up.

GET STARTED

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Subscribe to our newsletter

To be updated with all the latest news, offers and special announcements.

Recent posts

Free Codacy Pro account to fight COVID-19

Our hearts go out to everyone who has been directly or indirectly impacted by the global coronavirus (COVID-19) pandemic. We are committed...

Introducing GitHub Apps for improved user access control

We are very excited to announce our recent migration to GitHub Apps to improve the experience of GitHub Cloud users. Now, you...

How Agile & Container Technology led to the rise of enterprise DevSecOps

New development processes and open-source technologies have shifted the technology security landscape for enterprises. Previously a separate security department often dealt with...

Spring tech events with Codacy: Coverage & what's next

Although many Spring tech events are being canceled or turning virtual due to coronavirus we are fortunate to have already participated in...

Top 6 items for your code review checklist

At Codacy we set high standards, and care about the quality of the code we produce. In order to provide optimal experiences...