diff --git a/README.md b/README.md index 548f19b..9aed659 100644 --- a/README.md +++ b/README.md @@ -137,12 +137,12 @@ The **END** represents the end of the prompts below, when files are written afte
  • Producer
  • -
  • For which topic? +
  • Which topic for (entity/prefix)?
  • +
  • If "Producer" was selected: Do you want to send ordered messages for (entity/prefix) production? (default = Y)
  • Do you want to continue adding consumers or producers? (default = N)
  • If "N" was selected: END
  • diff --git a/USE_CASES.md b/USE_CASES.md index d842abc..cfdcf2c 100644 --- a/USE_CASES.md +++ b/USE_CASES.md @@ -23,7 +23,7 @@ After following the first 3 steps of the [basic usage](README.md#basic-usage) ab 3. "Do you want to clean up your current Kafka configuration?" - Your answer or 'n' by default 4. "For which entity (class name)?" - Foo (the available entities are retrieved in the `.jhipster` folder as `.json`) 5. "Which components would you like to generate?" - Consumer -6. "For which topic?" - Any choice (choosing "Custom topic name" will add another question "What is the topic name?") +6. "Which topic for Foo?" - Any choice (choosing "Custom topic name" will add another question "What is the topic name for Foo?") 7. "What is the consumer polling timeout (in ms)?" - Your answer or '10000' by default (global for all consumers) 8. "Define the auto offset reset policy?" - Your answer or 'earliest' by default (global for all consumers) 9. "Do you want to continue adding consumers or producers?" - Your answer or 'N' par default @@ -39,10 +39,11 @@ After following the first 3 steps of the [basic usage](README.md#basic-usage) ab 3. "Do you want to clean up your current Kafka configuration?" - Your answer or 'n' by default 4. "For which entity (class name)?" - Foo (the available entities are retrieved in the `.jhipster` folder as `.json`) 5. "Which components would you like to generate?" - Producer -6. "For which topic?" - Any choice (choosing "Custom topic name" will add another question "What is the topic name?") -7. "Do you want to continue adding consumers or producers?" - Your answer or 'N' par default -8. Overwrite all files in conflict -9. `FooProducer` (produces `Foo`) is available with a `FooSerializer` and a `FooKafkaResource` to [help testing](README.md#test-consumers-and-producers) +6. "Which topic for Foo?" - Any choice (choosing "Custom topic name" will add another question "What is the topic name for Foo?") +7. "Do you want to send ordered messages for (entity/prefix) production?" - Your answer or 'y' by default +8. "Do you want to continue adding consumers or producers?" - Your answer or 'N' par default +9. Overwrite all files in conflict +10. `FooProducer` (produces `Foo`) is available with a `FooSerializer` and a `FooKafkaResource` to [help testing](README.md#test-consumers-and-producers) ### Create a consumer NOT linked to an entity @@ -54,7 +55,7 @@ After following the first 3 steps of the [basic usage](README.md#basic-usage) ab 4. "For which entity (class name)?" - No entity (will be typed String) 5. "How would you prefix your objects (no entity, for instance: [SomeEventType]Consumer|Producer...)?" - someEventType 6. "Which components would you like to generate?" - Consumer -7. "For which topic?" - Any choice (choosing "Custom topic name" will add another question "What is the topic name?") +7. "Which topic for someEventType?" - Any choice (choosing "Custom topic name" will add another question "What is the topic name for someEventType?") 8. "What is the consumer polling timeout (in ms)?" - Your answer or '10000' by default (global for all consumers) 9. "Define the auto offset reset policy?" - Your answer or 'earliest' by default (global for all consumers) 10. "Do you want to continue adding consumers or producers?" - Your answer or 'N' par default @@ -71,7 +72,8 @@ After following the first 3 steps of the [basic usage](README.md#basic-usage) ab 4. "For which entity (class name)?" - No entity (will be typed String) 5. "How would you prefix your objects (no entity, for instance: [SomeEventType]Consumer|Producer...)?" - someEventType 6. "Which components would you like to generate?" - Producer -7. "For which topic?" - Any choice (choosing "Custom topic name" will add another question "What is the topic name?") -8. "Do you want to continue adding consumers or producers?" - Your answer or 'N' par default -9. Overwrite all files in conflict -10. `SomeEventTypeProducer` (produces `String`) is available with a `SomeEventTypeSerializer` and a `SomeEventTypeKafkaResource` to [help testing](README.md#test-consumers-and-producers) +7. "Which topic for someEventType?" - Any choice (choosing "Custom topic name" will add another question "What is the topic name for someEventType?") +8. "Do you want to send ordered messages for (entity/prefix) production?" - Your answer or 'y' by default +9. "Do you want to continue adding consumers or producers?" - Your answer or 'N' par default +10. Overwrite all files in conflict +11. `SomeEventTypeProducer` (produces `String`) is available with a `SomeEventTypeSerializer` and a `SomeEventTypeKafkaResource` to [help testing](README.md#test-consumers-and-producers) diff --git a/generators/app/files.js b/generators/app/files.js index a8fab86..90ec48c 100644 --- a/generators/app/files.js +++ b/generators/app/files.js @@ -39,6 +39,7 @@ function initVariables(generator) { generator.topics = generator.props.topics; generator.pollingTimeout = generator.props.pollingTimeout; generator.autoOffsetResetPolicy = generator.props.autoOffsetResetPolicy; + generator.entitiesOrder = generator.props.entitiesOrder; // show all variables generator.log('\n--- some config read from config ---'); @@ -152,6 +153,8 @@ function writeFiles(generator) { generator.entityClass = entity; generator.camelCaseEntityClass = _.camelCase(entity); generator.type = useEntityAsType ? entity : 'String'; + generator.entityOrdered = generator.props.entitiesOrder[entity]; + generateSerdeFiles(generator, entity, useEntityAsType); if (haveComponentForEntity(constants.CONSUMER_COMPONENT, entity)) { diff --git a/generators/app/index.js b/generators/app/index.js index dcb036e..b727b6b 100644 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -25,7 +25,8 @@ module.exports = class extends BaseGenerator { componentsByEntityConfig: [], componentsPrefixes: [], topics: [], - cleanup: false || this.options['skip-prompts'] + cleanup: false || this.options['skip-prompts'], + entitiesOrder: [] }; this.setupClientOptions(this); diff --git a/generators/app/prompts.js b/generators/app/prompts.js index 7b94c3f..ca5e419 100644 --- a/generators/app/prompts.js +++ b/generators/app/prompts.js @@ -251,6 +251,11 @@ function askForEntityComponentsOperations(generator, done) { return availableComponents; }; + let name = generator.props.currentEntity; + if (generator.props.currentEntity === constants.NO_ENTITY) { + name = generator.props.currentPrefix; + } + const unitaryEntityPrompt = [ { when: generator.props.currentEntity, @@ -271,7 +276,7 @@ function askForEntityComponentsOperations(generator, done) { response.currentEntityComponents.includes(constants.PRODUCER_COMPONENT), type: 'list', name: 'topic', - message: 'For which topic?', + message: `Which topic for ${name}?`, choices: topicsChoices(generator, previousConfiguration(generator, generator.props.cleanup)), default: constants.DEFAULT_TOPIC }, @@ -279,7 +284,7 @@ function askForEntityComponentsOperations(generator, done) { when: response => response.topic === constants.CUSTOM_TOPIC, type: 'input', name: 'topicName', - message: 'What is the topic name?', + message: `What is the topic name for ${name}?`, validate: input => validateTopic(input) }, { @@ -304,6 +309,13 @@ function askForEntityComponentsOperations(generator, done) { choices: offsetChoices(), default: constants.EARLIEST_OFFSET }, + { + when: response => response.currentEntityComponents.includes(constants.PRODUCER_COMPONENT), + type: 'confirm', + name: 'sendOrderedMessages', + message: `Do you want to send ordered messages for ${name} production?`, + default: true + }, { type: 'confirm', name: 'continueAddingEntitiesComponents', @@ -322,8 +334,6 @@ function askForEntityComponentsOperations(generator, done) { generator.props.topics = []; } - pushTopicName(generator, answers.topic, answers.topicName); - if (answers.currentEntityComponents && answers.currentEntityComponents.length > 0) { if (generator.props.currentEntity === constants.NO_ENTITY) { pushComponentsByEntity( @@ -336,6 +346,8 @@ function askForEntityComponentsOperations(generator, done) { } } + pushTopicName(generator, answers.topic, answers.topicName); + if (answers.pollingTimeout) { generator.props.pollingTimeout = +answers.pollingTimeout; // force conversion to int } @@ -344,6 +356,18 @@ function askForEntityComponentsOperations(generator, done) { generator.props.autoOffsetResetPolicy = answers.autoOffsetResetPolicy; } + if (answers.sendOrderedMessages) { + if (generator.props.currentEntity === constants.NO_ENTITY) { + pushEntitiesOrder( + generator, + utils.transformToJavaClassNameCase(generator.props.currentPrefix), + answers.sendOrderedMessages + ); + } else { + pushEntitiesOrder(generator, generator.props.currentEntity, answers.sendOrderedMessages); + } + } + if (answers.continueAddingEntitiesComponents) { askForEntityOperations(generator, done); } else { @@ -355,6 +379,11 @@ function askForEntityComponentsOperations(generator, done) { }); } +function pushEntitiesOrder(generator, name, sendOrderedMessages) { + generator.props.entitiesOrder.push(name); + generator.props.entitiesOrder[name] = sendOrderedMessages; +} + function pushComponentsByEntity(generator, currentEntityComponents, entity) { generator.props.componentsByEntityConfig.push(entity); if (generator.props.componentsByEntityConfig[entity]) { diff --git a/generators/app/templates/src/main/java/package/service/kafka/producer/EntityProducer.java.ejs b/generators/app/templates/src/main/java/package/service/kafka/producer/EntityProducer.java.ejs index 220f22a..e9faf40 100644 --- a/generators/app/templates/src/main/java/package/service/kafka/producer/EntityProducer.java.ejs +++ b/generators/app/templates/src/main/java/package/service/kafka/producer/EntityProducer.java.ejs @@ -34,6 +34,12 @@ import <%= packageName %>.domain.<%= entityClass %>; @Service public class <%= entityClass %>Producer { +<%_ if (entityOrdered) { _%> + enum <%= entityClass %>ProducerKey { + <%= _.toUpper(entityClass) %>_CREATE, <%= _.toUpper(entityClass) %>_READ, <%= _.toUpper(entityClass) %>_UPDATE, <%= _.toUpper(entityClass) %>_DELETE + } + +<%_ } _%> private final Logger log = LoggerFactory.getLogger(<%= entityClass %>Producer.class); private final KafkaProducer> kafkaProducer; @@ -51,7 +57,11 @@ public class <%= entityClass %>Producer { } public void send(final <%= type %> message) { +<%_ if (entityOrdered) { _%> + final ProducerRecord> record = new ProducerRecord<>(topicName, <%= entityClass %>ProducerKey.<%= _.toUpper(entityClass) %>_READ.toString(), message); +<%_ } else { _%> final ProducerRecord> record = new ProducerRecord<>(topicName, message); +<%_ } _%> try { log.info("Sending asynchronously a <%= type %> record to topic: '" + topicName + "'"); kafkaProducer.send(record); diff --git a/test/app.spec.js b/test/app.spec.js index 4c7f1fa..704cdc1 100644 --- a/test/app.spec.js +++ b/test/app.spec.js @@ -74,7 +74,7 @@ describe('JHipster generator kafka', () => { }); }); - describe('with only a producer for a single entity', () => { + describe('with only a producer for a single entity (ordered)', () => { before(done => { helpers .run(path.join(__dirname, '../generators/app')) @@ -84,6 +84,7 @@ describe('JHipster generator kafka', () => { .withPrompts({ currentEntity: FOO_ENTITY, currentEntityComponents: [constants.PRODUCER_COMPONENT], + sendOrderedMessages: true, continueAddingEntitiesComponents: false }) .on('end', done); @@ -129,6 +130,37 @@ describe('JHipster generator kafka', () => { const entityTestYmlConsumerBlock = testApplicationYml.kafka.consumer; assert.strictEqual(entityTestYmlConsumerBlock, undefined); }); + + it('should send ordered messages', () => { + assert.fileContent( + `${jhipsterConstants.SERVER_MAIN_SRC_DIR}com/mycompany/myapp/service/kafka/producer/${FOO_ENTITY}Producer.java`, + new RegExp(`enum ${FOO_ENTITY}ProducerKey`, 'g') + ); + }); + }); + + describe('with only a producer for a single entity (not ordered)', () => { + before(done => { + helpers + .run(path.join(__dirname, '../generators/app')) + .inTmpDir(dir => { + fse.copySync(path.join(__dirname, '../test/templates/message-broker-with-entities-1st-call'), dir); + }) + .withPrompts({ + currentEntity: FOO_ENTITY, + currentEntityComponents: [constants.PRODUCER_COMPONENT], + sendOrderedMessages: false, + continueAddingEntitiesComponents: false + }) + .on('end', done); + }); + + it('should not send ordered messages', () => { + assert.noFileContent( + `${jhipsterConstants.SERVER_MAIN_SRC_DIR}com/mycompany/myapp/service/kafka/producer/${FOO_ENTITY}Producer.java`, + new RegExp(`enum ${FOO_ENTITY}ProducerKey`, 'g') + ); + }); }); describe('with only a consumer for a single entity', () => {