<?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: Daniel Malek</title>
    <description>The latest articles on DEV Community by Daniel Malek (@dan1618).</description>
    <link>https://dev.to/dan1618</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1887353%2F42dbc22f-c002-49c9-b796-8933fe0df800.png</url>
      <title>DEV Community: Daniel Malek</title>
      <link>https://dev.to/dan1618</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dan1618"/>
    <language>en</language>
    <item>
      <title>Building a Knowledge Base with RAG Using NestJS, LangChain and OpenAI</title>
      <dc:creator>Daniel Malek</dc:creator>
      <pubDate>Tue, 03 Mar 2026 11:27:12 +0000</pubDate>
      <link>https://dev.to/dan1618/building-a-knowledge-base-with-rag-using-nestjs-langchain-and-openai-12nk</link>
      <guid>https://dev.to/dan1618/building-a-knowledge-base-with-rag-using-nestjs-langchain-and-openai-12nk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/Dan1618/Articles-rag" rel="noopener noreferrer"&gt;github.com/Dan1618/Articles-rag&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. What We're Building and Why
&lt;/h2&gt;

&lt;p&gt;Retrieval-Augmented Generation (RAG) is a technique that enhances Large Language Model responses by grounding them in external data. Instead of relying solely on the model's training data, RAG retrieves relevant information from your own curated sources and injects it into the prompt, producing answers that are more accurate and up-to-date.&lt;br&gt;
In this project we build a system where you can &lt;strong&gt;save articles from the web into a vector database&lt;/strong&gt; and then &lt;strong&gt;ask questions about their content&lt;/strong&gt; through a chat-like interface. Think of it as assembling a personal knowledge base: every article you feed in becomes searchable context for future queries. &lt;br&gt;
Could you just point a bot at a live website each time? Sure — but by persisting the data in a vector store you are &lt;strong&gt;building a knowledge base that grows over time&lt;/strong&gt;. There is nothing stopping you from combining both approaches, or extending the pipeline to ingest PDFs and other document types as well.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Stack
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Backend framework&lt;/strong&gt; - NestJS &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Templating / UI&lt;/strong&gt; - Handlebars &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM orchestration&lt;/strong&gt; - LangChain&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vector store&lt;/strong&gt; - FAISS (local) &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embeddings &amp;amp; chat model&lt;/strong&gt; - OpenAI API&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LangChain&lt;/strong&gt; is an open-source framework that simplifies building applications powered by language models. It provides ready-made abstractions for document loading, text splitting, embedding, vector storage, and chaining LLM calls together — so you can focus on your application logic rather than low-level plumbing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FAISS&lt;/strong&gt; (Facebook AI Similarity Search) is a library for efficient similarity search over dense vectors. We use it here as a &lt;em&gt;local&lt;/em&gt; vector store, which is simpler to set up and demonstrate than a hosted vector database, while still being fast enough for production-grade similarity lookups.&lt;/p&gt;
&lt;h3&gt;
  
  
  The UI
&lt;/h3&gt;

&lt;p&gt;The user interface includes three input fields for links, a field for asking related questions, and buttons to save articles to FAISS or generate answers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4uj393v18tdwvmwyfdfm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4uj393v18tdwvmwyfdfm.png" alt=" " width="606" height="645"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Technical Implementation
&lt;/h2&gt;

&lt;p&gt;The application is split into two main services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IngestService&lt;/code&gt;&lt;/strong&gt; — loads articles from the web, splits them into chunks, creates embeddings, and saves them to the FAISS index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AppService&lt;/code&gt;&lt;/strong&gt; — loads the saved FAISS index, retrieves relevant chunks for a given question, and runs a Map-Reduce QA chain to produce an answer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2.1 Setup — NestJS + Handlebars
&lt;/h3&gt;

&lt;p&gt;NestJS gives us a structured, modular backend with dependency injection out of the box. Handlebars is wired in as the view engine so we can serve a lightweight chat-style UI without pulling in a full frontend framework. The two services above are standard NestJS &lt;code&gt;@Injectable()&lt;/code&gt; providers.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.2 Ingesting Articles
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;IngestService.ingest()&lt;/code&gt; method handles the entire pipeline from raw URL to searchable vector store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IngestService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;directory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;faiss_store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;urlsFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;urls.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;ingest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Building new FAISS store...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. Load data from each URL using Cheerio&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CheerioWebBaseLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadedDocs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;loadedDocs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Split documents into chunks&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textSplitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;separators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;chunkSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;splitDocs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;textSplitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. Create embeddings and persist the FAISS index&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vectorStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;FaissStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;splitDocs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;urlsFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's walk through the key stages.&lt;/p&gt;

&lt;h4&gt;
  
  
  Loading — &lt;code&gt;CheerioWebBaseLoader&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;CheerioWebBaseLoader&lt;/code&gt; is a LangChain document loader that fetches a web page and extracts its text content using the Cheerio HTML parser. Each URL becomes a &lt;code&gt;Document&lt;/code&gt; object containing the page text and metadata.&lt;/p&gt;

&lt;h4&gt;
  
  
  Splitting — Why Chunks Matter
&lt;/h4&gt;

&lt;p&gt;LLMs have a finite &lt;strong&gt;context window&lt;/strong&gt; — the maximum number of tokens a model can process in a single request. If a model has a 4,000-token limit and your prompt already uses 3,500 tokens, only 500 tokens are left for the completion. A full article easily exceeds that budget, so we need to &lt;strong&gt;split it into smaller chunks&lt;/strong&gt; that fit comfortably inside the context window.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt; handles this by trying a hierarchy of separators (&lt;code&gt;\n\n&lt;/code&gt;, &lt;code&gt;\n&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;,&lt;/code&gt;) to find natural break points. We set &lt;code&gt;chunkSize: 1000&lt;/code&gt; to keep each chunk under 1,000 characters.&lt;/p&gt;

&lt;h5&gt;
  
  
  Overlap Chunks
