<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ismayil Khayredinov on Medium]]></title>
        <description><![CDATA[Stories by Ismayil Khayredinov on Medium]]></description>
        <link>https://medium.com/@ismayilkhayredinov?source=rss-45b096460c47------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*TgwOPNzFCBuD72DM.jpg</url>
            <title>Stories by Ismayil Khayredinov on Medium</title>
            <link>https://medium.com/@ismayilkhayredinov?source=rss-45b096460c47------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 01:42:37 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ismayilkhayredinov/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[A mental model for AI-assisted development]]></title>
            <link>https://itnext.io/a-mental-model-for-ai-assisted-development-06f161a26e0f?source=rss-45b096460c47------2</link>
            <guid isPermaLink="false">https://medium.com/p/06f161a26e0f</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[agentic-workflow]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[claude]]></category>
            <category><![CDATA[llm]]></category>
            <dc:creator><![CDATA[Ismayil Khayredinov]]></dc:creator>
            <pubDate>Wed, 03 Dec 2025 09:31:51 GMT</pubDate>
            <atom:updated>2025-12-03T22:38:14.222Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>This is a story of how many iterations and course corrections later, I have landed on something that might actually be useful for AI-assisted software engineering workflows.</strong></p><p>TLDR; check out this repository: <a href="https://github.com/hypeJunction/ai-assistant-starter">https://github.com/hypeJunction/ai-assistant-starter</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GqT0ka6xmlb4ymfHsYfCBw.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@shaotsui?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Xiao Cui</a> on <a href="https://unsplash.com/photos/black-red-and-white-abstract-painting-3S2FmTt4HKE?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>About a month ago, I have set out to write a piece sharing my frustration with the AI tools in software development and the breadth of disconnect between what we are promised and what we actually get. I have given it an honest try, explored many of the IDEs and tools out there, only to realise that, for the most part, AI was just a marketing gimmick, and it would take considerable effort to transform its capabilities into well-thought products that create synergies with how we think and work.</p><p>I am an AI sceptic. Not because I fail to see the potential, but because the mass adoption is pioneered by a group of tech monopolies, which are commodifying everything from our relationships with each other to our perception of self. Having been exposed to their algorithms, designed to manipulate our actions and beliefs by exploiting our innate instincts for profit, it is not hard to imagine what such powerful tools could be turned into when capitalist greed for growth supersedes public interest or common sense. Hearing the CEOs of world’s largest unregulated and unhinged corporations talk about AI gives me creeps, as their rhetoric is reminiscent of many dystopian sci-fi novels I have enjoyed reading for their educational and entertainment value — not because I thought I would find myself living in one.</p><p>I shouldn’t really be using the acronym AI to refer to LLMs, as it misrepresents all the other areas of machine learning and AI research that go far beyond language and speech. It is indeed unfortunate that a small subset of ideas and products have hijacked the entirety of human imagination when it comes to artificial intelligence and its application to the betterment of human experience. Plagiarising all of humanity’s cultural heritage to sell it back to us in an entishitified slop form as something that should supposedly make us obsolete is a rather cynical business model.</p><p>Over the past year, I have been cautiously tipping my toes to see what the fuss is all about, what these LLMs are capable of, and how I could leverage them to increase my productivity at work and beyond. I have used various assistants to see how they would translate into an improved developer experience and free up my time to focus on things that aren’t as mundane. Sadly, I have found most of them to be completely disconnected from the realities and needs of software engineering: built as thin wrappers on top of LLMs trained on questionable datasets, they produced subpar results unacceptable for enterprise-grade software. Even IDE integrations that could at the very least tap into IntelliSense and make meaningful code suggestions were hallucinating such nonsense that by now I have permanently and inadvertently removed all auto-completions and chats from my IDE (I have switched exclusively to working with AI in my terminal, as I could no longer deal with the amount of cognitive energy I was wasting on discriminating total shit from plausible shit).</p><p>There were (and still are) moments, when I look at the LLM output with awe. It is really a fantastical experience to see it spit out large swaths of text and code in a matter of seconds. It makes you feel elated and excited, when things go your way, and you get the desired output. But, as we all know by now, such moments are rare — hardly ever the outcome is 100% in line with how you would have done it yourself (unless the reason you use AI is that you have no clue), which forces you to make compromises and/or engage in endless iterations of prompting. In its own right, the effort would have been justified, were it not for that “reasoning” model dreaming up new creative solutions with every new context window, oblivious to all the detailed instructions you have spent hours crafting earlier.</p><p>Working with LLM is really like being in an abusive relationship — you keep telling yourself that it must be your fault for not saying the right things, for not being able to formulate your needs and wants, for feeling like giving up. When you look around, the loudest voices are preaching how AI is turning mountains for them. The hype is so real that you feel inferior for not being able to make it work for you, like so many other people seem to have done already. Tech companies would make you believe that they have already replaced half of their workforce with AI; exuberant investments will make you think that it’s the only way forward and you should really be adapting to the new reality, if you don’t want to be doomed for obsolescence. But everyone is suspiciously quiet about the upfront time investment, resources spent on damage control, and the continuous, persistent and imminent need for constant oversight, which is ultimately unfeasible due to the sheer amount of output. AI burnout is the next level of worker exhaustion.</p><p>Everyone seems too afraid to be critical of AI in software engineering, trapped under layers of cognitive dissonance, doubt, peer pressure, job insecurity — it’s like reliving the impostor syndrome all over again. Not enough software developers are taking a principled stand, having honest conversations, calling out LLMs for what they really are, and sending a clear signal to the tech industry that we have seen their shenanigans play out not in our favour before, and that we refuse to play by their rules. The potential is great, but didn’t we learn the hard way to evaluate our options in a forward thinking manner to pick the most future-proof solutions, best suited for the job?! The entire AI hype is built on a trickle down marketing campaign, where millions are spent on convincing us that we are delusional if we don’t think that the tools we are given are not the tools that we have asked for.</p><p>Feelings and bad experiences aside, LLMs are here to stay, and even if the bubble pops, there is still some utility we can salvage for ourselves. If we look at things pragmatically, transcend all the hype, look beyond tinker bell sprinkles that now cover every landing page, and focus on how we can build upon what we already know, there is a way to gradually integrate LLMs into our daily practices. The keyword being <em>gradually</em>.</p><p>We often jump on the AI bandwagon without much thought about what we want to get out of it, or how we imagine our longer term strategy of working with AI. Convinced by all the marketing that AI is a know-all do-all, we dump an entire PRD into a chat window and expect it to work miracles. That of course fails.</p><p>Despite all of my discomfort with the state of AI, I posit that there is a better, more systematic and more pragmatic way to engage with it — a middle ground that allows us to remain relevant, maximising our productivity not for the sake of maximising our productivity, but with a goal to free up time for learning new skills, experimenting with our stack and innovating, and spending more time in discovery and planning. LLMs present a new way of automating some of our workflows, the ones that are particularly repetitive time-sinks that none of us should be wasting time on.</p><p>The key in my opinion is a need for a deep reflection on who we are as software developers, what set of skills distinguish us from machines, the kinds of tasks we should ideally be focusing our attention on, and identify opportunities for AI not to replace us, but to enhance our daily experiences and practices. I refuse to subscribe to the notion that AI saves me time typing — it’s not the bottleneck that I am trying to eliminate.</p><p>I will be honest that after 15 years of writing software, I am sated with the repetitive nature of many of the tasks I perform on daily and weekly basis. It would be a dream to focus purely on engineering solutions, and not spend 90% of my time building a 100th variation of the button component and adding the 1000th endpoint to proxy the data from the database to the client. The reality for me and many of my peers is that we are constantly overworked — there is never enough time for everything, we keep juggling and re-prioritising, cutting corners, dreaming of skeletons we buried in our code at night. Yet we spend an incredible amount of time on mind-numbing things, replicating the patterns we have already perfected, hundreds if not thousands of times.</p><p>I have been stretched thin over the past year. Working at a feature factory is not always easy, especially when deadlines are like nooses over your neck getting tighter and tighter with every new feature you manage to ship on time. Eventually it takes a toll - velocity drops, ability to follow best practices is compromised, desire for perfection is eroded. There are many parts of my work that I enjoy, and I would really like to have the time to actually focus on them. If AI is that tool that I am missing, it seems only prudent to give it my best go, and see where I can benefit from it, knowing all too well that I can not rely on it 100%.</p><p>All of these good and bad experiences have distilled into something over the past month, since I decided to shift gears and to rethink how and when to use the AI. When I refactored one of the AI-generated functions from 100 lines of ifs and thens into a one-liner, I have realised that this was not a good fit. I have had enough of shaming AI for being stupid and not wanting to write the code that I haven’t yet understood how to write myself. The decision to not use AI as a shortcut was indeed a breakthrough, and led me to uncover better ways of interacting with it, eventually evolving into something that I now find helpful.</p><p>I would like to share some of the mental cues that I have settled on, in hope that, perhaps, they would also help you find a different model for working with AI, without feeling trapped in the useless hype.</p><h4>Do not substitute learning for AI</h4><p>The common fallacy we all fall for — oh, I don’t know how to do it, let me ask AI to generate the code for me. This sets a dangerous precedent, where we neither learn, nor can verify the output because, well, we didn’t make the effort to learn. Make it a rule to never rely on AI for things that you haven’t mastered or understood yourself!</p><p>I would strongly suggest to anyone in a junior role to ditch AI-assisted development. By doing so, you will do your future self a favour. If you still want to benefit from it, rethink how you approach it — have a go at using AI to do the research, help you with deeper understanding of the problem space and available solutions, analyse, compare, learn, but do the actual implementation yourself. Let AI empower you, not sustain your replaceability.</p><h4>Break down responsibilities and tasks</h4><p>One thing that really helped me get more efficient was to delineate my development environments: I have removed all AI features from my primary IDE — it’s the place of Zen, which I use for focused work, contemplation, crafting code that should survive the test of time and changing requirements. Whenever I encounter a task that could benefit from AI input, such as research, prototyping, replication, I switch to Claude Code in my command line or use Zed with OpenRouter. This enforces a certain discipline, where I have to decide beforehand on the nature of the task — either I am writing code, or I am working with AI. AI doesn’t write any new code, it only works with the code I have already written. I innovate and set the way forward, AI does the follow up to adopt and replicate the new patterns. AI helps me do the research, but ultimately I make the decisions and write the original code.</p><p>There is a set of tasks that LLMs shine at and there are new possibilities that programming with natural language unfolds. Figure out what those are for you: research, prototyping, documentation, scaffolding or anything else. Create a clear-cut list of tasks where you think you would benefit from AI and stick to it. It’s easy to fall into a trap of reaching out for AI, whenever you feel slightly distracted, tired or stuck, so you need to be determined about adhering to the workflows you design for yourself. You need to dive into it with a plan and conscious intent. I am quite sure that tackling the problem head-on is what excites you the most as a software engineer, so do it — let yourself build the foundation, lay the original bricks, design the floorplan. You don’t need AI for the things that you are good at and enjoy doing. Never ask AI to generate code that you wouldn’t be able to write yourself.</p><p>In my case, it was all the regressions that started to get on my nerves, so I set out to use AI to help me improve the test coverage. I have identified low impact areas in my codebase where something is better than nothing. It helped me to get started, I iterated over and over, until I got to a point where it actually became consistent and useful, and could eventually help me with other tasks, including refactors, quality checks, component composition and more.</p><p>Focus on engineering simple, predictable and replicable patterns, document them well, and let the AI do the heavy lifting. What separates us from AI is experience that translates into a gut feeling — noone is going to take that away, and using AI doesn’t change the nature of what we do, it just simplifies some of it.</p><h4>Yield Control</h4><p>One thing became apparent to me: once you start developing with AI, it is almost impossible to go back to maintaining that code by hand. Be smart about where you use AI-assisted development. It is probably a good idea to build internal tools with AI — low impact, high gains. However it would be terrible if you had lost control over your production codebase.</p><p>If you do care about implementation details, write it yourself. Do not let AI into mission-critical parts of your workflow without strong guardrails and confidence that you can properly review and verify the result. Taking shortcuts is not how you gain a productivity boost, but you get there through scrupulous work of identifying and preparing the parts of your codebase that are best suited for AI-assisted development and maintenance.</p><p>If you are starting a new project make sure to start with the tests, or at least have some visual or debugging output that you can quickly validate. I have seen it one time too many how lack of checks at each step results in a unicorn which looks like a unicorn, shits like a unicorn, but is nothing but a horse with a fancy horn. If you are not careful, you will end up like me, with a progress bar that satisfies a badly written prompt and does exactly what I asked for — a bar that just shows some progress animation, but backed no actual data.</p><p>I have coined a term AI-4-AI to explain this. Use AI where you really don’t care about the nitty-gritty of the how. You can describe the implementation steps, but you don’t really care about the implementation details themselves. Make sure you build a clear boundary of what is AI-maintained code, and what is a human-maintained code. Yield control over AI-maintained code, let the AI operate within that boundary and do what it needs to. You don’t change any AI output in that boundary, but you can instruct it to make the changes that you want. You operate under an assumption that AI-generated code will be AI-maintained code, so you build it accordingly, baking in context, memory, and guardrails.</p><p>In my case, I have orchestrated Storybook testing and mocking infrastructure, and outsourced most of story-writing to AI. It does a fantastic job at identifying the requirements and covering them with stories and tests. Every story starts with a test plan in Gherkin syntax that document desired component behavior based on implementation and any product requirements I feed to it. It has all the necessary knowledge in the context about the data model, entity relationships, mocking approaches, Story templates, accessibility-driven testing, and it spits out stories that cover all the happy and unhappy paths. Having visual Stories as output is a simple way to verify the output — I scan the code for obvious issues, but it’s too much code to review in detail. Given that it’s low-impact, non-production code, I treat it as a good compromise. It has already helped me spot many issues. There is no chance we would have achieved the same results by writing all the stories ourselves, they may sometimes lack in quality, but iterating on the instructions makes them better. I am still in control over my codebase, but I use AI sparingly and strategically to help me build better software by outsourcing some of the steps that I would have skipped anyway.</p><h4>Start small and increment</h4><p>No matter how great you are at writing prompts, AI assistants will consistently disappoint. We often fail to appreciate the complexity of the work we do, all the implicit knowledge scattered around the codebase, all the decisions buried in the heads of people that come and go, all the steps and micro actions that result in a product that we ship to production with some level of confidence. Much of it is intuitive and automatic, and it takes some practice to pause and figure out if what we know is what we expect others to know without bridging the gaps in understanding and perception.</p><p>Sure, some coding assistants have embedded knowledge of software engineering processes, they know how to pivot when they encounter certain challenges, they know how to perform some generic agentic tasks, but each product and codebase are unique. You can’t screw every screw with the same screwdriver — you need to know which tools fit the job and use them accordingly.</p><p>AI can’t tell the difference between tech debt and your state of the art pattern. Unless you are explicit and coherent, you will end up with decisions AI makes on the fly based on universally bad ideas that are statistically prevalent, but it doesn’t make them any less bad.</p><p>Find a simple workflow you would like to automate. Try it out, see what works and what doesn’t. If you notice ambiguity, disambiguate it with new instructions. AI-assisted development is a process. It will take time to lay out the foundation, to document the things that you know to be true about your codebase and your business logic. LLMs can only do so much by parsing bits and pieces of your code and inferring some meaning, and with their limited context window you will never be able to achieve consistency unless you make a decision to iterate over and work with it to minimise inference and decision-making based on impaired and limited understanding.</p><p>If at the end of this process you still find that AI is useless, well, at least you have put some thoughts in writing. Knowledge silos are the biggest challenge in software engineering — having to wait for someone to come back from a holiday to be able to proceed with a task is what really kills productivity.</p><h4>AI instructions as code</h4><p>I am starting to think of English as another programming language. We have languages for low-level programming, languages designed for specific use-cases, languages that shine under certain conditions (and then there is bureacratic German ;). So, why not think of English as a meta-programming language that is designed to capture intent, with ambiguity being a feature rather than a bug.</p><p>While programming languages remain precise and can be used to enumerate a set of specific conditions, natural language allows us to capture entire categories of fuzzy requirements. Instead of limiting ourselves to what we know to be true today, we allow ourselves to express requirements that we can evaluate against in the future. Natural language is more natural to us, it’s better aligned with how we reason about the real-world, and more so the uncertainty and entropy that we know exists out there.</p><p>Software engineering to me is a process of disambiguation. It takes implicit and explicit knowledge to distil things into deterministic behaviours. The world however is not always deterministic, so having a probabilistic tool in our toolbox can be an asset.</p><p>In the last month, I have embraced AI assistant instructions as part of my codebase. They live next to my code, and evolve with it. More so, they evolve with my understanding of my intentions when it comes to leveraging AI, my processes and workflows. By working on AI instructions, I am able to formalise my decisions, document my thinking, note down any concerns, make plans for the future. Things that used to live in JIRA as tickets with dumb titles that noone could parse 2 weeks later have now turned into living documents that gather context, meaning, relations to other units of work, examples of possible solutions and so forth.</p><p>What started as copilot-instructions.md has now evolved into a knowledge base that actually has applications beyond the AI workflows. Anyone working with the codebaes can now read the docs or use AI to navigate them to get a good understanding of the main patterns, decisions and processes.</p><p>At some point, trying to document everything in one file has become unwieldy. I have noticed that certain requirements and rules were interfering with each other depending on the types of tasks I was working on, or languages that were involved. They were also polluting the context window with too much unrelated information. When I came across <a href="https://github.blog/ai-and-ml/github-copilot/how-to-build-reliable-ai-workflows-with-agentic-primitives-and-context-engineering/">How to build reliable AI workflows with agentic primitives and context engineering</a> I have told Claude to rewrite all the instructions I have already accumulated and reorganize them into the structure recommended by the blog post. Ever since it has become much easier to work with the assistant to expand on this knowledge and periodically sync it with what’s happening in the codebase.</p><p>Now whenever I see the assistant doing something weird, I tell it to correct course and update AI instructions, and it’s been improving my workflow and deriving better results with each new iteration.</p><h4>Composable workflows</h4><p>We have embraced infrastructure as code; we build CI pipelines with precise steps that ensure we ship reliable products to our customers; that makes me wonder why not do something similar for our local development flows, but using the flexibility of the natural language. Our process is deterministic, we know exactly what software development lifecycle looks like. However our thinking isn’t linear: we deviate, pivot, recalibrate based on things we learn and observe.</p><p>I am a big practitioner of atomic principles. Everything is composable. We can break down our process into steps that can be executed in no particular order carrying the context of the previous operation. We can deconstruct and recompose our workflows. Such line of thinking fits quite well with agentic primitives — we can document every step of the way, disambiguate and refine, provide precise instructions and validation logic, rinse and repeat.</p><p>Every atomic task we perform can be documented, contextualized, and assigned to a specific role. These tasks can then be composed into workflows that reflect the best case scenario for how our actions culminate in a working product.</p><p>A workflow is an entry point, it’s a blueprint for how we can achieve something through a sequence of steps. We can add decision-making gates, we can express fuzzy logic that changes based on context and scope. Such AI-driven pipelines can help us be more structured and disciplined about our process, while giving us the flexibility to deviate based on uncertainty that each requirement entails.</p><p>I have been deconstructing my processes for some weeks now, and I find myself becoming a better developer by doing so. Quality of my work is improving, and I find that I can eliminate much of the doubt that is inherent when using AI by following well-defined workflows that enumerate every step of the way: from ideation to production.</p><p>I have now extracted most of these approaches into a <a href="https://github.com/hypejunction/ai-assistant-starter">starter project</a> that you can also adopt in your project.</p><p>I will finish by confessing that using AI at work has somewhat revigorated my long-lost excitement for the change we could achieve with technology. By having muted out all the nonsensical noise around AI, unfollowing anyone preaching God-AI, and focusing on what I need and want out of it, I was able to make my daily routines more enjoyable.</p><p>I am curious to see where LLMs are going. There is a lot of work happening out in the open, and there are many interesting open-source models and tools to explore. We do not have to live in fear of a dystopic AI-first metaverse with zuckermuskian puppetmasters stering us into obsolescence. We can go back to thinking for ourselves as we used to before influencers started telling us how to wipe our butts correctly and which toiler paper to use for that.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=06f161a26e0f" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/a-mental-model-for-ai-assisted-development-06f161a26e0f">A mental model for AI-assisted development</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating Vue Nx monorepo to Eslint flat config]]></title>
            <link>https://javascript.plainenglish.io/migrating-vue-nx-monorepo-to-eslint-flat-config-75ac67a0df26?source=rss-45b096460c47------2</link>
            <guid isPermaLink="false">https://medium.com/p/75ac67a0df26</guid>
            <category><![CDATA[nx]]></category>
            <category><![CDATA[eslint]]></category>
            <category><![CDATA[vue]]></category>
            <dc:creator><![CDATA[Ismayil Khayredinov]]></dc:creator>
            <pubDate>Fri, 10 Jan 2025 16:36:47 GMT</pubDate>
            <atom:updated>2025-01-13T07:57:55.072Z</atom:updated>
            <content:encoded><![CDATA[<p>After a few initial attempts a few months back, I have given on up on trying to <a href="https://eslint.org/docs/latest/use/migrate-to-9.0.0">migrate our codebase to Eslint 9</a> and its new flat config design. It seemed like a straightforward project, but sadly, as it usually happens with JavaScript tooling, it turned into a torturous ordeal. So I left it be until such a time that spending an entire day on tinkering with monorepo configuration did not seem like a luxury or thankless waste of time.</p><p>Such a day has come, and after a very long evening of loud keyboard pounding and screaming at the screen, I am a happy to report that I have succeeded in making this thing work. <em>And I really wish we had just stopped making things so complicated in the name of making them “better” and “easy-to-use”.</em></p><p>Things I have tried in the process:</p><ul><li><a href="https://nx.dev/recipes/tips-n-tricks/flat-config">Nx automigration </a>command</li><li><a href="https://eslint.org/blog/2024/05/eslint-configuration-migrator/">Eslint configuration migrator</a></li><li>Copy/pasting configs from newly generated Nx project</li><li>Copy/pasting configs from new generated Vue project</li><li>Using approaches documented in various Medium articles</li><li>Writing configs from scratch</li></ul><p>After much suffering and dozens of annoying issues and cryptic error messages, I have at last stitched together a working type-checked Eslint config. In the hopes that it will save you time and shield you from a nervous breakdown, I give to you the config that works with Nx boundaries, Vue SFC libraries and Prettier.</p><pre>// ./eslint.base.config.cjs<br>// We will reference this base config inside our apps and libraries<br><br>const nx = require(&#39;@nx/eslint-plugin&#39;);<br>const vue = require(&#39;eslint-plugin-vue&#39;);<br>const vueTsEslint = require(&#39;@vue/eslint-config-typescript&#39;);<br>const skipFormatting = require(&#39;@vue/eslint-config-prettier/skip-formatting&#39;);<br><br>module.exports = [<br>    {<br>        ignores: [&#39;**/eslint.config.cjs&#39;, &#39;**/*.d.ts&#39;, &#39;**/*.stories.ts&#39;, &#39;**/*.stories.tsx&#39;],<br>    },<br>    ...nx.configs[&#39;flat/base&#39;],<br>    ...nx.configs[&#39;flat/typescript&#39;],<br>    ...nx.configs[&#39;flat/javascript&#39;],<br>    ...vueTsEslint.defineConfig(<br>        vue.configs[&#39;flat/essential&#39;],<br>        vueTsEslint.createConfig({<br>            extends: [&#39;recommendedTypeChecked&#39;],<br>        }),<br>        {<br>            files: [&#39;**/*.ts&#39;, &#39;**/*.tsx&#39;, &#39;**/*.mts&#39;, &#39;**/*.vue&#39;],<br>            rules: {<br>                &#39;@typescript-eslint/consistent-type-exports&#39;: &#39;error&#39;,<br>                &#39;@typescript-eslint/consistent-type-imports&#39;: &#39;error&#39;,<br>                <br>                &#39;vue/multi-word-component-names&#39;: &#39;off&#39;,<br><br>                // other rule overrides go here<br>            },<br>        }<br>    ),<br><br>    {<br>        files: [&#39;**/*.ts&#39;, &#39;**/*.tsx&#39;, &#39;**/*.js&#39;, &#39;**/*.jsx&#39;, &#39;**/*.vue&#39;],<br>        rules: {<br>            &#39;@nx/enforce-module-boundaries&#39;: [<br>                &#39;error&#39;,<br>                {<br>                    enforceBuildableLibDependency: true,<br>                    allow: [&#39;^.*/eslint(\\.base)?\\.config\\.[cm]?js$&#39;],<br>                    depConstraints: [<br>                        {<br>                            sourceTag: &#39;*&#39;,<br>                            onlyDependOnLibsWithTags: [&#39;*&#39;],<br>                        },<br>                    ],<br>                },<br>            ],<br>        },<br>    },<br>    skipFormatting,<br>];</pre><pre>// ./eslint.config.cjs<br>// This is the config of the main application<br><br>const base = require(&#39;./eslint.base.config.cjs&#39;);<br><br>module.exports = [<br>    ...base,<br>    {<br>        ignores: [&#39;*&#39;, `!src/`],<br>    },<br>    {<br>        languageOptions: {<br>            parserOptions: {<br>                parser: &#39;@typescript-eslint/parser&#39;,<br>                tsconfigRootDir: __dirname,<br>                projectService: true,<br>            },<br>        },<br>    },<br>];</pre><pre>// ./lib/ts-lib/eslint.config.cjs<br>// This is the config of the basic typescript library that does not require any additional configuration<br>const baseConfig = require(&#39;../../eslint.base.config.cjs&#39;);<br><br>module.exports = [...baseConfig];</pre><pre>// ./lib/vue-lib/eslint.config.cjs<br>// This is the config of a more complex library that requires type-checking or has custom logi<br>const baseConfig = require(&#39;../../eslint.base.config.cjs&#39;);<br><br>module.exports = [<br>    ...baseConfig,<br>    {<br>        ignores: [&#39;**/dependency/*.js&#39;],<br>    },<br>    {<br>        languageOptions: {<br>            parserOptions: {<br>                parser: &#39;@typescript-eslint/parser&#39;,<br>                tsconfigRootDir: __dirname,<br>                projectService: true,<br>            },<br>        },<br>    },<br>    {<br>        rules: {<br>            &#39;@typescript-eslint/no-unnecessary-type-assertion&#39;: &#39;warn&#39;,<br>        },<br>    },<br>];</pre><h3>Thank you for being a part of the community</h3><p><em>Before you go:</em></p><ul><li>Be sure to <strong>clap</strong> and <strong>follow</strong> the writer ️👏<strong>️️</strong></li><li>Follow us: <a href="https://x.com/inPlainEngHQ"><strong>X</strong></a> | <a href="https://www.linkedin.com/company/inplainenglish/"><strong>LinkedIn</strong></a> | <a href="https://www.youtube.com/channel/UCtipWUghju290NWcn8jhyAw"><strong>YouTube</strong></a> | <a href="https://newsletter.plainenglish.io/"><strong>Newsletter</strong></a> | <a href="https://open.spotify.com/show/7qxylRWKhvZwMz2WuEoua0"><strong>Podcast</strong></a></li><li><a href="https://cofeed.app/"><strong>Check out CoFeed, the smart way to stay up-to-date with the latest in tech</strong></a> <strong>🧪</strong></li><li><a href="https://differ.blog/"><strong>Start your own free AI-powered blog on Differ</strong></a> 🚀</li><li><a href="https://discord.gg/in-plain-english-709094664682340443"><strong>Join our content creators community on Discord</strong></a> 🧑🏻‍💻</li><li>For more content, visit <a href="https://plainenglish.io/"><strong>plainenglish.io</strong></a> + <a href="https://stackademic.com/"><strong>stackademic.com</strong></a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=75ac67a0df26" width="1" height="1" alt=""><hr><p><a href="https://javascript.plainenglish.io/migrating-vue-nx-monorepo-to-eslint-flat-config-75ac67a0df26">Migrating Vue Nx monorepo to Eslint flat config</a> was originally published in <a href="https://javascript.plainenglish.io/">JavaScript in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Type-safe context injections in Vue.js (a-la React)]]></title>
            <link>https://ismayilkhayredinov.medium.com/type-safe-context-injections-in-vue-js-a-la-react-2af3acb13828?source=rss-45b096460c47------2</link>
            <guid isPermaLink="false">https://medium.com/p/2af3acb13828</guid>
            <dc:creator><![CDATA[Ismayil Khayredinov]]></dc:creator>
            <pubDate>Fri, 05 Jul 2024 13:28:04 GMT</pubDate>
            <atom:updated>2024-07-05T13:28:04.419Z</atom:updated>
            <content:encoded><![CDATA[<p>I find provide and inject in Vue.js to be quite limiting, so I wrote some syntactic sugar to get the most out of them. I do prefer context providers in React, so you will notice some similarities.</p><p>Thanks to this method, I do not need to pollute my global scope and can contextualize my state as needed. This inversion of control makes testing much simpler, and allows me to reuse components across the app without worrying much about leaking state.</p><h4>Provider Factory</h4><pre>import { inject, provide } from &#39;vue&#39;;<br><br>export const defineProvider = &lt;T, A extends unknown[]&gt;(name: string, fn: (...args: A) =&gt; T) =&gt; {<br>    const injectionKey = Symbol(name);<br><br>    return {<br>        provide: (...args: Parameters&lt;typeof fn&gt;) =&gt; {<br>            const ctx = fn(...args);<br>            provide&lt;T&gt;(injectionKey, ctx);<br>            return ctx;<br>        },<br>        inject: () =&gt; {<br>            const ctx = inject(injectionKey);<br>            if (!ctx) {<br>                throw new Error(&#39;You must create a context before using it&#39;);<br>            }<br>            return ctx as T;<br>        },<br>    };<br>};</pre><h4>Context Factory</h4><pre>const createMyContext = (b: string) =&gt; {<br>    return {<br>        a: 1,<br>        b<br>    }<br>}<br><br>const { provide, inject } = defineProvider(&#39;MyContext&#39;, createMyContext);<br><br>export { provide as provisionMyContext, inject as useMyContext };</pre><h4>Provider Component</h4><pre>&lt;script setup&gt;<br>import { provisionMyContext } from &#39;./context&#39;<br><br>const ctx= provisionMyContext(b)<br>&lt;/script&gt;<br><br>&lt;template&gt;<br>b is {{ ctx.b }}<br>&lt;/template&gt;</pre><h4>Child Component</h4><pre>&lt;script setup&gt;<br>import { useMyContext } from &#39;./context&#39;<br><br>const ctx= useMyContext()<br>&lt;/script&gt;<br><br>&lt;template&gt;<br>b is {{ ctx.b }}<br>&lt;/template&gt;</pre><p>Bisous.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2af3acb13828" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[REST, GraphQL or RPC — A Decision Paralysis]]></title>
            <link>https://itnext.io/rest-graphql-or-rpc-a-decision-paralysis-0acaa84b5a27?source=rss-45b096460c47------2</link>
            <guid isPermaLink="false">https://medium.com/p/0acaa84b5a27</guid>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[api-development]]></category>
            <category><![CDATA[rpc]]></category>
            <category><![CDATA[rest-api]]></category>
            <dc:creator><![CDATA[Ismayil Khayredinov]]></dc:creator>
            <pubDate>Tue, 17 Oct 2023 05:29:01 GMT</pubDate>
            <atom:updated>2023-10-17T06:51:09.173Z</atom:updated>
            <content:encoded><![CDATA[<h3>REST, GraphQL or RPC — A Decision Paralysis</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kxwWdV01rNGMtdIrM27aWg.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@the_meaning_of_love?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Aarón Blanco Tejedor</a> on <a href="https://unsplash.com/photos/VBe9zj-JHBs?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></figcaption></figure><p>Ever since AJAX capabilities landed in our browsers some 20+ years ago, developers around the world had been busy pushing these technologies to their limits, engineering creative ways of challenging the foundations of the original internet, and transforming it from a web of static pages to a web of dynamic data flows.</p><p>Application programming interfaces (APIs) that power all this data exchange have gone through many iterations, and it’s unsurprising that in 2023 many development teams struggle with decisions about the design and implementation of their APIs. With a variety of programming languages, transfer protocols, and infrastructure architectures, finding a way to define reliable contracts for data transfer, and more so to implement and maintain them in the face of changing requirements, is no easy feat.</p><p>Dealing with APIs on a daily basis, and observing the discussions around their design in my professional circles, it appeared to me it would be useful to put down some of my thoughts in writing. With <strong>REST</strong>, <strong>GraphQL</strong> and <strong>RPC</strong> being the most commonly discussed design patterns, I will focus on those and summarise some of my thoughts and experiences. Having worked with all 3 of them, I realise there is no silver bullet, and I think we are a long way away from having a common ground, similar to W3C standards, which helped bring sense and structure to browser technologies.</p><p>Before we start, let me clarify some of the assumptions:</p><ul><li>In a way, treating these 3 paradigms as equals is like comparing apples to oranges. After all, GraphQL is just a query language specification and could be instrumented in a way that is very similar to an RPC gateway. On the query side of things, it is also very similar to REST with it’s focus on resources, and in fact nothing prevents you from sending GraphQL documents as part of your REST calls, and use field resolvers as part of your controller implementation. So, for the sake of this article, I will be thinking of GraphQL as an implementation of the specification, i.e. a server akin Apollo capable of receiving and serving data over HTTP.</li><li>Speaking of RPC, I am not referring to any specific implementation, such as <a href="https://grpc.io/">gRPC</a>, but rather a paradigm of using procedure calls instead of operating on resources with unique addresses in the network, as is common in REST. Even though I intend to draw parallels with other transfer protocols, for the purposes of this article, API will stand to mean data transfer over HTTP. I will put aside networking considerations to focus on actual API design rather than mechanics of data moving over the wire. As such, it’s best to think of an RPC implementation similar to <a href="https://en.wikipedia.org/wiki/JSON-RPC">JSON-RPC</a> or <a href="https://trpc.io/">tRPC</a> rather than gRPC (with its use of HTTP/2 for bidirectional streaming of binary data).</li></ul><p>Let’s look at the various aspects of API design that need to be considered when making a decision and see how the 3 patterns hold up against these requirements.</p><h3>Schema Definition</h3><p>Ability to express APIs in abstract terms using a schema definition language (SDL) is an important consideration to make when choosing our API design. SDLs allow us to detach our design from implementation details, focus on our data model, and express a contract in a way that is interoperable throughout a variety of clients that could be interacting with our API in the future. SDLs make it easier to build tooling to automate some of otherwise painstakingly manual processes, e.g. by generating client SDKs or types for our data transfer objects.</p><p>Working without an SDL is a deal breaker, as you end up having to maintain a set of documentation in some random format that will be difficult to keep up to date, evolve and communicate to API clients.</p><p>When evaluating an SDL consider what your development workflow is going to look like:</p><ul><li><strong>Schema-first approach</strong> is practical for larger teams that deal with organisational complexity. Various teams can work together on defining the contract before moving forward with the actual implementation. This approach requires more time in planning and communication, but as a result allows the teams to work independently and in parallel, not having to deal with varying implementation schedules. This also results in better code quality as such a development flow requires mocks and tests to be feasible, perhaps even pushing development towards TDD. An added benefit of schema-first approach is that it’s agnostic to actual implementation details — underlying technology can always be swapped without affecting the workflow or impacting the clients. Using this approach however does require that everyone learn to speak a new language, so the syntax and usability are quite important considerations.</li><li><strong>Code-first approach </strong>is more practical for smaller teams that want to maintain agility, while having some level of contractual safety between clients and benefiting from the tooling within the SDL ecosystem. Code-first approach is tightly coupled with the tooling chosen for the development, and it comes with a risk of a costly effort that would be required to switch to a schema-first approach some time in the future, e.g. when the tools are no longer maintained by their authors, or the growing organisational complexity prevents the team from collaborating efficiently.</li></ul><p><strong>GraphQL </strong>in its essence is an SDL specification, which makes it a good candidate for expressing APIs. It has a built-in support for defining procedures (queries and mutations), input and response object types.</p><p><strong>REST </strong>and <strong>RPC </strong>are abstract concepts and do not have a standard SDL, but there are several general purpose frameworks that can be used to describe HTTP-based APIs.</p><ul><li><a href="https://www.openapis.org/">OpenAPI</a> (formerly Swagger) is the most common SDL specification that allows API authors to express HTTP requests and responses in YAML or JSON. OpenAPI ecosystem features a number of <a href="https://openapi.tools/">cool development tools</a><a href="https://openapi-generator.tech/">.</a></li><li><a href="https://www.asyncapi.com/">AsyncAPI</a> is another option to consider for <strong>RPC </strong>and other systems that focus on exchanging messages rather than resources. Its syntax is similar to OpenAPI, but it can better express the needs that go beyond the HTTP protocol.</li></ul><p>APIs expressed through SDLs also help us address type safety and serialisation concerns, making contracts enforceable throughout our stack. There are several serialisation and validation patterns that we can build on top of our specs.</p><ul><li><a href="https://protobuf.dev/">Protocol Buffers</a> is a platform and language neutral mechanism to serialize structured data. Models expressed in OpenAPI can be exported into protobuf format with <a href="https://github.com/google/gnostic">gnostic</a> making it easier to reliably exchange data between the server and the clients.</li><li><a href="https://json-schema.org/">JSON-Schema</a> is another standard for expressing serialisable data, as well as validation requirements for such data. OpenAPI specs could be converted to JSON-Schema using <a href="https://github.com/openapi-contrib/openapi-schema-to-json-schema">converters</a>. It’s an interesting approach if you want to validate your data at various stages of the request lifecycle.</li><li><a href="https://modelina.org/">Modelina</a> and other tools can be used to generate models from OpenAPI, AsyncAPI, and JSON-Schema.</li></ul><p>When defining your schema, you may also want to explore existing conventions that ensure that your API design is as compatible as possible with other systems you may want to integrate with in the future.</p><ul><li><a href="https://www.jsonrpc.org/specification">JSON-RPC</a> is a convention for structuring your RPC messages.</li><li><a href="https://cloudevents.io/">cloudevents</a> is a broader attempt to find a common language when documenting events in various formats and can be useful for various RPC considerations.</li><li>Consider various filtering, sorting, and pagination conventions that already exist, e.g. <a href="https://datatracker.ietf.org/doc/html/draft-ietf-scim-api-12#section-3.2.2.2">SCIM</a>, <a href="https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html">OData</a>, <a href="https://portal.ogc.org/files/96288#cql-core">CQL</a>.</li></ul><p>Other things to keep in mind when dealing with SDL:</p><ul><li>does it allow you to describe your resources in a manner that would allow any client to easily understand the logic, resources and relationships</li><li>does it allow you to express authentication requirements and accessibility of resources based on client roles/permissions</li><li>does it support all the data types (scalars, enums and complex unions) that your application would require</li><li>does it have the tooling necessary to serialise data in a format suitable for the HTTP (e.g. URL query and JSON) and other protocols</li><li>does it allow you to express your data model, resources and procedures beyond the HTTP protocol, i.e. you would prefer to use the same SDL to describe data transfer in other communication protocols (such message queues, or even command line interfaces)</li></ul><p>The great thing about the SDL is that it can serve as documentation for your API, both internally and externally. Making the schema accessible to the clients makes it possible for them to introspect the schema to discover the available resources within your API. There are wonderful GUI playgrounds that allow you to explore and interact with GraphQL (e.g. <a href="https://studio.apollographql.com/sandbox/explorer/?">Apollo Studio)</a> and OpenAPI (e.g. <a href="https://swagger.io/tools/swagger-ui/">Swagger UI)</a> schemas. And of course they integrate well with our beloved <a href="https://www.postman.com/">Postman</a>, allowing us to create collections from schema definitions, and even export collections into some of the common SDLs.</p><h3>Data Model</h3><p>A decision about API design can not be made without a good understanding of the specifics and complexity of the underlying data model, and the needs of the clients in accessing the resources.</p><h4>Object-relational complexity</h4><p>Relational model complexity is a major concern in API design, as traversing complex relational models via an API can be difficult. Relations that exist between your domain entities could be a deciding factor in favour or against a specific approach.</p><p>In a standard <strong>REST</strong> implementation, each resource has it’s own unique network address. You can access a resource with a straightforward GET request, e.g. GET /users/1. Things get murkier when you try to represent collections and relations, e.g. to get a list of user’s friends you would go to GET /users/1/friends, then however it gets ambiguous if you want to get a specific friend: it’s quite unclear whether you access it via GET /users/1/friends/friendship-1 or GET /friendships/friendship-1 or GET /users/2. It is also unclear where you should put your nested documents, e.g. should a user address be returned as part of the user resource, or should it be it’s own resource living on the GET /user/1/addresses edge. Such ambiguity plaques development teams with endless decisions about domain boundaries and their representation in the REST graph. Compromises are made between domain model uniformity and performance, making it hard for API clients to know exactly where to look.</p><p>Aside from that, <strong>REST</strong> places the burden of traversing the graph on the API client. If you ever tried to resolve a list of second-degree relations, you know what a nightmare it is, with a cascade of hundreds, if not thousands, of API requests. On top of it, if you are dealing with bidirectional relations, you have to account for node repetition as well as circularity, making it the responsibility of the client to implement some form of caching and deduplication to avoid infinite loops.</p><p>In order to alleviate some of this pain, REST API authors resort to creative solutions (a.k.a hacks) that allow relations to be resolved when a resource is requested. For example, Stripe API uses expand parameter. This does address the traversal concerns, but leads to non-deterministic response types, which again have to be dealt with by the API client, forcing it to do lots of type juggling and coercion.</p><p>With the emergence of social networks and their complex social graphs, <strong>REST</strong> had become quite unmanageable. <strong>GraphQL</strong>, as the name suggests, was created by Facebook as a way of traversing a complex social graph, representing an entirety of human society with its six degrees of separation. Facebook was trying to solve a problem inherent to social complexity with a multitude of actors and multidirectional relationships between them, and you should keep that in mind when evaluating whether or not your API design might benefit from it.</p><p><strong>GraphQL</strong> is brilliant in its simplicity. By letting the client specify precisely what data it needs and fetch it in a single request, it shifts the responsibility of traversing and stitching the graph to the server. Embedding types and relations within the schema design, it can use field resolvers to extract the necessary data and traverse the query of any complexity. This doesn’t always come cheap, and leads to additional problems and tooling (e.g. data loaders to address the n+1 problems), but allows the server to use any number of data stores to aggregate properties and relations, making API clients agnostic to such implementation details.</p><p>At their core, both <strong>REST</strong> and <strong>GraphQL</strong> are focused on resources and their representation. This is usually tightly coupled with how we represent entities in our databases. In a sense, these entities always exist in their platonic form, regardless of who the observer is. This can be problematic in certain systems, where not all clients are created equal. Having a static schema or a graph that doesn’t account for use cases and requester roles can lead to all kind of issues in the design. Presume our employee model has a salary property which can only be accessed by an accountant role — how do we represent and implement this in our API design? Do we make the property optional? If it’s optional how do we know what the lack of a value represents (there is no data in the storage or there is no accessible data for this requester). Do we create another edge in REST that throws an HTTP error? Do we send null or an error in GraphQL? This ambiguity can lead to additional complexity and uncertainty on the client-side, and should be factored in at the API design stage.</p><p>Resource-centric approach to our API design tends to make us think about our APIs in terms of our data storage model. While it’s justified in some cases, it can be problematic if we couple our API too tightly with our databases. All too often, we reuse our ORM models as our API models, and rely on tools such as <a href="https://hasura.io/">Hasura</a>, <a href="https://www.prisma.io/graphql">Prisma GraphQL</a>, <a href="https://postgrest.org/en/stable/index.html">PostgREST</a>, and many others to interface directly with our database via API. These tools can save a great deal of time, but they blur the lines between the architectural layers, and make our API clients susceptible to breaking changes. Depending on how and where the clients are used, such an approach poses a risk to data integrity, and requires a solution to rectifying race conditions of running data and schema migrations, generating and tagging new SDK versions, and propagating the changes to all the clients. API versioning becomes an issue if data storage engine does not support any versioning. Aside from that complex access controls become a pain point requiring us to hook into these tools to manage roles and permissions.</p><p><strong>RPC</strong> on the other hand is unopinionated about resources and their representation. It allows API authors to move away from trying to reproduce the data model and instead focus on use cases — procedures can be tailored towards specific needs of the clients and actors in the system. Unlike REST, where the client hardly has any say in what it gets, and GraphQL, where the client can indiscriminately request as much data as it wants (vis Cambridge Analytica), RPC can accommodate various requirements arising from different API applications. RPC servers can be instrumented in a way that rips the best from all worlds — from resolvers to untangle the graph to routing middleware to manage the request/response flow to isomorphic route and query level permission controls. RPC API can be built around immutable contracts and evolve over time to address changing requirements in a backward compatible manner. RPC allows us to separate API concerns from data persistence/storage concerns and other implementation details, leading to a more future-proof API design that doesn’t have to change every time the data schema changes.</p><p>While GraphQL is a good solution for a larger API surface, where the needs of the clients can not be well predicted, RPC could be a good alternative for smaller projects — essentially, instead of making the client write queries and enumerate all fields that have to be returned to the client, the response type could be explicitly defined as part of the RPC schema. In essence, the same could be said about REST, but that goes against the nature of the paradigm that dictates that each resource should be available at its own network locator.</p><h4>Updating Data</h4><p>Let’s be honest, when it comes to updating data via <strong>REST</strong>, it’s a pain in the ass. Different HTTP methods that are supposed to make things transparent, instead raise all kinds of questions:</p><ul><li>what method should be used to soft-delete or archive a resource? Should it be a DELETE request with some flag, or POST request with an action name (in effect making it an RPC request?), or should it be PATCH request updating the deleted or archived property of an object?</li><li>what about partial updates? Should we use PUT or PATCH? What about updates that have specific side effects, e.g. changing account email? Should they still go into a PATCH request, or be sent as POST with an action name?</li><li>what about granularity of partial updates where certain actors are only allowed to make certain changes? How do we split and describe this logic in our REST API design?</li><li>what about logic that does not really have any resources attached to it? Where should, for example, the various steps of the password reset flow live? Should they be in the root of the REST API or some edge of the API?</li><li>what happens with nested documents? What if those nested updates require transactional safety?</li></ul><p>The fact is, whenever you build an SDK around the REST API, you end up using procedure calls to describe the various actions that it performs, e.g. deleteUser() or capturePayment() rather than operating with get/post/put/patch/delete methods directly. My personal opinion is that REST is no longer suitable for modern day application design — requirements are far too complex to embed them into opinionated resource-centric API design.</p><p>Procedure calls are more suited for the complexity of modern day applications, allowing API designers to address specific use cases rather than trying to accommodate every possible scenario in a limited set of RESTful endpoints. Changing user password, is not the same as changing user email, or opting a user into a newsletter. All three may be part of the same resource, but hardly ever would you update a password and subscribe a user to the newsletter in the same request. All of these procedures have distinct side effects, and may require custom validation and permission checks.</p><p>RPC fits in nicely with CQRS principles. You can think of procedure calls as queries and commands, which make transport protocol considerations irrelevant. HTTP controllers receive and validate payloads, and then pass them on to query and command busses. This makes it easy to reuse procedure calls for any other transport — from command line to message queue to chatbots to whatever else that can receive a procedure call.</p><p>GraphQL mutations are in fact RPC. Aside from a terrible name that makes me think of unpredictable gene mutations and lockdowns, it is very flexible. The question however remains, what is to be considered a mutation — sending emails or downloading documents are procedures that don’t really mutate anything, so should they be part of the graph at all?</p><p>Another important thing to consider in the context of API design is whether or not data updates are synchronous or asynchronous. In distributed systems, synchronicity might not always be an option, so you should plan ahead to figure out a way to communicate persistence events to the API client, and how those are going to be reflected in API schema and documentation. Where your clients live will determine what technology you can use for that: webhooks, WebSockets, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events">Server-Side Events</a>, push notifications or something else. Here is an <a href="https://github.com/wso2/reference-architecture/blob/master/event-driven-api-architecture.md">interesting read</a> on the subject of asynchronous event-driven APIs.</p><p>Speaking of distributed systems, we should also consider what implications using GraphQL might have. On one hand GraphQL is well suited for aggregating data from various sources and can be used as a gateway to your microservices. On the other hand, however, mixing GraphQL with other API designs across the stack (or even having multiple GraphQL servers) might become unwieldy. You will need to think about ways to stitch or federate your servers to ensure the integrity of the graph.</p><h4>Data Types</h4><p>When it comes to API design there are a few considerations to make with regards to types of serialisable data that the API is going to transmit:</p><ul><li>how are you going to communicate serialisation requirements for custom scalar types</li><li>how are you going to express mixed types and oneOf logic, and how will you discriminate such union types</li></ul><p>Not all scalars are created equal. Having an API schema littered with string and number types is nothing short of a nightmare. A contract should be explicit about the requirements — API clients should not be second guessing what { contact: string } means, especially if there is validation attached to that property:</p><ul><li>use string identities, e.g. UUID, Email, IPv6, ISBN etc, and be explicit about custom string patterns</li><li>use integer identities, e.g. Year, Timestamp etc, and be explicit about ranges for custom integers</li><li>use float identities, e.g. Lat, Long etc, and be explicit about ranges and precision for custom floats</li><li>use enums to express finite lists of scalar options</li><li>adopt a single date format, i.e. ISO Date string or Unix timestamp</li></ul><p>Both GraphQL and OpenAPI support scalar type refinements, but they leave serialisation concerns to the servers and clients, so be sure to account for custom scalar type serialisation in your implementation. GraphQL, unlike OpenAPI, does not offer a native approach to documenting validation requirements at the schema-level, so be ready for some runtime error handling. There are some attempts to use non-standard directives to express <a href="https://github.com/confuser/graphql-constraint-directive/blob/master/README.md">validation constraints</a> and <a href="https://github.com/Saeris/graphql-directives#formatdate">formatting requirements</a>, but you will need to instrument them yourself.</p><p>Another important aspect of modelling data in API design is mixed types. Presume your API is going to serve a catalogue of items available in a library. You may have to deal with books, magazines, films, cassettes, CDs etc, all with their own data model. Depending on the design, your API clients may need to discriminate between these models to infer the correct type to build the logic around it.</p><p>OpenAPI spec comes with built-in support for unions of any types. GraphQL on the other hand isn’t so useful when it comes to discriminated unions and mixed types. Mixed scalar types are a no go. While GraphQL supports unions, they should all share the same interface, and should not have overlapping fields of varying types — you will have to work around these constraints and it’s not always possible, so you end up with a scalar JSON type, which isn’t that great for type safety.</p><p>These limitations are most evident when you work with structured/rich text. If you are working with a Headless CMS that ships a flavour of structured text, e.g. <a href="https://github.com/portabletext/portabletext">PortableText</a> or <a href="https://docs.slatejs.org/concepts/10-serializing#plaintext">Slate</a>, you will need to think of how to represent these content models in your API schema.</p><h4>Filtering, Sorting and Pagination</h4><p>A standard oversight when designing an API is a standardised approach to filtering, sorting and pagination. As we start with a small dataset we fail to foresee the future with millions of records, and neglect these concerns, which later leads to rebuilding the API schema to accommodate the new requirements.</p><p>There is no one standard approach to these concerns and different APIs address them in different ways. Take a look at <a href="https://datatracker.ietf.org/doc/html/draft-ietf-scim-api-12#section-3.2.2.2">SCIM</a>, <a href="https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html">OData</a>, <a href="https://portal.ogc.org/files/96288#cql-core">CQL</a> for some ideas.</p><p>When it comes to filtering, I draw inspiration from <a href="https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#filter-conditions-and-operators">Prisma.io</a>. They have designed a beautiful approach to querying the database, but their approach can as well be applicable to API design. Because these filters are serialisable, they can be easily integrated with HTTP requests, and will cover a wide range of requirements for filtering large datasets.</p><p>Pagination design is often dictated by the underlying data model, as well as the amount of traffic the database receives. Datasets that change often might benefit from cursor-based approach, while others can exist quite happily with an offset approach. Here is <a href="https://ignaciochiazzo.medium.com/paginating-requests-in-apis-d4883d4c1c4c">an interesting article</a> on the subject.</p><p>One thing to note here is how we draw a line between GET and POST requests. Rooted in REST and early-day HTTP principles, this dogmatic allocation of GET requests for queries, and POST requests for data updates, make working with large filters a headache. If you look at pros of REST approach, it boasts cacheability, but let’s be honest, what was the last time your relied on browser cache or CDN for your API requests. The idea that using URL query parameters instead of body payload would somehow make the world a better place is frankly outdated. URL serialisation sucks. URL length is limited. So, let’s just dispose of this dogma, and use POST requests with JSON payloads for all API requests. That’s pretty much what GraphQL clients end up doing as they encounter URL query length limitations. Think of it in RPC terms — you are not getting the data, but you are posting a procedure to the server and receive an answer from it.</p><h3>Environment &amp; Tooling</h3><p>An API design decision can not be made without taking into account what server technologies will be used to serve it, and what clients will be used to access it.</p><p>Because REST has evolved alongside the HTTP protocol, it is based on a set of universal standards. The stack is really quite irrelevant — all server and client technologies are able to communicate over HTTP, and the tooling around orchestrating a REST server is quite rich, regardless what programming language or server infrastructure you choose.</p><p>RPC implementations vary. There are various frameworks that bring their own ideas to the table. There is <a href="https://grpc.io/">gRPC</a> that is environment-agnostic, but is quite nuanced with regards to its bi-directional communication and use of Protocol Buffers. There is <a href="https://trpc.io/">tRPC</a> with its full-stack TypeScript implementation that borrows from REST and GraphQL to create one of the most loved API tools. There is <a href="https://zeroc.com/">ZeroC</a> and <a href="https://unum-cloud.github.io/ucall/">UCall</a> and many others. Ultimately, nothing is stopping you from using REST tooling to build RPC APIs — get rid of the brain fart that the REST is and just give all your queries and commands proper names and send them over the wire using standard HTTP tooling. You can namespace your procedures with URL segments, similar to REST, but you don’t have to bake in resource IDs into the URL structure and deal with complexity of HTTP methods.</p><p>When it comes to GraphQL, the idea behind it is brilliantly simple:</p><ul><li>each client defines the data it needs using a query language designed specifically for expressing graph nodes in a very simple form</li><li>the server parses the query, and passes each requested field through a resolver that has access to the root node and can access various data stores and/or external services to compute the return value</li></ul><p>The problem with introducing a new language, however, is that it needs a lexer, which can parse the query into a more standard form that can be digested by various server technologies. As most API clients live in the browser, GraphQL tooling evolved around the needs of front-end applications, in a JavaScript ecosystem with browser compatibility in mind. While it makes GraphQL a very pleasant experience on the front-end, the tooling around server to server communication is very much lacking — from bootstrapping a client and figuring out caching and other nuances like fragmentation to writing and parsing queries that can be used by the said client. Even though GraphQL is designed for standard HTTP protocol, the problem is that GraphQL in itself is not standard and might not be the best choice for a versatile API design.</p><h3>Observability &amp; Scalability</h3><p>API health and performance are important metrics that allow us to make conclusions about our API design. Ability to observe these metrics continuously and react to latency and performance issues, as well as runtime errors, should be a motivating factor in our decision-making.</p><p>While single point of entry APIs offer simplicity, they are quite a headache in terms of observability and scalability. Figuring out the health of your GraphQL or JSON-RPC APIs is quite a challenge without extra instrumentation. While REST is most likely to provide you with detailed logs of what resources were accessed, how many times, and how long it took to process the request, getting the same stats for APIs that have a single endpoint is quite impossible. Instead you end up with millions of log entries for the same endpoint, and unless you make an effort to provision extra logging with operation/method names, you would have no idea what those requests were. Things are even worse with GraphQL that doesn’t believe in HTTP status codes, so all requests end up with 200 status code, so tracing and debugging really turns into adventure every single time.</p><p>Using a single point of entry is also a DevOps problem. Depending on the environment, you may want to provision more resources for API endpoints that have higher latency or require more computational resources, but instead you are forced to scale the entire API server with an entirety of its dependencies and the impact of its memory footprint.</p><p>A single point of entry also makes it difficult to rip the benefits of reverse proxies that can reroute specific requests, help with caching, observability, load balancing, encryption, access control and other concerns.</p><h3>Error Handling</h3><p>HTTP codes are too generic. As I <a href="https://medium.com/itnext/graceful-error-handling-in-rest-driven-web-applications-d4209b4937aa">wrote some years ago</a>, APIs should be able to communicate problems to the clients, and subsequently the users. Whatever API design you chose, make sure you build a standardised approach to communicating errors to the client and contextualising them.</p><p>GraphQL’s error handling is a blessing and a curse. On one hand, it is very verbose, and can report errors from individual field resolvers. On the other hand, it makes debugging and error handling a pain — by merging success and error states into a single response it makes it so much harder for the clients to differentiate between network errors, invariants, schema validation issues, and actual success responses. According to the specification, partial success responses are in fact possible even if individual resolvers report errors, so it is quite cumbersome to try an reconcile null field values with errors (unless the GraphQL server prevents partial responses, as is the case with Apollo). So, give some consideration to how your server deals with errors, and how your clients are going to make sense of them.</p><p>As mentioned earlier, you may want to instrument additional tooling to help you with tracing errors in systems that have a single point of entry.</p><h3>Versioning</h3><p>Whether or not your API requires versioning is largely a usage problem. If your API is consumed in house and you are able to evolve APIs while keeping clients up to date, it might not be an issue. If you are distributing your APIs externally, you might want to consider some form of versioning.</p><p>If your API exists largely as a proxy to your database model, versioning can be somewhat of an acute problem. Once the database schema changes, there isn’t really a way to maintain backwards compatibility, especially if your API is autogenerated from database models. So, give it some thought, when chosing your tooling.</p><p>As mentioned earlier, RPC could be an interesting option for decoupling the storage model from the API model, giving you more flexibility. You can evolve your API without necessarily versioning it by introducing new procedures and deprecating the old ones. GraphQL tends to brush off versioning, leaning into the same approach — add new fields and deprecate the old ones. The difference is that however with RPC you have more control over request routing and can still introduce versioning. With GraphQL your versioning capabilities are limited — you may need to spin up a different container that serves the older version of the graph. Besides you want to get rid of dead and outdated code from time to time, so some way of communicating breaking changes to the clients would have been a plus.</p><h3>Access Controls</h3><p>You may want to invest some time giving access requirements a thought. Whether or not you require route/procedure level, resource level, or field level access can have a great impact on the your choice of API design.</p><p>Should resource-level checks be sufficient, you still need to consider, whether representation of the resource is identical for all users. Should you need higher granularity in access controls, you may need to find a way to communicate these requirements to the clients. Whatever route you take on the server (preventing access to the whole resource, or nulling the field value, or communicating an error on field access), it will be the responsibility of the client to deal with the fallout.</p><p>GraphQL is quite handy if you need field level access, though it does lack SDL level standars for documenting access requirements (aside from non-standard directives). REST API might not be an ideal solution for applications that need high levels granularity, as it prevents you from branching resource representation. RPC might offer some flexibility in designing the API with your particular requirements in mind.</p><h3>Conclusions</h3><p>API design is a difficult choice that should be driven by a product discovery phase, where you understand the innate nature of your application, and other actors in the system.</p><p>Finger to the wind.</p><p>Use <strong>REST</strong> if:</p><ul><li>you are building an API to interact with your database using CRUD operations</li><li>your data model is not heavy on relations</li><li>you have a limited number of user roles and access requirements</li></ul><p>Use <strong>GraphQL</strong> If:</p><ul><li>you are building a gateway to connect multiple data sources or services</li><li>your data model is heavy on relations and represents a graph</li><li>you have high-granularity access requirements</li><li>you are building an API targeted mostly at front-end applications</li><li>you have a large number of API clients with varying data requirements</li></ul><p>Use <strong>RPC</strong> if:</p><ul><li>you want to be flexible in naming and routing conventions</li><li>you want to build semantic, easy to read APIs</li><li>you need to address diverging, volatile, or complex use cases</li><li>your services interact outside of the HTTP protocol</li><li>you want to build a robust future-proof API decoupled from implementation and data storage concerns</li></ul><p>If you ask for my personal opinion, I think the future is RPC. I love GraphQL for front-end development, but I don’t think it’s an ideal solution for cross-service communication, and once you tip your toes into it it bites your foot off, and makes things so much harder to reason about. By all means, use GraphQL as a proxy for your read-model, but don’t bring it into a mix as part of your internal services. And as for the REST, let it rest.</p><p>Disagreements are welcome in comments. Always happy to hear that I have stirred someone towards actions with my writing.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0acaa84b5a27" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/rest-graphql-or-rpc-a-decision-paralysis-0acaa84b5a27">REST, GraphQL or RPC — A Decision Paralysis</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Layout composition using styled components without CSS-in-JS]]></title>
            <link>https://itnext.io/layout-composition-using-styled-components-without-css-in-js-cbc523be503e?source=rss-45b096460c47------2</link>
            <guid isPermaLink="false">https://medium.com/p/cbc523be503e</guid>
            <category><![CDATA[css]]></category>
            <category><![CDATA[styled-components]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[composition]]></category>
            <category><![CDATA[front-end-development]]></category>
            <dc:creator><![CDATA[Ismayil Khayredinov]]></dc:creator>
            <pubDate>Mon, 22 May 2023 18:16:21 GMT</pubDate>
            <atom:updated>2023-05-22T19:00:44.586Z</atom:updated>
            <content:encoded><![CDATA[<h3>Layout Composition Using Styled Components Without CSS-in-JS</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/736/1*7dQFLoJWtPrYlJomcD4MIA.jpeg" /><figcaption>Image Source: <a href="https://thebrooklyninstitute.com/items/courses/new-york/critical-design-interface-and-imagination/">Brooklyn Institute for Social Research</a></figcaption></figure><p>Layout composition is an emerging pattern that allows us to build truly reusable components that can adapt to various use cases that diverge so often in an evolving product design. There is a new generation of libraries, such as <a href="https://www.radix-ui.com/">Radix UI</a>, that finally allow us to use the tools functionally and not worry about their impact on the presentational layer of our applications.</p><p>A common approach to composition is to extend the root level component with additional properties that represent nested child components. This pattern is easy on the eyes and quite friendly - you know exactly what the descendants of the specific root component are.</p><pre>const ProductCard = ({ addToCart }) =&gt; (<br>  &lt;Card&gt;<br>    &lt;Card.Heading&gt;Webcam&lt;/Card.Heading&gt;<br>    &lt;Card.Illustration&gt;<br>      &lt;img src=&quot;webcam.jpg&quot; alt=&quot;Webcam promo image&quot; /&gt;<br>    &lt;/Card.Illustration&gt;<br>    &lt;Card.Body&gt;Really cool webcam&lt;/Card.Body&gt;<br>    &lt;Card.Footer&gt;<br>      &lt;Card.Footer.Button onClick={addToCart}&gt;Add to cart&lt;/Card.Footer.Button&gt;<br>    &lt;/Card.Footer&gt;<br>  &lt;/Card&gt;<br>)</pre><p>I am not a big fan of CSS-in-JS, but there is one pattern that emerged out of it that I find quite beneficial: clear separation of presentational concerns from other stateful and transactional aspects of the application. When working with CSS we tend to litter our code with presentational class names instead of embracing a level of abstraction inherent to styled components. In reality, nothing is stopping us from creating styled components that do not use CSS-in-JS, and instead rely solely on CSS classes (scoped or not):</p><pre>const Card = styled(&#39;section&#39;, { className: &#39;card&#39; })</pre><p>We could take our styled components one step further by enumerating all of available descendants for each styled component. You will probably notice how well this aligns with the way we think about our CSS selectors (especially if we use BEM).</p><pre>const Card = styled(&#39;section&#39;, { className: &#39;card&#39; }, {<br>  Heading: styled(&#39;h2&#39;, { className: &#39;card__heading&#39; }),<br>  Illustration: styled(&#39;div&#39;, { className: &#39;card__illustration&#39; }),<br>  Body: styled(&#39;div&#39;, { className: &#39;card__body&#39; }),<br>  Footer: styled(&#39;nav&#39;, { className: &#39;card__footer&#39; }, {<br>    Button: styled(&#39;button&#39;, { <br>      type: &#39;button&#39;, <br>      className: &#39;card__footer__button&#39;<br>    })<br>  })<br>})</pre><p>Let’s see how the styled factory for such component hierarchy could look like in React:</p><pre>import React, { ComponentProps, ElementType, PropsWithChildren, JSX } from &quot;react&quot;;<br><br>// this is very primitive <br>// there are better alternatives such as mergeProps in react-aria<br>const mergeProps = &lt;T extends ElementType&gt;(<br>  ...args: Array&lt;Partial&lt;ComponentProps&lt;T&gt;&gt;&gt;<br>) =&gt; {<br>  return args.reduce((acc, cur) =&gt; {<br>    return {<br>      ...acc,<br>      ...cur,<br>      className: [acc.className, cur.className].filter(Boolean).join(&quot; &quot;),<br>    };<br>  }, {} as JSX.LibraryManagedAttributes&lt;T, any&gt;);<br>};<br><br>export const styled = &lt;T extends ElementType, E&gt;(<br>  ComponentType: T,<br>  defaultProps: Partial&lt;ComponentProps&lt;T&gt;&gt;,<br>  elements?: E<br>): React.FC&lt;ComponentProps&lt;T&gt;&gt; &amp; E =&gt; {<br>  const component = (componentProps: PropsWithChildren&lt;ComponentProps&lt;T&gt;&gt;) =&gt; {<br>    return &lt;ComponentType {...mergeProps&lt;T&gt;(defaultProps, componentProps)} /&gt;;<br>  };<br><br>  return Object.assign(component, elements);<br>};</pre><p>We could take this one step further and apply styling to any another component in our library (as long as it supports className). For example, we could use Radix UI to make our card collapsible.</p><pre>import * as Collapsible from &#39;@radix-ui/react-collapsible&#39;;<br>import { ComponentProps, ForwardedRef, forwardRef } from &quot;react&quot;;<br><br>export const Card = styled(Collapsible.Root, { className: &#39;card&#39; }, {<br>    Heading: styled(&#39;h2&#39;, { className: &#39;card__heading&#39; }),<br>    CollapseTrigger: forwardRef(({children, ...props}: ComponentProps&lt;typeof Collapsible.Trigger&gt;, ref: ForwardedRef&lt;HTMLButtonElement&gt;) =&gt; {<br>        const Component = styled(Collapsible.Trigger, { className: &#39;card__collapse-trigger&#39;})<br>        return &lt;Component ref={ref} {...props}&gt;{children}&lt;/Component&gt;<br>    }),<br>    Content: forwardRef(({children, ...props}: ComponentProps&lt;typeof Collapsible.Content&gt;, ref: ForwardedRef&lt;HTMLDivElement&gt;) =&gt; {<br>        const Component = styled(Collapsible.Content, { className: &#39;card__content&#39;})<br>        return &lt;Component ref={ref} {...props}&gt;{children}&lt;/Component&gt;<br>    }),<br>    Illustration: styled(&#39;div&#39;, { className: &#39;card__illustration&#39; }),<br>    Body: styled(&#39;div&#39;, { className: &#39;card__body&#39; }),<br>    Footer: styled(&#39;nav&#39;, { className: &#39;card__footer&#39; }, {<br>      Button: styled(&#39;button&#39;, { <br>        type: &#39;button&#39;, <br>        className: &#39;card__footer__button&#39;<br>      })<br>    })<br>  })</pre><p>That’s it for now. Hopefully, you can draw some inspiration from this approach and apply it to reduce the boilerplate around building and styling layout elements in your application.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cbc523be503e" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/layout-composition-using-styled-components-without-css-in-js-cbc523be503e">Layout composition using styled components without CSS-in-JS</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Obituaries are dead. How to announce the demise of tech with empathy]]></title>
            <link>https://medium.com/geekculture/stop-whining-or-how-to-announce-that-tech-is-dead-with-empathy-396dc50b8a22?source=rss-45b096460c47------2</link>
            <guid isPermaLink="false">https://medium.com/p/396dc50b8a22</guid>
            <category><![CDATA[obituary]]></category>
            <category><![CDATA[tooling]]></category>
            <category><![CDATA[tech-writing]]></category>
            <category><![CDATA[rant]]></category>
            <dc:creator><![CDATA[Ismayil Khayredinov]]></dc:creator>
            <pubDate>Sun, 19 Jun 2022 11:21:44 GMT</pubDate>
            <atom:updated>2022-06-21T06:01:22.063Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*U2cNn-or8VL-fn5zHQ-p8A.jpeg" /><figcaption>Image Credit: <a href="https://twitter.com/dogmodog">@dogmodog</a></figcaption></figure><p>Proclamations a-la “PHP is dead” and “Stop using Next.js” are littering Medium. My reaction to one of those is almost always the same: there is no merit to it and the author is just an amateur, who hasn’t been long enough to be taken seriously.</p><p>Just because there are perceived better alternatives or some dubious stats you find on the internet back your claim, you can’t assume that something is dead. There are people that continue maintaining swaths of the web and enterprise software that is built with “obsolete” technology. A programming language that’s been around since 1972 is sometimes a better fit for <a href="https://softwareengineering.stackexchange.com/a/159638">powering Mars rovers</a> than some hyped new language without enough time in the wild to show its weaknesses.</p><p>A good developer knows that success of the software is almost entirely dependent on the choice of tools. Just because someone thinks that X is better than Y in whatever narrow context they are working on, does not entitle them to proclaim that everyone should immediately cease and desist using Y. Ultimately, it’s all about understanding the requirements, considering the implications and weighing trade-offs.</p><p>Instead of wasting your time and boring your readers with yet another tech obituary, consider being more mindful and helpful with one of these formats.</p><h3>Article Formats</h3><h4>Deep Dive Article</h4><p>Did you experiment with some new tech and found it to be the right fit for the job, share your experiences:</p><ul><li>What are some underlying principles that the tool is based upon? What were the motivations that the authors were pursuing?</li><li>What dependencies does the tool have, and what implications might they have on various aspects of software development (DevEx, CI/CD, testability, observability etc)</li><li>What are some use cases that this tool would be a good fit for?</li><li>How do you envision this tool’s future: any suggestions on how to improve it, any background on who are behind this tool and how will they ensure that they tool is not deprecated half a year later?</li><li>Is this tool production ready? Is it tested and testable in integration? Does the repository hygiene look satisfying to you?</li><li>What are some of the preceding and alternative tools, and why would you recommend this tool over those?</li></ul><h4>Pros and Cons Article</h4><p>No matter how wonderful the tool you are trying to sell to others as the panacea to their worries, everything comes with trade-offs. If you don’t have any concerns about the approach or tool you are recommending, perhaps you should hold off writing an article, as you don’t really understand the subject matter well enough. Without enough information you might be setting someone off on a wrong path.</p><ul><li>Assist others in their decision-making process by listing advantages and disadvantages of various tools aiming to solve the same problem</li><li>Prepare others for problems they might encounter when choosing one over the other</li><li>Provide links to other resources that helped you understand the tools better and make your own educated decision</li><li>Show snippets of code to demonstrate how implementation differs, and why one is more beneficial or easier to adopt than the other</li></ul><h4>Reflection Article</h4><p>If you really wish some tool dead, because it wasted your time and wasn’t suitable for the job, don’t denigrate people, who have taken time out of their schedule to build and support the tool. Instead try and reflect on your own experience, and understand where you went wrong with the decision:</p><ul><li>How did you evaluate the tool before using it?</li><li>Was it your specific use case that this tool failed to address? Why do you think that is and what should others watch out for?</li><li>Was the documentation misleading and how would you improve it to prevent others from repeating your mistake?</li><li>Was lack of support or communication from the authors a factor in your inability to take full advantage of the tool? Would this tool be more reliable if there were enough monetary and coding support from the community to boost the authors’ motivation?</li><li>If you were to author a similar tool, which decisions would you make differently, and what do you think it would take to refactor/enhance the tool to be more usable.</li></ul><h4>Tutorial</h4><p>If you think that A is better than B, and convinced that everyone should make a switch, don’t tell them to Stop using B without understanding what that would entail in practical terms. Instead, if you had to follow your own advice, what steps would you take:</p><ul><li>What is the easiest way to switch from B to A? Do you have any tips and shortcuts?</li><li>Can you provide step-by-step instructions and how-to’s?</li><li>How can someone estimate the amount of work that would go into refactoring an existing code base?</li><li>How can someone estimate the benefits of switching and calculate the return on investment? What are some KPIs that could be taken into consideration?</li><li>How would you sell the need to switch to your management? What are some longer term benefits? Will it improve testability or observability? Will it improve DevEx and make the team more efficient?</li><li>Which resources are there should someone encounter problems during the migration (documentation, forums, articles etc)?</li></ul><h3>Mindful Headlines</h3><p>Everything starts with a headline, so next time you sit down to write, read your headline and ask yourself — is this helpful to anyone?</p><p>Rewrite your headline to be helpful, and the rest will follow.</p><h4>X is dead</h4><ul><li>10 reasons why the adoption of X is declining</li><li>X is dying — how can you prepare of its demise</li><li>What can we do to keep X alive</li><li>Why I am sad to see X go</li><li>The world would be a safer place without X</li><li>How to prevent the X disaster from happening again</li><li>X: the good, the bad and the ugly</li></ul><h4><strong>Stop using X. Use Y instead</strong></h4><ul><li>How X compares to Y and why should you care</li><li>Accessing the benefits of switching from X to Y</li><li>How switching from X to Y made me happier/more productive</li><li>A roadmap for sunsetting X</li><li>Improve testability/observability/dev usability/accessibility by switching to Y</li><li>10 reasons to switch from X to Y</li></ul><p>You can also reframe the argument and instead of focusing on specific tools, use your bad experiences to formulate what an ideal choice should be and how to make that choice.</p><ul><li>Image processing: 5 tools you will love</li><li>A comparison of micro-frontend utilities</li><li>How to choose the right content management system</li><li>What to look out for in a good ORM</li><li>5 performance metrics of a good UI library</li></ul><p>Choose to accept the fact that we are here on Medium to learn, and not to listen to rants and waste time on unhelpful rhetoric about yet another tool that fell out of your graces. Less is more: sometimes it’s better to not write than to write something that is baseless, unargumentative, underresearched, and unconstructive. Impulse writing belongs in your diary and not in public domain.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=396dc50b8a22" width="1" height="1" alt=""><hr><p><a href="https://medium.com/geekculture/stop-whining-or-how-to-announce-that-tech-is-dead-with-empathy-396dc50b8a22">Obituaries are dead. How to announce the demise of tech with empathy</a> was originally published in <a href="https://medium.com/geekculture">Geek Culture</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Orchestrating and dockerizing a monorepo with Yarn 3 and Turborepo]]></title>
            <link>https://ismayilkhayredinov.medium.com/orchestrating-and-dockerizing-a-monorepo-with-yarn-3-and-turborepo-e26241a285cb?source=rss-45b096460c47------2</link>
            <guid isPermaLink="false">https://medium.com/p/e26241a285cb</guid>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[dockerfiles]]></category>
            <category><![CDATA[yarn-workspace]]></category>
            <category><![CDATA[turborepo]]></category>
            <category><![CDATA[monorepo]]></category>
            <dc:creator><![CDATA[Ismayil Khayredinov]]></dc:creator>
            <pubDate>Wed, 15 Jun 2022 10:46:57 GMT</pubDate>
            <atom:updated>2022-06-15T10:46:57.267Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3UYvbPI5NKHcZpwvnmiqNA.jpeg" /><figcaption>Image Credit: <a href="https://blog.red-badger.com/why-dont-you-have-a-monorepo">RedBadger</a></figcaption></figure><p>After a year of working on a monorepo with Yarn Workspaces and Lerna, we have learnt that Lerna is being deprecated, so we had to go back to the drawing board and come up with an alternative development pipeline for our multi-package application.</p><h3>Switching to Yarn 3</h3><p>The newer version of <a href="https://yarnpkg.com/">Yarn</a> ships with a lot of performance optimizations, but it also changes the way we reason about our dependencies, so there were a few a-ha moments.</p><p>To start using Yarn 3, create .yarnrc.yml file in the root of your project. If you already have a .npmrc file, you will need to migrate the configuration to the <a href="https://yarnpkg.com/configuration/yarnrc">new configuration format</a>.</p><p>For instance, if you are using private repositories from npm, you will need to change:</p><pre>//registry.npmjs.org/:_authToken=&lt;YOUR TOKEN&gt;</pre><p>to</p><pre>npmRegistries<strong>:<br>  </strong>&quot;https://registry.npmjs.org/&quot;<strong>:<br>    </strong>npmAuthToken<strong>: </strong>&lt;YOUR TOKEN&gt;</pre><p>If you are using packages from other private repositories, e.g. premium FontAwesome, you will need to change:</p><pre>@fortawesome:registry=https://npm.fontawesome.com/<br>//npm.fontawesome.com/:_authToken=&lt;YOUR TOKEN&gt;</pre><p>to</p><pre>npmScopes<strong>:<br>  </strong>fortawesome<strong>:<br>    </strong>npmAuthToken<strong>: </strong>&lt;YOUR TOKEN&gt;<br>    npmRegistryServer<strong>: </strong>&quot;https://npm.fontawesome.com/&quot;</pre><p>Once you have done this, you can switch your yarn version to either latest or a set version (e.g. 3.2.1 at the time of writing).</p><pre>yarn set version &lt;version&gt;</pre><p>Yarn team was hard work to ensure consistency of dependency management across larger teams, so once you run yarn install, you will notice a new folder inside your root directory called .yarn. Now, intuition will tell you to add it to your .gitignore, but don’t do that. Instead, <a href="https://yarnpkg.com/getting-started/qa/#which-files-should-be-gitignored">use the following ignore rules</a>:</p><pre># Yarn<br>.yarn/*<br><strong>!</strong>.yarn/patches<br><strong>!</strong>.yarn/releases<br><strong>!</strong>.yarn/plugins<br><strong>!</strong>.yarn/sdks<br><strong>!</strong>.yarn/versions<br>.pnp.*</pre><p>With Yarn 3, you also have an option to use Plug’n’Play mode, which would allow you to install and build your application offline. You can read more about it <a href="https://yarnpkg.com/features/pnp">here</a>.</p><p>For a monorepo setup, you would want to use a Yarn Workspace, which allows you to define dependencies on local packages, so that you don’t have to publish your packages to a registry to use them inside other local packages.</p><p>In our setup, we split the monorepo into packages and applications. Packages are libraries that power standalone applications. You can split your monorepo in many other ways, using the workspaces config in package.json .</p><pre>{<br>  &quot;name&quot;: &quot;@me/monorepo&quot;,<br>  &quot;private&quot;: true,<br>  &quot;workspaces&quot;: [<br>    &quot;packages/**/*&quot;,<br>    &quot;applications/**/*&quot;<br>  ]<br>}</pre><p>To consume a package inside our application, we would then update our dependencies <a href="https://yarnpkg.com/features/workspaces/#workspace-ranges-workspace">with workspace ranges</a> as such:</p><pre>{<br>  &quot;name&quot;: &quot;@me/application-a&quot;,<br>  &quot;dependencies&quot;: {<br>    &quot;@me/package-a&quot;: &quot;workspace:^&quot;<br>  }<br>}</pre><p>Yarn will now create a semantic link between the package in node_modules and the source, so you can use it inside your application as you would any another node module.</p><h3>Setting up Turborepo</h3><p><a href="https://turborepo.org/docs/getting-started">Turborepo</a> is a new build system developed by Vercel specifically for managing pipelines inside monorepos. Using pipeline configurations, you can define dependencies for your lifecycle commands, e.g. you can tell Turborepo to first build your package before building your application, or you can tell it to start a server before running an integration test. Besides that, Turborepo has a smart caching system that allows you to define inputs and outputs, so that it can figure out if changes were made to inputs before re-running a specific command, so it saves you time by replaying outputs if no changes were made.</p><p>Let’s say we have a React component library written in TypeScript, and a Next.js application that uses it.</p><p>Ideally we would want to be able to:</p><ol><li>Watch for changes in our package when Next.js server is running in dev mode</li><li>Build our package before building our Next.js production server</li><li>Build our package before testing our Next.js application</li></ol><p>Let’s define our commands:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7261c70a972ed0c4ff2648c3b42a2cbf/href">https://medium.com/media/7261c70a972ed0c4ff2648c3b42a2cbf/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1470e519edf3052f11a6d473aaa3c77c/href">https://medium.com/media/1470e519edf3052f11a6d473aaa3c77c/href</a></iframe><p>We can now configure Turborepo to help us work on both packages:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c7b01cc303fb1452eeef95e90e0d9c42/href">https://medium.com/media/c7b01cc303fb1452eeef95e90e0d9c42/href</a></iframe><p>This is a contrived example, so the actual setup will vary, but it gives you an idea:</p><ol><li>Whenever build command is called on any package, it will also run build on all dependencies</li><li>Whenever test command is called on any package, it will run without looking for cache</li><li>Whenever dev command is called on any package, it will run the build first and will not cache the output</li><li>Whenever watch command is called it will run without cache</li></ol><p>We then have configurations for commands specific to our application, which allows us to define other rules, e.g. we can say that we do actually want to build dependencies before we run tests inside our application (unlike our packages).</p><p>We can then update our root package.json with some helper commands:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/332bfc4a776662284f30a7e326d06ca0/href">https://medium.com/media/332bfc4a776662284f30a7e326d06ca0/href</a></iframe><p>Notice that we are using a concurrent job to start Next.js server and a separate watch job. That is because Turborepo does not support jobs that do not exit — as a build tool, it needs a successful exit code (or an error code if --continueflag is set) to continue with the pipeline. It’s a minor hinderance, but easy to work around, and it still gives you advantages of performing dependent jobs before running watch command inside the packages.</p><h3>Yarn and Turborepo caches</h3><p>Once you start using this setup, you will notice .yarn/cache directory in the root, as well as .turbo inside your packages. These caches can greatly increase your productivity and time spent on build and other jobs, but they also raise a question — what to ignore and when.</p><p>Both caches are environment specific, so you should probably ignore them whenever the code moves between environments — so add them to .gitignore and .dockerignore (that said, there may be exceptions, e.g. if you want to develop locally and inside the docker container, you can benefit from shared yarn cache).</p><p>Your CI/CD pipeline could benefit from both caches, so make sure to take advantage of them. That said, be mindful of what commands are being cached and what command output is being cached — in certain cases you may <em>not</em> want to skip the job (use --no-cache flag if needed). Worth noting that if you are using an LRU cache, you may end up with some inconsistencies if the Turborepo cache gets partially ejected, so do some trial and error testing on your CI/CD jobs.</p><h3>Dockerizing the production build</h3><p>Most of the credit for this solution, goes to my colleague <a href="https://www.linkedin.com/in/ruben-costa-82869b53/">Ruben Costa</a>, as he weathered a day of stress trying to reduce the image size of our original build from 3Gb to only 250Mb.</p><p>Our monorepo consists of a Next.js application and 2 React SPAs. There are no cross-dependencies between applications, but they depend on various packages. So the challenge was to orchestrate a multi-step build that would contain only the production dependencies needed by each application.</p><p>After going through documentation for Yarn and Turborepo, we have identified two tools that helped us achieve the goal.</p><h4>turbo prune command</h4><p>This command allows you to aggregate a local package with all of its local dependencies in a different location, where you can run install and build, getting only the dependencies you need for that segment of your monorepo to build. See the command documentation <a href="https://turborepo.org/docs/reference/command-line-reference#turbo-prune---scopetarget">here</a>.</p><p>To use this command you will need turbo installed in your environment, either locally or globally, but can always rely on good-old npx to do the job.</p><pre>npx turbo prune --scope @me/application-a</pre><h4>yarn workspaces focus plugin</h4><p>This plugin allows you to substitute the old yarn install --prod, which is no longer part of Yarn CLI. See the plugin documentation <a href="https://yarnpkg.com/cli/workspaces/focus">here</a>.</p><p>Install the plugin to your workspace:</p><pre><em>yarn plugin import workspace-tools</em></pre><p>Use it:</p><pre>yarn workspaces focus --all --production</pre><h4>Dockerfile for a Next.js application</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c5a602d367f0006b0a40baabfb35a9b6/href">https://medium.com/media/c5a602d367f0006b0a40baabfb35a9b6/href</a></iframe><p>Let’s see what’s happening:</p><p>If you are using <a href="https://docs.docker.com/buildx/working-with-buildx/">Docker Buildx</a>, you can mount a cache volume to speed up yarn installs.</p><pre>--mount=type=cache,target=/app/.yarn/cache</pre><p>Prune the application you want to build:</p><pre>npx turbo prune --scope=@me/application-a</pre><p>The command will copy the package and its dependencies into ./out directory.</p><p>Copy everything else you need for your application to build, i.e. global tsconfig.json, install dependencies and build the application:</p><pre>cp -R .yarn .yarnrc.yml tsconfig.json postcss.config.js out/ &amp;&amp; \    cd out &amp;&amp; \    <br>yarn install &amp;&amp; \    <br>turbo run build --filter=<a href="http://twitter.com/me/application-a">@me/application-a</a></pre><p>Reinstall the dependencies skipping devDependencies:</p><pre>yarn workspaces focus --all --production</pre><p>Clean up useless files that are not needed in the Docker image:</p><pre>rm -rf node_modules/.cache .yarn/cache applications/web-server/.next/cache</pre><p>Once you have a built application, you can copy clean production files into a different step and start the server.</p><p>Building images for SPAs is quite straightforward, as you don’t need to worry about extraneous dependencies ending up in the final static build — you just copy over the dist and setup your Nginx or other server to serve static files.</p><h3>Conclusion</h3><p>Monorepos are not a silver bullet — they make development easier, but they complicate CI/CD, and make the dependency hell even worse. Tools like Turborepo make life a bit easier, but do not address the fundamental problems of a JS ecosystem — complexity of dependency modularisation throughout incompatible module systems (ES, CJS etc) that require both runtime and build-time tooling.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e26241a285cb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Accessibility as a design pattern]]></title>
            <link>https://itnext.io/accessibility-as-a-design-pattern-d58940601cab?source=rss-45b096460c47------2</link>
            <guid isPermaLink="false">https://medium.com/p/d58940601cab</guid>
            <category><![CDATA[cypress]]></category>
            <category><![CDATA[accessibility]]></category>
            <category><![CDATA[test]]></category>
            <category><![CDATA[front-end-development]]></category>
            <dc:creator><![CDATA[Ismayil Khayredinov]]></dc:creator>
            <pubDate>Sun, 09 Jan 2022 15:18:29 GMT</pubDate>
            <atom:updated>2022-01-09T15:18:29.684Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LK8X5pKKkdU5LSZ6xHirdg.jpeg" /><figcaption>Image source: <a href="https://www.3playmedia.com/blog/whitepaper-solving-web-accessibility-leaving-no-one-behind/">3playmedia</a></figcaption></figure><p>We often think of accessibility as something that requires extra development effort, treating it as a good-to-have feature, aimed at a small subset of users with special needs. But such line of thinking misses the mark and we fail to appreciate how accessibility standards can improve our day-to-day experiences with building front-end interfaces, leading to healthier more readable code, cleaner tests, and better overall architecture of our applications. Not to mention that it benefits all users in equal measure.</p><p>In the last few months, I have been trying to understand, why front-end developers (myself included) hate writing tests, and why we are so adamant in our belief that “front-end testing is hard”. My explorations have converged on a subject of accessibility, and <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA">ARIA</a> in particular, as a missing ingredient that would make our experiences with testing our code more enjoyable.</p><h3>The Evil of Ambiguity</h3><p>As developers, we spend a lot of time thinking about naming conventions — in essence, we value properly named variables and functions as they reduce ambiguity, allowing us to clearly communicate our intentions to everyone else reading or using the code. Type-safety is another example of disambiguation - it ensures that our code is interpreted as intended, leaving little room for assumption and misuse.</p><p>While we are so focused on the code itself, we don’t often give as much consideration to the resulting output of what this code produces. We write beautiful React components that follow established design patterns and strict naming conventions, we cover them with unit tests, but we are less concerned with how ambiguous the HTML output of these components is, especially when they are composed together on a page.</p><p>Part of the problem is that we are working with CSS, which provides a certain level of visual disambiguation — spacing, borders and box-shadows allow us to deconstruct the page into logical units, which have an implicit semantic meaning. Using visual boundaries, we know, where an element begins and ends, and we can interact with elements we intend to interact with without getting confused by blocks of meaningless text.</p><p>Remove all the CSS, however, and you are left with a bunch of spaghetti HTML that neither yourself, nor the screenreader, nor the test runner can make sense of.</p><h3>The Joy of Readable Code</h3><p>We love issuing commands to our voice assistants using natural language. Code is nothing but a series of commands we give to a computer, so naturally the most readable code is the one that approximates human language.</p><p>In my thinking about front-end testing, I started drawing parallels between writing Cypress tests and using Alexa to navigate a website. If there were an Alexa skill for interacting with web-pages, it would use an engine similar to Cypress with a testing library on top of it.</p><blockquote>— Alexa, find a Login form <br> — Alexa, enter john.doe in Username field<br> — Alexa, enter secret in Password field<br> — Alexa, press Submit button</blockquote><p>The same can be expressed in code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a6e2b013465a48341ebb11eef36ba7ae/href">https://medium.com/media/a6e2b013465a48341ebb11eef36ba7ae/href</a></iframe><p>With some syntactic sugar, we can make the test read like natural language.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7f9f2009ac401abf72460c4c0b230754/href">https://medium.com/media/7f9f2009ac401abf72460c4c0b230754/href</a></iframe><p>We can take it one step further, and map each command to a Gherkin keyword (this would require some instrumentation to ensure that once a landmark has been “seen”, the consecutive calls are executed within its boundaries).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/568745d566ccf5ab60d6cbbaa30e3cff/href">https://medium.com/media/568745d566ccf5ab60d6cbbaa30e3cff/href</a></iframe><p>Of course none of this is possible on a page that has a form styled like a form but doesn’t express the same semantics through HTML code. It can look like a duck, quack like a duck, but it could be just a styled cuckoo.</p><p>The cornerstone of accessibility is cross-device interoperability of user interfaces, and to achieve it, we must first ensure that our interfaces are machine-friendly and are not based solely on the subjectivity of comprehension that is affected by physical characteristics of individual users and determined by the visual appearance of the application alone (at least not until the artificial “intelligence” is intelligent enough to infer semantics from appearance).</p><h3>Conveying Meaning with HTML</h3><p>As noted above, CSS is a powerful tool that can help us convey meaning, but relying on CSS alone is fallacy. First of all, CSS is media-specific, and common sense dictates that you shouldn’t ship CSS to devices that can’t render it. Secondly, you should maintain <a href="https://itnext.io/improving-design-systems-with-solid-principles-part-i-separation-of-concerns-c14088ed09c3"><em>separation of concerns</em></a> and CSS is meant for styling and theming — no matter how hard you try at BEM it’s not the right way of contextualising your HTML markup (besides all that goes out of the window if you use CSS modules). data- attributes are powerful and can help shim certain use-cases, but we already have a wide range of tools at our disposal, including HTML itself and ARIA guidelines, designed by a bunch of smart people, who see the bigger picture. Standards are deterministic and assertive, so they can easily be expressed as testing commands. For example, this ARIA guideline:</p><blockquote><em>The required context role defines the owning container where this </em><a href="https://www.w3.org/TR/wai-aria/#dfn-role"><em>role</em></a><em> is allowed. If a role has a required context, authors </em>MUST<em> ensure that an element with the role is contained inside (or </em><a href="https://www.w3.org/TR/wai-aria/#dfn-owned-element"><em>owned</em></a><em> by) an element with the required context role. For example, an element with role </em><em>listitem is only meaningful when contained inside (or owned by) an element with role </em><em>list.</em></blockquote><p>can be expressed in pseudo code as:</p><pre>expect(listItem).toBeOwnedBy(list);<br>expect(list).toOwn(listItem);</pre><p>Test-driven development is a fine approach, but it’s not easy without an established methodology and a command framework. Test-driven thinking should probably precede TDD — design your tests before your write any. Work out how you want your tests to read first, create utilities to achieve that, and write your components in a way that satisfies the testability criteria. All too often we write integration and e2e tests retrospectively, which end up reading as if we were testing our CSS selector naming conventions. Also remember that for complex use cases, you can export Cypress commands from your component libraries, and import them into your application as a plugin (just like the testing library does).</p><p>A test is a contract with your user, not with the user’s screen, and what matters is that the user can have meaningful interactions with the application, regardless of what device they prefer to use on a given day. Wouldn’t it be great to have a command-line tool for interacting with a website?</p><pre>hj list forms <a href="http://mysite.com/">http://mysite.com</a><br>1. Login<br>2. Register<br>3. Newsletter Signup</pre><pre>hj fill Login<br>Username: <br>Password:<br>Submit?</pre><p>The test case we looked at above is quite straightforward in terms of HTML markup.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/351ac3c82da2ee987bbd3c39c850a6af/href">https://medium.com/media/351ac3c82da2ee987bbd3c39c850a6af/href</a></iframe><p>The problem however is that we don’t know that this particular form is a login form. If we add a registration form on the same page, we are running into an issue of ambiguity — we can no longer distinguish between the two forms as they have a similar set of fields. We could add ids to forms and form elements, or use CSS selectors, but that wouldn’t solve the problem with ambiguity — someone on a screenreader still wouldn’t know the difference.</p><h4>Labelled Landmarks</h4><p>We can disambiguate our interfaces by providing meaningful labels to all landmark roles, such as form, list, region, dialog etc. HTML and ARIA standards allow us to use &lt;label&gt;, aria-label, aria-labelledby to achieve that.</p><p>Let’s disambiguate an infamous To-Do list:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0d11304a6ca6648b301ae0dbe73821ec/href">https://medium.com/media/0d11304a6ca6648b301ae0dbe73821ec/href</a></iframe><p>We can now interact with our list without any problems, making it easy for anyone to code review the tests (without having to know the implementation details of the component). We don’t need the hard-to-read IDs, data-attributes, CSS selectors, n-th child selectors, etc. An additional benefit is that devices without screens can interact with the interface just as well, as your test runner can.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b1a03d05e5372e61fae735a8290775cd/href">https://medium.com/media/b1a03d05e5372e61fae735a8290775cd/href</a></iframe><h4>Relational Markup</h4><p>HTML on its own relies heavily on hierarchies to convey relationships between elements. Using ARIA, we can build more complex semantic relationships between elements.</p><p>As an example, let’s take a look at contextualising errors, as it is a common-enough use-case that leads to complex selector juggling in tests.</p><p>Let’s build a file upload component that stacks the files into a list, validates the file type and shows an error if something fails validation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ef51e9fddaad53e806ec58bdb3c8d990/href">https://medium.com/media/ef51e9fddaad53e806ec58bdb3c8d990/href</a></iframe><p>Using ARIA we can build relationships between elements, which can then be used to abstract repetitive queries, as well as complex queries for elements, which are not direct descendants. In the example above we are indicating that both the input and the line item are described by the same error that relates to the second uploaded item, so it becomes easier to deal with assertions in the tests.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6b21d648b7d7a82599d6006be7a0fd81/href">https://medium.com/media/6b21d648b7d7a82599d6006be7a0fd81/href</a></iframe><p>Combining ARIA roles, semantic HTML tags, and defining relationships between elements, we can start giving meaning to our markup, making it easier to target elements in our tests, while enhancing the experiences of our users.</p><p>Does your spinner mean anything without the CSS? Does the user know that something is happening on the page if they can’t see the screen? Do they know that they have made a mistake, or that it is now safe to submit the form with all their files uploaded?</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a4cd6d9217c8264ffb74fa37c1a76f6f/href">https://medium.com/media/a4cd6d9217c8264ffb74fa37c1a76f6f/href</a></iframe><h4>Statefulness and Readiness</h4><p>Retriability in Cypress is a very useful feature, but it also makes us forget to wonder, how would a user know that after their action something has happened and something else is ready to be interacted with? For instance, how would a user know that the dropdown has appeared and they should make a selection, if they can’t see the screen?</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0b4f3c964f44120052e4b22b7d7acd93/href">https://medium.com/media/0b4f3c964f44120052e4b22b7d7acd93/href</a></iframe><p>We can now make sure that the popup’s visibility is properly communicated to the user (without presuming that if the Cypress can retry it, so can the user).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b73a550ae5afeebaa8acace7d601518f/href">https://medium.com/media/b73a550ae5afeebaa8acace7d601518f/href</a></iframe><h3>Conclusion</h3><p>I am leaving you with two terms to ponder: <strong>Test-Driven Accessibility </strong>and <strong>Accessibility-Driven Testing</strong>.</p><p>Embracing ARIA as a testability criteria is a great opportunity to drive accessibility of our applications, while improving our own experiences with testing. Of course there is a lost more to accessibility than labels and relationships, but we have to start somewhere.</p><p>ARIA specifications are designed with assistive technologies in mind, but the fact remains that a test runner isn’t that different from a screenreader — both rely on markup to do their job, and with a bit of semantics both can excel at it.</p><p>Moving away from CSS selectors and custom data attributes, and relying more on established ARIA guidelines offers a great opportunity to disambiguate the HTML markup, improving usability of the code base as well as it’s final outcome, with a multitude of experiences that diverse user groups have with it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d58940601cab" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/accessibility-as-a-design-pattern-d58940601cab">Accessibility as a design pattern</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Improving design systems with SOLID principles. Part I: Separation of Concerns]]></title>
            <link>https://itnext.io/improving-design-systems-with-solid-principles-part-i-separation-of-concerns-c14088ed09c3?source=rss-45b096460c47------2</link>
            <guid isPermaLink="false">https://medium.com/p/c14088ed09c3</guid>
            <category><![CDATA[component-libraries]]></category>
            <category><![CDATA[separation-of-concerns]]></category>
            <category><![CDATA[design-systems]]></category>
            <dc:creator><![CDATA[Ismayil Khayredinov]]></dc:creator>
            <pubDate>Sun, 02 May 2021 20:54:42 GMT</pubDate>
            <atom:updated>2021-05-03T11:29:39.463Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/560/1*0DcWAntiQmQYoVWpP8f0sg.jpeg" /></figure><p>Not so long ago, should anyone mention a “design pattern” my mind would wander off to plotting Scottish kilts and Polka dots, but the more I deal with and scale real-life interfaces, the more convinced I become that these principles not only have their place in front-end development, but should in fact define it as much as they do back-end development.</p><p>User interfaces should be resilient to change, and it shouldn’t matter if the wind blows from the tail or the head [pun intended] — adapting to changing product requirements and design needs should be a painless developer experience. SOLID principles can help us build better design systems and component libraries by applying established guidelines to engineering future-proof and testable front-end solutions.</p><p>I won’t go into the details of defining what SOLID stands for, as there had been plenty written on the subject: <a href="https://medium.com/@alshdavid/solid-front-end-less-frameworks-more-engineering-patterns-31b8f25135a">here</a>, <a href="https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898">here</a>, and <a href="https://www.baeldung.com/solid-principles">here</a>. I would like to focus instead on what I have learned from my mistakes, and the rules I have established to govern my work.</p><p>For the sake of brevity, I will not illustrate these ideas with code, and will instead continue this chain of thought in series of articles focusing on the assumptions I make here.</p><h3>Separation of Concerns</h3><p>Front-end applications at large are burdened with a variety of responsibilities, and often have too many reasons to change. When approaching a design system from a SOLID perspective, we should look at the different layers that comprise our application, and find ways to split out or abstract the logic to satisfy single-responsibility principle and the other 4 in equal measure.</p><h4>Inversion of control</h4><ul><li><strong>Dependency Layer: </strong>Numerous npm debacles we have seen over the years are a sufficient proof that while we should avoid reinventing the wheel, we should also have an option to replace the wheel if it gets flat. Our libraries should be designed in such a way as to allow substitution of dependencies without a full-scale refactoring mission.</li></ul><blockquote><strong><em>Design system components should not rely on third-party dependencies directly.</em></strong></blockquote><ul><li><strong>Service Layer: </strong>Wherever possible, service singletons should not be imported from other modules directly as it complicates testing and keeps the application coupled with a single concrete implementation. Instead service dependencies should be inverted or injected.</li></ul><blockquote><strong><em>Design system components should not import service singletons directly.</em></strong></blockquote><h4><strong>Error Handling</strong></h4><ul><li><strong>Error Handling Layer: </strong>Errors unfortunately are a natural part of every application, and they vary in their severity and effect on the application and the user flow. While error handling is important, individual components should probably abstain from catching and handling errors, as they should remain dumb and unaware of what logic the application and its parent components use for handling and reporting errors. Error handlers should either be injected into the component with props, or they should be caught at a higher level. Components should not be aware of their runtime environment, hence logging to console is also a no-no.</li></ul><blockquote>Design system components should not attempt to handle errors on their own.</blockquote><h4>Fetching and propagating data to/from external services</h4><ul><li><strong>Transport Layer: </strong>Communication with external services should not be tied to any specific client implementation. Whether you use fetch or axios, or whether you communicate via web sockets or HTTP, should not affect other parts of the application.</li></ul><blockquote><strong><em>Design system components should be oblivious to how the application communicates with the rest of the world.</em></strong></blockquote><ul><li><strong>API layer: </strong>It is common for external services to change their specifications, add or modify call endpoints and signatures, alter response codes and more. A design system should not make assumptions about API implementation details, and should provide a sufficient level of abstraction. Whether the backend uses RESTful API or GraphQL shouldn’t matter to individual components.</li></ul><blockquote><strong><em>Design system components should not know where the data is coming from.</em></strong></blockquote><ul><li><strong>Transformation Layer: </strong>Data models change, newer versions of the API specifications go live, and often require that front-end applications adapt to these changes. Front-end models should therefore not be coupled with back-end DTOs to avoid drilling changes into the component tree.</li></ul><blockquote><strong><em>Design system components should not care about the format or the scope of the external data flows, and should only consume what they need in a format they prefer.</em></strong></blockquote><ul><li><strong>Synchronization Layer:</strong> Dealing with asynchronous data is the day-to-day pain of most applications. Fetching, refetching and caching should be handled by a dedicated layer inside your application and components should either receive resolved data or be responsible for pending the rendering of child components. And as stated already, components should be independent from libraries being used for handling asynchronous data: useSWR, useQuery, useAsync etc all do the same thing but all have their caveats and components should not have to deal with their quirks.</li></ul><blockquote><strong><em>Design system components should ideally be stateless, synchronous and controlled .</em></strong></blockquote><h4>State management</h4><ul><li><strong>Storage Layer: </strong>Components should be able to exist in isolation and not be tied to the specifics of global storage mechanism. Consumers could be using Redux, Recoil, Context or anything else to manage their state. As suggested earlier, components should try to stay stateless and be controlled from the outside through containers and/or higher-order components. State mutations should take place via handlers injected into components with props. As it stands, components should not even be aware that they are mutating something — they should simply use callbacks to dispatch events to the outside world, and what happens with these events is not of components’ concern.</li></ul><blockquote><em>Design system components should not have direct access to global application state.</em></blockquote><ul><li><strong>Caching Layer: </strong>Local storage is not universal and may not be available in the runtime environment. Despite of many storage mechanisms available in the browser, applications should not rely on their availability or accessibility at all times (due to privacy preferences, vendor inconsistencies, size limitations etc). Caching should be optional, and should always take place outside of the component scope.</li></ul><blockquote><em>Design system components should not have direct access to cache.</em></blockquote><h4>Runtime Environment</h4><ul><li><strong>Runtime Layer: </strong>Modern applications are built, rendered and run in various environments: browsers, headless browsers, SSR servers, CI/CD environments. Design system should be universal and not target any one specific environment or rely on the availability of runtime-specific APIs. A window is not always a window.</li></ul><blockquote><em>Design system components should not make assumptions about their runtime environment.</em></blockquote><ul><li><strong>Viewport Layer: </strong>Smart watches, mobiles, tablets, desktops — design systems should work across a variety of screen sizes. It is probably not a good idea for a single component to try and adapt itself to the size of the screen — they should either be instructed from above, or the design system should provide multiple components suited for various screen sizes. Charging components with providing functionality and adapting to various screen sizes bloats the scope of their responsibility.</li></ul><blockquote><em>Design system components should be responsive by design while remaining agnostic to the size of the viewport they are being rendered in.</em></blockquote><ul><li><strong>Animation Layer: </strong>Hardware acceleration is a tough nut to crack. In certain cases, no animation is better than a choppy one. Noone cares for animations on a screen reader. Besides animation should be contextual and fit the overall flow of the user experience.</li></ul><blockquote><em>Design system components should not perform their own animations.</em></blockquote><ul><li><strong>Accessibility Layer: </strong>Mouse, keyboard, touch screen, reader — navigating the application and interacting with it should be possible on a variety of device types. Components should use semantic markup and follow ARIA guidelines to ensure maximum compatibility, accessibility and interoperability.</li></ul><blockquote><em>Design system components should be accessible on all types of devices without knowing what type of device is used to access them.</em></blockquote><ul><li><strong>Localization Layer: </strong>A design system should not become a bottleneck for localization and internationalization. There is no reason for a component to prefer English over French, inches over centimetres, or confusing and frustrating people with other regional nuances and peculiarities (looking at you, US of A).</li></ul><blockquote><em>Design system components should work seamlessly in all locales without knowing what locale they are used in.</em></blockquote><h4><strong>DOM</strong></h4><ul><li><strong>Hierarchy Layer:</strong> To ensure composability, components should not make assumptions about where in the DOM they are mounted and where they are located in relationship to other nodes. For example, adding margins to individual components is in violation of several SOLID principles.</li></ul><blockquote><em>Components should not be aware of their position in the DOM or know of their siblings or parents.</em></blockquote><ul><li><strong>3D Positioning Layer: </strong>Stacking of elements in the CSS tree should be centralized and dictated by the needs of the application, and not presumed by the type of the component.</li></ul><blockquote><em>Components should not manage their own z-indexes or be aware of where in the stack they are located.</em></blockquote><h4>Presentation</h4><ul><li><strong>Composition Layer: </strong>A design system should provide a set of primitives that can be used to compose other components, which can then be used to compose further components: atomic design principles are a great starting point for ensuring that the components are reusable.</li></ul><blockquote><em>Components should be able to (re)arrange their children in any arbitrary order.</em></blockquote><ul><li><strong>Styling Layer: </strong>Composed components should be able to represent their composition and state (e.g. disabled, active, invalid) to the external world (e.g. using Block-Element-Modifier class system), however they should not have any internal inline styles, as that would violate multiple SOLID principles. Components can adapt their composition based on their variant, but they should not have the slightest clue how that variant translates into their visual appearance. A consumer should be able to switch off all the styles (e.g. on a screen reader) and still make sense of the application.</li></ul><blockquote><em>Components should be able to exist without any styles, and should not care how they look to the outside world.</em></blockquote><ul><li><strong>Theming Layer: </strong>Appearance decisions should be made at the level of design system tokens. Changes should cascade to an entire component library or its subset without any additional changes needed inside the components. You should be able to rebrand your business by only changing a few variables, hence blindly following examples in hyped CSS/JS frameworks will complicate your life in the future and will not make your library usable outside of your own organisation.</li></ul><blockquote><em>Components should exist in a black box without any knowledge of colors and absolute units of measurement that exist in the outside world.</em></blockquote><h3>Conclusion</h3><p>This will be it for today. I have started working on some code to illustrate these principles and will share it as the time allows. If you disagree with any of my conclusions, feel free to leave a comment and let’s deliberate together. However subjective these posits are, I think they do reflect real-life challenges I have experienced over the years.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c14088ed09c3" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/improving-design-systems-with-solid-principles-part-i-separation-of-concerns-c14088ed09c3">Improving design systems with SOLID principles. Part I: Separation of Concerns</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unit testing React components in Cypress]]></title>
            <link>https://ismayilkhayredinov.medium.com/unit-testing-react-components-in-cypress-e8a92e819d93?source=rss-45b096460c47------2</link>
            <guid isPermaLink="false">https://medium.com/p/e8a92e819d93</guid>
            <category><![CDATA[unit-testing]]></category>
            <category><![CDATA[cypress]]></category>
            <category><![CDATA[webpack]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Ismayil Khayredinov]]></dc:creator>
            <pubDate>Mon, 15 Feb 2021 08:09:41 GMT</pubDate>
            <atom:updated>2021-02-15T08:09:41.416Z</atom:updated>
            <content:encoded><![CDATA[<h3>Unit testing React components in Cypress without CRA and react-scripts</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3x3krcZ0QN7x8Vh-_p474w.png" /></figure><p>So you have an app or component library that you have created without create-react-app and you want to unit test it using Cypress. I had to go through some hassle to get that working with a custom webpack configuration, but it’s finally working, and here is my solution.</p><h4>1. Install Cypress</h4><pre>npm install --save-dev cypress @cypress/react</pre><p>Additional installation instructions can be found <a href="https://docs.cypress.io/guides/getting-started/installing-cypress.html">here</a>.</p><h4>2. Add cypress.json</h4><pre>{<br>  &quot;testFiles&quot;: &quot;**/*.spec.js&quot;,<br>  &quot;experimentalComponentTesting&quot;: true,<br>  &quot;componentFolder&quot;: &quot;./src&quot;,<br>  &quot;nodeVersion&quot;: &quot;system&quot;,<br>  &quot;env&quot;: {<br>    &quot;webpackFilename&quot;: &quot;./webpack.tests.config.js&quot;<br>  }<br>}</pre><ul><li>testFiles specifies a pattern for files that contain your tests</li><li>experimentalComponentTesting enables unit testing support. Though it says experimental, it’s quite stable now</li><li>componentFolder is akin integrationFolder and specifies where your unit and integration tests reside. You could keep your unit tests inside your ./src folder and have one to one mapping to your components, i.e. src/Button/Button.js and src/Button/Button.spec.js</li><li>nodeVersion tells Cypress to use the node version of the system it runs on, and not use the bundled version. This might be necessary if you are using node-sass and other binaries that are platform specific.</li><li>env.webpackFilename specifies the localtion of the webpack config you want to use for your tests. Cypress will transpile your components on the fly, hence you may need to have a different configuration that what you use to build your project. Think Storybook.</li></ul><p>All the configuration options are defined <a href="https://docs.cypress.io/guides/references/configuration.html">here</a>.</p><h4>3. Update support bindings</h4><p>Update ./cypress/support/index.js and import directives needed for Cypress to mount your unit tests into the Cypress DOM.</p><pre>// ./cypress/support/index.js</pre><pre>import &#39;@cypress/react/support&#39;;</pre><h4>4. Configure the plugins</h4><p>Update ./cypress/plugins/index.js and tell Cypress how to process your test files.</p><pre>// ./cypress/plugins/index.js</pre><pre>module.exports = (on, config) =&gt; {<br>    require(&#39;@cypress/react/plugins/load-webpack&#39;)(on, config);</pre><pre><em>    </em>return config;<br>};</pre><p>load-webpack plugin will transpile your components files during testing using webpack config you have specified in env.webpackFilename .</p><h4>5. Write your tests</h4><pre>// ./src/Button.spec.js</pre><pre>import React from &#39;react&#39;;<br>import Button from &#39;./Button&#39;;<br>import { mount } from &#39;@cypress/react&#39;;</pre><pre>describe(&#39;Button&#39;, () =&gt; {<br>    it(&#39;should handle onClick&#39;, () =&gt; {<br>        const onClick = cy.stub().as(&#39;onClick&#39;);<br><br>        mount(<br>            &lt;Button onClick={onClick}&gt;Click me&lt;/Button&gt;,<br>        );<br><br>        cy.findByRole(&#39;button&#39;, { name: &#39;Click me&#39; }).click();<br><br>        cy.get(&#39;@onClick&#39;).should(&#39;have.been.calledOnce&#39;);<br>    });<br>});</pre><h4>6. Run your tests</h4><p>You can now run your tests with ./node_modules/.bin/cypress open or ./node_modules/.bin/cypress run .</p><h4>7. Rejoice</h4><p>Running components tests in an actual DOM boosts confidence in the reliability of your tests. You can run them in multiple browsers. You can create functional tests combining multiple components together to see how they interact with each other, how user interacts with them using the mouse and the keyboard, and you can stub your dependencies to avoid complex cy.intercept definitions.</p><p>And added bonus, is that Cypress automatically generates test coverage reports, so you don’t have to instrument anything.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e8a92e819d93" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>