From 1dc33bd1506ef9acb8110cefc0509901877ff43f Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Mon, 22 Oct 2018 23:08:12 +0200 Subject: [PATCH] Docs from vcs to user (#747) * new docs * new docs * order * fixed link * comments --- docs/content/design/from-vcs2user.md | 133 +++++++++++++++++++++++++++ docs/static/from-vcs-to-user.png | Bin 0 -> 9373 bytes 2 files changed, 133 insertions(+) create mode 100644 docs/content/design/from-vcs2user.md create mode 100644 docs/static/from-vcs-to-user.png diff --git a/docs/content/design/from-vcs2user.md b/docs/content/design/from-vcs2user.md new file mode 100644 index 00000000..d892c83d --- /dev/null +++ b/docs/content/design/from-vcs2user.md @@ -0,0 +1,133 @@ +--- +title: "From VCS to the User" +date: 2018-02-11T15:56:56-05:00 +--- + +You read about [proxy](/design/proxy), [communication](/design/communication/) and then opened a codebase and thought to yourself: This is not as simple as described in the docs. + +Athens has a set of architectural components that handle Go modules from their journey from the VCS into storage and down to the user. If you feel lost on how all these pieces work, read on my friend! + +From [communication](/design/communication/), you know that when a module is not backed up in the storage it gets downloaded from VCS (such as github.com) and then it is served to the user. You also know that this whole process is synchronous. But when you read a code you see module fetchers and download protocol stashers and you struggle to figure out what's what and how they differ. It might seem complicated, but this document will help explain everything that's going on. + +## Components + +Let's start with describing all the components you will see along the way. There's no better way to get a clear picture of everything than with a diagram. + +![Architecture chart of components](/from-vcs-to-user.png) + +As you can see, there are a lot of layers and wrappers. The first two pieces you'll see in the code are the Storage and the Fetcher. We'll start our journey there. + +### Storage +Storage is what it sounds like. Storage instance created in `proxy/storage.go`'s `GetStorage` function. +Based on storage type passed as an ENV variable it will create in-memory, filesystem, mongo... storage. +This is where modules live. Once there, always there. + +### Fetcher +`Fetcher` is the first component on our way. As we can guess from the name, Fetcher (`pkg/module/fetcher.go`) is responsible for fetching the sources from VCS. +For this, it needs two things: the `go` binary and `afero.FileSystem`. Path to binary and filesystems are passed to `Fetcher` during initialization. + +```go +mf, err := module.NewGoGetFetcher(goBin, fs) +if err != nil { + return err +} +``` +_app_proxy.go_ + +When a request for a new module comes, the `Fetch` function is invoked. + +```go +Fetch(ctx context.Context, mod, ver string) (*storage.Version, error) +``` +_fetch function_ + +Then the `Fetcher`: + +- creates a temp directory using an injected `FileSystem` +- in this temp dir, it constructs _dummy_ go project consisting of simple `main.go` and `go.mod` so the `go` CLI can be used. +- invokes `go mod download -json {module}` + +This command downloads the module into the cache directory within a created temp directory. +Once the download is completed, the `Fetch` function reads the module bits from FileSystem and returns them to the caller. +The exact path of module files is returned by `go mod` as part of `JSON` response. + +### Stash +As it is important for us to keep components small and readable, we did not want to bloat `Fetcher` with storing functionality. For storing modules into a storage we use `Stash`er. This is the single responsibility of a simple Stasher. + +We think it's important to keep components small and orthogonal, so the `Fetcher` and the `storage.Backend` don't interact. Instead, the `Stasher` composes them together and orchestrates the process of fetching code and then storing it. + +The New method accepts the `Fetcher` and the `storage.Backend` with a set of wrappers (explained later). + +```go +New(f module.Fetcher, s storage.Backend, wrappers ...Wrapper) Stasher +``` +_stasher.go_ + +The code in `pkg/stash/stasher.go` isn't complex, but it's important: + +I think this does two things: + +- invokes `Fetcher` to get module bits +- stores the bits using a `storage` + +If you read carefully you noticed wrappers passed into a basic `Stash`er implementation. +These wrappers add more advanced logic and help to keep components clean. + +The new method then returns a `Stasher` which is a result of wrapping basic `Stash`er with wrappers. + +```go +for _, w := range wrappers { + st = w(st) +} +``` +_stasher.go_ + +### Stash wrapper - Pool +As downloading a module is resource heavy (memory) operation, `Pool` (pkg/stash/with_pool.go) helps us to control simultaneous downloads. + +It uses N-worker patter which spins up the specified number of workers which then waits for a job to complete. Once they complete their job, they return the result and are ready for the next one. + +A job, in this case, is a call to Stash function on a backing `Stash`er. + +### Stash wrapper - SingleFlight +We know that module fetching is a resource-heavy operation and we just put a limit on a number of parallel downloads. To help us save more resources we wanted to avoid processing the same module multiple times. + +`SingleFlight` wrapper (pkg/stash/with_singleflight.go) takes care of that. +Internally it keeps track of currently running downloads using a map. +If a job arrives and `map[moduleVersion]` is empty, it initiates it with a callback channel and invokes a job on a backing `Stasher`. + +```go +s.subs[mv] = []chan error{subCh} +go s.process(ctx, mod, ver) +``` + +if there is an entry for the requested module, `SingleFlight` will subscribe for a result + +```go +s.subs[mv] = append(s.subs[mv], subCh) +``` + +and once the job is complete, the module is served one level up to the `download protocol` (or to wrapping `stasher` possibly) + +### Download protocol +The outer most level is the `download protocol`. + +```go +dpOpts := &download.Opts{ + Storage: s, + Stasher: st, + Lister: lister, +} +dp := download.New(dpOpts, addons.WithPool(protocolWorkers)) +``` +It contains two components we already mentioned: `Storage`, `Stasher` +and one more additional: `Lister`. + +`Lister` is used in `List` and `Latest` funcs to look upstream for the list of versions available. + +`Storage` is here again. We saw it in a `Stasher` before, used for saving. +In _Download protocol_ it is used to check whether or not the module is already present. If it is, it is served directly from `storage`. + +Otherwise, _Download protocol_ uses `Stasher` to download module, store it into a `storage` and then it serves it back to the user. + +You can also see `addons.WithPool` in a code snippet above. This addon is something similar to `Stash wrapper - Pool`. It controls the number of concurrent requests proxy can handle. \ No newline at end of file diff --git a/docs/static/from-vcs-to-user.png b/docs/static/from-vcs-to-user.png new file mode 100644 index 0000000000000000000000000000000000000000..2da2b46adf577129832055b83eb913d8d910c5b8 GIT binary patch literal 9373 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