Dubbo for the First Experience of RPC Framework

Keywords: Dubbo xml Spring Apache

Dubbo for the First Experience of RPC Framework

Version description: dubbo 2.7.2

Dubbo is an RPC framework of Ali Open Source, which was mentioned recently when learning micro services. So we have an introductory experience of Dubbo. Here we mainly experience several configurations of Dubbo, such as XML configuration, API configuration, annotation configuration, and integration of Dubbo in Springboot. We also experience several registries, such as simple, zk, redis, multicast.

Dubbo's official website is rich in content and supports Chinese. Address: http://dubbo.apache.org/zh-cn/docs/user/quick-start.html

1 Dubbo configuration usage

1.1 Configuration based on native API

Official Documents: http://dubbo.apache.org/zh-cn/docs/user/configuration/api.html

How to say, when referring to the official website, we stepped on a lot of pits, because the registry provided by the official website can not be used, is the first contact, has taken many detours, and oneself is a rectum, we have to implement a registry by oneself, and then refer to the example of Simple registry given by the official website, there are two compelling points. I'm obsessive-compulsive, first of all, using XML configuration to create registry services, and I just want to implement it through native API, the other one.

    <! - Simple registry implementation, self-expanding to achieve cluster and state synchronization - >
    <bean id="registryService" class="org.apache.dubbo.registry.simple.SimpleRegistryService" />

The class org. apache. dubbo. registry. simple. SimpleRegistry Service mentioned above, I can't find it. It's been a day of tangling over these two issues. It was finally solved and found in the Dubbo source test file on github. Ten thousand CNMs are running in my heart. Returning to the topic, using native API to configure is actually creating their own instance, passing in configuration parameters, starting services. Here is a step-by-step explanation.

1.1.1 Project preparation

First create a module called dubbo-demo-api, and then create three sub-modules under that module

dubbo-demo-api/
├── dubbo-demo-api-api
├── dubbo-demo-api-consumer
├── dubbo-demo-api-provider
└── pom.xml
  • dubbo-demo-api-api: Provide demo interface
  • dubbo-demo-api-consumer: service consumers
  • dubbo-demo-api-provider: service provider

1.1.2 dubbo-demo-api-api module

This module mainly provides the interface for demonstration, and creates the DemoService interface under the learn.demo.dubbo.api.api package, as follows

package learn.demo.dubbo.api.api;

/**
 * Created by shirukai on 2019-06-20 09:23
 * DemoService Interface
 */
public interface DemoService {
    String sayHello(String name);
}

1.1.3 dubbo-demo-api-provider module

This module is to create a dubbo provider service. First, we need to introduce dubbo dependencies

        <!-- dubbo -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.2</version>
        </dependency>

Then introduce api module

        <dependency>
            <groupId>learn.demo</groupId>
            <artifactId>dubbo-demo-api-api</artifactId>
            <version>1.0</version>
            <scope>compile</scope>
        </dependency>

1.1.3.1 Implementing DemoService Interface

The DemoService interface we defined in the API module above is implemented under the package learn.demo.dubbo.api.provider. Create a class named DemoServiceImpl, which reads as follows:

package learn.demo.dubbo.api.provider;

import learn.demo.dubbo.api.api.DemoService;

/**
 * Created by shirukai on 2019-06-20 09:25
 * DemoService Interface Implementation
 */
public class DemoServiceImpl implements DemoService {
    @Override
    public String sayHello(String name) {
        return "This service base in API.\nHello " + name;
    }
}

1.1.3.2 Implementation of Simple Registry

Here, before implementing Provider, we first implement the following Simple Registry. We also mentioned that when we talk about Simple Registry on the official website, we use Spring's XML configuration to generate beans and then create services. Moreover, the Simple Registry Service class mentioned in the article can not be found. After the ninety-eighty-one difficulty, we can use API here. Implement a Simple Registry. There are several classes needed: AbstractRegistry Service, SimpleRegistry Exporter, SimpleRegistry Service. These classes can be found on dubbo's GitHub at https://github.com/apache/dubbo/tree/master/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo.

We can create the org.apache.dubbo.registry package in our own project and then copy the three classes into it.

