Estoy creando un bot para Facebook Messenger usando Microsoft Bot Framework. Estoy planeando usar CosmosDB para State Management y también como mi almacén de datos de back-end. (No estoy atascado en CosmosBD y puedo usar cualquier otra tienda si es necesario)

Necesito enviar mensajes proactivos diarios / semanales (notificaciones push) a los usuarios según su preferencia de tiempo. Capturaré su preferencia de tiempo cuando interactúen por primera vez con el bot.

¿Cuál es la mejor manera de entregar estas notificaciones?

Como almacenaré estas preferencias en CosmosDB, estoy pensando en usar ComosDB desencadenante de crear una función de Azure y programarlo según la preferencia de tiempo del usuario. Esta función de Azure hará una llamada a mi webhook que entregará estos mensajes. Si se requiere, cambiaré el horario de la Función cuando un usuario cambie su preferencia.

Mis preguntas son:

  1. ¿Es un buen enfoque?
  2. ¿Hay alguna otra alternativa (Centro de notificaciones?)
  3. Debería poder establecer horarios específicos para las notificaciones (como en la hora más alta o algo así), ¿tiene sentido programar una función de Azure para que se ejecute a estas horas en lugar de crear una función basada en la preferencia del usuario (puedo en realidad combina estos dos enfoques también)

Gracias de antemano.

2
user3731783 23 ene. 2018 a las 20:33

3 respuestas

La mejor respuesta

Primero, no creo que haya una respuesta "correcta" para dar aquí; dependerá mucho de las necesidades específicas de su dominio. La escala jugará un factor importante en el diseño de esto. ¿Tendrás 100 usuarios? 10000 usuarios? 1mil usuarios? Asumiré que desea diseñar para una escala máxima por adelantado, pero podría ser excesivo.

Primero, en base a lo que ha descrito, no creo que un desencadenante CosmosDB sea necesariamente la solución a su problema porque eso solo se disparará cuando se creen / actualicen los datos de preferencia. Supongo que, a partir de ese momento, su función debe activarse continuamente en el intervalo de tiempo en el que han optado, ¿correcto?

Supongamos que permite que las personas elijan entre las 24 horas del día. Un enfoque ingenuo sería simplemente usar un activador programado que se activa cada hora, consulta al CosmosDB por todos los documentos en los que la preferencia se establece en esa hora en particular y luego comienza a enviar notificaciones desde allí. El problema es cómo escalas a partir de ahí y lidias con los problemas de idempotencia frente a los fracasos.

En primer lugar, un disparador de temporizador solo gira una instancia. Si solo fuera a consultar los documentos de CosmosDB y comenzara a procesarlos uno por uno en el alcance de ese disparador único, alcanzaría un límite relativamente rápido en cuántas notificaciones puede escalar. En cambio, lo que querría hacer es usar ese disparador de temporizador para desplegar las notificaciones a tantas instancias de funciones de "trabajador" como sea posible. El disparador del temporizador puede actuar como el orquestador en el sentido de que puede ser propietario de la consulta contra el CosmosDB y luego convertir cada resultado del documento que encuentre para esa ventana de tiempo de notificación particular en un mensaje que coloca en una cola para ser procesado por una función separada que se ampliará por sí solo.

En realidad, hay un par de formas en que puede lograr esto con Azure Functions, realmente depende de cuán temprano sea un adoptador de tecnología con el que se sienta cómodo.

El primero es lo que yo llamaría la forma "manual" que se haría simplemente usando la extensión existente de Azure Storage Queue tomando un IAsyncCollector<YourNotificationWorkerMessage> como parámetro para la función del temporizador que está vinculada a la cola de trabajo y luego bombeando los mensajes a través de eso. Luego, escribe una segunda función complementaria que utiliza un QueueTrigger, la vincula a la misma cola y se encargará de procesar cada mensaje. Esta segunda función es donde obtiene el escalado, lo que permite procesar todos los mensajes en cola lo más rápido posible en función de los parámetros de escalado que elija configurar. Este es el enfoque "más simple"

El segundo enfoque sería adoptar la nueva extensión de Funciones Durables. Con ese modelo, no tiene que pensar directamente en crear una cola de trabajo. Simplemente inicie una nueva instancia de su función de orquestador desde la función de temporizador y el orquestador despliega el trabajo invocando llamadas "simultáneas" N a una acción para cada notificación. Ahora, sucede que distribuye esas llamadas usando colas debajo de las cubiertas, pero ese es un detalle de implementación que ya no necesita mantener. Además, si el trabajo de entregar la notificación requiere un trabajo más complicado y / o una lógica de reintento, podría considerar usar una suborquestación en lugar de una acción simple. Finalmente, otro beneficio adicional de este enfoque es que puede "volver a conectarse" a su función principal del orquestador una vez que se entregan todas las notificaciones para realizar un trabajo de seguimiento ... incluso si eso es simplemente algún tipo de registro de eventos que la notificación El ciclo se ha completado para esta hora.

