技术控

    今日:19| 主题:49390
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Build a JSON API Microservice With Spring Boot and Elide

[复制链接]
零食戒了 发表于 2016-10-11 14:05:11
101 4

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
In the past, I havewritten about how    Elidecan be used with Spring to create    JSON API-compatible REST services. In this article, I want to show you how to create a self-contained microservice that implements Elide’s security layers, and also makes use of Spring and    H2to provide a testable environment that has no dependencies on external databases.  
  To get started, let's take a look at the Gradle build file.
  There is nothing particularly exciting here. Most of this code was generated with    Spring Initializr. There are some changes worth mentioning though:  
  
       
  • The H2 database is only compiled for the tests, as we are not using it for the main application, but are using it to mock up a database environment for unit tests.   
  • We’ve used Hibernate 5.1.2.Final. I had issues with Hibernate 5.2 with Elide, but these might be resolved by the time you read this article.  
  1. buildscript {
  2.     ext {
  3.         springBootVersion = '1.4.1.RELEASE'
  4.     }
  5.     repositories {
  6.         mavenCentral()
  7.     }
  8.     dependencies {
  9.         classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
  10.     }
  11. }
  12. apply plugin: 'java'
  13. apply plugin: 'eclipse'
  14. apply plugin: 'spring-boot'
  15. jar {
  16.     baseName = 'microservice-template'
  17.     version = '0.0.1-SNAPSHOT'
  18. }
  19. sourceCompatibility = 1.8
  20. targetCompatibility = 1.8
  21. repositories {
  22.     mavenCentral()
  23. }
  24. dependencies {
  25.     compile('org.springframework.boot:spring-boot-starter-data-jpa')
  26.     compile('org.springframework.boot:spring-boot-starter-data-rest')
  27.     testCompile('com.h2database:h2')
  28.     testCompile('org.springframework.boot:spring-boot-starter-test')
  29.     compile group: 'com.yahoo.elide', name: 'elide-core', version: '2.3.17'
  30.     compile group: 'com.yahoo.elide', name: 'elide-datastore-hibernate5', version: '2.3.17'
  31.     compile group: 'org.hibernate', name: 'hibernate-core', version: '5.1.2.Final'
  32.     compile group: 'org.hibernate', name: 'hibernate-entitymanager', version: '5.1.2.Final'
  33.     compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.39'
  34. }
复制代码
The entry point to the application is in MicroserviceTemplateApplication. Again, this is boilerplate code.
  1. package com.matthewcasperson;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class MicroserviceTemplateApplication {
  6.     public static void main(String[] args) {
  7.         SpringApplication.run(MicroserviceTemplateApplication.class, args);
  8.     }
  9. }