Create the SimpleRegistryService Provider class under the learn.demo.dubbo.api.provider package to provide Registry services. The code is as follows:

package learn.demo.dubbo.api.provider;

import org.apache.dubbo.registry.SimpleRegistryExporter;

/**
 * Created by shirukai on 2019-06-20 09:32
 * <p>
 * Simple Registry Service Provider
 * Simple Registry Based on Registry Service Interface
 * Code location:
 * https://github.com/apache/dubbo/tree/master/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo
 */
public class SimpleRegistryServiceProvider {
    public static void main(String[] args) throws Exception {

        // Exposure registry service, port 9090
        SimpleRegistryExporter.export(9090);

        // Arbitrary Input Exit
        System.in.read();
    }
}

1.1.3.3 Implementation of DemoService Provider

Using Dubbo's native API to implement the producer, the general process is as follows:

  1. To create application configuration Application Config, we can set application name, qos port and so on.
  2. Create registry configuration Registry Config, you can set the type of registry, address, port, user name, password, etc.
  3. Create a Service Config service configuration. This instance is important and has a connection to the registry.
  4. Exposure service export

The contents are as follows:

package learn.demo.dubbo.api.provider;

import learn.demo.dubbo.api.api.DemoService;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;

/**
 * Created by shirukai on 2019-06-20 09:30
 * <p>
 * DemoService Producer
 * The Demo registry uses a custom registry, SimpleRegistry Service
 * So before starting the service, you need to start the registry first.
 */
public class DemoServiceProvider {
    public static void main(String[] args) throws Exception {
        // Application Configuration
        ApplicationConfig applicationConfig = new ApplicationConfig("api-demo-service-provider");

        // qos default port 2222222 needs to be manually modified when multiple services are started locally at the same time
        applicationConfig.setQosPort(22222);

        // Registry Configuration
        RegistryConfig registryConfig = new RegistryConfig("127.0.0.0:9090");

        // Service configuration. This instance is very heavy. It encapsulates the connection to the registry. Please cache it yourself, otherwise it may cause memory and connection leaks.
        ServiceConfig<DemoService> service = new ServiceConfig<>();
        service.setApplication(applicationConfig);
        service.setRegistry(registryConfig);
        service.setInterface(DemoService.class);
        service.setRef(new DemoServiceImpl());

        // Exposure services
        service.export();

        // Enter and exit arbitrarily
        System.in.read();

    }
}

1.1.4 dubbo-demo-api-consumer module

dubbo service consumers still need to introduce jar packages of dubbo and custom api.

<!-- dubbo -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.2</version>
        </dependency>
        <dependency>
            <groupId>learn.demo</groupId>
            <artifactId>dubbo-demo-api-api</artifactId>
            <version>1.0</version>
            <scope>compile</scope>
        </dependency>

The implementation of consumer is similar to the provider step

  1. Create Application Config for Application Configuration
  2. Create a configuration center to configure Registry Config
  3. Create Service Reference Config
  4. Get an interface instance

The code is as follows:

package learn.demo.api.consumer;

import learn.demo.dubbo.api.api.DemoService;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;

/**
 * Created by shirukai on 2019-06-20 09:53
 * DemoService Consumer
 */
public class DemoServiceConsumer {
    public static void main(String[] args) {
        // Application Configuration
        ApplicationConfig applicationConfig = new ApplicationConfig("dubbo-demo-api-consumer");
        applicationConfig.setQosPort(22223);

        // Configure the registry
        RegistryConfig registryConfig = new RegistryConfig("127.0.0.1:9090");
        ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
        reference.setApplication(applicationConfig);
        reference.setRegistry(registryConfig);
        reference.setInterface(DemoService.class);
        // Access to services
        DemoService service = reference.get();
        // Service invocation
        String message = service.sayHello("dubbo !");
        System.out.println(message);
    }
}

1.2 XML-based configuration

Above we introduced the configuration of Dubbo using API, which is convenient to integrate other systems, but the implementation is tedious. So the government provides Spring's XML-based configuration, which relies on Spring to create services by loading beans. Official Documents: http://dubbo.apache.org/zh-cn/docs/user/configuration/xml.html

1.2.1 Project preparation

