На этой неделе я много думал об OpenWhisk и последовательностях, а точнее о том, как безсерверные технологии в целом могут помочь в разработке, позволяя объединять различные действия для формирования новых. Подобно тому, как Lego можно разобрать на части и собрать в новые конфигурации, меня очень интересуют возможности бессерверных действий, когда они связаны вместе.

Чтобы было ясно, я знаю, что в основном говорю здесь о повторном использовании кода, и в этом абсолютно нет ничего нового. Одна из причин, по которой я так люблю Node, — это простота использования npm и возможность использовать различные разрозненные пакеты для включения в свое приложение. Но с бессерверным интерфейсом все немного иначе, он мощнее и проще.

Во-первых — я могу комбинировать действия с совершенно разных платформ. Одна последовательность может состоять из кода Python, Swift и Node, и мне будет все равно.

Во-вторых, мне кажется, что бессерверный код еще больше приспособлен к такому подходу типа «мэшап». Вынуждая вас писать код, который только принимает ввод и возвращает вывод, он кажется еще более подходящим для использования в сочетании друг с другом.

Я, наверное, слишком усердствую в этом, но, как я уже сказал, я в восторге от этого! В этом ключе я создал две довольно крутые демонстрации этого в действии, которыми я хотел поделиться.

Чуть больше двух недель назад я написал пакет OpenWhisk для работы с сервисом Tone Analyzer IBM Watson. С тех пор я также работал над другими различными простыми действиями, включая одно для работы с Twitter и другое для работы с RSS-каналами. Вот первое демо, которое я создал.

Для моей первой демонстрации я создал последовательность, которая сочетает в себе два действия:

  • RSS to Entries — это действие просто берет URL-адрес RSS и возвращает массив записей.
  • Tone Analyzer — принимает строку и возвращает для нее анализ тона.

Чтобы собрать их вместе, я создал третье действие, которое назвал flattenRSSEntries. Все, что делало это действие, — это массировало данные одного действия, чтобы сделать их подходящими для другого действия. (У меня есть мысли по этому поводу в конце статьи.) Вот это действие.

function main(args) {

    let string = args.entries.reduce( (cur, val) => {
        if(val.description) cur+=val.description;
        return cur;
    }, '\n').trim();

    return {
        text:string,
        sentences:false,
        isHTML:true
    }

}

exports.main = main; 

По сути — взять массив — свести его к описанию из RSS-записи, а потом выплюнуть вывод для тонального анализатора.

И да — это так. Мне буквально пришлось написать около 10 строк кода для клея. Затем я сделал последовательность из трех действий:

wsk action update rssToTone --sequence utils/rssentries,flattenRSSEntries, mywatson/tone

И тогда я могу запустить его в командной строке следующим образом:

wsk action invoke rssToTone --param rssurl https://www.raymondcamden.com/index.xml -b -r

Вот результат:

{
    "document_tone": {
        "tone_categories": [
            {
                "category_id": "emotion_tone",
                "category_name": "Emotion Tone",
                "tones": [
                    {
                        "score": 0.147034,
                        "tone_id": "anger",
                        "tone_name": "Anger"
                    },
                    {
                        "score": 0.093125,
                        "tone_id": "disgust",
                        "tone_name": "Disgust"
                    },
                    {
                        "score": 0.130901,
                        "tone_id": "fear",
                        "tone_name": "Fear"
                    },
                    {
                        "score": 0.575317,
                        "tone_id": "joy",
                        "tone_name": "Joy"
                    },
                    {
                        "score": 0.575186,
                        "tone_id": "sadness",
                        "tone_name": "Sadness"
                    }
                ]
            },
            {
                "category_id": "language_tone",
                "category_name": "Language Tone",
                "tones": [
                    {
                        "score": 0.815472,
                        "tone_id": "analytical",
                        "tone_name": "Analytical"
                    },
                    {
                        "score": 0,
                        "tone_id": "confident",
                        "tone_name": "Confident"
                    },
                    {
                        "score": 0.543053,
                        "tone_id": "tentative",
                        "tone_name": "Tentative"
                    }
                ]
            },
            {
                "category_id": "social_tone",
                "category_name": "Social Tone",
                "tones": [
                    {
                        "score": 0.645549,
                        "tone_id": "openness_big5",
                        "tone_name": "Openness"
                    },
                    {
                        "score": 0.228331,
                        "tone_id": "conscientiousness_big5",
                        "tone_name": "Conscientiousness"
                    },
                    {
                        "score": 0.228709,
                        "tone_id": "extraversion_big5",
                        "tone_name": "Extraversion"
                    },
                    {
                        "score": 0.174568,
                        "tone_id": "agreeableness_big5",
                        "tone_name": "Agreeableness"
                    },
                    {
                        "score": 0.611825,
                        "tone_id": "emotional_range_big5",
                        "tone_name": "Emotional Range"
                    }
                ]
            }
        ]
    }
}

По-видимому, мои самые сильные эмоции — это смесь грусти и радости. Хорошо, я могу жить с этим. ;)

Твиттер в тон

Для моей второй демонстрации я создал последовательность, которая объединяет два действия:

  • Получить твиты — это действие просто позволяет вам искать учетную запись Twitter или ключевое слово. Я использовал его для получения твитов для аккаунта.
  • Tone Analyzer — принимает строку и возвращает для нее анализ тона.

Чтобы объединить эти две вещи, я снова сделал новое действие под названием flattenTweets. Это было немного сложнее. Я решил удалить ретвиты из примера. Я также думал об удалении ответов, но боялся, что у меня не будет достаточно данных. В нынешнем виде все еще кажется, что входные данные могут быть недостаточно глубокими для хорошего анализа, но я полагаю, что могу побеспокоиться об этом позже. Вот это действие:

