<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Pah Ferreira]]></title><description><![CDATA[Sharing my experience through the journey of production-ready software building.]]></description><link>https://blog.pahferreira.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 20 May 2026 19:36:10 GMT</lastBuildDate><atom:link href="https://blog.pahferreira.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Visibility Is an Architecture Problem]]></title><description><![CDATA[A few months after I joined a software team, I was asked to make a small change to our browser SDK, an NPM package used as the bridge between the UI (React) and Web Assembly Build (provided by our cor]]></description><link>https://blog.pahferreira.dev/visibility-is-an-architecture-problem</link><guid isPermaLink="true">https://blog.pahferreira.dev/visibility-is-an-architecture-problem</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[software development]]></category><category><![CDATA[software design]]></category><dc:creator><![CDATA[Pah Ferreira]]></dc:creator><pubDate>Thu, 02 Apr 2026 14:48:21 GMT</pubDate><content:encoded><![CDATA[<p>A few months after I joined a software team, I was asked to make a small change to our browser SDK, an NPM package used as the bridge between the UI (React) and Web Assembly Build (provided by our core team, built in C++).</p>
<p>It took me three days.</p>
<p>Not because the change was complex. I simply couldn't find where the change was supposed to live. The codebase had long files, mixed responsibilities, lots of duplicated logic, and no clear signal of where anything belonged (no clear separation of concern, et al.).</p>
<p>Internal helpers sat next to public-facing methods. Types bled across files. It was a tangled web; move one strand, and the whole structure shakes.</p>
<p>That's when I stopped trying to make the change and started asking a different question: <em>what would this look like if it were designed to change?</em></p>
<h2>The problem with "just works"</h2>
<p>The original SDK worked. It did its job. But "works" is a low bar when you're the one maintaining it six months later.</p>
<p>The thing that bothered me most wasn't the duplication or the long files; those are symptoms of an unhealthy organism. The real problem was <strong>visibility</strong>. <em>Everything was exposed</em>. Every internal helper, every type, every utility function was part of the public surface. If you installed this package, you were handed a map of the entire engine room and told to find the ignition yourself.</p>
<p>That goes against the principles of abstraction and information hiding that Ousterhout points out in his book "The Philosophy of Software Design":</p>
<blockquote>
<p>"If a piece of information is hidden, there are no dependencies on that information outside the module containing the information, so a design change related to that information will affect only the one module".</p>
</blockquote>
<p>Hiding information reduces the blast radius of change. It also reduces the cognitive load for the developers adopting the SDK. They shouldn't need to understand its internals to use it. A good library should feel like a set of explicit, opinionated methods; you call them, you get a result, you move on. The implementation is none of your business.</p>
<p>So I proposed a new architecture. Not a full rewrite, more like drawing lines that didn't exist before.</p>
<h2>Four layers and one direction</h2>
<p>The architecture I landed on has four layers. Data flows in one direction: top to bottom. Dependencies point inward. Each layer has exactly one reason to exist.</p>
<p>Here's the shape of it:</p>
<pre><code class="language-plaintext">Presentation → Application → Modules → Worker
</code></pre>
<p>Let me walk through each one.</p>
<h3>Layer 1: Presentation</h3>
<p>This is the only thing consumers of the SDK ever see.</p>
<p>It's also the simplest layer in the codebase — just functions. Not classes, not objects you need to instantiate. Functions. You call them, they return something, you're done.</p>
<pre><code class="language-typescript">// presentation/index.ts
export function cameraOpen() {
    return cameraService.open();
};

export function documentPhotoIdScan() {
    return documentService.photoIdScan();
};

