Skip to content

Commit

Permalink
39 Moving the Front end application from Go to Next.js (#236)
Browse files Browse the repository at this point in the history
  • Loading branch information
xoscar committed Aug 9, 2022
1 parent 64a4edf commit d168790
Show file tree
Hide file tree
Showing 171 changed files with 10,272 additions and 4,318 deletions.
4 changes: 3 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ CART_SERVICE_ADDR=cartservice:${CART_SERVICE_PORT}
CHECKOUT_SERVICE_PORT=5050
CHECKOUT_SERVICE_ADDR=checkoutservice:${CHECKOUT_SERVICE_PORT}

CURRENCY_SERVICE_PORT=7000
CURRENCY_SERVICE_PORT=7001
CURRENCY_SERVICE_ADDR=currencyservice:${CURRENCY_SERVICE_PORT}

EMAIL_SERVICE_PORT=6060
Expand All @@ -54,3 +54,5 @@ PROMETHEUS_SERVICE_PORT=9090

# Grafana
GRAFANA_SERVICE_PORT=3000

ENV_PLATFORM=local
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@ obj/
.idea/
build/
node_modules/
src/shippingservice/target/
src/shippingservice/target/

coverage
.next/
out/
build
src/frontend/protos
next-env.d.ts
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,5 @@ significant modifications will be credited to OpenTelemetry Authors.
([#260](https://github.com/open-telemetry/opentelemetry-demo/pull/260))
* Added span attributes to currency service
([#265](https://github.com/open-telemetry/opentelemetry-demo/pull/265))
* Reimplemented Frontend app using [Next.js](https://nextjs.org/) Browser client
([#236](https://github.com/open-telemetry/opentelemetry-demo/pull/236))
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ services:
ports:
- "${FRONTEND_PORT}:${FRONTEND_PORT}"
environment:
- PORT=${FRONTEND_PORT}
- FRONTEND_ADDR
- AD_SERVICE_ADDR
- CART_SERVICE_ADDR
Expand All @@ -169,6 +170,9 @@ services:
- RECOMMENDATION_SERVICE_ADDR
- SHIPPING_SERVICE_ADDR
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
- OTEL_RESOURCE_ATTRIBUTES=service.name=frontend
- OTEL_EXPORTER_OTLP_ENDPOINT
- ENV_PLATFORM
- OTEL_SERVICE_NAME=frontend
depends_on:
- adservice
Expand Down
26 changes: 26 additions & 0 deletions src/frontend/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"extends": ["plugin:react/recommended", "plugin:@typescript-eslint/recommended", "next/core-web-vitals"],
"plugins": ["@typescript-eslint"],
"root": true,
"globals": {},
"rules": {
"@typescript-eslint/no-non-null-assertion": "off",
"react-hooks/exhaustive-deps": "warn",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"max-len": [
"error",
{
"code": 150,
"ignoreComments": true,
"ignoreTrailingComments": true,
"ignoreUrls": true,
"ignoreStrings": true,
"ignoreTemplateLiterals": true
}
]
},
"parser": "@typescript-eslint/parser",
"env": {},
"overrides": []
}
6 changes: 6 additions & 0 deletions src/frontend/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.idea
.git
build
dist
.husky
node_modules
15 changes: 15 additions & 0 deletions src/frontend/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"singleQuote": true,
"arrowParens": "avoid",
"bracketSpacing": true,
"semi": true,
"trailingComma": "es5",
"printWidth": 120,
"jsxBracketSameLine": false,
"proseWrap": "always",
"quoteProps": "as-needed",
"tabWidth": 2,
"useTabs": false
}
84 changes: 40 additions & 44 deletions src/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,44 +1,40 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM golang:1.17.7-alpine AS builder

RUN apk add build-base protoc

WORKDIR /usr/src/app/

# Restore dependencies
COPY ./src/frontend/ ./
COPY ./pb/ ./proto/
RUN go mod download
RUN go get google.golang.org/protobuf/cmd/protoc-gen-go
RUN go get google.golang.org/grpc/cmd/protoc-gen-go-grpc

# Build executable
RUN protoc -I ./proto/ ./proto/demo.proto --go_out=./ --go-grpc_out=./
RUN go build -o /go/bin/frontend/ ./

# -----------------------------------------------------------------------------

FROM alpine

RUN apk add busybox-extras net-tools bind-tools
WORKDIR /usr/src/app/

COPY --from=builder /go/bin/frontend/ ./
COPY ./src/frontend/templates/ ./templates/
COPY ./src/frontend/static/ ./static/

EXPOSE ${FRONTEND_PORT}
ENTRYPOINT [ "./frontend" ]
FROM node:16-alpine AS deps
RUN apk add --no-cache libc6-compat

WORKDIR /app

COPY ./src/frontend/package*.json ./
RUN npm ci

FROM node:16-alpine AS builder
RUN apk add --no-cache libc6-compat protoc
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY ./pb ./pb
COPY ./src/frontend .

RUN npm run grpc:generate
RUN npm run build

FROM node:16-alpine AS runner
WORKDIR /app
RUN apk add --no-cache protoc

ENV NODE_ENV=production

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

ENV PORT 8080
EXPOSE ${PORT}

CMD ["node", "server.js"]
136 changes: 16 additions & 120 deletions src/frontend/README.md
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,131 +1,27 @@
# Frontend service

The **frontend** service is responsible for rendering the UI for the store's website.
It serves as the main entry point for the application routing requests to their
appropriate backend services.
The application uses Server Side Rendering (SSR) to generate HTML consumed by
the clients, which could be web browsers, web crawlers, mobile clients or something
else.
The frontend is a [Next.js](https://nextjs.org/) application that is composed
by two layers.

## OpenTelemetry instrumentation
1. Client side application. Which renders the components for the OTEL webstore.
2. API layer. Connects the client to the backend services by exposing REST endpoints.

### Initialization
## Build Locally

The OpenTelemetry SDK is initialized in `main` using the `InitTraceProvider` function.
By running `docker compose up` at the root of the project you'll have access to the
frontend client by going to <http://localhost:8080/>.

```go
func InitTracerProvider() *sdktrace.TracerProvider {
ctx := context.Background()
## Local development

exporter, err := otlptracegrpc.New(ctx)
if err != nil {
log.Fatal(err)
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return tp
}
```

Services should call `TraceProvider.Shutdown()` when the service is shutdown to
ensure all spans are exported.
This service makes that call as part of a deferred function in `main`.

```go
// Initialize OpenTelemetry Tracing
tp := InitTracerProvider()
defer func() {
if err := tp.Shutdown(context.Background()); err != nil {
log.Printf("Error shutting down tracer provider: %v", err)
}
}()
```

### HTTP instrumentation

This service receives HTTP requests, controlled by the gorilla/mux Router.
The following routes are defined by the frontend:

| Path | Method | Use |
|-------------------|--------|-----------------------------------|
| `/` | GET | Main index page |
| `/cart` | GET | View Cart |
| `/cart` | POST | Add to Cart |
| `/cart/checkout` | POST | Place Order |
| `/cart/empty` | POST | Empty Cart |
| `/logout` | GET | Logout |
| `/product/{id}` | GET | View Product |
| `/setCurrency` | POST | Set Currency |
| `/static/` | * | Static resources |
| `/robots.txt` | * | Search engine response (disallow) |
| `/_healthz` | * | Health check (ok) |

These requests are instrumented in the main function as part of the router's definition.

```go
// Add OpenTelemetry instrumentation to incoming HTTP requests controlled by the gorilla/mux Router.
r.Use(otelmux.Middleware("server"))
```
Currently, the easiest way to run the frontend for local development is to execute

### gRPC instrumentation

This service will issue several outgoing gRPC calls, which have instrumentation
hooks added in the `mustConnGRPC` function.

```go
func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) {
// Add OpenTelemetry instrumentation to outgoing gRPC requests
var err error
ctx, cancel := context.WithTimeout(ctx, time.Second*3)
defer cancel()
*conn, err = grpc.DialContext(ctx, addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
)
if err != nil {
panic(errors.Wrapf(err, "grpc: failed to connect %s", addr))
}
}
```

### Service specific instrumentation attributes

All requests incoming to the frontend service will receive the following attributes:

- `app.session.id`
- `app.request.id`
- `app.currency`
- `app.user.id` (when the user is present)

These attributes are added in the `instrumentHandler` function (defined in the
middleware.go file) which wraps all HTTP routes specified within the
gorilla/mux router.
Additional attributes are added within each handler's function as appropriate
(ie: `app.cart.size`, `app.cart.total.price`).

Adding attributes to existing auto-instrumented spans can be accomplished by
getting the current span from context, then adding attributes to it.

```go
span := trace.SpanFromContext(r.Context())
span.SetAttributes(
attribute.Int(instr.AppPrefix+"cart.size", cartSize(cart)),
attribute.Int(instr.AppPrefix+"cart.items.count", len(items)),
attribute.Float64(instr.AppPrefix+"cart.shipping.cost", shippingCostFloat),
attribute.Float64(instr.AppPrefix+"cart.total.price", totalPriceFloat),
)
```shell
docker compose run --service-ports -e NODE_ENV=development
--volume $(pwd)/src/frontend:/app --volume $(pwd)/pb:/app/pb frontend sh
```

When an error is encountered, the current span's status code and error message
are set.
from the root folder.

```go
// set span status on error
span := trace.SpanFromContext(r.Context())
span.SetStatus(codes.Error, errMsg)
```
It will start all of the required backend services
and within the container simply run `npm run dev`.
After that the app should be available at <http://localhost:8080/>.
20 changes: 20 additions & 0 deletions src/frontend/components/Ad/Ad.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import styled from 'styled-components';
import RouterLink from 'next/link';

export const Ad = styled.section`
position: relative;
background-color: ${({ theme }) => theme.colors.otelYellow};
font-size: ${({ theme }) => theme.sizes.dMedium};
text-align: center;
padding: 48px;
* {
color: ${({ theme }) => theme.colors.white};
margin: 0;
cursor: pointer;
}
`;

export const Link = styled(RouterLink)`
color: black;
`;
18 changes: 18 additions & 0 deletions src/frontend/components/Ad/Ad.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useAd } from '../../providers/Ad.provider';
import * as S from './Ad.styled';

const Ad = () => {
const {
adList: [{ text, redirectUrl } = { text: '', redirectUrl: '' }],
} = useAd();

return (
<S.Ad>
<S.Link href={redirectUrl}>
<p>{text}</p>
</S.Link>
</S.Ad>
);
};

export default Ad;
1 change: 1 addition & 0 deletions src/frontend/components/Ad/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Ad';

0 comments on commit d168790

Please sign in to comment.