function main(args) {

    /*
    only add if no retweeted status
    */
    let string = args.tweets.reduce( (cur, val) => {
        if(val.text && !val.retweeted_status) cur+=' '+val.text;
        return cur;
    }, '').trim();

    return {
        text:string,
        sentences:false,
        isHTML:true
    }

}

exports.main = main;

Как и раньше, я сделал свою последовательность, а затем я мог вызвать ее из CLI следующим образом:

wsk action invoke twitterToTone --param account raymondcamden -b -r

Если вам любопытно, это результат в моей ленте Twitter.

{
    "document_tone": {
        "tone_categories": [
            {
                "category_id": "emotion_tone",
                "category_name": "Emotion Tone",
                "tones": [
                    {
                        "score": 0.061874,
                        "tone_id": "anger",
                        "tone_name": "Anger"
                    },
                    {
                        "score": 0.532221,
                        "tone_id": "disgust",
                        "tone_name": "Disgust"
                    },
                    {
                        "score": 0.092915,
                        "tone_id": "fear",
                        "tone_name": "Fear"
                    },
                    {
                        "score": 0.586786,
                        "tone_id": "joy",
                        "tone_name": "Joy"
                    },
                    {
                        "score": 0.539941,
                        "tone_id": "sadness",
                        "tone_name": "Sadness"
                    }
                ]
            },
            {
                "category_id": "language_tone",
                "category_name": "Language Tone",
                "tones": [
                    {
                        "score": 0,
                        "tone_id": "analytical",
                        "tone_name": "Analytical"
                    },
                    {
                        "score": 0,
                        "tone_id": "confident",
                        "tone_name": "Confident"
                    },
                    {
                        "score": 0.63355,
                        "tone_id": "tentative",
                        "tone_name": "Tentative"
                    }
                ]
            },
            {
                "category_id": "social_tone",
                "category_name": "Social Tone",
                "tones": [
                    {
                        "score": 0.040865,
                        "tone_id": "openness_big5",
                        "tone_name": "Openness"
                    },
                    {
                        "score": 0,
                        "tone_id": "conscientiousness_big5",
                        "tone_name": "Conscientiousness"
                    },
                    {
                        "score": 0.00002,
                        "tone_id": "extraversion_big5",
                        "tone_name": "Extraversion"
                    },
                    {
                        "score": 0.011157,
                        "tone_id": "agreeableness_big5",
                        "tone_name": "Agreeableness"
                    },
                    {
                        "score": 0.007357,
                        "tone_id": "emotional_range_big5",
                        "tone_name": "Emotional Range"
                    }
                ]
            }
        ]
    }
}

disgust был немного странным, но я как бы спорил с кем-то (хороший спор, я бы сказал!), так что, возможно, это произошло из-за этого.

Вывод

Во-первых, позвольте мне поделиться репозиторием, где вы можете найти этот код: https://github.com/cfjedimaster/Serverless-Examples. Дайте мне знать в комментариях ниже, если у вас есть какие-либо вопросы по этому поводу.

Я просто не могу свыкнуться с тем, как… приглашать сюда. Одна из вещей, которую я начал осознавать, став старше как разработчик, это то, что с некоторыми платформами, естественно, весело играть. Некоторые, естественно, приглашают к экспериментам и просто пробуют что-то новое. В то время как большинство платформ допускают одни и те же вещи, я просто гораздо больше хочу использовать что-то, что поощряет мою способность создавать глупые демонстрации (обычно с участием кошек, но сегодня я потерпел неудачу, я полагаю). Вот почему я люблю безсерверные технологии и OpenWhisk — мне кажется, что я могу все.

Еще кое-что

Ладно, серьезно, можете перестать читать, и я не обижусь. Итак, один из аспектов, который отчасти — я не скажу, беспокоил меня — но немного застрял у меня в голове — заключался в том, что мне нужно было написать свой собственный «объединяющий» код, чтобы мои последовательности работали. Для Twitter это имело смысл, так как я не просто сводил массив в строку, но также применял некоторую условную логику в качестве фильтра. Хотя для RSS я буквально просто брал один ключ из массива объектов.

Для этого было бы круто, если бы я мог использовать что-то вроде JSONPath для манипулирования результатами. В таких случаях я хотел бы иметь возможность вообще пропустить действие объединитель и, возможно, просто применить его как метаданные.

Теперь — вы можете сказать — как насчет создания действия JSONPath? Хорошо, но вот с этим проблема. Прямо сейчас в OpenWhisk вы не можете указать параметр по умолчанию для действия в последовательности за пределами первого действия. Итак, предполагая, что у меня есть действие JSONPath, когда я создавал свою последовательность, я не мог указать параметр по умолчанию для пути и применить его ко всей последовательности. Оно будет передано первому действию, но не передано второму, если только я не изменю свое первое действие, которое вы в любом случае не хотите делать. Ваши действия должны быть атомарными, автономными и т. д., и не иметь знаний о таких внешних вещах.

Чтобы было ясно, оба «столяра» заняли у меня около 5 минут работы. Я просто не могу отделаться от мысли, что может быть есть способ лучше и круче? Потом еще раз — может быть, нет. Я как бы сказал, что эта часть записи в блоге была немного не по теме остальных, но я также хотел бы получить любые отзывы по этой проблемной области.

Первоначально опубликовано на www.raymondcamden.com 7 апреля 2017 г.