Skip to content
Alexandr Kalinin edited this page Aug 4, 2017 · 7 revisions

SOCRAT is designed to be highly scalable. According to large-scale architecture principles, each module implements standard structure. You can add a new module to SOCRAT in a few easy steps:

Module template:

Custom Directive

Module template

Each module is represented by a collection of files, one file per AngularJs component, such as module, controller, directive, or service. These components are implemented as CoffeeScript classes in CommonJs module format. SOCRAT provides base classes for different types of components, which can be used to create custom implementations via inheritance mechanism. Services, as well as controllers and directives, are represented by custom classes, which instances will be automatically created later, opposite to module configs, that create module instance from base class instead of inheriting from it.

Let's create new instance of simple module, that doesn't have UI.

Simple module config

We can start by creating a new directory MyModule under scripts/analysis/ and create MyModule.module.coffee file inside:

'use strict'
# import module class
Module = require 'scripts/BaseClasses/BaseModule.coffee'
# export instance of new module
module.exports = myModule = new Module
  # module id for registration
  id: 'socrat_analysis_mymodule'
  # module components
  components:
    services:
      'socrat_analysis_mymodule_initService': require 'scripts/analysis/MyModule/MyModuleInit.service.coffee'
      'socrat_analysis_mymodule_msgService': require 'scripts/analysis/MyModule/MyModuleMsgService.service.coffee'
      'socrat_analysis_mymodule_myService': require 'scripts/analysis/MyModule/MyModuleMyService.service.coffee'
    runBlock: require 'scripts/analysis/MyModule/MyModuleRunBlock.run.coffee'

Here we can see that module instance we created contains just a few key items. id is a string that makes it addressable by SOCRAT. components contains imports of all services (also, controllers and directives) and run block of current module by referring to their implementations via require statement.

First 2 services are required for any module to initialize and communicate. Let's take a closer look on their implementation.

Init Service

Create MyModuleInit.service.coffee:

'use strict'
# import base init service class
BaseModuleInitService = require 'scripts/BaseClasses/BaseModuleInitService.coffee'
# export custom init service class
module.exports = class MyModuleInitService extends BaseModuleInitService
  # requires injection of message service as a dependency
  @inject 'socrat_analysis_mymodule_msgService'
  # entry point function:
  initialize: ->
    # this renaming is required for initialization!
    @msgService = @socrat_analysis_mymodule_msgService
    # required method call to initiate module messaging interface
    @setMsgList()

As you can see it's easy to create Init Service class based on already existing class that hides all necessary functions. Only thing we need to do is to provide a link to messaging service, which is a required dependency. To do that first we need to call base @inject method with message service id from module config. Then, in the standard initialize instance method we need to rename messaging service method to @msgService, such that base class methods can recognize and use it.

As a next step we can look at how to implement messaging service which will allow our module to interact with SOCRAT.

Messaging service

To be able to send and receive data from SOCRAT every module has use messaging system. For that purpose it's required to have messaging service that provides SOCRAT with module-specific list of messages, so Core modules can correctly organize inter-module communications.

Create MyModuleMsgService.service.coffee file:

'use strict'
# import base messaging module class
BaseModuleMessageService = require 'scripts/BaseClasses/BaseModuleMessageService.coffee'
# export custom messaging service class
module.exports = class MyModuleMsgService extends BaseModuleMessageService
  # required to define module message list
  msgList:
    outgoing: []
    incoming: []
    # required to be the same as module id
    scope: ['socrat_analysis_mymodule']

Again, implementation is very easy since all necessary methods are already are in the base class. Strict requirements here are presence of msgList with outgoing, incoming (both can be empty as long as defined), and scope which should always contain a string that matches module id from module config file.

Now we can create custom service that contains main logic of our module.

Custom service

Create MyModuleMyService.service.coffee:

'use strict'
# import base class for data service
BaseService = require 'scripts/BaseClasses/BaseService.coffee'
# export custom data service class 
module.exports = class MyModuleMyService extends BaseService
  initialize: () ->
  showAlert: ->
    alert 'I pray Thee, O Developer, that I may be beautiful within.'

This custom service simply defines a method that sends alert message to the screen.

Now we need to create run block to kickstart the module. Note, that if module implements controllers and corresponding routing state, it doesn't require run block and is started via controllers instead (see example below).

Run block

Create MyModuleRunBlock.run.coffee:

'use strict'
module.exports = class MyModuleRunBlock
  constructor: (@module) ->
  register: ->
    @module.run @myModuleRunBlock()
  myModuleRunBlock: ->
    runBlock = (socrat_analysis_mymodule_myService) ->
      socrat_analysis_mymodule_myService.showAlert()
    # inject dependencies for run block
    runBlock.$inject = ['socrat_analysis_mymodule_myService']
    runBlock

Run block file implements a class with an empty constructor, required register function, and custom run block function that calls showAlert method of our custom service, that is injected as a dependency.

That's it! In 5 simple files under 10 lines of code each, our custom module is fully functional and is ready to be added to SOCRAT infrastructure.

Adding module to SOCRAT

