<?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[iyzico.engineering - Medium]]></title>
        <description><![CDATA[The engineering behind iyzico, the sweet home for iyzicoders… - Medium]]></description>
        <link>https://iyzico.engineering?source=rss----c8b48d7c94e3---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>iyzico.engineering - Medium</title>
            <link>https://iyzico.engineering?source=rss----c8b48d7c94e3---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 13 Jun 2026 18:48:29 GMT</lastBuildDate>
        <atom:link href="https://iyzico.engineering/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Run JMeter Load Tests on AWS with Docker & Compose]]></title>
            <link>https://iyzico.engineering/run-jmeter-load-tests-on-aws-with-docker-compose-f40de7852b5c?source=rss----c8b48d7c94e3---4</link>
            <guid isPermaLink="false">https://medium.com/p/f40de7852b5c</guid>
            <category><![CDATA[iyzico]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[jmeter]]></category>
            <category><![CDATA[load-testing]]></category>
            <category><![CDATA[jmeter-load-testing]]></category>
            <dc:creator><![CDATA[Nurullah şahin]]></dc:creator>
            <pubDate>Mon, 26 Jan 2026 07:36:58 GMT</pubDate>
            <atom:updated>2026-01-26T07:36:56.019Z</atom:updated>
            <content:encoded><![CDATA[<p>Package once, run anywhere — EC2 execution, S3 report uploads, zero JMX internals.</p><h4>Table of Contents</h4><ol><li>Why Docker + AWS for JMeter?</li><li>What We’ll Build (Architecture)</li><li>Prerequisites</li><li>Project Layout</li><li>Dockerfile (containerizing JMeter)</li><li>Docker Build</li><li>docker-compose.yml (one-command runs)</li><li>Local Dry-Run (optional)</li><li>Create Task Definition</li><li>Create Cluster and Run Task</li><li>Result S3</li></ol><h4>Why Docker + AWS for JMeter?</h4><ul><li><strong>Reproducible</strong>: same image, same runtime, fewer “works on my machine” issues.</li><li><strong>Portable</strong>: local dev, EC2, or CI — identical command.</li><li><strong>Automatable</strong>: compose brings one-line runs; S3 stores artifacts cleanly.</li></ul><p>**We<em> </em><strong><em>don’t</em></strong><em> cover the JMeter script (.jmx) itself. Assume you already have a valid plan.</em></p><p>In our QA infrastructure at iyzico, running JMeter directly on EC2 or local machines used to create inconsistency — plugin mismatches, Java versions, and path issues. Containerizing the environment solved all of these, and we could finally reproduce load runs identically across local, staging, and production-like setups.</p><h4>What We’ll Build (Architecture)</h4><ul><li>A <strong>single Docker image</strong> that contains JMeter CLI + (optional) plugins.</li><li>A <strong>compose service</strong> that runs the plan headlessly, generates <strong>HTML report</strong>, and <strong>uploads artifacts to S3</strong>.</li><li>An <strong>EC2 instance</strong> (Amazon Linux) with Docker/Compose installed; optional IAM role grants S3 PutObject.</li></ul><p><strong>Flow:</strong><br> <em>Local Environment(optional build) → EC2 (compose up) → JMeter runs → JTL + HTML report → ZIP → S3</em></p><p>This approach was driven by a need to simplify how we trigger performance tests in CI/CD. Instead of maintaining multiple AMIs or EC2 setup scripts, everything now lives in a single image definition. When a new feature needs stress testing, the team can launch a task definition directly — no manual setup, no SSH.</p><h4>Prerequisites</h4><ul><li>An <strong>AWS account</strong> and an <strong>S3 bucket</strong> (e.g., my-loadtests).</li><li><strong>I AM permissions</strong>:<br><strong>EC2 path</strong>: an <strong>Instance Profile</strong> (role) with s3:PutObject for your bucket/prefix.</li><li><strong>ECS path</strong>: a <strong>Task Role</strong> with s3:PutObject; an <strong>Execution Role</strong> for ECR pull &amp; logs.</li><li>(ECS only) An <strong>ECR repository</strong> to store your image; a <strong>VPC</strong> with subnets &amp; a security group.</li><li>Local <strong>Docker</strong> and <strong>docker compose</strong> (for build &amp; optional dry-run)</li></ul><h4>Project Layout</h4><pre>jmeter-aws/<br>  Dockerfile<br>  docker-compose.yml<br>  jmeter/<br>    test.jmx                  # your existing JMeter test plan (left as-is)<br>    user.properties           # optional JMeter overrides<br>    data.csv                  # optional data file<br>  results/                    # mounted by compose (JTL, logs, HTML here)</pre><h4>Dockerfile (containerizing JMeter)</h4><p>A minimal yet practical image (includes AWS CLI and popular plugins; remove plugins if you don’t need them).</p><pre># Dockerfile<br>FROM adoptopenjdk/openjdk11:x86_64-alpine-jre-11.0.18_10<br><br>USER root  <br><br># (Optional) Plugins Manager + cmdrunner, then install a couple of common plugins<br>RUN apk update &amp;&amp; \<br>    apk add --update --no-cache curl py-pip &amp;&amp; \<br>    apk add ca-certificates &amp;&amp; \<br>    update-ca-certificates &amp;&amp; \<br>    pip install --upgrade --user awscli<br><br># Set variables<br>ENV JMETER_HOME=/usr/share/apache-jmeter \<br>    JMETER_VERSION=5.6.2 \<br>    TEST_SCRIPT_FILE=test.jmx \<br>    TEST_RESULT_DIR=/var/jmeter \<br>    TEST_LOG_FILE=/var/jmeter/test.log \<br>    TEST_RESULTS_FILE=/var/jmeter/test-result.csv \<br>    TEST_REPORT_DIR=/var/jmeter/result/html-report \<br>    THREAD_GROUP_NAME=&quot;thread name&quot; \<br>    VARIABLE=&quot;value&quot;<br><br># Install Apache JMeter<br>RUN wget http://archive.apache.org/dist/jmeter/binaries/apache-jmeter-${JMETER_VERSION}.tgz &amp;&amp; \<br>    tar zxvf apache-jmeter-${JMETER_VERSION}.tgz &amp;&amp; \<br>    rm -f apache-jmeter-${JMETER_VERSION}.tgz &amp;&amp; \<br>    mv apache-jmeter-${JMETER_VERSION} ${JMETER_HOME}<br><br>ENV PATH=&quot;$JMETER_HOME/bin:$PATH&quot;<br><br># Copy jmeter files<br>ADD jmeter/script /var/jmeter/<br><br>WORKDIR /loadtest<br>COPY plan/ ./plan/<br>COPY run.sh ./run.sh<br>RUN chmod +x ./run.sh<br><br># - Start the JMeter script<br>CMD echo -n &gt; $TEST_LOG_FILE &amp;&amp; \<br>    echo -n &gt; $TEST_RESULTS_FILE &amp;&amp; \<br>    export PATH=~/.local/bin:$PATH &amp;&amp; \<br>    . ./usr/local/bin/set_submerchant_detail.sh &amp;&amp; \<br>    $JMETER_HOME/bin/jmeter -n \<br>    -t=/var/jmeter/$TEST_SCRIPT_FILE \<br>    -j=$TEST_LOG_FILE \<br>    -l=$TEST_RESULTS_FILE \<br>    -JthreadGroupName=$THREAD_GROUP_NAME \<br>    -JrandomVariableForTest=$VARIABLE &amp;&amp; \<br>    echo -e &quot;\n\n===== UPLOADING THE FILES TO AWS S3 =====\n\n&quot; &amp;&amp; \<br>    aws s3 cp $TEST_LOG_FILE s3://performance-test-logging/uploads/$THREAD_GROUP_NAME/ &amp;&amp; \<br>    aws s3 cp $TEST_RESULTS_FILE s3://performance-test-logging/uploads/$THREAD_GROUP_NAME/ &amp;&amp; \<br>    aws s3 cp $TEST_REPORT_DIR s3://performance-test-logging/uploads/$THREAD_GROUP_NAME/ --recursive &amp;&amp; \<br>    echo -e &quot;\n\n===== UPLOAD COMPLETED SUCCESSFULLY =====\n\n&quot;</pre><h4>Docker Build</h4><p>Build locally; optionally tag for ECR:</p><pre># local build<br>docker build -t jmeter-aws:latest .<br><br># optional multi-arch (if you need both amd64/arm64)<br># docker buildx create --use<br># docker buildx build --platform linux/amd64,linux/arm64 -t &lt;account&gt;.dkr.ecr.&lt;region&gt;.amazonaws.com/jmeter-aws:latest --push .<br><br># tag for ECR (single-arch path)<br>aws ecr get-login-password --region &lt;region&gt; | \<br>  docker login --username AWS --password-stdin &lt;account&gt;.dkr.ecr.&lt;region&gt;.amazonaws.com<br>docker tag jmeter-aws:latest &lt;account&gt;.dkr.ecr.&lt;region&gt;.amazonaws.com/jmeter-aws:latest<br>docker push &lt;account&gt;.dkr.ecr.&lt;region&gt;.amazonaws.com/jmeter-aws:latest</pre><h4>Docker-compose.yml (one-command runs)</h4><p>Environment variables let you tweak runs without touching the JMX.</p><pre># docker-compose.yml<br>version: &quot;3.8&quot;<br><br>services:<br>  jmeter:<br>    build: .<br>    image: docker-image:latest<br>    platform: linux/amd64<br>    environment:<br>      AWS_REGION: &quot;eu-central-1&quot;<br>      S3_BUCKET: &quot;my-loadtests&quot;                    # leave empty to skip upload<br>      S3_PREFIX: &quot;runs/${COMPOSE_PROJECT_NAME:-local}&quot;<br>      THREAD_GROUP_NAME: &quot;thread_group_name&quot;<br>      VARIABLE: &quot;value&quot;<br>    volumes:<br>      - ./results:/loadtest/results</pre><h4>Local Dry-Run (optional)</h4><pre>docker-compose -f &lt;docker-compose_filePath&gt; up &lt;service_name&gt;<br><br>#docker-compose -f docker-compose.yml up jmeter</pre><h4>Create Task Definition</h4><p>1- ECR: push your image (see DockerBuild).</p><p>2- I am Roles:</p><ul><li><strong>Task Role</strong>: allow <em>s3:PutObject </em>to<em> arn:aws:s3:::my-loadtests/*.</em></li><li><strong>Execution Role:</strong> standard <em>AmazonECSTaskExecutionRolePolicy </em>(pull from ECR, push logs)</li></ul><p>3- Task Definition (Linux): example container def (JSON gist):</p><pre>{<br>  &quot;family&quot;: &quot;jmeter-aws&quot;,<br>  &quot;networkMode&quot;: &quot;awsvpc&quot;,<br>  &quot;cpu&quot;: &quot;1024&quot;,<br>  &quot;memory&quot;: &quot;2048&quot;,<br>  &quot;executionRoleArn&quot;: &quot;arn:aws:iam::&lt;account&gt;:role/ecsTaskExecutionRole&quot;,<br>  &quot;taskRoleArn&quot;: &quot;arn:aws:iam::&lt;account&gt;:role/jmeterS3TaskRole&quot;,<br>  &quot;containerDefinitions&quot;: [<br>    {<br>      &quot;name&quot;: &quot;jmeter&quot;,<br>      &quot;image&quot;: &quot;&lt;account&gt;.dkr.ecr.&lt;region&gt;.amazonaws.com/jmeter-aws:latest&quot;,<br>      &quot;essential&quot;: true,<br>      &quot;environment&quot;: [<br>        { &quot;name&quot;: &quot;AWS_REGION&quot;, &quot;value&quot;: &quot;&lt;region&gt;&quot; },<br>        { &quot;name&quot;: &quot;S3_BUCKET&quot;, &quot;value&quot;: &quot;my-loadtests&quot; },<br>        { &quot;name&quot;: &quot;S3_PREFIX&quot;, &quot;value&quot;: &quot;runs/ecs&quot; },<br>        { &quot;name&quot;: &quot;THREAD_GROUP_NAME&quot;, &quot;value&quot;: &quot;threadh group name&quot; },<br>          ....        <br>        { &quot;name&quot;: &quot;EXTRA_JMETER_ARGS&quot;, &quot;value&quot;: &quot;-Jthreads=200 -Jduration=180 -Jjmeter.reportgenerator.overall_granularity=1000&quot; }<br>      ],<br>      &quot;logConfiguration&quot;: {<br>        &quot;logDriver&quot;: &quot;awslogs&quot;,<br>        &quot;options&quot;: {<br>          &quot;awslogs-group&quot;: &quot;/ecs/jmeter-aws&quot;,<br>          &quot;awslogs-region&quot;: &quot;&lt;region&gt;&quot;,<br>          &quot;awslogs-stream-prefix&quot;: &quot;jmeter&quot;<br>        }<br>      }<br>    }<br>  ]<br>}</pre><h4>Create Cluster and Run Task</h4><ol><li>Create an <strong>ECS cluster</strong></li><li><strong>Run Task</strong> (one-off):</li></ol><ul><li>VPC/Subnets: public (assign public IP) or private + NAT</li><li>Security group: egress allowed</li><li>Task overrides (optional): tweak <strong>EXTRA_JMETER_ARGS</strong> for this run</li></ul><p>3. Watch <strong>CloudWatch Logs</strong> stream for progress. The container exist when finished.</p><h4>Result S3</h4><p>Verify upload:</p><pre>aws s3 ls s3://my-loadtests/runs/ecs/<br># report-YYYYMMDD-HHMMSS.zip</pre><p>Download &amp; inspect:</p><pre>aws s3 cp s3://my-loadtests/runs/ecs/report-YYYYMMDD-HHMMSS.zip .<br>unzip report-YYYYMMDD-HHMMSS.zip<br># open report/index.html in your browser</pre><p><strong>That’s it.</strong> You now have a script-agnostic, containerized way to run JMeter locally, on EC2, or serverlessly via ECS — always shipping clean artifacts to S3.</p><p>In real practice, this setup has helped us reduce setup time by more than 70%. Every test run produces a clean HTML report in S3 with timestamped folders, which makes trend comparison effortless.</p><p>If you’re dealing with multiple environments or distributed teams, this containerized JMeter pattern is one of the most maintainable ways to keep load testing consistent and auditable.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*piBEWU-G6KnLVFH6aU_Hig.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f40de7852b5c" width="1" height="1" alt=""><hr><p><a href="https://iyzico.engineering/run-jmeter-load-tests-on-aws-with-docker-compose-f40de7852b5c">Run JMeter Load Tests on AWS with Docker &amp; Compose</a> was originally published in <a href="https://iyzico.engineering">iyzico.engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Integration Tests with Testcontainers]]></title>
            <link>https://iyzico.engineering/integration-tests-with-testcontainers-e7a1a827ead4?source=rss----c8b48d7c94e3---4</link>
            <guid isPermaLink="false">https://medium.com/p/e7a1a827ead4</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[testcontainer]]></category>
            <category><![CDATA[junit]]></category>
            <category><![CDATA[iyzico]]></category>
            <category><![CDATA[integration-testing]]></category>
            <dc:creator><![CDATA[Ömer Faruk Karadeniz]]></dc:creator>
            <pubDate>Wed, 24 Dec 2025 07:34:28 GMT</pubDate>
            <atom:updated>2025-12-24T07:34:23.653Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WR74N-oKHM2vbEMfZVCuAA.png" /></figure><p>To build reliable software, we need to ensure our code works correctly through testing. <strong>Unit testing</strong> checks the smallest individual pieces of code in isolation, such as a single function or statement. <strong>Integration testing</strong> ensures that these different pieces, and even <strong><em>external services like databases or message queues</em></strong> work together as expected.</p><p>With integration testing we can prevent critical failures in production by detecting and fixing bugs before deployment. Also, when refactoring code that spans multiple components, integration tests ensure that these changes don’t affect existing functionality.</p><h3><strong>What are Testcontainers? What do they solve and how do they work?</strong></h3><p>Testcontainers allows us to programmatically control and manage lightweight, disposable instances of common services (databases, message brokers, web browsers, or basically anything else that can run in a Docker container) <strong>directly from the test code</strong>. Testcontainers provide us an isolated test environment, each test suite gets its own containerized services, eliminating the “works on my machine” problem that often frustrates development teams :)</p><p>For example, instead of using in-memory databases like H2 that might behave differently from your production database, Testcontainers spin up actual database instances (PostgreSQL, MySQL, etc.) in Docker containers. This ensures your tests run against the same database engine you use in production, catching database-specific issues that mock databases might miss. With Testcontainers we can test against <strong>the exact same versions of databases, message queues, caches, and other services that we use in production</strong>.</p><p><strong>How It Works Under the Hood</strong></p><p>Testcontainers acts as a smart controller for Docker. When you run your tests, it follows a simple but powerful lifecycle:</p><ol><li><strong>Declaration:</strong> In your test code, you declare a requirement for a service by defining a @Container, like PostgreSQL.</li><li><strong>Image Check &amp; Pull:</strong> Testcontainers checks if the specified Docker image (e.g., postgres:latest) is available locally. If not, it automatically pulls it from Docker Hub.</li><li><strong>Container Start:</strong> It then starts a new container from that image, creating a fresh, isolated instance of your service.</li><li><strong>Dynamic Discovery:</strong> Once the container is running, Testcontainers inspects it to find the dynamically assigned port and any generated credentials. This is crucial for avoiding port conflicts on your machine.</li><li><strong>Property Injection:</strong> It injects these connection details (the JDBC URL, username, and password) into the Spring application context, allowing your application to connect to the new container.</li><li><strong>Automatic Cleanup:</strong> After your tests are complete, Testcontainers automatically stops and removes the container, ensuring a clean environment for the next run.</li></ol><h3><strong>How to set up Testcontainers for a Spring Boot project?</strong></h3><p>Assuming we have Docker installed on our machine, we will add Testcontainers dependencies to our maven pom.xml file.</p><pre>  &lt;dependency&gt;<br>     &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;<br>     &lt;artifactId&gt;junit-jupiter&lt;/artifactId&gt;<br>     &lt;scope&gt;test&lt;/scope&gt;<br>  &lt;/dependency&gt;<br>  &lt;dependency&gt;<br>     &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;<br>     &lt;artifactId&gt;postgresql&lt;/artifactId&gt;<br>     &lt;scope&gt;test&lt;/scope&gt;<br>  &lt;/dependency&gt;</pre><p>After adding these dependencies here is an example test code.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7dcc81bd3118bfcc8cab5901bc4bee04/href">https://medium.com/media/7dcc81bd3118bfcc8cab5901bc4bee04/href</a></iframe><p>Here is a breakdown of what is happening in the code:</p><ol><li><em>@Testcontainers</em> tells JUnit to manage Testcontainers lifecycles.</li><li>@Container marks a field as a container that Testcontainers should manage</li><li>We specify the Docker image postgres:17 and some default credentials (though Testcontainers can generate them).</li><li>The configureProperties method, annotated with <em>@DynamicPropertySource</em>, is the bridge. It takes the dynamic JDBC url, username, and password from the postgresContainer (once it’s running) and sets the corresponding spring.datasource.* properties. Spring Boot then uses these properties to configure its DataSource.</li><li><em>@SpringBootTest</em> loads your application context, now configured to talk to the Testcontainer!</li></ol><h3>When to Use Testcontainers</h3><p><strong>Perfect for:</strong></p><ul><li>Database-specific feature testing (stored procedures, custom types etc.)</li><li>Multi service integration scenarios</li><li>Testing database migrations and schema changes</li><li>Validating external service integrations</li><li>End-to-end workflow testing</li></ul><p><strong>Consider alternatives when:</strong></p><ul><li>You need extremely fast test execution (use unit tests)</li><li>Testing simple CRUD operations (in-memory databases might suffice)</li><li>Resource constraints prevent Docker usage</li></ul><h3>Conclusion</h3><p>In the past, integration testing was often difficult. Test environments could break easily, and developers might have to share a single database. This created many issues, especially the famous <em>“it works on my machine”</em> problem, where code works for one person but fails for others.</p><p>Testcontainers offers a practical and powerful solution to these old problems. It lets you control the services your application needs (like databases or message queues) directly from your test code. This gives you much better control and ensures that your tests are consistent and run the same way every time.</p><p>The result is a testing process that is more trustworthy and easier to manage for both individual developers and automated CI/CD pipelines. By using Testcontainers in your integration tests, you can be confident that your tests use the same kind of services as your production environment, which means you can trust your code to work correctly after deployment.</p><h4><strong>Resources:</strong></h4><ul><li><a href="https://github.com/FarukKaradeniz/integration-tests-practising">Full example repository</a></li><li><a href="https://www.testcontainers.org/">Testcontainers Official Documentation</a></li><li><a href="https://spring.io/guides/gs/testing-web/">Spring Boot Testing Best Practices</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e7a1a827ead4" width="1" height="1" alt=""><hr><p><a href="https://iyzico.engineering/integration-tests-with-testcontainers-e7a1a827ead4">Integration Tests with Testcontainers</a> was originally published in <a href="https://iyzico.engineering">iyzico.engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Strangler Fig Pattern in Practice: Migrating from Email Login to GSM Login in Spring Boot]]></title>
            <link>https://iyzico.engineering/the-strangler-fig-pattern-in-practice-migrating-from-email-login-to-gsm-login-in-spring-boot-d7bb6499fcac?source=rss----c8b48d7c94e3---4</link>
            <guid isPermaLink="false">https://medium.com/p/d7bb6499fcac</guid>
            <category><![CDATA[iyzico]]></category>
            <category><![CDATA[refactoring]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[design-patterns]]></category>
            <dc:creator><![CDATA[Ismail Coskun]]></dc:creator>
            <pubDate>Thu, 20 Nov 2025 10:55:09 GMT</pubDate>
            <atom:updated>2025-11-20T10:55:07.952Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wSyChwTufFUTf5rkVifLgw.png" /></figure><h3>What Is the Strangler Fig Pattern?</h3><p>The <strong>Strangler Fig Pattern</strong> is a strategy for <strong>gradually replacing legacy systems</strong> without a complete rewrite or big-bang release. <br>The idea comes from nature — specifically from how <strong>strangler fig trees </strong>grow around a host tree. <br>Over time, the fig replaces the host completely, but the process is <strong>slow, controlled, and safe</strong>.</p><p>In software, it means:<br>- You keep your legacy system running.<br>- You build <strong>new services or modules</strong> that handle small pieces of functionality.<br>- You <strong>route traffic</strong> to the new service while keeping old endpoints alive.<br>- Gradually, you “strangle” the legacy system until it’s no longer needed.</p><p>This approach is ideal when:<br>- The legacy system is too complex to rewrite from scratch.<br>- You need to avoid downtime.<br>- You want to release improvements incrementally.</p><h3>How It is Used</h3><p>In one of our internal projects, the login system was old — users could only log in with <strong>email + password</strong>. <br>The business team wanted to support <strong>GSM (phone number) login</strong> with OTP (one-time password).</p><p>At first, I considered refactoring the existing code. But the old system was deeply coupled; even a small change risked breaking authentication for thousands of users. <br>That’s when I decided to apply the <strong>Strangler Fig Pattern</strong> — build the new GSM login flow <strong>side by side</strong> with the old email flow, and gradually transition users.</p><h3>Step 1: The Legacy Login Flow</h3><p>Here’s how the old system worked:</p><pre>public class LegacyAuthService {<br>    public TokenResponse login(String email, String password) {<br>        User user = userRepository.findByEmail(email)<br>            .orElseThrow(() - &gt; new UnauthorizedException(&quot;User not found&quot;));<br>        if (!PasswordHasher.matches(password, user.getPasswordHash())) {<br>            throw new UnauthorizedException(&quot;Invalid password&quot;);<br>        }<br>        return tokenService.generateToken(user);<br>    }<br>}</pre><h3>Step 2: Introducing a Facade Layer</h3><p>To start migrating, I created an <strong>Auth Facade Controller</strong>.<br> It acted as a single entry point for all login requests — internally deciding whether to use the <strong>legacy service</strong> or the <strong>new one</strong>.</p><pre>@RestController<br>@RequestMapping(&quot;/api/auth&quot;)<br>@RequiredArgsConstructor<br>public class AuthFacadeController {<br>    private final LegacyAuthClient legacyAuthClient;<br>    private final NewAuthService newAuthService;<br>    private final FeatureToggleService featureToggle;<br>   <br>    @PostMapping(&quot;/login&quot;)<br>    public ResponseEntity &lt; TokenResponse &gt; login(@RequestBody LoginRequest request) {<br>        if (featureToggle.isEnabled(&quot;use-gsm-login&quot;) &amp;&amp; request.isPhoneLogin()) {<br>            return ResponseEntity.ok(newAuthService.loginWithPhone(request));<br>        }<br>        return ResponseEntity.ok(legacyAuthClient.loginWithEmail(request));<br>    }<br>}</pre><p>This way, the client application didn’t need to know about the change.<br> It always called /api/auth/login, and the backend decided which system to use.</p><h3>Step 3: Building the New GSM Login Service</h3><p>Then, I created a <strong>Spring Boot microservice</strong> for the new login logic — fully independent from the legacy monolith.</p><pre>@Service<br>@RequiredArgsConstructor<br>public class NewAuthService {<br><br>    private final UserRepository repository;<br>    private final OtpService otpService;<br>    private final TokenService tokenService;<br><br>    public TokenResponse loginWithPhone(LoginRequest request) {<br>        if (!otpService.verifyOtp(request.phone(), request.otpCode())) {<br>            throw new UnauthorizedException(&quot;Invalid OTP&quot;);<br>        }<br><br>        User user = repository.findByPhone(request.phone())<br>            .orElseThrow(() - &gt; new UnauthorizedException(&quot;User not found&quot;));<br><br>        return tokenService.generateToken(user);<br>    }<br>}</pre><h3>Step 4: Supporting Both Systems Together</h3><p>The facade decided which system handled the login based on the request:</p><pre>public class LoginRequest {<br>    private String email;<br>    private String password;<br>    private String phone;<br>    private String otpCode;<br><br>    public boolean isPhoneLogin() {<br>        return phone != null &amp;&amp; otpCode != null;<br>    }<br>}</pre><p>This hybrid approach allowed me to:</p><ul><li>Keep existing users logging in with email.</li><li>Let new users register and log in via phone number.</li><li>Measure how many users switched before fully migrating.</li></ul><h3>Step 5: Gradual Transition</h3><p>Once the GSM login became stable, we enabled it for more users:</p><ol><li>New signups required a phone number.</li><li>Email logins were still supported but marked for deprecation.</li><li>After 6 weeks, we flipped the feature toggle — all logins now went to the new service.</li></ol><p>Monitoring and metrics helped ensure no spikes in login failures occurred. Eventually, the old flow received zero traffic, and we could safely remove it.</p><h3>Step 6: Cleaning Up</h3><p>Finally, we removed:</p><ul><li>Old /login/email endpoints.</li><li>Password hashing utilities.</li><li>Legacy authentication configs.</li></ul><p>The codebase got smaller, cleaner, and easier to maintain.</p><h3>Visual Overview</h3><p>Here’s a simple flow diagram to show how traffic was routed during the migration:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*junkkkJ0vvWM2mbP2roGEg.png" /></figure><p>At first, most requests went to the legacy branch.<br>Over time, as users adopted phone login, traffic naturally shifted to the new branch.</p><h3>Key Takeaways</h3><ol><li><strong>Don’t rush a full rewrite.</strong> Replace piece by piece.</li><li><strong>Feature toggles are your safety net.</strong> Roll out slowly, roll back easily.</li><li><strong>Monitor usage.</strong> Data should tell you when it’s safe to remove old code.</li><li><strong>Keep user experience consistent.</strong> Clients shouldn’t feel the migration happening.</li></ol><h3>Final Thoughts</h3><p>Migrating authentication logic is always risky — it’s the gateway to your entire system.<br> But with the <strong>Strangler Fig Pattern</strong>, I managed to evolve it safely, without downtime or massive rewrites.</p><p>Sometimes, the best way to modernize software is not to “burn down the old tree,”<br>but to let the new one grow around it — quietly, steadily, and sustainably.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d7bb6499fcac" width="1" height="1" alt=""><hr><p><a href="https://iyzico.engineering/the-strangler-fig-pattern-in-practice-migrating-from-email-login-to-gsm-login-in-spring-boot-d7bb6499fcac">The Strangler Fig Pattern in Practice: Migrating from Email Login to GSM Login in Spring Boot</a> was originally published in <a href="https://iyzico.engineering">iyzico.engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Agentic Commerce Üzerine]]></title>
            <link>https://iyzico.engineering/agentic-commerce-%C3%BCzerine-1b9e8a731391?source=rss----c8b48d7c94e3---4</link>
            <guid isPermaLink="false">https://medium.com/p/1b9e8a731391</guid>
            <category><![CDATA[iyzico]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[ecommerce]]></category>
            <category><![CDATA[agentic-commerce]]></category>
            <dc:creator><![CDATA[Abdullah Başaran]]></dc:creator>
            <pubDate>Tue, 11 Nov 2025 09:14:14 GMT</pubDate>
            <atom:updated>2025-11-11T09:14:13.263Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lOzNJmsor6hhgYFzzIbp1g.png" /></figure><p>E‑ticaret hayatımıza girdiğinden beri alışverişin doğası köklü bir biçimde değişti. E-ticaretin mobil deneyimi, sosyal medya üzerinden E-ticaret ve yapay zeka tabanlı E-ticaret ile beraber kişiselleştirilmiş deneyim derken şimdi sırada Agentic Commerce var: yalnızca öneri yapan değil, kullanıcı adına eyleme geçen yapay zekâ sistemleri.</p><p>Agentic Commerce için yaygın bir Türkçe kullanım yok, ancak bu yazıda hem kavramın ne anlama geldiğini hem de nasıl çalıştığını teknik ve pratik yönleriyle anlamaya çalışacağız.</p><h3>Agentic Commerce Nedir?</h3><p>Kısaca: <strong>Yapay zeka agentlarının kullanıcı adına ticari işlem yapabilmesi</strong>.</p><p>Yapay zeka, kullanıcının doğal dilde ifade ettiği niyeti anlayarak; bunu yorumlar, gerekli API çağrılarını ve veri analizlerini zincirleme biçimde yürütür, ardından işlem motorları aracılığıyla ticari aksiyonları otomatik olarak gerçekleştirir.</p><p>Yani klasik öneri yapan yapay zekanın ötesinde, sistem artık sadece <em>öneri yapmakla</em> kalmayıp <em>eyleme de geçen</em> bir modele dönüşür. Onlarca sekme açmak, fiyat karşılaştırmak ve kullanıcı yorumlarına gömülmek, bilgilerini girip ödemeye ilerlemek yerine günümüzde sadece şunu demen yeterli;</p><p><em>“Eşim için sade ama kaliteli bir sevgililer günü hediyesi bul.”</em></p><h3><strong>Teknik Bakış ve Zorluklar</strong></h3><p>Bu model, kendine özgü bazı teknik ve operasyonel zorlukları da beraberinde getiriyor.</p><ul><li><strong>Ürün Kataloğu ve Stok</strong></li></ul><p>Üye işyerlerinin yapay zeka üzerinden satış yapabilmeleri için öncelikle ellerinde bulunan ürün id’sini, ürün başlığını, ürün açıklamalarını, fiyat ve stok bilgilerini, görsellerini ve kargo seçeneklerini uygun formatlarda (TSV, CSV, XML, JSON gibi) ve düzenli bir şekilde güncelleyerek platform sağlayıcısına iletmeleri gerekmektedir. Yapılacak satışın sıklığına göre bu bilgilerin sık sık (genellikle 15 dakikada bir) güncellenmesi gerekecektir.</p><ul><li><strong>Kargo</strong></li></ul><p>Üye işyerlerinin seçtiği kargo seçeneğinin ve bu kargo seçeneğine göre çıkacak fiyatı yapay zeka tarafına iletmesi bir sonraki adım olan ödeme adımı için önemli bir veri olacaktır. Buradaki en büyük zorluk, seçilecek adres bilgisine göre dinamik olarak değişen yapıda bir kargo ücretinin varlığıyla alakalıdır.</p><p>Aynı zamanda, çift yönlü API iletişimiyle üye işyeri kargo sürecini otomatik olarak başlatmalıdır. Aksi halde ödeme alınmış, siparişe dönüşmüş ancak kargo süreci başlamamış olacaktır.</p><ul><li><strong>Ödeme</strong></li></ul><p>Üye işyerleri yapay zeka tarafında hali hazırda ön tanımlı olan ödeme sağlayıcılar üzerinden işlem gerçekleştirebilir. Burada “shared payment token” kavramı ortaya çıkar. Çünkü bir yapay zeka agentının üye işyeri adına finansal bir işlem gerçekleştirebilmesi izne bağlı olmalıdır. Çünkü teorik olarak finansal işlem doğrudan üye işyeri tarafından başlatılmaz. Üye işyeri bazı kimlik bilgilerini (API key gibi) yapay zeka ile paylaşmalı ve yapay zekayı finansal işlem yapabilmek üzerine yetkilendirmelidir.</p><p>Eğer ön tanımlı bir ödeme sağlayıcı yoksa, yapay zeka ile bir entegrasyon yapılmalı, güvenli ve gerçek zamanlı, iki taraflı API iletişim ortamı kurulmalıdır.</p><ul><li><strong>İade</strong></li></ul><p>İade akışının nereden başlayıp nereden bittiği de teknik bir tartışma konusudur. Sipariş yapay zekadan başlar ancak E-ticaret firmasında son bulur. Ancak teorik olarak iade her iki kanaldan da tetiklenebilir. Bu sebeple, iade başlatıldığında hangi kanaldan başlatılırsa başlatılsın API seviyesindeki iletişim ile beraber veri eşleşmesini kritik bir konu haline getirecektir.</p><p>Ayrıca yine iadenin kendisinin hangi ödemeye ait iade olduğu da yine bir veri eşleşmesi konusu olacaktır.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*mpQshjiefXmr1dzg" /><figcaption>OpenAI tarafından paylaşılan Agentic Commerce Akış Diyagramı (<a href="https://openai.com/tr-TR/index/buy-it-in-chatgpt/">kaynak</a>)</figcaption></figure><h3>Global Örnekler</h3><p>Bu alanda öncü adımlar hızla atılıyor:</p><ul><li><strong>OpenAI x Shopify(<em>Mayıs’25</em>) &amp; Etsy(<em>Eylül’25</em>)</strong><br>ChatGPT kullanıcıları artık sohbet içinde bir ürün aramak istediğinde doğrudan Shopify ya da Etsy mağazalarındaki ürünleri görebiliyor. Arka planda, OpenAI API’si Shopify’ın kataloglarını sorgulayıp sonuçları doğal dilde sunuyor ve ardından ödeme formuna yönlendirip istenilen ödeme yöntemiyle siparişi tamamlıyor.</li><li><strong>Gemini x PayPal (<em>Eylül’25</em>)</strong><br>Gemini kullanıcıları artık sohbet içinde bir ürün arayıp satın almak istediğinde, eğer ürünün satıldığı e-ticaret sitesinde PayPal geçerliyse, bu ürünü doğrudan satın alabiliyor.</li></ul><h3>Geleceğe Dair Sorular</h3><p>Agentic commerce sadece ticareti değil, “karar verme” kavramını da dönüştürüyor. Peki gelecekte ne olabilir soruları ise aklımızda şimdiden yer edinmeye başladı…</p><ul><li>Bireysel yapay zeka asistanları müşteriler için yine yapay zeka agentları üzerinden alışveriş yapabilecek mi?</li><li>Markalar insanlara mı, yoksa onların dijital temsilcilerine mi satış yapacak?</li><li>“Tüketici” kim olacak — biz mi, yoksa bizim adımıza karar veren yazılım mı?</li><li>Nesnelerin yapay zeka asistanları olabilir mi ve o nesneler, yapay zeka yardımı ile sipariş verebilir mi?</li></ul><h3>Henüz Erken, Ama Yön Belli</h3><p>Bugün agentic commerce hala erken aşamada. Ancak yapay zekaların bizim adımıza <em>düşünmeye</em>, <em>seçmeye</em> ve <em>hareket etmeye</em> başlaması sadece ticareti değil; pazarlamayı, tasarımı, güven sistemlerini, veri gizliliğini ve etiği de kökten dönüştürecek.</p><p>E-ticaret devriminin aksine, bu kez dönüşümün merkezinde “<em>websitesi</em>” değil, “<em>etkileşim</em>” var ve belki de çok yakında, en iyi satış temsilciniz bir insan değil, sizinle yıllardır konuşan kişisel yapay zeka ajanınız olacak.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b9e8a731391" width="1" height="1" alt=""><hr><p><a href="https://iyzico.engineering/agentic-commerce-%C3%BCzerine-1b9e8a731391">Agentic Commerce Üzerine</a> was originally published in <a href="https://iyzico.engineering">iyzico.engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spring Boot, JPA & Hibernate: The Power of the Trio]]></title>
            <link>https://iyzico.engineering/spring-boot-jpa-hibernate-the-power-of-the-trio-085ea7bd81bb?source=rss----c8b48d7c94e3---4</link>
            <guid isPermaLink="false">https://medium.com/p/085ea7bd81bb</guid>
            <category><![CDATA[iyzico]]></category>
            <category><![CDATA[jpa]]></category>
            <category><![CDATA[spring-data]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[Mustafa Eren DEMİR]]></dc:creator>
            <pubDate>Mon, 13 Oct 2025 08:22:23 GMT</pubDate>
            <atom:updated>2025-10-13T08:22:18.720Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iZ6RJ53g7R63M7PL_cq6sw.png" /><figcaption>Spring Boot, JPA &amp; Hibernate: The Power of the Trio</figcaption></figure><p>Hello! When discussing technologies that simplify database operations in the software world, there’s a trio we often hear about: Spring Boot, JPA, and Hibernate. The combination of these three is the key to simplifying and accelerating database interactions in modern Java applications. Let’s dive into each part of this powerful combination, explore why they are so effective, and understand how they work in more detail.</p><h3>What is JPA ?</h3><p>The Java Persistence API (JPA) defines a standard for managing persistence between objects and relational databases in Java applications. JPA itself is not a tool or framework; rather, it is a specification consisting of a set of interfaces and rules. In essence, JPA says, “ I tell you what to do not how to do it.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fyRnEqL0FEBoAD9RUDLClQ.png" /><figcaption>Java Persistence API</figcaption></figure><h3>The Key Features and Purpose of JPA</h3><h4>Standardization and Portability</h4><p>One of JPA’s biggest advantages is ensuring that different ORM implementations (such as Hibernate, EclipseLink, or OpenJPA) adhere to a standard. This means that if you ever want to switch your persistence layer to a different JPA provider, you only need to make minimal changes to your code. This adds portability to your application.</p><h4>Entity Concept</h4><p>The main structure of JPA is the representation of database tables as Java classes. By marking a Java class with the @Entity annotation, it becomes a JPA entity.</p><h4>Mapping with Annotations and XML</h4><p>JPA provides a set of standard annotations (e.g., @Table, @Column, @Id, @GeneratedValue, @OneToMany, @ManyToOne) to define how the fields of your entity classes map to the columns of database tables. Alternatively, you can also define these mappings in an XML file like orm.xml. However, annotations are much more common and practical in modern development.</p><h4>EntityManager Interface</h4><p>The EntityManager interface is the central API of JPA, used to interact with the database. It includes methods for basic CRUD operations and querying, such as persist() (save), find() (find by ID), merge() (update), remove() (delete), and createQuery() (create queries). The EntityManager instance is associated with a &quot;persistence context,&quot; which tracks the entities being managed at any given time.</p><h4>JPQL (Java Persistence Query Language)</h4><p>JPQL is an object-oriented query language similar to SQL but operates on entity classes and fields instead of database tables and columns. It allows you to write database-independent queries. Example:SELECT s FROM Student s WHERE s.lastName = &#39;Smith&#39;.</p><h3>What is Hibernate?</h3><p>Hibernate is the most popular and mature ORM (Object-Relational Mapping) framework that implements the JPA specification. It means Hibernate is the concrete tool that follows the rules and interfaces defined by JPA to perform actual database operations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NbERNcpwAYYxKRqXDVHeZg.png" /><figcaption>Hibernate</figcaption></figure><h3>What Hibernate Offers and Its Working Principles:</h3><h4>JPA Implementation and Beyond</h4><p>Hibernate fully supports JPA standards and even provides additional features and optimizations that JPA does not cover. For instance, its custom <strong>Session API</strong> (similar to JPA’s EntityManager) offers more detailed control and performance configurations.</p><h4>SQL Generation and Data Transformation</h4><p>Hibernate’s core task is to automatically convert the operations you perform on Java objects (e.g., save, update) into appropriate SQL queries for the underlying database and map the query results back into Java objects. This eliminates the need for developers to write SQL directly.</p><h4>Database Dialects</h4><p>Hibernate uses “dialect” classes to manage differences in SQL syntax and data types across various databases. This allows Hibernate to generate database-specific SQL queries from your Java entities.</p><p>Below are examples of a simple User Java entity class with only an id field, and the corresponding CREATE TABLE queries generated using org.hibernate.dialect.PostgreSQLDialect and org.hibernate.dialect.OracleDialect.</p><p>This class represents the users table in the database.</p><pre>@Entity<br>@Table(name = &quot;users&quot;)<br>public class User {</pre><pre>    @Id<br>    @GeneratedValue(strategy = GenerationType.IDENTITY)<br>    @Column(name = &quot;id&quot;)<br>    private Long id;<br>}</pre><ul><li><strong>PostgreSQL Dialect</strong>: PostgreSQL usesBIGINT data type and theIDENTITY column property for auto-incrementing id fields.</li></ul><pre>CREATE TABLE users (<br>     id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,<br>     PRIMARY KEY (id)<br> );</pre><ul><li><strong>Oracle Dialect:</strong> Oracle uses the NUMBER data type and the IDENTITY column property for auto-incrementing id fields.</li></ul><pre>CREATE TABLE users (<br>    id NUMBER(19,0) GENERATED BY DEFAULT AS IDENTITY NOT NULL,<br>    PRIMARY KEY (id)<br>);</pre><p>These examples demonstrate how the same simple Java entity is translated into different SQL CREATE TABLE statements through different database dialects.</p><h4>Database Schema Management (DDL Generation)</h4><p>Using the hibernate.hbm2ddl.auto property, Hibernate can automatically create, update, or validate the database schema at application startup (values: create, create-drop, update, validate). This is particularly useful in development and testing environments. These settings <strong>should not be used in a live (production) environment</strong>. create, create-drop, and even update modes pose significant risks for production:</p><ul><li><strong>create</strong>: This option <strong>deletes and recreates your entire database</strong> every time the application starts. In production, this means <strong>irreversible data loss</strong>. All your critical data simply vanishes.</li><li><strong>create-drop</strong>: Like create, it creates the database at startup, but also <strong>deletes it when the application stops</strong>. If accidentally used in production, it carries the same <strong>catastrophic data loss risk</strong> as create.</li><li><strong>update</strong>: This mode attempts to update the existing schema. While seemingly less destructive, it can lead to <strong>unexpected data loss</strong> (e.g., when dropping columns or changing types) or <strong>compatibility issues</strong>. Losing control over your production schema sets the stage for major problems.</li><li><strong>validate</strong>: This mode simply <strong>validates your Hibernate configuration against the existing schema without making any changes</strong>. It&#39;s generally <strong>safe for production</strong>, but it should still be part of a broader, controlled schema management process.</li></ul><p>Relying on hibernate.hbm2ddl.auto in production results in a <strong>critical loss of control</strong> over your database schema. Production schema changes require careful planning, versioning, and rigorous testing. Automated operations <strong>completely bypass this controlled workflow</strong>, inviting unforeseen errors and system downtime. Automatically generated issues are far harder to fix than those introduced through a managed process.</p><h4>Caching</h4><ul><li><strong>First-Level Cache:</strong><br>A default, session-specific cache. If you read the same entity multiple times in a single session, the database is queried only once, and subsequent reads are served from the cache.</li><li><strong>Second-Level Cache:</strong><br>An optional cache operating at the SessionFactory level, shared across multiple sessions. It is used to improve the performance of frequently accessed data and requires external providers like Ehcache or Redis.</li></ul><h4>Transaction Management</h4><p>Ensures the integrity of database operations. All operations within a transaction either succeed as a whole or fail entirely. Hibernate supports both JTA (Java Transaction API) and native (JDBC) transaction management.</p><h3>What is Spring Boot?</h3><p>Spring Boot is a framework built on top of the Spring Framework, designed to make it easy to create standalone, production-ready, Spring-based applications quickly. It operates on the principle of “convention over configuration,” allowing developers to focus on business logic instead of writing boilerplate code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vX40SpiI9iaS-W2QNNIDZw.png" /><figcaption>Spring Boot, JPA, Hibernate</figcaption></figure><h4>How Spring Boot Does It Combine the Trio?</h4><h4>Automatic Configuration</h4><p>The biggest advantage of Spring Boot is its automatic configuration. When you add the spring-boot-starter-data-jpa dependency to your project, Spring Boot automatically configures several components for you:</p><ul><li><strong>A DataSource:</strong> Automatically configures the database connection pool (e.g., HikariCP) using the spring.datasource settings in your application.yml.</li><li><strong>An EntityManagerFactory:</strong> Creates the main factory for JPA and sets Hibernate as the default provider.</li><li><strong>A PlatformTransactionManager:</strong> Configures the necessary component to manage database transactions.</li><li>Makes it easy to manage Hibernate settings (e.g., hibernate.dialect, ddl-auto, show_sql, etc.) through application.yml or application.properties.</li></ul><h4>Spring Data JPA</h4><p>Spring Data JPA is the key component that makes working with Hibernate and JPA incredibly simple in Spring Boot. It is an abstraction layer built on top of JPA. Developers can define repository interfaces to perform common database operations (CRUD) with minimal or no code. Spring Data JPA automatically generates queries based on method names.</p><pre>import org.springframework.data.jpa.repository.JpaRepository;<br>import java.util.List;</pre><pre>// We create a JpaRepository with the Student entity and ID type Long<br>public interface StudentRepository extends JpaRepository&lt;Student, Long&gt; {<br>    //Spring Data JPA automatically generates the query &quot;SELECT s FROM Student s WHERE s.lastName = :lastName&quot; based on this method signature.<br>    List&lt;Student&gt; findByLastName(String lastName);</pre><pre>    // We can also define custom queries<br>    @Query(&quot;SELECT s FROM Student s WHERE s.firstName LIKE %:name%&quot;)<br>    List&lt;Student&gt; findStudentsByFirstNameContains(@Param(&quot;name&quot;) String name);<br>}</pre><p>With this setup, you get basic CRUD methods like save(), findById(), findAll(), and delete() by simply extending the StudentRepository interface.</p><h3>Why Is This Trio So Powerful?</h3><p>The combination of these three technologies makes database operations in modern Java applications highly efficient, fast, and enjoyable:</p><ul><li><strong>JPA Defines the Standard:</strong> It provides developers with flexibility and portability through a well-defined contract.</li><li><strong>Hibernate Implements the Standard:</strong> It brings JPA’s rules to life in a robust and optimized way, abstracting the complexity of database interactions. Hibernate also handles details like performance optimization, caching, and relationship management.</li><li><strong>Spring Boot Simplifies Everything:</strong> It makes the configuration and usage of JPA and Hibernate extremely simple.</li></ul><p>This synergy has become the gold standard for developing database-driven applications in the Java ecosystem. Developers can focus on business logic instead of database details, enabling faster time-to-market for their applications.</p><p>Hopefully, this detailed explanation helps you better understand the relationship between Spring Boot, JPA, and Hibernate, and how each contributes to the ecosystem!</p><p>Feel free to explore the full source code for the application discussed in this article, or try it out yourself, by visiting the <a href="https://github.com/merendemir/spring-boot-jpa-hibernate-examples"><strong><em>GitHub repository</em></strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=085ea7bd81bb" width="1" height="1" alt=""><hr><p><a href="https://iyzico.engineering/spring-boot-jpa-hibernate-the-power-of-the-trio-085ea7bd81bb">Spring Boot, JPA &amp; Hibernate: The Power of the Trio</a> was originally published in <a href="https://iyzico.engineering">iyzico.engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[API Optimization is Not a Dark Art: Demystifying Performance]]></title>
            <link>https://iyzico.engineering/api-optimization-is-not-a-dark-art-demystifying-performance-4a2f99634599?source=rss----c8b48d7c94e3---4</link>
            <guid isPermaLink="false">https://medium.com/p/4a2f99634599</guid>
            <category><![CDATA[software-design]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[api-optimization]]></category>
            <category><![CDATA[iyzico]]></category>
            <dc:creator><![CDATA[Yunus Alkan]]></dc:creator>
            <pubDate>Thu, 02 Oct 2025 06:24:08 GMT</pubDate>
            <atom:updated>2025-10-02T06:24:04.607Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FD3gtmjBBcUrP4KPx9ZJqw.png" /></figure><p>In today’s fast-paced, ever-changing world of technology, one of the most critical factors for companies to achieve their goals, increase efficiency, and hit financial targets is a strong and efficient technological infrastructure. One of the most important components of this infrastructure is Application Programming Interfaces (APIs), which allow us to expose our applications to the outside world or allow end-users to interact with front-end applications.</p><p>To provide uninterrupted service to our users, whose numbers are increasing day by day, a well-designed API that is efficient, fast, reliable, and scalable is of vital importance for the success of our company. The performance of APIs not only affects customer satisfaction, but also directly affects factors such as the efficiency of business operations and the budget allocated to technology investments.</p><h3>What is API Optimization ?</h3><p>In a very general sense, API optimization involves various steps aimed at increasing the performance and efficiency of APIs. Examples of these steps include reducing service response times, minimizing latency between requests, and optimizing database queries.</p><p>In this article, I’ll cover this practical techniques and reliable methods to optimize your API for better performance and user experience.</p><h4><strong>1-) Defining suitable names and using appropriate HTTP Methods</strong></h4><p>API optimization is not only about performance, but also about readability, sustainability, and correct resource modeling. In this context, the use of appropriate HTTP methods and endpoint naming are the cornerstones of good API design. Here are a few key points:</p><ul><li>Nouns should be used instead of verbs in endpoint naming. /users, /products, /orders etc.</li><li>A hierarchical relationship should be established between related resources, as in the following example: /users/123/orders</li><li>The GET method should never be used to create or modify data. It should only be used to retrieve data, and the endpoint should be idempotent.</li><li>For creating a new resource, use the POST method; for updating all fields of an existing resource, use the PUT method; and for partially updating data, use the PATCH method.</li></ul><h4>2-) Prevention of Abuse</h4><p>DDoS is one of the most well-known examples of abuse. A malicious attacker can send thousands of API requests in a very short time, rapidly exhausting your application server’s thread pools, driving CPU and memory usage to critical levels, and eventually causing the server to become completely unresponsive.</p><p>However, similar consequences may also arise from entirely innocent behavior — for instance, when a legitimate customer unintentionally triggers a high volume of simultaneous requests to some of your services.</p><p>To mitigate the effects of such high-intensity usage patterns, a well-designed rate limiter implementation can be considered. By enforcing request limits — especially on services that handle complex business logic or intensive computations — you can prevent server overload and maintain system availability.</p><h4>3-) Limiting Response</h4><p>Limiting the contents of API response bodies and returning only the necessary data is considered a best practice. This approach not only minimizes the risk of data breaches caused by exposing unnecessary information but also reduces the amount of data that services need to process, resulting in improved performance.</p><p>However, there are exceptional cases where returning large payloads through APIs becomes inevitable. For instance, in an e-commerce application’s admin panel, sellers may want to view detailed reports of their sales for the last quarter of the year. Serving the entire dataset in a single response can severely degrade the performance of the service due to the size of the data.</p><p>In such cases, one of the most effective solutions is to paginate the data — delivering it to the user in chunks. The client then sends the same request multiple times to retrieve each subsequent block of data. Another alternative is to send the full dataset in a compressed format, such as a GZIP archive, rather than in raw form. This significantly reduces the size of the data being transferred and lowers bandwidth usage across the network. In the table below, you can observe the comparative differences between the three most commonly used algorithms.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*STqr8LzmYLK7gL6lS9lFmQ.png" /><figcaption>Comparison of Common Compression Algorithm</figcaption></figure><h4>4-) Caching</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2UnNDYX19EMcJ9E2UsGUWw.png" /><figcaption>A Simple Cache Flow</figcaption></figure><p>Caching is one of the most important techniques for improving API performance. In essence, it is based on the principle of storing frequently accessed data or computation results in memory for a specific key value, allowing it to be retrieved or computed quickly without the need for re-accessing or re-computing. Thanks to caching:</p><ul><li>Backend load can be minimized.</li><li>User satisfaction is increased by reducing delays in service calls.</li><li>System resources such as memory and network bandwidth are consumed less, contributing to system reliability.</li></ul><p>I’d like to talk about two concepts that you may frequently encounter in caching:</p><p><strong><em>Cache Hits: </em></strong>If the requested data is found in the cache, it is referred to as a cache hit.</p><p><strong><em>Cache Miss: </em></strong>If the data is not found in the cache, it is termed a cache miss. In this case, as you can see in the flow diagram above, the backend server is accessed to find the desired data, and the result is added to the cache.</p><p>Caching can be widely used in various components of large-scale applications. Let’s take a look at the types of caching…</p><p><strong>1.In-Memory Caching</strong></p><p>This type of caching is also known as server-side caching. It is based on the fundamental principle of storing data in memory, rather than on a hard disk or a database, and accessing it from there. Since the speed of reading data from memory is much higher than from a hard disk, the performance is improved by minimizing the latency time. Products such as Redis, Memcache, and Amazon ElastiCache are very common caching implementations.</p><p>When you want to create a cache, there are a few things to keep in mind:</p><p><strong><em>TTL Duration(Time To Live): </em></strong>This determines how long data will remain in the cache. If your data is not updated very often, you can set the TTL period to be long. However, it is important to set the TTL period to be short for data that is updated frequently. Otherwise, users may continue to see outdated data. Setting the TTL correctly according to the data’s need for updating is one of the most critical issues.</p><p><strong><em>Eviction Policy: </em></strong>When data is stored in cache, it is typically not stored in memory indefinitely. Since memory capacity is limited, data stored here should be purged according to policies set to maintain a balance of memory load. The most commonly used policies are as follows:</p><ul><li><strong><em>LRU (Least Recently Used): </em></strong>Removes the data that was used the longest time ago from the cache.</li><li><strong><em>LFU (Least Frequently Used): </em></strong>Removes the least frequently used data from the cache.</li><li><strong><em>FIFO (First-In, First-Out): </em></strong>Removes the data that was first entered into the cache</li></ul><p><strong><em>Key Design: </em></strong>It is particularly important to assign unique key values in caching implementations where data is stored in key-value pairs. Otherwise, there is a high probability that you will experience conflicts and data inconsistencies. An example key value will be <strong><em>customer:123456:order:1</em></strong></p><p><strong>2. Database Optimization</strong></p><p>Effective database optimization plays a critical role in improving API performance. These optimizations impact not only the queries used to retrieve data but also the communication between the database and the API. Indexes are highly beneficial for accelerating data access within tables. In particular, creating single or composite indexes on columns that are frequently used in the <strong>WHERE</strong> clause is essential. However, using indexes also introduces overhead. Since each newly inserted record requires the index to be updated, this comes at a cost in terms of both storage size and CPU usage. Therefore, for tables with high insert rates, index design should be carefully planned. Over time, as the number of indexed columns increases, insert operations may experience a significant degradation in performance.</p><p>When an API accesses the database, it must first establish a connection — a process that carries its own cost. If incoming API requests increase rapidly, opening new database connections for each concurrent request can place a heavy load on the database. To mitigate this, a connection pool should be utilized. A predefined and limited number of connections are kept alive in the pool and returned to it once the task is completed. This eliminates the overhead of creating a new connection for each request. Properly configuring the connection pool — including the maximum number of connections and the idle timeout — is crucial. If the number of available connections is insufficient, threads may end up waiting for a connection to become available, potentially leading to application downtime.</p><p>The number of records returned by a database query should always be limited using pagination. Retrieving millions of records in a single request is highly risky — both for the database and the API layer. Additionally, selecting all columns using a wildcard (SELECT *) when it is not necessary leads to excessive memory usage and unnecessary network bandwidth consumption. Only the required fields should be explicitly included in the query results.</p><p>In high-write workloads, read performance may degrade over time. A common and effective solution to this problem is to separate read and write operations across different databases. While write operations are handled by the master database, the data is replicated — with a slight delay — to one or more read-only replicas. This ensures that read operations are isolated from write-intensive processes. However, the main drawback of this architecture is the potential for eventual consistency issues due to replication delays, especially for critical data.</p><p><strong>3. HTTP Caching</strong></p><p>The goal of this type of caching is to store HTML page content (such as images, URLs, CSS and JavaScript files) in the user’s browser on the first request to a web site, and then retrieve this content from the local cache on subsequent requests. In this way, both faster response time and increased user satisfaction, as well as a decrease in server bandwidth usage, can be achieved. Caching is not limited to the browser memory alone. An additional server called a Proxy Cache is located between the client and the server and works like CDN. In this case, the cached data is not only specific to the user, but can also be opened to all public users.</p><p>The HTTP Caching process is accomplished through directives in the request or response headers. Let’s take a look a simple response header:</p><pre>HTTP/1.1 200 OK<br>Content-Type: application/json<br>Cache-Control: public, max-age=3600, must-revalidate<br>ETag: &quot;a7c3d-5e1f-abc123&quot;<br>Last-Modified: Wed, 07 Aug 2025 18:30:00 GMT<br>Expires: Wed, 07 Aug 2025 19:30:00 GMT</pre><p><strong>Cache-Control:</strong> In this section, we indicate whether the cached data is private or open to everyone, for how many seconds it will be valid, and whether it should be revalidated from the server after the time expires.</p><p><strong>ETag: </strong>The name of the header is derived from the combination of words ‘entity’ and ‘tag’. It represents a unique value for the resource to be cached by the backend server. If this resource value changes at any time in the future, it indicates that the original resource has changed and the cache information needs to be flushed and refilled.</p><p><strong>Expires: </strong>As the name suggests, this header information also specifies when the cache information will expire.</p><p><strong>4-)</strong> <strong>Using Asynchronous Processing</strong></p><p>One of the best practices is to complete processes that do not need to be completed in a very short time asynchronously. For example, since it is not necessary to send a post-order notification email immediately in an e-commerce application, it can be implemented asynchronously using message queue systems such as Kafka and RabbitMQ. For a solid example of asynchronous processing implementation, consider reviewing this <a href="https://iyzico.engineering/outbox-pattern-best-practices-for-reliable-messaging-in-distributed-systems-923201f03fd5">article</a>. The advantages of asynchronous processes can be listed as follows:</p><ul><li>By moving time-intensive operations to an asynchronous structure, the services that perform the main task will work more efficiently.</li><li>Since email delivery is performed asynchronously, various delays or sudden interruptions in the email delivery service will not affect the order creation process.</li><li>The greatest advantage of using message queues is that, in the event of an error during the operation, retry mechanisms allow for repeated attempts at specific intervals without data loss.</li></ul><p>While asynchronous processing offers significant advantages, it also comes with challenges that, if not properly managed, can cause more harm than benefit. A few key considerations include:</p><ul><li><strong>Difficulty in Error Handling</strong>: Due to the non-linear flow of code, handling errors within deeply nested asynchronous calls can become increasingly complex.</li><li><strong>Decreased Code Readability</strong>: As the number of nested asynchronous methods that call each other increases, code readability and maintainability can suffer significantly. This situation is commonly referred to as “Callback Hell” or the “Pyramid of Doom.”</li><li><strong>Resource Management</strong>: Although asynchronous processes may seem to run quietly in the background, they can significantly increase memory usage — especially when multiple asynchronous operations are initiated simultaneously — which may negatively impact the performance of the main thread. Additionally, concurrent asynchronous processes accessing the same resource (such as a database record with the same ID) can lead to race conditions and result in data inconsistencies.</li><li><strong>Debugging and Testing Challenges</strong>: The non-sequential nature of asynchronous code makes detecting and fixing bugs more difficult compared to synchronous implementations. Similarly, crafting and executing reliable test scenarios in such environments requires extra care and attention.</li><li><strong>Thread Pool Management: </strong>When implementing asynchronous calls using custom-created threads, it is often more efficient to utilize a customized thread pool mechanism. Creating a new thread for each individual process is highly resource-intensive. Instead, pre-initialized threads can be reused through pooling, thereby avoiding the overhead of thread creation and enabling more efficient utilization of system resources such as memory and CPU. However, thread pool configuration must be handled with great care. If the pool is not properly sized, issues such as thread starvation, resource contention, or even memory overflows (e.g., OutOfMemory errors) may occur. Therefore, parameters like core pool size, maximum pool size, queue capacity, and thread keep-alive time should be carefully tuned based on whether the workload is CPU-bound or I/O-bound.</li></ul><p><strong>5. Content Delivery Networks (CDNs)</strong></p><p>It can be very costly for application servers to prepare and serve static content, such as URLs, HTML content, and images, for each request from an API request. This is especially true when the size of the visual content is large, in which case the bandwidth consumed by the application will increase dramatically. To address this issue, CDNs (Content Delivery Networks) have been developed.</p><p>CDNs, which operate with a distributed server cluster architecture, include a caching implementation that purges the stored data at regular intervals. When we return the addresses of large volumes of data stored on the CDN in service responses, this content will be delivered to the user making the request from the CDN server closest to them. This improves latency and ensures efficient use of our application server’s memory and network bandwidth.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*C5IcQiH9C9Hq242dfOJ-3Q.png" /><figcaption>A Typical CDN Cluster</figcaption></figure><h4>Conclusion</h4><p>Optimizing APIs is not merely a matter of improving speed — it is about ensuring reliability, scalability, and a seamless experience for both users and systems. From adopting proper endpoint naming conventions and safeguarding services against abuse, to limiting response payloads, implementing effective caching strategies, and leveraging asynchronous processing, each technique plays a vital role in building resilient APIs.</p><p>By approaching API optimization as an ongoing process rather than a one-time effort, organizations can reduce operational costs, enhance customer satisfaction, and create a robust technological foundation that grows with their business needs. In a world where milliseconds matter, well-optimized APIs are no longer a competitive advantage — they are a necessity.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4a2f99634599" width="1" height="1" alt=""><hr><p><a href="https://iyzico.engineering/api-optimization-is-not-a-dark-art-demystifying-performance-4a2f99634599">API Optimization is Not a Dark Art: Demystifying Performance</a> was originally published in <a href="https://iyzico.engineering">iyzico.engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Maven Lifecycle and Advanced Usage Parameters]]></title>
            <link>https://iyzico.engineering/maven-lifecycle-and-advanced-usage-parameters-5a841640e8f8?source=rss----c8b48d7c94e3---4</link>
            <guid isPermaLink="false">https://medium.com/p/5a841640e8f8</guid>
            <category><![CDATA[iyzico]]></category>
            <category><![CDATA[maven]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[build-tool]]></category>
            <dc:creator><![CDATA[Süleyman Şahin]]></dc:creator>
            <pubDate>Thu, 21 Aug 2025 07:37:32 GMT</pubDate>
            <atom:updated>2025-08-21T07:37:32.661Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f1ZNDEem2N3eztlmZuspNQ.jpeg" /></figure><p>Developing software processes has come to value abstraction and classification. This has made it easier to adapt to the developing software world regarding understanding and use when a layered structure is established.<br> <br> As you know, Maven is a tool that manages project configuration and the build process. However, most developers usually focus on the basic lifecycle phases and commands. In this article, we will cover some lesser known but advanced topics related to the Maven lifecycle that can improve the development process.<br> <br> For those who are not familiar with it, I will briefly touch on the basic phases before moving on to the main topics.</p><h3>Maven Lifecycle Overview</h3><p>Maven has three built-in build lifecycles:</p><p><strong>Default: </strong>Handles project compilation, testing, packaging, and deployment.<br><strong>Clean: </strong>Removes all files generated by previous builds.<br><strong>Site: </strong>Generates a project website.<br> <br> Maven’s lifecycle stages generally build on each other. For example, if a project starts in the clean phase, the subsequent stages (validate, compile, test, package) execute in order, with each phase taking the output of the previous one and continuing the process. <strong><em>In short, a stage covers the previous one and completes the work done by the previous stage.</em></strong></p><p>However, each stage serves a specific purpose, and the operations within a phase can be thought of as a subset of the previous ones. For example, the compile phase compiles the code as a result of the verifications made in the validation phase.</p><p>After a general introduction to these phases, how about learning some details that will speed up and make our work easier?</p><h3>Skipping Tests</h3><p>Sometimes you may want to skip tests. If the tests have already passed successfully and you do not want to run the tests on the same code again, skipping the tests saves time. In case the tests are taking too much time, skipping the tests temporarily can speed up the build process.</p><pre>mvn clean package -DskipTests</pre><ul><li><strong>DskipTests</strong>: This does not run tests but still compiles the test code.</li></ul><pre>mvn clean package -Dmaven.test.skip=true</pre><ul><li><strong>Dmaven.test.skip=true</strong>: Skips compiling the test code.</li></ul><h3>Using Parallel Build (-T Parameter)</h3><p>By default, Maven builds the project with a single thread. However, it has parallel build support to speed up the build process for large projects.</p><pre>mvn clean install -T 1C</pre><ul><li><strong>1C</strong> Uses as many threads as the number of CPU cores.</li><li><strong>2C</strong> Uses twice as many threads as the number of cores.</li><li><strong>4 </strong>Uses a fixed 4 threads.</li></ul><p>This parameter can significantly speed up the build process in projects consisting of independent modules. Let’s take a look at the performance test graph I ran on my local computer.</p><p>Here are my computer’s CPU specifications:</p><ul><li><strong>Model:</strong> Apple M2</li><li><strong>Total Cores:</strong> 8</li><li><strong>Physical Cores:</strong> 8</li><li><strong>Logical Cores (Threads):</strong> 8</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/758/1*IKblsgR5oJlyK5schnJc3A.png" /></figure><h3>Profiles (-P Parameter)</h3><p>Many projects require different configurations for development, testing, and production environments. Profiles help manage the appropriate settings for these environments. Or maybe you want to create a jar file locally and compile it with the version that works in the dev environment. In this case, you can run the install with the profile variable and create the jar.</p><pre>mvn clean install -P dev</pre><h3>Forcing Dependency Updates (-U Parameter)</h3><p>This parameter tells Maven to always download and update dependencies. When you re-download old or broken dependencies in the local repository or if you add new dependencies to your project.</p><pre>mvn clean install -U</pre><h3>Adjusting Log Levels</h3><p>The following parameters can be used to make Maven’s output more detailed or simple.</p><ul><li><strong>X</strong>: Enables debug mode.</li><li><strong>q</strong>: Quiet mode, shows only error messages.</li><li><strong>e</strong>: Shows more detailed error messages.</li></ul><pre>mvn clean install -X</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BqLzIyR-EIElU-MRAbebSw.png" /></figure><pre>mvn clean install -q</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Shwh9NnpUU96LYGhhBDuqA.png" /></figure><pre>mvn clean install -e</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gKBV6wodnwedPpacZp3Vyw.png" /></figure><h3>Properties Set</h3><p>This parameter is used to set a custom property to be used in the Maven configuration.</p><pre>clean install -DskipTests </pre><p>It compiles by skipping tests. By the way, we have mentioned the similar test skipping issue above; I would like to express that we found it right to mention it separately because we think that tests have a special place in software development.</p><pre>mvn clean install -Denv=prod</pre><p>Sets a custom “env” property to “prod”. This is typically used for profile or condition-based configuration.</p><h3>Offline Mode (-o Parameter)</h3><p>Runs Maven without an internet connection.</p><pre>mvn clean install -o</pre><h3>Dependency Resolution and Analysis</h3><p>These commands are very useful in debugging unnecessary or faulty dependencies.</p><ul><li><strong>dependency:tree</strong>: Shows the tree structure of dependencies.</li><li><strong>dependency:analyze</strong>: Detects unused or missing dependencies.</li></ul><pre>mvn dependency:tree</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4csm-_OfuCtq8FLgUhyG-w.png" /></figure><pre>mvn dependency:analyze</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gqZqSx6eyPWvb5Ho-HJywg.png" /></figure><h3>Conclusion</h3><p>Maven lifecycle is not just about basic phases. Using parallel build, optimizing tests, defining custom lifecycles, dependency analysis, and offline mode can make the build process more efficient. By mastering these features, you can optimize the build process and improve your development workflow.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5a841640e8f8" width="1" height="1" alt=""><hr><p><a href="https://iyzico.engineering/maven-lifecycle-and-advanced-usage-parameters-5a841640e8f8">Maven Lifecycle and Advanced Usage Parameters</a> was originally published in <a href="https://iyzico.engineering">iyzico.engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Maven vs Gradle]]></title>
            <link>https://iyzico.engineering/maven-vs-gradle-0bedab9187b3?source=rss----c8b48d7c94e3---4</link>
            <guid isPermaLink="false">https://medium.com/p/0bedab9187b3</guid>
            <category><![CDATA[iyzico]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[build-tool]]></category>
            <category><![CDATA[maven]]></category>
            <dc:creator><![CDATA[Adem Dogan]]></dc:creator>
            <pubDate>Mon, 28 Jul 2025 11:38:46 GMT</pubDate>
            <atom:updated>2025-07-28T11:38:46.587Z</atom:updated>
            <content:encoded><![CDATA[<p>Maven and Gradle are build tools used for Java projects. They simplify tasks such as building, testing, managing dependencies, and deploying software projects. Both are popular tools for compiling and managing projects, but they offer different approaches and features. Before diving into their differences, it is important to understand what each technology is.</p><p><strong>What is Maven?</strong></p><p><strong>Maven</strong> is a tool that simplifies dependency management in Java projects. It allows for the quick and easy inclusion and updating of external libraries used in a project (such as the PostgreSQL driver, MapStruct, Hibernate, and Spring). This makes it possible to manage all required libraries in a centralized manner, ensuring efficiency and consistency.</p><p>Maven utilizes an XML-based configuration file known as <em>pom.xml</em> to manage project setup. The <strong>POM</strong> (Project Object Model) file is used to define and manage the dependencies, plugins, and other configurations for the project. Maven follows the instructions in the <em>pom.xml</em> file to execute the build process. When a project requires an external library, that library is considered a dependency of the project. In Maven, these external dependencies are referred to as <strong>dependencies</strong>, and they are defined within the <em>pom.xml</em> file to ensure proper integration and management throughout the project lifecycle.</p><p>Maven resolves dependencies by downloading the required files from the sources specified in the <strong>repositories</strong> section, such as Maven Central or a private Nexus repository. These files are then stored in the local cache located in the <strong>.m2/repository</strong> directory. To streamline project management and configuration, Maven operates on a plugin-based structure, where each task (e.g., compiling, testing, or packaging) is executed by a specific plugin goal. With its standardized lifecycle structure and declarative configuration model, Maven not only ensures consistency across projects but also makes it possible to reuse processes efficiently.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*IOEvnnDgVfzGvWQt.png" /><figcaption>Maven Architecture</figcaption></figure><p><strong>What is Gradle?</strong></p><p>Gradle is an open-source build automation tool that is widely used for Java, Kotlin, and Android projects. However, due to its versatile structure, it can also be utilized with other programming languages and platforms. Gradle simplifies and automates the software development process, improving efficiency by providing a flexible and customizable approach to building, testing, and deploying applications.</p><p>Gradle is a tool used for compiling projects, managing dependencies, and automating build processes. Its operation begins by reading the <em>settings.gradle</em> or <em>settings.gradle.kts</em> file to define the current project and module structures. It then loads configuration details such as <strong>dependencies</strong>, <strong>repositories</strong>, and <strong>tasks</strong> from the <em>build.gradle</em> or <em>build.gradle.kts </em>file. Gradle undergoes three main phases during the build process:</p><ol><li><strong>Initialization Phase</strong>: This phase loads the metadata for the project and tasks.</li><li><strong>Configuration Phase</strong>: During this phase, the execution logic of tasks and how dependencies will be resolved are determined.</li><li><strong>Execution Phase</strong>: In this final phase, the defined tasks are executed sequentially or in parallel, depending on the configuration.</li></ol><p>To resolve dependencies, Gradle downloads the required files from the sources specified in the <strong>repositories</strong> section (such as Maven Central or Google Maven). To improve performance, Gradle utilizes the Gradle Daemon and a local cache located in the <strong>.gradle/caches</strong> directory. Additionally, Gradle’s flexible Domain-Specific Language (DSL) structure makes it easy to define custom tasks, plugins, and build processes, allowing for extensive customization and optimization.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1003/1*EErtFIFHpqOFBALD26_ykQ.png" /><figcaption>Gradle Basics</figcaption></figure><h3>Pre-Maven and Gradle Era (Ant and Manual Build Processes)</h3><p>Before modern build tools like <strong>Maven and Gradle</strong>, Java developers had to rely on manual compilation and rudimentary automation tools to manage their projects. Early Java development involved manually invoking the javac compiler, setting up classpaths, and packaging JAR files by hand. As projects grew in complexity, these manual processes became inefficient, leading to the need for automation. This gap was initially filled by <strong>Apache Ant</strong>, which introduced XML-based build scripts but lacked proper dependency management. Over time, more advanced solutions like <strong>Maven</strong> and <strong>Gradle</strong> emerged, streamlining the build process, dependency management, and overall project structure.</p><h3>1. Manual Compilation and Build Processes</h3><p>In the early days of Java, projects were compiled and packaged manually. This meant:</p><ul><li>Compiling source code files one by one using the javac command</li><li>Manually setting up the <strong>CLASSPATH</strong></li><li>Creating <strong>JAR/WAR files</strong> manually</li><li>Managing dependencies by hand</li></ul><p>This process was manageable for small projects, but as projects grew larger, <strong>dependency management and build processes became a nightmare</strong>.</p><h3>2. Apache Ant (1999)</h3><p>Ant was an <strong>XML-based build tool</strong> that introduced an automation system for Java projects.</p><p>✔ <strong>Advantages:</strong></p><ul><li>Automated compilation, testing, and packaging using the build.xml file.</li><li>Allowed execution of repetitive commands through a <strong>task-based approach</strong>.</li><li>Eliminated the need for manually using javac and jar commands.</li></ul><p>❌ <strong>Disadvantages:</strong></p><ul><li><strong>No built-in dependency management</strong> (You had to download and add external JAR files manually).</li><li><strong>XML files became harder to read and maintain</strong> as they grew larger.</li></ul><p>As a result, advanced build tools like Maven and Gradle were developed to prevent time loss, reduce errors, and manage projects more efficiently.</p><h3>Maven vs Gradle</h3><p>As previously discussed, Maven and Gradle are two widely used build automation tools in Java development. Both tools are primarily designed to compile projects, execute tests, manage dependencies, and package applications. However, despite their shared objectives, there are several key differences between Maven and Gradle. These differences will be explored in detail under various headings, highlighting the unique characteristics and advantages of each tool.</p><h3>1. <strong>Configuration Language and File Format</strong></h3><p><strong>Maven</strong>:</p><ul><li>Maven uses an XML-based configuration file, <strong>pom.xml</strong>, which contains detailed information about the project’s configuration and dependencies.</li><li>XML enforces the adherence to a specific format. However, this format can sometimes become complex and hinder readability.</li><li>Maven adheres more closely to the “config-over-convention” approach, where explicit configuration is prioritized over convention.</li></ul><p><strong>Gradle</strong>:</p><ul><li>Gradle uses <strong>build.gradle</strong> or <strong>build.gradle.kts</strong> files, which are based on Groovy or Kotlin DSL (Domain Specific Language).</li><li>Groovy or Kotlin provides a more readable and programming-based structure, making it easier to implement project-specific customizations.</li><li>Gradle follows the “convention-over-configuration” principle, which allows you to accomplish more with less code by relying on sensible defaults.</li></ul><h3>2. Performance</h3><p><strong>Maven</strong>:</p><ul><li>Maven recompiles the entire project from scratch during each build process. Due to the lack of incremental build support, the build time may increase in large projects.</li></ul><p><strong>Gradle</strong>:</p><ul><li>Gradle, through its incremental build and build cache mechanisms, processes only the changed files. This significantly speeds up the build process.</li><li>The performance advantage of Gradle is particularly noticeable in large and complex projects.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*IVyGbnwsQ7_NJU8rmpYvzQ.png" /><figcaption>Maven vs Gradle for build times</figcaption></figure><h3>3. Dependency Management</h3><p><strong>Maven</strong>:</p><ul><li>Maven uses the information specified in the <strong>pom.xml</strong> file to manage dependencies.</li><li>By default, Maven supports Maven Central. In case of dependency conflicts, the “dependency mediation” mechanism is activated.</li></ul><p><strong>Gradle</strong>:</p><ul><li>Gradle supports various repository systems, such as Maven Central, JCenter, and Ivy.</li><li>Gradle’s syntax for defining dependencies is simpler and more readable.</li><li>It offers a more flexible experience with advanced dependency resolution mechanisms.</li></ul><h3>4. Flexibility and Customization</h3><p><strong>Maven</strong>:</p><ul><li>Maven tends to be rigid, often confined to a specific structure.</li><li>Customization in Maven requires the use of plugins, which can sometimes be complex and limited.</li></ul><p><strong>Gradle</strong>:</p><ul><li>Gradle is much more flexible due to its scripting language. It allows for detailed control over the build processes.</li><li>By integrating Groovy or Kotlin-based code into the build process, you can develop project-specific solutions.</li></ul><h3>5. Learning Curve</h3><p><strong>Maven</strong>:</p><ul><li>Maven is easier to learn due to its simpler structure. The XML format is straightforward to understand as long as the specified rules are followed.</li></ul><p><strong>Gradle</strong>:</p><ul><li>Gradle can be slightly more complex due to its scripting-based nature. Initially, knowledge of Groovy or Kotlin may be required.</li><li>However, once you learn Gradle, you will realize that it offers a much more flexible and powerful structure.</li></ul><h3>6. Community and Use Cases</h3><p><strong>Maven</strong>:</p><ul><li>Being an older tool, it has a large community and an extensive library of documentation.</li><li>It is particularly widely used in enterprise Java projects.</li></ul><p><strong>Gradle</strong>:</p><ul><li>Due to its more modern structure, it is used as the default build tool in newer technologies, such as Android.</li><li>Its community is steadily growing, and it is particularly popular among mobile developers.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y-NuFq2ym3UROS1oJF24SA.png" /><figcaption>Interest over time from Google Trends</figcaption></figure><p>Now, all of these features are summarized in the following table, which provides a detailed comparison between Maven and Gradle.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dXdnZfJHsNJsuX83dZcIXA.png" /><figcaption>Maven vs Gradle</figcaption></figure><h3>Conclusion</h3><ul><li><strong>Maven</strong>: It is ideal for simpler and enterprise projects. It may be more suitable for teams that prefer a traditional structure.</li><li><strong>Gradle</strong>: It has a more modern, flexible, and faster structure. It is particularly better suited for large projects or Android developers.</li></ul><p>It is recommended to make a decision based on the needs of your project and the skill level of your team.</p><h4><strong>REFERENCES</strong></h4><ol><li><a href="https://maven.apache.org/what-is-maven.html">https://maven.apache.org/what-is-maven.html</a></li><li><a href="https://www.baeldung.com/maven-artifact">https://www.baeldung.com/maven-artifact</a></li><li><a href="https://www.simplilearn.com/tutorials/gradle-tutorial/what-is-gradle">https://www.simplilearn.com/tutorials/gradle-tutorial/what-is-gradle</a></li><li><a href="https://www.geeksforgeeks.org/difference-between-gradle-and-maven/">https://www.geeksforgeeks.org/difference-between-gradle-and-maven/</a></li><li><a href="https://chakray.com/gradle-vs-maven-definitions-and-main-differences/">https://chakray.com/gradle-vs-maven-definitions-and-main-differences/</a></li><li><a href="https://gradle.org/maven-and-gradle/">https://gradle.org/maven-and-gradle/</a></li><li><a href="https://www.geeksforgeeks.org/apache-maven/">https://www.geeksforgeeks.org/apache-maven/</a></li><li><a href="https://docs.gradle.org/">https://docs.gradle.org/</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0bedab9187b3" width="1" height="1" alt=""><hr><p><a href="https://iyzico.engineering/maven-vs-gradle-0bedab9187b3">Maven vs Gradle</a> was originally published in <a href="https://iyzico.engineering">iyzico.engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Less Effort, More Control: Real-Time Microservice Monitoring with Spring Boot Admin 3 & Microsoft…]]></title>
            <link>https://iyzico.engineering/less-effort-more-control-real-time-microservice-monitoring-with-spring-boot-admin-3-microsoft-96f0fb58a78d?source=rss----c8b48d7c94e3---4</link>
            <guid isPermaLink="false">https://medium.com/p/96f0fb58a78d</guid>
            <category><![CDATA[monitoring-software]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[iyzico]]></category>
            <category><![CDATA[software-architecture]]></category>
            <dc:creator><![CDATA[Berkan YILDIZ]]></dc:creator>
            <pubDate>Wed, 16 Jul 2025 11:50:39 GMT</pubDate>
            <atom:updated>2025-07-16T11:50:39.189Z</atom:updated>
            <content:encoded><![CDATA[<h3>Less Effort, More Control: Real-Time Microservice Monitoring with Spring Boot Admin 3 &amp; Microsoft Teams</h3><p>Nowadays, monitoring and managing microservices in real-time is a key requirement for stable and performant systems. Tools like <strong>Spring Boot Admin</strong> can play an important role in quickly identifying potential issues — such as high CPU usage, low disk availability, abnormal thread counts, and misconfigured log levels — in a single dashboard. For me, it also integrates easily with Microsoft Teams to send <strong>notifications</strong> whenever certain KPIs approach critical thresholds.</p><p>This article focuses on <strong>Spring Boot Admin 3.x.x</strong>, illustrating how I monitor CPU, memory, disk usage, threads, logs, and other metrics of my microservices. I’ll compare it briefly with popular solutions like <strong>Prometheus-Grafana</strong> and explain why I preferred a “less effort, more control” philosophy in my recent project.</p><h3>Project Goals and Workflow</h3><p>When setting up my microservices environment, my primary goal was to <strong>centrally manage</strong> all services, predict critical resource usage, and reduce reaction time. As traffic surges (e.g., campaign periods) or unexpected failures occur, I wanted to quickly detect performance bottlenecks and respond. <strong>Spring Boot Admin</strong> provided a straightforward solution, thanks to its direct integration with the Spring ecosystem.</p><h3>Defining KPIs</h3><p>To keep a consistent standard across all services, I set measurable goals:</p><ol><li><strong>CPU usage should stay below 90%.</strong></li></ol><ul><li>Heavy business logic under sudden load can spike CPU usage, impacting response times. Early detection prevents slowdowns or possible crashes.</li></ul><p><strong>2. Disk usage should not exceed 80%.</strong></p><ul><li>Logs, cache files, and other data can fill up disk space. Once the disk is near capacity, read/write operations slow down, risking failures.</li></ul><p><strong>3. Thread count should stay under 150.</strong></p><ul><li>Excessive threads consume system resources and complicate debugging in logs. High thread counts might indicate deadlocks or resource leaks.</li></ul><p><strong>4. Anticipating Server Uptime and Downtime</strong></p><ul><li>In a dynamic microservices environment, services may frequently go <strong>up</strong> or <strong>down</strong> due to auto-scaling events, new deployments, or unforeseen crashes. Proactively monitoring these transitions is essential for maintaining system stability. By setting up clear alerts — such as “Server Up” or “Server Down” notifications — your team can quickly respond to issues, investigate the root causes, and minimize disruption. Regular health checks and readiness probes also help detect potential outages before they impact users, ensuring each microservice remains reliable under changing load conditions.</li></ul><p>These KPIs were chosen based on past team experiences and known performance issues in similar projects.</p><h3>Why Choose Spring Boot Admin?</h3><p>While <strong>Prometheus-Grafana</strong> is excellent for long-term data storage and more advanced dashboards, Spring Boot Admin provided:</p><ul><li><strong>Minimal Integration:</strong><br>Just add spring-boot-admin-starter-server to the monitoring server application and spring-boot-admin-starter-client to each microservice.</li><li><strong>Immediate Metrics via Actuator:</strong><br>Actuator automatically supplies CPU, memory, thread, log, and cache metrics to SBA.</li><li><strong>Instant Notifications in Microsoft Teams:</strong><br>A small configuration snippet can push notifications whenever thresholds are crossed.</li><li><strong>Less Effort, More Control:</strong><br>Instead of creating custom scrape configs or separate alert managers, SBA works “out of the box” with the Spring environment.</li></ul><h3>Integration Steps</h3><p>Below is a <strong>step-by-step</strong> guide on how I integrated <strong>Spring Boot Admin</strong> into a microservices setup. Keep in mind that <strong>there are two separate applications</strong>: one is the <strong>Admin Server</strong>, and the others are <strong>Client</strong> microservices that register themselves to this Admin Server.</p><h3>1. Create a Spring Boot Admin Server</h3><p>Create a new Spring Boot application (often named something like sba-admin-server) and include the following dependency in your pom.xml or build.gradle</p><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;de.codecentric&lt;/groupId&gt;<br>    &lt;artifactId&gt;spring-boot-admin-starter-server&lt;/artifactId&gt;<br>    &lt;version&gt;3.0.0&lt;/version&gt;<br>&lt;/dependency&gt;</pre><p>Then enable the Admin Server in your main application class:</p><pre>@SpringBootApplication<br>@EnableAdminServer<br>public class SbaAdminServerApplication {<br>    public static void main(String[] args) {<br>        SpringApplication.run(SbaAdminServerApplication.class, args);<br>    }<br>}</pre><p>In application.ymlYou can configure the port, security, and internal admin endpoints. For example, to allow a <strong>basic security setup</strong>, you might do:</p><pre>server:<br>  port: 8080<br>management:<br>  server:<br>    port: 8081<br>  endpoints:<br>    web:<br>      base-path: /management<br>      exposure:<br>        include: &quot;*&quot;<br>spring:<br>  security:<br>    user:<br>      name: admin<br>      password: admin123</pre><p>This ensures only authenticated users can see the Admin UI.</p><h3>2. Add the Client Starter to Each Microservice</h3><p>Each microservice (e.g., order-service) needs:</p><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;de.codecentric&lt;/groupId&gt;<br>    &lt;artifactId&gt;spring-boot-admin-starter-client&lt;/artifactId&gt;<br>    &lt;version&gt;3.0.0&lt;/version&gt;<br>&lt;/dependency&gt;</pre><p>Also, <strong>expose</strong> the necessary Actuator endpoints in application.yml:</p><pre>management:<br>  endpoints:<br>    web:<br>      exposure:<br>        include: &quot;*&quot;<br>  health:<br>    probes:<br>      enabled: true</pre><p>In the same file, specify the <strong>Admin Server URL</strong> so the microservice automatically <strong>registers</strong> itself:</p><pre>spring:<br>  boot:<br>    admin:<br>      client:<br>        url: http://localhost:8080<br>        enabled: true<br>        auto-registration: true</pre><h3>3. Microsoft Teams Integration</h3><ol><li><strong>Create a Webhook</strong> in Microsoft Teams:</li></ol><ul><li>Go to the channel where you want to receive notifications.</li><li>Click on “Connectors” (or “Apps”) and add an “Incoming Webhook.”</li><li>Copy the <strong>Webhook URL</strong>.</li></ul><p><strong>2. Configure Spring Boot Admin</strong> to use that Webhook: In your Admin Serverapplication.yml, set:</p><pre>spring:<br>  boot:<br>    admin:<br>      notify:<br>        teams:<br>          enabled: true<br>          webhook-url: &quot;YOUR_TEAMS_WEBHOOK_URL&quot;</pre><pre>@Configuration<br>public class NotifierConfiguration {<br><br>    @Bean<br>    public TeamsWebhookNotifier teamsWebhookNotifier(InstanceRepository repository,<br>                                                     WebClient webClient,<br>                                                     @Value(&quot;${teams.webhook-url}&quot;) String webhookUrl) {<br>        return new TeamsWebhookNotifier(repository, webClient, webhookUrl);<br>    }<br><br>}</pre><pre>@Component<br>public class TeamsMessageSender {<br><br>    private final Queue&lt;String&gt; messageQueue = new ConcurrentLinkedQueue&lt;&gt;();<br>    private final WebClient webClient;<br><br>    @Value(&quot;${teams.webhook-url}&quot;)<br>    private String webhookUrl;<br><br>    public TeamsMessageSender(WebClient webClient) {<br>        this.webClient = webClient;<br>    }<br>    public void enqueueMessage(String message) {<br>        messageQueue.add(message);<br>        System.out.println(&quot;Message queued: &quot; + message);<br>    }<br><br>    @Scheduled(fixedRate = 250)<br>    public void processQueue() {<br>        String message = messageQueue.poll();<br>        if (message != null) {<br>            sendToTeams(message);<br>        }<br>    }<br><br>    private void sendToTeams(String message) {<br>        webClient.post()<br>                .uri(webhookUrl)<br>                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)<br>                .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)<br>                .bodyValue(message)<br>                .retrieve()<br>                .bodyToMono(Void.class)<br>                .doOnSuccess(v -&gt; System.out.println(&quot;Message sent successfully: &quot; + message))<br>                .doOnError(error -&gt; {<br>                    System.out.println(&quot;Message delivery failed, re-queuing: &quot; + error.getMessage());<br>                    enqueueMessage(message);<br>                })<br>                .subscribe();<br>    }<br>}</pre><p><strong>3. Trigger Threshold-based Notifications</strong> (optional):</p><ul><li>You can leverage the Actuator’s health checks or custom metrics to send alerts. If CPU usage crosses 90% or thread count goes beyond 150, you can configure SBA to push a notification to Teams.</li><li>Keep in mind that Teams webhooks allow a maximum of 4 notifications per second. If your system can exceed this rate, consider buffering or queueing alerts.</li></ul><p>That’s all it takes to get <strong>instant notifications</strong> when your thresholds are breached!</p><h3>Identifying and Solving Problems</h3><h4>1. CPU and Memory Monitoring</h4><p>Spring Boot Admin’s <strong>Metrics</strong> tab shows real-time CPU and memory usage. If the CPU nears 90% or memory spikes, you’ll receive a “CPU usage is critical!” or “Memory usage is high!” notification in Teams. This early warning allows you to <strong>scale out</strong> your service or optimize resource-heavy operations before major slowdowns occur.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TAnFlmeb-7B94xtMHo_WhQ.png" /></figure><h4>2. Disk and Cache Status</h4><p>A near-full disk can lead to slow I/O and application crashes. SBA’s interface highlights disk usage, and you’ll get alerts when it exceeds 80%. This is crucial for implementing <strong>log rotation</strong> or <strong>cache eviction</strong> before space runs out.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EK3q-XrcBajdIfVUIx67vg.png" /></figure><h4>3. Server Availability and Thread Management</h4><p>If a microservice goes down, a “Service Down!” message will appear in Teams immediately. For thread management, SBA helps monitor if you exceed 150 threads. In that case, you might enable DEBUG logs (temporarily!) to see what’s spawning so many threads — before it leads to a crash or hidden deadlock.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CdPCx2KuiBq-7sUX3icO1Q.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zFJ5xPLAQOOVgjhe2hahcg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_0MAxmbS1-MdqNNmBgT9rQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CdPCx2KuiBq-7sUX3icO1Q.png" /></figure><h4>4. Log Levels</h4><p>Keeping logs at DEBUG all the time not only fills up your disk faster, but also makes it harder to efficiently trace errors. The excessive debug logs often bury critical failure messages, complicating troubleshooting. It’s best to enable DEBUG only for targeted debugging sessions and revert to INFO or WARN for normal operation.</p><h3>Alternative Tools: Why I Chose Spring Boot Admin</h3><p>While <strong>Prometheus-Grafana</strong> excels in <strong>long-term metrics storage</strong> and <strong>custom dashboards</strong>, Spring Boot Admin offered a tighter, simpler integration for my specific needs:</p><ul><li><strong>Configuration:</strong><br>Prometheus requires scrape configs, Grafana needs data sources and alert rules. By contrast, SBA auto-discovers Actuator metrics from each microservice.</li><li><strong>Alerts in Microsoft Teams:</strong><br>SBA supports out-of-the-box Teams notifications with minimal YAML. Meanwhile, Grafana Alerts or Alertmanager require additional setups.</li><li><strong>Ready Features:</strong><br>Environment details, metrics, threads, and loggers are immediately visible, without extra instrumentation code.</li></ul><h3>Security Considerations</h3><p>For real-world deployments, it’s important to <strong>secure</strong> the Admin UI and hide sensitive properties. At minimum, configure <strong>basic authentication</strong> on the Admin Server. In more advanced setups, you might integrate with <strong>OAuth2</strong> or single sign-on solutions. Also be mindful of <strong>management endpoints</strong> — not all should be publicly exposed in production.</p><h3>Conclusion</h3><p>In this article, I demonstrated how <strong>Spring Boot Admin 3.x.x</strong> can significantly simplify <strong>real-time monitoring</strong> in a microservices-based project. By setting <strong>tangible KPIs</strong> — like keeping CPU below 90%, disk usage under 80%, and thread counts below 150 — you can proactively detect and address bottlenecks before they become outages.</p><p>Compared to tools like <strong>Prometheus-Grafana</strong>, Spring Boot Admin offers a fast path to “less effort, more control,” thanks to <strong>deep Spring integration</strong> and <strong>built-in</strong> environment, metrics, thread, and log features. The easy Microsoft Teams integration further empowers teams to respond swiftly to any anomaly.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=96f0fb58a78d" width="1" height="1" alt=""><hr><p><a href="https://iyzico.engineering/less-effort-more-control-real-time-microservice-monitoring-with-spring-boot-admin-3-microsoft-96f0fb58a78d">Less Effort, More Control: Real-Time Microservice Monitoring with Spring Boot Admin 3 &amp; Microsoft…</a> was originally published in <a href="https://iyzico.engineering">iyzico.engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Outbox Pattern Best Practices for Reliable Messaging in Distributed Systems]]></title>
            <link>https://iyzico.engineering/outbox-pattern-best-practices-for-reliable-messaging-in-distributed-systems-923201f03fd5?source=rss----c8b48d7c94e3---4</link>
            <guid isPermaLink="false">https://medium.com/p/923201f03fd5</guid>
            <category><![CDATA[application-architecture]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[outbox-pattern]]></category>
            <category><![CDATA[iyzico]]></category>
            <category><![CDATA[software-architecture]]></category>
            <dc:creator><![CDATA[pnrkarga]]></dc:creator>
            <pubDate>Tue, 01 Jul 2025 14:37:43 GMT</pubDate>
            <atom:updated>2025-07-01T14:37:42.921Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SgkxlwWwaGNquU1rmCqIVA.jpeg" /></figure><p>In distributed systems, ensuring reliable communication between services is essential for maintaining data consistency and integrity. The Outbox Pattern is an architectural solution to this problem, providing a way to reliably send messages while preserving the transactional integrity of your system. In this article, I will explain the best practices to follow when implementing the Outbox Pattern to maximize its reliability and performance.</p><p><strong>Content</strong></p><ul><li><strong>What is the Outbox Pattern?</strong></li><li><strong>Best Practices for Implementing the Outbox Pattern</strong></li></ul><p>o <strong>Transactional Integrity</strong></p><p>o <strong>Implement Idempotent Event Processing</strong></p><p>o <strong>Optimize the Outbox Table Design</strong></p><p>o <strong>Reliable Event Publishing</strong></p><p>o <strong>Event Acknowledgment and Cleanup</strong></p><p>o <strong>Scaling</strong></p><p>o <strong>Security</strong></p><ul><li><strong>When NOT to Use the Outbox Pattern</strong></li><li><strong>Disadvantages of the Outbox Pattern</strong></li></ul><p><strong>What is the Outbox Pattern?</strong></p><p>The Outbox Pattern is an architectural design pattern used in distributed systems to ensure reliable messaging between different components or services, especially in scenarios where consistency and data integrity are critical. It involves storing events or messages in an “outbox” table within the database. Once a business operation is committed, the events are stored in the outbox table as part of the same transaction, ensuring reliability. A separate process later reads these events and sends them to external systems, such as message brokers or other services.</p><p>This approach ensures that messages are never lost or duplicated and that the system remains consistent, even in the event of failures.</p><p><strong>Best Practices for Implementing the Outbox Pattern</strong></p><p><strong>1. Transactional Integrity</strong></p><p><strong>Single Transaction for Outbox Event</strong>: Ensure that both the business operation and the creation of the outbox event happen within the same transaction. This guarantees that the database transaction and message creation are atomic, meaning either both succeed or fail together.</p><p><strong>Example:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/788/1*ZETDeNmRaLPjahQtecbWjQ.png" /></figure><p><strong>2. Implement Idempotent Event Processing</strong></p><p><strong>Idempotent Event Handlers</strong>: The event processor should be idempotent. This means that even if an event is processed multiple times, the system should not cause unexpected side effects.</p><p><strong>Prevent Event Duplication</strong>: Track processed events using unique event reference IDs and status fields in the outbox table. Mark the event&#39;s status as “processed/sent” only after they’ve been successfully sent to the message broker.</p><p><strong>3. Optimize the Outbox Table Design</strong></p><p><strong>Dedicated Outbox Table</strong>: Use a separate outbox table to store events. This keeps business data isolated and makes it easier to manage events asynchronously.</p><p><strong>Columns</strong>:</p><p><strong>event_id:</strong> Unique identifier for the event.</p><p><strong>event_type:</strong> The type of event</p><p><strong>payload:</strong> The event data (usually in JSON format).</p><p><strong>event_status:</strong> A flag indicating whether the event is pending, processed, or failed.</p><p><strong>event_created_at:</strong> Timestamp of when the event was created.</p><p><strong>4. Reliable Event Publishing</strong></p><p><strong>Use Reliable Messaging Systems</strong>: Once the event is created in the outbox table, it should be reliably published to a message broker (RabbitMQ, Kafka, AWS SNS/SQS). The event processor should handle retries and ensure the successful delivery of events.</p><p><strong>Retry Logic</strong>: Implement retry logic to ensure events are eventually delivered to the message broker.</p><p><strong>Dead-letter Queue</strong>: For events that cannot be processed after several retries, use a dead-letter queue to isolate and handle failures.</p><p><strong>5. Event Acknowledgment and Cleanup</strong></p><p><strong>Event Acknowledgment</strong>: Ensure that after an event is successfully published, it is marked as processed in the outbox table.</p><p><strong>Data Retention</strong>: Regularly clean up the outbox table after events are successfully processed to prevent the database from becoming large amounts of data. If we need to archive old events, we can store another history table.</p><p><strong>6. Scaling</strong></p><p><strong>Parallel Processing: </strong>For systems with high event volumes, you can process the Outbox table in parallel by splitting the workload across multiple processors. Use locking mechanisms to claim batches of messages, allowing multiple processors to operate concurrently without conflict.</p><p><strong>Example: Using SKIP LOCKED in SQL for Parallel Processing</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/386/1*KB6IQP52kUtyQV1uvKarRg.png" /></figure><p><strong>FOR UPDATE SKIP LOCKED: </strong>This locks rows without blocking other transactions. If one processor locks a batch of rows, others will skip those rows and work on the next available batch.</p><p><strong>LIMIT 100:</strong> Controls batch size, fetching 100 messages at a time.</p><p><strong>Batch Processing:</strong></p><p>Processing a single event at a time can become a bottleneck. Batch processing helps by reducing the number of transactions and improving efficiency.</p><p>Instead of processing one message at a time, you can process multiple messages in a single transaction.</p><p><strong>Advantages:</strong> Fewer database round-trips and reduced I/O operations.</p><p><strong>Batch Size Configuration:</strong> Tune the batch size based on the available memory and database performance.</p><p><strong>Adjust Processing Frequency:</strong></p><p>If your Outbox processor runs on a scheduled job, you can adjust the interval to process events more frequently.</p><p><strong><em>Scaling Tip:</em></strong><em> Deploy multiple instances of your Outbox processor to scale horizontally. Ensure each instance locks and processes different batches.</em></p><p><strong>Combining Strategies for Maximum Performance</strong></p><p>For high-throughput systems, combining these strategies provides the best results:</p><p>· <strong>Parallel Processing + Locking (SKIP LOCKED)</strong> for concurrency.</p><p>· <strong>Batch Processing</strong> to minimize database operations.</p><p>· <strong>High-Frequency Scheduling</strong> to reduce latency and ensure near-real-time processing.</p><p><strong>7. Security and Compliance</strong></p><p><strong>Encrypt Sensitive Data: </strong>If the event payload contains sensitive information, consider encrypting the data before storing it in the outbox table.</p><p><strong>Auditability: </strong>Maintain detailed logs of all events for auditing and compliance purposes. This allows you to trace the flow of data through the system, which is important for troubleshooting and regulative requirements.</p><p><strong>8. Monitor and Log Event Processing</strong></p><p><strong>Event Monitoring</strong>: Use monitoring tools to observe the flow of event processing in real time. This helps identify problems like failed deliveries or delays, ensuring you can act quickly to maintain system reliability.<br><strong>Example Tools</strong>: Prometheus, Grafana, or AWS CloudWatch.</p><p><strong>Logging and Alerts</strong>: Implement detailed logging and alerting for failures in event processing. This ensures that you can take timely action when events are stuck or the processing is delayed.</p><p><strong><em>Tip</em></strong><em>: Use structured logging to make it easier to analyze logs and correlate them with specific events or issues</em></p><p><strong>When NOT to Use the Outbox Pattern</strong></p><p>While the Outbox Pattern is effective for ensuring consistency between the database and message broker, there are situations where it may not be the best choice:</p><ol><li><strong>Real-Time Requirements</strong></li></ol><p>The Outbox Pattern introduces <strong>delays</strong> because events are processed in batches. If your system requires <strong>real-time, low-latency event processing</strong>, direct publishing to the message broker might be better.</p><p><strong>Example:</strong> A stock trading system where delays of milliseconds can cause significant losses.</p><p><strong>2. Simple Systems with Low Volume</strong></p><p>For small-scale applications or low message throughput, the complexity of implementing an Outbox Pattern might outweigh its benefits.</p><p><strong>Example:</strong> A simple e-commerce site with low traffic that doesn’t require complex messaging guarantees.</p><p><strong>Disadvantages of the Outbox Pattern</strong></p><ol><li><strong>Increased Complexity</strong></li></ol><p>The pattern requires additional infrastructure, like an <strong>Outbox table</strong>, <strong>background job</strong>, and <strong>scheduling mechanisms</strong>. Ensuring message deduplication and reliable delivery adds to the system’s complexity.</p><p><strong><em>Solution:</em></strong><em> Use a framework or library to abstract these complexities where possible</em></p><p><strong>2. Higher Database Load</strong></p><p>Storing and querying events in the same database can lead to increased <strong>I/O and locking contention</strong>, especially with high-frequency writes. This can degrade the performance of core business operations.</p><p><strong><em>Solution:</em></strong><em> Archive or purge old events from the Outbox table regularly.</em></p><p><strong>3. Delayed Event Propagation</strong></p><p>Event processing is <strong>not immediate</strong>; depending on the scheduling frequency of the Outbox processor, there may be delays in delivering events.</p><p><strong><em>Solution:</em></strong><em> Adjust the processing interval and batch size to balance throughput and latency.</em></p><p><strong>4. Potential for Partial Failures</strong></p><p>If the Outbox processor fails after marking some events as processed but before publishing them, you risk <strong>data inconsistency</strong> or <strong>missed messages</strong>.</p><p><strong><em>Solution:</em></strong><em> Use idempotent consumers and implement a retry mechanism for failed events.</em></p><p><strong>5. Requires Monitoring and Error Handling</strong></p><p>The Outbox Processor can become a <strong>single point of failure</strong> if not monitored properly. Misconfigurations or bugs can lead to missed or duplicated events.</p><p><strong><em>Solution:</em></strong><em> Implement monitoring, alerting, and retry mechanisms to ensure reliability.</em></p><p><strong>6. Schema Changes Can Be Challenging</strong></p><p>Adding or modifying fields in the event payload schema can introduce compatibility issues across services.</p><p><strong><em>Solution:</em></strong><em> Use versioned event schemas (e.g. JSON Schema) and evolve the schema carefully.</em></p><p><strong>Conclusion</strong></p><p>The <strong>Outbox Pattern</strong> is a powerful tool for ensuring reliable messaging and data consistency in distributed systems. By following best practices such as ensuring transactional integrity, implementing idempotent event processing, and optimizing the outbox table, you can build robust, scalable, and fault-tolerant systems. Whether you’re building a microservices architecture or integrating multiple systems, the Outbox Pattern can help you maintain consistency while minimizing the risk of message loss or duplication.</p><p>By adopting these best practices, you can leverage the full potential of the Outbox Pattern to create reliable, maintainable, and performant distributed systems.</p><p>🔗 <strong>You can explore the full implementation of this concept in my </strong><a href="https://github.com/pnrkarga/outbox-event-sample"><strong>Outbox Event Sample GitHub repository</strong></a>.<br>It demonstrates a clean and modular approach to the Outbox Pattern using Spring Boot, RabbitMQ, PostgreSQL, and JPA. The project includes publisher and consumer setups, database schema, and transaction-safe message handling.</p><h4><strong>References</strong></h4><p><a href="https://www.milanjovanovic.tech/blog/implementing-the-outbox-pattern">Implementing the Outbox Pattern</a></p><p><a href="https://microservices.io/patterns/data/transactional-outbox.html">https://microservices.io/patterns/data/transactional-outbox.html</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=923201f03fd5" width="1" height="1" alt=""><hr><p><a href="https://iyzico.engineering/outbox-pattern-best-practices-for-reliable-messaging-in-distributed-systems-923201f03fd5">Outbox Pattern Best Practices for Reliable Messaging in Distributed Systems</a> was originally published in <a href="https://iyzico.engineering">iyzico.engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>