&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt; also supports an &lt;strong&gt;overlap&lt;/strong&gt; option (&lt;code&gt;chunkOverlap&lt;/code&gt;), which allows adjacent chunks to share a portion of text at their boundaries. Think of it like the "previously on…" recap at the start of a TV episode followed by the "coming up next" teaser at the end — it ensures that context isn't lost at the seams between chunks. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcevtj486h5qon50bxqyg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcevtj486h5qon50bxqyg.jpg" alt=" " width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Embedding &amp;amp; Saving — &lt;code&gt;FaissStore.fromDocuments&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;FaissStore.fromDocuments(splitDocs, embeddings)&lt;/code&gt; sends each chunk to the OpenAI Embeddings API, converts the text into high-dimensional vectors, and indexes them in a FAISS store. The resulting index is then saved to disk with &lt;code&gt;vectorStore.save()&lt;/code&gt;, so it can be reloaded later without re-embedding.&lt;/p&gt;

&lt;p&gt;It is worth highlighting here that to vectorize the data, the app connects to OpenAI API, using it is not free (although for some older models like 'gpt-4o-mini' it is still quite cheap). If you want to use this application please remember to include your OpenAI API key in .env file. You can see the logs of connecting to OpenAI in the console, similar process will take place when retrieving the data from the the FAISS index when answering questions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8l37mob0w3za3wplt1j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8l37mob0w3za3wplt1j.png" alt=" " width="763" height="79"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  2.3 Answering Questions
&lt;/h3&gt;

&lt;p&gt;Once the articles are ingested, the &lt;code&gt;AppService.answerQuestion()&lt;/code&gt; method handles the retrieval and answering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;answerQuestion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vectorStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;FaissStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;modelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loadQAChain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;map_reduce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Retrieve the most relevant chunks&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asRetriever&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retrievedDocs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Run the QA chain&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;input_documents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;retrievedDocs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the sources&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retrievedDocs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sources&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step by step:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Load the vector store&lt;/strong&gt; — &lt;code&gt;FaissStore.load()&lt;/code&gt; reads the previously saved index from disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create the LLM&lt;/strong&gt; — We use &lt;code&gt;gpt-4o-mini&lt;/code&gt; with a temperature of 0.7 for a good balance of creativity and accuracy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieve relevant chunks&lt;/strong&gt; — &lt;code&gt;vectorStore.asRetriever()&lt;/code&gt; returns a retriever that performs a similarity search. When we call &lt;code&gt;retriever.invoke(question)&lt;/code&gt;, it embeds the question and finds the most similar chunks in the FAISS index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the Map-Reduce chain&lt;/strong&gt; — The retrieved documents and the question are passed into the chain, which produces the final answer.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  2.4 Map-Reduce: Reassembling the Chunks
&lt;/h3&gt;

&lt;p&gt;When we split an article into chunks for ingestion, we eventually need a strategy to &lt;strong&gt;recombine&lt;/strong&gt; those chunks when answering a question. This is where the &lt;strong&gt;Map-Reduce&lt;/strong&gt; pattern comes in.   &lt;/p&gt;

&lt;p&gt;In the context of LLM applications, Map-Reduce operates in two phases: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Map&lt;/strong&gt; — Each retrieved chunk is sent to the LLM individually. The model extracts or summarizes only the information relevant to the question, producing a &lt;em&gt;filtered chunk&lt;/em&gt; (FC). This step runs in parallel, and its primary goal is to reduce the size of each chunk down to the essential content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce&lt;/strong&gt; — All the filtered chunks are combined into a single summary, which is then sent to the LLM along with the original question to produce the final answer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqjmmnoaw2who90zxk36.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqjmmnoaw2who90zxk36.jpg" alt=" " width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LangChain's &lt;code&gt;loadQAChain&lt;/code&gt; with &lt;code&gt;type: 'map_reduce'&lt;/code&gt; wires this up for you. Under the hood it uses two sub-chains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;LLM chain&lt;/strong&gt; that processes each individual document (the Map step).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;combine documents chain&lt;/strong&gt; that merges the Map outputs into one cohesive input for the final LLM call (the Reduce step).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The "Stuff" Optimization
&lt;/h4&gt;

&lt;p&gt;Although in this implementation we set &lt;code&gt;map_reduce&lt;/code&gt; explicitly, LangChain includes an internal optimization: if the total retrieved text (all chunks combined) is smaller than the LLM's context window or a pre-defined &lt;code&gt;token_max&lt;/code&gt; limit, the chain detects that it is &lt;strong&gt;cheaper and faster to skip the Map phase entirely&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of performing multiple Map calls followed by one Reduce call, it simply &lt;strong&gt;"stuffs"&lt;/strong&gt; all the documents into a single prompt and makes one LLM call. This automatic fallback saves both time and API cost when the input is small enough to fit. &lt;/p&gt;




&lt;h2&gt;
  
  
  3. Summary and Ideas for the Future
&lt;/h2&gt;

&lt;p&gt;We've built a RAG pipeline that ingests articles from the web, stores them in a local FAISS vector store, and answers questions using a Map-Reduce QA chain powered by OpenAI. The key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAG grounds LLM responses&lt;/strong&gt; in your own data, making answers more accurate and verifiable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunking with overlap&lt;/strong&gt; preserves context across split boundaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map-Reduce&lt;/strong&gt; elegantly handles cases where retrieved content exceeds the context window, with an automatic "Stuff" fallback for smaller inputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FAISS&lt;/strong&gt; provides a zero-infrastructure vector store that is perfect for demos and small-to-medium workloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where to Go from Here
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add PDF and file ingestion&lt;/strong&gt; — extend the loader to support &lt;code&gt;PDFLoader&lt;/code&gt;, &lt;code&gt;TextLoader&lt;/code&gt;, and other LangChain document loaders for a richer knowledge base.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent hosted vector store&lt;/strong&gt; — migrate from local FAISS to a managed solution like Pinecone, Weaviate, or Qdrant for multi-user, production-grade deployments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming responses&lt;/strong&gt; — use LangChain's streaming callbacks to deliver answers token-by-token for a more responsive chat experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid retrieval&lt;/strong&gt; — combine vector similarity search with keyword-based (BM25) retrieval for better recall on exact-match queries.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nestjs</category>
      <category>langchain</category>
      <category>rag</category>
      <category>ai</category>
    </item>
    <item>
      <title>You can not make a reverse engineering of “why” somebody made a decision</title>
      <dc:creator>Daniel Malek</dc:creator>
      <pubDate>Thu, 01 May 2025 19:58:46 +0000</pubDate>
      <link>https://dev.to/dan1618/you-can-not-make-a-reverse-engineering-of-why-somebody-made-a-decision-4imk</link>
      <guid>https://dev.to/dan1618/you-can-not-make-a-reverse-engineering-of-why-somebody-made-a-decision-4imk</guid>
      <description>&lt;p&gt;Reverse engineering is a process of disassembling and analyzing a finished product to understand how it works, often with the goal of recreating or improving upon it. It is also commonly met in software development, when you want to know how a program works. &lt;/p&gt;

