<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shiveen Pandita</title>
    <description>The latest articles on DEV Community by Shiveen Pandita (@shavz).</description>
    <link>https://dev.to/shavz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F126834%2Ffb826cca-3f24-43cd-8658-42b99054f62d.jpg</url>
      <title>DEV Community: Shiveen Pandita</title>
      <link>https://dev.to/shavz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shavz"/>
    <language>en</language>
    <item>
      <title>Software Is Not a Race</title>
      <dc:creator>Shiveen Pandita</dc:creator>
      <pubDate>Thu, 26 Aug 2021 05:32:43 +0000</pubDate>
      <link>https://dev.to/shavz/software-is-not-a-race-15e2</link>
      <guid>https://dev.to/shavz/software-is-not-a-race-15e2</guid>
      <description>&lt;p&gt;Too often I see people building software and doing changes in an existing piece of code as if they're texting one...excruciating...line...at...a...time. As if it's important to get one disjointed piece of functionality out and making up what's next on the go.&lt;/p&gt;

&lt;p&gt;This is bad. Software like any other piece of well thought out functionality should be &lt;strong&gt;deliberate&lt;/strong&gt;. Don't rush into adding a change because the current circumstances confirm it. Check if the change needs to be done based on the requirements of the system. Confirm it with your teammates. More often than not, you will realise that you have missed a key detail of the system. And that the change can be done in another part of the system or not needed at all.&lt;/p&gt;

&lt;p&gt;I have long held the belief that a key and critical milestone in the path of young precocious programmer progressing from making computers go &lt;em&gt;bleep blop&lt;/em&gt; to building programming megastructures, is the ability to pause and question, deeply, pragmatically, WHY. It's easy to forget in the pace of building a new systems and delivering rapid value, that the best software is the one that's not needed at all. Each extra line of code in the system brings with itself an extra line to maintain and code reading overhead.&lt;/p&gt;

&lt;p&gt;Since most of the code ever written is write once and maintain/read many times - it is imperative to question the need. Each line of code needs to earn its place there and should have a rock solid reason to be there.&lt;/p&gt;

&lt;p&gt;There are some good strategies that can be applied to enable well thought out software in the org:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;No matter what the change, always communicate the need. Writing a detailed why on pull requests is good enough for trivial changes. For non-trivial changes, write a small RFC or some form of &lt;code&gt;Approach Validation Document&lt;/code&gt; ™ and get a consensus sign off from your team or a subset of the team. This adds a small overhead to delivering your work but in the end your team will thank you for it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Treat your codebase not as a junkyard to throw shit in instead a garden to be cultivated. Encourage this mentality within your team as well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hone and cultivate a strong bullshit meter for changes that are done to cover and compensate the deficiencies of the system or the process. Quick hacks to get stuff shipped are okay, living with those hacks without even discussing fixing the problems elsewhere seldom is.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd like to end this post with a caveat that there are cases where it's necessary to add quick &amp;amp; dirty hacks to fix production or hustle changed to prod to meet a deadline. Such things are a part and parcel of any engineering org to a certain degree and no one should expect you to write a detailed write up for every single commit you make in these cases. But, recognising the time and place for such changes and balancing that with careful followup is the key to not let the system fall under ever increasing tech debt.&lt;/p&gt;

