CINXE.COM
Feature Folders — Kamil Grzybek
<!DOCTYPE html> <html lang="en"> <head> <!-- Google tag (gtag.js) --> <script async src="https://www.googletagmanager.com/gtag/js?id=UA-134625954-1"></script> <script type="application/javascript"> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-134625954-1'); </script> <title>Feature Folders — Kamil Grzybek</title> <meta name="description" content="Kamil Grzybek Personal Site"> <meta charset="utf-8"> <meta name="author" content="Kamil Grzybek"> <!--[if IE]><meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'><![endif]--> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Favicons --> <link rel="shortcut icon" href="/images/favicon.png"> <link rel="stylesheet" href="/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style-responsive.css"> <link rel="stylesheet" href="/css/animate.min.css"> <link rel="stylesheet" href="/css/vertical-rhythm.min.css"> <link rel="stylesheet" href="/css/owl.carousel.css"> <link rel="stylesheet" href="/css/magnific-popup.css"> </head> <body class="appear-animate"> <!-- Page Loader --><div class="page-loader"> <div class="loader">Loading...</div> </div> <!-- End Page Loader --> <!-- Skip to Content --> <a href="#main" class="btn skip-to-content">Skip to Content</a> <div class="page" id="top"> <nav class="main-nav js-stick"> <div class="full-wrapper relative clearfix"> <div class="nav-logo-wrap local-scroll"> <a href="/" class="logo"> KAMILGRZYBEK.COM </a> </div> <div class="mobile-nav" role="button" tabindex="0"> <i class="fa fa-bars"></i> <span class="sr-only">Menu</span> </div> <div class="inner-nav desktop-nav"> <ul class="clearlist scroll-nav local-scroll"> <li class="active"><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/blog" class="mn-has-sub">Blog <i class="fa fa-angle-down"></i></a> <ul class="mn-sub mn-has-multi"> <li class="mn-sub-multi"> <a class="mn-group-title">Categories</a> <ul> <li><a href="/blog/categories/architecture-and-design">Architecture & Design</a></li> <li><a href="/blog/categories/ddd">Domain-Driven Design</a></li> <li><a href="/blog/categories/eda">Event-Driven Architecture</a></li> <li><a href="/blog/categories/dotnet">.NET</a></li> <li><a href="/blog/categories/all">All categories</a></li> <li><a href="/blog/">All posts</a></li> </ul> </li> <li class="mn-sub-multi"> <a class="mn-group-title">Series</a> <ul> <li><a href="/blog/series/automated-tests">Automated Tests</a></li> <li><a href="/blog/series/modular-monolith">Modular Monolith</a></li> </ul> </li> </ul> </li> <li> <a href="#" class="mn-has-sub">OSS <i class="fa fa-angle-down"></i></a> <ul class="mn-sub to-right"> <li> <a href="https://github.com/kgrzybek/modular-monolith-with-ddd">Modular Monolith</a> </li> <li> <a href="https://github.com/kgrzybek/sample-dotnet-core-cqrs-api">.NET Core REST API CQRS</a> </li> <li> <a href="https://github.com/kgrzybek?tab=repositories">See all</a> </li> </ul> </li> <li><a href="/services">Services</a></li> <li><a href="/speaking">Speaking</a></li> <li><a href="/newsletter">Newsletter</a></li> <li><a href="/contact">Contact</a></li> <li><a href="/rss.xml">RSS</a></li> </ul> </div> </div> </nav> <main id="main"> <section class="small-section bg-dark-lighter"> <div class="relative container align-left"> <div class="row"> <div class="col-md-8"> <h1 class="hs-line-11 font-alt mb-20 mb-xs-0">Blog</h1> <div class="hs-line-4 font-alt"> Post: Feature Folders </div> </div> </div> </div> </section><section class="page-section" style="padding-bottom: 10px"> <div class="container relative"> <div class="row"> <!-- Content --> <div class="col-sm-10 offset-sm-1"> <!-- Post --> <div class="blog-item mb-80 mb-xs-40"> <!-- Text --> <div class="blog-item-body"> <h1 class="mt-0 font-alt">Feature Folders</h1> <div class="blog-item-data"> 2018-12-03 <i class="fa fa-folder-open"></i><a href="/blog/categories/architecture-and-design">Architecture & Design </a><span> </span> </div> <div class="blog-media mt-40 mb-40 mb-xs-30"> <ul class="clearlist content-slider"> <li> <img src="/images/blog/posts/feature-folders/folders.jpg" alt=""> </li> </ul> </div> <h2 id="introduction">Introduction</h2> <p>Today I would like to suggest a less-common but in my opinion a much better way to organize our codebase. Meet the <em>Feature Folders</em>.</p> <h2 id="problem">Problem</h2> <p>For ages we have been (at least in the .NET environment) used to thinking about our code structure taking into account the technical aspects. For example <em>MVC</em> application project templates assume the division our objects into separate directories - Controllers, Views, Scripts and so on. We can see the same in many tutorials. If we need add new feature, following this approach we should add all objects in different places.</p> <p>This approach is found not only on the application layer but on others too. I have seen many times in the business logic layer the big folders called Aggregates, Entities, Domain Events with a lot of classes unrelated to each other. But what does it mean “unrelated”?</p> <p>We can specify 2 types of relationships between objects - <strong>technical</strong> and <strong>business</strong>.</p> <p>Technical relationship tells whether two objects have similar meaning from technical perspective. That is, whether they have the same usage. Two controllers are technically related, but controller and application service are not related - their purpose is different.</p> <p>Business relationship tells whether two objects support the fulfillment of the same use case. These can be definitely different objects from a technical point of view - for example: a validator and a command handler.</p> <p>Let’s see how technical folders can look like:</p> <p><img src="/images/blog/posts/feature-folders/FeatureFolders.png" alt="Technical folders"></p> <p>What is the problem with this design? As you can see, we have three modules: Commands, Handlers and Validators. Each of these modules has very <strong>low</strong> <a href="https://en.wikipedia.org/wiki/Cohesion_(computer_science)">cohesion</a> because as Wiki says:</p> <blockquote> <p>cohesion refers to the degree to which the elements inside a module belong together</p> </blockquote> <p>For example the new requirement appears and we need add new attribute which can be editable. We need change all of three objects associated with editing so in this particular design we need change all three modules as well. The same if we would like to move all functionality to a separate service. This is not good. We have to try achieve as high cohesion as possible.</p> <h2 id="solution">Solution</h2> <p>The solution is to stop thinking about the technical aspects of our objects and focus instead on the business relationship. It will provide high cohesion with all its benefits (maintainability, reusability, less complexity and so on).</p> <p>For every feature/use case we should create separate <em>Feature Folder</em> with all related <strong>from business perspective</strong> objects:</p> <p><img src="/images/blog/posts/feature-folders/FeatureFolders2.png" alt="Feature folders"></p> <p>The same rule applies for business logic layer and I think it is even more important here. We should design our domain model per aggregates:</p> <p><img src="/images/blog/posts/feature-folders/FeatureFolders3.png" alt="Feature folders"></p> <p>This is very simple approach but improves our design and especially in projects with many features makes work definitely easier.</p> <p>There is an extra bonus. With this design we create templates for later requirements. For example if we need add implementation for adding a new product, we can copy paste whole feature folder, rename objects and we know exactly what we have to implement. Great!</p> <h2 id="summary">Summary</h2> <p>In this post I showed what <em>Feature Folders</em> are. By good codebase organization we can achieve more better and elegant design. If you still use “technical folders” I encourage you to try this solution - you will not regret it. :)</p> </div> <!-- End Text --> <div class="blog-item-body"> <div id="disqusPageId" style="visibility: hidden">http://www.kamilgrzybek.com/design/feature-folders</div> <h4 class="blog-page-title font-alt">Comments</h4> <div id="disqus_thread"></div> </div> </div> <script> var disqus_config = function () { var disqusPageId = document.getElementById("disqusPageId").innerHTML; this.page.url = disqusPageId; // Replace PAGE_URL with your page's canonical URL variable this.page.identifier = disqusPageId; // Replace PAGE_IDENTIFIER with your page's unique identifier variable }; (function() { // DON'T EDIT BELOW THIS LINE var d = document, s = d.createElement('script'); s.src = 'https://kamil-grzybek.disqus.com/embed.js'; s.setAttribute('data-timestamp', new Date().toString()); (d.head || d.body).appendChild(s); })(); </script> </div> </div> </div> </section><section class="page-section" id="news" style="padding-top: 10px"> <div class="container relative"> <h2 class="section-title font-alt align-left mb-70 mb-sm-40"> Related posts <a href="/blog" class="section-more right">See all blog posts<i class="fa fa-angle-right"></i></a> </h2> <div class="row multi-columns-row"> <div class="col-sm-6 col-md-4 col-lg-4 mb-md-50 wow fadeIn" data-wow-delay="0.1s" data-wow-duration="2s"> <div class="post-prev-img"> <a href="/blog/posts/simple-cqrs-implementation-raw-sql-ddd"><img src="/images/blog/posts/simple-cqrs-implementation-raw-sql-ddd/sa.jpg" alt=""></a> </div> <div class="post-prev-title font-alt"> <a href="/blog/posts/simple-cqrs-implementation-raw-sql-ddd">Simple CQRS implementation with raw SQL and DDD</a> </div> <div class="post-prev-info font-alt"> 4 February 2019 </div> <div class="post-prev-text"> In this post I wanted to show you how you can quickly implement simple REST API application with CQRS using the .NET Core. </div> <div class="post-prev-more"> <a href="/blog/posts/simple-cqrs-implementation-raw-sql-ddd" class="btn btn-mod btn-gray btn-round">Read More <i class="fa fa-angle-right"></i></a> </div> </div> </div> </div> </section> </main> <footer class="page-section bg-gray-lighter footer pb-60"> <div class="container"> <div class="local-scroll mb-30 wow fadeInUp" data-wow-duration="1.2s"> <a href="#top"><img src="/images/android-chrome-192x192.png" width="78" height="36" alt="Company logo"><span class="sr-only">Scroll to the top of the page</span></a> </div> <div class="footer-social-links mb-30 mb-xs-60"> <a href="https://twitter.com/kamgrzybek" title="Twitter" target="_blank"><i class="fa fa-twitter"></i> <span class="sr-only">Twitter profile</span></a> <a href="https://github.com/kgrzybek" title="GitHub" target="_blank"><i class="fa fa-github"></i> <span class="sr-only">GitHub profile</span></a> <a href="https://www.linkedin.com/in/kamilgrzybek/" title="LinkedIn" target="_blank"><i class="fa fa-linkedin"></i> <span class="sr-only">LinkedIn profile</span></a> </div> <div class="footer-text"> <div class="footer-copy font-alt"> © Kamil Grzybek 2023 </div> <div class="footer-made"> Software Engineering & Architecture </div> <div class="footer-made"> <a href="/privacy-policy">Privacy Policy</a> </div> </div> </div> <div class="local-scroll"> <a href="#top" class="link-to-top"><i class="fa fa-caret-up"></i><span class="sr-only">Scroll to top</span></a> </div> </footer> </div> <script type="text/javascript" src="/js/jquery.min.js"></script> <script type="text/javascript" src="/js/jquery.easing.1.3.js"></script> <script type="text/javascript" src="/js/bootstrap.bundle.min.js"></script> <script type="text/javascript" src="/js/SmoothScroll.js"></script> <script type="text/javascript" src="/js/jquery.scrollTo.min.js"></script> <script type="text/javascript" src="/js/jquery.localScroll.min.js"></script> <script type="text/javascript" src="/js/jquery.viewport.mini.js"></script> <script type="text/javascript" src="/js/jquery.sticky.js"></script> <script type="text/javascript" src="/js/jquery.parallax-1.1.3.js"></script> <script type="text/javascript" src="/js/jquery.fitvids.js"></script> <script type="text/javascript" src="/js/owl.carousel.min.js"></script> <script type="text/javascript" src="/js/isotope.pkgd.min.js"></script> <script type="text/javascript" src="/js/imagesloaded.pkgd.min.js"></script> <script type="text/javascript" src="/js/jquery.magnific-popup.min.js"></script> <script type="text/javascript" src="/js/wow.min.js"></script> <script type="text/javascript" src="/js/masonry.pkgd.min.js"></script> <script type="text/javascript" src="/js/morphext.js"></script> <script type="text/javascript" src="/js/jquery.lazyload.min.js"></script> <script type="text/javascript" src="/js/all.js"></script> <script type="text/javascript" src="/js/contact-form.js"></script> <script type="text/javascript" src="/js/jquery.ajaxchimp.min.js"></script> <!--[if lt IE 10]><script type="text/javascript" src="js/placeholder.js"></script><![endif]--> <script id="dsq-count-scr" src="//kamil-grzybek.disqus.com/count.js" async></script> </body> </html>