Para ejecutar las instancias del programa debe encontrarse en el directorio pedidos-rust
Autorizando pagos aleatoriamente
RUST_LOG=info cargo run --bin gateway <config-path>Autorizando pagos manualmente
INPUT=1 RUST_LOG=info cargo run --bin gateway <config-path>Equivalente al anterior, tomando la configuracion de la carpeta (config.json)
make gatewayRUST_LOG=info cargo run --bin restaurant <id> <config-path>Equivalente al anterior, tomando la configuracion de la carpeta (config.json)
make restaurant id=<id>Aceptando ofertas aleatoriamente
RUST_LOG=info cargo run --bin rider <id> <config-path>Aceptando ofertas manualmente
INPUT=1 RUST_LOG=info cargo run --bin rider <id> <config-path>Equivalente al anterior, tomando la configuracion de la carpeta (config.json)
make rider id=<id>Eligiendo restaurante para pedir aleatoriamente
RUST_LOG=info cargo run --bin customer <id> <config-path>Eligiendo restaurante para pedir manualmente
INPUT=1 RUST_LOG=info cargo run --bin customer <id> <config-path>Equivalente al anterior, tomando la configuracion de la carpeta (config.json)
make customer id=<id>Hay un archivo de configuración con los ids posibles para cada aplicación, y en que dirección (ip + puerto) debe levantarse. Ej.:
{
"comensales": [
{id: 0, addr: "127.0.0.1:11000", x: 4, y: 3},
... otros comensales
],
"restaurantes": [
{id: 0, addr: "127.0.0.1:12000", x: 2, y: 5},
... otros restaurantes
],
"repartidores": [
{id: 0, addr: "127.0.0.1:13000", x: 5, y: 2},
... otros repartidores
],
"gateway": {id:0, addr: "127.0.0.1:14000"}
}
La idea general es tener 4 actores:
-
Comensal: el comensal tratará de conectarse con algún restaurante, y espera a ser contactado por el restaurante líder, que conocerá todos los restaurantes abiertos, y le proveerá una lista con los restaurantes que tiene cercanos (los ids de los mismos). De entre estos, el comensal elige alguno, autoriza el pago del pedido en el gateway, y envía el pedido al restaurante elegido. Después esperará una actualización del restaurante para saber si el pedido fue aceptado o cancelado por alguna razón; cuando está siendo preparado y listo para ser retirado. Luego de eso, un repartidor debería conectarse con él para indicarle que está en viaje, y el momento en el que llega.
-
Restaurante: manejará los pedidos de los comensales, cuando un comensal le realiza un pedido, le avisa que lo empieza a preparar, lo preparara (modelar algún sleep), y cuando lo termina, le avisa que su pedido está listo, y en ese momento le envía un mensaje al restaurante líder para que le consiga un repartidor. Los repartidores interesados se contactarán para aceptar el reparto. El restaurante le contestará a todos, pero solo le confirma el reparto al primero. El repartidor informará al restaurante sobre su estado (retirando el pedido, y que lo entregó). Una vez que el repartidor avise que el pedido ya fue entregado, el restaurante le pedirá al gateway de pago que sea liberado el pago. Si un cliente quiere realizar una nueva conexión, el restaurante que reciba la petición le informará al líder para que este le informe de los restaurantes que tenga cerca.
- Habrá un restaurante líder que será quien consiga un repartidor para los pedidos que haya que entregar, para esto, buscará los repartidores más cercanos (dentro de una radio) y les enviará una oferta de reparto, con la información del pedido y el restaurante al que hay que contactar para aceptarla. Además, todo restaurante que quiera conectarse, se contacta con este (de forma parecida a lo que hacen los comensales), por lo que sabrá que restaurantes están operando para cuando se conecten los comensales. Cuando le llega la información de un nuevo comensal, se contacta con este enviándole los restaurantes abiertos más cercanos a él. Todo repartidor también empezará por contactarse con este restaurante líder y le enviará actualizaciones de estado periódicas. También funciona como backup para los demás restaurantes, guardando la información de sus pedidos por si se caen.
-
Repartidor: el repartidor tratará de conectarse con algún restaurante (lo mismo que el comensal), y espera a ser contactado por el restaurante líder. A partir de este momento, se queda dando vueltas hasta que le llega una oferta de trabajo. Mientras se mueve le envía actualizaciones de su estado al restaurante líder. Una vez que le llegue alguna oferta de trabajo puede aceptarla o no (random o input), si decide aceptarla le envía un mensaje al restaurante, y este debera avisarle si se lo confirma o no. En caso afirmativo, va al restaurante a buscar el pedido, le avisa cuando lo retira (cuando está en la misma ubicación), y se pone en contacto con el comensal para avisarle que está yendo. Después le avisa cuando llega a la ubicación del comensal, tanto al comensal como al restaurante (para que pueda liberar el pago). Por las actualizaciones de estado periódicas en este momento el líder se va a dar cuenta de que está libre nuevamente.
-
Gateway de pago: será el encargado de aceptar o rechazar los pagos de los comensales. Retendrá el dinero del comensal cuando realice el pedido, si el pago es aceptado le avisará al restaurante para que pueda comenzar a preparar el pedido. Si se completa correctamente el pedido, será contactado por el restaurante y entregará el dinero. En caso de que se cancele el pedido o rechace el pago, devolverá el dinero al comensal.
struct Ubicacion {
x: usize,
y: usize,
}struct Comensal {
// id del archivo de configuración
id : usize,
ubicacion : Ubicacion,
// id de los restaurantes semilla para iniciar su conexión (va tratando de contactar hasta establecer conexión con uno)
restaurantes : Vec<usize>,
// id del restaurante lider, por si se cae el restaurante donde hizo el pedido y necesita pedir un reembolso
restaurante_lider : Option<usize>,
// id del gateway de pago (si mantenemos solo un gateway sería 0)
gateway_pago : usize,
// pedido que realizó (tiene el id de a que restaurante lo hizo, monto, etc, aparece definido mas abajo)
pedido : Option<Pedido>
}struct InfoPedidosRestaurante {
// todos los hashmaps tienen como clave el id del comensal que realizó el pedido
// Pedidos pendientes de autorización de pago
pedidos_no_confirmado: HashMap<usize, Pedido>,
// Pedidos que se están preparando
pedidos_en_preparacion: HashMap<usize, Pedido>,
// pedidos que están esperando un repartidor (están listos en el restaurante)
pedidos_a_retirar: HashMap<usize, Pedido>,
// ya los tiene el repartidor que esta dentro de la info del pedido
pedidos_retirados: HashMap<usize, Pedido>,
}
struct Restaurante {
id : usize,
ubicacion : Ubicacion,
// id del restaurante lider
restaurante_lider : Option<usize>,
// información de sus pedidos
info_pedidos_restaurante: InfoPedidosRestaurante,
// si es lider tiene esta información
lider_info : Option<RestauranteLiderInfo>,
// si es el backup del lider (se explica mas adelante) tiene esta información del lider
// tiene el id del restaurante que es replica
backup_info : Option<(usize, InfoPedidosRestaurante)>,
}
// PARA EL LIDER
enum RepartidorState {
Libre,
// Si un repartidor tiene este estado está esperando confirmación de un trabajo, no acepta otras ofertas
EsperandoConfirmacion(Pedido),
YendoARetirarPedido(Pedido),
LlevandoPedidoAComensal(Pedido)
}
struct RepartidorInfo {
// id del repartidor
id: usize,
// ubicación actual del repartidor
ubicacion : Ubicacion,
// estado actual del repartidor
estado : RepartidorState,
}
struct RestauranteLiderInfo {
// ids de los restaurantes que están conectados (se puede saber su ubicación y dirección a partir de la configuración)
restaurantes: Vec<usize>,
// indexados por su id
repartidores_libres : HashMap<usize, RepartidorInfo>,
repartidores_ocupados : HashMap<usize, RepartidorInfo>,
// id del restaurante que tiene mi información de los pedidos (explicado en detalle en backup)
// es un option porque tal vez el lider es el único restaurante conectado
restaurante_backup_id : Option<usize>,
// backup de los estados de los restaurantes por si alguno se cae, indexados por el id de restaurante
backup : HashMap<usize, InfoPedidosRestaurante>
}struct Repartidor {
id : usize,
ubicacion : Ubicacion,
// al igual que el comensal, para poder conectarse al sistema trata de contactar algun restaurante
restaurantes : Vec<usize>,
restaurante_lider : Option<usize>,
estado_actual : RepartidorState,
}struct Pago {
monto : usize,
id_comensal : usize,
id_restaurante : usize,
}
struct GatewayDePago {
// pagos sin confirmar, simplemente tiene el monto retenido, indexados por el id del comensal
pagos_pendientes : HashMap<usize, Pago>
}// lo envía el comensal al primer restaurante que logra contactar
// y es reenviado por el restaurante que lo recibe al restaurante líder
struct NuevoComensal {
// a partir de la configuración y el id se puede obtener su direccion
id_comensal : usize,
ubicacion : Ubicacion
}// lo envía el restaurante líder al nuevo comensal
struct RestaurantesDisponibles {
// vector con los ids de los restaurantes disponibles y cercanos
restaurantes : Vec<usize>
}// pedido que le envía el comensal al restaurante donde quiere pedir
// tambien se usa como estructura de información dentro del restaurante
struct Pedido {
// id y ubicacion del restaurante al que se hace el pedido
id_restaurante : usize,
ubicacion_restaurante : Ubicacion,
// id del comensal que hizo el pedido
id_comensal : usize,
ubicacion_comensal : Ubicacion,
monto : usize,
// id del repartido asignado (None si no se asignó todavia)
repartidor_asignado : Option<usize>,
}// mensaje que envía el comensal al gateway de pagos
struct PedidoAutorizacionPago {
id_comensal : usize,
id_restaurante : usize, // restaurante donde se hizo el pedido
monto : usize,
datos_tarjeta : usize,
}// mensaje que envía el gateway de pagos al restaurante
struct RespuestaAutorizacionPago {
id_comensal : usize,
monto : usize,
autorizado : bool,
}A partir de esto, el restaurante puede avisarle al comensal que su pedido está siendo preparado, o que su pago fue rechazado.
// Si el pago fue correcto y el pedido aceptado se le envía al comensal el mensaje
struct PedidoEnPreparacion {
pedido : Pedido,
}
enum MotivoCancelacion {
PagoRechazado,
NoHayRepartidor,
NoHayStock,
RestauranteCaido,
RepartidorCaido,
Otro(String), // El string contiene el motivo del error
}
// mensaje que envía el restaurante al comensal si su pago fue rechazado
struct PedidoCancelado {
motivo : MotivoCancelacion
}Una vez que el restaurante termina de preparar el pedido le envía la actualización al comensal
// mensaje que envía el restaurante al comensal cuando su pedido esta listo
struct PedidoPreparado {}En el mismo momento, el restaurante se pondrá en contacto con el líder para que un repartidor se encargue de la entrega
// mensaje que envía el restaurante al restaurante líder cuando termina de preparar un pedido
struct BuscarRepartidor {
// pedido para el que se busca repartidor (repartidor asignado va a ser None)
// ya contiene tanto el id del comensal que lo realizó, el id y ubicación del restaurante donde hay que buscarlo
pedido: Pedido,
// si es un reintento, el líder lo tendrá en cuenta para buscar repartidores más lejanos
es_reintento: bool,
}El restaurante líder al recibir el mensaje de buscar repartidor, calcula cuáles son los repartidores libres cercanos a ese restaurante y les envía un mensaje de oferta
// mensaje que envía el restaurante líder a los repartidores cercanos
struct OfertaDeReparto {
// pedido para el cual se busca repartidor
pedido: Pedido,
}Cuando el repartidor recibe una oferta de reparto responderá al restaurante (a donde hay que ir a buscar el pedido, no al líder) con un mensaje si desea aceptarla:
// mensaje que envía el repartidor al restaurante para aceptar el reparto
struct AceptarReparto {
// id del repartidor que esta aceptando (quien envía este mensaje)
repartidor : usize,
// pedido que se esta aceptando para repartir
pedido: Pedido,
}Una vez que un repartidor acepta un reparto, cambia su estado (en su estado interno), pasando a estar EsperandoConfirmación, en este momento, no aceptará ninguna otra oferta hasta que se le confirme si es o no el encargado de repartir ese pedido
El restaurante responderá a cada repartidor que le envíe el AceptarReparto con un mensaje ConfirmacionReparto, de esta forma el repartidor puede verificar si fue elegido o no viendo el repartidor asignado para ese pedido.
El repartidor asignado será el primero en contestar a la OfertaTrabajo con AceptarReparto
struct ConfirmacionReparto {
// Como se mostró mas arriba, dentro del pedido hay un option con el repartidor asignado, que en este punto ya tendra el id del mismo
pedido : Pedido,
}La idea de enviar la oferta, esperar a ver quien acepta, y realizar una confirmación es una especie de exclusión mutua centralizada (solo un repartidor puede encargarse del pedido). Lo incluimos como uno de los dos algoritmos vistos en clase que tienen que estar en el trabajo.
Si el restaurante envía el BuscarRepartidor al lider y luego de un tiempo no recibe ninguna respuesta de repartidores, vuelve a intententar una segunda vez, indicando con un booleano en el mensaje que ya es un reintento. En este caso, el líder lo tendrá en cuenta y tratará de buscar repartidores nuevamente pero teniendo en cuenta un rango de distancia mayor al usado la primera vez.
Si aun luego de esto el restaurante que necesitaba el repartidor no recibe respuesta, se le informa al comensal que su pedido fue cancelado (el mismo mensaje que si se rechazaba el pago, pero con el motivo de NoHayRepartidor), en ese caso, se hace el reembolso enviandole al gateway de pago el mensaje:
struct DevolverPago {
id_comensal: usize
}El gateway, al procesar el mensaje, le avisa al comensal que se le devolvió el dinero con el siguiente mensaje, aquí el comensal ya puede desconectarse.
struct PagoDevuelto {}Al recibir una confirmación de reparto, el repartidor pasa a estar ocupado (YendoARetirarPedido), (la idea es modelar el viaje con varios sleep, que van a tener actualizaciones al restaurante líder intermedias). Al llegar al restaurante, se comunica con el mismo enviándole un mensaje RetiroPedido, que también envía al comensal para informarle que está yendo a su ubicación.
struct RetiroPedido {
pedido: Pedido
}Ahora el repartidor pasa a estar LlevandoPedidoAComensal, (modelamos el viaje con varios sleep donde también se irá informando el estado al líder), al llegar a la ubicación del comensal, le envía un mensaje tanto al comensal, como al restaurante:
struct PedidoEntregado {
pedido: Pedido
}Aquí el repartidor pasará nuevamente a estar libre, el restaurante líder se entera de esto por las actualizaciones periódicas de estado.
En este momento, el restaurante podrá solicitar al gateway de pago que libere el pago anteriormente autorizado para este pedido con el mensaje.
struct LiberarPago {
id_comensal: usize
}El repartidor informará al restaurante líder su estado en todo momento.
struct EstadoDeRepartidor {
id_repartidor : usize,
estado : RepartidorState,
ubicacion : Ubicacion,
}Parecido a como se conectan los comensales, el restaurante que se quiere conectar tratará de contactar a algún restaurante (del archivo de configuración) y le envía el mensaje:
// lo envía el restaurante que se conecta al primer restaurante que logra contactar
// y es reenviado por el restaurante que lo recibe al restaurante líder
struct NuevoRestaurante {
// a partir de la configuración y el id se puede obtener su direccion
id_restaurante : usize,
ubicacion : Ubicacion,
}El restaurante líder le enviará entonces el mensaje:
struct RestauranteConectado {
id_restaurante_lider : usize,
// En caso de que el restaurante estuviera conectado anteriormente, esta estructura contiene su info de pedidos
// Si no, tendra todos los hashmaps vacíos
info_restaurantes: InfoPedidosRestaurante,
}Si no logra contactar a ninguno de los restaurantes, será automaticamente el lider.
Simil a como se conecta un restaurante
// lo envía el repartidor al primer restaurante que logra contactar
// y es reenviado por el restaurante que lo recibe al restaurante líder
struct NuevoRepartidor {
// a partir de la configuración y el id se puede obtener su direccion
id_repartidor : usize,
ubicacion : Ubicacion,
}El restaurante líder le enviará entonces el mensaje:
struct RepartidorConectado {
id_restaurante_lider : usize,
// En caso de que el repartidor estuviera conectado anteriormente, esta estructura contiene su ultima ubicacion y estado
// Si no, contiene la ubicacion del primer mensaje y 'Libre'
estado_repartidor : EstadoDeRepartidor,
}Para poder recuperar estados de los restaurantes que puedan caerse, el líder mantendrá la información de los pedidos de todos estos. Dentro de su struct RestauranteLiderInfo tiene un hashmap backup que para cada id de restaurante tiene su información de pedidos.
Ante una caída de un restaurante que posteriormente se reconecta, el líder le facilita esta información para que pueda seguir operando (como vimos en conexión de restaurante).
Por cada cambio de estado de un pedido, ya sea porque se terminó de preparar, es un pedido nuevo, etc, los restaurantes deberán entonces comunicar este cambio de estado para que actualice su backup
El punto de falla es entonces que se caiga el restaurante líder, perdiendo su información de pedidos, para evitar esto el líder elegirá tener un restaurante de réplica, para elegirlo, toma el primer restaurante con el que se conecte, para tener una replicca lo mas rapido posible.
Al elegir la réplica, el líder le enviará su información de pedidos. Y luego por cada cambio de estado de su información de pedidos, le enviará una actualización.
struct BackupInicial {
restaurante_info : InfoPedidosRestaurante,
}enum EstadoPedido {
PedidoPendiente,
PedidoEnPreparacion,
PedidoListo,
PedidoRetirado,
Eliminado,
}
struct ActualizacionBackup {
pedido_info : Pedido,
nuevo_estado : EstadoPedido
}Para la elección del líder decidimos utilizar el algoritmo de bully, siendo entonces la segunda herramienta de concurrencia distribuida utilizada.
Al detectar que el restaurante líder se cayó (dejo de contestar), se envía un mensaje election con el id propio a todos los que tengan ID mayor.
struct Election {
id_restaurante: usize,
}Si el mensaje es recibido por algún restaurante de ID mayor, este contestará un OK, indicando que el es quien continúa el proceso de elección. En este punto, quien contestó el OK enviará election a todos los que tengan ID mayor, y asi sucesivamente
struct OK {}Una vez que el restaurante que está llevando a cabo el proceso de elección no reciba ningún OK luego de cierto timeout, se proclamará como líder, enviando a todos los restaurantes, repartidores y comensales el mensaje COORDINATOR.
Se lo envía a todos los que haya en la configuración (solo podrsá enviarlo efectivamente a los que estén conectados), pues en este punto no tiene información de que actores están conectados y cuales no.
struct Coordinator {
id_restaurante_lider : usize,
}Al recibir este mensaje, los repartidores cambiarán el restaurante líder por lo que le empezarán a enviar a él sus actualizaciones de posición y estado. De esta forma, el nuevo líder obtendrá información del estado de todos los repartidores.
Los comensales simplemente se guardan en su estado interno el id del nuevo líder, por si el restaurante donde tienen hecho el pedido se cae y necesitan cancelar el pedido
Uno de los restaurantes que recibe el mensaje coordinator, tendrá el backup de la información de pedidos del líder anterior por ser su replica, en este punto, se la enviará al nuevo líder, y deja de ser la réplica (Pasa el option a None, ya no se percibirá como réplica).
Además, todos los restaurantes enviarán su estado de pedidos actual (BackupInicial que contiene la info de los pedidos), para luego ir solo avisando de actualizaciones. Gracias a que todos tienen que enviar su información de pedidos, aquí es cuando el líder sabra que restaurantes están conectados.
Decidimos que elegirá como su réplica al primer restaurante del que reciba un mensaje, de forma de resguardar su información lo antes posible, enviandole su información de pedidos como se explicó en la sección de Backup.
Luego de estos dos pasos, el líder ya tiene toda la información que necesita.
Al caerse un repartidor, el restaurante líder aún tendrá su última información por las actualizaciones que le fue enviando.
En caso de que este repartidor se reconecte (va a contactar al líder en algún momento), este le envía su último estado para que pueda retomarlo como se explicó en la sección de conexión de un repartidor.
Si no tenía ningún pedido asignado, no hay problema. Si tenía un pedido asignado pero aún no lo había buscado en el restaurante se esperará un tiempo a que se reconecte, y en caso de no reconectarse se buscará un nuevo repartidor.
Si ya había buscado el pedido, se esperará un tiempo a su reconexión, en caso de no reconectarse luego de un tiempo, el comensal le envía un mensaje de QuieroCancelarPedido con el motivo, RepartidorCaido al lider, este se encargará de cancelar el pedido, avisando al restaurante que lo elimine de su estado interno, y le enviará al gateway de pago el mensaje DevolverPago con el id del comensal. Al recibir este mensaje, el gateway le enviará PagoDevuelto al comensal, por lo que este ya puede desconectarse.
Si el comensal no había hecho ningún pedido aún, nadie se entera y no hay problema (tampoco hizo ningún pago), no hay estado a recuperar
Si había hecho un pedido que aún no se había terminado de preparar, el restaurante es quien se dará cuenta de esto (al querer enviarle un PedidoPreparado), esperará un tiempo antes de descartar el pedido (en ese caso se le avisaría al líder también que se eliminó el pedido, y se procede a liberar el pago del gateway por ser culpa del comensal).
En caso de que se reconecte el comensal antes de dar por eliminado el pedido, el restaurante líder, mirando por los pedidos de backup que tiene de todos los restaurantes se dará cuenta por lo que le avisa al restaurante asignado para que ahora si le envíe el mensaje de que tiene un pedido listo (junto con la información de ese pedido), y se procederá entonces a buscar un repartidor. Notar que en este caso al comensal no le llegaría como primer mensaje el RestaurantesDisponibles, sino un PedidoPreparado.
Si su pedido ya había sido asignado a un repartidor, entonces quien se dará cuenta será ese repartidor al querer informarle que está retirando su pedido, aquí espera durante un tiempo:
- Si después de ese tiempo el restaurante líder no se comunicó avisándole que el cliente se reconectó, le avisa al restaurante de donde retiró el pedido que lo elimine y pasa a estar libre (se confirma el pago por ser culpa del cliente)
- Si el comensal se reconecta, este se comunicará con el restaurante líder, el cual verá dentro de los backups que ya tiene un pedido en estado retirado, por lo que el líder va a contactar al repartidor avisandole, para que ahora si pueda informarle que retiró su pedido y se lo entregue (parecido al caso anterior, el primer mensaje que recibe el cliente no va a ser un RestaurantesDisponibles).
Si un repartidor ya se lo estaba llevando (ya estaba avisado que su pedido fue retirado) pasa algo similar al caso anterior, el reparatidor se dará cuenta cuando le quiera decir que el pedido fue entregado. Se maneja de forma similar al caso anterior, espera cierto tiempo antes de informar que simplemente debe eliminarse el pedido (se confirma el pago al restaurante por ser culpa del cliente). Si en este tiempo el cliente se reconecta, el restaurante líder, al igual que en el otro caso, lo contactará avisándole que se reconectó y puede completar la entrega.
Al igual que con los comensales, si no tenía pedidos no hay información a recuperar.
Si el que se da cuenta es un comensal por querer pedir allí (el líder le había dicho que estaba abierto pero ahora cerro), puede probar con otro (antes tiene que pedir que le devuelvan el pago, el gateway ya también se va a dar cuenta que el restaurante no está conectado).
Si el pedido estaba en preparación, se dará cuenta el cliente de la caída (luego de un tiempo esperando que le avisen que su pedido fue preparado), esperará un tiempo para ver si obtiene actualizaciones de su pedido (porque se volvio a levantar el restaurante), si no recibe nada, le enviará un mensaje al líder para que cancele el pedido y le informe al gateway para que le devuelva el dinero.
Si el que se da cuenta es un repartidor cuando va a retirar un pedido, espera un cierto tiempo, en el cual en caso de una reconexión debería ser contactado por ese restaurante. Sino, pasa a estar libre y le avisa al comensal que no pudo retirar su pedido, y el comensal hace lo mismo que en el caso anterior, le avisa al líder que quiere cancelar el pedido y espera la devolución del gateway.
El comensal le manda un mensaje al restaurante lider:
struct QuieroCancelarPedido {
id_comensal: usize,
// el motivo será RestauranteCaido
motivo: MotivoCancelacion,
}El lider lo cancela (quitandolo del estado interno y avisandole al restaurante a cargo en caso de que para ese punto se haya reconectado, solo que demoró mucho), el líder también le enviará al gateway de pago el mensaje DevolverPago con el id del comensal. Al recibir este mensaje, el gateway le enviará PagoDevuelto al comensal, por lo que este ya puede desconectarse.
Si el que se da cuenta es un repartidor cuando entrega el pedido (porque le avisa al restaurante que puede pedir el pago), lo que hace es avisarle directamente al líder que ese pedido fue completado, y es este quien realiza la liberación del pago, pues el pedido se completó correctamente.
Si el restaurante se reconecta, el líder le va responder con una InformacionPedidosRestaurante no vacía (tomada del backup). Luego, deberá informar a los repartidores que tenía asignados y que aún no se desconectaron (si ya dieron por cancelado ni siquiera los va a tener en su información, el líder los habrá quitado) que volvió para que continuen su tarea. Si hay pedidos listos sin repartidor, hace como cuando recién los termina de preparar, pidiendo al líder que busque un repartidor. Y continuará con los pedidos que están en preparación.
Para todas las comunicaciones vamos a usar TCP para delegar la responsabilidad de los errores en la recepción y envíos de mensajes. Como se explicó al inicio, habrá un archivo de configuración que tendrá los ids de los distintos restaurantes, repartidores y comensales, junto con sus direcciones. De esta forma, cualquiera puede querer enviarle un mensaje a cualquier otro actor usando su id y su tipo.
De alguna forma se van guardando las conexiones ya activas. Al querer el actor enviar un mensaje al actor x, si ya hay una conexión, simplemente se envía, sino se realiza una conexión usando los datos de configuración y luego se envía
Cada actor también escucha por conexiones entrantes en su dirección asignada.
La forma de darse cuenta de una desconexión es al momento de querer hacer el read o el write en el stream, que al estar la conexión tcp cerrada dará algun tipo de error. También podría manejarse con timeouts.
Para los mensajes pensamos usar serde_json, pasando a formato json los structs, y con un \n al final, puede manejarse como un LinesStream.
Para modelar las distintas aplicaciones decidimos utilizar actores de actix, de forma que cada actor maneje su propia lógica y estado interno, y se comunique con los demás actores a través de mensajes. Donde sea necesario se usarán handlers o tareas asincrónicas para manejar la lógica necesaria, por ejemplo, para preparar los pedidos, se necesita un sleep que no queremos que bloquee al restaurante.
También para facilitar el manejo de la comunicación al actor principal, cada uno de ellos tendrá un actor de conexión que se encargará de todo el manejo de las conexiones entrantes, mensajes entrantes, desconexiones, envió de mensajes, etc. De esta forma, el actor principal solo se encargará de la lógica de negocio y no del manejo de las conexiones.
Por cada conexión activa el actor de conexión usa un actor TcpSender que maneja ese stream de mensajes (tanto para recibir como para envios), al usar el LinesStream de tokio (va leyendo linea por linea) se maneja facilmente la desconexión con el evento de finish, en ese momento se da cuenta de que el otro lado del stream se desconectó y le avisa al actor principal, que puede o no estar interesado en ese momento de esa desconexión.
Las conexiones son duraderas, por lo que al momento de enviar el primer mensaje se establece la conexión, y para los siguientes ya se mantiene.
En caso de que la cantidad de conexiones represente algun problema en algun momento, el actor principal al completar todo un intercambio (por ejemplo, el repartidor acaba de completar un pedido, la conexión con el comensal se va a cerrar sola por irse el comensal, pero podría tambien decidir cerrar la conexión con el restaurante del que acaba de entregar el pedido si este no es el lider). Tambien por ejemplo, el restaurante que recibe una conexion de un comensal y lo unico que tiene que hacer es reenviar el mensaje al lider, puede luego de reenviado, cerrar la conexión con ese comensal (si justo el comensal quiere pedir ahi, puede reestablecerla sin problema).
Cambios más importantes que hicimos del diseño en la implementacion:
- El customer pide al gateway que le autorice el pago y el mismo le confirma al customer si fue o no autorizado. Solo en el caso de ser autorizado realiza el pedido al restaurante. Antes lo habiamos planteado para que envie la autorización y el pedido a la vez, y el gateway le confirmaba al restaurante, el problema de esto es que traía muchas complicaciones al poder llegar en distinto orden los mensajes al restaurante por cuestiones de red.
- Decidimos que no hace falta que el restaurante este conectado para retirar un pedido del mismo. Tomamos como que perdio la conexión, pero fisicamente está alli y el rider puede retirarlo. En lugar de el restaurante al saber que el pedido fue retirado informarle al lider, el rider le informa del retiro tanto al restaurante como al lider. De esa forma, aunque el restaurante esté desconectado, cuando se reconecte va a tener este estado actualizado.
- Parecido al caso anterior, no hace falta que el customer este conectado para entregarle el pedido, puede haber perdido la conexión pero el rider se lo encontraría fisicamente. No hay problema con esto si se reconecta el cliente ya que el rider tambien informa al restaurante que el pedido fue entregado.