Skip to content

Commit

Permalink
Updated codesections
Browse files Browse the repository at this point in the history
  • Loading branch information
atooni committed Oct 20, 2020
1 parent b307d21 commit 5ce6927
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 55 deletions.
14 changes: 8 additions & 6 deletions content/docs/blended-zio-jmx/MBeanPublisher.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
---
giturl: "https://github.com/blended-zio/blended-zio-jmx"
giturl: "https://github.com/blended-zio/blended-zio-jmx"
jmxsrc: "modules/blended-zio-jmx/blended-zio-jmx/jvm/src/main/scala"
jmxtest: "modules/blended-zio-jmx/blended-zio-jmx/jvm/src/test/scala"
---
# MBean Publisher

The MBean publisher is used to publish arbitrary case classes as `DynamicMBean`s via JMX. A generic mapper will examine the structure of the given case class instance and recursively map all attributes to corresponding attributes within th MBean.

As such the interface definition for the publishing service is:

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/publish/ProductMBeanPublisher.scala" section="service" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/publish/ProductMBeanPublisher.scala" section="service" >}}

## Using the MBean publisher

The easiest way to use the MBeanPublisher is to make it available through a layer like it is done within the tests:

{{< codesection file="modules/blended-zio-jmx/ziojmx/test/jvm/blended/zio/jmx/publish/MBeanPublisherTest.scala" section="layer" >}}
{{< codesection dirref="jmxtest" file="blended/zio/jmx/publish/MBeanPublisherTest.scala" section="layer" >}}


Then the MBeanPublisher can be used by simply passing a case class to `updateMBean`. For now the case class also needs to implement `Nameable` so that the proper `ObjectName` can be calculated.

{{< codesection file="modules/blended-zio-jmx/ziojmx/test/jvm/blended/zio/jmx/publish/MBeanPublisherTest.scala" section="simple" >}}
{{< codesection dirref="jmxtest" file="blended/zio/jmx/publish/MBeanPublisherTest.scala" section="simple" >}}

In the test case we are also using the `MBeanServerFacade` to verify that the `MBean` has been published correctly and has the correct values.

Expand All @@ -34,7 +36,7 @@ The service implementation keeps track of the published values in a `TMap` with

To manipulate the `TMap` we use some helper methods to either create or update an entry within the `TMap`:

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/publish/ProductMBeanPublisher.scala" section="helpers" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/publish/ProductMBeanPublisher.scala" section="helpers" >}}

{{< hint warning >}}
The implementation uses [STM](https://zio.dev/docs/datatypes/datatypes_stm) under the covers. It is important to note that STM code should not include side effecting code that might not be idempotent (such as appending to a file or as in our case register an MBean). The reason for that
Expand All @@ -51,4 +53,4 @@ scenario.

With the helper methods in place, actual service implementation methods is fairly straightforward:

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/publish/ProductMBeanPublisher.scala" section="methods" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/publish/ProductMBeanPublisher.scala" section="methods" >}}
68 changes: 34 additions & 34 deletions content/docs/blended-zio-jmx/MBeanServerFacade.md
Original file line number Diff line number Diff line change
@@ -1,79 +1,79 @@
---
giturl: "https://github.com/blended-zio/blended-zio-jmx"
giturl: "https://github.com/blended-zio/blended-zio-jmx"
jmxsrc: "modules/blended-zio-jmx/blended-zio-jmx/jvm/src/main/scala"
jmxtest: "modules/blended-zio-jmx/blended-zio-jmx/jvm/src/test/scala"
---
# A simple facade to the Platform MBean Server

This service is a small wrapper around the platform MBean server within a JVM. The use case is to
have a server side component which can query for the names within the MBeanServer for a given pattern
and to retrieve the MBean Information for a given object name. All JMX specific data shall be mapped
This service is a small wrapper around the platform MBean server within a JVM. The use case is to
have a server side component which can query for the names within the MBeanServer for a given pattern
and to retrieve the MBean Information for a given object name. All JMX specific data shall be mapped
to appropriate classes, so that the data can be used later on with a simple read-only JMX REST service.

This leads to the following, simple interface definition:

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/MBeanServerFacade.scala" section="service" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/MBeanServerFacade.scala" section="service" >}}

{{< hint info >}}
Even though the interface is defined without any environment restrictions, the actual `live` service requires that
a `Logging` service is available. We have decided to push the requirement for a `Logging` service into the instantiation
of the `live` service as we might come up with `test` instances at some point that should just mock up the interface and
does not require any logging at all.
Even though the interface is defined without any environment restrictions, the actual `live` service requires that
a `Logging` service is available. We have decided to push the requirement for a `Logging` service into the instantiation
of the `live` service as we might come up with `test` instances at some point that should just mock up the interface and
does not require any logging at all.

We will use the [zio-logging](https://zio.github.io/zio-logging/) API to perform the actual logging. See
[this post]({{< ref "/posts/2020-09-28-ZIOLogging.md" >}}) for more details on injecting different logging backends into the
We will use the [zio-logging](https://zio.github.io/zio-logging/) API to perform the actual logging. See
[this post]({{< ref "/posts/2020-09-28-ZIOLogging.md" >}}) for more details on injecting different logging backends into the
`live` service instance.
{{< /hint >}}

## Querying for MBean Names

To query for a set of MBean names with an optional is fairly straight forward wrapper around the original JMX API.
To query for a set of MBean names with an optional is fairly straight forward wrapper around the original JMX API.
We just have to translate from the case class we want to use in our API to a JMX search pattern and call the API.

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/MBeanServerFacade.scala" section="names" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/MBeanServerFacade.scala" section="names" >}}

In order to make the code a bit more readable, we encapsulate the translation within some helper methods abstracting
over the case that the pattern may be optional. It might be that a single helper method to translate the pattern
would have been sufficient, but in this case it seemed to improve the code's readability to have two helper methods.
In order to make the code a bit more readable, we encapsulate the translation within some helper methods abstracting
over the case that the pattern may be optional. It might be that a single helper method to translate the pattern
would have been sufficient, but in this case it seemed to improve the code's readability to have two helper methods.

The `queryNames` method performs the actual JMX call and translates the resulting Java object into a `List` of
The `queryNames` method performs the actual JMX call and translates the resulting Java object into a `List` of
`JmxObjectName`

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/MBeanServerFacade.scala" section="helper" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/MBeanServerFacade.scala" section="helper" >}}

## Retrieving MBean information

The complicated part retrieving MBean information is to translate the attributes within the MBean information to an actual
case class. We assume that the MBeans do have properties which are allowed for
[OpenMBeans](https://docs.oracle.com/cd/E19206-01/816-4178/6madjde4v/index.html).
The complicated part retrieving MBean information is to translate the attributes within the MBean information to an actual
case class. We assume that the MBeans do have properties which are allowed for
[OpenMBeans](https://docs.oracle.com/cd/E19206-01/816-4178/6madjde4v/index.html).

If we encounter an attribute that is not valid as an attribute in the sense of the Open MBean specification, we will ignore
that attribute in our mapping rather than throw an exception. As a result, some attributes _may_ be missing for certain
MBean Info objects.
If we encounter an attribute that is not valid as an attribute in the sense of the Open MBean specification, we will ignore
that attribute in our mapping rather than throw an exception. As a result, some attributes _may_ be missing for certain
MBean Info objects.

The mapping between attributes and their case class representation happens within the `JmxAttributeCompanion` object. For the
The mapping between attributes and their case class representation happens within the `JmxAttributeCompanion` object. For the
simple types this is straight forward:

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/JmxAttributeCompanion.scala" section="simple" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/JmxAttributeCompanion.scala" section="simple" >}}

To map the complex data we will rely on ZIO's `collectPar` operator:

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/JmxAttributeCompanion.scala" section="tabdata" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/JmxAttributeCompanion.scala" section="tabdata" >}}

Note, that whithin `JmxAttributeCompanion` the overall signature is
Note, that whithin `JmxAttributeCompanion` the overall signature is

```scala
def make(v: Any): ZIO[Any, IllegalArgumentException, AttributeValue[_]]
```
This means that the error handling is in the responsibility of the user of the `make` effect.

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/MBeanServerFacade.scala" section="attribute" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/MBeanServerFacade.scala" section="attribute" >}}

Here, the `orElse` operator will handle the error by just ignoring the attribute that was faulty.
Here, the `orElse` operator will handle the error by just ignoring the attribute that was faulty.

### Representation of an entire MBean Info

The entire MBean Info object contains the `JmxObjectName` it belongs to and a Map of attribute names to their corresponding values.
In other words, the attributes of a MBean Info can be represented by an instance of `CompositeAttributeValue`.

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/JmxAttribute.scala" section="beaninfo" >}}

In other words, the attributes of a MBean Info can be represented by an instance of `CompositeAttributeValue`.

{{< codesection dirref="jmxsrc" file="blended/zio/jmx/JmxAttribute.scala" section="beaninfo" >}}
16 changes: 9 additions & 7 deletions content/docs/blended-zio-jmx/ServiceMetrics.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
---
giturl: "https://github.com/blended-zio/blended-zio-jmx"
giturl: "https://github.com/blended-zio/blended-zio-jmx"
jmxsrc: "modules/blended-zio-jmx/blended-zio-jmx/jvm/src/main/scala"
jmxtest: "modules/blended-zio-jmx/blended-zio-jmx/jvm/src/test/scala"
---

# ZIO based Service Metrics implementation
Expand All @@ -16,7 +18,7 @@ In addition to this we would like to retrieve the current list of active invocat

This leads to the follwing interface definition:

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/metrics/ServiceMetrics.scala" section="service" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/metrics/ServiceMetrics.scala" section="service" >}}

Note, that all methods on the interface return ZIO effects.

Expand All @@ -26,28 +28,28 @@ The implementation needs to maintain a list currently active of service invocati

Inspired by [this article](https://scalac.io/how-to-write-a-completely-lock-free-concurrent-lru-cache-with-zio-stm/) about implementing a concurrent LRU cache I have decided to implement a ConcurrentServiceTracker using STM References under the covers:

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/metrics/ServiceMetrics.scala" section="tracker" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/metrics/ServiceMetrics.scala" section="tracker" >}}

First of all we need a couple of helpers helping us to manipulate the two maps. The names of the helper functions speak for themselves and all of them use STM under the covers, so that we can compose them to implement the actual business functions and finally call commit in order to end up with a ZIO effect as result.

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/metrics/ServiceMetrics.scala" section="helpers" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/metrics/ServiceMetrics.scala" section="helpers" >}}

Within the implementation the `update`method is responsible for recording the completion or failure for a given invocation id. Therefore we need to determine the currently active entry from our active map and also the existing summary. Note that if everything works as designed, the summary mst already exist at this point in time. However, either of these calls may fail with a ServiceMetricsException, which is reflected in the method signature.

Once we have looked up the entries, we can simpy perform the required update and we are done.

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/metrics/ServiceMetrics.scala" section="update" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/metrics/ServiceMetrics.scala" section="update" >}}

The `start` method is very similar. We are using `getExistingActive(evt.id).flip`, so that having an already defined entry for the given id will be considered an exception. Also, in this case we are using `getOrCreateSummary(evt)` to ensure that the summary map definitely has an entry.

Finally, we are using `mapError` to create the proper exception indicating the a service with the same id was already active.

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/metrics/ServiceMetrics.scala" section="start" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/metrics/ServiceMetrics.scala" section="start" >}}

## Testing

Testing is done with [zio-test](https://zio.dev/docs/howto/howto_test_effects) and is fairly straight forward. The tests provide the `live` service via `ZLayer` and then use the interface methods to call the service and verify the result with assertions.

For example, the test to verify that a successful service completion is implemented as follows:

{{< codesection file="modules/blended-zio-jmx/ziojmx/test/jvm/blended/zio/jmx/metrics/ServiceMetricsTest.scala" section="complete" >}}
{{< codesection dirref="jmxtest" file="blended/zio/jmx/metrics/ServiceMetricsTest.scala" section="complete" >}}
14 changes: 7 additions & 7 deletions content/posts/2020-09-28-ZIOLogging.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ tags:
categories:
- ZIO
Summary: Keep business interfaces clean of non-functional requirements such as loggin, but still allow to use them in service instances.

jmxsrc: "modules/blended-zio-jmx/blended-zio-jmx/jvm/src/main/scala"
jmxtest: "modules/blended-zio-jmx/blended-zio-jmx/jvm/src/test/scala"
---

# Use ZIO logging
Expand All @@ -16,11 +19,11 @@ Within _Blended ZIO_ the service are kept clean of non functional requirements s

For example, the `Service` within `MBeanServerFacade` is defined as follows.

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/MBeanServerFacade.scala" section="service" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/MBeanServerFacade.scala" section="service" >}}

However, within the service's implementation `JvmMBeanServerFacade` the corresponding methods leverage the API of [zio-logging](https://zio.github.io/zio-logging/) to produce some output while executing the effects.

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/MBeanServerFacade.scala" section="info" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/MBeanServerFacade.scala" section="info" >}}

So, when we assemble the service

Expand All @@ -30,13 +33,10 @@ So, when we assemble the service

The code to construct the live service which requires `Logging` leverages `ZLayer.fromFunction`. We see that a `Logging` service is required within the environment and we can use the parameter to the `fromFunction` call in the `provide` operator so that the requirement of having a `Logging` service is eliminated and the sole business service interface remains.

{{< codesection file="modules/blended-zio-jmx/ziojmx/jvm/blended/zio/jmx/MBeanServerFacade.scala" section="zlayer" >}}
{{< codesection dirref="jmxsrc" file="blended/zio/jmx/MBeanServerFacade.scala" section="zlayer" >}}

We might have other service implementations that do not require logging or use a different logging API while keeping the same business interface.

Finally, we can construct the environment for our program as we do in the testcase:

{{< codesection file="modules/blended-zio-jmx/ziojmx/test/jvm/blended/zio/jmx/MBeanServerTest.scala" section="zlayer" >}}



{{< codesection dirref="jmxtest" file="blended/zio/jmx/MBeanServerTest.scala" section="zlayer" >}}
4 changes: 3 additions & 1 deletion themes/book/layouts/shortcodes/codesection.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{{ $section := .Get "section" }}
{{ $code := readFile (.Get "file") }}
{{ $dirvar := .Get "dirref" }}
{{ $basedir := $.Page.Params.Get $dirvar }}
{{ $code := readFile (printf "%s/%s" $basedir (.Get "file")) }}
{{ $lines := split $code "\n" }}
{{ $scratch := newScratch }}

Expand Down

0 comments on commit 5ce6927

Please sign in to comment.