COCO is designed to facilitate the use of Spring WebFlux, WebClient, Redis, Flyway, TestContainer and jOOQ.
You can use COCO by @CocoWebApplication
@CocoWebApplication
class Application@CocoWebApplication imports configuration class for web application (such as JsonConfiguration, WebConfig, FlywayConfiguration ...) and also enables the use of SpringBoot.
@SpringBootApplication
@EnableCocoApplication
@Import(
JsonConfiguration::class,
WebConfig::class,
FlywayConfiguration::class,
WebClientConfiguration::class,
ApiUtilsFactory::class,
EventNotificationBusInjector::class,
DslContextInjector::class,
ReactiveRequestContextInjector::class,
)
annotation class CocoWebApplication()You can handle Effect (which is data structure of Arrow(FP Library)) by using 'handle'
suspend fun <A> handle(
successCode: HttpStatus = HttpStatus.OK,
handler: Effect<ApiError, A>,
): ResponseEntity<A> {
return handler.fold({ throw ApiError.ApiException(it) }, { result = ResponseEntity.status(successCode).body(it) })
}Handler example
@PostMapping("/login")
suspend fun login(
@RequestBody request: LoginRequest,
) = handle {
service
.login(
LoginCommand(
username = request.username,
password = request.password,
),
).bindOrRaise {
when (it) {
LoginError.NotFoundUser -> raiseNotFound("사용자가 존재하지 않습니다")
LoginError.PasswordMismatch -> raiseBadRequest("비밀번호가 일치하지 않습니다")
}
}You can use Flyway simply by only adding YAML configuration.
spring:
flyway:
location: { your sql file location }You can use TestContainer simply by only adding YAML configuration.
test:
db:
username: { test-user }
password: { test-password }
schema: { test-schema }
engine: mariadb
endpoint: localhost
# classpath 하위에 만들 것
flyway-sql-location: db/migration
flyway-schema-history: { flyway_schema_history }You can use redis by @Import(RedisConfig::class)
@CocoWebApplication
@Import(RedisConfig::class)
class Applicationapp:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}You have to do is implements SimpleRedisRepositoryBase
You can customize 'READ' method (e.g retrieveByKey)
class RedisRepositoryImpl(private val redissonReactiveClient: RedissonReactiveClient) :
SimpleRedisRepositoryBase<Entity>() {
override fun generateKey(entity: Entity): String {}
override fun delete(entity: Entity): Effect<Nothing, LocalDateTime> {}
override fun save(entity: Entity): Effect<Nothing, Entity> {}
fun retrieveByKey(key: Key): Effect<NotFound, Entity> {}
}You can use Spring WebClient by ApiUtils
ApiUtils()
.post(API_ENDPOINT)
.headers(mapOf("Authorization" to authToken))
.body(mapOf("grant_type" to "account_credentials"))
.contentType(MediaType.FormUrlEncode)
.call(Response::class)You can use jOOQ by implementing SimpleCrudRepositoryBase.
Implementing SimpleCrudRepositoryBase allows you to use CRUD methods.
abstract class SimpleCrudRepositoryBase<Id : EntityId<*>, Entity : EntityBase<Id>, R : Record>(
protected val table: Table<R>,
protected val toJooq: Entity.() -> R,
protected val toDomain: R.() -> Entity,
private val keyColumn: String = "id",
) : JooqRepositoryBase(), Repository<Id, Entity>
---
UserRepository : SimpleCrudRepositoryBase<User.Id, User, UserRecord>
---
UserRepository().save(user)
UserRepository().retrieve(user.id)
UserRepository().delete(user)You can use 'dynamic query' by implementing SearchRepositoryBase
abstract class SearchRepositoryBase<Id : EntityId<*>, Entity : EntityBase<Id>, R : Record, S : SearchDtoBase>(
table: Table<R>,
toJooq: Entity.() -> R,
toDomain: R.() -> Entity,
val selectConditionBuilder: suspend S.() -> Condition,
) : SearchRepository<Entity, S>, SimpleCrudRepositoryBase<Id, Entity, R>(table, toJooq, toDomain)
---
UserRepository : SearchRepositoryBase<User.Id, User, UserRecord, UserSearchDto>
---
UserRepository().search(
UserSearchDto(
username = "username",
name = "name",
email = "email",
)
)You can use EventNotificationBus, which enables the transfer of event between different modules.
class A {
fun emitEvent() {
EventNotificationBus().emitNotification(MyCustomEvent())
}
}
---
class B {
fun handleEvent() {
EventNotificationBus()
.notifications()
.handleEvent<MyCustomEvent> { event -> customLogic(event) }
.subscribe()
}
}
You can use pagination by only using 'PageRequest' in your Controller
@GetMapping()
suspend fun page(
@RequestBody request: Request,
page: PageRequest,
) {
...
}