&lt;p&gt;To achieve understanding of existing code you need to use a special debugging tools and typically spend a lot of time. Once you understand it as a whole, you can do whatever you need… usually. What if you need to write further feature, but the code was written in a not standard way or there is a workaround, which you can not simply ignore? In such situation you would want to know why the code is made this way to safely finish your task. But the &lt;strong&gt;reverse engineering process might not answer the question “why”.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Similar situations might take place in other engineering fields, like building construction, let me bring some examples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Space Planning and Layout:&lt;/strong&gt;&lt;br&gt;
&lt;u&gt;The Observable:&lt;/u&gt; The kitchen is located on the north side of the house. &lt;br&gt;
&lt;u&gt;The Difficulty in Reverse Engineering:&lt;/u&gt; Was it to maximize natural light in the living areas on the south? Was it due to plumbing constraints? Did the homeowner prefer a cooler kitchen? Was it simply the most logical flow based on the overall footprint of the house? Maybe the architect had a specific design philosophy about the placement of service areas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Material Selection:&lt;/strong&gt;&lt;br&gt;
&lt;u&gt;The Observable:&lt;/u&gt; A specific type of brick was chosen for the facade. &lt;br&gt;
&lt;u&gt;The Difficulty in Reverse Engineering:&lt;/u&gt; Was it solely based on cost? Aesthetics? Durability in the local climate? A personal preference of the architect? A long-standing relationship with a particular supplier offering a "good deal"?&lt;br&gt;
Perhaps the architect envisioned a certain texture or color that only this brick provided, even if alternatives existed. Maybe local material availability played a significant role that isn't explicitly documented. &lt;br&gt;
  &lt;br&gt;
If a new person was assigned to building project and needs to make changes in the constructions or architecture, he will definitively need to understand why things has been designed certain way to proceed.&lt;/p&gt;

&lt;p&gt;Same applies to software engineering, let’s check up:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Choice of Programming Language:&lt;/strong&gt;&lt;br&gt;
&lt;u&gt;The Observable:&lt;/u&gt; The application is built using Python. &lt;br&gt;
&lt;u&gt;The Difficulty in Reverse Engineering:&lt;/u&gt; Was it due to the team's existing expertise? The availability of specific libraries or frameworks? The perceived speed of development? Performance requirements for certain tasks? Perhaps the initial prototype was quickly built in Python, and the team decided to stick with it. Maybe the lead developer had a strong preference for Python's syntax and ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Architectural Pattern Selection:&lt;/strong&gt;&lt;br&gt;
&lt;u&gt;The Observable:&lt;/u&gt; The software follows a microservices architecture. &lt;br&gt;
&lt;u&gt;The Difficulty in Reverse Engineering:&lt;/u&gt; Was it chosen for scalability and independent deployments? To allow different teams to work on separate parts? Did the team anticipate a large and complex system from the outset? Perhaps a previous monolithic architecture proved difficult to maintain and evolve. Maybe the team wanted to experiment with a modern architectural style.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use of a Specific Library or Framework Version:&lt;/strong&gt;&lt;br&gt;
&lt;u&gt;The Observable:&lt;/u&gt; The project uses version 3.2.1 of a particular library. &lt;br&gt;
&lt;u&gt;The Difficulty in Reverse Engineering:&lt;/u&gt; Was this version chosen because it was the latest stable release at the time? Because it offered a specific feature that was required? Or perhaps because the team had prior experience with that exact version and felt comfortable with it? Maybe a newer version introduced breaking changes that the team wasn't ready to address. Without commit messages or project notes detailing the upgrade (or lack thereof), the exact reasoning is speculative. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Implementation Details of the Singleton Pattern:&lt;/strong&gt;&lt;br&gt;
&lt;u&gt;The Observable:&lt;/u&gt; A class is implemented as a Singleton. &lt;br&gt;
&lt;u&gt;The Difficulty in Reverse Engineering:&lt;/u&gt; Was the Singleton pattern used to ensure a single instance for resource management? To provide a global point of access? Were the potential drawbacks of Singletons (like tight coupling and testability issues) fully considered? Were lazy initialization or thread-safe implementations specific requirements that influenced the chosen approach?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Implementation Details of a Specific Algorithm:&lt;/strong&gt;&lt;br&gt;
&lt;u&gt;The Observable:&lt;/u&gt; A particular sorting algorithm is implemented in a specific way. &lt;br&gt;
&lt;u&gt;The Difficulty in Reverse Engineering:&lt;/u&gt; Was this implementation chosen for its time complexity? Its space complexity? Its readability? Did the developer optimize it for a specific use case or data distribution? Perhaps the developer simply remembered or copied this particular implementation without fully understanding the alternatives or the nuanced trade-offs.&lt;/p&gt;

&lt;p&gt;   &lt;br&gt;
In software engineering, the "why" behind a code decision can be influenced by a multitude of factors: technical constraints, team skills, time pressures, business requirements, personal preferences of developers, evolving understanding of the problem, and even legacy choices that were carried forward. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;While code comments and documentation should ideally explain the reasoning, they are often incomplete or missing. This makes truly reverse engineering the original intent a challenging, if not impossible, task. We can analyze the code and its behavior, but the full story of the decisions that shaped it often remains within the minds of the developers who wrote it.&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Managing software complexity – Simple is not easy</title>
      <dc:creator>Daniel Malek</dc:creator>
      <pubDate>Thu, 01 May 2025 19:37:55 +0000</pubDate>
      <link>https://dev.to/dan1618/managing-software-complexity-simple-is-not-easy-6b4</link>
      <guid>https://dev.to/dan1618/managing-software-complexity-simple-is-not-easy-6b4</guid>
      <description>&lt;p&gt;Recently I have seen a sentence “Simple solutions scale better, are easier to maintain and deliver value faster.”&lt;/p&gt;

&lt;p&gt;In this post I will show some examples, where initial code which seems fine to implement, may not be relevant when application grows and requires thinking in terms of scalability, maintainability and readability. Some parts of code will require accepting different trade offs, broader perspective on what needs to be achieved and definietly – more abstraction layers and sophisticated approach.&lt;/p&gt;

&lt;p&gt;The examples below are written in JavaScript, but I believe they may be understood by any software developer.&lt;br&gt;
 &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Imperative vs. Declarative Programming (describing logic)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Illusion of Simplicity (Imperative): Imperative programming focuses on how to achieve a result by explicitly stating the steps. It can seem simple for basic tasks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5];
const doubled = [];
for (let i = 0; i &amp;lt; numbers.length; i++) {
  doubled.push(numbers[i] * 2);
}
console.log(doubled); // [2, 4, 6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hidden complexity: As the logic becomes more intricate, imperative code can become verbose and harder to reason about. Managing state and side effects explicitly can lead to more opportunities for errors.&lt;/p&gt;

&lt;p&gt;The path to simplicity: Declarative programming focuses on what the result should be, abstracting away the control flow. Higher-order functions in JavaScript enable a more declarative style.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(number =&amp;gt; number * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The map function abstracts away the iteration process, making the code more concise and easier to understand the intent – to double each number in the array. While the map function itself has underlying complexity, it provides a simpler interface for the developer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Reduce vs Map and Filter&lt;/strong&gt;&lt;br&gt;
This example will show that using more sophisticated code (which is more efficient) might cause a cognitive overwhelm. Let’s say we want to have an array of doubled numbers, only when the value is higher than 3. You may use 'reduce' for that it will loop through the array, multiply number if it is higher than 2, then push it to the output array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5];
const doubledHigherThanTwo = numbers.reduce((acc, number) =&amp;gt; {
  if (number &amp;gt; 2) {
    acc.push(number * 2);
  }
  return acc;
}, []);
console.log(doubledHigherThanTwo); // [6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this task you may also use filter and map combined, which seems more clear. It filters out numbers higher than 2, then makes multiplying on each element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5];
const doubledHigherThanTwo =
  numbers
    .filter(number =&amp;gt; number &amp;gt; 2)
    .map(number =&amp;gt; number * 2);
console.log(doubledHigherThanTwo); // [6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both of the solutions are fine and using reduce may be more flexible in future, but you can use the second approach, when code readability or maintainability (debugging) is your priority.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Factory design pattern&lt;/strong&gt;&lt;br&gt;
Imagine you're building a system to manage different types of notifications (email, SMS, push). Creating a notification might involve setting up API keys, formatting the message, and potentially logging the creation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class EmailNotification {
  constructor(recipient, subject, body, apiKey) {
    this.recipient = recipient;
    this.subject = subject;
    this.body = body;
    this.apiKey = apiKey;
    this.setupEmailService();
    this.formatMessage();
    this.logCreation();
  }

  setupEmailService() {
    console.log(`Setting up email service with API key: ${this.apiKey.substring(0, 5)}...`);
    // Imagine actual API client initialization here
  }

  formatMessage() {
    this.formattedBody = `Subject: ${this.subject}\n\n${this.body}`;
    console.log("Email message formatted.");
  }

  send() {
    console.log(`Sending email to ${this.recipient}:\n${this.formattedBody}`);
    // Imagine actual sending logic here
  }

  logCreation() {
    console.log(`Email notification created for ${this.recipient} at ${new Date().toLocaleTimeString()}.`);
  }
}

class SMSNotification {
  constructor(phoneNumber, message, accountSid, authToken) {
    this.phoneNumber = phoneNumber;
    this.message = message;
    this.accountSid = accountSid;
    this.authToken = authToken;
    this.setupSMSService();
    this.truncateMessage();
    this.logCreation();
  }

  setupSMSService() {
    console.log(`Setting up SMS service with SID: ${this.accountSid.substring(0, 5)}...`);
    // Imagine actual API client initialization here
  }

  truncateMessage() {
    this.truncatedMessage = this.message.substring(0, 140); // Basic truncation
    console.log("SMS message truncated (if necessary).");
  }

  send() {
    console.log(`Sending SMS to ${this.phoneNumber}: ${this.truncatedMessage}`);
    // Imagine actual sending logic here
  }

  logCreation() {
    console.log(`SMS notification created for ${this.phoneNumber} at ${new Date().toLocaleTimeString()}.`);
  }
}

// Client code creating notifications directly
const email = new EmailNotification(
  "user@example.com",
  "Important Update",
  "This is the content of the important update.",
  "YOUR_EMAIL_API_KEY_SECRET"
);
email.send();

const sms = new SMSNotification(
  "+1234567890",
  "Hey, check out the latest news!",
  "ACCOUNTSID12345",
  "AUTHTOKEN_SECRET"
);
sms.send();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;Problems with this approach:&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;Repetitive Initialization: The client code is responsible for knowing which concrete class to instantiate and providing all the necessary initialization parameters.&lt;/p&gt;

&lt;p&gt;Tight Coupling: The client code is directly coupled to the concrete EmailNotification and SMSNotification classes. If you add a new notification type, you'll need to modify the client code.&lt;/p&gt;

&lt;p&gt;Scattered Logic: The complex initialization steps are within each notification class. If the initialization logic becomes more involved or shared across notification types, it can lead to duplication.&lt;/p&gt;

&lt;p&gt;Now, let's refactor this using a Factory Pattern. We'll create a NotificationFactory to handle the object creation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class EmailNotification {
  constructor(recipient, subject, body) {
    this.recipient = recipient;
    this.subject = subject;
    this.body = body;
    this.isServiceSetup = false;
    this.isMessageFormatted = false;
  }

  setupService(apiKey) {
    console.log(`Setting up email service with API key: ${apiKey.substring(0, 5)}...`);
    this.apiKey = apiKey;
    this.isServiceSetup = true;
  }

  formatMessage() {
    this.formattedBody = `Subject: ${this.subject}\n\n${this.body}`;
    console.log("Email message formatted.");
    this.isMessageFormatted = true;
  }

  send() {
    if (!this.isServiceSetup || !this.isMessageFormatted) {
      console.error("Email service not properly set up or message not formatted.");
      return;
    }
    console.log(`Sending email to ${this.recipient}:\n${this.formattedBody}`);
    // Imagine actual sending logic here
  }

  logCreation() {
    console.log(`Email notification created for ${this.recipient} at ${new Date().toLocaleTimeString()}.`);
  }
}

