<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hayim Romanovsky</title>
    <description>The latest articles on DEV Community by Hayim Romanovsky (@hayim_romanovsky_2b964e3c).</description>
    <link>https://dev.to/hayim_romanovsky_2b964e3c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4229887%2Fab3f3ada-8b43-40ef-b1a1-754e984d288c.jpg</url>
      <title>DEV Community: Hayim Romanovsky</title>
      <link>https://dev.to/hayim_romanovsky_2b964e3c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hayim_romanovsky_2b964e3c"/>
    <language>en</language>
    <item>
      <title>Migrating Lotus Notes to SQL Server: mapping documents to tables</title>
      <dc:creator>Hayim Romanovsky</dc:creator>
      <pubDate>Fri, 06 Nov 2026 08:00:19 +0000</pubDate>
      <link>https://dev.to/hayim_romanovsky_2b964e3c/migrating-lotus-notes-to-sql-server-mapping-documents-to-tables-3b5a</link>
      <guid>https://dev.to/hayim_romanovsky_2b964e3c/migrating-lotus-notes-to-sql-server-mapping-documents-to-tables-3b5a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://migrationparity.com/articles/lotus-notes-to-sql-server/" rel="noopener noreferrer"&gt;migrationparity.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The inventory script finished at two in the morning and told me our twenty-year-old&lt;br&gt;
NSF contained not one schema but three. Documents saved before 2008 had one set of&lt;br&gt;
items, documents from the middle years another, recent ones a third — same form name&lt;br&gt;
on all of them. SQL Server was going to want one answer, and the database had nine&lt;br&gt;
thousand opinions.&lt;/p&gt;

&lt;p&gt;That tension — a bag-of-items document store on one side, tables with columns on the&lt;br&gt;
other — is the whole project. The export itself is a solved problem; any tool can walk&lt;br&gt;
an NSF and emit rows. What follows is where the mapping decisions actually live, from&lt;br&gt;
a ~9,000-document workflow application I took to .NET with a SQL Server backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Columns come from documents, not from the form
&lt;/h2&gt;

&lt;p&gt;The form designer shows you what a &lt;em&gt;new&lt;/em&gt; document would get. It says nothing about&lt;br&gt;
what the existing nine thousand are carrying. A Notes document keeps every item it&lt;br&gt;
was ever saved with, under whichever revision of the form existed that year — fields&lt;br&gt;
that occur only in 2003–2007, fields renamed twice, one field whose name contains a&lt;br&gt;
typo made around 1998 that could never be fixed because thousands of documents&lt;br&gt;
already had it. The typo goes in the mapping table like everything else.&lt;/p&gt;

&lt;p&gt;So the schema comes from an item census: scan all documents, collect item names,&lt;br&gt;
types, value counts, fill rates. Cluster by generation. The SQL schema is designed&lt;br&gt;
against the union, with an explicit rule per generation — convert, default, or&lt;br&gt;
consciously drop. Anything built from the current form design alone loses data&lt;br&gt;
without telling you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-value items want child tables
&lt;/h2&gt;

&lt;p&gt;Notes lets any field hold a list. Approvers, categories, linked batch numbers —&lt;br&gt;
one item, five values, no ceremony. SQL has three ways to take this and two of them&lt;br&gt;
are traps. Concatenating with semicolons means every future query does string&lt;br&gt;
surgery. Columns named &lt;code&gt;Approver1&lt;/code&gt; through &lt;code&gt;Approver8&lt;/code&gt; work until the ninth approver.&lt;/p&gt;

&lt;p&gt;The boring correct answer is a child table per genuinely multi-valued item:&lt;br&gt;
&lt;code&gt;document_id&lt;/code&gt;, &lt;code&gt;position&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;. Position matters — in Notes workflows the order&lt;br&gt;
of a names list often &lt;em&gt;is&lt;/em&gt; the approval order, and flattening it to a set quietly&lt;br&gt;
rewrites the business process. Where a multi-value field turned out to hold one value&lt;br&gt;
in 99% of documents, I kept it as a column and moved the exceptions into the child&lt;br&gt;
table during cleanup, each one eyeballed. There were eleven.&lt;/p&gt;

&lt;h2&gt;
  
  
  The response hierarchy is already a foreign key
&lt;/h2&gt;

&lt;p&gt;Notes documents form trees: main document, responses, responses-to-responses. Under&lt;br&gt;
the hood each response carries &lt;code&gt;$REF&lt;/code&gt; — the 32-character UNID of its parent. That is&lt;br&gt;
a parent key wearing a trench coat.&lt;/p&gt;

&lt;p&gt;The mapping is mechanical — &lt;code&gt;parent_id&lt;/code&gt; column, foreign key, done — but two things&lt;br&gt;
bite. First, UNIDs must survive the migration as a column (unique-indexed), because&lt;br&gt;
every doc link anyone ever pasted into an email or another document addresses the&lt;br&gt;
UNID, and because your own migration needs the old identity to be re-runnable.&lt;br&gt;
Second, orphans exist: responses whose parent was deleted years ago. Our database&lt;br&gt;
had a few dozen. Decide their fate in the mapping spec — adopt, archive, or drop —&lt;br&gt;
not at 2 a.m. when the foreign-key constraint fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rich text is a file format, attachments are a separate estate
&lt;/h2&gt;

&lt;p&gt;I covered rich text in detail in&lt;br&gt;
&lt;a href="https://dev.to/articles/lotus-notes-domino-to-dotnet/"&gt;what actually breaks in a Notes migration&lt;/a&gt;:&lt;br&gt;
treat it as a document tree, convert to disciplined HTML, extract embedded objects.&lt;br&gt;
For the SQL mapping the decision is narrower — rich text does not go &lt;em&gt;into&lt;/em&gt; SQL&lt;br&gt;
Server beyond the HTML body. Images, attachments and OLE objects go to real file&lt;br&gt;
storage with reference rows.&lt;/p&gt;

&lt;p&gt;One trap deserves its own paragraph. If the Domino server uses DAOS, the NSF you&lt;br&gt;
copied for testing does not contain the attachments at all — it contains tickets&lt;br&gt;
pointing into a directory of &lt;code&gt;.nlo&lt;/code&gt; files on the server. Ours held 32 GB across&lt;br&gt;
roughly sixty thousand of them. A migration rehearsed against a bare NSF copy&lt;br&gt;
reports success and delivers a database where every paperclip icon leads nowhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Names are not users
&lt;/h2&gt;

&lt;p&gt;Every author, approver and reader in the NSF is recorded as a hierarchical Notes&lt;br&gt;
name — &lt;code&gt;CN=Dana Levy/OU=QA/O=ACME&lt;/code&gt; — not as a login your new system knows. People&lt;br&gt;
married, transferred departments, retired in 2011 and are still named on open&lt;br&gt;
documents. SQL wants a &lt;code&gt;users&lt;/code&gt; table with stable IDs; your HR system joins to Notes&lt;br&gt;
names only by string matching, if at all.&lt;/p&gt;

