<?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: Mark Parker</title>
    <description>The latest articles on DEV Community by Mark Parker (@markparker5).</description>
    <link>https://dev.to/markparker5</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1038598%2F48cd7105-eeae-4b91-83e8-d2e2c777e1a5.jpg</url>
      <title>DEV Community: Mark Parker</title>
      <link>https://dev.to/markparker5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/markparker5"/>
    <language>en</language>
    <item>
      <title>MajorDom — Your Effortlessly Simple Smart Home, Launched on Kickstarter</title>
      <dc:creator>Mark Parker</dc:creator>
      <pubDate>Thu, 03 Oct 2024 19:29:23 +0000</pubDate>
      <link>https://dev.to/markparker5/majordom-your-effortlessly-simple-smart-home-launched-on-kickstarter-1ecp</link>
      <guid>https://dev.to/markparker5/majordom-your-effortlessly-simple-smart-home-launched-on-kickstarter-1ecp</guid>
      <description>&lt;p&gt;Automate routines, save energy, and enjoy hands-free control with ease.&lt;br&gt;
Secure, private, open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.kickstarter.com/projects/parkerindustries/majordom-your-effortlessly-simple-smart-home" rel="noopener noreferrer"&gt;Go to Kickstarter&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Smart Home
&lt;/h2&gt;

&lt;p&gt;A true smart home is a system that provides comfort, security, and energy efficiency autonomously. While some actions are easier to control via smartphone or voice command, most should work without user intervention. That’s what MajorDom is — a truly smart home that works autonomously in the background.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Smart Home
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automate routines:&lt;/strong&gt; Dim lights at bedtime, adjust the thermostat when you leave, and lock doors at  — all automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimise energy:&lt;/strong&gt; Cut down on energy waste by turning off lights in empty rooms and adjusting HVAC based on occupancy and weather conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Peace of Mind:&lt;/strong&gt; Get real-time alerts for leaks, fires, break-ins, or when your washing machine finishes — monitor everything from your phone, anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vacation mode:&lt;/strong&gt; Automate lights and TV to make it look like someone’s home while you’re away, with security fully active.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote control:&lt;/strong&gt; Check if you left the lights on or the door unlocked from anywhere in the world, and adjust your home’s temperature remotely for a warm welcome.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intercom:&lt;/strong&gt; Send voice messages to all speakers in your home.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personalised assistant:&lt;/strong&gt; Receive spoken notifications, set custom alerts, control your home hands-free, and manage lists, calendars, and more without touching your phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;And many more:&lt;/strong&gt; Imagine a custom movie night scene that dims lights, lowers blinds, and sets up your sound system—all with a single voice command.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Best of Both Worlds: Usability and Functionality
&lt;/h2&gt;

&lt;p&gt;Vendor products often limit functionality, while popular open-source systems are overwhelming. MajorDom offers the best of both worlds—a plug-and-play solution with no compromises.&lt;/p&gt;

&lt;h2&gt;
  
  
  MajorDom Makes Home Automation Really Simple
&lt;/h2&gt;

&lt;p&gt;We believe technology should be available to everyone. While smart homes have traditionally had a high entry barrier, MajorDom is here to change that. It’s not only simple to use but, more importantly, simple to start with — making home automation accessible to tech enthusiasts, families, kids, and even complete non-tech-savvy beginners.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complete Plug&amp;amp;Play Solution
&lt;/h3&gt;

&lt;p&gt;Unlike other open-source platforms, MajorDom offers both hardware and software, delivering a truly comprehensive plug-and-play experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy as a Game
&lt;/h3&gt;

&lt;p&gt;Getting started is easy — no lengthy preparation or complicated installation required. It’s like playing a game with levels. Start with MajorDom Hub and MajorDom Audio, and our app will guide you step by step on your journey to a smarter home.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fqu4myed483yh0vy2bs7n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fqu4myed483yh0vy2bs7n.png" alt="MajorDom App Tutorials" width="680" height="783"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Intuitive and Friendly Interface
&lt;/h3&gt;

&lt;p&gt;MajorDom provides a single app for everything, with a simple and user-friendly interface. No need for complicated dashboards with tons of graphs and charts — this is your home, not a spaceship. Check your home’s status at a glance, control devices, and set up scenes and automations with ease. Your digital majordomo handles everything else.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fe4u88x34n3x2p1clflgx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fe4u88x34n3x2p1clflgx.png" alt="MajorDom Hub and App" width="680" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Vendor Unlock and Wide Device Support
&lt;/h3&gt;

&lt;p&gt;It's all about freedom. Vendors lock users in their ecosystems, so you can't use a device of another brand. But MajorDom breaks those barriers. There are many protocols, and the good thing is that MajorDom can handle all of them: WiFi, Bluetooth, Matter, ZigBee, Z-Wave-whatever. It even detects most devices automatically for easy pairing. Pick any, it just works. And even if you found an extremely rare device that doesn't — don't worry, either our team or the open source community will fix it soon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fg0sobgv94seip09gofuo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fg0sobgv94seip09gofuo.png" alt="MajorDom Supported Devices and Protocols in Room (HUD view)" width="680" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Family and Guests Ready
&lt;/h3&gt;

&lt;p&gt;MajorDom is designed to be intuitive for everyone. You can easily share access with family and guests while maintaining full control. But automations handle most tasks in the background, ensuring that everyone can enjoy the convenience without needing to manage it themselves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F0qf49ridh42gkx56368k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F0qf49ridh42gkx56368k.png" alt="MajorDom App Access Sharing" width="680" height="761"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  MajorDom Makes Home Truly Smart
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Automations
&lt;/h3&gt;

&lt;p&gt;In MajorDom, you can set up any scenario with ease in a few taps. Or just describe your idea and let Archie do the work. If you’re unsure, add all your devices and check the suggestions section. It doesn’t just analyze your devices; it learns your habits and patterns for better recommendations. You can even allow it to add scenes and automations without confirmation for an even more magical experience. We call it Smart Automations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fyemb8d96i4a2j4bp14s6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fyemb8d96i4a2j4bp14s6.png" alt="MajorDom App Smart Home Automation Creation and Suggestions" width="680" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Meet Archie: Your Personal Assistant
&lt;/h3&gt;

&lt;p&gt;Archie, your personalized voice assistant, listens in multiple languages, understands complex contexts, and handles multiple commands in a single request. But Archie isn’t just for controlling your home — it manages your day-to-day tasks, provides information, and integrates seamlessly with your calendar, reminders, notes, contacts, and more. Powered by large language models, Archie’s skills are limitless. Top of the line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fdkhd8ee0u569p20ny4y1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fdkhd8ee0u569p20ny4y1.png" alt="MajorDom Archie Voice Assistant" width="680" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Archie embodies MajorDom's core values: privacy, autonomy, plugins, and user-friendliness. It operates entirely on-device, but internet expands its possibilities even further.&lt;/p&gt;

&lt;h2&gt;
  
  
  MajorDom — Smart Home that is Actually Reliable
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Privacy at the Core
&lt;/h3&gt;

&lt;p&gt;All current vendor products operate through the cloud and, what's more, are owned by the same companies that profit from selling user data, running ads, or hosting marketplaces. In contrast, we’re convinced that privacy and security must be foundational. That’s why MajorDom stores all data locally and processes it on-device. For remote control, communication is end-to-end encrypted, ensuring that only you can access your home. With MajorDom, your data remains yours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Always On, Even Offline
&lt;/h3&gt;

&lt;p&gt;Another advantage of on-device processing — it doesn’t rely on the internet. No matter what happens to the cloud or your internet provider, your home stays smart, as if nothing happened.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fx687p0a67tykoepoifw9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fx687p0a67tykoepoifw9.png" width="680" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ownership, Not Renting
&lt;/h3&gt;

&lt;p&gt;Since you own the hardware and the software runs entirely on-device, you’re always in control of your home and your data. It’s about ownership, not renting. Your home — your rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  MajorDom Is Open in Every Way
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Open Source Transparency
&lt;/h3&gt;

&lt;p&gt;MajorDom is built on open-source technology, ensuring complete transparency. You can see, trust, and even customize every part of your smart home system. Transparency is at the core of our commitment to your privacy and security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open for Innovations: Meet Idea Forge
&lt;/h3&gt;

&lt;p&gt;We want to make it as easy as possible for people to integrate their ideas — whether hardware or software. That’s why we created the ‘Idea Forge.’ This platform transforms user proposals into practical features. Anyone can submit ideas through the suggestion form — no coding required, just a spark of innovation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fh21v8x03o2uecyyrlide.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fh21v8x03o2uecyyrlide.png" alt="MajorDom Idea Forge Features Voting Board" width="680" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These suggestions then appear on a community voting board, allowing everyone to influence the development process. Democracy in action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open for Extensions and Customizations
&lt;/h3&gt;

&lt;p&gt;But it’s not just a waiting game. If you’re feeling adventurous or just can’t wait, roll up your sleeves and code any feature yourself. You contribute, you create, and who knows — you might just end up part of the MajorDom team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fxiwl38tia1l1oev7xx1p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fxiwl38tia1l1oev7xx1p.png" alt="MajorDom Mascot MajorDog Coder Programmer Sticker" width="680" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MajorDom’s plugin system makes this even easier — any community-built feature can be installed directly through the UI. Developers can implement their ideas as plugins or propose them for integration into MajorDom’s core via merge requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open for ALL Devices (DIY Friendly)
&lt;/h3&gt;

&lt;p&gt;It’s not just about unlocking support for devices from all brands and manufacturers. With MajorDom, you can integrate anything as a fully functional smart home accessory in just minutes: from your Raspberry Pi to an Arduino. For example, imagine customizing LED lights or automating plant watering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F7iuk6577m1pngzhjb6hw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F7iuk6577m1pngzhjb6hw.png" alt="MajorDom Mascot MajorDog Engineer Sticker" width="680" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  MajorDom Hub
&lt;/h3&gt;

&lt;p&gt;The heart of your smart home, seamlessly connecting and supporting a wide range of accessories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fxny2fcacsszmp6to28g2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fxny2fcacsszmp6to28g2.png" alt="MajorDom Smart Home Hub" width="680" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MajorDom Audio
&lt;/h3&gt;

&lt;p&gt;A smart speaker with built-in Archie, our advanced voice assistant, ready to assist you in every aspect of your smart home.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F7v2sd36zec032h555ijr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F7v2sd36zec032h555ijr.png" alt="MajorDom Audio Smart Speaker with Voice Assistant Archie" width="680" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Limited-Edition Merch
&lt;/h3&gt;

&lt;p&gt;Let's be closer. Originally created for our team to celebrate our first anniversary, we’ve decided to share a bit of our spirit with you too &amp;lt;3&lt;br&gt;
  (Available only through the Kickstarter campaign)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fm81a6an534d11arbv5t9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fm81a6an534d11arbv5t9.png" alt="MajorDom Merch: Shaping Tomorrow Collection" width="800" height="846"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stretch Goals: Unlock More Features Together!
&lt;/h2&gt;

&lt;p&gt;We’d love to give you everything right from the start, but quality takes time, and every feature requires resources. That’s why we’ve set stretch goals — to ensure we can deliver the best possible experience without compromising on what makes MajorDom great. So, share this page with your friends, family, and everyone who might be interested. The more the campaign raise, the more features everyone gets to enjoy. It’s a win-win for all of us! Let’s build the future of smart homes together.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;60k MajorDom Hub &amp;amp; Audio, Smartphone App, and all features that aren't mentioned bellow&lt;/li&gt;
&lt;li&gt;80k Home Screen Widgets&lt;/li&gt;
&lt;li&gt;100k Idea Forge&lt;/li&gt;
&lt;li&gt;150k Tablet / iPad App&lt;/li&gt;
&lt;li&gt;200k Access Sharing&lt;/li&gt;
&lt;li&gt;300k Smartwatch (wearable) App&lt;/li&gt;
&lt;li&gt;400k Documented SDKs for plugins, apps, and DIY devices&lt;/li&gt;
&lt;li&gt;500k App (Plugin) Store&lt;/li&gt;
&lt;li&gt;600k Cameras support&lt;/li&gt;
&lt;li&gt;700k On-device LLM&lt;/li&gt;
&lt;li&gt;800k SubGHz and IR devices support&lt;/li&gt;
&lt;li&gt;1m+ Customizable Voice for Archie&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*Merch has it's own stretch goals: we need to have at lest 50 orders of the item to be able to produce it with reasonable price.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media.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%2Fv4tht5vb9by9b4zl6rs1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fv4tht5vb9by9b4zl6rs1.png" alt="MajorDom Kickstarter Launch Roadmap" width="600" height="1728"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping and Taxes
&lt;/h2&gt;

&lt;p&gt;MajorDom will be shipped worldwide.&lt;/p&gt;

&lt;p&gt;Shipping will be charged via our pledge manager after the campaign, where you will also be able to confirm your rewards, address, and choose extra add-ons. Please note, that our prices do not include local taxes, duties, and VAT, that will be subject to the laws and regulations of each individual country.&lt;/p&gt;

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

&lt;p&gt;Privacy-first design, open source, intuitive interface, wide device support, friendly game-like setup, smart automations that adapt to your habbits, and Archie—the best-in-class personal voice assistant. Plus, exclusive merch. So, why wait?&lt;/p&gt;

&lt;h3&gt;
  
  
  Join the smart home revolution today!
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start Simple:&lt;/strong&gt; Begin with the Hub and Audio to explore the possibilities as you go. You’ll have a strong foundation for your smart home and immediate access to the best voice assistant around. Follow the app’s step-by-step guides to make your home smart at your own pace.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limited-Time Offer:&lt;/strong&gt; Kickstarter discounts are a one-time deal — don’t miss out!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Perfect Gift:&lt;/strong&gt; MajorDom makes a great gift, bringing smart home convenience to your loved ones.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Back MajorDom on &lt;a href="https://www.kickstarter.com/projects/parkerindustries/majordom-your-effortlessly-simple-smart-home" rel="noopener noreferrer"&gt;Kickstarter&lt;/a&gt; today and make your home truly smart!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>smarthome</category>
      <category>homeautomation</category>
      <category>iot</category>
      <category>voiceassistant</category>
    </item>
    <item>
      <title>Meet MajorDom: a smart home of the future that is really smart</title>
      <dc:creator>Mark Parker</dc:creator>
      <pubDate>Fri, 31 May 2024 01:05:00 +0000</pubDate>
      <link>https://dev.to/markparker5/meet-majordom-a-smart-home-of-the-future-that-is-really-smart-22gk</link>
      <guid>https://dev.to/markparker5/meet-majordom-a-smart-home-of-the-future-that-is-really-smart-22gk</guid>
      <description>&lt;p&gt;MajorDom - a brand new open-source smart home ecosystem, built for privacy, autonomy, and wide device support. The platform combines intelligent automations with easy plug-n-play design and a really smart voice assistant.&lt;/p&gt;




&lt;p&gt;In the world of smart homes, you often have to choose between usability and functionality. Thinking about what an ideal smart home could be, we came up with the idea of MajorDom — a system that seeks to change this balance and simplify life without sacrifice. In this post, we'll share our vision and some core principles of the new ecosystem, including privacy, autonomy, and broad device support.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fseb1v7cxx1cn19jbr8od.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fseb1v7cxx1cn19jbr8od.jpg" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Today there are many different gadgets for the home: lamps, curtains, heaters, vacuum cleaners, security, and microclimate sensors. They are designed to make life easier, but not everything is so simple.&lt;/p&gt;

&lt;p&gt;Previously, each device had its own control protocol, its own standards, its own security methods, and each of them needed a separate application. The more devices there are in the house, the more time you need to devote to managing them, which turns into a new routine. It's like juggling too many balls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fl9fv6gwi0xiajymocu3v.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fl9fv6gwi0xiajymocu3v.jpg" width="746" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Current solutions
&lt;/h2&gt;

