Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add caffeine cache implementation #117

Merged
merged 23 commits into from
Dec 2, 2020

Conversation

avdev4j
Copy link
Contributor

@avdev4j avdev4j commented Nov 11, 2020

#9

  • Enable User Hibernate second level cache (Entity, relationships and methods)
  • Enable Entity Hibernate second level cache (Entity and relationships)
  • Enable Caffeine Quarkus cache (maven and gradle)
  • Activate cache prompt with Caffeine
  • Enable UserService cache through Quarkus impl
  • Update CI
  • Update npm tests
  • CI is green
  • set cache properties to default values for production purpose
  • Add constants for maximum-size and expire-after-write properties
  • Update application.properties for both User and Authority
  • Add tests in server test generator test suite
  • Add needle and needle-api for Quarkus application.properties
  • Use needle api, every time we create a new Entity, to add maximum-size and expire-after-write properties
  • Add tests in entity-server generator test suite

@avdev4j avdev4j mentioned this pull request Nov 11, 2020
@avdev4j avdev4j marked this pull request as draft November 11, 2020 20:18
@avdev4j avdev4j force-pushed the add-caffeine-cache-impl branch 11 times, most recently from 83253a1 to 254258b Compare November 13, 2020 15:25
@avdev4j
Copy link
Contributor Author

avdev4j commented Nov 16, 2020

@nshaw @joewhite101
I was a little bit annoyed with duplication code issues in tests, I decided to create a "test context builder API" to have something modular and reusable. No more duplicated code, builder pattern based api, default and generic methods for main cases with the possibility to override context properties such prompts, options...

@avdev4j
Copy link
Contributor Author

avdev4j commented Nov 16, 2020

The Quarkus properties have not been added for the moment. We could do it by 2 ways:

  • configuring user cache
  • configuring each entity cache by using a needle (Quarkus needle in properties file) -> this mean we need to create a needle, test it...

I have no idea, if we should provide default configuration or not.

FYI in JHipster we have Java configuration that define default values provided by the JHipster framework: https://github.com/jhipster/generator-jhipster/blob/main/generators/server/templates/src/main/java/package/config/CacheConfiguration.java.ejs#L203 it's a bit different because this Java class configuration while we have properties configuration in Quarkus.

/discuss

@avdev4j avdev4j marked this pull request as ready for review November 16, 2020 17:36
@nshaw
Copy link

nshaw commented Nov 16, 2020

@avdev4j , 1) I like the test refactoring, less code especially in this area is always good. 2) I'd favor defaulting to prod-ready settings which would include a default cache configuration. (I assume a cluster-aware implementation, e.g. redis, is also on the roadmap.) I don't think I know enough in this area to comment on which option is better but providing a default will help get a developer one step closer to a full application.

@avdev4j
Copy link
Contributor Author

avdev4j commented Nov 16, 2020

I'd favor defaulting to prod-ready settings which would include a default cache configuration (I assume a cluster-aware implementation, e.g. redis, is also on the roadmap.)

This is my main issue right now. I think the best thing to do is to set caches with default values just like JHipster does.

quarkus.cache.caffeine.ENTITY.maximum-size=100
quarkus.cache.caffeine.ENTITY.expire-after-write=3600S

We need to do this for:

  • User entity
  • Authority entity
  • Each generated Entity

I will do it tomorrow ;)

@nshaw
Copy link

nshaw commented Nov 16, 2020

Sounds like a good plan, thanks!

@avdev4j
Copy link
Contributor Author

avdev4j commented Nov 16, 2020

Oh and yes, we need to provide a least a solution for distributed cache implementation. Unfortunately, it seems Quarkus does not provide an abstraction for this kind of scenario. The only solution seems to be using a client librairie. I have in mind redis, Infinispan and Hazelcast.

I would like to do it for the next PR, It will be better to separate those from the current one.

@nshaw
Copy link

nshaw commented Nov 16, 2020

@joewhite101 probably has an opinion but we have Infinispan for Entando 6.2 and are adding redis for 6.3, so those would be my preferences, with Hazelcast in third place.

Definitely agree, doing that in its own PR makes sense. This one already has a lot.

@avdev4j
Copy link
Contributor Author

avdev4j commented Nov 16, 2020

