CH04 Spring Data Common

Spring Data ๊ตฌ์„ฑ ๋ฐ ํ•™์Šต์ˆœ์„œ

๊น€์˜ํ•œ๋‹˜ ๋„์„œ ๋ฐ ๊ฐ•์˜ ์ •๋ฆฌ์—์„œ ์ด๋ฏธ ๋‹ค๋ฃฌ ๋‚ด์šฉ๋“ค์ด๋ผ์„œ ์ž์„ธํ•œ ์ •๋ฆฌ๋Š” ์ƒ๋žตํ•œ๋‹ค.

AbstractAggregateRoot ์ด์šฉํ•˜์—ฌ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ

๊ธฐ์กด์— ApplicationEventPublisher ๋ฅผ ํ†ตํ•ด์„œ ์ˆ˜ํ–‰ํ•œ Event ๋ฐœํ–‰ ๋ฐฉ์‹์„ AbstractAggregateRoot ๋ฅผ ์ด์šฉํ•ด์„œ ๋„๋ฉ”์ธ ๋‚ด์—์„œ ๋ฐ”๋กœ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

/**
 * Convenience base class for aggregate roots that exposes a {@link #registerEvent(Object)} to capture domain events and
 * expose them via {@link #domainEvents()}. The implementation is using the general event publication mechanism implied
 * by {@link DomainEvents} and {@link AfterDomainEventPublication}. If in doubt or need to customize anything here,
 * rather build your own base class and use the annotations directly.
 *
 * @author Oliver Gierke
 * @author Christoph Strobl
 * @since 1.13
 */
public class AbstractAggregateRoot<A extends AbstractAggregateRoot<A>> {

	private transient final @Transient List<Object> domainEvents = new ArrayList<>();

	/**
	 * Registers the given event object for publication on a call to a Spring Data repository's save methods.
	 *
	 * @param event must not be {@literal null}.
	 * @return the event that has been added.
	 * @see #andEvent(Object)
	 */
	protected <T> T registerEvent(T event) {

		Assert.notNull(event, "Domain event must not be null");

		this.domainEvents.add(event);
		return event;
	}

	/**
	 * Clears all domain events currently held. Usually invoked by the infrastructure in place in Spring Data
	 * repositories.
	 */
	@AfterDomainEventPublication
	protected void clearDomainEvents() {
		this.domainEvents.clear();
	}

	/**
	 * All domain events currently captured by the aggregate.
	 */
	@DomainEvents
	protected Collection<Object> domainEvents() {
		return Collections.unmodifiableList(domainEvents);
	}

	/**
	 * Adds all events contained in the given aggregate to the current one.
	 *
	 * @param aggregate must not be {@literal null}.
	 * @return the aggregate
	 */
	@SuppressWarnings("unchecked")
	protected final A andEventsFrom(A aggregate) {

		Assert.notNull(aggregate, "Aggregate must not be null");

		this.domainEvents.addAll(aggregate.domainEvents());

		return (A) this;
	}

	/**
	 * Adds the given event to the aggregate for later publication when calling a Spring Data repository's save-method.
	 * Does the same as {@link #registerEvent(Object)} but returns the aggregate instead of the event.
	 *
	 * @param event must not be {@literal null}.
	 * @return the aggregate
	 * @see #registerEvent(Object)
	 */
	@SuppressWarnings("unchecked")
	protected final A andEvent(Object event) {

		registerEvent(event);

		return (A) this;
	}
}

์ˆ˜ํ–‰ํ•˜๋Š” ๊ณผ์ •์— domainEvents() ์— ์ž„์‹œ๋กœ ๋‹ด๊ฒผ๋‹ค๊ฐ€ ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์ดํ›„์—๋Š” ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด์„œ clearDomainEvents() ๋ฅผ ํ†ตํ•ด ์ œ๊ฑฐ๋œ๋‹ค. ์œ„ ๋ชจ๋“  ๊ณผ์ •์€ save() ์™€ ๋™์‹œ์— ๋ฐœ์ƒ๋œ๋‹ค.