复制代码
The next four classes are used to configure access to the database.
  
       
  • DatasourceConfiguration, which holds some Spring annotations that configure data access, and return a bean that configures the embedded Tomcat server with a data source.   
  • DatasourceConnection, which either returns the data source hosted by Tomcat, or the H2 database that we’ll create as part of the TEST profile.   
  • DatasourceEmfConfiguration, which configures the JPA EntityManager Factory.   
  • DatasourceTXManagerConfiguration, which configures that transaction manager.  
  1. package com.matthewcasperson.config;
  2. import org.apache.catalina.Context;
  3. import org.apache.catalina.startup.Tomcat;
  4. import org.apache.tomcat.jdbc.pool.DataSource;
  5. import org.apache.tomcat.util.descriptor.web.ContextResource;
  6. import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer;
  7. import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.context.annotation.Import;
  11. import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
  12. import org.springframework.transaction.annotation.EnableTransactionManagement;
  13. /**
  14. * Spring JPA configuration for app quote microservice data
  15. */
  16. @Configuration
  17. @EnableJpaRepositories(
  18.     // This needs to match the entity manager factory created below
  19.     entityManagerFactoryRef = "MicroserviceEMF",
  20.     // This needs to match the transaction manager created below
  21.     transactionManagerRef = "MicroserviceTX",
  22.     // This is the package that holds the Spring repository using the classes exposed by this EMF
  23.     basePackages = "com.matthewcasperson.repository"
  24. )
  25. @EnableTransactionManagement
  26. @Import({
  27.     DatasourceConnection.class,
  28.     DatasourceEmfConfiguration.class,
  29.     DatasourceTXManagerConfiguration.class
  30. })
  31. public class DatasourceConfiguration {
  32.     @Bean
  33.     public TomcatEmbeddedServletContainerFactory tomcatFactory() {
  34.         return new TomcatEmbeddedServletContainerFactory() {
  35.             @Override
  36.             protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(final Tomcat tomcat) {
  37.                 tomcat.enableNaming();
  38.                 return super.getTomcatEmbeddedServletContainer(tomcat);
  39.             }
  40.             @Override
  41.             protected void postProcessContext(Context context) {
  42.                 ContextResource resource = new ContextResource();
  43.                 resource.setName("jdbc/microservice");
  44.                 resource.setType(DataSource.class.getName());
  45.                 resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
  46.                 resource.setProperty("driverClassName", "com.mysql.jdbc.Driver");
  47.                 resource.setProperty("username", "root");
  48.                 resource.setProperty("password", "password1!");
  49.                 resource.setProperty("url", "jdbc:mysql://localhost:3306/microservice");
  50.                 context.getNamingResources().addResource(resource);
  51.             }
  52.         };
  53.     }
  54. }
复制代码
The DatasourceConnection class is where we swap between the production database (MySQL in this case, but could be any other database with some slight configuration changes), and the H2 in-memory database based on the Spring profile that is being used. The in-memory database makes testing easy as we can run real tests against a real database without any additional infrastructure.
  As you can see, it is very easy to use the H2 database. Once a connection is made, the database is created automatically and the INIT parameter on the JBDC URL gives us a way to initialize any required schemas.
  1. package com.matthewcasperson.config;
  2. import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi;
  3. import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
  4. import org.springframework.boot.context.properties.ConfigurationProperties;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Conditional;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.context.annotation.Profile;
  9. import org.springframework.jdbc.datasource.DriverManagerDataSource;
  10. import org.springframework.jndi.JndiTemplate;
  11. import java.util.Properties;
  12. import javax.sql.DataSource;
  13. /**
  14. * Setup a connection to the database
  15. */
  16. @Configuration
  17. public class DatasourceConnection {
  18.     private static final String H2_JDBC = "jdbc:h2:mem:microservice;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS MicroserviceDatabase";
  19.     private static final String H2_USER = "";
  20.     private static final String H2_PASSWORD = "";
  21.     private static final String H2_DRIVER = "org.h2.Driver";
  22.     /**
  23.      * This is where Tomcat has created our datasource
  24.      */
  25.     private static final String LOOKUP_DATASOURCE_JNDI = "java:/comp/env/jdbc/microservice";
  26.     @Bean(name = "MicroserviceDS")
  27.     @Profile("!TEST")
  28.     public DataSource jndiLookupDataSource() throws Exception {
  29.         return (DataSource) new JndiTemplate().lookup(LOOKUP_DATASOURCE_JNDI);
  30.     }
  31.     /**
  32.      * When running tests we connect to an in memory H2 database
  33.      */
  34.     @Bean(name = "MicroserviceDS")
  35.     @Profile("TEST")
  36.     public DataSource driverManagerLookupDataSource() throws Exception {
  37.         DriverManagerDataSource dataSource = new DriverManagerDataSource();
  38.         dataSource.setDriverClassName(H2_DRIVER);
  39.         dataSource.setUrl(H2_JDBC);
  40.         dataSource.setUsername(H2_USER);
  41.         dataSource.setPassword(H2_PASSWORD);
  42.         return dataSource;
  43.     }
  44. }
