From 882e03f5a36405447893eb410cb54288f6e35e51 Mon Sep 17 00:00:00 2001 From: "al.e.savchenko" Date: Wed, 13 Nov 2024 15:39:26 +0300 Subject: [PATCH] Rewrite migration with js --- backend/build.sbt | 6 - .../src/main/resources/application.conf | 7 - .../migration/src/main/resources/logback.xml | 21 --- .../ru/tinkoff/tcb/GrpcStubV4Migration.scala | 48 ------- .../tcb/configuration/MongoConfig.scala | 15 -- .../ru/tinkoff/tcb/dao/GrpcStubV2DAO.scala | 30 ---- .../tcb/service/MigrationService.scala | 134 ------------------ .../GrpcMethodDescriptionProxyUrlPatch.scala | 19 --- .../GrpcStubMethodDescriptionIdPatch.scala | 20 --- .../tcb/service/model/GrpcStubV2.scala | 44 ------ backend/migrations/grpc-migration.js | 61 ++++++++ docker-compose.yml | 8 ++ grpc-configuration.md | 31 ++-- 13 files changed, 89 insertions(+), 355 deletions(-) delete mode 100644 backend/migration/src/main/resources/application.conf delete mode 100644 backend/migration/src/main/resources/logback.xml delete mode 100644 backend/migration/src/main/scala/ru/tinkoff/tcb/GrpcStubV4Migration.scala delete mode 100644 backend/migration/src/main/scala/ru/tinkoff/tcb/configuration/MongoConfig.scala delete mode 100644 backend/migration/src/main/scala/ru/tinkoff/tcb/dao/GrpcStubV2DAO.scala delete mode 100644 backend/migration/src/main/scala/ru/tinkoff/tcb/service/MigrationService.scala delete mode 100644 backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcMethodDescriptionProxyUrlPatch.scala delete mode 100644 backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcStubMethodDescriptionIdPatch.scala delete mode 100644 backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcStubV2.scala create mode 100644 backend/migrations/grpc-migration.js diff --git a/backend/build.sbt b/backend/build.sbt index f06cc709..d9c6ee98 100644 --- a/backend/build.sbt +++ b/backend/build.sbt @@ -210,15 +210,9 @@ val examples = (project in file("examples")) ) ) -val migration = (project in file("migration")) - .settings(Settings.common) - .aggregate(mockingbird) - .dependsOn(mockingbird) - val root = (project in file(".")) .disablePlugins(ContribWarts) .aggregate( - migration, utils, circeUtils, dataAccess, diff --git a/backend/migration/src/main/resources/application.conf b/backend/migration/src/main/resources/application.conf deleted file mode 100644 index 2275a479..00000000 --- a/backend/migration/src/main/resources/application.conf +++ /dev/null @@ -1,7 +0,0 @@ -ru.tinkoff.tcb { - db { - mongo { - uri = "mongodb://localhost/mockingbird" - } - } -} diff --git a/backend/migration/src/main/resources/logback.xml b/backend/migration/src/main/resources/logback.xml deleted file mode 100644 index 836b68ff..00000000 --- a/backend/migration/src/main/resources/logback.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - UTF-8 - - - - - - - - - - - - - diff --git a/backend/migration/src/main/scala/ru/tinkoff/tcb/GrpcStubV4Migration.scala b/backend/migration/src/main/scala/ru/tinkoff/tcb/GrpcStubV4Migration.scala deleted file mode 100644 index 7d1e934c..00000000 --- a/backend/migration/src/main/scala/ru/tinkoff/tcb/GrpcStubV4Migration.scala +++ /dev/null @@ -1,48 +0,0 @@ -package ru.tinkoff.tcb - -import com.mongodb.ConnectionString -import org.mongodb.scala.MongoClient -import org.mongodb.scala.MongoCollection -import org.mongodb.scala.MongoDatabase -import org.mongodb.scala.bson.BsonDocument - -import ru.tinkoff.tcb.configuration.MongoCollections -import ru.tinkoff.tcb.configuration.MongoConfig -import ru.tinkoff.tcb.dao.GrpcStubV2DAOImpl -import ru.tinkoff.tcb.mockingbird.dal.GrpcMethodDescriptionDAOImpl -import ru.tinkoff.tcb.mockingbird.dal.GrpcStubDAOImpl -import ru.tinkoff.tcb.service.MigrationService -import ru.tinkoff.tcb.service.MigrationServiceImpl - -object GrpcStubV4Migration extends ZIOAppDefault { - - private val mongoLayer = ZLayer { - for { - config <- ZIO.service[MongoConfig] - } yield MongoClient(config.uri).getDatabase(new ConnectionString(config.uri).getDatabase) - } - - private def collection( - name: MongoCollections => String - ): URLayer[MongoConfig & MongoDatabase, MongoCollection[BsonDocument]] = - ZLayer { - for { - mongo <- ZIO.service[MongoDatabase] - config <- ZIO.service[MongoConfig] - } yield mongo.getCollection(name(config.collections)) - } - - def run = { - ZIO.logInfo("Script started") *> - ZIO.serviceWithZIO[MigrationService](_.migrateGrpcStubCollection) <* - ZIO.logInfo("Script finished") - } - .provide( - MongoConfig.layer, - mongoLayer, - collection(_.grpcStub) >>> GrpcStubDAOImpl.live, - collection(_.grpcStub) >>> GrpcStubV2DAOImpl.live, - collection(_.grpcMethodDescription) >>> GrpcMethodDescriptionDAOImpl.live, - MigrationServiceImpl.layer, - ) -} diff --git a/backend/migration/src/main/scala/ru/tinkoff/tcb/configuration/MongoConfig.scala b/backend/migration/src/main/scala/ru/tinkoff/tcb/configuration/MongoConfig.scala deleted file mode 100644 index f392e2bb..00000000 --- a/backend/migration/src/main/scala/ru/tinkoff/tcb/configuration/MongoConfig.scala +++ /dev/null @@ -1,15 +0,0 @@ -package ru.tinkoff.tcb.configuration - -import com.typesafe.config.ConfigFactory -import net.ceedubs.ficus.Ficus.* -import net.ceedubs.ficus.readers.ArbitraryTypeReader.* - -final case class MongoCollections(grpcStub: String, grpcMethodDescription: String) - -final case class MongoConfig(uri: String, collections: MongoCollections) - -object MongoConfig { - val layer: ULayer[MongoConfig] = ZLayer.succeed { - ConfigFactory.load().getConfig("ru.tinkoff.tcb.db.mongo").as[MongoConfig] - } -} diff --git a/backend/migration/src/main/scala/ru/tinkoff/tcb/dao/GrpcStubV2DAO.scala b/backend/migration/src/main/scala/ru/tinkoff/tcb/dao/GrpcStubV2DAO.scala deleted file mode 100644 index 7248ec40..00000000 --- a/backend/migration/src/main/scala/ru/tinkoff/tcb/dao/GrpcStubV2DAO.scala +++ /dev/null @@ -1,30 +0,0 @@ -package ru.tinkoff.tcb.dao - -import scala.annotation.implicitNotFound - -import cats.tagless.autoFunctorK -import org.mongodb.scala.MongoCollection -import org.mongodb.scala.bson.BsonDocument - -import ru.tinkoff.tcb.mongo.DAOBase -import ru.tinkoff.tcb.mongo.MongoDAO -import ru.tinkoff.tcb.service.model.GrpcStubV2 - -@implicitNotFound("Could not find an instance of GrpcStubDAO for ${F}") -@autoFunctorK -trait GrpcStubV2DAO[F[_]] extends MongoDAO[F, GrpcStubV2] - -object GrpcStubV2DAO - -class GrpcStubV2DAOImpl(collection: MongoCollection[BsonDocument]) - extends DAOBase[GrpcStubV2](collection) - with GrpcStubV2DAO[Task] - -object GrpcStubV2DAOImpl { - val live = ZLayer { - for { - mc <- ZIO.service[MongoCollection[BsonDocument]] - sd = new GrpcStubV2DAOImpl(mc) - } yield sd.asInstanceOf[GrpcStubV2DAO[Task]] - } -} diff --git a/backend/migration/src/main/scala/ru/tinkoff/tcb/service/MigrationService.scala b/backend/migration/src/main/scala/ru/tinkoff/tcb/service/MigrationService.scala deleted file mode 100644 index b7afa287..00000000 --- a/backend/migration/src/main/scala/ru/tinkoff/tcb/service/MigrationService.scala +++ /dev/null @@ -1,134 +0,0 @@ -package ru.tinkoff.tcb.service - -import io.scalaland.chimney.dsl.* -import org.mongodb.scala.bson.BsonDocument -import org.mongodb.scala.bson.BsonString -import zio.stream.ZStream - -import ru.tinkoff.tcb.bson.* -import ru.tinkoff.tcb.criteria.* -import ru.tinkoff.tcb.criteria.Typed.* -import ru.tinkoff.tcb.criteria.Untyped.* -import ru.tinkoff.tcb.dao.GrpcStubV2DAO -import ru.tinkoff.tcb.mockingbird.dal.GrpcMethodDescriptionDAO -import ru.tinkoff.tcb.mockingbird.dal.GrpcStubDAO -import ru.tinkoff.tcb.mockingbird.model.GProxyResponse -import ru.tinkoff.tcb.mockingbird.model.GrpcConnectionType -import ru.tinkoff.tcb.mockingbird.model.GrpcMethodDescription -import ru.tinkoff.tcb.mockingbird.model.GrpcStub -import ru.tinkoff.tcb.mockingbird.model.Scope -import ru.tinkoff.tcb.service.model.GrpcMethodDescriptionProxyUrlPatch -import ru.tinkoff.tcb.service.model.GrpcStubMethodDescriptionIdPatch -import ru.tinkoff.tcb.service.model.GrpcStubV2 -import ru.tinkoff.tcb.utils.id.SID - -trait MigrationService { - def migrateGrpcStubCollection: Task[Unit] -} - -final class MigrationServiceImpl( - grpcStubDAO: GrpcStubDAO[Task], - grpcMethodDescriptionDAO: GrpcMethodDescriptionDAO[Task], - grpcStubV2DAO: GrpcStubV2DAO[Task], -) extends MigrationService { - override def migrateGrpcStubCollection: Task[Unit] = - (for { - scope <- ZIO.succeed(Scope.Persistent) - stubAndScope <- getStubAndScope(scope) - stubsMigrated <- ZIO.foreach(stubAndScope) { case (stub, scope) => - processMigration(stub, scope) - .tap(migrated => ZIO.logInfo(s"Migrated $migrated grpc stubs")) - } - _ <- ZIO.logInfo(s"Nothing to migrate").when(stubsMigrated.isEmpty) - } yield ()).catchAllTrace { case (e, stackTrace) => - ZIO.logErrorCause("Error during processing migrateGrpcStubCollection", Cause.fail(e, stackTrace)) - } - - private def getStubAndScope(scope: Scope): Task[Option[(GrpcStubV2, Scope)]] = - for { - nextMethodOpt <- getGrpcStubV2(scope) - nextMethodAndScope <- nextMethodOpt match { - case None => - val nextScope = getNextScope(scope) - getGrpcStubV2(nextScope).map(_.map(_ -> nextScope)) - case Some(nextMethod) => ZIO.some(nextMethod -> scope) - } - } yield nextMethodAndScope - - private def processMigration(grpcStubV2: GrpcStubV2, scope: Scope): Task[Int] = - ZStream - .paginateZIO(grpcStubV2 -> scope) { case (grpcStubV2, scope) => - for { - grpcStubsByMethodName <- grpcStubV2DAO.findChunk( - prop[GrpcStubV2](_.methodName) === grpcStubV2.methodName && - prop[GrpcStubV2](_.scope) === scope, - 0, - Integer.MAX_VALUE - ) - proxyUrl = grpcStubsByMethodName.collectFirst { - case stub if GProxyResponse.prism.getOption(stub.response).isDefined => - (GProxyResponse.prism >> GProxyResponse.endpoint).getOption(stub.response).flatten - }.flatten - methodDescriptionOpt <- grpcMethodDescriptionDAO - .findOne( - prop[GrpcMethodDescription](_.methodName) === grpcStubV2.methodName - ) - methodDescriptionId <- methodDescriptionOpt match { - case Some(methodDescription) if methodDescription.proxyUrl.isEmpty && proxyUrl.isDefined => - grpcMethodDescriptionDAO - .patch( - GrpcMethodDescriptionProxyUrlPatch(methodDescription.id, proxyUrl) - ) - .as(methodDescription.id) - case Some(methodDescription) => ZIO.succeed(methodDescription.id) - case None => - for { - now <- ZIO.clockWith(_.instant) - methodDescription = grpcStubV2 - .into[GrpcMethodDescription] - .withFieldConst(_.id, SID.random[GrpcMethodDescription]) - .withFieldConst(_.description, "") - .withFieldConst(_.connectionType, GrpcConnectionType.Unary) - .withFieldConst(_.proxyUrl, proxyUrl) - .withFieldConst(_.created, now) - .transform - _ <- grpcMethodDescriptionDAO.insert(methodDescription) - } yield methodDescription.id - } - _ <- ZIO.foreachParDiscard(grpcStubsByMethodName) { stub => - unsetField(stub.id) *> - grpcStubDAO.patch( - GrpcStubMethodDescriptionIdPatch(stub.id, methodDescriptionId) - ) - } - nextMethodAndScope <- getStubAndScope(scope) - } yield grpcStubsByMethodName.size -> nextMethodAndScope - } - .runSum - - private def unsetField[T](stubId: SID[GrpcStub]) = - grpcStubV2DAO.update( - where(_._id === stubId), - BsonDocument( - "$unset" -> BsonDocument( - Seq("methodName", "service", "requestSchema", "requestClass", "responseSchema", "responseClass") - .map(_ -> BsonString("")) - ) - ) - ) - - private def getNextScope(scope: Scope): Scope = scope match { - case Scope.Persistent => Scope.Ephemeral - case _ => Scope.Countdown - } - - private def getGrpcStubV2(scope: Scope): Task[Option[GrpcStubV2]] = - grpcStubV2DAO.findOne( - prop[GrpcStubV2](_.methodName).exists && prop[GrpcStubV2](_.scope) === scope - ) -} - -object MigrationServiceImpl { - val layer: URLayer[GrpcStubDAO[Task] & GrpcMethodDescriptionDAO[Task] & GrpcStubV2DAO[Task], MigrationService] = - ZLayer.derive[MigrationServiceImpl] -} diff --git a/backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcMethodDescriptionProxyUrlPatch.scala b/backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcMethodDescriptionProxyUrlPatch.scala deleted file mode 100644 index 014b77b9..00000000 --- a/backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcMethodDescriptionProxyUrlPatch.scala +++ /dev/null @@ -1,19 +0,0 @@ -package ru.tinkoff.tcb.service.model - -import derevo.derive - -import ru.tinkoff.tcb.bson.annotation.BsonKey -import ru.tinkoff.tcb.bson.derivation.bsonEncoder -import ru.tinkoff.tcb.generic.PropSubset -import ru.tinkoff.tcb.mockingbird.model.GrpcMethodDescription -import ru.tinkoff.tcb.utils.id.SID - -@derive(bsonEncoder) -final case class GrpcMethodDescriptionProxyUrlPatch( - @BsonKey("_id") id: SID[GrpcMethodDescription], - proxyUrl: Option[String], -) - -object GrpcMethodDescriptionProxyUrlPatch { - implicitly[PropSubset[GrpcMethodDescriptionProxyUrlPatch, GrpcMethodDescription]] -} diff --git a/backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcStubMethodDescriptionIdPatch.scala b/backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcStubMethodDescriptionIdPatch.scala deleted file mode 100644 index a0d05bf2..00000000 --- a/backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcStubMethodDescriptionIdPatch.scala +++ /dev/null @@ -1,20 +0,0 @@ -package ru.tinkoff.tcb.service.model - -import derevo.derive - -import ru.tinkoff.tcb.bson.annotation.BsonKey -import ru.tinkoff.tcb.bson.derivation.bsonEncoder -import ru.tinkoff.tcb.generic.PropSubset -import ru.tinkoff.tcb.mockingbird.model.GrpcMethodDescription -import ru.tinkoff.tcb.mockingbird.model.GrpcStub -import ru.tinkoff.tcb.utils.id.SID - -@derive(bsonEncoder) -final case class GrpcStubMethodDescriptionIdPatch( - @BsonKey("_id") id: SID[GrpcStub], - methodDescriptionId: SID[GrpcMethodDescription], -) - -object GrpcStubMethodDescriptionIdPatch { - implicitly[PropSubset[GrpcStubMethodDescriptionIdPatch, GrpcStub]] -} diff --git a/backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcStubV2.scala b/backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcStubV2.scala deleted file mode 100644 index e629c955..00000000 --- a/backend/migration/src/main/scala/ru/tinkoff/tcb/service/model/GrpcStubV2.scala +++ /dev/null @@ -1,44 +0,0 @@ -package ru.tinkoff.tcb.service.model - -import java.time.Instant - -import derevo.derive -import eu.timepit.refined.types.numeric.NonNegInt -import eu.timepit.refined.types.string.NonEmptyString -import io.circe.Json - -import ru.tinkoff.tcb.bson.* -import ru.tinkoff.tcb.bson.annotation.BsonKey -import ru.tinkoff.tcb.bson.derivation.bsonDecoder -import ru.tinkoff.tcb.bson.derivation.bsonEncoder -import ru.tinkoff.tcb.circe.bson.* -import ru.tinkoff.tcb.mockingbird.model.GrpcProtoDefinition -import ru.tinkoff.tcb.mockingbird.model.GrpcStub -import ru.tinkoff.tcb.mockingbird.model.GrpcStubResponse -import ru.tinkoff.tcb.mockingbird.model.Scope -import ru.tinkoff.tcb.predicatedsl.Keyword -import ru.tinkoff.tcb.predicatedsl.json.JsonPredicate -import ru.tinkoff.tcb.protocol.bson.* -import ru.tinkoff.tcb.utils.circe.optics.JsonOptic -import ru.tinkoff.tcb.utils.id.SID - -@derive(bsonDecoder, bsonEncoder) -final case class GrpcStubV2( - @BsonKey("_id") id: SID[GrpcStub], - scope: Scope, - created: Instant, - service: NonEmptyString, - times: Option[NonNegInt], - methodName: String, - name: NonEmptyString, - requestSchema: GrpcProtoDefinition, - requestClass: String, - responseSchema: GrpcProtoDefinition, - responseClass: String, - response: GrpcStubResponse, - seed: Option[Json], - state: Option[Map[JsonOptic, Map[Keyword.Json, Json]]], - requestPredicates: JsonPredicate, - persist: Option[Map[JsonOptic, Json]], - labels: Seq[String] -) diff --git a/backend/migrations/grpc-migration.js b/backend/migrations/grpc-migration.js new file mode 100644 index 00000000..04dd94d8 --- /dev/null +++ b/backend/migrations/grpc-migration.js @@ -0,0 +1,61 @@ +var bulk = db.mockingbirdGrpcStubs.initializeOrderedBulkOp(); + +db.mockingbirdGrpcStubs + .find( { "methodName": {$exists: true} } ) + .sort( { "scope": 1 } ) + .forEach(function (doc) { + var existedMethodDescription = db.mockingbirdGrpcMethodDescriptions.findOne({"methodName": doc.methodName}); + + let methodDescriptionId; + if (existedMethodDescription) + methodDescriptionId = existedMethodDescription._id; + else { + var methodDescriptionDoc = { + "_id": UUID().toString().split('"')[1], + "description": "", + "created": new Date(), + "service": doc.service, + "methodName": doc.methodName, + "connectionType": "UNARY", + "requestClass": doc.requestClass, + "requestSchema": doc.requestSchema, + "responseClass": doc.responseClass, + "responseSchema": doc.responseSchema + } + + db.mockingbirdGrpcMethodDescriptions.insertOne( + methodDescriptionDoc + ); + methodDescriptionId = methodDescriptionDoc._id; + } + + if (doc.proxyUrl) + db.mockingbirdGrpcMethodDescriptions.updateOne( + { "_id": methodDescriptionId, "proxyUrl": {$exists: false} }, + { $set: {"proxyUrl": doc.proxyUrl} } + ); + + var setPatch = { + "methodDescriptionId": methodDescriptionId + }; + var unsetPatch = { + "methodName": "", + "service": "", + "requestSchema": "", + "requestClass": "", + "responseSchema": "", + "responseClass": "" + }; + + bulk + .find( { "_id": doc._id } ) + .updateOne( + { + $set: setPatch, + $unset: unsetPatch + } + ); + }); +if (bulk.length > 0) { + bulk.execute(); +} diff --git a/docker-compose.yml b/docker-compose.yml index 43e9f539..d30a9b2d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,14 @@ services: - "27017:27017" networks: - app-tier + migration: + image: mongo + networks: + - app-tier + volumes: + - ./backend/migrations/grpc-migration.js:/migrations/grpc-migration.js + command: mongosh mongo/mockingbird /migrations/grpc-migration.js + mock: image: "ghcr.io/tinkoff/mockingbird:3.10.0-native" ports: diff --git a/grpc-configuration.md b/grpc-configuration.md index fa71f206..dbde05e0 100644 --- a/grpc-configuration.md +++ b/grpc-configuration.md @@ -2,14 +2,16 @@ ## Connection Types -GRPC stubs API has versions: `/v2` and `/v4`. The main difference between them +GRPC stubs API has versions: `/v2` and `/v4`. The main difference between them is the ability to maintain streaming: -- `/v2` supports only UNARY connection + +- `/v2` supports only UNARY connection - `/v4` supports all connections, which are: UNARY, CLIENT_STREAMING, SERVER_STREAMING, BIDI_STREAMING ## Response Modes Response body generation in GRPC stubs can work in the following modes: + * fill - the response is generated as one element from request body * proxy - the response is generated from proxy response * fill_stream - the response is generated as several elements from request body @@ -21,22 +23,29 @@ The `fill_stream` and `repeat` modes in response can be used only for stream res The `no_body` mode in response may be used for state changing. In 'delay' and 'stream_delay' field you can pass a correct FiniteDuration no longer than 30 seconds. -The first one is for delay before hole stub response and the second one is for delay before each element in stream response like `fill_stream` and `repeat`. +The first one is for delay before hole stub response and the second one is for delay before each element in stream +response like `fill_stream` and `repeat`. -For a stream input for each element will be selected a stub. But it is not necessary to create stubs for each one, it is only important that the answer is not empty. -It is also possible to select stubs with different response modes for a connection. For example input stream could be partially proxy and partially filled. +For a stream input for each element will be selected a stub. But it is not necessary to create stubs for each one, it is +only important that the answer is not empty. +It is also possible to select stubs with different response modes for a connection. For example input stream could be +partially proxy and partially filled. ## Method Description -In API v4 a part related to a static information about GRPC method was isolated from the stub into a separate entity - method description. -It contains connection type, method name, request and response schemas. Method description is linked to a method 1:1 by method name. +In API v4 a part related to a static information about GRPC method was isolated from the stub into a separate entity - +method description. +It contains connection type, method name, request and response schemas. Method description is linked to a method 1:1 by +method name. So only one method description could exist for a method. ## Proxy For proxy there is a `proxy` response mode. Proxy url is located in method description. -If proxy url is defined for unary input type connections, the connection to the proxy will be established only if the proxy stub is selected. -But for stream output the connection to the proxy will be established for every request regardless of whether a proxy stub has been selected. +If proxy url is defined for unary input type connections, the connection to the proxy will be established only if the +proxy stub is selected. +But for stream output the connection to the proxy will be established for every request regardless of whether a proxy +stub has been selected. ## Examples @@ -186,7 +195,7 @@ But for stream output the connection to the proxy will be established for every ### How to migrate from stub v2 to stub v4 For newly created stubs v2 will be created unary method description. But existed stubs will fail requests. -To migrate already existing stubs set mongo uri in `application.conf` and run the migration script `GrpcStubV4Migration.scala` in `migration` module. -Method description information will be taken from the random stub with the highest priority of the scope (persistent, ephemeral, countdown). +To migrate already existing stubs up docker-compose service `migration` or just run all the services. +Method description information will be taken from the random stub with the highest priority by the scope (persistent, ephemeral, countdown).