Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Руслан Черемин — Тестирование конфигурации для Java-разработчиков: практический опыт

Руслан Черемин — Тестирование конфигурации для Java-разработчиков: практический опыт

На одном из Heisenbug Андрей Сатарин рассказывал, как можно покрывать тестами не только код, но и конфигурацию.

С его подачи последние 3 года в Deutsche Bank используют этот подход в своих проектах.
И да, тесты для конфигурации существенно уменьшают количество ошибок при развертывании приложения.
Но писать и поддерживать такие тесты может быть непросто, это новая и непривычная задача для тестирования, со своими тонкостями.

Руслан поделится своим опытом: с чего начинать, какие есть подводные камни,
какие решения оказались удобными и полезными при разработке таких тестов на Java.

3fc5b5eb32bd3b48d7810fd67b37f9a1?s=128

Moscow JUG

June 07, 2018
Tweet

Transcript

  1. Russia Development Centre Deutsche Bank Тестирование конфигурации для java-разработчиков Черемин

    Руслан cheremin@gmail.com
  2. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Safe harbour Данный

    материал не является предложением или предоставлением какой- либо услуги. Данный материал предназначен исключительно для информационных и иллюстративных целей и не предназначен для распространения в рекламных целях. Любой анализ третьих сторон не предполагает какого-либо одобрения или рекомендации. Мнения, выраженные в данном материале, являются актуальными на текущий момент, появляются только в этом материале и могут быть изменены без предварительного уведомления. Эта информация предоставляется с пониманием того, что в отношении материала, предоставленного здесь, вы будете принимать самостоятельное решение в отношении любых действий в связи с настоящим материалом, и это решение является основанным на вашем собственном суждении, и что вы способны понять и оценить последствия этих действий. ООО "Дойче Банк Техцентр" не несет никакой ответственности за любые убытки любого рода, относящихся к этому материалу. !2
  3. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Простыми словами -Все

    персонажи выдуманы -Пользуйтесь с осторожностью -Похороны за свой счет !3
  4. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Что такое “тестирование

    конфигурации”? !4
  5. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Есть важный код

    src/main/java/com/db/app/ GainMoney.java … !5
  6. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Для важного кода

    есть тесты src/main/java/com/db/app/ GainMoney.java src/test/java/com/db/app/ GainMoneyTest.java … !6
  7. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Есть конфигурация src/main/java/com/db/app/

    GainMoney.java src/test/java/com/db/app/ GainMoneyTest.java src/main/resources/ app-lab.properties app-uat.properties app-prod.properties !7
  8. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Почему ее важно

    тестировать? - ошибки конфигурации вредят бизнесу так же, как и ошибки кода - конфигурация не проверяется компилятором/IDE - а часто недостаточно проверяется и при использовании - UAT не гарантирует корректности PROD- конфигурации - и даже в PROD многие ошибки проявляются не сразу !8
  9. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Тесты для конфигурации:

    This page was intentionally left blank !9
  10. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre !10

  11. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Общий план -

    Что можно успеть до обеда в понедельник - простые полезные примеры, “так можно делать” - Понедельник, два года спустя: - где и как можно сделать лучше - Поддержка для рефакторинга конфигурации - как добиться плотного покрытия - программная модель конфигурации !11
  12. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre “Так делать —

    можно” !12
  13. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre JMX ports conflict

    issue env-uat.properties: configchanger.jmx.port = 18501 … spotserver.jmx.port = 18503 … republisher.jmx.port = 18504 … ratefan.jmx.port = 18505 … newservice.jmx.port = 18505 !13
  14. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre JMX ports conflict

    issue @Test
 public void jmxPortsAreUnique() {
 File configsFolder = new File(CONFIGS_FOLDER);
 
 for( File configFile : configsFolder.listFiles(…) ) {
 Properties config = load( configFile );
 List<String> ports = filterValues( config, name -> name.contains( "jmx.port" ) );
 assertThat( ports, itemsAreUnique() ); }
 }
 !14
  15. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre JMX ports conflict

    issue @Test
 public void jmxPortsAreUnique() {
 File configsFolder = new File(CONFIGS_FOLDER);
 
 for( File configFile : configsFolder.listFiles(…) ) {
 Properties config = load( configFile );
 List<String> ports = filterValues( config, name -> name.contains( "jmx.port" ) );
 assertThat( ports, itemsAreUnique() ); }
 }
 !15 WTFА что, так можно было?
  16. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Ports are all

    valid @Test
 public void portsAreValid() {
 File configsFolder = new File(CONFIGS_FOLDER);
 for( File configFile : configsFolder.listFiles(…) ) {
 Properties config = load( configFile );
 List<String> ports = filterValues( config, name -> name.endsWith( ".port" ) );
 assertThat( ports, eachItem(intValue(is(validNetworkPort()))) ); }
 } !16
  17. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Что проверяем здесь?

    @Test
 public void obscured() {
 File configsFolder = new File(CONFIGS_FOLDER);
 for( File configFile : configsFolder.listFiles(…) ) { if( configFile.getName().contains( “prod" ) ) {
 Properties config = load( configFile );
 List<String> passwords = filterValues( config, name -> name.contains( ".passw" ) ); assertThat( passwords, everyItem(is(placeholder()))); } }
 } !17
  18. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Что проверяем здесь?

    @Test
 public void obscured() {
 File configsFolder = new File(CONFIGS_FOLDER);
 for( File configFile : configsFolder.listFiles(…) ) { if( configFile.getName().contains( “prod" ) ) {
 Properties config = load( configFile );
 List<String> passwords = filterValues( config, name -> name.contains( ".passw" ) ); assertThat( passwords, everyItem(is(placeholder()))); } }
 } !18 jdbc.password = ${very.secret.password}
  19. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre — А что

    считать конфигурацией? !19
  20. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre — А что

    считать конфигурацией? — Да все, что хочется !20
  21. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Пример: EOF для

    SQL-plus 14365-insert-important-data.sql: … INSERT INTO … VALUES … … / <<EOF>> must be on new line! !21
  22. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Пример: EOF для

    SQL-plus @RunWith( Theories.class )
 public class SQLPlusScriptsTest {
 @Theory
 public void scriptHasCorrectEnding( File sqlFile ) {
 String sql = Files.toString( sqlFile, US_ASCII );
 
 assertThat( sql, endsWith( "\n" ) );
 assertThat( sql.trim(), endsWith( "/" ) );
 } 
 @DataPoints
 public static File[] sqlScripts() {
 return SQL_PATCHES_FOLDER.listFiles( ( dir, name ) -> 
 name.endsWith( ".sql" )
 );
 }
 } !22
  23. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Пример: crontabs lab-sg.crontab:

    0 12 * * 0 …/startAllServices.sh 0 10 * * 6 …/stopAllServices.sh !23
  24. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Пример: crontabs lab-sg.crontab:

    0 12 * * 0 …/startAllServices.sh 0 10 * * 6 …/stopAllServices.sh <<EOF>> !24
  25. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Пример: crontabs lab-sg.crontab:

    0 12 * * 0 …/startAllServices.sh 0 10 * * 6 …/stopAllServices.sh <<EOF>> !25 @Theory
 schedulesAreValid(File crontab) @Theory
 EOF_isOnNewLine(File crontab)
  26. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Пример: shell-скрипты env-uat-us.sh:

    … JAVA_HOME=… LD_LIBRARY_PATH=… AGGRESSIVE=1 … !26
  27. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Пример: shell-скрипты env-uat-us.sh:

    … JAVA_HOME=… LD_LIBRARY_PATH=… AGGRESSIVE=1 … !27 @Theory
 javaHomeIsDefined(File envFile) @Theory
 NDALib_IsInLD_LIBRARY_PATH(File envFile)
  28. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Итого - Тесты

    конфигурации поначалу смущают - Потом пишутся легко и весело - Много easy wins/low hanging fruits - Уменьшают затраты на обнаружение и исправление ошибок конфигурации - Дарят вторую молодость !28
  29. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Per-file tests .../resources

    app-lab-uk.properties app-uat-us.properties app-prod-uk.properties !29 @Theory
 forEachFileSomethingIsTrue(File envFile)
  30. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Сценарии посложнее -

    UI-приложение соединяется с сервером своего environment-а - Все сервисы одного environment-а соединяются с одним и тем же management-сервером - Все сервисы одного environment-а используют одну и ту же базу данных !30
  31. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre !31 pricing-server: lab-uk.properties

    uat-us.properties prod-uk.properties client-api: client-lab-uk.properties client-uat-us.properties client-prod-uk.properties control-server: lab-uk.properties uat-us.properties prod-uk.properties rate-fan: lab-uk.properties uat-us.properties prod-uk.properties risk-management: arm-lab-uk.properties arm-uat-us.properties arm-prod-uk.properties vdc: vdc-lab-uk.properties vdc-uat-us.properties vdc-prod-uk.properties monitoring-bridge: mon-lab-uk.properties mon-uat-us.properties mon-prod-uk.properties dashboard: lab-uk.properties uat-us.properties prod-uk.properties common serverside: common-lab-uk.properties common-uat-us.properties common-prod-uk.properties
  32. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Как было бы

    удобнее? @Theory
 public void eachEnvironmentIsXXX( Environment environment ) {
 for( Server server : environment.servers() ) {
 for( Service service : server.services() ) {
 Properties config = buildConfigFor(
 environment,
 server,
 service
 );
 //… check {something} about config
 }
 }
 } !32
  33. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Deployment layout !33

    environment server#1 service#1 config#1 config#2 config#3 server#2 server#3 service#2 service#3
  34. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Deployment layout !34

    environment server#1 service#1 config#1 config#2 config#3 server#2 server#3 service#2 service#3 +rack, +region, +data center…
  35. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Где взять deployment

    layout? 1. Сначала — захардкодить 2. Потом (может быть когда-нибудь) заинтегрироваться с существующей системой IM !35
  36. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Deployment layout public

    enum Environment {
 PROD( PROD_UK_PRIMARY, PROD_UK_BACKUP,
 PROD_US_PRIMARY, PROD_US_BACKUP, 
 PROD_SG_PRIMARY, PROD_SG_BACKUP ) …
 
 public Server[] servers() {…}
 }
 
 public enum Server {
 PROD_UK_PRIMARY(“rflx-ldn-1"), PROD_UK_BACKUP("rflx-ldn-2"),
 PROD_US_PRIMARY(“rflx-nyc-1"), PROD_US_BACKUP("rflx-nyc-2"),
 PROD_SG_PRIMARY(“rflx-sng-1"), PROD_SG_BACKUP("rflx-sng-2"),
 
 public Service[] services() {…}
 } !36
  37. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Deployment layout public

    enum Environment {
 PROD( PROD_UK_PRIMARY, PROD_UK_BACKUP,
 PROD_US_PRIMARY, PROD_US_BACKUP, 
 PROD_SG_PRIMARY, PROD_SG_BACKUP ) …
 
 public Server[] servers() {…}
 }
 
 public enum Server {
 PROD_UK_PRIMARY(“rflx-ldn-1"), PROD_UK_BACKUP("rflx-ldn-2"),
 PROD_US_PRIMARY(“rflx-nyc-1"), PROD_US_BACKUP("rflx-nyc-2"),
 PROD_SG_PRIMARY(“rflx-sng-1"), PROD_SG_BACKUP("rflx-sng-2"),
 
 public Service[] services() {…}
 } !37 startServices-prod-uk-bcp.sh: … $START control-server $START pricing-server $START rate-fan …
  38. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Приятные бонусы на

    сдачу public enum Environment {
 PROD( PROD_UK_PRIMARY, PROD_UK_BACKUP,
 PROD_US_PRIMARY, PROD_US_BACKUP, 
 PROD_SG_PRIMARY, PROD_SG_BACKUP ) …
 
 public Server[] servers() {…}
 }
 
 public enum Server {
 PROD_UK_PRIMARY(“rflx-ldn-1"), PROD_UK_BACKUP("rflx-ldn-2"),
 PROD_US_PRIMARY(“rflx-nyc-1"), PROD_US_BACKUP("rflx-nyc-2"),
 PROD_SG_PRIMARY(“rflx-sng-1"), PROD_SG_BACKUP("rflx-sng-2"),
 
 public Service[] services() {…}
 } !38 @Theory eachEnvironmentHasCoreServices(Environment) @Theory criticalServicesHaveBackupInstances(Environment)
  39. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Поддерживаем актуальность public

    class HardCodedLayoutConsistencyTest {
 @Theory
 eachHardCodedEnvironmentHasConfigFiles(Environment env){ … }
 
 @Theory
 eachConfigFileHasHardCodedEnvironment(File configFile){ … }
 }
 !39
  40. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Итого: deployment layout

    - Упрощает написание сложных тестов - Делает их нагляднее и читабельнее - В ходе его создания выясняются многие сакральные знания о deployment-е - Хорошо дополняет документацию (особенно если ее нет) !40
  41. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Проблема assertThat( filterProperties(

    properties, name -> name.contains(".port") ), eachItem(intValue(is(validNetworkPort()))) ); —> Error: 123456 is not valid network port !41
  42. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Нет контекста ошибки

    assertThat( filterProperties( properties, name -> name.contains(".port") ), eachItem(intValue(is(validNetworkPort()))) ); —> Error: 123456 is not valid network port !42 file, propertyName?.. Чинить — где?
  43. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre “Декларативные” тесты SELECT

    environment, server, component, configLocation, propertyName, propertyValue FROM configuration(environment, server, component) WHERE propertyName like “%.port%” and propertyValue is not validNetworkPort() !43
  44. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre “Декларативные” тесты SELECT

    environment, server, component, configLocation, propertyName, propertyValue FROM configuration(environment, server, component) WHERE propertyName like “%.port%” and propertyValue is not validNetworkPort() !44
  45. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre “Декларативные” тесты SELECT

    environment, server, component, configLocation, propertyName, propertyValue FROM configuration(environment, server, component) WHERE propertyName like “%.port%” and propertyValue is not validNetworkPort() !45
  46. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre “Декларативные” тесты SELECT

    environment, server, component, configLocation, propertyName, propertyValue FROM configuration(environment, server, component) WHERE propertyName like “%.port%” and propertyValue is not validNetworkPort() !46
  47. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Use Streams, Luke

    ValueWithContext[] incorrectPorts =
 flattenedProperties( environment )
 .filter( propertyNameContains( ".port" ) )
 .filter(
 !isInteger( propertyValue )
 || !isValidNetworkPort( propertyValue )
 )
 .toArray(); 
 assertThat( incorrectPorts, emptyArray() ); !47
  48. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Use Streams, Luke

    ValueWithContext[] incorrectPorts =
 flattenedProperties( environment )
 .filter( propertyNameContains( ".port" ) )
 .filter(
 !isInteger( propertyValue )
 || !isValidNetworkPort( propertyValue )
 )
 .toArray(); 
 assertThat( incorrectPorts, emptyArray() ); !48 ValueWithContext { environment, server, service, configPath, propertyName, propertyValue }
  49. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Use Streams, Luke

    ValueWithContext[] incorrectPorts =
 flattenedProperties( environment )
 .filter( propertyNameContains( ".port" ) )
 .filter(
 !isInteger( propertyValue )
 || !isValidNetworkPort( propertyValue )
 )
 .toArray(); 
 assertThat( incorrectPorts, emptyArray() ); !49
  50. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Use Streams, Luke

    ValueWithContext[] incorrectPorts =
 flattenedProperties( environment )
 .filter( propertyNameContains( ".port" ) )
 .filter(
 !isInteger( propertyValue )
 || !isValidNetworkPort( propertyValue )
 )
 .toArray(); 
 assertThat( incorrectPorts, emptyArray() ); !50 Expected: Empty array got: [<PROD/PROD_UK/vdc:vdc-prod—uk.properties:vdc.jmx.port = 123456>]
  51. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Тестирование как поддержка

    для рефакторинга !51
  52. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre С чем имеем

    дело xxx.address=${${hostname}.bus.address} … xxx.name=${Seq-${hostname}}.${${hostname}.shortname} … xxx.mcast=${Seq.udp.${seq.${hostname}.order}.recv.mcast} # и так до самого низа… !52
  53. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre С чем имеем

    дело apps/sequencer.properties include logging.properties include logging-${OS}.properties include network.properties include admin.properties include ports.properties include env/${env}.properties include hosts/${host-1}.properties include hosts/${host-2}.properties include hosts/${host-3}.properties !53
  54. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Конфигурация 1 сервиса

    10 include-ов глубиной 3 ~450 параметров всего Нужны этому сервису только 10-15% !54 * Автор — гений, но его с нами больше нет
  55. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Хочется !55 -

    Конфигурацию отрефакторить и упростить
  56. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Хочется !56 -

    Конфигурацию отрефакторить и упростить - После рефакторинга каждый сервис имеет все необходимые параметры
  57. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Хочется !57 -

    Конфигурацию отрефакторить и упростить - После рефакторинга каждый сервис имеет все необходимые параметры - …и не имеет лишних параметров
  58. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Хочется !58 -

    Конфигурацию отрефакторить и упростить - После рефакторинга каждый сервис имеет все необходимые параметры - …и не имеет лишних параметров - Сервисы успешно соединяются в кластер
  59. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Хочется - Конфигурацию

    отрефакторить и упростить - После рефакторинга каждый сервис имеет все необходимые параметры - …и не имеет лишних параметров - Сервисы успешно соединяются в кластер - (…но неизвестно, соединяются ли они сейчас!) !59
  60. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Программа-минимум !60 -

    Проверить корректность каждого параметра каждого сервиса в изоляции - Проверить ключевые взаимосвязи между параметрами
  61. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Нужна модель конфигурации

    Service1: parameters in use { TCP.Address: string( IP | hostname ) TCP.Port: int( 2000 … 32000, not 2378, 13654 ) UDP.Multicast: string( e.g. IP class D ) UDP.TTL: int( >0 ), default 4 LOG.DIR: string( path ) default “./logs” TMP.DIR: string( path ) default ${java.io.tmp} … } !61
  62. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Чем она нам

    поможет? !62 Configuration( Full ){ ~450 параметров } Model( Service1 ){ 26 описаний параметров } Configuration( Service1 ){ 26 параметров + типизированные + значения по-умолчанию + валидированные } ConfigurationException{ “TCP.Port = ${…}” is not valid network port }
  63. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Модель конфигурации в

    коде interface IConfigurationModel { @Override List<FetchedValue<?>> fetch( Configuration config ); } class FetchedValue<T> {
 public final String propertyName;
 public final T propertyValue;
 …
 } !63
  64. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Как построить такую

    модель? !64
  65. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Начнем с простого

    public class SimpleComponent { … public void configure( final Configuration conf ) {
 int port = conf.getInt( "Port", -1 );
 if( port < 0 ) throw new ConfigurationException();
 
 String ip = conf.getString( "Address", null );
 if( ip == null ) throw new ConfigurationException(); …
 } …
 } !65
  66. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Начнем с простого

    public class SimpleComponent { … public void configure( final Configuration conf ) {
 int port = conf.getInt( "Port", -1 );
 if( port < 0 ) throw new ConfigurationException();
 
 String ip = conf.getString( "Address", null );
 if( ip == null ) throw new ConfigurationException(); …
 } …
 } !66 name default value validation type
  67. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Элементарные свойства interface

    IProperty<T> {
 /* (name, defaultValue, matcher…) */ /** lookup (or use default), * convert type, * validate value against matcher */
 FetchedValue<T> fetch( Configuration config ) } class FetchedValue<T> {
 public final String propertyName;
 public final T propertyValue;
 …
 } !67
  68. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Элементарные свойства IProperty<Integer>

    PORT = intProperty("Port")
 .withDefaultValue( -1 )
 .matchedWith( validNetworkPort() );
 
 IProperty<String> ADDRESS = stringProperty("Address")
 .withDefaultValue( null )
 .matchedWith( validIPAddress() ); !68
  69. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Модель конфигурации class

    SimpleComponentModel implements IConfigurationModel{ IProperty<Integer> PORT = intProperty("Port")
 .withDefaultValue( -1 )
 .matchedWith( validNetworkPort() );
 
 IProperty<String> ADDRESS = stringProperty("Address")
 .withDefaultValue( null )
 .matchedWith( validIPAddress() ); @Override List<FetchedValue<?>> fetch( Configuration config ){ return asList( PORT.fetch( config ), ADDRESS.fetch( config ) ); } } !69
  70. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Задача решена? Да,

    но: - Модель громоздкая (20-60 полей) - Сложно поддерживать: нет прямого соответствия между кодом и его моделью - Что делать с ветвлениями и полиморфизмом? !70
  71. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Задача решена? Да,

    но: - Модель громоздкая (20-60 полей) - Сложно поддерживать: нет прямого соответствия между кодом и его моделью - Что делать с ветвлениями и полиморфизмом? Похоже, нужна декомпозиция - конфигурируемый класс => ConfigurationModel !71
  72. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Модели комбинируемы class

    ComplexComponentModel implements IConfigurationModel{ final IConfigurationModel subComponent1 = …; final IConfigurationModel subComponent2 = …;
 
 @Override List<FetchedValue<?>> fetch(Configuration config){ return union( subComponent1.fetch(config), subComponent2.fetch(config) ); } } !72
  73. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Что получается -

    Иерархия объектов в сервисе соответствует (+/-) иерархии ConfigurationModels - Есть (почти) прямое соответствие между классом и его моделью !73
  74. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Чего это стоило

    - 12 сервисов - 70 конфигурируемых классов 
 => 70 ConfigurationModels (~60 тривиальны) - 2 человеко-недели !74
  75. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Как с этой

    хренью взлетать @Theory
 public void propertiesValidInIsolation( Environment environment ){
 for( Server server : environment.servers() ) {
 for( Service service : server.services() ) {
 Configuration config = fetchConfigFor(
 environment,
 server,
 service
 );
 IConfigurationModel model = service.configurationModel();
 model.fetch( config );
 }
 }
 } !75
  76. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Взаимозависимости сервисов !76

    Большинство зависимостей это сетевые связи: “Сервис А слушает именно тот адрес, на который отправляет пакеты сервис Б”
  77. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Network endpoints IProperty<IEndpoint>

    TCP_REQUEST = outcomingTCP( // (+matchers, +default values) “TCP.Request.Address”, “TCP.Request.Port” ); class OutcomingTCPEndpoint implements IEndpoint { //(localInterface, localAddress, multicastGroup, port) @Override boolean matches( IEndpoint other); } !77
  78. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Проверка связности кластера

    ValueWithContext[] allEndpoints = flattenedConfigurationValues(environment) .filter( valueIsEndpoint() ) .toArray(); ValueWithContext[] unpairedEndpoints = Arrays.stream( allEndpoints ) .filter( e -> !hasMatchedEndpoint(e, allEndpoints) ) .toArray(); assertThat( unpairedEndpoints, emptyArray() ); !78
  79. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Швейцарский нож на

    сдачу… ConfigurationModel позволяет: - Конвертировать в другой формат - Выполнять запросы к конфигурации
 (“все udp-порты, используемые сервисами на данном сервере”) - Экспортировать сетевые связи между сервисами в виде диаграммы !79
  80. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Выводы - Тестировать

    конфигурацию важно - Поле непаханое, а порог входа низкий - От самых простых тестов уже много пользы - Тестировать сложные взаимосвязи тоже возможно - На простых тестах возможности не заканчиваются - Можно решать и сложные задачи - Получая на сдачу полезные инструменты !80
  81. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Что дальше? !81

    Дальше пока не придумал, импровизируй
  82. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Что плохо !82

    “...подход разочаровал. Вместо систематического контроля конфигов мы наделали еще кучу кода на каждый чих“ *Из отзывов с Гейзенбага 2018
  83. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Модель конфигурации Service1

    { TCP.Address: string( IP | hostname ) TCP.Port: int( 2000 … 32000, not 2378, 13654 ) UDP.Multicast: string( e.g. IP class D ) UDP.TTL: int( >0 ), default 4 LOG.DIR: string( path ) default “./logs” TMP.DIR: string( path ) default ${java.io.tmp} … } !83
  84. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Модель конфигурации Service1

    { TCP.Address: string( IP | hostname ) TCP.Port: int( 2000 … 32000, not 2378, 13654 ) UDP.Multicast: string( e.g. IP class D ) UDP.TTL: int( >0 ), default 4 LOG.DIR: string( path ) default “./logs” TMP.DIR: string( path ) default ${java.io.tmp} … } !84 Создана для тестирования конфигурации. А почему не для самой конфигурации?
  85. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Что могло бы

    быть лучше? !85 - Валидаторы конфигурации - Например XML/JSON-schema - Описывают валидацию конфигурации с помощью кода - Может быть сразу конфигурировать приложение с помощью кода?
  86. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Configuration as code

    !86 - Я не имею в виду bash code - Я не имею в виду “что-то, что лежит в VCS” - Я имею в виду код: - Строго типизированный, компилируемый - С поддержкой IDE - С возможностью использовать доменные объекты основного приложения
  87. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Configuration as code

    !87 - Я не имею в виду bash/JavaScript - Я не имею в виду “что-то, что лежит в VCS” - Я имею в виду код: - Строго типизированный, компилируемый - С поддержкой IDE - С возможностью использовать доменные объекты основного приложения - То есть Java/Kotlin :)
  88. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Идея бродит в

    воздухе !88 - “Автоматизация экспериментов с Kotlin DSL”
 /Aleksandr Tarasov - “Kotlin DSL: теория и практика” 
 /Иван Осипов
  89. Руслан Черемин 18/05/2018 Deutsche Bank Technology Centre Для жалоб и

    предложений Руслан Черемин cheremin@gmail.com, ruslan.cheremin@db.com blog: dev.cheremin.info !89