{"id":1273,"date":"2021-04-02T16:51:19","date_gmt":"2021-04-02T15:51:19","guid":{"rendered":"https:\/\/coding.moris.org\/?p=1273"},"modified":"2022-05-21T23:16:55","modified_gmt":"2022-05-21T22:16:55","slug":"how-can-you-minimize-the-impact-of-the-persistence-layer-on-your-domain-models","status":"publish","type":"post","link":"https:\/\/priscimon.net\/coding\/2021\/04\/02\/how-can-you-minimize-the-impact-of-the-persistence-layer-on-your-domain-models\/","title":{"rendered":"How can you minimize the impact of the persistence layer on your domain models?"},"content":{"rendered":"\n<p>This post answers the question <a href=\"https:\/\/old.reddit.com\/r\/dotnet\/comments\/mhux12\/design_how_can_you_minimize_the_impact_of_the\/\">&#8216;How can you minimize the impact of the persistence layer on your domain models?&#8217;<\/a> posted on reddit <em>\/r\/dotnet<\/em>, showing how I would implement a solution in the purest OOP [<em>sic<\/em>] possible with C#.<\/p>\n\n\n\n<p>(Note: The factory and persistence classes are simplified for demonstration purposes.)<\/p>\n\n\n\n<p> So here is the code. It is tested with .NET 5 on Windows 10.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>using System;\nusing System.Data;\nusing System.Diagnostics;\n\nnamespace Example.Domain\n{\n    \/\/ Let's pretend this namespace is in a separate assembly so that\n    \/\/ the 'internal' scope modifier applies\n\n    public interface IAccount\n    {\n\tstring Username { get; }\n\n\tbool IsEnabled { get; }\n\n\tvoid Disable();\n    }\n\n    public interface ISavedAccount : IAccount\n    {\n\tint Id { get; }\n\t\n\tvoid Enable();\n    }\n\n    \/\/ Not accessible from outside assembly\n    internal abstract class AccountBase : IAccount\n    {\n\tpublic string Username { get; protected set; }\n\n\tpublic bool IsEnabled { get; protected set; }\n\n\tinternal AccountBase(string username)\n\t{\n\t    Username = username;\n\t}\n\n\tpublic void Disable()\n\t{\n\t    IsEnabled = false;\n\t}\n    }\n\n    \/\/ Not accessible from outside assembly\n    internal class MyAccount : AccountBase\n    {\n\tinternal MyAccount(string username)\n\t    : base(username)\n\t{\n\t}\n    }\n\n    \/\/ Not accessible from outside assembly\n    internal class MySavedAccount : AccountBase, ISavedAccount\n    {\n\tpublic int Id { get; private set; }\n\n\tinternal MySavedAccount(int id, string username, bool isEnabled)\n\t    : base(username)\n\t{\n\t    Id = id;\n\t    if (isEnabled) \n\t    {\n\t\tEnable();\n\t    }\n\t}\n\n\tpublic void Enable()\n\t{\n\t    IsEnabled = true;\n\t}\n    }\n\n    public static class AccountFactory\n    {\n\tpublic static IAccount Create(string username)\n\t{\n\t    return new MyAccount(username);\n\t}\n\n\tpublic static ISavedAccount Create(int id, string username, bool isEnabled)\n\t{\n\t    return new MySavedAccount(id, username, isEnabled);\n\t}\n    }\n}\n\nnamespace Example.Persistence\n{\n    using Example.Domain;\n\n    public static class AccountStorage\n    {\n\tstatic readonly DataTable dt;\n\n\tstatic AccountStorage()\n\t{\n\t    dt = new DataTable();\n\t    dt.Columns.Add(\"id\", typeof(int));\n\t    dt.Columns.Add(\"username\", typeof(string));\n\t    dt.Columns.Add(\"enabled\", typeof(bool));\n\t}\n\n\tpublic static DataRow AddAccountRow(ref IAccount account)\n\t{\n\t    var dr = dt.NewRow();\n\t    dr&#91;\"id\"] = dt.Rows.Count+1;\n\t    dr&#91;\"username\"] = account.Username;\n\t    dr&#91;\"enabled\"] = account.IsEnabled;\n\t    dt.Rows.Add(dr);\n\n\t    \/\/ This variable does not point to a valid account after it has been saved\n\t    account = null;\n\n\t    return dr;\n\t}\n\n\tpublic static DataRow UpdateAccountRow(ISavedAccount account)\n\t{\n\t    var dr = FindAccountRow(account.Id);\n\t    dr&#91;\"username\"] = account.Username;\n\t    dr&#91;\"enabled\"] = account.IsEnabled;\n\t    return dr;\n\t}\n\n\tpublic static DataRow FindAccountRow(int id)\n\t{\n\t    for (var i=0; i&lt;dt.Rows.Count; ++i)\n\t    {\n\t\tDataRow dr = dt.Rows&#91;i];\n\t\tif (((int)dr&#91;\"id\"]) == id)\n\t\t{\n\t\t    return dr;\n\t\t}\n\t    }\n\t    return null;\n\t}\n    }\n}\n\nnamespace Example.Client\n{\n    \/\/ Let's pretend this namespace is in a separate assembly so that it\n    \/\/ doesn't see the 'internal' definitions from the Example.Domain\n    \/\/ namespace\n\n    using Example.Domain;\n    using Example.Persistence;\n\n    class Program\n    {\n        static void Main(string&#91;] args)\n        {\n\t    var account = AccountFactory.Create(\"joebloggs\");\n\n\t    \/\/ FAIL! A new account does not have an id\n\t    \/\/ Debug.Assert(account.Id &gt; 0);\n\n\t    \/\/ FAIL! A new account cannot be enabled\n\t    \/\/ account.Enable();\n\n\t    var newrow = AccountStorage.AddAccountRow(ref account);\n\n            Debug.Assert(account == null, \"The account object is no longer valid\");\n\n\t    Debug.Assert(((int)newrow&#91;\"id\"]) &gt; 0, \"The 'id' value is set\");\n\t    Debug.Assert(\"joebloggs\".Equals(newrow&#91;\"username\"]), \"The 'username' value is set\");\n\t    Debug.Assert(! (bool)newrow&#91;\"enabled\"], \"The 'enabled' value is not set\");\n\n\t    var savedaccount = AccountFactory.Create((int)newrow&#91;\"id\"], (string)newrow&#91;\"username\"], (bool)newrow&#91;\"enabled\"]);\n\t    savedaccount.Enable();\n\t    Debug.Assert(savedaccount.IsEnabled, \"The account is enabled\");\n\n\t    var unused = AccountStorage.UpdateAccountRow(savedaccount);\n\n\t    var loadedrow = AccountStorage.FindAccountRow(savedaccount.Id);\n\t    var loaded = AccountFactory.Create((int)loadedrow&#91;\"id\"], (string)loadedrow&#91;\"username\"], (bool)loadedrow&#91;\"enabled\"]);\n\n\t    Debug.Assert(loaded.Id &gt; 0, \"An id is assigned\");\n\t    Debug.Assert(\"joebloggs\".Equals(loaded.Username), \"The username is set correctly\");\n\t    Debug.Assert(loaded.IsEnabled, \"The account is enabled\");\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p>The line below could be questionable because we pass ownership of object <code>account<\/code> to the persistence layer, but we could also assume that there is a contract that formalises this.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>\t    \/\/ This variable does not point to a valid account after it has been saved\n\t    account = null;<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>This post answers the question &#8216;How can you minimize the impact of the persistence layer on your domain models?&#8217; posted on reddit \/r\/dotnet, showing how I would implement a solution in the purest OOP [sic] possible with C#. (Note: The factory and persistence classes are simplified for demonstration purposes.) So here is the code. It [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1273","post","type-post","status-publish","format-standard","hentry","category-general"],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p3I4g9-kx","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/priscimon.net\/coding\/wp-json\/wp\/v2\/posts\/1273","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/priscimon.net\/coding\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/priscimon.net\/coding\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/priscimon.net\/coding\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/priscimon.net\/coding\/wp-json\/wp\/v2\/comments?post=1273"}],"version-history":[{"count":17,"href":"https:\/\/priscimon.net\/coding\/wp-json\/wp\/v2\/posts\/1273\/revisions"}],"predecessor-version":[{"id":1731,"href":"https:\/\/priscimon.net\/coding\/wp-json\/wp\/v2\/posts\/1273\/revisions\/1731"}],"wp:attachment":[{"href":"https:\/\/priscimon.net\/coding\/wp-json\/wp\/v2\/media?parent=1273"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/priscimon.net\/coding\/wp-json\/wp\/v2\/categories?post=1273"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/priscimon.net\/coding\/wp-json\/wp\/v2\/tags?post=1273"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}