&lt;p&gt;Budget a name-resolution pass as a first-class migration table: Notes name →&lt;br&gt;
canonical person → new system account, with a human owner for the unmatched&lt;br&gt;
residue. It looks like a utility script. It is two weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reader fields become row-level security, or your migration is a breach
&lt;/h2&gt;

&lt;p&gt;Reader and author items make documents invisible at the engine level; export them&lt;br&gt;
to SQL without reproducing that and on day one everyone reads everything —&lt;br&gt;
confidential cases included. Nothing will look broken. In SQL Server the honest&lt;br&gt;
equivalent is row-level security predicates driven by the same person/role mapping&lt;br&gt;
as above, tested by logging in as sampled real identities on both systems and&lt;br&gt;
diffing what each one sees.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version of the mapping
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;In the NSF&lt;/th&gt;
&lt;th&gt;In SQL Server&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Form (all historical generations)&lt;/td&gt;
&lt;td&gt;Table designed against the item &lt;em&gt;union&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Item&lt;/td&gt;
&lt;td&gt;Column, with per-generation conversion rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-value item&lt;/td&gt;
&lt;td&gt;Child table with position&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response / &lt;code&gt;$REF&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;parent_id&lt;/code&gt; FK; UNID kept as unique column&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;HTML body + files in storage, reference rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DAOS attachment&lt;/td&gt;
&lt;td&gt;File in storage — only reachable via the server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notes hierarchical name&lt;/td&gt;
&lt;td&gt;Row in a name-resolution table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reader / author fields&lt;/td&gt;
&lt;td&gt;Row-level security predicates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document history item&lt;/td&gt;
&lt;td&gt;Verbatim text, appended-to, never rewritten&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is deliberate. Our application carried its whole audit trail as one&lt;br&gt;
ever-growing text item per document. The temptation is to parse it into neat event&lt;br&gt;
rows. Parse a &lt;em&gt;copy&lt;/em&gt; if you want the analytics — but keep the original text column&lt;br&gt;
byte-for-byte, because it is the only record auditors already trust, and the parser&lt;br&gt;
will be wrong about 1997.&lt;/p&gt;

&lt;p&gt;The export tool finished its part in an afternoon. The mapping spec — the real&lt;br&gt;
deliverable — was forty pages, and every page was one of the rows above arguing&lt;br&gt;
with twenty years of actual documents.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the kind of mapping our&lt;br&gt;
&lt;a href="https://dev.to/services/lotus-notes-domino-migration/"&gt;Lotus Notes / Domino migration service&lt;/a&gt;&lt;br&gt;
produces before any code is written — and the parity harness then proves the result&lt;br&gt;
against the live NSF, document by document. If you are sizing such a move, start&lt;br&gt;
with the assessment.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>lotusnotes</category>
      <category>domino</category>
      <category>sqlserver</category>
      <category>migration</category>
    </item>
    <item>
      <title>Case study: the 1997 insurance system that stopped printing on Windows 11</title>
      <dc:creator>Hayim Romanovsky</dc:creator>
      <pubDate>Fri, 06 Nov 2026 08:00:16 +0000</pubDate>
      <link>https://dev.to/hayim_romanovsky_2b964e3c/case-study-the-1997-insurance-system-that-stopped-printing-on-windows-11-hgj</link>
      <guid>https://dev.to/hayim_romanovsky_2b964e3c/case-study-the-1997-insurance-system-that-stopped-printing-on-windows-11-hgj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://migrationparity.com/articles/case-1997-agency-system-windows-11/" rel="noopener noreferrer"&gt;migrationparity.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The inquiry came through the site's contact form on a Monday: a small insurance&lt;br&gt;
firm in the American South, still running the agency management system it bought&lt;br&gt;
in 1997. The software worked. The data was fine. But on the office's new&lt;br&gt;
Windows 11 laptop, under the vDos DOS emulator, every report ended in the same red&lt;br&gt;
box — &lt;em&gt;"Couldn't access temporary file"&lt;/em&gt; — and nothing had printed for months. The&lt;br&gt;
old Windows XP machine, where everything worked, had just refused to boot.&lt;/p&gt;

&lt;p&gt;The owner didn't want a migration. They wanted their balance sheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was actually running
&lt;/h2&gt;

&lt;p&gt;It was a Clipper-built agency management package from 1997 — vendor long gone, no&lt;br&gt;
source code, modules for marketing, accounting and document imaging. Reports go&lt;br&gt;
through an embedded R&amp;amp;R Report Writer runtime from 1995. Printing was designed for&lt;br&gt;
an HP LaserJet on a serial port, speaking PCL. The office's current printer is a&lt;br&gt;
Wi-Fi inkjet that has never heard of PCL.&lt;/p&gt;

&lt;p&gt;The installation media the client could share was the original distribution: nine&lt;br&gt;
spanned diskette images, straight from the era when software arrived in a cardboard&lt;br&gt;
box. I installed it into a clean vDos sandbox on my side and had their exact error&lt;br&gt;
on the screen the same evening — without any printer attached at all. That&lt;br&gt;
mattered: the printer was innocent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual fault, three layers down
&lt;/h2&gt;

&lt;p&gt;The report module failed before it ever reached printing. The main executable is&lt;br&gt;
built with Blinker 3.30, a DOS extender; when it launches the report engine it swaps&lt;br&gt;
itself out of memory and execs the child process. That swap-run mechanism needs a&lt;br&gt;
DPMI host — a memory-management service that real DOS setups of the era, and the&lt;br&gt;
NTVDM layer in Windows XP, quietly provided.&lt;/p&gt;

&lt;p&gt;vDos doesn't provide one. The swap fails silently, the report engine never starts,&lt;br&gt;
and the application reports the only thing it can see: the output file its child&lt;br&gt;
never produced. "Couldn't access temporary file" — technically true, maximally&lt;br&gt;
misleading. It took a 49-byte stub program planted in place of the report engine to&lt;br&gt;
prove the engine was never being called at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix was small — finding it was the job
&lt;/h2&gt;

&lt;p&gt;Once the cause was pinned down, the remedy fit in a small kit the client installed&lt;br&gt;
themselves from a two-step README: a memory-management host that loads before the&lt;br&gt;
application, and a rendering layer that catches the application's print jobs and&lt;br&gt;
turns each one into a PDF — from there, any modern printer. The 1990s printer&lt;br&gt;
profiles inside the application stayed untouched; PCL is just a language, and it&lt;br&gt;
got a translator.&lt;/p&gt;

&lt;p&gt;Two days of that week went into the diagnosis — reproducing the environment,&lt;br&gt;
instrumenting a binary with no source, ruling out the printer, the config and the&lt;br&gt;
emulator one by one. The installation took the client about a minute. That ratio&lt;br&gt;
is normal for this kind of work; it's also why the fix survives Windows updates&lt;br&gt;
instead of being a lucky tweak.&lt;/p&gt;

