<?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: UpSlide</title>
    <description>The latest articles on DEV Community by UpSlide (@upslide).</description>
    <link>https://dev.to/upslide</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F7052%2Faf850cc4-3cf4-44f6-9252-e5fe7462175b.jpg</url>
      <title>DEV Community: UpSlide</title>
      <link>https://dev.to/upslide</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/upslide"/>
    <language>en</language>
    <item>
      <title>Continuous Translation with .NET Framework</title>
      <dc:creator>Grégoire Scano</dc:creator>
      <pubDate>Thu, 09 Jan 2025 19:50:03 +0000</pubDate>
      <link>https://dev.to/upslide/continuous-translation-with-net-framework-5h7a</link>
      <guid>https://dev.to/upslide/continuous-translation-with-net-framework-5h7a</guid>
      <description>&lt;p&gt;Most popular softwares are available in multiple languages, and that is awesome: you are a native Spanish speaker, you can use Microsoft PowerPoint in Spanish, that’s it. But when it’s your turn to deliver a new innovative software, you rarely think of the translation of its user interface as a primary goal. You focus on impacting features and working code. Indeed, this is what matters the most in the initial stages of the lifecycle. If the software is natively supporting English, it takes even longer to consider supporting other languages because its use is widespread.&lt;/p&gt;

&lt;h3&gt;
  
  
  English Localization
&lt;/h3&gt;

&lt;p&gt;As UpSlide was first available in French, its growing success quickly required English support to expand abroad. The translation was thought to be a onetime effort and needed to be carried out swiftly with a low development cost.&lt;/p&gt;

&lt;p&gt;At that time, strings were stored in the code in two different ways: one using fields, the second using &lt;code&gt;ResX&lt;/code&gt; resource files¹; and so, the translation was made in one of these formats without changing the architecture of the code.&lt;/p&gt;

&lt;p&gt;For fields, the migration consisted in making their static constructors initialize strings to the relevant language on startup. It was quite straightforward, but a huge work, nonetheless.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SettingsMessages&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;WindowTitle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Localize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CultureInfo&lt;/span&gt; &lt;span class="n"&gt;culture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;culture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;SupportedCultures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;English&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;WindowTitle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Settings"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;SupportedCultures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;French&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="n"&gt;WindowTitle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Paramètres"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Unknown culture: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;culture&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;ResX&lt;/code&gt; files, it was even simpler. Thanks to its &lt;code&gt;.NET&lt;/code&gt; native internationalization support, it is possible to have multiple &lt;code&gt;ResX&lt;/code&gt; files with a language suffix such as &lt;code&gt;Ribbon.en.resx&lt;/code&gt; for a localization of &lt;code&gt;Ribbon.resx&lt;/code&gt;. The content of the English version is then stored in a satellite assembly and retrieved at runtime if the culture of the resource is set to &lt;code&gt;en&lt;/code&gt; or any region for this language, such as &lt;code&gt;en-US&lt;/code&gt; for instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Resources&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Localize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CultureInfo&lt;/span&gt; &lt;span class="n"&gt;culture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Ribbon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Culture&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;culture&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;French was kept as the default language to allow for incremental translation. The transition was thus an easy, but painstaking process of translations and reviews (our product team had professional proficiency in both languages).&lt;/p&gt;

&lt;p&gt;The result worked wonders. It has supported the company's continuous success in expanding to other countries all over the world for about a decade. So, it could very much have been the end of the story.&lt;/p&gt;

&lt;h3&gt;
  
  
  German Office Called: Katastrophe…
&lt;/h3&gt;

&lt;p&gt;But it was not going to be enough; English is not the only widespread professional language after all. According to a &lt;a href="https://www.babbel.com/en/magazine/how-many-people-speak-english-and-where-is-it-spoken" rel="noopener noreferrer"&gt;Babel article&lt;/a&gt;, less than 20% of the world speaks it. Some German clients even bought the solution under the assumption that the software would be available in German — this is because UpSlide also has an office in Germany. It seemed obvious to them but not us: the English translation had worked so well so far; we didn’t think it'd require further work.&lt;/p&gt;

&lt;p&gt;Besides hindering future sales, missing a translated language could slow down product adoption in other countries as well. Indeed, although some French users can use English, they most often prefer to use the French version… The conclusion was simple: the software must be ready to be translated to other languages.&lt;/p&gt;

&lt;p&gt;So far, only two languages were dealt with, and they were fairly similar. As a result, the methods used to add English would not necessarily be replicable, and translating to other, potentially very different, languages was unknown territory. In particular, pluralization and average word length could differ and impact both the code structure and the visual rendering. Also, it may not be spoken fluently by people in charge of string editions, namely product engineers and managers, so it was not very clear how the actual translation would take place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internationalization &amp;amp; Localization
&lt;/h3&gt;

&lt;p&gt;According to &lt;a href="https://en.wikipedia.org/wiki/Internationalization_and_localization" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;, “Internationalization is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes. Localization is the process of adapting internationalized software for a specific region or language by translating text and adding locale-specific components”.&lt;/p&gt;

&lt;p&gt;Internationalization is complex because it has to bend a formal language to be able to display natural languages, with a common interface for all supported locales. It is also not just about the language in which an application is displayed but also involves reworking on the graphical user interface to accommodate for different writing directions, strings length, buttons’ locations, and color schemes.&lt;/p&gt;

&lt;p&gt;GNU brilliantly solved part of the problem with &lt;a href="https://www.gnu.org/software/gettext" rel="noopener noreferrer"&gt;&lt;code&gt;gettext&lt;/code&gt;&lt;/a&gt; for textual changes. One program extracts the strings in the default language from the code itself. Another manipulates those for each language. The last one retrieves the correct string depending on the localization at runtime. In particular, &lt;code&gt;gettext&lt;/code&gt; handles pluralization in a very smart and efficient way for any type of language, especially those for which more than two plural forms exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Ukrainian has 3 plural forms:
# nplurals=3
# The plural form (the index in the msgstr array) is determined by the following expression:
# plural=(n%10==1 &amp;amp;&amp;amp; n%100!=11 ? 0 : n%10&amp;gt;=2 &amp;amp;&amp;amp; n%10&amp;lt;=4 &amp;amp;&amp;amp; (n%100&amp;lt;10 || n%100&amp;gt;=20) ? 1 : 2)

