<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: DEVANSHU PATIL</title>
    <description>The latest articles on DEV Community by DEVANSHU PATIL (@devanshu_patil).</description>
    <link>https://dev.to/devanshu_patil</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4112603%2F2dbe1932-ed99-4536-bdd7-f380a1fbe42d.png</url>
      <title>DEV Community: DEVANSHU PATIL</title>
      <link>https://dev.to/devanshu_patil</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devanshu_patil"/>
    <language>en</language>
    <item>
      <title>The Hidden Cost of “Just One More Feature”</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Fri, 25 Sep 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/the-hidden-cost-of-just-one-more-feature-156j</link>
      <guid>https://dev.to/devanshu_patil/the-hidden-cost-of-just-one-more-feature-156j</guid>
      <description>&lt;h1&gt;
  
  
  The Hidden Cost of “Just One More Feature”
&lt;/h1&gt;

&lt;p&gt;When you're building a real application, adding a feature rarely means adding only a feature.&lt;/p&gt;

&lt;p&gt;A new feature usually touches existing data, existing screens, existing business rules, and existing assumptions.&lt;/p&gt;

&lt;p&gt;I've seen this repeatedly while building FinLedger.&lt;/p&gt;

&lt;p&gt;At first, a request can sound simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Add support for recurring transactions."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The obvious implementation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New screen
   ↓
Save recurring transaction
   ↓
Done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;But that's not really the feature.&lt;/p&gt;

&lt;p&gt;You also have to ask:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
When should it execute?&lt;br&gt;
What happens if the app wasn't opened?&lt;br&gt;
Can it create duplicates?&lt;br&gt;
How is the next occurrence calculated?&lt;br&gt;
What happens when the user edits it?&lt;br&gt;
What happens when it is disabled?&lt;br&gt;
Where is its state stored?&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Suddenly, the feature is a system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features have dependencies
&lt;/h2&gt;

&lt;p&gt;A useful way to think about a feature is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Feature&lt;br&gt;
 ├── UI&lt;br&gt;
 ├── State&lt;br&gt;
 ├── Business rules&lt;br&gt;
 ├── Persistence&lt;br&gt;
 ├── Background work&lt;br&gt;
 └── Existing features&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The visible screen is often the smallest part.&lt;/p&gt;

&lt;p&gt;For Android applications, this becomes especially important when a feature needs to survive beyond the current screen.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Compose UI&lt;br&gt;
    ↓&lt;br&gt;
ViewModel&lt;br&gt;
    ↓&lt;br&gt;
Repository&lt;br&gt;
    ↓&lt;br&gt;
Room&lt;br&gt;
    ↓&lt;br&gt;
WorkManager&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Each component may need to understand a different part of the feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't start by writing the screen
&lt;/h2&gt;

&lt;p&gt;This is one habit I'm trying to improve.&lt;/p&gt;

&lt;p&gt;Instead of immediately creating:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;kotlin&lt;br&gt;
@Composable&lt;br&gt;
fun NewFeatureScreen() {&lt;br&gt;
    // ...&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I want to first answer:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
What data does this feature own?&lt;br&gt;
What existing data does it depend on?&lt;br&gt;
What rules must always hold?&lt;br&gt;
What happens when the app is closed?&lt;br&gt;
What happens when the operation fails?&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Those questions expose the real complexity before code starts spreading across the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background work changes everything
&lt;/h2&gt;

&lt;p&gt;A feature that only works while the app is open is fundamentally different from one that needs background execution.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
User opens app&lt;br&gt;
   ↓&lt;br&gt;
Calculate something&lt;br&gt;
   ↓&lt;br&gt;
Show result&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;is straightforward.&lt;/p&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
User configures something&lt;br&gt;
   ↓&lt;br&gt;
App closes&lt;br&gt;
   ↓&lt;br&gt;
Hours pass&lt;br&gt;
   ↓&lt;br&gt;
Background task runs&lt;br&gt;
   ↓&lt;br&gt;
Database changes&lt;br&gt;
   ↓&lt;br&gt;
User opens app&lt;br&gt;
   ↓&lt;br&gt;
UI reflects new state&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now you need to think about persistence, scheduling, retries, and idempotency.&lt;/p&gt;

&lt;p&gt;This is why Android tools such as WorkManager become architectural components rather than just utilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Existing data is where complexity hides
&lt;/h2&gt;

&lt;p&gt;Suppose you introduce a new field:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
recurring = true&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What about existing records?&lt;/p&gt;

&lt;p&gt;You now have a migration problem.&lt;/p&gt;

&lt;p&gt;The application needs to understand both:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Old database&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
New database&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;without destroying existing user data.&lt;/p&gt;

&lt;p&gt;This is one reason database schema changes deserve more thought than they usually get during feature development.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feature should have one source of truth
&lt;/h2&gt;

&lt;p&gt;Another problem appears when the same state is stored in multiple places.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
UI state&lt;br&gt;
Database state&lt;br&gt;
Cached state&lt;br&gt;
Background worker state&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If these can disagree, debugging becomes painful.&lt;/p&gt;

&lt;p&gt;You might see:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
UI says: enabled&lt;br&gt;
Database says: disabled&lt;br&gt;
Worker thinks: enabled&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now the bug isn't in one line.&lt;/p&gt;

&lt;p&gt;It's a synchronization problem.&lt;/p&gt;

&lt;p&gt;A better design starts by deciding which layer owns the authoritative state.&lt;/p&gt;

&lt;h2&gt;
  
  
  More code isn't always more complete
&lt;/h2&gt;

&lt;p&gt;There's also a trap in feature development:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
More classes&lt;br&gt;
More interfaces&lt;br&gt;
More abstractions&lt;br&gt;
More configuration&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;can create the illusion that the feature is production-ready.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;A feature is complete when its important states and failure paths have been considered.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Create&lt;br&gt;
Read&lt;br&gt;
Update&lt;br&gt;
Delete&lt;br&gt;
Empty state&lt;br&gt;
Invalid input&lt;br&gt;
Failure&lt;br&gt;
Retry&lt;br&gt;
Existing data&lt;br&gt;
App restart&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The exact list depends on the feature, but the principle is the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  My biggest takeaway
&lt;/h2&gt;

&lt;p&gt;Building real software has made me more cautious about the phrase:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"It's just a small feature."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Small from the user's perspective does not necessarily mean small from the system's perspective.&lt;/p&gt;

&lt;p&gt;The right question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How quickly can I add this screen?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What parts of the existing system does this feature change?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question leads to better estimates, better architecture, and fewer surprises halfway through implementation.&lt;/p&gt;

&lt;p&gt;**A feature isn't a screen.&lt;/p&gt;

&lt;p&gt;A feature is a change to the system.**&lt;/p&gt;

&lt;h1&gt;
  
  
  android #kotlin #jetpackcompose #softwarearchitecture #finledger #room #workmanager #softwareengineering #buildinpublic
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>The Most Useful Backend Skill I Learned From Building on a Small VPS</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Fri, 25 Sep 2026 06:08:14 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/the-most-useful-backend-skill-i-learned-from-building-on-a-small-vps-23bm</link>
      <guid>https://dev.to/devanshu_patil/the-most-useful-backend-skill-i-learned-from-building-on-a-small-vps-23bm</guid>
      <description>&lt;h1&gt;
  
  
  The Most Useful Backend Skill I Learned From Building on a Small VPS
&lt;/h1&gt;

&lt;p&gt;When building backend applications, it's easy to focus on frameworks.&lt;/p&gt;

&lt;p&gt;Node.js.&lt;/p&gt;

&lt;p&gt;Express.&lt;/p&gt;

&lt;p&gt;MongoDB.&lt;/p&gt;

&lt;p&gt;Docker.&lt;/p&gt;

&lt;p&gt;APIs.&lt;/p&gt;

&lt;p&gt;But running my own applications on a VPS taught me that the framework is only one part of backend engineering.&lt;/p&gt;

&lt;p&gt;The more important skill is understanding &lt;strong&gt;what your application is actually doing on the machine&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  An API is not just an endpoint
&lt;/h2&gt;

&lt;p&gt;Suppose I have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/projects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;From the outside, it looks simple:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Client&lt;br&gt;
  ↓&lt;br&gt;
HTTP request&lt;br&gt;
  ↓&lt;br&gt;
API response&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But on the server, there is a lot more happening:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Internet&lt;br&gt;
   ↓&lt;br&gt;
Reverse Proxy&lt;br&gt;
   ↓&lt;br&gt;
Application&lt;br&gt;
   ↓&lt;br&gt;
Business Logic&lt;br&gt;
   ↓&lt;br&gt;
Database&lt;br&gt;
   ↓&lt;br&gt;
Response&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If something goes wrong, knowing Express syntax isn't enough.&lt;/p&gt;

&lt;p&gt;I need to know which layer is responsible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the request lifecycle
&lt;/h2&gt;

&lt;p&gt;A useful debugging model is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Request&lt;br&gt;
   ↓&lt;br&gt;
Routing&lt;br&gt;
   ↓&lt;br&gt;
Authentication&lt;br&gt;
   ↓&lt;br&gt;
Validation&lt;br&gt;
   ↓&lt;br&gt;
Business Logic&lt;br&gt;
   ↓&lt;br&gt;
Database&lt;br&gt;
   ↓&lt;br&gt;
Response&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Each step can fail independently.&lt;/p&gt;

&lt;p&gt;For example, if an API returns an unexpected result, I shouldn't immediately assume the database is wrong.&lt;/p&gt;

&lt;p&gt;Maybe:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Route matched incorrectly&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Request body wasn't parsed as expected&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Validation changed the input&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
The database query used the wrong filter&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Tracing the lifecycle is usually faster than randomly changing code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logs should tell a story
&lt;/h2&gt;

&lt;p&gt;A backend log should help answer:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
What request arrived?&lt;br&gt;
What did the application do?&lt;br&gt;
What external operation happened?&lt;br&gt;
What was the result?&lt;br&gt;
Why did the request fail?&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Request: POST /api/transactions&lt;br&gt;
Validation: passed&lt;br&gt;
Database: insert started&lt;br&gt;
Database: insert completed&lt;br&gt;
Response: 201&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Compare that with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Error occurred&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The second message technically tells you something went wrong.&lt;/p&gt;

&lt;p&gt;It doesn't tell you why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't expose internal errors to users
&lt;/h2&gt;

&lt;p&gt;There's also an important distinction between &lt;strong&gt;developer information&lt;/strong&gt; and &lt;strong&gt;client information&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Internally, an error might contain:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Database connection timeout&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But sending the entire internal error to the client isn't always appropriate.&lt;/p&gt;

&lt;p&gt;The API can instead return something like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "error": "Unable to process request"&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;while the server logs contain the technical details needed for debugging.&lt;/p&gt;

&lt;p&gt;That gives you two useful layers:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`text&lt;br&gt;
Client&lt;br&gt;
 ↓&lt;br&gt;
Safe, meaningful error&lt;/p&gt;

&lt;p&gt;Server&lt;br&gt;
 ↓&lt;br&gt;
Detailed diagnostic information&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate at the boundary
&lt;/h2&gt;

&lt;p&gt;One habit that becomes more important as an API grows is validating incoming data before passing it deeper into the system.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Request&lt;br&gt;
 ↓&lt;br&gt;
Business logic&lt;br&gt;
 ↓&lt;br&gt;
Database&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;prefer:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Request&lt;br&gt;
 ↓&lt;br&gt;
Validation&lt;br&gt;
 ↓&lt;br&gt;
Business logic&lt;br&gt;
 ↓&lt;br&gt;
Database&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For example, if an endpoint expects:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "amount": 500&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;the application should not blindly assume that &lt;code&gt;amount&lt;/code&gt; exists and is valid.&lt;/p&gt;

&lt;p&gt;External input should be treated as untrusted.&lt;/p&gt;

&lt;p&gt;That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request bodies&lt;/li&gt;
&lt;li&gt;Query parameters&lt;/li&gt;
&lt;li&gt;Path parameters&lt;/li&gt;
&lt;li&gt;Headers&lt;/li&gt;
&lt;li&gt;Uploaded data&lt;/li&gt;
&lt;li&gt;Third-party API responses&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Database connections are part of backend engineering
&lt;/h2&gt;

&lt;p&gt;An application can have perfectly good business logic and still fail because its database connection isn't healthy.&lt;/p&gt;

&lt;p&gt;The backend therefore needs to account for things like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Connection failures&lt;br&gt;
Timeouts&lt;br&gt;
Unavailable database&lt;br&gt;
Invalid queries&lt;br&gt;
Unexpected data&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is one reason I prefer understanding the entire stack rather than treating the database as something that simply "works."&lt;/p&gt;

&lt;p&gt;The application depends on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment makes backend problems more real
&lt;/h2&gt;

&lt;p&gt;Locally, an application might look like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
localhost&lt;br&gt;
   ↓&lt;br&gt;
Node.js&lt;br&gt;
   ↓&lt;br&gt;
MongoDB&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;On a VPS, the environment becomes more realistic:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Internet&lt;br&gt;
   ↓&lt;br&gt;
DNS&lt;br&gt;
   ↓&lt;br&gt;
Traefik&lt;br&gt;
   ↓&lt;br&gt;
Docker&lt;br&gt;
   ↓&lt;br&gt;
Node.js&lt;br&gt;
   ↓&lt;br&gt;
MongoDB&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now networking, environment variables, TLS, firewall rules, container health, logs, storage, and resource usage all matter.&lt;/p&gt;

&lt;p&gt;This is where backend development starts feeling less like writing endpoints and more like engineering a system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;Building and self-hosting applications has changed how I think about backend development.&lt;/p&gt;

&lt;p&gt;I don't want to only know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do I write this API?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I also want to know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What happens to this request from the moment it enters the server until the response leaves it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That understanding makes debugging easier, architecture decisions clearer, and deployments less mysterious.&lt;/p&gt;

&lt;p&gt;The framework is just one layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend engineering is understanding the entire path that your data takes through the system.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  backend #nodejs #express #mongodb #api #docker #linux #devops #softwareengineering #selfhosting #buildinpublic
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>devops</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>The Hard Part of an API Isn't Calling It</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Thu, 24 Sep 2026 01:38:00 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/the-hard-part-of-an-api-isnt-calling-it-380g</link>
      <guid>https://dev.to/devanshu_patil/the-hard-part-of-an-api-isnt-calling-it-380g</guid>
      <description>&lt;h1&gt;
  
  
  The Hard Part of an API Isn't Calling It
&lt;/h1&gt;

&lt;p&gt;When I started working with APIs, it was easy to think of them as simple plumbing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request → API → Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call an endpoint, parse JSON, display the result.&lt;/p&gt;

&lt;p&gt;That mental model works for a while.&lt;/p&gt;

&lt;p&gt;Then you build software that depends on real APIs, and you realize the difficult part isn't making the HTTP request.&lt;/p&gt;

&lt;p&gt;The difficult part is deciding &lt;strong&gt;what your application should do when the API doesn't behave exactly as expected&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A successful HTTP request doesn't mean successful data
&lt;/h2&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You might receive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request succeeded.&lt;/p&gt;

&lt;p&gt;But what does an empty result mean?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No transactions exist?
Wrong account?
Wrong date range?
Temporary backend issue?
Incorrect query parameters?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTTP request alone doesn't tell you the business meaning.&lt;/p&gt;

&lt;p&gt;Your application needs to interpret the response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat external APIs as boundaries
&lt;/h2&gt;

&lt;p&gt;A useful mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your application
       ↓
API boundary
       ↓
External system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything outside your application is something you don't fully control.&lt;/p&gt;

&lt;p&gt;The API might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return an unexpected status code&lt;/li&gt;
&lt;li&gt;Change a field&lt;/li&gt;
&lt;li&gt;Return missing data&lt;/li&gt;
&lt;li&gt;Take too long&lt;/li&gt;
&lt;li&gt;Fail temporarily&lt;/li&gt;
&lt;li&gt;Return malformed data&lt;/li&gt;
&lt;li&gt;Reject a request&lt;/li&gt;
&lt;li&gt;Return a valid response with unexpected business meaning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the API layer should be treated as a boundary where external data becomes internal application data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't spread API models everywhere
&lt;/h2&gt;

&lt;p&gt;Suppose an API returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"display_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Devanshu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-22T00:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't necessarily want every part of your application to depend directly on that structure.&lt;/p&gt;

&lt;p&gt;A useful approach is to map external data into your own model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API Response
     ↓
DTO
     ↓
Mapper
     ↓
Domain Model
     ↓
Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;UserDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;could become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the rest of the application doesn't need to know how the external API names its fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error handling should have meaning
&lt;/h2&gt;

&lt;p&gt;This is another place where simplistic API handling causes problems.&lt;/p&gt;

&lt;p&gt;You don't want every failure to become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Something went wrong.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different failures can require different behavior.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;401 → Authentication problem
403 → Permission problem
404 → Resource doesn't exist
429 → Rate limit
500 → Server-side failure
Timeout → Network problem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can then decide what makes sense for each case.&lt;/p&gt;

&lt;p&gt;Maybe a timeout should be retried.&lt;/p&gt;

&lt;p&gt;Maybe a 401 should require authentication again.&lt;/p&gt;

&lt;p&gt;Maybe a 404 should show an empty state.&lt;/p&gt;

&lt;p&gt;Maybe a 500 should be surfaced as a temporary error.&lt;/p&gt;

&lt;p&gt;The important part is that the API layer shouldn't throw away useful information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retries are not always harmless
&lt;/h2&gt;

&lt;p&gt;It's tempting to automatically retry every failed request.&lt;/p&gt;

&lt;p&gt;But imagine an endpoint that creates a transaction:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The request reaches the server.&lt;/p&gt;

&lt;p&gt;The server creates the transaction.&lt;/p&gt;

&lt;p&gt;Then the network connection fails before the client receives the response.&lt;/p&gt;

&lt;p&gt;The client sees:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Should it send the request again?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;But if the operation isn't idempotent, you might create the transaction twice.&lt;/p&gt;

&lt;p&gt;This is why retries need to be designed around the operation.&lt;/p&gt;

&lt;p&gt;A read request and a money-moving operation shouldn't necessarily have the same retry strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeouts are part of API design
&lt;/h2&gt;

&lt;p&gt;A request without a sensible timeout can leave the application waiting unnecessarily.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
  ↓
Waiting...
  ↓
Waiting...
  ↓
Waiting...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user doesn't care that the TCP connection technically still exists.&lt;/p&gt;

&lt;p&gt;They care that the application appears stuck.&lt;/p&gt;

&lt;p&gt;A timeout gives the system a defined failure state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
  ↓
Timeout
  ↓
Handle failure
  ↓
Retry / cached data / error UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failure is easier to manage when it has boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API should not define your entire architecture
&lt;/h2&gt;

&lt;p&gt;This is probably the biggest lesson.&lt;/p&gt;

&lt;p&gt;If your application structure mirrors an external API too closely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API
 ↓
Everything else
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then an API change can ripple through the entire codebase.&lt;/p&gt;

&lt;p&gt;Instead, I prefer thinking in terms of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;External API
     ↓
Integration layer
     ↓
Application model
     ↓
Business logic
     ↓
UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The external API is an input to the system.&lt;/p&gt;

&lt;p&gt;It shouldn't automatically become the system's architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm learning
&lt;/h2&gt;

&lt;p&gt;Working with APIs has changed the way I think about network code.&lt;/p&gt;

&lt;p&gt;The important question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do I call this endpoint?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How does my application safely depend on a system I don't control?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question leads to better decisions around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DTOs&lt;/li&gt;
&lt;li&gt;Mapping&lt;/li&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;li&gt;Timeouts&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Caching&lt;/li&gt;
&lt;li&gt;Idempotency&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The HTTP request is usually the easy part.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The engineering starts after you realize the response isn't guaranteed to be exactly what you wanted.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  api #backend #kotlin #android #softwarearchitecture #webdevelopment #systemdesign #programming #softwareengineering #buildinpublic
&lt;/h1&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Self-Hosting Taught Me That Deployment Is Part of Development</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Wed, 23 Sep 2026 13:37:15 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/self-hosting-taught-me-that-deployment-is-part-of-development-23dg</link>
      <guid>https://dev.to/devanshu_patil/self-hosting-taught-me-that-deployment-is-part-of-development-23dg</guid>
      <description>&lt;h1&gt;
  
  
  Self-Hosting Taught Me That Deployment Is Part of Development
&lt;/h1&gt;

&lt;p&gt;When I first thought about deploying an application, I mostly thought about getting the application onto a server.&lt;/p&gt;

&lt;p&gt;Build the project.&lt;/p&gt;

&lt;p&gt;Start the process.&lt;/p&gt;

&lt;p&gt;Point the domain to it.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;Running applications on my own VPS changed that perspective.&lt;/p&gt;

&lt;p&gt;Deployment isn't the final step after development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment is another part of the system you are building.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My deployment stack is intentionally simple
&lt;/h2&gt;

&lt;p&gt;For my self-hosted applications, I've worked with an Oracle Cloud Always Free VPS, Docker, Coolify, Traefik, MongoDB, HTTPS, and Linux server configuration.&lt;/p&gt;

&lt;p&gt;The important thing isn't the specific tools.&lt;/p&gt;

&lt;p&gt;It's the chain connecting them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet
   ↓
Domain
   ↓
DNS
   ↓
VPS
   ↓
Traefik
   ↓
Docker container
   ↓
Application
   ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;When everything works, this feels invisible.&lt;/p&gt;

&lt;p&gt;When something breaks, every layer matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  A deployment is a system
&lt;/h2&gt;

&lt;p&gt;Suppose my application isn't reachable.&lt;/p&gt;

&lt;p&gt;There are many possible causes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
DNS problem&lt;br&gt;
   ↓&lt;br&gt;
Firewall problem&lt;br&gt;
   ↓&lt;br&gt;
Reverse proxy problem&lt;br&gt;
   ↓&lt;br&gt;
Container problem&lt;br&gt;
   ↓&lt;br&gt;
Application problem&lt;br&gt;
   ↓&lt;br&gt;
Database problem&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If I only know how to start the application, I can still be completely stuck.&lt;/p&gt;

&lt;p&gt;That's why self-hosting has been useful for learning.&lt;/p&gt;

&lt;p&gt;It forces me to understand what happens outside the application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverse proxies are more than "configuration"
&lt;/h2&gt;

&lt;p&gt;A reverse proxy sits between the public internet and the application.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Client&lt;br&gt;
  ↓&lt;br&gt;
HTTPS&lt;br&gt;
  ↓&lt;br&gt;
Traefik&lt;br&gt;
  ↓&lt;br&gt;
Application container&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It can handle things such as routing traffic to the correct service and terminating HTTPS.&lt;/p&gt;

&lt;p&gt;This becomes especially useful when multiple applications need to run on the same server.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
app.example.com&lt;br&gt;
api.example.com&lt;br&gt;
admin.example.com&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;can all point toward the same VPS while the reverse proxy decides where the traffic should go.&lt;/p&gt;

&lt;p&gt;The application doesn't need to expose every service directly to the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Containers solve one problem, not everything
&lt;/h2&gt;

&lt;p&gt;Docker makes application deployment much more reproducible.&lt;/p&gt;

&lt;p&gt;Instead of manually installing every dependency on the server, I can package the application environment into a container.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Application&lt;br&gt;
+&lt;br&gt;
Dependencies&lt;br&gt;
+&lt;br&gt;
Runtime&lt;br&gt;
      ↓&lt;br&gt;
Container&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But containers don't magically solve operational problems.&lt;/p&gt;

&lt;p&gt;You still need to think about:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Logs&lt;br&gt;
Storage&lt;br&gt;
Networking&lt;br&gt;
Environment variables&lt;br&gt;
Backups&lt;br&gt;
Updates&lt;br&gt;
Security&lt;br&gt;
Resource usage&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A container can make deployment easier while still leaving you with a badly designed deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linux knowledge becomes practical very quickly
&lt;/h2&gt;

&lt;p&gt;Self-hosting also made Linux commands much more useful to me.&lt;/p&gt;

&lt;p&gt;Instead of learning commands only because they appear in tutorials, there is now a reason behind them.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
free -h&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;helps inspect memory.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
df -h&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;helps inspect disk usage.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
uptime&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;gives a quick view of system load and uptime.&lt;/p&gt;

&lt;p&gt;And process/container logs can tell you what the application is actually doing.&lt;/p&gt;

&lt;p&gt;The important skill isn't memorizing commands.&lt;/p&gt;

&lt;p&gt;It's knowing &lt;strong&gt;which question each command helps answer&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring changes how you think
&lt;/h2&gt;

&lt;p&gt;A server can be "working" while slowly heading toward a problem.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Disk usage increasing&lt;br&gt;
        ↓&lt;br&gt;
Logs accumulate&lt;br&gt;
        ↓&lt;br&gt;
Available storage decreases&lt;br&gt;
        ↓&lt;br&gt;
Eventually something fails&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The application might appear perfectly healthy until the moment it isn't.&lt;/p&gt;

&lt;p&gt;This is why monitoring resources such as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
CPU&lt;br&gt;
RAM&lt;br&gt;
Disk&lt;br&gt;
Network&lt;br&gt;
Container health&lt;br&gt;
Application logs&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;matters even for small projects.&lt;/p&gt;

&lt;p&gt;You don't need a massive observability platform to start paying attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable part of self-hosting
&lt;/h2&gt;

&lt;p&gt;Self-hosting gives you control.&lt;/p&gt;

&lt;p&gt;It also gives you responsibility.&lt;/p&gt;

&lt;p&gt;If you use a managed platform, many infrastructure problems are someone else's responsibility.&lt;/p&gt;

&lt;p&gt;On your own VPS, they aren't.&lt;/p&gt;

&lt;p&gt;You become responsible for understanding:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Updates&lt;br&gt;
Security&lt;br&gt;
Firewall rules&lt;br&gt;
Backups&lt;br&gt;
SSL&lt;br&gt;
Resource limits&lt;br&gt;
Database availability&lt;br&gt;
Application failures&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's not a downside if your goal is to learn infrastructure.&lt;/p&gt;

&lt;p&gt;But pretending self-hosting is just "free hosting" misses the point.&lt;/p&gt;

&lt;p&gt;The infrastructure becomes your problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;The biggest thing I have learned from deploying my own applications is that the code is only one part of the product.&lt;/p&gt;

&lt;p&gt;A working application needs:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Code&lt;br&gt;
+&lt;br&gt;
Database&lt;br&gt;
+&lt;br&gt;
Networking&lt;br&gt;
+&lt;br&gt;
Infrastructure&lt;br&gt;
+&lt;br&gt;
Security&lt;br&gt;
+&lt;br&gt;
Deployment&lt;br&gt;
+&lt;br&gt;
Monitoring&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You don't need to master all of these before deploying your first project.&lt;/p&gt;

&lt;p&gt;But you should understand enough of each layer to troubleshoot when something inevitably goes wrong.&lt;/p&gt;

&lt;p&gt;That's what makes deployment engineering rather than simply uploading a project to a server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The moment your application is running on a real machine connected to the internet, infrastructure becomes part of your codebase—even if it isn't written in your programming language.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  devops #linux #vps #selfhosting #docker #coolify #traefik #deployment #backend #softwareengineering
&lt;/h1&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>infrastructure</category>
      <category>linux</category>
    </item>
    <item>
      <title>Debugging Got Easier When I Stopped Guessing</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Tue, 22 Sep 2026 05:49:00 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/debugging-got-easier-when-i-stopped-guessing-3ajc</link>
      <guid>https://dev.to/devanshu_patil/debugging-got-easier-when-i-stopped-guessing-3ajc</guid>
      <description>&lt;h1&gt;
  
  
  Debugging Got Easier When I Stopped Guessing
&lt;/h1&gt;

&lt;p&gt;One of the biggest mistakes I make while debugging is trying to fix the problem before understanding it.&lt;/p&gt;

&lt;p&gt;A screen doesn't update.&lt;/p&gt;

&lt;p&gt;An API returns unexpected data.&lt;/p&gt;

&lt;p&gt;A database query gives the wrong result.&lt;/p&gt;

&lt;p&gt;A deployment fails.&lt;/p&gt;

&lt;p&gt;The instinct is immediately:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What code should I change?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've found a better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What exactly do I know is failing?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That small change in mindset makes debugging much more systematic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the symptom
&lt;/h2&gt;

&lt;p&gt;Suppose an Android screen isn't showing a transaction.&lt;/p&gt;

&lt;p&gt;There are several possible failures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User action
    ↓
ViewModel
    ↓
Repository
    ↓
Room
    ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;The UI might be the problem.&lt;/p&gt;

&lt;p&gt;But it could also be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The event never reached the ViewModel&lt;/li&gt;
&lt;li&gt;The ViewModel didn't call the repository&lt;/li&gt;
&lt;li&gt;The repository queried the wrong data&lt;/li&gt;
&lt;li&gt;The database contains unexpected data&lt;/li&gt;
&lt;li&gt;The state wasn't updated&lt;/li&gt;
&lt;li&gt;The Compose UI isn't observing the expected state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looking only at the screen can send you in completely the wrong direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow the data
&lt;/h2&gt;

&lt;p&gt;Instead of changing code randomly, I try to trace the value through the system.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Expected transaction&lt;br&gt;
        ↓&lt;br&gt;
Was the user action triggered?&lt;br&gt;
        ↓&lt;br&gt;
Was the ViewModel called?&lt;br&gt;
        ↓&lt;br&gt;
Was the repository called?&lt;br&gt;
        ↓&lt;br&gt;
Did the database query return the record?&lt;br&gt;
        ↓&lt;br&gt;
Did the state contain the record?&lt;br&gt;
        ↓&lt;br&gt;
Did the UI receive that state?&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;At some point, the expected behavior diverges from the actual behavior.&lt;/p&gt;

&lt;p&gt;That's where I want to investigate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logs should answer questions
&lt;/h2&gt;

&lt;p&gt;Adding logs everywhere isn't debugging.&lt;/p&gt;

&lt;p&gt;Useful logs answer specific questions.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Fetching transactions for personId = 42&lt;br&gt;
Query returned 3 transactions&lt;br&gt;
Calculated balance = ₹2500&lt;br&gt;
Updating UI state&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now the logs tell you something about the execution path.&lt;/p&gt;

&lt;p&gt;Compare that with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
DEBUG: function called&lt;br&gt;
DEBUG: something happened&lt;br&gt;
DEBUG: result&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Those logs create noise without explaining the system.&lt;/p&gt;

&lt;p&gt;The goal isn't to have more logs.&lt;/p&gt;

&lt;p&gt;It's to have &lt;strong&gt;useful evidence&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce before fixing
&lt;/h2&gt;

&lt;p&gt;If a bug happens only sometimes, the first objective should be making it reproducible.&lt;/p&gt;

&lt;p&gt;Try to identify:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`text&lt;br&gt;
Input&lt;br&gt;
+&lt;br&gt;
Sequence of actions&lt;br&gt;
+&lt;/p&gt;

&lt;h1&gt;
  
  
  Application state
&lt;/h1&gt;

&lt;p&gt;Bug&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Open transaction&lt;br&gt;
→ Edit amount&lt;br&gt;
→ Save&lt;br&gt;
→ Navigate back&lt;br&gt;
→ Open transaction again&lt;br&gt;
→ Incorrect value appears&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now you have a scenario you can test repeatedly.&lt;/p&gt;

&lt;p&gt;Without reproducibility, you can easily "fix" the wrong problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check assumptions
&lt;/h2&gt;

&lt;p&gt;A surprisingly large number of bugs come from assumptions.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This ID can never be null."&lt;/p&gt;

&lt;p&gt;"This API always returns this field."&lt;/p&gt;

&lt;p&gt;"This query always returns one record."&lt;/p&gt;

&lt;p&gt;"The database must contain the correct value."&lt;/p&gt;

&lt;p&gt;"The code definitely reached this function."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of trusting the assumption, verify it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Assumption&lt;br&gt;
    ↓&lt;br&gt;
Evidence&lt;br&gt;
    ↓&lt;br&gt;
Confirmed / disproved&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is especially important when debugging systems with multiple layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't change five things at once
&lt;/h2&gt;

&lt;p&gt;If I change:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Query&lt;br&gt;
+&lt;br&gt;
ViewModel&lt;br&gt;
+&lt;br&gt;
UI state&lt;br&gt;
+&lt;br&gt;
Database schema&lt;br&gt;
+&lt;br&gt;
API response&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and the bug disappears, I still don't know what actually fixed it.&lt;/p&gt;

&lt;p&gt;Worse, I might have introduced another bug.&lt;/p&gt;

&lt;p&gt;A better approach is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Hypothesis&lt;br&gt;
    ↓&lt;br&gt;
Small change / test&lt;br&gt;
    ↓&lt;br&gt;
Observe result&lt;br&gt;
    ↓&lt;br&gt;
Update hypothesis&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Debugging becomes an investigation rather than a guessing game.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same method works outside Android
&lt;/h2&gt;

&lt;p&gt;This approach isn't specific to Compose or Kotlin.&lt;/p&gt;

&lt;p&gt;For a backend request:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Client&lt;br&gt;
 ↓&lt;br&gt;
Reverse proxy&lt;br&gt;
 ↓&lt;br&gt;
API&lt;br&gt;
 ↓&lt;br&gt;
Business logic&lt;br&gt;
 ↓&lt;br&gt;
Database&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For a VPS deployment:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
DNS&lt;br&gt;
 ↓&lt;br&gt;
Server&lt;br&gt;
 ↓&lt;br&gt;
Reverse proxy&lt;br&gt;
 ↓&lt;br&gt;
Container&lt;br&gt;
 ↓&lt;br&gt;
Application&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For every system, the same question applies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Where does the actual behavior first differ from the expected behavior?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Find that point.&lt;/p&gt;

&lt;p&gt;Then investigate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson I'm taking from this
&lt;/h2&gt;

&lt;p&gt;Debugging isn't primarily about knowing more fixes.&lt;/p&gt;

&lt;p&gt;It's about reducing uncertainty.&lt;/p&gt;

&lt;p&gt;At the beginning:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Something is broken.&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After investigation:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
The request reaches the API.&lt;br&gt;
The API returns the correct data.&lt;br&gt;
The repository receives it.&lt;br&gt;
The ViewModel state is correct.&lt;br&gt;
The UI is displaying stale state.&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now the problem is much smaller.&lt;/p&gt;

&lt;p&gt;And once the problem is smaller, the fix is usually much easier to find.&lt;/p&gt;

&lt;p&gt;**Don't debug by changing code until something works.&lt;/p&gt;

&lt;p&gt;Debug by collecting evidence until you know what is wrong.**&lt;/p&gt;

&lt;h1&gt;
  
  
  debugging #android #kotlin #softwareengineering #backend #programming #jetpackcompose #development #buildinpublic
&lt;/h1&gt;

</description>
      <category>debugging</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Why I Keep My Database Layer Boring</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Sun, 20 Sep 2026 18:42:00 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/why-i-keep-my-database-layer-boring-4o83</link>
      <guid>https://dev.to/devanshu_patil/why-i-keep-my-database-layer-boring-4o83</guid>
      <description>&lt;h1&gt;
  
  
  Why I Keep My Database Layer Boring
&lt;/h1&gt;

&lt;p&gt;When building an application, it's easy to get excited about the visible parts.&lt;/p&gt;

&lt;p&gt;A new Compose screen.&lt;/p&gt;

&lt;p&gt;A better API.&lt;/p&gt;

&lt;p&gt;A new feature.&lt;/p&gt;

&lt;p&gt;A cleaner dashboard.&lt;/p&gt;

&lt;p&gt;The database usually gets less attention.&lt;/p&gt;

&lt;p&gt;But while working on FinLedger, I've found that the database layer is one of the places where boring engineering is actually a good thing.&lt;/p&gt;

&lt;p&gt;For a finance application, I don't want the database to be clever.&lt;/p&gt;

&lt;p&gt;I want it to be predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the data model
&lt;/h2&gt;

&lt;p&gt;Before writing queries, I need to understand what I'm actually storing.&lt;/p&gt;

&lt;p&gt;For example, a transaction isn't just:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;It can have relationships with other parts of the application:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Transaction&lt;br&gt;
 ├── amount&lt;br&gt;
 ├── date&lt;br&gt;
 ├── type&lt;br&gt;
 ├── category/tag&lt;br&gt;
 ├── person&lt;br&gt;
 └── other metadata&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Those relationships matter.&lt;/p&gt;

&lt;p&gt;If the data model is unclear, the application code eventually becomes full of special cases trying to compensate for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep persistence responsibilities clear
&lt;/h2&gt;

&lt;p&gt;A useful separation looks like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
UI&lt;br&gt;
 ↓&lt;br&gt;
ViewModel&lt;br&gt;
 ↓&lt;br&gt;
Repository&lt;br&gt;
 ↓&lt;br&gt;
DAO&lt;br&gt;
 ↓&lt;br&gt;
Room&lt;br&gt;
 ↓&lt;br&gt;
SQLite&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The UI shouldn't need to know SQL.&lt;/p&gt;

&lt;p&gt;The DAO shouldn't decide business rules.&lt;/p&gt;

&lt;p&gt;The repository shouldn't become a second UI layer.&lt;/p&gt;

&lt;p&gt;Each component should have a reasonably narrow responsibility.&lt;/p&gt;

&lt;p&gt;For example, a DAO might answer:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;kotlin&lt;br&gt;
@Query("SELECT * FROM transactions ORDER BY date DESC")&lt;br&gt;
fun observeTransactions(): Flow&amp;lt;List&amp;lt;Transaction&amp;gt;&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Its job is retrieving data.&lt;/p&gt;

&lt;p&gt;It shouldn't also decide how the dashboard calculates financial insights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database constraints are useful
&lt;/h2&gt;

&lt;p&gt;It's tempting to rely entirely on application code for validation.&lt;/p&gt;

&lt;p&gt;But the database can enforce important assumptions too.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
amount cannot be NULL&lt;br&gt;
required relationship must exist&lt;br&gt;
identifier must be unique&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Why duplicate validation?&lt;/p&gt;

&lt;p&gt;Because different parts of an application can eventually write data.&lt;/p&gt;

&lt;p&gt;A database constraint provides a final line of defense.&lt;/p&gt;

&lt;p&gt;Application validation improves the user experience.&lt;/p&gt;

&lt;p&gt;Database constraints protect the integrity of stored data.&lt;/p&gt;

&lt;p&gt;They're solving different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't make every query generic
&lt;/h2&gt;

&lt;p&gt;Another trap is trying to build a universal repository that can do everything.&lt;/p&gt;

&lt;p&gt;Something like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
save()&lt;br&gt;
update()&lt;br&gt;
delete()&lt;br&gt;
find()&lt;br&gt;
query()&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;can look clean at first.&lt;/p&gt;

&lt;p&gt;But eventually the repository becomes an abstraction that hides what the application is actually doing.&lt;/p&gt;

&lt;p&gt;A specific operation can sometimes be clearer:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
getTransactionsForMonth()&lt;br&gt;
getTransactionsForPerson()&lt;br&gt;
getTransactionsByType()&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The database layer should make important data access patterns understandable.&lt;/p&gt;

&lt;p&gt;Abstraction is useful when it removes meaningful complexity.&lt;/p&gt;

&lt;p&gt;If it only hides a simple query behind five interfaces, it may be making the code harder to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observe data instead of manually refreshing everything
&lt;/h2&gt;

&lt;p&gt;One thing I like about using Room with Kotlin is the ability to work with observable data.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Database changes&lt;br&gt;
      ↓&lt;br&gt;
Flow emits new data&lt;br&gt;
      ↓&lt;br&gt;
ViewModel receives state&lt;br&gt;
      ↓&lt;br&gt;
Compose recomposes&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That is cleaner than having every screen manually ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Did the database change?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The data source can become the trigger for updating the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance still matters
&lt;/h2&gt;

&lt;p&gt;A database can work correctly and still become a performance problem.&lt;/p&gt;

&lt;p&gt;For example, fetching a huge dataset when the screen only needs the latest records is wasteful.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Load everything&lt;br&gt;
 ↓&lt;br&gt;
Filter in application&lt;br&gt;
 ↓&lt;br&gt;
Display 20 records&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;you often want:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Query only what you need&lt;br&gt;
 ↓&lt;br&gt;
Return relevant records&lt;br&gt;
 ↓&lt;br&gt;
Display them&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The database is good at filtering data.&lt;/p&gt;

&lt;p&gt;Use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boring database principle
&lt;/h2&gt;

&lt;p&gt;The more important the data, the less interested I am in clever persistence logic.&lt;/p&gt;

&lt;p&gt;For a financial application, I want:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Predictable schema&lt;br&gt;
Clear relationships&lt;br&gt;
Explicit queries&lt;br&gt;
Useful constraints&lt;br&gt;
Observable data&lt;br&gt;
Simple migrations&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Magic abstraction&lt;br&gt;
Hidden side effects&lt;br&gt;
Queries nobody understands&lt;br&gt;
Business rules scattered across DAOs&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The database should be something I can inspect and reason about when something goes wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger lesson
&lt;/h2&gt;

&lt;p&gt;I've started thinking about database code differently.&lt;/p&gt;

&lt;p&gt;The goal isn't to write the most sophisticated persistence layer.&lt;/p&gt;

&lt;p&gt;The goal is to make it &lt;strong&gt;hard to accidentally corrupt the application's data model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's especially important for software where the data represents something users care about.&lt;/p&gt;

&lt;p&gt;Fancy architecture is useful when it solves a real problem.&lt;/p&gt;

&lt;p&gt;But for the foundation of an application, boring is often a feature.&lt;/p&gt;

&lt;p&gt;**Make the database predictable.&lt;/p&gt;

&lt;p&gt;Make the queries understandable.&lt;/p&gt;

&lt;p&gt;Make the data difficult to corrupt.&lt;/p&gt;

&lt;p&gt;Then build the interesting features on top of it.**&lt;/p&gt;

&lt;h1&gt;
  
  
  android #kotlin #room #database #sqlite #softwarearchitecture #jetpackcompose #softwareengineering #buildinpublic
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>database</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>A Fast VPS Is Useless If You Can't Explain Why It's Slow</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Sun, 20 Sep 2026 15:12:00 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/a-fast-vps-is-useless-if-you-cant-explain-why-its-slow-40jo</link>
      <guid>https://dev.to/devanshu_patil/a-fast-vps-is-useless-if-you-cant-explain-why-its-slow-40jo</guid>
      <description>&lt;h1&gt;
  
  
  A Fast VPS Is Useless If You Can't Explain Why It's Slow
&lt;/h1&gt;

&lt;p&gt;One of the most useful habits I've picked up from running applications on my own VPS is this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't optimize based on feelings. Measure the request.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a website feels slow, it's tempting to immediately blame the server.&lt;/p&gt;

&lt;p&gt;Maybe the VPS needs more RAM.&lt;/p&gt;

&lt;p&gt;Maybe the CPU is too slow.&lt;/p&gt;

&lt;p&gt;Maybe Docker is the problem.&lt;/p&gt;

&lt;p&gt;Maybe the database is slow.&lt;/p&gt;

&lt;p&gt;But "the website is slow" isn't a diagnosis.&lt;/p&gt;

&lt;p&gt;It's a symptom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Break the request into pieces
&lt;/h2&gt;

&lt;p&gt;A web request isn't one operation.&lt;/p&gt;

&lt;p&gt;Conceptually, it looks more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   ↓
DNS lookup
   ↓
TCP connection
   ↓
TLS handshake
   ↓
Server / reverse proxy
   ↓
Application
   ↓
Database
   ↓
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;If the total response time is high, the first job is finding which part is responsible.&lt;/p&gt;

&lt;p&gt;For example, if the request takes 800 ms:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
DNS       → 20 ms&lt;br&gt;
Connect   → 30 ms&lt;br&gt;
TLS       → 80 ms&lt;br&gt;
Server    → 650 ms&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then replacing your VPS probably isn't the first thing you should do.&lt;/p&gt;

&lt;p&gt;The application or database deserves investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  TTFB is useful
&lt;/h2&gt;

&lt;p&gt;One metric I pay attention to is &lt;strong&gt;Time To First Byte (TTFB)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It gives you a useful indication of how long it takes before the server starts responding.&lt;/p&gt;

&lt;p&gt;You can use tools such as &lt;code&gt;curl&lt;/code&gt; to inspect different parts of the request.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
curl -o /dev/null -s \&lt;br&gt;
  -w "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \&lt;br&gt;
  https://example.com&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The exact numbers aren't as important as the pattern.&lt;/p&gt;

&lt;p&gt;If DNS is taking most of the time, investigate DNS.&lt;/p&gt;

&lt;p&gt;If TLS dominates, investigate the connection path.&lt;/p&gt;

&lt;p&gt;If TTFB is high, look closer at the server-side work.&lt;/p&gt;

&lt;p&gt;If everything is fast until the browser renders the page, the bottleneck may be somewhere else entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't confuse server resources with application performance
&lt;/h2&gt;

&lt;p&gt;A server can have plenty of CPU and RAM and still serve a slow application.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
VPS&lt;br&gt;
 ├── CPU: plenty available&lt;br&gt;
 ├── RAM: plenty available&lt;br&gt;
 └── Application: slow database query&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Adding more resources doesn't necessarily fix the underlying problem.&lt;/p&gt;

&lt;p&gt;The same applies in reverse.&lt;/p&gt;

&lt;p&gt;If the server is genuinely overloaded, optimizing one SQL query won't solve the infrastructure bottleneck.&lt;/p&gt;

&lt;p&gt;You need evidence before deciding which layer deserves attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure before changing architecture
&lt;/h2&gt;

&lt;p&gt;This is especially important when self-hosting.&lt;/p&gt;

&lt;p&gt;It's easy to jump from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"My application is slow."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I need Kubernetes."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You probably don't.&lt;/p&gt;

&lt;p&gt;At least, you don't know that yet.&lt;/p&gt;

&lt;p&gt;Start with simpler questions:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Is the CPU saturated?&lt;br&gt;
Is memory under pressure?&lt;br&gt;
Is swap being used heavily?&lt;br&gt;
Is the database slow?&lt;br&gt;
Is the application blocking?&lt;br&gt;
Is the network slow?&lt;br&gt;
Is the reverse proxy introducing latency?&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then measure.&lt;/p&gt;

&lt;p&gt;Only after that should you consider architectural changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple infrastructure makes debugging easier
&lt;/h2&gt;

&lt;p&gt;One reason I like running relatively straightforward deployments is that the system remains understandable.&lt;/p&gt;

&lt;p&gt;A request might pass through:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Internet&lt;br&gt;
   ↓&lt;br&gt;
Traefik&lt;br&gt;
   ↓&lt;br&gt;
Docker container&lt;br&gt;
   ↓&lt;br&gt;
Application&lt;br&gt;
   ↓&lt;br&gt;
Database&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If something breaks, there are a limited number of places to investigate.&lt;/p&gt;

&lt;p&gt;That's much better than adding infrastructure simply because it sounds more professional.&lt;/p&gt;

&lt;p&gt;Complexity has a cost.&lt;/p&gt;

&lt;p&gt;Every additional component becomes another thing you need to monitor, configure, secure, upgrade, and debug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;Performance engineering isn't:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Slow&lt;br&gt;
 ↓&lt;br&gt;
Buy bigger server&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Slow&lt;br&gt;
 ↓&lt;br&gt;
Measure&lt;br&gt;
 ↓&lt;br&gt;
Locate bottleneck&lt;br&gt;
 ↓&lt;br&gt;
Understand cause&lt;br&gt;
 ↓&lt;br&gt;
Optimize&lt;br&gt;
 ↓&lt;br&gt;
Measure again&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's a much more reliable process.&lt;/p&gt;

&lt;p&gt;Running applications on a VPS has made this especially obvious to me.&lt;/p&gt;

&lt;p&gt;You don't need an enormous infrastructure stack to learn performance engineering.&lt;/p&gt;

&lt;p&gt;Sometimes a VPS, Linux tools, application logs, and a few well-chosen measurements are enough to teach you where your system is actually spending its time.&lt;/p&gt;

&lt;p&gt;**Don't optimize the server you imagine you have.&lt;/p&gt;

&lt;p&gt;Optimize the bottleneck you can prove exists.**&lt;/p&gt;

&lt;h1&gt;
  
  
  linux #vps #selfhosting #devops #performance #backend #docker #webdevelopment #softwareengineering
&lt;/h1&gt;

</description>
      <category>debugging</category>
      <category>devops</category>
      <category>monitoring</category>
      <category>performance</category>
    </item>
    <item>
      <title>Parsing SMS Transactions Is a Data Quality Problem, Not Just a Regex Problem</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Sat, 19 Sep 2026 14:11:05 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/parsing-sms-transactions-is-a-data-quality-problem-not-just-a-regex-problem-3dpo</link>
      <guid>https://dev.to/devanshu_patil/parsing-sms-transactions-is-a-data-quality-problem-not-just-a-regex-problem-3dpo</guid>
      <description>&lt;h1&gt;
  
  
  Parsing SMS Transactions Is a Data Quality Problem, Not Just a Regex Problem
&lt;/h1&gt;

&lt;p&gt;One of the more interesting parts of building FinLedger has been working with transaction data that doesn't arrive in a clean format.&lt;/p&gt;

&lt;p&gt;When transaction information comes from SMS messages, the application isn't receiving a nice API response like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"merchant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example Store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DEBIT"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, it may receive human-readable text.&lt;/p&gt;

&lt;p&gt;That changes the problem completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The input is messy
&lt;/h2&gt;

&lt;p&gt;A transaction message might conceptually contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your account has been debited by Rs. 500
at Example Store.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another message from a different bank may use completely different wording.&lt;/p&gt;

&lt;p&gt;So the parser has to identify useful information from text rather than simply reading predefined fields.&lt;/p&gt;

&lt;p&gt;The pipeline becomes something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SMS
 ↓
Parser
 ↓
Extracted transaction data
 ↓
Validation
 ↓
Transaction model
 ↓
Local database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that &lt;strong&gt;parsing and accepting data are not the same thing&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regex is useful, but it isn't the whole solution
&lt;/h2&gt;

&lt;p&gt;Regular expressions can be useful for extracting predictable pieces of information.&lt;/p&gt;

&lt;p&gt;For example, an amount might follow a pattern such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rs. 500
INR 500.00
₹500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A parser can look for these patterns.&lt;/p&gt;

&lt;p&gt;But extracting:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;doesn't prove that the message represents a valid transaction.&lt;/p&gt;

&lt;p&gt;You still need to answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is this actually a financial transaction?
Is it a debit or credit?
Can the amount be trusted?
Was the transaction already processed?
Can the date be determined?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Extraction gives you candidate data.&lt;/p&gt;

&lt;p&gt;Validation decides whether that data should enter your accounting system.&lt;/p&gt;

&lt;h2&gt;
  
  
  False positives are dangerous
&lt;/h2&gt;

&lt;p&gt;For a finance application, an incorrect transaction can be worse than a missed transaction.&lt;/p&gt;

&lt;p&gt;Imagine a parser incorrectly interprets a promotional SMS as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expense = ₹500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application has now corrupted the user's financial history.&lt;/p&gt;

&lt;p&gt;That's why automated parsing should be conservative.&lt;/p&gt;

&lt;p&gt;A useful mindset is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unknown
   ↓
Try to parse
   ↓
Validate
   ↓
Accept only if sufficiently reliable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't force every message into a transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotency matters too
&lt;/h2&gt;

&lt;p&gt;Automation creates another problem: the same message might be processed more than once.&lt;/p&gt;

&lt;p&gt;If the application sees the same transaction twice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SMS
 ↓
Parse
 ↓
Create transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and later processes it again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Same SMS
 ↓
Parse
 ↓
Create another transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the user's balance can become incorrect.&lt;/p&gt;

&lt;p&gt;So automated ingestion needs some way to recognize that a transaction has already been processed.&lt;/p&gt;

&lt;p&gt;The general principle is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Processing the same input twice should not accidentally create two financial events.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the same kind of problem you'll encounter in backend systems, payment processing, message queues, and distributed systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Store useful raw information
&lt;/h2&gt;

&lt;p&gt;Another useful design consideration is keeping enough information to investigate parsing decisions.&lt;/p&gt;

&lt;p&gt;Instead of only storing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amount = 500
type = DEBIT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the system may benefit from retaining appropriate source information or metadata that allows the application to understand where the transaction came from.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because when parsing goes wrong, you need evidence.&lt;/p&gt;

&lt;p&gt;Without the original context, debugging becomes much harder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing should be treated as an evolving system
&lt;/h2&gt;

&lt;p&gt;Bank and payment messages aren't necessarily designed as a stable API contract for your application.&lt;/p&gt;

&lt;p&gt;Formats can differ between institutions and can change over time.&lt;/p&gt;

&lt;p&gt;That means a parser shouldn't be treated as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write regex once
↓
Never touch it again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A better mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input formats
     ↓
Detection
     ↓
Extraction
     ↓
Normalization
     ↓
Validation
     ↓
Transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage has a responsibility.&lt;/p&gt;

&lt;p&gt;That makes failures easier to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger lesson
&lt;/h2&gt;

&lt;p&gt;Building an automated transaction parser has reinforced something I keep seeing across software engineering:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-world data is rarely as clean as the data in your database schema.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The database might expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amount
type
date
merchant
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the outside world gives you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unstructured text
different formats
missing fields
unexpected wording
duplicate events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engineering challenge is building a reliable boundary between those two worlds.&lt;/p&gt;

&lt;p&gt;And that principle applies far beyond SMS parsing.&lt;/p&gt;

&lt;p&gt;APIs can change.&lt;/p&gt;

&lt;p&gt;User input can be malformed.&lt;/p&gt;

&lt;p&gt;Third-party integrations can behave unexpectedly.&lt;/p&gt;

&lt;p&gt;Files can have inconsistent formats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your application should treat external data as untrusted input and make it trustworthy before building business logic on top of it.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  android #kotlin #fintech #softwareengineering #parsing #databases #architecture #jetpackcompose #buildinpublic
&lt;/h1&gt;

</description>
      <category>backend</category>
      <category>data</category>
      <category>fintech</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Offline-First Doesn't Mean "No Internet"</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Wed, 16 Sep 2026 02:45:44 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/offline-first-doesnt-mean-no-internet-4a0p</link>
      <guid>https://dev.to/devanshu_patil/offline-first-doesnt-mean-no-internet-4a0p</guid>
      <description>&lt;h1&gt;
  
  
  Offline-First Doesn't Mean "No Internet"
&lt;/h1&gt;

&lt;p&gt;One architectural decision I've found particularly interesting while building FinLedger is the idea of making the application &lt;strong&gt;offline-first&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At first, "offline-first" can sound like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The app should work without the internet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's true, but it's not the complete idea.&lt;/p&gt;

&lt;p&gt;The more important question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What should the application consider its source of truth when the network is unavailable?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a personal finance application, I think this matters a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The database should not depend on the network
&lt;/h2&gt;

&lt;p&gt;If I record a transaction, I don't want the core operation to depend on an API being available.&lt;/p&gt;

&lt;p&gt;A useful flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User action
    ↓
Local database
    ↓
UI updates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;The network becomes an additional capability rather than a requirement for basic functionality.&lt;/p&gt;

&lt;p&gt;This is one reason local persistence is so important in an offline-first architecture.&lt;/p&gt;

&lt;p&gt;With FinLedger, the application is designed around local data and uses Android technologies such as Kotlin, Jetpack Compose, Room, and WorkManager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Think about the source of truth
&lt;/h2&gt;

&lt;p&gt;Imagine I add:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Expense: ₹500&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The UI shouldn't need to wait for:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Internet&lt;br&gt;
   ↓&lt;br&gt;
API&lt;br&gt;
   ↓&lt;br&gt;
Server&lt;br&gt;
   ↓&lt;br&gt;
Response&lt;br&gt;
   ↓&lt;br&gt;
Database&lt;br&gt;
   ↓&lt;br&gt;
UI&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;just to show the transaction.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Expense created&lt;br&gt;
      ↓&lt;br&gt;
Room database updated&lt;br&gt;
      ↓&lt;br&gt;
Observable state changes&lt;br&gt;
      ↓&lt;br&gt;
Compose UI updates&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The user gets immediate feedback.&lt;/p&gt;

&lt;p&gt;The application can then perform network-related work separately when necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offline-first changes how you design features
&lt;/h2&gt;

&lt;p&gt;Consider exchange rates.&lt;/p&gt;

&lt;p&gt;Unlike a locally created transaction, exchange rates are external data.&lt;/p&gt;

&lt;p&gt;They may require a network request.&lt;/p&gt;

&lt;p&gt;So the architecture can treat them differently:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Local transaction&lt;br&gt;
      ↓&lt;br&gt;
Local database&lt;br&gt;
      ↓&lt;br&gt;
Immediately available&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;versus:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Exchange rate&lt;br&gt;
      ↓&lt;br&gt;
Fetch from API&lt;br&gt;
      ↓&lt;br&gt;
Store locally&lt;br&gt;
      ↓&lt;br&gt;
Use cached value when offline&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now the application can still provide useful functionality even when the network isn't currently available.&lt;/p&gt;

&lt;h2&gt;
  
  
  WorkManager fits naturally here
&lt;/h2&gt;

&lt;p&gt;Some operations don't need to happen immediately.&lt;/p&gt;

&lt;p&gt;For example, background work can be scheduled for tasks that should happen when the required conditions are available.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Application&lt;br&gt;
    ↓&lt;br&gt;
Schedule background work&lt;br&gt;
    ↓&lt;br&gt;
WorkManager&lt;br&gt;
    ↓&lt;br&gt;
Check constraints&lt;br&gt;
    ↓&lt;br&gt;
Perform operation&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is different from blocking the user's current interaction until the network finishes.&lt;/p&gt;

&lt;p&gt;The user can continue using the application while background work happens independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offline-first also creates new problems
&lt;/h2&gt;

&lt;p&gt;It's not automatically easier.&lt;/p&gt;

&lt;p&gt;You now have to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stale data&lt;/li&gt;
&lt;li&gt;Synchronization&lt;/li&gt;
&lt;li&gt;Conflicts&lt;/li&gt;
&lt;li&gt;Failed requests&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Cached data&lt;/li&gt;
&lt;li&gt;Data consistency&lt;/li&gt;
&lt;li&gt;Background execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a fully local application, some of these problems disappear.&lt;/p&gt;

&lt;p&gt;But once remote data or synchronization is introduced, they become architectural concerns.&lt;/p&gt;

&lt;p&gt;That's why I don't think "offline-first" should be treated as just a feature.&lt;/p&gt;

&lt;p&gt;It's a &lt;strong&gt;data architecture decision&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The important distinction
&lt;/h2&gt;

&lt;p&gt;There is a big difference between:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Offline-capable&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Offline-first&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;An offline-capable application might say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If the internet disappears, we'll try to keep working."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An offline-first application starts from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The local experience should work independently, and network connectivity is something we can use when available."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That difference affects the architecture from the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm learning
&lt;/h2&gt;

&lt;p&gt;Building a real application has made me think less about individual technologies and more about the responsibilities between them.&lt;/p&gt;

&lt;p&gt;Room isn't just a database library.&lt;/p&gt;

&lt;p&gt;WorkManager isn't just a background-task API.&lt;/p&gt;

&lt;p&gt;Jetpack Compose isn't just a UI toolkit.&lt;/p&gt;

&lt;p&gt;Together, they can support an architecture where the application doesn't make the network a dependency for every user action.&lt;/p&gt;

&lt;p&gt;For a finance application, that makes sense to me.&lt;/p&gt;

&lt;p&gt;Your transaction history shouldn't become inaccessible simply because your connection disappeared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A good offline-first architecture doesn't pretend the network doesn't exist.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It makes sure the application doesn't depend on it for everything.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  android #kotlin #jetpackcompose #room #workmanager #architecture #mobiledevelopment #softwareengineering #buildinpublic
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>When a Feature Looks Simple, Write Down Its Invariants First</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Tue, 15 Sep 2026 05:55:14 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/when-a-feature-looks-simple-write-down-its-invariants-first-2f27</link>
      <guid>https://dev.to/devanshu_patil/when-a-feature-looks-simple-write-down-its-invariants-first-2f27</guid>
      <description>&lt;h1&gt;
  
  
  When a Feature Looks Simple, Write Down Its Invariants First
&lt;/h1&gt;

&lt;p&gt;One of the things I've learned while building FinLedger is that a feature can look simple from the UI while being surprisingly complicated underneath.&lt;/p&gt;

&lt;p&gt;Take something like editing a transaction.&lt;/p&gt;

&lt;p&gt;At first glance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tap transaction
   ↓
Edit amount
   ↓
Save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy.&lt;/p&gt;

&lt;p&gt;But the moment real data is involved, the questions start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if the amount changes?&lt;/li&gt;
&lt;li&gt;What if the transaction type changes?&lt;/li&gt;
&lt;li&gt;What happens to the current balance?&lt;/li&gt;
&lt;li&gt;What happens to the transaction history?&lt;/li&gt;
&lt;li&gt;What if the record no longer exists?&lt;/li&gt;
&lt;li&gt;What if the user saves the same data without making changes?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't UI questions.&lt;/p&gt;

&lt;p&gt;They're &lt;strong&gt;business-rule questions&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with invariants
&lt;/h2&gt;

&lt;p&gt;An invariant is something that should remain true regardless of which operation the user performs.&lt;/p&gt;

&lt;p&gt;For a transaction system, examples might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Amount must be valid
A transaction must belong to a valid entity
A balance must be calculated consistently
A deleted record must not leave invalid references
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact rules depend on the application, but writing them down changes how you implement the feature.&lt;/p&gt;

&lt;p&gt;Instead of starting with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which Compose components do I need?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you start with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What must always be true after this operation?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a much better starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Think about the operation as a state transition
&lt;/h2&gt;

&lt;p&gt;A useful way to reason about changes is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before
  ↓
Operation
  ↓
After
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:
Outstanding = ₹5,000

Edit transaction:
₹5,000 → ₹7,000

After:
Outstanding = ₹7,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now consider a more complicated case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:
Outstanding = ₹5,000

Repayment:
₹2,000

After:
Outstanding = ₹3,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important question isn't simply how to update a number.&lt;/p&gt;

&lt;p&gt;It's whether the operation preserves all the relationships and history the application expects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't let the UI define your business logic
&lt;/h2&gt;

&lt;p&gt;This is where architecture becomes important.&lt;/p&gt;

&lt;p&gt;A screen might collect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="n"&gt;type&lt;/span&gt;
&lt;span class="n"&gt;date&lt;/span&gt;
&lt;span class="n"&gt;note&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it shouldn't be responsible for deciding what those values mean to the entire application.&lt;/p&gt;

&lt;p&gt;A cleaner flow is something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compose UI
    ↓
User event
    ↓
ViewModel
    ↓
Business logic
    ↓
Repository
    ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI collects intent.&lt;/p&gt;

&lt;p&gt;The business layer determines what that intent means.&lt;/p&gt;

&lt;p&gt;The repository handles persistence.&lt;/p&gt;

&lt;p&gt;That separation makes the feature easier to test and reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge cases expose weak designs
&lt;/h2&gt;

&lt;p&gt;The happy path rarely tells you whether your design is good.&lt;/p&gt;

&lt;p&gt;Try the uncomfortable cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Edit an existing transaction
Edit it without changing anything
Change the amount
Change the transaction type
Delete it immediately after editing
Open an old transaction
Use invalid input
Perform the operation twice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the system behaves consistently across these cases, your design is probably getting stronger.&lt;/p&gt;

&lt;p&gt;If every edge case requires another special condition, that's a warning sign.&lt;/p&gt;

&lt;p&gt;The problem may not be the edge cases.&lt;/p&gt;

&lt;p&gt;The problem may be the underlying model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson I'm taking from this
&lt;/h2&gt;

&lt;p&gt;I've started trying to solve fewer problems by immediately writing code.&lt;/p&gt;

&lt;p&gt;Before implementing a feature, I want to understand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What state exists?
What can change?
What must remain true?
What events can modify it?
What happens when the operation fails?
What happens when it is repeated?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially important in applications where data has relationships and history.&lt;/p&gt;

&lt;p&gt;A feature isn't just a screen.&lt;/p&gt;

&lt;p&gt;It's a &lt;strong&gt;state transition inside a larger system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And the better you understand that transition before coding, the less likely you are to build a feature that works only in the happy path.&lt;/p&gt;

&lt;h1&gt;
  
  
  android #kotlin #jetpackcompose #softwarearchitecture #backend #databases #systemdesign #buildinpublic
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>software</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Jetpack Compose Made Me Rethink How I Build UI</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Mon, 14 Sep 2026 10:13:31 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/jetpack-compose-made-me-rethink-how-i-build-ui-293e</link>
      <guid>https://dev.to/devanshu_patil/jetpack-compose-made-me-rethink-how-i-build-ui-293e</guid>
      <description>&lt;h1&gt;
  
  
  Jetpack Compose Made Me Rethink How I Build UI
&lt;/h1&gt;

&lt;p&gt;When I started working with Jetpack Compose, the biggest change wasn't the syntax.&lt;/p&gt;

&lt;p&gt;It was the way I had to think about UI.&lt;/p&gt;

&lt;p&gt;With traditional Android UI, it's easy to think in terms of views:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find a view
 ↓
Change its value
 ↓
Update another view
 ↓
Refresh something
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Compose pushes you toward a different model:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
State&lt;br&gt;
  ↓&lt;br&gt;
UI&lt;br&gt;
  ↓&lt;br&gt;
User action&lt;br&gt;
  ↓&lt;br&gt;
State changes&lt;br&gt;
  ↓&lt;br&gt;
UI recomposes&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The UI becomes a function of state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't make the UI the source of truth
&lt;/h2&gt;

&lt;p&gt;Consider a simple screen displaying a transaction.&lt;/p&gt;

&lt;p&gt;Instead of having the UI independently track everything, the screen can consume a state object:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;kotlin&lt;br&gt;
data class TransactionUiState(&lt;br&gt;
    val transactions: List&amp;lt;Transaction&amp;gt;,&lt;br&gt;
    val isLoading: Boolean,&lt;br&gt;
    val error: String? = null&lt;br&gt;
)&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then the UI can essentially ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Given this state, what should I display?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;kotlin&lt;br&gt;
when {&lt;br&gt;
    state.isLoading -&amp;gt; LoadingIndicator()&lt;br&gt;
    state.error != null -&amp;gt; ErrorMessage(state.error)&lt;br&gt;
    state.transactions.isEmpty() -&amp;gt; EmptyState()&lt;br&gt;
    else -&amp;gt; TransactionList(state.transactions)&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is much easier to reason about than having different pieces of UI independently deciding what state the screen is in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Events should flow in one direction
&lt;/h2&gt;

&lt;p&gt;A pattern I've found useful is treating user interactions as events.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
User taps Save&lt;br&gt;
      ↓&lt;br&gt;
onSaveClicked()&lt;br&gt;
      ↓&lt;br&gt;
ViewModel&lt;br&gt;
      ↓&lt;br&gt;
Repository&lt;br&gt;
      ↓&lt;br&gt;
Data changes&lt;br&gt;
      ↓&lt;br&gt;
New UI state&lt;br&gt;
      ↓&lt;br&gt;
Compose recomposes&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The UI doesn't need to know how the transaction is stored.&lt;/p&gt;

&lt;p&gt;It only needs to communicate the user's intent.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;kotlin&lt;br&gt;
Button(&lt;br&gt;
    onClick = {&lt;br&gt;
        viewModel.saveTransaction()&lt;br&gt;
    }&lt;br&gt;
) {&lt;br&gt;
    Text("Save")&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The ViewModel handles what happens next.&lt;/p&gt;

&lt;p&gt;That separation becomes increasingly useful as the application grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recomposition isn't something to fear
&lt;/h2&gt;

&lt;p&gt;One of the first concepts that can feel strange with Compose is recomposition.&lt;/p&gt;

&lt;p&gt;The important mental model is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your composable should be able to run again.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means avoiding unnecessary side effects directly inside composable functions.&lt;/p&gt;

&lt;p&gt;Instead of thinking:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
"This function runs once."&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;think:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
"This function describes the UI for the current state."&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If the state changes, Compose may run the composable again.&lt;/p&gt;

&lt;p&gt;That's not a bug.&lt;/p&gt;

&lt;p&gt;That's the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  State ownership matters
&lt;/h2&gt;

&lt;p&gt;Another important question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Who should own this state?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not every piece of state belongs in the ViewModel.&lt;/p&gt;

&lt;p&gt;For example, temporary UI state such as whether a dropdown is expanded can often remain local to the composable.&lt;/p&gt;

&lt;p&gt;But application state such as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Transactions&lt;br&gt;
Current balance&lt;br&gt;
Loading state&lt;br&gt;
Error state&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;usually needs a longer lifecycle and may belong higher in the architecture.&lt;/p&gt;

&lt;p&gt;A useful rule is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`text&lt;br&gt;
Local UI state&lt;br&gt;
    ↓&lt;br&gt;
Composable&lt;/p&gt;

&lt;p&gt;Screen/application state&lt;br&gt;
    ↓&lt;br&gt;
ViewModel&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The exact architecture can vary, but the principle is to keep state as close as possible to where it is actually needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compose doesn't eliminate architecture problems
&lt;/h2&gt;

&lt;p&gt;This is probably the most important lesson.&lt;/p&gt;

&lt;p&gt;A modern UI framework doesn't automatically create a good application architecture.&lt;/p&gt;

&lt;p&gt;You can still build a mess with Compose.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Composable&lt;br&gt;
   ↓&lt;br&gt;
Database&lt;br&gt;
   ↓&lt;br&gt;
Business logic&lt;br&gt;
   ↓&lt;br&gt;
Network request&lt;br&gt;
   ↓&lt;br&gt;
More UI state&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It might work initially.&lt;/p&gt;

&lt;p&gt;But eventually the screen becomes responsible for too many things.&lt;/p&gt;

&lt;p&gt;Compose makes it easier to write UI declaratively.&lt;/p&gt;

&lt;p&gt;It doesn't remove the need for good separation of concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm learning
&lt;/h2&gt;

&lt;p&gt;Working with Jetpack Compose has made me think less about manipulating UI elements and more about &lt;strong&gt;modeling state and events&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do I update this screen?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm increasingly asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What state should this screen represent, and what events can change that state?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That shift is useful beyond Android.&lt;/p&gt;

&lt;p&gt;It's a general way of thinking about interactive software.&lt;/p&gt;

&lt;p&gt;The UI is not the application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The UI is a representation of the application's current state.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once that becomes your mental model, many UI problems become easier to reason about.&lt;/p&gt;

&lt;h1&gt;
  
  
  android #kotlin #jetpackcompose #softwarearchitecture #mobiledevelopment #programming #buildinpublic
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

</description>
      <category>android</category>
      <category>frontend</category>
      <category>kotlin</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Self-Hosting Taught Me That Deployment Is an Engineering Problem</title>
      <dc:creator>DEVANSHU PATIL</dc:creator>
      <pubDate>Sat, 12 Sep 2026 13:49:35 +0000</pubDate>
      <link>https://dev.to/devanshu_patil/self-hosting-taught-me-that-deployment-is-an-engineering-problem-29bb</link>
      <guid>https://dev.to/devanshu_patil/self-hosting-taught-me-that-deployment-is-an-engineering-problem-29bb</guid>
      <description>&lt;h1&gt;
  
  
  Self-Hosting Taught Me That Deployment Is an Engineering Problem
&lt;/h1&gt;

&lt;p&gt;I used to think of deployment as the final step after development.&lt;/p&gt;

&lt;p&gt;Build the application.&lt;/p&gt;

&lt;p&gt;Push the code.&lt;/p&gt;

&lt;p&gt;Deploy it.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;Working with my own VPS changed that perspective.&lt;/p&gt;

&lt;p&gt;When you deploy your own application, you become responsible for much more than the application code.&lt;/p&gt;

&lt;p&gt;You have to think about the server, networking, containers, domains, HTTPS, environment variables, logs, updates, and security.&lt;/p&gt;

&lt;h2&gt;
  
  
  My deployment stack
&lt;/h2&gt;

&lt;p&gt;The setup I'm working with looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub
   ↓
Coolify
   ↓
Docker
   ↓
Application
   ↓
Reverse Proxy
   ↓
Domain
   ↓
Internet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;The interesting part is that every layer can fail independently.&lt;/p&gt;

&lt;p&gt;Your application can be perfectly fine while the deployment is broken.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Application works locally&lt;br&gt;
        ↓&lt;br&gt;
Docker container starts&lt;br&gt;
        ↓&lt;br&gt;
But the port is wrong&lt;br&gt;
        ↓&lt;br&gt;
Reverse proxy can't reach it&lt;br&gt;
        ↓&lt;br&gt;
Domain shows an error&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;From the browser, all you see is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The website isn't working."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the actual problem might have nothing to do with your application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  A VPS forces you to understand the infrastructure
&lt;/h2&gt;

&lt;p&gt;With managed hosting, many infrastructure decisions are hidden from you.&lt;/p&gt;

&lt;p&gt;That's convenient.&lt;/p&gt;

&lt;p&gt;But when something goes wrong, you may not understand what is happening underneath.&lt;/p&gt;

&lt;p&gt;With a VPS, you start seeing the actual layers:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Linux&lt;br&gt;
 ↓&lt;br&gt;
Docker&lt;br&gt;
 ↓&lt;br&gt;
Container&lt;br&gt;
 ↓&lt;br&gt;
Application&lt;br&gt;
 ↓&lt;br&gt;
Network&lt;br&gt;
 ↓&lt;br&gt;
Reverse Proxy&lt;br&gt;
 ↓&lt;br&gt;
DNS&lt;br&gt;
 ↓&lt;br&gt;
HTTPS&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's more responsibility, but also more learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coolify makes the process easier
&lt;/h2&gt;

&lt;p&gt;Using Coolify means I don't have to manually manage every deployment command.&lt;/p&gt;

&lt;p&gt;I can connect a repository, configure the application, provide environment variables, and let the platform handle much of the deployment workflow.&lt;/p&gt;

&lt;p&gt;But Coolify doesn't remove the need to understand infrastructure.&lt;/p&gt;

&lt;p&gt;It just moves the repetitive work away from the command line.&lt;/p&gt;

&lt;p&gt;If something fails, I still need to understand whether the problem is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Git&lt;br&gt;
Docker&lt;br&gt;
Application&lt;br&gt;
Environment&lt;br&gt;
Network&lt;br&gt;
DNS&lt;br&gt;
Reverse Proxy&lt;br&gt;
Server&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's where the real learning happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  DNS is part of deployment
&lt;/h2&gt;

&lt;p&gt;One thing that's easy to overlook is that your application isn't truly accessible just because it is running on the VPS.&lt;/p&gt;

&lt;p&gt;The domain needs to point to the right server.&lt;/p&gt;

&lt;p&gt;The traffic needs to reach the correct service.&lt;/p&gt;

&lt;p&gt;HTTPS needs to be configured.&lt;/p&gt;

&lt;p&gt;So the complete path becomes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
User enters domain&lt;br&gt;
        ↓&lt;br&gt;
DNS resolves domain&lt;br&gt;
        ↓&lt;br&gt;
Request reaches VPS&lt;br&gt;
        ↓&lt;br&gt;
Reverse proxy receives request&lt;br&gt;
        ↓&lt;br&gt;
Request forwarded to container&lt;br&gt;
        ↓&lt;br&gt;
Application responds&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Every arrow represents another possible failure point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment changes how you write software
&lt;/h2&gt;

&lt;p&gt;Once you know your application will eventually run somewhere other than your laptop, you start making better decisions.&lt;/p&gt;

&lt;p&gt;You think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration outside the source code&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Reproducible builds&lt;/li&gt;
&lt;li&gt;Containerization&lt;/li&gt;
&lt;li&gt;Logs&lt;/li&gt;
&lt;li&gt;Health checks&lt;/li&gt;
&lt;li&gt;Database persistence&lt;/li&gt;
&lt;li&gt;Backups&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Rollbacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't "DevOps things" that someone else will deal with later.&lt;/p&gt;

&lt;p&gt;They're part of running the software.&lt;/p&gt;

&lt;h2&gt;
  
  
  The biggest lesson
&lt;/h2&gt;

&lt;p&gt;Self-hosting has made one thing very clear to me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing code is only one part of software engineering.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An application isn't finished when it compiles.&lt;/p&gt;

&lt;p&gt;It isn't finished when it works on localhost.&lt;/p&gt;

&lt;p&gt;It's finished when you can reliably build it, deploy it, operate it, debug it, and recover when something goes wrong.&lt;/p&gt;

&lt;p&gt;I'm still learning these parts by actually deploying and managing my own projects.&lt;/p&gt;

&lt;p&gt;And honestly, that's much more valuable than memorizing deployment commands.&lt;/p&gt;

&lt;p&gt;Because commands can be searched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understanding the system is what stays with you.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  devops #selfhosting #docker #coolify #vps #linux #deployment #softwareengineering #buildinpublic
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

</description>
      <category>deployment</category>
      <category>devops</category>
      <category>docker</category>
      <category>infrastructure</category>
    </item>
  </channel>
</rss>