&lt;p&gt;No reinstall, no data conversion, no settings changed inside the application. The&lt;br&gt;
client's verdict, verbatim: &lt;em&gt;"This ran perfectly… probably under DOS 6.0."&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time from first email to confirmed fix&lt;/td&gt;
&lt;td&gt;8 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote sessions / calls needed&lt;/td&gt;
&lt;td&gt;0 — email and screenshots only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;a few hundred dollars, fixed — half up front, no-fix-no-fee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change to the client's data or workflow&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Follow-up utilities since&lt;/td&gt;
&lt;td&gt;2 small ones, same by-email pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A week later the client was entering policies again and running the system&lt;br&gt;
side-by-side with the books through year end. The agency's thirty years of customer history&lt;br&gt;
stayed exactly where it has always lived — in DBF tables a&lt;br&gt;
&lt;a href="https://dev.to/tools/dbf-analyzer/"&gt;free analyzer&lt;/a&gt; can read in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is on a migration consultancy's site
&lt;/h2&gt;

&lt;p&gt;Because containment is a legitimate outcome. The decision framework in&lt;br&gt;
&lt;a href="https://dev.to/articles/rewrite-vs-convert-vs-wrap/"&gt;rewrite vs convert vs wrap&lt;/a&gt; has a branch&lt;br&gt;
most vendors skip: when the system is sound, the business is small and the pain is&lt;br&gt;
environmental, the right engineering is to fix the environment — and keep the&lt;br&gt;
migration conversation for the moment it earns its cost. A repair costing a few&lt;br&gt;
hundred dollars that buys years is a better first step than a $50,000 rewrite that&lt;br&gt;
buys the same years with&lt;br&gt;
more risk. When this client is ready for more — a modern front end, a second user,&lt;br&gt;
the cloud — the path is mapped, and nothing done this week blocks it.&lt;/p&gt;

&lt;p&gt;If you have a Clipper-era system that still earns its keep but fights its&lt;br&gt;
environment, that's exactly the shape of problem our&lt;br&gt;
&lt;a href="https://dev.to/services/clipper-xbase-migration/"&gt;Clipper / xBase service&lt;/a&gt; starts with —&lt;br&gt;
assessment first, and sometimes the assessment honestly says: don't migrate yet.&lt;/p&gt;

</description>
      <category>casestudy</category>
      <category>clipper</category>
      <category>vdos</category>
      <category>dos</category>
    </item>
    <item>
      <title>Rewrite vs convert vs wrap: choosing a legacy migration strategy honestly</title>
      <dc:creator>Hayim Romanovsky</dc:creator>
      <pubDate>Fri, 06 Nov 2026 07:43:51 +0000</pubDate>
      <link>https://dev.to/hayim_romanovsky_2b964e3c/rewrite-vs-convert-vs-wrap-choosing-a-legacy-migration-strategy-honestly-3o0m</link>
      <guid>https://dev.to/hayim_romanovsky_2b964e3c/rewrite-vs-convert-vs-wrap-choosing-a-legacy-migration-strategy-honestly-3o0m</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://migrationparity.com/articles/rewrite-vs-convert-vs-wrap/" rel="noopener noreferrer"&gt;migrationparity.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every legacy application owner eventually sits through three contradictory sales pitches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;"Rewrite it"&lt;/strong&gt; — from a dev shop that has never seen your business rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Convert it automatically"&lt;/strong&gt; — from a tool vendor whose demo compiles your code to C# in
an afternoon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Don't touch it, just wrap it"&lt;/strong&gt; — from whoever will be blamed if the migration fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All three strategies are real, and each is correct &lt;em&gt;somewhere&lt;/em&gt;. Here is the honest comparison —&lt;br&gt;
including the option the pitches skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rewrite: rebuild behavior on a new stack
&lt;/h2&gt;

&lt;p&gt;A rewrite does not have to mean "start from the requirements and hope". Done properly, the old&lt;br&gt;
system itself is the specification: you capture its actual behavior as an executable&lt;br&gt;
&lt;a href="https://dev.to/articles/clipper-xbase-migration-parity-testing/"&gt;parity harness&lt;/a&gt; — reports, totals, edge&lt;br&gt;
cases, bugs included — and rebuild module by module until the diff is empty.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Get:&lt;/strong&gt; a system you fully own — modern stack, hireable developers, testable code, and the
first real documentation the application has ever had (the harness itself).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay:&lt;/strong&gt; the most money and calendar time of the three. The parity harness is 15–25% of the
budget before any new code runs — that is the price of "nothing changed except the platform".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail mode:&lt;/strong&gt; the &lt;em&gt;unharnessed&lt;/em&gt; rewrite — screens rebuilt from screenshots, rules from
memory. This is the version that produced the migration horror story your CFO remembers, and
it deserves its reputation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Convert: transpile the code automatically
&lt;/h2&gt;

&lt;p&gt;Conversion tools parse your Clipper/FoxPro/VB/PowerBuilder code and emit the equivalent in a&lt;br&gt;
modern language. The demo is genuinely impressive: a week in, everything compiles.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Get:&lt;/strong&gt; speed to "runs on the new platform", preserved logic (including logic nobody
understands), and sometimes the only viable path when the codebase is enormous and the
budget is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay:&lt;/strong&gt; afterwards. The output is your old program wearing a new language — xBase idioms,
global state and GOTO-shaped control flow transliterated into C# that no C# developer will
enjoy or safely change. You have swapped a maintenance problem you knew for one you do not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail mode:&lt;/strong&gt; treating conversion as the destination. As a &lt;em&gt;bridge&lt;/em&gt; — convert to retire the
dead platform today, then refactor with the converted code as a reference — it can be
rational. As an endpoint, it usually means paying for the same migration twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap: keep the engine, replace the surface
&lt;/h2&gt;

&lt;p&gt;Wrapping keeps the legacy core running and builds new access around it: a web front-end over&lt;br&gt;
the old database, an API layer screen-scraping or driving the old app, new reports reading a&lt;br&gt;
mirrored copy of the data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Get:&lt;/strong&gt; fast, cheap, low-risk wins — modern UI for the 20% of workflows that needed it,
integration endpoints, reporting freedom. Often the right &lt;em&gt;first&lt;/em&gt; move, and sometimes right
for years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay:&lt;/strong&gt; the legacy core is still there — same platform risk, same 2 GB limits, same single
point of failure, plus a new synchronization layer to operate. Complexity went up, not down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail mode:&lt;/strong&gt; the permanent temporary solution. The wrap relieves the pain that was funding
the migration, the project stops, and five years later the organization runs &lt;em&gt;two&lt;/em&gt; systems
forever. Wrap with an explicit expiry date or do not wrap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The skipped option: contain, then decide
&lt;/h2&gt;