First, open src/scripts/Socrat/SocratModuleList.coffee and edit the class SocratModuleList by adding path to your module config to the analysis list, e.g.:

  analysis: [
    # ... already existing objects are here ...
    require 'scripts/Database/Database.module.coffee'
  ,
    require 'scripts/analysis/MyModule/MyModule.module.coffee'
  ,
    Tools: [
      require 'scripts/analysis/tools/My/Cluster.module.coffee'
    ]

This will add module config to global config, so SOCRAT is aware of it.

Now SOCRAT is ready to be started with your new module. Go ahead and try it!


Although it looks like an overkill to implement such a simple module, this structure is important to build real-world modules that can represent small applications by themselves. Now we can add more functionality to our example to demonstrate how it can work with data, implement UI, and appear in main menu under Tools drop-down.

Extended module config

Let's start with editing our module config by adding one more service, controllers, and directive:

'use strict'
# import module class
Module = require 'scripts/BaseClasses/BaseModule.coffee'
# export instance of new module
module.exports = myModule = new Module
  # module id for registration
  id: 'socrat_analysis_mymodule'
  # module components
  components:
    services:
      'socrat_analysis_mymodule_initService': require 'scripts/analysis/tools/MyModule/MyModuleInit.service.coffee'
      'socrat_analysis_mymodule_msgService': require 'scripts/analysis/tools/MyModule/MyModuleMsgService.service.coffee'
      'socrat_analysis_mymodule_dataService': require 'scripts/analysis/tools/MyModule/MyModuleDataService.service.coffee'
      'socrat_analysis_mymodule_myService': require 'scripts/analysis/tools/MyModule/MyModuleMyService.service.coffee'
    controllers:
      'mymoduleMainCtrl': require 'scripts/analysis/tools/MyModule/MyModuleMainCtrl.ctrl.coffee'
      'mymoduleSidebarCtrl': require 'scripts/analysis/tools/MyModule/MyModuleSidebarCtrl.ctrl.coffee'
    directives:
      'socratMyModuleDir': require 'scripts/analysis/tools/MyModule/MyModuleDir.directive.coffee'
  # module state config
  state:
    # module name to show in UI
    name: 'My Awesome Module'
    url: '/tools/mymodule'
    mainTemplate: require 'partials/analysis/tools/MyModule/main.jade'
    sidebarTemplate: require 'partials/analysis/tools/MyModule/sidebar.jade'

Biggest change here that we add new field to the config. state is used to add a new state to routing system, identified by corresponding url, providing name that will be used in the SOCRAT main menu and Jade templates with module UI. Both templates have corresponding controllers, to which we will come back later. Just notice that we don't need runBlock anymore. Right now, let's take a look at the new service.

Data service

SOCRAT provides a convenience base service class to simplify requesting data inside a module. It can also be created in few lines of code. Create MyModuleDataService.service.coffee:

'use strict'
# import base class for data service
BaseModuleDataService = require 'scripts/BaseClasses/BaseModuleDataService.coffee'
# export custom data service class 
module.exports = class MyModuleDataService extends BaseModuleDataService
  # requires injection of $q and message service
  @inject '$q', 'socrat_analysis_mymodule_msgService'
  # requires renaming message service injection to @msgService
  initialize: () ->
    @msgManager = @socrat_analysis_cluster_msgService
    # indication of default messages for requesting and receiving data from SOCRAT
    @getDataRequest = @msgManager.getMsgList().outgoing[0]
    @getDataResponse = @msgManager.getMsgList().incoming[0]

Code is very similar to previous services and includes already familiar initialize instance method with renaming of messaging service. It also requires to identify which messages from msgList of MyModuleMsgService should be used to request and receive data from SOCRAT. In order to do that first we need let's add messages to MyModuleMsgService:

'use strict'
# import base messaging module class
BaseModuleMessageService = require 'scripts/BaseClasses/BaseModuleMessageService.coffee'
# export custom messaging service class
module.exports = class MyModuleMsgService extends BaseModuleMessageService
  # define module message list
  msgList:
    outgoing: ['mymodule:getData']
    incoming: ['mymodule:receiveData']
    # required to be the same as module id
    scope: ['socrat_analysis_mymodule']

Now both MyModuleMsgService and MyModuleDataService have access to messages that they can use to communicate with SOCRAT, e.g. request data from database. To do that happen open src/scripts/Socrat/ScoratMessageMap.coffee and and edit the class SocratMessageMap by adding two message redirection paths to @_msgMap:

module.exports = class SocratMessageMap
  constructor: () ->
    @_msgMap = [
      # ... already existing objects are here ...
    ,
      msgFrom: 'mymodule:getData'
      scopeFrom: ['socrat_analysis_mymodule']
      msgTo: 'database:getData'
      scopeTo: ['socrat_analysis_database']
    ,
      msgFrom: 'database:receiveData'
      scopeFrom: ['socrat_analysis_database']
      msgTo: 'mymodule:receiveData'
      scopeTo: ['socrat_analysis_mymodule']
    ]

This will tell SOCRAT to correctly redirect requests from our module to database. It is needed because both our custom module and database module are unaware of each other's existence and we need to provide Core modules information on how to organize communication between them.

Next, we move onto creating controller and their corresponding Jade templates. SOCRAT by default splits working area of application into 2 parts: sidebar, where modules should locate their UI controls, and mainArea, which serves as container for main module content: visualizations, tables, etc. We will start with sidebar.

Sidebar controller and template

Main controller and template

Custom directive

TODO: finish writing