> 2021年11月26信息消化 ### DDD in a nutshell origin: [DDD in a nutshell](https://tech.tamara.co/ddd-in-a-nutshell-3852b0a6155a) > Domain-driven design is the concept that the structure and language of **software code should match the business domain**. For example, if a software processes loan applications, it might have classes such as LoanApplication and Customer, and methods such as AcceptOffer and Withdraw. [Wikipedia](https://en.wikipedia.org/wiki/Domain-driven_design) In general, talking upon DDD mainly about Strategic and Tactical patterns: - **The strategic pattern** will help you design your domains, sub-domains that are communicated by the ubiquitous language then support you to organize/structure your teams based on that outcome. - **The tactical pattern** will guide you on how to implement your application in a scaling way. #### Strategic Design > Strategic pattern helps you design your domains, sub-domains that are communicated by the **ubiquitous language** then support you to organize/structure your teams based on that outcome. ![img](https://raw.githubusercontent.com/Phalacrocorax/memo-image-host/master/uPic/1*vLC-bTHsWR8Vmfo_VR_HTQ.jpeg) #### Tactical Design > Tactical pattern will guide you on how to implement your application in a scaling way. ![img](https://raw.githubusercontent.com/Phalacrocorax/memo-image-host/master/uPic/1*iWJ0ihKzNGpjaDbWqFWPDg.png) The Tactical Design helps you create an elegant Domain Model using Building Blocks, see below the main Building Blocks: ##### Aggregates It is one of the most important and complex patterns of *Tactical Design*, *Aggregates* are based on two other *Tactical Standards*, which are *Entities* and *Value Objects*. An *Aggregate* is a Cluster of one or more *Entities*, and may also contain *Value Objects*. The Parent *Entity* of this Cluster receives the name of *Aggregate Root*. (*thedomaindrivendesign.io*) For instance, in the Ecommerce domain, you will have Order as an aggregate which contains Address (Value Object) and Consumer (Entity) 它是战术设计中最重要和最复杂的模式之一,聚合基于另外两个战术标准,即实体和值对象。聚合是一个或多个实体的集群,也可能包含值对象。该集群的父实体接收聚合根的名称。 (域驱动设计.io) 例如,在电子商务领域,您将订单作为包含地址(值对象)和消费者(实体)的聚合体 ##### Entities An *Entity* is a potentially changeable object, which has a unique identifier. *Entities* have a life of their own within their *Domain Model*, which enables you to obtain the entire transition history of this *Entity.* 实体是一个潜在的可变对象,它有一个唯一的标识符。实体在它们的领域模型中有自己的生命周期,这使您能够获得该实体的整个转换历史。 ##### Value Objects What differentiates a *Value Object* from an *Entity* is that *Value Objects* are **immutable** and do not have a unique identity, are defined only by the values of their attributes. The consequence of this immutability is that in order to update a *Value Object*, you must create a new instance to replace the old one. 值对象与实体的区别在于值对象是不可变的并且没有唯一标识,仅由其属性值定义。这种不变性的结果是,为了更新值对象,您必须创建一个新实例来替换旧实例。 ##### Services *Services* are **stateless objects** that perform some logic that do not fit with an operation on an *Entity* or *Value Object*. They perform domain-specific operations, which can involve multiple domain objects. 服务是无状态对象,它们执行一些不适合对实体或值对象的操作的逻辑。 它们执行特定于域的操作,这可能涉及多个域对象。 ##### Repositories *Repositories* are mainly used to deal with storage, they abstract concerns about data storage. They are responsible for persisting *Aggregates*. 存储库主要用于处理存储,它们抽象了对数据存储的关注。他们负责持久化聚合。 ##### Factories *Factories* are used to provide abstraction in the construction of an Object and can return an *Aggregate* root, an *Entity*, or a *Value Object*. *Factories* are an alternative for building objects that have complexity in building via the constructor method. 工厂用于在对象的构造中提供抽象,并且可以返回聚合根、实体或值对象。工厂是通过构造函数方法构建具有复杂性的对象的替代方法。 ##### Events *Events* indicate significant occurrences that have occurred in the domain and need to be reported to other stakeholders belonging to the domain. It is common for *Aggregates* to publish events. 事件表示领域中发生的重大事件,需要向属于该领域的其他利益相关者报告。聚合发布事件是很常见的。 ##### Modules *Modules* are little mentioned by the developers, however, their use can be very interesting. Modules help us segregate concepts, can be defined as a *package* or a *namespace* depending on the programming languages, and always follow the *Ubiquitous Language*. *(The building blocks text is from thedomaindrivendesign.io)* Combining the above building blocks, the Tactical Design also comes with many valuable patterns such as **CQRS — Event Sourcing,** **Layered Architecture**… which will help you build better software that is easy to maintain and extend. 结合上述构建块,Tactical Design 还附带了许多有价值的模式,例如 **CQRS** — 事件溯源、分层架构……这将帮助您构建更好的易于维护和扩展的软件。 In the upcoming articles, we will discuss more details about that patterns. At Tamara, we are applying the DDD approach from the beginning, and it helps us a lot in building an excellent service that serves both Merchants and Consumers daily. ### Practical SOLID in Golang: Interface Segregation Principle origin: [Practical SOLID in Golang: Interface Segregation Principle](https://levelup.gitconnected.com/practical-solid-in-golang-interface-segregation-principle-f272c2a9a270) > MEMO 接口隔离原则 > 按context去区分接口。比如登录用户和未登录用户区别很大,做接口区分(不需要isLoggedIn方法),在登录用户里实现普通用户和高级用户。 > Whenever we want to **cover more types**, we should **protect them with different interfaces**. We should avoid making our interfaces too small but deliver complete functionality. #### When we do not respect The Interface Segregation > Keep interfaces small so that users don’t end up depending on things they don’t need. [Uncle Bob](https://twitter.com/unclebobmartin) coined this principle, and more details about it you can find on his [blog](https://blog.cleancoder.com/uncle-bob/2020/10/18/Solid-Relevance.html). This principle clearly defines its requirement, probably the best of all other SOLID principles. Its simple statement to keep interfaces as small as possible we should not understand as just one-method-interface, but to look more in a context of the **features’ cohesion** that interface holds. Let us examine the code below: ```go type User interface { AddToShoppingCart(product Product) IsLoggedIn() bool Pay(money Money) error HasPremium() bool HasDiscountFor(product Product) bool // // some additional methods // } type Guest struct { cart ShoppingCart // // some additional fields // } func (g *Guest) AddToShoppingCart(product Product) { g.cart.Add(product) } func (g *Guest) IsLoggedIn() bool { return false } func (g *Guest) Pay(Money) error { return errors.New("user is not logged in") } func (g *Guest) HasPremium() bool { return false } func (g *Guest) HasDiscountFor(Product) bool { return false } type NormalCustomer struct { cart ShoppingCart wallet Wallet // // some additional fields // } func (c *NormalCustomer) AddToShoppingCart(product Product) { c.cart.Add(product) } func (c *NormalCustomer) IsLoggedIn() bool { return true } func (c *NormalCustomer) Pay(money Money) error { return c.wallet.Deduct(money) } func (c *NormalCustomer) HasPremium() bool { return false } func (c *NormalCustomer) HasDiscountFor(Product) bool { return false } type PremiumCustomer struct { cart ShoppingCart wallet Wallet policies []DiscountPolicy // // some additional fields // } func (c *PremiumCustomer) AddToShoppingCart(product Product) { c.cart.Add(product) } func (c *PremiumCustomer) IsLoggedIn() bool { return true } func (c *PremiumCustomer) Pay(money Money) error { return c.wallet.Deduct(money) } func (c *PremiumCustomer) HasPremium() bool { return true } func (c *PremiumCustomer) HasDiscountFor(product Product) bool { for _, p := range c.policies { if p.IsApplicableFor(c, product) { return true } } return false } type UserService struct { // // some fields // } func (u *UserService) Checkout(ctx context.Context, user User, product Product) error { if !user.IsLoggedIn() { return errors.New("user is not logged in") } var money Money // // some calculation // if user.HasDiscountFor(product) { // // apply discount // } return user.Pay(money) } ``` Let us assume we want to deliver an application for shopping. One of the approaches is to define an interface `User`, as we did in the code example. This interface holds many features a user can have. A `User` on our platform can add a `Product` to the `ShoppingCart`. They can buy it. They can get a discount on a particular `Product`. The only problem is that only a specific `User` can do all of that. The actual implementations for this interface are three structs. The first one is the `Guest` struct. It should be a `User` who is not logged in on our system, but at least they can add a `Product` to the `ShoppingCart`. The second implementation is the `NormalCustomer`. It can do whatever `Guest` can, plus to buy a `Product`. The third implementation is the`PremiumCustomer`, which can use all features of our system. Now, look at all of those three structs. Only `PremiumCustomer` requires all three methods. Maybe we can assign all of them to `NormalCustomer`, but definitely, we hardly need more than two for `Guest`. Methods `HasPremium` and `HasDiscountFor` do not have any sense for `Guest`. If that struct represents the `User` who is not logged in, why would we even consider implementing methods for discounts? And we did all of this to add generalization inside UserService to handle all types of `Users` in the same place, with the same code. But, because of that, we need to implement a bunch of unused methods. So, to have better generalization, we got: 1. Many structs with unused methods. 2. Methods that we need somehow to mark so that others do not use them. 3. Much additional code for unit testing. 4. Unnatural polymorphism. 5. … So, let us refactor this mess. ##### How we do respect The Interface Segregation > Build interfaces around the minimal cohesive group of features. We do not need to invent some space science here. The only needed is to define a minimal interface that delivers a complete set of features. Let us check the code below: ```go type User interface { AddToShoppingCart(product Product) // // some additional methods // } type LoggedInUser interface { User Pay(money Money) error // // some additional methods // } type PremiumUser interface { LoggedInUser HasDiscountFor(product Product) bool // // some additional methods // } type Guest struct { cart ShoppingCart // // some additional fields // } func (g *Guest) AddToShoppingCart(product Product) { g.cart.Add(product) } type NormalCustomer struct { cart ShoppingCart wallet Wallet // // some additional fields // } func (c *NormalCustomer) AddToShoppingCart(product Product) { c.cart.Add(product) } func (c *NormalCustomer) Pay(money Money) error { return c.wallet.Deduct(money) } type PremiumCustomer struct { cart ShoppingCart wallet Wallet policies []DiscountPolicy // // some additional fields // } func (c *PremiumCustomer) AddToShoppingCart(product Product) { c.cart.Add(product) } func (c *PremiumCustomer) Pay(money Money) error { return c.wallet.Deduct(money) } func (c *PremiumCustomer) HasDiscountFor(product Product) bool { for _, p := range c.policies { if p.IsApplicableFor(c, product) { return true } } return false } type UserService struct { // // some fields // } func (u *UserService) Checkout(ctx context.Context, user User, product Product) error { loggedIn, ok := user.(LoggedInUser) if !ok { return errors.New("user is not logged in") } var money Money // // some calculation // if premium, ok := loggedIn.(PremiumUser); ok && premium.HasDiscountFor(product) { // // apply discount // } return loggedIn.Pay(money) } ``` Now, instead of one, we have three interfaces. `PremiumUser` embeds `LoggedInUser`, which embeds `User`. In addition, each of them introduces one method. The `User` now represents only customers who are still **not authenticated** on our platform. For such type, we know they can use features of `ShoppingCart`. The new `LoggedInUser` interface represents all our **authenticated customers**, and the`PremiumUser` interface represents all authenticated customers with a paid premium account. As you see in the `UserService`, instead of using methods with the boolean result, we just clarify the subtype of the `User` interface. If `User` implements `LoggedInUser`, we know that we talk about the authenticated customer. As you see in the `UserService`, instead of using methods with the boolean result, we just clarify the subtype of the `User` interface. If `User` implements `LoggedInUser`, we know that we talk about the authenticated customer. Besides those two methods, all structs from before are now more lightweight. Instead of each of them having five methods, where many of them are not used at all, now they just have methods they really need. #### Summary The Interface Segregation Principle is the fourth SOLID principle, and it stands for the letter *I* in the word *SOLID*. It teaches us to make our interfaces as tiny as possible. Whenever we want to cover more types, we should protect them with different interfaces. We should avoid making our interfaces too small but deliver complete functionality. ### Learning by Doing: When Does it Work? When Does it Fail? origin: [Learning by Doing: When Does it Work? When Does it Fail?](https://www.scotthyoung.com/blog/2021/11/23/learning-by-doing-deep-dive/) 1. #### Complexity and Cognitive Load One of the major mediators of the “learning by doing” thesis is task complexity. Absent instruction, we need to rely on what cognitive scientists refer to as “weak methods” to solve problems. These include trial-and-error and means-end analysis. There’s research showing that, at least in some cases, an overreliance on this kind of “figuring things out” can make it harder to learn.[1](https://www.scotthyoung.com/blog/2021/11/23/learning-by-doing-deep-dive/#easy-footnote-bottom-1-13720) “边做边学”论文的主要中介之一是任务复杂性。如果没有指导,我们需要依靠认知科学家所说的“弱方法”来解决问题。这些包括反复试验和手段-目的分析。有研究表明,至少在某些情况下,过度依赖这种“弄清楚事情”会使学习变得更加困难。 1 This is the conclusion behind John Sweller’s research on [cognitive load theory](https://en.wikipedia.org/wiki/Cognitive_load). Experiments show that seeing lots of examples tends to beat problem solving in a head-to-head comparison for algebra problems.[2](https://www.scotthyoung.com/blog/2021/11/23/learning-by-doing-deep-dive/#easy-footnote-bottom-2-13720) However, the result flips once you figure out the pattern, and problem solving becomes more useful.[3](https://www.scotthyoung.com/blog/2021/11/23/learning-by-doing-deep-dive/#easy-footnote-bottom-3-13720) 这是 John Sweller 认知负荷理论研究背后的结论。实验表明,在代数问题的正面比较中,看到大量示例往往会击败解决问题。2 然而,一旦你找出模式,结果就会翻转,解决问题变得更加有用。 3 My views on this issue seem to correspond most closely to Jeroen J. G. Van Merriënboer and Paul Kirschner. Their instructional design guide, [Ten Steps to Complex Learning](https://www.amazon.com/Steps-Complex-Learning-Four-Component-Instructional-ebook/dp/B076PWSXKZ/ref=tmm_kin_swatch_0?_encoding=UTF8&qid=1634577085&sr=1-1), argues for **the importance of starting with real tasks to be learned**. But, they also stress the importance of having lots of support and structure in the early phases of learning to avoid the problems Sweller’s research highlights. 我对这个问题的看法似乎与 Jeroen J. G. Van Merriënboer 和 Paul Kirschner 最接近。他们的教学设计指南,复杂学习的十个步骤,论证了从要学习的实际任务开始的重要性。但是,他们也强调了在学习的早期阶段获得大量支持和结构以避免 Sweller 研究强调的问题的重要性。 2. #### Prerequisite Skills and Concepts A representative of the former view would be Allan Collins and John Seely Brown’s [cognitive apprenticeship](https://en.wikipedia.org/wiki/Cognitive_apprenticeship). The idea here is to present students with the real situations and give enough assistance and structure so that the cognitive load isn’t overwhelming. An alternative approach, [Direct Instruction](https://en.wikipedia.org/wiki/Direct_instruction), was developed by Sigfried Engelmann and Wesley Becker. In this theory, skills are built from the bottom-up, with careful attention paid to the full range of **component skills** to minimize later issues with transfer to the whole task. Direct Instruction performs well in educational settings, [despite being unpopular](https://marginalrevolution.com/marginalrevolution/2018/02/direct-instruction-half-century-research-shows-superior-results.html) for its perceived “assembly line” approach to teaching. What’s the right approach? I think it depends. If you were trying to learn quantum mechanics and didn’t know algebra, you’d be in for a hard time trying to start there. But many educational programs pretend there are prerequisites that don’t actually exist in practice—learning to speak Chinese, for instance, doesn’t actually require learning to handwrite, and teaching it that way may throw up unnecessary barriers for someone who only wants to speak. > MEMO > Learn on demand? 3. #### Deliberate Practice and Plateaus In some ways, there’s no contradiction, only a matter of scope. You get good at what you do. If you play basketball, you’ll get better at basketball. If you practice shooting layups, you’ll get better at shooting layups. If you can practice a few hundred layups in an hour doing drills, and only a handful of times during a game, you’ll get better faster doing drills. The drilled layups may be harder to integrate than the layups played during the game. Still, the rate of practice is much higher in the drills, so the efficiency may be worth the trade-off. 在某些方面,没有矛盾,只是范围问题。你会擅长你的工作。如果你打篮球,你会打得更好。如果你练习投篮上篮,你会更好地投篮。如果你可以在一小时内练习几百次上篮,而在一场比赛中只能练习几次,那么你在练习时会变得更快。钻出的上篮可能比比赛中的上篮更难整合。尽管如此,练习中的练习率要高得多,因此效率可能值得权衡。 One route to improvement would be to **disengage from the performance requirements** of the job and **focus exclusively on practice**. But it depends on accurately identifying critical skills and creating conditions for practice. This is hard to do for nebulous skills, and, I’d argue, a lot of what we’re trying to get good at is in ill-defined domains of performance.≈ 改进的一种途径是摆脱工作的绩效要求,专注于实践。但这取决于准确识别关键技能并为实践创造条件。这对于模糊的技能来说很难做到,而且,我认为,我们试图擅长的很多东西都在不明确的表现领域中。 4. #### Background Knowledge Knowledge tends to be more skill-like than we may assume. Research on [transfer-appropriate processing](https://en.wikipedia.org/wiki/Transfer-appropriate_processing), for instance, shows that we encode the same information differently, depending on how we think we’re going to use it. Work on the [testing effect](https://en.wikipedia.org/wiki/Testing_effect) and retrieval practice shows that practicing remembering strengthens memory more than repeatedly looking at the content. 知识往往比我们想象的更像技能。例如,对传输适当处理的研究表明,我们对相同信息的编码方式不同,这取决于我们认为我们将如何使用它。测试效果和检索练习的工作表明,练习记忆比反复查看内容更能增强记忆力。 ### Misc - [Tell HN: Happy Thanksgiving Everyone](https://news.ycombinator.com/item?id=29338155) > I've been a HN member for 11 years and 11 months to this exact day (I joined Christmas 2009). > Since then, I've lived in 6 different cities, 5 different timezones and worked on countless side projects. But all this time, HN has always been my most visited month after month. > > I can always count on this place to keep me **up to date with the latest tech trends and read discussions between reasonable people**. Thank you all and especially dang for keeping discussions high quality. Especially as the quality of content on the rest of the internet deteriorates in the pursuit of virility. > > It's a testament to you all that place feels the same as it did 10+ years ago.