class SMSNotification {
  constructor(phoneNumber, message) {
    this.phoneNumber = phoneNumber;
    this.message = message;
    this.isServiceSetup = false;
    this.isMessageTruncated = false;
  }

  setupService(accountSid, authToken) {
    console.log(`Setting up SMS service with SID: ${accountSid.substring(0, 5)}...`);
    this.accountSid = accountSid;
    this.authToken = authToken;
    this.isServiceSetup = true;
  }

  truncateMessage() {
    this.truncatedMessage = this.message.substring(0, 140); // Basic truncation
    console.log("SMS message truncated (if necessary).");
    this.isMessageTruncated = true;
  }

  send() {
    if (!this.isServiceSetup || !this.isMessageTruncated) {
      console.error("SMS service not properly set up or message not truncated.");
      return;
    }
    console.log(`Sending SMS to ${this.phoneNumber}: ${this.truncatedMessage}`);
    // Imagine actual sending logic here
  }

  logCreation() {
    console.log(`SMS notification created for ${this.phoneNumber} at ${new Date().toLocaleTimeString()}.`);
  }
}

class NotificationFactory {
  constructor(emailApiKey, smsAccountSid, smsAuthToken) {
    this.emailApiKey = emailApiKey;
    this.smsAccountSid = smsAccountSid;
    this.smsAuthToken = smsAuthToken;
  }

  createNotification(type, ...args) {
    switch (type) {
      case 'email':
        const emailNotification = new EmailNotification(args[0], args[1], args[2]);
        emailNotification.setupService(this.emailApiKey);
        emailNotification.formatMessage();
        emailNotification.logCreation();
        return emailNotification;
      case 'sms':
        const smsNotification = new SMSNotification(args[0], args[1]);
        smsNotification.setupService(this.smsAccountSid, this.smsAuthToken);
        smsNotification.truncateMessage();
        smsNotification.logCreation();
        return smsNotification;
      default:
        throw new Error(`Unknown notification type: ${type}`);
    }
  }
}

// Client code using the factory
const notificationFactory = new NotificationFactory(
  "YOUR_EMAIL_API_KEY_SECRET",
  "ACCOUNTSID12345",
  "AUTHTOKEN_SECRET"
);

const emailNotification = notificationFactory.createNotification(
  'email',
  "user@example.com",
  "Important Update",
  "This is the content of the important update."
);
emailNotification.send();

const smsNotification = notificationFactory.createNotification(
  'sms',
  "+1234567890",
  "Hey, check out the latest news!"
);
smsNotification.send();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;Benefits of using the Factory Pattern here:&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;Centralized Initialization: The complex initialization logic (setting up services, formatting/truncating messages, logging) is now handled within the NotificationFactory. The client code doesn't need to know the specifics of how each notification type is set up.&lt;/p&gt;

&lt;p&gt;Decoupling: The client code interacts with the NotificationFactory interface, not the concrete notification classes directly. This makes it easier to add new notification types in the future without modifying the client code. You would simply extend the factory.&lt;/p&gt;

&lt;p&gt;Simplified Client Code: The client code for creating notifications becomes much cleaner and more focused on providing the necessary data (recipient, subject, body, phone number, message).&lt;/p&gt;

&lt;p&gt;Improved Maintainability: Changes to the initialization process for a specific notification type are isolated within the factory, making the system easier to maintain and debug.&lt;/p&gt;

&lt;p&gt;In this example, the NotificationFactory encapsulates the complex steps involved in creating and initializing different notification objects, providing a cleaner and more maintainable way to manage object creation.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;
&lt;strong&gt;SUMMARY&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These examples illustrate that what appears simple on the surface can often lead to significant complexity as the application scales or the requirements become more intricate. Achieving true simplicity often involves adopting more sophisticated patterns, architectures and language features that abstract away underlying complexities, leading to more maintainable, readable and robust code in the long run. "Simple is not easy" because it requires careful design, thoughtful abstraction, and often, embracing more powerful but initially seemingly more complex tools and paradigms.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Code as design</title>
      <dc:creator>Daniel Malek</dc:creator>
      <pubDate>Wed, 11 Dec 2024 18:00:03 +0000</pubDate>
      <link>https://dev.to/dan1618/code-as-design-46cj</link>
      <guid>https://dev.to/dan1618/code-as-design-46cj</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;There has been an open discussion if it is better to treat software as a design or as an engineering in the industry for many years, starting with the famous blog post “What Is Software Design” by Jack W. Reeves, 1992.&lt;/p&gt;

&lt;p&gt;While this share similar topic, I believe the primary focus should be on the desired outcome of the process rather than the specific terminology used. The difference between "development" and "engineering" is ultimately secondary to the fundamental goal of projects.&lt;/p&gt;

&lt;p&gt;In this post I will show some techniques where well designed code is something more than just a working program, making it communicative and understandable not only by the experts.&lt;/p&gt;

&lt;h2&gt;
  
  
   
&lt;/h2&gt;

&lt;h2&gt;
  
  
  2. Design patterns
&lt;/h2&gt;

&lt;h2&gt;
  
  
  2.1. Builder pattern
&lt;/h2&gt;

&lt;p&gt;Let us say the goal is to create a linechart composed of an X axis, a Y axis, and a line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcymcli3tw7p47vcwr71.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcymcli3tw7p47vcwr71.png" alt="Image description" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;you can use this simple piece of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chartBuilder
  .setXAxis()
  .setYAxis()
  .setLine()
  .build();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is quite clear to understand – it creates two axes and a line, it consists of invoking functions that display geometric objects. Order of invoking does not matter here. What if you wanted to add two more lines and a grid?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chartBuilder
  .setXAxis()
  .setYAxis()
  .setGrid()
  .setLine()
  .setLine()
  .setLine()
  .build();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why do I find this example important? Because I suppose anyone can understand this code, what opens a way to make a code collaborative for people of different specializations. &lt;/p&gt;

