In MOSIP, the source code modules are categorized under different repositories:
Commons - This contains common reusable library modules and service modules
Kernel - The collection of common reusable library modules and services
ID-Repo - The ID Repository services used by many of the MOSIP modules
Feature Specific Modules (for example)
registration
registration-processor
pre-registration
id-authentication
If a new source code module is planned to be added, and if it is a reusable library / service on which other modules will be depending on, then it should be added under Commons, otherwise, it can be created as a separate repository.
Source code of any feature in MOSIP will be organized as below:
feature-parent
feature-core - jar
feature-common - jar
feature-service-1 - SpringBoot service
feature-service-2 - SpringBoot service
This should be a POM module that aggregates all the sub-modules. This should contain,
List of all sub-modules
Common properties such as versions used by all sub-modules
Common dependencies used by all of the sub-modules
Common maven-plugins used by all sub modules
There should be a JAR module that defines core source code files such as,
APIs (Application Programming Interfaces)
SPIs (Service Provider Interfaces)
Common Utility classes
Helper classes
DTO classes
Custom Exception classes
There can be a JAR module that provides abstract base implementations or common implementations of the APIs and SPIs. Other modules such as service module will be depending on this module using the API/SPI implementations or extending their base implementations.
Any Service implementation class should have 'Impl' suffix.
A feature can have one or more Spring Boot Service modules, each will have one or more related services.
Each Spring Boot Service Modules will be deployed as a separate deployment unit such as a docker container. So each will contain the separate Dockerfile
containing the docker build instructions for that module.
Each Spring Boot Service Modules should be configured with Swagger configuration (except specific cases where) for easy understanding of the feature, easy unit-testing and verification for the developers.
No Spring Boot Service module should be used as a dependency to any other module. Any such code dependency should be going inside the Common Implementation module.
In MOSIP following naming of Class/Interfaces their names should be of Camel cases, without any underscore.
They should have following suffixes in their names and they should be under appropriate package name based on their category,
XyzService - For a Service interface. A service class should not be annotated with @Service
. This will be in the core-module. This will be under *.spi.xyz.service
package.
XyzServiceImpl - For a Spring Service implementation class. This class should be annotated with @Service
. This should not be defined in a core-module. It will be defined in a common
or service
module. This will be under *.spi.xyz.service.impl
package.
XyzRequestDto - For a Request Data Transfer class in a Service. This will be in the core-module. This will be under *.xyz.dto
package.
XyzResponseDto - For a Response Data Transfer class in a Service. This will be in the core-module. This will be under *.xyz.dto
package.
XyzEntity or XyzDao- For an Entity/ Data Access class, which will use JPA annotations for the entity. This will be in the core-module. This will be under or *.xyz.entity
or *.xyz.dao
package.
XyzRepository - For a JPA Repository interface, which will be extending the base MOSIP Repository interface io.mosip.kernel.core.dataaccess.spi.repository.BaseRepository
. This will be annotated with @Repository annotation. This will be under *.xyz.repository
package.
XyzController - For a Spring Controller class, which will be annotated with @Controller annotation. This will be under *.xyz.controller
package.
XyzConfiguration - For a Spring configuration, which will be annotated with @Configuration. This will be under *.xyz.config
package.
XyzUtil or XyzUtils - For a utility class. This will expose public static utility methods. This will be under *.xyz.util
package.
XyzHelper - For a helper class. This will be a Spring Component class annotated with @Component. This will expose public instance helper methods. This will be under *.xyz.helper
package.
XyzManager - For any management class. Also for any class that will connect external module using REST service calls. This will be a Spring Component class annotated with @Component.
XyzConstants - For file storing constants. Please prefer to have a Constants file to define common and related constants used across the classes in a feature. This can be inside the core-module or common-module. This will be under *.xyz.constant
package.
XyzException - For a custom exception, which will be extending io.mosip.kernel.core.exception.BaseCheckedException
and io.mosip.kernel.core.exception.BaseUncheckedException
. This will be defined in a core-module. This will be under *.xyz.exception
package.
XyzExceptionHandler - For the centralized exception handler class in a service-module, annotated with @ControllerAdvice
or @RestControllerAdvice
annotation. This will be under *.xyz.exception
package.
XyzFactory - For any class/interface defining Factory design pattern. This will be under *.xyz.factory
package.
Xyzbuilder - For any class/interface defining Builder design pattern. This will be under *.xyz.builder
package.
XyzFacade - For any class/interface defining Facade design pattern.
XyzImpl - For any implementation class that extends an API/SPI interface.
A service implementation should be always with a combination of Service Interface and its Spring Service Implementation Class. It should not be without a Service Interface defined in core-module.
Any Spring Component will be annotated with @Component
. A component may or may not be implementing an interface.
Any dependency injection of a Spring Service should only to be assigned to a variable of its Service Interface type but not to its Implementation class type.
For dependency injection of a Spring Component should be assigned to a variable its Interface type if any, otherwise to the class type.
All data classes such as DTOs, Entity classes use Lombok annotations to automatically create appropriate constructors, getters and setters.
Make sure to setup the IDE used for the development for using Lombok.
Any JPA repository created in MOSIP should be extending the base MOSIP Repository interface io.mosip.kernel.core.dataaccess.spi.repository.BaseRepository
.
Appropriate database configurations properties should be added to the app configuration file.
MOSIP has many common libraries and services implemented under Commons/Kernel repository.
Kernel-Core module contains may common utility classes such as Loggers, DateUtils, StringUtils and Crypto-Core etc… Also many utility services such as Kernel-KeyManager, Kernel-CryptoService, Kernel-Audit-Service, Kernel-Auth-Service, etc... .
If any common utility needs to be implemented, first check Commons/Kernel if such utility is already present and make sure it is not already implemented in Commons/Kernel. It is always welcomed to contribute any new features/ bug fixes for the existing utilities, instead of creating a new utility.
Any request and response content in a service in MOSIP should be of application/json content type.
Any byte arrays to be returned in response should be Base-64-URL encoded within the response.
If any sensitive information is getting transferred in the request/response, the request/response block should be encrypted using the MOSIP public key
.
Any service in MOSIP should never return error code other than 200. One or more errors to be reported in the response be returned as an array as part of "errors"
attribute.
Each of the "error"
attribute should under "errors"
should have "errorCode"
, "errorMessage
" and an optional "actionMessage"
. * For any service, possible "errorCode"
, "errorMessage"
and the optional "actionMessage"
should be properly listed documented in its API specification.
Any request/response body should have proper meta-data such as "id
, "version"
and "requestTime"
/"responseTime"
, etc... .
For example:
The number of lines in the Java files is restricted to 2000 lines. Beyond 2000 lines, the java file is refactored into multiple files.
Each java file contains one public class or interface.
When some private classes and interfaces are associated, this can be in the same file.
Declare the public class and interface as the first declaration in the file.
When a java file is written, the following order is maintained,
The beginning comment should be in a C-style comment. Following is the format of the comment.
The first non-comment line is the package statement.
After a line-break below the package statement, import statements are placed. The import statements are grouped and segregated by a line-break. For example,
Do not use asterisk symbol while importing packages.
This comment will be going in to the Javadocs
Then the public class or interface is defined.
Then other private class or interface are followed.
Following is the order of elements inside the class declaration
Constant fields (static and final fields) should be on the top inside a Class
Then any non-final static fields are followed
The public class variables are followed by the protected and the private variables. These can be final or non-final fields.
Initializing Fields in a class
Do not initialize non-primitive fields with null in a Class;
Always initialize non-static fields from constructors in a Class.
Avoid using static non-final fields in a class. If it required for any specific reason, avoid initializing it in a constructor or a post-construct method.
Avoid using static initializers to initialize static final/non-final fields, instead create private static methods and call it for initializing static fields.
The constructor declarations ordered by the number of parameters.
The methods are ordered by the functionality. The methods need not to be in the order of scope or accessibility.
The indentation unit it TAB.
The number of characters in a line should not exceed 80 characters
When the number of characters exceed the limit, the line should be broken into multiple lines. The following standard is used during the line breaks,
Break the line after the comma
Break before the operator
From higher-level breaks go to the lower-level breaks.
Align the new line with the beginning of the expression at the same level on the previous line.
There are two types of comments in Java.
Implementation comments
Documentation comments
Both the comment types are used in the source code of MOSIP.
This is the comment type used for better understanding of the code. Most of the times, the source code will be maintained by different people. The programmer writes these comments so that the other programmers will come to know the functionality of the code in plain English language. Following lines are used.
Java source code can have their implementation comments in various parts of code blocks/lines with appropriate description about what the code block or line is doing.
Specifically the if-else if-else
conditions can have their descriptions about their various conditional expressions.
Any complex piece of code such as a regular expression / mathematical expression can be described with appropriate descriptions about it.
When the developer needs to explain in detail about some functionality, block comments are used. Block comments are used anywhere in the code. It can be for the class or interface declarations or it can be within the java methods also. Example of block comments:
Single line comments are used for short description. For example,
Trailing comments are given in the same line where the source code resides. For example,
The end-of-line comments are also used in the source file. For example,
Documentation comments are used to generate the javadoc files.
Every source code file in Java should have proper Java Documentation such as description, Author and Version.
All Classes, Interfaces, Methods, Fields should have appropriate clear description about each.
A method documentation should have description about all parameters, exceptions thrown and return value.
Documentation comments can be of two kinds:
Single line documentation comment: /** COMMENT */
Multi-line documentation comment:
Example:
One variable is declared per line.
Variables are declared at the beginning of the code block. For example,
No space between a method name and the parenthesis "(" starting its parameter list
Open brace "{" appears at the end of the same line as the declaration statement
Closing brace "}" starts a line by itself indented to match its corresponding opening statement,
Except when it is a null statement the "}" should appear immediately after the "{"
An empty line is there in between the method declarations.
A method should follow the "Single Responsibility" principle that will perform only one task. If it seems to do multiple sub-tasks, each sub-tasks should be created as a new method and then be invoked in that method.
If there is a logic even of a single line getting repeated in more than one place, it should be made as a separate method.
A method line count should not exceed 80 lines. If required break the blocks of code in the method to separate methods.
Methods are separated by a blank line.
Never re-assign a parameter value. This may lead to unpredictable bugs.
Don't use too many number of parameters. Keep the maximum number of method parameters to 5 for simplicity.
Prefer method parameter type to be more generic such as a Super Interface or Super Class. For example, List
instead of ArrayList
.
Never return null for an Array return type. Return an empty array of 0 like return new String[0]
.
Never return null for a Set/List/Map collection return types. Return corresponding empty values such as Collections.emptySet()
, Collections.emptyList()
or Collections.emptyMap()
;
Prefer method return type to be more generic such as a Super Interface or Super Class. For example, List
instead of ArrayList
.
Avoid having multiple return statements in a method. Prefer to provide a single point of exit to a method. For example:
Use Optional
return type to avoid null checking. When there is possible to return null
for a method, return Optional.empty()
.
Use OptionalInt
, OptionalLong
return types for a method when there is an unknown value of Integer/Long to be returned like -1
;
Each line should contain at most one statement. Example:
Compound statements are statements that contain lists of statements enclosed in braces
The enclosed statements should be indented one more level than the compound statement.
The opening brace should be at the end of the line that begins the compound statement; the closing brace should begin a line and be indented to the beginning of the compound statement.
Braces are used around all statements, even single statements, when they are part of a control structure, such as an if-else or for statement. This makes it easier to add statements without accidentally introducing bugs due to forgetting to add braces. For example:
A return statement with a value should not use parentheses unless they make the return value more obvious in some way. Example:
Refer to Method return statement for more information.
Always curly braces are used in the if-else statements. Even though there is a single statement below the if-else statement, curly braces is used. For example,
If there is a Boolean value to be returned in a method or assigned to a variable based on an if-else condition, the condition itself should be returned or assigned instead of true
or false
for the condition. For example,
Prefer "switch" statement when possible over having multiple “if-else if-else if-else” statements.
If any binary operator is used before "?" in the ternary operator, then parentheses is used.
Prefer if-else statement over conditional expressions if it gets complex with the condition or the statements.
Avoid using nested conditional expressions for better readability.
A switch statement should have the following form:
Every time a case falls through (doesn't include a break statement), add a comment where the break statement would normally be. This is shown in the preceding code example with the /* falls through */
comment.
Every switch statement should include a default
case. The break in the default case is redundant, but it prevents a fall-through error if later another case is added.
Only for the following situations 2 blank lines are used,
Between 2 different sections of the source file
If you have more than one class or interface, use 2 blank lines
Only for the following situations, 1 blank line is used,
Between method declarations.
Between the variable declarations and the method code.
Before the block comment or line comment.
To provide a better readability in between logical blocks, an empty line are used, wherever applicable.
Under the following circumstances, blank space are used,
When a keyword followed by parenthesis, a blank space should be given after the keyword. For example,
In the argument list, the parameters are given a space after comma.
All the package name in MOSIP application starts with io.mosip. Refer to Classes/Interfaces in MOSIP section for various kinds or classes and the package names under which they should be kept.
The names given to classes are always nouns and in camel case. Refer to Classes/Interfaces in MOSIP section for various kinds or classes and their names.
The names given to interface is always noun and in camel case. Refer to Classes/Interfaces in MOSIP section for various kinds or interfaces and their names.
The method names are always verbs and in camel case. For example
The variable names are short and meaningful. Any new observer can understand the meaning of the variable. No single letter variable names are used. Camel case is used when declaring variables.
The constants are given the name in capital letters. The words in the names are separated by underscore.
String literals should not be used in the code directly. Declare them as constant in the class.
Magic Numbers should not be used in code, they should be declared as constants.
Create XyzConstants class to group related and reused constants within a module/feature.
The instance variables should not be made public unless you have a specific reason.
Provide the most restrictive access to the fields, methods and Inner Classes such as private
, default or protected
. Avoid giving public
access to them unless it is really required. Avoid using public non-final fields in a Class.
Always use the class name to call the static method. For example,
Numerical values should not be used in the code directly. Declare them and use it in the code. For example,
Avoid multiple assignments in the same line. For example,
Prefer primitive type variables over boxed types wherever possible. For example, prefer int
, boolean
and long
over their Boxed counterparts such as Integer
, Boolean
and Long
.
Prefer variable type to be more generic such as a Super Interface or Super Class. For example, List
instead of ArrayList
.
Use Optional
return type in a method to avoid null checking. When there is possible to return null
for a method, return Optional.empty()
.
Use OptionalInt
or OptionalLong
return type in a method when there is an unknown value of Integer/Long to be returned like -1
;
Avoid getting value from Optional
using Optional.get()
without checking for Optional.isPresent()
condition, otherwise use Optional.orElse()
.
Use primitive optional classes such as OptionalInt
or OptionalLong
over Optional<Integer>
or Optional<Long>
.
Prefer Method Reference over a Lambda Expression
Keep Lambda Expressions Short and Self-explanatory so that it is easy to understand. . Provide clear understandable name to the parameters in Lambda Expressions.
Always use parameter type inference. For example,
Do not use the parenthesis for a single parameter lambda expression.
Use “Effectively Final” Variables in Lambda Expressions. It is not required to mark every target variable as final.
Avoid mutating Object Variables in Lambda Expression.
Avoid using the block lambdas wherever an expression lambda are used. For example:
Prefer Standard Functional Interfaces over creating a similar one unless it is really required. Use Standard Functional interfaces, which are gathered in the java.util.function
package, satisfy most developers' needs in providing target types for lambda expressions and method references.
On a new Functional Interface declaration always use @FunctionalInterface
annotation. This is not only for the documentation purpose but also to avoid accidentally breaking the Functional Interface behavior.
Instantiate Functional Interfaces with Lambda Expressions instead of creating anonymous inner class instances for that.
Whenever calling the functional interface, place them at last in the parameter list.
For example:
Prefer Streams over for loops. Streams are more readable and functional than the "for" loops, as they support operations such as map, flatMap, filter, reduce and collect.
Exception handling in the streams are carefully handled.
Avoid mutating objects within Collection.forEach()
or Stream.forEach()
, use Stream.collect()
instead. For example use Stream.collect(Collectors.toList())
instead of mutating a list for collecting elements to a list.
Use parallel streams where ever possible to boost performance, whenever it does not involve sort/order/limit intermediate operations.
MOSIP applications should never allow to exit abruptly because of a critical error. Even if there is a critical error there should be a graceful exit with proper information about the error.
Each feature should be defining their own Checked and Unchecked Exceptions by extending io.mosip.kernel.core.exception.BaseCheckedException
and io.mosip.kernel.core.exception.BaseUncheckedException
.
The Checked and Unchecked exceptions should be used appropriately as needed. Make sure which one to use when based on the exception handling requirement.
Throw specific exceptions in a method, rather than generic exceptions. For example,
The exceptions are documented clearly. For example,
Always prefer to use try-with-resource block when applicable like instantiating a Input or Output stream/reader or Connection, which are AutoCloseable.
The following example uses a try-with-resources statement to automatically close a java.sql.Statement
object:
A try-with-resources statement can have catch and finally blocks just like an ordinary try statement.
Always catch specific exceptions over a more generic exceptions like Exception/Throwable class. For example,
Never leave a catch block empty. Either handle the exception, or say proper reason for doing nothing in it.
Use multi-catch blocks wherever possible to club common handling of multiple exceptions.
Always log exceptions to file in a catch block for debugging purpose.
Error and Throwable are never caught in MOSIP.
Any service module should handle their exceptions in a common place such as a common Exception Handler which can be annotated with @ControllerAdvice
or @RestControllerAdvice
Any service in MOSIP should never return error code other than 200. One or more errors to be reported in the response be returned as an array as part of "errors"
attribute.
Logs are classified and logged accordingly. Following are the various log levels used in the MOSIP application.
TRACE
DEBUG
INFO
WARN
ERROR
The log levels are configured according to the environment such as Development, Testing, UAT or Production. For example, the log levels in production is from WARN. Whereas in Development and Testing environment, it is from TRACE.
MOSIP's log component from the core-kernel is used to log entries. The log module in the core-kernel is used to log all the log entries.
First create a logger utility class under the core module of the feature like below:
To log any information/error/debug in a class,
create a logger instance in that class as below: private static Logger mosipLogger = XYZLogger.getLogger(MyClass.class);
In appropriate places invoke the appropriate log method of mosipLogger such as error
, debug
or info
with appropriate parameters passed to it.
Every log entry contains the following format,
For example,
Never log any sensitive information such as user credentials, individual identity information to the log, mask the data if required before logging.
Care should be taken, not to log any sensitive information is logged. Modules leads to review the code to ensure that no sensitive information is logged.
Any service in MOSIP should invoke Kernel's AuditManager REST Service for audit logging of the status of the services such as
Success
Failure
Exception occurred - the error codes and error messages.
Define the appropriate Audit Modules and Audit Events for any feature and use pass them appropriately in the Audit Parameters while invoking the Audit REST service.
Make sure to invoke the Audit REST service Asynchronously to prevent any time lagging in response because of the Audit REST service call.
Apache Commons is used to handle the common utilities like StringUtil, FileUtil, Hashcode, toString, null check etc., are
In case if Apache Commons doesn't have the necessary utilities, the util project from mosip-core is used.
Following are the miscellaneous practices which are followed in MOSIP.
Parenthesis are used in the code liberally to improve the readability and for precedence clarity.
Never type cast a variable without doing instanceof
checking.
Avoid unnecessary type casting when the type of the value/expression is already assigned to a correct variable type/return type.
Avoid using Generic classes without Parameter types. For example:
Use diamond operator while constructing Generic objects. For example:
While chaining multiple method calls, keep one method call per line for better clarity and easy debugging of any issue (especially to get line number in exception stack trace where exactly is the error/exception occurs). For example:
Special comments are used to give a hint for further development. Following are the special comments used in the MOSIP project,
TODO
FIXME
NOTE
OPTIMIZE
It should be made sure to track the above comments, perform action accordingly, and remove them when they become irrelevant.