Ahora, el desafío con cualquiera de estos enfoques es en realidad tratar con el fracaso en buscar inicialmente a los candidatos para la notificación de CosmosDB, revisar los resultados y asegurarse de que realmente los despliegue de manera idempotente. Debe lidiar con posibles contratiempos a medida que navega y debe lidiar con el hecho de que toda su función podría verse afectada y podría tener que reiniciar. Tal vez en la ejecución inicial de las notificaciones de las 8AM que recibió a través de la página 273 de 371 páginas y luego fue golpeado con una falla de conectividad de red completa o la VM en la que se estaba ejecutando su función sufrió una falla de energía. Podría reanudar, pero necesitaría saber que dejó en la página 273 y que en realidad procesó el registro 27 de esa página y comenzar desde allí. De lo contrario, corre el riesgo de enviar notificaciones dobles a sus usuarios. Tal vez sea algo que puedas aceptar, tal vez no lo sea. Tal vez estés de acuerdo con que las 27 notificaciones en esa página se dupliquen mientras las primeras 272 páginas no lo estén. Una vez más, esto es algo que debe decidir para su dominio, pero si desea evitar este problema, su función de orquestador deberá realizar un seguimiento de su progreso para asegurarse de que no envíe engaños. Una vez más, diría que Durable Functions tiene una ventaja aquí, ya que viene con la capacidad de configurar reintentos. Sin embargo, mantener el estado de una ejecución particular se deja al autor en cualquiera de los enfoques.

3
Drew Marsh 23 ene. 2018 a las 20:56

Es posible que desee verificar la política de FB con respecto a los mensajes proactivos. Hay un límite de 24 horas, pero es posible que no esté totalmente atornillado en su caso

https://developers.facebook.com/docs/messenger-platform/policy/policy-overview#standard_messaging

1
JohnSDev 23 ene. 2018 a las 18:24

Utilizo un diálogo proactivo ampliamente con botframwork y messenger sin ningún problema. Durante su proceso de aprobación de Facebook, simplemente necesita informarles que enviará notificaciones a través de Messenger con su bot. Por lo general, si lo usa para informar a su usuario y mantenerse alejado del contenido promocional, debería estar bien.

También uso la función azul para activar el diálogo proactivo desde un punto final de controlador personalizado.

A continuación se muestra el código para la función azul:

public static void Run(TimerInfo notificationTrigger, TraceWriter log)
{
    try
    {
        //Serialize request object
        string timerInfo = JsonConvert.SerializeObject(notificationTrigger);

        //Create a request for bot service with security token
        HttpRequestMessage hrm = new HttpRequestMessage()
        {
            Method = HttpMethod.Post,
            RequestUri = new Uri(NotificationEndPointUrl),
            Content = new StringContent(timerInfo, Encoding.UTF8, "application/json")
        };
        hrm.Headers.Add("Authorization", NotificationApiKey);


        log.Info(JsonConvert.SerializeObject(hrm));

        //Call service
        using (var client = new HttpClient())
        {
            Task task = client.SendAsync(hrm).ContinueWith((taskResponse) =>
            {
                HttpResponseMessage result = taskResponse.Result;
                var jsonString = result.Content.ReadAsStringAsync();
                jsonString.Wait();

                if (result.StatusCode != System.Net.HttpStatusCode.OK)        
                {
                    //Throw what ever problem as an exception with  details
                    throw new Exception($"AzureFunction - ERRROR - HTTP {result.StatusCode}");
                }
            });

            task.Wait();
        }

    }
    catch (Exception ex)
    {
        //TODO log
    }
}

Debajo del código de muestra para iniciar el diálogo proactivo:

public static async Task Resume<T, R>(string resumptionCookie) where T : IDialog<R>, new()
{
    //Deserialize reference to conversation
    ConversationReference conversationReference = JsonConvert.DeserializeObject<ConversationReference>(resumptionCookie);

    //Generate message from bot to user
    var message = conversationReference.GetPostToBotMessage();

    var builder = new ContainerBuilder();
    using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
    {
        //From a cold start the service is not yet authenticated with dev bot azure services 
        //We thus must trust endpoint url.
        if (!MicrosoftAppCredentials.IsTrustedServiceUrl(message.ServiceUrl))
        {
            MicrosoftAppCredentials.TrustServiceUrl(message.ServiceUrl, DateTime.MaxValue);
        }


        var botData = scope.Resolve<IBotData>();
        await botData.LoadAsync(CancellationToken.None);

        //This is our dialog stack
        var task = scope.Resolve<IDialogTask>();

        T dialog = scope.Resolve<T>(); //Resolve the dialog using autofac        

        try
        {
            task.Call(dialog.Void<R, IMessageActivity>(), null);
            await task.PollAsync(CancellationToken.None);
        }
        catch (Exception ex)
        {
            //TODO log
        }
        finally
        {
            //flush dialog stack
            await botData.FlushAsync(CancellationToken.None);
        }
    }
}

Su diálogo debe estar registrado en autofac. Tu reanudación La cookie debe guardarse en tu base de datos.

2
Greg 24 ene. 2018 a las 14:30