First create a module called dubbo-demo-xml, and then create three sub-modules under that module

dubbo-demo-xml/
├── dubbo-demo-xml-api
├── dubbo-demo-xml-consumer
├── dubbo-demo-xml-provider
└── pom.xml
  • dubbo-demo-xml-api: Provide demo interface
  • dubbo-demo-xml-consumer: service consumers
  • dubbo-demo-xml-provider: service provider

1.2.2 dubbo-demo-xml-api module

Same as dubbo-demo-api-api.

1.2.3 dubbo-demo-xml-provider module

Ibid. introduce api and dubbo dependencies.

1.2.3.1 DemoServiceImpl

Implement the defined api interface.

package learn.demo.dubbo.xml.provider;

import learn.demo.dubbo.xml.api.DemoService;

/**
 * Created by shirukai on 2019-06-20 10:19
 * Experimental DemoService Interface
 */
public class DemoServiceImpl implements DemoService {
    @Override
    public String sayHello(String name) {
        return "This service base in XML.\nHello " + name;
    }
}

1.2.3.2 dubbo-provider.xml

Create dubbo-provider.xml configuration file under resource/spring to configure the provider service of dubbo. Specific parameters refer to the official website.

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- provider's application name, used for tracing dependency relationship -->
    <dubbo:application name="demo-xml-provider">
        <!-- Appoint qos Port, configuration priority is lower than dubbo.properties-->
        <dubbo:parameter key="qos.port" value="22222"/>
    </dubbo:application>

    <!-- Radio-based Registration Center -->
    <dubbo:registry address="multicast://224.5.6.7:1234"/>

    <!-- use dubbo protocol to export service on port 20880 -->
    <dubbo:protocol name="dubbo"/>

    <!-- service implementation, as same as regular local bean -->
    <bean id="demoService" class="learn.demo.dubbo.xml.provider.DemoServiceImpl"/>

    <!-- declare the service interface to be exported -->
    <dubbo:service interface="learn.demo.dubbo.xml.api.DemoService" ref="demoService"/>

</beans>

1.2.3.3 DemoServiceProvider

Provider implementation is relatively simple, using ClassPath Xml Application Context to get the application context from xml, and then start.

package learn.demo.dubbo.xml.provider;

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by shirukai on 2019-06-20 10:18
 * Doubbo Service Provider Based on XML
 */
public class DemoServiceProvider {
    public static void main(String[] args) throws Exception {
        // Getting the application context from the XML configuration file
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
        context.start();
        System.in.read();
    }
}

1.2.4 dubbo-demo-xml-consumer module

Similarly, api and dubbo dependencies need to be introduced into pom.

1.2.4.1 dubbo-consumer.xml

Use XML to configure consumer services. Create dubbo-consumer.xml under resource/spring, as follows

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-xml-consumer"/>

    <!-- Use of the Broadcasting Registry -->
    <dubbo:registry address="multicast://224.5.6.7:1234"/>

    <!-- generate proxy for the remote service, then demoService can be used in the same way as the
    local regular interface -->
    <dubbo:reference id="demoService" check="false" interface="learn.demo.dubbo.xml.api.DemoService"/>

</beans>

1.2.4.2 DemoServiceConsumer

Use ClassPathXml Application Context to get the application context, and get the corresponding Bean of DemoService interface in the context, then call the interface method.

package learn.demo.dubbo.xml.consumer;

import learn.demo.dubbo.xml.api.DemoService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by shirukai on 2019-06-20 11:07
 * Consumption of Dubbo Services Based on XML
 */
public class DemoServiceConsumer {
    public static void main(String[] args){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");
        context.start();
        DemoService demoService = context.getBean("demoService", DemoService.class);
        String message = demoService.sayHello("world");
        System.out.println(message);
    }
}

1.2.4.3 Supplement, About dubbo Configuration

Dubbo configuration, we can configure through the dubbo.properties file, if I want to modify the qos port, you can only create dubbo.properties file, and then add the following configuration:

# dubbo related configuration file
dubbo.application.qos.port=22223

1.3 Annotation-based Configuration

Official website: http://dubbo.apache.org/zh-cn/docs/user/configuration/annotation.html