复制代码
  1. package com.matthewcasperson.config;
  2. import org.springframework.beans.factory.annotation.Qualifier;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.context.annotation.Profile;
  6. import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
  7. import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
  8. import java.util.HashMap;
  9. import java.util.Map;
  10. import javax.persistence.EntityManagerFactory;
  11. import javax.sql.DataSource;
  12. /**
  13. * Set up JPA EMF for lookup database
  14. */
  15. @Configuration
  16. public class DatasourceEmfConfiguration {
  17.     /**
  18.      * Prod code uses MySQL databases
  19.      */
  20.     @Bean(name = "MicroserviceEMF")
  21.     @Profile("!TEST")
  22.     public EntityManagerFactory lookupEntityManagerFactory(
  23.         @Qualifier("MicroserviceDS") final DataSource lookupDataSource
  24.     ) {
  25.         final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
  26.         vendorAdapter.setGenerateDdl(false);
  27.         final Map<String, String> properties = new HashMap<>();
  28.         properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
  29.         final LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
  30.         factory.setJpaVendorAdapter(vendorAdapter);
  31.         factory.setJpaPropertyMap(properties);
  32.         factory.setPackagesToScan("com.matthewcasperson.domain.**");
  33.         factory.setDataSource(lookupDataSource);
  34.         factory.setPersistenceUnitName("LookupPersistenceUnit");
  35.         factory.afterPropertiesSet();
  36.         return factory.getObject();
  37.     }
  38.     /**
  39.      * When running tests we will be using an in memory h2 database
  40.      */
  41.     @Bean(name = "MicroserviceEMF")
  42.     @Profile("TEST")
  43.     public EntityManagerFactory lookupTestEntityManagerFactory(
  44.         @Qualifier("MicroserviceDS") final DataSource lookupDataSource
  45.     ) {
  46.         final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
  47.         vendorAdapter.setGenerateDdl(false);
  48.         final Map<String, String> properties = new HashMap<>();
  49.         properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
  50.         properties.put("hibernate.current_session_context_class", "org.springframework.orm.hibernate5.SpringSessionContext");
  51.         final LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
  52.         factory.setJpaVendorAdapter(vendorAdapter);
  53.         factory.setJpaPropertyMap(properties);
  54.         factory.setPackagesToScan("com.matthewcasperson.domain.**");
  55.         factory.setDataSource(lookupDataSource);
  56.         factory.setPersistenceUnitName("LookupPersistenceUnit");
  57.         factory.afterPropertiesSet();
  58.         return factory.getObject();
  59.     }
  60. }
复制代码
  1. package com.matthewcasperson.config;
  2. import org.springframework.beans.factory.annotation.Qualifier;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.orm.jpa.JpaTransactionManager;
  5. import org.springframework.transaction.PlatformTransactionManager;
  6. import javax.persistence.EntityManagerFactory;
  7. /**
  8. * Set up transaction manager for lookup database
  9. */
  10. public class DatasourceTXManagerConfiguration {
  11.     @Bean(name = "MicroserviceTX")
  12.     public PlatformTransactionManager lookupTransactionManager(
  13.         @Qualifier("MicroserviceEMF") EntityManagerFactory lookupEntityManagerFactory
  14.     ) {
  15.         final JpaTransactionManager txManager = new JpaTransactionManager();
  16.         txManager.setEntityManagerFactory(lookupEntityManagerFactory);
  17.         return txManager;
  18.     }
  19. }
