GithubHelp home page GithubHelp logo

Comments (10)

virtualdogbert avatar virtualdogbert commented on August 29, 2024

Hey, Pavel, i think,

The issue is that @Transactional adds a @CompileStatic to the original method which doesn't work with the @Enforce annotations because it tries to statically compile what is in the annotation. So to compensate for this I made the variations that you've seen:
@Enforce // Original
@EnforceS //Statically compiles the original method.
@EnforceT //Add Transactionality to the original
@EnforceTS //Statically compiles, and adds Transactionality to the original method.

I also added versions of @Transactional and @CompileStatic that don't mess with the @Enforce annotations:
@EnforcerCompileStatic
@EnforcerTransactional

So double-check the documentation for this and see if it makes sense:
https://virtualdogbert.github.io/Enforcer/#enforcer-2-0-0

If not I'll have to update that, but the gist is that you can't use the Grails @Transational annotation with @Enforce, So if you need a method that uses @Enforce to be transactional you would use EnforceT. If you wanted that Method to also be Statically compiled you would use @EnforceTS. If you wanted the whole service to be Transactional and not mess with the existing Enforce Annotations you would use @EnforcerTransactional on the class, combined with @EnforceT or @EnforceTS on the methods.

from enforcer.

virtualdogbert avatar virtualdogbert commented on August 29, 2024

@pshepilov-amc Did what I suggested work? Is the documentation clear enough or need some work? I want to make sure that it is clear, and that I draw the proper attention to it, so that others don't struggle with it and just think it's broken and not use it.

from enforcer.

pshepilov-amc avatar pshepilov-amc commented on August 29, 2024

