9 ms·
Better Software Design with Clean Architecture
- paulio 9y agoAt what point should basic validation be completed?
- lgclrd 9y agoSuperficial validation on things like form fields still occurs up in the UI layer before a Use Case is invoked. Additional validation is likely to happen in the Use Case as well. It's a ubiquitous thing so just like in any other app it should be happening at a few different points but the stuff validated in the Use Case is going to be related to the business rules it's trying to execute.
- Spearchucker 9y agoMy approach has always been to validate anything that crosses a trust boundary. This might be user->app; client->gateway; or app->database.
- lgclrd 9y agoThis is a great/simple rule to keep in mind.
- mariusmg 9y ago>My approach has always been to validate anything that crosses a trust boundary. That's a good approach unless a validation requires a db call with inner join (for example). This becomes too costly to do it 5 times (for example) just to get something written into db.
- Spearchucker 9y agoIt remains a good approach, even in your scenario - because that perf problem is solved using a temporal cache.
- barrkel 9y agoCaches are an excellent way to introduce path-dependent, time-sensitive bugs.
- Spearchucker 9y agoThe safest, most bug-free code is no code at all.
- mariusmg 9y agoI see it now ..... A interface to define the validation aspect. 3 separate implementations of said interface : 1 for db lookup, 1 for cache lookup and last for unit tests. 1 factory method which return the instance to use depending of context. Another temporal cache that holds the instance of said interface (after all, you can never have enough caches). And all of a sudden, validating some single shit takes +300 lines of code (plus tests). Welcome to enterprise , clean, better designed, greatly arhitected software everyone....
- Spearchucker 9y agoWhy do an interface at all? Is the validation definitely being shared across business functions?
- eecc 9y agoHmm, how do you define "superficial validation"? Form field facultativity (better word?) cross-field consistency, range acceptance, presence of sql escape characters ... they can all be considered superficial, but also core business rule IMHO
- UK-AL 9y ago"presence of sql escape characters" - isn't a business rule. That's technical. Range acceptance - could defiantly be a business rule. Cross-field consistency - Could also be a business rule. I implement these rules both sides. Ideally i don't want an invalid command sent off in the first place. I also don't want badly implemented UI corrupting my business data. This way i can provide instant feedback to the user, and also protect the data in case the UI is badly implemented. The main point of these architectures is that you can use them from many user interfaces.
- paulio 9y ago"I implement these rules both sides" ... and the maintenance overhead?
- UK-AL 9y agoStill easier than having separate domain models. Adhering to dry in all cases, is not always the best solution. As the software community has discovered over the past years. Especially in relation to microservices. In my software there's a bit of a disconnect between the application and interactors. As there is a message queue between them.
- c0nfused 9y agoThis is stuff like there is JavaScript regex that checks for an @ character in an email and in the back end code. There is no reason to not do both.
- deleted 9y ago[deleted]
- 9y ago
- UK-AL 9y agoField level validation(Is this email in a valid format) checking that input is sane is done at the UI level. Business rule validation(E.g student can't be assigned more than 10 courses) is done on the entities themselves. You put it on both sides to get the best of both. Instant feedback to the user, and on entities as last resort protection against a badly implemented interface.
- dozzie 9y ago> Field level validation(Is this email in a valid format) checking that input is sane is done at the UI level. In two places in UI: as close to the user as possible (which improves ergonomics) and at the system's border (which prevents entering invalid data to the system at all). While the former is somewhat optional, the latter is absolutely necessary and cannot be left to client-side JavaScript.
- redy 9y agoThere is no easy answer here. Where and when validation happens requires some careful analysis. I find it helps a lot to think carefully about (1) message validation -- is this a valid message? (2) entity validation -- is this entity in a valid state? and (3) enterprise validation -- is the business system as a whole now in a valid state? These three questions map on to what is often an ACL, domain, and domain services layer. At the end of the day there's going to be corner cases though in which case as a rule of thumb there's something to be said for doing validation at the edges and moving it in closing to the center as needed. In my experience when it's not clear where validation belongs that often means nobody really understands that validation (or that validation is even wrong and is not what the business wants -- it happens!) and so there's something to be said for keeping it out of the core domain.
- Spearchucker 9y agoI've seen this a few times before, and it led me to believe that I was stupid. Comments like "...business rules simply don’t know anything at all about the outside world" left me wondering what components that DO know something about the outside world, actually know about the outside world? Turns out that the answer is a data access component might know where to find a database, and a client app might know where to find a web service. These things were rather obvious to me, so I failed by trying to find loftier answers. Which probably says a lot of about how I think. Also, the concentric circles did nothing for my way of thinking, as messages are linear - they start from a place, and they end in a place. Thus a stack was a lot easier for me to digest than these concentric circles. And finally, the ambiguous language. Why should I care that there are enterprise business rules, and application business rules? They're both rules. Rules go into a component in the middle, or business layer. And thus I found it a lot more useful to think of app design in terms of vertical tiers (hardware abstractions), and layers (software abstractions). Tiers include perimeter, DMZ and corpnet, and layers include (but not limited to) user interface, façade/service gateway, business layer, and data layer. With some cross-cutting concerns like comms, security and operational management (logging, exceptions and so on). I don't think either is better than the other. It was, however, the first time I really understood that two people can be very knowledgeable about the same thing, and yet speak using a completely different vocabulary.
- coldtea 9y ago>Also, the concentric circles did nothing for my way of thinking, as messages are linear - they start from a place, and they end in a place. Thus a stack was a lot easier for me to digest than these concentric circles. Messages might be linear but scopes are encompassing inner scopes. Encapsulation is not usually depicted as a stack. >And finally, the ambiguous language. Why should I care that there are enterprise business rules, and application business rules? They're both rules. Obviously because different scopes apply to the former than to the latter. TFA even clarifies that: "The key is that they ("enterprise business rules") contain rules that are not application specific - so basically any global or shareable logic that could be reused in other applications should be encapsulated in an entity". In general, the similarities ("they're both rules") between two things don't say much (if anything at all) without considering the differences. Shiitake and Amanita Muscaria are "just mushrooms", but one can kill you.
- throwaway13337 9y agoSurprising this has been written in 2017. It looks like java written a decade ago. This can be a kind of organizational solution much like microservices to separate concerns. This is important when collaborating with a large amount of people. Clear boundaries and all that. You pay for those boundaries with a convoluted mess of classes that describe the design pattern rather than the business logic. You end up with AbstractRequestInterfaceFactory type stuff. Trade offs. Decide when they're worth it.
- Spearchucker 9y ago"You pay for those boundaries with a convoluted mess of classes that describe the design pattern rather than the business logic. You end up with AbstractRequestInterfaceFactory type stuff." I disagree. If a design (call it an architecture) is clean and coherent, it's obvious. Because obviously a client must send the order to a server, which is protected by a gateway. And of course that gatway validates the client request, and the data coming in. And of course the server must have business rules to validate and process the order. And clearly the business components must call a data access component to persist the orders to a database. Simple, no?
- chii 9y ago> Simple, no? what happens when the business rule clashes with the database's constraint implementation, because different people either misinterpreted, or due to incompetence or miscommunication? I think splitting up sounds great in theory, but in practise, has lots of pitfalls. That's not to say it's not a good idea, but one musn't look at it with rose-tinted glasses.
- Spearchucker 9y agoWell you'd test, which would catch that scenario. Assuming you're not testing and discover it after releasing to production you'd just use your change process. Either way this is just part of building a system. Refector, re-test and release (or re-release). It's hardly going to be the only bug you find.
- 9y ago
- grierson 9y agoI been using a very similar pattern [Port and Adapters]http://blog.ploeh.dk/2016/03/18/functional-architecture-is-ports-and-adapters/ http://blog.ploeh.dk/2016/03/18/functional-architecture-is-p... since moving to F#, paired with [Railway Oriented Programming]https://fsharpforfunandprofit.com/rop/ https://fsharpforfunandprofit.com/rop/ for the 'Adaptper' level and using DDD for modelling the 'Entities' & 'Use Case' layers' and it been working well for me so far. Scott Wlaschin has recently released an [ebook]https://pragprog.com/book/swdddf/domain-modeling-made-functional https://pragprog.com/book/swdddf/domain-modeling-made-functi... on the topic which goes into more detail.
- BoiledCabbage 9y agoThese are all architecture ideas I've been interested in, I'll have to check out the book. Overall architecture in development is still early on in maturity.
- edejong 9y agoIt's interesting to note that a layered dependency architecture is nothing new. As a matter of fact, it was one of the fundamental design decisions of the 1968 THE Operating System [1], on which Edsger Dijkstra had been programming and designing extensively. [1] https://en.wikipedia.org/wiki/THE_multiprogramming_system https://en.wikipedia.org/wiki/THE_multiprogramming_system
- barrkel 9y agoThis design puts entities in the middle - everything else pivots around entities. Additionally, it uses the OO approach of implementing logic as methods on the entities. After a couple of decades of experience, I've come to the conclusion that this isn't right. Most business rules involve processes, which are inherently procedural, or, from another perspective, functional - functions of the whole state of the system to a new state of the system. Most processes don't logically belong on an object, and when you stick them to one end, you create lots of problems for yourself. Just take that example Student entity class; what stops you from writing: student.RegisteredCourses.Add(course); Moreover, when you have a relation between two entities, it's not uncommon for the relation to be modelled at both ends; that is, a student has a list of courses, and a course has a list of students. Do you implement the same validation at both ends? Make one end (choose one) read-only? Do edits done on one end automatically turn up on the other end? How do you protect the visibility of methods that keep either end in sync, without exposing invariant-violating APIs to other code? Invariants and validations for fields are trivial with an OO approach; for entities, they're reasonably easy, but sometimes you need partially invalid versions while editing or constructing an entity; but once you bring in multiple entities, and relations, everything starts getting hairy pretty quickly. Assertions and validations that would be trivial to write [1] in a relational language like SQL aren't possible in an object-oriented language without a lot of system-building. So I've come around to the idea that the database is a better thing to put at the centre; that encapsulation and hiding of the main fact store is harmful to the architecture of a system, especially in a heterogeneous environment where your entities are represented in different languages, all backed by the same fact store. [1] Trivial to write, but not necessarily cheap to evaluate. I'm not advocating writing all global validations in SQL.
- UK-AL 9y agoThis is solved by DDD and aggregates. Here's how i would do it. Course - Aggregate Root Student - Aggregate Root RegisteredCourses[RegisteredCourse[]) - Sub Entity Collection - with id reference to course. With metadata like date time when the registration occurred. RegisterForCouse Method There should be no mutable public properties, internal methods should be private. Everything should go through an aggregate root method.
- TekMol 9y agoWhich language is he using? Is that C#? It looks like this only deals with in-memory data structures. How does stuff get read from / written to the DB?
- salex89 9y agoActually, very similar, since Entity Framework is usually used. Sources implement the IQueriable interface which lets you write queries with method chaining or LINQ. To be honest, it kinda looks better than it is. In reality I find it produces sub-optimum performance and a lot of things can not be done without spilling query logic to the app. Not to mention a lot of linq/method chaining queries cannot be translated to SQL (by the database provider).
- UK-AL 9y agoYou can persist this model using anything you want. You would have some kind adapter/library that handles the persistence. It would take these models, and covert them into your datastore.
- TekMol 9y agoHow would that work in practice? Would this line stay the same: RegisteredCourses.Add(course); And magically trigger an insert in the database?
- UK-AL 9y agoThe repository pattern in the original example would allow you serailise your objects to whatever datastore you want. His object design isn't that great for being persistence ignorant though. I would instead do something like this. Course - Aggregate Root Student - Aggregate Root RegisteredCourse[] - RegisteredCourses - An array objects with date time of registration, and id reference to course. The method for adding a course would simply create a new RegisteredCourse and place it in the registered courses array. _studentRepository.save(student) would be a simple matter converting this in memory representation into sql. I'm not big fan of using lazy loading, and direct references to other aggregates inside of other aggregates. This makes mapping these models without ORM difficult.
- 1_player 9y agoI was studying this topic yesterday, the same idea with another name, functional core and imperative shell. Came across this collection of articles and talks, for anyone interested: https://gist.github.com/kbilsted/abdc017858cad68c3e7926b03646554e https://gist.github.com/kbilsted/abdc017858cad68c3e7926b0364...
- andreasgonewild 9y agoYou know someone is certified when you have to scroll code sideways because the names don't fit the screen. I thought we already agreed that process is the opposite of progress? I mean come on, RequestCourseRegistrationInteractor, who are you trying to impress here? These processes were designed to turn humans into machines; to decrease the dependence on creativity and skill at the cost of additional effort and complexity; to enable large groups of unmotivated developers to deliver mediocre software reliably; which makes them sub-optimal for any other use case.
- penetrarthur 9y agoWho have you agreed with? Software has to be predictable and easy to maintain. It is great if you can predict what the class does based on its name. It is also great if you know what you have to search for based on the name of pattern. Software is there not to express the creativity of a given programmer, but rather to meet the requirement of the technical tasks.
- andreasgonewild 9y agoWe are all here to express our creativity, that's priority #1 and the only reason we ever went anywhere but in circles. I don't mind descriptive names; these names are not descriptive, these names are part of the process. This is how you really do it: 1) start from the problem you are trying to solve, 2) solve the actual problem in the easiest way possible to get experience, 3) improve the solution until it says exactly what you mean. Bottom up, not top down; skills and creativity, not rigid rules; that's how you build great software.
- penetrarthur 9y agoMost of the programming problems have been solved before. Some of the solutions turned out to be solid programming patterns. Before you write a single line of code, you check if there is a "standard" way of solving the given problem. That way the code is more maintainable and more people will be able to work with your code.
- saltedmd5 9y agoOh look, more kludgy over-designed enterprise gumf. When are people going to learn that if you just start with the entry point with granular components, letting each define the interface for its dependencies, you get a much nicer, looser, more flexible structure than these enterprise "patterns" that ultimately all just turn into a big ball of mud? Stop pretending you can design codebases.
- UK-AL 9y agoWhich is essentially what this is.
- andreareina 9y agoMy objection to Uncle Bob is that it seems really heavy on process, with lots of indirection via adapters, abstract base classes, etc. I get that they're useful for taming a certain amount and kind of complexity, but it's not clear to me that it's always going to be apparent at the beginning that it's going to need taming in that specific way. I've found that starting with the concrete cases, I only sometimes have to go up a level of abstraction and indirection. Conversely, I've built the wrong abstraction many times by starting too high. I don't read or write Java so I'm sure I'm missing a lot of context. That's what I'm looking for. Uncle Bob's an eloquent speaker and his talks make a lot of sense, but I have trouble reconciling that with the code samples I see.
- waibelp 9y agoThis plus checking codebase in regular intervals for: * KISS? * Bundled components? * Dependency injection useful? * Duplicated components / functions? * Too much/less abstract classes? Interfaces useful? In my case that looks like: Write code for 6h, review and refactor code 2h. Result is that less code is produced (due to refactoring/removing/etc.) and codebase keeps being simple. On the other hand it's easier to write tests. No need to use complex enterprise patterns. Most of time simple facades and delegators are enough. Consider writing small simple components instead of using heavy patterns with a lot of boilerplate code.
- JustSomeNobody 9y ago
- Quarrelsome 9y agoOh god no. DON'T POLLUTE THE CURRENCY. This is a massive fuck up and it won't scale or be modular. The idea is fine but you need the entities/currency into a smaller core with your DAL and relationship objects above that layer. The only functions your currency should have are accessors or read-only convenience calculations. Other operations should be handled by another parent because let me tell you that that Course collection is going to end up being null or empty A LOT when it "shouldn't" be. You'll want your use cases to return shallow object graphs (e.g. GetCourses => courses.Enrolled => StudentName, StudentId) from the data layer/services and then if you want to look up a Students details you make that another call. The alternative is to run some sort of "scope" object that defines the depth of graph you want when accessing a general api. The thing you're looking to avoid though is returning more information than is necessary. Also who taught this guy to model? Students are in courses in this model the courses are inside students and that's silly.
- kromem 9y agoFor shallow vs deep, what I decided for my code base was that I'd pick the appropriate depth as a general rule for an object and not worry about I/O micromanagement. If it is cheap to pull the additional data and it makes sense to expect that data in using the object, I'd simply embed the representation. If it was expensive or not commonly used, I'd include a reference to the data (usually ID(s) of the data). Then in the repository, I'd just get everything necessary to return a full object as designated. An onion architecture gets funky if you don't know if the object you are working with has all its data, especially because the object itself shouldn't know how to fetch more data about itself. In this particular case, the two objects probably shouldn't have ANY domain connection, and there should just be a method on the course repository to "GetCoursesForStudent" that accepts a student ID. It's perfectly ok to model relationships in the database that don't have parallel relationships in the domain objects. Or, one could create a "SemesterEnrollment" object that contains a student object and a collection of courses. Which would probably make more sense, as that's the object that should be referenced to generate bills, report cards, etc.
- bsaul 9y agoIMHO, the most important thing in architecture are the concerns and goals of the architecture. Not the particular architecture itself. A single architecture can't check all the marks, or you end up with a monster. The job of an architect is to identify the main risks and pain point of a particular system, and adapt the structure of the project to address them best. "Enforcing separation of concerns" is a good one. But so would be "identify the various runtime threads of your system easily", or "minimize code surface responsible for mutation of shared data", or "decouple configuration primitives from the components themselves". Those concerns are more or less important depending on your use case. MMORPG server code or regular desktop app, or mobile webapps sold as templates, all have very very different concerns, so i dont think it would make sense to use a single architecture as a template for every problem.
- dayjah 9y agoI had been quite skeptical of Clean Architecture when I first came across it. I don't find Uncle Bob's post on it particularly insightful; for me it's not vocational enough. Then a few years ago I had to maintain a software stack written by contractors from Pivotal (https://pivotal.io/ https://pivotal.io/) in a Clean Architecture style - it was truly a revelation for me; akin to that "aha" moment of fully grokking homoiconicity in LISPs. Now, up until this point, all code I'd encountered at Twitch heavily reflected the domain of the problem it was solving and the technology in which it was written. That is, if you were looking at a certain piece of architecture you had to really fully understand not only exactly the intent of that code base but also the details of the framework in which it was written. As codebases increased in number and size, it became much harder to scale as an engineer. Jumping from a Rails monolith, to a highly concurrent Golang HTTP CRUD-API, to a highly asynchronous Twisted-Python request routing system (broadly the main three backend techs) had a very large cognitive load. The eng org cleaved along these lines and maintaining velocity in that world was very hard; attempts to introduce new tech or join chunks of the org took on a "religious" tone. So initially coming across this clean architecture stack felt very similar to that. It had a lot of "weird new things" in it, but once I understood that much of it was routing (tagging inbound requests in a manner that a deeper layer could understand the intent of the request and pass it to the correct interactor, which would then work on the appropriate entities) it suddenly became incredibly easy to hop around the code base and update the important aspects of it. I asked the authors who had been contracted to build this system where they got their inspiration and they cited many lunchtime discussions and pair programming sessions influenced heavily by Uncle Bob's clean architecture. I would have really enjoyed seeing more systems built like this because, to the maintenance programmer, it was very clear where things had to go. However only encountering one Clean Architected system didn't really give me a solid idea of how well it would scale across various domains.
- ninjakeyboard 9y agolooks like onion architecture, no?
- kromem 9y agoBasically "Uncle Bob" took the onion architecture, changed it slightly, and took ownership of it. But yeah, regardless of the lame appropriation, a solid concept.
- luord 9y agoThe entire time I read this I kept thinking of the fundamental theorem (and its corollary). Also, considering this an all-solving hammer has the risk, like with everything, of being premature optimization, methinks.
- nradov 9y agoEvery new system starts out fairly clean, at least in certain dimensions. Then the real world intervenes.
- bg4 9y agoAlso relevant to this discussion is 'Domain Driven Design and Onion Architecture in Scala' by Wade Waldron from Scala Days 2016 https://www.youtube.com/watch?v=MnNeDXg3Qao https://www.youtube.com/watch?v=MnNeDXg3Qao
- lotsoflumens 9y agoIf you see an architecture that has a box or a ring with the word "controller" in it - the design is wrong.
- ManuelKiessling 9y agoIt's a shameless plug, but I just have to throw in http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/ http://manuel.kiessling.net/2012/09/28/applying-the-clean-ar...
- arwhatever 9y agoIt's so refreshing to see vibrant discussion of how a domain layer should be implemented, when I have practically been run off from teams for suggesting that a domain layer pattern, any domain layer pattern, should be used. I've seen enormously complex domains implemented as DTOs that are acted duplicately in ORM queries, in PDF rendering, in CSV exporting and then again in CSV importing.
- kromem 9y agoI've been using a very similar architecture on an application for about 2 years now, and it's been incredibly smooth to work with, especially in Go (passively satisfied interfaces and enfirced lack of inheritance), though I do feel like the article overcomplicates the design (also recommend looking up onion architecture). Basically, for me, it boils down to the following: 1. Create domain package(s) with the basic business entities (users, vendors, products, etc) - can't import anything. 2. Create usecases package(s) that acts on these objects and can import anything in the domain package, but nothing else (though can accept repository store interfaces). 3. Create infrastructure packages for taking to databases, caches, etc - can't import from rest of application. 4. Build repository stores to translate between repositories and domain objects (can import domain and usecases, and accepts interfaces for infrastructure). 5. Add controllers/main packages that build repository dependencies with infrastructure config, and pass those interfaces into usecases that do the necessary work and spot back results (imports everything). The end result is a bit topsy turvy to get used to at first, but then it's a dream. Extremely easy to test (very little dependency coupling in each package), and the most complex parts of the application logic are totally isolated from the complex parts of the infrastructure, so you end up with less cognitive load when dealing with either. After my experiences so far, I can't see ever switching back to "top down" architecture instead of "inner out" when given a choice.
- ryanmarsh 9y agoAs a teenager I would sit in the offices of grey haired old men at the software company I had no business working for after dropping out of high school. These men would hand me a photocopy of an OOPSLA paper, or something from a journal. I was told to read it, then come back and discuss. This was my intro to many areas of software architecture. I became familiar with the names of people like Booch, Rumbaugh, Jacobson and others. Over the years I learned various object oriented programming languages and patterns. Each new thing was like discovering a horcrux, at first magical and powerful, but ultimately evil. Later I began to learn functional programming and that's what I try to use most these days but it too has its promises and lies. I have built and helped others reason about many many complex systems and all I can say is this: The only system that is well ordered internally is the one that accreted complexity in increments, and was continually refactored along the way. Best practices be damned. Those systems might not look how you'd design them were you Uncle Bob. They work, can be understood, and can be modified without much consternation. We all can recite examples of masterpiece turned morass. Conversely some of us can recite an example of a frog, a weird complex beast but ultimately very well adapted to its environment and quite resilient. Frogs are not beautiful but great at eating bugs. One fact of complex systems is: humans cannot know the "right" design until AFTER he has arrived at it. If a human can know the design a priori then the problem is NOT COMPLEX and if it is not complex then it is not modern software. This is humbling knowledge for someone who wants to believe there can be a language and pattern of order for all systems. One that can be expressed in anything less precise than code itself. Given a fanciful machine that could assemble subatomic particles in any fashion the user desires none of us could, having never seen one, design a frog.