<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tech Adventures in Business Central | AL Development &amp; AI Solutions</title>
    <description>Exploring Business Central, AL coding, and AI-driven ERP solutions for developers and businesses.</description>
    <link>https://tine.staric.net/</link>
    <atom:link href="https://tine.staric.net/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Fri, 13 Mar 2026 10:15:25 +0100</pubDate>
    <lastBuildDate>Fri, 13 Mar 2026 10:15:25 +0100</lastBuildDate>
    <generator>Jekyll v4.3.2</generator>
    
      <item>
        <title>Format Cheatsheet</title>
        <description>&lt;p&gt;This is a &lt;strong&gt;very quick cheatsheet&lt;/strong&gt; for all the different &lt;strong&gt;&lt;em&gt;formats&lt;/em&gt;&lt;/strong&gt; that you get out of &lt;strong&gt;AL&lt;/strong&gt; using the &lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Format()&lt;/code&gt; function&lt;/strong&gt; with various &lt;strong&gt;&lt;em&gt;format options.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve recently been trying to again find the format that returns the &lt;strong&gt;GUID value&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;without the curly braces&lt;/em&gt;&lt;/strong&gt;, and as I didn’t want to test all the options again, I finally created this &lt;strong&gt;cheatsheet&lt;/strong&gt; for myself. Maybe it will be &lt;strong&gt;&lt;em&gt;useful for you&lt;/em&gt;&lt;/strong&gt; as well.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Format 8&lt;/em&gt; was omitted from this table as it produces no meaningful formatting for any data type.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Click&lt;/em&gt; on any cell to &lt;em&gt;expand&lt;/em&gt; and see the &lt;em&gt;full value&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;em&gt;Values marked with an &lt;strong&gt;asterisk (*)&lt;/strong&gt; are &lt;strong&gt;language dependent&lt;/strong&gt; and to make that obvious, I left them in Spanish.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;style&gt;
.format-table-wrapper {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5em 0;
}
.format-table {
  border-collapse: collapse;
  width: 100%;
  min-width: 1000px;
  font-size: 0.85em;
  font-family: &apos;Courier New&apos;, Courier, monospace;
}
.format-table th,
.format-table td {
  border-bottom: 1px solid #ddd;
  padding: 10px 8px;
  text-align: left;
  white-space: nowrap;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: all 0.2s ease;
}
.format-table th {
  font-weight: 600;
  border-bottom: 2px solid #999;
  background: white;
  font-family: -apple-system, BlinkMacSystemFont, &apos;Segoe UI&apos;, sans-serif;
}
.format-table th:first-child,
.format-table td:first-child {
  position: sticky;
  left: 0;
  background: white;
  font-weight: 500;
  z-index: 1;
  box-shadow: 2px 0 4px rgba(0,0,0,0.05);
  font-family: -apple-system, BlinkMacSystemFont, &apos;Segoe UI&apos;, sans-serif;
  min-width: 90px;
  max-width: 90px;
}
.format-table th:first-child {
  z-index: 2;
}
.format-table td:not(:first-child) {
  cursor: pointer;
}
.format-table td.expanded {
  white-space: normal;
  word-break: break-all;
  max-width: none;
  background: #f0f7ff;
  position: relative;
}
.format-table td.expanded::before {
  content: &apos;✕&apos;;
  position: absolute;
  top: 4px;
  right: 4px;
  font-size: 0.7em;
  color: #666;
  font-family: Arial, sans-serif;
}
&lt;/style&gt;

&lt;script&gt;
document.addEventListener(&apos;DOMContentLoaded&apos;, function() {
  const cells = document.querySelectorAll(&apos;.format-table td:not(:first-child)&apos;);
  cells.forEach(cell =&gt; {
    cell.addEventListener(&apos;click&apos;, function() {
      // Close all other expanded cells first
      cells.forEach(c =&gt; {
        if (c !== cell) c.classList.remove(&apos;expanded&apos;);
      });
      // Toggle current cell
      this.classList.toggle(&apos;expanded&apos;);
    });
  });
});
&lt;/script&gt;

&lt;div class=&quot;format-table-wrapper&quot;&gt;
  &lt;table class=&quot;format-table&quot;&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Data Type&lt;/th&gt;
        &lt;th&gt;Format 0&lt;/th&gt;
        &lt;th&gt;Format 1&lt;/th&gt;
        &lt;th&gt;Format 2&lt;/th&gt;
        &lt;th&gt;Format 3&lt;/th&gt;
        &lt;th&gt;Format 4&lt;/th&gt;
        &lt;th&gt;Format 5&lt;/th&gt;
        &lt;th&gt;Format 6&lt;/th&gt;
        &lt;th&gt;Format 7&lt;/th&gt;
        &lt;th&gt;Format 9&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;DateTime&lt;/td&gt;
        &lt;td&gt;10/07/25 02:30 PM&lt;/td&gt;
        &lt;td&gt;10/07/25 02:30:52 PM&lt;/td&gt;
        &lt;td&gt;10/07/25 02:30 PM&lt;/td&gt;
        &lt;td&gt;10/07/25 02:30:52 PM&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;2025-10-07T11:30:52Z&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Date&lt;/td&gt;
        &lt;td&gt;10/07/2025&lt;/td&gt;
        &lt;td&gt;10/07/2025&lt;/td&gt;
        &lt;td&gt;100725D&lt;/td&gt;
        &lt;td&gt;25/10/07&lt;/td&gt;
        &lt;td&gt;*Octubre 7, 2025&lt;/td&gt;
        &lt;td&gt;100725&lt;/td&gt;
        &lt;td&gt;251007&lt;/td&gt;
        &lt;td&gt;*Oct 7, 2025&lt;/td&gt;
        &lt;td&gt;07/10/2025&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Time&lt;/td&gt;
        &lt;td&gt;2:30:52 PM&lt;/td&gt;
        &lt;td&gt;2:30:52 PM&lt;/td&gt;
        &lt;td&gt;143052T&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;14:30:52&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Guid&lt;/td&gt;
        &lt;td&gt;{FDBEA7C4-9205-4A3A-998E-D7B020529BC1}&lt;/td&gt;
        &lt;td&gt;{FDBEA7C4-9205-4A3A-998E-D7B020529BC1}&lt;/td&gt;
        &lt;td&gt;{FDBEA7C4-9205-4A3A-998E-D7B020529BC1}&lt;/td&gt;
        &lt;td&gt;FDBEA7C492054A3A998ED7B020529BC1&lt;/td&gt;
        &lt;td&gt;FDBEA7C4-9205-4A3A-998E-D7B020529BC1&lt;/td&gt;
        &lt;td&gt;(FDBEA7C4-9205-4A3A-998E-D7B020529BC1)&lt;/td&gt;
        &lt;td&gt;{0XFDBEA7C4,0X9205,0X4A3A,{0X99,0X8E,0XD7,0XB0,0X20,0X52,0X9B,0XC1}}&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;{FDBEA7C4-9205-4A3A-998E-D7B020529BC1}&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Decimal&lt;/td&gt;
        &lt;td&gt;1,234,567.89&lt;/td&gt;
        &lt;td&gt;1234567.89&lt;/td&gt;
        &lt;td&gt;1234567.89&lt;/td&gt;
        &lt;td&gt;1,234,567.89&lt;/td&gt;
        &lt;td&gt;1234567.89&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;1234567.89&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Boolean&lt;/td&gt;
        &lt;td&gt;*Sí&lt;/td&gt;
        &lt;td&gt;*Sí&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;TRUE&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Option&lt;/td&gt;
        &lt;td&gt;*Pedido abierto&lt;/td&gt;
        &lt;td&gt;*Pedido abierto&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Enum&lt;/td&gt;
        &lt;td&gt;*Pedido abierto&lt;/td&gt;
        &lt;td&gt;*Pedido abierto&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Duration&lt;/td&gt;
        &lt;td&gt;*1 hora 1 minuto 1 segundo&lt;/td&gt;
        &lt;td&gt;*1 hora 1 minuto 1 segundo&lt;/td&gt;
        &lt;td&gt;1 2 1 4 1 6&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;P0DT1H1M1.0S&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;RecordId&lt;/td&gt;
        &lt;td&gt;Frmt. Tst. Tbl.: 1&lt;/td&gt;
        &lt;td&gt;Format Test Table: 1&lt;/td&gt;
        &lt;td&gt;Frmt. Tst. Tbl.: 1&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;[MyCompany.Tables.&quot;Frmt. Tst. Tbl.&quot;]: 1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;DateFormula&lt;/td&gt;
        &lt;td&gt;*1T+2D&lt;/td&gt;
        &lt;td&gt;*1T+2D&lt;/td&gt;
        &lt;td&gt;&amp;lt;1Q+2D&amp;gt;&lt;/td&gt;    
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;N/A&lt;/td&gt;
        &lt;td&gt;1Q+2D&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;

</description>
        <pubDate>Tue, 07 Oct 2025 10:00:00 +0200</pubDate>
        <link>https://tine.staric.net/blog/2025/format-cheatsheet/</link>
        <guid isPermaLink="true">https://tine.staric.net/blog/2025/format-cheatsheet/</guid>
        
        
        <category>al</category>
        
      </item>
    
      <item>
        <title>Copilot is just a fancy autocomplete (vol.2)</title>
        <description>&lt;p&gt;A few weeks ago, I was asked if I’d have time to deliver the &lt;strong&gt;&lt;em&gt;“Copilot is just a fancy autocomplete…“&lt;/em&gt;&lt;/strong&gt; session from this year’s &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.bctechdays.com/event&quot;&gt;BC TechDays&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; again for one of our partners at their internal event. As I was preparing, I realized that even in the span of &lt;strong&gt;3 months since the session&lt;/strong&gt;, my workflow with &lt;em&gt;AI&lt;/em&gt; has again changed so much that maybe it’s time for a &lt;strong&gt;part 2 of the blog post&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;&lt;em&gt;“fancy autocomplete”&lt;/em&gt;&lt;/strong&gt; story started at the beginning of the year with the &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;/blog/2025/copilot-fancy-autocomplete/&quot;&gt;part 1 blog post&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;. All the points from the blog post then matured into a &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=zTejuQzYAb8&quot;&gt;session at BC TechDays 2025&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;, which is also now available online.&lt;/p&gt;

&lt;div class=&quot;responsive-iframe&quot;&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/zTejuQzYAb8?si=NjXpEsyXi2BPUAqW&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;The session covered some &lt;strong&gt;best practices&lt;/strong&gt; to get the best results out of the &lt;em&gt;fancy autocomplete&lt;/em&gt;, but then quickly turned towards &lt;strong&gt;other capabilities&lt;/strong&gt;. &lt;em&gt;Copilot Edits&lt;/em&gt; take the center stage, as the next step, and we later progress into &lt;strong&gt;&lt;em&gt;agent mode&lt;/em&gt;&lt;/strong&gt;, and compare &lt;em&gt;VS Code&lt;/em&gt; to &lt;em&gt;Cursor&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you haven’t seen the session yet, or if you’re at the start of your &lt;em&gt;Copilot journey&lt;/em&gt;, I’d suggest you first take the &lt;strong&gt;90 minutes&lt;/strong&gt; to watch the session. &lt;strong&gt;This blog post picks up where the session ended.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The most significant change is that, during the session, I take a stance that &lt;strong&gt;&lt;em&gt;Agent mode is nice, but Edits is where our focus should be.&lt;/em&gt;&lt;/strong&gt; Realistically, during the last 3 months, I’ve &lt;strong&gt;exclusively used only Agent mode&lt;/strong&gt; for both &lt;em&gt;vibe-coded projects&lt;/em&gt; and &lt;em&gt;AL tasks&lt;/em&gt;, and I don’t even look at &lt;em&gt;Edits&lt;/em&gt; anymore.&lt;/p&gt;

&lt;p&gt;I do believe &lt;em&gt;Edits&lt;/em&gt; will eventually be removed. &lt;em&gt;Cursor&lt;/em&gt; has already removed its &lt;strong&gt;&lt;em&gt;“manual”&lt;/em&gt;&lt;/strong&gt; mode, which was the &lt;em&gt;Edit equivalent&lt;/em&gt;. But for the time being, I think they can still have a purpose. If you’re &lt;strong&gt;entirely new to the Copilot journey&lt;/strong&gt;, especially if you’re a &lt;em&gt;skeptic&lt;/em&gt;, it can be a nice, &lt;strong&gt;slower introduction&lt;/strong&gt; to how &lt;em&gt;AI&lt;/em&gt; can help read and modify multiple files at scale, compared to the &lt;em&gt;Agent mode&lt;/em&gt;, which will have more &lt;strong&gt;&lt;em&gt;“a mind of its own”&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I already introduced &lt;em&gt;Agents&lt;/em&gt; during the session, but here it comes again. &lt;strong&gt;&lt;em&gt;Agent mode is similar to Edit mode&lt;/em&gt;&lt;/strong&gt;, except that it tries to &lt;strong&gt;find its own context&lt;/strong&gt; for the changes it needs to make, and it has access to &lt;strong&gt;&lt;em&gt;“tools”&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/context.png&quot; alt=&quot;Agent Context&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I made a &lt;strong&gt;big deal&lt;/strong&gt; in the session about &lt;strong&gt;&lt;em&gt;taking control of your context&lt;/em&gt;&lt;/strong&gt;. But it feels like that’s &lt;strong&gt;less important now&lt;/strong&gt;. If you have specific files that you want the agent to look at, by all means, pull them in. But also rest assured that it will do a &lt;strong&gt;pretty good job&lt;/strong&gt; at finding the files on its own:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/context-2.png&quot; alt=&quot;Agent Context 2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s able to do that because it has &lt;strong&gt;access to tools&lt;/strong&gt;. It’s no longer just &lt;em&gt;reading content&lt;/em&gt; and &lt;em&gt;generating new tokens&lt;/em&gt;. It can &lt;strong&gt;take action within your IDE&lt;/strong&gt;, like &lt;em&gt;search for files&lt;/em&gt;, &lt;em&gt;run commands&lt;/em&gt;, &lt;em&gt;do web search&lt;/em&gt;, &lt;em&gt;check errors and warnings&lt;/em&gt;, …&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/tools.png&quot; alt=&quot;Agent Tools&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We’ll return to &lt;strong&gt;tools&lt;/strong&gt; when we discuss &lt;em&gt;MCP Servers&lt;/em&gt;, but for now, know that this is the &lt;strong&gt;&lt;em&gt;“secret sauce”&lt;/em&gt;&lt;/strong&gt; behind the power of &lt;em&gt;Agent mode&lt;/em&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;agent-settings-for-best-results&quot;&gt;Agent Settings for Best Results&lt;/h2&gt;

&lt;p&gt;To have the &lt;strong&gt;best results with Agents&lt;/strong&gt;, here are two settings you should enable:&lt;/p&gt;

&lt;h3 id=&quot;todo-list-tool&quot;&gt;Todo List Tool&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/todo-setting.png&quot; alt=&quot;Todo Setting&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This tool will allow the agent to &lt;strong&gt;create its own to-do list&lt;/strong&gt; as it works through a more extensive task. It helps the agent &lt;strong&gt;&lt;em&gt;“remember”&lt;/em&gt;&lt;/strong&gt; what it has to implement throughout the length of a more extended conversation. Here’s how it looks:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/todo-agent.png&quot; alt=&quot;Todo Agent&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/todo-example.png&quot; alt=&quot;Todo Example&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As it goes through the individual &lt;em&gt;Todo items&lt;/em&gt;, it &lt;strong&gt;marks them as completed&lt;/strong&gt;, helping it maintain focus on the current task.&lt;/p&gt;

&lt;h3 id=&quot;max-requests-setting&quot;&gt;Max Requests Setting&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/max-requests.png&quot; alt=&quot;Max Requests&quot; /&gt;&lt;/p&gt;

&lt;p&gt;By default, the value is set to &lt;strong&gt;15&lt;/strong&gt; (or at least it was, when I changed it for myself). After &lt;em&gt;15 steps&lt;/em&gt;, the agent stops and asks the user, &lt;strong&gt;&lt;em&gt;“Do you want to continue or stop the process?”&lt;/em&gt;&lt;/strong&gt;. Fifteen steps is &lt;strong&gt;not a lot of steps&lt;/strong&gt;. I quickly grew annoyed at having to keep pressing &lt;em&gt;‘Continue’&lt;/em&gt; on larger tasks, so I changed mine to &lt;strong&gt;100&lt;/strong&gt;. I haven’t seen an interruption since, and I’m free to &lt;strong&gt;browse LinkedIn&lt;/strong&gt; as the agent implements larger items of my work. :P&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;copilot-instructions&quot;&gt;Copilot Instructions&lt;/h2&gt;

&lt;p&gt;If you want to get the &lt;strong&gt;best results with the agent mode&lt;/strong&gt;, you should couple it with &lt;strong&gt;&lt;em&gt;copilot-instructions.md&lt;/em&gt;&lt;/strong&gt;. The instructions file will be &lt;strong&gt;appended to each request&lt;/strong&gt; you make to the agent. During the session, I explained how you can use this file as a &lt;strong&gt;&lt;em&gt;“memory” file&lt;/em&gt;&lt;/strong&gt;, to make sure the agent codes the way you would. Do you omit spaces in &lt;em&gt;object and field names&lt;/em&gt;, but agents use them? &lt;strong&gt;Put it in the file.&lt;/strong&gt; Do you want to have explicit parameters with &lt;em&gt;internal functions&lt;/em&gt; (Insert, Modify, Delete), but the agent skips them? &lt;strong&gt;Put it in the file.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When creating these files, you don’t have to &lt;strong&gt;start from scratch&lt;/strong&gt;. &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/in/dmitrykatson&quot;&gt;Dmitry Katson&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; shared his &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://alguidelines.dev/docs/vibe-coding/&quot;&gt;instruction files&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; with the community. I keep mine &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://github.com/tinestaric/BCExamples/blob/Master/.github/copilot-instructions.md&quot;&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;, but they’re far less extensive, as I’ve generally had &lt;strong&gt;good experience&lt;/strong&gt; with the proposed code, with only minor adjustments needed that I keep in the instructions file.&lt;/p&gt;