msgid "%d constructor found\n"          # Singular in English
msgid_plural "%d constructors found\n"  # Plural in English (to indicate the pluralized content to the translator - 'constructors' here)

msgstr[0] "%d конструктор знайдено\n"   # Singular nominative form for numbers ending in 1 (except 11)
msgstr[1] "%d конструктори знайдено\n"  # Plural nominative form for numbers ending in 2, 3, and 4 (except 12, 13, and 14)
msgstr[2] "%d конструкторів знайдено\n" # Plural genetive form for all other numbers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regarding graphical user interfaces, solutions are most commonly handcrafted depending on the application. However, &lt;code&gt;WinForm&lt;/code&gt; properties such as the size of graphical elements can be adjusted through properties defined in &lt;code&gt;ResX&lt;/code&gt; files. It is also possible to do the same for &lt;code&gt;WPF&lt;/code&gt; elements, even without using the designer.&lt;/p&gt;

&lt;p&gt;Localization is challenging because it requires synchronisation between the product and developer teams. Many solutions exist to provide online localisation services supporting various input formats such as GNU &lt;code&gt;gettext&lt;/code&gt; and &lt;code&gt;ResX&lt;/code&gt; files. But a strong requirement, the code should not leave the company’s premises, removed almost all available solutions. Not to mention that they were also quite expensive. &lt;a href="https://weblate.org/" rel="noopener noreferrer"&gt;&lt;code&gt;Weblate&lt;/code&gt;&lt;/a&gt; on the other hand combines the advantages of being free and self-hosted. The code would not have to leave the premises, and support is affordable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remaining Pragmatic
&lt;/h3&gt;

&lt;p&gt;There are a couple of NuGet packages out there to harness the cleverness of &lt;code&gt;gettext&lt;/code&gt; in &lt;code&gt;.NET&lt;/code&gt;. However, integration required additional dependencies, both for compilation and runtime, and resulted in a more complex and less stable build. The approach was thus deemed unfit for our &lt;code&gt;.NET&lt;/code&gt; Framework projects.&lt;/p&gt;

&lt;p&gt;Besides technical challenges, the long-term target languages did not have more than two plural forms. These targeted languages were carefully identified with a high return on investment and limited expected graphical user interface modifications. To achieve the transition, all static strings had to be migrated to &lt;code&gt;ResX&lt;/code&gt; files and plurals had to be handled by a method similar to &lt;code&gt;ngettext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Of course, this migration had a big impact. But it was still costly to add and edit strings both because the code had to be edited but also because product and developer teams needed synchronization. This is where &lt;code&gt;Weblate&lt;/code&gt; and continuous translation brought the solution to yet another level. It allows many within the company to engage in the translation process. All in a shared and constant environment. Translations can be peer-reviewed, and developers do not have to worry too much about them either, freeing up some time and reducing friction in the whole development process. Subcontractors can also be involved to translate to languages that are not mastered within the company. &lt;/p&gt;

&lt;p&gt;Building on the previous success of the incremental translation to English, the migration is also incremental. The technical solution is ready, and we can already start translating to other languages. This makes the company a little more resilient against new language requirements and a little more ready for future challenges.&lt;/p&gt;




&lt;p&gt;¹ A ResX file is an XML description of resources. Some, such as strings, are directly contained verbatim, while others are a pointer to any type of files, most notably icons, images, and audio files. Those files, along with an internal representation of the XML file, are embedded in the final assembly so that those resources can be retrieved in two convenient ways at run time. First, using a resource manager that loads the assembly, locates the resource and returns it. Second, having a single file generator wrap the resource file within a generated class with properties corresponding to each resource.&lt;/p&gt;

</description>
      <category>internationalization</category>
      <category>localization</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>How and why did we improve our API hosting?</title>
      <dc:creator>Clément MARTINEZ</dc:creator>
      <pubDate>Tue, 17 Sep 2024 12:22:06 +0000</pubDate>
      <link>https://dev.to/upslide/how-and-why-did-we-improve-our-api-hosting-13pe</link>
      <guid>https://dev.to/upslide/how-and-why-did-we-improve-our-api-hosting-13pe</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;UpSlide is a Microsoft 365 add-in that runs with PowerPoint, Excel and Word. At startup, the UpSlide add-in sends requests to our various endpoints to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;check the license;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;perform auto-update;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;retrieve global settings managed from our “Portal” website;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;send usage statistics.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These endpoints are critical to ensure the add-in works correctly, which is why we need a stable and secure release process to guarantee their continuous availability.&lt;/p&gt;

&lt;p&gt;To host these endpoints, we’ve been using Azure App Services for a long time, organized into three distinct environments:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Develop&lt;/th&gt;
&lt;th&gt;Testing&lt;/th&gt;
&lt;th&gt;Production&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;to deploy and test the code during the sprint&lt;/td&gt;
&lt;td&gt;to run automated tests in an environment close to the production one (similar data, same performance)&lt;/td&gt;
&lt;td&gt;to handle UpSlide add-in’s requests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Historically, each environment was materialized by an App Service deployment slot, in which we configured slot settings. These were used to fill the application settings to target the right resources (i.e., the right database, service, or warehouse) based on the environment. Slot settings were actually environment variables.&lt;/p&gt;

&lt;p&gt;On release day, the production roll-out process involved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploying a new version on the "testing" slot&lt;/li&gt;
&lt;li&gt;Updating the “testing” slot settings&lt;/li&gt;
&lt;li&gt;Ensuring everything was working correctly&lt;/li&gt;
&lt;li&gt;Updating the “production” slot settings&lt;/li&gt;
&lt;li&gt;Swapping the "testing" and "production" slots to complete the deployment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcuoddc0pc5gwuc6kndjg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcuoddc0pc5gwuc6kndjg.png" alt="Illustration of our release process" width="351" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the new version had any problems, we would restore the previous one. &lt;/p&gt;

&lt;p&gt;This solution was functional, but there was still room for improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges with Azure App Services
&lt;/h2&gt;

&lt;p&gt;First, there were still manual operations during the deployment process, which, in my opinion, often results in the introduction of bugs in a release process. Updating deployment slot settings (i.e., environment variables) because of a change in application settings was one of the elements that needed to be automated.&lt;/p&gt;

