热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

typescript_清洁架构的TypeScript刺

typescriptbyWarrenBell沃伦贝尔(WarrenBell)清洁架构的TypeScript刺(ATypeScriptStabatCleanArchitecture)

typescript

by Warren Bell

沃伦·贝尔(Warren Bell)

清洁架构的TypeScript刺 (A TypeScript Stab at Clean Architecture)

剥洋葱皮的方法不止一种。 (More than one way to skin an onion.)

清洁建筑 (Clean Architecture)

There are many videos and articles explaining clean architecture. Most of these go over the concepts from a 20,000 foot view. I don’t know about you, but I don’t learn things very well at that elevation. Not a lot of oxygen up there. I learn by jumping in head first and coding. This article and the accompanying code is what I ended up with after such a leap.

有许多视频和文章介绍了干净的体系结构。 其中大多数从20,000英尺的角度审视了这些概念。 我不了解你,但是我在那个高度上学得不好。 那里没有很多氧气。 我通过跳入头脑和编码来学习。 经过如此飞跃,本文和附带的代码才是我最终的目标。

鲍伯的叔叔 (Bob’s Your Uncle)

The term “Clean Architecture” was made popular by Robert Martin (Uncle Bob) and his book “Clean Architecture: A Craftsman’s Guide to Software Structure and Design.” Now I don’t proclaim to be an expert in this field and I haven’t read his book, though I intend to. But I can completely relate to the problems it is trying to solve.

Robert Martin(鲍勃叔叔)和他的书《 Clean Architecture:软件结构和设计的工匠指南》使“ Clean Architecture”一词成为流行。 现在,尽管我打算,但我并没有宣布自己是该领域的专家,也没有读过他的书。 但是我完全可以解决它要解决的问题。

How do you write a software system that is not dependent on anything other than a primary language ? We were promised this in the past with interfaces and other OO principles, but I had never before seen a “clean”, pun intended, explanation on how to do this regarding the whole system. And yes, I am a bit late to this party, being that Uncle Bob started to talk about these concepts in 2012, which is a century ago in software years.

您如何编写不依赖于主要语言之外的任何东西的软件系统? 过去,我们曾通过接口和其他OO原则向我们承诺过这一点,但是我从来没有见过关于如何在整个系统上做到这一点的“干净的”双关语。 是的,我参加这个聚会有点晚了,因为Bob叔叔在2012年(这是一个世纪的软件时代)开始谈论这些概念。

令我困惑的图 (The Diagram That Baffled Me)

Here is the original diagram Uncle Bob and others used in their presentations when explaining Clean Architecture. This simple little diagram became an obsession of mine. I had long ago purged my memory of anything UML related and was struggling with the has-a, and uses-a relationships indicated by the open and close arrow heads. The only way I was going to figure this out was by writing some code.

这是Bob叔叔和其他人在解释Clean Architecture时在演示中使用的原始图。 这个简单的小图成了我的痴迷。 我很早以前就清除了与UML相关的任何内容,并且一直在用打开和关闭箭头指示的has-a和use-a关系进行挣扎。 我要弄清楚这一点的唯一方法是编写一些代码。

认识你的洋葱 (Know Your Onions)

One way to look at Clean Architecture is as an onion with layers. All layers can only depend on a layer that is closer to the center. That is, all dependencies point inward and not outward.

观察“干净架构”的一种方法是将洋葱分层。 所有图层只能依赖于更靠近中心的图层。 也就是说,所有依赖关系都指向内部而不是向外。

这些日子之一,我要组织起来 (One of These Days, I’m Gonna Get Organizized)

In our example, there are 4 modules that correspond with each layer of this onion. Eventually these could be separate npm modules. For readability’s sake, I tried to name things according to Uncle Bob’s original Clean Architecture diagram at the top of this article. In the real world you would probably preface all names with what ever use case they represented.

