<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Saleor Commerce</title>
    <description>The latest articles on DEV Community by Saleor Commerce (@saleor).</description>
    <link>https://dev.to/saleor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F6215%2F59386e70-64b9-4592-9a8d-45bb80842d1a.png</url>
      <title>DEV Community: Saleor Commerce</title>
      <link>https://dev.to/saleor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saleor"/>
    <language>en</language>
    <item>
      <title>Building Reliable Software: The Trap of Convenience</title>
      <dc:creator>Patryk Zawadzki</dc:creator>
      <pubDate>Mon, 16 Feb 2026 14:53:09 +0000</pubDate>
      <link>https://dev.to/saleor/building-reliable-software-the-trap-of-convenience-26jo</link>
      <guid>https://dev.to/saleor/building-reliable-software-the-trap-of-convenience-26jo</guid>
      <description>&lt;p&gt;When I started to learn programming a PC (as opposed to programming an Amiga), it was still the 20th century. In the days of yore, when electricity was a novel concept and computer screens had to be illuminated by candlelight in the evenings, we'd use languages like C or Pascal. While the standard libraries of those languages provided most of the needed primitives, it was by no means a "batteries included" situation. And even where a standard library solution existed, we'd still drop to inline assembly for performance-critical sections because those computers were definitely not fast enough to spare any CPU cycles. PCs were also still similar enough that they used the same CPU architecture, and thus the same machine code, so the assembly sections were not that hard to maintain.&lt;/p&gt;

&lt;p&gt;Today, the x86 architectures come with dozens of optional extensions and you're not even guranteed to encounter an "Intel" machine (technically referred to as "amd64"). RISC CPUs are coming back to reclaim computing thanks to the efforts of Apple (Apple Silicon), Amazon (AWS Graviton), Microsoft (Azure Cobalt), and others licensees of ARM. In 2026, writing assembly code is something you only do if there is absolutely no other solution. The number of versions of that inline section keeps growing with every new CPU family. Meanwhile, modern compilers are getting so good at optimizing the resulting machine code, and the computers got so fast, that manual optimization is usually not worth the effort. Unless it absolutely is.&lt;/p&gt;

&lt;p&gt;So the modern programming languages split. There are system programming languages that optimize for the performance with an extra focus on safety, like Rust. And there are application programming languages that optimize for "productivity", that is the speed at which we produce useful software, rather than the speed at which said software runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Productivity Through Convenience
&lt;/h2&gt;

&lt;p&gt;Productivity demands higher-order abstractions. Instead of representing how the underlying hardware works, the programming languages and their libraries instead model how people &lt;em&gt;think&lt;/em&gt; about the problems.&lt;/p&gt;

&lt;p&gt;Thanks to this, instead of writing several pages of C code to allocate a send buffer and a receive buffer, open a socket, set its options, resolve the target hostname, establish a connection, and so on, you can fetch and parse a web resource in a few lines of Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With just a few keystrokes I can achieve what used to take me hours just to type out. Thanks to both Python (and C#, and TypeScript, and likely also your favorite language) and the &lt;code&gt;requests&lt;/code&gt; library (and its equivalents) being open-source and available for everyone to use for free, we can all collectively and individually build more complex systems with less effort.&lt;/p&gt;

&lt;p&gt;Except that, as I mentioned in my previous post, &lt;a href="https://dev.to/saleor/building-reliable-software-planning-for-things-to-break-19nh"&gt;it's systems all the way down&lt;/a&gt;. And all those systems make a (conscious or not) choice on what it means to be a reliable tool.&lt;/p&gt;

&lt;p&gt;As a fun exercise, look at the above example and try to figure out what the biggest problem with that bit of code is. It certainly works for the happy path, which would make it pass a lot of the unit tests!&lt;/p&gt;

&lt;p&gt;Let's walk through several (but not all, the complete list would be way too long) of the things that can go wrong in just two lines of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Litany of Failure Modes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;An error will be raised if the URL is not a valid URL (or not even a string, yay modern languages!).&lt;/li&gt;
&lt;li&gt;An error will be raised if the target hostname cannot be resolved (either the domain does not exist, or your DNS server cannot be reached).&lt;/li&gt;
&lt;li&gt;If the target hostname resolves to multiple IP (IPv6 or IPv4) addresses, then try each one is tried in sequence until one accepts the connection on the destination port. Since no timeout is specified, the default system timeout for TCP is used (6 connection attempts totalling about 127 seconds on modern Linux systems) &lt;em&gt;for each individual IP address&lt;/em&gt;. If none of the IP addresses end up accepting, an error is raised.&lt;/li&gt;
&lt;li&gt;If the target system does not speak our desired protocol and responds with random gibberish, an error is raised.&lt;/li&gt;
&lt;li&gt;An error is raised if the protocol is secure (like HTTPS) and the target server does not offer any of the TLS variants we trust.&lt;/li&gt;
&lt;li&gt;If the protocol is secure (like HTTPS) and the target system responds with an invalid TLS certificate (either broken, expired, or not trusted by any of the certificates our system trusts), an error is raised.&lt;/li&gt;
&lt;li&gt;An error is raised if the certificate is valid but does not match the target hostname.&lt;/li&gt;
&lt;li&gt;If the target system stops responding, an error is raised. Since no timeout is specified, the default system TCP read timeout is used (60 seconds on modern Linux systems). If the target system sends &lt;em&gt;anything&lt;/em&gt; during that time window, the system timer will be reset as the read was successful.&lt;/li&gt;
&lt;li&gt;If the response is a valid HTTP redirect response, the process is restarted from step 1, using the redirect URL as the new target URL.&lt;/li&gt;
&lt;li&gt;If we somehow get to this point (as unlikely as it seems) and the response is not a valid JSON string, an error is raised.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Different parts of the above are problematic, or &lt;em&gt;extremely&lt;/em&gt; problematic, depending on what your code is attempting to achieve.&lt;/p&gt;

&lt;p&gt;If your goal is to download a movie to watch, preserve artifacts of a system you're about to delete, or create complete copies of websites for a project like the Internet Archive's Wayback Machine, then chances are you want the code to take all the necessary time, perhaps even multiple attempts instead of giving up on the first transient error. The desired outcome is to access the resource at all costs.&lt;/p&gt;

&lt;p&gt;But if your goal is to figure out if an order is eligible for free delivery, you probably don't want to keep the user waiting for literally &lt;em&gt;minutes&lt;/em&gt; just because some external server crashed. By the time you send your fallback response, the user will be nowhere to be found, having abandoned their order and long since closed the browser tab.&lt;/p&gt;

&lt;p&gt;A web API that takes minutes to respond is a useless one, but the same is true for many other use cases. Imagine having to stand in front of an ATM for several minutes before the machine finally spits your card out with an error. All the while people behind you start to make arrangements for your upcoming funeral.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failing Fast
&lt;/h2&gt;

&lt;p&gt;If you guessed that the problem with the code is that it could crash, you probably guessed wrong. I'm going with "probably", because &lt;em&gt;I&lt;/em&gt; don't know what &lt;em&gt;your&lt;/em&gt; use case is. But most systems handle failing fast rather gracefully.&lt;/p&gt;

&lt;p&gt;A simple &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;except&lt;/code&gt; block wrapped around the call to our function could take care of specifying the fallback behavior. And even that is absent, the underlying framework is likely built to withstand that and return an error instead of crashing, like in the old times. What it can't do is rewind the &lt;em&gt;time&lt;/em&gt; it took the code to fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resisting Abuse
&lt;/h2&gt;

&lt;p&gt;You can't have reliability with at least some resilience (but I guess &lt;em&gt;failing reliably&lt;/em&gt; is also a form of consistency). So you need to teach the system how to defend itself against undesirable behaviors. Some of them outright malicious, some not.&lt;/p&gt;

&lt;p&gt;In the above example, an extremely malicious behavior would be to take a domain name and configure its zone record to resolve to 511 different IP addresses, all from non-routable network segments such as &lt;code&gt;196.168.0.0/16&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Or to have a domain resolve to 9 non-routable IPs and one that returns a HTTP redirect to the same domain.&lt;/p&gt;

&lt;p&gt;Or to point the URL to a server that streams the response by sending one byte every 50 seconds, thus never triggering a read timeout.&lt;/p&gt;

&lt;p&gt;If those numbers sound oddly specific, this is because we did all those things internally at Saleor. I don't remember why 511 was the number of IPs we went for, maybe CloudFlare didn't allow more records to be added, maybe it didn't matter because no one was going to wait for that test to time out anyway.&lt;/p&gt;

&lt;p&gt;But a malicious actor could also ask your system to access a URL of an internal system they can't access directly. If the URL comes from an untrusted source, it could be used to probe your internal network for open ports, based on the error codes you send back. And if your system is foolish enough to show the entire "unexpected response" from such an URL, also to steal your credentials.&lt;/p&gt;

&lt;p&gt;Did you know that any EC2 instance on AWS can make a request to &lt;code&gt;http://169.254.169.254/latest/meta-data/&lt;/code&gt; to learn about its own roles? And that a subsequent call to &lt;code&gt;http://169.254.169.254/latest/meta-data/iam/security-credentials/&amp;lt;role-id&amp;gt;/&lt;/code&gt; returns both an AWS access key and its corresponding secret? Yikes on leaking that!&lt;/p&gt;

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

&lt;p&gt;Convenience is the biggest pitfall of the modern high-abstraction productivity. All the important bits and compromises are buried deep in the convenience layers, making it impossible to reason about systems without popping the hood. Meanwhile, your IDE, your code review tools, your whiteboard interviews, surface the types of problems that—in the grand scheme of things—don't matter all that much: the ones your system can recover from automatically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you ever find yourself accessing &lt;em&gt;the great unknown&lt;/em&gt; from Python code, take a look at the &lt;a href="https://github.com/saleor/requests-hardened" rel="noopener noreferrer"&gt;&lt;code&gt;requests-hardened&lt;/code&gt;&lt;/a&gt; wrapper we created for &lt;code&gt;requests&lt;/code&gt;. It makes it safe to point the library at untrusted URLs from code that doesn't have forever to wait for the outcome. It also works around a &lt;a href="https://github.com/python/cpython/issues/106283" rel="noopener noreferrer"&gt;DoS potential in Python's standard library&lt;/a&gt; that we also reported (responsibly, it's only public because we were asked by the maintainers to make it public).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Make sure your team doesn't mistake simple code for the simplicity of the underlying systems. The reassurance offered by the complexity becoming invisible is a fake one.&lt;/p&gt;

&lt;p&gt;Happy failures. Farewell and until next time!&lt;/p&gt;

</description>
      <category>sre</category>
      <category>architecture</category>
      <category>security</category>
      <category>software</category>
    </item>
    <item>
      <title>Building Reliable Software: Planning for Things to Break</title>
      <dc:creator>Patryk Zawadzki</dc:creator>
      <pubDate>Fri, 13 Feb 2026 15:18:24 +0000</pubDate>
      <link>https://dev.to/saleor/building-reliable-software-planning-for-things-to-break-19nh</link>
      <guid>https://dev.to/saleor/building-reliable-software-planning-for-things-to-break-19nh</guid>
      <description>&lt;p&gt;We often joke that software is usually implemented in two steps: the first 80% of the time is spent on making it work, and then the second 80% of the time is spend on making it work well. People mistake demos, proofs-of-concept, and walking skeletons for products because the optimistic path is often realized in full, so under ideal lab conditions, a PoC behaves just like the full product.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://saleor.io/" rel="noopener noreferrer"&gt;Saleor&lt;/a&gt;, where I act as a CTO, we spend a significant part of our engineering effort embracing the different failure states and making sure the unhappy paths are covered as well as the happy ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embracing the Failure