dubbo's configuration of annotations is the same as xml, except that we don't need to configure xml, add @Service annotations to the interface implementation classes that need to be exposed, and then inject the relevant configuration into Spring through @Configuration.

1.3.1 Project preparation

First create a module called dubbo-demo-xml, and then create three sub-modules under that module

dubbo-demo-annotation/
├── dubbo-demo-annotation-api
├── dubbo-demo-annotation-consumer
├── dubbo-demo-annotation-provider
└── pom.xml
  • dubbo-demo-annotation-api: Provide demo interface
  • dubbo-demo-annotation-consumer: service consumers
  • dubbo-demo-annotation-provider: service provider

1.3.2 dubbo-demo-annotation-api module

Same as dubbo-demo-api-api module.

1.3.3 dubbo-demo-annotation-provider module

1.3.3.1 DemoServiceImpl

Ibid., implement the DemoService interface, but use @Service to mark that the implementation class exposes the service through Provider.

package learn.demo.dubbo.annotation.provider;

import lear.demo.dubbo.annotation.api.DemoService;
import org.apache.dubbo.config.annotation.Service;

/**
 * Created by shirukai on 2019-06-20 14:04
 * DemoService Implementation class
 */
@Service
public class DemoServiceImpl implements DemoService {
    @Override
    public String sayHello(String name) {
        return "The service base in Annotation.\nHello" + name;
    }
}

1.3.3.2 dubbo-provider.properties

Dubbo providers are configured by dubbo-provider. properties. Create the dubbo-provider.properties file under resource/spring, as follows

dubbo.application.name=dubbo-demo-annotation-provider
dubbo.application.qos.port=22222
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

1.3.3.3 DemoServiceProvider

As with xml configuration, you need to get the application context, just using Annotation Config Application Context, and you need to inject dubbo configuration into Spring through @Configuration, and develop the package path that needs to be scanned, as well as the path where the configuration file is located. The contents are as follows:

package learn.demo.dubbo.annotation.provider;

import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
 * Created by shirukai on 2019-06-20 14:06
 * DemoService Providers
 */
public class DemoServiceProvider {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
        context.start();
        System.in.read();
    }

    @Configuration
    @EnableDubbo(scanBasePackages = "learn.demo.dubbo.annotation.provider")
    @PropertySource("classpath:/spring/dubbo-provider.properties")
    static class ProviderConfiguration {
        @Bean
        public RegistryConfig registryConfig() {
            RegistryConfig registryConfig = new RegistryConfig();
            registryConfig.setAddress("multicast://224.5.6.7:1234");
            return registryConfig;
        }
    }

}

1.3.4 dubbo-demo-annotation-consumer module

1.3.4.1 dubbo-consumer.properties

Configure the relevant configuration of Dubbo consumer through dubbo-consumer.properties, such as service name, registry address, etc.

dubbo.application.name=dubbo-demo-annotation-consumer
dubbo.registry.address=multicast://224.5.6.7:1234

1.3.4.2 DemoServiceComponent

Create DemoService components through @Component and inject interface instances provided by remote Dubbo through @Reference.

package learn.demo.dubbo.annotation.consumer.comp;

import lear.demo.dubbo.annotation.api.DemoService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Component;

/**
 * Created by shirukai on 2019-06-20 14:22
 * DemoService assembly
 */
@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService {
    @Reference
    private DemoService demoService;

    @Override
    public String sayHello(String name) {
        return demoService.sayHello(name);
    }
}

1.3.4.3 DemoServiceConsumer

dubbo-related configuration is injected through @Configuration, and application context is obtained through Annotation Config Application Context.

package learn.demo.dubbo.annotation.consumer;

import lear.demo.dubbo.annotation.api.DemoService;
import learn.demo.dubbo.annotation.consumer.comp.DemoServiceComponent;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
 * Created by shirukai on 2019-06-20 14:10
 * DemoService Consumer
 */
public class DemoServiceConsumer {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
        context.start();
        DemoService service = context.getBean("demoServiceComponent", DemoServiceComponent.class);
        String message = service.sayHello("world");
        System.out.println(message);
    }

    @Configuration
    @EnableDubbo(scanBasePackages = "learn.demo.dubbo.annotation.consumer.comp")
    @PropertySource("classpath:/spring/dubbo-consumer.properties")
    @ComponentScan(value = {"learn.demo.dubbo.annotation.consumer.comp"})
    static class ConsumerConfiguration {

    }
}