&lt;p&gt;Smart home (or home automation) systems were supposed to solve this problem, but they are still far from ideal. There are two types of such systems: proprietary from digital corporations and popular open source. Unfortunately, both of them have disadvantages. I propose to draw up a graph in which the ease of use of complex to simple ones will be on the &lt;code&gt;x&lt;/code&gt; axis, and the smartness and functionality of the system will be on the &lt;code&gt;y&lt;/code&gt; axis.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fgzgbchqc4prmvo9b10c3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fgzgbchqc4prmvo9b10c3.jpg" width="764" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It turns out that all proprietary systems are located somewhere in the center-bottom. They provide some functionality that the average person can handle after spending some time learning. Most often, these systems are closed and support only their limited list of devices in their special application. Their functionality boils down to replacing the physical switch with a button on the phone or simple voice commands. Sometimes there are elementary automations, or rather scripts that need to be written manually.&lt;/p&gt;

&lt;p&gt;At the same time, they are too dependent on cloud solutions. A server failure, changes in geopolitics, new regulations, or a simple lack of Internet means a smart home shutdown.&lt;/p&gt;

&lt;p&gt;But what’s worse is that the most popular systems belong to advertising or marketplace giants who make money by selling users’ personal data. This is the basis of their business model, which is why they cannot change, so trust and privacy are out of the question.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fwpmj7pj7wz9uozcu7ouq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fwpmj7pj7wz9uozcu7ouq.jpg" width="780" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Those who want more features or don't want surveillance have to switch to open solutions and pay for it with the complexity of configuration and installation. This is the area above and to the left of center, but often it's worth it. Open systems offer more freedom, integration with almost any device and protocol (thanks to plugins), unlimited customization and full control. This is a task for techies who want to spend their evenings studying forums and developing their own configurations. Likewise, some people like to spend hours in the garage going through all the parts of the car. I'm not saying it's bad, everybody deserves a hobby, but most people want a car just to drive it. Of course, you can hire a professional to handle all the devices for a fortune. But what if you don't want to hire a professional or become one yourself?&lt;/p&gt;

&lt;p&gt;We want to make a system that will occupy the upper right corner: it will work right out of the box, support a wide range of devices, securely store user data, and at the same time be smarter and more functional than the rest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F9qbwxy35ezsmc9ejiqk4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F9qbwxy35ezsmc9ejiqk4.jpg" width="764" height="722"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Smart home ecosystem
&lt;/h2&gt;

&lt;p&gt;Let's talk about how smart home ecosystems work and how they differ from home automation systems. It all starts with devices that directly control the house: lamps, relays, modules with a motor. This is the first "physical" layer. The second layer is for interface, mobile apps, voice assistants, etc.; let's call it "application" layer. Now, let's connect them via wifi or bluetooth.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F8tpqtwkpmdhj9qho0bcl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F8tpqtwkpmdhj9qho0bcl.jpg" width="399" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But let's imagine that we have several devices, and each one has an app. Doesn't look very comfortable, does it?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F07fdk90w1b03qa9jmdy8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F07fdk90w1b03qa9jmdy8.jpg" width="799" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we will transfer the devices to more energy-efficient radio protocols. But how now to connect them to your smartphone? Let's add an intermediary in the form of a hub, which has a radio module on one side and the same wifi on the other. As a bonus, we will connect all devices from the same manufacturer to it. Now each app can control a few devices, but only produced by the same brand. This is what closed “ecosystems” look like. Each uses its own protocols and standards, so they are not compatible with each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Felfycqy9dtip3qfhssym.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Felfycqy9dtip3qfhssym.jpg" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But what’s worse is that not all applications can communicate with the hub directly within a local network (LAN) and use the server even when you are at home. This is the case when turning off the Internet means a complete blackout, while turning on means remote control of the home from the cloud (do you trust the cloud of a company that makes money from selling your personal data but doesn't care about keeping it safe?)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Flhx0vlq7a957opa003yf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Flhx0vlq7a957opa003yf.jpg" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To fix this, we’ll replace the proprietary hub with a raspberry pi with some open source home automation system, and also add plugins for device integration. This makes it possible to combine all devices into one system, for example to program global automations or advanced scenarios. It's better, but one little thing called the interface is missing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fnywyazzeo4tvja6lapal.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fnywyazzeo4tvja6lapal.jpg" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fortunately, some open source solutions come with a web frontend or even a mobile application (not always user-friendly). By adding a couple more plugins, we can forward some devices (or all, if we are very lucky) of the devices to an application of one of the ecosystems. In this case, the hub acts as an intermediary or adapter for third-party devices. But now we are dependent on this ecosystem and receive all its disadvantages, which were discussed at the beginning of the article. Alternatively, we can connect another plugin with for a custom or 3rd-party cloud solution, but this becomes either too complicated or still not secure enough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fjrjs9rxv3onjcxh010mg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fjrjs9rxv3onjcxh010mg.jpg" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I would like to note that it is not always possible to completely replace a proprietary hub with a custom one. Often you will need to have both hubs (proprietary and custom) for the system to work and support required devices and applications. This could end up becoming a tangle of technology. In short, it's too complicated, and it's not worth it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.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%2F22ku0fldlo5qem5mslvz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F22ku0fldlo5qem5mslvz.jpg" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the way, this example reminds me of something:&lt;br&gt;
&lt;a href="https://media.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%2Fh6a6f560xn8dvmhcpu1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fh6a6f560xn8dvmhcpu1o.png" alt="Don't be that open-source user, don't be me - Jacob Tomlinson" width="385" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we have 4 layers: devices, hub (automation, software control), servers, interface (apps, voice assistants, etc.). The ecosystem is all 4 layers and their connection, not just one. To make everything work perfectly, we don't just do one of the layers, for example an automation system at the hub level. We do all three top levels: app, voice assistant, cloud, and hub with maximum support for third-party devices, thus maximizing compatibility and integration of the whole system out of the box: autonomous, private, independent and secure. This is MajorDom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Ft2vhivvseg8183g319s8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ft2vhivvseg8183g319s8.jpg" width="800" height="623"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why our ecosystem is smarter: our philosophy
&lt;/h2&gt;

&lt;p&gt;We lay the following principles as the foundation of our work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete privacy of personal data — home is not a place for prying eyes. Privacy is a basic right of every user.&lt;/li&gt;
&lt;li&gt;Autonomy — maximum independence from the outside world, disconnected Internet should not be a problem&lt;/li&gt;
&lt;li&gt;Ease of setup and use — technology should serve people, and not vice versa. It's fine to dive deep into hobbies, but many just need something that works right out of the box.&lt;/li&gt;
&lt;li&gt;Maximum support for different devices, protocols, and integrations — in addition to the previous point&lt;/li&gt;
&lt;li&gt;No artificial restrictions — avoid being Apple and give the opportunity for deep configuration and customization to those who require it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We make a completely private, fully autonomous, truly SMART home that is different to the options promoted by big tech. We are convinced that privacy is not just something abstract, not even a promise. Privacy — is your core right. And we believe that technology must work for you, not the other way around. &lt;br&gt;
No sacrifices, no exceptions.&lt;/p&gt;

&lt;p&gt;So, we are going to reinvent the smart home. In our vision, a true smart home has an invisible army of devices, that are working autonomously in the background, improving your everyday life and covering your back. It's like having a digital majordomo.&lt;/p&gt;

&lt;p&gt;A true smart home must be independent from the outside world. No failures because the internet or a random server is down. It's a fully autonomous ecosystem that doesn't require anything else, either: not internet, not cloud services, and not even a human.&lt;/p&gt;

&lt;p&gt;At the same time, a system must be simple to use. No long installations or configurations. No periodical change of settings. No code writing. Just plug and play. &lt;strong&gt;Technology must work for you, remember?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How: Architecture design
&lt;/h2&gt;

&lt;p&gt;But, how do we do it? First things first, the system needs a name. We called the system MajorDom. And it perfectly fits the description.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy Implementation
&lt;/h3&gt;

&lt;p&gt;But how we deal with the most important thing — your data?&lt;/p&gt;

&lt;p&gt;While all other systems are black boxes, we believe that MajorDom must be open source, so there is no behind-the-scenes manipulation and anyone can open and read it, highlight issues, suggest changes, or even contribute to it.&lt;/p&gt;

&lt;p&gt;While the source code is publicly available, your data is protected like never before. Let’s talk about some technical solutions MajorDom utilizes. &lt;/p&gt;

&lt;p&gt;To make the system private and autonomous, most of the data is stored locally on the devices and generally on the Hub. This guarantees privacy, and since all data is stored locally, automations and all the other features work perfectly even in the absence of the Internet. This is unlike other systems, which always rely on the internet connection because all data is sent and stored somewhere on the server. We have no 3rd-party services, no extra transfers, no data collectors.&lt;/p&gt;

&lt;p&gt;But in some cases data must be transferred though the internet, for example with remote control when you're on the go. In this case, data is securely end-to-end encrypted and keys are stored only on user’s physical devices. That means you can actually access all of your accessories remotely, only you, and absolutely no one else, including admins and developers. &lt;/p&gt;

&lt;h3&gt;
  
  
  Supported Devices: Matter, Merlin.
&lt;/h3&gt;

&lt;p&gt;Of course, before setting up automation, you need devices in the house. The Zigbee Alliance, renamed the Connectivity Standards Alliance or CSA, is a coalition of various smart home companies that have decided to create a universal communication protocol for all home automation devices. They called this protocol Matter. And MajorDom is compatible with it. This means you can add any Matter compatible device to your MajorDom system. And that is not all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F5gieobr9212r6voyhztt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F5gieobr9212r6voyhztt.jpg" alt="Image description" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Matter natively only supports the most common underlying devices, which is why we created the Merlin communication protocol. Thanks to a more flexible architecture, it not only significantly expands the list of supported devices, but also makes it endless.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fmwj8k63wn4g3nemlri1g.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fmwj8k63wn4g3nemlri1g.jpg" alt="Image description" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the same time, we understand that today only a small portion of devices already released support one of these protocols, so we are also going to add integrations of devices using zigbee, z-wave, wifi and BLE, thus becoming the most universal ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fb6uby1ojkk9xn4lacm9a.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fb6uby1ojkk9xn4lacm9a.jpg" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Interface
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fuk4dakwh1381l8cnuvn3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fuk4dakwh1381l8cnuvn3.jpg" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, how are we going to communicate with this? For this task, we designed a beautiful application, with customizable themes. It's a real pleasure to use. And it's available for all major platforms: android, including smartphones, tablets, and watches, and iOS family with iPhones, iPads, Macs, Watches, tvOS, and even future visionOS. The app also includes widgets that you can add to your desktop, home, or lock screen with any supported device, so you can access your home even without launching the app. We have ensured that full control of your home is always at your fingertips.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fqxjhvbp464araxi9ditz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fqxjhvbp464araxi9ditz.jpg" alt="Image description" width="800" height="1218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But a truly smart home must work autonomously most of the time, in the background, so you don't even notice it. Now, how are we going to automate this? Write scripts, like every other system, right? We’re going to use scripts? No. Nobody wants to write scripts. And we know this. Instead of this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F4nleace76oo5mm6yljk9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F4nleace76oo5mm6yljk9.jpg" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For automations, we added the second amazing tab to the app, which helps you set up any scenario with ease.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Freiuw0lcvug8iy657sgg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Freiuw0lcvug8iy657sgg.jpg" alt="Image description" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To simplify the process even more, we invented a smart suggestions technology that predicts what you want to automate. And sometimes it's so smart, so it even doesn't require the user’s help. For instance, you can just add all accessories and start using them without adding any automation or scenario. After a short time, this technology will suggest scenes and automations based on your habits. You can even allow it to add scenes and automations without confirmation in the background. It's that easy. And it's phenomenal. It works like magic. We called this technology Smart Automation. &lt;/p&gt;

&lt;h3&gt;
  
  
  Archie — a Smart Voice Assistant
&lt;/h3&gt;

&lt;p&gt;Automations are good for everyday routine. But sometimes you need a more personal touch that's more than just commands and controls. &lt;/p&gt;

&lt;p&gt;Archie, the Autonomous Responsive Cognitive Home Interaction Engine. The voice assistant that outsmarts all others and really understands you. Archie is able to understand what you need to an extremely high level of precision and makes communication effortless by offering a live language experience that feels spontaneous and conversational.  Top of the line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Ff3p1p46fohxxvmtte3yq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ff3p1p46fohxxvmtte3yq.jpg" alt="Image description" width="600" height="849"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like a truly professional majordomo, Archie speaks many languages. He can simultaneously listen to up to three preselected languages.&lt;/p&gt;

&lt;p&gt;But Archie isn't just for controlling your home; it's for every facet of your daily life, from managing tasks to providing general information.&lt;/p&gt;

&lt;p&gt;Archie inherits all the core values of MajorDom: private, autonomous, easy to use, and truly smart. Archie can work completely offline, but the Internet expands its possibilities to an unlimited range. Powered by the largest language models, Archie's skills are closer to real artificial intelligence than ever before in human history.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fqt5tlrpwd7gxh2w627ke.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fqt5tlrpwd7gxh2w627ke.jpg" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Work out of the box: we make our own devices
&lt;/h3&gt;

&lt;p&gt;“People who are serious about software should make their own hardware” — this quote is especially relevant for a smart home. This is the only way to ensure seamless integration of multiple protocols out of the box. The same goes for the assistant: not every platform can handle offline speech recognition, processing, and synthesis. In addition, you can completely trust it only when you know that there are no third-party software on the device with access to the microphone. That's why we're developing two of our own: MajorDom Hub for device management, automation; a portal to the ecosystem — like the hands of the home. And MajorDom Audio — a smart speaker for Archie; the ears and voice of the home.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fhsh8ln6thc17w3uy6ene.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fhsh8ln6thc17w3uy6ene.jpg" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One last thing… for Contributors.
&lt;/h2&gt;

&lt;p&gt;We want to make it as easy as possible for people to integrate their ideas — whether hardware or software. To this end, we have created two major tools to allow everybody to become a contributor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fwzbatsn7gi4momrdzxu0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fwzbatsn7gi4momrdzxu0.jpg" alt="Image description" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, in the development of MajorDom, we've established the 'Idea Forge'. This platform transforms user proposals into practical features. Anyone can submit ideas through the idea suggestion form. You don't need to be a coder; you just need a spark of innovation. These suggestions then appear on a community voting board, allowing everyone to influence the development process. Democracy in action. The most popular ideas are considered for implementation by the MajorDom Team. This isn't just about users; it's about turning users into makers.&lt;/p&gt;

&lt;p&gt;But it's not just a waiting game. If you're feeling adventurous, or you just can't wait, you can roll up your sleeves and code any feature yourself. You contribute, you create, and who knows, you might just end up part of the MajorDom team.&lt;/p&gt;

&lt;p&gt;Secondly, we are simplifying the job for all developers. We build modular hub firmware with plugin support and convenient libraries with detailed documentation.&lt;/p&gt;

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

&lt;p&gt;At the moment, the project is still under active development, but most of what is mentioned has already been implemented, including the core of the system, integration of some protocols, remote control, automations, the offline part of Archie and the mobile app. We will publish further news here, but I also recommend subscribing to the relevant project pages on social networks. Sign up for early access at &lt;a href="https://majordom.io" rel="noopener noreferrer"&gt;majordom.io&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Join the project
&lt;/h2&gt;

&lt;p&gt;The project is quite complex and large-scale, and a high-quality result requires many hours of work by professional engineers, programmers and designers. In the modern capitalist world, only commercial development can guarantee a stable result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-orders, Kickstarter, Donations
&lt;/h3&gt;