&lt;p&gt;However, something that I haven’t mentioned in the session, nor captured in the above instructions file, that I think should be part of &lt;strong&gt;every single repository&lt;/strong&gt; is a &lt;strong&gt;&lt;em&gt;project overview&lt;/em&gt;&lt;/strong&gt;. Every project should have an explanation of what it’s about, what the architecture looks like, and the main patterns used. And even there, you don’t have to &lt;strong&gt;start from scratch&lt;/strong&gt;. A few releases ago, the &lt;em&gt;VSC team&lt;/em&gt; added the action to &lt;strong&gt;generate the project overview instructions&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/generate-instructions.png&quot; alt=&quot;Generate Instructions&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When executed, it will prompt the agent with a &lt;strong&gt;preset prompt&lt;/strong&gt; to scan the workspace and create (or update) the instructions file with the project overview:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/instructions-prompt.png&quot; alt=&quot;Instructions Prompt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And here are some of the results&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/instructions-example.png&quot; alt=&quot;Instructions Example&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Considering how &lt;strong&gt;easy it is&lt;/strong&gt; to include an overview, I don’t see a reason why you wouldn’t go and include it in your repos &lt;strong&gt;&lt;em&gt;right now&lt;/em&gt;&lt;/strong&gt;. :P&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;models&quot;&gt;Models&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/models.jpg&quot; alt=&quot;Models&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Next, I want to touch on the topic of &lt;strong&gt;models&lt;/strong&gt; again. My stance remains the same; &lt;strong&gt;&lt;em&gt;Claude models appear to be the best for AL&lt;/em&gt;&lt;/strong&gt;. However, we’ve recently received &lt;strong&gt;two others&lt;/strong&gt; that I sometimes like to switch to, especially when &lt;em&gt;Claude&lt;/em&gt; appears to &lt;strong&gt;&lt;em&gt;get drunk&lt;/em&gt;&lt;/strong&gt; and proposes &lt;em&gt;stupid solutions&lt;/em&gt; (this one was suggested to &lt;em&gt;code hand-crafter&lt;/em&gt; &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/in/ajkauffmann&quot;&gt;AJ Kauffmann&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/dumb-agent.jpg&quot; alt=&quot;Dumb Agent&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;models I like for AL&lt;/strong&gt; are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Claude Sonnet 4&lt;/em&gt;&lt;/strong&gt; – My default, &lt;strong&gt;best results&lt;/strong&gt;, most consistent, overall the &lt;strong&gt;best experience&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;GPT-5&lt;/em&gt;&lt;/strong&gt; – After poor &lt;em&gt;AL results&lt;/em&gt; with &lt;em&gt;4o&lt;/em&gt;, &lt;em&gt;4.1&lt;/em&gt;, &lt;em&gt;o3&lt;/em&gt;, &lt;em&gt;4.5&lt;/em&gt;, this is the &lt;strong&gt;first GPT model&lt;/strong&gt; that I can easily trust to get things right. I like to use it as an alternative to &lt;em&gt;Sonnet&lt;/em&gt; when I have tasks that require &lt;strong&gt;more of an analysis&lt;/strong&gt; of a codebase. Apparently, &lt;em&gt;Copilot&lt;/em&gt; was also tweaked to work better with this model, as evidenced by these &lt;strong&gt;two settings&lt;/strong&gt; I found (but I haven’t noticed too much of a difference)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/gpt-5-settings.png&quot; alt=&quot;GPT-5 Settings&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Grok Code Fast 1&lt;/em&gt;&lt;/strong&gt; – This is the only &lt;strong&gt;&lt;em&gt;“included”&lt;/em&gt;&lt;/strong&gt; model (meaning &lt;em&gt;“free”&lt;/em&gt;) that I like to use with &lt;em&gt;AL&lt;/em&gt;. It’s &lt;strong&gt;faster&lt;/strong&gt; than the other two, and it does the work without all the meaningless tokens for &lt;strong&gt;&lt;em&gt;“You’re absolutely right!”&lt;/em&gt;&lt;/strong&gt;. It, however, &lt;strong&gt;doesn’t handle picture input&lt;/strong&gt;. Less relevant for &lt;em&gt;AL&lt;/em&gt;, but worth keeping in mind.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still remember that if you don’t have any of these models available in your model selector, you (or your organization admin) have to enable them in &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://github.com/settings/copilot/features&quot;&gt;GitHub Settings&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And if you’re of the &lt;strong&gt;exploratory type&lt;/strong&gt; when it comes to models, you absolutely have to give &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/in/stefano-demiliani&quot;&gt;Stefano Demiliani&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; a follow. He &lt;strong&gt;tests each new model with AL&lt;/strong&gt; and reports his impression on all social media. I only touch models that &lt;strong&gt;he approves&lt;/strong&gt;. :P&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;mcp-servers&quot;&gt;MCP Servers&lt;/h2&gt;

&lt;p&gt;Lastly, let’s talk about the &lt;strong&gt;&lt;em&gt;MCP servers&lt;/em&gt;&lt;/strong&gt;. They’ve been &lt;strong&gt;all around the news&lt;/strong&gt; for a few months now, but I think we’re right at the brink of getting a &lt;strong&gt;&lt;em&gt;“perfect bouquet of MCPs”&lt;/em&gt;&lt;/strong&gt; to use for &lt;em&gt;AL development&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Before I jump into my current recommendations, I want to provide a &lt;strong&gt;short explanation&lt;/strong&gt; of what an &lt;em&gt;MCP server&lt;/em&gt; is. I’ll use the same analogy I used in the recent session for our partner.&lt;/p&gt;

&lt;p&gt;Everyone understands the concept of &lt;strong&gt;&lt;em&gt;VSC extensions&lt;/em&gt;&lt;/strong&gt;. We have this &lt;em&gt;vanilla development environment&lt;/em&gt; that allows us to write code. Still, if we want to add any &lt;strong&gt;additional functionalities&lt;/strong&gt; to it (like support for &lt;em&gt;AL&lt;/em&gt;), we need to install extensions for it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/vsc-extension.png&quot; alt=&quot;VSC Extension&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But an extension that’s built for &lt;em&gt;VSC&lt;/em&gt; will &lt;strong&gt;not work&lt;/strong&gt; for &lt;em&gt;Visual Studio&lt;/em&gt; or &lt;em&gt;JetBrains&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/vsc-extension-error.png&quot; alt=&quot;VSC Extension Error&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The same story applies to &lt;em&gt;Copilot&lt;/em&gt; and other &lt;strong&gt;&lt;em&gt;AI-assisted development tools&lt;/em&gt;&lt;/strong&gt;. Remember that the &lt;strong&gt;true power&lt;/strong&gt; of the &lt;em&gt;GitHub Copilot Agent mode&lt;/em&gt; comes from its tools:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/tools.png&quot; alt=&quot;Tools&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But if we create an additional tool for &lt;em&gt;GitHub Copilot&lt;/em&gt;, it won’t work with tools like &lt;strong&gt;&lt;em&gt;Claude Code&lt;/em&gt;&lt;/strong&gt;, &lt;em&gt;Codex&lt;/em&gt;, &lt;em&gt;Aider&lt;/em&gt;, and others.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/copilot-extension.png&quot; alt=&quot;Copilot Extension&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And that’s what &lt;strong&gt;&lt;em&gt;MCP (Model Context Protocol)&lt;/em&gt;&lt;/strong&gt; is here to solve. If we build our &lt;em&gt;Copilot extension&lt;/em&gt; in a way that it’s &lt;strong&gt;&lt;em&gt;MCP-compliant&lt;/em&gt;&lt;/strong&gt;, then &lt;strong&gt;any tool that supports MCP&lt;/strong&gt; can make use of our additional tools.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/mcp-extension.png&quot; alt=&quot;MCP Extension&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Regardless of whether the tools are added through an &lt;em&gt;extension&lt;/em&gt; or through an &lt;em&gt;MCP server&lt;/em&gt;, their &lt;strong&gt;behaviour remains the same&lt;/strong&gt;. It’s just a protocol of how the tools &lt;strong&gt;&lt;em&gt;“plug in”&lt;/em&gt;&lt;/strong&gt; to the client.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/mcp-examples.png&quot; alt=&quot;MCP Examples&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A lot of people have used the &lt;strong&gt;&lt;em&gt;“USB-C”&lt;/em&gt;&lt;/strong&gt; analogy in the past, and it does fit. Let’s say you have a printer that plugs into your PC via &lt;em&gt;USB-C&lt;/em&gt;. The &lt;strong&gt;&lt;em&gt;“logic”&lt;/em&gt;&lt;/strong&gt; is in the printer, not in the &lt;em&gt;USB-C connector&lt;/em&gt;. The connector is just a connector. But it allows us to &lt;strong&gt;plug the printer into anything&lt;/strong&gt; that has a &lt;em&gt;USB-C hole&lt;/em&gt;. The same applies to &lt;em&gt;MCPs&lt;/em&gt;. The &lt;strong&gt;logic is in the “extension”&lt;/strong&gt;; &lt;em&gt;MCP&lt;/em&gt; ensures that we can plug our logic into &lt;strong&gt;anything that has an MCP hole&lt;/strong&gt;. :D&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Okay, so what &lt;strong&gt;&lt;em&gt;MCPs should you try out&lt;/em&gt;&lt;/strong&gt; for &lt;em&gt;AL development&lt;/em&gt;?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;GitHub / Azure DevOps&lt;/em&gt;&lt;/strong&gt; – depending on what you use for issue tracking, these are an &lt;strong&gt;obvious choice&lt;/strong&gt;. It enables the agent to &lt;em&gt;read and update tasks&lt;/em&gt;, as well as handle &lt;em&gt;pipelines&lt;/em&gt;. &lt;strong&gt;Very useful&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Microsoft Docs&lt;/em&gt;&lt;/strong&gt; – I’ve had several tasks where the agent surprised me with &lt;strong&gt;&lt;em&gt;“let me search the documentation”&lt;/em&gt;&lt;/strong&gt;, found the &lt;em&gt;BC related articles&lt;/em&gt;, and continued with &lt;strong&gt;much better context&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the &lt;strong&gt;easy-to-install MCPs&lt;/strong&gt; that you can find by opening the &lt;strong&gt;&lt;em&gt;MCP library&lt;/em&gt;&lt;/strong&gt; from &lt;em&gt;VSC&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/browser-mcp.png&quot; alt=&quot;Browser MCP&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;A worthwhile &lt;strong&gt;&lt;em&gt;non-MCP recommendation&lt;/em&gt;&lt;/strong&gt; is something you might already have in your toolbox: &lt;strong&gt;&lt;em&gt;NAB AL Tools&lt;/em&gt;&lt;/strong&gt;. &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/in/johanneswikman/&quot;&gt;Johannes&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; added &lt;em&gt;Copilot tools&lt;/em&gt; to his already existing extension, and if you’re working with &lt;strong&gt;translations&lt;/strong&gt;, the following blog is a &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/pulse/language-model-tools-ai-translations-johannes-wikman-qiebf/?trackingId=BI%2BPcryjdFcmxbaQ3u5O5Q%3D%3D&quot;&gt;MUST READ&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;But back to &lt;em&gt;MCPs&lt;/em&gt;, the following ones will need a &lt;strong&gt;bit more manual work&lt;/strong&gt;, but everything is explained in the readme files:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://github.com/StefanMaron/AL-Dependency-MCP-Server&quot;&gt;Al-symbols-mcp&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; - Created by &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/in/stefan-maron-709928206/&quot;&gt;Stefan Maron&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;, it allows the agent to &lt;strong&gt;look into the dependencies&lt;/strong&gt; for additional context. Usually, when working on an app, the agent can only see the code from the workspace. This &lt;em&gt;MCP&lt;/em&gt; adds tools that can give it access to &lt;strong&gt;objects that are in the .alpackages folder&lt;/strong&gt;. It’s &lt;strong&gt;brilliant for PTE apps&lt;/strong&gt; where most of the context is in the dependencies—more information in the &lt;em&gt;ReadMe file&lt;/em&gt; in the repo.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.npmjs.com/package/bc-code-intelligence-mcp&quot;&gt;Bc-code-intelligence-mcp&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; - This one is from &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/in/jeremyvyska/&quot;&gt;Jeremy Vyska&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;, another &lt;strong&gt;must-follow person&lt;/strong&gt; if you’re into the &lt;em&gt;Copilot story&lt;/em&gt;. It’s hard to believe &lt;em&gt;Jeremy&lt;/em&gt; was in the &lt;strong&gt;&lt;em&gt;“autocomplete”&lt;/em&gt;&lt;/strong&gt; camp 6 months ago, because he has already done &lt;strong&gt;so much&lt;/strong&gt; to push the &lt;em&gt;Copilot story&lt;/em&gt; further for &lt;em&gt;AL devs&lt;/em&gt;! Anyway, this &lt;em&gt;MCP&lt;/em&gt; brings &lt;strong&gt;&lt;em&gt;A TON of AL dev guidelines&lt;/em&gt;&lt;/strong&gt; to your agent so it writes code as it should be written. It’s hard to explain everything this &lt;em&gt;MCP&lt;/em&gt; does, so it’s best if you check &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://nubimancy.com/2025/09/22/bc-code-intelligence-launch-announcement/&quot;&gt;Jeremy’s recent blog post&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;The last &lt;em&gt;MCP&lt;/em&gt; recommendations are a bit more &lt;strong&gt;experimental&lt;/strong&gt;. I have not spent enough time with them, but I have a &lt;strong&gt;strong feeling&lt;/strong&gt; they’re going to be very instrumental in the future:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://github.com/oraios/serena&quot;&gt;Serena&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; - By default, the agent navigates through your codebase using &lt;em&gt;text search&lt;/em&gt;. &lt;em&gt;Serena&lt;/em&gt; enables it to do &lt;strong&gt;semantic retrieval&lt;/strong&gt;, navigate through the codebase on the &lt;em&gt;symbol level&lt;/em&gt;, and exploit &lt;strong&gt;relational structure&lt;/strong&gt;. Usually, these advanced tools would be reserved for &lt;strong&gt;&lt;em&gt;“mature”&lt;/em&gt;&lt;/strong&gt; languages like &lt;em&gt;Python&lt;/em&gt;, &lt;em&gt;C#&lt;/em&gt;, or &lt;em&gt;JavaScript&lt;/em&gt;, but thanks to the &lt;strong&gt;&lt;em&gt;“madman”&lt;/em&gt;&lt;/strong&gt; named &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/in/torben-l%C3%B8kke-leth/&quot;&gt;Torben Leth&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;, they have just received a &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://github.com/oraios/serena/pull/593&quot;&gt;PR&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; with &lt;em&gt;AL support&lt;/em&gt;. I use &lt;em&gt;madman&lt;/em&gt; in a &lt;strong&gt;very complimentary tone&lt;/strong&gt;. :D &lt;em&gt;Torben&lt;/em&gt; is one of those people who gets involved in projects that I’d consider &lt;strong&gt;impossible&lt;/strong&gt; (like &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/posts/torben-l%C3%B8kke-leth_emea2025-msdyn365bc-activity-7367584117506465795-4_Uy?utm_source=social_share_send&amp;amp;utm_medium=member_desktop_web&amp;amp;rcm=ACoAACLigK8B1d5zenhN3aNEAnW6bNGFZZNzj_E&quot;&gt;bringing BC to Linux&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;He also just published the &lt;em&gt;MCP&lt;/em&gt; for the &lt;strong&gt;&lt;em&gt;AL Object ID Ninja&lt;/em&gt;&lt;/strong&gt; made by &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/in/vjeko/&quot;&gt;Vjeko&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;. Finally, agents will &lt;strong&gt;know which ID to propose&lt;/strong&gt;, but I have yet to test this one out: &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://github.com/SShadowS/al-objid-mcp-server&quot;&gt;al-objid-mcp-server&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Don’t go crazy installing &lt;strong&gt;&lt;em&gt;all the MCPs&lt;/em&gt;&lt;/strong&gt; you can find. Every added tool will &lt;strong&gt;eat away at the context window&lt;/strong&gt; of an agent, and there will be &lt;strong&gt;&lt;em&gt;less space for context&lt;/em&gt;&lt;/strong&gt; about your project or the goal. &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/in/stefano-demiliani&quot;&gt;Stefano&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; described it perfectly in his recent &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://demiliani.com/2025/09/04/model-context-protocol-and-the-too-many-tools-problem/&quot;&gt;blog post&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;VS Code&lt;/em&gt; will warn you if you’re trying to fit &lt;strong&gt;more than 128 tools&lt;/strong&gt; in your prompt, but even &lt;em&gt;128&lt;/em&gt; is already a &lt;strong&gt;huge number&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fancyautocompletevol2/too-many-tools.png&quot; alt=&quot;Too Many Tools&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Azure DevOps MCP&lt;/em&gt; alone comes with &lt;strong&gt;70 new tools&lt;/strong&gt;, and do you really need to have all the actions related to &lt;em&gt;pipelines&lt;/em&gt; enabled when your usual workflow consists of &lt;strong&gt;&lt;em&gt;reading and maybe updating work items?&lt;/em&gt;&lt;/strong&gt; I think not.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Was this too long? Nah, I had to put all the current ideas into &lt;em&gt;vol.2&lt;/em&gt;. I already know there’s going to be a &lt;strong&gt;&lt;em&gt;vol. 3&lt;/em&gt;&lt;/strong&gt; soon. The next area worth mentioning is going to be the &lt;strong&gt;background agents&lt;/strong&gt;, the ones we no longer oversee. I also think we’ll see &lt;strong&gt;many more MCPs&lt;/strong&gt; in the next few months, until they all consolidate into an &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=waldo.al-extension-pack&quot;&gt;AL Extension Pack&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; equivalent of &lt;em&gt;MCP servers&lt;/em&gt;. I think it’s also time to &lt;strong&gt;revisit the topic of code review&lt;/strong&gt;, this time through an &lt;em&gt;AI-assisted lens&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let me know your thoughts on any of the social media, and see you on the next post!&lt;/p&gt;

</description>
        <pubDate>Sun, 21 Sep 2025 10:00:00 +0200</pubDate>
        <link>https://tine.staric.net/blog/2025/copilot-fancy-autocomplete-vol2/</link>
        <guid isPermaLink="true">https://tine.staric.net/blog/2025/copilot-fancy-autocomplete-vol2/</guid>
        
        
        <category>al</category>
        
      </item>
    
      <item>
        <title>100 Areopa Webinars Visualized</title>
        <description>&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://areopa.academy/&quot;&gt;Areopa Webinars&lt;/a&gt;&lt;/strong&gt; just hit number &lt;strong&gt;&lt;em&gt;100!&lt;/em&gt;&lt;/strong&gt; And I used this milestone as another &lt;strong&gt;visualization side-project.&lt;/strong&gt; :P&lt;/p&gt;

&lt;p&gt;If you don’t know &lt;strong&gt;Areopa&lt;/strong&gt;, the short description of it is a &lt;em&gt;YouTube channel&lt;/em&gt; of BC-related webinars that have been going strong most Mondays since &lt;strong&gt;&lt;em&gt;2019!&lt;/em&gt;&lt;/strong&gt; &lt;a href=&quot;https://www.linkedin.com/in/lvanvugt/&quot;&gt;Luc van Vugt&lt;/a&gt; leads it, but the &lt;strong&gt;&lt;em&gt;whole community&lt;/em&gt;&lt;/strong&gt; contributed to the content! Luc, if you’re reading this, you really do deserve a shoutout… You didn’t only create this huge (and still growing) collection of BC knowledge, but you’ve also setup a platform where &lt;strong&gt;&lt;em&gt;anyone&lt;/em&gt;&lt;/strong&gt; can come and share what they know with &lt;strong&gt;&lt;em&gt;everyone&lt;/em&gt;&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Here’s why I care so much about Areopa. I’ve been very lucky to be in a position, where I get to travel to multiple conferences every year, and I listen to so many amazing sessions. It’s easy to &lt;strong&gt;stay &lt;em&gt;“up to date”&lt;/em&gt;&lt;/strong&gt; when I hear about best practices all the time, directly from the industry’s best. But &lt;strong&gt;&lt;em&gt;not everyone gets that chance&lt;/em&gt;&lt;/strong&gt;. And here’s where Areopa really excels, it brings that same level of high-quality sessions to &lt;strong&gt;&lt;em&gt;everyone! For free! Forever!&lt;/em&gt;&lt;/strong&gt; Conference sessions are great, but we &lt;strong&gt;forget&lt;/strong&gt; most of what we hear the very &lt;strong&gt;next day.&lt;/strong&gt; Areopa makes it available forever. You can always get back to a certain topic once it’s relevant to you again.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I didn’t want to just give praise to Areopa though. As I mentioned at the beginning, I used this opportunity as another &lt;strong&gt;visualization side project.&lt;/strong&gt; Back in November, after &lt;strong&gt;Ignite&lt;/strong&gt;, I came across &lt;a href=&quot;https://markmap.js.org/full#?d=gist:38520ad7809001358250f62e88695a80:2024-Ignite-Book-of-News.md&quot;&gt;this visualization&lt;/a&gt; by &lt;a href=&quot;https://www.linkedin.com/in/nityan/&quot;&gt;Nitya Narasimhan&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/areopavisualized/ignite.png&quot; alt=&quot;Ignite sessions visualized&quot; /&gt;&lt;/p&gt;

&lt;p&gt;She grouped all Ignite sessions into groups and visualized them. I was so amazed with the concept that I knew I needed to find some time to play around with &lt;strong&gt;Markmap&lt;/strong&gt;. My initial idea was (and still is) to make a similar visualization for all sessions at &lt;strong&gt;Directions EMEA&lt;/strong&gt;, so I can have an easier time deciding what I’d like to listen to. I mean, it’s super hard to keep track of &lt;strong&gt;~200 sessions&lt;/strong&gt; otherwise…&lt;/p&gt;

&lt;p&gt;I’ve started being &lt;strong&gt;more involved with Areopa&lt;/strong&gt; within the past few months. &lt;em&gt;Finding speakers, moderating webinars, and presenting my own topics,&lt;/em&gt; and as we were approaching this &lt;strong&gt;triple-digit number&lt;/strong&gt;, I realized I have no clue about half the videos available on the channel. I was thinking about how could we make this huge collection of videos &lt;strong&gt;more discoverable.&lt;/strong&gt; You can already see how this turned into a perfect opportunity to play with &lt;strong&gt;Markmap&lt;/strong&gt;. Below you’ll find the result. I invite you to explore the graph, and if any video sparks your interest, go ahead and watch it! :P&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;But just before I let you go, I wanted to share a few &lt;strong&gt;“fun facts”&lt;/strong&gt; that I found as I was handling the data of the videos:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First video’s publish date: Jul 7, 2019&lt;/li&gt;
  &lt;li&gt;Total number of unique speakers: 64&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;top-5-most-frequent-presenters&quot;&gt;Top 5 most frequent presenters:&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/in/lvanvugt/&quot;&gt;Luc van Vugt&lt;/a&gt; – 13 webinars&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/in/tobiasfenster&quot;&gt;Tobias Fenster&lt;/a&gt; – 8 webinars&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/in/ajkauffmann&quot;&gt;Arend-Jan Kauffmann&lt;/a&gt; – 6 webinars&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/in/ericwauters&quot;&gt;waldo&lt;/a&gt; – 6 webinars&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/in/dmitrykatson&quot;&gt;Dmitry Katson&lt;/a&gt; – 5 webinars&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;top-5-most-watched-webinars&quot;&gt;Top 5 most watched webinars:&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtube.com/watch?v=mNgx1d7TAXQ&quot;&gt;Replicating Business Central data to Microsoft Dataverse (former CDS)&lt;/a&gt; (by &lt;a href=&quot;https://www.linkedin.com/in/andrey-baludin-9a7014191&quot;&gt;Andrey Baludin&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtube.com/watch?v=4X-tSjL0XaA&quot;&gt;Working with files in Dynamics 365 Business Central SaaS&lt;/a&gt; (by &lt;a href=&quot;https://www.linkedin.com/in/stefano-demiliani&quot;&gt;Stefano Demiliani&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtube.com/watch?v=Fjz9LgviV2Q&quot;&gt;Sync your Business Central data with Azure Data Lake&lt;/a&gt; (by &lt;a href=&quot;https://www.linkedin.com/in/bertverbeek&quot;&gt;Bert Verbeek&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtube.com/watch?v=3w018zjbUwQ&quot;&gt;Working with XML in AL&lt;/a&gt; (by &lt;a href=&quot;https://www.linkedin.com/in/ajkauffmann&quot;&gt;Arend-Jan Kauffmann&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtube.com/watch?v=yeKRuw9MtSQ&quot;&gt;OAuth authentication with Business Central APIs&lt;/a&gt; (by &lt;a href=&quot;https://www.linkedin.com/in/ajkauffmann&quot;&gt;Arend-Jan Kauffmann&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;most-common-topics&quot;&gt;Most common topics:&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Test Automation: 16 videos&lt;/li&gt;
  &lt;li&gt;AL: 12 videos&lt;/li&gt;
  &lt;li&gt;DevOps: 12 videos&lt;/li&gt;
  &lt;li&gt;Functional: 12 videos&lt;/li&gt;
  &lt;li&gt;Power Platform: 7 videos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Interested in what’s under these topics? Well then I won’t keep you waiting any longer, here’s the visualization. You can zoom in and out, move the graph around, and click on the nodes to expand or collapse them. &lt;strong&gt;&lt;em&gt;Click around, explore!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;!-- Load required libraries --&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/d3@7&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;https://cdn.jsdelivr.net/npm/markmap-view&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;https://cdn.jsdelivr.net/npm/markmap-lib&quot;&gt;&lt;/script&gt;

&lt;!-- Mindmap container --&gt;
&lt;div id=&quot;mindmap&quot;&gt;
  &lt;svg style=&quot;width: 100%; height: 800px&quot;&gt;&lt;/svg&gt;
&lt;/div&gt;

&lt;!-- Initialize and render mindmap --&gt;
&lt;script&gt;
document.addEventListener(&apos;DOMContentLoaded&apos;, async () =&gt; {
  const { Markmap } = window.markmap;
  
  // Fetch markdown content from latest release
  try {
    // First, get the latest release info
    const releaseResponse = await fetch(&apos;https://api.github.com/repos/tinestaric/AreopaAnalyzer/releases/latest&apos;);
    const release = await releaseResponse.json();
    
    // Find the videos_markmap.md asset
    const asset = release.assets.find(a =&gt; a.name === &apos;videos_markmap.md&apos;);
    
    if (!asset) {
      throw new Error(&apos;videos_markmap.md not found in latest release&apos;);
    }
    
    // Use CORS proxy to fetch the asset content
    const proxyUrl = &apos;https://api.codetabs.com/v1/proxy?quest=&apos; + encodeURIComponent(asset.browser_download_url);
    const response = await fetch(proxyUrl);
    const markdown = await response.text();

    // Transform markdown to mindmap data
    const { Transformer } = window.markmap;
    const transformer = new Transformer();
    const { root } = transformer.transform(markdown);

    // Configure visualization options
    const { deriveOptions } = window.markmap;
    const jsonOptions = {
      initialExpandLevel: 2,
      colorFreezeLevel: 3,
      duration: 1000,
      spacingVertical: 10
    };
    const options = deriveOptions(jsonOptions);

    // Create and render visualization
    const svg = d3.select(&quot;#mindmap svg&quot;);
    const mm = Markmap.create(svg.node(), options, root);
  } catch (error) {
    console.error(&apos;Error fetching markmap data from release:&apos;, error);
    document.getElementById(&apos;mindmap&apos;).innerHTML = &apos;&lt;p style=&quot;text-align: center; color: #666; padding: 40px;&quot;&gt;Unable to load visualization. Please try again later.&lt;/p&gt;&apos;;
  }
});
&lt;/script&gt;

</description>
        <pubDate>Sun, 02 Feb 2025 09:00:00 +0100</pubDate>
        <link>https://tine.staric.net/blog/2025/areopa-visualized/</link>
        <guid isPermaLink="true">https://tine.staric.net/blog/2025/areopa-visualized/</guid>
        
        
        <category>al</category>
        
      </item>
    
      <item>
        <title>Copilot is just a fancy autocomplete...</title>
        <description>&lt;p&gt;December was a very &lt;strong&gt;Copilot&lt;/strong&gt; month for me. After seeing some glimpses of new capabilities at &lt;em&gt;Directions&lt;/em&gt; and more later at &lt;em&gt;Ignite&lt;/em&gt;, I decided to spend some time trying out the &lt;em&gt;“new”&lt;/em&gt; features of &lt;em&gt;GitHub Copilot&lt;/em&gt; in &lt;em&gt;Visual Studio Code&lt;/em&gt;. They’re not really new; they’ve been around for a while. I just never used them, as I thought they were &lt;strong&gt;not useful for AL development.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Boy was I wrong…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Copilot is no longer just a fancy autocomplete…&lt;/em&gt; &lt;strong&gt;&lt;em&gt;it has become much more!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn’t want to get my hopes up too much with &lt;em&gt;AL&lt;/em&gt;, so I started testing everything out with more &lt;em&gt;mature&lt;/em&gt; languages like &lt;em&gt;C#&lt;/em&gt; and &lt;em&gt;TypeScript&lt;/em&gt;, but the more I tried the new features with &lt;em&gt;AL&lt;/em&gt;, the more I was convinced that I needed to start sharing that &lt;strong&gt;autocomplete is no longer the only useful part of Copilot!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They’ve now also introduced a &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://docs.github.com/en/copilot/managing-copilot/managing-copilot-as-an-individual-subscriber/about-github-copilot-free&quot;&gt;free tier for GitHub Copilot&lt;/a&gt;.&lt;/em&gt;&lt;/strong&gt; This means everyone can now &lt;em&gt;“just try it”&lt;/em&gt;, without any commitments!&lt;/p&gt;

&lt;p&gt;For everyone who’s going to try Copilot out, or has been using it for a while, I wanted to share a few &lt;strong&gt;very concrete examples&lt;/strong&gt; of how I’ve been using features like &lt;strong&gt;Inline Chat&lt;/strong&gt;, &lt;strong&gt;Copilot Edits&lt;/strong&gt;, and &lt;strong&gt;Copilot Chat for AL&lt;/strong&gt; (and a few for other languages), that will hopefully move you to explore more than just the &lt;em&gt;“tab, tab, tab”&lt;/em&gt; behavior of AI.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;auto-complete&quot;&gt;Auto-complete&lt;/h2&gt;

&lt;p&gt;I really won’t spend much time here, this is the first use-case you’ve noticed, and it feels most natural. &lt;em&gt;Do you see a grey suggested code you like?&lt;/em&gt; A tab and it’s yours 😉&lt;/p&gt;

&lt;p&gt;I use this all the time, from &lt;em&gt;procedure declaration&lt;/em&gt;, and &lt;em&gt;adding fields&lt;/em&gt;, to &lt;em&gt;commenting code&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/complete-procedure.png&quot; alt=&quot;Auto-complete procedure declaration&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/complete-field.png&quot; alt=&quot;Auto-complete field declaration&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you didn’t write quality ToolTips so far, then don’t expect quality ToolTips from Copilot&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/complete-comment.png&quot; alt=&quot;Auto-complete comment&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Yeah, yeah,&lt;/em&gt; it’s not a meaningful comment, but you get the idea.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;One very important note to keep in mind is &lt;strong&gt;how Copilot knows what to suggest&lt;/strong&gt;. It looks at your open files and takes &lt;strong&gt;up to 4 files as context&lt;/strong&gt;. If you’ve got more than 4 opened, it &lt;strong&gt;&lt;em&gt;tries&lt;/em&gt;&lt;/strong&gt; to do its best to take something, which is not always the most appropriate file. So, if you want to have &lt;strong&gt;better Copilot suggestions&lt;/strong&gt;, close the unrelated tabs and &lt;strong&gt;only keep the relevant ones open.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was used to having &lt;em&gt;tens of files opened&lt;/em&gt;, so I needed to get used to &lt;em&gt;right-clicking&lt;/em&gt; the tabs a lot and closing anything that was no longer relevant.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/close-tab.png&quot; alt=&quot;Close tabs&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The autocomplete is something you’ll discover immediately, and even this feature alone saves enough time to fully justify the &lt;em&gt;20$&lt;/em&gt; price tag of the &lt;em&gt;Pro&lt;/em&gt; version.&lt;/p&gt;

&lt;p&gt;The most interesting thing about autocomplete though? It’s &lt;strong&gt;&lt;a href=&quot;https://githubnext.com/projects/copilot-next-edit-suggestions/&quot;&gt;what’s coming to it next!&lt;/a&gt;&lt;/strong&gt; Awesome feature, can’t wait for it to be released.&lt;/p&gt;

&lt;p&gt;Anyway, let’s move on.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;copilot-edits&quot;&gt;Copilot edits&lt;/h2&gt;

&lt;p&gt;In my opinion, &lt;strong&gt;this is the game changer!&lt;/strong&gt; This is the feature that &lt;strong&gt;turns coding into a conversation.&lt;/strong&gt; A very brief description of it is a chatbot, that won’t only suggest you a code snippet that you should paste somewhere but goes in and &lt;em&gt;modifies&lt;/em&gt; your files.&lt;/p&gt;

&lt;p&gt;I first used it with a &lt;em&gt;C#&lt;/em&gt; project, and &lt;strong&gt;I was blown away!&lt;/strong&gt; I was working on some old code I wrote a year ago, and I asked Copilot to apply &lt;em&gt;best practices&lt;/em&gt;. Within a few seconds my code was much more readable, had safe &lt;em&gt;typecasting&lt;/em&gt;, used &lt;em&gt;HashSets&lt;/em&gt; instead of &lt;em&gt;Lists&lt;/em&gt;, and was just generally better code than I would ever write on my own. Devs that work with C# daily would still run circles around me as I &lt;em&gt;“craft”&lt;/em&gt; my prompts, but &lt;strong&gt;I made much better progress than I could without it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/csharp.png&quot; alt=&quot;Apply best practices to C# code&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Later I started playing with some &lt;em&gt;TypeScript&lt;/em&gt;. I have very little knowledge of TypeScript, and it would probably take me days to learn the basics of &lt;em&gt;React&lt;/em&gt; on top. But &lt;strong&gt;within a few hours&lt;/strong&gt;, I was able to make an Azure DevOps extension in TypeScript that allows me to visualize Page Scripting yaml files directly in DevOps. It was such a fun journey. More on that in a separate post one day! :P&lt;/p&gt;

&lt;div style=&quot;display: flex; justify-content: space-between; gap: 10px;&quot;&gt;
    &lt;img src=&quot;/images/copilotintro/pagescript-raw.png&quot; alt=&quot;Page Scripting YAML in raw view&quot; style=&quot;width: 48%;&quot; /&gt;
    &lt;img src=&quot;/images/copilotintro/pagescript-pretty.png&quot; alt=&quot;Page Scripting YAML in pretty view&quot; style=&quot;width: 48%;&quot; /&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Okay, okay, Tine, &lt;strong&gt;everything works great with C# and TypeScript&lt;/strong&gt;, we’re here for AL, &lt;strong&gt;does it work with AL?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;It does! And wonderfully so!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Of course, not to the extent that it works with &lt;em&gt;C#&lt;/em&gt;. You won’t get any meaningful best practices applied in &lt;em&gt;AL&lt;/em&gt;. We still have the same issue of &lt;strong&gt;&lt;em&gt;“There’s not enough AL data to train these models”&lt;/em&gt;&lt;/strong&gt;. But here are two very important parts that make &lt;strong&gt;Copilot Edits work so well for AL.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, you can &lt;strong&gt;&lt;em&gt;select a different LLM model&lt;/em&gt;&lt;/strong&gt; to power the code generation. &lt;em&gt;Autocomplete&lt;/em&gt; is powered by &lt;strong&gt;&lt;em&gt;GPT-4o&lt;/em&gt;&lt;/strong&gt;, which is &lt;em&gt;“meh”&lt;/em&gt; for &lt;em&gt;AL&lt;/em&gt;, but in &lt;em&gt;Copilot Edits&lt;/em&gt;, you can switch between models.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/models.png&quot; alt=&quot;Different models in Copilot Edits&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Claude 3.5 Sonnet is so much better when it comes to AL.&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;Why? I have no clue.&lt;/em&gt; But it just is. So, if you’re going to play with Edits (or Chat and Inline) for &lt;em&gt;AL&lt;/em&gt;, &lt;strong&gt;&lt;em&gt;switch to Claude immediately!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you don’t see Claude, you have to enable it in your &lt;a href=&quot;https://github.com/settings/copilot&quot;&gt;GitHub settings&lt;/a&gt; (or if you’re getting your license through an organization, the organization admin has to enable it for you).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A better model is just part of the story though. The second part is &lt;strong&gt;&lt;em&gt;“limited scope”&lt;/em&gt;&lt;/strong&gt;. I mentioned in the auto-complete part that it will look at &lt;em&gt;up to 4 open files&lt;/em&gt; and try to give you a good suggestion. Well, with Edits &lt;strong&gt;&lt;em&gt;you are in complete control of the context&lt;/em&gt;&lt;/strong&gt; that the model receives.&lt;/p&gt;

&lt;p&gt;You add files you’d like the model to consider to the working set. Either by &lt;em&gt;dragging and dropping&lt;/em&gt; them or by clicking &lt;em&gt;“add files”&lt;/em&gt;. Once you have files in the working set, those are the &lt;em&gt;only files that the model will focus on.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Okay, enough theory,&lt;/em&gt; let me show you &lt;strong&gt;how I’ve used Edits for some AL workloads.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A bit of a backstory.&lt;/p&gt;

&lt;p&gt;We were recently working with &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://wair.ai/&quot;&gt;Wair&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;, a partner that has a backend service for &lt;strong&gt;AI inventory predictions&lt;/strong&gt;. They wanted to make life easier for ISVs and VARs in the world of BC who would want to integrate a BC solution with their backend system.&lt;/p&gt;

&lt;p&gt;Usually, each partner would have to develop logic that &lt;em&gt;authenticates&lt;/em&gt; against the backend, &lt;em&gt;builds&lt;/em&gt; the HTTP request, and then &lt;em&gt;sends&lt;/em&gt; the request in the correct format. To make life easier we built a library app that introduces a set of temporary tables that match the &lt;strong&gt;HTTP request payload&lt;/strong&gt;, and a set of codeunits that turn these tables into &lt;em&gt;JSON payloads&lt;/em&gt; and send the request to the correct &lt;em&gt;endpoint&lt;/em&gt;. That way, the BC partner needs to insert a record in the table and call a procedure with that table. &lt;strong&gt;That’s it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can imagine that when the backend introduces a new endpoint, we want to add new tables and codeunits to support it.&lt;/p&gt;

&lt;p&gt;So, I tried if I could add a table with &lt;em&gt;Edits&lt;/em&gt; that would follow all the conventions we have in other tables.&lt;/p&gt;

&lt;p&gt;In this case, &lt;strong&gt;Brand is the new endpoint&lt;/strong&gt; I want to support. I create a new Brand.Table.al file (I’ve noticed &lt;em&gt;Copilot&lt;/em&gt; sometimes struggles with creating files, so I prefer to create them on my own and let Copilot fill them in), and pull it in the working set. I added one of the &lt;strong&gt;existing tables&lt;/strong&gt;, so Copilot knows how I want to have it &lt;strong&gt;structured&lt;/strong&gt;, and here comes the prompt:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Create a Brand table with brand ID, code, and description. It should follow the structure of the Category table.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Seconds later&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/edits-table.png&quot; alt=&quot;Copilot Edits with Table&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Everything on the left side was just generated. &lt;strong&gt;Of course, it struggles with Object IDs.&lt;/strong&gt; No one likes object IDs. Not even Copilot! :D&lt;/p&gt;

&lt;p&gt;But you can see that in seconds I have a table with the &lt;em&gt;namespace&lt;/em&gt;, &lt;em&gt;properties&lt;/em&gt; that we use, and even a &lt;em&gt;check for non-empty code&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You could say &lt;strong&gt;&lt;em&gt;“Yeah, but that’s a simple example”&lt;/em&gt;&lt;/strong&gt;. Fair, but I’d argue the &lt;strong&gt;&lt;em&gt;easy and boring parts are the best ones to automate.&lt;/em&gt;&lt;/strong&gt; Yes, you could &lt;em&gt;copy-paste&lt;/em&gt; the other table and change a few things until it fits your new use case, but &lt;strong&gt;a)&lt;/strong&gt; you’re bound to miss something, &lt;strong&gt;b)&lt;/strong&gt; you’ll still be slower, and &lt;strong&gt;c)&lt;/strong&gt; wouldn’t you rather spend your time on something else?  Who wants to work on boring stuff anyway?&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I suggest you always &lt;strong&gt;&lt;em&gt;finish the Edits session&lt;/em&gt;&lt;/strong&gt; when you’re done with those files and open a new one. &lt;strong&gt;&lt;em&gt;LLM models operate on a limited context window&lt;/em&gt;&lt;/strong&gt;, and even though we hear about context windows in the millions of tokens, anything &lt;strong&gt;&lt;em&gt;above ~8k tokens&lt;/em&gt;&lt;/strong&gt; starts to give &lt;strong&gt;&lt;em&gt;diminishing results&lt;/em&gt;&lt;/strong&gt;, so it’s best to start a new session every time.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/copilot-close.png&quot; alt=&quot;Copilot Edits - Close session&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Anyway, let me continue with the example of what else I &lt;em&gt;“made in seconds”&lt;/em&gt;. We now have the table for our new endpoint. We need a codeunit that will &lt;em&gt;transform&lt;/em&gt; the content of this &lt;strong&gt;table into a JSON payload&lt;/strong&gt; and call the appropriate &lt;em&gt;endpoint&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I’ll again create an empty file where I want my codeunit for handling the &lt;em&gt;Brand API&lt;/em&gt; to be created:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/codeunit-empty.png&quot; alt=&quot;Empty Codeunit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This time, I’m pulling in my &lt;em&gt;empty file&lt;/em&gt;, my &lt;em&gt;existing codeunit&lt;/em&gt;, and the &lt;em&gt;table we just created&lt;/em&gt;, so Copilot knows what properties are available for brands. The prompt is straightforward&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/codeunit-prompt.png&quot; alt=&quot;Copilot Edits prompt for Codeunit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And a second later, boom, a &lt;strong&gt;codeunit that builds the payload and sends the request&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/edits-codeunit.png&quot; alt=&quot;Copilot Edits with Codeunit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The important part here is that &lt;strong&gt;&lt;em&gt;I passed a similar implementation as part of the context.&lt;/em&gt;&lt;/strong&gt; Out of curiosity, I tried a similar prompt &lt;strong&gt;without&lt;/strong&gt; including the Category API Handler. I was still &lt;strong&gt;pleasantly surprised&lt;/strong&gt; by the result:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/edits-codeunit-raw.png&quot; alt=&quot;Copilot Edits with Raw Codeunit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It doesn’t follow the structure I need for this project, but &lt;strong&gt;it knows how HTTP requests are built and sent in AL.&lt;/strong&gt; This would still be a cool start if I needed to build my first handler that I could later spread across other tables.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Okay, &lt;strong&gt;tables?&lt;/strong&gt; &lt;em&gt;Check.&lt;/em&gt; &lt;strong&gt;Codeunits?&lt;/strong&gt; &lt;em&gt;Check.&lt;/em&gt; &lt;strong&gt;Pages?&lt;/strong&gt; &lt;em&gt;Eh, trust me, it works, I don’t want to make more screenshots.&lt;/em&gt; &lt;strong&gt;What about tests?&lt;/strong&gt; &lt;em&gt;Same principle.&lt;/em&gt; Do you have similar tests? Then yes. Look:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/edits-tests.png&quot; alt=&quot;Copilot Edits with Tests&quot; /&gt;&lt;/p&gt;

&lt;p&gt;These tests worked because &lt;strong&gt;the overall structure was very similar between the existing objects&lt;/strong&gt; and the new ones. But testing can really become a breeze. &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.linkedin.com/in/vjeko&quot;&gt;Vjeko&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; had an awesome session on it at &lt;strong&gt;Directions&lt;/strong&gt;, and hopefully, he’ll do an extended version at &lt;strong&gt;&lt;a href=&quot;https://www.bctechdays.com/event&quot;&gt;BC Tech Days&lt;/a&gt;&lt;/strong&gt;. He’s been preaching &lt;strong&gt;Interfaces as a way of truly testing your code in isolation&lt;/strong&gt; for a few years now, and with Copilot, most of those &lt;strong&gt;tests can now be generated.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;But here’s the catch.&lt;/em&gt;&lt;/strong&gt; You have to use &lt;strong&gt;interfaces&lt;/strong&gt; in your code. Your procedures have to have a &lt;strong&gt;single responsibility&lt;/strong&gt;. Your files have to be &lt;strong&gt;small&lt;/strong&gt;. To get the most out of &lt;em&gt;AI&lt;/em&gt;, your code should be &lt;strong&gt;SOLID&lt;/strong&gt;. But you should be following &lt;em&gt;SOLID&lt;/em&gt; principles for your own sake anyway, so take this as another nudge towards spending more time on writing &lt;em&gt;SOLID&lt;/em&gt; code if you haven’t started already.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I know not everything is a &lt;strong&gt;greenfield project&lt;/strong&gt; and you sometimes have to deal with big codeunits. Here are two prompts that I’ve used on legacy code with Edits&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Move global procedures to local&lt;/em&gt;&lt;br /&gt;
It analyses all the global variables and moves them to procedures if they’re not used anywhere else.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Extract procedure X to codeunit Y&lt;/em&gt;&lt;br /&gt;
Unlike a simple extraction, it will also add a codeunit variable in the local scope and replace the call with the codeunit’s procedure call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both will still start to &lt;strong&gt;take too long&lt;/strong&gt; if your files go &lt;strong&gt;beyond 500 LoC&lt;/strong&gt;, so please, for the sake of AI, keep your files small :P&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Even in perfect conditions, Copilot may sometimes take long or even get stuck. It happens, it’s best to &lt;strong&gt;accept it&lt;/strong&gt;, stop the execution, discard the changes, and try again:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/edits-stop.png&quot; alt=&quot;Copilot Edits Stop&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;This section is getting long&lt;/em&gt;; we have to move on. But I really am excited about this feature, it brings so much joy to me when I get to &lt;em&gt;blitz through code&lt;/em&gt;. &lt;strong&gt;&lt;em&gt;Coding is the boring part, it’s the problem-solving that’s the fun part.&lt;/em&gt;&lt;/strong&gt; That’s also why I don’t believe &lt;strong&gt;AI is replacing devs&lt;/strong&gt;, it’s just helping with the boring parts…&lt;/p&gt;

&lt;p&gt;Let’s take a look at more incremental edits next.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;inline-chat&quot;&gt;Inline Chat&lt;/h2&gt;

&lt;p&gt;This one was available for a while, but I never really found a use case for it that I’d like. Well, now I have a couple, so let me give you some ideas.&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;add-comments&quot;&gt;Add comments&lt;/h4&gt;

&lt;p&gt;I’m right now working on a &lt;strong&gt;legacy project&lt;/strong&gt;. &lt;em&gt;Large files&lt;/em&gt;, not much new is created, it’s about &lt;em&gt;maintaining the existing code&lt;/em&gt;. In the days of &lt;em&gt;NAV&lt;/em&gt;, we didn’t have the &lt;em&gt;internal&lt;/em&gt; access modifier, so everything was either &lt;em&gt;public&lt;/em&gt; or &lt;em&gt;global&lt;/em&gt;. The &lt;strong&gt;problem with global&lt;/strong&gt; is that you can’t change a thing, as it’s a &lt;strong&gt;breaking change&lt;/strong&gt;, so on this project, we decided to make all procedures internal. If a procedure needs to be public, it should have &lt;strong&gt;XML documentation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, you start with &lt;em&gt;three slashes&lt;/em&gt; to get the docs snippet&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/inline-snippet.png&quot; alt=&quot;Inline Chat - Comments Snippet&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You accept it, but then you see this: 😮&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/inline-docs-empty.png&quot; alt=&quot;Inline Chat - Empty XML Document&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Well, that’s going to be boring…&lt;/em&gt; The names are self-explanatory, but we agreed on what we agreed, and I have to fill in all the parameters. &lt;em&gt;If I try to autocomplete this, I’ll tab for a while…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ctrl + i to the rescue!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/inline-docs-full.png&quot; alt=&quot;Inline Chat - Full XML Documentation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You could also select the whole procedure and prompt with &lt;strong&gt;&lt;em&gt;“/doc”&lt;/em&gt;&lt;/strong&gt;, but I found this way to give me better results.&lt;/p&gt;

&lt;p&gt;Am I super proud of this comment? No, but hey, &lt;strong&gt;legacy has better fires we can fight.&lt;/strong&gt; :D&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;subscribe-to-events&quot;&gt;Subscribe to events&lt;/h4&gt;

&lt;p&gt;Subscribing to events is something &lt;strong&gt;very common&lt;/strong&gt; for an AL developer. Here’s how I’d go about &lt;strong&gt;&lt;em&gt;before Copilot.&lt;/em&gt;&lt;/strong&gt; Let’s say I’m extending &lt;em&gt;Sales-Post&lt;/em&gt;. I’d first find the event I like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/sales-post-event.png&quot; alt=&quot;Event in Sales - Post codeunit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Next, in my subscriber codeunit, I’d use the &lt;strong&gt;&lt;em&gt;eventsub&lt;/em&gt;&lt;/strong&gt; snippet:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/event-subscriber-snippet.png&quot; alt=&quot;Event Subscriber snippet&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Start filling out all the parameters…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/snippet-1.png&quot; alt=&quot;Fill the codeunit in the subscriber&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Go back to the &lt;em&gt;Sales-Post&lt;/em&gt; codeunit and copy the event name…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/snippet-2.png&quot; alt=&quot;Fill the event name in the subscriber&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Remove element name, think for a second about skipping the event on missing permissions or license, but then just put &lt;strong&gt;false&lt;/strong&gt;, because hey, I always put false…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/snippet-3.png&quot; alt=&quot;Fill remaining parameters in the subscriber&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Go back to Sales-Post and copy the signature of the event publisher…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/snippet-4.png&quot; alt=&quot;Add Event signature to subscriber&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And now finally, I can &lt;em&gt;add my logic&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I mean, it’s &lt;strong&gt;not the slowest process&lt;/strong&gt; in the world, but here’s my &lt;strong&gt;new alternative.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I still find the event publisher, but I immediately copy the event signature, go to my codeunit, press &lt;strong&gt;Ctrl + i&lt;/strong&gt; and ask for a subscriber&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/copilot-subscribe.png&quot; alt=&quot;Prompt Copilot for a subscriber&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Seconds later…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/copilot-subscribe-generated.png&quot; alt=&quot;Copilot generated subscriber&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is now my preferred way!&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;add-fields&quot;&gt;Add fields&lt;/h4&gt;

&lt;p&gt;I mentioned earlier that Edits start to &lt;strong&gt;break down on large files.&lt;/strong&gt; I try to keep them &lt;strong&gt;sub-300 LoC.&lt;/strong&gt; But that’s not always an option, so Inline Chat helps in those cases. I use it for adding multiple fields:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/inline-fields.png&quot; alt=&quot;Inline Chat - Add Fields&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Perfect? No. Faster? Yup.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;A few more prompts that I occasionally use&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Forward slash&lt;/em&gt;&lt;br /&gt;
a very lazy way to switch all the pasted paths from backslashes to forward slashes as I’d otherwise have to escape them in JSON files 😅&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Add label for regex that matches 123-1414-2124-223&lt;/em&gt;&lt;br /&gt;
Quickly get regex patterns that match the validations I’m looking for&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;chat&quot;&gt;Chat&lt;/h2&gt;

&lt;p&gt;This one I admit I don’t use as much for AL, but it’s quite useful for other languages. You can probably tell that &lt;strong&gt;I love Copilot Edits.&lt;/strong&gt; But Edits want to implement something &lt;strong&gt;too quickly.&lt;/strong&gt; Like that junior developer that wants to code before I’m &lt;em&gt;halfway through my sentence…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That’s when I turn to chat as a starting point. I discuss what I want to do, and &lt;strong&gt;when I’m happy with the suggested code, I pass that suggestion to Edits&lt;/strong&gt; so it gets added.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/chat-edits.png&quot; alt=&quot;Pass Chat Context to Edits&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Edits will take the conversation as context and will apply the changes in the necessary files. Chat is a nice &lt;em&gt;“safeguard”&lt;/em&gt; when I’m not completely sure how I want to solve the problem.&lt;/p&gt;

&lt;p&gt;It’s also useful when the file is &lt;strong&gt;too big for Edits&lt;/strong&gt; (&amp;gt; 500 Loc), as I can always copy-paste the suggested snippet into the correct place.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;a-few-final-nuggets&quot;&gt;A few final nuggets&lt;/h2&gt;

&lt;p&gt;Here are a few more tiny notes that can help you make the most out of &lt;strong&gt;GitHub Copilot:&lt;/strong&gt;&lt;/p&gt;

&lt;h4 id=&quot;commit-message&quot;&gt;Commit message&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/commit-message.png&quot; alt=&quot;Suggest commit message&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Generate commit messages for staged changes.&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;rename&quot;&gt;Rename&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;/images/copilotintro/rename.png&quot; alt=&quot;Suggest names&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Renaming suggestions for just about everything that can be renamed&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;different-models&quot;&gt;Different models&lt;/h4&gt;

&lt;p&gt;If you’re only working with AL, &lt;strong&gt;stick to Claude.&lt;/strong&gt; If you’re working with other languages, here’s how I see and choose models&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Claude:&lt;/em&gt;&lt;/strong&gt; Great for generating new code, but gets “drunk” quickly if there’s too much context and starts suggesting weird things&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;o1:&lt;/em&gt;&lt;/strong&gt; Great for reviewing existing code. It takes longer and there’s a rate limit after which you have to wait for some time before using o1 again&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;GPT 4o:&lt;/em&gt;&lt;/strong&gt; fastest responses but seems to shoot from the hip a lot. Less accurate results with complex requests.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;o1-mini:&lt;/em&gt;&lt;/strong&gt; Somewhere between o1 and GPT 4o, It’s faster than o1, and better at reviewing code than GPT 4o. I usually use it when I hit the rate limit of o1.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Gemini 1.5:&lt;/em&gt;&lt;/strong&gt; No idea yet, I’m still waiting for it to be released&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;free-vs-pro-tier&quot;&gt;Free vs Pro Tier&lt;/h4&gt;

&lt;p&gt;The &lt;em&gt;free tier&lt;/em&gt; right now gives you&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Access to &lt;strong&gt;Claude 3.5 Sonnet&lt;/strong&gt; models&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;2000&lt;/em&gt; Code completions per month&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;50&lt;/em&gt; chats per month (edits and chat count towards the same limit)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my opinion, that’s enough for you to test these features out if they work for you. But as I mentioned at the beginning, even the &lt;strong&gt;autocomplete brings enough value to AL developers&lt;/strong&gt; that it’s worth the 20$ for a Pro license.&lt;/p&gt;

&lt;p&gt;Pro has &lt;em&gt;unlimited completions&lt;/em&gt; and chats (except the o1 rate limit)&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;quick-recap&quot;&gt;Quick Recap&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Copilot Edits&lt;/em&gt;&lt;/strong&gt; – Best for large-scale code changes, refactoring, and generating new files.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Inline Chat&lt;/em&gt;&lt;/strong&gt; – Great for small additions, comments, and repetitive edits.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Copilot Chat&lt;/em&gt;&lt;/strong&gt; – A fallback when edits feel too aggressive or when brainstorming solutions.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Autocomplete&lt;/em&gt;&lt;/strong&gt; – The most familiar feature – saves time with quick code suggestions (worth the Pro tier alone).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Model Tip&lt;/em&gt;&lt;/strong&gt; – Use Claude 3.5 Sonnet for AL work; it handles AL better than GPT-4o.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Scope Control&lt;/em&gt;&lt;/strong&gt; – Keep files small and focus Copilot on relevant files for better suggestions.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;final-remarks&quot;&gt;Final remarks&lt;/h2&gt;

&lt;p&gt;This was a very long post. But that’s because there’s so much to say about this &lt;strong&gt;awesome tool&lt;/strong&gt; that we have. I honestly believe it’s the &lt;strong&gt;next big thing.&lt;/strong&gt; Then again, I’ve been an enthusiast for a while :P&lt;/p&gt;

&lt;p&gt;Anyway, I’m playing with the idea of &lt;strong&gt;going deeper into this topic&lt;/strong&gt; at some conferences this year. &lt;em&gt;Share more examples, show more pitfalls, and explain more of the under-the-hood parts.&lt;/em&gt; What do you think? I’d love to know your opinion. You can let me know on &lt;a href=&quot;https://www.linkedin.com/in/tinestaric/&quot;&gt;LinkedIn&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/tinestaric.bsky.social&quot;&gt;BlueSky&lt;/a&gt;, or &lt;a href=&quot;https://twitter.com/TineStaric&quot;&gt;X&lt;/a&gt;!&lt;/p&gt;

</description>
        <pubDate>Fri, 03 Jan 2025 09:00:00 +0100</pubDate>
        <link>https://tine.staric.net/blog/2025/copilot-fancy-autocomplete/</link>
        <guid isPermaLink="true">https://tine.staric.net/blog/2025/copilot-fancy-autocomplete/</guid>
        
        
        <category>al</category>
        
      </item>
    
      <item>
        <title>Trigger Sequence in AL</title>
        <description>&lt;p&gt;Here’s a small &lt;strong&gt;&lt;em&gt;“let me bookmark this”&lt;/em&gt;&lt;/strong&gt; post.&lt;/p&gt;

&lt;p&gt;I often did not completely understand trigger and event sequences in AL. I &lt;strong&gt;&lt;em&gt;sort of&lt;/em&gt;&lt;/strong&gt; knew them, but I doubted myself just enough to keep going back to the documentation. It’s not like the documentation is bad, but every time it took me just a few additional minutes to figure out the answer, that I decided I wanted to &lt;strong&gt;visualize&lt;/strong&gt; this.&lt;/p&gt;

&lt;p&gt;It came in handy for me, maybe it will for you too. I’d love to hear if you’d add any other sequence!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Few things to remember that I didn’t want to jam into the visualizations&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Apps with &lt;strong&gt;lower IDs&lt;/strong&gt; execute their triggers and subscribers &lt;strong&gt;before those with higher IDs.&lt;/strong&gt;&lt;br /&gt;
For example, if &lt;em&gt;App A&lt;/em&gt; has an ID like &lt;em&gt;“1bs3..”&lt;/em&gt; and &lt;em&gt;App B&lt;/em&gt; has &lt;em&gt;“c23f..”&lt;/em&gt;, App A’s triggers and subscribers will always run first.&lt;/li&gt;
  &lt;li&gt;Within the same App, objects with &lt;strong&gt;lower Object IDs&lt;/strong&gt; execute their triggers and subscribers &lt;strong&gt;before those with higher Object IDs&lt;/strong&gt;&lt;br /&gt;
A subscriber in &lt;em&gt;Codeunit 50000&lt;/em&gt; will execute before one in &lt;em&gt;Codeunit 50001&lt;/em&gt;. Similarly, triggers in &lt;em&gt;Table Extension 50000&lt;/em&gt; will run before those in &lt;em&gt;Table Extension 50001&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;Subscribers in the same codeunit are executed in &lt;strong&gt;alphabetical order&lt;/strong&gt; by procedure name&lt;br /&gt;
For instance, a procedure named &lt;em&gt;MySubscriber1&lt;/em&gt; will run before &lt;em&gt;MySubscriber2&lt;/em&gt;, regardless of their order in the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When looking at the Lookup flow, remember that &lt;strong&gt;only one OnLookup trigger can be executed&lt;/strong&gt; at a time. Once the first one is executed, others are ignored.&lt;/p&gt;

&lt;p&gt;I made a small trigger logger for the purpose of this post, you can find it &lt;strong&gt;&lt;a href=&quot;https://github.com/tinestaric/BCExamples/tree/Master/TriggerLogger&quot;&gt;here&lt;/a&gt;&lt;/strong&gt;. It’s a simple extension that logs the trigger and event sequence to a table.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 style=&quot;text-align: center;&quot;&gt;Trigger Flow Diagrams&lt;/h3&gt;

&lt;link rel=&quot;stylesheet&quot; href=&quot;/assets/css/graph-demo.css&quot; /&gt;

&lt;div class=&quot;graph-demo-button-group&quot;&gt;
    &lt;div class=&quot;graph-demo-group-heading&quot;&gt;Record Operation Triggers:&lt;/div&gt;
    &lt;div class=&quot;graph-demo-button-container&quot;&gt;
        &lt;button class=&quot;graph-demo-flow-button&quot; onclick=&quot;showFlow(&apos;insert&apos;)&quot;&gt;Insert&lt;/button&gt;
        &lt;button class=&quot;graph-demo-flow-button&quot; onclick=&quot;showFlow(&apos;modify&apos;)&quot;&gt;Modify&lt;/button&gt;
        &lt;button class=&quot;graph-demo-flow-button&quot; onclick=&quot;showFlow(&apos;delete&apos;)&quot;&gt;Delete&lt;/button&gt;
        &lt;button class=&quot;graph-demo-flow-button&quot; onclick=&quot;showFlow(&apos;rename&apos;)&quot;&gt;Rename&lt;/button&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;graph-demo-button-group&quot;&gt;
    &lt;div class=&quot;graph-demo-group-heading&quot;&gt;Field Operation Triggers:&lt;/div&gt;
    &lt;div class=&quot;graph-demo-button-container&quot;&gt;
        &lt;button class=&quot;graph-demo-flow-button&quot; onclick=&quot;showFlow(&apos;validate&apos;)&quot;&gt;Validate&lt;/button&gt;
        &lt;button class=&quot;graph-demo-flow-button&quot; onclick=&quot;showFlow(&apos;lookup&apos;)&quot;&gt;Lookup&lt;/button&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;graph-demo-diagram-container&quot;&gt;
    &lt;div class=&quot;mermaid&quot; id=&quot;flowchart-container&quot;&gt;
        flowchart TD
        Start --&amp;gt; NoChart
        NoChart(Click a button to display trigger flow)
    &lt;/div&gt;
&lt;/div&gt;

&lt;script src=&quot;https://unpkg.com/mermaid@9.4.3/dist/mermaid.min.js&quot;&gt;&lt;/script&gt;

&lt;script type=&quot;module&quot;&gt;
  let mermaidLoaded = false;
  mermaid.initialize({ 
    startOnLoad: true,
    securityLevel: &apos;loose&apos;
  });
  // Wait for mermaid to be fully loaded
  mermaid.init();
  mermaidLoaded = true;
  import { loadFlow } from &apos;/assets/scripts/loadFlows.js&apos;;
  window.showFlow = async function(type) {
    if (!mermaidLoaded) {
      await new Promise(resolve =&gt; setTimeout(resolve, 500));
    }
    const chart = await loadFlow(type);
    const container = document.getElementById(&apos;flowchart-container&apos;);
    container.innerHTML = chart;    
    mermaid.render(&apos;newFlow&apos;, chart, function(svgCode) {
      container.innerHTML = svgCode;
    });
  }
&lt;/script&gt;

</description>
        <pubDate>Wed, 01 Jan 2025 09:00:00 +0100</pubDate>
        <link>https://tine.staric.net/blog/2025/trigger-sequence/</link>
        <guid isPermaLink="true">https://tine.staric.net/blog/2025/trigger-sequence/</guid>
        
        
        <category>al</category>
        
      </item>
    
      <item>
        <title>Forget how; when should we use preprocessing symbols?</title>
        <description>&lt;p&gt;This was one of the most &lt;em&gt;difficult&lt;/em&gt; blog posts to write so far. I kept changing directions of what I even wanted to explain with it…&lt;/p&gt;

&lt;p&gt;You see, I come up with blog ideas by &lt;strong&gt;explaining concepts&lt;/strong&gt; to colleagues internally, and when a topic comes up &lt;strong&gt;often enough&lt;/strong&gt;, I write it down as it’s easier to share with everyone. The other aspect is when I see a concept &lt;em&gt;“in the wild”&lt;/em&gt; which gets me thinking about how we should &lt;strong&gt;adopt it internally&lt;/strong&gt;, and to structure my thoughts, I write it down, making it easier to &lt;strong&gt;defend my position&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The topic of &lt;strong&gt;preprocessing directives&lt;/strong&gt; comes mainly from this second part. The way &lt;strong&gt;&lt;em&gt;Companial&lt;/em&gt;&lt;/strong&gt; is positioned, I get to work with &lt;em&gt;many different partners&lt;/em&gt; every year. Sometimes I learn from them, sometimes I can suggest best practices that I’ve seen elsewhere. In my opinion, &lt;strong&gt;&lt;em&gt;it’s a win-win&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I recently started working with a partner that has a &lt;em&gt;huge&lt;/em&gt; solution. I mean &lt;strong&gt;&lt;em&gt;HUGE!&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;20k+ objects.&lt;/em&gt; That’s a &lt;em&gt;crazy&lt;/em&gt; amount of code. It was also my first time using &lt;em&gt;preprocessing directives&lt;/em&gt;, which made me wonder &lt;em&gt;why I had never needed them before&lt;/em&gt;.&lt;/p&gt;

&lt;h4 id=&quot;heres-why&quot;&gt;Here’s why:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;I was building apps &lt;strong&gt;from scratch&lt;/strong&gt;, that no one had a dependency on yet &lt;br /&gt;
It means I didn’t have to &lt;em&gt;worry about breaking changes&lt;/em&gt; or the obsoletion process&lt;/li&gt;
  &lt;li&gt;We targeted &lt;strong&gt;SaaS tenants only&lt;/strong&gt; &lt;br /&gt;
There was no need to support &lt;em&gt;multiple versions&lt;/em&gt; of codebases in parallel&lt;/li&gt;
  &lt;li&gt;The work items were never &lt;strong&gt;too big&lt;/strong&gt; &lt;br /&gt;
We never had &lt;em&gt;big long-lived branches&lt;/em&gt; that would cause merge conflicts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above three bullet points took me &lt;strong&gt;quite a while to realize&lt;/strong&gt;, but once I wrapped my head around it, it was finally clear to me when I’d want to use the &lt;em&gt;preprocessing symbols&lt;/em&gt; and that’s what I want to share with you today.&lt;/p&gt;

&lt;p&gt;I’m not going to talk too much about &lt;strong&gt;&lt;em&gt;how&lt;/em&gt;&lt;/strong&gt; they work, &lt;a href=&quot;https://www.linkedin.com/in/yzhums&quot;&gt;Yun&lt;/a&gt; had a great &lt;a href=&quot;https://yzhums.com/2606/&quot;&gt;blog post&lt;/a&gt; on that topic, back when they were introduced. Another &lt;a href=&quot;https://nataliekarolak.wordpress.com/2023/05/08/conditional-directives-clean22-demystified/&quot;&gt;great post&lt;/a&gt; I found was by &lt;a href=&quot;https://x.com/KarolakNatalie&quot;&gt;Natalie&lt;/a&gt;. She describes how we should test code with conditional directives very well. But I’ll focus mainly on &lt;strong&gt;&lt;em&gt;when&lt;/em&gt;&lt;/strong&gt; is a good idea to use them for our apps.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;introduce-changes-gradually-announce-changes&quot;&gt;Introduce changes gradually (announce changes)&lt;/h3&gt;

&lt;p&gt;We’re now in the &lt;em&gt;AppSource&lt;/em&gt; era. Anyone can potentially depend on the apps we publish there. We cannot just remove a public procedure from our app, as that &lt;strong&gt;might break any app&lt;/strong&gt; that is using it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hold on, aren’t obsoletion tags and &lt;strong&gt;App Source Cop&lt;/strong&gt; there to prevent that?&lt;/em&gt; &lt;em&gt;Yes!&lt;/em&gt; However, these tools only prevent &lt;strong&gt;hard-breaking changes&lt;/strong&gt;. Changes that would prevent an external party from even compiling their app against our new release. However, they do nothing when it comes to &lt;strong&gt;soft-breaking changes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Soft-breaking changes?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Changes that won’t &lt;strong&gt;prevent&lt;/strong&gt; an &lt;em&gt;external party&lt;/em&gt; from compiling their app, but their apps will &lt;strong&gt;no longer work as expected&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Take the removal of a field for example.&lt;/p&gt;

&lt;p&gt;Let’s look at the &lt;em&gt;“IC Partner G/L Acc. No.”&lt;/em&gt; That will be removed from &lt;em&gt;Gen. Journal Line&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/preprocessing/icglacc-start.png&quot; alt=&quot;IC G/L Account at the beginning&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We know we can’t just remove a field. &lt;em&gt;App Source Cop&lt;/em&gt; will be all up in our faces if we try that. So, we &lt;strong&gt;obsolete&lt;/strong&gt; it first, and we’ll remove it in one of the subsequent releases.&lt;/p&gt;

&lt;p&gt;But no cop would yell at us for removing the field’s trigger, on any of its references… Imagine if this is how we &lt;em&gt;obsoleted&lt;/em&gt; the field:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/preprocessing/icglacc-clean-wrong.png&quot; alt=&quot;IC G/L Account bad cleanup&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/preprocessing/icglacc-ref-clean-wrong.png&quot; alt=&quot;IC G/L Account reference bad cleanup&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We removed the trigger, and we removed the references, &lt;strong&gt;but hey, we marked the field as obsolete&lt;/strong&gt;. &lt;em&gt;We’re gradually introducing changes…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Yeah, no&lt;/em&gt;&lt;/strong&gt;. We still &lt;strong&gt;broke everyone&lt;/strong&gt; using this field, immediately after this &lt;strong&gt;update is deployed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;So, what’s the better way?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Wrap the changes in preprocessing symbols and clean them in a later release. Here’s how MS actually obsoleted this field.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/preprocessing/icglacc-end.png&quot; alt=&quot;IC G/L Account at the end&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/preprocessing/icglacc-ref-clean-right.png&quot; alt=&quot;IC G/L Account reference good cleanup&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It means that today, the field still &lt;strong&gt;works exactly as it did for years&lt;/strong&gt;. But anyone using it in their apps will get a &lt;em&gt;warning&lt;/em&gt; that they should move away from it. When the time comes, they will enable the &lt;strong&gt;&lt;em&gt;CLEAN22 symbol&lt;/em&gt;&lt;/strong&gt;, the field becomes removed, and the code is no longer executed.&lt;/p&gt;

&lt;p&gt;This becomes especially important if we want to &lt;strong&gt;&lt;em&gt;refactor events&lt;/em&gt;&lt;/strong&gt;. Let’s look at the following change.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/preprocessing/event-bad.png&quot; alt=&quot;Bad event cleanup&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;App Source Cop&lt;/em&gt; is fine with this change. We’ve obsoleted an event. The next release can remove it. However, since the event is no longer being fired, any changes from event subscribers are now ignored. All external apps that were using this event are now &lt;strong&gt;broken&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If we want to introduce changes gradually, this should be the way to go:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/preprocessing/event-good.png&quot; alt=&quot;Good event cleanup&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Today, the event still &lt;strong&gt;fires off&lt;/strong&gt;. Any subscribers are &lt;strong&gt;warned&lt;/strong&gt; that the event is &lt;em&gt;going away&lt;/em&gt; and that they should find a different solution. When it’s time, we can &lt;strong&gt;enable and clean up the preprocessing symbols&lt;/strong&gt; to apply our change.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Does that mean I should wrap every change I make in preprocessing symbols?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://xkcd.com/1172/&quot;&gt;Every change breaks someone’s workflow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/preprocessing/xkcd.png&quot; alt=&quot;Every change breaks someone&apos;s workflow - xkcd&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But that doesn’t mean we should be &lt;strong&gt;afraid to make changes&lt;/strong&gt;. &lt;em&gt;Preprocessing everything&lt;/em&gt; is a sure way to &lt;strong&gt;&lt;em&gt;maintenance hell&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What I want to point out is that if we have a code path that &lt;strong&gt;many external apps extend or use&lt;/strong&gt; we should think about &lt;strong&gt;&lt;em&gt;gradually introducing changes&lt;/em&gt;&lt;/strong&gt; in that code path, and preprocessing symbols are the way to go about it. &lt;strong&gt;Especially when dealing with events.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;support-multiple-versions&quot;&gt;Support multiple versions&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;OnPrem isn’t dead.&lt;/em&gt;&lt;/strong&gt; And when supporting &lt;em&gt;OnPrem&lt;/em&gt; customers, we usually have to support more than one target version of BC. Supporting them through a branch-per-version approach the symbols are less relevant to you, but if you have a single codebase, here’s why you should consider using them.&lt;/p&gt;

&lt;h4 id=&quot;dont-wait-until-the-last-second-to-make-changes&quot;&gt;Don’t wait until the last second to make changes&lt;/h4&gt;

&lt;p&gt;When &lt;em&gt;Base App&lt;/em&gt; obsoletes a functionality or an event, &lt;strong&gt;&lt;em&gt;should you fix it immediately?&lt;/em&gt;&lt;/strong&gt; Or wait until it &lt;strong&gt;&lt;em&gt;becomes an error?&lt;/em&gt;&lt;/strong&gt; Waiting until it becomes an error is a &lt;strong&gt;&lt;em&gt;big no-no&lt;/em&gt;&lt;/strong&gt; in my book. I believe in the &lt;strong&gt;&lt;em&gt;no-warnings approach&lt;/em&gt;&lt;/strong&gt;. I know it’s not as easy for &lt;em&gt;old legacy codebases&lt;/em&gt;, but new development should have &lt;em&gt;zero warnings. &lt;strong&gt;Period.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In v24, &lt;strong&gt;&lt;em&gt;No. Series&lt;/em&gt; procedures became &lt;em&gt;obsolete&lt;/em&gt;&lt;/strong&gt; due to the move to the &lt;strong&gt;&lt;em&gt;Business Foundation&lt;/em&gt;&lt;/strong&gt;. I had a couple of apps where I had to fix these warnings. I didn’t &lt;strong&gt;&lt;em&gt;have&lt;/em&gt;&lt;/strong&gt; to do that. Old code will still &lt;strong&gt;&lt;em&gt;work until (at least) v26.&lt;/em&gt;&lt;/strong&gt; But I’d just be pushing work down the road. It’s understandable, if there’s a &lt;em&gt;higher priority&lt;/em&gt; right now. But if we have the time, &lt;em&gt;why take that additional tech debt on?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What if I’d still have to &lt;strong&gt;&lt;em&gt;support v23 as well?&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;That means I cannot just fix all warnings…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Well, I can, with preprocessing symbols:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/preprocessing/noseries.png&quot; alt=&quot;No. Series cleanup with preprocessing symbols&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When I build higher versions, I enable the symbol, and the warnings are gone. When I &lt;em&gt;drop support for v23&lt;/em&gt;, I can &lt;strong&gt;&lt;em&gt;clean up the v24OrGreater symbol&lt;/em&gt;&lt;/strong&gt;, and I’m done.&lt;/p&gt;

&lt;p&gt;This point applies even more if we’re building &lt;strong&gt;&lt;em&gt;a new feature&lt;/em&gt;&lt;/strong&gt;. If we build a new feature and decide to use the old &lt;em&gt;NoSeriesManagement&lt;/em&gt; codeunit, we’re willingly throwing even more work on the &lt;strong&gt;&lt;em&gt;tech debt pile.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;Quick side note, this nice line highlighting showing me which line is active and which obsolete is provided by &lt;strong&gt;AZ AL Dev Tools&lt;/strong&gt;, so if you’re working with preprocessing symbols, the extension is a must-have.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;merge-changes-early&quot;&gt;Merge changes early&lt;/h3&gt;

&lt;p&gt;Out of the three reasons for using the &lt;em&gt;preprocessing symbols&lt;/em&gt;, this is the one I used the least, but at the same time, I’m also most interested in it.&lt;/p&gt;

&lt;p&gt;This summer I held a session at &lt;em&gt;Tech Days&lt;/em&gt; about &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=v-EaIJ0f9tU&quot;&gt;Code Review&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; where I was trying to be quite dogmatic about &lt;strong&gt;keeping pull requests small&lt;/strong&gt;. &lt;em&gt;Long-lived feature branches&lt;/em&gt; are a recipe for issues; &lt;em&gt;constant merges, merge conflicts, and very painful code reviews&lt;/em&gt;. And yet a &lt;em&gt;few months later&lt;/em&gt;, &lt;strong&gt;I find myself working on long-lived branches and huge pull requests&lt;/strong&gt;. It was easy to &lt;em&gt;“preach”&lt;/em&gt; small pull requests when we were only building new features. Now we’re dealing with &lt;strong&gt;refactoring a huge monolith&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I’m changing &lt;em&gt;“just”&lt;/em&gt; one field and have to &lt;em&gt;touch more than 150 other objects.&lt;/em&gt; &lt;strong&gt;&lt;em&gt;Crazy.&lt;/em&gt;&lt;/strong&gt; But I get it, changes have to be made on an &lt;strong&gt;&lt;em&gt;all-or-nothing basis.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But that got me thinking, &lt;em&gt;why is that?&lt;/em&gt; &lt;strong&gt;Why&lt;/strong&gt; do they have to be made on an &lt;strong&gt;all-or-nothing basis?&lt;/strong&gt; &lt;em&gt;Can we get around that somehow?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I talked with friends who work in &lt;strong&gt;other languages&lt;/strong&gt; about how they would deal with this issue. At first, it seemed they didn’t quite get my problem. They just said &lt;strong&gt;&lt;em&gt;“feature flags”&lt;/em&gt;&lt;/strong&gt; to everything I said…&lt;/p&gt;

&lt;p&gt;Okay, &lt;em&gt;feature flags&lt;/em&gt;. Hide the functionality behind a flag and &lt;strong&gt;merge to the main branch&lt;/strong&gt;. &lt;em&gt;But what about the table fields that I have to add?&lt;/em&gt; I can’t &lt;em&gt;“hide”&lt;/em&gt; the additional fields on a table behind a feature flag, and if I just add them and make a mistake, &lt;strong&gt;I won’t be able to remove them&lt;/strong&gt;, as it would be a &lt;strong&gt;breaking change…&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that’s when it hit me. &lt;strong&gt;&lt;em&gt;What if I use preprocessing symbols as feature flags?&lt;/em&gt;&lt;/strong&gt; This would solve my problems. &lt;strong&gt;Merge smaller chunks of changes&lt;/strong&gt; to the &lt;em&gt;main branch&lt;/em&gt; quicker, and once everything is in and tested, &lt;strong&gt;turn it on and clean it up!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As I said, I’ve &lt;strong&gt;only started exploring&lt;/strong&gt; the feasibility of this approach as an alternative to &lt;strong&gt;long-lived branches and big PRs&lt;/strong&gt;. &lt;em&gt;But I do like how it looks…&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Okay, this should give you an idea when &lt;strong&gt;&lt;em&gt;using symbols can be a good idea.&lt;/em&gt;&lt;/strong&gt; Before I wrap this long post up, here are a few more quick questions I sometimes get from developers:&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;when-should-we-clean-up-the-symbols&quot;&gt;When should we clean up the symbols?&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Clean them up immediately&lt;/strong&gt; after you turn them on for a &lt;em&gt;production release&lt;/em&gt;. &lt;strong&gt;&lt;em&gt;Seriously.&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;I guarantee you&lt;/em&gt;, that you’ll never &lt;em&gt;enable a symbol, build, test, and release the app&lt;/em&gt;, and then realize, &lt;strong&gt;oops,&lt;/strong&gt; we need to undo those changes. If we need an option to &lt;strong&gt;&lt;em&gt;“undo”&lt;/em&gt;&lt;/strong&gt; the behavior, &lt;em&gt;run-time feature flags&lt;/em&gt; are a much better option.&lt;/p&gt;

&lt;p&gt;The only exception to this rule is the &lt;em&gt;preprocessing symbols&lt;/em&gt; used to support &lt;em&gt;multiple versions&lt;/em&gt;, like our &lt;strong&gt;v24OrGreater&lt;/strong&gt; example above. Clean those up as soon as they’re &lt;strong&gt;&lt;em&gt;enabled for all versions you support&lt;/em&gt;&lt;/strong&gt;. In our case, that would mean when we &lt;strong&gt;drop the support for v23.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;how-do-i-clean-them-up&quot;&gt;How do I clean them up?&lt;/h4&gt;

&lt;p&gt;I have a &lt;strong&gt;small script&lt;/strong&gt; that you can find &lt;a href=&quot;https://github.com/tinestaric/BCExamples/blob/Master/PreprocessingSymbolCleanup/PreprocessingSymbolCleanup.ps1&quot;&gt;here&lt;/a&gt;. It loops through all files in the project and &lt;strong&gt;removes the code paths for a specified feature flag.&lt;/strong&gt; There are likely other tools already out there, that can handle the cleanup. &lt;em&gt;Preprocessing symbols&lt;/em&gt; aren’t exactly a &lt;em&gt;novelty&lt;/em&gt;, even in AL they’ve been around for a while.&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;how-do-i-enable-a-symbol-for-the-base-app&quot;&gt;How do I enable a Symbol for the Base App?&lt;/h4&gt;

&lt;p&gt;I got this question quite a couple of times, and I remember I too was confused in the beginning when I didn’t understand &lt;em&gt;preprocessing symbols&lt;/em&gt;. You open a base app object like &lt;em&gt;Sales-Post&lt;/em&gt; and see a &lt;strong&gt;CLEAN23&lt;/strong&gt; code path. &lt;strong&gt;&lt;em&gt;How do I “enable” this new code path?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You don’t.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Microsoft&lt;/em&gt; does. And when they will, you won’t see those symbols anymore, as they’ll &lt;strong&gt;also clean them up.&lt;/strong&gt; Think of those code paths as &lt;em&gt;“what’s going to change in the future”&lt;/em&gt;, but is currently not in use. As we talked about, it’s mainly about &lt;em&gt;Microsoft&lt;/em&gt; &lt;strong&gt;introducing changes gradually.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;finally&quot;&gt;Finally&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Do preprocessing symbols make the code less readable?&lt;/em&gt; &lt;strong&gt;&lt;em&gt;Yes, yes, they do.&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;Do they make testing harder, as there are now more possible code paths?&lt;/em&gt; &lt;strong&gt;&lt;em&gt;Another resounding yes.&lt;/em&gt;&lt;/strong&gt; All these coding decisions always come with &lt;strong&gt;trade-offs&lt;/strong&gt;. &lt;em&gt;Should you use them?&lt;/em&gt; &lt;strong&gt;&lt;em&gt;I don’t know.&lt;/em&gt;&lt;/strong&gt; I made this post, to make my reasoning about it easier. But it doesn’t mean that they will &lt;strong&gt;apply to your situation.&lt;/strong&gt; At least next time you have issues like these, you know that &lt;em&gt;preprocessing symbols&lt;/em&gt; can be a way to deal with them.&lt;/p&gt;

</description>
        <pubDate>Sun, 06 Oct 2024 10:00:00 +0200</pubDate>
        <link>https://tine.staric.net/blog/2024/preprocessing-symbols/</link>
        <guid isPermaLink="true">https://tine.staric.net/blog/2024/preprocessing-symbols/</guid>
        
        
        <category>al</category>
        
      </item>
    
      <item>
        <title>Namespaces in AL - Is it time to start using them?</title>
        <description>&lt;p&gt;&lt;em&gt;School’s back&lt;/em&gt;, so it’s time for the blogs to get back! This time I want to take a jab at &lt;strong&gt;namespaces&lt;/strong&gt;. Not really the technical &lt;strong&gt;&lt;em&gt;“how”&lt;/em&gt;&lt;/strong&gt; of namespaces. I believe that part is rather easy and has already been covered multiple times. Microsoft presented a &lt;a href=&quot;https://www.youtube.com/watch?v=F8NH69BGnKk&quot;&gt;brilliant session&lt;/a&gt; about namespaces at &lt;a href=&quot;https://www.bctechdays.com/event&quot;&gt;BCTechDays&lt;/a&gt; this year.
Instead I want to focus on &lt;strong&gt;&lt;em&gt;WHY&lt;/em&gt;&lt;/strong&gt;. That’s a question I recently received multiple times. &lt;em&gt;We never needed namespaces in the past, why do we need them now? Do they bring any benefits or are they just a hassle? Is it time to start using them?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Over the past few months, I got to work on apps that &lt;em&gt;were namespaced&lt;/em&gt;, apps that &lt;em&gt;weren’t&lt;/em&gt;, and completely &lt;em&gt;greenfield&lt;/em&gt; apps where I could decide if we wanted to adopt namespaces or not. So, this post is a view on namespaces from &lt;strong&gt;my perspective&lt;/strong&gt;, and feel free to disagree with me.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;why-namespaces&quot;&gt;Why Namespaces?&lt;/h3&gt;

&lt;h4 id=&quot;structure&quot;&gt;Structure&lt;/h4&gt;

&lt;p&gt;I often do &lt;strong&gt;&lt;em&gt;“audits”&lt;/em&gt; of large code bases&lt;/strong&gt; for partners, trying to determine the &lt;em&gt;extent of their tech debt&lt;/em&gt;, whether they will ever get to &lt;em&gt;multi-app architecture&lt;/em&gt;, or reach &lt;em&gt;AppSource compatibility&lt;/em&gt;. To be able to do that, I need to &lt;strong&gt;understand the app’s architecture&lt;/strong&gt;, &lt;em&gt;what am I even looking at&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;When I have to work with large apps where the src folder looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/folders-bad.png&quot; alt=&quot;Bad folder structure&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I take a deep breath&lt;/em&gt;, ask myself 7 times why am I doing this to myself, and then try to make sense of files that are supposedly important, and work my way through their &lt;strong&gt;references&lt;/strong&gt;. &lt;em&gt;It’s never a fun experience.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On the other hand, if I open a project and the project is separated into areas like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/folders-good.png&quot; alt=&quot;Good folder structure&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It makes a &lt;strong&gt;huge difference&lt;/strong&gt;. I can wrap my head around individual parts that &lt;strong&gt;logically fit together&lt;/strong&gt;. Structuring your code in &lt;strong&gt;functional areas&lt;/strong&gt; does wonders for &lt;strong&gt;discoverability&lt;/strong&gt;. &lt;em&gt;Shocker, right?&lt;/em&gt; Any new developer you try to onboard onto your codebase will have to go through the same process I have to go through, and &lt;strong&gt;high discoverability&lt;/strong&gt; makes this process &lt;strong&gt;much smoother&lt;/strong&gt;. &lt;em&gt;If I can’t convince you that the second folder structure is better than the first one, that I probably won’t ever get you to uptake namespaces…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fine Tine, &lt;strong&gt;organized code is better than not organized&lt;/strong&gt;, what do &lt;strong&gt;namespaces&lt;/strong&gt; have to do with that?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Well, structuring your code in &lt;strong&gt;folders per functional area&lt;/strong&gt; is in my opinion a &lt;strong&gt;prerequisite&lt;/strong&gt; for starting with namespaces. Discoverability is something &lt;strong&gt;legacy codebases lack the most&lt;/strong&gt;. Not surprising, considering we used to just work with a long list of objects sorted by type and ID.&lt;/p&gt;

&lt;p&gt;Let’s say I open a file like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/enum-no-namespace.png&quot; alt=&quot;Enum without namespace&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Where does this Enum belong? Who knows…&lt;/em&gt; I have to go &lt;strong&gt;through the references&lt;/strong&gt; to figure that out.&lt;/p&gt;

&lt;p&gt;But if it has a namespace defined:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/enum-yes-namespace.png&quot; alt=&quot;Enum with namespace&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Then I can quickly see it’s &lt;strong&gt;part of the Stock APIs functionality&lt;/strong&gt;. And you’re right, if I only care about the &lt;strong&gt;code discoverability&lt;/strong&gt;, then I could just look at the folder structure where the &lt;em&gt;Document Type&lt;/em&gt; file is placed and get the same idea, &lt;strong&gt;I don’t &lt;em&gt;need&lt;/em&gt; namespaces for that.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But structuring your codebase and making it discoverable is just &lt;strong&gt;one of the benefits of namespaces&lt;/strong&gt;, while it’s the &lt;strong&gt;only benefit&lt;/strong&gt; if you use &lt;strong&gt;folders as the only method&lt;/strong&gt;. That’s why we should do &lt;strong&gt;both&lt;/strong&gt;. Organize code in folders and use namespaces. It makes the project easily discoverable, but also unlocks other benefits of namespaces like &lt;strong&gt;dependency analysis&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;dependency-analysis&quot;&gt;Dependency Analysis&lt;/h4&gt;

&lt;p&gt;Let’s talk about &lt;em&gt;dependency analysis&lt;/em&gt; next. This one has been huge for me lately. It allows us to ensure that &lt;strong&gt;cleanly architected apps remain clean&lt;/strong&gt;. It also helped with &lt;strong&gt;de-tangling&lt;/strong&gt; functionalities in &lt;strong&gt;monolithic apps&lt;/strong&gt; and breaking down those horrible &lt;em&gt;“Management” codeunits&lt;/em&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I’ll assume you already know that when we start adopting namespaces we move from a &lt;strong&gt;&lt;em&gt;global namespace&lt;/em&gt;&lt;/strong&gt;, where we could define any object as a variable, to a &lt;strong&gt;&lt;em&gt;specific namespace&lt;/em&gt;&lt;/strong&gt;, where we can only define objects &lt;em&gt;defined in the same namespace&lt;/em&gt;, &lt;em&gt;fully qualify object names&lt;/em&gt;, or &lt;strong&gt;&lt;em&gt;“import” objects&lt;/em&gt;&lt;/strong&gt; from other namespaces using a &lt;strong&gt;“using” statement&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In other words, we used to be able to do this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/var-declaration-old.png&quot; alt=&quot;Old way of declaring variables&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With namespaces, &lt;em&gt;we can’t&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/var-declaration-namespace-error.png&quot; alt=&quot;Old way breaks when declaring a namespace on the file&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Unless we do one of the two:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/var-declaration-new.png&quot; alt=&quot;New way of declaring variables&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Before we get back to dependency analysis, let’s talk about &lt;strong&gt;which of the two options we should go for&lt;/strong&gt;. &lt;em&gt;Fully qualified name, or the using statement.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We decided to &lt;strong&gt;always go with using statements&lt;/strong&gt; in favor of fully qualified names. &lt;strong&gt;&lt;em&gt;Why?&lt;/em&gt;&lt;/strong&gt; Because it allows us to &lt;strong&gt;easily see dependencies&lt;/strong&gt; at the top of the file (this is the &lt;em&gt;dependency analysis&lt;/em&gt; that we’ll get to in a second). We haven’t found a real use case when we’d prefer to go for &lt;em&gt;fully qualifying names&lt;/em&gt;. In the &lt;em&gt;C# world&lt;/em&gt;, I’d go for fully qualified names if I’m using &lt;strong&gt;two objects that have the same name&lt;/strong&gt; but reside &lt;strong&gt;in different namespaces.&lt;/strong&gt; However, in &lt;strong&gt;AL&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;We’re quite used to making &lt;strong&gt;globally unique object names&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;As of now, &lt;strong&gt;can’t define an object with the same name&lt;/strong&gt; in the same app, regardless of namespaces&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Back to the &lt;strong&gt;&lt;em&gt;dependency analysis&lt;/em&gt;&lt;/strong&gt; now. We now know that anytime we want to define a variable for an object from a different namespace, we have to use a &lt;strong&gt;“using” statement&lt;/strong&gt;. This means that from a quick look, we can see &lt;strong&gt;what other modules our object is tied to.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s how a &lt;strong&gt;&lt;em&gt;Sales Header&lt;/em&gt;&lt;/strong&gt; table looks once &lt;em&gt;Microsoft&lt;/em&gt; applied the namespace to it:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/sales-header-using.png&quot; alt=&quot;Using statements on Sales Header&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I couldn’t even capture all of them in one screenshot. There are &lt;strong&gt;&lt;em&gt;76&lt;/em&gt;&lt;/strong&gt; of them. &lt;strong&gt;&lt;em&gt;Crazy&lt;/em&gt;&lt;/strong&gt;. Also, a bit &lt;em&gt;ugly&lt;/em&gt;. But at the same time &lt;strong&gt;a good thing!&lt;/strong&gt; At least now &lt;em&gt;Microsoft&lt;/em&gt; has a &lt;strong&gt;high-level overview&lt;/strong&gt; of all the parts that are jammed in the &lt;em&gt;Sales Header&lt;/em&gt; table and can start working towards &lt;strong&gt;extracting irrelevant parts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By &lt;em&gt;irrelevant&lt;/em&gt; I don’t mean that the &lt;em&gt;functionality&lt;/em&gt; is &lt;em&gt;irrelevant&lt;/em&gt;. But does the &lt;em&gt;Sales Header&lt;/em&gt; have to be &lt;strong&gt;coupled to Dataverse?&lt;/strong&gt; Or &lt;em&gt;Outlook?&lt;/em&gt; Doesn’t it feel &lt;em&gt;a bit off?&lt;/em&gt; &lt;strong&gt;Removing Dataverse&lt;/strong&gt; functionality shouldn’t &lt;strong&gt;break sales documents&lt;/strong&gt;. I’m not trying to diss &lt;em&gt;Microsoft’s&lt;/em&gt; code. The base app is old, and there’s a lot of &lt;strong&gt;legacy code&lt;/strong&gt; in there. Here’s how the relationships in the Base App look like today:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/baseapp-web.png&quot; alt=&quot;Spider web of dependencies in the Base App&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To be honest, &lt;strong&gt;&lt;em&gt;we all wrote entangled code&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;like that in the past&lt;/em&gt;. But what I’m trying to say is that with namespaces we can now finally &lt;strong&gt;start spotting these spider webs&lt;/strong&gt;. Both in our &lt;strong&gt;old code&lt;/strong&gt; and when writing &lt;strong&gt;new code&lt;/strong&gt;. Does the latest pull request try to add two &lt;strong&gt;&lt;em&gt;new “using” statements to a management codeunit?&lt;/em&gt;&lt;/strong&gt; Are we creating another &lt;strong&gt;&lt;em&gt;Swiss army knife&lt;/em&gt;&lt;/strong&gt; codeunit? Maybe we should &lt;strong&gt;separate them&lt;/strong&gt; and stick to a &lt;strong&gt;single responsibility&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;naming&quot;&gt;Naming&lt;/h4&gt;

&lt;p&gt;Naming is a &lt;em&gt;very common reason&lt;/em&gt; why we’d want to use namespaces in other languages. As we only ever had a &lt;strong&gt;&lt;em&gt;global namespace&lt;/em&gt;&lt;/strong&gt; in AL (and C/AL), all of our objects had to have &lt;strong&gt;globally unique names&lt;/strong&gt;. That’s why we had a &lt;em&gt;“Sales Header”&lt;/em&gt; and a &lt;em&gt;“Purchase Header”&lt;/em&gt;. If we were in the world of &lt;strong&gt;C#&lt;/strong&gt;, these would likely be &lt;em&gt;Microsoft.Sales.Document.Header&lt;/em&gt; and &lt;em&gt;Microsoft.Purchase.Document.Header&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two &lt;em&gt;“Header”&lt;/em&gt; objects&lt;/strong&gt;, but since they reside in separate namespaces, it’s &lt;strong&gt;not a naming collision&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, we’re &lt;strong&gt;not there yet with AL&lt;/strong&gt;. Defining two objects with the same name in different namespaces in the same app &lt;strong&gt;produces an error&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/naming-collision.png&quot; alt=&quot;Two objects with the same name produce an error even with different namespaces&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Which is weird to me, since what I can do is &lt;strong&gt;define an object&lt;/strong&gt; with the &lt;strong&gt;same name&lt;/strong&gt; as an object in a &lt;strong&gt;different app&lt;/strong&gt;. Like creating my own &lt;em&gt;Customer&lt;/em&gt; table in my own namespace:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/another-customer.png&quot; alt=&quot;Defining a Customer object in my own app works fine&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So, &lt;strong&gt;namespaces prevent naming collisions&lt;/strong&gt;, but not in the same app?…
&lt;em&gt;Hopefully, this will work in the future…&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The one naming benefit we however do have is that we don’t have to try and &lt;strong&gt;jam long names in only 30 characters&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s an example from one of the projects: &lt;strong&gt;&lt;em&gt;“MYPETaxFSTBlock”&lt;/em&gt;&lt;/strong&gt; &lt;br /&gt;
&lt;em&gt;Any idea what this is? Yeah, me neither.&lt;/em&gt; &lt;em&gt;ETax&lt;/em&gt; and &lt;em&gt;Block&lt;/em&gt; I get, but what’s &lt;em&gt;FST?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Well, if we had &lt;strong&gt;namespaces&lt;/strong&gt; at the time, this file would likely be&lt;/p&gt;

&lt;p&gt;Namespace MyPublisher.&lt;strong&gt;Banking.ETax.Declaration&lt;/strong&gt; &lt;br /&gt;
Codeunit 50000 “MYPFormStructureTypeBlock”&lt;/p&gt;

&lt;p&gt;By moving the &lt;strong&gt;area&lt;/strong&gt; that this objects belongs to &lt;strong&gt;out of the file name&lt;/strong&gt;, we free up the 30 characters for &lt;strong&gt;actual name of the file&lt;/strong&gt;. This again makes the codebase more &lt;strong&gt;onboarding friendly&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;the-how-of-namespaces&quot;&gt;The how of namespaces&lt;/h3&gt;

&lt;p&gt;I said I won’t go into the &lt;strong&gt;“how”&lt;/strong&gt; of namespaces, but I want to mention a couple more technical &lt;strong&gt;good-to-knows&lt;/strong&gt; about namespaces&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;how-should-i-structure-namespaces&quot;&gt;How should I structure namespaces?&lt;/h4&gt;

&lt;p&gt;Here’s what we’re using. Again, only a recommendation and a guideline, deviate from it as much as you’d like.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&amp;lt;Publisher&amp;gt;.&amp;lt;AppArea&amp;gt;.FolderStructure&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; &lt;em&gt;Companial.Banking.Setup&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Important!&lt;/em&gt;&lt;/strong&gt; We don’t (and won’t) register namespaces with MS (like we register affixes), so make sure yours are &lt;strong&gt;unique&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;The first part&lt;/strong&gt; should be your &lt;strong&gt;&lt;em&gt;publisher’s name&lt;/em&gt;&lt;/strong&gt; to ensure the &lt;strong&gt;namespace is unique&lt;/strong&gt; from other partners.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The second part&lt;/strong&gt; is the &lt;strong&gt;&lt;em&gt;App area&lt;/em&gt;&lt;/strong&gt;. &lt;strong&gt;Not the App name mind you&lt;/strong&gt;. We try to find a more &lt;em&gt;generic&lt;/em&gt; name. Mainly, so that if we’d ever move an object to a different extension, we don’t end up with an object that has a &lt;strong&gt;very app specific namespace&lt;/strong&gt; and would &lt;em&gt;“stick out”&lt;/em&gt;. This point is &lt;strong&gt;very applicable to monoliths&lt;/strong&gt; that we’ll want to break down into smaller apps.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The third part&lt;/strong&gt; is then the &lt;strong&gt;folder structure&lt;/strong&gt;. The actual &lt;em&gt;path&lt;/em&gt; of the file. This ensures files are &lt;strong&gt;easily discoverable&lt;/strong&gt; by following the namespace.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For comparison, here’s &lt;em&gt;Microsoft’s&lt;/em&gt; recommendation:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/ms-namespace-structure.png&quot; alt=&quot;Microsoft&apos;s namespace naming suggestion&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Whatever you do, please &lt;strong&gt;don’t just add one namespace to every object in your project&lt;/strong&gt;. Unless your project has &lt;em&gt;less than 25 objects&lt;/em&gt;, I believe you have &lt;strong&gt;multiple areas&lt;/strong&gt; hiding in there.&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;how-to-ensure-i-dont-forget-to-add-namespaces&quot;&gt;How to ensure I don’t forget to add namespaces?&lt;/h4&gt;

&lt;p&gt;There’s a &lt;strong&gt;CodeCop warning (&lt;em&gt;AA0247&lt;/em&gt;)&lt;/strong&gt; we can use to help us spot missing namespaces. It’s &lt;strong&gt;disabled by default&lt;/strong&gt;, so we have to raise it’s severity in the &lt;strong&gt;&lt;em&gt;ruleset.json&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/namespace-warning.png&quot; alt=&quot;Warning for missing namespaces&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;when-should-i-not-adopt-namespaces&quot;&gt;When should I not adopt namespaces?&lt;/h4&gt;

&lt;p&gt;If you’re app has a &lt;strong&gt;dependency on another app that has not yet adopted namespaces&lt;/strong&gt;. Do not add namespaces to your objects in these cases as &lt;strong&gt;your app will break&lt;/strong&gt; once the dependency adopts them. Let me quickly show you &lt;strong&gt;why&lt;/strong&gt;:&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;heres-the-happy-path&quot;&gt;Here’s the &lt;em&gt;happy path&lt;/em&gt;:&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;App A&lt;/em&gt; depends on &lt;em&gt;App B&lt;/em&gt;. &lt;strong&gt;No namespaces are used.&lt;/strong&gt; Everything works.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/dependency-both-global.png&quot; alt=&quot;Two objects, both in global namespace&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Dependency (&lt;em&gt;App B&lt;/em&gt;) introduces a namespace. &lt;strong&gt;No issues in &lt;em&gt;App A&lt;/em&gt;&lt;/strong&gt;, as we’re still using the &lt;strong&gt;global namespace&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/dependency-namespace-first.png&quot; alt=&quot;Dependency introduces namespaces&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;App A&lt;/em&gt; now introduces namespaces&lt;/strong&gt; and at the same time has to &lt;strong&gt;add a using statement&lt;/strong&gt; for the dependency codeunit and can compile against the new version of the &lt;em&gt;App B&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/dependency-main-namespace-second.png&quot; alt=&quot;Main app now introduces namespaces&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;now-the-unhappy-path&quot;&gt;Now the unhappy path:&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;App A&lt;/em&gt; is the first to define a namespace and everything works since the &lt;strong&gt;Dependency is still in the global namespace&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/dependency-main-namespace-first.png&quot; alt=&quot;Main app introduces namespaces first&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But now when &lt;em&gt;App B&lt;/em&gt; adds a namespace, &lt;strong&gt;&lt;em&gt;App A&lt;/em&gt; is broken&lt;/strong&gt; and cannot work with the new version of App B&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/dependency-namespace-second.png&quot; alt=&quot;Dependency introduces namespaces second&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The problem is, that &lt;strong&gt;you don’t know when your dependencies will adopt namespaces&lt;/strong&gt;, so I’d personally &lt;strong&gt;avoid them until all dependencies are namespaces&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In all other cases, I’d say &lt;strong&gt;go for it!&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;is-adding-a-namespace-a-breaking-change&quot;&gt;Is adding a namespace a breaking change?&lt;/h4&gt;

&lt;p&gt;No. Yes, we’ve seen how it can break dependencies, but it’s not a breaking change in the sense of the &lt;strong&gt;AppSource validation&lt;/strong&gt;. You can add namespaces to your objects and still pass the validation.&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;where-can-i-learn-more-about-namespaces&quot;&gt;Where can I learn more about namespaces?&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=F8NH69BGnKk&quot;&gt;The recording&lt;/a&gt;&lt;/strong&gt; of &lt;em&gt;Microsoft’s&lt;/em&gt; session is a &lt;strong&gt;must-watch&lt;/strong&gt; if you’d like to see how they approached the challenge of &lt;strong&gt;namespacing the whole base app&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s a few common questions they answered in the session:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/namespaces/rapid-fire-questions.png&quot; alt=&quot;Rapid Fire Questions&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And of course, the &lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-namespaces-overview&quot;&gt;official documentation&lt;/a&gt;&lt;/strong&gt; is always a good place to start.&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;so-is-it-time-to-use-namespaces-and-should-i-now-go-and-namespace-my-huge-monolithic-app&quot;&gt;So, is it time to use namespaces and should I now go and namespace my huge monolithic app?&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Is it time to use namespaces?&lt;/em&gt; &lt;strong&gt;Absolutely.&lt;/strong&gt; Small apps, new apps, greenfield apps, all should start using namespaces. They bring a lot of benefits, and I believe they’re a &lt;strong&gt;step towards a cleaner codebase&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But should you go and refactor your huge monolithic app to use namespaces?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Probably not.&lt;/em&gt;&lt;/strong&gt; I mean, yes, there are benefits to using namespaces, but if you have a huge project, it will take you &lt;strong&gt;a long time to properly segregate objects&lt;/strong&gt; into namespaces areas. Unless you have enough time to do it properly, I’d postpone it. Again, &lt;strong&gt;adding a single namespace across the whole project is not a good idea.&lt;/strong&gt;&lt;/p&gt;

</description>
        <pubDate>Sun, 01 Sep 2024 10:00:00 +0200</pubDate>
        <link>https://tine.staric.net/blog/2024/namespaces/</link>
        <guid isPermaLink="true">https://tine.staric.net/blog/2024/namespaces/</guid>
        
        
        <category>al</category>
        
      </item>
    
      <item>
        <title>Conferences are awesome! Here&apos;s a quick recap...</title>
        <description>&lt;p&gt;I’m about to go off on a longer vacation, but there’s one more post I wanted to make before I go offline. I was partially inspired by &lt;a href=&quot;https://nataliekarolak.wordpress.com/2024/06/16/waking-up-after-the-bc-techdays-2024/&quot;&gt;Natalie’s non-technical post&lt;/a&gt; right after BC Tech Days about her experience there, and I wanted to do the same, but for the full spring conference swing.&lt;/p&gt;

&lt;p&gt;I was lucky enough to participate in &lt;strong&gt;&lt;em&gt;6&lt;/em&gt; (six!)&lt;/strong&gt; events in the past 4 months. &lt;em&gt;Days of Knowledge (DOK) UK, DOK Nordic, Directions NA, Dynamics Minds, DOK Central, and BC Tech Days&lt;/em&gt;. I loved every second of them. But even for this &lt;strong&gt;&lt;em&gt;“youngling”&lt;/em&gt;&lt;/strong&gt;, that was a lot of traveling, and I’m taking a more relaxed approach to summer.&lt;/p&gt;

&lt;p&gt;I was thinking a bit about how I wanted to structure this post. I want to &lt;strong&gt;present the events&lt;/strong&gt; if you may not yet know them, and don’t know if you (or your employees) should attend them. And I also want to &lt;strong&gt;share my takeaways&lt;/strong&gt; from each of them. Now these takeaways might be completely random and highly personal, but to me, that’s the best part about these in-person events. You get to talk to people and at some point, you might hear some &lt;strong&gt;&lt;em&gt;highly&lt;/em&gt; interesting views!&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;days-of-knowledge-tour&quot;&gt;&lt;a href=&quot;https://www.directionsforpartners.com/&quot;&gt;Days of Knowledge Tour&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Days of Knowledge&lt;/em&gt; is a set of events organized by the same amazing people that bring us &lt;strong&gt;Directions EMEA&lt;/strong&gt; (and Asia) every year. However, unlike Directions, these events are entirely &lt;strong&gt;focused on upskilling people&lt;/strong&gt;. There are three tracks of sessions running in parallel. Developer, consultant, and Power Platform track. I’ve been speaking at three events this year; &lt;em&gt;UK, Nordics (Denmark), and Central (Germany)&lt;/em&gt;, but &lt;strong&gt;there’s another one happening in the US (Atlanta) in September&lt;/strong&gt;, and you can still register for it &lt;strong&gt;&lt;a href=&quot;https://www.directionsforpartners.com/conferences-and-events/days-of-knowledge/americas-2024/program&quot;&gt;here.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I love the concept!&lt;/strong&gt; Bringing deeper knowledge (plenty of &lt;em&gt;level 300 or 400 sessions&lt;/em&gt;) than what you’d get at Directions, and for a &lt;strong&gt;lower price&lt;/strong&gt;. I wish there were even more events in the upcoming years, so more people could experience it. But as we’ve already noticed this year, with the growing number of events in the BC sphere, it’s &lt;strong&gt;taking a toll on speakers&lt;/strong&gt;, as they have to travel quite a lot.&lt;/p&gt;

&lt;p&gt;My take is that there’s &lt;strong&gt;no better event for consultants&lt;/strong&gt; than &lt;em&gt;Days of Knowledge&lt;/em&gt;. But also for developers, who want to understand more of the functional and Power Platform side. I think &lt;strong&gt;more often should developers&lt;/strong&gt; try and &lt;strong&gt;understand the functional side&lt;/strong&gt;, it makes us better. Here we can, with a dedicated track for non-development sessions. I’m not saying that developer sessions are not great, but the functional track is why you should consider sending developers to other events than simply Tech Days.&lt;/p&gt;

&lt;p&gt;Some interesting things I’ve heard or seen at DOK events that made me go &lt;em&gt;“huh”&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Being &lt;strong&gt;natively fluent in English&lt;/strong&gt; can be a huge boost in how your session turns out&lt;/li&gt;
  &lt;li&gt;Most AL developers are &lt;strong&gt;not technical&lt;/strong&gt; (by education)&lt;/li&gt;
  &lt;li&gt;It’s hard to present details if people don’t know the basics&lt;/li&gt;
  &lt;li&gt;This one was interestingly controversial. &lt;strong&gt;Don’t be passionate about AL.&lt;/strong&gt; AL is a language that pays our bills. If you want to be passionate about technology, find a side project where you can be creative.&lt;/li&gt;
  &lt;li&gt;Don’t run Power Automate flows on &lt;strong&gt;user accounts&lt;/strong&gt;. You fire a guy, and half the integrations stop working.&lt;/li&gt;
  &lt;li&gt;The delivery of a topic is as (if not more) important as the content itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some of the people that made these events &lt;strong&gt;fun!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/conferencerecap24/doknordic.jpg&quot; alt=&quot;DOK Nordic speakers&quot; style=&quot;display:block; margin-left:auto; margin-right:auto&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/conferencerecap24/dokcentral.png&quot; alt=&quot;DOK Central speakers&quot; style=&quot;display:block; margin-left:auto; margin-right:auto&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;directions-north-america&quot;&gt;&lt;a href=&quot;https://directionsna.com/&quot;&gt;Directions North America&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;I doubt Directions North America needs an introduction. It’s been around for a while. I also don’t have much of a recommendation of who should you send to these events. I’ll mainly share my &lt;em&gt;“huh”&lt;/em&gt; moments, but before I get there, here are a couple of things I found…&lt;strong&gt;different&lt;/strong&gt;, compared to Directions EMEA.&lt;/p&gt;

&lt;p&gt;Look at these statistics they shared (disregard the yellow lines, those were for entertainment purposes):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/conferencerecap24/dna-statistics.jpg&quot; alt=&quot;Directions NA attendee statistics&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When I saw these numbers, I was mainly surprised by two parts. The &lt;strong&gt;age of participants, and their role.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two-thirds&lt;/strong&gt; of the participants were &lt;strong&gt;over 40&lt;/strong&gt;. I know there are probably a lot of actual reasons for that (it’s an expensive event, it’s a commercial event, …), but as someone who sees these conferences as the main reason why I decided to stay in this industry, &lt;em&gt;I felt uneasy.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I want more younglings here! :D&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Close to &lt;strong&gt;three-quarters&lt;/strong&gt; of attendees were in &lt;strong&gt;Sales and Leadership&lt;/strong&gt; positions. You could feel that in the sessions as well, they were &lt;em&gt;less attended&lt;/em&gt;. I get that Europe has more of a &lt;em&gt;“builder”&lt;/em&gt; mindset, while the US prefers to buy solutions, okay, fine. But still, &lt;strong&gt;where do your people then get upskilled&lt;/strong&gt; if not at these kinds of events?&lt;/p&gt;

&lt;p&gt;Some &lt;em&gt;“huh”&lt;/em&gt; moments:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Copilots are everywhere.&lt;/strong&gt; It’s not that long since it was introduced, and partners are &lt;strong&gt;already trailing&lt;/strong&gt; regarding AI knowledge.&lt;/li&gt;
  &lt;li&gt;MS is hesitant to &lt;strong&gt;publicly&lt;/strong&gt; announce what they’re doing, but when approached individually they’re happy to share the direction in which they’re going.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/conferencerecap24/dna.jpg&quot; alt=&quot;Directions North America Speakers&quot; style=&quot;display:block; margin-left:auto; margin-right:auto&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;dynamicsminds&quot;&gt;&lt;a href=&quot;https://www.dynamicsminds.com/&quot;&gt;DynamicsMinds&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;This is probably the most &lt;strong&gt;“under the radar”&lt;/strong&gt; event in the BC sphere. But it shouldn’t be. It’s a fairly small event (if you can call 1250 attendees small) in Portorož, Slovenia, aimed at the &lt;strong&gt;full D365 stack (FO, CE, PP, BC).&lt;/strong&gt; It’s a brilliant event! Organization-wise, second to none. It’s hard to describe what makes it so amazing. The attention to detail, the creativity behind the keynote, the fact that MS only gets 5 minutes per product for their sales pitch during the keynote, the location, the food, … it’s probably just all of the above.&lt;/p&gt;

&lt;p&gt;When I was sharing everything they did to ensure speakers had a good time, someone summed it up perfectly: &lt;em&gt;“It’s &lt;strong&gt;not that you expect&lt;/strong&gt; that they would do all these things for you, &lt;strong&gt;but they do them anyway&lt;/strong&gt;”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you want to experience an event like no other, this is the place to go. But even though, I’m &lt;strong&gt;highly supportive&lt;/strong&gt; of this event, there is something to consider when you’re deciding who should attend it.&lt;/p&gt;

&lt;p&gt;If you have practices that work with FO, CE, or PP, &lt;strong&gt;stop thinking, and send them all&lt;/strong&gt;. How many other events do you have that &lt;strong&gt;cater to FO?&lt;/strong&gt; But if your focus is primarily BC, my recommendation is to send more architect or manager-level people. There’s so much you can learn from where other parts of D365 are evolving. But if you’re &lt;strong&gt;only looking for BC&lt;/strong&gt; sessions, you might sometimes &lt;strong&gt;struggle&lt;/strong&gt; with what to listen to. Especially if you’re used to BC events where you have multiple sessions specifically for BC in each time slot.&lt;/p&gt;

&lt;p&gt;I’m super sure that this event will sell out next year, so if you’re interested, get your tickets early.&lt;/p&gt;

&lt;p&gt;Some &lt;em&gt;“huh”&lt;/em&gt; moments from this one:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;There’s so much &lt;strong&gt;more to Power Platform&lt;/strong&gt; than what we &lt;em&gt;“the BC”&lt;/em&gt; people are aware of&lt;/li&gt;
  &lt;li&gt;PP has a hard time working with BC due to the &lt;strong&gt;&lt;em&gt;“we can solve it in BC”&lt;/em&gt; mentality&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/conferencerecap24/dynamicsminds.jpg&quot; alt=&quot;Dynamics Minds speakers&quot; style=&quot;display:block; margin-left:auto; margin-right:auto&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;bc-techdays&quot;&gt;&lt;a href=&quot;https://www.bctechdays.com/event&quot;&gt;BC TechDays&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;Another event that needs no introduction. &lt;strong&gt;The crown jewel&lt;/strong&gt; if you’re a BC developer. BC Tech Days was the &lt;strong&gt;first conference I ever attended&lt;/strong&gt;. Two years ago, I was that unsure developer, standing in the corner somewhere, not really knowing what to do during coffee breaks. Fast forward two years, and I couldn’t believe just &lt;strong&gt;how fast the event passed by&lt;/strong&gt;. So many people to meet, so many people to talk to.&lt;/p&gt;

&lt;p&gt;To me, this was also &lt;strong&gt;the most demanding event&lt;/strong&gt; to prepare for. My sessions are usually &lt;em&gt;45 minutes&lt;/em&gt; long, this was the first time I did a &lt;em&gt;90-minute&lt;/em&gt; one, and boy did it take some prep time… &lt;strong&gt;It was all worth it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the great things about BC TechDays is that &lt;strong&gt;all sessions are recorded&lt;/strong&gt;, so if you’d like to check out what I was talking about, you can find it &lt;strong&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=v-EaIJ0f9tU&quot;&gt;here&lt;/a&gt;.&lt;/strong&gt; And you can find all the other recordings &lt;a href=&quot;https://www.youtube.com/mibusocom&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The only &lt;em&gt;“huh”&lt;/em&gt; moment I took from this one, which isn’t even that surprising, is:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;This community and its people are awesome!&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/conferencerecap24/techdays.jpg&quot; alt=&quot;BC Tech Days fun people&quot; style=&quot;display:block; margin-left:auto; margin-right:auto&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Okay, so now you know a little bit about the sessions you should attend next year. Here’s a quick recap of my take on the target audiences:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Days of Knowledge&lt;/strong&gt; – Awesome event for upskilling consultants and especially Senior Developers wanting to broaden their functional or PP knowledge.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Directions NA&lt;/strong&gt; – Sales &amp;amp; Leadership mainly, but there are a lot of cool sessions as well&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;DynamicsMinds&lt;/strong&gt; – One-of-a-kind conference experience. Content is best suited for Architects, cross-product developers, D365 people, and Leadership&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;BC Tech Days&lt;/strong&gt; – Developer’s favorite. BC Developers of all levels&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;This conference season was a blast for me. I already knew &lt;strong&gt;I loved sharing knowledge&lt;/strong&gt; and doing sessions last year, but this year truly &lt;strong&gt;cemented it&lt;/strong&gt;. I put a lot of effort into my sessions.&lt;/p&gt;

&lt;p&gt;Before I ever started doing conference sessions, I talked with one of the &lt;strong&gt;greats in our industry&lt;/strong&gt; about just how much time it takes them to prepare sessions. The answer was 50 - 100 hours depending on the session. That made me realize from the beginning that &lt;strong&gt;great content and great delivery take time&lt;/strong&gt;, and I’m more than willing to put that time in. I delivered a total of &lt;strong&gt;&lt;em&gt;12 sessions&lt;/em&gt;&lt;/strong&gt; at these events with an average &lt;strong&gt;speaker rating of 4.92/5&lt;/strong&gt; and a &lt;strong&gt;content rating of 4.76/5&lt;/strong&gt; out of roughly 200 ratings. I’m proud of these numbers. It means the time was well spent, the audience enjoyed it, and &lt;strong&gt;it all becomes worth it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you’re ever sitting in a conference session, I want you to &lt;strong&gt;leave a rating and a comment&lt;/strong&gt;. If you liked it, the &lt;strong&gt;good comments stay with a speaker like a long-lasting applause&lt;/strong&gt;. If you think there’s something to improve, comment as well. I believe my session at BC Tech Days turned out as well as it did mainly &lt;strong&gt;because I received constructive feedback&lt;/strong&gt; on previous events. That’s how I knew what content the audience was missing, that I should interact with them more, and that everyone likes memes, and I should include more. :P&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Okay, enough. Now you know what cool events are out there and you should definitely look out for them next year! Now, it’s time for me to pack and &lt;strong&gt;take some time off.&lt;/strong&gt;&lt;/p&gt;

</description>
        <pubDate>Wed, 10 Jul 2024 10:00:00 +0200</pubDate>
        <link>https://tine.staric.net/blog/2024/spring-conf-recap/</link>
        <guid isPermaLink="true">https://tine.staric.net/blog/2024/spring-conf-recap/</guid>
        
        
        <category>al</category>
        
      </item>
    
      <item>
        <title>BC Online has new operational limits! Here&apos;s how to make the most out of them...</title>
        <description>&lt;p&gt;While in &lt;em&gt;San Diego&lt;/em&gt; for &lt;a href=&quot;https://directionsna.com/&quot;&gt;Directions NA&lt;/a&gt; I attended a couple of sessions held by &lt;a href=&quot;https://www.linkedin.com/in/swinarko&quot;&gt;Sandy Winarko&lt;/a&gt; on &lt;strong&gt;new operational limits for Job and API limits in BC Online&lt;/strong&gt;. I think it’s great that they’re working on making the cloud more appealing, but to make the most out of the new limits we have to change how we run jobs or integrate with BC. &lt;em&gt;How?&lt;/em&gt; We’ll get to that in the second part, let’s first talk about what changed.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;new-schedule-task-limits&quot;&gt;New schedule task limits&lt;/h3&gt;

&lt;p&gt;Let’s start with the &lt;em&gt;scheduled tasks&lt;/em&gt; more commonly known as &lt;em&gt;job queue entries&lt;/em&gt;. &lt;strong&gt;Previously&lt;/strong&gt;, the maximum number of concurrent jobs that could run simultaneously was &lt;strong&gt;3&lt;/strong&gt;. That was a hard limit set &lt;strong&gt;per environment&lt;/strong&gt;. Three concurrent jobs were not enough, especially if multiple companies needed to run scheduled jobs. They have now changed this limit from 3-per-environment to &lt;strong&gt;5-per-user&lt;/strong&gt;. This means that I can now schedule &lt;strong&gt;10 jobs to run simultaneously&lt;/strong&gt;, as long as &lt;strong&gt;5 jobs&lt;/strong&gt; are scheduled by &lt;strong&gt;User1&lt;/strong&gt; and &lt;strong&gt;5&lt;/strong&gt; by &lt;strong&gt;User2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;This is an important detail&lt;/em&gt;&lt;/strong&gt;. &lt;strong&gt;More users&lt;/strong&gt; means &lt;strong&gt;more parallel jobs&lt;/strong&gt;, but not if all jobs are scheduled by a single user. The limit is per user, so different users need to schedule jobs to make use of this higher limit, otherwise 5 jobs will be executed and others will wait.&lt;/p&gt;

&lt;p&gt;Now this is annoying, especially since it’s not uncommon to have &lt;strong&gt;just one person responsible for scheduling jobs&lt;/strong&gt;. However, we can take advantage of these new limits as they &lt;strong&gt;also apply to S2S users&lt;/strong&gt;. Something I was playing around with is, that I registered multiple &lt;em&gt;Entra Applications&lt;/em&gt; in BC, and created an action on the &lt;em&gt;Job Queue Entry Card&lt;/em&gt; to schedule the job as a &lt;em&gt;System user&lt;/em&gt;. More on that in the second part.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;new-odata-request-limits&quot;&gt;New OData request limits&lt;/h3&gt;

&lt;p&gt;Similarly, the OData limits also got a refresh from per-environment to per-user limits. Let’s first take a look at the old limits.&lt;/p&gt;

&lt;p&gt;We could send &lt;strong&gt;up to 600 requests per minute&lt;/strong&gt; (300 for sandbox environments), of which &lt;strong&gt;5 were concurrently processed&lt;/strong&gt;, the rest piled in a queue of up to 95. When the queue was full, &lt;em&gt;429 - Too Many Requests&lt;/em&gt; was returned.&lt;/p&gt;

&lt;p&gt;First, the change in rate limits. It changed from 600 per minute to &lt;strong&gt;6000 per 5 minutes&lt;/strong&gt;. Meaning, we can now have &lt;strong&gt;higher peaks&lt;/strong&gt; of requests as long as they’re not too close together.&lt;/p&gt;

&lt;p&gt;The new concurrency limit is again per user. Say we have &lt;strong&gt;5 users&lt;/strong&gt;, we can have &lt;strong&gt;25 OData requests processed concurrently&lt;/strong&gt; if they’re coming in as 5 different users. This also means we have 5 queues where the requests will wait. But the same &lt;strong&gt;&lt;em&gt;“important detail”&lt;/em&gt;&lt;/strong&gt; applies here. Just because we have 5 users in our environment doesn’t mean we can process 25 parallel requests. &lt;strong&gt;&lt;em&gt;They will only be processed in parallel if they come in as 5 different users.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I personally always built integrations with BC by creating an Entra App Registration and sending all the requests as this one user, but to make use of this additional throughput, I started looking into &lt;strong&gt;distributing the requests across multiple Entra Applications&lt;/strong&gt; and thus benefiting from parallel processing. More on that in the second part.&lt;/p&gt;

&lt;p&gt;You can read more about the new operational limits &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/administration/operational-limits-online&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;But now let’s jump into &lt;strong&gt;how to make the most of the new limits&lt;/strong&gt;. I would like to add that I’m &lt;strong&gt;not a fan&lt;/strong&gt; of these session-hacking approaches. &lt;em&gt;Should we really be doing this?&lt;/em&gt; Microsoft said yes, but honestly if they’re giving us &lt;strong&gt;additional capacity per user&lt;/strong&gt;, why add this (&lt;em&gt;maybe stupid&lt;/em&gt;) limitation that it also needs to be &lt;strong&gt;consumed per user?&lt;/strong&gt; Is it really &lt;em&gt;additional capacity per user&lt;/em&gt;, when we can create an &lt;strong&gt;unlimited amount of Entra Application users&lt;/strong&gt;? I have a feeling this will &lt;strong&gt;change again soon&lt;/strong&gt;, as this is more of a workaround than an architectural approach. But hey, &lt;strong&gt;sometimes workarounds are needed&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;schedule-a-job-as-an-s2s-user&quot;&gt;Schedule a Job as an S2S user&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Okay, I can achieve higher throughput by scheduling jobs as different users. But scheduling jobs is my responsibility, I don’t want to go around asking people to schedule jobs for me…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To solve this, I started playing around with the functionality that would allow me to &lt;strong&gt;schedule a job as a system user&lt;/strong&gt;, using one of the S2S application users. To get there, I created a couple of app registrations&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/entra-apps.png&quot; alt=&quot;Entra Applications&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve added two new fields to the card, to specify if the application should be &lt;strong&gt;used for scheduling jobs&lt;/strong&gt; and its &lt;strong&gt;client secret&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/entra-app-card.png&quot; alt=&quot;Entra Application Card&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now I can go and create a new Job Queue Entry as I normally would. Once I set it up the way I want it, I have a new action to &lt;strong&gt;schedule the task as a System User&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/schedule-job-card.png&quot; alt=&quot;Schedule a new job&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Once the action is executed, the job is scheduled as one of the &lt;strong&gt;available Entra App users&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/scheduled-job.png&quot; alt=&quot;Job scheduled as S2S user&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Let’s take a look at what’s happening in the &lt;em&gt;background&lt;/em&gt; when the action is executed.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;&lt;em&gt;tl;dr&lt;/em&gt;&lt;/strong&gt; is, the action &lt;strong&gt;finds an Entra App&lt;/strong&gt; with the least number of scheduled jobs assigned to it and &lt;strong&gt;calls a BC API to schedule the selected job&lt;/strong&gt; as this Entra App. But let’s still look at the code a bit.&lt;/p&gt;

&lt;p&gt;The action calls this &lt;em&gt;EnqueueJob&lt;/em&gt; procedure:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/enqueue-job.png&quot; alt=&quot;Enqueue job procedure&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Check Web Service&lt;/em&gt; simply ensures that my API for enqueuing jobs is &lt;strong&gt;published&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/check-web-service.png&quot; alt=&quot;Check Web Service Procedure&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is the API that will &lt;strong&gt;schedule the job&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/enqueue-job-api.png&quot; alt=&quot;Enqueue job API&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find Least Loaded App&lt;/em&gt; finds the Entra Application with the &lt;strong&gt;least amount of Job Queue Entries assigned&lt;/strong&gt; to it. Only Entra Applications that are set up for Job Queue scheduling are considered. Currently, it just looks over all the job queue entries. The next step would be to take the scheduled period into account. If the new job should be scheduled for &lt;em&gt;Monday morning&lt;/em&gt;, then I’m only interested in Entra Apps that are least loaded on &lt;em&gt;Monday morning&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/scheduled-job-query.png&quot; alt=&quot;Find least loaded app query&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I won’t get into the details of &lt;em&gt;GetToken&lt;/em&gt; and &lt;em&gt;SendRequest&lt;/em&gt;, one retrieves an &lt;em&gt;AccessToken&lt;/em&gt; from &lt;em&gt;Entra&lt;/em&gt; for the given app, and the other builds and sends a request to the above-mentioned API. If you’d like to see the &lt;strong&gt;full source&lt;/strong&gt; for this example, you can find it &lt;a href=&quot;https://github.com/tinestaric/BCExamples/tree/Master/ScheduleJobsAsS2S&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This approach now lets me take full advantage of &lt;strong&gt;new concurrency limits&lt;/strong&gt; by simply adding &lt;strong&gt;more Entra Applications&lt;/strong&gt; as needed and scheduling the jobs under these apps.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;integrate-with-bc-online-using-a-s2s-user-pool&quot;&gt;Integrate with BC Online using a S2S user pool&lt;/h3&gt;

&lt;p&gt;So we know how to make the most out of the new scheduled tasks concurrency limits. &lt;strong&gt;&lt;em&gt;What about OData?&lt;/em&gt;&lt;/strong&gt; Well, to make the most out of this one, we’ll have to &lt;strong&gt;change the way we authenticate our requests&lt;/strong&gt; that go to BC. As mentioned earlier, in the past, we created an &lt;strong&gt;Entra App&lt;/strong&gt; and used this app &lt;strong&gt;for all requests&lt;/strong&gt;. Now, we should instead introduce an &lt;strong&gt;app pool&lt;/strong&gt;, and &lt;strong&gt;distribute&lt;/strong&gt; our requests &lt;strong&gt;across many Entra Apps&lt;/strong&gt;. I built a simple example in &lt;strong&gt;C#&lt;/strong&gt; to show how this can be used. You can find the &lt;strong&gt;full source code&lt;/strong&gt; &lt;a href=&quot;https://github.com/tinestaric/BCExamples/tree/Master/CallApiWithUserPool&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Again, a quick &lt;strong&gt;&lt;em&gt;tl;dr&lt;/em&gt;&lt;/strong&gt; first. The program initializes a &lt;em&gt;User Pool Manager&lt;/em&gt; with my &lt;em&gt;Entra Applications&lt;/em&gt;, creates an &lt;em&gt;Api Service&lt;/em&gt; using this pool manager, and then when calling the API, it will &lt;strong&gt;rotate the Entra applications&lt;/strong&gt; used in authentication in a &lt;strong&gt;round-robin manner&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Keep in mind, that this is only a &lt;em&gt;quick demo&lt;/em&gt;, so there are many shortcomings. Don’t hardcode secrets, don’t request a new token each time, as they’re valid for a longer period, … But I hope this example still illustrates how we can &lt;strong&gt;rotate S2S users for request authentication&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s the &lt;em&gt;Main class&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/main-class.png&quot; alt=&quot;Main Class for integrating with user pool&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;S2SUser&lt;/em&gt; is just a simple class to contain credentials&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/s2s-class.png&quot; alt=&quot;S2S User Class&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;UserPoolManager&lt;/em&gt; manages the pool of users and rotates them&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/user-pool-manager.png&quot; alt=&quot;User Pool Manager Class&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ApiService&lt;/em&gt; is where we then actually call the API using a &lt;strong&gt;different user every time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/operationallimits/api-service.png&quot; alt=&quot;Api Service Class&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Using this approach, I can now shoot calls off to BC and have &lt;strong&gt;15 requests processed in parallel&lt;/strong&gt; instead of the usual 5. If I’d need even more throughput, I’d create &lt;strong&gt;more Entra Apps&lt;/strong&gt;, and &lt;strong&gt;spread the load across&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;One final thing to keep in mind. At some point, &lt;em&gt;Microsoft&lt;/em&gt; will introduce &lt;strong&gt;global limits or “quotas”&lt;/strong&gt;, so this &lt;em&gt;“free performance”&lt;/em&gt; won’t scale &lt;em&gt;forever&lt;/em&gt;, but at this point, they haven’t yet decided on the quotas. Once they’re in place, the &lt;strong&gt;docs will be updated&lt;/strong&gt; accordingly. But overall, It’s great seeing improvements on the infrastructure side of BC. I hope we’ll move away from having to abuse multiple sessions, but anyway, looking forward to what’s coming next.&lt;/p&gt;

</description>
        <pubDate>Wed, 24 Apr 2024 10:00:00 +0200</pubDate>
        <link>https://tine.staric.net/blog/2024/new-operational-limits/</link>
        <guid isPermaLink="true">https://tine.staric.net/blog/2024/new-operational-limits/</guid>
        
        
        <category>al</category>
        
      </item>
    
      <item>
        <title>Exploring the new Release Wave as a Department - The Blueprint</title>
        <description>&lt;h3 id=&quot;a-new-release-is-on-the-streets-yay&quot;&gt;A new release is on the streets! Yay!&lt;/h3&gt;

&lt;p&gt;I like new stuff, it’s exciting. But a &lt;strong&gt;Release Wave every 6 months&lt;/strong&gt;, even if it’s not the biggest wave, makes it difficult for us as a &lt;em&gt;Business Central&lt;/em&gt; department to &lt;strong&gt;stay up to date&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Usually, it comes down to individuals taking their time to investigate &lt;strong&gt;&lt;em&gt;what’s new&lt;/em&gt;&lt;/strong&gt;, and maybe sharing with a couple of colleagues &lt;em&gt;over a coffee&lt;/em&gt;. But that wasn’t the best approach, as not everyone took the time to look into the release notes, and even if we did, we usually focused on things that &lt;em&gt;sounded&lt;/em&gt; interesting to us. That means, we know what the new topics in &lt;strong&gt;development&lt;/strong&gt; are, but &lt;em&gt;Power Platform? Governance? Reporting? &lt;strong&gt;Eh, not my area…&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Turns out, the longer you ignore &lt;em&gt;“not my area”&lt;/em&gt; topics, the more difficult it becomes to pick them up when a project rolls around that needs to leverage the &lt;em&gt;Power Platform&lt;/em&gt; for example. This wasn’t the best approach, so for this release, we decided to take a &lt;strong&gt;more structured approach&lt;/strong&gt; to how we’re going to stay on top of everything &lt;em&gt;Microsoft&lt;/em&gt; released. And hence, here’s &lt;strong&gt;&lt;em&gt;“The Blueprint”&lt;/em&gt;&lt;/strong&gt; of how we went about it. &lt;em&gt;Is it the best way?&lt;/em&gt; &lt;strong&gt;&lt;em&gt;We don’t know yet.&lt;/em&gt;&lt;/strong&gt; It worked out pretty okay this time, but we’ll know when &lt;strong&gt;future releases start rolling out.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;the-problems-we-wanted-to-solve&quot;&gt;The problems we wanted to solve:&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;How do we explore &lt;strong&gt;ALL&lt;/strong&gt; the topics of a new release&lt;/li&gt;
  &lt;li&gt;How do we share the findings with the &lt;strong&gt;whole department&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;How do we ensure that &lt;strong&gt;knowledge is retained&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we could go for &lt;em&gt;Directions for Partners&lt;/em&gt; &lt;a href=&quot;https://community.directions4partners.com/news/1923113&quot;&gt;What’s new webinar&lt;/a&gt; or &lt;em&gt;Companial’s&lt;/em&gt; &lt;a href=&quot;https://companial.com/webinars/microsoft-dynamics-365-business-central-2024-release-wave-1/&quot;&gt;overview of functional and technical enhancements&lt;/a&gt;. But as great of an overview as these two webinars provide, they still &lt;strong&gt;only scratch the surface&lt;/strong&gt;, and as a department we don’t have any &lt;strong&gt;hands-on experience&lt;/strong&gt; with the new topics. So we needed something more.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;heres-our-approach&quot;&gt;Here’s our approach&lt;/h3&gt;
&lt;p&gt;We built a team of 6 people, all experienced, but not only seniors, who were tasked with exploring the new release and we broke the &lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/release-plan/2024wave1/smb/dynamics365-business-central/planned-features&quot;&gt;release plan&lt;/a&gt;&lt;/strong&gt; down into 6 topics, one for each:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Power Platform&lt;/li&gt;
  &lt;li&gt;Application, Country and Regional, Legislation&lt;/li&gt;
  &lt;li&gt;Copilot and AI innovation&lt;/li&gt;
  &lt;li&gt;Development&lt;/li&gt;
  &lt;li&gt;Governance and administration&lt;/li&gt;
  &lt;li&gt;Reporting and data analysis, user experiences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some topics are bigger than others. We approached it in two ways. If an area was too big, someone with a smaller area took some of the topics. The alternative was, that the person with a smaller area went a &lt;strong&gt;release further back&lt;/strong&gt;, to v23 in this case, and explored what was new there. As mentioned at the start, we weren’t exactly thorough with previous releases, so there are plenty of things to &lt;strong&gt;catch up on&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We decided to spend a day off-site to work on our exploration. Nothing fancy, just away from the office. There’s always an email or a bug waiting otherwise. We scheduled it during &lt;em&gt;Microsoft’s&lt;/em&gt; &lt;strong&gt;&lt;a href=&quot;aka.ms/bcle&quot;&gt;Launch Event&lt;/a&gt;&lt;/strong&gt;, primarily because one of the goals each person had was to &lt;strong&gt;come up with at least 3 questions&lt;/strong&gt; to ask during the &lt;strong&gt;Q&amp;amp;A sessions&lt;/strong&gt;. It’s how we ensured that each one has gone &lt;strong&gt;deep enough&lt;/strong&gt; into the topic and also &lt;strong&gt;critically thought about it&lt;/strong&gt;, &lt;em&gt;what’s missing? What’s the future? What could be clearer?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;During the day, the focus was mainly on launch event videos, documentation and hands-on demos. We had a couple of quick catch-ups to present our findings among ourselves and just brainstorm on potential questions we’d like to get answered. The only preparation we did beforehand was for everyone to set up their &lt;strong&gt;cloud sandboxes&lt;/strong&gt; where we demoed the features.&lt;/p&gt;

&lt;p&gt;After our exploration day had ended, we started preparing for a &lt;strong&gt;90-minute Knowledge Sharing session for the whole department&lt;/strong&gt; we held a week later. We wanted each one to have their time slot, instead of one person presenting everything. Partially so everyone could &lt;strong&gt;hone their presentation skills&lt;/strong&gt;, but also so they become the go-to person for their area. Not like &lt;em&gt;the&lt;/em&gt; go-to person, but just &lt;strong&gt;someone who others can now reach out to&lt;/strong&gt; if they have a question in this area.&lt;/p&gt;

&lt;p&gt;This worked out pretty well. We now have a department that is &lt;em&gt;aware&lt;/em&gt; of new features and &lt;strong&gt;6 people who have a deep understanding&lt;/strong&gt; of new release areas. The plan for the next release is to &lt;strong&gt;rotate them&lt;/strong&gt; and get &lt;strong&gt;6 new people&lt;/strong&gt; diving into the &lt;strong&gt;Release Wave 2&lt;/strong&gt;. That way, we’ll slowly upskill more people into &lt;strong&gt;&lt;em&gt;“off-brand” topics&lt;/em&gt;&lt;/strong&gt; like &lt;em&gt;governance&lt;/em&gt; or &lt;em&gt;data analysis&lt;/em&gt;, and additionally, they’ll have people from the first Wave to discuss topics with.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;lessons-learned&quot;&gt;Lessons learned&lt;/h3&gt;

&lt;p&gt;Things &lt;em&gt;sound&lt;/em&gt; great so far, but here’s a couple of lessons learned that we’ll try to &lt;strong&gt;address next time&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Don’t expect that people will &lt;strong&gt;catch up on 4 years of release waves in 8 hours&lt;/strong&gt;. Keep investigations contained.&lt;/li&gt;
  &lt;li&gt;People can get caught on a single topic for a full day. &lt;strong&gt;Time box and move on&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Ask &lt;strong&gt;very specific questions&lt;/strong&gt; to Microsoft. &lt;em&gt;“What’s the future of Copilot?”&lt;/em&gt; will get a vague response. They won’t commit to any future plans. But they’ll gladly clarify details of new features.&lt;/li&gt;
  &lt;li&gt;8 hours of research is &lt;strong&gt;draining&lt;/strong&gt;. Remember to take breaks and keep things light.&lt;/li&gt;
  &lt;li&gt;Even a seemingly &lt;strong&gt;small release can easily occupy 6 people&lt;/strong&gt; for a day.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;I know &lt;em&gt;“losing”&lt;/em&gt; 6 people for a day might be a big ask for some partners, but I believe it’s totally worth it. The tasks regarding &lt;em&gt;tenant administration&lt;/em&gt;, or &lt;em&gt;virtual tables&lt;/em&gt; will feel far &lt;strong&gt;less daunting&lt;/strong&gt;, as we now have people who can support us on these &lt;strong&gt;non-traditional work items&lt;/strong&gt;. Also, I can’t speak for the rest of the team, but I found diving into topics &lt;em&gt;“beyond”&lt;/em&gt; BC quite exciting!&lt;/p&gt;

</description>
        <pubDate>Mon, 15 Apr 2024 10:00:00 +0200</pubDate>
        <link>https://tine.staric.net/blog/2024/release-wave-blueprint/</link>
        <guid isPermaLink="true">https://tine.staric.net/blog/2024/release-wave-blueprint/</guid>
        
        
        <category>al</category>
        
      </item>
    
  </channel>
</rss>