2 SpringBoot Integration Dubbo

SpringBoot integrates the simplicity of Dubbo with no more simplicity, no additional configuration is required, just a few dependency packages are introduced.

2.1 Project preparation

First create a module called dubbo-demo-spring boot, and then create three sub-modules under that module

dubbo-demo-springboot/
├── dubbo-demo-springboot-api
├── dubbo-demo-springboot-consumer
├── dubbo-demo-springboot-provider
└── pom.xml
  • Dubbo-demo-spring boot-api: Provides demo interface
  • Dubbo-demo-spring boot-consumer: service consumers
  • Dubbo-demo-spring boot-provider: service provider

2.2 dubbo-demo-spring boot-api module

Like previous api modules, the DemoService interface is defined.

2.3 Dubbo-demo-spring boot-provider module

2.3.1 Introducing Dependency

Because this project is a spring boot project, add the spring boot module to parent

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

Introducing spring boot starter

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

Introducing Dubbo spring boot starter

        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.1</version>
        </dependency>

Introducing dubbo dependencies

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.2</version>
        </dependency>

Introducing api dependencies

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.2</version>
        </dependency>

2.3.2 application.properties

Create application.properties configuration file in resource, add dubbo configuration

spring.application.name=dubbo-spring-boot-provider
# dubbo
dubbo.scan.base-packages=learn.demo.dubbo.springboot.provider
dubbo.protocol.name=dubbo
dubbo.protocol.port=12345
dubbo.registry.address=multicast://224.5.6.7:1234

2.3.3 DemoServiceImpl

Implement the DemoService interface and mark such service instances exposed by dubbo with the @Service annotation of dubbo

package learn.demo.dubbo.springboot.provider;

import learn.demo.dubbo.springboot.api.DemoService;
import org.apache.dubbo.config.annotation.Service;

/**
 * Created by shirukai on 2019-06-20 15:08
 */
@Service(version = "1.0.0")
public class DemoServiceImpl implements DemoService {
    @Override
    public String sayHello(String name) {
        return "The service of dubbo from springboot.\nHello " + name;
    }
}

2.3.4 DemoServiceProvider

Dubbo service provides implementation, mainly starting SpringBoot.

package learn.demo.dubbo.springboot.provider;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Created by shirukai on 2019-06-20 15:08
 */
@SpringBootApplication
public class DemoServiceProvider {
    public static void main(String[] args) {
        SpringApplication.run(DemoServiceProvider.class, args);
    }
}

2.4 dubbo-demo-spring boot-consumer module

2.3.1 Introducing Dependency

Like provider, spring boot-starter, dubbo-spring-boot-starter, dubbo, api dependencies are introduced. In addition, a REST interface is provided, so web service-related dependencies need to be introduced.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

2.3.2 application.properties

spring.application.name=dubbo-spring-boot-consumer
# dubbo
dubbo.scan.base-packages=learn.demo.dubbo.springboot.consumer
dubbo.registry.address=multicast://224.5.6.7:1234

2.3.4 DemoController

Provide a REST interface and invoke RPC services

package learn.demo.dubbo.springboot.consumer.controller;

import learn.demo.dubbo.springboot.api.DemoService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by shirukai on 2019-06-20 16:03
 */
@RestController
public class DemoController {
    @Reference(version = "1.0.0")
    private DemoService demoService;

    @GetMapping(value = "/sayHello")
    public String sayHello(@RequestParam("name") String name) {
        return demoService.sayHello(name);
    }
}

2.3.5 DemoServiceConsumer

The Dubbo Consumser service starts and SpringBoot starts normally.

package learn.demo.dubbo.springboot.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Created by shirukai on 2019-06-20 16:00
 */
@SpringBootApplication
public class DemoServiceConsumer {
    public static void main(String[] args) {
        SpringApplication.run(DemoServiceConsumer.class, args);
    }
}

Posted by dbomb101 on Tue, 20 Aug 2019 21:10:53 -0700