&lt;p&gt;In the future, the project will be published on Kickstarter — a crowdfunding platform on which it will be possible to place the earliest pre-orders of devices, but you already can financially support the project on &lt;a href="https://patreon.com/MarkParker5" rel="noopener noreferrer"&gt;patreon&lt;/a&gt; or &lt;a href="https://buymeacoffee.com/markparker5" rel="noopener noreferrer"&gt;buymeacoffee&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Become part of the team
&lt;/h3&gt;

&lt;p&gt;Our team already has designers, software engineers for the frontend, backend, mobile apps, the Hub, and the voice assistant. At the same time, we are looking for industrial designers, embedded hardware engineers, as well as programmers who understand the low-level details of popular protocols in the smart home field. If you are doing something else, but want to join the project, write your suggestions, everyone is welcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  Investors
&lt;/h3&gt;

&lt;p&gt;We are also considering receiving investment from a $50k pre-seed round for a share of the company. Speaking of numbers, the current smart home market is valued at US$100 billion, with a projected growth of up to US$600 billion in 2033. Looks like a great investment opportunity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contacts
&lt;/h3&gt;

&lt;p&gt;Telegram: &lt;a href="https://t.me/MarkParker5" rel="noopener noreferrer"&gt;t.me/MarkParker5&lt;/a&gt;&lt;br&gt;
Email: &lt;a href="//mailto:mark@parker-programs.com"&gt;mark@parker-programs.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>smarthome</category>
      <category>voiceassistant</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>Python Architecture Essentials: Building Scalable and Clean Application for Juniors</title>
      <dc:creator>Mark Parker</dc:creator>
      <pubDate>Tue, 28 May 2024 22:00:00 +0000</pubDate>
      <link>https://dev.to/markparker5/python-architecture-essentials-building-scalable-and-clean-application-for-juniors-2o14</link>
      <guid>https://dev.to/markparker5/python-architecture-essentials-building-scalable-and-clean-application-for-juniors-2o14</guid>
      <description>&lt;p&gt;Structuring a project with SOLID, KISS, DRY, and clean code principles, along with efficient design patterns. In simple words with real examples.&lt;/p&gt;




&lt;p&gt;Dive into the fundamentals of scalable and clean application architecture in Python with this beginner-friendly guide. Here, we explore essential concepts such as Object-Oriented Programming (OOP), SOLID principles, Dependency Injection (DI), and typing. This article is designed to take you from zero to hero, transforming complex architectural standards into actionable insights that enhance modularity, low coupling, and high cohesion. Through practical examples, you'll learn how to develop multilayered architectures that ensure modularity and independence, preparing you for successful, maintainable projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Clean Code Theory
&lt;/h2&gt;

&lt;p&gt;Before diving into architecture, I would like to answer a few frequently asked questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are the benefits of specifying types in Python?&lt;/li&gt;
&lt;li&gt;What are the reasons for splitting an app into layers?&lt;/li&gt;
&lt;li&gt;What are the advantages of using OOP?&lt;/li&gt;
&lt;li&gt;What are the drawbacks of using global variables or singletons?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to skip the theory sections if you already know the answers and jump directly to the "Building the App" section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Always Annotate Types
&lt;/h3&gt;

&lt;p&gt;Annotating all types in Python significantly enhances your code by increasing its clarity, robustness, and maintainability:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Type Safety&lt;/strong&gt;: Type annotations help catch type mismatches early, reducing bugs and ensuring your code behaves as expected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Self-documenting Code&lt;/strong&gt;: Type hints increase the readability of your code and act as built-in documentation, clarifying the expected input and output data types of functions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Improved Code Quality&lt;/strong&gt;: Employing type hints encourages better design and architecture, promoting thoughtful planning and implementation of data structures and interfaces.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced Tool Support&lt;/strong&gt;: Tools like &lt;strong&gt;mypy&lt;/strong&gt; utilize type annotations for static type checking, identifying potential errors before runtime, thus enhancing the development process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Support from Modern Libraries&lt;/strong&gt;: Libraries such as &lt;strong&gt;FastAPI&lt;/strong&gt; and &lt;strong&gt;Pydantic&lt;/strong&gt; leverage type annotations to provide functionalities like automatic data validation, schema generation, and comprehensive API documentation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advantages of Typed Dataclasses Over Simple Data Structures&lt;/strong&gt;: Typed dataclasses improve readability, structured data handling, and type safety compared to dicts and tuples. They use attributes instead of string keys, which minimizes errors due to typos and improves code autocompletion. Dataclasses also provide clear definitions of data structures, support default values, and simplify code maintenance and debugging.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why Do We Need to Split the App Into Layers
&lt;/h3&gt;

&lt;p&gt;Splitting an application into layers enhances maintainability, scalability, and flexibility.  Key reasons for this strategy include:&lt;/p&gt;

&lt;h4&gt;
  
  
  Separation of concerns
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Each layer focuses on a specific aspect, which simplifies development, debugging and maintenance.
#### Reusability&lt;/li&gt;
&lt;li&gt;Layers can be reused in different parts of the application or in other projects. Code duplication is eliminated.
#### Scalability&lt;/li&gt;
&lt;li&gt;Allows different layers to scale independently of each other depending on needs.
#### Serviceability&lt;/li&gt;
&lt;li&gt;Simplifies maintenance by localizing common functions in separate layers.
#### Improved collaboration&lt;/li&gt;
&lt;li&gt;Teams can work on different layers independently.
#### Flexibility and adaptability&lt;/li&gt;
&lt;li&gt;Changes in technology or design can be implemented in specific layers. Only the affected layers need to be adapted, the others remain unaffected.
#### Testability&lt;/li&gt;
&lt;li&gt;Each layer can be tested independently, simplifying unit testing and debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adopting a layered architecture offers significant advantages in development speed, operational management, and long-term maintenance, making systems more robust, manageable, and adaptable to change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global Constants vs Injected Parameters
&lt;/h3&gt;

&lt;p&gt;When developing software, the choice between using global constants and employing dependency injection (DI) can significantly impact the flexibility, maintainability, and scalability of applications. This analysis delves into the drawbacks of global constants and contrasts them with the advantages provided by dependency injection.&lt;/p&gt;

&lt;h4&gt;
  
  
  Global Constants
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fixed Configuration&lt;/strong&gt;: Global constants are static and cannot dynamically adapt to different environments or requirements without modifying the codebase. This rigidity limits their utility across diverse operational scenarios.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limited Testing Scope&lt;/strong&gt;: Testing becomes challenging with global constants as they are not easily overridden. Developers might need to alter the global state or employ complex workarounds to accommodate different test scenarios, thereby increasing the risk of errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduced Modularity&lt;/strong&gt;: Relying on global constants decreases modularity since components become dependent on specific values being set globally. This dependence reduces the reusability of components across different projects or contexts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tight Coupling&lt;/strong&gt;: Global constants integrate specific behaviors and configurations directly into the codebase, making it difficult to adapt or evolve the application without extensive modifications.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hidden Dependencies&lt;/strong&gt;: Similar to global variables, global constants obscure the dependencies within an application. It becomes unclear which parts of the system rely on these constants, complicating both the understanding and the maintenance of the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Namespace Pollution and Scalability Challenges&lt;/strong&gt;: Global constants can clutter the namespace and complicate the scaling of applications. They promote a design that isn't modular, hindering efficient distribution across different processes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maintenance and Refactoring Difficulties&lt;/strong&gt;: Over time, the use of global constants can lead to maintenance challenges. Refactoring such a codebase is risky as changes to the constants might inadvertently affect disparate parts of the application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Module-Level State Duplication&lt;/strong&gt;: In Python, module-level code might be executed multiple times if imports occur under different paths (e.g., absolute vs. relative). This can lead to duplication of global instances and hard-to-track maintenance bugs, further complicating the application’s stability and predictability. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Injected Parameters
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic Flexibility and Configurability&lt;/strong&gt;: Dependency injection allows for the dynamic configuration of components, making applications adaptable to varying conditions without needing code changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced Testability&lt;/strong&gt;: DI improves testability by enabling the injection of mock objects or alternative configurations during tests, effectively isolating components from external dependencies and ensuring more reliable test outcomes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Increased Modularity and Reusability&lt;/strong&gt;: Components become more modular and reusable as they are designed to operate with any injected parameters that conform to expected interfaces. This separation of concerns enhances the portability of components across various parts of an application or even different projects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Loose Coupling&lt;/strong&gt;: Injected parameters promote loose coupling by decoupling the system’s logic from its configuration. This approach facilitates easier updates and changes to the application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Explicit Dependency Declaration&lt;/strong&gt;: With DI, components clearly declare their dependencies, typically through constructor parameters or setters. This clarity makes the system easier to understand, maintain, and extend.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scalability and Complexity Management&lt;/strong&gt;: As applications grow, DI helps manage complexity by localizing concerns and separating configuration from usage, aiding in the effective scaling and maintenance of large systems.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Procedural vs OOP
&lt;/h3&gt;

&lt;p&gt;Using Object-Oriented Programming (OOP) and Dependency Injection (DI) can significantly enhance code quality and maintainability compared to a procedural approach with global variables and functions. Here's a simple comparison that demonstrates these advantages:&lt;/p&gt;

&lt;h4&gt;
  
  
  Procedural Approach: Global Variables and Functions
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# Global configuration
&lt;/span&gt;&lt;span class="n"&gt;database_config&lt;/span&gt; &lt;span class="o"&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;host&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;localhost&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;port&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3306&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user&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;user&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;password&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;pass&lt;/span&gt;&lt;span class="sh"&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;connect_to_database&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connecting to database on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;database_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Assume connection is made
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;database_connection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;database_connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fetching user &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; using &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;database_connection&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Fetch user logic
&lt;/span&gt;    &lt;span class="k"&gt;return&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="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&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;John Doe&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Usage
&lt;/span&gt;&lt;span class="n"&gt;db_connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;connect_to_database&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code Duplication&lt;/strong&gt;: &lt;code&gt;database_config&lt;/code&gt; must be passed around or accessed globally in multiple functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing Difficulty&lt;/strong&gt;: Mocking the database connection or configuration involves manipulating global state, which is error-prone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tight Coupling&lt;/strong&gt;: Functions directly depend on global state and specific implementations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  OOP + DI Approach
&lt;/h4&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;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstractmethod&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MySQLConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DatabaseConnection&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connecting to MySQL database on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Assume connection is made
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fetching user &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; from MySQL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="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="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&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;John Doe&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;UserService&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db_connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DatabaseConnection&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db_connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db_connection&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Configuration and DI
&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&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;host&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;localhost&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;port&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3306&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user&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;user&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;password&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;pass&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MySQLConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&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="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;user_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UserService&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Code Duplication&lt;/strong&gt;: The database configuration is encapsulated within the connection object; no need to pass it around.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DI Possibilities&lt;/strong&gt;: Easily switch out &lt;code&gt;MySQLConnection&lt;/code&gt; with another database connection class without changing &lt;code&gt;UserService&lt;/code&gt; code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encapsulation and Abstraction&lt;/strong&gt;: Implementation details of how users are fetched or how the database is connected are hidden away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ease of Mocking and Testing&lt;/strong&gt;: &lt;code&gt;UserService&lt;/code&gt; can be easily tested by injecting a mock or stub of &lt;code&gt;DatabaseConnection&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object Lifetime Management&lt;/strong&gt;: The lifecycle of database connections can be managed more granularly (e.g., using context managers).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use of OOP Principles&lt;/strong&gt;: Demonstrates inheritance (abstract base class), polymorphism (implementing abstract methods), and protocols (interfaces defined by &lt;code&gt;DatabaseConnection&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By structuring the application using OOP and DI, the code becomes more modular, easier to test, and flexible to changes, such as swapping out dependencies or changing configurations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the App
&lt;/h2&gt;

&lt;p&gt;You can find all examples and more details with comments at the &lt;a href="https://github.com/MarkParker5/python-app-architecture-demo" rel="noopener noreferrer"&gt;repo&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting a new Project
&lt;/h3&gt;

&lt;p&gt;Just a short check-list:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Project and Dependency Management with Poetry
&lt;/h4&gt;

&lt;p&gt;Poetry shines not only as a project creation tool but also excels in managing dependencies and virtual environments. Start by setting up your project structure using the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

poetry new python-app-architecture-demo


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

&lt;/div&gt;

&lt;p&gt;This command crafts a well-organized directory structure: separate folders for Python code and tests, with a root directory for meta-information like &lt;code&gt;pyproject.toml&lt;/code&gt;, lock files, and git configurations.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Version Control with Git
&lt;/h4&gt;

&lt;p&gt;Initialize Git in your project directory:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

git init


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

&lt;/div&gt;

&lt;p&gt;Add a &lt;code&gt;.gitignore&lt;/code&gt; file to exclude unnecessary files from your repository. Use the standard Python &lt;code&gt;.gitignore&lt;/code&gt; provided by GitHub, and append any specific exclusions like &lt;code&gt;.DS_Store&lt;/code&gt; for macOS or IDE configs (&lt;code&gt;.idea&lt;/code&gt;, &lt;code&gt;.vscode&lt;/code&gt;, &lt;code&gt;.zed&lt;/code&gt;, etc):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

wget &lt;span class="nt"&gt;-O&lt;/span&gt; .gitignore https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore


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

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; .DS_Store &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  3. Manage Dependencies
&lt;/h4&gt;

&lt;p&gt;Install your project’s dependencies using Poetry:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

poetry add fastapi pytest aiogram


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

&lt;/div&gt;

&lt;p&gt;You can install all dependencies later using:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

poetry &lt;span class="nb"&gt;install&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Refer to the official documentation of each library if you need more specific instructions.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Configuration Files
&lt;/h4&gt;

&lt;p&gt;Create a &lt;code&gt;config.py&lt;/code&gt; file to centralize your application settings—a common and effective approach.&lt;/p&gt;

&lt;p&gt;Set environment variables for secrets and parameters:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;touch&lt;/span&gt; .env example.env


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

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; contains sensitive data and should be git-ignored, while &lt;code&gt;example.env&lt;/code&gt; holds placeholder or default values and is checked into the repository.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Application Entry Point
&lt;/h4&gt;

&lt;p&gt;Define the entry point of your application in &lt;code&gt;main.py&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;python_app_architecture/main.py:&lt;/em&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;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&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;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# avoid run on import
&lt;/span&gt;    &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Make your project usable as a library and allow programmatic access by importing the &lt;code&gt;run&lt;/code&gt; function in the &lt;code&gt;__init__.py&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;python_app_architecture/&lt;strong&gt;init&lt;/strong&gt;.py&lt;/em&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;.main&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Enable direct project execution with Poetry by adding a shortcut in &lt;code&gt;__main__.py&lt;/code&gt;. This allows you to use the command &lt;code&gt;poetry run python python_app_architecture&lt;/code&gt; instead of the longer &lt;code&gt;poetry run python python_app_architecture/main.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;python_app_architecture/&lt;strong&gt;main&lt;/strong&gt;.py:&lt;/em&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;.main&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;
&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Defining Directories and Layers
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Of course, every application is different and their architecture will differ depending on the goals and objectives. I'm not saying that this is the only correct option, but I think that this one is fairly average and suitable for a big part of projects. Try to focus on main approaches and ideas rather than specific examples.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, let's setup directories for different layers of the application. &lt;/p&gt;

&lt;p&gt;Typically, it's wise to version your API (e.g., by creating subdirectories like &lt;code&gt;api/v1&lt;/code&gt;), but we'll keep things simple for now and omit this step.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

.
├── python_app_architecture_demo
│   ├── coordinator.py
│   ├── entities
│   ├── general
│   ├── mappers
│   ├── providers
│   ├── repository
│   │   └── models
│   └── services
│       ├── api_service
│       │   └── api
│       │       ├── dependencies
│       │       ├── endpoints
│       │       └── schemas
│       └── telegram_service
└── tests


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The App&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entities&lt;/strong&gt; — App-wide data structures. Purely carriers of data with no embedded logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;General&lt;/strong&gt; — The utility belt! A consolidated hub for shared utilities, helpers, and library facades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mappers&lt;/strong&gt; — Specialists in data translation, converting between data forms like database models to entities or between different data formats. It's good practice to encapsulate mappers to its usage boundary instead of making them global. For example, models-entities mapper may be part of the repository module. Another example: schemas-entities mapper must stay inside api service and be its private tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Providers&lt;/strong&gt; — The backbone of core business logic. Providers implement the main logic of the application but remain agnostic of the interface details, ensuring their operations are abstract and isolated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repository&lt;/strong&gt; — The librarians. Custodians of data access, abstracting the complexities of data source interactions through models, thus isolating database operations from the broader app.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Models&lt;/strong&gt; — Definitions of local data structures interacting with the database, distinct and isolated from Entities.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Services&lt;/strong&gt; — Each service acts as a (almost) standalone sub-application, orchestrating its specific domain of business logic while delegating foundational tasks to providers. This configuration ensures centralized and uniform application-wide logic

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Service&lt;/strong&gt; — Manages external communications via HTTP/S, structured around a FastAPI framework.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies&lt;/strong&gt; — Essential tools and helpers required by various parts of your API, integrated via FastAPI’s DI system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endpoints&lt;/strong&gt; — Routes that publicly expose your business logic via HTTP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schemas&lt;/strong&gt; — Data structure definitions for API inputs and outputs, confined to their service layer to maintain encapsulation.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Telegram Service&lt;/strong&gt; — Operates similarly to the API Service, providing functionality via Telegram without replicating the core business logic. This is achieved by calling the same providers that API Service uses, ensuring consistency and reducing code redundancy.&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Tests&lt;/strong&gt; — Dedicated solely to testing, this directory contains all test code, maintaining a clear separation from application logic.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The connection between layers will look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fczbgeyjcf0bpdq6sw05r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fczbgeyjcf0bpdq6sw05r.jpg" width="492" height="1210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that &lt;em&gt;entities&lt;/em&gt; are not active components, but only data structures that are passed between layers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fix83dbxujh2s6w7pbxie.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fix83dbxujh2s6w7pbxie.jpg" width="800" height="856"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind that layers are not directly connected, but only depend on abstractions. Implementations are passed using dependency injection:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F8dtm1ciuqctocu2p2bef.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F8dtm1ciuqctocu2p2bef.jpg" width="586" height="1210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This flexible structure allows you to easily add functionality, for example, change the database, create a service, or connect a new interface without extra changes or code duplication, because logic of each module is located on its own layer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fz1hcage5cwibvpyla332.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fz1hcage5cwibvpyla332.jpg" width="800" height="978"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the same time, all logic of a separate service is encapsulated within it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Flwdfea7nfi5uqqgj2t1h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Flwdfea7nfi5uqqgj2t1h.jpg" width="800" height="928"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Exploring the Code
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Endpoint
&lt;/h4&gt;

&lt;p&gt;Let's start from the endpoint:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# api_service/api/endpoints/user.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;entities.user&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserCreate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;..dependencies.providers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user_provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 1
&lt;/span&gt;    &lt;span class="n"&gt;UserProvider&lt;/span&gt; &lt;span class="c1"&gt;# 2
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/register&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserCreate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 3
&lt;/span&gt;    &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UserProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_provider&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="c1"&gt;# 4
&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 5
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&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;User created!&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;Importing dependency injection helper function (we will look at it in a minute)&lt;/li&gt;
&lt;li&gt;Importing UserProvider &lt;strong&gt;protocol&lt;/strong&gt; for type annotation&lt;/li&gt;
&lt;li&gt;Endpoint requires the request's body to have &lt;code&gt;UserCreate&lt;/code&gt; scheme in json format&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;provider&lt;/code&gt; parameter in the &lt;code&gt;register&lt;/code&gt; function is an instance of implementation of &lt;code&gt;UserProvider&lt;/code&gt;, injected by FastAPI using the &lt;code&gt;Depends&lt;/code&gt; mechanism.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;create_user&lt;/code&gt; method of the &lt;code&gt;UserProvider&lt;/code&gt; is called with the parsed user data. This demonstrates a clear separation of concerns, where the API layer delegates business logic to the provider layer, adhering to the principle that interface layers should not contain business logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  UserProvider
&lt;/h4&gt;

&lt;p&gt;Now, let's see the business logic:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# providers/user_provider.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Protocol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;runtime_checkable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing_extensions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;runtime_checkable&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserRepository&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;providers.mail_provider&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MailProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;entities.user&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserCreate&lt;/span&gt;


&lt;span class="nd"&gt;@runtime_checkable&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Protocol&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# 1
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserCreate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="nd"&gt;@runtime_checkable&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProviderOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Protocol&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# 2
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_provider_created_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserCreate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProviderImpl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# 3
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 4 
&lt;/span&gt;        &lt;span class="n"&gt;mail_provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MailProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 4
&lt;/span&gt;        &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserProviderOutput&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 5
&lt;/span&gt;        &lt;span class="n"&gt;on_user_created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;UserCreate&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="c1"&gt;# 6
&lt;/span&gt;    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mail_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mail_provider&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_user_created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_user_created&lt;/span&gt;

    &lt;span class="c1"&gt;# Implementation
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserCreate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# 7
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 8
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mail_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 9
&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# unwraping the optional
&lt;/span&gt;            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_provider_created_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 10
&lt;/span&gt;
        &lt;span class="c1"&gt;# 11
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;on_user_created&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_user_created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;on_user_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&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;&lt;strong&gt;Defining the Interface&lt;/strong&gt;: &lt;code&gt;UserProvider&lt;/code&gt; is a protocol that specifies the method &lt;code&gt;create_user&lt;/code&gt;, which any class adhering to this protocol must implement. It serves as a formal contract for user creation functionality.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observer Protocol&lt;/strong&gt;: &lt;code&gt;UserProviderOutput&lt;/code&gt; serves as an observer (or delegate) that is notified when a user is created. This protocol enables loose coupling and enhances the event-driven architecture of the application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Protocol Implementation&lt;/strong&gt;: &lt;code&gt;UserProviderImpl&lt;/code&gt; implements the user creation logic but does not need to explicitly declare its adherence to &lt;code&gt;UserProvider&lt;/code&gt; due to Python's dynamic nature and the use of duck typing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Core Dependencies&lt;/strong&gt;: The constructor accepts &lt;code&gt;UserRepository&lt;/code&gt; and &lt;code&gt;MailProvider&lt;/code&gt;—both defined as protocols—as parameters. By relying solely on these protocols, the &lt;code&gt;UserProviderImpl&lt;/code&gt; remains decoupled from specific implementations, illustrating the principles of Dependency Injection where the provider is agnostic of the underlying details, interfacing only through defined contracts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optional Output Delegate&lt;/strong&gt;: The constructor accepts an optional &lt;code&gt;UserProviderOutput&lt;/code&gt; instance, which, if provided, will be notified upon the completion of user creation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Callback Function&lt;/strong&gt;: As an alternative to the output delegate, a callable &lt;code&gt;on_user_created&lt;/code&gt; can be passed to handle additional actions post-user creation, providing flexibility in response to events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Central Business Logic&lt;/strong&gt;: The &lt;code&gt;create_user&lt;/code&gt; method encapsulates the main business logic for adding a user, demonstrating separation from API handling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Repository Interaction&lt;/strong&gt;: Utilizes the &lt;code&gt;UserRepository&lt;/code&gt; to abstract the database operations (e.g., adding a user), ensuring the provider does not directly manipulate the database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extended Business Logic&lt;/strong&gt;: Involves sending an email through &lt;code&gt;MailProvider&lt;/code&gt;, illustrating that the provider's responsibilities can extend beyond simple CRUD operations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Event Notification&lt;/strong&gt;: If an output delegate is provided, it notifies this delegate about the user creation event, leveraging the observer pattern for enhanced interactivity and modular reactions to events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Callback Execution&lt;/strong&gt;: Optionally executes a callback function, providing a straightforward method to extend functionality without complex class hierarchies or dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  FastAPI Dependencies
&lt;/h4&gt;

&lt;p&gt;Ok, but how to instantiate the provider and inject it? Let's take a look at the injection code, powered by FastAPI's DI engine:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# services/api_service/api/dependencies/providers.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserRepository&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;providers.user_provider&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserProviderImpl&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;providers.mail_provider&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MailProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;coordinator&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Coordinator&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.database&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_coordinator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Coordinator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# private helper function
&lt;/span&gt;    &lt;span class="c1"&gt;# NOTE: You can pass the DIContainer in the same way
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinator&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_session&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="c1"&gt;# 1
&lt;/span&gt;    &lt;span class="n"&gt;coordinator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Coordinator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_get_coordinator&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="c1"&gt;# 2
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;UserProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# 3
&lt;/span&gt;    &lt;span class="c1"&gt;# UserProvider's lifecycle is bound to short endpoint's lifecycle, so it's safe to use strong references here
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;UserProviderImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;# 4
&lt;/span&gt;        &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;UserRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# 5
&lt;/span&gt;        &lt;span class="n"&gt;mail_provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MailProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mail_token&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# 6
&lt;/span&gt;        &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;coordinator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 7
&lt;/span&gt;        &lt;span class="n"&gt;on_user_created&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;coordinator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_user_created&lt;/span&gt; &lt;span class="c1"&gt;# 8
&lt;/span&gt;        &lt;span class="c1"&gt;# on_user_created: lambda: coordinator.on_user_created() # add a lambda if the method's signature is not compatible
&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;Getting a database session through FastAPI's dependency injection system, ensuring that each request has a clean session.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Obtaining an instance of &lt;code&gt;Coordinator&lt;/code&gt; from the application state, which is responsible for managing broader application-level tasks and acting as an event manager.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Note: the function returns protocol but not the exact implementation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Constructing an instance of &lt;code&gt;UserProviderImpl&lt;/code&gt; by injecting all the necessary dependencies. This demonstrates a practical application of dependency injection to assemble complex objects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Initializing &lt;code&gt;UserRepository&lt;/code&gt; with the session obtained from FastAPI's DI system. This repository handles all data persistence operations, abstracting the database interactions from the provider.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting up &lt;code&gt;MailProvider&lt;/code&gt; using a configuration token.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Injecting &lt;code&gt;Coordinator&lt;/code&gt; as the output protocol. This assumes that &lt;code&gt;Coordinator&lt;/code&gt; implements the &lt;code&gt;UserProviderOutput&lt;/code&gt; protocol, allowing it to receive notifications when a user is created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Assigns a method from &lt;code&gt;Coordinator&lt;/code&gt; as a callback to be executed upon user creation. This allows for additional operations or notifications to be triggered as a side effect of the user creation process.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This structured approach ensures that the &lt;code&gt;UserProvider&lt;/code&gt; is equipped with all necessary tools to perform its tasks effectively, while adhering to the principles of loose coupling and high cohesion.&lt;/p&gt;

&lt;h4&gt;
  
  
  Coordinator
&lt;/h4&gt;

&lt;p&gt;The Coordinator class acts as the main orchestrator within your application, managing various services, interactions, events, setting the initial state, and injecting dependencies. Here's a detailed breakdown of its roles and functionalities based on the provided code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# coordinator.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Thread&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;weakref&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uvicorn&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;services.api_service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_app&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;get_fastapi_app&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;entities.user&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserCreate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;repository.user_repository&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserRepository&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;providers.mail_provider&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MailProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;providers.user_provider&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserProviderImpl&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;services.report_service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ReportService&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;services.telegram_service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TelegramService&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Coordinator&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# 1
&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telegram_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TelegramService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;# 2
&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telegram_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;get_user_provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserProviderImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;UserRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;mail_provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MailProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mail_token&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;on_user_created&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_user_created&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ReportService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;get_users_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users_count&lt;/span&gt; &lt;span class="c1"&gt;# 3
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Coordinator's Interface
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_initial_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;fastapi_app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_fastapi_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;fastapi_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt; &lt;span class="c1"&gt;# 4
&lt;/span&gt;
        &lt;span class="c1"&gt;# 5
&lt;/span&gt;        &lt;span class="n"&gt;fastapi_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;uvicorn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fastapi_app&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;fastapi_thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# 6
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;telegram_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# UserProviderOutput Protocol Implementation
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_provider_created_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserCreate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_user_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Event handlers
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_user_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User created: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users_count&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;# 7
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;interval_seconds&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 8
&lt;/span&gt;

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

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Some state can be shared between different providers, services, layers, and the entire app&lt;/li&gt;
&lt;li&gt;Assembling implementations and injecting dependencies&lt;/li&gt;
&lt;li&gt;Be aware of circular references, deadlocks, and memory leaks here, see &lt;a href="https://github.com/MarkParker5/python-app-architecture-demo" rel="noopener noreferrer"&gt;the full code&lt;/a&gt; for detatils&lt;/li&gt;
&lt;li&gt;Pass the coordinator instance to the FastAPI app state so you can access it in the endpoints via FastAPI's DI system&lt;/li&gt;
&lt;li&gt;Start all services in separate threads&lt;/li&gt;
&lt;li&gt;Already runs in a separate thread inside the service&lt;/li&gt;
&lt;li&gt;Some cross-service logic here, just for example&lt;/li&gt;
&lt;li&gt;Example of controlling services from the coordinator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This orchestrator centralizes control and communication between different components, enhancing manageability and scalability of the application. It effectively coordinates actions across services, ensuring that the application responds appropriately to state changes and user interactions. This design pattern is crucial for maintaining a clean separation of concerns and enabling more robust and flexible application behavior.&lt;/p&gt;

&lt;h4&gt;
  
  
  DI Container
&lt;/h4&gt;

&lt;p&gt;However, in large-scale applications manual DI can lead to a significant amount of boilerplate code. This is when DI Container comes to rescue. DI Containers, or Dependency Injection Containers, are powerful tools used in software development to manage dependencies within an application. They serve as a central location where objects and their dependencies are registered and managed. When an object requires a dependency, the DI Container automatically handles the instantiation and provision of these dependencies, ensuring that objects receive all the necessary components to function effectively. This approach promotes loose coupling, enhances testability, and improves the overall maintainability of the codebase by abstracting the complex logic of dependency management away from the business logic of the application. DI Containers simplify the development process by automating and centralizing the configuration of component dependencies.&lt;/p&gt;

&lt;p&gt;For python, there are many libraries providing different DI Container implementations, I looked almost all of them and wrote down the best IMO&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://python-dependency-injector.ets-labs.org/introduction/di_in_python.html" rel="noopener noreferrer"&gt;python-dependency-injector&lt;/a&gt; — automated, class-based, has different lifecycle options like Singleton or Factory&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lagom-di.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;lagom&lt;/a&gt; — a dictionary interface with automatic resolve&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/reagento/dishka" rel="noopener noreferrer"&gt;dishka&lt;/a&gt; — good scope control via context manager&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/modern-python/that-depends" rel="noopener noreferrer"&gt;that-depends&lt;/a&gt; — support for context managers (objects required to be closed in the end), native fastapi integration&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bobthemighty/punq" rel="noopener noreferrer"&gt;punq&lt;/a&gt; — more classic approach with &lt;code&gt;register&lt;/code&gt; and &lt;code&gt;resolve&lt;/code&gt; methods&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Neoteroi/rodi" rel="noopener noreferrer"&gt;rodi&lt;/a&gt; — classic, simple, automatic&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  main.py
&lt;/h4&gt;