&lt;p&gt;It is also worth to mention, that there is an extra work for engineers to handle things that happen under each of the function (e.g setLine, setGrid) and expose them in a comprehensible way.&lt;/p&gt;

&lt;p&gt;This example is one of many Design Patterns -- typical solutions to common problems in software design. You can learn more about them here &lt;a href="https://refactoring.guru/design-patterns" rel="noopener noreferrer"&gt;https://refactoring.guru/design-patterns&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
   
&lt;/h2&gt;

&lt;h2&gt;
  
  
  2.2. Adapter pattern
&lt;/h2&gt;

&lt;p&gt;Consider a scenario where there is an old library that uses XML to store data, but your new application requires JSON data. You can use adapter pattern to convert the XML data to JSON before passing it to your application. The aim of adapter pattern is to allow two incompatible interfaces to work together.&lt;/p&gt;

&lt;p&gt;Here is a short code snippets, where an adapter is handled by single function XMLToJSONAdapter.&lt;/p&gt;

&lt;p&gt;After XML Data is fetched, it is passed as argument to XMLToJSONAdapter function (which is in charge of switching XML formant into JSON). The JSON formatted data is eventually passed to constant JSONData.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const XMLData = fetchData();
const JSONData = getJSONData(XMLToJSONAdapter(XMLData));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I believe that entry level to understand this code is pretty low.&lt;/p&gt;

