Features¶
This document describes the features which hook into Micro. Micro acts as a “service loader”. Each individual feature
provides new functionality to Micro. Many features of Micro are loaded by default. You can install additional features
in the initializeServer
function of your Service
(Main.kt
):
micro.install(AuthenticatorFeature)
micro.install(BackgroundScopeFeature)
BackgroundScopeFeature¶
Default: Yes
Exported services:
Micro.backgroundScope: BackgroundScope
Micro.ioDispatcher: CoroutineDispatcher
Provides a BackgroundScope
. This is a coroutine scope which provides a bigger thread pool, more suitable for the
occasional blocking task. This includes start and shutdown hooks for UCloud.
ClientFeature¶
Default: Yes
Exported services:
Micro.client: RpcClient
Provides an RpcClient
to the micro-services. This client will be used for all RPCs to other services.
Example: Configuring the client
rpc:
client:
host:
host: dev.cloud.sdu.dk
scheme: https
port: 443
http: true # Is HTTP enabled, default = true
websockets: true # Is WebSockets enabled, default = true
ConfigurationFeature¶
Default: Yes
Exported services:
Micro.configuration: ServerConfiguration
Command line flags:
--config-dir <DIRECTORY>
--config <FILE>
Provides a configuration mechiansm for micro-services to use. The configuration feature will read directories or files
which are formatted as either YAML
or JSON
.
In addition to the directories and files provided via the command line flags, this feature will read the following directories:
~/sducloud
(only if development mode is active)
The files which are read are merged into one big document. Micro-services can read parts of this document using the following methods:
fun ServerConfiguration.requestChunk<T>(node: String): T
fun ServerConfiguration.requestChunkAt<T>(vararg path: String): T
fun ServerConfiguration.requestChunkOrNull<T>(node: String): T?
fun ServerConfiguration.requestChunkAtOrNull<T>(vararg path: String): T?
Example: Reading a configuration file and passing it to Server.kt
override fun initializeServer(micro: Micro): CommonServer {
micro.install(AuthenticatorFeature)
micro.install(BackgroundScopeFeature)
val folder = micro.configuration.requestChunkAtOrNull("ceph") ?: CephConfiguration()
val config = micro.configuration.requestChunkAtOrNull("storage") ?: StorageConfiguration()
return Server(config, folder, micro)
}
DatabaseConfigurationFeature¶
Default: Yes
Exported services:
Micro.jdbcUrl: String
Micro.databaseConfig: DatabaseConfig
Provides and reads database configuration. The database configuration is read using the ConfigurationFeature
at
"database"
. The configuration uses the following format:
data class Config(
val profile: Profile = Profile.PERSISTENT_POSTGRES,
// Will automatically attempt to use the first valid hostname from ["postgres", "localhost"]
val hostname: String? = null,
val credentials: Credentials? = null,
// Defaults to "postgres"
val database: String? = null,
// Defaults to 5432
val port: Int? = null,
)
enum class Profile {
PERSISTENT_POSTGRES
}
data class Credentials(val username: String, val password: String)
Example: Creating a database connection
val db = AsyncDBSessionFactory(micro)
DeinitFeature¶
Default: Yes
Exported services: None
Provides a feature to run shutdown handlers.
Example: Run a shutdown handler
micro.feature(DeinitFeature).addHandler {
// Run my code
}
DevelopmentOverrides¶
Default: Yes
Exported services:
Micro.developmentModeEnabled: Boolean
Command line arguments:
--dev
Provides a flag for development mode. Development mode can be enabled by passing the --dev
command line flag.
Provides a way to hardcode the location of certain services. These overrides will change both the port that the server
runs on and it will be used by the client to use the correct service.
Note: This system has for the most part been superseded by the launcher
module.
Example: Providing service overrides for some services
development:
serviceDiscovery:
avatar: localhost:4201
project: localhost:4202
ElasticFeature¶
Default: No
Exported services:
Micro.elasticHighLevelClient: RestHighLevelClient
Micro.elasticLowLevelClient: RestClient
Provides a standardized way to retrieve a elasticsearch client.
Example: Configuration for ElasticSearch
elk:
elasticsearch:
hostname: host
port: 9200
credentials:
username: usernamegoeshere
password: passwordgoeshere
FlywayFeature¶
Default: Yes
Exported services: None
Command line arguments
--run-script migrate-db
Provides a script handler to run database migrations, powered by Flyway. Migrations are stored
in the classpath at db/migration
and are simple SQL scripts. The migration scripts must follow the following
convention: V${index}__${scriptName}.sql
. index
is 1-indexed and must be sequential.
Example: A simple migration script
-- Must be stored in example-service/src/main/resources/db/migration/V1__Initial.sql
create table foobar(
a int primary key,
b int
);
HealthCheckFeature¶
Default: Yes
Exported services: None
Provides a health-check endpoint at GET /status
. This endpoint will return 200 OK
if all the internal services
provided by Micro
are working as intended.
The following services are currently checked:
Redis
ElasticSearch
Ktor (webserver)
KtorServerProviderFeature and ServerFeature¶
Default: Yes
Exported services:
Micro.serverProvider: HttpServerProvider
Micro.server: RpcServer
Provides a webserver used by both the HTTP and WebSocket backends. As the name implies, the web server is provided by Ktor.
The ktor engine to use is provided by KtorServerProviderFeature
while ServerFeature
uses this server to initialize
and start the server. This will also install middleware to provide additional features
(e.g. auditing).
LogFeature¶
Default: Yes
Exported services: None
Command line arguments:
--dev
(turns on global debug logs)--debug
(turns on global debug logs but without development mode)
Provides a default configuration for the logging framework used in UCloud. The default configuration will check if the
server is running in development mode. If the server is in development mode then the default log level will be DEBUG
otherwise INFO
will be used. Logs are written using Log4j 2 and are written
to stdout
.
The logging levels can be changed programmatically, see example below.
Example: Change log levels programmatically
micro.feature(LogFeature).configureLevels(
// Enables trace level debugging for the dk.sdu.cloud.avatar package
mapOf(
"dk.sdu.cloud.avatar" to Level.TRACE
)
)
Example: Obtaining a logging instance
class MyService {
// Service code goes here
fun myServiceFunction() {
log.info("Performing some work")
}
companion object : Loggable {
override val log = logger()
}
}
Redis Feature¶
Default: Yes
Exported services:
Micro.redisConnectionManager: RedisConnectionManager
Micro.eventStreamService: RedisStreamService
Provides configuration needed for event streams and distributed locks.
Example: Configuration for Redis
redis:
hostname: localhost # defaults to first valid hostname in ["redis", "localhost"]
port: 6379 # defaults to 6379
Example: Broadcasting stream
val broadcastingStream = RedisBroadcastingStream(micro.redisConnectionManager)
broadcastingStream.broadcast(MyMessage(42), MyStreams.stream)
Example: Producing a message
val eventProducer = micro.eventStreamService.createProducer(ProjectEvents.events)
eventProducer.produce(ProjectEvent.Created("foobar"))
Example: Creating a distributed lock
val distributedLocks = DistributedLockBestEffortFactory(micro)
val lock = distributedLockFactory.create("metadata-recovery-lock", duration = 60_000)
while (true) {
val didAcquire = lock.acquire()
if (didAcquire) {
processing@while (true) {
// Do work here
if (!lock.renew(60_000)) {
log.info("We lost the lock!")
break@processing
}
}
}
// Introduce randomness to make it more likely that clients don't try simultaneously
delay(15000 + Random.nextLong(5000))
}
ScriptFeature¶
Default: Yes
Exported services:
Micro.scriptsToRun: List<String>
fun Micro.optionallyAddScriptHandler(scriptName: String, handler: ScriptHandler)
fun Micro.runScriptHandler()
Command line arguments:
--run-script <SCRIPTNAME>
Provides a way to run ad-hoc scripts at the Micro feature level. This is, for example, utilized by the
FlywayFeature. This feature is mostly intended to be used by other Micro features. The scripts are run
before before the server is started but after initializeServer
.
Example: Add a script handler and terminate UCloud
micro.optionallyAddScriptHandler("my-script") {
// Code goes here
ScriptHandlerResult.STOP
}
Example: Add a script handler and continue launching UCloud
micro.optionallyAddScriptHandler("my-script") {
// Code goes here
ScriptHandlerResult.CONTINUE
}
ServiceDiscoveryOverrides¶
Default: Yes
Exported services: None
Provides a way to provide service overrides programmatically. This is similar to the code used in DevelopmentOverrides.
ServiceInstanceFeature¶
Default: Yes
Exported services:
Micro.serviceInstance: ServiceInstance
Provides a ServiceInstance
to the micro-service. This contains information valuable for auditing.
data class ServiceInstance(
val definition: ServiceDefinition,
val hostname: String,
val port: Int,
val ipAddress: String? = null
)
data class ServiceDefinition(
val name: String,
val version: String
)
TokenValidationFeature¶
Default: Yes
Exported services:
Micro.tokenValidation: TokenValidation<Any>
Provides a way for services to validate JSON Web Tokens (JWTs). The feature supports both public
certificate signing (RSA256
) and shared secret signing (HMAC512
). The details are described
here.
Example: Shared secret configuration
tokenValidation:
jwt:
sharedSecret: notverysecret
AuthenticatorFeature (formerly RefreshingJWTCloudFeature)¶
Default: No
Exported services:
Micro.authenticator: RefreshingJWTAuthenticator
Provides a way to use automatically renew accessToken
s from a refreshToken
. The RefreshingJWTAuthenticator
can
authenticate individual calls as well as a complete client (returning an AuthenticatedClient
). It will read a
refreshToken
from configuration for the service itself. This will be used to make calls on behalf of the server.
It is also possible to create a RefreshingJWTAuthenticator
with a different refreshToken
(see below). This is useful
if the service has extended a user’s token for some purpose. User tokens are extended when a service needs to act
on the user’s behalf on a later point in time.
Example: Configuration file
refreshToken: my-refresh-token-goes-here
Example: Creating an authenticated client
val serviceClientHttp = micro.authenticator.authenticateClient(OutgoingHttpCall)
val serviceClientWebSockets = micro.authenticator.authenticateClient(OutgoingWSCall)
Example: Creating an AuthenticatedClient
from an extended refreshToken
val clientFromToken = RefreshingJWTAuthenticator(
micro.client,
refreshToken,
micro.tokenValidation as TokenValidationJWT
).authenticateClient(OutgoingHttpCall)