&lt;p&gt;For 16-bit-era xBase systems there is a fourth move the pitches rarely mention because no one&lt;br&gt;
sells licenses for it: &lt;strong&gt;containment on an open-source runtime&lt;/strong&gt;. Recompiling Clipper-dialect&lt;br&gt;
code with &lt;a href="https://dev.to/services/clipper-xbase-migration/"&gt;Harbour&lt;/a&gt; produces a native modern executable on&lt;br&gt;
the same data files — the platform emergency (dying hardware, DOS boxes, 16-bit Windows) ends&lt;br&gt;
in weeks, for a fraction of any other option, without betting the business on a big project.&lt;/p&gt;

&lt;p&gt;Containment is not the destination either. It is what buys the calm in which a rewrite can be&lt;br&gt;
done properly instead of heroically.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually choose
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;If the answer is yes →&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Is the platform actively collapsing (hardware, OS, drivers)?&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Contain first&lt;/strong&gt; — remove the emergency before deciding anything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Will the business still run on this logic in 10 years?&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Rewrite&lt;/strong&gt;, parity-tested, in slices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the codebase huge, the budget small, and the platform dead?&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Convert&lt;/strong&gt; — as a bridge with a refactoring plan, not an endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the pain concentrated in access (UI/integration/reporting), not the core?&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Wrap&lt;/strong&gt; — with a written expiry date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the application mostly habit?&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Retire it&lt;/strong&gt; — the cheapest migration is an export and an archive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two patterns from the field, offered as priors: the total cost ranking over a 10-year horizon&lt;br&gt;
is usually &lt;em&gt;retire &amp;lt; contain &amp;lt; rewrite &amp;lt; wrap-forever &amp;lt; convert-then-rewrite-anyway&lt;/em&gt;. And the&lt;br&gt;
single best predictor of migration success is not the strategy chosen — it is whether the&lt;br&gt;
current system's behavior was captured as tests before anyone wrote new code.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Our &lt;a href="https://dev.to/services/"&gt;assessment&lt;/a&gt; prices all applicable paths for your specific system — including&lt;br&gt;
the ones we would not be hired for. That is deliberate: a roadmap you can sanity-check against&lt;br&gt;
a vendor's pitch is worth more than any pitch.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>strategy</category>
      <category>methodology</category>
      <category>legacy</category>
    </item>
    <item>
      <title>Lotus Notes/Domino to .NET: what actually breaks</title>
      <dc:creator>Hayim Romanovsky</dc:creator>
      <pubDate>Fri, 06 Nov 2026 07:37:31 +0000</pubDate>
      <link>https://dev.to/hayim_romanovsky_2b964e3c/lotus-notesdomino-to-net-what-actually-breaks-511n</link>
      <guid>https://dev.to/hayim_romanovsky_2b964e3c/lotus-notesdomino-to-net-what-actually-breaks-511n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://migrationparity.com/articles/lotus-notes-domino-to-dotnet/" rel="noopener noreferrer"&gt;migrationparity.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I recently took a quality-management workflow application — roughly 9,000 documents accumulated&lt;br&gt;
over two decades in a single NSF — from Lotus Notes/Domino to .NET 8 with a SQL backend. The&lt;br&gt;
export part that every migration tool advertises took days. The part that took months was&lt;br&gt;
everything the brochures do not mention.&lt;/p&gt;

&lt;p&gt;This is the list I wish I had at the start: the six places a Notes migration actually breaks,&lt;br&gt;
and what to do about each.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. There is no schema — there are 25 years of schemas
&lt;/h2&gt;

&lt;p&gt;A Notes document is a bag of items. The form defines what &lt;em&gt;new&lt;/em&gt; documents get; every old&lt;br&gt;
document keeps whatever fields it was saved with, under whichever version of the form existed&lt;br&gt;
that year. In practice our "one form" was a geological record: fields that appear only in&lt;br&gt;
documents from 2003–2007, fields renamed twice, fields whose type quietly changed from text to&lt;br&gt;
multi-value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What works:&lt;/strong&gt; inventory the &lt;em&gt;documents&lt;/em&gt;, not the form. Scan every document's item names and&lt;br&gt;
types, cluster by generation, and design the SQL schema against the union — with explicit&lt;br&gt;
migration rules per generation. Any mapping built from the current form design alone will drop&lt;br&gt;
data silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Rich text is a container format wearing a text costume
&lt;/h2&gt;

&lt;p&gt;Notes rich-text fields hold formatted text — plus embedded images, file attachments, OLE&lt;br&gt;
objects (that spreadsheet someone pasted in 2009), collapsible sections, tables, and doc links&lt;br&gt;
pointing at other documents in other databases. A naive HTML export flattens this into soup;&lt;br&gt;
a text export just deletes it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What works:&lt;/strong&gt; treat rich text as a document tree. Convert formatting to sane HTML, extract&lt;br&gt;
attachments and images to real storage with references, and translate doc links into links in&lt;br&gt;
the &lt;em&gt;new&lt;/em&gt; system — which means migrating link targets first or maintaining a UNID-to-new-ID&lt;br&gt;
map for the whole estate. Budget real time here: rich-text fidelity was our single largest&lt;br&gt;
line item.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Reader fields: the security model that migrations silently drop
&lt;/h2&gt;

&lt;p&gt;Reader and author fields make documents invisible to unauthorized users — enforced by the&lt;br&gt;
database engine itself. Confidential HR cases, supplier complaints, audit findings: in Notes,&lt;br&gt;
users literally cannot see documents they are not named in. Export the documents to SQL,&lt;br&gt;
build a pretty new UI, forget this layer — and on day one every user can read everything.&lt;br&gt;
This is the most dangerous failure mode in Notes migration, because nothing &lt;em&gt;looks&lt;/em&gt; broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What works:&lt;/strong&gt; extract the effective access model — ACLs, roles, groups (expanded via the&lt;br&gt;
directory), reader/author items per document — and reproduce it as row-level security in the&lt;br&gt;
new system. Then test it &lt;em&gt;as identities&lt;/em&gt;: log in as a sampled set of real users on both systems&lt;br&gt;
and diff what each can see.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Roles fan out into hundreds of decisions
&lt;/h2&gt;

&lt;p&gt;The application had a handful of ACL roles — approver, coordinator, quality, admin. Innocent&lt;br&gt;
looking. But roles are referenced in form formulas, view selection formulas, hide-when clauses,&lt;br&gt;
agent logic and workflow-state transitions. Mapped onto the new system's authorization model,&lt;br&gt;
our modest role list exploded into &lt;strong&gt;hundreds of individual mapping decisions&lt;/strong&gt; — who may press&lt;br&gt;
this button in this state, who sees this view, who gets this escalation. It became the go-live&lt;br&gt;
blocker; not because any single decision was hard, but because each one needed a business owner&lt;br&gt;
to say yes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What works:&lt;/strong&gt; surface this early. Machine-extract every role reference, generate the decision&lt;br&gt;
matrix in week one, and put a named human owner on it. It is the longest-lead item in the whole&lt;br&gt;
project precisely because it is not a technical task.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Views and agents are the application
&lt;/h2&gt;

