One place for hosting & domains

      Implement

      7 Ways to Implement Conditional Rendering in React Applications


      Introduction

      With React, we can build Single Page Applications that are dynamic and highly interactive. One way we fully utilize such interactivity is through conditional rendering.

      Conditional rendering as a term describes the ability to render different UI markup based on certain conditions. In React-speak, it is a way to render different elements or components based on a condition. This concept is applied often in the following scenarios:

      • Rendering external data from an API
      • Showing/hiding elements
      • Toggling application functionality
      • Implementing permission levels
      • Authentication and Authorization

      In this article, we examine seven(7) ways to implement such conditional rendering in React applications.

      The Challenge

      As a challenge, based on the value of isLoggedIn in our component state, we want to be able to display a Login button if the user isn’t logged in, and a Logout button if he/she is.

      This is what our starter component looks like:

      Visually:

      Code:

      import React, { Component } from "react";
      import ReactDOM from "react-dom";
      import "./styles.css";
      
      
      class App extends Component {
        constructor(props) {
          super(props);
          this.state = {
            isLoggedIn: true
          };
        }
        render() {
          return (
            <div className="App">
              <h1>
                This is a Demo showing several ways to implement Conditional Rendering
                in React.
              </h1>
              <button>Login</button>
              <button>Logout</button>
            </div>
          );
        }
      }
      
      
      const rootElement = document.getElementById("root");
      ReactDOM.render(<App />, rootElement);
      

      Bear in mind that within the code snippets implies that some code which isn’t directly connected with the point being explained goes there.

      1. Using an If…else Statement

      An if…else statement allows us to specify that a particular action be carried out if a condition evaluates to true as well as do something else if it doesn’t. Using the sample project, we will examine two ways if…else conditions may be used to implement conditional rendering in React.

      In JSX, we are able to mix up JavaScript code with our markup to ensure stunning interactivity within our application. To do this we use a set of curly braces {} and write our JavaScript within. The caveat however is that there is a limit to what can be done within such braces. As a result the code snippet below would fail to achieve the desired result.

      // index.js
      ...
      render() {
          let {isLoggedIn} = this.state;
          return (
            <div className="App">
              <h1>
                This is a Demo showing several ways to implement Conditional Rendering
                in React.
              </h1>
              {
                if(isLoggedIn){
                  return <button>Logout</button>
                } else{
                  return <button>Login</button>
                }
              }
            </div>
          );
      }
      ...
      

      To understand more about this behavior, visit this link.

      To solve this, we extract the conditional logic into a function as shown below:

      // index.js
      ...
      render() {
          let {isLoggedIn} = this.state;
          const renderAuthButton = ()=>{
            if(isLoggedIn){
              return <button>Logout</button>
            } else{
              return <button>Login</button>
            }
          }
          return (
            <div className="App">
              <h1>
                This is a Demo showing several ways to implement Conditional Rendering
                in React.
              </h1>
              {renderAuthButton()}
            </div>
          );
        }
      ...
      

      Notice that we extract the logic from JSX into a function renderAuthButton. Thus, we only need to execute the function within the JSX curly braces.

      Multiple return statements

      In using this method, the component must be kept as simple as possible to avoid a wasted re-render of sibling or parent components. As a result of this, we create a new functional component called AuthButton.

      // AuthButton.js
      
      import React from "react";
      
      const AuthButton = props => {
        let { isLoggedIn } = props;
        if (isLoggedIn) {
          return <button>Logout</button>;
        } else {
          return <button>Login</button>;
        }
      };
      export default AuthButton;
      

      AuthButton returns various elements/components depending on the value of state that is passed down via the isLoggedIn props. Thus we import it in our index.js and pass down the appropriate state as shown below:

      // index.js
      ...
      import AuthButton from "./AuthButton";
      
      ...
        render() {
          let { isLoggedIn } = this.state;
          return (
            <div className="App">
            ...
              <AuthButton isLoggedIn={isLoggedIn} />
            </div>
          );
        }
      ...
      

      You must avoid doing this:

      // index.js
      ...
      render() {
          let { isLoggedIn } = this.state;
          if (isLoggedIn) {
            return (
              <div className="App">
                <h1>
                  This is a Demo showing several ways to implement Conditional
                  Rendering in React.
                </h1>
                <button>Logout</button>;
              </div>
            );
          } else {
            return (
              <div className="App">
                <h1>
                  This is a Demo showing several ways to implement Conditional
                  Rendering in React.
                </h1>
                <button>Login</button>
              </div>
            );
          }
        }
      }
      ...
      

      The snippet above would achieve the same result but bloat the component unnecessarily while introducing performance issues as a result of constantly re-rendering an unchanging component.

      2. Using Element Variables

      Element variables are an extension of **Extracting the conditional rendering into a function** as shown above. Element variables are simply variables that hold JSX elements. Thus we can conditionally assign elements/ components to these variables outside our JSX and only render the variable within JSX. See demo below:

      // index.js
      ...
      render() {
          let { isLoggedIn } = this.state;
          let AuthButton;
          if (isLoggedIn) {
            AuthButton = <button>Logout</button>;
          } else {
            AuthButton = <button>Login</button>;
          }
          return (
            <div className="App">
              <h1>
                This is a Demo showing several ways to implement Conditional Rendering
                in React.
              </h1>
              {AuthButton}
            </div>
          );
        }
      ...
      

      Notice how we conditionally assign values(components) to AuthButton and then we only have to render it neatly within our JSX.

      3. Using a Switch Statement

      As shown previously, we can conditionally return different markup from a component based on set conditions using an if…else statement. The same could be achieved with a switch statement where we can specify the markup for various conditions. See example below:

      // AuthButton.js
      import React from "react";
      
      const AuthButton = props => {
        let { isLoggedIn } = props;
        switch (isLoggedIn) {
          case true:
            return <button>Logout</button>;
            break;
          case false:
            return <button>Login</button>;
            break;
          default:
            return null;
        }
      };
      export default AuthButton;
      

      Notice how we return various buttons based on the value of isLoggedIn. It is more reasonable to apply this method when there’s more than two possible values or outcomes. You may also do away with the break statement as the return statement automatically terminates the execution.

      Note: Returning **null** from a component will cause it to hide itself/display nothing. This a good way to toggle visibility of components.

      4. Ternary Operators

      The conditional (ternary) operator is the only JavaScript operator that takes three operands. This operator is frequently used as a shortcut for the if statement.

      If you are familiar with ternary operators, then you are aware that is is simply a more concise way to write an if statement. Thus we have:

      // index.js
      ...
      render() {
          let { isLoggedIn } = this.state;
          return (
            <div className="App">
              <h1>
                This is a Demo showing several ways to implement Conditional Rendering
                in React.
              </h1>
              {isLoggedIn ? <button>Logout</button> : <button>Login</button>}
            </div>
          );
        }
      ...
      

      In cases where, this approach makes the component bloated, bulky or less readable, you may encapsualte the conditional within a functional component as shown below:

      // AuthButton.js
      import React from "react";
      
      const AuthButton = props => {
        let { isLoggedIn } = props;
        return isLoggedIn ? <button>Logout</button> : <button>Login</button>;
      };
      
      export default AuthButton;
      

      5. Logical && (Short Circuit Evaluation with &&)

      Short circuit evaluation is a technique used to ensure that there are no side effects during the evaluation of eperands in an expression. The logical && helps us specify that an action should be taken only on one condition, otherwise, it would be ignored entirely. This is useful for situations where you only need to take an action when a certain condition is true, otherwise do nothing.

      For instance if we only needed to show the Logout button if the person is logged in, otherwise we do nothing. We’d have something like this:

      // index.js
      ...
      render() {
          let { isLoggedIn } = this.state;
          return (
            <div className="App">
              <h1>
                This is a Demo showing several ways to implement Conditional Rendering
                in React.
              </h1>
              {isLoggedIn && <button>Logout</button>}
            </div>
          );
        }
      ...
      

      This would display the logout button if isLoggedIn is true otherwise it’d display nothing. We could adapt this to fit our use case as shown below. However, it is not advisable.

      // index.js
      ...
      return (
            <div className="App">
              <h1>
                This is a Demo showing several ways to implement Conditional Rendering
                in React.
              </h1>
              {isLoggedIn && <button>Logout</button>}
              {!isLoggedIn && <button>Login</button>}
            </div>
          );
        }
      ...
      

      This would render the right button based on the value of isLoggedIn. However, this isn’t recommended as there are better, cleaner ways to achieve the same effect. Also this could easily make your code look messy and unintuitive once the component gets slightly larger.

      Earlier we covered that JSX limitations make it unable to execute every type of JavaScript code. This isn’t entirely true as there are ways to bypass such behavior. One such way is by using IIFEs.

      An IIFE (Immediately Invoked Function Expression) is a JavaScript function that runs as soon as it is defined. It’s used in the format below.

      (function () {
          statements
      })();
      

      You may learn more about IIFE’s from MDN here.

      With this technique, we are able to to write conditional logic directly within JSX but wrapped within an anonymous function that is immediately invoked on evaluation of that portion of our code. See example below:

      //index.js
      ...
      render() {
          let { isLoggedIn } = this.state;
          return (
            <div className="App">
              <h1>
                This is a Demo showing several ways to implement Conditional Rendering
                in React.
              </h1>
              {(function() {
                if (isLoggedIn) {
                  return <button>Logout</button>;
                } else {
                  return <button>Login</button>;
                }
              })()}
            </div>
          );
        }
      ...
      

      This can also be written in a slightly more concise manner using an arrow function as shown below:

      // index.js
      ...
      return (
            <div className="App">
              <h1>
                This is a Demo showing several ways to implement Conditional Rendering
                in React.
              </h1>
              {(()=> {
                if (isLoggedIn) {
                  return <button>Logout</button>;
                } else {
                  return <button>Login</button>;
                }
              })()}
            </div>
          );
        }
      ...
      

      7. Using Enhanced JSX

      Certain libaries expose functionality to extend JSX, thus making it possible to implement conditional rendering directly with JSX. One of such libraries is JSX Control Statements. It is a Babel plugin that transforms component-like control statements into their JavaScript counterparts during transpilation. See example below for how this may be implemented.

      // index.js
      ...
      return (
            <div className="App">
              <h1>
                This is a Demo showing several ways to implement Conditional Rendering
                in React.
              </h1>
              <Choose>
                <When condition={isLoggedIn}>
                   <button>Logout</button>;
                </When>
                <When condition={!isLoggedIn}>
                   <button>Login</button>;
                </When>
              </Choose>
            </div>
          );
        }
      ...
      

      This approach is however not recommended as the code you write is eventually transpiled to a regular JavaScript conditional. It is probably always better to just write JavaScript than add an extra dependency over something so trivial.

      Performance Concerns

      As a general rule, it is best to ensure that in implemementing conditional rendering you:

      • Do not change the position of components arbitrarily in order to prevent components from unmounting and remounting unnecessarily.
      • Change only the markup that is concerned with the conditional rendering and leave out every other unchanging bit of the component.
      • Do not bloat your component unnecessarily within the render method, thus causing components to delay in rendering.

      For more on writing high performing conditionals in React, see this article by Cole Williams.

      Conclusion

      We have successfully examined 7 ways to implement conditional rendering in React. Each method has it’s own advantage and the choice of which to use is mostly dependent on the use case. Things to consider include:

      • The size of markup to be rendered conditionally
      • The number of possible outcomes
      • Which would be more intuitive and readable

      Generally, keep in mind the following recommendations:

      • When there is only one expected outcome, the Logical && Operator comes in very handy.
      • For boolean situations or use cases with only 2 possible outcomes, you may use If…else, Element variables, Ternary Operators and IIFEs.
      • For cases of more than 2 outcomes, you may use a Switch statement, an extracted function or extracted functional component.

      This is however merely a recommendation and the choice of which to go with is primarily yours.

      Further Reading

      You may learn more via the following resources:



      Source link

      How To Implement Distributed Tracing with Jaeger on Kubernetes


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Kubernetes and the microservice architectures that it enables can create very efficient and scalable systems. But problems arise when one of these microservices develops performance problems. Typically, we first notice that response times from our customer-facing services are getting longer and longer. The problem might be with one of the backend services, or perhaps a database that is beyond its optimal capacity. To discover the root of our problem, we need to implement distributed tracing.

      Jaeger is a distributed tracing solution and a graduate of the Cloud Native Computing Foundation’s Incubation Project. It features a pleasant UI for visualizing traces, Jaeger sidecars for collecting traces, and several other components. Distributed tracing systems like Jaeger let us trace the lifecycle of each customer-generated event and see how each service processes that event.

      In this tutorial, we will deploy a very small distributed application to a Kubernetes cluster and simulate a performance lag using a sleep function in our code. To find the root cause of this issue and to trace each event, we will use Jaeger. With tracing enabled, we’ll see how effective it is at observing the behavior of Services and pinpointing issues.

      Prerequisites

      Before you begin, you will need the following tools and accounts:

      Step 1 — Building the Sample Application

      In order to test Jaeger’s tracing abilities, we will build and deploy a sample application, sammy-jaeger, which uses two services: one for the frontend and one for the backend. We will build both using Python and the Flask microframework.

      Our application will be a hit counter whose value increases every time we call the frontend. To simulate performance issues, we will code a randomized sleep function that executes whenever the frontend sends a GET request to the backend. In this step, we will build and deploy that application. In the following steps, we will deploy the app to Kubernetes, install Jaeger, and then use it to trace our service issue.

      First, let’s create a project directory structure and navigate inside:

      • mkdir -p ./sammy-jaeger/frontend ./sammy-jaeger/backend && cd ./sammy-jaeger

      We now have a root directory, sammy-jaeger, and two subdirectories:

      output

      . ├── backend └── frontend

      We also changed into the root directory, /sammy-jaeger. We will run all remaining commands from here.

      Let’s start building the frontend application.

      Building the Frontend Application

      Using your preferred text editor, create and open a new file called frontend.py in ./frontend:

      nano ./frontend/frontend.py
      

      Add the following code. This will import Flask, build our counter functions, and define one route for HTTP requests:

      ./frontend/frontend.py

      import os
      import requests
      from flask import Flask
      app = Flask(__name__)
      
      def get_counter(counter_endpoint):
          counter_response = requests.get(counter_endpoint)
          return counter_response.text
      
      def increase_counter(counter_endpoint):
          counter_response = requests.post(counter_endpoint)
          return counter_response.text
      
      @app.route('/')
      def hello_world():
          counter_service = os.environ.get('COUNTER_ENDPOINT', default="https://localhost:5000")
          counter_endpoint = f'{counter_service}/api/counter'
          counter = get_counter(counter_endpoint)
      
          increase_counter(counter_endpoint)
      
          return f"""Hello, World!
      
      You're visitor number {counter} in here!nn"""
      

      We are importing three modules. The os module will communicate with our operating system. The requests module is a library for sending HTTP requests. Flask is a microframework that will host our app.

      We are then defining our get_counter() and increase_counter() functions, which both accept the parameter counter_endpoint. get_counter() will call the backend using the GET method to find the current counter state. increase_counter() will call the backend with the POST method to increment the counter.

      We then define our route /, which will call another function called hello_world(). This function will retrieve a URL and a port for our backend pod, assign it to a variable, and then pass that variable to our first two functions, get_counter() and increase_counter(), which will send the GET and POST requests to the backend. The backend will then pause for a random period of time (our simulated lag) before incrementing the current counter number and then returning that number. Lastly, hello_world() will take this value and print a “Hello World!” string to our console that includes our new visitor count.

      You might have noticed that we did not create a Python environment, nor did we install pip on our local machine. We will complete these steps when we containerize our application using Docker.

      Save and close frontend.py.

      Now we will build a Dockerfile for the frontend application. This Dockerfile will include all the necessary commands to build our containerized environment.

      Create and open a new Dockerfile in ./frontend:

      • nano ./frontend/Dockerfile

      Add the following content:

      ./frontend/Dockerfile

      FROM alpine:3.8
      
      RUN apk add --no-cache py3-pip python3 && 
          pip3 install flask requests
      
      COPY . /usr/src/frontend
      
      ENV FLASK_APP frontend.py
      
      WORKDIR /usr/src/frontend
      
      CMD flask run --host=0.0.0.0 --port=8000
      

      In this Dockerfile, we instruct our image to build from the base Alpine Linux image. We then install Python3, pip, and several additional dependencies. Next, we copy the application source code, set an environment variable pointing to the main application code, set the working directory, and write a command to run Flask whenever we create a container from the image.

      Save and close the file.

      Now let’s build the Docker image for our frontend application and push it to a repository in Docker Hub.

      First, check that you are signed in to Docker Hub:

      • docker login --username=your_username --password=your_password

      Build the image:

      • docker build -t your_username/do-visit-counter-frontend:v1 ./frontend

      Now push the image to Docker Hub:

      • docker push your_username/do-visit-counter-frontend:v1

      Our frontend application is now built and available in Docker Hub. Before we deploy it to Kubernetes, however, let’s code and build our backend application.

      Building the Backend Application

      The backend application requires the same steps that the frontend required.

      First, create and open a file called backend.py in ./backend:

      • nano ./backend/backend.py

      Add the following content, which will define two functions and another route:

      ./backend/backend.py

      from random import randint
      from time import sleep
      
      from flask import request
      from flask import Flask
      app = Flask(__name__)
      
      counter_value = 1
      
      def get_counter():
          return str(counter_value)
      
      def increase_counter():
          global counter_value
          int(counter_value)
          sleep(randint(1,10))
          counter_value += 1
          return str(counter_value)
      
      @app.route('/api/counter', methods=['GET', 'POST'])
      def counter():
          if request.method == 'GET':
              return get_counter()
          elif request.method == 'POST':
              return increase_counter()
      

      We are importing several modules, including random and sleep. We are then setting our counter value to 1 and defining two functions. The first, get_counter, returns the current counter value, which is stored as counter_value. The second function, increase_counter, performs two actions. It increments our counter value by 1 and it uses the sleep module to delay the function’s completion by a random amount of time.

      The backend also has one route (/api/counter) that accepts two methods: POST and GET.
      When we call this route using the GET method, it calls get_counter() and returns our counter value. When we call this route using the POST method, it calls increase_counter() and increases the value of the counter while waiting a random amount of time.

      Save and close the file.

      Our backend application will also require its own Dockerfile, which is almost identical to the frontend’s version.

      Create and open a second Dockerfile in ./backend:

      • nano ./backend/Dockerfile

      Add the following content. The one major difference here, besides filepaths, will be the port:

      ./backend/Dockerfile

      FROM alpine:3.8
      
      RUN apk add --no-cache py3-pip python3 && 
          pip3 install flask
      
      COPY . /usr/src/backend
      
      ENV FLASK_APP backend.py
      
      WORKDIR /usr/src/backend
      
      CMD flask run --host=0.0.0.0 --port=5000
      

      Save and close the file.

      Now build the image:

      • docker build -t your_username/do-visit-counter-backend:v1 ./backend

      Push it to Docker Hub:

      • docker push your_username/do-visit-counter-backend:v1

      With our application available on Docker Hub, we are now ready to deploy it to our cluster and test it in the next step.

      Step 2 — Deploying and Testing the Application

      Writing our code and publishing our containers was our first major step. Now we need to deploy to Kubernetes and test the basic application. After that, we can add Jaeger and explore the potential of distributed tracing.

      Let’s get started with deployment and testing.

      At this point, our directory tree looks like this:

      .
      ├── backend
      │   ├── Dockerfile
      │   └── backend.py
      └── frontend
          ├── Dockerfile
          └── frontend.py
      

      To deploy this application to our cluster, we will also need two Kubernetes manifests; one for each half of the application.

      Create and open a new manifest file in ./frontend:

      • nano ./frontend/deploy_frontend.yaml

      Add the following content. This manifest will specify how Kubernetes builds our Deployment (remember to replace the highlighted section with your Docker Hub username):

      ./frontend/deploy_frontend.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: do-visit-counter-frontend
        labels:
          name: do-visit-counter-frontend
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: do-visit-counter-frontend
        template:
          metadata:
            labels:
              app: do-visit-counter-frontend
          spec:
            containers:
              - name: do-visit-counter-frontend
                image: your_dockerhub_username/do-visit-counter-frontend:v1
                imagePullPolicy: Always
                env:
                  - name: COUNTER_ENDPOINT
                    value: "http://do-visit-counter-backend.default.svc.cluster.local:5000"
                ports:
                  - name: frontend-port
                    containerPort: 8000
                    protocol: TCP
      
      

      We have specified Kubernetes to build a Deployment, to name it do-visit-counter-frontend, and to deploy one replica using our frontend image on Docker Hub. We have also configured an environment variable named COUNTER_ENDPOINT to link the two halves of our application.

      Save and close the file.

      Now create the manifest for our backend application in ./backend:

      • nano ./backend/deploy_backend.yaml

      Add the following content, again replacing the highlighted section with your Docker Hub username:

      ./backend/deploy_backend.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: do-visit-counter-backend
        labels:
          name: do-visit-counter-backend
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: do-visit-counter-backend
        template:
          metadata:
            labels:
              app: do-visit-counter-backend
          spec:
            containers:
              - name: do-visit-counter-backend
                image: your_dockerhub_username/do-visit-counter-backend:v1
                imagePullPolicy: Always
                ports:
                  - name: backend-port
                    containerPort: 5000
                    protocol: TCP
      ---
      apiVersion: v1
      kind: Service
      metadata:
          name: do-visit-counter-backend
      spec:
          selector:
              app: do-visit-counter-backend
          ports:
              - protocol: TCP
                port: 5000
                targetPort: 5000
      

      In this manifest you are defining a Deployment and a Service for our backend. The Deployment describes how and what the container will run. Note that our port has changed from 8000 on the frontend to 5000 on the backend. The Service allows inter-cluster connections from the frontend to the backend.

      Save and close the file.

      Now let’s deploy our counter to the cluster using kubectl. Start with the frontend:

      • kubectl apply -f ./frontend/deploy_frontend.yaml

      And then deploy the backend:

      • kubectl apply -f ./backend/deploy_backend.yaml

      To verify that everything is working, call kubectl get pods:

      You will see an output like this:

      Output

      NAME READY STATUS RESTARTS AGE do-visit-counter-backend-79f6964-prqpb 1/1 Running 0 3m do-visit-counter-frontend-6985bdc8fd-92clz 1/1 Running 0 3m

      We want all the pods in the READY state. If they are not yet ready, wait a few minutes and rerun the previous command.

      Finally, we want to use our application. To do that, we will forward ports from the cluster and then communicate with the frontend using the curl command. Make sure to open a second terminal window because forwarding ports will block one window.

      Use kubectl to forward the port:

      • kubectl port-forward $(kubectl get pods -l=app="do-visit-counter-frontend" -o name) 8000:8000

      Now, in a second terminal window, send three requests to your frontend application:

      for i in 1 2 3; do curl localhost:8000; done
      

      Each curl call will increment the visit number. You will see an output like this:

      Output

      Hello, World! You're visitor number 1 in here! Hello, World! You're visitor number 2 in here! Hello, World! You're visitor number 3 in here!

      Our visitor counter is working properly, but you likely noticed a delay between each response. This is the result of our sleep function, which is simulating a performance lag.

      With our distributed application ready, it’s time to install Jaeger and trace these events.

      Step 3 — Deploying Jaeger

      Collecting traces and visualizing them is Jaeger’s specialty. In this step, we will deploy Jaeger to our cluster so it can find our performance lags.

      Jaeger’s official documentation includes commands for installing the Jaeger Operator. It also includes four additional manifests that you must deploy for the tool to work. Let’s do that now:

      First, create the Custom Resource Definition required by the Jaeger Operator. We will use the recommended templates available on Jaeger’s official documentation:

      • kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/crds/jaegertracing.io_jaegers_crd.yaml

      Next, create a Service Account, a Role, and Role Binding for Role-Based Access Control:

      • kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/service_account.yaml
      • kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role.yaml
      • kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role_binding.yaml

      Finally, deploy the Jaeger Operator:

      • kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/operator.yaml

      The Operator itself doesn’t mean we have Jaeger working. This is where the Custom Resource Definitions come into play. We need to create a resource describing the Jaeger instance we want the Operator to manage. Once again, we will follow the steps listed in Jaeger’s official documentation:

      Use a heredoc to create this resource from the command line:

      • kubectl apply -f - <<EOF
      • apiVersion: jaegertracing.io/v1
      • kind: Jaeger
      • metadata:
      • name: simplest
      • EOF

      Press ENTER to create the resource.

      Now check your deployments again:

      You will see an output with your Jaeger operator and the simplest deployment:

      Output

      NAME READY STATUS RESTARTS AGE do-visit-counter-backend-79f6964-prqpb 1/1 Running 0 3m do-visit-counter-frontend-6985bdc8fd-92clz 1/1 Running 0 3m jaeger-operator-547567dddb-rxsd2 1/1 Running 0 73s simplest-759cb7d586-q6x28 1/1 Running 0 42s

      To validate that Jaeger is working correctly, let’s forward its port and see if we can access the UI:

      • kubectl port-forward $(kubectl get pods -l=app="jaeger" -o name) 16686:16686

      Open a browser and navigate to http://localhost:16686. The Jaeger UI will load.

      Jaeger UI

      Both our application and Jaeger are working. In the next step, we will add instrumentation to let Jaeger collect data and find our performance lag.

      Step 4 — Adding Instrumentation

      Although Jaeger automates many tasks when used with Kubernetes, we still need to add instrumentation manually to our application. Fortunately, we have the Flask-OpenTracing module to handle that task.

      OpenTracing is one of the standards of distributed tracing. It has been proposed by the authors of Jaeger with the aim to also support other tracing tools. It is vendor-neutral and supports many different programming languages and popular frameworks.

      As is the case with all OpenTracing implementations, we need to modify our original applications by adding the Jaeger configuration and appending the tracing decorators to the endpoints that we want to trace.

      Let’s add Flask-OpenTracing to our frontend code.

      Reopen .frontend.py:

      • nano ./frontend/frontend.py

      Now add the following highlighted code, which will embed OpenTracing:

      ./frontend/frontend.py

      import os
      import requests
      from flask import Flask
      from jaeger_client import Config
      from flask_opentracing import FlaskTracing
      
      app = Flask(__name__)
      config = Config(
          config={
              'sampler':
              {'type': 'const',
               'param': 1},
                              'logging': True,
                              'reporter_batch_size': 1,}, 
                              service_name="service")
      jaeger_tracer = config.initialize_tracer()
      tracing = FlaskTracing(jaeger_tracer, True, app)
      
      def get_counter(counter_endpoint):
          counter_response = requests.get(counter_endpoint)
          return counter_response.text
      
      def increase_counter(counter_endpoint):
          counter_response = requests.post(counter_endpoint)
          return counter_response.text
      
      @app.route('/')
      def hello_world():
          counter_service = os.environ.get('COUNTER_ENDPOINT', default="https://localhost:5000")
          counter_endpoint = f'{counter_service}/api/counter'
          counter = get_counter(counter_endpoint)
      
          increase_counter(counter_endpoint)
      
          return f"""Hello, World!
      
      You're visitor number {counter} in here!nn"""
      

      Save and close the file. You can learn more about the Flask OpenTracing configurations on their GitHub page.

      Now open your backend application code:

      • nano ./backend/backend.py

      Add the highlighted code. This is the same code that we placed in frontend.py:

      ./backend/backend.py

      from random import randint
      from time import sleep
      
      from flask import Flask
      from flask import request
      from jaeger_client import Config
      from flask_opentracing import FlaskTracing
      
      
      app = Flask(__name__)
      config = Config(
          config={
              'sampler':
              {'type': 'const',
               'param': 1},
                              'logging': True,
                              'reporter_batch_size': 1,}, 
                              service_name="service")
      jaeger_tracer = config.initialize_tracer()
      tracing = FlaskTracing(jaeger_tracer, True, app)
      
      counter_value = 1
      
      def get_counter():
          return str(counter_value)
      
      def increase_counter():
          global counter_value
          int(counter_value)
          sleep(randint(1,10))
          counter_value += 1
          return str(counter_value)
      
      @app.route('/api/counter', methods=['GET', 'POST'])
      def counter():
          if request.method == 'GET':
              return get_counter()
          elif request.method == 'POST':
              return increase_counter()
      
      

      Save and close the file.

      Since we’re adding additional libraries, we also have to modify our Dockerfiles for both services.

      Open the Dockerfile for the frontend:

      nano ./frontend/Dockerfile
      

      Add the highlighted code:

      ./frontend/Dockerfile

      FROM alpine:3.8
      
      RUN apk add --no-cache py3-pip python3 && 
          pip3 install flask requests Flask-Opentracing jaeger-client
      
      COPY . /usr/src/frontend
      
      ENV FLASK_APP frontend.py
      
      WORKDIR /usr/src/frontend
      
      CMD flask run --host=0.0.0.0 --port=8000
      

      Save and close the file.

      Now open the backend’s Dockerfile:

      • nano ./backend/Dockerfile

      Add the highlighted code:

      ./backend/Dockerfile

      FROM alpine:3.8
      
      RUN apk add --no-cache py3-pip python3 && 
          pip3 install flask Flask-Opentracing jaeger-client
      
      COPY . /usr/src/backend
      
      ENV FLASK_APP backend.py
      
      WORKDIR /usr/src/backend
      
      CMD flask run --host=0.0.0.0 --port=5000
      

      With these changes, we want to rebuild and push the new versions of our containers.

      Build and push the frontend application. Note the v2 tag at the end:

      • docker build -t your_username/do-visit-counter-frontend:v2 ./frontend
      • docker push your_username/do-visit-counter-frontend:v2

      Now build and push the backend application:

      • docker build -t your_username/do-visit-counter-backend:v2 ./backend
      • docker push your_username/do-visit-counter-backend:v2

      Our distributed tracing system requires one final piece: We want to inject Jaeger sidecars into our application pods to listen to traces from the pod and forward them to the Jaeger server. For that, we need to add an annotation to our manifests.

      Open the manifest for the frontend:

      • nano ./frontend/deploy_frontend.yaml

      Add the highlighted code. Note that we are also replacing our image with the v2 version. Make sure to revise that line and add your Docker Hub username:

      ./frontend/deploy_frontend.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: do-visit-counter-frontend
        labels:
          name: do-visit-counter-frontend
        annotations:
          "sidecar.jaegertracing.io/inject": "true"
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: do-visit-counter-frontend
        template:
          metadata:
            labels:
              app: do-visit-counter-frontend
          spec:
            containers:
              - name: do-visit-counter-frontend
                   image: your_dockerhub_username/do-visit-counter-frontend:v2
                imagePullPolicy: Always
                env:
                  - name: COUNTER_ENDPOINT
                    value: "http://do-visit-counter-backend.default.svc.cluster.local:5000"
                ports:
                  - name: frontend-port
                    containerPort: 8000
                    protocol: TCP
      

      This annotation will inject a Jaeger sidecar into our pod.

      Save and close the file.

      Now open the manifest for the backend:

      • nano ./backend/deploy_backend.yaml

      Repeat the process, adding the highlighted lines to inject the Jaeger sidecar and updating your image tag:

      ./backend/deploy_backend.yaml

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: do-visit-counter-backend
        labels:
          name: do-visit-counter-backend
        annotations:
          "sidecar.jaegertracing.io/inject": "true"
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: do-visit-counter-backend
        template:
          metadata:
            labels:
              app: do-visit-counter-backend
          spec:
            containers:
              - name: do-visit-counter-backend
                   image: your_dockerhub_username/do-visit-counter-backend:v2
                imagePullPolicy: Always
                ports:
                  - name: backend-port
                    containerPort: 5000
                    protocol: TCP
      ---
      apiVersion: v1
      kind: Service
      metadata:
          name: do-visit-counter-backend
      spec:
          selector:
              app: do-visit-counter-backend
          ports:
              - protocol: TCP
                port: 5000
                targetPort: 5000
      

      With our new manifests in place, we need to apply them to the cluster and wait for the pods
      to create.

      Let’s delete our old resources:

      • kubectl delete -f ./frontend/deploy_frontend.yaml
      • kubectl delete -f ./backend/deploy_backend.yaml

      And then replace them:

      • kubectl apply -f ./frontend/deploy_frontend.yaml
      • kubectl apply -f ./backend/deploy_backend.yaml

      This time the pods for our applications will consist of two containers: one for the application and a second for the Jaeger sidecar.

      Use kubectl to check this:

      Our application pods now appear with 2/2 in the READY column:

      Output

      NAME READY STATUS RESTARTS AGE jaeger-operator-547567dddb-rxsd2 1/1 Running 0 23m simplest-759cb7d586-q6x28 1/1 Running 0 22m do-visit-counter-backend-694c7db576-jcsmv 2/2 Running 0 73s do-visit-counter-frontend-6d7d47f955-lwdnf 2/2 Running 0 42s

      With our sidecars and instrumentation in place, now we can rerun our program and investigate the traces in the Jaeger UI.

      Step 5 — Investigating Traces in Jaeger

      Now we can reap the benefits of tracing. The goal here is to see what call might be a performance issue by looking at the Jaeger UI. Of course, if we want to see some traces in the UI, we first have to generate some data by using our application.

      Let’s set this up by opening a second and third terminal window. We will use two windows to port-forward Jaeger and our application and the third to send HTTP requests to the frontend from our machine via curl.

      In the first window, forward the port for the frontend service:

      • kubectl port-forward $(kubectl get pods -l=app="do-visit-counter-frontend" -o name) 8000:8000

      In the second window, forward the port for Jaeger:

      • kubectl port-forward $(kubectl get pods -l=app="jaeger" -o name) 16686:16686

      In the third window, use curl in a loop to generate 10 HTTP requests:

      for i in 0 1 2 3 4 5 6 7 8 9; do curl localhost:8000; done
      

      You will receive an output like before:

      Output

      Hello, World! You're visitor number 1 in here! Hello, World! You're visitor number 2 in here! . . . Hello, World! You're visitor number 10 in here!

      This will give us enough different data points to compare them in the visualization.

      Open a browser and navigate to http://localhost:16686. Set the Service dropdown menu to service and change limit results to 30. Press Find Traces.

      The traces from our application will appear in the graph:

      Jaeger traces

      Here, we see that different calls to the service have different execution times. Jaeger has traced how long our applications take to process information and which functions contribute the most time. Notice how, as a result of our sleep function, the time it takes for our hello_world() function to complete is highly variable. This is very suspicious, and it gives us a place to focus our investigation. Jaeger has effectively visualized the performance leak inside our distributed application.

      By implementing tracing and using the Jaeger UI, we were able to find the cause of our irregular response time.

      Conclusion

      In this article, we set up a distributed tracing system using Jaeger and added instrumentation to a small application. Now we can deploy other workloads to the cluster, inject Jaeger sidecars, and see how our various services interact and what operations are taking the most time.

      Finding performance bottlenecks in applications using multiple services is much faster with distributed tracing. This example, however, demonstrates only a fraction of Jaeger’s potential. In a more complex production environment, you can use Jaeger to compare different traces and really drill down on performance leaks. The complex visualizations that Jaeger can produce are quite impressive and very useful. To learn more about how Jaeger can help you monitor and resolve performance issues in your cluster, visit their official documentation.



      Source link

      How To Implement Pagination in MySQL with PHP on Ubuntu 18.04


      The author selected the the Apache Software Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      Pagination is the concept of constraining the number of returned rows in a recordset into separate, orderly pages to allow easy navigation between them, so when there is a large dataset you can configure your pagination to only return a specific number of rows on each page. For example, pagination can help to avoid overwhelming users when a web store contains thousands of products by reducing the number of items listed on a page, as it’s often unlikely a user will need to view every product. Another example is an application that shows records on a mobile device; enabling pagination in such a case would split records into multiple pages that can fit better on a screen.

      Besides the visual benefits for end-users, pagination makes applications faster because it reduces the number of records that are returned at a time. This limits the data that needs to be transmitted between the client and the server, which helps preserve server resources such as RAM.

      In this tutorial, you’ll build a PHP script to connect to your database and implement pagination to your script using the MySQL LIMIT clause.

      Prerequisites

      Before you begin, you will need the following:

      Step 1 — Creating a Database User and a Test Database

      In this tutorial you’ll create a PHP script that will connect to a MySQL database, fetch records, and display them in an HTML page within a table. You’ll test the PHP script in two different ways from your web browser. First, creating a script without any pagination code to see how the records are displayed. Second, adding page navigation code in the PHP file to understand how pagination works practically.

      The PHP code requires a MySQL user for authentication purposes and a sample database to connect to. In this step you’ll create a non-root user for your MySQL database, a sample database, and a table to test the PHP script.

      To begin log in to your server. Then log in to your MySQL server with the following command:

      Enter the root password of your MySQL server and hit ENTER to continue. Then, you’ll see the MySQL prompt. To create a sample database, which we will call test_db in this tutorial, run the following command:

      You will see the following output:

      Output

      Query OK, 1 row affected (0.00 sec)

      Then, create a test_user and grant the user all privileges to the test_db. Replace PASSWORD with a strong value:

      • GRANT ALL PRIVILEGES ON test_db.* TO 'test_user'@'localhost' IDENTIFIED BY 'PASSWORD';

      Output

      Query OK, 1 row affected (0.00 sec)

      Reload the MySQL privileges with:

      Output

      Query OK, 1 row affected (0.00 sec)

      Next, switch to the test_db database to start working directly on the test_db database:

      Output

      Database changed

      Now create a products table. The table will hold your sample products—for this tutorial you’ll require only two columns for the data. The product_id column will serve as the primary key to uniquely identify each record. You’ll use the product_name field to differentiate each item by name:

      • Create table products (product_id BIGINT PRIMARY KEY, product_name VARCHAR(50) NOT NULL ) Engine = InnoDB;

      Output

      Query OK, 0 rows affected (0.02 sec)

      To add ten test products to the products table run the following SQL statements:

      • Insert into products(product_id, product_name) values ('1', 'WIRELESS MOUSE');
      • Insert into products(product_id, product_name) values ('2', 'BLUETOOTH SPEAKER');
      • Insert into products(product_id, product_name) values ('3', 'GAMING KEYBOARD');
      • Insert into products(product_id, product_name) values ('4', '320GB FAST SSD');
      • Insert into products(product_id, product_name) values ('5', '17 INCHES TFT');
      • Insert into products(product_id, product_name) values ('6', 'SPECIAL HEADPHONES');
      • Insert into products(product_id, product_name) values ('7', 'HD GRAPHIC CARD');
      • Insert into products(product_id, product_name) values ('8', '80MM THERMAL PRINTER');
      • Insert into products(product_id, product_name) values ('9', 'HDMI TO VGA CONVERTER');
      • Insert into products(product_id, product_name) values ('10', 'FINGERPRINT SCANNER');

      You’ll see this output:

      Output

      Query OK, 1 row affected (0.02 sec)

      Verify that the products were inserted to the table by running:

      You’ll see the products in your output within the two columns:

      Output

      +------------+-----------------------+ | product_id | product_name | +------------+-----------------------+ | 1 | WIRELESS MOUSE | | 2 | BLUETOOTH SPEAKER | | 3 | GAMING KEYBOARD | | 4 | 320GB FAST SSD | | 5 | 17 INCHES TFT | | 6 | SPECIAL HEADPHONES | | 7 | HD GRAPHIC CARD | | 8 | 80MM THERMAL PRINTER | | 9 | HDMI TO VGA CONVERTER | | 10 | FINGERPRINT SCANNER | +------------+-----------------------+ 10 rows in set (0.00 sec)

      Exit MySQL:

      With the sample database, table, and test data in place, you can now create a PHP script to display data on a web page.

      Now you’ll create a PHP script that connects to the MySQL database that you created in the previous step and list the products in a web browser. In this step, your PHP code will run without any form of pagination to demonstrate how non-split records show on a single page. Although you only have ten records for testing purposes in this tutorial, seeing the records without pagination will demonstrate why segmenting data will ultimately create a better user experience and put less burden on the server.

      Create the PHP script file in the document root of your website with the following command:

      • sudo nano /var/www/html/pagination_test.php

      Then add the following content to the file. Remember to replace PASSWORD with the correct value of the password that you assigned to the test_user in the previous step:

      /var/www/html/pagination_test.php

      <?php
      
      try {
      
          $pdo = new PDO("mysql:host=localhost;dbname=test_db", "test_user", "PASSWORD");
          $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
          $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
      
          $sql="select * from products";
      
          $stmt = $pdo->prepare($sql);
      
          $stmt->execute();
      
          echo "<table border='1' align='center'>";
      
          while ( ($row = $stmt->fetch(PDO::FETCH_ASSOC) ) !== false) {
              echo "<tr>";
      
              echo "<td>".$row['product_id']."</td>";
      
              echo "<td>".$row['product_name']."</td>";
      
              echo "</tr>";
      
          }
      
          echo "</table>";
      
      }
      
        catch(PDOException $e)
      
      {
          echo  $e->getMessage();
      }
      
      ?>
      

      Save the file by pressing CTRL+X, Y, and ENTER.

      In this script you’re connecting to the MySQL database using the PDO (PHP Data Object) library with the database credentials that you created in Step 1.

      PDO is a light-weight interface for connecting to databases. The data access layer is more portable and can work on different databases with just minor code rewrites. PDO has greater security since it supports prepared statements—a feature for making queries run faster in a secure way.

      Then, you instruct the PDO API to execute the select * from products statement and list products in an HTML table without pagination. The line $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false); ensures that the data types are returned as they appear in the database. This means that PDO will return the product_id as an integer and the product_name as a string. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); instructs PDO to throw an exception if an error is encountered. For easier debugging you’re catching the error inside the PHP try{}...catch{} block.

      To execute the /var/www/html/pagination_test.php PHP script file that you’ve created, visit the following URL replacing your-server-IP with the public IP address of your server:

      http://your-server-IP/pagination_test.php
      

      You’ll see a page with a table of your products.

      MySQL Records Displayed with a PHP script - No Pagination

      Your PHP script is working as expected; listing all products on one page. If you had thousands of products, this would result in a long loop as the products are fetched from the database and rendered on the PHP page.

      To overcome this limitation, you will modify the PHP script and include the MySQL LIMIT clause and some navigation links at the bottom of the table to add pagination functionality.

      In this step your goal is to split the test data into multiple and manageable pages. This will not only enhance readability but also use the resources of the server more efficiently. You will modify the PHP script that you created in the previous step to accommodate pagination.

      To do this, you’ll be implementing the MySQL LIMIT clause. Before adding this to the script, let’s see an example of the MySQL LIMIT syntax:

      • Select [column1, column2, column n...] from [table name] LIMIT offset, records;

      The LIMIT clause takes two arguments as shown toward the end of this statement. The offset value is the number of records to skip before the first row. records sets the maximum number of records to display per page.

      To test pagination, you’ll display three records per page. To get the total number of pages, you must divide the total records from your table with the rows that you want to display per page. You then round the resulting value to the nearest integer using PHP Ceil function as shown in the following PHP code snippet example:

      $total_pages=ceil($total_records/$per_page);
      

      Following is the modified version of the PHP script with the full pagination code. To include the pagination and navigation codes, open the /var/www/html/pagination_test.php file:

      • sudo nano /var/www/html/pagination_test.php

      Then, add the following highlighted code to your file:

      /var/www/html/pagination_test.php

      <?php
      
      try {
      
          $pdo = new PDO("mysql:host=localhost;dbname=test_db", "test_user", "PASSWORD");
          $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
          $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
      
          /* Begin Paging Info */
      
          $page=1;
      
          if (isset($_GET['page'])) {
              $page=filter_var($_GET['page'], FILTER_SANITIZE_NUMBER_INT);
          }
      
          $per_page=3;
      
          $sqlcount="select count(*) as total_records from products";
          $stmt = $pdo->prepare($sqlcount);
          $stmt->execute();
          $row = $stmt->fetch();
          $total_records= $row['total_records'];
      
          $total_pages=ceil($total_records/$per_page);
      
          $offset=($page-1)*$per_page;
      
          /* End Paging Info */
      
          $sql="select * from products limit $offset,$per_page";
      
          $stmt = $pdo->prepare($sql);
      
          $stmt->execute();
      
      
          echo "<table border='1' align='center'>";
      
          while ( ($row = $stmt->fetch(PDO::FETCH_ASSOC) ) !== false) {
              echo "<tr>";
      
              echo "<td>".$row['product_id']."</td>";
      
              echo "<td>".$row['product_name']."</td>";
      
              echo "</tr>";
      
          }
      
          echo "</table>";
      
          /* Begin Navigation */
      
          echo "<table border='1' align='center'>";
      
          echo "<tr>";
      
          if( $page-1>=1) {
              echo "<td><a href=".$_SERVER['PHP_SELF']."?page=".($page-1).">Previous</a></td>";
          }
      
          if( $page+1<=$total_pages) {
              echo "<td><a href=".$_SERVER['PHP_SELF']."?page=".($page+1).">Next</a></td>";
          }
      
          echo "</tr>";
      
          echo "</table>";
      
          /* End Navigation */
      
      }
      
      catch(PDOException $e) {
              echo  $e->getMessage();
      }
      
      ?>
      

      In your file you’ve used additional parameters to execute paging:

      • $page : This variable holds the current page in your script. When moving between the pages, your script retrieves a URL parameter named page using the $_GET['page'] variable.
      • $per_page: This variable holds the maximum records that you want to be displayed per page. In your case, you want to list three products on each page.
      • $total_records: Before you list the products, you’re executing a SQL statement to get a total count of records in your target table and assigning it to the $total_records variable.
      • $offset: This variable represents the total records to skip before the first row. This value is calculated dynamically by your PHP script using the formula $offset=($page-1)*$per_page. You may adapt this formula to your PHP pagination projects. Remember you can change the $per_page variable to suit your needs. For instance, you might change it to a value of 50 to display fifty items per page if you’re running a website or another amount for a mobile device.

      Again, visit your IP address in a browser and replace your_server_ip with the public IP address of your server:

      http://your_server_ip/pagination_test.php
      

      You’ll now see some navigation buttons at the bottom of the page. On the first page, you will not get a Previous button. The same case happens on the last page where you will not get the Next page button. Also, note how the page URL parameter changes as you visit each page.

      MySQL Records Displayed with a PHP script with Pagination - Page 1

      MySQL Records Displayed with a PHP script with Pagination - Page 2

      Final page of MySQL Records Displayed with a PHP script with Pagination - Page 4

      The navigation links at the bottom of the page are achieved using the following PHP code snippet from your file:

      /var/www/html/pagination_test.php

      . . .
          if( $page-1>=1) {
              echo "<td><a href=".$_SERVER['PHP_SELF']."?page=".($page-1).">Previous</a></td>";
          }
      
          if( $page+1<=$total_pages) {
              echo "<td><a href=".$_SERVER['PHP_SELF']."?page=".($page+1).">Next</a></td>";
          }
      . . .
      

      Here, the $page variable represents the current page number. Then, to get the previous page, the code will minus 1 from the variable. So, if you’re on page 2, the formula (2-1) will give you a result of 1 and this will be the previous page to appear in the link. However, keep in mind that it will only show the previous page if there is a result greater or equal to 1.

      Similarly, to get to the next page, you add one to the $page variable and you must also make sure that the $page result that we append to the page URL parameter is not greater than the total pages that you’ve calculated in your PHP code.

      At this point, your PHP script is working with pagination and you are able to implement the MySQL LIMIT clause for better record navigation.

      Conclusion

      In this tutorial, you implemented paging in MySQL with PHP on an Ubuntu 18.04 server. You can use these steps with a larger recordset using the PHP script to include pagination. By using pagination on your website or application you can create better user navigation and optimum resource utilization on your server.

      From here you could consider further optimization for your database and other database tasks with these tutorials:



      Source link