Repository: beatlabs/patron
Pull request #272 (2020-10-15 13:11:00)

Documentation Improvement
<!--
Thanks for taking precious time for making a PR.

Before creating a pull request, please make sure:
- Your PR solves one problem for which a issue exist and a solution has been discussed
- You have read the guide for contributing
  - See https://github.com/beatlabs/patron/blob/master/README.md#how-to-contribute
- You signed all your commits (otherwise we won't be able to merge the PR)
  - See https://github.com/beatlabs/patron/blob/master/SIGNYOURWORK.md
- You added unit tests for the new functionality
- You mention in the PR description which issue it is addressing, e.g. "Resolves #123"
-->

## Which problem is this PR solving?

Closes #272.

## Short description of the changes

- Create new pages
- Made README lean.


- [x] [Architecture](docs/Architecture.md)
- [x] [CLI](docs/other/CLI.md)
- [x] Components
  - [x] [Async](docs/components/async/Async.md)
    - [x] [Kafka](docs/components/async/Kafka.md)
    - [x] [AMQP](docs/components/async/AMQP.md)
    - [x] [AWS SQS](docs/components/async/AWSSQS.md)
  - [x] [HTTP](docs/components/HTTP.md)
  - [x] [gRPC](docs/components/gRPC.md)
- [x] Clients
- [x] Packages
  - [x] [Reliability](docs/other/Reliability.md)
  - [x] [Observability](docs/observability/Observability.md)
  - [x] [Logging](docs/observability/Logging.md)
  - [x] [Distributed Tracing](docs/observability/DistributedTracing.md)  
  - [x] [Caching](docs/other/Caching.md)
  - [x] [Encoding](docs/other/Encoding.md)
  - [x] [Errors](docs/other/Errors.md)
- [x] [Examples](docs/Examples.md)
- [x] [Code of Conduct](docs/CodeOfConduct.md)
- [x] [Contribution Guidelines](docs/ContributionGuidelines.md)
- [x] [Acknowledgments](docs/ACKNOWLEDGMENTS.md)


codecov[bot]: # [Codecov](https://codecov.io/gh/beatlabs/patron/pull/272?src=pr&el=h1) Report
> Merging [#272](https://codecov.io/gh/beatlabs/patron/pull/272?src=pr&el=desc) into [master](https://codecov.io/gh/beatlabs/patron/commit/ec3a39c39e02ab84139befb3fd8aeb189541f81e?el=desc) will **increase** coverage by `0.06%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/beatlabs/patron/pull/272/graphs/tree.svg?width=650&height=150&src=pr&token=sxY15rXW1X)](https://codecov.io/gh/beatlabs/patron/pull/272?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #272      +/-   ##
==========================================
+ Coverage   72.38%   72.45%   +0.06%     
==========================================
  Files          61       61              
  Lines        3111     3111              
==========================================
+ Hits         2252     2254       +2     
+ Misses        785      783       -2     
  Partials       74       74              
```


| [Impacted Files](https://codecov.io/gh/beatlabs/patron/pull/272?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [component/async/kafka/simple/duration\_client.go](https://codecov.io/gh/beatlabs/patron/pull/272/diff?src=pr&el=tree#diff-Y29tcG9uZW50L2FzeW5jL2thZmthL3NpbXBsZS9kdXJhdGlvbl9jbGllbnQuZ28=) | `96.36% <0.00%> (+3.63%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/beatlabs/patron/pull/272?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/beatlabs/patron/pull/272?src=pr&el=footer). Last update [ec3a39c...757db26](https://codecov.io/gh/beatlabs/patron/pull/272?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

bhiravabhatla: Hi Team,

Was going through set up for examples. Had a small suggestion -

As we have to run docker-compose up from inside examples directory ( mounting config for prometheus from $PWD). Can we change the go run statements to -
```
go run seventh/main.go
go run first/main.go
...
```
bhiravabhatla: Hey Team,

Couple more observations while running examples. 

* Looks like there is a breaking change in localstack, all the aws mock services are exposed at single port (4566) now (https://github.com/localstack/localstack#announcements).

We have used individual port bindings in example 4 and its **failing** now. We should be binding 4566 in docker-compose and use localhost:4566 while interacting with localstack (sqs/sns). I would happy to raise a PR for this.

* examples/sixth/greeter.pb.go is not present but mentioned in README.md  in Running the examples section.

@mantzas @tpaschalis @drakos74 
bhiravabhatla: Any Thoughts on above. Should I open an issue for the same?

 @mantzas @tpaschalis @drakos74
mantzas: > Any Thoughts on above. Should I open an issue for the same?
> 
> @mantzas @tpaschalis @drakos74

Nice catch, let's do this.
bhiravabhatla: > > Any Thoughts on above. Should I open an issue for the same?
> > @mantzas @tpaschalis @drakos74
> 
> Nice catch, let's do this.

Opened Issue#282 for the same. 

https://github.com/beatlabs/patron/issues/282

Patch:
diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md
deleted file mode 100644
index 207804610..000000000
--- a/ACKNOWLEDGMENTS.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Acknowledgments
-
-The framework makes primarily use of the following OSS projects:
-
-* [blackfriday](https://github.com/russross/blackfriday)
-* [sarama](https://github.com/Shopify/sarama)
-* [httprouter](https://github.com/julienschmidt/httprouter)
-* [opentracing-go](https://github.com/opentracing/opentracing-go)
-* [client_golang](https://github.com/prometheus/client_golang)
-* [zerolog](https://github.com/rs/zerolog)
-* [amqp](https://github.com/streadway/amqp)
-* [testify](https://github.com/stretchr/testify)
-* [jaeger-client-go](https://github.com/uber/jaeger-client-go)
diff --git a/README.md b/README.md
index 8b4458a47..52c59b354 100644
--- a/README.md
+++ b/README.md
@@ -19,482 +19,27 @@ The entry point of the framework is the `Service`. The `Service` uses `Component
 `Patron` provides the same defaults for making the usage as simple as possible.
 `Patron` needs Go 1.14 as a minimum.
 
-## How to Contribute
-
-1. **Contributor**: An issue has to be created with a problem description and possible solutions using the github template. The better the problem and solution are described, the easier for the **Curators and Others** to understand it and the faster the process. In case of a bug, steps to reproduce will help a lot.
-2. **Curators and Others**: The curators will engage in a discussion about the problem and the possible solution. Others can join the discussion to bring other solutions and insights at any point.
-3. **Curators**: After the discussion mentioned above, it will be determined if the proposed solution will be implemented or not. Appropriate tags will be applied to the issue.
-4. **Contributor**: The contributor will work as follows:
-    - assign the issue to himself
-    - create a fork
-    - git clone the fork on your machine
-    - enable [signing your work](SIGNYOURWORK.md)
-    - create a PR. use WIP to mark unfinished work e.g. __WIP: Fixing a bug (fixes #1)__
-    - after development has finished remove the WIP if applied
-5. **Curators**: The curators will conduct a full code review
-6. **Curators**: After at least 2 curators have approved the PR, it will be merged to master
-7. **Curators**: A release will follow after that at some point
-
-PR's should have the following requirements:
-
-- Tests are required (where applicable, terms may vary)
-  - Unit
-  - Component
-  - Integration
-- High code coverage
-- Coding style (go fmt)
-- Linting we use [golangci-lint](https://github.com/golangci/golangci-lint)
-
-## Code of conduct
-
-Please note that this project is released with a [Contributor Code of Conduct](https://www.contributor-covenant.org/adopters). By participating in this project and its community you agree to abide by those terms.
-
-## patron-cli
-
-The framework supplies a cli in order to simplify repository generation with the following features:
-
-- git repository creation
-- cmd folder and main.go creation with build version support (`go build -ldflags '-X main.version=1.0.0' main.go`)
-- go module support and vendoring
-- Dockerfile with version support (`docker build --build-arg version=1.0.0`)
-
-The latest version can be installed with
-
-```go
-go get github.com/beatlabs/patron/cmd/patron
-```
-
-Below is an example of a service created with the cli that has a module name `github.com/beatlabs/test` and will be created in the test folder in the current directory.
-
-```go
-patron -m "github.com/beatlabs/test" -p "test"
-```
-
-## Service
-
-The `Service` has the role of glueing all of the above together, which are:
-
-- setting up logging
-- setting up default HTTP component with the following endpoints configured:
-  - profiling via pprof
-  - liveness check
-  - readiness check
-- setting up termination by os signal
-- setting up SIGHUP custom hook if provided by an option
-- starting and stopping components
-- handling component errors
-- setting up metrics and tracing
-
-The service has some default settings which can be changed via environment variables:
-
-- Service HTTP port, for setting the default HTTP components port to `50000` with `PATRON_HTTP_DEFAULT_PORT`
-- Service HTTP read and write timeout, for changing the default HTTP read and write timeout to any value, use `PATRON_HTTP_READ_TIMEOUT`, `PATRON_HTTP_WRITE_TIMEOUT` respectively. For acceptable values check [here](https://golang.org/pkg/time/#ParseDuration).
-- Log level, for setting zerolog with `INFO` log level with `PATRON_LOG_LEVEL`
-- Tracing, for setting up jaeger tracing with
-  - agent host `0.0.0.0` with `PATRON_JAEGER_AGENT_HOST`
-  - agent port `6831` with `PATRON_JAEGER_AGENT_PORT`
-  - sampler type `probabilistic`with `PATRON_JAEGER_SAMPLER_TYPE`
-  - sampler param `0.0` with `PATRON_JAEGER_SAMPLER_PARAM`, which means that traces are not initiated here.
-
-### Component
-
-A `Component` is an interface that exposes the following API:
-
-```go
-type Component interface {
-  Run(ctx context.Context) error  
-}
-```
-
-The above API gives the `Service` the ability to start and gracefully shutdown a `component` via context cancellation. Furthermore, the component describes itself by implementing the `Info` method and thus giving the service the ability to report the information of all components. The framework divides the components in 2 categories:
-
-- synchronous, which are components that follow the request/response pattern and
-- asynchronous, which consume messages from a source but don't respond anything back
-
-The following component implementations are available:
-
-- HTTP (sync)
-- gRPC
-- RabbitMQ consumer (async)
-- Kafka consumer (async)
-- AWS SQS (async)
-
-Adding to the above list is as easy as implementing a `Component` and a `Processor` for that component.
-
-### HTTP Middlewares
-
-A `MiddlewareFunc` preserves the default net/http middleware pattern.
-You can create new middleware functions and pass them to Service to be chained on all routes in the default Http Component.
-
-```go
-type MiddlewareFunc func(next http.Handler) http.Handler
-
-// Setup a simple middleware for CORS
-newMiddleware := func(h http.Handler) http.Handler {
-    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-        w.Header().Add("Access-Control-Allow-Origin", "*")
-        // Next
-        h.ServeHTTP(w, r)
-    })
-}
-```
-
-### gRPC
-
-On the server side, the gRPC component injects a `UnaryInterceptor` which handles tracing and log propagation.
-On the client side, we inject a `UnaryInterceptor` which handles tracing and log propagation.
-
-## Examples
-
-Detailed examples can be found in the [examples](/examples) folder with the following components involved:
-
-- [HTTP Component, HTTP Tracing, HTTP middleware](/examples/http/main.go)
-- [Kafka Component, HTTP Component, HTTP Authentication, Kafka Tracing](/examples/kafka/main.go)
-- [Kafka Component, AMQP Tracing](/examples/amqp/main.go)
-- [AMQP Component, AWS SNS](/examples/sns/main.go)
-- [AWS SQS consumer performing gRPC request](/examples/sqs/main.go)
-- [AWS SQS consumer, highly customised](/examples/sqs-simple/main.go)
-- [gRPC](/examples/grpc/main.go)
-
-## Processors
-
-### Synchronous
-
-The implementation of the processor is responsible to create a `Request` by providing everything that is needed (Headers, Fields, decoder, raw io.Reader) pass it to the implementation by invoking the `Process` method and handle the `Response` or the `error` returned by the processor.
-
-The sync package contains only a function definition along with the models needed:
-
-```go
-type ProcessorFunc func(context.Context, *Request) (*Response, error)
-```
-
-The `Request` model contains the following properties (which are provided when calling the "constructor" `NewRequest`)
-
-- Fields, which may contain any fields associated with the request
-- Raw, the raw request data (if any) in the form of a `io.Reader`
-- Headers, the request headers in the form of `map[string]string`
-- decode, which is a function of type `encoding.Decode` that decodes the raw reader
-
-An exported function exists for decoding the raw io.Reader in the form of
-
-```go
-Decode(v interface{}) error
-```
-
-The `Response` model contains the following properties (which are provided when calling the "constructor" `NewResponse`)
-
-- Payload, which may hold a struct of type `interface{}`
-
-### Middlewares per Route
-
-Middlewares can also run per routes using the processor as Handler.
-So using the `Route` helpers:
-
-```go
-// A route with ...MiddlewareFunc that will run for this route only + tracing
-route := NewRoute("/index", "GET" ProcessorFunc, true, ...MiddlewareFunc)
-// A route with ...MiddlewareFunc that will run for this route only + auth + tracing
-routeWithAuth := NewAuthRoute("/index", "GET" ProcessorFunc, true, Authendicator, ...MiddlewareFunc)
-```
-
-### HTTP Caching
-
-The caching layer for HTTP routes is specified per Route.
-
-```go
-// RouteCache is the builder needed to build a cache for the corresponding route
-type RouteCache struct {
-	// cache is the ttl cache implementation to be used
-	cache cache.TTLCache
-	// age specifies the minimum and maximum amount for max-age and min-fresh header values respectively
-	// regarding the client cache-control requests in seconds
-	age age
-}
-
-func NewRouteCache(ttlCache cache.TTLCache, age Age) *RouteCache
-```
-
-#### server cache
-- The **cache key** is based on the route path and the url request parameters.
-- The server caches only **GET requests**.
-- The server implementation must specify an **Age** parameters upon construction.
-- Age with **Min=0** and **Max=0** effectively disables caching
-- The route should return always the most fresh object instance.
-- An **ETag header** must be always in responses that are part of the cache, representing the hash of the response.
-- Requests within the time-to-live threshold, will be served from the cache. 
-Otherwise the request will be handled as usual by the route processor function. 
-The resulting response will be cached for future requests.
-- Requests where the client control header requirements cannot be met i.e. **very low max-age** or **very high min-fresh** parameters,
-will be returned to the client with a `Warning` header present in the response. 
-
-```
-Note : When a cache is used, the handler execution might be skipped.
-That implies that all generic handler functionalities MUST be delegated to a custom middleware.
-i.e. counting number of server client requests etc ... 
-```
-
-### Usage
-
-- provide the cache in the route builder
-```go
-NewRouteBuilder("/", handler).
-	WithRouteCache(cache, http.Age{
-		Min: 30 * time.Minute,
-		Max: 1 * time.Hour,
-	}).
-    MethodGet()
-```
-
-- use the cache as a middleware
-```go
-NewRouteBuilder("/", handler).
-    WithMiddlewares(NewCachingMiddleware(NewRouteCache(cc, Age{Max: 10 * time.Second}))).
-    MethodGet()
-```
-
-#### client cache-control
-The client can control the cache with the appropriate Headers
-- `max-age=?` 
-
-returns the cached instance only if the age of the instance is lower than the max-age parameter.
-This parameter is bounded from below by the server option `minAge`.
-This is to avoid chatty clients with no cache control policy (or very aggressive max-age policy) to effectively disable the cache
-- `min-fresh=?` 
- 
-returns the cached instance if the time left for expiration is lower than the provided parameter.
-This parameter is bounded from above by the server option `maxFresh`.
-This is to avoid chatty clients with no cache control policy (or very aggressive min-fresh policy) to effectively disable the cache
-
-- `no-cache` / `no-store`
-
-returns a new response to the client by executing the route processing function.
-NOTE : Except for cases where a `minAge` or `maxFresh` parameter has been specified in the server.
-This is again a safety mechanism to avoid 'aggressive' clients put unexpected load on the server.
-The server is responsible to cap the refresh time, BUT must respond with a `Warning` header in such a case.
-- `only-if-cached`
-
-expects any response that is found in the cache, otherwise returns an empty response
-
-#### metrics
-
-The http cache exposes several metrics, used to 
-- assess the state of the cache
-- help trim the optimal time-to-live policy
-- identify client control interference
-
-By default we are using prometheus as the the pre-defined metrics framework.
-
-- `additions = misses + evictions`
-
-Always , the cache addition operations (objects added to the cache), 
-must be equal to the misses (requests that were not cached) plus the evictions (expired objects).
-Otherwise we would expect to notice also an increased amount of errors or having the cache misbehaving in a different manner.
-
-- `additions ~ misses`
-
-If the additions and misses are comparable e.g. misses are almost as many as the additions, 
-it would point to some cleanup of the cache itself. In that case the cache seems to not be able to support
-the request patterns and control headers.
-
-- `hits ~ additions`
-
-The cache hit count represents how well the cache performs for the access patterns of client requests. 
-If this number is rather low e.g. comparable to the additions, 
-this would signify that probably a cache is not a good option for the access patterns at hand.
-
-- `eviction age`
-
-The age at which the objects are evicted from the cache is a very useful indicator. 
-If the vast amount of evictions are close to the time to live setting, it would indicate a nicely working cache.
-If we find that many evictions happen before the time to live threshold, clients would be making use cache-control headers.
- 
-
-#### cache design reference
-- https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
-- https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
-
-#### improvement considerations
-- we can could the storing of the cached objects and their age counter. That way we would avoid loading the whole object in memory,
-if the object is already expired. This approach might provide considerable performance (in terms of memory utilisation) 
-improvement for big response objects. 
-- we could extend the metrics to use the key of the object as a label as well for more fine-grained tuning.
-But this has been left out for now, due to the potentially huge number of metric objects.
-We can review according to usage or make this optional in the future.
-- improve the serialization performance for the cache response objects
-
-### Asynchronous
-
-The implementation of the async processor follows exactly the same principle as the sync processor.
-The main difference is that:
-
-- The `Request` is the `Message` and contains only data as `[]byte`
-- There is no `Response`, so the processor may return an error
-
-```go
-type ProcessorFunc func(context.Context, *Message) error
-```
-
-Everything else is exactly the same.
-
-## Metrics and Tracing
-
-Tracing and metrics are provided by Jaeger's implementation of the OpenTracing project.
-Every component has been integrated with the above library and produces traces and metrics.
-Metrics are provided with the default HTTP component at the `/metrics` route for Prometheus to scrape.
-Tracing will be sent to a jaeger agent which can be setup through environment variables mentioned in the config section. Sane defaults are applied for making the use easy.
-We have included some clients inside the trace package which are instrumented and allow propagation of tracing to
-downstream systems. The tracing information is added to each implementations header. These clients are:
-
-- HTTP
-- AMQP
-- Kafka
-- SQL
-
-## Correlation ID propagation
-
-Patron receives and propagates a correlation ID. Much like the distributed tracing id, the correlation id is receiver on the entry points of the service e.g. HTTP, Kafka, etc. and is propagated via the provided clients. In case no correlation ID has been received, a new one is created.  
-The ID is usually received and sent via a header with key `X-Correlation-Id`.
-
-## Reliability
-
-The reliability package contains the following implementations:
-
-- Circuit Breaker
-
-### Circuit Breaker
-
-The circuit breaker supports a half-open state which allows to probe for successful responses in order to close the circuit again. Every aspect of the circuit breaker is configurable via its settings.
-
-## Clients
-
-The following clients have been implemented:
-
-- http, with distributed tracing and optional circuit breaker
-- sql, with distributed tracing
-- kafka, with distributed tracing
-- amqp, with distributed tracing
-
-## Logging
-
-The log package is designed to be a leveled logger with field support.
-
-The log package defines the logger interface and a factory function type that needs to be implemented in order to set up the logging in this framework.
-
-```go
-  // instantiate the implemented factory func type and fields (map[string]interface{})
-  err := log.Setup(factory, fields)
-  // handle error
-```
-
-`If the setup is omitted the package will not setup any logging!`
-
-From there logging is as simple as
-
-```go
-  log.Info("Hello world!")
-```
-
-The implementations should support the following log levels:
-
-- Debug, which should log the message with debug level
-- Info, which should log the message with info level
-- Warn, which should log the message with warn level
-- Error, which should log the message with error level
-- Panic, which should log the message with panic level and panics
-- Fatal, which should log the message with fatal level and terminates the application
-
-The first four (Debug, Info, Warn and Error) give the opportunity to differentiate the messages by severity. The last two (Panic and Fatal) do the same and do additional actions (panic and termination).
-
-The package supports fields, which are logged along with the message, to augment the information further to ease querying in the log management system.
-
-The following implementations are provided as sub-package and are by default wired up in the framework:
-
-- zerolog, which supports the excellent [zerolog](https://github.com/rs/zerolog) library and is set up by default
-
-### Context Logging
-
-Logs can be associated with some contextual data e.g. a request id. Every line logged should contain this id thus grouping the logs together. This is achieved with the usage of the context package as demonstrated below:
-
-```go
-ctx := log.WithContext(r.Context(), log.Sub(map[string]interface{}{"requestID": uuid.New().String()}))
-```
-
-The context travels through the code as an argument and can be acquired as follows:
-
-```go
-logger:=log.FromContext(ctx)
-logger.Infof("request processed")
-```
-
-Benchmarks are provided to show the performance of this.
-
-`Every provided component creates a context logger which is then propagated in the context`
-
-### Logger
-
-The logger interface defines the actual logger.
-
-```go
-type Logger interface {
-  Fatal(...interface{})
-  Fatalf(string, ...interface{})
-  Panic(...interface{})
-  Panicf(string, ...interface{})
-  Error(...interface{})
-  Errorf(string, ...interface{})
-  Warn(...interface{})
-  Warnf(string, ...interface{})
-  Info(...interface{})
-  Infof(string, ...interface{})
-  Debug(...interface{})
-  Debugf(string, ...interface{})
-}
-```
-
-In order to be consistent with the design the implementation of the `Fatal(f)` have to terminate the application with an error and the `Panic(f)` need to panic.
-
-### Factory
-
-The factory function type defines a factory for creating a logger.
-
-```go
-type FactoryFunc func(map[string]interface{}) Logger
-```
-
-## Security
-
-The necessary abstraction is available to implement authentication in the following components:
-
-- HTTP
-
-### HTTP
-
-In order to use authentication, an authenticator has to be implemented following the interface:
-
-```go
-type Authenticator interface {
-  Authenticate(req *http.Request) (bool, error)
-}
-```
-
-This authenticator can then be used to set up routes with authentication.
-
-The following authenticator is available:
-
-- API key authenticator, see examples
-
-## HTTP lifecycle endpoints
-
-When creating a new HTTP component, Patron will automatically create a liveness and readiness route, which can be used to know the lifecycle of the application:
-
-```
-# liveness
-GET /alive
-
-# readiness
-GET /ready
-```
-
-Both can return either a `200 OK` or a `503 Service Unavailable` status code (default: `200 OK`).
-
-It is possible to customize their behaviour by injecting an `http.AliveCheck` and/or an `http.ReadyCheck` `OptionFunc` to the HTTP component constructor.
+## Table of Contents
+
+- [Architecture](docs/Architecture.md)
+- [CLI](docs/other/CLI.md)
+- Components
+  - [Async](docs/components/async/Async.md)
+    - [Kafka](docs/components/async/Kafka.md)
+    - [AMQP](docs/components/async/AMQP.md)
+    - [AWS SQS](docs/components/async/AWSSQS.md)
+  - [HTTP](docs/components/HTTP.md)
+  - [gRPC](docs/components/gRPC.md)
+- [Clients](docs/clients/Clients.md)
+- Packages
+  - [Reliability](docs/other/Reliability.md)
+  - [Observability](docs/observability/Observability.md)
+  - [Logging](docs/observability/Logging.md)
+  - [Distributed Tracing](docs/observability/DistributedTracing.md)  
+  - [Caching](docs/other/Caching.md)
+  - [Encoding](docs/other/Encoding.md)
+  - [Errors](docs/other/Errors.md)
+- [Examples](docs/Examples.md)
+- [Code of Conduct](docs/CodeOfConduct.md)
+- [Contribution Guidelines](docs/ContributionGuidelines.md)
+- [Acknowledgments](docs/ACKNOWLEDGMENTS.md)
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 000000000..18854876c
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1,1 @@
+theme: jekyll-theme-midnight
\ No newline at end of file
diff --git a/component/async/kafka/kafka.go b/component/async/kafka/kafka.go
index 81af52ac0..2847933da 100644
--- a/component/async/kafka/kafka.go
+++ b/component/async/kafka/kafka.go
@@ -34,9 +34,6 @@ var topicPartitionOffsetDiff *prometheus.GaugeVec
 var messageStatus *prometheus.CounterVec
 var messageConfirmation *prometheus.CounterVec
 
-// TimeExtractor defines a function extracting a time from a Kafka message.
-type TimeExtractor func(*sarama.ConsumerMessage) (time.Time, error)
-
 // TopicPartitionOffsetDiffGaugeSet creates a new Gauge that measures partition offsets.
 func TopicPartitionOffsetDiffGaugeSet(group, topic string, partition int32, high, offset int64) {
 	topicPartitionOffsetDiff.WithLabelValues(group, topic, strconv.FormatInt(int64(partition), 10)).Set(float64(high - offset))
diff --git a/component/async/kafka/option.go b/component/async/kafka/option.go
index 9e4d779d6..85feb791e 100644
--- a/component/async/kafka/option.go
+++ b/component/async/kafka/option.go
@@ -90,20 +90,3 @@ func DecoderJSON() OptionFunc {
 		return nil
 	}
 }
-
-// WithDurationOffset allows creating a consumer from a given duration.
-// It accepts a function indicating how to extract the time from a Kafka message.
-func WithDurationOffset(since time.Duration, timeExtractor TimeExtractor) OptionFunc {
-	return func(c *ConsumerConfig) error {
-		if since < 0 {
-			return errors.New("duration must be positive")
-		}
-		if timeExtractor == nil {
-			return errors.New("empty time extractor function")
-		}
-		c.DurationBasedConsumer = true
-		c.DurationOffset = since
-		c.TimeExtractor = timeExtractor
-		return nil
-	}
-}
diff --git a/component/async/kafka/simple/duration_client.go b/component/async/kafka/simple/duration_client.go
index 0b1f77aca..ec3366d44 100644
--- a/component/async/kafka/simple/duration_client.go
+++ b/component/async/kafka/simple/duration_client.go
@@ -6,7 +6,6 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/beatlabs/patron/component/async/kafka"
 	"github.com/beatlabs/patron/log"
 )
 
@@ -21,7 +20,7 @@ func newDurationClient(client durationKafkaClientAPI) (durationClient, error) {
 	return durationClient{client: client}, nil
 }
 
-func (d durationClient) getTimeBasedOffsetsPerPartition(ctx context.Context, topic string, since time.Time, timeExtractor kafka.TimeExtractor) (map[int32]int64, error) {
+func (d durationClient) getTimeBasedOffsetsPerPartition(ctx context.Context, topic string, since time.Time, timeExtractor TimeExtractor) (map[int32]int64, error) {
 	partitionIDs, err := d.client.getPartitionIDs(topic)
 	if err != nil {
 		return nil, err
@@ -38,7 +37,7 @@ type partitionOffsetResponse struct {
 	err         error
 }
 
-func (d durationClient) triggerWorkers(ctx context.Context, topic string, since time.Time, timeExtractor kafka.TimeExtractor, partitionIDs []int32, responseCh chan<- partitionOffsetResponse) {
+func (d durationClient) triggerWorkers(ctx context.Context, topic string, since time.Time, timeExtractor TimeExtractor, partitionIDs []int32, responseCh chan<- partitionOffsetResponse) {
 	for _, partitionID := range partitionIDs {
 		partitionID := partitionID
 		go func() {
@@ -78,7 +77,7 @@ func (d durationClient) aggregateResponses(ctx context.Context, partitionIDs []i
 	}
 }
 
-func (d durationClient) getTimeBasedOffset(ctx context.Context, topic string, since time.Time, partitionID int32, timeExtractor kafka.TimeExtractor) (int64, error) {
+func (d durationClient) getTimeBasedOffset(ctx context.Context, topic string, since time.Time, partitionID int32, timeExtractor TimeExtractor) (int64, error) {
 	left, err := d.client.getOldestOffset(topic, partitionID)
 	if err != nil {
 		return 0, err
@@ -94,7 +93,7 @@ func (d durationClient) getTimeBasedOffset(ctx context.Context, topic string, si
 	return d.offsetBinarySearch(ctx, topic, since, partitionID, timeExtractor, left, right)
 }
 
-func (d durationClient) offsetBinarySearch(ctx context.Context, topic string, since time.Time, partitionID int32, timeExtractor kafka.TimeExtractor, left, right int64) (int64, error) {
+func (d durationClient) offsetBinarySearch(ctx context.Context, topic string, since time.Time, partitionID int32, timeExtractor TimeExtractor, left, right int64) (int64, error) {
 	for left <= right {
 		mid := left + (right-left)/2
 
diff --git a/component/async/kafka/simple/simple.go b/component/async/kafka/simple/simple.go
index cf66ae099..7cd11fce8 100644
--- a/component/async/kafka/simple/simple.go
+++ b/component/async/kafka/simple/simple.go
@@ -14,6 +14,26 @@ import (
 	"github.com/beatlabs/patron/log"
 )
 
+// TimeExtractor defines a function extracting a time from a Kafka message.
+type TimeExtractor func(*sarama.ConsumerMessage) (time.Time, error)
+
+// WithDurationOffset allows creating a consumer from a given duration.
+// It accepts a function indicating how to extract the time from a Kafka message.
+func WithDurationOffset(since time.Duration, timeExtractor TimeExtractor) kafka.OptionFunc {
+	return func(c *kafka.ConsumerConfig) error {
+		if since < 0 {
+			return errors.New("duration must be positive")
+		}
+		if timeExtractor == nil {
+			return errors.New("empty time extractor function")
+		}
+		c.DurationBasedConsumer = true
+		c.DurationOffset = since
+		c.TimeExtractor = timeExtractor
+		return nil
+	}
+}
+
 // Factory definition of a consumer factory.
 type Factory struct {
 	name    string
diff --git a/docs/ACKNOWLEDGMENTS.md b/docs/ACKNOWLEDGMENTS.md
new file mode 100644
index 000000000..401e11e01
--- /dev/null
+++ b/docs/ACKNOWLEDGMENTS.md
@@ -0,0 +1,19 @@
+# Acknowledgments
+
+The framework makes primarily use of the following OSS projects:
+
+* [github.com/Shopify/sarama](https://github.com/Shopify/sarama)
+* [github.com/julienschmidt/httprouter](https://github.com/julienschmidt/httprouter)
+* [github.com/opentracing/opentracing-go](https://github.com/opentracing/opentracing-go)
+* [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang)
+* [github.com/rs/zerolog](https://github.com/rs/zerolog)
+* [github.com/streadway/amqp](https://github.com/streadway/amqp)
+* [github.com/stretchr/testify](https://github.com/stretchr/testify)
+* [github.com/uber/jaeger-client-go](https://github.com/uber/jaeger-client-go)
+* [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go)
+* [github.com/elastic/go-elasticsearch](https://github.com/elastic/go-elasticsearch)
+* [github.com/go-redis/redis](https://github.com/go-redis/redis)
+* [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
+* [github.com/google/uuid](https://github.com/google/uuid) 
+* [github.com/hashicorp/golang-lru](https://github.com/hashicorp/golang-lru)
+* [github.com/ory/dockertest](https://github.com/ory/dockertest)
diff --git a/docs/Architecture.md b/docs/Architecture.md
new file mode 100644
index 000000000..51beb79aa
--- /dev/null
+++ b/docs/Architecture.md
@@ -0,0 +1,56 @@
+# Architecture
+
+Patron has two basic concepts:
+
+- The *Component*, which defines a long-running task like a server e.g. HTTP, gRPC, Kafka consumer, etc.
+- The *Service*, which is responsible for running provided components and monitoring them for errors
+
+## Component
+
+A `Component` implements the following interface:
+
+```go
+type Component interface {
+  Run(ctx context.Context) error  
+}
+```
+
+This allows a `Service` to start and then gracefully shutdown a `Component` via context cancellation.  
+The framework distinguishes between two types of components:
+
+- synchronous, which are components that follow the request/response pattern and
+- asynchronous, which consume messages from a source but don't respond anything back
+
+The following component implementations are available:
+
+- HTTP (sync)
+- gRPC
+- RabbitMQ consumer (async)
+- Kafka consumer (async)
+- AWS SQS (async)
+
+## Service
+
+The `Service` has the role of gluing all the above together:
+
+- setting up logging, metrics and tracing
+- setting up a default HTTP component with the following endpoints configured:
+  - profiling via pprof
+  - liveness check
+  - readiness check
+- setting up termination by an OS signal
+- setting up SIGHUP custom hook if provided by an option
+- starting and stopping components
+- handling component errors
+
+The service has some default settings which can be changed via environment variables:
+
+- Service HTTP port, for setting the default HTTP components port to `50000` with `PATRON_HTTP_DEFAULT_PORT`
+- Service HTTP read and write timeout, use `PATRON_HTTP_READ_TIMEOUT`, `PATRON_HTTP_WRITE_TIMEOUT` respectively. For acceptable values check [here](https://golang.org/pkg/time/#ParseDuration).
+- Log level, for setting the logger with `INFO` log level with `PATRON_LOG_LEVEL`
+- Tracing, for setting up jaeger tracing with
+  - agent host `0.0.0.0` with `PATRON_JAEGER_AGENT_HOST`
+  - agent port `6831` with `PATRON_JAEGER_AGENT_PORT`
+  - sampler type `probabilistic`with `PATRON_JAEGER_SAMPLER_TYPE`
+  - sampler param `0.0` with `PATRON_JAEGER_SAMPLER_PARAM`, which means that no traces are sent.
+  
diff --git a/CODE_OF_CONDUCT.md b/docs/CodeOfConduct.md
similarity index 87%
rename from CODE_OF_CONDUCT.md
rename to docs/CodeOfConduct.md
index 7563b4bdf..b29c441e7 100644
--- a/CODE_OF_CONDUCT.md
+++ b/docs/CodeOfConduct.md
@@ -1,6 +1,12 @@
-# Contributor Covenant Code of Conduct
+# Code of conduct
 
-## Our Pledge
+Please note that this project is released with a [Contributor Code of Conduct](https://www.contributor-covenant.org/adopters).  
+By participating in this project and its community you agree to abide by those terms.  
+A copy can be read below.
+
+## Contributor Covenant Code of Conduct
+
+### Our Pledge
 
 In the interest of fostering an open and welcoming environment, we as
 contributors and maintainers pledge to making participation in our project and
@@ -9,7 +15,7 @@ size, disability, ethnicity, sex characteristics, gender identity and expression
 level of experience, education, socio-economic status, nationality, personal
 appearance, race, religion, or sexual identity and orientation.
 
-## Our Standards
+### Our Standards
 
 Examples of behavior that contributes to creating a positive environment
 include:
@@ -31,7 +37,7 @@ Examples of unacceptable behavior by participants include:
 * Other conduct which could reasonably be considered inappropriate in a
   professional setting
 
-## Our Responsibilities
+### Our Responsibilities
 
 Project maintainers are responsible for clarifying the standards of acceptable
 behavior and are expected to take appropriate and fair corrective action in
@@ -43,7 +49,7 @@ that are not aligned to this Code of Conduct, or to ban temporarily or
 permanently any contributor for other behaviors that they deem inappropriate,
 threatening, offensive, or harmful.
 
-## Scope
+### Scope
 
 This Code of Conduct applies both within project spaces and in public spaces
 when an individual is representing the project or its community. Examples of
@@ -52,7 +58,7 @@ address, posting via an official social media account, or acting as an appointed
 representative at an online or offline event. Representation of a project may be
 further defined and clarified by project maintainers.
 
-## Enforcement
+### Enforcement
 
 Instances of abusive, harassing, or otherwise unacceptable behavior may be
 reported by contacting the project team at smantziaris@gmail.com. All
@@ -65,9 +71,9 @@ Project maintainers who do not follow or enforce the Code of Conduct in good
 faith may face temporary or permanent repercussions as determined by other
 members of the project's leadership.
 
-## Attribution
+### Attribution
 
 This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
 available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
 
-[homepage]: https://www.contributor-covenant.org
+[homepage]: https://www.contributor-covenant.org
\ No newline at end of file
diff --git a/docs/ContributionGuidelines.md b/docs/ContributionGuidelines.md
new file mode 100644
index 000000000..272d4f873
--- /dev/null
+++ b/docs/ContributionGuidelines.md
@@ -0,0 +1,27 @@
+# Contribution Guidelines
+
+1. **Contributor**: Issues have to be created using the supplied GitHub template. They should include a problem description as well as possible solutions. The better the problem and solution are described, the easier for the **Curators and Others** to understand it and the faster the process. In case of a bug, steps to reproduce will help a lot.
+2. **Curators and Others**: The curators will engage in a discussion about the problem and the possible solution. Others can join the discussion to bring other solutions and insights at any point.
+3. **Curators**: After the discussion mentioned above, it will be determined if the proposed solution will be implemented or not. Appropriate tags will be applied to the issue.
+4. **Contributor**: The contributor will work as follows:
+    - assign the issue to himself
+    - create a fork
+    - git clone the fork on your machine
+    - enable [signing your work](SIGNYOURWORK.md)
+    - create a PR. use WIP to mark unfinished work e.g. __WIP: Fixing a bug (fixes #1)__
+    - after development has finished remove the WIP if applied
+5. **Curators**: The curators will conduct a full code review
+6. **Curators**: After at least 2 curators have approved the PR, it will be merged to master
+7. **Curators**: A release will follow after that at some point
+
+PR's should have the following requirements:
+
+- Tests are required (where applicable, terms may vary)
+  - Unit
+  - Component
+  - Integration
+- High code coverage
+- Coding style (go fmt)
+- Linting we use [golangci-lint](https://github.com/golangci/golangci-lint)
+
+The Make file contains the appropriate command in order to verify all the above.
diff --git a/docs/Examples.md b/docs/Examples.md
new file mode 100644
index 000000000..c9d68bccb
--- /dev/null
+++ b/docs/Examples.md
@@ -0,0 +1,135 @@
+# Examples
+
+The [examples/](/examples) folder contains a number of small services which work together to provide an overview of Patron's clients and components, as well as the built-in tracing and logging. After you start them all, you can see how a request travels through all services by triggering the `start_processing.sh` script.
+
+## Prerequisites
+
+Before all services start we should first start all dependencies with `docker-compose`.
+
+```shell
+docker-compose up -d
+```
+
+To tear down the above just:
+
+```shell
+docker-compose down
+```
+
+## [HTTP Cache Service](../examples/http-cache/main.go) 
+
+The service shows how to use:
+ 
+- HTTP caching in a specific route using Redis
+- Textual logger 
+  
+The service can be started as follows:
+
+```shell
+go run examples/http-cache/main.go
+```
+
+## [HTTP Service](../examples/http/main.go)
+
+The service shows how to use:
+ 
+- HTTP with CORS
+- HTTP client with API key
+- Textual logger with predefined fields
+  
+The service can be started as follows:
+
+```shell
+go run examples/http/main.go
+```
+
+## [HTTP API Key Service](../examples/http-sec/main.go)
+
+- HTTP service with a secured route (API KEY)
+- Async Kafka publisher
+- Default structured logger with predefined fields
+
+The service can be started as follows:
+
+```shell
+go run examples/http-sec/main.go
+```
+
+## [Kafka Service](../examples/kafka/main.go)
+
+The service shows how to use:
+
+- Kafka with a group consumer
+- AMQP publisher
+- Textual logger
+
+The service can be started as follows:
+
+```shell
+go run examples/amqp/main.go
+```
+
+## [AMQP Service](../examples/kafka/main.go)
+
+The service shows how to use:
+
+- AMQP consumer
+- AWS SNS Publisher
+- AWS SQS Publisher
+- Default structured logger
+
+The service can be started as follows:
+
+```shell
+go run examples/sns/main.go
+```
+
+## [AWS SQS Service](../examples/sqs/main.go)
+
+The service shows how to use:
+
+- AWS SQS Consumer
+- gRPC client
+- Default structured logger
+
+The service can be started as follows:
+
+```shell
+go run examples/sqs/main.go
+```
+
+## [AWS SQS Concurrent Service](../examples/sqs-simple/main.go)
+
+The service shows how to use:
+
+- AWS SQS Concurrent Consumer
+- Default structured logger
+
+The service can be started as follows:
+
+```shell
+go run examples/sqs-simple/main.go
+```
+
+## [gRPC Service](../examples/grpc/main.go)
+
+The service shows how to use:
+
+- gRPC Server
+- Textual logger
+
+The service can be started as follows:
+
+```shell
+go run examples/grpc/main.go
+```
+
+## All of them working together
+
+After all services have been started successfully we can send a request and see how it travels through all of them by running. 
+
+```shell
+../examples/start_processing.sh
+```
+
+After that head over to [jaeger](http://localhost:16686/search) and [prometheus](http://localhost:9090/graph).
\ No newline at end of file
diff --git a/SIGNYOURWORK.md b/docs/SIGNYOURWORK.md
similarity index 98%
rename from SIGNYOURWORK.md
rename to docs/SIGNYOURWORK.md
index 6911de3e3..604c1343d 100644
--- a/SIGNYOURWORK.md
+++ b/docs/SIGNYOURWORK.md
@@ -57,4 +57,5 @@ If you want this to be automatic you can set up some aliases:
 
 ```
 git config --add alias.amend "commit -s --amend"
-git config --add alias.c "commit -s"
\ No newline at end of file
+git config --add alias.c "commit -s"
+```
\ No newline at end of file
diff --git a/docs/clients/Clients.md b/docs/clients/Clients.md
new file mode 100644
index 000000000..93ffa1076
--- /dev/null
+++ b/docs/clients/Clients.md
@@ -0,0 +1,58 @@
+# Clients
+
+Patron microservices can interact with other microservices, APIs and applications using a number of clients.
+
+All clients contain integrated tracing powered by `opentracing-go`; any new clients should attempt to do the same.
+
+**Third-party dependencies**  
+github.com/opentracing/opentracing-go v1.1.0
+
+
+## HTTP Client
+Patron provides an HTTP client which integrates tracing into all outgoing requests by wrapping the default `net/http` client. Users can configure the client's Timeout, RoundTripper and/or set up a circuit breaker.
+
+
+## AMQP
+The AMQP client allows users to connect to a RabbitMQ instance and publish JSON or Protobuf messages using a fan-out exchange. The published messages have integrated tracing headers by default. Users can configure the timeout for connecting to the RabbitMQ instance.
+
+**Third-party dependencies**  
+github.com/streadway/amqp v0.0.0-20180315184602-8e4aba63da9f
+
+## gRPC
+The gRPC client initiates a client connection to a given target while injecting a `UnaryInterceptor` to integrate tracing capabilities. By default, this is a non-blocking connection and users can pass in any number of [`grpc.DialOption`](https://github.com/grpc/grpc-go/blob/master/dialoptions.go) arguments to configure its behavior.
+
+**Third-party dependencies**  
+google.golang.org/grpc v1.27.1
+
+
+## Kafka
+The Kafka client allows users to create a synchronous or asynchronous Kafka producer and publish Kafka messages with tracing headers. The builder pattern allows users to configure the Kafka version, the dial timeout, the RequiredAcks policy, as well as the encoder used to serialize the messages.
+
+**Third-party dependencies**  
+github.com/Shopify/sarama v1.21.0
+
+
+## Redis
+The Redis client allows users to connect to a Redis instance and execute commands. The connection can be configured using [`redis.Options`](https://github.com/go-redis/redis/blob/v7/options.go).
+
+**Third-party dependencies**  
+github.com/go-redis/redis/v7 v7.0.0-beta.5
+
+
+## SQL
+The SQL client enhances the standard library SQL by integrating tracing capabilities. It has support for prepared statements, queries, as well as low-level handling of transactions.
+
+
+## SNS - SQS
+The SNS and SQS clients provide a set of interfaces and structs useful for publishing messages to AWS SNS and SQS, with integrating tracing.
+
+**Third-party dependencies**  
+github.com/aws/aws-sdk-go v1.21.8
+
+
+## Elasticsearch
+The Elasticsearch client allows users to connect to an elasticsearch instance. Its behavior can be configured by providing an [`elasticsearch.Config`](https://github.com/elastic/go-elasticsearch/blob/4b40206692088570801280584e614027e6ce818b/elasticsearch.go#L32) struct
+
+**Third-party dependencies**  
+github.com/elastic/go-elasticsearch/v8 v8.0.0-20190731061900-ea052088db25
+
diff --git a/docs/clients/Clients.sh b/docs/clients/Clients.sh
new file mode 100644
index 000000000..2c7b1c668
--- /dev/null
+++ b/docs/clients/Clients.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+cat << EOF
+# Clients
+
+Patron microservices can interact with other microservices, APIs and applications using a number of clients.
+
+All clients contain integrated tracing powered by \`opentracing-go\`; any new clients should attempt to do the same.
+
+**Third-party dependencies**  
+$(cat ../../go.mod | grep github.com/opentracing/opentracing-go | xargs)
+
+
+## HTTP Client
+Patron provides an HTTP client which integrates tracing into all outgoing requests by wrapping the default \`net/http\` client. Users can configure the client's Timeout, RoundTripper and/or set up a circuit breaker.
+
+
+## AMQP
+The AMQP client allows users to connect to a RabbitMQ instance and publish JSON or Protobuf messages using a fan-out exchange. The published messages have integrated tracing headers by default. Users can configure the timeout for connecting to the RabbitMQ instance.
+
+**Third-party dependencies**  
+$(cat ../../go.mod | grep github.com/streadway/amqp | xargs)
+
+## gRPC
+The gRPC client initiates a client connection to a given target while injecting a \`UnaryInterceptor\` to integrate tracing capabilities. By default, this is a non-blocking connection and users can pass in any number of [\`grpc.DialOption\`](https://github.com/grpc/grpc-go/blob/master/dialoptions.go) arguments to configure its behavior.
+
+**Third-party dependencies**  
+$(cat ../../go.mod | grep google.golang.org/grpc | xargs)
+
+
+## Kafka
+The Kafka client allows users to create a synchronous or asynchronous Kafka producer and publish Kafka messages with tracing headers. The builder pattern allows users to configure the Kafka version, the dial timeout, the RequiredAcks policy, as well as the encoder used to serialize the messages.
+
+**Third-party dependencies**  
+$(cat ../../go.mod | grep github.com/Shopify/sarama | xargs)
+
+
+## Redis
+The Redis client allows users to connect to a Redis instance and execute commands. The connection can be configured using [\`redis.Options\`](https://github.com/go-redis/redis/blob/v7/options.go).
+
+**Third-party dependencies**  
+$(cat ../../go.mod | grep github.com/go-redis/redis/v7 | xargs)
+
+
+## SQL
+The SQL client enhances the standard library SQL by integrating tracing capabilities. It has support for prepared statements, queries, as well as low-level handling of transactions.
+
+
+## SNS - SQS
+The SNS and SQS clients provide a set of interfaces and structs useful for publishing messages to AWS SNS and SQS, with integrating tracing.
+
+**Third-party dependencies**  
+$(cat ../../go.mod | grep github.com/aws/aws-sdk-go | xargs)
+
+
+## Elasticsearch
+The Elasticsearch client allows users to connect to an elasticsearch instance. Its behavior can be configured by providing an [\`elasticsearch.Config\`](https://github.com/elastic/go-elasticsearch/blob/4b40206692088570801280584e614027e6ce818b/elasticsearch.go#L32) struct
+
+**Third-party dependencies**  
+$(cat ../../go.mod | grep github.com/elastic/go-elasticsearch | xargs)
+
+EOF
\ No newline at end of file
diff --git a/docs/components/HTTP.md b/docs/components/HTTP.md
new file mode 100644
index 000000000..29e8d9e8b
--- /dev/null
+++ b/docs/components/HTTP.md
@@ -0,0 +1,468 @@
+* [HTTP](#http)
+  * [HTTP lifecycle endpoints](#http-lifecycle-endpoints)
+  * [HTTP Middlewares](#http-middlewares)
+    * [Middleware Chain](#middleware-chain)
+    * [Helper Middlewares](#helper-middlewares)
+  * [HTTP Routes](#http-routes)
+    * [HTTP Method](#http-method)
+    * [Processor](#processor)
+    * [Raw RouteBuilder Constructor](#raw-routebuilder-constructor)
+    * [Middlewares per Route](#middlewares-per-route)
+    * [Security](#security)
+    * [Tracing](#tracing)
+    * [HTTP Caching](#http-caching)
+
+# HTTP
+
+The HTTP component provides the functionality for creating an HTTP server exposing the relevant routes. 
+It wraps the logic and handles the boilerplate for the `net.http` go package.
+
+The way to initialise an HTTP component is through the `patron http.Builder` struct.
+```go
+// NewBuilder initiates the HTTP component builder chain.
+// The builder instantiates the component using default values for
+// HTTP Port, Alive/Ready check functions and Read/Write timeouts.
+func NewBuilder() *Builder {
+	// ...
+}
+
+// WithSSL sets the filenames for the Certificate and Keyfile, in order to enable SSL.
+func (cb *Builder) WithSSL(c, k string) *Builder {
+	// ..
+}
+
+// WithRoutesBuilder adds routes builder to the HTTP component.
+func (cb *Builder) WithRoutesBuilder(rb *RoutesBuilder) *Builder {
+	// ...
+}
+
+// WithMiddlewares adds middlewares to the HTTP component.
+func (cb *Builder) WithMiddlewares(mm ...MiddlewareFunc) *Builder {
+	// ...
+}
+
+// WithReadTimeout sets the Read Timeout for the HTTP component.
+func (cb *Builder) WithReadTimeout(rt time.Duration) *Builder {
+	// ...
+}
+
+// WithWriteTimeout sets the Write Timeout for the HTTP component.
+func (cb *Builder) WithWriteTimeout(wt time.Duration) *Builder {
+	// ...
+}
+
+// WithShutdownGracePeriod sets the Shutdown Grace Period for the HTTP component.
+func (cb *Builder) WithShutdownGracePeriod(gp time.Duration) *Builder {
+	// ...
+}
+
+// WithPort sets the port used by the HTTP component.
+func (cb *Builder) WithPort(p int) *Builder {
+	// ...
+}
+
+// WithAliveCheckFunc sets the AliveCheckFunc used by the HTTP component.
+func (cb *Builder) WithAliveCheckFunc(acf AliveCheckFunc) *Builder {
+	// ...
+}
+
+// WithReadyCheckFunc sets the ReadyCheckFunc used by the HTTP component.
+func (cb *Builder) WithReadyCheckFunc(rcf ReadyCheckFunc) *Builder {
+	// ...
+}
+
+// Create constructs the HTTP component by applying the gathered properties.
+func (cb *Builder) Create() (*Component, error) {
+	// ...
+}
+```
+
+## HTTP lifecycle endpoints
+
+When creating a new HTTP component, Patron will automatically create a liveness and readiness route, which can be used to probe the lifecycle of the application:
+
+```
+# liveness
+GET /alive
+
+# readiness
+GET /ready
+```
+
+Both can return either a `200 OK` or a `503 Service Unavailable` status code (default: `200 OK`).
+
+It is possible to customize their behaviour by injecting an `http.AliveCheck` and/or an `http.ReadyCheck` `OptionFunc` to the HTTP component constructor.
+
+## HTTP Middlewares
+
+A `MiddlewareFunc` preserves the default net/http middleware pattern.
+You can create new middleware functions and pass them to Service to be chained on all routes in the default HTTP Component.
+
+```go
+type MiddlewareFunc func(next http.Handler) http.Handler
+
+// Setup a simple middleware for CORS
+newMiddleware := func(h http.Handler) http.Handler {
+    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+        w.Header().Add("Access-Control-Allow-Origin", "*")
+        // Next
+        h.ServeHTTP(w, r)
+    })
+}
+```
+
+### Middleware Chain
+
+Middlewares are invoked sequentially. The object handling this is the MiddlewareChain
+
+```go
+// MiddlewareChain chains middlewares to a handler func.
+func MiddlewareChain(f http.Handler, mm ...MiddlewareFunc) http.Handler {
+	for i := len(mm) - 1; i >= 0; i-- {
+		f = mm[i](f)
+	}
+	return f
+}
+```
+
+### Helper Middlewares
+
+Patron comes with some predefined middlewares, as helper tools to inject functionality into the HTTP endpoint or individual routes.
+
+```go
+
+// NewRecoveryMiddleware creates a MiddlewareFunc that ensures recovery and no panic.
+func NewRecoveryMiddleware() MiddlewareFunc {
+    // ...
+}
+
+// NewAuthMiddleware creates a MiddlewareFunc that implements authentication using an Authenticator.
+func NewAuthMiddleware(auth auth.Authenticator) MiddlewareFunc {
+    // ...
+}
+
+// NewLoggingTracingMiddleware creates a MiddlewareFunc that continues a tracing span and finishes it.
+// It also logs the HTTP request on debug logging level
+func NewLoggingTracingMiddleware(path string) MiddlewareFunc {
+    // ...
+}
+
+// NewCachingMiddleware creates a cache layer as a middleware
+// when used as part of a middleware chain any middleware later in the chain,
+// will not be executed, but the headers it appends will be part of the cache
+func NewCachingMiddleware(rc *cache.RouteCache) MiddlewareFunc {
+    // ...
+}
+```
+
+## HTTP Routes
+
+Each HTTP component can contain several routes. These are injected through the `RoutesBuilder`
+
+```go
+// RouteBuilder for building a route.
+type RouteBuilder struct {
+	// ...
+}
+
+// NewRouteBuilder constructor.
+func NewRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
+    // ...
+}
+
+
+// WithTrace enables route tracing.
+func (rb *RouteBuilder) WithTrace() *RouteBuilder {
+	// ...
+}
+
+// WithMiddlewares adds middlewares.
+func (rb *RouteBuilder) WithMiddlewares(mm ...MiddlewareFunc) *RouteBuilder {
+	// ...
+}
+
+// WithAuth adds authenticator.
+func (rb *RouteBuilder) WithAuth(auth auth.Authenticator) *RouteBuilder {
+	// ...
+}
+
+// WithRouteCache adds a cache to the corresponding route
+func (rb *RouteBuilder) WithRouteCache(cache cache.TTLCache, ageBounds httpcache.Age) *RouteBuilder {
+	// ...
+}
+
+// Build a route.
+func (rb *RouteBuilder) Build() (Route, error) {
+	// ...
+}
+```
+
+The main components that hold the logic for a route are the **processor** and the **middlewares**
+
+### HTTP Method 
+
+The method for each route cn be defined through the builder as well
+
+```go
+
+// MethodGet HTTP method.
+func (rb *RouteBuilder) MethodGet() *RouteBuilder {
+	// ...
+}
+
+// MethodHead HTTP method.
+func (rb *RouteBuilder) MethodHead() *RouteBuilder {
+	// ...
+}
+
+// MethodPost HTTP method.
+func (rb *RouteBuilder) MethodPost() *RouteBuilder {
+	// ...
+}
+
+// MethodPut HTTP method.
+func (rb *RouteBuilder) MethodPut() *RouteBuilder {
+	// ...
+}
+...
+```
+
+and for reducing boilerplate code one can also combine this in the constructor call for the Builder
+
+```go
+
+// NewGetRouteBuilder constructor
+func NewGetRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
+	// ...
+}
+
+// NewHeadRouteBuilder constructor.
+func NewHeadRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
+	// ...
+}
+
+// NewPostRouteBuilder constructor.
+func NewPostRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
+	// ...
+}
+
+... 
+```
+
+### Processor
+
+The processor is responsible for creating a `Request` by providing everything that is needed (Headers, Fields, decoder, raw io.Reader), passing it to the implementation by invoking the `Process` method and handling the `Response` or the `error` returned by the processor.
+
+The sync package contains only a function definition along with the models needed:
+
+```go
+type ProcessorFunc func(context.Context, *Request) (*Response, error)
+```
+
+The `Request` model contains the following properties (which are provided when calling the "constructor" `NewRequest`)
+
+- Fields, which may contain any fields associated with the request
+- Raw, the raw request data (if any) in the form of a `io.Reader`
+- Headers, the request headers in the form of `map[string]string`
+- decode, which is a function of type `encoding.Decode` that decodes the raw reader
+
+An exported function exists for decoding the raw io.Reader in the form of
+
+```go
+Decode(v interface{}) error
+```
+
+The `Response` model contains the following properties (which are provided when calling the "constructor" `NewResponse`)
+
+- Payload, which may hold a struct of type `interface{}`
+
+### Raw RouteBuilder Constructor
+
+```go
+// NewRawRouteBuilder constructor.
+func NewRawRouteBuilder(path string, handler http.HandlerFunc) *RouteBuilder {
+	// ...
+}
+```
+
+The Raw Route Builder allows for lower level processing of the request and response objects. 
+It s main difference with the Route Builder is the processing function. Which in this case is the `native` go http handler func.
+
+```go
+// The HandlerFunc type is an adapter to allow the use of
+// ordinary functions as HTTP handlers. If f is a function
+// with the appropriate signature, HandlerFunc(f) is a
+// Handler that calls f.
+type HandlerFunc func(ResponseWriter, *Request)
+```
+
+```
+The Raw Route Builder constructor should be used,
+if the default behaviour and assumptions of the wrapped Route Builder 
+do not fit into the routes requirements or use-case.
+```
+
+### Middlewares per Route
+
+Middlewares can also run per routes using the processor as Handler.
+So using the `Route` builder:
+
+```go
+// WithMiddlewares adds middlewares.
+func (rb *RouteBuilder) WithMiddlewares(mm ...MiddlewareFunc) *RouteBuilder {
+	if len(mm) == 0 {
+		rb.errors = append(rb.errors, errors.New("middlewares are empty"))
+	}
+	rb.middlewares = mm
+	return rb
+}
+```
+
+### Security
+
+Users can implement the `Authenticator` interface to provide authentication capabilities for HTTP components and Routes
+```go
+type Authenticator interface {
+  Authenticate(req *http.Request) (bool, error)
+}
+```
+
+Patron also includes a ready-to-use implementation of an *API key authenticator*. 
+
+### Tracing
+
+One of the main features of patron is the tracing functionality for Routes. 
+Tracing can either be enabled by default from the Buidler.
+
+```go
+// WithTrace enables route tracing.
+func (rb *RouteBuilder) WithTrace() *RouteBuilder {
+	rb.trace = true
+	return rb
+}
+```
+
+### HTTP Caching
+
+The caching layer for HTTP routes is specified per Route.
+
+```go
+// RouteCache is the builder needed to build a cache for the corresponding route
+type RouteCache struct {
+	// cache is the ttl cache implementation to be used
+	cache cache.TTLCache
+	// age specifies the minimum and maximum amount for max-age and min-fresh header values respectively
+	// regarding the client cache-control requests in seconds
+	age age
+}
+
+func NewRouteCache(ttlCache cache.TTLCache, age Age) *RouteCache
+```
+
+**server cache**
+- The **cache key** is based on the route path and the url request parameters.
+- The server caches only **GET requests**.
+- The server implementation must specify an **Age** parameters upon construction.
+- Age with **Min=0** and **Max=0** effectively disables caching
+- The route should return always the most fresh object instance.
+- An **ETag header** must be always in responses that are part of the cache, representing the hash of the response.
+- Requests within the time-to-live threshold, will be served from the cache. 
+Otherwise the request will be handled as usual by the route processor function. 
+The resulting response will be cached for future requests.
+- Requests where the client control header requirements cannot be met i.e. **very low max-age** or **very high min-fresh** parameters,
+will be returned to the client with a `Warning` header present in the response. 
+
+```
+Note : When a cache is used, the handler execution might be skipped.
+That implies that all generic handler functionalities MUST be delegated to a custom middleware.
+i.e. counting number of server client requests etc ... 
+```
+
+**Usage**
+
+- provide the cache in the route builder
+```go
+NewRouteBuilder("/", handler).
+	WithRouteCache(cache, http.Age{
+		Min: 30 * time.Minute,
+		Max: 1 * time.Hour,
+	}).
+    MethodGet()
+```
+
+- use the cache as a middleware
+```go
+NewRouteBuilder("/", handler).
+    WithMiddlewares(NewCachingMiddleware(NewRouteCache(cc, Age{Max: 10 * time.Second}))).
+    MethodGet()
+```
+
+**client cache-control**
+The client can control the cache with the appropriate Headers
+- `max-age=?` 
+
+returns the cached instance only if the age of the instance is lower than the max-age parameter.
+This parameter is bounded from below by the server option `minAge`.
+This is to avoid chatty clients with no cache control policy (or very aggressive max-age policy) to effectively disable the cache
+- `min-fresh=?` 
+ 
+returns the cached instance if the time left for expiration is lower than the provided parameter.
+This parameter is bounded from above by the server option `maxFresh`.
+This is to avoid chatty clients with no cache control policy (or very aggressive min-fresh policy) to effectively disable the cache
+
+- `no-cache` / `no-store`
+
+returns a new response to the client by executing the route processing function.
+NOTE : Except for cases where a `minAge` or `maxFresh` parameter has been specified in the server.
+This is again a safety mechanism to avoid 'aggressive' clients put unexpected load on the server.
+The server is responsible to cap the refresh time, BUT must respond with a `Warning` header in such a case.
+- `only-if-cached`
+
+expects any response that is found in the cache, otherwise returns an empty response
+
+**metrics**
+
+The http cache exposes several metrics, used to 
+- assess the state of the cache
+- help trim the optimal time-to-live policy
+- identify client control interference
+
+By default we are using prometheus as the the pre-defined metrics framework.
+
+- `additions = misses + evictions`
+
+Always , the cache addition operations (objects added to the cache), 
+must be equal to the misses (requests that were not cached) plus the evictions (expired objects).
+Otherwise we would expect to notice also an increased amount of errors or having the cache misbehaving in a different manner.
+
+- `additions ~ misses`
+
+If the additions and misses are comparable e.g. misses are almost as many as the additions, 
+it would point to some cleanup of the cache itself. In that case the cache seems to not be able to support
+the request patterns and control headers.
+
+- `hits ~ additions`
+
+The cache hit count represents how well the cache performs for the access patterns of client requests. 
+If this number is rather low e.g. comparable to the additions, 
+this would signify that probably a cache is not a good option for the access patterns at hand.
+
+- `eviction age`
+
+The age at which the objects are evicted from the cache is a very useful indicator. 
+If the vast amount of evictions are close to the time to live setting, it would indicate a nicely working cache.
+If we find that many evictions happen before the time to live threshold, clients would be making use cache-control headers.
+ 
+
+**cache design reference**
+- https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
+- https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+
+**improvement considerations**
+- we can reconsider the storing of the cached objects and their age counter. That way we would avoid loading the whole object in memory,
+if the object is already expired. This approach might provide considerable performance (in terms of memory utilisation) 
+improvement for big response objects. 
+- we could extend the metrics to use the key of the object as a label as well for more fine-grained tuning.
+But this has been left out for now, due to the potentially huge number of metric objects.
+We can review according to usage or make this optional in the future.
+- improve the serialization performance for the cache response objects
diff --git a/docs/components/async/AMQP.md b/docs/components/async/AMQP.md
new file mode 100644
index 000000000..41ecace1c
--- /dev/null
+++ b/docs/components/async/AMQP.md
@@ -0,0 +1,11 @@
+# AMQP
+
+The AMQP component allows users to construct consumers for AMQP-based queues. It also provides helper functions for working with consumed messages and the `async.Message` abstraction. The consumer supports JSON and Protobuf-encoded messages.
+
+The supported [exchange types](https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchanges) are four; *direct*, *fanout*, *topic* and *header*.
+
+Users can configure the incoming messages buffer size, the connection timeout, whether rejected message should be requeued, as well as provide custom exchange-queue [bindings](https://www.rabbitmq.com/tutorials/amqp-concepts.html#bindings).
+
+The AMQP consumer component is powered by the battle-tested [`streadway/amqp`](https://www.rabbitmq.com/tutorials/amqp-concepts.html#bindings) package. In the [examples](/examples/amqp/amqp.go) folder you can see the component in action.
+
+As with all Patron components, tracing capabilities are included out of the box.
diff --git a/docs/components/async/AWSSQS.md b/docs/components/async/AWSSQS.md
new file mode 100644
index 000000000..56b0f20dd
--- /dev/null
+++ b/docs/components/async/AWSSQS.md
@@ -0,0 +1,18 @@
+# AWS SQS
+
+The SQS component allows users to construct SQS consumers and handle messages under the `async.Message` abstraction. It supports JSON and Protobuf-encoded messages.
+
+The package collects Prometheus metrics regarding the queue usage. These metrics are about the message age, the queue size, the total number of messages, as well as how many of them were delayed or not visible (in flight).
+
+Users can configure
+
+- the maximum number of messages to [fetch at once](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ReceiveMessage.html)
+- the use of [short- or long-polling](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-short-and-long-polling.html)
+- the wait time for the long-polling mechanism
+- the message [visibility timeout](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html)
+- the buffer size for consuming messages concurrently
+- the interval at which stats are collected
+
+The component utilizes the official [AWS SDK for Go](http://github.com/aws/aws-sdk-go/); to get a head start you can go ahead and take a look at the [sqs example](/examples/sqs/main.go) for a hands-on demonstration of the SQS package in the context of collaborating Patron components.
+
+As with all Patron components, tracing capabilities are included out of the box.
diff --git a/docs/components/async/Async.md b/docs/components/async/Async.md
new file mode 100644
index 000000000..b8c78e7f7
--- /dev/null
+++ b/docs/components/async/Async.md
@@ -0,0 +1,62 @@
+# Async
+
+The component is responsible setting up a consumer using the consumer factory, fetching messages from the underlying system and handling the processing of the messages. In case of success the component acknowledges the message and moves to the next. When a message fails to be processed the component will execute the failure strategy setup. The component has also setup logging, capturing of metrics and distributed tracing.
+
+The component makes use of the `Builder` pattern, and expects a consumer factory and a processor function but also provides additional setup methods for the failure strategy, retries, etc.
+
+## Consumer and Factory
+
+The component uses the consumer in order to get messages from the Message Broker/Stream.  
+The concrete implementation follows the interface:
+
+```go
+// Consumer interface which every specific consumer has to implement.
+type Consumer interface {
+    Consume(context.Context) (<-chan Message, <-chan error, error)
+    Close() error
+}
+```
+
+The component accepts a factory in order to be able to recreate the consumer when there is need for it. The implementation follows the interface.
+
+```go
+// ConsumerFactory interface for creating consumers.
+type ConsumerFactory interface {
+    Create() (Consumer, error)
+}
+```
+
+## Processor function
+
+The actual processing of the function that needs to be provided is following the type:
+
+```go
+// ProcessorFunc definition of a async processor.
+type ProcessorFunc func(Message) error
+```
+
+It accepts a `Message` and returns either a nil for success or an error in order to be handled by the failure strategy.
+
+## Message
+
+The messages of the component should follow the interface:
+
+```go
+type Message interface {
+    Context() context.Context
+    Decode(v interface{}) error
+    Ack() error
+    Nack() error
+    Source() string
+    Payload() []byte
+}
+```
+
+## Failure Strategy
+
+The failure strategy defines how the system will behave during the processing of a message.  
+The following strategies are available:
+
+- `NackExitStrategy` does not acknowledge the message and exits the application on error
+- `NackStrategy` does not acknowledge the message, leaving it for reprocessing, and continues
+- `AckStrategy` acknowledges message and continues
diff --git a/docs/components/async/Kafka.md b/docs/components/async/Kafka.md
new file mode 100644
index 000000000..72dcd96d9
--- /dev/null
+++ b/docs/components/async/Kafka.md
@@ -0,0 +1,11 @@
+# Kafka Consumer
+
+The package contains two sub-packages:
+
+- `simple` which connects to each partition and consumes messages from each partition independently
+- `group` which uses consumer groups in order to get messages
+
+Both of the packages contain the factory and consumer implementation.
+
+There is a special feature in the simple package which allows the consumer to go back a specific amount of time in each partition.  
+This allows us to consume the messages from an approximate time onwards.
diff --git a/docs/components/gRPC.md b/docs/components/gRPC.md
new file mode 100644
index 000000000..87554bf18
--- /dev/null
+++ b/docs/components/gRPC.md
@@ -0,0 +1,10 @@
+# gRPC
+
+The gRPC component can be used to create a gRPC server. 
+To enable observability, it injects unary and stream interceptors.
+
+As the server implements the Patron `component` interface, it also handles graceful shutdown via the passed context.
+
+Setting up a gRPC component is done via the Builder (which follows the builder pattern), and supports various configuration values in the form of the `grpc.ServerOption` struct during setup.
+
+Check out the [examples/](/examples) folder for an hands-on tutorial on setting up a server and working with gRPC in Patron.
\ No newline at end of file
diff --git a/docs/observability/DistributedTracing.md b/docs/observability/DistributedTracing.md
new file mode 100644
index 000000000..07d9ccdd7
--- /dev/null
+++ b/docs/observability/DistributedTracing.md
@@ -0,0 +1,30 @@
+# Distributed Tracing
+
+Modern distributed software architectures (such as microservices) enable team autonomy and provide enormous scaling capabilities.
+
+At the same time, they introduce new issues with debugging and monitoring of applications; for example it can be extremely hard to diagnose why a request spanning multiple microservices is sometimes slower than usual, or outright fails.
+
+In many cases, [distributed tracing](https://opentracing.io/docs/overview/what-is-tracing/) can help pinpoint where failures occur and what causes poor performance.
+
+Implementing distributed tracing means adding instrumentation to your application code; in the context of multiple microservices owned by different teams, this can pose a big challenge.
+
+One of Patron's goals is to enable uniformity between microservices and allow end-users to focus on their application code. For this reason, *all* of Patron's components and clients contain built-in tracing, from SQL transactions, to asynchronous producing and consuming of messages, HTTP and gRPC calls and caching mechanisms, distributed tracing is ubiquitous throughout Patron.
+
+To see this in action, one can refer to the `examples/` folder; the examples built a chain of seven services, showcasing the majority of Patron's components and clients. The entrypoint is the `start_processing.sh` script. 
+
+```
+$ cd patron/examples
+$ docker-compose up -d
+$ go run http/main.go &
+$ go run kafka/main.go &
+$ go run amqp/main.go & 
+$ go run grpc/main.go &
+$ go run http-cache/main.go &
+$ go run http-sec/main.go &
+$ go run sqs/main.go &
+$ ./start_processing.sh
+``` 
+
+After running these commands, you can visit the Jaeger client at `localhost:16686/search` and see how you can make use of distributed tracing to debug and optimize your code in complex, distributed systems.
+
+We make use of the battle-tested OpenTracing specification and client, a CNCF project used in production by many tech giants. If you wish to better understand how Distributed Tracing works, you can refer to the official [OpenTracing docs](https://opentracing.io/docs/overview/), read about [spans](https://opentracing.io/docs/overview/spans/) which make up the primary building block of a distributed trace, see how spans work in a [concurrent system](https://opentracing.io/docs/overview/scopes-and-threading/), as well as how spans are [injected and extracted](https://opentracing.io/docs/overview/inject-extract/) to and from carriers.
diff --git a/docs/observability/Logging.md b/docs/observability/Logging.md
new file mode 100644
index 000000000..c848bdfc3
--- /dev/null
+++ b/docs/observability/Logging.md
@@ -0,0 +1,87 @@
+# Logging
+
+The log package is designed to be a leveled logger with field support.
+
+The log package defines the logger interface and a factory function type that needs to be implemented in order to set up the logging in this framework.
+
+```go
+    func Setup(l Logger) error
+```
+
+`If the setup is omitted the package will not setup any logging!`
+
+From there logging is as simple as
+
+```go
+  log.Info("Hello world!")
+```
+
+The implementations should support the following log levels:
+
+- Debug, which should log the message with debug level
+- Info, which should log the message with info level
+- Warn, which should log the message with warn level
+- Error, which should log the message with error level
+- Panic, which should log the message with panic level and panics
+- Fatal, which should log the message with fatal level and terminates the application
+
+The first four (Debug, Info, Warn and Error) give the opportunity to differentiate the messages based on severity. The last two (Panic and Fatal) do the same and do additional actions (panic and termination).
+
+The package supports fields, which are logged along with the message, to augment the information further to ease querying in the log management system.
+
+## Logger Interface
+
+The existing 
+
+```go
+type Logger interface {
+    Sub(map[string]interface{}) Logger
+    Fatal(...interface{})
+    Fatalf(string, ...interface{})
+    Panic(...interface{})
+    Panicf(string, ...interface{})
+    Error(...interface{})
+    Errorf(string, ...interface{})
+    Warn(...interface{})
+    Warnf(string, ...interface{})
+    Info(...interface{})
+    Infof(string, ...interface{})
+    Debug(...interface{})
+    Debugf(string, ...interface{})
+    Level() Level
+}
+```
+
+In order to be consistent with the design, the implementation of the `Fatal(f)` has to terminate the application with an error and the `Panic(f)` needs to panic.
+The `Sub` function should return a new logger instance which is a clone of the current one, along with the additional fields applied.
+
+## Context Logging
+
+Logs can be associated with some contextual data e.g. a request id. Every line logged should contain this id thus grouping the logs together. This is achieved with the usage of the context package as demonstrated below:
+
+```go
+ctx := log.WithContext(r.Context(), log.Sub(map[string]interface{}{"requestID": uuid.New().String()}))
+```
+
+The context travels through the code as an argument and can be acquired as follows:
+
+```go
+logger:=log.FromContext(ctx)
+logger.Infof("request processed")
+```
+
+Benchmarks are provided to show the performance of this.
+
+`Every provided component creates a context logger which is then propagated in the context`
+
+## Correlation ID propagation
+
+Patron receives and propagates a correlation ID. Much like the distributed tracing id, the correlation id is receiver on the entry points of the service e.g. HTTP, Kafka, etc. and is propagated via the provided clients. In case no correlation ID has been received, a new one is created.  
+The ID is usually received and sent via a header with key `X-Correlation-Id`.
+
+## Provided logger implementations
+
+The following implementations are provided as sub-packages
+
+- zerolog, which supports the excellent [zerolog](https://github.com/rs/zerolog) package, which provides structured logging
+- std, which wraps the standard log package by implementing the `Logger` interface and provides textual logging
\ No newline at end of file
diff --git a/docs/observability/Observability.md b/docs/observability/Observability.md
new file mode 100644
index 000000000..c4979c38c
--- /dev/null
+++ b/docs/observability/Observability.md
@@ -0,0 +1,10 @@
+# Observability
+
+## Metrics and Tracing
+
+Tracing and metrics are provided by Jaeger's implementation of the OpenTracing project and Prometheus.
+Every component has been integrated with the above library and produces traces and metrics.
+Metrics are can be scraped via the default HTTP component at the `/metrics` route for Prometheus.  
+Traces will be sent to a Jaeger agent, which can be setup through environment variables mentioned in the config section.    
+Sane defaults are applied for making the use easy.  
+The `component` and `client` packages implement capturing and propagating of metrics and traces.
\ No newline at end of file
diff --git a/docs/other/CLI.md b/docs/other/CLI.md
new file mode 100644
index 000000000..eaa1abe56
--- /dev/null
+++ b/docs/other/CLI.md
@@ -0,0 +1,20 @@
+# CLI
+
+The framework supplies a CLI in order to simplify repository generation with the following features:
+
+- git repository creation
+- cmd folder and main.go creation with build version support (`go build -ldflags '-X main.version=1.0.0' main.go`)
+- go module support and vendoring
+- Dockerfile with version support (`docker build --build-arg version=1.0.0`)
+
+The latest version can be installed with
+
+```go
+go get github.com/beatlabs/patron/cmd/patron
+```
+
+Below is an example of a service created with the cli that has a module name `github.com/beatlabs/test` and will be created in the test folder in the current directory.
+
+```go
+patron -m "github.com/beatlabs/test" -p "test"
+```
\ No newline at end of file
diff --git a/docs/other/Caching.md b/docs/other/Caching.md
new file mode 100644
index 000000000..60f341678
--- /dev/null
+++ b/docs/other/Caching.md
@@ -0,0 +1,33 @@
+# Caching
+
+The caching package contains two interfaces for caching implementations,  
+one simple and one supporting setting TTL.
+
+## Interfaces
+
+Cache interface:
+
+```go
+type Cache interface {
+    Get(key string) (interface{}, bool, error)
+    Purge() error
+    Remove(key string) error
+    Set(key string, value interface{}) error
+}
+```
+
+TTLCache interface:
+
+```go
+type TTLCache interface {
+    Cache
+    SetTTL(key string, value interface{}, ttl time.Duration) error
+}
+```
+
+## Implementations
+
+Subpackages contain concrete implementations of the aforementioned interfaces:
+
+- `lru` which contains an in-memory LRU cache implementation of the `Cache` interface
+- `redis` which contains a Redis-based cache implementation of the `TTLCache` interface
\ No newline at end of file
diff --git a/docs/other/Encoding.md b/docs/other/Encoding.md
new file mode 100644
index 000000000..6f5a6f208
--- /dev/null
+++ b/docs/other/Encoding.md
@@ -0,0 +1,19 @@
+# Encoding
+
+The encoding package contains the following functions definitions that are used throughout the framework.
+
+```go
+// DecodeFunc function definition of a JSON decoding function.
+type DecodeFunc func(data io.Reader, v interface{}) error
+
+// DecodeRawFunc function definition of a JSON decoding function from a byte slice.
+type DecodeRawFunc func(data []byte, v interface{}) error
+
+// EncodeFunc function definition of a JSON encoding function.
+type EncodeFunc func(v interface{}) ([]byte, error) 
+```
+
+The following sub-packages are provided:
+
+- `json` which contains implementations of the encoding functions
+- `protobuf` which contains implementations of the encoding functions
\ No newline at end of file
diff --git a/docs/other/Errors.md b/docs/other/Errors.md
new file mode 100644
index 000000000..83c2f73a4
--- /dev/null
+++ b/docs/other/Errors.md
@@ -0,0 +1,5 @@
+# Errors
+
+## Aggregate function
+
+The function allows for accepting a list of errors and aggregate them into one by concatenating the individual error messages.
\ No newline at end of file
diff --git a/docs/other/Reliability.md b/docs/other/Reliability.md
new file mode 100644
index 000000000..8b2fe2de8
--- /dev/null
+++ b/docs/other/Reliability.md
@@ -0,0 +1,34 @@
+# Reliability
+
+The reliability package contains the following implementations:
+
+- Circuit Breaker Pattern
+- Retry Pattern
+
+## Circuit Breaker Pattern
+
+The circuit breaker supports a half-open state which allows to probe for successful responses in order to close the circuit again.  
+Every aspect of the circuit breaker is configurable via its settings.
+
+The action to be executed has the signature type of:
+
+```go
+type Action func() (interface{}, error)
+```
+
+and its following aspects can be configured:
+
+- The threshold for the circuit to open
+- The timeout after which we set the state to half-open and allow retries
+- The threshold of retry successes which returns the state to open
+- The threshold of how many retry executions are allowed when the status is half-open
+
+## Retry Pattern
+
+Retry accepts a function with the following signature:
+
+```go
+type Action func() (interface{}, error)
+``` 
+
+and retries the action for a configurable amount of retries with a specific fixed time interval between them.
\ No newline at end of file
diff --git a/examples/README.md b/examples/README.md
deleted file mode 100644
index 44c597c8c..000000000
--- a/examples/README.md
+++ /dev/null
@@ -1,88 +0,0 @@
-# example
-
-The following example will show off the usage of patron involving all components implemented.
-The processing will be kicked of by sending a request to the HTTP component. The flow then will be the following:
-
-## Service 1
-
-- Accepts HTTP JSON request from curl
-- Sends HTTP protobuf request to service 2
-- Responds to curl
-
-## Service 2
-
-- Accepts HTTP protobuf request from service 1
-- Publishes a message to Kafka
-- Responds to service 1
-
-## Service 3
-
-- Consumes a message from service 2 via Kafka
-- Publishes a message to AMQP
-
-## Service 4
-
-- Consumes a message from service 3 via AMQP
-- Publishes a message to AWS SNS linked to an SQS queue
-- Publishes a message to AWS SQS directly
-
-## Service 5
-
-- Consumes a message from service 4 via AWS SQS
-- Send a rpc request to gRPC server of service 5 and logs response
-
-## Service 6
-
-- Receives a request from service 5
-- Responds to service 5
-
-## Service 7
-
-- receives a raw http request
-- returns the 7th minute unix interval for the current server time
-- http makes a request to the http-cache service
-- the http-cache service responds with the 7th minute interval information
-
-Since tracing instrumentation is in place we can observer the flow in Jaeger.
-
-## Prerequisites
-
-- Docker
-- Docker compose
-
-## Setting up environment
-
-To run the full example we need to start [jaeger](https://www.jaegertracing.io/) and [prometheus](https://prometheus.io/). We can startup both of them using docker-compose with the following command.
-
-```shell
-docker-compose up -d
-```
-
-To tear down the above just:
-
-```shell
-docker-compose down
-```
-
-## Running the examples
-
-When the services started with Docker Compose are ready, you will need to start each of the seven
-examples in order:
-
-```shell
-go run examples/http-cache/main.go
-go run examples/http/main.go
-go run examples/kafka/main.go
-go run examples/amqp/main.go
-go run examples/sns/main.go
-go run examples/sqs/main.go
-go run examples/grpc/main.go examples/grpc/greeter.pb.go
-```
-
-and then send a sample request:
-
-```shell
-./start_processing.sh
-```
-
-After that head over to [jaeger](http://localhost:16686/search) and [prometheus](http://localhost:9090/graph).
diff --git a/examples/amqp/main.go b/examples/amqp/main.go
index 94121d5c3..85577a823 100644
--- a/examples/amqp/main.go
+++ b/examples/amqp/main.go
@@ -2,26 +2,48 @@ package main
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"os"
 	"time"
 
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/credentials"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/sns"
+	"github.com/aws/aws-sdk-go/service/sns/snsiface"
+	"github.com/aws/aws-sdk-go/service/sqs"
+	"github.com/aws/aws-sdk-go/service/sqs/sqsiface"
 	"github.com/beatlabs/patron"
-	"github.com/beatlabs/patron/client/amqp"
+	patronsns "github.com/beatlabs/patron/client/sns"
+	patronsqs "github.com/beatlabs/patron/client/sqs"
 	"github.com/beatlabs/patron/component/async"
-	"github.com/beatlabs/patron/component/async/kafka"
-	"github.com/beatlabs/patron/component/async/kafka/group"
+	"github.com/beatlabs/patron/component/async/amqp"
 	"github.com/beatlabs/patron/encoding/json"
 	"github.com/beatlabs/patron/examples"
 	"github.com/beatlabs/patron/log"
+	oamqp "github.com/streadway/amqp"
 )
 
 const (
-	amqpURL      = "amqp://guest:guest@localhost:5672/"
-	amqpExchange = "patron"
-	kafkaTopic   = "patron-topic"
-	kafkaGroup   = "patron-group"
-	kafkaBroker  = "localhost:9092"
+	amqpURL          = "amqp://guest:guest@localhost:5672/"
+	amqpQueue        = "patron"
+	amqpExchangeName = "patron"
+	amqpExchangeType = oamqp.ExchangeFanout
+
+	// Shared AWS config
+	awsRegion = "eu-west-1"
+	awsID     = "test"
+	awsSecret = "test"
+	awsToken  = "token"
+
+	// SQS config
+	awsSQSEndpoint = "http://localhost:4566"
+	awsSQSQueue    = "patron"
+
+	// SNS config
+	awsSNSEndpoint = "http://localhost:4566"
+	awsSNSTopic    = "patron-topic"
 )
 
 func init() {
@@ -35,7 +57,7 @@ func init() {
 		fmt.Printf("failed to set sampler env vars: %v", err)
 		os.Exit(1)
 	}
-	err = os.Setenv("PATRON_HTTP_DEFAULT_PORT", "50002")
+	err = os.Setenv("PATRON_HTTP_DEFAULT_PORT", "50003")
 	if err != nil {
 		fmt.Printf("failed to set default patron port env vars: %v", err)
 		os.Exit(1)
@@ -46,56 +68,156 @@ func main() {
 	name := "amqp"
 	version := "1.0.0"
 
-	service, err := patron.New(name, version, patron.TextLogger())
+	service, err := patron.New(name, version)
 	if err != nil {
 		fmt.Printf("failed to set up service: %v", err)
 		os.Exit(1)
 	}
 
-	kafkaCmp, err := newKafkaComponent(name, kafkaBroker, kafkaTopic, kafkaGroup, amqpURL, amqpExchange)
+	// Programmatically create an empty SQS queue for the sake of the example
+	sqsAPI := sqs.New(getAWSSession(awsSQSEndpoint))
+	sqsQueueURL, err := createSQSQueue(sqsAPI)
+	if err != nil {
+		log.Fatalf("failed to create sqs queue: %v", err)
+	}
+
+	// Programmatically create an SNS topic for the sake of the example
+	snsAPI := sns.New(getAWSSession(awsSNSEndpoint))
+	snsTopicArn, err := createSNSTopic(snsAPI)
+	if err != nil {
+		log.Fatalf("failed to create sns topic: %v", err)
+	}
+
+	// Route the SNS topic to the SQS queue, so that any message received on the SNS topic
+	// will be automatically sent to the SQS queue.
+	err = routeSNSTOpicToSQSQueue(snsAPI, sqsQueueURL, snsTopicArn)
+	if err != nil {
+		log.Fatalf("failed to route sns to sqs: %v", err)
+	}
+
+	// Create an SNS publisher
+	snsPub, err := patronsns.NewPublisher(snsAPI)
+	if err != nil {
+		log.Fatalf("failed to create sns publisher: %v", err)
+	}
+
+	// Create an SQS publisher
+	sqsPub, err := patronsqs.NewPublisher(sqsAPI)
+	if err != nil {
+		log.Fatalf("failed to create sqs publisher: %v", err)
+	}
+
+	// Initialise the AMQP component
+	amqpCmp, err := newAmqpComponent(
+		amqpURL,
+		amqpQueue,
+		amqpExchangeName,
+		amqpExchangeType,
+		[]string{"bind.one.*", "bind.two.*"},
+		snsTopicArn,
+		snsPub,
+		sqsPub,
+		sqsQueueURL,
+	)
 	if err != nil {
 		log.Fatalf("failed to create processor %v", err)
 	}
 
 	ctx := context.Background()
-	err = service.WithComponents(kafkaCmp.cmp).Run(ctx)
+	err = service.WithComponents(amqpCmp.cmp).Run(ctx)
 	if err != nil {
 		log.Fatalf("failed to create and run service %v", err)
 	}
 }
 
-type kafkaComponent struct {
-	cmp patron.Component
-	pub amqp.Publisher
+func getAWSSession(endpoint string) *session.Session {
+	return session.Must(
+		session.NewSession(
+			&aws.Config{
+				Region:      aws.String(awsRegion),
+				Credentials: credentials.NewStaticCredentials(awsID, awsSecret, awsToken),
+			},
+			&aws.Config{Endpoint: aws.String(endpoint)},
+		),
+	)
 }
 
-func newKafkaComponent(name, broker, topic, groupID, amqpURL, amqpExc string) (*kafkaComponent, error) {
-	kafkaCmp := kafkaComponent{}
+func createSQSQueue(api sqsiface.SQSAPI) (string, error) {
+	out, err := api.CreateQueue(&sqs.CreateQueueInput{
+		QueueName: aws.String(awsSQSQueue),
+	})
+	if out.QueueUrl == nil {
+		return "", errors.New("could not create the queue")
+	}
+	return *out.QueueUrl, err
+}
 
-	cf, err := group.New(name, groupID, []string{topic}, []string{broker}, kafka.Decoder(json.DecodeRaw))
+func createSNSTopic(snsAPI snsiface.SNSAPI) (string, error) {
+	out, err := snsAPI.CreateTopic(&sns.CreateTopicInput{
+		Name: aws.String(awsSNSTopic),
+	})
+	if err != nil {
+		return "", err
+	}
+	return *out.TopicArn, nil
+}
+
+func routeSNSTOpicToSQSQueue(snsAPI snsiface.SNSAPI, sqsQueueArn, topicArn string) error {
+	_, err := snsAPI.Subscribe(&sns.SubscribeInput{
+		Protocol: aws.String("sqs"),
+		TopicArn: aws.String(topicArn),
+		Endpoint: aws.String(sqsQueueArn),
+		Attributes: map[string]*string{
+			// Set the RawMessageDelivery to "true" in order to be able to pass the MessageAttributes from SNS
+			// to SQS, and therefore to propagate the trace.
+			// See https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html for more information.
+			"RawMessageDelivery": aws.String("true"),
+		},
+	})
+
+	return err
+}
+
+type amqpComponent struct {
+	cmp         patron.Component
+	snsTopicArn string
+	snsPub      patronsns.Publisher
+	sqsPub      patronsqs.Publisher
+	sqsQueueURL string
+}
+
+func newAmqpComponent(url, queue, exchangeName, exchangeType string, bindings []string, snsTopicArn string, snsPub patronsns.Publisher,
+	sqsPub patronsqs.Publisher, sqsQueueURL string) (*amqpComponent, error) {
+	amqpCmp := amqpComponent{
+		snsTopicArn: snsTopicArn,
+		snsPub:      snsPub,
+		sqsPub:      sqsPub,
+		sqsQueueURL: sqsQueueURL,
+	}
+
+	exchange, err := amqp.NewExchange(exchangeName, exchangeType)
 	if err != nil {
 		return nil, err
 	}
 
-	cmp, err := async.New("kafka-cmp", cf, kafkaCmp.Process).
-		WithRetries(10).
-		WithRetryWait(5 * time.Second).
-		Create()
+	cf, err := amqp.New(url, queue, *exchange, amqp.Bindings(bindings...))
 	if err != nil {
 		return nil, err
 	}
-	kafkaCmp.cmp = cmp
 
-	pub, err := amqp.NewPublisher(amqpURL, amqpExc)
+	cmp, err := async.New("amqp-cmp", cf, amqpCmp.Process).
+		WithRetries(10).
+		WithRetryWait(10 * time.Second).
+		Create()
 	if err != nil {
 		return nil, err
 	}
-	kafkaCmp.pub = pub
+	amqpCmp.cmp = cmp
 
-	return &kafkaCmp, nil
+	return &amqpCmp, nil
 }
 
-func (kc *kafkaComponent) Process(msg async.Message) error {
+func (ac *amqpComponent) Process(msg async.Message) error {
 	var u examples.User
 
 	err := msg.Decode(&u)
@@ -103,14 +225,32 @@ func (kc *kafkaComponent) Process(msg async.Message) error {
 		return err
 	}
 
-	amqpMsg, err := amqp.NewProtobufMessage(&u)
+	payload, err := json.Encode(u)
 	if err != nil {
 		return err
 	}
 
-	err = kc.pub.Publish(msg.Context(), amqpMsg)
+	// Create a new SNS message and publish it
+	snsMsg, err := patronsns.NewMessageBuilder().
+		Message(string(payload)).
+		TopicArn(ac.snsTopicArn).
+		Build()
 	if err != nil {
-		return err
+		return fmt.Errorf("failed to create message: %v", err)
+	}
+	_, err = ac.snsPub.Publish(msg.Context(), *snsMsg)
+	if err != nil {
+		return fmt.Errorf("failed to publish message to SNS: %v", err)
+	}
+
+	// Create a new SQS message and publish it
+	sqsMsg, err := patronsqs.NewMessageBuilder().
+		Body(string(payload)).
+		QueueURL(ac.sqsQueueURL).
+		Build()
+	_, err = ac.sqsPub.Publish(msg.Context(), *sqsMsg)
+	if err != nil {
+		return fmt.Errorf("failed to publish message to SQS: %v", err)
 	}
 
 	log.FromContext(msg.Context()).Infof("request processed: %s %s", u.GetFirstname(), u.GetLastname())
diff --git a/examples/http-sec/main.go b/examples/http-sec/main.go
new file mode 100644
index 000000000..2e8fdcf30
--- /dev/null
+++ b/examples/http-sec/main.go
@@ -0,0 +1,132 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"os"
+	"time"
+
+	"github.com/beatlabs/patron"
+	clienthttp "github.com/beatlabs/patron/client/http"
+	"github.com/beatlabs/patron/client/kafka"
+	patronhttp "github.com/beatlabs/patron/component/http"
+	"github.com/beatlabs/patron/component/http/auth/apikey"
+	"github.com/beatlabs/patron/examples"
+	"github.com/beatlabs/patron/log"
+)
+
+const (
+	kafkaTopic  = "patron-topic"
+	kafkaBroker = "localhost:9092"
+)
+
+func init() {
+	err := os.Setenv("PATRON_LOG_LEVEL", "debug")
+	if err != nil {
+		fmt.Printf("failed to set log level env var: %v", err)
+		os.Exit(1)
+	}
+	err = os.Setenv("PATRON_JAEGER_SAMPLER_PARAM", "1.0")
+	if err != nil {
+		fmt.Printf("failed to set sampler env vars: %v", err)
+		os.Exit(1)
+	}
+
+	err = os.Setenv("PATRON_HTTP_DEFAULT_PORT", "50001")
+	if err != nil {
+		fmt.Printf("failed to set default patron port env vars: %v", err)
+		os.Exit(1)
+	}
+}
+
+func main() {
+	name := "http-sec"
+	version := "1.0.0"
+
+	service, err := patron.New(name, version, patron.LogFields(map[string]interface{}{"env": "staging"}))
+	if err != nil {
+		fmt.Printf("failed to set up service: %v", err)
+		os.Exit(1)
+	}
+
+	httpCmp, err := newHTTPComponent(kafkaBroker, kafkaTopic, "http://localhost:50000/kafka")
+	if err != nil {
+		log.Fatalf("failed to create processor %v", err)
+	}
+
+	auth, err := apikey.New(&apiKeyValidator{validKey: "123456"})
+	if err != nil {
+		log.Fatalf("failed to create authenticator %v", err)
+	}
+
+	routesBuilder := patronhttp.NewRoutesBuilder().
+		Append(patronhttp.NewGetRouteBuilder("/", httpCmp.kafkaHandler).WithTrace().WithAuth(auth))
+
+	ctx := context.Background()
+	err = service.WithRoutesBuilder(routesBuilder).Run(ctx)
+	if err != nil {
+		log.Fatalf("failed to create and run service %v", err)
+	}
+}
+
+type httpComponent struct {
+	prd   kafka.Producer
+	topic string
+}
+
+func newHTTPComponent(kafkaBroker, topic, _ string) (*httpComponent, error) {
+	prd, chErr, err := kafka.NewBuilder([]string{kafkaBroker}).CreateAsync()
+	if err != nil {
+		return nil, err
+	}
+	go func() {
+		for {
+			err := <-chErr
+			log.Errorf("error producing Kafka message: %v", err)
+		}
+	}()
+	return &httpComponent{prd: prd, topic: topic}, nil
+}
+
+func (hc *httpComponent) kafkaHandler(ctx context.Context, req *patronhttp.Request) (*patronhttp.Response, error) {
+	var u examples.User
+	err := req.Decode(&u)
+	if err != nil {
+		return nil, fmt.Errorf("failed to decode message: %w", err)
+	}
+
+	googleReq, err := http.NewRequest("GET", "https://www.google.com", nil)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create request for www.google.com: %w", err)
+	}
+	cl, err := clienthttp.New(clienthttp.Timeout(5 * time.Second))
+	if err != nil {
+		return nil, err
+	}
+	_, err = cl.Do(ctx, googleReq)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get www.google.com: %w", err)
+	}
+
+	kafkaMsg := kafka.NewMessage(hc.topic, &u)
+
+	err = hc.prd.Send(ctx, kafkaMsg)
+	if err != nil {
+		return nil, err
+	}
+
+	log.FromContext(ctx).Infof("request processed: %s %s", u.GetFirstname(), u.GetLastname())
+	return nil, nil
+}
+
+type apiKeyValidator struct {
+	validKey string
+}
+
+func (av apiKeyValidator) Validate(key string) (bool, error) {
+	if key == av.validKey {
+		return true, nil
+	}
+	return false, nil
+}
diff --git a/examples/kafka/main.go b/examples/kafka/main.go
index e5423afcc..c51155255 100644
--- a/examples/kafka/main.go
+++ b/examples/kafka/main.go
@@ -3,22 +3,25 @@ package main
 import (
 	"context"
 	"fmt"
-	"net/http"
 	"os"
 	"time"
 
 	"github.com/beatlabs/patron"
-	clienthttp "github.com/beatlabs/patron/client/http"
-	"github.com/beatlabs/patron/client/kafka"
-	patronhttp "github.com/beatlabs/patron/component/http"
-	"github.com/beatlabs/patron/component/http/auth/apikey"
+	"github.com/beatlabs/patron/client/amqp"
+	"github.com/beatlabs/patron/component/async"
+	"github.com/beatlabs/patron/component/async/kafka"
+	"github.com/beatlabs/patron/component/async/kafka/group"
+	"github.com/beatlabs/patron/encoding/json"
 	"github.com/beatlabs/patron/examples"
 	"github.com/beatlabs/patron/log"
 )
 
 const (
-	kafkaTopic  = "patron-topic"
-	kafkaBroker = "localhost:9092"
+	amqpURL      = "amqp://guest:guest@localhost:5672/"
+	amqpExchange = "patron"
+	kafkaTopic   = "patron-topic"
+	kafkaGroup   = "patron-group"
+	kafkaBroker  = "localhost:9092"
 )
 
 func init() {
@@ -32,8 +35,7 @@ func init() {
 		fmt.Printf("failed to set sampler env vars: %v", err)
 		os.Exit(1)
 	}
-
-	err = os.Setenv("PATRON_HTTP_DEFAULT_PORT", "50001")
+	err = os.Setenv("PATRON_HTTP_DEFAULT_PORT", "50002")
 	if err != nil {
 		fmt.Printf("failed to set default patron port env vars: %v", err)
 		os.Exit(1)
@@ -44,89 +46,73 @@ func main() {
 	name := "kafka"
 	version := "1.0.0"
 
-	service, err := patron.New(name, version, patron.LogFields(map[string]interface{}{"env": "staging"}))
+	service, err := patron.New(name, version, patron.TextLogger())
 	if err != nil {
 		fmt.Printf("failed to set up service: %v", err)
 		os.Exit(1)
 	}
 
-	httpCmp, err := newHTTPComponent(kafkaBroker, kafkaTopic, "http://localhost:50000/kafka")
+	kafkaCmp, err := newKafkaComponent(name, kafkaBroker, kafkaTopic, kafkaGroup, amqpURL, amqpExchange)
 	if err != nil {
 		log.Fatalf("failed to create processor %v", err)
 	}
 
-	auth, err := apikey.New(&apiKeyValidator{validKey: "123456"})
-	if err != nil {
-		log.Fatalf("failed to create authenticator %v", err)
-	}
-
-	routesBuilder := patronhttp.NewRoutesBuilder().
-		Append(patronhttp.NewGetRouteBuilder("/", httpCmp.kafkaHandler).WithTrace().WithAuth(auth))
-
 	ctx := context.Background()
-	err = service.WithRoutesBuilder(routesBuilder).Run(ctx)
+	err = service.WithComponents(kafkaCmp.cmp).Run(ctx)
 	if err != nil {
 		log.Fatalf("failed to create and run service %v", err)
 	}
 }
 
-type httpComponent struct {
-	prd   kafka.Producer
-	topic string
+type kafkaComponent struct {
+	cmp patron.Component
+	pub amqp.Publisher
 }
 
-func newHTTPComponent(kafkaBroker, topic, _ string) (*httpComponent, error) {
-	prd, chErr, err := kafka.NewBuilder([]string{kafkaBroker}).CreateAsync()
+func newKafkaComponent(name, broker, topic, groupID, amqpURL, amqpExc string) (*kafkaComponent, error) {
+	kafkaCmp := kafkaComponent{}
+
+	cf, err := group.New(name, groupID, []string{topic}, []string{broker}, kafka.Decoder(json.DecodeRaw))
 	if err != nil {
 		return nil, err
 	}
-	go func() {
-		for {
-			err := <-chErr
-			log.Errorf("error producing Kafka message: %v", err)
-		}
-	}()
-	return &httpComponent{prd: prd, topic: topic}, nil
-}
 
-func (hc *httpComponent) kafkaHandler(ctx context.Context, req *patronhttp.Request) (*patronhttp.Response, error) {
-	var u examples.User
-	err := req.Decode(&u)
+	cmp, err := async.New("kafka-cmp", cf, kafkaCmp.Process).
+		WithRetries(10).
+		WithRetryWait(5 * time.Second).
+		Create()
 	if err != nil {
-		return nil, fmt.Errorf("failed to decode message: %w", err)
+		return nil, err
 	}
+	kafkaCmp.cmp = cmp
 
-	googleReq, err := http.NewRequest("GET", "https://www.google.com", nil)
-	if err != nil {
-		return nil, fmt.Errorf("failed to create request for www.google.com: %w", err)
-	}
-	cl, err := clienthttp.New(clienthttp.Timeout(5 * time.Second))
+	pub, err := amqp.NewPublisher(amqpURL, amqpExc)
 	if err != nil {
 		return nil, err
 	}
-	_, err = cl.Do(ctx, googleReq)
-	if err != nil {
-		return nil, fmt.Errorf("failed to get www.google.com: %w", err)
-	}
+	kafkaCmp.pub = pub
+
+	return &kafkaCmp, nil
+}
 
-	kafkaMsg := kafka.NewMessage(hc.topic, &u)
+func (kc *kafkaComponent) Process(msg async.Message) error {
+	var u examples.User
 
-	err = hc.prd.Send(ctx, kafkaMsg)
+	err := msg.Decode(&u)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
-	log.FromContext(ctx).Infof("request processed: %s %s", u.GetFirstname(), u.GetLastname())
-	return nil, nil
-}
-
-type apiKeyValidator struct {
-	validKey string
-}
+	amqpMsg, err := amqp.NewProtobufMessage(&u)
+	if err != nil {
+		return err
+	}
 
-func (av apiKeyValidator) Validate(key string) (bool, error) {
-	if key == av.validKey {
-		return true, nil
+	err = kc.pub.Publish(msg.Context(), amqpMsg)
+	if err != nil {
+		return err
 	}
-	return false, nil
+
+	log.FromContext(msg.Context()).Infof("request processed: %s %s", u.GetFirstname(), u.GetLastname())
+	return nil
 }
diff --git a/examples/sns/main.go b/examples/sns/main.go
deleted file mode 100644
index 4a89a7222..000000000
--- a/examples/sns/main.go
+++ /dev/null
@@ -1,258 +0,0 @@
-package main
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"os"
-	"time"
-
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/aws/credentials"
-	"github.com/aws/aws-sdk-go/aws/session"
-	"github.com/aws/aws-sdk-go/service/sns"
-	"github.com/aws/aws-sdk-go/service/sns/snsiface"
-	"github.com/aws/aws-sdk-go/service/sqs"
-	"github.com/aws/aws-sdk-go/service/sqs/sqsiface"
-	"github.com/beatlabs/patron"
-	patronsns "github.com/beatlabs/patron/client/sns"
-	patronsqs "github.com/beatlabs/patron/client/sqs"
-	"github.com/beatlabs/patron/component/async"
-	"github.com/beatlabs/patron/component/async/amqp"
-	"github.com/beatlabs/patron/encoding/json"
-	"github.com/beatlabs/patron/examples"
-	"github.com/beatlabs/patron/log"
-	oamqp "github.com/streadway/amqp"
-)
-
-const (
-	amqpURL          = "amqp://guest:guest@localhost:5672/"
-	amqpQueue        = "patron"
-	amqpExchangeName = "patron"
-	amqpExchangeType = oamqp.ExchangeFanout
-
-	// Shared AWS config
-	awsRegion = "eu-west-1"
-	awsID     = "test"
-	awsSecret = "test"
-	awsToken  = "token"
-
-	// SQS config
-	awsSQSEndpoint = "http://localhost:4566"
-	awsSQSQueue    = "patron"
-
-	// SNS config
-	awsSNSEndpoint = "http://localhost:4566"
-	awsSNSTopic    = "patron-topic"
-)
-
-func init() {
-	err := os.Setenv("PATRON_LOG_LEVEL", "debug")
-	if err != nil {
-		fmt.Printf("failed to set log level env var: %v", err)
-		os.Exit(1)
-	}
-	err = os.Setenv("PATRON_JAEGER_SAMPLER_PARAM", "1.0")
-	if err != nil {
-		fmt.Printf("failed to set sampler env vars: %v", err)
-		os.Exit(1)
-	}
-	err = os.Setenv("PATRON_HTTP_DEFAULT_PORT", "50003")
-	if err != nil {
-		fmt.Printf("failed to set default patron port env vars: %v", err)
-		os.Exit(1)
-	}
-}
-
-func main() {
-	name := "sns"
-	version := "1.0.0"
-
-	service, err := patron.New(name, version)
-	if err != nil {
-		fmt.Printf("failed to set up service: %v", err)
-		os.Exit(1)
-	}
-
-	// Programmatically create an empty SQS queue for the sake of the example
-	sqsAPI := sqs.New(getAWSSession(awsSQSEndpoint))
-	sqsQueueURL, err := createSQSQueue(sqsAPI)
-	if err != nil {
-		log.Fatalf("failed to create sqs queue: %v", err)
-	}
-
-	// Programmatically create an SNS topic for the sake of the example
-	snsAPI := sns.New(getAWSSession(awsSNSEndpoint))
-	snsTopicArn, err := createSNSTopic(snsAPI)
-	if err != nil {
-		log.Fatalf("failed to create sns topic: %v", err)
-	}
-
-	// Route the SNS topic to the SQS queue, so that any message received on the SNS topic
-	// will be automatically sent to the SQS queue.
-	err = routeSNSTOpicToSQSQueue(snsAPI, sqsQueueURL, snsTopicArn)
-	if err != nil {
-		log.Fatalf("failed to route sns to sqs: %v", err)
-	}
-
-	// Create an SNS publisher
-	snsPub, err := patronsns.NewPublisher(snsAPI)
-	if err != nil {
-		log.Fatalf("failed to create sns publisher: %v", err)
-	}
-
-	// Create an SQS publisher
-	sqsPub, err := patronsqs.NewPublisher(sqsAPI)
-	if err != nil {
-		log.Fatalf("failed to create sqs publisher: %v", err)
-	}
-
-	// Initialise the AMQP component
-	amqpCmp, err := newAmqpComponent(
-		amqpURL,
-		amqpQueue,
-		amqpExchangeName,
-		amqpExchangeType,
-		[]string{"bind.one.*", "bind.two.*"},
-		snsTopicArn,
-		snsPub,
-		sqsPub,
-		sqsQueueURL,
-	)
-	if err != nil {
-		log.Fatalf("failed to create processor %v", err)
-	}
-
-	ctx := context.Background()
-	err = service.WithComponents(amqpCmp.cmp).Run(ctx)
-	if err != nil {
-		log.Fatalf("failed to create and run service %v", err)
-	}
-}
-
-func getAWSSession(endpoint string) *session.Session {
-	return session.Must(
-		session.NewSession(
-			&aws.Config{
-				Region:      aws.String(awsRegion),
-				Credentials: credentials.NewStaticCredentials(awsID, awsSecret, awsToken),
-			},
-			&aws.Config{Endpoint: aws.String(endpoint)},
-		),
-	)
-}
-
-func createSQSQueue(api sqsiface.SQSAPI) (string, error) {
-	out, err := api.CreateQueue(&sqs.CreateQueueInput{
-		QueueName: aws.String(awsSQSQueue),
-	})
-	if out.QueueUrl == nil {
-		return "", errors.New("could not create the queue")
-	}
-	return *out.QueueUrl, err
-}
-
-func createSNSTopic(snsAPI snsiface.SNSAPI) (string, error) {
-	out, err := snsAPI.CreateTopic(&sns.CreateTopicInput{
-		Name: aws.String(awsSNSTopic),
-	})
-	if err != nil {
-		return "", err
-	}
-	return *out.TopicArn, nil
-}
-
-func routeSNSTOpicToSQSQueue(snsAPI snsiface.SNSAPI, sqsQueueArn, topicArn string) error {
-	_, err := snsAPI.Subscribe(&sns.SubscribeInput{
-		Protocol: aws.String("sqs"),
-		TopicArn: aws.String(topicArn),
-		Endpoint: aws.String(sqsQueueArn),
-		Attributes: map[string]*string{
-			// Set the RawMessageDelivery to "true" in order to be able to pass the MessageAttributes from SNS
-			// to SQS, and therefore to propagate the trace.
-			// See https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html for more information.
-			"RawMessageDelivery": aws.String("true"),
-		},
-	})
-
-	return err
-}
-
-type amqpComponent struct {
-	cmp         patron.Component
-	snsTopicArn string
-	snsPub      patronsns.Publisher
-	sqsPub      patronsqs.Publisher
-	sqsQueueURL string
-}
-
-func newAmqpComponent(url, queue, exchangeName, exchangeType string, bindings []string, snsTopicArn string, snsPub patronsns.Publisher,
-	sqsPub patronsqs.Publisher, sqsQueueURL string) (*amqpComponent, error) {
-	amqpCmp := amqpComponent{
-		snsTopicArn: snsTopicArn,
-		snsPub:      snsPub,
-		sqsPub:      sqsPub,
-		sqsQueueURL: sqsQueueURL,
-	}
-
-	exchange, err := amqp.NewExchange(exchangeName, exchangeType)
-	if err != nil {
-		return nil, err
-	}
-
-	cf, err := amqp.New(url, queue, *exchange, amqp.Bindings(bindings...))
-	if err != nil {
-		return nil, err
-	}
-
-	cmp, err := async.New("amqp-cmp", cf, amqpCmp.Process).
-		WithRetries(10).
-		WithRetryWait(10 * time.Second).
-		Create()
-	if err != nil {
-		return nil, err
-	}
-	amqpCmp.cmp = cmp
-
-	return &amqpCmp, nil
-}
-
-func (ac *amqpComponent) Process(msg async.Message) error {
-	var u examples.User
-
-	err := msg.Decode(&u)
-	if err != nil {
-		return err
-	}
-
-	payload, err := json.Encode(u)
-	if err != nil {
-		return err
-	}
-
-	// Create a new SNS message and publish it
-	snsMsg, err := patronsns.NewMessageBuilder().
-		Message(string(payload)).
-		TopicArn(ac.snsTopicArn).
-		Build()
-	if err != nil {
-		return fmt.Errorf("failed to create message: %v", err)
-	}
-	_, err = ac.snsPub.Publish(msg.Context(), *snsMsg)
-	if err != nil {
-		return fmt.Errorf("failed to publish message to SNS: %v", err)
-	}
-
-	// Create a new SQS message and publish it
-	sqsMsg, err := patronsqs.NewMessageBuilder().
-		Body(string(payload)).
-		QueueURL(ac.sqsQueueURL).
-		Build()
-	_, err = ac.sqsPub.Publish(msg.Context(), *sqsMsg)
-	if err != nil {
-		return fmt.Errorf("failed to publish message to SQS: %v", err)
-	}
-
-	log.FromContext(msg.Context()).Infof("request processed: %s %s", u.GetFirstname(), u.GetLastname())
-	return nil
-}


Test patch:
diff --git a/component/async/kafka/option_test.go b/component/async/kafka/option_test.go
index d9518707f..1cc9dc5da 100644
--- a/component/async/kafka/option_test.go
+++ b/component/async/kafka/option_test.go
@@ -1,9 +1,7 @@
 package kafka
 
 import (
-	"errors"
 	"reflect"
-	"runtime"
 	"testing"
 	"time"
 
@@ -152,54 +150,3 @@ func TestDecoderJSON(t *testing.T) {
 		reflect.ValueOf(c.DecoderFunc).Pointer(),
 	)
 }
-
-func TestWithDurationOffset(t *testing.T) {
-	f := func(_ *sarama.ConsumerMessage) (time.Time, error) {
-		return time.Time{}, nil
-	}
-
-	type args struct {
-		since         time.Duration
-		timeExtractor TimeExtractor
-	}
-	testCases := map[string]struct {
-		args        args
-		expectedErr error
-	}{
-		"success": {
-			args: args{
-				since:         time.Second,
-				timeExtractor: f,
-			},
-		},
-		"error - negative since duration": {
-			args: args{
-				since:         -time.Second,
-				timeExtractor: f,
-			},
-			expectedErr: errors.New("duration must be positive"),
-		},
-		"error - nil time extractor": {
-			args: args{
-				since: time.Second,
-			},
-			expectedErr: errors.New("empty time extractor function"),
-		},
-	}
-	for name, tt := range testCases {
-		t.Run(name, func(t *testing.T) {
-			c := ConsumerConfig{}
-			err := WithDurationOffset(tt.args.since, tt.args.timeExtractor)(&c)
-			if tt.expectedErr != nil {
-				assert.EqualError(t, err, tt.expectedErr.Error())
-			} else {
-				assert.NoError(t, err)
-				assert.True(t, c.DurationBasedConsumer)
-				assert.Equal(t, time.Second, c.DurationOffset)
-				assert.Equal(t,
-					runtime.FuncForPC(reflect.ValueOf(tt.args.timeExtractor).Pointer()).Name(),
-					runtime.FuncForPC(reflect.ValueOf(c.TimeExtractor).Pointer()).Name())
-			}
-		})
-	}
-}
diff --git a/component/async/kafka/simple/simple_test.go b/component/async/kafka/simple/simple_test.go
index 477c5311b..c1f0a5125 100644
--- a/component/async/kafka/simple/simple_test.go
+++ b/component/async/kafka/simple/simple_test.go
@@ -1,8 +1,13 @@
 package simple
 
 import (
+	"errors"
+	"reflect"
+	"runtime"
 	"testing"
+	"time"
 
+	"github.com/Shopify/sarama"
 	"github.com/beatlabs/patron/component/async/kafka"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -98,3 +103,54 @@ func TestFactory_Create(t *testing.T) {
 		})
 	}
 }
+
+func TestWithDurationOffset(t *testing.T) {
+	f := func(_ *sarama.ConsumerMessage) (time.Time, error) {
+		return time.Time{}, nil
+	}
+
+	type args struct {
+		since         time.Duration
+		timeExtractor TimeExtractor
+	}
+	testCases := map[string]struct {
+		args        args
+		expectedErr error
+	}{
+		"success": {
+			args: args{
+				since:         time.Second,
+				timeExtractor: f,
+			},
+		},
+		"error - negative since duration": {
+			args: args{
+				since:         -time.Second,
+				timeExtractor: f,
+			},
+			expectedErr: errors.New("duration must be positive"),
+		},
+		"error - nil time extractor": {
+			args: args{
+				since: time.Second,
+			},
+			expectedErr: errors.New("empty time extractor function"),
+		},
+	}
+	for name, tt := range testCases {
+		t.Run(name, func(t *testing.T) {
+			c := kafka.ConsumerConfig{}
+			err := WithDurationOffset(tt.args.since, tt.args.timeExtractor)(&c)
+			if tt.expectedErr != nil {
+				assert.EqualError(t, err, tt.expectedErr.Error())
+			} else {
+				assert.NoError(t, err)
+				assert.True(t, c.DurationBasedConsumer)
+				assert.Equal(t, time.Second, c.DurationOffset)
+				assert.Equal(t,
+					runtime.FuncForPC(reflect.ValueOf(tt.args.timeExtractor).Pointer()).Name(),
+					runtime.FuncForPC(reflect.ValueOf(c.TimeExtractor).Pointer()).Name())
+			}
+		})
+	}
+}
diff --git a/test/docker/kafka/consumer_simple_integration_test.go b/test/docker/kafka/consumer_simple_integration_test.go
index 44929515d..73b9a3cf8 100644
--- a/test/docker/kafka/consumer_simple_integration_test.go
+++ b/test/docker/kafka/consumer_simple_integration_test.go
@@ -130,7 +130,7 @@ func TestSimpleConsume_WithDurationOffset(t *testing.T) {
 	chErr := make(chan error)
 	go func() {
 		factory, err := simple.New("test1", simpleTopic3, Brokers(), kafka.DecoderJSON(), kafka.Version(sarama.V2_1_0_0.String()),
-			kafka.StartFromNewest(), kafka.WithDurationOffset(4*time.Hour, timestampExtractor))
+			kafka.StartFromNewest(), simple.WithDurationOffset(4*time.Hour, timestampExtractor))
 		if err != nil {
 			chErr <- err
 			return