&lt;p&gt;Users do not experience "documents" — they experience views: selection formulas, categorized&lt;br&gt;
columns, computed values, twisties. And the workflow does not run itself — scheduled agents&lt;br&gt;
reassign, notify, escalate. Some agents encode the real process; some have been failing&lt;br&gt;
silently for a decade and the business adapted around the failure (which means &lt;em&gt;reviving&lt;/em&gt; them&lt;br&gt;
faithfully would break the current process!).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What works:&lt;/strong&gt; rebuild views as queries against the new schema, with the parity discipline of&lt;br&gt;
diffing view contents between systems on frozen data. For agents: inventory, then &lt;em&gt;observe&lt;/em&gt; —&lt;br&gt;
which ones actually fired last year, what they changed — before deciding which to reproduce,&lt;br&gt;
which to redesign, and which to let die officially.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Cutover is a coexistence problem, not an event
&lt;/h2&gt;

&lt;p&gt;Nine thousand documents of living workflow cannot stop for a migration weekend. The old and new&lt;br&gt;
systems overlap: new cases open in .NET while old cases finish their lifecycle in Notes, or&lt;br&gt;
historical documents remain readable in Notes long after writes moved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What works:&lt;/strong&gt; plan the bridge deliberately. COM interop against the Notes client (or Domino's&lt;br&gt;
HTTP interfaces) lets the new system read live Notes data during the transition — clunky but&lt;br&gt;
effective; keep the bridge component registered and documented until the last dependency is&lt;br&gt;
gone, then remove it &lt;em&gt;on purpose&lt;/em&gt; rather than leaving it as a permanent mystery service. Retire&lt;br&gt;
Domino to read-only, then to archive, only after a full business cycle proves nothing still&lt;br&gt;
reaches for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the effort
&lt;/h2&gt;

&lt;p&gt;For our ~9,000-document application, the honest cost breakdown was approximately:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workstream&lt;/th&gt;
&lt;th&gt;Share of effort&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rich text, attachments, doc links&lt;/td&gt;
&lt;td&gt;~30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security model + role mapping decisions&lt;/td&gt;
&lt;td&gt;~25%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema archaeology &amp;amp; document migration&lt;/td&gt;
&lt;td&gt;~20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Views, agents, workflow reproduction&lt;/td&gt;
&lt;td&gt;~15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coexistence bridge &amp;amp; cutover&lt;/td&gt;
&lt;td&gt;~10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note what is &lt;em&gt;not&lt;/em&gt; on the list: "exporting the documents". That part is a solved problem. The&lt;br&gt;
project is everything around it — which is why per-document licensing of export tools tells&lt;br&gt;
you almost nothing about what a Notes migration will cost.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This experience is packaged as our&lt;br&gt;
&lt;a href="https://dev.to/services/lotus-notes-domino-migration/"&gt;Lotus Notes / Domino migration service&lt;/a&gt;. If your&lt;br&gt;
estate is bigger than one database — most are — start with the assessment: usage measurement&lt;br&gt;
across databases typically retires half the estate before anything is migrated at all.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>lotusnotes</category>
      <category>domino</category>
      <category>dotnet</category>
      <category>migration</category>
    </item>
    <item>
      <title>Harbour in 2026: the first stable release in 15 years, and what it means for your Clipper app</title>
      <dc:creator>Hayim Romanovsky</dc:creator>
      <pubDate>Fri, 06 Nov 2026 07:37:29 +0000</pubDate>
      <link>https://dev.to/hayim_romanovsky_2b964e3c/harbour-in-2026-the-first-stable-release-in-15-years-and-what-it-means-for-your-clipper-app-4d9h</link>
      <guid>https://dev.to/hayim_romanovsky_2b964e3c/harbour-in-2026-the-first-stable-release-in-15-years-and-what-it-means-for-your-clipper-app-4d9h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://migrationparity.com/articles/harbour-in-2026/" rel="noopener noreferrer"&gt;migrationparity.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In July 2026, the &lt;a href="https://harbour.github.io/" rel="noopener noreferrer"&gt;Harbour project&lt;/a&gt; shipped version 3.2.0 —&lt;br&gt;
its first stable release since 3.0.0 in &lt;strong&gt;2011&lt;/strong&gt;. Fifteen years between stable tags is the&lt;br&gt;
kind of fact that sounds like a punchline about dead software. It is the opposite: the 3.2&lt;br&gt;
branch has been alive, maintained and quietly running production systems that whole time —&lt;br&gt;
as an eternal "development snapshot". What changed in July is not the code. It is the label,&lt;br&gt;
and labels matter more than engineers like to admit.&lt;/p&gt;

&lt;p&gt;If your company still runs a Clipper-era application, this release is the best news your&lt;br&gt;
legacy stack has had in a decade. Here is why — and where the enthusiasm should stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Harbour is, in one paragraph
&lt;/h2&gt;

&lt;p&gt;Harbour is the open-source successor of the Clipper language: a compiler that takes the same&lt;br&gt;
PRG code Clipper took, compiles it to C, and produces &lt;strong&gt;native executables for modern&lt;br&gt;
platforms&lt;/strong&gt; — 32- and 64-bit Windows, Linux, and more. It reads and writes the same DBF&lt;br&gt;
tables and NTX/CDX indexes through replaceable database drivers, so a ported application&lt;br&gt;
works on the &lt;em&gt;same live data files&lt;/em&gt; as the original EXE. The classic Clipper extension&lt;br&gt;
libraries have Harbour counterparts, the build tool (&lt;code&gt;hbmk2&lt;/code&gt;) turns the old make ritual into&lt;br&gt;
a one-line command, and the language has grown modern conveniences (classes, hash tables,&lt;br&gt;
multithreading) without abandoning dialect compatibility. Commercial forks (xHarbour and&lt;br&gt;
friends) exist alongside it; Harbour is the community mainline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a version label matters to your business
&lt;/h2&gt;

&lt;p&gt;For engineers, "we've run the dev snapshot in production for years" is normal. For everyone&lt;br&gt;
who signs things, it is a problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Procurement and audits.&lt;/strong&gt; "Our platform is a nightly build of an open-source project"
reads very differently from "our platform is Harbour 3.2.0, released 2026". Auditors,
insurers and customers' vendor-security questionnaires want a version, a date, a tag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproducible builds.&lt;/strong&gt; A stable tag means you can pin the exact compiler, archive it,
and rebuild the same binary in five years — the foundation of every serious containment
plan for a legacy system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Succession.&lt;/strong&gt; The developer who eventually inherits your system can download the same
documented release, not "the snapshot from the USB stick Misha left".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signal.&lt;/strong&gt; A project that cuts a stable release after 15 years is telling you it intends
to be here for the next 15. For a language whose commercial vendor died in the 1990s,
that continuity signal is the entire value proposition.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What a Harbour port actually looks like
&lt;/h2&gt;