@Entity
public class Post extends AbstractAggregateRoot<Post> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String description;

    public void setName(String name) {
        this.name = name;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    // ์—ฌ๊ธฐ์„œ event๋ฅผ AbstractAggregateRoot๋ฅผ ํ†ตํ•ด์„œ ๋“ฑ๋กํ•œ๋‹ค. 
    public void registerEvent() {
        PostPublishedEvent postPublishedEvent = new PostPublishedEvent(this);
        this.registerEvent(postPublishedEvent);
    }
}
    @Override
    @Transactional
    public void run(ApplicationArguments args) throws Exception {
        Post post = new Post();
        post.setName("event publish test");
        post.setDescription("event description");

        post.registerEvent(); // <-- ๋„๋ฉ”์ธ ๋‚ด์—์„œ ์„ ์–ธํ•œ event ๋“ฑ๋ก์„ ํ•˜๋ฉด
        postRepository.save(post); // <-- save ์‹œ์ ์— publish ๋œ๋‹ค.
    }

์œ„์™€ ๊ฐ™์ด ์ฒ˜๋ฆฌํ•˜๊ธฐ์ „์— ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก์„ ํ•ด๋‘๊ณ  ์›ํ•˜๋Š” ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌ์„ฑํ•ด๋‘๋ฉด ๋œ๋‹ค. ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์€ DDD ๊ตฌํ˜„์— ๋” ์šฉ์ดํ•œ ๊ฒƒ ๊ฐ™๋‹ค.

Spring Data Common Web ๊ธฐ๋Šฅ

Spring Data Common ์ด ์ œ๊ณตํ•ด์ฃผ๋Š” Web support ๊ธฐ๋Šฅ๋“ค์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณธ๋‹ค.

Domain Class Converter

public class DomainClassConverter<T extends ConversionService & ConverterRegistry>
		implements ConditionalGenericConverter, ApplicationContextAware

๊ฐ€ Converter Registry ์— ๋“ค์–ด๊ฐ€์„œ ํ•ธ๋“ค๋Ÿฌ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ปจ๋ฒ„ํŒ… ํ•˜๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉ๋œ๋‹ค. ๋‚ด๋ถ€์ ์œผ๋กœ DomainClassConverter๋‚ด์— ์„ ์–ธ๋œ ์•„๋ž˜ ๋‘ ์ปจ๋ฒ„ํ„ฐ๊ฐ€ ์‚ฌ์šฉ๋œ๋‹ค. ์ปจ๋ฒ„ํŒ… ๊ณผ์ •์—์„œ Repository ๋ฅผ ์‚ฌ์šฉํ•ด ์กฐํšŒํ•œ๋‹ค.

	private Optional<ToEntityConverter> toEntityConverter = Optional.empty();
	private Optional<ToIdConverter> toIdConverter = Optional.empty();

๊ทธ๋ž˜์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ํ•ธ๋“ค๋Ÿฌ์—์„œ ๋ฐ”๋กœ entity ์กฐํšŒ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

    @GetMapping("/posts/{id}")
    public String getAPost(@PathVariable Long id) {
        Optional<Post> byId = postRepository.findById(id);
        Post post = byId.get();
        return post.getTitle();
    }
    @GetMapping("/posts/{id}")
    public String getAPost(@PathVariable(โ€œidโ€) Post post) {
        return post.getTitle();
    }

HandlerMethodArgumentResolver

ํ•ธ๋“ค๋Ÿฌ์—์„œ Pageable ์„ ๊ตฌ์„ฑํ•˜๋Š” ์•„๋ž˜ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋“ค์–ด์˜จ๋‹ค๋Š” ์ „์ œํ•˜์— ๋ฐ”๋กœ Pageable ๊ฐ์ฒด๋ฅผ ๋ฐ”๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

  • page

  • size

  • sort(ex. sort=createdAt,desc)

    @GetMapping("/posts")
    public void testHandler(Pageable pageable){
        
    }

Last updated