Под конец прошлого года компания Amazon выпустила (наконец-то!) C++ SDK для своей платформы AWS. Непонятно, почему так долго тянули — есть масса приложений, написанных на С++, из которых давно хотелось удобно пользоваться AWS. Не джавой же единой, в самом деле.

Всегда интересно посмотреть на что-то, написанное на С++ в наши дни с нуля, без груза обратной совместимости и странных архитектурных решений, присущих некоторым разработкам прошлого. Я не буду тут пересказывать документацию, но остановлюсь на ключевых моментах, определяющих всё поведение SDK.

С++11


Как в своё время должен был быть разрушен Карфаген, так сегодня должны умереть все библиотеки и приложения, до сих пор не перешедшие на С++11. Им просто не место в современном мире. Со слезами на глазах я читаю посты вроде этого о библиотеках, которые «чтобы расширить аудиторию — стараются обходиться без C++11». Как по мне, то это решение где-то около «будем ездить на лошадях, потому что они привычнее автомобилей». Новое SDK от Amazon не гонится за ретроградами — из коробки нам предлагаются все фичи С++11: auto, умные указатели, лямбды и т.д. Дать возможность указать в виде колбэк-функции лямбду — жизненно необходимо, без этого нас даже Javascript засмеёт. Ну и никаких new\delete в явном виде, конечно. Красота!

Простота кода


Как результат предыдущего пункта — посмотрите только на код, который получается в результате. Положим в DynamoDB пару «ключ-значение»:

DynamoDBClient client;
AttributeValue haskKeyAttribute;
hashKeyAttribute.SetS("SampleHashKeyValue")
AttributeValue valueAttribute;
valueAttribute.SetS("SampleValue")

PutItemRequest putItemRequest;
putItemRequest.WithTableName("TestTableName").AddItem("HashKey", hashKeyAttribute).AddItem("Value", valueAttribute);

client.PutItem(putItemRequest);


Ну вот и скажите мне, любители выражений вроде «С++ слишком сложен и избыточен» — чем это хуже Java или Python? А памяти съест меньше и выполнится быстрее.

Нет лишних зависимостей


Ребята из Амазона понимают, что каждая лишняя зависимость порождает дополнительную сложность при подключении и сборке проекта, а значит уменьшает количество пользователей. Поэтому AWS C++ SDK не зависит ни от boost, ни от других фундаментальных фреймворков. Даже маленьких библиотек, вроде логирования, в нём нет. Единственная зависимость — HTTP клиент. Всё-таки запихивать в SDK собственную реализацию этого дела было бы уже чересчур. Из коробки предлагается на выбор привязка к системным CURL, WinHTTP, WinInet или возможность внедрить свою реализацию.

CMake


Мир С++ по-маленьку движется к стандартизации описания проектов. CMake может быть ещё пока и не победил окончательно, но у тех, кому нужно дать возможность сборки под как можно более широкий набор платформ и компиляторов, этот вариант приходит на ум первым. Не отличился тут и Amazon — всё в CMake и компилируйте себе на чём угодно. Очень удобно работать в CLion.

Dependency Injection


Внедрение зависимостей, как основа конфигурации приложения. У нас есть возможность использовать дефолтные реализации от Amazon или вместо них свои буквально для всего: http-клиента, логирования, авторизации, аллокатора памяти, стратегий ограничения скорости и повтора попыток, обработчика асихронных событий и т.д. В итоге каждый из этих компонентов получается небольшим, поддающимся тестированию и не вызывающим проблем при интеграции.

Логирование


Библиотеки для логирование в зависимостях нет. Если вы хотите получать логи — реализуйте определённый интерфейс и передайте его в конфиг при инициализации клиента. Будете получать колбэк-вызовы с текстом логов — и пишите их куда хотите. Дефолтная реализация пишет в логи всё подряд, в текущую папку и удаляет старые логи каждый час.

Синхронные и асинхронные версии всех операций


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

