Skip to content

Commit

Permalink
add featureflag service (Erlang/Elixir)
Browse files Browse the repository at this point in the history
HTTP interface is on port FEATURE_FLAG_SERVICE_PORT and
the grpc service is run on FEATURE_FLAG_GRPC_SERVICE_PORT.

Creating and updating flags is done through the web interface.
The GRPC service only provides fetching of a flag by name.
  • Loading branch information
tsloughter committed Jun 15, 2022
1 parent 1bcefb2 commit f759a30
Show file tree
Hide file tree
Showing 74 changed files with 4,662 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ RECOMMENDATION_SERVICE_ADDR=recommendationservice:${RECOMMENDATION_SERVICE_PORT}

SHIPPING_SERVICE_PORT=50051
SHIPPING_SERVICE_ADDR=shippingservice:${SHIPPING_SERVICE_PORT}

FEATURE_FLAG_SERVICE_PORT=50052
FEATURE_FLAG_GRPC_SERVICE_PORT=50053
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ release.
([#26](https://github.com/open-telemetry/opentelemetry-demo-webstore/pull/26))
* Added span attributes to frontend service
([#82](https://github.com/open-telemetry/opentelemetry-demo-webstore/pull/82))
* Added feature flag service implementation
([#141](https://github.com/open-telemetry/opentelemetry-demo-webstore/pull/141))
24 changes: 24 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ services:
- ./src/otelcollector/otelcol-config.yml:/etc/otelcol-config.yml
ports:
- "4317"
- "4318"
depends_on:
- jaeger

Expand Down Expand Up @@ -198,6 +199,29 @@ services:
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
- OTEL_RESOURCE_ATTRIBUTES=service.name=shippingservice

# FeatureFlagService
featureflagservice:
build:
context: ./src/featureflagservice
ports:
- "${FEATURE_FLAG_SERVICE_PORT}"
- "${FEATURE_FLAG_GRPC_SERVICE_PORT}"
environment:
- PORT=${FEATURE_FLAG_SERVICE_PORT}
- GRPC_PORT=${FEATURE_FLAG_GRPC_SERVICE_PORT}
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
- OTEL_RESOURCE_ATTRIBUTES=service.name=featureflagservice
- DATABASE_URL=ecto://ffs:ffs@ffs_postgres:5432/ffs
depends_on:
- ffs_postgres

ffs_postgres:
image: cimg/postgres:14.2
environment:
- POSTGRES_USER=ffs
- POSTGRES_DB=ffs
- POSTGRES_PASSWORD=ffs

# LoadGenerator
loadgenerator:
image: ${IMAGE_NAME}:${IMAGE_VERSION}-loadgenerator
Expand Down
45 changes: 45 additions & 0 deletions src/featureflagservice/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# This file excludes paths from the Docker build context.
#
# By default, Docker's build context includes all files (and folders) in the
# current directory. Even if a file isn't copied into the container it is still sent to
# the Docker daemon.
#
# There are multiple reasons to exclude files from the build context:
#
# 1. Prevent nested folders from being copied into the container (ex: exclude
# /assets/node_modules when copying /assets)
# 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc)
# 3. Avoid sending files containing sensitive information
#
# More information on using .dockerignore is available here:
# https://docs.docker.com/engine/reference/builder/#dockerignore-file

.dockerignore

# Ignore git, but keep git HEAD and refs to access current commit hash if needed:
#
# $ cat .git/HEAD | awk '{print ".git/"$2}' | xargs cat
# d0b8727759e1e0e7aa3d41707d12376e373d5ecc
.git
!.git/HEAD
!.git/refs

# Common development/test artifacts
/cover/
/doc/
/test/
/tmp/
.elixir_ls

# Mix artifacts
/_build/
/deps/
*.ez

# Generated on crash by the VM
erl_crash.dump

# Static artifacts - These should be fetched and built inside the Docker image
/assets/node_modules/
/priv/static/assets/
/priv/static/cache_manifest.json
5 changes: 5 additions & 0 deletions src/featureflagservice/.formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
import_deps: [:ecto, :phoenix],
inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"],
subdirectories: ["priv/*/migrations"]
]
35 changes: 35 additions & 0 deletions src/featureflagservice/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# The directory Mix will write compiled artifacts to.
/_build/
ffs_grpc/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
featureflagservice-*.tar

# Ignore assets that are produced by build tools.
/priv/static/assets/

# Ignore digested assets cache.
/priv/static/cache_manifest.json

# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/

/assets/vendor/
93 changes: 93 additions & 0 deletions src/featureflagservice/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian instead of
# Alpine to avoid DNS resolution issues in production.
#
# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu
# https://hub.docker.com/_/ubuntu?tab=tags
#
#
# This file is based on these images:
#
# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
# - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20210902-slim - for the release image
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: hexpm/elixir:1.13.3-erlang-25.0-debian-bullseye-20210902-slim
#
ARG ELIXIR_VERSION=1.13.3
ARG OTP_VERSION=25.0
ARG DEBIAN_VERSION=bullseye-20210902-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"

FROM ${BUILDER_IMAGE} as builder

# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*

# prepare build dir
WORKDIR /app

# install hex + rebar
RUN mix local.hex --force && \
mix local.rebar --force

# set build ENV
ENV MIX_ENV="prod"

# install mix dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get --only $MIX_ENV
RUN mkdir config

# copy compile-time config files before we compile dependencies
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
RUN mix deps.compile

COPY priv priv

COPY lib lib

COPY assets assets

# compile assets
RUN mix assets.deploy

# Compile the release
RUN mix compile

# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/

COPY rel rel
RUN mix release

# start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE}

RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*

# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

WORKDIR "/app"
RUN chown nobody /app

# set runner ENV
ENV MIX_ENV="prod"
ENV SECRET_KEY_BASE="mNhoOKKxgyvBIwbtw0P23waQcvUOmusb2U1moG2I7JQ3Bt6+MlGb5ZTrHwqbqy7j"

# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/featureflagservice ./

USER nobody

CMD ["/app/bin/server"]
45 changes: 45 additions & 0 deletions src/featureflagservice/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Feature Flag Service

This project provides an web interface for creating and updating feature flags
and a GRPC service for fetching the status of flags by their name. Each runs on
their own port but are in the same Release.

## Running

To run individually and not part of the demo the Release can be built with
`mix`:

``` shell
$ MIX_ENV=prod mix release
```

Then start Postgres with `docker compose`

``` shell
$ docker compose up
```

And run the Release:

``` shell
$ PHX_SERVER=1 PORT=4000 GRPC_PORT=4001 _build/prod/rel/featureflagservice/bin/featureflagservice start_iex
```

## Instrumentation

Traces of interaction with the web interface is provided by the OpenTelemetry
[Phoenix
instrumentation](https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_phoenix)
with Spans for database queries added through the [Ecto
instrumentation](https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_ecto).

The GRPC service uses [grpcbox](https://github.com/tsloughter/grpcbox) and uses
the [grpcbox
interceptor](https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_grpcbox)
for instrumentation.

## Building Protos

A copy of the `FeatureFlagService` protos from `demo.proto` are kept in
`proto/featureflag.proto` and `rebar3 grpc gen` will update the corresponding
Erlang module `src/ffs_featureflag_pb.erl`.
120 changes: 120 additions & 0 deletions src/featureflagservice/assets/css/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/* This file is for your main application CSS */
@import "./phoenix.css";

/* Alerts and form errors used by phx.new */
.alert {
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
}
.alert-info {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
}
.alert-warning {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
}
.alert-danger {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}
.alert p {
margin-bottom: 0;
}
.alert:empty {
display: none;
}
.invalid-feedback {
color: #a94442;
display: block;
margin: -1rem 0 2rem;
}

/* LiveView specific classes for your customization */
.phx-no-feedback.invalid-feedback,
.phx-no-feedback .invalid-feedback {
display: none;
}

.phx-click-loading {
opacity: 0.5;
transition: opacity 1s ease-out;
}

.phx-loading{
cursor: wait;
}

.phx-modal {
opacity: 1!important;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}

.phx-modal-content {
background-color: #fefefe;
margin: 15vh auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
}

.phx-modal-close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}

.phx-modal-close:hover,
.phx-modal-close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}

.fade-in-scale {
animation: 0.2s ease-in 0s normal forwards 1 fade-in-scale-keys;
}

.fade-out-scale {
animation: 0.2s ease-out 0s normal forwards 1 fade-out-scale-keys;
}

.fade-in {
animation: 0.2s ease-out 0s normal forwards 1 fade-in-keys;
}
.fade-out {
animation: 0.2s ease-out 0s normal forwards 1 fade-out-keys;
}

@keyframes fade-in-scale-keys{
0% { scale: 0.95; opacity: 0; }
100% { scale: 1.0; opacity: 1; }
}

@keyframes fade-out-scale-keys{
0% { scale: 1.0; opacity: 1; }
100% { scale: 0.95; opacity: 0; }
}

@keyframes fade-in-keys{
0% { opacity: 0; }
100% { opacity: 1; }
}

@keyframes fade-out-keys{
0% { opacity: 1; }
100% { opacity: 0; }
}
Loading

0 comments on commit f759a30

Please sign in to comment.