<?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: Akintunde Morakinyo</title>
    <description>The latest articles on DEV Community by Akintunde Morakinyo (@akintunde_morakinyo_db6b2).</description>
    <link>https://dev.to/akintunde_morakinyo_db6b2</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%2F2813810%2F413e041a-5070-4a35-9eb2-b00520863373.png</url>
      <title>DEV Community: Akintunde Morakinyo</title>
      <link>https://dev.to/akintunde_morakinyo_db6b2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akintunde_morakinyo_db6b2"/>
    <language>en</language>
    <item>
      <title>How I Simplified My Backend Architecture for Business Applications</title>
      <dc:creator>Akintunde Morakinyo</dc:creator>
      <pubDate>Sat, 08 Aug 2026 21:36:20 +0000</pubDate>
      <link>https://dev.to/akintunde_morakinyo_db6b2/how-i-simplified-my-backend-architecture-for-business-applications-161l</link>
      <guid>https://dev.to/akintunde_morakinyo_db6b2/how-i-simplified-my-backend-architecture-for-business-applications-161l</guid>
      <description>&lt;p&gt;After more than a decade of building business applications, I noticed something interesting.&lt;/p&gt;

&lt;p&gt;Very few projects became difficult because the business rules were complicated.&lt;/p&gt;

&lt;p&gt;Most became difficult because of everything surrounding the business rules.&lt;/p&gt;

&lt;p&gt;Every new module seemed to require another controller, another service, another repository, another set of CRUD methods, another search endpoint, another authorization check and another audit implementation.&lt;/p&gt;

&lt;p&gt;Over time, the application became full of infrastructure code that looked remarkably similar across different modules.&lt;/p&gt;

&lt;p&gt;That made me ask a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Can infrastructure be designed once so developers spend most of their time writing business logic instead of rewriting the same plumbing?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question completely changed how I approach backend architecture.&lt;/p&gt;

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

&lt;p&gt;When I build business applications, I try to separate business behaviour from infrastructure.&lt;/p&gt;

&lt;p&gt;Infrastructure includes things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CRUD&lt;/li&gt;
&lt;li&gt;Searching&lt;/li&gt;
&lt;li&gt;Pagination&lt;/li&gt;
&lt;li&gt;Auditing&lt;/li&gt;
&lt;li&gt;Multi-tenancy&lt;/li&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are necessary, but they are rarely unique to a specific module.&lt;/p&gt;

&lt;p&gt;Whether you're building Products, Customers, Orders or Suppliers, those capabilities are usually identical.&lt;/p&gt;

&lt;p&gt;Business logic should be where the application becomes different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the Architecture Small
&lt;/h2&gt;

&lt;p&gt;Instead of adding more layers, I started removing unnecessary ones.&lt;/p&gt;

&lt;p&gt;The architecture now looks something like this:&lt;/p&gt;

&lt;p&gt;HTTP Request&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Controller&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Business Service&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
BaseService&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
IDataService&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Database&lt;/p&gt;

&lt;p&gt;Each layer has one clear responsibility.&lt;/p&gt;

&lt;p&gt;Controllers receive requests.&lt;/p&gt;

&lt;p&gt;Business services implement business rules.&lt;/p&gt;

&lt;p&gt;The base service provides reusable application behaviour.&lt;/p&gt;

&lt;p&gt;The data service handles persistence.&lt;/p&gt;

&lt;p&gt;That simple separation has proven easier to maintain than having every service implement the same CRUD operations repeatedly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Group by Business Capability
&lt;/h2&gt;

&lt;p&gt;Another change I made was grouping services around business capabilities rather than around every model.&lt;/p&gt;

&lt;p&gt;For example, instead of creating services simply because a model exists, I group related functionality into services such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product Service&lt;/li&gt;
&lt;li&gt;Order Service&lt;/li&gt;
&lt;li&gt;Inventory Service&lt;/li&gt;
&lt;li&gt;Customer Service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supporting models don't automatically require their own service unless they introduce their own business behaviour.&lt;/p&gt;

&lt;p&gt;That keeps the solution easier to navigate and reduces unnecessary abstractions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching Shouldn't Require New Endpoints
&lt;/h2&gt;

&lt;p&gt;Search requirements tend to grow throughout the lifetime of an application.&lt;/p&gt;

&lt;p&gt;It often starts with a few simple endpoints and gradually expands into dozens of variations.&lt;/p&gt;

&lt;p&gt;Rather than creating a new endpoint every time another filter is needed, I prefer allowing the client to describe the search.&lt;/p&gt;

&lt;p&gt;The backend remains responsible for validating those criteria and translating them into efficient database queries.&lt;/p&gt;

&lt;p&gt;The API surface stays stable while search capabilities continue to grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make Infrastructure Automatic
&lt;/h2&gt;