复制代码
The JPA entity is defined in the MicroserviceKeyValue class. Again, this class is mostly boilerplate code, with the exception of the    @ReadPermissionand    @ComputedAttributeannotations, which are used by Elide to enforce some security restrictions. These will be covered later.  
  1. package com.matthewcasperson.domain;
  2. import com.yahoo.elide.annotation.ComputedAttribute;
  3. import com.yahoo.elide.annotation.ReadPermission;
  4. import javax.persistence.Basic;
  5. import javax.persistence.Column;
  6. import javax.persistence.Entity;
  7. import javax.persistence.GeneratedValue;
  8. import javax.persistence.GenerationType;
  9. import javax.persistence.Id;
  10. import javax.persistence.Table;
  11. import javax.persistence.Transient;
  12. /**
  13. * Auotgenerated JPA entity, with some additions to support Elide.
  14. * Notably we have added the @ReadPermission annotation, which will
  15. * only allow entities that pass the rule we have called
  16. * "Client supplied secret".
  17. */
  18. @ReadPermission(expression = "Client supplied secret")
  19. @Entity
  20. @Table(name = "Microservice", schema = "MicroserviceDatabase")
  21. public class MicroserviceKeyValue {
  22.     private Integer id;
  23.     private String key;
  24.     private String value;
  25.     private String secret;
  26.     @Id
  27.     @GeneratedValue(strategy= GenerationType.IDENTITY)
  28.     @Column(name = "id")
  29.     public Integer getId() {
  30.         return id;
  31.     }
  32.     public void setId(Integer id) {
  33.         this.id = id;
  34.     }
  35.     @Basic
  36.     @Column(name = "key")
  37.     public String getKey() {
  38.         return key;
  39.     }
  40.     public void setKey(String key) {
  41.         this.key = key;
  42.     }
  43.     @Basic
  44.     @Column(name = "value")
  45.     public String getValue() {
  46.         return value;
  47.     }
  48.     public void setValue(String value) {
  49.         this.value = value;
  50.     }
  51.     @Override
  52.     public boolean equals(Object o) {
  53.         if (this == o) {
  54.             return true;
  55.         }
  56.         if (o == null || getClass() != o.getClass()) {
  57.             return false;
  58.         }
  59.         MicroserviceKeyValue that = (MicroserviceKeyValue) o;
  60.         if (id != that.id) {
  61.             return false;
  62.         }
  63.         if (key != null ? !key.equals(that.key) : that.key != null) {
  64.             return false;
  65.         }
  66.         if (value != null ? !value.equals(that.value) : that.value != null) {
  67.             return false;
  68.         }
  69.         return true;
  70.     }
  71.     @Override
  72.     public int hashCode() {
  73.         int result = id;
  74.         result = 31 * result + (key != null ? key.hashCode() : 0);
  75.         result = 31 * result + (value != null ? value.hashCode() : 0);
  76.         return result;
  77.     }
  78.     @Transient
  79.     @ComputedAttribute
  80.     public String getSecret() {
  81.         return secret;
  82.     }
  83.     public void setSecret(String secret) {
  84.         this.secret = secret;
  85.     }
  86. }
复制代码
In order to expose the JPA entity to Elide, we need an    @Includeannotation in the package-info.java file.  
  1. /**
  2. * JPA entities that represent the domain objects of this service
  3. */
  4. @Include(rootLevel=true)
  5. package com.matthewcasperson.domain;
  6. import com.yahoo.elide.annotation.Include;
复制代码
A Spring repository gives us easy CRUD access to the database. This is more boilerplate code.
  1. package com.matthewcasperson.repository;
  2. import com.matthewcasperson.domain.MicroserviceKeyValue;
  3. import org.springframework.data.repository.CrudRepository;
  4. /**
  5. * A repo for working with jpa entities
  6. */
  7. public interface MicroserviceRepository extends CrudRepository<MicroserviceKeyValue, Integer> {
  8. }