在我们的示例中,有4个模块与该洋葱的每一层相对应。 最终,这些可以是单独的npm模块。 出于可读性考虑,我尝试根据本文顶部Bob叔叔的原始Clean Architecture图来命名。 在现实世界中,您可能会在所有名称的开头加上它们所代表的用例。

The infrastructure (blue) layer is where all of our outside pluggable systems live. These outside systems such as devices, web, and UIs, shown in the onion diagram, will use our IRequest and IViewModel interfaces to communicate with our Controller and Presenter while the db and external interfaces, shown in the onion diagram, will use the IEntityGateway interface to communicate with our Interactor.

基础架构(蓝色)层是我们所有外部可插拔系统所在的位置。 洋葱图中显示的这些外部系统,例如设备,Web和UI,将使用IRequest和IViewModel接口与Controller和Presenter进行通信,而洋葱图中显示的db和外部接口将使用IEntityGateway接口与我们的交互器进行通信。

Our example will have one entity called a Widget with three properties. It also uses one use case “create widget” which takes a widget from the UI, saves the widget to some sort of storage, and returns back to the UI a newly created widget with an id and a revision number.

我们的示例将具有一个名为Widget的实体,该实体具有三个属性。 它还使用了一个用例“创建窗口小部件”,它从UI中获取窗口小部件,将窗口小部件保存到某种存储中,并将具有ID和修订号的新创建的窗口小部件返回给UI。

更多视觉辅助 (More Visual Aids)

Here is the directory structure. Everything gets wired together in each module’s index.ts file. The entry point is demonstrated in a test located in infrastructure/test/TestEntryPoint.spec.ts .

这是目录结构。 一切都在每个模块的index.ts文件中连接在一起。 入口点在位于Infrastructure / test / TestEntryPoint.spec.ts中的测试中进行了演示。

他去了哪条路? (Which Way Did He Go?)

One of my original hangups was how the outer most layer communicates with the inner layers. I thought all you had to do is call some createWidget() function, for example, on a Controller and you would get a nice shiny new widget returned back to you. Wrong.

我最初的挂断之一是最外层如何与内层进行通信。 我以为您要做的就是在控制器上调用一些createWidget()函数,您会得到一个漂亮的闪亮新小部件,该小部件将返回给您。 错误。

What you want to do is send the widget to be created down to the use case (Interactor) on a certain path and have the use case (Interactor) send the new widget back up to you on a different path. This is similar to a callback function or a Promise. I found a good diagram illustrating this in an article titled “Implementing Clean Architecture — Of controllers and presenters” (link below). In our example I have not implemented a RequestModel or a ResponseModel.

您想要做的是将要创建的小部件发送到特定路径上的用例(Interactor),并让用例(Interactor)在另一条路径上将新的小部件发送回给您。 这类似于回调函数或Promise。 我在题为“实现控制器和演示者的清洁架构”的文章中找到了说明这一点的好图表(下面的链接)。 在我们的示例中,我尚未实现RequestModel或ResponseModel。

进入,跨过和跳出OO方法 (Step Into, Step Over, and Step Out, the OO Way)

So let’s first create a widget with classes and interfaces.

因此,让我们首先创建一个带有类和接口的小部件。

面向对象的步骤1 (OO Step 1)

This is the entry point. This code would be located in the infrastructure (blue) layer. This layer is where your mobile app, web app, API, CLI, etc. lives. Also all of your outside systems like external APIs, frameworks, libraries and databases live here too. Everything is pluggable in this layer and communicates with our system via interfaces we provide.

这是入口点。 此代码将位于基础结构(蓝色)层中。 该层是您的移动应用程序,Web应用程序,API,CLI等所在的位置。 同样,您的所有外部系统(例如外部API,框架,库和数据库)也都位于此处。 一切都可插入此层,并通过我们提供的接口与我们的系统通信。

First you create your ViewModel implementation of the IViewModel interface where a new widget will appear and you can update your UI within the implemented function presentWidget(widget).

首先,您将创建IViewModel接口的ViewModel实现,其中将出现一个新的小部件,并且可以在已实现的函数presentWidget(widget)中更新UI。

