Skip to content

Commit

Permalink
An initial stab at Airlift documentation
Browse files Browse the repository at this point in the history
I used Dropwizard as a template. Note: this is only a first attempt.
If you like this direction I will continue otherwise let me know what
to do differently.
  • Loading branch information
Randgalt authored and martint committed Jun 18, 2021
1 parent 749d356 commit 0da1b8f
Show file tree
Hide file tree
Showing 11 changed files with 857 additions and 0 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,37 @@
Airlift is a framework for building REST services in Java.

This project is used as the foundation for distributed systems like [Trino (formerly PrestoSQL)](https://trino.io).

Airlift pulls together stable, mature libraries from the Java ecosystem into a simple, light-weight package that lets you focus on getting things done and includes built-in support for configuration, metrics, logging, dependency injection, and much more, enabling you and your team to ship a production-quality web service in the shortest time possible.

Airlift takes the best-of-breed libraries from the Java ecosystem and glues them together based on years of experience in building high performance Java services without getting in your way and without forcing you into a large, proprietary framework.

## Getting Started

- [Overview](docs/overview.md)
- [Getting Started](docs/getting_started.md)
- Then see [Next Steps](docs/next_steps.md)

## Reference

- [Configuration](docs/ref_configuration.md)
- [Lifecycle/Bootstrapping](docs/ref_lifecycle.md)
- TBD - Concurrency
- TBD - Database Pooling
- TBD - Discovery
- TBD - Events
- TBD - HTTP server
- TBD - HTTP client
- TBD - Packaging
- TBD - Logging
- TBD - Tracing
- TBD - Maven BOM
- TBD - Jackson/JSON

## Recipes

- How do I ... [do conditional binding based on config](docs/recipes.md#how-do-i-do-conditional-binding-based-on-a-config-value)
- How do I ... [serve static HTML files](docs/recipes.md#how-do-i-serve-static-html-files)
- How do I ... [package my service](docs/recipes.md#how-do-i-package-my-service)
- TBD - How do I do ... ?

171 changes: 171 additions & 0 deletions docs/add_config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
[◀︎ Airlift](../README.md)[◀︎ Getting Started](getting_started.md)[◀︎ Next Steps](next_steps.md)

## Add Configuration

Airlift's Configuration support is simple and straightforward. Let's add configuration to our
example project.

Currently, the message returned by the `hello()` method is hard coded. Let's make it configurable:

### Step 1 - Add Needed Dependencies

We need a few additional dependencies. Add the following to the dependencies section of your
`pom.xml` file:

```xml
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>

<dependency>
<groupId>io.airlift</groupId>
<artifactId>configuration</artifactId>
</dependency>

<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
```

### Step 2 - Create the Config Class

Create `src/main/java/example/ServiceConfig.java` with the following content:

```java
package example;

import io.airlift.configuration.Config;

import javax.validation.constraints.NotBlank;

public class ServiceConfig
{
private String helloMessage = "Hello Airlift!";

@NotBlank
public String getHelloMessage()
{
return helloMessage;
}

@Config("hello.message")
public ServiceConfig setHelloMessage(String helloMessage)
{
this.helloMessage = helloMessage;
return this;
}
}
```

### Step 3 - Bind the Config Class

We need to bind the Config class in our Guice module. Airlift's `configBinder` is used for this.
Edit your `ServiceModule.java` file so that it looks like this (add the new import and new binding):

```java
package example;

import com.google.inject.Binder;
import com.google.inject.Module;

import static io.airlift.configuration.ConfigBinder.configBinder; // NEW LINE
import static io.airlift.jaxrs.JaxrsBinder.jaxrsBinder;

public class ServiceModule
implements Module
{
@Override
public void configure(Binder binder)
{
jaxrsBinder(binder).bind(ServiceResource.class);
configBinder(binder).bindConfig(ServiceConfig.class); // NEW LINE
}
}
```

### Step 4 - Use the Config Object

Modify the Service resource to use the config object. Edit `ServiceResource.java` to look like this:

```java
package example;

import javax.inject.Inject; // NEW
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/v1/service")
public class ServiceResource
{
private final ServiceConfig config; // NEW

@Inject // NEW
public ServiceResource(ServiceConfig config) // NEW
{ // NEW
this.config = config; // NEW
} // NEW

@GET
@Produces(MediaType.APPLICATION_JSON)
public String hello()
{
return config.getHelloMessage(); // CHANGED
}
}
```

### Step 5 - Test The Change

Build, run, test:

```
mvn clean verify
mvn exec:java -Dexec.mainClass=example.Service -Dnode.environment=test
```

_In a different terminal_ : `curl http://localhost:8080/v1/service`

Then CTRL+C the service.

-----

We assign a default value to `helloMessage` in `ServiceConfig` so Airlift uses that by default. Now
let's run specifying a different value on the command line.

```
mvn exec:java -Dexec.mainClass=example.Service -Dnode.environment=test -Dhello.message=Changed
```

_In a different terminal_ : `curl http://localhost:8080/v1/service`

Then CTRL+C the service.

### Step 6 - Config File

While it's simple to pass configuration on the command line, for production you will use a configuration
properties file. Airlift configuration supports standard field=value property files.

Create `config.properties` in the root of your project with the following content:

```java
node.environment=test
hello.message=Hello from a config file!
```

Now run and tell Airlift where to find the configuration file:

```java
mvn exec:java -Dexec.mainClass=example.Service -Dconfig=config.properties
```

_In a different terminal_ : `curl http://localhost:8080/v1/service`

Then CTRL+C the service.

## Next Steps

Return to [Next Steps](next_steps.md) to learn about other Airlift features.
40 changes: 40 additions & 0 deletions docs/add_logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[◀︎ Airlift](../README.md)[◀︎ Getting Started](getting_started.md)[◀︎ Next Steps](next_steps.md)

## Add Logging

Airlift includes a simple logging API based on the JDK logging package.

### Step 1 - Add Needed Dependencies

We need a few additional dependencies. Add the following to the dependencies section of your
`pom.xml` file:

```xml
<dependency>
<groupId>io.airlift</groupId>
<artifactId>log</artifactId>
</dependency>

<dependency>
<groupId>io.airlift</groupId>
<artifactId>log-manager</artifactId>
</dependency>
```

### Step 2 - Start Logging

Example logging:

```java
import io.airlift.log.Logger;

public class MyClass
{
private static final Logger LOG = Logger.get(MyClass.class);

public void fooBar(String argument)
{
LOG.info("Formatted output %s", argument);
}
}
```
146 changes: 146 additions & 0 deletions docs/add_metrics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
[◀︎ Airlift](../README.md)[◀︎ Getting Started](getting_started.md)[◀︎ Next Steps](next_steps.md)

## Add Metrics

Airlift incorporates the [jmxutils](https://github.com/martint/jmxutils) library. Exposing
JMX metrics is very simple.

### Step 1 - Add Needed Dependencies

We need a few additional dependencies. Add the following to the dependencies section of your
`pom.xml` file:

```xml
<dependency>
<groupId>org.weakref</groupId>
<artifactId>jmxutils</artifactId>
</dependency>

<dependency>
<groupId>io.airlift</groupId>
<artifactId>jmx</artifactId>
</dependency>
```

### Step 2 - Add the JMX Modules

Edit `Service.java` to look like this:

```java
package example;

import io.airlift.bootstrap.Bootstrap;
import io.airlift.event.client.EventModule;
import io.airlift.http.server.HttpServerModule;
import io.airlift.jaxrs.JaxrsModule;
import io.airlift.jmx.JmxHttpModule; // NEW
import io.airlift.jmx.JmxModule; // NEW
import io.airlift.jmx.http.rpc.JmxHttpRpcModule; // NEW
import io.airlift.json.JsonModule;
import io.airlift.node.NodeModule;
import org.weakref.jmx.guice.MBeanModule; // NEW

public class Service
{
public static void main(String[] args)
{
Bootstrap app = new Bootstrap(new ServiceModule(),
new JmxModule(), // NEW
new JmxHttpModule(), // NEW
new JmxHttpRpcModule(), // NEW
new MBeanModule(), // NEW
new NodeModule(),
new HttpServerModule(),
new EventModule(),
new JsonModule(),
new JaxrsModule());
app.strictConfig().initialize();
}

private Service() {}
}
```

### Step 3 - Expose Some Metrics

Let's add a counter to our REST endpoint. Modify `ServiceResource.java` to look like this:

```java
package example;

import org.weakref.jmx.Managed; // NEW

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import java.util.concurrent.atomic.AtomicLong; // NEW

@Path("/v1/service")
public class ServiceResource
{
private final ServiceConfig config;
private final AtomicLong helloCount = new AtomicLong(); // NEW

@Inject
public ServiceResource(ServiceConfig config)
{
this.config = config;
}

@GET
@Produces(MediaType.APPLICATION_JSON)
public String hello()
{
helloCount.incrementAndGet(); // NEW
return config.getHelloMessage();
}

@Managed // NEW
public long getHelloCount() // NEW
{
return helloCount.get(); // NEW
}
}
```

### Step 4 - Bind for JMX

Expose the JMX value by binding it. Edit `ServiceModule.java` to look like this:

```java
package example;

import com.google.inject.Binder;
import com.google.inject.Module;

import static io.airlift.configuration.ConfigBinder.configBinder;
import static io.airlift.jaxrs.JaxrsBinder.jaxrsBinder;
import static org.weakref.jmx.guice.ExportBinder.newExporter; // NEW

public class ServiceModule
implements Module
{
@Override
public void configure(Binder binder)
{
jaxrsBinder(binder).bind(ServiceResource.class);
configBinder(binder).bindConfig(ServiceConfig.class);
newExporter(binder).export(ServiceResource.class).withGeneratedName(); // NEW
}
}
```

### Step 5 - Test The Change

Build, run, test:

```
mvn clean verify
mvn exec:java -Dexec.mainClass=example.Service -Dnode.environment=test
```

Open `jconsole` or your preferred JMX tool and locate the "example" MBean. You will see the count
increment for each time you `curl http://localhost:8080/v1/service`.
Loading

0 comments on commit 0da1b8f

Please sign in to comment.