复制代码
Now let's take a look at the REST controller. Here we have implemented the GET HTTP method, and used Elide to respond to the request. I won’t go into to much detail here as most of this code has been covered in    Create a JSON API REST Service With Spring Boot and Elide, with a few exceptions.  
  1. package com.matthewcasperson;
  2. import com.matthewcasperson.domain.MicroserviceKeyValue;
  3. import com.matthewcasperson.security.OpaqueUser;
  4. import com.matthewcasperson.security.ValidateSecretDetails;
  5. import com.yahoo.elide.Elide;
  6. import com.yahoo.elide.ElideResponse;
  7. import com.yahoo.elide.audit.AuditLogger;
  8. import com.yahoo.elide.audit.Slf4jLogger;
  9. import com.yahoo.elide.core.DataStore;
  10. import com.yahoo.elide.core.EntityDictionary;
  11. import com.yahoo.elide.core.Initializer;
  12. import com.yahoo.elide.datastores.hibernate5.HibernateStore;
  13. import com.yahoo.elide.security.checks.Check;
  14. import org.hibernate.SessionFactory;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.http.MediaType;
  17. import org.springframework.transaction.annotation.Transactional;
  18. import org.springframework.web.bind.annotation.*;
  19. import javax.persistence.EntityManagerFactory;
  20. import javax.servlet.http.HttpServletRequest;
  21. import javax.ws.rs.QueryParam;
  22. import javax.ws.rs.core.MultivaluedHashMap;
  23. import javax.ws.rs.core.MultivaluedMap;
  24. import java.util.HashMap;
  25. import java.util.Map;
  26. import static com.matthewcasperson.MicroserviceController.BASE_URL;
  27. /**
  28. * This is the service that applications can call to determine what application was
  29. * used to generate a quote.
  30. */
  31. @RestController
  32. @RequestMapping(BASE_URL)
  33. public class MicroserviceController {
  34.     protected static final String BASE_URL = "/microservice/1.0";
  35.     @Autowired
  36.     private EntityManagerFactory emf;
  37.     @Autowired
  38.     private Initializer initializer;
  39.     /**
  40.      * Converts a plain map to a multivalued map
  41.      * @param input The original map
  42.      * @return A MultivaluedMap constructed from the input
  43.      */
  44.     private MultivaluedMap<String, String> fromMap(final Map<String, String> input) {
  45.         return new MultivaluedHashMap<String, String>(input);
  46.     }
  47.     @RequestMapping(
  48.             method = RequestMethod.GET,
  49.             produces = MediaType.APPLICATION_JSON_VALUE,
  50.             value={"/{entity}", "/{entity}/{id}/relationships/{entity2}", "/{entity}/{id}/{child}", "/{entity}/{id}"})
  51.     @Transactional
  52.     @ResponseBody
  53.     public String jsonApiGet(
  54.             @RequestParam final Map<String, String> allRequestParams,
  55.             @QueryParam("secret") final String secret,
  56.             final HttpServletRequest request) {
  57. /*
  58.             Get thr request URI, and normalise it against the microservice endpoint prefix
  59.          */
  60.         final String restOfTheUrl = request.getRequestURI().replaceFirst(BASE_URL, "");
  61.         /*
  62.             Elide works with the Hibernate SessionFactory, not the JPA EntityManagerFactory.
  63.             Fortunately we san unwrap the JPA EntityManagerFactory to get access to the
  64.             Hibernate SessionFactory.
  65.          */
  66.         final SessionFactory sessionFactory = emf.unwrap(SessionFactory.class);
  67. /*
  68.             Elide takes a hibernate session factory
  69.         */
  70.         final DataStore dataStore = new HibernateStore(sessionFactory);
  71.         /*
  72.             Define a logger
  73.          */
  74.         final AuditLogger logger = new Slf4jLogger();
  75. /*
  76. These are the Elide rules that will be checked to ensure that only matching information
  77. has been sent to the client
  78. */
  79.         final Map<String, Class<? extends Check>> checks = new HashMap<>();
  80.         checks.put("Client supplied secret", ValidateSecretDetails.class);
  81.         final EntityDictionary dictionary = new EntityDictionary(checks);
  82.         /*
  83.             Create the Elide object
  84.          */
  85.         final Elide elide = new Elide.Builder(dataStore)
  86.                 .withAuditLogger(logger)
  87.                 .withEntityDictionary(dictionary)
  88.                 .build();
  89.         /*
  90.             This is the object that we use to enrich the JPA entities with security
  91.             information.
  92.          */
  93.         dictionary.bindInitializer(initializer, MicroserviceKeyValue.class);
  94. /*
  95.             Convert the map to a MultivaluedMap, which we can then pass to Elide
  96.          */
  97.         final MultivaluedMap<String, String> params = fromMap(allRequestParams);
  98.         /*
  99.             This is where the magic happens. We pass in the path, the params, and a place holder security
  100.             object (which we won't be using here in any meaningful way, but can be used by Elide
  101.             to authorize certain actions), and we get back a response with all the information
  102.             we need.
  103.          */
  104.         final ElideResponse response = elide.get(restOfTheUrl, params, new OpaqueUser(secret));
  105.         /*
  106.             Return the JSON response to the client
  107.          */
  108.         return response.getBody();
  109.     }
  110. }