&lt;p&gt;(x-post from my &lt;a href="https://www.shiveenp.com/posts/software-is-not-a-race/"&gt;blog&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>software</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Dynamodb Gotchas</title>
      <dc:creator>Shiveen Pandita</dc:creator>
      <pubDate>Wed, 19 May 2021 10:16:26 +0000</pubDate>
      <link>https://dev.to/shavz/dynamodb-gotchas-bjm</link>
      <guid>https://dev.to/shavz/dynamodb-gotchas-bjm</guid>
      <description>&lt;p&gt;This is just a collection of things that required some google searches to resolve while working with DynamoDB at work recently. All the code examples here use Kotlin, though, I'm confident that they would still appear in Java.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persisting a DynamoDb Object that extends a class
&lt;/h2&gt;

&lt;p&gt;DynamoDB doesn't automatically work with Abstract classes. For example, let's say you have an &lt;code&gt;abstract class&lt;/code&gt; which contains some common fields (such as the hashKey and the range fields),&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AbstractBook&lt;/span&gt; &lt;span class="nd"&gt;@JvmOverloads&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@DynamoDBHashKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributeName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"isbn"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;isbn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="nd"&gt;@DynamoDBTyped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DynamoDBMapperFieldModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DynamoDBAttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@DynamoDBRangeKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributeName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;bookType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then you define the actual class, that extends this base class,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Novella&lt;/span&gt; &lt;span class="nd"&gt;@JvmOverloads&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bookType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AbstractBook&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bookType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you try to use a &lt;code&gt;DynamoDBMapper&lt;/code&gt; to save the object it will throw an exception.&lt;/p&gt;

&lt;p&gt;To fix the exception, you'll need to add the &lt;code&gt;@DynamoDBDocument&lt;/code&gt; to the AbstractClass to let the mapper know that it's the abstract version of the actual persisted entity. This is what the JavaDoc for the annotation says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An annotation that marks a class which can be serialised to a DynamoDB document or sub-document. Behaves exactly the same as DynamoDBTable, but without requiring you to specify a tableName.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which means that this annotation is necessary to serialise objects types that are not directly part of the actual stored object type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persisting a List of Objects in DynamoDB
&lt;/h2&gt;

&lt;p&gt;Another little snowflake behaviour I encountered was when persisting a list of objects. Let's say we go ahead and add some more data to the Novella object, like a List of Publishers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Publishers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Novella&lt;/span&gt; &lt;span class="nd"&gt;@JvmOverloads&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bookType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nd"&gt;@DynamoDBTypeConvertedJson&lt;/span&gt; 
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;publishers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Publishers&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AbstractBook&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bookType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@DynamoDBTypeConvertedJson&lt;/code&gt; is the annotation DynamoDB recommends for storing objects when using DynamoDBMapper. It has a strange behaviour where it can auto serialise a &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; but it loses type information on deserialisation and deserialises the object as an amorphous map. Which means you get exceptions like:&lt;br&gt;
&lt;code&gt;java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to Publisher&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Based on this &lt;a href="https://stackoverflow.com/questions/30793481/dynamodb-jsonmarshaller-cannot-deserialize-list-of-object"&gt;thread&lt;/a&gt; this has to do with type erasure, wherein &lt;code&gt;T : Object&lt;/code&gt; which results in bad behaviour at deserialisation time (the default Jackson marshaller is smart enough during serialisation).&lt;/p&gt;

&lt;p&gt;The best way to solve this is to define a custom serialiser and deserialiser for your object.&lt;/p&gt;

&lt;p&gt;So, in this case it's a matter of defining a class like,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PublishersMapListConverter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DynamoDBTypeConverter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Publishers&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;objectMapper&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;jacksonObjectMapper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;publishers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Publishers&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeValueAsString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;publishers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;unconvert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;publishers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Publishers&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;TypeReference&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Publishers&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;publishers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then adding the &lt;code&gt;@DynamoDBTypeConverted(converter = PublishersMapListConverter::class)&lt;/code&gt;,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Novella&lt;/span&gt; &lt;span class="nd"&gt;@JvmOverloads&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bookType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;@DynamoDBTypeConverted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;converter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PublishersMapListConverter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;publishers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Publishers&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AbstractBook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isbn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bookType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully this post helps someone else save some time as well.&lt;/p&gt;




&lt;p&gt;x-post from my &lt;a href="https://www.shiveenp.com/posts/dynamodb-gotchas/"&gt;blog&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>dynamodb</category>
      <category>database</category>
      <category>aws</category>
    </item>
    <item>
      <title>Building a Reactive Oauth Client App with SpringBoot and Kotlin Coroutines</title>
      <dc:creator>Shiveen Pandita</dc:creator>
      <pubDate>Sun, 14 Jun 2020 11:18:15 +0000</pubDate>
      <link>https://dev.to/shavz/building-a-reactive-oauth-client-app-with-springboot-and-kotlin-coroutines-2goc</link>
      <guid>https://dev.to/shavz/building-a-reactive-oauth-client-app-with-springboot-and-kotlin-coroutines-2goc</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdrt1kc7fczpu4em9x73o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdrt1kc7fczpu4em9x73o.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;In this post, I’ll walk through two new exciting things that have happened in the spring ecosystem recently. First is spring webflux support for kotlin &lt;a href="https://kotlinlang.org/docs/reference/coroutines-overview.html" rel="noopener noreferrer"&gt;couroutines&lt;/a&gt; and second is the overhaul of spring security, and the addition of the out of the box oauth2 client support for &lt;a href="https://spring.io/blog/2018/07/03/spring-social-end-of-life-announcement" rel="noopener noreferrer"&gt;social logins&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For me one of the most impactful new features is the integration with spring coroutines throw the &lt;a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html" rel="noopener noreferrer"&gt;Flow&lt;/a&gt; primitive. The integration makes writing reactive code a lot more straightforward - no more &lt;code&gt;subscribeOn&lt;/code&gt; and &lt;code&gt;observeOn&lt;/code&gt; operations. Instead of thinking in terms of &lt;code&gt;Mono&lt;/code&gt; and &lt;code&gt;Flux&lt;/code&gt; based primitives borrowed from &lt;a href="https://github.com/reactor/reactor-core" rel="noopener noreferrer"&gt;reactor&lt;/a&gt;, it allows a straightforward way to generate cold streams instead. Kotlin flow allows us to write purely asynchronous code in an sequential/imperative manner - which in turn means an existing codebase using blocking code can be converted to use the non-reactive paradigm a lot easier. Another advantage of using coroutines based non-blocking code is that coroutines have been implemented as &lt;a href="https://medium.com/@elizarov/blocking-threads-suspending-coroutines-d33e11bf4761" rel="noopener noreferrer"&gt;lightweight threads&lt;/a&gt; whereas schedulers with reactor can incur a lot of performance overhead as they have to context switch between threads. As we progress forward in this post, people who are already familiar with composing non-reactive code in webflux will see what I mean. &lt;/p&gt;

&lt;h2&gt;
  
  
  What we will be building
&lt;/h2&gt;

&lt;p&gt;To demonstrate the way this works, we will try to develop an asynchronous api that connects to a GitHub account and gets all the starred repos for a user. The api will use spring webflux with Kotlin Flow and we will integrate that with the spring oauth2 client to ensure the user is logged in.&lt;/p&gt;

&lt;p&gt;To start off, let’s create a new gradle project in whatever IDE you prefer (I use Intellij IDEA) and add the following set of dependencies:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.security:spring-security-oauth2-client")
implementation("org.springframework.security:spring-security-oauth2-jose")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once setup, lets start building our controller first, creating a new file &lt;code&gt;Router.kt&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

@RestController
class Router {

    @Bean
    fun routes(handler: Handler) = coRouter {
        "/".nest {
            GET("", handler::getStarredRepos)
        }
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see we have simply used the normal way of defining a new spring based REST api controller but instead of using the standard router annotations we took the approach of using the coRouter. It’s more of a personal preference since it allows me to see my whole api surface area in a more concise format. However, you could easily replace it with the more common Spring MVC style annotation based approach.&lt;/p&gt;

&lt;p&gt;Next we will build a handler that let’s us handle our service response.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

@Component
class Handler(val service: Service) {

    suspend fun getStarredRepos(req: ServerRequest): ServerResponse {

        return ok().bodyAndAwait(service.getUserStarredRepos())
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Nothing special about the handler except two main distinctions when compared to equivalent reactor based approach. First is that we have made the function &lt;code&gt;getStarredRepos()&lt;/code&gt; a suspending function - which means it tells the compiler that this code will be run inside a couroutines context. The second interesting thing to note is theat we used a &lt;code&gt;bodyAndAwait()&lt;/code&gt; instead of &lt;code&gt;body()&lt;/code&gt; method for a server response. This extension allows us to correctly get the body from suspending couroutine context while the service is generating a response without blocking the calling thread.&lt;/p&gt;

&lt;p&gt;Now let’s jump into the service code - we will use the new spring Oauth2 client with comes with first class webflux support to make our app support the GitHub Oauth login.&lt;/p&gt;

&lt;p&gt;To begin with, &lt;a href="https://github.com/spring-projects/spring-security/tree/5.3.3.RELEASE/samples/boot/oauth2login#github-register-application" rel="noopener noreferrer"&gt;register a new app in Github&lt;/a&gt;. Once done, add a new &lt;code&gt;application.yml&lt;/code&gt; file and add your oauth details like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            clientId: &amp;lt;add-github-client-id-here&amp;gt;
            clientSecret: &amp;lt;add-github-client-secret-here&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This tells the Oauth client what credentials to use when redirecting a non authenticated user to Github authentication page. The default authorization callback url setup by the spring is: &lt;code&gt;http://localhost:8080/login/oauth2/code/github&lt;/code&gt; .In and of itself all oauth client will do is grab the github authorization grant and store that data in a &lt;code&gt;JSESSION&lt;/code&gt; cookie. If that's all you want to do then that's fine - however, I would also like to show the user their starred github repositories. To achieve that we need to be able to somehow get the access token and make an authenticated call on behalf of the user. Spring provides a nice and secure interface to achieve that as well - enter the authenticated webclient.&lt;/p&gt;

&lt;p&gt;Webclient is the reactive counterpart of the old and trusty RestTemplate from the Spring MVC days introduced in Spring webflux. It allows us to make calls to APIs in a non-blocking api and comes with nice composition and testing support. In our case, we will build a spring config that will populate the current context with an authenticated webclient for the current logged in user.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

@Configuration
class GithubWebClientConfig {

    @Bean
    fun webClient(clientRegistrations: ReactiveClientRegistrationRepository?,
                  authorizedClients: ServerOAuth2AuthorizedClientRepository?): WebClient? {
        val oauth = ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients)
        oauth.setDefaultOAuth2AuthorizedClient(true)
        return WebClient.builder()
            .filter(oauth)
            .build()
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The key to setting the right authentication credentials is the line &lt;code&gt;oauth.setDefaultOAuth2AuthorizedClient(true)&lt;/code&gt; which allows the github client bean to be automatically authenticated with the current users github tokens, simple and delightful.&lt;/p&gt;

&lt;p&gt;Now we'll go ahead and wire up the last piece of this puzzle which is the service layer that allows us to tie the authenticated webclient with an API call. So let's go ahead and do that:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

@Service
class Service(val client: WebClient) {

    suspend fun getUserStarredRepos(): Flow&amp;lt;String&amp;gt; {
        return client
            .get()
            .uri("https://api.github.com/user/starred?page=1")
            .retrieve()
            .bodyToFlow()
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To explain a bit of what's going in this code snippet, we have created another suspendable function &lt;code&gt;getUserStaarredRepos()&lt;/code&gt; that retrieves the starred repos from github and uses our pre authenticated oauth webclient to do so. One callout here would be the return type which is of the type &lt;code&gt;Flow&amp;lt;String&amp;gt;&lt;/code&gt; . This possible due to Kotlins extension based programming approach where the coroutines team have created an extension for the webclient which is functionally equal to reactor's &lt;code&gt;bodyToFlux()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;That concludes our build for this app. To test, let's fire it up using the ever helpful gradle &lt;code&gt;bootRun&lt;/code&gt; command (you should run this in your terminal with the current directory set to this sample project root):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

./gradlew bootRun


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once done, this will start up our Springboot app and you should be able to navigate to &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;localhost:8080&lt;/a&gt; to see it in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As someone who moved from springboot with spring mvc building blocking code to using spring webflux and spending hours to understand the non-blocking paradigm - the Flow support in webflux is a welcome evolution. The potential of migrating existing codebases to non-blocking stream based paradigm has been greatly amplified, and I can already see the impact it is having in making my own side projects a lot easier to build and reason about! &lt;/p&gt;

&lt;p&gt;If you'd like to experiment yourself the source code for this post can be found on &lt;a href="https://github.com/shavz/spring-reactive-kotlin-oauth2-github-example" rel="noopener noreferrer"&gt;github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  This post can also be found on my &lt;a href="https://www.shiveenp.com/posts/spring-boot-reactive-oauth-client-with-coroutines/" rel="noopener noreferrer"&gt;blog&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  References:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://medium.com/better-programming/asynchronous-data-loading-with-new-kotlin-flow-233f85ae1d8b" rel="noopener noreferrer"&gt;https://medium.com/better-programming/asynchronous-data-loading-with-new-kotlin-flow-233f85ae1d8b&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spring.io/blog/2019/04/12/going-reactive-with-spring-coroutines-and-kotlin-flow" rel="noopener noreferrer"&gt;https://spring.io/blog/2019/04/12/going-reactive-with-spring-coroutines-and-kotlin-flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@elizarov/execution-context-of-kotlin-flows-b8c151c9309b" rel="noopener noreferrer"&gt;https://medium.com/@elizarov/execution-context-of-kotlin-flows-b8c151c9309b&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://todd.ginsberg.com/post/springboot-reactive-kotlin-coroutines/" rel="noopener noreferrer"&gt;https://todd.ginsberg.com/post/springboot-reactive-kotlin-coroutines/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spring.io/guides/tutorials/spring-boot-oauth2/" rel="noopener noreferrer"&gt;https://spring.io/guides/tutorials/spring-boot-oauth2/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spring.io/guides/tutorials/spring-boot-oauth2/#github-register-application" rel="noopener noreferrer"&gt;https://spring.io/guides/tutorials/spring-boot-oauth2/#github-register-application&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>spring</category>
      <category>oauth</category>
      <category>coroutines</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>Right Way To Shadow Jar When Using Jetty With Http4k</title>
      <dc:creator>Shiveen Pandita</dc:creator>
      <pubDate>Sat, 18 Apr 2020 03:21:38 +0000</pubDate>
      <link>https://dev.to/shavz/right-way-to-shadow-jar-when-using-jetty-with-http4k-4n74</link>
      <guid>https://dev.to/shavz/right-way-to-shadow-jar-when-using-jetty-with-http4k-4n74</guid>
      <description>&lt;p&gt;(x-post from my blog &lt;a href="https://www.shiveenp.com/posts/fix-shadow-jar-http4k-jetty/"&gt;here&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;If you’ve been spring boot for a while, you’re probably familiar with Spring’s bootJar functionality that lets you create a new executable “fat jar” with all its dependencies pre defined. However, while I was building a kotlin app using &lt;a href="https://www.http4k.org/"&gt;http4k&lt;/a&gt;, which doesn’t come with any such built in tooling I had to resort to using the shadowJar plugin to build a fat jar.&lt;/p&gt;

&lt;p&gt;However post deploy, the app stopped starting up and I noticed the following error in the logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Exception &lt;span class="k"&gt;in &lt;/span&gt;thread “main” java.lang.ExceptionInInitializerError
    at org.eclipse.jetty.http.MimeTypes&lt;span class="nv"&gt;$Type&lt;/span&gt;.&amp;lt;init&amp;gt;&lt;span class="o"&gt;(&lt;/span&gt;MimeTypes.java:98&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.http.MimeTypes&lt;span class="nv"&gt;$Type&lt;/span&gt;.&amp;lt;clinit&amp;gt;&lt;span class="o"&gt;(&lt;/span&gt;MimeTypes.java:56&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.http.MimeTypes.&amp;lt;clinit&amp;gt;&lt;span class="o"&gt;(&lt;/span&gt;MimeTypes.java:175&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.server.handler.ContextHandler.doStart&lt;span class="o"&gt;(&lt;/span&gt;ContextHandler.java:806&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.servlet.ServletContextHandler.doStart&lt;span class="o"&gt;(&lt;/span&gt;ServletContextHandler.java:275&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start&lt;span class="o"&gt;(&lt;/span&gt;AbstractLifeCycle.java:72&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.util.component.ContainerLifeCycle.start&lt;span class="o"&gt;(&lt;/span&gt;ContainerLifeCycle.java:169&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart&lt;span class="o"&gt;(&lt;/span&gt;ContainerLifeCycle.java:110&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.server.handler.AbstractHandler.doStart&lt;span class="o"&gt;(&lt;/span&gt;AbstractHandler.java:100&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.websocket.server.WebSocketHandler.doStart&lt;span class="o"&gt;(&lt;/span&gt;WebSocketHandler.java:84&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start&lt;span class="o"&gt;(&lt;/span&gt;AbstractLifeCycle.java:72&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.util.component.ContainerLifeCycle.start&lt;span class="o"&gt;(&lt;/span&gt;ContainerLifeCycle.java:169&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.server.Server.start&lt;span class="o"&gt;(&lt;/span&gt;Server.java:407&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart&lt;span class="o"&gt;(&lt;/span&gt;ContainerLifeCycle.java:110&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.server.handler.AbstractHandler.doStart&lt;span class="o"&gt;(&lt;/span&gt;AbstractHandler.java:100&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.server.Server.doStart&lt;span class="o"&gt;(&lt;/span&gt;Server.java:371&lt;span class="o"&gt;)&lt;/span&gt;
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start&lt;span class="o"&gt;(&lt;/span&gt;AbstractLifeCycle.java:72&lt;span class="o"&gt;)&lt;/span&gt;
    at org.http4k.server.Jetty&lt;span class="nv"&gt;$toServer$3&lt;/span&gt;.start&lt;span class="o"&gt;(&lt;/span&gt;jetty.kt:33&lt;span class="o"&gt;)&lt;/span&gt;
    at io.taggit.AppKt.main&lt;span class="o"&gt;(&lt;/span&gt;App.kt:175&lt;span class="o"&gt;)&lt;/span&gt;
    at io.taggit.AppKt.main&lt;span class="o"&gt;(&lt;/span&gt;App.kt&lt;span class="o"&gt;)&lt;/span&gt;
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
    at org.eclipse.jetty.http.PreEncodedHttpField.&amp;lt;clinit&amp;gt;&lt;span class="o"&gt;(&lt;/span&gt;PreEncodedHttpField.java:68&lt;span class="o"&gt;)&lt;/span&gt;
    … 20 more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perplexed, since when I used the same plugin with another one of my projects I had never noticed this error. &lt;/p&gt;

&lt;p&gt;After much googling, turns out Jetty bundles HttpField encoders by referencing &lt;code&gt;META-INF/services/org.eclipse.jetty.http.HttpFieldPreEncoder&lt;/code&gt; so when shadow jar created the fat jar it didn’t have the data that Jetty was looking for which in turn meant Jetty threw an initialisation exception.&lt;/p&gt;

&lt;p&gt;The fix?&lt;/p&gt;

&lt;p&gt;Adding the &lt;code&gt;mergeServiceFiles()&lt;/code&gt; attribute to the shadowJar task, as per the shadow documentation &lt;a href="https://imperceptiblethoughts.com/shadow/configuration/merging/#merging-service-descriptor-files"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;shadowJar&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;baseName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="n"&gt;taggit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;
    &lt;span class="n"&gt;zip64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="n"&gt;mergeServiceFiles&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And voila, the service was back up and running again. This is becauase multiple libraries potentially use the same service descriptor files (usually &lt;code&gt;META-INF&lt;/code&gt;) and in case of creating fat jars its generally desired to merge the service descriptors to make sure all libraries have their service descriptors loaded at runtime.&lt;/p&gt;

&lt;p&gt;P.S. - It might be worth mentioning here as well that the reason I didn’t encounter the same error for one of my other services is due to that project using Netty as the underlying webserver.&lt;/p&gt;

&lt;p&gt;Cover Photo by Markus Spiske on Unsplash&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>http4k</category>
    </item>
    <item>
      <title>Software Engineering Levels</title>
      <dc:creator>Shiveen Pandita</dc:creator>
      <pubDate>Sun, 05 Jan 2020 02:17:02 +0000</pubDate>
      <link>https://dev.to/shavz/software-engineering-levels-35p0</link>
      <guid>https://dev.to/shavz/software-engineering-levels-35p0</guid>
      <description>&lt;p&gt;For many software engineers, SE job titles hold a &lt;a href="https://blog.usejournal.com/the-software-engineering-job-ladder-4bf70b4c24f3" rel="noopener noreferrer"&gt;special place&lt;/a&gt;. No other topic elicits a more passionate response than when an engineer thinks that their ranking in the pecking order is threatened by an unworthy usurper in their company. The pattern is the same, whether it be a 1000+ tech drone corporate or small tight-knit startup. &lt;/p&gt;

&lt;p&gt;Although initially used to define various steps in the career ladder &lt;sup&gt;&lt;a href="https://www.coderhood.com/software-engineering-job-titles/" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/sup&gt; which meant more prestige from the colleagues and more importantly, better compensation from the employer - the ranks have warped into a pissing contest between new developers in the industry. The way I like to look at it is that the ranks are purely indicative of the level of impact, and the breadth of responsibility that any engineering individual in the company has. Unlike popular opinion, I don't think it's necessarily tied to experience either - I have seen engineers with &amp;lt; 2 years of experience handling complex architectural problems and conversely seen someone with &amp;gt; 10 years of experience in the industry struggling with basic programming concepts. There's also the matter of company size, a mid level at a large company might be a senior at a small company and vice versa. The career progression paths are different in every company and where you might lie based on that should also be taken into account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F224w97ouzvgf46pp9uhv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F224w97ouzvgf46pp9uhv.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For someone who has been coding since the age of 13 and has had various jobs at different levels in a professional setting for the past five years, I felt like now was the time to put in writing my thoughts on what constitutes developers at various levels. It is worth noting however that whatever you read here isn't definitive, and as always factors outside of technical and soft skills should be taken into account. &lt;/p&gt;

&lt;h3&gt;
  
  
  Junior Software Engineer
&lt;/h3&gt;

&lt;p&gt;This is someone who's just tasted the wonderful life of what it means to make machines do mans bidding. More often than not, people that are put in this category are either fresh out of university with a degree that had some programming background or have just graduated from an academy, or an institute specializing in teaching people how to code.&lt;/p&gt;

&lt;p&gt;In my opinion, such candidates are the lifeblood of any organisation, and they should be given space, time and most importantly mentorship, to grow. Usually the expectations for people in this role are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write code.&lt;/strong&gt; As simple as that. As a junior, the expectations are not multi dimensional, and the company would thank you for just writing code and getting s*&amp;amp;$ done! You might be good at leetcode and blazingly fast at writing linkedlist implementations, but if you can't write proper javascript and css as a frontend engineer, you're already on a sinking ship. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Network and be open to feedback.&lt;/strong&gt; Perhaps the most important skill of them all when you're just starting is to always be open to feedback from peers, whether it be via pull requests submitted at work or via a mentorship sessions with someone with a bit more experience. Another important professional goal should be to network with as many people as possible within the industry. This can involve going to meetups, tech fairs, conferences, hackathons etc. Programming by nature attracts social recluses, so going to events like this can provide a safe environment to meet new people in the industry without the undue pressure of an alternative social setting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Learn various auxiliary system.&lt;/strong&gt; Professional programming isn't simply writing code, it involves using version control systems, dealing with frameworks, reading APIs and reasoning about the system from them, working with databases etc. No one would expect you to be an expert in these systems, but knowing your way around it is almost essential part of the role. Fortunately, any good company will be willing to spend time and mentorship hours to coach you on any of those topics if you'ev never done them before.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fundamentals and algorithms are good, but pick a modern framework (and if possible, stick with it).&lt;/strong&gt; As the saying goes, frameworks come and go, but the logic never changes (okay yes, I just made that up). It's hardly a secret that there always seems to be a new framework to achieve a result, this is especially true of frontend - but it's worth investing and picking up one of the popular framework to gain deep knowledge in, especially for juniors. I picked up VueJs and Spring boot as my front end and backend frameworks of choice and have never looked back. It was one of the best decisions I made as a junior.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Mid level Software Engineer
&lt;/h3&gt;

&lt;p&gt;These are the underdogs of any dev shop. Almost always underappreciated but are the ones making sure the product guys are happy with the sprint velocity. Humor aside, mid level engineers ensure the rhythmic hum of new features being churned and priority bugs being squashed withing the organisation. The people that fit in this role usually have following expectations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Working autonomously or with minimal direction.&lt;/strong&gt; A solid mid level engineer, after an initial period of acclimatisation is in tune with the product that the company is selling is able to pick up technically challenging or unassigned work without little to no effort. They may still require some guidance here and there, but for the most part the company, and their peers can be confident that they will always deliver on their assigned work.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mentoring or showing the ropes to juniors&lt;/strong&gt;. Often companies will expect mid level engineers to be showing the ropes to interns and junior software engineers - this can mean many things, however, usually involves pairing with them through onboarding and making sure onboarding docs are up to date, guiding them on fixing issues with individual components and generally being a validation checkpoint for their development queries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Comfortable in making component and micro level architectural decisions.&lt;/strong&gt; This part of mid level engineer roles, overlaps with a senior engineer/solution architects role. As a mid level engineer you're also expected autonomously make micro level code and drive technical decisions within a small 2-3 team pod. Quite frankly, most companies won't expect it but in my opinion if someone is truly looking to breach into the senior engineer and then perhaps technical leadership ladder this is an essential skill.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Senior Software Engineer
&lt;/h3&gt;

&lt;p&gt;People in these positions are usually regarded as the influencers and drivers of technical direction in a given stack/project domain for the company. Routine expectations for people in such roles are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deep knowledge of their stack and company's product domain.&lt;/strong&gt; Senior engineers are usually expected to be the go to authority on complex technical questions and architectural design decisions. They're expected to know their technical domain very well, including any quirks, and are expected to use that knowledge to solve complex product and build challenges.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Technical stewardship.&lt;/strong&gt; Most companies expect senior engineers to be the stewards of their chosen tech stack. This can manifest in various ways, for example, some are champions of their industry and are regularly given talks about a particular programming concept or tool. Others, tend to do things more privately and are consistently experimenting with better and smarter way to build systems and improve business outcomes within the company. This requires people in senior engineering position to be constantly updating their skills.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Long term focus.&lt;/strong&gt; This probably the most underappreciated part of a senior engineers roles, but which I think is what really differentiates a true senior engineer from the lot. A senior engineer is expected to always prioritise maintainability/extensibility over everything. This is part of their programming DNA, and this is what allows them to dismiss the new fancy framework of the season, or the next -killer. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Communication
&lt;/h3&gt;

&lt;p&gt;Communication is such an essential skill for engineers at all levels, so I decided to dedicate a whole section to it. Good communication skills are hard to quantify but very easy to identify. More often than not, technical and project management, inside a company would rather have engineers that can communicate but produce marginally less output over engineers that are rockstars but lack the desired communication skills. This is not universal, but it is definitely the trend.&lt;/p&gt;

&lt;p&gt;For entry level/junior engineers it's important to be to consistently ask for feedback and articulate why the code they added/changed was done in the way it did. &lt;/p&gt;

&lt;p&gt;For mid level engineers, it's important to be able communicate with juniors on the team as well as be able to document design decisions made over the course of delivering product features.&lt;/p&gt;

&lt;p&gt;For senior engineers and above it is even more critical, as a big part of their job is to communicate their ideas and have them percolate through various teams inside the development org.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Why leave the rest
&lt;/h3&gt;

&lt;p&gt;I understand that an engineering org can, and mostly is, made up of other varieties of engineers such as intern or graduate/entry level engineers, technical analysts, software architects, tech leads, devops ninja etc etc. This is post is not aimed at them and sometimes a lot of their skills will overlap with developers. I might do a follow up post in the future that targets people working in cross-cutting development roles.&lt;/p&gt;

&lt;h4&gt;
  
  
  References
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.coderhood.com/software-engineering-job-titles/" rel="noopener noreferrer"&gt;Software Engineering Job Titles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kitchensoap.com/2012/10/25/on-being-a-senior-engineer/" rel="noopener noreferrer"&gt;On Being A Senior Engineer&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This article was also posted on my blog &lt;a href="https://www.shiveenp.com/2020/01/05/software-engineering-levels/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>general</category>
      <category>software</category>
      <category>career</category>
      <category>skills</category>
    </item>
    <item>
      <title>Building a small S3 Browser in pure kotlin</title>
      <dc:creator>Shiveen Pandita</dc:creator>
      <pubDate>Wed, 14 Aug 2019 11:13:10 +0000</pubDate>
      <link>https://dev.to/shavz/building-a-small-s3-browser-in-pure-kotlin-em</link>
      <guid>https://dev.to/shavz/building-a-small-s3-browser-in-pure-kotlin-em</guid>
      <description>&lt;p&gt;(x-posted from my blog &lt;a href="https://www.shiveenp.com/2019/07/29/S3-Browser-With-Kweb/"&gt;here&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Recently I found myself with a little bit of extra development time on hand. Now usually, most of my projects start off as big grand ideas and as soon as I start working on them, I lose steam or life comes in the way and things just sit their eating dust as a private github repo.&lt;/p&gt;

&lt;p&gt;However, I stumbled upon &lt;a href="https://github.com/kwebio/kweb-core"&gt;Kweb&lt;/a&gt; which is a server side rendered web app building library, but written entirely in Kotlin. Kweb provides a nice &lt;a href="https://en.wikipedia.org/wiki/Domain-specific_language"&gt;dsl&lt;/a&gt; like interface to build web apps by programmatically defining the html elements for the app instead of writing the html and CSS by hand. For those who don't know, Kotlin provides a really nice way to build &lt;a href="https://kotlinlang.org/docs/reference/type-safe-builders.html"&gt;type safe declarative builders&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since it's all created declarative in the kotlin code, we get access coroutines, extensions and all the nice things that make working with Kotlin such a joy. For those of you who have worked with pure code based server side rendered frameworks before, this might remind you of &lt;a href="https://vaadin.com/"&gt;Vaadin&lt;/a&gt; which is an industry leader in its space, but there are subtle and not so subtle differences which you can be found on the &lt;a href="http://docs.kweb.io/en/latest/faq.html"&gt;Kweb FAQS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Going through their codebase, I though it would be a really good opportunity to try and a build a quick and easy app by just using pure kotlin. Why you ask? Cause it was quick and I could smash it out in a few hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple Kotlin S3 Client
&lt;/h2&gt;

&lt;p&gt;To start off, I wrote a basic S3 browsing class, starting off small - I copied the code from AWS examples on creating a new S# client and then added the ability to search for all public keys in a given bucket and get some metadata and download links:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bucketName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;


    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AmazonS3ClientBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withPathStyleAccessEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withEndpointConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AwsClientBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EndpointConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ap-southeast-2"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;listAllKeys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;S3Data&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;req&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ListObjectsV2Request&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;withBucketName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucketName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;withMaxKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;keyList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;S3Data&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listObjectsV2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;objectSummaries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;keyList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;S3Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$endpoint/$bucketName/${it.key}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toDouble&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;keyList&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the &lt;code&gt;listAllKeys()&lt;/code&gt; functions returns a list of all keys in that given bucket, which I can then map to a custom S3Data class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;S3Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;lastModifiedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building the UI with Kweb
&lt;/h2&gt;

&lt;p&gt;Once done, I got cracking on the UI interface. I wanted something quick and simple, crude even, just to demonstrate that it all works as proposed. So I setup a container with some form fields, a search button and a table to input the S3 region link and the name of the bucket. &lt;/p&gt;

&lt;p&gt;I also needed a table to display all the keys (sans pagination, who builds pagination in PoCs anyway? 🤠). To enable holding the data, I used something called &lt;a href="https://github.com/kwebio/kweb-core/blob/master/src/main/kotlin/io/kweb/state/KVar.kt"&gt;KVAR&lt;/a&gt; which is simply a state store used by Kweb to support propagating state changes to the web app via &lt;a href="https://en.wikipedia.org/wiki/Observer_pattern"&gt;Observer Pattern&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The following code fragment gets the initialises the S3 data Kvar (setup as an empty list initially) - which eventually propagates it to the table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fomantic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fomantic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fomantic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Welcome to S3 Browser 💻"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;keyData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;S3Data&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;loader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"class"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"ui active text loader"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;addText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Retrieving keys..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"class"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ui disabled text loader"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;createInputSegment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keyData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;createKeysTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, here is where Kwebs deep integration with kotlin really comes in handy, since it allows us to use &lt;a href="https://kotlinlang.org/docs/reference/coroutines-overview.html"&gt;kotlin coroutines&lt;/a&gt; to handle tasks with considerable i/o (such as retrieving data from an AWS bucket). I have recently started using coroutines frequently in production code and I can without doubt say they're the best way to write asynchronous tasks without worrying about threads. The low touch syntax setup and the results are so easy it almost feels like cheating.&lt;/p&gt;

&lt;p&gt;The following code fragment uses the &lt;code&gt;S3Client&lt;/code&gt; introduced earlier to launch a coroutine and when the user hits the search button, and displays a loading icon until all the data is retrieved or an error is thrown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ElementCreator&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DivElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInputSegment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;keyData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;KVar&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;S3Data&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fomantic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fomantic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;endpointInput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InputType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;placeholder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Enter S3 Endpoint Url"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bucketInput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InputType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;placeholder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Enter S3 Bucket Name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"class"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"ui primary button"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Search"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;GlobalScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"class"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ui active text loader"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;s3Client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                        &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpointInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;await&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;bucketInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;await&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;keyData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listAllKeys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;p&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ERROR_TOAST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"class"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ui disabled text loader"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNotEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;p&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SUCCESS_TOAST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"class"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ui disabled text loader"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far so good, now that we have successfully pulled in the data in out Kvar container, we can start rendering a table. Now I also wanted to show a nice little icon to show that the retrieved object was a file - and also allow the ability to click name of the key as a link so the user can download.&lt;/p&gt;

&lt;p&gt;Now Kweb as far as I could tell didn't have the ability to specify that via a DSL object, however, it does provide the ability to specify nested HTML inside a table element to add my own custom behaviour.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ElementCreator&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DivElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;createKeysTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;keyData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;KVar&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;S3Data&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"class"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"ui celled striped table"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;thead&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;th&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;th&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"File Size (in KB)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;th&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Last Modified At"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;tbody&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;keyData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;td&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data-lable"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;innerHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;i class=\"file outline icon\"&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;a target=\"_blank\" href=${it.downloadUrl} download=${it.key}&amp;gt;${it.key}&amp;lt;/a&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="nf"&gt;td&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data-lable"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"File Size"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${it.size} KB"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="nf"&gt;td&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data-lable"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"Last Modified At"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastModifiedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above creates a new table and generates a new row in the table for each public key present in the provided bucket. If no data is present, nothing gets rendered. &lt;/p&gt;

&lt;p&gt;This probably also the time to give a shoutout to the Kweb creators for an integration with &lt;a href="https://fomantic-ui.com/"&gt;Fomantic UI&lt;/a&gt; which comes pre-configured with nice UI elements. Although, the integration doesn't end there and there is a nice APi for anyone to write a new plugin with their favourite UI elements library. &lt;/p&gt;

&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;And that is all that's needed to write a simple S3 browsing web app using Kweb. &lt;a href="https://secure-scrubland-34237.herokuapp.com/"&gt;Here&lt;/a&gt; is the app deployed on heroku and the full working code is on &lt;a href="https://github.com/shavz/Bows3r"&gt;github&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Gif Demo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TPVvSEcH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://imgur.com/YoJdUxj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TPVvSEcH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://imgur.com/YoJdUxj.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>kweb</category>
      <category>web</category>
      <category>ssr</category>
    </item>
    <item>
      <title>Sending Multipart Form Data Using Spring WebTestClient</title>
      <dc:creator>Shiveen Pandita</dc:creator>
      <pubDate>Fri, 03 May 2019 11:15:22 +0000</pubDate>
      <link>https://dev.to/shavz/sending-multipart-form-data-using-spring-webtestclient-2gb7</link>
      <guid>https://dev.to/shavz/sending-multipart-form-data-using-spring-webtestclient-2gb7</guid>
      <description>&lt;p&gt;(x-posted from personal blog &lt;a href="https://www.shiveenp.com/2019/04/21/Sending-Multipart-Form-Data-Using-Spring-WebTestClient-md/" rel="noopener noreferrer"&gt;here&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;For the past year or so, I have been working extensively with spring, especially spring webflux; building scalable reactive micro services for our customers.&lt;/p&gt;

&lt;p&gt;Coming from spring MVC, learning webflux and getting used to reactive programming in general has been a great and worthy learning experience and I highly suggest going through the references section if you haven’t heard of reactive programming and/or have been thinking about giving it a go and don’t know where to start. But essentially reactive programming involves a model of creating, requesting and manipulating data in a controllable (from a consumers perspective) and non-blocking manner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#webtestclient" rel="noopener noreferrer"&gt;WebTestClient&lt;/a&gt; is a reactive testing high level http client with fluent assertions, packaged in spring web flux. Recently, while integration testing an application that accepted data as &lt;a href="https://tools.ietf.org/html/rfc7578" rel="noopener noreferrer"&gt;multipart/form-data&lt;/a&gt; I had to figure out how to test the data effectively using the webtestclient and personally found the lack of comprehensive resources on the internet lacking, so I wrote this blogpost to share my own learnings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Form Testing with Webflux
&lt;/h2&gt;

&lt;p&gt;Let’s suppose that we’re trying to send the request to fill a form api that accepts a document (image, text, plain binary etc.) and some textual data.&lt;/p&gt;

&lt;p&gt;To aid with our example, lets imagine the form is a profile setup for an document share service and takes the following input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Profile Image (&lt;em&gt;api label: profileImage&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Username (&lt;em&gt;api label: username&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Email (&lt;em&gt;api label: email&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;PDF document to share (&lt;em&gt;api label: userDocument&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For us to begin sending the data, we’ll use the spring library called &lt;a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/client/MultipartBodyBuilder.html" rel="noopener noreferrer"&gt;MultipartBodyBuilder&lt;/a&gt; which provides a nice api for setting up the body for multipart requests.&lt;/p&gt;

&lt;p&gt;To send the first part, the profile image we can set it up as:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bodyBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MultipartBodyBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;bodyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"profileImage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ClassPathResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test-image.jpg"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readBytes&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Disposition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"form-data; name=profileImage; filename=profile-image.jpg"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To explain a bit about what’s going on there, we’re simply telling the body builder to upload an image found in &lt;code&gt;src/test/resources&lt;/code&gt; folder with the name &lt;code&gt;test-image.jpg&lt;/code&gt; as the profile image part of this body. The real kicker here is setting up the &lt;strong&gt;Header&lt;/strong&gt; part as that is what’s used by the webtestclient internals (specifically the &lt;a href="https://github.com/synchronoss/nio-multipart" rel="noopener noreferrer"&gt;Synchronoss-nio&lt;/a&gt; library which webflux uses internally) to determine the type of form data being sent and how to process it.&lt;/p&gt;

&lt;p&gt;Also, note that the real file name that will get uploaded in the web server receiving the request is the &lt;code&gt;profile-image.jpg&lt;/code&gt; filename that gets sent as part of the headers,.&lt;/p&gt;

&lt;p&gt;Similar to the profile image, we can also send the document part of the whole request payload:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="n"&gt;bodyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"userDocument"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ClassPathResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user-document.pdf"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readBytes&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Disposition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"form-data; name=userDocument; filename=my-thesis.pdf"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Similar to the previous payload we test the body builder  💪 to read a file in the test resources folder called &lt;code&gt;user-document.pdf&lt;/code&gt;  as bytes and send the document with the name &lt;code&gt;my-thesis.pdf&lt;/code&gt; to the form web api. &lt;/p&gt;

&lt;p&gt;As you can already see, compared to some other ways of doing it, such as in this &lt;a href="https://www.baeldung.com/spring-rest-template-multipart-upload" rel="noopener noreferrer"&gt;excellent blog&lt;/a&gt; , using the MultipartBodyBuilder is rather conveneient. &lt;/p&gt;

&lt;p&gt;Now for the last two remaining pieces of the form api, which are usually only plain text, we can set them up as:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="n"&gt;bodyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shiveenpandita"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TEXT_PLAIN&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Disposition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"form-data; name=username"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;bodyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shiveenpandita@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TEXT_PLAIN&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Disposition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"form-data; name=email"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Woohoo! 🎉 We’ve got all our form fields wired now.&lt;/p&gt;

&lt;p&gt;Now to see it all in action and bring it all together, we can simply setup a spring integration test and use our freshly setup body builder as:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@RunWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SpringRunner&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;
&lt;span class="nd"&gt;@AutoConfigureWebTestClient&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebClientTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;webclient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebTestClient&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`test&lt;/span&gt; &lt;span class="n"&gt;webform&lt;/span&gt; &lt;span class="nf"&gt;api`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bodyBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MultipartBodyBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;bodyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"profileImage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ClassPathResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test-image.jpg"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readBytes&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Disposition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"form-data; name=profileImage; filename=profile-image.jpg"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;bodyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"userDocument"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ClassPathResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test-document.pdf"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readBytes&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Disposition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"form-data; name=userDocument; filename=my-thesis.pdf"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;bodyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shiveenpandita"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TEXT_PLAIN&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Disposition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"form-data; name=username"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;bodyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"shiveenpandita@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TEXT_PLAIN&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Disposition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"form-data; name=email"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;webClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/v1/test-api"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MULTIPART_FORM_DATA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BodyInserters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromMultipartData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bodyBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expectStatus&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;isOk&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The above code snippet will successfully send the required data to our test api and the webtestclient asserts that the response is 200 OK.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4fnzxn93w1idfpej955.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4fnzxn93w1idfpej955.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.reactivemanifesto.org/" rel="noopener noreferrer"&gt;Reactive Manifesto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#spring-webflux" rel="noopener noreferrer"&gt;Spring Webflux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lucamezzalira/awesome-reactive-programming" rel="noopener noreferrer"&gt;Awesome-List&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>kotlin</category>
      <category>spring</category>
      <category>webflux</category>
    </item>
  </channel>
</rss>