You then create a Controller that implements the IRequest interface by passing the EntityGateway and the ViewModel you created above to a constructor. Finally your UI calls createWidget(widget) on the Controller where your new widget begins its journey to the Interactor.

然后,通过将上面创建的EntityGateway和ViewModel传递给构造函数,来创建实现IRequest接口的Controller。 最后,您的UI调用Controller上的createWidget(widget),新的小部件开始进入Interactor的旅程。

什么是EntityGateway? (What’s an EntityGateway?)

An EntityGateway implements the IEntityGateway interface and is where you implement specific code that persists your widget. It lives in the infrastructure (blue) layer. This could be any type of existing or future external API or persistence system such as a database.

EntityGateway实现IEntityGateway接口,在这里您可以实现持久化窗口小部件的特定代码。 它位于基础结构(蓝色)层中。 它可以是任何类型的现有或将来的外部API或持久性系统,例如数据库。

To change out to a different system, you would just simply wire together the new EntityGateway implementation with the IEntityGateway interface. In this example, I use a Promise to simulate some sort of persistence operation.

要更改为其他系统,只需将新的EntityGateway实现与IEntityGateway接口连接在一起。 在此示例中,我使用Promise模拟某种持久性操作。

连线什么? (Wire up What?)

The file infrastructure/src/index.ts in the infrastructure module is where you can wire up your different implementations of your IEntityGateway interface. The “from” path of the import statement points to the correct implementation. In this case it is a persistence system named AnyDB.

基础结构模块中的文件Infrastructure / src / index.ts是连接IEntityGateway接口的不同实现的位置。 import语句的“ from”路径指向正确的实现。 在这种情况下,它是一个名为AnyDB的持久性系统。

Uncle Bob also talks about the use of a Main class where you can do this type of wiring up or do other initializing code. The Main class would also live in the infrastructure module and be pluggable. It would also communicate in the same manner as the other systems in the infrastructure module do. For example, you would initiate this class in your UI’s initializing code and pass it down into your more inner layers to be used via some sort of configuration interface.

Bob叔叔还谈到了Main类的使用,您可以在其中进行这种类型的连接或进行其他初始化代码。 Main类还将存在于基础结构模块中并且是可插入的。 它还将以与基础结构模块中其他系统相同的方式进行通信。 例如,您可以在UI的初始化代码中初始化此类,然后将其传递到更内部的层中,以通过某种配置界面使用。

Our example does not use a Main class and instead is passing the persistence system down into the interactor via the createWidget() function. This is probably not the “pure” way of doing this, but was done to make our example easier to read.

我们的示例不使用Main类,而是通过createWidget()函数将持久性系统向下传递给交互器。 这可能不是做到这一点的“纯粹”方法,但是这样做是为了使我们的示例更易于阅读。

面向对象的步骤2 (OO Step 2)

The Controller is a very busy place. First, the EntityGateway passes through unchanged to our Interactor constructor. Then our ViewModel gets passed to the constructor of our Presenter which in turn also gets passed to our Interactor constructor. This all happens in the createWidget(widget) function of the Controller which was called by our UI in step 1 via the IRequest interface. We will talk about our Presenter in step 4 when the newly created widget travels back up to the UI.

控制器是一个非常繁忙的地方。 首先,EntityGateway不变地传递给我们的Interactor构造函数。 然后,我们的ViewModel被传递给Presenter的构造函数,后者又被传递给Interactor构造函数。 所有这些都发生在Controller的createWidget(widget)函数中,该函数在第1步中通过IRequest接口由我们的UI调用。 当新创建的小部件返回到UI时,我们将在步骤4中讨论Presenter。

面向对象的步骤3 (OO Step 3)

Finally we are at the most inner layer of our journey, the usecase layer where our Interactor lives. Or better known as our home for all of our app’s use case logic. There is one more inner layer, the domain. This is where all of our business entities and business specific logic lives. In this example, we really don’t have any need to go there except to borrow the WidgetType and IEntityGateway interfaces.

