Clean Architecture with NET 1st Edition Esposito download
Clean Architecture with NET 1st Edition Esposito download
pdf download
https://textbookfull.com/product/clean-architecture-with-net-1st-
edition-esposito/
https://textbookfull.com/product/clean-architecture-with-net-
developer-reference-1st-edition-esposito-dino/
https://textbookfull.com/product/programming-microsoft-asp-net-
mvc-dino-esposito/
https://textbookfull.com/product/get-your-hands-dirty-on-clean-
architecture-build-clean-applications-with-code-examples-in-
java-2nd-edition-tom-hombergs/
https://textbookfull.com/product/clean-cooking-healthy-eating-is-
easy-with-delicious-clean-recipes-2nd-edition-booksumo-press/
Pro C# 7 with .NET and .NET Core Andrew Troelsen
https://textbookfull.com/product/pro-c-7-with-net-and-net-core-
andrew-troelsen/
https://textbookfull.com/product/arduino-programming-with-net-
and-sketch-1st-edition-agus-kurniawan/
https://textbookfull.com/product/arduino-programming-with-net-
and-sketch-1st-edition-agus-kurniawan-2/
https://textbookfull.com/product/modern-data-access-with-entity-
framework-core-database-programming-techniques-for-net-net-core-
uwp-and-xamarin-with-c-1st-edition-holger-schwichtenberg/
https://textbookfull.com/product/modern-data-access-with-entity-
framework-core-database-programming-techniques-for-net-net-core-
uwp-and-xamarin-with-c-1st-edition-holger-schwichtenberg-2/
Clean Architecture
with .NET
Dino Esposito
Clean Architecture with .NET Editor-in-Chief
Published with the authorization of Microsoft Corporation by: Brett Bartow
All rights reserved. This publication is protected by copyright, and permission Associate Editor
must be obtained from the publisher prior to any prohibited reproduction, Shourav Bose
storage in a retrieval system, or transmission in any form or by any means,
electronic, mechanical, photocopying, recording, or likewise. For information Development Editor
regarding permissions, request forms, and the appropriate contacts within the Kate Shoup
Pearson Education Global Rights & Permissions Department, please visit
www.pearson.com/permissions. Managing Editor
No patent liability is assumed with respect to the use of the information con- Sandra Schroeder
tained herein. Although every precaution has been taken in the preparation
Senior Project Editor
of this book, the publisher and author assume no responsibility for errors or
omissions. Nor is any liability assumed for damages resulting from the use of Tracey Croom
the information contained herein. Copy Editor
ISBN-13: 978-0-13-820328-3 Dan Foster
ISBN-10: 0-13-820328-8
Indexer
Library of Congress Control Number: 2024930932
Ken Johnson
$PrintCode
Proofreader
Trademarks
Jennifer Hinchliffe
Microsoft and the trademarks listed at http://www.microsoft.com on the
“Trademarks” webpage are trademarks of the Microsoft group of companies. Technical Editor
All other marks are property of their respective owners. Milan Jovanovic
Warning and Disclaimer Editorial Assistant
Every effort has been made to make this book as complete and as accurate as Cindy Teeters
possible, but no warranty or fitness is implied. The information provided is on
an “as is” basis. The author, the publisher, and Microsoft Corporation shall have Cover Designer
neither liability nor responsibility to any person or entity with respect to any Twist Creative, Seattle
loss or damages arising from the information contained in this book or from
the use of the programs accompanying it. Compositor
codeMantra
Special Sales
For information about buying this title in bulk quantities, or for special sales Graphics
opportunities (which may include electronic versions; custom cover designs; codeMantra
and content particular to your business, training goals, marketing focus, or
branding interests), please contact our corporate sales department at
corpsales@pearsoned.com or (800) 382-3419.
For government sales inquiries, please contact
governmentsales@pearsoned.com.
For questions about sales outside the U.S., please contact intlcs@pearson.com.
Contents at a Glance
Introduction xv
Index 301
This page intentionally left blank
Contents
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Ubiquitous language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
A domain-specific language vocabulary . . . . . . . . . . . . . . . . . . . . . . . . . 29
Building the glossary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Keeping business and code in sync . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
v
Devising bounded contexts. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Applying modularization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
The presentation layer: interacting with the outside world. . . . . . . . . 51
The application layer: processing received commands. . . . . . . . . . . . . 51
The domain layer: representing domain entities. . . . . . . . . . . . . . . . . . 52
The data/infrastructure layer: persisting data . . . . . . . . . . . . . . . . . . . . 52
Achieving modularity. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
More modularity in monoliths. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Introducing microservices. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
The simplest solution ever . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Maintainability. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Designing for testability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
vi Contents
Designing the physical context map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Task orchestration. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
What is a task, anyway?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
An example distributed task. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
An example task in Project Renoir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Data transfer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
From the presentation layer to the application layer. . . . . . . . . . . . . 100
From the application layer to the persistence layer . . . . . . . . . . . . . . 104
Contents vii
Caching and caching patterns. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Injecting SignalR connection hubs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
viii Contents
Sending business emails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
Service to hash passwords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Contents ix
Early adopters. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
Tenets of a microservices architecture and SOA . . . . . . . . . . . . . . . . . 224
How big or small is “micro”?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
The benefits of microservices. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
The gray areas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
x Contents
Chapter 11 Technical debt and credit 285
The hidden cost of technical debt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
Dealing with technical debt. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
Ways to address debt. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
Debt amplifiers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
Index 301
Contents xi
This page intentionally left blank
Acknowledgments
As hair thins and grays, memories return of when I was the youngest in every meeting
or conference room. In 30 years of my career, I witnessed the explosion of Windows as
an operating system, the rise of the web accompanied by websites and applications, and
then the advent of mobile and cloud technologies.
Several times, I found myself having visions related to software technology devel-
opments, not too far from what happened a few years later. At other times, I surprised
myself by formulating personal projects halfway between dreams and ambitious goals.
The most unspoken of all is the desire to travel the world, speaking at international
conferences without the pressure to talk about what is cool and trendy but only about
what I have seen and made work—without mincing words and without filters or
reservations. To do this, I needed to work—finally—daily on the development of real
applications that contributed to some kind of business and simplified the lives of some
kind of audience.
After many years, I have a full-time position (CTO of Crionet), a team of people grown
in a few years from juniors to bold and capable professionals, and the will to share with
everyone a recipe for making software that is neither secret nor magical.
I have nothing to sell; only to tell. And this book is for those who want to listen.
This book was made possible by Loretta and Shourav and came out as you’re getting
it thanks to Milan, Tracey, Dan, and Kate.
xiii
This page intentionally left blank
Introduction
I graduated in Computer Science in the summer of 1990. At the time, there were not
many places in Europe to study computers. The degree course was not even set up with
its own Computer Science faculty but was an extension of the more classical faculty of
Mathematics, Physics, and Natural Sciences. Those with strong computer expertise in the
1990s were really cool people—in high demand but with unclear career paths. I started
as a Windows developer. Computer magazines were popular and eagerly awaited every
month. I dreamt of writing for one of them. I won the chance to do it once and liked it so
much that I’m still doing it today, 30 years later.
My passion for sharing knowledge was so intense that five years after my first serious
developer job it became my primary occupation. For over two decades all I did was write
books and articles, speak at conferences, teach courses, and do occasional consulting.
Until 2020, I had a very limited exposure to production code and the routine of day-by-
day development. Yet, I managed to write successful books for those who were involved
in real-world projects.
Still, in a remote area of my mind was a thorny doubt: Am I just a lecture type of pro-
fessional or am I also an action person? Will I be able to ever build a real-world system?
The pandemic and other life changes brought me to ultimately find an answer.
I faced the daunting task of building a huge and intricate system in a fraction of the
time originally scheduled that the pandemic sharply cut off. No way to design, be agile,
do testing and planning—the deadline was the only certain thing. I resorted to doing—
and letting a few other people do—just what I taught and had discovered while teaching
for years. It worked. Not just that. Along the way, I realized that the approach we took
to build software, and related patterns, also had a name: clean architecture. This book is
the best I know and have learned in three years of everyday software development after
over two decades of learning, teaching, and consulting.
In our company, we have several developers who joined as juniors and have grown
up using and experimenting with the content of this book. It worked for us; I hope it will
work for you, too!
xv
Who should read this book
Software professionals are the audience for this book, including architects, lead develop-
ers, and—I would say, especially—developers of any type of .NET applications. Everyone
who wants to be a software architect should find this book helpful and worth the cost.
And valid architects are, for the most part, born developers. I strongly believe that the
key to great software passes through great developers, and great developers grow out
of good teachers, good examples, and—hopefully—good books and courses.
Is this book only for .NET professionals? Although all chapters have a .NET flavor, most
of the content is readable by any software professional.
Assumptions
This book expects that you have at least a minimal understanding of .NET development and
object-oriented programming concepts. A good foundation in using the .NET platform and
knowledge of some data-access techniques will also help. We put great effort into making
this book read well. It’s not a book about abstract design concepts, and it’s not a classic archi-
tecture book either, full of cross-references or fancy strings in square brackets that hyperlink
to some old paper listed in a bibliography at the end of the book. It’s a book about building
systems in the 2020s and facing the dilemmas of the 2020s, from the front end to the back
end, passing through cloud platforms and scalability issues.
xvi Introduction
Part I of this book, titled “The Holy Grail of modularity,” lays the foundation of soft-
ware modularity, tracing back the history of software architecture and summarizing the
gist of domain-driven design (DDD)—one of the most helpful methodologies for break-
ing down business domains, though far from being an absolute necessity in a project.
Part II, “Architecture cleanup,” is about the five layers that constitute, in the vision of
this book, a “clean” architecture. The focus is not much on the concentric rendering of
the architecture, as popularized by tons of books and articles, but on the actual value
delivered by constituent layers: presentation, application, domain, domain services, and
infrastructure.
Finally, Part III, “Common dilemmas,” focuses on three frequently faced stumbling
blocks: monoliths or microservices, client-side or server-side for the front end, and the
role and weight of technical debt.
https://github.com/Youbiquitous/project-renoir
MicrosoftPressStore.com/NET/errata
If you discover an error that is not already listed, please submit it to us at the same
page.
Introduction xvii
For additional book support and information, please visit
MicrosoftPressStore.com/Support.
Please note that product support for Microsoft software and hardware is not offered
through the previous addresses. For help with Microsoft software or hardware, go to
http://support.microsoft.com.
Stay in touch
Let’s keep the conversation going! We’re on Twitter: http://twitter.com/MicrosoftPress
xviii Introduction
PAR T I
1
This page intentionally left blank
CHAPTER 1
S oftware as we know it today, in the middle of the 2020s, is the waste product of a more profound
learning and transformation process whose origins are deeply rooted within the history of logic and
mathematics. Since the 17th century, some of the world’s greatest minds focused on building a coher-
ent, logical system that could allow for mechanical reasoning. The proof that it was not just dreaming
came only in the 1930s with Kurt Gödel’s Theorem of Incompleteness. From there, Alan Turing and John
Von Neumann started engineering physical machines.
None of them, though, ever dreamed of anything near the software of today. Their goal was to
mechanize the human way of reasoning, as simple and ambitious as that may still sound. The early
“thinking” machines of the 1950s were iron monoliths made of valves, pistons, and cables—wired hard-
ware, no more, no less. John Von Neumann had the intuition that instructions were better separated
from hardware so that the same machine could do different things, such as mathematics and text pro-
cessing. The popular “von Neumann architecture” ultimately refers to having a stored program whose
instructions are fetched one by one and processed sequentially.
Software gained its own dignity and identity only at the end of the 1960s, about the time human-
kind landed on the moon. The first reported use of the term “software engineering” dates to the
mid-1960s. Nobody ever managed to create the software; rather, it emerged as a side effect—or waste
product—of more ambitious research. Separating hardware from software was the first step of modu-
larization ever experienced in computer science.
It seems that humans have always approached solving problems using an end-to-end sequence of
steps, with references and interconnections between states set and exchanged as needed to reach a
solution. As spaghetti code has shown, software is no exception.
Note The quest for modularization started as early as software itself and soon moved from
the level of application code to the level of application architecture.
3
In the beginning, it was three-tier
The first historical example of a software architecture that expands beyond the realm of a single
computer was proposed in the 1960s with the IBM 360 system. The idea was that a remote workstation
could send the central mainframe a request to execute some non-interactive data-processing opera-
tion, called a job. Further refined in successive years, the model became universally known as client/
server architecture after the paper “Separating Data from Function in a Distributed File System,” written
by a group of Xerox PARC computer scientists in 1978. Client/server was the canonical way of building
business applications when I got my first job as a developer right after graduating from university.
Note Von Neumann broke up the computer monolith into hardware and software com-
ponents, and IBM and Xerox researchers broke the software monolith into client and server
components.
We all heartily welcomed three-tier architecture in the late 1990s. At the time, the definition of an
additional software tier, which took on some client liabilities and some server tasks, proved necessary
to better handle the complexity of the (new) applications being built at a fairly rapid pace.
Note In the previous paragraph, I deliberately placed the adjective “new” in parentheses
because it referred to applications planned and built using the three-tier architecture before
the commercial explosion of the internet. At the time, colossus line-of-business applica-
tions (for example, financial, telecom, government, utilities, healthcare systems, and so
on) remained safely anchored to the existing mainframe-based client/server schema. Even
today, mainframes remain hard at work carrying on high-volume, real-time transactions,
such as credit card and ATM operations. Performance and cost-effectiveness are the crucial
reasons for this, despite the emergence of cloud, edge computing, Blockchain, and
massively distributed systems.
PRESENTATION
TIER
CLIENT
TIER
ONE BUSINESS
TIER TIER
SERVER
TIER
DATA
TIER
FIGURE 1-1 First stages of the evolution from single-tier to multi-tier architecture.
Software monoliths
The current definition of a software monolith is different from what it was in the 1990s—a start-to-
finish sequence of instructions with some input loop to keep it live and waiting for further instructions.
Today, monolithic software is commonly intended to be an application made of multiple components
combined in a single, self-contained deliverable. All the codebase lives in a single code solution and
is deployed in a single step on a single production server, whether on-premises or in the cloud. Any
constituent components become invisible from the outside once the application is deployed. Any
disastrous bug could potentially take the entire application down, and any necessary improvements
for scalability must be applied to the entire block. This can lead to significant rewrites of parts or pure
vertical hardware-based scalability.
In the common industry jargon, the terms tier and layer are often used interchangeably. In reality,
they both refer to distinct pieces of a software application but differ significantly from a deployment
perspective. A tier denotes a physical server, or at least a different process-execution space. In contrast,
a layer is a logical container for different portions of code and needs a physical tier to be deployed.
Note All layers are deployed to a physical tier, but different layers can go to different tiers.
In summary, tiers can provide a structure for scaling an application, but their mere presence doesn’t
guarantee faster performance. Efficient scaling involves not only the organization of tiers but also fac-
tors such as load balancing, code optimization, and the use of appropriate technologies for decoupling
(for example, a bus). Tiers help by offering a logical separation of responsibilities, but their performance
benefits are realized through thoughtful design, resource allocation, and performance tuning.
BUSINESS #1
DATA #1
BUSINESS #2
DATA #2
PRESENTATION
DATA #M
BUSINESS #N
Tiers and layers have followed different scales. The popular approach based on microservices tends
to increase the number of physical tiers up to hundreds. In contrast, the number of layers within a
single tier rarely exceeds four, which is considered the ideal number by the canonical supporting archi-
tecture of the domain-driven design (DDD) methodology. As you’ll see shortly, the four layers are:
■■ Presentation This layer collects user requests and input to submit to processing layers down
the stack.
■■ Application This layer receives raw input from the presentation layer and orchestrates any
necessary tasks.
■■ Infrastructure This layer deals with external services (for example, APIs and web services) and
storage.
In action, a typical web application develops over two tiers: the client browser and the server (cloud)
environment, sometimes referred to as the back end. How many tiers and layers exist within the back
end? Classic ASP.NET and ASP.NET Core applications and Blazor server applications often count two
tiers and multiple (N > 3) layers. One tier is the core application, and another is represented by the
primary database server—for example, a relational database management system (RDBMS).
The challenge for everyone, then, is learning the good and bad of every possible application archi-
tecture pattern, making a thoughtful choice based on the specific business context, and, most of all,
avoiding dogmatic disputes.
Note While researching the origin of the three-tier architecture, I ran into a curious fact
I’d never heard before. In the United States during the 1930s, right after the repeal of the
Prohibition Act, the government introduced a new distributed system for ensuring people’s
access to alcohol. Guess what? It was named the three-tier system, and the tiers were, from
bottom to top, producers, distributors, and retailers.
Behind known benefits such as the ability to reuse components, the parallelization of development,
and ease of maintenance, the ultimate goal and primary benefit of modularization is separation of
concerns (SoC)—a universal principle of software formalized in 1974 by Edsger W. Dijkstra in the paper
“On the Role of Scientific Thought.”
Note Although I have stated the core difference between a tier and a layer, from now on,
for the sake of simplicity, I’ll use the term layer to indicate tier or layer unless it becomes
necessary to distinguish between the two.
In contrast, in a web scenario, the user interface consists of a mixture of HTML, CSS, and JavaS-
cript rendered in a browser. Alternatively, it can be a runnable piece of WebAssembly code, like the
code generated by Blazor. Being run on a physically different machine, it is a real tier. The application,
though, might also consist of a special presentation layer whose primary purpose is to route requests
to some module to handle it. For ASP.NET Core applications, the presentation layer contains controller
classes and, more generally, code that is directly connected to reachable endpoints.
In recent years, a commonly recurring query in training sessions and workshops pertained to
the optimal placement of specific code segments. For instance, questions often arose regarding the
appropriate location for input validation code. Should it reside in the presentation or business layer?
Alternatively, is it advisable to defer validation until it hits the database, possibly handled by a stored
procedure or some surrounding code?
At its core, the business layer leaves such gray areas unclear. For this reason, a four-layer architecture
emerged.
Recently, the data layer has been abstracted into an infrastructural layer where persistence is just the
primary (but not unique) responsibility. Seen as infrastructure, this layer is also responsible for emails
and connections to external APIs.
The three-tier architecture started creaking under the weight of this complexity—not so much
because of its inherent inefficiencies, but rather due to the need to increase modularization to manage
business and implementation requirements and (hopefully) scale. (It was at this time that the term scal-
ability rose to prominence and gained its meaning as we know today—a system’s ability to maintain a
good level of service even if the number of requests grows unexpectedly.)
The domain-driven design (DDD) methodology systematized several practices and solutions that
proved valid on the ground. Bundled with the design methodology, there was also a canonical sup-
porting architecture.
Note I don’t use the phrase “monumental complexity” by chance. It is a quote from the
stories I’ve heard from the people who devised DDD and an homage to all of them.
2. The business layer was broken in two, with an application layer for the orchestration of use
cases and a domain layer for pure business logic. The domain layer in turn was composed of
two elements: a collection of persistence-agnostic domain models and a collection of persis-
tence-aware domain services. This is a key aspect of DDD.
3. The data layer was renamed the infrastructure layer, and the most common and important
service it provided was persistence.
The presentation layer can take various technological forms. It might be a desktop application (for
example, .NET MAUI, Electron, old-fashioned WPF, or Windows Forms), a mobile application, a mini-
mal API, or a fully-fledged web application (whether ASP.NET Core, Blazor, Angular, React, or Svelte
and friends). Also, the protocols involved may vary a bit, and include HTTPS, gRPC, and, in scenarios
involving the internet of things (IoT), Message Queue Telemetry Transport (MQTT), Advanced Message
Queuing Protocol (AMQP), and more.
Considering this, it is key to remember that despite the fact that the layer name (presentation)
evokes the presence of some graphical front end, a visual interface is not necessary. Even when the
bounded context is expected to be a plain web API, a presentation layer makes sense because, as men-
tioned, it represents a sort of reception and the gateway to internal functions and layers.
The application layer goes hand-in-hand with the presentation layer and supplies one action
method for each possible trigger detected by the presentation actors. When multiple versions of an
application are necessary (say, one for web and one for mobile), each should have its own application
layer unless the triggers and expected reactions are nearly the same.
Errors in Chapter VI
10. Die Keyserinn.—The Empress.
anomalous . in original
To England straightway let him send,
n in “send” invisible
When Mr. Douce stated that it was
Mr Douce
It strikingly exemplifies Mr. Douce’s eagerness
Mr Douce’s
forms the tail-piece at the end of the volume.
tailpiece
[VI-121]
footnote tag missing: best guess
WOOD ENGRAVING.
446
CHAPTER VII.
REVIVAL OF WOOD ENGRAVING.
Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.
textbookfull.com