{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"91a0b82d-55ee-5f65-88a2-165a2e1c5a81","frontmatter":{"category":"Engineering","title":"Functional style MDC","date":"2022-05-20","summary":"Mapped Diagnostic Context for everyone","thumbnail":null,"authorName":"Adam Seretny","authorDescription":"Adam is a Senior Software Engineer in our Katowice office.","authorAvatar":{"relativePath":"pages/functional-style-mdc/adam.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAA7CAAAOwgEVKEqAAAAEL0lEQVQ4y2XTW0yaZxgHcNOrmW6rRWjl+4BkN0uXNbqtq3ZpxrTOuRY7sUzBAwcR5KgoIKicz2cKioeIVu3qiWkr2mpBPE1AtNJVi2czd9F0SZvsYsmSLbsZziZTmzx3b37v+3/e5J8AKK3xSZGbUuTmFJUVrnUg9Y4Mfg2GRsjG5VD55em3m6C6JoTOAeicoPY2qLEDKtuhSniDZUa43AQozHCdI43NwmR/cgN98asrFzJSUeya0s8creeVNoTahlDZQJX1kPyP4VJ9fACFBamwXP766qULQH5mGvt7dPGNjOxPURhGGVLjQMiMCLkJoTCBCvMxDMSlVB+/FSUxoLMu8/HZHlONv7VxoVs9aKq5/sVHqAomQmZCNmqRMj0Yz3gstkgJFynA+DFXyMLnBFokK0P21fvO56Oul8GhQR0LkX4JFKmR9RqkRHcCW2AMPozChlXyPySWO+tI/ubGpbumaL/56YBl19+77XXl5l1LZghRYg2yXh3Pfwwnk1kQHPFsIRWFwTqEpAm7ONilezZkjw7YF3rNm6PN1Xzqe+QqlECBFKlBqeEtXFB2toAIz/pGTr/FI+TirqbOtUkeaDml6FSvva5Nzf2Yzj3HqUcKFKDkBCaxkrFl7+YVf55f0CEkGemFldnpD7WcUJu8v7Fy2eNc7FTmcHlJNAGqVoY4hhWWZCITii05nUf4srhk0sjzKtl3uAS/oXZnpHnjx6a9xz2rw67UCjaUVouqlZ7E0DjGlcFwRFh+saGBO28VullFEwZBbMAeG3G9mOsvlza8T6CDDCGi5q2XoWQWtIhynkB952ZJIZM2aeGbKgp66iuCXfrwD6bnj9xpdM6ZUibAFoN8GSjRA0rbkdgkJqSIAsNTzxSUXSSQ3Eq2iYjpImN9ck6kR+vpsQKl9HOUqhS2GDjAhiNYaYUQGUk3CZD8kiRM4Sn0DUE15Z6YPNxAi3Rp93zulnZzwnU8lMSBs0RwngRo0B12481vQ/Dlp7Mwp65kJWZk5lXxHOo6XSVuUFvt69SGh1uW5scrmtoAMjuxhJlMFwBizRGssCTmYiHoa5lUmqbZOTI5Njpyz8wjdevr5odaF4fbnz2Z23/123g4zGjp+IAlgFRLQLX9AIMqW7zJWIm009MXWAxOzAZ6Pf3TS6EBl27MpVmd6G+VVm3t7zzZiD3dWF/b3ZqIhL9zuaEyc7xFCXClFamy3Znyr26t+8Oh+/7Hg2MPppYjM6H5Dq1IiP92oLf99d9/zi5Fopsbi2trsc2YbPRhksQY7/YBBpXWu9OB7V92A6Hgo+mp8Slf8OdoOLbmbDLVcyl//PPXi99fh1ai63vbm7/u/zQ/W9vngcjMCNV/O4NKS8fMbGg91uf3DQamhgIB72LYPeYVWo3e5cjaq5fhnS3fSjS+88L2lnd2ht7dd4j/BfAXIxQ37XYMAAAAAElFTkSuQmCC","width":50,"height":50,"src":"/static/37b7f8651bdc618080e3838b6beeb39a/45876/adam.png","srcSet":"/static/37b7f8651bdc618080e3838b6beeb39a/45876/adam.png 1x,\n/static/37b7f8651bdc618080e3838b6beeb39a/eb85b/adam.png 1.5x,\n/static/37b7f8651bdc618080e3838b6beeb39a/4f71c/adam.png 2x,\n/static/37b7f8651bdc618080e3838b6beeb39a/9ec3e/adam.png 3x"}}},"headerImage":null},"html":"<h1>Logging with MDC in functional style</h1>\n<h2>Introduction</h2>\n<p>If you are already familiar with MDC (Mapped Diagnostic Context) you can go to the next section of this article. If not, please take a look at <a href=\"https://www.baeldung.com/mdc-in-log4j-2-logback\">this</a> first to get a better understanding of what MDC is.</p>\n<h2>Use case</h2>\n<p>In one of our services we had an endpoint responsible for invoking process related to car auction with a lot of logs for debugging purposes. We already had a request-id to trace it in Kibana, but we wanted to filter logs in a more convenient way, by value that we know even before invoking this endpoint which was the stock number of the car. So we decided to use MDC as it allows us to add diagnostic key-value pairs to every log in given Thread.</p>\n<h2>MDC code drawbacks</h2>\n<p>Although MDC is a great tool, it forces programmers to write clunky code due to the fact that every key-value pair that was set should be cleared, otherwise in some edge cases it could be used by another thread.</p>\n<p>Clearing can be done by invoking MDC.clear() or by MDC.remove(key). First one clears all MDC keys so we could accidentally remove keys that were set higher in stack. Second one is better then but it has to be invoked for every key. And also in both cases it should be done in finally block to be sure that no matter what happened, context was cleared.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">try {\n  MDC.put(\"key1\", \"value1\");\n  MDC.put(\"key1\", \"value2\");\n  //some process\n  log.info(\"very important log\");\n}finally {\n  MDC.remove(\"key1\");\n  MDC.remove(\"key2\");\n}</code></pre></div>\n<p>MDC also has <code class=\"language-text\">method putCloseable(key, value)</code> which returns <code class=\"language-text\">AutoClosable object</code> but it allows only one key-value pair and has to be assigned to a variable, which may look better for a single key but it is not perfect. IDE will complain about never used variable</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">try (MDC.MDCCloseable closeable = MDC.putCloseable(\"key\", \"value\")) {\n  //some process\n  log.info(\"very important log\");\n}</code></pre></div>\n<h2>Solution</h2>\n<p>To stand against MDC drawbacks, we came up with this functional style solution:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">LogContext.withLogKey(\"key1\", \"value1\")\n  .andKey(\"key2\", \"value2\")\n  .andKey(\"key3\", \"value3\")\n  .execute(() -> {\n    // some process\n    log.info(\"very important log\");\n  });</code></pre></div>\n<p>At first glance we can see that there is no <em>Try Catch</em> block anymore and we are only setting key-value pairs and not clearing them, however everything works exactly the same. So how is it done?</p>\n<p>Solution is pretty simple. It is based on a container class for storing MDC context keys mixed with <a href=\"https://www.svlada.com/step-builder-pattern/\"><em>Step Builder Pattern</em></a>. Let's take a look at implementation.</p>\n<h3>LogContextSteps</h3>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@NoArgsConstructor(access = AccessLevel.PRIVATE)\npublic  final  class  LogContextSteps {\n\n  public  interface  ContextStep {\n    ExecuteStep andKey(final String key, final Object value);\n  }\n\n  public  interface  ExecuteStep  extends  ContextStep {\n\n    &lt;T> T execute(final Supplier&lt;T> action);\n\n    default  void  execute(final Runnable action) {\n      if (action == null) {\n        throw  new IllegalArgumentException(\"Action for execution cannot be null.\");\n      }\n\n      execute(() -> {\n        action.run();\n        return  null;\n      });\n    }\n  }\n}</code></pre></div>\n<p><strong>LogContextSteps</strong> is a wrapping class for two step interfaces:</p>\n<ul>\n<li>\n<p><strong>ContextStep</strong> which contains method for adding new log key-value pair</p>\n</li>\n<li>\n<p><strong>ExecuteStep</strong> which also extends ContextStep and serves execute method responsible for invoking action that we want to wrap with MDC.</p>\n</li>\n</ul>\n<h3>LogContext</h3>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">public  class  LogContext  implements  ContextStep, ExecuteStep {\n  private  static  final MDCLogContextSetter MDC_LOG_CONTEXT_SETTER = new MDCLogContextSetter();\n\n  private  final LogContextSetter contextSetter;\n  private  final Set&lt;String> contextKeys = new HashSet&lt;>();\n\n  private  LogContext(final LogContextSetter contextSetter) {\n    this.contextSetter = contextSetter;\n  }\n\n  public  static ExecuteStep withLogKey(String key, Object value) {\n    return withContextSetter(MDC_LOG_CONTEXT_SETTER).andKey(key,value);\n  }\n\n  static ExecuteStep withContextSetter(LogContextSetter contextSetter){\n    return  new LogContext(contextSetter);\n  }\n\n  @Override\n  public ExecuteStep andKey(String key, Object value) {\n    contextSetter.put(key, value);\n    contextKeys.add(key);\n    return  this;\n  }\n\n  @Override\n  public &lt;T> T execute(final Supplier&lt;T> action) {\n    if (action == null) {\n      throw  new IllegalArgumentException(\"Action for execution cannot be null.\");\n    }\n\n    try {\n      return action.get();\n    } finally {\n      contextKeys.forEach(contextSetter::remove);\n    }\n  }\n\n  interface  LogContextSetter {\n\n    void  remove(final String key);\n\n    void  put(final String key, final Object value);\n  }\n\n  private  static  class  MDCLogContextSetter  implements  LogContextSetter {\n\n    @Override\n    public  void  remove(String key) {\n      MDC.remove(key);\n    }\n\n    @Override\n    public  void  put(String key, Object value) {\n      MDC.put(key, Objects.toString(value));\n    }\n  }\n}</code></pre></div>\n<p><strong><code class=\"language-text\">LogContext</code></strong> is our main class here. It:</p>\n<ul>\n<li>\n<p>implements interfaces from <strong><code class=\"language-text\">LogContextSteps</code></strong></p>\n</li>\n<li>\n<p>stores MDC keys in <code class=\"language-text\">Set</code></p>\n</li>\n<li>\n<p>executes action that we want to wrap with MDC context</p>\n</li>\n</ul>\n<p>For testing purposes there are some package protected classes and methods like <strong><code class=\"language-text\">LogContextSetter</code></strong> interface and its implementation <strong><code class=\"language-text\">MDCLogContextSetter</code></strong>, as it would be hard to test static methods of MDC class.</p>\n<p><strong>So, how does this flow work?</strong></p>\n<p>We begin with the static factory method <code class=\"language-text\">LogContext.withLogKey</code>(\"key1\", \"value1\") which sets up our <strong><code class=\"language-text\">MDCLogContextSetter</code></strong> and invokes the <code class=\"language-text\">andKey</code>(\"key2\", \"value2\") method for the first time. Then we can chain next invocations of <code class=\"language-text\">andKey</code> where every invocation causes two things:</p>\n<ul>\n<li>\n<p>MDC <em>key</em> is stored in <code class=\"language-text\">Set&lt;String> contextKeys</code> as only keys are needed to clear context</p>\n</li>\n<li>\n<p><strong><code class=\"language-text\">LogContextSetter::put</code></strong> method is invoked where implementation is: <code class=\"language-text\">MDC.put(key, Objects.toString(value))</code> and at that moment <em>key-value</em> is stored in MDC</p>\n</li>\n</ul>\n<p>Finally we can invoke <code class=\"language-text\">execute(()</code> -> <code class=\"language-text\">action())</code> which is classic <em>try-finally</em> block where <em>try</em> invokes our <em>action</em> and <em>finally</em> clears MDC iterating every through <code class=\"language-text\">contextKeys</code> and invoking <strong><code class=\"language-text\">LogContextSetter::remove</code></strong> where implementation id <code class=\"language-text\">MDC.remove(key)</code></p>\n<h2>Practical usage (filtering in Kibana)</h2>\n<p>Assuming that we have an ELK stack for monitoring and proper log configuration we can go to Kibana to find interesting logs via our MDC <em>key-value</em> pairs. Depending on the log configuration, <em>key</em> can be directly accessible or under the <strong>flat</strong> field (which was in our case)</p>\n<p>For <em>key-value</em> pair: <strong>stock-number - SN12356</strong> Kibana filter may look like this:</p>\n<p><img src=\"https://lh6.googleusercontent.com/F4M8xRSQWe86JzTN8G5uPIc62Y_uNhPB-_YDLlhHtTpy0PseDIX7fb8_jh37YbpVQEIJiy4gE76vP0eqkU5jjHKOeac5h3tprc52Iz2oQvv0Ros5AHC00DqsW_YZ6H_hXWdJ0yPe6evaU3Ir7Q\"></p>","fields":{"slug":"/functional-style-mdc/","tags":["auto1","engineering","java"]}}},{"node":{"id":"f3a0fb4b-eb2f-5624-a408-d63e3cee5184","frontmatter":{"category":"Coding","title":"Spring batch integration","date":"2020-02-07","summary":"Usage cases for spring-batch.","thumbnail":null,"authorName":"Artur Yolchyan","authorDescription":"Artur is a Senior Software Engineer at AUTO1 Group.","authorAvatar":{"relativePath":"pages/spring-batch-integration/avatar.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsSAAALEgHS3X78AAAEfElEQVQ4yz2O2VMbBQDGlzJQSylXIEc3F0eODYGEJAQI5GBzbK49kt0cm703CSRQqfQCB9taap1qgY61VXqoPZxxbKe2dWo7tY6d0RdHX/0HfPLRF991qTPO/OZ7+33fB+RDFjruKMWhcgwqwnYqaiVhKwXbiolRgZiuVxJ1LrsgEg25sFilFqrFBblQFXNVgajyBMCkxiuIs4yMMoiDSTiwoC3kc/hdVpdVPzFqRua9MpupS7mqmK9LVF0u1CVFzssCIXMYwKZcTGpMyWLUOe2yjoxAQyNO1YCutbV9f1tbe1u7dcRY47GaMiXuVVSFvMTjModLLApwaRefHqeibqNm4EBnb/+A1mo2T9kHcZ9tGR5LQocPvdFmNusEOqv8lPicxBESi4kstifzabeMuj1Wnba7i/Q71vDgJ/Xc4zXhyWrxq1pyG/eWvEMd+9umfWMLAiFWshKD/g8goROV5LjdBJa9ls9r2edr4svTjRenG8836o9WK7e46EXM5zbpNGoVX0qKdEqk00qFgkBnAGrOko+Nz1jA94iZhyvFZ8fZ707JLzaWft48/vJ08/6b5Y+K4azHph5QlbCIXE7yJYRXco8U4Ffvn7ZoAiPgRSp8vU59s9Z89s7RJ2fXtxerbxGpo5nIOWxaCLr1BhOZDUqlOF+ICyVEQSwnAcuBFmtHi8ekRT0OMjBZQyI3lvgduVCLzvGx0HJi9nw+WPTbJ4dNdSLMUTBXiAnF+GsSgKN7n62zxWnQVAIuOjCxEJnaxuevleMfkMjJzPxGcuZTPs4FoA0U3uHIIhHkqKgCr1CIAx6wBeptTY3ZTiRnzmPhbTp9gyFuc+hNBnm6Kj48tXy1Ri7Brs8WxTMVvITN8fkIT8I8GRXIKFCAR11g1wSo3WSIb9dXbojEnUb1C5m5XkJ+ePvI4/WVHSm3BE98SKNCZpbBg6xyntzz92QxN+U39XYBQDMdfbTWvCpiF/Kpyyy1Wy/fapS+XC5tcclq0JGdMOViHhYP/Sdzyn5+HuBR3+xwXwcABG1DNxvkOgqfLxR3q4vvoujFQvzeSuGymKV8w5C+m4p7GTTIYEE2F+b2iABcZnLW2tsNAPrOg5f4zJVyYgfH7vHibY76epm8eyR/BpuU0inPNBFwW6RcSPGVfQYPcbl5gM14Ex69/gDQvW8f7rHuyujtGnm3yT84Id45Sp8lw0dwZHPrqX0s3N/Xnwl56UyAI0KvCQN02lOIOcfV7erWFvBgOwwZj+VSm0LleDlfR+aOsfSDV3+yS1udHV2aAZNaBQ4aByddTjQ6VcGCAJv1wX472K9S96gOHeyZmkHoWJiJ+Jsxz9ax1Ve//33p3k86tVmnM2s0JgWtdlCrHdKDw5AVAoio+7DW2NOj6+lRazQj71/99crayY8b+P3rd3/5459Hv/017o7otENGo82gt5jNo0rq9cMGg0UPWoBZj7O3Dxzo1/f1alyT6OL645Xm2cvXflw+93Rz93tx6YJaZRocHDMYIKMRAg9bQHDYZIIMBrvZ7PwXcPlkRtKKCQsAAAAASUVORK5CYII=","width":50,"height":50,"src":"/static/dc9516b9327e627890a1f5c7f84c004a/45876/avatar.png","srcSet":"/static/dc9516b9327e627890a1f5c7f84c004a/45876/avatar.png 1x,\n/static/dc9516b9327e627890a1f5c7f84c004a/eb85b/avatar.png 1.5x,\n/static/dc9516b9327e627890a1f5c7f84c004a/4f71c/avatar.png 2x,\n/static/dc9516b9327e627890a1f5c7f84c004a/9ec3e/avatar.png 3x"}}},"headerImage":null},"html":"<p><strong>Intro</strong></p>\n<p>In this article, we will take a look at spring-batch and how it could be used. We will walk through various configurations and we will create an application which reads from a CSV file &#x26; writes into a database with outstanding performance.</p>\n<p>I used the following: Java 11, Spring 5+, Spring Boot 2 and Maven-based project.</p>\n<p><strong>Create a Project</strong></p>\n<p>First, we need to create a Spring Boot 2 project. We recommend that you do so by visiting <a href=\"https://start.spring.io/\">spring initializer</a>, which is a useful tool to generate spring projects with required dependencies and configurations.</p>\n<p><strong>Dependencies</strong></p>\n<p>We need the dependencies below to run and test the project:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">&lt;dependency> \n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter&lt;/artifactId>\n&lt;/dependency>\n   \n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-test&lt;/artifactId>\n &lt;scope>test&lt;/scope>\n &lt;exclusions>\n    &lt;exclusion>\n     &lt;groupId>org.junit.vintage&lt;/groupId>\n      &lt;artifactId>junit-vintage-engine&lt;/artifactId>\n    &lt;/exclusion>\n &lt;/exclusions>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-batch&lt;/artifactId>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-data-jpa&lt;/artifactId>\n&lt;/dependency>    \n\n&lt;dependency>\n &lt;groupId>org.projectlombok&lt;/groupId>\n &lt;artifactId>lombok&lt;/artifactId>\n &lt;version>1.18.10&lt;/version>\n &lt;scope>provided&lt;/scope>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>com.h2database&lt;/groupId>\n &lt;artifactId>h2&lt;/artifactId>\n&lt;/dependency>\n    \n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-test&lt;/artifactId>\n &lt;scope>test&lt;/scope>\n &lt;exclusions>\n     &lt;exclusion>\n     &lt;groupId>org.junit.vintage&lt;/groupId>\n     &lt;artifactId>junit-vintage-engine&lt;/artifactId>\n     &lt;/exclusion>\n &lt;/exclusions>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.springframework.batch&lt;/groupId>\n &lt;artifactId>spring-batch-test&lt;/artifactId>\n &lt;scope>test&lt;/scope>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.hamcrest&lt;/groupId>\n &lt;artifactId>hamcrest-all&lt;/artifactId>\n &lt;version>1.3&lt;/version>\n &lt;scope>test&lt;/scope>\n&lt;/dependency></code></pre></div>\n<p><em>spring-boot-starter-batch</em> dependency includes all the configurations to run the spring batch application. <a href=\"https://projectlombok.org/\">Lombok</a>  is just a helper dependency to write the code faster and cleaner. <a href=\"http://www.h2database.com/\">H2</a>  is used as an in-memory database. <em>spring-boot-starter-test</em> and <em>spring-batch-test</em> are included for test purposes.</p>\n<p><strong>Book Class</strong></p>\n<p>Let’s create a model class book, which will represent a book. This will just serve as a model class and will help us during implementation of spring batch.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Data\n\npublic class Book {\nprivate String title;\nprivate String description;\nprivate String author;\n}</code></pre></div>\n<p><strong>Configuration</strong></p>\n<p>Let’s create a class called SpringBatchConfiguration. We will add all required configurations here. First, let’s annotate this class with <a href=\"https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html\">@Configuration</a> to be able to inject beans, and with <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.html\">@EnableBatchProcessing</a> to enable spring batch processing. Additionally, we can add <a href=\"https://projectlombok.org/features/constructor\">@RequiredArgsConstructor</a> from Lombok which would help us to generate constructor with parameters. These parameters are the ones which are marked as final class properties. These properties will be injected as spring beans. Our class will be like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Configuration\n@EnableBatchProcessing\n@RequiredArgsConstructor\npublic class SpringBatchConfiguration</code></pre></div>\n<p>Now, in SpringBatchConfiguration class let’s add properties which need to be injected into the constructor:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">private final JobBuilderFactory jobBuilderFactory;\nprivate final StepBuilderFactory stepBuilderFactory;</code></pre></div>\n<p><em>jobBuilderFactory</em> and <em>stepBuilderFactory</em> are declared in spring batch jar as spring beans so that we can inject them in any class we want.</p>\n<p><strong>File Reader</strong></p>\n<p>Now, we need to declare and initialize spring beans to configure the batch process. The first bean which we will need will be responsible for reading from a file line by line. Spring Batch provides a default class for it. The class name is <em>FlatFileItemReader</em>. Similarly, spring has different default reader classes for reading from a relational database, mongodb and etc. However, if you need, you can create your own reader and implement it in a way you want.</p>\n<p>Let’s now see what <em>FlatFileItemReader</em> injection will look like.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\n@StepScope\npublic FlatFileItemReader&lt;Book> bookReader(@Value(\"#{jobParameters['filePath']}\") final String filePath\n) {\nreturn new FlatFileItemReaderBuilder&lt;Book>()\n    .name(\"personItemReader\")\n    .resource(new ClassPathResource(filePath))\n    .delimited()\n    .names(new String[]{\"title\", \"description\", \"author\"})\n    .fieldSetMapper(new BeanWrapperFieldSetMapper&lt;Book>() {{\n    setTargetType(Book.class);\n    }})\n    .build();\n}</code></pre></div>\n<p>We are configuring that the bean reader should read data from the given file path, which should be a CSV file having rows with title, description and author respectively.</p>\n<p>The <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/scope/StepScope.html\">StepScope</a> means to initialize this bean after each step, so file path could be dynamically set when the spring batch job is launched. We will come to that later, how to pass the file path later in this article.</p>\n<p><strong>Item Writer</strong></p>\n<p>Now, let’s create a writer class, which will take the data and write into the relational database.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\npublic JdbcBatchItemWriter&lt;Book> writer(final DataSource dataSource) {\n    return new JdbcBatchItemWriterBuilder&lt;Book>()\n        .itemPreparedStatementSetter((book, preparedStatement) -> {\n            preparedStatement.setString(1, book.getTitle());\n            preparedStatement.setString(2, book.getDescription());\n            preparedStatement.setString(3, book.getAuthor());\n        })\n        .sql(\"INSERT INTO books (title, description, author) VALUES (title, description, author_surname)\")\n        .dataSource(dataSource)\n        .build();\n} </code></pre></div>\n<p>In the writer bean, we are setting item processors and adding values inside <em>preparedStatement</em> accordingly, which book property should be inserted for each db table column.  </p>\n<p><strong>Step Configuration</strong></p>\n<p>Now, let’s configure a step which will be executed in the batch process. In our case, the configuration will look like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\npublic Step step1(final ItemWriter&lt;Book> writer, final ItemReader&lt;Book> reader) {\n    return stepBuilderFactory.get(\"step1\")\n        .&lt;Book, Book> chunk(100)\n        .reader(reader)\n        .processor((ItemProcessor&lt;Book, Book>) book -> book)\n        .writer(writer)\n        .build();\n}</code></pre></div>\n<p>In our scenario, we have only one step, but it's also possible to configure multiple steps. Here, we are creating a spring batch step with the name <em>step1</em> and setting reader and writer accordingly. These are the readers and writers which we created as spring beans earlier.</p>\n<p>We are setting chunk as 100, which means the items chunk proceeded is 100. We can make this configurable too. The processor is for converting the current object to the one which the writer should proceed with. In our case, it is the same object.</p>\n<p><strong>Job Configuration</strong></p>\n<p>Last but not least, let’s configure the job which should be executed.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\npublic Job importUserJob(final Step step1) {\n    return jobBuilderFactory.get(\"bookReaderJob\")\n        .incrementer(new RunIdIncrementer())\n        .flow(step1)\n        .end()\n        .build();\n}</code></pre></div>\n<p>Here, we create a job with the name <em>bookReaderJob</em>. We add an incrementer <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/launch/support/RunIdIncrementer.html\">RunIdIncrementer</a>, which is an id generator for the tables which are specifically designed to store the details about job executions in the database. After each time the spring batch job is executed, it will save details about the execution. To check the schema structure for storing this data, take a look at <a href=\"https://github.com/spring-projects/spring-batch/blob/master/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql\">this sql</a>. It is for MySql, but other SQL scripts are available too.</p>\n<p>Additionally, we add flow by which the job should be executed. Currently, we have only one step in our flow, so we add it.  </p>\n<p>Please also add this config: <code class=\"language-text\">spring.batch.job.enabled=false</code> in your properties config file so that spring batch job won’t be executed automatically with no parameters when the application is started.</p>\n<p><strong>Execution</strong></p>\n<p>To execute the job, we need to declare <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/launch/JobLauncher.html\">JobLauncher</a> and launch the job. To do so, we need to create the below class:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Component\n@RequiredArgsConstructor\npublic class SpringBatchExecutor {  \n    private final JobLauncher jobLauncher;\n    private final Job job;  \n\n    @SneakyThrows\n    public void execute(final String filePath) {\n        JobParameters parameters = new JobParametersBuilder()\n            .addString(\"filePath\", filePath)\n            .toJobParameters();  \n        jobLauncher.run(job, parameters);\n    }  \n}</code></pre></div>\n<p>Now, we can call the execute method from whenever we want, with the file path from which the data should be read.</p>\n<p><strong>Additional Classes</strong></p>\n<p>There are a few additional classes which you will need to execute your code.</p>\n<p>Create a class <em>BookEntity</em> so that spring data JPA will automatically create the book table for you. Then you can create repository.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Data\n@Entity\n@Table(name = \"books\")\npublic class BookEntity {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n    private String title;\n    private String description;\n    private String authorFullName;\n}</code></pre></div>\n<p>Then create the interface <em>BookRepository</em> and extend it from <em>JpaRepository</em>. At the moment, we will need this only for testing.  </p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Repository\npublic interface BookRepository extends JpaRepository&lt;BookEntity, Long> {\n} </code></pre></div>\n<p><strong>Testing</strong></p>\n<p>No one likes a code which is not tested. So, let’s write a few test cases for our class.</p>\n<p>Here is a code sample for test cases:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Slf4j\n\n@SpringBootTest\nclass SpringBatchSampleApplicationIntegrationTest {\n\n    @Autowired\n    private SpringBatchExecutor springBatchExecutor;\n    \n    @Autowired\n    private BookRepository bookRepository;\n\n    @Test\n    public void testExecution() {\n        long initialCount = bookRepository.count();\n        assertThat(initialCount, equalTo(0L));\n        springBatchExecutor.execute(\"sample-data.csv\");\n        long count = bookRepository.count();\n        assertThat(count, equalTo(7L));\n    } \n\n    @Test\n    public void testLargeData() {\n        long startTime = System.currentTimeMillis();\n        long initialCount = bookRepository.count();\n        assertThat(initialCount, equalTo(0L));\n        springBatchExecutor.execute(\"large-data.csv\");\n        long count = bookRepository.count();\n        assertThat(count, equalTo(60000L));\n        long endTime = System.currentTimeMillis();\n        log.info(\"executed in miles: {}\", endTime - startTime);\n    }\n}</code></pre></div>\n<p>The second test executes in ≈ <strong>3500 ms</strong> in a machine with a 2.2 Ghz Intel Core i7 and 16GB ram. It proceeds 60K lines of CSV file, and saves it into a relational database.</p>\n<p>You can check out the working sample code in <a href=\"https://github.com/yolch-yolchyan/spring-batch-sample\">github</a>,</p>","fields":{"slug":"/spring-batch-integration/","tags":["auto1","engineering","spring","code","java"]}}},{"node":{"id":"e2563427-db60-5c74-aaff-0c8c2c483f69","frontmatter":{"category":"Engineering","title":"DevoxxUA was awesome!","date":"2019-12-05","summary":"A few words about one of the biggest Java conferences and AUTO1's presence there.","thumbnail":{"relativePath":"pages/auto1-devoxx/ivan_thumb.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABQABBP/EABUBAQEAAAAAAAAAAAAAAAAAAAEA/9oADAMBAAIQAxAAAAFobuDFqyL/xAAaEAABBQEAAAAAAAAAAAAAAAACAAEDEzES/9oACAEBAAEFApMKYRldXWI3bvF//8QAFREBAQAAAAAAAAAAAAAAAAAAARD/2gAIAQMBAT8BJ//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EAB0QAAEEAgMAAAAAAAAAAAAAAAABERIhAxBCUYH/2gAIAQEABj8C9H496SmExxqQx//EABsQAAMAAgMAAAAAAAAAAAAAAAABETFRIUGh/9oACAEBAAE/IWihpe8JGzIwO65PagSFoof/2gAMAwEAAgADAAAAEEgv/8QAFREBAQAAAAAAAAAAAAAAAAAAEDH/2gAIAQMBAT8Qg//EABYRAQEBAAAAAAAAAAAAAAAAAAEQIf/aAAgBAgEBPxBNn//EAB4QAQACAgIDAQAAAAAAAAAAAAEAESFBMWFRcbHh/9oACAEBAAE/EPcQfYt7YYNab5CuSAbFIzJ3xnp6mFDht8n9YZ1dVqq6n//Z","width":520,"height":325,"src":"/static/150f8a5d8b98da0a021f785cc45b217a/a2998/ivan_thumb.jpg","srcSet":"/static/150f8a5d8b98da0a021f785cc45b217a/a2998/ivan_thumb.jpg 1x,\n/static/150f8a5d8b98da0a021f785cc45b217a/99cce/ivan_thumb.jpg 1.5x,\n/static/150f8a5d8b98da0a021f785cc45b217a/6e995/ivan_thumb.jpg 2x,\n/static/150f8a5d8b98da0a021f785cc45b217a/968b1/ivan_thumb.jpg 3x"}}},"authorName":"Ivan Kozlov","authorDescription":"Ivan is the senior team lead of the AUTO1 Group Kiev engineering office.","authorAvatar":{"relativePath":"pages/auto1-devoxx/avatar.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAACyElEQVQ4y4VUO2gUURR9mZmN8YcItoKVjeXuzsxu9jOzm92NkBBTqDEmjZAEgtqIpSgI1gp+uhS6kEI7wc7GQsFCLERiISGCaGHhF02yu+M5s/cujyC4cJY379573r3n3XdNvV51xsYiJ4rKbqlUyBj8wjAYyWazp4BVYB3YAraBDeAhMBcE/l76lkrFTLVachuNyCGXEbJMrVbx6ADnaWAtn88nvu8nWBMdQcJ9Auv3wCxjWq2aC9IMSF3DzCyyG1YAs9pSYkL2melmLpfT71tKGkUlz2iZJAuCgA5dEjGAwPoV0AbuAy+tfR7YkZjb5CiXR1OutEw5jWQdCfgCnEiSxNg/7LWAj+KTSiHSzBvrAtascrriPCmnDkP4AYQ0Em3TGPHf8P38ARpP/4PsHQ7aU69Hqdi4OJeI47JXLg9IX1tVbUuWCzSsygcJO+L0goGNRpySoB1colarerhEvcCn4tuxCB/TsC5ZdS3Ct2Ho75uZmRoigWbIbgCxt7Q0O8TLsgg17o2R2+JHzzJcV/1IoBlybel42SLsCcc3I6UqoaZ+lUHFYjjCzJSQa+4J4UVLKiX8ScOGVXJXDN+xd7SfZXFYS65U+n2Wz+cOw+ezHadSkfCRdVJiZdlmMG9ZtHMpgWR3Z0eMSvWExjmrbXoCbZ951dIim7Yk6u1IYtlwavChW51Pp01tXOwf01eC9RHs/Rbbnx2Hf4IUh/Q5zdol8H3KG22Pj9edOK54LF1876pd37TELhqdZ+J4MwxDHUvXgDOFQrB7YqLpjI4WdhG8GLYNbCeBK/1+TWNWRG+Pfy5Hj5Dek3IXzX9+8DkL/AIeLC/PDU1NHXdSQk5altNsDkjPAT+ArzKyFnK5bB2IxbYik4hanmfM5GTL6Q/Y2DUc25y0HLToubR8TI39cL4APJOxr/35AXgOXEJnHKQvepP6eiQj119kDrZjqLo8HAAAAABJRU5ErkJggg==","width":50,"height":50,"src":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png","srcSet":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png 1x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/eb85b/avatar.png 1.5x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/4f71c/avatar.png 2x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/9ec3e/avatar.png 3x"}}},"headerImage":{"relativePath":"pages/auto1-devoxx/header.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAJABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIDBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAe21ELH/xAAZEAACAwEAAAAAAAAAAAAAAAACEQABEiD/2gAIAQEAAQUC0TZSuP/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABcQAAMBAAAAAAAAAAAAAAAAAAAgMTL/2gAIAQEABj8CyRf/xAAaEAEAAgMBAAAAAAAAAAAAAAABADERIFEh/9oACAEBAAE/IaFO5g3PlzMEdP/aAAwDAQACAAMAAAAQAA//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAbEAADAAIDAAAAAAAAAAAAAAABESEAMSBBYf/aAAgBAQABPxClqP0uMAdzbvAsCMJhXD//2Q==","width":640,"height":284,"src":"/static/cae90168dbe3b194c3406bc103d1a5f3/c8462/header.jpg","srcSet":"/static/cae90168dbe3b194c3406bc103d1a5f3/c8462/header.jpg 1x"}}}},"html":"<p><strong>Devoxx</strong>, formerly known as JavaPolis, is an international community conference which first took place in 2001 in Belgium. Since then it has developed into one of the largest Java events in Europe, with chapters in Belgium, France, UK, Poland and Ukraine, as well as an established conference in Morocco.</p>\n<p>Among the speakers at <strong>DevoxxUA</strong> (Devoxx Ukraine) 2019 I’d like to mention <strong>Josh Long</strong>, the Spring Developer Advocate at <em>Pivotal</em> and <strong>Cay Horstmann</strong> (yes, that’s him, the author of “Core Java”, “Big Java” and other great books). I got a chance to participate as a speaker on the second day of the conference, delivering a presentation on one of our latest solutions for implementing GraphQL with authorization in a microservice architecture. Was very excited to see that the talk got the highest amount of participants for that time slot and the room was packed, including people standing on their feet for the entire duration of my presentation!</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/0c7d3daa298009c0e59f41dbb964406e/c1e84/talk.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQFAQP/xAAXAQADAQAAAAAAAAAAAAAAAAAAAQID/9oADAMBAAIQAxAAAAHrjUsl0ii0/8QAGxAAAwACAwAAAAAAAAAAAAAAAAECAwQSFCL/2gAIAQEAAQUCumpnZ4nbZsrzkkdOT//EABcRAAMBAAAAAAAAAAAAAAAAAAIQERL/2gAIAQMBAT8B1Ci//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGhAAAgIDAAAAAAAAAAAAAAAAAAIBITFhof/aAAgBAQAGPwJZZDBSdII2Uf/EABoQAAMAAwEAAAAAAAAAAAAAAAABESFRYUH/2gAIAQEAAT8htAfXCmJYPyGFX7BOV0fRpMVo/9oADAMBAAIAAwAAABAzL//EABcRAQEBAQAAAAAAAAAAAAAAAAEAESH/2gAIAQMBAT8QyiLl/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERMf/aAAgBAgEBPxDURn//xAAdEAEAAgICAwAAAAAAAAAAAAABESEAUTGBocHh/9oACAEBAAE/ECIgSshRTjrzhIK7Im3WKag5tT0Yrlo7kyJ8xS6gQlOjOzeGFWJg1Mx6z//Z'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"talk\"\n        title=\"Full room!\"\n        src=\"/static/0c7d3daa298009c0e59f41dbb964406e/f8fb9/talk.jpg\"\n        srcset=\"/static/0c7d3daa298009c0e59f41dbb964406e/e8976/talk.jpg 148w,\n/static/0c7d3daa298009c0e59f41dbb964406e/63df2/talk.jpg 295w,\n/static/0c7d3daa298009c0e59f41dbb964406e/f8fb9/talk.jpg 590w,\n/static/0c7d3daa298009c0e59f41dbb964406e/85e3d/talk.jpg 885w,\n/static/0c7d3daa298009c0e59f41dbb964406e/d1924/talk.jpg 1180w,\n/static/0c7d3daa298009c0e59f41dbb964406e/9452e/talk.jpg 1770w,\n/static/0c7d3daa298009c0e59f41dbb964406e/c1e84/talk.jpg 4000w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>All in all, it was a great experience of sharing what &#x26; how we’ve built while getting valuable feedback and interesting questions. Have to say it felt incredible to stand on stage next to all the stars of the engineering world, during the closing ceremony of the conference!</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/6a09cfcfbbf496ecd690c246c1eb193c/c1e84/stage.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIFA//EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAHBaTlQywM//8QAGRAAAwEBAQAAAAAAAAAAAAAAAAECEhET/9oACAEBAAEFAlDSwxzxzEmIPCD/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAZEAEBAQADAAAAAAAAAAAAAAAAMQEiMpH/2gAIAQEABj8CjeKI6pnj/8QAHBAAAwEAAgMAAAAAAAAAAAAAABEhATFhcZHB/9oACAEBAAE/IYp10NpCAvsx4qr5LPMPyM+jH//aAAwDAQACAAMAAAAQVB//xAAYEQADAQEAAAAAAAAAAAAAAAAAARExIf/aAAgBAwEBPxCrIPeI/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAEhUf/aAAgBAgEBPxC6Jn//xAAeEAEAAgEEAwAAAAAAAAAAAAABABEhMUFhkVFxgf/aAAgBAQABPxALXY7vM1LAYcd+peIuQZ51jpoKye8DVkzanMU2fIXbP//Z'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"stage\"\n        title=\"Awesome people\"\n        src=\"/static/6a09cfcfbbf496ecd690c246c1eb193c/f8fb9/stage.jpg\"\n        srcset=\"/static/6a09cfcfbbf496ecd690c246c1eb193c/e8976/stage.jpg 148w,\n/static/6a09cfcfbbf496ecd690c246c1eb193c/63df2/stage.jpg 295w,\n/static/6a09cfcfbbf496ecd690c246c1eb193c/f8fb9/stage.jpg 590w,\n/static/6a09cfcfbbf496ecd690c246c1eb193c/85e3d/stage.jpg 885w,\n/static/6a09cfcfbbf496ecd690c246c1eb193c/d1924/stage.jpg 1180w,\n/static/6a09cfcfbbf496ecd690c246c1eb193c/9452e/stage.jpg 1770w,\n/static/6a09cfcfbbf496ecd690c246c1eb193c/c1e84/stage.jpg 4000w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>Here's my entire talk:\n\n        <div class=\"embedVideo-container\">\n            <iframe\n              width=\"800\"\n              height=\"400\"\n              src=\"https://www.youtube-nocookie.com/embed/JbidXuC_0M4?rel=0\"\n              class=\"embedVideo-iframe\"\n              style=\"border:0\"\n              allowfullscreen\n            ></iframe>\n        </div></p>","fields":{"slug":"/auto1-devoxx/","tags":["auto1","engineering","java","conference","talk","devoxx"]}}},{"node":{"id":"7efdac28-b97f-53e0-adf8-48d022f3eecc","frontmatter":{"category":"Coding","title":"Integration Test Speedup with Spring","date":"2019-03-11","summary":"Here is what we found on speeding up your integration tests in spring based applications.","thumbnail":null,"authorName":"Boris Faniuk","authorDescription":"Boris is a former member of Engineering of AUTO1 Group.","authorAvatar":{"relativePath":"pages/integration-test-speedup/avatar.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAACyElEQVQ4y4VUO2gUURR9mZmN8YcItoKVjeXuzsxu9jOzm92NkBBTqDEmjZAEgtqIpSgI1gp+uhS6kEI7wc7GQsFCLERiISGCaGHhF02yu+M5s/cujyC4cJY379573r3n3XdNvV51xsYiJ4rKbqlUyBj8wjAYyWazp4BVYB3YAraBDeAhMBcE/l76lkrFTLVachuNyCGXEbJMrVbx6ADnaWAtn88nvu8nWBMdQcJ9Auv3wCxjWq2aC9IMSF3DzCyyG1YAs9pSYkL2melmLpfT71tKGkUlz2iZJAuCgA5dEjGAwPoV0AbuAy+tfR7YkZjb5CiXR1OutEw5jWQdCfgCnEiSxNg/7LWAj+KTSiHSzBvrAtascrriPCmnDkP4AYQ0Em3TGPHf8P38ARpP/4PsHQ7aU69Hqdi4OJeI47JXLg9IX1tVbUuWCzSsygcJO+L0goGNRpySoB1colarerhEvcCn4tuxCB/TsC5ZdS3Ct2Ho75uZmRoigWbIbgCxt7Q0O8TLsgg17o2R2+JHzzJcV/1IoBlybel42SLsCcc3I6UqoaZ+lUHFYjjCzJSQa+4J4UVLKiX8ScOGVXJXDN+xd7SfZXFYS65U+n2Wz+cOw+ezHadSkfCRdVJiZdlmMG9ZtHMpgWR3Z0eMSvWExjmrbXoCbZ951dIim7Yk6u1IYtlwavChW51Pp01tXOwf01eC9RHs/Rbbnx2Hf4IUh/Q5zdol8H3KG22Pj9edOK54LF1876pd37TELhqdZ+J4MwxDHUvXgDOFQrB7YqLpjI4WdhG8GLYNbCeBK/1+TWNWRG+Pfy5Hj5Dek3IXzX9+8DkL/AIeLC/PDU1NHXdSQk5altNsDkjPAT+ArzKyFnK5bB2IxbYik4hanmfM5GTL6Q/Y2DUc25y0HLToubR8TI39cL4APJOxr/35AXgOXEJnHKQvepP6eiQj119kDrZjqLo8HAAAAABJRU5ErkJggg==","width":50,"height":50,"src":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png","srcSet":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png 1x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/eb85b/avatar.png 1.5x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/4f71c/avatar.png 2x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/9ec3e/avatar.png 3x"}}},"headerImage":{"relativePath":"pages/integration-test-speedup/header.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABQABAv/EABYBAQEBAAAAAAAAAAAAAAAAAAIAAf/aAAwDAQACEAMQAAABJ6Uw4FJSv//EABsQAAICAwEAAAAAAAAAAAAAAAECAAMEFCEx/9oACAEBAAEFAl9tuawzH6WUPNYT/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFREBAQAAAAAAAAAAAAAAAAAAARD/2gAIAQIBAT8BJ//EABsQAAEEAwAAAAAAAAAAAAAAAAABEBIhIjFR/9oACAEBAAY/AjJo9SizZ//EABwQAQEAAgIDAAAAAAAAAAAAAAERAHEQMUFR0f/aAAgBAQABPyFMdusqfAgeuBQwbbGHwF1jT4z/2gAMAwEAAgADAAAAELsf/8QAFhEBAQEAAAAAAAAAAAAAAAAAARBB/9oACAEDAQE/EA2f/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERIf/aAAgBAgEBPxDSpD//xAAbEAEBAQADAQEAAAAAAAAAAAABEQAhMXFBYf/aAAgBAQABPxAMiBZ241OQL4wZbyughIofpPEuZhdFS61EeZ//2Q==","width":1024,"height":768,"src":"/static/9f60dcc74069c89c91db86dc33946167/9f594/header.jpg","srcSet":"/static/9f60dcc74069c89c91db86dc33946167/9f594/header.jpg 1x"}}}},"html":"<h2>Problem statement</h2>\n<p>Over time we realized that our microservices built on top of Spring framework take tons of time on the integration test step. Every single PR runs all tests and this takes 10-20 minutes for some services. To decrease PR build time we decided to research why this happens.\nOne of the problems that this research highlighted is ramping up heavy spring context several times during execution of all module’s tests.</p>\n<p>As a proof of concept we have taken 3 modules and cleaned up their tests to achieve better integration test performance.</p>\n<p>The results are pretty much self-explanatory: </p>\n<ol>\n<li>Module A now takes 3 minutes to test versus 11 before</li>\n<li>Module B now takes 3 minutes to test versus 8 before</li>\n<li>Module C now takes 4 minutes to test versus 12 before</li>\n</ol>\n<p>Below I list all factors that produce many spring contexts and how we managed to overcome them.</p>\n<h2>Spring features to be avoided</h2>\n<h3><code class=\"language-text\">@ActiveProfiles</code> and <code class=\"language-text\">@ContextConfiguration</code> with different attributes</h3>\n<p>Our spring-based microservices actively use spring profiles and configuration classes.\nMany components from common libraries are hidden under profiles and configuration classes to not be active for every single module / environment without need. So, integration test authors define profiles and configuration classes that should be activated for their tests using these annotations:</p>\n<ol>\n<li><code class=\"language-text\">@ActiveProfiles({\"integration-test\", \"pg-test\", \"mongo-test-two-nodes\"})</code></li>\n<li><code class=\"language-text\">@ContextConfiguration(classes = {Application.class, AwsTestConfiguration.class})</code></li>\n</ol>\n<p>Here <em>mongo-test-two-nodes</em> activates special mode for mongo based services to operate with two separate mongo instances. As not all tests need this, the profile is turned off by default and only those tests that need this behaviour would explicitly turn it on. But this, unfortunately, leads to multiple spring contexts being created.</p>\n<p>The same happens with <em>AwsTestConfiguration</em> that deploys special test-component to mock AWS calls.</p>\n<p>The solution was to make all tests use the same set of active profiles and context configuration classes for every single integration test.\nThe drawback is that some components are deployed, even for the tests that do not need it. But deploying single component takes O(1) time comparing with the whole spring context.</p>\n<p>Another point to keep in mind is that order of profiles (configuration classes) matters.\n<code class=\"language-text\">@ActiveProfiles({\"integration-test\", \"pg-test\"})</code>\n<code class=\"language-text\">@ActiveProfiles({\"pg-test\", \"integration-test\"})</code></p>\n<p>Two settings mentioned above result in 2 different spring contexts.</p>\n<h3><code class=\"language-text\">@SpyBean</code> and <code class=\"language-text\">@MockBean</code></h3>\n<p>One interesting feature in spring-boot-test is mocking / spying real spring beans.\nThis was mostly used when we don’t care what some method does in test but we need to check that it was called by parent components.\nUnfortunately this also makes test to create separate spring context.\nWhat we’ve done here is replacing <code class=\"language-text\">@SpyBean</code> annotation with configuration class that wraps real spring bean into <code class=\"language-text\">Mockito.spy(..)</code>:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Configuration\npublic class SomeComponentSpyFactory {\n\t@Autowired\n\tprivate SomeComponent someComponent;\n\n\t@Bean\n\t@Primary\n\tpublic SomeComponent someComponentSpy() {\n    \t\treturn Mockito.spy(someComponent);\n\t}\n}</code></pre></div>\n<p>Another option is to use the combination of <code class=\"language-text\">@Autowired</code> and <code class=\"language-text\">@Spy</code> annotations.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">\t@InjectMocks\n\t@Autowired\n\tprivate ParentComponent parentComponent;\n\n\t@Spy\n\t@Autowired\n\tprivate ChildComponent childComponent;</code></pre></div>\n<p>However, the drawback of the last solution is that, <em>parentComponent</em> needs to have a setter for <em>childComponent</em> which is not good in terms of encapsulation. But if you don’t use autowiring on constructor level it is fine. Anyway, one can choose whatever fits the situation better.</p>\n<h3><code class=\"language-text\">@DirtiesContext</code></h3>\n<p>One of the services used this annotation to clear the caches between different tests.\nClearing a cache is really a problem (see section below), but this solution comes to separate spring context per test class or even test method (depending on annotation parameters). Context per method was actually the case for a few test classes. Instead in this modules we added methods to clear the caches programmatically without deploying the whole new context.\nOne can use java reflection API instead of adding special production-level methods, again whatever fits you better.</p>\n<h2>Challenge: test execution order and cleaning collections (caches)</h2>\n<p>After all this “multiple context” factors are gone one may face (and we did) a challenge to fix or re-organize some tests. Before above enhancements were done all tests were running in separate spring contexts, therefore used separate instances of every component and therefore those problems could be hidden.</p>\n<p>As an example some tests in mongo-based service were testing <code class=\"language-text\">getAll()</code>-like operations. Those tests were running functions that fetched all records from some collections. This was not a problem as embedded mongo DB was different instance. After all tests started to exploit the same spring context some of them started to fail intermittently. The failures were intermittent because test execution order is different. One service may break another, but not vice versa.\nSo, to overcome this, we added missing <code class=\"language-text\">@Before</code> operations that cleaned those collections.</p>\n<p>The rule that I would suggest here is that every test should clean up whatever it needs to be clean and not rely on fairness of other tests. That was a problem in our case. Some tests had <code class=\"language-text\">@After</code> operations that cleaned up, but not everything produced by the test was cleaned up. This caused intermittent failures of other tests.</p>\n<h2>Tools we created to reduce test execution time</h2>\n<p>So, now after we know all this, we created two tools (and may create more in future) that help to identify those problems.</p>\n<h3>Jenkins PR pipeline step</h3>\n<p>After every PR commit we run one additional step that analyzes test execution log.\nIf more than one spring context is created this leaves some traces and a notification is sent to the author of the commit via slack</p>\n<p>As an enhancement, the jenkins jobs could be modified to fail the build if the number of spring contexts is greater than a certain threshold. The threshold could start with a high number and can gradually be reduced as the team cleans up the existing tests by adopting the aforementioned practices/fixes</p>\n<h3>IntelliJ IDEA plugin</h3>\n<p>We wrote an IntelliJ plugin to identify and solve the aforementioned problems. There will be a separate blog post about this plugin coming very soon.</p>","fields":{"slug":"/integration-test-speedup/","tags":["auto1","engineering","spring","java","testing"]}}}]}},"pageContext":{"slug":"/tags/java","tag":"java","categories":["Architecture","Coding","DevOps","Engineering","ProjectManagement","QA","Social","TechRadar"]}}