草庐IT

java - 整洁建筑设计模式

coder 2024-03-12 原文



https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

我对此模式有疑问。数据库位于外层,但实际上如何工作?例如,如果我有一个仅管理此实体的微服务:

person{
  id,
  name,
  age
}


用例之一是管理人员。 Manage Persons正在保存/检索/.. Persons(=> CRUD操作),但是要做到这一点,Usecase需要与数据库对话。但这将违反依赖性规则

The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards.


  • 这甚至是有效的用例吗?
  • 如果数据库位于外层,如何访问数据库? (依赖项转换?)

  • 如果我收到GET /person/{id}请求,我的微服务应该按这样处理吗?



    但是使用依赖倒置将违反

    Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in an inner circle. That includes, functions, classes. variables, or any other named software entity.



    Crossing boundaries. At the lower right of the diagram is an example of how we cross the circle boundaries. It shows the Controllers and Presenters communicating with the Use Cases in the next layer. Note the flow of control. It begins in the controller, moves through the use case, and then winds up executing in the presenter. Note also the source code dependencies. Each one of them points inwards towards the use cases.

    We usually resolve this apparent contradiction by using the Dependency Inversion Principle. In a language like Java, for example, we would arrange interfaces and inheritance relationships such that the source code dependencies oppose the flow of control at just the right points across the boundary.

    For example, consider that the use case needs to call the presenter. However, this call must not be direct because that would violate The Dependency Rule: No name in an outer circle can be mentioned by an inner circle. So we have the use case call an interface (Shown here as Use Case Output Port) in the inner circle, and have the presenter in the outer circle implement it.

    The same technique is used to cross all the boundaries in the architectures. We take advantage of dynamic polymorphism to create source code dependencies that oppose the flow of control so that we can conform to The Dependency Rule no matter what direction the flow of control is going in.



    用例层是否应该声明一个存储库接口(interface),该接口(interface)将由数据库包实现(框架和驱动程序层)



    如果服务器收到一个GET /persons/1请求,则PersonRest将创建一个PersonRepository并将此存储库+ ID传递给ManagePerson::getPerson函数,getPerson不知道PersonRepository但知道它实现的接口(interface),因此它不违反任何规则吗?
    ManagePerson::getPerson将使用该存储库查找该实体,并将Person实体返回给PersonRest::get,这会将一个Json Objekt返回给客户端,对吗?

    遗憾的是英语不是我的母语,所以我希望你们能让我知道我是否理解正确的模式,并可能回答我的一些问题。

    提前输入

    最佳答案

    The Database is at outter Layer but how would that work in reality?


    您可以在用例层中创建独立于技术的接口(interface),并在网关层中实现该接口(interface)。我猜这就是为什么该层称为接口(interface)适配器的原因,因为您需要调整在内部层中定义的接口(interface)。例如。
    public interface OrderRepository {
        public List<Order> findByCustomer(Customer customer);
    }
    
    实现在网关层
    public class HibernateOrderRepository implements OrderRepository {
          ...
    }
    
    在运行时,您将实现实例传递给用例的构造函数。由于用例仅依赖于接口(interface)(在上例中为OrderRepository),因此您对网关的实现没有源代码的依赖。
    您可以通过扫描导入语句来查看。

    And one of the use cases would be manage Persons. Manage Persons is saving / retrieving / .. Persons (=> CRUD operations), but to do this the Usecase needs to talk to a database. But that would be a violation of the Dependency rule


    不,这不会违反依赖关系规则,因为用例定义了所需的接口(interface)。数据库只是实现它。
    如果使用maven管理应用程序依赖性,则会看到db jar模块取决于用例,反之亦然。但是将这些用例接口(interface)提取到自己的模块中甚至更好。
    然后模块依赖关系看起来像这样
    +-----+      +---------------+     +-----------+
    |  db | -->  | use-cases-api | <-- | use cases |
    +-----+      +---------------+     +-----------+
    
    那是依赖的倒置,否则看起来像这样
    +-----+      +-----------+
    |  db | <--  | use cases |
    +-----+      +-----------+
    

    If i get a GET /person/{id} Request should my Microservices process it like this?


    是的,那是违法的,因为Web层访问db层。更好的方法是Web层访问 Controller 层, Controller 层访问用例层,依此类推。
    为了保持依赖关系反转,您必须使用上面显示的接口(interface)将各层解耦。
    因此,如果要将数据传递给内层,则必须在内层引入一个接口(interface),该接口(interface)定义了获取所需数据并在外层实现该数据的方法。
    在 Controller 层中,您将指定一个这样的接口(interface)
    public interface ControllerParams {
        public Long getPersonId();
    }
    
    在网络层中,您可以这样实现服务
    @Path("/person")
    public PersonRestService {
    
        // Maybe injected using @Autowired if you are using spring
        private SomeController someController;
    
        @Get
        @Path("{id}")
        public void getPerson(PathParam("id") String id){
           try {
               Long personId = Long.valueOf(id);
    
               someController.someMethod(new ControllerParams(){
                    public Long getPersonId(){
                        return personId;
                    }
               });
           } catch (NumberFormatException e) {
               // handle it
           }
        }
    }
    
    乍一看,它似乎是样板代码。但是请记住,您可以让其余框架将请求反序列化为Java对象。并且该对象可能改为实现ControllerParams
    因此,如果您遵循依赖关系反转规则和干净的体系结构,您将永远不会在内层中看到外层类的import语句。
    干净架构的目的是使主要业务类别不依赖于任何技术或环境。由于依存关系从外层指向内层,因此外层发生变化的唯一原因是由于内层发生了变化。或者,如果您交换外层的实现技术。例如。休息-> SOAP
    那么我们为什么要这样做呢?
    罗伯特·C·马丁(Robert C. Martin)在第5章“面向对象的编程”中讲述了这一点。在依赖倒置一节的最后,他说:

    With this approach, software architects working in systems written in OO languages have absolute control over the direction of all source code dependencies in the system. Thay are not constrained to align those dependencies with the flow of control. No matter which module does the calling and which module is called, the software architect can point the source code dependency in either direction.


    That is power!


    我猜开发人员经常对控制流和源代码依赖性感到困惑。控制流通常保持不变,但是源代码依赖关系相反。这使我们有机会创建插件体系结构。每个接口(interface)都是插入点。因此可以互换,例如由于技术或测试原因。
    编辑

    gateway layer = interface OrderRepository => shouldnt the OrderRepository-Interface be inside of UseCases because i need to use the crud operations on that level?


    是的,应该在用例层中定义OrderRepository接口(interface)。还应考虑应用接口(interface)隔离原理并定义MyCuseCaseRepository接口(interface),而不是每个用例都使用的OrderRepository
    之所以这样做,是为了防止用例通过通用接口(interface)耦合,并遵守单一责任原则。因为专用于一个用例的存储库接口(interface)只有一个更改理由。
    编辑
    应用接口(interface)隔离原则并提供专用于该用例的自己的存储库接口(interface)也是一个好主意。这将有助于使用例彼此分离。如果所有用例都使用相同的Repository接口(interface),则此接口(interface)将累积所有用例的所有方法。您可以通过更改此接口(interface)的方法来轻松打破一个用例。
    因此,我通常应用接口(interface)隔离原理并创建以用例命名的存储库接口(interface)。例如。
    public interface PlaceOrderRepository {
         public void storeOrder(Order order);
    }
    
    另一个用例的界面可能如下所示:
    public interface CancelOrderRepository {
         public void removeOrder(Order order);
    }
    

    关于java - 整洁建筑设计模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52352815/

    有关java - 整洁建筑设计模式的更多相关文章

    1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

      我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

    2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    3. ruby - 如何在续集中重新加载表模式? - 2

      鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

    4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

      我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

    5. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    6. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    7. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

      给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

    8. java - 我的模型类或其他类中应该有逻辑吗 - 2

      我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

    9. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

      什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

    10. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

      这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

    随机推荐