Skip to content

COCO is framework for 'async & non-blocking' web application.

Notifications You must be signed in to change notification settings

projects-coco/coco

Repository files navigation

COCO

COCO is designed to facilitate the use of Spring WebFlux, Batch, WebClient, Redis, Flyway, TestContainer and jOOQ.

Examples (Kotlin Code)

project start

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()

Webflux handler

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 Application
app:
  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 SpringBatch by @CocoBatchApplication

@CocoBatchApplication
class Application

With COCO, you have to do is only writing batch logic.
COCO provides simple function for batch tasks such as 'intervalNMinutesSchedule(N)'(running batches every N minute) so that you can use batch easily.

@Configuration
class BatchExample {
    @Component
    class Job(
        private val cocoCoroutineScopeProvider: CocoCoroutineScopeProvider,
        private val batchService: BatchService,
    ) : org.quartz.Job {
        override fun execute(jobExecutionContext: JobExecutionContext?) {
            cocoCoroutineScopeProvider.provide().launch {
                batchService.batch().bindOrNothing()
            }
        }
    }

    @Bean
    fun batchJobDetail(): JobDetail =
        JobBuilder
            .newJob(Job::class.java)
            .withIdentity(Job::class.qualifiedName)
            .storeDurably()
            .build()

    @Bean
    fun batchJobTrigger(): Trigger =
        TriggerBuilder
            .newTrigger()
            .forJob(Job::class.qualifiedName)
            .startNow()
            .withSchedule(intervalNMinutesSchedule(1))
            .build()
}

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",
    )
)

Event Notification Bus

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()
    }
}

Pagination

You can use pagination by only using 'PageRequest' in your Controller

    @GetMapping()
suspend fun page(
    @RequestBody request: Request,
    page: PageRequest,
) {
    ...
}

About

COCO is framework for 'async & non-blocking' web application.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages