EasyRest-NAS
中文文档地址:
EasyRest integration with Netty, Akka and Spring.
This is the high performance RESTful framework, designed for the fast development. Easy to cluster and distributed. You can focus on your business logic.
No tomcat, no web.xml, only need a jar package with 'main' function, you can get a prefect distributed system.
You can don't know Netty, Akka even Spring. You can still using this framework.
Quick start:
The rest definition
@BindURL("/rest/{TENANT}/stock")
public interface StockInfoRest {
@Post("/personal/{USER_ID}/favorite/{CODE}")
void addFavorite(String TENANT, String USER_ID, String CODE, long time);
@Post
ResponseEntity addStocks(int userNumber, String userName, List stockList);
@Get("/personal/{USER_ID}/favorite/list")
List getStockList(String USER_ID);
}
The Service is the bean of spring, you can integrate with spring
@Service
public class StockInfoRestController implements StockInfoRest {
@Override
public void addFavorite(String TENANT, String USER_ID, String CODE, long time) {
System.out.println(TENANT + " " + USER_ID + " " + CODE + " " + time);
}
@Override
@AllDefined
public ResponseEntity addStocks(int userNumber, String userName, List stockList) {
return ResponseEntity.buildOkResponse(Lists.asList(userNumber, userName, new List[]{stockList}));
}
@Override
public List getStockList(String USER_ID) {
return Lists.newArrayList(new Stock(100000, "stock1"), new Stock(100001, "stock2"), new Stock(100002, "stock3"));
}
}
The main class.
public class Example {
public static void main(String[] args) {
EasyRest easyRest = new EasyRest("classpath:MyExampleApplicationContext.xml");
easyRest.startup("EasyRestServer");
}
}
An empty spring config file
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
@BindURL("/rest/{TENANT}/stock") will bind this endpoint at "/rest/{TENANT}/stock"
@AllDefined will check all parameters not be null, if any parameter is null, the framework will reject the request directly.
@Service is spring annotation, that will create bean by spring.
ResponseEntity is the generic response entity, you can put any thing you want in it.
If you have own spring properties, you can create EasyRest by
EasyRest easyRest = new EasyRest("classpath:MyApplicationContext-01.xml", "classpath:MyApplicationContext-02.xml"...);
All over the rest interface will be detected by EasyRest automatically, you just need start the server.
easyRest.startup("EasyRestServer");
REST CALL EXAMPLE
Methd 1
@Post("/personal/{USER_ID}/favorite/{CODE}")
void addFavorite(String TENANT, String USER_ID, String CODE, long time);
Call it at:
Content-Type is 'application/json'
POST body is:
{"time":1524827542}
The output is:
100000001 001 100001 1524827542
And the response is:
{
"code": "1",
"message": "ok"
}
Methd 2
@Post
@AllDefined
ResponseEntity addStocks(int userNumber, String userName, List stockList);
Call it at:
Content-Type is 'application/json'
POST body is:
{"userNumber":1, "userName":"Louie", "stockList":[{"code":100001, "name":"stock1"}, {"code":100002, "name":"stock2"}]}
And the response is:
{
"code": "1",
"data": [
1,
"Louie",
[
{
"code": 100001,
"name": "stock1"
},
{
"code": 100002,
"name": "stock2"
}
]
]
}
The method has annotation @AllDefined, so if any one of the parameter is missing, e.g. "userName". The response will be:
{
"code": "-1",
"message": "Failed",
"data": {
"errorType": "ParameterNotFoundException",
"errorMessage": "userName is not defined."
}
}
Methd 3
@Get("/personal/{USER_ID}/favorite/list")
List getStockList(String USER_ID);
Call it at:
And the response is:
[
{
"code": 100000,
"name": "stock1"
},
{
"code": 100001,
"name": "stock2"
},
{
"code": 100002,
"name": "stock3"
}
]
For the content type, 'multipart/form-data' is also supported.
Distributed is supported and very easily.
Distribute service example
All of the code are in the Example module.
- Example-Distributed-Service-1
- example-service-1-api
- example-service-1-main
- Example-Distributed-Service-2
- example-service-2-api
- example-service-2-main
- Example-Distributed-Service-Model
Example-Distributed-Service-1 will get request from the rest call, then will invoke Example-Distributed-Service-2 to create a People and response the rest call with this People.
Example-Distributed-Service-Model
People class
public class People {
private String name;
private int age;
private long birthday;
private List skills;
private People boss;
public People(String name, int age, long birthday, List skills, People boss) {
this.name = name;
this.age = age;
this.birthday = birthday;
this.skills = skills;
this.boss = boss;
}
}
Example-Distributed-Service-1
example-service-1-api
Interface definition
@BindURL("/service1")
public interface Service1 {
@Post
@AllDefined
ResponseEntity createPeople(String name, int age, long birthday, List skills, People boss);
}
example-service-1-main
Interface Implement
@Service
public class Service1Impl implements Service1 {
@Override
public ResponseEntity createPeople(String name, int age, long birthday, List skills, People boss) {
Service2 service2 = EasyRestServiceLookup.lookup(Service2.class);
return ResponseEntity.buildOkResponse(service2.getPeople(name, age, birthday, skills, boss));
}
}
EasyRestServiceLookup has a static method lookup. You can use this method to get any service instance, even this service on the other server!
The main class
public class Startup {
private static String systemName = "example-service-1";
public static void main(String[] args) throws IOException {
EasyRestDistributedServiceBind.loadConfiguration(Startup.class.getClassLoader().getResourceAsStream("services-mapping-01.json"));
EasyRest easyRest = new EasyRest("classpath:MyExampleApplicationContext-01.xml");
easyRest.startup(systemName, new NettyInit(8001));
}
}
EasyRestDistributedServiceBind.loadConfiguration(Startup.class.getClassLoader().getResourceAsStream("services-mapping-01.json")); will load the service mapping for the framework.
akka config file: application.conf
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
transport = "akka.remote.netty.NettyRemoteTransport"
netty {
tcp {
hostname = "127.0.0.1"
port = 2551
}
}
}
}
Akka system will detect this file, and open the port to listening remote request.
Distributed service mapping file:(services-mapping-01.json)
{
"self": {
"akkaSystemName": "example-service-1",
"host": "127.0.0.1",
"port": "2551"
},
"services" : [
{
"akkaSystemName": "example-service-1",
"host": "127.0.0.1",
"port": "2551"
},
{
"akkaSystemName": "example-service-2",
"host": "127.0.0.1",
"port": "2552"
}
]
}
Service mapping file only need two fields: Self to record local system info. Services is an array to record all system info, including self.
The akkaSystemName must be the same with systemName in Main class.
An empty spring config file:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
Example-Distributed-Service-2
example-service-2-api
Interface definition
@BindURL("/service2")
public interface Service2 {
@Get
People getPeople(String name, int age, long birthday, List skills, People boss);
}
example-service-2-main
Interface Implement
@Service
public class Service2Impl implements Service2 {
@Override
public People getPeople(String name, int age, long birthday, List skills, People boss) {
return new People(name, age, birthday, skills, boss);
}
}
The main class
public class Startup {
private static String systemName = "example-service-2";
public static void main(String[] args) throws IOException {
EasyRestDistributedServiceBind.loadConfiguration(Startup.class.getClassLoader().getResourceAsStream("services-mapping-02.json"));
EasyRest easyRest = new EasyRest("classpath:MyExampleApplicationContext-02.xml");
easyRest.startup(systemName, new NettyInit(8002));
}
}
akka config file: application.conf
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
transport = "akka.remote.netty.NettyRemoteTransport"
netty {
tcp {
hostname = "127.0.0.1"
port = 2552
}
}
}
}
Distributed service mapping file:(services-mapping-02.json)
{
"self": {
"akkaSystemName": "example-service-2",
"host": "127.0.0.1",
"port": "2552"
},
"services" : [
{
"akkaSystemName": "example-service-1",
"host": "127.0.0.1",
"port": "2551"
},
{
"akkaSystemName": "example-service-2",
"host": "127.0.0.1",
"port": "2552"
}
]
}
An empty spring config file:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
Start Service 1 and Service 2.
When you see this log on each console:
[example-service-1-akka.actor.default-dispatcher-5] INFO com.easyrest.utils.LogUtils - From com.easyrest.actors.remote.RemoteServiceExchangeActor: Service mapping init success.
[example-service-1-akka.actor.default-dispatcher-5] INFO com.easyrest.utils.LogUtils - example-service-2 is running on the port 8001.
[example-service-2-akka.actor.default-dispatcher-3] INFO com.easyrest.utils.LogUtils - From com.easyrest.actors.remote.RemoteServiceExchangeActor: Service mapping init success.
[example-service-2-akka.actor.default-dispatcher-3] INFO com.easyrest.utils.LogUtils - example-service-2 is running on the port 8002.
That means services are ready now!
Now, We will invoke the service1 via rest call.
http://127.0.0.1:8001/service1/createPeople Content-Type:application/json Body: {"name":"Louie", "age":18, "birthday":763401600, "skills":["java", "netty", "akka", "spring"], "boss":{"name":"Louie_B", "age":18, "birthday":763401600}}
And then we can get response:
{
"code": "1",
"data": {
"name": "Louie",
"age": 18,
"birthday": 763401600,
"skills": [
"java",
"netty",
"akka",
"spring"
],
"boss": {
"name": "Louie_B",
"age": 18,
"birthday": 763401600
}
}
}
That's work!
Not the end...
TODO
Support callback