&lt;p&gt;For the end, let's update the main.py file:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="c1"&gt;# main.py&lt;br&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;coordinator&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Coordinator&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="c1"&gt;# entry point, no logic here, only run the coordinator&lt;br&gt;
&lt;/span&gt;    &lt;span class="n"&gt;coordinator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Coordinator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;br&gt;
    &lt;span class="n"&gt;coordinator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup_initial_state&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;&lt;strong&gt;name&lt;/strong&gt;&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&lt;strong&gt;main&lt;/strong&gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;br&gt;
    &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/p&gt;

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

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Conclusion&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;To gain a comprehensive understanding of the architectural and implementation strategies discussed, it is beneficial to review all files in the &lt;a href="https://github.com/MarkParker5/python-app-architecture-demo" rel="noopener noreferrer"&gt;repository&lt;/a&gt;. Despite the limited amount of code, each file is enriched with insightful comments and additional details that offer a deeper understanding of the application’s structure and functionality. Exploring these aspects will enhance your familiarity with the system, ensuring you are well-equipped to adapt or expand the application effectively.&lt;/p&gt;

&lt;p&gt;This approach is universally beneficial across various Python applications. It's effective for stateless backend servers such as those built with FastAPI, but its advantages are particularly pronounced in no-framework apps and applications that manage state. This includes desktop applications (both GUI and command-line), as well as systems that control physical devices like IoT devices, robotics, drones, and other hardware-centric technologies.&lt;/p&gt;

&lt;p&gt;Additionally, I highly recommend reading &lt;em&gt;Clean Code&lt;/em&gt; by Robert Martin for further enrichment. You can find a summary and key takeaways &lt;a href="https://gist.github.com/MarkParker5/6f47112b73d695127cdf5447213f3bb4" rel="noopener noreferrer"&gt;here&lt;/a&gt;. This resource will provide you with foundational principles and practices that are crucial for maintaining high standards in software development.&lt;/p&gt;

</description>
      <category>python</category>
      <category>development</category>
      <category>tutorial</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>AI-powered Mobile App with Backend in Two Days + MVVMP Architecture Overview (Tutorial)</title>
      <dc:creator>Mark Parker</dc:creator>
      <pubDate>Mon, 27 May 2024 13:46:53 +0000</pubDate>
      <link>https://dev.to/markparker5/ai-powered-mobile-app-with-backend-in-two-days-mvvmp-architecture-overview-tutorial-mh1</link>
      <guid>https://dev.to/markparker5/ai-powered-mobile-app-with-backend-in-two-days-mvvmp-architecture-overview-tutorial-mh1</guid>
      <description>&lt;p&gt;Creating a Proof of Concept SwiftUI mobile app with clean MVVMP architecture and a small FastAPI backend.&lt;/p&gt;




&lt;p&gt;Previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/markparker5/how-we-built-an-ai-startup-in-a-weekend-hackathon-in-germany-2c3k"&gt;How We Built an AI Startup in a Weekend Hackathon in Germany&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/markparker5/dr-house-ai-diagnostician-in-your-phone-passing-the-startup-torch-to-capable-hands-13pf"&gt;House, MD - AI Diagnostician in Your Phone: Passing the Startup Torch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article delves into the nuts and bolts of creating a Proof of Concept (PoC) of ф mobile app built with the SwiftUI framework and a backend using FastAPI. As an extra, I'll demonstrate effective architecture patterns for SwiftUI apps, specifically MVVMP combined with SOLID principles and Dependency Injection (DI). For android, the code can be easily translated to Kotlin using Jetpack Compose Framework almost without changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Need a Backend
&lt;/h2&gt;

&lt;p&gt;Someone might say that you can just cram all the logic into the application, send requests to chatgpt directly and make a backendless app. And I agree, it is indeed possible (and I'll show it later), but the backend provides several important advantages.&lt;/p&gt;

&lt;p&gt;The backend serves as the backbone for any sophisticated app, especially those requiring secure data management, business logic processing, and service integration. Here’s why a robust backend is crucial:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;: A backend helps protect sensitive data and user authentication tokens from MITM (Man-in-the-Middle) attacks. It acts as a secure gateway between the user's device and the database or external services, ensuring that all data exchanges are encrypted and authenticated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Control Over Service Usage&lt;/strong&gt;: By managing APIs and user interactions through the backend, you can monitor and control how the app is used. This includes throttling to manage load, preventing abuse, and ensuring that resources are used efficiently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database Integration&lt;/strong&gt;: A backend allows for seamless integration with databases, enabling dynamic data storage, retrieval, and real-time updates. This is essential for apps that require user accounts, store user preferences, or need to retrieve large amounts of data quickly and securely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Subscription and Freemium Models&lt;/strong&gt;: Implementing subscription services or a freemium model requires a backend to handle billing, track usage, and manage user tiers. The backend can securely process payments and subscriptions, providing a seamless user experience while ensuring compliance with data protection regulations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scalability and Maintenance&lt;/strong&gt;: With a backend, you can scale your application more effectively. Server-side logic can be updated without needing to push updates to the client, facilitating easier maintenance and quicker rollouts of new features.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In essence, a backend is not just about functionality — it's about creating a secure, scalable, and sustainable environment for your app to thrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explaining the Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SwiftUI&lt;/strong&gt;: The go-to for native iOS apps now that UIKit is on its way out. It's declarative and streamlined, with XCode as the indispensable editor. For android, the code can be easily translated to Kotlin using Jetpack Compose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt;: Chosen for the backend for its speed, minimal boilerplate, and declarative nature, edited with the superb Zed.dev.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ChatGPT API&lt;/strong&gt;: Used here as a large language model (LLM); choice may vary based on the need for customization (see &lt;a href="//github.com/HouseMDAI/house-notebook/blob/main/Technical%20Info.md"&gt;Technical Info&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ngrok&lt;/strong&gt;: Implements tunneling with a simple CLI command to expose your local server to the internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building the iOS App
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Theory: Architecture Patterns
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Model View ViewModel Presenter (MVVMP)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model&lt;/strong&gt;: Represents the data structures used in the app, such as Question, Answer, Questionary, and FilledQuestionary. These models are simple and only hold data, following the KISS principle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View&lt;/strong&gt;: SwiftUI views are responsible only for UI presentation and delegate all data and logic to presenters. They contain no business logic and are designed to be simple and focused on UI rendering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ViewModel&lt;/strong&gt;: In SwiftUI, ViewModel is represented by ObservableObject, which serves as a data-only observable model. No methods or logic here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Presenter&lt;/strong&gt;: The Presenter manages all logic related to the module (screen or view), but not the business logic. It communicates with the domain layer for business logic operations, such as interacting with APIs or managing data persistence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain Layer&lt;/strong&gt;: This layer encapsulates the business logic of the application and interacts with external resources such as databases, APIs, or other services. It consists of several components, such as Services, Providers, Managers, Repositories, Mappers, Factories, etc.&lt;/li&gt;
&lt;li&gt;Actually, the MP in MVVMP stands for Mark Parker and the full form is "Model View ViewModel by Mark Parker"&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SOLID Principles&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single-responsibility Principle&lt;/strong&gt;: Each class should have only one reason to change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-closed Principle&lt;/strong&gt;: Components should be open for extension but closed for modification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liskov Substitution Principle&lt;/strong&gt;: Objects of a superclass should be replaceable with objects of subclasses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface Segregation Principle&lt;/strong&gt;: No client should be forced to depend on interfaces it doesn't use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Inversion Principle&lt;/strong&gt;: Depend on abstractions, not concretes, facilitated by DI.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dependency Injection (DI)&lt;/strong&gt;: a programming technique in which an object or function receives other objects or functions that it requires, as opposed to creating them internally.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Drafting the Backend
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/HouseMDAI/house-backend/blob/master/backend" rel="noopener noreferrer"&gt;backend's code&lt;/a&gt; is quite simple. Endpoints (main.py):&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;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DoctorResponseAnswer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DoctorResponseQuestionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.user_card&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserCardSimple&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.prompting&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;


&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/onboarding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DoctorResponseQuestionary&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;onboarding&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;DoctorResponseQuestionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;UserCardSimple&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__fields__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/doctor&lt;/span&gt;&lt;span class="sh"&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;doctor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserCardSimple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filled_questionary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;(...)):&lt;/span&gt;
    &lt;span class="n"&gt;json_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filled_questionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="n"&gt;loaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;loaded&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;There are two endpoints. The "onboarding" provides a list of anamnesis questions that needs to be filled at the first launch of the app. Answers will be stored on the device and used for personalized future diagnosis. The "doctor" is the main endpoint: it generates questions based on earlier answers and user's card, or returns the result of diagnosis. &lt;/p&gt;

&lt;p&gt;Models:&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;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;filled_questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&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;DoctorResponseAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DoctorResponseQuestionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&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;UserCardSimple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;sex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;special_conditions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Prompting:&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;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&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;FilledQuestionary&lt;/span&gt;


&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_key&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;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filled_questionary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;format_question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;questions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;},{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;second question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}]}&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;format_advice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Advice: Drink more water&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    You are a doctor that gives user an opportunity to swiftly check up health and diagnos an illness using anamnes and a short questionary. 
    Your task is to ask short questions and give your opinion and advices.
    Your questions are accamulated in the filled questionary, which is empty in the first itteration. 

    Strive to about 1-2 questions per iteration and up to 6 questions in total (can be less). Questions must be short, clear, shouldn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t repeat, 
    and should be relevant to the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s health condition, and should require easy answers.
    Ask questions only in the json format &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;format_question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.

    Number of answered questions: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filled_questionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filled_questions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
    If the Number of answered questions is more then 6, you should stop asking questions an`d provide an give your final opinion, 
    an assumption or an advice in the json format &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;format_advice&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;request message: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;; anamnesis: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_card&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;; filled questionary: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filled_questionary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;;&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;chat_completion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;messages&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;role&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;system&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="si"&gt;}&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="si"&gt;}&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="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-3.5-turbo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;max_tokens&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;chat_completion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;

&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;prompting&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;utilizes&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s GPT-3.5 to generate responses based on user input, anamnesis, and filled questionnaires. It prompts the user with relevant questions and advice for health diagnosis. As you can see, there is nothing complicated here. The code is elementary, and the prompt is just a set of clear instructions for the LLM. 

Setup the env and run the server using `fastapi dev main.py`.

Details: 
- fastapi.tiangolo.com/tutorial/first-steps
- pypi.org/project/openai/

### Making Localhost Accessible Over the Internet

1. Sign up at ngrok.com and get an access token.
2. Install ngrok from ngrok.com/download.
3. Run `ngrok config add-authtoken &amp;lt;TOKEN&amp;gt;`.
4. Start the service with `ngrok http http://localhost:8080` (adjust the port as necessary).

