Cats Tutorial

This tutorial demonstrates using Cats Effect within a Spring Boot application.

The application retrieves a message from an H2 database using doobie and returns it in an HTTP response.

Tutorial Source

Maven dependency

Add Cats Effect and doobie support alongside the Spring Boot starter:

<dependency>
    <groupId>net.officefloor.scala</groupId>
    <artifactId>officescala_cats</artifactId>
</dependency>
<dependency>
    <groupId>org.tpolecat</groupId>
    <artifactId>doobie-h2_${scala-series}</artifactId>
</dependency>

Application class

The application registers DefaultScalaModule for Jackson serialisation and creates a doobie Transactor as a Spring bean using the auto-configured H2 DataSource:

@SpringBootApplication
class Application {

  @Bean
  def scalaJacksonModule(): JacksonModule = DefaultScalaModule

  @Bean
  def transactor(dataSource: DataSource): Transactor[IO] =
    Transactor.fromDataSource[IO](dataSource, ExecutionContext.global)
}

object Application extends App {
  SpringApplication.run(classOf[Application], args: _*)
}

Cats with doobie

The MessageRepository issues a SQL query via doobie and returns it as a Cats IO:

object MessageRepository {

  def findById(id: Int)(implicit xa: Transactor[IO]): IO[Message] =
    sql"SELECT id, content FROM message WHERE id = $id".query[Message].unique.transact(xa)
}

Service logic

The service method receives the @RequestBody and the injected Transactor (from the Spring bean), runs the doobie query, and returns IO[CatsResponse]. OfficeFloor (via officescala_cats) detects the IO return type, runs it, and passes the result as the @Parameter to the send method:

def service(@RequestBody request: CatsRequest)(implicit xa: Transactor[IO]): IO[CatsResponse] =
  for {
    message <- MessageRepository.findById(request.id)
    response = CatsResponse(message.content + " and Cats")
  } yield response
def send(@Parameter message: CatsResponse, response: ObjectResponse[CatsResponse]): Unit =
  response.send(message)

REST endpoint

The YAML chains the two methods:

service:
  class: net.officefloor.tutorial.catshttpserver.ServiceLogic
  method: service
  next: send

send:
  class: net.officefloor.tutorial.catshttpserver.ServiceLogic
  method: send

Request and Response

case class CatsRequest(id: Int)
case class CatsResponse(message: String)

Database

Spring Boot auto-initialises the embedded H2 database from schema.sql and data.sql on the classpath:

DROP TABLE IF EXISTS message;
CREATE TABLE message (id INT PRIMARY KEY, content VARCHAR(50));
INSERT INTO message (id, content) VALUES (1, 'Hi via doobie');

Testing

@SpringBootTest
@AutoConfigureMockMvc
class CatsHttpServerTest {

  @Autowired var mvc: MockMvc = _
  @Autowired var mapper: ObjectMapper = _
  @Autowired var transactor: Transactor[IO] = _

  @Test
  def repositoryTest(): Unit = {
    implicit val runtime: IORuntime = IORuntime.global
    implicit val xa: Transactor[IO] = transactor
    val message = MessageRepository.findById(1).unsafeRunSync()
    assertEquals("Hi via doobie", message.content)
  }

  @Test
  def httpServerTest(): Unit = {
    mvc.perform(post("/")
        .contentType(MediaType.APPLICATION_JSON)
        .content(mapper.writeValueAsString(CatsRequest(1)))
        .accept(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk)
      .andExpect(content().json(mapper.writeValueAsString(CatsResponse("Hi via doobie and Cats"))))
  }
}

Next

The next tutorial covers ZIO.