@virtualdogbert, sorry for not responding for some time.

  1. In the documentation and in your first comment you mention that @transactional adds @CompileStatic behavior to the original method, did I get it right? But I can't confirm it: I can't find it in any documentation, can't find it in the source code, can't test it myself. For example, the following code compiles fine (although method bar() doesn't exist on User class)
class MyService {

    @Transactional
    void foo() {
        User user = new User()
        user.bar()
    }

}

So could you please help me and point out the documentation, where I can read more about it?

  1. What is the actual purpose of @EnforcerCompileStatic and @EnforcerTransactional. As I understood one should use it instead of @CompileStatic and @transactional when using @Enforce annotation. But how is that differ from using @EnforceS/@EnforceT/@EnforceTS ? Or it is just yet another way of doing it?

  2. Have you considered implementing the following functionality?
    When someone adds @Enforce(S/T) annotation we create to new methods: enforcerInvocation and newMethod. Then we move enforcer part into the first method and apply (S/T) logic to it only. Then move the method code to the second method and apply other annotations to it (e.g. @transactional, @CompileStatic etc.). Original method code is replaced by invocation of these two methods and its annotations are disabled (removed or so, if its possible).
    So the following code

class MyService {
    @EnforceS({ hasRole('ADMIN') })
    @Transactional
    void foo() {
        User user = new User()
        user.save()
    }
}

will transform into something like

class MyService {
    EnforcerService enforcerService

    void foo() {
        enforce_foo()
        original_foo()
    }

    @CompileStatic
    void enforce_foo() {
        enforcerService.enforce ({ hasRole('ADMIN') })
    }

    @Transactional
    void original_foo() {
        User user = new User()
        user.save()
    }
}

From my point of view, this approach is more clear and more flexible in usage. What do you think about it?

from enforcer.

virtualdogbert avatar virtualdogbert commented on August 29, 2024
  1. It's not in the documentation but in the source code
    https://github.com/grails/grails-data-mapping/blob/master/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/transactions/transform/TransactionalTransform.groovy

Which extends:
https://github.com/grails/grails-data-mapping/blob/master/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/transform/AbstractDatastoreMethodDecoratingTransformation.groovy

Which calls a compileMethodStatically method from:
https://github.com/grails/grails-data-mapping/blob/master/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/transform/AbstractMethodDecoratingTransformation.groovy#L221

This code is fairly complex and was hard for me to figure out. However, if you run the Gradle task console with your example and use the ASTBrowser you'll be able to see that it does add a compile static.

  1. Yes @EnforcerCompileStatic and @EnforcerTransactional are essentially versions of CompileStatic and Transactional, that you can apply at a class level, that will not affect any method and you have an @Enforce method on or it's variants.

@EnforceS is Enforce but with the `@CompileStatic' functionality for the method, you are annotating.

@EnforceT is Enforce but with the @Tansactional functionality for the method, you are annotating.

@EnforceTS is Enforce but with the @Tansactional and `@CompileStatic' functionality for the method, you are annotating.

  1. Actually that's how the @EnforceS, @EnforceT and @EnforceTS already work you just don't really see it because it's happening at compile time. However is you use the Gradle console command, and look at the ATS browser you'll see the methods it adds.

class MyService {
    @EnforceTS({ hasRole('ADMIN') })
    void foo() {
        User user = new User()
        user.save()
    }
   @EnforceT({ hasRole('ADMIN') })
    void bar() {
        User user = new User()
        user.save()
    }

   @EnforceS({ hasRole('ADMIN') })
    void baz() {
        User user = new User()
        user.save()
    }
}

will transform into something like

class MyService {
    EnforcerService enforcerService

    void foo() {
        wrapped_foo()
    }

    @CompileStatic
    @Transactional
    void wrapped_foo() {
        enforcerService.enforce ({ hasRole('ADMIN') })
        User user = new User()
        user.save()
    }


    void bar() {
        wrapped_bar()
    }

    @Transactional
    void wrapped_bar() {
        enforcerService.enforce ({ hasRole('ADMIN') })
        User user = new User()
        user.save()
    }

   void baz() {
        wrapped_baz()
    }

    @CompileStatic
    void wrapped_baz() {
        enforcerService.enforce ({ hasRole('ADMIN') })
        User user = new User()
        user.save()
    }
}

However, when you look at the console AST Browser you'll see slightly different code because @Transactional does its own wrapping, which in its code call weaving.

from enforcer.

pshepilov-amc avatar pshepilov-amc commented on August 29, 2024

Our issue with #3 is that we would like to separate transactions for enforcer logic and for main code. For example when we need to not open transaction for the main method, but enforcer logic requires transaction. Do you think its reasonable?

from enforcer.

virtualdogbert avatar virtualdogbert commented on August 29, 2024

So even with me explaining it in two different ways, you're still not understanding how this works...
Maybe another example

@EnforcerTransactional //Because @Transactional doesn't work with @Enforce.
class MyService {
     //This would be transactional but not have any enforcement
    void someMethod(){
        User user = new User()
        user.save()
    }

    //This would be transaction, compile static and have enforcment
    @EnforceTS({ hasRole('ADMIN') })
    void foo() {
        User user = new User()
        user.save()
    }

   ///This would be transactional and have enforcment.
   @EnforceT({ hasRole('ADMIN') })
    void bar() {
        User user = new User()
        user.save()
    }

    //This woulld be compile static and have enforcement, but no transaction.
   @EnforceS({ hasRole('ADMIN') })
    void baz() {
        //some code
    }
}

I also asked Anton(on linkedin) to take a look at this thread to see if he understands what I'm trying to explain, and if he does maybe he can explain it to you, and explain where I'm going wrong trying to explain it to you...

from enforcer.

AntonPanikov avatar AntonPanikov commented on August 29, 2024

I think there were two misunderstandings at the beginning:

  • Enforce annotations can not be used with Transactional and Compile static annotations - it is clear now, and that there is an alternative annotations from Enforce package
  • The second one was about the possibility to detach the scope of transaction or static compilation of Enforce arguments and method itself

Currently, if you use for example transactional version on a method:

 @EnforceT({ hasRole('ADMIN') })  // (1)
    void bar() {                  // (2)
        User user = new User()
        user.save()
    }

It will bound both closure from (1) and method from (2) into a single transaction. But sometime we would like to have transaction only in (1) and has no transaction in (2) or vice versa has no transaction in (1) and transaction in (2).

Things are getting worse if the method (2) is calling another service method that is transactional and/or the code is invoked asynchronously. In that case transaction from EnforceT became a parent transaction - and it is causing issues further down. That I believe the issue Pavel is facing in the background of a scene. And the solution he is looking for is to avoid transaction nesting to have a cleaner approach for transaction management.

So the question here is that is it possible now or in the future to separate scopes of a transaction and compile static scope for a closure of Enforce annotation and a method itself?

from enforcer.

virtualdogbert avatar virtualdogbert commented on August 29, 2024

I think I understand now, while the code example is bad example, the code the annotation calls requires a transaction, but the code in the method doesn't? In that case what the annotation is doing in injecting a call to the EnforcerService:

https://github.com/virtualdogbert/Enforcer/blob/master/src/main/templates/InstalledEnforcerService.groovy.template

where the closure gets executed, calling a delegate method, that delegate method could have a @Transactional added to it. This way you wouldn't have to mess with changing the annotation logic and get the same effect. The EnforcerService is in your code and you have the ability to update it, or preferably extend it using traits, which would add methods that the anotation could call.

Also if you want to be able to manipulate the transactions @EnforceT and @EnforceTS have all the same parameters as @Transactional:
https://github.com/virtualdogbert/Enforcer/blob/master/src/main/groovy/com/virtualdogbert/ast/EnforceT.groovy#L68

So if you wanted something like
@Transactional(propagation = Propagation.REQUIRES_NEW)

you could have
@EnforceT(value = {/*some test*/}, propagation = Propagation.REQUIRES_NEW)

take a look at the java docs in the link for the annotations.

Also so you can see what the transforms are actually doing I created a couple of gists, one source using Enforcer and one that has what the transform does to the code, as viewed from the Groovy AST Browser

Sample Enforcer code
https://gist.github.com/virtualdogbert/35c97135a6a64ff6cb1362fc77a42c9c

SampleTransformed Enforcer code:
https://gist.github.com/virtualdogbert/bc4b3a84bc67ce60cf8a708e3372dbd5

Let me know if this helps.

from enforcer.

pshepilov-amc avatar pshepilov-amc commented on August 29, 2024

Hey, Tucker.

Thank you for your explanation, I appreciate your help. I will look into your notes more carefully and try to use it.

from enforcer.

virtualdogbert avatar virtualdogbert commented on August 29, 2024

No prob, sometime in the near future, I'll fold back in some of these explanations and examples into the docs, so hopefully, things will be clearer for anyone else who tries to uses it. If you have other questions let me know.

from enforcer.

Related Issues (15)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.