&lt;p&gt;Imagine Alex, a super-happy developer at UpSlide. On release day, Alex manually updates the App Service production slot settings, as application settings were added or modified during the sprint.&lt;/p&gt;

&lt;p&gt;He is bound to encounter situations where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;he forgets to add or edit slot settings;&lt;/li&gt;
&lt;li&gt;a value entered in slot settings contains a typo;&lt;/li&gt;
&lt;li&gt;thinking he's doing the right thing, he corrects a setting,  but the correction is incorrect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the best-case scenario, these errors have a minor impact on the service. In the worst case, the service goes down. &lt;/p&gt;

&lt;p&gt;But it's not over yet. Let's assume these slot settings changes have been carried out correctly: the new version works, but an hour later, we realize that a regression has been introduced. We expect to quickly fix the problem by returning to the previous version, just long enough to understand what's wrong. So Alex figures he can simply swap the "testing" and "production" slots again. But a new problem arises: the slot settings have changed! As these settings stick to slots during a swap operation, Alex has to urgently determine what changes have been made to revert them.&lt;/p&gt;

&lt;p&gt;What should be a simple situation becomes a headache, and the stress and urgency can lead Alex to make other mistakes.&lt;/p&gt;

&lt;p&gt;So far, our developers have been vigilant enough to avoid any major problems. But we didn't want to rely solely on their vigilance, so we had to prioritize improvements to the release process. We agreed on the following resolutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;environment variable updates must not be done manually;&lt;/li&gt;
&lt;li&gt;redeploying an older version of our endpoints must be easy and without risks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This brought us to our new approach: &lt;strong&gt;Container Apps&lt;/strong&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  A new hosting solution: Container Apps
&lt;/h2&gt;

&lt;p&gt;Azure Container Apps provide a serverless environment for running microservices and containerized applications. They feature the concept of 'Revisions', which encapsulate both application code and environment variables, ensuring consistent and reliable deployments.&lt;/p&gt;

&lt;p&gt;Let’s go back to the previous situation, this time with container apps. Alex deploys the latest version of our endpoints and the set of environment variables linked to that revision. An hour later, a regression is detected. Alex just needs to reactivate the previous version and redirect traffic. In a matter of seconds, and without the risk of making a mistake, the situation returns to normal, and Alex can focus on fixing the regression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating Environment Variable Updates
&lt;/h2&gt;

&lt;p&gt;One problem was fixed, but we still needed a way to automatically update environment variables.&lt;/p&gt;

&lt;p&gt;Our application defines settings provided by its running environment. Since settings can be added or removed over time, environment variables must be modified accordingly. Application settings are modified during development, while environment variables are set on a release day. These steps are usually separated by a few days, so we must remember to replicate all changes. A must-have would be to declare changes to environment variables during development. That way, all changes are prepared and can be performed automatically during the release.&lt;/p&gt;

&lt;p&gt;We easily achieved this by adding a file called &lt;code&gt;production.env&lt;/code&gt;, in which we declared all environment variable values, being either hard-coded values or &lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/manage-secrets?tabs=azure-portal" rel="noopener noreferrer"&gt;references to an existing key vault secret&lt;/a&gt;.  Here is an example for this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=xxxx-xxx-xxx-xxx-xxxx;IngestionEndpoint=https://applicationinsights.azure.com/;LiveEndpoint=https://monitor.azure.com/
DB_CONNECTION_STRING=secretref:dbconnectionstring
ASPNETCORE_ENVIRONMENT=Production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then updated the release pipeline to extract values from &lt;code&gt;production.env&lt;/code&gt; and use them while deploying a new revision (as the deploy command has a parameter to provide a set of environment variables).&lt;/p&gt;

&lt;p&gt;We ended up with a single source of truth for environment variables that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;developers can update during development when values need to be added or removed;&lt;/li&gt;
&lt;li&gt;is reviewed by others before merging;&lt;/li&gt;
&lt;li&gt;is automatically deployed during the release.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  First Deployment
&lt;/h2&gt;

&lt;p&gt;Implementing the plan wasn't without its challenges.&lt;/p&gt;

&lt;p&gt;We set up the necessary infrastructure on Azure, including the Registry and Container App, and modify our pipelines accordingly. However, upon our first deployment, we encountered an error explaining Container Apps only support images built in a Linux environment. This is not really an issue as .NET Core is cross-platform, so we simply built our API with Linux. However, another error occurred:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;System.Security.Cryptography.CryptographicException: ASN1 corrupted data. &lt;br&gt;
---&amp;gt; System.Formats.Asn1.AsnContentException: The provided data is tagged with 'Application' class value '13', but it should have been 'Universal' class value '16'.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was triggered by System.Security.Cryptography.X509Certificates, a set of classes we were using to manage our certificates. After some research, we found differences in the way Windows and Linux interpret the certificate value provided. &lt;/p&gt;

&lt;p&gt;This issue highlighted the fact that, even though frameworks and packages can be cross-platform, we should still be aware of the differences that may exist in behaviors between each platform. Therefore, we decided to do without this package for the deployed container to start up correctly, as the benefits of using Container Apps justified getting rid of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling Challenges
&lt;/h2&gt;

&lt;p&gt;Once everything was set up, we redirected the traffic to our new Container Apps.&lt;/p&gt;

&lt;p&gt;However, a new issue emerged: our endpoints experience peak loads at certain times of the day, challenging the automatic scaling capabilities of Container Apps.&lt;/p&gt;

&lt;p&gt;Why those peaks?&lt;/p&gt;

&lt;p&gt;Most of our users begin their workday by opening Microsoft 365, so UpSlide triggers license checks, auto-updates, and settings retrieval. This behavior results in daily peaks of requests at certain times of the day.&lt;br&gt;
As UpSlide is used in different parts of the world, these peaks occur at different times, as shown in this graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw52umjlfhyobq9ccetri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw52umjlfhyobq9ccetri.png" alt="Graph representing the request count on our API over a day" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We expected Container Apps to handle this load by scaling automatically, based on rules Azure allows us to define. Those rules include predefined criteria, or custom criteria to configure more advanced rules. We initially only used a predefined criteria: the number of concurrent HTTP requests per second. When this value is exceeded, a new revision replica is created.&lt;/p&gt;

