У меня есть простой интерфейс репозитория с операциями CRUD (возможно, плохая идея передавать неявный сеанс в качестве параметра в общем признаке):
trait Repository[Entity, PK] {
def find(pk: PK)(implicit session: DBSession): OptionT[IO, Entity]
def insert(e: Entity)(implicit session: DBSession): IO[Entity]
def update(e: Entity)(implicit session: DBSession): IO[Entity]
def delete(pk: PK)(implicit session: DBSession): IO[Int]
def findAll()(implicit session: DBSession): IO[List[Entity]]
}
И я хочу использовать его следующим образом:
for {
_ <- repository.insert(???)
_ <- repository.delete(???)
v <- repository.find(???).value
_ <- someFunctionReliesOnReturnedValue(v)
} yield (???)
Кроме того, я хочу остановить выполнение, если v равно None, и откатить транзакцию, если есть какая-либо ошибка (я использую scalikejdbc).Итак, как мне кажется, я должен сделать это на своем уровне обслуживания следующим образом (+ обернуть его в Try или что-то вроде этого, чтобы создать бизнес-исключение):
def logic(???) = {
DB localTx {
implicit session => {
(for {
_ <- repository.insert(???)
_ <- repository.delete(???)
v <- repository.find(???).value
_ <- someFunctionReliesOnReturnedValue(v)
} yield (???)).unsafeRunSync() // to rollback transaction if there is any error
}
}
}
Проблема здесь: someFunctionReliesOnReturnedValue(v)
,Это может быть произвольная функция, которая принимает Entity
, а не Option[Entity]
.Как я могу преобразовать результат OptionT[IO, Entity]
в IO[Entity]
и сохранить семантику Option[]
?Это правильный подход или я где-то ошибся?
import java.nio.file.{Files, Paths}
import cats.data.OptionT
import cats.effect.IO
import scalikejdbc._
import scala.util.Try
case class Entity(id: Long, value: String)
object Entity extends SQLSyntaxSupport[Entity] {
override def tableName: String = "entity"
override def columnNames: Seq[String] = Seq("id", "value")
def apply(g: SyntaxProvider[Entity])(rs: WrappedResultSet): Entity = apply(g.resultName)(rs)
def apply(r: ResultName[Entity])(rs: WrappedResultSet): Entity =
Entity(rs.long(r.id), rs.string(r.value))
}
trait Repository[Entity, PK] {
def find(pk: PK)(implicit session: DBSession): OptionT[IO, Entity]
def insert(e: Entity)(implicit session: DBSession): IO[Entity]
}
class EntityRepository extends Repository[Entity, Long] {
private val alias = Entity.syntax("entity")
override def find(pk: Long)(implicit session: DBSession): OptionT[IO, Entity] = OptionT{
IO{
withSQL {
select(alias.resultAll).from(Entity as alias).where.eq(Entity.column.id, pk)
}.map(Entity(alias.resultName)(_)).single().apply()
}
}
override def insert(e: Entity)(implicit session: DBSession): IO[Entity] = IO{
withSQL {
insertInto(Entity).namedValues(
Entity.column.id -> e.id,
Entity.column.value -> e.value,
)
}.update().apply()
e
}
}
object EntityRepository {
def apply(): EntityRepository = new EntityRepository()
}
object Util {
def createFile(value: String): IO[Unit] = IO(Files.createDirectory(Paths.get("path", value)))
}
class Service {
val repository = EntityRepository()
def logic(): Either[Throwable, Unit] = Try {
DB localTx {
implicit session => {
val result: IO[Unit] = for {
_ <- repository.insert(Entity(1, "1"))
_ <- repository.insert(Entity(2, "2"))
e <- repository.find(3)
_ <- Util.createFile(e.value) // error
//after this step there is possible more steps (another insert or find)
} yield ()
result.unsafeRunSync()
}
}
}.toEither
}
object Test extends App {
ConnectionPool.singleton("jdbc:postgresql://localhost:5433/postgres", "postgres", "")
val service = new Service()
service.logic()
}
Таблица:
create table entity (id numeric(38), value varchar(255));
И я получил ошибку компиляции:
Ошибка: (69, 13) несоответствие типов;найдено: cats.effect.IO [Блок] требуется: cats.data.OptionT [cats.effect.IO ,?] _ <- Util.createFile (e.value) </p>