Skip to content

Commit

Permalink
Added Zuul app to the whole picture
Browse files Browse the repository at this point in the history
  • Loading branch information
marcingrzejszczak committed Dec 21, 2015
1 parent 3c08649 commit 8f89796
Show file tree
Hide file tree
Showing 42 changed files with 465 additions and 76 deletions.
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,21 @@ And here additional tech related applications:
### Aggregating service

- Service contains a warehouse ("database") where is stores the ingredients
- Basing on the order placed it will contact the external services to retrieve the real ingredients
- Basing on the order placed it will contact the Zuul proxy to fetch ingredients
- You have to have all 4 ingredients reach their threshold (1000) to start maturing the beer
- Once the threshold is met the application sends a request to the maturing service
- Each time a request is sent to the aggregating service it returns as a response its warehouse state

### Zuul proxy

- Proxy over the "adapters" to external world to fetch ingredients
- Routes all requests to the respective "ingredient adapter"
- For simplicity we have one ingredient adapter called "ingredients" that returns a stubbed quantity

### Ingredients service

- Returns a fixed value of ingredients

### Maturing service

- It receives a request with ingredients needed to brew a beer
Expand All @@ -54,10 +64,12 @@ And here additional tech related applications:
├── git-props (properties for config-server to pick)
├── gradle (gradle related stuff)
├── img (the fabulous diagram of the brewery)
├── ingredients (service that "connects" to ext. services for ingredients)
├── maturing (service that matures the beer)
├── presenting (UI of the brewery)
├── zipkin-server (Zipkin Server for Sleuth Stream tests)
└── zookeeper (embedded zookeeper)
├── zookeeper (embedded zookeeper)
└── zuul (Zuul proxy that forwards requests to ingredients)
```

## How to build it?
Expand Down Expand Up @@ -129,11 +141,11 @@ The easiest way is to:
* Create a symbolic link somewhere on your drive to the `acceptance-tests/scripts/runDockerAcceptanceTests.sh` file.
* You can execute that script with such options
* `-t` what do you want to test (`SLEUTH`, `ZOOKEEPER` etc.)
* `-v` in which version (`1.0.0.BUILD-SNAPSHOT`)
* `-v` in which version of the BOM (defaults to `Brixton.BUILD-SNAPSHOT`)
* `-r` is brewery repo already in place and needs to be reset?

Once you run the script, the brewery app will be cloned, built with proper lib versions and proper tests
will be executed
will be executed.

## Authors

Expand Down
14 changes: 11 additions & 3 deletions acceptance-tests/scripts/runDockerAcceptanceTests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ RETRIES="${RETRIES:-48}"
DEFAULT_VERSION="${DEFAULT_VERSION:-Brixton.BUILD-SNAPSHOT}"

HEALTH_HOST="127.0.0.1"
HEALTH_PORTS=('9991' '9992' '9993' '9994')
HEALTH_PORTS=('9991' '9992' '9993' '9994' '9995' '9996')
HEALTH_ENDPOINTS="$( printf "http://${HEALTH_HOST}:%s/health " "${HEALTH_PORTS[@]}" )"

BOM_VERSION_PROP_NAME="BOM_VERSION"
Expand Down Expand Up @@ -93,20 +93,28 @@ echo -e "\n\n"
./docker-compose-$WHAT_TO_TEST.sh

# Wait for the apps to boot up
echo "Waiting for the apps to boot for [$(( WAIT_TIME * RETRIES ))] seconds"
echo -e "\n\nWaiting for the apps to boot for [$(( WAIT_TIME * RETRIES ))] seconds"
for i in $( seq 1 "${RETRIES}" ); do
sleep "${WAIT_TIME}"
curl -m 5 ${HEALTH_ENDPOINTS} && READY_FOR_TESTS="yes" && break
echo "Fail #$i/${RETRIES}... will try again in [${WAIT_TIME}] seconds"
done

echo -e "\n\nChecking for the presence of all services in Service Discovery for [$(( WAIT_TIME * RETRIES ))] seconds"
for i in $( seq 1 "${RETRIES}" ); do
sleep "${WAIT_TIME}"
curl -m 5 http://localhost:9991/health | grep presenting | grep aggregating |
grep maturing | grep bottling | grep ingredients && READY_FOR_TESTS="yes" && break
echo "Fail #$i/${RETRIES}... will try again in [${WAIT_TIME}] seconds"
done

echo

TESTS_PASSED="no"

# Run acceptance tests
if [[ "${READY_FOR_TESTS}" == "yes" ]] ; then
echo "Successfully booted up all the apps. Proceeding with the acceptance tests"
echo -e "\n\nSuccessfully booted up all the apps. Proceeding with the acceptance tests"
COMMAND_LINE_ARGS="-DWHAT_TO_TEST=${WHAT_TO_TEST}"
if [ ! -z "$TEST_OPTS" ]; then
COMMAND_LINE_ARGS="\"${COMMAND_LINE_ARGS}\" \"${TEST_OPTS}\""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ abstract class AbstractBreweryAcceptanceSpec extends Specification implements Sl

public static final String TRACE_ID_HEADER_NAME = 'X-TRACE-ID'
public static final String SPAN_ID_HEADER_NAME = 'X-SPAN-ID'

public static final Logger log = LoggerFactory.getLogger(AbstractBreweryAcceptanceSpec)

private static final List<String> APPS_NAMES_AND_PORTS_IN_ZIPKIN = ['presenting:9991', 'maturing:9993', 'bottling:9994',
'aggregating:9992', ':9995', 'ingredients:9996']

@Autowired ServiceUrlFetcher serviceUrlFetcher
@Value('${presenting.poll.interval:1}') Integer pollInterval
@Value('${presenting.timeout:30}') Integer timeout
Expand Down Expand Up @@ -84,7 +86,7 @@ abstract class AbstractBreweryAcceptanceSpec extends Specification implements Sl
log.info("Response from the Zipkin query service about the trace id [$response] for trace with id [$traceId]")
assert response.statusCode == HttpStatus.OK
assert response.hasBody()
assert ['presenting', 'maturing', 'bottling', 'aggregating'].every {
assert APPS_NAMES_AND_PORTS_IN_ZIPKIN.every {
response.body.contains(it)
}
log.info("Zipkin tracing is working! Sleuth is working! Let's be happy!")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,25 @@ AsyncRestTemplate asyncRestTemplate() {
@Bean
IngredientsAggregator ingredientsAggregator(IngredientsProperties ingredientsProperties,
IngredientWarehouse ingredientWarehouse,
MaturingServiceClient maturingServiceClient,
@LoadBalanced RestTemplate restTemplate) {
MaturingServiceUpdater maturingServiceUpdater,
IngredientsCollector ingredientsCollector) {
return new IngredientsAggregator(ingredientsProperties, ingredientWarehouse,
maturingServiceClient, restTemplate);
maturingServiceUpdater, ingredientsCollector);
}

@Bean
MaturingServiceUpdater maturingServiceUpdater(IngredientsProperties ingredientsProperties,
IngredientWarehouse ingredientWarehouse,
MaturingServiceClient maturingServiceClient,
@LoadBalanced RestTemplate restTemplate) {
return new MaturingServiceUpdater(ingredientsProperties,
ingredientWarehouse, maturingServiceClient, restTemplate);
}

@Bean
IngredientsCollector ingredientsCollector(@LoadBalanced RestTemplate restTemplate,
IngredientsProxy ingredientsProxy) {
return new IngredientsCollector(restTemplate, ingredientsProxy);
}
}

Original file line number Diff line number Diff line change
@@ -1,49 +1,35 @@
package io.spring.cloud.samples.brewery.aggregating;

import java.util.List;
import java.util.stream.Collectors;

import io.spring.cloud.samples.brewery.aggregating.model.Ingredients;
import org.springframework.web.client.RestTemplate;

import lombok.extern.slf4j.Slf4j;
import io.spring.cloud.samples.brewery.aggregating.model.Ingredient;
import io.spring.cloud.samples.brewery.aggregating.model.Order;
import lombok.extern.slf4j.Slf4j;

@Slf4j
class IngredientsAggregator {

private final IngredientsProperties ingredientsProperties;
private final MaturingServiceUpdater maturingUpdater;
private final IngredientWarehouse ingredientWarehouse;
private final Integer threshold;
private final IngredientsCollector ingredientsCollector;

IngredientsAggregator(IngredientsProperties ingredientsProperties,
IngredientWarehouse ingredientWarehouse,
MaturingServiceClient maturingServiceClient, RestTemplate restTemplate) {
MaturingServiceUpdater maturingServiceUpdater,
IngredientsCollector ingredientsCollector) {
this.ingredientWarehouse = ingredientWarehouse;
this.threshold = ingredientsProperties.getThreshold();
this.maturingUpdater = new MaturingServiceUpdater(ingredientsProperties,
ingredientWarehouse, maturingServiceClient, restTemplate);
this.ingredientsCollector = ingredientsCollector;
this.maturingUpdater = maturingServiceUpdater;
this.ingredientsProperties = ingredientsProperties;
}

// TODO: Consider simplifying the case by removing the DB (always matches threshold)
Ingredients fetchIngredients(Order order, String processId) {
log.info("Fetching ingredients for order [{}] , processId [{}] and threshold [{}]",
order, processId, threshold);
ingredients(order).stream()
log.info("Fetching ingredients for order [{}] , processId [{}]",order, processId);
ingredientsCollector.collectIngredients(order, processId).stream()
.filter(ingredient -> ingredient != null)
.forEach(ingredientWarehouse::addIngredient);
Ingredients ingredients = ingredientWarehouse.getCurrentState();
return maturingUpdater.updateIfLimitReached(ingredients, processId);
}

private List<Ingredient> ingredients(Order order) {
return order.getItems()
.stream()
.map(ingredientType -> new Ingredient(ingredientType, ingredientsProperties.getReturnedIngredientsQuantity()))
.collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.spring.cloud.samples.brewery.aggregating;

import io.spring.cloud.samples.brewery.aggregating.model.Ingredient;
import io.spring.cloud.samples.brewery.aggregating.model.Order;
import io.spring.cloud.samples.brewery.common.TestConfigurationHolder;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.stream.Collectors;

import static io.spring.cloud.samples.brewery.common.TestConfigurationHolder.TestCommunicationType.FEIGN;
import static io.spring.cloud.samples.brewery.common.TestRequestEntityBuilder.requestEntity;

public class IngredientsCollector {

private final RestTemplate restTemplate;
private final IngredientsProxy ingredientsProxy;

public IngredientsCollector(RestTemplate restTemplate, IngredientsProxy ingredientsProxy) {
this.restTemplate = restTemplate;
this.ingredientsProxy = ingredientsProxy;
}

List<Ingredient> collectIngredients(Order order, String processId) {
switch (TestConfigurationHolder.TEST_CONFIG.get().getTestCommunicationType()) {
case FEIGN:
return callViaFeign(order, processId);
default:
return callViaRestTemplate(order, processId);
}
}

private List<Ingredient> callViaFeign(Order order, String processId) {
return order.getItems()
.stream()
.map(item -> ingredientsProxy.ingredients(item, processId, FEIGN.name()))
.collect(Collectors.toList());
}

private List<Ingredient> callViaRestTemplate(Order order, String processId) {
return order.getItems()
.stream()
.map(item ->
restTemplate.exchange(requestEntity()
.processId(processId)
.serviceName("zuul")
.url(item.name())
.httpMethod(HttpMethod.POST)
.build(), Ingredient.class).getBody()
)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,13 @@
package io.spring.cloud.samples.brewery.aggregating;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import io.spring.cloud.samples.brewery.aggregating.model.IngredientType;
import org.springframework.boot.context.properties.ConfigurationProperties;

import com.google.common.collect.ImmutableMap;
import lombok.Data;
import io.spring.cloud.samples.brewery.aggregating.model.Order;

@ConfigurationProperties("ingredients")
@Data
public class IngredientsProperties {

private Map<IngredientType, String> serviceNames = ImmutableMap.<IngredientType, String>builder()
.put(IngredientType.WATER, IngredientType.WATER.name().toLowerCase())
.put(IngredientType.MALT, IngredientType.MALT.name().toLowerCase())
.put(IngredientType.HOP, IngredientType.HOP.name().toLowerCase())
.put(IngredientType.YEAST, IngredientType.YEAST.name().toLowerCase())
.build();
private Integer threshold = 2000;

/**
* Set to 1000 to correlate with what comes back from config-server
*/
private Integer returnedIngredientsQuantity = 1000;

public List<String> getListOfServiceNames(Order order) {
return serviceNames.entrySet()
.stream()
.filter((entry -> order.getItems().contains(entry.getKey())))
.map((Map.Entry::getValue))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.spring.cloud.samples.brewery.aggregating;

import static io.spring.cloud.samples.brewery.common.TestConfigurationHolder.TEST_COMMUNICATION_TYPE_HEADER_NAME;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import io.spring.cloud.samples.brewery.aggregating.model.Ingredient;
import io.spring.cloud.samples.brewery.aggregating.model.IngredientType;

@FeignClient("zuul")
public interface IngredientsProxy {

@RequestMapping(value = "/{ingredient}", method = RequestMethod.POST)
Ingredient ingredients(@PathVariable("ingredient") IngredientType ingredientType,
@RequestHeader("PROCESS-ID") String processId,
@RequestHeader(TEST_COMMUNICATION_TYPE_HEADER_NAME) String testCommunicationType);
}
1 change: 0 additions & 1 deletion aggregating/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ logging:
server:
port: 9992

spring.cloud.zookeeper.discovery.root: /pl
spring.rabbitmq.host: ${RABBIT_HOST:localhost}

logging.file: build/log/application.log
1 change: 0 additions & 1 deletion bottling/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ logging:
server:
port: 9994

spring.cloud.zookeeper.discovery.root: /pl
spring.rabbitmq.host: ${RABBIT_HOST:localhost}

logging.file: build/log/application.log
22 changes: 22 additions & 0 deletions docker-compose-CONSUL.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,28 @@ presenting:
ports:
- 9991:9991

zuul:
build: zuul/build/docker
environment:
spring.profiles.active: consul
spring.cloud.config.uri: http://configserver:8888
links:
- discovery
- configserver
ports:
- 9995:9995

ingredients:
build: ingredients/build/docker
environment:
spring.profiles.active: consul
spring.cloud.config.uri: http://configserver:8888
links:
- discovery
- configserver
ports:
- 9996:9996

configserver:
build: config-server/build/docker
ports:
Expand Down
10 changes: 1 addition & 9 deletions docker-compose-EUREKA.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,4 @@ fi

# Then the rest
echo -e "\n\nStarting brewery apps..."
docker-compose -f $dockerComposeFile up -d

# Wait for the apps to boot up
echo "Waiting for the apps to boot for [$(( WAIT_TIME * RETRIES ))] seconds. Will check for the presence of all services in Eureka"
for i in $( seq 1 "${RETRIES}" ); do
sleep "${WAIT_TIME}"
curl -m 5 http://localhost:9991/health | grep presenting | grep aggregating | grep maturing | grep bottling && READY_FOR_TESTS="yes" && break
echo "Fail #$i/${RETRIES}... will try again in [${WAIT_TIME}] seconds"
done
docker-compose -f $dockerComposeFile up -d
Loading

0 comments on commit 8f89796

Please sign in to comment.