&lt;p&gt;We have ported a production Clipper-family utility — a decades-old codebase over live&lt;br&gt;
DBF/CDX data — to Harbour, building both 32- and 64-bit native Windows executables and&lt;br&gt;
verifying them against the same server-hosted tables the original used. The honest&lt;br&gt;
experience, in proportions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Most of the code just recompiles.&lt;/strong&gt; Core xBase logic — database access, index
expressions, business rules, SET RELATION chains — carries over untouched. This is what
makes a port a containment project rather than a rewrite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The work concentrates at the edges.&lt;/strong&gt; Screen handling beyond plain @SAY/GET, printer
tricks that assumed an LPT port, inline C or assembler, and third-party .LIB files are
where the effort lives. Most classic libraries have Harbour equivalents; truly exotic
ones need small shims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The build system is a gift.&lt;/strong&gt; &lt;code&gt;hbmk2 app.prg&lt;/code&gt; replaces the RTLINK incantations, and the
same project file builds x86 and x64 from one source tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;64-bit is not vanity.&lt;/strong&gt; It means no 16-bit emulation anywhere in the chain, first-class
behavior on current Windows, and — where the data layer is client/server (for example an
Advantage-backed system) — access to modern 64-bit client stacks, which in our
benchmarking were meaningfully faster than their 32-bit siblings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A port of this kind is measured in weeks, not quarters — a fraction of any migration —&lt;br&gt;
and at the end the application is the &lt;em&gt;same application&lt;/em&gt;: same keystrokes, same data files,&lt;br&gt;
same muscle memory on the plant floor. That sameness is precisely the point. Behavior&lt;br&gt;
preserved by recompilation is behavior you do not have to reverse-engineer later; the&lt;br&gt;
ported binary becomes a scriptable reference implementation for the&lt;br&gt;
&lt;a href="https://dev.to/articles/clipper-xbase-migration-parity-testing/"&gt;parity testing&lt;/a&gt; that a future real&lt;br&gt;
migration will need.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Harbour does not fix
&lt;/h2&gt;

&lt;p&gt;Containment is a strategy, not a destination, and it is worth being blunt about the limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is still xBase.&lt;/strong&gt; The pool of developers who can extend the system does not grow
because the compiler got younger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is still DBF&lt;/strong&gt; (unless you move the data layer to a server backend) — the 2 GB
per-table ceiling, the shared-file locking model and the index-corruption folklore come
along for the ride.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The UI stays the UI.&lt;/strong&gt; Your veterans will love that; your new hires and your customers
will not. Web access, integrations, audit trails — none of that appears by recompiling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The clock keeps ticking.&lt;/strong&gt; A Harbour port removes the &lt;em&gt;platform&lt;/em&gt; emergency (16-bit
executables, dying hardware, unsupported OSes). The &lt;em&gt;organizational&lt;/em&gt; emergency — one
person who understands the system, zero tests, undocumented rules — is untouched.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why we treat Harbour as step one of two: contain now, on your timeline, cheaply —&lt;br&gt;
then decide between keeping the contained system, wrapping it, or a parity-tested&lt;br&gt;
migration, with the pressure off. The full decision framework is in&lt;br&gt;
&lt;a href="https://dev.to/articles/rewrite-vs-convert-vs-wrap/"&gt;rewrite vs convert vs wrap&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is a port right for your system?
&lt;/h2&gt;

&lt;p&gt;Signals that say yes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The application still runs the business, and runs it well — the pain is the platform,
not the logic.&lt;/li&gt;
&lt;li&gt;You are one hardware failure away from an outage (the last DOS-capable PC, a fragile VM,
an emulator nobody dares touch).&lt;/li&gt;
&lt;li&gt;The source code substantially exists. (Partial loss is survivable; total loss changes
the conversation.)&lt;/li&gt;
&lt;li&gt;A full migration is desirable &lt;em&gt;eventually&lt;/em&gt; but not fundable &lt;em&gt;now&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Signals that say skip straight to migration: the business has outgrown the workflows&lt;br&gt;
themselves, the data already needs to live in SQL for integration reasons, or compliance&lt;br&gt;
requirements (audit trails, access control, encryption) can never be met by a shared-file&lt;br&gt;
database no matter what compiles it.&lt;/p&gt;

&lt;p&gt;With 3.2.0 stable, the port option finally comes with a version number your CFO can put in&lt;br&gt;
a risk register. For a 30-year-old system, that is not a small thing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Our &lt;a href="https://dev.to/services/clipper-xbase-migration/"&gt;Clipper/xBase practice&lt;/a&gt; runs exactly this&lt;br&gt;
two-step: Harbour containment measured in weeks, then — if and when you choose — a&lt;br&gt;
parity-tested migration. The fixed-price assessment tells you which step your system&lt;br&gt;
actually needs first.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>harbour</category>
      <category>clipper</category>
      <category>xbase</category>
      <category>containment</category>
    </item>
    <item>
      <title>Migrating a Clipper/xBase app without losing 30 years of business rules</title>
      <dc:creator>Hayim Romanovsky</dc:creator>
      <pubDate>Fri, 06 Nov 2026 07:31:04 +0000</pubDate>
      <link>https://dev.to/hayim_romanovsky_2b964e3c/migrating-a-clipperxbase-app-without-losing-30-years-of-business-rules-2ah7</link>
      <guid>https://dev.to/hayim_romanovsky_2b964e3c/migrating-a-clipperxbase-app-without-losing-30-years-of-business-rules-2ah7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://migrationparity.com/articles/clipper-xbase-migration-parity-testing/" rel="noopener noreferrer"&gt;migrationparity.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every failed xBase migration I have seen died the same death. Not from bad technology choices —&lt;br&gt;
from a quiet assumption: &lt;em&gt;"we know what the old system does."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nobody knows what a 30-year-old Clipper application does. Not the company that owns it, not the&lt;br&gt;
operators who use it eight hours a day, and not the developer who wrote it, even when he is&lt;br&gt;
still around. The application &lt;strong&gt;is&lt;/strong&gt; the specification. The PRG files are where the truth&lt;br&gt;
lives, and a third of that truth was never intended: rounding artifacts that customers' invoices&lt;br&gt;
have depended on since 1996, an index collation quirk that determines which duplicate wins, a&lt;br&gt;
date-window trick from the Y2K panic that still silently reclassifies years.&lt;/p&gt;

&lt;p&gt;If you rewrite from a requirements document, you are rewriting from fiction. Here is the&lt;br&gt;
methodology that works instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0: get the old system somewhere you can run it on demand
&lt;/h2&gt;