CreateTableOutcome createTableOutcome = dynamoDbClient->CreateTable(createTableRequest);
  if (createTableOutcome.IsSuccess())
  {
...
  } else {
...
  }


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

void putItemFinished(const DynamoDBClient *client, const PutItemRequest& request, 
				const PutItemOutcome& outcome,
				const std::shared_ptr<AsyncContext>& context)
{
 if (outcome.IsSuccess())
 {
 	SEND_QUEUE.erase(context->GetUUID());
 }
}

auto context = Aws::MakeShared<AsyncContext>("Hello!");
context.SetUUID("UniqueRequestKey");
SEND_QUEUE["UniqueRequestKey"] = putItemRequest;
client.PutItemAsync(putItemRequest, &putItemFinished, context);


Снова слава С++11: в качестве колбэка можно передавать не только функцию, но и метод класса некоторого объекта (через std::bind) или лямбду.

Поддержка Asynchronous executor


Если вы любите асинхронные операции и у вас в программе уже есть какой-нибудь пул бэкграунд-потоков, класс выполнения асинхронных операций — вы можете задействовать его для запуска операций AWS C++ SDK. Таким образом все асинхронные операции в вашем приложении будут выполнятся по одной схеме и вы сэкономите ресурсы на фоновых потоках, которые запускала бы SDK в противном случае.

Ограничение скорости


Если вам нужно ограничивать скорость передачи данных для некоторых операций — всё в вашей власти. Реализуете интерфейс RateLimiter, передаёте свою реализацию в конфиг при инициализации — и получаете полный контроль полосы передачи данных.

Нет исключений


Вопрос использования или неиспользования исключений — давний холивар в мире С++. Но во всё большем количестве примеров в последнее время исключений стараются избегать. В AWS C++ SDK исключений тоже нет. Аргументируют большей гибкостью и производительностью. Нужен код ошибки — проверяйте outcome.IsSuccess(), если там false — в outcome будут также дополнительные данные об ошибке. Не знаю как вам, а мне такой подход нравится.

Включенная по умолчанию стратегия повтора попыток выполнения операции


Поскольку любая SDK от Amazon — это в первую очередь история о сетевых операциях, то нужно понимать, что сетевые операции частенько заканчиваются ошибками. Где-то Wi-Fi подвёл, где-то мобильная сеть, а где-то вроде бы стабильный наземный канал почему-то отказался загружать смешной трёхтеррабайтный файл. AWS C++ SDK по-умолчанию пытается повторить закончившуюся ошибкой операцию. Детали алгоритма (количество попыток повтора, время между попытками) можно контролировать собственной реализацией стратегии повторов.

Возможность определения аллокаторов памяти


Если для вас важно, сколько памяти использует ваше приложение, когда оно её выделяет и когда удаляет, как быстро это происходит (а для вас это всё должно быть интересно, зачем иначе вы вообще пишете на С++?) — вы можете определить собственный аллокатор памяти. Для этого нужно унаследоваться от MemorySystemInterface и определить несколько методов. Можно попробовать стянуть откуда-нибудь хороший аллокатор, заточить его под свои объёмы данных и алгоритмы и получить неплохой прирост производительности.

GitHub


Всё живёт на GitHub, принимаются Issues, Pull Requests. Контакт с сообществом поставлен хорошо.

Удачи в использовании AWS из приложений на С++!