最后,我们处于旅程的最内层,即Interactor所在的用例层。 或更广为人知的是我们所有应用程序用例逻辑的家。 还有一层内层,即领域。 这是我们所有业务实体和业务特定逻辑的所在地。 在此示例中,除了借用WidgetType和IEntityGateway接口之外,我们真的不需要去那里。

莫文 (Movin On Up)

Here in our Interactor we take the EntityGateway that was passed through from the Controller and call its saveWidget(widget) function via the IEntityGateway interface. This function returns a Promise from the EntityGateway which resolves in .then() with a newly created widget. We then call the Presenter’s presentWidget(widget) function via the IOutputBoundary interface which starts the newly created widget back up to the UI. This all happens in the Interactor’s createWidget(widget) function which was called by our Controller via the IInputBoundary interface in step 2.

在我们的Interactor中,我们采用从Controller传递过来的EntityGateway,并通过IEntityGateway接口调用其saveWidget(widget)函数。 此函数从EntityGateway返回一个Promise,它使用新创建的小部件在.then()中解析。 然后,我们通过IOutputBoundary接口调用Presenter的presentWidget(widget)函数,该接口将新创建的小部件启动回到UI。 这一切都发生在Interactor的createWidget(widget)函数中,该函数在步骤2中由我们的控制器通过IInputBoundary接口调用。

面向对象的步骤4 (OO Step 4)

Here in our Presenter, we simply pass the widget to our ViewModel’s presentWidget(widget) function we created in our UI. This all happens in the Presenter’s presentWidget(widget) function via the IOutputBoundary interface which was called in the Interactor’s createWidget(widget) function in step 3. More can happen here, but not in our example.

在Presenter中,我们只需将小部件传递给我们在UI中创建的ViewModel的presentWidget(widget)函数。 所有这些都通过IOutputBoundary接口在Presenter的presentWidget(widget)函数中发生,该接口在第3步中的Interactor的createWidget(widget)函数中被调用。

面向对象的步骤5 (OO Step 5)

Finally our newly created widget is back home ready to be displayed in our UI. This is the exact spot (code) where we started in step 1. Updating the UI happens in the ViewModel’s presentWidget(widget) function via the IViewModel interface which was called in the Presenter’s presentWidget(widget) function in step 4.

最后,我们新创建的窗口小部件已准备就绪,可以在我们的UI中显示。 这是我们从步骤1开始的确切位置(代码)。UI的更新是通过IViewModel接口在ViewModel的presentWidget(widget)函数中进行的,该接口在步骤4中在Presenter的presentWidget(widget)函数中调用。

OO支持演员 (OO Supporting Cast Members)

Here are all the remaining interfaces and type definitions clumped together in one file.

这是所有剩余的接口和类型定义,它们集中在一个文件中。

2个人进入1个人离开 (2 Men Enter 1 Man Leaves)

I wrote the class and interface version of this project first. I wanted to try and make it match Uncle Bob’s original diagram as close as I could. When I finished that project, I realized I could have done the same thing with functions and type definitions. So I created an identical project and replaced Classes with Functions and Interfaces with Type definitions.

我首先编写了该项目的类和接口版本。 我想尝试使其与Bob叔叔的原始图尽可能地接近。 当我完成该项目时,我意识到我可以使用函数和类型定义完成同样的事情。 因此,我创建了一个相同的项目,并使用函数和接口将类型替换为类型定义。

And here is the difference between a Controller class and a Controller function.

这是Controller类和Controller函数之间的区别。

进入,跨出和退出功能方式 (Step Into, Step Over, and Step Out, the Function Way)

Now lets give a stab at creating widgets with functions and type definitions.

现在,让我们扼杀使用函数和类型定义创建小部件的过程。

一般差异 (General Differences)

WidgetType is identical as the OO version above and IEntityGateway, IRequest, IViewModel, IInputBoundary, and IOutputBoundary are now type definitions instead of interfaces.

