├── docker-compose.yml ├── gen-data.sh ├── outline.txt ├── pom.xml ├── run.sh ├── server.jks ├── sonarqube.sh └── src ├── main ├── docker │ └── Dockerfile ├── java │ └── demo │ │ ├── DemoApplication.java │ │ ├── Product.java │ │ └── ProductRepository.java └── resources │ ├── application.properties │ └── logback.xml └── test └── java └── demo └── DemoApplicationTests.java /docker-compose.yml: -------------------------------------------------------------------------------- 1 | graphite: 2 | image: hopsoft/graphite-statsd 3 | ports: 4 | - "80:80" 5 | - "2003:2003" 6 | - "8125:8125/udp" 7 | 8 | logstash: 9 | image: pblittle/docker-logstash 10 | environment: 11 | - LOGSTASH_CONFIG_URL=https://raw.githubusercontent.com/mstine/statsd-graphite-demo/master/logstash.conf 12 | ports: 13 | - "10042:10042" 14 | - "9292:9292" 15 | - "9200:9200" -------------------------------------------------------------------------------- /gen-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | seq 1000 | while read l ; do 6 | curl -H "Content-Type: application/json" -d "{\"sku\":\"sku1${l}\"}" http://localhost:8080/products ; 7 | s=`gshuf -i 1-10 -n 1` 8 | echo 9 | echo 0.$s 10 | sleep 0.$s 11 | done 12 | -------------------------------------------------------------------------------- /outline.txt: -------------------------------------------------------------------------------- 1 | # The Operationalized Application 2 | 3 | * start.spring.io and choose the 1.3 bits (demo curl!) 4 | * build a simple JPA repository 5 | * export it as a REST endpoint w/ annotations 6 | * change the server to Jetty 7 | * stand up the Spring Boot Actuator bits 8 | * move management to diff context-path 9 | * make the Health endpoint respond to failure modes in the application 10 | * git commit ID in the /info endpoint 11 | 12 | 13 | pl.project13.maven 14 | git-commit-id-plugin 15 | 16 | 17 | TODO RUN mvn clean install !!!! 18 | and 19 | 20 | 21 | 22 | src/main/resources 23 | true 24 | 25 | 26 | 27 | info.build.artifact=${project.artifactId} 28 | info.build.version=${project.version} 29 | 30 | 31 | 32 | * org.springframework.boot:spring-boot-actuator-docs &&& endpoints.hal.path=/hal && add org.webjars:hal-browser:* 33 | * collect custom business metrics using the counter/meter service 34 | * expose a custom REST endpoint that can be instrumented using @RepositoryEventHandler and @HandlerAfter*(T t) 35 | * customize the actuator endpoints and use a custom counterService in the custom 36 | REST endpoint and the Spring Data REST endpoint 37 | * configure custom reporters / writers using DW Metrics and raw Actuator: io.dropwizard.metrics:metrics-graphite && com.timgroup:java-statsd-client 38 | * docker-compose up 39 | * what about stuff you can't monitor in terms of numbers: meters, counters, gauges, etc. 40 | * logback.xml: net.logstash.logback:¸logstash-logback-encoder : 4.2 41 | private static Logger LOGGER = LoggerFactory.getLogger( ProductApplication.class); 42 | LogstashMarker logstashMarker = Markers.append( "event", "products." + k) 43 | LOGGER.info(logstashMarker, "products." + k); 44 | * server.ssl.* 45 | * write out a PID file 46 | * run the executable jar. 47 | * run the executable jar by delegating to -D, environment variables, etc. 48 | * run the executable _executable_ jar. 49 | * make the jar a Docker friendly thing. 50 | 51 | 52 | 53 | 54 | 55 | #!/bin/bash 56 | 57 | set -e 58 | 59 | seq 1000 | while read l ; do 60 | curl -H "Content-Type: application/json" -d '{"sku":"sku1$l"}' http://localhost:8080/products ; 61 | s=`gshuf -i 1-10 -n 1` 62 | echo 0.$s 63 | sleep 0.$s 64 | done 65 | 66 | 67 | 68 | http://peter-on-java.blogspot.com/2013/12/importing-ssl-certificates-to-keystore.html 69 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.test 7 | product-service 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-parent 15 | 1.3.0.RELEASE 16 | 17 | 18 | 19 | 20 | 21 | starbuxman 22 | UTF-8 23 | 1.8 24 | 25 | 26 | 27 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-actuator 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-data-jpa 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-data-rest 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-remote-shell 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-web 55 | 56 | 57 | 58 | com.h2database 59 | h2 60 | runtime 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-test 65 | test 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-actuator-docs 70 | 71 | 75 | 76 | org.springframework.data 77 | spring-data-rest-hal-browser 78 | 79 | 80 | 81 | net.logstash.logback 82 | logstash-logback-encoder 83 | 4.2 84 | 85 | 86 | io.dropwizard.metrics 87 | metrics-graphite 88 | 89 | 90 | com.timgroup 91 | java-statsd-client 92 | 93 | 94 | 95 | 96 | ${project.artifactId} 97 | 98 | 99 | src/main/resources 100 | true 101 | 102 | 103 | 104 | 105 | pl.project13.maven 106 | git-commit-id-plugin 107 | 108 | 109 | org.springframework.boot 110 | spring-boot-maven-plugin 111 | 112 | true 113 | 114 | 115 | 116 | 117 | 118 | com.spotify 119 | docker-maven-plugin 120 | 0.2.3 121 | 122 | 123 | 124 | install 125 | 126 | build 127 | 128 | 129 | 130 | 131 | 132 | ${docker.image.prefix}/${project.artifactId} 133 | src/main/docker 134 | 135 | 136 | / 137 | ${project.build.directory} 138 | ${project.build.finalName}.jar 139 | 140 | 141 | / 142 | ${project.build.directory} 143 | classes/server.jks 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker run -e STATSD_HOST=$STATSD_HOST -e STATSD_PORT=$STATSD_PORT -e GRAPHITE_HOST=$GRAPHITE_HOST -e GRAPHITE_PORT=$GRAPHITE_PORT starbuxman/product-service 4 | -------------------------------------------------------------------------------- /server.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshlong-attic/the-operationalized-application/6a22b8501ef0a00402007ebb0b98dc8155efdf9b/server.jks -------------------------------------------------------------------------------- /sonarqube.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # see https://github.com/SonarSource/docker-sonarqube for more 6 | 7 | docker run -d --name sonarqube -p 9000:9000 -p 9092:9092 sonarqube:5.1 8 | mvn sonar:sonar -Dsonar.host.url=http://$(boot2docker ip):9000 -Dsonar.jdbc.url="jdbc:h2:tcp://$(boot2docker ip)/sonar" 9 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8 2 | VOLUME /tmp 3 | EXPOSE 9000 8000 8081 8080 4 | ADD product-service.jar app.jar 5 | ADD classes/server.jks server.jks 6 | RUN bash -c 'touch /app.jar' 7 | ENTRYPOINT ["java", "-Dserver.ssl.key-store=/server.jks","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] 8 | -------------------------------------------------------------------------------- /src/main/java/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import com.codahale.metrics.graphite.Graphite; 5 | import com.codahale.metrics.graphite.GraphiteReporter; 6 | import net.logstash.logback.marker.LogstashMarker; 7 | import net.logstash.logback.marker.Markers; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.CommandLineRunner; 13 | import org.springframework.boot.actuate.autoconfigure.ExportMetricWriter; 14 | import org.springframework.boot.actuate.health.Health; 15 | import org.springframework.boot.actuate.health.HealthIndicator; 16 | import org.springframework.boot.actuate.metrics.CounterService; 17 | import org.springframework.boot.actuate.metrics.statsd.StatsdMetricWriter; 18 | import org.springframework.boot.actuate.system.ApplicationPidFileWriter; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | import org.springframework.boot.builder.SpringApplicationBuilder; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.data.rest.core.annotation.HandleAfterCreate; 23 | import org.springframework.data.rest.core.annotation.HandleAfterDelete; 24 | import org.springframework.data.rest.core.annotation.HandleAfterSave; 25 | import org.springframework.data.rest.core.annotation.RepositoryEventHandler; 26 | import org.springframework.stereotype.Component; 27 | 28 | import java.util.concurrent.TimeUnit; 29 | import java.util.stream.IntStream; 30 | 31 | @SpringBootApplication 32 | public class DemoApplication { 33 | 34 | 35 | private static Logger LOGGER = LoggerFactory.getLogger( 36 | DemoApplication.class); 37 | 38 | 39 | @Bean 40 | HealthIndicator healthIndicator() { 41 | return () -> Health.status("I <3 Spring!").build(); 42 | } 43 | 44 | @Bean 45 | GraphiteReporter graphiteReporter(MetricRegistry registry, 46 | @Value("${graphite.host}") String host, 47 | @Value("${graphite.port}") int port) { 48 | 49 | GraphiteReporter reporter = GraphiteReporter 50 | .forRegistry(registry) 51 | .prefixedWith("products") 52 | .build(new Graphite(host, port)); 53 | 54 | reporter.start(2, TimeUnit.SECONDS); 55 | 56 | return reporter; 57 | } 58 | /* 59 | @Bean 60 | @ExportMetricWriter 61 | StatsdMetricWriter statsdMetricWriter( 62 | @Value("${statsd.host}") String host, 63 | @Value("${statsd.port}") int port) { 64 | return new StatsdMetricWriter("statsd-products", host, port); 65 | }*/ 66 | 67 | 68 | @Bean 69 | CommandLineRunner dummy(ProductRepository pr) { 70 | return args -> 71 | IntStream.range(0, 1000) 72 | .forEach(i -> pr.save(new Product("sku" + i))); 73 | } 74 | 75 | @Component 76 | @RepositoryEventHandler 77 | public static class ProductEventHandler { 78 | 79 | @Autowired 80 | private CounterService counterService; 81 | 82 | 83 | @HandleAfterCreate 84 | public void create(Product p) { 85 | count("products.create", p); 86 | } 87 | 88 | @HandleAfterSave 89 | public void save(Product p) { 90 | count("products.save", p); 91 | count("products." + p.getId() + ".save", p); 92 | } 93 | 94 | @HandleAfterDelete 95 | public void delete(Product p) { 96 | count("products.delete", p); 97 | } 98 | 99 | private void count(String evt, Product p) { 100 | LogstashMarker logstashMarker = Markers.append("event", evt) 101 | .and(Markers.append("sku", p.getSku())) 102 | .and(Markers.append("id", p.getId())); 103 | 104 | LOGGER.info(logstashMarker, evt); 105 | 106 | this.counterService.increment(evt); 107 | this.counterService.increment("meter." + evt); 108 | } 109 | } 110 | 111 | public static void main(String[] args) { 112 | //SpringApplication.run(DemoApplication.class, args); 113 | new SpringApplicationBuilder(DemoApplication.class) 114 | .listeners(new ApplicationPidFileWriter()) 115 | .run(args); 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /src/main/java/demo/Product.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.Id; 6 | 7 | 8 | @Entity 9 | public class Product { 10 | @Id 11 | @GeneratedValue 12 | private Long id; 13 | 14 | private String sku; 15 | 16 | @Override 17 | public String toString() { 18 | return "Product{" + "id=" + id + 19 | ", sku='" + sku + '\'' + 20 | '}'; 21 | } 22 | 23 | Product() { // why JPA why?? 24 | } 25 | 26 | public Product(String sku) { 27 | this.sku = sku; 28 | } 29 | 30 | public Long getId() { 31 | return id; 32 | } 33 | 34 | public String getSku() { 35 | return sku; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/demo/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.repository.query.Param; 5 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 6 | import org.springframework.data.rest.core.annotation.RestResource; 7 | 8 | import java.util.Collection; 9 | 10 | @RepositoryRestResource 11 | public interface ProductRepository extends JpaRepository { 12 | 13 | @RestResource (path = "by-sku") 14 | Collection findBySku (@Param("sku") String sku) ; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | management.context-path=/admin 2 | 3 | info.build.artifact=${project.artifactId} 4 | info.build.version=${project.version} 5 | 6 | 7 | server.ssl.key-store = server.jks 8 | server.ssl.key-store-password = password 9 | server.ssl.key-password = password 10 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 192.168.99.100 7 | 10042 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/test/java/demo/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.test.context.web.WebAppConfiguration; 6 | import org.springframework.boot.test.SpringApplicationConfiguration; 7 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8 | 9 | @RunWith(SpringJUnit4ClassRunner.class) 10 | @SpringApplicationConfiguration(classes = DemoApplication.class) 11 | @WebAppConfiguration 12 | public class DemoApplicationTests { 13 | 14 | @Test 15 | public void contextLoads() { 16 | } 17 | 18 | } 19 | --------------------------------------------------------------------------------