Хола, коллеги!
У меня есть список некоторых сущностей. Они могут быть вложены друг в друга. Например:
case class Entity(id: Long, parentId: Long)
val list = List(
Entity(1,0),
Entity(2,1),
Entity(3,2),
Entity(4,1),
Entity(5,0)
)
Мне нужно добавить их в БД. Мой сервис:
val listOfFutures = list map createEntity // createEntity - мой метод,
// который добавляет сущность в БД.
// Возвращает Future[Entity]
Future.sequence( listOfFutures ) map { _ => println("Ok!") }
Проблема в constraints(Как их по-русски назвать?). Получаю ошибку:
Create failed. No such parent 1 entity exists.
Я думаю, что проблема в том, что метод createEntity выполняется параллельно. Как заставить этот код выполняться последовательно, сущность за сущностью?
Ответ
Future считается монадой, так как у неё есть последовательный flatMap
list.foldLeft(Future.successful(()))(( prevF, entity) =>
prevF
.flatMap( _ => createEntity(entity))
.map( _ => ())
)
Аналогичный код, используя for-comprehension:
list.foldLeft(Future.successful(()))(( prevF, entity) =>
for {
_ <- prevF
_ <- createEntity(entity)
} yield ()
)
Объяснение:
val entity = Entity(0, 1)
createEntity(entity).map( _ => println("Ok!"))
Метод map запоминает полученную функцию и выполняет её после того как получит успешный результат Future
Meтод flatMap работает также, только ожидает что полученная функция тоже будет возвращать Future
createEntity(entityOne).flatMap( _ => createEntity(entityTwo))
Таким образом мы соединяем две Future последовательно, т.е. вторая вызовется только после успешного выполнения первой.
Отсюда очевидно что мы можем выстраивать длинные последовательные цепочки.
createEntity(entityOne)
.flatMap( _ => createEntity(entityTwo))
.flatMap( _ => createEntity(entityThree))
.flatMap( _ => createEntity(entityFour))
А результатом будет новая Future.
Ок, а как поступить если у нас есть список Future? Так же как мы бы поступили бы например с числами.
val listNumbers = List(1, 2, 3, 4, 5)
val zeroNumber = 0
def joinNumbers(a: Int, b: Int): Int = a + b
listNumbers.foldLeft(zeroNumber)(joinNumbers)
Почему foldLeft, а не map? Потому что прибавляем по очереди, и нам нужна сумма предыдущих чисел.
Зачем нужен zeroNumber? Ну а вдруг список пустой - вернем 0.
Теперь с Future:
val listEntities: List[Entity] = ???
val zeroFuture = Future.successful(())
def joinFuture(prevF: Future[Unit], entity: Entity): Future[Unit] =
prevF
.flatMap(_ => createEntity(entity)) // тут вторая Future
.map( _ => ())
listEntities.foldLeft(zeroFuture)(joinFuture)
Следующий createEntity выполняется после предыдущих.
Зачем нужен zeroFuture - ну а вдруг список пустой.
UPD:
Попробую подробней расписать почему foldLeft, а не map.
Сначала взглянем на map
val listOfFutures = list.map( entity => createEntity(entity))
map просто проходит по листу и вызывает создание Энтити. Он не ждёт пока создание завершится, он просто создаёт новую Future и идёт дальше к следующему элементу листа.
Т.е. на каждой итерации нам нужно "нечто" что будет знать о том что на предыдущем шаге Энтити создался:
val listOfFutures = list.map( entity =>
val prevEntityWasCreated = ??? // что-то, что знает о предыдущем Энтити
// flatMap заставляет выполнятся ПОСЛЕ
prevEntityWasCreated.flatMap(_ => createEntity(entity))
)
Т.е. проходимся по списку и на каждом этапе ждем пока завершиться предыдущий. Что-то подобное нам надо, верно?
Точнее такое:
val listOfFutures = list.map( (prevEntityWasCreated, entity) =>
prevEntityWasCreated.flatMap(_ => createEntity(entity))
)
Супер. Второй энтити ждет когда создастся первый, третий ждёт когда создастся второй. А чего ждёт первый? Надо создать пустую Future специально для первой этнити:
val zeroFuture = Future.successful(())
val listOfFutures = list.map(zeroFuture)( (prevEntityWasCreated, entity) =>
prevEntityWasCreated.flatMap(_ => createEntity(entity))
)
Всё отлично, только тип не совпадает:
val zeroFuture: Future[Unit] = Future.successful(())
val foo: Future[Entity] = prevEntityWasCreated.flatMap(_ => createEntity(entity))
Просто добавим map и изменим тип на одинаковый
val zeroFuture: Future[Unit] = Future.successful(())
val foo: Future[Unit] = prevEntityWasCreated.flatMap(_ => createEntity(entity)).map( _ => ())
Последнии штрих - переименовать новую функцию в foldLeft
list.foldLeft(Future.successful(()))(( prevF, entity) =>
val foo: Future[Unit] =
prevF
.flatMap( _ => createEntity(entity))
.map( _ => ())
// возвращаем Future для следующего элемента
foo
)
Комментариев нет:
Отправить комментарий