Недели три назад мне пришлось основательно взяться за разработку под iOS и (куда же тут без него) Objective-C. Из всего опыта была только прочитанная летом наполовину книжка по сабжу, пару раз открытый XCode, и попытка собрать ScummVM для iPad из сорцов. Надо сказать что идея овладеть obj-c появилась у меня в голове ещё больше года назад (с появлением iPad), а после переезда на мак этому не оставалось уже никаких препятствий.
Этим и следующим постом (который, надеюсь, будет вот совсем скоро) я хочу познакомить вас с замечательной ORM-библиотекой для scala, которую сейчас использую в одном из рабочих проектов. Не надо пугаться аббревиатуры ORM: возожности маппинга к объектам здесь принципиально реализованы на совсем базовом уровне, а вот симпатичный DSL для запросов стоит того, чтобы на него взглянуть.
В предыдущем посте я рассказывал об исполняемых конфигах ostrich и не упомянул особых их достоинств, кроме type safety и удобства работы с настройками со стороны приложения. На прошлой неделе я наткнулся на ещё один хороший юзкейс для таких конфигов.
Предположим, мы развёртываем наше приложение в разных окружениях (dev, test, prod), и в некоторых из них было бы здорово иметь в базе некоторые начальные данные для упрощения, например, процесса тестирования. Есть несколько достаточно тривиальных, но не очень удобных способов решения этой проблемы, особенно если загрузка этих начальных данных - часть автоматического развёртывания через CI-сервер. В скриптовых языках, где исполняемые файлы настроек - норма, такие данные (называемые fixtures) часто делаются частью конфига.
С ostrich реализация такой штуки становится делом буквально нескольких строк кода:
Регистрируясь на coderwall (по наводке @shaman_sir) увидел замечательный пример той самой области UX, не относящейся ни к дизайну ни к юзабилити. Ничего супер-пупер-грандиозного, просто под полем для ввода электропочты в форме регистрации - небольшое примечание.
No spam, always private. We respect the sanctity of your email and share your dislike for spam and unnecessarily frequent newsletters. If you’re interested in future coderwall news, we suggest you follow us on twitter.
Не знаю как вам, а мне безумно приятно видеть сервис, уважающий моё личное информационное пространство и максимально ненавязчиво предлагающий просто подписаться на твиттер, если уж мне так хочется читать относящиеся к нему новости. По-моему, это гораздо лучше обычной галочки “присылать/не присылать новости”: в случае с твиттером заранее знаешь, что отписаться при желании можно будет в любой момент и одной кнопкой. Я уже не говорю о случаях, когда пользователю вообще не оставляют никакого выбора.
В общем, я кажется нашёл главный секрет хорошего UX: “Уважай своего пользователя”. Просто ведь, правда?
В предыдущем посте я мельком упомянул библиотеку ostrich в качестве инструмента для загрузки конфигов, в этом же постараюсь сделать более подробный обзор. Итак, ostrich - это внутренняя библиотека от разработчиков “твиттера”, используемая в его компонентах. Несмотря на фичастость и навороченность, местами довольно-таки заметен её стиль, как библиотеки написанной в первую очередь “для себя”.
Оstrich выполняет следующие задачи:
загрузка и парсинг typesafe-конфигов (проще, говоря, конфигов в виде scala-кода)
сбор рантайм-статистики и разнообразных метрик
управление сервисами внутри процесса (запуск/остановка)
предоставление простенького, но расширяемого администраторского интерфейса через http/socket
В этом посте я опишу работу с конфигами, а остальные возможности попробую раскрыть в последующих постах.
Страус и конфиги
Итак, ostrich предлагает использовать в качестве конфигов не просто структурированные текстовые файлы (json/xml/properties), а scala-код. Такой подход требует компиляции конфига при загрузке, но имеет ряд серьёзных преимуществ:
типизация. Отпадает необходимость проверять и приводить значения из конфиг-файла к нужным типам
возможность писать произвольный код в конфигах, помимо простых значений. Например, использовать текущую дату в произвольном формате в имени лог-файла, или получить значения каких-то параметров из базы или стороннего сервиса. По сути, конфиг-файл становится тем, что называется extension point.
становятся ненужными отдельные классы, инкапсулирующие конфигурацию различных компонентов программы, или, по крайней мере, упрощается их создание.
Для начала, нам нужно создать родительский класс для настроек. Чаще всего удобно сделать его же конфигом по умолчанию. Предположим, мы хотим сконфигурировать небольшое серверное приложение. Для этого ostrich предоставляет класс ServerConfig (являющийся наследником Config, предоставляющего базовые функции такие как компиляция, валидация и обязательные/необязательные поля).
12345678910111213141516171819202122232425
importcom.twitter.ostrich.admin._importcom.twitter.ostrich.admin.config._importcom.twitter.logging.config._importcom.twitter.logging.LevelclassMyServerConfigextendsServerConfig[MyServer]{//ServerConfig, в отличие от просто Config, должен определить //метод Apply, для создания инстанса сервераdefapply(runtime:RuntimeEnvironment)={newMyServer(this)}varport=1234varworkersNum=10varbaseUrl="http://localhost:%s"formatportvardbConfig=newDBConfig}caseclassDBConfig(vardriver="org.h2.Driver"varuri="jdbc:h2:mem:"varcreate=true)
Надо заметить, что мы используем в этом конфиге изменяемые переменные (var). В Scala это зачастую является признаком недостаточно функционального (декларативного) стиля, однако в данном случае это позволит писать лаконичные конфиги, выставляя значение полей простым присваиванием. Кроме того, этот же стиль используется при объявлении уже имеющихся в ServerConfig значений.
Итак, теперь наш конфиг для, например, тестового сервера, может выглядеть так:
Вполне лаконично, и ничуть не хуже .properties и уж тем более XML-файла. Можем сохранить его под именем test.scala, тогда наш сервер мы сможем запускать как
1
java -jar my-server.jar -f ./test.scala
Загрузка
Теперь, наш файл настроек надо загрузить и использовать по назначению. Делать это логичнее всего поближе к точке входа, например в main. Путь к файлу можно передать с параметром -f при запуске приложения, а если его нет, то ostrich попытается найти его сам. Определение местоположения конфиг-файла - это то самое место, где становится видно, что проект делался для себя: ostrich ищет его в довольно специфических местах, пытаясь сначала выяснить имя jar-файла. Поэтому, проще всего всегда передавать путь к конфигу через -f плюс явно указать конфиг по умолчанию. Поскольку конфиг по умолчанию является обычным scala-классом, можно просто создать его инстанс в коде. Вот как выглядит загрузка и использование конфига у меня:
123456789101112131415161718192021
objectMyServer{defmain(args:Array[String]){valruntime=RuntimeEnvironment(this,args)valserver=if(runtime.configFile.exists)//если ostrich нашёл конфиг-файл runtime.loadRuntimeConfig[Server]()else//default(newMyServerConfig)()(runtime)server.start()}}classMyServer(valconfig:MyServerConfig){valdb=newDB(config.dbConfig)defstart(){//...}}
Обратите внимание на строчку (new MyServerConfig)()(runtime): сначала мы вызываем метод apply без параметров, а потом вызываем полученную функцию с параметром типа RuntimeEnvironment. При необходимости, этот способ можно расширить для загрузки различных конфигов по умолчанию, например в зависимости от run.mode в lift.
Я не использовал возможность ostrich объявлять поля как обязательные/опциональные, но промолчать о ней будет, наверное, неправильно. В приведённой к классу Config документации всё довольно просто:
В вышеприведённом примере мы вынесли настройку базы данных в отдельный класс DBConfig. Иногда, со стороны приложения бывает удобно вынести конфигурацию в отдельный класс, но усложнять структуру конфиг-файла не хочется. Хорошим примером является случай, когда конфигурируемый компонент находится в другом (под)проекте. В этом случае, конфиг можно объявить трейтом и подмешать его в основной конфиг
Представим, что мы хотим добавить в наш север небольшой внутренний почтовый сервис для отсылки писем пользователям.
В нашем конфиг-файле (test.scala) эти поля будут работать точно так же как и все остальные. При инстанциировании менеджера можно просто передать ему общий конфиг:
Также, в отдельный трейт можно вынести готовые значения для настроек если они представляют собой что-то более сложное чем простые значения.
Вот собственно и всё что я хотел рассказать об использовании ostrich для конфигурирования. Я намеренно не стал рассматривать имеющиеся в ServerConfig поля для настройки логгинга, статистики и админки. Мы вернёмся к ним попозже, когда я буду рассказывать о соответствующих фичах “страуса”.
Для Scala, как для языка довольно молодого, пока ещё нет (а может уже и не будет) единого стандарта для такой обыденной вещи как конфиг-файлы. Вариантов не так уж много и в этом посте я хочу сделать небольшой обзор тех, которые мне так или иначе довелось использовать.
.properties
Текстовые и XML файлы, хранящие пары “ключ-значение”. Вполне подходят для конфигурирования большинства проектов, а в стандартной библиотеке JRE, есть средства для работы с ними. Что касается поддержки со стороны Scala, в пакете scala.collection есть объекты JavaConverters и JavaConversions, предоставляющие implicit методы для конвертации объектов класса Properties в mutable.Map[String, String].
Эти файлы часто используются не столько для хранения конфигов, сколько для локализации. Тут нельзя промолчать об одной их особенности, о которой, наверное, знает каждый Java-программист, но которая всегда меня изумляла: в платформе, где Unicode является основной кодировкой для работы со строками, для хранения локализованных строк предлагается формат, не поддерживаюший unicode. Файлы предлагается сначала писать в unicode, а потом конвертировать в странный и уродливый формат утилитой native2ascii. Ну не отвратительно ли?
Эти же файлы использует фреймворк Lift (с расширением .props), добавляя к ним замысловатый механизм выбора нужного файла в зависимости от режима работы (run mode) и окружения.
XML и JSON
Здесь всё понятно: стандартных библиотек целая куча, и старые явовские, и специально сделанные для Scala с вкусностями типа XPath-подобных DSL. Такие конфиги имеют привычку разрастаться до неприличных размеров, а JSON ещё и не умеет удобно работать с длинными и многострочными значениями и не имеет констукции для комментариев. Впрочем, можно просто использовать для этой цели обычные поля, которые при чтении конфига будут игнорироваться.
configgy
configgy - замечательная Scala-библиотека, выполняющая сразу две задачи: работа с конфиг-файлами и удобный логгинг. Эти задачи практически никак не связаны между собой (за исключением того, что логгинг тоже надо конфигурировать), но это именно те две задачи, которые требуется решать в практически любом проекте больше сотни строк.
Configgy поддерживает несколько форматов для конфигов, как линейный, так и со вложенными блоками, типа такого:
# JDBC parameters
jdbc {
driver = "com.mysql.jdbc.Driver"
uri = "jdbc:mysql://localhost:3306/test?characterEncoding=UTF8"
limit = 5000 # Batch size for selects
}
#logging parameters, see configgy readme for details
log {
filename = "debug.log"
level = "debug"
utc = false
console = false
}
Логгинг автоматически подхватывает параметры из секции log при чтении конфига, что очень удобно - вся обычная рутина с конфигурированием сводится практически к одной строчке.
Ostrich
Когда мы собрались прицепить configgy к одному из очередных наших проектов, то обнаружили (на страничке проекта на github), что проект теперь deprecated (хотя и будет пока поддерживаться в каком-то виде) и вместо него предлагается использовать Scala-библиотеки от twitter. Для логгинга - util-logging из набора Util, который помимо лог-файлов поддерживает Scribe и Syslog, для конфигурирования - ostrich.
Ostrich - это довольно развесистая библиотека, предназначенная для
конфигурирования
сбора статистики
запуска/остановки приложения и его сервисов
предоставления админского интерфейса через HTTP или просто сокет
Главная особенность конфиг-файлов ostrich - они являются обычными Scala-классами, со всемы вытекающими: они типизированы и могут наследоваться от абстрактного класса или трейта. В тех местах, где конфигурация используется, отпадает необходимость извлечения значений из конфига, проверки на то что они присутствуют, предоставления дефолтных значений и приведения к нужному типу. Обратной стороной медали можно было бы назвать необходимость компиляции конфига в рантайме, но поскольку делать это нужно не так уж часто (обычно при старте приложения), то ради красивых type-safe конфигов можно и потерпеть.
Также, ostrich предоставляет готовую заготовку ServerConfig, включающую в себя настройку логгинга и поднятие http/socket интерфейса при загрузке.
Несмотря на фичастость, мне кажется что автор configgy совершенно зря остановил развитие библиотеки: она очень проста в использовании и идеально подходит для большого диапазона проектов. Ostrich же относится к совершенно другой категории и для многих задач его навороченность выглядит излишней. Кроме того, в его использовании есть несколько подводных камней, о которых я постараюсь рассказать поподробнее в следующий раз.
Утром наткнулся в zite на замечательную статью: ”Are You a Good Programmer?”, в которой приведена интересная классификация хороших программистов. Несмотря на всю относительность таких классификаций, очень занимательно примерять это всё на себя.
Мне трудно судить, являюсь ли я на данный момент действительно хорошим программистом (и каким критериям, собственно, надо для этого соответствовать?), но определённо могу сказать что последний год развиваюсь в сторону первого типа (Philosopher). Этому явно способствует и спользование такого языка как scala, склоняющая к очень “безопасному” стилю программирования, и позволяющая хорошо управлять ограничениями в коде с помощью системы типов, при этом сохраняя красоту и лаконичность кода.
Было бы интересно узнать, к какому типу вы себя относите? Понятно что типы могут сочетаться в разных пропорциях, и вполне возможно, что можно придумать ещё несколько.
Уф, я, кажется, таки настроил octopress и, наверное, таки останусь здесь.
Несмотря на излишнюю красноглазость (девиз октопресса говорит сам за себя), эта платформа ближе всего к идеалу блогдвижка, как я его представляю.
Как дойдут руки, я попробую доконфигурить это до юзабельного состояния (“ха-ха!” - как бы говорит линуксоедный опыт) и начать уже писать посты,
благо, исчезает последняя отмазка этого не делать - отсутствие нормального блога.