Комментарии (11)


  1. ZOXEXIVO
    22.02.2016 15:16
    -3

    Честно говоря, смущает скрещивание быстрого C++ и медленного облака.
    Если вы делаете что-то критичное к производительности, то обычно это уж совсем системные вещи, а если нет, то зачем использовать C++ в тех местах, для которых он не предназначен?


    1. neurocod
      22.02.2016 16:01
      +3

      Есть большая программа на С++, которой понадобилась функция "сохранить в облако". Нанимать программистов на других языках программирования? Или следить, чтобы в штате хоть кто-то знал этот другой ЯП? Писать интерфейсы в другой ЯП/библиотеку? Зачем такие сложности ради 20 строк кода на С++?


    1. tangro
      22.02.2016 16:35
      +2

      • Игра на мобильном приложении (сама на С++ ради производительности, использует облако как сервис хранения данных).
      • Приложения на С++, которые и так уже работают на амазоновских серверах и им нужно общаться между собой или с инфраструктурой Амазона.
      • Платформы, где кроме как на С++ писать сложно (какой-нибудь мини-датчик в умном доме, который хочет репортить статус на Амазон).


    1. neverice
      22.02.2016 19:26
      +1

      С++ прекрасно подходит и для "медленных" облаков. Что статья наглядно и продемонстрировала. Ну и да — это же SDK, тоесть библиотека, реализующая некоторый интерфейс взаимодействия. И выбирают язык SDK исходя из того, на каком языке написано приложение. Amazon свое SDK предоставляет не только на C++


  1. slonm
    22.02.2016 19:15
    +1

    Как в своё время должен был быть разрушен Карфаген, так сегодня должны умереть все библиотеки и приложения, до сих пор не перешедшие на С++11. Им просто не место в современном мире. Со слезами на глазах я читаю посты вроде этого о библиотеках, которые «чтобы расширить аудиторию — стараются обходиться без C++11».

    Если бы все было так просто, как хотелось. Я поддерживаю клиентскую часть проекта облачного диска, который хранит файлы на Amazon S3 на С++. Основное предназначение это резервное копирование данных с NAS. Поддерживать нужно работу на устройствах около 10 поставщиков. В номенклатуре каждого 1-3 разных аппаратных архитектуры и каждый предоставляет кросс-тулчейн с GCC для сборки. Всего около 20 тулчейнов. Большая часть предоставляет GCC 4.2-4.7, т.е. С++ 98. Да что там говорить — на некоторых Linux операционках устройств ядро более-менее новое, а GLIBC настолько старая, что мне приходится компилировать со самописаным заголовочным файлом для Inotify и прямыми обращениями к сисиемным вызовам, т.к. GLIBC его не знает.
    Да. Я могу скомпилировать новый GCC для каждого тулчена для перехода на С++11, но также потребуется распространять и C++ рантайм вместе с программой. Это будет еще больше раздувать дистрибутив, а на NAS далеко не всегда выделяют много дискового места для программ.

    Вобщем у программно-аппаратных комплексов время жизни намного больше чем хотелось бы для ускорения чисто программного прогресса. И пока он удовлетворительно выполняет свою основную функцию врядли у кого-то появится интерес обновлять железо ради нового ПО.


    1. RPG18
      23.02.2016 17:15

      Понимаю весь геморрой с NAS'ами и их тулчейнами. Все больше и больше склоняюсь к сборке своего тулчейна. Таким путем пошли в Symform, там собрали свой тулчейн, и своим тулченом собрали Mono.

      Со своим тулчейном можно ограничится x86-64(пока еще не было потребности в x86), ARMv5, PowerPC


      1. slonm
        23.02.2016 17:34

        Собрать свой тулчейн дело не такое уж и хитрое. Но в этом случае нужно либо делать полностью статическую компоновку итогового исполнимого файла, что его сильно раздувает, либо использовать GLIBC и другие базовые зависимости, заведомо настолько старых версий, чтобы быть совместимым со всем ныне живущим. Кроме того некоторые NAS'ы добросовестно делают обновления безопасности. Обновления таких зависимостей как OpenSSL и самой GLIBC (вдруг кто-то из поставщиков привидения испугается и обновит) лучше бы делать независимо от обновления нашей программы, а не статически компоновать.


        1. RPG18
          23.02.2016 18:12

          Я в курсе этого геморроя. Для устройств QNAP у меня нет статической компоновки и я кладу весь необходимый runtime. В итоге не сильно и раздуто получилось. При этому смотрю на производителей стороннего софта для NAS, люди просто кладут все необходимое. Некоторые тащат свой Perl/Python и не парятся. Ограничение на размер всегда можно обойти выкачиванием бинарей при установке, как например сделали ребята из Symform. Их пакет мало весит, но при установке выкачивает все из сети.


  1. Ivan_83
    22.02.2016 20:18
    -5

    «Как в своё время должен был быть разрушен Карфаген, так сегодня должны умереть все библиотеки и приложения, до сих пор не перешедшие на С++11.» — Те вот у нас есть микроскоп (С++), мы им умеем работать, поэтому гвозди им будем забивать и дрова рубить.
    Ачо, это же модный современный высокотехнологичный инструмент.
    Ещё им говно и говно убирать можно с лужаек.

    То что С++, а тем более 11 нужен/интересен/полезен мизерному проценту разработчиков это фигня.

    Скажу за себя.
    Я не перехожу на С++ по следующим причнам:
    1. у меня много кода на с, который можно собрать без модификаций/с мизерными модификациями на любой платформе, в юзерспейсе и ядре
    2. я не знаю плюсов и пройдёт пара лет прежде чем я освою все фичи
    3. плюсы это юзерспейс
    4. с++11 это ограниченное число платформ
    5. с++ если что, то немного сложнее интегрируется с другими языками в виде тех же либ

    Те для меня переход на С++ выглядит так: я теряю время, теряю возможность встраивать код в ядро, теряю поддерживаемые платформы.
    Что я получаю?
    Ну типа код меньше и читабельнее — спасибо, не интересно, следующий. (для руст, го и прочих те же аргументы)

    «Им просто не место в современном мире. Со слезами на глазах я читаю посты вроде этого о библиотеках, которые «чтобы расширить аудиторию — стараются обходиться без C++11».»
    Подключая чужую либу ты:
    1. Теряешь полный контроль на проектом
    2. К своим багам добавляешь чужие
    3. Вынужден подстраиваться под чужие архитектурные решения
    4. Вынужден мирится с особенностями реализации
    5. Во время очередного обновления АПИ может сломаться/устареть и потребуется правка
    6. Менее очевидные вещи типа дллхэл — когда тебе нужна строго одна версия либы, другому другая и хз как это всё в одной системе должно жить вместе

    Профиты:
    1. херак-херак и продакшн — типа не надо самому думать, понимать и разбираться, достаточно скопипастить примеры
    2. скорость запуска проекта (в смысле когда он начнёт делать что то полезное, а не время старта приложения)

    И так для каждой либы нужно взвешивать за и против.
    Для меня лично против всегда много больше, и внешние либы я использую только когда совсем припирает что называется, и когда время которое я в теории затрачу на интеграцию либы и поддержку на протяжении всей жизни программы будет меньше, чем время за которое я слеплю аналогичный функционал.
    Из примеров того на что я согласен в виде либ: кодеки(+там куча всего около) типа ффмпега, вероятно какие то сложные либы типа OpenCV и подобных где много собственных сложных алгоритмов реализовано, архиваторы.
    Всякие бусты, либев, либевент — нафиг, давно есть своё.
    Крипта тема отдельная. TLS я сам не хочу и лучше возьму LibreSSL, а вот одиночные алгоритмы проще имплементировать (если их ещё нет у меня :) ) и встроить.

    Резюмирая: выбор С++ для вещей которые должны получить максимальное распространение это заведомый фейл, и коммент slonm тому подтверждение.


  1. defuz
    23.02.2016 06:44

    Ну вот и скажите мне, любители выражений вроде «С++ слишком сложен и избыточен» — чем это хуже Java или Python?
    Необходимостью вручную управлять использованием памяти на свой страх и риск?


    1. GamePad64
      23.02.2016 17:52
      +1

      В C++11 уже памятью управлять не надо почти. Есть умные указатели, есть RAII.
      В своём достаточно большом проекте использовал new/delete от силы пару раз, остальное make_shared и make_unique (да, это из C++14).