I have a monolith app where its models are joined to each others(OnetOne, ManyToMany..).
I was able to create the different Microservices, but I got stuck on how to transition these relationships into Microservices.
Here is my first Class:
#Entity
#Table
public class A {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#ManyToOne
#JoinColumn(name = "ID",referencedColumnName="ID")
private B b;
//getters and setters
}
#Entity
#Table
public class B{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
//getters and setters
}
I also Created a microservice for A (controller,repository, service...) and a separate microservice for B.
I am trying to call the Class Model B from the microservice B. But I am not sure how to do it?
I also wonder if it is write to link two classes by joint in microservices or not ?
Thanks
The join relations such as #OneToOne or #ManyToMany are JPA specific and there is no straightforward way to make them work in microservice world.
In general, in microservice world you give up the ACID transactions for cross-service relations and replace them with BASE transactions (eventual consistency behaviour).
In your example, you can achieve this by implementing one of the following strategies.
Fetch the required entity using rest API from the other service.
As you divide your domain into different bounded contexts (services), you will eventually create two different stores with the following entities:
Service A
#Entity
#Table
public class A {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#Column(name = "B_REFERENCE_ID")
private Integer bId;
//getters and setters
}
And Service B:
#Entity
#Table
public class B{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#ElementCollection
private List<Integer> aIds;
//getters and setters
}
Then, you create your target DTO in the the service (example for service B):
public class BDto{
private int id;
private String name;
private List<ADto> aIds;
//getters and setters
}
Then, you need to fetch the dto you want to expose/consume yourself:
#RestController
public class BController {
private final WebClient webClient;
public BController() {
this.webClient = WebClient.builder()
.baseUrl(SERVICE_A_URL)
.build();
}
#GetMapping(path = "/{id}")
public Mono<BDto> getB(#PathVariable int id) {
Optional<B> bEntity = fetchBFromDatabase();
if (bEntity.isPresent()) {
var b = bEntity.get();
var aEntityIds = b.getaIds();
return webClient
.method(HttpMethod.GET)
.uri(GET_A_URL, aEntityIds)
.exchangeToFlux(response -> response.bodyToFlux(ADto.class))
.collect(Collectors.toList()).map(aDtos -> new BDto(b.getId(), b.getName(), aDtos));
}
return Mono.empty();
}
}
If you are unfimiliar with WebClient and reactive concepts, reference spring boot docs https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html
Of course, the good old RestTemplate can be used here as well.
In order to provide data consistency, you will need to implements eventing system with a message broker in between such as Kafka, Apache Pulsar or RabbitMQ.
As an alternative approach, you can keep both A and B entities in both microservices. In service A, you store only the information of B entity that is required in the service A domain and vice versa. In microservice world it is rare that you will require all the B data in service A.
Then, you can keep your join relations as they are in A and B services for fetching purposes.
Remember that you will still require only single source of truth. So, if data changes in service B, then you will need to update your B_ENTITY data in service A and vice versa. Thus, eventing system will still be required to properly updates states in both your services.
The topic of state management in microservices is a complex one, so I recommend to read more about it to get more comfortable with the topic:
https://phoenixnap.com/kb/acid-vs-base
https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215
https://samnewman.io/books/building_microservices_2nd_edition/
Microservices are meant to be indepedent, so that if one service failed, it will not affect the others.
But if you are using multi-module structure, then add the module using the following code to your pom.xml:
<modules>
<module>module1</module>
<module>module2</module>
</modules>
but I am not sure this will work with Jpa.
Related
I am exploring possible ideas when it comes to designing the one-to-one and one-to-many relationships while using Spring Data R2DBC.
As Spring Data R2DBC still do not support relationships natively there is still a need to handle those on our own (unlike Spring Data JDBC).
What I would imagine that when it comes to one-to-one mapping, the implementation could look like this:
#Table("account")
public class Account {
#Id
private Long id;
#Transient // one-to-one
private Address address;
}
#Table("address")
public class Address {
#Id
private Integer id;
}
while the database schema would be defined as follows:
--address
CREATE TABLE address
(
id SERIAL PRIMARY KEY
)
--account
CREATE TABLE account
(
id SERIAL PRIMARY KEY,
address_id INTEGER REFERENCES address(id)
)
As the Account object is my aggregate root what I would imagine is that I am supposed to load the Address object with it following the advice of Jens Schaduer:
An aggregate is a cluster of objects that form a unit, which should
always be consistent. Also, it should always get persisted (and
loaded) together.
source: Spring Data JDBC, References, and Aggregates
This leads me to thinking that in case of one-to-one relationships like this one I in fact should have my Account entity defined like this:
#Table("account")
public class Account {
#Id
private Long id;
#Transient // one-to-one
private Address address;
#Column("address_id")
private Integer addressId;
}
and later on to recreate the full Account aggregate entity with an Address I would write something like:
#Service
public class AccountServiceImpl implements AccountService {
private final AccountRepository accountRepository;
private final AddressRepository addressRepository;
public AccountServiceImpl(AccountRepository accountRepository,
AddressRepository addressRepository) {
this.accountRepository = accountRepository;
this.addressRepository = addressRepository;
}
#Override
public Mono<Account> loadAccount(Integer id) {
return accountRepository.getAccountById(id)
.flatMap(account ->
Mono.just(account)
.zipWith(addressRepository.getAddressByAccountId(account.getAddressId()))
.map(result -> {
result.getT1().setAddress(result.getT2());
return result.getT1();
})
);
}
}
If that is not the case, how else should I handle one-to-one relationships while using Spring Data R2DBC?
I think your approach is reasonable. There are just a couple of nitpicks:
Do you need the flatMap -> Mono.just ? Can't you just use map directly?
I wouldn't consider this a service, but a repository (it's just not implemented by Spring Data directly.
You might be able to that code in a after load callback.
I needed a RoleMappingService class(which is annotated by #Service) object into a Employee class (which is annotated by #Entity)
below are my classes
********************* RoleMappingsService class **********************
#Service
public class RoleMappingsService {
#Autowired
RolesMappingDao rolesMappingDao;
public List<RolesMappings> getRolesMappingByauthSystemRole(String authSystemRole) {
return rolesMappingDao.getRolesMappingByauthSystemRole(authSystemRole);
}
}
############### Employee class
#Configurable
#Component
#Entity
#NamedQuery(name = "Employee.findAll", query = "SELECT e FROM Employee e")
public class Employee implements Serializable, UserDetails {
#Autowired
#Transient
RoleMappingsService roleMappingsService;
public static final String STATUS_ACTIVE = "ACTIVE";
public static final String STATUS_INACTIVE = "INACTIVE";
public static final String STATUS_LOCKED = "LOCKED";
public static final String STATUS_ONLEAVE = "ONLEAVE";
public static final String STATUS_EXPIRED = "EXPIRED";
private static final long serialVersionUID = 1L;
#Id
#Column(name = "emp_id")
private String empId;
#Column(name = "emp_password")
private String empPassword;
#Column(name = "emp_email")
private String empEmail;
#Column(name = "emp_address")
private String empAddress;
#Column(name = "emp_age")
private int empAge;
#Column(name = "emp_firstname")
private String empFirstname;
}
Here Autowire is not working for roleMappingsService and the object is always found null. However I tried to autowire same object in some other service and there Autowire is perfectly working.
( I know Entity class is only used for representing database table but in my case I need to set some field values which depend on another table so need to fetch data using service)
JB Nizet is totally right
I'll try to provide more explanations here.
Spring can Autowire only beans, objects that it manages, and not arbitrary objects.
Entities are usually created from within a JPA (Hibernate) and are not something that you want to manage by Spring.
There is a related discussion available here but bottom line you should never do something like this.
Why not?
Here are a couple of questions/reasons:
Maybe these entities will go outside spring context at all (serialization), what should that reference contain? Should we also serialize the service? How?
What will happen if the method that turns to the service will be called "outside" the spring driven application (maybe even in different JVM)?
If there are, say 1000 objects returned by that query, do you really want all of them to reside in application context? Or maybe should they be of "prototype" scope?
As you see, it doesn't play nice with spring concepts. I think the reason for it is that Hibernate and JPA do not "support" an idea of methods inside entities, it's just a different framework. I know there are other frameworks that do allow such a concept, but Hibernate/JPA just doesn't, period
So instead of trying to inject the service into the entity bean, probably you should redesign the application so that the service method will be called from outside, maybe via some facade, and entities will be just populated by query, and then "enriched" with additional information if we're talking about SELECT queries, or, alternatively, some information should be pre-set on entity objects, generated by the Business Logic Layer and only then the entity object should be stored in DB
Whats the best practise in Spring Boot to model custom relations between two entities in projection.
My entity Participation links to Competition, User and Team.
public class Participation
{
#Id
private String id;
#NonNull
#OneToOne
private Competition competition;
#OneToOne
private Team team;
#NonNull
#OneToOne
private User user;
private String info;
}
In my project, I want to link all participating Users for a specific Team to a Competition. To achieve this, I wrote a CompetitionDTO that has a field List<User> participants that is filled by a custom CompetitionService:
public Page<CompetitionDTO> teamParticipations (Team team, Pageable pageable)
{
Page<CompetitionDTO> page = cRep.findTeamParticipation(team, pageable);
page.forEach(competition -> competition.setParticipants(pRep.findParticipants(competition, team)));
return page;
}
I don't like this approach a lot because I assume there is a more elegant way to do this with Spring.
I'm using Spring JPA in my DAO layer. I have an entity Projet having inside an entity property Client:
Project.java
#Entity
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int projetId;
private String libelle;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="client_id")
private Client client;
// ... constructors, getters & setters
}
Client.java
#Entity
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int clientId;
private String denomination;
// ... constructors, getters & setters
}
in my DAO interface I have the following specifications:
ProjetDao.java
#Repository
#Transactional
public interface ProjetDao extends CrudRepository<Projet, Integer> {
#Transactional
public Projet findByLibelle(String libelle);
#Transactional
public Projet findByProjetId(int projetId);
}
My question is: How can I specify in my DAO interface a method that will return all clients distinct in List<Client>?
From the documentation and JIRA:
List<Project> findAllDistinctBy();
The query builder mechanism built into Spring Data repository infrastructure is useful for building constraining queries over entities of the repository. The mechanism strips the prefixes find…By, read…By, query…By, count…By, and get…By from the method and starts parsing the rest of it. The introducing clause can contain further expressions such as a Distinct to set a distinct flag on the query to be created. However, the first By acts as delimiter to indicate the start of the actual criteria. At a very basic level you can define conditions on entity properties and concatenate them with And and Or.
You are dealing with a one-to-one relationship, in this case I guess the list that you need is not really related to specific project, you just want a distinct list of clients.
You will need to create another repository (ClientRepository) for the Client entity and add a findAllDistinct method in this repository.
my problem can be broken down to this little example:
I have a entity class A and a entity class B. A has a List of B objects. Now there is always only one B relevant. So I do not want to load all B's of an A, only to access this one B (last inserted B inside a A).
The question: Can I manipulate an entity without an service, so that there is a #Transient variable, that is always the newest B? And also without saving the newest B separately in A. Is there a way to achieve this?
class B{
#Id
#GeneratedValue
private Long id;
#Column(nullable=false)
private String name;
#Column(nullable=false)
private Date created = new Date();
}
class A{
#Id
#GeneratedValue
private Long id;
#OneToMany
#OrderBy("created ASC")
private List<B> b;
#Transient
private B newestB; // Here should be only the newest B
}
Yes. Forget storing the newest B as a variable and instead simply add a getter for it:
#Transient
public B getNewestB() {
return b.get(b.size() -1);
}
This will solve your problem under the assumption that b is set to FetchType.EAGER. Fetching using b's getter and FetchType.LAZY may not be so straight forward as Spring may rely on an AOP proxy call to trigger the lazy load (you'd need to experiment).
However, I'd discourage both these approaches. You're effectively trying to fit business logic into your Entity. Why not keep your entity clean and perform this query using B's repository?
E.g.
public interface BRepository extends CrudRepository<B, Long> {
#Query(...) //query to get newest B for specified A
B getNewest(A a)
}