&lt;p&gt;Another principle I try to follow is making infrastructure happen automatically whenever possible.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Tenant filtering&lt;/li&gt;
&lt;li&gt;Audit information&lt;/li&gt;
&lt;li&gt;Pagination&lt;/li&gt;
&lt;li&gt;Index creation&lt;/li&gt;
&lt;li&gt;Timestamps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers shouldn't need to remember these concerns throughout the application.&lt;/p&gt;

&lt;p&gt;The framework should handle them consistently.&lt;/p&gt;

&lt;p&gt;That not only reduces repetitive code but also helps prevent subtle bugs caused by inconsistent implementations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not GraphQL or gRPC?
&lt;/h2&gt;

&lt;p&gt;GraphQL and gRPC are both excellent technologies.&lt;/p&gt;

&lt;p&gt;The choice depends on the problem you're solving.&lt;/p&gt;

&lt;p&gt;GraphQL shines when clients need fine-grained control over the shape of the data they retrieve.&lt;/p&gt;

&lt;p&gt;gRPC excels at high-performance communication between services.&lt;/p&gt;

&lt;p&gt;Many business applications, however, primarily need predictable APIs with flexible searching, strong authorization, auditing and maintainable business logic.&lt;/p&gt;

&lt;p&gt;In those cases, a well-designed REST architecture with reusable infrastructure often provides everything required while remaining straightforward to develop, test and maintain.&lt;/p&gt;

&lt;p&gt;The objective isn't to avoid newer technologies.&lt;/p&gt;

&lt;p&gt;It's to choose the simplest architecture that solves the problem well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;One lesson I've learned is that complexity rarely arrives all at once.&lt;/p&gt;

&lt;p&gt;It accumulates.&lt;/p&gt;

&lt;p&gt;A duplicated CRUD method here.&lt;/p&gt;

&lt;p&gt;Another search endpoint there.&lt;/p&gt;

&lt;p&gt;A new authorization implementation in another module.&lt;/p&gt;

&lt;p&gt;Individually, they don't seem significant.&lt;/p&gt;

&lt;p&gt;Collectively, they become expensive to maintain.&lt;/p&gt;

&lt;p&gt;Good architecture isn't about adding more layers or adopting every new technology.&lt;/p&gt;

&lt;p&gt;It's about removing unnecessary repetition so developers can focus on solving business problems.&lt;/p&gt;

&lt;p&gt;That's the principle I now try to apply whenever I design a new business application.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What architectural decisions have helped you reduce repetitive code in your own backend applications? I'd be interested to hear the patterns that have worked well for you.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>dotnet</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
    <item>
      <title>Building an Efficient Authentication Pipeline with ASP.NET Core, Playwright and Token Caching</title>
      <dc:creator>Akintunde Morakinyo</dc:creator>
      <pubDate>Sun, 02 Aug 2026 19:16:48 +0000</pubDate>
      <link>https://dev.to/akintunde_morakinyo_db6b2/building-an-efficient-authentication-pipeline-with-aspnet-core-playwright-and-token-caching-2fc1</link>
      <guid>https://dev.to/akintunde_morakinyo_db6b2/building-an-efficient-authentication-pipeline-with-aspnet-core-playwright-and-token-caching-2fc1</guid>
      <description>&lt;p&gt;Authentication is one of the most important parts of any application that integrates with external services. While many modern platforms expose OAuth APIs or SDKs for third-party developers, there are situations where an application needs to interact with an authentication flow that is primarily designed for browser users.&lt;/p&gt;

&lt;p&gt;Recently, while building a mobile application that integrates with an external football management platform, I needed to design an authentication architecture that was both reliable and efficient. Rather than focusing solely on obtaining an access token, the bigger engineering challenge was ensuring that users were not repeatedly authenticated on every request.&lt;/p&gt;

&lt;p&gt;This article explains the architecture I adopted and the design decisions behind it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;The mobile application communicates with an ASP.NET Core backend, which in turn communicates with an external service requiring authenticated requests.&lt;/p&gt;

&lt;p&gt;A naïve implementation would attempt to authenticate every time a user opens the application. Besides introducing unnecessary latency, this approach would repeatedly perform browser automation and create avoidable load on both the application and the external service.&lt;/p&gt;

&lt;p&gt;The objective was therefore to design a solution that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authenticates users only when necessary;&lt;/li&gt;
&lt;li&gt;securely stores authentication information;&lt;/li&gt;
&lt;li&gt;reuses valid access tokens;&lt;/li&gt;
&lt;li&gt;refreshes authentication only after token expiry; and&lt;/li&gt;
&lt;li&gt;keeps the mobile client completely unaware of the authentication complexity.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  High-Level Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────────┐
│   React Native App    │
└──────────┬────────────┘
           │
           ▼