&lt;p&gt;You cannot test against a system you are afraid to touch. For DOS-era Clipper this often means&lt;br&gt;
a containment step first: port the code to &lt;a href="https://harbour.github.io/" rel="noopener noreferrer"&gt;Harbour&lt;/a&gt; — the&lt;br&gt;
open-source Clipper-compatible compiler, which shipped a fresh stable release in July 2026 —&lt;br&gt;
so the same PRG code runs as a native Windows executable against the same DBF/NTX/CDX files.&lt;br&gt;
We have done this with production utilities: most code recompiles; the work concentrates in&lt;br&gt;
screen handling, printing and the occasional inline C or assembler.&lt;/p&gt;

&lt;p&gt;The port pays for itself twice. It ends the 16-bit hardware emergency immediately, and it turns&lt;br&gt;
the legacy system into something you can drive from scripts — which is everything for what&lt;br&gt;
comes next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: build the parity harness before the new system
&lt;/h2&gt;

&lt;p&gt;A parity harness is three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Frozen inputs.&lt;/strong&gt; A copy of production data at a known point, plus a library of scripted
transactions: post an order, receive goods, close a month. In xBase systems the "API" is
often the database itself, which is convenient — writing DBF rows &lt;em&gt;is&lt;/em&gt; driving the app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Captured outputs.&lt;/strong&gt; Everything the business actually consumes: report files, printed
invoices redirected to text, month-end totals, the resulting state of every table. Text-mode
legacy output is a gift here — it diffs perfectly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A differ with a memory.&lt;/strong&gt; Something that runs both systems on the same inputs and produces
a human-readable list of differences — and remembers which differences were reviewed and
accepted, so the signal never drowns in known noise.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The critical rule: &lt;strong&gt;capture behavior, not intentions.&lt;/strong&gt; If the old system computes 12% of 1850&lt;br&gt;
as 221.99 because of how Clipper's numeric type rounds through an intermediate, then 221.99 is&lt;br&gt;
the correct answer. Your customers have 30 years of paper that says so. You can fix the bug&lt;br&gt;
later — &lt;em&gt;deliberately, with sign-off&lt;/em&gt; — but on migration day, faithful means identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: mine the places where rules hide
&lt;/h2&gt;

&lt;p&gt;Business logic in xBase systems does not live only in the obvious calculation routines. The&lt;br&gt;
parity harness must cover the habitats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Index expressions.&lt;/strong&gt; &lt;code&gt;UPPER(SUBSTR(CUSTID,1,3)) + DTOS(DUEDATE)&lt;/code&gt; is not plumbing — it is
the rule for how work is sequenced. Conditional indexes (&lt;code&gt;FOR !PAID&lt;/code&gt;) are business filters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collation.&lt;/strong&gt; Index order under a DOS codepage differs from ANSI order for every character
beyond ASCII. Reports "sorted by name" can legitimately differ between old and new — decide
which truth wins &lt;em&gt;before&lt;/em&gt; users find out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deleted records.&lt;/strong&gt; &lt;code&gt;SET DELETED OFF&lt;/code&gt; code paths see flagged-deleted rows. Some reports in
the wild depend on that accidentally, and have for decades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty vs zero.&lt;/strong&gt; xBase distinguishes an empty numeric field from 0 in ways that SQL NULL
maps onto imperfectly. Aggregates over "empty" columns are a classic parity breaker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Date arithmetic.&lt;/strong&gt; Two-digit-year windows, &lt;code&gt;DATE()+30&lt;/code&gt; month-end assumptions, 1900/2000
pivots patched during Y2K by someone in a hurry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The codepage layer.&lt;/strong&gt; If the data is cp862 Hebrew or cp866 Cyrillic (usually with &lt;em&gt;no&lt;/em&gt;
codepage byte set in the DBF header — DOS apps rarely recorded one), then every comparison,
sort and print step embeds an encoding assumption. Get it wrong and parity fails everywhere
at once, uninformatively.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The assessment phase is largely about finding these habitats in &lt;em&gt;your&lt;/em&gt; codebase and making sure&lt;br&gt;
the harness exercises them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: migrate in slices, gate each slice on the diff
&lt;/h2&gt;

&lt;p&gt;With the harness in place, the migration itself becomes almost boring — which is the goal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Move the data to SQL with its own verification (row counts, per-column checksums, codepage
round-trips). Keep the pipeline repeatable; run it nightly.&lt;/li&gt;
&lt;li&gt;Rebuild one module at a time on the new stack, starting with read-only reports.&lt;/li&gt;
&lt;li&gt;Run both systems on the frozen inputs. Diff. Fix. Repeat until the diff is empty or every
remaining line has a written, signed-off explanation.&lt;/li&gt;
&lt;li&gt;Cut that module over. The legacy system remains the system of record for everything else.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;"Done" stops being a feeling and becomes a number: &lt;em&gt;zero unexplained differences across N&lt;br&gt;
scenarios covering M% of last year's transaction volume.&lt;/em&gt; Management can see the number move&lt;br&gt;
weekly. When someone asks "is the new system ready?", the answer is a report, not a promise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this costs — and what it saves
&lt;/h2&gt;

&lt;p&gt;Building the harness is typically 15–25% of the migration budget, spent before any new-system&lt;br&gt;
code exists. Teams under time pressure are tempted to skip it. What they buy instead is the&lt;br&gt;
expensive kind of testing: production users discovering differences one angry phone call at a&lt;br&gt;
time, for a year.&lt;/p&gt;

&lt;p&gt;The harness also outlives the migration. It becomes the regression suite for the new system —&lt;br&gt;
the first automated tests this application has had in its entire multi-decade life.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The old system is the only complete specification of itself. Treat it as the oracle.&lt;/li&gt;
&lt;li&gt;Preserve bugs first, fix them second — separately, visibly, with sign-off.&lt;/li&gt;
&lt;li&gt;Contain the platform emergency (Harbour) so you can migrate calmly instead of heroically.&lt;/li&gt;
&lt;li&gt;Let a diff decide when you are done. Opinions, including yours and mine, do not count.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This is the methodology behind our &lt;a href="https://dev.to/services/clipper-xbase-migration/"&gt;Clipper/xBase migration service&lt;/a&gt;&lt;br&gt;
and, with different tooling, our &lt;a href="https://dev.to/services/foxpro-to-dotnet/"&gt;FoxPro&lt;/a&gt; work. If you want the&lt;br&gt;
short version applied to your system: that is what the &lt;a href="https://dev.to/contact/"&gt;assessment&lt;/a&gt; is for.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>clipper</category>
      <category>xbase</category>
      <category>harbour</category>
      <category>methodology</category>
    </item>
    <item>
      <title>SAP Advantage Database Server after EOL: your realistic options in 2026</title>
      <dc:creator>Hayim Romanovsky</dc:creator>
      <pubDate>Fri, 06 Nov 2026 07:31:01 +0000</pubDate>
      <link>https://dev.to/hayim_romanovsky_2b964e3c/sap-advantage-database-server-after-eol-your-realistic-options-in-2026-3pck</link>
      <guid>https://dev.to/hayim_romanovsky_2b964e3c/sap-advantage-database-server-after-eol-your-realistic-options-in-2026-3pck</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://migrationparity.com/articles/advantage-database-after-eol/" rel="noopener noreferrer"&gt;migrationparity.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Advantage Database Server was the best xBase-family product most people never heard of: a real&lt;br&gt;