复制代码
There are two things about this implementation that are worth pointing out.
  The first is the mapping of the ValidateSecretDetails class to the security rule "Client supplied secret."
  1. package com.matthewcasperson;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class MicroserviceTemplateApplication {
  6.     public static void main(String[] args) {
  7.         SpringApplication.run(MicroserviceTemplateApplication.class, args);
  8.     }
  9. }0
复制代码
You might be wondering why you would need to use Elide’s security when Spring offers a wealth of security either at the method level or via AOP. The reason is because the method that is responding to the REST request is very generic. Take a look at the URL that is assigned to the request mapping for the REST method.
  1. package com.matthewcasperson;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class MicroserviceTemplateApplication {
  6.     public static void main(String[] args) {
  7.         SpringApplication.run(MicroserviceTemplateApplication.class, args);
  8.     }
  9. }1
复制代码
It has been written in such a way as to respond to any GET request that is defined in the JSON API specification. This method doesn’t know if you are returning a collection of “message of the day” quotes or nuclear launch codes. Not only that, but because JSON API supports returning compound documents, you could conceivably craft a request that traversed and returned many JPA relationships. The    Elide documentationhas a more detailed explanation of these security issues.  
  The general nature of the method and the expressive functionality of the JSON API specification make writing security at the method level quite difficult. You would need to be able to understand the format of the various query parameters and unwind the relationships being requested before you could make any meaningful security decisions.
  Thankfully, Elide provides a security layer that inherently understands the relationship between the request parameters and the resulting entity graph that is returned to the user. In our case, we are using the ValidateSecretDetails class to only allow entities that pass a custom security check to be included in the returned result.
  In our case, we require the end user to supply a secret string, and only entities that share that secret string will be returned. This may seem like a trivial example, but in the real world, you might do something similar with those secret questions that are often used to perform a password reset.
  1. package com.matthewcasperson;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class MicroserviceTemplateApplication {
  6.     public static void main(String[] args) {
  7.         SpringApplication.run(MicroserviceTemplateApplication.class, args);
  8.     }
  9. }2
复制代码
So where does this secret string come from? If you look back at the JPA entity in the MicroserviceKeyValue class, you’ll see that the getSecret() method has both a    @Transientand a    @ComputedAttributeannotation.    @Transientis a JPA annotation and means that the field will not be persisted to the database.    @ComputedAttributeis an Elide annotation, and it means that the value will be returned as part of the JSON API result, even though it will not be persisted in the database.  
  We use this transient property to hold the secret string that our clients must supply in order to have the entity returned. But if this value doesn’t come from the database, where does it come from? That is where the second significant change comes in, where we assign an Elide Initializer to enrich the JPA entities as they are read from the database.
  1. package com.matthewcasperson;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class MicroserviceTemplateApplication {
  6.     public static void main(String[] args) {
  7.         SpringApplication.run(MicroserviceTemplateApplication.class, args);
  8.     }
  9. }3