export function documentPassportScan() {
    return documentService.passportScan();
};
</code></pre>
<p>That's the whole layer. A function that calls a method on an instance and returns the result. No error handling here, no business logic, no orchestration.</p>
<p>The point is simple: to control what's visible. This layer is a deliberate constraint on the public API surface. If it's not exported here, it doesn't exist to the outside world.</p>
<p>There's also a practical reason for functions over classes. JavaScript developers don't expect to instantiate a library. You import a function, you call it. That's the idiom. The presentation layer respects that.</p>
<h3>Layer 2: Application</h3>
<p>This is where the SDK comes together. The application layer instantiates all the module classes and wires their dependencies. If module A needs an instance of module B to work, that relationship is declared here and nowhere else.</p>
<pre><code class="language-typescript">// application/index.ts
const permissions = new PermissionsService();
const camera = new CameraService(permissions);
const verification = new DocumentService(camera);
</code></pre>
<p>I deliberately avoided a dependency injection container here. We were already making a lot of changes at once: new architecture, code improvements, migrating the worker layer (more on that in a moment).</p>
<p>DI framework abstracts the dependencies through the modules. In many cases, as it is in NestJS, you see the dependencies being defined in the class through <code>@Injectable</code> notation. Although that handles the complexity of managing multiple dependencies, our case was simpler and I did want to have an explicit way of mapping the dependencies in a single place and that place is the application layer.</p>
<p>Adding a DI framework on top of that would have been one more concept to learn and one more thing to debug. More importantly: we didn't feel the pain that a DI container solves. That's the signal. Don't reach for a tool before the problem it solves is real.</p>
<p>We kept it simple. Plain constructor injection, explicit wiring, no magic.</p>
<h3>Layer 3: Modules</h3>
<p>This is where the work happens. Modules come in three types:</p>
<p><strong>Core modules</strong> own the business logic. In an identity verification SDK, that's the stuff that matters — the verification flow, the state machine, the rules about what's valid and what isn't. If the SDK were a company, this is the product.</p>
<p><strong>Support modules</strong> handle things that aren't core value but are required for core value to work. Camera and Permissions are good examples. Accessing the camera requires a browser permission. Managing that permission isn't our core product, but without it, nothing works. Support modules exist in service of core modules.</p>
<p><strong>Internal modules</strong> are <em>never exposed</em>. Feature flags live here. Shared utilities live here. And critically, this is the only layer that talks to the layer below it. If something needs to reach the worker, it goes through an internal module. That boundary is intentional.</p>
<h3>Layer 4: Worker</h3>
<p>At the bottom of the stack, there's a single Web Worker.</p>
<p>Its job is narrow: communicate with our one external dependency provided as WebAssembly build from another team. That WASM build does the heavy lifting: face recognition, document scanning, and cross-referencing the two.</p>
<p>Since it handles computationally heavy processing that we don't want blocking the main thread, we decided to have a Web Worker dealing with this communication. That way it can run in isolation and returns results through <em>callbacks</em>.</p>
<p>When I arrived, this layer was a JavaScript file. It worked, until it didn't.</p>
<p>We started hitting silly runtime errors we couldn't catch until they happened in production. Most of them related to TypeErrors such as trying to access something that was <code>undefined</code>.</p>
<p>So I rewrote the worker as a TypeScript class, consistent with everything else in the new architecture. The instance of this worker class is exposed using <a href="https://github.com/GoogleChromeLabs/comlink">Comlink</a> avoiding the complexity of <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects#transferring_objects_between_threads">transferring objects between threads</a>.</p>
<p>The upgrade from JavaScript to TypeScript wasn't about preference. It was about making a class of errors impossible at runtime by catching them at compile time, in a layer that communicates with an opaque external dependency, that matters.</p>
<h2>The part about logs</h2>
<p>One thing I didn't expect to value as much as I do: every log in the SDK is tagged with its layer.</p>
<pre><code class="language-plaintext">[CameraService] open called
[DocumentService] scanPassport start
[Worker] scanPassportWASM error: { message: 'Error' }
</code></pre>
<p>When something breaks, and things break, I know exactly where to look. Before this architecture, debugging meant scanning through files hoping for a clue. Now it means reading the layer tag and opening one file.</p>
<p>That's not a feature. That's a property of good layering. When responsibilities are clear, failures are locatable.</p>
<h2>You don't need to see the future</h2>
<p>I didn't design this architecture by predicting every feature we'd need to build. I didn't know what new modules were coming, what the worker layer would eventually look like, or how complex the verification flow would get.</p>
<p>What I did was draw clear lines in the present.</p>
<p>Lines about what's visible and what isn't. Lines about where dependencies are declared. Lines about which layer is allowed to talk to which.</p>
<p>Those lines didn't predict the future; they made the future easier to navigate. That's the difference between code that scales and code that just works. Not clever abstractions or over-engineered patterns. Just clear, honest boundaries that tell the next engineer (maybe future you) exactly where things belong.</p>
]]></content:encoded></item><item><title><![CDATA[Atomic User Story Refinement]]></title><description><![CDATA[Introduction
Hi there fellow dev! Have you ever wondered what a refinement process is? Well, refinement is the practice of taking a user story and creating technical pieces of work (tasks) that will help the team understand what needs to be built to ...]]></description><link>https://blog.pahferreira.dev/atomic-refinement</link><guid isPermaLink="true">https://blog.pahferreira.dev/atomic-refinement</guid><category><![CDATA[Frontend Development]]></category><category><![CDATA[agile development]]></category><category><![CDATA[agile]]></category><category><![CDATA[agile methodology]]></category><category><![CDATA[planning]]></category><dc:creator><![CDATA[Pah Ferreira]]></dc:creator><pubDate>Mon, 24 Jul 2023 17:03:32 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-introduction"><strong>Introduction</strong></h1>
<p>Hi there fellow dev! Have you ever wondered what a refinement process is? Well, refinement is the practice of taking a <strong>user story</strong> and creating technical pieces of work (tasks) that will help the team understand what needs to be built to accomplish the user story's goal. In this article, I'll share with you the process that I developed as a Frontend Staff Engineer at Traive, with the help of a co-worker friend (thanks, Mr. T.), called <strong>Atomic refinement.</strong></p>
<p>This process shares the fundamental concept of Atomic Design, which involves identifying the essential building blocks required for your design and establishing their relationships, defining atoms, molecules, organisms, templates, and pages. In our case, we will take the perspective of finding these building blocks in the context of Frontend Refinement, but before we dive into it, let me just clarify two things:</p>
<ul>
<li><p>There is no right or wrong way to do refinement. It depends on the needs and challenges you and your team face during development.</p>
</li>
<li><p>Like any other process, it takes time for you and your team to adapt to it, so be patient.</p>
</li>
</ul>
<p>So without any further ado, let's make things atomic.</p>
<h2 id="heading-the-benefits-of-atomic-refinement"><strong>The Benefits of Atomic Refinement</strong></h2>
<p>Atomic Refinement offers several benefits that make it a valuable development process. Using it allows development to be streamlined, and better products can be delivered. In this section, we will cover several goals that we aim to achieve with this process.</p>
<h3 id="heading-decreasing-mistakes"><strong>Decreasing Mistakes</strong></h3>
<p>The chances of overlooking crucial details are minimized by breaking down tasks into smaller, manageable scopes. The smaller the task, the less room there is for mistakes, enhancing the overall quality of the work.</p>
<h3 id="heading-establishing-solid-requirements"><strong>Establishing Solid Requirements</strong></h3>
<p>With Atomic Refinement, you can define detailed specifications for each step within a context. By splitting and working on tasks separately, you better understand each specific context, which allows you to create more thorough requirements in alignment with the bigger picture, i.e., the user story.</p>
<h3 id="heading-facilitating-code-review"><strong>Facilitating Code Review</strong></h3>
<p>Having atomic pieces of work translates to smaller merge requests during code review. This streamlined process makes code review less intimidating and more efficient for developers and reviewers alike.</p>
<h3 id="heading-improving-code-quality"><strong>Improving Code Quality</strong></h3>
<p>With a clearer focus on specific contexts and smaller scopes, reviewers can investigate and provide better feedback. This level of attention to detail helps the team improve code quality, leading to a more robust and maintainable codebase.</p>
<h1 id="heading-understanding-atomic-refinement"><strong>Understanding Atomic Refinement</strong></h1>
<h2 id="heading-what-is-atomic-refinement"><strong>What is Atomic Refinement?</strong></h2>
<p>This name just makes my inner Walter White so happy. Talking a bit about chemistry, an atom is a basic unit of matter that works as a primary building block to construct more complex structures, such as molecules.</p>
<p>Getting into the context of software development:</p>
<ul>
<li><p>Atoms would be the <strong>tasks</strong> that the dev team works on.</p>
</li>
<li><p>Molecules would be the <strong>user stories</strong> that we deliver. Which are full of tasks (atoms).</p>
</li>
<li><p>An organism would be the <strong>epic</strong> that is delivered after completing all the user stories.</p>
</li>
</ul>
<p>That means the team will engage in these small tasks, and by completing them, we will eventually deliver features that are necessary to achieve our goals as a software development team.</p>
<h2 id="heading-applying-it-to-software-development"><strong>Applying it to Software Development</strong></h2>
<p>First, we need to identify the <strong>context</strong> of our tasks, which can vary based on the architecture of your project. Usually, we will have these four contexts:</p>
<p><strong>UI</strong> - When it comes to developing software, one of the most important aspects is the user interface. It is through the interface that users interact with the program and access its various features. Therefore, it is essential to give careful consideration to the design and functionality of the interface. In this context, coding components and laying out pages are critical tasks that require careful attention. By coding components, we mean designing and implementing the various elements of the interface, such as buttons, menus, and input fields. Laying out pages, on the other hand, involves arranging the different components in a previously designed way that requires responsiveness implementation.</p>
<p><strong>UX</strong> - These tasks can range from small tweaks to more complex experiences. The goal of these tickets is to implement the seamless and engaging experiences designed by our Design Team. Ultimately, a successful UX task should result in increased user engagement, and satisfaction, leading to a more successful and impactful software application.</p>
<p><strong>DATA</strong> -In this section, you can expect to see that the tickets would revolve around the consumption of data from APIs or data manipulation. To accomplish these tasks, you would need to implement various methods and API calls based on the tech stack that you are using for the project. Additionally, you would also need to test the implemented methods and API calls to ensure that they are working correctly and producing accurate results.</p>
<p><strong>INTEGRATION</strong> - To achieve a fully functional and seamless app, it is crucial to have bridges that connect the data and UI/UX. These tickets are meant to serve these connections between the data gathered and the different components of the app and ensure that they work together seamlessly.</p>
<h2 id="heading-how-to-implement-atomic-refinement"><strong>How to Implement Atomic Refinement</strong></h2>
<p>Now that we have identified the <strong>contexts</strong> in which our tasks can be categorized, let's delve into how to implement Atomic Refinement for a real-world project. Imagine you are working on a social media platform, and the Product Managers have assigned a new user story to implement a post-creation feature.</p>
<h3 id="heading-ui-tasks"><strong>UI Tasks</strong></h3>
<p>In this context, your focus is on the <strong>UI</strong> elements of the post-creation feature. You begin by building the static layout of the post-creation form, including components such as input fields for text, image upload, and tag users. Additionally, you create buttons for publishing and discarding drafts.</p>
<p>As part of the UI tasks, you also ensure that the design is responsive and displays correctly on various devices and screen sizes.</p>
<h3 id="heading-ux-tasks"><strong>UX Tasks</strong></h3>
<p>Now that the UI elements are in place, it's time to work on the <strong>UX</strong> tasks to enhance the user experience during post-creation. You implement interactions such as showing a character counter for text input, allowing users to preview their uploaded images before publishing, and providing real-time feedback for invalid inputs during form submission.</p>
<p>The goal is to make the post-creation process smooth and intuitive for users, with clear feedback and guidance as they compose their posts.</p>
<h3 id="heading-data-tasks"><strong>Data Tasks</strong></h3>
<p>Now, let's handle the <strong>Data</strong> tasks for the post-creation feature. As users create posts, the data entered by the form needs to be managed and stored on the server.</p>
<p>You create data tasks to implement API calls for creating, updating, and deleting posts. The data submitted through the form will be sent to the server by the code created in these tickets that later will be used in the integration task to connect UI/UX and DATA. Additionally, you implement data retrieval to load existing drafts or previous versions of posts for users to edit.</p>
<h3 id="heading-integration-tasks"><strong>Integration Tasks</strong></h3>
<p>Now comes the final step of <strong>Integration</strong>, where you connect all the pieces necessary to ensure a seamless and fully functional post-creation experience.</p>
<p>Integration tasks involve tying together the form with the data submitted and retrieved from the server. For example, when a user clicks the publish button, the post data is sent to the server through an API call (created by the data ticket), and the post is displayed in real-time on the user's feed.</p>
<p>You also handle error scenarios, such as failed API calls, and provide appropriate error messages to users when necessary. Additionally, integration tasks involve coordinating interactions with other parts of the application, such as updating the user's profile with the new post information.</p>
<h2 id="heading-atomic-doesnt-mean-perfect">Atomic doesn't mean perfect.</h2>
<p>Like any other development process, Atomic Refinement is also not perfect. Some challenges may appear during your experience with atomic refinement and understanding them can be crucial to success.</p>
<p>For example, having smaller pieces of work increases the number of tickets that you will need to create. This process can be exhausting if you don't use any resources like templates. Having templates for each type of ticket may earn you some precious time. Another thing that may help with this is applying reusability. In that case, as long as you progress with your app, more and more pieces will be already done for you to just use.</p>
<p>Also, smaller tasks increase the dependency between them. We cannot implement a form data entry functionality without having the form UI. Also, how can I integrate if the DATA layer is not ready yet? Mapping these dependencies is important to create a timeline on how to proceed with a user story and it will also create virtual phases for the team where they can expect that something in the INTEGRATION phase it's almost ready to go, or something that is still UI phase means that it just begun.</p>
<h1 id="heading-embracing-atomic-refinement"><strong>Embracing Atomic Refinement</strong></h1>
<p>That concludes the concept of Atomic Refinement! By breaking down tasks into smaller, manageable contexts, you can streamline your development process, improve code quality, and deliver seamless features to your users.</p>
<p>Embracing Atomic Refinement in your software development process can allow each member of your team to focus on their strengths concisely. Encouraging your team to engage in this process will facilitate their understanding of the core concepts of your application, making them more familiar with the issues and challenges they may face in this journey.</p>
<p>Thank you for your time, and happy coding!</p>
]]></content:encoded></item></channel></rss>