Chapter 11: Working with larger projects
The chapter introduces Go’s packages, modules, versions, and workspaces as tools for turning a small RSS application into an organized multimodule project.
After this chapter you will be able to
- Go code organization layers. Explain how packages, modules, module versions, and workspaces fit together: modules group packages and track dependencies in go.mod, versions identify releases, and workspaces coordinate independent modules during development.
- Independent RSS reader modules. Describe the planned split of the RSS reader into a shared domain model, bbolt-backed storage, a REST API service, and a background worker that fetches feeds asynchronously, with separate modules allowing components to evolve independently.
- RSS reader module boundaries. Explain how the migrated RSS reader is divided into domain, storage, api, and worker modules, and why the example keeps them separate.
- Module paths and dependencies. Describe how go mod init creates go.mod, how the module path identifies the module and prefixes its packages’ import paths, and how go get or go mod tidy adds dependencies.
- Dependency checksums and cleanup. Explain how go.sum checksums verify downloaded dependencies and how go mod tidy synchronizes go.mod and go.sum with the code’s imports.
- Circular import prohibition. Explain that Go rejects circular package dependencies, including cycles through intermediate packages, and that this rule encourages separation of concerns.
- Conventional project directories. Describe the optional roles of cmd, pkg, api, web, configs, scripts, and docs in a larger Go project, including a separate cmd subdirectory for each executable. Explain that these are conventions rather than a required layout.
- Internal package import boundary. Explain that the special internal directory restricts imports to code within the directory tree rooted at internal's parent, unlike a pkg directory that conventionally signals code intended for external use.
- Shared domain entities. Describe how the domain module defines Article and Feed as the system’s core concepts, giving the API and worker a shared vocabulary without tying those concepts to infrastructure.
- Focused domain behavior interfaces. Explain how Fetcher and Storage define the operations the application needs without choosing implementations: Fetcher takes a URL and returns a Feed, while Storage describes article storage operations.
- Inward domain dependency flow. Trace the hexagonal architecture’s dependency direction: the domain owns entities and interface ports, while API and worker adapters depend on the domain; the domain never imports those implementation modules. Explain how this avoids circular dependencies and keeps the core independent of HTTP, databases, and RSS infrastructure.
- Module versions and compatibility. Explain what major, minor, and patch version changes communicate, and describe why tags for multiple modules in one repository identify the module as well as its version.
- Storage port and adapter. Explain how the storage module implements the domain.Storage interface as an adapter, so the domain and services depend on required operations rather than bbolt details.
- Shared persistence and initialization. Justify persistent storage for the independent API and worker processes, and describe how NewBoltStore prepares bbolt for read-only or read-write access.
- Transactional article storage. Describe how AddArticles stores articles as JSON in bbolt using article IDs as keys within a single write transaction.
- Recent article retrieval. Trace how GetRecent reads stored articles and returns the most recently published ones, including its treatment of missing publication dates.
- Workspace development across modules. Explain that a Go workspace lets developers work on interdependent modules together: each module retains its own go.mod file and releases independently, while development uses the modules’ latest code directly without repeatedly publishing new versions.
- Local module resolution. Explain how a go.work file changes dependency resolution during development: Go uses the local modules it lists instead of downloading their published versions.
- Independent module versions. Distinguish development in a workspace from consuming published modules: each module keeps its own go.mod and version, while users importing it receive a published version rather than the developer's go.work file.
- Read-only handler dependency. Explain why handlers accept domain.ArticleReader rather than the larger domain.Storage interface, and describe how a BoltStore can still be passed to them. Describe how RegisterRoutes adds the handlers' routes to a supplied mux.
- Isolated handler tests. Explain how a test double implementing domain.ArticleReader supplies canned data so HTTP handler tests can check requests and JSON responses without using a database. Distinguish a stub that returns predetermined data from a mock that verifies calls and arguments.
- API startup and shared storage. Trace how cmd/api/main.go initializes BoltStore, checks the initialization error, defers closing the store, and passes it to the handlers before starting the HTTP server. Explain that the API and worker use the same database file path so the worker can write articles that the API reads.
- Independent worker and API roles. Explain that the worker periodically fetches and stores articles while the API serves already-fetched data, so API requests do not wait for RSS providers. Describe how the services share domain types and a storage implementation without duplicating code or directly coupling the services.
- RSS adapter boundary. Describe how RSSReader uses gofeed to implement the domain.Fetcher interface, converting library Feed and Item values into domain types at the boundary. Explain that the compile-time interface check catches an incomplete implementation during compilation.
- Fetch and store isolation. Explain that fetchAndStore contains the feed-fetching and article-persistence logic, while a failure for one feed does not stop processing the remaining feeds. Describe how separating this function from main and using interface-based dependencies makes the logic testable apart from service orchestration.
- Database access and locking. Distinguish the API's read-only bbolt opening from the worker's writable opening, and explain how this enforces least privilege even if API handler code has a bug. Describe how read-only shared locking permits multiple API instances and how the worker's writes cause only brief interruptions.
- Complete system test sequence. Describe how to run the worker and API as separate processes, then query the API to verify that articles from multiple feeds appear in one JSON response sorted by publication date. Explain what querying before and after the worker’s first fetch shows.
- Independent service responsibilities. Explain that the worker fetches feeds on startup and every five minutes, logging a failed feed and continuing with the rest, while the API only serves stored articles. Relate that separation to API requests not waiting on slow RSS providers.
- Shared database access. Explain that the worker and API create separate BoltStore instances pointing to the same articles.db file. Describe how Bbolt’s write transactions and concurrent read-only views support periodic writes alongside API reads, and how article IDs prevent duplicates after a worker restart.
- Workspace local module resolution. Explain that go.work resolves the API’s and worker’s dependencies to local domain and storage modules even before versions are published. Describe how a change to domain/article.go reaches both services on their next go run without a release tag or dependency update.
- Independent module release histories. Explain that each module has its own Git-tagged release history, so domain, API, and worker can be versioned according to their own changes and released at different times.
- Breaking domain dependency updates. Trace what happens if the shared domain module makes a breaking change: it moves to v2.0.0, and API and worker must update their go.mod files to adopt it. Distinguish this from changes confined to API or worker, which need not affect the other module while both still satisfy the domain model.
- Independent modules and local changes. Explain how the API and worker keep separate go.mod files and versions while sharing a stable domain model, and how a workspace makes changes to related modules available locally without publishing tags or updating dependency versions.
- Appropriate use of workspaces. Distinguish situations that benefit from a workspace—related modules evolving together, cross-module testing before publication, and independently versioned monorepo components—from single-module projects or stable external dependencies that do not need one.
What it builds on
- (chapter intro) (chapter 9) · The example migrates the chapter 9 RSS reader application.
- Initializing the project module (chapter 2) · The earlier project initializes a Go module.
- Interfaces (chapter 5) · Go interfaces describe behavior.
- Simulating dependencies with an HTTP server (chapter 8) · Testing HTTP behavior with a simulated server.
- Workers (chapter 9) · The worker-pool pattern proposed for higher-throughput fetching.
Section by section
Introduction
The chapter introduces Go’s packages, modules, versions, and workspaces as tools for turning a small RSS application into an organized multimodule project.
- Go code organization layers: Explain how packages, modules, module versions, and workspaces fit together: modules group packages and track dependencies in go.mod, versions identify releases, and workspaces coordinate independent modules during development.
Why it matters: These layers provide the vocabulary for understanding how the project is organized. - Independent RSS reader modules: Describe the planned split of the RSS reader into a shared domain model, bbolt-backed storage, a REST API service, and a background worker that fetches feeds asynchronously, with separate modules allowing components to evolve independently.
Why it matters: The four-part RSS reader gives a concrete purpose to the chapter’s organizational tools. - Real project dependency pressures: Explain why the earlier self-contained examples do not represent many everyday projects: applications often rely on externally maintained libraries and interact with external services, making dependency documentation and management important.
Why it matters: Managing outside dependencies is part of the reason larger projects need deliberate organization.
Key terms: packages, modules, go.mod, module versions, workspaces, shared domain module, domain model, storage module, bbolt, REST API service
11.1 Modules
The section sets up a modular RSS reader project and explains how Go’s module files manage dependencies and verify downloaded code.
- RSS reader module boundaries: Explain how the migrated RSS reader is divided into domain, storage, api, and worker modules, and why the example keeps them separate.
Why it matters: The module boundaries show how services can share definitions and storage without being tightly coupled. - Module paths and dependencies: Describe how go mod init creates go.mod, how the module path identifies the module and prefixes its packages’ import paths, and how go get or go mod tidy adds dependencies.
Why it matters: Correct module paths and dependency declarations let Go locate and build each component. - Dependency checksums and cleanup: Explain how go.sum checksums verify downloaded dependencies and how go mod tidy synchronizes go.mod and go.sum with the code’s imports.
Why it matters: Verification and cleanup help keep builds trustworthy and module files accurate.
Key terms: domain, storage, api, worker, modules, go mod init, go.mod, module path, go get, go mod tidy
11.1.1 Packages
This section explains Go package rules and practical directory conventions for organizing a larger, multimodule project.
- Package roles and entry point: Explain that packages organize Go code and that an executable starts at main() in a main package, while other packages are named for their purpose.
Why it matters: This distinction helps you recognize executable code within a larger project. - Circular import prohibition: Explain that Go rejects circular package dependencies, including cycles through intermediate packages, and that this rule encourages separation of concerns.
Why it matters: Avoiding import cycles shapes how you divide code into packages. - Conventional project directories: Describe the optional roles of cmd, pkg, api, web, configs, scripts, and docs in a larger Go project, including a separate cmd subdirectory for each executable. Explain that these are conventions rather than a required layout.
Why it matters: Meaningful directories make a larger codebase easier to navigate and maintain without imposing one official layout. - Internal package import boundary: Explain that the special internal directory restricts imports to code within the directory tree rooted at internal's parent, unlike a pkg directory that conventionally signals code intended for external use.
Why it matters: The internal rule lets a project keep implementation packages unavailable to outside importers. - Repository root over src: Justify placing Go packages at the repository root or in meaningfully named directories rather than adding a Java-style src directory, because the repository root already serves as the source root.
Why it matters: Avoiding an unnecessary src layer keeps the project consistent with the layout described in the section.
Key terms: package, main package, main(), circular dependencies, imports, separation of concerns, cmd, pkg, api, web
11.1.2 Domain module: Shared domain model
The section builds a shared domain module for articles and feeds, then uses interfaces and inward-pointing dependencies to keep the API and worker independent of specific infrastructure implementations.
- Shared domain entities: Describe how the domain module defines Article and Feed as the system’s core concepts, giving the API and worker a shared vocabulary without tying those concepts to infrastructure.
Why it matters: A shared domain model lets separate services agree on what the system represents. - Focused domain behavior interfaces: Explain how Fetcher and Storage define the operations the application needs without choosing implementations: Fetcher takes a URL and returns a Feed, while Storage describes article storage operations.
Why it matters: Focused interfaces let the application depend on required behavior rather than a particular library or storage system. - Interchangeable interface implementations: Explain the Liskov substitution principle as used here: any implementation supplied as a Fetcher should work wherever a Fetcher is expected, allowing a real RSSReader, a test MockFetcher, or a future replacement without changing consuming code.
Why it matters: Interchangeable implementations make testing and later changes less disruptive. - Inward domain dependency flow: Trace the hexagonal architecture’s dependency direction: the domain owns entities and interface ports, while API and worker adapters depend on the domain; the domain never imports those implementation modules. Explain how this avoids circular dependencies and keeps the core independent of HTTP, databases, and RSS infrastructure.
Why it matters: One-way dependencies keep the core stable as external systems change.
Key terms: domain module, domain-driven design, Article, Feed, Fetcher, Storage, interfaces, Liskov substitution principle, LSP, RSSReader
11.2 Versioning modules
The section versions the domain module and builds a separately versioned bbolt storage adapter that lets the API and worker share article data.
- Module versions and compatibility: Explain what major, minor, and patch version changes communicate, and describe why tags for multiple modules in one repository identify the module as well as its version.
Why it matters: Versioned modules let services depend on specific releases and understand the compatibility they can expect. - Storage port and adapter: Explain how the storage module implements the domain.Storage interface as an adapter, so the domain and services depend on required operations rather than bbolt details.
Why it matters: Keeping storage behind an interface allows its implementation to change without rewriting the domain, API, or worker. - Shared persistence and initialization: Justify persistent storage for the independent API and worker processes, and describe how NewBoltStore prepares bbolt for read-only or read-write access.
Why it matters: The services need shared data, and their different access modes affect how the database must be started. - Transactional article storage: Describe how AddArticles stores articles as JSON in bbolt using article IDs as keys within a single write transaction.
Why it matters: The key choice prevents duplicate records, while a single transaction prevents partially stored batches. - Recent article retrieval: Trace how GetRecent reads stored articles and returns the most recently published ones, including its treatment of missing publication dates.
Why it matters: The API needs recent results without exposing partial writes or blocking other readers. - Individual article lookup outcomes: Describe how GetByID retrieves an article by its ID and distinguishes a missing article from a database failure.
Why it matters: The distinction lets an API report a missing article differently from a storage failure.
Key terms: semantic versioning, vMAJOR.MINOR.PATCH, domain/v0.0.1, domain.Storage, adapter pattern, ports and adapters, dependency inversion, bbolt, NewBoltStore, readOnly
11.3 Workspaces
Introduces Go workspaces as a way to develop services alongside a shared, versioned domain module without giving up independent modules or releases.
- Workspace development across modules: Explain that a Go workspace lets developers work on interdependent modules together: each module retains its own go.mod file and releases independently, while development uses the modules’ latest code directly without repeatedly publishing new versions.
Why it matters: This distinction explains how the upcoming services can evolve alongside the domain module without coupling their releases.
Key terms: Go workspaces, interdependent modules, go.mod, domain module
11.3.1 Understanding workspaces
Explains how a go.work workspace lets independently versioned Go modules use local copies of one another during development.
- Local module resolution: Explain how a go.work file changes dependency resolution during development: Go uses the local modules it lists instead of downloading their published versions.
Why it matters: This is what makes changes across modules available together during development. - Independent module versions: Distinguish development in a workspace from consuming published modules: each module keeps its own go.mod and version, while users importing it receive a published version rather than the developer's go.work file.
Why it matters: A workspace coordinates local work without tying the modules’ releases together. - Initial workspace setup: Describe placing go.work at the go-news repository root, above the domain, storage, api, and worker directories, and initially including the domain and storage modules before adding API and worker as they are created.
Why it matters: The initial setup establishes which local modules Go can use as the project grows.
Key terms: workspace, go.work, local modules, module proxy, go.mod, versioned independently, go-news, domain, storage, api
11.3.2 API module: REST service
The section builds a REST API module that uses shared domain and storage modules, keeps HTTP handlers read-only, tests them in isolation, and wires them to a persistent store.
- API module layout: Describe how the API module separates its executable entry point in cmd/api/main.go from module-private HTTP handlers in internal/handlers, while importing the shared storage module rather than defining its own store package.
Why it matters: This layout keeps HTTP code local to the API while allowing services to reuse the same storage implementation. - Read-only handler dependency: Explain why handlers accept domain.ArticleReader rather than the larger domain.Storage interface, and describe how a BoltStore can still be passed to them. Describe how RegisterRoutes adds the handlers' routes to a supplied mux.
Why it matters: A narrow interface makes the handlers' read-only role explicit and lets the same HTTP layer work with different implementations. - Isolated handler tests: Explain how a test double implementing domain.ArticleReader supplies canned data so HTTP handler tests can check requests and JSON responses without using a database. Distinguish a stub that returns predetermined data from a mock that verifies calls and arguments.
Why it matters: Substituting a small test implementation lets HTTP behavior be checked without also testing storage. - API startup and shared storage: Trace how cmd/api/main.go initializes BoltStore, checks the initialization error, defers closing the store, and passes it to the handlers before starting the HTTP server. Explain that the API and worker use the same database file path so the worker can write articles that the API reads.
Why it matters: The entry point connects the concrete storage adapter to the read-only HTTP layer and manages the database resource.
Key terms: API module, cmd directory pattern, internal directory, storage module, domain.ArticleReader, domain.Storage, BoltStore, RegisterRoutes, mux, Liskov substitution principle
11.3.3 Worker module: Background feed fetcher
This section builds a background RSS worker and shows how it shares domain and storage modules with an independently running API.
- Independent worker and API roles: Explain that the worker periodically fetches and stores articles while the API serves already-fetched data, so API requests do not wait for RSS providers. Describe how the services share domain types and a storage implementation without duplicating code or directly coupling the services.
Why it matters: The separation lets the API remain responsive even when a feed is slow or the worker fails. - RSS adapter boundary: Describe how RSSReader uses gofeed to implement the domain.Fetcher interface, converting library Feed and Item values into domain types at the boundary. Explain that the compile-time interface check catches an incomplete implementation during compilation.
Why it matters: Keeping external RSS types behind an adapter limits what must change if the RSS library changes. - Fetch and store isolation: Explain that fetchAndStore contains the feed-fetching and article-persistence logic, while a failure for one feed does not stop processing the remaining feeds. Describe how separating this function from main and using interface-based dependencies makes the logic testable apart from service orchestration.
Why it matters: A bad RSS source need not halt all updates, and the central behavior can be tested on its own. - Database access and locking: Distinguish the API's read-only bbolt opening from the worker's writable opening, and explain how this enforces least privilege even if API handler code has a bug. Describe how read-only shared locking permits multiple API instances and how the worker's writes cause only brief interruptions.
Why it matters: Database permissions reinforce the service boundary while allowing the API to keep serving requests during updates. - Worker scheduling and shutdown: Describe main's operational role in wiring dependencies, scheduling periodic fetches, and handling shutdown signals cleanly. State that this implementation fetches feeds sequentially and that a controlled worker pool would improve throughput for many feeds without changing the architecture.
Why it matters: Keeping lifecycle concerns in main makes the worker easier to operate and leaves room to scale feed fetching.
Key terms: worker module, API module, domain module, storage module, producer-consumer pattern, workspace, RSSReader, domain.Fetcher, gofeed, port-adapter boundary
11.3.4 Testing the complete system
This section tests the worker and API together, showing how workspace-local modules and shared storage let independent services produce and serve articles.
- Complete system test sequence: Describe how to run the worker and API as separate processes, then query the API to verify that articles from multiple feeds appear in one JSON response sorted by publication date. Explain what querying before and after the worker’s first fetch shows.
Why it matters: These observations verify that the services work together, not just separately. - Independent service responsibilities: Explain that the worker fetches feeds on startup and every five minutes, logging a failed feed and continuing with the rest, while the API only serves stored articles. Relate that separation to API requests not waiting on slow RSS providers.
Why it matters: Separating fetching from serving keeps a bad or slow feed from disrupting API responses. - Shared database access: Explain that the worker and API create separate BoltStore instances pointing to the same articles.db file. Describe how Bbolt’s write transactions and concurrent read-only views support periodic writes alongside API reads, and how article IDs prevent duplicates after a worker restart.
Why it matters: Shared persistent storage lets the independent processes exchange articles safely without duplicating them on restart. - Workspace local module resolution: Explain that go.work resolves the API’s and worker’s dependencies to local domain and storage modules even before versions are published. Describe how a change to domain/article.go reaches both services on their next go run without a release tag or dependency update.
Why it matters: Local workspace resolution makes it practical to develop and test all four modules together.
Key terms: worker, API, JSON response, publication date, feeds, graceful degradation, BoltStore, articles.db, Bbolt, write transactions
11.3.5 Versioning independent modules
This section explains how modules in a multimodule Go project can be released on separate version timelines while communicating compatibility to their users.
- Independent module release histories: Explain that each module has its own Git-tagged release history, so domain, API, and worker can be versioned according to their own changes and released at different times.
Why it matters: Separate release timelines let teams update one component without coordinating a release of every module. - Version numbers as compatibility signals: Explain that a published version communicates a compatibility expectation: after api/v1.0.0, future v1.x releases promise to remain compatible. Describe how version numbers also help identify a problematic release in logs or monitoring and support rollback.
Why it matters: Version numbers help users decide when to upgrade and help teams trace problems after release. - Breaking domain dependency updates: Trace what happens if the shared domain module makes a breaking change: it moves to v2.0.0, and API and worker must update their go.mod files to adopt it. Distinguish this from changes confined to API or worker, which need not affect the other module while both still satisfy the domain model.
Why it matters: The distinction shows when independent releases remain independent and when a shared dependency requires coordinated adoption.
Key terms: independent module versioning, Git tag, domain module, API module, worker module, versioning, api/v1.0.0, v1.x releases, logs, monitoring
11.3.6 Workspace benefits demonstrated
This section shows how a Go workspace supports independently versioned modules that evolve together during development, and when that arrangement is worth using.
- Independent modules and local changes: Explain how the API and worker keep separate go.mod files and versions while sharing a stable domain model, and how a workspace makes changes to related modules available locally without publishing tags or updating dependency versions.
Why it matters: This is the development benefit the example workspace demonstrates. - Module boundaries and extension: Describe the responsibilities of the domain, storage, API, and worker modules, and explain how depending on domain interfaces rather than implementations lets further modules plug into the system.
Why it matters: Clear boundaries make the system easier to extend without tightly coupling its services. - Appropriate use of workspaces: Distinguish situations that benefit from a workspace—related modules evolving together, cross-module testing before publication, and independently versioned monorepo components—from single-module projects or stable external dependencies that do not need one.
Why it matters: Workspaces are a development-time coordination tool, not a default requirement for every project.
Key terms: workspace, go.mod, domain model, module versions, domain interfaces, dependency inversion principle, storage, API, worker, workspaces
11.4 Summary
The section recaps how packages, modules, domain boundaries, interfaces, testing, and workspaces help organize and extend a larger Go project.
Concepts explained in this chapter
- module pathA module path is the identifier that names a Go module and distinguishes it when resolving versions and imports.
- versionA version number is an identifier representing a particular software release or state, often communicating its compatibility or maturity.
- breaking changeA breaking change is a modification that can prevent existing consumers from continuing to work without updates.
- hexagonal architecturePorts and Adapters is an architectural pattern that isolates core application logic behind interfaces and connects those interfaces to external implementations.
- semantic versioningSemantic versioning is a versioning convention in which changes to major, minor, and patch components communicate compatibility and release significance.
- Liskov Substitution PrincipleThe Liskov substitution principle states that objects of a subtype should be usable wherever objects of its supertype are expected without invalidating program correctness.
- adapterThe adapter pattern presents an existing component through an interface compatible with the surrounding application's expectations.
- cmd directory patternThe cmd directory pattern organizes independently executable Go programs into separate subdirectories within a module.
- go workspaceA Go workspace is a development-time configuration that coordinates multiple Go modules so they can be developed and tested together without publishing versions.
- go moduleA Go module is a versioned collection of Go packages described by a go.mod file and managed as a unit for dependency resolution and builds.
- packageA package is a Go compilation and namespace unit that groups related declarations and controls their visibility across source files.
- go.mod fileA go.mod file is a Go module definition file that records the module path, Go language version, and module dependencies.
- test doubleA test double is a substitute component that provides controlled behavior or data in place of a real dependency during testing.
- domain modelA domain model is a representation of the concepts and relationships in a problem domain that software components conform to.
- domain-driven designDomain-Driven Design is a software design approach that structures software around real-world business concepts and domain behavior.
- HTTP layerAn HTTP layer is the application layer responsible for receiving HTTP requests, invoking application behavior, and producing HTTP responses.
- dependency injectionDependency injection is a design technique in which a component receives its required collaborators from outside rather than constructing them internally.
- endpointAn HTTP endpoint is a network-accessible interface identified by an HTTP method and resource location that handles client requests.
- checksumA cryptographic checksum is a digest computed from data that can reveal whether the data has changed.
- dependency managementDependency management is the process of declaring, resolving, versioning, and obtaining software components required by a project.
- go mod tidygo mod tidy is a Go command that synchronizes module dependencies and their checksums with the packages imported by a project.
- go.sumgo.sum is a Go module file containing cryptographic checksums for downloaded module dependencies.
- reproducible buildA reproducible build is a build process that produces the same result from the same source and dependency inputs.
- compile-time interface checkA compile-time interface check is a declaration or construct that makes the compiler verify that a type satisfies a specified interface.
- producer-consumerA producer-consumer pattern separates components that generate data from components that consume it, allowing them to operate independently.
- entityAn entity is a domain object representing a distinct business concept with identity and associated data or behavior.
- circular dependencyA circular dependency is a dependency cycle in which packages or modules depend on one another directly or through an intermediate chain.
- entry pointThe main function is the specially named Go function that serves as the starting point of an executable program.
- main packageThe main package is the special Go package whose compiled program is intended to be an independently runnable executable.
- workerA worker service is a background process that performs scheduled or queued processing independently of request-serving services.
- go mod initgo mod init is a Go command that creates a go.mod file and initializes a module in a project directory.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