复制代码
The initializer object is an instance of the SecretEnricher class. It was assigned via a Spring injection, which is important because it demonstrates that we can use Spring objects with Elide, meaning that our enrichment code has full access to the Spring library.
  Even though it wasn’t necessary in this example, the SecretEnricher class is a Spring Component. Inside we generate a new secret string for each entity returned from the database. Obviously, this implementation doesn’t reflect how you would generate security codes in real life, but it is a simple example of using Elide classes to enrich JPA entities with additional information at run time.
  1. package com.matthewcasperson;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class MicroserviceTemplateApplication {
  6.     public static void main(String[] args) {
  7.         SpringApplication.run(MicroserviceTemplateApplication.class, args);
  8.     }
  9. }4
复制代码
The last piece of the puzzle is how we get the secret string passed by the user into the method in the ValidateSecretDetails class which does the actual authorization. This is done with a POJO called OpaqueUser. This class serves as our Elide user and contains whatever information we require to perform our security checks.
  1. package com.matthewcasperson;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class MicroserviceTemplateApplication {
  6.     public static void main(String[] args) {
  7.         SpringApplication.run(MicroserviceTemplateApplication.class, args);
  8.     }
  9. }5
复制代码
The OpaqueUser class itself is nothing more than a data object that holds the secret string.
  1. package com.matthewcasperson;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class MicroserviceTemplateApplication {
  6.     public static void main(String[] args) {
  7.         SpringApplication.run(MicroserviceTemplateApplication.class, args);
  8.     }
  9. }6
复制代码
With these classes in place, a GET request will perform the following:
  
       
  • User makes a GET request with a secret query param.   
  • The MicroserviceController class intercepts the request and runs the jsonApiGet method.   
  • Elide is configured with the Check class ValidateSecretDetails and the Initializer SecretEnricher.   
  • The MicroserviceKeyValue entities are generated from the information in the database.   
  • The MicroserviceKeyValue entities are enriched by the SecretEnricher instance, and given a secret string.   
  • The MicroserviceKeyValue entities are filtered by the ValidateSecretDetails, and any whose secret string doesn’t match the value supplied by the user are removed from the result.   
  • The JSON API document is returned to the user.  
  Of course, every good microservice has a suite of tests to validate their functionality. Spring makes testing a mock REST controller easy, and the H2 memory database we defined earlier means that we can test the entire process of requesting, filtering and returning results without any external infrastructure.
  Our test class does two important things.
  
       
  • It creates the database in H2 and populates it with dummy data.   
  • It executes a simulated HTTP request against the REST controller and validates the results.  
  1. package com.matthewcasperson;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class MicroserviceTemplateApplication {
  6.     public static void main(String[] args) {
  7.         SpringApplication.run(MicroserviceTemplateApplication.class, args);
  8.     }
  9. }7
复制代码
The end result of this tutorial is a standalone microservice that exposes JPA entities via the JSON API specification with custom security routines and tests that can be run independently of any external database. This is a great foundation on which to build a robust and standards-compliant microservice.
  Grab the source code from    GitHub.
友荐云推荐




上一篇:Jenkins+Gradle实现Android自动化构建
下一篇:react-adsence:简单的 Google 广告 React 组件
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

百年不腻好吗 发表于 2016-10-11 15:19:43
在神经的人群里呆久了,我发现我正常了。
回复 支持 反对

使用道具 举报

梦中人是她 发表于 2016-10-11 17:31:12
大学就是大概学学!
回复 支持 反对

使用道具 举报

我相信 发表于 2016-11-16 01:40:28
very good
回复 支持 反对

使用道具 举报

katenMido 发表于 2016-11-16 02:02:08
能力就像瓜子仁,只有咬牙才能嗑出来。
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表