MCPcopy
hub / github.com/sqshq/piggymetrics

github.com/sqshq/piggymetrics @spring.version.2.0.3 sqlite

repository ↗ · DeepWiki ↗ · release spring.version.2.0.3 ↗
609 symbols 1,994 edges 101 files 37 documented · 6%
README

Build Status codecov.io GitHub license Join the chat at https://gitter.im/sqshq/PiggyMetrics

Piggy Metrics

A simple way to deal with personal finances

This is a proof-of-concept application, which demonstrates Microservice Architecture Pattern using Spring Boot, Spring Cloud and Docker. With a pretty neat user interface, by the way.

Piggy Metrics

Functional services

PiggyMetrics was decomposed into three core microservices. All of them are independently deployable applications, organized around certain business domains.

Functional services

Account service

Contains general user input logic and validation: incomes/expenses items, savings and account settings.

Method | Path | Description | User authenticated | Available from UI ------------- | ------------------------- | ------------- |:-------------:|:----------------:| GET | /accounts/{account} | Get specified account data | |
GET | /accounts/current | Get current account data | × | × GET | /accounts/demo | Get demo account data (pre-filled incomes/expenses items, etc) | | × PUT | /accounts/current | Save current account data | × | × POST | /accounts/ | Register new account | | ×

Statistics service

Performs calculations on major statistics parameters and captures time series for each account. Datapoint contains values, normalized to base currency and time period. This data is used to track cash flow dynamics in account lifetime.

Method | Path | Description | User authenticated | Available from UI ------------- | ------------------------- | ------------- |:-------------:|:----------------:| GET | /statistics/{account} | Get specified account statistics | |
GET | /statistics/current | Get current account statistics | × | × GET | /statistics/demo | Get demo account statistics | | × PUT | /statistics/{account} | Create or update time series datapoint for specified account | |

Notification service

Stores users contact information and notification settings (like remind and backup frequency). Scheduled worker collects required information from other services and sends e-mail messages to subscribed customers.

Method | Path | Description | User authenticated | Available from UI ------------- | ------------------------- | ------------- |:-------------:|:----------------:| GET | /notifications/settings/current | Get current account notification settings | × | × PUT | /notifications/settings/current | Save current account notification settings | × | ×

Notes

  • Each microservice has it's own database, so there is no way to bypass API and access persistance data directly.
  • In this project, I use MongoDB as a primary database for each service. It might also make sense to have a polyglot persistence architecture (сhoose the type of db that is best suited to service requirements).
  • Service-to-service communication is quite simplified: microservices talking using only synchronous REST API. Common practice in a real-world systems is to use combination of interaction styles. For example, perform synchronous GET request to retrieve data and use asynchronous approach via Message broker for create/update operations in order to decouple services and buffer messages. However, this brings us to the eventual consistency world.

Infrastructure services

There's a bunch of common patterns in distributed systems, which could help us to make described core services work. Spring cloud provides powerful tools that enhance Spring Boot applications behaviour to implement those patterns. I'll cover them briefly. Infrastructure services

Config service

Spring Cloud Config is horizontally scalable centralized configuration service for distributed systems. It uses a pluggable repository layer that currently supports local storage, Git, and Subversion.

In this project, I use native profile, which simply loads config files from the local classpath. You can see shared directory in Config service resources. Now, when Notification-service requests it's configuration, Config service responses with shared/notification-service.yml and shared/application.yml (which is shared between all client applications).

Client side usage

Just build Spring Boot application with spring-cloud-starter-config dependency, autoconfiguration will do the rest.

Now you don't need any embedded properties in your application. Just provide bootstrap.yml with application name and Config service url:

spring:
  application:
    name: notification-service
  cloud:
    config:
      uri: http://config:8888
      fail-fast: true
With Spring Cloud Config, you can change app configuration dynamically.

For example, EmailService bean was annotated with @RefreshScope. That means, you can change e-mail text and subject without rebuild and restart Notification service application.

First, change required properties in Config server. Then, perform refresh request to Notification service: curl -H "Authorization: Bearer #token#" -XPOST http://127.0.0.1:8000/notifications/refresh

Also, you could use Repository webhooks to automate this process

Notes
  • There are some limitations for dynamic refresh though. @RefreshScope doesn't work with @Configuration classes and doesn't affect @Scheduled methods
  • fail-fast property means that Spring Boot application will fail startup immediately, if it cannot connect to the Config Service.
  • There are significant security notes below