┌───────────────────────┐
│   ASP.NET Core API    │
└──────────┬────────────┘
           │
           ▼
┌───────────────────────┐
│      MongoDB Cache    │
└──────┬─────────┬──────┘
       │         │
 Token Exists?   │
       │         │
       ▼         ▼
 Return Token  Launch Playwright
                    │
                    ▼
          External Authentication
                    │
                    ▼
           Receive Access Token
                    │
                    ▼
          Store Token + Expiry
                    │
                    ▼
         Return Authenticated User
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Authentication Flow
&lt;/h2&gt;

&lt;p&gt;The authentication pipeline follows a simple decision process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 – Check Existing User
&lt;/h3&gt;

&lt;p&gt;When a login request reaches the backend, the application first checks whether the user already exists in MongoDB.&lt;/p&gt;

&lt;p&gt;Instead of immediately attempting another login, the stored authentication information is inspected.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2 – Validate Token Expiry
&lt;/h3&gt;

&lt;p&gt;Each stored authentication record contains an access token together with its expiration timestamp.&lt;/p&gt;

&lt;p&gt;If the token is still valid, the backend simply reuses it.&lt;/p&gt;

&lt;p&gt;This avoids unnecessary authentication while significantly reducing response time for returning users.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3 – Authenticate Only When Necessary
&lt;/h3&gt;

&lt;p&gt;If the user is logging in for the first time, or the existing token has expired, the backend performs a fresh authentication using Playwright.&lt;/p&gt;

&lt;p&gt;Once authentication succeeds, the new access token and its expiry time are stored for future requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Centralising Authentication
&lt;/h2&gt;

&lt;p&gt;One design decision that proved particularly useful was centralising authentication inside the backend.&lt;/p&gt;

&lt;p&gt;The React Native application simply submits user credentials once. After successful authentication, the backend becomes responsible for managing the lifecycle of the access token.&lt;/p&gt;

&lt;p&gt;This provides several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a simpler mobile application;&lt;/li&gt;
&lt;li&gt;reduced duplication of authentication logic;&lt;/li&gt;
&lt;li&gt;easier maintenance; and&lt;/li&gt;
&lt;li&gt;the flexibility to change authentication behaviour without updating the client application.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Automatic Token Injection
&lt;/h2&gt;

&lt;p&gt;Once a valid token is available, it is attached to the shared &lt;code&gt;HttpClient&lt;/code&gt; before any authenticated requests are made.&lt;/p&gt;

&lt;h2&gt;
  
  
  This means the remainder of the application can focus entirely on business logic without worrying about manually attaching authentication headers for every request.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Why Cache Authentication?
&lt;/h2&gt;

&lt;p&gt;Caching authentication information offers several practical benefits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Performance
&lt;/h3&gt;

&lt;p&gt;Returning users avoid repeating the authentication process, resulting in noticeably faster login times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lower Infrastructure Cost
&lt;/h3&gt;

&lt;p&gt;Browser automation is significantly more resource-intensive than validating a stored token. Reusing valid tokens reduces server workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved User Experience
&lt;/h3&gt;

&lt;p&gt;Users experience a seamless login flow while the backend transparently manages authentication behind the scenes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleaner Architecture
&lt;/h3&gt;

&lt;p&gt;Separating authentication management from business functionality keeps services easier to test and maintain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Designing authentication is about much more than obtaining an access token.&lt;/p&gt;

&lt;p&gt;A robust authentication system should answer questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When should a user authenticate?&lt;/li&gt;
&lt;li&gt;How long should authentication remain valid?&lt;/li&gt;
&lt;li&gt;When should cached credentials be reused?&lt;/li&gt;
&lt;li&gt;How can authentication complexity be hidden from client applications?&lt;/li&gt;
&lt;li&gt;How can token management remain maintainable as the application grows?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By treating authentication as an architectural concern rather than simply a login feature, it becomes possible to build systems that are faster, cleaner and significantly easier to evolve.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Every integration presents its own authentication challenges, but the underlying engineering principles remain consistent.&lt;/p&gt;

&lt;p&gt;By introducing token caching, validating token expiry before re-authentication, and centralising authentication management within the backend, I was able to create an architecture that is efficient, maintainable and scalable.&lt;/p&gt;

&lt;p&gt;The implementation discussed here was developed while building a mobile integration for a football management platform, but the same design principles can be applied to many applications that interact with external authenticated services.&lt;/p&gt;

&lt;p&gt;In future articles, I'll explore additional aspects of the project, including designing reusable API clients, integrating a React Native front end with the backend, and structuring applications for long-term maintainability.&lt;/p&gt;

</description>
      <category>dotnetcore</category>
      <category>aspnet</category>
      <category>playwright</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