&lt;p&gt;Another, a bit more complicated example of adapter pattern in Object oriented way, making pretty much same work under getJsonData function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class XmlToJsonAdapter {
    constructor(xmlData) {
        this.xmlData = xmlData;
    }

    getJsonData() {
        // ... convert XML to JSON ...
        return jsonData;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
   
&lt;/h2&gt;

&lt;h2&gt;
  
  
  2.3. Facade pattern
&lt;/h2&gt;

&lt;p&gt;Another example is Facade design pattern, the aim of which is to provide a simplified interface to a complex subsystem. This pattern defines a higher-level interface that makes the subsystem easier to use. In the code below the difficult part of coding, which is connecting to external system or any additional computation would be hidden in &lt;em&gt;Weather&lt;/em&gt; class (&lt;em&gt;getTemperature&lt;/em&gt;, &lt;em&gt;getHumidity&lt;/em&gt;, &lt;em&gt;getWindSpeed&lt;/em&gt; functions).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
getCurrentWeather() {
    const temperature = Weather.getTemperature();
    const humidity = Weather.getHumidity();
    const windSpeed = Weather.getWindSpeed();

    return {
        temperature,
        humidity,
        windSpeed,
    };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;getCurrentWeather&lt;/em&gt; function returns temperature, humidity and wind speed. Person who reads the code sees where those indicators come from and how they are returned together as a cohesive weather state. Getting the weather state, which we consider for this example more complicated is moved to &lt;em&gt;getTemperature, getHumidity, getWindSpeed&lt;/em&gt; functions.&lt;/p&gt;

&lt;h2&gt;
  
  
   
&lt;/h2&gt;

&lt;h2&gt;
  
  
  2.4. Example connecting different parts of code and multiple Design Patterns
&lt;/h2&gt;

&lt;p&gt;Let us say there is an existing system that is no longer developed but some of its features will be utilized by a new system, that is being built.&lt;br&gt;
In such case, a good solution might be creating a new piece of software that will translate between those systems and do not let the old system make direct impact on the new one. Such approach is called Anti-corruption layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples of usage of ACL in real life:&lt;/strong&gt;&lt;br&gt;
&lt;u&gt;Core Banking and Mobile Banking:&lt;/u&gt; The ACL could be used to transform complex financial data from the core banking system into a simplified format suitable for mobile devices, ensuring a smooth user experience.&lt;br&gt;
&lt;u&gt;IoT Devices and Supply Chain Management:&lt;/u&gt; The ACL could be used to transform raw IoT data into meaningful insights that can be integrated into the supply chain management system, improving visibility and efficiency.&lt;/p&gt;

&lt;p&gt;Anti-corruption layer is also a Design Pattern, but in example presented here it will operate on two different systems and consists of multiple parts of code.&lt;/p&gt;

&lt;p&gt;The diagram below shows an overview for two exemplary systems connected with Anti-corruption layer. On the right hand side, there is a system that we want to connect with, in the middle there is ACL and on the left hand side there is your brand new system which you are proud of. In Anti-corruption layer you can see a Facade which role is to hide complexity of the system you want to connect to. There are also two Adapters, which role is to translate data format of previous system to the one that that will be currently used.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5fehav4s0joet0ea2db.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5fehav4s0joet0ea2db.png" alt="Image description" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating a diagram, that connects different sub-systems is a good idea, when you want to make your project more understandable by both, technical and non-technical people. A diagram might be treated as a map that we use to navigate in codebase. Thanks to its visual nature, anyone can see how things in your codebase are connected and if more detailed understanding is needed, people may read the code by themselves. &lt;/p&gt;
&lt;h2&gt;
  
  
   
&lt;/h2&gt;
&lt;h2&gt;
  
  
  3. Infrastructure as code
&lt;/h2&gt;

&lt;p&gt;This chapter will show yet another approach to utilize the code. I find it important to notice, because Infrastructure as Code is closer to physical world, as is Design in its roots. &lt;/p&gt;

&lt;p&gt;Infrastructure as Code is a practice that involves managing and provisioning infrastructure through code. This means that instead of manually configuring servers or networks they can be defined in configuration files. These files can be versioned, tested, and deployed just like any other software code.&lt;/p&gt;

&lt;p&gt;Below diagram visualizes the process. User writes code, which is then version-controlled and mapped through Automation API or Server directly into an infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36irg4xwn8ufqifh8xat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36irg4xwn8ufqifh8xat.png" alt="Image description" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As mentioned before, IaC is closer to the physical world than regular programming. The idea of defining and building products through precise instructions, rather than manual processes is also common for manufacturing industry and has a strong connection with design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key similarities between IaC and Manufacturing:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Blueprint-Driven Approach:&lt;/u&gt;&lt;br&gt;
IaC: Engineers write code (blueprints) to define the desired infrastructure.&lt;br&gt;
Manufacturing: Engineers create blueprints (CAD models, schematics) to design physical products.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Automation and Repeatability:&lt;/u&gt;&lt;br&gt;
IaC: Automation tools execute the code to provision and configure infrastructure, ensuring consistency.&lt;br&gt;
Manufacturing: Automated machinery follows precise instructions to produce identical products.  &lt;/p&gt;

&lt;p&gt;&lt;u&gt;Version Control and Traceability:&lt;/u&gt;&lt;br&gt;
IaC: Code is version-controlled, allowing tracking of changes, collaboration, and rollback.&lt;br&gt;
Manufacturing: Product designs and manufacturing processes are version-controlled to maintain quality and consistency.  &lt;/p&gt;

&lt;p&gt;&lt;u&gt;Continuous Improvement:&lt;/u&gt;&lt;br&gt;
IaC: Infrastructure code is continuously refined and optimized to improve performance and reliability.&lt;br&gt;
Manufacturing: Manufacturing processes are constantly analyzed and improved to increase efficiency and reduce costs.&lt;/p&gt;
&lt;h2&gt;
  
  
   
&lt;/h2&gt;

&lt;p&gt;At the end, I would like to show an example snippet of IaC, to show that some basic configurations may be clear for non-technical people. Although the configuration describes technical infrastructure, so it requires some vocabulary. Moreover the code describing more advanced infrastructure might be much more complex. &lt;/p&gt;

&lt;p&gt;Bellow snippet defines a reusable configuration for a Google Compute Engine virtual machine with a Debian 11 boot disk. By providing values for the deployment_identifier variable, you can create multiple virtual machines with unique names based on this configuration. You can also modify the machine_type and network configuration to suit  specific needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "machine_type" {
  type    = string
  default = "n1-standard-1"
}

variable "zone" {
  type    = string
  default = "us-central1-a"
}

variable "deployment_identifier" {
  description = "The unique name for your instance"
  type        = string
}

resource "google_compute_instance" "default" {
  name         = "vm-${var.deployment_identifier}"
  machine_type = var.machine_type
  zone         = var.zone

  boot_disk {
    device_name = "boot"
    auto_delete = true
    initialize_params {
      image = "debian-cloud/debian-11"
    }
  }

  network_interface {
    network = "default"
    access_config {
      // Ephemeral IP
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Source: &lt;a href="https://cloud.google.com/service-catalog/docs/terraform-configuration" rel="noopener noreferrer"&gt;https://cloud.google.com/service-catalog/docs/terraform-configuration&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
   
&lt;/h2&gt;

&lt;h2&gt;
  
  
  4. Summary
&lt;/h2&gt;

&lt;p&gt;Code is a flexible tool, based on the latin alphabet (and special characters), which allows  to articulate processes of varying complexity and give instructions that are understandable both to humans and machines, thereby making technology more inclusive. Beside a difficulties that comes with writing software, code has traits that make it comprehensible.&lt;/p&gt;

&lt;h2&gt;
  
  
   
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Bibliography
&lt;/h2&gt;

&lt;p&gt;Design: The Whole Story by Elizabeth Wilhide&lt;br&gt;
Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans&lt;br&gt;
&lt;a href="https://www.developerdotstar.com/mag/articles/reeves_design.html" rel="noopener noreferrer"&gt;https://www.developerdotstar.com/mag/articles/reeves_design.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>design</category>
      <category>code</category>
      <category>designpatterns</category>
      <category>ddd</category>
    </item>
    <item>
      <title>Clean architecture with Next.js</title>
      <dc:creator>Daniel Malek</dc:creator>
      <pubDate>Mon, 05 Aug 2024 18:38:07 +0000</pubDate>
      <link>https://dev.to/dan1618/clean-architecture-with-nextjs-43cg</link>
      <guid>https://dev.to/dan1618/clean-architecture-with-nextjs-43cg</guid>
      <description>&lt;h2&gt;
  
  
  1) Introduction and Clean architecture
&lt;/h2&gt;

&lt;p&gt;Software architecture depends on many things, but there are still some concepts and good practices, that is worth to be familiar with. In this post I will show how to implement clean architecture using NextJS framework with focus on most important parts, It may be helpful if you plan tu start a new project with up to several software developers onboard. Intermediate knowledge about software development would be needed to understand this article.&lt;br&gt;
I have created a basic Calendar app to show how you can shape an example project, &lt;a href="https://github.com/Dan1618/clean-nextjs-example-app" rel="noopener noreferrer"&gt;check it on github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The key concepts of Clean Architecture are: &lt;br&gt;
&lt;u&gt;Separation of Concerns:&lt;/u&gt; Different parts of the application handle distinct responsibilities, making the code base easier to understand and modify.&lt;br&gt;
&lt;u&gt;Dependency Rule:&lt;/u&gt; Inner layers should not depend on outer layers. This improves maintainability, flexibility and reusability, making code modular, easier to refactor and more technology agnostic.&lt;br&gt;
&lt;u&gt;Testability:&lt;/u&gt; The architecture facilitates thorough unit testing by isolating components.&lt;/p&gt;
&lt;h2&gt;
  
  
   
&lt;/h2&gt;
&lt;h2&gt;
  
  
  2) Layers
&lt;/h2&gt;

&lt;p&gt;From technical point of view the idea is based on dividing application into layers and connect them with a bunch of adapters, repository pattern and dependency inversion. On the picture below, you can see how well each concern of any app might be separted. Further, I will describe each part with code examples.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5jpc72hgrkh5p0mtuh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5jpc72hgrkh5p0mtuh5.png" alt="Image description" width="753" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Source: &lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
   
&lt;/h2&gt;
&lt;h2&gt;
  
  
  3) Entities and Use Cases
&lt;/h2&gt;

&lt;p&gt;&lt;u&gt;Entities&lt;/u&gt; represent core business concepts with their data and rules, forming the heart of the system.&lt;br&gt;
&lt;u&gt;Use Cases&lt;/u&gt; define the actions a system can perform, orchestrating how entities interact to achieve specific goals. Think of entities as the building blocks and Use Cases as the blueprints for constructing a software application.&lt;/p&gt;

&lt;p&gt;Core business logic can stay in one place, framework independent and well tested. Does it sound good? You can achieve it by utilizing Entities and Use Cases. Database may change, framework may change, but core logic will remain independent in one place. What happen when new business requirements arrive? Just edit proper Use Case.&lt;/p&gt;

&lt;p&gt;In the example of the Calendar App I provided, Use Case examples seems way simple (even &lt;a href="https://github.com/Dan1618/clean-nextjs-example-app/blob/main/components/calendarEvents/calendarEvent.entity.ts" rel="noopener noreferrer"&gt;ICalendarEvent&lt;/a&gt; entity is as simple as interface), but their business logic might be extended by adding such features as:&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Date and time consistency:&lt;/u&gt; Verify that start time precedes end time.&lt;br&gt;
&lt;u&gt;Event duration limits:&lt;/u&gt; Define minimum and maximum event durations.&lt;br&gt;
&lt;u&gt;Conflict detection:&lt;/u&gt; Check for overlapping events based on event times and locations.&lt;br&gt;
&lt;u&gt;Capacity limits:&lt;/u&gt; Enforce attendance restrictions for events.&lt;br&gt;
&lt;u&gt;Category-based filtering:&lt;/u&gt; Implement filtering events based on categories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remember:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep Use Cases focused on a single responsibility.&lt;/li&gt;
&lt;li&gt;Prioritize business logic over technical implementation details (like database structures or UI elements).&lt;/li&gt;
&lt;li&gt;Write clear and concise code for maintainability.&lt;/li&gt;
&lt;li&gt;Test your Use Cases to ensure correct behavior.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
   
&lt;/h2&gt;
&lt;h2&gt;
  
  
  4) Controllers, Presenters and Dependency inversion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqnsguazalpki4uvpnv9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqnsguazalpki4uvpnv9.png" alt="Image description" width="244" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Controller captures an event that takes place in application, invokes a Use Case and, if needed, passes output from Use Case to presenter to map data and make it fit to an UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtx5kp67spokk3f2dlc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtx5kp67spokk3f2dlc6.png" alt="Image description" width="760" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Controller is a place which connects the outside world with the application's core logic. What is very important to mention here, it also utilizes dependency inversion principle. According to this principle Use Cases should not depend on outer layers. Advantage of dependency inversion principle are:&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Improved maintainability:&lt;/u&gt; Changes in the underlying implementation (e.g., database) have minimal impact on the Use Cases.&lt;br&gt;
&lt;u&gt;Better code organization:&lt;/u&gt; Clear separation of concerns between business logic and technical details.&lt;br&gt;
&lt;u&gt;Enhanced flexibility:&lt;/u&gt; You can easily swap out different implementations of dependencies without affecting the core Use Cases.&lt;br&gt;
&lt;u&gt;Increased testability:&lt;/u&gt; By isolating Use Cases from external dependencies, you can easily write unit tests without relying on complex setups.&lt;/p&gt;

&lt;p&gt;In Calendar App that I have provided, you can see exactly that for example when deleting calendar event, &lt;a href="https://github.com/Dan1618/clean-nextjs-example-app/blob/11b0995a687683b6a37ba9e8988098ab8a179948/components/eventModal/eventModal.use-cases.ts#L23" rel="noopener noreferrer"&gt;deleteEventUseCase&lt;/a&gt; is invoked with repository and output of the Use Case is used to refresh the view. Controllers that I have made receive many arguments, which make them hard to maintain. That might be improved in several ways, for example by using some external JavaScript dependency inversion library or creating your own system for that. A simple improvement in the Calendar Application might be made by creating a hooks controllers, which will encapsulate state and interaction methods and return them:&lt;/p&gt;

&lt;p&gt;&lt;u&gt;useCalendarViewController:&lt;/u&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export function useCalendarViewController(
  repository: IRepository,
) {
  const [calendarViewData, setCalendarViewData] = useState&amp;lt;TCalendarView | null&amp;gt;(null);

  // const nextCalendarView = async () =&amp;gt; {
  // const prevCalendarView = async () =&amp;gt; {
  // const fetchCalendarEventData = async (
  // const searchCalendarEvent = async (


  return { calendarViewData, nextCalendarView, prevCalendarView, fetchCalendarEventData, searchCalendarEvent }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;CalendarViewComponent:&lt;/u&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { calendarViewData, nextCalendarView, prevCalendarView, fetchCalendarEventData, searchCalendarEvent } =  useCalendarViewController(repository);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
   
&lt;/h2&gt;

&lt;h2&gt;
  
  
  5) Outer layer and Repository pattern
&lt;/h2&gt;

&lt;p&gt;In Clean Architecture, the outer layer is the furthest from the core business logic. It's where all the implementation details reside. This layer is often referred to as the Frameworks and Drivers Layer.&lt;/p&gt;

&lt;p&gt;In the Calendar App, you can see that React components are yet another layer, it relays on data and abstraction provided by inner layers. Repository with database implementation details is also passed from here. What is advantage of using repository pattern? At any time you can change database (for example from mongodb to mysql, but also some code details) without touching core business logic, as long as interface fits.&lt;/p&gt;

&lt;h2&gt;
  
  
   
&lt;/h2&gt;

&lt;h2&gt;
  
  
  6) Summary
&lt;/h2&gt;

&lt;p&gt;We went through the most important parts of clean architecture. It provides good practices for setting up your project and it is definitely worth considering especially if you know that the project will grow medium or bigger size. For compact projects using all the rules might lead to increased boilerplate code or redundant complexity in the project structure. But there is also a drawback, you need to maintain more code when you pick the approach with repository.&lt;/p&gt;

&lt;h2&gt;
  
  
   
&lt;/h2&gt;

&lt;h2&gt;
  
  
  7) Bibliography
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=wnxO4AT2N4o" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=wnxO4AT2N4o&lt;/a&gt;&lt;br&gt;
&lt;a href="https://betterprogramming.pub/clean-architecture-with-react-cc097a08b105" rel="noopener noreferrer"&gt;https://betterprogramming.pub/clean-architecture-with-react-cc097a08b105&lt;/a&gt;&lt;br&gt;
&lt;a href="https://tooploox.com/yet-another-clean-architecture" rel="noopener noreferrer"&gt;https://tooploox.com/yet-another-clean-architecture&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>cleancode</category>
      <category>architecture</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