WidgetType与上面的OO版本相同,并且IEntityGateway,IRequest,IViewModel,IInputBoundary和IOutputBoundary现在是类型定义,而不是接口。

功能步骤1 (Function Step 1)

Every thing is the same as OO step 1 above, other than that we are now importing a function named “controllerConstructor” instead of a class named “Controller.” And importing a function named “entityGateway” instead of a class named EntityGateway. Last but not least, the ViewModel we created is now an object with a presentWidget() function in it instead of a class with a presentWidget() function.

每件事都与上面的OO步骤1相同,除了我们现在要导入一个名为“ controllerConstructor”的函数,而不是一个名为“ Controller”的类。 然后导入一个名为“ entityGateway”的函数,而不是一个名为EntityGateway的类。 最后但并非最不重要的一点是,我们创建的ViewModel现在是一个带有presentWidget()函数的对象,而不是带有presentWidget()函数的类。

实体网关又来了? (EntityGateway Again?)

The EntityGateway does the same task as the OO version above. It is now a function instead of a class. It returns a saveWidget() function wrapped in an object.

EntityGateway与上述OO版本执行相同的任务。 现在它是一个函数而不是一个类。 它返回包装在对象中的saveWidget()函数。

更多接线 (More Wiring)

Same as OO version above except we are now exporting a function instead of a class.

与上面的OO版本相同,除了我们现在导出的是函数而不是类。

功能步骤2 (Function Step 2)

Our Controller is still a busy place and does the same tasks as the OO version. We are now importing a function named interactorConstructor instead of a class named Interactor. We are exporting a function named “controllerConstructor” instead of a class named “Controller.” It returns a function named “createWidget wrapped in an object.

我们的控制器仍然很忙,并且执行与OO版本相同的任务。 现在,我们将导入一个名为interactorConstructor的函数,而不是一个名为Interactor的类。 我们正在导出一个名为“ controllerConstructor”的函数,而不是一个名为“ Controller”的类。 它返回一个包装在对象中的名为“ createWidget”的函数。

功能步骤3 (Function Step 3)

Back in the Iteractor in our usecase module, we are executing the same tasks as the OO version above. We are now exporting a function named “interactorConstructor” instead of a class named “Interactor.” It returns a function named “createWidget wrapped in an object.

回到用例模块的Iteractor中,我们正在执行与上述OO版本相同的任务。 现在,我们正在导出一个名为“ interactorConstructor”的函数,而不是一个名为“ Interactor”的类。 它返回一个包装在对象中的名为“ createWidget”的函数。

功能步骤4 (Function Step 4)

We are now passing the newly created widget back up in our Presenter where we are executing the same tasks as the OO version above. We export a function named “presenterConstructor” instead of a class named “Presenter.” It returns a function named “presentWidget wrapped in an object.

现在,我们将新创建的小部件传递回Presenter中,在其中执行与上述OO版本相同的任务。 我们导出一个名为“ presenterConstructor”的函数,而不是一个名为“ Presenter”的类。 它返回一个包装在对象中的名为“ presentWidget”的函数。

功能步骤5 (Function Step 5)

Again we have come full circle and we are back in the exact spot (code) where we started in step 1. Our UI gets updated with our newly created widget in the ViewModel’s presentWidget() function.

再次,我们绕了一圈,回到了第一步中的确切位置(代码)。在ViewModel的presentWidget()函数中,使用新创建的小部件对UI进行了更新。

职能支持演员 (Function Supporting Cast Members)

Here are all the remaining type definitions clumped together in one file. These are our interfaces.

以下是所有剩余的类型定义,它们集中在一个文件中。 这些是我们的界面。

所有这一切都该死的小部件? (All That for a Damn Widget?)

Yes, but you also get the promise of a completely decoupled system where you can plug in different implementations of your outside (infrastructure blue layer) systems, including different types of UIs, external APIs, databases, libraries, frameworks and more.

是的,但是您也可以得到一个完全解耦的系统的希望,您可以在其中插入外部(基础架构蓝色层)系统的不同实现,包括不同类型的UI,外部API,数据库,库,框架等。

我们不需要Stinkin Profiler (We Don’t Need No Stinkin Profilers)

My original hunch was that the class and interface version would be slower than the function version. So I ran both projects through my advance profiling tools of typing “npm test” and pressing enter until my finger cramped up.

我最初的直觉是类和接口的版本会比函数的版本慢。 因此,我通过高级配置工具(输入“ npm test”)并按Enter直到手指收紧,来运行两个项目。

My first observation was that the function version was about twice as fast, WOW. Then I decided to refactor the function version to return all of the important functions wrapped in objects so I could enforce the function names. I then ran both versions through my advance profilers and they were about the same speed.

我的第一个观察结果是该功能版本的运​​行速度大约是WOW的两倍。 然后,我决定重构函数版本,以返回包装在对象中的所有重要函数,以便执行函数名。 然后,我通过高级分析器运行了两个版本,它们的速度大致相同。

I have no idea why wrapping a function in an object would slow it down that much. Maybe I didn’t actually get Adobe Flash completely uninstalled from my laptop and it decided to interfere. Anyways, it would be interesting to get a more accurate measure of speed using the correct tools against the compiled Javascript.

我不知道为什么将函数包装在对象中会降低它的速度。 也许我实际上并没有从笔记本电脑上完全卸载Adobe Flash,它决定进行干预。 无论如何,使用针对已编译Javascript的正确工具来获得更准确的速度度量将很有趣。

拿走 (The Take Away)

The OO version has more code but may be easier to read and follow. The function version has less code but may be harder to read and follow.

OO版本具有更多代码,但可能更易于阅读和遵循。 该功能版本的代码较少,但可能难以阅读和遵循。

Personally I like the function version, being that I have done a lot of programming in Java and I am tired of writing so many classes. One of the things I like the most about TypeScript/Javascript is the ability to use object literals. And with TypeScript type definitions, you can now apply some safety to using object literals.

就我个人而言,我喜欢函数版本,因为我已经用Java完成了许多编程工作,并且我厌倦了编写这么多类。 我最喜欢TypeScript / Javascript的一件事是使用对象文字的能力。 通过TypeScript类型定义,您现在可以在使用对象文字时应用一些安全措施。

Another take away is that you don’t need to rigidly conform to the clean architecture as diagrammed above to achieve a decoupled system. For example, you could just as easily have your UI communicate directly with your use case layer bypassing the delivery layer if it’s not needed. All of these layers may physically live in different places and have different ways of communicating with each other.

另一个要解决的问题是,您无需严格遵循上述架构,即可实现分离的系统。 例如,如果不需要,您可以轻松地让UI绕过交付层直接与用例层进行通信。 所有这些层在物理上可能都生活在不同的地方,并且具有彼此通信的不同方式。

试试看 (Give it a Try)

Here are some of the things I intend to enforce in my next project.

这是我打算在下一个项目中执行的一些操作。

  1. Dependencies should always go one way.

    依赖关系应该始终是一种方式。
  2. Dependencies should always point from your outside systems (UI, db, etc.) to your business entities and business logic.

    依赖关系应始终从外部系统(UI,数据库等)指向业务实体和业务逻辑。
  3. Your inner layers (delivery, use case, and business entities) need to expose interfaces for the more outer layers to use.

    您的内部层(交付,用例和业务实体)需要公开接口以供更多外部层使用。
  4. You should always start developing from the most inner layer out. Start with the business entities and logic first and test. Create the interfaces that will be used and then test these interfaces. I am guilty of working the other way around. I think we all like to start with the UI, because it immediately lets us visually see how our system will look to a user. Plus the UI is where a lot of the “cool” technologies live.

    您应该始终从最内层开始开发。 首先从业务实体和逻辑开始,然后进行测试。 创建将要使用的接口,然后测试这些接口。 我对此感到内working。 我认为我们都喜欢从UI入手,因为它可以立即使我们直观地看到系统对用户的外观。 另外,UI是许多“炫酷”技术的所在地。
  5. Use TDD (Test Driven Development). Clean architecture allows you to do this much more easily. Everything is more compartmentalized and easier to mock. The implementation of the IEntityGateway above is basically a mock of a database.

    使用TDD(测试驱动开发)。 干净的体系结构使您更轻松地执行此操作。 一切都变得更加分隔,更易于嘲笑。 上面的IEntityGateway的实现基本上是数据库的模拟。
  6. Last but not least, be flexible. Don’t knock yourself out trying to adhere to clean architecture when that library or framework you want to use just won’t work with it. But be warned that this is probably a good indication that you will eventually have some sort of problems regarding that library or framework, especially if it wants you to extend their classes. Decoupling should be your goal.

    最后但并非最不重要的一点是要灵活。 当您要使用的库或框架无法使用干净的体系结构时,请不要大惊小怪。 但请注意,这很可能很好地表明您最终将对该库或框架产生某种问题,尤其是如果它希望您扩展其类时。 去耦应该是您的目标。

但是,但是,那... (But, But, What About…)

Please ask questions and give feedback, there is no better way to learn than to get constructive criticism from your peers. And it is highly probable that I missed something somewhere.

请提出问题并提供反馈,没有比从同行那里获得建设性批评更好的学习方法了。 而且我很可能在某处错过了一些东西。

资源: (Resources:)

OO版本的代码位于: (Code for the OO version is located at:)

https://github.com/warrenbell/cleanarch-tsoo

https://github.com/warrenbell/cleanarch-tsoo

该功能版本的代码位于: (Code for the function version is located at:)

https://github.com/warrenbell/cleanarch-tsfun

https://github.com/warrenbell/cleanarch-ts有趣

ts节点 (ts-node)

Handy little TypeScript tool.

方便的小型TypeScript工具。

https://github.com/TypeStrong/ts-node

https://github.com/TypeStrong/ts-node

鲍勃叔叔的清洁建筑 (The Clean Architecture by Uncle Bob)

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

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

这本书 (The Book)

https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164

https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164

许多视频之一 (One of Many Videos)

They are all basically the same except for the the first 5 minutes where Uncle Bob likes to muse about something loosely related and then makes a hard segue into clean architecture.

除了前5分钟外,它们基本上都是相同的,前5分钟Bob叔叔喜欢思考一些松散相关的内容,然后艰难地研究出干净的建筑。

https://www.youtube.com/watch?v=Nltqi7ODZTM

https://www.youtube.com/watch?v=Nltqi7ODZTM

实施清洁架构—控制器和演示者的 (Implementing Clean Architecture — Of controllers and presenters)

https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/

https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/

清洁建筑:站在巨人的肩膀上 (Clean Architecture: Standing on the shoulders of giants)

https://herbertograca.com/2017/09/28/clean-architecture-standing-on-the-shoulders-of-giants/

https://herbertograca.com/2017/09/28/clean-architecture-standing-on-the-shoulders-of-giants/

干净的体系结构:用例包含演示者或返回数据? (Clean Architecture: Use case containing the presenter or returning data?)

https://softwareengineering.stackexchange.com/questions/357052/clean-architecture-use-case-containing-the-presenter-or-returning-data

https://softwareengineering.stackexchange.com/questions/357052/clean-architecture-use-case- contains-the-presenter-or-returning-data

干净的架构。 主持人的工作是什么? (Clean architecture. What are the jobs of presenter?)

https://stackoverflow.com/questions/46510550/clean-architecture-what-are-the-jobs-of-presenter

https://stackoverflow.com/questions/46510550/clean-architecture-what-are-the-jobs-of-presenter

翻译自: https://www.freecodecamp.org/news/a-typescript-stab-at-clean-architecture-b51fbb16a304/

typescript



推荐阅读
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
author-avatar
雅俊建安义琬
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有