Scala Type System Help: Пусть сбой каскадируется в стеке - PullRequest
0 голосов
/ 15 декабря 2018

Я боролся с этим зверем в течение нескольких дней и нуждаюсь в некотором руководстве.Мой оригинальный код слишком велик и громоздок, поэтому я попытался создать все фрагменты здесь, и он (почти) компилируется, я получаю некоторые ошибки с моим составленным кодом ниже.

То, что я пытаюсь сделать, это позволить "Отказу" каскадно подойти к моему уровню API.Домен подключается к бэкэнду, чтобы получить список проверок состояния оборудования по MAC-адресу.Учетная запись имеет несколько устройств, а оборудование имеет несколько MAC-адресов.Только первичные маки получат успешный ответ от бэкэнд-системы.

Обновление 1: здесь под отказом я имею в виду проблемы с подключением к бэкэнд-клиенту.Неизвестный Mac (т.е. Mac, который не найден / не разрешен) не считается ошибкой.Об этом следует говорить как об успехе.

Вот то, что я до сих пор воссоздал для вашего удовольствия подражать моей системе.Строки 18 и 19 можно переключать, чтобы увидеть разные условия.

    import scala.util._

    trait EquipmentStatus { val mac: String }
    case class Offline(mac: String) extends EquipmentStatus
    case class Online(mac: String) extends EquipmentStatus
    case class Unknown(mac: String) extends EquipmentStatus

    case class EquipmentHealth(mac: String, status: EquipmentStatus)

    case class Account(number: Int, equipments: List[Equipment])
    case class Equipment(macs: List[String]) { 
        def primaryMacs = macs.filter(_.endsWith("00")) 
    }

    object StatusChecker { 
        def checkBatchStatuses(macs: List[String]): 
        Try[List[EquipmentStatus]] = 
            //Success(macs.map(Online(_))) 
            Failure(new Exception("Connection Timed Out"))
    }


    object DeviceService {

        def getMacsByAccount(macs: List[String], equipments: List[Equipment]): Try[List[EquipmentHealth]] = {

            for {
                mac <- macs 
                equipment <- equipments.filter(_.macs.contains(mac))
                statuses <- StatusChecker.checkBatchStatuses(equipment.primaryMacs)
            } yield resolveStatus(statuses, mac)// ######### HOW DO I CONVERT/COLLECT Try[EquipmentHealth] to Try[List[EquipmentHealth]] AND ALSO ALLOW Try[Exception()] TO PROPAGATE UP AS WELL? 

        }

        def resolveStatus(statuses: List[EquipmentStatus], mac: String): Try[EquipmentHealth] = {

            statuses.partition(_.mac == mac) match {
                case (Nil, Nil) => Success(EquipmentHealth(mac, Unknown(mac)))
                case (List(one), Nil) => Success(EquipmentHealth(mac, one))
                case _ => Success(EquipmentHealth(mac, Unknown(mac)))
            }
        }
    }


    val equipments = List(Equipment(List("mac100", "mac222")), Equipment(List("mac333", "mac400")))
    val exampleAcc = Account(1234, equipments)

    DeviceService.getMacsByAccount(List("mac222"), exampleAcc.equipments)

В моей базе кода Try на самом деле представляет собой пользовательский тип Boxed (Either), содержащий Succeeds и Fails.Мои навыки понимания отсутствуют.Я хотел бы перейти от Попробовать [EquipmentHealth] к Попробовать [List [EquipmentHealth]].

Я делаю это слишком сложным?Есть ли более простой способ, который я не вижу?

Ответы [ 2 ]

0 голосов
/ 16 декабря 2018

Текущая подпись getMacsByAccount равна

def getMacsByAccount(macs: List[String], ...): Try[List[EquipmentHealth]]

Это позволяет получить один Success / Failure результат, и вы не можете проверить каждый mac независимо.Если вы хотите отслеживать состояние ошибки каждого mac, вам нужно вернуть List[Try[EquipmentHealth]] вместо Try[List[EquipmentHealth]]:

def getMacsByAccount(macs: List[String], ...): List[Try[EquipmentHealth]]

Для реализации этого нужно просто изменить getMacsByAccount:

def getMacsByAccount(macs: List[String], equipments: List[Equipment]): List[Try[EquipmentHealth]] =
  for {
    mac <- macs
    equipment <- equipments.filter(_.macs.contains(mac))
    statuses = StatusChecker.checkBatchStatuses(equipment.primaryMacs)
  } yield
    statuses.flatMap(resolveStatus(_, mac))

Примечание: В данный момент resolveStatus всегда возвращает Success, и в этом случае он может также просто вернуть EquipmentHealth.Если вы измените это, измените flatMap выше на map.

Редактировать после комментариев

Если вам нужен только один провал или успех, оберните все это в Try и распакуйте внутренние значения Try, используя get:

def getMacsByAccount(macs: List[String], equipments: List[Equipment]): Try[List[EquipmentHealth]] =
  Try {
    for {
      mac <- macs
      equipment <- equipments.filter(_.macs.contains(mac))
      statuses = StatusChecker.checkBatchStatuses(equipment.primaryMacs).get
    } yield resolveStatus(statuses, mac).get
  }

Если любое из значений Try равно Failure, то get сгенерирует исключение, которое перехватывается внешним Try.

Конечно, было бы проще, если бы checkBatchStatuses и resolveStatus просто выдавали исключение при сбое, а не возвращали Try.

0 голосов
/ 15 декабря 2018

Разве вы не хотите иметь List[Try[EquipmentStatus]] вместо Try[List[EquipmentStatus]]?Первое не позволяет восстанавливаться после отдельных сбоев.Понимание всегда дает повторяющийся результат, вы не можете вернуть Try[List[Something]] из него.

Чтобы дать более подробный ответ, мне нужно, чтобы вы разъяснили ожидаемое поведение.Довольно странно, что ваш DeviceService.resolveStatus может привести только к успеху, и это главным образом потому, что вы не храните никакой информации о том, какие запросы были неудачными, поэтому вы не можете выбрать между «этот поиск не удался» и «этот MAC-адрес неизвестен» .Я думаю, что дело unknown должно быть удалено, и мы всегда предполагаем, что если успех не был возвращен, то это был сбой.В противном случае вам нужно хранить немного больше информации, например, List[(String, Try[EquipmentStatus]], где первый элемент кортежа - это MAC-адрес, который был запрошен (или, для повышения производительности, с использованием карты, ключи которой являются адресами).

...