client/server engine over DBF and ADT tables, with transactions, server-side indexes and none&lt;br&gt;
of the file-share corruption folklore that haunts Clipper and FoxPro deployments. Vertical&lt;br&gt;
software vendors loved it — point of sale, logistics, medical, manufacturing — which is why&lt;br&gt;
thousands of businesses run it today &lt;em&gt;without knowing it&lt;/em&gt;, under an application bought long ago&lt;br&gt;
from a vendor that may no longer exist.&lt;/p&gt;

&lt;p&gt;Then SAP, which inherited Advantage through Sybase, walked away: sales ended at the close of&lt;br&gt;
2019, maintenance wound down after, and version 12 is the end of the line. No successor, no&lt;br&gt;
migration tool, no plan. If ADS is under your application, you inherited an orphan.&lt;/p&gt;

&lt;p&gt;Here are the four realistic paths, in increasing order of effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: freeze it — properly
&lt;/h2&gt;

&lt;p&gt;Running unsupported software is a risk posture, not automatically a mistake. ADS 11/12 binaries&lt;br&gt;
are stable, and a well-contained installation can serve for years. "Well-contained" is the load-&lt;br&gt;
bearing phrase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolate and pin.&lt;/strong&gt; A dedicated VM, an OS version you control, snapshots before any patching.
Do not let the ADS host ride the general update train.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard the installers and licenses.&lt;/strong&gt; The downloads are gone from official channels. The
setup EXEs and license keys you have are irreplaceable artifacts — vault them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Script verified backups&lt;/strong&gt; and actually drill restores. ADS supports online backup; use it,
and prove quarterly that the restore produces a working server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mind the 32-bit client rot.&lt;/strong&gt; Most deployed ADS clients are 32-bit ODBC/OLE DB stacks.
Each Windows release tolerates them a little less; keep a documented, tested client install
procedure while it still works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One arbiter per table, always.&lt;/strong&gt; ADS owns locking and index maintenance for its tables. The
classic self-inflicted outage is a "quick export" or local xBase tool opening the same
DBF/CDX files directly while the server is live — that is how indexes corrupt. All access
goes through the server, no exceptions, ever.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Freezing buys time. It does not buy a future — the client-driver problem alone guarantees this&lt;br&gt;
posture has an expiry date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: replace the server, keep the clients
&lt;/h2&gt;

&lt;p&gt;Because the Advantage wire protocol is stable and well understood, open-source reimplementations&lt;br&gt;
of the server side exist — the OpenADS project being the visible one. The proposition: your&lt;br&gt;
untouched client applications keep connecting exactly as before, while the server under them&lt;br&gt;
becomes maintained, license-free software that can even move the storage onto modern backends.&lt;/p&gt;

&lt;p&gt;We benchmarked this path against a real SAP ADS server on production-shaped workloads before&lt;br&gt;
recommending it to anyone, and the honest findings are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is not a speed upgrade.&lt;/strong&gt; Mature ADS is fast; the replacement is comparable, not
dramatically better. If raw throughput is your pain, this is not your fix.&lt;/li&gt;
&lt;li&gt;(A side note from the same benchmarks: simply moving from the 32-bit &lt;code&gt;ace32&lt;/code&gt; client stack to
64-bit &lt;code&gt;ace64&lt;/code&gt; gave us roughly 1.4–1.7× on client-side throughput — if you are staying on
ADS a while and your app can load the 64-bit client, that is free performance.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The value is longevity, not latency.&lt;/strong&gt; Maintained code, no license cliff, multi-backend
storage options, and an exit from the abandoned-binary problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature coverage is the gating question.&lt;/strong&gt; Which ADS features does your application
actually use — ADT-specific types, stored procedures, replication, DD security? Compatibility
must be tested against &lt;em&gt;a copy of your data with your client binaries&lt;/em&gt;, not assumed from a
feature matrix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This option shines when the client application cannot change (vendor gone, source lost) but the&lt;br&gt;
business needs the platform to stop being end-of-life.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: migrate the data out from under it
&lt;/h2&gt;

&lt;p&gt;ADS data is ultimately DBF (or ADT, which is documented enough to extract). That means the full&lt;br&gt;
&lt;a href="https://dev.to/services/dbase-dbf-migration/"&gt;DBF migration discipline&lt;/a&gt; applies: per-column codepages, memo&lt;br&gt;
files, deleted-row policy, index expressions re-implemented as SQL constraints — plus the&lt;br&gt;
ADS-specific layer of data-dictionary rules, triggers and stored procedures that live on the&lt;br&gt;
server.&lt;/p&gt;

&lt;p&gt;The destination is a mainstream engine (SQL Server, PostgreSQL) and a rebuilt or repointed&lt;br&gt;
client. This is the permanent fix, and the only option that improves your position every year&lt;br&gt;
after it ships. It is also the only option that requires touching the application — which is&lt;br&gt;
why it pairs naturally with a planned modernization rather than a panic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 4: retire the application
&lt;/h2&gt;

&lt;p&gt;Said rarely by consultants, so let me say it: some ADS-backed applications no longer earn their&lt;br&gt;
keep. If the vertical app overlaps 80% with your current ERP and the remaining 20% is habit,&lt;br&gt;
the cheapest migration is an export, an archive, and a decommissioning party. The assessment&lt;br&gt;
should always price this path too — if only to make the other numbers honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your situation&lt;/th&gt;
&lt;th&gt;Sane path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App works, vendor gone, horizon under ~3 years&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Freeze properly&lt;/strong&gt; (option 1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App must live longer, clients cannot change&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Server replacement&lt;/strong&gt; (option 2), tested on your data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App is yours to change, or modernization is due anyway&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Migrate the data out&lt;/strong&gt; (option 3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App is mostly habit&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Retire&lt;/strong&gt; (option 4)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of these four paths require no changes to your application at all — which surprises most&lt;br&gt;
owners, who assumed EOL meant an immediate, total rewrite. It does not. It means you now need&lt;br&gt;
a deliberate strategy instead of a support contract.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We run this exact decision process, with your data and your client binaries, as a fixed-price&lt;br&gt;
&lt;a href="https://dev.to/services/advantage-database-migration/"&gt;assessment&lt;/a&gt;. The output is the table above with your&lt;br&gt;
numbers in it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>advantagedatabase</category>
      <category>ads</category>
      <category>eol</category>
      <category>dbf</category>
    </item>
  </channel>
</rss>
