-
Notifications
You must be signed in to change notification settings - Fork 55
Module structure
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:
- Simple module config
- Init Service
- Messaging service
- Custom service
- Run block
- Adding module to SOCRAT
- Extended module config
- Data service
- Sidebar controller and template
- Main controller and template
- Custom directive
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.
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.
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
.
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.
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).
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.
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.
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
.
TODO: finish writing