&lt;p&gt;However, starting a revision takes about one minute, and Azure checks for a scaling need every 30 seconds as a fixed interval. So, by the time the Container App starts the new revisions, a large part of the peak load is already absorbed by the initial revision, and the benefits of scaling are lost.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3x1jkc23euq80enkem16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3x1jkc23euq80enkem16.png" alt="Illustration of the load compared to the capacity enhanced by the automatic scaling" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Currently, we mitigate this by scaling the Container Apps automatically before expected peak times. This scaling is done using a &lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/scale-app?pivots=azure-cli#custom" rel="noopener noreferrer"&gt;custom cron rule&lt;/a&gt; that allows us to specify time slots during which we want to scale, and the number of replicas expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Here we are: our Container App works well, but there are still limitations on the packages that can be used in the solution and on automatic scaling. Despite this, our release process has improved and is now more flexible and comfortable for developers. &lt;/p&gt;

&lt;p&gt;Looking ahead, we aim to improve the startup speed of our solution to enable the Container App to have better automatic scaling.&lt;/p&gt;

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

&lt;p&gt;Azure also offers other hosting solutions, to be chosen from according to your needs. We opted for Container Apps due to their simplicity, ease of management, and seamless integration with our existing infrastructure. The abstraction of Kubernetes complexities allowed us to focus on application deployment and scalability without the overhead of managing intricate configurations.&lt;/p&gt;

&lt;p&gt;As we continue to innovate and adapt, we remain open to exploring new solutions that can further enhance our deployment processes and overall efficiency.&lt;/p&gt;

&lt;p&gt;What about you? What do you use to host your APIs?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Special thanks to Fabien SINQUIN and Clément MICHEL for their major contribution to this article 🙏&lt;/em&gt;&lt;/p&gt;

</description>
      <category>containerapps</category>
      <category>api</category>
      <category>devops</category>
      <category>ci</category>
    </item>
    <item>
      <title>Ubiquitous Language: the Good, the Bad, and the Lessons</title>
      <dc:creator>Fabien Sinquin</dc:creator>
      <pubDate>Thu, 02 May 2024 16:17:49 +0000</pubDate>
      <link>https://dev.to/upslide/ubiquitous-language-the-good-the-bad-and-the-lessons-c2p</link>
      <guid>https://dev.to/upslide/ubiquitous-language-the-good-the-bad-and-the-lessons-c2p</guid>
      <description>&lt;h2&gt;
  
  
  Ubiquitous Language Is Great
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://martinfowler.com/bliki/UbiquitousLanguage.html" rel="noopener noreferrer"&gt;Ubiquitous Language&lt;/a&gt; is the idea of defining a common language to be used at all levels in your product: user interface, documentation, client presentations, code, etc. This language should be shared between domain experts, users, marketing, and devs.&lt;/p&gt;

&lt;p&gt;The history of languages shows that &lt;a href="https://en.wikipedia.org/wiki/Linguistic_prescription" rel="noopener noreferrer"&gt;Linguistic prescription&lt;/a&gt; (defining and enforcing a standard language) is tricky. For example, every time the &lt;em&gt;Académie Française&lt;/em&gt; has tried to impose freshly minted terms to replace Anglicisms – such as email, upload etc. – it failed. Most French people kept using the English words. If only because of reluctance to change, a fully ubiquitous natural language is not realistic. &lt;/p&gt;

&lt;p&gt;So you may wonder: is Ubiquitous Language worth the trouble? How helpful is it?&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid Duplicated Effort
&lt;/h3&gt;

&lt;p&gt;If you are familiar with Microsoft Word, you may have noticed you have two ways to color the background of your text: “Text Highlight Color” and “Shading”.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Text Highlight Color&lt;/th&gt;
&lt;th&gt;Shading&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2Fxw8x1leld22zspvldugd.png" alt="Text Highlight Color – Make your text pop by highlighting it in a bright color."&gt;&lt;/td&gt;
&lt;td&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%2Fp9czbixr9w0yzakvogxo.png" alt="Shading – Change the color behind the selected text, paragraph or table cell. This is especially useful when you want information to jump off the page."&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is a significant overlap between both features, but they do not cover the same cases and they do not work well together. They are – most likely – two separate implementations covering a single need. A user might have a hard time deciding whether their text should rather jump off the page or just pop… &lt;/p&gt;

&lt;p&gt;Looking at these buttons, I cannot help but think the lack of a common language resulted in waste: having to implement and maintain a feature twice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid Bugs
&lt;/h3&gt;

&lt;p&gt;It is possible to open hidden presentations (without a window) in PowerPoint. When all presentation windows are closed, PowerPoint will remain active as long as a hidden presentation is opened.&lt;/p&gt;

&lt;p&gt;In one of our sprints, we needed to implement a routine to force close all such presentations. But in our code base, we had an older method named &lt;code&gt;GetHiddenPresentations&lt;/code&gt; that listed &lt;strong&gt;minimized&lt;/strong&gt; presentations.&lt;/p&gt;

&lt;p&gt;As you can guess, we ended up force-closing all minimized presentations. This – definitely – was not a welcomed feature! In the post-mortem that followed this bug, we decided to use “windowless” and “minimized” instead of the ambiguous “hidden”. We felt this simple wording change would have avoided the mistake from the beginning, and would have helped us spot that bug during the review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ubiquitous Language Is Hard
&lt;/h2&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-7269997868-575" src="https://platform.twitter.com/embed/Tweet.html?id=7269997868"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-7269997868-575');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=7269997868&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Ubiquitous Language sounds simple, but – since it is all about naming things – it is not. I’ve been struggling with it ever since I joined &lt;a href="https://upslide.net/" rel="noopener noreferrer"&gt;UpSlide&lt;/a&gt;. &lt;em&gt;Please don’t tell my colleagues&lt;/em&gt; 😉&lt;/p&gt;

&lt;p&gt;In 2022, we wanted to simplify the way our users apply style to their PowerPoint tables. We already had a “Table Styles” feature, so we came up with an idea: a menu that would pop up when users select tables.&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%2F9cxwgozbdly8niw6c1b1.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%2F9cxwgozbdly8niw6c1b1.png" alt="A “Table Styles” menu is floating above a PowerPoint table."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was the first time we implemented such a menu, so we had a question…&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;What should we name this kind of menu?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Let’s Brainstorm!
&lt;/h3&gt;

&lt;p&gt;Imagine you’re part of our team. You are looking for the name of “a context-dependent menu that pops up to nudge users”).&lt;/p&gt;

&lt;p&gt;Gather a few colleagues and start throwing ideas around.&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%2Frri8495syy7db6h7j5et.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%2Frri8495syy7db6h7j5et.png" alt="A mind map that shows the words from the brief, and possible names."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once someone suggests “minibar 🍸”, you can all agree you have run out of ideas.&lt;/p&gt;

&lt;h3&gt;
  
  
  There Can Only Be One!
&lt;/h3&gt;

&lt;p&gt;Now it’s time to eliminate ideas…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let’s start with the obvious ones, “Menu” and “Suggestion” are too generic. Out!&lt;/li&gt;
&lt;li&gt;“Popup” is not so bad, but the word has gained such a bad reputation (because of website ads) that it’s out the window. “Pop menu” removes some of that discomfort but sounds unfamiliar.&lt;/li&gt;
&lt;li&gt;“Prompt¹” means something else to devs (&lt;a href="https://en.wikipedia.org/wiki/Cmd.exe" rel="noopener noreferrer"&gt;Command Prompt&lt;/a&gt; or &lt;a href="https://www.w3schools.com/jsref/met_win_prompt.asp" rel="noopener noreferrer"&gt;Javascript prompt&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;From a marketing perspective, “Toolbar” / “mini toolbar²” do not spark joy. Pre-Office 2007, toolbars were the rage³, but they disappeared when the ribbon arrived.&lt;/li&gt;
&lt;li&gt;“Context Menu” sounds OK, but it is the name commonly used for the right-click menu…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s assume that you’ve settled on “&lt;strong&gt;Contextual menu&lt;/strong&gt;” (like we did at the time). You can now proceed to use that term everywhere: code, documentation, discussions, etc.&lt;br&gt;
And – because you want this Ubiquitous Language approach to work– you’ll make a point of correcting anyone who uses something else: “Careful, this is not a popup⁴, it’s a contextual menu.”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#happily-ever-after&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Marketing Called: They Have a Better Name…
&lt;/h3&gt;

&lt;p&gt;While the feature is getting ready for the general release, our fellows from Marketing have gathered insights from users and concluded that the feature should be named a “&lt;strong&gt;prompt&lt;/strong&gt;”.&lt;/p&gt;

&lt;p&gt;To keep a Ubiquitous Language, we now have to rename all occurrences in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code: IDEs are great nowadays and make this step easy. (&amp;lt;3 &lt;a href="https://www.jetbrains.com/" rel="noopener noreferrer"&gt;JetBrains&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Documentation: this one is more painful, because:

&lt;ul&gt;
&lt;li&gt;our documentation is spread across several tools⁵ (Notion, a bit of Zendesk for end-users, and an Azure DevOps wiki for technical topics);&lt;/li&gt;
&lt;li&gt;search and replace is a blunt tool.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;my head: on this front, tooling cannot help… Once they have gotten used to it, people will have a hard time avoiding the now-obsolete name. So “Contextual Menu” will keep popping by in emails, and other conversations. It eventually gets better, but the transition is painful.&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  More Surprises 😅
&lt;/h3&gt;

&lt;p&gt;Remember when I said renaming the code was easy? Well, I lied… Some occurrences of “Contextual Menu” are serialized in our user settings.&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%2Fk3v6ucnvv0jaey4iu4wj.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%2Fk3v6ucnvv0jaey4iu4wj.png" alt="A YAML description of Preferences with a field named DisableTableStylesContextualMenu."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When this happens, you must choose between a breaking change and quasi-ubiquitous language.&lt;/p&gt;

&lt;p&gt;The same kind of issue can also happen with old emails or messages: they cannot be fixed and might cause head-scratching later on.&lt;/p&gt;

&lt;p&gt;By the time we got around calling those menus “Prompt”, ChatGPT arrived. And with it, a new meaning for “prompts” emerged. For anyone in the software industry, a “prompt” is now the input given to a Large Language Model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, should we rename our “prompts” to avoid confusion, or should we keep that name?&lt;/strong&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  “Well-established” Trumps “Cool”.
&lt;/h3&gt;

&lt;p&gt;We are a Microsoft 365 add-in, deeply integrated within Microsoft Office. We were implementing a feature that behaves like the Office “Mini toolbar”… From a Ubiquitous Language perspective, this makes a very strong case for naming our feature “Mini toolbar”.&lt;/p&gt;

&lt;h3&gt;
  
  
  Name Deliberately
&lt;/h3&gt;

&lt;p&gt;When we started working on the Proof of Concept for Table Styles, we delayed thinking about the feature name. It made sense because we were unsure of what we could build exactly. By not picking a name, we encountered many challenges along the way.&lt;/p&gt;

&lt;p&gt;In retrospect, we should probably have…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deliberately picked a dummy name for the future feature. Maybe even a silly one – say Stylinator – to make changing it easier.&lt;/li&gt;
&lt;li&gt;Started looking for a good name as soon as the final feature behavior was clear.&lt;/li&gt;
&lt;li&gt;Involved all parties (devs, product, marketing, domain experts) in that search.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Despite all your efforts, external factors will force you into renaming things. In 2022 it might have been OpenAI that changed the meaning of “prompt”. In 2023 it might have been Microsoft's decision to rename Azure AD to Entra ID. Change is inevitable.&lt;/p&gt;

&lt;p&gt;Over the years, I have found it helpful to view Ubiquitous Language as a journey, rather than a destination. Maintaining a common vocabulary can be a pain at times, but it is often worth it. Trust me, you do not want the blood of those minimized presentations on your hands 😅&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about you? What is the worst bug you have ever met that was caused by a misunderstanding?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;This was early 2022, before ChatGPT, so “prompt” was not commonly associated with AI.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Microsoft Office has a feature &lt;a href="https://support.microsoft.com/en-gb/office/use-the-mini-toolbar-to-format-text-47012f83-b553-40a9-b7de-f038876f4db3" rel="noopener noreferrer"&gt;named “mini toolbar”&lt;/a&gt; that behaves like our menu.&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%2F139pn0ng26tuo7m1fnj6.gif" 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%2F139pn0ng26tuo7m1fnj6.gif" alt="Office mini-toolbar"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the 2000s, it was common for software to bundle browser toolbars, and you could end up with an unusable browser…&lt;br&gt;
&lt;iframe class="tweet-embed" id="tweet-1585243632160092164-342" src="https://platform.twitter.com/embed/Tweet.html?id=1585243632160092164"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1585243632160092164-342');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1585243632160092164&amp;amp;theme=dark"
  }



&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When developing the Proof-of-Concept, the initial name we used was “popup”.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We are actively looking at reducing the number of tools we use for our documentation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://www.pexels.com/fr-fr/photo/texte-du-dictionnaire-avec-effet-bokeh-267669/" rel="noopener noreferrer"&gt;Cover photo source&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>softwareengineering</category>
      <category>softwaredevelopment</category>
      <category>software</category>
      <category>howto</category>
    </item>
    <item>
      <title>Make your Azure OpenAI apps compliant with RBAC</title>
      <dc:creator>Thomas W. Drake</dc:creator>
      <pubDate>Mon, 25 Mar 2024 17:05:18 +0000</pubDate>
      <link>https://dev.to/upslide/using-rbac-with-azure-openai-services-1kl5</link>
      <guid>https://dev.to/upslide/using-rbac-with-azure-openai-services-1kl5</guid>
      <description>&lt;p&gt;Microsoft has effectively provided developers with relevant documentation to bootstrap their AI projects using &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/overview" rel="noopener noreferrer"&gt;Azure OpenAI Services&lt;/a&gt;. But, as we delve into more advanced scenarios and start encountering edge cases, there seems to be noticeably less guidelines available.&lt;/p&gt;

&lt;p&gt;What’s a recent example of this? Switching from using shared keys to &lt;strong&gt;Entra ID Authentication/Authorization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Although shared keys are very easy to set up, you’ll likely want to avoid them for several reasons.&lt;br&gt;
Firstly, if you lose the key or if someone gets their hands on it, you need to reset it. All systems that used the old key then need to be updated with the new one to keep working.&lt;br&gt;
This becomes an issue when people leave the company; they might still have that shared key stored somewhere, and there is no way to revoke their permissions.&lt;br&gt;
Plus, storing the key can be a security hazard in of itself.&lt;/p&gt;

&lt;p&gt;Using Entra ID Authentication eliminates all these problems. It authenticates the user, and then delegates the &lt;a href="https://auth0.com/docs/get-started/identity-fundamentals/authentication-and-authorization#authentication-vs-authorization" rel="noopener noreferrer"&gt;authorization&lt;/a&gt; component to a fine-grained, configurable system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/role-based-access-control/overview" rel="noopener noreferrer"&gt;Role-Based Access Management (RBAC)&lt;/a&gt; is the current standard in Azure user access management. It’s the backbone of the entire authorization component in Entra ID authentication.&lt;/p&gt;

&lt;p&gt;Permissions on a specific resource are set via &lt;strong&gt;user roles&lt;/strong&gt;. Each user role encompasses a range of permissions. But when it comes to Azure OpenAI Services, what user roles should we use?&lt;/p&gt;

&lt;p&gt;Disclaimer: Azure OpenAI Services is still a new feature and the technology may be subject to change. The tips shared in this article work now but may become less relevant in the future.&lt;/p&gt;

&lt;p&gt;Also, Microsoft has developed specific libraries, such as &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/msal-overview" rel="noopener noreferrer"&gt;MSAL&lt;/a&gt;, to run the client-side authentication process. Unless you have a good reason for sending the requests yourself, you should use those for the OAuth component. I will share the different requests involved in the OAuth 2.0 implicit flow, as well as how to retrieve an access token for Azure Open AI Services using Entra ID authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set up resources and permissions in Azure
&lt;/h2&gt;

&lt;p&gt;Let’s assume you have already successfully &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?pivots=web-portal" rel="noopener noreferrer"&gt;created an Azure Open AI Services deployment&lt;/a&gt;. In my case, I have a text-davinci-003 deployment named &lt;code&gt;MyDaVinciDeployment&lt;/code&gt;, inside a resource called &lt;code&gt;OpenAIResource&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;✅ This following process works for any model, including GPT 3.5 turbo, GPT 4 and even Dall-E.&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%2Few2efmnm67t5oo7o3jq0.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%2Few2efmnm67t5oo7o3jq0.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To give users access to the AI service, you must provide them with the Cognitive Services User role by navigating to your AI resource and open the Access control (IAM) tab.&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%2Ftto4vnpjtkruf15l7yme.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%2Ftto4vnpjtkruf15l7yme.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create an app registration to access the API outside the Azure OpenAI Studio Playground.&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%2Fouj4ngnbp556ox61gou8.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%2Fouj4ngnbp556ox61gou8.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This important step provides the Application (client) ID, so copy it and keep it safe.&lt;/p&gt;

&lt;p&gt;The app registration is not enough to gain access to the service. It registers the application, but does not grant any permissions. The app registration must be set up to perform authentication so that it can run some operations in the user’s name, with the user’s permissions.&lt;/p&gt;

&lt;p&gt;On the app registration page, navigate to the authentication tab and set up a platform configuration for Single-page application. Set your own callback URL, and set the token type to &lt;code&gt;access token&lt;/code&gt;.&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%2Fe6y1bpray72o80delv7f.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%2Fe6y1bpray72o80delv7f.png" alt="Image description"&gt;&lt;/a&gt;&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%2Fsqogvf4sgclj6kgm6wq2.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%2Fsqogvf4sgclj6kgm6wq2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to specify which permissions the authenticated user will delegate to the application. In the API permissions tab, add the &lt;code&gt;Microsoft Cognitive Services &amp;gt; user_impersonation&lt;/code&gt; permission. You can find the Microsoft Cognitive Services API by searching it in the “APIs my organization uses” tab. Note that this permission might not show up if there is no AI service registered.&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%2Fqcr517ync4v4octwasn9.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%2Fqcr517ync4v4octwasn9.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy steps for authenticating
&lt;/h2&gt;

&lt;p&gt;We will be performing all of the authentication requests manually, however for testing purposes, you might want to use an API testing tool such as &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; or &lt;a href="https://insomnia.rest/" rel="noopener noreferrer"&gt;Insomnia&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a &lt;a href="https://raw.githubusercontent.com/Quertie/public-storage/main/Azure%20Open%20AI%20RBAC.postman_collection.json" rel="noopener noreferrer"&gt;Postman collection&lt;/a&gt; to get you started.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-implicit-grant-flow" rel="noopener noreferrer"&gt;OAuth 2.0 implicit grant flow&lt;/a&gt; (the simplest) works as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client sends a request to the authentication endpoint, specifying an application ID (to indicate which application is sending the request) and the desired scope - in our case: &lt;code&gt;https://cognitiveservices.azure.com/.default&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The authentication server returns a login page.&lt;/li&gt;
&lt;li&gt;When the login is complete, the authentication endpoint issues an access token which can be used to request resources accessible to the logged in user and within the requested scope. In practice, the server redirects the user to a specific callback url and the token is communicated as a parameter or a fragment of that callback url.&lt;/li&gt;
&lt;/ul&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%2F6r4tz0zzeavc187cnfzy.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%2F6r4tz0zzeavc187cnfzy.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's cover both of these requests individually. First, the authentication request.&lt;/p&gt;

&lt;p&gt;To keep things minimalistic, we do not include some optional parameters, but you might want to check them out depending on your use case.&lt;/p&gt;

&lt;p&gt;For the following request, &lt;a href="https://learn.microsoft.com/en-us/azure/azure-portal/get-subscription-tenant-id#find-your-microsoft-entra-tenant" rel="noopener noreferrer"&gt;here's how to find your tenant id&lt;/a&gt;.&lt;/p&gt;

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

GET https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id={client id}
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;response_type=token
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;redirect_uri={redirect uri}
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;scope={https://cognitiveservices.azure.com/.default} # parameters should be URL-encoded
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;response_mode=fragment


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

&lt;/div&gt;

&lt;p&gt;As an example, this is the request I’ll send:&lt;/p&gt;

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

GET https://login.microsoftonline.com/mycompany.com/oauth2/v2.0/authorize?
client_id=535fb089-9ff3-47b6-9bfb-4f1264799865 # use client ID you copied earlier
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;response_type=token
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;redirect_uri=https%3A%2F%2Fexample.com%2Foauth%2Fcallback
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;scope=https%3A%2F%2Fcognitiveservices.azure.com%2F.default
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;response_mode=fragment


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

&lt;/div&gt;

&lt;p&gt;If you copy this url into a browser, you should be able to log in and eventually be redirected to your callback URL, which contains the access token as a fragment:&lt;/p&gt;

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

GET {callback url}#access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2Z...
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;token_type=Bearer
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;expires_in=3599
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;scope=https%3A%2F%2Fcognitiveservices.azure.com%2F.default


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

&lt;/div&gt;

&lt;p&gt;💡 If the authentication fails, you might want to make sure that Multi-Factor Authentication is enabled. It might not work without it.&lt;br&gt;
Also, you might need administrator approval for this step.&lt;/p&gt;

&lt;p&gt;You can then use this token to interact with your deployment using &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/reference" rel="noopener noreferrer"&gt;Azure Open AI Service’s REST API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As an example:&lt;/p&gt;

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

POST https://OpenAiResource.openai.azure.com/openai/deployments/MyDaVinciDeployment/
completions?api-version=2022-12-01

# Headers
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2Z...

# Body
{
    "prompt": "Once upon a time"
}


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

&lt;/div&gt;

&lt;p&gt;This token is valid for a limited time, after which it expires unless it is &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-implicit-grant-flow#refreshing-tokens" rel="noopener noreferrer"&gt;refreshed&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  More AI from Microsoft
&lt;/h2&gt;

&lt;p&gt;The concepts covered in this article remain virtually the same throughout the Azure platform, but you will have to adapt some parameters (namely the user roles and requested scopes) to get your apps to work in the different contexts.&lt;/p&gt;

&lt;p&gt;Microsoft offers an array of different AI-powered products, including &lt;a href="https://azure.microsoft.com/en-us/products/ai-services/openai-service" rel="noopener noreferrer"&gt;Azure OpenAI Service&lt;/a&gt;, &lt;a href="https://azure.microsoft.com/en-us/products/ai-services/ai-search/" rel="noopener noreferrer"&gt;Azure AI Search&lt;/a&gt;, &lt;a href="https://azure.microsoft.com/en-us/products/ai-services/ai-speech/" rel="noopener noreferrer"&gt;Azure AI Speech&lt;/a&gt;, and their most recent &lt;a href="https://www.microsoft.com/en-us/microsoft-365/enterprise/copilot-for-microsoft-365" rel="noopener noreferrer"&gt;Microsoft Copilot for Office 365&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each of these services opens the door to a spectrum of exciting possibilities that we look forward to exploring.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>azure</category>
      <category>ai</category>
      <category>oauth</category>
      <category>security</category>
    </item>
    <item>
      <title>Concise Gherkin - How brevity improves BDD scenarios</title>
      <dc:creator>Ivan Poiraudeau</dc:creator>
      <pubDate>Tue, 27 Feb 2024 09:42:34 +0000</pubDate>
      <link>https://dev.to/upslide/concise-gherkin-how-brevity-improves-bdd-scenarios-3d86</link>
      <guid>https://dev.to/upslide/concise-gherkin-how-brevity-improves-bdd-scenarios-3d86</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;💡 New to behavior-driven development? It’s a fantastic way to collaborate and produce better documentation.&lt;br&gt;
Here’s an &lt;a href="https://cucumber.io/docs/bdd/" rel="noopener noreferrer"&gt;introduction&lt;/a&gt;, with details about its dedicated language, &lt;a href="https://cucumber.io/docs/gherkin/reference/" rel="noopener noreferrer"&gt;Gherkin&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In behavior-driven development (BDD), it’s best practice to &lt;a href="https://cucumber.io/blog/bdd/keep-your-scenarios-brief/" rel="noopener noreferrer"&gt;keep your scenarios BRIEF&lt;/a&gt;, an acronym for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Business language&lt;/strong&gt;: The words used in a scenario should be drawn from the business domain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real data&lt;/strong&gt;: Examples should use concrete, real data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intention revealing&lt;/strong&gt;: Scenarios should reveal the &lt;em&gt;intent&lt;/em&gt; of what the actors in the scenario are trying to achieve, rather than describing the &lt;em&gt;mechanics&lt;/em&gt; of how they will achieve it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Essential&lt;/strong&gt;: The purpose of a scenario is to illustrate how a rule should behave. Any part of a scenario that does not directly contribute to this purpose are incidental and should be removed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focused&lt;/strong&gt;: Most scenarios should be focused on illustrating a single rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last year, &lt;a href="https://upslide.net/" rel="noopener noreferrer"&gt;my team at UpSlide&lt;/a&gt; overhauled our user management system through BDD. We noticed that focusing on &lt;strong&gt;brevity&lt;/strong&gt; let us validate several principles at once: our scenarios were becoming more intention revealing, essential and focused.&lt;/p&gt;

&lt;h3&gt;
  
  
  Brevity reveals scenarios intention
&lt;/h3&gt;

&lt;p&gt;Consider this login step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="nf"&gt;Given &lt;/span&gt;I visit &lt;span class="s"&gt;"/login"&lt;/span&gt;
&lt;span class="nf"&gt;When &lt;/span&gt;I enter &lt;span class="s"&gt;"Bob"&lt;/span&gt; in the &lt;span class="s"&gt;"user name"&lt;/span&gt; field
  &lt;span class="nf"&gt;And &lt;/span&gt;I enter &lt;span class="s"&gt;"tester"&lt;/span&gt; in the &lt;span class="s"&gt;"password"&lt;/span&gt; field
  &lt;span class="nf"&gt;And &lt;/span&gt;I press the &lt;span class="s"&gt;"login"&lt;/span&gt; button
&lt;span class="nf"&gt;Then &lt;/span&gt;I should be greeted with a welcome message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scenario is filled with unimportant and brittle implementation details. By prioritizing brevity, we can simplify it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="nf"&gt;When &lt;/span&gt;Bob logs in
&lt;span class="nf"&gt;Then &lt;/span&gt;Bob should be greeted with a welcome message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scenario intention becomes clearer: it emphasizes the need for a welcome message upon login, without getting bogged down in implementation specifics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Brevity keeps scenarios essential
&lt;/h3&gt;

&lt;p&gt;Here is a scenario about email notifications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="nf"&gt;Given &lt;/span&gt;Alice's email storage is not full
&lt;span class="nf"&gt;And &lt;/span&gt;Alice is not offline
&lt;span class="nf"&gt;When &lt;/span&gt;Alice sends an email
&lt;span class="nf"&gt;Then &lt;/span&gt;Alice should get notified that the email was sent.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Alice’s storage is full or if she’s offline, the email indeed cannot be sent so she shouldn’t get notified that it was.&lt;/p&gt;

&lt;p&gt;In most email happy paths however, these conditions won’t apply and the reader doesn’t need to know about them.&lt;br&gt;
In the test implementation, we should instead ensure the &lt;a href="https://en.wikipedia.org/wiki/System_under_test" rel="noopener noreferrer"&gt;system under test&lt;/a&gt; has enough message storage for Alice and operates as if it’s online. We can then streamline the scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="nf"&gt;When &lt;/span&gt;Alice sends an email
&lt;span class="nf"&gt;Then &lt;/span&gt;Alice should get notified that the email was sent.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Considering storage space and internet connection separately lets us explore other behaviors, each with their own scenarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="nf"&gt;Given &lt;/span&gt;Alice's email storage is full
&lt;span class="nf"&gt;When &lt;/span&gt;Alice sends an email
&lt;span class="nf"&gt;Then &lt;/span&gt;Alice should be notified that the email couldn't be sent
&lt;span class="nf"&gt;And &lt;/span&gt;that she needs to free up storage

&lt;span class="nf"&gt;Given &lt;/span&gt;Alice is offline
&lt;span class="nf"&gt;When &lt;/span&gt;Alice sends an email
&lt;span class="nf"&gt;Then &lt;/span&gt;Alice should be notified that the email couldn't be sent
&lt;span class="nf"&gt;And &lt;/span&gt;that she needs to connect to the Internet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Brevity focuses scenarios
&lt;/h3&gt;

&lt;p&gt;Follow this example from Monopoly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="nf"&gt;Given &lt;/span&gt;the player went over the GO square [A]
&lt;span class="nf"&gt;When &lt;/span&gt;the player falls on a CHANCE square [B]
&lt;span class="nf"&gt;Then &lt;/span&gt;the player should earn 200₩
&lt;span class="nf"&gt;And &lt;/span&gt;the player should take the top CHANCE card
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that mixing [A] and [B] in other ways (&lt;em&gt;not&lt;/em&gt; going over the GO square, &lt;em&gt;not&lt;/em&gt; falling on the CHANCE square) could lead to a combinatorial explosion of scenarios.&lt;/p&gt;

&lt;p&gt;Again, noting this scenario lacks brevity leads us to more independent scenarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="nf"&gt;When &lt;/span&gt;the player goes over the GO square
&lt;span class="nf"&gt;Then &lt;/span&gt;the player should earn 200₩

&lt;span class="nf"&gt;When &lt;/span&gt;the player falls on a CHANCE square
&lt;span class="nf"&gt;Then &lt;/span&gt;the player should take the top CHANCE card
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  More on BDD principles
&lt;/h3&gt;

&lt;p&gt;In a process akin to other forms of writings, brevity helps me uncover the core ideas behind the system I’m specifying.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 If you’re keen on crafting &lt;strong&gt;concise scenarios,&lt;/strong&gt; I’ve put together a &lt;strong&gt;step-by-step&lt;/strong&gt; kata to assist you:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://trello.com/b/qO5804nS/kata-behavior-driven-developmentlink" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Check it here!&lt;/a&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I didn’t talk much about the two other key principles, Business Language and Real Data. This video by Gáspár Nagy (creator of SpecFlow, a .NET BDD framework) delves deeper into them:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/awwFfCYoGFQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>bdd</category>
      <category>gherkin</category>
      <category>documentation</category>
      <category>codequality</category>
    </item>
  </channel>
</rss>
