<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Atlantbh Engineering - Medium]]></title>
        <description><![CDATA[Tech-tips and lessons learned about Software Engineering - Medium]]></description>
        <link>https://medium.com/atlantbh?source=rss----eb9022a96e98---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Atlantbh Engineering - Medium</title>
            <link>https://medium.com/atlantbh?source=rss----eb9022a96e98---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 14:45:55 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/atlantbh" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[When Shipping Meets Code: A Story of Collaboration and Innovation]]></title>
            <link>https://medium.com/atlantbh/when-shipping-meets-code-a-story-of-collaboration-and-innovation-be81fabda9e4?source=rss----eb9022a96e98---4</link>
            <guid isPermaLink="false">https://medium.com/p/be81fabda9e4</guid>
            <category><![CDATA[shipping]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Atlantbh]]></dc:creator>
            <pubDate>Wed, 05 Mar 2025 13:55:12 GMT</pubDate>
            <atom:updated>2025-03-05T13:50:06.224Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZK3lJ6SzDubQIiEf.jpg" /></figure><p>What connects an IT company from Sarajevo with one of the largest shipping firms in the U.S.? The answer is their shared vision for innovation.</p><p>This story is about building a successful long-term collaboration with our client and crafting several products that helped them overcome some of their main challenges — scaling their business and bringing several cost reductions in an industry that was once resistant to change.</p><h3>The Rise of Digitalization in the Shipping Industry</h3><p>With the constant growth of technology and the countless benefits digitalization has brought to various areas of life, the shipping industry has also opened its doors to automation. And our client recognized this from the very beginning. Investing in digitalization has led them to be one of the fastest-growing shipping companies in the US.</p><h3>Seizing Opportunity in a Time of Crisis</h3><p>At the beginning of 2020, a difficult time of the COVID-19 pandemic, when the world faced uncertainty, so did the IT industry. But for us, an opportunity for collaboration emerged, opening up a new path during a critical time. Our client recognized a need for a product that would automate and enhance one of its processes. What initially began as developing what was a primary task; as trust was established and quality was achieved, new project opportunities began to arise. Thus, in Atlantbh, a small but dedicated team of six people was formed, ready to take on this challenge.</p><h3>Marking the Starting Line</h3><p>You’ve likely purchased a frozen product at some point. When buying such products, they are kept at precisely controlled temperatures in the store, and we’ve often heard warnings against refreezing once-thawed items. However, the journey of these initially frozen products to markets across the country is a process most consumers rarely think about. Frozen and temperature-sensitive goods are transported in refrigerated trucks with strictly controlled temperatures. Even minor fluctuations can lead to spoilage and significant losses.</p><p>Before collaborating with us, the systems in place to monitor these highly sensitive temperature changes in refrigerated trucks were decentralized and required human intervention to address any issues. Therefore, it wasn’t a scalable system and had possibilities for failure as it heavily relied on the people operating the system. Automation of this system was a natural step forward, and that’s where we fit in with our first project — Trailer Monitoring.</p><p>The team that started work on the project included three Software Engineers, one Quality Assurance Engineer, one DevOps Engineer, a UX/UI designer, and a Product Manager on the team as well.</p><h3>From Concept to Code</h3><p>The project started with the goal of having a working production solution in two months. To achieve this, we worked on delivering a backend-only Spring Boot project with an event-driven architecture consisting of event generators, event processors, and a notification engine.</p><p>This approach allowed us to have a seamless data flow from receiving an external update to sending a notification email. Decoupling event generation, processing, and notifying allowed us to have a scalable, maintainable design.</p><ul><li>Event generators were isolated allowing for independent data processing in parallel.</li><li>Event processors handled all of the business logic, and they were clearly separated from the rest of the system.</li><li>The notification engine had a simple entry point allowing us to easily add or change the communication method in the future.</li></ul><p>No external libraries were used for sending and receiving these events, but rather a simple producer &gt; consumer mechanism, built to allow scaling and future changes.</p><p>We knew upfront that the number of events could be large and that we had to store each event in order to know exactly what happened at any moment. As a point of reference, a trailer sent an update approximately every hour, and there were hundreds of trailers. After researching ways to keep up with this volume of data, we ended up using <a href="https://www.timescale.com/">TimescaleDB </a>with PostgreSQL. This proved to be a superb solution allowing for storing and querying millions of rows in a matter of milliseconds. We liked it so much that we even wrote another about it.</p><p>From the pool of millions of rows of data, there was the need to make geospatial computations (distance calculations, closest points, etc.) To support this, we extended PostgreSQL with and also stored the latest state for each trailer. This made querying the current trailer state, all the trailers at some location, or the trailer path easy and fast, even after the trailer fleet expanded.</p><p>The project quickly expanded with the emergence of requests for a user interface. The UI started as a reporting dashboard at first, but over time, it became an essential tool for monitoring and controlling the trailer fleet, and it was included in the client’s onboarding process.</p><ul><li>The maintenance crew used it to check for faulty trailers.</li><li>Operation managers used it to check the daily audit and statistics.</li><li>Driver managers used it to always be up to date on the trailer state.</li><li>Admins for controlling notifications in real-time and setting schedules so no emails get lost.</li></ul><p>Requests for new features were pouring in, and the trust we had established pushed us into new waters as a new project was around the corner.</p><h3>Loading Things Up</h3><p>Thanks to the success and value of our collaboration, we were given the opportunity to expand our team and work in another important branch of the client’s business, — dealing with finding the right <strong>carrier</strong> to outsource transportation of certain goods (referenced as <strong>load</strong> in the text that follows). At the time, this whole process was completely manual — making inquiries, agreeing on the price for each load and handling common scenarios such as last-minute cancellations. The importance of this process and the scale at which it was performed made its automation a natural next step. A new team was formed, which ended up sharing members across different teams. This setup posed challenges as it introduced a new team dynamic, but it also had advantages — business logic often overlapped, and workload distribution across projects allowed for more flexibility.</p><p>The product aimed to automate the whole process from a load becoming available for transport, through offering it to specific carriers, to finally putting it up for auction where any partnering carrier could bid for it. With the new domain also came new challenges and considerations:</p><p>We explored multiple user management services, finally settling on using . We aimed to create a user-friendly solution that represents the client’s company in the best way, and that would scale with the increased number of users. We had to put procedures in place for onboarding users to a new tool and working on the product while it was actively used in production.</p><p>In case a load wasn’t accepted by any carrier it was offered to, it would get published to an auction where any of the almost a thousand partners could bid for it using the application. It was paramount to ensure the correct state of the system after a possibly large amount of user interactions at the same time. To prevent race conditions, we explored different <em>locking solutions </em>. Testing this feature was a challenge in itself, and our QA team kept improving the automation suite over time, challenging the limits of the application and exploring complex scenarios.</p><p>Besides the carrier-facing UI that lets them keep track of offers and ongoing auctions, we implemented a dashboard aimed at <strong>brokers</strong> — users monitoring the complete lifecycle of a load. It surfaced a myriad of actions they could take to resolve even the most complicated edge cases that would ultimately come up. This dashboard utilized a based communication protocol so the data presented on the dashboards would update in real-time.</p><p>The application was deployed to production after a year of development, and it has since been an invaluable resource in automating a high-yield business process.</p><h3>The Golden Age</h3><p>We were actively working on a couple of projects involving over 20 team members. The systems grew, as did our client’s trust and satisfaction. In 2023, a new opportunity emerged. Our client recognized the need to automate one of their HR processes. After extensive discussions and work on the MVP, we realized that the scope of the work extended far beyond improving a single process. With valuable experience behind us, this was an opportunity to start a project and adopt all the lessons we learned.</p><p>This system encompassed monitoring one of their yards, tracking personnel and equipment moving in and out of it, conducting various employee performance surveys, generating diverse analytics and behavioral insights, and even an in-app domain-specific task management system.</p><p>One aspect we are particularly proud of-and which significantly streamlined the development of this product-was our <strong>design process and established design system</strong>. Our design processes evolved with our projects over the years. We started with one UX/UI designer and ended with a team of four designers. Over the years, they have been building a reusable design system consisting of custom components in , which reached its peak at the time we started working on this project. Custom components were translated into UI components and used as building blocks for complex pages. This sophisticated design system, complete with a , helped turn frontend development into an intuitive, puzzle-like experience.</p><p>A distinctive characteristic of this project is that a lot of user input came from complex <strong>multi-stage questionnaires.</strong></p><ul><li>This required a scalable data model that supported different types of questions (text, multiple choice, single choice, answering by choosing from a map, etc.) as well as many ways to group them to create very specific forms</li><li>Forms on the UI were fully dynamic, powered by the backend service that was responsible for storing the full structure of the questionnaire, specific questions, and their answers</li><li>Later improvements included autosave capabilities and mechanisms for answering questions through integration with external services</li></ul><h3>Lasting Impact: More Than Just Software</h3><p>Back in 2020, we didn’t know much about the complexity of the shipping industry and its core businesses. It is actually a lot more than moving different kinds of goods from point A to point B. Its role is crucial in global supply chains, ensuring products reach businesses and consumers efficiently. The systems we built covered key aspects of this industry: fleet management, logistics coordination, and regulatory compliance, all of which contribute to efficient and timely delivery.</p><p>Four years later, hard work and dedication have exceeded expectations. The team has grown significantly and set up four different tracks, developing products of great strategic value to the client, improving their key processes, and leaving a lasting impact on the entire organization. Many business processes were automated and centralized, leading to significant cost reductions and general business scalability. As the projects grew, so did our team and individuals. We learned a lot and established some of the procedures that are used on other projects as well. Over this period, more than 30 individuals contributed to these projects, building a shared legacy that the Atlantbh company will rely on in the future.</p><p><em>Originally published at </em><a href="https://www.atlantbh.com/when-shipping-meets-code-a-story-of-collaboration-and-innovation/"><em>https://www.atlantbh.com</em></a><em> on March 4, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=be81fabda9e4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/atlantbh/when-shipping-meets-code-a-story-of-collaboration-and-innovation-be81fabda9e4">When Shipping Meets Code: A Story of Collaboration and Innovation</a> was originally published in <a href="https://medium.com/atlantbh">Atlantbh Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Consuming and Storing Kafka Messages in Snowflake Using Kafka Connect (Part 2)]]></title>
            <link>https://medium.com/atlantbh/consuming-and-storing-kafka-messages-in-snowflake-using-kafka-connect-part-2-580fdb25bdb6?source=rss----eb9022a96e98---4</link>
            <guid isPermaLink="false">https://medium.com/p/580fdb25bdb6</guid>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[kafka-connect]]></category>
            <category><![CDATA[snowflake]]></category>
            <dc:creator><![CDATA[Darko Kojović]]></dc:creator>
            <pubDate>Tue, 12 Nov 2024 14:11:31 GMT</pubDate>
            <atom:updated>2024-11-12T14:11:30.985Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EdziDTDSBr18R0ugXhyaXw.jpeg" /></figure><p>In the previous <a href="https://www.atlantbh.com/kafka-connect/">blog</a> we explained what Kafka Connect is, what we can do with it, and what are some of the most important components that we can use to establish a reliable and fault-tolerant big data pipeline.</p><p>In this blog, we will deploy a small-scale but production-ready Kafka Connect cluster where we will take Avro and Protobuf messages from different topics and store them in Snowflake tables. We will use Schema Registry to validate our Avro and Protobuf schemas.</p><p>In theory, it seems like a straightforward process but in practice, it can get a bit complex especially if you are setting up Kafka Connect for the first time. So many properties can be easily overlooked, but precisely those properties can save you from losing your precious Kafka messages.</p><p>We will go over the whole process and show you how we can set up a production-ready pipeline from scratch.</p><h3>Environment setup</h3><ul><li>Kubernetes cluster*</li><li>Kafka cluster</li><li>Schema registry</li><li>Snowflake account<br><em>*Kubernetes cluster is not required, but if you plan to set up Kafka Connect somewhere else (like EC2, VMs), you must make some adjustments.</em></li></ul><p>All the above services (besides Snowflake) can be deployed locally or you can use managed services.<br>To make this blog less complex since the main focus is on Kafka Connect, (for our example), we will use <a href="https://confluent.cloud/">Confluent Platform</a> for the Kafka cluster and schema registry.</p><p>We understand that not everybody can use Confluent Platform for commercial projects, and you do not have to. The process is not tied to Confluent, which can be achieved without using any Confluent products; instead, it can be done using open-source software.For reference, our company is not using anything related to Confluent, not even the schema registry or converters.<br>If you want to use Confluent Platform to follow along, there is a 30 day free trial that is more than enough to replicate everything from this blog. The same 30 day trial exists in Snowflake too.</p><p>To confirm that it also works without the Confluent Platform we also deployed Kafka using <a href="https://github.com/banzaicloud/koperator">Koperator</a> on our Kubernetes cluster, and the setup was the same. For the schema registry, you can try out <a href="https://github.com/Aiven-Open/karapace">Karapace</a>.</p><h3>Setting up Kafka Connect to store messages in Snowflake</h3><p>To make our example more straightforward, we will imagine that we are a <strong>sushi restaurant</strong> that uses Kafka, Kubernetes and Snowflake to manage their customers, orders and reviews (this is probably not the best approach but is easier to understand). Even though we will use a small sample dataset, the same setup will work with any dataset size.</p><p>Our company is using a similar pipeline in production to process millions of records each day, and it works without any issues. Kafka Connect clusters are horizontally scalable, which means that you will only need to increase the replica count to support larger workloads.</p><p>Our application will have three topics: two will use <strong>Protobuf,</strong> and one will use <strong>Avro</strong>. We wanted to cover multiple data serialization formats to demonstrate how to handle various different Kafka Connect clusters.</p><p>The diagram that we will follow to set up our Kafka to Snowflake pipeline is below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*292KZUj0lkxT5X4P" /></figure><p>We will be using Kubernetes, but if you want to deploy it somewhere else, the Kafka Connect setup is the same; the only difference is how you want to deploy it.</p><p>Feel free to use this Docker image <a href="https://hub.docker.com/repository/docker/dksadx/kafka-connect-snowflake/general">dksadx/kafka-connect-snowflake</a>, or if you want to deploy it on bare metal, you can use this <a href="https://github.com/DKSadx/kafka-connect-blog/blob/main/Dockerfile">Dockerfile</a> as a guide.</p><h3>Setting up the demo environment (optional)</h3><p>This section is optional; if you already have Kafka, Kubernetes, and Snowflake set up, feel free to skip this and go straight to the <a href="#746f"><strong><em>Configuring Kafka Connect</em></strong></a> section.</p><p>If you want to follow along, we need to set up a demo environment first.</p><h3>Creating a Kubernetes cluster</h3><p>We will use <a href="https://podman.io/">Podman</a> with <a href="https://kind.sigs.k8s.io/">Kind</a> to set up a local Kubernetes cluster. You can use any other tool to set up your cluster or deploy a managed cluster via your preferred cloud provider.</p><pre># Install Podman, QEMO, Kind and kubectl (MacOS only)<br>$ brew install podman qemu kind kubectl helm<br><br># Create and start a VM for the cluster (increase resources if deploying Kafka)<br>$ podman machine init - cpus 4 - disk-size 30 - memory 8192 - now demo-vm<br><br># Create a Kubernetes cluster with Kind<br>$ KIND_EXPERIMENTAL_PROVIDER=podman kind create cluster - name kafka_connect_demo<br><br># Set the context<br>$ kubectl config set-context kafka_connect_demo<br></pre><p>Check if everything works correctly:</p><pre>$ kubectl get all<br>#   NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE<br>#   service/kubernetes   ClusterIP   10.96.0.1    &lt;none&gt;        443/TCP   2m48s</pre><h3>Setting up Kafka on Kubernetes</h3><p>If you do not have a Kafka cluster, you can create one for free via the <a href="https://confluent.cloud/">Confluent Platform</a>.<br>After creating the cluster, create three topics called <strong>avro-customers</strong>, <strong>proto-orders</strong>, and <strong>proto-reviews</strong>.</p><p>If you do not want to use the Confluent Platform and instead want to deploy it locally on the Kubernetes cluster we previously created, the easiest way to set it up is via the Koperator Helm chart. To set up the latest and most up-to-date version of the chart, use their guide, which can be found in the README.md <a href="https://github.com/banzaicloud/koperator/blob/master/README.md">here</a>.</p><p>After the setup, we can create three topics for our sushi demo app.</p><pre>kubectl create -n kafka -f - &lt;&lt;EOF<br>apiVersion: kafka.banzaicloud.io/v1alpha1<br>kind: KafkaTopic<br>metadata:<br>    name: avro-customers<br>spec:<br>    clusterRef:<br>        name: kafka<br>    name: customers<br>    partitions: 1<br>    replicationFactor: 1<br>    config:<br>        &quot;retention.ms&quot;: &quot;604800000&quot;<br>        &quot;cleanup.policy&quot;: &quot;delete&quot;<br>EOF</pre><p>Do the same for the other two topics: <strong>proto-reviews</strong>, and <strong>proto-orders</strong>.</p><h3>Setting up the Snowflake database</h3><p>For storing Kafka messages, we will use Snowflake, and thus, we will need a Snowflake account. If you do not have an account, you can sign up for a free 30-day trial on their page: <a href="http://www.snowflake.com/">www.snowflake.com</a>.<br>After creating an account, we will need to create a warehouse. If you are not familiar with Snowflake, warehouses are basically compute instances in Snowflake.</p><p>You can create a warehouse by going to the Admin panel and clicking the blue <strong>+ WAREHOUSE</strong> button in the top right corner. Choose XS size for this demo.</p><p>After creating a warehouse, go to <strong>Projects -&gt; Worksheets -&gt; SQL Worksheet</strong>.</p><p>Now, we need to configure Snowflake so that we can actually use the Kafka Connect connector.</p><p>There are few steps that we have to do:</p><ol><li>Create a database and schema:</li></ol><pre>CREATE DATABASE kafka_connect_demo;<br>CREATE SCHEMA kafka_connect_demo.sushi_restaurant;</pre><p>2. Create user, role and configure all the privileges:</p><pre>-- Use a role that can create and manage roles and privileges.<br>USE ROLE accountadmin;<br><br>-- Create a Snowflake role with the privileges to work with the connector.<br>CREATE ROLE kafka_connector_role_sushi;<br><br>-- Create a Snowflake user that will be used by the kafka connect connector to access Snowflake.<br>CREATE USER IF NOT EXISTS kafka_connect_sushi_user<br>    PASSWORD = &quot;my_very_strong_password&quot;<br>    LOGIN_NAME = &quot;kafka_connect_sushi_user&quot;<br>    DEFAULT_ROLE = &quot;kafka_connector_role_sushi&quot;<br>    DEFAULT_SECONDARY_ROLES = (&#39;ALL&#39;);<br><br>-- Grant privileges on the database.<br>GRANT USAGE ON DATABASE kafka_connect_demo TO ROLE kafka_connector_role_sushi;<br><br>-- Grant privileges on the schema.<br>GRANT USAGE ON SCHEMA kafka_connect_demo.sushi_restaurant TO ROLE kafka_connector_role_sushi;<br>GRANT CREATE TABLE ON SCHEMA kafka_connect_demo.sushi_restaurant TO ROLE kafka_connector_role_sushi;<br>GRANT CREATE STAGE ON SCHEMA kafka_connect_demo.sushi_restaurant TO ROLE kafka_connector_role_sushi;<br>GRANT CREATE PIPE ON SCHEMA kafka_connect_demo.sushi_restaurant TO ROLE kafka_connector_role_sushi;<br><br>-- Grant the custom role to an existing user.<br>GRANT ROLE kafka_connector_role_sushi TO USER kafka_connect_sushi_user;<br><br>-- Grant the custom role to your user (assuming you are using ACCOUNTADMIN)<br>GRANT ROLE kafka_connector_role_sushi TO USER ACCOUNTADMIN;<br><br>-- Set the custom role as the default role for the user.<br>-- If you encounter an &#39;Insufficient privileges&#39; error, verify the role that has the OWNERSHIP privilege on the user.<br>ALTER USER kafka_connect_sushi_user SET DEFAULT_ROLE = kafka_connector_role_sushi;<br><br>-- Grant privileges on a warehouse to our role.<br>GRANT ALL PRIVILEGES ON WAREHOUSE kafkaconnect TO ROLE kafka_connector_role_sushi;</pre><p>3. Generate an RSA key pair. If you are unfamiliar with how to do that, you can use this guide: <a href="https://docs.snowflake.com/en/user-guide/kafka-connector-install#using-key-pair-authentication-key-rotation">Using key pair authentication &amp; key rotation</a>.</p><p>4. Add the public key to the user you created in step 2, in our case the user is called <strong><em>kafka_connect_sushi_user</em></strong>.<br>You must remove all the new lines and also the headers and footers (<em> — — -BEGIN PUBLIC KEY — — — , — — -END PUBLIC KEY — — -</em>) from the public key.</p><pre>-- Add RSA public key to user. This is how your public key needs to look like, without headers and new lines.<br>ALTER USER kafka_connect_sushi_user SET RSA_PUBLIC_KEY=&#39;MIIBIj...&#39;;</pre><p>5. While still signed in to Snowflake, go to the home pages and save the url. The url should look like this: <strong><em>https://app.snowflake.com/&lt;orgname&gt;/&lt;accountname&gt;/worksheets</em></strong>.<br>We will need this information when setting up Kafka Connect.</p><h3>Setting up Schema Registry</h3><p>Having a schema registry is not required, and you can do the whole setup without it but I highly recommend using it. If you are working or planning to work on a larger project, having a schema registry can save you a lot of trouble. If you do not plan to use schema registry, skip this section and remove all config properties that have schema in its name and you should be good to go.</p><p>To set up a schema registry there are two choices:</p><ol><li>sign up to <a href="https://www.confluent.io/">Confluent Platform</a> and use schema registry as a service,</li><li>host your own.</li></ol><p>Whichever option you choose, the rest will be the same; only the self-hosted one will require additional steps. To reduce the complexity of the blog, we will use <a href="https://www.confluent.io/">Confluent Platform</a> for this demo.</p><p>If you choose to use Confluent Platform, go to their site, create an account, and start a 30-day free trial.</p><p>After finishing all the required steps, your environment and cluster should be ready. The only thing that we need to do here is find the endpoint url and generate an API key. You can do that by clicking on your environment and in the right sidebar you can find the endpoint. Below the endpoint, you can generate an API key. Save the above info because we will need it in the next step.</p><h3>Configuring Kafka Connect</h3><p>After everything is set up and deployed, we are ready for the main part, configuring and deploying Kafka Connect clusters.<br>For our example, we will need three clusters since we have avro, protobuf, and string converters.</p><p>We will configure and deploy Kafka Connect in a distributed mode because it is the most reliable and fault-tolerant. In most cases, you will want to use this mode.</p><p>There are two configuration files that we need to edit:</p><ol><li><strong>connect-distributed.properties</strong> — a file that configures the Kafka Connect cluster,</li><li><strong>snowflake.json</strong> — file that configures the Snowflake connector.</li></ol><p>I will not go into too much detail about what all properties mean because you can find that in the official documentation. I will go over the most interesting ones for our use case.</p><p><strong>connect-distributed.properties<br></strong>This file configures the Kafka Connect cluster, it does not configure any connectors. We will add connectors after the cluster is running and in a healthy state.</p><p>Since we are deploying our cluster in distributed mode, we will need three additional topics that will be used to store the configs, statuses, and offsets for the clusters.</p><p>This is what the configuration for our avro cluster looks like:</p><pre># Unique name for the cluster, used in forming the Connect cluster group. Note that this must not conflict with consumer group IDs<br>group.id=kafka-connect-avro<br><br># Topic to use for storing offsets. This topic should have many partitions and be replicated and compacted.<br># Kafka Connect will attempt to create the topic automatically when needed, but you can always manually create<br># the topic before starting Kafka Connect if a specific topic configuration is needed.<br>offset.storage.topic=kafka-connect-avro-offsets<br>offset.storage.replication.factor=3<br>offset.storage.partitions=3<br><br># Topic to use for storing connector and task configurations; note that this should be a single partition, highly replicated,<br># and compacted topic. Kafka Connect will attempt to create the topic automatically when needed, but you can always manually create<br># the topic before starting Kafka Connect if a specific topic configuration is needed.<br>config.storage.topic=kafka-connect-avro-configs<br>config.storage.replication.factor=3<br>config.storage.partitions=1<br><br># Topic to use for storing statuses. This topic can have multiple partitions and should be replicated and compacted.<br># Kafka Connect will attempt to create the topic automatically when needed, but you can always manually create<br># the topic before starting Kafka Connect if a specific topic configuration is needed.<br>status.storage.topic=kafka-connect-avro-statuses<br>status.storage.replication.factor=3<br>status.storage.partitions=3</pre><p>You can create the three above topics yourself or let Kafka Connect create it for you.</p><p>To configure the schema registry, we will use the information that we got from the previous step:</p><pre>value.converter.basic.auth.credentials.source=USER_INFO<br>value.converter.basic.auth.user.info=YOUR_API_KEY:YOUR_API_SECRET<br>value.converter.expected.schema.name=customers<br>value.converter.schema.registry.url=https://YOUR_ACCOUNT.confluent.cloud</pre><p>The most interesting properties for us are the converter properties because if those are wrong, we will not be able to serialize or deserialize messages correctly.</p><p>We need to define which converters we want to use for both keys and values. Usually you can use <strong>StringConverter</strong> for keys but for values you need specific converters.</p><p>There are many converters out there, some are officially supported and some are custom made for specific use cases by the community. An example of such a converter is <a href="https://github.com/farmdawgnation/registryless-avro-converter">registryless-avro-converter</a>, which is an avro converter that does not depend on the Confluent Schema Registry.</p><p>We will use 3 types of converters in our example:</p><ol><li>Avro — <strong>io.confluent.connect.avro.AvroConverter</strong></li><li>Protobuf —<strong> io.confluent.connect.protobuf.ProtobufConverter</strong></li><li>String — <strong>org.apache.kafka.connect.storage.StringConverter</strong></li></ol><pre>key.converter=org.apache.kafka.connect.storage.StringConverter<br># Using avro converter<br>value.converter=io.confluent.connect.avro.AvroConverter</pre><p><strong>snowflake.json<br></strong>This file configures the Snowflake connector for Kafka Connect. It is a JSON file with all the secrets and configurations required for the connector to function. Since this is a connector configuration file, we do not specify it while starting the cluster; instead, we need to send a POST request with the file content as payload to the Kafka Connect endpoint once the cluster is up and running.</p><p>You can send the request from anywhere; it does not need to be from the container or pod, but I recommend that you do not expose the Kafka Connect endpoint externally if you really do not need to, mainly for security reasons.</p><p>Having the <strong>snowflake.json</strong> file along with the <strong>connect-distributed.properties</strong> file can be beneficial, as it allows us to recreate the connector if something unexpected happens automatically.<br>If you are running the cluster for months, something may break the cluster, and having a backup solution will be a lifesaver in those moments.</p><p>It rarely happens, but occurs sometimes, so configuring liveness and startup probes to take care of this is a big plus.</p><p>You can find detailed instructions on configuring a Snowflake connector in their official documentation <a href="https://docs.snowflake.com/en/user-guide/kafka-connector-install">here</a>.<br>Most of the configuration is self-explanatory but I will point out a few fields that may cause problems:</p><p><strong>name<br></strong>Name of the connector. This name will be part of the url, and it will be used when performing any actions on the connector, e.g., updating the config properties. If you plan to have multiple connectors on a single cluster, make sure they have different names.</p><p><strong>snowflake.url.name<br></strong>Format for the url is <strong>&lt;orgname&gt;-&lt;accountname&gt;.snowflakecomputing.com:443</strong>. You can find the <strong>&lt;orgname&gt;</strong> and <strong>&lt;accountname&gt;</strong> in the url when you log in to your account via the browser.<br>The url should be similar to this: <a href="https://app.snowflake.com/kalsvgw/ab20210/worksheets"><em>https://app.snowflake.com/kalsvgw/ab20210/worksheets</em></a> where <strong>&lt;orgname&gt;</strong> is <em>kalsvgw</em> and <strong>&lt;accountname&gt;</strong> is <em>ab20210</em>.</p><p><strong>snowflake.private.key<br></strong>This is the <strong>encrypted</strong> RSA key that we generated in the section where we did the Snowflake setup. Make sure that you remove all <strong>line breaks, whitespace and headers/footers</strong> before pasting the key.</p><p><strong>snowflake.private.key.passphrase<br></strong>This is the passphrase used while creating the key.</p><h3>Deploying Kafka Connect clusters</h3><p>We are almost ready to deploy our Kafka Connect clusters once the configurations for Kafka Connect and Snowflake connector are ready.</p><p>Starting the Kafka Connect in distributed mode is as simple as running <strong>the connect-distributed.sh</strong> script that comes with every Kafka installation and providing the configuration properties as the first argument:</p><pre>$ ./connect-distributed.sh connect-distributed.properties</pre><p>Keep in mind that you need different dependencies (jar files) for different connectors and converters. To see which dependencies are required for our example you can check this <a href="https://github.com/DKSadx/kafka-connect-blog/blob/main/Dockerfile">Dockerfile</a>.</p><p>If everything is configured correctly, we will see that three new topics for offsets, statuses, and configs appeared in our Kafka cluster. These topics are used by the cluster instances/replicas to communicate with each other and distribute the load.</p><p>After the cluster is up and there are no errors in the logs, we can add connectors. To add a new connector, we need to send a POST request to the cluster’s endpoint:</p><pre>curl -X POST -H &quot;Content-Type: application/json&quot; - data @snowflake.json http://localhost:8083/connectors</pre><p>We should see messages arriving in our Snowflake tables if everything is configured correctly.</p><p>Since we will be deploying the cluster in Kubernetes for our sushi restaurant example, we prepared a Helm chart that will take care of most of the setup. We only have to configure some of the cluster and connector properties like bootstrap servers and credentials.</p><ol><li>First we need to get the chart</li></ol><pre>git clone https://github.com/DKSadx/kafka-connect-blog.git<br>cd deploy/helm</pre><p>2. You will see three types of configuration files in the <strong>configs</strong> directory, <strong>connect-distributed-&lt;TYPE&gt;.properties</strong>, <strong>snowflake-&lt;TYPE&gt;.json</strong>, and <strong>config.yaml</strong>. Fill out the empty fields with your Kafka and Snowflake information in all of those configs.<br>If you are confused about what <strong>config.yaml</strong> is, that is a configuration file that is used by our simple demo Go app called <strong>sushi-restaurant-kafka-demo-app</strong>.<br>The purpose of the app is to read some example dummy messages from <em>app/data/</em> directory and publish those messages to corresponding topics specified in config.yaml.<br>Besides topics, the configuration file also contains information about the Kafka cluster so that it can connect and publish those messages.</p><p>3. After everything is configured, we are ready to install the helm chart:</p><pre>helm install sushi-restaurant .</pre><p>Let’s check the pods and wait for everything to be in <em>RUNNING</em> state:</p><pre>$ kubectl get pods<br>NAME                                                          READY   STATUS      RESTARTS   AGE<br>demo-app-dqw6n                                                0/1     Completed   0          9s<br>sushi-restaurant-customers-avro-0-c8d7c8689-brmm7             1/1     Running     0          9s<br>sushi-restaurant-customers-avro-0-c8d7c8689-sh4h9             1/1     Running     0          9s<br>sushi-restaurant-dlq-string-2-56f7cf687-58qb9                 1/1     Running     0          9s<br>sushi-restaurant-dlq-string-2-56f7cf687-kwhpg                 1/1     Running     0          9s<br>sushi-restaurant-reviews-orders-protobuf-1-6546fd87fb-5882s   0/1     Running     0          9s<br>sushi-restaurant-reviews-orders-protobuf-1-6546fd87fb-9dskd   0/1     Running     0          9s</pre><p>When you inspect the logs of the demo app, they need to look like this. If that is the case, that means that the messages are being published to our topics:</p><pre>$ kubectl get logs demo-app-dqw6n<br><br>2024/09/02 12:08:40 Created Producer rdkafka#producer-1<br>2024/09/02 12:08:40 Publishing orders to proto-orders topic<br>2024/09/02 12:08:40 Publishing customers to avro-customers topic<br>2024/09/02 12:08:40 Publishing reviews to proto-reviews topic<br>2024/09/02 12:08:41 Delivered message to topic proto-reviews [1] at offset 0<br>2024/09/02 12:08:41 Delivered message to topic proto-orders [1] at offset 0<br>2024/09/02 12:08:41 Delivered message to topic avro-customers [0] at offset 0<br>2024/09/02 12:08:41 Delivered message to topic proto-orders [1] at offset 1<br>2024/09/02 12:08:41 Delivered message to topic avro-customers [1] at offset 0<br>2024/09/02 12:08:41 Delivered message to topic proto-orders [1] at offset 2</pre><p>When you now check logs of any of the Kafka Connect pods, if the log looks similar to this, then it means that messages are successfully being consumed from topics and pushed to Snowflake:</p><pre>$ kubectl logs sushi-restaurant-customers-avro-0-c8d7c8689-6gqkj <br><br>2024-09-02 12:11:32,784] INFO [snowflake_sushi_customers|task-2] [SF_INGEST] Channel=CUSTOMERS_2 created for table=CUSTOMERS (net.snowflake.ingest.streaming.internal.SnowflakeStreamingIngestChannelInternal:58)<br>[2024-09-02 12:11:33,140] INFO [snowflake_sushi_customers|task-0] [SF_KAFKA_CONNECTOR] Fetched offsetToken for channelName:KAFKA_CONNECT_DEMO.SUSHI_RESTAURANT.CUSTOMERS.CUSTOMERS_0, offset:1 (com.snowflake.kafka.connector.internal.streaming.TopicPartitionChannel:46)<br>[2024-09-02 12:11:33,168] INFO [snowflake_sushi_customers|task-0] [SF_KAFKA_CONNECTOR] task opened with 1 partitions, execution time: 2955 milliseconds (com.snowflake.kafka.connector.SnowflakeSinkTask:46)<br>[2024-09-02 12:11:33,169] INFO [snowflake_sushi_customers|task-0] [Consumer clientId=connector-consumer-snowflake_sushi_customers-0, groupId=avro-customers] Seeking to offset 2 for partition customers-0 (org.apache.kafka.clients.consumer.KafkaConsumer:1585)<br>[2024-09-02 12:11:33,469] INFO [snowflake_sushi_customers|task-2] [SF_KAFKA_CONNECTOR] Fetched offsetToken for channelName:KAFKA_CONNECT_DEMO.SUSHI_RESTAURANT.CUSTOMERS.CUSTOMERS_2, offset:2 (com.snowflake.kafka.connector.internal.streaming.TopicPartitionChannel:46)<br>[2024-09-02 12:11:33,471] INFO [snowflake_sushi_customers|task-2] [SF_KAFKA_CONNECTOR] task opened with 1 partitions, execution time: 3255 milliseconds (com.snowflake.kafka.connector.SnowflakeSinkTask:46)<br>[2024-09-02 12:11:33,471] INFO [snowflake_sushi_customers|task-2] [Consumer clientId=connector-consumer-snowflake_sushi_customers-2, groupId=avro-customers] Seeking to offset 3 for partition customers-2 (org.apache.kafka.clients.consumer.KafkaConsumer:1585)<br>[2024-09-02 12:11:36,038] INFO [snowflake_sushi_customers|task-0] [SF_KAFKA_CONNECTOR] Fetched offsetToken for channelName:KAFKA_CONNECT_DEMO.SUSHI_RESTAURANT.CUSTOMERS.CUSTOMERS_0, offset:1 (com.snowflake.kafka.connector.internal.streaming.TopicPartitionChannel:46)</pre><p>You are probably wondering, how does it work if we did not add the Snowflake connector. The reason for this is that we automated the process using <strong>Startup Probes</strong> and this <a href="https://github.com/DKSadx/kafka-connect-blog/blob/main/scripts/restart-connector.sh">script</a>. The script will check if the connector exists, and if not, it will add it automatically.</p><p>Now, if we go to Snowflake and check any of the new tables, you will see the example messages there:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*iECbjCPJVJ4GEXuz" /></figure><h3>Dead letter queue to the rescue</h3><p>Great, the pipeline works, but what happens if somebody sends a message that was serialized using a different converter, e.g., sends a protobuf message on an avro topic.</p><p>We can not ignore those messages, maybe those are some really important transactions that were sent by mistake.</p><p>To handle such cases, we need a <strong>dead letter queue</strong> or <strong>DLQ</strong>.</p><p>If you remember from the previous <a href="https://www.atlantbh.com/kafka-connect/">blog</a> or check the example configuration file <strong>snowflake.json</strong> in the Helm chart, you will see that DLQ properties are already configured:</p><pre>&quot;errors.deadletterqueue.topic.name&quot;: &quot;kafka_connect_dlq&quot;,<br>&quot;errors.deadletterqueue.topic.replication.factor&quot;: 3,<br>&quot;errors.deadletterqueue.context.headers.enable&quot;: true</pre><p>You will also see one more property that is not directly related to DLQ but is important to set it up correctly.</p><pre>&quot;errors.tolerance&quot;: &quot;all&quot;</pre><p>By default, error.tolerance is set to none, which means that if a message can not be deserialized, the Kafka Connect instance will throw an error and stop.</p><p>For some use cases, that can be exactly what we want, but for some, we can not afford to stop the whole pipeline if someone publishes a message to a wrong topic. In such cases it is good to have a Kafka Connect cluster specifically for DLQ with the property <strong><em>error.tolerance</em></strong> set to <strong><em>all</em></strong>.</p><p>With that approach you have a dedicated cluster that will take all the failed messages that went to the DLQ and push those messages to Snowflake as they are.</p><p>Now, we can continue consuming messages while also saving the failed messages to be analyzed later. For production environments, you will want to configure <a href="https://docs.snowflake.com/en/guides-overview-alerts">alerts</a> on the Snowflake side so that you will be notified if new messages are added to the DLQ table.</p><h3>Conclusion</h3><p>When faced with the challenge of transferring messages from Kafka to another destination, in our case, it was Snowflake, Kafka Connect will be the best approach for most cases.</p><p>We used Snowflake as an example since we are using it heavily on our projects and it is becoming a go-to platform when it comes to big data pipelines. Snowflake is just one sink connector, but there are hundreds more. If you want to take messages from Kafka topics and upload them somewhere or vice-versa, there is a good chance that someone already wrote a connector for that.</p><p>Having written custom consumers and discovering bugs when least expecting them is never a good feeling. That is one of the reasons why using a battle-tested tool with many users and a large community behind it is, in most cases, the way to go.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=580fdb25bdb6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/atlantbh/consuming-and-storing-kafka-messages-in-snowflake-using-kafka-connect-part-2-580fdb25bdb6">Consuming and Storing Kafka Messages in Snowflake Using Kafka Connect (Part 2)</a> was originally published in <a href="https://medium.com/atlantbh">Atlantbh Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Consuming and Storing Kafka Messages in Snowflake Using Kafka Connect (Part 1)]]></title>
            <link>https://medium.com/atlantbh/consuming-and-storing-kafka-messages-in-snowflake-using-kafka-connect-part-1-f4a14a692b3a?source=rss----eb9022a96e98---4</link>
            <guid isPermaLink="false">https://medium.com/p/f4a14a692b3a</guid>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[snowflake]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[kafka-connect]]></category>
            <dc:creator><![CDATA[Darko Kojović]]></dc:creator>
            <pubDate>Thu, 16 May 2024 14:35:50 GMT</pubDate>
            <atom:updated>2024-05-16T14:43:35.154Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TfJ1RM_NbxKAYO-QOVS20A.jpeg" /></figure><p>Every year, zettabytes of data are transferred over the Internet. Managing, processing, and storing the data can be a really complex task and requires cutting-edge tools. Many tools can be used, but two technologies that are almost essential to every modern data pipeline: <strong>Kafka</strong> and <strong>Snowflake</strong>.</p><p>If you are planning to stream a large amount of data, Kafka is usually the best choice. Scalability, reliability, low latency and popularity are reasons why Kafka stands out. Kafka is by far the most popular event streaming platform and a go-to for many cases when it comes to streaming data.</p><p>Since you need to process and store the data somewhere, Snowflake is one of the top contenders for that. If you are wondering why Snowflake is so popular, look at the user satisfaction — it’s over the roof. Snowflake took most of the drawbacks and oversights that other data storage solutions had and addressed it. Probably the most important feature is having the storage and compute separately scaled.</p><h3>What is Kafka Connect, and why did we decide to use it?</h3><p>Kafka Connect is a tool for scalably and reliably streaming data between Apache Kafka and other data systems. It provides streaming integration between Kafka and other data sources or sinks, enabling seamless data movement and transformation. Kafka Connect uses connectors to ingest data into Kafka topics or export data from Kafka topics to external systems.</p><p>Kafka Connect is made for use cases like this.</p><h3>Installing Kafka Connect and choosing the correct mode</h3><p>If you search for Kafka Connect, you will see that there is no download link. That is because Kafka Connect is not a standalone tool; it is bundled with the main Kafka installation. If you communicated with Kafka from the command line before, you probably used one of the scripts from the directory like <em>kafka-topics.sh </em>. Two more scripts that are used for Kafka Connect are in the same directory as the kafka-topics.sh script. Those two scripts are for two different modes, <strong>standalone</strong> and <strong>distributed</strong>:</p><p><strong>1. connect-standalone.sh</strong> — Only runs one node of Kafka Connect and saves all the configs, offsets, and statuses locally. If the node goes down, all Kafka Connect processes are down.<br>This is okay for testing but <strong>should not be used in production</strong> as it is neither reliable nor fault-tolerant.</p><p><strong>2. connect-distributed.sh</strong> — Runs a cluster of Kafka Connect nodes. Saves all configs, offsets and statuses in specific Kafka topics designated for Kafka Connect. If one node goes down, another node will take over the workload from that node.</p><p>The connect-standalone.sh script can be used for testing, but connect-distributed.sh should be used for production.</p><h3>Kafka Connect components</h3><p>There are a few Kafka Connect components, but the most important for us are:</p><ol><li><strong>Connectors (sink and source)</strong></li><li><strong>Converters (including transforms)</strong></li><li><strong>Dead Letter Queue (DLQ)</strong></li></ol><p>If you are new to Kafka Connect, you may be confused about the difference between <strong>connectors</strong> and <strong>converters</strong>.</p><h4>Connectors</h4><p>Connectors, as the name says, connect your Kafka Connect cluster to a specific source/destination. In our case, that will be but it can also be HDFS, S3, Elasticsearch, and many more.</p><p>There are two types of connectors:</p><ol><li><strong>Source Connectors </strong>— Take the data from a source and send it to Kafka topic(s). In the diagram below, we can see how it would look if we wanted to take files from S3 and publish them to a Kafka topic.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2Ax_ZV67dXwMVSeV.png" /></figure><p><strong>2. Sink Connectors </strong>— Take the data from Kafka topic(s) and send it to a destination. In the diagram below, we can see how it would look if we wanted to take messages from a Kafka topic and store them in Snowflake.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*CLKCgb-nfh7ilOTC.png" /></figure><h4>Converters</h4><p>Converters are the most confusing component to set up because it’s easy to misunderstand and misconfigure them. Since Kafka messages are just bytes, converters translate data between the internal data format used by Kafka Connect and the format specific to the source or sink connector. When we store messages in Kafka topics they will always get serialized, even if we do not specify the serialization format. The Kafka producer will use the String serializer by default to serialize the message and save it to a topic as an array of bytes. Usually, you will use serialization formats like <strong>Avro</strong> or <strong>Protobuf</strong> for more complex pipelines. Since we are using both connectors and converters, let’s refer to them together as a Kafka Connect system to make it less confusing.</p><p>We have two systems: a <strong>source system</strong> and a <strong>sink system</strong>. You may see that in some places, those two components are both referred to as <strong>connectors,</strong> but that is why many people get confused. We will expand both previous diagrams to demonstrate how both source and sink systems look.</p><h4>Source system</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*D2SVQrg9GkyAx1pf.png" /></figure><p>As you can see in the diagram, the source connector (in our case S3 connector) establishes the connection to S3 and starts polling the desired file. After the S3 connector gets a file, it will hand out the contents of that file to the converter. The converter will take that content and serialize it with the specified serializer to the specified format. After serialization, it will publish the serialized message to a Kafka topic.</p><h4>Sink system</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2HuG_3kntdc8d_JB.png" /></figure><p>In sink systems, the converter first takes the serialized message from a Kafka topic, deserializes it, and hands it over to the sink connector. After that, the sink connector (in our case the Snowflake connector) saves the formatted message into the Snowflake table. If you are still confused, imagine that the connector connects Kafka Connect to a source/destination and acts as a bridge between those two points while the converter does all the data transformations (serialization/deserialization).</p><p>Understanding converters is a crucial part of setting up Kafka Connect. I highly recommend spending some time to understand connectors and converters before attempting to set up Kafka Connect for a complex pipeline. It will save you a lot of time in the long run.</p><p>Confluent published a great blog about converters here: <a href="https://www.confluent.io/blog/kafka-connect-deep-dive-converters-serialization-explained/">Kafka Connect Deep Dive — Converters and Serialization Explained | Confluent</a>.</p><h3>Dead Letter Queue (DLQ)</h3><p>A Dead letter queue or DLQ is a component that can be easily overlooked since it is not a required component for setting up Kafka Connect. In my opinion, DLQ is a must for any production grade data pipeline. DLQ is a component that is responsible for handling messages that failed to be consumed. In terms of Kafka, DLQ is a separate topic that is only used to catch messages that had some issues and could not be consumed.</p><p>The most common issue when this happens is when using the wrong converter. If you have worked with Avro, there is a good chance you saw the “<em>Unknown magic byte!” </em>error. That error occurs when you try to use an Avro converter to deserialize messages that are not Avro. A wrong converter is one example, but there can be more issues. To avoid losing messages, we need a DLQ that will handle those cases. By default, the Kafka Connect cluster will stop if it cannot deserialize a message, and we will need to manually fix the issue and restart it.</p><p>In production environments we cannot allow downtime nor lose messages and that’s where DLQ can help us. We can configure Kafka Connect to not crash when a message cannot be consumed or deserialized but instead send it to the DLQ topic.</p><p>These are the properties that we need to define if we want to do that:</p><pre>&quot;errors.tolerance&quot;: &quot;all&quot;, <br>&quot;errors.deadletterqueue.topic.name&quot;: &quot;MY_DLQ_TOPIC&quot;,<br>&quot;errors.deadletterqueue.topic.replication.factor&quot;: 3,<br>&quot;errors.deadletterqueue.context.headers.enable&quot;: true</pre><h3>Schema registry</h3><p>Schema registry is an additional service that helps us improve the data quality of our Kafka data. It’s a centralized repository where we can store our schemas for our Kafka messages. You can think of schemas as blueprints for how the message should look like. Schema definition contains all the fields and their types that a message needs to have. When a producer wants to publish a message to a topic, it must first validate whether the message satisfies the schema definition. Only then can the message be published. The same applies for consumers. This helps us have standards, and establishes better collaboration across different teams, and helps prevent mistakes and inconsistencies.</p><p>This is an example how a schema definition looks like:</p><pre>{<br>  &quot;doc&quot;: &quot;Sample schema&quot;,<br>  &quot;fields&quot;: [<br>    {<br>      &quot;doc&quot;: &quot;ID of the user. Type Int.&quot;,<br>      &quot;name&quot;: &quot;id&quot;,<br>      &quot;type&quot;: &quot;int&quot;<br>    },<br>    {<br>      &quot;doc&quot;: &quot;Users full name. Type String.&quot;,<br>      &quot;name&quot;: &quot;name&quot;,<br>      &quot;type&quot;: &quot;string&quot;<br>    }<br>  ],<br>  &quot;name&quot;: &quot;sampleRecord&quot;,<br>  &quot;namespace&quot;: &quot;com.dk.mynamespace&quot;,<br>  &quot;type&quot;: &quot;record&quot;<br>}</pre><p>Kafka Connect is made by Confluent and has many integrations with the Confluent Platform, and also different licenses. To reduce the complexity of the blog, in part 2 we will use the Confluent Platform schema registry.</p><p>In case you cannot use the Confluent Platform, Confluent provides the schema registry under <em>Community License </em>and in 99% of the cases you will be fine to host your own instance of the schema registry. The only exception is if you want to offer it as a service and compete with Confluent. In those cases, feel free to use alternatives like <a href="https://github.com/Aiven-Open/karapace">Karapace</a>.</p><h3>Conclusion</h3><p>When it comes to streaming messages from Kafka to Snowflake, Kafka Connect was a clear winner for us. It provided us with high reliability, scalability and fault tolerance while also having out of the box integration with schema registry and dead letter queues.</p><p>In this blog, we learned about Kafka Connect, how to use it, and which features are necessary for a production-grade big data pipeline.</p><p>For a practical guide on how to set up Kafka Connect; <a href="https://www.atlantbh.com/blog/">stay tuned</a> for <strong>part 2</strong> of the blog where we configure and deploy three Kafka Connect clusters (avro, protobuf and string) on Kubernetes and demonstrate the process how we can utilize Schema Registry and DLQ (Dead Letter Queue) to establish a production grade data pipeline.</p><p>The architecture we will set up is shown here:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2EG57DQC0ns_dETt.png" /></figure><p><em>Originally published at </em><a href="https://www.atlantbh.com/kafka-connect/"><em>https://www.atlantbh.com</em></a><em> on May 16, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f4a14a692b3a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/atlantbh/consuming-and-storing-kafka-messages-in-snowflake-using-kafka-connect-part-1-f4a14a692b3a">Consuming and Storing Kafka Messages in Snowflake Using Kafka Connect (Part 1)</a> was originally published in <a href="https://medium.com/atlantbh">Atlantbh Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comprehensive Guide: Creating an ML-Based Text Classification Model]]></title>
            <link>https://medium.com/atlantbh/comprehensive-guide-creating-an-ml-based-text-classification-model-62dfbdbf5ab1?source=rss----eb9022a96e98---4</link>
            <guid isPermaLink="false">https://medium.com/p/62dfbdbf5ab1</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[natural-language-processi]]></category>
            <category><![CDATA[text-classification-model]]></category>
            <dc:creator><![CDATA[Atlantbh]]></dc:creator>
            <pubDate>Fri, 26 Apr 2024 10:43:06 GMT</pubDate>
            <atom:updated>2024-04-26T10:42:55.098Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ttzKjKHnrCK-EGRr9Yeh_g.jpeg" /></figure><p>In the <a href="https://www.atlantbh.com/harnessing-the-potential-of-nlp/">previous blog</a>, we first defined the problem of customer support ticket classification that Atlantbh had the opportunity to solve. After presenting the business goal, we briefly described the proposed approach and obtained results. This blog post serves as a follow-up. We aim to delve into individual steps of the proposed approach, highlighting the best practices and providing insights gained from our experience.</p><p>Note that although we will remain in the context of customer support ticket classification, most of the steps described below apply to almost any text classification task, regardless of the specific problem domain.</p><p>Figure 4. in the previous blog post shows the overview of text classification flow using the ML approach. This figure outlines six steps, from problem definition and business goal to classifier deployment. On the other hand, the figure below shows a more granular flow of text classification by splitting feature engineering and model construction phases into multiple individual steps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*t-fylqerJAq7DQeZhu8QKA.png" /><figcaption>Figure 1. Text Classification Flow (Anja Plakalovic)</figcaption></figure><h3>Exploratory Data Analysis</h3><p>Exploratory Data Analysis (EDA) holds significant practical value for any ML task, providing insight into underlying structure and data characteristics. EDA enables the identification of the class distribution and detection of a potentially imbalanced dataset. <strong>However, an imbalanced dataset does not necessarily mean a “red flag”. In practice, imbalanced datasets are quite common</strong>. This is also the case with our dataset. For example, customers are more likely to report shipping issues than their account settings issues. There are many different ways to “combat” an imbalanced dataset, such as oversampling or undersampling, and many others, but we will not explain each of these techniques in detail here. Nevertheless, it is important to point out that in the case of an imbalanced dataset, selecting the appropriate metrics for model performance evaluation is of great importance. This will be discussed further as we go through the model construction process.</p><p>EDA can also provide valuable information and basic statistics of text corpus (i.e., unstructured text dataset), such as discovering the various data structures within a dataset. It is important to note that EDA is not a one-time process performed during the project’s initial phase, as some may assume by looking at the simplified version of the text classification flowchart. (Figure 1.) On the contrary, <strong>there is a feedback loop between EDA and feature engineering steps</strong>. (Figure 2.)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*klyR9KEfn3HkEaoxuUtG-Q.png" /><figcaption>Figure 2. Feedback Loop Between EDA and Feature Engineering Steps (Anja Plakalovic)</figcaption></figure><p>Insights gained from the initial EDA directly influence the selection of steps we will perform in the feature engineering phase. Let’s say that we discover through EDA that there are some irrelevant records — it is necessary to remove these records from the dataset in the data cleaning phase. Conversely, the feature engineering steps also affect EDA. As the data goes through various changes in the feature engineering phase, it prompts a re-examination of the EDA to assess the impact of the feature engineering on the dataset and yield new insights. For example, stop word removal or lemmatization changes the previously established characteristics of the text corpus, and it is necessary to re-establish them. This feedback loop between EDA and feature engineering allows the development of a robust text classification model that increases the effectiveness of capturing underlying patterns in text data.</p><h3>Feature Engineering Phase</h3><p>Several fundamental steps are often performed during the feature engineering phase of a text classification task to ensure the quality and relevance of features extracted from raw text data. However, steps such as language translation or certain data cleaning and text preprocessing techniques may vary depending on the dataset characteristics and task-specific requirements. For example, language translation is crucial when dealing with multilingual datasets but is irrelevant for monolingual ones. Similarly, if a dataset contains different data formats, additional steps of data cleaning and text preprocessing may be required to resolve this variability. By recognizing a balance between commonly applicable procedures and task-specific requirements, we can tailor our feature engineering strategies to effectively address the unique challenges given by each NLP task and dataset.</p><h4>Data Cleaning</h4><p>After performing EDA on the initially obtained data, we quickly realized that our dataset contains records in various formats. In addition to plain text, which accounts for most of the dataset, we concluded that around 10% of records represent inquiries in HTML format. In many real-world situations, this often happens. For example, when collecting data from websites or emails, text usually contains HTML tags, which are irrelevant for analysis and may even interfere with further preprocessing steps. Removing these HTML tags and converting such text into plain, readable format was one of the first steps in data cleaning. Beautiful SoupPython library has proven to be a powerful tool to efficiently identify HTML-like messages, while the html2text library demonstrated its effectiveness in converting these messages to plain text.</p><p>The second part of data cleaning involved identifying and removing duplicates and irrelevant or noisy records from the dataset to ensure the data quality. In our scenario, the dataset included different types of non-standard records, such as test messages or other forms of inquiries that do not contribute to the classification objective, so it was necessary to develop specific conditions for filtering such records. Manual inspection also enabled us to verify and flag potentially irrelevant records. Once identified, such records were excluded from further analysis during the data cleaning process to prevent them from influencing the model training process and compromising classification performance.</p><p>Data cleaning usually reduces the dimensionality of the initial dataset by removing irrelevant features and records. In general, by filtering unnecessary columns (i.e., features) and discarding rows (i.e., records), the dataset becomes more streamlined. However, <strong>one should be very careful not to overdo the cleaning process, as excessive data removal can result in the loss of potentially valuable information</strong>, which can also negatively affect the performance of the classification model. Therefore, finding the right balance between the need for data cleaning and the preservation of relevant data is essential.</p><h4>Language Detection &amp; Translation</h4><p>In the customer support domain, where inquiries may be in different languages, translation is necessary when creating a classification model. Instead of creating different models for each language, using language detection and translation tools is a more cost-effective and pragmatic approach. This strategy consolidates inquiries in multiple languages into a single dataset, facilitating comprehensive analysis. Since we worked with the data of an international company, it is not surprising that our dataset is multilingual. More precisely, our dataset contains customer inquiries in over 20 languages, where about 13% of records are in a language other than English.</p><p>When using APIs for language detection and translation, it is critical to consider data confidentiality and security. <strong>Ensuring that sensitive information remains protected during the translation is paramount</strong> and requires a careful selection of APIs and robust security protocols to protect data integrity and privacy.</p><h4>Text Preprocessing</h4><p>As already mentioned, text preprocessing steps are highly dependent on the specifics of the task and the dataset we are working with. In-depth EDA typically yields an intuitive determination of the necessary text preprocessing steps. We used the power of the Natural Language Toolkit (NLTK) Python library to perform around 15 individual steps as part of the text preprocessing phase. This library is popular and widely used for various NLP tasks. Performed steps range from converting all text to lowercase to removing emojis, URLs, special characters, irrelevant phrases, stop words, and many more. (Figure 3.)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5-hkR5IGE3QKUDl3sUwFtg.png" /><figcaption>Figure 3. Some of the Basic Text Preprocessing Steps (Anja Plakalovic)</figcaption></figure><p>Here, we will focus on two crucial steps in almost every text classification task: lemmatization and tokenization.</p><p><strong>Lemmatization</strong></p><p>Lemmatization is a text preprocessing technique that reduces words to their base or root forms (i.e., lemmas). Applying this preprocessing step leads to text normalization and vocabulary dimensionality reduction. There is another similar text preprocessing technique called stemming. Stemming removes suffixes to find the word root form, which does not necessarily result in valid words. On the other hand, <strong>lemmatization considers word context and part of speech, resulting in more precise transformations that represent valid words</strong>. (Figure 4.) However, although lemmatization is a more sophisticated approach than stemming, the downside is that it is more computationally intensive and much slower than stemming, which should be kept in mind when working with high-dimensional text datasets.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dQMkQbQS8I5uTmIsYMx4cQ.png" /><figcaption>Figure 4. Lemmatization vs. Stemming (Anja Plakalovic)</figcaption></figure><p><strong>Tokenization</strong></p><p>The term “tokenization” without specifying any additional context refers to the word tokenization. It is the most commonly used form of tokenization and involves splitting a text into individual words or tokens. However, we should note that there are also other types of tokenization. For example, NLTK also implements the sentence tokenization method. By breaking down text into smaller chunks, tokenization enables more granular analysis and further text data processing. <strong>Tokenization is a fundamental technique in every text classification task, as it serves as the preliminary step in converting raw text data into a format suitable for analysis by ML algorithm.</strong></p><h4>Train-Test Split</h4><p>After tokenization, it is time to split the dataset into training and test subsets. This step is always better to perform before vectorization, especially when training our vectorization model rather than using a pre-trained one. We leave the test dataset aside and use it only at the very end to evaluate the performance of the classification model. This way, we ensure that the training and test datasets remain independent, thus preventing any information leakage from the test to the training dataset.</p><p>In general, the train-test split ratio always hovers around 80/20. This ratio has its roots in the well-known <strong>Pareto principle </strong>(also known as the 80/20 rule), which states that approximately 80% of consequences come from 20% of causes (the “vital few”). We decided to split our dataset using a 75/25 ratio. In the case of an imbalanced dataset like ours, it is necessary to provide an equal distribution of classes in the training and test datasets. This way, we ensure that the minority class is not included exclusively in the training or test dataset. Fortunately, this can be easily achieved using the “shuffle” and “stratify” arguments of sklearn’s train_test_split method.</p><h4>Vectorization</h4><p>Since ML algorithms cannot directly process text, conversion of text to numerical representation is an essential part of every text classification task. This process is called vectorization and is the final step in the feature engineering phase. We decided to use Word2Vec and train it on our data instead of directly using a pre-trained model. For training the Word2Vec model, we used the power of the Gensim library. This Python library has a module that efficiently implements the Word2Vec family of algorithms.</p><p>Once trained, Word2Vec is used on both training and test datasets to generate word embeddings for each word in the dataset vocabulary. This way, words are represented as high-dimensional vectors of numbers optimized for our domain-specific semantics. At the same time, <strong>Word2Vec ensures that relationships between words are captured so that similar meanings or contexts have similar vector representations</strong>.</p><p>Given that the previously tokenized dataset contains customer inquiries of variable length, word embeddings are first generated for each word and then aggregated to obtain a single vector representation for the entire record. This aggregation combines the information from all words in the record into a fixed-length vector determined by the dimensionality specified during the training of Word2Vec model. One of the most frequently used methods of aggregating is averaging word embeddings. (Figure 5.) This fixed-size vector of numbers captures the semantic information within the record and represents a suitable input to ML models.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Z0HeDjENFlcFO-yJiK9hLg.png" /><figcaption>Figure 5. Text Vectorization (Anja Plakalovic)</figcaption></figure><h3>Model Construction Phase</h3><p>The model construction phase, which follows feature engineering, consists of six steps (Figure 1.). Although these steps are closely related, they are presented separately on the flowchart to underline their importance. In the following sections, we will clarify these steps by emphasizing two decisions: classification algorithm selection and evaluation metrics.</p><h4>Classification Algorithm Selection</h4><p>Choosing the appropriate classification algorithm is a vital decision that is heavily influenced by the data scientist’s experience and domain knowledge. Experienced data scientists use their understanding of the dataset characteristics, the nature of the problem, and the known strengths and limitations of various algorithms to decide which algorithm to use. However, experimenting with different classification algorithms is often convenient as it provides valuable insights into which algorithms work best for a given task.</p><p>After evaluating the strengths and weaknesses of various algorithms, we chose <strong>Support Vector Machine (SVM) as the classification algorithm for its proven effectiveness in different NLP tasks</strong>. SVM’s several advantages make it a compelling choice for text classification tasks. Firstly, SVM is well-known for its versatility and robustness. It can handle high-dimensional data and capture complex relationships. SVM has the ability to find the optimal hyperplane for separating classes in a feature space, even in cases where the data is not linearly separable. It achieves this by using different kernel functions (e.g., linear, polynomial, radial basis functions). Moreover, SVM’s regularization parameter allows for fine-tuning the trade-off between model complexity and generalization. This flexibility ensures avoiding overfitting while maintaining good performance on unseen data.</p><p>Firstly, the baseline SVM model is trained and evaluated to establish a performance benchmark. The performance of this baseline model is then used to estimate the level of improvement in subsequent iterations. Hyperparameter tuning enabled us to determine the optimal SVM model parameters (e.g., kernel type, regularization parameter, and gamma value). More precisely, hyperparameter tuning entails multiple iterations of model training and evaluation using different combinations of hyperparameters. Once the model iterations are complete, the best-performing SVM model is selected. In the case of experimenting with several classification algorithms, the previously described steps are performed for each selected algorithm. Finally, by comparing each of them, the final model is chosen.</p><p>It is important to note that if we are not satisfied with the obtained results, we should consider going back to EDA, reviewing the steps performed in the feature engineering phase, modifying some of the preprocessing steps, or perhaps considering using a different algorithm to generate word embeddings.</p><h4>Evaluation Metrics</h4><p>For assessing the performance of a classifier, choosing the right evaluation metrics, especially in the context of imbalanced datasets, is vital. Stakeholder input becomes crucial as their priorities and goals often determine evaluation metrics. In our scenario, the stakeholders’ input primarily focused <strong>on accuracy as the key metric</strong>. However, we also relied on class-wise metrics such as precision, recall, and F1-score to ensure a comprehensive assessment. In addition, by using micro, macro, and weighted averages, we gained overall insights into model effectiveness.</p><h3>Classifier Deployment</h3><p>After the final model selection, the classifier is deployed in the production environment. Applying the created classifier in production on new customer support tickets (i.e., unseen text) consists of several steps. Firstly, it is necessary to detect the language of the new text and translate it if it is not in English. After that, the exact text preprocessing steps included in the process of classifier construction follow. Finally comes vectorization, which converts the text into a form suitable as input to the created classifier. By applying the classifier, the output is the category of a customer support ticket.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TfplXmgwCFghIs7ZiSGnTA.png" /><figcaption>Figure 6. Using Text Classifier in a Production</figcaption></figure><p>In the classifier deployment phase, <strong>cooperation between different teams is crucial</strong>. Software engineers developing the system are responsible for the seamless classifier integration, while DevOps engineers manage the deployment pipeline and infrastructure. Data scientists who build the model work alongside them to ensure a smooth transition and continuous monitoring of the deployed classifier.</p><p>Proper implementation of MLOps practices ensures continuous integration, deployment, and monitoring of classifier performance. In addition, client expectations regarding the frequency of retraining should be clearly defined and agreed upon to maintain the model’s accuracy and relevance over time. This collaborative effort and adherence to best practices in implementation are essential to achieving sustainable and efficient ML-based solutions in real-world applications.</p><p><em>Originally published at </em><a href="https://www.atlantbh.com/harnessing-the-potential-of-nlp/"><em>https://www.atlantbh.com</em></a><em> on April 9, 2024.</em></p><p>Blog by Anja Plakalović, Data Analyst at Atlantbh.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=62dfbdbf5ab1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/atlantbh/comprehensive-guide-creating-an-ml-based-text-classification-model-62dfbdbf5ab1">Comprehensive Guide: Creating an ML-Based Text Classification Model</a> was originally published in <a href="https://medium.com/atlantbh">Atlantbh Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Harnessing the Potential of NLP: Effortless Experience & Efficient Customer Service]]></title>
            <link>https://medium.com/atlantbh/harnessing-the-potential-of-nlp-effortless-experience-efficient-customer-serviceas-businesses-35daaeff3dba?source=rss----eb9022a96e98---4</link>
            <guid isPermaLink="false">https://medium.com/p/35daaeff3dba</guid>
            <category><![CDATA[natural-language-processi]]></category>
            <category><![CDATA[natural-language-process]]></category>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Atlantbh]]></dc:creator>
            <pubDate>Fri, 26 Apr 2024 08:32:34 GMT</pubDate>
            <atom:updated>2024-04-26T11:26:17.720Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iKX0ulCE7z7b9G_n6j90MA.jpeg" /></figure><p>Natural Language Processing (NLP), one of the branches of AI, encompasses a powerful set of techniques with a wide range of applications, especially in the field of customer support. By enabling machines to understand, interpret, and generate human language, NLP breaks down the barriers between technology and human interaction, paving the way for a more intuitive user experience and personalized customer service.</p><p>Whether you are an experienced organization or a budding entrepreneur, it is important that you understand how NLP can be used to improve your business. Join us as we explore NLP by practically applying it to one common task. In addition, discover the success story of the collaboration of two Atlantbh teams, the development team and a small group of people from the data team, which led to the construction of a customized text classification model and its smooth deployment in production.</p><h3>Using NLP in Customer Service</h3><p>NLP tasks play an important role in automating and optimizing various aspects of customer service, improving the overall user experience and operational efficiency. More and more businesses are developing conversational agents (chatbots) that can interact with customers in natural language, respond to frequently asked questions, or solve basic inquiries. Text summarization techniques can be applied to automatically generate concise summaries in cases of rather long customer inquiries. Speech recognition techniques enable transcribing phone calls or voicemails into text. Real-time translation services provide multilingual customer support and enable smooth communication with customers who speak different languages.</p><p>There are numerous examples of using different NLP tasks in customer support, but text classification is certainly one of the most common. Problems such as spam filtering, sentiment analysis, ticket triaging, or ticket classification can be considered as variations of the same task: text classification.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5g-Nn37Qspi_iR4o.png" /></figure><p><em>Figure 1. Examples of Text Classification in Customer Service (Anja Plakalovic)</em></p><h3>Problem Definition: Customer Support Ticket Classification</h3><p>For any business, customer complaints are important, as they can often indicate shortcomings in their products or services. If these complaints are not resolved quickly, it can lead to customer dissatisfaction, while a recurring trend of dissatisfaction can lead to reduced revenue.</p><p>Therefore, solving the problem of automatic classification of customer support tickets has a significant value, both for businesses and for customers. From a business perspective, it leads to a streamlined customer support workflow and improved operational efficiency. On the other hand, from a customer perspective, it ensures that their inquiries are immediately routed to the appropriate team, resulting in faster responses and a smoother user experience.</p><p><a href="https://www.atlantbh.com/services/">Atlantbh</a> had the opportunity to work on solving the problem of classification of customer support tickets for an international company. We can formally define this problem as follows:</p><p><strong>Given a set of customer support tickets (i.e., formal records of customer inquiries, issues, or complaints) and a predefined set of categories (e.g., shipping issues, refund issues, account settings issues), the task is to construct a model that can accurately classify each ticket into the appropriate category, depending on the ticket content.</strong></p><h3>Business Goal</h3><p>The existing process for handling customer support tickets relied entirely on manual categorization, where customers were tasked with selecting a specific category before submitting a problem. However, this approach has proven to be error-prone, with frequent occurrences of misclassification. Consequently, customer inquiries were often routed to the wrong support teams, leading to delays in resolution and customer frustration. In addition, support teams spent significant time recategorizing misclassified tickets and routing them to the appropriate teams. This manual reassignment process increased response times and resulted in redundant efforts and inefficiencies across various support teams.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*g3f5UQgeP5t7Wm0X.png" /></figure><p><em>Figure 2. Customer Support Ticket Handling Process: Before vs. After (Anja Plakalovic)</em></p><p>The proposed support ticket handling process intends to automate ticket categorization using a custom text classification model. This solution eliminates the need for customers to manually select ticket categories by using NLP and ML techniques to automatically classify tickets based on their content. This also simplifies the user interface for reporting problems, allowing users to report problems more quickly without having to decide which category the problem belongs to. (Figure 3.)</p><p><strong>By implementing this proposed approach, the goal is to achieve a considerable improvement in the accuracy and efficiency of support ticket classification, surpassing the estimated accuracy of the existing solution, which is approximately 80%</strong>. This way, we aim to reduce the time teams spend recategorizing misclassified tickets and improve overall customer satisfaction by providing faster solutions to their inquiries.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wZfOJfYCkGH_oLfM.png" /></figure><p><em>Figure 3. Report a Problem Form: Before vs. After (Anja Plakalovic)</em></p><h3>Approach</h3><p>The client first approached the development team with a request to create a solution for automatically classifying support tickets. After defining the problem, the development team extracted data from the ticketing system. This way, a labeled dataset was collected, containing the text content of existing tickets (i.e., customer inquiries) and each ticket’s corresponding category or label.</p><p>The development team tried to solve the problem by applying a rule-based approach. This method is often the initial and simplest strategy for solving text classification problems. A rule-based approach uses predefined rules to classify support tickets based on specific criteria or linguistic patterns. However, despite the team’s efforts, <strong>it quickly became apparent that the rule-based approach alone could not adequately address the complexity and variability of ticket content</strong>. As a result, the team recognized the need to explore more advanced methodologies, which led to collaboration with the data team and using ML approach to solve this problem.</p><p>The figure below shows an overview of creating a text classifier using a supervised ML approach, delineating the teams involved and their respective responsibilities. Considering that we already clarified the first two steps, we will henceforth describe the remaining steps included in the used approach.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*z4fBob1qe9DtZILO.png" /></figure><p><em>Figure 4. Overview of Text Classification Flow Using ML Approach (Anja Plakalovic)</em></p><p>The data team’s first step after being introduced to the problem by the development team was to familiarize themselves with the data and develop a sense of what could be derived from it. This step is formally called <strong>Exploratory Data Analysis</strong> (EDA). It enabled us to raise awareness of potential challenges and constraints early in the project lifecycle, guiding the appropriate further approaches and risk mitigation. In the context of our customer support ticket classification problem, EDA provided valuable insights regarding customer inquiries. These, among others, include the distribution of ticket categories, the frequency of specific keywords or phrases within each category, and the customer inquiry length distribution.</p><p>A thorough EDA created a good foundation for defining steps in the feature engineering phase, which ensured converting the raw text data into a format suitable for the ML classification algorithm. Some of the performed feature engineering steps include data cleaning, standard text preprocessing techniques such as stop word removal, lemmatization or tokenization, and text vectorization at the end. <strong>Making the strategic decision to use Word2Vec as a text vectorization algorithm ensured we captured semantic relationships and context within the text.</strong> Rather than relying on pre-trained models, training Word2Vec on our dataset provided word embeddings tailored to our use case. Before performing text vectorization, we split the dataset into training and test datasets and trained Word2Vec only on training data. Afterward, we saved the trained Word2Vec model to preserve the learned word embeddings and use it to vectorize training and test data.</p><p>Upon completing the transformation of our raw customer inquiries into a suitable format as input to the ML algorithm, the next step entailed selecting a suitable classification algorithm. Extensive research encompassing a spectrum of classification algorithms, complemented by our profound domain knowledge acquired through the EDA phase and an exhaustive feature engineering endeavor, played a crucial role in this decision-making process. The culmination of these efforts led us to discern the <strong>Support Vector Machine (SVM) as the optimal choice for our text classification task</strong>. This decision was reinforced by SVM’s well-documented prowess in handling high-dimensional and sparse data since these characteristics are common in text classification tasks. Its ability to delineate complex decision boundaries, robustness in handling non-linear relationships, and good generalization performance further solidified its importance in our use case. By implementing hyperparameter tuning, we ensured using the optimal SVM parameters that improve classification performance. After training the model with these parameters on the training dataset, we saved the SVM model. Then, we used it on the test dataset to evaluate the performance of the created model.</p><p>It is interesting to consider the distribution of time that the Atlantbh data team invested in implementing the previously mentioned phases. It is important to note that the following results represent rough estimates and depend significantly on the nature of the problem and the specific use case. In our scenario, we invested approximately 10% of the total time in EDA. This effort mainly included a thorough familiarization with the dataset at the beginning of the project. On the other hand, <strong>the feature engineering phase took the majority share with 60%, showing significant time investment in the data preprocessing before the model training</strong>. Model construction, which included ML models’ development and refinement, accounted for the remaining 30%.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Nlk7l8PthimKHPLH.png" /></figure><p><em>Figure 5. Time Allocation Overview: EDA, Feature Engineering, and Model Construction (Anja Plakalovic)</em></p><h3>Results</h3><p>By implementing the proposed approach, our primary goal was to significantly improve the accuracy and operational efficiency of the customer support ticket classification system. <strong>We exceeded the estimated accuracy of the existing solution, which was around 80%. More precisely, our approach resulted in an outstanding performance, achieving 93% accuracy when evaluated on the test dataset</strong>. After presenting the results to our clients, diligent work and seamless cooperation of different Atlantbh teams enabled the smooth deployment of the proposed model to production. A few months after the model deployment, we evaluated its accuracy in production. <strong>It turned out that in production, the proposed model for customer support ticket classification has an accuracy of an impressive 98%, which significantly exceeds even its initially estimated accuracy</strong>.</p><p>This significant improvement not only confirms the effectiveness of our approach but also highlights the commitment and expertise of everyone involved in the process. By surpassing the previous performance, we effectively reduced the time spent recategorizing misclassified tickets, streamlined operational flows, and increased team productivity. Moreover, this improved accuracy further leads to faster and more accurate responses to customer inquiries, which increases customer satisfaction. This success underlines the significant benefits and improvements businesses can achieve using NLP and ML techniques, and Atlantbh’s commitment to delivering exceptional results and creating tailored solutions.</p><p><em>If you found this blog engaging, we encourage you to read part two: </em><a href="https://www.atlantbh.com/creating-an-ml-based-text-classification-model/"><em>“Comprehensive Guide: Creating an ML-Based Text Classification Model”</em></a><em>. As the name suggests, this blog provides an in-depth description of individual steps of the proposed approach.</em></p><p><em>Originally published at </em><a href="https://www.atlantbh.com/harnessing-the-potential-of-nlp/"><em>https://www.atlantbh.com</em></a><em> on April 9, 2024.</em></p><p>Blog by Anja Plakalović, Data Analyst at Atlantbh.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=35daaeff3dba" width="1" height="1" alt=""><hr><p><a href="https://medium.com/atlantbh/harnessing-the-potential-of-nlp-effortless-experience-efficient-customer-serviceas-businesses-35daaeff3dba">Harnessing the Potential of NLP: Effortless Experience &amp; Efficient Customer Service</a> was originally published in <a href="https://medium.com/atlantbh">Atlantbh Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Test Automation Integrated into AWS Step Function Workflow]]></title>
            <link>https://medium.com/atlantbh/test-automation-integrated-into-aws-step-function-workflow-ee5f2d07bd0a?source=rss----eb9022a96e98---4</link>
            <guid isPermaLink="false">https://medium.com/p/ee5f2d07bd0a</guid>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[automation-testing]]></category>
            <dc:creator><![CDATA[Atlantbh]]></dc:creator>
            <pubDate>Mon, 20 Nov 2023 11:21:42 GMT</pubDate>
            <atom:updated>2023-11-20T11:20:35.207Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N62xUK2dithDjyhiG7ajbA.jpeg" /></figure><h3>Test Automation Integrated into AWS Step Function Workflow</h3><p>AWS Step Function is an AWS resource that offers serverless function orchestration making it easy to sequence multiple AWS resources into one business workflow. This tool allows us to create and run many checkpointed and event-driven workflows that maintain the application state, ideal for projects such as data pipelines and many others. In this blog, we will explain the concept of development and application of the Step Function, with test automation integrated into its workflow.</p><h3>Pricing of AWS Step Function</h3><p>AWS Step Function pricing works based on ‘you pay what you use’. We have two types of workflows: Standard Workflows and Express Workflows. By default, Step Function uses Standard Workflow, and its price is $0.000025 per state transition. The pricing for Express Workflow is $0.000001 per request. In this blog, we will use the default option.</p><h3>Step Function states</h3><p>AWS Step Function provides a set of states that you can use to build workflows. Each state represents a specific action or behavior within the workflow. Here are some of the commonly used Step Function states:</p><ol><li><strong>Task State:</strong> Represents a single unit of work in a Step Function workflow. It can be used to invoke AWS Lambda functions, run ECS tasks, or perform other activities.</li><li><strong>Choice State:</strong> Allows you to define conditional logic within your workflow. It evaluates a condition and transitions to different states based on the result.</li><li><strong>Wait State</strong>: Delays the execution of the workflow for a specific period.</li><li><strong>Pass State:</strong> Passes its input to its output without performing work. It is useful when constructing and debugging state machines.</li><li><strong>Parallel State:</strong> Allows us to run multiple branches of execution in parallel. Within the parallel state every branch can be separated.</li><li><strong>Succeed State:</strong> Ends execution as successfully processed.</li><li><strong>Fail State:</strong> Ends execution as failure. It also provides more ways to find out the root cause of errors. Additionally, it could provide us with the ‘Catch’ block if necessary.</li><li><strong>Map State:</strong> You can think of it like a for-each loop for Step Function. It allows us to iterate over an array/list of items and perform the same workflow steps for every item in the array.</li></ol><h3>Let us learn by the example:</h3><h4>Demo project description:</h4><p>In this example, we will create a simple demo software with the Step Function, Lambda, DynamoDB, GitHub Actions, and Serverless framework for easier Step Function creation. In the DynamoDB, we will create one table called ‘messages’. This table is supposed to receive data from other imaginary services. Message record should contain the following:</p><ul><li>ID,</li><li>First name (fName),</li><li>Last name (lName),</li><li>Region (US/EU),</li><li>Points,</li><li>Column representing flag for processed messages (is_processed)</li></ul><p>Our Step function should contain a workflow that takes all of the unprocessed messages, separates them by the region of the US and EU users, and puts those messages into the other appropriate tables: US_users and EU_users.</p><h4>Step function implementation:</h4><p>We will use the Serverless framework to easily implement the Step Function, its Lambdas and IAM roles. The Serverless framework offers infrastructure as a code implementation and deployment for the AWS Step function. It uses Lambdas and additional necessary services that we need in the state machine. We will not go in-depth with the Serverless framework, but if you would like to know more, you can find it on their official <a href="https://www.serverless.com/framework/docs">website</a>.</p><p>Follow these steps to start with the Serverless framework:</p><ol><li>Create empty folder</li></ol><p>2. Instantiate NPM with the ‘npm init’ command</p><p>3. Install serverless framework via NPM ‘npm install -g serverless’</p><p>4. Add the following dependencies that we will further use in package.json:</p><pre>{<br>  &quot;name&quot;: &quot;sfn-blog&quot;,<br>  &quot;dependencies&quot;: {<br>    &quot;@aws-sdk/client-dynamodb&quot;: &quot;^3.405.0&quot;,<br>    &quot;@aws-sdk/client-s3&quot;: &quot;^3.405.0&quot;,<br>    &quot;axios&quot;: &quot;^1.4.0&quot;,<br>    &quot;jest&quot;: &quot;^29.4.1&quot;,<br>    &quot;jest-junit&quot;: &quot;^15.0.0&quot;<br>  },<br>  &quot;version&quot;: &quot;1.0.0&quot;,<br>  &quot;description&quot;: &quot;&quot;,<br>  &quot;main&quot;: &quot;index.js&quot;,<br>  &quot;directories&quot;: {<br>    &quot;test&quot;: &quot;test&quot;<br>  },<br>  &quot;scripts&quot;: {<br>    &quot;test&quot;: &quot;jest&quot;<br>  },<br>  &quot;author&quot;: &quot;&quot;,<br>  &quot;license&quot;: &quot;ISC&quot;,<br>  &quot;devDependencies&quot;: {<br>    &quot;@types/jest&quot;: &quot;^29.2.5&quot;,<br>    &quot;serverless-step-functions&quot;: &quot;^3.13.1&quot;<br>  }<br>}</pre><p>5. Run ‘npm install’</p><p>6. Create <em>serverless.yml </em>which will be used for infrastructure as a code</p><p>7. Create for lambda functions written in Node JS</p><p><strong>Note</strong>: Do not forget to add your AWS credentials (access key and secret key). If you are on a MAC machine, you can do this by running the following command:</p><pre>sls config credentials --provider aws --key &lt;YOUR_ACCESS_KEY&gt; --secret &lt;YOUR_SECRET_KEY&gt;</pre><p>8. Create DynamoDB table ‘messages” with ID type String as a Partition key:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Zhb402AxdYk4D5RJ.png" /></figure><p>9. Choose Customize Settings, On-demand capacity mode, and click ‘ <strong>Create table</strong> ‘ at the end of the page.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZTO6n8_7I7LNu5QU.png" /></figure><p>10. In the same way, create two more tables: US_users_events and EU_users_events.</p><p>11. In the serverless yml file, we will define our needed Step Function workflow:</p><pre>service: sfn-blog<br>frameworkVersion: &quot;3&quot;<br># Define  cloud provider settings and IAM roles needed for our SF to work<br>provider:<br>  name: aws<br>  runtime: nodejs18.x<br>  region: eu-central-1<br>  environment: ${file(env.json)}<br>  iamRoleStatements:<br>    - Effect: Allow<br>      Action: dynamodb:*<br>      Resource: arn:aws:dynamodb:eu-central-1:178190218027:table/messages<br>    - Effect: Allow<br>      Action: dynamodb:*<br>      Resource: arn:aws:dynamodb:eu-central-1:178190218027:table/US_users_events<br>    - Effect: Allow<br>      Action: dynamodb:*<br>      Resource: arn:aws:dynamodb:eu-central-1:178190218027:table/EU_users_events<br>    - Effect: Allow<br>      Action: s3:*<br>      Resource: arn:aws:s3:::sfn-blog<br>plugins:<br>  - serverless-step-functions<br># Define a path to the AWS Lambda Functions<br>functions:<br>  FetchFromDynamoDBState:<br>    handler: handler.FetchFromDynamoDBState<br>  ProcessUSUsersEvents:<br>    handler: handler.ProcessUSUsersEvents<br>  ProcessEUUsersEvents:<br>    handler: handler.ProcessEUUsersEvents<br>  InsertMessageDynamoDBTest:<br>    handler: handler.InsertMessageDynamoDBTest<br>  VerifyStepFunctionOutcomeTest:<br>    handler: handler.VerifyStepFunctionOutcomeTest<br># Define Step Function and its state machine<br>stepFunctions:<br>  stateMachines:<br>    proceedRewards:<br>      name: proceedRewards<br>      definition:<br>        StartAt: InsertMessageDynamoDBTest<br>        States:<br>          InsertMessageDynamoDBTest:<br>            Type: Task<br>            Resource:<br>              Fn::GetAtt: [InsertMessageDynamoDBTest, Arn]<br>            ResultPath: &quot;$.response&quot;<br>            Next: WaitState<br>          WaitState:<br>            Type: Wait<br>            Seconds: 200<br>            Next: FetchFromDynamoDBState<br>          FetchFromDynamoDBState:<br>            Type: Task<br>            Resource:<br>              Fn::GetAtt: [FetchFromDynamoDBState, Arn]<br>            ResultPath: &quot;$.items&quot;<br>            Next: ProcessDataState<br>          ProcessDataState:<br>            Type: Map<br>            ItemsPath: &quot;$.items.items&quot;<br>            ResultPath: &quot;$.mappedData&quot;<br>            MaxConcurrency: 2<br>            Iterator: <br>              StartAt: ProceedChoiceState<br>              States:<br>                ProceedChoiceState:<br>                  Type: Choice<br>                  Choices:<br>                    - Variable: $.region.S<br>                      StringEquals: &quot;US&quot;<br>                      Next: ProcessUSUsersEventsState<br>                    - Variable: $.region.S<br>                      StringEquals: &quot;EU&quot;<br>                      Next: ProcessEUUsersEventsState<br>                  Default: DefaultState<br>                ProcessUSUsersEventsState:<br>                  Type: Task<br>                  Resource:<br>                    Fn::GetAtt: [ProcessUSUsersEvents, Arn]<br>                  End: true<br>                ProcessEUUsersEventsState:<br>                  Type: Task<br>                  Resource:<br>                    Fn::GetAtt: [ProcessEUUsersEvents, Arn]<br>                  End: true<br>                DefaultState:<br>                  Type: Fail<br>                  Cause: &#39;Invalid region value.&#39;<br>                  Error: &#39;InvalidRegionError&#39;<br>            Next: VerifyStepFunctionOutcomeTest<br>          VerifyStepFunctionOutcomeTest:<br>            Type: Task<br>            Resource:<br>              Fn::GetAtt: [VerifyStepFunctionOutcomeTest, Arn]<br>            End: true</pre><p>12. Include the following Lambda functions in the handler.js file (if you are wondering why we are using these keys ‘S’, ’N’ and ‘BOOL’ in params for DynamoDB client it is because the library itself needs that specification of the data types, ‘S’ for string, ’N’ for number, ‘BOOL’ for boolean and etc.):</p><pre>const { DynamoDBClient, PutItemCommand, ScanCommand, UpdateItemCommand } = require(&#39;@aws-sdk/client-dynamodb&#39;);<br>const dynamoDbClient = new DynamoDBClient({ region: &#39;eu-central-1&#39; });<br>module.exports.FetchFromDynamoDBState = async () =&gt; {<br>try {<br>const params = {<br>            TableName: &#39;messages&#39;,<br>            FilterExpression: &#39;is_processed = :processed&#39;,<br>            ExpressionAttributeValues: {<br>&#39;:processed&#39;: { BOOL: false },<br>            }<br>        };<br>const scanCommand = new ScanCommand(params);<br>let result = await dynamoDbClient.send(scanCommand);<br>const items = result.Items;<br>return {<br>            items<br>        };<br>    } catch (error) {<br>throw error;<br>    }<br>}<br>module.exports.ProcessUSUsersEvents = async (item) =&gt; {<br>return await processUsersEvents(item, &#39;US_users_events&#39;);<br>}<br>module.exports.ProcessEUUsersEvents = async (item) =&gt; {<br>return await processUsersEvents(item, &#39;EU_users_events&#39;);<br>}<br>const processUsersEvents = async (item, tableName) =&gt; {<br>let dataProcessed = false;<br>try {<br>await processItem(item, tableName);<br>await updateIsProcessedColumn(item.ID.S);<br>        dataProcessed = true;<br>    } catch (error) {<br>return dataProcessed;<br>    }<br>return dataProcessed;<br>}<br>const processItem = async (item, tableName) =&gt; {<br>const eventItem = {<br>        ID: { S: item.ID.S },<br>        fName: { S: item.fName.S },<br>        lName: { S: item.lName.S },<br>        points: { N: item.points.N },<br>    }<br>const params = {<br>        TableName: tableName,<br>        Item: eventItem<br>    };<br>const command = new PutItemCommand(params);<br>await dynamoDbClient.send(command);<br>}<br>const updateIsProcessedColumn = async (itemId) =&gt; {<br>const paramsForUpdate = {<br>        TableName: &#39;messages&#39;,<br>        Key: {<br>            ID: { S: itemId },<br>            UpdateExpression: &#39;SET is_processed = :value&#39;,<br>            ExpressionAttributeValues: { &#39;:value&#39;: { BOOL: true } },<br>            ReturnValues: &#39;UPDATED_NEW&#39;<br>        }<br>    };<br>const updateItemCommand = new UpdateItemCommand(paramsForUpdate);<br>await dynamoDbClient.send(updateItemCommand);<br>}13. Run command: ‘sls deploy’ to the Step Function:</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*quFPBl0NsPWpiFsw.png" /></figure><h4>Test Automation</h4><p>In real-world scenarios, this Step Function can be scheduled to run a workflow at regular intervals, such as every hour, so we have to split the test automation into two parts: data insertion and verification.</p><p>Firstly, we will create a test automation script to insert a message with test data to the DynamoDB table ‘messages’ and save this message as a test job artifact to an S3 bucket.</p><p>AWS S3 is a cloud-based object storage service provided by Amazon Web Services (AWS) (you can learn more <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html">here</a> ) that we need in this case to store our test artifacts that will be later used from the second script that will do the verification part. You can think of the S3 bucket as a virtual container where our test data will be kept, but of course, you can use other services for this purpose. The good alternatives could be saving the data as the GitHub artifacts, or if you choose to work this with Jenkins, you can have the same concept and also save the data on your own servers. It is also important to explain that we can not save the artifacts locally on AWS Lambdas because they are serverless computing services that are packaged into isolated environments (containers) where it runs on a multi-tenant cluster of machines managed by AWS.</p><p>We will use Node JS with JEST for this example.</p><pre>const { DynamoDBClient, PutItemCommand } = require(&#39;@aws-sdk/client-dynamodb&#39;);<br>const dynamoDbClient = new DynamoDBClient({ region: &#39;eu-central-1&#39; });<br>const S3Client = require(&#39;./S3Client&#39;);<br>describe(&#39;Adding message to the DynamoDB messages table&#39;, () =&gt; {<br>let item;<br>    beforeAll(async () =&gt; {<br>        item = {<br>            ID: { S: new Date().toISOString() },<br>            fName: { S: &#39;Test_First_Name&#39; },<br>            lName: { S: &#39;Test_Last_Name&#39; },<br>            points: { N: &#39;500&#39; },<br>            is_processed: { BOOL: false },<br>            region: { S: &#39;US&#39; }<br>        };<br>    });<br>    it(&#39;Adding message&#39;, async () =&gt; {<br>const params = {<br>            TableName: &#39;messages&#39;,<br>            Item: item<br>        };<br>try {<br>const command = new PutItemCommand(params);<br>await dynamoDbClient.send(command);<br>console.log(&#39;Data inserted successfully.&#39;);<br>        } catch (error) {<br>console.log(&#39;Error inserting data:&#39;, error);<br>        }<br>    }, 10000);<br>    it(&#39;Save message to S3 as a job artifact &#39;, async () =&gt; {<br>const s3Client = new S3Client();<br>const bucket = &#39;sfn-blog&#39;;<br>const key = &#39;test-message.json&#39;;<br>await s3Client.removeObject(bucket, key);<br>await s3Client.uploadFileToS3(<br>            bucket,<br>            key,<br>JSON.stringify(item),<br>&#39;application/json&#39;<br>        );<br>    }, 20000);<br>});</pre><p>Secondly, we will create a script that should be run after the Step Function finishes to verify that the message has been processed. In this script, we will pull the artifact JSON file from S3 bucket, and verify that this record can be found in the ‘US_users_events’ table in DynamoDB.</p><pre>const { DynamoDBClient, ScanCommand } = require(&#39;@aws-sdk/client-dynamodb&#39;);<br>const dynamoDbClient = new DynamoDBClient({ region: &#39;eu-central-1&#39; });<br>const S3Client = require(&#39;./S3Client&#39;);<br>describe(&#39;Verify that message has been processed&#39;, () =&gt; {<br>let item;<br>const s3Client = new S3Client();<br>    beforeAll(async () =&gt; {<br>        item = await s3Client.getObject(&#39;sfn-blog&#39;, &#39;test-message.json&#39;);<br>    }, 200000);<br>    it(&#39;Verify DynamoDB US_users_events&#39;, async () =&gt; {<br>const params = {<br>            TableName: &#39;US_users_events&#39;<br>        };<br>const scanCommand = new ScanCommand(params);<br>const response = await dynamoDbClient.send(scanCommand);<br>const records = response.Items;<br>const testRecord = records.filter(it =&gt; it.ID.S == item.ID.S);<br>        expect(testRecord.length).toBe(1);<br>        expect(testRecord[0].fName.S).toBe(item.fName.S);<br>        expect(testRecord[0].lName.S).toBe(item.lName.S);<br>        expect(testRecord[0].points.N).toBe(item.points.N);<br>    }, 20000);<br>});</pre><p>We also need a helper file for S3 related methods. Create S3Client.js file:</p><pre>const { S3, DeleteObjectCommand, GetObjectCommand, PutObjectCommand } = require(&#39;@aws-sdk/client-s3&#39;);<br>class S3Client {<br>    s3Client;<br>constructor() {<br>this.s3Client = new S3({ region: &#39;eu-central-1&#39; });<br>    }<br>async getObject(bucketName, key) {<br>try {<br>const params = {<br>                Bucket: bucketName,<br>                Key: key<br>            };<br>const getObjectCommand = new GetObjectCommand(params);<br>const response = await this.s3Client.send(getObjectCommand);<br>const bodyStream = response.Body;<br>let data = &quot;&quot;;<br>for await (const chunk of bodyStream) {<br>                data += chunk;<br>            }<br>const object = JSON.parse(data);<br>return object;<br>        } catch (error) {<br>console.log(error);<br>return null;<br>        }<br>    }<br>async removeObject(bucketName, key) {<br>try {<br>const params = {<br>                Bucket: bucketName,<br>                Key: key<br>            };<br>const removeObjectCommand = new DeleteObjectCommand(params);<br>await this.s3Client.send(removeObjectCommand);<br>        } catch (error) {<br>console.log(error);<br>return null;<br>        }<br>    }<br>async uploadFileToS3(bucketName, key, body, contentType) {<br>try {<br>const params = {<br>                Bucket: bucketName,<br>                Key: key,<br>                Body: body,<br>                ContentType: contentType<br>            }<br>const putObjectCommand = new PutObjectCommand(params);<br>await this.s3Client.send(putObjectCommand);<br>        } catch (error) {<br>console.log(error);<br>return null;<br>        }<br>    }<br>}<br>module.exports = S3Client;</pre><p>We will now create workflows (think of it as a Jenkins job) for these two scripts with GitHub Actions, but you can use any tool you want, for instance, Jenkins, TeamCity, etc. If you want to know more about GitHub Actions, you can find it on their <a href="https://github.com/features/actions">official website</a>.</p><p>To define workflows in GitHub Actions, follow these steps:</p><ol><li>Create a repository on GitHub,</li><li>Upload test files, including package.json,</li><li>Click on the ‘Actions’ button</li><li>Click on the ‘New workflow’ button,</li><li>Choose ‘Node.js’ workflow,</li><li>Define your workflow for the first test script as a yml file inside folder ‘.github/workflows’:</li></ol><pre>name: insert-message-DynamoDB<br>on:<br>  workflow_dispatch:<br>env:<br>AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}<br>AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}<br>AWS_REGION: eu-central-1<br>jobs:<br>  insert_message_in_DynamoDB:<br>    name: Insert message in DynamoDB<br>    runs-on: ubuntu-latest<br>    steps:<br>    - name: Checkout repository<br>      uses: actions/checkout@v2<br>    - name: Set up Node.js<br>      uses: actions/setup-node@v2<br>      with:<br>        node-version: &#39;18&#39;<br>    - name: Configure AWS credentials<br>      run: aws configure set aws_access_key_id ${{ env.AWS_ACCESS_KEY_ID }} &amp;&amp; aws configure set aws_secret_access_key ${{ env.AWS_SECRET_ACCESS_KEY }} &amp;&amp; aws configure set region ${{ env.AWS_REGION }}<br>    - name: Install dependencies<br>      run: npm install<br>    - name: Run InsertMessageDynamoDB tests<br>      run: npm run test insertMessageDynamoDB.test.js</pre><p>7. Do the same for the second script:</p><pre>name: verify-step-function-outcome<br>on:<br>  workflow_dispatch:<br>env:<br>AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}<br>AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}<br>AWS_REGION: eu-central-1<br>jobs:<br>  verify_outcome:<br>    name: Verify Step Function Outcome<br>    runs-on: ubuntu-latest<br>    steps:<br>    - name: Checkout repository<br>      uses: actions/checkout@v2<br>    - name: Set up Node.js<br>      uses: actions/setup-node@v2<br>      with:<br>        node-version: &#39;18&#39;<br>    - name: Configure AWS credentials<br>      run: aws configure set aws_access_key_id ${{ env.AWS_ACCESS_KEY_ID }} &amp;&amp; aws configure set aws_secret_access_key ${{ env.AWS_SECRET_ACCESS_KEY }} &amp;&amp; aws configure set region ${{ env.AWS_REGION }}<br>    - name: Install dependencies<br>      run: npm install<br>    - name: Run VerifyStepFunctionOutcome tests<br>      run: npm run test verifyStepFunctionOutcome.test.js</pre><p>8. Add InsertMessageDynamoDBTest as a first step in the Step Function States array, which should invoke the Lambda function that will trigger the Github Action workflow for running the first test script that will insert a message in DynamoDB ‘messages’ table,</p><p>9. Add a wait state after state for triggering the first test script so we can be sure that script execution on the GitHub side has been finished</p><p>10. Add a state at the end of the Step Function that should invoke the Lambda function that will trigger the Github Action workflow for running the second test case that will verify that the message has been processed successfully</p><p>11. Add env.json file with the GitHub Actons credentials and trigger IDs:</p><pre>{<br>    &quot;INSERT_ACTION_TRIGGER_ID&quot;: &lt;GITHUB_WORKFLOW_ID_FOR_DYNAMODB_TEST_DATA_INSERTION&gt;,<br>    &quot;VERIFY_ACTION_TRIGGER_ID&quot;:&lt;GITHUB_WORKFLOW_ID_FOR_DYNAMODB_TEST_DATA_VERIFICATION&gt;,<br>    &quot;GITHUB_OWNER&quot;: &lt;YOUR_GITHUB_USERNAME&gt;,<br>    &quot;GITHUB_REPO&quot;: &lt;YOUR_GITHUB_REPO&gt;,<br>    &quot;GITHUB_TOKEN&quot;: &lt;YOUR_GITHUB_TOKEN&gt;<br>}</pre><p>12. Specify the environment file inside serverless.yml file under the provider configuration</p><p>The final version of the serverless.yml file for the Step Function definition should look like the following:</p><pre>service: sfn-blog<br>frameworkVersion: &quot;3&quot;<br># Define  cloud provider settings and IAM roles needed for our SF to work<br>provider:<br>  name: aws<br>  runtime: nodejs18.x<br>  region: eu-central-1<br>  environment: ${file(env.json)}<br>  iamRoleStatements:<br>    - Effect: Allow<br>      Action: dynamodb:*<br>      Resource: arn:aws:dynamodb:eu-central-1:178190218027:table/messages<br>    - Effect: Allow<br>      Action: dynamodb:*<br>      Resource: arn:aws:dynamodb:eu-central-1:178190218027:table/US_users_events<br>    - Effect: Allow<br>      Action: dynamodb:*<br>      Resource: arn:aws:dynamodb:eu-central-1:178190218027:table/EU_users_events<br>    - Effect: Allow<br>      Action: s3:*<br>      Resource: arn:aws:s3:::sfn-blog<br>plugins:<br>  - serverless-step-functions<br># Define a path to the AWS Lambda Functions<br>functions:<br>  FetchFromDynamoDBState:<br>    handler: handler.FetchFromDynamoDBState<br>  ProcessUSUsersEvents:<br>    handler: handler.ProcessUSUsersEvents<br>  ProcessEUUsersEvents:<br>    handler: handler.ProcessEUUsersEvents<br>  InsertMessageDynamoDBTest:<br>    handler: handler.InsertMessageDynamoDBTest<br>  VerifyStepFunctionOutcomeTest:<br>    handler: handler.VerifyStepFunctionOutcomeTest<br># Define Step Function and its state machine<br>stepFunctions:<br>  stateMachines:<br>    proceedRewards:<br>      name: proceedRewards<br>      definition:<br>        StartAt: InsertMessageDynamoDBTest<br>        States:<br>          InsertMessageDynamoDBTest:<br>            Type: Task<br>            Resource:<br>              Fn::GetAtt: [InsertMessageDynamoDBTest, Arn]<br>            ResultPath: &quot;$.response&quot;<br>            Next: WaitState<br>          WaitState:<br>            Type: Wait<br>            Seconds: 200<br>            Next: FetchFromDynamoDBState<br>          FetchFromDynamoDBState:<br>            Type: Task<br>            Resource:<br>              Fn::GetAtt: [FetchFromDynamoDBState, Arn]<br>            ResultPath: &quot;$.items&quot;<br>            Next: ProcessDataState<br>          ProcessDataState:<br>            Type: Map<br>            ItemsPath: &quot;$.items.items&quot;<br>            ResultPath: &quot;$.mappedData&quot;<br>            MaxConcurrency: 2<br>            Iterator: <br>              StartAt: ProceedChoiceState<br>              States:<br>                ProceedChoiceState:<br>                  Type: Choice<br>                  Choices:<br>                    - Variable: $.region.S<br>                      StringEquals: &quot;US&quot;<br>                      Next: ProcessUSUsersEventsState<br>                    - Variable: $.region.S<br>                      StringEquals: &quot;EU&quot;<br>                      Next: ProcessEUUsersEventsState<br>                  Default: DefaultState<br>                ProcessUSUsersEventsState:<br>                  Type: Task<br>                  Resource:<br>                    Fn::GetAtt: [ProcessUSUsersEvents, Arn]<br>                  End: true<br>                ProcessEUUsersEventsState:<br>                  Type: Task<br>                  Resource:<br>                    Fn::GetAtt: [ProcessEUUsersEvents, Arn]<br>                  End: true<br>                DefaultState:<br>                  Type: Fail<br>                  Cause: &#39;Invalid region value.&#39;<br>                  Error: &#39;InvalidRegionError&#39;<br>            Next: VerifyStepFunctionOutcomeTest<br>          VerifyStepFunctionOutcomeTest:<br>            Type: Task<br>            Resource:<br>              Fn::GetAtt: [VerifyStepFunctionOutcomeTest, Arn]<br>            End: true</pre><p>The additional Lambdas for triggering GitHub workflows should look like this:</p><pre>const axios = require(&#39;axios&#39;);<br>module.exports.InsertMessageDynamoDBTest = async () =&gt; {<br>await triggerWorkflow(process.env.INSERT_ACTION_TRIGGER_ID);<br>}<br>module.exports.VerifyStepFunctionOutcomeTest = async () =&gt; {<br>await triggerWorkflow(process.env.VERIFY_ACTION_TRIGGER_ID);<br>}<br>const triggerWorkflow = async (workFlowId) =&gt; {<br>const owner = process.env.GITHUB_OWNER;<br>const repo = process.env.GITHUB_REPO;<br>const token = process.env.GITHUB_TOKEN;<br>let response;<br>try {<br>        response = await axios.post(<br>`https://api.github.com/repos/${owner}/${repo}/actions/workflows/${workFlowId}/dispatches`,<br>            {<br>                ref: &#39;main&#39;<br>            },<br>            {<br>                headers: {<br>&#39;Content-Type&#39;: &#39;application/json&#39;,<br>&#39;Authorization&#39;: `Bearer ${token}`<br>                }<br>            }<br>        );<br>console.log(&#39;Job triggered successfully:&#39;, response.data);<br>    } catch (error) {<br>console.error(&#39;Failed to trigger job:&#39;, error.response.data);<br>    }<br>return response;<br>}</pre><p>The final version of the Step Function should look like the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*iX3xGIh-XmTVyAZc.png" /></figure><p>This way, we will ensure that our Step Function continuously processes the messages successfully.</p><p><strong><em>Note:</em></strong><em> The testing states should be present only in the testing environments.</em></p><h3>Alternatives</h3><p>We could implement test automation for this kind of system by using other approaches. For example, we could create scheduled triggers for GitHub Actions Workflows. The first one for data insertion to the DynamoDB could be scheduled at the beginning of the day, and the second one for the verification of processed data could be scheduled at a time when we expect that at least one Step Function Workflow execution has been finished. The advantage of this approach is that we could avoid using the first and the last lambda functions for triggering the GitHub Actions Workflows that we specified in our Test Automation part. The advantage of the first approach that we explained is that if someone makes some changes on the Step Function, it will be verified on the next execution. We could also implement both approaches with Jenkins and any other CI/CD tool.</p><h3>The main challenges</h3><p>The main challenges for the implementation of test automation on systems based on Step Functions (systems that run periodically or per some event) are unique, and here are some of the main ones:</p><ol><li><strong>Time-dependent testing:</strong> Systems triggered periodically often involve time-based operations. We may need to control the passage of time during tests to validate time-dependent behaviors.</li><li><strong>Data Security:</strong> We must ensure that test data are handled securely and that the test environments are adequately isolated, especially if we are working with sensitive data.</li><li><strong>End-to-End Testing:</strong> We need to ensure that all components work together, and it often requires two or more separate scripts that will orchestrate together by exchanging the data as artifacts for testing just one system behavior.</li><li><strong>Continuous Integration/Continuous Deployment (CI/CD):</strong> We must ensure that tests run automatically with each deployment.</li><li><strong>Handling Large Data Sets:</strong> Systems with Step Functions may process large data sets. Testing with large data can take time and resources, so it needs a particular approach.</li><li><strong>Environment Consistency:</strong> To catch environment-specific issues, test environments should closely resemble production environments, including Cloud configurations.</li><li><strong>Resource Setup and Teardown: </strong>Setting up and tearing down AWS resources, such as databases, queues, or S3 buckets, for each test can be time-consuming and costly.</li><li><strong>Data Generation:</strong> Creating test data to simulate real-world scenarios and edge cases can be challenging. We must ensure that the data is consistent with the state transitions in our Step Function workflows.</li><li><strong>Connection with External Services:</strong> Establishing connections to the different services like databases and etc., can sometimes be very challenging based on many different situations, including security configurations, etc.</li></ol><h3>Conclusion</h3><p>We have gone through the overall concept of development and application of the Step Function and the integration of automation testing within its workflow. Through all this, we were introduced to additional tools from the domain of cloud computing, CI/CD, and test automation.</p><p>You can find the full project on <a href="https://github.com/ATLANTBH/testing-research/tree/master/step_function_integrated_test_automation">this GitHub repository</a>.</p><h3>Sources:</h3><p><a href="https://docs.aws.amazon.com/step-functions/latest/dg/concepts-states.html">https://docs.aws.amazon.com/step-functions/latest/dg/concepts-states.html</a></p><p>If you found this useful, check out other Atlantbh <a href="https://www.atlantbh.com/blog/">blogs</a>!</p><p><em>Originally published at </em><a href="https://www.atlantbh.com/test-automation-integrated-into-aws-step-function-workflow/"><em>https://www.atlantbh.com</em></a><em> on November 20, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ee5f2d07bd0a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/atlantbh/test-automation-integrated-into-aws-step-function-workflow-ee5f2d07bd0a">Test Automation Integrated into AWS Step Function Workflow</a> was originally published in <a href="https://medium.com/atlantbh">Atlantbh Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Crafting Collaboration in Elixir: A Case Study of Success in Software Development]]></title>
            <link>https://medium.com/atlantbh/crafting-collaboration-in-elixir-a-case-study-of-success-in-software-development-cce6e472f6e?source=rss----eb9022a96e98---4</link>
            <guid isPermaLink="false">https://medium.com/p/cce6e472f6e</guid>
            <category><![CDATA[collaboration]]></category>
            <category><![CDATA[product-management]]></category>
            <category><![CDATA[elixir]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Atlantbh]]></dc:creator>
            <pubDate>Thu, 17 Aug 2023 10:36:16 GMT</pubDate>
            <atom:updated>2023-08-17T10:40:49.442Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_P_LDDxu-Z3c04QJX7FbYw.jpeg" /></figure><p>Uniting different skill sets, experiences, and perspectives through engineering collaboration is a process that brings complex challenges, but also can forge innovation through synergy if done correctly. Atlantbh had a chance to work on such a project where close day-to-day collaboration with an external development team was needed for successful delivery. In this case study, we’ll explore how we collaborated to update the system’s architecture, handle changes in technology and onboard to Elixir, and navigate the business landscape while nurturing a partnership built on adaptability and shared knowledge.</p><h3>The problem</h3><p>The client had been developing a backend system for a number of years, for which they realized that the initial architecture and application model were no longer suitable to their current needs. As the business was spreading, this implicated a rise in the number of end-users and different use cases which haven’t been foreseen and didn’t fit in the current architecture. To bypass system limitations, numerous temporary solutions needed to be implemented to address these gaps. Unfortunately, when the system outgrows its initial architecture and temporary solutions come into play for a longer period, at some point the development team finds itself fully consumed with fixing production issues that arise due to this very situation, as was the case with this particular project.</p><p>Consequently, a decision has been made to rewrite the old backend system using a more appropriate tech stack and to create a flexible model capable of accommodating all use cases. In search of a partner, the client was looking for someone who would invest considerable time and expertise in analyzing the requirements, learning both the business and new technology, collaborating closely with the internal technical team, and successfully integrating into the existing ecosystem.</p><h3>Challenges</h3><p>During the initial weeks, we devoted time to gathering and analyzing requirements. Additionally, we have invested efforts in familiarizing ourselves with the legacy application, and the whole ecosystem into which our service would integrate, addressing technical challenges, and gaining an understanding of the client’s expectations.</p><p>The team was faced with the following challenges:</p><ul><li><strong>New technology stack — Elixir.</strong> The transition from <a href="https://dev.java/">Java</a> (our usual choice of technology) to Elixir required learning and adapting to the new technology and frameworks. We did have expertise in Ruby which is a similar programming language, but not with the particular one. What also helped is the fact that our software engineers are full-stack engineers, and are used to adopting different technology stacks.</li><li><strong>Event-driven model approach.</strong> Adopting an event-driven model for software development introduced a paradigm shift in our development approach. It already helped that we had expertise in event-based communication, designing asynchronous workflows, etc.</li><li><strong>Complex ecosystem and business logic.</strong> Our software was part of a larger ecosystem, including multiple interconnected services that shared data and events. We did have experience in working with different teams in large ecosystems, but this one was quite different in size and complexity. We had to find the best collaboration model which would work for both teams, to understand dependencies and common practices.</li></ul><h3>Approach</h3><h3>Our journey with Elixir</h3><p>The client offered assistance in knowledge transfer and onboarding to the new technology stack to address these challenges. We conducted knowledge transfer and pair programming sessions, sharing expertise and guiding us to avoid common pitfalls when starting with this stack. The team was grateful for such an opportunity, which leveraged our efforts later. We continued independently familiarizing ourselves with Elixir, and started the development of standalone features.</p><h3>Collaboration model</h3><p>The chosen collaboration model involved regular communication and coordination between the teams:</p><ul><li>Daily sync calls were conducted, with representatives from both teams sharing updates and discussing progress.</li><li>Weekly sync meetings between the product manager, technical lead, and our service lead helped align expectations and address any concerns.</li><li>Planning and breakdown sessions were organized as needed to ensure efficient project management and decision-making.</li><li>All parties were open to constructive feedback, which helped us overcome challenging situations and manage expectations.</li></ul><h3>Development and delivery</h3><p>In collaboration with a product manager and technical lead, we defined the scope for the initial version of the product, ensuring it would provide immediate value to the users. Our team was responsible for the backend service which would replace a specific segment of the old system.</p><p>As the business logic defined was unlikely to undergo significant changes in the future, we have agreed on the approach that involved implementing the defined functionalities one by one, in detail, incorporating comprehensive logic and thorough unit and integration testing. To document the system behavior, we adopted the Gherkin language which was already being used in the rest of the ecosystem, ensuring clarity and future stability. While this approach demanded more time for development and delivery upfront, the client was aware of the long-term benefits of investing in a solid foundation.</p><p>As of now, our service has been successfully deployed in production, catering to a small user base. By diligently gathering feedback and continuously adapting to improve our service, we are moving on our way to transition to larger user groups. The feedback received thus far validated the stability and reliability of our core logic.</p><h3>Conclusion</h3><p>Each new project presents its unique challenges, but a willingness to embrace new approaches and technologies brings significant advantages to software companies, broadening their horizons and knowledge base.</p><p>Throughout this endeavor, our team demonstrated key characteristics that played an essential role in a successful partnership with our client. Our expertise in software delivery, profound technical knowledge of <a href="https://www.atlantbh.com/service-ux-ui-design/">design</a> principles and <a href="https://www.atlantbh.com/service-software-development/">software</a> engineering, problem-solving skills, and readiness to embrace new methodologies and feedback were instrumental in achieving positive outcomes.</p><p><em>Technology stack: Elixir, Phoenix, HTML, CSS, Rabbitmq, Gherkin<br>Project duration: 1+ year</em></p><p><em>Blog by </em><a href="https://www.atlantbh.com/author/irma-smajic/"><em>Irma Smajić</em></a><em>, Product Owner at </em><a href="https://www.atlantbh.com/"><em>Atlantbh</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cce6e472f6e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/atlantbh/crafting-collaboration-in-elixir-a-case-study-of-success-in-software-development-cce6e472f6e">Crafting Collaboration in Elixir: A Case Study of Success in Software Development</a> was originally published in <a href="https://medium.com/atlantbh">Atlantbh Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unleashing the Power of AI in Product Development]]></title>
            <link>https://medium.com/atlantbh/unleashing-the-power-of-ai-in-product-development-28106e69cc0e?source=rss----eb9022a96e98---4</link>
            <guid isPermaLink="false">https://medium.com/p/28106e69cc0e</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[product-development]]></category>
            <category><![CDATA[product-management]]></category>
            <dc:creator><![CDATA[Atlantbh]]></dc:creator>
            <pubDate>Thu, 17 Aug 2023 10:14:12 GMT</pubDate>
            <atom:updated>2023-08-17T10:13:38.303Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*3fP4ljSCmaCUHdl7.jpg" /></figure><p>Lately, we have seen a significant number of discussions regarding the potential of AI and strategies to incorporate it into the product development process. Specifically, how can we enhance, innovate, and scale in ways we were not able to do before? In this interview, we asked Samra Tanovic, our VP of Analytics and Services, to share her experience and insights on this topic.</p><p><strong><em>Q: Could you tell us about your journey in incorporating AI into products?</em></strong></p><p><strong>A:</strong> Several years ago, when the need for AI functionalities arose within one of our projects, we were faced with a series of questions: How do we enable our teams and build the expertise needed for such tasks? How long will it take? How do we spot opportunities for the use of AI faster? What are the costs?</p><p>The key takeaway from this experience is that it’s essential to start with an explicit, well-defined goal that aligns with business and user needs and then to consider how AI could assist in achieving this goal. It’s important to recognize that not all problems require an AI solution. There’s a risk of wasting significant time and resources trying to utilize AI just for the sake of using it. Instead, we learned to carefully examine existing challenges and ask: Can AI solve this problem more efficiently? Is there potential for scalability? Does the benefit of using AI outweigh the cost?</p><p>Our journey also taught us that data science projects, unlike standard software engineering, involve a series of experiments that may or may not lead to discoveries or entirely new lines of research. The inherent uncertainties in this process make it difficult to define and control, which is why a unique management approach is required.</p><p>Another key aspect we realized is the importance of the data that AI uses and produces. While the size of the training data does impact the model’s performance, data quality often plays a considerably more influential role. Investing in clean, relevant, and diverse data preprocessing turned out to be essential for accurate AI outcomes, making it an investment worth prioritizing.</p><p>Finally, in order to build expertise and prepare our company for the rapidly evolving AI landscape, we set up a team dedicated to the research, development, and implementation of cutting-edge AI solutions within existing and new projects. This step allowed us to continue innovating and incorporating AI where it made sense, enhancing both our products and our ability to adapt to future technological advancements.</p><p><strong><em>Q: Could you provide examples where your teams used AI to enhance problem-solving efficiency?</em></strong></p><p><strong>A:</strong> The main criterion is to consider processes that are excessively manual, do not scale well with product growth, or areas where customer experiences could be improved through personalization or automation. ML is mostly required in cases when the targeted behavior cannot be accurately expressed in software logic without relying on external data sources.</p><p>A few years back, we used AI to analyze text and extract common business attributes, such as business category, address and operating hours from an unstructured text, a process that was previously time-consuming for humans. The efficiency of the validation of the business attributes, which was our main use case, was increased by 80%.</p><p>An interesting and simple example involved removing a common UI element, a dropdown menu, and using classification to determine the correct option automatically. Previously, selecting the wrong option led to downstream problems and complicated user experience. However, by leveraging ML to accurately determine the appropriate dropdown option based on the user’s input, we managed to reduce mistakes by 30%.</p><p>Moreover, an intriguing area to consider is threshold replacements. Traditional business rules with set thresholds are often used to trigger specific actions. However, these thresholds can be too rigid and may not adapt well to changing circumstances. AI models, in contrast, can learn from data patterns and make more flexible, dynamic decisions.</p><p><strong><em>Q: Lastly, drawing from your experience, what are some obstacles that businesses might encounter while implementing AI, and what strategies can they employ to overcome them?</em></strong></p><p><strong>A:</strong> One of the biggest challenges is the lack of trust in AI-powered solutions. Many businesses are hesitant to rely on AI for critical decisions because they don’t fully understand how algorithms work or the resulting accuracy is not meeting their expectations.</p><p>AI systems are designed to learn but can make errors or provide inaccurate results based on various factors, such as the quality of the training data, complexity of the task, and limitations of the algorithms used.</p><p>In scenarios where explicit and deterministic results are critical, businesses should carefully assess the suitability of AI models. Hybrid approaches that pair AI with deterministic methods should be considered, along with models with high interpretability, to maintain control and build trust. It is also useful to be transparent about limitations and potential biases.</p><p>Speaking of biases, if not designed and trained with fairness and ethical considerations in mind, AI systems have the potential to perpetuate biases and discrimination. Ensuring compliance with regulations, regular audits, testing for bias and seeking legal advice can help.</p><p><strong><em>Q: Thank you. It was a pleasure discussing this topic.</em></strong></p><p><strong>A:</strong> Thank you. I’m excited to see how AI continues to shape and enhance the field of product development.</p><p><em>Originally published at </em><a href="https://www.atlantbh.com/unleashing-the-power-of-ai-in-product-development/"><em>https://www.atlantbh.com</em></a><em> on July 24, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=28106e69cc0e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/atlantbh/unleashing-the-power-of-ai-in-product-development-28106e69cc0e">Unleashing the Power of AI in Product Development</a> was originally published in <a href="https://medium.com/atlantbh">Atlantbh Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Random String Detection]]></title>
            <link>https://medium.com/atlantbh/random-string-detection-1ed48de05a0e?source=rss----eb9022a96e98---4</link>
            <guid isPermaLink="false">https://medium.com/p/1ed48de05a0e</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[gibberish]]></category>
            <category><![CDATA[keyboard-smash]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[random-string]]></category>
            <dc:creator><![CDATA[Mehmed Kadric]]></dc:creator>
            <pubDate>Mon, 22 May 2023 13:28:43 GMT</pubDate>
            <atom:updated>2024-06-23T10:58:49.204Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="https://pypi.org/project/random-string-detector/">https://pypi.org/project/random-string-detector/</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8krMy25yEcQfOLXirrjrZQ.jpeg" /></figure><p>Data analysis often presents us with the challenge of dealing with noisy inputs. This is particularly evident when working with large datasets of user inputs. Detecting and filtering out random string inputs can prove invaluable in various scenarios, such as data validation, quality control or the development of machine learning (ML) based natural language processing (NLP) systems (check for data quality improvement steps in <a href="https://www.atlantbh.com/data-centric-ai-big-data-vs-good-data/">this blog post</a>).</p><figure><img alt="Image 1 — Random Strings" src="https://cdn-images-1.medium.com/max/1024/1*3aXECpbyV0mqEvQ1KrC_4A.png" /><figcaption><em>Image 1 — Random Strings</em></figcaption></figure><h3>What should/can be considered a ‘random string’?</h3><p>The term “random string” may seem straightforward at first glance, but its interpretation can be subjective and context-dependent. What one person perceives as a random string, another may see as a meaningful pattern or representation. This relativity is particularly evident when considering visual or linguistic aspects.</p><p>Let’s delve into a few examples to highlight the importance of relativity when defining random strings. Take, for instance, the string ‘\__o__/’. At first glance, it might appear random, but one could argue that it resembles a stick figure of a man with raised hands. In this case, the interpretation of randomness becomes subjective, as different individuals may perceive different patterns or associations.</p><p>Similarly, let’s consider the word “Szczesny.” In the English language, it may appear as a random combination of letters, devoid of any meaning. However, in Polish, it happens to be the surname of a famous goalkeeper (Image 2). This example demonstrates how the interpretation of randomness can be influenced by language and cultural context. What may seem random in one language could hold significance or meaning in another.</p><figure><img alt="Image 2 [Source: Yahoo/Reuters] — Wojciech Szczesny, a Polish professional footballer" src="https://cdn-images-1.medium.com/max/1024/1*Aubsp9vf8IMyTYxo_bphgQ.jpeg" /><figcaption><em>Image 2 [Source: </em><a href="https://ca.sports.yahoo.com/news/ll-probably-banned-szczesny-reveals-235708569.html"><em>Yahoo/Reuters</em></a><em>] — Wojciech Szczesny, a Polish professional footballer</em></figcaption></figure><p>It’s crucial to acknowledge that randomness is not an inherent property of a string itself but rather a perception based on patterns or associations that individuals may perceive. In the context of data analysis and ML, understanding this relativity is vital when developing algorithms or models that aim to detect and filter out random strings.</p><p>When designing systems to identify random strings, it becomes essential to consider the specific domain, context, and purpose of the analysis. What may be considered random noise in one scenario could be a valuable signal in another. For instance, when analyzing social media data, a sequence of random characters might indicate spam or bot-generated content. On the other hand, in cryptography, random strings play a crucial role in generating secure encryption keys.</p><p>Moreover, the concept of randomness itself can vary depending on the intended application. Some algorithms generate strings that may appear random to human observers but possess specific statistical properties that make them suitable for cryptographic purposes. In contrast, other applications require strings that exhibit specific patterns or structures to serve their intended function.</p><h3>How to detect a random string of characters in a specific locale?</h3><p>If the goal is to detect random strings which consist of a specific set of characters (e.g., English alphabet characters), one possible way of detecting random strings is using bigrams.</p><p>To be able to do that, you need to create a reference dictionary of bigrams for the considered locale. For the English language, we can use bigrams and their frequencies from <a href="http://norvig.com/mayzner.html">Peter Norvig’s analysis</a>. For example, in English language text, the bigram “th” is very common, while the bigram “zx” is not. Using the available list of bigrams and their frequencies, it is possible to normalize it (e.g., min-max normalization) and create a reference dictionary. An important note here is that you can easily create your own list of bigrams (extracted from word corpus) for any locale if you are able to find open-source books, newspapers or magazines.</p><p>Reference dictionary is a dictionary of all 26² = 676 combinations of English bigrams with associated frequency numbers (from 0 to 100) where higher values represent more frequent bigrams (like “th”) and lower values represent less frequent bigrams (like “zx”). Users are able to adjust the threshold value (default value is 0.1) in order to define a boundary between less common and common bigrams. For example, if the threshold is 0, all bigrams will be considered as common bigrams. Otherwise, if the threshold is 100, all bigrams will be considered as less common bigrams. You can find a reference dictionary of bigrams <a href="https://github.com/mehmedkadric/random-string-detector/blob/main/random_string_detector/random_string_detector.py">here</a>.</p><p>To consider whether a word is a random string or not, here is a list of steps that are used to help detect random strings:</p><ol><li><strong>Length and Character Validation</strong>: The first checkpoint of the algorithm verifies that the word is longer than three characters (words up to three characters may be regular abbreviations) and consists solely of English alphabetic characters. This ensures that we are working with meaningful words rather than arbitrary combinations of symbols</li><li><strong>Repeating Characters</strong>: If the entire word is made up of the same character, such as “hhhhhhh”, it is immediately labeled as random. This is because the presence of repeating characters indicates a lack of complexity and structure, suggesting a random generation</li><li><strong>Lowercasing the Word</strong>: To eliminate the case sensitivity, the algorithm converts the word to lowercase. This prevents the distinction between uppercase and lowercase characters from affecting the detection process</li><li><strong>Bigrams Analysis</strong>: A bigram refers to a sequence of two consecutive characters in a word. The algorithm generates a list of all possible bigrams within the word. For example, the word “random” would produce the following bigrams: <em>ra, an, nd, do, om</em></li><li><strong>Common and Uncommon Bigrams</strong>: Each bigram is compared against a reference dictionary of common English bigrams. If a bigram has a frequency above a given threshold, it is considered common; otherwise, it is classified as uncommon. The choice of threshold depends on the desired sensitivity of the algorithm</li><li><strong>Counting Common and Uncommon Bigrams</strong>: The algorithm calculates the number of common and uncommon bigrams in the word. If the count of common bigrams exceeds that of uncommon bigrams, the word is classified as non-random. Conversely, if the count of uncommon bigrams surpasses that of common bigrams, the word is deemed random</li></ol><p>Here are Python and Java codes for the proposed algorithm:</p><p><strong><em>Edit</em></strong>: Check for the updated version of random-string-detector at <a href="https://github.com/mehmedkadric/random-string-detector">https://github.com/mehmedkadric/random-string-detector</a>.</p><p>Python:</p><pre>def is_random_string(word, threshold=0.1):<br>    # Allow only words longer than 3 characters which contains only English alphabetic characters<br>    if len(word) &lt; 4 or not word.isalpha():<br>        return False<br><br>    # Repeating characters<br>    if len(set(word)) == 1:<br>        return True<br><br>    # Turn word into lowercase<br>    word = word.lower()<br><br>    # Get list of bigrams from the word<br>    bigrams = [word[i:i + 2] for i in range(len(word) - 1)]<br><br>    # Get number of common and uncommon bigrams<br>    num_common_bigrams = sum(1 for bigram in bigrams if en_bigrams_dict.get(bigram, 0) &gt; threshold)<br>    num_uncommon_bigrams = len(bigrams) - num_common_bigrams<br><br>    # Higher number wins<br>    if num_common_bigrams &gt; num_uncommon_bigrams:<br>        return False<br>    else:<br>        return True</pre><p>Java:</p><pre>package checker;<br><br><br>import java.util.HashMap;<br>import java.util.Map;<br><br><br>public class RandomStringChecker {<br>   private static final double DEFAULT_THRESHOLD = 0.1;<br>   private static final Map&lt;String, Double&gt; enBigramsDict = new HashMap&lt;&gt;();<br><br><br>   static {<br>       // Populate the dictionary of English bigrams here<br>       enBigramsDict.put(&quot;ab&quot;, 6.461565670356169);<br>       enBigramsDict.put(&quot;bc&quot;, 0.0531330714265234);<br>       enBigramsDict.put(&quot;cd&quot;, 0.06273467822837461);<br>       // ...<br>   }<br><br><br>   public static boolean isRandomString(String word) {<br>       return isRandomString(word, DEFAULT_THRESHOLD);<br>   }<br><br><br>   public static boolean isRandomString(String word, double threshold) {<br>       // Allow only words longer than 3 characters which contain only English alphabetic characters<br>       if (word.length() &lt; 4 || !word.matches(&quot;[a-zA-Z]+&quot;)) {<br>           return false;<br>       }<br><br><br>       // Repeating characters<br>       if (word.chars().distinct().count() == 1) {<br>           return true;<br>       }<br><br><br>       // Turn word into lowercase<br>       word = word.toLowerCase();<br><br><br>       // Get list of bigrams from the word<br>       String[] bigrams = new String[word.length() - 1];<br>       for (int i = 0; i &lt; word.length() - 1; i++) {<br>           bigrams[i] = word.substring(i, i + 2);<br>       }<br><br><br>       // Get number of common and uncommon bigrams<br>       int numCommonBigrams = 0;<br>       for (String bigram : bigrams) {<br>           if (enBigramsDict.containsKey(bigram) &amp;&amp; enBigramsDict.get(bigram) &gt; threshold) {<br>               numCommonBigrams++;<br>           }<br>       }<br>       int numUncommonBigrams = bigrams.length - numCommonBigrams;<br><br><br>       // Higher number wins<br>       return numCommonBigrams &lt;= numUncommonBigrams;<br>   }<br>}</pre><p>This algorithm has been released as a <a href="https://pypi.org/project/random-string-detector/">Python package</a> and can easily be installed using the command:</p><pre>pip install random-string-detector</pre><h3>Examples</h3><p>Python method calls:</p><pre>from random_string_detector.random_string_detector import is_random_string<br><br>print(is_random_string(&#39;abcd&#39;)) # True<br>print(is_random_string(&#39;Thgrbh&#39;)) # False<br>print(is_random_string(&#39;Thgrbh&#39;, 5)) # True - threshold adjusted</pre><p>Java method call:</p><pre>public static void main(String[] args) {<br>   System.out.println(isRandomString(&quot;abcd&quot;)); // true<br>}</pre><h3>Conclusion</h3><p>The interpretation of randomness depends on the specific domain, context, and purpose of the analysis. What may be considered random noise in one scenario could be valuable information or a critical signal in another. It is essential to align the definition of random strings with the objectives and requirements of the particular application to ensure the reliability and quality of results.</p><p>While the proposed algorithm is not foolproof and cannot definitively determine randomness, it serves as a useful tool for various applications that require distinguishing between random and non-random strings.</p><p>A potential algorithm improvement would be to have the bigram dictionary in a separate file that we could load into memory.</p><h3>Appendix</h3><p>PyPI: <a href="https://pypi.org/project/random-string-detector/">https://pypi.org/project/random-string-detector/</a></p><p>GitHub: <a href="https://github.com/mehmedkadric/random-string-detector">https://github.com/mehmedkadric/random-string-detector</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1ed48de05a0e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/atlantbh/random-string-detection-1ed48de05a0e">Random String Detection</a> was originally published in <a href="https://medium.com/atlantbh">Atlantbh Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Data-centric AI / Big data vs. Good data]]></title>
            <link>https://medium.com/atlantbh/data-centric-ai-big-data-vs-good-data-cf7b9a04be04?source=rss----eb9022a96e98---4</link>
            <guid isPermaLink="false">https://medium.com/p/cf7b9a04be04</guid>
            <category><![CDATA[data-analytics]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[data-analysis]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Atlantbh]]></dc:creator>
            <pubDate>Tue, 10 Jan 2023 12:43:03 GMT</pubDate>
            <atom:updated>2023-01-10T12:41:49.036Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xjPczWGV1HJMfUSibFYczw.jpeg" /></figure><p>We can agree that AI is not a one-size-fits-all solution. However, for many companies, AI can provide significant benefits and help drive growth and success. Some potential benefits include the following:</p><ul><li>Improved efficiency: AI can automate tasks and processes, saving time and resources for other activities.</li><li>Increased accuracy: AI can make more accurate predictions and decisions than humans, reducing the risk of errors.</li><li>Enhanced customer experience: AI can personalize customer interactions, providing a more tailored and seamless experience.</li><li>Cost savings: AI can help businesses reduce costs by automating tasks, improving efficiency, and reducing the need for human labor.</li><li>Competitive advantage: Companies that use AI can gain a competitive advantage over those that don’t by being able to analyze and act on data faster and more effectively.</li></ul><p>The last decade has brought significant growth in the field of Data Science in general. However, the total value of AI is still locked in many sectors, such as health care, manufacturing, and government technology. In a study published by <a href="https://www.accenture.com/_acnmedia/Thought-Leadership-Assets/PDF-2/Accenture-Built-to-Scale-PDF-Report.pdf#zoom=50">Accenture</a>, 80% of all Proof of Concepts (PoCs) do not make it into production. What exactly is going wrong, and why is AI not as successful in the real world as it is in academic studies?</p><h3>Model-centric vs. Data-centric approach</h3><p>A strong model-centric AI (traditionally presented in academia) considers data only as a static parameter. If a model’s performance is not as expected, AI/ML engineers will try to tune the model’s hyperparameters or even change the model while data stays untouched.</p><p>Subject Matter Experts do not play a vital role in the whole process of developing AI-based systems in a model-centric approach. Instead, AI/ML engineers usually make business-related decisions during that process.</p><p>As per Andrew Ng, an AI system is a combination of code and data. While a significant amount of time and resources have been dedicated to developing code and algorithms, it is now necessary to prioritize improving data quality and relevance to achieve desired outcomes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/0*jxHh8kVj9K8VeZaY.png" /></figure><p>By shifting the focus to data, we are basically moving from model-centric to data-centric AI.</p><p>In a data-centric approach, the role of Subject Matter Experts is significant, and AI systems are much closer to every business.</p><p>Adopting a data-centric approach in the real world or during work on a practical project is more fruitful. That means the model stays fixed and focuses on feeding the model with good or high-quality data. A decent algorithm with good data may even outperform a great algorithm with not-so-good data.</p><p>Data should not be the only priority in the development process. It is also important to consider the structure and functionality of the model and code used. The choice of model can significantly impact the accuracy, as demonstrated in the analysis of the <a href="https://www.atlantbh.com/titanic-disaster-through-data-analysis/">Titanic disaster,</a> where the prediction accuracy varied from 78% to 94%, depending on the algorithm.</p><h3>Big Data vs. Good Data: What’s More Important for AI?</h3><p>Good Data and Big Data are two terms often used in the field of Data Science and AI. Good data refers to data that is accurate, relevant, and well-organized. Big data, on the other hand, refers to large datasets that are too large and complex to be processed and analyzed using traditional data processing tools and techniques.</p><p>In order for AI algorithms to make accurate predictions and decisions, they need to be trained on large amounts of data. However, simply having a lot of data is not enough. The data must also be high-quality and well-organized for the AI model to learn effectively.</p><p>In many cases, companies only have access to small datasets, which can lead to poor results if the focus is solely on the model. Andrew Ng emphasizes the benefits of a data-centric approach to machine learning and suggests that there should be a shift towards this approach within the community. He uses the example of a steel defect detection problem, in which the data-centric approach improved the model’s accuracy by 16% compared to a model-centric approach [Source: <a href="https://neptune.ai/blog/data-centric-vs-model-centric-machine-learning#:~:text=It&#39;s%20sometimes%20referred%20to%20as,permanent%20asset%2C%20whereas%20applications%20change.">neptune.ai</a>].</p><h3>Data Quality Issues</h3><p>Many famous datasets have data quality issues. For instance, The COCO and ImageNet datasets are widely used in the field of computer vision for tasks such as object detection, image classification, and image segmentation. While these datasets are widely used and generally considered high quality, some issues have been discovered with them that may impact their usefulness for specific tasks. Some of the problems detected in the COCO dataset include mislabelled objects (first image) and labeling inconsistencies (second image) [Source: <a href="https://www.neuralception.com/mislabelled-traffic">neuralception.com</a>].</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CaNy9UvVutVHdiKz9voRlg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/968/1*_KNtklQp__snqop4CbPkYw.png" /></figure><h3>Ensuring Data Quality in Machine Learning Development</h3><p>While the specific methods for improving data quality may vary depending on a specific problem, some general steps can be applied in many situations:</p><ul><li>Verify data sources.</li><li>Ensure that data sources are reliable and accurate.</li><li>Make sure your model gets enough data to be able to generalize.</li><li>Validate and clean data.</li><li>Check labeling inconsistencies.</li><li>Perform validation checks and look-ups on labeled data if applicable.</li><li>Check labels distribution and manually check entries with low-frequency labels.</li><li>Try to make a tool that will give you suspicious labels so you can check them manually.</li><li>Standardize data to a common format or schema.</li><li>Involve Subject Matter Experts.</li><li>Document agreements after discussing inconsistencies with labelers.</li><li>Analyze data and perform feature engineering.</li><li>Train a model with labeled data and apply the model to training data. Check the diffs.</li><li>Perform error analysis.</li></ul><p>The development of machine learning-based systems is a highly iterative process. Keeping track of your notes, findings, and conventions in a documentation system can help maintain data quality. By following mentioned steps and being vigilant about potential issues, you can help ensure that your data is of high quality and ready for use in your machine-learning projects.</p><h3>Wrapping Up</h3><p>It is important to consider the quality and quantity of data and the structure and functionality of the model and code used. By prioritizing data quality and involving subject matter experts, companies can improve the accuracy and effectiveness of their AI systems, drive growth and success, and gain a competitive advantage.</p><p><em>Blog by </em><a href="https://www.atlantbh.com/author/mehmed-kadric/"><em>Mehmed Kadrić</em></a><em>, Senior Data Analyst at </em><a href="https://www.atlantbh.com/"><em>Atlantbh</em></a></p><p><em>Originally published at </em><a href="https://www.atlantbh.com/data-centric-ai-big-data-vs-good-data/"><em>https://www.atlantbh.com</em></a><em> on January 10, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cf7b9a04be04" width="1" height="1" alt=""><hr><p><a href="https://medium.com/atlantbh/data-centric-ai-big-data-vs-good-data-cf7b9a04be04">Data-centric AI / Big data vs. Good data</a> was originally published in <a href="https://medium.com/atlantbh">Atlantbh Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>