Auth service

Authorization responsibilities are completely extracted to separate server, which grants OAuth2 tokens for the backend resource services. Auth Server is used for user authorization as well as for secure machine-to-machine communication inside a perimeter.

In this project, I use Password credentials grant type for users authorization (since it's used only by native PiggyMetrics UI) and Client Credentials grant for microservices authorization.

Spring Cloud Security provides convenient annotations and autoconfiguration to make this really easy to implement from both server and client side. You can learn more about it in documentation and check configuration details in Auth Server code.

From the client side, everything works exactly the same as with traditional session-based authorization. You can retrieve Principal object from request, check user's roles and other stuff with expression-based access control and @PreAuthorize annotation.

Each client in PiggyMetrics (account-service, statistics-service, notification-service and browser) has a scope: server for backend services, and ui - for the browser. So we can also protect controllers from external access, for example:

@PreAuthorize("#oauth2.hasScope('server')")
@RequestMapping(value = "accounts/{name}", method = RequestMethod.GET)
public List<DataPoint> getStatisticsByAccountName(@PathVariable String name) {
    return statisticsService.findByAccountName(name);
}

API Gateway

As you can see, there are three core services, which expose external API to client. In a real-world systems, this number can grow very quickly as well as whole system complexity. Actually, hundreds of services might be involved in rendering of one complex webpage.

In theory, a client could make requests to each of the microservices directly. But obviously, there are challenges and limitations with this option, like necessity to know all endpoints addresses, perform http request for each peace of information separately, merge the result on a client side. Another problem is non web-friendly protocols which might be used on the backend.

Usually a much better approach is to use API Gateway. It is a single entry point into the system, used to handle requests by routing them to the appropriate backend service or by invoking multiple backend services and aggregating the results. Also, it can be used for authentication, insights, stress and canary testing, service migration, static response handling, active traffic management.

Netflix opensourced such an edge service, and now with Spring Cloud we can enable it with one @EnableZuulProxy annotation. In this project, I use Zuul to store static content (ui application) and to route requests to appropriate microservices. Here's a simple prefix-based routing configuration for Notification service:

zuul:
  routes:
    notification-service:
        path: /notifications/**
        serviceId: notification-service
        stripPrefix: false

That means all requests starting with /notifications will be routed to Notification service. There is no hardcoded address, as you can see. Zuul uses Service discovery mechanism to locate Notification service instances and also Circuit Breaker and Load Balancer, described below.

Service discovery

Another commonly known architecture pattern is Service discovery. It allows automatic detection of network locations for service instances, which could have dynamically assigned addresses because of auto-scaling, failures and upgrades.

The key part of Service discovery is Registry. I use Netflix Eureka in this project. Eureka is a good example of the client-side discovery pattern, when client is responsible for determining locations of available service instances (using Registry server) and load balancing requests across them.

With Spring Boot, you can easily build Eureka Registry with spring-cloud-starter-eureka-server dependency, @EnableEurekaServer annotation and simple configuration properties.

Client support enabled with @EnableDiscoveryClient annotation an bootstrap.yml with application name:

spring:
  application:
    name: notification-service

Now, on application startup, it will register with Eureka Server and provide meta-data, such as host and port, health indicator URL, home page etc. Eureka receives heartbeat messages from each instance belonging to a service. If the heartbeat fails over a configurable timetable, the instance will be removed from the registry.

Also, Eureka provides a simple interface, where you can track running services and number of available instances: http://localhost:8761

Load balancer, Circuit breaker and Http client

Netflix OSS provides another great set of tools.

Ribbon

Ribbon is a client side load balancer which gives you a lot of control over the behaviour of HTTP and TCP clients. Compared to a traditional load balancer, there is no need in additional hop for every over-the-wire invocation - you can contact desired service directly.

Out of the box, it natively integrates with Spring Cloud and Service Discovery. Eureka Client provides a dynamic list of available servers so Ribbon could balance between them.

Hystrix

Hystrix is the implementation of Circuit Breaker pattern, which gives a control over latency and failure from dependencies accessed over the network. The main idea is to stop cascading failures in a distributed environment with a large number of microservices. That helps to fail fast and recover as soon as possible - important aspects of fault-tolerant systems that self-heal.

Besides circuit breaker control, with Hystrix you can add a fallback method that will be called to obtain a default value in case the main command fails.

Moreover, Hystrix generates metrics on execution outcomes and latency for each command, that we can use to monitor system behavior.

Feign

Feign is a declarative Http client, which seamlessly integrates with Ribbon and Hystrix. Actually, wi

Extension points exported contracts — how you extend this code

ExchangeRatesClient (Interface)
(no doc) [4 implementers]
statistics-service/src/main/java/com/piggymetrics/statistics/client/ExchangeRatesClient.java
UserService (Interface)
(no doc) [3 implementers]
auth-service/src/main/java/com/piggymetrics/auth/service/UserService.java
EmailService (Interface)
(no doc) [2 implementers]
notification-service/src/main/java/com/piggymetrics/notification/service/EmailService.java
AccountService (Interface)
(no doc) [2 implementers]
account-service/src/main/java/com/piggymetrics/account/service/AccountService.java
StatisticsService (Interface)
(no doc) [3 implementers]
statistics-service/src/main/java/com/piggymetrics/statistics/service/StatisticsService.java
UserRepository (Interface)
(no doc)
auth-service/src/main/java/com/piggymetrics/auth/repository/UserRepository.java
RecipientService (Interface)
(no doc) [2 implementers]
notification-service/src/main/java/com/piggymetrics/notification/service/RecipientService.java
AuthServiceClient (Interface)
(no doc) [1 implementers]
account-service/src/main/java/com/piggymetrics/account/client/AuthServiceClient.java

Core symbols most depended-on inside this repo

e
called by 33
gateway/src/main/resources/static/js/lib/extrascripts.js
t
called by 31
gateway/src/main/resources/static/js/lib/extrascripts.js
a
called by 30
gateway/src/main/resources/static/js/lib/extrascripts.js
f
called by 23
gateway/src/main/resources/static/js/lib/extrascripts.js
d
called by 20
gateway/src/main/resources/static/js/lib/extrascripts.js
c
called by 19
gateway/src/main/resources/static/js/lib/extrascripts.js
toString
called by 18
notification-service/src/main/java/com/piggymetrics/notification/domain/Recipient.java
getName
called by 18
account-service/src/main/java/com/piggymetrics/account/domain/Account.java

Shape

Method 296
Function 218
Class 73
Interface 15
Enum 7

Languages

Java64%
TypeScript36%

Modules by API surface

gateway/src/main/resources/static/js/lib/jquery.min.js63 symbols
gateway/src/main/resources/static/js/lib/touchscreens.js59 symbols
gateway/src/main/resources/static/js/main.js39 symbols
gateway/src/main/resources/static/js/lib/extrascripts.js36 symbols
account-service/src/main/java/com/piggymetrics/account/domain/Account.java13 symbols
statistics-service/src/main/java/com/piggymetrics/statistics/service/security/CustomUserInfoTokenServices.java11 symbols
statistics-service/src/main/java/com/piggymetrics/statistics/domain/timeseries/DataPoint.java11 symbols
statistics-service/src/main/java/com/piggymetrics/statistics/domain/Saving.java11 symbols
account-service/src/main/java/com/piggymetrics/account/service/security/CustomUserInfoTokenServices.java11 symbols
account-service/src/main/java/com/piggymetrics/account/domain/Saving.java11 symbols
account-service/src/main/java/com/piggymetrics/account/domain/Item.java11 symbols
auth-service/src/main/java/com/piggymetrics/auth/domain/User.java10 symbols

Dependencies from manifests, versioned

com.jayway.jsonpath:json-path2.2.0 · 1×
de.flapdoodle.embed:de.flapdoodle.embed.mongo1.50.3 · 1×
org.springframework.boot:spring-boot-starter-actuator
org.springframework.boot:spring-boot-starter-data-mongodb
org.springframework.boot:spring-boot-starter-mail
org.springframework.boot:spring-boot-starter-security
org.springframework.boot:spring-boot-starter-test
org.springframework.boot:spring-boot-starter-web
org.springframework.cloud:spring-cloud-config-server
org.springframework.cloud:spring-cloud-dependencies
org.springframework.cloud:spring-cloud-netflix-hystrix-stream

Datastores touched

(mongodb)Database · 1 repos

For agents

$ claude mcp add piggymetrics \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact