Ratnesh Dubey- Engineering & AI Insights

,

  • Home
  • About
  • Blog
  • Resources
  • Contact
  • What Are Weights and Parameters in an LLM 

    What are the use of a model weight and parameters?

    A model weight is used to decide what matters more when the model makes a decision.

    That’s the only job of a weight.

    Before we go further, one important clarification.

    When people say weights or parameters, they are talking about the same thing.

    They are just numbers learned by the model. Different words, same idea.

    Now let us see one everyday example

    You are deciding:

    Should I carry an umbrella?

    You look at three things:

    Is it raining?

    Is it cloudy?

    Is it windy?

    But you don’t treat them equally.

    Rain matters a lot, Clouds matter a little, Wind barely matters

    So in your head, you automatically do this:

    Rain → very important
    Clouds→ somewhat important
    Wind → not very important

    Those “importance levels” are weights.

    They help you reach the right decision.

    Now the same idea in LLMs

    An AI model constantly answers one question:

    What should come next?

    Example:

    “I love ___”

    Possible next words:

    • you
    • food
    • rain
    • bugs

    The model assigns numbers to each option:

    you → high number
    food → medium number
    rain → low number
    bugs → very low number

    The word with the highest number is chosen.

    👉 That is the use of weights:

    to score options and pick the most likely one.

    A weight is not a rule or a sentence stored in memory.

    It is just a number — a small nudge — that makes one word slightly more likely than another.


    Why weights are necessary

    Without weights:

    • every word would be treated the same
    • the model would guess randomly
    • language would make no sense

    With weights:

    • common patterns matter more
    • rare or wrong patterns matter less
    • predictions improve over time

    How weights get their correct values

    At first, weights are random → wrong answers.

    Each time the model is wrong:

    • it compares its answer with the correct one
    • slightly adjusts the weights
    • tries again

    After millions of examples:

    useful connections get stronger weights and useless ones get weaker weights.

    This preference to adjust weight didn’t come from reasoning or understanding.

    It came from practice — seeing similar sentences millions of times before.

    Here, learning doesn’t mean understanding like a human.

    It simply means numbers being adjusted based on past patterns.

    So, If you remember just one thing from this blog, let it be this:

    A weight, also called a parameter, is a number the model learned because it helped reduce mistakes.

    All the impressive behavior we see later emerges from those numbers.

    With this, it becomes clear why people say a model has 7 billion parameters — it simply means there are 7 billion learned numbers shaping its behavior.

    So at its core, an LLM is not storing answers.

    It is carrying a vast set of learned numbers that gently push it toward one word instead of another.

    I hope I was able to explain this in a clear and simple way. Happy Learning!.

  • Understanding Transformers, the Engine of LLMs

    Transformers are the backbone of modern AI models like GPT, Claude, Llama, and Gemini. Their core idea is straightforward: language only makes sense when you understand how words relate to each other.

    A transformer begins by turning each word into a vector that captures meaning — “coffee” sits closer to “tea” than to “mountain,” “king” sits near “queen,” and so on. These embeddings give the model a foundation for comparing concepts.

    Instead of reading text word by word, transformers look at entire sentences at once. This lets them identify long-range connections. In a sentence like “The boy who came yesterday forgot his bag,” the model uses attention to connect “his” with “boy,” not with “yesterday.”

    Language has many layers — grammar, description, tone, reference — so transformers examine text through multiple attention heads. Each head focuses on a different angle, and stacked layers refine the understanding step by step.

    In the raw input, a word like “bank” is ambiguous. After passing through a transformer, that ambiguity is resolved. The internal representation becomes different for “river bank” than for “money bank.” This transformation happens gradually, layer by layer, through attention and feed-forward steps. By the end of the process, the model has turned:

    (1) plain token embeddings → (2) contextual meaning representations → (3) usable outputs, such as the next word, a summary, or a translation.

    So A transformer is a neural network architecture designed to transform a sequence of inputs into a more meaningful sequence of representations. It doesn’t transform one language into another by default — it transforms each word (after being converted into numbers) into deeper, more accurate vectors that reflect its meaning in context.

    Transformers come in three shapes:

    Encoder (built for understanding; e.g., BERT)

    Decoder (built for generation; e.g., GPT, Llama, Claude)

    Encoder-Decoder (built for structured input→output tasks; e.g., T5)

    Most large language models today use decoder-only transformers, because predicting the next word scales extremely well and leads to broad general capabilities.

    You can see the whole design in action with a simple prompt:

    “Ravi forgot his umbrella because…”

    The model doesn’t retrieve a stored fact — it predicts the next most natural continuation based on relationships it has learned:

    “…he left in a hurry.”

    “…the weather changed suddenly.”

    One word at a time, guided by learned patterns.

    That’s the transformer in practical terms — an architecture built on meaning, relationships, and refinement — and the foundation behind the AI systems we use every day. Without transformers, We would still be stuck with old recurrent networks that could only handle short sentences like “This is good/bad” and nothing more. Everything from long-context reasoning to multimodal understanding to code generation directly depends on the transformer — without it, the entire modern AI ecosystem simply wouldn’t exist.

  • TOON vs JSON: Revolutionizing Data Transmission for LLM

    In the booming era of artificial intelligence, large language models (LLMs) are transforming how we interact with data. Yet, a challenge remains—how do we efficiently send structured data to these models without inflating token usage and cost? Traditionally, JSON (JavaScript Object Notation) has been the universal data format for transmitting information. But a rising alternative called TOON (Token-Oriented Object Notation) is redefining the game with clever token efficiency designed specifically for AI.

    The JSON Legacy

    JSON’s simplicity and flexibility made it the staple format for data exchange. Its key-value pairs, arrays, and nested objects suit a wide variety of applications—from APIs to configuration files. However, JSON’s verbosity, with repeated keys, braces, quotes, and commas, leads to bloated token counts when used as input for LLMs. Each token sent to an AI model costs time and money, and JSON often inflates that cost unnecessarily.
    Example: Sending User Data in JSON
    Consider this simple list of users:

    {
    “users”: [
    { “id”: 1, “name”: “Alice”, “role”: “admin” },
    { “id”: 2, “name”: “Bob”, “role”: “user” },
    { “id”: 3, “name”: “Carol”, “role”: “user” }
    ]
    }

    Notice how the keys “id”, “name”, and “role” repeat three times, and punctuation characters add to the overall size. Each of these elements consumes tokens and adds to the cost of processing.

    TOON: Designed for LLMs

    TOON simplifies this by cutting down on redundancy and structural overhead. It adopts a lightweight, tabular format that declares headers once, followed by rows of values. This approach minimizes tokens while keeping the data structured and easy to understand.

    The Same User Data in TOON

    users[3]{id,name,role}:
    1,Alice,admin
    2,Bob,user
    3,Carol,user

    With TOON, the keys are declared a single time, followed by compact rows of comma-separated values. No quotes, no braces, and no repeated keys—just the essential data.

    Why TOON Excels in LLM Data Transmission

    • Token Efficiency: TOON reduces token usage by 30–60%, significantly lowering API costs and speeding up model response time.
    • Better Model Parsing: The explicit header row helps LLMs understand and process data with improved accuracy over verbose JSON.
    • Human Readability: The tabular style of TOON makes data easier to read and debug in context, especially for large datasets.
    • Ideal Use Cases: TOON shines with bulk, flat datasets like user lists, logs, product inventories, and analytics—where repetitive structures make JSON costly.

    When to Use JSON

    • For deeply nested or complex data structures requiring broad compatibility.
    • When existing toolchains and APIs demand standard JSON.
    • For irregular data that doesn’t fit TOON’s tabular format.

    As LLMs continue to shape the future, TOON offers a smart, specialized alternative to JSON—one that optimizes token usage, reduces operational costs, and improves clarity for AI-powered workflows. By adopting TOON where appropriate, developers and AI practitioners can unlock faster, cheaper, and more accurate interactions with language models.
    If you’re working with large, structured datasets in AI, the choice between JSON and TOON can have a material impact. TOON’s elegant design ensures your data travels light—perfectly tailored for the token-conscious world of large language models.

  • Why Microservices Aren’t the Answer (“until they really are”)

    If you aren’t solving a specific problem that demands microservices, you’re likely creating more problems than you’re solving.

    When it comes to architectural decisions, the lure of microservices is undeniable. You’ve read the success stories: scalability, modularity, independent deployments – a utopia of clean, manageable services. But let me tell you this: the true horror, pain, and suffering of microservices only reveal themselves when you run them in production.

    This isn’t to say microservices are inherently bad. Quite the contrary – when done right, they solve specific problems extremely well. But “done right” isn’t easy, and the cost of getting it wrong is higher than you might imagine.

    If you’re a small or medium-sized company, or even a team in the early stages of building software, start simple. The simplicity of a monolith isn’t a bad thing – it’s your safety net. I want to share some hard-won lessons that may save you from unnecessary chaos.

    Microservices Look Good on Paper

    On paper, microservices are simple:

    1. Split your app into small services.

    2. Each service owns one domain.

    3. Services can scale independently.

    4. Teams deploy and iterate faster.

    Sounds great, right? Each service can be built, tested, and deployed independently. You no longer have to deal with a bloated monolith.

    The reality, however, is much messier. Each “small” service adds complexity to your infrastructure, observability, testing, and coordination. The system as a whole becomes harder to debug, monitor, and scale holistically.

    If you aren’t solving a specific problem that demands microservices, you’re likely creating more problems than you’re solving.

    The Pain Points You Won’t See Coming

    Here are the key challenges you’ll face once microservices hit production:

    1. Distributed Debugging: Where Did It Fail?

    When your monolith crashes, you know where to look: the single application logs, the database, or a debugger. With microservices, failures ripple across services, each with its own logs, metrics, and traces.

    Imagine a user request traversing 10 services. If something fails, you now have to:

    • Correlate logs across multiple systems.

    • Determine which service failed.

    • Figure out why it failed.

    Without a proper observability strategy (tracing, logging, and metrics), debugging production issues becomes a nightmare. You’ll spend hours sifting through disconnected logs.

    2. Deployment Hell

    In a monolith, deploying is simple: one unit, one pipeline, one go-live. With microservices, you now have dozens or hundreds of services to coordinate. Even if each service is independently deployable, the dependencies between them introduce fragility.

    A breaking change in Service A might quietly kill Service B until you realize what happened.

    In production, this leads to:

    • Dependency hell: Services breaking each other unintentionally.

    • Version mismatches: Deployed code doesn’t align across environments.

    • Longer release times: Coordinating multiple pipelines is no joke.

    3. The Illusion of Independence

    One of the key promises of microservices is independent teams working autonomously. In reality, few services are truly independent. Most services:

    • Share data.

    • Rely on the same downstream services.

    • Use a common deployment platform.

    Breaking a shared database or common API will ripple across multiple services, defeating the independence you hoped for.

    4. Operational Complexity

    Microservices require heavy operational investment:

    • Observability: Logs, metrics, and tracing must be centralized.

    • Service Discovery: Services need to find and talk to each other dynamically.

    • Resiliency: Things will fail. You’ll need retries, fallbacks, and circuit breakers.

    • Networking: Latency, timeouts, and load balancing must be managed.

    If you don’t have the infrastructure in place to handle these, production incidents will come thick and fast.

    Microservices Solve Problems You May Not Have

    Microservices solve specific problems: scaling large teams, handling massive load, or managing disparate domains. For small teams or simpler systems, a monolith often makes more sense.

    A well-architected monolith:

    • Is easier to deploy and debug.

    • Has fewer network calls, reducing latency.

    • Allows you to focus on features, not operational overhead.

    If you don’t yet have the problems microservices solve, you’re prematurely complicating your architecture.


    Start Simple, Scale Smart

    Here’s my advice:

    1. Start with a monolith. Build a clean, modular monolith with strong boundaries between domains. Monoliths don’t have to be messy – you can use good design principles to keep things maintainable.

    2. Solve real problems. Only move to microservices when you outgrow the monolith. Are teams blocked by shared code? Is deployment taking forever? Are certain modules demanding independent scaling? Those are good reasons.

    3. Focus on infrastructure early. If you do decide to go the microservices route, invest in:

    • Logging, tracing, and metrics.

    • Deployment pipelines.

    • Testing frameworks for distributed systems.

    • Resiliency patterns like retries and circuit breakers.

    4. Split gradually. You don’t have to go “all in.” Start by extracting a single domain or service from your monolith. Get that running smoothly in production before breaking out more.

    Microservices are not a silver bullet. They won’t automatically make your system faster, your teams more productive, or your code cleaner. In fact, they’ll make everything harder until you have the right infrastructure, culture, and practices in place.

    So before you jump on the microservices bandwagon, ask yourself: Do you need them?

  • Is Node.js Really Single-Threaded?

    If you’re a developer working with Node.js, you’ve probably heard the statement, “Node.js is single-threaded.” It’s a common belief, but is it entirely accurate? Well, not exactly! While it’s true that your JavaScript code in Node.js runs on a single thread, there’s a lot more going on beneath the surface, especially when it comes to performance and I/O operations.

    Let’s explore what this really means, and break down the myth that Node.js is simply a single-threaded platform.

    The Common Misconception: “Node.js is Single-Threaded”

    At first glance, the idea that Node.js is single-threaded seems correct. After all, JavaScript—Node.js’s core language—runs on a single thread. This means that JavaScript processes one task at a time, making Node.js suitable for non-blocking operations.

    However, here’s where the confusion begins. People assume that everything in Node.js happens on this one thread, but this isn’t quite the whole truth, especially when it comes to handling things like reading files or making network requests.

    So, is Node.js really single-threaded? Not exactly.

    Let’s See an Example: A Simple File Read

    Imagine you’ve written a basic piece of code to read a file in Node.js:

    const fs = require('fs');
    
    console.log('Start reading a file...');
    fs.readFile('file.txt', (err, data) => {
      if (err) throw err;
      console.log('File content:', data.toString());
    });
    console.log('Continue doing other things...');
    

    At first, this looks straightforward. fs.readFile() is an asynchronous operation, meaning that when Node.js hits this line, it doesn’t wait for the file to be read before moving on. Instead, it immediately continues to the next console.log('Continue doing other things...'). The actual reading of the file happens behind the scenes, and once the file is ready, Node.js runs the callback to display the file content.

    Here’s the magic: although your JavaScript code runs on a single thread, Node.js itself doesn’t handle everything on that one thread. Let me introduce you to Node.js’s real workhorse: libuv.

    Meet libuv: The Multi-Threaded Powerhouse Behind Node.js

    libuv is a crucial part of Node.js. It’s a cross-platform library that allows Node.js to handle asynchronous I/O operations like file system access, network requests, timers, and more. This is where the multi-threaded aspect of Node.js comes in.

    In our file-reading example, when fs.readFile() is called, Node.js offloads this task to libuv. libuv assigns the file reading operation to one of its worker threads, allowing the main thread to keep running other code. Once the file is read, the result is handed back to the event loop, which then runs the callback on the main thread.

    This seamless handing off of I/O tasks to background threads is what makes Node.js incredibly efficient and non-blocking, even though your code runs in a single thread.

    So, How Does This Really Work?

    Here’s a breakdown of what’s happening:

    1. JavaScript Execution: The code you write, such as console.log(), loops, and function calls, is all executed on the main thread. This is the part that is truly “single-threaded.”
    2. I/O Operations: When you perform I/O tasks (like reading a file, sending a network request, or accessing a database), Node.js delegates these tasks to libuv. This library has a thread pool (typically 4 threads by default) that manages these operations in the background.
    3. The Event Loop: Once the I/O operation completes, libuv signals the event loop in Node.js. The event loop, which continuously monitors for completed tasks, triggers the callback (or resolves a promise) on the main thread once the result is ready.

    In essence, while your JavaScript runs on a single thread, Node.js leverages multiple threads internally to handle non-blocking operations like file I/O, ensuring that your application remains fast and responsive.

    Is Node.js Single-Threaded?

    Yes and No.

    • Yes, the JavaScript code you write runs on a single thread. This is why you can’t have multiple pieces of JavaScript code running in parallel unless you explicitly use something like Worker Threads.
    • No, because Node.js uses multiple threads under the hood to handle I/O operations (via libuv). This allows Node.js to offload time-consuming tasks like database queries, file access, and networking to background threads, freeing up the main thread for more tasks.

    This distinction is the key to Node.js’s non-blocking nature and why it’s great for building scalable, fast applications.

    Conclusion: Node.js – Single-Threaded but Not Really!

    So, is Node.js single-threaded? The answer is both yes and no.

    • Yes, your JavaScript code runs on a single thread.
    • No, because Node.js offloads I/O tasks to multiple threads via libuv, making it capable of handling asynchronous operations efficiently.

    Understanding this core concept is essential when working on performance improvements in Node.js. It helps you make better decisions about your code structure and avoid common pitfalls like blocking the main thread with CPU-bound tasks.

    Next time you hear someone say, “Node.js is single-threaded,” you’ll know the truth behind the myth—and you’ll be ready to explain how libuv and the event loop make Node.js a powerhouse for building fast, scalable applications!


    I hope this clears up the confusion and helps you understand how Node.js truly works under the hood. Happy coding!

  • Understanding setImmediate in Node.js: A Simple Guide


    What is setImmediate?

    If you’re working with Node.js, you might have come across a function called setImmediate. But what exactly does it do? Simply put, setImmediate is a function in Node.js that lets you execute a callback function as soon as the current event loop cycle is complete. It’s somewhat similar to setTimeout, but with a key difference: setImmediate is designed to run a task as soon as the current phase of the event loop is over.

    How Does the Node.js Event Loop Work?

    To really understand setImmediate, it helps to have a basic grasp of how the Node.js event loop works. The event loop is the engine that powers asynchronous programming in Node.js, allowing it to handle non-blocking I/O operations efficiently, even though JavaScript itself runs on a single thread.

    Here’s a simplified breakdown of the event loop phases:

    • Timers Phase: This is where callbacks scheduled by setTimeout and setInterval are executed.
    • Pending Callbacks Phase: Handles I/O callbacks that were deferred to the next loop iteration.
    • Idle, Prepare Phase: Mostly used for internal operations, not something we typically interact with directly.
    • Poll Phase: This phase handles retrieving new I/O events and executing I/O-related callbacks, like reading files.
    • Check Phase: This is where setImmediate callbacks are executed.
    • Close Callbacks Phase: Manages cleanup tasks, such as closing connections.

    Each of these phases handles a specific type of task, and the event loop cycles through them repeatedly to keep your Node.js application running smoothly.

    How setImmediate Works

    When you call setImmediate, Node.js schedules the callback function you pass to it to be executed in the “check phase” of the event loop. This phase happens right after the “poll phase,” which means your setImmediate callback will run after all the current operations are done, but before any I/O timers (setTimeout, setInterval) scheduled for the next loop iteration.

    Behind the Scenes: What Happens When You Use setImmediate?

    Let’s break down what actually happens when you call setImmediate:

    1. Call to setImmediate: When you use setImmediate(callback), Node.js stores your callback function in a queue that’s associated with the “check phase” of the event loop.
    2. Continue Current Execution: Node.js doesn’t stop what it’s doing. It continues executing the rest of your current code. The setImmediate function doesn’t block anything; it just schedules your callback for later.
    3. Event Loop Phases: The event loop moves through its phases. When it reaches the “check phase,” Node.js checks the queue and runs all the callbacks that were scheduled with setImmediate.
    4. Execute Callback: All the callbacks queued by setImmediate are executed during the “check phase.” This execution is asynchronous, which means the main thread won’t wait for these callbacks to finish before moving on to other tasks.

    Visualizing setImmediate with an Example

    Imagine you’re at a restaurant, which we’ll use as a metaphor for the event loop:

    • Current Task (Main Code): You’re eating your meal. This represents the main code that’s running right now.
    • Waiter (setImmediate): You tell the waiter to bring dessert as soon as you finish your meal (current event loop cycle). This represents setImmediate.
    • Check Phase (Serving Dessert): After you finish eating (current code execution), the waiter brings the dessert immediately before anything else happens in the restaurant (before the next event loop tasks).

    In this analogy, setImmediate ensures that the dessert (your callback) is served right after you finish eating, but before any other tasks the kitchen (event loop) might have.

    Key Takeaways

    • Immediate Execution After Current Code: setImmediate ensures your callback runs as soon as possible after the current code execution, but it still gives Node.js a chance to handle other tasks, like I/O operations, in the meantime.
    • Non-Blocking: It doesn’t block your main code. The main code finishes first, and then setImmediate runs.
    • Order of Execution: If you have multiple setImmediate calls, they will be executed in the order they were scheduled.

    When Should You Use setImmediate?

    setImmediate is particularly useful when you want to defer tasks that can wait until after the current phase of the event loop finishes. This can be helpful for background tasks, like logging, updating status reports, or performing any non-critical operations that don’t need to happen right away. For example, if you’re processing a subscription creation in your app and need to update a report after each step, you can use setImmediate to handle the reporting in a way that doesn’t slow down the main process.

    Conclusion

    setImmediate is a handy tool in Node.js for scheduling tasks that should run as soon as the current event loop phase finishes, but before any setTimeout or setInterval callbacks that might be scheduled for the next loop iteration. By understanding how and when to use setImmediate, you can write more efficient and non-blocking code, keeping your Node.js applications running smoothly.


  • Thinking Models: Navigating Complexity in Software Engineering

    My journey into the world of thinking models began unexpectedly. It wasn’t until I delved into the book “The Great Mental Model” recently that I discovered the concept of thinking models. This sparked my curiosity to explore what thinking models exist specifically within the realm of software engineering. Let’s embark on this exploration together, diving into some of these models and illustrating their impact with real-world examples in the domain of software engineering.

    Systems Thinking: Seeing the Forest for the Trees

    Understanding the Whole System: Systems thinking compels us to look beyond individual components of a software system to see the entire ecosystem. It’s about understanding how parts interact, influence one another, and contribute to the system’s overall behavior.

    Real-World Application: Consider the optimization of a cloud-based storage service. By employing systems thinking, engineers identified that the bottleneck wasn’t just the storage retrieval speed, but also the way data was replicated and cached across regions. By addressing these interrelated components as a whole, they significantly improved data retrieval times and user satisfaction.

    The Pareto Principle: Focusing on the Vital Few

    Maximizing Impact with Minimal Effort: The Pareto Principle, or the 80/20 rule, suggests that a small number of causes often lead to a large portion of the effects. In software engineering, this means identifying the critical features or bugs that will have the most significant impact on performance or user experience.

    Real-World Application: A social media platform used the Pareto Principle to enhance its moderation system. By analyzing user reports, they discovered that 20% of the content types were responsible for 80% of the community’s complaints. Targeting these content types for stricter moderation policies resulted in a significant decrease in user complaints.

    First Principles Thinking: Building from the Ground Up

    Questioning Assumptions for Innovative Solutions: First principles thinking involves breaking down complex problems into their most basic elements and reassembling them in new, innovative ways. It encourages questioning assumptions and approaching problems from a foundational perspective.

    Real-World Application: In developing a new encryption algorithm, a team applied first principles thinking to challenge existing assumptions about cryptographic security and computational efficiency. This approach led them to develop a novel algorithm that was not only more secure but also more efficient, revolutionizing data protection strategies in their application.

    The OODA Loop: Staying Agile in Decision Making

    Cycle of Observation and Action: The OODA Loop, standing for Observe, Orient, Decide, Act, is a model developed for quick, effective decision-making. It’s about constantly assessing the situation, making decisions, and adapting to outcomes—a perfect fit for the agile nature of software development.

    Real-World Application: A mobile game development team applied the OODA Loop to rapidly iterate on their game’s design based on player feedback. By quickly observing player behaviors, orienting their strategy accordingly, deciding on the changes, and implementing them, they were able to significantly improve player engagement and retention.

    Computational Thinking: Problem Solving Like a Computer Scientist

    Systematic Approach to Problem Solving: Computational thinking is about approaching problems in a way that a computer might solve them. It involves skills such as decomposition, recognizing patterns, abstracting, and creating algorithms.

    Real-World Application: Faced with the challenge of improving search functionality in a large database, a team employed computational thinking to decompose the problem, identify inefficient search patterns, and abstract these patterns into a more efficient search algorithm. The result was a dramatic reduction in search times and an improvement in user experience.

    Embracing Diverse Thinking Models

    The application of these thinking models in software engineering not only broadens our approach to solving problems but also ignites creativity, improves efficiency, and enhances our ability to develop solutions that truly meet user needs. By adopting models like Systems Thinking, the Pareto Principle, First Principles Thinking, the OODA Loop, and Computational Thinking, engineers can navigate the complexities of software development with greater agility and inventiveness.

    As the field of software engineering continues to advance, embracing these diverse thinking models will be crucial for staying ahead of the curve. They empower us to dissect intricate challenges, foster innovation, and ultimately, craft software solutions that are not only technically robust but also deeply impactful.

  • Common RESTful API Design Mistakes and Their Remedies

    Designing RESTful APIs is an art that requires attention to detail, a deep understanding of HTTP, and a commitment to the principles laid out by Roy Fielding. However, many APIs that claim to be RESTful fall short of these principles. In this blog, let’s explore 20 common mistakes and their remedies :

    1. Misuse of HTTP Methods

    • Non-RESTful: POST /getSubscription?id=123
    • RESTful Remedy: GET /subscriptions/123

    2. Ignoring Caching Opportunities

    • Non-RESTful: Responses without Cache-Control.
    • RESTful Remedy: Include Cache-Control: max-age=3600 in GET responses for subscriptions.

    3. Inconsistent Error Handling

    • Non-RESTful: {"error": "It failed"}
    • RESTful Remedy: {"error": {"code": 404, "message": "Subscription not found"}} with a 404 status code.

    4. Lack of Resource Nesting

    • Non-RESTful: /subscriptions, /users
    • RESTful Remedy: /users/123/subscriptions to list subscriptions for user 123.

    5. Overuse of Query Parameters

    • Non-RESTful: /subscriptions?userId=123
    • RESTful Remedy: /users/123/subscriptions

    6. Forgetting Pagination

    • Non-RESTful: Returning all subscriptions in one call.
    • RESTful Remedy: /subscriptions?page=1&limit=10

    7. Failing to Version the API

    • Non-RESTful: Directly modifying the API structure.
    • RESTful Remedy: /v2/subscriptions/123

    8. Underutilizing HTTP Headers

    • Non-RESTful: Passing API keys as query parameters.
    • RESTful Remedy: Authorization: Bearer <token>

    9. Not Supporting Conditional Requests

    • Non-RESTful: Ignoring use of ETag or Last-Modified.
    • RESTful Remedy: Respond with ETag header and honor If-None-Match requests.

    10. Poor Resource Identification

    • Non-RESTful: Ambiguous identifiers.
    • RESTful Remedy: /subscriptions/{uuid} where uuid is a unique identifier.

    11. Ignoring Idempotency in Non-GET Requests

    • Non-RESTful: POST /subscriptions/123/renew leading to multiple renewals.
    • RESTful Remedy: PUT /subscriptions/123/renewal to renew idempotently.

    12. Not Leveraging HATEOAS

    • Non-RESTful: { "subscriptionId": "123", "status": "active" }
    • RESTful Remedy:

    { "subscriptionId": "123", "status": "active", "_links": { "self": { "href": "/subscriptions/123" }, "cancel": { "href": "/subscriptions/123/cancel" } } }

    13. Mixing Singular and Plural Nouns

    • Non-RESTful: Inconsistent use of singular and plural in resource paths.
    • RESTful Remedy: Consistently use plural nouns for resource names: /subscriptions for collection routes and /subscriptions/{id} for specific entities.

    14. Exposing Internal Implementation Details

    • Non-RESTful: Revealing database IDs or internal structure through APIs.
    • RESTful Remedy: Use abstracted, meaningful identifiers: /subscriptions/12345 where 12345 is an opaque identifier.

    15. Not Providing Filtering, Sorting, or Searching Capabilities

    • Non-RESTful: Forcing clients to download entire data sets.
    • RESTful Remedy: Implement query parameters for server-side operations: /subscriptions?status=active&sortBy=startDate

    16. Lack of Media Type Negotiation

    • Non-RESTful: Only supporting application/json.
    • RESTful Remedy: Use the Accept header for format requests. Respond to Accept: application/xml for XML format.

    17. Incomplete or Missing Documentation

    • Non-RESTful: APIs without comprehensive documentation.
    • RESTful Remedy: Provide detailed, up-to-date documentation portal.

    18. Not Utilizing PATCH for Partial Updates

    • Non-RESTful: Using PUT for all updates.
    • RESTful Remedy: Implement PATCH for partial resource updates.

    19. Treating Collections and Individual Resources the Same

    • Non-RESTful: Same endpoint for collection and individual resource operations.
    • RESTful Remedy: Differentiate endpoints for collections and individual resources: POST /subscriptions for creating and GET /subscriptions/123 for fetching.

    20. Not Handling Asynchronous Operations Properly

    • Non-RESTful: Expecting synchronous completion of long-running tasks.
    • RESTful Remedy: Use asynchronous patterns with initial 202 Accepted responses: POST /subscriptions/123/renew returns 202 Accepted with a Location header for status checks.

  • The Strangler Pattern: A Pragmatic Approach to Modernizing Legacy Applications


    Introduction

    Updating a legacy software system is a critical challenge many businesses face. It’s about enhancing what works while introducing new, efficient technologies. This is where the Strangler Pattern shines. It offers a structured yet flexible way to modernize systems without the need for a complete overhaul from the start. Let’s explore how this approach makes the complex process of software upgrade more manageable and less risky.

    The Legacy Software Dilemma

    Legacy systems are often the bedrock of a business but come with their set of problems. They might be built on outdated technologies that are hard to integrate with newer systems, and their monolithic nature can make any change a risky venture. The challenge lies in updating these systems without disrupting the daily flow of business operations.

    Key Challenges in Legacy Systems

    1. Outdated Technologies: Systems built on older frameworks struggle to integrate with newer, more efficient technologies.
    2. Monolithic Architecture: Changes in one part of the system can have unforeseen effects on others, complicating updates and bug fixes.
    3. Integration Complexities: Bridging the gap between older systems and modern development practices is often not straightforward.

    Implementing the Strangler Pattern

    1. Module Identification: The first step is to identify independent modules within the application that can be updated separately.
    2. Parallel Development: Develop new features alongside the existing system. This is akin to building a new structure next to an old one.
    3. Redirecting Traffic: Gradually shift the user interactions from the old components to the new ones, ensuring the new system is reliable before fully transitioning.
    4. Phasing Out Old Components: Once the new system is stable, start retiring the old components.
    5. Repeat the Process: Continue this process, one module at a time, until the entire system is updated.

    A Real-World Example

    Consider modernizing the user authentication system of an e-commerce platform. Instead of revamping the entire platform, the authentication module is rebuilt using modern standards and gradually integrated into the existing system. This way, the platform remains operational while the module is updated.

    Conclusion

    The Strangler Pattern offers a strategic way to modernize legacy systems. By focusing on one component at a time and ensuring each new piece works seamlessly before moving on to the next, it reduces risk and allows for a smoother transition to modern technologies. It’s a practical approach for businesses looking to update their software systems efficiently and effectively.


  • How to replace if-else with polymorphism

    In object-oriented programming, the use of if-else statements can quickly make code difficult to read, understand and maintain. As the number of conditions and their complexity increases, so does the potential for errors and bugs. Polymorphism, on the other hand, offers a more elegant and efficient solution to this problem. By allowing objects of different classes to be treated as objects of a common superclass, polymorphism can simplify the code, making it more readable and extendable. In this article, we will explore how to improve if-else statements with polymorphism and how it can help you write better and more maintainable code. Let us understand this with examples:-

    class ShapeCalculator {
        public double CalculateArea(Shape shape) {
            if (shape is Circle) {
                Circle c = (Circle) shape;
                return Math.PI * c.radius * c.radius;
            } else if (shape is Rectangle) {
                Rectangle r = (Rectangle) shape;
                return r.width * r.height;
            }
            return 0.0;
        }
    }
    

    In this example, the CalculateArea method uses an if-else statement to check the type of the shape object and then calls the appropriate calculation method for that type. This approach works, but it’s less maintainable and extendable than using polymorphism.

    For example if a new shape ‘Square’ is added to the code, we will have to add an additional ‘else if’ block to handle that. And every time a new shape is added, we will have to modify the CalculateArea method which can lead to errors and increased complexity in the code. On the other hand, with polymorphism, as every class knows how to calculate its area, this problem can be avoided.

    class Shape {
        public virtual double Area() {
            return 0.0;
        }
    }
    
    class Circle : Shape {
        private double radius;
        public Circle(double r) {
            radius = r;
        }
        public override double Area() {
            return Math.PI * radius * radius;
        }
    }
    
    class Rectangle : Shape {
        private double width;
        private double height;
        public Rectangle(double w, double h) {
            width = w;
            height = h;
        }
        public override double Area() {
            return width * height;
        }
    }
    
    class Triangle : Shape {
        private double base;
        private double height;
        public Triangle(double b, double h) {
            base = b;
            height = h;
        }
        public override double Area() {
            return 0.5 * base * height;
        }
    }
    
    

    Now we can use this class to calculate the area of any shape, whether it’s a circle, rectangle, or triangle, without having to use any if-else statement. Because each shape knows how to calculate its own area through the Area method, the CalculateArea method can simply call the Area method on the passed-in shape object, regardless of its actual type.

    The use of polymorphism here allows the calculation of area to be done in a more maintainable and extendable way. As it allows you to add new shape with new area calculation method without modifying the existing code.

    In conclusion, polymorphism allows us to replace a series of if-else statements with virtual functions, making our code more readable, maintainable and extendable.

  • Five React must-follow practices

    Here are five best practices for working with React:

    1.Use functional components whenever possible: Functional components are simpler and easier to understand than class-based components, and they can often do everything that class-based components can do. For example:

    2. Use the useEffect hook for side effects: The useEffect hook allows you to perform side effects (such as API calls or subscribing to events) in functional components. It is a safer and more efficient alternative to the componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods. For example:

    3. Use the useMemo hook for performance optimization: The useMemo hook allows you to memoize the result of expensive calculations so that they are only recalculated when the input dependencies change. This can improve the performance of your application by avoiding unnecessary re-renders. For example:

    4. Use the useCallback hook to avoid unnecessary re-renders: The useCallback hook allows you to return a memoized callback function that will not change unless one of its dependencies changes. This can be useful for avoiding unnecessary re-renders when passing callback props to child components. For example:

    5. Use the React.Fragment component to avoid unnecessary DOM nodes: The React.Fragment component allows you to return multiple elements from a component without adding an extra DOM node. This can be useful for keeping your DOM structure clean and avoid issues with CSS or other DOM manipulation. For example:

  • Patterns for designing self healing apps

    Self healing is important concern while developing an application. For example, if a downstream service is not available , how can the app handle this situation? Will it retry more for the service which is already down or will it understand the situation and stop hammering a failing service. What if there is failure in one subsystem which can sometime cascade, for example a thread or a socket not getting freed in timely manner can result to cascading failures. All too often, success path is well tested but not the failure path. There are many patterns for handling these failures but here are few must have pattern to gracefully handle these situation.

    PatternPremiseAkaHow does the pattern mitigate?
    Retry failed operations with retry strategy (Retry Pattern)
    Implementation Detail
    Many faults are transient and may self-correct after a short delay. Have a retry pattern with strategy of increasing delay. “Maybe it’s just a blip”Allows configuring automatic retries.
    Protecting failing app with Circuit Breaker
    (Circuit-breaker)
    Implementation Detail
    When a system is seriously struggling, failing fast is better than making users/callers wait.

    Protecting a faulting system from overload can help it recover.
    “Stop doing it if it hurts”

    “Give that system a break”
    Breaks the circuit (blocks executions) for a period, when faults exceed some pre-configured threshold.
    Better caller experience with timeout
    (Timeout)
    Implementation Detail
    Beyond a certain wait, a success result is unlikely.“Don’t wait forever”Guarantees the caller won’t have to wait beyond the timeout.
    Isolate critical resources (Bulkhead Isolation)
    Implementation Detail
    A Ship should not sink because there is hole in one place. When a process faults, multiple failing calls can stack up (if unbounded) and can easily swamp resource (threads/ CPU/ memory) in a host.

    This can affect performance more widely by starving other operations of resource, bringing down the host, or causing cascading failures upstream.
    “One fault shouldn’t sink the whole ship”Constrains the governed actions to a fixed-size resource pool, isolating their potential to affect others.
    Throttle clients with Rate Limit (Rate-limit)
     Implementation Detail
    Limiting the rate a system handles requests is another way to control load.

    This can apply to the way your system accepts incoming calls, and/or to the way you call downstream services.
    “Slow down a bit, will you?”Constrains executions to not exceed a certain rate.
    Fallback
    Implementation Detail
    Things will still fail – plan what you will do when that happens.“Degrade gracefully”Defines an alternative value to be returned (or action to be executed) on failure.

  • What’s wrong with just hashing a password?

    Storing password is critical for any application. If you do not take right precaution then you loose your user password to attacker. For password security, storing password in plain text in database is certainly a bad design. Hashing password is well known but unfortunately it is also not enough. As we know that when user tries to log in, the hash of the password they entered is checked against the hash of their password in the database. If the hash matches, the user gains access to the account. If an attacker gains access to password database, they can use the rainbow table attack to compare hashed passwords to potential hashes in the table. The rainbow table then gives plain text possibilities with each hash, which the attacker can use to access an account. For example, if attacker has a rainbow table with the hash for the password “welcome123” any user that uses that password will have the same hash, so that password can easily be cracked.

    To mitigate this attack, we use password salting. As per OWASP “a salt is a unique, randomly generated string that is added to each password as part of the hashing process”.

    The password in the database can be stored in the following format Hash(password + salt). A salt randomizes each hash by adding random data that is unique to each user to their password hash, so even the same password has a unique hash. If someone tried to compare hashes in a rainbow table to those in a database, none of the hashes would match, even if the passwords were the same.

    Nonetheless, rainbow tables may not be the biggest threat to organizations today. Still, they are certainly a threat and should be considered and accounted for as part of an overall security strategy.

  • 5 Tech Talks Every Software Engineer should watch

    I love watching tech talks. I’ve compiled a list of must-see tech talks for every software engineer. However, this list doesn’t have any category on the basis of programming language or platform instead it focuses on high level general topics:

    How to Design A Good API and Why it matters, a wonderful tech by Joshua Bloch at Google.

    The Myth of the Genius Programmer, I’m glad I watched this google tech talk.

    How To Write Clean Testable Code, a tech talk by Misko Hevery.

    How To Become Effective Engineer, by Edmond Lau at Google. I recommend his book The Effective Engineer also.

    Java Script:The Good Part, a wonderful tech talk for Java script lovers

Create a website or blog at WordPress.com

 

Loading Comments...
 

    • Subscribe Subscribed
      • Ratnesh Dubey- Engineering & AI Insights
      • Already have a WordPress.com account? Log in now.
      • Ratnesh Dubey- Engineering & AI Insights
      • Subscribe Subscribed
      • Sign up
      • Log in
      • Report this content
      • View site in Reader
      • Manage subscriptions
      • Collapse this bar