&lt;/h2&gt;

&lt;p&gt;Because it does not matter how good your software is or how expensive your hardware is, something will eventually break. The only systems that never break are ones that are never used. Amazon's AWS spends more money on preventive measures than you will ever be able to, and yet, a major outage took out the entire &lt;code&gt;us-east-1&lt;/code&gt; region just last October. In 2016, the world's largest particle accelerator, CERN's Large Hadron Collider, was taken offline by a single weasel. Google's Chromecast service was down  for days because someone forgot to renew an intermediate CA certificate, something that needs to be done once every 10 years.&lt;/p&gt;

&lt;p&gt;The question is not &lt;em&gt;if&lt;/em&gt; but &lt;em&gt;when&lt;/em&gt;. Reliability is both about pushing that point as far it's practically possible and about planning what happens when it inevitably comes. And both suffer from brutally diminishing returns.&lt;/p&gt;

&lt;p&gt;Every additional "nine" in your uptime—getting from 90% to 99%, from 99% to 99.9%, and so on—requires ten times as much resources as the previous one. Getting from one nine to two is usually trivial and gives you roughly 33 days of additional uptime per year. The next step is ten times as much work for only 3.2 additional days. Then it's even more expensive and results in just under 8 hours of additional uptime. You then get to 47 minutes, 4.7 minutes, 37 seconds, and so on. At some point the cost of getting to the next step exceeds the losses from being unavailable.&lt;/p&gt;

&lt;p&gt;It's similar with your firefighting tools. You can get from multiple down to one business day per simple fix with relatively simple measures. It takes more expensive tools, stricter procedures, and paid on-call duty to guarantee a same-day attempt at fixing. Shortening it further requires investing in even more specialized (and costlier) tools, better training for engineers, and a lot of upfront work on observability. And again, at some point the cost of lowering the downtime even further is guaranteed to exceed the cost of any prevented downtime.&lt;/p&gt;

&lt;p&gt;Some of the component failures you'll encounter will be self-inflicted. Because one day you'll discover that a database server needs to be brought offline to upgrade it to the newer version that fixes a critical CVE.&lt;/p&gt;

&lt;p&gt;Given all the above, the pragmatic approach dictates that instead of trying to achieve the impossible, we should build systems that anticipate failures, and, ideally, recover from them without human intervention. While every component's availability is capped by the product of all the SLOs of its direct dependencies, the larger system can be built to tolerate at least some failing components.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CAP Theorem
&lt;/h2&gt;

&lt;p&gt;The CAP theorem dictates that any distributed stateful system can only achieve at most two of the three guarantees: consistency, availability, and partition tolerance.&lt;/p&gt;

&lt;p&gt;What is a distributed stateful system? Anything that stores any data and consists of more than one component. A shell script accessing a database is such a system, and so is a Kubernetes service talking to a server-less database.&lt;/p&gt;

&lt;p&gt;The consistency guarantee demands that every time the system returns data, it either returns the most up-to-date data, or the read fails. Under no circumstances can the system return a stale copy as doing so could break an even larger system for which your system is a dependency.&lt;/p&gt;

&lt;p&gt;The availability guarantee dictates that if the system receives a request, it must not fail to provide a response.&lt;/p&gt;

&lt;p&gt;Partition tolerance means the system needs to remain fully operational even if some of its components are unable to communicate with some other components.&lt;/p&gt;

&lt;p&gt;I think it's clear that it's impossible for a system to always return the latest data and never return an error while it can't reach its main database. That's why you can only pick two of the virtues and in most cases it's only practical to achieve one.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's Systems All the Way Down
&lt;/h2&gt;

&lt;p&gt;It's also important to note that any complex solution is usually  a multitude of smaller systems in a trench coat. You can have systems within systems and you can pick different corners of the CAP triangle for every individual subsystem.&lt;/p&gt;

&lt;p&gt;A practical example may be an online store that uses an external system to figure out if a given order qualifies for free shipping. The free shipping decision is delegated to a third-party system, a black box only accessed through its API. The order lines and the cost of regular shipping are stored in some sort of a database, and the storefront is backed by a web service that needs to return the valid shipping methods.&lt;/p&gt;

&lt;p&gt;Now we have the following systems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The external shipping discount service that we don't control. that can provide any of the CAP guarantees. Whatever it does is beyond our control.&lt;/li&gt;
&lt;li&gt;Our internal free shipping eligibility service that depends on the database (as it needs to be able to send the cart contents) and the external service (as it needs to receive the response).&lt;/li&gt;
&lt;li&gt;Our public web service that tells the storefront what shipping methods are available that depends on our internal free shipping eligibility service and the database (to figure out the cost of regular shipping).&lt;/li&gt;
&lt;li&gt;The entire store that depends on the storefront running in the client's browser being able to communicate, over the internet, with our public web service.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since we can't do much about the external system (and if it goes down, fixing it is beyond our reach), we can make the pragmatic decision to make any system that depends on it focus on partition tolerance. For example, we could decide that if the external system can't be reached, any order is eligible for free shipping. This way, when the external system inevitably goes down, we can err on the side of generosity and lose some money on shipping but keep our store transactional (which usually more than makes up for the shipping cost). We could also decide the opposite, that if the service is down, no order can be shipped for free, potentially upsetting some customers, but still taking orders from everyone else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better Fault Tolerance
&lt;/h2&gt;

&lt;p&gt;I think it's clear that whichever way we choose is preferable to the entire store becoming unavailable and thus accepting no orders at all.&lt;/p&gt;

&lt;p&gt;If we broaden up the partition tolerance to general fault tolerance, we can design systems that are internally as fault-tolerant as is pragmatic and externally as available as practically possible. This prevents cracks from propagating from component to component, which gives the larger system a chance of staying transactional even while some of its individual subsystems struggle to stay online.&lt;/p&gt;

&lt;p&gt;Fault tolerance can be achieved through documented fallbacks and software design patterns. It's a process that needs to start during the design stages as it's not easy to bolt onto an existing system. All external communication has to be safeguarded and time-boxed, with timeouts short enough not to grind the larger system to a halt. Repeated failures can temporarily eliminate the external dependency through patterns like the circuit breaker.&lt;/p&gt;

&lt;p&gt;High availability is usually achieved through redundancy. If a single component has a 1% chance of randomly failing, adding a second duplicate as a fallback reduces that chance to 0.01%. With proper load balancing it also provides additional capacity and is a first step to auto-scaling. Of course, failure is rarely &lt;em&gt;truly&lt;/em&gt; random and is often tied to the underlying hardware or other components, so those, too, may need to be made redundant. Multi-zone or multi-region deployments, database clustering, those are all tools that let you lower the chance of things going south at the expense of hard earned cash.&lt;/p&gt;

&lt;p&gt;It's up to you to figure out the sweet spot that offers you relative peace of mind while still keeping the operational expenses below the potential losses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Healing Systems
&lt;/h2&gt;

&lt;p&gt;Given that we can't fully prevent components from failing, what if we at least eliminated the necessity of a human tending to them once they do? A self-healing system is one that is designed  to recover from failures without external intervention. I'm not talking about self-adapting code paths that the prophets of AGI promise, I'm talking about automatic retry mechanisms, dead letter queues for unprocessable events, and robust work queues that guarantee at-least-once delivery.&lt;/p&gt;

&lt;p&gt;A good system is one that fails in a predictable manner and recovers from the failure in a similarly predictable manner. Eventual consistency is much easier to achieve than immediate consistency. Exactly-once delivery is often impossible to guarantee but at-least-once beats at-most-once under most circumstances.&lt;/p&gt;

&lt;p&gt;Design your systems with idempotency in mind so it's safe to retry partial successes. Use fair queues to prevent a single noisy task from adding hours to wait time to all its neighbors. Treat every component as if it was malfunctioning or outright malicious and ask yourself, "How can I have the system not only tolerate this but also fully recover?"&lt;/p&gt;

&lt;p&gt;Perhaps the most extreme version of this is the Chaos Monkey from Netflix, a tool designed to break your system's components in controllable yet unpredictable way. The engineers behind Chaos Monkey theorized that in a system designed around reliability, the actions of the Monkey should be completely invisible from the outermost systems perspective. True, with an asterisk that if you get anything wrong, your services are down and you're losing money. Perhaps not everyone can afford that.&lt;/p&gt;

&lt;p&gt;And to get it right is often more about being smart than clever. The self-healing part could be as easy as implementing a health check and restarting the component. Or it could mean dropping the cache if you're unable to deserialize its contents, because maybe you forgot that caches can persist across schema changes. Or even restarting your HTTP server every 27 requests while you're figuring out why the 29th request always causes it to crash. Observe your systems and learn from their failures, adding preventive measures for similar classes of future problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remain Vigilant
&lt;/h2&gt;

&lt;p&gt;In 2026, perhaps more than ever, remain vigilant. With the advent of generative AI, some parts of your service will likely end up being written by an LLM. That model, like all models, was trained on a large corpus of code, both purely commercial and Open Source. You have to remember that most of this code, even if it didn't completely neglect its reliability engineering homework, may have vastly different assumptions about where it stands with regard to the CAP theorem.&lt;/p&gt;

&lt;p&gt;You cannot blindly transplant code from one project to another, from an AI chatbot, or from a StackOverflow answer, without also consciously asking yourself, "How does this code anticipate and deal with failures? And does it fit my goals for this particular subsystem?"&lt;/p&gt;

&lt;p&gt;Happy failures. Farewell and until next time!&lt;/p&gt;

</description>
      <category>sre</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>software</category>
    </item>
    <item>
      <title>Cleaning up large frontend codebase</title>
      <dc:creator>Lukasz Ostrowski</dc:creator>
      <pubDate>Wed, 15 Oct 2025 14:21:30 +0000</pubDate>
      <link>https://dev.to/saleor/cleaning-up-large-frontend-codebase-1ani</link>
      <guid>https://dev.to/saleor/cleaning-up-large-frontend-codebase-1ani</guid>
      <description>&lt;p&gt;Recently I started work on the new extensions functionality in Saleor Dashboard. This repo is quite large (450k LOC). I decided to make some cleanup before I introduce the feature.&lt;/p&gt;

&lt;p&gt;So the plan was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make a feature...&lt;/li&gt;
&lt;li&gt;... but first, refactor this and that....&lt;/li&gt;
&lt;li&gt;... but first, remove some dead code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;tldr: This post is about removing approx 30k LOC (~6.6%) of the codebase and bundle size (pre-gzipped) lowered by 350KB&lt;/p&gt;

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

&lt;p&gt;First of all, I have diagnosed where the main gains are. I found three main areas&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Invalid module refactor&lt;/li&gt;
&lt;li&gt;Stale feature flags&lt;/li&gt;
&lt;li&gt;Unused graphQL queries&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted to focus mainly on them, because I already had some understanding what's going on. I also wanted to keep them specifically, to reduce the code complexity before my change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invalid module refactor
&lt;/h3&gt;

&lt;p&gt;Some time ago Saleor consolidated extensions model - we have merged Apps, Plugins and Webhooks under the single domain - extensions.&lt;/p&gt;

&lt;p&gt;The refactor happened using feature flag, but it was not "branching" with specific differences (like show route A or B). Instead entire module was copy pasted 1:1 to a new directory. Then for few months we maintained both of them and they slowly started to differ.&lt;/p&gt;

&lt;p&gt;The goal is to drop the old code without breaking anything&lt;/p&gt;

&lt;h3&gt;
  
  
  Stale feature flags
&lt;/h3&gt;

&lt;p&gt;We also had several feature flags, all of them quite stale. All of them enabled, but entire code branching for disabled flags still was in the source. The goal here was to drop flags and remove dead code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unused queries
&lt;/h3&gt;

&lt;p&gt;When I removed flags, I realised many graphQL queries exist in the codebase (and are processed by codegen into types and executable operations). The goal here was to remove as many of them as possible&lt;/p&gt;

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

&lt;p&gt;I will not write a deep dive of my every move in this process, but rather share some learnings on the process&lt;/p&gt;

&lt;h3&gt;
  
  
  Static analysis
&lt;/h3&gt;

&lt;p&gt;During the cleanup I was using existing static analysis tool and improved their config. It's probably the best possible way to diagnose and maintain quality around dead code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESLint - detect unused declarations, plugins like graphQL plugin can help detecting issues with queries, etc.&lt;/li&gt;
&lt;li&gt;Knip - scans the code for unused exports. Not always working, but it can show not-so-easy dependencies. For example, when code is imported by test, technically this code is not "unused". Knip can detect that&lt;/li&gt;
&lt;li&gt;Dependency cruiser - can enforce architecture decisions like forbid  circular deps etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was using such tools on every PR to validate my changes and find new issues&lt;/p&gt;

&lt;h3&gt;
  
  
  Tests
&lt;/h3&gt;

&lt;p&gt;Obviously before the refactor, we should have good tests coverage to verify if our changes didn't break anything. &lt;/p&gt;

&lt;p&gt;With AI this is easier than ever. Code that is hard to test is often not tested. Claude wrote tests for me, I only reviewed if assertions are valid.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dropping &lt;code&gt;default export&lt;/code&gt; and barrel files
&lt;/h3&gt;

&lt;p&gt;Both &lt;code&gt;export default&lt;/code&gt; and barrel files (index.js/ts) are known anti-patterns. Their existence make static analysis a nightmare. I did several runs to remove them before moving with other changes&lt;/p&gt;

&lt;h3&gt;
  
  
  Small PRs
&lt;/h3&gt;

&lt;p&gt;It's tempting to add more and more changes into the same PR, but it never works. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large PR is hard/impossible to be checked by human properly&lt;/li&gt;
&lt;li&gt;LLMS context is too low to review them as well&lt;/li&gt;
&lt;li&gt;It's not friendly to ask someone to check such code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, I tried (not always successfully, but it's something) to introduce small and cohesive changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PR 1: Remove &lt;code&gt;export default&lt;/code&gt; statements and update imports → reviewer only has one "context" to verify and usually if it builds, it works&lt;/li&gt;
&lt;li&gt;PR 2: Remove barrel files and update imports → ditto&lt;/li&gt;
&lt;li&gt;PR 3: Remove bunch of files, nothing else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;etc.&lt;/p&gt;

&lt;p&gt;I broke this rule once - and on the review the bug was found. I didn't have time to fix it then, and the change was quite large. Before I was able to go back to my work, I had so many conflicts, I had to start from scratch&lt;/p&gt;

&lt;h3&gt;
  
  
  AI codemods instead of direct changes
&lt;/h3&gt;

&lt;p&gt;I realized using AI that for such a large codebase, AI often fail to keep the context. I use it to write a test suite, but I can't ask it to refactor the codebase.&lt;/p&gt;

&lt;p&gt;What I did instead was starting to use AI to write codemods and other scripts.&lt;/p&gt;

&lt;p&gt;For example, I haven't found a tool that would find unused graphQL queries (maybe because we colocate them in .ts files?). It's not easy to find dead code here, because codegen is transforming them to documents, hooks etc. So only way to find it is&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove document, rebuild codegen, build app/types and check if it's failing&lt;/li&gt;
&lt;li&gt;Automate it somehow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To automate it, I gave Claude rules how codegen is generating queries (eg &lt;code&gt;query ProductsPage&lt;/code&gt; generates &lt;code&gt;useProductsPage&lt;/code&gt; hook). It took approx 30 minutes, but it vibecoded a script that pretty much worked. Script itself can be a trash that I won't maintain, but I can use it to find what to remove, and delete it afterwards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep flags clean
&lt;/h3&gt;

&lt;p&gt;Our flags are controlled custom way, so we don't have any fancy tools to control their staleness. But we should, either use a service or introduce a process that will allow us to periodically drop them. Flags usually add major complexity and should be short living.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>refactoring</category>
      <category>react</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Upgrading React 17 to 18 in a large codebase using AI</title>
      <dc:creator>Lukasz Ostrowski</dc:creator>
      <pubDate>Mon, 11 Aug 2025 06:24:11 +0000</pubDate>
      <link>https://dev.to/saleor/upgrading-react-17-18-in-large-codebase-using-ai-506j</link>
      <guid>https://dev.to/saleor/upgrading-react-17-18-in-large-codebase-using-ai-506j</guid>
      <description>&lt;p&gt;Recently, at Saleor, we &lt;a href="https://saleor.io/blog/cmd-k" rel="noopener noreferrer"&gt;invested some time into CMD+K&lt;/a&gt; (command bar) enhancements. While working on it, I realised our custom solution had become too limited and hard to maintain, so it was time to switch to an existing library.&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://github.com/saleor/saleor-dashboard" rel="noopener noreferrer"&gt;Dashboard&lt;/a&gt; - a large and mature codebase - is still running on React 17, which turned out to be a blocker because the library requires v18. You might wonder why we’re still on a five-year-old version. The answer is simple: if we don’t need a change, we prioritise other work. Until now, there was no real need to upgrade.&lt;/p&gt;

&lt;p&gt;So, the question was: is the new CMD+K enough of a reason to invest the time in upgrading?&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope of Changes
&lt;/h2&gt;

&lt;p&gt;Fortunately, changes from 17 to 18 are quite simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change how ReactDOM creates a root node (manual change, usually once per project)&lt;/li&gt;
&lt;li&gt;Upgrade types: get rid of React.FC and fix types in general.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I couldn’t find a codemod that safely migrated &lt;code&gt;React.FC&lt;/code&gt; to the shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Pros&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so I started doing this manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying an Agent
&lt;/h2&gt;

&lt;p&gt;Manually fixing these would be a monkey job, so it's time to leverage AI for help.&lt;/p&gt;

&lt;p&gt;Spoiler: it worked, but I had a few rounds of incrementing prompts:&lt;/p&gt;

&lt;h3&gt;
  
  
  Precise Prompt
&lt;/h3&gt;

&lt;p&gt;Explaining to the model what to do worked quite well, but it was too literal.&lt;/p&gt;

&lt;p&gt;For example, some components used destructuring (&lt;code&gt;{a, b, c}: Props&lt;/code&gt;), others used &lt;code&gt;props: Props&lt;/code&gt;. This mattered - in some places we passed &lt;code&gt;props&lt;/code&gt; to legacy Material UI styling via &lt;code&gt;useStyles(props)&lt;/code&gt;, so destructuring broke the flow.&lt;/p&gt;

&lt;p&gt;I had to do a few rounds to explain exactly what to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus on Type-Checked Files
&lt;/h3&gt;

&lt;p&gt;Instead of scanning all the files, I told the agent to run &lt;code&gt;tsc&lt;/code&gt; first and aggregate files with errors. This kept the AI focused on relevant files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working in Batches
&lt;/h3&gt;

&lt;p&gt;Model context was clearly not enough to scan entire codebase. I explicitly asked to work in batches, clearing context in the meantime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Saving State
&lt;/h3&gt;

&lt;p&gt;At the end of each prompt, I asked to dump the “enhanced" prompt in the &lt;code&gt;.md&lt;/code&gt; file, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The original prompt&lt;/li&gt;
&lt;li&gt;Its refined version&lt;/li&gt;
&lt;li&gt;A list of files already fixed&lt;/li&gt;
&lt;li&gt;Any performance optimisations it used&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I committed these files to git to make it easy to pause, resume later, or hand off to another AI. The results were surprisingly good.&lt;/p&gt;

&lt;h3&gt;
  
  
  Picking up by another agent
&lt;/h3&gt;

&lt;p&gt;To avoid hitting usage limits, I switched between Claude Code, Atlassian's Rovo, and JetBrains Juni. Each could pick up exactly where the other left off using the saved prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live Editing vs Codegen
&lt;/h3&gt;

&lt;p&gt;While researching this topic, I noticed an interesting approach, heavily used by Juni. It created codemods instead of fixing each file directly in the following flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write a transformation script&lt;/li&gt;
&lt;li&gt;Test it on one file&lt;/li&gt;
&lt;li&gt;Iterate&lt;/li&gt;
&lt;li&gt;Apply it to the whole codebase&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In future upgrades, I might skip manual batching and go straight to having AI generate a codemod.&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>refactoring</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Branded Types in TypeScript: techniques</title>
      <dc:creator>Krzysztof Żuraw</dc:creator>
      <pubDate>Wed, 11 Jun 2025 05:58:04 +0000</pubDate>
      <link>https://dev.to/saleor/branded-types-in-typescript-techniques-340f</link>
      <guid>https://dev.to/saleor/branded-types-in-typescript-techniques-340f</guid>
      <description>&lt;p&gt;In this post, I’ll walk you through several ways to implement branded types in TypeScript. If you’re new to the concept, check out &lt;a href="https://egghead.io/blog/using-branded-types-in-typescript" rel="noopener noreferrer"&gt;this intro to branded types&lt;/a&gt; before diving in.&lt;/p&gt;

&lt;p&gt;Branded types let you create more specific types from primitives like &lt;code&gt;string&lt;/code&gt; or &lt;code&gt;number&lt;/code&gt;. This helps TypeScript catch bugs where you might accidentally mix up values that are technically the same primitive type but represent different concepts (e.g., &lt;code&gt;UserId&lt;/code&gt; vs. &lt;code&gt;string&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1. Using a Type Wrapper&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;__brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUserId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Add validation logic here if needed&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createUserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_id_42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// ✅ Works&lt;/span&gt;
&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_id_42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// ❌ Type error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is lightweight and doesn’t require any extra dependencies. You define a branded type and enforce its usage via a factory function (&lt;code&gt;createUserId&lt;/code&gt;). This ensures only properly branded values are passed around your codebase.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Zero dependencies&lt;/li&gt;
&lt;li&gt;Simple to implement&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;No runtime validation unless you add it yourself&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;__brand&lt;/code&gt; property is only a TypeScript construct—it doesn’t exist at runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. Using JavaScript Classes / Objects&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_id_42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// ✅ Works&lt;/span&gt;
&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_id_42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// ❌ Type error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to encapsulate logic (like validation, formatting, or transformation), classes are a good fit. For simple cases, though, this approach can be a bit verbose.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Can add methods, validation, and encapsulate logic&lt;/li&gt;
&lt;li&gt;Runtime type information&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;More boilerplate&lt;/li&gt;
&lt;li&gt;Not always necessary for simple cases&lt;/li&gt;
&lt;li&gt;Hard to compare classes - instead of comparing &lt;code&gt;UserId&lt;/code&gt; you need to compare its internal &lt;code&gt;id&lt;/code&gt; property&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. Using Zod’s &lt;code&gt;brand()&lt;/code&gt; Method&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userIdSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UserId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUserId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;userIdSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;userIdSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createUserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_id_42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// ✅ Works&lt;/span&gt;
&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_id_42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// ❌ Type error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’re already using &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, its &lt;a href="https://zod.dev/api#branded-types" rel="noopener noreferrer"&gt;&lt;strong&gt;`.&lt;/strong&gt;brand()`&lt;/a&gt; method gives you branded types with built-in validation. This is especially handy for APIs or form inputs.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Built-in validation&lt;/li&gt;
&lt;li&gt;Clean integration with Zod schemas&lt;/li&gt;
&lt;li&gt;Type-safe and runtime-safe&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Requires Zod as a dependency&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type wrapper&lt;/strong&gt;: Great for lightweight use cases, no dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class-based&lt;/strong&gt;: Useful when you need encapsulation or extra logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zod brand&lt;/strong&gt;: Perfect if you already use Zod and want validation + branding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Branded types are a powerful way to make your TypeScript code safer and more expressive. Pick the approach that fits your project and team best! We in Saleor use both &lt;a href="https://github.com/search?q=repo%3Asaleor%2Fapps+.brand+path%3A%2F%5Eapps%5C%2Fstripe%5C%2Fsrc%5C%2F%2F&amp;amp;type=code" rel="noopener noreferrer"&gt;Zod brand&lt;/a&gt; and &lt;a href="https://github.com/search?q=repo%3Asaleor%2Fapps+class+path%3A%2F%5Eapps%5C%2Fstripe%5C%2Fsrc%5C%2F%2F&amp;amp;type=code" rel="noopener noreferrer"&gt;classes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>zod</category>
      <category>programming</category>
    </item>
    <item>
      <title>TypeScript type inference - the dark side</title>
      <dc:creator>Lukasz Ostrowski</dc:creator>
      <pubDate>Thu, 05 Jun 2025 06:47:17 +0000</pubDate>
      <link>https://dev.to/saleor/typescript-type-inference-the-dark-side-12i1</link>
      <guid>https://dev.to/saleor/typescript-type-inference-the-dark-side-12i1</guid>
      <description>&lt;p&gt;Type inference in TypeScript allows it to figure out what is the type, without explicitly declaring it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mutableVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;// Type is number&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;immutableVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;// Type is 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can try it &lt;a href="https://www.typescriptlang.org/play/?#code/DYUwLgBAtgrmCGAjUA1EAnAzgSwPYDsIBeCARgCgBjAzSbKWBZENLPQk0oA" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TypeScript is not only smart enough to figure out that &lt;code&gt;1&lt;/code&gt; is a number (duh) but also to understand that &lt;code&gt;const&lt;/code&gt; declaration for a primitive can't be reassigned (it's a value, not a reference) - hence it will stay &lt;code&gt;1&lt;/code&gt; forever, contrary to &lt;code&gt;let&lt;/code&gt; which can change.&lt;/p&gt;

&lt;p&gt;TypeScript tries to do it's best and usually it works quite well. Together with &lt;code&gt;satisfies&lt;/code&gt; and &lt;code&gt;as const&lt;/code&gt; statements we can write type-safe code barely declaring anything.&lt;/p&gt;

&lt;p&gt;However, there are code-architecture downsides to relying too heavily on type inference, which can eventually make maintenance difficult.&lt;/p&gt;

&lt;h1&gt;
  
  
  Code first or design first
&lt;/h1&gt;

&lt;p&gt;From my experience, most developers tend to write code and figure out the design as the outcome. "Something" eventually works, a few tests are added on top and we are done.&lt;/p&gt;

&lt;p&gt;This approach may be more satisfying but is more "artistic" than an "engineering" approach. The implementation of the logic itself is not too important, if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It works, which tests proof&lt;/li&gt;
&lt;li&gt;Is performant enough to match our metrics&lt;/li&gt;
&lt;li&gt;Is encapsulated, so it doesn't leak where it doesn't belong to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If all these 3 are met, we can always easily replace the implementation without affecting the rest of the program.&lt;/p&gt;

&lt;p&gt;In the context of the design, encapsulation is critical. It draws the boundary of the abstractions (function, classes, modules) and allows us to design how they coexist and communicate.&lt;/p&gt;

&lt;p&gt;At this point, you may be thinking - what does it have in common with inference?&lt;/p&gt;

&lt;h1&gt;
  
  
  Interface vs inference
&lt;/h1&gt;

&lt;p&gt;By allowing language to infer the type, we accept it to follow a "code first" approach.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapCollection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What does it return? Apart from the cognitive load (you force someone to read and understand implementation to understand what is returned), you just let TypeScript figure it out. And this is a simple function, for sure you have seen multiple-layered &lt;em&gt;map/filter/reduce&lt;/em&gt; monster, best if placed in some React component, to make testing even harder 🥲&lt;/p&gt;

&lt;p&gt;You can also be a good colleague and do this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CollectionItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;WrapCollection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CollectionItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapCollection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WrapCollection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What has changed? You started with declaring the data shape and data flow (in and out), &lt;em&gt;then&lt;/em&gt; started to implement. Even empty, not implemented functions will be ready to import and write tests at an early stage, where you can validate the API.&lt;/p&gt;

&lt;p&gt;Relying on inference is like writing only half of the interface.&lt;/p&gt;

&lt;h1&gt;
  
  
  Impact on the maintenance
&lt;/h1&gt;

&lt;p&gt;Using inference not only makes it difficult to design a good code but also makes it harder to maintain.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;wrapCollection&lt;/code&gt; in a few months can be used by many engineers in many places. They will rely on inferred type... Then you need to refactor. &lt;/p&gt;

&lt;p&gt;Say, you want to change it to &lt;code&gt;for&lt;/code&gt; instead of &lt;code&gt;reduce&lt;/code&gt; because you operate on a large amount of data and need to &lt;a href="https://leanylabs.com/blog/js-forEach-map-reduce-vs-for-for_of/" rel="noopener noreferrer"&gt;improve performance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You change the inner implementation, &lt;em&gt;but there is no Typescript boundary preventing you from changing the outer shape&lt;/em&gt;. Yes, the rest of the code hopefully will be "red" and your tests will fail. But there are many codebases, including ones with not full TypeScript coverage and missing tests.&lt;/p&gt;

&lt;p&gt;But every time, your function defines its outer shape (doesn't have to be an interface, can be just a static declaration of the returned type), you are locally protected from breaking that contract.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;I'm not declaring types literally everywhere, but I believe strong type coverage makes code more maintainable. &lt;/p&gt;

&lt;p&gt;The more complex the function is, the higher the ROI is. Simple, especially private functions are not that important if we know that only one caller exists. But public methods, widely used across the codebase, benefit from being typed.&lt;/p&gt;

&lt;p&gt;You can use &lt;a href="https://typescript-eslint.io/rules/explicit-function-return-type/" rel="noopener noreferrer"&gt;ESLint rule&lt;/a&gt; to require explicit function return type as well.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Error modelling</title>
      <dc:creator>Lukasz Ostrowski</dc:creator>
      <pubDate>Thu, 29 May 2025 06:52:24 +0000</pubDate>
      <link>https://dev.to/saleor/error-modelling-4471</link>
      <guid>https://dev.to/saleor/error-modelling-4471</guid>
      <description>&lt;h1&gt;
  
  
  Intro
&lt;/h1&gt;

&lt;p&gt;Let’s have a conceptual look at error modeling. I will use Node.js ecosystem and TypeScript in these examples (and some pseudo-code). I find built-in error management in the JS ecosystem rather poor compared to some other languages, which makes it even more important to treat this topic seriously in this tech stack&lt;/p&gt;

&lt;p&gt;I focus on the error modeling, but I don’t focus on the data flow. In another article, I will write more about managing errors Rust-way (which has the built-in distinction between recoverable and non-recoverable errors)&lt;/p&gt;

&lt;h1&gt;
  
  
  Role of errors
&lt;/h1&gt;

&lt;p&gt;Let’s think about all of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SyntaxError: JSON.parse: unexpected character&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TypeError: Cannot read property 'value' of null&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ValidationError: Email already exists&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Internal Server Error&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of them is an error with a different origin and reason.&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;JSON.parse&lt;/code&gt; will happen when we try to parse a string that is not valid JSON. It can occur when we directly catch the external API response body and without checking the response type, we parse e.g. error page which is HTML&lt;/p&gt;

&lt;p&gt;Another &lt;code&gt;TypeError&lt;/code&gt; can be a pure static code issue - we can try to access a property of nullish value, for example, accessing an object property before it has been created.&lt;/p&gt;

&lt;p&gt;Common things for these two is that they are mainly useful for the developer. Best if they are caught during compilation or static analysis if possible, then we can protect ourselves by writing proper tests. Once we reach the runtime, we must ensure we will be able to recognize them when the application crashes - in logs or error-tracking platforms like Sentry. These errors are also often non-recoverable - the app probably can’t find another way to work if the code can’t execute anymore.&lt;/p&gt;

&lt;p&gt;Once we reach &lt;code&gt;ValidationError&lt;/code&gt; we change the abstraction level. First of all, it’s not language that will throw such errors, but either our database or our internal data layer that is trying e.g. to insert a user into the database. Validation is a graceful way to recover from the issue, giving clear feedback without crashing the app. Such an error is also different from the previous two: it’s rather not interesting to track it in the error tracker (it’s not something we can fix) and this error should be returned in the response, so the user/frontend can handle it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Internal Server Error&lt;/code&gt; on the other hand is an error on the HTTP layer. It’s represented by the semantic code (500) but also provides information that it’s a crash on the server side. Something we definitely should catch and fix. We definitely need as many details as possible in our internal tracking systems, but also do not expose any detail to the front end to avoid leaking any implementation details.&lt;/p&gt;

&lt;p&gt;You can see now, that errors are not equal to errors - depending on the context they differ. And for that reason, it requires us to model errors carefully.&lt;/p&gt;

&lt;h1&gt;
  
  
  Abstraction is the problem
&lt;/h1&gt;

&lt;p&gt;Conceptually errors can propagate through the stack trace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nc"&gt;FunctionA&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nc"&gt;FunctionB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;FunctionC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stack trace follows the function execution. Then, the opposite when we are catching it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;FunctionA&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;FunctionB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nc"&gt;FunctionC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner error will be traveling through the execution until some &lt;code&gt;catch&lt;/code&gt; block intercepts it (or the program finishes with the unhandled exception).&lt;/p&gt;

&lt;p&gt;Now when we think about it in scale - a program running hundreds of functions to process the request, we travel through the abstraction layers. &lt;/p&gt;

&lt;p&gt;Errors that arise in the controllers can be often caused by validation logic, partially represented business rules (minimal password length), partially expected data format (JSON), etc. &lt;/p&gt;

&lt;p&gt;Errors that are caught in the model are likely related strictly to the domain, for example, we can’t add to a cart product that doesn’t exist anymore.&lt;/p&gt;

&lt;p&gt;Sometimes, errors happen in external services. Can be our database downtime or external API not responding.&lt;/p&gt;

&lt;p&gt;And in every other place, we can face dozens of programming errors, caused by wrong implementation on the language level.&lt;/p&gt;

&lt;p&gt;Depending on the abstraction we will need a different handling &lt;/p&gt;

&lt;h1&gt;
  
  
  Errors chaining
&lt;/h1&gt;

&lt;p&gt;In Python, there is a concept of &lt;code&gt;raise ErrorA from ErrorB&lt;/code&gt;. Its purpose is to indicate one error is caused by another, especially useful when we transform errors.&lt;/p&gt;

&lt;p&gt;In the JS stack, we can use &lt;code&gt;Error.prototype.cause&lt;/code&gt; for that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// not real API&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stripeError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stripeError&lt;/span&gt; &lt;span class="nx"&gt;instanceOf&lt;/span&gt; &lt;span class="nx"&gt;Stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InvalidCardDetails&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PaymentFailedError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Payment failed to due invalid payment details&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stripeError&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a powerful pattern.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, we have locally intercepted errors from the 3rd party API. It gives us control, at this point, we can log, track, set metrics, emit events, or anything else.&lt;/li&gt;
&lt;li&gt;Second, we can match the error type. External APIs will provide us a unified errors layer (likely HTTP serialized errors if a list of enum codes) - some of them can be recoverable, some not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An error like “invalid card details” is expected and recoverable - the user must try again.&lt;/p&gt;

&lt;p&gt;An error like “invalid Stripe secret key” is not an action for the customer, but definitely should reach the payment operator to fix it, otherwise, the business critical path may be down. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Third, we still chain the reasons, allowing us to track not only the stack tree (representing function calls) but also the human-readable messages we wrote in our code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we move level up from the payment example above, we will be able to see simplified reasoning of how powerful error matching is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// controller / app service / use case&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;paymentProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="nx"&gt;instanceOf&lt;/span&gt; &lt;span class="nx"&gt;PaymentFailedError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;tracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trackEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid_payment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;// In real life respond with a json-like structure with a payment refusal reason&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid payment details&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="nx"&gt;instanceOf&lt;/span&gt; &lt;span class="nx"&gt;GatewayAuthError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;captureException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Payment Gateway auth rejected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FATAL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error processing payment, please try a different payment method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In real life, it will be much broader, due to all handled error cases - but conceptually, we can achieve the same thing: a strong and clear distinction between what class of error we are dealing with, who should receive it, what data we provide and how do we monitor these.&lt;/p&gt;

&lt;p&gt;Passing cause gives us additional context we can log (e.g. into Sentry), but we don’t have to return it to the storefront. Or maybe we want to, but only for test environments (we do that in our Stripe App in Saleor)&lt;/p&gt;

&lt;p&gt;Error matching (that can be implemented with extending Errors or by enum-like reasons) allows us to route error handling and react properly depending on the issue.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Try to think about what types of errors your application can produce&lt;/li&gt;
&lt;li&gt;Model what data you need to attach to your errors (both internal and external systems)&lt;/li&gt;
&lt;li&gt;Leverage the &lt;code&gt;Error.prototype.cause&lt;/code&gt; field to chain errors&lt;/li&gt;
&lt;li&gt;Transform errors when they travel through the abstraction layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the upcoming article, I will write more about error implementation and error flow in the application.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Dynamic Configuration for MCP Servers Using Environment Variables</title>
      <dc:creator>Krzysztof Żuraw</dc:creator>
      <pubDate>Thu, 29 May 2025 06:24:56 +0000</pubDate>
      <link>https://dev.to/saleor/dynamic-configuration-for-mcp-servers-using-environment-variables-2a0o</link>
      <guid>https://dev.to/saleor/dynamic-configuration-for-mcp-servers-using-environment-variables-2a0o</guid>
      <description>&lt;p&gt;This blog post explains how to configure Model Context Protocol (&lt;a href="https://modelcontextprotocol.io/introduction" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;) server that can be dynamically controlled through environment variables.&lt;/p&gt;

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

&lt;p&gt;When setting up &lt;a href="https://github.com/blurrah/mcp-graphql" rel="noopener noreferrer"&gt;mcp-graphql&lt;/a&gt;, we needed a way for the MCP server to use a dynamic GraphQL endpoint and token. Hardcoding these values was not an option, as different Saleor environments require different configurations.&lt;/p&gt;

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

&lt;p&gt;Here’s what we did:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Created a shell script to manage the mcp-graphql startup.&lt;/li&gt;
&lt;li&gt;Loaded the endpoint and token from a &lt;strong&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt; file within the script.&lt;/li&gt;
&lt;li&gt;Launched the MCP server using these environment variables.&lt;/li&gt;
&lt;li&gt;Used this shell script as the entry point for MCP configuration in clients like Cursor or VSCode&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Below is the shell script (&lt;code&gt;start-mcp-graphql.sh&lt;/code&gt;) that performs steps 1–3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="c"&gt;# Load environment variables from .env file next to this script&lt;/span&gt;
&lt;span class="nv"&gt;SCRIPT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASH_SOURCE&lt;/span&gt;&lt;span class="p"&gt;[0]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SCRIPT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/.env"&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; +a

&lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MCP_GRAPHQL_ENDPOINT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;HEADERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Bearer &lt;/span&gt;&lt;span class="nv"&gt;$MCP_GRAPHQL_TOKEN&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; npx mcp-graphql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Using the Script in MCP Clients&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To use this setup with Cursor, add the following to your configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Saleor GraphQL API"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./mcp/start-mcp-graphql.sh"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For VSCode, configure the server as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Saleor GraphQL API"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stdio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/mcp/start-mcp-graphql.sh"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;With this approach, you can easily configure your MCP server dynamically using environment variables. You’ll find all the source code, scripts, and editor configs ready to explore in the &lt;a href="https://github.com/saleor/apps/tree/main" rel="noopener noreferrer"&gt;Saleor apps repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>graphql</category>
      <category>ai</category>
      <category>cursor</category>
    </item>
    <item>
      <title>UUID migration in Django with PostgreSQL</title>
      <dc:creator>Iga Karbowiak</dc:creator>
      <pubDate>Tue, 18 Feb 2025 11:38:54 +0000</pubDate>
      <link>https://dev.to/saleor/uuid-migration-in-django-with-postgresql-4p3m</link>
      <guid>https://dev.to/saleor/uuid-migration-in-django-with-postgresql-4p3m</guid>
      <description>&lt;h2&gt;
  
  
  What is UUID?
&lt;/h2&gt;

&lt;p&gt;UUID (&lt;strong&gt;universally unique identifier&lt;/strong&gt;) is a 128-bit label, generated with the use of the standardized algorithm, almost impossible to duplicate, which makes it a perfect identifier. It’s represented as a string that contains five groups of 32 hexadecimal digits separated by hyphens. Here is an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;f6a7f195-8220-408c-9906-7395b870db61
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why it’s so useful?
&lt;/h2&gt;

&lt;p&gt;The question arises as to why &lt;code&gt;UUID&lt;/code&gt; is better than the typical sequential integer Primary Key (PK) generated by the database, which seems to be handled efficiently.&lt;br&gt;
We have been using integer IDs for many years, as this is what databases and Django does by default. We decided to move to UUID for a few reasons.&lt;/p&gt;

&lt;p&gt;Firstly, &lt;code&gt;UUIDs&lt;/code&gt; are unpredictable, unlike standard integer IDs. This lack of predictability enhances security.&lt;/p&gt;

&lt;p&gt;Secondly, consider a system usable both online and offline, where users can create new content. When the user reconnects to the internet, the new instances are merged into the database. With conventional auto-incrementing primary keys, there's a significant chance of conflicts. However, by employing UUIDs as primary keys, the likelihood of encountering such conflicts is nearly eliminated, providing a robust solution to potential problems.&lt;/p&gt;

&lt;p&gt;Aside from that UUIDs are less prone to human error when writing queries and working with code, it's hard to spot that a wrong field has been used in a JOIN statement and using integer IDs is less likely to cause an error in cases like that. With UUIDs it's less likely.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to migrate my data from ID to UUID while ensuring both the wolves are satisfied and the sheep remain unharmed
&lt;/h2&gt;

&lt;p&gt;So, what should you do if you decide that UUIDs are necessary and want to migrate your database objects to use UUIDs as primary keys? If you’re in the early stages, without a production environment, the transition is relatively straightforward. However, if your system is already in production, hosting millions of objects with complex relationships, you’ll need to carefully consider this decision. The process can be challenging and require significant effort. But don’t worry, I’ll guide you through it to make it as smooth as possible. 🙂&lt;/p&gt;
&lt;h2&gt;
  
  
  Happy scenario
&lt;/h2&gt;

&lt;p&gt;To illustrate the problem, imagine we have the following &lt;code&gt;Order&lt;/code&gt; model, which represents an order placed by a customer in an online store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DecimalField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;max_digits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;decimal_places&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most straightforward solution, that probably came to your mind, is to add a new &lt;code&gt;UUID&lt;/code&gt; field that will be the new primary key (PK).  Let’s name it &lt;code&gt;token&lt;/code&gt;. Populate it with unique values and make this field the new PK. Let's illustrate this process using the &lt;code&gt;Order&lt;/code&gt; model as an example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; All examples in this article are for illustration purposes; production systems will need solutions that scale with locking and batch updates.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Migration for adding new &lt;code&gt;token&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0135_alter_order_options&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Custom migration that populates the &lt;code&gt;token&lt;/code&gt; field of existing instances with unique &lt;code&gt;UUID&lt;/code&gt; values.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.postgres.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RandomUUID&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_order_token_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_schema_editor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token__isnull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;RandomUUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0136_order_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RunPython&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;set_order_token_values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunPython&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;noop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Changing token into the primary key&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0137_fulfil_order_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AlterField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;editable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;serialize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And... this will work, but EXCLUSIVELY when your model contains ONLY &lt;code&gt;one to many&lt;/code&gt; relations.&lt;/p&gt;

&lt;p&gt;Now, consider that the &lt;code&gt;Order&lt;/code&gt; model has a many-to-one relationship with the &lt;code&gt;Invoice&lt;/code&gt; model, where each &lt;code&gt;Invoice&lt;/code&gt; represents a generated invoice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SET_NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the migration would fail with the following error:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;django.db.utils.InternalError: cannot drop constraint order_ordereditem_pkey on table order_order because other objects depend on it&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Why is that?&lt;/p&gt;

&lt;p&gt;The reason is that your related models will still have primary key columns pointing to the obsolete &lt;code&gt;pk&lt;/code&gt; values, which will no longer exist. If things seem a bit foggy, no worries! Check out the next section that explains in detail how relationships are established in the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the relations are defined in your database?
&lt;/h2&gt;

&lt;p&gt;Before we go on let me explain how the relations that you define in ORM are implemented in the database. If it’s clear to you, you can skip this section and go directly to &lt;code&gt;So what is the problem?&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The relational database consists of tables with columns and constraints that specify the rules for the data in tables. All limitations are ensured by the constraints, like uniqueness, value requirement, primary keys, and relations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Many-to-one&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the &lt;strong&gt;many-to-one&lt;/strong&gt; relation, we have two models where the entity of &lt;code&gt;model A&lt;/code&gt; can have multiple associations with entities of &lt;code&gt;model B&lt;/code&gt;, but the entity of &lt;code&gt;model B&lt;/code&gt; can have only one association with the entity of &lt;code&gt;model A&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the Django framework, it’s defined with the use of &lt;code&gt;ForeignKey&lt;/code&gt; in &lt;code&gt;model B&lt;/code&gt;, which points the &lt;code&gt;model A&lt;/code&gt;. Under the hood, the new column is added to the &lt;code&gt;model B&lt;/code&gt;, and the foreign key constraint is created. The column name is the field name with &lt;code&gt;_id&lt;/code&gt; ending and will keep the &lt;code&gt;id&lt;/code&gt;s of associated entities. The foreign key constraint is just the rule of the database table (&lt;code&gt;model A&lt;/code&gt;) that says the values in a given column must match the values (in our case the &lt;code&gt;id&lt;/code&gt;) of a column in some other table (&lt;code&gt;model A&lt;/code&gt;). The referenced column (in our case &lt;code&gt;is&lt;/code&gt; on &lt;code&gt;mode a&lt;/code&gt;)  must contain only unique values. So the foreign key constraint depends on the unique or primary key constraint from the relational table.&lt;/p&gt;

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

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

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One-to-one&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In &lt;strong&gt;one-to-one&lt;/strong&gt; relationship, the entity of &lt;code&gt;model A&lt;/code&gt; can have only one association with entities of &lt;code&gt;model B&lt;/code&gt;, and the same rule applies to &lt;code&gt;model A&lt;/code&gt; in relation to &lt;code&gt;model B&lt;/code&gt;. In the Django framework, it’s defined with the use of the &lt;code&gt;OneToOne&lt;/code&gt; field on any of those models. Under the hood, the same things happened as in a many-to-one relationship, but also the unique constraint for the newly created column is added, to ensure that only one entity of &lt;code&gt;model A&lt;/code&gt; will exist with the given field value of the pointed column in &lt;code&gt;table B&lt;/code&gt;.&lt;/p&gt;

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

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

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Many-to-many&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In &lt;strong&gt;many-to-many&lt;/strong&gt; relation entities of &lt;code&gt;model A&lt;/code&gt; can have multiple associations with entities of &lt;code&gt;model B&lt;/code&gt;, and &lt;code&gt;model B&lt;/code&gt; can have multiple associations with entities of &lt;code&gt;model A&lt;/code&gt;. In Django framework, it’s defined with the use of the &lt;code&gt;ManyToMany&lt;/code&gt; field on any model. Under the hood, this case is a little bit more complex than the previous ones. As we can have multiple connections from both sides of the relationship, we cannot just create a new column on one of the tables. Instead, a new table is created that keeps the ids from both sides of relations. The new table contains columns, one for each site of relations, that keep the corresponding entity field value. To ensure references, for each field the foreign key constraint is created. Additionally, a unique constraint is added to ensure that there is only one row responsible for the relationship between every two instances.&lt;/p&gt;

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

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

&lt;h2&gt;
  
  
  So what is the problem?
&lt;/h2&gt;

&lt;p&gt;As we learned in the previous section, relationships based on foreign key constraints utilize a primary key or unique constraint. Modifying the model's &lt;code&gt;pk&lt;/code&gt; to &lt;code&gt;UUID&lt;/code&gt; involves recreating the primary key, which deletes the old constraint and creates a new one. If you try to change the primary key, you might encounter errors such as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;django.db.utils.InternalError: cannot drop constraint order_ordereditem_pkey on table order_order because other objects depend on it&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This error implies that the primary key constraint of the &lt;code&gt;id&lt;/code&gt; field, which defines &lt;code&gt;foreign key&lt;/code&gt; relations, cannot be dropped.&lt;/p&gt;

&lt;p&gt;That's why we had to update the relations before modifying the &lt;code&gt;pk&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution in a nutshell
&lt;/h2&gt;

&lt;p&gt;To solve the issue, we will introduce a new field called &lt;code&gt;old_id&lt;/code&gt; to keep a copy of the old &lt;code&gt;Order&lt;/code&gt; &lt;code&gt;id&lt;/code&gt;. Then, we will redirect our existing relationships to this new field, and create another field on related models to store the new &lt;code&gt;UUID&lt;/code&gt; values (that will become the &lt;code&gt;pk&lt;/code&gt; values) of related instances. After changing the &lt;code&gt;ID&lt;/code&gt; to &lt;code&gt;UUID&lt;/code&gt; in our target model, on the related models, we'll transform the fields that store related instances' &lt;code&gt;UUID&lt;/code&gt; values into relational fields, and remove the old relational fields pointing to &lt;code&gt;old_id&lt;/code&gt;s. This way, we’ll maintain the same relationships, but with pointing to new &lt;code&gt;UUID&lt;/code&gt; values. Sounds complicated? Let’s explore the whole process in the example to make it clear.&lt;/p&gt;

&lt;p&gt;💡 Solution Shortcut&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the target model (e.g. &lt;code&gt;Order&lt;/code&gt;) create the new fields, one the UUID field (e.g. &lt;code&gt;token&lt;/code&gt; field) that will become a PK, and one for keeping the old ID (e.g. &lt;code&gt;old_id&lt;/code&gt; field)&lt;/li&gt;
&lt;li&gt;Populate those fields&lt;/li&gt;
&lt;li&gt;Redirect each existing relation to the field with the old id (&lt;code&gt;old_id&lt;/code&gt; field)&lt;/li&gt;
&lt;li&gt;On each relation model create a new field that will keep the new UUID values of the related instances (e.g. &lt;code&gt;order_token&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Change the &lt;code&gt;UUID&lt;/code&gt; field into the primary key.&lt;/li&gt;
&lt;li&gt;On each relation model, change the field that keeps new UUID values (&lt;code&gt;order_token&lt;/code&gt;) into the relation field (e.g.&lt;code&gt;ForeginKey&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;On each relation model, remove the old relation field&lt;/li&gt;
&lt;li&gt;On each relation model, rename the new relation field to the initial name&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Before delving into managing relationships, let's add an &lt;code&gt;old_id&lt;/code&gt; field to store a copy of the previous integer primary key. We also need a &lt;code&gt;token&lt;/code&gt; field, which will later serve as our new primary key. Let’s update the changes introduced in our happy scenario.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The first step is adding a new nullable &lt;code&gt;token&lt;/code&gt; and &lt;code&gt;old_id&lt;/code&gt; fields. &lt;/p&gt;

&lt;p&gt;Here are the changes in the &lt;code&gt;models.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;old_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;And corresponding data migration:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0135_alter_order_options&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;old_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, besides populating the &lt;code&gt;token&lt;/code&gt; field of existing instances with unique &lt;code&gt;UUID&lt;/code&gt; values, we'll also copy the current &lt;code&gt;id&lt;/code&gt; to the &lt;code&gt;old_id&lt;/code&gt; field using a custom migration.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.postgres.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RandomUUID&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_order_token_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_schema_editor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token__isnull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;RandomUUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_order_old_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema_editor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0136_order_token_and_old_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RunPython&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;set_order_token_values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reverse_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunPython&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;noop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RunPython&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;set_order_old_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reverse_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunPython&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;noop&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Handling database relations
&lt;/h2&gt;

&lt;p&gt;Now, we can move to the question: what is the easiest and safest way to update the model relations? &lt;/p&gt;

&lt;p&gt;We need to handle all relations:  &lt;code&gt;many to one&lt;/code&gt;, &lt;code&gt;many to many&lt;/code&gt; and &lt;code&gt;one to one&lt;/code&gt;. In all cases, we can split the changes into two parts: before model &lt;code&gt;id&lt;/code&gt; migration to &lt;code&gt;UUID&lt;/code&gt;, and after.&lt;/p&gt;

&lt;p&gt;Now, let's examine each step for each relation type in detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Few tips at the beginning:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Make sure that at each stage of the migration, you hold the identifier of the related objects that will allow to recreate the relationships.&lt;/li&gt;
&lt;li&gt;Always create a full backup and test the code before running the migrations. If something goes wrong, it might be challenging or even impossible to undo.&lt;/li&gt;
&lt;li&gt;Be patient. If the model you're migrating has numerous relations, preparing everything might take some time, but don’t give up it’s doable!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Our goal in this data migration is to preserve the references between objects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before &lt;code&gt;UUID&lt;/code&gt; migration
&lt;/h3&gt;

&lt;p&gt;This phase comprises two steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Redirect each existing relation to the &lt;code&gt;old_id&lt;/code&gt; field.&lt;/li&gt;
&lt;li&gt;For each relation model, create and populate a new field (&lt;code&gt;order_token&lt;/code&gt;) that will store the new &lt;code&gt;UUID&lt;/code&gt; values of the related &lt;code&gt;order&lt;/code&gt; instances.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s see how to apply those steps to both &lt;code&gt;many-to-one&lt;/code&gt;, &lt;code&gt;one-to-one&lt;/code&gt;, and &lt;code&gt;many-to-many&lt;/code&gt; relations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Many-to-one and one-to-one relationship&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;many-to-one&lt;/code&gt; and &lt;code&gt;one-to-one&lt;/code&gt; relations are similar. The only difference is that the &lt;code&gt;one-to-one&lt;/code&gt; relation has an additional unique constraint. Therefore, the method for handling these relations is the same.&lt;br&gt;
In those cases, we need to prepare migrations on the model where the relation is defined. Let’s see the process on an example of &lt;code&gt;Invoice&lt;/code&gt; that has &lt;code&gt;Many-to-one&lt;/code&gt; relation with &lt;code&gt;Order&lt;/code&gt; model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The first step is to define the &lt;code&gt;UUID&lt;/code&gt; field that will hold the new &lt;code&gt;UUID&lt;/code&gt; values of related objects. In the example below, we have the &lt;code&gt;Invoice&lt;/code&gt; model that is related to &lt;code&gt;Order&lt;/code&gt;. We're adding a new field, &lt;code&gt;order_token&lt;/code&gt;, which will be filled with the new &lt;code&gt;token&lt;/code&gt; value of the corresponding &lt;code&gt;Order&lt;/code&gt; instances.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SET_NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;order_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The migration as follows:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0006_invoiceevent_app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;The next step is to populate this new field with corresponding &lt;code&gt;token&lt;/code&gt; values. We could write a Python function for this, but it could be challenging to write an efficient one for a large dataset. The quickest solution is to write a simple SQL operation:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0007_invoice_order_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0137_fulfil_order_token_and_old_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
            UPDATE invoice_invoice
            SET order_token = (
                SELECT token
                FROM order_order
                WHERE invoice_invoice.order_id = order_order.id
            )
            WHERE order_id IS NOT NULL;
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reverse_sql&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;noop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;So, what's going on here? We're updating the &lt;code&gt;order_token&lt;/code&gt; column in the &lt;code&gt;invoice_invoice&lt;/code&gt; table, which corresponds to the &lt;code&gt;Invoice&lt;/code&gt; model. For all instances where the &lt;code&gt;Order&lt;/code&gt; relation exists, we populate the new &lt;code&gt;order_token&lt;/code&gt; field with the &lt;code&gt;token&lt;/code&gt; value from the corresponding &lt;code&gt;Order&lt;/code&gt; instance. This &lt;code&gt;token&lt;/code&gt; value is sourced from the &lt;code&gt;order_order&lt;/code&gt; table, where the &lt;code&gt;id&lt;/code&gt; matches the &lt;code&gt;order_id&lt;/code&gt; from &lt;code&gt;invoice_invoice&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;Also, note that the dependencies list is extended with the order migration. We need to ensure that this migration is applied after the migration that populates the new &lt;code&gt;token&lt;/code&gt; field in the order model.&lt;/p&gt;

&lt;p&gt;💡 To write an SQL operation, it's essential to know the table and column names. In Django, the table name is formed by joining the app name and model name with an underscore. If you are unsure about your table name, you can always check it in your database shell.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;The final step is to change the current relation field to point to the &lt;code&gt;old_id&lt;/code&gt; field. This action will drop the existing foreign key constraint that relies on the primary key constraint, and create a new one for the &lt;code&gt;old_id&lt;/code&gt; field. You can see this change in the &lt;a href="http://models.py" rel="noopener noreferrer"&gt;&lt;code&gt;models.py&lt;/code&gt;&lt;/a&gt;  file:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SET_NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;to_field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;old_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;order_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;And corresponding migration:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;django.db.models.deletion&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0008_fulfill_invoice_order_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AlterField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SET_NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order.order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;to_field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;old_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Be sure to prepare these migrations for all models that depend on a changing model.&lt;/p&gt;&lt;/li&gt;

&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Many-to-many relations&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;code&gt;many_to_many&lt;/code&gt; relations, we need to perform similar steps but operate on the cross-reference table created for this relation. This involves writing an SQL operation to execute the changes.&lt;/p&gt;

&lt;p&gt;We’ll analyze the steps in the example of the relation between &lt;code&gt;GiftCard&lt;/code&gt; and &lt;code&gt;Order&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;gift_cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ManyToManyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;GiftCard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Similar to the previous steps, firstly we will add a new column to store the new &lt;code&gt;token&lt;/code&gt; values.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;order_token&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, we aim to fill the newly added column with corresponding values. In this case, we don't need to check if the column is empty since all instances of cross-reference table must have a value.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;order_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;order_order&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order_order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now, we can set the condition that the new column &lt;code&gt;order_token&lt;/code&gt; must not be null:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;order_token&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The next step is to remove the old foreign constraint for the column that points to the current &lt;code&gt;id&lt;/code&gt; field. You need to find the name of this constraint. As before, you can check it in the &lt;code&gt;psql&lt;/code&gt; command line or the administration platform - pgAdmin.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards_order_id_ce5608c4_fk_order_order_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The final step involves creating a new foreign key constraint for our column that stores token values.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards_order_id_fk_order_order_old_id&lt;/span&gt;
&lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;order_order&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, let's consolidate everything into the migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"0137_fulfil_order_token_and_old_id"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;"&lt;/span&gt;&lt;span class="se"&gt;""&lt;/span&gt;&lt;span class="nv"&gt;
            ALTER TABLE order_order_gift_cards
            ADD COLUMN order_token uuid;

            UPDATE order_order_gift_cards
            SET order_token = (
                SELECT token
                FROM order_order
                WHERE order_order_gift_cards.order_id = order_order.id
            );

            ALTER TABLE order_order_gift_cards
            ALTER COLUMN order_token SET NOT NULL;

            ALTER TABLE order_order_gift_cards
            DROP CONSTRAINT
                    order_order_gift_cards_order_id_ce5608c4_fk_order_order_id;

            ALTER TABLE order_order_gift_cards
            ADD CONSTRAINT order_order_gift_cards_order_id_fk_order_order_old_id
                FOREIGN KEY (order_id) REFERENCES order_order (old_id);

            &lt;/span&gt;&lt;span class="se"&gt;""&lt;/span&gt;&lt;span class="nv"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reverse_sql&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;noop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As before, remember to create corresponding migrations for all &lt;code&gt;many-to-many&lt;/code&gt; relationships linked to the model you're modifying.&lt;/p&gt;

&lt;p&gt;Afterward, we are prepared to change the primary key to the &lt;code&gt;UUID&lt;/code&gt; value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set &lt;code&gt;UUID&lt;/code&gt; column as the primary key
&lt;/h3&gt;

&lt;p&gt;Modifying the model &lt;code&gt;pk&lt;/code&gt; also involves two steps, but both can be executed within a single migration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, we aim to convert our &lt;code&gt;token&lt;/code&gt; value into a primary key. Django will automatically eliminate the old &lt;code&gt;id&lt;/code&gt; field along with the primary key constraint. The modifications in the models.py file will appear as follows:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;editable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;old_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, we will rename the &lt;code&gt;token&lt;/code&gt; to &lt;code&gt;id&lt;/code&gt; to achieve the target state:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;editable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;old_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Here is the migration that will combine both changes:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0138_alter_order_gift_cards&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0008_fulfill_invoice_order_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RemoveField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AlterField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;editable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;serialize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenameField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;old_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What’s important, the migration's dependencies list must include all migrations that set token values on new temporary fields and redirect relation to &lt;code&gt;old_id&lt;/code&gt;.  In this context, the required migration to include in the dependencies list is &lt;code&gt;0008_fulfill_invoice_order_token&lt;/code&gt; from the invoice app.&lt;/p&gt;

&lt;h3&gt;
  
  
  After &lt;code&gt;UUID&lt;/code&gt; migration
&lt;/h3&gt;

&lt;p&gt;Now we are almost done. The last step is to rewrite the relations to point at the new primary key field instead of &lt;code&gt;old_id&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The steps for this phase applied to the relation models are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Change the field that keeps new UUID values (&lt;code&gt;order_token&lt;/code&gt;) into the relation field -&lt;code&gt;ForeginKey&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Remove the old relation field&lt;/li&gt;
&lt;li&gt;Rename the new relation field to the initial name&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s analyze those in our example.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Many-to-one and one-to-one relationship&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The steps for rewriting the &lt;code&gt;many-to-one&lt;/code&gt; and &lt;code&gt;one-to-one&lt;/code&gt; relationships can be combined into one migration. Each step will be explained separately, and then we'll consolidate them into a single migration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Firstly, our goal is to convert all temporary fields storing instances' &lt;code&gt;token&lt;/code&gt; values into relational fields. Specifically, we will convert the &lt;code&gt;Invoice.order_token&lt;/code&gt; &lt;code&gt;UUID&lt;/code&gt; field into a &lt;code&gt;ForeignKey&lt;/code&gt;, as follows:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SET_NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;to_field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;old_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;order_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Migration operation:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AlterField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order.order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The next step is to delete the previous relation field which was pointing to &lt;code&gt;old_id&lt;/code&gt;. It's no longer needed since we now have a field pointing to the new &lt;code&gt;Order&lt;/code&gt; primary key. As a result, the &lt;code&gt;Invoice&lt;/code&gt; model will only retain the &lt;code&gt;order_token&lt;/code&gt; field.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Migration operation:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RemoveField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, we will rename the &lt;code&gt;order_token&lt;/code&gt; field to match the name of the field we previously removed. So we are changing &lt;code&gt;order_token&lt;/code&gt; to &lt;code&gt;order&lt;/code&gt; field.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Migration operation:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenameField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;old_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The final step involves modifying our new relational field to match the original one. In our case, the only change is to set the proper &lt;code&gt;related_name&lt;/code&gt; value.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Migration operation:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AlterField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order.order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bringing it all together, the migration will look as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;django.db.models.deletion&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0139_update_order_pk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0009_alter_invoice_order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AlterField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order.order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RemoveField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenameField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;old_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AlterField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order.order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Naturally, our migration must depend on the migration from the order module that changes &lt;code&gt;id&lt;/code&gt; to &lt;code&gt;UUID&lt;/code&gt;. If not, we will encounter an error during the migration process.&lt;/p&gt;

&lt;p&gt;💡 Remember to apply the changes to all corresponding models.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Many-to-many relations&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Changes to &lt;code&gt;many-to-many&lt;/code&gt; relationships, as before, are more complex. We need to perform the same changes as for &lt;code&gt;many-to-one&lt;/code&gt; relationships, but in the proxy table using SQL operations.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, we will add the &lt;code&gt;ForeignKey&lt;/code&gt; constraint to our field that stores the &lt;code&gt;token&lt;/code&gt; value of corresponding &lt;code&gt;order&lt;/code&gt; instances. Since the &lt;code&gt;id&lt;/code&gt; has already been migrated to &lt;code&gt;UUID&lt;/code&gt;, we will point to the &lt;code&gt;id&lt;/code&gt; field of the &lt;code&gt;Order&lt;/code&gt; model:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards_order_token_fk_order_order_id&lt;/span&gt;
&lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;order_order&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The next step is to remove the constraint that we previously created, which points to the &lt;code&gt;old_id&lt;/code&gt; field.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards_order_id_fk_order_order_old_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now, we can delete the column that holds the old ID values. We wouldn't be able to do this without the previous step.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Afterward, rename the column that points to the &lt;code&gt;UUID&lt;/code&gt; values to the initial name.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards&lt;/span&gt;
&lt;span class="k"&gt;RENAME&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;order_token&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The final step involves renaming the constraint that was created in &lt;code&gt;step 1&lt;/code&gt; to match the new column name:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards&lt;/span&gt;
&lt;span class="k"&gt;RENAME&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards_order_token_fk_order_order_id&lt;/span&gt;
&lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;order_order_gift_cards_order_id_ce5608c4_fk_order_order_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bringing it all together, the migration will look as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0139_update_order_pk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;# rewrite order - gift cards relation - all operations are performed on
&lt;/span&gt;        &lt;span class="c1"&gt;# order_order_gift_cards table which is responsible for order-gift cards
&lt;/span&gt;        &lt;span class="c1"&gt;# many to many relation
&lt;/span&gt;        &lt;span class="c1"&gt;#   - add fk constraint to order id
&lt;/span&gt;        &lt;span class="c1"&gt;#   - delete constraint to order old_id
&lt;/span&gt;        &lt;span class="c1"&gt;#   - delete order_id column
&lt;/span&gt;        &lt;span class="c1"&gt;#   - rename order_token to order_id
&lt;/span&gt;        &lt;span class="c1"&gt;#   - rename newly added constraint
&lt;/span&gt;        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
            ALTER TABLE order_order_gift_cards
            ADD CONSTRAINT order_order_gift_cards_order_token_fk_order_order_id
                FOREIGN KEY (order_token) REFERENCES order_order (id);

            ALTER TABLE order_order_gift_cards
            DROP CONSTRAINT order_order_gift_cards_order_id_fk_order_order_old_id;

            ALTER TABLE order_order_gift_cards
            DROP COLUMN order_id;

            ALTER TABLE order_order_gift_cards
            RENAME COLUMN order_token TO order_id;

            ALTER TABLE order_order_gift_cards
            RENAME CONSTRAINT order_order_gift_cards_order_token_fk_order_order_id
            TO order_order_gift_cards_order_id_ce5608c4_fk_order_order_id;
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reverse_sql&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;noop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, remember to include the migrations responsible for changing the primary key in your dependencies list.&lt;/p&gt;

&lt;p&gt;And that's it. We have returned to the primary stage with all the relations intact, but with the model &lt;code&gt;id&lt;/code&gt; changed to &lt;code&gt;UUID&lt;/code&gt;. ✨&lt;/p&gt;

&lt;h3&gt;
  
  
  At the end
&lt;/h3&gt;

&lt;p&gt;At the end, you can discard &lt;code&gt;old_id&lt;/code&gt; if it's not needed. Also, reconsider the default ordering. If your default ordering uses &lt;code&gt;pk&lt;/code&gt;, it will no longer work as before, since the instances won't be sorted in the creation order due to the random value of &lt;code&gt;UUID&lt;/code&gt;. If you wish to maintain the order of instance creation, consider adding a creation date to your model, if one doesn't exist, and sort by this value.&lt;/p&gt;

&lt;p&gt;The field responsible for the creation date can be added in the same step where &lt;code&gt;token&lt;/code&gt; and &lt;code&gt;old_id&lt;/code&gt; are added. To preserve the old order, fill the instances with the current date and time, replacing the seconds with the old id value.&lt;/p&gt;

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

&lt;p&gt;As you can see, there are lots of steps to do, but I hope that this tutorial helps you go smoothly through this process with an understanding of each step. &lt;/p&gt;

&lt;p&gt;You can also check our &lt;a href="https://github.com/saleor/saleor" rel="noopener noreferrer"&gt;https://github.com/saleor/saleor&lt;/a&gt; repository for real-life examples. We performed such operations on &lt;code&gt;Order&lt;/code&gt;, &lt;code&gt;OrderLine&lt;/code&gt;, and &lt;code&gt;CheckoutLine&lt;/code&gt; models.&lt;/p&gt;

</description>
      <category>database</category>
      <category>django</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>DynamoDB access modeling for multi-tenant services</title>
      <dc:creator>Krzysztof Żuraw</dc:creator>
      <pubDate>Fri, 14 Feb 2025 10:11:00 +0000</pubDate>
      <link>https://dev.to/saleor/dynamodb-access-modelling-for-multi-tenant-services-1i3k</link>
      <guid>https://dev.to/saleor/dynamodb-access-modelling-for-multi-tenant-services-1i3k</guid>
      <description>&lt;p&gt;Recently at &lt;a href="https://saleor.io/" rel="noopener noreferrer"&gt;Saleor&lt;/a&gt; we encountered the challenge of implementing multi-tenancy in Saleor &lt;a href="https://docs.saleor.io/developer/app-store/overview" rel="noopener noreferrer"&gt;apps&lt;/a&gt;. In this blog post, I'll share my experience with access modelling when using DynamoDB for multi-tenant services.&lt;/p&gt;

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

&lt;p&gt;When developing a Saleor app that can be installed for multiple tenants, we faced several requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ensure data isolation between tenants&lt;/li&gt;
&lt;li&gt;Provide quick access times for serverless functions (which are main components of Saleor apps)&lt;/li&gt;
&lt;li&gt;Achieve good scalability&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Given these requirements, we chose DynamoDB as our database solution. Its flexible schema, low latency, and ability to scale horizontally made it an ideal fit for our multi-tenant Saleor app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modelling Approach
&lt;/h2&gt;

&lt;p&gt;Before diving into implementation, it's crucial to model our data and access patterns. Let's take the example of storing app configurations for multiple tenants.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Patterns
&lt;/h3&gt;

&lt;p&gt;We identified two main access patterns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read operations:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;API routes: Retrieve API keys for third-party services from saved configuration&lt;/li&gt;
&lt;li&gt;UI: Display current configuration values to end-users (via &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write operations:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;UI: Update app configuration (via &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Data Structure
&lt;/h3&gt;

&lt;p&gt;Our app configuration typically consists of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encrypted fields (e.g., API keys for third-party services)&lt;/li&gt;
&lt;li&gt;Mappings between Saleor channels and app configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  DynamoDB Table Design
&lt;/h3&gt;

&lt;p&gt;Following recommendations from &lt;a href="https://www.dynamodbbook.com/" rel="noopener noreferrer"&gt;The DynamoDB Book&lt;/a&gt; we decided to store all data for each app in a single DynamoDB table. Here's how we structured our table:&lt;/p&gt;

&lt;h3&gt;
  
  
  Primary Key (PK)
&lt;/h3&gt;

&lt;p&gt;We use a composite primary key in the format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{saleorApiUrl}#{saleorAppId}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure ensures that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each tenant's data is isolated&lt;/li&gt;
&lt;li&gt;New app installations don't access data from previous installations&lt;/li&gt;
&lt;li&gt;App can fetch multiple entities from DynamoDB in one request&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sort Key (SK)
&lt;/h3&gt;

&lt;p&gt;Our sort key follows the format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_CONFIG#{configKey}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;configKey&lt;/code&gt; represents the configuration version. This approach allows for easy future migrations to new configuration versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of This Design
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenancy:&lt;/strong&gt; The &lt;code&gt;saleorApiUrl&lt;/code&gt; in the PK enables multi-tenancy by separating data for different Saleor instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning:&lt;/strong&gt; The &lt;code&gt;configKey&lt;/code&gt; in the SK allows for multiple versions of configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility:&lt;/strong&gt; We can easily add other entity types by using different SK prefixes (e.g., &lt;code&gt;TRANSACTION#{transactionId}&lt;/code&gt; for storing webhook-related information).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Implementation Example
&lt;/h2&gt;

&lt;p&gt;Here's a TypeScript example of how we might query our DynamoDB table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocumentClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GetCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/lib-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DynamoDBClient&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;docClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBDocumentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAppConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saleorApiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;saleorAppId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;configKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AppTable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;saleorApiUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;saleorAppId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;SK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`APP_CONFIG#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;configKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;docClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;When modelling DynamoDB access for multi-tenant services:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identify access patterns:&lt;/strong&gt; Understand how your data will be read and written.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define data structure:&lt;/strong&gt; Determine what needs to be stored in DynamoDB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design table structure:&lt;/strong&gt; Choose appropriate primary and sort keys to enable efficient querying and ensure data isolation between tenants.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By following these steps, we've created a scalable and efficient DynamoDB structure for our multi-tenant Saleor app. This approach allows us to maintain data isolation, achieve quick access times, and easily adapt to future requirements.&lt;/p&gt;

</description>
      <category>dynamodb</category>
      <category>serverless</category>
      <category>typescript</category>
      <category>programming</category>
    </item>
    <item>
      <title>From Cypress to Playwright - Saleor’s Voyage</title>
      <dc:creator>Michalina Graczyk</dc:creator>
      <pubDate>Fri, 29 Nov 2024 11:05:23 +0000</pubDate>
      <link>https://dev.to/saleor/from-cypress-to-playwright-saleors-voyage-5d8f</link>
      <guid>https://dev.to/saleor/from-cypress-to-playwright-saleors-voyage-5d8f</guid>
      <description>&lt;p&gt;This is the story of our path from our early days of automated testing, to adapting to new tools, and finally to where we stand today with a more robust and efficient testing framework.&lt;/p&gt;

&lt;p&gt;Saleor recently migrated our end-to-end test suite from Cypress to Playwright. In this article we share our journey, the challenges we encountered, and how the transition unfolded.&lt;/p&gt;

&lt;p&gt;The decision to make the switch from one tool to another is never taken lightly. It needs to be carefully weighed up as it can impact the entire workflow and stability of our projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How it started&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s take a trip back to 2020. It’s the beginning of the pandemic and I’m diving into adding the first storefront end-to-end tests written in Cypress. Back then, we didn't have a CI/CD setup - GitHub history shows that our deployment workflow was established in 2021.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why we decided to use Cypress in 2020&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Well, one thing was certain, we needed automation. There weren’t many tools to choose between on the market, so the decision was straightforward. Selenium’s ongoing problems with drivers and debugging made it a no-go for us. Cypress was more user-friendly and was on top at the time - the future seemed bright for end-to-end automation.&lt;/p&gt;

&lt;p&gt;As the years passed, our QA team grew and we had more and more tests. Around 2022 we decided to upgrade to the paid version of Cypress to use the dashboard (now Cloud). It was a significant improvement for us, finally we could run tests in parallel!&lt;/p&gt;

&lt;p&gt;Test results were visible, we did some automation with Github. We are still using bots that tell us if the release has some failed tests or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Playwright was on the horizon&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As with every successful tech company, we keep an eye on ‘new-tech’ news. Playwright was gaining some attention and I became intrigued by this new tool. I was also a bit hesitant -  with PlayWright being a new tool, the community was only just starting to grow. In comparison, Cypress was more mature and well-documented, the community was more established, and we had experience with it.&lt;/p&gt;

&lt;p&gt;Even though we had encountered slowdowns and issues with Cypress, I was wary of making a rushed decision to switch test frameworks. Also, who would find the time to rewrite all our Cypress tests to Playwright?!&lt;/p&gt;

&lt;p&gt;Day by day, article by article, it became clear that we would eventually need to make the move. We were running tests so frequently and across so many versions that the cost of Cypress Cloud was becoming prohibitive. We tasked one of our QA team members to experiment with PW in their project. Tests ran faster, and a lot of the issues we experienced with Cypress seemed to vanish.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Cypress vs Playwright&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s take a look at a comparison between the two:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  &lt;strong&gt;Test structures&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s compare the test structures in Playwright and Cypress.&lt;/p&gt;

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

&lt;p&gt;At first, you can notice that PW uses &lt;code&gt;test&lt;/code&gt; to define the test case and &lt;code&gt;expect&lt;/code&gt; from its testing library for assertions.&lt;/p&gt;

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

&lt;p&gt;Cypress uses &lt;strong&gt;&lt;code&gt;it&lt;/code&gt;&lt;/strong&gt; to define the test case and Chai (&lt;strong&gt;&lt;code&gt;expect&lt;/code&gt;&lt;/strong&gt;) for assertions.&lt;/p&gt;

&lt;p&gt;As you can see, the test code looks different. Playwright uses a way of writing code called async/await, which is built into JavaScript. Cypress, on the other hand, has its own method for dealing with tasks that happen at different times, so you don't always need async/await. Assertions with PW uses Jest’s library, whereas Cypress uses its built-in assertion library, powered by Chai.&lt;/p&gt;

&lt;p&gt;Additionally, Playwright features a built-in auto-wait strategy. If the anticipated checks fail to materialize within the designated timeout, the action is flagged with a TimeoutError, mitigating flakiness. In contrast, Cypress is engineered to wait automatically for elements to be present in the Document Object Model (DOM) prior to executing actions such as clicking or form submission. However, this can extend the test duration.&lt;/p&gt;

&lt;p&gt;One of the advantages of PW is iFrame support, Cypress has problems with that which leads to having to do workarounds.&lt;/p&gt;

&lt;p&gt;These examples give you an idea of how Cypress and Playwright differ, but there's much more to explore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision and struggling
&lt;/h2&gt;

&lt;p&gt;So, in the middle of 2023, I began discussing the transition to PW with the QA team. We prioritized rewriting the most critical paths first and then tackled less critical but still important tests. During this refactor, we also cleaned up our tests, reducing the number of end-to-end tests in favor of more integration tests.&lt;/p&gt;

&lt;p&gt;As of 2024, we're still grappling with some configuration and workflow issues.&lt;/p&gt;

&lt;p&gt;Our first challenge was adding new workflows for running Playwright on the main branch, on pull requests and keeping workflows for Cypress since we still support older versions of Saleor. We want to keep posting test results on Slack and receive concise information about whether tests pass or fail on the release pull request.&lt;/p&gt;

&lt;p&gt;Currently each pull request trigger the PlayWright tests in parallel. This ensure us that only the most recent changes are tested.&lt;/p&gt;

&lt;p&gt;We also created a workflow for a nightly tests which are running three times per week.&lt;/p&gt;

&lt;p&gt;Last but no least, we have possibility of running tests via Github Actions. It is possible to run it on selected branch or tag.&lt;/p&gt;

&lt;p&gt;We needed to add a Slack notification. We wanted to receive concise information about whether tests pass or fail on the release pull request.&lt;/p&gt;

&lt;p&gt;Therefore, when a release pull request is ready, tests are triggered automatically, and the results are sent to TestMo (the tool for test cases and test results that we are using). In case some tests fail, we receive a Slack notification and comments on the release pull request. If all tests pass, then auto-merge should occur.&lt;/p&gt;

&lt;p&gt;There is still some work to be done, and we still support Cypress tests for older versions of Saleor.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summarizing
&lt;/h1&gt;

&lt;p&gt;Saleor's journey from Cypress to Playwright highlights the evolution of our automated testing framework. Starting with Cypress in 2020, we upgraded to the paid version to support parallel testing. However, rising costs and performance issues led us to explore Playwright, which offered faster execution, broader browser support, and finer control over browser interactions.&lt;/p&gt;

&lt;p&gt;Despite initial hesitations, we began switching to Playwright in mid-2023. We faced configuration and workflow challenges but implemented new workflows for parallel testing, nightly tests, and GitHub Actions integration. Especially, developers found it easier to dive into Playwright tests compared to Cypress. While initially challenging for our QA team, our tests are now faster, more stable, and better organized.&lt;/p&gt;

&lt;p&gt;As of 2024, we still support Cypress for older versions but continue to refine our Playwright-based testing framework for greater efficiency and stability in our CI/CD pipeline. I'm proud of our team for making the switch!&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>testing</category>
      <category>typescript</category>
      <category>cypress</category>
    </item>
  </channel>
</rss>