Find detailed setup instructions at [ngrok documentation](https://ngrok.com/docs/getting-started).

### Coding the App

I won&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;entire&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;what&lt;/span&gt; &lt;span class="n"&gt;GitHub&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Find&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HouseMDAI&lt;/span&gt; &lt;span class="n"&gt;iOS&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;HouseMDAI&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;house&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ios&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;HouseMDAI&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt; &lt;span class="n"&gt;Instead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ll focus only on the important (IMO) points.

Let&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;quick&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;we&lt;/span&gt; &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;textfield&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;home&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ability&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;ask&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Also&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;we&lt;/span&gt; &lt;span class="n"&gt;require&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="n"&gt;onboarding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Okay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s code.

First thing first, we need some models, and they are pretty simple (KISS principle).
```swift
struct Question {
    var text: String
}

struct Answer {
    var text: String
}

struct Questionary {
    var questions: [Question]
}

struct FilledQuestionary {
    var filledQuestions: [String: String]
}
```

Now, let&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;onboarding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Keep&lt;/span&gt; &lt;span class="n"&gt;following&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;KISS&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nc"&gt;SRP &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Single&lt;/span&gt; &lt;span class="n"&gt;Responsibility&lt;/span&gt; &lt;span class="n"&gt;Principle&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;business&lt;/span&gt; &lt;span class="n"&gt;logic&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt; &lt;span class="n"&gt;either&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;UI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;just&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;questions&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;scroll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;All&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;logic&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;delegated&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;presenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;interesting&lt;/span&gt; &lt;span class="n"&gt;thing&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="n"&gt;helper&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="sb"&gt;`bindingForQuestion`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;probably&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;presenter&lt;/span&gt; &lt;span class="n"&gt;but&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;doesn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t matter now.

```swift
import SwiftUI

struct OnboardingView: View {

    @StateObject var presenter: OnboardingPresenter

    var body: some View {
        ScrollView {
            Spacer()
            VStack {
                ForEach(presenter.questions.questions) { question in
                    VStack {
                        Text(question.text)
                        TextField(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="s"&gt;, text: bindingForQuestion(question))
                            .formItem()
                    }
                    .padding()
                }
            }.padding()

            Button(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Save&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, action: presenter.save)
            Spacer()
        }
    }

    private func bindingForQuestion(_ question: Question) -&amp;gt; Binding&amp;lt;String&amp;gt; {
        Binding(
            get: { presenter.answers.filledQuestions[question.text] ?? &lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="s"&gt; },
            set: { presenter.answers.filledQuestions[question.text] = $0 }
        )
    }
}
```

You will be surprised, but there is no business logic in the presenter either!

```swift
class OnboardingPresenter: ObservableObject {

    @Published public var answers: FilledQuestionary
    private(set) public var questions: Questionary
    private var completion: (FilledQuestionary) -&amp;gt; Void

    init(questions: Questionary, answers: FilledQuestionary, completion: @escaping (FilledQuestionary) -&amp;gt; Void) {
        self.questions = questions
        self.answers = answers
        self.completion = completion
    }

    func save() {
        completion(answers)
    }
}
```

 Still everything is *simple, stupid*, and have only a *single responsibility*. Presenter must contain only the logic of its view. App-level business-logic is out of its jurisdiction, so the presenter is just delegating it to the top.

Also, you can see that both View and Presenter don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;instantiate&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="n"&gt;but&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt; &lt;span class="n"&gt;them&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;follows&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Dependency&lt;/span&gt; &lt;span class="n"&gt;Inversion&lt;/span&gt; &lt;span class="n"&gt;Principle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;depend&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;but&lt;/span&gt; &lt;span class="n"&gt;both&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="n"&gt;depend&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;abstractions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;allows&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;flexibility&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;easier&lt;/span&gt; &lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;well&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;making&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;straightforward&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;replace&lt;/span&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;inject&lt;/span&gt; &lt;span class="n"&gt;mocks&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;testing&lt;/span&gt; &lt;span class="n"&gt;purposes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Dependency&lt;/span&gt; &lt;span class="n"&gt;Injection&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;provided&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;outside&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;rather&lt;/span&gt; &lt;span class="n"&gt;than&lt;/span&gt; &lt;span class="n"&gt;being&lt;/span&gt; &lt;span class="n"&gt;instantiated&lt;/span&gt; &lt;span class="n"&gt;internally&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;promotes&lt;/span&gt; &lt;span class="n"&gt;decoupling&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;allows&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;easier&lt;/span&gt; &lt;span class="n"&gt;maintenance&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;Although&lt;/span&gt; &lt;span class="n"&gt;protocols&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;explicitly&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s worth mentioning that protocols can play a crucial role in code, especially for abstraction and easier testing. By defining protocols for views, presenters, and dependencies, it becomes easier to swap out implementations or provide mocks during testing.

&amp;gt; If you&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt; &lt;span class="n"&gt;considering&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;protocols&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SwiftUI&lt;/span&gt; &lt;span class="n"&gt;Views&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;there&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s an important consideration to keep in mind. Since View in SwiftUI is a structure, it requires explicit specification of its property types. This means you&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;ll&lt;/span&gt; &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;generic&lt;/span&gt; &lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="n"&gt;through&lt;/span&gt; &lt;span class="nb"&gt;all&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resulting&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;lot&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;boilerplate&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;However&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;there&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s an alternative approach offered by [MarkParker5/AnyObservableObject](https://github.com/MarkParker5/AnyObservableObject). This library works similarly to native SwiftUI property wrappers but removes the compile-time type check in favor of a runtime one. While this approach may introduce some risks, they are easily mitigated by writing elementary Xcode tests that simply instantiate the views in the same way you do it at runtime.
&amp;gt; 
&amp;gt; By using this alternative, you can simplify your code and streamline the process of working with protocols in SwiftUI Views.

So, if the presenter doesn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;contain&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;business&lt;/span&gt; &lt;span class="n"&gt;logic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;who&lt;/span&gt; &lt;span class="n"&gt;does&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;usually&lt;/span&gt; &lt;span class="n"&gt;contains&lt;/span&gt; &lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;Managers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;They&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="n"&gt;very&lt;/span&gt; &lt;span class="n"&gt;similar&lt;/span&gt; &lt;span class="n"&gt;destiny&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;difference&lt;/span&gt; &lt;span class="n"&gt;between&lt;/span&gt; &lt;span class="n"&gt;them&lt;/span&gt; &lt;span class="n"&gt;still&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;discussions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Let&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s create the `OnboardingProvider` that will contain all business-logic of the onboarding process.

```swift
class OnboardingProvider: ObservableObject {

    init() {
        loadFilledOnboardingFromDefaults()
    }

    // MARK: Interface

    @Published private(set) var needsOnboarding: Bool = true

    private(set) var filledOnboarding: FilledQuestionary? {
        didSet {
            if let filledOnboarding {
                saveFilledOnboardingToDefaults(filledQuestionary: filledOnboarding)
            }
        }
    }

    func getOnboardingQuestionary() -&amp;gt; Questionary {
        // NOTE: it&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;better&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;take&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;questions&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;
        &lt;span class="nc"&gt;Questionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;height&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;special_conditions&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="n"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;saveOnboardingAnswers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filledQuestionary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;needsOnboarding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;
        &lt;span class="n"&gt;filledOnboarding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filledQuestionary&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;MARK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Private&lt;/span&gt;

    &lt;span class="n"&gt;private&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;saveFilledOnboardingToDefaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filledQuestionary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;UserDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filledOnboarding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JSONEncoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filledQuestionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;UserDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filledOnboarding&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;private&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;loadFilledOnboardingFromDefaults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UserDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filledOnboarding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;needsOnboarding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;savedFilledQuestionary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;loadedQuestionary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="n"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;self&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="n"&gt;savedFilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filledOnboarding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loadedQuestionary&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;needsOnboarding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;

&lt;span class="n"&gt;Again&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;handles&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="n"&gt;responsibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;managing&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;business&lt;/span&gt; &lt;span class="n"&gt;logic&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;onboarding&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;encapsulation&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;allows&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;interact&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;without&lt;/span&gt; &lt;span class="n"&gt;needing&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;worry&lt;/span&gt; &lt;span class="n"&gt;about&lt;/span&gt; &lt;span class="n"&gt;its&lt;/span&gt; &lt;span class="n"&gt;internal&lt;/span&gt; &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;promoting&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;cleaner&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt; &lt;span class="n"&gt;maintainable&lt;/span&gt; &lt;span class="n"&gt;codebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s put everything together in the entry point.
```swift
import SwiftUI

@main
struct HouseMDAI: App {

    @StateObject private var onboardingProvider: OnboardingProvider
    @StateObject private var onboardingPresenter: OnboardingPresenter
    @StateObject private var homePresenter: HomePresenter

    init() {
        let onboardingProvider = OnboardingProvider()

        let onboardingPresenter = OnboardingPresenter(
            questions: onboardingProvider.getOnboardingQuestionary(),
            answers: FilledQuestionary(filledQuestions: [:]),
            completion: onboardingProvider.saveOnboardingAnswers
        )

        let homePresenter = HomePresenter()

        _onboardingProvider = StateObject(wrappedValue: onboardingProvider)
        _onboardingPresenter = StateObject(wrappedValue: onboardingPresenter)
        _homePresenter = StateObject(wrappedValue: homePresenter)
    }

    var body: some Scene {
        WindowGroup {
            if onboardingProvider.needsOnboarding {
                OnboardingView(presenter: onboardingPresenter)
            } else {
                TabView {
                    HomeView(presenter: homePresenter)

                    if let profile = onboardingProvider.filledOnboarding {
                        ProfileView(profile: profile)
                    }
                }
            }
        }
    } // body
}
```

This SwiftUI app sets up its initial state using `StateObject` property wrappers. It initializes an `OnboardingProvider`, `OnboardingPresenter`, and `HomePresenter` in its init method. The `OnboardingProvider` is responsible for managing onboarding-related data, while the `OnboardingPresenter` handles the logic for the onboarding view. The `HomePresenter` manages the main home view.

The body of the app&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;scene&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;onboarding&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;needed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;presents&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="sb"&gt;`OnboardingView`&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="sb"&gt;`OnboardingPresenter`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Otherwise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;presents&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="sb"&gt;`TabView`&lt;/span&gt; &lt;span class="n"&gt;containing&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="sb"&gt;`HomeView`&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="sb"&gt;`HomePresenter`&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="sb"&gt;`ProfileView`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;Now&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s time for the home view. The logic is simple:
1. Get a message from user
2. Using the message, request a list of questions from the backend 
3. Show the questions one by one using the native push navigation
4. Add answers to the request and repeat 2-4 until the backend-doctor returns a final result
5. Show the final result

```swift
struct HomeView: View {

    @StateObject var presenter: HomePresenter

    var body: some View {
        NavigationStack(path: $presenter.navigationPath) {
            VStack {
                // 1
                Text(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How are you?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)
                TextField(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, text: $presenter.message)
                    .lineLimit(5...10)
                    .formItem()

                // 2
                Button(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Send&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, action: presenter.onSend)
            }
            .padding()
            .navigationDestination(for: NavigationPage.self) { page in
                switch page {
                case .questinary(let questions, let answers):
                    // 3
                    QuestionaryView(
                        presenter: QuestionaryPresenter(
                            questions: questions,
                            answers: answers,
                            completion: presenter.onQuestionaryFilled
                        )
                    )
                case .answer(let string):
                    // 5
                    VStack {
                        Text(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The doctor says...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)
                        Text(string)
                            .font(.title2)
                            .padding()
                    }
                }
            }
        }
    }
}
```

Looks like I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;ve&lt;/span&gt; &lt;span class="n"&gt;missed&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Since&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t content any logic, this part in handled by its presenter.

```swift
enum NavigationPage: Hashable {
    case questinary(Questionary, FilledQuestionary)
    case answer(String)
}

class HomePresenter: ObservableObject {

    @Published var message: String = &lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="s"&gt;
    @Published var navigationPath: [NavigationPage] = []

    init(message: String = &lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="s"&gt;) {
        self.message = message
    }

    func onSend() {
        Task {
            let doctor = DoctorProvider()
            let answer = try! await doctor.sendMessage(message: message)

            switch answer {
            case .questions(let questions):
                navigationPath.append(.questinary(questions, FilledQuestionary(filledQuestions: [:])))
            case .answer(let string):
                navigationPath.append(.answer(string))
            }
        }
    }

    func onQuestionaryFilled(filled: FilledQuestionary) {
        Task {
            let doctor = DoctorProvider()
            let answer = try! await doctor.sendAnswers(message: message, answers: filled)

            switch answer {
            case .questions(let newQuestions):
                navigationPath.append(.questinary(newQuestions, filled))
            case .answer(let string):
                navigationPath.append(.answer(string))
            }
        }
    }
}
```

It manages the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;updates&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;navigation&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="n"&gt;based&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;Upon&lt;/span&gt; &lt;span class="n"&gt;sending&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="sb"&gt;`onSend()`&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="n"&gt;sends&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="sb"&gt;`DoctorProvider`&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;awaits&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Depending&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;updates&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;navigation&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;either&lt;/span&gt; &lt;span class="n"&gt;display&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;questions&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;Similarly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;questionary&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="sb"&gt;`onQuestionaryFilled()`&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="n"&gt;sends&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;filled&lt;/span&gt; &lt;span class="n"&gt;questionary&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;updates&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;navigation&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="n"&gt;accordingly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;There&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s a slight code duplication here between the `onSend()` and `onQuestionaryFilled()` methods, which could be refactored into a single method to handle both cases. However, this is left as an exercise for further refinement.

The Questionary module (View+Presenter) is almost a copy of the Onboarding and simply delegates the logic up to `HomePresenter`, so I don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;see&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Again&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;there&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="n"&gt;things&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt; &lt;span class="n"&gt;implementations&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="sb"&gt;`DoctorProvider`&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;responsibility&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`DoctorResponse`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="n"&gt;our&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;swift&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Alamofire&lt;/span&gt;

&lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;DoctorResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Questionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;let&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;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&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="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;questions&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
                &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="nc"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Questionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;self&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="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="se"&gt;\"&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;let&lt;/span&gt; &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="nc"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;self&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="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;NSError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DoctorResponseError&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NSLocalizedDescriptionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown response format&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;NSError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DoctorResponseError&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NSLocalizedDescriptionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid string encoding&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;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DoctorProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;private&lt;/span&gt; &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

    &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DoctorResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendAnswers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filledQuestions&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;func&lt;/span&gt; &lt;span class="nf"&gt;sendAnswers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DoctorResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DoctorParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Codable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;
            &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userCard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;filledQuestionary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FilledQuestionary&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;onboard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OnboardingProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;paramsObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DoctorParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;userCard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;onboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filledOnboarding&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filledQuestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;filledQuestionary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JSONParameterEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;
        &lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keyEncodingStrategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convertToSnakeCase&lt;/span&gt;

        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;responseString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;AF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/doctor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;paramsObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;serializingString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="nc"&gt;DoctorResponse&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="n"&gt;responseString&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="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;

&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="nf"&gt;directly &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backendless&lt;/span&gt; &lt;span class="n"&gt;approach&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;almost&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;prompting&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;swift&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PromptsProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;private&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;public&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;homeRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;take&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;

    &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&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="n"&gt;String&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;message&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;String&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;try&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;String&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;try&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;MARK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Private&lt;/span&gt;

    &lt;span class="n"&gt;private&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Encodable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;coder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JSONEncoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;coder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;??&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HouseMDAIProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;private&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;openAI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;openAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiToken&lt;/span&gt;&lt;span class="p"&gt;:&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;func&lt;/span&gt; &lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DoctorResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendAnswers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filledQuestions&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;func&lt;/span&gt; &lt;span class="nf"&gt;sendAnswers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FilledQuestionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DoctorResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;NOTE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Draft&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DI&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="n"&gt;instead&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;promptProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PromptsProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OnboardingProvider&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;filledOnboarding&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;

        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatQuery&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gpt3_5Turbo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;promptProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;homeRole&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;promptProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="nc"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;promptProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="nc"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;promptProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;openAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&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;try&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="nc"&gt;DoctorResponse&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="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="err"&gt;??&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="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;

&lt;span class="n"&gt;Both&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt; &lt;span class="n"&gt;expose&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;same&lt;/span&gt; &lt;span class="n"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="n"&gt;they&lt;/span&gt; &lt;span class="nf"&gt;can &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;implement&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;same&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;easily&lt;/span&gt; &lt;span class="n"&gt;interchangeable&lt;/span&gt; &lt;span class="n"&gt;thanks&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;DI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;We&lt;/span&gt; &lt;span class="n"&gt;didn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t get around to this during development, so let this also remain homework.
### Another Example

Explore a more refined example of this architecture in my project TwiTreads at [github.com/MarkParker5/TwiTreads](https://github.com/MarkParker5/TwiTreads)

### What to Do Next

- Integrate authentication and user database into the backend. Utilize the official FastAPI&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FastAPI&lt;/span&gt; &lt;span class="n"&gt;Project&lt;/span&gt; &lt;span class="n"&gt;Generation&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;fastapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tiangolo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Implement&lt;/span&gt; &lt;span class="n"&gt;authentication&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Focus&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;enhancing&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s design to improve user experience. Let&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;beautiful&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;

&lt;span class="c1"&gt;## Conclusion
&lt;/span&gt;
&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt; &lt;span class="n"&gt;examples&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;jumpstart&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;own&lt;/span&gt; &lt;span class="n"&gt;development&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Remember&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;beauty&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;technology&lt;/span&gt; &lt;span class="n"&gt;lies&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;iteration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Start&lt;/span&gt; &lt;span class="n"&gt;simple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;continuously&lt;/span&gt; &lt;span class="n"&gt;refine&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Each&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="n"&gt;forward&lt;/span&gt; &lt;span class="n"&gt;brings&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;closer&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;mastering&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;art&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;software&lt;/span&gt; &lt;span class="n"&gt;development&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;potentially&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt; &lt;span class="n"&gt;big&lt;/span&gt; &lt;span class="n"&gt;breakthrough&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tech&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;  &lt;span class="n"&gt;Happy&lt;/span&gt; &lt;span class="n"&gt;coding&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>mobile</category>
      <category>tutorial</category>
      <category>ai</category>
      <category>development</category>
    </item>
    <item>
      <title>Dr. House — AI Diagnostician in your phone. Passing the Startup Torch to Capable Hands</title>
      <dc:creator>Mark Parker</dc:creator>
      <pubDate>Wed, 22 May 2024 11:36:13 +0000</pubDate>
      <link>https://dev.to/markparker5/dr-house-ai-diagnostician-in-your-phone-passing-the-startup-torch-to-capable-hands-13pf</link>
      <guid>https://dev.to/markparker5/dr-house-ai-diagnostician-in-your-phone-passing-the-startup-torch-to-capable-hands-13pf</guid>
      <description>&lt;p&gt;Introducing our healthcare app and passing the startup torch to capable hands. Now open source.&lt;/p&gt;




&lt;p&gt;This article picks up where the previous one left off, &lt;a href="https://dev.to/markparker5/how-we-built-an-ai-startup-in-a-weekend-hackathon-in-germany-2c3k"&gt;How We Built an AI Startup in a Weekend Hackathon in Germany&lt;/a&gt;, focusing more on the final product rather than the hackathon process itself.&lt;/p&gt;

&lt;p&gt;When you're creating an AI product about health, there is always a question of ethics, accuracy, responsibility, and trust. That's why it's important to separate healthy lifestyle products and real medical ones. We thought long and hard about the line between them and how close we can get to it. It's a complex question, but we found a solution: create two standalone products that are far away from the line but on opposite sides.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Best Friend"
&lt;/h3&gt;

&lt;p&gt;The first one is about a healthy lifestyle. It's your virtual best friend, and even more: assistant, coach, motivator, sometimes even your mom or a mini psychologist. By collecting essential information about you from the chat and by having in memory your anamnesis, bio, and history, the best friend can just listen to how you spend your day, comment on it, help you with everyday advice and suggestions, or even help you move towards your goals and dreams using personalized motivation, human sympathy, and understanding your feelings.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Dr. House"
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Problem
&lt;/h4&gt;

&lt;p&gt;The second product is a real medical one. Let's begin with the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The complexity of the diagnostic process itself due to the complex structure of the human body and the huge number of possible cases that cannot always be completely treated by a doctor&lt;/li&gt;
&lt;li&gt;Overburdened healthcare systems and professionals&lt;/li&gt;
&lt;li&gt;Inefficient and delayed diagnosis in healthcare, leading to worsened patient outcomes.&lt;/li&gt;
&lt;li&gt;Accessibility issues for remote or underserved populations&lt;/li&gt;
&lt;li&gt;People's reluctance to visit hospitals, fear of doctors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Diagnosis is important
&lt;/h4&gt;

&lt;p&gt;Why is diagnosis so critical? Einstein is quoted as having said, “If I had an hour to solve a problem, I'd spend 55 minutes thinking about the problem and five minutes thinking about solutions.” The point he makes is significant: preparation has great value to problem-solving. The same holds true in medicine. Professionals say: correct diagnosis is ~70% of healing. And we believe we can improve 70% of modern medicine via a single AI product.&lt;/p&gt;

&lt;h4&gt;
  
  
  Implementation
&lt;/h4&gt;

&lt;p&gt;Meet Dr. House — AI diagnostician in your phone. A mobile app that provides AI-powered diagnosis in minutes.&lt;/p&gt;

&lt;p&gt;Now, how are we going to achieve this? From the beginning of time until now, we believe that the best way of communication is speech. If you want to know something — ask questions. And by asking questions, I mean asking &lt;strong&gt;correct&lt;/strong&gt; questions. So, we're going to make an app that will have your full anamnesis and will ask correct personalized questions and based on the answers suppose possible diagnoses. Sounds pretty simple but helpful, isn't it? And much better than googling some of your symptoms (I have cancer every time).&lt;/p&gt;

&lt;p&gt;And it helps not only ordinary people. Doctors can also use this for double-checks, second opinions, or preliminary prognoses, for example in an ambulance.&lt;/p&gt;

&lt;p&gt;Also, when Dr. House recommends a visit to the hospital, the process will be significantly faster because the app already has complete anamnesis, medical history, and a filled questionnaire, so both the customer and doctor need to spend significantly less time. And the same app can be used as an emergency card if the user is “offline”.&lt;/p&gt;

&lt;h4&gt;
  
  
  Can do better?
&lt;/h4&gt;

&lt;p&gt;Can it be even better? We say yes! How? Integration! The app can also take into account info from Apple's HealthKit and medical devices. Or vice versa, the API can transfer data to the hospital (and vice versa) even without direct human contact.&lt;/p&gt;

&lt;p&gt;If the app monitors one's health, it means the person can be sent home from the hospital much earlier and free up space for somebody who requires it more. Doctors will monitor the status remotely, and the app will notify them if something is wrong.&lt;/p&gt;

&lt;p&gt;Also, if we want ordinary people to use this app, we should add a better description of diseases using simple words; translate from medical to English.&lt;/p&gt;

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

&lt;p&gt;Unfortunately, our team does not have the opportunity to work on this startup full-time, so we published all the results of our work in the public domain. You can find the slides for this article &lt;a href="https://github.com/HouseMDAI/house-notebook/blob/main/Presentation%20-%20slides%20only.pdf" rel="noopener noreferrer"&gt;here&lt;/a&gt; . For full details on the project, including the source code and the custdev, check the &lt;a href="https://github.com/HouseMDAI" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>opensource</category>
      <category>mobile</category>
      <category>ai</category>
    </item>
    <item>
      <title>How We Built an AI Startup in a Weekend Hackathon in Germany</title>
      <dc:creator>Mark Parker</dc:creator>
      <pubDate>Tue, 21 May 2024 15:49:20 +0000</pubDate>
      <link>https://dev.to/markparker5/how-we-built-an-ai-startup-in-a-weekend-hackathon-in-germany-2c3k</link>
      <guid>https://dev.to/markparker5/how-we-built-an-ai-startup-in-a-weekend-hackathon-in-germany-2c3k</guid>
      <description>&lt;p&gt;In this article, I share 8 basic steps to create a startup using our project as an example. All sources are open.&lt;/p&gt;




&lt;p&gt;Here's a rundown of my weekend at a Cologne hackathon, where we aimed to start an AI startup in just two days. We went from pitching ideas on Friday night to demoing a working app by Sunday. It involved coding late into the night, figuring out last-minute tech snags, and even putting together a presentation minutes before our demo. As a bonus, I have highlighted a to-do list of the main points for creating a startup.&lt;/p&gt;

&lt;p&gt;A few weeks ago, I found myself in Cologne for an electrifying AI Startup Hackathon hosted by &lt;a href="https://www.startplatz.de/en" rel="noopener noreferrer"&gt;Startplatz&lt;/a&gt;, situated in the heart of &lt;a href="https://de.wikipedia.org/wiki/Mediapark" rel="noopener noreferrer"&gt;MediaPark&lt;/a&gt;. The venue buzzed with ideas on the third floor of a vibrant office building, setting the stage for a weekend of innovative frenzy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Day 1: Ideation and Team Formation (Friday, 6pm to 10pm)
&lt;/h3&gt;

&lt;p&gt;The event kicked off with around 40 participants, each pitching their ideas anonymously. After a democratic voting process, the top eight concepts were selected for development. These ideas ranged from an AI for paramedics (our baby, dubbed "AI Dr. House") to something as quirky as AI evaluating scores in Warhammer — quite the spectrum!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Step 1. &lt;strong&gt;Start with an Idea&lt;/strong&gt;&lt;br&gt;
Pinpoint a unique concept that addresses an unsolved problem or improves on existing solutions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's a snapshot of the diversity and creativity showcased in the pitches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Paramedic AI (ours)&lt;/strong&gt;: Enhancing emergency medical responses with AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM without Hallucination&lt;/strong&gt;: Aiming to create a more reliable language model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge Management Through &amp;amp; With AI&lt;/strong&gt;: Harnessing AI to optimize information flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gesture Recognition for Contactless Playback Control&lt;/strong&gt;: Innovating user interfaces with computer vision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SchoolGPT&lt;/strong&gt;: Applying groundbreaking technology in educational settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluate Scores in Warhammer with AI&lt;/strong&gt;: Bringing AI to the fantasy battlefield.&lt;/li&gt;
&lt;li&gt;And a couple more, each brimming with potential.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Step 2. &lt;strong&gt;Determine the Pain You Solve&lt;/strong&gt;&lt;br&gt;
Identify the specific challenges your potential customers face and how your product can address these issues effectively.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each team then refined their ideas, weighing pros and cons, potential impacts, and business models. The day wrapped up with networking over pizza, setting a casual yet ambitious tone for the weekend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Night 1: Exploring Cologne
&lt;/h3&gt;

&lt;p&gt;While others retreated for the night, I embraced the Cologne nightlife, from bustling business districts to tranquil residential areas. My detour included culinary stops and riverside reflections. However, the relentless drive of a startup founder never truly powers down; I coded an early draft of our diagnostician app in Xcode. Later, duty called from my primary venture, &lt;a href="https://www.majordom.io" rel="noopener noreferrer"&gt;MajorDom&lt;/a&gt;, requiring a few dedicated hours in Jira to keep our other projects on track until dawn.&lt;/p&gt;

&lt;h3&gt;
  
  
  Day 2: Intensive Development and Unexpected Pivots (Saturday, 8am-10pm)
&lt;/h3&gt;

&lt;p&gt;Grateful for the coworking space's hospitality, I managed to sneak in a breakfast and an hour of 'meditative' closed-eye thinking before diving into the fray at 9:15am. &lt;/p&gt;

&lt;p&gt;Energized, we started with a lively warm-up session followed by an insightful lecture on customer development and product positioning. We immediately put this newfound knowledge to the test by drafting a questionnaire to better gauge public opinion and refine our project's direction.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CustDev: Define the Target Audience&lt;/strong&gt;
Understand who your customers are, what their needs are, and how they behave. Engage with them early through customer development interviews to refine your product's focus.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's also important to ask questions about the past and present, such as "How often do you visit the doctor?", but not questions about the future, such as "Would you use an app instead?". The first questions give a real picture, while the second are unreliable hallucinations. Also, don’t start an interview like “We’re developing an app to replace a doctor”, because then the conversation will become emotional and people will tailor the answers you want to hear instead of telling the truth, which you might not like. Instead, ask questions that are as neutral as possible.&lt;/p&gt;

&lt;p&gt;Some of our custdev interviews can be found &lt;a href="https://github.com/HouseMDAI/house-notebook/blob/main/CustDev%20(Customer%20Development).md" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, interviews with potential customers may give you the first FAQ list, which helps to improve the pitch. Our FAQ can be found &lt;a href="https://github.com/HouseMDAI/house-notebook/blob/main/FAQ.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Analyze Alternatives and Your Competitors&lt;/strong&gt;
Conduct thorough research on both direct competitors and adjacent market players. Understand their strengths, weaknesses, and how they meet customer needs.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Although we did not find any direct competitors, we still noted down other projects in this area that might be useful to us. The list is &lt;a href="https://github.com/HouseMDAI/house-notebook/blob/main/General%20(Other%20Thoughts%20and%20Notes).md" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Develop a UVP and a Pitch&lt;/strong&gt;
Craft a Unique Value Proposition that clearly articulates why your product is different and better than others. Perfect a compelling pitch for different stakeholders (investors, customers, partners).&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since we found no direct competitors, creating a unique offer was not difficult, so we simply described our updated vision.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Figure Out the Monetization Model and Business Model&lt;/strong&gt;
Determine how your business will make money — through subscriptions, ads, direct sales, etc. Decide whether you'll operate on a B2B (business-to-business), B2C (business-to-consumer), or another framework like SaaS (Software as a Service). Consider the scalability and potential for recurring revenue.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our project turned out to be quite flexible in terms of monetization, so we can experiment with different options: from SaaS to B2B partnership with hospitals. The list can be found &lt;a href="https://github.com/HouseMDAI/house-notebook/blob/main/Business%20Model.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Mid-morning brought a welcome surprise — two engineers from Microsoft dropped by to impart wisdom on data science, GPT models, and the broader AI landscape, sprinkling in valuable tips and tricks. Admittedly, I may have nodded off during the 20-minute segment on prompt engineering, but I assure you, the rest was captivating.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a PoC to Test the Idea&lt;/strong&gt;
Develop a Proof of Concept to demonstrate the feasibility of your idea in solving the problem at hand. Use this to gather feedback and make necessary adjustments.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;The bulk of the day was spent on development. While rival teams wrestled with GPTs and their APIs, our squad tackled ethical dilemmas, the nuances of European regulations, and the intricacies of prompt design, leading us to pivot from a strictly medical-focused app to a broader lifestyle application. Meantime, I was fighting the urge to fall asleep under the table, but continued writing code.&lt;/p&gt;

&lt;p&gt;By late afternoon, our proof of concept was nearly operational: a sleek SwiftUI app interface with a FastAPI backend utilizing the (not so) quick ChatGPT API. A pesky 422 validation error, caused by a slight mismatch in data models between the frontend and backend, was all that stood in our way. We opted to tackle this glitch after a good night's rest.&lt;/p&gt;

&lt;p&gt;We wrapped the day with more pizza and networking. Fortunately, this time I managed to secure a hotel room just a five-minute walk from the hackathon venue — finally, some well-deserved sleep was within reach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Day 3: Final Developments and Dual Demos (Sunday, 9am-2pm)
&lt;/h3&gt;

&lt;p&gt;The last day of development often involves refining and scaling ideas, but this time, the challenge was tougher. We were straddling the line between medical and lifestyle categories, and every tweak nudged us towards one side. I believe in creating products without limits or compromises — my philosophy is simple: if you don’t like it, don’t use it.&lt;/p&gt;

&lt;p&gt;A good night's sleep brought clarity and a bold new vision. First thing in the morning, I shared this breakthrough with the team, using a flip chart to map out our strategy. We decided to split our efforts into two distinct apps: one strictly medical, staying true to our original concept, and another purely for lifestyle purposes.&lt;/p&gt;

&lt;p&gt;We divided into two subteams of two each to tackle the projects. The medical app and its backend were quickly brought up to speed and stabilized. Thanks to Ngrok, we had a public URL for our backend, bringing "Dr. House" to life just in time for the demo. Meanwhile, the lifestyle-focused team used a more straightforward approach with the partyrock constructor from aws to finalize the "Best Friend AI" app (the demo is &lt;a href="https://partyrock.aws/u/wefersmasasana/sVROkXP6p/HealthcareHelper/snapshot/eKapV8IL7" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;With only 20 minutes to the curtain call, we realized we hadn't prepared a formal presentation — ironically, that had been our morning's mission! With the clock ticking, we rapidly assembled a coherent plan and cobbled together several impactful keynote slides. &lt;/p&gt;

&lt;p&gt;The challenge intensified as each team was allocated just five minutes for their pitch and an additional five for questions — hardly enough to cover one product, let alone two. The clock wound down right before the demo, but we were lucky (or we planned it, nobody knows): the first question from the audience was to show the demo. This "accidental" timing allowed us to sneak in some extra minutes under the guise of answering queries. We used airplay to broadcast a live screencast from an iphone to the stage screen via my macbook, turning our presentation into an engaging showcase that resonated well with the audience. It was not only a demonstration of our apps' capabilities but also a testament to quick thinking and adaptability under pressure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Develop and Launch the MVP Using the Pareto (80-20) Principle&lt;/strong&gt;
Focus on the 20% of features that deliver 80% of the value. Quickly develop and launch a Minimum Viable Product to start the learning cycle, aiming to provide maximum value while spending minimum time and resources. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember, the beauty of technology lies in iteration. Start simple, build a prototype, and continuously refine it. Each step forward brings you closer to mastering the art of software development and potentially the next big breakthrough in tech.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unfortunately, our team does not have the opportunity to work on this startup full-time, so we published all the results of our work in the public domain. You can find the slides &lt;a href="https://github.com/HouseMDAI/house-notebook/blob/main/Presentation%20-%20slides%20only.pdf" rel="noopener noreferrer"&gt;here&lt;/a&gt; and the text of my speech &lt;a href="https://github.com/HouseMDAI/house-notebook/blob/main/README.md" rel="noopener noreferrer"&gt;here&lt;/a&gt; (written from memory a week later). For full details on the project, including all notes and the source code, check the &lt;a href="https://github.com/HouseMDAI" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To sum up, the hackathon was fantastic. I made new friends, expanded my network, and gained valuable knowledge, experiences, and team work skills. It was a great mix of geeks, nerds, developers, data scientists, and entrepreneurs. Potentially, each such hackathon can generate strong teams with interesting startups in a weekend. I highly recommend these events and look forward to the next one.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>hackathon</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>S.T.A.R.K - The First Voice Assistant's Framework</title>
      <dc:creator>Mark Parker</dc:creator>
      <pubDate>Wed, 20 Sep 2023 09:08:22 +0000</pubDate>
      <link>https://dev.to/markparker5/stark-the-first-voice-assistants-framework-i8a</link>
      <guid>https://dev.to/markparker5/stark-the-first-voice-assistants-framework-i8a</guid>
      <description>&lt;p&gt;Hello, tech enthusiasts! Today, I'm thrilled to introduce you to S.T.A.R.K, the Speech and Text Algorithmic Recognition Kit. If you've ever dreamt of creating a voice assistant that's modern, advanced, and incredibly intuitive, S.T.A.R.K is your answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why S.T.A.R.K?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Autonomous and Privacy-Centric&lt;/strong&gt;: In an age where data privacy is paramount, S.T.A.R.K operates entirely on-device. This ensures that your data remains private, giving you peace of mind.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context-Awareness&lt;/strong&gt;: With S.T.A.R.K, you can easily define context and parameters for subsequent requests. It even allows you to parse multiple commands simultaneously, offering a seamless user experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asynchronous Commands&lt;/strong&gt;: Start a task and continue using your voice assistant. S.T.A.R.K will notify you upon completion, ensuring you're always in the loop.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiple Responses&lt;/strong&gt;: Get real-time updates for ongoing tasks. Whether it's monitoring download progress or tracking a delivery, S.T.A.R.K keeps you informed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Patterns Parsing&lt;/strong&gt;: Custom patterns syntax simplifies the process of extracting any parameter from strings, making it effortless to understand and respond to user commands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extendable with Leading Language Models&lt;/strong&gt;: Enhance S.T.A.R.K's cognition by integrating it with top-tier language models like ChatGPT.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multilingual Support&lt;/strong&gt;: Interact with your voice assistant in multiple languages, making it globally accessible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Absolute Customization&lt;/strong&gt;: Craft intricate commands, integrate various speech or text interfaces, and even override existing classes to make your voice assistant truly unique.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Community Support&lt;/strong&gt;: Join the &lt;a href="https://github.com/MarkParker5/STARK-PLACE" rel="noopener noreferrer"&gt;STARK-PLACE&lt;/a&gt; repository and tap into a library filled with community extensions. Use commands crafted by others and share your innovations.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;For a deeper dive into each feature and to understand the full capabilities of S.T.A.R.K, check out the &lt;a href="https://stark.markparker.me/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you're as excited about S.T.A.R.K as we are, head over to the &lt;a href="https://github.com/MarkParker5/STARK" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. If you find it valuable, don't forget to add a star ⭐ to the repo. This way, you won't miss out on any updates, and it helps others discover this fantastic framework!&lt;/p&gt;

&lt;p&gt;Dive in, explore, and let your voice be heard!&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>programming</category>
      <category>development</category>
    </item>
    <item>
      <title>Inside MajorDom v1.0: Exploring the Architecture of a New Smart Home System</title>
      <dc:creator>Mark Parker</dc:creator>
      <pubDate>Thu, 01 Jun 2023 15:11:37 +0000</pubDate>
      <link>https://dev.to/markparker5/inside-majordom-v10-exploring-the-architecture-of-a-new-smart-home-system-3i18</link>
      <guid>https://dev.to/markparker5/inside-majordom-v10-exploring-the-architecture-of-a-new-smart-home-system-3i18</guid>
      <description>&lt;p&gt;&lt;a href="https://markparker.me/blog/inside-major-dom-v-1-0-exploring-the-architecture-of-a-new-smart-home-system" rel="noopener noreferrer"&gt;Original article&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the previous article, I talked about the origin of the idea to create a new smart home. Now, I want to take a closer look at the architecture of the system.&lt;br&gt;
MajorDom consists of several key components: devices, hub, cloud, bridge, mobile application, and voice assistant.&lt;br&gt;
Devices play a crucial role in the system as they enable control over physical parts of the home. They communicate using radio modules and the "Merlin" protocol that receive commands from the hub, and transmit events back to it.&lt;br&gt;
The hub is the central element of the system. It manages the devices and coordinates their operations. The hub holds the main database with information about users, home, rooms, and devices. Through a local HTTP server and WS server, the hub provides an API for interacting with the database and high-level controlling the devices.&lt;br&gt;
The cloud is the server-side component and plays a vital role in user authentication. It stores the database of users, homes, hubs, and their access rights. Device models with a list of parameters and a firmware update system are also stored in the cloud.&lt;br&gt;
The bridge is a WS server that provides communication between the hub and remote user over the internet. It enables sending commands and receiving information from the hub without being within the home's local network.&lt;br&gt;
The system can have multiple bridges located in different places. Clients select the nearest or least loaded bridge to minimize communication delays.&lt;br&gt;
One of the main features of the MajorDom is its fault tolerance. Despite the collapse of one or multiple bridges, the system perseveres and maintains its functionality. The remaining available bridges take on the tasks of the inactive bridges, ensuring uninterrupted data exchange between the hub and clients.&lt;br&gt;
Even a complete loss of internet connection on the hub is not a problem. All the logic and command processing occur locally, ensuring independence from the internet connection and maintaining the ability to control devices within the local network. However, such scenarios are rare since the hub can be simultaneously connected via Wi-Fi and an Ethernet cable, and future support for cellular network will be added.&lt;br&gt;
Smart home control is achieved through a mobile application that provides a user-friendly interface. However, in practice, the role of the mobile application often reduces to system configuration, while day-to-day device management is carried out using automatic scenarios and the voice assistant.&lt;br&gt;
Like the hub, the voice assistant can work completely offline, ensuring security, privacy, and reliability of use.&lt;br&gt;
Most importantly, now anyone, even those without programming experience, can install the MajorDom system. In the next article, I will share a detailed guide on how to do it.&lt;/p&gt;

</description>
      <category>smarthome</category>
      <category>development</category>
      <category>raspberrypi</category>
      <category>arduino</category>
    </item>
    <item>
      <title>Building a Smart Home - from Voice Assistant to MajorDom v1.0</title>
      <dc:creator>Mark Parker</dc:creator>
      <pubDate>Fri, 26 May 2023 06:46:01 +0000</pubDate>
      <link>https://dev.to/markparker5/building-a-smart-home-from-voice-assistant-to-majordom-v10-3f3o</link>
      <guid>https://dev.to/markparker5/building-a-smart-home-from-voice-assistant-to-majordom-v10-3f3o</guid>
      <description>&lt;p&gt;&lt;a href="https://markparker.me/blog/building-a-smart-home-from-voice-assistant-to-major-dom-v-1-0" rel="noopener noreferrer"&gt;Original post&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In 2019, I first learned about the possibility of speech recognition and synthesis in Python. Google Assistant, Siri, Cortana, and other assistants were even more limited and helpless than they are now. Adding custom voice commands was out of the question. That's when I became inspired to create my own voice assistant that would rival even Tony Stark's Jarvis.&lt;/p&gt;

&lt;p&gt;While working on the core functionality, I started thinking about where to host this assistant. Keeping a laptop constantly powered on was not an option, and I didn't have any other computers available. That's when I discovered the Raspberry Pi single-board computers. I wanted my voice assistant to be able to control lights, LED strips, and curtains. Arduino boards were great for handling such tasks. The only thing left was to find a way to send commands to the Raspberry Pi. I didn't want to use Wi-Fi or Bluetooth right from the start. I found information online about the nRF24L01 modules, gave them a try, and liked the results.&lt;/p&gt;

&lt;p&gt;This system worked quite well, but it had two key drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The range was limited by the sensitivity of the microphone. &lt;/li&gt;
&lt;li&gt;With a good microphone, everything worked perfectly within a room, but not beyond.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each parameter of each device, I had to add identical voice commands that only differed in the address and message. It was inconvenient, but tolerable for the time being.&lt;br&gt;
To solve the first issue, I added http interface to the voice assistant using Django, which could accept an audio file or a string. In combination with a Kotlin mobile application, I created a wireless microphone, expanding the range to the router's coverage area, meaning from the room to the entire apartment and even slightly beyond. Carrying a phone around the home wasn't always convenient, so a few days later, I developed an application for my Wear OS smartwatch, which turned out to be an incredibly handy solution.&lt;/p&gt;

&lt;p&gt;However, I desired more – access to my assistant anytime, not just at home. The simplest solution was a using a Telegram bot as the input-output interface. However, I couldn't shake the feeling that a bot wasn't quite right. I decided to keep it as a temporary solution until I had time to develop something better.&lt;/p&gt;

&lt;p&gt;I wanted to be able to use my mobile application to access the assistant remotely. I just needed to find a way to send a request to the local Django server without being on the local network. I was ready to open and forward ports on my router, but my provider didn't give me a public IP address. That's when I tried ngrok. It worked well at first, but in the free version, the server occasionally crashed and changed its address. I quickly dismissed the VPN tunnel option. The cost of a VPS was equivalent to the ngrok subscription, but the implementation was much more complex.&lt;/p&gt;

&lt;p&gt;Then I remembered that I had free hosting for PHP websites on Beget and reinvented Long Polling and queues. The implementation was straightforward: the application sent a request to the hosting server, where the PHP code added the request body (JSON) to the end of an array and wrote it to a local file. The Raspberry Pi at home sent a read request to this file every second, and after reading it, cleared the array. This way, I was able to send commands home from anywhere on the planet! Similarly, I set up receiving responses from the assistant: I duplicated the implementation and reversed the roles. Two files and four endpoints on the free PHP hosting provided me with stable bidirectional communication with my home assistant. Shortly after, I taught the assistant to send me messages on its own, such as the room number of the next class at the beginning of each break. Before I could boast about it in college, someone started spamming me at home. I had to add authentication: the login and password were hardcoded in the application, and the server had a check kind of this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$login&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'markparker'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$password&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'MyVeryStrongP@ssw0rd!'&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 application's repositories were private, and the server had no repository (why have a repository for just one file with less than 100 lines?), so the level of security was more than sufficient.&lt;/p&gt;

&lt;p&gt;Shortly after, the system gained its first automatic command trigger. With the help of a small feature in my application, I was able to capture an event every time an alarm went off on my phone. This trigger launched the first full scenario: the curtains opened simultaneously, the assistant announced the time, weather, and class schedule at the college. If the room was still dark, the lamp smoothly turned on. At that moment, I felt like a real Tony Stark.&lt;/p&gt;

&lt;p&gt;Then I wanted to add more automatic scenarios using motion sensors, presence detectors, lighting sensors, and so on. At this point, the second drawback I mentioned earlier became more noticeable. There was a lot of code duplication, and working with it became less convenient. The project only had the concept of voice commands; there was no notion of devices and triggers. That's when I realized how much my voice assistant had grown: I was already building a full-fledged smart home, not just a question-and-answer assistant.&lt;/p&gt;

&lt;p&gt;This realization led me to the decision to separate the voice assistant and make the smart home a standalone project, focusing on device control rather than voice commands. And I decided to do it properly from the start, with a full-fledged server, databases, authentication, and a mobile application. Later, a college professor suggested to use web sockets instead of my php array-to-file workaround. This is how I implement remote control of devices over the Internet.&lt;/p&gt;

&lt;p&gt;The overall concept remained unchanged: a hub in the form of a Raspberry Pi single-board computer controls Arduinos via the nRF24L01 radio module. I'll explain the architecture in more detail in the next article.&lt;/p&gt;

</description>
      <category>smarthome</category>
      <category>raspberrypi</category>
      <category>voiceassistant</category>
      <category>arduino</category>
    </item>
    <item>
      <title>How to Localize All Your iOS Apps into 20 Languages in 5 Minutes</title>
      <dc:creator>Mark Parker</dc:creator>
      <pubDate>Sat, 04 Mar 2023 19:47:31 +0000</pubDate>
      <link>https://dev.to/markparker5/how-to-localize-all-your-ios-apps-into-20-languages-in-5-minutes-5b2f</link>
      <guid>https://dev.to/markparker5/how-to-localize-all-your-ios-apps-into-20-languages-in-5-minutes-5b2f</guid>
      <description>&lt;p&gt;&lt;em&gt;Original post: &lt;a href="https://markparker.me/blog/localize-ios-app-in-5-minutes" rel="noopener noreferrer"&gt;https://markparker.me/blog/localize-ios-app-in-5-minutes&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;In today's globalized world, the demand for mobile applications that cater to users from different countries and regions has increased significantly. To reach a wider audience, it's important for iOS application developers to make their apps accessible to users in multiple languages. Localizing an iOS app can offer many benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reach a wider audience&lt;/strong&gt;: By localizing your app, you can reach users who may not speak your app's default language. This can help you tap into new markets and increase your user base.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve user experience&lt;/strong&gt;: Users are more likely to use and enjoy an app that is available in their native language. Localizing your app can help you provide a better user experience, which can lead to better reviews, ratings, and retention rates. Localizing your app can give you a competitive advantage in regions where your competitors may not have localized their apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Increase revenue&lt;/strong&gt;: Localizing your app can lead to increased revenue, as users are more likely to make in-app purchases or pay for subscriptions if the app is available in their native language.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there are also a few problems and potential issues that developers may run into. Localizing an app can be time-consuming and resource intensive, especially for apps with a large amount of content and a long list of languages. Developers must allocate sufficient time and resources to ensure effective localization.&lt;/p&gt;

&lt;p&gt;But we wouldn't be developers if we didn't always try to automate everything. Automated localization can translate content quickly and efficiently, reduce the costs associated with manual translation, such as hiring professional translators or dedicating internal resources to the task. This is especially effective if the application is being created by a single developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare your project
&lt;/h2&gt;

&lt;p&gt;I won't go into detail about how localization of strings works in a xcode project, there are already many other tutorials on this subject. I'll just highlight a few key steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create Localizable.strings file and fill it with strings you want to localize.&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

/* optional description */
"[key]" = "[string]";

/* Example: */
"welcome text" = "Welcome to XCodelocalize";
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2.  Go to the project file settings, add the desired languages. Apple recommended EFIGS (English French Italian German Spanish) + Chinese as a base.
3.  Create Localizable.strings files for all selected languages. You may also want to localize plist, storyboard and intentdefinition files.

## Install [XCodeLocalize](https://github.com/MarkParker5/XCodeLocalize)

Unexpectedly, the ios development tool turned out to be written in python, so make sure you have python 3.9+

After that, install the tool using [pip](https://pypi.org/project/xcodelocalize/):

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

&lt;/div&gt;



&lt;p&gt;pip3 install xcodelocalize &lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Also, available installations from .whl file from [github releases](https://github.com/MarkParker5/XCodeLocalize/releases/latest) or via poetry from [source code](https://github.com/MarkParker5/XCodeLocalize).

## Run automatic localization

`cd` to the project root folder and run

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

&lt;/div&gt;



&lt;p&gt;xcodelocalize [OPTIONS]&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
or

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

&lt;/div&gt;



&lt;p&gt;python3 -m xcodelocalize [OPTIONS]&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
### Options

-  `--base-language`: code of the language from which all strings will be translated. _[default: 'en']_
-  `--override / --no-override`: a boolean value that indicates whether strings that already exist in the file will be translated. Retranslate if `override`, skip if `no-override`. _[default: no-override]_
-  `--format-base / --no-format-base`: sort base file strings by key. _[default: no-format-base]_
-  `--file`: Names of the strings files to be translated. Multiple files can be specified. If not specified, all files will be translated. _[default: None]_
    Example:
    ```


    xcodelocalize --file InfoPlist
    xcodelocalize --file InfoPlist --file MainStoryboard --file Localizable 


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt; &lt;code&gt;--key&lt;/code&gt;: Keys of the strings to be translated. Multiple keys can be specified. If not specified, all keys will be translated. &lt;em&gt;[default: None]&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; &lt;code&gt;--language&lt;/code&gt;: Language codes of the strings files to be translated. Multiple language codes can be specified. If not specified, all files will be translated. &lt;em&gt;[default: None]&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; &lt;code&gt;--log-level&lt;/code&gt;: One from [progress|errors|group|string]. &lt;em&gt;[default: group]&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; &lt;code&gt;--help&lt;/code&gt;: Show info&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;You can go to &lt;code&gt;Target -&amp;gt; Build Phases -&amp;gt; New Run Script Phase&lt;/code&gt; in your xcode project and paste &lt;code&gt;xcodelocalize&lt;/code&gt; there. It will localize necessary strings during every build and your localization files will always be up-to-date.&lt;/p&gt;

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

&lt;p&gt;Now you know how to quickly localize your iOS applications in many other languages.&lt;/p&gt;

&lt;p&gt;Feel free to leave comments. Add a star to the &lt;a href="https://github.com/MarkParker5/XCodeLocalize" rel="noopener noreferrer"&gt;repo&lt;/a&gt; if you like it.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>xcode</category>
      <category>swift</category>
      <category>localization</category>
    </item>
  </channel>
</rss>