I agree, according to main JHipster features I would say:

  1. redis
  2. infinispan (in beta in the main project)
  3. Hazelcast (seems to be to less popular right now)

Redis seems to be quite used by cloud services too but I don't master this point so only suppositions.

@avdev4j
Copy link
Contributor Author

avdev4j commented Nov 17, 2020

How JHipster deal with Spring Cache in Main generator:

code layer: applicative cache --- hibernate 2nd level cache
                               |
             abstraction layer: Spring cache manager 
                               |
          implementation layer:  Caffeine, Redis, EhCache...

It's easy to understand that both applicative and hibernate 2nd level cache share the same implementation, so if we use a distributed or centralized one we can't have issue with synchronization between different instances.

Also, the code won't change because it will always use the Spring annotations, only the configuration part will be different according to the chosen solution.

Unfortunately, this is not possible in Quarkus. We have Quarkus cache but it has only one impl with Caffeine. If we use that with Hibernate 2nd level cache we would not have issue because everything is local.
Indeed, because Hibernate handles is own cache impl, we can't use it with distributed one.

For me we coud only do that :

  • Allow hibernate 2nd level cache only with Caffeine or without cache aka 'no' (or any local caching solution in the future)
  • Provide another caching solution outside Quarkus cache with client like Redis or Infinispan (will be done in another PR)

How JHipster Quarkus will deal with Caching:

On app generation

  • You chose Caffeine: User and UserService have a Quarkus cache impl (userByLogin and userByEmail), application.properties has configuration set
    @CacheResult(cacheName = USERS_BY_LOGIN_CACHE)
    public static Optional<User> findOneWithAuthoritiesByLogin(String login) {
        return find("FROM User u LEFT JOIN FETCH u.authorities WHERE u.login = ?1", login)
            .firstResultOptional();
    }
    @CacheInvalidate(cacheName = User.USERS_BY_LOGIN_CACHE)
    public void clearUserCachesByLogin(String login) {}
    # configure your caches
    quarkus.cache.caffeine."usersByEmail".maximum-size=100
    quarkus.cache.caffeine."usersByEmail".expire-after-write=3600S
    quarkus.cache.caffeine."usersByLogin".maximum-size=100
    quarkus.cache.caffeine."usersByLogin".expire-after-write=3600S
  • You chose Hibernate 2nd level cache: User and Authority have annotations
    @Entity
    @Table(name = "jhi_user")
    @Cacheable
    public class User extends PanacheEntityBase implements Serializable {};
    @Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
    @BatchSize(size = 20)
    public Set<Authority> authorities = new HashSet<>();

On entity generation e.g. foo

For this part nothing will be done with Caffeine, it's up to developer to do something close we have in User/UserService. Applicative cache is more related to business logic.

  • Hibernate second level cache is selected: The entity has all annotations, we add properties for each cache in application properties (relationship includes)
    @Entity
    @Table(name = "foo")
    @Cacheable
    @RegisterForReflection
    public class Foo extends PanacheEntityBase implements Serializable {}
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    public Set<Label> labels = new HashSet<>();
    quarkus.hibernate-orm.cache."io.github.jhipster.sample.domain.Foo".expiration.max-idle=3600S
    quarkus.hibernate-orm.cache."io.github.jhipster.sample.domain.Foo".memory.object-count=100
    quarkus.hibernate-orm.cache."io.github.jhipster.sample.domain.Foo.labels".expiration.max-idle=3600S
    quarkus.hibernate-orm.cache."io.github.jhipster.sample.domain.Foo.labels".memory.object-count=100

@avdev4j
Copy link
Contributor Author

avdev4j commented Nov 17, 2020

@nshaw @joewhite101 @danielpetisme

I wrote this documentation to explain a bit more how this works, hope it will help (we could update the readme to add that).

Copy link
Member

@danielpetisme danielpetisme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, Awesome work

@danielpetisme
Copy link
Member

Regarding the caching doc, should it be done in the README.MD or in an external documentation

@sonarcloud
Copy link

sonarcloud bot commented Dec 2, 2020

Kudos, SonarCloud Quality Gate passed!

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
0.0% 0.0% Duplication

@danielpetisme danielpetisme merged commit 54195e4 into jhipster:main Dec 2, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants